Identities are loading; starting to think through workflow
Signed-off-by: James Ketrenos <james_git@ketrenos.com>
This commit is contained in:
parent
1ed1b1d1ea
commit
e8de846ed0
@ -26,20 +26,30 @@ div {
|
|||||||
border: 1px solid green;
|
border: 1px solid green;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Identity {
|
.Face {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
box-sizing: border-box;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 0.125rem;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Identity:hover {
|
.Face:hover {
|
||||||
border: 1px solid yellow;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Identity .Title {
|
.Face .Image {
|
||||||
|
border: 0.25rem solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Face:hover .Image {
|
||||||
|
border: 0.25rem solid yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Face.Selected .Image {
|
||||||
|
border: 0.25rem solid blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Face .Title {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -50,7 +60,9 @@ div {
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Identity .Face {
|
.Face .Image {
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
width: 8rem;
|
width: 8rem;
|
||||||
height: 8rem;
|
height: 8rem;
|
||||||
background-size: contain !important;
|
background-size: contain !important;
|
||||||
@ -58,16 +70,18 @@ div {
|
|||||||
background-position: 50% 50% !important;;
|
background-position: 50% 50% !important;;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.Cluster {
|
.Cluster {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
border: 1px solid red;
|
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Cluster .Face.Selected {
|
||||||
|
/* filter: grayscale(100%); */
|
||||||
|
}
|
||||||
|
|
||||||
.Cluster .Info {
|
.Cluster .Info {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -3,6 +3,21 @@ import React, { useState, useMemo, useEffect } from 'react';
|
|||||||
import { useApi } from './useApi';
|
import { useApi } from './useApi';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
|
||||||
|
const Face = ({ faceId, onClick, title }: any) => {
|
||||||
|
const idPath = String(faceId % 100).padStart(2, '0');
|
||||||
|
return (
|
||||||
|
<div onClick={(e) => { onClick(e, faceId) }}
|
||||||
|
className='Face'>
|
||||||
|
<div className='Image'
|
||||||
|
style={{
|
||||||
|
background: `url("/faces/${idPath}/${faceId}.jpg")`,
|
||||||
|
}}>
|
||||||
|
<div className='Title'>{title}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
type ClusterProps = {
|
type ClusterProps = {
|
||||||
id: number
|
id: number
|
||||||
};
|
};
|
||||||
@ -23,48 +38,113 @@ const Cluster = ({ id }: ClusterProps) => {
|
|||||||
}
|
}
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const relatedFacesJSX = useMemo(() => {
|
const relatedFacesJSX = useMemo(() => {
|
||||||
|
const faceClicked = (e: any, id: any) => {
|
||||||
|
if (!identity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const el = e.currentTarget;
|
||||||
|
const face = identity.relatedFaces.find(item => item.faceId === id);
|
||||||
|
el.classList.toggle('Selected');
|
||||||
|
console.log(face);
|
||||||
|
}
|
||||||
if (identity === undefined) {
|
if (identity === undefined) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
return identity.relatedFaces.map((face) => {
|
return identity.relatedFaces.map(face =>
|
||||||
const idPath = String(face.faceId % 100).padStart(2, '0');
|
<Face key={face.faceId}
|
||||||
return (
|
faceId={face.faceId}
|
||||||
<div key={face.faceId}
|
onClick={faceClicked}
|
||||||
className='Identity'>
|
title={face.distance}/>
|
||||||
<div className='Title'>
|
|
||||||
{face.distance}
|
|
||||||
</div>
|
|
||||||
<div className='Face'
|
|
||||||
style={{
|
|
||||||
background: `url("/faces/${idPath}/${face.faceId}.jpg")`,
|
|
||||||
}} />
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
});
|
|
||||||
}, [identity]);
|
}, [identity]);
|
||||||
|
|
||||||
|
const lastNameChanged = (e: any) => {
|
||||||
|
setIdentity(Object.assign(
|
||||||
|
{},
|
||||||
|
identity, {
|
||||||
|
lastName: e.currentTarget.value
|
||||||
|
}
|
||||||
|
));
|
||||||
|
};
|
||||||
|
const firstNameChanged = (e: any) => {
|
||||||
|
setIdentity(Object.assign(
|
||||||
|
{},
|
||||||
|
identity, {
|
||||||
|
firstName: e.currentTarget.value
|
||||||
|
}
|
||||||
|
));
|
||||||
|
};
|
||||||
|
const middleNameChanged = (e: any) => {
|
||||||
|
setIdentity(Object.assign(
|
||||||
|
{},
|
||||||
|
identity, {
|
||||||
|
middleName: e.currentTarget.value
|
||||||
|
}
|
||||||
|
));
|
||||||
|
};
|
||||||
|
const displayNameChanged = (e: any) => {
|
||||||
|
setIdentity(Object.assign(
|
||||||
|
{},
|
||||||
|
identity, {
|
||||||
|
displayName: e.currentTarget.value
|
||||||
|
}
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (<div className='Cluster'>
|
||||||
|
{loading && `Loading ${id}...`}
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (identity === undefined) {
|
||||||
|
return (<div className='Cluster'>
|
||||||
|
Select identity to load.
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='Cluster'>
|
<div className='Cluster'>
|
||||||
{ loading && `Loading ${id}`}
|
<div className="Info">
|
||||||
{ identity !== undefined && <div className="Info">
|
<form>
|
||||||
<div>{identity.lastName}</div>
|
<div><label>Last name:<input type="text"
|
||||||
<div>{identity.firstName}</div>
|
value={identity.lastName}
|
||||||
<div>{identity.middleName}</div>
|
onChange={lastNameChanged}/>
|
||||||
<div>{identity.displayName}</div>
|
</label></div>
|
||||||
|
<div><label>First name:<input type="text"
|
||||||
|
value={identity.firstName}
|
||||||
|
onChange={firstNameChanged} />
|
||||||
|
</label></div>
|
||||||
|
<div><label>Middle name:<input type="text"
|
||||||
|
value={identity.middleName}
|
||||||
|
onChange={middleNameChanged} />
|
||||||
|
</label></div>
|
||||||
|
<div><label>Display name:<input type="text"
|
||||||
|
value={identity.displayName}
|
||||||
|
onChange={displayNameChanged} />
|
||||||
|
</label></div>
|
||||||
<div>Faces: {identity.relatedFaces.length}</div>
|
<div>Faces: {identity.relatedFaces.length}</div>
|
||||||
</div> }
|
</form>
|
||||||
{ identity !== undefined && <div className="Faces">
|
</div>
|
||||||
|
<div className="Faces">
|
||||||
{ relatedFacesJSX }
|
{ relatedFacesJSX }
|
||||||
</div> }
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
type Face = {
|
type FaceData = {
|
||||||
distance: number,
|
|
||||||
faceId: number,
|
faceId: number,
|
||||||
photoId: number
|
lastName: string,
|
||||||
|
firstName: string,
|
||||||
|
middleName: string,
|
||||||
|
displayName: string,
|
||||||
|
identityId: number,
|
||||||
|
distance: number,
|
||||||
|
descriptors: any[]
|
||||||
};
|
};
|
||||||
|
|
||||||
type Identity = {
|
type Identity = {
|
||||||
@ -74,7 +154,7 @@ type Identity = {
|
|||||||
descriptors: number[],
|
descriptors: number[],
|
||||||
id: number
|
id: number
|
||||||
displayName: string,
|
displayName: string,
|
||||||
relatedFaces: Face[]
|
relatedFaces: FaceData[]
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IdentitiesProps {
|
interface IdentitiesProps {
|
||||||
@ -83,8 +163,6 @@ interface IdentitiesProps {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Identities = ({ identities, setIdentity } : IdentitiesProps) => {
|
const Identities = ({ identities, setIdentity } : IdentitiesProps) => {
|
||||||
|
|
||||||
|
|
||||||
const identitiesJSX = useMemo(() => {
|
const identitiesJSX = useMemo(() => {
|
||||||
const loadIdentity = (id: number): void => {
|
const loadIdentity = (id: number): void => {
|
||||||
if (setIdentity) {
|
if (setIdentity) {
|
||||||
@ -93,19 +171,11 @@ const Identities = ({ identities, setIdentity } : IdentitiesProps) => {
|
|||||||
};
|
};
|
||||||
return identities.map((identity) => {
|
return identities.map((identity) => {
|
||||||
const face = identity.relatedFaces[0];
|
const face = identity.relatedFaces[0];
|
||||||
const idPath = String(face.faceId % 100).padStart(2, '0');
|
|
||||||
return (
|
return (
|
||||||
<div key={face.faceId}
|
<Face key={face.faceId}
|
||||||
|
faceId={face.faceId}
|
||||||
onClick={() => loadIdentity(identity.id)}
|
onClick={() => loadIdentity(identity.id)}
|
||||||
className='Identity'>
|
title={identity.displayName}/>
|
||||||
<div className='Title'>
|
|
||||||
{identity.displayName}
|
|
||||||
</div>
|
|
||||||
<div className='Face'
|
|
||||||
style={{
|
|
||||||
background: `url("/faces/${idPath}/${face.faceId}.jpg")`,
|
|
||||||
}}/>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}, [ setIdentity, identities ]);
|
}, [ setIdentity, identities ]);
|
||||||
@ -133,7 +203,7 @@ const App = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<div className="Worksheet">
|
<div className="Worksheet">
|
||||||
{ loading && <div>Loading...</div> }
|
{ loading && <div style={{margin:'1rem'}}>Loading...</div> }
|
||||||
{ !loading && identity !== 0 && <Cluster id={identity} />}
|
{ !loading && identity !== 0 && <Cluster id={identity} />}
|
||||||
{ !loading && identity === 0 && <div className="Cluster">
|
{ !loading && identity === 0 && <div className="Cluster">
|
||||||
Select identity to edit
|
Select identity to edit
|
||||||
|
@ -8,31 +8,30 @@ type UseApi = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const useApi = (_url: string, _options?: {}) : UseApi => {
|
const useApi = (_url: string, _options?: {}) : UseApi => {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(true);
|
||||||
const [data, setData] = useState(undefined);
|
const [data, setData] = useState(undefined);
|
||||||
const [error, setError] = useState<any>(undefined);
|
const [error, setError] = useState<any>(undefined);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (_url === '' || loading) {
|
if (_url === '') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const fetchApi = async () => {
|
const fetchApi = async () => {
|
||||||
console.log(`Fetching ${_url}...`);
|
console.log(`Fetching ${_url}...`);
|
||||||
setLoading(true);
|
|
||||||
try {
|
try {
|
||||||
const res = await window.fetch(_url, _options);
|
const res = await window.fetch(_url, _options);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setData(data);
|
setData(data);
|
||||||
|
setLoading(false);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
setError(e)
|
setError(e)
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchApi();
|
fetchApi();
|
||||||
}, [_url, _options, loading]);
|
}, [_url, _options]);
|
||||||
|
|
||||||
return { loading, data, error };
|
return { loading, data, error };
|
||||||
};
|
};
|
||||||
|
7
reset-identities.sh
Normal file
7
reset-identities.sh
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
. .env
|
||||||
|
cat << EOF | ./query.sh
|
||||||
|
update faces set identityId=NULL where identityId is not null;
|
||||||
|
delete from identities where id>0;
|
||||||
|
delete from sqlite_sequence where name='identities';
|
||||||
|
EOF
|
Loading…
x
Reference in New Issue
Block a user