├── .editorconfig ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── angular.json ├── docs ├── 3rdpartylicenses.txt ├── assets │ ├── highlight │ │ ├── highlight.pack.js │ │ └── styles │ │ │ └── github-gist.css │ └── imgs │ │ ├── sample-0.jpg │ │ ├── sample-1.jpg │ │ ├── sample-2.jpg │ │ ├── sample-3.jpg │ │ ├── sample-4.jpg │ │ └── sample-5.jpg ├── favicon.ico ├── index.html ├── main.bc2b9b3053e33707165e.js ├── pdf-test.pdf ├── polyfills.1df7106a4b82f3594681.js ├── runtime.a66f828dca56eeb90e02.js ├── scripts.d85b5235671ec5adb745.js ├── styles.98640af9fab2d5f3b235.css └── vendor.d4514cf4105bb8af5394.js ├── e2e ├── protractor.conf.js ├── src │ ├── app.e2e-spec.ts │ └── app.po.ts └── tsconfig.e2e.json ├── greenkeeper.json ├── package-lock.json ├── package.json ├── projects └── ngx-imageviewer │ ├── karma.conf.js │ ├── ng-package.json │ ├── ng-package.prod.json │ ├── package.json │ ├── src │ ├── lib │ │ ├── image.loader.ts │ │ ├── imagecache.service.spec.ts │ │ ├── imagecache.service.ts │ │ ├── imageviewer.component.spec.ts │ │ ├── imageviewer.component.ts │ │ ├── imageviewer.config.ts │ │ ├── imageviewer.model.ts │ │ ├── imageviewer.module.ts │ │ └── pdf.loader.ts │ ├── public_api.ts │ └── test.ts │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ └── tslint.json ├── src ├── app │ ├── app-routing.module.ts │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── app.server.module.ts │ ├── autoresize │ │ ├── autoresize.component.html │ │ ├── autoresize.component.scss │ │ ├── autoresize.component.spec.ts │ │ └── autoresize.component.ts │ ├── basicusage │ │ ├── basicusage.component.html │ │ ├── basicusage.component.spec.ts │ │ └── basicusage.component.ts │ ├── conditionaldisplay │ │ ├── conditionaldisplay.component.html │ │ ├── conditionaldisplay.component.spec.ts │ │ └── conditionaldisplay.component.ts │ ├── gettingstarted │ │ ├── gettingstarted.component.html │ │ ├── gettingstarted.component.scss │ │ ├── gettingstarted.component.spec.ts │ │ └── gettingstarted.component.ts │ ├── shared │ │ ├── shared.module.spec.ts │ │ └── shared.module.ts │ └── uploadpreview │ │ ├── uploadpreview.component.html │ │ ├── uploadpreview.component.spec.ts │ │ └── uploadpreview.component.ts ├── assets │ ├── highlight │ │ ├── highlight.pack.js │ │ └── styles │ │ │ └── github-gist.css │ └── imgs │ │ ├── sample-0.jpg │ │ ├── sample-1.jpg │ │ ├── sample-2.jpg │ │ ├── sample-3.jpg │ │ ├── sample-4.jpg │ │ └── sample-5.jpg ├── browserslist ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── karma.conf.js ├── main.server.ts ├── main.ts ├── pdf-test.pdf ├── polyfills.ts ├── styles.scss ├── styles │ └── app.scss ├── test.ts ├── theme.scss ├── tsconfig.app.json ├── tsconfig.server.json ├── tsconfig.spec.json └── tslint.json ├── tsconfig.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | 4 | addons.apt: 5 | update: true 6 | sources: google-chrome 7 | packages: google-chrome-stable 8 | 9 | language: node_js 10 | 11 | branches.only: 12 | - master 13 | - /^greenkeeper/.*$/ 14 | 15 | cache: 16 | yarn: true 17 | directories: 18 | - node_modules 19 | 20 | notifications.email: false 21 | 22 | node_js: '8' 23 | 24 | before_install: 25 | - yarn global add greenkeeper-lockfile@1 26 | before_script: 27 | - greenkeeper-lockfile-update 28 | script: 29 | - yarn build 30 | - yarn test 31 | after_script: 32 | - greenkeeper-lockfile-upload 33 | 34 | after_success: 35 | - yarn travis-deploy-once "yarn semantic-release" 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Hallyson Almeida 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular 8 - Canvas Image/PDF Viewer 2 | 3 | [![travis build](https://travis-ci.org/hallysonh/ngx-imageviewer.svg?branch=master)](https://travis-ci.org/hallysonh/ngx-imageviewer) 4 | [![Greenkeeper badge](https://badges.greenkeeper.io/hallysonh/ngx-imageviewer.svg)](https://greenkeeper.io/) 5 | [![version](https://img.shields.io/npm/v/@hallysonh/ngx-imageviewer.svg)](http://npm.im/@hallysonh/ngx-imageviewer) 6 | [![MIT License](https://img.shields.io/github/license/hallysonh/ngx-imageviewer.svg)](https://opensource.org/licenses/MIT) 7 | [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) 8 | 9 | This project generate a image/pdf viewer using canvas. 10 | 11 | ## Features 12 | 13 | - Configurable 14 | - Resizeble component 15 | - Supports JPEG, PNG, GIF and **PDF** 16 | - Support File Objects 17 | - Avaliable actions: 18 | - **Rotate** 19 | - **Zoom** 20 | - Reset to maximize size 21 | - Free movable 22 | - Change page (available just for PDF files) 23 | 24 | ## Demo 25 | 26 | Access a demo [here](https://hallysonh.github.io/ngx-imageviewer/) or download this project and execute: `yarn && yarn start` or `npm install && npm run start` to self server it. 27 | 28 | ## Install 29 | 30 | Run `yarn add @hallysonh/ngx-imageviewer hammerjs` to install it and its dependency. 31 | 32 | > hammerjs is currently mandatory, but it will be optional in a future release. 33 | 34 | ## Icon Font 35 | 36 | You can use any icon font to render the button's icons. However, the default icon font is the Google's Material Icons. To use them you can just add the follow line to your index.html: 37 | 38 | ```html 39 | 40 | ``` 41 | 42 | Optionaly, you can also install the font library via npm or yarn. 43 | 44 | > when using another icon font, you should provide a config object with the button icon mapping 45 | 46 | ## Basic Usage 47 | 48 | After import the module `ImageViewerModule`: 49 | 50 | ```typescript 51 | import { ImageViewerModule } from '@hallysonh/ngx-imageviewer'; 52 | 53 | @NgModule({ 54 | imports: [ImageViewerModule], 55 | }) 56 | export class AppModule {} 57 | ``` 58 | 59 | Use the follow code on your html: 60 | 61 | ```html 62 | 63 | ``` 64 | 65 | Optionaly, you can provide the fields `width` and `height`. If you omit those values, the width and height in the config object will be used. 66 | 67 | ## Add PDF Support 68 | 69 | To add PDF rendering support, you must first include `pdfjs` by running `yarn add pdfjs-dist@2.0.489` and add its reference in your `angular.json` file, like below: 70 | 71 | ```json 72 | { 73 | ... 74 | "scripts": [ 75 | { 76 | "input": "node_modules/pdfjs-dist/build/pdf.min.js" 77 | }, { 78 | "input": "node_modules/pdfjs-dist/build/pdf.worker.min.js" 79 | } 80 | ], 81 | ... 82 | } 83 | ``` 84 | 85 | ## Custom Configuration 86 | 87 | Optionaly, you can provide a custom configuration like below: 88 | 89 | ```typescript 90 | import { IMAGEVIEWER_CONFIG, ImageViewerConfig } from '@hallysonh/ngx-imageviewer'; 91 | ... 92 | const MY_IMAGEVIEWER_CONFIG: ImageViewerConfig = { 93 | buttonStyle: { 94 | bgStyle: '#B71C1C' // custom container's background style 95 | } 96 | }; 97 | ... 98 | @Component({ 99 | ... 100 | providers: [ 101 | { 102 | provide: IMAGEVIEWER_CONFIG, 103 | useValue: MY_IMAGEVIEWER_CONFIG 104 | } 105 | ] 106 | ... 107 | }) 108 | ... 109 | ``` 110 | 111 | The default configuration available is: 112 | 113 | ```typescript 114 | export const IMAGEVIEWER_CONFIG_DEFAULT: ImageViewerConfig = { 115 | width: 800, // component default width 116 | height: 600, // component default height 117 | bgStyle: '#ECEFF1', // component background style 118 | scaleStep: 0.1, // zoom scale step (using the zoom in/out buttons) 119 | rotateStepper: false, // touch rotate should rotate only 90 to 90 degrees 120 | loadingMessage: 'Loading...', 121 | buttonStyle: { 122 | iconFontFamily: 'Material Icons', // font used to render the button icons 123 | alpha: 0.5, // buttons' transparence value 124 | hoverAlpha: 0.7, // buttons' transparence value when mouse is over 125 | bgStyle: '#000000', // buttons' background style 126 | iconStyle: '#ffffff', // buttons' icon colors 127 | borderStyle: '#000000', // buttons' border style 128 | borderWidth: 0, // buttons' border width (0 == disabled) 129 | }, 130 | tooltips: { 131 | enabled: true, // enable or disable tooltips for buttons 132 | bgStyle: '#000000', // tooltip background style 133 | bgAlpha: 0.5, // tooltip background transparence 134 | textStyle: '#ffffff', // tooltip's text style 135 | textAlpha: 0.9, // tooltip's text transparence 136 | padding: 15, // tooltip padding 137 | radius: 20, // tooltip border radius 138 | }, 139 | zoomOutButton: { 140 | // zoomOut button config 141 | icon: 'zoom_out', // icon text 142 | tooltip: 'Zoom out', // button tooltip 143 | sortId: 0, // number used to determine the order of the buttons 144 | show: true, // used to show/hide the button 145 | }, 146 | 147 | // shorter button configuration style 148 | nextPageButton: createButtonConfig('navigate_next', 'Next page', 0), 149 | beforePageButton: createButtonConfig('navigate_before', 'Previous page', 1), 150 | zoomInButton: createButtonConfig('zoom_in', 'Zoom in', 1), 151 | rotateLeftButton: createButtonConfig('rotate_left', 'Rotate left', 2), 152 | rotateRightButton: createButtonConfig('rotate_right', 'Rotate right', 3), 153 | resetButton: createButtonConfig('autorenew', 'Reset', 4), 154 | }; 155 | ``` 156 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "ngx-imageviewer-app": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": { 12 | "@schematics/angular:component": { 13 | "styleext": "scss" 14 | } 15 | }, 16 | "architect": { 17 | "build": { 18 | "builder": "@angular-devkit/build-angular:browser", 19 | "options": { 20 | "outputPath": "dist/ngx-imageviewer-app", 21 | "index": "src/index.html", 22 | "main": "src/main.ts", 23 | "polyfills": "src/polyfills.ts", 24 | "tsConfig": "src/tsconfig.app.json", 25 | "assets": [ 26 | "src/favicon.ico", 27 | "src/assets", 28 | "src/pdf-test.pdf" 29 | ], 30 | "styles": [ 31 | { 32 | "input": "node_modules/@angular/material/prebuilt-themes/indigo-pink.css" 33 | }, 34 | "src/styles.scss" 35 | ], 36 | "scripts": [ 37 | { 38 | "input": "node_modules/pdfjs-dist/build/pdf.min.js" 39 | }, { 40 | "input": "node_modules/pdfjs-dist/build/pdf.worker.min.js" 41 | } 42 | ], 43 | "stylePreprocessorOptions": { 44 | "includePaths": [ 45 | "./", 46 | "./themes" 47 | ] 48 | } 49 | }, 50 | "configurations": { 51 | "production": { 52 | "fileReplacements": [ 53 | { 54 | "replace": "src/environments/environment.ts", 55 | "with": "src/environments/environment.prod.ts" 56 | } 57 | ], 58 | "optimization": true, 59 | "outputHashing": "all", 60 | "sourceMap": false, 61 | "extractCss": true, 62 | "namedChunks": false, 63 | "aot": true, 64 | "extractLicenses": true, 65 | "vendorChunk": false, 66 | "buildOptimizer": true 67 | } 68 | } 69 | }, 70 | "serve": { 71 | "builder": "@angular-devkit/build-angular:dev-server", 72 | "options": { 73 | "browserTarget": "ngx-imageviewer-app:build" 74 | }, 75 | "configurations": { 76 | "production": { 77 | "browserTarget": "ngx-imageviewer-app:build:production" 78 | } 79 | } 80 | }, 81 | "extract-i18n": { 82 | "builder": "@angular-devkit/build-angular:extract-i18n", 83 | "options": { 84 | "browserTarget": "ngx-imageviewer-app:build" 85 | } 86 | }, 87 | "test": { 88 | "builder": "@angular-devkit/build-angular:karma", 89 | "options": { 90 | "main": "src/test.ts", 91 | "polyfills": "src/polyfills.ts", 92 | "tsConfig": "src/tsconfig.spec.json", 93 | "karmaConfig": "src/karma.conf.js", 94 | "styles": [ 95 | { 96 | "input": "node_modules/@angular/material/prebuilt-themes/indigo-pink.css" 97 | }, 98 | "src/styles.scss" 99 | ], 100 | "scripts": [ 101 | { 102 | "input": "node_modules/pdfjs-dist/build/pdf.min.js" 103 | }, { 104 | "input": "node_modules/pdfjs-dist/build/pdf.worker.min.js" 105 | } 106 | ], 107 | "assets": [ 108 | "src/favicon.ico", 109 | "src/assets", 110 | "src/pdf-test.pdf" 111 | ] 112 | } 113 | }, 114 | "lint": { 115 | "builder": "@angular-devkit/build-angular:tslint", 116 | "options": { 117 | "tsConfig": [ 118 | "src/tsconfig.app.json", 119 | "src/tsconfig.spec.json" 120 | ], 121 | "exclude": [ 122 | "**/node_modules/**" 123 | ] 124 | } 125 | }, 126 | "server": { 127 | "builder": "@angular-devkit/build-angular:server", 128 | "options": { 129 | "outputPath": "dist/ngx-imageviewer-app-server", 130 | "main": "src/main.server.ts", 131 | "tsConfig": "src/tsconfig.server.json" 132 | } 133 | } 134 | } 135 | }, 136 | "ngx-imageviewer-app-e2e": { 137 | "root": "e2e/", 138 | "projectType": "application", 139 | "architect": { 140 | "e2e": { 141 | "builder": "@angular-devkit/build-angular:protractor", 142 | "options": { 143 | "protractorConfig": "e2e/protractor.conf.js", 144 | "devServerTarget": "ngx-imageviewer-app:serve" 145 | } 146 | }, 147 | "lint": { 148 | "builder": "@angular-devkit/build-angular:tslint", 149 | "options": { 150 | "tsConfig": "e2e/tsconfig.e2e.json", 151 | "exclude": [ 152 | "**/node_modules/**" 153 | ] 154 | } 155 | } 156 | } 157 | }, 158 | "ngx-imageviewer": { 159 | "root": "projects/ngx-imageviewer", 160 | "sourceRoot": "projects/ngx-imageviewer/src", 161 | "projectType": "library", 162 | "prefix": "lib", 163 | "architect": { 164 | "build": { 165 | "builder": "@angular-devkit/build-ng-packagr:build", 166 | "options": { 167 | "tsConfig": "projects/ngx-imageviewer/tsconfig.lib.json", 168 | "project": "projects/ngx-imageviewer/ng-package.json" 169 | }, 170 | "configurations": { 171 | "production": { 172 | "project": "projects/ngx-imageviewer/ng-package.prod.json" 173 | } 174 | } 175 | }, 176 | "test": { 177 | "builder": "@angular-devkit/build-angular:karma", 178 | "options": { 179 | "main": "projects/ngx-imageviewer/src/test.ts", 180 | "tsConfig": "projects/ngx-imageviewer/tsconfig.spec.json", 181 | "karmaConfig": "projects/ngx-imageviewer/karma.conf.js" 182 | } 183 | }, 184 | "lint": { 185 | "builder": "@angular-devkit/build-angular:tslint", 186 | "options": { 187 | "tsConfig": [ 188 | "projects/ngx-imageviewer/tsconfig.lib.json", 189 | "projects/ngx-imageviewer/tsconfig.spec.json" 190 | ], 191 | "exclude": [ 192 | "**/node_modules/**" 193 | ] 194 | } 195 | } 196 | } 197 | } 198 | }, 199 | "defaultProject": "ngx-imageviewer-app" 200 | } 201 | -------------------------------------------------------------------------------- /docs/3rdpartylicenses.txt: -------------------------------------------------------------------------------- 1 | rxjs@6.1.0 2 | Apache-2.0 3 | Apache License 4 | Version 2.0, January 2004 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | 180 | APPENDIX: How to apply the Apache License to your work. 181 | 182 | To apply the Apache License to your work, attach the following 183 | boilerplate notice, with the fields enclosed by brackets "[]" 184 | replaced with your own identifying information. (Don't include 185 | the brackets!) The text should be enclosed in the appropriate 186 | comment syntax for the file format. We also recommend that a 187 | file or class name and description of purpose be included on the 188 | same "printed page" as the copyright notice for easier 189 | identification within third-party archives. 190 | 191 | Copyright (c) 2015-2018 Google, Inc., Netflix, Inc., Microsoft Corp. and contributors 192 | 193 | Licensed under the Apache License, Version 2.0 (the "License"); 194 | you may not use this file except in compliance with the License. 195 | You may obtain a copy of the License at 196 | 197 | http://www.apache.org/licenses/LICENSE-2.0 198 | 199 | Unless required by applicable law or agreed to in writing, software 200 | distributed under the License is distributed on an "AS IS" BASIS, 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | See the License for the specific language governing permissions and 203 | limitations under the License. 204 | 205 | @angular/cdk@6.0.2 206 | MIT 207 | The MIT License 208 | 209 | Copyright (c) 2018 Google LLC. 210 | 211 | Permission is hereby granted, free of charge, to any person obtaining a copy 212 | of this software and associated documentation files (the "Software"), to deal 213 | in the Software without restriction, including without limitation the rights 214 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 215 | copies of the Software, and to permit persons to whom the Software is 216 | furnished to do so, subject to the following conditions: 217 | 218 | The above copyright notice and this permission notice shall be included in 219 | all copies or substantial portions of the Software. 220 | 221 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 222 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 223 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 224 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 225 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 226 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 227 | THE SOFTWARE. 228 | 229 | @angular/material@6.0.2 230 | MIT 231 | The MIT License 232 | 233 | Copyright (c) 2018 Google LLC. 234 | 235 | Permission is hereby granted, free of charge, to any person obtaining a copy 236 | of this software and associated documentation files (the "Software"), to deal 237 | in the Software without restriction, including without limitation the rights 238 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 239 | copies of the Software, and to permit persons to whom the Software is 240 | furnished to do so, subject to the following conditions: 241 | 242 | The above copyright notice and this permission notice shall be included in 243 | all copies or substantial portions of the Software. 244 | 245 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 246 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 247 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 248 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 249 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 250 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 251 | THE SOFTWARE. 252 | 253 | @angular/flex-layout@6.0.0-beta.15 254 | MIT 255 | The MIT License 256 | 257 | Copyright (c) 2018 Google LLC. 258 | 259 | Permission is hereby granted, free of charge, to any person obtaining a copy 260 | of this software and associated documentation files (the "Software"), to deal 261 | in the Software without restriction, including without limitation the rights 262 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 263 | copies of the Software, and to permit persons to whom the Software is 264 | furnished to do so, subject to the following conditions: 265 | 266 | The above copyright notice and this permission notice shall be included in 267 | all copies or substantial portions of the Software. 268 | 269 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 270 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 271 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 272 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 273 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 274 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 275 | THE SOFTWARE. 276 | 277 | @angular/core@6.0.2 278 | MIT 279 | MIT 280 | 281 | @angular/common@6.0.2 282 | MIT 283 | MIT 284 | 285 | showdown@1.8.6 286 | BSD-3-Clause 287 | Showdown Copyright (c) 2007, John Fraser 288 | 289 | All rights reserved. 290 | 291 | Original Markdown copyright (c) 2004, John Gruber 292 | 293 | All rights reserved. 294 | 295 | Redistribution and use in source and binary forms, with or without 296 | modification, are permitted provided that the following conditions are 297 | met: 298 | 299 | * Redistributions of source code must retain the above copyright notice, 300 | this list of conditions and the following disclaimer. 301 | 302 | * Redistributions in binary form must reproduce the above copyright 303 | notice, this list of conditions and the following disclaimer in the 304 | documentation and/or other materials provided with the distribution. 305 | 306 | * Neither the name "Markdown" nor the names of its contributors may 307 | be used to endorse or promote products derived from this software 308 | without specific prior written permission. 309 | 310 | This software is provided by the copyright holders and contributors "as 311 | is" and any express or implied warranties, including, but not limited 312 | to, the implied warranties of merchantability and fitness for a 313 | particular purpose are disclaimed. In no event shall the copyright owner 314 | or contributors be liable for any direct, indirect, incidental, special, 315 | exemplary, or consequential damages (including, but not limited to, 316 | procurement of substitute goods or services; loss of use, data, or 317 | profits; or business interruption) however caused and on any theory of 318 | liability, whether in contract, strict liability, or tort (including 319 | negligence or otherwise) arising in any way out of the use of this 320 | software, even if advised of the possibility of such damage. 321 | 322 | @angular/animations@6.0.2 323 | MIT 324 | MIT 325 | 326 | @angular/router@6.0.2 327 | MIT 328 | MIT 329 | 330 | @angular/platform-browser@6.0.2 331 | MIT 332 | MIT 333 | 334 | @angular/forms@6.0.2 335 | MIT 336 | MIT 337 | 338 | tslib@1.9.1 339 | Apache-2.0 340 | Apache License 341 | 342 | Version 2.0, January 2004 343 | 344 | http://www.apache.org/licenses/ 345 | 346 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 347 | 348 | 1. Definitions. 349 | 350 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 351 | 352 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 353 | 354 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 355 | 356 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 357 | 358 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 359 | 360 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 361 | 362 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 363 | 364 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 365 | 366 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 367 | 368 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 369 | 370 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 371 | 372 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 373 | 374 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 375 | 376 | You must give any other recipients of the Work or Derivative Works a copy of this License; and 377 | 378 | You must cause any modified files to carry prominent notices stating that You changed the files; and 379 | 380 | You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 381 | 382 | If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 383 | 384 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 385 | 386 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 387 | 388 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 389 | 390 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 391 | 392 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 393 | 394 | END OF TERMS AND CONDITIONS 395 | 396 | hammerjs@2.0.8 397 | MIT 398 | The MIT License (MIT) 399 | 400 | Copyright (C) 2011-2014 by Jorik Tangelder (Eight Media) 401 | 402 | Permission is hereby granted, free of charge, to any person obtaining a copy 403 | of this software and associated documentation files (the "Software"), to deal 404 | in the Software without restriction, including without limitation the rights 405 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 406 | copies of the Software, and to permit persons to whom the Software is 407 | furnished to do so, subject to the following conditions: 408 | 409 | The above copyright notice and this permission notice shall be included in 410 | all copies or substantial portions of the Software. 411 | 412 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 413 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 414 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 415 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 416 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 417 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 418 | THE SOFTWARE. 419 | 420 | core-js@2.5.6 421 | MIT 422 | Copyright (c) 2014-2018 Denis Pushkarev 423 | 424 | Permission is hereby granted, free of charge, to any person obtaining a copy 425 | of this software and associated documentation files (the "Software"), to deal 426 | in the Software without restriction, including without limitation the rights 427 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 428 | copies of the Software, and to permit persons to whom the Software is 429 | furnished to do so, subject to the following conditions: 430 | 431 | The above copyright notice and this permission notice shall be included in 432 | all copies or substantial portions of the Software. 433 | 434 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 435 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 436 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 437 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 438 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 439 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 440 | THE SOFTWARE. 441 | 442 | zone.js@0.8.26 443 | MIT 444 | The MIT License 445 | 446 | Copyright (c) 2016-2018 Google, Inc. 447 | 448 | Permission is hereby granted, free of charge, to any person obtaining a copy 449 | of this software and associated documentation files (the "Software"), to deal 450 | in the Software without restriction, including without limitation the rights 451 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 452 | copies of the Software, and to permit persons to whom the Software is 453 | furnished to do so, subject to the following conditions: 454 | 455 | The above copyright notice and this permission notice shall be included in 456 | all copies or substantial portions of the Software. 457 | 458 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 459 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 460 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 461 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 462 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 463 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 464 | THE SOFTWARE. -------------------------------------------------------------------------------- /docs/assets/highlight/highlight.pack.js: -------------------------------------------------------------------------------- 1 | /*! highlight.js v9.12.0 | BSD3 License | git.io/hljslicense */ 2 | !function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(e){function n(e){return e.replace(/&/g,"&").replace(//g,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0===t.index}function a(e){return k.test(e)}function i(e){var n,t,r,i,o=e.className+" ";if(o+=e.parentNode?e.parentNode.className:"",t=B.exec(o))return w(t[1])?t[1]:"no-highlight";for(o=o.split(/\s+/),n=0,r=o.length;r>n;n++)if(i=o[n],a(i)||w(i))return i}function o(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!==r[0].offset?e[0].offset"}function u(e){s+=""}function c(e){("start"===e.event?o:u)(e.node)}for(var l=0,s="",f=[];e.length||r.length;){var g=i();if(s+=n(a.substring(l,g[0].offset)),l=g[0].offset,g===e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g===e&&g.length&&g[0].offset===l);f.reverse().forEach(o)}else"start"===g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return s+n(a.substr(l))}function l(e){return e.v&&!e.cached_variants&&(e.cached_variants=e.v.map(function(n){return o(e,{v:null},n)})),e.cached_variants||e.eW&&[o(e)]||[e]}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var o={},u=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");o[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?u("keyword",a.k):x(a.k).forEach(function(e){u(e,a.k[e])}),a.k=o}a.lR=t(a.l||/\w+/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),null==a.r&&(a.r=1),a.c||(a.c=[]),a.c=Array.prototype.concat.apply([],a.c.map(function(e){return l("self"===e?a:e)})),a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var c=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=c.length?t(c.join("|"),!0):{exec:function(){return null}}}}r(e)}function f(e,t,a,i){function o(e,n){var t,a;for(t=0,a=n.c.length;a>t;t++)if(r(n.c[t].bR,e))return n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function c(e,n){return!a&&r(n.iR,e)}function l(e,n){var t=N.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function p(e,n,t,r){var a=r?"":I.classPrefix,i='',i+n+o}function h(){var e,t,r,a;if(!E.k)return n(k);for(a="",t=0,E.lR.lastIndex=0,r=E.lR.exec(k);r;)a+=n(k.substring(t,r.index)),e=l(E,r),e?(B+=e[1],a+=p(e[0],n(r[0]))):a+=n(r[0]),t=E.lR.lastIndex,r=E.lR.exec(k);return a+n(k.substr(t))}function d(){var e="string"==typeof E.sL;if(e&&!y[E.sL])return n(k);var t=e?f(E.sL,k,!0,x[E.sL]):g(k,E.sL.length?E.sL:void 0);return E.r>0&&(B+=t.r),e&&(x[E.sL]=t.top),p(t.language,t.value,!1,!0)}function b(){L+=null!=E.sL?d():h(),k=""}function v(e){L+=e.cN?p(e.cN,"",!0):"",E=Object.create(e,{parent:{value:E}})}function m(e,n){if(k+=e,null==n)return b(),0;var t=o(n,E);if(t)return t.skip?k+=n:(t.eB&&(k+=n),b(),t.rB||t.eB||(k=n)),v(t,n),t.rB?0:n.length;var r=u(E,n);if(r){var a=E;a.skip?k+=n:(a.rE||a.eE||(k+=n),b(),a.eE&&(k=n));do E.cN&&(L+=C),E.skip||(B+=E.r),E=E.parent;while(E!==r.parent);return r.starts&&v(r.starts,""),a.rE?0:n.length}if(c(n,E))throw new Error('Illegal lexeme "'+n+'" for mode "'+(E.cN||"")+'"');return k+=n,n.length||1}var N=w(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var R,E=i||N,x={},L="";for(R=E;R!==N;R=R.parent)R.cN&&(L=p(R.cN,"",!0)+L);var k="",B=0;try{for(var M,j,O=0;;){if(E.t.lastIndex=O,M=E.t.exec(t),!M)break;j=m(t.substring(O,M.index),M[0]),O=M.index+j}for(m(t.substr(O)),R=E;R.parent;R=R.parent)R.cN&&(L+=C);return{r:B,value:L,language:e,top:E}}catch(T){if(T.message&&-1!==T.message.indexOf("Illegal"))return{r:0,value:n(t)};throw T}}function g(e,t){t=t||I.languages||x(y);var r={r:0,value:n(e)},a=r;return t.filter(w).forEach(function(n){var t=f(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}),a.language&&(r.second_best=a),r}function p(e){return I.tabReplace||I.useBR?e.replace(M,function(e,n){return I.useBR&&"\n"===e?"
":I.tabReplace?n.replace(/\t/g,I.tabReplace):""}):e}function h(e,n,t){var r=n?L[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function d(e){var n,t,r,o,l,s=i(e);a(s)||(I.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):n=e,l=n.textContent,r=s?f(s,l,!0):g(l),t=u(n),t.length&&(o=document.createElementNS("http://www.w3.org/1999/xhtml","div"),o.innerHTML=r.value,r.value=c(t,u(o),l)),r.value=p(r.value),e.innerHTML=r.value,e.className=h(e.className,s,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function b(e){I=o(I,e)}function v(){if(!v.called){v.called=!0;var e=document.querySelectorAll("pre code");E.forEach.call(e,d)}}function m(){addEventListener("DOMContentLoaded",v,!1),addEventListener("load",v,!1)}function N(n,t){var r=y[n]=t(e);r.aliases&&r.aliases.forEach(function(e){L[e]=n})}function R(){return x(y)}function w(e){return e=(e||"").toLowerCase(),y[e]||y[L[e]]}var E=[],x=Object.keys,y={},L={},k=/^(no-?highlight|plain|text)$/i,B=/\blang(?:uage)?-([\w-]+)\b/i,M=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,C="
",I={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0};return e.highlight=f,e.highlightAuto=g,e.fixMarkup=p,e.highlightBlock=d,e.configure=b,e.initHighlighting=v,e.initHighlightingOnLoad=m,e.registerLanguage=N,e.listLanguages=R,e.getLanguage=w,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e});hljs.registerLanguage("bash",function(e){var t={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)}/}]},s={cN:"string",b:/"/,e:/"/,c:[e.BE,t,{cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]}]},a={cN:"string",b:/'/,e:/'/};return{aliases:["sh","zsh"],l:/\b-?[a-z\._]+\b/,k:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"meta",b:/^#![^\n]+sh\s*$/,r:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:!0,c:[e.inherit(e.TM,{b:/\w[\w\d_]*/})],r:0},e.HCM,s,a,t]}});hljs.registerLanguage("json",function(e){var i={literal:"true false null"},n=[e.QSM,e.CNM],r={e:",",eW:!0,eE:!0,c:n,k:i},t={b:"{",e:"}",c:[{cN:"attr",b:/"/,e:/"/,c:[e.BE],i:"\\n"},e.inherit(r,{b:/:/})],i:"\\S"},c={b:"\\[",e:"\\]",c:[e.inherit(r)],i:"\\S"};return n.splice(n.length,0,t,c),{c:n,k:i,i:"\\S"}});hljs.registerLanguage("xml",function(s){var e="[A-Za-z0-9\\._:-]+",t={eW:!0,i:/`]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist"],cI:!0,c:[{cN:"meta",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},s.C("",{r:10}),{b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{b:/<\?(php)?/,e:/\?>/,sL:"php",c:[{b:"/\\*",e:"\\*/",skip:!0}]},{cN:"tag",b:"|$)",e:">",k:{name:"style"},c:[t],starts:{e:"",rE:!0,sL:["css","xml"]}},{cN:"tag",b:"|$)",e:">",k:{name:"script"},c:[t],starts:{e:"",rE:!0,sL:["actionscript","javascript","handlebars","xml"]}},{cN:"meta",v:[{b:/<\?xml/,e:/\?>/,r:10},{b:/<\?\w+/,e:/\?>/}]},{cN:"tag",b:"",c:[{cN:"name",b:/[^\/><\s]+/,r:0},t]}]}});hljs.registerLanguage("typescript",function(e){var r={keyword:"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class public private protected get set super static implements enum export import declare type namespace abstract as from extends async await",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document any number boolean string void Promise"};return{aliases:["ts"],k:r,c:[{cN:"meta",b:/^\s*['"]use strict['"]/},e.ASM,e.QSM,{cN:"string",b:"`",e:"`",c:[e.BE,{cN:"subst",b:"\\$\\{",e:"\\}"}]},e.CLCM,e.CBCM,{cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+e.IR+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:e.IR},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:r,c:["self",e.CLCM,e.CBCM]}]}]}],r:0},{cN:"function",b:"function",e:/[\{;]/,eE:!0,k:r,c:["self",e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:r,c:[e.CLCM,e.CBCM],i:/["'\(]/}],i:/%/,r:0},{bK:"constructor",e:/\{/,eE:!0,c:["self",{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:r,c:[e.CLCM,e.CBCM],i:/["'\(]/}]},{b:/module\./,k:{built_in:"module"},r:0},{bK:"module",e:/\{/,eE:!0},{bK:"interface",e:/\{/,eE:!0,k:"interface extends"},{b:/\$[(.]/},{b:"\\."+e.IR,r:0},{cN:"meta",b:"@[A-Za-z]+"}]}});hljs.registerLanguage("shell",function(s){return{aliases:["console"],c:[{cN:"meta",b:"^\\s{0,3}[\\w\\d\\[\\]()@-]*[>%$#]",starts:{e:"$",sL:"bash"}}]}}); -------------------------------------------------------------------------------- /docs/assets/highlight/styles/github-gist.css: -------------------------------------------------------------------------------- 1 | /** 2 | * GitHub Gist Theme 3 | * Author : Louis Barranqueiro - https://github.com/LouisBarranqueiro 4 | */ 5 | 6 | .hljs { 7 | display: block; 8 | background: white; 9 | padding: 0.5em; 10 | color: #333333; 11 | overflow-x: auto; 12 | } 13 | 14 | .hljs-comment, 15 | .hljs-meta { 16 | color: #969896; 17 | } 18 | 19 | .hljs-string, 20 | .hljs-variable, 21 | .hljs-template-variable, 22 | .hljs-strong, 23 | .hljs-emphasis, 24 | .hljs-quote { 25 | color: #df5000; 26 | } 27 | 28 | .hljs-keyword, 29 | .hljs-selector-tag, 30 | .hljs-type { 31 | color: #a71d5d; 32 | } 33 | 34 | .hljs-literal, 35 | .hljs-symbol, 36 | .hljs-bullet, 37 | .hljs-attribute { 38 | color: #0086b3; 39 | } 40 | 41 | .hljs-section, 42 | .hljs-name { 43 | color: #63a35c; 44 | } 45 | 46 | .hljs-tag { 47 | color: #333333; 48 | } 49 | 50 | .hljs-title, 51 | .hljs-attr, 52 | .hljs-selector-id, 53 | .hljs-selector-class, 54 | .hljs-selector-attr, 55 | .hljs-selector-pseudo { 56 | color: #795da3; 57 | } 58 | 59 | .hljs-addition { 60 | color: #55a532; 61 | background-color: #eaffea; 62 | } 63 | 64 | .hljs-deletion { 65 | color: #bd2c00; 66 | background-color: #ffecec; 67 | } 68 | 69 | .hljs-link { 70 | text-decoration: underline; 71 | } 72 | -------------------------------------------------------------------------------- /docs/assets/imgs/sample-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hallysonh/ngx-imageviewer/1367ec3b3e34663465fd07aaa300997a45579700/docs/assets/imgs/sample-0.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/sample-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hallysonh/ngx-imageviewer/1367ec3b3e34663465fd07aaa300997a45579700/docs/assets/imgs/sample-1.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/sample-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hallysonh/ngx-imageviewer/1367ec3b3e34663465fd07aaa300997a45579700/docs/assets/imgs/sample-2.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/sample-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hallysonh/ngx-imageviewer/1367ec3b3e34663465fd07aaa300997a45579700/docs/assets/imgs/sample-3.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/sample-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hallysonh/ngx-imageviewer/1367ec3b3e34663465fd07aaa300997a45579700/docs/assets/imgs/sample-4.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/sample-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hallysonh/ngx-imageviewer/1367ec3b3e34663465fd07aaa300997a45579700/docs/assets/imgs/sample-5.jpg -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hallysonh/ngx-imageviewer/1367ec3b3e34663465fd07aaa300997a45579700/docs/favicon.ico -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | NgxImageViewerView 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 77 | 78 | 79 | 80 | 81 |
82 | 86 | 87 | 88 | 89 |
90 |
91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /docs/pdf-test.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hallysonh/ngx-imageviewer/1367ec3b3e34663465fd07aaa300997a45579700/docs/pdf-test.pdf -------------------------------------------------------------------------------- /docs/runtime.a66f828dca56eeb90e02.js: -------------------------------------------------------------------------------- 1 | !function(r){function e(e){for(var t,p,c=e[0],a=e[1],f=e[2],l=0,s=[];l { 4 | let page: AppPage; 5 | 6 | beforeEach(() => { 7 | page = new AppPage(); 8 | }); 9 | 10 | it('should display Getting Started in title', () => { 11 | page.navigateToRoot(); 12 | expect(page.getContentTitle()).toEqual('Getting Started'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateToRoot() { 5 | return browser.get('/'); 6 | } 7 | 8 | getContentTitle() { 9 | return element(by.css('mat-sidenav-content > mat-toolbar > span')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /greenkeeper.json: -------------------------------------------------------------------------------- 1 | { 2 | "groups": { 3 | "default": { 4 | "packages": [ 5 | "package.json", 6 | "projects/ngx-imageviewer/ng-package.json", 7 | "projects/ngx-imageviewer/package.json" 8 | ] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngx-imageviewer-app", 3 | "version": "0.0.0-development", 4 | "author": "Hallyson Almeida ", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/hallysonh/ngx-imageviewer" 9 | }, 10 | "homepage": "https://github.com/hallysonh/ngx-imageviewer#readme", 11 | "scripts": { 12 | "ng": "ng", 13 | "start": "yarn build:lib && ng serve", 14 | "test": "ng test", 15 | "lint": "ng lint", 16 | "e2e": "ng e2e", 17 | "build": "yarn build:lib", 18 | "build:lib": "ng build ngx-imageviewer --prod && cp README.md dist/ngx-imageviewer", 19 | "build:docs": "ng build ngx-imageviewer-app --prod --build-optimizer --vendor-chunk --output-path docs --base-href=/ngx-imageviewer/", 20 | "postbuild": "tar -C dist --overwrite -czf dist/ngx-imageviewer.tgz ngx-imageviewer", 21 | "semantic-release": "semantic-release", 22 | "travis-deploy-once": "travis-deploy-once", 23 | "commit": "git-cz" 24 | }, 25 | "dependencies": { 26 | "@angular/animations": "^8.2.10", 27 | "@angular/cdk": "^8.2.3", 28 | "@angular/common": "^8.2.10", 29 | "@angular/compiler": "^8.2.10", 30 | "@angular/core": "^8.2.10", 31 | "@angular/flex-layout": "^8.0.0-beta.27", 32 | "@angular/forms": "^8.2.10", 33 | "@angular/http": "^7.2.15", 34 | "@angular/material": "^6.0.2", 35 | "@angular/platform-browser": "^8.2.10", 36 | "@angular/platform-browser-dynamic": "^8.2.10", 37 | "@angular/platform-server": "^8.2.10", 38 | "@angular/router": "^8.2.10", 39 | "core-js": "^2.5.4", 40 | "hammerjs": "^2.0.8", 41 | "pdfjs-dist": "2.0.489", 42 | "rxjs": "^6.0.0", 43 | "showdown": "^1.8.6", 44 | "zone.js": "^0.8.26" 45 | }, 46 | "devDependencies": { 47 | "@angular-devkit/build-angular": "~0.803.9", 48 | "@angular-devkit/build-ng-packagr": "~0.803.9", 49 | "@angular/cli": "~8.3.9", 50 | "@angular/compiler-cli": "^8.2.10", 51 | "@angular/language-service": "^8.2.10", 52 | "@types/jasmine": "~2.8.6", 53 | "@types/jasminewd2": "~2.0.3", 54 | "@types/node": "~10.1.1", 55 | "@types/showdown": "^1.7.3", 56 | "codelyzer": "~4.3.0", 57 | "commitizen": "^2.9.6", 58 | "cp": "^0.2.0", 59 | "cz-conventional-changelog": "^2.1.0", 60 | "jasmine": "^3.1.0", 61 | "jasmine-core": "~3.1.0", 62 | "jasmine-spec-reporter": "~4.2.1", 63 | "karma": "~2.0.2", 64 | "karma-chrome-launcher": "~2.2.0", 65 | "karma-coverage-istanbul-reporter": "~2.0.0", 66 | "karma-jasmine": "~1.1.1", 67 | "karma-jasmine-html-reporter": "^1.1.0", 68 | "ng-packagr": "^3.0.0-rc.2", 69 | "protractor": "~5.3.0", 70 | "puppeteer": "^1.4.0", 71 | "semantic-release": "^15.11.0", 72 | "travis-deploy-once": "^5.0.9", 73 | "ts-node": "^6.0.3", 74 | "tsickle": ">=0.25.5", 75 | "tslib": "^1.7.1", 76 | "tslint": "~5.10.0", 77 | "typescript": "^3.5.3" 78 | }, 79 | "release": { 80 | "publish": [ 81 | { 82 | "path": "@semantic-release/npm", 83 | "pkgRoot": "dist/ngx-imageviewer" 84 | }, 85 | { 86 | "path": "@semantic-release/github", 87 | "assets": [ 88 | "dist/ngx-imageviewer.tgz" 89 | ] 90 | } 91 | ] 92 | }, 93 | "config": { 94 | "commitizen": { 95 | "path": "./node_modules/cz-conventional-changelog" 96 | } 97 | }, 98 | "greenkeeper": { 99 | "ignore": [ 100 | "typescript" 101 | ] 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /projects/ngx-imageviewer/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../../coverage'), 20 | reports: ['html', 'lcovonly'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['ChromeHeadless'], 29 | singleRun: true, 30 | customLaunchers: { 31 | ChromeHeadless: { 32 | base: 'Chrome', 33 | flags: [ 34 | '--disable-translate', 35 | '--headless', 36 | '--disable-gpu', 37 | '--disable-extensions', 38 | '--remote-debugging-port=9222' 39 | ] 40 | } 41 | } 42 | }); 43 | }; 44 | -------------------------------------------------------------------------------- /projects/ngx-imageviewer/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/ngx-imageviewer", 4 | "deleteDestPath": false, 5 | "lib": { 6 | "entryFile": "src/public_api.ts", 7 | "externals": { 8 | "moment": "moment" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /projects/ngx-imageviewer/ng-package.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/lib", 4 | "lib": { 5 | "entryFile": "src/public_api.ts" 6 | } 7 | } -------------------------------------------------------------------------------- /projects/ngx-imageviewer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@hallysonh/ngx-imageviewer", 3 | "version": "0.0.0-development", 4 | "author": "Hallyson Almeida ", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/hallysonh/ngx-imageviewer" 9 | }, 10 | "keywords":[ 11 | "image", "viewer", "angular", "pdf", "canvas", "component" 12 | ], 13 | "homepage": "https://github.com/hallysonh/ngx-imageviewer#readme", 14 | "peerDependencies": { 15 | "@angular/common": "^8.2.10", 16 | "@angular/core": "^8.2.10", 17 | "hammerjs": "^2.x" 18 | }, 19 | "optionalDependencies": { 20 | "pdfjs-dist": "2.0.489" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /projects/ngx-imageviewer/src/lib/image.loader.ts: -------------------------------------------------------------------------------- 1 | import { ResourceLoader, Dimension, toSquareAngle } from './imageviewer.model'; 2 | import { ImageViewerConfig } from './imageviewer.config'; 3 | 4 | export class ImageResourceLoader extends ResourceLoader { 5 | 6 | setUp() { 7 | this.loadResource(); 8 | } 9 | 10 | loadResource() { 11 | this.loading = true; 12 | this._image = new Image(); 13 | this._image.addEventListener('load', (evt) => { 14 | this.loaded = true; 15 | this.loading = false; 16 | this.resourceChange.next(); 17 | }, false); 18 | this._image.src = this.src; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /projects/ngx-imageviewer/src/lib/imagecache.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { ImageCacheService } from './imagecache.service'; 4 | 5 | describe('ImageCacheService', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [ImageCacheService] 9 | }); 10 | }); 11 | 12 | it('should be created', inject([ImageCacheService], (service: ImageCacheService) => { 13 | expect(service).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /projects/ngx-imageviewer/src/lib/imagecache.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | export interface CacheDef { 4 | url: string; 5 | page: number; 6 | image: any; 7 | } 8 | 9 | @Injectable({ providedIn: 'root' }) 10 | export class ImageCacheService { 11 | 12 | private _cache: CacheDef[] = []; 13 | 14 | constructor() {} 15 | 16 | get cache(): CacheDef[] { 17 | return this._cache; 18 | } 19 | 20 | getCache(url: string, page: number) { 21 | return this.cache.find(i => i.url === url && i.page === page); 22 | } 23 | 24 | getImage(url: string, page: number) { 25 | const c = this.getCache(url, page); 26 | return c ? c.image : null; 27 | } 28 | 29 | saveImage(url: string, page: number, image: any) { 30 | const cache = this.getCache(url, page); 31 | if (cache) { 32 | cache.image = image; 33 | } else { 34 | this.cache.push({ url, page, image }); 35 | } 36 | } 37 | 38 | disposeCache() { 39 | this.cache.forEach(i => URL.revokeObjectURL(i.image.src)); 40 | this._cache = []; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /projects/ngx-imageviewer/src/lib/imageviewer.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { IMAGEVIEWER_CONFIG, IMAGEVIEWER_CONFIG_DEFAULT } from './imageviewer.config'; 3 | 4 | import 'hammerjs'; 5 | 6 | import { ImageViewerComponent } from './imageviewer.component'; 7 | 8 | describe('ImageViewerComponent', () => { 9 | let component: ImageViewerComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ ImageViewerComponent ], 15 | providers: [{ provide: IMAGEVIEWER_CONFIG, useValue: IMAGEVIEWER_CONFIG_DEFAULT }], 16 | }) 17 | .compileComponents(); 18 | })); 19 | 20 | beforeEach(() => { 21 | fixture = TestBed.createComponent(ImageViewerComponent); 22 | component = fixture.componentInstance; 23 | fixture.detectChanges(); 24 | }); 25 | 26 | it('should create', () => { 27 | expect(component).toBeTruthy(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /projects/ngx-imageviewer/src/lib/imageviewer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, ViewChild, AfterViewInit, Renderer2, Inject, OnDestroy } from '@angular/core'; 2 | import { DomSanitizer } from '@angular/platform-browser'; 3 | import { Subscription } from 'rxjs'; 4 | 5 | import { ImageViewerConfig, IMAGEVIEWER_CONFIG, IMAGEVIEWER_CONFIG_DEFAULT, ButtonConfig, ButtonStyle } from './imageviewer.config'; 6 | import { Viewport, Button, toSquareAngle, ResourceLoader } from './imageviewer.model'; 7 | import { ImageResourceLoader } from './image.loader'; 8 | import { ImageCacheService } from './imagecache.service'; 9 | import { PdfResourceLoader } from './pdf.loader'; 10 | 11 | const MIN_TOOLTIP_WIDTH_SPACE = 500; 12 | 13 | @Component({ 14 | selector: 'ngx-imageviewer', 15 | template: ` 16 | 20 | 21 | `, 22 | styles: [` 23 | :host { display: block } 24 | :host canvas { margin: 0 auto; display: block } 25 | [hidden] { display: none !important } 26 | `] 27 | }) 28 | export class ImageViewerComponent implements AfterViewInit, OnDestroy { 29 | 30 | //#region Input properties 31 | private _src: string | File; 32 | get src() { return this._src; } 33 | @Input('src') set src(value) { 34 | if (value === this._src) { return; } 35 | this._src = value; 36 | this.setUpResource(); 37 | } 38 | 39 | // FIX not workign properly 40 | private _filetype: string; 41 | get filetype() { return this._filetype; } 42 | @Input('filetype') set filetype(value: string) { 43 | if (value === this._filetype) { return; } 44 | this._filetype = value; 45 | this.setUpResource(); 46 | } 47 | 48 | private _width: number; 49 | get width() { return this._width; } 50 | @Input('width') set width(value) { 51 | if (value === this._width) { return; } 52 | this._width = value; 53 | if (this._canvas) { this._canvas.width = this._width; } 54 | this.resetImage(); 55 | } 56 | 57 | private _height: number; 58 | get height() { return this._height; } 59 | @Input('height') set height(value) { 60 | if (value === this._height) { return; } 61 | this._height = value; 62 | if (this._canvas) { this._canvas.height = this._height; } 63 | this.resetImage(); 64 | } 65 | 66 | @ViewChild('imageContainer', {static: false}) canvasRef: any; 67 | //#endregion 68 | 69 | //#region Private properties 70 | // Canvas 2D context 71 | private _canvas: HTMLCanvasElement; 72 | private _context: CanvasRenderingContext2D; 73 | 74 | // dirty state 75 | private _dirty = true; 76 | 77 | // action buttons 78 | private _nextPageButton: Button; 79 | private _beforePageButton: Button; 80 | private _zoomOutButton: Button; 81 | private _zoomInButton: Button; 82 | private _rotateLeftButton: Button; 83 | private _rotateRightButton: Button; 84 | private _resetButton: Button; 85 | 86 | // contains all active buttons 87 | private _buttons = []; 88 | 89 | // current tool tip (used to track change of tool tip) 90 | private _currentTooltip = null; 91 | 92 | // cached data when touch events started 93 | private _touchStartState: any = {}; 94 | 95 | // list of event listener destroyers 96 | private _listenDestroyList = []; 97 | 98 | // image / Pdf Drawable Resource 99 | private _resource: ResourceLoader; 100 | private _resourceChangeSub: Subscription; 101 | 102 | // Caching resourceLoader instances to reuse 103 | private _imageResource: ImageResourceLoader; 104 | private _pdfResource: PdfResourceLoader; 105 | 106 | //#endregion 107 | 108 | //#region Lifecycle events 109 | constructor( 110 | private _sanitizer: DomSanitizer, 111 | private _renderer: Renderer2, 112 | private _imageCache: ImageCacheService, 113 | @Inject(IMAGEVIEWER_CONFIG) private config: ImageViewerConfig 114 | ) { 115 | this.config = this.extendsDefaultConfig(config); 116 | this._nextPageButton = new Button(this.config.nextPageButton, this.config.buttonStyle); 117 | this._beforePageButton = new Button(this.config.beforePageButton, this.config.buttonStyle); 118 | this._zoomOutButton = new Button(this.config.zoomOutButton, this.config.buttonStyle); 119 | this._zoomInButton = new Button(this.config.zoomInButton, this.config.buttonStyle); 120 | this._rotateLeftButton = new Button(this.config.rotateLeftButton, this.config.buttonStyle); 121 | this._rotateRightButton = new Button(this.config.rotateRightButton, this.config.buttonStyle); 122 | this._resetButton = new Button(this.config.resetButton, this.config.buttonStyle); 123 | this._buttons = [ 124 | this._zoomOutButton, 125 | this._zoomInButton, 126 | this._rotateLeftButton, 127 | this._rotateRightButton, 128 | this._resetButton 129 | ].filter(item => item.display) 130 | .sort((a, b) => a.sortId - b.sortId); 131 | } 132 | 133 | ngAfterViewInit() { 134 | this._canvas = this.canvasRef.nativeElement; 135 | this._context = this._canvas.getContext('2d'); 136 | 137 | // setting canvas dimention 138 | this._canvas.width = this.width || this.config.width; 139 | this._canvas.height = this.height || this.config.height; 140 | 141 | // setting buttons actions 142 | this._nextPageButton.onClick = (evt) => { this.nextPage(); return false; }; 143 | this._beforePageButton.onClick = (evt) => { this.previousPage(); return false; }; 144 | this._zoomOutButton.onClick = (evt) => { this.zoomOut(); return false; }; 145 | this._zoomInButton.onClick = (evt) => { this.zoomIn(); return false; }; 146 | this._rotateLeftButton.onClick = (evt) => { this.rotateLeft(); return false; }; 147 | this._rotateRightButton.onClick = (evt) => { this.rotateRight(); return false; }; 148 | this._resetButton.onClick = (evt) => { this.resetImage(); return false; }; 149 | 150 | // register event listeners 151 | this.addEventListeners(); 152 | 153 | this.updateCanvas(); 154 | } 155 | 156 | ngOnDestroy() { 157 | // unregiste event listeners 158 | this._listenDestroyList.forEach(listenDestroy => { 159 | if (typeof listenDestroy === 'function') { 160 | listenDestroy(); 161 | } 162 | }); 163 | this._imageCache.disposeCache(); 164 | } 165 | 166 | setUpResource() { 167 | if (this.isImage(this.src) && (!this._resource || !(this._resource instanceof ImageResourceLoader))) { 168 | if (this._resourceChangeSub) { 169 | this._resourceChangeSub.unsubscribe(); 170 | } 171 | if (!this._imageResource) { 172 | this._imageResource = new ImageResourceLoader(); 173 | } 174 | this._resource = this._imageResource; 175 | } else if (this.isPdf(this.src) && (!this._resource || !(this._resource instanceof PdfResourceLoader))) { 176 | if (this._resourceChangeSub) { 177 | this._resourceChangeSub.unsubscribe(); 178 | } 179 | if (!this._pdfResource) { 180 | this._pdfResource = new PdfResourceLoader(this._imageCache); 181 | } 182 | this._resource = this._pdfResource; 183 | } 184 | if (this._resource) { 185 | this._resource.src = this.src instanceof File ? URL.createObjectURL(this.src) : this.src; 186 | this._resourceChangeSub = this._resource.onResourceChange().subscribe(() => { 187 | this.updateCanvas(); 188 | if (this.src instanceof File) { 189 | URL.revokeObjectURL(this._resource.src); 190 | } 191 | }); 192 | this._resource.setUp(); 193 | this.resetImage(); 194 | if (this._context) { this.updateCanvas(); } 195 | } 196 | } 197 | //#endregion 198 | 199 | //#region Touch events 200 | onTap(evt) { 201 | const position = { x: evt.pageX, y: evt.pageY }; 202 | const activeElement = this.getUIElement(this.screenToCanvasCentre(position)); 203 | if (activeElement !== null) { activeElement.onClick(evt); } 204 | } 205 | 206 | onTouchEnd() { 207 | this._touchStartState.viewport = undefined; 208 | this._touchStartState.scale = undefined; 209 | this._touchStartState.rotate = undefined; 210 | } 211 | 212 | processTouchEvent(evt) { 213 | // process pan 214 | if (!this._touchStartState.viewport) { this._touchStartState.viewport = Object.assign({}, this._resource.viewport); } 215 | 216 | const viewport = this._resource.viewport; 217 | viewport.x = this._touchStartState.viewport.x + evt.deltaX; 218 | viewport.y = this._touchStartState.viewport.y + evt.deltaY; 219 | 220 | // process pinch in/out 221 | if (!this._touchStartState.scale) { this._touchStartState.scale = this._resource.viewport.scale; } 222 | const newScale = this._touchStartState.scale * evt.scale; 223 | viewport.scale = newScale > this._resource.maxScale ? this._resource.maxScale : 224 | newScale < this._resource.minScale ? this._resource.minScale : newScale; 225 | 226 | // process rotate left/right 227 | if (!this._touchStartState.rotate) { this._touchStartState.rotate = { rotation: viewport.rotation, startRotate: evt.rotation }; } 228 | if (evt.rotation !== 0) { 229 | const newAngle = this._touchStartState.rotate.rotation + evt.rotation - this._touchStartState.rotate.startRotate; 230 | viewport.rotation = this.config.rotateStepper ? toSquareAngle(newAngle) : newAngle; 231 | } 232 | this._dirty = true; 233 | } 234 | //#endregion 235 | 236 | //#region Mouse Events 237 | private addEventListeners() { 238 | // zooming 239 | this._listenDestroyList.push(this._renderer.listen(this._canvas, 'DOMMouseScroll', (evt) => this.onMouseWheel(evt))); 240 | this._listenDestroyList.push(this._renderer.listen(this._canvas, 'mousewheel', (evt) => this.onMouseWheel(evt))); 241 | 242 | // show tooltip when mouseover it 243 | this._listenDestroyList.push(this._renderer.listen(this._canvas, 'mousemove', (evt) => 244 | this.checkTooltipActivation(this.screenToCanvasCentre({ x: evt.clientX, y: evt.clientY })) 245 | )); 246 | } 247 | 248 | private onMouseWheel(evt) { 249 | if (!evt) { evt = event; } 250 | evt.preventDefault(); 251 | if (evt.detail < 0 || evt.wheelDelta > 0) { // up -> larger 252 | this.zoomIn(); 253 | } else { // down -> smaller 254 | this.zoomOut(); 255 | } 256 | } 257 | 258 | private checkTooltipActivation(pos: { x: number, y: number }) { 259 | this.getUIElements().forEach(x => x.hover = false); 260 | const activeElement = this.getUIElement(pos); 261 | const oldToolTip = this._currentTooltip; 262 | if (activeElement !== null) { 263 | if (typeof activeElement.hover !== 'undefined') { 264 | activeElement.hover = true; 265 | } 266 | if (typeof activeElement.tooltip !== 'undefined') { 267 | this._currentTooltip = activeElement.tooltip; 268 | } 269 | } 270 | if (oldToolTip !== this._currentTooltip) { this._dirty = true; } 271 | } 272 | //#endregion 273 | 274 | //#region Button Actions 275 | 276 | private nextPage() { 277 | if (!this._resource) { return; } 278 | if (this._resource.currentItem >= this._resource.totalItem) { return; } 279 | if (this._resource.currentItem < 1) { this._resource.currentItem = 0; } 280 | this._resource.currentItem++; 281 | this._resource.loadResource(); 282 | this._dirty = true; 283 | } 284 | 285 | private previousPage() { 286 | if (!this._resource) { return; } 287 | if (this._resource.currentItem <= 1) { return; } 288 | if (this._resource.currentItem > this._resource.totalItem) { this._resource.currentItem = this._resource.totalItem + 1; } 289 | this._resource.currentItem--; 290 | this._resource.loadResource(); 291 | this._dirty = true; 292 | } 293 | 294 | private zoomIn() { 295 | if (!this._resource) { return; } 296 | const newScale = this._resource.viewport.scale * (1 + this.config.scaleStep); 297 | this._resource.viewport.scale = newScale > this._resource.maxScale ? this._resource.maxScale : newScale; 298 | this._dirty = true; 299 | } 300 | 301 | private zoomOut() { 302 | if (!this._resource) { return; } 303 | const newScale = this._resource.viewport.scale * (1 - this.config.scaleStep); 304 | this._resource.viewport.scale = newScale < this._resource.minScale ? this._resource.minScale : newScale; 305 | this._dirty = true; 306 | } 307 | 308 | private rotateLeft() { 309 | if (!this._resource) { return; } 310 | const viewport = this._resource.viewport; 311 | viewport.rotation = viewport.rotation === 0 ? 270 : viewport.rotation - 90; 312 | this._dirty = true; 313 | } 314 | 315 | private rotateRight() { 316 | if (!this._resource) { return; } 317 | const viewport = this._resource.viewport; 318 | viewport.rotation = viewport.rotation === 270 ? 0 : viewport.rotation + 90; 319 | this._dirty = true; 320 | } 321 | 322 | private resetImage() { 323 | if (!this._resource) { return; } 324 | this._resource.resetViewport(this._canvas); 325 | this._dirty = true; 326 | } 327 | //#endregion 328 | 329 | //#region Draw Canvas 330 | private updateCanvas() { 331 | this.resetImage(); 332 | 333 | // start new render loop 334 | this.render(); 335 | } 336 | 337 | private render() { 338 | const vm = this; 339 | // only re-render if dirty 340 | if (this._dirty && this._resource) { 341 | this._dirty = false; 342 | 343 | const ctx = this._context; 344 | ctx.save(); 345 | 346 | this._resource.draw(ctx, this.config, this._canvas, () => { 347 | ctx.restore(); 348 | 349 | if (vm._resource.loaded) { 350 | // draw buttons 351 | this.drawButtons(ctx); 352 | 353 | // draw paginator 354 | if (this._resource.showItemsQuantity) { 355 | this.drawPaginator(ctx); 356 | } 357 | } 358 | }); 359 | } 360 | requestAnimationFrame(() => this.render()); 361 | } 362 | 363 | private drawButtons(ctx) { 364 | const padding = this.config.tooltips.padding; 365 | const radius = this.config.tooltips.radius; 366 | const gap = 2 * radius + padding; 367 | const x = this._canvas.width - radius - padding; 368 | const y = this._canvas.height - radius - padding; 369 | 370 | // draw buttons 371 | for (let i = 0; i < this._buttons.length; i++) { 372 | this._buttons[i].draw(ctx, x, y - gap * i, radius); 373 | } 374 | 375 | // draw tooltip 376 | if (this._currentTooltip !== null && this._canvas.width > MIN_TOOLTIP_WIDTH_SPACE) { 377 | ctx.save(); 378 | const fontSize = radius; 379 | ctx.font = fontSize + 'px sans-serif'; 380 | 381 | // calculate position 382 | const textSize = ctx.measureText(this._currentTooltip).width 383 | , rectWidth = textSize + padding 384 | , rectHeight = fontSize * 0.70 + padding 385 | , rectX = this._canvas.width 386 | - (2 * radius + 2 * padding) // buttons 387 | - rectWidth 388 | , rectY = this._canvas.height - rectHeight - padding 389 | , textX = rectX + 0.5 * padding 390 | , textY = this._canvas.height - 1.5 * padding; 391 | 392 | ctx.globalAlpha = this.config.tooltips.bgAlpha; 393 | ctx.fillStyle = this.config.tooltips.bgStyle; 394 | this.drawRoundRectangle(ctx, rectX, rectY, rectWidth, rectHeight, 8, true, false); 395 | 396 | ctx.globalAlpha = this.config.tooltips.textAlpha; 397 | ctx.fillStyle = this.config.tooltips.textStyle; 398 | ctx.fillText(this._currentTooltip, textX, textY); 399 | 400 | ctx.restore(); 401 | } 402 | } 403 | 404 | private drawPaginator(ctx) { 405 | const padding = this.config.tooltips.padding; 406 | const radius = this.config.tooltips.radius; 407 | const labelWidth = 50; 408 | const x1 = (this._canvas.width - labelWidth) / 2 - radius - padding; // PrevPageButton 409 | const x2 = this._canvas.width / 2; // Label 410 | const x3 = (this._canvas.width + labelWidth) / 2 + radius + padding; // NextPageButton 411 | const y = this._canvas.height - radius - padding; 412 | const label = this._resource.currentItem + '/' + this._resource.totalItem; 413 | const fontSize = 25; 414 | 415 | ctx.save(); 416 | this._beforePageButton.draw(ctx, x1, y, radius); 417 | this._nextPageButton.draw(ctx, x3, y, radius); 418 | ctx.restore(); 419 | 420 | ctx.save(); 421 | ctx.font = fontSize + 'px Verdana'; 422 | ctx.textAlign = 'center'; 423 | ctx.fillText(label, x2, this._canvas.height - padding - fontSize / 2, labelWidth); 424 | ctx.restore(); 425 | } 426 | 427 | private drawRoundRectangle(ctx, x, y, width, height, radius, fill, stroke) { 428 | radius = (typeof radius === 'number') ? radius : 5; 429 | fill = (typeof fill === 'boolean') ? fill : true; // fill = default 430 | stroke = (typeof stroke === 'boolean') ? stroke : false; 431 | 432 | // draw round rectangle 433 | ctx.beginPath(); 434 | ctx.moveTo(x + radius, y); 435 | ctx.lineTo(x + width - radius, y); 436 | ctx.quadraticCurveTo(x + width, y, x + width, y + radius); 437 | ctx.lineTo(x + width, y + height - radius); 438 | ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); 439 | ctx.lineTo(x + radius, y + height); 440 | ctx.quadraticCurveTo(x, y + height, x, y + height - radius); 441 | ctx.lineTo(x, y + radius); 442 | ctx.quadraticCurveTo(x, y, x + radius, y); 443 | ctx.closePath(); 444 | 445 | if (fill) { ctx.fill(); } 446 | if (stroke) { ctx.stroke(); } 447 | } 448 | 449 | //#endregion 450 | 451 | //#region Utils 452 | 453 | private extendsDefaultConfig(cfg: ImageViewerConfig) { 454 | const defaultCfg = IMAGEVIEWER_CONFIG_DEFAULT; 455 | const localCfg = Object.assign({}, defaultCfg, cfg); 456 | if (cfg.buttonStyle) { localCfg.buttonStyle = Object.assign(defaultCfg.buttonStyle, cfg.buttonStyle); } 457 | if (cfg.tooltips) { localCfg.tooltips = Object.assign(defaultCfg.tooltips, cfg.tooltips); } 458 | if (cfg.nextPageButton) { localCfg.nextPageButton = Object.assign(defaultCfg.nextPageButton, cfg.nextPageButton); } 459 | if (cfg.beforePageButton) { localCfg.beforePageButton = Object.assign(defaultCfg.beforePageButton, cfg.beforePageButton); } 460 | if (cfg.zoomOutButton) { localCfg.zoomOutButton = Object.assign(defaultCfg.zoomOutButton, cfg.zoomOutButton); } 461 | if (cfg.zoomOutButton) { localCfg.zoomOutButton = Object.assign(defaultCfg.zoomOutButton, cfg.zoomOutButton); } 462 | if (cfg.zoomInButton) { localCfg.zoomInButton = Object.assign(defaultCfg.zoomInButton, cfg.zoomInButton); } 463 | if (cfg.rotateLeftButton) { localCfg.rotateLeftButton = Object.assign(defaultCfg.rotateLeftButton, cfg.rotateLeftButton); } 464 | if (cfg.rotateRightButton) { localCfg.rotateRightButton = Object.assign(defaultCfg.rotateRightButton, cfg.rotateRightButton); } 465 | if (cfg.resetButton) { localCfg.resetButton = Object.assign(defaultCfg.resetButton, cfg.resetButton); } 466 | return localCfg; 467 | } 468 | 469 | private screenToCanvasCentre(pos: { x: number, y: number }) { 470 | const rect = this._canvas.getBoundingClientRect(); 471 | return { x: pos.x - rect.left, y: pos.y - rect.top }; 472 | } 473 | 474 | private getUIElements(): Button[] { 475 | const hoverElements = this._buttons.slice(); 476 | hoverElements.push(this._nextPageButton); 477 | hoverElements.push(this._beforePageButton); 478 | return hoverElements; 479 | } 480 | 481 | private getUIElement(pos: { x: number, y: number }) { 482 | const activeUIElement = this.getUIElements().filter((uiElement) => { 483 | return uiElement.isWithinBounds(pos.x, pos.y); 484 | }); 485 | return (activeUIElement.length > 0) ? activeUIElement[0] : null; 486 | } 487 | 488 | private isImage(file: string | File) { 489 | if (this._filetype && this._filetype.toLowerCase() === 'image') { return true; } 490 | return testFile(file, '\\.(png|jpg|jpeg|gif)|image/png'); 491 | } 492 | 493 | private isPdf(file: string | File) { 494 | if (this._filetype && this._filetype.toLowerCase() === 'pdf') { return true; } 495 | return testFile(file, '\\.(pdf)|application/pdf'); 496 | } 497 | //#endregion 498 | } 499 | 500 | function testFile(file: string | File, regexTest: string) { 501 | if (!file) { return false; } 502 | const name = file instanceof File ? file.name : file; 503 | return name.toLowerCase().match(regexTest) !== null; 504 | } 505 | -------------------------------------------------------------------------------- /projects/ngx-imageviewer/src/lib/imageviewer.config.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, InjectionToken } from '@angular/core'; 2 | 3 | export class ImageViewerConfig { 4 | width?: number; 5 | height?: number; 6 | bgStyle?: string; 7 | scaleStep?: number; 8 | rotateStepper?: boolean; 9 | buttonStyle?: ButtonStyle; 10 | loadingMessage?: string; 11 | tooltips?: { 12 | enabled?: boolean, 13 | bgStyle?: string, 14 | bgAlpha?: number, 15 | textStyle?: string, 16 | textAlpha?: number, 17 | padding?: number, 18 | radius?: number 19 | }; 20 | nextPageButton?: ButtonConfig; 21 | beforePageButton?: ButtonConfig; 22 | zoomOutButton?: ButtonConfig; 23 | zoomInButton?: ButtonConfig; 24 | rotateLeftButton?: ButtonConfig; 25 | rotateRightButton?: ButtonConfig; 26 | resetButton?: ButtonConfig; 27 | } 28 | 29 | export interface ButtonStyle { 30 | iconFontFamily?: string; 31 | alpha?: number; 32 | hoverAlpha?: number; 33 | bgStyle?: string; 34 | iconStyle?: string; 35 | borderStyle?: string; 36 | borderWidth?: number; 37 | } 38 | 39 | export interface ButtonConfig { 40 | icon?: string; 41 | tooltip?: string; 42 | sortId?: number; 43 | show?: boolean; 44 | } 45 | 46 | export function createButtonConfig(icon?: string, tooltip?: string, sortId: number = 0, show: boolean = true) { 47 | return { icon: icon, tooltip: tooltip, sortId: sortId, show: show }; 48 | } 49 | 50 | export const IMAGEVIEWER_CONFIG = new InjectionToken('imageviewer.config'); 51 | 52 | export let IMAGEVIEWER_CONFIG_DEFAULT: ImageViewerConfig = { 53 | width: 800, // component default width 54 | height: 600, // component default height 55 | bgStyle: '#ECEFF1', // component background style 56 | scaleStep: 0.1, // zoom scale step (using the zoom in/out buttons) 57 | rotateStepper: false, 58 | loadingMessage: 'Loading...', 59 | buttonStyle: { 60 | iconFontFamily: 'Material Icons', // font used to render the button icons 61 | alpha: 0.5, // buttons' transparence value 62 | hoverAlpha: 0.7, // buttons' transparence value when mouse is over 63 | bgStyle: '#000000', // buttons' background style 64 | iconStyle: '#ffffff', // buttons' icon colors 65 | borderStyle: '#000000', // buttons' border style 66 | borderWidth: 0 // buttons' border width (0 == disabled) 67 | }, 68 | tooltips: { 69 | enabled: true, // enable or disable tooltips for buttons 70 | bgStyle: '#000000', // tooltip background style 71 | bgAlpha: 0.5, // tooltip background transparence 72 | textStyle: '#ffffff', // tooltip's text style 73 | textAlpha: 0.9, // tooltip's text transparence 74 | padding: 15, // tooltip padding 75 | radius: 20 // tooltip border radius 76 | }, 77 | nextPageButton: createButtonConfig(String.fromCharCode(0xE409), 'Next page', 0), 78 | beforePageButton: createButtonConfig(String.fromCharCode(0xE408), 'Previous page', 1), 79 | zoomOutButton: createButtonConfig(String.fromCharCode(0xE900), 'Zoom out', 0), 80 | zoomInButton: createButtonConfig(String.fromCharCode(0xE8FF), 'Zoom in', 1), 81 | rotateLeftButton: createButtonConfig(String.fromCharCode(0xE419), 'Rotate left', 2), 82 | rotateRightButton: createButtonConfig(String.fromCharCode(0xE41A), 'Rotate right', 3), 83 | resetButton: createButtonConfig(String.fromCharCode(0xE863), 'Reset', 4) 84 | }; 85 | -------------------------------------------------------------------------------- /projects/ngx-imageviewer/src/lib/imageviewer.model.ts: -------------------------------------------------------------------------------- 1 | import { ButtonConfig, ButtonStyle, ImageViewerConfig } from './imageviewer.config'; 2 | import { Observable, Subject } from 'rxjs'; 3 | 4 | export class Button { 5 | //#region Properties 6 | sortId = 0; 7 | 8 | icon: string; 9 | tooltip: string; 10 | 11 | // hover state 12 | hover: boolean | (() => boolean) = false; 13 | 14 | // show/hide button 15 | display = true; 16 | 17 | // drawn on position 18 | private drawPosition = null; 19 | private drawRadius = 0; 20 | //#endregion 21 | 22 | //#region Lifecycle events 23 | constructor( 24 | config: ButtonConfig, 25 | private style: ButtonStyle 26 | ) { 27 | this.sortId = config.sortId; 28 | this.display = config.show; 29 | this.icon = config.icon; 30 | this.tooltip = config.tooltip; 31 | } 32 | //#endregion 33 | 34 | //#region Events 35 | // click action 36 | onClick(evt) { alert('no click action set!'); return true; } 37 | 38 | // mouse down action 39 | onMouseDown(evt) { return false; } 40 | //#endregion 41 | 42 | //#region Draw Button 43 | draw(ctx, x, y, radius) { 44 | this.drawPosition = { x: x, y: y }; 45 | this.drawRadius = radius; 46 | 47 | // preserve context 48 | ctx.save(); 49 | 50 | // drawing settings 51 | const isHover = (typeof this.hover === 'function') ? this.hover() : this.hover; 52 | ctx.globalAlpha = (isHover) ? this.style.hoverAlpha : this.style.alpha; 53 | ctx.fillStyle = this.style.bgStyle; 54 | ctx.lineWidth = 0; 55 | 56 | // draw circle 57 | ctx.beginPath(); 58 | ctx.arc(x, y, radius, 0, 2 * Math.PI); 59 | ctx.closePath(); 60 | ctx.fill(); 61 | if (this.style.borderWidth > 0) { 62 | ctx.lineWidth = this.style.borderWidth; 63 | ctx.strokeStyle = this.style.borderStyle; 64 | ctx.stroke(); 65 | } 66 | 67 | // draw icon 68 | if (this.icon !== null) { 69 | ctx.save(); 70 | // ctx.globalCompositeOperation = 'destination-out'; 71 | this.drawIconFont(ctx, x, y, radius); 72 | ctx.restore(); 73 | } 74 | 75 | // restore context 76 | ctx.restore(); 77 | } 78 | 79 | private drawIconFont(ctx, centreX, centreY, size) { 80 | // font settings 81 | ctx.font = size + 'px ' + this.style.iconFontFamily; 82 | ctx.fillStyle = this.style.iconStyle; 83 | 84 | // calculate position 85 | const textSize = ctx.measureText(this.icon); 86 | const x = centreX - textSize.width / 2; 87 | const y = centreY + size / 2; 88 | 89 | // draw it 90 | ctx.fillText(this.icon, x, y); 91 | } 92 | //#endregion 93 | 94 | //#region Utils 95 | isWithinBounds(x, y) { 96 | if (this.drawPosition === null) { return false; } 97 | const dx = Math.abs(this.drawPosition.x - x), dy = Math.abs(this.drawPosition.y - y); 98 | return dx * dx + dy * dy <= this.drawRadius * this.drawRadius; 99 | } 100 | //#endregion 101 | } 102 | 103 | export class Viewport { 104 | constructor( 105 | public width: number, 106 | public height: number, 107 | public scale: number, 108 | public rotation: number, 109 | public x: number, 110 | public y: number 111 | ) {} 112 | } 113 | 114 | export interface Dimension { width: number; height: number; } 115 | 116 | export abstract class ResourceLoader { 117 | public src: string; 118 | public sourceDim: { width: number, height: number }; 119 | public viewport: Viewport = { width: 0, height: 0, scale: 1, rotation: 0, x: 0, y: 0 }; 120 | public minScale = 0; 121 | public maxScale = 4; 122 | public currentItem = 1; 123 | public totalItem = 1; 124 | public showItemsQuantity = false; 125 | public loaded = false; 126 | public loading = false; 127 | public rendering = false; 128 | 129 | protected _image; 130 | protected resourceChange = new Subject(); 131 | 132 | abstract setUp(); 133 | abstract loadResource(); 134 | 135 | public resetViewport(canvasDim: Dimension): boolean { 136 | if (!this.loaded || !canvasDim) { return; } 137 | 138 | const rotation = this.viewport ? this.viewport.rotation : 0; 139 | const inverted = toSquareAngle(rotation) / 90 % 2 !== 0; 140 | const canvas = { 141 | width: !inverted ? canvasDim.width : canvasDim.height, 142 | height: !inverted ? canvasDim.height : canvasDim.width 143 | }; 144 | 145 | if (((canvas.height / this._image.height) * this._image.width) <= canvas.width) { 146 | this.viewport.scale = canvas.height / this._image.height; 147 | } else { 148 | this.viewport.scale = canvas.width / this._image.width; 149 | } 150 | this.minScale = this.viewport.scale / 4; 151 | this.maxScale = this.viewport.scale * 4; 152 | 153 | // start point to draw image 154 | this.viewport.width = this._image.width * this.viewport.scale; 155 | this.viewport.height = this._image.height * this.viewport.scale; 156 | this.viewport.x = (canvasDim.width - this.viewport.width) / 2; 157 | this.viewport.y = (canvasDim.height - this.viewport.height) / 2; 158 | } 159 | 160 | public draw(ctx, config: ImageViewerConfig, canvasDim: Dimension, onFinish) { 161 | // clear canvas 162 | ctx.clearRect(0, 0, canvasDim.width, canvasDim.height); 163 | 164 | // Draw background color; 165 | ctx.fillStyle = config.bgStyle; 166 | ctx.fillRect(0, 0, canvasDim.width, canvasDim.height); 167 | 168 | // draw image (transformed, rotate and scaled) 169 | if (!this.loading && this.loaded) { 170 | ctx.translate(this.viewport.x + this.viewport.width / 2, this.viewport.y + this.viewport.height / 2); 171 | ctx.rotate(this.viewport.rotation * Math.PI / 180); 172 | ctx.scale(this.viewport.scale, this.viewport.scale); 173 | ctx.drawImage(this._image, -this._image.width / 2, -this._image.height / 2); 174 | } else { 175 | ctx.fillStyle = '#333'; 176 | ctx.font = '25px Verdana'; 177 | ctx.textAlign = 'center'; 178 | ctx.fillText(config.loadingMessage || 'Loading...', canvasDim.width / 2, canvasDim.height / 2); 179 | } 180 | 181 | onFinish(ctx, config, canvasDim); 182 | } 183 | 184 | public onResourceChange() { return this.resourceChange.asObservable(); } 185 | } 186 | 187 | export function toSquareAngle(angle: number) { 188 | return 90 * ((Math.trunc(angle / 90) + (Math.trunc(angle % 90) > 45 ? 1 : 0)) % 4); 189 | } 190 | -------------------------------------------------------------------------------- /projects/ngx-imageviewer/src/lib/imageviewer.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { ImageViewerComponent } from './imageviewer.component'; 3 | import { IMAGEVIEWER_CONFIG, IMAGEVIEWER_CONFIG_DEFAULT } from './imageviewer.config'; 4 | 5 | @NgModule({ 6 | providers: [{ provide: IMAGEVIEWER_CONFIG, useValue: IMAGEVIEWER_CONFIG_DEFAULT }], 7 | declarations: [ImageViewerComponent], 8 | exports: [ImageViewerComponent], 9 | }) 10 | export class ImageViewerModule { } 11 | -------------------------------------------------------------------------------- /projects/ngx-imageviewer/src/lib/pdf.loader.ts: -------------------------------------------------------------------------------- 1 | import { ResourceLoader, Dimension, toSquareAngle } from './imageviewer.model'; 2 | import { ImageCacheService } from './imagecache.service'; 3 | import { ImageViewerConfig } from './imageviewer.config'; 4 | import { PDFJSStatic, PDFDocumentProxy, PDFPageProxy } from 'pdfjs-dist'; 5 | 6 | declare var pdfjsLib: any; 7 | declare var pdfjsWorker: any; 8 | 9 | export class PdfResourceLoader extends ResourceLoader { 10 | private _pdf: PDFDocumentProxy; 11 | private _page: PDFPageProxy; 12 | private _pendingReload: boolean; 13 | 14 | constructor(private _imageCache: ImageCacheService) { 15 | super(); 16 | if (typeof window !== 'undefined' && 'Worker' in window) { 17 | if (pdfjsLib && pdfjsLib.GlobalWorkerOptions && !pdfjsLib.GlobalWorkerOptions.workerSrc) { 18 | pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker; 19 | } 20 | } 21 | this.showItemsQuantity = true; 22 | } 23 | 24 | setUp() { 25 | const vm = this; 26 | if (vm.loading || !vm.src) { return; } 27 | const loadingTask = pdfjsLib.getDocument(vm.src); 28 | vm.loading = true; 29 | vm.currentItem = 1; 30 | loadingTask.then((pdf: PDFDocumentProxy) => { 31 | vm._pdf = pdf; 32 | vm.totalItem = pdf.numPages; 33 | vm.loaded = true; 34 | vm.loadResource(); 35 | }, (reason: string) => { 36 | console.error(reason); 37 | }); 38 | } 39 | 40 | loadResource() { 41 | const vm = this; 42 | if (!vm.loaded) { 43 | vm._pendingReload = true; 44 | return; 45 | } 46 | vm.loaded = false; 47 | const url = vm.src; 48 | const page = vm.currentItem; 49 | 50 | vm._pdf.getPage(page).then((pdfPage) => { 51 | vm._page = pdfPage; 52 | vm.loadImage(url, page, () => { 53 | vm.loaded = true; 54 | vm.loading = false; 55 | if (vm._pendingReload) { 56 | vm._pendingReload = false; 57 | vm.loadResource(); 58 | } else { 59 | vm.resourceChange.next(); 60 | } 61 | }); 62 | }); 63 | } 64 | 65 | private loadImage(src: string, page: number, onFinish: () => void) { 66 | const vm = this; 67 | const cacheimg = vm._imageCache.getImage(src, page); 68 | if (cacheimg) { 69 | vm._image = cacheimg; 70 | onFinish(); 71 | return; 72 | } 73 | 74 | const canvas: HTMLCanvasElement = document.createElement('canvas'); 75 | const context = canvas.getContext('2d'); 76 | const pageVp = vm._page.getViewport(2); 77 | 78 | canvas.width = pageVp.width; 79 | canvas.height = pageVp.height; 80 | 81 | const renderContext = { 82 | canvasContext: context, 83 | viewport: pageVp 84 | }; 85 | const renderTask = vm._page.render(renderContext); 86 | renderTask.then(function () { 87 | canvas.toBlob(blob => { 88 | const img = new Image(); 89 | img.onload = onFinish; 90 | img.src = URL.createObjectURL(blob); 91 | vm._imageCache.saveImage(src, page, img); 92 | vm._image = img; 93 | }); 94 | }); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /projects/ngx-imageviewer/src/public_api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of ngx-imageviewer 3 | */ 4 | 5 | export { ImageViewerModule } from './lib/imageviewer.module'; 6 | export { ImageViewerConfig, createButtonConfig, IMAGEVIEWER_CONFIG } from './lib/imageviewer.config'; 7 | -------------------------------------------------------------------------------- /projects/ngx-imageviewer/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'core-js/es7/reflect'; 4 | import 'zone.js/dist/zone'; 5 | import 'zone.js/dist/zone-testing'; 6 | import { getTestBed } from '@angular/core/testing'; 7 | import { 8 | BrowserDynamicTestingModule, 9 | platformBrowserDynamicTesting 10 | } from '@angular/platform-browser-dynamic/testing'; 11 | 12 | declare const require: any; 13 | 14 | // First, initialize the Angular testing environment. 15 | getTestBed().initTestEnvironment( 16 | BrowserDynamicTestingModule, 17 | platformBrowserDynamicTesting() 18 | ); 19 | // Then we find all the tests. 20 | const context = require.context('./', true, /\.spec\.ts$/); 21 | // And load the modules. 22 | context.keys().map(context); 23 | -------------------------------------------------------------------------------- /projects/ngx-imageviewer/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "target": "es2015", 6 | "module": "es2015", 7 | "moduleResolution": "node", 8 | "declaration": true, 9 | "sourceMap": true, 10 | "inlineSources": true, 11 | "emitDecoratorMetadata": true, 12 | "experimentalDecorators": true, 13 | "importHelpers": true, 14 | "types": [], 15 | "lib": [ 16 | "dom", 17 | "es2015" 18 | ] 19 | }, 20 | "angularCompilerOptions": { 21 | "annotateForClosureCompiler": false, 22 | "skipTemplateCodegen": true, 23 | "strictMetadataEmit": true, 24 | "fullTemplateTypeCheck": true, 25 | "strictInjectionParameters": true, 26 | "flatModuleId": "AUTOGENERATED", 27 | "flatModuleOutFile": "AUTOGENERATED" 28 | }, 29 | "exclude": [ 30 | "src/test.ts", 31 | "**/*.spec.ts" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /projects/ngx-imageviewer/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts" 12 | ], 13 | "include": [ 14 | "**/*.spec.ts", 15 | "**/*.d.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /projects/ngx-imageviewer/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "ngx", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "ngx", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { GettingStartedComponent } from './gettingstarted/gettingstarted.component'; 4 | import { BasicUsageComponent } from './basicusage/basicusage.component'; 5 | import { AutoResizeComponent } from './autoresize/autoresize.component'; 6 | import { UploadPreviewComponent } from './uploadpreview/uploadpreview.component'; 7 | import { ConditionalDisplayComponent } from './conditionaldisplay/conditionaldisplay.component'; 8 | 9 | const routes: Routes = [ 10 | { path: '', redirectTo: '/getting-started', pathMatch: 'full' }, 11 | { path: 'getting-started', component: GettingStartedComponent }, 12 | { path: 'basic-usage', component: BasicUsageComponent }, 13 | { path: 'autoresize', component: AutoResizeComponent }, 14 | { path: 'upload-preview', component: UploadPreviewComponent }, 15 | { path: 'conditional-display', component: ConditionalDisplayComponent }, 16 | { path: '**', redirectTo: '/' } 17 | ]; 18 | 19 | export const MAIN_ROUTES = [ 20 | { path: 'getting-started', title: 'Getting Started', label: 'Getting Started' } 21 | ]; 22 | 23 | export const EXAMPLES_ROUTES = [ 24 | { path: 'basic-usage', title: 'Basic Usage', label: 'Basic Usage' }, 25 | { path: 'conditional-display', title: 'Show/hide Test', label: 'Conditional Display' }, 26 | { path: 'autoresize', title: 'Autoresize Test', label: 'Autoresize' }, 27 | { path: 'upload-preview', title: 'Preview file from Upload field', label: 'Upload Preview' }, 28 | ]; 29 | 30 | @NgModule({ 31 | imports: [RouterModule.forRoot(routes)], 32 | exports: [RouterModule] 33 | }) 34 | export class AppRoutingModule { } 35 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 9 | {{menu}} 10 | 11 | {{link.label}} 12 | 13 | 14 | 15 | {{link.label}} 16 | 17 | 18 | 19 | 20 | 28 | {{ currentTitle }} 29 | 30 |
31 | 32 |
33 |
34 |
35 | 36 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | .menu-title { 2 | padding: 10px 20px 0; 3 | margin: 0; 4 | } 5 | 6 | .sidenav-container { 7 | height: 100%; 8 | } 9 | 10 | .sidenav { 11 | width: 200px; 12 | } 13 | 14 | .route-content { 15 | padding: 20px; 16 | min-height: calc(100vh - 64px - 40px); 17 | max-height: calc(100vh - 64px - 40px); 18 | overflow-y: auto; 19 | } 20 | -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async, ComponentFixture } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 4 | import { SharedModule } from './shared/shared.module'; 5 | 6 | import 'hammerjs'; 7 | 8 | import { AppComponent } from './app.component'; 9 | 10 | describe('AppComponent', () => { 11 | let component: AppComponent; 12 | let fixture: ComponentFixture; 13 | 14 | beforeEach(async(() => { 15 | TestBed.configureTestingModule({ 16 | imports: [ 17 | RouterTestingModule, 18 | BrowserAnimationsModule, 19 | SharedModule 20 | ], 21 | declarations: [ 22 | AppComponent 23 | ], 24 | }).compileComponents(); 25 | })); 26 | 27 | beforeEach(() => { 28 | fixture = TestBed.createComponent(AppComponent); 29 | component = fixture.componentInstance; 30 | fixture.detectChanges(); 31 | }); 32 | 33 | it('should create', () => { 34 | expect(component).toBeTruthy(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy } from '@angular/core'; 2 | import { BreakpointObserver, Breakpoints, BreakpointState } from '@angular/cdk/layout'; 3 | import { Observable, Subscription } from 'rxjs'; 4 | import { Router, NavigationEnd } from '@angular/router'; 5 | 6 | import { MAIN_ROUTES, EXAMPLES_ROUTES } from './app-routing.module'; 7 | 8 | const APP_MENU = 'NgxImageViewer'; 9 | const DEFAULT_TITLE = 'Not found'; 10 | 11 | @Component({ 12 | selector: 'app-root', 13 | templateUrl: './app.component.html', 14 | styleUrls: ['./app.component.scss'] 15 | }) 16 | export class AppComponent implements OnDestroy { 17 | menu = APP_MENU; 18 | mainlinks = MAIN_ROUTES; 19 | exampleLinks = EXAMPLES_ROUTES; 20 | currentTitle = DEFAULT_TITLE; 21 | 22 | private eventSub: Subscription; 23 | 24 | isHandset: Observable = this.breakpointObserver.observe(Breakpoints.Handset); 25 | constructor( 26 | private breakpointObserver: BreakpointObserver, 27 | private router: Router 28 | ) { 29 | this.eventSub = this.router.events.subscribe(event => { 30 | if (event instanceof NavigationEnd) { 31 | const newRoute = event.urlAfterRedirects; 32 | const mapping = [ ... MAIN_ROUTES, ... EXAMPLES_ROUTES].filter( x => x.path === newRoute.substr(1)); 33 | this.currentTitle = mapping.length ? mapping[0].title : DEFAULT_TITLE; 34 | } 35 | }); 36 | } 37 | 38 | ngOnDestroy() { 39 | this.eventSub.unsubscribe(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 3 | import { NgModule } from '@angular/core'; 4 | 5 | import { AppRoutingModule } from './app-routing.module'; 6 | import { AppComponent } from './app.component'; 7 | 8 | import { GettingStartedComponent } from './gettingstarted/gettingstarted.component'; 9 | import { SharedModule } from './shared/shared.module'; 10 | import { BasicUsageComponent } from './basicusage/basicusage.component'; 11 | import { AutoResizeComponent } from './autoresize/autoresize.component'; 12 | import { UploadPreviewComponent } from './uploadpreview/uploadpreview.component'; 13 | import { ConditionalDisplayComponent } from './conditionaldisplay/conditionaldisplay.component'; 14 | 15 | @NgModule({ 16 | declarations: [ 17 | AppComponent, 18 | GettingStartedComponent, 19 | BasicUsageComponent, 20 | AutoResizeComponent, 21 | UploadPreviewComponent, 22 | ConditionalDisplayComponent 23 | ], 24 | imports: [ 25 | BrowserModule.withServerTransition({ appId: 'serverApp' }), 26 | AppRoutingModule, 27 | BrowserAnimationsModule, 28 | SharedModule 29 | ], 30 | providers: [], 31 | bootstrap: [AppComponent] 32 | }) 33 | export class AppModule { } 34 | -------------------------------------------------------------------------------- /src/app/app.server.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { ServerModule } from '@angular/platform-server'; 3 | 4 | import { AppModule } from './app.module'; 5 | import { AppComponent } from './app.component'; 6 | 7 | @NgModule({ 8 | imports: [ 9 | AppModule, 10 | ServerModule, 11 | ], 12 | bootstrap: [AppComponent], 13 | }) 14 | export class AppServerModule {} 15 | -------------------------------------------------------------------------------- /src/app/autoresize/autoresize.component.html: -------------------------------------------------------------------------------- 1 |

Resize the window to see the expected behavior

2 |
3 | 4 |
5 | -------------------------------------------------------------------------------- /src/app/autoresize/autoresize.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display: flex; 3 | flex-direction: column; 4 | height: 100%; 5 | } 6 | 7 | h2 { 8 | margin-left: 20px; 9 | white-space: nowrap; 10 | } 11 | 12 | $sidebarWidth: 200px; 13 | $contentPadding: 20px; 14 | $wDiff: $sidebarWidth + $contentPadding * 2; 15 | 16 | $headerHeight: 64px; 17 | $subHeaderHeight: 32px + 16px; 18 | $hDiff: $headerHeight + $subHeaderHeight + $contentPadding * 2; 19 | 20 | .imagewrapper { 21 | flex: 1; 22 | box-sizing: border-box; 23 | overflow: hidden; 24 | max-width: calc(100vw - #{$wDiff}); 25 | min-width: calc(100vw - #{$wDiff}); 26 | width: calc(100vw - #{$wDiff}); 27 | max-height: calc(100vh - #{$hDiff}); 28 | min-height: calc(100vh - #{$hDiff}); 29 | height: calc(100vh - #{$hDiff}); 30 | 31 | @media only screen and (max-width: 600px) { 32 | max-width: calc(100vw - #{$contentPadding * 2}); 33 | min-width: calc(100vw - #{$contentPadding * 2}); 34 | width: calc(100vw - #{$contentPadding * 2}); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/app/autoresize/autoresize.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 3 | import { SharedModule } from '../shared/shared.module'; 4 | 5 | import { AutoResizeComponent } from './autoresize.component'; 6 | 7 | describe('AutoResizeComponent', () => { 8 | let component: AutoResizeComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(async(() => { 12 | TestBed.configureTestingModule({ 13 | imports: [ BrowserAnimationsModule, SharedModule ], 14 | declarations: [ AutoResizeComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(AutoResizeComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/autoresize/autoresize.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, AfterViewInit, ViewChild, ElementRef, HostListener } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-autoresize', 5 | templateUrl: './autoresize.component.html', 6 | styleUrls: ['./autoresize.component.scss'] 7 | }) 8 | export class AutoResizeComponent implements AfterViewInit { 9 | @ViewChild('imagewrapper',{static: false}) wrapper: ElementRef; 10 | 11 | private _canvasDim = { width: 10, height: 10 }; 12 | get canvasDim() { 13 | return this._canvasDim; 14 | } 15 | 16 | ngAfterViewInit() { 17 | this.updateCanvasDim(); 18 | } 19 | 20 | @HostListener('window:resize', ['$event']) 21 | onResize(event) { 22 | this.updateCanvasDim(); 23 | } 24 | 25 | private updateCanvasDim() { 26 | const el = this.wrapper && this.wrapper.nativeElement ? this.wrapper.nativeElement : null; 27 | if (el && (el.offsetWidth !== this._canvasDim.width || el.offsetHeight !== this._canvasDim.height)) { 28 | const newDim = { width: el.offsetWidth - 2, height: el.offsetHeight - 2 }; 29 | setTimeout(() => this._canvasDim = newDim, 0); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/app/basicusage/basicusage.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | clear 5 | 6 | 7 |
8 | 9 | {{ sample.label }} 10 | 11 |
12 | 13 | -------------------------------------------------------------------------------- /src/app/basicusage/basicusage.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 3 | import { SharedModule } from '../shared/shared.module'; 4 | 5 | import { BasicUsageComponent } from './basicusage.component'; 6 | 7 | describe('BasicUsageComponent', () => { 8 | let component: BasicUsageComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(async(() => { 12 | TestBed.configureTestingModule({ 13 | imports: [ BrowserAnimationsModule, SharedModule ], 14 | declarations: [ BasicUsageComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(BasicUsageComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/basicusage/basicusage.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-basicusage', 5 | templateUrl: './basicusage.component.html' 6 | }) 7 | export class BasicUsageComponent { 8 | samples = [ 9 | { label: 'PDF Test', url: 'https://hallysonh.github.io/ngx-imageviewer/pdf-test.pdf' }, 10 | { label: 'Image 1 (BIG)', url: 'https://hallysonh.github.io/ngx-imageviewer/assets/imgs/sample-0.jpg' }, 11 | { label: 'Image 2', url: 'https://hallysonh.github.io/ngx-imageviewer/assets/imgs/sample-1.jpg' }, 12 | { label: 'Image 3', url: 'https://hallysonh.github.io/ngx-imageviewer/assets/imgs/sample-2.jpg' }, 13 | { label: 'Image 4', url: 'https://hallysonh.github.io/ngx-imageviewer/assets/imgs/sample-3.jpg' }, 14 | { label: 'Image 5', url: 'https://hallysonh.github.io/ngx-imageviewer/assets/imgs/sample-4.jpg' }, 15 | { label: 'Image 6', url: 'https://hallysonh.github.io/ngx-imageviewer/assets/imgs/sample-5.jpg' } 16 | ]; 17 | 18 | canvasWidth = 800; 19 | canvasHeight = 600; 20 | imageSrc = this.samples[0].url; 21 | 22 | constructor() { } 23 | } 24 | -------------------------------------------------------------------------------- /src/app/conditionaldisplay/conditionaldisplay.component.html: -------------------------------------------------------------------------------- 1 |

Test conditional display

2 | 3 |
4 |
5 | 6 | {{ condition ? 'Visible' : 'Hidden'}} 7 |
8 |
9 | 10 | 11 |
12 |
13 | 14 | 15 | 16 |
Nothing here
17 | -------------------------------------------------------------------------------- /src/app/conditionaldisplay/conditionaldisplay.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 3 | import { SharedModule } from '../shared/shared.module'; 4 | 5 | import { ConditionalDisplayComponent } from './conditionaldisplay.component'; 6 | 7 | describe('ConditionalDisplayComponent', () => { 8 | let component: ConditionalDisplayComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(async(() => { 12 | TestBed.configureTestingModule({ 13 | imports: [ BrowserAnimationsModule, SharedModule ], 14 | declarations: [ ConditionalDisplayComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(ConditionalDisplayComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/conditionaldisplay/conditionaldisplay.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | const URLS = [ 4 | 'https://hallysonh.github.io/ngx-imageviewer/pdf-test.pdf', 5 | 'https://hallysonh.github.io/ngx-imageviewer/assets/imgs/sample-1.jpg' 6 | ]; 7 | 8 | @Component({ 9 | selector: 'app-conditionaldisplay', 10 | templateUrl: './conditionaldisplay.component.html' 11 | }) 12 | export class ConditionalDisplayComponent { 13 | condition = true; 14 | url = URLS[0]; 15 | 16 | private _currentIndex = 0; 17 | 18 | constructor() { } 19 | 20 | toggleUrl() { 21 | this._currentIndex = this._currentIndex ? 0 : 1; 22 | this.url = URLS[this._currentIndex]; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/app/gettingstarted/gettingstarted.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 | -------------------------------------------------------------------------------- /src/app/gettingstarted/gettingstarted.component.scss: -------------------------------------------------------------------------------- 1 | .loading-set { 2 | margin-top: 50px; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/gettingstarted/gettingstarted.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 3 | import { SharedModule } from '../shared/shared.module'; 4 | 5 | import { GettingStartedComponent } from './gettingstarted.component'; 6 | 7 | describe('GettingStartedComponent', () => { 8 | let component: GettingStartedComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(async(() => { 12 | TestBed.configureTestingModule({ 13 | imports: [ BrowserAnimationsModule, SharedModule ], 14 | declarations: [ GettingStartedComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(GettingStartedComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/gettingstarted/gettingstarted.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, AfterContentInit } from '@angular/core'; 2 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; 3 | import { HttpClient } from '@angular/common/http'; 4 | import { Subscription } from 'rxjs'; 5 | import { Converter } from 'showdown'; 6 | 7 | declare const hljs: any; 8 | 9 | const README_URL = 'https://raw.githubusercontent.com/hallysonh/ngx-imageviewer/master/README.md'; 10 | 11 | @Component({ 12 | selector: 'app-gettingstarted', 13 | templateUrl: './gettingstarted.component.html', 14 | styleUrls: ['./gettingstarted.component.scss'] 15 | }) 16 | export class GettingStartedComponent implements OnDestroy, AfterContentInit { 17 | html: SafeHtml; 18 | isLoading = false; 19 | 20 | private converter: Converter; 21 | private subscrition: Subscription; 22 | private contentInited = false; 23 | 24 | constructor( 25 | private http: HttpClient, 26 | private sanitizer: DomSanitizer 27 | ) { 28 | this.isLoading = true; 29 | this.converter = new Converter(); 30 | this.subscrition = this.http.get(README_URL, { responseType: 'text' }).subscribe(markdown => { 31 | this.html = this.sanitizer.bypassSecurityTrustHtml(this.converter.makeHtml(markdown)); 32 | this.initHightlight(); 33 | this.isLoading = false; 34 | }, () => this.isLoading = false); 35 | } 36 | 37 | ngAfterContentInit() { 38 | this.contentInited = true; 39 | this.initHightlight(); 40 | } 41 | 42 | ngOnDestroy() { 43 | this.subscrition.unsubscribe(); 44 | } 45 | 46 | private initHightlight() { 47 | if (!this.html || !this.contentInited) { return; } 48 | setTimeout(() => hljs.initHighlighting(), 1200); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/app/shared/shared.module.spec.ts: -------------------------------------------------------------------------------- 1 | import { SharedModule } from './shared.module'; 2 | 3 | describe('SharedModule', () => { 4 | let sharedModule: SharedModule; 5 | 6 | beforeEach(() => { 7 | sharedModule = new SharedModule(); 8 | }); 9 | 10 | it('should create an instance', () => { 11 | expect(sharedModule).toBeTruthy(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { FormsModule } from '@angular/forms'; 3 | import { CommonModule } from '@angular/common'; 4 | import { HttpClientModule } from '@angular/common/http'; 5 | import { FlexLayoutModule } from '@angular/flex-layout'; 6 | import { LayoutModule } from '@angular/cdk/layout'; 7 | import { 8 | MatToolbarModule, MatButtonModule, MatSidenavModule, 9 | MatIconModule, MatListModule, MatFormFieldModule, 10 | MatInputModule, MatButtonToggleModule, MatSlideToggleModule, MatProgressSpinnerModule 11 | } from '@angular/material'; 12 | 13 | import { ImageViewerModule } from '@hallysonh/ngx-imageviewer'; 14 | 15 | @NgModule({ 16 | imports: [ 17 | CommonModule 18 | ], 19 | declarations: [], 20 | exports: [ 21 | CommonModule, 22 | HttpClientModule, 23 | FormsModule, 24 | FlexLayoutModule, 25 | LayoutModule, 26 | MatToolbarModule, 27 | MatButtonModule, 28 | MatButtonToggleModule, 29 | MatSidenavModule, 30 | MatIconModule, 31 | MatListModule, 32 | MatFormFieldModule, 33 | MatInputModule, 34 | MatSlideToggleModule, 35 | MatProgressSpinnerModule, 36 | ImageViewerModule 37 | ] 38 | }) 39 | export class SharedModule { } 40 | -------------------------------------------------------------------------------- /src/app/uploadpreview/uploadpreview.component.html: -------------------------------------------------------------------------------- 1 |

Preview file from Upload field

2 |
3 | 4 | 5 |
6 | 7 | 8 | 9 |

Select any of your local files to preview it here

10 | -------------------------------------------------------------------------------- /src/app/uploadpreview/uploadpreview.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 3 | import { SharedModule } from '../shared/shared.module'; 4 | 5 | import { UploadPreviewComponent } from './uploadpreview.component'; 6 | 7 | describe('UploadpreviewComponent', () => { 8 | let component: UploadPreviewComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(async(() => { 12 | TestBed.configureTestingModule({ 13 | imports: [ BrowserAnimationsModule, SharedModule ], 14 | declarations: [ UploadPreviewComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(UploadPreviewComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/uploadpreview/uploadpreview.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-uploadpreview', 5 | templateUrl: './uploadpreview.component.html' 6 | }) 7 | export class UploadPreviewComponent { 8 | docFile: File; 9 | } 10 | -------------------------------------------------------------------------------- /src/assets/highlight/highlight.pack.js: -------------------------------------------------------------------------------- 1 | /*! highlight.js v9.12.0 | BSD3 License | git.io/hljslicense */ 2 | !function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(e){function n(e){return e.replace(/&/g,"&").replace(//g,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0===t.index}function a(e){return k.test(e)}function i(e){var n,t,r,i,o=e.className+" ";if(o+=e.parentNode?e.parentNode.className:"",t=B.exec(o))return w(t[1])?t[1]:"no-highlight";for(o=o.split(/\s+/),n=0,r=o.length;r>n;n++)if(i=o[n],a(i)||w(i))return i}function o(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!==r[0].offset?e[0].offset"}function u(e){s+=""}function c(e){("start"===e.event?o:u)(e.node)}for(var l=0,s="",f=[];e.length||r.length;){var g=i();if(s+=n(a.substring(l,g[0].offset)),l=g[0].offset,g===e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g===e&&g.length&&g[0].offset===l);f.reverse().forEach(o)}else"start"===g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return s+n(a.substr(l))}function l(e){return e.v&&!e.cached_variants&&(e.cached_variants=e.v.map(function(n){return o(e,{v:null},n)})),e.cached_variants||e.eW&&[o(e)]||[e]}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var o={},u=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");o[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?u("keyword",a.k):x(a.k).forEach(function(e){u(e,a.k[e])}),a.k=o}a.lR=t(a.l||/\w+/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),null==a.r&&(a.r=1),a.c||(a.c=[]),a.c=Array.prototype.concat.apply([],a.c.map(function(e){return l("self"===e?a:e)})),a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var c=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=c.length?t(c.join("|"),!0):{exec:function(){return null}}}}r(e)}function f(e,t,a,i){function o(e,n){var t,a;for(t=0,a=n.c.length;a>t;t++)if(r(n.c[t].bR,e))return n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function c(e,n){return!a&&r(n.iR,e)}function l(e,n){var t=N.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function p(e,n,t,r){var a=r?"":I.classPrefix,i='',i+n+o}function h(){var e,t,r,a;if(!E.k)return n(k);for(a="",t=0,E.lR.lastIndex=0,r=E.lR.exec(k);r;)a+=n(k.substring(t,r.index)),e=l(E,r),e?(B+=e[1],a+=p(e[0],n(r[0]))):a+=n(r[0]),t=E.lR.lastIndex,r=E.lR.exec(k);return a+n(k.substr(t))}function d(){var e="string"==typeof E.sL;if(e&&!y[E.sL])return n(k);var t=e?f(E.sL,k,!0,x[E.sL]):g(k,E.sL.length?E.sL:void 0);return E.r>0&&(B+=t.r),e&&(x[E.sL]=t.top),p(t.language,t.value,!1,!0)}function b(){L+=null!=E.sL?d():h(),k=""}function v(e){L+=e.cN?p(e.cN,"",!0):"",E=Object.create(e,{parent:{value:E}})}function m(e,n){if(k+=e,null==n)return b(),0;var t=o(n,E);if(t)return t.skip?k+=n:(t.eB&&(k+=n),b(),t.rB||t.eB||(k=n)),v(t,n),t.rB?0:n.length;var r=u(E,n);if(r){var a=E;a.skip?k+=n:(a.rE||a.eE||(k+=n),b(),a.eE&&(k=n));do E.cN&&(L+=C),E.skip||(B+=E.r),E=E.parent;while(E!==r.parent);return r.starts&&v(r.starts,""),a.rE?0:n.length}if(c(n,E))throw new Error('Illegal lexeme "'+n+'" for mode "'+(E.cN||"")+'"');return k+=n,n.length||1}var N=w(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var R,E=i||N,x={},L="";for(R=E;R!==N;R=R.parent)R.cN&&(L=p(R.cN,"",!0)+L);var k="",B=0;try{for(var M,j,O=0;;){if(E.t.lastIndex=O,M=E.t.exec(t),!M)break;j=m(t.substring(O,M.index),M[0]),O=M.index+j}for(m(t.substr(O)),R=E;R.parent;R=R.parent)R.cN&&(L+=C);return{r:B,value:L,language:e,top:E}}catch(T){if(T.message&&-1!==T.message.indexOf("Illegal"))return{r:0,value:n(t)};throw T}}function g(e,t){t=t||I.languages||x(y);var r={r:0,value:n(e)},a=r;return t.filter(w).forEach(function(n){var t=f(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}),a.language&&(r.second_best=a),r}function p(e){return I.tabReplace||I.useBR?e.replace(M,function(e,n){return I.useBR&&"\n"===e?"
":I.tabReplace?n.replace(/\t/g,I.tabReplace):""}):e}function h(e,n,t){var r=n?L[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function d(e){var n,t,r,o,l,s=i(e);a(s)||(I.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):n=e,l=n.textContent,r=s?f(s,l,!0):g(l),t=u(n),t.length&&(o=document.createElementNS("http://www.w3.org/1999/xhtml","div"),o.innerHTML=r.value,r.value=c(t,u(o),l)),r.value=p(r.value),e.innerHTML=r.value,e.className=h(e.className,s,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function b(e){I=o(I,e)}function v(){if(!v.called){v.called=!0;var e=document.querySelectorAll("pre code");E.forEach.call(e,d)}}function m(){addEventListener("DOMContentLoaded",v,!1),addEventListener("load",v,!1)}function N(n,t){var r=y[n]=t(e);r.aliases&&r.aliases.forEach(function(e){L[e]=n})}function R(){return x(y)}function w(e){return e=(e||"").toLowerCase(),y[e]||y[L[e]]}var E=[],x=Object.keys,y={},L={},k=/^(no-?highlight|plain|text)$/i,B=/\blang(?:uage)?-([\w-]+)\b/i,M=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,C="
",I={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0};return e.highlight=f,e.highlightAuto=g,e.fixMarkup=p,e.highlightBlock=d,e.configure=b,e.initHighlighting=v,e.initHighlightingOnLoad=m,e.registerLanguage=N,e.listLanguages=R,e.getLanguage=w,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e});hljs.registerLanguage("bash",function(e){var t={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)}/}]},s={cN:"string",b:/"/,e:/"/,c:[e.BE,t,{cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]}]},a={cN:"string",b:/'/,e:/'/};return{aliases:["sh","zsh"],l:/\b-?[a-z\._]+\b/,k:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"meta",b:/^#![^\n]+sh\s*$/,r:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:!0,c:[e.inherit(e.TM,{b:/\w[\w\d_]*/})],r:0},e.HCM,s,a,t]}});hljs.registerLanguage("json",function(e){var i={literal:"true false null"},n=[e.QSM,e.CNM],r={e:",",eW:!0,eE:!0,c:n,k:i},t={b:"{",e:"}",c:[{cN:"attr",b:/"/,e:/"/,c:[e.BE],i:"\\n"},e.inherit(r,{b:/:/})],i:"\\S"},c={b:"\\[",e:"\\]",c:[e.inherit(r)],i:"\\S"};return n.splice(n.length,0,t,c),{c:n,k:i,i:"\\S"}});hljs.registerLanguage("xml",function(s){var e="[A-Za-z0-9\\._:-]+",t={eW:!0,i:/`]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist"],cI:!0,c:[{cN:"meta",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},s.C("",{r:10}),{b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{b:/<\?(php)?/,e:/\?>/,sL:"php",c:[{b:"/\\*",e:"\\*/",skip:!0}]},{cN:"tag",b:"|$)",e:">",k:{name:"style"},c:[t],starts:{e:"",rE:!0,sL:["css","xml"]}},{cN:"tag",b:"|$)",e:">",k:{name:"script"},c:[t],starts:{e:"",rE:!0,sL:["actionscript","javascript","handlebars","xml"]}},{cN:"meta",v:[{b:/<\?xml/,e:/\?>/,r:10},{b:/<\?\w+/,e:/\?>/}]},{cN:"tag",b:"",c:[{cN:"name",b:/[^\/><\s]+/,r:0},t]}]}});hljs.registerLanguage("typescript",function(e){var r={keyword:"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class public private protected get set super static implements enum export import declare type namespace abstract as from extends async await",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document any number boolean string void Promise"};return{aliases:["ts"],k:r,c:[{cN:"meta",b:/^\s*['"]use strict['"]/},e.ASM,e.QSM,{cN:"string",b:"`",e:"`",c:[e.BE,{cN:"subst",b:"\\$\\{",e:"\\}"}]},e.CLCM,e.CBCM,{cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+e.IR+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:e.IR},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:r,c:["self",e.CLCM,e.CBCM]}]}]}],r:0},{cN:"function",b:"function",e:/[\{;]/,eE:!0,k:r,c:["self",e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:r,c:[e.CLCM,e.CBCM],i:/["'\(]/}],i:/%/,r:0},{bK:"constructor",e:/\{/,eE:!0,c:["self",{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:r,c:[e.CLCM,e.CBCM],i:/["'\(]/}]},{b:/module\./,k:{built_in:"module"},r:0},{bK:"module",e:/\{/,eE:!0},{bK:"interface",e:/\{/,eE:!0,k:"interface extends"},{b:/\$[(.]/},{b:"\\."+e.IR,r:0},{cN:"meta",b:"@[A-Za-z]+"}]}});hljs.registerLanguage("shell",function(s){return{aliases:["console"],c:[{cN:"meta",b:"^\\s{0,3}[\\w\\d\\[\\]()@-]*[>%$#]",starts:{e:"$",sL:"bash"}}]}}); -------------------------------------------------------------------------------- /src/assets/highlight/styles/github-gist.css: -------------------------------------------------------------------------------- 1 | /** 2 | * GitHub Gist Theme 3 | * Author : Louis Barranqueiro - https://github.com/LouisBarranqueiro 4 | */ 5 | 6 | .hljs { 7 | display: block; 8 | background: white; 9 | padding: 0.5em; 10 | color: #333333; 11 | overflow-x: auto; 12 | } 13 | 14 | .hljs-comment, 15 | .hljs-meta { 16 | color: #969896; 17 | } 18 | 19 | .hljs-string, 20 | .hljs-variable, 21 | .hljs-template-variable, 22 | .hljs-strong, 23 | .hljs-emphasis, 24 | .hljs-quote { 25 | color: #df5000; 26 | } 27 | 28 | .hljs-keyword, 29 | .hljs-selector-tag, 30 | .hljs-type { 31 | color: #a71d5d; 32 | } 33 | 34 | .hljs-literal, 35 | .hljs-symbol, 36 | .hljs-bullet, 37 | .hljs-attribute { 38 | color: #0086b3; 39 | } 40 | 41 | .hljs-section, 42 | .hljs-name { 43 | color: #63a35c; 44 | } 45 | 46 | .hljs-tag { 47 | color: #333333; 48 | } 49 | 50 | .hljs-title, 51 | .hljs-attr, 52 | .hljs-selector-id, 53 | .hljs-selector-class, 54 | .hljs-selector-attr, 55 | .hljs-selector-pseudo { 56 | color: #795da3; 57 | } 58 | 59 | .hljs-addition { 60 | color: #55a532; 61 | background-color: #eaffea; 62 | } 63 | 64 | .hljs-deletion { 65 | color: #bd2c00; 66 | background-color: #ffecec; 67 | } 68 | 69 | .hljs-link { 70 | text-decoration: underline; 71 | } 72 | -------------------------------------------------------------------------------- /src/assets/imgs/sample-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hallysonh/ngx-imageviewer/1367ec3b3e34663465fd07aaa300997a45579700/src/assets/imgs/sample-0.jpg -------------------------------------------------------------------------------- /src/assets/imgs/sample-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hallysonh/ngx-imageviewer/1367ec3b3e34663465fd07aaa300997a45579700/src/assets/imgs/sample-1.jpg -------------------------------------------------------------------------------- /src/assets/imgs/sample-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hallysonh/ngx-imageviewer/1367ec3b3e34663465fd07aaa300997a45579700/src/assets/imgs/sample-2.jpg -------------------------------------------------------------------------------- /src/assets/imgs/sample-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hallysonh/ngx-imageviewer/1367ec3b3e34663465fd07aaa300997a45579700/src/assets/imgs/sample-3.jpg -------------------------------------------------------------------------------- /src/assets/imgs/sample-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hallysonh/ngx-imageviewer/1367ec3b3e34663465fd07aaa300997a45579700/src/assets/imgs/sample-4.jpg -------------------------------------------------------------------------------- /src/assets/imgs/sample-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hallysonh/ngx-imageviewer/1367ec3b3e34663465fd07aaa300997a45579700/src/assets/imgs/sample-5.jpg -------------------------------------------------------------------------------- /src/browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # For IE 9-11 support, please uncomment the last line of the file and adjust as needed 5 | > 0.5% 6 | last 2 versions 7 | Firefox ESR 8 | not dead 9 | # IE 9-11 -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * In development mode, to ignore zone related error stack frames such as 11 | * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can 12 | * import the following file, but please comment it out in production mode 13 | * because it will have performance impact when throw error 14 | */ 15 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 16 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hallysonh/ngx-imageviewer/1367ec3b3e34663465fd07aaa300997a45579700/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | NgxImageViewerView 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 77 | 78 | 79 | 80 | 81 |
82 | 86 | 87 | 88 | 89 |
90 |
91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | var path = require('path'); 4 | // process.env.CHROME_BIN = require('puppeteer').executablePath(); 5 | 6 | module.exports = function (config) { 7 | config.set({ 8 | basePath: '', 9 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 10 | plugins: [ 11 | require('karma-jasmine'), 12 | require('karma-chrome-launcher'), 13 | require('karma-jasmine-html-reporter'), 14 | require('karma-coverage-istanbul-reporter'), 15 | require('@angular-devkit/build-angular/plugins/karma') 16 | ], 17 | client: { 18 | clearContext: false // leave Jasmine Spec Runner output visible in browser 19 | }, 20 | coverageIstanbulReporter: { 21 | dir: require('path').join(__dirname, '../coverage'), 22 | reports: ['html', 'lcovonly'], 23 | fixWebpackSourcePaths: true 24 | }, 25 | reporters: ['progress', 'kjhtml'], 26 | port: 9876, 27 | colors: true, 28 | logLevel: config.LOG_INFO, 29 | autoWatch: true, 30 | browsers: ['ChromeHeadless'], 31 | singleRun: true, 32 | customLaunchers: { 33 | ChromeHeadless: { 34 | base: 'Chrome', 35 | flags: [ 36 | '--disable-translate', 37 | '--headless', 38 | '--disable-gpu', 39 | '--disable-extensions', 40 | '--remote-debugging-port=9222' 41 | ] 42 | } 43 | } 44 | }); 45 | }; 46 | -------------------------------------------------------------------------------- /src/main.server.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | 3 | import { environment } from './environments/environment'; 4 | 5 | if (environment.production) { 6 | enableProdMode(); 7 | } 8 | 9 | export { AppServerModule } from './app/app.server.module'; 10 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | import 'hammerjs'; 8 | 9 | if (environment.production) { 10 | enableProdMode(); 11 | } 12 | 13 | document.addEventListener('DOMContentLoaded', () => { 14 | platformBrowserDynamic().bootstrapModule(AppModule) 15 | .catch(err => console.log(err)); 16 | }); 17 | -------------------------------------------------------------------------------- /src/pdf-test.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hallysonh/ngx-imageviewer/1367ec3b3e34663465fd07aaa300997a45579700/src/pdf-test.pdf -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | // import 'core-js/es6/symbol'; 23 | // import 'core-js/es6/object'; 24 | // import 'core-js/es6/function'; 25 | // import 'core-js/es6/parse-int'; 26 | // import 'core-js/es6/parse-float'; 27 | // import 'core-js/es6/number'; 28 | // import 'core-js/es6/math'; 29 | // import 'core-js/es6/string'; 30 | // import 'core-js/es6/date'; 31 | // import 'core-js/es6/array'; 32 | // import 'core-js/es6/regexp'; 33 | // import 'core-js/es6/map'; 34 | // import 'core-js/es6/weak-map'; 35 | // import 'core-js/es6/set'; 36 | 37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 38 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 39 | 40 | /** IE10 and IE11 requires the following for the Reflect API. */ 41 | // import 'core-js/es6/reflect'; 42 | 43 | 44 | /** Evergreen browsers require these. **/ 45 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. 46 | import 'core-js/es7/reflect'; 47 | 48 | 49 | /** 50 | * Web Animations `@angular/platform-browser/animations` 51 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 52 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 53 | **/ 54 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 55 | 56 | /** 57 | * By default, zone.js will patch all possible macroTask and DomEvents 58 | * user can disable parts of macroTask/DomEvents patch by setting following flags 59 | */ 60 | 61 | // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 62 | // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 63 | // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 64 | 65 | /* 66 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 67 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 68 | */ 69 | // (window as any).__Zone_enable_cross_context_check = true; 70 | 71 | /*************************************************************************************************** 72 | * Zone JS is required by default for Angular itself. 73 | */ 74 | import 'zone.js/dist/zone'; // Included with Angular CLI. 75 | 76 | 77 | 78 | /*************************************************************************************************** 79 | * APPLICATION IMPORTS 80 | */ 81 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | @import './styles/app.scss'; 4 | 5 | @import '~@angular/material/theming'; 6 | 7 | // always include only once per project 8 | @include mat-core(); 9 | 10 | // import our custom theme 11 | @import 'theme.scss'; 12 | 13 | // specify theme class eg: ... 14 | .app-theme { 15 | // use our theme with angular-material-theme mixin 16 | @include angular-material-theme($app-theme); 17 | 18 | .mat-button-toggle-checked { 19 | background-color: mat-color($app-accent); 20 | color: mat-color($app-accent, default-contrast); 21 | } 22 | } -------------------------------------------------------------------------------- /src/styles/app.scss: -------------------------------------------------------------------------------- 1 | body { margin: 0 } 2 | h3 { margin: 0 } 3 | .mb { margin-bottom: 20px } 4 | 5 | ngx-imageviewer canvas { 6 | border: 1px solid #ccc; 7 | } -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /src/theme.scss: -------------------------------------------------------------------------------- 1 | @include mat-core(); 2 | 3 | // Define the palettes for your theme using the Material Design palettes available in palette.scss 4 | // (imported above). For each palette, you can optionally specify a default, lighter, and darker 5 | // hue. 6 | $app-primary: mat-palette($mat-red, 700); 7 | $app-accent: mat-palette($mat-blue, A200, A100, A400); 8 | $app-warn: mat-palette($mat-deep-orange); 9 | $app-theme: mat-light-theme($app-primary, $app-accent, $app-warn); 10 | 11 | // Define an alternate dark theme. 12 | $dark-primary: mat-palette($mat-red, 700); 13 | $dark-accent: mat-palette($mat-blue, A200, A100, A400); 14 | $dark-warn: mat-palette($mat-deep-orange); 15 | $dark-theme: mat-dark-theme($dark-primary, $dark-accent, $dark-warn); 16 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "es2015", 6 | "types": [] 7 | }, 8 | "exclude": [ 9 | "src/test.ts", 10 | "**/*.spec.ts" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /src/tsconfig.server.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.app.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app-server", 5 | "baseUrl": ".", 6 | "module": "commonjs" 7 | }, 8 | "angularCompilerOptions": { 9 | "entryModule": "app/app.server.module#AppServerModule" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "module": "commonjs", 6 | "types": [ 7 | "jasmine", 8 | "node" 9 | ] 10 | }, 11 | "files": [ 12 | "test.ts", 13 | "polyfills.ts" 14 | ], 15 | "include": [ 16 | "**/*.spec.ts", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "moduleResolution": "node", 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ], 15 | "lib": [ 16 | "es2017", 17 | "dom" 18 | ], 19 | "paths": { 20 | "@hallysonh/ngx-imageviewer": [ 21 | "dist/ngx-imageviewer" 22 | ] 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "deprecation": { 15 | "severity": "warn" 16 | }, 17 | "eofline": true, 18 | "forin": true, 19 | "import-blacklist": [ 20 | true, 21 | "rxjs/Rx" 22 | ], 23 | "import-spacing": true, 24 | "indent": [ 25 | true, 26 | "spaces" 27 | ], 28 | "interface-over-type-literal": true, 29 | "label-position": true, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-arg": true, 47 | "no-bitwise": true, 48 | "no-console": [ 49 | true, 50 | "debug", 51 | "info", 52 | "time", 53 | "timeEnd", 54 | "trace" 55 | ], 56 | "no-construct": true, 57 | "no-debugger": true, 58 | "no-duplicate-super": true, 59 | "no-empty": false, 60 | "no-empty-interface": true, 61 | "no-eval": true, 62 | "no-inferrable-types": [ 63 | true, 64 | "ignore-params" 65 | ], 66 | "no-misused-new": true, 67 | "no-non-null-assertion": true, 68 | "no-shadowed-variable": true, 69 | "no-string-literal": false, 70 | "no-string-throw": true, 71 | "no-switch-case-fall-through": true, 72 | "no-trailing-whitespace": true, 73 | "no-unnecessary-initializer": true, 74 | "no-unused-expression": true, 75 | "no-use-before-declare": true, 76 | "no-var-keyword": true, 77 | "object-literal-sort-keys": false, 78 | "one-line": [ 79 | true, 80 | "check-open-brace", 81 | "check-catch", 82 | "check-else", 83 | "check-whitespace" 84 | ], 85 | "prefer-const": true, 86 | "quotemark": [ 87 | true, 88 | "single" 89 | ], 90 | "radix": true, 91 | "semicolon": [ 92 | true, 93 | "always" 94 | ], 95 | "triple-equals": [ 96 | true, 97 | "allow-null-check" 98 | ], 99 | "typedef-whitespace": [ 100 | true, 101 | { 102 | "call-signature": "nospace", 103 | "index-signature": "nospace", 104 | "parameter": "nospace", 105 | "property-declaration": "nospace", 106 | "variable-declaration": "nospace" 107 | } 108 | ], 109 | "unified-signatures": true, 110 | "variable-name": false, 111 | "whitespace": [ 112 | true, 113 | "check-branch", 114 | "check-decl", 115 | "check-operator", 116 | "check-separator", 117 | "check-type" 118 | ], 119 | "no-output-on-prefix": true, 120 | "use-input-property-decorator": true, 121 | "use-output-property-decorator": true, 122 | "use-host-property-decorator": true, 123 | "no-input-rename": true, 124 | "no-output-rename": true, 125 | "use-life-cycle-interface": true, 126 | "use-pipe-transform-interface": true, 127 | "component-class-suffix": true, 128 | "directive-class-suffix": true 129 | } 130 | } 131 | --------------------------------------------------------------------------------