/* 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( "< Back | " + "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: " + data.path + "") 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()