diff --git a/src-ui/src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.spec.ts b/src-ui/src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.spec.ts
index 52789fb49..2a1ea25fe 100644
--- a/src-ui/src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.spec.ts
+++ b/src-ui/src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.spec.ts
@@ -20,6 +20,7 @@ import { TagsComponent } from '../../input/tags/tags.component'
import { TextComponent } from '../../input/text/text.component'
import { EditDialogMode } from '../edit-dialog.component'
import { ConsumptionTemplateEditDialogComponent } from './consumption-template-edit-dialog.component'
+import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
describe('ConsumptionTemplateEditDialogComponent', () => {
let component: ConsumptionTemplateEditDialogComponent
@@ -93,6 +94,15 @@ describe('ConsumptionTemplateEditDialogComponent', () => {
}),
},
},
+ {
+ provide: CustomFieldsService,
+ useValue: {
+ listAll: () =>
+ of({
+ results: [],
+ }),
+ },
+ },
],
imports: [
HttpClientTestingModule,
diff --git a/src-ui/src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.ts b/src-ui/src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.ts
index 3f89e5d76..dedbd3523 100644
--- a/src-ui/src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.ts
+++ b/src-ui/src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.ts
@@ -18,6 +18,8 @@ import { SettingsService } from 'src/app/services/settings.service'
import { EditDialogComponent } from '../edit-dialog.component'
import { MailRuleService } from 'src/app/services/rest/mail-rule.service'
import { PaperlessMailRule } from 'src/app/data/paperless-mail-rule'
+import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
+import { PaperlessCustomField } from 'src/app/data/paperless-custom-field'
export const DOCUMENT_SOURCE_OPTIONS = [
{
@@ -45,6 +47,7 @@ export class ConsumptionTemplateEditDialogComponent extends EditDialogComponent<
documentTypes: PaperlessDocumentType[]
storagePaths: PaperlessStoragePath[]
mailRules: PaperlessMailRule[]
+ customFields: PaperlessCustomField[]
constructor(
service: ConsumptionTemplateService,
@@ -54,7 +57,8 @@ export class ConsumptionTemplateEditDialogComponent extends EditDialogComponent<
storagePathService: StoragePathService,
mailRuleService: MailRuleService,
userService: UserService,
- settingsService: SettingsService
+ settingsService: SettingsService,
+ customFieldsService: CustomFieldsService
) {
super(service, activeModal, userService, settingsService)
@@ -77,6 +81,11 @@ export class ConsumptionTemplateEditDialogComponent extends EditDialogComponent<
.listAll()
.pipe(first())
.subscribe((result) => (this.mailRules = result.results))
+
+ customFieldsService
+ .listAll()
+ .pipe(first())
+ .subscribe((result) => (this.customFields = result.results))
}
getCreateTitle() {
@@ -106,6 +115,7 @@ export class ConsumptionTemplateEditDialogComponent extends EditDialogComponent<
assign_view_groups: new FormControl([]),
assign_change_users: new FormControl([]),
assign_change_groups: new FormControl([]),
+ assign_custom_fields: new FormControl([]),
})
}
diff --git a/src-ui/src/app/data/paperless-consumption-template.ts b/src-ui/src/app/data/paperless-consumption-template.ts
index c303fc8d4..94e6202c1 100644
--- a/src-ui/src/app/data/paperless-consumption-template.ts
+++ b/src-ui/src/app/data/paperless-consumption-template.ts
@@ -38,4 +38,6 @@ export interface PaperlessConsumptionTemplate extends ObjectWithId {
assign_change_users?: number[] // [PaperlessUser.id]
assign_change_groups?: number[] // [PaperlessGroup.id]
+
+ assign_custom_fields?: number[] // [PaperlessCustomField.id]
}
diff --git a/src/documents/consumer.py b/src/documents/consumer.py
index fa8f8fcfe..4f97881ef 100644
--- a/src/documents/consumer.py
+++ b/src/documents/consumer.py
@@ -29,6 +29,8 @@ from documents.loggers import LoggingMixin
from documents.matching import document_matches_template
from documents.models import ConsumptionTemplate
from documents.models import Correspondent
+from documents.models import CustomField
+from documents.models import CustomFieldInstance
from documents.models import Document
from documents.models import DocumentType
from documents.models import FileInfo
@@ -124,6 +126,7 @@ class Consumer(LoggingMixin):
self.override_asn = None
self.task_id = None
self.override_owner_id = None
+ self.override_custom_field_ids = None
self.channel_layer = get_channel_layer()
@@ -333,6 +336,7 @@ class Consumer(LoggingMixin):
override_view_groups=None,
override_change_users=None,
override_change_groups=None,
+ override_custom_field_ids=None,
) -> Document:
"""
Return the document object if it was successfully created.
@@ -353,6 +357,7 @@ class Consumer(LoggingMixin):
self.override_view_groups = override_view_groups
self.override_change_users = override_change_users
self.override_change_groups = override_change_groups
+ self.override_custom_field_ids = override_custom_field_ids
self._send_progress(
0,
@@ -644,6 +649,11 @@ class Consumer(LoggingMixin):
template_overrides.change_groups = [
group.pk for group in template.assign_change_groups.all()
]
+ if template.assign_custom_fields is not None:
+ template_overrides.custom_field_ids = [
+ field.pk for field in template.assign_custom_fields.all()
+ ]
+
overrides.update(template_overrides)
return overrides
@@ -782,6 +792,14 @@ class Consumer(LoggingMixin):
}
set_permissions_for_object(permissions=permissions, object=document)
+ if self.override_custom_field_ids:
+ for field_id in self.override_custom_field_ids:
+ field = CustomField.objects.get(pk=field_id)
+ CustomFieldInstance.objects.create(
+ field=field,
+ document=document,
+ ) # adds to document
+
def _write(self, storage_type, source, target):
with open(source, "rb") as read_file, open(target, "wb") as write_file:
write_file.write(read_file.read())
diff --git a/src/documents/data_models.py b/src/documents/data_models.py
index 29a23fa7a..8b53e2c14 100644
--- a/src/documents/data_models.py
+++ b/src/documents/data_models.py
@@ -28,6 +28,7 @@ class DocumentMetadataOverrides:
view_groups: Optional[list[int]] = None
change_users: Optional[list[int]] = None
change_groups: Optional[list[int]] = None
+ custom_field_ids: Optional[list[int]] = None
def update(self, other: "DocumentMetadataOverrides") -> "DocumentMetadataOverrides":
"""
@@ -74,6 +75,12 @@ class DocumentMetadataOverrides:
self.change_groups = other.change_groups
elif other.change_groups is not None:
self.change_groups.extend(other.change_groups)
+
+ if self.custom_field_ids is None:
+ self.custom_field_ids = other.custom_field_ids
+ elif other.custom_field_ids is not None:
+ self.custom_field_ids.extend(other.custom_field_ids)
+
return self
diff --git a/src/documents/migrations/1042_consumptiontemplate_assign_custom_fields.py b/src/documents/migrations/1042_consumptiontemplate_assign_custom_fields.py
new file mode 100644
index 000000000..08d6062ea
--- /dev/null
+++ b/src/documents/migrations/1042_consumptiontemplate_assign_custom_fields.py
@@ -0,0 +1,23 @@
+# Generated by Django 4.2.7 on 2023-11-30 17:44
+
+from django.db import migrations
+from django.db import models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("documents", "1041_alter_consumptiontemplate_sources"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="consumptiontemplate",
+ name="assign_custom_fields",
+ field=models.ManyToManyField(
+ blank=True,
+ related_name="+",
+ to="documents.customfield",
+ verbose_name="assign these custom fields",
+ ),
+ ),
+ ]
diff --git a/src/documents/models.py b/src/documents/models.py
index c3eea0ac9..d688253de 100644
--- a/src/documents/models.py
+++ b/src/documents/models.py
@@ -743,140 +743,6 @@ class ShareLink(models.Model):
return f"Share Link for {self.document.title}"
-class ConsumptionTemplate(models.Model):
- class DocumentSourceChoices(models.IntegerChoices):
- CONSUME_FOLDER = DocumentSource.ConsumeFolder.value, _("Consume Folder")
- API_UPLOAD = DocumentSource.ApiUpload.value, _("Api Upload")
- MAIL_FETCH = DocumentSource.MailFetch.value, _("Mail Fetch")
-
- name = models.CharField(_("name"), max_length=256, unique=True)
-
- order = models.IntegerField(_("order"), default=0)
-
- sources = MultiSelectField(
- max_length=5,
- choices=DocumentSourceChoices.choices,
- default=f"{DocumentSource.ConsumeFolder},{DocumentSource.ApiUpload},{DocumentSource.MailFetch}",
- )
-
- filter_path = models.CharField(
- _("filter path"),
- max_length=256,
- null=True,
- blank=True,
- help_text=_(
- "Only consume documents with a path that matches "
- "this if specified. Wildcards specified as * are "
- "allowed. Case insensitive.",
- ),
- )
-
- filter_filename = models.CharField(
- _("filter filename"),
- max_length=256,
- null=True,
- blank=True,
- help_text=_(
- "Only consume documents which entirely match this "
- "filename if specified. Wildcards such as *.pdf or "
- "*invoice* are allowed. Case insensitive.",
- ),
- )
-
- filter_mailrule = models.ForeignKey(
- "paperless_mail.MailRule",
- null=True,
- blank=True,
- on_delete=models.SET_NULL,
- verbose_name=_("filter documents from this mail rule"),
- )
-
- assign_title = models.CharField(
- _("assign title"),
- max_length=256,
- null=True,
- blank=True,
- help_text=_(
- "Assign a document title, can include some placeholders, "
- "see documentation.",
- ),
- )
-
- assign_tags = models.ManyToManyField(
- Tag,
- blank=True,
- verbose_name=_("assign this tag"),
- )
-
- assign_document_type = models.ForeignKey(
- DocumentType,
- null=True,
- blank=True,
- on_delete=models.SET_NULL,
- verbose_name=_("assign this document type"),
- )
-
- assign_correspondent = models.ForeignKey(
- Correspondent,
- null=True,
- blank=True,
- on_delete=models.SET_NULL,
- verbose_name=_("assign this correspondent"),
- )
-
- assign_storage_path = models.ForeignKey(
- StoragePath,
- null=True,
- blank=True,
- on_delete=models.SET_NULL,
- verbose_name=_("assign this storage path"),
- )
-
- assign_owner = models.ForeignKey(
- User,
- null=True,
- blank=True,
- on_delete=models.SET_NULL,
- related_name="+",
- verbose_name=_("assign this owner"),
- )
-
- assign_view_users = models.ManyToManyField(
- User,
- blank=True,
- related_name="+",
- verbose_name=_("grant view permissions to these users"),
- )
-
- assign_view_groups = models.ManyToManyField(
- Group,
- blank=True,
- related_name="+",
- verbose_name=_("grant view permissions to these groups"),
- )
-
- assign_change_users = models.ManyToManyField(
- User,
- blank=True,
- related_name="+",
- verbose_name=_("grant change permissions to these users"),
- )
-
- assign_change_groups = models.ManyToManyField(
- Group,
- blank=True,
- related_name="+",
- verbose_name=_("grant change permissions to these groups"),
- )
-
- class Meta:
- verbose_name = _("consumption template")
- verbose_name_plural = _("consumption templates")
-
- def __str__(self):
- return f"{self.name}"
-
-
class CustomField(models.Model):
"""
Defines the name and type of a custom field
@@ -1013,3 +879,144 @@ if settings.AUDIT_LOG_ENABLED:
auditlog.register(Note)
auditlog.register(CustomField)
auditlog.register(CustomFieldInstance)
+
+
+class ConsumptionTemplate(models.Model):
+ class DocumentSourceChoices(models.IntegerChoices):
+ CONSUME_FOLDER = DocumentSource.ConsumeFolder.value, _("Consume Folder")
+ API_UPLOAD = DocumentSource.ApiUpload.value, _("Api Upload")
+ MAIL_FETCH = DocumentSource.MailFetch.value, _("Mail Fetch")
+
+ name = models.CharField(_("name"), max_length=256, unique=True)
+
+ order = models.IntegerField(_("order"), default=0)
+
+ sources = MultiSelectField(
+ max_length=5,
+ choices=DocumentSourceChoices.choices,
+ default=f"{DocumentSource.ConsumeFolder},{DocumentSource.ApiUpload},{DocumentSource.MailFetch}",
+ )
+
+ filter_path = models.CharField(
+ _("filter path"),
+ max_length=256,
+ null=True,
+ blank=True,
+ help_text=_(
+ "Only consume documents with a path that matches "
+ "this if specified. Wildcards specified as * are "
+ "allowed. Case insensitive.",
+ ),
+ )
+
+ filter_filename = models.CharField(
+ _("filter filename"),
+ max_length=256,
+ null=True,
+ blank=True,
+ help_text=_(
+ "Only consume documents which entirely match this "
+ "filename if specified. Wildcards such as *.pdf or "
+ "*invoice* are allowed. Case insensitive.",
+ ),
+ )
+
+ filter_mailrule = models.ForeignKey(
+ "paperless_mail.MailRule",
+ null=True,
+ blank=True,
+ on_delete=models.SET_NULL,
+ verbose_name=_("filter documents from this mail rule"),
+ )
+
+ assign_title = models.CharField(
+ _("assign title"),
+ max_length=256,
+ null=True,
+ blank=True,
+ help_text=_(
+ "Assign a document title, can include some placeholders, "
+ "see documentation.",
+ ),
+ )
+
+ assign_tags = models.ManyToManyField(
+ Tag,
+ blank=True,
+ verbose_name=_("assign this tag"),
+ )
+
+ assign_document_type = models.ForeignKey(
+ DocumentType,
+ null=True,
+ blank=True,
+ on_delete=models.SET_NULL,
+ verbose_name=_("assign this document type"),
+ )
+
+ assign_correspondent = models.ForeignKey(
+ Correspondent,
+ null=True,
+ blank=True,
+ on_delete=models.SET_NULL,
+ verbose_name=_("assign this correspondent"),
+ )
+
+ assign_storage_path = models.ForeignKey(
+ StoragePath,
+ null=True,
+ blank=True,
+ on_delete=models.SET_NULL,
+ verbose_name=_("assign this storage path"),
+ )
+
+ assign_owner = models.ForeignKey(
+ User,
+ null=True,
+ blank=True,
+ on_delete=models.SET_NULL,
+ related_name="+",
+ verbose_name=_("assign this owner"),
+ )
+
+ assign_view_users = models.ManyToManyField(
+ User,
+ blank=True,
+ related_name="+",
+ verbose_name=_("grant view permissions to these users"),
+ )
+
+ assign_view_groups = models.ManyToManyField(
+ Group,
+ blank=True,
+ related_name="+",
+ verbose_name=_("grant view permissions to these groups"),
+ )
+
+ assign_change_users = models.ManyToManyField(
+ User,
+ blank=True,
+ related_name="+",
+ verbose_name=_("grant change permissions to these users"),
+ )
+
+ assign_change_groups = models.ManyToManyField(
+ Group,
+ blank=True,
+ related_name="+",
+ verbose_name=_("grant change permissions to these groups"),
+ )
+
+ assign_custom_fields = models.ManyToManyField(
+ CustomField,
+ blank=True,
+ related_name="+",
+ verbose_name=_("assign these custom fields"),
+ )
+
+ class Meta:
+ verbose_name = _("consumption template")
+ verbose_name_plural = _("consumption templates")
+
+ def __str__(self):
+ return f"{self.name}"
diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py
index b75ca3418..2373a25dd 100644
--- a/src/documents/serialisers.py
+++ b/src/documents/serialisers.py
@@ -429,7 +429,7 @@ class ReadWriteSerializerMethodField(serializers.SerializerMethodField):
class CustomFieldInstanceSerializer(serializers.ModelSerializer):
field = serializers.PrimaryKeyRelatedField(queryset=CustomField.objects.all())
- value = ReadWriteSerializerMethodField()
+ value = ReadWriteSerializerMethodField(allow_null=True)
def create(self, validated_data):
type_to_data_store_name_map = {
@@ -1166,6 +1166,7 @@ class ConsumptionTemplateSerializer(serializers.ModelSerializer):
"assign_view_groups",
"assign_change_users",
"assign_change_groups",
+ "assign_custom_fields",
]
def validate(self, attrs):
diff --git a/src/documents/tasks.py b/src/documents/tasks.py
index e89b4fa47..10a44a8fe 100644
--- a/src/documents/tasks.py
+++ b/src/documents/tasks.py
@@ -179,6 +179,7 @@ def consume_file(
override_view_groups=overrides.view_groups,
override_change_users=overrides.change_users,
override_change_groups=overrides.change_groups,
+ override_custom_field_ids=overrides.custom_field_ids,
task_id=self.request.id,
)
diff --git a/src/documents/tests/test_api.py b/src/documents/tests/test_api.py
index 2cda45e7f..e671ce2ce 100644
--- a/src/documents/tests/test_api.py
+++ b/src/documents/tests/test_api.py
@@ -5649,6 +5649,11 @@ class TestApiConsumptionTemplates(DirectoriesMixin, APITestCase):
self.t2 = Tag.objects.create(name="t2")
self.t3 = Tag.objects.create(name="t3")
self.sp = StoragePath.objects.create(path="/test/")
+ self.cf1 = CustomField.objects.create(name="Custom Field 1", data_type="string")
+ self.cf2 = CustomField.objects.create(
+ name="Custom Field 2",
+ data_type="integer",
+ )
self.ct = ConsumptionTemplate.objects.create(
name="Template 1",
@@ -5669,6 +5674,8 @@ class TestApiConsumptionTemplates(DirectoriesMixin, APITestCase):
self.ct.assign_view_groups.add(self.group1.pk)
self.ct.assign_change_users.add(self.user3.pk)
self.ct.assign_change_groups.add(self.group1.pk)
+ self.ct.assign_custom_fields.add(self.cf1.pk)
+ self.ct.assign_custom_fields.add(self.cf2.pk)
self.ct.save()
def test_api_get_consumption_template(self):
diff --git a/src/documents/tests/test_consumer.py b/src/documents/tests/test_consumer.py
index 831dbcc3a..e2cd74016 100644
--- a/src/documents/tests/test_consumer.py
+++ b/src/documents/tests/test_consumer.py
@@ -22,6 +22,7 @@ from documents.consumer import Consumer
from documents.consumer import ConsumerError
from documents.consumer import ConsumerFilePhase
from documents.models import Correspondent
+from documents.models import CustomField
from documents.models import Document
from documents.models import DocumentType
from documents.models import FileInfo
@@ -458,6 +459,29 @@ class TestConsumer(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
self.assertIn(t3, document.tags.all())
self._assert_first_last_send_progress()
+ def testOverrideCustomFields(self):
+ cf1 = CustomField.objects.create(name="Custom Field 1", data_type="string")
+ cf2 = CustomField.objects.create(
+ name="Custom Field 2",
+ data_type="integer",
+ )
+ cf3 = CustomField.objects.create(
+ name="Custom Field 3",
+ data_type="url",
+ )
+ document = self.consumer.try_consume_file(
+ self.get_test_file(),
+ override_custom_field_ids=[cf1.id, cf3.id],
+ )
+
+ fields_used = [
+ field_instance.field for field_instance in document.custom_fields.all()
+ ]
+ self.assertIn(cf1, fields_used)
+ self.assertNotIn(cf2, fields_used)
+ self.assertIn(cf3, fields_used)
+ self._assert_first_last_send_progress()
+
def testOverrideAsn(self):
document = self.consumer.try_consume_file(
self.get_test_file(),
diff --git a/src/documents/tests/test_consumption_templates.py b/src/documents/tests/test_consumption_templates.py
index dd5d7b2af..3abbacf14 100644
--- a/src/documents/tests/test_consumption_templates.py
+++ b/src/documents/tests/test_consumption_templates.py
@@ -11,6 +11,7 @@ from documents.data_models import ConsumableDocument
from documents.data_models import DocumentSource
from documents.models import ConsumptionTemplate
from documents.models import Correspondent
+from documents.models import CustomField
from documents.models import DocumentType
from documents.models import StoragePath
from documents.models import Tag
@@ -32,6 +33,11 @@ class TestConsumptionTemplates(DirectoriesMixin, FileSystemAssertsMixin, TestCas
self.t2 = Tag.objects.create(name="t2")
self.t3 = Tag.objects.create(name="t3")
self.sp = StoragePath.objects.create(path="/test/")
+ self.cf1 = CustomField.objects.create(name="Custom Field 1", data_type="string")
+ self.cf2 = CustomField.objects.create(
+ name="Custom Field 2",
+ data_type="integer",
+ )
self.user2 = User.objects.create(username="user2")
self.user3 = User.objects.create(username="user3")
@@ -95,6 +101,8 @@ class TestConsumptionTemplates(DirectoriesMixin, FileSystemAssertsMixin, TestCas
ct.assign_view_groups.add(self.group1.pk)
ct.assign_change_users.add(self.user3.pk)
ct.assign_change_groups.add(self.group1.pk)
+ ct.assign_custom_fields.add(self.cf1.pk)
+ ct.assign_custom_fields.add(self.cf2.pk)
ct.save()
self.assertEqual(ct.__str__(), "Template 1")
@@ -128,6 +136,10 @@ class TestConsumptionTemplates(DirectoriesMixin, FileSystemAssertsMixin, TestCas
overrides["override_title"],
"Doc from {correspondent}",
)
+ self.assertEqual(
+ overrides["override_custom_field_ids"],
+ [self.cf1.pk, self.cf2.pk],
+ )
info = cm.output[0]
expected_str = f"Document matched template {ct}"
From ade16d19479a1a1d37f627be0a561085d8dbeba5 Mon Sep 17 00:00:00 2001
From: Trenton Holmes <797416+stumpylog@users.noreply.github.com>
Date: Sun, 3 Dec 2023 19:09:02 -0800
Subject: [PATCH 25/41] Fixes the 0023 migration to include the new help text
and verbose name
---
..._mailrule_filter_attachment_filename_and_more.py | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/src/paperless_mail/migrations/0023_remove_mailrule_filter_attachment_filename_and_more.py b/src/paperless_mail/migrations/0023_remove_mailrule_filter_attachment_filename_and_more.py
index 17cb7d645..1a1eac790 100644
--- a/src/paperless_mail/migrations/0023_remove_mailrule_filter_attachment_filename_and_more.py
+++ b/src/paperless_mail/migrations/0023_remove_mailrule_filter_attachment_filename_and_more.py
@@ -1,4 +1,4 @@
-# Generated by Django 4.2.7 on 2023-11-28 17:47
+# Generated by Django 4.2.7 on 2023-12-04 03:06
from django.db import migrations
from django.db import models
@@ -26,4 +26,15 @@ class Migration(migrations.Migration):
verbose_name="filter attachment filename exclusive",
),
),
+ migrations.AlterField(
+ model_name="mailrule",
+ name="filter_attachment_filename_include",
+ field=models.CharField(
+ blank=True,
+ help_text="Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive.",
+ max_length=256,
+ null=True,
+ verbose_name="filter attachment filename inclusive",
+ ),
+ ),
]
From 1a36d8ff23f85963dee3fb874755dcee9f213258 Mon Sep 17 00:00:00 2001
From: shamoon <4887959+shamoon@users.noreply.github.com>
Date: Mon, 4 Dec 2023 06:40:17 -0800
Subject: [PATCH 26/41] Fix: bulk edit object permissions should use
permissions object (#4797)
---
.../permissions-dialog.component.spec.ts | 11 ++++++++++-
.../permissions-dialog.component.ts | 13 +++++++++++--
2 files changed, 21 insertions(+), 3 deletions(-)
diff --git a/src-ui/src/app/components/common/permissions-dialog/permissions-dialog.component.spec.ts b/src-ui/src/app/components/common/permissions-dialog/permissions-dialog.component.spec.ts
index 47f245d0a..3f601d771 100644
--- a/src-ui/src/app/components/common/permissions-dialog/permissions-dialog.component.spec.ts
+++ b/src-ui/src/app/components/common/permissions-dialog/permissions-dialog.component.spec.ts
@@ -80,7 +80,16 @@ describe('PermissionsDialogComponent', () => {
it('should return permissions', () => {
expect(component.permissions).toEqual({
owner: null,
- set_permissions: null,
+ set_permissions: {
+ view: {
+ users: [],
+ groups: [],
+ },
+ change: {
+ users: [],
+ groups: [],
+ },
+ },
})
component.form.get('permissions_form').setValue(set_permissions)
expect(component.permissions).toEqual(set_permissions)
diff --git a/src-ui/src/app/components/common/permissions-dialog/permissions-dialog.component.ts b/src-ui/src/app/components/common/permissions-dialog/permissions-dialog.component.ts
index 8e5f2321b..e6d5fdb2b 100644
--- a/src-ui/src/app/components/common/permissions-dialog/permissions-dialog.component.ts
+++ b/src-ui/src/app/components/common/permissions-dialog/permissions-dialog.component.ts
@@ -52,8 +52,17 @@ export class PermissionsDialogComponent {
get permissions() {
return {
owner: this.form.get('permissions_form').value?.owner ?? null,
- set_permissions:
- this.form.get('permissions_form').value?.set_permissions ?? null,
+ set_permissions: this.form.get('permissions_form').value
+ ?.set_permissions ?? {
+ view: {
+ users: [],
+ groups: [],
+ },
+ change: {
+ users: [],
+ groups: [],
+ },
+ },
}
}
From d82e97fab7b157e563138a4d422464ac538b1af3 Mon Sep 17 00:00:00 2001
From: shamoon <4887959+shamoon@users.noreply.github.com>
Date: Mon, 4 Dec 2023 17:17:40 -0800
Subject: [PATCH 27/41] Feature: pngx PDF viewer with updated pdfjs (#4679)
---
src-ui/.eslintrc.json | 3 +-
src-ui/angular.json | 7 +-
.../document-detail/document-detail.spec.ts | 2 +-
src-ui/jest.config.js | 1 +
src-ui/package-lock.json | 376 ++++--
src-ui/package.json | 2 +-
src-ui/src/app/app.module.ts | 4 +-
.../pdf-viewer/pdf-viewer.component.html | 3 +
.../pdf-viewer/pdf-viewer.component.scss | 1014 +++++++++++++++++
.../common/pdf-viewer/pdf-viewer.component.ts | 599 ++++++++++
.../components/common/pdf-viewer/typings.ts | 17 +
.../pdf-viewer/utils/event-bus-utils.ts | 182 +++
.../document-detail.component.html | 85 +-
.../document-detail.component.scss | 19 +-
.../document-detail.component.spec.ts | 35 +-
.../document-detail.component.ts | 64 +-
16 files changed, 2286 insertions(+), 127 deletions(-)
create mode 100644 src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.html
create mode 100644 src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.scss
create mode 100644 src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.ts
create mode 100644 src-ui/src/app/components/common/pdf-viewer/typings.ts
create mode 100644 src-ui/src/app/components/common/pdf-viewer/utils/event-bus-utils.ts
diff --git a/src-ui/.eslintrc.json b/src-ui/.eslintrc.json
index 12b35ce26..37f9c7c12 100644
--- a/src-ui/.eslintrc.json
+++ b/src-ui/.eslintrc.json
@@ -1,7 +1,8 @@
{
"root": true,
"ignorePatterns": [
- "projects/**/*"
+ "projects/**/*",
+ "/src/app/components/common/pdf-viewer/**"
],
"overrides": [
{
diff --git a/src-ui/angular.json b/src-ui/angular.json
index 1fef2fe7d..9a73922c7 100644
--- a/src-ui/angular.json
+++ b/src-ui/angular.json
@@ -65,7 +65,7 @@
"src/assets",
"src/manifest.webmanifest",
{
- "glob": "pdf.worker.min.js",
+ "glob": "{pdf.worker.min.js,pdf.min.js}",
"input": "node_modules/pdfjs-dist/build/",
"output": "/assets/js/"
}
@@ -75,7 +75,8 @@
],
"scripts": [],
"allowedCommonJsDependencies": [
- "ng2-pdf-viewer"
+ "pdfjs-dist",
+ "pdfjs-dist/web/pdf_viewer"
],
"vendorChunk": true,
"extractLicenses": false,
@@ -109,7 +110,7 @@
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
- "maximumError": "10kb"
+ "maximumError": "30kb"
}
]
},
diff --git a/src-ui/e2e/document-detail/document-detail.spec.ts b/src-ui/e2e/document-detail/document-detail.spec.ts
index 1bc2b6aa9..e40b88ccc 100644
--- a/src-ui/e2e/document-detail/document-detail.spec.ts
+++ b/src-ui/e2e/document-detail/document-detail.spec.ts
@@ -79,7 +79,7 @@ test('should show a mobile preview', async ({ page }) => {
await page.setViewportSize({ width: 400, height: 1000 })
await expect(page.getByRole('tab', { name: 'Preview' })).toBeVisible()
await page.getByRole('tab', { name: 'Preview' }).click()
- await page.waitForSelector('pdf-viewer')
+ await page.waitForSelector('pngx-pdf-viewer')
})
test('should show a list of notes', async ({ page }) => {
diff --git a/src-ui/jest.config.js b/src-ui/jest.config.js
index 78edc0822..b544a4767 100644
--- a/src-ui/jest.config.js
+++ b/src-ui/jest.config.js
@@ -7,6 +7,7 @@ module.exports = {
'abstract-name-filter-service',
'abstract-paperless-service',
],
+ coveragePathIgnorePatterns: ['/src/app/components/common/pdf-viewer/*'],
transformIgnorePatterns: [`
/node_modules/(?!.*\\.mjs$|lodash-es)`],
moduleNameMapper: {
'^src/(.*)': '/src/$1',
diff --git a/src-ui/package-lock.json b/src-ui/package-lock.json
index cf0882708..4e898bf81 100644
--- a/src-ui/package-lock.json
+++ b/src-ui/package-lock.json
@@ -25,11 +25,11 @@
"bootstrap": "^5.3.2",
"file-saver": "^2.0.5",
"mime-names": "^1.0.0",
- "ng2-pdf-viewer": "^10.0.0",
"ngx-color": "^9.0.0",
"ngx-cookie-service": "^16.0.1",
"ngx-file-drop": "^16.0.0",
"ngx-ui-tour-ng-bootstrap": "^13.0.6",
+ "pdfjs-dist": "^3.11.174",
"rxjs": "^7.8.1",
"tslib": "^2.6.2",
"uuid": "^9.0.1",
@@ -4728,6 +4728,116 @@
"integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==",
"dev": true
},
+ "node_modules/@mapbox/node-pre-gyp": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
+ "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==",
+ "optional": true,
+ "dependencies": {
+ "detect-libc": "^2.0.0",
+ "https-proxy-agent": "^5.0.0",
+ "make-dir": "^3.1.0",
+ "node-fetch": "^2.6.7",
+ "nopt": "^5.0.0",
+ "npmlog": "^5.0.1",
+ "rimraf": "^3.0.2",
+ "semver": "^7.3.5",
+ "tar": "^6.1.11"
+ },
+ "bin": {
+ "node-pre-gyp": "bin/node-pre-gyp"
+ }
+ },
+ "node_modules/@mapbox/node-pre-gyp/node_modules/are-we-there-yet": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
+ "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
+ "optional": true,
+ "dependencies": {
+ "delegates": "^1.0.0",
+ "readable-stream": "^3.6.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@mapbox/node-pre-gyp/node_modules/gauge": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
+ "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
+ "optional": true,
+ "dependencies": {
+ "aproba": "^1.0.3 || ^2.0.0",
+ "color-support": "^1.1.2",
+ "console-control-strings": "^1.0.0",
+ "has-unicode": "^2.0.1",
+ "object-assign": "^4.1.1",
+ "signal-exit": "^3.0.0",
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1",
+ "wide-align": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
+ "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+ "optional": true,
+ "dependencies": {
+ "semver": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "optional": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@mapbox/node-pre-gyp/node_modules/nopt": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
+ "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
+ "optional": true,
+ "dependencies": {
+ "abbrev": "1"
+ },
+ "bin": {
+ "nopt": "bin/nopt.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@mapbox/node-pre-gyp/node_modules/npmlog": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
+ "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
+ "optional": true,
+ "dependencies": {
+ "are-we-there-yet": "^2.0.0",
+ "console-control-strings": "^1.1.0",
+ "gauge": "^3.0.0",
+ "set-blocking": "^2.0.0"
+ }
+ },
+ "node_modules/@mapbox/node-pre-gyp/node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "optional": true
+ },
"node_modules/@ng-bootstrap/ng-bootstrap": {
"version": "15.1.2",
"resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-15.1.2.tgz",
@@ -6728,7 +6838,7 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
- "dev": true
+ "devOptional": true
},
"node_modules/accepts": {
"version": "1.3.8",
@@ -6823,7 +6933,7 @@
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"debug": "4"
},
@@ -6974,7 +7084,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
"integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==",
- "dev": true
+ "devOptional": true
},
"node_modules/are-we-there-yet": {
"version": "3.0.1",
@@ -7332,7 +7442,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true
+ "devOptional": true
},
"node_modules/base64-js": {
"version": "1.5.1",
@@ -7476,7 +7586,7 @@
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -7682,6 +7792,21 @@
}
]
},
+ "node_modules/canvas": {
+ "version": "2.11.2",
+ "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz",
+ "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==",
+ "hasInstallScript": true,
+ "optional": true,
+ "dependencies": {
+ "@mapbox/node-pre-gyp": "^1.0.0",
+ "nan": "^2.17.0",
+ "simple-get": "^3.0.3"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@@ -7740,7 +7865,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
- "dev": true,
+ "devOptional": true,
"engines": {
"node": ">=10"
}
@@ -7884,7 +8009,7 @@
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
- "dev": true,
+ "devOptional": true,
"bin": {
"color-support": "bin.js"
}
@@ -7974,7 +8099,7 @@
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
- "dev": true
+ "devOptional": true
},
"node_modules/concurrently": {
"version": "8.2.2",
@@ -8101,7 +8226,7 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
- "dev": true
+ "devOptional": true
},
"node_modules/content-disposition": {
"version": "0.5.4",
@@ -8616,6 +8741,18 @@
"integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==",
"dev": true
},
+ "node_modules/decompress-response": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
+ "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
+ "optional": true,
+ "dependencies": {
+ "mimic-response": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/dedent": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz",
@@ -8705,7 +8842,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
- "dev": true
+ "devOptional": true
},
"node_modules/depd": {
"version": "2.0.0",
@@ -8735,6 +8872,15 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
+ "node_modules/detect-libc": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz",
+ "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==",
+ "optional": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/detect-newline": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
@@ -8863,12 +9009,6 @@
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
- "node_modules/dommatrix": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/dommatrix/-/dommatrix-1.0.3.tgz",
- "integrity": "sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww==",
- "deprecated": "dommatrix is no longer maintained. Please use @thednp/dommatrix."
- },
"node_modules/domutils": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
@@ -10175,7 +10315,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
- "dev": true
+ "devOptional": true
},
"node_modules/fsevents": {
"version": "2.3.2",
@@ -10469,7 +10609,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==",
- "dev": true
+ "devOptional": true
},
"node_modules/hasown": {
"version": "2.0.0",
@@ -10706,7 +10846,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"agent-base": "6",
"debug": "4"
@@ -10907,7 +11047,7 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
@@ -10917,7 +11057,7 @@
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true
+ "devOptional": true
},
"node_modules/ini": {
"version": "4.1.1",
@@ -13824,6 +13964,18 @@
"node": ">=6"
}
},
+ "node_modules/mimic-response": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
+ "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==",
+ "optional": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/mini-css-extract-plugin": {
"version": "2.7.6",
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.6.tgz",
@@ -13853,7 +14005,7 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
@@ -13874,7 +14026,7 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
- "dev": true,
+ "devOptional": true,
"engines": {
"node": ">=8"
}
@@ -14048,7 +14200,7 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"minipass": "^3.0.0",
"yallist": "^4.0.0"
@@ -14061,7 +14213,7 @@
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"yallist": "^4.0.0"
},
@@ -14073,13 +14225,13 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
- "dev": true
+ "devOptional": true
},
"node_modules/mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
- "dev": true,
+ "devOptional": true,
"bin": {
"mkdirp": "bin/cmd.js"
},
@@ -14129,6 +14281,12 @@
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
"dev": true
},
+ "node_modules/nan": {
+ "version": "2.18.0",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz",
+ "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==",
+ "optional": true
+ },
"node_modules/nanoid": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
@@ -14209,18 +14367,6 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true
},
- "node_modules/ng2-pdf-viewer": {
- "version": "10.0.0",
- "resolved": "https://registry.npmjs.org/ng2-pdf-viewer/-/ng2-pdf-viewer-10.0.0.tgz",
- "integrity": "sha512-zEefcAsTpDoxFceQYs3ycPMaUAkt5UX4OcTstVQoNqRK6w+vOY+V8z8aFCuBwnt+7iN1EHaIpquOf4S9mWc04g==",
- "dependencies": {
- "pdfjs-dist": "~2.16.105",
- "tslib": "^2.3.0"
- },
- "peerDependencies": {
- "pdfjs-dist": "~2.16.105"
- }
- },
"node_modules/ngx-color": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/ngx-color/-/ngx-color-9.0.0.tgz",
@@ -14312,6 +14458,48 @@
"integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==",
"dev": true
},
+ "node_modules/node-fetch": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "optional": true,
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/node-fetch/node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "optional": true
+ },
+ "node_modules/node-fetch/node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "optional": true
+ },
+ "node_modules/node-fetch/node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "optional": true,
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
"node_modules/node-forge": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
@@ -14776,6 +14964,15 @@
"node": ">=8"
}
},
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "optional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/object-inspect": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
@@ -14825,7 +15022,7 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"wrappy": "1"
}
@@ -15396,7 +15593,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
- "dev": true,
+ "devOptional": true,
"engines": {
"node": ">=0.10.0"
}
@@ -15456,21 +15653,25 @@
"node": ">=8"
}
},
+ "node_modules/path2d-polyfill": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz",
+ "integrity": "sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA==",
+ "optional": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/pdfjs-dist": {
- "version": "2.16.105",
- "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-2.16.105.tgz",
- "integrity": "sha512-J4dn41spsAwUxCpEoVf6GVoz908IAA3mYiLmNxg8J9kfRXc2jxpbUepcP0ocp0alVNLFthTAM8DZ1RaHh8sU0A==",
- "dependencies": {
- "dommatrix": "^1.0.3",
- "web-streams-polyfill": "^3.2.1"
+ "version": "3.11.174",
+ "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-3.11.174.tgz",
+ "integrity": "sha512-TdTZPf1trZ8/UFu5Cx/GXB7GZM30LT+wWUNfsi6Bq8ePLnb+woNKtDymI2mxZYBpMbonNFqKmiz684DIfnd8dA==",
+ "engines": {
+ "node": ">=18"
},
- "peerDependencies": {
- "worker-loader": "^3.0.8"
- },
- "peerDependenciesMeta": {
- "worker-loader": {
- "optional": true
- }
+ "optionalDependencies": {
+ "canvas": "^2.11.2",
+ "path2d-polyfill": "^2.0.1"
}
},
"node_modules/picocolors": {
@@ -15990,7 +16191,7 @@
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
@@ -16243,7 +16444,7 @@
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"glob": "^7.1.3"
},
@@ -16258,7 +16459,7 @@
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
@@ -16333,7 +16534,7 @@
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "dev": true,
+ "devOptional": true,
"funding": [
{
"type": "github",
@@ -16647,7 +16848,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
- "dev": true
+ "devOptional": true
},
"node_modules/set-function-length": {
"version": "1.1.1",
@@ -16755,6 +16956,37 @@
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
+ "node_modules/simple-concat": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
+ "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "optional": true
+ },
+ "node_modules/simple-get": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz",
+ "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==",
+ "optional": true,
+ "dependencies": {
+ "decompress-response": "^4.2.0",
+ "once": "^1.3.1",
+ "simple-concat": "^1.0.0"
+ }
+ },
"node_modules/sisteransi": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
@@ -17018,7 +17250,7 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"safe-buffer": "~5.2.0"
}
@@ -17186,7 +17418,7 @@
"version": "6.1.15",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz",
"integrity": "sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
@@ -17219,7 +17451,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"minipass": "^3.0.0"
},
@@ -17231,7 +17463,7 @@
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"yallist": "^4.0.0"
},
@@ -17243,7 +17475,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
- "dev": true
+ "devOptional": true
},
"node_modules/terser": {
"version": "5.19.2",
@@ -17892,7 +18124,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
- "dev": true
+ "devOptional": true
},
"node_modules/utils-merge": {
"version": "1.0.1",
@@ -18108,14 +18340,6 @@
"defaults": "^1.0.3"
}
},
- "node_modules/web-streams-polyfill": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
- "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==",
- "engines": {
- "node": ">= 8"
- }
- },
"node_modules/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
@@ -18484,7 +18708,7 @@
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
"integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"string-width": "^1.0.2 || 2 || 3 || 4"
}
@@ -18596,7 +18820,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
- "dev": true
+ "devOptional": true
},
"node_modules/write-file-atomic": {
"version": "4.0.2",
diff --git a/src-ui/package.json b/src-ui/package.json
index 7f0720388..a2c94084c 100644
--- a/src-ui/package.json
+++ b/src-ui/package.json
@@ -27,11 +27,11 @@
"bootstrap": "^5.3.2",
"file-saver": "^2.0.5",
"mime-names": "^1.0.0",
- "ng2-pdf-viewer": "^10.0.0",
"ngx-color": "^9.0.0",
"ngx-cookie-service": "^16.0.1",
"ngx-file-drop": "^16.0.0",
"ngx-ui-tour-ng-bootstrap": "^13.0.6",
+ "pdfjs-dist": "^3.11.174",
"rxjs": "^7.8.1",
"tslib": "^2.6.2",
"uuid": "^9.0.1",
diff --git a/src-ui/src/app/app.module.ts b/src-ui/src/app/app.module.ts
index 684cf6d18..8d7ea5663 100644
--- a/src-ui/src/app/app.module.ts
+++ b/src-ui/src/app/app.module.ts
@@ -51,7 +51,6 @@ import { SavedViewWidgetComponent } from './components/dashboard/widgets/saved-v
import { StatisticsWidgetComponent } from './components/dashboard/widgets/statistics-widget/statistics-widget.component'
import { UploadFileWidgetComponent } from './components/dashboard/widgets/upload-file-widget/upload-file-widget.component'
import { WidgetFrameComponent } from './components/dashboard/widgets/widget-frame/widget-frame.component'
-import { PdfViewerModule } from 'ng2-pdf-viewer'
import { WelcomeWidgetComponent } from './components/dashboard/widgets/welcome-widget/welcome-widget.component'
import { YesNoPipe } from './pipes/yes-no.pipe'
import { FileSizePipe } from './pipes/file-size.pipe'
@@ -106,6 +105,7 @@ import { CustomFieldsComponent } from './components/manage/custom-fields/custom-
import { CustomFieldEditDialogComponent } from './components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
import { CustomFieldsDropdownComponent } from './components/common/custom-fields-dropdown/custom-fields-dropdown.component'
import { ProfileEditDialogComponent } from './components/common/profile-edit-dialog/profile-edit-dialog.component'
+import { PdfViewerComponent } from './components/common/pdf-viewer/pdf-viewer.component'
import localeAf from '@angular/common/locales/af'
import localeAr from '@angular/common/locales/ar'
@@ -258,6 +258,7 @@ function initializeApp(settings: SettingsService) {
CustomFieldEditDialogComponent,
CustomFieldsDropdownComponent,
ProfileEditDialogComponent,
+ PdfViewerComponent,
],
imports: [
BrowserModule,
@@ -267,7 +268,6 @@ function initializeApp(settings: SettingsService) {
FormsModule,
ReactiveFormsModule,
NgxFileDropModule,
- PdfViewerModule,
NgSelectModule,
ColorSliderModule,
TourNgBootstrapModule,
diff --git a/src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.html b/src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.html
new file mode 100644
index 000000000..cd2c85af5
--- /dev/null
+++ b/src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.html
@@ -0,0 +1,3 @@
+
diff --git a/src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.scss b/src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.scss
new file mode 100644
index 000000000..408bbb0a0
--- /dev/null
+++ b/src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.scss
@@ -0,0 +1,1014 @@
+/**
+ * This file is taken and modified from https://github.com/VadimDez/ng2-pdf-viewer/blob/10.0.0/src/app/pdf-viewer/pdf-viewer.component.scss
+ * Created by vadimdez on 21/06/16.
+ */
+.pngx-pdf-viewer-container {
+ overflow-x: auto;
+ position: absolute;
+ height: 100%;
+ width: 100%;
+ -webkit-overflow-scrolling: touch;
+ }
+
+ :host {
+ display: block;
+ position: relative;
+ }
+
+ :host ::ng-deep {
+ /* Copyright 2014 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ --pdfViewer-padding-bottom: 0;
+ --page-margin: 1px auto -8px;
+ --page-border: none;
+ --spreadHorizontalWrapped-margin-LR: -3.5px;
+ --viewer-container-height: 0;
+ --annotation-unfocused-field-background: url("data:image/svg+xml;charset=UTF-8,");
+ --xfa-unfocused-field-background: var(
+ --annotation-unfocused-field-background
+ );
+ --page-border-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAA1ElEQVQ4jbWUWw6EIAxFy2NFs/8NzR4UJhpqLsdi5mOmSSMUOfYWqv3S0gMr4XlYH/64gZa/gN3ANYA7KAXALt4ktoQ5MI9YxqaG8bWmsIysMuT6piSQCa4whZThCu8CM4zP9YJaKci9jicPq3NcBWYoPMGUlhG7ivtkB+gVyFY75wXghOvh8t5mto1Mdim6e+MBqH6XsY+YAwjpq3vGF7weTWQptLEDVCZvPTMl5JZZsdh47FHW6qFMyvLYqjcnmdFfY9Xk/KDOlzCusX2mi/ofM7MPkzBcSp4Q1/wAAAAASUVORK5CYII=')
+ 9 9 repeat;
+ --scale-factor: 1;
+
+ --focus-outline: solid 2px blue;
+ --hover-outline: dashed 2px blue;
+ --freetext-line-height: 1.35;
+ --freetext-padding: 2px;
+ --editorInk-editing-cursor: pointer;
+
+ @media screen and (forced-colors: active) {
+ --pdfViewer-padding-bottom: 9px;
+ --page-margin: 8px auto -1px;
+ --page-border: 1px solid CanvasText;
+ --page-border-image: none;
+ --spreadHorizontalWrapped-margin-LR: 3.5px;
+ }
+
+ @media (forced-colors: active) {
+ --focus-outline: solid 3px ButtonText;
+ --hover-outline: dashed 3px ButtonText;
+ }
+
+ .textLayer {
+ position: absolute;
+ text-align: initial;
+ left: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ overflow: hidden;
+ opacity: 0.2;
+ line-height: 1;
+ -webkit-text-size-adjust: none;
+ -moz-text-size-adjust: none;
+ text-size-adjust: none;
+ forced-color-adjust: none;
+ }
+
+ .textLayer span,
+ .textLayer br {
+ color: transparent;
+ position: absolute;
+ white-space: pre;
+ cursor: text;
+ transform-origin: 0% 0%;
+ }
+
+ /* Only necessary in Google Chrome, see issue 14205, and most unfortunately
+ * the problem doesn't show up in "text" reference tests. */
+ .textLayer span.markedContent {
+ top: 0;
+ height: 0;
+ }
+
+ .textLayer .highlight {
+ margin: -1px;
+ padding: 1px;
+ background-color: rgba(180, 0, 170, 1);
+ border-radius: 4px;
+ }
+
+ .textLayer .highlight.appended {
+ position: initial;
+ }
+
+ .textLayer .highlight.begin {
+ border-radius: 4px 0 0 4px;
+ }
+
+ .textLayer .highlight.end {
+ border-radius: 0 4px 4px 0;
+ }
+
+ .textLayer .highlight.middle {
+ border-radius: 0;
+ }
+
+ .textLayer .highlight.selected {
+ background-color: rgba(0, 100, 0, 1);
+ }
+
+ .textLayer ::-moz-selection {
+ background: rgba(0, 0, 255, 1);
+ }
+
+ .textLayer ::selection {
+ background: rgba(0, 0, 255, 1);
+ }
+
+ /* Avoids https://github.com/mozilla/pdf.js/issues/13840 in Chrome */
+ .textLayer br::-moz-selection {
+ background: transparent;
+ }
+
+ .textLayer br::selection {
+ background: transparent;
+ }
+
+ .textLayer .endOfContent {
+ display: block;
+ position: absolute;
+ left: 0;
+ top: 100%;
+ right: 0;
+ bottom: 0;
+ z-index: -1;
+ cursor: default;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+ }
+
+ .textLayer .endOfContent.active {
+ top: 0;
+ }
+
+ @media (forced-colors: active) {
+ .annotationLayer .textWidgetAnnotation input:required,
+ .annotationLayer .textWidgetAnnotation textarea:required,
+ .annotationLayer .choiceWidgetAnnotation select:required,
+ .annotationLayer .buttonWidgetAnnotation.checkBox input:required,
+ .annotationLayer .buttonWidgetAnnotation.radioButton input:required {
+ outline: 1.5px solid selectedItem;
+ }
+ }
+
+ .annotationLayer {
+ position: absolute;
+ top: 0;
+ left: 0;
+ pointer-events: none;
+ transform-origin: 0 0;
+ }
+
+ .annotationLayer section {
+ position: absolute;
+ text-align: initial;
+ pointer-events: auto;
+ box-sizing: border-box;
+ transform-origin: 0 0;
+ }
+
+ .annotationLayer .linkAnnotation > a,
+ .annotationLayer .buttonWidgetAnnotation.pushButton > a {
+ position: absolute;
+ font-size: 1em;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ }
+
+ .annotationLayer .buttonWidgetAnnotation.pushButton > canvas {
+ width: 100%;
+ height: 100%;
+ }
+
+ .annotationLayer .linkAnnotation > a:hover,
+ .annotationLayer .buttonWidgetAnnotation.pushButton > a:hover {
+ opacity: 0.2;
+ background: rgba(255, 255, 0, 1);
+ box-shadow: 0 2px 10px rgba(255, 255, 0, 1);
+ }
+
+ .annotationLayer .textAnnotation img {
+ position: absolute;
+ cursor: pointer;
+ width: 100%;
+ height: 100%;
+ }
+
+ .annotationLayer .textWidgetAnnotation input,
+ .annotationLayer .textWidgetAnnotation textarea,
+ .annotationLayer .choiceWidgetAnnotation select,
+ .annotationLayer .buttonWidgetAnnotation.checkBox input,
+ .annotationLayer .buttonWidgetAnnotation.radioButton input {
+ background-image: var(--annotation-unfocused-field-background);
+ border: 1px solid transparent;
+ box-sizing: border-box;
+ font: calc(9px * var(--scale-factor)) sans-serif;
+ height: 100%;
+ margin: 0;
+ vertical-align: top;
+ width: 100%;
+ }
+
+ .annotationLayer .textWidgetAnnotation input:required,
+ .annotationLayer .textWidgetAnnotation textarea:required,
+ .annotationLayer .choiceWidgetAnnotation select:required,
+ .annotationLayer .buttonWidgetAnnotation.checkBox input:required,
+ .annotationLayer .buttonWidgetAnnotation.radioButton input:required {
+ outline: 1.5px solid red;
+ }
+
+ .annotationLayer .choiceWidgetAnnotation select option {
+ padding: 0;
+ }
+
+ .annotationLayer .buttonWidgetAnnotation.radioButton input {
+ border-radius: 50%;
+ }
+
+ .annotationLayer .textWidgetAnnotation textarea {
+ resize: none;
+ }
+
+ .annotationLayer .textWidgetAnnotation input[disabled],
+ .annotationLayer .textWidgetAnnotation textarea[disabled],
+ .annotationLayer .choiceWidgetAnnotation select[disabled],
+ .annotationLayer .buttonWidgetAnnotation.checkBox input[disabled],
+ .annotationLayer .buttonWidgetAnnotation.radioButton input[disabled] {
+ background: none;
+ border: 1px solid transparent;
+ cursor: not-allowed;
+ }
+
+ .annotationLayer .textWidgetAnnotation input:hover,
+ .annotationLayer .textWidgetAnnotation textarea:hover,
+ .annotationLayer .choiceWidgetAnnotation select:hover,
+ .annotationLayer .buttonWidgetAnnotation.checkBox input:hover,
+ .annotationLayer .buttonWidgetAnnotation.radioButton input:hover {
+ border: 1px solid rgba(0, 0, 0, 1);
+ }
+
+ .annotationLayer .textWidgetAnnotation input:focus,
+ .annotationLayer .textWidgetAnnotation textarea:focus,
+ .annotationLayer .choiceWidgetAnnotation select:focus {
+ background: none;
+ border: 1px solid transparent;
+ }
+
+ .annotationLayer .textWidgetAnnotation input :focus,
+ .annotationLayer .textWidgetAnnotation textarea :focus,
+ .annotationLayer .choiceWidgetAnnotation select :focus,
+ .annotationLayer .buttonWidgetAnnotation.checkBox :focus,
+ .annotationLayer .buttonWidgetAnnotation.radioButton :focus {
+ background-image: none;
+ background-color: transparent;
+ outline: auto;
+ }
+
+ .annotationLayer .buttonWidgetAnnotation.checkBox input:checked:before,
+ .annotationLayer .buttonWidgetAnnotation.checkBox input:checked:after,
+ .annotationLayer .buttonWidgetAnnotation.radioButton input:checked:before {
+ background-color: CanvasText;
+ content: '';
+ display: block;
+ position: absolute;
+ }
+
+ .annotationLayer .buttonWidgetAnnotation.checkBox input:checked:before,
+ .annotationLayer .buttonWidgetAnnotation.checkBox input:checked:after {
+ height: 80%;
+ left: 45%;
+ width: 1px;
+ }
+
+ .annotationLayer .buttonWidgetAnnotation.checkBox input:checked:before {
+ transform: rotate(45deg);
+ }
+
+ .annotationLayer .buttonWidgetAnnotation.checkBox input:checked:after {
+ transform: rotate(-45deg);
+ }
+
+ .annotationLayer .buttonWidgetAnnotation.radioButton input:checked:before {
+ border-radius: 50%;
+ height: 50%;
+ left: 30%;
+ top: 20%;
+ width: 50%;
+ }
+
+ .annotationLayer .textWidgetAnnotation input.comb {
+ font-family: monospace;
+ padding-left: 2px;
+ padding-right: 0;
+ }
+
+ .annotationLayer .textWidgetAnnotation input.comb: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: 103%;
+ }
+
+ .annotationLayer .buttonWidgetAnnotation.checkBox input,
+ .annotationLayer .buttonWidgetAnnotation.radioButton input {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ }
+
+ .annotationLayer .popupTriggerArea {
+ height: 100%;
+ width: 100%;
+ }
+
+ .annotationLayer .popupWrapper {
+ position: absolute;
+ font-size: calc(9px * var(--scale-factor));
+ width: 100%;
+ min-width: calc(180px * var(--scale-factor));
+ pointer-events: none;
+ }
+
+ .annotationLayer .popup {
+ position: absolute;
+ max-width: calc(180px * var(--scale-factor));
+ background-color: rgba(255, 255, 153, 1);
+ box-shadow: 0 calc(2px * var(--scale-factor))
+ calc(5px * var(--scale-factor)) rgba(136, 136, 136, 1);
+ border-radius: calc(2px * var(--scale-factor));
+ padding: calc(6px * var(--scale-factor));
+ margin-left: calc(5px * var(--scale-factor));
+ cursor: pointer;
+ font: message-box;
+ white-space: normal;
+ word-wrap: break-word;
+ pointer-events: auto;
+ }
+
+ .annotationLayer .popup > * {
+ font-size: calc(9px * var(--scale-factor));
+ }
+
+ .annotationLayer .popup h1 {
+ display: inline-block;
+ }
+
+ .annotationLayer .popupDate {
+ display: inline-block;
+ margin-left: calc(5px * var(--scale-factor));
+ }
+
+ .annotationLayer .popupContent {
+ border-top: 1px solid rgba(51, 51, 51, 1);
+ margin-top: calc(2px * var(--scale-factor));
+ padding-top: calc(2px * var(--scale-factor));
+ }
+
+ .annotationLayer .richText > * {
+ white-space: pre-wrap;
+ font-size: calc(9px * var(--scale-factor));
+ }
+
+ .annotationLayer .highlightAnnotation,
+ .annotationLayer .underlineAnnotation,
+ .annotationLayer .squigglyAnnotation,
+ .annotationLayer .strikeoutAnnotation,
+ .annotationLayer .freeTextAnnotation,
+ .annotationLayer .lineAnnotation svg line,
+ .annotationLayer .squareAnnotation svg rect,
+ .annotationLayer .circleAnnotation svg ellipse,
+ .annotationLayer .polylineAnnotation svg polyline,
+ .annotationLayer .polygonAnnotation svg polygon,
+ .annotationLayer .caretAnnotation,
+ .annotationLayer .inkAnnotation svg polyline,
+ .annotationLayer .stampAnnotation,
+ .annotationLayer .fileAttachmentAnnotation {
+ cursor: pointer;
+ }
+
+ .annotationLayer section svg {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ }
+
+ .annotationLayer .annotationTextContent {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ opacity: 0;
+ color: transparent;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+ pointer-events: none;
+ }
+
+ .annotationLayer .annotationTextContent span {
+ width: 100%;
+ display: inline-block;
+ }
+
+ @media (forced-colors: active) {
+ .xfaLayer *:required {
+ outline: 1.5px solid selectedItem;
+ }
+ }
+
+ .xfaLayer .highlight {
+ margin: -1px;
+ padding: 1px;
+ background-color: rgba(239, 203, 237, 1);
+ border-radius: 4px;
+ }
+
+ .xfaLayer .highlight.appended {
+ position: initial;
+ }
+
+ .xfaLayer .highlight.begin {
+ border-radius: 4px 0 0 4px;
+ }
+
+ .xfaLayer .highlight.end {
+ border-radius: 0 4px 4px 0;
+ }
+
+ .xfaLayer .highlight.middle {
+ border-radius: 0;
+ }
+
+ .xfaLayer .highlight.selected {
+ background-color: rgba(203, 223, 203, 1);
+ }
+
+ .xfaLayer ::-moz-selection {
+ background: rgba(0, 0, 255, 1);
+ }
+
+ .xfaLayer ::selection {
+ background: rgba(0, 0, 255, 1);
+ }
+
+ .xfaPage {
+ overflow: hidden;
+ position: relative;
+ }
+
+ .xfaContentarea {
+ position: absolute;
+ }
+
+ .xfaPrintOnly {
+ display: none;
+ }
+
+ .xfaLayer {
+ position: absolute;
+ text-align: initial;
+ top: 0;
+ left: 0;
+ transform-origin: 0 0;
+ line-height: 1.2;
+ }
+
+ .xfaLayer * {
+ color: inherit;
+ font: inherit;
+ font-style: inherit;
+ font-weight: inherit;
+ font-kerning: inherit;
+ letter-spacing: -0.01px;
+ text-align: inherit;
+ text-decoration: inherit;
+ box-sizing: border-box;
+ background-color: transparent;
+ padding: 0;
+ margin: 0;
+ pointer-events: auto;
+ line-height: inherit;
+ }
+
+ .xfaLayer *:required {
+ outline: 1.5px solid red;
+ }
+
+ .xfaLayer div {
+ pointer-events: none;
+ }
+
+ .xfaLayer svg {
+ pointer-events: none;
+ }
+
+ .xfaLayer svg * {
+ pointer-events: none;
+ }
+
+ .xfaLayer a {
+ color: blue;
+ }
+
+ .xfaRich li {
+ margin-left: 3em;
+ }
+
+ .xfaFont {
+ color: black;
+ font-weight: normal;
+ font-kerning: none;
+ font-size: 10px;
+ font-style: normal;
+ letter-spacing: 0;
+ text-decoration: none;
+ vertical-align: 0;
+ }
+
+ .xfaCaption {
+ overflow: hidden;
+ flex: 0 0 auto;
+ }
+
+ .xfaCaptionForCheckButton {
+ overflow: hidden;
+ flex: 1 1 auto;
+ }
+
+ .xfaLabel {
+ height: 100%;
+ width: 100%;
+ }
+
+ .xfaLeft {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ }
+
+ .xfaRight {
+ display: flex;
+ flex-direction: row-reverse;
+ align-items: center;
+ }
+
+ .xfaLeft > .xfaCaption,
+ .xfaLeft > .xfaCaptionForCheckButton,
+ .xfaRight > .xfaCaption,
+ .xfaRight > .xfaCaptionForCheckButton {
+ max-height: 100%;
+ }
+
+ .xfaTop {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ }
+
+ .xfaBottom {
+ display: flex;
+ flex-direction: column-reverse;
+ align-items: flex-start;
+ }
+
+ .xfaTop > .xfaCaption,
+ .xfaTop > .xfaCaptionForCheckButton,
+ .xfaBottom > .xfaCaption,
+ .xfaBottom > .xfaCaptionForCheckButton {
+ width: 100%;
+ }
+
+ .xfaBorder {
+ background-color: transparent;
+ position: absolute;
+ pointer-events: none;
+ }
+
+ .xfaWrapped {
+ width: 100%;
+ height: 100%;
+ }
+
+ .xfaTextfield:focus,
+ .xfaSelect:focus {
+ background-image: none;
+ background-color: transparent;
+ outline: auto;
+ outline-offset: -1px;
+ }
+
+ .xfaCheckbox:focus,
+ .xfaRadio:focus {
+ outline: auto;
+ }
+
+ .xfaTextfield,
+ .xfaSelect {
+ height: 100%;
+ width: 100%;
+ flex: 1 1 auto;
+ border: none;
+ resize: none;
+ background-image: var(--xfa-unfocused-field-background);
+ }
+
+ .xfaTop > .xfaTextfield,
+ .xfaTop > .xfaSelect,
+ .xfaBottom > .xfaTextfield,
+ .xfaBottom > .xfaSelect {
+ flex: 0 1 auto;
+ }
+
+ .xfaButton {
+ cursor: pointer;
+ width: 100%;
+ height: 100%;
+ border: none;
+ text-align: center;
+ }
+
+ .xfaLink {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
+ }
+
+ .xfaCheckbox,
+ .xfaRadio {
+ width: 100%;
+ height: 100%;
+ flex: 0 0 auto;
+ border: none;
+ }
+
+ .xfaRich {
+ white-space: pre-wrap;
+ width: 100%;
+ height: 100%;
+ }
+
+ .xfaImage {
+ -o-object-position: left top;
+ object-position: left top;
+ -o-object-fit: contain;
+ object-fit: contain;
+ width: 100%;
+ height: 100%;
+ }
+
+ .xfaLrTb,
+ .xfaRlTb,
+ .xfaTb {
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ .xfaLr {
+ display: flex;
+ flex-direction: row;
+ align-items: stretch;
+ }
+
+ .xfaRl {
+ display: flex;
+ flex-direction: row-reverse;
+ align-items: stretch;
+ }
+
+ .xfaTb > div {
+ justify-content: left;
+ }
+
+ .xfaPosition {
+ position: relative;
+ }
+
+ .xfaArea {
+ position: relative;
+ }
+
+ .xfaValignMiddle {
+ display: flex;
+ align-items: center;
+ }
+
+ .xfaTable {
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ .xfaTable .xfaRow {
+ display: flex;
+ flex-direction: row;
+ align-items: stretch;
+ }
+
+ .xfaTable .xfaRlRow {
+ display: flex;
+ flex-direction: row-reverse;
+ align-items: stretch;
+ flex: 1;
+ }
+
+ .xfaTable .xfaRlRow > div {
+ flex: 1;
+ }
+
+ .xfaNonInteractive input,
+ .xfaNonInteractive textarea,
+ .xfaDisabled input,
+ .xfaDisabled textarea,
+ .xfaReadOnly input,
+ .xfaReadOnly textarea {
+ background: initial;
+ }
+
+ @media print {
+ .xfaTextfield,
+ .xfaSelect {
+ background: transparent;
+ }
+
+ .xfaSelect {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ text-indent: 1px;
+ text-overflow: '';
+ }
+ }
+
+ [data-editor-rotation='90'] {
+ transform: rotate(90deg);
+ }
+ [data-editor-rotation='180'] {
+ transform: rotate(180deg);
+ }
+ [data-editor-rotation='270'] {
+ transform: rotate(270deg);
+ }
+
+ .annotationEditorLayer {
+ background: transparent;
+ position: absolute;
+ top: 0;
+ left: 0;
+ font-size: calc(100px * var(--scale-factor));
+ transform-origin: 0 0;
+ }
+
+ .annotationEditorLayer .selectedEditor {
+ outline: var(--focus-outline);
+ resize: none;
+ }
+
+ .annotationEditorLayer .freeTextEditor {
+ position: absolute;
+ background: transparent;
+ border-radius: 3px;
+ padding: calc(var(--freetext-padding) * var(--scale-factor));
+ resize: none;
+ width: auto;
+ height: auto;
+ z-index: 1;
+ transform-origin: 0 0;
+ touch-action: none;
+ }
+
+ .annotationEditorLayer .freeTextEditor .internal {
+ background: transparent;
+ border: none;
+ top: 0;
+ left: 0;
+ overflow: visible;
+ white-space: nowrap;
+ resize: none;
+ font: 10px sans-serif;
+ line-height: var(--freetext-line-height);
+ }
+
+ .annotationEditorLayer .freeTextEditor .overlay {
+ position: absolute;
+ display: none;
+ background: transparent;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ }
+
+ .annotationEditorLayer .freeTextEditor .overlay.enabled {
+ display: block;
+ }
+
+ .annotationEditorLayer .freeTextEditor .internal:empty::before {
+ content: attr(default-content);
+ color: gray;
+ }
+
+ .annotationEditorLayer .freeTextEditor .internal:focus {
+ outline: none;
+ }
+
+ .annotationEditorLayer .inkEditor.disabled {
+ resize: none;
+ }
+
+ .annotationEditorLayer .inkEditor.disabled.selectedEditor {
+ resize: horizontal;
+ }
+
+ .annotationEditorLayer .freeTextEditor:hover:not(.selectedEditor),
+ .annotationEditorLayer .inkEditor:hover:not(.selectedEditor) {
+ outline: var(--hover-outline);
+ }
+
+ .annotationEditorLayer .inkEditor {
+ position: absolute;
+ background: transparent;
+ border-radius: 3px;
+ overflow: auto;
+ width: 100%;
+ height: 100%;
+ z-index: 1;
+ transform-origin: 0 0;
+ cursor: auto;
+ }
+
+ .annotationEditorLayer .inkEditor.editing {
+ resize: none;
+ cursor: var(--editorInk-editing-cursor), pointer;
+ }
+
+ .annotationEditorLayer .inkEditor .inkEditorCanvas {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ touch-action: none;
+ }
+
+ [data-main-rotation='90'] {
+ transform: rotate(90deg) translateY(-100%);
+ }
+ [data-main-rotation='180'] {
+ transform: rotate(180deg) translate(-100%, -100%);
+ }
+ [data-main-rotation='270'] {
+ transform: rotate(270deg) translateX(-100%);
+ }
+
+ .pdfViewer {
+ padding-bottom: var(--pdfViewer-padding-bottom);
+ }
+
+ .pdfViewer .canvasWrapper {
+ overflow: hidden;
+ }
+
+ .pdfViewer .page {
+ direction: ltr;
+ width: 816px;
+ height: 1056px;
+ margin: var(--page-margin);
+ position: relative;
+ overflow: visible;
+ border: var(--page-border);
+ -o-border-image: var(--page-border-image);
+ border-image: var(--page-border-image);
+ background-clip: content-box;
+ background-color: rgba(255, 255, 255, 1);
+ }
+
+ .pdfViewer .dummyPage {
+ position: relative;
+ width: 0;
+ height: var(--viewer-container-height);
+ }
+
+ .pdfViewer.removePageBorders .page {
+ margin: 0 auto 10px;
+ border: none;
+ }
+
+ .pdfViewer.singlePageView {
+ display: inline-block;
+ }
+
+ .pdfViewer.singlePageView .page {
+ margin: 0;
+ border: none;
+ }
+
+ .pdfViewer.scrollHorizontal,
+ .pdfViewer.scrollWrapped,
+ .spread {
+ margin-left: 3.5px;
+ margin-right: 3.5px;
+ text-align: center;
+ }
+
+ .pdfViewer.scrollHorizontal,
+ .spread {
+ white-space: nowrap;
+ }
+
+ .pdfViewer.removePageBorders,
+ .pdfViewer.scrollHorizontal .spread,
+ .pdfViewer.scrollWrapped .spread {
+ margin-left: 0;
+ margin-right: 0;
+ }
+
+ .spread .page,
+ .spread .dummyPage,
+ .pdfViewer.scrollHorizontal .page,
+ .pdfViewer.scrollWrapped .page,
+ .pdfViewer.scrollHorizontal .spread,
+ .pdfViewer.scrollWrapped .spread {
+ display: inline-block;
+ vertical-align: middle;
+ }
+
+ .spread .page,
+ .pdfViewer.scrollHorizontal .page,
+ .pdfViewer.scrollWrapped .page {
+ margin-left: var(--spreadHorizontalWrapped-margin-LR);
+ margin-right: var(--spreadHorizontalWrapped-margin-LR);
+ }
+
+ .pdfViewer.removePageBorders .spread .page,
+ .pdfViewer.removePageBorders.scrollHorizontal .page,
+ .pdfViewer.removePageBorders.scrollWrapped .page {
+ margin-left: 5px;
+ margin-right: 5px;
+ }
+
+ .pdfViewer .page canvas {
+ margin: 0;
+ display: block;
+ }
+
+ .pdfViewer .page canvas[hidden] {
+ display: none;
+ }
+
+ .pdfViewer .page .loadingIcon {
+ position: absolute;
+ display: block;
+ left: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ background: url('data:image/gif;base64,R0lGODlhGAAYAPQQAM7Ozvr6+uDg4LCwsOjo6I6OjsjIyJycnNjY2KioqMDAwPLy8nZ2doaGhri4uGhoaP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/ilPcHRpbWl6ZWQgd2l0aCBodHRwczovL2V6Z2lmLmNvbS9vcHRpbWl6ZQAh+QQJBwAQACwAAAAAGAAYAAAFmiAkjiTkOGVaBgjZNGSgkgKjjM8zLoI8iy+BKCdiCX8iBeMAhEEIPRXLxViYUE9CbCQoFAzFhHY3zkaT3oPvBz1zE4UBsr1eWZH4vAowOBwGAHk8AoQLfH6Agm0Ed3qOAXWOIgQKiWyFJQgDgJEpdG+WEACNEFNFmKVlVzJQk6qdkwqBoi1mebJ3ALNGeIZHtGSwNDS1RZKueCEAIfkECQcAEAAsAAAAABgAGAAABZcgJI4kpChlWgYCWRQkEKgjURgjw4zOg9CjVwuiEyEeO6CxkBC9nA+HiuUqLEyoBZI0Mx4SAFFgQCDZuguBoGv6Dtg0gvpqdhxQQDkBzuUr/4A1JwMKP39pc2mDhYCIc4GQYn6QCwCMeY91l0p6dBAEJ0OfcFRimZ91Mwt0alxxAIZyRmuAsKxDLKKvZbM1tJxmvGKRpn8hACH5BAkHABAALAAAAAAYABgAAAWhICSOJGQYZVoGAnkcJBKoI3EAY1GMCtPSosSBINKJBIwGkHdwBGGQA0OhYpEGQxNqkYzNIITBACEKKBaxxNfBeOCO4vMy0Hg8nDHFeCktkKtfNAtoS4UqAicKBj9zBAKPC4iKi4aRkISGmWWBmjUIAIyHkCUEAKCVo2WmREecVqoCgZhgP4NHrGWCj7e3szSpuxAsoVWxnp6cVV4kyZW+KSEAIfkECQcAEAAsAAAAABgAGAAABZkgJI4kBABlWgYEOQykEKgjMSDjcYxG0dKi108nEhQKQN4rCIMkCgbawjWYnSCLY2yGVSgEooBhWqsGGwxc0RtNBgoMhmJ1QgETjANYFeBKyUmBKQQIdT9JDmgPDQ6EhoKJD4sOgpWWgiwChyqEBH5hmptSoSOZgJ4kLKWkYTF7C2SaqaM/hEWygay4mYG8t6uffFuzl1iANCEAIfkECQcAEAAsAAAAABgAGAAABZ0gJI4khCBlmhKkopBCoI6LIozDMAIHO4uuBVBnOiR+I4FrCDwAZsKdQnaCLIwwmRUA8JmioprWUCjcwlwUMnAoG0qL03k2KCS8cC0UjOzDCQKBfHQFDAwFU4CCfgqFhy9+kZJWgzSKSAcPZn+BfQENDw8OljGWJAFeDoZPYTBnC1GdSXqnsoBolSulX2GyP6hgvnG0KrS3NJNhuSQhACH5BAkHABAALAAAAAAYABgAAAWaICSOJCQIZZoupGGQRKCOC0CMijIiwz2LABtQZxoMfjQhxAXszWQ7gOwECRhh0MCJJRJARTUoIHFAgbfI6uBwAJS01J/i4PClVYHvfV8lbLlIBmwFbQt+aGmChG18jXeGT4dICQxlb4g/AQUMDER9XjR6BAdiDQwINDBmkAsPDVh4cX4imw53iLKuaVqAcUsPqEiidkt6j4AzIQAh+QQJBwAQACwAAAAAGAAYAAAFmSAkjiREEGWaBiSCtCoZCMsIAKOg1LEo0KKbaKFQ9EYLoOkFuQlirNxzCQkUW9GZ0hQd4nyDAWr4G/esYSbyZFYZwu3jqiuvr8u8I2BwOAwASXh1e31/doeHC3klWnElfAlTd46MfQUGk2stCVEGBQWSdCciDg5VDAVYKoEiDQ0iBwxGcj9RDw8+qHIzebc2DJJQJK6qiKVyIQAh+QQJBwAQACwAAAAAGAAYAAAFmSAkjiS0LGWaBiRBtCoZCKgoCCMB1DF0sz6cCQDo5W62l28XAyZFpyECBv3lnCbhUqHMIo0Qg4Jbmn1jRCa4iV27TzfXGjEecOFWMN1OdvvfPGUuXSoKBw6EXokrAwcHRVU0UAeEBANAAAmUI1gNDyhjJgUHLW0iDg8FIqOnBQZrDA9TELE2rEYIDw4jta2LMpCrqld/YQpgIQAh+QQJBwAQACwAAAAAGAAYAAAFmyAkjiS0LGWaBiRBkKw6BgIqCsJcyyMe4yJajhcEml5H26o1PN2QQd3uFiv2AADlAgflIbDdZLgkABOJgep5LfWty4p4zeU+w+XsvJWXliEKDwdEBgMKYQ4PDw1qK3EDCCMAiQ5BCV0LCj+FSDQkgCgGBiYHAy2MIgoMghAHqw4HAGsNDEMFBTekdgwKI7aRB2MwkL2rVHoQoWchACH5BAkHABAALAAAAAAYABgAAAWWICSOJLQsZZoGJEGQrDoGAioKwlzLIx7jIlqOFwSaXkfbqjU83ZBB3e4WK0qrCxyU55peid0qcUwuixyNx6PhILsAcAJazXYj4lvz2MkLiFsHDAlEcABKZwwMBX8pBgoKQxAIigpBA1sLBj+PSDQkB4uSACYDlTMyBgWDEKVnl2QFBUigN61gBQYjtLV5JZ4jtlR6omMhACH5BAkHABAALAAAAAAYABgAAAWaICSOJLQsZZoGJEGQrDoGAioKwlzLIx7jIlqOFwSaXkdbidYanm7I4AjwYDh6saJuJ3JUG1mZi9srPA7EcRimJLrfJYWZUVC8TziXnEG3u/E+cIJaPAFrPQl1aQAIbRAGBZGHJQiMUQKRBkEKbQsAPZaEXQcslSYKmjMyAAdXj34ACkNEiUgDA5t+PAQHn6Ogjkuzry2DNwhuIQAh+QQFBwAQACwAAAAAGAAYAAAFnCAkjiS0LGVaBgBJEGSguo8zCsK4CPIsMg+ECCcKEH0ix6MwhJl4KiOp8UCdmrEbo6EoHpxF8A6aBBZ6vhf5dmAkkGr0CoWs21WGQ2FvsI9xC3l7B311fy93iWGKJQQOhHCAJQB6A3IqcWwJLU90i2FkUiMKlhBELEI6MwgDXRAGhQgAYD6tTqRFAJxpA6mvrqazSKJJhUWMpjlIIQA7')
+ center no-repeat;
+ }
+
+ .pdfViewer .page .loadingIcon.notVisible {
+ background: none;
+ }
+
+ .pdfViewer.enablePermissions .textLayer span {
+ -webkit-user-select: none !important;
+ -moz-user-select: none !important;
+ user-select: none !important;
+ cursor: not-allowed;
+ }
+
+ .pdfPresentationMode .pdfViewer {
+ padding-bottom: 0;
+ }
+
+ .pdfPresentationMode .spread {
+ margin: 0;
+ }
+
+ .pdfPresentationMode .pdfViewer .page {
+ margin: 0 auto;
+ border: 2px solid transparent;
+ }
+ }
diff --git a/src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.ts b/src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.ts
new file mode 100644
index 000000000..f9e7eb005
--- /dev/null
+++ b/src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.ts
@@ -0,0 +1,599 @@
+/**
+ * This file is taken and modified from https://github.com/VadimDez/ng2-pdf-viewer/blob/10.0.0/src/app/pdf-viewer/pdf-viewer.component.ts
+ * Created by vadimdez on 21/06/16.
+ */
+import {
+ Component,
+ Input,
+ Output,
+ ElementRef,
+ EventEmitter,
+ OnChanges,
+ SimpleChanges,
+ OnInit,
+ OnDestroy,
+ ViewChild,
+ AfterViewChecked,
+ NgZone,
+} from '@angular/core'
+import { from, fromEvent, Subject } from 'rxjs'
+import { debounceTime, filter, takeUntil } from 'rxjs/operators'
+import * as PDFJS from 'pdfjs-dist'
+import * as PDFJSViewer from 'pdfjs-dist/web/pdf_viewer'
+
+import { createEventBus } from './utils/event-bus-utils'
+
+import type {
+ PDFSource,
+ PDFPageProxy,
+ PDFProgressData,
+ PDFDocumentProxy,
+ PDFDocumentLoadingTask,
+ PDFViewerOptions,
+ ZoomScale,
+} from './typings'
+import { PDFSinglePageViewer } from 'pdfjs-dist/web/pdf_viewer'
+
+PDFJS['verbosity'] = PDFJS.VerbosityLevel.ERRORS
+
+// Yea this is a straight hack
+declare global {
+ interface WeakKeyTypes {
+ symbol: Object
+ }
+
+ type WeakKey = WeakKeyTypes[keyof WeakKeyTypes]
+}
+
+export enum RenderTextMode {
+ DISABLED,
+ ENABLED,
+ ENHANCED,
+}
+
+@Component({
+ selector: 'pngx-pdf-viewer',
+ templateUrl: './pdf-viewer.component.html',
+ styleUrls: ['./pdf-viewer.component.scss'],
+})
+export class PdfViewerComponent
+ implements OnChanges, OnInit, OnDestroy, AfterViewChecked
+{
+ static CSS_UNITS = 96.0 / 72.0
+ static BORDER_WIDTH = 9
+
+ @ViewChild('pdfViewerContainer')
+ pdfViewerContainer!: ElementRef
+
+ public eventBus!: PDFJSViewer.EventBus
+ public pdfLinkService!: PDFJSViewer.PDFLinkService
+ public pdfViewer!: PDFJSViewer.PDFViewer | PDFSinglePageViewer
+
+ private isVisible = false
+
+ private _cMapsUrl =
+ typeof PDFJS !== 'undefined'
+ ? `https://unpkg.com/pdfjs-dist@${(PDFJS as any).version}/cmaps/`
+ : null
+ private _imageResourcesPath =
+ typeof PDFJS !== 'undefined'
+ ? `https://unpkg.com/pdfjs-dist@${(PDFJS as any).version}/web/images/`
+ : undefined
+ private _renderText = true
+ private _renderTextMode: RenderTextMode = RenderTextMode.ENABLED
+ private _stickToPage = false
+ private _originalSize = true
+ private _pdf: PDFDocumentProxy | undefined
+ private _page = 1
+ private _zoom = 1
+ private _zoomScale: ZoomScale = '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 | null
+ private _latestScrolledPage!: number
+
+ private resizeTimeout: number | null = null
+ private pageScrollTimeout: number | null = null
+ private isInitialized = false
+ private loadingTask?: PDFDocumentLoadingTask | null
+ private destroy$ = new Subject()
+
+ @Output('after-load-complete') afterLoadComplete =
+ new EventEmitter()
+ @Output('page-rendered') pageRendered = new EventEmitter()
+ @Output('pages-initialized') pageInitialized = new EventEmitter()
+ @Output('text-layer-rendered') textLayerRendered =
+ new EventEmitter()
+ @Output('error') onError = new EventEmitter()
+ @Output('on-progress') onProgress = new EventEmitter()
+ @Output() pageChange: EventEmitter = new EventEmitter(true)
+ @Input() src?: string | Uint8Array | PDFSource
+
+ @Input('c-maps-url')
+ set cMapsUrl(cMapsUrl: string) {
+ this._cMapsUrl = cMapsUrl
+ }
+
+ @Input('page')
+ set page(_page: number | string | any) {
+ _page = parseInt(_page, 10) || 1
+ const originalPage = _page
+
+ if (this._pdf) {
+ _page = this.getValidPageNumber(_page)
+ }
+
+ this._page = _page
+ if (originalPage !== _page) {
+ this.pageChange.emit(_page)
+ }
+ }
+
+ @Input('render-text')
+ set renderText(renderText: boolean) {
+ this._renderText = renderText
+ }
+
+ @Input('render-text-mode')
+ set renderTextMode(renderTextMode: RenderTextMode) {
+ this._renderTextMode = renderTextMode
+ }
+
+ @Input('original-size')
+ set originalSize(originalSize: boolean) {
+ this._originalSize = originalSize
+ }
+
+ @Input('show-all')
+ set showAll(value: boolean) {
+ this._showAll = value
+ }
+
+ @Input('stick-to-page')
+ set stickToPage(value: boolean) {
+ this._stickToPage = value
+ }
+
+ @Input('zoom')
+ set zoom(value: number) {
+ if (value <= 0) {
+ return
+ }
+
+ this._zoom = value
+ }
+
+ get zoom() {
+ return this._zoom
+ }
+
+ @Input('zoom-scale')
+ set zoomScale(value: ZoomScale) {
+ this._zoomScale = value
+ }
+
+ get zoomScale() {
+ return this._zoomScale
+ }
+
+ @Input('rotation')
+ set rotation(value: number) {
+ if (!(typeof value === 'number' && value % 90 === 0)) {
+ console.warn('Invalid pages rotation angle.')
+ return
+ }
+
+ this._rotation = value
+ }
+
+ @Input('external-link-target')
+ set externalLinkTarget(value: string) {
+ this._externalLinkTarget = value
+ }
+
+ @Input('autoresize')
+ set autoresize(value: boolean) {
+ this._canAutoResize = Boolean(value)
+ }
+
+ @Input('fit-to-page')
+ set fitToPage(value: boolean) {
+ this._fitToPage = Boolean(value)
+ }
+
+ @Input('show-borders')
+ set showBorders(value: boolean) {
+ this._showBorders = Boolean(value)
+ }
+
+ static getLinkTarget(type: string) {
+ switch (type) {
+ case 'blank':
+ return (PDFJSViewer as any).LinkTarget.BLANK
+ case 'none':
+ return (PDFJSViewer as any).LinkTarget.NONE
+ case 'self':
+ return (PDFJSViewer as any).LinkTarget.SELF
+ case 'parent':
+ return (PDFJSViewer as any).LinkTarget.PARENT
+ case 'top':
+ return (PDFJSViewer as any).LinkTarget.TOP
+ }
+
+ return null
+ }
+
+ constructor(
+ private element: ElementRef,
+ private ngZone: NgZone
+ ) {
+ PDFJS.GlobalWorkerOptions['workerSrc'] = '/assets/js/pdf.worker.min.js'
+ }
+
+ ngAfterViewChecked(): void {
+ if (this.isInitialized) {
+ return
+ }
+
+ const offset = this.pdfViewerContainer.nativeElement.offsetParent
+
+ if (this.isVisible === true && offset == null) {
+ this.isVisible = false
+ return
+ }
+
+ if (this.isVisible === false && offset != null) {
+ this.isVisible = true
+
+ setTimeout(() => {
+ this.initialize()
+ this.ngOnChanges({ src: this.src } as any)
+ })
+ }
+ }
+
+ ngOnInit() {
+ this.initialize()
+ this.setupResizeListener()
+ }
+
+ ngOnDestroy() {
+ this.clear()
+ this.destroy$.next()
+ this.loadingTask = null
+ }
+
+ ngOnChanges(changes: SimpleChanges) {
+ if (!this.isVisible) {
+ return
+ }
+
+ if ('src' in changes) {
+ this.loadPDF()
+ } else if (this._pdf) {
+ if ('renderText' in changes || 'showAll' in changes) {
+ this.setupViewer()
+ this.resetPdfDocument()
+ }
+ if ('page' in changes) {
+ const { page } = changes
+ if (page.currentValue === this._latestScrolledPage) {
+ return
+ }
+
+ // New form of page changing: The viewer will now jump to the specified page when it is changed.
+ // This behavior is introduced by using the PDFSinglePageViewer
+ this.pdfViewer.scrollPageIntoView({ pageNumber: this._page })
+ }
+
+ this.update()
+ }
+ }
+
+ public updateSize() {
+ from(
+ this._pdf!.getPage(
+ this.pdfViewer.currentPageNumber
+ ) as unknown as Promise
+ )
+ .pipe(takeUntil(this.destroy$))
+ .subscribe({
+ next: (page: PDFPageProxy) => {
+ 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
+
+ // Scale the document when it shouldn't be in original size or doesn't fit into the viewport
+ if (
+ !this._originalSize ||
+ (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
+ }
+
+ setTimeout(() => {
+ this.pdfViewer.currentScale = scale
+ })
+ },
+ })
+ }
+
+ public clear() {
+ if (this.loadingTask && !this.loadingTask.destroyed) {
+ this.loadingTask.destroy()
+ }
+
+ if (this._pdf) {
+ this._latestScrolledPage = 0
+ this._pdf.destroy()
+ this._pdf = undefined
+ }
+ }
+
+ private getPDFLinkServiceConfig() {
+ const linkTarget = PdfViewerComponent.getLinkTarget(
+ this._externalLinkTarget
+ )
+
+ if (linkTarget) {
+ return { externalLinkTarget: linkTarget }
+ }
+
+ return {}
+ }
+
+ private initEventBus() {
+ this.eventBus = createEventBus(PDFJSViewer, this.destroy$)
+
+ fromEvent(this.eventBus, 'pagerendered')
+ .pipe(takeUntil(this.destroy$))
+ .subscribe((event) => {
+ this.pageRendered.emit(event)
+ })
+
+ fromEvent(this.eventBus, 'pagesinit')
+ .pipe(takeUntil(this.destroy$))
+ .subscribe((event) => {
+ this.pageInitialized.emit(event)
+ })
+
+ fromEvent(this.eventBus, 'pagechanging')
+ .pipe(takeUntil(this.destroy$))
+ .subscribe(({ pageNumber }: any) => {
+ if (this.pageScrollTimeout) {
+ clearTimeout(this.pageScrollTimeout)
+ }
+
+ this.pageScrollTimeout = window.setTimeout(() => {
+ this._latestScrolledPage = pageNumber
+ this.pageChange.emit(pageNumber)
+ }, 100)
+ })
+
+ fromEvent(this.eventBus, 'textlayerrendered')
+ .pipe(takeUntil(this.destroy$))
+ .subscribe((event) => {
+ this.textLayerRendered.emit(event)
+ })
+ }
+
+ private initPDFServices() {
+ this.pdfLinkService = new PDFJSViewer.PDFLinkService({
+ eventBus: this.eventBus,
+ ...this.getPDFLinkServiceConfig(),
+ })
+ }
+
+ private getPDFOptions(): PDFViewerOptions {
+ return {
+ eventBus: this.eventBus,
+ container: this.element.nativeElement.querySelector('div')!,
+ removePageBorders: !this._showBorders,
+ linkService: this.pdfLinkService,
+ textLayerMode: this._renderText
+ ? this._renderTextMode
+ : RenderTextMode.DISABLED,
+ imageResourcesPath: this._imageResourcesPath,
+ }
+ }
+
+ private setupViewer() {
+ PDFJS['disableTextLayer'] = !this._renderText
+
+ this.initPDFServices()
+
+ if (this._showAll) {
+ this.pdfViewer = new PDFJSViewer.PDFViewer(this.getPDFOptions())
+ } else {
+ this.pdfViewer = new PDFJSViewer.PDFSinglePageViewer(this.getPDFOptions())
+ }
+ this.pdfLinkService.setViewer(this.pdfViewer)
+
+ this.pdfViewer._currentPageNumber = this._page
+ }
+
+ private getValidPageNumber(page: number): number {
+ if (page < 1) {
+ return 1
+ }
+
+ if (page > this._pdf!.numPages) {
+ return this._pdf!.numPages
+ }
+
+ return page
+ }
+
+ private getDocumentParams() {
+ const srcType = typeof this.src
+
+ if (!this._cMapsUrl) {
+ return this.src
+ }
+
+ const params: any = {
+ cMapUrl: this._cMapsUrl,
+ cMapPacked: true,
+ enableXfa: true,
+ }
+
+ if (srcType === 'string') {
+ params.url = this.src
+ } else if (srcType === 'object') {
+ if ((this.src as any).byteLength !== undefined) {
+ params.data = this.src
+ } else {
+ Object.assign(params, this.src)
+ }
+ }
+
+ return params
+ }
+
+ private loadPDF() {
+ if (!this.src) {
+ return
+ }
+
+ if (this.lastLoaded === this.src) {
+ this.update()
+ return
+ }
+
+ this.clear()
+
+ this.setupViewer()
+
+ this.loadingTask = PDFJS.getDocument(this.getDocumentParams())
+
+ this.loadingTask!.onProgress = (progressData: PDFProgressData) => {
+ this.onProgress.emit(progressData)
+ }
+
+ const src = this.src
+
+ from(this.loadingTask!.promise as Promise)
+ .pipe(takeUntil(this.destroy$))
+ .subscribe({
+ next: (pdf) => {
+ this._pdf = pdf
+ this.lastLoaded = src
+
+ this.afterLoadComplete.emit(pdf)
+ this.resetPdfDocument()
+
+ this.update()
+ },
+ error: (error) => {
+ this.lastLoaded = null
+ this.onError.emit(error)
+ },
+ })
+ }
+
+ private update() {
+ this.page = this._page
+
+ this.render()
+ }
+
+ private render() {
+ this._page = this.getValidPageNumber(this._page)
+
+ if (
+ this._rotation !== 0 ||
+ this.pdfViewer.pagesRotation !== this._rotation
+ ) {
+ setTimeout(() => {
+ this.pdfViewer.pagesRotation = this._rotation
+ })
+ }
+
+ if (this._stickToPage) {
+ setTimeout(() => {
+ this.pdfViewer.currentPageNumber = this._page
+ })
+ }
+
+ 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
+
+ if (
+ pdfContainerHeight === 0 ||
+ viewportHeight === 0 ||
+ pdfContainerWidth === 0 ||
+ viewportWidth === 0
+ ) {
+ return 1
+ }
+
+ let ratio = 1
+ switch (this._zoomScale) {
+ case 'page-fit':
+ ratio = Math.min(
+ pdfContainerHeight / viewportHeight,
+ pdfContainerWidth / viewportWidth
+ )
+ break
+ case 'page-height':
+ ratio = pdfContainerHeight / viewportHeight
+ break
+ case 'page-width':
+ default:
+ ratio = pdfContainerWidth / viewportWidth
+ break
+ }
+
+ return (this._zoom * ratio) / PdfViewerComponent.CSS_UNITS
+ }
+
+ private resetPdfDocument() {
+ this.pdfLinkService.setDocument(this._pdf, null)
+ this.pdfViewer.setDocument(this._pdf!)
+ }
+
+ private initialize(): void {
+ if (!this.isVisible) {
+ return
+ }
+
+ this.isInitialized = true
+ this.initEventBus()
+ this.setupViewer()
+ }
+
+ private setupResizeListener(): void {
+ this.ngZone.runOutsideAngular(() => {
+ fromEvent(window, 'resize')
+ .pipe(
+ debounceTime(100),
+ filter(() => this._canAutoResize && !!this._pdf),
+ takeUntil(this.destroy$)
+ )
+ .subscribe(() => {
+ this.updateSize()
+ })
+ })
+ }
+}
diff --git a/src-ui/src/app/components/common/pdf-viewer/typings.ts b/src-ui/src/app/components/common/pdf-viewer/typings.ts
new file mode 100644
index 000000000..8dad45f30
--- /dev/null
+++ b/src-ui/src/app/components/common/pdf-viewer/typings.ts
@@ -0,0 +1,17 @@
+export type PDFPageProxy =
+ import('pdfjs-dist/types/src/display/api').PDFPageProxy
+export type PDFSource =
+ import('pdfjs-dist/types/src/display/api').DocumentInitParameters
+export type PDFDocumentProxy =
+ import('pdfjs-dist/types/src/display/api').PDFDocumentProxy
+export type PDFDocumentLoadingTask =
+ import('pdfjs-dist/types/src/display/api').PDFDocumentLoadingTask
+export type PDFViewerOptions =
+ import('pdfjs-dist/types/web/pdf_viewer').PDFViewerOptions
+
+export interface PDFProgressData {
+ loaded: number
+ total: number
+}
+
+export type ZoomScale = 'page-height' | 'page-fit' | 'page-width'
diff --git a/src-ui/src/app/components/common/pdf-viewer/utils/event-bus-utils.ts b/src-ui/src/app/components/common/pdf-viewer/utils/event-bus-utils.ts
new file mode 100644
index 000000000..8ae501169
--- /dev/null
+++ b/src-ui/src/app/components/common/pdf-viewer/utils/event-bus-utils.ts
@@ -0,0 +1,182 @@
+/**
+ * This file is taken and modified from https://github.com/VadimDez/ng2-pdf-viewer/blob/10.0.0/src/app/pdf-viewer/utils/event-bus-utils.ts
+ * Created by vadimdez on 21/06/16.
+ */
+import { fromEvent, Subject } from 'rxjs'
+import { takeUntil } from 'rxjs/operators'
+
+import type { EventBus } from 'pdfjs-dist/web/pdf_viewer'
+
+// interface EventBus {
+// on(eventName: string, listener: Function): void;
+// off(eventName: string, listener: Function): void;
+// _listeners: any;
+// dispatch(eventName: string, data: Object): void;
+// _on(eventName: any, listener: any, options?: null): void;
+// _off(eventName: any, listener: any, options?: null): void;
+// }
+
+export function createEventBus(pdfJsViewer: any, destroy$: Subject) {
+ const globalEventBus: EventBus = new pdfJsViewer.EventBus()
+ attachDOMEventsToEventBus(globalEventBus, destroy$)
+ return globalEventBus
+}
+
+function attachDOMEventsToEventBus(
+ eventBus: EventBus,
+ destroy$: Subject
+): void {
+ fromEvent(eventBus, 'documentload')
+ .pipe(takeUntil(destroy$))
+ .subscribe(() => {
+ const event = document.createEvent('CustomEvent')
+ event.initCustomEvent('documentload', true, true, {})
+ window.dispatchEvent(event)
+ })
+
+ fromEvent(eventBus, 'pagerendered')
+ .pipe(takeUntil(destroy$))
+ .subscribe(({ pageNumber, cssTransform, source }: any) => {
+ const event = document.createEvent('CustomEvent')
+ event.initCustomEvent('pagerendered', true, true, {
+ pageNumber,
+ cssTransform,
+ })
+ source.div.dispatchEvent(event)
+ })
+
+ fromEvent(eventBus, 'textlayerrendered')
+ .pipe(takeUntil(destroy$))
+ .subscribe(({ pageNumber, source }: any) => {
+ const event = document.createEvent('CustomEvent')
+ event.initCustomEvent('textlayerrendered', true, true, { pageNumber })
+ source.textLayerDiv?.dispatchEvent(event)
+ })
+
+ fromEvent(eventBus, 'pagechanging')
+ .pipe(takeUntil(destroy$))
+ .subscribe(({ pageNumber, source }: any) => {
+ const event = document.createEvent('UIEvents') as any
+ event.initEvent('pagechanging', true, true)
+ /* tslint:disable:no-string-literal */
+ event['pageNumber'] = pageNumber
+ source.container.dispatchEvent(event)
+ })
+
+ fromEvent(eventBus, 'pagesinit')
+ .pipe(takeUntil(destroy$))
+ .subscribe(({ source }: any) => {
+ const event = document.createEvent('CustomEvent')
+ event.initCustomEvent('pagesinit', true, true, null)
+ source.container.dispatchEvent(event)
+ })
+
+ fromEvent(eventBus, 'pagesloaded')
+ .pipe(takeUntil(destroy$))
+ .subscribe(({ pagesCount, source }: any) => {
+ const event = document.createEvent('CustomEvent')
+ event.initCustomEvent('pagesloaded', true, true, { pagesCount })
+ source.container.dispatchEvent(event)
+ })
+
+ fromEvent(eventBus, 'scalechange')
+ .pipe(takeUntil(destroy$))
+ .subscribe(({ scale, presetValue, source }: any) => {
+ const event = document.createEvent('UIEvents') as any
+ event.initEvent('scalechange', true, true)
+ /* tslint:disable:no-string-literal */
+ event['scale'] = scale
+ /* tslint:disable:no-string-literal */
+ event['presetValue'] = presetValue
+ source.container.dispatchEvent(event)
+ })
+
+ fromEvent(eventBus, 'updateviewarea')
+ .pipe(takeUntil(destroy$))
+ .subscribe(({ location, source }: any) => {
+ const event = document.createEvent('UIEvents') as any
+ event.initEvent('updateviewarea', true, true)
+ event['location'] = location
+ source.container.dispatchEvent(event)
+ })
+
+ fromEvent(eventBus, 'find')
+ .pipe(takeUntil(destroy$))
+ .subscribe(
+ ({
+ source,
+ type,
+ query,
+ phraseSearch,
+ caseSensitive,
+ highlightAll,
+ findPrevious,
+ }: any) => {
+ if (source === window) {
+ return // event comes from FirefoxCom, no need to replicate
+ }
+ const event = document.createEvent('CustomEvent')
+ event.initCustomEvent('find' + type, true, true, {
+ query,
+ phraseSearch,
+ caseSensitive,
+ highlightAll,
+ findPrevious,
+ })
+ window.dispatchEvent(event)
+ }
+ )
+
+ fromEvent(eventBus, 'attachmentsloaded')
+ .pipe(takeUntil(destroy$))
+ .subscribe(({ attachmentsCount, source }: any) => {
+ const event = document.createEvent('CustomEvent')
+ event.initCustomEvent('attachmentsloaded', true, true, {
+ attachmentsCount,
+ })
+ source.container.dispatchEvent(event)
+ })
+
+ fromEvent(eventBus, 'sidebarviewchanged')
+ .pipe(takeUntil(destroy$))
+ .subscribe(({ view, source }: any) => {
+ const event = document.createEvent('CustomEvent')
+ event.initCustomEvent('sidebarviewchanged', true, true, { view })
+ source.outerContainer.dispatchEvent(event)
+ })
+
+ fromEvent(eventBus, 'pagemode')
+ .pipe(takeUntil(destroy$))
+ .subscribe(({ mode, source }: any) => {
+ const event = document.createEvent('CustomEvent')
+ event.initCustomEvent('pagemode', true, true, { mode })
+ source.pdfViewer.container.dispatchEvent(event)
+ })
+
+ fromEvent(eventBus, 'namedaction')
+ .pipe(takeUntil(destroy$))
+ .subscribe(({ action, source }: any) => {
+ const event = document.createEvent('CustomEvent')
+ event.initCustomEvent('namedaction', true, true, { action })
+ source.pdfViewer.container.dispatchEvent(event)
+ })
+
+ fromEvent(eventBus, 'presentationmodechanged')
+ .pipe(takeUntil(destroy$))
+ .subscribe(({ active, switchInProgress }: any) => {
+ const event = document.createEvent('CustomEvent')
+ event.initCustomEvent('presentationmodechanged', true, true, {
+ active,
+ switchInProgress,
+ })
+ window.dispatchEvent(event)
+ })
+
+ fromEvent(eventBus, 'outlineloaded')
+ .pipe(takeUntil(destroy$))
+ .subscribe(({ outlineCount, source }: any) => {
+ const event = document.createEvent('CustomEvent')
+ event.initCustomEvent('outlineloaded', true, true, { outlineCount })
+ source.container.dispatchEvent(event)
+ })
+}
diff --git a/src-ui/src/app/components/document-detail/document-detail.component.html b/src-ui/src/app/components/document-detail/document-detail.component.html
index 19ee955ae..8b50e6f2e 100644
--- a/src-ui/src/app/components/document-detail/document-detail.component.html
+++ b/src-ui/src/app/components/document-detail/document-detail.component.html
@@ -1,9 +1,20 @@
-
+
+
+
+
+
+
+
+