From f64358ba5a9fe3308c22f3593977b568ce4035d8 Mon Sep 17 00:00:00 2001 From: SerDiuK Date: Fri, 18 Sep 2020 15:38:14 +0200 Subject: [PATCH] implement --- .editorconfig | 2 +- .eslintignore | 15 + .eslintrc.json | 28 + .prettierignore | 14 + .prettierrc | 2 + .vscode/extensions.json | 8 + .vscode/launch.json | 13 + .vscode/settings.json | 27 + package-lock.json | 10 +- package.json | 6 +- src/app/app.component.html | 4 +- src/app/app.module.ts | 20 +- src/app/pdf-viewer/pager/pager.component.html | 20 + src/app/pdf-viewer/pager/pager.component.scss | 51 ++ .../pdf-viewer/pager/pager.component.spec.ts | 25 + src/app/pdf-viewer/pager/pager.component.ts | 52 ++ src/app/pdf-viewer/pdf-viewer.component.html | 18 + src/app/pdf-viewer/pdf-viewer.component.scss | 843 ++++++++++-------- src/app/pdf-viewer/pdf-viewer.component.ts | 724 +++++++++------ src/app/pdf-viewer/pdf-viewer.module.ts | 22 +- src/app/utils/event-bus-utils.ts | 56 +- src/app/utils/get-position.ts | 17 + src/styles.scss | 2 +- tsconfig.json | 10 +- tslint.json | 91 -- 25 files changed, 1255 insertions(+), 825 deletions(-) create mode 100644 .eslintignore create mode 100644 .eslintrc.json create mode 100644 .prettierignore create mode 100644 .vscode/extensions.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 src/app/pdf-viewer/pager/pager.component.html create mode 100644 src/app/pdf-viewer/pager/pager.component.scss create mode 100644 src/app/pdf-viewer/pager/pager.component.spec.ts create mode 100644 src/app/pdf-viewer/pager/pager.component.ts create mode 100644 src/app/pdf-viewer/pdf-viewer.component.html create mode 100644 src/app/utils/get-position.ts delete mode 100644 tslint.json diff --git a/.editorconfig b/.editorconfig index e89330a61..6e87a003d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,4 @@ -# Editor configuration, see https://editorconfig.org +# Editor configuration, see http://editorconfig.org root = true [*] diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..e461be3f2 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,15 @@ +# Valid +/**/*.spec.ts +/**/*.stories.ts +/**/*.json +/**/*.po.ts + +# GQL auto generated files +/**/*types.ts + +# Config file in JS +/**/*index.js +/**/*index.ts +wallaby.js +decorate-angular-cli.js + diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 000000000..f00cd6c95 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,28 @@ +{ + "parser": "@typescript-eslint/parser", + "extends": [ + "@wemaintain/eslint-config", + "eslint:recommended", + "plugin:@angular-eslint/recommended", + "plugin:prettier/recommended", + "prettier/@typescript-eslint", + "prettier" + ], + "parserOptions": { + "ecmaVersion": 2020, + "sourceType": "module", + "project": "./tsconfig.base.json" + }, + "rules": { + "max-len": ["off"] + }, + "overrides": [ + { + "files": ["**/*.ts", "**/*.spec.ts"], + "plugins": ["@angular-eslint/template"], + "rules": { + "@typescript-eslint/no-explicit-any": ["off"] + } + } + ] +} diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..1f0d12187 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,14 @@ +# Artifacts +coverage +dist +build + +# GQL auto generated files +/**/*types.ts + +# Config file in JS +/**/*index.js +/**/*index.ts +wallaby.js +decorate-angular-cli.js + diff --git a/.prettierrc b/.prettierrc index 544138be4..f8dec5b18 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,3 +1,5 @@ { + "tabWidth": 2, + "semi": false, "singleQuote": true } diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..7804e2602 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "nrwl.angular-console", + "angular.ng-template", + "ms-vscode.vscode-typescript-tslint-plugin", + "esbenp.prettier-vscode" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..fa52a7397 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,13 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "chrome", + "request": "launch", + "name": "Launch Chrome against localhost", + "url": "http://localhost:4200", + "webRoot": "${workspaceFolder}", + "runtimeExecutable": "/usr/bin/google-chrome-stable" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..546876fe9 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,27 @@ +{ + "editor.tabSize": 2, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true + } + }, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true + } + }, + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true + } + }, + "[html]": { + "editor.defaultFormatter": "vscode.html-language-features" + }, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true + } +} diff --git a/package-lock.json b/package-lock.json index ea8ef0112..339bf22ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { - "name": "ng2-pdf-viewer", - "version": "6.3.2", + "name": "pdf-viewer", + "version": "1.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -13557,9 +13557,9 @@ } }, "typescript": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.5.tgz", - "integrity": "sha512-BEjlc0Z06ORZKbtcxGrIvvwYs5hAnuo6TKdNFL55frVDlB+na3z5bsLhFaIxmT+dPWgBIjMo6aNnTOgHHmHgiQ==", + "version": "3.7.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz", + "integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==", "dev": true }, "ua-parser-js": { diff --git a/package.json b/package.json index 643148acb..6bd042a44 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "ng2-pdf-viewer", - "version": "6.3.2", + "name": "pdf-viewer", + "version": "1.0.0", "description": "Angular 5+ component for rendering PDF", "license": "MIT", "scripts": { @@ -72,7 +72,7 @@ "rxjs": "~6.5.3", "ts-node": "~8.6.2", "tslint": "~5.20.0", - "typescript": "~3.6.5", + "typescript": "^3.7.5", "webpack-bundle-analyzer": "^3.7.0", "zone.js": "~0.10.2" }, diff --git a/src/app/app.component.html b/src/app/app.component.html index 4fb6a87a8..e1f57c7e3 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -183,8 +183,8 @@ - diff --git a/src/app/app.module.ts b/src/app/app.module.ts index ae4a6c3b6..cdd5ee3d7 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,11 +1,10 @@ -import { BrowserModule } from '@angular/platform-browser'; -import { NgModule } from '@angular/core'; -import { FormsModule } from '@angular/forms'; -import { NoopAnimationsModule } from '@angular/platform-browser/animations'; - -import { DemoMaterialModule } from './material.module'; -import { PdfViewerModule } from './pdf-viewer/pdf-viewer.module'; -import { AppComponent } from './app.component'; +import { NgModule } from '@angular/core' +import { FormsModule } from '@angular/forms' +import { BrowserModule } from '@angular/platform-browser' +import { NoopAnimationsModule } from '@angular/platform-browser/animations' +import { AppComponent } from './app.component' +import { DemoMaterialModule } from './material.module' +import { PdfViewerModule } from './pdf-viewer/pdf-viewer.module' @NgModule({ declarations: [AppComponent], @@ -14,10 +13,9 @@ import { AppComponent } from './app.component'; FormsModule, NoopAnimationsModule, DemoMaterialModule, - - PdfViewerModule + PdfViewerModule, ], providers: [], - bootstrap: [AppComponent] + bootstrap: [AppComponent], }) export class AppModule {} diff --git a/src/app/pdf-viewer/pager/pager.component.html b/src/app/pdf-viewer/pager/pager.component.html new file mode 100644 index 000000000..3b40a16cc --- /dev/null +++ b/src/app/pdf-viewer/pager/pager.component.html @@ -0,0 +1,20 @@ +
+
Page {{ page }} / {{ numPages }}
+
+ + add + + + remove + +
+
+ + zoom_in + + {{ zoom | percent }} + + zoom_out + +
+
diff --git a/src/app/pdf-viewer/pager/pager.component.scss b/src/app/pdf-viewer/pager/pager.component.scss new file mode 100644 index 000000000..ab3a614d1 --- /dev/null +++ b/src/app/pdf-viewer/pager/pager.component.scss @@ -0,0 +1,51 @@ +.pager { + position: absolute; + bottom: 60px; + left: 50%; + color: white; + display: flex; + font-size: 12px; + height: 36px; + + .page { + display: flex; + align-items: center; + background: rgba(0, 0, 0, 0.7); + + border-right: 1px solid white; + padding: 8px; + } + + .operators { + font-size: 12px; + display: flex; + align-items: center; + border-right: 1px solid white; + + .operator { + cursor: pointer; + display: block; + background: rgba(0, 0, 0, 0.7); + display: flex; + align-items: center; + width: 36px; + height: 36px; + justify-content: center; + font-size: 18px; + + &:hover { + background: rgba(0, 0, 0, 0.6); + } + } + + .zoom-value { + font-size: 12px; + background: rgba(0, 0, 0, 0.7); + display: flex; + align-items: center; + width: 36px; + height: 36px; + justify-content: center; + } + } +} diff --git a/src/app/pdf-viewer/pager/pager.component.spec.ts b/src/app/pdf-viewer/pager/pager.component.spec.ts new file mode 100644 index 000000000..85ba45a4f --- /dev/null +++ b/src/app/pdf-viewer/pager/pager.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PagerComponent } from './pager.component'; + +describe('PagerComponent', () => { + let component: PagerComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ PagerComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PagerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pdf-viewer/pager/pager.component.ts b/src/app/pdf-viewer/pager/pager.component.ts new file mode 100644 index 000000000..8ad442881 --- /dev/null +++ b/src/app/pdf-viewer/pager/pager.component.ts @@ -0,0 +1,52 @@ +import { + Component, + EventEmitter, + Input, + OnChanges, + OnInit, + Output, +} from '@angular/core' + +@Component({ + selector: 'app-pager', + templateUrl: './pager.component.html', + styleUrls: ['./pager.component.scss'], +}) +export class PagerComponent implements OnInit { + /** Current page */ + @Input() page: number + + /** Current zoom level */ + @Input() zoom: number + + /** Total pages */ + @Input() numPages: number + + /** Emits when the current page has changed */ + @Output() pageChange: EventEmitter = new EventEmitter() + + /** Emits when the zoom has changed */ + @Output() zoomChange: EventEmitter = new EventEmitter() + + constructor() {} + + ngOnInit(): void {} + + public nextPage(): void { + this.pageChange.emit(this.page + 1) + } + + public previousPage(): void { + this.pageChange.emit(this.page - 1) + } + + public zoomIn(): void { + this.zoom = Math.round((this.zoom + 0.1) * 100) / 100 + this.zoomChange.emit(this.zoom) + } + + public zoomOut(): void { + this.zoom = Math.round((this.zoom - 0.1) * 100) / 100 + this.zoomChange.emit(this.zoom) + } +} diff --git a/src/app/pdf-viewer/pdf-viewer.component.html b/src/app/pdf-viewer/pdf-viewer.component.html new file mode 100644 index 000000000..5c3f14170 --- /dev/null +++ b/src/app/pdf-viewer/pdf-viewer.component.html @@ -0,0 +1,18 @@ +
+
+ +
+
+ +
+ +
{{ annotation.index }}
+
+ +
+ diff --git a/src/app/pdf-viewer/pdf-viewer.component.scss b/src/app/pdf-viewer/pdf-viewer.component.scss index 3a5d9d41e..8782ddbe6 100644 --- a/src/app/pdf-viewer/pdf-viewer.component.scss +++ b/src/app/pdf-viewer/pdf-viewer.component.scss @@ -1,111 +1,216 @@ -.ng2-pdf-viewer-container { - overflow-x: auto; - position: relative; +.pdf-viewer-container { + overflow-y: auto; + overflow-x: hidden; height: 100%; -webkit-overflow-scrolling: touch; -} + padding: 20px; + background: lightgray; + position: relative; + + .pdfViewer { + position: relative; + } -:host ::ng-deep { - .textLayer { + .annotations { position: absolute; - left: 0; + height: 100%; + width: 100%; top: 0; - right: 0; - bottom: 0; - overflow: hidden; - opacity: 0.2; - line-height: 1; - - > span { - color: transparent; + pointer-events: none; + + .annotation-card { + pointer-events: initial; position: absolute; - white-space: pre; - cursor: text; - -webkit-transform-origin: 0% 0%; - transform-origin: 0% 0%; + background: white; + margin-left: -100px; + margin-top: 40px; + box-shadow: 0 2px 5px 0 rgba(39, 44, 108, 0.3); + width: 400px; + padding: 20px; + z-index: 1; + + &:before { + bottom: 100%; + left: 112px; + border: solid transparent; + content: ' '; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + border-color: rgba(136, 183, 213, 0); + border-bottom-color: rgba(39, 44, 108, 0.05); + border-width: 12px; + margin-left: -12px; + } + + &:after { + bottom: 100%; + left: 112px; + border: solid transparent; + content: ' '; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + border-color: rgba(136, 183, 213, 0); + border-bottom-color: white; + border-width: 10px; + margin-left: -10px; + } } - .highlight { - margin: -1px; - padding: 1px; - background-color: rgb(180, 0, 170); - border-radius: 4px; + .annotation { + pointer-events: initial; + position: absolute; + cursor: pointer; + font-weight: bold; + font-size: 14px; + background: black; + color: white; + display: flex; + align-items: center; + justify-content: center; + height: 24px; + width: 24px; + border-radius: 50%; + } + } +} - &.begin { - border-radius: 4px 0px 0px 4px; - } +:host { + height: 100vh; + display: block; + position: relative; + + ::-webkit-scrollbar { + width: 8px; + height: 8px; + } - &.end { - border-radius: 0px 4px 4px 0px; + ::-webkit-scrollbar-thumb { + background: #999; + border-radius: 0; + } + + ::-webkit-scrollbar-track { + background: #eee; + } + + &::ng-deep { + .textLayer { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + overflow: hidden; + opacity: 0.2; + line-height: 1; + + > span { + color: transparent; + position: absolute; + white-space: pre; + cursor: text; + -webkit-transform-origin: 0% 0%; + transform-origin: 0% 0%; } - &.middle { - border-radius: 0px; + .highlight { + margin: -1px; + padding: 1px; + background-color: rgb(180, 0, 170); + border-radius: 4px; + + &.begin { + border-radius: 4px 0px 0px 4px; + } + + &.end { + border-radius: 0px 4px 4px 0px; + } + + &.middle { + border-radius: 0px; + } + + &.selected { + background-color: rgb(0, 100, 0); + } } - &.selected { - background-color: rgb(0, 100, 0); + ::-moz-selection { + background: rgb(0, 0, 255); } - } - ::-moz-selection { - background: rgb(0, 0, 255); - } + ::selection { + background: rgb(0, 0, 255); + } - ::selection { - background: rgb(0, 0, 255); + .endOfContent { + display: block; + position: absolute; + left: 0px; + top: 100%; + right: 0px; + bottom: 0px; + z-index: -1; + cursor: default; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + &.active { + top: 0px; + } + } } - .endOfContent { - display: block; - position: absolute; - left: 0px; - top: 100%; - right: 0px; - bottom: 0px; - z-index: -1; - cursor: default; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - - &.active { - top: 0px; + .annotationLayer { + section { + position: absolute; } - } - } - .annotationLayer { - section { - position: absolute; - } + .linkAnnotation > a, + .buttonWidgetAnnotation.pushButton > a { + position: absolute; + font-size: 1em; + top: 0; + left: 0; + width: 100%; + height: 100%; + } - .linkAnnotation > a, - .buttonWidgetAnnotation.pushButton > a { - position: absolute; - font-size: 1em; - top: 0; - left: 0; - width: 100%; - height: 100%; - } + .linkAnnotation > a:hover, + .buttonWidgetAnnotation.pushButton > a:hover { + opacity: 0.2; + background: #ff0; + box-shadow: 0px 2px 10px #ff0; + } - .linkAnnotation > a:hover, - .buttonWidgetAnnotation.pushButton > a:hover { - opacity: 0.2; - background: #ff0; - box-shadow: 0px 2px 10px #ff0; - } + .textAnnotation img { + position: absolute; + cursor: pointer; + } - .textAnnotation img { - position: absolute; - cursor: pointer; - } + .textWidgetAnnotation { + input, + textarea { + background-color: rgba(0, 54, 255, 0.13); + border: 1px solid transparent; + box-sizing: border-box; + font-size: 9px; + height: 100%; + margin: 0; + padding: 0 3px; + vertical-align: top; + width: 100%; + } + } - .textWidgetAnnotation { - input, - textarea { + .choiceWidgetAnnotation select { background-color: rgba(0, 54, 255, 0.13); border: 1px solid transparent; box-sizing: border-box; @@ -116,396 +221,384 @@ vertical-align: top; width: 100%; } - } - .choiceWidgetAnnotation select { - background-color: rgba(0, 54, 255, 0.13); - border: 1px solid transparent; - box-sizing: border-box; - font-size: 9px; - height: 100%; - margin: 0; - padding: 0 3px; - vertical-align: top; - width: 100%; - } + .buttonWidgetAnnotation { + &.checkBox input, + &.radioButton input { + background-color: rgba(0, 54, 255, 0.13); + border: 1px solid transparent; + box-sizing: border-box; + font-size: 9px; + height: 100%; + margin: 0; + padding: 0 3px; + vertical-align: top; + width: 100%; + } + } - .buttonWidgetAnnotation { - &.checkBox input, - &.radioButton input { - background-color: rgba(0, 54, 255, 0.13); - border: 1px solid transparent; - box-sizing: border-box; - font-size: 9px; - height: 100%; - margin: 0; - padding: 0 3px; - vertical-align: top; - width: 100%; + .choiceWidgetAnnotation select option { + padding: 0; } - } - .choiceWidgetAnnotation select option { - padding: 0; - } + .buttonWidgetAnnotation.radioButton input { + border-radius: 50%; + } - .buttonWidgetAnnotation.radioButton input { - border-radius: 50%; - } + .textWidgetAnnotation { + textarea { + font: message-box; + font-size: 9px; + resize: none; + } - .textWidgetAnnotation { - textarea { - font: message-box; - font-size: 9px; - resize: none; + input[disabled], + textarea[disabled] { + background: none; + border: 1px solid transparent; + cursor: not-allowed; + } } - input[disabled], - textarea[disabled] { + .choiceWidgetAnnotation select[disabled] { background: none; border: 1px solid transparent; cursor: not-allowed; } - } - .choiceWidgetAnnotation select[disabled] { - background: none; - border: 1px solid transparent; - cursor: not-allowed; - } + .buttonWidgetAnnotation { + &.checkBox input[disabled], + &.radioButton input[disabled] { + background: none; + border: 1px solid transparent; + cursor: not-allowed; + } + } - .buttonWidgetAnnotation { - &.checkBox input[disabled], - &.radioButton input[disabled] { - background: none; - border: 1px solid transparent; - cursor: not-allowed; + .textWidgetAnnotation { + input:hover, + textarea:hover { + border: 1px solid #000; + } } - } - .textWidgetAnnotation { - input:hover, - textarea:hover { + .choiceWidgetAnnotation select:hover { border: 1px solid #000; } - } - .choiceWidgetAnnotation select:hover { - border: 1px solid #000; - } + .buttonWidgetAnnotation { + &.checkBox input:hover, + &.radioButton input:hover { + border: 1px solid #000; + } + } - .buttonWidgetAnnotation { - &.checkBox input:hover, - &.radioButton input:hover { - border: 1px solid #000; + .textWidgetAnnotation { + input:focus, + textarea:focus { + background: none; + border: 1px solid transparent; + } } - } - .textWidgetAnnotation { - input:focus, - textarea:focus { + .choiceWidgetAnnotation select:focus { background: none; border: 1px solid transparent; } - } - .choiceWidgetAnnotation select:focus { - background: none; - border: 1px solid transparent; - } + .buttonWidgetAnnotation { + &.checkBox input:checked { + &:before, + &:after { + background-color: #000; + content: ''; + display: block; + position: absolute; + } + } - .buttonWidgetAnnotation { - &.checkBox input:checked { - &:before, - &:after { + &.radioButton input:checked:before { background-color: #000; content: ''; display: block; position: absolute; } - } - - &.radioButton input:checked:before { - background-color: #000; - content: ''; - display: block; - position: absolute; - } - &.checkBox input:checked { - &:before, - &:after { - height: 80%; - left: 45%; - width: 1px; + &.checkBox input:checked { + &:before, + &:after { + height: 80%; + left: 45%; + width: 1px; + } + + &:before { + -webkit-transform: rotate(45deg); + transform: rotate(45deg); + } + + &:after { + -webkit-transform: rotate(-45deg); + transform: rotate(-45deg); + } } - &:before { - -webkit-transform: rotate(45deg); - transform: rotate(45deg); - } - - &:after { - -webkit-transform: rotate(-45deg); - transform: rotate(-45deg); + &.radioButton input:checked:before { + border-radius: 50%; + height: 50%; + left: 30%; + top: 20%; + width: 50%; } } - &.radioButton input:checked:before { - border-radius: 50%; - height: 50%; - left: 30%; - top: 20%; - width: 50%; - } - } - - .textWidgetAnnotation input.comb { - font-family: monospace; - padding-left: 2px; - padding-right: 0; + .textWidgetAnnotation input.comb { + font-family: monospace; + padding-left: 2px; + padding-right: 0; - &:focus { - /* + &:focus { + /* * Letter spacing is placed on the right side of each character. Hence, the * letter spacing of the last character may be placed outside the visible * area, causing horizontal scrolling. We avoid this by extending the width * when the element has focus and revert this when it loses focus. */ - width: 115%; + width: 115%; + } } - } - .buttonWidgetAnnotation { - &.checkBox input, - &.radioButton input { - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - padding: 0; + .buttonWidgetAnnotation { + &.checkBox input, + &.radioButton input { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + padding: 0; + } } - } - - .popupWrapper { - position: absolute; - width: 20em; - } - .popup { - position: absolute; - z-index: 200; - max-width: 20em; - background-color: #ffff99; - box-shadow: 0px 2px 5px #888; - border-radius: 2px; - padding: 6px; - margin-left: 5px; - cursor: pointer; - font: message-box; - font-size: 9px; - word-wrap: break-word; + .popupWrapper { + position: absolute; + width: 20em; + } - > * { + .popup { + position: absolute; + z-index: 200; + max-width: 20em; + background-color: #ffff99; + box-shadow: 0px 2px 5px #888; + border-radius: 2px; + padding: 6px; + margin-left: 5px; + cursor: pointer; + font: message-box; font-size: 9px; - } + word-wrap: break-word; - h1 { - display: inline-block; - // font-size: 1em; - // border-bottom: 1px solid #000000; - // margin: 0; - // padding-bottom: 0.2em; - } + > * { + font-size: 9px; + } - span { - display: inline-block; - margin-left: 5px; + h1 { + display: inline-block; + // font-size: 1em; + // border-bottom: 1px solid #000000; + // margin: 0; + // padding-bottom: 0.2em; + } + + span { + display: inline-block; + margin-left: 5px; + } + + p { + border-top: 1px solid #333; + margin-top: 2px; + padding-top: 2px; + } } - p { - border-top: 1px solid #333; - margin-top: 2px; - padding-top: 2px; + .highlightAnnotation, + .underlineAnnotation, + .squigglyAnnotation, + .strikeoutAnnotation, + .freeTextAnnotation, + .lineAnnotation svg line, + .squareAnnotation svg rect, + .circleAnnotation svg ellipse, + .polylineAnnotation svg polyline, + .polygonAnnotation svg polygon, + .caretAnnotation, + .inkAnnotation svg polyline, + .stampAnnotation, + .fileAttachmentAnnotation { + cursor: pointer; } } - .highlightAnnotation, - .underlineAnnotation, - .squigglyAnnotation, - .strikeoutAnnotation, - .freeTextAnnotation, - .lineAnnotation svg line, - .squareAnnotation svg rect, - .circleAnnotation svg ellipse, - .polylineAnnotation svg polyline, - .polygonAnnotation svg polygon, - .caretAnnotation, - .inkAnnotation svg polyline, - .stampAnnotation, - .fileAttachmentAnnotation { - cursor: pointer; - } - } + .pdfViewer { + .canvasWrapper { + overflow: hidden; + } - .pdfViewer { - .canvasWrapper { - overflow: hidden; - } + .page { + direction: ltr; + width: 816px; + height: 1056px; + margin: 1px auto -8px auto; + position: relative; + overflow: visible; + // https://stackoverflow.com/questions/21025212/border-image-not-showing-in-safari + // "transparent" makes the border not show up in Safari. But adding a light transparent color did + // rgba(0,0,0,0.01). But there needs to be an alpha value larger than 0. + border: 9px solid rgba(0, 0, 0, 0.01); + box-sizing: initial; + background-clip: content-box; + -webkit-border-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAA6UlEQVR4Xl2Pi2rEMAwE16fm1f7/r14v7w4rI0IzLAF7hLxNevBSEMEF5+OilNCsRd8ZMyn+a4NmsOT8WJw1lFbSYgGFzF2bLFoLjTClWjKKGRWpDYAGXUnZ4uhbBUzF3Oe/GG/ue2fn4GgsyXhNgysV2JnrhKEMg4fEZcALmiKbNhBBRFpSyDOj1G4QOVly6O1FV54ZZq8OVygrciDt6JazRgi1ljTPH0gbrPmHPXAbCiDd4GawIjip1TPh9tt2sz24qaCjr/jAb/GBFTbq9KZ7Ke/Cqt8nayUikZKsWZK7Fe6bg5dOUt8fZHWG2BHc+6EAAAAASUVORK5CYII=') + 9 9 repeat; + -o-border-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAA6UlEQVR4Xl2Pi2rEMAwE16fm1f7/r14v7w4rI0IzLAF7hLxNevBSEMEF5+OilNCsRd8ZMyn+a4NmsOT8WJw1lFbSYgGFzF2bLFoLjTClWjKKGRWpDYAGXUnZ4uhbBUzF3Oe/GG/ue2fn4GgsyXhNgysV2JnrhKEMg4fEZcALmiKbNhBBRFpSyDOj1G4QOVly6O1FV54ZZq8OVygrciDt6JazRgi1ljTPH0gbrPmHPXAbCiDd4GawIjip1TPh9tt2sz24qaCjr/jAb/GBFTbq9KZ7Ke/Cqt8nayUikZKsWZK7Fe6bg5dOUt8fZHWG2BHc+6EAAAAASUVORK5CYII=') + 9 9 repeat; + border-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAA6UlEQVR4Xl2Pi2rEMAwE16fm1f7/r14v7w4rI0IzLAF7hLxNevBSEMEF5+OilNCsRd8ZMyn+a4NmsOT8WJw1lFbSYgGFzF2bLFoLjTClWjKKGRWpDYAGXUnZ4uhbBUzF3Oe/GG/ue2fn4GgsyXhNgysV2JnrhKEMg4fEZcALmiKbNhBBRFpSyDOj1G4QOVly6O1FV54ZZq8OVygrciDt6JazRgi1ljTPH0gbrPmHPXAbCiDd4GawIjip1TPh9tt2sz24qaCjr/jAb/GBFTbq9KZ7Ke/Cqt8nayUikZKsWZK7Fe6bg5dOUt8fZHWG2BHc+6EAAAAASUVORK5CYII=') + 9 9 repeat; + background-color: white; + } - .page { - direction: ltr; - width: 816px; - height: 1056px; - margin: 1px auto -8px auto; - position: relative; - overflow: visible; - // https://stackoverflow.com/questions/21025212/border-image-not-showing-in-safari - // "transparent" makes the border not show up in Safari. But adding a light transparent color did - // rgba(0,0,0,0.01). But there needs to be an alpha value larger than 0. - border: 9px solid rgba(0, 0, 0, 0.01); - box-sizing: initial; - background-clip: content-box; - -webkit-border-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAA6UlEQVR4Xl2Pi2rEMAwE16fm1f7/r14v7w4rI0IzLAF7hLxNevBSEMEF5+OilNCsRd8ZMyn+a4NmsOT8WJw1lFbSYgGFzF2bLFoLjTClWjKKGRWpDYAGXUnZ4uhbBUzF3Oe/GG/ue2fn4GgsyXhNgysV2JnrhKEMg4fEZcALmiKbNhBBRFpSyDOj1G4QOVly6O1FV54ZZq8OVygrciDt6JazRgi1ljTPH0gbrPmHPXAbCiDd4GawIjip1TPh9tt2sz24qaCjr/jAb/GBFTbq9KZ7Ke/Cqt8nayUikZKsWZK7Fe6bg5dOUt8fZHWG2BHc+6EAAAAASUVORK5CYII=') - 9 9 repeat; - -o-border-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAA6UlEQVR4Xl2Pi2rEMAwE16fm1f7/r14v7w4rI0IzLAF7hLxNevBSEMEF5+OilNCsRd8ZMyn+a4NmsOT8WJw1lFbSYgGFzF2bLFoLjTClWjKKGRWpDYAGXUnZ4uhbBUzF3Oe/GG/ue2fn4GgsyXhNgysV2JnrhKEMg4fEZcALmiKbNhBBRFpSyDOj1G4QOVly6O1FV54ZZq8OVygrciDt6JazRgi1ljTPH0gbrPmHPXAbCiDd4GawIjip1TPh9tt2sz24qaCjr/jAb/GBFTbq9KZ7Ke/Cqt8nayUikZKsWZK7Fe6bg5dOUt8fZHWG2BHc+6EAAAAASUVORK5CYII=') - 9 9 repeat; - border-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAA6UlEQVR4Xl2Pi2rEMAwE16fm1f7/r14v7w4rI0IzLAF7hLxNevBSEMEF5+OilNCsRd8ZMyn+a4NmsOT8WJw1lFbSYgGFzF2bLFoLjTClWjKKGRWpDYAGXUnZ4uhbBUzF3Oe/GG/ue2fn4GgsyXhNgysV2JnrhKEMg4fEZcALmiKbNhBBRFpSyDOj1G4QOVly6O1FV54ZZq8OVygrciDt6JazRgi1ljTPH0gbrPmHPXAbCiDd4GawIjip1TPh9tt2sz24qaCjr/jAb/GBFTbq9KZ7Ke/Cqt8nayUikZKsWZK7Fe6bg5dOUt8fZHWG2BHc+6EAAAAASUVORK5CYII=') - 9 9 repeat; - background-color: white; - } + &.removePageBorders .page { + margin: 0px auto 10px auto; + border: none; + } - &.removePageBorders .page { - margin: 0px auto 10px auto; - border: none; - } + padding-bottom: 10px; + &.removePageBorders { + padding-bottom: 0; + } - padding-bottom: 10px; - &.removePageBorders { - padding-bottom: 0; - } + &.singlePageView { + display: inline-block; - &.singlePageView { - display: inline-block; + .page { + margin: 0; + border: none; + } + } - .page { - margin: 0; - border: none; + &.scrollHorizontal, + &.scrollWrapped { + margin-left: 3.5px; + margin-right: 3.5px; + text-align: center; } } - &.scrollHorizontal, - &.scrollWrapped { + .spread { margin-left: 3.5px; margin-right: 3.5px; text-align: center; } - } - .spread { - margin-left: 3.5px; - margin-right: 3.5px; - text-align: center; - } - - .pdfViewer.scrollHorizontal, - .spread { - white-space: nowrap; - } - - .pdfViewer { - &.removePageBorders, - &.scrollHorizontal .spread, - &.scrollWrapped .spread { - margin-left: 0; - margin-right: 0; + .pdfViewer.scrollHorizontal, + .spread { + white-space: nowrap; } - } - .spread .page { - display: inline-block; - vertical-align: middle; - } + .pdfViewer { + &.removePageBorders, + &.scrollHorizontal .spread, + &.scrollWrapped .spread { + margin-left: 0; + margin-right: 0; + } + } - .pdfViewer { - &.scrollHorizontal .page, - &.scrollWrapped .page, - &.scrollHorizontal .spread, - &.scrollWrapped .spread { + .spread .page { display: inline-block; vertical-align: middle; } - } - .spread .page { - margin-left: -3.5px; - margin-right: -3.5px; - } + .pdfViewer { + &.scrollHorizontal .page, + &.scrollWrapped .page, + &.scrollHorizontal .spread, + &.scrollWrapped .spread { + display: inline-block; + vertical-align: middle; + } + } - .pdfViewer { - &.scrollHorizontal .page, - &.scrollWrapped .page { + .spread .page { margin-left: -3.5px; margin-right: -3.5px; } - &.removePageBorders { - .spread .page, + .pdfViewer { &.scrollHorizontal .page, &.scrollWrapped .page { - margin-left: 5px; - margin-right: 5px; + margin-left: -3.5px; + margin-right: -3.5px; } - } - - .page { - canvas { - margin: 0; - display: block; - &[hidden] { - display: none; + &.removePageBorders { + .spread .page, + &.scrollHorizontal .page, + &.scrollWrapped .page { + margin-left: 5px; + margin-right: 5px; } } - .loadingIcon { - position: absolute; - display: block; - left: 0; - top: 0; - right: 0; - bottom: 0; - background: url('data:image/gif;base64,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') - center no-repeat; + .page { + canvas { + margin: 0; + display: block; + + &[hidden] { + display: none; + } + } + + .loadingIcon { + position: absolute; + display: block; + left: 0; + top: 0; + right: 0; + bottom: 0; + background: url('data:image/gif;base64,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') + center no-repeat; + } } } - } - .pdfPresentationMode { - .pdfViewer { - margin-left: 0; - margin-right: 0; + .pdfPresentationMode { + .pdfViewer { + margin-left: 0; + margin-right: 0; - .page, - .spread { - display: block; - } + .page, + .spread { + display: block; + } - .page, - &.removePageBorders .page { - margin-left: auto; - margin-right: auto; + .page, + &.removePageBorders .page { + margin-left: auto; + margin-right: auto; + } } - } - &:-ms-fullscreen .pdfViewer .page { - margin-bottom: 100% !important; - } + &:-ms-fullscreen .pdfViewer .page { + margin-bottom: 100% !important; + } - &:-webkit-full-screen .pdfViewer .page, - &:-moz-full-screen .pdfViewer .page, - &:fullscreen .pdfViewer .page { - margin-bottom: 100%; - border: 0; + &:-webkit-full-screen .pdfViewer .page, + &:-moz-full-screen .pdfViewer .page, + &:fullscreen .pdfViewer .page { + margin-bottom: 100%; + border: 0; + } } } } diff --git a/src/app/pdf-viewer/pdf-viewer.component.ts b/src/app/pdf-viewer/pdf-viewer.component.ts index 40560270c..e0d6a2670 100644 --- a/src/app/pdf-viewer/pdf-viewer.component.ts +++ b/src/app/pdf-viewer/pdf-viewer.component.ts @@ -1,367 +1,431 @@ +import { TemplatePortal } from '@angular/cdk/portal' /** * Created by vadimdez on 21/06/16. */ import { + AfterViewChecked, + AfterViewInit, + ChangeDetectorRef, Component, - Input, - Output, ElementRef, EventEmitter, - OnChanges, - SimpleChanges, - OnInit, HostListener, + Input, + NgZone, + OnChanges, OnDestroy, + OnInit, + Output, + SimpleChanges, + TemplateRef, ViewChild, - AfterViewChecked -} from '@angular/core'; +} from '@angular/core' import { PDFDocumentProxy, - PDFViewerParams, PDFPageProxy, - PDFSource, PDFProgressData, - PDFPromise -} from 'pdfjs-dist'; - -import { createEventBus } from '../utils/event-bus-utils'; + PDFPromise, + PDFSource, + PDFViewerParams, +} from 'pdfjs-dist' +import { createEventBus } from '../utils/event-bus-utils' +import { getPosition } from '../utils/get-position' -let PDFJS: any; -let PDFJSViewer: any; +let PDFJS: any +let PDFJSViewer: any function isSSR() { - return typeof window === 'undefined'; + return typeof window === 'undefined' } if (!isSSR()) { - PDFJS = require('pdfjs-dist/build/pdf'); - PDFJSViewer = require('pdfjs-dist/web/pdf_viewer'); + PDFJS = require('pdfjs-dist/build/pdf') + PDFJSViewer = require('pdfjs-dist/web/pdf_viewer') - PDFJS.verbosity = PDFJS.VerbosityLevel.ERRORS; + PDFJS.verbosity = PDFJS.VerbosityLevel.ERRORS } export enum RenderTextMode { DISABLED, ENABLED, - ENHANCED + ENHANCED, +} + +export interface Annotation { + id: string + index: number + positionX: number + positionY: number +} + +export interface Coordinates { + positionX: number + positionY: number +} + +export interface TextSelection { + positionX: number + positionY: number + text: string } @Component({ selector: 'pdf-viewer', - template: ` -
-
-
- `, - styleUrls: ['./pdf-viewer.component.scss'] + templateUrl: './pdf-viewer.component.html', + styleUrls: ['./pdf-viewer.component.scss'], }) export class PdfViewerComponent - implements OnChanges, OnInit, OnDestroy, AfterViewChecked { - @ViewChild('pdfViewerContainer') pdfViewerContainer; - private isVisible: boolean = false; + implements OnChanges, OnInit, OnDestroy, AfterViewChecked, AfterViewInit { + @ViewChild('pdfViewerContainer') pdfViewerContainer: any + private isVisible: boolean = false + + @ViewChild('annotationCardTemplate', { static: false }) + annotationCardTemplate: TemplateRef - static CSS_UNITS: number = 96.0 / 72.0; - static BORDER_WIDTH: number = 9; + static CSS_UNITS: number = 96.0 / 72.0 + static BORDER_WIDTH: number = 9 - private pdfMultiPageViewer: any; - private pdfMultiPageLinkService: any; - private pdfMultiPageFindController: any; + private pdfMultiPageViewer: any + private pdfMultiPageLinkService: any + private pdfMultiPageFindController: any - private pdfSinglePageViewer: any; - private pdfSinglePageLinkService: any; - private pdfSinglePageFindController: any; + private pdfSinglePageViewer: any + private pdfSinglePageLinkService: any + private pdfSinglePageFindController: any private _cMapsUrl = typeof PDFJS !== 'undefined' ? `https://unpkg.com/pdfjs-dist@${(PDFJS as any).version}/cmaps/` - : null; - private _renderText = true; - private _renderTextMode: RenderTextMode = RenderTextMode.ENABLED; - private _stickToPage = false; - private _originalSize = true; - private _pdf: PDFDocumentProxy; - private _page = 1; - private _zoom = 1; - private _zoomScale: 'page-height'|'page-fit'|'page-width' = 'page-width'; - private _rotation = 0; - private _showAll = true; - private _canAutoResize = true; - private _fitToPage = false; - private _externalLinkTarget = 'blank'; - private _showBorders = false; - private lastLoaded: string | Uint8Array | PDFSource; - private _latestScrolledPage: number; - - private resizeTimeout: NodeJS.Timer; - private pageScrollTimeout: NodeJS.Timer; - private isInitialized = false; - private loadingTask: any; - - @Output('after-load-complete') afterLoadComplete = new EventEmitter< - PDFDocumentProxy - >(); - @Output('page-rendered') pageRendered = new EventEmitter(); - @Output('text-layer-rendered') textLayerRendered = new EventEmitter< - CustomEvent - >(); - @Output('error') onError = new EventEmitter(); - @Output('on-progress') onProgress = new EventEmitter(); - @Output() pageChange: EventEmitter = new EventEmitter(true); + : null + private _renderText = true + private _renderTextMode: RenderTextMode = RenderTextMode.ENABLED + private _stickToPage = true + private _originalSize = true + private _pdf: PDFDocumentProxy | null + private _page = 1 + private _zoom = 1 + private _zoomScale: 'page-height' | 'page-fit' | 'page-width' = 'page-width' + private _rotation = 0 + private _showAll = true + private _canAutoResize = true + private _fitToPage = false + private _externalLinkTarget = 'blank' + private _showBorders = false + private lastLoaded: string | Uint8Array | PDFSource + private _latestScrolledPage: number + + private resizeTimeout: NodeJS.Timer + private pageScrollTimeout: NodeJS.Timer + private isInitialized = false + private loadingTask: any + + public annotationCardCoordinates: Coordinates | null + public portal: TemplatePortal + + @Output() afterLoadComplete = new EventEmitter() + @Output() pageRendered = new EventEmitter() + @Output() textLayerRendered = new EventEmitter() + @Output() errored = new EventEmitter() + @Output() progressChange = new EventEmitter() + @Output() pageChange: EventEmitter = new EventEmitter(true) + @Output() annotationCardOpened: EventEmitter< + TextSelection + > = new EventEmitter() + @Output() annotationCardClosed: EventEmitter = new EventEmitter() + + /** PDF source */ @Input() - src: string | Uint8Array | PDFSource; + src: string | Uint8Array | PDFSource - @Input('c-maps-url') - set cMapsUrl(cMapsUrl: string) { - this._cMapsUrl = cMapsUrl; - } - - @Input('page') - set page(_page) { - _page = parseInt(_page, 10) || 1; - const orginalPage = _page; + /** List of annotations */ + @Input() + annotations: Annotation[] - if (this._pdf) { - _page = this.getValidPageNumber(_page); - } + /** Currently active annotation */ + @Input() + activeAnnotation: Annotation - this._page = _page; - if (orginalPage !== _page) { - this.pageChange.emit(_page); - } + @Input('c-maps-url') + set cMapsUrl(cMapsUrl: string) { + this._cMapsUrl = cMapsUrl } @Input('render-text') set renderText(renderText: boolean) { - this._renderText = renderText; + this._renderText = renderText } @Input('render-text-mode') set renderTextMode(renderTextMode: RenderTextMode) { - this._renderTextMode = renderTextMode; + this._renderTextMode = renderTextMode } @Input('original-size') set originalSize(originalSize: boolean) { - this._originalSize = originalSize; + this._originalSize = originalSize } @Input('show-all') set showAll(value: boolean) { - this._showAll = value; + this._showAll = value } @Input('stick-to-page') set stickToPage(value: boolean) { - this._stickToPage = value; + this._stickToPage = value } @Input('zoom') set zoom(value: number) { if (value <= 0) { - return; + return } - this._zoom = value; + this._zoom = value } get zoom() { - return this._zoom; + return this._zoom } @Input('zoom-scale') - set zoomScale(value: 'page-height'|'page-fit' | 'page-width') { - this._zoomScale = value; + set zoomScale(value: 'page-height' | 'page-fit' | 'page-width') { + this._zoomScale = value } get zoomScale() { - return this._zoomScale; + return this._zoomScale } @Input('rotation') set rotation(value: number) { if (!(typeof value === 'number' && value % 90 === 0)) { - console.warn('Invalid pages rotation angle.'); - return; + console.warn('Invalid pages rotation angle.') + return } - this._rotation = value; + this._rotation = value } @Input('external-link-target') set externalLinkTarget(value: string) { - this._externalLinkTarget = value; + this._externalLinkTarget = value } @Input('autoresize') set autoresize(value: boolean) { - this._canAutoResize = Boolean(value); + this._canAutoResize = Boolean(value) } @Input('fit-to-page') set fitToPage(value: boolean) { - this._fitToPage = Boolean(value); + this._fitToPage = Boolean(value) } @Input('show-borders') set showBorders(value: boolean) { - this._showBorders = Boolean(value); + this._showBorders = Boolean(value) + } + + get page() { + return this._page + } + + set page(_page: any) { + _page = parseInt(_page, 10) || 1 + const orginalPage = _page + + if (this._pdf) { + _page = this.getValidPageNumber(_page) + } + + this._page = _page + if (orginalPage !== _page) { + this.pageChange.emit(_page) + } + } + + get numPages() { + return this._pdf?.numPages } static getLinkTarget(type: string) { switch (type) { case 'blank': - return (PDFJS).LinkTarget.BLANK; + return (PDFJS).LinkTarget.BLANK case 'none': - return (PDFJS).LinkTarget.NONE; + return (PDFJS).LinkTarget.NONE case 'self': - return (PDFJS).LinkTarget.SELF; + return (PDFJS).LinkTarget.SELF case 'parent': - return (PDFJS).LinkTarget.PARENT; + return (PDFJS).LinkTarget.PARENT case 'top': - return (PDFJS).LinkTarget.TOP; + return (PDFJS).LinkTarget.TOP } - return null; + return null } static setExternalLinkTarget(type: string) { - const linkTarget = PdfViewerComponent.getLinkTarget(type); + const linkTarget = PdfViewerComponent.getLinkTarget(type) if (linkTarget !== null) { - (PDFJS).externalLinkTarget = linkTarget; + ;(PDFJS).externalLinkTarget = linkTarget } } - constructor(private element: ElementRef) { + constructor( + private element: ElementRef, + private cd: ChangeDetectorRef, + private ngZone: NgZone + ) { if (isSSR()) { - return; + return } - let pdfWorkerSrc: string; + let pdfWorkerSrc: string if ( window.hasOwnProperty('pdfWorkerSrc') && typeof (window as any).pdfWorkerSrc === 'string' && (window as any).pdfWorkerSrc ) { - pdfWorkerSrc = (window as any).pdfWorkerSrc; + pdfWorkerSrc = (window as any).pdfWorkerSrc } else { pdfWorkerSrc = `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${ (PDFJS as any).version - }/pdf.worker.min.js`; + }/pdf.worker.min.js` } - (PDFJS as any).GlobalWorkerOptions.workerSrc = pdfWorkerSrc; + ;(PDFJS as any).GlobalWorkerOptions.workerSrc = pdfWorkerSrc } ngAfterViewChecked(): void { if (this.isInitialized) { - return; + return } - const offset = this.pdfViewerContainer.nativeElement.offsetParent; + const offset = this.pdfViewerContainer.nativeElement.offsetParent if (this.isVisible === true && offset == null) { - this.isVisible = false; - return; + this.isVisible = false + return } if (this.isVisible === false && offset != null) { - this.isVisible = true; + this.isVisible = true setTimeout(() => { - this.ngOnInit(); - this.ngOnChanges({ src: this.src } as any); - }); + this.ngOnInit() + this.ngOnChanges({ src: this.src } as any) + }) } } ngOnInit() { if (!isSSR() && this.isVisible) { - this.isInitialized = true; - this.setupMultiPageViewer(); - this.setupSinglePageViewer(); + this.isInitialized = true + this.setupMultiPageViewer() + this.setupSinglePageViewer() + } + } + + ngAfterViewInit(): void { + this.ngZone.runOutsideAngular(() => this.handleTextSelection()) + } + + ngOnChanges(changes: SimpleChanges) { + if (isSSR() || !this.isVisible) { + return + } + + if ('src' in changes) { + this.loadPDF() + } else if (this._pdf) { + if ('renderText' in changes) { + this.getCurrentViewer().textLayerMode = this._renderText + ? this._renderTextMode + : RenderTextMode.DISABLED + this.resetPdfDocument() + } else if ('showAll' in changes) { + this.resetPdfDocument() + } else if ( + 'activeAnnotation' in changes && + changes.activeAnnotation.currentValue === undefined + ) { + window?.getSelection()?.removeAllRanges() + } + + this.update() } } ngOnDestroy() { - this.clear(); + this.clear() } @HostListener('window:resize', []) public onPageResize() { if (!this._canAutoResize || !this._pdf) { - return; + return } if (this.resizeTimeout) { - clearTimeout(this.resizeTimeout); + clearTimeout(this.resizeTimeout) } this.resizeTimeout = setTimeout(() => { - this.updateSize(); - }, 100); + this.updateSize() + }, 100) } get pdfLinkService(): any { return this._showAll ? this.pdfMultiPageLinkService - : this.pdfSinglePageLinkService; + : this.pdfSinglePageLinkService } get pdfViewer(): any { - return this.getCurrentViewer(); + return this.getCurrentViewer() } get pdfFindController(): any { return this._showAll ? this.pdfMultiPageFindController - : this.pdfSinglePageFindController; + : this.pdfSinglePageFindController } - ngOnChanges(changes: SimpleChanges) { - if (isSSR() || !this.isVisible) { - return; - } - - if ('src' in changes) { - this.loadPDF(); - } else if (this._pdf) { - if ('renderText' in changes) { - this.getCurrentViewer().textLayerMode = this._renderText - ? this._renderTextMode - : RenderTextMode.DISABLED; - this.resetPdfDocument(); - } else if ('showAll' in changes) { - this.resetPdfDocument(); - } - if ('page' in changes) { - if (changes['page'].currentValue === this._latestScrolledPage) { - return; - } + public pageChanged(page: number): void { + this.page = page - // New form of page changing: The viewer will now jump to the specified page when it is changed. - // This behavior is introducedby using the PDFSinglePageViewer - this.getCurrentViewer().scrollPageIntoView({ pageNumber: this._page }); - } + const currentPage: HTMLElement = document.querySelector( + '[data-page-number="' + this._page + '"]' + ) as HTMLElement + currentPage.scrollIntoView() + this.pdfViewer.scrollPageIntoView({ pageNumber: this._page }) + } + public zoomChanged(zoom: number): void { + this.zoom = zoom + this.updateSize() + } - this.update(); - } + public openAnnotationForm(textSelection: TextSelection): void { + this.annotationCardOpened.emit(textSelection) } public updateSize() { - const currentViewer = this.getCurrentViewer(); - this._pdf + const currentViewer = this.getCurrentViewer() + ;(this._pdf as PDFDocumentProxy) .getPage(currentViewer.currentPageNumber) .then((page: PDFPageProxy) => { - const rotation = this._rotation || page.rotate; + const rotation = this._rotation || page.rotate const viewportWidth = (page as any).getViewport({ scale: this._zoom, - rotation - }).width * PdfViewerComponent.CSS_UNITS; - let scale = this._zoom; - let stickToPage = true; + rotation, + }).width * PdfViewerComponent.CSS_UNITS + let scale = this._zoom + let stickToPage = true // Scale the document when it shouldn't be in original size or doesn't fit into the viewport if ( @@ -369,65 +433,68 @@ export class PdfViewerComponent (this._fitToPage && viewportWidth > this.pdfViewerContainer.nativeElement.clientWidth) ) { - const viewPort = (page as any).getViewport({ scale: 1, rotation }); - scale = this.getScale(viewPort.width, viewPort.height); - stickToPage = !this._stickToPage; + const viewPort = (page as any).getViewport({ scale: 1, rotation }) + scale = this.getScale(viewPort.width, viewPort.height) + stickToPage = !this._stickToPage } - currentViewer._setScale(scale, stickToPage); - }); + currentViewer._setScale(scale, stickToPage) + }) } public clear() { if (this.loadingTask && !this.loadingTask.destroyed) { - this.loadingTask.destroy(); + this.loadingTask.destroy() } if (this._pdf) { - this._pdf.destroy(); - this._pdf = null; - this.pdfMultiPageViewer.setDocument(null); - this.pdfSinglePageViewer.setDocument(null); + this._pdf.destroy() + this._pdf = null + this.pdfMultiPageViewer.setDocument(null) + this.pdfSinglePageViewer.setDocument(null) - this.pdfMultiPageLinkService.setDocument(null, null); - this.pdfSinglePageLinkService.setDocument(null, null); + this.pdfMultiPageLinkService.setDocument(null, null) + this.pdfSinglePageLinkService.setDocument(null, null) - this.pdfMultiPageFindController.setDocument(null); - this.pdfSinglePageFindController.setDocument(null); + this.pdfMultiPageFindController.setDocument(null) + this.pdfSinglePageFindController.setDocument(null) } } private setupMultiPageViewer() { - (PDFJS as any).disableTextLayer = !this._renderText; + ;(PDFJS as any).disableTextLayer = !this._renderText - PdfViewerComponent.setExternalLinkTarget(this._externalLinkTarget); + PdfViewerComponent.setExternalLinkTarget(this._externalLinkTarget) - const eventBus = createEventBus(PDFJSViewer); + const eventBus = createEventBus(PDFJSViewer) - eventBus.on('pagerendered', e => { - this.pageRendered.emit(e); - }); + eventBus.on('pagerendered', (e: any) => { + this.pageRendered.emit(e) + }) - eventBus.on('pagechanging', e => { + eventBus.on('pagechanging', (e: any) => { if (this.pageScrollTimeout) { - clearTimeout(this.pageScrollTimeout); + clearTimeout(this.pageScrollTimeout) } + this.page = e.pageNumber + this.cd.markForCheck() + this.pageScrollTimeout = setTimeout(() => { - this._latestScrolledPage = e.pageNumber; - this.pageChange.emit(e.pageNumber); - }, 100); - }); + this._latestScrolledPage = e.pageNumber + this.pageChange.emit(e.pageNumber) + }, 100) + }) - eventBus.on('textlayerrendered', e => { - this.textLayerRendered.emit(e); - }); + eventBus.on('textlayerrendered', (e: any) => { + this.textLayerRendered.emit(e) + }) - this.pdfMultiPageLinkService = new PDFJSViewer.PDFLinkService({ eventBus }); + this.pdfMultiPageLinkService = new PDFJSViewer.PDFLinkService({ eventBus }) this.pdfMultiPageFindController = new PDFJSViewer.PDFFindController({ linkService: this.pdfMultiPageLinkService, - eventBus - }); + eventBus, + }) const pdfOptions: PDFViewerParams | any = { eventBus: eventBus, @@ -437,42 +504,43 @@ export class PdfViewerComponent textLayerMode: this._renderText ? this._renderTextMode : RenderTextMode.DISABLED, - findController: this.pdfMultiPageFindController - }; + findController: this.pdfMultiPageFindController, + } - this.pdfMultiPageViewer = new PDFJSViewer.PDFViewer(pdfOptions); - this.pdfMultiPageLinkService.setViewer(this.pdfMultiPageViewer); - this.pdfMultiPageFindController.setDocument(this._pdf); + this.pdfMultiPageViewer = new PDFJSViewer.PDFViewer(pdfOptions) + this.pdfMultiPageLinkService.setViewer(this.pdfMultiPageViewer) + this.pdfMultiPageFindController.setDocument(this._pdf) } private setupSinglePageViewer() { - (PDFJS as any).disableTextLayer = !this._renderText; + ;(PDFJS as any).disableTextLayer = !this._renderText - PdfViewerComponent.setExternalLinkTarget(this._externalLinkTarget); + PdfViewerComponent.setExternalLinkTarget(this._externalLinkTarget) - const eventBus = createEventBus(PDFJSViewer); + const eventBus = createEventBus(PDFJSViewer) - eventBus.on('pagechanging', e => { - if (e.pageNumber != this._page) { - this.page = e.pageNumber; + eventBus.on('pagechanging', (e: any) => { + if (e.pageNumber !== this._page) { + this.page = e.pageNumber + this.cd.markForCheck() } - }); + }) - eventBus.on('pagerendered', e => { - this.pageRendered.emit(e); - }); + eventBus.on('pagerendered', (e: any) => { + this.pageRendered.emit(e) + }) - eventBus.on('textlayerrendered', e => { - this.textLayerRendered.emit(e); - }); + eventBus.on('textlayerrendered', (e: any) => { + this.textLayerRendered.emit(e) + }) this.pdfSinglePageLinkService = new PDFJSViewer.PDFLinkService({ - eventBus - }); + eventBus, + }) this.pdfSinglePageFindController = new PDFJSViewer.PDFFindController({ linkService: this.pdfSinglePageLinkService, - eventBus - }); + eventBus, + }) const pdfOptions: PDFViewerParams | any = { eventBus: eventBus, @@ -482,167 +550,237 @@ export class PdfViewerComponent textLayerMode: this._renderText ? this._renderTextMode : RenderTextMode.DISABLED, - findController: this.pdfSinglePageFindController - }; + findController: this.pdfSinglePageFindController, + } - this.pdfSinglePageViewer = new PDFJSViewer.PDFSinglePageViewer(pdfOptions); - this.pdfSinglePageLinkService.setViewer(this.pdfSinglePageViewer); - this.pdfSinglePageFindController.setDocument(this._pdf); + this.pdfSinglePageViewer = new PDFJSViewer.PDFSinglePageViewer(pdfOptions) + this.pdfSinglePageLinkService.setViewer(this.pdfSinglePageViewer) + this.pdfSinglePageFindController.setDocument(this._pdf) - this.pdfSinglePageViewer._currentPageNumber = this._page; + this.pdfSinglePageViewer._currentPageNumber = this._page } private getValidPageNumber(page: number): number { if (page < 1) { - return 1; + return 1 } - if (page > this._pdf.numPages) { - return this._pdf.numPages; + if (page > (this._pdf as PDFDocumentProxy).numPages) { + return (this._pdf as PDFDocumentProxy).numPages } - return page; + return page } private getDocumentParams() { - const srcType = typeof this.src; + const srcType = typeof this.src if (!this._cMapsUrl) { - return this.src; + return this.src } const params: any = { cMapUrl: this._cMapsUrl, - cMapPacked: true - }; + cMapPacked: true, + } if (srcType === 'string') { - params.url = this.src; + params.url = this.src } else if (srcType === 'object') { if ((this.src as any).byteLength !== undefined) { - params.data = this.src; + params.data = this.src } else { - Object.assign(params, this.src); + Object.assign(params, this.src) } } - return params; + return params } private loadPDF() { if (!this.src) { - return; + return } if (this.lastLoaded === this.src) { - this.update(); - return; + this.update() + return } - this.clear(); + this.clear() - this.loadingTask = (PDFJS as any).getDocument(this.getDocumentParams()); + this.loadingTask = (PDFJS as any).getDocument(this.getDocumentParams()) this.loadingTask.onProgress = (progressData: PDFProgressData) => { - this.onProgress.emit(progressData); - }; + this.progressChange.emit(progressData) + } - const src = this.src; - (>this.loadingTask.promise).then( + const src = this.src + ;(>this.loadingTask.promise).then( (pdf: PDFDocumentProxy) => { - this._pdf = pdf; - this.lastLoaded = src; + this._pdf = pdf + this.lastLoaded = src - this.afterLoadComplete.emit(pdf); + this.afterLoadComplete.emit(pdf) if (!this.pdfMultiPageViewer) { - this.setupMultiPageViewer(); - this.setupSinglePageViewer(); + this.setupSinglePageViewer() } - this.resetPdfDocument(); + this.resetPdfDocument() - this.update(); + this.update() }, (error: any) => { - this.onError.emit(error); + this.errored.emit(error) } - ); + ) } private update() { - this.page = this._page; + this.page = this._page - this.render(); + this.render() + this.cd.markForCheck() } private render() { - this._page = this.getValidPageNumber(this._page); - const currentViewer = this.getCurrentViewer(); + this._page = this.getValidPageNumber(this._page) + const currentViewer = this.getCurrentViewer() if ( this._rotation !== 0 || currentViewer.pagesRotation !== this._rotation ) { setTimeout(() => { - currentViewer.pagesRotation = this._rotation; - }); + currentViewer.pagesRotation = this._rotation + }) } if (this._stickToPage) { setTimeout(() => { - currentViewer.currentPageNumber = this._page; - }); + currentViewer.currentPageNumber = this._page + }) } - this.updateSize(); + this.updateSize() } private getScale(viewportWidth: number, viewportHeight: number) { - const borderSize = (this._showBorders ? 2 * PdfViewerComponent.BORDER_WIDTH : 0); - const pdfContainerWidth = this.pdfViewerContainer.nativeElement.clientWidth - borderSize; - const pdfContainerHeight = this.pdfViewerContainer.nativeElement.clientHeight - borderSize; + const borderSize = this._showBorders + ? 2 * PdfViewerComponent.BORDER_WIDTH + : 0 + const pdfContainerWidth = + this.pdfViewerContainer.nativeElement.clientWidth - borderSize + const pdfContainerHeight = + this.pdfViewerContainer.nativeElement.clientHeight - borderSize - if (pdfContainerHeight === 0 || viewportHeight === 0 || pdfContainerWidth === 0 || viewportWidth === 0) { - return 1; + if ( + pdfContainerHeight === 0 || + viewportHeight === 0 || + pdfContainerWidth === 0 || + viewportWidth === 0 + ) { + return 1 } - let ratio = 1; + let ratio = 1 switch (this._zoomScale) { case 'page-fit': - ratio = Math.min((pdfContainerHeight / viewportHeight), (pdfContainerWidth / viewportWidth)); - break; + ratio = Math.min( + pdfContainerHeight / viewportHeight, + pdfContainerWidth / viewportWidth + ) + break case 'page-height': - ratio = (pdfContainerHeight / viewportHeight); - break; + ratio = pdfContainerHeight / viewportHeight + break case 'page-width': default: - ratio = (pdfContainerWidth / viewportWidth); - break; + ratio = pdfContainerWidth / viewportWidth + break } - return (this._zoom * ratio) / PdfViewerComponent.CSS_UNITS; + return (this._zoom * ratio) / PdfViewerComponent.CSS_UNITS } private getCurrentViewer(): any { - return this._showAll ? this.pdfMultiPageViewer : this.pdfSinglePageViewer; + return this._showAll ? this.pdfMultiPageViewer : this.pdfSinglePageViewer } private resetPdfDocument() { - this.pdfFindController.setDocument(this._pdf); + this.pdfFindController.setDocument(this._pdf) if (this._showAll) { - this.pdfSinglePageViewer.setDocument(null); - this.pdfSinglePageLinkService.setDocument(null); + this.pdfSinglePageViewer.setDocument(null) + this.pdfSinglePageLinkService.setDocument(null) - this.pdfMultiPageViewer.setDocument(this._pdf); - this.pdfMultiPageLinkService.setDocument(this._pdf, null); + this.pdfMultiPageViewer.setDocument(this._pdf) + this.pdfMultiPageLinkService.setDocument(this._pdf, null) } else { - this.pdfMultiPageViewer.setDocument(null); - this.pdfMultiPageLinkService.setDocument(null); + this.pdfMultiPageViewer.setDocument(null) + this.pdfMultiPageLinkService.setDocument(null) - this.pdfSinglePageViewer.setDocument(this._pdf); - this.pdfSinglePageLinkService.setDocument(this._pdf, null); + this.pdfSinglePageViewer.setDocument(this._pdf) + this.pdfSinglePageLinkService.setDocument(this._pdf, null) } } + + private handleTextSelection(): void { + document.querySelector('.pdfViewer')?.addEventListener('mouseup', () => { + this.annotationCardClosed.emit() + const selection: Selection = window?.getSelection() as Selection + const textSelection = selection.toString() + + if (textSelection.length > 0) { + const anchor: HTMLElement = this.createAnchor(selection) + const anchorPosition = getPosition(anchor) + + console.log(anchorPosition) + + let adjustedPosition + const offsetRight = + (document.querySelector('.pdf-viewer-container') as HTMLElement) + .offsetWidth - anchorPosition.positionX + + if (offsetRight < 360) { + const adjustedCardWidth = 340 + + adjustedPosition = { + positionY: (anchorPosition.positionY - 6) / this.zoom, + positionX: + (anchorPosition.positionX - adjustedCardWidth + offsetRight) / + this.zoom, + } + } else { + adjustedPosition = { + positionY: (anchorPosition.positionY - 6) / this.zoom, + positionX: (anchorPosition.positionX - 110) / this.zoom, + } + } + + this.annotationCardCoordinates = adjustedPosition + + this.openAnnotationForm({ + positionY: (anchorPosition.positionY - 6) / this.zoom, + positionX: (anchorPosition.positionX - 110) / this.zoom, + text: textSelection, + }) + + anchor.remove() + this.cd.markForCheck() + } + }) + } + + private createAnchor(selection: Selection): HTMLElement { + const range = selection.getRangeAt(0) + const el = document.createElement('span') + el.id = 'annotation-anchor' + + range.collapse() + range.insertNode(el) + + return document.getElementById('annotation-anchor') as HTMLElement + } } diff --git a/src/app/pdf-viewer/pdf-viewer.module.ts b/src/app/pdf-viewer/pdf-viewer.module.ts index 297e21428..3861145b0 100644 --- a/src/app/pdf-viewer/pdf-viewer.module.ts +++ b/src/app/pdf-viewer/pdf-viewer.module.ts @@ -1,13 +1,18 @@ /** * Created by vadimdez on 01/11/2016. */ -import { NgModule } from '@angular/core'; +import { NgModule } from '@angular/core' -import { PdfViewerComponent } from './pdf-viewer.component'; -import { PDFJSStatic } from 'pdfjs-dist'; +import { PdfViewerComponent } from './pdf-viewer.component' +import { PagerComponent } from './pager/pager.component' + +import { PDFJSStatic } from 'pdfjs-dist' +import { CommonModule } from '@angular/common' +import { MatIconModule } from '@angular/material/icon' +import { OverlayModule } from '@angular/cdk/overlay' declare global { - const PDFJS: PDFJSStatic; + const PDFJS: PDFJSStatic } export { @@ -17,11 +22,12 @@ export { PDFPageProxy, PDFSource, PDFProgressData, - PDFPromise -} from 'pdfjs-dist'; + PDFPromise, +} from 'pdfjs-dist' @NgModule({ - declarations: [PdfViewerComponent], - exports: [PdfViewerComponent] + imports: [CommonModule, MatIconModule, OverlayModule], + declarations: [PdfViewerComponent, PagerComponent], + exports: [PdfViewerComponent], }) export class PdfViewerModule {} diff --git a/src/app/utils/event-bus-utils.ts b/src/app/utils/event-bus-utils.ts index 627eba562..d5cf0d1bb 100644 --- a/src/app/utils/event-bus-utils.ts +++ b/src/app/utils/event-bus-utils.ts @@ -6,58 +6,58 @@ export function _createEventBus(pdfJsViewer: any): any { } function attachDOMEventsToEventBus(eventBus: any) { - eventBus.on('documentload', function() { + eventBus.on('documentload', function () { const event = document.createEvent('CustomEvent'); event.initCustomEvent('documentload', true, true, {}); window.dispatchEvent(event); }); - eventBus.on('pagerendered', function(evt) { + eventBus.on('pagerendered', function (evt: any) { const event = document.createEvent('CustomEvent'); event.initCustomEvent('pagerendered', true, true, { pageNumber: evt.pageNumber, - cssTransform: evt.cssTransform + cssTransform: evt.cssTransform, }); evt.source.div.dispatchEvent(event); }); - eventBus.on('textlayerrendered', function(evt) { + eventBus.on('textlayerrendered', function (evt: any) { const event = document.createEvent('CustomEvent'); event.initCustomEvent('textlayerrendered', true, true, { - pageNumber: evt.pageNumber + pageNumber: evt.pageNumber, }); evt.source.textLayerDiv.dispatchEvent(event); }); - eventBus.on('pagechanging', function(evt) { - const event = document.createEvent('UIEvents'); + eventBus.on('pagechanging', function (evt: any) { + const event: any = document.createEvent('UIEvents'); event.initEvent('pagechanging', true, true); event['pageNumber'] = evt.pageNumber; evt.source.container.dispatchEvent(event); }); - eventBus.on('pagesinit', function(evt) { + eventBus.on('pagesinit', function (evt: any) { const event = document.createEvent('CustomEvent'); event.initCustomEvent('pagesinit', true, true, null); evt.source.container.dispatchEvent(event); }); - eventBus.on('pagesloaded', function(evt) { + eventBus.on('pagesloaded', function (evt: any) { const event = document.createEvent('CustomEvent'); event.initCustomEvent('pagesloaded', true, true, { - pagesCount: evt.pagesCount + pagesCount: evt.pagesCount, }); evt.source.container.dispatchEvent(event); }); - eventBus.on('scalechange', function(evt) { - const event = document.createEvent('UIEvents'); + eventBus.on('scalechange', function (evt: any) { + const event: any = document.createEvent('UIEvents'); event.initEvent('scalechange', true, true); event['scale'] = evt.scale; event['presetValue'] = evt.presetValue; evt.source.container.dispatchEvent(event); }); - eventBus.on('updateviewarea', function(evt) { - const event = document.createEvent('UIEvents'); + eventBus.on('updateviewarea', function (evt: any) { + const event: any = document.createEvent('UIEvents'); event.initEvent('updateviewarea', true, true); event['location'] = evt.location; evt.source.container.dispatchEvent(event); }); - eventBus.on('find', function(evt) { + eventBus.on('find', function (evt: any) { if (evt.source === window) { return; // event comes from FirefoxCom, no need to replicate } @@ -67,50 +67,50 @@ function attachDOMEventsToEventBus(eventBus: any) { phraseSearch: evt.phraseSearch, caseSensitive: evt.caseSensitive, highlightAll: evt.highlightAll, - findPrevious: evt.findPrevious + findPrevious: evt.findPrevious, }); window.dispatchEvent(event); }); - eventBus.on('attachmentsloaded', function(evt) { + eventBus.on('attachmentsloaded', function (evt: any) { const event = document.createEvent('CustomEvent'); event.initCustomEvent('attachmentsloaded', true, true, { - attachmentsCount: evt.attachmentsCount + attachmentsCount: evt.attachmentsCount, }); evt.source.container.dispatchEvent(event); }); - eventBus.on('sidebarviewchanged', function(evt) { + eventBus.on('sidebarviewchanged', function (evt: any) { const event = document.createEvent('CustomEvent'); event.initCustomEvent('sidebarviewchanged', true, true, { - view: evt.view + view: evt.view, }); evt.source.outerContainer.dispatchEvent(event); }); - eventBus.on('pagemode', function(evt) { + eventBus.on('pagemode', function (evt: any) { const event = document.createEvent('CustomEvent'); event.initCustomEvent('pagemode', true, true, { - mode: evt.mode + mode: evt.mode, }); evt.source.pdfViewer.container.dispatchEvent(event); }); - eventBus.on('namedaction', function(evt) { + eventBus.on('namedaction', function (evt: any) { const event = document.createEvent('CustomEvent'); event.initCustomEvent('namedaction', true, true, { - action: evt.action + action: evt.action, }); evt.source.pdfViewer.container.dispatchEvent(event); }); - eventBus.on('presentationmodechanged', function(evt) { + eventBus.on('presentationmodechanged', function (evt: any) { const event = document.createEvent('CustomEvent'); event.initCustomEvent('presentationmodechanged', true, true, { active: evt.active, - switchInProgress: evt.switchInProgress + switchInProgress: evt.switchInProgress, }); window.dispatchEvent(event); }); - eventBus.on('outlineloaded', function(evt) { + eventBus.on('outlineloaded', function (evt: any) { const event = document.createEvent('CustomEvent'); event.initCustomEvent('outlineloaded', true, true, { - outlineCount: evt.outlineCount + outlineCount: evt.outlineCount, }); evt.source.container.dispatchEvent(event); }); diff --git a/src/app/utils/get-position.ts b/src/app/utils/get-position.ts new file mode 100644 index 000000000..def17eba4 --- /dev/null +++ b/src/app/utils/get-position.ts @@ -0,0 +1,17 @@ +import { Coordinates } from '../pdf-viewer/pdf-viewer.component'; + +export const getPosition = (element: HTMLElement): Coordinates => { + let positionX = 0; + let positionY = 0; + + while (element) { + positionX += element.offsetLeft; + positionY += element.offsetTop; + element = element?.offsetParent as HTMLElement; + } + + return { + positionX, + positionY, + }; +}; diff --git a/src/styles.scss b/src/styles.scss index 2efd0cc2e..b5cbe59a6 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -1,2 +1,2 @@ /* You can add global styles to this file, and also import other style files */ -@import '../node_modules/@angular/material/prebuilt-themes/indigo-pink.css'; \ No newline at end of file +@import '../node_modules/@angular/material/prebuilt-themes/indigo-pink.css'; diff --git a/tsconfig.json b/tsconfig.json index 30956ae7e..4cd62f6ff 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,13 +11,9 @@ "moduleResolution": "node", "importHelpers": true, "target": "es2015", - "typeRoots": [ - "node_modules/@types" - ], - "lib": [ - "es2018", - "dom" - ] + "typeRoots": ["node_modules/@types"], + "noImplicitAny": true, + "lib": ["es2018", "dom"] }, "angularCompilerOptions": { "fullTemplateTypeCheck": true, diff --git a/tslint.json b/tslint.json deleted file mode 100644 index f85fc68d9..000000000 --- a/tslint.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "extends": "tslint:recommended", - "rules": { - "array-type": false, - "arrow-parens": false, - "deprecation": { - "severity": "warning" - }, - "component-class-suffix": true, - "contextual-lifecycle": true, - "directive-class-suffix": true, - "directive-selector": [ - true, - "attribute", - "app", - "camelCase" - ], - "component-selector": [ - true, - "element", - "app", - "kebab-case" - ], - "import-blacklist": [ - true, - "rxjs/Rx" - ], - "interface-name": false, - "max-classes-per-file": false, - "max-line-length": [ - true, - 140 - ], - "member-access": false, - "member-ordering": [ - true, - { - "order": [ - "static-field", - "instance-field", - "static-method", - "instance-method" - ] - } - ], - "no-consecutive-blank-lines": false, - "no-console": [ - true, - "debug", - "info", - "time", - "timeEnd", - "trace" - ], - "no-empty": false, - "no-inferrable-types": [ - true, - "ignore-params" - ], - "no-non-null-assertion": true, - "no-redundant-jsdoc": true, - "no-switch-case-fall-through": true, - "no-var-requires": false, - "object-literal-key-quotes": [ - true, - "as-needed" - ], - "object-literal-sort-keys": false, - "ordered-imports": false, - "quotemark": [ - true, - "single" - ], - "trailing-comma": false, - "no-conflicting-lifecycle": true, - "no-host-metadata-property": true, - "no-input-rename": true, - "no-inputs-metadata-property": true, - "no-output-native": true, - "no-output-on-prefix": true, - "no-output-rename": true, - "no-outputs-metadata-property": true, - "template-banana-in-box": true, - "template-no-negated-async": true, - "use-lifecycle-interface": true, - "use-pipe-transform-interface": true - }, - "rulesDirectory": [ - "codelyzer" - ] -}