diff --git a/frontend/src/components/dirent-detail/dirent-details/dir-details.js b/frontend/src/components/dirent-detail/dirent-details/dir-details.js index d1a803c7fe..e2c4209c6d 100644 --- a/frontend/src/components/dirent-detail/dirent-details/dir-details.js +++ b/frontend/src/components/dirent-detail/dirent-details/dir-details.js @@ -9,8 +9,9 @@ import { useMetadataStatus } from '../../../hooks'; import { Utils } from '../../../utils/utils'; import { SYSTEM_FOLDERS } from '../../../constants'; -const DirDetails = ({ direntDetail }) => { +const DirDetails = ({ readOnly = false, direntDetail, tagsData }) => { const { enableMetadata, enableMetadataManagement } = useMetadataStatus(); + const lastModifiedTimeField = useMemo(() => { return { type: CellType.MTIME, name: gettext('Last modified time') }; }, []); @@ -39,7 +40,7 @@ const DirDetails = ({ direntDetail }) => { : } - + )} @@ -50,7 +51,9 @@ const DirDetails = ({ direntDetail }) => { }; DirDetails.propTypes = { + readOnly: PropTypes.bool, direntDetail: PropTypes.object, + tagsData: PropTypes.object, }; export default DirDetails; diff --git a/frontend/src/components/dirent-detail/dirent-details/file-details/index.js b/frontend/src/components/dirent-detail/dirent-details/file-details/index.js index e79f086b6b..df54bf130e 100644 --- a/frontend/src/components/dirent-detail/dirent-details/file-details/index.js +++ b/frontend/src/components/dirent-detail/dirent-details/file-details/index.js @@ -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 { enableFaceRecognition, enableMetadata } = useMetadataStatus(); const { record } = useMetadataDetails(); @@ -102,7 +102,7 @@ const FileDetails = React.memo(({ repoID, dirent, path, direntDetail, isShowRepo /> )} - {enableMetadata && } + {enableMetadata && } ); @@ -162,6 +162,8 @@ FileDetails.propTypes = { direntDetail: PropTypes.object, repoTags: PropTypes.array, fileTagList: PropTypes.array, + readOnly: PropTypes.bool, + tagsData: PropTypes.object, onFileTagChanged: PropTypes.func, }; diff --git a/frontend/src/components/dirent-detail/dirent-details/index.js b/frontend/src/components/dirent-detail/dirent-details/index.js index 44199c0719..5e2d6d8b03 100644 --- a/frontend/src/components/dirent-detail/dirent-details/index.js +++ b/frontend/src/components/dirent-detail/dirent-details/index.js @@ -100,6 +100,7 @@ class DirentDetails extends React.Component { dirent={dirent} direntDetail={direntDetail} direntType={dirent?.type !== 'file' ? 'dir' : 'file'} + modifyLocalFileTags={this.props.modifyLocalFileTags} >
@@ -111,7 +112,11 @@ class DirentDetails extends React.Component { {dirent && direntDetail && (
{dirent.type !== 'file' ? ( - + ) : ( )} @@ -141,6 +148,13 @@ DirentDetails.propTypes = { onFileTagChanged: PropTypes.func.isRequired, repoTags: 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; diff --git a/frontend/src/components/dirent-detail/embedded-file-details/index.js b/frontend/src/components/dirent-detail/embedded-file-details/index.js index d0fbf4b598..3aa500b0c8 100644 --- a/frontend/src/components/dirent-detail/embedded-file-details/index.js +++ b/frontend/src/components/dirent-detail/embedded-file-details/index.js @@ -11,6 +11,7 @@ import { MetadataDetailsProvider } from '../../../metadata/hooks'; import AIIcon from '../../../metadata/components/metadata-details/ai-icon'; import SettingsIcon from '../../../metadata/components/metadata-details/settings-icon'; import Loading from '../../loading'; +import { useTags } from '../../../tag/hooks'; import './index.css'; @@ -21,6 +22,8 @@ const EmbeddedFileDetails = ({ repoID, repoInfo, dirent, path, onClose, width = const [direntDetail, setDirentDetail] = useState(''); const [isFetching, setIsFetching] = useState(true); + const { tagsData, addTag } = useTags(); + const isView = useMemo(() => { const urlParams = new URLSearchParams(window.location.search); return urlParams.has('view'); @@ -87,7 +90,15 @@ const EmbeddedFileDetails = ({ repoID, repoInfo, dirent, path, onClose, width = : dirent && direntDetail && (
- + {}} + />
)} diff --git a/frontend/src/components/dirent-detail/index.js b/frontend/src/components/dirent-detail/index.js index d1e5f082b6..f4fbc96e7e 100644 --- a/frontend/src/components/dirent-detail/index.js +++ b/frontend/src/components/dirent-detail/index.js @@ -8,8 +8,13 @@ import { MetadataContext } from '../../metadata'; import { PRIVATE_FILE_TYPE } from '../../constants'; import { METADATA_MODE, TAGS_MODE } from '../dir-view-mode/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 { 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 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} repoTags={repoTags} fileTags={fileTags} + enableMetadata={enableMetadata} + enableFaceRecognition={enableFaceRecognition} + detailsSettings={detailsSettings} + modifyDetailsSettings={modifyDetailsSettings} + tagsData={tagsData} + addTag={addTag} + modifyLocalFileTags={modifyLocalFileTags} onFileTagChanged={onFileTagChanged} onClose={onClose} /> diff --git a/frontend/src/components/dirent-detail/lib-details.js b/frontend/src/components/dirent-detail/lib-details.js index 6a03b82d7b..8acb9c2532 100644 --- a/frontend/src/components/dirent-detail/lib-details.js +++ b/frontend/src/components/dirent-detail/lib-details.js @@ -73,7 +73,7 @@ const LibDetail = React.memo(({ currentRepoInfo, onClose }) => { LibDetail.propTypes = { currentRepoInfo: PropTypes.object.isRequired, - onClose: PropTypes.func.isRequired, + onClose: PropTypes.func, }; export default LibDetail; diff --git a/frontend/src/components/search/details/details.js b/frontend/src/components/search/details/details.js new file mode 100644 index 0000000000..9a5be7973e --- /dev/null +++ b/frontend/src/components/search/details/details.js @@ -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 ( +
+
+
+ + {Utils.imageCheck(dirent.name) && ( +
+ +
+ )} +
+ {dirent.type !== 'file' ? ( + + ) : ( + {}} + /> + )} +
+ +
+
+ ); +}; + +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; diff --git a/frontend/src/components/search/details/index.css b/frontend/src/components/search/details/index.css new file mode 100644 index 0000000000..60a608c674 --- /dev/null +++ b/frontend/src/components/search/details/index.css @@ -0,0 +1,4 @@ +.searched-item-details .detail-header { + border: none; + padding: 8px; +} diff --git a/frontend/src/components/search/details/index.js b/frontend/src/components/search/details/index.js new file mode 100644 index 0000000000..51ab82c3d4 --- /dev/null +++ b/frontend/src/components/search/details/index.js @@ -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 ( +
+ +
+ ); + } + + 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 ( + + +
+ + + ); +}; + +SearchedItemDetails.propTypes = { + repoID: PropTypes.string.isRequired, + path: PropTypes.string.isRequired, + dirent: PropTypes.object.isRequired, +}; + +export default SearchedItemDetails; + diff --git a/frontend/src/components/search/search-result-item.js b/frontend/src/components/search/search-result-item.js index 67edbb4d6a..98255e35c5 100644 --- a/frontend/src/components/search/search-result-item.js +++ b/frontend/src/components/search/search-result-item.js @@ -5,17 +5,38 @@ import { Utils } from '../../utils/utils'; const propTypes = { item: PropTypes.object.isRequired, + idx: PropTypes.number.isRequired, onItemClickHandler: PropTypes.func.isRequired, isHighlight: PropTypes.bool, setRef: PropTypes.func, + onHighlightIndex: PropTypes.func, + timer: PropTypes.number, + onSetTimer: PropTypes.func, }; class SearchResultItem extends React.Component { + constructor(props) { + super(props); + this.controller = null; + } + onClickHandler = () => { 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() { const { item, setRef = (() => {}) } = this.props; 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 })} onClick={this.onClickHandler} ref={ref => setRef(ref)} + onMouseEnter={this.onMouseEnter} >
diff --git a/frontend/src/components/search/search.js b/frontend/src/components/search/search.js index 591c6f8630..f158d897e5 100644 --- a/frontend/src/components/search/search.js +++ b/frontend/src/components/search/search.js @@ -8,7 +8,7 @@ import searchAPI from '../../utils/search-api'; import { gettext } from '../../utils/constants'; import SearchResultItem from './search-result-item'; import SearchResultLibrary from './search-result-library'; -import { Utils } from '../../utils/utils'; +import { debounce, Utils } from '../../utils/utils'; import toaster from '../toast'; import Loading from '../loading'; 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 SearchTags from './search-tags'; import IconBtn from '../icon-btn'; +import SearchedItemDetails from './details'; +import { CollaboratorsProvider } from '../../metadata'; const propTypes = { repoID: PropTypes.string, @@ -75,6 +77,7 @@ class Search extends Component { this.isChineseInput = false; this.searchResultListContainerRef = React.createRef(); this.calculateStoreKey(props); + this.timer = null; } componentDidMount() { @@ -138,7 +141,7 @@ class Search extends Component { }; onFocusHandler = () => { - this.setState({ width: '570px', isMaskShow: true }); + this.setState({ width: '100%', isMaskShow: true }); this.calculateHighlightType(); }; @@ -700,22 +703,49 @@ class Search extends Component { 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 ( + + + + ); + }; + + debounceHighlight = debounce((index) => { + this.setState({ highlightIndex: index }); + }, 200); + renderResults = (resultItems, isVisited) => { const { highlightIndex } = this.state; const results = ( <> - {isVisited &&

{gettext('Search results visited recently')}

} + {isVisited ? ( +

{gettext('Search results visited recently')}

+ ) : ( +

{gettext('Files')}

+ )}
    {resultItems.map((item, index) => { const isHighlight = index === highlightIndex; return ( {this.highlightRef = ref;} : () => {}} + onHighlightIndex={this.debounceHighlight} + timer={this.timer} + onSetTimer={(timer) => {this.timer = timer;}} /> ); })} @@ -726,8 +756,12 @@ class Search extends Component { return ( <> - {!isVisited &&

    {gettext('Files')}

    } -
    {results}
    +
    +
    {results}
    +
    + {this.renderDetails(resultItems)} +
    +
    {results} diff --git a/frontend/src/css/search.css b/frontend/src/css/search.css index 6179e091f0..6ea4f12dda 100644 --- a/frontend/src/css/search.css +++ b/frontend/src/css/search.css @@ -22,7 +22,7 @@ box-shadow: 0 3px 8px 0 rgba(116, 129, 141, 0.1); background-color: #fff; cursor: default; - width: 600px; + width: 700px; padding: 16px 0; } @@ -112,8 +112,6 @@ } .dropdown-search-result-container { - max-height: 300px; - overflow: auto; position: relative; top: 0; box-shadow: none; @@ -134,6 +132,9 @@ .search-result-container .search-result-list-container { + max-height: 400px; + display: flex; + flex-direction: column; overflow: auto; scrollbar-color: #C1C1C1 rgba(0, 0, 0, 0); flex: 1; @@ -149,11 +150,28 @@ border-radius: 4px; } -.search-result-container .search-result-item:hover, .search-result-container .search-result-item.search-result-item-highlight { 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 { width: 36px; height: 36px; diff --git a/frontend/src/hooks/metadata-status.js b/frontend/src/hooks/metadata-status.js index 8144e4c5c9..afba9c1681 100644 --- a/frontend/src/hooks/metadata-status.js +++ b/frontend/src/hooks/metadata-status.js @@ -126,11 +126,10 @@ export const MetadataStatusProvider = ({ repoID, repoInfo, hideMetadataView, sta if (isLoading) { return ( -
    +
    ); - } return ( diff --git a/frontend/src/metadata/components/cell-formatter/index.js b/frontend/src/metadata/components/cell-formatter/index.js index c5560ea431..7f469fbaf4 100644 --- a/frontend/src/metadata/components/cell-formatter/index.js +++ b/frontend/src/metadata/components/cell-formatter/index.js @@ -4,9 +4,8 @@ import Formatter from '../formatter'; import FileName from './file-name'; import { useCollaborators } from '../../hooks'; 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 props = useMemo(() => { return { @@ -20,7 +19,6 @@ const CellFormatter = ({ readonly, value, field, record, ...params }) => { record, }; }, [readonly, value, field, collaborators, collaboratorsCache, updateCollaboratorsCache, queryUser, record]); - const { tagsData } = useTags(); if (field.type === CellType.FILE_NAME) { return (); @@ -36,6 +34,7 @@ CellFormatter.propTypes = { value: PropTypes.any, field: PropTypes.object.isRequired, record: PropTypes.object, + tagsData: PropTypes.object, }; export default CellFormatter; diff --git a/frontend/src/metadata/components/metadata-details/index.js b/frontend/src/metadata/components/metadata-details/index.js index f8bc984d47..260433aafd 100644 --- a/frontend/src/metadata/components/metadata-details/index.js +++ b/frontend/src/metadata/components/metadata-details/index.js @@ -1,4 +1,5 @@ import React, { useMemo } from 'react'; +import PropTypes from 'prop-types'; import CellFormatter from '../cell-formatter'; import DetailEditor from '../detail-editor'; import DetailItem from '../../../components/dirent-detail/detail-item'; @@ -13,7 +14,7 @@ import Location from './location'; import './index.css'; -const MetadataDetails = () => { +const MetadataDetails = ({ readOnly, tagsData }) => { const { canModifyRecord, record, columns, onChange, modifyColumnData, updateFileTags } = useMetadataDetails(); const displayColumns = useMemo(() => columns.filter(c => c.shown), [columns]); @@ -34,7 +35,7 @@ const MetadataDetails = () => { return ; } - let canEdit = canModifyRecord && field.editable; + let canEdit = canModifyRecord && field.editable && !readOnly; if (!isImageOrVideo && IMAGE_PRIVATE_COLUMN_KEYS.includes(field.key)) { canEdit = false; } else if (field.key === PRIVATE_COLUMN_KEY.TAGS && isDir) { @@ -59,6 +60,7 @@ const MetadataDetails = () => { value={value} emptyTip={gettext('Empty')} className="sf-metadata-property-detail-formatter" + tagsData={tagsData} /> } @@ -68,4 +70,9 @@ const MetadataDetails = () => { ); }; +MetadataDetails.propTypes = { + readOnly: PropTypes.bool, + tagsData: PropTypes.object, +}; + export default MetadataDetails; diff --git a/frontend/src/metadata/components/metadata-details/location/index.js b/frontend/src/metadata/components/metadata-details/location/index.js index fab4b0d3b6..1f553c5440 100644 --- a/frontend/src/metadata/components/metadata-details/location/index.js +++ b/frontend/src/metadata/components/metadata-details/location/index.js @@ -41,8 +41,10 @@ class Location extends React.Component { this.initMap(); this.unsubscribeClearMapInstance = eventBus.subscribe(EVENT_BUS_TYPE.CLEAR_MAP_INSTANCE, () => { - window.mapInstance = null; - delete window.mapInstance; + if (window.mapInstance) { + window.mapInstance = null; + delete window.mapInstance; + } }); } diff --git a/frontend/src/metadata/hooks/metadata-details.js b/frontend/src/metadata/hooks/metadata-details.js index 412c1d085e..757f5194f1 100644 --- a/frontend/src/metadata/hooks/metadata-details.js +++ b/frontend/src/metadata/hooks/metadata-details.js @@ -9,16 +9,14 @@ import { normalizeFields } from '../components/metadata-details/utils'; import { CellType, EVENT_BUS_TYPE, PRIVATE_COLUMN_KEY } from '../constants'; import { getCellValueByColumn, getColumnOptionNamesByIds, getColumnOptionNameById, getRecordIdFromRecord, getServerOptions } from '../utils/cell'; import tagsAPI from '../../tag/api'; -import { useTags } from '../../tag/hooks'; import { getColumnByKey, getColumnOptions, getColumnOriginName } from '../utils/column'; import ObjectUtils from '../../utils/object'; import { NOT_DISPLAY_COLUMN_KEYS } from '../components/metadata-details/constants'; 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 { modifyLocalFileTags } = useTags(); const [isLoading, setLoading] = useState(true); const [record, setRecord] = useState(null); @@ -137,7 +135,7 @@ export const MetadataDetailsProvider = ({ repoID, repoInfo, path, dirent, dirent }, [repoID, record, modifyLocalFileTags]); 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]); const modifyHiddenColumns = useCallback((hiddenColumns) => { diff --git a/frontend/src/metadata/views/kanban/boards/board/card/index.js b/frontend/src/metadata/views/kanban/boards/board/card/index.js index 068ab8293a..0367e32988 100644 --- a/frontend/src/metadata/views/kanban/boards/board/card/index.js +++ b/frontend/src/metadata/views/kanban/boards/board/card/index.js @@ -14,6 +14,7 @@ const Card = ({ record, titleColumn, displayColumns, + tagsData, onOpenFile, onSelectCard, onContextMenu, @@ -45,7 +46,7 @@ const Card = ({ > {titleColumn && (
    - +
    )}
    @@ -65,7 +66,7 @@ const Card = ({ return (
    {displayColumnName && (
    {column.name}
    )} - +
    ); })} diff --git a/frontend/src/metadata/views/kanban/boards/board/formatter.js b/frontend/src/metadata/views/kanban/boards/board/formatter.js index b3d6a52958..fbc7308bab 100644 --- a/frontend/src/metadata/views/kanban/boards/board/formatter.js +++ b/frontend/src/metadata/views/kanban/boards/board/formatter.js @@ -14,7 +14,7 @@ const SPECIAL_FILE_ICON = [ 'word.png', ]; -const Formatter = ({ value, column, record, ...params }) => { +const Formatter = ({ value, column, record, tagsData, ...params }) => { let className = ''; if (column.type === CellType.FILE_NAME && value) { @@ -24,7 +24,7 @@ const Formatter = ({ value, column, record, ...params }) => { } } - return (); + return (); }; Formatter.propTypes = { diff --git a/frontend/src/metadata/views/kanban/boards/board/header/index.js b/frontend/src/metadata/views/kanban/boards/board/header/index.js index 129b58cc81..f50802f045 100644 --- a/frontend/src/metadata/views/kanban/boards/board/header/index.js +++ b/frontend/src/metadata/views/kanban/boards/board/header/index.js @@ -8,7 +8,7 @@ import { CellType } from '../../../../../constants'; 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 const [active, setActive] = useState(false); @@ -48,7 +48,7 @@ const Header = ({ readonly, haveFreezed, value, groupByColumn, cardsQuantity, on
    {value ? ( - + ) : ( {gettext('Uncategorized')} )} diff --git a/frontend/src/metadata/views/kanban/boards/board/index.js b/frontend/src/metadata/views/kanban/boards/board/index.js index c398e6dedd..5196996b2a 100644 --- a/frontend/src/metadata/views/kanban/boards/board/index.js +++ b/frontend/src/metadata/views/kanban/boards/board/index.js @@ -8,6 +8,7 @@ import { useMetadataView } from '../../../../hooks/metadata-view'; import { getRowById } from '../../../../../components/sf-table/utils/table'; import { getRecordIdFromRecord } from '../../../../utils/cell'; import { gettext } from '@/utils/constants'; +import { useTags } from '../../../../../tag/hooks'; import './index.css'; @@ -37,6 +38,7 @@ const Board = ({ const cardsQuantity = useMemo(() => board.children.length, [board.children]); const { metadata } = useMetadataView(); + const { tagsData } = useTags(); const onDragStart = useCallback(({ payload }) => { updateDragging(true); @@ -68,6 +70,7 @@ const Board = ({ groupByColumn={groupByColumn} haveFreezed={haveFreezed} cardsQuantity={cardsQuantity} + tagsData={tagsData} onDelete={() => deleteOption(board.key)} onFreezed={onFreezed} onUnFreezed={onUnFreezed} @@ -114,6 +117,7 @@ const Board = ({ record={record} titleColumn={titleColumn} displayColumns={displayColumns} + tagsData={tagsData} onOpenFile={onOpenFile} onSelectCard={onSelectCard} onContextMenu={(e) => onContextMenu(e, recordId)} diff --git a/frontend/src/metadata/views/table/table-main/records/record/cell/formatter.js b/frontend/src/metadata/views/table/table-main/records/record/cell/formatter.js index 0f32d67450..9a36d86b2c 100644 --- a/frontend/src/metadata/views/table/table-main/records/record/cell/formatter.js +++ b/frontend/src/metadata/views/table/table-main/records/record/cell/formatter.js @@ -6,7 +6,7 @@ import RateEditor from '../../../../../../components/cell-editors/rate-editor'; import { canEditCell } from '../../../../../../utils/column'; 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 cellEditAble = canEditCell(field, record, true); if (type === CellType.CHECKBOX && cellEditAble) { @@ -16,7 +16,7 @@ const Formatter = ({ isCellSelected, field, value, onChange, record, ...params } return (); } - return (); + return (); }; Formatter.propTypes = { diff --git a/frontend/src/metadata/views/table/table-main/records/record/cell/index.js b/frontend/src/metadata/views/table/table-main/records/record/cell/index.js index c311ebbb3d..d48ebc0709 100644 --- a/frontend/src/metadata/views/table/table-main/records/record/cell/index.js +++ b/frontend/src/metadata/views/table/table-main/records/record/cell/index.js @@ -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 { checkIsDir } from '../../../../../../utils/row'; import { openFile } from '../../../../../../utils/file'; +import { useTags } from '../../../../../../../tag/hooks'; import './index.css'; @@ -27,6 +28,7 @@ const Cell = React.memo(({ frozen, height, }) => { + const { tagsData } = useTags(); const canEditable = useMemo(() => { const { type } = column; if (!window.sfMetadataContext.canModifyColumn(column)) return false; @@ -171,7 +173,7 @@ const Cell = React.memo(({ return (
    - + {isCellSelected && ()}
    ); diff --git a/frontend/src/tag/utils/column.js b/frontend/src/tag/utils/column.js index c68866adfa..155f47d7c7 100644 --- a/frontend/src/tag/utils/column.js +++ b/frontend/src/tag/utils/column.js @@ -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) => { const { key } = column; let width = keyColumnWidth[column.key];