From df764ad342d9b68dcbb7fb34058abedf94cadb62 Mon Sep 17 00:00:00 2001 From: James Ketrenos Date: Wed, 25 Jan 2023 13:12:46 -0800 Subject: [PATCH] Fixed all React warnings Signed-off-by: James Ketrenos --- client/src/App.css | 11 ++- client/src/App.tsx | 174 ++++++++++++++++++------------------ server/routes/identities.js | 19 ++-- 3 files changed, 101 insertions(+), 103 deletions(-) diff --git a/client/src/App.css b/client/src/App.css index 6b38d0b..b386a25 100644 --- a/client/src/App.css +++ b/client/src/App.css @@ -148,8 +148,6 @@ div { .Face .Image { position: relative; box-sizing: border-box; - /*width: 8rem; - height: 8rem;*/ display: flex; justify-content: center; } @@ -173,11 +171,18 @@ div { align-items: flex-start; } +.Image img { + object-fit: cover; /* contain */ + width: 100%; + height: 100%; + min-width: 8rem; + min-height: 8rem; +} + .Cluster .Faces { display: grid; gap: 0.25rem; grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr)); width: 100%; - height: 100%; flex-wrap: wrap; } \ No newline at end of file diff --git a/client/src/App.tsx b/client/src/App.tsx index 249128c..5a493aa 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -10,7 +10,6 @@ import { } from "react-router-dom"; import { VirtuosoGrid } from 'react-virtuoso' import moment from 'moment'; -//import equal from "fast-deep-equal"; import './App.css'; const base = process.env.PUBLIC_URL; /* /identities -- set in .env */ @@ -130,6 +129,7 @@ const Photo = ({ photoId, onFaceClick }: any) => { return (
{image.filename} { e.preventDefault(); }; -const Face = ({ face, onFaceClick, title, ...rest }: any) => { +const Face = ({ face, onFaceClick, title, isSelected }: any) => { const faceId = face.faceId; const idPath = String(faceId % 100).padStart(2, '0'); const img = faceId === -1 - ?
?
- : ; + ?
?
+ : {faceId}; return (
{ onFaceClick(e, face) }} onMouseEnter={(e) => { onFaceMouseEnter(e, face) }} onMouseLeave={(e) => { onFaceMouseLeave(e, face) }} - className='Face'> + className={`Face ${isSelected ? 'Selected' : ''}`}>
{ img }
{title}
@@ -215,17 +209,21 @@ const Face = ({ face, onFaceClick, title, ...rest }: any) => { type ClusterProps = { identity: IdentityData, - setImage(image: number): void, - setSelected(selected: number[]): void, - setIdentity(identity: IdentityData | undefined): void + setIdentity(identity: IdentityData | undefined): void, identities: IdentityData[], - setIdentities(identiteis: IdentityData[]): void + setIdentities(identiteis: IdentityData[]): void, + setImage(image: number): void, + selected: number[], + setSelected(selected: number[]): void, }; const Cluster = ({ identity, setIdentity, identities, setIdentities, - setImage, setSelected }: ClusterProps) => { + selected, + setSelected, + setImage, +}: ClusterProps) => { const lastNameChanged = (e: any) => { setIdentity({...identity, lastName: e.currentTarget.value }); @@ -240,23 +238,15 @@ const Cluster = ({ setIdentity({...identity, displayName: e.currentTarget.value }); }; - const faceClicked = async (e: any, face: FaceData) => { - if (!identity) { - return; - } + const faceClicked = useCallback((e: any, face: FaceData) => { const el = e.currentTarget; /* Control -- select / deselect single item */ if (e.ctrlKey) { - const cluster = document.querySelector('.Cluster'); el.classList.toggle('Selected'); - if (!cluster) { - return; - } - - const selected = [...cluster.querySelectorAll('.Selected')] - .map((face: any) => face.getAttribute('data-face-id')); - setSelected(selected); + const tmp = [...document.querySelectorAll('.Cluster .Selected')] + .map((face: any) => +face.getAttribute('data-face-id')); + setSelected(tmp); return; } @@ -269,7 +259,7 @@ const Cluster = ({ e.stopPropagation(); e.preventDefault(); setImage(face.photoId); - }; + }, [setSelected, setImage]); const deleteIdentity = async () => { try { @@ -278,7 +268,7 @@ const Cluster = ({ method: 'DELETE', headers: { 'Content-Type': 'application/json' } }); - const updated = await res.json(); + await res.json(); const index = identities .findIndex((item: IdentityData) => item.identityId === identity.identityId); @@ -298,7 +288,7 @@ const Cluster = ({ 'id', 'displayName', 'firstName', 'lastName', 'middleName']; const filtered: any = Object.assign({}, identity); for (let key in filtered) { - if (validFields.indexOf(key) == -1) { + if (validFields.indexOf(key) === -1) { delete filtered[key] } } @@ -308,7 +298,7 @@ const Cluster = ({ headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(filtered) }); - const updated = await res.json(); + await res.json(); setIdentity({ ...identity }); } catch (error) { console.error(error); @@ -321,7 +311,7 @@ const Cluster = ({ 'id', 'displayName', 'firstName', 'lastName', 'middleName']; const filtered: any = Object.assign({}, identity); for (let key in filtered) { - if (validFields.indexOf(key) == -1) { + if (validFields.indexOf(key) === -1) { delete filtered[key] } } @@ -339,7 +329,6 @@ const Cluster = ({ } }; - if (identity === undefined) { return (
Select identity to load. @@ -375,10 +364,13 @@ const Cluster = ({
Faces: {identity.relatedFaces.length}
( + itemContent={(_index, face) => ( x === face.faceId) !== -1 + } face={face} onFaceClick={faceClicked} title={face.distance} /> @@ -391,10 +383,6 @@ const Cluster = ({ type FaceData = { faceId: number, photoId: number, - /* lastName: string, - firstName: string, - middleName: string, - displayName: string,*/ identity: IdentityData, identityId: number, distance: number, @@ -402,7 +390,7 @@ type FaceData = { top: number right: number, bottom: number, - left: number, + left: number }; type IdentityData = { @@ -423,25 +411,23 @@ interface IdentitiesProps { }; const Identities = ({ identities, onFaceClick } : IdentitiesProps) => { - const identitiesJSX = useMemo(() => { - return identities.map((identity) => { - const face = identity.relatedFaces[0]; - return ( -
- -
- ); - }); - }, [ identities, onFaceClick ]); + const identitiesJSX = identities.map((identity) => { + const face = identity.relatedFaces[0]; + return ( +
+ +
+ ); + }); return (
@@ -470,6 +456,19 @@ const App = () => { ); const [selected, setSelected] = useState([]); + /* 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) => { try { const res = await window.fetch(`${base}/api/v1/identities/${identityId}`); @@ -569,7 +568,7 @@ const App = () => { method: 'DELETE', headers: { 'Content-Type': 'application/json' } }); - const updated = await res.json(); + await res.json(); const index = identities .findIndex((item: IdentityData) => item.identityId === identity.identityId); @@ -597,6 +596,7 @@ const App = () => { const data = await res.json(); removeFacesFromIdentities(data.faces); + deselectAll(); } catch (error) { console.error(error); } @@ -617,6 +617,7 @@ const App = () => { const data = await res.json(); removeFacesFromIdentities(data.faces); + deselectAll(); } catch (error) { console.error(error); } @@ -647,20 +648,17 @@ const App = () => { faces: selected }) }); - const data = await res.json(); + await res.json(); removeFacesFromIdentities(selected); + deselectAll(); } catch (error) { console.error(error); } }; const deselectAll = () => { - const cluster = document.querySelector('.Cluster'); - if (!cluster) { - return; - } - [...cluster.querySelectorAll('.Selected')].forEach(item => { - item.classList.remove('Selected') + [...document.querySelectorAll('.Cluster .Selected')].forEach(item => { + item.classList.remove('Selected'); }); setSelected([]); }; @@ -681,24 +679,21 @@ const App = () => { }; const identitiesOnFaceClick = (e: any, face: FaceData) => { - const identitiesEl = document.querySelector('.Identities'); - if (!identitiesEl) { - return; - } const identityId = face.identityId; - const el = e.currentTarget; /* Control -- select / deselect single item */ if (e.ctrlKey) { - [...identitiesEl.querySelectorAll('.Selected')].forEach(item => { + let set = !el.classList.contains('Selected'); + [...document.querySelectorAll('.Identities .Selected')].forEach(item => { item.classList.remove('Selected') }); - el.classList.toggle('Selected'); - - const selected = [...identitiesEl.querySelectorAll('.Selected')] - .map((face: any) => face.getAttribute('data-identity-id')); - setSelectedIdentities(selected); + if (set) { + el.classList.add('Selected'); + } + const tmp = [...document.querySelectorAll('.Identities .Selected')] + .map((face: any) => +face.getAttribute('data-identity-id')); + setSelectedIdentities(tmp); return; } @@ -711,6 +706,11 @@ const App = () => { e.stopPropagation(); e.preventDefault(); + [...document.querySelectorAll('.Cluster .Faces img')] + .forEach((img: any) => { + img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; + }); + loadIdentity(identityId); } @@ -721,20 +721,21 @@ const App = () => { autoSaveId="persistence" direction="horizontal"> {loading &&
Loading...
} - {!loading && identity !== undefined && + { !loading && identity !== undefined && } + selected, + setSelected, + }} /> } {!loading && identity === undefined &&
Select identity to edit
}
- {selected.length === 1 && <> + { selected.length === 1 && <> } { selected.length !== 0 && <> @@ -743,9 +744,9 @@ const App = () => { } - {selectedIdentities.length !== 0 && <> + { selectedIdentities.length !== 0 && <> - } + }
@@ -766,7 +767,8 @@ const App = () => { { !loading && } + {... { onFaceClick: identitiesOnFaceClick, identities }}/> + }
diff --git a/server/routes/identities.js b/server/routes/identities.js index 9f24839..a57df3b 100755 --- a/server/routes/identities.js +++ b/server/routes/identities.js @@ -170,19 +170,6 @@ router.delete('/:id', async (req, res) => { 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 * it as a guess -- does not modify the DB */ 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 === undefined) { 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 { where = 'identityId=:identityId'; limit = 'LIMIT 1'; @@ -650,6 +640,7 @@ router.get("/:id?", async (req, res) => { } else { where = 'identityId=:identityId' } + identity.relatedFaces = await photoDB.sequelize.query( 'SELECT id as faceId,identityId,photoId,distance ' + 'FROM faces ' +