diff --git a/ui/src/App.css b/ui/src/App.css index d31b3528e24bcc40edc6d5ffdc69b7058eae1879..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 --- a/ui/src/App.css +++ b/ui/src/App.css @@ -1,4 +0,0 @@ -img { - max-width: 200px; - max-height: 200px; -} diff --git a/ui/src/api/model/Image.ts b/ui/src/api/model/Image.ts index 16683c3fc246c123665c6bf403aeb49f5d2ab33b..710716b15e7110af39cc998c7df7edb07a3fe825 100644 --- a/ui/src/api/model/Image.ts +++ b/ui/src/api/model/Image.ts @@ -7,9 +7,17 @@ export interface Image { updated_at: string, original_name: string, mime_type: string, - state: string, + state: ImageState, metadata: { [key: string]: string }, url: string, } + +export enum ImageState { + CREATED = "created", + QUEUED = "queued", + IN_PROGRESS = "in_progress", + DONE = "done", + ERROR = "error" +} diff --git a/ui/src/components/ImageList.tsx b/ui/src/components/ImageList.tsx index f7b025d801f7c6052268586c1a3eaded0ffdd4a1..f46895f91444e2e45f2a159d2acf7600cd8e006d 100644 --- a/ui/src/components/ImageList.tsx +++ b/ui/src/components/ImageList.tsx @@ -9,14 +9,12 @@ export default function ImageList() { <div> <p>{status}</p> <p>{error as string}</p> - <ul> - {data?.map(image => ( - <ImageView - key={image.id} - image={image} - /> - ))} - </ul> + {data?.map(image => ( + <ImageView + key={image.id} + image={image} + /> + ))} </div> ); } diff --git a/ui/src/components/ImageMetadataView.tsx b/ui/src/components/ImageMetadataView.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b7a2b9a0267a64191bb2e93ba24c7f072ac2092f --- /dev/null +++ b/ui/src/components/ImageMetadataView.tsx @@ -0,0 +1,196 @@ +import React, {Fragment} from "react"; +import {ImageMetadata, ratioToTime} from "../metadata/ImageMetadata"; +import {ratioToFloat} from "../metadata/Ratio"; +import {ExposureMode} from "../metadata/ExposureMode"; +import {ExposureProgram} from "../metadata/ExposureProgram"; +import {LightSource} from "../metadata/LightSource"; +import {MeteringMode} from "../metadata/MeteringMode"; +import {WhiteBalance} from "../metadata/WhiteBalance"; +import {SceneMode} from "../metadata/SceneMode"; +import {ContrastProcessing} from "../metadata/ContrastProcessing"; +import {SharpnessProcessing} from "../metadata/SharpnessProcessing"; +import {SubjectDistanceRange} from "../metadata/SubjectDistanceRange"; +import {BrightnessMedium, Camera, Copyright, Event, Exposure, PhotoCamera, ZoomIn} from "@material-ui/icons"; +import {AngleAcute, ArrowExpandHorizontal, Blur, CameraTimer, Flash, WhiteBalanceIncandescent} from "mdi-material-ui"; +import {FlashMode} from "../metadata/FlashMode"; +import {ListItem, ListItemIcon, ListItemText} from "@material-ui/core"; + +export interface ImageMetadataViewProps { + metadata: ImageMetadata +} + +export default function ImageMetadataView({metadata}: ImageMetadataViewProps) { + return ( + <Fragment> + {metadata.make !== undefined && ( + <ListItem dense> + <ListItemIcon><PhotoCamera/></ListItemIcon> + <ListItemText primary="Make" secondary={metadata.make}/> + </ListItem> + )} + {metadata.model !== undefined && ( + <ListItem dense> + <ListItemText inset primary="Model" secondary={metadata.model}/> + </ListItem> + )} + {metadata.software !== undefined && ( + <ListItem dense> + <ListItemText inset primary="Software" secondary={metadata.software}/> + </ListItem> + )} + {metadata.copyright !== undefined && ( + <ListItem dense> + <ListItemIcon><Copyright/></ListItemIcon> + <ListItemText primary="Copyright" secondary={metadata.copyright}/> + </ListItem> + )} + {metadata.dateTimeCreated !== undefined && ( + <ListItem dense> + <ListItemIcon><Event/></ListItemIcon> + <ListItemText primary="Created At" secondary={metadata.dateTimeCreated.toISOString()}/> + </ListItem> + )} + {metadata.dateTimeDigitized !== undefined && ( + <ListItem dense> + <ListItemText inset primary="Digitized At" + secondary={metadata.dateTimeDigitized.toISOString()}/> + </ListItem> + )} + {metadata.dateTimeOriginal !== undefined && ( + <ListItem dense> + <ListItemText inset primary="Shot At" secondary={metadata.dateTimeOriginal.toISOString()}/> + </ListItem> + )} + {metadata.digitalZoomRatio !== undefined && ( + <ListItem dense> + <ListItemIcon><ZoomIn/></ListItemIcon> + <ListItemText primary="Zoom" secondary={`${ratioToFloat(metadata.digitalZoomRatio)}x`}/> + </ListItem> + )} + {metadata.focalLength !== undefined && ( + <ListItem dense> + <ListItemIcon><AngleAcute/></ListItemIcon> + <ListItemText primary="Focal Length" secondary={`${ratioToFloat(metadata.focalLength)}mm`}/> + </ListItem> + )} + {metadata.focalLength35mm !== undefined && ( + <ListItem dense> + <ListItemText inset primary="35mm equivalent" + secondary={`${ratioToFloat(metadata.focalLength35mm)}mm`}/> + </ListItem> + )} + {metadata.shutterSpeed !== undefined && ( + <ListItem dense> + <ListItemIcon><CameraTimer/></ListItemIcon> + <ListItemText primary="Shutter Speed" secondary={ratioToTime(metadata.shutterSpeed)}/> + </ListItem> + )} + {metadata.aperture !== undefined && ( + <ListItem dense> + <ListItemIcon><Camera/></ListItemIcon> + <ListItemText primary="Aperture" secondary={ratioToFloat(metadata.aperture)}/> + </ListItem> + )} + {metadata.isoSpeedRating !== undefined && ( + <ListItem dense> + <ListItemIcon>ISO</ListItemIcon> + <ListItemText primary="ISO" secondary={metadata.isoSpeedRating}/> + </ListItem> + )} + {metadata.exposure !== undefined && ( + <ListItem dense> + <ListItemIcon><Exposure/></ListItemIcon> + <ListItemText primary="Exposure" secondary={ratioToFloat(metadata.exposure)}/> + </ListItem> + )} + {metadata.exposureMode !== undefined && ( + <ListItem dense> + <ListItemText inset primary="Mode" secondary={ExposureMode[metadata.exposureMode]}/> + </ListItem> + )} + {metadata.exposureProgram !== undefined && ( + <ListItem dense> + <ListItemText inset primary="Program" secondary={ExposureProgram[metadata.exposureProgram]}/> + </ListItem> + )} + {metadata.meteringMode !== undefined && ( + <ListItem dense> + <ListItemText inset primary="Metering mode" secondary={MeteringMode[metadata.meteringMode]}/> + </ListItem> + )} + {metadata.flash !== undefined && ( + <Fragment> + <ListItem dense> + <ListItemIcon><Flash/></ListItemIcon> + <ListItemText primary="Flash" + secondary={metadata.flash.available ? "Available" : "Unavailable"}/> + </ListItem> + <ListItem dense> + <ListItemText inset primary="Fired" secondary={metadata.flash.fired ? "Yes" : "No"}/> + </ListItem> + <ListItem dense> + <ListItemText inset primary="Red Eye Reduction" + secondary={metadata.flash.redEyeReduction ? "Yes" : "No"}/> + </ListItem> + {metadata.flash.mode !== undefined && ( + <ListItem dense> + <ListItemText inset primary="Mode" secondary={FlashMode[metadata.flash.mode]}/> + </ListItem> + )} + {metadata.flash.strength !== undefined && ( + <ListItem dense> + <ListItemText inset primary="Strength" secondary={`${metadata.flash.strength} BCPS`}/> + </ListItem> + )} + <ListItem dense> + <ListItemText inset primary="Strobe Detection" + secondary={!metadata.flash.strobeDetection.available ? "Unvailable" : + metadata.flash.strobeDetection.detected ? "Strobe detected" : + "No strobe detected"} + /> + </ListItem> + </Fragment> + )} + {metadata.lightSource !== undefined && ( + <ListItem dense> + <ListItemIcon><WhiteBalanceIncandescent/></ListItemIcon> + <ListItemText primary="Light Source" secondary={LightSource[metadata.lightSource]}/> + </ListItem> + )} + {metadata.whiteBalance !== undefined && ( + <ListItem dense> + <ListItemIcon><WhiteBalanceIncandescent/></ListItemIcon> + <ListItemText primary="White balance" secondary={WhiteBalance[metadata.whiteBalance]}/> + </ListItem> + )} + {metadata.subjectDistance !== undefined && ( + <ListItem dense> + <ListItemIcon><ArrowExpandHorizontal/></ListItemIcon> + <ListItemText primary="Subject Distance" secondary={metadata.subjectDistance}/> + </ListItem> + )} + {metadata.subjectDistanceRange !== undefined && ( + <ListItem dense> + <ListItemIcon><ArrowExpandHorizontal/></ListItemIcon> + <ListItemText primary="Subject Distance Range" + secondary={SubjectDistanceRange[metadata.subjectDistanceRange]}/> + </ListItem> + )} + {metadata.sceneMode !== undefined && ( + <p><b>Scene Mode</b>: {SceneMode[metadata.sceneMode]}</p> + )} + {metadata.contrast !== undefined && ( + <ListItem dense> + <ListItemIcon><BrightnessMedium/></ListItemIcon> + <ListItemText primary="Contrast Processing" secondary={ContrastProcessing[metadata.contrast]}/> + </ListItem> + )} + {metadata.sharpness !== undefined && ( + <ListItem dense> + <ListItemIcon><Blur/></ListItemIcon> + <ListItemText primary="Sharpness Processing" secondary={SharpnessProcessing[metadata.sharpness]}/> + </ListItem> + )} + </Fragment> + ); +} diff --git a/ui/src/components/ImageView.tsx b/ui/src/components/ImageView.tsx index cf98f573538874b3c82d40c494dfa263ee117447..a8bc7400c9c4ba38fa2087e5f910f0ca99d0ae37 100644 --- a/ui/src/components/ImageView.tsx +++ b/ui/src/components/ImageView.tsx @@ -1,21 +1,12 @@ import {Image} from "../api/model/Image"; -import React, {Fragment, useMemo, useState} from "react"; +import React, {useMemo, useState} from "react"; import {useUpdateImage} from "../api/useUpdateImage"; import {useDeleteImage} from "../api/useDeleteImage"; -import {parseMetadata, ratioToTime} from "../metadata/ImageMetadata"; -import {ratioToFloat} from "../metadata/Ratio"; -import {ExposureMode} from "../metadata/ExposureMode"; -import {ExposureProgram} from "../metadata/ExposureProgram"; -import {LightSource} from "../metadata/LightSource"; -import {MeteringMode} from "../metadata/MeteringMode"; -import {WhiteBalance} from "../metadata/WhiteBalance"; -import {SceneMode} from "../metadata/SceneMode"; -import {ContrastProcessing} from "../metadata/ContrastProcessing"; -import {SharpnessProcessing} from "../metadata/SharpnessProcessing"; -import {SubjectDistanceRange} from "../metadata/SubjectDistanceRange"; -import {BrightnessMedium, Camera, Copyright, Event, Exposure, PhotoCamera, ZoomIn} from "@material-ui/icons"; -import {AngleAcute, ArrowExpandHorizontal, Blur, CameraTimer, Flash, WhiteBalanceIncandescent} from "mdi-material-ui"; -import {FlashMode} from "../metadata/FlashMode"; +import {parseMetadata} from "../metadata/ImageMetadata"; +import ImageMetadataView from "./ImageMetadataView"; +import {Button, List, ListItem, ListItemIcon, ListItemText, TextField} from "@material-ui/core"; +import {Event, Info} from "@material-ui/icons"; +import {File, Tag} from "mdi-material-ui"; export interface ImageProps { image: Image @@ -37,135 +28,70 @@ export default function ImageView({image}: ImageProps) { <p>RemoveError: {JSON.stringify(removeError, null, 2)}</p> <p>UpdateLoading: {JSON.stringify(updateLoading, null, 2)}</p> <p>RemoveLoading: {JSON.stringify(removeLoading, null, 2)}</p> - <p>{image.id}</p> - <p>{image.owner}</p> - <label> - Title - <input - type="text" - value={title} - onChange={({target: {value}}) => - setTitle(value)} - /> - </label> - <br/> - <label> - Description - <input - type="text" - value={description} - onChange={({target: {value}}) => - setDescription(value)} - /> - </label> - <p>{image.original_name}</p> - <p>{image.mime_type}</p> - <p>{image.created_at}</p> - <p>{image.updated_at}</p> - <p>{image.state}</p> - <h3>Metadata</h3> - {metadata.make !== undefined && ( - <p><b>Make</b>: {metadata.make}</p> - )} - {metadata.model !== undefined && ( - <p><PhotoCamera/><b>Model</b>: {metadata.model}</p> - )} - {metadata.software !== undefined && ( - <p><b>Software</b>: {metadata.software}</p> - )} - {metadata.copyright !== undefined && ( - <p><Copyright/><b>Copyright</b>: {metadata.copyright}</p> - )} - {metadata.dateTimeCreated !== undefined && ( - <p><Event/><b>DateTime Created</b>: {metadata.dateTimeCreated?.toISOString()}</p> - )} - {metadata.dateTimeDigitized !== undefined && ( - <p><Event/><b>DateTime Digitized</b>: {metadata.dateTimeDigitized?.toISOString()}</p> - )} - {metadata.dateTimeOriginal !== undefined && ( - <p><Event/><b>DateTime Original</b>: {metadata.dateTimeOriginal?.toISOString()}</p> - )} - {metadata.digitalZoomRatio !== undefined && ( - <p><ZoomIn/><b>Digital Zoom</b>: {ratioToFloat(metadata.digitalZoomRatio)}</p> - )} - {metadata.exposure !== undefined && ( - <p><Exposure/><b>Exposure</b>: {ratioToFloat(metadata.exposure)}</p> - )} - {metadata.exposureMode !== undefined && ( - <p><Exposure/><b>Exposure Mode</b>: {ExposureMode[metadata.exposureMode]}</p> - )} - {metadata.exposureProgram !== undefined && ( - <p><Exposure/><b>Exposure Program</b>: {ExposureProgram[metadata.exposureProgram]}</p> - )} - {metadata.shutterSpeed !== undefined && ( - <p><CameraTimer/><b>Shutter Speed</b>: {ratioToTime(metadata.shutterSpeed)}</p> - )} - {metadata.aperture !== undefined && ( - <p><Camera/><b>Aperture</b>: {ratioToFloat(metadata.aperture)}</p> - )} - {metadata.focalLength !== undefined && ( - <p><AngleAcute/><b>Focal Length</b>: {ratioToFloat(metadata.focalLength)}mm</p> - )} - {metadata.focalLength35mm !== undefined && ( - <p><AngleAcute/><b>Focal Length (35mm equivalent)</b>: {ratioToFloat(metadata.focalLength35mm)}mm</p> - )} - {metadata.isoSpeedRating !== undefined && ( - <p><b>ISO</b>: {metadata.isoSpeedRating}</p> - )} - {metadata.flash !== undefined && ( - <Fragment> - <p><Flash/><b>Flash</b></p> - <p><b>Available</b>: {metadata.flash.available ? "Yes" : "No"}</p> - <p><b>Fired</b>: {metadata.flash.fired ? "Yes" : "No"}</p> - <p><b>Red Eye Reduction</b>: {metadata.flash.redEyeReduction ? "Yes" : "No"}</p> - <p><b>Strobe Detection Available</b>: {metadata.flash.strobeDetection.available ? "Yes" : "No"}</p> - <p><b>Strobe Detection Used</b>: {metadata.flash.strobeDetection.detected ? "Yes" : "No"}</p> - {metadata.flash.mode !== undefined && ( - <p><b>Flash Mode</b>: {FlashMode[metadata.flash.mode]}</p> - )} - </Fragment> - )} - {metadata.lightSource !== undefined && ( - <p><WhiteBalanceIncandescent/><b>Light source</b>: {LightSource[metadata.lightSource]}</p> - )} - {metadata.meteringMode !== undefined && ( - <p><b>Metering mode</b>: {MeteringMode[metadata.meteringMode]}</p> - )} - {metadata.whiteBalance !== undefined && ( - <p><WhiteBalanceIncandescent/><b>White balance</b>: {WhiteBalance[metadata.whiteBalance]}</p> - )} - {metadata.sceneMode !== undefined && ( - <p><b>Scene Mode</b>: {SceneMode[metadata.sceneMode]}</p> - )} - {metadata.contrast !== undefined && ( - <p><BrightnessMedium/><b>Contrast Processing</b>: {ContrastProcessing[metadata.contrast]}</p> - )} - {metadata.sharpness !== undefined && ( - <p><Blur/><b>Sharpness Processing</b>: {SharpnessProcessing[metadata.sharpness]}</p> - )} - {metadata.subjectDistance !== undefined && ( - <p><ArrowExpandHorizontal/><b>Subject Distance</b>: {metadata.subjectDistance}</p> - )} - {metadata.subjectDistanceRange !== undefined && ( - <p><ArrowExpandHorizontal/><b>Subject Distance - Range</b>: {SubjectDistanceRange[metadata.subjectDistanceRange]}</p> - )} - <img src={image.url + "t"} alt=""/> - <br/> - <input - type="submit" - value="Save" + <img src={image.url + "l"} alt=""/> + <List dense> + <ListItem dense> + <ListItemIcon><Info/></ListItemIcon> + <ListItemText primary="Id" secondary={image.id}/> + </ListItem> + <ListItem dense> + <ListItemText inset primary="Owner" secondary={image.owner}/> + </ListItem> + <ListItem dense> + <ListItemText inset primary="State" secondary={image.state}/> + </ListItem> + <ListItem dense> + <ListItemIcon><Tag/></ListItemIcon> + {/* TODO: Fix this ugly nesting */} + <ListItemText + primary="Title" + secondary={<TextField + fullWidth + value={title} + onChange={({target: {value}}) => + setTitle(value)} + />} + /> + </ListItem> + <ListItem dense> + {/* TODO: Fix this ugly nesting */} + <ListItemText + inset + primary="Description" + secondary={<TextField + fullWidth + value={description} + onChange={({target: {value}}) => + setDescription(value)} + />} + /> + </ListItem> + <ListItem dense> + <ListItemIcon><File/></ListItemIcon> + <ListItemText primary="Filename" secondary={image.original_name}/> + </ListItem> + <ListItem dense> + <ListItemText inset primary="MIME Type" secondary={image.mime_type}/> + </ListItem> + <ListItem dense> + <ListItemIcon><Event/></ListItemIcon> + <ListItemText primary="Uploaded At" secondary={image.created_at}/> + </ListItem> + <ListItem dense> + <ListItemText inset primary="Modified At" secondary={image.updated_at}/> + </ListItem> + <ImageMetadataView metadata={metadata}/> + </List> + <Button onClick={() => update({ ...image, title, description, })} - /> - <input - type="submit" - value="Delete" + >Save</Button> + <Button onClick={() => remove(image)} - /> + >Delete</Button> </div> ) }