1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-03 07:55:36 +00:00

[tags] redesigned 'Tags' dialog

- redesigned 'tag color edit', 'tag name edit', 'tag delete'
This commit is contained in:
llj
2022-02-25 18:11:54 +08:00
parent 8862328958
commit 97da4f2b63
6 changed files with 242 additions and 91 deletions

View File

@@ -83,14 +83,14 @@ class TagItem extends React.Component {
const isTagSelected = repoTagIdList.indexOf(repoTag.id) != -1;
return (
<li
className={`tag-list-item px-4 d-flex justify-content-between align-items-center ${isTagHighlighted ? 'hl' : ''}`}
className={`tag-list-item cursor-pointer px-4 d-flex justify-content-between align-items-center ${isTagHighlighted ? 'hl' : ''}`}
onClick={this.onEditFileTag}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
>
<div className="d-flex align-items-center">
<span className="tag-color w-4 h-4 rounded-circle" style={{backgroundColor: repoTag.color}}></span>
<span className="tag-name">{repoTag.name}</span>
<span className="tag-name mx-2">{repoTag.name}</span>
</div>
{isTagSelected && <i className="fas fa-check tag-selected-icon"></i>}
</li>

View File

@@ -6,13 +6,15 @@ import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
import toaster from '../toast';
import RepoTag from '../../models/repo-tag';
import TagColor from './tag-color';
import TagName from './tag-name';
import '../../css/repo-tag.css';
const tagListItemPropTypes = {
item: PropTypes.object.isRequired,
onTagUpdate: PropTypes.func.isRequired,
onListTaggedFiles: PropTypes.func.isRequired,
repoID: PropTypes.string.isRequired,
onDeleteTag : PropTypes.func.isRequired
};
class TagListItem extends React.Component {
@@ -20,45 +22,43 @@ class TagListItem extends React.Component {
constructor(props) {
super(props);
this.state = {
showSelectedTag: false
isTagHighlighted: false
};
}
onMouseOver = () => {
this.setState({
showSelectedTag: true
isTagHighlighted: true
});
}
onMouseOut = () => {
this.setState({
showSelectedTag: false
isTagHighlighted: false
});
}
onTagUpdate = () => {
this.props.onTagUpdate(this.props.item);
}
onListTaggedFiles = () => {
this.props.onListTaggedFiles(this.props.item);
deleteTag = () => {
this.props.onDeleteTag(this.props.item);
}
render() {
let color = this.props.item.color;
let drakColor = Utils.getDarkColor(color);
const fileCount = this.props.item.fileCount;
let fileTranslation = (fileCount === 1 || fileCount === 0) ? gettext('file') : gettext('files');
const { isTagHighlighted } = this.state;
const { item, repoID } = this.props;
return (
<li className="tag-list-item">
<div className="tag-demo" style={{backgroundColor:color}} onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut}>
<span className={`${this.state.showSelectedTag ? 'show-tag-selected': ''}`} style={{backgroundColor: drakColor}}></span>
<span className="tag-name">{this.props.item.name}</span>
<button className="tag-files border-0 bg-transparent" onClick={this.onListTaggedFiles}>
{fileCount}{' '}{fileTranslation}
</button>
</div>
<button className="tag-edit fa fa-pencil-alt border-0" onClick={this.onTagUpdate} aria-label={gettext('Edit')} title={gettext('Edit')}></button>
<li
className={`tag-list-item px-4 d-flex justify-content-between align-items-center ${isTagHighlighted ? 'hl' : ''}`}
onMouseOver={this.onMouseOver}
onMouseOut={this.onMouseOut}
>
<TagColor repoID={repoID} tag={item} />
<TagName repoID={repoID} tag={item} />
<button
className={`tag-delete-icon sf2-icon-delete border-0 px-0 bg-transparent cursor-pointer ${isTagHighlighted ? '' : 'invisible'}`}
onClick={this.deleteTag}
aria-label={gettext('Delete')}
title={gettext('Delete')}
></button>
</li>
);
}
@@ -69,16 +69,14 @@ TagListItem.propTypes = tagListItemPropTypes;
const listTagPropTypes = {
repoID: PropTypes.string.isRequired,
onListTagCancel: PropTypes.func.isRequired,
onCreateRepoTag: PropTypes.func.isRequired,
onUpdateRepoTag: PropTypes.func.isRequired,
onListTaggedFiles: PropTypes.func.isRequired,
onCreateRepoTag: PropTypes.func.isRequired
};
class ListTagDialog extends React.Component {
constructor(props) {
super(props);
this.state = {
repotagList: [],
repotagList: []
};
}
@@ -91,7 +89,7 @@ class ListTagDialog extends React.Component {
repotagList.push(repo_tag);
});
this.setState({
repotagList: repotagList,
repotagList: repotagList
});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
@@ -108,24 +106,44 @@ class ListTagDialog extends React.Component {
this.props.onCreateRepoTag();
}
onDeleteTag = (tag) => {
const { repoID } = this.props;
const { id: targetTagID } = tag;
seafileAPI.deleteRepoTag(repoID, targetTagID).then((res) => {
this.setState({
repotagList: this.state.repotagList.filter(tag => tag.id != targetTagID)
});
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
render() {
return (
<Fragment>
<ModalHeader toggle={this.toggle}>{gettext('Tags')}</ModalHeader>
<ModalBody>
<ModalBody className="px-0">
<ul className="tag-list tag-list-container">
{this.state.repotagList.map((repoTag, index) => {
return (
<TagListItem
key={index}
item={repoTag}
onTagUpdate={this.props.onUpdateRepoTag}
onListTaggedFiles={this.props.onListTaggedFiles}
repoID={this.props.repoID}
onDeleteTag={this.onDeleteTag}
/>
);
})}
</ul>
<a href="#" className="add-tag-link" onClick={this.createNewTag}>{gettext('Create a new tag')}</a>
<a
href="#"
className="add-tag-link px-4 py-2 d-flex align-items-center"
onClick={this.createNewTag}
>
<span className="sf2-icon-plus mr-2"></span>
{gettext('Create a new tag')}
</a>
</ModalBody>
<ModalFooter>
<Button color="secondary" onClick={this.toggle}>{gettext('Close')}</Button>

View File

@@ -0,0 +1,99 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Popover, PopoverBody } from 'reactstrap';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
import toaster from '../toast';
import '../../css/repo-tag.css';
const tagColorPropTypes = {
tag: PropTypes.object.isRequired,
repoID: PropTypes.string.isRequired
};
class TagColor extends React.Component {
constructor(props) {
super(props);
this.state = {
tagColor: this.props.tag.color,
isPopoverOpen: false
};
}
togglePopover = () => {
this.setState({
isPopoverOpen: !this.state.isPopoverOpen
});
}
selectTagColor = (e) => {
const newColor = e.target.value;
const { repoID, tag } = this.props;
const { id, name } = tag;
seafileAPI.updateRepoTag(repoID, id, name, newColor).then(() => {
this.setState({
tagColor: newColor,
isPopoverOpen: !this.state.isPopoverOpen
});
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
render() {
const { isPopoverOpen, tagColor } = this.state;
const { tag } = this.props;
const { id, color } = tag;
let colorList = ['#FBD44A', '#EAA775', '#F4667C', '#DC82D2', '#9860E5', '#9F8CF1', '#59CB74', '#ADDF84', '#89D2EA', '#4ECCCB', '#46A1FD', '#C2C2C2'];
// for color from previous color options
if (colorList.indexOf(color) == -1) {
colorList.unshift(color);
}
return (
<div>
<span
id={`tag-${id}-color`}
className="tag-color cursor-pointer w-4 h-4 rounded-circle d-flex align-items-center justify-content-center"
style={{backgroundColor: tagColor}}
onClick={this.togglePopover}
>
<i className="fas fa-caret-down text-white"></i>
</span>
<Popover
target={`tag-${id}-color`}
isOpen={isPopoverOpen}
placement="bottom"
toggle={this.togglePopover}
className="mw-100"
>
<PopoverBody className="p-2">
<div className="d-flex justify-content-between">
{colorList.map((item, index)=>{
return (
<div key={index} className="tag-color-option mx-1">
<label className="colorinput">
<input name="color" type="radio" value={item} className="colorinput-input" defaultChecked={item == tagColor} onClick={this.selectTagColor} />
<span className="colorinput-color rounded-circle d-flex align-items-center justify-content-center" style={{backgroundColor: item}}>
<i className="fas fa-check color-selected"></i>
</span>
</label>
</div>
);
})
}
</div>
</PopoverBody>
</Popover>
</div>
);
}
}
TagColor.propTypes = tagColorPropTypes;
export default TagColor;

View File

@@ -0,0 +1,86 @@
import React from 'react';
import PropTypes from 'prop-types';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
import toaster from '../toast';
import '../../css/repo-tag.css';
const tagNamePropTypes = {
tag: PropTypes.object.isRequired,
repoID: PropTypes.string.isRequired
};
class TagName extends React.Component {
constructor(props) {
super(props);
this.state = {
tagName: this.props.tag.name,
isEditing: false
};
this.input = React.createRef();
}
toggleMode = () => {
this.setState({
isEditing: !this.state.isEditing
}, () => {
if (this.state.isEditing) {
this.input.current.focus();
}
});
}
updateTagName = (e) => {
const newName = e.target.value;
const { repoID, tag } = this.props;
const { id, color } = tag;
seafileAPI.updateRepoTag(repoID, id, newName, color).then(() => {
this.setState({
tagName: newName
});
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
onInputKeyDown = (e) => {
if (e.key == 'Enter') {
this.toggleMode();
this.updateTagName(e);
}
}
onInputBlur = (e) => {
this.toggleMode();
this.updateTagName(e);
}
render() {
const { isEditing, tagName } = this.state;
return (
<div className="mx-2 flex-fill d-flex">
{isEditing ?
<input
type="text"
ref={this.input}
defaultValue={tagName}
onBlur={this.onInputBlur}
onKeyDown={this.onInputKeyDown}
className="flex-fill form-control-sm form-control"
/> :
<span
onClick={this.toggleMode}
className="cursor-pointer flex-fill"
>{tagName}</span>
}
</div>
);
}
}
TagName.propTypes = tagNamePropTypes;
export default TagName;