Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 0 additions & 47 deletions packages/client/src/components/DatasetControl.component.tsx

This file was deleted.

58 changes: 58 additions & 0 deletions packages/client/src/components/DatasetTable.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { DataGrid, GridColDef } from '@mui/x-data-grid';
import { useState, useEffect } from 'react';
import { Dataset, Entry } from '../graphql/graphql';
import { useEntryForDatasetQuery } from '../graphql/entry';
import { EntryView } from './EntryView.component';

export interface DatasetTableProps {
dataset: Dataset;
additionalColumns?: GridColDef[];
}

export const DatasetTable: React.FC<DatasetTableProps> = (props) => {
const [entries, setEntries] = useState<Entry[]>([]);
const columns = [...defaultColumns, ...(props.additionalColumns ?? [])];

const entryForDatasetResult = useEntryForDatasetQuery({ variables: { dataset: props.dataset._id } });

// TODO: Add in logic to re-fetch data when the presigned URL expires
useEffect(() => {
if (entryForDatasetResult.data) {
setEntries(entryForDatasetResult.data.entryForDataset);
}
}, [entryForDatasetResult.data]);

return (
<DataGrid
getRowHeight={() => 'auto'}
rows={entries}
columns={columns}
initialState={{
pagination: {
paginationModel: {
pageSize: 5
}
}
}}
getRowId={(row) => row._id}
pageSizeOptions={[5, 10, 15]}
checkboxSelection
disableRowSelectionOnClick
/>
);
};

const defaultColumns: GridColDef[] = [
{
field: 'view',
headerName: 'View',
width: 300,
renderCell: (params) => <EntryView entry={params.row as Entry} width={300} />
},
{
field: 'entryID',
headerName: 'Entry ID',
width: 150,
editable: false
}
];
29 changes: 29 additions & 0 deletions packages/client/src/components/DatasetsView.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Accordion, AccordionSummary, Typography, Stack, AccordionDetails } from '@mui/material';
import { Dataset } from '../graphql/graphql';
import { DatasetTable } from './DatasetTable.component';
import { ExpandMore } from '@mui/icons-material';

export interface DatasetsViewProps {
datasets: Dataset[];
}

// TODO: Implement lazy loading on accordion open to prevent loading all datasets at once
export const DatasetsView: React.FC<DatasetsViewProps> = ({ datasets }) => {
return (
<>
{datasets.map((dataset: Dataset) => (
<Accordion key={dataset._id} disableGutters>
<AccordionSummary expandIcon={<ExpandMore />}>
<Stack direction="row" spacing={6}>
<Typography>{dataset.name}</Typography>
<Typography>{dataset.description}</Typography>
</Stack>
</AccordionSummary>
<AccordionDetails>
<DatasetTable dataset={dataset} />
</AccordionDetails>
</Accordion>
))}
</>
);
};
96 changes: 96 additions & 0 deletions packages/client/src/components/EntryView.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { Entry } from '../graphql/graphql';
import { useEffect, useRef } from 'react';

export interface EntryViewProps {
entry: Entry;
width: number;
}

export const EntryView: React.FC<EntryViewProps> = (props) => {
return getEntryView(props.entry);
};

const getEntryView = (entry: Entry) => {
if (entry.contentType.startsWith('video/')) {
return <VideoEntryView entry={entry} width={150} />;
}
if (entry.contentType.startsWith('image/')) {
return <ImageEntryView entry={entry} width={150} />;
}
console.error('Unknown entry type');
return <p>Placeholder</p>;
};

const VideoEntryView: React.FC<EntryViewProps> = (props) => {
const videoRef = useRef<HTMLVideoElement>(null);

/** Start the video at the begining */
const handleStart: React.MouseEventHandler = () => {
if (!videoRef.current) {
return;
}
videoRef.current.currentTime = 0;
videoRef.current?.play();
};

/** Stop the video */
const handleStop: React.MouseEventHandler = () => {
if (!videoRef.current) {
return;
}
videoRef.current.pause();
setMiddleFrame();
};

/** Set the video to the middle frame */
const setMiddleFrame = async () => {
if (!videoRef.current) {
return;
}
const duration = await getDuration();
videoRef.current.currentTime = duration / 2;
};

/** Get the duration, there is a known issue on Chrome with some audio/video durations */
const getDuration = async () => {
if (!videoRef.current) {
return 0;
}

const video = videoRef.current!;

// If the duration is infinity, this is part of a Chrome bug that causes
// some durations to not load for audio and video. The StackOverflow
// link below discusses the issues and possible solutions
// Then, wait for the update event to be triggered
await new Promise<void>((resolve, _reject) => {
video.ontimeupdate = () => {
// Remove the callback
video.ontimeupdate = () => {};
// Reset the time
video.currentTime = 0;
resolve();
};

video.currentTime = 1e101;
});

// Now try to get the duration again
return video.duration;
};

// Set the video to the middle frame when the video is loaded
useEffect(() => {
setMiddleFrame();
}, [videoRef.current]);

return (
<video width={props.width} onMouseEnter={handleStart} onMouseLeave={handleStop} ref={videoRef}>
<source src={props.entry.signedUrl} />
</video>
);
};

const ImageEntryView: React.FC<EntryViewProps> = (props) => {
return <img src={props.entry.signedUrl} width={props.width} />;
};
122 changes: 17 additions & 105 deletions packages/client/src/components/TagTraining.component.tsx
Original file line number Diff line number Diff line change
@@ -1,111 +1,23 @@
import { Box, Accordion, AccordionSummary, Typography, AccordionDetails, Container } from '@mui/material';
import { DatasetControl } from './DatasetControl.component';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { GridColDef } from '@mui/x-data-grid';
import { DatasetsView } from './DatasetsView.component';
import { useState, useEffect } from 'react';
import { useGetDatasetsQuery } from '../graphql/dataset/dataset';
import { Dataset } from '../graphql/graphql';

interface Control {
name: string;
description: string;
}

const rows = [
{
id: 1,
view: '12',
entry: '41',
responder: '0',
partOf: true,
available: true
},
{
id: 2,
view: '5',
entry: '9',
responder: '0',
partOf: true,
available: true
},
{
id: 3,
view: '11',
entry: '9',
responder: '2',
partOf: true,
available: true
},
{
id: 4,
view: '0',
entry: '10',
responder: '5',
partOf: true,
available: true
}
];
export const TagTrainingComponent = () => {
const [datasets, setDatasets] = useState<Dataset[]>([]);
const getDatasetsResults = useGetDatasetsQuery();

const columns: GridColDef[] = [
{ field: 'id', headerName: 'ID', flex: 0.2 },
{
field: 'view',
headerName: 'View',
flex: 0.3,
editable: true
},
{
field: 'entry',
headerName: 'Entry ID',
flex: 0.3,
editable: true
},
{
field: 'responder',
headerName: 'Responder ID',
flex: 0.5,
editable: true
},
{
field: 'partOf',
headerName: 'Is Part of Training Set',
flex: 0.75,
editable: true
},
{
field: 'available',
headerName: 'Available for Tagging',
flex: 0.75,
editable: true
}
];
// TODO: In the future, the datasets retrieved should only be datasets
// accessible by the current project
useEffect(() => {
if (getDatasetsResults.data) {
setDatasets(getDatasetsResults.data.getDatasets);
}
}, [getDatasetsResults.data]);

export const TagTrainingComponent = () => {
const controls = [
{ name: 'First', description: 'The description of the object' },
{ name: 'Second', description: 'Description of the second object' }
];
return (
<Box sx={{ width: '100%' }}>
{controls.map((item: Control) => (
<Accordion key={item.name} disableGutters>
<AccordionSummary expandIcon={<ExpandMoreIcon />} aria-controls="panel1a-content" id="panel1a-header">
<Typography sx={{ fontWeight: 'normal', position: 'absolute', top: '14px', left: '3%' }}>
{item.name}
</Typography>
<Typography sx={{ fontWeight: 'normal', position: 'absolute', top: '14px', left: '20%' }}>
{item.description}
</Typography>
</AccordionSummary>
<AccordionDetails>
<Container
sx={{ width: '100%', display: 'flex', flexDirection: 'column', justifyContext: 'space-between' }}
>
<Box sx={{ position: '-webkit-sticky' }}>
<DatasetControl tableRows={rows} columns={columns} />
</Box>
</Container>
</AccordionDetails>
</Accordion>
))}
<br />
</Box>
<>
<DatasetsView datasets={datasets} />
</>
);
};
14 changes: 14 additions & 0 deletions packages/client/src/graphql/entry.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
query entryForDataset($dataset: ID!) {
entryForDataset(dataset: $dataset) {
_id,
organization,
entryID,
contentType,
dataset,
creator,
dateCreated,
meta,
signedUrl,
signedUrlExpiration
}
}
Loading