Add Meta Data section to FileInfo component and update file thumbnail fetching logic
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good

- Introduced a new Meta Data section in the FileInfo component, allowing users to view file metadata.
- Added a fetchFileThumbnail function in ApiServerContext to retrieve file thumbnails, enhancing the user experience with profile images.
- Updated the UserProfileImage component to utilize the new thumbnail fetching logic, improving image loading efficiency.
- Added a hasThumbnails property to the File model to indicate the presence of thumbnail data.
This commit is contained in:
Tom Butcher 2026-07-26 03:07:31 +01:00
parent ff61cd4f06
commit 9aa2b0f2f8
4 changed files with 66 additions and 6 deletions

View File

@ -25,6 +25,9 @@ import FilePreview from '../../common/FilePreview.jsx'
import MissingPlaceholder from '../../common/MissingPlaceholder.jsx' import MissingPlaceholder from '../../common/MissingPlaceholder.jsx'
import { ApiServerContext } from '../../context/ApiServerContext.jsx' import { ApiServerContext } from '../../context/ApiServerContext.jsx'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
import JsonObjectIcon from '../../../Icons/JsonObjectIcon.jsx'
import ObjectProperty from '../../common/ObjectProperty.jsx'
import { getModelProperty } from '../../../../database/ObjectModels.js'
const log = loglevel.getLogger('FileInfo') const log = loglevel.getLogger('FileInfo')
log.setLevel(config.logLevel) log.setLevel(config.logLevel)
@ -95,6 +98,7 @@ const FileInfo = () => {
items={[ items={[
{ key: 'info', label: 'File Information' }, { key: 'info', label: 'File Information' },
{ key: 'preview', label: 'Preview' }, { key: 'preview', label: 'Preview' },
{ key: 'metaData', label: 'Meta Data' },
{ key: 'notes', label: 'Notes' }, { key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' } { key: 'auditLogs', label: 'Audit Logs' }
]} ]}
@ -131,7 +135,9 @@ const FileInfo = () => {
}} }}
editLoading={objectFormState.editLoading} editLoading={objectFormState.editLoading}
formValid={objectFormState.formValid} formValid={objectFormState.formValid}
disabled={objectFormState.beingEditedByOther || objectFormState.loading} disabled={
objectFormState.beingEditedByOther || objectFormState.loading
}
loading={objectFormState.editLoading} loading={objectFormState.editLoading}
/> />
</Space> </Space>
@ -164,6 +170,8 @@ const FileInfo = () => {
loading={loading} loading={loading}
isEditing={isEditing} isEditing={isEditing}
type='file' type='file'
labelWidth={170}
visibleProperties={{ metaData: false }}
objectData={objectData} objectData={objectData}
/> />
)} )}
@ -188,6 +196,24 @@ const FileInfo = () => {
<MissingPlaceholder message={'No file.'} /> <MissingPlaceholder message={'No file.'} />
)} )}
</InfoCollapse> </InfoCollapse>
<InfoCollapse
title='Meta Data'
icon={<JsonObjectIcon />}
active={collapseState.metaData}
onToggle={(expanded) => updateCollapseState('metaData', expanded)}
collapseKey='metaData'
>
{objectFormState?.objectData?._id ? (
<Card>
<ObjectProperty
{...getModelProperty('file', 'metaData')}
objectData={objectFormState?.objectData}
/>
</Card>
) : (
<MissingPlaceholder message={'No file meta data.'} />
)}
</InfoCollapse>
<InfoCollapse <InfoCollapse
title='Notes' title='Notes'
icon={<NoteIcon />} icon={<NoteIcon />}

View File

@ -39,9 +39,9 @@ const UserProfileImage = memo(function UserProfileImage({
profileImageId, profileImageId,
displayName displayName
}) { }) {
const { fetchFileContent } = useContext(ApiServerContext) const { fetchFileThumbnail } = useContext(ApiServerContext)
const fetchFileContentRef = useRef(fetchFileContent) const fetchFileThumbnailRef = useRef(fetchFileThumbnail)
fetchFileContentRef.current = fetchFileContent fetchFileThumbnailRef.current = fetchFileThumbnail
const [profileImageUrl, setProfileImageUrl] = useState(null) const [profileImageUrl, setProfileImageUrl] = useState(null)
const profileImageUrlRef = useRef(null) const profileImageUrlRef = useRef(null)
@ -66,11 +66,11 @@ const UserProfileImage = memo(function UserProfileImage({
} }
let cancelled = false let cancelled = false
const file = { _id: profileImageId, name: '', extension: '' } const file = { _id: profileImageId }
const loadProfileImage = async () => { const loadProfileImage = async () => {
try { try {
const fileURL = await fetchFileContentRef.current(file, false) const fileURL = await fetchFileThumbnailRef.current(file, 64)
if (!cancelled) { if (!cancelled) {
if (profileImageUrlRef.current) { if (profileImageUrlRef.current) {
URL.revokeObjectURL(profileImageUrlRef.current) URL.revokeObjectURL(profileImageUrlRef.current)

View File

@ -1438,6 +1438,31 @@ const ApiServerProvider = ({ children }) => {
} }
} }
const fetchFileThumbnail = async (file, size) => {
try {
const response = await axios.get(
`${config.backendUrl}/files/${file._id}/thumbnail`,
{
params: { size },
headers: {
Accept: 'image/jpeg',
Authorization: `Bearer ${token}`
},
responseType: 'blob'
}
)
const blob = new Blob([response.data], {
type: response.headers['content-type'] || 'image/jpeg'
})
return window.URL.createObjectURL(blob)
} catch (err) {
console.error(err)
showError(err, () => {
fetchFileThumbnail(file, size)
})
}
}
// Fetch notes for a specific parent // Fetch notes for a specific parent
const fetchNotes = async (parentId) => { const fetchNotes = async (parentId) => {
logger.debug('Fetching notes for parent:', parentId) logger.debug('Fetching notes for parent:', parentId)
@ -2136,6 +2161,7 @@ const ApiServerProvider = ({ children }) => {
fetchLoading, fetchLoading,
showError, showError,
fetchFileContent, fetchFileContent,
fetchFileThumbnail,
exportToExcel, exportToExcel,
exportToCsv, exportToCsv,
fetchTemplatePreview, fetchTemplatePreview,

View File

@ -167,6 +167,14 @@ export const File = {
type: 'data', type: 'data',
readOnly: true, readOnly: true,
required: false required: false
},
{
name: 'hasThumbnails',
label: 'Has Thumbnails',
type: 'bool',
readOnly: true,
required: false,
columnWidth: 100
} }
] ]
} }