diff options
| -rw-r--r-- | client/actions.js | 2 | ||||
| -rw-r--r-- | client/applet.js | 3 | ||||
| -rw-r--r-- | client/common/index.js | 2 | ||||
| -rw-r--r-- | client/common/upload.helpers.js (renamed from client/faceSearch/upload.js) | 237 | ||||
| -rw-r--r-- | client/common/uploadImage.component.js | 46 | ||||
| -rw-r--r-- | client/faceAnalysis/faceAnalysis.actions.js | 75 | ||||
| -rw-r--r-- | client/faceAnalysis/faceAnalysis.container.js | 24 | ||||
| -rw-r--r-- | client/faceAnalysis/faceAnalysis.query.js | 82 | ||||
| -rw-r--r-- | client/faceAnalysis/faceAnalysis.reducer.js | 32 | ||||
| -rw-r--r-- | client/faceAnalysis/faceAnalysis.result.js | 127 | ||||
| -rw-r--r-- | client/faceAnalysis/index.js | 5 | ||||
| -rw-r--r-- | client/faceSearch/faceSearch.query.js | 42 | ||||
| -rw-r--r-- | client/store.js | 2 | ||||
| -rw-r--r-- | client/types.js | 4 | ||||
| -rw-r--r-- | client/util.js | 17 | ||||
| -rw-r--r-- | package-lock.json | 167 | ||||
| -rw-r--r-- | package.json | 3 |
17 files changed, 669 insertions, 201 deletions
diff --git a/client/actions.js b/client/actions.js index 2be8229d..7007eb76 100644 --- a/client/actions.js +++ b/client/actions.js @@ -1,7 +1,9 @@ +import * as faceAnalysis from './faceAnalysis/faceAnalysis.actions' import * as faceSearch from './faceSearch/faceSearch.actions' import * as nameSearch from './nameSearch/nameSearch.actions' export { + faceAnalysis, faceSearch, nameSearch, } diff --git a/client/applet.js b/client/applet.js index 80d40657..25291401 100644 --- a/client/applet.js +++ b/client/applet.js @@ -1,12 +1,15 @@ import React, { Component } from 'react' import { Container as FaceSearchContainer } from './faceSearch' +import { Container as FaceAnalysisContainer } from './faceAnalysis' import { Container as NameSearchContainer } from './nameSearch' export default class Applet extends Component { render() { // console.log(this.props) switch (this.props.payload.cmd) { + case 'face_analysis': + return <FaceAnalysisContainer {...this.props} /> case 'face_search': return <FaceSearchContainer {...this.props} /> case 'name_search': diff --git a/client/common/index.js b/client/common/index.js index cfb34b32..cbd3166e 100644 --- a/client/common/index.js +++ b/client/common/index.js @@ -3,6 +3,7 @@ import DetectionBoxes from './detectionBoxes.component' import DetectionList from './detectionList.component' // import Header from './header.component' import Loader from './loader.component' +import UploadImage from './uploadImage.component' import Sidebar from './sidebar.component' import Gate from './gate.component' import Video from './video.component' @@ -12,6 +13,7 @@ import './common.css' export { Sidebar, Loader, + UploadImage, Gate, TableObject, TableArray, diff --git a/client/faceSearch/upload.js b/client/common/upload.helpers.js index f18bdce6..5a041fd4 100644 --- a/client/faceSearch/upload.js +++ b/client/common/upload.helpers.js @@ -1,119 +1,26 @@ -const MAX_SIDE = 300 +import ExifReader from 'exifreader' -function render(img){ - var resized = renderToCanvas(img, { correctOrientation: true }) - var canvas = document.createElement('canvas') // document.querySelector('#user_photo_canvas') - ctx = canvas.getContext('2d') - ctx.fillStyle = 'black' - ctx.fillRect(0, 0, MAX_SIDE, MAX_SIDE) - var x_offset = (MAX_SIDE - resized.width) / 2 - var y_offset = (MAX_SIDE - resized.height) / 2 - ctx.drawImage(resized, x_offset, y_offset) - return canvas -} -function renderToCanvas(img, options) { - if (!img) return - options = options || {} - - // Canvas max size for any side - var maxSize = MAX_SIDE - var canvas = document.createElement('canvas') - var ctx = canvas.getContext('2d') - var initialScale = options.scale || 1 - // Scale to needed to constrain canvas to max size - var scale = getScale(img.width * initialScale, img.height * initialScale, maxSize, maxSize, true) - // Still need to apply the user defined scale - scale *= initialScale - var width = canvas.width = Math.round(img.width * scale) - var height = canvas.height = Math.round(img.height * scale) - var correctOrientation = options.correctOrientation - var jpeg = !!img.src.match(/data:image\/jpeg|\.jpeg$|\.jpg$/i) - var hasDataURI = !!img.src.match(/^data:/) - - ctx.save() - - // Can only correct orientation on JPEGs represented as dataURIs - // for the time being - if (correctOrientation && jpeg && hasDataURI) { - applyOrientationCorrection(canvas, ctx, img.src) - } - // Resize image if too large - if (scale !== 1) { - ctx.scale(scale, scale) - } - - ctx.drawImage(img, 0, 0) - ctx.restore() +export const MAX_SIDE = 300 - return canvas -} - -function getScale(width, height, viewportWidth, viewportHeight, fillViewport) { - fillViewport = !!fillViewport - var landscape = (width / height) > (viewportWidth / viewportHeight) - if (landscape) { - if (fillViewport) { - return fitVertical() - } else if (width > viewportWidth) { - return fitHorizontal() - } - } else { - if (fillViewport) { - return fitHorizontal() - } else if (height > viewportHeight) { - return fitVertical() - } - } - return 1 - - function fitHorizontal() { - return viewportWidth / width - } - - function fitVertical() { - return viewportHeight / height - } -} - -function applyOrientationCorrection(canvas, ctx, uri) { - var orientation = getOrientation(uri) - // Only apply transform if there is some non-normal orientation - if (orientation && orientation !== 1) { - var transform = orientationToTransform[orientation] - var rotation = transform.rotation - var mirror = transform.mirror - var flipAspect = rotation === 90 || rotation === 270 - if (flipAspect) { - // Fancy schmancy swap algo - canvas.width = canvas.height + canvas.width - canvas.height = canvas.width - canvas.height - canvas.width -= canvas.height - } - if (rotation > 0) { - applyRotation(canvas, ctx, rotation) - } - } -} - -function applyRotation(canvas, ctx, deg) { - var radians = deg * (Math.PI / 180) - if (deg === 90) { - ctx.translate(canvas.width, 0) - } else if (deg === 180) { - ctx.translate(canvas.width, canvas.height) - } else if (deg == 270) { - ctx.translate(0, canvas.height) +function base64ToUint8Array(string, start, finish) { + start = start || 0 + finish = finish || string.length + // atob that shit + const binary = atob(string) + const buffer = new Uint8Array(binary.length) + for (let i = start; i < finish; i++) { + buffer[i] = binary.charCodeAt(i) } - ctx.rotate(radians) + return buffer } -function getOrientation (uri) { - var exif = new ExifReader +function getOrientation(uri) { + const exif = new ExifReader() // Split off the base64 data - var base64String = uri.split(',')[1] + const base64String = uri.split(',')[1] // Read off first 128KB, which is all we need to // get the EXIF data - var arr = base64ToUint8Array(base64String, 0, Math.pow(2, 17)) + const arr = base64ToUint8Array(base64String, 0, 2 ** 17) try { exif.load(arr.buffer) return exif.getTagValue('Orientation') @@ -122,16 +29,16 @@ function getOrientation (uri) { } } -function base64ToUint8Array(string, start, finish) { - var start = start || 0 - var finish = finish || string.length - // atob that shit - var binary = atob(string) - var buffer = new Uint8Array(binary.length) - for (var i = start; i < finish; i++) { - buffer[i] = binary.charCodeAt(i) +function applyRotation(canvas, ctx, deg) { + const radians = deg * (Math.PI / 180) + if (deg === 90) { + ctx.translate(canvas.width, 0) + } else if (deg === 180) { + ctx.translate(canvas.width, canvas.height) + } else if (deg === 270) { + ctx.translate(0, canvas.height) } - return buffer + ctx.rotate(radians) } /** @@ -141,7 +48,7 @@ function base64ToUint8Array(string, start, finish) { * Derived from: * http://www.daveperrett.com/articles/2012/07/28/exif-orientation-handling-is-a-ghetto/ */ -var orientationToTransform = { +const orientationToTransform = { 1: { rotation: 0, mirror: false }, 2: { rotation: 0, mirror: true }, 3: { rotation: 180, mirror: false }, @@ -152,3 +59,97 @@ var orientationToTransform = { 8: { rotation: 270, mirror: false } } +function applyOrientationCorrection(canvas, ctx, uri) { + const orientation = getOrientation(uri) + // Only apply transform if there is some non-normal orientation + if (orientation && orientation !== 1) { + const transform = orientationToTransform[orientation] + const { rotation } = transform + const flipAspect = rotation === 90 || rotation === 270 + if (flipAspect) { + // Fancy schmancy swap algo + canvas.width = canvas.height + canvas.width + canvas.height = canvas.width - canvas.height + canvas.width -= canvas.height + } + if (rotation > 0) { + applyRotation(canvas, ctx, rotation) + } + } +} + +function getScale(width, height, viewportWidth, viewportHeight, fillViewport) { + function fitHorizontal() { + return viewportWidth / width + } + function fitVertical() { + return viewportHeight / height + } + fillViewport = !!fillViewport + const landscape = (width / height) > (viewportWidth / viewportHeight) + if (landscape) { + if (fillViewport) { + return fitVertical() + } + if (width > viewportWidth) { + return fitHorizontal() + } + } else { + if (fillViewport) { + return fitHorizontal() + } + if (height > viewportHeight) { + return fitVertical() + } + } + return 1 +} + +export function renderToCanvas(img, options) { + if (!img) return null + options = options || {} + + // Canvas max size for any side + const maxSize = MAX_SIDE + const canvas = document.createElement('canvas') + const ctx = canvas.getContext('2d') + const initialScale = options.scale || 1 + // Scale to needed to constrain canvas to max size + let scale = getScale(img.width * initialScale, img.height * initialScale, maxSize, maxSize, true) + // Still need to apply the user defined scale + scale *= initialScale + canvas.width = Math.round(img.width * scale) + canvas.height = Math.round(img.height * scale) + const { correctOrientation } = options + const jpeg = !!img.src.match(/data:image\/jpeg|\.jpeg$|\.jpg$/i) + const hasDataURI = !!img.src.match(/^data:/) + + ctx.save() + + // Can only correct orientation on JPEGs represented as dataURIs + // for the time being + if (correctOrientation && jpeg && hasDataURI) { + applyOrientationCorrection(canvas, ctx, img.src) + } + // Resize image if too large + if (scale !== 1) { + ctx.scale(scale, scale) + } + + ctx.drawImage(img, 0, 0) + ctx.restore() + + return canvas +} + +export function renderThumbnail(img) { + const resized = renderToCanvas(img, { correctOrientation: true }) + const canvas = document.createElement('canvas') // document.querySelector('#user_photo_canvas') + const ctx = canvas.getContext('2d') + ctx.fillStyle = 'black' + ctx.fillRect(0, 0, MAX_SIDE, MAX_SIDE) + const xOffset = (MAX_SIDE - resized.width) / 2 + const yOffset = (MAX_SIDE - resized.height) / 2 + ctx.drawImage(resized, xOffset, yOffset) + return canvas +} diff --git a/client/common/uploadImage.component.js b/client/common/uploadImage.component.js new file mode 100644 index 00000000..eb8cc60f --- /dev/null +++ b/client/common/uploadImage.component.js @@ -0,0 +1,46 @@ +import React, { Component } from 'react' + +import { renderThumbnail } from './upload.helpers' + +export default class UploadImageComponent extends Component { + upload(e) { + const files = e.dataTransfer ? e.dataTransfer.files : e.target.files + let i + let file + for (i = 0; i < files.length; i++) { + file = files[i] + if (file && file.type.match('image.*')) break + } + if (!file) return + const fr = new FileReader() + fr.onload = fileReaderEvent => { + fr.onload = null + const img = new Image() + img.onload = () => { + img.onload = null + this.resizeAndUpload(img) + } + img.src = fileReaderEvent.result + } + fr.readAsDataURL(files[0]) + } + + resizeAndUpload(img) { + const canvas = renderThumbnail(img) + canvas.toBlob(blob => { + this.props.onUpload(blob) + }, 'image/jpeg', 80) + } + + render() { + return ( + <input + type="file" + name="img" + accept="image/*" + onChange={this.upload.bind(this)} + required + /> + ) + } +} diff --git a/client/faceAnalysis/faceAnalysis.actions.js b/client/faceAnalysis/faceAnalysis.actions.js new file mode 100644 index 00000000..90d7156f --- /dev/null +++ b/client/faceAnalysis/faceAnalysis.actions.js @@ -0,0 +1,75 @@ +// import fetchJsonp from 'fetch-jsonp' +import * as types from '../types' +// import { hashPath } from '../util' +import { store } from '../store' +import { get, post } from '../util' +// import querystring from 'query-string' + +// urls + +const url = { + upload: (dataset) => process.env.API_HOST + '/api/dataset/' + dataset + '/face', +} +export const publicUrl = { +} + +// standard loading events + +const loading = (tag, offset) => ({ + type: types.faceAnalysis.loading, + tag, + offset +}) +const loaded = (tag, data, offset = 0) => ({ + type: types.faceAnalysis.loaded, + tag, + data, + offset +}) +const error = (tag, err) => ({ + type: types.faceAnalysis.error, + tag, + err +}) + +// search UI functions + +export const updateOptions = opt => dispatch => { + dispatch({ type: types.faceAnalysis.update_options, opt }) +} + +// API functions + +export const upload = (payload, file) => dispatch => { + // const { options } = store.getState().faceAnalysis + const tag = 'result' + const fd = new FormData() + fd.append('query_img', file) + // fd.append('limit', options.perPage) + // if (!query) { + dispatch(loading(tag)) + // } + post(url.upload(payload.dataset), fd) + .then(data => { + dispatch(loaded(tag, data)) + }) + .catch(err => dispatch(error(tag, err))) +} + +// task polling + +const POLL_DELAY = 500 +let pollTimeout = null + +export const poll = (payload, taskURL) => dispatch => { + const tag = 'poll' + clearTimeout(pollTimeout) + dispatch(loading(tag)) + get(taskURL) + .then(data => { + dispatch(loaded(tag, data)) + // check if complete + pollTimeout = setTimeout(() => poll(payload, taskURL), POLL_DELAY) + }) + .catch(err => dispatch(error(tag, err))) +} diff --git a/client/faceAnalysis/faceAnalysis.container.js b/client/faceAnalysis/faceAnalysis.container.js new file mode 100644 index 00000000..a86bcaa4 --- /dev/null +++ b/client/faceAnalysis/faceAnalysis.container.js @@ -0,0 +1,24 @@ +import React, { Component } from 'react' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' + +import * as actions from './faceAnalysis.actions' + +import FaceAnalysisQuery from './faceAnalysis.query' +import FaceAnalysisResult from './faceAnalysis.result' + +class FaceAnalysisContainer extends Component { + render() { + const { payload } = this.props + // console.log(payload) + return ( + <div className='searchContainer'> + <FaceAnalysisQuery payload={payload} /> + <FaceAnalysisResult payload={payload} /> + </div> + ) + } +} + + +export default FaceAnalysisContainer diff --git a/client/faceAnalysis/faceAnalysis.query.js b/client/faceAnalysis/faceAnalysis.query.js new file mode 100644 index 00000000..86dbe1ae --- /dev/null +++ b/client/faceAnalysis/faceAnalysis.query.js @@ -0,0 +1,82 @@ +import React, { Component } from 'react' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' + +import { Loader } from '../common' +import * as actions from './faceAnalysis.actions' + +// function parse_bbox(s) { +// // "BBox: (77,86), (166, 177), width:89, height:91" +// try { +// const [x, y, w, h, width, height] = s.replace(/\D/g, ' ').replace(/\s+/g, ' ').trim().split(' ') +// return { x, y, w, h } +// } +// } + +class FaceAnalysisQuery extends Component { + state = { + image: null + } + + upload(blob) { + this.props.actions.upload(this.props.payload, blob) + } + + render() { + const { result } = this.props + const { image } = this.state + console.log(result) + const style = {} + if (image) { + style.backgroundImage = 'url(' + image + ')' + style.backgroundSize = 'cover' + style.opacity = 1 + } + return ( + <div className='query row'> + <div className='uploadContainer'> + <div style={style}> + {image ? null : <img src="/assets/img/icon_camera.svg" />} + <input + type="file" + name="img" + accept="image/*" + onChange={this.upload.bind(this)} + required + /> + </div> + {result.loading && ( + <div className='loading' style={style}> + <Loader /> + </div> + )} + </div> + <div className='cta'> + <h2>Face Analysis</h2> + <p> + {'Put yourself under the microscope of various facial recognition algorithms. See what can be determined from a photo.'} + </p> + <ol> + <li>Upload a photo of yourself</li> + <li>{'Your search data is never stored and immediately cleared '} + {'once you leave this page.'}</li> + </ol> + <p> + Read more about <a href='/about/privacy/'>privacy</a>. + </p> + </div> + </div> + ) + } +} + +const mapStateToProps = state => ({ + result: state.faceAnalysis.result, + options: state.faceAnalysis.options, +}) + +const mapDispatchToProps = dispatch => ({ + actions: bindActionCreators({ ...actions }, dispatch), +}) + +export default connect(mapStateToProps, mapDispatchToProps)(FaceAnalysisQuery) diff --git a/client/faceAnalysis/faceAnalysis.reducer.js b/client/faceAnalysis/faceAnalysis.reducer.js new file mode 100644 index 00000000..d8e914ab --- /dev/null +++ b/client/faceAnalysis/faceAnalysis.reducer.js @@ -0,0 +1,32 @@ +import * as types from '../types' + +const initialState = () => ({ + query: {}, + result: {}, + loading: false, +}) + +export default function faceAnalysisReducer(state = initialState(), action) { + switch (action.type) { + case types.faceAnalysis.loading: + return { + ...state, + [action.tag]: { loading: true }, + } + + case types.faceAnalysis.loaded: + return { + ...state, + [action.tag]: action.data, + } + + case types.faceAnalysis.error: + return { + ...state, + [action.tag]: { error: action.err }, + } + + default: + return state + } +} diff --git a/client/faceAnalysis/faceAnalysis.result.js b/client/faceAnalysis/faceAnalysis.result.js new file mode 100644 index 00000000..b825a0cb --- /dev/null +++ b/client/faceAnalysis/faceAnalysis.result.js @@ -0,0 +1,127 @@ +import React, { Component } from 'react' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' +import { courtesyS } from '../util' + +import * as actions from './faceAnalysis.actions' +import { Loader } from '../common' + +const errors = { + bbox: ( + <div> + <h2>No face found</h2> + {"Sorry, we didn't detect a face in that image. "} + {"Please choose an image where the face is large and clear."} + </div> + ), + nomatch: ( + <div> + <h2>{"You're clear"}</h2> + {"No images in this dataset match your face. We show only matches above 70% probability."} + </div> + ), + error: ( + <div> + <h2>{"No matches found"}</h2> + {"Sorry, an error occured."} + </div> + ), + bad_dataset: ( + <div> + <h2>{""}</h2> + {""} + </div> + ), + not_an_image: ( + <div> + <h2>{"Not an image"}</h2> + {"Sorry, the file you uploaded was not recognized as an image. Please upload a JPG or PNG image and try again."} + </div> + ), +} + +class FaceAnalysisResult extends Component { + render() { + const { dataset } = this.props.payload + const { query, distances, results, loading, error } = this.props.result + console.log(this.props.result) + if (loading) { + return ( + <div className='result'> + <div> + <Loader /><br /> + <h2>Searching...</h2> + </div> + </div> + ) + } + if (error) { + // console.log(error) + let errorMessage = errors[error] || errors.error + return ( + <div className='result'>{errorMessage}</div> + ) + } + if (!results) { + return <div className='result'></div> + } + if (!results.length) { + return ( + <div className='result'>{errors.nomatch}</div> + ) + } + const els = results.map((result, i) => { + const distance = distances[i] + const { uuid } = result.file_record + const { x, y, w, h } = result.face_roi + const { fullname, gender, description, images } = result.identity + const bbox = { + left: (100 * x) + '%', + top: (100 * y) + '%', + width: (100 * w) + '%', + height: (100 * h) + '%', + } + // console.log(bbox) + return ( + <div key={i}> + <div className='img'> + <img src={'https://megapixels.nyc3.digitaloceanspaces.com/v1/media/' + dataset + '/' + uuid + '.jpg'} /> + <div className='bbox' style={bbox} /> + </div> + {fullname} {'('}{gender}{')'}<br/> + {description}<br/> + {courtesyS(images, 'image')}{' in dataset'}<br /> + {Math.round((1 - distance) * 100)}{'% match'} + </div> + ) + }) + + return ( + <div className='result'> + <div className="about"> + <h2>Did we find you?</h2> + {'These faces matched images in the '} + <b><tt>{dataset}</tt></b> + {' dataset with over 70% probability.'} + <br /> + <small>Query took {query.timing.toFixed(2)} seconds</small> + </div> + <div className='results'> + {els} + </div> + </div> + ) + } +} + +const mapStateToProps = state => ({ + query: state.faceAnalysis.query, + result: state.faceAnalysis.result, + options: state.faceAnalysis.options, +}) + +const mapDispatchToProps = dispatch => ({ + actions: bindActionCreators({ ...actions }, dispatch), +}) + +export default connect(mapStateToProps, mapDispatchToProps)(FaceAnalysisResult) diff --git a/client/faceAnalysis/index.js b/client/faceAnalysis/index.js new file mode 100644 index 00000000..efa39ded --- /dev/null +++ b/client/faceAnalysis/index.js @@ -0,0 +1,5 @@ +import Container from './faceAnalysis.container' + +export { + Container, +} diff --git a/client/faceSearch/faceSearch.query.js b/client/faceSearch/faceSearch.query.js index 9f778ca0..2d140813 100644 --- a/client/faceSearch/faceSearch.query.js +++ b/client/faceSearch/faceSearch.query.js @@ -2,7 +2,7 @@ import React, { Component } from 'react' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' -import { Loader } from '../common' +import { Loader, UploadImage } from '../common' import * as actions from './faceSearch.actions' // function parse_bbox(s) { @@ -18,23 +18,8 @@ class FaceSearchQuery extends Component { image: null } - upload(e) { - const { payload } = this.props - const files = e.dataTransfer ? e.dataTransfer.files : e.target.files - let i - let file - for (i = 0; i < files.length; i++) { - file = files[i] - if (file && file.type.match('image.*')) break - } - if (!file) return - const fr = new FileReader() - fr.onload = () => { - fr.onload = null - this.setState({ image: fr.result }) - } - fr.readAsDataURL(files[0]) - this.props.actions.upload(this.props.payload, file) + upload(blob) { + this.props.actions.upload(this.props.payload, blob) } render() { @@ -50,22 +35,15 @@ class FaceSearchQuery extends Component { return ( <div className='query row'> <div className='uploadContainer'> - {result.loading ? - <div className='loading' style={style}> - <Loader /> - </div> - : <div style={style}> + <div style={style}> {image ? null : <img src="/assets/img/icon_camera.svg" />} - - <input - type="file" - name="img" - accept="image/*" - onChange={this.upload.bind(this)} - required - /> + <UploadImage onUpload={this.upload.bind(this)} /> </div> - } + {result.loading && ( + <div className='loading' style={style}> + <Loader /> + </div> + )} </div> <div className='cta'> <h2>Search by Image</h2> diff --git a/client/store.js b/client/store.js index 13612f2d..e896bc58 100644 --- a/client/store.js +++ b/client/store.js @@ -1,10 +1,12 @@ import { applyMiddleware, compose, combineReducers, createStore } from 'redux' import thunk from 'redux-thunk' +import faceAnalysisReducer from './faceAnalysis/faceAnalysis.reducer' import faceSearchReducer from './faceSearch/faceSearch.reducer' import nameSearchReducer from './nameSearch/nameSearch.reducer' const rootReducer = combineReducers({ + faceAnalysis: faceAnalysisReducer, faceSearch: faceSearchReducer, nameSearch: nameSearchReducer, }) diff --git a/client/types.js b/client/types.js index fb1fbe30..2d35ec36 100644 --- a/client/types.js +++ b/client/types.js @@ -6,6 +6,10 @@ export const tagAsType = (type, names) => ( }, {}) ) +export const faceAnalysis = tagAsType('faceAnalysis', [ + 'loading', 'loaded', 'error', 'update_options', +]) + export const faceSearch = tagAsType('faceSearch', [ 'loading', 'loaded', 'error', 'update_options', ]) diff --git a/client/util.js b/client/util.js index f181ad0f..d0db0d98 100644 --- a/client/util.js +++ b/client/util.js @@ -82,6 +82,21 @@ export const preloadImage = opt => { /* AJAX */ +export const get = (uri, data) => { + let headers = { + Accept: 'application/json, application/xml, text/play, text/html, *.*', + } + let opt = { + method: 'GET', + body: data, + headers, + // credentials: 'include', + } + // console.log(headers) + // headers['X-CSRFToken'] = csrftoken + return fetch(uri, opt).then(res => res.json()) +} + export const post = (uri, data) => { let headers if (data instanceof FormData) { @@ -99,7 +114,7 @@ export const post = (uri, data) => { method: 'POST', body: data, headers, - credentials: 'include', + // credentials: 'include', } // console.log(headers) // headers['X-CSRFToken'] = csrftoken diff --git a/package-lock.json b/package-lock.json index a42dca34..da0dfcae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2007,9 +2007,9 @@ } }, "connect-history-api-fallback": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz", - "integrity": "sha1-sGhzk0vF40T+9hGhlqb6rgruAVo=", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", "dev": true }, "connected-react-router": { @@ -3427,6 +3427,11 @@ "strip-eof": "^1.0.0" } }, + "exifreader": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/exifreader/-/exifreader-2.5.0.tgz", + "integrity": "sha512-jxS+cSjalvtF4Ga1D0G1YbVwPBOUYIGMuVbUNfor1+5CSc4h2FBvFC59HSkpPTqiHnA9zF0W5BW8eV3CjmklcQ==" + }, "exit-hook": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", @@ -3493,7 +3498,7 @@ "dependencies": { "array-flatten": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", "dev": true } @@ -3628,7 +3633,7 @@ }, "finalhandler": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", "dev": true, "requires": { @@ -3681,9 +3686,9 @@ } }, "follow-redirects": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", - "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.6.1.tgz", + "integrity": "sha512-t2JCjbzxQpWvbhts3l6SH1DKzSrx8a+SsaVf4h6bG4kOXUuPYS/kg2Lr4gQSb7eemaHqJkOThF1BGyjlUkO1GQ==", "dev": true, "requires": { "debug": "=3.1.0" @@ -4400,7 +4405,7 @@ }, "globby": { "version": "6.1.0", - "resolved": "http://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", "dev": true, "requires": { @@ -4413,7 +4418,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } @@ -4425,9 +4430,9 @@ "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" }, "handle-thing": { - "version": "1.2.5", - "resolved": "http://registry.npmjs.org/handle-thing/-/handle-thing-1.2.5.tgz", - "integrity": "sha1-/Xqtcmvxpf0W38KbL3pmAdJxOcQ=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.0.tgz", + "integrity": "sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==", "dev": true }, "has": { @@ -4715,7 +4720,7 @@ }, "http-errors": { "version": "1.6.3", - "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, "requires": { @@ -4744,7 +4749,7 @@ }, "http-proxy-middleware": { "version": "0.18.0", - "resolved": "http://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", "integrity": "sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q==", "dev": true, "requires": { @@ -4830,7 +4835,7 @@ }, "is-accessor-descriptor": { "version": "0.1.6", - "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { @@ -4850,7 +4855,7 @@ }, "is-data-descriptor": { "version": "0.1.4", - "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { @@ -5828,7 +5833,7 @@ }, "media-typer": { "version": "0.3.0", - "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "dev": true }, @@ -6712,7 +6717,7 @@ "dependencies": { "async": { "version": "1.5.2", - "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", "dev": true } @@ -8338,32 +8343,75 @@ "dev": true }, "spdy": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-3.4.7.tgz", - "integrity": "sha1-Qv9B7OXMD5mjpsKKq7c/XDsDrLw=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.0.tgz", + "integrity": "sha512-ot0oEGT/PGUpzf/6uk4AWLqkq+irlqHXkrdbk51oWONh3bxQmBuljxPNl66zlRRcIJStWq0QkLUCPOPjgjvU0Q==", "dev": true, "requires": { - "debug": "^2.6.8", - "handle-thing": "^1.2.5", + "debug": "^4.1.0", + "handle-thing": "^2.0.0", "http-deceiver": "^1.2.7", - "safe-buffer": "^5.0.1", "select-hose": "^2.0.0", - "spdy-transport": "^2.0.18" + "spdy-transport": "^3.0.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } } }, "spdy-transport": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-2.1.1.tgz", - "integrity": "sha512-q7D8c148escoB3Z7ySCASadkegMmUZW8Wb/Q1u0/XBgDKMO880rLQDj8Twiew/tYi7ghemKUi/whSYOwE17f5Q==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", "dev": true, "requires": { - "debug": "^2.6.8", - "detect-node": "^2.0.3", + "debug": "^4.1.0", + "detect-node": "^2.0.4", "hpack.js": "^2.1.6", - "obuf": "^1.1.1", - "readable-stream": "^2.2.9", - "safe-buffer": "^5.0.1", - "wbuf": "^1.7.2" + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "readable-stream": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.1.1.tgz", + "integrity": "sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } } }, "split-string": { @@ -9902,9 +9950,9 @@ } }, "webpack-dev-server": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.1.10.tgz", - "integrity": "sha512-RqOAVjfqZJtQcB0LmrzJ5y4Jp78lv9CK0MZ1YJDTaTmedMZ9PU9FLMQNrMCfVu8hHzaVLVOJKBlGEHMN10z+ww==", + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.1.14.tgz", + "integrity": "sha512-mGXDgz5SlTxcF3hUpfC8hrQ11yhAttuUQWf1Wmb+6zo3x6rb7b9mIfuQvAPLdfDRCGRGvakBWHdHOa0I9p/EVQ==", "dev": true, "requires": { "ansi-html": "0.0.7", @@ -9926,12 +9974,14 @@ "portfinder": "^1.0.9", "schema-utils": "^1.0.0", "selfsigned": "^1.9.1", + "semver": "^5.6.0", "serve-index": "^1.7.2", "sockjs": "0.3.19", "sockjs-client": "1.3.0", - "spdy": "^3.4.1", + "spdy": "^4.0.0", "strip-ansi": "^3.0.0", "supports-color": "^5.1.0", + "url": "^0.11.0", "webpack-dev-middleware": "3.4.0", "webpack-log": "^2.0.0", "yargs": "12.0.2" @@ -10083,13 +10133,13 @@ } }, "execa": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", - "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, "requires": { "cross-spawn": "^6.0.0", - "get-stream": "^3.0.0", + "get-stream": "^4.0.0", "is-stream": "^1.1.0", "npm-run-path": "^2.0.0", "p-finally": "^1.0.0", @@ -10141,7 +10191,7 @@ }, "is-accessor-descriptor": { "version": "0.1.6", - "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { @@ -10161,7 +10211,7 @@ }, "is-data-descriptor": { "version": "0.1.4", - "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { @@ -10257,6 +10307,15 @@ } } }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, "glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -10402,12 +10461,12 @@ } }, "os-locale": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.0.1.tgz", - "integrity": "sha512-7g5e7dmXPtzcP4bgsZ8ixDVqA7oWYuEz4lOSujeWyliPai4gfVDiFIcwBg3aGCPnmSGfzOKTK3ccPn0CKv3DBw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", "dev": true, "requires": { - "execa": "^0.10.0", + "execa": "^1.0.0", "lcid": "^2.0.0", "mem": "^4.0.0" } @@ -10421,6 +10480,16 @@ "has-flag": "^3.0.0" } }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, "yargs": { "version": "12.0.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.2.tgz", diff --git a/package.json b/package.json index feef9b94..1848cc7c 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "data-uri-to-buffer": "^2.0.0", "date-fns": "^1.29.0", "dotenv": "^6.0.0", + "exifreader": "^2.5.0", "fetch-jsonp": "^1.1.3", "file-saver": "^2.0.0-rc.3", "history": "^4.7.2", @@ -72,6 +73,6 @@ "uglifyjs-webpack-plugin": "^1.3.0", "webpack": "3.x.x", "webpack-cli": "^3.1.0", - "webpack-dev-server": "^3.1.10" + "webpack-dev-server": "^3.1.14" } } |
