1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-04-27 19:05:16 +00:00

Change generate tags UI (#7712)

* 01 change header icon class

* 02 change tags UI

* 03 change modal header title
This commit is contained in:
Michael An 2025-04-08 16:47:35 +08:00 committed by GitHub
parent e67fc4a3d9
commit 1be01e5186
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 178 additions and 81 deletions

View File

@ -25,7 +25,7 @@
cursor: pointer;
}
.detail-header .detail-control .detail-control-close {
.detail-header .detail-control .detail-control-icon {
font-size: 16px;
fill: #666;
}

View File

@ -15,7 +15,7 @@ const Header = ({ title, icon, iconSize = 32, onClose, children, component = {}
{children}
{onClose && (
<div className="detail-control" onClick={onClose}>
{closeIcon ? closeIcon : <Icon symbol="close" className="detail-control-close" />}
{closeIcon ? closeIcon : <Icon symbol="close" className="detail-control-icon" />}
</div>
)}
</div>

View File

@ -1,24 +1,76 @@
.sf-metadata-auto-image-tags .modal-body {
.sf-file-tags-backdrop.show {
opacity: 0;
background-color: #fff;
}
.sf-file-tags {
margin-top: 370px;
}
.sf-file-tags .modal-content {
border: 1px solid #efefef;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
}
.sf-file-tags .modal-header {
height: 48px;
}
.sf-file-tags .modal-body {
min-height: 160px;
}
.sf-metadata-auto-image-tags .auto-image-tags-container {
display: flex;
align-items: center;
flex-wrap: wrap;
}
.sf-metadata-auto-image-tags .auto-image-tag {
height: 28px;
.sf-file-tags .sf-file-new-tag {
display: inline-flex;
height: 20px;
padding: 0 8px;
border: 1px solid #dedede;
border-radius: 4px;
border-radius: 14px;
margin-right: 8px;
margin-bottom: 8px;
line-height: 26px;
line-height: 20px;
cursor: pointer;
font-size: 13px;
align-items: center;
}
.sf-file-exit-tag {
display: inline-flex;
align-items: center;
height: 20px;
margin-right: 10px;
padding: 0 8px 0 2px;
font-size: 13px;
border-radius: 10px;
border: 1px solid #dbdbdb;
background: #fff;
margin-top: 5px;
margin-bottom: 5px;
cursor: pointer;
}
.sf-metadata-auto-image-tags .auto-image-tag.selected {
.sf-file-exit-tag .sf-file-exit-tag-color {
width: 12px;
height: 12px;
border-radius: 50%;
margin-left: 1px;
}
.sf-file-exit-tag .sf-file-exit-tag-name {
display: inline-block;
height: 20px;
line-height: 20px;
color: #212529;
margin-left: 8px;
max-width: 200px;
flex: 1 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.sf-file-new-tag.selected,
.sf-file-exit-tag.selected {
border-color: #FF9800;
}

View File

@ -1,11 +1,9 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { Button, Modal, ModalBody, ModalFooter } from 'reactstrap';
import { Modal, ModalBody, ModalHeader } from 'reactstrap';
import CenteredLoading from '../../../../components/centered-loading';
import toaster from '../../../../components/toast';
import EmptyTip from '../../../../components/empty-tip';
import SeahubModalHeader from '@/components/common/seahub-modal-header';
import { gettext } from '../../../../utils/constants';
import { Utils } from '../../../../utils/utils';
import { getFileNameFromRecord, getParentDirFromRecord, getTagsFromRecord, getRecordIdFromRecord } from '../../../utils/cell';
@ -20,12 +18,13 @@ import './index.css';
const FileTagsDialog = ({ record, onToggle, onSubmit }) => {
const [isLoading, setLoading] = useState(true);
const [isSubmitting, setSubmitting] = useState(false);
const [fileTags, setFileTags] = useState([]);
const [newTags, setNewTags] = useState([]);
const [exitTags, setExitTags] = useState([]);
const [selectedTags, setSelectedTags] = useState([]);
const fileName = useMemo(() => getFileNameFromRecord(record), [record]);
const lastSettingsValue = parseInt(localStorage.getItem('sf_cur_view_detail_width'));
const { tagsData, addTags } = useTags();
useEffect(() => {
@ -40,7 +39,18 @@ const FileTagsDialog = ({ record, onToggle, onSubmit }) => {
}
window.sfMetadataContext.generateFileTags(path).then(res => {
const tags = res.data.tags || [];
setFileTags(tags);
let newTags = [];
let exitTags = [];
tags.forEach(tag => {
const tagObj = getTagByName(tagsData, tag);
if (tagObj) {
exitTags.push(tagObj);
} else {
newTags.push(tag);
}
});
setNewTags(newTags);
setExitTags(exitTags);
setLoading(false);
}).catch(error => {
const errorMessage = gettext('Failed to generate file tags');
@ -48,9 +58,9 @@ const FileTagsDialog = ({ record, onToggle, onSubmit }) => {
setLoading(false);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [tagsData]);
const onSelectImageTag = useCallback((tagName) => {
const onClickTag = useCallback((tagName) => {
let newSelectedTags = selectedTags.slice(0);
const tagNameIndex = selectedTags.findIndex(i => i === tagName);
if (tagNameIndex === -1) {
@ -62,37 +72,40 @@ const FileTagsDialog = ({ record, onToggle, onSubmit }) => {
}, [selectedTags]);
const handelSubmit = useCallback(() => {
setSubmitting(true);
if (selectedTags.length === 0) {
if (isLoading || selectedTags.length === 0) {
onToggle();
return;
}
let { newTags, exitTagIds } = selectedTags.reduce((cur, pre) => {
const tag = getTagByName(tagsData, pre);
let selectedNewTags = [];
let selectedExitTags = [];
selectedTags.forEach(tagName => {
const tag = getTagByName(tagsData, tagName);
if (tag) {
cur.exitTagIds.push(getTagId(tag));
selectedExitTags.push(tag);
} else {
cur.newTags.push(pre);
selectedNewTags.push(tagName);
}
return cur;
}, { newTags: [], exitTagIds: [] });
});
newTags = newTags.map(tagName => {
selectedNewTags = selectedNewTags.map(tagName => {
const defaultOptions = SELECT_OPTION_COLORS.slice(0, 24);
const defaultOption = defaultOptions[Math.floor(Math.random() * defaultOptions.length)];
return { [TAGS_PRIVATE_COLUMN_KEY.TAG_NAME]: tagName, [TAGS_PRIVATE_COLUMN_KEY.TAG_COLOR]: defaultOption.COLOR };
return {
[TAGS_PRIVATE_COLUMN_KEY.TAG_NAME]: tagName,
[TAGS_PRIVATE_COLUMN_KEY.TAG_COLOR]: defaultOption.COLOR,
};
});
const recordId = getRecordIdFromRecord(record);
let value = getTagsFromRecord(record);
value = value ? value.map(item => item.row_id) : [];
if (newTags.length > 0) {
addTags(newTags, {
if (selectedNewTags.length > 0) {
addTags(selectedNewTags, {
success_callback: (operation) => {
const newTagIds = operation.tags?.map(tag => getTagId(tag));
let newValue = [...value, ...newTagIds];
exitTagIds.forEach(id => {
selectedExitTags.forEach(id => {
if (!newValue.includes(id)) {
newValue.push(id);
}
@ -101,56 +114,88 @@ const FileTagsDialog = ({ record, onToggle, onSubmit }) => {
onToggle();
},
fail_callback: (error) => {
setSubmitting(false);
toaster.danger(Utils.getErrorMsg(error));
},
});
return;
}
let newValue = [...value];
exitTagIds.forEach(id => {
if (!newValue.includes(id)) {
newValue.push(id);
} else {
let newValue = [...value];
selectedExitTags.forEach(id => {
if (!newValue.includes(id)) {
newValue.push(id);
}
});
if (newValue.length !== value.length) {
onSubmit([{ record_id: recordId, tags: newValue, old_tags: value }]);
}
});
if (newValue.length !== value.length) {
onSubmit([{ record_id: recordId, tags: newValue, old_tags: value }]);
onToggle();
}
onToggle();
}, [selectedTags, onSubmit, onToggle, record, addTags, tagsData]);
}, [selectedTags, onSubmit, onToggle, record, addTags, tagsData, isLoading]);
return (
<Modal isOpen={true} toggle={() => onToggle()} className="sf-metadata-auto-image-tags">
<SeahubModalHeader toggle={() => onToggle()}>{fileName + gettext('\'s tags')}</SeahubModalHeader>
<ModalBody>
{isLoading ? (
<CenteredLoading />
) : (
<div className="auto-image-tags-container">
{fileTags.length > 0 ? (
<>
{fileTags.map((tagName, index) => {
const isSelected = selectedTags.includes(tagName);
return (
<div
key={index}
className={classNames('auto-image-tag', { 'selected': isSelected })}
onClick={() => onSelectImageTag(tagName)}
>
{tagName}
</div>
);
})}
</>
) : (
<EmptyTip className="w-100 h-100" text={gettext('No tags')} />
)}
</div>
)}
</ModalBody>
<ModalFooter>
<Button color="secondary" onClick={() => onToggle()}>{gettext('Cancel')}</Button>
<Button color="primary" disabled={isLoading || isSubmitting || fileTags.length === 0} onClick={handelSubmit}>{gettext('Submit')}</Button>
</ModalFooter>
<Modal
isOpen={true}
toggle={() => { handelSubmit(); }}
className="sf-file-tags"
backdropClassName="sf-file-tags-backdrop"
style={{ marginRight: lastSettingsValue }}
>
<div onClick={(e) => e.stopPropagation()} className="modal-content">
<ModalHeader>{fileName + ' ' + gettext('tags')}</ModalHeader>
<ModalBody>
{isLoading ?
<CenteredLoading />
:
<div>
<div className="mb-6">
<div className='mb-1'>{gettext('Matching tags')}</div>
{exitTags.length > 0 && (
<>
{exitTags.map((tag, index) => {
const { _tag_color: tagColor, _tag_name: tagName } = tag;
const isSelected = selectedTags.includes(tagName);
return (
<div
key={index}
className={classNames('sf-file-exit-tag', { 'selected': isSelected })}
onClick={() => onClickTag(tagName)}
>
<div className="sf-file-exit-tag-color" style={{ backgroundColor: tagColor }}></div>
<div className="sf-file-exit-tag-name">{tagName}</div>
</div>
);
})}
</>
)}
{exitTags.length === 0 && (
<span className='tip'>{gettext('No matching tags')}</span>
)}
</div>
<div className="mb-6">
<div className='mb-1'>{gettext('Recommended new tags')}</div>
{newTags.length > 0 && (
<>
{newTags.map((tagName, index) => {
const isSelected = selectedTags.includes(tagName);
return (
<div
key={index}
className={classNames('sf-file-new-tag', { 'selected': isSelected })}
onClick={() => onClickTag(tagName)}
>
{tagName}
</div>
);
})}
</>
)}
{newTags.length === 0 && (
<span className='tip'>{gettext('No recommended new tags')}</span>
)}
</div>
</div>
}
</ModalBody>
</div>
</Modal>
);
};

View File

@ -147,7 +147,7 @@ const AIIcon = () => {
tabIndex={0}
>
<div className="detail-control mr-2">
<Icon symbol="ai" className="detail-control-close" />
<Icon symbol="ai" className="detail-control-icon" />
</div>
</DropdownToggle>
{isMenuShow && (

View File

@ -21,7 +21,7 @@ const SettingsIcon = () => {
return (
<>
<div className="detail-control mr-2" id={target} onClick={onSetterToggle}>
<Icon symbol="set-up" className="detail-control-close" />
<Icon symbol="set-up" className="detail-control-icon" />
</div>
{isShowSetter && (
<HideColumnPopover