mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-17 15:53:28 +00:00
Files view mode sort (#6210)
* ['Files'] added 'view mode' options & added 'grid' mode for 'My Libraries' & 'Shared with me' * ['Files'] added 'grid' mode for 'Shared with all' & 'department/group'; redesigned the empty tip for 'grid' mode; replaced 'star/unstar/monitored' icons * ['Files'] added 'sort'(WIP) * ['Files' page] added 'sort' for all the libraries
This commit is contained in:
@@ -1,22 +1,20 @@
|
|||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { UncontrolledTooltip } from 'reactstrap';
|
import { UncontrolledTooltip } from 'reactstrap';
|
||||||
import Icon from '../components/icon';
|
|
||||||
import { gettext } from '../utils/constants';
|
import { gettext } from '../utils/constants';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
repoID: PropTypes.string.isRequired
|
repoID: PropTypes.string.isRequired,
|
||||||
|
className: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
class RepoMonitoredIcon extends React.Component {
|
class RepoMonitoredIcon extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { repoID } = this.props;
|
const { repoID, className } = this.props;
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<span id={`watching-${repoID}`} className="ml-1">
|
<i id={`watching-${repoID}`} className={`sf3-font-monitor sf3-font ${className ? className : ''}`}></i>
|
||||||
<Icon symbol='monitor' />
|
|
||||||
</span>
|
|
||||||
<UncontrolledTooltip
|
<UncontrolledTooltip
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
target={`#watching-${repoID}`}
|
target={`#watching-${repoID}`}
|
||||||
|
@@ -22,6 +22,7 @@ import RepoShareAdminDialog from '../dialog/repo-share-admin-dialog';
|
|||||||
import RepoMonitoredIcon from '../../components/repo-monitored-icon';
|
import RepoMonitoredIcon from '../../components/repo-monitored-icon';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
|
currentViewMode: PropTypes.string,
|
||||||
currentGroup: PropTypes.object,
|
currentGroup: PropTypes.object,
|
||||||
libraryType: PropTypes.string,
|
libraryType: PropTypes.string,
|
||||||
repo: PropTypes.object.isRequired,
|
repo: PropTypes.object.isRequired,
|
||||||
@@ -140,9 +141,10 @@ class SharedRepoListItem extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
getRepoComputeParams = () => {
|
getRepoComputeParams = () => {
|
||||||
let repo = this.props.repo;
|
const { repo, currentViewMode } = this.props;
|
||||||
|
|
||||||
let iconUrl = Utils.getLibIconUrl(repo);
|
const useBigLibaryIcon = currentViewMode == 'grid';
|
||||||
|
const iconUrl = Utils.getLibIconUrl(repo, useBigLibaryIcon);
|
||||||
let iconTitle = Utils.getLibIconTitle(repo);
|
let iconTitle = Utils.getLibIconTitle(repo);
|
||||||
let libPath = `${siteRoot}library/${repo.repo_id}/${Utils.encodePath(repo.repo_name)}/`;
|
let libPath = `${siteRoot}library/${repo.repo_id}/${Utils.encodePath(repo.repo_name)}/`;
|
||||||
|
|
||||||
@@ -630,31 +632,63 @@ class SharedRepoListItem extends React.Component {
|
|||||||
|
|
||||||
renderPCUI = () => {
|
renderPCUI = () => {
|
||||||
let { iconUrl, iconTitle, libPath } = this.getRepoComputeParams();
|
let { iconUrl, iconTitle, libPath } = this.getRepoComputeParams();
|
||||||
let { repo } = this.props;
|
const { repo, currentViewMode } = this.props;
|
||||||
return (
|
return currentViewMode == 'list' ? (
|
||||||
<Fragment>
|
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseOver={this.onMouseOver} onMouseLeave={this.onMouseLeave} onFocus={this.onMouseEnter}>
|
||||||
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseOver={this.onMouseOver} onMouseLeave={this.onMouseLeave} onFocus={this.onMouseEnter}>
|
<td className="text-center">
|
||||||
<td className="text-center">
|
<i
|
||||||
<a href="#" role="button" aria-label={this.state.isStarred ? gettext('Unstar') : gettext('Star')} onClick={this.onToggleStarRepo}>
|
role="button"
|
||||||
<i className={`fa-star ${this.state.isStarred ? 'fas' : 'far star-empty'}`}></i>
|
title={this.state.isStarred ? gettext('Unstar') : gettext('Star')}
|
||||||
</a>
|
aria-label={this.state.isStarred ? gettext('Unstar') : gettext('Star')}
|
||||||
</td>
|
onClick={this.onToggleStarRepo}
|
||||||
<td><img src={iconUrl} title={iconTitle} alt={iconTitle} width="24" /></td>
|
className={`op-icon m-0 ${this.state.isStarred ? 'sf3-font-star' : 'sf3-font-star-empty'} sf3-font`}
|
||||||
<td>
|
>
|
||||||
{this.state.isRenaming ?
|
</i>
|
||||||
<Rename name={repo.repo_name} onRenameConfirm={this.onRenameConfirm} onRenameCancel={this.onRenameCancel}/> :
|
</td>
|
||||||
<Fragment>
|
<td><img src={iconUrl} title={iconTitle} alt={iconTitle} width="24" /></td>
|
||||||
<Link to={libPath}>{repo.repo_name}</Link>
|
<td>
|
||||||
{repo.monitored && <RepoMonitoredIcon repoID={repo.repo_id} />}
|
{this.state.isRenaming ?
|
||||||
</Fragment>
|
<Rename name={repo.repo_name} onRenameConfirm={this.onRenameConfirm} onRenameCancel={this.onRenameCancel}/> :
|
||||||
}
|
<Fragment>
|
||||||
</td>
|
<Link to={libPath}>{repo.repo_name}</Link>
|
||||||
<td>{this.state.isOperationShow && this.generatorPCMenu()}</td>
|
{repo.monitored && <RepoMonitoredIcon repoID={repo.repo_id} className="ml-1 op-icon" />}
|
||||||
<td>{repo.size}</td>
|
</Fragment>
|
||||||
<td title={moment(repo.last_modified).format('llll')}>{moment(repo.last_modified).fromNow()}</td>
|
}
|
||||||
<td title={repo.owner_contact_email}>{repo.owner_name}</td>
|
</td>
|
||||||
</tr>
|
<td>{this.state.isOperationShow && this.generatorPCMenu()}</td>
|
||||||
</Fragment>
|
<td>{repo.size}</td>
|
||||||
|
<td title={moment(repo.last_modified).format('llll')}>{moment(repo.last_modified).fromNow()}</td>
|
||||||
|
<td title={repo.owner_contact_email}>{repo.owner_name}</td>
|
||||||
|
</tr>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className="library-grid-item px-3 d-flex justify-content-between align-items-center"
|
||||||
|
onMouseEnter={this.onMouseEnter}
|
||||||
|
onMouseLeave={this.onMouseLeave}
|
||||||
|
onFocus={this.onMouseEnter}
|
||||||
|
>
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<img src={iconUrl} title={iconTitle} alt={iconTitle} width="36" className="mr-2" />
|
||||||
|
{this.state.isRenaming ?
|
||||||
|
<Rename name={repo.repo_name} onRenameConfirm={this.onRenameConfirm} onRenameCancel={this.onRenameCancel}/> :
|
||||||
|
<Fragment>
|
||||||
|
<Link to={libPath}>{repo.repo_name}</Link>
|
||||||
|
<i
|
||||||
|
role="button"
|
||||||
|
title={this.state.isStarred ? gettext('Unstar') : gettext('Star')}
|
||||||
|
aria-label={this.state.isStarred ? gettext('Unstar') : gettext('Star')}
|
||||||
|
onClick={this.onToggleStarRepo}
|
||||||
|
className={`op-icon library-grid-item-icon ${this.state.isStarred ? 'sf3-font-star' : 'sf3-font-star-empty'} sf3-font`}
|
||||||
|
>
|
||||||
|
</i>
|
||||||
|
{repo.monitored && <RepoMonitoredIcon repoID={repo.repo_id} className="op-icon library-grid-item-icon" />}
|
||||||
|
</Fragment>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{this.state.isOperationShow && this.generatorPCMenu()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -677,7 +711,7 @@ class SharedRepoListItem extends React.Component {
|
|||||||
<Rename name={repo.repo_name} onRenameConfirm={this.onRenameConfirm} onRenameCancel={this.onRenameCancel} /> :
|
<Rename name={repo.repo_name} onRenameConfirm={this.onRenameConfirm} onRenameCancel={this.onRenameCancel} /> :
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Link to={libPath}>{repo.repo_name}</Link>
|
<Link to={libPath}>{repo.repo_name}</Link>
|
||||||
{repo.monitored && <RepoMonitoredIcon repoID={repo.repo_id} />}
|
{repo.monitored && <RepoMonitoredIcon repoID={repo.repo_id} className="ml-1 op-icon" />}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
}
|
}
|
||||||
<br />
|
<br />
|
||||||
|
@@ -8,6 +8,7 @@ import LibsMobileThead from '../libs-mobile-thead';
|
|||||||
import Loading from '../loading';
|
import Loading from '../loading';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
|
currentViewMode: PropTypes.string,
|
||||||
libraryType: PropTypes.string,
|
libraryType: PropTypes.string,
|
||||||
currentGroup: PropTypes.object,
|
currentGroup: PropTypes.object,
|
||||||
isShowTableThread: PropTypes.bool,
|
isShowTableThread: PropTypes.bool,
|
||||||
@@ -84,6 +85,7 @@ class SharedRepoListView extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
renderRepoListView = () => {
|
renderRepoListView = () => {
|
||||||
|
const { currentViewMode = 'list' } = this.props;
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
{this.props.repoList.map(repo => {
|
{this.props.repoList.map(repo => {
|
||||||
@@ -100,6 +102,7 @@ class SharedRepoListView extends React.Component {
|
|||||||
onItemDelete={this.props.onItemDelete}
|
onItemDelete={this.props.onItemDelete}
|
||||||
onItemRename={this.props.onItemRename}
|
onItemRename={this.props.onItemRename}
|
||||||
onMonitorRepo={this.props.onMonitorRepo}
|
onMonitorRepo={this.props.onMonitorRepo}
|
||||||
|
currentViewMode={currentViewMode}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -108,10 +111,10 @@ class SharedRepoListView extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
renderPCUI = () => {
|
renderPCUI = () => {
|
||||||
const { theadHidden = false } = this.props;
|
const { theadHidden = false, currentViewMode = 'list' } = this.props;
|
||||||
const { sortByName, sortByTime, sortBySize, sortIcon } = this.getSortMetaData();
|
const { sortByName, sortByTime, sortBySize, sortIcon } = this.getSortMetaData();
|
||||||
|
|
||||||
return (
|
return currentViewMode == 'list' ? (
|
||||||
<table className={theadHidden ? 'table-thead-hidden' : ''}>
|
<table className={theadHidden ? 'table-thead-hidden' : ''}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -128,6 +131,10 @@ class SharedRepoListView extends React.Component {
|
|||||||
{this.renderRepoListView()}
|
{this.renderRepoListView()}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
) : (
|
||||||
|
<div className="d-flex justify-content-between flex-wrap">
|
||||||
|
{this.renderRepoListView()}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -4,8 +4,10 @@ import PropTypes from 'prop-types';
|
|||||||
import '../css/single-selector.css';
|
import '../css/single-selector.css';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
isDropdownToggleShown: PropTypes.bool.isRequired,
|
customSelectorToggle: PropTypes.object,
|
||||||
currentSelectedOption: PropTypes.object.isRequired,
|
menuCustomClass: PropTypes.string,
|
||||||
|
isDropdownToggleShown: PropTypes.bool,
|
||||||
|
currentSelectedOption: PropTypes.object,
|
||||||
options: PropTypes.array.isRequired,
|
options: PropTypes.array.isRequired,
|
||||||
selectOption: PropTypes.func.isRequired,
|
selectOption: PropTypes.func.isRequired,
|
||||||
operationBeforeSelect: PropTypes.func,
|
operationBeforeSelect: PropTypes.func,
|
||||||
@@ -63,15 +65,21 @@ class Selector extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { isPopoverOpen } = this.state;
|
const { isPopoverOpen } = this.state;
|
||||||
const { currentSelectedOption, options, isDropdownToggleShown } = this.props;
|
const { currentSelectedOption, options, isDropdownToggleShown, menuCustomClass = '',
|
||||||
|
customSelectorToggle = null
|
||||||
|
} = this.props;
|
||||||
return (
|
return (
|
||||||
<div className="sf-single-selector position-relative">
|
<div className="sf-single-selector position-relative">
|
||||||
<span className="cur-option" onClick={this.onToggleClick}>
|
<div onClick={this.onToggleClick}>
|
||||||
{currentSelectedOption.text}
|
{customSelectorToggle ? customSelectorToggle : (
|
||||||
{isDropdownToggleShown && <i className="fas fa-caret-down ml-2 toggle-icon"></i>}
|
<span className="cur-option">
|
||||||
</span>
|
{currentSelectedOption.text}
|
||||||
|
{isDropdownToggleShown && <i className="fas fa-caret-down ml-2 toggle-icon"></i>}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
{isPopoverOpen && (
|
{isPopoverOpen && (
|
||||||
<div className="options-container position-absolute rounded shadow mt-1" ref={ref => this.selector = ref}>
|
<div className={`options-container position-absolute rounded shadow mt-1 ${menuCustomClass}`} ref={ref => this.selector = ref}>
|
||||||
<ul className="option-list list-unstyled py-3 o-auto">
|
<ul className="option-list list-unstyled py-3 o-auto">
|
||||||
{options.map((item, index) => {
|
{options.map((item, index) => {
|
||||||
return (
|
return (
|
||||||
|
@@ -1,12 +1,43 @@
|
|||||||
|
.repos-sort-menu-toggle:hover {
|
||||||
|
background: #e5e6e7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-single-selector .options-container.repos-sort-menu {
|
||||||
|
min-width: 215px;
|
||||||
|
}
|
||||||
|
|
||||||
#files-content-container td:first-child {
|
#files-content-container td:first-child {
|
||||||
text-align: left!important;
|
text-align: left!important;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#files-content-container .libraries-empty-tip {
|
#files-content-container .libraries-empty-tip-in-list-mode {
|
||||||
color: #a4a4a4;
|
color: #a4a4a4;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 4px 0;
|
padding: 4px 0;
|
||||||
border-bottom: 1px solid #e8e8e8;
|
border-bottom: 1px solid #e8e8e8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#files-content-container .libraries-empty-tip-in-grid-mode {
|
||||||
|
color: #a4a4a4;
|
||||||
|
text-align: center;
|
||||||
|
height: 56px;
|
||||||
|
line-height: 52px;
|
||||||
|
border: 2px dashed #eeeeee;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-grid-item {
|
||||||
|
width: 48%;
|
||||||
|
height: 56px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-grid-item:hover {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-grid-item-icon {
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -12,6 +12,7 @@ import Repo from '../../models/repo';
|
|||||||
import '../../css/groups.css';
|
import '../../css/groups.css';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
|
currentViewMode: PropTypes.string,
|
||||||
group: PropTypes.object.isRequired,
|
group: PropTypes.object.isRequired,
|
||||||
updateGroup: PropTypes.func.isRequired
|
updateGroup: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
@@ -107,14 +108,14 @@ class GroupItem extends React.Component {
|
|||||||
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { group } = this.props;
|
const { group, currentViewMode = 'list' } = this.props;
|
||||||
const { parent_group_id, admins } = group;
|
const { parent_group_id, admins } = group;
|
||||||
const emptyTip = <p className="group-item-empty-tip">{gettext('No libraries')}</p>;
|
const emptyTip = <p className={`libraries-empty-tip-in-${currentViewMode}-mode`}>{gettext('No libraries')}</p>;
|
||||||
|
|
||||||
const isDeptAdmin = parent_group_id != 0 && admins.indexOf(username) > -1;
|
const isDeptAdmin = parent_group_id != 0 && admins.indexOf(username) > -1;
|
||||||
return (
|
return (
|
||||||
<div className="pb-3">
|
<div className="pb-3">
|
||||||
<div className="d-flex justify-content-between mt-3 py-1 sf-border-bottom">
|
<div className={`d-flex justify-content-between mt-3 py-1 ${currentViewMode == 'list' ? 'sf-border-bottom' : ''}`}>
|
||||||
<h4 className="sf-heading m-0 d-flex align-items-center">
|
<h4 className="sf-heading m-0 d-flex align-items-center">
|
||||||
<span className={`${group.parent_group_id == 0 ? 'sf3-font-group' : 'sf3-font-department'} sf3-font nav-icon`} aria-hidden="true"></span>
|
<span className={`${group.parent_group_id == 0 ? 'sf3-font-group' : 'sf3-font-department'} sf3-font nav-icon`} aria-hidden="true"></span>
|
||||||
<a href={`${siteRoot}group/${group.id}/`} title={group.name} className="ellipsis">{group.name}</a>
|
<a href={`${siteRoot}group/${group.id}/`} title={group.name} className="ellipsis">{group.name}</a>
|
||||||
@@ -136,6 +137,7 @@ class GroupItem extends React.Component {
|
|||||||
onItemDelete={this.onItemDelete}
|
onItemDelete={this.onItemDelete}
|
||||||
onItemRename={this.onItemRename}
|
onItemRename={this.onItemRename}
|
||||||
onMonitorRepo={this.onMonitorRepo}
|
onMonitorRepo={this.onMonitorRepo}
|
||||||
|
currentViewMode={currentViewMode}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{this.state.isCreateRepoDialogOpen &&
|
{this.state.isCreateRepoDialogOpen &&
|
||||||
|
@@ -8,13 +8,14 @@ import toaster from '../../components/toast';
|
|||||||
import Repo from '../../models/repo';
|
import Repo from '../../models/repo';
|
||||||
import Group from '../../models/group';
|
import Group from '../../models/group';
|
||||||
import Loading from '../../components/loading';
|
import Loading from '../../components/loading';
|
||||||
|
import Selector from '../../components/single-selector';
|
||||||
import TopToolbar from '../../components/toolbar/top-toolbar';
|
import TopToolbar from '../../components/toolbar/top-toolbar';
|
||||||
import SingleDropdownToolbar from '../../components/toolbar/single-dropdown-toolbar';
|
import SingleDropdownToolbar from '../../components/toolbar/single-dropdown-toolbar';
|
||||||
import SortOptionsDialog from '../../components/dialog/sort-options';
|
import SortOptionsDialog from '../../components/dialog/sort-options';
|
||||||
import GuideForNewDialog from '../../components/dialog/guide-for-new-dialog';
|
import GuideForNewDialog from '../../components/dialog/guide-for-new-dialog';
|
||||||
import CreateRepoDialog from '../../components/dialog/create-repo-dialog';
|
import CreateRepoDialog from '../../components/dialog/create-repo-dialog';
|
||||||
import MylibRepoListView from '../../pages/my-libs/mylib-repo-list-view';
|
import MylibRepoListView from '../../pages/my-libs/mylib-repo-list-view';
|
||||||
import SharedLibs from '../../pages/shared-libs/shared-libs.js';
|
import SharedLibs from '../../pages/shared-libs/shared-libs';
|
||||||
import SharedWithAll from '../../pages/shared-with-all';
|
import SharedWithAll from '../../pages/shared-with-all';
|
||||||
import GroupItem from '../../pages/groups/group-item';
|
import GroupItem from '../../pages/groups/group-item';
|
||||||
|
|
||||||
@@ -28,19 +29,27 @@ const propTypes = {
|
|||||||
class Libraries extends Component {
|
class Libraries extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
this.sortOptions = [
|
||||||
|
{value: 'name-asc', text: gettext('By name ascending')},
|
||||||
|
{value: 'name-desc', text: gettext('By name descending')},
|
||||||
|
{value: 'time-asc', text: gettext('By time ascending')},
|
||||||
|
{value: 'time-desc', text: gettext('By time descending')}
|
||||||
|
];
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
// for 'my libs'
|
// for 'my libs'
|
||||||
errorMsg: '',
|
errorMsg: '',
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
repoList: [],
|
repoList: [],
|
||||||
isSortOptionsDialogOpen: false,
|
isSortOptionsDialogOpen: false,
|
||||||
sortBy: cookie.load('seafile-repo-dir-sort-by') || 'name', // 'name' or 'time' or 'size'
|
|
||||||
sortOrder: cookie.load('seafile-repo-dir-sort-order') || 'asc', // 'asc' or 'desc'
|
|
||||||
isGuideForNewDialogOpen: window.app.pageOptions.guideEnabled,
|
isGuideForNewDialogOpen: window.app.pageOptions.guideEnabled,
|
||||||
groupList: [],
|
groupList: [],
|
||||||
sharedRepoList:[],
|
sharedRepoList:[],
|
||||||
publicRepoList: [],
|
publicRepoList: [],
|
||||||
isCreateRepoDialogOpen: false
|
isCreateRepoDialogOpen: false,
|
||||||
|
currentViewMode: localStorage.getItem('sf_repo_list_view_mode') || 'list',
|
||||||
|
sortBy: localStorage.getItem('sf_repos_sort_by') || 'name', // 'name' or 'time'
|
||||||
|
sortOrder: localStorage.getItem('sf_repos_sort_order') || 'asc', // 'asc' or 'desc'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,21 +58,20 @@ class Libraries extends Component {
|
|||||||
const promiseListGroups = seafileAPI.listGroups(true);
|
const promiseListGroups = seafileAPI.listGroups(true);
|
||||||
Promise.all([promiseListRepos, promiseListGroups]).then(res => {
|
Promise.all([promiseListRepos, promiseListGroups]).then(res => {
|
||||||
const [resListRepos, resListGroups] = res;
|
const [resListRepos, resListGroups] = res;
|
||||||
const allRepoList = resListRepos.data.repos.map((item) => new Repo(item));
|
const repoList = resListRepos.data.repos.map((item) => new Repo(item));
|
||||||
const myRepoList = allRepoList.filter(item => item.type === 'mine');
|
const groups = resListGroups.data.map(item => {
|
||||||
const sharedRepoList = allRepoList.filter(item => item.type === 'shared');
|
|
||||||
const publicRepoList = allRepoList.filter(item => item.type === 'public');
|
|
||||||
const groupList = resListGroups.data.map(item => {
|
|
||||||
let group = new Group(item);
|
let group = new Group(item);
|
||||||
group.repos = item.repos.map(item => new Repo(item));
|
group.repos = item.repos.map(item => new Repo(item));
|
||||||
return group;
|
return group;
|
||||||
}).sort((a, b) => a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1 );
|
}).sort((a, b) => a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1 );
|
||||||
|
const { allRepoList, myRepoList, sharedRepoList, publicRepoList, groupList } = this.sortRepos(repoList, groups);
|
||||||
this.setState({
|
this.setState({
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
allRepoList,
|
||||||
groupList,
|
groupList,
|
||||||
sharedRepoList,
|
sharedRepoList,
|
||||||
publicRepoList,
|
publicRepoList,
|
||||||
repoList: Utils.sortRepos(myRepoList, this.state.sortBy, this.state.sortOrder),
|
repoList: myRepoList
|
||||||
});
|
});
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -73,6 +81,18 @@ class Libraries extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sortRepos = (repoList, groups) => {
|
||||||
|
const allRepoList = Utils.sortRepos(repoList, this.state.sortBy, this.state.sortOrder);
|
||||||
|
const myRepoList = allRepoList.filter(item => item.type === 'mine');
|
||||||
|
const sharedRepoList = allRepoList.filter(item => item.type === 'shared');
|
||||||
|
const publicRepoList = allRepoList.filter(item => item.type === 'public');
|
||||||
|
const groupList = groups.map(item => {
|
||||||
|
item.repos = Utils.sortRepos(item.repos, this.state.sortBy, this.state.sortOrder);
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
return { allRepoList, myRepoList, sharedRepoList, publicRepoList, groupList };
|
||||||
|
};
|
||||||
|
|
||||||
toggleSortOptionsDialog = () => {
|
toggleSortOptionsDialog = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
isSortOptionsDialogOpen: !this.state.isSortOptionsDialogOpen
|
isSortOptionsDialogOpen: !this.state.isSortOptionsDialogOpen
|
||||||
@@ -100,6 +120,23 @@ class Libraries extends Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onSelectSortOption = (sortOption) => {
|
||||||
|
const [sortBy, sortOrder] = sortOption.value.split('-');
|
||||||
|
this.setState({sortBy, sortOrder}, () => {
|
||||||
|
localStorage.setItem('sf_repos_sort_by', sortBy);
|
||||||
|
localStorage.setItem('sf_repos_sort_order', sortOrder);
|
||||||
|
const { allRepoList: repoList, groupList: groups } = this.state;
|
||||||
|
const { allRepoList, myRepoList, sharedRepoList, publicRepoList, groupList } = this.sortRepos(repoList, groups);
|
||||||
|
this.setState({
|
||||||
|
allRepoList,
|
||||||
|
groupList,
|
||||||
|
sharedRepoList,
|
||||||
|
publicRepoList,
|
||||||
|
repoList: myRepoList
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
sortRepoList = (sortBy, sortOrder) => {
|
sortRepoList = (sortBy, sortOrder) => {
|
||||||
cookie.save('seafile-repo-dir-sort-by', sortBy);
|
cookie.save('seafile-repo-dir-sort-by', sortBy);
|
||||||
cookie.save('seafile-repo-dir-sort-order', sortOrder);
|
cookie.save('seafile-repo-dir-sort-order', sortOrder);
|
||||||
@@ -181,8 +218,33 @@ class Libraries extends Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
switchViewMode = (newMode) => {
|
||||||
|
this.setState({
|
||||||
|
currentViewMode: newMode
|
||||||
|
}, () => {
|
||||||
|
localStorage.setItem('sf_repo_list_view_mode', newMode);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { isLoading } = this.state;
|
const { isLoading, currentViewMode, sortBy, sortOrder } = this.state;
|
||||||
|
const baseClass = 'btn btn-secondary btn-icon sf-view-mode-btn ';
|
||||||
|
const isDesktop = Utils.isDesktop();
|
||||||
|
|
||||||
|
const sortOptions = this.sortOptions.map(item => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
isSelected: item.value == `${sortBy}-${sortOrder}`
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const customSelectorToggle = (
|
||||||
|
<button className="btn btn-secondary border-0 op-btn repos-sort-menu-toggle">
|
||||||
|
<i className="sf3-font-sort2 sf3-font"></i>
|
||||||
|
<i className="sf3-font-down sf3-font ml-1"></i>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<TopToolbar
|
<TopToolbar
|
||||||
@@ -194,28 +256,45 @@ class Libraries extends Component {
|
|||||||
<div className="cur-view-container">
|
<div className="cur-view-container">
|
||||||
<div className="cur-view-path">
|
<div className="cur-view-path">
|
||||||
<h3 className="sf-heading m-0">{gettext('Files')}</h3>
|
<h3 className="sf-heading m-0">{gettext('Files')}</h3>
|
||||||
|
{isDesktop &&
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<div className="view-modes mr-2">
|
||||||
|
<button className={`${baseClass} sf3-font-list-view sf3-font ${currentViewMode === 'list' ? 'current-mode' : ''}`} id='list' title={gettext('List')} aria-label={gettext('List')} onClick={this.switchViewMode.bind(this, 'list')}></button>
|
||||||
|
<button className={`${baseClass} sf3-font-grid-view sf3-font ${currentViewMode === 'grid' ? 'current-mode' : ''}`} id='grid' title={gettext('Grid')} aria-label={gettext('Grid')} onClick={this.switchViewMode.bind(this, 'grid')}></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Selector
|
||||||
|
customSelectorToggle={customSelectorToggle}
|
||||||
|
options={sortOptions}
|
||||||
|
selectOption={this.onSelectSortOption}
|
||||||
|
menuCustomClass='repos-sort-menu dropdown-menu-right'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
{isLoading ?
|
{isLoading ?
|
||||||
<Loading /> :
|
<Loading /> :
|
||||||
<div className="cur-view-content" id="files-content-container">
|
<div className="cur-view-content" id="files-content-container">
|
||||||
|
|
||||||
<table aria-hidden={true} className="my-3">
|
{(Utils.isDesktop() && currentViewMode == 'list') && (
|
||||||
<thead>
|
<table aria-hidden={true} className="my-3">
|
||||||
<tr>
|
<thead>
|
||||||
<th width="4%"></th>
|
<tr>
|
||||||
<th width="3%"><span className="sr-only">{gettext('Library Type')}</span></th>
|
<th width="4%"></th>
|
||||||
<th width="35%">{gettext('Name')}</th>
|
<th width="3%"><span className="sr-only">{gettext('Library Type')}</span></th>
|
||||||
<th width="10%"><span className="sr-only">{gettext('Actions')}</span></th>
|
<th width="35%">{gettext('Name')}</th>
|
||||||
<th width="14%">{gettext('Size')}</th>
|
<th width="10%"><span className="sr-only">{gettext('Actions')}</span></th>
|
||||||
<th width="17%">{gettext('Last Update')}</th>
|
<th width="14%">{gettext('Size')}</th>
|
||||||
<th width="17%">{gettext('Owner')}</th>
|
<th width="17%">{gettext('Last Update')}</th>
|
||||||
</tr>
|
<th width="17%">{gettext('Owner')}</th>
|
||||||
</thead>
|
</tr>
|
||||||
</table>
|
</thead>
|
||||||
|
</table>
|
||||||
|
)}
|
||||||
|
|
||||||
{canAddRepo && (
|
{canAddRepo && (
|
||||||
<div className="pb-3">
|
<div className="pb-3">
|
||||||
<div className="d-flex justify-content-between mt-3 py-1 sf-border-bottom">
|
<div className={`d-flex justify-content-between mt-3 py-1 ${currentViewMode == 'list' ? 'sf-border-bottom' : ''}`}>
|
||||||
<h4 className="sf-heading m-0 d-flex align-items-center">
|
<h4 className="sf-heading m-0 d-flex align-items-center">
|
||||||
<span className="sf3-font-mine sf3-font nav-icon" aria-hidden="true"></span>
|
<span className="sf3-font-mine sf3-font nav-icon" aria-hidden="true"></span>
|
||||||
{gettext('My Libraries')}
|
{gettext('My Libraries')}
|
||||||
@@ -229,7 +308,7 @@ class Libraries extends Component {
|
|||||||
</div>
|
</div>
|
||||||
{this.state.errorMsg ? <p className="error text-center mt-8">{this.state.errorMsg}</p> : (
|
{this.state.errorMsg ? <p className="error text-center mt-8">{this.state.errorMsg}</p> : (
|
||||||
this.state.repoList.length === 0 ? (
|
this.state.repoList.length === 0 ? (
|
||||||
<p className="libraries-empty-tip">{gettext('No libraries')}</p>
|
<p className={`libraries-empty-tip-in-${currentViewMode}-mode`}>{gettext('No libraries')}</p>
|
||||||
) : (
|
) : (
|
||||||
<MylibRepoListView
|
<MylibRepoListView
|
||||||
sortBy={this.state.sortBy}
|
sortBy={this.state.sortBy}
|
||||||
@@ -242,17 +321,26 @@ class Libraries extends Component {
|
|||||||
onRepoClick={this.onRepoClick}
|
onRepoClick={this.onRepoClick}
|
||||||
sortRepoList={this.sortRepoList}
|
sortRepoList={this.sortRepoList}
|
||||||
inAllLibs={true}
|
inAllLibs={true}
|
||||||
|
currentViewMode={currentViewMode}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="pb-3">
|
<div className="pb-3">
|
||||||
<SharedLibs inAllLibs={true} repoList={this.state.sharedRepoList} />
|
<SharedLibs
|
||||||
|
repoList={this.state.sharedRepoList}
|
||||||
|
inAllLibs={true}
|
||||||
|
currentViewMode={currentViewMode}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{canViewOrg &&
|
{canViewOrg &&
|
||||||
<div className="pb-3">
|
<div className="pb-3">
|
||||||
<SharedWithAll inAllLibs={true} repoList={this.state.publicRepoList} />
|
<SharedWithAll
|
||||||
|
repoList={this.state.publicRepoList}
|
||||||
|
inAllLibs={true}
|
||||||
|
currentViewMode={currentViewMode}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div className="group-list-panel">
|
<div className="group-list-panel">
|
||||||
@@ -263,6 +351,7 @@ class Libraries extends Component {
|
|||||||
key={index}
|
key={index}
|
||||||
group={group}
|
group={group}
|
||||||
updateGroup={this.updateGroup}
|
updateGroup={this.updateGroup}
|
||||||
|
currentViewMode={currentViewMode}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
@@ -25,6 +25,7 @@ import LibOldFilesAutoDelDialog from '../../components/dialog/lib-old-files-auto
|
|||||||
import RepoMonitoredIcon from '../../components/repo-monitored-icon';
|
import RepoMonitoredIcon from '../../components/repo-monitored-icon';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
|
currentViewMode: PropTypes.string,
|
||||||
repo: PropTypes.object.isRequired,
|
repo: PropTypes.object.isRequired,
|
||||||
isItemFreezed: PropTypes.bool.isRequired,
|
isItemFreezed: PropTypes.bool.isRequired,
|
||||||
onFreezedItem: PropTypes.func.isRequired,
|
onFreezedItem: PropTypes.func.isRequired,
|
||||||
@@ -318,16 +319,21 @@ class MylibRepoListItem extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
renderPCUI = () => {
|
renderPCUI = () => {
|
||||||
let repo = this.props.repo;
|
const { repo, currentViewMode = 'list' } = this.props;
|
||||||
let iconUrl = Utils.getLibIconUrl(repo);
|
let useBigLibaryIcon = currentViewMode == 'grid';
|
||||||
|
let iconUrl = Utils.getLibIconUrl(repo, useBigLibaryIcon);
|
||||||
let iconTitle = Utils.getLibIconTitle(repo);
|
let iconTitle = Utils.getLibIconTitle(repo);
|
||||||
let repoURL = `${siteRoot}library/${repo.repo_id}/${Utils.encodePath(repo.repo_name)}/`;
|
let repoURL = `${siteRoot}library/${repo.repo_id}/${Utils.encodePath(repo.repo_name)}/`;
|
||||||
return (
|
return currentViewMode == 'list' ? (
|
||||||
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} onFocus={this.onFocus}>
|
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} onFocus={this.onFocus}>
|
||||||
<td className="text-center">
|
<td className="text-center">
|
||||||
<a href="#" role="button" aria-label={this.state.isStarred ? gettext('Unstar') : gettext('Star')} onClick={this.onToggleStarRepo}>
|
<i
|
||||||
<i className={`fa-star ${this.state.isStarred ? 'fas' : 'far star-empty'}`}></i>
|
role="button"
|
||||||
</a>
|
title={this.state.isStarred ? gettext('Unstar') : gettext('Star')}
|
||||||
|
aria-label={this.state.isStarred ? gettext('Unstar') : gettext('Star')}
|
||||||
|
onClick={this.onToggleStarRepo}
|
||||||
|
className={`op-icon m-0 ${this.state.isStarred ? 'sf3-font-star' : 'sf3-font-star-empty'} sf3-font`}
|
||||||
|
></i>
|
||||||
</td>
|
</td>
|
||||||
<td><img src={iconUrl} title={iconTitle} alt={iconTitle} width="24" /></td>
|
<td><img src={iconUrl} title={iconTitle} alt={iconTitle} width="24" /></td>
|
||||||
<td>
|
<td>
|
||||||
@@ -341,7 +347,7 @@ class MylibRepoListItem extends React.Component {
|
|||||||
{!this.state.isRenaming && repo.repo_name && (
|
{!this.state.isRenaming && repo.repo_name && (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Link to={repoURL}>{repo.repo_name}</Link>
|
<Link to={repoURL}>{repo.repo_name}</Link>
|
||||||
{repo.monitored && <RepoMonitoredIcon repoID={repo.repo_id} />}
|
{repo.monitored && <RepoMonitoredIcon repoID={repo.repo_id} className="ml-1 op-icon" />}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)}
|
)}
|
||||||
{!this.state.isRenaming && !repo.repo_name &&
|
{!this.state.isRenaming && !repo.repo_name &&
|
||||||
@@ -367,6 +373,54 @@ class MylibRepoListItem extends React.Component {
|
|||||||
{storages.length > 0 && <td>{repo.storage_name}</td>}
|
{storages.length > 0 && <td>{repo.storage_name}</td>}
|
||||||
<td title={moment(repo.last_modified).format('llll')}>{moment(repo.last_modified).fromNow()}</td>
|
<td title={moment(repo.last_modified).format('llll')}>{moment(repo.last_modified).fromNow()}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className="library-grid-item px-3 d-flex justify-content-between align-items-center"
|
||||||
|
onMouseEnter={this.onMouseEnter}
|
||||||
|
onMouseLeave={this.onMouseLeave}
|
||||||
|
onFocus={this.onFocus}
|
||||||
|
>
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<img src={iconUrl} title={iconTitle} alt={iconTitle} width="36" className="mr-2" />
|
||||||
|
{this.state.isRenaming && (
|
||||||
|
<Rename
|
||||||
|
name={repo.repo_name}
|
||||||
|
onRenameConfirm={this.onRenameConfirm}
|
||||||
|
onRenameCancel={this.onRenameCancel}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!this.state.isRenaming && repo.repo_name && (
|
||||||
|
<Fragment>
|
||||||
|
<Link to={repoURL}>{repo.repo_name}</Link>
|
||||||
|
<i
|
||||||
|
role="button"
|
||||||
|
title={this.state.isStarred ? gettext('Unstar') : gettext('Star')}
|
||||||
|
aria-label={this.state.isStarred ? gettext('Unstar') : gettext('Star')}
|
||||||
|
className={`op-icon library-grid-item-icon ${this.state.isStarred ? 'sf3-font-star' : 'sf3-font-star-empty'} sf3-font`}
|
||||||
|
onClick={this.onToggleStarRepo}
|
||||||
|
>
|
||||||
|
</i>
|
||||||
|
{repo.monitored && <RepoMonitoredIcon repoID={repo.repo_id} className="op-icon library-grid-item-icon" />}
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
|
{!this.state.isRenaming && !repo.repo_name &&
|
||||||
|
(<span>{gettext('Broken (please contact your administrator to fix this library)')}</span>)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
{(repo.repo_name && this.state.isOpIconShow) && (
|
||||||
|
<div>
|
||||||
|
<a href="#" className="op-icon sf3-font-share sf3-font" title={gettext('Share')} role="button" aria-label={gettext('Share')} onClick={this.onShareToggle}></a>
|
||||||
|
<a href="#" className="op-icon sf3-font-delete1 sf3-font" title={gettext('Delete')} role="button" aria-label={gettext('Delete')} onClick={this.onDeleteToggle}></a>
|
||||||
|
<MylibRepoMenu
|
||||||
|
isPC={true}
|
||||||
|
repo={this.props.repo}
|
||||||
|
onMenuItemClick={this.onMenuItemClick}
|
||||||
|
onFreezedItem={this.props.onFreezedItem}
|
||||||
|
onUnfreezedItem={this.onUnfreezedItem}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -390,7 +444,7 @@ class MylibRepoListItem extends React.Component {
|
|||||||
{!this.state.isRenaming && repo.repo_name && (
|
{!this.state.isRenaming && repo.repo_name && (
|
||||||
<div>
|
<div>
|
||||||
<Link to={repoURL}>{repo.repo_name}</Link>
|
<Link to={repoURL}>{repo.repo_name}</Link>
|
||||||
{repo.monitored && <RepoMonitoredIcon repoID={repo.repo_id} />}
|
{repo.monitored && <RepoMonitoredIcon repoID={repo.repo_id} className="ml-1 op-icon" />}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!this.state.isRenaming && !repo.repo_name &&
|
{!this.state.isRenaming && !repo.repo_name &&
|
||||||
|
@@ -70,6 +70,7 @@ class MylibRepoListView extends React.Component {
|
|||||||
onDeleteRepo={this.props.onDeleteRepo}
|
onDeleteRepo={this.props.onDeleteRepo}
|
||||||
onTransferRepo={this.props.onTransferRepo}
|
onTransferRepo={this.props.onTransferRepo}
|
||||||
onMonitorRepo={this.props.onMonitorRepo}
|
onMonitorRepo={this.props.onMonitorRepo}
|
||||||
|
currentViewMode={this.props.currentViewMode}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -78,11 +79,11 @@ class MylibRepoListView extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
renderPCUI = () => {
|
renderPCUI = () => {
|
||||||
const { inAllLibs } = this.props;
|
const { inAllLibs, currentViewMode = 'list' } = this.props;
|
||||||
const showStorageBackend = !inAllLibs && storages.length > 0;
|
const showStorageBackend = !inAllLibs && storages.length > 0;
|
||||||
const sortIcon = this.props.sortOrder === 'asc' ? <span className="fas fa-caret-up"></span> : <span className="fas fa-caret-down"></span>;
|
const sortIcon = this.props.sortOrder === 'asc' ? <span className="fas fa-caret-up"></span> : <span className="fas fa-caret-down"></span>;
|
||||||
|
|
||||||
return (
|
return currentViewMode == 'list' ? (
|
||||||
<table className={inAllLibs ? 'table-thead-hidden' : ''}>
|
<table className={inAllLibs ? 'table-thead-hidden' : ''}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -99,6 +100,10 @@ class MylibRepoListView extends React.Component {
|
|||||||
{this.renderRepoListView()}
|
{this.renderRepoListView()}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
) : (
|
||||||
|
<div className="d-flex justify-content-between flex-wrap">
|
||||||
|
{this.renderRepoListView()}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -54,10 +54,10 @@ class Content extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { loading, errorMsg, items, sortBy, sortOrder, theadHidden, inAllLibs } = this.props;
|
const { loading, errorMsg, items, sortBy, sortOrder, theadHidden, inAllLibs, currentViewMode } = this.props;
|
||||||
|
|
||||||
const emptyTip = inAllLibs ?
|
const emptyTip = inAllLibs ?
|
||||||
<p className="libraries-empty-tip">{gettext('No shared libraries')}</p> :
|
<p className={`libraries-empty-tip-in-${currentViewMode}-mode`}>{gettext('No shared libraries')}</p> :
|
||||||
<EmptyTip>
|
<EmptyTip>
|
||||||
<h2>{gettext('No shared libraries')}</h2>
|
<h2>{gettext('No shared libraries')}</h2>
|
||||||
<p>{gettext('No libraries have been shared directly with you. A shared library can be shared with full or restricted permission. If you need access to a library owned by another user, ask the user to share the library with you.')}</p>
|
<p>{gettext('No libraries have been shared directly with you. A shared library can be shared with full or restricted permission. If you need access to a library owned by another user, ask the user to share the library with you.')}</p>
|
||||||
@@ -89,30 +89,41 @@ class Content extends Component {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const isDesktop = Utils.isDesktop();
|
const isDesktop = Utils.isDesktop();
|
||||||
const table = (
|
const itemsContent = (
|
||||||
|
<>
|
||||||
|
{items.map((item, index) => {
|
||||||
|
return <Item
|
||||||
|
key={index}
|
||||||
|
data={item}
|
||||||
|
isDesktop={isDesktop}
|
||||||
|
isItemFreezed={this.state.isItemFreezed}
|
||||||
|
freezeItem={this.freezeItem}
|
||||||
|
onMonitorRepo={this.props.onMonitorRepo}
|
||||||
|
currentViewMode={currentViewMode}
|
||||||
|
/>;
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
const content = currentViewMode == 'list' ? (
|
||||||
<table className={(isDesktop && !theadHidden)? '' : 'table-thead-hidden'}>
|
<table className={(isDesktop && !theadHidden)? '' : 'table-thead-hidden'}>
|
||||||
{isDesktop ? desktopThead : <LibsMobileThead />}
|
{isDesktop ? desktopThead : <LibsMobileThead />}
|
||||||
<tbody>
|
<tbody>
|
||||||
{items.map((item, index) => {
|
{itemsContent}
|
||||||
return <Item
|
|
||||||
key={index}
|
|
||||||
data={item}
|
|
||||||
isDesktop={isDesktop}
|
|
||||||
isItemFreezed={this.state.isItemFreezed}
|
|
||||||
freezeItem={this.freezeItem}
|
|
||||||
onMonitorRepo={this.props.onMonitorRepo}
|
|
||||||
/>;
|
|
||||||
})}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
) : (
|
||||||
|
<div className="d-flex justify-content-between flex-wrap">
|
||||||
|
{itemsContent}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
return items.length ? table : emptyTip;
|
return items.length ? content : emptyTip;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Content.propTypes = {
|
Content.propTypes = {
|
||||||
|
currentViewMode: PropTypes.string,
|
||||||
inAllLibs: PropTypes.bool.isRequired,
|
inAllLibs: PropTypes.bool.isRequired,
|
||||||
theadHidden: PropTypes.bool.isRequired,
|
theadHidden: PropTypes.bool.isRequired,
|
||||||
loading: PropTypes.bool.isRequired,
|
loading: PropTypes.bool.isRequired,
|
||||||
@@ -257,9 +268,9 @@ class Item extends Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = this.props.data;
|
const { data, currentViewMode = 'list' } = this.props;
|
||||||
|
const useBigLibaryIcon = currentViewMode == 'grid';
|
||||||
data.icon_url = Utils.getLibIconUrl(data);
|
data.icon_url = Utils.getLibIconUrl(data, useBigLibaryIcon);
|
||||||
data.icon_title = Utils.getLibIconTitle(data);
|
data.icon_title = Utils.getLibIconTitle(data);
|
||||||
|
|
||||||
let iconVisibility = this.state.showOpIcon ? '' : ' invisible';
|
let iconVisibility = this.state.showOpIcon ? '' : ' invisible';
|
||||||
@@ -272,46 +283,96 @@ class Item extends Component {
|
|||||||
|
|
||||||
const desktopItem = (
|
const desktopItem = (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut} onFocus={this.handleMouseOver}>
|
{currentViewMode == 'list' ? (
|
||||||
<td className="text-center">
|
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut} onFocus={this.handleMouseOver}>
|
||||||
<a href="#" role="button" aria-label={this.state.isStarred ? gettext('Unstar') : gettext('Star')} onClick={this.onToggleStarRepo}>
|
<td className="text-center">
|
||||||
<i className={`fa-star ${this.state.isStarred ? 'fas' : 'far star-empty'}`}></i>
|
<i
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td><img src={data.icon_url} title={data.icon_title} alt={data.icon_title} width="24" /></td>
|
|
||||||
<td>
|
|
||||||
<Fragment>
|
|
||||||
<Link to={shareRepoUrl}>{data.repo_name}</Link>
|
|
||||||
{data.monitored && <RepoMonitoredIcon repoID={data.repo_id} />}
|
|
||||||
</Fragment>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{(isPro && data.is_admin) &&
|
|
||||||
<a href="#" className={shareIconClassName} title={gettext('Share')} role="button" aria-label={gettext('Share')} onClick={this.share}></a>
|
|
||||||
}
|
|
||||||
<a href="#" className={leaveShareIconClassName} title={gettext('Leave Share')} role="button" aria-label={gettext('Leave Share')} onClick={this.leaveShare}></a>
|
|
||||||
{enableMonitorRepo &&
|
|
||||||
<Dropdown isOpen={this.state.isOpMenuOpen} toggle={this.toggleOpMenu}>
|
|
||||||
<DropdownToggle
|
|
||||||
tag="i"
|
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex="0"
|
title={this.state.isStarred ? gettext('Unstar') : gettext('Star')}
|
||||||
className={`sf-dropdown-toggle sf3-font-more sf3-font ${iconVisibility}`}
|
aria-label={this.state.isStarred ? gettext('Unstar') : gettext('Star')}
|
||||||
title={gettext('More operations')}
|
onClick={this.onToggleStarRepo}
|
||||||
aria-label={gettext('More operations')}
|
className={`op-icon m-0 ${this.state.isStarred ? 'sf3-font-star' : 'sf3-font-star-empty'} sf3-font`}
|
||||||
data-toggle="dropdown"
|
></i>
|
||||||
aria-expanded={this.state.isOpMenuOpen}
|
</td>
|
||||||
/>
|
<td><img src={data.icon_url} title={data.icon_title} alt={data.icon_title} width="24" /></td>
|
||||||
<DropdownMenu>
|
<td>
|
||||||
<DropdownItem onClick={data.monitored ? this.unwatchFileChanges : this.watchFileChanges}>{data.monitored ? gettext('Unwatch File Changes') : gettext('Watch File Changes')}</DropdownItem>
|
<Fragment>
|
||||||
</DropdownMenu>
|
<Link to={shareRepoUrl}>{data.repo_name}</Link>
|
||||||
</Dropdown>
|
{data.monitored && <RepoMonitoredIcon repoID={data.repo_id} className="ml-1 op-icon" />}
|
||||||
}
|
</Fragment>
|
||||||
</td>
|
</td>
|
||||||
<td>{data.size}</td>
|
<td>
|
||||||
<td title={moment(data.last_modified).format('llll')}>{moment(data.last_modified).fromNow()}</td>
|
{(isPro && data.is_admin) &&
|
||||||
<td title={data.owner_contact_email}>{data.owner_name}</td>
|
<a href="#" className={shareIconClassName} title={gettext('Share')} role="button" aria-label={gettext('Share')} onClick={this.share}></a>
|
||||||
</tr>
|
}
|
||||||
|
<a href="#" className={leaveShareIconClassName} title={gettext('Leave Share')} role="button" aria-label={gettext('Leave Share')} onClick={this.leaveShare}></a>
|
||||||
|
{enableMonitorRepo &&
|
||||||
|
<Dropdown isOpen={this.state.isOpMenuOpen} toggle={this.toggleOpMenu}>
|
||||||
|
<DropdownToggle
|
||||||
|
tag="i"
|
||||||
|
role="button"
|
||||||
|
tabIndex="0"
|
||||||
|
className={`sf-dropdown-toggle sf3-font-more sf3-font ${iconVisibility}`}
|
||||||
|
title={gettext('More operations')}
|
||||||
|
aria-label={gettext('More operations')}
|
||||||
|
data-toggle="dropdown"
|
||||||
|
aria-expanded={this.state.isOpMenuOpen}
|
||||||
|
/>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownItem onClick={data.monitored ? this.unwatchFileChanges : this.watchFileChanges}>{data.monitored ? gettext('Unwatch File Changes') : gettext('Watch File Changes')}</DropdownItem>
|
||||||
|
</DropdownMenu>
|
||||||
|
</Dropdown>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td>{data.size}</td>
|
||||||
|
<td title={moment(data.last_modified).format('llll')}>{moment(data.last_modified).fromNow()}</td>
|
||||||
|
<td title={data.owner_contact_email}>{data.owner_name}</td>
|
||||||
|
</tr>
|
||||||
|
): (
|
||||||
|
<div
|
||||||
|
className="library-grid-item px-3 d-flex justify-content-between align-items-center"
|
||||||
|
onMouseOver={this.handleMouseOver}
|
||||||
|
onMouseOut={this.handleMouseOut}
|
||||||
|
onFocus={this.handleMouseOver}
|
||||||
|
>
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<img src={data.icon_url} title={data.icon_title} alt={data.icon_title} width="36" className="mr-2" />
|
||||||
|
<Link to={shareRepoUrl}>{data.repo_name}</Link>
|
||||||
|
<i
|
||||||
|
role="button"
|
||||||
|
title={this.state.isStarred ? gettext('Unstar') : gettext('Star')}
|
||||||
|
aria-label={this.state.isStarred ? gettext('Unstar') : gettext('Star')}
|
||||||
|
onClick={this.onToggleStarRepo}
|
||||||
|
className={`op-icon library-grid-item-icon ${this.state.isStarred ? 'sf3-font-star' : 'sf3-font-star-empty'} sf3-font`}
|
||||||
|
></i>
|
||||||
|
{data.monitored && <RepoMonitoredIcon repoID={data.repo_id} className="op-icon library-grid-item-icon" />}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{(isPro && data.is_admin) &&
|
||||||
|
<a href="#" className={shareIconClassName} title={gettext('Share')} role="button" aria-label={gettext('Share')} onClick={this.share}></a>
|
||||||
|
}
|
||||||
|
<a href="#" className={leaveShareIconClassName} title={gettext('Leave Share')} role="button" aria-label={gettext('Leave Share')} onClick={this.leaveShare}></a>
|
||||||
|
{enableMonitorRepo &&
|
||||||
|
<Dropdown isOpen={this.state.isOpMenuOpen} toggle={this.toggleOpMenu}>
|
||||||
|
<DropdownToggle
|
||||||
|
tag="i"
|
||||||
|
role="button"
|
||||||
|
tabIndex="0"
|
||||||
|
className={`sf-dropdown-toggle sf3-font-more sf3-font ${iconVisibility}`}
|
||||||
|
title={gettext('More operations')}
|
||||||
|
aria-label={gettext('More operations')}
|
||||||
|
data-toggle="dropdown"
|
||||||
|
aria-expanded={this.state.isOpMenuOpen}
|
||||||
|
/>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownItem onClick={data.monitored ? this.unwatchFileChanges : this.watchFileChanges}>{data.monitored ? gettext('Unwatch File Changes') : gettext('Watch File Changes')}</DropdownItem>
|
||||||
|
</DropdownMenu>
|
||||||
|
</Dropdown>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{this.state.isShowSharedDialog && (
|
{this.state.isShowSharedDialog && (
|
||||||
<ModalPotal>
|
<ModalPotal>
|
||||||
<ShareDialog
|
<ShareDialog
|
||||||
@@ -336,7 +397,7 @@ class Item extends Component {
|
|||||||
<td onClick={this.visitRepo}><img src={data.icon_url} title={data.icon_title} alt={data.icon_title} width="24" /></td>
|
<td onClick={this.visitRepo}><img src={data.icon_url} title={data.icon_title} alt={data.icon_title} width="24" /></td>
|
||||||
<td onClick={this.visitRepo}>
|
<td onClick={this.visitRepo}>
|
||||||
<Link to={shareRepoUrl}>{data.repo_name}</Link>
|
<Link to={shareRepoUrl}>{data.repo_name}</Link>
|
||||||
{data.monitored && <RepoMonitoredIcon repoID={data.repo_id} />}
|
{data.monitored && <RepoMonitoredIcon repoID={data.repo_id} className="ml-1 op-icon" />}
|
||||||
<br />
|
<br />
|
||||||
<span className="item-meta-info" title={data.owner_contact_email}>{data.owner_name}</span>
|
<span className="item-meta-info" title={data.owner_contact_email}>{data.owner_name}</span>
|
||||||
<span className="item-meta-info">{data.size}</span>
|
<span className="item-meta-info">{data.size}</span>
|
||||||
@@ -386,6 +447,7 @@ class Item extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Item.propTypes = {
|
Item.propTypes = {
|
||||||
|
currentViewMode: PropTypes.string,
|
||||||
isDesktop: PropTypes.bool.isRequired,
|
isDesktop: PropTypes.bool.isRequired,
|
||||||
data: PropTypes.object.isRequired,
|
data: PropTypes.object.isRequired,
|
||||||
isItemFreezed: PropTypes.bool.isRequired,
|
isItemFreezed: PropTypes.bool.isRequired,
|
||||||
@@ -425,7 +487,7 @@ class SharedLibraries extends Component {
|
|||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
items: Utils.sortRepos(this.props.repoList, this.state.sortBy, this.state.sortOrder)
|
items: this.props.repoList
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -457,18 +519,20 @@ class SharedLibraries extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
renderContent = () => {
|
renderContent = () => {
|
||||||
const { inAllLibs = false } = this.props; // inAllLibs: in 'All Libs'('Files') page
|
const { inAllLibs = false, currentViewMode = 'list', repoList } = this.props; // inAllLibs: in 'All Libs'('Files') page
|
||||||
|
const { items } = this.state;
|
||||||
return (
|
return (
|
||||||
<Content
|
<Content
|
||||||
loading={this.state.loading}
|
loading={this.state.loading}
|
||||||
errorMsg={this.state.errorMsg}
|
errorMsg={this.state.errorMsg}
|
||||||
items={this.state.items}
|
items={inAllLibs ? repoList : items}
|
||||||
sortBy={this.state.sortBy}
|
sortBy={this.state.sortBy}
|
||||||
sortOrder={this.state.sortOrder}
|
sortOrder={this.state.sortOrder}
|
||||||
sortItems={this.sortItems}
|
sortItems={this.sortItems}
|
||||||
onMonitorRepo={this.onMonitorRepo}
|
onMonitorRepo={this.onMonitorRepo}
|
||||||
theadHidden={inAllLibs}
|
theadHidden={inAllLibs}
|
||||||
inAllLibs={inAllLibs}
|
inAllLibs={inAllLibs}
|
||||||
|
currentViewMode={currentViewMode}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -482,14 +546,14 @@ class SharedLibraries extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { inAllLibs = false } = this.props; // inAllLibs: in 'All Libs'('Files') page
|
const { inAllLibs = false, currentViewMode = 'list' } = this.props; // inAllLibs: in 'All Libs'('Files') page
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
{inAllLibs ? (
|
{inAllLibs ? (
|
||||||
<>
|
<>
|
||||||
<div className="d-flex justify-content-between mt-3 py-1 sf-border-bottom">
|
<div className={`d-flex justify-content-between mt-3 py-1 ${currentViewMode == 'list' ? 'sf-border-bottom' : ''}`}>
|
||||||
<h4 className="sf-heading m-0">
|
<h4 className="sf-heading m-0">
|
||||||
{inAllLibs && <span className="sf3-font-share-with-me sf3-font nav-icon" aria-hidden="true"></span>}
|
<span className="sf3-font-share-with-me sf3-font nav-icon" aria-hidden="true"></span>
|
||||||
{gettext('Shared with me')}
|
{gettext('Shared with me')}
|
||||||
</h4>
|
</h4>
|
||||||
{this.renderSortIconInMobile()}
|
{this.renderSortIconInMobile()}
|
||||||
@@ -523,6 +587,7 @@ class SharedLibraries extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SharedLibraries.propTypes = {
|
SharedLibraries.propTypes = {
|
||||||
|
currentViewMode: PropTypes.string,
|
||||||
inAllLibs: PropTypes.bool,
|
inAllLibs: PropTypes.bool,
|
||||||
repoList: PropTypes.array,
|
repoList: PropTypes.array,
|
||||||
};
|
};
|
||||||
|
@@ -16,6 +16,7 @@ import TopToolbar from './top-toolbar';
|
|||||||
const propTypes = {
|
const propTypes = {
|
||||||
onShowSidePanel: PropTypes.func,
|
onShowSidePanel: PropTypes.func,
|
||||||
onSearchedClick: PropTypes.func,
|
onSearchedClick: PropTypes.func,
|
||||||
|
currentViewMode: PropTypes.string,
|
||||||
inAllLibs: PropTypes.bool,
|
inAllLibs: PropTypes.bool,
|
||||||
repoList: PropTypes.array,
|
repoList: PropTypes.array,
|
||||||
};
|
};
|
||||||
@@ -117,10 +118,10 @@ class PublicSharedView extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
renderContent = () => {
|
renderContent = () => {
|
||||||
const { inAllLibs = false } = this.props; // inAllLibs: in 'All Libs'('Files') page
|
const { inAllLibs = false, currentViewMode = 'list' } = this.props; // inAllLibs: in 'All Libs'('Files') page
|
||||||
const { errMessage } = this.state;
|
const { errMessage } = this.state;
|
||||||
const emptyTip = inAllLibs ?
|
const emptyTip = inAllLibs ?
|
||||||
<p className="libraries-empty-tip">{gettext('No public libraries')}</p> : (
|
<p className={`libraries-empty-tip-in-${currentViewMode}-mode`}>{gettext('No public libraries')}</p> : (
|
||||||
<EmptyTip>
|
<EmptyTip>
|
||||||
<h2>{gettext('No public libraries')}</h2>
|
<h2>{gettext('No public libraries')}</h2>
|
||||||
<p>{gettext('No public libraries have been created yet. A public library is accessible by all users. You can create a public library by clicking the "Add Library" button in the menu bar.')}</p>
|
<p>{gettext('No public libraries have been created yet. A public library is accessible by all users. You can create a public library by clicking the "Add Library" button in the menu bar.')}</p>
|
||||||
@@ -141,6 +142,7 @@ class PublicSharedView extends React.Component {
|
|||||||
onItemUnshare={this.onItemUnshare}
|
onItemUnshare={this.onItemUnshare}
|
||||||
onItemDelete={this.onItemDelete}
|
onItemDelete={this.onItemDelete}
|
||||||
theadHidden={inAllLibs}
|
theadHidden={inAllLibs}
|
||||||
|
currentViewMode={currentViewMode}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</>
|
</>
|
||||||
@@ -157,12 +159,12 @@ class PublicSharedView extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { inAllLibs = false } = this.props; // inAllLibs: in 'All Libs'('Files') page
|
const { inAllLibs = false, currentViewMode = 'list' } = this.props; // inAllLibs: in 'All Libs'('Files') page
|
||||||
|
|
||||||
if (inAllLibs) {
|
if (inAllLibs) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="d-flex justify-content-between mt-3 py-1 sf-border-bottom">
|
<div className={`d-flex justify-content-between mt-3 py-1 ${currentViewMode == 'list' ? 'sf-border-bottom' : ''}`}>
|
||||||
<h4 className="sf-heading m-0">
|
<h4 className="sf-heading m-0">
|
||||||
<span className="sf3-font-share-with-all sf3-font nav-icon" aria-hidden="true"></span>
|
<span className="sf3-font-share-with-all sf3-font nav-icon" aria-hidden="true"></span>
|
||||||
{gettext('Shared with all')}
|
{gettext('Shared with all')}
|
||||||
|
Reference in New Issue
Block a user