Fixed all React warnings
Signed-off-by: James Ketrenos <james_git@ketrenos.com>
This commit is contained in:
parent
d45600f6a7
commit
df764ad342
@ -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;
|
||||
}
|
@ -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 (<div className="PhotoPanel">
|
||||
<div className="Image" ref={ref}>
|
||||
<img
|
||||
alt={image.filename}
|
||||
src={`${base}/../${image.path}thumbs/scaled/${image.filename}`.replace(/ /g, '%20')}
|
||||
style={{
|
||||
objectFit: 'contain',
|
||||
@ -185,26 +185,20 @@ const onFaceMouseLeave = (e: any, face: FaceData) => {
|
||||
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
|
||||
? <div className='UnknownFace'>?</div>
|
||||
: <img src={`${base}/../faces/${idPath}/${faceId}.jpg`}
|
||||
style={{
|
||||
objectFit: 'cover',//'contain',
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}} />;
|
||||
? <div className='UnknownFace'>?</div>
|
||||
: <img alt={faceId} src={`${base}/../faces/${idPath}/${faceId}.jpg`}/>;
|
||||
return (
|
||||
<div
|
||||
data-face-id={face.faceId}
|
||||
data-identity-id={face.identityId}
|
||||
{...rest}
|
||||
onClick={(e) => { onFaceClick(e, face) }}
|
||||
onMouseEnter={(e) => { onFaceMouseEnter(e, face) }}
|
||||
onMouseLeave={(e) => { onFaceMouseLeave(e, face) }}
|
||||
className='Face'>
|
||||
className={`Face ${isSelected ? 'Selected' : ''}`}>
|
||||
<div className='Image'>
|
||||
{ img }
|
||||
<div className='Title'>{title}</div>
|
||||
@ -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 (<div className='Cluster'>
|
||||
Select identity to load.
|
||||
@ -375,10 +364,13 @@ const Cluster = ({
|
||||
<div>Faces: {identity.relatedFaces.length}</div>
|
||||
<VirtuosoGrid
|
||||
data={identity.relatedFaces}
|
||||
|
||||
listClassName='Faces'
|
||||
itemContent={(index, face) => (
|
||||
itemContent={(_index, face) => (
|
||||
<Face
|
||||
isSelected={
|
||||
selected.findIndex(
|
||||
(x: number) => 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 (
|
||||
<div
|
||||
key={face.faceId}
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
}}>
|
||||
<Face
|
||||
face={face}
|
||||
onFaceClick={onFaceClick}
|
||||
title={`${identity.displayName} (${identity.facesCount})`}/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}, [ identities, onFaceClick ]);
|
||||
const identitiesJSX = identities.map((identity) => {
|
||||
const face = identity.relatedFaces[0];
|
||||
return (
|
||||
<div
|
||||
key={face.faceId}
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
}}>
|
||||
<Face
|
||||
face={face}
|
||||
onFaceClick={onFaceClick}
|
||||
title={`${identity.displayName} (${identity.facesCount})`}/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className='Identities'>
|
||||
@ -470,6 +456,19 @@ const App = () => {
|
||||
);
|
||||
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) => {
|
||||
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">
|
||||
<Panel defaultSize={50} className="ClusterEditor">
|
||||
{loading && <div style={{ margin: '1rem' }}>Loading...</div>}
|
||||
{!loading && identity !== undefined &&
|
||||
{ !loading && identity !== undefined &&
|
||||
<Cluster {...{
|
||||
identity,
|
||||
setIdentity,
|
||||
identities,
|
||||
setIdentities,
|
||||
setImage,
|
||||
setSelected
|
||||
}} />}
|
||||
selected,
|
||||
setSelected,
|
||||
}} /> }
|
||||
{!loading && identity === undefined && <div className="Cluster">
|
||||
Select identity to edit
|
||||
</div>}
|
||||
<div className="Actions">
|
||||
{selected.length === 1 && <>
|
||||
{ selected.length === 1 && <>
|
||||
<Button onClick={guessIdentity}>Guess</Button>
|
||||
</>}
|
||||
{ selected.length !== 0 && <>
|
||||
@ -743,9 +744,9 @@ const App = () => {
|
||||
<Button onClick={changeSelectedIdentity}>Change Identity</Button>
|
||||
<Button onClick={deselectAll}>Deselect All</Button>
|
||||
</>}
|
||||
{selectedIdentities.length !== 0 && <>
|
||||
{ selectedIdentities.length !== 0 && <>
|
||||
<Button onClick={mergeIdentity}>Merge</Button>
|
||||
</>}
|
||||
</> }
|
||||
</div>
|
||||
</Panel>
|
||||
<PanelResizeHandle className="Resizer"/>
|
||||
@ -766,7 +767,8 @@ const App = () => {
|
||||
<PanelResizeHandle className="Resizer" />
|
||||
<Panel defaultSize={8.5} minSize={8.5} className="IdentitiesList">
|
||||
{ !loading && <Identities
|
||||
{... { onFaceClick: identitiesOnFaceClick, identities }}/> }
|
||||
{... { onFaceClick: identitiesOnFaceClick, identities }}/>
|
||||
}
|
||||
</Panel>
|
||||
</PanelGroup>
|
||||
</div>
|
||||
|
@ -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 ' +
|
||||
|
Loading…
x
Reference in New Issue
Block a user