1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-27 15:54:39 +00:00

Change transfer repo dialog UI (#7801)

* 01 change transfer repo to user

* 01 change transfer repo to department
This commit is contained in:
Michael An
2025-05-09 17:28:05 +08:00
committed by GitHub
parent 77260ba050
commit df4ba2a8c2
10 changed files with 130 additions and 79 deletions

View File

@@ -47,6 +47,19 @@
background: #fff; background: #fff;
} }
.seafile-customize-select .selected-option .selected-option-show-container {
background: #F2F2F2;
border-radius: 10px;
padding-right: 6px;
padding-left: 12px;
margin-right: 6px;
}
.seafile-customize-select .selected-option .selected-option-show-container .sf3-font-x-01 {
font-size: 12px;
color: #999;
}
.seafile-customize-select .selected-option .custom-select-dropdown-icon { .seafile-customize-select .selected-option .custom-select-dropdown-icon {
height: 12px; height: 12px;
width: 12px; width: 12px;
@@ -97,4 +110,5 @@
line-height: 1; line-height: 1;
font-size: 14px; font-size: 14px;
white-space: nowrap; white-space: nowrap;
color: #999;
} }

View File

@@ -88,7 +88,7 @@ class CustomizeSelect extends Component {
render() { render() {
const { className, value, options, placeholder, searchable, searchPlaceholder, noOptionsPlaceholder, const { className, value, options, placeholder, searchable, searchPlaceholder, noOptionsPlaceholder,
readOnly, isInModal, addOptionAble, component } = this.props; readOnly, isInModal, addOptionAble, component, enableDeleteSelected } = this.props;
return ( return (
<div <div
@@ -101,9 +101,16 @@ class CustomizeSelect extends Component {
onClick={this.onSelectToggle}> onClick={this.onSelectToggle}>
<div className="selected-option"> <div className="selected-option">
{value && value.label ? {value && value.label ?
<span className="selected-option-show">{value.label}</span> (enableDeleteSelected ?
: <span className="selected-option-show-container">
<span className="select-placeholder">{placeholder}</span> <span className='selected-option-show'>{value.label}</span>
<span className='selected-option-delete ml-1' onClick={this.props.deleteSelected}>
<i className="sf3-font sf3-font-x-01" aria-hidden="true"></i>
</span>
</span>
: <span className="selected-option-show">{value.label}</span>
)
: <span className="select-placeholder">{placeholder}</span>
} }
{this.renderDropDownIcon()} {this.renderDropDownIcon()}
</div> </div>
@@ -168,6 +175,8 @@ CustomizeSelect.propTypes = {
supportMultipleSelect: PropTypes.bool, supportMultipleSelect: PropTypes.bool,
isShowSelected: PropTypes.bool, isShowSelected: PropTypes.bool,
isInModal: PropTypes.bool, // if select component in a modal (option group need ModalPortal to show) isInModal: PropTypes.bool, // if select component in a modal (option group need ModalPortal to show)
enableDeleteSelected: PropTypes.bool,
deleteSelected: PropTypes.func,
}; };
export default CustomizeSelect; export default CustomizeSelect;

View File

@@ -56,6 +56,11 @@
cursor: pointer; cursor: pointer;
} }
.seafile-select-option .sf2-icon-tick {
right: 10px;
position: absolute;
}
.seafile-select-option.seafile-select-option-active .select-option-name { .seafile-select-option.seafile-select-option-active .select-option-name {
color: #fff; color: #fff;
} }

View File

@@ -128,7 +128,7 @@ class SelectOptionGroup extends Component {
}; };
renderOptGroup = (searchVal) => { renderOptGroup = (searchVal) => {
let { noOptionsPlaceholder, onSelectOption } = this.props; let { noOptionsPlaceholder, onSelectOption, value } = this.props;
this.filterOptions = this.props.getFilterOptions(searchVal); this.filterOptions = this.props.getFilterOptions(searchVal);
if (this.filterOptions.length === 0) { if (this.filterOptions.length === 0) {
return ( return (
@@ -138,6 +138,7 @@ class SelectOptionGroup extends Component {
return this.filterOptions.map((opt, i) => { return this.filterOptions.map((opt, i) => {
let key = opt.value.column ? opt.value.column.key : i; let key = opt.value.column ? opt.value.column.key : i;
let isActive = this.state.activeIndex === i; let isActive = this.state.activeIndex === i;
const isSelected = value && opt.value === value.value;
return ( return (
<Option <Option
key={key} key={key}
@@ -150,6 +151,7 @@ class SelectOptionGroup extends Component {
disableHover={this.state.disableHover} disableHover={this.state.disableHover}
> >
{opt.label} {opt.label}
{isSelected && <i className="sf2-icon-tick"></i>}
</Option> </Option>
); );
}); });

View File

@@ -1,9 +1,8 @@
import React, { Fragment } from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Button, Modal, ModalBody, ModalFooter, import { Button, Modal, ModalBody, ModalFooter,
Nav, NavItem, NavLink, TabContent, TabPane, Label } from 'reactstrap'; Nav, NavItem, NavLink, TabContent, TabPane, Label } from 'reactstrap';
import SeahubModalHeader from '@/components/common/seahub-modal-header'; import SeahubModalHeader from '@/components/common/seahub-modal-header';
import makeAnimated from 'react-select/animated';
import { seafileAPI } from '../../utils/seafile-api'; import { seafileAPI } from '../../utils/seafile-api';
import { systemAdminAPI } from '../../utils/system-admin-api'; import { systemAdminAPI } from '../../utils/system-admin-api';
import { orgAdminAPI } from '../../utils/org-admin-api'; import { orgAdminAPI } from '../../utils/org-admin-api';
@@ -11,8 +10,9 @@ import { gettext, isPro, orgID } from '../../utils/constants';
import { Utils } from '../../utils/utils'; import { Utils } from '../../utils/utils';
import toaster from '../toast'; import toaster from '../toast';
import UserSelect from '../user-select'; import UserSelect from '../user-select';
import { SeahubSelect } from '../common/select';
import Switch from '../switch'; import Switch from '../switch';
import CustomizeSelect from '../customize-select';
import '../../css/transfer-dialog.css'; import '../../css/transfer-dialog.css';
const propTypes = { const propTypes = {
@@ -43,8 +43,19 @@ class TransferDialog extends React.Component {
}; };
} }
handleSelectChange = (option) => { handleSelectDepartment = (value) => {
if (this.state.selectedOption && this.state.selectedOption.value === value) {
this.setState({ selectedOption: null });
} else {
const option = this.state.options.find(item => item.value === value);
this.setState({ selectedOption: option }); this.setState({ selectedOption: option });
}
};
deleteSelectedDepartment = (e) => {
e.stopPropagation();
e.preventDefault();
this.setState({ selectedOption: null });
}; };
onUsersChange = (selectedUsers) => { onUsersChange = (selectedUsers) => {
@@ -53,7 +64,7 @@ class TransferDialog extends React.Component {
submit = () => { submit = () => {
const { activeTab, reshare, selectedOption, selectedUsers } = this.state; const { activeTab, reshare, selectedOption, selectedUsers } = this.state;
const email = activeTab === TRANS_DEPART ? selectedOption.email : selectedUsers[0].email; const email = activeTab === TRANS_DEPART ? selectedOption.value : selectedUsers[0].email;
this.props.onTransferRepo(email, reshare); this.props.onTransferRepo(email, reshare);
}; };
@@ -89,9 +100,9 @@ class TransferDialog extends React.Component {
updateOptions = (departmentsRes) => { updateOptions = (departmentsRes) => {
const options = departmentsRes.data.map(item => { const options = departmentsRes.data.map(item => {
let option = { let option = {
value: item.name, value: item.email,
email: item.email,
label: item.name, label: item.name,
name: item.name,
}; };
return option; return option;
}); });
@@ -128,8 +139,20 @@ class TransferDialog extends React.Component {
if (this.props.canTransferToDept != undefined) { if (this.props.canTransferToDept != undefined) {
canTransferToDept = this.props.canTransferToDept; canTransferToDept = this.props.canTransferToDept;
} }
const { selectedOption } = this.state;
let buttonDisabled = false;
if (activeTab === TRANS_DEPART) {
if (selectedOption === null || (Array.isArray(selectedOption) && selectedOption.length === 0)) {
buttonDisabled = true;
}
} else {
if (this.state.selectedUsers.length === 0) {
buttonDisabled = true;
}
}
return ( return (
<Fragment> <>
<div className="transfer-dialog-side"> <div className="transfer-dialog-side">
<Nav pills> <Nav pills>
{!this.props.isDepAdminTransfer && {!this.props.isDepAdminTransfer &&
@@ -154,12 +177,13 @@ class TransferDialog extends React.Component {
> >
{gettext('Transfer to department')} {gettext('Transfer to department')}
</NavLink> </NavLink>
</NavItem>} </NavItem>
}
</Nav> </Nav>
</div> </div>
<div className="transfer-dialog-main"> <div className="transfer-dialog-main">
<TabContent activeTab={this.state.activeTab}> <TabContent activeTab={this.state.activeTab}>
<Fragment> <>
<TabPane tabId="transUser" role="tabpanel" id="transfer-user-panel"> <TabPane tabId="transUser" role="tabpanel" id="transfer-user-panel">
<Label className='transfer-repo-label'>{gettext('Users')}</Label> <Label className='transfer-repo-label'>{gettext('Users')}</Label>
<UserSelect <UserSelect
@@ -173,7 +197,7 @@ class TransferDialog extends React.Component {
disabled={false} disabled={false}
size="large" size="large"
textPosition="right" textPosition="right"
className='transfer-repo-reshare-switch w-100 mt-3 mb-1' className='transfer-repo-reshare-switch w-100 mt-6 mb-1'
onChange={this.toggleReshareStatus} onChange={this.toggleReshareStatus}
placeholder={gettext('Keep sharing')} placeholder={gettext('Keep sharing')}
/> />
@@ -182,50 +206,45 @@ class TransferDialog extends React.Component {
{isPro && canTransferToDept && {isPro && canTransferToDept &&
<TabPane tabId="transDepart" role="tabpanel" id="transfer-depart-panel"> <TabPane tabId="transDepart" role="tabpanel" id="transfer-depart-panel">
<Label className='transfer-repo-label'>{gettext('Departments')}</Label> <Label className='transfer-repo-label'>{gettext('Departments')}</Label>
<SeahubSelect <CustomizeSelect
isClearable readOnly={false}
maxMenuHeight={200}
hideSelectedOptions={true}
components={makeAnimated()}
placeholder={gettext('Select a department')}
options={this.state.options}
onChange={this.handleSelectChange}
value={this.state.selectedOption} value={this.state.selectedOption}
className="transfer-repo-select-department" options={this.state.options}
onSelectOption={this.handleSelectDepartment}
searchable={true}
supportMultipleSelect={false}
placeholder={gettext('Select a department')}
searchPlaceholder={gettext('Search department')}
enableDeleteSelected={true}
deleteSelected={this.deleteSelectedDepartment}
/> />
<Switch <Switch
checked={reshare} checked={reshare}
disabled={false} disabled={false}
size="large" size="large"
textPosition="right" textPosition="right"
className='transfer-repo-reshare-switch w-100 mt-3 mb-1' className='transfer-repo-reshare-switch w-100 mt-6 mb-1'
onChange={this.toggleReshareStatus} onChange={this.toggleReshareStatus}
placeholder={gettext('Keep sharing')} placeholder={gettext('Keep sharing')}
/> />
<div className='tip'>{gettext('If the library is shared to another department, the sharing will be kept.')}</div> <div className='tip'>{gettext('If the library is shared to another department, the sharing will be kept.')}</div>
</TabPane>} </TabPane>
</Fragment> }
</>
</TabContent> </TabContent>
<ModalFooter>
<Button color="secondary" onClick={this.props.toggleDialog}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={this.submit} disabled={buttonDisabled}>{gettext('Submit')}</Button>
</ModalFooter>
</div> </div>
</Fragment> </>
); );
}; };
render() { render() {
const { selectedOption, activeTab } = this.state; let { itemName } = this.props;
const { itemName: repoName } = this.props;
let title = gettext('Transfer Library {library_name}'); let title = gettext('Transfer Library {library_name}');
title = title.replace('{library_name}', '<span class="op-target text-truncate mx-1">' + Utils.HTMLescape(repoName) + '</span>'); title = title.replace('{library_name}', '<span class="op-target text-truncate mx-1">' + Utils.HTMLescape(itemName) + '</span>');
let buttonDisabled = false;
if (activeTab === TRANS_DEPART) {
if (selectedOption === null || (Array.isArray(selectedOption) && selectedOption.length === 0)) {
buttonDisabled = true;
}
} else {
if (this.state.selectedUsers.length === 0) {
buttonDisabled = true;
}
}
return ( return (
<Modal isOpen={true} style={{ maxWidth: '720px' }} toggle={this.props.toggleDialog} className="transfer-dialog"> <Modal isOpen={true} style={{ maxWidth: '720px' }} toggle={this.props.toggleDialog} className="transfer-dialog">
<SeahubModalHeader toggle={this.props.toggleDialog}> <SeahubModalHeader toggle={this.props.toggleDialog}>
@@ -234,10 +253,6 @@ class TransferDialog extends React.Component {
<ModalBody className="transfer-dialog-content" role="tablist"> <ModalBody className="transfer-dialog-content" role="tablist">
{this.renderTransContent()} {this.renderTransContent()}
</ModalBody> </ModalBody>
<ModalFooter>
<Button color="secondary" onClick={this.props.toggleDialog}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={this.submit} disabled={buttonDisabled}>{gettext('Submit')}</Button>
</ModalFooter>
</Modal> </Modal>
); );
} }

View File

@@ -4,7 +4,6 @@
margin: 2px 10px 2px 0; margin: 2px 10px 2px 0;
padding: 0 8px 0 2px; padding: 0 8px 0 2px;
height: 20px; height: 20px;
font-size: 13px;
border-radius: 10px; border-radius: 10px;
background: #eaeaea; background: #eaeaea;
} }
@@ -44,7 +43,6 @@
display: inline-block; display: inline-block;
font-size: 12px; font-size: 12px;
color: #909090; color: #909090;
transform: scale(.8);
cursor: pointer; cursor: pointer;
} }

View File

@@ -14,7 +14,8 @@ import ClickOutside from './click-outside';
import '../css/user-select.css'; import '../css/user-select.css';
const propTypes = { const propTypes = {
placeholder: PropTypes.string.isRequired, placeholder: PropTypes.string,
searchPlaceholder: PropTypes.string,
onSelectChange: PropTypes.func.isRequired, onSelectChange: PropTypes.func.isRequired,
isMulti: PropTypes.bool, isMulti: PropTypes.bool,
className: PropTypes.string, className: PropTypes.string,
@@ -162,16 +163,20 @@ class UserSelect extends React.Component {
onUserClick = (user) => { onUserClick = (user) => {
const { isMulti = true } = this.props; const { isMulti = true } = this.props;
let selectedUsers = this.props.selectedUsers.slice(0); let selectedUsers = this.props.selectedUsers.slice(0);
if (isMulti) {
const index = selectedUsers.findIndex(item => item.email === user.email); const index = selectedUsers.findIndex(item => item.email === user.email);
if (isMulti) {
if (index > -1) { if (index > -1) {
selectedUsers.splice(index, 1); selectedUsers.splice(index, 1);
} else { } else {
selectedUsers.push(user); selectedUsers.push(user);
} }
} else {
if (index > -1) {
selectedUsers = [];
} else { } else {
selectedUsers = [user]; selectedUsers = [user];
} }
}
this.props.onSelectChange(selectedUsers); this.props.onSelectChange(selectedUsers);
}; };
@@ -229,7 +234,7 @@ class UserSelect extends React.Component {
<div className="user-search-container"> <div className="user-search-container">
<SearchInput <SearchInput
autoFocus={true} autoFocus={true}
placeholder={this.props.placeholder || gettext('Search users')} placeholder={this.props.searchPlaceholder || gettext('Search users')}
value={searchValue} value={searchValue}
onChange={this.onValueChanged} onChange={this.onValueChanged}
onKeyDown={this.onKeyDown} onKeyDown={this.onKeyDown}
@@ -246,6 +251,7 @@ class UserSelect extends React.Component {
onClick={this.onUserClick.bind(this, user)} onClick={this.onUserClick.bind(this, user)}
> >
<UserItem user={user} enableDeleteUser={false} /> <UserItem user={user} enableDeleteUser={false} />
{selectedUsers.find(u => u.email === user.email) && <i className="sf2-icon-tick"></i>}
</div> </div>
); );
}) })

View File

@@ -118,6 +118,7 @@
.tip { .tip {
color: #666; color: #666;
font-size: 13px;
margin-bottom: 1rem; margin-bottom: 1rem;
} }

View File

@@ -41,12 +41,14 @@
.transfer-dialog-content .transfer-dialog-main { .transfer-dialog-content .transfer-dialog-main {
display: flex; display: flex;
flex-direction: column;
flex-basis: 78%; flex-basis: 78%;
padding: 1rem;
} }
.transfer-dialog-content .transfer-dialog-main .tab-content { .transfer-dialog-content .transfer-dialog-main .tab-content {
flex: 1; flex: 1;
padding: 1rem;
min-height: 400px;
} }
.transfer-dialog-content .transfer-dialog-main .tab-pane { .transfer-dialog-content .transfer-dialog-main .tab-pane {
@@ -57,8 +59,7 @@
color: #666; color: #666;
} }
.transfer-dialog-content .transfer-dialog-main .user-select, .transfer-dialog-content .transfer-dialog-main .user-select {
.transfer-dialog-content .transfer-dialog-main .transfer-repo-select-department {
padding: 8px 0; padding: 8px 0;
border-top: 1px solid #eee; border-top: 1px solid #eee;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;

View File

@@ -4,7 +4,7 @@
} }
.selected-user-item-container .user-select-placeholder { .selected-user-item-container .user-select-placeholder {
color: #808080; color: #999;
} }
.user-select-container { .user-select-container {