summaryrefslogtreecommitdiff
path: root/old/faiss/static/js/app.js
diff options
context:
space:
mode:
Diffstat (limited to 'old/faiss/static/js/app.js')
-rw-r--r--old/faiss/static/js/app.js491
1 files changed, 491 insertions, 0 deletions
diff --git a/old/faiss/static/js/app.js b/old/faiss/static/js/app.js
new file mode 100644
index 00000000..77164c76
--- /dev/null
+++ b/old/faiss/static/js/app.js
@@ -0,0 +1,491 @@
+/* eslint no-use-before-define: 0, camelcase: 0, one-var-declaration-per-line: 0, one-var: 0, quotes: 0, prefer-destructuring: 0, no-alert: 0, no-console: 0, no-multi-assign: 0 */
+
+function loadApp() {
+ const result_template = document.querySelector('#result-template').innerHTML
+ const results_el = document.querySelector('.results')
+ const query_div = document.body.querySelector('.query > div')
+ let bounds
+ let token, username
+ let x, y, mouse_x, mouse_y, dx, dy, box
+ let dragging = false
+ let cropping = false
+ let creating = false
+ let did_check = false
+
+ function init() {
+ login()
+ bind()
+ route()
+ }
+ function bind() {
+ window.onpopstate = route
+ document.querySelector('[name=img]').addEventListener('change', upload)
+ on('click', '.results a', preventDefault)
+ on('click', '.search', search)
+ on('click', '.panic', panic)
+ on('click', '.upload_again', upload_again)
+ on('click', '.browse', browse)
+ on('click', '.results img', save)
+ on('click', '.view_saved', loadSaved)
+ on('click', '.create_new_group', createNewGroup)
+ on('click', '.reset', reset)
+ on('click', '.random', random)
+ on('click', '.check', check)
+ on('mousedown', '.query img', down)
+ window.addEventListener('mousemove', move)
+ window.addEventListener('mouseup', up)
+ window.addEventListener('keydown', keydown)
+ }
+ function route() {
+ const path = window.location.pathname.split('/')
+ // remove initial slash
+ path.shift()
+ // remove dummy route
+ if (path[0] === 'search') path.shift()
+ switch (path[0]) {
+ case 'fetch':
+ search({ target: { url: window.location.search.substr(1).split('=')[1] } })
+ break
+ case 'view':
+ search(path.slice(1))
+ break
+ case 'q':
+ if (path.length === 3) {
+ search({ target: { dir: path[1], fn: path[2] } })
+ } else {
+ browse({ target: { dir: path[1], fn: null } })
+ }
+ break
+ case 'saved':
+ loadSaved()
+ break
+ default:
+ break
+ }
+ }
+ function keydown(e) {
+ switch (e.keyCode) {
+ case 27: // escape
+ panic()
+ break
+ default:
+ break
+ }
+ }
+
+ // load search results
+ function loadResults(data) {
+ console.log(data)
+ if (!data.query.url) return
+ // console.log(data)
+ document.body.className = 'searching'
+ const path = getPathFromImage(data.query.url)
+ pushState('searching', "/search/fetch/?url=" + path.url)
+ if (path.dir === 'uploaded' && path.fn.match('_filename')) {
+ loadMessage(
+ "<a href='javascript:history.go(-1)'>&lt; Back</a> | "
+ + "Searching subregion, "
+ + "found " + data.results.length + " images"
+ )
+ } else {
+ loadMessage(
+ "Found " + data.results.length + " images"
+ )
+ }
+ loadQuery(data.query.url)
+ if (!data.results.length) {
+ results_el.innerHTML = "No results"
+ return
+ }
+ const saved = window.store.get('saved', [])
+
+ results_el.innerHTML = data.results.map(res => {
+ const { distance, file, hash, frame, url } = res
+ const isSaved = saved.indexOf(url) !== -1
+ const { type } = getPathFromImage(url)
+ let className = isSaved ? 'saved' : ''
+ className += ' ' + type
+ let t = result_template
+ .replace('{score}', Math.floor(clamp(1 - distance, 0, 1) * 100) + "%")
+ .replace('{browse}', '/search/q/' + hash)
+ .replace('{search}', '/search/view/' + [file, hash, frame].join('/'))
+ .replace('{metadata}', '/metadata/' + hash)
+ .replace('{className}', className)
+ .replace('{saved_msg}', isSaved ? 'Saved' : 'Save')
+ .replace('{img}', url)
+ return t
+ }).join('')
+ }
+
+ function loadDirectory(data) {
+ console.log(data)
+ document.body.className = 'browsing'
+ pushState('searching', "/search/q/" + data.path)
+ loadMessage("Video: <b>" + data.path + "</b>")
+ loadQuery("")
+ if (!data.results.length) {
+ results_el.innerHTML = "No frames found"
+ return
+ }
+ const saved = window.store.get('saved', [])
+ results_el.innerHTML = data.results
+ .map(result => [parseInt(result.frame, 10), result])
+ .sort((a, b) => a[0] - b[0])
+ .map(pair => {
+ let { file, hash, frame, url } = pair[1]
+ const isSaved = saved.indexOf(url) !== -1
+ let className = isSaved ? 'saved' : ''
+ let t = result_template
+ .replace('{img}', url)
+ .replace('{browse}', '/search/q/' + hash)
+ .replace('{search}', '/search/view/' + [file, hash, frame].join('/'))
+ .replace('{metadata}', '/metadata/' + hash)
+ .replace('{className}', className)
+ .replace('{saved_msg}', isSaved ? 'Saved' : 'Save')
+ return t
+ }).join('')
+ }
+ function loadSaved() {
+ document.body.className = 'saving'
+ pushState('View saved', "/search/saved")
+ const saved = window.store.get('saved', [])
+ cropping = false
+ loadMessage(saved.length + " saved image" + (saved.length === 1 ? "" : "s"))
+ loadQuery('')
+ const box_el = document.querySelector('.box')
+ if (box_el) box_el.parentNode.removeChild(box_el)
+ results_el.innerHTML = saved.map(href => {
+ const { url, dir } = getPathFromImage({ src: href })
+ let className = 'saved'
+ let t = result_template
+ .replace('{img}', href)
+ .replace('{browse}', '/search/q/' + dir)
+ .replace('{search}', '/search/fetch/?url=' + url)
+ .replace('{metadata}', '/metadata/' + dir)
+ .replace('{className}', className)
+ .replace('{saved_msg}', 'Saved')
+ return t
+ }).join('')
+ }
+ function loadQuery(path) {
+ if (cropping) return
+ const qd = document.querySelector('.query div')
+ qd.innerHTML = ''
+ if (path.match(/(gif|jpe?g|png)$/)) {
+ const img = new Image()
+ img.setAttribute('crossorigin', 'anonymous')
+ img.src = path.replace('sm', 'md')
+ qd.appendChild(img)
+ } else {
+ qd.innerHTML = path || ""
+ }
+ }
+ function loadMessage(msg) {
+ document.querySelector('.query .msg').innerHTML = msg
+ }
+
+ // panic button
+ function panic() {
+ loadMessage('Query cleared')
+ loadQuery('')
+ results_el.innerHTML = ''
+ }
+
+ // adding stuff to localstorage
+ function save(e) {
+ const { url } = getPathFromImage(e.target)
+ const saved = window.store.get('saved', [])
+ let newList = saved || []
+ if (saved.indexOf(url) !== -1) {
+ newList = saved.filter(f => f !== url)
+ e.target.parentNode.classList.remove('saved')
+ } else {
+ newList.push(url)
+ e.target.parentNode.classList.add('saved')
+ }
+ window.store.set('saved', newList)
+ }
+ function reset() {
+ const shouldReset = window.confirm("This will reset the saved images. Are you sure?")
+ if (!shouldReset) return
+ window.store.set('saved', [])
+ loadSaved()
+ document.querySelector('[name=title]').value = ''
+ window.alert("Reset saved images")
+ }
+
+ // submit the new group
+ function createNewGroup() {
+ const title = document.querySelector('[name=title]').value.trim().replace(/[^-_a-zA-Z0-9 ]/g, "")
+ const saved = window.store.get('saved', [])
+ const graphic = document.querySelector('[name=graphic]').checked
+ if (!title.length) return alert("Please enter a title for this group")
+ if (!saved.length) return alert("Please pick some images to save")
+ if (!did_check) {
+ alert('Automatically checking for duplicates. Please doublecheck your selection.')
+ return check()
+ }
+ if (creating) return null
+ creating = true
+ return http_post("/api/images/import/new/", {
+ title,
+ graphic,
+ saved
+ }).then(res => {
+ console.log(res)
+ window.store.set('saved', [])
+ window.location.href = '/groups/show/' + res.image_group.id
+ }).catch(res => {
+ alert('Error creating group. The server response is logged to the console.')
+ console.log(res)
+ creating = false
+ })
+ }
+
+ // api queries
+ function login() {
+ const isLocal = (window.location.hostname === '0.0.0.0')
+ try {
+ // csrftoken = "test" // getCookie('csrftoken')
+ const auth = JSON.parse(window.store.get('persist:root').auth)
+ token = auth.token
+ username = auth.user.username
+ if (!token && !isLocal) {
+ window.location.href = '/'
+ }
+ } catch (e) {
+ if (!isLocal) {
+ window.location.href = '/'
+ }
+ }
+ document.querySelector('.logged-in .capitalize').innerHTML = username || 'user'
+ }
+
+ function upload(e) {
+ cropping = false
+ const files = e.dataTransfer ? e.dataTransfer.files : e.target.files
+ let i, f
+ for (i = 0, f; i < files.length; i++) {
+ f = files[i]
+ if (f && f.type.match('image.*')) break
+ }
+ if (!f) return
+ do_upload(f)
+ }
+
+ function do_upload(f) {
+ const fd = new FormData()
+ fd.append('query_img', f)
+ document.body.className = 'loading'
+ http_post('/search/api/upload', fd).then(loadResults)
+ }
+
+ function upload_again() {
+ const { files } = document.querySelector('input[type=file]')
+ if (!files.length) {
+ window.alert('Please upload a file.')
+ return
+ }
+ upload({
+ dataTransfer: { files }
+ })
+ }
+
+ function search(e) {
+ if (e.length) return search_by_vector(e)
+ const { url } = getPath(e.target)
+ cropping = false
+ document.body.className = 'loading'
+ loadQuery(url)
+ loadMessage('Loading results...')
+ http_get('/search/api/fetch/?url=' + url).then(loadResults)
+ }
+
+ function search_by_vector(e) {
+ cropping = false
+ document.body.className = 'loading'
+ loadQuery('')
+ loadMessage('Loading results...')
+ http_get('/search/api/search/' + e.join('/')).then(loadResults)
+ }
+
+ function browse(e) {
+ document.body.className = 'loading'
+ cropping = false
+ let dir;
+ if (e.target.dir) {
+ dir = e.target.dir
+ }
+ else {
+ const href = e.target.parentNode.href
+ dir = href.split('/')[5]
+ console.log(href, dir)
+ }
+ loadMessage('Listing video...')
+ http_get('/search/api/list/' + dir).then(loadDirectory)
+ }
+
+ function check() {
+ http_post('/api/images/import/search/', {
+ saved: window.store.get('saved') || [],
+ }).then(res => {
+ console.log(res)
+ const { good, bad } = res
+ did_check = true
+ window.store.set('saved', good)
+ if (!bad.length) {
+ return alert("No duplicates found.")
+ }
+ bad.forEach(path => {
+ const el = document.querySelector('img[src="' + path + '"]')
+ if (el) el.parentNode.classList.remove('saved')
+ })
+ return alert("Untagged " + bad.length + " duplicate" + (bad.length === 1 ? "" : "s") + ".")
+ })
+ }
+
+ function random() {
+ http_get('/search/api/random').then(loadResults)
+ }
+
+ // drawing a box
+ function down(e) {
+ e.preventDefault()
+ dragging = true
+ bounds = query_div.querySelector('img').getBoundingClientRect()
+ mouse_x = e.pageX
+ mouse_y = e.pageY
+ x = mouse_x - bounds.left
+ y = mouse_y - bounds.top
+ dx = dy = 0
+ box = document.querySelector('.box') || document.createElement('div')
+ box.className = 'box'
+ box.style.left = x + 'px'
+ box.style.top = y + 'px'
+ box.style.width = 0 + 'px'
+ box.style.height = 0 + 'px'
+ query_div.appendChild(box)
+ }
+ function move(e) {
+ if (!dragging) return
+ e.preventDefault()
+ dx = clamp(e.pageX - mouse_x, 0, bounds.width - x)
+ dy = clamp(e.pageY - mouse_y, 0, bounds.height - y)
+ box.style.width = dx + 'px'
+ box.style.height = dy + 'px'
+ }
+ function up(e) {
+ if (!dragging) return
+ dragging = false
+ e.preventDefault()
+ const img = query_div.querySelector('img')
+ const canvas = query_div.querySelector('canvas') || document.createElement('canvas')
+ const ctx = canvas.getContext('2d')
+ const ratio = img.naturalWidth / bounds.width
+ canvas.width = dx * ratio
+ canvas.height = dy * ratio
+ if (dx < 10 || dy < 10) {
+ if (canvas.parentNode) canvas.parentNode.removeChild(canvas)
+ const box_el = document.querySelector('.box')
+ if (box_el) box_el.parentNode.removeChild(box_el)
+ return
+ }
+ query_div.appendChild(canvas)
+ ctx.drawImage(
+ img,
+ x * ratio,
+ y * ratio,
+ dx * ratio,
+ dy * ratio,
+ 0, 0, canvas.width, canvas.height
+ )
+ cropping = true
+ const blob = window.dataUriToBlob(canvas.toDataURL('image/jpeg', 0.9))
+ do_upload(blob)
+ }
+
+ // utility functions
+ function http_get(url) {
+ return fetch(url).then(res => res.json())
+ }
+ function http_post(url, data) {
+ let headers
+ if (data instanceof FormData) {
+ headers = {
+ Accept: 'application/json, application/xml, text/play, text/html, *.*',
+ Authorization: 'Token ' + token,
+ }
+ } else {
+ headers = {
+ Accept: 'application/json, application/xml, text/play, text/html, *.*',
+ 'Content-Type': 'application/json; charset=utf-8',
+ Authorization: 'Token ' + token,
+ }
+ data = JSON.stringify(data)
+ }
+
+ // headers['X-CSRFToken'] = csrftoken
+ return fetch(url, {
+ method: 'POST',
+ body: data,
+ credentials: 'include',
+ headers,
+ }).then(res => res.json())
+ }
+ function on(evt, sel, handler) {
+ document.addEventListener(evt, function (event) {
+ let t = event.target
+ while (t && t !== this) {
+ if (t.matches(sel)) {
+ handler.call(t, event)
+ }
+ t = t.parentNode
+ }
+ })
+ }
+ function getPathFromImage(el) {
+ const url = el.src ? el.src : el
+ const partz = url.split('/')
+ let type, dir, fn
+ if (partz.length === 3) {
+ type = 'photo'
+ dir = ''
+ fn = ''
+ }
+ if (partz.length === 9) {
+ type = 'photo'
+ dir = partz[6]
+ fn = ''
+ } else if (partz.length === 10) {
+ type = 'video'
+ dir = partz[6]
+ fn = partz[7]
+ }
+ return { type, dir, fn, url }
+ }
+ function getPath(el) {
+ if (el.url) {
+ return getPathFromImage(el.url)
+ } if (el.dir) {
+ return el
+ }
+ el = el.parentNode.parentNode.parentNode.querySelector('img')
+ return getPathFromImage(el)
+ }
+ function pushState(txt, path) {
+ if (window.location.pathname === path) return
+ console.log('pushstate', path)
+ window.history.pushState({}, txt, path)
+ }
+ function preventDefault(e) {
+ if (e && !e.target.classList.contains('metadata')) {
+ e.preventDefault()
+ }
+ }
+ function clamp(n, a, b) { return n < a ? a : n < b ? n : b }
+
+ // initialize the app when the DOM is ready
+ document.addEventListener('DOMContentLoaded', init)
+}
+
+loadApp()