Fixed all React warnings

Signed-off-by: James Ketrenos <james_git@ketrenos.com>
This commit is contained in:
James Ketr 2023-01-25 13:12:46 -08:00
parent d45600f6a7
commit df764ad342
3 changed files with 101 additions and 103 deletions

View File

@ -148,8 +148,6 @@ div {
.Face .Image { .Face .Image {
position: relative; position: relative;
box-sizing: border-box; box-sizing: border-box;
/*width: 8rem;
height: 8rem;*/
display: flex; display: flex;
justify-content: center; justify-content: center;
} }
@ -173,11 +171,18 @@ div {
align-items: flex-start; align-items: flex-start;
} }
.Image img {
object-fit: cover; /* contain */
width: 100%;
height: 100%;
min-width: 8rem;
min-height: 8rem;
}
.Cluster .Faces { .Cluster .Faces {
display: grid; display: grid;
gap: 0.25rem; gap: 0.25rem;
grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr)); grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
width: 100%; width: 100%;
height: 100%;
flex-wrap: wrap; flex-wrap: wrap;
} }

View File

@ -10,7 +10,6 @@ import {
} from "react-router-dom"; } from "react-router-dom";
import { VirtuosoGrid } from 'react-virtuoso' import { VirtuosoGrid } from 'react-virtuoso'
import moment from 'moment'; import moment from 'moment';
//import equal from "fast-deep-equal";
import './App.css'; import './App.css';
const base = process.env.PUBLIC_URL; /* /identities -- set in .env */ const base = process.env.PUBLIC_URL; /* /identities -- set in .env */
@ -130,6 +129,7 @@ const Photo = ({ photoId, onFaceClick }: any) => {
return (<div className="PhotoPanel"> return (<div className="PhotoPanel">
<div className="Image" ref={ref}> <div className="Image" ref={ref}>
<img <img
alt={image.filename}
src={`${base}/../${image.path}thumbs/scaled/${image.filename}`.replace(/ /g, '%20')} src={`${base}/../${image.path}thumbs/scaled/${image.filename}`.replace(/ /g, '%20')}
style={{ style={{
objectFit: 'contain', objectFit: 'contain',
@ -185,26 +185,20 @@ const onFaceMouseLeave = (e: any, face: FaceData) => {
e.preventDefault(); e.preventDefault();
}; };
const Face = ({ face, onFaceClick, title, ...rest }: any) => { const Face = ({ face, onFaceClick, title, isSelected }: any) => {
const faceId = face.faceId; const faceId = face.faceId;
const idPath = String(faceId % 100).padStart(2, '0'); const idPath = String(faceId % 100).padStart(2, '0');
const img = faceId === -1 const img = faceId === -1
? <div className='UnknownFace'>?</div> ? <div className='UnknownFace'>?</div>
: <img src={`${base}/../faces/${idPath}/${faceId}.jpg`} : <img alt={faceId} src={`${base}/../faces/${idPath}/${faceId}.jpg`}/>;
style={{
objectFit: 'cover',//'contain',
width: '100%',
height: '100%'
}} />;
return ( return (
<div <div
data-face-id={face.faceId} data-face-id={face.faceId}
data-identity-id={face.identityId} data-identity-id={face.identityId}
{...rest}
onClick={(e) => { onFaceClick(e, face) }} onClick={(e) => { onFaceClick(e, face) }}
onMouseEnter={(e) => { onFaceMouseEnter(e, face) }} onMouseEnter={(e) => { onFaceMouseEnter(e, face) }}
onMouseLeave={(e) => { onFaceMouseLeave(e, face) }} onMouseLeave={(e) => { onFaceMouseLeave(e, face) }}
className='Face'> className={`Face ${isSelected ? 'Selected' : ''}`}>
<div className='Image'> <div className='Image'>
{ img } { img }
<div className='Title'>{title}</div> <div className='Title'>{title}</div>
@ -215,17 +209,21 @@ const Face = ({ face, onFaceClick, title, ...rest }: any) => {
type ClusterProps = { type ClusterProps = {
identity: IdentityData, identity: IdentityData,
setImage(image: number): void, setIdentity(identity: IdentityData | undefined): void,
setSelected(selected: number[]): void,
setIdentity(identity: IdentityData | undefined): void
identities: IdentityData[], identities: IdentityData[],
setIdentities(identiteis: IdentityData[]): void setIdentities(identiteis: IdentityData[]): void,
setImage(image: number): void,
selected: number[],
setSelected(selected: number[]): void,
}; };
const Cluster = ({ const Cluster = ({
identity, setIdentity, identity, setIdentity,
identities, setIdentities, identities, setIdentities,
setImage, setSelected }: ClusterProps) => { selected,
setSelected,
setImage,
}: ClusterProps) => {
const lastNameChanged = (e: any) => { const lastNameChanged = (e: any) => {
setIdentity({...identity, lastName: e.currentTarget.value }); setIdentity({...identity, lastName: e.currentTarget.value });
@ -240,23 +238,15 @@ const Cluster = ({
setIdentity({...identity, displayName: e.currentTarget.value }); setIdentity({...identity, displayName: e.currentTarget.value });
}; };
const faceClicked = async (e: any, face: FaceData) => { const faceClicked = useCallback((e: any, face: FaceData) => {
if (!identity) {
return;
}
const el = e.currentTarget; const el = e.currentTarget;
/* Control -- select / deselect single item */ /* Control -- select / deselect single item */
if (e.ctrlKey) { if (e.ctrlKey) {
const cluster = document.querySelector('.Cluster');
el.classList.toggle('Selected'); el.classList.toggle('Selected');
if (!cluster) { const tmp = [...document.querySelectorAll('.Cluster .Selected')]
return; .map((face: any) => +face.getAttribute('data-face-id'));
} setSelected(tmp);
const selected = [...cluster.querySelectorAll('.Selected')]
.map((face: any) => face.getAttribute('data-face-id'));
setSelected(selected);
return; return;
} }
@ -269,7 +259,7 @@ const Cluster = ({
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
setImage(face.photoId); setImage(face.photoId);
}; }, [setSelected, setImage]);
const deleteIdentity = async () => { const deleteIdentity = async () => {
try { try {
@ -278,7 +268,7 @@ const Cluster = ({
method: 'DELETE', method: 'DELETE',
headers: { 'Content-Type': 'application/json' } headers: { 'Content-Type': 'application/json' }
}); });
const updated = await res.json(); await res.json();
const index = identities const index = identities
.findIndex((item: IdentityData) => .findIndex((item: IdentityData) =>
item.identityId === identity.identityId); item.identityId === identity.identityId);
@ -298,7 +288,7 @@ const Cluster = ({
'id', 'displayName', 'firstName', 'lastName', 'middleName']; 'id', 'displayName', 'firstName', 'lastName', 'middleName'];
const filtered: any = Object.assign({}, identity); const filtered: any = Object.assign({}, identity);
for (let key in filtered) { for (let key in filtered) {
if (validFields.indexOf(key) == -1) { if (validFields.indexOf(key) === -1) {
delete filtered[key] delete filtered[key]
} }
} }
@ -308,7 +298,7 @@ const Cluster = ({
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(filtered) body: JSON.stringify(filtered)
}); });
const updated = await res.json(); await res.json();
setIdentity({ ...identity }); setIdentity({ ...identity });
} catch (error) { } catch (error) {
console.error(error); console.error(error);
@ -321,7 +311,7 @@ const Cluster = ({
'id', 'displayName', 'firstName', 'lastName', 'middleName']; 'id', 'displayName', 'firstName', 'lastName', 'middleName'];
const filtered: any = Object.assign({}, identity); const filtered: any = Object.assign({}, identity);
for (let key in filtered) { for (let key in filtered) {
if (validFields.indexOf(key) == -1) { if (validFields.indexOf(key) === -1) {
delete filtered[key] delete filtered[key]
} }
} }
@ -339,7 +329,6 @@ const Cluster = ({
} }
}; };
if (identity === undefined) { if (identity === undefined) {
return (<div className='Cluster'> return (<div className='Cluster'>
Select identity to load. Select identity to load.
@ -375,10 +364,13 @@ const Cluster = ({
<div>Faces: {identity.relatedFaces.length}</div> <div>Faces: {identity.relatedFaces.length}</div>
<VirtuosoGrid <VirtuosoGrid
data={identity.relatedFaces} data={identity.relatedFaces}
listClassName='Faces' listClassName='Faces'
itemContent={(index, face) => ( itemContent={(_index, face) => (
<Face <Face
isSelected={
selected.findIndex(
(x: number) => x === face.faceId) !== -1
}
face={face} face={face}
onFaceClick={faceClicked} onFaceClick={faceClicked}
title={face.distance} /> title={face.distance} />
@ -391,10 +383,6 @@ const Cluster = ({
type FaceData = { type FaceData = {
faceId: number, faceId: number,
photoId: number, photoId: number,
/* lastName: string,
firstName: string,
middleName: string,
displayName: string,*/
identity: IdentityData, identity: IdentityData,
identityId: number, identityId: number,
distance: number, distance: number,
@ -402,7 +390,7 @@ type FaceData = {
top: number top: number
right: number, right: number,
bottom: number, bottom: number,
left: number, left: number
}; };
type IdentityData = { type IdentityData = {
@ -423,25 +411,23 @@ interface IdentitiesProps {
}; };
const Identities = ({ identities, onFaceClick } : IdentitiesProps) => { const Identities = ({ identities, onFaceClick } : IdentitiesProps) => {
const identitiesJSX = useMemo(() => { const identitiesJSX = identities.map((identity) => {
return identities.map((identity) => { const face = identity.relatedFaces[0];
const face = identity.relatedFaces[0]; return (
return ( <div
<div key={face.faceId}
key={face.faceId} style={{
style={{ display: "flex",
display: "flex", justifyContent: 'center',
justifyContent: 'center', alignItems: 'center'
alignItems: 'center' }}>
}}> <Face
<Face face={face}
face={face} onFaceClick={onFaceClick}
onFaceClick={onFaceClick} title={`${identity.displayName} (${identity.facesCount})`}/>
title={`${identity.displayName} (${identity.facesCount})`}/> </div>
</div> );
); });
});
}, [ identities, onFaceClick ]);
return ( return (
<div className='Identities'> <div className='Identities'>
@ -470,6 +456,19 @@ const App = () => {
); );
const [selected, setSelected] = useState<number[]>([]); const [selected, setSelected] = useState<number[]>([]);
/* If 'selected' changes, clear any selected face which is not in the
* selected array. */
useEffect(() => {
[...document.querySelectorAll('.Cluster .Selected')].forEach(el => {
const faceId = el.getAttribute('data-face-id');
if (faceId) {
if (selected.findIndex(item => item === +faceId) === -1) {
el.classList.remove('Selected');
}
}
});
}, [selected]);
const loadIdentity = async (identityId: number) => { const loadIdentity = async (identityId: number) => {
try { try {
const res = await window.fetch(`${base}/api/v1/identities/${identityId}`); const res = await window.fetch(`${base}/api/v1/identities/${identityId}`);
@ -569,7 +568,7 @@ const App = () => {
method: 'DELETE', method: 'DELETE',
headers: { 'Content-Type': 'application/json' } headers: { 'Content-Type': 'application/json' }
}); });
const updated = await res.json(); await res.json();
const index = identities const index = identities
.findIndex((item: IdentityData) => .findIndex((item: IdentityData) =>
item.identityId === identity.identityId); item.identityId === identity.identityId);
@ -597,6 +596,7 @@ const App = () => {
const data = await res.json(); const data = await res.json();
removeFacesFromIdentities(data.faces); removeFacesFromIdentities(data.faces);
deselectAll();
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
@ -617,6 +617,7 @@ const App = () => {
const data = await res.json(); const data = await res.json();
removeFacesFromIdentities(data.faces); removeFacesFromIdentities(data.faces);
deselectAll();
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
@ -647,20 +648,17 @@ const App = () => {
faces: selected faces: selected
}) })
}); });
const data = await res.json(); await res.json();
removeFacesFromIdentities(selected); removeFacesFromIdentities(selected);
deselectAll();
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
}; };
const deselectAll = () => { const deselectAll = () => {
const cluster = document.querySelector('.Cluster'); [...document.querySelectorAll('.Cluster .Selected')].forEach(item => {
if (!cluster) { item.classList.remove('Selected');
return;
}
[...cluster.querySelectorAll('.Selected')].forEach(item => {
item.classList.remove('Selected')
}); });
setSelected([]); setSelected([]);
}; };
@ -681,24 +679,21 @@ const App = () => {
}; };
const identitiesOnFaceClick = (e: any, face: FaceData) => { const identitiesOnFaceClick = (e: any, face: FaceData) => {
const identitiesEl = document.querySelector('.Identities');
if (!identitiesEl) {
return;
}
const identityId = face.identityId; const identityId = face.identityId;
const el = e.currentTarget; const el = e.currentTarget;
/* Control -- select / deselect single item */ /* Control -- select / deselect single item */
if (e.ctrlKey) { if (e.ctrlKey) {
[...identitiesEl.querySelectorAll('.Selected')].forEach(item => { let set = !el.classList.contains('Selected');
[...document.querySelectorAll('.Identities .Selected')].forEach(item => {
item.classList.remove('Selected') item.classList.remove('Selected')
}); });
el.classList.toggle('Selected'); if (set) {
el.classList.add('Selected');
const selected = [...identitiesEl.querySelectorAll('.Selected')] }
.map((face: any) => face.getAttribute('data-identity-id')); const tmp = [...document.querySelectorAll('.Identities .Selected')]
setSelectedIdentities(selected); .map((face: any) => +face.getAttribute('data-identity-id'));
setSelectedIdentities(tmp);
return; return;
} }
@ -711,6 +706,11 @@ const App = () => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
[...document.querySelectorAll('.Cluster .Faces img')]
.forEach((img: any) => {
img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
});
loadIdentity(identityId); loadIdentity(identityId);
} }
@ -721,20 +721,21 @@ const App = () => {
autoSaveId="persistence" direction="horizontal"> autoSaveId="persistence" direction="horizontal">
<Panel defaultSize={50} className="ClusterEditor"> <Panel defaultSize={50} className="ClusterEditor">
{loading && <div style={{ margin: '1rem' }}>Loading...</div>} {loading && <div style={{ margin: '1rem' }}>Loading...</div>}
{!loading && identity !== undefined && { !loading && identity !== undefined &&
<Cluster {...{ <Cluster {...{
identity, identity,
setIdentity, setIdentity,
identities, identities,
setIdentities, setIdentities,
setImage, setImage,
setSelected selected,
}} />} setSelected,
}} /> }
{!loading && identity === undefined && <div className="Cluster"> {!loading && identity === undefined && <div className="Cluster">
Select identity to edit Select identity to edit
</div>} </div>}
<div className="Actions"> <div className="Actions">
{selected.length === 1 && <> { selected.length === 1 && <>
<Button onClick={guessIdentity}>Guess</Button> <Button onClick={guessIdentity}>Guess</Button>
</>} </>}
{ selected.length !== 0 && <> { selected.length !== 0 && <>
@ -743,9 +744,9 @@ const App = () => {
<Button onClick={changeSelectedIdentity}>Change Identity</Button> <Button onClick={changeSelectedIdentity}>Change Identity</Button>
<Button onClick={deselectAll}>Deselect All</Button> <Button onClick={deselectAll}>Deselect All</Button>
</>} </>}
{selectedIdentities.length !== 0 && <> { selectedIdentities.length !== 0 && <>
<Button onClick={mergeIdentity}>Merge</Button> <Button onClick={mergeIdentity}>Merge</Button>
</>} </> }
</div> </div>
</Panel> </Panel>
<PanelResizeHandle className="Resizer"/> <PanelResizeHandle className="Resizer"/>
@ -766,7 +767,8 @@ const App = () => {
<PanelResizeHandle className="Resizer" /> <PanelResizeHandle className="Resizer" />
<Panel defaultSize={8.5} minSize={8.5} className="IdentitiesList"> <Panel defaultSize={8.5} minSize={8.5} className="IdentitiesList">
{ !loading && <Identities { !loading && <Identities
{... { onFaceClick: identitiesOnFaceClick, identities }}/> } {... { onFaceClick: identitiesOnFaceClick, identities }}/>
}
</Panel> </Panel>
</PanelGroup> </PanelGroup>
</div> </div>

View File

@ -170,19 +170,6 @@ router.delete('/:id', async (req, res) => {
return res.status(200).send({}); return res.status(200).send({});
}); });
const writeIdentityDescriptors = async (identity) => {
await photoDB.sequelize.query(
'UPDATE identities ' +
'SET descriptors=:descriptors' +
'WHERE id=:identityId', {
replacements: identity
}
);
};
/* Given a faceId, find the closest defined identity and return /* Given a faceId, find the closest defined identity and return
* it as a guess -- does not modify the DB */ * it as a guess -- does not modify the DB */
router.get("/faces/guess/:faceId", async (req, res) => { router.get("/faces/guess/:faceId", async (req, res) => {
@ -642,7 +629,10 @@ router.get("/:id?", async (req, res) => {
/* If id was not set, only return a single face */ /* If id was not set, only return a single face */
if (id === undefined) { if (id === undefined) {
if (identity.faceId !== -1 && identity.faceId !== null) { if (identity.faceId !== -1 && identity.faceId !== null) {
where = 'faceId=:faceId'; /* Return the identity faceId, and make sure the face
* is associated with that id -- they can get out of sync
* when faces are added/removed from an identity */
where = 'faceId=:faceId AND identityId=:identityId';
} else { } else {
where = 'identityId=:identityId'; where = 'identityId=:identityId';
limit = 'LIMIT 1'; limit = 'LIMIT 1';
@ -650,6 +640,7 @@ router.get("/:id?", async (req, res) => {
} else { } else {
where = 'identityId=:identityId' where = 'identityId=:identityId'
} }
identity.relatedFaces = await photoDB.sequelize.query( identity.relatedFaces = await photoDB.sequelize.query(
'SELECT id as faceId,identityId,photoId,distance ' + 'SELECT id as faceId,identityId,photoId,distance ' +
'FROM faces ' + 'FROM faces ' +