/* Polyfills for IE */ import 'react-app-polyfill/ie11'; import 'core-js/features/array/find'; import 'core-js/features/array/includes'; import 'core-js/features/number/is-nan'; /* App starts here */ import React from "react"; import { withRouter, NavLink, Route, Switch } from "react-router-dom"; import Modal from "react-bootstrap/Modal"; import Button from "react-bootstrap/Button"; import "bootstrap/dist/css/bootstrap.min.css"; /* Custom components */ import './modest.css'; import "./App.css"; class Header extends React.Component { constructor(props) { super(props); } componentDidMount() { } render() { return (
); } } class Logo extends React.Component { render() { return
} } class Footer extends React.Component { constructor(props) { super(props); } render() { return (
Copyright 2020 James Ketrenos
); } } function noChange() {}; class Identities extends React.Component { constructor(props) { super(props); } componentDidMount() { let params = window.location.search ? window.location.search.replace(/^\?/, "").split("&") : [], id; for (let i in params) { let parts = params[i].split("="); switch (parts[0]) { case "id": id = parseInt(parts[1]); if (id != parts[1]) { id = undefined; } break; } } loadFace(id); const newIdentity = document.getElementById("new-identity"); newIdentity.appendChild(createNewIdenityEditor()); } render() { return (
) } } class App extends React.Component { constructor(props) { super(props); } componentDidMount() { const location = this.props.history.location.pathname; console.log(`App.mounted at ${location}`); } render() { console.log("App"); return (
(this.app = ref) }>
}/>
}/>
}/>
); } } export default withRouter(App); /* 1. Query server for a face with no bound identity. 2. Query for faces related to that face 3. Put a UI that let's the user pick from existing identities. 4. Select / Deselect all faces that match 5. "Match" to bind the faces to the identity 6. Create new Identity */ function createFace(faceId, photoId, selectable) { var div = document.createElement("div"); div.classList.add("face"); div.setAttribute("photo-id", photoId); div.setAttribute("face-id", faceId); div.style.backgroundImage = "url(face-data/" + (faceId % 100) + "/" + faceId + "-original.png)"; div.addEventListener("click", (event) => { if (event.shiftKey) { /* identities */ let faceId = parseInt(event.currentTarget.getAttribute("face-id")); if (faceId) { window.location.href = "identities.html?id=" + faceId; } else { alert("No face id mapped to face."); } } else if (!selectable || event.ctrlKey) { /* face-explorer */ let photoId = parseInt(event.currentTarget.getAttribute("photo-id")); if (photoId) { window.open("face-explorer.html?" + photoId, "photo-" + photoId); } else { alert("No photo id mapped to face."); } } else { if (event.currentTarget.hasAttribute("disabled")) { event.currentTarget.removeAttribute("disabled"); } else { event.currentTarget.setAttribute("disabled", ""); } } }); return div; } function loadFace(id) { const faceEditorBlock = document.getElementById("face-editor"); faceEditorBlock.innerHTML = ""; window.fetch("api/v1/faces" + (id ? "/" + id : "")).then(res => res.json()).then((faces) => { if (faces.length == 0) { return; } const face = faces[0]; if (face.identityId) { getIdentity(face.identityId); } else { getIdentities(face.id); } const editor = document.createElement("div"); editor.classList.add("editor"); editor.appendChild(createFace(face.id, face.photoId)); let row = document.createElement("div"), label = document.createElement("div"), a = document.createElement("a"); row.classList.add("editor-row"); label.textContent = "View photo: "; row.appendChild(label); a.setAttribute("target", "photo-" + face.photoId); a.href = "face-explorer.html?" + face.photoId; a.textContent = "photo " + face.photoId; row.appendChild(a); editor.appendChild(row); row = document.createElement("div"); row.classList.add("editor-row"); let message = "Unassigned related faces: "; if (face.relatedFaces.length > 0) { message += face.relatedFaces.length + " related (tap to deselect)"; } else { message += "No matches found."; } row.textContent = message; editor.appendChild(row); row = document.createElement("div"); row.classList.add("editor-row"); row.classList.add("related-faces"); editor.appendChild(row); let button = document.createElement("button"); button.textContent = "show more"; button.addEventListener("click", (event) => { showMore(row, face.relatedFaces); }); editor.appendChild(button); showMore(row, face.relatedFaces); faceEditorBlock.appendChild(editor); }); } function showMore(row, faces) { let index = row.querySelectorAll(".face").length, max = Math.min(faces.length - index, 9); let more = index + max < faces.length; for (let i = index; i < index + max; i++) { let relation = faces[i]; const distance = document.createElement("div"), facePhoto = createFace(relation.faceId, relation.photoId, true); distance.classList.add("distance"); distance.textContent = relation.distance.toFixed(2); facePhoto.appendChild(distance); row.appendChild(facePhoto); } if (!more) { row.parentElement.querySelector("button").style.display = "none"; } } function getIdentity(identityId) { const identitiesBlock = document.getElementById("identities"); identitiesBlock.innerHTML = ""; window.fetch("api/v1/identities/" + identityId).then(res => res.json()).then((identities) => { identities.forEach((identity) => { const block = createIdentityBlock(identity, true), button = createUseThisIdentityButton(identity); block.insertBefore(button, block.firstChild); identitiesBlock.appendChild(block); }) }); } function getIdentities(faceId) { const identitiesBlock = document.getElementById("identities"); identitiesBlock.innerHTML = ""; const search = faceId ? "?withScore=" + faceId : ""; window.fetch("api/v1/identities" + search).then(res => res.json()).then((identities) => { identities.sort((a, b) => { if (a.lastName == b.lastName) { return a.firstName.localeCompare(b.firstName); } return a.lastName.localeCompare(b.lastName); }); identities.forEach((identity) => { const block = createIdentityBlock(identity), button = createUseThisIdentityButton(identity); block.insertBefore(button, block.firstChild); identitiesBlock.appendChild(block); }); }); } function createUseThisIdentityButton(identity) { const button = document.createElement("button"); button.textContent = "Use this identity"; button.addEventListener("click", (event) => { const object = { faces: [] }; Array.prototype.forEach.call(document.body.querySelectorAll("#face-editor .face"), (face) => { if (!face.hasAttribute("disabled")) { object.faces.push(face.getAttribute("face-id")); } }); window.fetch("api/v1/identities/faces/add/" + identity.id, { method: "PUT", headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify(object) }).then(res => res.json()).then((identities) => { console.log("Updated identity: ", identities[0]); const face = document.body.querySelector("#face-editor .face"); loadFace(parseInt(face.getAttribute("face-id"))); }); }); return button; } function createIdentityBlock(identity, nolimit) { const block = document.createElement("div"); block.classList.add("block"); let div = document.createElement("div"); div.textContent = identity.name; block.appendChild(div); div = document.createElement("div"); div.textContent = "Related faces " + identity.relatedFaces.length; block.appendChild(div); identity.relatedFaces.sort((a, b) => { return a.distance - b.distance; }); div = document.createElement("div"); div.classList.add("face-block"); if (nolimit) { div.classList.add("face-block-nolimit"); } let minDistance = { distance: 1 }; for (let i = 0; i < identity.relatedFaces.length; i++) { if (!nolimit && i >= 4) { break; } const target = identity.relatedFaces[i]; const facePhoto = createFace(target.faceId, target.photoId), distance = document.createElement("div"); if (target.distance < minDistance.distance) { minDistance.distance = target.distance; minDistance.photoId = target.photoId; minDistance.faceId = target.faceId; } distance.classList.add("distance"); distance.textContent = target.distance.toFixed(2); facePhoto.appendChild(distance); div.appendChild(facePhoto); } if (minDistance.distance < 0.45) { const bestMatch = document.getElementById("best-match"); const distance = document.createElement("div"), facePhoto = createFace(minDistance.faceId, minDistance.photoId); distance.classList.add("distance"); distance.textContent = minDistance.distance.toFixed(2); facePhoto.appendChild(distance); bestMatch.innerHTML = ""; bestMatch.appendChild(facePhoto); } else { const bestMatch = document.getElementById("best-match"); bestMatch.innerHTML = "No best guess for match."; } block.appendChild(div); return block; } function createNewIdenityEditor() { const block = document.createElement("div"); block.classList.add("block"); const editor = document.createElement("div"); editor.classList.add("editor"); const button = document.createElement("button"); button.textContent = "New Identity"; editor.appendChild(button); [ "lastName", "firstName", "middleName", "name" ].forEach((key) => { const row = document.createElement("div"), left = document.createElement("div"), right = document.createElement("input"); row.classList.add("editor-row"); left.classList.add("key"); left.textContent = key; row.appendChild(left); row.appendChild(right); editor.appendChild(row); }); button.addEventListener("click", (event) => { const rows = event.currentTarget.parentElement.querySelectorAll(".editor-row"), object = {}; Array.prototype.forEach.call(rows, (row) => { object[row.firstChild.textContent] = row.lastChild.value; }); object.faces = []; Array.prototype.forEach.call(document.body.querySelectorAll("#face-editor .face"), (face) => { if (!face.hasAttribute("disabled")) { object.faces.push(face.getAttribute("face-id")); } }); console.log(object); window.fetch("api/v1/identities", { method: "POST", headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify(object) }).then(res => res.json()).then((identities) => { console.log("New identity: ", identities[0]); let inputs = editor.querySelectorAll("input"); Array.prototype.forEach.call(inputs, (el) => { if (el.value) { el.value = ""; } }); const face = document.body.querySelector("#face-editor .face"); loadFace(parseInt(face.getAttribute("face-id"))); }); }); block.appendChild(editor); return block; }