mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-06 17:33:18 +00:00
Change select group UI (#7201)
* 01 share repo to group UI * 02 ADD_SHARED_REPO_INTO_GROUP * 03 share folder to groups * 04 system admin share repo to group * 05 remove old NoGroupMessage * 06 change API * change API * change select icons indents
This commit is contained in:
@@ -5,7 +5,7 @@ export const EVENT_BUS_TYPE = {
|
|||||||
|
|
||||||
// group
|
// group
|
||||||
ADD_NEW_GROUP: 'add_new_group',
|
ADD_NEW_GROUP: 'add_new_group',
|
||||||
ADD_SHARED_REPO_INO_GROUP: 'add_shared_repo_into_group',
|
ADD_SHARED_REPO_INTO_GROUP: 'add_shared_repo_into_group',
|
||||||
UNSHARE_REPO_TO_GROUP: 'unshare_repo_to_group',
|
UNSHARE_REPO_TO_GROUP: 'unshare_repo_to_group',
|
||||||
|
|
||||||
RESTORE_IMAGE: 'restore_image',
|
RESTORE_IMAGE: 'restore_image',
|
||||||
|
43
frontend/src/components/common/group-select/click-outside.js
Normal file
43
frontend/src/components/common/group-select/click-outside.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
class ClickOutside extends React.Component {
|
||||||
|
|
||||||
|
isClickedInside = false;
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
document.addEventListener('mousedown', this.handleDocumentClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
document.removeEventListener('mousedown', this.handleDocumentClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDocumentClick = (e) => {
|
||||||
|
if (this.isClickedInside) {
|
||||||
|
this.isClickedInside = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.onClickOutside(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleMouseDown = () => {
|
||||||
|
this.isClickedInside = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return React.cloneElement(
|
||||||
|
React.Children.only(this.props.children), {
|
||||||
|
onMouseDownCapture: this.handleMouseDown
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ClickOutside.propTypes = {
|
||||||
|
children: PropTypes.element.isRequired,
|
||||||
|
onClickOutside: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ClickOutside;
|
103
frontend/src/components/common/group-select/index.css
Normal file
103
frontend/src/components/common/group-select/index.css
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
.group-select {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-select.custom-select {
|
||||||
|
display: flex;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 3px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
max-width: 900px;
|
||||||
|
user-select: none;
|
||||||
|
text-align: left;
|
||||||
|
border-color: hsl(0, 0%, 80%);
|
||||||
|
height: auto;
|
||||||
|
min-height: 38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-select.custom-select:hover {
|
||||||
|
border-color: hsl(0, 0%, 70%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-select.custom-select:focus,
|
||||||
|
.group-select.custom-select.focus {
|
||||||
|
border-color: #1991eb !important;
|
||||||
|
box-shadow: 0 0 0 2px rgba(70, 127, 207, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-select.custom-select.disabled:focus,
|
||||||
|
.group-select.custom-select.focus.disabled,
|
||||||
|
.group-select.custom-select.disabled:hover {
|
||||||
|
border-color: rgba(0, 40, 100, 0.12) !important;
|
||||||
|
box-shadow: unset;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-select.custom-select:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
border-color: rgb(179, 179, 179);
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-select .sf3-font-down {
|
||||||
|
display: inline-block;
|
||||||
|
color: #999;
|
||||||
|
transform: translateY(2px);
|
||||||
|
transition: all 0.1s;
|
||||||
|
font-size: 14px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-select .sf3-font-down:hover {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-select .selected-option {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-select.selector-collaborator .option-group .option-group-content {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-select.custom-select.selector-collaborator .option-group .option-group-content {
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-select.custom-select.selector-collaborator .option {
|
||||||
|
padding: 5px 0 5px 10px !important;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-select .select-placeholder {
|
||||||
|
line-height: 1;
|
||||||
|
font-size: 14px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-select .selected-option-show {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-select .selected-option-show .selected-option-item {
|
||||||
|
background-color: rgb(240, 240, 240);
|
||||||
|
border-radius: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-select .selected-option-show .selected-option-item .selected-option-item-name {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-select .selected-option-show .selected-option-item .sf2-icon-close {
|
||||||
|
cursor: pointer;
|
||||||
|
color: rgb(103, 103, 103);
|
||||||
|
}
|
138
frontend/src/components/common/group-select/index.js
Normal file
138
frontend/src/components/common/group-select/index.js
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import ModalPortal from '../../modal-portal';
|
||||||
|
import SelectOptionGroup from './select-option-group.js';
|
||||||
|
|
||||||
|
import './index.css';
|
||||||
|
|
||||||
|
class GroupSelect extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isShowSelectOptions: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectToggle = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (this.state.isShowSelectOptions) event.stopPropagation();
|
||||||
|
let eventClassName = event.target.className;
|
||||||
|
if (eventClassName.indexOf('sf2-icon-close') > -1 || eventClassName === 'option-group-search') return;
|
||||||
|
if (event.target.value === '') return;
|
||||||
|
this.setState({
|
||||||
|
isShowSelectOptions: !this.state.isShowSelectOptions
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onClickOutside = (event) => {
|
||||||
|
if (this.props.isShowSelected && event.target.className.includes('icon-fork-number')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.selector.contains(event.target)) {
|
||||||
|
this.closeSelect();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
closeSelect = () => {
|
||||||
|
this.setState({ isShowSelectOptions: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||||
|
if (nextProps.selectedOptions.length !== this.props.selectedOptions.length) {
|
||||||
|
// when selectedOptions change and dom rendered, calculate top
|
||||||
|
setTimeout(() => {
|
||||||
|
this.forceUpdate();
|
||||||
|
}, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getSelectedOptionTop = () => {
|
||||||
|
if (!this.selector) return 38;
|
||||||
|
const { height } = this.selector.getBoundingClientRect();
|
||||||
|
return height;
|
||||||
|
};
|
||||||
|
|
||||||
|
getFilterOptions = (searchValue) => {
|
||||||
|
const { options } = this.props;
|
||||||
|
const validSearchVal = searchValue.trim().toLowerCase();
|
||||||
|
if (!validSearchVal) return options || [];
|
||||||
|
return options.filter(option => option.name.toLowerCase().includes(validSearchVal));
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let { className, selectedOptions, options, placeholder, searchPlaceholder, noOptionsPlaceholder, isInModal } = this.props;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={(node) => this.selector = node}
|
||||||
|
className={classnames('group-select custom-select',
|
||||||
|
{ 'focus': this.state.isShowSelectOptions },
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
onClick={this.onSelectToggle}>
|
||||||
|
<div className="selected-option">
|
||||||
|
{selectedOptions.length > 0 ?
|
||||||
|
<span className="selected-option-show">
|
||||||
|
{selectedOptions.map(item =>
|
||||||
|
<span key={item.id} className="selected-option-item mr-1 px-1">
|
||||||
|
<span className='selected-option-item-name'>{item.name}</span>
|
||||||
|
<i className="sf2-icon-close ml-1" onClick={() => {this.props.onDeleteOption(item);}}></i>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
:
|
||||||
|
<span className="select-placeholder">{placeholder}</span>
|
||||||
|
}
|
||||||
|
<i className="sf3-font-down sf3-font"></i>
|
||||||
|
</div>
|
||||||
|
{this.state.isShowSelectOptions && !isInModal && (
|
||||||
|
<SelectOptionGroup
|
||||||
|
selectedOptions={selectedOptions}
|
||||||
|
top={this.getSelectedOptionTop()}
|
||||||
|
options={options}
|
||||||
|
onSelectOption={this.props.onSelectOption}
|
||||||
|
searchPlaceholder={searchPlaceholder}
|
||||||
|
noOptionsPlaceholder={noOptionsPlaceholder}
|
||||||
|
onClickOutside={this.onClickOutside}
|
||||||
|
closeSelect={this.closeSelect}
|
||||||
|
getFilterOptions={this.getFilterOptions}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{this.state.isShowSelectOptions && isInModal && (
|
||||||
|
<ModalPortal>
|
||||||
|
<SelectOptionGroup
|
||||||
|
className={className}
|
||||||
|
selectedOptions={selectedOptions}
|
||||||
|
position={this.selector.getBoundingClientRect()}
|
||||||
|
isInModal={isInModal}
|
||||||
|
top={this.getSelectedOptionTop()}
|
||||||
|
options={options}
|
||||||
|
onSelectOption={this.props.onSelectOption}
|
||||||
|
searchPlaceholder={searchPlaceholder}
|
||||||
|
noOptionsPlaceholder={noOptionsPlaceholder}
|
||||||
|
onClickOutside={this.onClickOutside}
|
||||||
|
closeSelect={this.closeSelect}
|
||||||
|
getFilterOptions={this.getFilterOptions}
|
||||||
|
/>
|
||||||
|
</ModalPortal>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupSelect.propTypes = {
|
||||||
|
className: PropTypes.string,
|
||||||
|
selectedOptions: PropTypes.array,
|
||||||
|
options: PropTypes.array,
|
||||||
|
placeholder: PropTypes.string,
|
||||||
|
onSelectOption: PropTypes.func,
|
||||||
|
onDeleteOption: PropTypes.func,
|
||||||
|
searchable: PropTypes.bool,
|
||||||
|
searchPlaceholder: PropTypes.string,
|
||||||
|
noOptionsPlaceholder: PropTypes.string,
|
||||||
|
isInModal: PropTypes.bool, // if select component in a modal (option group need ModalPortal to show)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GroupSelect;
|
46
frontend/src/components/common/group-select/option.js
Normal file
46
frontend/src/components/common/group-select/option.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
class Option extends Component {
|
||||||
|
|
||||||
|
onSelectOption = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.props.onSelectOption(this.props.option);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMouseEnter = () => {
|
||||||
|
if (!this.props.disableHover) {
|
||||||
|
this.props.changeIndex(this.props.index);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMouseLeave = () => {
|
||||||
|
if (!this.props.disableHover) {
|
||||||
|
this.props.changeIndex(-1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={this.props.isActive ? 'option option-active' : 'option'}
|
||||||
|
onClick={this.onSelectOption}
|
||||||
|
onMouseEnter={this.onMouseEnter}
|
||||||
|
onMouseLeave={this.onMouseLeave}
|
||||||
|
>{this.props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Option.propTypes = {
|
||||||
|
index: PropTypes.number,
|
||||||
|
isActive: PropTypes.bool,
|
||||||
|
changeIndex: PropTypes.func,
|
||||||
|
option: PropTypes.object,
|
||||||
|
children: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
|
||||||
|
onSelectOption: PropTypes.func,
|
||||||
|
disableHover: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Option;
|
@@ -0,0 +1,86 @@
|
|||||||
|
.option-group {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
min-height: 60px;
|
||||||
|
max-height: 300px;
|
||||||
|
min-width: 100%;
|
||||||
|
max-width: 15rem;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid rgba(0, 40, 100, 0.12);
|
||||||
|
border-radius: 3px;
|
||||||
|
z-index: 10001;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-group .option-group-search {
|
||||||
|
width: 100%;
|
||||||
|
padding: 6px 10px;
|
||||||
|
min-width: 170px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-group-search .form-control {
|
||||||
|
height: 31px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-group .none-search-result {
|
||||||
|
height: 100px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-group .option-group-content {
|
||||||
|
max-height: 252px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
line-height: 24px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
clear: both;
|
||||||
|
font-weight: 400;
|
||||||
|
text-align: inherit;
|
||||||
|
background-color: transparent;
|
||||||
|
border: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option.option-active {
|
||||||
|
background-color: #20a0ff;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option.option-active .sf2-icon-tick,
|
||||||
|
.option.option-active .select-option-name {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option .select-option-name .single-select-option {
|
||||||
|
margin: 0 0 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option .select-option-name .multiple-select-option {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-group-selector-single-select .select-option-name {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-group-selector-single-select .option:hover,
|
||||||
|
.option-group-selector-single-select .option.option-active,
|
||||||
|
.option-group-selector-multiple-select .option:hover,
|
||||||
|
.option-group-selector-multiple-select .option.option-active {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
@@ -0,0 +1,215 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import { DTableSearchInput } from 'dtable-ui-component';
|
||||||
|
import Option from './option';
|
||||||
|
import KeyCodes from '../../../constants/keyCodes';
|
||||||
|
import ClickOutside from './click-outside';
|
||||||
|
|
||||||
|
import './select-option-group.css';
|
||||||
|
|
||||||
|
const OPTION_HEIGHT = 32;
|
||||||
|
|
||||||
|
class SelectOptionGroup extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
searchVal: '',
|
||||||
|
activeIndex: -1,
|
||||||
|
disableHover: false,
|
||||||
|
};
|
||||||
|
this.filterOptions = null;
|
||||||
|
this.timer = null;
|
||||||
|
this.searchInputRef = React.createRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
window.addEventListener('keydown', this.onHotKey);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.resetMenuStyle();
|
||||||
|
}, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.filterOptions = null;
|
||||||
|
this.timer && clearTimeout(this.timer);
|
||||||
|
window.removeEventListener('keydown', this.onHotKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetMenuStyle = () => {
|
||||||
|
const { isInModal, position } = this.props;
|
||||||
|
const { top, height } = this.optionGroupRef.getBoundingClientRect();
|
||||||
|
if (isInModal) {
|
||||||
|
if (position.y + position.height + height > window.innerHeight) {
|
||||||
|
this.optionGroupRef.style.top = (position.y - height) + 'px';
|
||||||
|
}
|
||||||
|
this.optionGroupRef.style.opacity = 1;
|
||||||
|
this.searchInputRef.current && this.searchInputRef.current.inputRef.focus();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (height + top > window.innerHeight) {
|
||||||
|
const borderWidth = 2;
|
||||||
|
this.optionGroupRef.style.top = -1 * (height + borderWidth) + 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onHotKey = (event) => {
|
||||||
|
const keyCode = event.keyCode;
|
||||||
|
if (keyCode === KeyCodes.UpArrow) {
|
||||||
|
this.onPressUp();
|
||||||
|
} else if (keyCode === KeyCodes.DownArrow) {
|
||||||
|
this.onPressDown();
|
||||||
|
} else if (keyCode === KeyCodes.Enter) {
|
||||||
|
let option = this.filterOptions && this.filterOptions[this.state.activeIndex];
|
||||||
|
if (option) {
|
||||||
|
this.props.onSelectOption(option);
|
||||||
|
}
|
||||||
|
} else if (keyCode === KeyCodes.Tab || keyCode === KeyCodes.Escape) {
|
||||||
|
this.props.closeSelect();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onPressUp = () => {
|
||||||
|
if (this.state.activeIndex > 0) {
|
||||||
|
this.setState({ activeIndex: this.state.activeIndex - 1 }, () => {
|
||||||
|
this.scrollContent();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onPressDown = () => {
|
||||||
|
if (this.filterOptions && this.state.activeIndex < this.filterOptions.length - 1) {
|
||||||
|
this.setState({ activeIndex: this.state.activeIndex + 1 }, () => {
|
||||||
|
this.scrollContent();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMouseDown = (e) => {
|
||||||
|
const { isInModal } = this.props;
|
||||||
|
// prevent event propagation when click option or search input
|
||||||
|
if (isInModal) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.nativeEvent.stopImmediatePropagation();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
scrollContent = () => {
|
||||||
|
const { offsetHeight, scrollTop } = this.optionGroupContentRef;
|
||||||
|
this.setState({ disableHover: true });
|
||||||
|
this.timer = setTimeout(() => {
|
||||||
|
this.setState({ disableHover: false });
|
||||||
|
}, 500);
|
||||||
|
if (this.state.activeIndex * OPTION_HEIGHT === 0) {
|
||||||
|
this.optionGroupContentRef.scrollTop = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.activeIndex * OPTION_HEIGHT < scrollTop) {
|
||||||
|
this.optionGroupContentRef.scrollTop = scrollTop - OPTION_HEIGHT;
|
||||||
|
}
|
||||||
|
else if (this.state.activeIndex * OPTION_HEIGHT > offsetHeight + scrollTop) {
|
||||||
|
this.optionGroupContentRef.scrollTop = scrollTop + OPTION_HEIGHT;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
changeIndex = (index) => {
|
||||||
|
this.setState({ activeIndex: index });
|
||||||
|
};
|
||||||
|
|
||||||
|
onChangeSearch = (searchVal) => {
|
||||||
|
this.setState({ searchVal: searchVal || '', activeIndex: -1, });
|
||||||
|
};
|
||||||
|
|
||||||
|
clearValue = () => {
|
||||||
|
this.setState({ searchVal: '', activeIndex: -1, });
|
||||||
|
};
|
||||||
|
|
||||||
|
renderOptGroup = (searchVal) => {
|
||||||
|
let { noOptionsPlaceholder, onSelectOption, selectedOptions } = this.props;
|
||||||
|
this.filterOptions = this.props.getFilterOptions(searchVal);
|
||||||
|
if (this.filterOptions.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className="none-search-result">{noOptionsPlaceholder}</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return this.filterOptions.map((option, index) => {
|
||||||
|
const isSelected = selectedOptions.some(item => item.id === option.id);
|
||||||
|
return (
|
||||||
|
<Option
|
||||||
|
key={`${option.id}-${index}`}
|
||||||
|
index={index}
|
||||||
|
isActive={this.state.activeIndex === index}
|
||||||
|
option={option}
|
||||||
|
onSelectOption={onSelectOption}
|
||||||
|
changeIndex={this.changeIndex}
|
||||||
|
disableHover={this.state.disableHover}
|
||||||
|
>
|
||||||
|
<div className='option-label'>{option.label}</div>
|
||||||
|
{isSelected && <i className="sf2-icon-tick text-gray font-weight-bold"></i>}
|
||||||
|
</Option>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { searchPlaceholder, top, left, minWidth, isInModal, position, className } = this.props;
|
||||||
|
let { searchVal } = this.state;
|
||||||
|
let style = { top: top || 0, left: left || 0 };
|
||||||
|
if (minWidth) {
|
||||||
|
style = { top: top || 0, left: left || 0, minWidth };
|
||||||
|
}
|
||||||
|
if (isInModal) {
|
||||||
|
style = {
|
||||||
|
position: 'fixed',
|
||||||
|
left: position.x,
|
||||||
|
top: position.y + position.height,
|
||||||
|
minWidth: position.width,
|
||||||
|
opacity: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<ClickOutside onClickOutside={this.props.onClickOutside}>
|
||||||
|
<div
|
||||||
|
className={classnames('pt-0 option-group', className ? 'option-group-' + className : '')}
|
||||||
|
ref={(ref) => this.optionGroupRef = ref}
|
||||||
|
style={style}
|
||||||
|
onMouseDown={this.onMouseDown}
|
||||||
|
>
|
||||||
|
<div className="option-group-search position-relative">
|
||||||
|
<DTableSearchInput
|
||||||
|
className="option-search-control"
|
||||||
|
placeholder={searchPlaceholder}
|
||||||
|
onChange={this.onChangeSearch}
|
||||||
|
ref={this.searchInputRef}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="option-group-content" ref={(ref) => this.optionGroupContentRef = ref}>
|
||||||
|
{this.renderOptGroup(searchVal)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ClickOutside>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectOptionGroup.propTypes = {
|
||||||
|
top: PropTypes.number,
|
||||||
|
left: PropTypes.number,
|
||||||
|
minWidth: PropTypes.number,
|
||||||
|
options: PropTypes.array,
|
||||||
|
onSelectOption: PropTypes.func,
|
||||||
|
searchPlaceholder: PropTypes.string,
|
||||||
|
noOptionsPlaceholder: PropTypes.string,
|
||||||
|
onClickOutside: PropTypes.func.isRequired,
|
||||||
|
closeSelect: PropTypes.func.isRequired,
|
||||||
|
getFilterOptions: PropTypes.func.isRequired,
|
||||||
|
selectedOptions: PropTypes.array,
|
||||||
|
isInModal: PropTypes.bool,
|
||||||
|
position: PropTypes.object,
|
||||||
|
className: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SelectOptionGroup;
|
@@ -1,5 +1,4 @@
|
|||||||
import SeahubSelect from './seahub-select';
|
import SeahubSelect from './seahub-select';
|
||||||
import { NoGroupMessage } from './no-group-message';
|
|
||||||
import { MenuSelectStyle, UserSelectStyle, NoOptionsStyle } from './seahub-select-style';
|
import { MenuSelectStyle, UserSelectStyle, NoOptionsStyle } from './seahub-select-style';
|
||||||
|
|
||||||
export { SeahubSelect, NoGroupMessage, MenuSelectStyle, UserSelectStyle, NoOptionsStyle };
|
export { SeahubSelect, MenuSelectStyle, UserSelectStyle, NoOptionsStyle };
|
||||||
|
@@ -1,16 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { gettext } from '../../../utils/constants';
|
|
||||||
import { NoOptionsStyle } from './seahub-select-style';
|
|
||||||
|
|
||||||
const NoGroupMessage = (props) => {
|
|
||||||
return (
|
|
||||||
<div {...props.innerProps} style={NoOptionsStyle}>{gettext('Group not found')}</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
NoGroupMessage.propTypes = {
|
|
||||||
innerProps: PropTypes.any.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export { NoGroupMessage };
|
|
@@ -3,13 +3,27 @@ import PropTypes from 'prop-types';
|
|||||||
import Select, { components } from 'react-select';
|
import Select, { components } from 'react-select';
|
||||||
import { MenuSelectStyle } from './seahub-select-style';
|
import { MenuSelectStyle } from './seahub-select-style';
|
||||||
|
|
||||||
|
const DropdownIndicator = props => {
|
||||||
|
return (
|
||||||
|
components.DropdownIndicator && (
|
||||||
|
<components.DropdownIndicator {...props}>
|
||||||
|
<span className="sf3-font sf3-font-down" style={{ fontSize: '12px', marginLeft: '-2px' }} aria-hidden="true"></span>
|
||||||
|
</components.DropdownIndicator>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const ClearIndicator = ({ innerProps, ...props }) => {
|
const ClearIndicator = ({ innerProps, ...props }) => {
|
||||||
const onMouseDown = e => {
|
const onMouseDown = e => {
|
||||||
e.nativeEvent.stopImmediatePropagation();
|
e.nativeEvent.stopImmediatePropagation();
|
||||||
innerProps.onMouseDown(e);
|
innerProps.onMouseDown(e);
|
||||||
};
|
};
|
||||||
props.innerProps = { ...innerProps, onMouseDown };
|
props.innerProps = { ...innerProps, onMouseDown };
|
||||||
return <components.ClearIndicator {...props} />;
|
return (
|
||||||
|
<components.ClearIndicator {...props} >
|
||||||
|
<span className="sf3-font sf3-font-x-01" style={{ fontSize: '12px', marginLeft: '-2px' }} aria-hidden="true"></span>
|
||||||
|
</components.ClearIndicator>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
ClearIndicator.propTypes = {
|
ClearIndicator.propTypes = {
|
||||||
@@ -93,7 +107,7 @@ export default class SeahubSelect extends React.Component {
|
|||||||
className={className}
|
className={className}
|
||||||
classNamePrefix={classNamePrefix}
|
classNamePrefix={classNamePrefix}
|
||||||
styles={MenuSelectStyle}
|
styles={MenuSelectStyle}
|
||||||
components={{ Option, MenuList, ClearIndicator }}
|
components={{ Option, DropdownIndicator, MenuList, ClearIndicator }}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
isSearchable={isSearchable}
|
isSearchable={isSearchable}
|
||||||
isClearable={isClearable}
|
isClearable={isClearable}
|
||||||
|
@@ -6,7 +6,7 @@ import { seafileAPI } from '../../utils/seafile-api';
|
|||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
import SharePermissionEditor from '../select-editor/share-permission-editor';
|
import SharePermissionEditor from '../select-editor/share-permission-editor';
|
||||||
import FileChooser from '../file-chooser';
|
import FileChooser from '../file-chooser';
|
||||||
import { SeahubSelect, NoGroupMessage } from '../common/select';
|
import GroupSelect from '../common/group-select';
|
||||||
import toaster from '../../components/toast';
|
import toaster from '../../components/toast';
|
||||||
|
|
||||||
class GroupItem extends React.Component {
|
class GroupItem extends React.Component {
|
||||||
@@ -96,7 +96,7 @@ class LibSubFolderSetGroupPermissionDialog extends React.Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
selectedOption: null,
|
selectedOptions: [],
|
||||||
errorMsg: [],
|
errorMsg: [],
|
||||||
permission: 'rw',
|
permission: 'rw',
|
||||||
groupPermissionItems: [],
|
groupPermissionItems: [],
|
||||||
@@ -111,10 +111,6 @@ class LibSubFolderSetGroupPermissionDialog extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSelectChange = (option) => {
|
|
||||||
this.setState({ selectedOption: option });
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.loadOptions();
|
this.loadOptions();
|
||||||
this.listGroupPermissionItems();
|
this.listGroupPermissionItems();
|
||||||
@@ -126,6 +122,7 @@ class LibSubFolderSetGroupPermissionDialog extends React.Component {
|
|||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
label: item.name,
|
label: item.name,
|
||||||
|
name: item.name,
|
||||||
value: item.name
|
value: item.name
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -152,20 +149,41 @@ class LibSubFolderSetGroupPermissionDialog extends React.Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onSelectOption = (option) => {
|
||||||
|
const selectedOptions = this.state.selectedOptions.slice(0);
|
||||||
|
const index = selectedOptions.findIndex(item => item.id === option.id);
|
||||||
|
if (index > -1) {
|
||||||
|
selectedOptions.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
selectedOptions.push(option);
|
||||||
|
}
|
||||||
|
this.setState({ selectedOptions: selectedOptions });
|
||||||
|
};
|
||||||
|
|
||||||
|
onDeleteOption = (option) => {
|
||||||
|
const selectedOptions = this.state.selectedOptions.slice(0);
|
||||||
|
const index = selectedOptions.findIndex(item => item.id === option.id);
|
||||||
|
if (index > -1) {
|
||||||
|
selectedOptions.splice(index, 1);
|
||||||
|
}
|
||||||
|
this.setState({ selectedOptions: selectedOptions });
|
||||||
|
};
|
||||||
|
|
||||||
setPermission = (permission) => {
|
setPermission = (permission) => {
|
||||||
this.setState({ permission: permission });
|
this.setState({ permission: permission });
|
||||||
};
|
};
|
||||||
|
|
||||||
addGroupFolderPerm = () => {
|
addGroupFolderPerm = () => {
|
||||||
const { selectedOption } = this.state;
|
const { selectedOptions } = this.state;
|
||||||
const folderPath = this.props.folderPath || this.state.folderPath;
|
const folderPath = this.props.folderPath || this.state.folderPath;
|
||||||
if (!selectedOption || !folderPath) {
|
if (selectedOptions.length === 0 || !folderPath) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const targetGroupIds = selectedOptions.map(op => op.id);
|
||||||
const request = this.props.isDepartmentRepo ?
|
const request = this.props.isDepartmentRepo ?
|
||||||
seafileAPI.addDepartmentRepoGroupFolderPerm(this.props.repoID, this.state.permission, folderPath, selectedOption.id) :
|
seafileAPI.addDepartmentRepoGroupFolderPerm(this.props.repoID, this.state.permission, folderPath, targetGroupIds) :
|
||||||
seafileAPI.addGroupFolderPerm(this.props.repoID, this.state.permission, folderPath, selectedOption.id);
|
seafileAPI.addGroupFolderPerm(this.props.repoID, this.state.permission, folderPath, targetGroupIds);
|
||||||
request.then(res => {
|
request.then(res => {
|
||||||
let errorMsg = [];
|
let errorMsg = [];
|
||||||
if (res.data.failed.length > 0) {
|
if (res.data.failed.length > 0) {
|
||||||
@@ -176,7 +194,7 @@ class LibSubFolderSetGroupPermissionDialog extends React.Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
errorMsg: errorMsg,
|
errorMsg: errorMsg,
|
||||||
groupPermissionItems: this.state.groupPermissionItems.concat(res.data.success),
|
groupPermissionItems: this.state.groupPermissionItems.concat(res.data.success),
|
||||||
selectedOption: null,
|
selectedOptions: [],
|
||||||
permission: 'rw',
|
permission: 'rw',
|
||||||
folderPath: ''
|
folderPath: ''
|
||||||
});
|
});
|
||||||
@@ -296,13 +314,14 @@ class LibSubFolderSetGroupPermissionDialog extends React.Component {
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<SeahubSelect
|
<GroupSelect
|
||||||
onChange={this.handleSelectChange}
|
selectedOptions={this.state.selectedOptions}
|
||||||
options={this.options}
|
options={this.options}
|
||||||
placeholder={gettext('Select a group')}
|
onSelectOption={this.onSelectOption}
|
||||||
maxMenuHeight={200}
|
onDeleteOption={this.onDeleteOption}
|
||||||
value={this.state.selectedOption}
|
searchPlaceholder={gettext('Search groups')}
|
||||||
noOptionsMessage={NoGroupMessage}
|
noOptionsPlaceholder={gettext('No results')}
|
||||||
|
isInModal={true}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
{showPath &&
|
{showPath &&
|
||||||
|
@@ -6,9 +6,9 @@ import { seafileAPI } from '../../utils/seafile-api';
|
|||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
import toaster from '../toast';
|
import toaster from '../toast';
|
||||||
import SharePermissionEditor from '../select-editor/share-permission-editor';
|
import SharePermissionEditor from '../select-editor/share-permission-editor';
|
||||||
import { SeahubSelect, NoGroupMessage } from '../common/select';
|
|
||||||
import EventBus from '../common/event-bus';
|
import EventBus from '../common/event-bus';
|
||||||
import { EVENT_BUS_TYPE } from '../common/event-bus-type';
|
import { EVENT_BUS_TYPE } from '../common/event-bus-type';
|
||||||
|
import GroupSelect from '../common/group-select';
|
||||||
|
|
||||||
class GroupItem extends React.Component {
|
class GroupItem extends React.Component {
|
||||||
|
|
||||||
@@ -127,7 +127,7 @@ class ShareToGroup extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
options: [],
|
options: [],
|
||||||
selectedOption: null,
|
selectedOptions: [],
|
||||||
errorMsg: [],
|
errorMsg: [],
|
||||||
permission: 'rw',
|
permission: 'rw',
|
||||||
sharedItems: [],
|
sharedItems: [],
|
||||||
@@ -151,8 +151,24 @@ class ShareToGroup extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSelectChange = (option) => {
|
onSelectOption = (option) => {
|
||||||
this.setState({ selectedOption: option });
|
const selectedOptions = this.state.selectedOptions.slice(0);
|
||||||
|
const index = selectedOptions.findIndex(item => item.id === option.id);
|
||||||
|
if (index > -1) {
|
||||||
|
selectedOptions.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
selectedOptions.push(option);
|
||||||
|
}
|
||||||
|
this.setState({ selectedOptions: selectedOptions });
|
||||||
|
};
|
||||||
|
|
||||||
|
onDeleteOption = (option) => {
|
||||||
|
const selectedOptions = this.state.selectedOptions.slice(0);
|
||||||
|
const index = selectedOptions.findIndex(item => item.id === option.id);
|
||||||
|
if (index > -1) {
|
||||||
|
selectedOptions.splice(index, 1);
|
||||||
|
}
|
||||||
|
this.setState({ selectedOptions: selectedOptions });
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -174,6 +190,7 @@ class ShareToGroup extends React.Component {
|
|||||||
obj.value = res.data[i].name;
|
obj.value = res.data[i].name;
|
||||||
obj.id = res.data[i].id;
|
obj.id = res.data[i].id;
|
||||||
obj.label = res.data[i].name;
|
obj.label = res.data[i].name;
|
||||||
|
obj.name = res.data[i].name;
|
||||||
options.push(obj);
|
options.push(obj);
|
||||||
}
|
}
|
||||||
this.setState({ options: options });
|
this.setState({ options: options });
|
||||||
@@ -205,10 +222,10 @@ class ShareToGroup extends React.Component {
|
|||||||
shareToGroup = () => {
|
shareToGroup = () => {
|
||||||
const eventBus = EventBus.getInstance();
|
const eventBus = EventBus.getInstance();
|
||||||
const { isGroupOwnedRepo, itemPath: path, repoID } = this.props;
|
const { isGroupOwnedRepo, itemPath: path, repoID } = this.props;
|
||||||
const { permission, selectedOption } = this.state;
|
const { permission, selectedOptions } = this.state;
|
||||||
const targetGroupId = selectedOption.id;
|
const targetGroupIds = selectedOptions.map(item => item.id);
|
||||||
if (isGroupOwnedRepo) {
|
if (isGroupOwnedRepo) {
|
||||||
seafileAPI.shareGroupOwnedRepoToGroup(repoID, permission, targetGroupId, path).then(res => {
|
seafileAPI.shareGroupOwnedRepoToGroup(repoID, permission, targetGroupIds, path).then(res => {
|
||||||
let errorMsg = [];
|
let errorMsg = [];
|
||||||
if (res.data.failed.length > 0) {
|
if (res.data.failed.length > 0) {
|
||||||
for (let i = 0 ; i < res.data.failed.length ; i++) {
|
for (let i = 0 ; i < res.data.failed.length ; i++) {
|
||||||
@@ -228,13 +245,15 @@ class ShareToGroup extends React.Component {
|
|||||||
|
|
||||||
if (this.props.repo && res.data.success.length > 0) {
|
if (this.props.repo && res.data.success.length > 0) {
|
||||||
const sharedRepo = { ...this.props.repo, permission: res.data.success[0].permission };
|
const sharedRepo = { ...this.props.repo, permission: res.data.success[0].permission };
|
||||||
eventBus.dispatch(EVENT_BUS_TYPE.ADD_SHARED_REPO_INO_GROUP, { repo: sharedRepo, group_id: targetGroupId });
|
targetGroupIds.forEach(targetGroupId => {
|
||||||
|
eventBus.dispatch(EVENT_BUS_TYPE.ADD_SHARED_REPO_INTO_GROUP, { repo: sharedRepo, group_id: targetGroupId });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
errorMsg: errorMsg,
|
errorMsg: errorMsg,
|
||||||
sharedItems: this.state.sharedItems.concat(items),
|
sharedItems: this.state.sharedItems.concat(items),
|
||||||
selectedOption: null,
|
selectedOptions: [],
|
||||||
permission: 'rw',
|
permission: 'rw',
|
||||||
});
|
});
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
@@ -242,7 +261,7 @@ class ShareToGroup extends React.Component {
|
|||||||
toaster.danger(errMessage);
|
toaster.danger(errMessage);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
seafileAPI.shareFolder(repoID, path, 'group', permission, [targetGroupId]).then(res => {
|
seafileAPI.shareFolder(repoID, path, 'group', permission, targetGroupIds).then(res => {
|
||||||
let errorMsg = [];
|
let errorMsg = [];
|
||||||
if (res.data.failed.length > 0) {
|
if (res.data.failed.length > 0) {
|
||||||
for (let i = 0 ; i < res.data.failed.length ; i++) {
|
for (let i = 0 ; i < res.data.failed.length ; i++) {
|
||||||
@@ -252,13 +271,15 @@ class ShareToGroup extends React.Component {
|
|||||||
|
|
||||||
if (this.props.repo && res.data.success.length > 0) {
|
if (this.props.repo && res.data.success.length > 0) {
|
||||||
const sharedRepo = { ...this.props.repo, permission: res.data.success[0].permission };
|
const sharedRepo = { ...this.props.repo, permission: res.data.success[0].permission };
|
||||||
eventBus.dispatch(EVENT_BUS_TYPE.ADD_SHARED_REPO_INO_GROUP, { repo: sharedRepo, group_id: targetGroupId });
|
targetGroupIds.forEach(targetGroupId => {
|
||||||
|
eventBus.dispatch(EVENT_BUS_TYPE.ADD_SHARED_REPO_INTO_GROUP, { repo: sharedRepo, group_id: targetGroupId });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
errorMsg: errorMsg,
|
errorMsg: errorMsg,
|
||||||
sharedItems: this.state.sharedItems.concat(res.data.success),
|
sharedItems: this.state.sharedItems.concat(res.data.success),
|
||||||
selectedOption: null,
|
selectedOptions: [],
|
||||||
permission: 'rw'
|
permission: 'rw'
|
||||||
});
|
});
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
@@ -347,15 +368,14 @@ class ShareToGroup extends React.Component {
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<SeahubSelect
|
<GroupSelect
|
||||||
onChange={this.handleSelectChange}
|
selectedOptions={this.state.selectedOptions}
|
||||||
options={this.state.options}
|
options={this.state.options}
|
||||||
placeholder={gettext('Select groups')}
|
onSelectOption={this.onSelectOption}
|
||||||
maxMenuHeight={200}
|
onDeleteOption={this.onDeleteOption}
|
||||||
value={this.state.selectedOption}
|
searchPlaceholder={gettext('Search groups')}
|
||||||
noOptionsMessage={NoGroupMessage}
|
noOptionsPlaceholder={gettext('No results')}
|
||||||
isSearchable={true}
|
isInModal={true}
|
||||||
isClearable={true}
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
@@ -80,7 +80,7 @@ class SysAdminShareDialog extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Modal isOpen={true} style={{ maxWidth: '720px' }} className="share-dialog" toggle={this.props.toggleDialog}>
|
<Modal isOpen={true} style={{ maxWidth: '800px' }} className="share-dialog" toggle={this.props.toggleDialog}>
|
||||||
<ModalHeader toggle={this.props.toggleDialog}>{gettext('Share')} <span className="op-target" title={this.props.itemName}>{this.props.itemName}</span></ModalHeader>
|
<ModalHeader toggle={this.props.toggleDialog}>{gettext('Share')} <span className="op-target" title={this.props.itemName}>{this.props.itemName}</span></ModalHeader>
|
||||||
<ModalBody className="share-dialog-content">
|
<ModalBody className="share-dialog-content">
|
||||||
{this.renderDirContent()}
|
{this.renderDirContent()}
|
||||||
|
@@ -7,7 +7,7 @@ import { systemAdminAPI } from '../../../utils/system-admin-api';
|
|||||||
import { Utils } from '../../../utils/utils';
|
import { Utils } from '../../../utils/utils';
|
||||||
import toaster from '../../toast';
|
import toaster from '../../toast';
|
||||||
import SharePermissionEditor from '../../select-editor/share-permission-editor';
|
import SharePermissionEditor from '../../select-editor/share-permission-editor';
|
||||||
import { SeahubSelect, NoGroupMessage } from '../../common/select';
|
import GroupSelect from '../../common/group-select';
|
||||||
|
|
||||||
class GroupItem extends React.Component {
|
class GroupItem extends React.Component {
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ class SysAdminShareToGroup extends React.Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
selectedOption: null,
|
selectedOptions: [],
|
||||||
errorMsg: [],
|
errorMsg: [],
|
||||||
permission: 'rw',
|
permission: 'rw',
|
||||||
sharedItems: []
|
sharedItems: []
|
||||||
@@ -128,10 +128,6 @@ class SysAdminShareToGroup extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSelectChange = (option) => {
|
|
||||||
this.setState({ selectedOption: option });
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.loadOptions();
|
this.loadOptions();
|
||||||
this.listSharedGroups();
|
this.listSharedGroups();
|
||||||
@@ -142,6 +138,7 @@ class SysAdminShareToGroup extends React.Component {
|
|||||||
this.options = [];
|
this.options = [];
|
||||||
for (let i = 0 ; i < res.data.length; i++) {
|
for (let i = 0 ; i < res.data.length; i++) {
|
||||||
let obj = {};
|
let obj = {};
|
||||||
|
obj.name = res.data[i].name;
|
||||||
obj.value = res.data[i].name;
|
obj.value = res.data[i].name;
|
||||||
obj.id = res.data[i].id;
|
obj.id = res.data[i].id;
|
||||||
obj.label = res.data[i].name;
|
obj.label = res.data[i].name;
|
||||||
@@ -153,6 +150,26 @@ class SysAdminShareToGroup extends React.Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onSelectOption = (option) => {
|
||||||
|
const selectedOptions = this.state.selectedOptions.slice(0);
|
||||||
|
const index = selectedOptions.findIndex(item => item.id === option.id);
|
||||||
|
if (index > -1) {
|
||||||
|
selectedOptions.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
selectedOptions.push(option);
|
||||||
|
}
|
||||||
|
this.setState({ selectedOptions: selectedOptions });
|
||||||
|
};
|
||||||
|
|
||||||
|
onDeleteOption = (option) => {
|
||||||
|
const selectedOptions = this.state.selectedOptions.slice(0);
|
||||||
|
const index = selectedOptions.findIndex(item => item.id === option.id);
|
||||||
|
if (index > -1) {
|
||||||
|
selectedOptions.splice(index, 1);
|
||||||
|
}
|
||||||
|
this.setState({ selectedOptions: selectedOptions });
|
||||||
|
};
|
||||||
|
|
||||||
listSharedGroups = () => {
|
listSharedGroups = () => {
|
||||||
let repoID = this.props.repoID;
|
let repoID = this.props.repoID;
|
||||||
systemAdminAPI.sysAdminListRepoSharedItems(repoID, 'group').then((res) => {
|
systemAdminAPI.sysAdminListRepoSharedItems(repoID, 'group').then((res) => {
|
||||||
@@ -172,12 +189,11 @@ class SysAdminShareToGroup extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
shareToGroup = () => {
|
shareToGroup = () => {
|
||||||
let groups = [];
|
|
||||||
let repoID = this.props.repoID;
|
let repoID = this.props.repoID;
|
||||||
if (this.state.selectedOption) {
|
let { selectedOptions } = this.state;
|
||||||
groups[0] = this.state.selectedOption.id;
|
if (selectedOptions.length === 0) return;
|
||||||
}
|
let targetGroupIDs = selectedOptions.map(item => { return item.id; });
|
||||||
systemAdminAPI.sysAdminAddRepoSharedItem(repoID, 'group', groups, this.state.permission).then(res => {
|
systemAdminAPI.sysAdminAddRepoSharedItem(repoID, 'group', targetGroupIDs, this.state.permission).then(res => {
|
||||||
let errorMsg = [];
|
let errorMsg = [];
|
||||||
if (res.data.failed.length > 0) {
|
if (res.data.failed.length > 0) {
|
||||||
for (let i = 0 ; i < res.data.failed.length ; i++) {
|
for (let i = 0 ; i < res.data.failed.length ; i++) {
|
||||||
@@ -188,7 +204,7 @@ class SysAdminShareToGroup extends React.Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
errorMsg: errorMsg,
|
errorMsg: errorMsg,
|
||||||
sharedItems: this.state.sharedItems.concat(items),
|
sharedItems: this.state.sharedItems.concat(items),
|
||||||
selectedOption: null,
|
selectedOptions: [],
|
||||||
permission: 'rw',
|
permission: 'rw',
|
||||||
});
|
});
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
@@ -247,13 +263,14 @@ class SysAdminShareToGroup extends React.Component {
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<SeahubSelect
|
<GroupSelect
|
||||||
onChange={this.handleSelectChange}
|
selectedOptions={this.state.selectedOptions}
|
||||||
options={this.options}
|
options={this.options}
|
||||||
placeholder={gettext('Select groups')}
|
onSelectOption={this.onSelectOption}
|
||||||
maxMenuHeight={200}
|
onDeleteOption={this.onDeleteOption}
|
||||||
value={this.state.selectedOption}
|
searchPlaceholder={gettext('Search groups')}
|
||||||
noOptionsMessage={NoGroupMessage}
|
noOptionsPlaceholder={gettext('No results')}
|
||||||
|
isInModal={true}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
@@ -52,7 +52,7 @@ class Libraries extends Component {
|
|||||||
|
|
||||||
const eventBus = EventBus.getInstance();
|
const eventBus = EventBus.getInstance();
|
||||||
this.unsubscribeAddNewGroup = eventBus.subscribe(EVENT_BUS_TYPE.ADD_NEW_GROUP, this.addNewGroup);
|
this.unsubscribeAddNewGroup = eventBus.subscribe(EVENT_BUS_TYPE.ADD_NEW_GROUP, this.addNewGroup);
|
||||||
this.unsubscribeAddSharedRepoIntoGroup = eventBus.subscribe(EVENT_BUS_TYPE.ADD_SHARED_REPO_INO_GROUP, this.insertRepoIntoGroup);
|
this.unsubscribeAddSharedRepoIntoGroup = eventBus.subscribe(EVENT_BUS_TYPE.ADD_SHARED_REPO_INTO_GROUP, this.insertRepoIntoGroup);
|
||||||
this.unsubscribeUnsharedRepoToGroup = eventBus.subscribe(EVENT_BUS_TYPE.UN_SHARE_REPO_TO_GROUP, this.unshareRepoToGroup);
|
this.unsubscribeUnsharedRepoToGroup = eventBus.subscribe(EVENT_BUS_TYPE.UN_SHARE_REPO_TO_GROUP, this.unshareRepoToGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -267,18 +267,14 @@ class SeafileAPI {
|
|||||||
return this.req.delete(url, { data: params });
|
return this.req.delete(url, { data: params });
|
||||||
}
|
}
|
||||||
|
|
||||||
shareGroupOwnedRepoToGroup(repoID, permission, groupID, path) {
|
shareGroupOwnedRepoToGroup(repoID, permission, groupIDs, path) {
|
||||||
const url = this.server + '/api/v2.1/group-owned-libraries/' + repoID + '/group-share/';
|
const url = this.server + '/api/v2.1/group-owned-libraries/' + repoID + '/group-share/';
|
||||||
let form = new FormData();
|
let form = new FormData();
|
||||||
form.append('permission', permission);
|
form.append('permission', permission);
|
||||||
form.append('path', path);
|
form.append('path', path);
|
||||||
if (Array.isArray(groupID)) {
|
groupIDs.forEach(item => {
|
||||||
groupID.forEach(item => {
|
form.append('group_id', item);
|
||||||
form.append('group_id', item);
|
});
|
||||||
});
|
|
||||||
} else {
|
|
||||||
form.append('group_id', groupID);
|
|
||||||
}
|
|
||||||
return this._sendPostRequest(url, form);
|
return this._sendPostRequest(url, form);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1825,12 +1821,14 @@ class SeafileAPI {
|
|||||||
return this.req.get(url);
|
return this.req.get(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
addGroupFolderPerm(repoID, permission, folderPath, groupID) {
|
addGroupFolderPerm(repoID, permission, folderPath, groupIDs) {
|
||||||
const url = this.server + '/api2/repos/' + repoID + '/group-folder-perm/';
|
const url = this.server + '/api2/repos/' + repoID + '/group-folder-perm/';
|
||||||
let form = new FormData();
|
let form = new FormData();
|
||||||
form.append('permission', permission);
|
form.append('permission', permission);
|
||||||
form.append('folder_path', folderPath);
|
form.append('folder_path', folderPath);
|
||||||
form.append('group_id', groupID);
|
groupIDs.forEach(item => {
|
||||||
|
form.append('group_id', item);
|
||||||
|
});
|
||||||
return this._sendPostRequest(url, form);
|
return this._sendPostRequest(url, form);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1903,12 +1901,14 @@ class SeafileAPI {
|
|||||||
return this.req.get(url);
|
return this.req.get(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
addDepartmentRepoGroupFolderPerm(repoID, permission, folderPath, groupID) {
|
addDepartmentRepoGroupFolderPerm(repoID, permission, folderPath, groupIDs) {
|
||||||
const url = this.server + '/api/v2.1/group-owned-libraries/' + repoID + '/group-folder-permission/';
|
const url = this.server + '/api/v2.1/group-owned-libraries/' + repoID + '/group-folder-permission/';
|
||||||
let form = new FormData();
|
let form = new FormData();
|
||||||
form.append('permission', permission);
|
form.append('permission', permission);
|
||||||
form.append('folder_path', folderPath);
|
form.append('folder_path', folderPath);
|
||||||
form.append('group_id', groupID);
|
groupIDs.forEach(item => {
|
||||||
|
form.append('group_id', item);
|
||||||
|
});
|
||||||
return this._sendPostRequest(url, form);
|
return this._sendPostRequest(url, form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user