@vaadin-component-factory/vcf-pdf-viewer 2.1.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vaadin-component-factory/vcf-pdf-viewer",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Polymer element providing pdf viewer",
|
|
5
5
|
"main": "vcf-pdf-viewer.js",
|
|
6
6
|
"repository": {
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"@vaadin/button": "^24.1.1",
|
|
29
29
|
"@vaadin/component-base": "^24.1.1",
|
|
30
30
|
"@vaadin/icon": "^24.1.1",
|
|
31
|
+
"@vaadin/icons": "^24.1.1",
|
|
31
32
|
"@vaadin/item": "^24.1.1",
|
|
32
33
|
"@vaadin/list-box": "^24.1.1",
|
|
33
34
|
"@vaadin/select": "^24.1.1",
|
package/src/vcf-pdf-viewer.js
CHANGED
|
@@ -7,6 +7,7 @@ import '@vaadin/select';
|
|
|
7
7
|
import '@vaadin/item';
|
|
8
8
|
import '@vaadin/button';
|
|
9
9
|
import '@vaadin/icon';
|
|
10
|
+
import '@vaadin/icons';
|
|
10
11
|
import '@vaadin/tooltip';
|
|
11
12
|
|
|
12
13
|
import * as pdfjsLib from '../pdfjs/dist/pdf';
|
|
@@ -56,8 +57,8 @@ class PdfViewerElement extends
|
|
|
56
57
|
[part~="toolbar"] #totalPages,
|
|
57
58
|
[part~="toolbar"] #previousPage,
|
|
58
59
|
[part~="toolbar"] #nextPage,
|
|
59
|
-
[part~="toolbar"]
|
|
60
|
-
[part~="toolbar"]
|
|
60
|
+
[part~="toolbar"] #zoom,
|
|
61
|
+
[part~="toolbar"] #sidebarToggle {
|
|
61
62
|
display: none;
|
|
62
63
|
}
|
|
63
64
|
|
|
@@ -66,8 +67,8 @@ class PdfViewerElement extends
|
|
|
66
67
|
[part~="toolbar"].ready #totalPages,
|
|
67
68
|
[part~="toolbar"].ready #previousPage,
|
|
68
69
|
[part~="toolbar"].ready #nextPage,
|
|
69
|
-
[part~="toolbar"].ready
|
|
70
|
-
[part~="toolbar"].ready
|
|
70
|
+
[part~="toolbar"].ready #zoom,
|
|
71
|
+
[part~="toolbar"].ready #sidebarToggle {
|
|
71
72
|
display: inherit;
|
|
72
73
|
}
|
|
73
74
|
|
|
@@ -195,7 +196,7 @@ class PdfViewerElement extends
|
|
|
195
196
|
align-items: baseline;
|
|
196
197
|
}
|
|
197
198
|
|
|
198
|
-
#currentPage {
|
|
199
|
+
::slotted(#currentPage) {
|
|
199
200
|
align-self: baseline;
|
|
200
201
|
}
|
|
201
202
|
|
|
@@ -233,7 +234,7 @@ class PdfViewerElement extends
|
|
|
233
234
|
background-color: rgba(0, 0, 0, 0.15);
|
|
234
235
|
}
|
|
235
236
|
|
|
236
|
-
#sidebarToggle {
|
|
237
|
+
::slotted(#sidebarToggle) {
|
|
237
238
|
margin-left: -10px;
|
|
238
239
|
margin-right: 15px;
|
|
239
240
|
border: 2px solid;
|
|
@@ -241,12 +242,12 @@ class PdfViewerElement extends
|
|
|
241
242
|
width: 40px;
|
|
242
243
|
}
|
|
243
244
|
|
|
244
|
-
#nextPage, #previousPage {
|
|
245
|
+
::slotted(#nextPage), ::slotted(#previousPage) {
|
|
245
246
|
width: 30px;
|
|
246
247
|
margin: 0;
|
|
247
248
|
}
|
|
248
|
-
|
|
249
|
-
[part~="toolbar"].ready
|
|
249
|
+
|
|
250
|
+
[part~="toolbar"].ready ::slotted(.toolbar-zoom.hide-zoom) {
|
|
250
251
|
display: none;
|
|
251
252
|
}
|
|
252
253
|
|
|
@@ -259,26 +260,16 @@ class PdfViewerElement extends
|
|
|
259
260
|
</div>
|
|
260
261
|
</div>
|
|
261
262
|
<div id="mainContainer" part="main-container">
|
|
262
|
-
<div id="toolbar" part="toolbar">
|
|
263
|
-
<
|
|
264
|
-
<vaadin-icon part="toggle-button-icon" slot="prefix"></vaadin-icon>
|
|
265
|
-
<vaadin-tooltip slot="tooltip" text="{{sidebarToggleTooltip}}"></vaadin-tooltip>
|
|
266
|
-
</vaadin-button>
|
|
263
|
+
<div id="toolbar" part="toolbar">
|
|
264
|
+
<slot name="sidebar-toggle-button-slot"></slot>
|
|
267
265
|
<span id="title" part="toolbar-text toolbar-title">{{__title}}</span>
|
|
268
|
-
<
|
|
269
|
-
</vaadin-select>
|
|
266
|
+
<slot name="toolbar-zoom-slot"></slot>
|
|
270
267
|
<div part="toolbar-pages">
|
|
271
|
-
<
|
|
268
|
+
<slot name="toolbar-current-page-slot"></slot>
|
|
272
269
|
<span id="pageSeparator" part="toolbar-text toolbar-page-separator">/</span>
|
|
273
270
|
<span id="totalPages" part="toolbar-text toolbar-total-pages">{{__totalPages}}</span>
|
|
274
|
-
<
|
|
275
|
-
|
|
276
|
-
<vaadin-tooltip slot="tooltip" text="{{previousPageTooltip}}"></vaadin-tooltip>
|
|
277
|
-
</vaadin-button>
|
|
278
|
-
<vaadin-button id="nextPage" part="toolbar-button toolbar-button-next-page" theme="icon" on-click="__nextPage" aria-label="Next page">
|
|
279
|
-
<vaadin-icon part="next-page-button-icon" slot="prefix"></vaadin-icon>
|
|
280
|
-
<vaadin-tooltip slot="tooltip" text="{{nextPageTooltip}}"></vaadin-tooltip>
|
|
281
|
-
</vaadin-button>
|
|
271
|
+
<slot name="previous-page-button-slot"></slot>
|
|
272
|
+
<slot name="next-page-button-slot"></slot>
|
|
282
273
|
</div>
|
|
283
274
|
<slot></slot>
|
|
284
275
|
</div>
|
|
@@ -287,8 +278,7 @@ class PdfViewerElement extends
|
|
|
287
278
|
<div id="viewer" part="viewer"></div>
|
|
288
279
|
</div>
|
|
289
280
|
</div>
|
|
290
|
-
|
|
291
|
-
</div>
|
|
281
|
+
</div>
|
|
292
282
|
`;
|
|
293
283
|
}
|
|
294
284
|
|
|
@@ -297,7 +287,7 @@ class PdfViewerElement extends
|
|
|
297
287
|
}
|
|
298
288
|
|
|
299
289
|
static get version() {
|
|
300
|
-
return '
|
|
290
|
+
return '3.0.0';
|
|
301
291
|
}
|
|
302
292
|
|
|
303
293
|
static get properties() {
|
|
@@ -357,8 +347,7 @@ class PdfViewerElement extends
|
|
|
357
347
|
*/
|
|
358
348
|
zoom: {
|
|
359
349
|
type: String,
|
|
360
|
-
value: 'auto'
|
|
361
|
-
observer: '__zoomChanged'
|
|
350
|
+
value: 'auto'
|
|
362
351
|
},
|
|
363
352
|
/**
|
|
364
353
|
* The current page visible viewed right now
|
|
@@ -436,6 +425,7 @@ class PdfViewerElement extends
|
|
|
436
425
|
type: Boolean,
|
|
437
426
|
value: false
|
|
438
427
|
},
|
|
428
|
+
|
|
439
429
|
__zoomItems: {
|
|
440
430
|
computed: '__computeZoomItems(autoZoomOptionLabel, fitZoomOptionLabel)'
|
|
441
431
|
},
|
|
@@ -466,6 +456,104 @@ class PdfViewerElement extends
|
|
|
466
456
|
};
|
|
467
457
|
}
|
|
468
458
|
|
|
459
|
+
__createToolbarButton() {
|
|
460
|
+
const icon = document.createElement('vaadin-icon');
|
|
461
|
+
icon.setAttribute('slot', 'prefix');
|
|
462
|
+
|
|
463
|
+
const tooltip = document.createElement('vaadin-tooltip');
|
|
464
|
+
tooltip.setAttribute('slot', 'tooltip');
|
|
465
|
+
|
|
466
|
+
const button = document.createElement('vaadin-button');
|
|
467
|
+
button.classList.add('toolbar-button');
|
|
468
|
+
button.setAttribute('theme', 'icon');
|
|
469
|
+
|
|
470
|
+
button.appendChild(icon);
|
|
471
|
+
button.appendChild(tooltip);
|
|
472
|
+
return button;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Adds toggle button to the toolbar slot named "sidebar-toggle-button-slot".
|
|
477
|
+
*/
|
|
478
|
+
_createSideBarToggleButton() {
|
|
479
|
+
const button = this.__createToolbarButton();
|
|
480
|
+
const icon = button.querySelector('vaadin-icon');
|
|
481
|
+
icon.classList.add('toggle-button-icon')
|
|
482
|
+
button.querySelector('vaadin-tooltip').setAttribute('text', this.sidebarToggleTooltip);
|
|
483
|
+
button.setAttribute('slot', 'sidebar-toggle-button-slot');
|
|
484
|
+
button.setAttribute('id','sidebarToggle');
|
|
485
|
+
button.setAttribute('aria-label', 'Sidebar toggle');
|
|
486
|
+
button.addEventListener('click', () => {
|
|
487
|
+
this.__toogleSidebar();
|
|
488
|
+
if(this.$.outerContainer.classList.contains('sidebarOpen')) {
|
|
489
|
+
icon.classList.add('sidebarOpen');
|
|
490
|
+
} else {
|
|
491
|
+
icon.classList.remove('sidebarOpen');
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
this.appendChild(button);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Adds previous page button to the toolbar slot named "previous-page-button-slot".
|
|
499
|
+
*/
|
|
500
|
+
_createPreviousPageButton(){
|
|
501
|
+
const button = this.__createToolbarButton();
|
|
502
|
+
button.querySelector('vaadin-icon').classList.add('previous-page-button-icon')
|
|
503
|
+
button.querySelector('vaadin-tooltip').setAttribute('text', this.previousPageTooltip);
|
|
504
|
+
button.setAttribute('slot', 'previous-page-button-slot');
|
|
505
|
+
button.setAttribute('id', 'previousPage');
|
|
506
|
+
button.setAttribute('aria-label', 'Previous page');
|
|
507
|
+
button.addEventListener('click', () => this.__previousPage());
|
|
508
|
+
this.appendChild(button);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Adds next page button to the toolbar slot named "next-page-button-slot".
|
|
513
|
+
*/
|
|
514
|
+
_createNextPageButton() {
|
|
515
|
+
const button = this.__createToolbarButton();
|
|
516
|
+
button.querySelector('vaadin-icon').classList.add('next-page-button-icon')
|
|
517
|
+
button.querySelector('vaadin-tooltip').setAttribute('text', this.nextPageTooltip);
|
|
518
|
+
button.setAttribute('slot', 'next-page-button-slot');
|
|
519
|
+
button.setAttribute('id', 'nextPage');
|
|
520
|
+
button.setAttribute('aria-label', 'Next page');
|
|
521
|
+
button.addEventListener('click', () => this.__nextPage());
|
|
522
|
+
this.appendChild(button);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Adds current page text field to the toolbar slot named "toolbar-current-page-slot".
|
|
527
|
+
*/
|
|
528
|
+
_createCurrentPageTextField() {
|
|
529
|
+
const textField = document.createElement('vaadin-text-field');
|
|
530
|
+
textField.setAttribute('slot', 'toolbar-current-page-slot');
|
|
531
|
+
textField.setAttribute('id', 'currentPage');
|
|
532
|
+
textField.classList.add('toolbar-current-page');
|
|
533
|
+
textField.setAttribute('value', this.currentPage);
|
|
534
|
+
textField.addEventListener('change', () => this.__pageChange());
|
|
535
|
+
this.appendChild(textField);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Adds zoom select to the toolbar slot named "toolbar-zoom-slot".
|
|
540
|
+
*/
|
|
541
|
+
_createZoomSelect() {
|
|
542
|
+
const select = document.createElement('vaadin-select');
|
|
543
|
+
select.setAttribute('slot', 'toolbar-zoom-slot');
|
|
544
|
+
select.setAttribute('id', 'zoom');
|
|
545
|
+
select.classList.add('toolbar-zoom');
|
|
546
|
+
select.setAttribute('value', this.zoom);
|
|
547
|
+
select.items = this.__zoomItems;
|
|
548
|
+
select.addEventListener('value-changed', (e) => this.__zoomChanged(e.detail.value));
|
|
549
|
+
if(this.hideZoom) {
|
|
550
|
+
select.classList.add('hide-zoom');
|
|
551
|
+
} else {
|
|
552
|
+
select.classList.remove('hide-zoom');
|
|
553
|
+
}
|
|
554
|
+
this.appendChild(select);
|
|
555
|
+
}
|
|
556
|
+
|
|
469
557
|
__computeZoomItems(autoZoomOptionLabel, fitZoomOptionLabel) {
|
|
470
558
|
return [
|
|
471
559
|
{ label: autoZoomOptionLabel, value:'auto' },
|
|
@@ -483,8 +571,7 @@ class PdfViewerElement extends
|
|
|
483
571
|
|
|
484
572
|
static get observers() {
|
|
485
573
|
return [
|
|
486
|
-
'__setTitle(__pdfTitle, __filename)'
|
|
487
|
-
'__updateZoomVisibility()'
|
|
574
|
+
'__setTitle(__pdfTitle, __filename)'
|
|
488
575
|
];
|
|
489
576
|
}
|
|
490
577
|
|
|
@@ -504,16 +591,19 @@ class PdfViewerElement extends
|
|
|
504
591
|
}
|
|
505
592
|
}
|
|
506
593
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
594
|
+
_addToolbarButtons() {
|
|
595
|
+
this._createSideBarToggleButton();
|
|
596
|
+
this._createZoomSelect();
|
|
597
|
+
this._createCurrentPageTextField();
|
|
598
|
+
this._createPreviousPageButton();
|
|
599
|
+
this._createNextPageButton();
|
|
513
600
|
}
|
|
514
601
|
|
|
515
602
|
ready() {
|
|
516
603
|
super.ready();
|
|
604
|
+
|
|
605
|
+
this._addToolbarButtons();
|
|
606
|
+
|
|
517
607
|
this.$.viewerContainer.addEventListener('focus', e => this.__setFocused(true), true);
|
|
518
608
|
this.$.viewerContainer.addEventListener('blur', e => this.__setFocused(false), true);
|
|
519
609
|
this.$.viewerContainer.addEventListener('mousedown', e => {
|
|
@@ -577,6 +667,7 @@ class PdfViewerElement extends
|
|
|
577
667
|
if(this.__thumbnailViewer && this.__thumbnailViewer.renderingQueue.isThumbnailViewEnabled){
|
|
578
668
|
this.__thumbnailViewer.scrollThumbnailIntoView(this.currentPage);
|
|
579
669
|
}
|
|
670
|
+
this.querySelector('#currentPage').value = this.currentPage;
|
|
580
671
|
});
|
|
581
672
|
|
|
582
673
|
this.__resizeObserver = new ResizeObserver(() => {
|
|
@@ -712,8 +803,8 @@ class PdfViewerElement extends
|
|
|
712
803
|
}
|
|
713
804
|
|
|
714
805
|
__updatePageNumberStates() {
|
|
715
|
-
this
|
|
716
|
-
this
|
|
806
|
+
this.querySelector('#previousPage').disabled = (this.currentPage === "1");
|
|
807
|
+
this.querySelector('#nextPage').disabled = (this.currentPage === "" + this.__totalPages);
|
|
717
808
|
}
|
|
718
809
|
|
|
719
810
|
__zoomChanged(value) {
|
|
@@ -731,10 +822,11 @@ class PdfViewerElement extends
|
|
|
731
822
|
}
|
|
732
823
|
|
|
733
824
|
__pageChange(event) {
|
|
734
|
-
|
|
825
|
+
const currentPageValue = this.querySelector('#currentPage').value;
|
|
826
|
+
let pageNumber = parseInt(currentPageValue, 10);
|
|
735
827
|
if (isNaN(pageNumber)) {
|
|
736
828
|
pageNumber = this.__viewer.currentPageNumber;
|
|
737
|
-
this
|
|
829
|
+
this.querySelector('#currentPage').value = "" + pageNumber;
|
|
738
830
|
}
|
|
739
831
|
if (pageNumber < 1) {
|
|
740
832
|
pageNumber = 1;
|
|
@@ -747,7 +839,7 @@ class PdfViewerElement extends
|
|
|
747
839
|
|
|
748
840
|
setCurrentPage(value) {
|
|
749
841
|
if (value != undefined) {
|
|
750
|
-
this
|
|
842
|
+
this.querySelector('#currentPage').value = "" + value;
|
|
751
843
|
}
|
|
752
844
|
this.__pageChange();
|
|
753
845
|
}
|
|
@@ -13,7 +13,7 @@ import '@vaadin/item/theme/lumo/vaadin-item-styles.js';
|
|
|
13
13
|
registerStyles(
|
|
14
14
|
'vcf-pdf-viewer',
|
|
15
15
|
css`
|
|
16
|
-
|
|
16
|
+
:host {
|
|
17
17
|
background-color: var(--lumo-base-color);
|
|
18
18
|
border: 1px solid var(--lumo-contrast-10pct);
|
|
19
19
|
border-radius: var(--lumo-border-radius, var(--lumo-border-radius-m));
|
|
@@ -47,7 +47,7 @@ registerStyles(
|
|
|
47
47
|
justify-content: flex-end;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
::slotted(.toolbar-current-page) {
|
|
51
51
|
width: 3.25em;
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -70,7 +70,7 @@ registerStyles(
|
|
|
70
70
|
text-overflow: ellipsis;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
::slotted(.toolbar-button){
|
|
74
74
|
height: var(--lumo-size-m);
|
|
75
75
|
border-radius: var(--lumo-border-radius, var(--lumo-border-radius-m));
|
|
76
76
|
color: var(--lumo-contrast-80pct);
|
|
@@ -78,49 +78,33 @@ registerStyles(
|
|
|
78
78
|
margin: var(--lumo-space-xs);
|
|
79
79
|
background: transparent;
|
|
80
80
|
border: none;
|
|
81
|
-
padding-top: 0.
|
|
81
|
+
padding-top: 0.3em;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
::slotted(.toolbar-button[disabled]) {
|
|
85
85
|
color: var(--lumo-contrast-40pct);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
::slotted(.toolbar-button:hover) {
|
|
89
89
|
background-color: var(--lumo-contrast-5pct);
|
|
90
90
|
color: var(--lumo-contrast-80pct);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
::slotted(.toolbar-button[disabled]:hover) {
|
|
94
94
|
background-color: transparent;
|
|
95
95
|
color: var(--lumo-contrast-40pct);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
::slotted(.toolbar-button:focus) {
|
|
99
99
|
outline: none;
|
|
100
100
|
box-shadow: 0 0 0 2px var(--lumo-primary-color-50pct);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
::slotted(.toolbar-button) {
|
|
104
104
|
font-family: 'lumo-icons';
|
|
105
105
|
font-size: var(--lumo-icon-size-m);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
[part~="previous-page-button-icon"]::before {
|
|
109
|
-
content: var(--lumo-icons-angle-up);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
[part~="next-page-button-icon"]::before {
|
|
113
|
-
content: var(--lumo-icons-angle-down);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
[part~="toggle-button-icon"]::before {
|
|
117
|
-
content: var(--lumo-icons-chevron-right);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
#outerContainer.sidebarOpen [part~="toggle-button-icon"]::before {
|
|
121
|
-
content: var(--lumo-icons-chevron-left);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
108
|
.page {
|
|
125
109
|
padding: var(--lumo-space-m);
|
|
126
110
|
padding-bottom: 0;
|
|
@@ -159,7 +143,7 @@ registerStyles(
|
|
|
159
143
|
flex: none;
|
|
160
144
|
}
|
|
161
145
|
|
|
162
|
-
[part~="toolbar"].small-size
|
|
146
|
+
[part~="toolbar"].small-size ::slotted(.toolbar-zoom) {
|
|
163
147
|
position: absolute;
|
|
164
148
|
bottom: var(--lumo-space-s);
|
|
165
149
|
left: 50%;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { registerStyles, css } from '@vaadin/vaadin-themable-mixin/register-styles.js';
|
|
2
|
+
|
|
3
|
+
import '@vaadin/icon/theme/lumo/vaadin-icon-styles.js';
|
|
4
|
+
|
|
5
|
+
import '@vaadin/vaadin-lumo-styles/font-icons.js';
|
|
6
|
+
|
|
7
|
+
registerStyles(
|
|
8
|
+
'vaadin-icon',
|
|
9
|
+
css`
|
|
10
|
+
:host(.previous-page-button-icon)::before {
|
|
11
|
+
content: var(--pdf-viewer-previous-page-button-icon, var(--lumo-icons-angle-up));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
:host(.next-page-button-icon)::before {
|
|
15
|
+
content: var(--pdf-viewer-next-page-button-icon, var(--lumo-icons-angle-down));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
:host(.toggle-button-icon)::before {
|
|
19
|
+
content: var(--pdf-viewer-toggle-button-icon, var(--lumo-icons-chevron-right));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
:host(.sidebarOpen.toggle-button-icon)::before {
|
|
23
|
+
content:var(--pdf-viewer-toggle-button-icon, var(--lumo-icons-chevron-left));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
`,
|
|
27
|
+
{ moduleId: 'lumo-vcf-pdf-viewer-toolbar-icons' }
|
|
28
|
+
);
|
|
29
|
+
|