Get preview images on the fly and keep them in cache (#5478)
Improved image preview loading for **Tasks**, **Jobs** and **Projects**
views
Backend behaviour change: creating image previews by request and storing
them in the cache
Added corresponding endpoints:
tasks/{id}/preview
projects/{id}/preview
jobs/{id}/preview
Demonstration(added random 0-1s delay for demo purposes):
https://user-images.githubusercontent.com/41117609/208106321-951b8647-6e6b-452e-910c-31c4d0b8682d.mp4
https://user-images.githubusercontent.com/41117609/208106339-2d3a5a7b-d422-4b27-9e76-08729022e1ca.mp4
main
parent
37b685f47a
commit
1ecc607286
@ -1,51 +0,0 @@
|
||||
// Copyright (C) 2021-2022 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { PictureOutlined } from '@ant-design/icons';
|
||||
import Spin from 'antd/lib/spin';
|
||||
import { getCloudStoragePreviewAsync } from 'actions/cloud-storage-actions';
|
||||
import { CombinedState, CloudStorage } from 'reducers';
|
||||
|
||||
interface Props {
|
||||
cloudStorage: CloudStorage;
|
||||
}
|
||||
|
||||
export default function Preview({ cloudStorage }: Props): JSX.Element {
|
||||
const dispatch = useDispatch();
|
||||
const preview = useSelector((state: CombinedState) => state.cloudStorages.previews[cloudStorage.id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (preview === undefined) {
|
||||
dispatch(getCloudStoragePreviewAsync(cloudStorage));
|
||||
}
|
||||
}, [preview]);
|
||||
|
||||
if (!preview || (preview && preview.fetching)) {
|
||||
return (
|
||||
<div className='cvat-cloud-storage-item-loading-preview' aria-hidden>
|
||||
<Spin size='default' />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (preview.initialized && !preview.preview) {
|
||||
return (
|
||||
<div className='cvat-cloud-storage-item-empty-preview' aria-hidden>
|
||||
<PictureOutlined />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<img
|
||||
className='cvat-cloud-storage-item-preview'
|
||||
src={preview.preview}
|
||||
alt='Preview image'
|
||||
aria-hidden
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,99 @@
|
||||
// Copyright (C) 2022 CVAT.ai Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { PictureOutlined } from '@ant-design/icons';
|
||||
import Spin from 'antd/lib/spin';
|
||||
import { getJobPreviewAsync } from 'actions/jobs-actions';
|
||||
import { getTaskPreviewAsync } from 'actions/tasks-actions';
|
||||
import { getProjectsPreviewAsync } from 'actions/projects-actions';
|
||||
import { getCloudStoragePreviewAsync } from 'actions/cloud-storage-actions';
|
||||
import {
|
||||
CombinedState, Job, Task, Project, CloudStorage,
|
||||
} from 'reducers';
|
||||
|
||||
interface Props {
|
||||
job?: Job | undefined;
|
||||
task?: Task | undefined;
|
||||
project?: Project | undefined;
|
||||
cloudStorage?: CloudStorage | undefined;
|
||||
onClick?: (event: React.MouseEvent) => void;
|
||||
loadingClassName?: string;
|
||||
emptyPreviewClassName?: string;
|
||||
previewWrapperClassName?: string;
|
||||
previewClassName?: string;
|
||||
}
|
||||
|
||||
export default function Preview(props: Props): JSX.Element {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const {
|
||||
job,
|
||||
task,
|
||||
project,
|
||||
cloudStorage,
|
||||
onClick,
|
||||
loadingClassName,
|
||||
emptyPreviewClassName,
|
||||
previewWrapperClassName,
|
||||
previewClassName,
|
||||
} = props;
|
||||
|
||||
const preview = useSelector((state: CombinedState) => {
|
||||
if (job !== undefined) {
|
||||
return state.jobs.previews[job.id];
|
||||
} if (project !== undefined) {
|
||||
return state.projects.previews[project.id];
|
||||
} if (task !== undefined) {
|
||||
return state.tasks.previews[task.id];
|
||||
} if (cloudStorage !== undefined) {
|
||||
return state.cloudStorages.previews[cloudStorage.id];
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (preview === undefined) {
|
||||
if (job !== undefined) {
|
||||
dispatch(getJobPreviewAsync(job));
|
||||
} else if (project !== undefined) {
|
||||
dispatch(getProjectsPreviewAsync(project));
|
||||
} else if (task !== undefined) {
|
||||
dispatch(getTaskPreviewAsync(task));
|
||||
} else if (cloudStorage !== undefined) {
|
||||
dispatch(getCloudStoragePreviewAsync(cloudStorage));
|
||||
}
|
||||
}
|
||||
}, [preview]);
|
||||
|
||||
if (!preview || (preview && preview.fetching)) {
|
||||
return (
|
||||
<div className={loadingClassName || ''} aria-hidden>
|
||||
<Spin size='default' />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (preview.initialized && !preview.preview) {
|
||||
return (
|
||||
<div className={emptyPreviewClassName || ''} aria-hidden>
|
||||
<PictureOutlined />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={previewWrapperClassName || ''} aria-hidden>
|
||||
<img
|
||||
className={previewClassName || ''}
|
||||
src={preview.preview}
|
||||
onClick={onClick}
|
||||
alt='Preview image'
|
||||
aria-hidden
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from django.db import migrations
|
||||
from django.conf import settings
|
||||
from cvat.apps.engine.log import get_migration_logger
|
||||
|
||||
def delete_previews(apps, schema_editor):
|
||||
migration_name = os.path.splitext(os.path.basename(__file__))[0]
|
||||
with get_migration_logger(migration_name) as log:
|
||||
def delete_object_previews(db_objects, root_path):
|
||||
for db_obj in db_objects:
|
||||
preview_path = os.path.join(root_path, str(db_obj.id), 'preview.jpeg')
|
||||
try:
|
||||
os.remove(preview_path)
|
||||
except Exception as e:
|
||||
log.error(f'Cannot delete path {preview_path}')
|
||||
log.error(str(e))
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
|
||||
log.info('\nDeleting Data previews...')
|
||||
Data = apps.get_model('engine', 'Data')
|
||||
delete_object_previews(Data.objects.all(), settings.MEDIA_DATA_ROOT)
|
||||
|
||||
log.info('\nDeleting Job previews...')
|
||||
Job = apps.get_model('engine', 'Job')
|
||||
delete_object_previews(Job.objects.all(), settings.JOBS_ROOT)
|
||||
|
||||
log.info('\nDeleting CloudStorage previews...')
|
||||
CloudStorage = apps.get_model('engine', 'CloudStorage')
|
||||
delete_object_previews(CloudStorage.objects.all(), settings.CLOUD_STORAGE_ROOT)
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('engine', '0061_auto_20221130_0844'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
code=delete_previews
|
||||
),
|
||||
]
|
||||
Loading…
Reference in New Issue