mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-23 04:18:21 +00:00
Feature/sidepanel in search result (#7844)
* show sidepanel in search result * show metadata details * show library details * optimize * optimize * optimize --------- Co-authored-by: zhouwenxuan <aries@Mac.local>
This commit is contained in:
@@ -9,8 +9,9 @@ import { useMetadataStatus } from '../../../hooks';
|
|||||||
import { Utils } from '../../../utils/utils';
|
import { Utils } from '../../../utils/utils';
|
||||||
import { SYSTEM_FOLDERS } from '../../../constants';
|
import { SYSTEM_FOLDERS } from '../../../constants';
|
||||||
|
|
||||||
const DirDetails = ({ direntDetail }) => {
|
const DirDetails = ({ readOnly = false, direntDetail, tagsData }) => {
|
||||||
const { enableMetadata, enableMetadataManagement } = useMetadataStatus();
|
const { enableMetadata, enableMetadataManagement } = useMetadataStatus();
|
||||||
|
|
||||||
const lastModifiedTimeField = useMemo(() => {
|
const lastModifiedTimeField = useMemo(() => {
|
||||||
return { type: CellType.MTIME, name: gettext('Last modified time') };
|
return { type: CellType.MTIME, name: gettext('Last modified time') };
|
||||||
}, []);
|
}, []);
|
||||||
@@ -39,7 +40,7 @@ const DirDetails = ({ direntDetail }) => {
|
|||||||
<Formatter field={CellType.TEXT} value={'--'} /> :
|
<Formatter field={CellType.TEXT} value={'--'} /> :
|
||||||
<Formatter field={sizeField} value={size} />}
|
<Formatter field={sizeField} value={size} />}
|
||||||
</DetailItem>
|
</DetailItem>
|
||||||
<MetadataDetails />
|
<MetadataDetails readOnly={readOnly} tagsData={tagsData} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<DetailItem field={lastModifiedTimeField} className="sf-metadata-property-detail-formatter">
|
<DetailItem field={lastModifiedTimeField} className="sf-metadata-property-detail-formatter">
|
||||||
@@ -50,7 +51,9 @@ const DirDetails = ({ direntDetail }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
DirDetails.propTypes = {
|
DirDetails.propTypes = {
|
||||||
|
readOnly: PropTypes.bool,
|
||||||
direntDetail: PropTypes.object,
|
direntDetail: PropTypes.object,
|
||||||
|
tagsData: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DirDetails;
|
export default DirDetails;
|
||||||
|
@@ -55,7 +55,7 @@ const getImageInfoValue = (key, value) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const FileDetails = React.memo(({ repoID, dirent, path, direntDetail, isShowRepoTags = true, repoTags, fileTagList, onFileTagChanged }) => {
|
const FileDetails = React.memo(({ repoID, dirent, path, direntDetail, isShowRepoTags = true, repoTags, fileTagList, readOnly = false, tagsData, onFileTagChanged }) => {
|
||||||
const [isCaptureInfoShow, setCaptureInfoShow] = useState(false);
|
const [isCaptureInfoShow, setCaptureInfoShow] = useState(false);
|
||||||
const { enableFaceRecognition, enableMetadata } = useMetadataStatus();
|
const { enableFaceRecognition, enableMetadata } = useMetadataStatus();
|
||||||
const { record } = useMetadataDetails();
|
const { record } = useMetadataDetails();
|
||||||
@@ -102,7 +102,7 @@ const FileDetails = React.memo(({ repoID, dirent, path, direntDetail, isShowRepo
|
|||||||
/>
|
/>
|
||||||
</DetailItem>
|
</DetailItem>
|
||||||
)}
|
)}
|
||||||
{enableMetadata && <MetadataDetails />}
|
{enableMetadata && <MetadataDetails readOnly={readOnly} tagsData={tagsData} />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -162,6 +162,8 @@ FileDetails.propTypes = {
|
|||||||
direntDetail: PropTypes.object,
|
direntDetail: PropTypes.object,
|
||||||
repoTags: PropTypes.array,
|
repoTags: PropTypes.array,
|
||||||
fileTagList: PropTypes.array,
|
fileTagList: PropTypes.array,
|
||||||
|
readOnly: PropTypes.bool,
|
||||||
|
tagsData: PropTypes.object,
|
||||||
onFileTagChanged: PropTypes.func,
|
onFileTagChanged: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -100,6 +100,7 @@ class DirentDetails extends React.Component {
|
|||||||
dirent={dirent}
|
dirent={dirent}
|
||||||
direntDetail={direntDetail}
|
direntDetail={direntDetail}
|
||||||
direntType={dirent?.type !== 'file' ? 'dir' : 'file'}
|
direntType={dirent?.type !== 'file' ? 'dir' : 'file'}
|
||||||
|
modifyLocalFileTags={this.props.modifyLocalFileTags}
|
||||||
>
|
>
|
||||||
<Detail>
|
<Detail>
|
||||||
<Header title={dirent?.name || ''} icon={Utils.getDirentIcon(dirent, true)} onClose={this.props.onClose} >
|
<Header title={dirent?.name || ''} icon={Utils.getDirentIcon(dirent, true)} onClose={this.props.onClose} >
|
||||||
@@ -111,7 +112,11 @@ class DirentDetails extends React.Component {
|
|||||||
{dirent && direntDetail && (
|
{dirent && direntDetail && (
|
||||||
<div className="detail-content">
|
<div className="detail-content">
|
||||||
{dirent.type !== 'file' ? (
|
{dirent.type !== 'file' ? (
|
||||||
<DirDetails direntDetail={direntDetail} />
|
<DirDetails
|
||||||
|
direntDetail={direntDetail}
|
||||||
|
tagsData={this.props.tagsData}
|
||||||
|
addTag={this.props.addTag}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<FileDetails
|
<FileDetails
|
||||||
repoID={repoID}
|
repoID={repoID}
|
||||||
@@ -120,6 +125,8 @@ class DirentDetails extends React.Component {
|
|||||||
direntDetail={direntDetail}
|
direntDetail={direntDetail}
|
||||||
repoTags={this.props.repoTags}
|
repoTags={this.props.repoTags}
|
||||||
fileTagList={dirent ? dirent.file_tags : fileTags}
|
fileTagList={dirent ? dirent.file_tags : fileTags}
|
||||||
|
tagsData={this.props.tagsData}
|
||||||
|
addTag={this.props.addTag}
|
||||||
onFileTagChanged={this.props.onFileTagChanged}
|
onFileTagChanged={this.props.onFileTagChanged}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -141,6 +148,13 @@ DirentDetails.propTypes = {
|
|||||||
onFileTagChanged: PropTypes.func.isRequired,
|
onFileTagChanged: PropTypes.func.isRequired,
|
||||||
repoTags: PropTypes.array,
|
repoTags: PropTypes.array,
|
||||||
fileTags: PropTypes.array,
|
fileTags: PropTypes.array,
|
||||||
|
enableMetadata: PropTypes.bool,
|
||||||
|
enableFaceRecognition: PropTypes.bool,
|
||||||
|
detailsSettings: PropTypes.object,
|
||||||
|
tagsData: PropTypes.object,
|
||||||
|
addTag: PropTypes.func,
|
||||||
|
modifyDetailsSettings: PropTypes.func,
|
||||||
|
modifyLocalFileTags: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DirentDetails;
|
export default DirentDetails;
|
||||||
|
@@ -11,6 +11,7 @@ import { MetadataDetailsProvider } from '../../../metadata/hooks';
|
|||||||
import AIIcon from '../../../metadata/components/metadata-details/ai-icon';
|
import AIIcon from '../../../metadata/components/metadata-details/ai-icon';
|
||||||
import SettingsIcon from '../../../metadata/components/metadata-details/settings-icon';
|
import SettingsIcon from '../../../metadata/components/metadata-details/settings-icon';
|
||||||
import Loading from '../../loading';
|
import Loading from '../../loading';
|
||||||
|
import { useTags } from '../../../tag/hooks';
|
||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
@@ -21,6 +22,8 @@ const EmbeddedFileDetails = ({ repoID, repoInfo, dirent, path, onClose, width =
|
|||||||
const [direntDetail, setDirentDetail] = useState('');
|
const [direntDetail, setDirentDetail] = useState('');
|
||||||
const [isFetching, setIsFetching] = useState(true);
|
const [isFetching, setIsFetching] = useState(true);
|
||||||
|
|
||||||
|
const { tagsData, addTag } = useTags();
|
||||||
|
|
||||||
const isView = useMemo(() => {
|
const isView = useMemo(() => {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
return urlParams.has('view');
|
return urlParams.has('view');
|
||||||
@@ -87,7 +90,15 @@ const EmbeddedFileDetails = ({ repoID, repoInfo, dirent, path, onClose, width =
|
|||||||
:
|
:
|
||||||
dirent && direntDetail && (
|
dirent && direntDetail && (
|
||||||
<div className="detail-content">
|
<div className="detail-content">
|
||||||
<FileDetails repoID={repoID} isShowRepoTags={false} dirent={dirent} direntDetail={direntDetail} />
|
<FileDetails
|
||||||
|
repoID={repoID}
|
||||||
|
isShowRepoTags={false}
|
||||||
|
dirent={dirent}
|
||||||
|
direntDetail={direntDetail}
|
||||||
|
tagsData={tagsData}
|
||||||
|
addTag={addTag}
|
||||||
|
onFileTagChanged={() => {}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Body>
|
</Body>
|
||||||
|
@@ -8,8 +8,13 @@ import { MetadataContext } from '../../metadata';
|
|||||||
import { PRIVATE_FILE_TYPE } from '../../constants';
|
import { PRIVATE_FILE_TYPE } from '../../constants';
|
||||||
import { METADATA_MODE, TAGS_MODE } from '../dir-view-mode/constants';
|
import { METADATA_MODE, TAGS_MODE } from '../dir-view-mode/constants';
|
||||||
import { FACE_RECOGNITION_VIEW_ID } from '../../metadata/constants';
|
import { FACE_RECOGNITION_VIEW_ID } from '../../metadata/constants';
|
||||||
|
import { useTags } from '../../tag/hooks';
|
||||||
|
import { useMetadataStatus } from '../../hooks';
|
||||||
|
|
||||||
const Detail = React.memo(({ repoID, path, currentMode, dirent, currentRepoInfo, repoTags, fileTags, onClose, onFileTagChanged }) => {
|
const Detail = React.memo(({ repoID, path, currentMode, dirent, currentRepoInfo, repoTags, fileTags, onClose, onFileTagChanged }) => {
|
||||||
|
const { enableMetadata, enableFaceRecognition, detailsSettings, modifyDetailsSettings } = useMetadataStatus();
|
||||||
|
const { tagsData, addTag, modifyLocalFileTags } = useTags();
|
||||||
|
|
||||||
const isView = useMemo(() => currentMode === METADATA_MODE || path.startsWith('/' + PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES), [currentMode, path]);
|
const isView = useMemo(() => currentMode === METADATA_MODE || path.startsWith('/' + PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES), [currentMode, path]);
|
||||||
const isTag = useMemo(() => currentMode === TAGS_MODE || path.startsWith('/' + PRIVATE_FILE_TYPE.TAGS_PROPERTIES), [currentMode, path]);
|
const isTag = useMemo(() => currentMode === TAGS_MODE || path.startsWith('/' + PRIVATE_FILE_TYPE.TAGS_PROPERTIES), [currentMode, path]);
|
||||||
|
|
||||||
@@ -53,6 +58,13 @@ const Detail = React.memo(({ repoID, path, currentMode, dirent, currentRepoInfo,
|
|||||||
currentRepoInfo={currentRepoInfo}
|
currentRepoInfo={currentRepoInfo}
|
||||||
repoTags={repoTags}
|
repoTags={repoTags}
|
||||||
fileTags={fileTags}
|
fileTags={fileTags}
|
||||||
|
enableMetadata={enableMetadata}
|
||||||
|
enableFaceRecognition={enableFaceRecognition}
|
||||||
|
detailsSettings={detailsSettings}
|
||||||
|
modifyDetailsSettings={modifyDetailsSettings}
|
||||||
|
tagsData={tagsData}
|
||||||
|
addTag={addTag}
|
||||||
|
modifyLocalFileTags={modifyLocalFileTags}
|
||||||
onFileTagChanged={onFileTagChanged}
|
onFileTagChanged={onFileTagChanged}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
/>
|
/>
|
||||||
|
@@ -73,7 +73,7 @@ const LibDetail = React.memo(({ currentRepoInfo, onClose }) => {
|
|||||||
|
|
||||||
LibDetail.propTypes = {
|
LibDetail.propTypes = {
|
||||||
currentRepoInfo: PropTypes.object.isRequired,
|
currentRepoInfo: PropTypes.object.isRequired,
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default LibDetail;
|
export default LibDetail;
|
||||||
|
90
frontend/src/components/search/details/details.js
Normal file
90
frontend/src/components/search/details/details.js
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Body, Header } from '../../dirent-detail/detail';
|
||||||
|
import { siteRoot, thumbnailSizeForGrid } from '../../../utils/constants';
|
||||||
|
import { Utils } from '../../../utils/utils';
|
||||||
|
import FileDetails from '../../dirent-detail/dirent-details/file-details';
|
||||||
|
import DirDetails from '../../dirent-detail/dirent-details/dir-details';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useMetadataStatus } from '../../../hooks';
|
||||||
|
import tagsAPI from '../../../tag/api';
|
||||||
|
import { PER_LOAD_NUMBER } from '../../../metadata/constants';
|
||||||
|
import { normalizeColumns } from '../../../tag/utils/column';
|
||||||
|
import { TAGS_DEFAULT_SORT } from '../../../tag/constants/sort';
|
||||||
|
import TagsData from '../../../tag/model/tagsData';
|
||||||
|
import toaster from '../../toast';
|
||||||
|
|
||||||
|
const Details = ({ repoID, repoInfo, path, dirent, direntDetail }) => {
|
||||||
|
const [tagsData, setTagsData] = useState(null);
|
||||||
|
const { enableMetadata, enableTags } = useMetadataStatus();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (enableMetadata && enableTags) {
|
||||||
|
tagsAPI.getTags(repoID, 0, PER_LOAD_NUMBER).then(res => {
|
||||||
|
const rows = res?.data?.results || [];
|
||||||
|
const columns = normalizeColumns(res?.data?.metadata);
|
||||||
|
const tagsData = new TagsData({ rows, columns, TAGS_DEFAULT_SORT });
|
||||||
|
setTagsData(tagsData);
|
||||||
|
}).catch(error => {
|
||||||
|
const errorMsg = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errorMsg);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setTagsData(null);
|
||||||
|
}
|
||||||
|
}, [repoID, enableMetadata, enableTags]);
|
||||||
|
|
||||||
|
let src = '';
|
||||||
|
if (repoInfo.encrypted) {
|
||||||
|
src = `${siteRoot}repo/${repoID}/raw` + Utils.encodePath(`${path === '/' ? '' : path}/${dirent.name}`);
|
||||||
|
} else {
|
||||||
|
src = `${siteRoot}thumbnail/${repoID}/${thumbnailSizeForGrid}` + Utils.encodePath(`${path === '/' ? '' : path}/${dirent.name}`) + '?mtime=' + direntDetail.mtime;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="searched-item-details">
|
||||||
|
<div
|
||||||
|
className="cur-view-detail"
|
||||||
|
style={{ width: 300 }}
|
||||||
|
>
|
||||||
|
<Header title={dirent?.name || ''} icon={Utils.getDirentIcon(dirent, true)}></Header>
|
||||||
|
<Body>
|
||||||
|
{Utils.imageCheck(dirent.name) && (
|
||||||
|
<div className="detail-image">
|
||||||
|
<img src={src} alt="" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="detail-content">
|
||||||
|
{dirent.type !== 'file' ? (
|
||||||
|
<DirDetails
|
||||||
|
direntDetail={direntDetail}
|
||||||
|
readOnly={true}
|
||||||
|
tagsData={tagsData}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<FileDetails
|
||||||
|
repoID={repoID}
|
||||||
|
dirent={dirent}
|
||||||
|
path={path}
|
||||||
|
direntDetail={direntDetail}
|
||||||
|
repoTags={[]}
|
||||||
|
fileTagList={dirent ? dirent.file_tags : []}
|
||||||
|
readOnly={true}
|
||||||
|
tagsData={tagsData}
|
||||||
|
onFileTagChanged={() => {}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Body>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Details.propTypes = {
|
||||||
|
repoID: PropTypes.string.isRequired,
|
||||||
|
repoInfo: PropTypes.object.isRequired,
|
||||||
|
path: PropTypes.string.isRequired,
|
||||||
|
dirent: PropTypes.object.isRequired,
|
||||||
|
direntDetail: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Details;
|
4
frontend/src/components/search/details/index.css
Normal file
4
frontend/src/components/search/details/index.css
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
.searched-item-details .detail-header {
|
||||||
|
border: none;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
103
frontend/src/components/search/details/index.js
Normal file
103
frontend/src/components/search/details/index.js
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Utils } from '../../../utils/utils';
|
||||||
|
import { seafileAPI } from '../../../utils/seafile-api';
|
||||||
|
import toaster from '../../toast';
|
||||||
|
import { MetadataDetailsProvider } from '../../../metadata';
|
||||||
|
import { Repo } from '../../../models';
|
||||||
|
import { MetadataStatusProvider } from '../../../hooks';
|
||||||
|
import Details from './details';
|
||||||
|
import LibDetail from '../../dirent-detail/lib-details';
|
||||||
|
|
||||||
|
import './index.css';
|
||||||
|
|
||||||
|
const SearchedItemDetails = ({ repoID, path, dirent }) => {
|
||||||
|
const [repoInfo, setRepoInfo] = useState(null);
|
||||||
|
const [direntDetail, setDirentDetail] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
seafileAPI.getRepoInfo(repoID).then(res => {
|
||||||
|
const repo = new Repo(res.data);
|
||||||
|
setRepoInfo(repo);
|
||||||
|
}).catch(error => {
|
||||||
|
const errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}, [repoID]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const controller = new AbortController();
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
if (!repoID || !path || !dirent || dirent.isLib) {
|
||||||
|
setDirentDetail(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await seafileAPI[dirent.type === 'file' ? 'getFileInfo' : 'getDirInfo'](
|
||||||
|
repoID,
|
||||||
|
path,
|
||||||
|
{ signal: controller.signal }
|
||||||
|
);
|
||||||
|
setDirentDetail(res.data);
|
||||||
|
} catch (error) {
|
||||||
|
if (error.name !== 'AbortError') {
|
||||||
|
const errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const timer = setTimeout(fetchData, 200);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
controller.abort();
|
||||||
|
clearTimeout(timer);
|
||||||
|
};
|
||||||
|
}, [repoID, repoInfo, path, dirent]);
|
||||||
|
|
||||||
|
if (!repoInfo) return;
|
||||||
|
|
||||||
|
if (dirent.isLib) {
|
||||||
|
return (
|
||||||
|
<div className="searched-item-details">
|
||||||
|
<LibDetail currentRepoInfo={repoInfo} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!direntDetail) return null;
|
||||||
|
|
||||||
|
let parentDir = path !== '/' && path.endsWith('/') ? path.slice(0, -1) : path; // deal with folder path comes from search results, eg: /folder/
|
||||||
|
parentDir = Utils.getDirName(parentDir);
|
||||||
|
return (
|
||||||
|
<MetadataStatusProvider key={repoID} repoID={repoID} repoInfo={repoInfo}>
|
||||||
|
<MetadataDetailsProvider
|
||||||
|
repoID={repoID}
|
||||||
|
repoInfo={repoInfo}
|
||||||
|
path={path !== '/' && path.endsWith('/') ? path.slice(0, -1) : path}
|
||||||
|
dirent={dirent}
|
||||||
|
direntDetail={direntDetail}
|
||||||
|
direntType={dirent?.type !== 'file' ? 'dir' : 'file'}
|
||||||
|
>
|
||||||
|
<Details
|
||||||
|
repoID={repoID}
|
||||||
|
repoInfo={repoInfo}
|
||||||
|
path={parentDir}
|
||||||
|
dirent={dirent}
|
||||||
|
direntDetail={direntDetail}
|
||||||
|
/>
|
||||||
|
</MetadataDetailsProvider>
|
||||||
|
</MetadataStatusProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
SearchedItemDetails.propTypes = {
|
||||||
|
repoID: PropTypes.string.isRequired,
|
||||||
|
path: PropTypes.string.isRequired,
|
||||||
|
dirent: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SearchedItemDetails;
|
||||||
|
|
@@ -5,17 +5,38 @@ import { Utils } from '../../utils/utils';
|
|||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
item: PropTypes.object.isRequired,
|
item: PropTypes.object.isRequired,
|
||||||
|
idx: PropTypes.number.isRequired,
|
||||||
onItemClickHandler: PropTypes.func.isRequired,
|
onItemClickHandler: PropTypes.func.isRequired,
|
||||||
isHighlight: PropTypes.bool,
|
isHighlight: PropTypes.bool,
|
||||||
setRef: PropTypes.func,
|
setRef: PropTypes.func,
|
||||||
|
onHighlightIndex: PropTypes.func,
|
||||||
|
timer: PropTypes.number,
|
||||||
|
onSetTimer: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
class SearchResultItem extends React.Component {
|
class SearchResultItem extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.controller = null;
|
||||||
|
}
|
||||||
|
|
||||||
onClickHandler = () => {
|
onClickHandler = () => {
|
||||||
this.props.onItemClickHandler(this.props.item);
|
this.props.onItemClickHandler(this.props.item);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onMouseEnter = () => {
|
||||||
|
if (this.props.isHighlight) return;
|
||||||
|
if (this.controller) {
|
||||||
|
this.controller.abort();
|
||||||
|
}
|
||||||
|
this.controller = new AbortController();
|
||||||
|
|
||||||
|
if (this.props.onHighlightIndex) {
|
||||||
|
this.props.onHighlightIndex(this.props.idx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { item, setRef = (() => {}) } = this.props;
|
const { item, setRef = (() => {}) } = this.props;
|
||||||
let folderIconUrl = item.link_content ? Utils.getFolderIconUrl(false, 192) : Utils.getDefaultLibIconUrl();
|
let folderIconUrl = item.link_content ? Utils.getFolderIconUrl(false, 192) : Utils.getDefaultLibIconUrl();
|
||||||
@@ -32,6 +53,7 @@ class SearchResultItem extends React.Component {
|
|||||||
className={classnames('search-result-item', { 'search-result-item-highlight': this.props.isHighlight })}
|
className={classnames('search-result-item', { 'search-result-item-highlight': this.props.isHighlight })}
|
||||||
onClick={this.onClickHandler}
|
onClick={this.onClickHandler}
|
||||||
ref={ref => setRef(ref)}
|
ref={ref => setRef(ref)}
|
||||||
|
onMouseEnter={this.onMouseEnter}
|
||||||
>
|
>
|
||||||
<img className={item.link_content ? 'item-img' : 'lib-item-img'} src={fileIconUrl} alt="" />
|
<img className={item.link_content ? 'item-img' : 'lib-item-img'} src={fileIconUrl} alt="" />
|
||||||
<div className="item-content">
|
<div className="item-content">
|
||||||
|
@@ -8,7 +8,7 @@ import searchAPI from '../../utils/search-api';
|
|||||||
import { gettext } from '../../utils/constants';
|
import { gettext } from '../../utils/constants';
|
||||||
import SearchResultItem from './search-result-item';
|
import SearchResultItem from './search-result-item';
|
||||||
import SearchResultLibrary from './search-result-library';
|
import SearchResultLibrary from './search-result-library';
|
||||||
import { Utils } from '../../utils/utils';
|
import { debounce, Utils } from '../../utils/utils';
|
||||||
import toaster from '../toast';
|
import toaster from '../toast';
|
||||||
import Loading from '../loading';
|
import Loading from '../loading';
|
||||||
import { SEARCH_MASK, SEARCH_CONTAINER } from '../../constants/zIndexes';
|
import { SEARCH_MASK, SEARCH_CONTAINER } from '../../constants/zIndexes';
|
||||||
@@ -16,6 +16,8 @@ import { PRIVATE_FILE_TYPE, SEARCH_FILTER_BY_DATE_OPTION_KEY, SEARCH_FILTER_BY_D
|
|||||||
import SearchFilters from './search-filters';
|
import SearchFilters from './search-filters';
|
||||||
import SearchTags from './search-tags';
|
import SearchTags from './search-tags';
|
||||||
import IconBtn from '../icon-btn';
|
import IconBtn from '../icon-btn';
|
||||||
|
import SearchedItemDetails from './details';
|
||||||
|
import { CollaboratorsProvider } from '../../metadata';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
repoID: PropTypes.string,
|
repoID: PropTypes.string,
|
||||||
@@ -75,6 +77,7 @@ class Search extends Component {
|
|||||||
this.isChineseInput = false;
|
this.isChineseInput = false;
|
||||||
this.searchResultListContainerRef = React.createRef();
|
this.searchResultListContainerRef = React.createRef();
|
||||||
this.calculateStoreKey(props);
|
this.calculateStoreKey(props);
|
||||||
|
this.timer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -138,7 +141,7 @@ class Search extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onFocusHandler = () => {
|
onFocusHandler = () => {
|
||||||
this.setState({ width: '570px', isMaskShow: true });
|
this.setState({ width: '100%', isMaskShow: true });
|
||||||
this.calculateHighlightType();
|
this.calculateHighlightType();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -700,22 +703,49 @@ class Search extends Component {
|
|||||||
this.getSearchResult(this.buildSearchParams(queryData));
|
this.getSearchResult(this.buildSearchParams(queryData));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
renderDetails = (results) => {
|
||||||
|
const { repoID: currentRepoID } = this.props;
|
||||||
|
const { highlightIndex } = this.state;
|
||||||
|
const item = results[highlightIndex];
|
||||||
|
if (!item) return null;
|
||||||
|
const repoID = item.repo_id;
|
||||||
|
const isLib = !currentRepoID && item.path === '/';
|
||||||
|
const dirent = { name: item.name, type: item.is_dir ? 'dir' : 'file', isLib, file_tags: [], path: item.path };
|
||||||
|
return (
|
||||||
|
<CollaboratorsProvider repoID={repoID}>
|
||||||
|
<SearchedItemDetails repoID={repoID} path={item.path} dirent={dirent} />
|
||||||
|
</CollaboratorsProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
debounceHighlight = debounce((index) => {
|
||||||
|
this.setState({ highlightIndex: index });
|
||||||
|
}, 200);
|
||||||
|
|
||||||
renderResults = (resultItems, isVisited) => {
|
renderResults = (resultItems, isVisited) => {
|
||||||
const { highlightIndex } = this.state;
|
const { highlightIndex } = this.state;
|
||||||
|
|
||||||
const results = (
|
const results = (
|
||||||
<>
|
<>
|
||||||
{isVisited && <h4 className="visited-search-results-title">{gettext('Search results visited recently')}</h4>}
|
{isVisited ? (
|
||||||
|
<h4 className="visited-search-results-title">{gettext('Search results visited recently')}</h4>
|
||||||
|
) : (
|
||||||
|
<h4 className="search-results-title">{gettext('Files')}</h4>
|
||||||
|
)}
|
||||||
<ul className="search-result-list" ref={this.searchResultListRef}>
|
<ul className="search-result-list" ref={this.searchResultListRef}>
|
||||||
{resultItems.map((item, index) => {
|
{resultItems.map((item, index) => {
|
||||||
const isHighlight = index === highlightIndex;
|
const isHighlight = index === highlightIndex;
|
||||||
return (
|
return (
|
||||||
<SearchResultItem
|
<SearchResultItem
|
||||||
key={index}
|
key={index}
|
||||||
|
idx={index}
|
||||||
item={item}
|
item={item}
|
||||||
onItemClickHandler={this.onItemClickHandler}
|
onItemClickHandler={this.onItemClickHandler}
|
||||||
isHighlight={isHighlight}
|
isHighlight={isHighlight}
|
||||||
setRef={isHighlight ? (ref) => {this.highlightRef = ref;} : () => {}}
|
setRef={isHighlight ? (ref) => {this.highlightRef = ref;} : () => {}}
|
||||||
|
onHighlightIndex={this.debounceHighlight}
|
||||||
|
timer={this.timer}
|
||||||
|
onSetTimer={(timer) => {this.timer = timer;}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -726,8 +756,12 @@ class Search extends Component {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MediaQuery query="(min-width: 768px)">
|
<MediaQuery query="(min-width: 768px)">
|
||||||
{!isVisited && <h4 className="search-results-title">{gettext('Files')}</h4>}
|
<div className="search-result-sidepanel-wrapper d-flex">
|
||||||
<div className="search-result-list-container" ref={this.searchResultListContainerRef}>{results}</div>
|
<div className="search-result-list-container" ref={this.searchResultListContainerRef}>{results}</div>
|
||||||
|
<div className="search-result-container-sidepanel d-flex flex-column flex-grow-1">
|
||||||
|
{this.renderDetails(resultItems)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</MediaQuery>
|
</MediaQuery>
|
||||||
<MediaQuery query="(max-width: 767.8px)">
|
<MediaQuery query="(max-width: 767.8px)">
|
||||||
{results}
|
{results}
|
||||||
|
@@ -22,7 +22,7 @@
|
|||||||
box-shadow: 0 3px 8px 0 rgba(116, 129, 141, 0.1);
|
box-shadow: 0 3px 8px 0 rgba(116, 129, 141, 0.1);
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
width: 600px;
|
width: 700px;
|
||||||
padding: 16px 0;
|
padding: 16px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,8 +112,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-search-result-container {
|
.dropdown-search-result-container {
|
||||||
max-height: 300px;
|
|
||||||
overflow: auto;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 0;
|
top: 0;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
@@ -134,6 +132,9 @@
|
|||||||
|
|
||||||
|
|
||||||
.search-result-container .search-result-list-container {
|
.search-result-container .search-result-list-container {
|
||||||
|
max-height: 400px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
scrollbar-color: #C1C1C1 rgba(0, 0, 0, 0);
|
scrollbar-color: #C1C1C1 rgba(0, 0, 0, 0);
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -149,11 +150,28 @@
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-result-container .search-result-item:hover,
|
|
||||||
.search-result-container .search-result-item.search-result-item-highlight {
|
.search-result-container .search-result-item.search-result-item-highlight {
|
||||||
background-color: #f0f0f0;
|
background-color: #f0f0f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search-result-container .search-result-container-sidepanel {
|
||||||
|
max-width: 300px;
|
||||||
|
max-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result-container .search-result-container-sidepanel .searched-item-details {
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result-container .sf-metadata-status-loading-container {
|
||||||
|
width: 300px;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.search-result-item .item-img {
|
.search-result-item .item-img {
|
||||||
width: 36px;
|
width: 36px;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
|
@@ -126,11 +126,10 @@ export const MetadataStatusProvider = ({ repoID, repoInfo, hideMetadataView, sta
|
|||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div style={{ width: '300px' }}>
|
<div className="metadata-status-loading-container">
|
||||||
<Loading/>
|
<Loading/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@@ -4,9 +4,8 @@ import Formatter from '../formatter';
|
|||||||
import FileName from './file-name';
|
import FileName from './file-name';
|
||||||
import { useCollaborators } from '../../hooks';
|
import { useCollaborators } from '../../hooks';
|
||||||
import { CellType } from '../../constants';
|
import { CellType } from '../../constants';
|
||||||
import { useTags } from '../../../tag/hooks';
|
|
||||||
|
|
||||||
const CellFormatter = ({ readonly, value, field, record, ...params }) => {
|
const CellFormatter = ({ readonly, value, field, record, tagsData, ...params }) => {
|
||||||
const { collaborators, collaboratorsCache, updateCollaboratorsCache, queryUser } = useCollaborators();
|
const { collaborators, collaboratorsCache, updateCollaboratorsCache, queryUser } = useCollaborators();
|
||||||
const props = useMemo(() => {
|
const props = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
@@ -20,7 +19,6 @@ const CellFormatter = ({ readonly, value, field, record, ...params }) => {
|
|||||||
record,
|
record,
|
||||||
};
|
};
|
||||||
}, [readonly, value, field, collaborators, collaboratorsCache, updateCollaboratorsCache, queryUser, record]);
|
}, [readonly, value, field, collaborators, collaboratorsCache, updateCollaboratorsCache, queryUser, record]);
|
||||||
const { tagsData } = useTags();
|
|
||||||
|
|
||||||
if (field.type === CellType.FILE_NAME) {
|
if (field.type === CellType.FILE_NAME) {
|
||||||
return (<FileName { ...props } { ...params } record={record} />);
|
return (<FileName { ...props } { ...params } record={record} />);
|
||||||
@@ -36,6 +34,7 @@ CellFormatter.propTypes = {
|
|||||||
value: PropTypes.any,
|
value: PropTypes.any,
|
||||||
field: PropTypes.object.isRequired,
|
field: PropTypes.object.isRequired,
|
||||||
record: PropTypes.object,
|
record: PropTypes.object,
|
||||||
|
tagsData: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CellFormatter;
|
export default CellFormatter;
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import CellFormatter from '../cell-formatter';
|
import CellFormatter from '../cell-formatter';
|
||||||
import DetailEditor from '../detail-editor';
|
import DetailEditor from '../detail-editor';
|
||||||
import DetailItem from '../../../components/dirent-detail/detail-item';
|
import DetailItem from '../../../components/dirent-detail/detail-item';
|
||||||
@@ -13,7 +14,7 @@ import Location from './location';
|
|||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
const MetadataDetails = () => {
|
const MetadataDetails = ({ readOnly, tagsData }) => {
|
||||||
const { canModifyRecord, record, columns, onChange, modifyColumnData, updateFileTags } = useMetadataDetails();
|
const { canModifyRecord, record, columns, onChange, modifyColumnData, updateFileTags } = useMetadataDetails();
|
||||||
|
|
||||||
const displayColumns = useMemo(() => columns.filter(c => c.shown), [columns]);
|
const displayColumns = useMemo(() => columns.filter(c => c.shown), [columns]);
|
||||||
@@ -34,7 +35,7 @@ const MetadataDetails = () => {
|
|||||||
return <Location key={field.key} position={value} record={record} />;
|
return <Location key={field.key} position={value} record={record} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
let canEdit = canModifyRecord && field.editable;
|
let canEdit = canModifyRecord && field.editable && !readOnly;
|
||||||
if (!isImageOrVideo && IMAGE_PRIVATE_COLUMN_KEYS.includes(field.key)) {
|
if (!isImageOrVideo && IMAGE_PRIVATE_COLUMN_KEYS.includes(field.key)) {
|
||||||
canEdit = false;
|
canEdit = false;
|
||||||
} else if (field.key === PRIVATE_COLUMN_KEY.TAGS && isDir) {
|
} else if (field.key === PRIVATE_COLUMN_KEY.TAGS && isDir) {
|
||||||
@@ -59,6 +60,7 @@ const MetadataDetails = () => {
|
|||||||
value={value}
|
value={value}
|
||||||
emptyTip={gettext('Empty')}
|
emptyTip={gettext('Empty')}
|
||||||
className="sf-metadata-property-detail-formatter"
|
className="sf-metadata-property-detail-formatter"
|
||||||
|
tagsData={tagsData}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</DetailItem>
|
</DetailItem>
|
||||||
@@ -68,4 +70,9 @@ const MetadataDetails = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
MetadataDetails.propTypes = {
|
||||||
|
readOnly: PropTypes.bool,
|
||||||
|
tagsData: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
export default MetadataDetails;
|
export default MetadataDetails;
|
||||||
|
@@ -41,8 +41,10 @@ class Location extends React.Component {
|
|||||||
this.initMap();
|
this.initMap();
|
||||||
|
|
||||||
this.unsubscribeClearMapInstance = eventBus.subscribe(EVENT_BUS_TYPE.CLEAR_MAP_INSTANCE, () => {
|
this.unsubscribeClearMapInstance = eventBus.subscribe(EVENT_BUS_TYPE.CLEAR_MAP_INSTANCE, () => {
|
||||||
|
if (window.mapInstance) {
|
||||||
window.mapInstance = null;
|
window.mapInstance = null;
|
||||||
delete window.mapInstance;
|
delete window.mapInstance;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -9,16 +9,14 @@ import { normalizeFields } from '../components/metadata-details/utils';
|
|||||||
import { CellType, EVENT_BUS_TYPE, PRIVATE_COLUMN_KEY } from '../constants';
|
import { CellType, EVENT_BUS_TYPE, PRIVATE_COLUMN_KEY } from '../constants';
|
||||||
import { getCellValueByColumn, getColumnOptionNamesByIds, getColumnOptionNameById, getRecordIdFromRecord, getServerOptions } from '../utils/cell';
|
import { getCellValueByColumn, getColumnOptionNamesByIds, getColumnOptionNameById, getRecordIdFromRecord, getServerOptions } from '../utils/cell';
|
||||||
import tagsAPI from '../../tag/api';
|
import tagsAPI from '../../tag/api';
|
||||||
import { useTags } from '../../tag/hooks';
|
|
||||||
import { getColumnByKey, getColumnOptions, getColumnOriginName } from '../utils/column';
|
import { getColumnByKey, getColumnOptions, getColumnOriginName } from '../utils/column';
|
||||||
import ObjectUtils from '../../utils/object';
|
import ObjectUtils from '../../utils/object';
|
||||||
import { NOT_DISPLAY_COLUMN_KEYS } from '../components/metadata-details/constants';
|
import { NOT_DISPLAY_COLUMN_KEYS } from '../components/metadata-details/constants';
|
||||||
|
|
||||||
const MetadataDetailsContext = React.createContext(null);
|
const MetadataDetailsContext = React.createContext(null);
|
||||||
|
|
||||||
export const MetadataDetailsProvider = ({ repoID, repoInfo, path, dirent, direntDetail, direntType, children }) => {
|
export const MetadataDetailsProvider = ({ repoID, repoInfo, path, dirent, direntDetail, direntType, modifyLocalFileTags, children }) => {
|
||||||
const { enableMetadata, detailsSettings, modifyDetailsSettings } = useMetadataStatus();
|
const { enableMetadata, detailsSettings, modifyDetailsSettings } = useMetadataStatus();
|
||||||
const { modifyLocalFileTags } = useTags();
|
|
||||||
|
|
||||||
const [isLoading, setLoading] = useState(true);
|
const [isLoading, setLoading] = useState(true);
|
||||||
const [record, setRecord] = useState(null);
|
const [record, setRecord] = useState(null);
|
||||||
@@ -137,7 +135,7 @@ export const MetadataDetailsProvider = ({ repoID, repoInfo, path, dirent, dirent
|
|||||||
}, [repoID, record, modifyLocalFileTags]);
|
}, [repoID, record, modifyLocalFileTags]);
|
||||||
|
|
||||||
const saveColumns = useCallback((columns) => {
|
const saveColumns = useCallback((columns) => {
|
||||||
modifyDetailsSettings({ columns: columns.map(c => ({ key: c.key, shown: c.shown })) });
|
modifyDetailsSettings && modifyDetailsSettings({ columns: columns.map(c => ({ key: c.key, shown: c.shown })) });
|
||||||
}, [modifyDetailsSettings]);
|
}, [modifyDetailsSettings]);
|
||||||
|
|
||||||
const modifyHiddenColumns = useCallback((hiddenColumns) => {
|
const modifyHiddenColumns = useCallback((hiddenColumns) => {
|
||||||
|
@@ -14,6 +14,7 @@ const Card = ({
|
|||||||
record,
|
record,
|
||||||
titleColumn,
|
titleColumn,
|
||||||
displayColumns,
|
displayColumns,
|
||||||
|
tagsData,
|
||||||
onOpenFile,
|
onOpenFile,
|
||||||
onSelectCard,
|
onSelectCard,
|
||||||
onContextMenu,
|
onContextMenu,
|
||||||
@@ -45,7 +46,7 @@ const Card = ({
|
|||||||
>
|
>
|
||||||
{titleColumn && (
|
{titleColumn && (
|
||||||
<div className="sf-metadata-kanban-card-header">
|
<div className="sf-metadata-kanban-card-header">
|
||||||
<Formatter value={titleValue} column={titleColumn} record={record} onFileNameClick={handleFilenameClick} />
|
<Formatter value={titleValue} column={titleColumn} record={record} onFileNameClick={handleFilenameClick} tagsData={tagsData} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="sf-metadata-kanban-card-body">
|
<div className="sf-metadata-kanban-card-body">
|
||||||
@@ -65,7 +66,7 @@ const Card = ({
|
|||||||
return (
|
return (
|
||||||
<div className="sf-metadata-kanban-card-record" key={column.key}>
|
<div className="sf-metadata-kanban-card-record" key={column.key}>
|
||||||
{displayColumnName && (<div className="sf-metadata-kanban-card-record-name">{column.name}</div>)}
|
{displayColumnName && (<div className="sf-metadata-kanban-card-record-name">{column.name}</div>)}
|
||||||
<Formatter value={value} column={column} record={record}/>
|
<Formatter value={value} column={column} record={record} tagsData={tagsData} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@@ -14,7 +14,7 @@ const SPECIAL_FILE_ICON = [
|
|||||||
'word.png',
|
'word.png',
|
||||||
];
|
];
|
||||||
|
|
||||||
const Formatter = ({ value, column, record, ...params }) => {
|
const Formatter = ({ value, column, record, tagsData, ...params }) => {
|
||||||
let className = '';
|
let className = '';
|
||||||
|
|
||||||
if (column.type === CellType.FILE_NAME && value) {
|
if (column.type === CellType.FILE_NAME && value) {
|
||||||
@@ -24,7 +24,7 @@ const Formatter = ({ value, column, record, ...params }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (<CellFormatter { ...params } readonly={true} className={className} value={value} field={column} record={record} />);
|
return (<CellFormatter { ...params } readonly={true} className={className} value={value} field={column} record={record} tagsData={tagsData} />);
|
||||||
};
|
};
|
||||||
|
|
||||||
Formatter.propTypes = {
|
Formatter.propTypes = {
|
||||||
|
@@ -8,7 +8,7 @@ import { CellType } from '../../../../../constants';
|
|||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
const Header = ({ readonly, haveFreezed, value, groupByColumn, cardsQuantity, onDelete, onFreezed, onUnFreezed, isCollapsed, onCollapse }) => {
|
const Header = ({ readonly, haveFreezed, value, groupByColumn, cardsQuantity, tagsData, onDelete, onFreezed, onUnFreezed, isCollapsed, onCollapse }) => {
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
const [active, setActive] = useState(false);
|
const [active, setActive] = useState(false);
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ const Header = ({ readonly, haveFreezed, value, groupByColumn, cardsQuantity, on
|
|||||||
<div className="sf-metadata-view-kanban-board-header" onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
|
<div className="sf-metadata-view-kanban-board-header" onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
|
||||||
<div className="sf-metadata-view-kanban-board-header-title" ref={headerRef}>
|
<div className="sf-metadata-view-kanban-board-header-title" ref={headerRef}>
|
||||||
{value ? (
|
{value ? (
|
||||||
<CellFormatter value={titleValue} field={groupByColumn} readonly={true} />
|
<CellFormatter value={titleValue} field={groupByColumn} readonly={true} tagsData={tagsData} />
|
||||||
) : (
|
) : (
|
||||||
<span>{gettext('Uncategorized')}</span>
|
<span>{gettext('Uncategorized')}</span>
|
||||||
)}
|
)}
|
||||||
|
@@ -8,6 +8,7 @@ import { useMetadataView } from '../../../../hooks/metadata-view';
|
|||||||
import { getRowById } from '../../../../../components/sf-table/utils/table';
|
import { getRowById } from '../../../../../components/sf-table/utils/table';
|
||||||
import { getRecordIdFromRecord } from '../../../../utils/cell';
|
import { getRecordIdFromRecord } from '../../../../utils/cell';
|
||||||
import { gettext } from '@/utils/constants';
|
import { gettext } from '@/utils/constants';
|
||||||
|
import { useTags } from '../../../../../tag/hooks';
|
||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
@@ -37,6 +38,7 @@ const Board = ({
|
|||||||
const cardsQuantity = useMemo(() => board.children.length, [board.children]);
|
const cardsQuantity = useMemo(() => board.children.length, [board.children]);
|
||||||
|
|
||||||
const { metadata } = useMetadataView();
|
const { metadata } = useMetadataView();
|
||||||
|
const { tagsData } = useTags();
|
||||||
|
|
||||||
const onDragStart = useCallback(({ payload }) => {
|
const onDragStart = useCallback(({ payload }) => {
|
||||||
updateDragging(true);
|
updateDragging(true);
|
||||||
@@ -68,6 +70,7 @@ const Board = ({
|
|||||||
groupByColumn={groupByColumn}
|
groupByColumn={groupByColumn}
|
||||||
haveFreezed={haveFreezed}
|
haveFreezed={haveFreezed}
|
||||||
cardsQuantity={cardsQuantity}
|
cardsQuantity={cardsQuantity}
|
||||||
|
tagsData={tagsData}
|
||||||
onDelete={() => deleteOption(board.key)}
|
onDelete={() => deleteOption(board.key)}
|
||||||
onFreezed={onFreezed}
|
onFreezed={onFreezed}
|
||||||
onUnFreezed={onUnFreezed}
|
onUnFreezed={onUnFreezed}
|
||||||
@@ -114,6 +117,7 @@ const Board = ({
|
|||||||
record={record}
|
record={record}
|
||||||
titleColumn={titleColumn}
|
titleColumn={titleColumn}
|
||||||
displayColumns={displayColumns}
|
displayColumns={displayColumns}
|
||||||
|
tagsData={tagsData}
|
||||||
onOpenFile={onOpenFile}
|
onOpenFile={onOpenFile}
|
||||||
onSelectCard={onSelectCard}
|
onSelectCard={onSelectCard}
|
||||||
onContextMenu={(e) => onContextMenu(e, recordId)}
|
onContextMenu={(e) => onContextMenu(e, recordId)}
|
||||||
|
@@ -6,7 +6,7 @@ import RateEditor from '../../../../../../components/cell-editors/rate-editor';
|
|||||||
import { canEditCell } from '../../../../../../utils/column';
|
import { canEditCell } from '../../../../../../utils/column';
|
||||||
import { CellType } from '../../../../../../constants';
|
import { CellType } from '../../../../../../constants';
|
||||||
|
|
||||||
const Formatter = ({ isCellSelected, field, value, onChange, record, ...params }) => {
|
const Formatter = ({ isCellSelected, field, value, onChange, record, tagsData, ...params }) => {
|
||||||
const { type } = field;
|
const { type } = field;
|
||||||
const cellEditAble = canEditCell(field, record, true);
|
const cellEditAble = canEditCell(field, record, true);
|
||||||
if (type === CellType.CHECKBOX && cellEditAble) {
|
if (type === CellType.CHECKBOX && cellEditAble) {
|
||||||
@@ -16,7 +16,7 @@ const Formatter = ({ isCellSelected, field, value, onChange, record, ...params }
|
|||||||
return (<RateEditor isCellSelected={isCellSelected} value={value} field={field} onChange={onChange} />);
|
return (<RateEditor isCellSelected={isCellSelected} value={value} field={field} onChange={onChange} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (<CellFormatter { ...params } readonly={true} value={value} field={field} record={record} />);
|
return (<CellFormatter { ...params } readonly={true} value={value} field={field} record={record} tagsData={tagsData} />);
|
||||||
};
|
};
|
||||||
|
|
||||||
Formatter.propTypes = {
|
Formatter.propTypes = {
|
||||||
|
@@ -9,6 +9,7 @@ import { isCellValueChanged, getCellValueByColumn } from '../../../../../../util
|
|||||||
import { CellType, PRIVATE_COLUMN_KEYS, TABLE_SUPPORT_EDIT_TYPE_MAP, EDITOR_TYPE, EVENT_BUS_TYPE } from '../../../../../../constants';
|
import { CellType, PRIVATE_COLUMN_KEYS, TABLE_SUPPORT_EDIT_TYPE_MAP, EDITOR_TYPE, EVENT_BUS_TYPE } from '../../../../../../constants';
|
||||||
import { checkIsDir } from '../../../../../../utils/row';
|
import { checkIsDir } from '../../../../../../utils/row';
|
||||||
import { openFile } from '../../../../../../utils/file';
|
import { openFile } from '../../../../../../utils/file';
|
||||||
|
import { useTags } from '../../../../../../../tag/hooks';
|
||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
@@ -27,6 +28,7 @@ const Cell = React.memo(({
|
|||||||
frozen,
|
frozen,
|
||||||
height,
|
height,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { tagsData } = useTags();
|
||||||
const canEditable = useMemo(() => {
|
const canEditable = useMemo(() => {
|
||||||
const { type } = column;
|
const { type } = column;
|
||||||
if (!window.sfMetadataContext.canModifyColumn(column)) return false;
|
if (!window.sfMetadataContext.canModifyColumn(column)) return false;
|
||||||
@@ -171,7 +173,7 @@ const Cell = React.memo(({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={`${record._id}-${column.key}`} {...containerProps}>
|
<div key={`${record._id}-${column.key}`} {...containerProps}>
|
||||||
<Formatter isCellSelected={isCellSelected} value={cellValue} field={column} onChange={modifyRecord} record={record} onFileNameClick={onFileNameClick} />
|
<Formatter isCellSelected={isCellSelected} value={cellValue} field={column} onChange={modifyRecord} record={record} tagsData={tagsData} onFileNameClick={onFileNameClick} />
|
||||||
{isCellSelected && (<CellOperationBtn record={record} column={column}/>)}
|
{isCellSelected && (<CellOperationBtn record={record} column={column}/>)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@@ -31,7 +31,7 @@ export const normalizeColumns = (columns) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyColumnWidth = window.sfTagsDataContext.localStorage.getItem('columns_width') || {};
|
const keyColumnWidth = window.sfTagsDataContext?.localStorage?.getItem('columns_width') || {};
|
||||||
return normalizedColumns.map((column) => {
|
return normalizedColumns.map((column) => {
|
||||||
const { key } = column;
|
const { key } = column;
|
||||||
let width = keyColumnWidth[column.key];
|
let width = keyColumnWidth[column.key];
|
||||||
|
Reference in New Issue
Block a user