From 8b55c3b681f7dbe2b5df5e4e6fdb9ae9a7b3b40c Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 11 Apr 2018 13:46:05 -0400 Subject: [PATCH] Fixed some bugs. Started auto complete --- config.py | 3 +- crawler.py | 22 ++- indexer.py | 19 ++- parsing.py | 232 +++++++++++----------------- search.py | 11 +- spec/TextFileParser_spec.py | 4 +- spec/ThumbnailGenerator_spec.py | 2 +- static/css/auto-complete.css | 9 ++ static/css/bootstrap-slider.min.css | 41 +++++ static/{js => css}/bootstrap.min.js | 0 static/js/auto-complete.min.js | 3 + static/js/bootstrap-slider.min.js | 5 + templates/layout.html | 7 +- templates/search.html | 120 ++++++++++---- templates/task.html | 18 ++- thumbnail.py | 23 +-- 16 files changed, 318 insertions(+), 201 deletions(-) create mode 100644 static/css/auto-complete.css create mode 100644 static/css/bootstrap-slider.min.css rename static/{js => css}/bootstrap.min.js (100%) create mode 100644 static/js/auto-complete.min.js create mode 100644 static/js/bootstrap-slider.min.js diff --git a/config.py b/config.py index 0ca843e..4af3922 100644 --- a/config.py +++ b/config.py @@ -5,5 +5,6 @@ default_options = { "TextFileContentLenght": "16384", "MimeGuesser": "extension", # extension, content "CheckSumCalculators": "", # md5, sha1, sha256 - } + +index_every = 50000 diff --git a/crawler.py b/crawler.py index 5879e16..88f398a 100644 --- a/crawler.py +++ b/crawler.py @@ -10,7 +10,7 @@ from search import Search from thumbnail import ThumbnailGenerator from storage import Directory import shutil - +import config class RunningTask: @@ -26,9 +26,11 @@ class RunningTask: class Crawler: - def __init__(self, enabled_parsers: list, mime_guesser: MimeGuesser=ContentMimeGuesser()): + def __init__(self, enabled_parsers: list, mime_guesser: MimeGuesser=ContentMimeGuesser(), indexer=None, dir_id=0): self.documents = [] self.enabled_parsers = enabled_parsers + self.indexer = indexer + self.dir_id = dir_id for parser in self.enabled_parsers: if parser.is_default: @@ -44,6 +46,8 @@ class Crawler: def crawl(self, root_dir: str, counter: Value=None): + document_counter = 0 + for root, dirs, files in os.walk(root_dir): for filename in files: @@ -53,6 +57,13 @@ class Crawler: parser = self.ext_map.get(mime, self.default_parser) + document_counter += 1 + if document_counter >= config.index_every: + document_counter = 0 + + self.indexer.index(self.documents, self.dir_id) + self.documents.clear() + try: if counter: counter.value += 1 @@ -64,6 +75,9 @@ class Crawler: except FileNotFoundError: continue # File was deleted + if self.indexer is not None: + self.indexer.index(self.documents, self.dir_id) + def countFiles(self, root_dir: str): count = 0 @@ -123,11 +137,9 @@ class TaskManager: MediaFileParser(chksum_calcs), TextFileParser(chksum_calcs, int(directory.get_option("TextFileContentLenght"))), PictureFileParser(chksum_calcs)], - mime_guesser) + mime_guesser, self.indexer, directory.id) c.crawl(directory.path, counter) - # todo: create indexer inside the crawler and index every X files - Indexer("changeme").index(c.documents, directory.id) done.value = 1 def execute_thumbnails(self, directory: Directory, total_files: Value, counter: Value, done: Value): diff --git a/indexer.py b/indexer.py index 9f40fa9..8a01d81 100644 --- a/indexer.py +++ b/indexer.py @@ -61,17 +61,26 @@ class Indexer: self.es.indices.create(index=self.index_name) self.es.indices.close(index=self.index_name) - self.es.indices.put_settings(body='{"analysis":{"tokenizer":{"path_tokenizer":{"type":"path_hierarchy"}}}}', index=self.index_name) - self.es.indices.put_settings(body='{"analysis":{"tokenizer":{"my_nGram_tokenizer":{"type":"nGram","min_gram":3,"max_gram":4}}}}') - self.es.indices.put_settings(body='{"analysis":{"analyzer":{"path_analyser":{"tokenizer":"path_tokenizer"}}}}') - self.es.indices.put_settings(body='{"analysis":{"analyzer":{"my_nGram":{"tokenizer":"my_nGram_tokenizer"}}}}') + self.es.indices.put_settings(body='{"analysis":{"tokenizer":{"path_tokenizer":{"type":"path_hierarchy"}}}}', + index=self.index_name) + self.es.indices.put_settings(body='{"analysis":{"tokenizer":{"my_nGram_tokenizer":{"type":"nGram","min_gram":3,"max_gram":4}}}}', + index=self.index_name) + self.es.indices.put_settings(body='{"analysis":{"analyzer":{"path_analyser":{"tokenizer":"path_tokenizer"}}}}', + index=self.index_name) + self.es.indices.put_settings(body='{"analysis":{"analyzer":{"my_nGram":{"tokenizer":"my_nGram_tokenizer", "filter": ["lowercase"]}}}}', + index=self.index_name) self.es.indices.put_mapping(body='{"properties": {' '"path": {"type": "text", "analyzer": "path_analyser", "copy_to": "suggest-path"},' '"suggest-path": {"type": "completion", "analyzer": "keyword"},' '"mime": {"type": "keyword"},' '"directory": {"type": "keyword"},' - '"name": {"analyzer": "my_nGram", "type": "text"}' + '"name": {"analyzer": "my_nGram", "type": "text"},' + '"album": {"analyzer": "my_nGram", "type": "text"},' + '"artist": {"analyzer": "my_nGram", "type": "text"},' + '"title": {"analyzer": "my_nGram", "type": "text"},' + '"genre": {"analyzer": "my_nGram", "type": "text"},' + '"album_artist": {"analyzer": "my_nGram", "type": "text"}' '}}', doc_type="file", index=self.index_name) self.es.indices.open(index=self.index_name) diff --git a/parsing.py b/parsing.py index 9a1391d..90b4e75 100644 --- a/parsing.py +++ b/parsing.py @@ -5,6 +5,7 @@ import mimetypes import subprocess import json import chardet +import html from PIL import Image class MimeGuesser: @@ -150,55 +151,55 @@ class MediaFileParser(GenericFileParser): super().__init__(checksum_calculators) self.mime_types = [ - "video/3gpp", - "video/mp4", - "video/mpeg", - "video/ogg", - "video/quicktime", - "video/webm", - "video/x-flv", - "video/x-mng", - "video/x-ms-asf", - "video/x-ms-wmv", - "video/x-msvideo", - "audio/basic", - "auido/L24", - "audio/mid", - "audio/mpeg", - "audio/mp4", - "audio/x-aiff", - "audio/ogg", - "audio/vorbis" - "audio/x-realaudio", - "audio/x-wav" + "video/3gpp", "video/mp4", "video/mpeg", "video/ogg", "video/quicktime", + "video/webm", "video/x-flv", "video/x-mng", "video/x-ms-asf", + "video/x-ms-wmv", "video/x-msvideo", "audio/basic", "auido/L24", + "audio/mid", "audio/mpeg", "audio/mp4", "audio/x-aiff", + "audio/ogg", "audio/vorbis" "audio/x-realaudio", "audio/x-wav", + "audio/flac", "audio/x-monkeys-audio", "audio/wav", "audio/wave", + "audio/x-wav", "audio/x-ms-wma" ] def parse(self, full_path: str): info = super().parse(full_path) - print("video/audio : " + full_path) + p = subprocess.Popen(["ffprobe", "-v", "quiet", "-print_format", "json=c=1", "-show_format", full_path], + stdout=subprocess.PIPE) + out, err = p.communicate() - result = subprocess.run(["ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", full_path], - stdout=subprocess.PIPE) + try: + metadata = json.loads(out.decode("utf-8")) - metadata = json.loads(result.stdout.decode("utf-8")) + if "format" in metadata: - if "format" in metadata: + if "bit_rate" in metadata["format"]: + info["bit_rate"] = int(metadata["format"]["bit_rate"]) - if "bit_rate" in metadata["format"]: - info["bit_rate"] = int(metadata["format"]["bit_rate"]) + if "nb_streams" in metadata["format"]: + info["nb_streams"] = int(metadata["format"]["nb_streams"]) - if "nb_streams" in metadata["format"]: - info["nb_streams"] = int(metadata["format"]["nb_streams"]) + if "duration" in metadata["format"]: + info["duration"] = float(metadata["format"]["duration"]) - if "duration" in metadata["format"]: - info["duration"] = float(metadata["format"]["duration"]) + if "format_name" in metadata["format"]: + info["format_name"] = metadata["format"]["format_name"] - if "format_name" in metadata["format"]: - info["format_name"] = metadata["format"]["format_name"] + if "format_long_name" in metadata["format"]: + info["format_long_name"] = metadata["format"]["format_long_name"] - if "format_long_name" in metadata["format"]: - info["format_long_name"] = metadata["format"]["format_long_name"] + if "tags" in metadata["format"]: + if "genre" in metadata["format"]["tags"]: + info["genre"] = metadata["format"]["tags"]["genre"] + if "title" in metadata["format"]["tags"]: + info["title"] = metadata["format"]["tags"]["title"] + if "album" in metadata["format"]["tags"]: + info["album"] = metadata["format"]["tags"]["album"] + if "album_artist" in metadata["format"]["tags"]: + info["album_artist"] = metadata["format"]["tags"]["album_artist"] + + except json.decoder.JSONDecodeError: + print("json decode error:" + full_path) + pass return info @@ -211,60 +212,25 @@ class PictureFileParser(GenericFileParser): super().__init__(checksum_calculators) self.mime_types = [ - "image/bmp", - "image/cgm", - "image/cis-cod", - "image/g3fax", - "image/gif", - "image/ief", - "image/jpeg", - "image/ktx", - "image/pipeg", - "image/pjpeg", - "image/png", - "image/prs.btif", - "image/svg+xml", - "image/tiff", - "image/vnd.adobe.photoshop", - "image/vnd.dece.graphic", - "image/vnd.djvu", - "image/vnd.dvb.subtitle", - "image/vnd.dwg", - "image/vnd.dxf", - "image/vnd.fastbidsheet", - "image/vnd.fpx", - "image/vnd.fst", - "image/vnd.fujixerox.edmics-mmr", - "image/vnd.fujixerox.edmics-rlc", - "image/vnd.ms-modi", - "image/vnd.net-fpx", - "image/vnd.wap.wbmp", - "image/vnd.xiff", - "image/webp", - "image/x-citrix-jpeg", - "image/x-citrix-png", - "image/x-cmu-raster", - "image/x-cmx", - "image/x-freehand", - "image/x-icon", - "image/x-pcx", - "image/x-pict", - "image/x-png", - "image/x-portable-anymap", - "image/x-portable-bitmap", - "image/x-portable-graymap", - "image/x-portable-pixmap", - "image/x-rgb", - "image/x-xbitmap", - "image/x-xpixmap", - "image/x-xwindowdump" + "image/bmp", "image/cgm", "image/cis-cod", "image/g3fax", "image/gif", + "image/ief", "image/jpeg", "image/ktx", "image/pipeg", "image/pjpeg", + "image/png", "image/prs.btif", "image/svg+xml", "image/tiff", + "image/vnd.adobe.photoshop", "image/vnd.dece.graphic", "image/vnd.djvu", + "image/vnd.dvb.subtitle", "image/vnd.dwg", "image/vnd.dxf", + "image/vnd.fastbidsheet", "image/vnd.fpx", "image/vnd.fst", + "image/vnd.fujixerox.edmics-mmr", "image/vnd.fujixerox.edmics-rlc", + "image/vnd.ms-modi", "image/vnd.net-fpx", "image/vnd.wap.wbmp", + "image/vnd.xiff", "image/webp", "image/x-citrix-jpeg", "image/x-citrix-png", + "image/x-cmu-raster", "image/x-cmx", "image/x-icon", + "image/x-pcx", "image/x-pict", "image/x-png", "image/x-portable-bitmap", + "image/x-portable-graymap", "image/x-portable-pixmap", + "image/x-rgb", "image/x-xbitmap", "image/x-xpixmap", "image/x-xwindowdump" ] def parse(self, full_path: str): info = super().parse(full_path) - print("picture") try: with open(full_path, "rb") as image_file: @@ -274,8 +240,7 @@ class PictureFileParser(GenericFileParser): info["format"] = image.format info["width"] = image.width info["height"] = image.height - except OSError as e: - print(e.strerror) + except (OSError, ValueError) as e: pass return info @@ -290,58 +255,40 @@ class TextFileParser(GenericFileParser): self.content_lenght = content_lenght self.mime_types = [ - "text/asp", - "text/css", - "text/ecmascript", - "text/html", - "text/javascript", - "text/mcf", - "text/pascal", - "text/plain", - "text/richtext", - "text/scriplet", - "text/sgml", - "text/tab-separated-values", - "text/uri-list", - "text/vnd.abc", - "text/vnd.fmi.flexstor", - "text/vnd.rn-realtext", - "text/vnd.wap.wml", - "text/vnd.wap.wmlscript", - "text/webviewhtml", - "text/x-asm", - "text/x-audiosoft-intra", - "text/x-c", - "text/x-component", - "text/x-fortran", - "text/x-h", - "text/x-java-source", - "text/x-la-asf", - "text/x-m", - "text/x-pascal", - "text/x-script", - "text/x-script.csh", - "text/x-script.elisp", - "text/x-script.guile", - "text/x-script.ksh", - "text/x-script.lisp", - "text/x-script.perl", - "text/x-script.perl-module", - "text/x-script.phyton", - "text/x-script.rexx", - "text/x-script.scheme", - "text/x-script.sh", - "text/x-script.tcl", - "text/x-script.tcsh", - "text/x-script.zsh", - "text/x-server-parsed-html", - "text/x-setext", - "text/x-sgml", - "text/x-speech", - "text/x-uil", - "text/x-uuencode", - "text/x-vcalendar", - "text/xml" + "text/asp", "text/css", "text/ecmascript", "text/html", "text/javascript", + "text/mcf", "text/pascal", "text/plain", "text/richtext", "text/scriplet", + "text/sgml", "text/tab-separated-values", "text/uri-list", "text/vnd.abc", + "text/vnd.fmi.flexstor", "text/vnd.rn-realtext", "text/vnd.wap.wml", + "text/vnd.wap.wmlscript", "text/webviewhtml", "text/x-asm", "text/x-audiosoft-intra", + "text/x-c", "text/x-component", "text/x-fortran", "text/x-h", "text/x-java-source", + "text/x-la-asf", "text/x-m", "text/x-pascal", "text/x-script", + "text/x-script.csh", "text/x-script.elisp", "text/x-script.guile", + "text/x-script.ksh", "text/x-script.lisp", "text/x-script.perl", + "text/x-script.perl-module", "text/x-script.phyton", "text/x-script.rexx", + "text/x-script.scheme", "text/x-script.sh", "text/x-script.tcl", + "text/x-script.tcsh", "text/x-script.zsh", "text/x-server-parsed-html", + "text/x-setext", "text/x-sgml", "text/x-speech", "text/x-uil", + "text/x-uuencode", "text/x-vcalendar", "text/xml" + ] + + self.encodings = [ + 'ascii', 'big5', 'big5hkscs', 'cp037', 'cp273', 'cp424', 'cp437', + 'cp500', 'cp720', 'cp737', 'cp775', 'cp850', 'cp852', 'cp855', + 'cp856', 'cp857', 'cp858', 'cp860', 'cp861', 'cp862', 'cp863', + 'cp864', 'cp865', 'cp866', 'cp869', 'cp874', 'cp875', 'cp932', + 'cp949', 'cp950', 'cp1006', 'cp1026', 'cp1125', 'cp1140', + 'cp1250', 'cp1251', 'cp1252', 'cp1253', 'cp1254', 'cp1255', + 'cp1256', 'cp1257', 'cp1258', 'cp65001', 'euc_jp', 'euc_jis_2004', + 'euc_jisx0213', 'euc_kr', 'gb2312', 'gbk', 'gb18030', 'hz', 'iso2022_jp', + 'iso2022_jp_1', 'iso2022_jp_2', 'iso2022_jp_2004', 'iso2022_jp_3', + 'iso2022_jp_ext', 'iso2022_kr', 'latin_1', 'iso8859_2', 'iso8859_3', + 'iso8859_4', 'iso8859_5', 'iso8859_6', 'iso8859_7', 'iso8859_8', + 'iso8859_9', 'iso8859_10', 'iso8859_11', 'iso8859_13', 'iso8859_14', + 'iso8859_15', 'iso8859_16', 'johab', 'koi8_r', 'koi8_t', 'koi8_u', + 'kz1048', 'mac_cyrillic', 'mac_greek', 'mac_iceland', 'mac_latin2', + 'mac_roman', 'mac_turkish', 'ptcp154', 'shift_jis', 'shift_jis_2004', + 'shift_jisx0213', 'utf_32', 'utf_32_be', 'utf_32_le', 'utf_16', 'utf_16_be', + 'utf_16_le', 'utf_7', 'utf_8', 'utf_8_sig' ] def parse(self, full_path: str): @@ -355,12 +302,11 @@ class TextFileParser(GenericFileParser): chardet.detect(raw_content) encoding = chardet.detect(raw_content)["encoding"] - if encoding is not None: - - print(full_path) - print(encoding) + if encoding is not None and encoding in self.encodings: info["encoding"] = encoding - info["content"] = raw_content.decode(encoding, "ignore") + content = raw_content.decode(encoding, "ignore") + + info["content"] = html.escape(content) return info diff --git a/search.py b/search.py index c33a209..77a1783 100644 --- a/search.py +++ b/search.py @@ -22,7 +22,7 @@ class Search: def get_all_documents(self, dir_id: int): return helpers.scan(client=self.es, - query={"_source": {"includes": ["path", "name"]}, + query={"_source": {"includes": ["path", "name", "mime", "extension"]}, "query": {"term": {"directory": dir_id}}}, index=self.index_name) @@ -58,7 +58,8 @@ class Search: page = self.es.search(body={"query": {"multi_match": { "query": query, - "fields": ["name", "content"] + "fields": ["name", "content", "album", "artist", "title", "genre", "album_artist"], + "operator": "and" }}, "sort": [ "_score" @@ -74,10 +75,14 @@ class Search: "prefix": query, "completion": { "field": "suggest-path", - "skip_duplicates": True + "skip_duplicates": True, + "size": 4000 } } }, + "aggs": { + "total_size": {"sum": {"field": "size"}} + }, "size": 40}, index=self.index_name, scroll="3m") return page diff --git a/spec/TextFileParser_spec.py b/spec/TextFileParser_spec.py index e803e1a..f72005c 100644 --- a/spec/TextFileParser_spec.py +++ b/spec/TextFileParser_spec.py @@ -6,10 +6,10 @@ class TextFileParserTest(TestCase): def test_parse_csv(self): - parser = TextFileParser([], 12345) + parser = TextFileParser([], 1234) info = parser.parse("test_files/text.csv") self.assertTrue(info["content"].startswith("rosbagTimestamp,header,seq,stamp,secs,nsecs,")) - self.assertEqual(len(info["content"]), 12345) + self.assertEqual(len(info["content"]), 1309) # Size is larger because of html escaping self.assertEqual(info["encoding"], "ascii") diff --git a/spec/ThumbnailGenerator_spec.py b/spec/ThumbnailGenerator_spec.py index 60c21ef..ac37031 100644 --- a/spec/ThumbnailGenerator_spec.py +++ b/spec/ThumbnailGenerator_spec.py @@ -11,7 +11,7 @@ class ThumbnailGeneratorTest(TestCase): generator = ThumbnailGenerator(300) # Original image is 420x315 - generator.generate("test_folder/sample_1.jpg", "test_thumb1.jpg") + generator.generate("test_folder/sample_1.jpg", "test_thumb1.jpg", "image/JPEG") img = Image.open("test_thumb1.jpg") width, height = img.size diff --git a/static/css/auto-complete.css b/static/css/auto-complete.css new file mode 100644 index 0000000..4261b1d --- /dev/null +++ b/static/css/auto-complete.css @@ -0,0 +1,9 @@ +.autocomplete-suggestions { + text-align: left; cursor: default; border: 1px solid #ccc; border-top: 0; background: #fff; box-shadow: -1px 1px 3px rgba(0,0,0,.1); + + /* core styles should not be changed */ + position: absolute; display: none; z-index: 9999; max-height: 254px; overflow: hidden; overflow-y: auto; box-sizing: border-box; +} +.autocomplete-suggestion { position: relative; padding: 0 .6em; line-height: 23px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 1.02em; color: #333; } +.autocomplete-suggestion b { font-weight: normal; color: #1f8dd6; } +.autocomplete-suggestion.selected { background: #f0f0f0; } diff --git a/static/css/bootstrap-slider.min.css b/static/css/bootstrap-slider.min.css new file mode 100644 index 0000000..1cf68b5 --- /dev/null +++ b/static/css/bootstrap-slider.min.css @@ -0,0 +1,41 @@ +/*! ======================================================= + VERSION 10.0.0 +========================================================= */ +/*! ========================================================= + * bootstrap-slider.js + * + * Maintainers: + * Kyle Kemp + * - Twitter: @seiyria + * - Github: seiyria + * Rohit Kalkur + * - Twitter: @Rovolutionary + * - Github: rovolution + * + * ========================================================= + * + * bootstrap-slider is released under the MIT License + * Copyright (c) 2017 Kyle Kemp, Rohit Kalkur, and contributors + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * ========================================================= */.slider{display:inline-block;vertical-align:middle;position:relative}.slider.slider-horizontal{width:210px;height:20px}.slider.slider-horizontal .slider-track{height:10px;width:100%;margin-top:-5px;top:50%;left:0}.slider.slider-horizontal .slider-selection,.slider.slider-horizontal .slider-track-low,.slider.slider-horizontal .slider-track-high{height:100%;top:0;bottom:0}.slider.slider-horizontal .slider-tick,.slider.slider-horizontal .slider-handle{margin-left:-10px}.slider.slider-horizontal .slider-tick.triangle,.slider.slider-horizontal .slider-handle.triangle{position:relative;top:50%;-ms-transform:translateY(-50%);transform:translateY(-50%);border-width:0 10px 10px 10px;width:0;height:0;border-bottom-color:#2e6da4;margin-top:0}.slider.slider-horizontal .slider-tick-container{white-space:nowrap;position:absolute;top:0;left:0;width:100%}.slider.slider-horizontal .slider-tick-label-container{white-space:nowrap;margin-top:20px}.slider.slider-horizontal .slider-tick-label-container .slider-tick-label{padding-top:4px;display:inline-block;text-align:center}.slider.slider-horizontal .tooltip{-ms-transform:translateX(-50%);transform:translateX(-50%)}.slider.slider-horizontal.slider-rtl .slider-track{left:initial;right:0}.slider.slider-horizontal.slider-rtl .slider-tick,.slider.slider-horizontal.slider-rtl .slider-handle{margin-left:initial;margin-right:-10px}.slider.slider-horizontal.slider-rtl .slider-tick-container{left:initial;right:0}.slider.slider-horizontal.slider-rtl .tooltip{-ms-transform:translateX(50%);transform:translateX(50%)}.slider.slider-vertical{height:210px;width:20px}.slider.slider-vertical .slider-track{width:10px;height:100%;left:25%;top:0}.slider.slider-vertical .slider-selection{width:100%;left:0;top:0;bottom:0}.slider.slider-vertical .slider-track-low,.slider.slider-vertical .slider-track-high{width:100%;left:0;right:0}.slider.slider-vertical .slider-tick,.slider.slider-vertical .slider-handle{margin-top:-10px}.slider.slider-vertical .slider-tick.triangle,.slider.slider-vertical .slider-handle.triangle{border-width:10px 0 10px 10px;width:1px;height:1px;border-left-color:#2e6da4;border-right-color:#2e6da4;margin-left:0;margin-right:0}.slider.slider-vertical .slider-tick-label-container{white-space:nowrap}.slider.slider-vertical .slider-tick-label-container .slider-tick-label{padding-left:4px}.slider.slider-vertical .tooltip{-ms-transform:translateY(-50%);transform:translateY(-50%)}.slider.slider-vertical.slider-rtl .slider-track{left:initial;right:25%}.slider.slider-vertical.slider-rtl .slider-selection{left:initial;right:0}.slider.slider-vertical.slider-rtl .slider-tick.triangle,.slider.slider-vertical.slider-rtl .slider-handle.triangle{border-width:10px 10px 10px 0}.slider.slider-vertical.slider-rtl .slider-tick-label-container .slider-tick-label{padding-left:initial;padding-right:4px}.slider.slider-disabled .slider-handle{background-image:-webkit-linear-gradient(top,#dfdfdf 0,#bebebe 100%);background-image:-o-linear-gradient(top,#dfdfdf 0,#bebebe 100%);background-image:linear-gradient(to bottom,#dfdfdf 0,#bebebe 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdfdfdf',endColorstr='#ffbebebe',GradientType=0)}.slider.slider-disabled .slider-track{background-image:-webkit-linear-gradient(top,#e5e5e5 0,#e9e9e9 100%);background-image:-o-linear-gradient(top,#e5e5e5 0,#e9e9e9 100%);background-image:linear-gradient(to bottom,#e5e5e5 0,#e9e9e9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe5e5e5',endColorstr='#ffe9e9e9',GradientType=0);cursor:not-allowed}.slider input{display:none}.slider .tooltip.top{margin-top:-36px}.slider .tooltip-inner{white-space:nowrap;max-width:none}.slider .hide{display:none}.slider-track{position:absolute;cursor:pointer;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#f9f9f9 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#f9f9f9 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#f9f9f9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);border-radius:4px}.slider-selection{position:absolute;background-image:-webkit-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#f9f9f9 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9',endColorstr='#fff5f5f5',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border-radius:4px}.slider-selection.tick-slider-selection{background-image:-webkit-linear-gradient(top,#8ac1ef 0,#82b3de 100%);background-image:-o-linear-gradient(top,#8ac1ef 0,#82b3de 100%);background-image:linear-gradient(to bottom,#8ac1ef 0,#82b3de 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff8ac1ef',endColorstr='#ff82b3de',GradientType=0)}.slider-track-low,.slider-track-high{position:absolute;background:transparent;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border-radius:4px}.slider-handle{position:absolute;top:0;width:20px;height:20px;background-color:#337ab7;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7',endColorstr='#ff2e6da4',GradientType=0);filter:none;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);border:0 solid transparent}.slider-handle.round{border-radius:50%}.slider-handle.triangle{background:transparent none}.slider-handle.custom{background:transparent none}.slider-handle.custom::before{line-height:20px;font-size:20px;content:'\2605';color:#726204}.slider-tick{position:absolute;width:20px;height:20px;background-image:-webkit-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#f9f9f9 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9',endColorstr='#fff5f5f5',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;filter:none;opacity:.8;border:0 solid transparent}.slider-tick.round{border-radius:50%}.slider-tick.triangle{background:transparent none}.slider-tick.custom{background:transparent none}.slider-tick.custom::before{line-height:20px;font-size:20px;content:'\2605';color:#726204}.slider-tick.in-selection{background-image:-webkit-linear-gradient(top,#8ac1ef 0,#82b3de 100%);background-image:-o-linear-gradient(top,#8ac1ef 0,#82b3de 100%);background-image:linear-gradient(to bottom,#8ac1ef 0,#82b3de 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff8ac1ef',endColorstr='#ff82b3de',GradientType=0);opacity:1} \ No newline at end of file diff --git a/static/js/bootstrap.min.js b/static/css/bootstrap.min.js similarity index 100% rename from static/js/bootstrap.min.js rename to static/css/bootstrap.min.js diff --git a/static/js/auto-complete.min.js b/static/js/auto-complete.min.js new file mode 100644 index 0000000..2d0f037 --- /dev/null +++ b/static/js/auto-complete.min.js @@ -0,0 +1,3 @@ +// JavaScript autoComplete v1.0.4 +// https://github.com/Pixabay/JavaScript-autoComplete +var autoComplete=function(){function e(e){function t(e,t){return e.classList?e.classList.contains(t):new RegExp("\\b"+t+"\\b").test(e.className)}function o(e,t,o){e.attachEvent?e.attachEvent("on"+t,o):e.addEventListener(t,o)}function s(e,t,o){e.detachEvent?e.detachEvent("on"+t,o):e.removeEventListener(t,o)}function n(e,s,n,l){o(l||document,s,function(o){for(var s,l=o.target||o.srcElement;l&&!(s=t(l,e));)l=l.parentElement;s&&n.call(l,o)})}if(document.querySelector){var l={selector:0,source:0,minChars:3,delay:150,offsetLeft:0,offsetTop:1,cache:1,menuClass:"",renderItem:function(e,t){t=t.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&");var o=new RegExp("("+t.split(" ").join("|")+")","gi");return'
'+e.replace(o,"$1")+"
"},onSelect:function(){}};for(var c in e)e.hasOwnProperty(c)&&(l[c]=e[c]);for(var a="object"==typeof l.selector?[l.selector]:document.querySelectorAll(l.selector),u=0;u0?i.sc.scrollTop=n+i.sc.suggestionHeight+s-i.sc.maxHeight:0>n&&(i.sc.scrollTop=n+s)}else i.sc.scrollTop=0},o(window,"resize",i.updateSC),document.body.appendChild(i.sc),n("autocomplete-suggestion","mouseleave",function(){var e=i.sc.querySelector(".autocomplete-suggestion.selected");e&&setTimeout(function(){e.className=e.className.replace("selected","")},20)},i.sc),n("autocomplete-suggestion","mouseover",function(){var e=i.sc.querySelector(".autocomplete-suggestion.selected");e&&(e.className=e.className.replace("selected","")),this.className+=" selected"},i.sc),n("autocomplete-suggestion","mousedown",function(e){if(t(this,"autocomplete-suggestion")){var o=this.getAttribute("data-val");i.value=o,l.onSelect(e,o,this),i.sc.style.display="none"}},i.sc),i.blurHandler=function(){try{var e=document.querySelector(".autocomplete-suggestions:hover")}catch(t){var e=0}e?i!==document.activeElement&&setTimeout(function(){i.focus()},20):(i.last_val=i.value,i.sc.style.display="none",setTimeout(function(){i.sc.style.display="none"},350))},o(i,"blur",i.blurHandler);var r=function(e){var t=i.value;if(i.cache[t]=e,e.length&&t.length>=l.minChars){for(var o="",s=0;st||t>40)&&13!=t&&27!=t){var o=i.value;if(o.length>=l.minChars){if(o!=i.last_val){if(i.last_val=o,clearTimeout(i.timer),l.cache){if(o in i.cache)return void r(i.cache[o]);for(var s=1;sh;h++){var j=this[h],k=a.data(j,b);if(k)if(a.isFunction(k[e])&&"_"!==e.charAt(0)){var l=k[e].apply(k,g);if(void 0!==l&&l!==k)return l}else f("no such method '"+e+"' for "+b+" instance");else f("cannot call methods on "+b+" prior to initialization; attempted to call '"+e+"'")}return this}var m=this.map(function(){var d=a.data(this,b);return d?(d.option(e),d._init()):(d=new c(this,e),a.data(this,b,d)),a(this)});return!m||m.length>1?m:m[0]}}if(a){var f="undefined"==typeof console?b:function(a){console.error(a)};return a.bridget=function(a,b){c(b),e(a,b)},a.bridget}}var d=Array.prototype.slice;c(a)}(a),function(a){function e(b,c){function d(a,b){var c="data-slider-"+b.replace(/_/g,"-"),d=a.getAttribute(c);try{return JSON.parse(d)}catch(e){return d}}this._state={value:null,enabled:null,offset:null,size:null,percentage:null,inDrag:!1,over:!1},this.ticksCallbackMap={},this.handleCallbackMap={},"string"==typeof b?this.element=document.querySelector(b):b instanceof HTMLElement&&(this.element=b),c=c?c:{};for(var e=Object.keys(this.defaultOptions),f=0;f0)for(var t=0;t0){for(this.ticksContainer=document.createElement("div"),this.ticksContainer.className="slider-tick-container",f=0;f0)for(this.tickLabelContainer=document.createElement("div"),this.tickLabelContainer.className="slider-tick-label-container",f=0;f0&&(this.options.max=Math.max.apply(Math,this.options.ticks),this.options.min=Math.min.apply(Math,this.options.ticks)),Array.isArray(this.options.value)?(this.options.range=!0,this._state.value=this.options.value):this.options.range?this._state.value=[this.options.value,this.options.max]:this._state.value=this.options.value,this.trackLow=k||this.trackLow,this.trackSelection=j||this.trackSelection,this.trackHigh=l||this.trackHigh,"none"===this.options.selection?(this._addClass(this.trackLow,"hide"),this._addClass(this.trackSelection,"hide"),this._addClass(this.trackHigh,"hide")):("after"===this.options.selection||"before"===this.options.selection)&&(this._removeClass(this.trackLow,"hide"),this._removeClass(this.trackSelection,"hide"),this._removeClass(this.trackHigh,"hide")),this.handle1=m||this.handle1,this.handle2=n||this.handle2,p===!0)for(this._removeClass(this.handle1,"round triangle"),this._removeClass(this.handle2,"round triangle hide"),f=0;f0){for(var d,e,f,g=0,h=1;hthis.options.max?this.options.max:k},toPercentage:function(a){if(this.options.max===this.options.min)return 0;if(this.options.ticks_positions.length>0){for(var b,c,d,e=0,f=0;f0?this.options.ticks[f-1]:0,d=f>0?this.options.ticks_positions[f-1]:0,c=this.options.ticks[f],e=this.options.ticks_positions[f];break}if(f>0){var g=(a-b)/(c-b);return d+g*(e-d)}}return 100*(a-this.options.min)/(this.options.max-this.options.min)}},logarithmic:{toValue:function(a){var b=0===this.options.min?0:Math.log(this.options.min),c=Math.log(this.options.max),d=Math.exp(b+(c-b)*a/100);return Math.round(d)===this.options.max?this.options.max:(d=this.options.min+Math.round((d-this.options.min)/this.options.step)*this.options.step,dthis.options.max?this.options.max:d)},toPercentage:function(a){if(this.options.max===this.options.min)return 0;var b=Math.log(this.options.max),c=0===this.options.min?0:Math.log(this.options.min),d=0===a?0:Math.log(a);return 100*(d-c)/(b-c)}}};if(d=function(a,b){return e.call(this,a,b),this},d.prototype={_init:function(){},constructor:d,defaultOptions:{id:"",min:0,max:10,step:1,precision:0,orientation:"horizontal",value:5,range:!1,selection:"before",tooltip:"show",tooltip_split:!1,handle:"round",reversed:!1,rtl:"auto",enabled:!0,formatter:function(a){return Array.isArray(a)?a[0]+" : "+a[1]:a},natural_arrow_keys:!1,ticks:[],ticks_positions:[],ticks_labels:[],ticks_snap_bounds:0,ticks_tooltip:!1,scale:"linear",focus:!1,tooltip_position:null,labelledby:null,rangeHighlights:[]},getElement:function(){return this.sliderElem},getValue:function(){return this.options.range?this._state.value:this._state.value[0]},setValue:function(a,b,c){a||(a=0);var d=this.getValue();this._state.value=this._validateInputValue(a);var e=this._applyPrecision.bind(this);this.options.range?(this._state.value[0]=e(this._state.value[0]),this._state.value[1]=e(this._state.value[1]),this._state.value[0]=Math.max(this.options.min,Math.min(this.options.max,this._state.value[0])),this._state.value[1]=Math.max(this.options.min,Math.min(this.options.max,this._state.value[1]))):(this._state.value=e(this._state.value),this._state.value=[Math.max(this.options.min,Math.min(this.options.max,this._state.value))],this._addClass(this.handle2,"hide"),"after"===this.options.selection?this._state.value[1]=this.options.max:this._state.value[1]=this.options.min),this.options.max>this.options.min?this._state.percentage=[this._toPercentage(this._state.value[0]),this._toPercentage(this._state.value[1]),100*this.options.step/(this.options.max-this.options.min)]:this._state.percentage=[0,0,100],this._layout();var f=this.options.range?this._state.value:this._state.value[0];return this._setDataVal(f),b===!0&&this._trigger("slide",f),d!==f&&c===!0&&this._trigger("change",{oldValue:d,newValue:f}),this},destroy:function(){this._removeSliderEventHandlers(),this.sliderElem.parentNode.removeChild(this.sliderElem),this.element.style.display="",this._cleanUpEventCallbacksMap(),this.element.removeAttribute("data"),a&&(this._unbindJQueryEventHandlers(),this.$element.removeData("slider"))},disable:function(){return this._state.enabled=!1,this.handle1.removeAttribute("tabindex"),this.handle2.removeAttribute("tabindex"),this._addClass(this.sliderElem,"slider-disabled"),this._trigger("slideDisabled"),this},enable:function(){return this._state.enabled=!0,this.handle1.setAttribute("tabindex",0),this.handle2.setAttribute("tabindex",0),this._removeClass(this.sliderElem,"slider-disabled"),this._trigger("slideEnabled"),this},toggle:function(){return this._state.enabled?this.disable():this.enable(),this},isEnabled:function(){return this._state.enabled},on:function(a,b){return this._bindNonQueryEventHandler(a,b),this},off:function(b,c){a?(this.$element.off(b,c),this.$sliderElem.off(b,c)):this._unbindNonQueryEventHandler(b,c)},getAttribute:function(a){return a?this.options[a]:this.options},setAttribute:function(a,b){return this.options[a]=b,this},refresh:function(){return this._removeSliderEventHandlers(),e.call(this,this.element,this.options),a&&a.data(this.element,"slider",this),this},relayout:function(){return this._resize(),this._layout(),this},_removeSliderEventHandlers:function(){if(this.handle1.removeEventListener("keydown",this.handle1Keydown,!1),this.handle2.removeEventListener("keydown",this.handle2Keydown,!1),this.options.ticks_tooltip){for(var a=this.ticksContainer.getElementsByClassName("slider-tick"),b=0;b=0?c:this.attributes["aria-valuenow"].value,e=parseInt(d,10);b.value[0]=e,b.percentage[0]=a.options.ticks_positions[e],a._setToolTipOnMouseOver(b),a._showTooltip()};return b.addEventListener("mouseenter",d,!1),d},addMouseLeave:function(a,b){var c=function(){a._hideTooltip()};return b.addEventListener("mouseleave",c,!1),c}}},_layout:function(){var a;if(a=this.options.reversed?[100-this._state.percentage[0],this.options.range?100-this._state.percentage[1]:this._state.percentage[1]]:[this._state.percentage[0],this._state.percentage[1]],this.handle1.style[this.stylePos]=a[0]+"%",this.handle1.setAttribute("aria-valuenow",this._state.value[0]),isNaN(this.options.formatter(this._state.value[0]))&&this.handle1.setAttribute("aria-valuetext",this.options.formatter(this._state.value[0])),this.handle2.style[this.stylePos]=a[1]+"%",this.handle2.setAttribute("aria-valuenow",this._state.value[1]),isNaN(this.options.formatter(this._state.value[1]))&&this.handle2.setAttribute("aria-valuetext",this.options.formatter(this._state.value[1])),this.rangeHighlightElements.length>0&&Array.isArray(this.options.rangeHighlights)&&this.options.rangeHighlights.length>0)for(var b=0;b0){var g,h="vertical"===this.options.orientation?"height":"width";g="vertical"===this.options.orientation?"marginTop":this.options.rtl?"marginRight":"marginLeft";var i=this._state.size/(this.options.ticks.length-1);if(this.tickLabelContainer){var j=0;if(0===this.options.ticks_positions.length)"vertical"!==this.options.orientation&&(this.tickLabelContainer.style[g]=-i/2+"px"),j=this.tickLabelContainer.offsetHeight;else for(k=0;kj&&(j=this.tickLabelContainer.childNodes[k].offsetHeight);"horizontal"===this.options.orientation&&(this.sliderElem.style.marginBottom=j+"px")}for(var k=0;k=a[0]&&l<=a[1]&&this._addClass(this.ticks[k],"in-selection"):"after"===this.options.selection&&l>=a[0]?this._addClass(this.ticks[k],"in-selection"):"before"===this.options.selection&&l<=a[0]&&this._addClass(this.ticks[k],"in-selection"),this.tickLabels[k]&&(this.tickLabels[k].style[h]=i+"px","vertical"!==this.options.orientation&&void 0!==this.options.ticks_positions[k]?(this.tickLabels[k].style.position="absolute",this.tickLabels[k].style[this.stylePos]=l+"%",this.tickLabels[k].style[g]=-i/2+"px"):"vertical"===this.options.orientation&&(this.options.rtl?this.tickLabels[k].style.marginRight=this.sliderElem.offsetWidth+"px":this.tickLabels[k].style.marginLeft=this.sliderElem.offsetWidth+"px",this.tickLabelContainer.style[g]=this.sliderElem.offsetWidth/2*-1+"px"))}}var m;if(this.options.range){m=this.options.formatter(this._state.value),this._setText(this.tooltipInner,m),this.tooltip.style[this.stylePos]=(a[1]+a[0])/2+"%";var n=this.options.formatter(this._state.value[0]);this._setText(this.tooltipInner_min,n);var o=this.options.formatter(this._state.value[1]);this._setText(this.tooltipInner_max,o),this.tooltip_min.style[this.stylePos]=a[0]+"%",this.tooltip_max.style[this.stylePos]=a[1]+"%"}else m=this.options.formatter(this._state.value[0]),this._setText(this.tooltipInner,m),this.tooltip.style[this.stylePos]=a[0]+"%";if("vertical"===this.options.orientation)this.trackLow.style.top="0",this.trackLow.style.height=Math.min(a[0],a[1])+"%",this.trackSelection.style.top=Math.min(a[0],a[1])+"%",this.trackSelection.style.height=Math.abs(a[0]-a[1])+"%",this.trackHigh.style.bottom="0",this.trackHigh.style.height=100-Math.min(a[0],a[1])-Math.abs(a[0]-a[1])+"%";else{"right"===this.stylePos?this.trackLow.style.right="0":this.trackLow.style.left="0",this.trackLow.style.width=Math.min(a[0],a[1])+"%","right"===this.stylePos?this.trackSelection.style.right=Math.min(a[0],a[1])+"%":this.trackSelection.style.left=Math.min(a[0],a[1])+"%",this.trackSelection.style.width=Math.abs(a[0]-a[1])+"%","right"===this.stylePos?this.trackHigh.style.left="0":this.trackHigh.style.right="0",this.trackHigh.style.width=100-Math.min(a[0],a[1])-Math.abs(a[0]-a[1])+"%";var p=this.tooltip_min.getBoundingClientRect(),q=this.tooltip_max.getBoundingClientRect();"bottom"===this.options.tooltip_position?p.right>q.left?(this._removeClass(this.tooltip_max,"bottom"),this._addClass(this.tooltip_max,"top"),this.tooltip_max.style.top="",this.tooltip_max.style.bottom="22px"):(this._removeClass(this.tooltip_max,"top"),this._addClass(this.tooltip_max,"bottom"),this.tooltip_max.style.top=this.tooltip_min.style.top,this.tooltip_max.style.bottom=""):p.right>q.left?(this._removeClass(this.tooltip_max,"top"),this._addClass(this.tooltip_max,"bottom"),this.tooltip_max.style.top="18px"):(this._removeClass(this.tooltip_max,"bottom"),this._addClass(this.tooltip_max,"top"),this.tooltip_max.style.top=this.tooltip_min.style.top)}},_createHighlightRange:function(a,b){return this._isHighlightRange(a,b)?a>b?{start:b,size:a-b}:{start:a,size:b-a}:null},_isHighlightRange:function(a,b){return a>=0&&100>=a&&b>=0&&100>=b?!0:!1},_resize:function(a){this._state.offset=this._offset(this.sliderElem),this._state.size=this.sliderElem[this.sizePos],this._layout()},_removeProperty:function(a,b){a.style.removeProperty?a.style.removeProperty(b):a.style.removeAttribute(b)},_mousedown:function(a){if(!this._state.enabled)return!1;this._state.offset=this._offset(this.sliderElem),this._state.size=this.sliderElem[this.sizePos];var b=this._getPercentage(a);if(this.options.range){var c=Math.abs(this._state.percentage[0]-b),d=Math.abs(this._state.percentage[1]-b);this._state.dragged=d>c?0:1,this._adjustPercentageForRangeSliders(b)}else this._state.dragged=0;this._state.percentage[this._state.dragged]=b,this._layout(),this.touchCapable&&(document.removeEventListener("touchmove",this.mousemove,!1),document.removeEventListener("touchend",this.mouseup,!1)),this.mousemove&&document.removeEventListener("mousemove",this.mousemove,!1),this.mouseup&&document.removeEventListener("mouseup",this.mouseup,!1),this.mousemove=this._mousemove.bind(this),this.mouseup=this._mouseup.bind(this),this.touchCapable&&(document.addEventListener("touchmove",this.mousemove,!1),document.addEventListener("touchend",this.mouseup,!1)),document.addEventListener("mousemove",this.mousemove,!1),document.addEventListener("mouseup",this.mouseup,!1),this._state.inDrag=!0;var e=this._calculateValue();return this._trigger("slideStart",e),this._setDataVal(e),this.setValue(e,!1,!0),a.returnValue=!1,this.options.focus&&this._triggerFocusOnHandle(this._state.dragged),!0},_touchstart:function(a){if(void 0===a.changedTouches)return void this._mousedown(a);var b=a.changedTouches[0];this.touchX=b.pageX,this.touchY=b.pageY},_triggerFocusOnHandle:function(a){0===a&&this.handle1.focus(),1===a&&this.handle2.focus()},_keydown:function(a,b){if(!this._state.enabled)return!1;var c;switch(b.keyCode){case 37:case 40:c=-1;break;case 39:case 38:c=1}if(c){if(this.options.natural_arrow_keys){var d="vertical"===this.options.orientation&&!this.options.reversed,e="horizontal"===this.options.orientation&&this.options.reversed;(d||e)&&(c=-c)}var f=this._state.value[a]+c*this.options.step,g=f/this.options.max*100;if(this._state.keyCtrl=a,this.options.range){this._adjustPercentageForRangeSliders(g);var h=this._state.keyCtrl?this._state.value[0]:f,i=this._state.keyCtrl?f:this._state.value[1];f=[h,i]}return this._trigger("slideStart",f),this._setDataVal(f),this.setValue(f,!0,!0),this._setDataVal(f),this._trigger("slideStop",f),this._layout(),this._pauseEvent(b),delete this._state.keyCtrl,!1}},_pauseEvent:function(a){a.stopPropagation&&a.stopPropagation(),a.preventDefault&&a.preventDefault(),a.cancelBubble=!0,a.returnValue=!1},_mousemove:function(a){if(!this._state.enabled)return!1;var b=this._getPercentage(a);this._adjustPercentageForRangeSliders(b),this._state.percentage[this._state.dragged]=b,this._layout();var c=this._calculateValue(!0);return this.setValue(c,!0,!0),!1},_touchmove:function(a){if(void 0!==a.changedTouches){var b=a.changedTouches[0],c=b.pageX-this.touchX,d=b.pageY-this.touchY;this._state.inDrag||("vertical"===this.options.orientation&&5>=c&&c>=-5&&(d>=15||-15>=d)?this._mousedown(a):5>=d&&d>=-5&&(c>=15||-15>=c)&&this._mousedown(a))}},_adjustPercentageForRangeSliders:function(a){if(this.options.range){var b=this._getNumDigitsAfterDecimalPlace(a);b=b?b-1:0;var c=this._applyToFixedAndParseFloat(a,b);0===this._state.dragged&&this._applyToFixedAndParseFloat(this._state.percentage[1],b)c?(this._state.percentage[1]=this._state.percentage[0],this._state.dragged=0):0===this._state.keyCtrl&&this._state.value[1]/this.options.max*100a&&(this._state.percentage[1]=this._state.percentage[0],this._state.keyCtrl=0,this.handle1.focus())}},_mouseup:function(){if(!this._state.enabled)return!1;this.touchCapable&&(document.removeEventListener("touchmove",this.mousemove,!1),document.removeEventListener("touchend",this.mouseup,!1)),document.removeEventListener("mousemove",this.mousemove,!1),document.removeEventListener("mouseup",this.mouseup,!1),this._state.inDrag=!1,this._state.over===!1&&this._hideTooltip();var a=this._calculateValue(!0);return this._layout(),this._setDataVal(a),this._trigger("slideStop",a),!1},_calculateValue:function(a){var b;if(this.options.range?(b=[this.options.min,this.options.max],0!==this._state.percentage[0]&&(b[0]=this._toValue(this._state.percentage[0]),b[0]=this._applyPrecision(b[0])),100!==this._state.percentage[1]&&(b[1]=this._toValue(this._state.percentage[1]),b[1]=this._applyPrecision(b[1]))):(b=this._toValue(this._state.percentage[0]),b=parseFloat(b),b=this._applyPrecision(b)),a){for(var c=[b,1/0],d=0;d - + + + + + @@ -22,6 +26,7 @@ .info-table pre { padding: 6px; margin: 4px; + white-space: unset; } .info-table td { diff --git a/templates/search.html b/templates/search.html index 4e106a1..f7e33ff 100644 --- a/templates/search.html +++ b/templates/search.html @@ -29,6 +29,11 @@ background-color: #AA99C9; } + .badge-audio { + color: #FFFFFF; + background-color: #00ADEF; + } + .badge-resolution { color: #212529; background-color: #FFC107; @@ -92,6 +97,16 @@ .hl { color: red; } + + .content-div { + font-family: SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; + font-size: 13px; + padding: 1em; + background-color: #f5f5f5; + border: 1px solid #ccc; + border-radius: 4px; + margin: 3px; + }
@@ -99,17 +114,32 @@
{#
An excellent form
#}
-
-
-
-
- - -
+ +
+ +
@@ -130,7 +160,12 @@ var stat = document.createElement("p"); stat.appendChild(document.createTextNode(searchResult["hits"]["total"] + " results in " + searchResult["took"] + "ms")); + + var sizeStat = document.createElement("span"); + sizeStat.appendChild(document.createTextNode(humanFileSize(searchResult["aggregations"]["total_size"]["value"]))); + statsCardBody.appendChild(stat); + statsCardBody.appendChild(sizeStat); statsCard.appendChild(statsCardBody); return statsCard; @@ -284,9 +319,15 @@ //Title var title = document.createElement("p"); title.setAttribute("class", "file-title"); - var extention = hit["_source"].hasOwnProperty("extension") && hit["_source"]["extension"] !== null ? "." + hit["_source"]["extension"] : ""; - title.insertAdjacentHTML('afterbegin', hit["highlight"]["name"] + extention); - title.setAttribute("title", hit["_source"]["path"]); + var extension = hit["_source"].hasOwnProperty("extension") && hit["_source"]["extension"] !== "" ? "." + hit["_source"]["extension"] : ""; + + if (hit.hasOwnProperty("highlight") && hit["highlight"].hasOwnProperty("name")) { + title.insertAdjacentHTML('afterbegin', hit["highlight"]["name"] + extension); + } else { + title.appendChild(document.createTextNode(hit["_source"]["name"])); + } + + title.setAttribute("title", hit["_source"]["path"] + hit["_source"]["name"] + extension); docCard.appendChild(title); var tagContainer = document.createElement("div"); @@ -323,7 +364,9 @@ //Resolution var resolutionBadge = document.createElement("span"); resolutionBadge.setAttribute("class", "badge badge-resolution"); - resolutionBadge.appendChild(document.createTextNode(hit["_source"]["width"] + "x" + hit["_source"]["height"])); + if (hit["_source"].hasOwnProperty("width")) { + resolutionBadge.appendChild(document.createTextNode(hit["_source"]["width"] + "x" + hit["_source"]["height"])); + } thumbnailOverlay.appendChild(resolutionBadge); var format = hit["_source"]["format"]; @@ -365,6 +408,13 @@ formatTag.appendChild(document.createTextNode(format)); tags.push(formatTag); + break; + case "audio": + formatTag = document.createElement("span"); + formatTag.setAttribute("class", "badge badge-pill badge-audio"); + formatTag.appendChild(document.createTextNode(hit["_source"]["format_name"])); + tags.push(formatTag); + break; } @@ -372,31 +422,38 @@ if (hit.hasOwnProperty("highlight") && hit["highlight"].hasOwnProperty("content")) { var contentDiv = document.createElement("div"); - contentDiv.innerHTML = hit["highlight"]["content"][0]; + contentDiv.setAttribute("class", "content-div bg-light"); + contentDiv.insertAdjacentHTML('afterbegin', hit["highlight"]["content"][0]); docCard.appendChild(contentDiv); } - //Size tag - var sizeTag = document.createElement("small"); - sizeTag.appendChild(document.createTextNode(humanFileSize(hit["_source"]["size"]))); - sizeTag.setAttribute("class", "text-muted"); + //Audio + if (mimeCategory === "audio") { + + } + + if (thumbnail !== null) { + imgWrapper.appendChild(thumbnail); + docCard.appendChild(imgWrapper); + } + if (thumbnailOverlay !== null) { + imgWrapper.appendChild(thumbnailOverlay); + } for (var i = 0; i < tags.length; i++) { tagContainer.appendChild(tags[i]); } - tagContainer.appendChild(sizeTag); - if (thumbnail !== null) { - imgWrapper.appendChild(thumbnail); - docCard.appendChild(imgWrapper); - - } - if (thumbnailOverlay !== null) { - imgWrapper.appendChild(thumbnailOverlay); - } } + //Size tag + var sizeTag = document.createElement("small"); + sizeTag.appendChild(document.createTextNode(humanFileSize(hit["_source"]["size"]))); + sizeTag.setAttribute("class", "text-muted"); + tagContainer.appendChild(sizeTag); + + //Download button downloadPopover(docCard, hit["_id"]); @@ -463,6 +520,8 @@ } }); + var pathAutoComplete; + searchBar.addEventListener("keyup", function () { //Clear old search results @@ -487,11 +546,21 @@ //Search stats searchResults.appendChild(makeStatsCard(searchResult)); + //Autocomplete + if (searchResult.hasOwnProperty("suggest") && searchResult["suggest"].hasOwnProperty("path")) { + pathAutoComplete = []; + for (var i = 0; i < searchResult["suggest"]["path"][0]["options"].length; i++) { + pathAutoComplete.push(searchResult["suggest"]["path"][0]["options"][i].text) + } + } + + //Setup page var resultContainer = makeResultContainer(); searchResults.appendChild(resultContainer); //Insert search results (hits) + docCount = 0; insertHits(resultContainer, searchResult["hits"]["hits"]); //Initialise download/view button popover @@ -505,5 +574,4 @@
- {% endblock body %} \ No newline at end of file diff --git a/templates/task.html b/templates/task.html index 2dcfe48..fc22a31 100644 --- a/templates/task.html +++ b/templates/task.html @@ -79,14 +79,22 @@ try { - var bar = document.getElementById("task-bar-" + currentTask.id); - bar.setAttribute("style", "width: " + percent + "%;"); - document.getElementById("task-label-" + currentTask.id).innerHTML = currentTask.parsed + " / " + currentTask.total + " (" + percent.toFixed(2) + "%)"; + if (currentTask.total === 0) { - if (percent === 100) { - bar.classList.add("bg-success") + document.getElementById("task-label-" + currentTask.id).innerHTML = "Calculating file count..."; + + } else { + var bar = document.getElementById("task-bar-" + currentTask.id); + bar.setAttribute("style", "width: " + percent + "%;"); + document.getElementById("task-label-" + currentTask.id).innerHTML = currentTask.parsed + " / " + currentTask.total + " (" + percent.toFixed(2) + "%)"; + + if (percent === 100) { + bar.classList.add("bg-success") + } } + + } catch (e) { window.reload(); } diff --git a/thumbnail.py b/thumbnail.py index 602d7d8..cf6b9d8 100644 --- a/thumbnail.py +++ b/thumbnail.py @@ -9,18 +9,18 @@ class ThumbnailGenerator: def __init__(self, size, quality=85, color="FF00FF"): self.size = (size, size) - self.mime_guesser = ContentMimeGuesser() self.quality = quality self.color = tuple(bytes.fromhex(color)) - def generate(self, path, dest_path): + def generate(self, path, dest_path, mime): - mime = self.mime_guesser.guess_mime(path) + if mime is None: + return if mime.startswith("image"): + try: self.generate_image(path, dest_path) - pass except OSError: print("Not an image " + path) @@ -36,16 +36,17 @@ class ThumbnailGenerator: except Exception as e: print("Couldn't make thumbnail for " + path) - def generate_all(self, docs, dest_path, counter: Value=None): + def generate_all(self, docs, dest_path, counter: Value=None): os.makedirs(dest_path, exist_ok=True) for doc in docs: - full_path = os.path.join(doc["_source"]["path"], doc["_source"]["name"]) + extension = "" if doc["_source"]["extension"] == "" else "." + doc["_source"]["extension"] + full_path = os.path.join(doc["_source"]["path"], doc["_source"]["name"] + extension) - if os.path.isfile(full_path): - self.generate(full_path, os.path.join(dest_path, doc["_id"])) + if os.path.isfile(full_path) and "mime" in doc["_source"]: + self.generate(full_path, os.path.join(dest_path, doc["_id"]), doc["_source"]["mime"]) if counter is not None: counter.value += 1 @@ -54,6 +55,11 @@ class ThumbnailGenerator: with open(path, "rb") as image_file: with Image.open(image_file) as image: + # https://stackoverflow.com/questions/43978819 + if image.mode == "I;16": + image.mode = "I" + image.point(lambda i: i * (1. / 256)).convert('L') + image.thumbnail(self.size, Image.BICUBIC) canvas = Image.new("RGB", image.size, self.color) @@ -68,4 +74,3 @@ class ThumbnailGenerator: canvas.save(dest_path, "JPEG", quality=self.quality, optimize=True) canvas.close() -