Initial commit (squashed)
							
								
								
									
										47
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					# See http://help.github.com/ignore-files/ for more about ignoring files.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# compiled output
 | 
				
			||||||
 | 
					**/dist
 | 
				
			||||||
 | 
					**/tmp
 | 
				
			||||||
 | 
					**out-tsc
 | 
				
			||||||
 | 
					# Only exists if Bazel was run
 | 
				
			||||||
 | 
					**bazel-out
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# dependencies
 | 
				
			||||||
 | 
					**/node_modules
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# profiling files
 | 
				
			||||||
 | 
					chrome-profiler-events.json
 | 
				
			||||||
 | 
					speed-measure-plugin.json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# IDEs and editors
 | 
				
			||||||
 | 
					/.idea
 | 
				
			||||||
 | 
					.project
 | 
				
			||||||
 | 
					.classpath
 | 
				
			||||||
 | 
					.c9/
 | 
				
			||||||
 | 
					*.launch
 | 
				
			||||||
 | 
					.settings/
 | 
				
			||||||
 | 
					*.sublime-workspace
 | 
				
			||||||
 | 
					*.iml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# IDE - VSCode
 | 
				
			||||||
 | 
					.vscode/*
 | 
				
			||||||
 | 
					!.vscode/settings.json
 | 
				
			||||||
 | 
					!.vscode/tasks.json
 | 
				
			||||||
 | 
					!.vscode/launch.json
 | 
				
			||||||
 | 
					!.vscode/extensions.json
 | 
				
			||||||
 | 
					.history/*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# misc
 | 
				
			||||||
 | 
					/.sass-cache
 | 
				
			||||||
 | 
					/connect.lock
 | 
				
			||||||
 | 
					/coverage
 | 
				
			||||||
 | 
					/libpeerconnection.log
 | 
				
			||||||
 | 
					npm-debug.log
 | 
				
			||||||
 | 
					yarn-error.log
 | 
				
			||||||
 | 
					testem.log
 | 
				
			||||||
 | 
					/typings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# System Files
 | 
				
			||||||
 | 
					.DS_Store
 | 
				
			||||||
 | 
					Thumbs.db
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								diagram.dia
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										37
									
								
								hashdemo.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					import imagehash
 | 
				
			||||||
 | 
					from PIL import Image
 | 
				
			||||||
 | 
					import numpy
 | 
				
			||||||
 | 
					from imagehash import ImageHash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def mhash(image, hash_size=8):
 | 
				
			||||||
 | 
					    if hash_size < 2:
 | 
				
			||||||
 | 
					        raise ValueError("Hash size must be greater than or equal to 2")
 | 
				
			||||||
 | 
					    image = image.convert("L").resize((hash_size, hash_size), Image.ANTIALIAS)
 | 
				
			||||||
 | 
					    pixels = numpy.asarray(image)
 | 
				
			||||||
 | 
					    m = pixels.median()
 | 
				
			||||||
 | 
					    diff = pixels > m
 | 
				
			||||||
 | 
					    return ImageHash(diff)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					im = Image.open("/home/simon/Downloads/a.jpg")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					im.resize((800,800), Image.NEAREST).save("out.png")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h = imagehash.whash(im, hash_size=8).hash
 | 
				
			||||||
 | 
					size = h.shape[::-1]
 | 
				
			||||||
 | 
					databytes = numpy.packbits(h, axis=1)
 | 
				
			||||||
 | 
					Image.frombytes(mode='1', size=size, data=databytes).resize((800, 800), Image.NEAREST).save("out8.png")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h = imagehash.whash(im, hash_size=16).hash
 | 
				
			||||||
 | 
					size = h.shape[::-1]
 | 
				
			||||||
 | 
					databytes = numpy.packbits(h, axis=1)
 | 
				
			||||||
 | 
					Image.frombytes(mode='1', size=size, data=databytes).resize((800, 800), Image.NEAREST).save("out16.png")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h = imagehash.whash(im, hash_size=32).hash
 | 
				
			||||||
 | 
					size = h.shape[::-1]
 | 
				
			||||||
 | 
					databytes = numpy.packbits(h, axis=1)
 | 
				
			||||||
 | 
					Image.frombytes(mode='1', size=size, data=databytes).resize((800, 800), Image.NEAREST).save("out32.png")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					os.system("convert +append out.png out8.png out16.png out32.png whash.png")
 | 
				
			||||||
							
								
								
									
										13
									
								
								imhashdb-frontend/.editorconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					# Editor configuration, see https://editorconfig.org
 | 
				
			||||||
 | 
					root = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[*]
 | 
				
			||||||
 | 
					charset = utf-8
 | 
				
			||||||
 | 
					indent_style = space
 | 
				
			||||||
 | 
					indent_size = 2
 | 
				
			||||||
 | 
					insert_final_newline = true
 | 
				
			||||||
 | 
					trim_trailing_whitespace = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[*.md]
 | 
				
			||||||
 | 
					max_line_length = off
 | 
				
			||||||
 | 
					trim_trailing_whitespace = false
 | 
				
			||||||
							
								
								
									
										27
									
								
								imhashdb-frontend/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					# ImhashdbFrontend
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.3.9.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Development server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Code scaffolding
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Running unit tests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Running end-to-end tests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Further help
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
 | 
				
			||||||
							
								
								
									
										138
									
								
								imhashdb-frontend/angular.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,138 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
 | 
				
			||||||
 | 
					  "version": 1,
 | 
				
			||||||
 | 
					  "newProjectRoot": "projects",
 | 
				
			||||||
 | 
					  "projects": {
 | 
				
			||||||
 | 
					    "imhashdb-frontend": {
 | 
				
			||||||
 | 
					      "root": "",
 | 
				
			||||||
 | 
					      "sourceRoot": "src",
 | 
				
			||||||
 | 
					      "projectType": "application",
 | 
				
			||||||
 | 
					      "prefix": "app",
 | 
				
			||||||
 | 
					      "schematics": {},
 | 
				
			||||||
 | 
					      "architect": {
 | 
				
			||||||
 | 
					        "build": {
 | 
				
			||||||
 | 
					          "builder": "@angular-devkit/build-angular:browser",
 | 
				
			||||||
 | 
					          "options": {
 | 
				
			||||||
 | 
					            "outputPath": "dist/imhashdb-frontend",
 | 
				
			||||||
 | 
					            "index": "src/index.html",
 | 
				
			||||||
 | 
					            "main": "src/main.ts",
 | 
				
			||||||
 | 
					            "polyfills": "src/polyfills.ts",
 | 
				
			||||||
 | 
					            "tsConfig": "src/tsconfig.app.json",
 | 
				
			||||||
 | 
					            "assets": [
 | 
				
			||||||
 | 
					              "src/favicon.ico",
 | 
				
			||||||
 | 
					              "src/assets"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "styles": [
 | 
				
			||||||
 | 
					              "./node_modules/@angular/material/prebuilt-themes/pink-bluegrey.css",
 | 
				
			||||||
 | 
					              "src/styles.css"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "scripts": [],
 | 
				
			||||||
 | 
					            "es5BrowserSupport": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "configurations": {
 | 
				
			||||||
 | 
					            "production": {
 | 
				
			||||||
 | 
					              "fileReplacements": [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                  "replace": "src/environments/environment.ts",
 | 
				
			||||||
 | 
					                  "with": "src/environments/environment.prod.ts"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					              ],
 | 
				
			||||||
 | 
					              "optimization": true,
 | 
				
			||||||
 | 
					              "outputHashing": "all",
 | 
				
			||||||
 | 
					              "sourceMap": false,
 | 
				
			||||||
 | 
					              "extractCss": true,
 | 
				
			||||||
 | 
					              "namedChunks": false,
 | 
				
			||||||
 | 
					              "aot": true,
 | 
				
			||||||
 | 
					              "extractLicenses": true,
 | 
				
			||||||
 | 
					              "vendorChunk": false,
 | 
				
			||||||
 | 
					              "buildOptimizer": true,
 | 
				
			||||||
 | 
					              "budgets": [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                  "type": "initial",
 | 
				
			||||||
 | 
					                  "maximumWarning": "2mb",
 | 
				
			||||||
 | 
					                  "maximumError": "5mb"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					              ]
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "serve": {
 | 
				
			||||||
 | 
					          "builder": "@angular-devkit/build-angular:dev-server",
 | 
				
			||||||
 | 
					          "options": {
 | 
				
			||||||
 | 
					            "browserTarget": "imhashdb-frontend:build"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "configurations": {
 | 
				
			||||||
 | 
					            "production": {
 | 
				
			||||||
 | 
					              "browserTarget": "imhashdb-frontend:build:production"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "extract-i18n": {
 | 
				
			||||||
 | 
					          "builder": "@angular-devkit/build-angular:extract-i18n",
 | 
				
			||||||
 | 
					          "options": {
 | 
				
			||||||
 | 
					            "browserTarget": "imhashdb-frontend:build"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "test": {
 | 
				
			||||||
 | 
					          "builder": "@angular-devkit/build-angular:karma",
 | 
				
			||||||
 | 
					          "options": {
 | 
				
			||||||
 | 
					            "main": "src/test.ts",
 | 
				
			||||||
 | 
					            "polyfills": "src/polyfills.ts",
 | 
				
			||||||
 | 
					            "tsConfig": "src/tsconfig.spec.json",
 | 
				
			||||||
 | 
					            "karmaConfig": "src/karma.conf.js",
 | 
				
			||||||
 | 
					            "styles": [
 | 
				
			||||||
 | 
					              "./node_modules/@angular/material/prebuilt-themes/pink-bluegrey.css",
 | 
				
			||||||
 | 
					              "src/styles.css"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "scripts": [],
 | 
				
			||||||
 | 
					            "assets": [
 | 
				
			||||||
 | 
					              "src/favicon.ico",
 | 
				
			||||||
 | 
					              "src/assets"
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "lint": {
 | 
				
			||||||
 | 
					          "builder": "@angular-devkit/build-angular:tslint",
 | 
				
			||||||
 | 
					          "options": {
 | 
				
			||||||
 | 
					            "tsConfig": [
 | 
				
			||||||
 | 
					              "src/tsconfig.app.json",
 | 
				
			||||||
 | 
					              "src/tsconfig.spec.json"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "exclude": [
 | 
				
			||||||
 | 
					              "**/node_modules/**"
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "imhashdb-frontend-e2e": {
 | 
				
			||||||
 | 
					      "root": "e2e/",
 | 
				
			||||||
 | 
					      "projectType": "application",
 | 
				
			||||||
 | 
					      "prefix": "",
 | 
				
			||||||
 | 
					      "architect": {
 | 
				
			||||||
 | 
					        "e2e": {
 | 
				
			||||||
 | 
					          "builder": "@angular-devkit/build-angular:protractor",
 | 
				
			||||||
 | 
					          "options": {
 | 
				
			||||||
 | 
					            "protractorConfig": "e2e/protractor.conf.js",
 | 
				
			||||||
 | 
					            "devServerTarget": "imhashdb-frontend:serve"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "configurations": {
 | 
				
			||||||
 | 
					            "production": {
 | 
				
			||||||
 | 
					              "devServerTarget": "imhashdb-frontend:serve:production"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "lint": {
 | 
				
			||||||
 | 
					          "builder": "@angular-devkit/build-angular:tslint",
 | 
				
			||||||
 | 
					          "options": {
 | 
				
			||||||
 | 
					            "tsConfig": "e2e/tsconfig.e2e.json",
 | 
				
			||||||
 | 
					            "exclude": [
 | 
				
			||||||
 | 
					              "**/node_modules/**"
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "defaultProject": "imhashdb-frontend"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										28
									
								
								imhashdb-frontend/e2e/protractor.conf.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					// Protractor configuration file, see link for more information
 | 
				
			||||||
 | 
					// https://github.com/angular/protractor/blob/master/lib/config.ts
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { SpecReporter } = require('jasmine-spec-reporter');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exports.config = {
 | 
				
			||||||
 | 
					  allScriptsTimeout: 11000,
 | 
				
			||||||
 | 
					  specs: [
 | 
				
			||||||
 | 
					    './src/**/*.e2e-spec.ts'
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  capabilities: {
 | 
				
			||||||
 | 
					    'browserName': 'chrome'
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  directConnect: true,
 | 
				
			||||||
 | 
					  baseUrl: 'http://localhost:4200/',
 | 
				
			||||||
 | 
					  framework: 'jasmine',
 | 
				
			||||||
 | 
					  jasmineNodeOpts: {
 | 
				
			||||||
 | 
					    showColors: true,
 | 
				
			||||||
 | 
					    defaultTimeoutInterval: 30000,
 | 
				
			||||||
 | 
					    print: function() {}
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  onPrepare() {
 | 
				
			||||||
 | 
					    require('ts-node').register({
 | 
				
			||||||
 | 
					      project: require('path').join(__dirname, './tsconfig.e2e.json')
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										23
									
								
								imhashdb-frontend/e2e/src/app.e2e-spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					import { AppPage } from './app.po';
 | 
				
			||||||
 | 
					import { browser, logging } from 'protractor';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('workspace-project App', () => {
 | 
				
			||||||
 | 
					  let page: AppPage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeEach(() => {
 | 
				
			||||||
 | 
					    page = new AppPage();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should display welcome message', () => {
 | 
				
			||||||
 | 
					    page.navigateTo();
 | 
				
			||||||
 | 
					    expect(page.getTitleText()).toEqual('Welcome to imhashdb-frontend!');
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  afterEach(async () => {
 | 
				
			||||||
 | 
					    // Assert that there are no errors emitted from the browser
 | 
				
			||||||
 | 
					    const logs = await browser.manage().logs().get(logging.Type.BROWSER);
 | 
				
			||||||
 | 
					    expect(logs).not.toContain(jasmine.objectContaining({
 | 
				
			||||||
 | 
					      level: logging.Level.SEVERE,
 | 
				
			||||||
 | 
					    } as logging.Entry));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										11
									
								
								imhashdb-frontend/e2e/src/app.po.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					import { browser, by, element } from 'protractor';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class AppPage {
 | 
				
			||||||
 | 
					  navigateTo() {
 | 
				
			||||||
 | 
					    return browser.get(browser.baseUrl) as Promise<any>;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getTitleText() {
 | 
				
			||||||
 | 
					    return element(by.css('app-root h1')).getText() as Promise<string>;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										13
									
								
								imhashdb-frontend/e2e/tsconfig.e2e.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "extends": "../tsconfig.json",
 | 
				
			||||||
 | 
					  "compilerOptions": {
 | 
				
			||||||
 | 
					    "outDir": "../out-tsc/app",
 | 
				
			||||||
 | 
					    "module": "commonjs",
 | 
				
			||||||
 | 
					    "target": "es5",
 | 
				
			||||||
 | 
					    "types": [
 | 
				
			||||||
 | 
					      "jasmine",
 | 
				
			||||||
 | 
					      "jasminewd2",
 | 
				
			||||||
 | 
					      "node"
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										13700
									
								
								imhashdb-frontend/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										54
									
								
								imhashdb-frontend/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,54 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "name": "imhashdb-frontend",
 | 
				
			||||||
 | 
					  "version": "1.0.0",
 | 
				
			||||||
 | 
					  "scripts": {
 | 
				
			||||||
 | 
					    "ng": "ng",
 | 
				
			||||||
 | 
					    "start": "ng serve",
 | 
				
			||||||
 | 
					    "build": "ng build",
 | 
				
			||||||
 | 
					    "test": "ng test",
 | 
				
			||||||
 | 
					    "lint": "ng lint",
 | 
				
			||||||
 | 
					    "e2e": "ng e2e"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "private": true,
 | 
				
			||||||
 | 
					  "dependencies": {
 | 
				
			||||||
 | 
					    "@angular/animations": "^9.1.1",
 | 
				
			||||||
 | 
					    "@angular/cdk": "^9.2.0",
 | 
				
			||||||
 | 
					    "@angular/common": "~9.1.1",
 | 
				
			||||||
 | 
					    "@angular/compiler": "~9.1.1",
 | 
				
			||||||
 | 
					    "@angular/core": "~9.1.1",
 | 
				
			||||||
 | 
					    "@angular/forms": "~9.1.1",
 | 
				
			||||||
 | 
					    "@angular/material": "^9.2.0",
 | 
				
			||||||
 | 
					    "@angular/platform-browser": "~9.1.1",
 | 
				
			||||||
 | 
					    "@angular/platform-browser-dynamic": "~9.1.1",
 | 
				
			||||||
 | 
					    "@angular/router": "~9.1.1",
 | 
				
			||||||
 | 
					    "@ngx-translate/core": "^12.1.2",
 | 
				
			||||||
 | 
					    "@ngx-translate/http-loader": "^4.0.0",
 | 
				
			||||||
 | 
					    "core-js": "^3.6.5",
 | 
				
			||||||
 | 
					    "lodash": "^4.17.15",
 | 
				
			||||||
 | 
					    "moment": "^2.24.0",
 | 
				
			||||||
 | 
					    "rxjs": "~6.5.5",
 | 
				
			||||||
 | 
					    "tslib": "^1.11.1",
 | 
				
			||||||
 | 
					    "zone.js": "~0.10.3"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "devDependencies": {
 | 
				
			||||||
 | 
					    "@angular-devkit/build-angular": "~0.901.1",
 | 
				
			||||||
 | 
					    "@angular/cli": "~9.1.1",
 | 
				
			||||||
 | 
					    "@angular/compiler-cli": "~9.1.1",
 | 
				
			||||||
 | 
					    "@angular/language-service": "~9.1.1",
 | 
				
			||||||
 | 
					    "@types/node": "~13.11.1",
 | 
				
			||||||
 | 
					    "@types/jasmine": "~3.5.10",
 | 
				
			||||||
 | 
					    "@types/jasminewd2": "~2.0.8",
 | 
				
			||||||
 | 
					    "codelyzer": "~5.2.2",
 | 
				
			||||||
 | 
					    "jasmine-core": "~3.5.0",
 | 
				
			||||||
 | 
					    "jasmine-spec-reporter": "~5.0.1",
 | 
				
			||||||
 | 
					    "karma": "~5.0.1",
 | 
				
			||||||
 | 
					    "karma-chrome-launcher": "~2.2.0",
 | 
				
			||||||
 | 
					    "karma-coverage-istanbul-reporter": "~2.1.1",
 | 
				
			||||||
 | 
					    "karma-jasmine": "~3.1.1",
 | 
				
			||||||
 | 
					    "karma-jasmine-html-reporter": "^1.5.3",
 | 
				
			||||||
 | 
					    "protractor": "~5.4.3",
 | 
				
			||||||
 | 
					    "ts-node": "~8.8.2",
 | 
				
			||||||
 | 
					    "tslint": "~6.1.1",
 | 
				
			||||||
 | 
					    "typescript": "^3.8.3"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										24
									
								
								imhashdb-frontend/src/app/about/about.component.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					figure {
 | 
				
			||||||
 | 
					  text-align: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					figure img {
 | 
				
			||||||
 | 
					  max-height: 500px;
 | 
				
			||||||
 | 
					  max-width: 100%;
 | 
				
			||||||
 | 
					  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					figure figcaption {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pre {
 | 
				
			||||||
 | 
					  background: #00000050;
 | 
				
			||||||
 | 
					  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
 | 
				
			||||||
 | 
					  padding: 1em;
 | 
				
			||||||
 | 
					  overflow-x: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.mat-card p, .mat-card h2 {
 | 
				
			||||||
 | 
					  font-size: 110% !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										86
									
								
								imhashdb-frontend/src/app/about/about.component.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,86 @@
 | 
				
			|||||||
 | 
					<mat-card>
 | 
				
			||||||
 | 
					  <mat-card-title>{{"about.title" | translate}}</mat-card-title>
 | 
				
			||||||
 | 
					  <mat-card-subtitle>{{"about.subtitle" | translate}}</mat-card-subtitle>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <mat-card-content>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <h2>Perceptual hashing</h2>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <app-git-repo name="fastimagehash (C/C++)" path="simon987/fastimagehash"></app-git-repo>
 | 
				
			||||||
 | 
					    <app-git-repo name="fastimagehash-go (C/go)" path="simon987/fastimagehash-go"></app-git-repo>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <figure>
 | 
				
			||||||
 | 
					      <img src="assets/dhash.png">
 | 
				
			||||||
 | 
					      <figcaption>Difference hash (dhash)</figcaption>
 | 
				
			||||||
 | 
					    </figure>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <figure>
 | 
				
			||||||
 | 
					      <img src="assets/mhash.png">
 | 
				
			||||||
 | 
					      <figcaption>Median hash (mhash)</figcaption>
 | 
				
			||||||
 | 
					    </figure>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <figure>
 | 
				
			||||||
 | 
					      <img src="assets/phash.png">
 | 
				
			||||||
 | 
					      <figcaption>phash</figcaption>
 | 
				
			||||||
 | 
					    </figure>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <figure>
 | 
				
			||||||
 | 
					      <img src="assets/whash.png">
 | 
				
			||||||
 | 
					      <figcaption>Wavelet hash (whash)</figcaption>
 | 
				
			||||||
 | 
					    </figure>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <figure>
 | 
				
			||||||
 | 
					      <img src="assets/phash_large.png">
 | 
				
			||||||
 | 
					      <figcaption>fastimagehash vs imagehash performance comparison (phash)</figcaption>
 | 
				
			||||||
 | 
					    </figure>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <h2>Hash query in PostgreSQL</h2>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <app-git-repo name="pg_hamming (C)" path="simon987/imhashdb"></app-git-repo>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <p>
 | 
				
			||||||
 | 
					      To check how similar two images hashes are, we need to compute the
 | 
				
			||||||
 | 
					      Hamming distance (or Hamming weight),
 | 
				
			||||||
 | 
					      which is the number of bits that are different in each hash.
 | 
				
			||||||
 | 
					    </p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <p>
 | 
				
			||||||
 | 
					      For a 64-bit hash, an hamming distance of 0 indicates a very strong match, while values higher
 | 
				
			||||||
 | 
					      than 10 usually suggests that the images are significantly different.
 | 
				
			||||||
 | 
					    </p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <p>
 | 
				
			||||||
 | 
					      On a processor with the POPCNT instruction (SSE4), one can calculate the Hamming distance in
 | 
				
			||||||
 | 
					      four instructions per 64-bit chunk. The domain-specific PostgreSQL module used in
 | 
				
			||||||
 | 
					      this project does not have any boundary checking or loops, and is essentially as fast
 | 
				
			||||||
 | 
					      as a typical sequential scan.
 | 
				
			||||||
 | 
					    </p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <pre><code>hash1 = 10110110
 | 
				
			||||||
 | 
					hash2 = 10010101
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mov     rax, [hash1]
 | 
				
			||||||
 | 
					xor     rax, [hash2]  ; rax = hash1 XOR hash2 = 00100011
 | 
				
			||||||
 | 
					popcnt  rax, rax      ; rax = popcount(00100011) = 3</code></pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <h2>Project overview</h2>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <app-git-repo name="imhashdb (go)" path="simon987/imhashdb"></app-git-repo>
 | 
				
			||||||
 | 
					    <app-git-repo name="imhashdb-frontend (Angular)" path="simon987/imhashdb-frontend"></app-git-repo>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <figure>
 | 
				
			||||||
 | 
					      <img src="assets/schema.png">
 | 
				
			||||||
 | 
					      <figcaption>Database schema (some hashes omitted)</figcaption>
 | 
				
			||||||
 | 
					    </figure>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <figure>
 | 
				
			||||||
 | 
					      <img src="assets/diagram.png">
 | 
				
			||||||
 | 
					      <figcaption>High level overview</figcaption>
 | 
				
			||||||
 | 
					    </figure>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <app-git-repo name="reddit_feed (Python)" path="simon987/reddit_feed"></app-git-repo>
 | 
				
			||||||
 | 
					    <app-git-repo name="chan_feed (Python)" path="simon987/chan_feed"></app-git-repo>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  </mat-card-content>
 | 
				
			||||||
 | 
					</mat-card>
 | 
				
			||||||
							
								
								
									
										15
									
								
								imhashdb-frontend/src/app/about/about.component.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					import {Component, OnInit} from "@angular/core";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Component({
 | 
				
			||||||
 | 
					  selector: "app-about",
 | 
				
			||||||
 | 
					  templateUrl: "./about.component.html",
 | 
				
			||||||
 | 
					  styleUrls: ["./about.component.css"]
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class AboutComponent implements OnInit {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor() {
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ngOnInit(): void {
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										121
									
								
								imhashdb-frontend/src/app/api.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,121 @@
 | 
				
			|||||||
 | 
					import {Injectable} from "@angular/core";
 | 
				
			||||||
 | 
					import {HttpClient} from "@angular/common/http";
 | 
				
			||||||
 | 
					import {from, from as fromPromise} from 'rxjs';
 | 
				
			||||||
 | 
					import {catchError, flatMap, map} from 'rxjs/operators';
 | 
				
			||||||
 | 
					import {Base64Service} from "./base64.service";
 | 
				
			||||||
 | 
					import * as _ from "lodash";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					export class ApiService {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // public url: string = window.location.protocol + "//" + window.location.host + "/api";
 | 
				
			||||||
 | 
					  public url: string = window.location.protocol + "//" + "192.168.1.57:8080" + "/api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private options: {
 | 
				
			||||||
 | 
					    withCredentials: true,
 | 
				
			||||||
 | 
					    responseType: "json"
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(private http: HttpClient, private b64: Base64Service) {
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  hash(data: string) {
 | 
				
			||||||
 | 
					    return from([JSON.parse('{"dhash8":{"size":8,"bytes":"1UOImJyelpY="},"dhash16":{"size":16,"bytes":"smB5M+MVSlZFZslmklOiU+Bj7GC4YW1naUNZ4yznWdc="},"dhash32":{"size":32,"bytes":"bV2VGElVixyjD48XxTSXB4ftshcdZpIT2XWeG0lomhtVNBSdNXS+HMfxtBiBoT0YReYlmZLAH7sQyy+7DJgOuQO4Dr2CeUudEHkoPHBdRDyE7kc8wq8mPuI+fT7ibDc6pWoLOlVUX3qXYU94zSKfuGlWNrqTnGW6k2VX+ZKTpnk="},"mhash8":{"size":8,"bytes":"v38/MzA4oIA="},"mhash16":{"size":16,"bytes":"/0f3d/53/1b/R78HPwcFD4AOgQ/gA8APAM4AzADKAMY="},"mhash32":{"size":32,"bytes":"+v+/cb79P3Be/j5+vv8/Pj7vLz/63Tcf//94f///fPv//3sw//9/Mf/vvxD/j3kw/w5/AP8PfgB/gH8AEIB/IACAeCAAwH4gA8B/AAfQHyAB+A8ABdxvAAR4fwAMwP4ADgB+AAgAfOAEANjwEFW08IIoZPAEIc/wBAA88AQAKOA="},"phash8":{"size":8,"bytes":"24MnOBjPszE="},"phash16":{"size":16,"bytes":"2yKDsCdsOE4Y/M+o8wkxZ0iG45+ePUDbJPmbzQd7BHw="},"phash32":{"size":32,"bytes":"2yLmAIOwBTknbGTpOE5r5hj8uffPKM3E8wm9TTEnCXZIhp02458RH549IwZA24Y4JPg9mZvNOcEHe2bPBPzPGDDgB+3w49nO4eM282/gZMLPRpK9wD4uwgaf2zXPJGzLL+CTLgjMP5i0BRm2bBIz/E2T7ID/JPg9vBnyzfwZMIg="},"whash8haar":{"size":8,"bytes":"v38/MzA4oIA="},"whash16haar":{"size":16,"bytes":"/8f3d/53/3b/R78HPwcFD4AOgQ/gA8APAM4AzADOAMY="},"whash32haar":{"size":32,"bytes":"+v+/cb79P3Be/j5+vv8/Pj7vLz/63Tcf//94f///fPv//3sw//9/Mf/vvxD/j3kw/w5/AP8PfgB/gH8AEIB/IACAeCAAwH4gA8B/AAfQHyAB+A8ABdxvAAR4fwAMwP4ADgB+AAgAfOAEANjwEFW08IIoZPAEIc/wBAA88AQAKOA="}}')])
 | 
				
			||||||
 | 
					    // return this.http.post(this.url + "/hash", {data: data}, this.options);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  query(hashType: string, hash: string, distance: number, limit: number, offset: number) {
 | 
				
			||||||
 | 
					    return fromPromise(
 | 
				
			||||||
 | 
					      this.http.post(
 | 
				
			||||||
 | 
					        this.url + "/query",
 | 
				
			||||||
 | 
					        {type: hashType, hash: hash, distance: distance, limit: limit, offset: offset},
 | 
				
			||||||
 | 
					        this.options
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    ).pipe(
 | 
				
			||||||
 | 
					      map(data => {
 | 
				
			||||||
 | 
					        data["images"].forEach(im => {
 | 
				
			||||||
 | 
					          im["sha1"] = this.b64.toHex(im["sha1"])
 | 
				
			||||||
 | 
					          im["sha256"] = this.b64.toHex(im["sha256"])
 | 
				
			||||||
 | 
					          im["md5"] = this.b64.toHex(im["md5"])
 | 
				
			||||||
 | 
					          im["meta"].forEach(ihm => {
 | 
				
			||||||
 | 
					            ihm["url"] = "https://" + ihm["url"]
 | 
				
			||||||
 | 
					            ihm["meta"]["meta"] = JSON.parse(atob(ihm["meta"]["meta"]))
 | 
				
			||||||
 | 
					            ihm["meta"]["retrieved_at"] *= 1000;
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        return data;
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  metaInfo() {
 | 
				
			||||||
 | 
					    //TODO: query server and cache
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      "reddit": {
 | 
				
			||||||
 | 
					        "background": "#ff2e00",
 | 
				
			||||||
 | 
					        "foreground": "#EEEEEE",
 | 
				
			||||||
 | 
					        "description": "Reddit"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "imgur": {
 | 
				
			||||||
 | 
					        "background": "#1BB76E",
 | 
				
			||||||
 | 
					        "foreground": "#000000",
 | 
				
			||||||
 | 
					        "description": "imgur"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  hashInfo() {
 | 
				
			||||||
 | 
					    //TODO: query server and cache
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      hashes: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          name: "Dhash",
 | 
				
			||||||
 | 
					          description: "Difference hash",
 | 
				
			||||||
 | 
					          resolution: [
 | 
				
			||||||
 | 
					            {name: "8 B", id: "dhash8", max_distance: 10},
 | 
				
			||||||
 | 
					            {name: "32 B", id: "dhash16", max_distance: 40},
 | 
				
			||||||
 | 
					            {name: "128 B", id: "dhash32", max_distance: 80},
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          name: "Phash",
 | 
				
			||||||
 | 
					          description: "Perceptual hash",
 | 
				
			||||||
 | 
					          resolution: [
 | 
				
			||||||
 | 
					            {name: "8 B", id: "phash8", max_distance: 10},
 | 
				
			||||||
 | 
					            {name: "32 B", id: "phash16", max_distance: 40},
 | 
				
			||||||
 | 
					            {name: "128 B", id: "phash32", max_distance: 80},
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          name: "Mhash",
 | 
				
			||||||
 | 
					          description: "Median hash",
 | 
				
			||||||
 | 
					          resolution: [
 | 
				
			||||||
 | 
					            {name: "8 B", id: "mhash8", max_distance: 10},
 | 
				
			||||||
 | 
					            {name: "32 B", id: "mhash16", max_distance: 40},
 | 
				
			||||||
 | 
					            {name: "128 B", id: "mhash32", max_distance: 80},
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          name: "Whash",
 | 
				
			||||||
 | 
					          description: "Wavelet hash",
 | 
				
			||||||
 | 
					          resolution: [
 | 
				
			||||||
 | 
					            {name: "8 B", id: "whash8", max_distance: 10},
 | 
				
			||||||
 | 
					            {name: "32 B", id: "whash16", max_distance: 40},
 | 
				
			||||||
 | 
					            {name: "128 B", id: "whash32", max_distance: 80},
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      presets: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          name: "16-bit",
 | 
				
			||||||
 | 
					          description: "TODO: description",
 | 
				
			||||||
 | 
					          queries: {
 | 
				
			||||||
 | 
					            phash16: {distance: 10},
 | 
				
			||||||
 | 
					            whash16: {distance: 10},
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										19
									
								
								imhashdb-frontend/src/app/app-routing.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					import {NgModule} from "@angular/core";
 | 
				
			||||||
 | 
					import {Routes, RouterModule} from "@angular/router";
 | 
				
			||||||
 | 
					import {IndexComponent} from "./index/index.component";
 | 
				
			||||||
 | 
					import {ContactComponent} from "./contact/contact.component";
 | 
				
			||||||
 | 
					import {AboutComponent} from "./about/about.component";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const routes: Routes = [
 | 
				
			||||||
 | 
					  {path: "", component: IndexComponent},
 | 
				
			||||||
 | 
					  {path: "contact", component: ContactComponent},
 | 
				
			||||||
 | 
					  {path: "about", component: AboutComponent},
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@NgModule({
 | 
				
			||||||
 | 
					  imports: [RouterModule.forRoot(routes)],
 | 
				
			||||||
 | 
					  exports: [RouterModule]
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class AppRoutingModule {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										13
									
								
								imhashdb-frontend/src/app/app.component.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					.large-nav {
 | 
				
			||||||
 | 
					  display: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media (min-width: 768px) {
 | 
				
			||||||
 | 
					  .large-nav {
 | 
				
			||||||
 | 
					    display: initial;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .small-nav {
 | 
				
			||||||
 | 
					    display: none;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										42
									
								
								imhashdb-frontend/src/app/app.component.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,42 @@
 | 
				
			|||||||
 | 
					<mat-toolbar color="primary">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <div class="large-nav">
 | 
				
			||||||
 | 
					    <button mat-button [class.mat-active]="router.url === '/'" class="nav-title"
 | 
				
			||||||
 | 
					            [routerLink]="''">{{"nav.title" | translate}}</button>
 | 
				
			||||||
 | 
					    <button mat-button [class.mat-active]="router.url === '/about'" class="nav-link"
 | 
				
			||||||
 | 
					            [routerLink]="'about'">{{"nav.about" | translate}}</button>
 | 
				
			||||||
 | 
					    <button mat-button [class.mat-active]="router.url === '/about'" class="nav-link"
 | 
				
			||||||
 | 
					            [routerLink]="'contact'">{{"nav.contact" | translate}}</button>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					  <div class="small-nav">
 | 
				
			||||||
 | 
					    <button mat-button [matMenuTriggerFor]="smallNav">
 | 
				
			||||||
 | 
					      <mat-icon>more_vert</mat-icon>
 | 
				
			||||||
 | 
					    </button>
 | 
				
			||||||
 | 
					    <mat-menu #smallNav>
 | 
				
			||||||
 | 
					      <button mat-menu-item [class.mat-active]="router.url === '/'" class="nav-title"
 | 
				
			||||||
 | 
					              [routerLink]="''">{{"nav.title" | translate}}</button>
 | 
				
			||||||
 | 
					      <button mat-button [class.mat-active]="router.url === '/about'" class="nav-link"
 | 
				
			||||||
 | 
					              [routerLink]="'about'">{{"nav.about" | translate}}</button>
 | 
				
			||||||
 | 
					      <button mat-button [class.mat-active]="router.url === '/about'" class="nav-link"
 | 
				
			||||||
 | 
					              [routerLink]="'contact'">{{"nav.contact" | translate}}</button>
 | 
				
			||||||
 | 
					    </mat-menu>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <span class="spacer"></span>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <!-- Lang select-->
 | 
				
			||||||
 | 
					  <button mat-button [matMenuTriggerFor]="langMenu">
 | 
				
			||||||
 | 
					    <mat-icon>language</mat-icon>
 | 
				
			||||||
 | 
					    {{"nav.lang_select" | translate}}
 | 
				
			||||||
 | 
					    <mat-icon>arrow_drop_down</mat-icon>
 | 
				
			||||||
 | 
					  </button>
 | 
				
			||||||
 | 
					  <mat-menu #langMenu>
 | 
				
			||||||
 | 
					    <button mat-menu-item *ngFor="let lang of langList" (click)="langChange(lang)">
 | 
				
			||||||
 | 
					      {{lang.display}}
 | 
				
			||||||
 | 
					    </button>
 | 
				
			||||||
 | 
					  </mat-menu>
 | 
				
			||||||
 | 
					</mat-toolbar>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div class="container">
 | 
				
			||||||
 | 
					  <router-outlet></router-outlet>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
							
								
								
									
										34
									
								
								imhashdb-frontend/src/app/app.component.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					import { Component } from "@angular/core";
 | 
				
			||||||
 | 
					import {TranslateService} from "@ngx-translate/core";
 | 
				
			||||||
 | 
					import {Router} from "@angular/router";
 | 
				
			||||||
 | 
					import * as moment from "moment";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Component({
 | 
				
			||||||
 | 
					  selector: "app-root",
 | 
				
			||||||
 | 
					  templateUrl: "./app.component.html",
 | 
				
			||||||
 | 
					  styleUrls: ["./app.component.css"]
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class AppComponent {
 | 
				
			||||||
 | 
					  constructor(private translate: TranslateService, public router: Router) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    translate.addLangs([
 | 
				
			||||||
 | 
					      'en',
 | 
				
			||||||
 | 
					      'fr',
 | 
				
			||||||
 | 
					      'ru'
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    translate.setDefaultLang('en');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  langList: any[] = [
 | 
				
			||||||
 | 
					    {lang: 'en', display: 'English'},
 | 
				
			||||||
 | 
					    {lang: 'fr', display: 'Français'},
 | 
				
			||||||
 | 
					    {lang: 'ru', display: 'Русский'},
 | 
				
			||||||
 | 
					  ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  langChange(lang: any) {
 | 
				
			||||||
 | 
					    this.translate.use(lang.lang);
 | 
				
			||||||
 | 
					    moment.locale(lang.lang);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										92
									
								
								imhashdb-frontend/src/app/app.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,92 @@
 | 
				
			|||||||
 | 
					import {BrowserModule} from "@angular/platform-browser";
 | 
				
			||||||
 | 
					import {NgModule} from "@angular/core";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import {AppRoutingModule} from "./app-routing.module";
 | 
				
			||||||
 | 
					import {AppComponent} from "./app.component";
 | 
				
			||||||
 | 
					import {TranslateLoader, TranslateModule} from "@ngx-translate/core";
 | 
				
			||||||
 | 
					import {HttpClient, HttpClientModule} from "@angular/common/http";
 | 
				
			||||||
 | 
					import {TranslateHttpLoader} from "@ngx-translate/http-loader";
 | 
				
			||||||
 | 
					import {MatToolbarModule} from "@angular/material/toolbar";
 | 
				
			||||||
 | 
					import {MatMenuModule} from "@angular/material/menu";
 | 
				
			||||||
 | 
					import {MatButtonModule} from "@angular/material/button";
 | 
				
			||||||
 | 
					import {MatIconModule} from "@angular/material/icon";
 | 
				
			||||||
 | 
					import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
 | 
				
			||||||
 | 
					import {IndexComponent} from './index/index.component';
 | 
				
			||||||
 | 
					import {ContactComponent} from './contact/contact.component';
 | 
				
			||||||
 | 
					import {MatCardModule} from "@angular/material/card";
 | 
				
			||||||
 | 
					import {MatFormFieldModule} from "@angular/material/form-field";
 | 
				
			||||||
 | 
					import {MatInputModule} from "@angular/material/input";
 | 
				
			||||||
 | 
					import {MatStepperModule} from "@angular/material/stepper";
 | 
				
			||||||
 | 
					import {SearchResultComponent} from './search-result/search-result.component';
 | 
				
			||||||
 | 
					import {ApiService} from "./api.service";
 | 
				
			||||||
 | 
					import {MatProgressBarModule} from "@angular/material/progress-bar";
 | 
				
			||||||
 | 
					import {MatListModule} from "@angular/material/list";
 | 
				
			||||||
 | 
					import {MatTreeModule} from "@angular/material/tree";
 | 
				
			||||||
 | 
					import {MatExpansionModule} from "@angular/material/expansion";
 | 
				
			||||||
 | 
					import {MatChipsModule} from "@angular/material/chips";
 | 
				
			||||||
 | 
					import {Base64Service} from "./base64.service";
 | 
				
			||||||
 | 
					import {MetaComponent} from './meta/meta.component';
 | 
				
			||||||
 | 
					import {MetaDialogComponent} from "./meta-dialog/meta-dialog.component";
 | 
				
			||||||
 | 
					import {MatDialogModule} from "@angular/material/dialog";
 | 
				
			||||||
 | 
					import { SearchSettingsComponent } from './search-settings/search-settings.component';
 | 
				
			||||||
 | 
					import {MatButtonToggleModule} from "@angular/material/button-toggle";
 | 
				
			||||||
 | 
					import {MatSliderModule} from "@angular/material/slider";
 | 
				
			||||||
 | 
					import { AboutComponent } from './about/about.component';
 | 
				
			||||||
 | 
					import { GitRepoComponent } from './git-repo/git-repo.component';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function createTranslateLoader(http: HttpClient) {
 | 
				
			||||||
 | 
					  return new TranslateHttpLoader(http, './assets/i18n/', '.json');
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@NgModule({
 | 
				
			||||||
 | 
					  declarations: [
 | 
				
			||||||
 | 
					    AppComponent,
 | 
				
			||||||
 | 
					    IndexComponent,
 | 
				
			||||||
 | 
					    ContactComponent,
 | 
				
			||||||
 | 
					    SearchResultComponent,
 | 
				
			||||||
 | 
					    MetaComponent,
 | 
				
			||||||
 | 
					    MetaDialogComponent,
 | 
				
			||||||
 | 
					    SearchSettingsComponent,
 | 
				
			||||||
 | 
					    AboutComponent,
 | 
				
			||||||
 | 
					    GitRepoComponent,
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  imports: [
 | 
				
			||||||
 | 
					    BrowserModule,
 | 
				
			||||||
 | 
					    AppRoutingModule,
 | 
				
			||||||
 | 
					    HttpClientModule,
 | 
				
			||||||
 | 
					    TranslateModule.forRoot({
 | 
				
			||||||
 | 
					        loader: {
 | 
				
			||||||
 | 
					          provide: TranslateLoader,
 | 
				
			||||||
 | 
					          useFactory: (createTranslateLoader),
 | 
				
			||||||
 | 
					          deps: [HttpClient]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    MatToolbarModule,
 | 
				
			||||||
 | 
					    MatMenuModule,
 | 
				
			||||||
 | 
					    MatButtonModule,
 | 
				
			||||||
 | 
					    MatIconModule,
 | 
				
			||||||
 | 
					    BrowserAnimationsModule,
 | 
				
			||||||
 | 
					    MatCardModule,
 | 
				
			||||||
 | 
					    MatFormFieldModule,
 | 
				
			||||||
 | 
					    MatInputModule,
 | 
				
			||||||
 | 
					    MatStepperModule,
 | 
				
			||||||
 | 
					    MatProgressBarModule,
 | 
				
			||||||
 | 
					    MatListModule,
 | 
				
			||||||
 | 
					    MatTreeModule,
 | 
				
			||||||
 | 
					    MatExpansionModule,
 | 
				
			||||||
 | 
					    MatChipsModule,
 | 
				
			||||||
 | 
					    MatDialogModule,
 | 
				
			||||||
 | 
					    MatButtonToggleModule,
 | 
				
			||||||
 | 
					    MatSliderModule,
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  providers: [
 | 
				
			||||||
 | 
					    ApiService,
 | 
				
			||||||
 | 
					    Base64Service
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  bootstrap: [AppComponent]
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class AppModule {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										78
									
								
								imhashdb-frontend/src/app/base64.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,78 @@
 | 
				
			|||||||
 | 
					import {Injectable} from '@angular/core';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					export class Base64Service {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor() {
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  encBuffer(arrayBuffer: ArrayBuffer) {
 | 
				
			||||||
 | 
					    return this.encBytes(new Uint8Array(arrayBuffer));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  toHex(input: string) {
 | 
				
			||||||
 | 
					    const encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
 | 
				
			||||||
 | 
					    let output = [];
 | 
				
			||||||
 | 
					    let chr1, chr2, chr3;
 | 
				
			||||||
 | 
					    let enc1, enc2, enc3, enc4;
 | 
				
			||||||
 | 
					    let i = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let j = 0;
 | 
				
			||||||
 | 
					    while (i < input.length) {
 | 
				
			||||||
 | 
					      enc1 = encodings.indexOf(input.charAt(i++));
 | 
				
			||||||
 | 
					      enc2 = encodings.indexOf(input.charAt(i++));
 | 
				
			||||||
 | 
					      enc3 = encodings.indexOf(input.charAt(i++));
 | 
				
			||||||
 | 
					      enc4 = encodings.indexOf(input.charAt(i++));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      chr1 = (enc1 << 2) | (enc2 >> 4);
 | 
				
			||||||
 | 
					      chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
 | 
				
			||||||
 | 
					      chr3 = ((enc3 & 3) << 6) | enc4;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      output[j++] = chr1;
 | 
				
			||||||
 | 
					      if (enc3 != 64)
 | 
				
			||||||
 | 
					        output[j++] = chr2;
 | 
				
			||||||
 | 
					      if (enc4 != 64)
 | 
				
			||||||
 | 
					        output[j++] = chr3;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return output.map(x => (x < 16 ? "0" + x.toString(16) : x.toString(16))).join("");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  encBytes(bytes: Uint8Array) {
 | 
				
			||||||
 | 
					    let base64 = ''
 | 
				
			||||||
 | 
					    const encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const byteLength = bytes.byteLength
 | 
				
			||||||
 | 
					    const byteRemainder = byteLength % 3
 | 
				
			||||||
 | 
					    const mainLength = byteLength - byteRemainder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let a, b, c, d
 | 
				
			||||||
 | 
					    let chunk
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (let i = 0; i < mainLength; i = i + 3) {
 | 
				
			||||||
 | 
					      chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      a = (chunk & 16515072) >> 18
 | 
				
			||||||
 | 
					      b = (chunk & 258048) >> 12
 | 
				
			||||||
 | 
					      c = (chunk & 4032) >> 6
 | 
				
			||||||
 | 
					      d = chunk & 63
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (byteRemainder == 1) {
 | 
				
			||||||
 | 
					      chunk = bytes[mainLength]
 | 
				
			||||||
 | 
					      a = (chunk & 252) >> 2 // 252 = (2^6 - 1) << 2
 | 
				
			||||||
 | 
					      b = (chunk & 3) << 4 // 3   = 2^2 - 1
 | 
				
			||||||
 | 
					      base64 += encodings[a] + encodings[b] + '=='
 | 
				
			||||||
 | 
					    } else if (byteRemainder == 2) {
 | 
				
			||||||
 | 
					      chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1]
 | 
				
			||||||
 | 
					      a = (chunk & 64512) >> 10 // 64512 = (2^6 - 1) << 10
 | 
				
			||||||
 | 
					      b = (chunk & 1008) >> 4 // 1008  = (2^6 - 1) << 4
 | 
				
			||||||
 | 
					      c = (chunk & 15) << 2 // 15    = 2^4 - 1
 | 
				
			||||||
 | 
					      base64 += encodings[a] + encodings[b] + encodings[c] + '='
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return base64
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1
									
								
								imhashdb-frontend/src/app/contact/contact.component.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					<p>contact works!</p>
 | 
				
			||||||
							
								
								
									
										15
									
								
								imhashdb-frontend/src/app/contact/contact.component.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					import { Component, OnInit } from "@angular/core";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Component({
 | 
				
			||||||
 | 
					  selector: 'app-contact',
 | 
				
			||||||
 | 
					  templateUrl: './contact.component.html',
 | 
				
			||||||
 | 
					  styleUrls: ['./contact.component.css']
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class ContactComponent implements OnInit {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor() { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ngOnInit(): void {
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					img, span, .mat-icon {
 | 
				
			||||||
 | 
					  vertical-align: middle;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.git-repo {
 | 
				
			||||||
 | 
					  margin-bottom: 0.8em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					<div class="git-repo">
 | 
				
			||||||
 | 
					  <mat-icon>chevron_right</mat-icon>
 | 
				
			||||||
 | 
					  <span>{{name}}</span>
 | 
				
			||||||
 | 
					   
 | 
				
			||||||
 | 
					  <a target="_blank" href="https://github.com/{{path}}">
 | 
				
			||||||
 | 
					    <img alt="GitHub stars"src="https://shields.simon987.net/github/stars/{{path}}?style=social">
 | 
				
			||||||
 | 
					  </a>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
							
								
								
									
										18
									
								
								imhashdb-frontend/src/app/git-repo/git-repo.component.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					import {Component, Input, OnInit} from '@angular/core';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Component({
 | 
				
			||||||
 | 
					  selector: 'app-git-repo',
 | 
				
			||||||
 | 
					  templateUrl: './git-repo.component.html',
 | 
				
			||||||
 | 
					  styleUrls: ['./git-repo.component.css']
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class GitRepoComponent implements OnInit {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Input() name;
 | 
				
			||||||
 | 
					  @Input() path;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor() { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ngOnInit(): void {
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										0
									
								
								imhashdb-frontend/src/app/index/index.component.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										25
									
								
								imhashdb-frontend/src/app/index/index.component.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					<input id="upload" type="file" style="display: none"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<mat-card>
 | 
				
			||||||
 | 
					  <mat-card-title>{{"index.title" | translate}}</mat-card-title>
 | 
				
			||||||
 | 
					  <mat-card-subtitle>{{"index.subtitle" | translate}}</mat-card-subtitle>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <mat-card-content>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <ng-template matStepLabel>Upload image</ng-template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <mat-form-field appearance="outline" style="width: 100%">
 | 
				
			||||||
 | 
					      <mat-label>Upload</mat-label>
 | 
				
			||||||
 | 
					      <input matInput placeholder="https://example.com/image.jpeg">
 | 
				
			||||||
 | 
					      <button mat-icon-button matSuffix (click)="onUpload()">
 | 
				
			||||||
 | 
					        <mat-icon>cloud_upload</mat-icon>
 | 
				
			||||||
 | 
					      </button>
 | 
				
			||||||
 | 
					      <mat-hint>Enter direct image URL or upload from your computer</mat-hint>
 | 
				
			||||||
 | 
					    </mat-form-field>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <app-search-settings (changed)="onSettingsChanged()"></app-search-settings>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  </mat-card-content>
 | 
				
			||||||
 | 
					</mat-card>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<search-result [show]="showResult" [progress]="progress" [query]="this.query"></search-result>
 | 
				
			||||||
							
								
								
									
										51
									
								
								imhashdb-frontend/src/app/index/index.component.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					import {Component, EventEmitter, Input, OnInit} from "@angular/core";
 | 
				
			||||||
 | 
					import {Base64Service} from "../base64.service";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Component({
 | 
				
			||||||
 | 
					  selector: 'app-index',
 | 
				
			||||||
 | 
					  templateUrl: './index.component.html',
 | 
				
			||||||
 | 
					  styleUrls: ['./index.component.css']
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class IndexComponent implements OnInit {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  query = new EventEmitter<string>()
 | 
				
			||||||
 | 
					  progress: number = -2
 | 
				
			||||||
 | 
					  showResult: boolean = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(private b64: Base64Service) {
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ngOnInit(): void {
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  onSettingsChanged() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  onUpload() {
 | 
				
			||||||
 | 
					    this.query.emit("test")
 | 
				
			||||||
 | 
					    this.showResult = true;
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const uploadElem = document.getElementById("upload") as HTMLInputElement;
 | 
				
			||||||
 | 
					    uploadElem.click()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uploadElem.onchange = () => {
 | 
				
			||||||
 | 
					      this.showResult = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (uploadElem.files.length === 0) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const reader = new FileReader();
 | 
				
			||||||
 | 
					      reader.onload = () => {
 | 
				
			||||||
 | 
					        const data = reader.result as ArrayBuffer;
 | 
				
			||||||
 | 
					        const b64array = this.b64.encBuffer(data);
 | 
				
			||||||
 | 
					        this.query.emit(b64array);
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      reader.readAsArrayBuffer(uploadElem.files[0]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					<h1>Metadata</h1>
 | 
				
			||||||
 | 
					<p><code>{{meta["meta"]["id"]}}</code></p>
 | 
				
			||||||
 | 
					<p>Fetched {{formatTs(meta["meta"]["retrieved_at"])}} from <code>{{meta["url"]}}</code></p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<pre>{{meta["meta"]["meta"] | json}}</pre>
 | 
				
			||||||
@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					import {Component, Inject, OnInit} from "@angular/core";
 | 
				
			||||||
 | 
					import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
 | 
				
			||||||
 | 
					import * as moment from "moment";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Component({
 | 
				
			||||||
 | 
					  selector: 'meta-dialog',
 | 
				
			||||||
 | 
					  templateUrl: './meta-dialog.component.html',
 | 
				
			||||||
 | 
					  styleUrls: ['./meta-dialog.component.css']
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class MetaDialogComponent implements OnInit {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(public dialogRef: MatDialogRef<MetaDialogComponent>,
 | 
				
			||||||
 | 
					              @Inject(MAT_DIALOG_DATA) public meta) {
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ngOnInit() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  formatTs(ts: number) {
 | 
				
			||||||
 | 
					    return moment(ts).fromNow()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										3
									
								
								imhashdb-frontend/src/app/meta/meta.component.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					.mat-raised-button {
 | 
				
			||||||
 | 
					  margin: 4px 8px 4px 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										12
									
								
								imhashdb-frontend/src/app/meta/meta.component.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					<mat-list-item>
 | 
				
			||||||
 | 
					  <p matLine>
 | 
				
			||||||
 | 
					    <code>{{meta["meta"]["id"]}}</code> 
 | 
				
			||||||
 | 
					  </p>
 | 
				
			||||||
 | 
					  <p class="muted" matLine [title]='formatTsTitle(meta["meta"]["retrieved_at"])'>
 | 
				
			||||||
 | 
					    Fetched {{formatTs(meta["meta"]["retrieved_at"])}}
 | 
				
			||||||
 | 
					  </p>
 | 
				
			||||||
 | 
					  <div matLine>
 | 
				
			||||||
 | 
					    <a mat-raised-button color="primary" [href]='meta["url"]' target="_blank"><mat-icon>image</mat-icon> URL</a>
 | 
				
			||||||
 | 
					    <button mat-raised-button color="primary" (click)="openDialog()"><mat-icon>info</mat-icon> Metadata</button>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</mat-list-item>
 | 
				
			||||||
							
								
								
									
										35
									
								
								imhashdb-frontend/src/app/meta/meta.component.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					import {Component, Inject, Input, OnInit} from "@angular/core";
 | 
				
			||||||
 | 
					import * as moment from "moment";
 | 
				
			||||||
 | 
					import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from "@angular/material/dialog";
 | 
				
			||||||
 | 
					import {MetaDialogComponent} from "../meta-dialog/meta-dialog.component";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Component({
 | 
				
			||||||
 | 
					  selector: 'app-meta',
 | 
				
			||||||
 | 
					  templateUrl: './meta.component.html',
 | 
				
			||||||
 | 
					  styleUrls: ['./meta.component.css']
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class MetaComponent implements OnInit {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Input() meta;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(public dialog: MatDialog) {
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ngOnInit(): void {
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  openDialog() {
 | 
				
			||||||
 | 
					    this.dialog.open(MetaDialogComponent, {data: this.meta})
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  formatTs(ts: number) {
 | 
				
			||||||
 | 
					    return moment(ts).fromNow()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  formatTsTitle(ts: number) {
 | 
				
			||||||
 | 
					    return moment(ts).format()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					.mat-expansion-panel-header-description {
 | 
				
			||||||
 | 
					  flex-grow: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.mat-expansion-panel-header {
 | 
				
			||||||
 | 
					  height: 80px !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.thumbnail {
 | 
				
			||||||
 | 
					  border-radius: 3px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  width: 64px;
 | 
				
			||||||
 | 
					  max-height: 64px;
 | 
				
			||||||
 | 
					  float: left;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  padding-right: 1em;
 | 
				
			||||||
 | 
					  object-fit: cover;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.fullsize-wrapper {
 | 
				
			||||||
 | 
					  text-align: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.fullsize {
 | 
				
			||||||
 | 
					  max-height: 350px;
 | 
				
			||||||
 | 
					  max-width: 100%;
 | 
				
			||||||
 | 
					  box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.mat-chip-list {
 | 
				
			||||||
 | 
					  display: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media (min-width: 992px) {
 | 
				
			||||||
 | 
					  .container {
 | 
				
			||||||
 | 
					    max-width: 960px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .mat-chip-list {
 | 
				
			||||||
 | 
					    display: inherit;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.mat-card {
 | 
				
			||||||
 | 
					  padding: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media (min-width: 576px) {
 | 
				
			||||||
 | 
					  .mat-card {
 | 
				
			||||||
 | 
					    padding: 16px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					<div style="margin-top: 2em" *ngIf="show">
 | 
				
			||||||
 | 
					  <mat-accordion>
 | 
				
			||||||
 | 
					    <mat-expansion-panel *ngFor="let im of queryResult"
 | 
				
			||||||
 | 
					                         (opened)="onPanelOpen(im)"
 | 
				
			||||||
 | 
					                         (closed)="onPanelClose(im)">
 | 
				
			||||||
 | 
					      <mat-expansion-panel-header>
 | 
				
			||||||
 | 
					        <mat-panel-title>
 | 
				
			||||||
 | 
					          <img class="thumbnail" *ngIf='!panelState[im["sha1"]]'
 | 
				
			||||||
 | 
					               [src]='imageUrls(im)[0]' [srcset]='imageUrls(im).join(", ")'>
 | 
				
			||||||
 | 
					          <span style="align-self: center">
 | 
				
			||||||
 | 
					          <code>{{im.sha1}}</code>   <span class="muted">{{formatSize(im)}}</span>
 | 
				
			||||||
 | 
					          </span>
 | 
				
			||||||
 | 
					        </mat-panel-title>
 | 
				
			||||||
 | 
					        <mat-panel-description style="align-self: center">
 | 
				
			||||||
 | 
					          <mat-chip-list>
 | 
				
			||||||
 | 
					            <mat-chip *ngFor='let meta of metaChipList(im)'
 | 
				
			||||||
 | 
					                      [style.background-color]="metaBackgroundColor(meta)"
 | 
				
			||||||
 | 
					                      [style.color]="metaColor(meta)"
 | 
				
			||||||
 | 
					                      [title]="metaDescription(meta)"
 | 
				
			||||||
 | 
					            >{{shortMetaName(meta)}}</mat-chip>
 | 
				
			||||||
 | 
					          </mat-chip-list>
 | 
				
			||||||
 | 
					        </mat-panel-description>
 | 
				
			||||||
 | 
					      </mat-expansion-panel-header>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <div class="fullsize-wrapper">
 | 
				
			||||||
 | 
					        <img class="fullsize" *ngIf='panelState[im["sha1"]]'
 | 
				
			||||||
 | 
					             [src]='imageUrls(im)[0]' [srcset]='imageUrls(im).join(", ")'>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <mat-list>
 | 
				
			||||||
 | 
					        <ng-template ngFor let-meta [ngForOf]='im["meta"]' let-i="index">
 | 
				
			||||||
 | 
					          <app-meta [meta]="meta"></app-meta>
 | 
				
			||||||
 | 
					          <mat-divider *ngIf='i != (im["meta"].length - 1)'></mat-divider>
 | 
				
			||||||
 | 
					        </ng-template>
 | 
				
			||||||
 | 
					      </mat-list>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    </mat-expansion-panel>
 | 
				
			||||||
 | 
					  </mat-accordion>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
@ -0,0 +1,121 @@
 | 
				
			|||||||
 | 
					import {Component, EventEmitter, Inject, Input, OnDestroy, OnInit} from "@angular/core";
 | 
				
			||||||
 | 
					import {Subscription} from "rxjs";
 | 
				
			||||||
 | 
					import {ApiService} from "../api.service";
 | 
				
			||||||
 | 
					import _ from "lodash"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Component({
 | 
				
			||||||
 | 
					  selector: 'search-result',
 | 
				
			||||||
 | 
					  templateUrl: './search-result.component.html',
 | 
				
			||||||
 | 
					  styleUrls: ['./search-result.component.css']
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class SearchResultComponent implements OnInit, OnDestroy {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Input() query: EventEmitter<string>;
 | 
				
			||||||
 | 
					  sub: Subscription;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Input() show: boolean;
 | 
				
			||||||
 | 
					  @Input() progress: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  queryResult = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(private api: ApiService) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ngOnInit(): void {
 | 
				
			||||||
 | 
					    this.sub = this.query.subscribe(str => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      this.api.hash(str).subscribe(data => {
 | 
				
			||||||
 | 
					        this.progress = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const hashTypes = Object.keys(data)
 | 
				
			||||||
 | 
					        hashTypes.forEach(h => {
 | 
				
			||||||
 | 
					          this.api.query(h, data[h]["bytes"], 10, 10, 0).subscribe(res => {
 | 
				
			||||||
 | 
					            res["images"].forEach(im => {
 | 
				
			||||||
 | 
					              if (!this.queryResult.some(x => x["sha1"] == im["sha1"])) {
 | 
				
			||||||
 | 
					                this.queryResult.push(im)
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            this.progress += (Object.keys(this.queryResult).length / hashTypes.length) * 100
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  panelState = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  onPanelOpen(im) {
 | 
				
			||||||
 | 
					    this.panelState[im["sha1"]] = true
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  onPanelClose(im) {
 | 
				
			||||||
 | 
					    this.panelState[im["sha1"]] = false
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ngOnDestroy(): void {
 | 
				
			||||||
 | 
					    this.sub.unsubscribe();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  barType(progress: number) {
 | 
				
			||||||
 | 
					    if (progress == -2) {
 | 
				
			||||||
 | 
					      return "indeterminate";
 | 
				
			||||||
 | 
					    } else if (progress == -1) {
 | 
				
			||||||
 | 
					      return "query";
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      return "determinate"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  //debug
 | 
				
			||||||
 | 
					  barColor(progress: number) {
 | 
				
			||||||
 | 
					    if (progress == -1) {
 | 
				
			||||||
 | 
					      return "primary";
 | 
				
			||||||
 | 
					    } else if (progress == 0) {
 | 
				
			||||||
 | 
					      return "accent";
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      return "warn"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  imageUrls(im) {
 | 
				
			||||||
 | 
					    return _.uniq(im["meta"].map(m => m["url"]))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  metaColor(meta) {
 | 
				
			||||||
 | 
					    return this.api.metaInfo()[meta["meta"]["id"].split(".")[0]]["foreground"]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  metaBackgroundColor(meta) {
 | 
				
			||||||
 | 
					    return this.api.metaInfo()[meta["meta"]["id"].split(".")[0]]["background"]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  metaDescription(meta) {
 | 
				
			||||||
 | 
					    return this.api.metaInfo()[meta["meta"]["id"].split(".")[0]]["description"]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  shortMetaName(meta) {
 | 
				
			||||||
 | 
					    return meta["meta"]["id"].split(".").slice(0, -1).join(".");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  metaChipList(im) {
 | 
				
			||||||
 | 
					    return _.uniqBy(im["meta"], m => this.shortMetaName(m));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  formatSize(im) {
 | 
				
			||||||
 | 
					    return bytes_to_readable(im.size)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function bytes_to_readable(bytes) {
 | 
				
			||||||
 | 
					  const scale = ["B", "KB", "MB"];
 | 
				
			||||||
 | 
					  for (let i = scale.length - 1; i >= 0; i--) {
 | 
				
			||||||
 | 
					    const cur = Math.pow(1024, i);
 | 
				
			||||||
 | 
					    if (cur < bytes) {
 | 
				
			||||||
 | 
					      return (bytes / cur).toFixed(i == 0 ? 0 : 1) + scale[i];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return "?";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					.hash-name {
 | 
				
			||||||
 | 
					  display: inline-block;
 | 
				
			||||||
 | 
					  width: 4em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.mat-list-item {
 | 
				
			||||||
 | 
					  margin-bottom: 1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.mat-button-toggle-group {
 | 
				
			||||||
 | 
					  margin-bottom: 0.5em;
 | 
				
			||||||
 | 
					  margin-right: 0.8em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.mat-expansion-panel {
 | 
				
			||||||
 | 
					  margin-top: 1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media (max-width: 576px) {
 | 
				
			||||||
 | 
					  .mat-expansion-panel {
 | 
				
			||||||
 | 
					    margin-left: -16px;
 | 
				
			||||||
 | 
					    margin-right: -16px;
 | 
				
			||||||
 | 
					    margin-bottom: -16px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .mat-expansion-panel-body {
 | 
				
			||||||
 | 
					    padding: 0 !important;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					<mat-expansion-panel>
 | 
				
			||||||
 | 
					  <mat-expansion-panel-header>
 | 
				
			||||||
 | 
					    <mat-panel-title>Search options</mat-panel-title>
 | 
				
			||||||
 | 
					    <mat-panel-description></mat-panel-description>
 | 
				
			||||||
 | 
					  </mat-expansion-panel-header>
 | 
				
			||||||
 | 
					  <mat-list>
 | 
				
			||||||
 | 
					    <mat-list-item *ngFor="let hash of settings.hashes">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <div matLine>
 | 
				
			||||||
 | 
					        <span class="hash-name" i>{{hash.name}}</span>
 | 
				
			||||||
 | 
					        <mat-button-toggle-group [title]="'Hash resolution'"
 | 
				
			||||||
 | 
					                                 (change)="toggleChange(hash.name,$event)" multiple>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <ng-template ngFor let-res [ngForOf]='hash.resolution' let-i="index">
 | 
				
			||||||
 | 
					            <mat-button-toggle [value]="res.id">{{res.name}}</mat-button-toggle>
 | 
				
			||||||
 | 
					          </ng-template>
 | 
				
			||||||
 | 
					        </mat-button-toggle-group>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <mat-slider [title]="'Maximum distance'" *ngIf="selectedResolution[hash.name]" thumbLabel
 | 
				
			||||||
 | 
					                    min="0" [max]='selectedResolution[hash.name].max_distance'
 | 
				
			||||||
 | 
					                    step="1" value="0"></mat-slider>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <p matLine class="muted">{{hash.description}}</p>
 | 
				
			||||||
 | 
					    </mat-list-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  </mat-list>
 | 
				
			||||||
 | 
					</mat-expansion-panel>
 | 
				
			||||||
@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					import {Component, Input, OnInit, Output} from "@angular/core";
 | 
				
			||||||
 | 
					import {Observable} from "rxjs";
 | 
				
			||||||
 | 
					import {ApiService} from "../api.service";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Component({
 | 
				
			||||||
 | 
					  selector: "app-search-settings",
 | 
				
			||||||
 | 
					  templateUrl: "./search-settings.component.html",
 | 
				
			||||||
 | 
					  styleUrls: ["./search-settings.component.css"]
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class SearchSettingsComponent implements OnInit {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Output() changed: Observable<void> = new Observable<void>();
 | 
				
			||||||
 | 
					  private settings;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private selectedResolution = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(private api: ApiService) {
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ngOnInit(): void {
 | 
				
			||||||
 | 
					    this.settings = this.api.hashInfo();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // https://stackoverflow.com/questions/51282112/
 | 
				
			||||||
 | 
					  toggleChange(hash, event) {
 | 
				
			||||||
 | 
					    let toggle = event.source;
 | 
				
			||||||
 | 
					    if (toggle) {
 | 
				
			||||||
 | 
					      let group = toggle.buttonToggleGroup;
 | 
				
			||||||
 | 
					      if (event.value.some(item => item == toggle.value)) {
 | 
				
			||||||
 | 
					        group.value = [toggle.value];
 | 
				
			||||||
 | 
					        this.selectedResolution[hash] = this.settings.hashes
 | 
				
			||||||
 | 
					          .find(h => h.name == hash).resolution
 | 
				
			||||||
 | 
					          .find(r => r.id == toggle.value);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      this.selectedResolution[hash] = undefined;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								imhashdb-frontend/src/assets/dhash.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1020 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								imhashdb-frontend/src/assets/diagram.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 54 KiB  | 
							
								
								
									
										16
									
								
								imhashdb-frontend/src/assets/i18n/en.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "nav": {
 | 
				
			||||||
 | 
					    "title": "imhashdb",
 | 
				
			||||||
 | 
					    "lang_select": "Language",
 | 
				
			||||||
 | 
					    "about": "About",
 | 
				
			||||||
 | 
					    "contact": "Contact"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "index": {
 | 
				
			||||||
 | 
					    "title": "imhashdb",
 | 
				
			||||||
 | 
					    "subtitle": "Image hash database"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "about": {
 | 
				
			||||||
 | 
					    "title": "About imhashdb",
 | 
				
			||||||
 | 
					    "subtitle": ""
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										7
									
								
								imhashdb-frontend/src/assets/i18n/fr.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					    "nav": {
 | 
				
			||||||
 | 
					      "title": "imhashdb (Fr)",
 | 
				
			||||||
 | 
					      "lang_select": "Langue",
 | 
				
			||||||
 | 
					      "test": "Bonjour"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										5
									
								
								imhashdb-frontend/src/assets/i18n/ru.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "nav": {
 | 
				
			||||||
 | 
					    "title": "imhashdb (Ру)"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								imhashdb-frontend/src/assets/mhash.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 903 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								imhashdb-frontend/src/assets/phash.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 912 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								imhashdb-frontend/src/assets/phash_large.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 14 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								imhashdb-frontend/src/assets/schema.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 70 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								imhashdb-frontend/src/assets/whash.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1015 KiB  | 
							
								
								
									
										11
									
								
								imhashdb-frontend/src/browserslist
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers
 | 
				
			||||||
 | 
					# For additional information regarding the format and rule options, please see:
 | 
				
			||||||
 | 
					# https://github.com/browserslist/browserslist#queries
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> 0.5%
 | 
				
			||||||
 | 
					last 2 versions
 | 
				
			||||||
 | 
					Firefox ESR
 | 
				
			||||||
 | 
					not dead
 | 
				
			||||||
 | 
					not IE 9-11
 | 
				
			||||||
							
								
								
									
										3
									
								
								imhashdb-frontend/src/environments/environment.prod.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					export const environment = {
 | 
				
			||||||
 | 
					  production: true
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										16
									
								
								imhashdb-frontend/src/environments/environment.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					// This file can be replaced during build by using the `fileReplacements` array.
 | 
				
			||||||
 | 
					// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
 | 
				
			||||||
 | 
					// The list of file replacements can be found in `angular.json`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const environment = {
 | 
				
			||||||
 | 
					  production: false
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * For easier debugging in development mode, you can import the following file
 | 
				
			||||||
 | 
					 * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This import should be commented out in production mode because it will have a negative impact
 | 
				
			||||||
 | 
					 * on performance if an error is thrown.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					// import 'zone.js/dist/zone-error';  // Included with Angular CLI.
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								imhashdb-frontend/src/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 5.3 KiB  | 
							
								
								
									
										18
									
								
								imhashdb-frontend/src/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					  <meta charset="utf-8">
 | 
				
			||||||
 | 
					  <title>imhashdb</title>
 | 
				
			||||||
 | 
					  <base href="/">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <meta name="viewport" content="width=device-width, initial-scale=1">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap" rel="stylesheet">
 | 
				
			||||||
 | 
					  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <link rel="icon" type="image/x-icon" href="favicon.ico">
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body class="mat-typography">
 | 
				
			||||||
 | 
					<app-root></app-root>
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										32
									
								
								imhashdb-frontend/src/karma.conf.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					// Karma configuration file, see link for more information
 | 
				
			||||||
 | 
					// https://karma-runner.github.io/1.0/config/configuration-file.html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = function (config) {
 | 
				
			||||||
 | 
					  config.set({
 | 
				
			||||||
 | 
					    basePath: '',
 | 
				
			||||||
 | 
					    frameworks: ['jasmine', '@angular-devkit/build-angular'],
 | 
				
			||||||
 | 
					    plugins: [
 | 
				
			||||||
 | 
					      require('karma-jasmine'),
 | 
				
			||||||
 | 
					      require('karma-chrome-launcher'),
 | 
				
			||||||
 | 
					      require('karma-jasmine-html-reporter'),
 | 
				
			||||||
 | 
					      require('karma-coverage-istanbul-reporter'),
 | 
				
			||||||
 | 
					      require('@angular-devkit/build-angular/plugins/karma')
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    client: {
 | 
				
			||||||
 | 
					      clearContext: false // leave Jasmine Spec Runner output visible in browser
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    coverageIstanbulReporter: {
 | 
				
			||||||
 | 
					      dir: require('path').join(__dirname, '../coverage/imhashdb-frontend'),
 | 
				
			||||||
 | 
					      reports: ['html', 'lcovonly', 'text-summary'],
 | 
				
			||||||
 | 
					      fixWebpackSourcePaths: true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    reporters: ['progress', 'kjhtml'],
 | 
				
			||||||
 | 
					    port: 9876,
 | 
				
			||||||
 | 
					    colors: true,
 | 
				
			||||||
 | 
					    logLevel: config.LOG_INFO,
 | 
				
			||||||
 | 
					    autoWatch: true,
 | 
				
			||||||
 | 
					    browsers: ['Chrome'],
 | 
				
			||||||
 | 
					    singleRun: false,
 | 
				
			||||||
 | 
					    restartOnFileChange: true
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										12
									
								
								imhashdb-frontend/src/main.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					import {enableProdMode} from "@angular/core";
 | 
				
			||||||
 | 
					import {platformBrowserDynamic} from "@angular/platform-browser-dynamic";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import {AppModule} from "./app/app.module";
 | 
				
			||||||
 | 
					import {environment} from "./environments/environment";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (environment.production) {
 | 
				
			||||||
 | 
					  enableProdMode();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					platformBrowserDynamic().bootstrapModule(AppModule)
 | 
				
			||||||
 | 
					  .catch(err => console.error(err));
 | 
				
			||||||
							
								
								
									
										63
									
								
								imhashdb-frontend/src/polyfills.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,63 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * This file includes polyfills needed by Angular and is loaded before the app.
 | 
				
			||||||
 | 
					 * You can add your own extra polyfills to this file.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This file is divided into 2 sections:
 | 
				
			||||||
 | 
					 *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
 | 
				
			||||||
 | 
					 *   2. Application imports. Files imported after ZoneJS that should be loaded before your main
 | 
				
			||||||
 | 
					 *      file.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
 | 
				
			||||||
 | 
					 * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
 | 
				
			||||||
 | 
					 * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Learn more in https://angular.io/guide/browser-support
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/***************************************************************************************************
 | 
				
			||||||
 | 
					 * BROWSER POLYFILLS
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** IE10 and IE11 requires the following for NgClass support on SVG elements */
 | 
				
			||||||
 | 
					// import 'classlist.js';  // Run `npm install --save classlist.js`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Web Animations `@angular/platform-browser/animations`
 | 
				
			||||||
 | 
					 * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
 | 
				
			||||||
 | 
					 * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					// import 'web-animations-js';  // Run `npm install --save web-animations-js`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * By default, zone.js will patch all possible macroTask and DomEvents
 | 
				
			||||||
 | 
					 * user can disable parts of macroTask/DomEvents patch by setting following flags
 | 
				
			||||||
 | 
					 * because those flags need to be set before `zone.js` being loaded, and webpack
 | 
				
			||||||
 | 
					 * will put import in the top of bundle, so user need to create a separate file
 | 
				
			||||||
 | 
					 * in this directory (for example: zone-flags.ts), and put the following flags
 | 
				
			||||||
 | 
					 * into that file, and then add the following code before importing zone.js.
 | 
				
			||||||
 | 
					 * import './zone-flags.ts';
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * The flags allowed in zone-flags.ts are listed here.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * The following flags will work for all browsers.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
 | 
				
			||||||
 | 
					 * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
 | 
				
			||||||
 | 
					 * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
 | 
				
			||||||
 | 
					 *  with the following flag, it will bypass `zone.js` patch for IE/Edge
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *  (window as any).__Zone_enable_cross_context_check = true;
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/***************************************************************************************************
 | 
				
			||||||
 | 
					 * Zone JS is required by default for Angular itself.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import 'zone.js/dist/zone';  // Included with Angular CLI.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/***************************************************************************************************
 | 
				
			||||||
 | 
					 * APPLICATION IMPORTS
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
							
								
								
									
										82
									
								
								imhashdb-frontend/src/styles.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,82 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					body {
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    font-family: Roboto, "Helvetica Neue", sans-serif;
 | 
				
			||||||
 | 
					    background: #303030;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.container {
 | 
				
			||||||
 | 
					    margin-right: auto;
 | 
				
			||||||
 | 
					    margin-left: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media (min-width: 576px) {
 | 
				
			||||||
 | 
					    .container {
 | 
				
			||||||
 | 
					        max-width: 540px;
 | 
				
			||||||
 | 
					        margin-top: 25px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media (min-width: 768px) {
 | 
				
			||||||
 | 
					    .container {
 | 
				
			||||||
 | 
					        max-width: 720px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media (min-width: 992px) {
 | 
				
			||||||
 | 
					    .container {
 | 
				
			||||||
 | 
					        max-width: 960px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media (min-width: 1200px) {
 | 
				
			||||||
 | 
					    .container {
 | 
				
			||||||
 | 
					        max-width: 1140px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media (min-width: 1600px) {
 | 
				
			||||||
 | 
					    .container {
 | 
				
			||||||
 | 
					        max-width: 1440px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media (min-width: 2200px) {
 | 
				
			||||||
 | 
					    .container {
 | 
				
			||||||
 | 
					        max-width: 2000px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.spacer {
 | 
				
			||||||
 | 
					    flex: 1 1 auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.mat-form-field-appearance-outline .mat-form-field-suffix {
 | 
				
			||||||
 | 
					  margin: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.mat-card .mat-divider-horizontal {
 | 
				
			||||||
 | 
					  position: inherit !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.mat-dialog-container {
 | 
				
			||||||
 | 
					  height: unset !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.muted {
 | 
				
			||||||
 | 
					  opacity: 0.7;
 | 
				
			||||||
 | 
					  font-size: 80%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.mat-list-text, .mat-line {
 | 
				
			||||||
 | 
					  overflow: unset !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					@media (max-width: 576px) {
 | 
				
			||||||
 | 
					  .mat-expansion-panel-body {
 | 
				
			||||||
 | 
					    padding: 0 !important;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  figure {
 | 
				
			||||||
 | 
					    margin: 0 !important;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										11
									
								
								imhashdb-frontend/src/tsconfig.app.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "extends": "../tsconfig.json",
 | 
				
			||||||
 | 
					  "compilerOptions": {
 | 
				
			||||||
 | 
					    "outDir": "../out-tsc/app",
 | 
				
			||||||
 | 
					    "types": []
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "exclude": [
 | 
				
			||||||
 | 
					    "test.ts",
 | 
				
			||||||
 | 
					    "**/*.spec.ts"
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										17
									
								
								imhashdb-frontend/src/tslint.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "extends": "../tslint.json",
 | 
				
			||||||
 | 
					  "rules": {
 | 
				
			||||||
 | 
					    "directive-selector": [
 | 
				
			||||||
 | 
					      true,
 | 
				
			||||||
 | 
					      "attribute",
 | 
				
			||||||
 | 
					      "app",
 | 
				
			||||||
 | 
					      "camelCase"
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "component-selector": [
 | 
				
			||||||
 | 
					      true,
 | 
				
			||||||
 | 
					      "element",
 | 
				
			||||||
 | 
					      "app",
 | 
				
			||||||
 | 
					      "kebab-case"
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										22
									
								
								imhashdb-frontend/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "compileOnSave": false,
 | 
				
			||||||
 | 
					  "compilerOptions": {
 | 
				
			||||||
 | 
					    "baseUrl": "./",
 | 
				
			||||||
 | 
					    "outDir": "./dist/out-tsc",
 | 
				
			||||||
 | 
					    "sourceMap": true,
 | 
				
			||||||
 | 
					    "declaration": false,
 | 
				
			||||||
 | 
					    "module": "es2015",
 | 
				
			||||||
 | 
					    "moduleResolution": "node",
 | 
				
			||||||
 | 
					    "emitDecoratorMetadata": true,
 | 
				
			||||||
 | 
					    "experimentalDecorators": true,
 | 
				
			||||||
 | 
					    "importHelpers": true,
 | 
				
			||||||
 | 
					    "target": "es5",
 | 
				
			||||||
 | 
					    "typeRoots": [
 | 
				
			||||||
 | 
					      "node_modules/@types"
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "lib": [
 | 
				
			||||||
 | 
					      "es2018",
 | 
				
			||||||
 | 
					      "dom"
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										75
									
								
								imhashdb-frontend/tslint.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,75 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "extends": "tslint:recommended",
 | 
				
			||||||
 | 
					  "rulesDirectory": [
 | 
				
			||||||
 | 
					    "codelyzer"
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "rules": {
 | 
				
			||||||
 | 
					    "array-type": false,
 | 
				
			||||||
 | 
					    "arrow-parens": false,
 | 
				
			||||||
 | 
					    "deprecation": {
 | 
				
			||||||
 | 
					      "severity": "warn"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "import-blacklist": [
 | 
				
			||||||
 | 
					      true,
 | 
				
			||||||
 | 
					      "rxjs/Rx"
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "interface-name": false,
 | 
				
			||||||
 | 
					    "max-classes-per-file": false,
 | 
				
			||||||
 | 
					    "max-line-length": [
 | 
				
			||||||
 | 
					      true,
 | 
				
			||||||
 | 
					      140
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "member-access": false,
 | 
				
			||||||
 | 
					    "member-ordering": [
 | 
				
			||||||
 | 
					      true,
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "order": [
 | 
				
			||||||
 | 
					          "static-field",
 | 
				
			||||||
 | 
					          "instance-field",
 | 
				
			||||||
 | 
					          "static-method",
 | 
				
			||||||
 | 
					          "instance-method"
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "no-consecutive-blank-lines": false,
 | 
				
			||||||
 | 
					    "no-console": [
 | 
				
			||||||
 | 
					      true,
 | 
				
			||||||
 | 
					      "debug",
 | 
				
			||||||
 | 
					      "info",
 | 
				
			||||||
 | 
					      "time",
 | 
				
			||||||
 | 
					      "timeEnd",
 | 
				
			||||||
 | 
					      "trace"
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "no-empty": false,
 | 
				
			||||||
 | 
					    "no-inferrable-types": [
 | 
				
			||||||
 | 
					      true,
 | 
				
			||||||
 | 
					      "ignore-params"
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "no-non-null-assertion": true,
 | 
				
			||||||
 | 
					    "no-redundant-jsdoc": true,
 | 
				
			||||||
 | 
					    "no-switch-case-fall-through": true,
 | 
				
			||||||
 | 
					    "no-use-before-declare": true,
 | 
				
			||||||
 | 
					    "no-var-requires": false,
 | 
				
			||||||
 | 
					    "object-literal-key-quotes": [
 | 
				
			||||||
 | 
					      true,
 | 
				
			||||||
 | 
					      "as-needed"
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "object-literal-sort-keys": false,
 | 
				
			||||||
 | 
					    "ordered-imports": false,
 | 
				
			||||||
 | 
					    "quotemark": [
 | 
				
			||||||
 | 
					      true,
 | 
				
			||||||
 | 
					      "single"
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "trailing-comma": false,
 | 
				
			||||||
 | 
					    "no-output-on-prefix": true,
 | 
				
			||||||
 | 
					    "use-input-property-decorator": true,
 | 
				
			||||||
 | 
					    "use-output-property-decorator": true,
 | 
				
			||||||
 | 
					    "use-host-property-decorator": true,
 | 
				
			||||||
 | 
					    "no-input-rename": true,
 | 
				
			||||||
 | 
					    "no-output-rename": true,
 | 
				
			||||||
 | 
					    "use-life-cycle-interface": true,
 | 
				
			||||||
 | 
					    "use-pipe-transform-interface": true,
 | 
				
			||||||
 | 
					    "component-class-suffix": true,
 | 
				
			||||||
 | 
					    "directive-class-suffix": true
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										11
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "requires": true,
 | 
				
			||||||
 | 
					  "lockfileVersion": 1,
 | 
				
			||||||
 | 
					  "dependencies": {
 | 
				
			||||||
 | 
					    "normalize.css": {
 | 
				
			||||||
 | 
					      "version": "8.0.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-8.0.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg=="
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||