tailjng 0.0.37 → 0.0.39
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/cli/settings/components-list.js +10 -6
- package/cli/settings/header-generator.js +1 -1
- package/package.json +1 -1
- package/src/lib/components/card/card-crud-complete/complete-crud-card.component.html +34 -33
- package/src/lib/components/dialog/dialog.component.html +32 -27
- package/src/lib/components/dialog/dialog.component.ts +3 -3
- package/src/lib/components/form/form-sidebar/sidebar-form.component.ts +4 -5
- package/src/lib/components/table/table-complete/complete-table.component.html +8 -7
- package/src/lib/components/table/table-crud-complete/complete-crud-table.component.html +1 -1
- package/src/lib/components/table/table-crud-complete/complete-crud-table.component.ts +4 -4
- package/src/lib/components/{image/image-viewer/viewer-image.component.html → viewer/viewer-image/image-viewer.component.html} +32 -29
- package/src/lib/components/{image/image-viewer/viewer-image.component.ts → viewer/viewer-image/image-viewer.component.ts} +56 -26
- package/src/lib/components/viewer/viewer-pdf/pdf-viewer.component.html +28 -0
- package/src/lib/components/viewer/viewer-pdf/pdf-viewer.component.scss +0 -0
- package/src/lib/components/viewer/viewer-pdf/pdf-viewer.component.ts +31 -0
- /package/src/lib/components/{image/image-viewer/viewer-image.component.css → viewer/viewer-image/image-viewer.component.css} +0 -0
|
@@ -50,10 +50,14 @@ function getComponentList() {
|
|
|
50
50
|
path: "src/lib/components/progress-bar",
|
|
51
51
|
dependencies: [],
|
|
52
52
|
},
|
|
53
|
-
'image
|
|
54
|
-
path: "src/lib/components/
|
|
53
|
+
'viewer-image': {
|
|
54
|
+
path: "src/lib/components/viewer/viewer-image",
|
|
55
55
|
dependencies: ["button"],
|
|
56
56
|
},
|
|
57
|
+
'viewer-pdf': {
|
|
58
|
+
path: "src/lib/components/viewer/viewer-pdf",
|
|
59
|
+
dependencies: [],
|
|
60
|
+
},
|
|
57
61
|
'dialog': {
|
|
58
62
|
path: "src/lib/components/dialog",
|
|
59
63
|
dependencies: [],
|
|
@@ -114,10 +118,10 @@ function getComponentList() {
|
|
|
114
118
|
path: "src/lib/components/menu/menu-options-table",
|
|
115
119
|
dependencies: ["button"],
|
|
116
120
|
},
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
121
125
|
'table-crud-complete': {
|
|
122
126
|
path: "src/lib/components/table/table-crud-complete",
|
|
123
127
|
dependencies: ["button", "paginator-complete", "filter-complete", "checkbox-input", "menu-options-table", "dialog", "image-viewer", "select-dropdown", "input"],
|
package/package.json
CHANGED
|
@@ -3,18 +3,18 @@
|
|
|
3
3
|
@if (isSearch) {
|
|
4
4
|
<JFilter
|
|
5
5
|
[params]="params"
|
|
6
|
-
[columns]="columns"
|
|
7
|
-
[endpoint]="endpoint"
|
|
8
|
-
[mainEndpoint]="mainEndpoint"
|
|
9
|
-
[data]="data"
|
|
10
|
-
[(searchQuery)]="searchQuery"
|
|
11
|
-
(search)="onSearch()"
|
|
6
|
+
[columns]="columns"
|
|
7
|
+
[endpoint]="endpoint"
|
|
8
|
+
[mainEndpoint]="mainEndpoint"
|
|
9
|
+
[data]="data"
|
|
10
|
+
[(searchQuery)]="searchQuery"
|
|
11
|
+
(search)="onSearch()"
|
|
12
12
|
[searchPlaceholder]="searchPlaceholder"
|
|
13
13
|
[(itemsPerPage)]="itemsPerPage"
|
|
14
|
-
[itemsPerPageOptions]="itemsPerPageOptions"
|
|
14
|
+
[itemsPerPageOptions]="itemsPerPageOptions"
|
|
15
15
|
(onItemsPerPageChangeEvent)="onItemsPerPageChange()"
|
|
16
|
-
[isLoadingSearch]="isLoading('search')"
|
|
17
|
-
[isLoadingPerPage]="isLoading('itemsPerPage')"
|
|
16
|
+
[isLoadingSearch]="isLoading('search')"
|
|
17
|
+
[isLoadingPerPage]="isLoading('itemsPerPage')"
|
|
18
18
|
[isLoadingAditionalButtons]="loadingStates.aditionalButtons"
|
|
19
19
|
(clearFilters)="onClearFilters($event)"
|
|
20
20
|
[filtersButton]="filtersButton"
|
|
@@ -33,12 +33,12 @@
|
|
|
33
33
|
@if (isPaginator) {
|
|
34
34
|
<div class="my-4">
|
|
35
35
|
<JCompletePaginator
|
|
36
|
-
[currentPage]="currentPage"
|
|
37
|
-
[itemsPerPageOptions]="itemsPerPageOptions"
|
|
36
|
+
[currentPage]="currentPage"
|
|
37
|
+
[itemsPerPageOptions]="itemsPerPageOptions"
|
|
38
38
|
[itemsPerPage]="itemsPerPage"
|
|
39
|
-
[totalItems]="totalItems"
|
|
40
|
-
[pages]="pages"
|
|
41
|
-
(pageChange)="handlePageChange($event)"
|
|
39
|
+
[totalItems]="totalItems"
|
|
40
|
+
[pages]="pages"
|
|
41
|
+
(pageChange)="handlePageChange($event)"
|
|
42
42
|
[isLoading]="isLoading('pagination')"
|
|
43
43
|
/>
|
|
44
44
|
</div>
|
|
@@ -57,25 +57,25 @@
|
|
|
57
57
|
<div class="j-crud-card grid grid-cols-4 items-start max-[1300px]:grid-cols-3 max-[1150px]:grid-cols-2 max-[800px]:grid-cols-1 gap-4 my-4">
|
|
58
58
|
@for (item of displayData; track item?.id) {
|
|
59
59
|
<div class="flex flex-col gap-3 rounded-xl border border-border dark:border-dark-border bg-white dark:bg-foreground shadow-sm hover:shadow-lg hover:border-primary/50 hover:dark:border-primary transition-all duration-300">
|
|
60
|
-
<ng-container
|
|
61
|
-
*ngTemplateOutlet="itemTemplate; context: {
|
|
62
|
-
$implicit: item,
|
|
63
|
-
getValue: getValue.bind(this),
|
|
64
|
-
columns: columns,
|
|
65
|
-
item: item,
|
|
66
|
-
template: getTemplate,
|
|
67
|
-
toggleExpanded: toggleExpanded.bind(this),
|
|
68
|
-
isExpanded: isExpanded.bind(this),
|
|
60
|
+
<ng-container
|
|
61
|
+
*ngTemplateOutlet="itemTemplate; context: {
|
|
62
|
+
$implicit: item,
|
|
63
|
+
getValue: getValue.bind(this),
|
|
64
|
+
columns: columns,
|
|
65
|
+
item: item,
|
|
66
|
+
template: getTemplate,
|
|
67
|
+
toggleExpanded: toggleExpanded.bind(this),
|
|
68
|
+
isExpanded: isExpanded.bind(this),
|
|
69
69
|
expandData: getExpandData.bind(this),
|
|
70
70
|
expandTemplate: expandTemplate,
|
|
71
71
|
isLoadingExpand: isLoadingExpand.bind(this),
|
|
72
|
-
}"
|
|
72
|
+
}"
|
|
73
73
|
/>
|
|
74
74
|
</div>
|
|
75
75
|
}
|
|
76
76
|
</div>
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
|
|
78
|
+
|
|
79
79
|
} @else {
|
|
80
80
|
<div class="j-crud-card w-full flex justify-center items-center h-100 bg-white dark:bg-foreground rounded-[20px]">
|
|
81
81
|
<p>No se ha definido ninguna plantilla personalizada (<code>itemTemplate</code>).</p>
|
|
@@ -83,20 +83,21 @@
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
} @else if (!isLoading('pagination')) {
|
|
86
|
-
<div class="j-crud-card w-full flex justify-center items-center h-100 bg-white dark:bg-foreground rounded-[20px] border border-border dark:border-dark-border hover:border-primary/50 hover:dark:border-primary">
|
|
87
|
-
|
|
86
|
+
<div class="j-crud-card w-full flex flex-col gap-3 justify-center items-center h-100 bg-white dark:bg-foreground rounded-[20px] border border-border dark:border-dark-border hover:border-primary/50 hover:dark:border-primary">
|
|
87
|
+
<lucide-icon [name]="iconsService.icons.info" size="35" class="text-primary"></lucide-icon>
|
|
88
|
+
<p class="text-black dark:text-white">No hay datos disponibles</p>
|
|
88
89
|
</div>
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
@if (isPaginator) {
|
|
92
93
|
<div class="my-4">
|
|
93
94
|
<JCompletePaginator
|
|
94
|
-
[currentPage]="currentPage"
|
|
95
|
-
[itemsPerPageOptions]="itemsPerPageOptions"
|
|
95
|
+
[currentPage]="currentPage"
|
|
96
|
+
[itemsPerPageOptions]="itemsPerPageOptions"
|
|
96
97
|
[itemsPerPage]="itemsPerPage"
|
|
97
|
-
[totalItems]="totalItems"
|
|
98
|
-
[pages]="pages"
|
|
99
|
-
(pageChange)="handlePageChange($event)"
|
|
98
|
+
[totalItems]="totalItems"
|
|
99
|
+
[pages]="pages"
|
|
100
|
+
(pageChange)="handlePageChange($event)"
|
|
100
101
|
[isLoading]="isLoading('pagination')"
|
|
101
102
|
/>
|
|
102
103
|
</div>
|
|
@@ -4,39 +4,47 @@
|
|
|
4
4
|
@if (overlay) {
|
|
5
5
|
<div class="fixed inset-0 z-[999] bg-black/50"></div>
|
|
6
6
|
}
|
|
7
|
-
|
|
7
|
+
|
|
8
8
|
<!-- Modal -->
|
|
9
9
|
<div class="fixed inset-0 z-[1000] flex pointer-events-none" [ngClass]="getPositionClass()">
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
|
|
11
|
+
<div @modalTransition
|
|
12
|
+
class="pointer-events-auto bg-white dark:bg-foreground rounded-[12px] shadow-lg border-2 border-border dark:border-dark-border"
|
|
13
|
+
[ngStyle]="getOffsetStyles()"
|
|
14
|
+
[style.width]="getModalWidth()"
|
|
15
|
+
[style.min-width]="!isFullScreen() ? '200px' : null"
|
|
16
|
+
[style.min-height]="!isFullScreen() ? '40px' : null"
|
|
17
|
+
data-draggable-dialog
|
|
18
|
+
>
|
|
17
19
|
<!-- Header draggable -->
|
|
18
20
|
@if (draggable) {
|
|
19
|
-
<div
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
<div
|
|
22
|
+
class="flex items-center p-1 pl-4 pr-4 gap-2 bg-primary dark:bg-dark-primary border-b border-border dark:border-dark-border rounded-[10px] font-semibold text-2sm cursor-move select-none"
|
|
23
|
+
(mousedown)="$event.stopPropagation(); startDrag($event)"
|
|
24
|
+
>
|
|
25
|
+
<h3
|
|
26
|
+
class="flex-1 min-w-0 text-[1em] font-semibold text-white leading-none truncate"
|
|
27
|
+
>
|
|
28
|
+
{{ title }}
|
|
29
|
+
</h3>
|
|
22
30
|
|
|
23
|
-
<button
|
|
24
|
-
type="button"
|
|
31
|
+
<button
|
|
32
|
+
type="button"
|
|
25
33
|
(click)="$event.stopPropagation(); onClose()"
|
|
26
|
-
class="p-2 rounded-full border border-border dark:border-dark-border text-white hover:bg-dark-background focus:outline-none cursor-pointer"
|
|
34
|
+
class="flex-shrink-0 p-2 rounded-full border border-border dark:border-dark-border text-white hover:bg-dark-background focus:outline-none cursor-pointer"
|
|
27
35
|
>
|
|
28
36
|
<lucide-icon [name]="iconsService.icons.close" size="16"></lucide-icon>
|
|
29
37
|
</button>
|
|
30
|
-
|
|
31
38
|
</div>
|
|
32
39
|
}
|
|
33
|
-
|
|
40
|
+
|
|
41
|
+
|
|
34
42
|
<!-- Header normal -->
|
|
35
43
|
@if (!draggable) {
|
|
36
44
|
<div class="flex p-1 pl-4 pr-4 justify-between items-center bg-primary dark:bg-dark-primary border-b border-border dark:border-dark-border rounded-[10px] font-semibold text-2sm cursor-normal select-none">
|
|
37
45
|
<h3 class="text-[1em] font-semibold text-white leading-none">{{ title }}</h3>
|
|
38
|
-
<button
|
|
39
|
-
type="button"
|
|
46
|
+
<button
|
|
47
|
+
type="button"
|
|
40
48
|
(click)="onClose()"
|
|
41
49
|
class="p-2 rounded-full border border-border dark:border-dark-border text-white hover:bg-dark-background focus:outline-none cursor-pointer"
|
|
42
50
|
>
|
|
@@ -47,22 +55,19 @@
|
|
|
47
55
|
|
|
48
56
|
<!-- Content -->
|
|
49
57
|
<div class="m-2"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
'min-width': !isFullScreen() ? '200px' : null,
|
|
55
|
-
'min-height': !isFullScreen() ? '40px' : null
|
|
56
|
-
}"
|
|
58
|
+
[ngClass]="{ 'jdialog-full': isFullScreen() }"
|
|
59
|
+
[ngStyle]="{
|
|
60
|
+
height: getModalHeight()
|
|
61
|
+
}"
|
|
57
62
|
>
|
|
58
63
|
@if (dialogTemplate) {
|
|
59
64
|
<ng-container [ngTemplateOutlet]="dialogTemplate"></ng-container>
|
|
60
65
|
}
|
|
61
66
|
</div>
|
|
62
|
-
|
|
67
|
+
|
|
68
|
+
|
|
63
69
|
</div>
|
|
64
70
|
|
|
65
71
|
</div>
|
|
66
72
|
|
|
67
73
|
}
|
|
68
|
-
|
|
@@ -123,8 +123,8 @@ export class JDialogComponent implements OnChanges {
|
|
|
123
123
|
* Handles the escape key press event.
|
|
124
124
|
* @param event The keyboard event.
|
|
125
125
|
*/
|
|
126
|
-
@HostListener('document:keydown.escape'
|
|
127
|
-
handleEscape(
|
|
126
|
+
@HostListener('document:keydown.escape')
|
|
127
|
+
handleEscape() {
|
|
128
128
|
if (this.openModal) {
|
|
129
129
|
this.onClose();
|
|
130
130
|
}
|
|
@@ -171,7 +171,7 @@ export class JDialogComponent implements OnChanges {
|
|
|
171
171
|
/**
|
|
172
172
|
* Starts the drag operation.
|
|
173
173
|
* @param event The mouse event.
|
|
174
|
-
* @returns
|
|
174
|
+
* @returns
|
|
175
175
|
*/
|
|
176
176
|
startDrag(event: MouseEvent) {
|
|
177
177
|
if (!this.draggable) return;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
import { CommonModule } from '@angular/common';
|
|
3
2
|
import { Component, CUSTOM_ELEMENTS_SCHEMA, EventEmitter, HostListener, Input, Output, TemplateRef } from '@angular/core';
|
|
4
3
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
@@ -38,13 +37,13 @@ export class JSidebarFormComponent {
|
|
|
38
37
|
|
|
39
38
|
titleForm: string = 'REGISTRO';
|
|
40
39
|
@Input() typeForm: FormType = 'none';
|
|
41
|
-
|
|
40
|
+
|
|
42
41
|
@Input() checkboxes: DynamicCheckbox[] = [];
|
|
43
42
|
@Input() size: 'small' | 'medium' | 'large' | 'xlarge' = 'medium';
|
|
44
43
|
@Input() bgColor: string = 'bg-white dark:bg-foreground';
|
|
45
44
|
@Input() style: { [key: string]: any } = {};
|
|
46
45
|
@Input() isLoading: boolean = false;
|
|
47
|
-
|
|
46
|
+
|
|
48
47
|
constructor(public readonly iconsService: JIconsService) { }
|
|
49
48
|
|
|
50
49
|
onSubmit() {
|
|
@@ -55,8 +54,8 @@ export class JSidebarFormComponent {
|
|
|
55
54
|
this.closeForm.emit()
|
|
56
55
|
}
|
|
57
56
|
|
|
58
|
-
@HostListener('document:keydown.escape'
|
|
59
|
-
handleEscape(
|
|
57
|
+
@HostListener('document:keydown.escape')
|
|
58
|
+
handleEscape() {
|
|
60
59
|
if (this.openForm) {
|
|
61
60
|
this.onClose();
|
|
62
61
|
}
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
<table class="min-w-full bg-white dark:bg-dark-background rounded">
|
|
48
48
|
<thead class="bg-primary dark:bg-dark-primary text-white dark:text-white select-none h-[50px]">
|
|
49
49
|
<tr>
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
<!-- Counter column -->
|
|
52
52
|
<th
|
|
53
53
|
class="px-4 py-2 text-center text-xs font-medium text-white uppercase tracking-wider border-b border-border dark:border-dark-border font-bold">
|
|
@@ -93,15 +93,15 @@
|
|
|
93
93
|
<!-- Actions column - Sticky header -->
|
|
94
94
|
@if (isOptions) {
|
|
95
95
|
<th class="!sticky !right-0 bg-primary dark:bg-dark-primary min-w-[50px] px-4 py-2 text-center text-xs font-medium text-white uppercase tracking-wider border-b border-border dark:border-dark-border font-bold shadow-[-4px_0_5px_rgba(0,0,0,0.1)] z-1">
|
|
96
|
-
|
|
96
|
+
|
|
97
97
|
<span class="text-[10px] opacity-80 border border-border dark:border-dark-border p-2 pl-3 pr-3 rounded-full">Opciones</span>
|
|
98
|
-
|
|
98
|
+
|
|
99
99
|
<!-- Pseudoelemento para el borde central -->
|
|
100
100
|
<div class="absolute top-[15px] bottom-[15px] left-0 w-[1px] bg-border dark:bg-dark-border"></div>
|
|
101
101
|
|
|
102
102
|
</th>
|
|
103
103
|
}
|
|
104
|
-
|
|
104
|
+
|
|
105
105
|
</tr>
|
|
106
106
|
</thead>
|
|
107
107
|
<tbody class="bg-white dark:bg-foreground text-black dark:text-white">
|
|
@@ -240,8 +240,9 @@
|
|
|
240
240
|
<tr>
|
|
241
241
|
<td [attr.colspan]="columns.length + 1"
|
|
242
242
|
class="px-4 py-8 text-center text-sm text-black dark:text-white">
|
|
243
|
-
<div class="flex flex-col gap-3 items-center justify-center py-4">
|
|
244
|
-
<
|
|
243
|
+
<div class="absolute inset-0 flex flex-col gap-3 items-center justify-center py-4 bg-white/80 dark:bg-foreground/80 backdrop-blur-sm z-501 select-none rounded">
|
|
244
|
+
<lucide-icon [name]="iconsService.icons.info" size="35" class="text-primary"></lucide-icon>
|
|
245
|
+
<p class="text-black dark:text-white">No hay datos disponibles</p>
|
|
245
246
|
</div>
|
|
246
247
|
</td>
|
|
247
248
|
</tr>
|
|
@@ -250,4 +251,4 @@
|
|
|
250
251
|
</table>
|
|
251
252
|
</div>
|
|
252
253
|
</div>
|
|
253
|
-
</div>
|
|
254
|
+
</div>
|
|
@@ -545,7 +545,7 @@
|
|
|
545
545
|
<td [attr.colspan]="getVisibleColumnsCount() + 3"
|
|
546
546
|
class="px-4 py-8 text-center text-sm text-black dark:text-white">
|
|
547
547
|
<div class="flex flex-col gap-3 items-center justify-center py-4">
|
|
548
|
-
<lucide-icon [name]="iconsService.icons.
|
|
548
|
+
<lucide-icon [name]="iconsService.icons.info" size="30" class="text-primary"></lucide-icon>
|
|
549
549
|
<p>No hay datos disponibles</p>
|
|
550
550
|
</div>
|
|
551
551
|
</td>
|
|
@@ -13,14 +13,14 @@ import { JButtonComponent } from '../../button/button.component';
|
|
|
13
13
|
import { JInputCheckboxComponent } from '../../checkbox/checkbox-input/input-checkbox.component';
|
|
14
14
|
import { JOptionsTableMenuComponent } from '../../menu/menu-options-table/options-table-menu.component';
|
|
15
15
|
import { JDialogComponent } from '../../dialog/dialog.component';
|
|
16
|
-
import { JViewerImageComponent } from '../../image/image-viewer/viewer-image.component';
|
|
17
16
|
import { JDropdownSelectComponent } from '../../select/select-dropdown/dropdown-select.component';
|
|
18
17
|
import { JInputComponent } from '../../input/input/input.component';
|
|
18
|
+
import { JViewerPdfComponent } from '../../viewer/viewer-pdf/pdf-viewer.component';
|
|
19
19
|
|
|
20
20
|
@Component({
|
|
21
21
|
selector: 'JCompleteCrudTable',
|
|
22
22
|
standalone: true,
|
|
23
|
-
imports: [CommonModule, FormsModule, JCompletePaginatorComponent, JCompleteFilterComponent, LucideAngularModule, JButtonComponent, JInputCheckboxComponent, JOptionsTableMenuComponent, JDialogComponent,
|
|
23
|
+
imports: [CommonModule, FormsModule, JCompletePaginatorComponent, JCompleteFilterComponent, LucideAngularModule, JButtonComponent, JInputCheckboxComponent, JOptionsTableMenuComponent, JDialogComponent, JViewerPdfComponent, JDropdownSelectComponent, JInputComponent],
|
|
24
24
|
templateUrl: './complete-crud-table.component.html',
|
|
25
25
|
styleUrl: './complete-crud-table.component.scss',
|
|
26
26
|
animations: [
|
|
@@ -76,7 +76,7 @@ export class JCompleteCrudTableComponent implements OnInit {
|
|
|
76
76
|
@Input() optionsType: 'button' | 'dropdown' = 'button';
|
|
77
77
|
@Input() isOptions: boolean = true;
|
|
78
78
|
|
|
79
|
-
// Expansion
|
|
79
|
+
// Expansion
|
|
80
80
|
expandTemplate?: (row: any) => string;
|
|
81
81
|
expandedRows: Set<any> = new Set();
|
|
82
82
|
|
|
@@ -1049,4 +1049,4 @@ export class JCompleteCrudTableComponent implements OnInit {
|
|
|
1049
1049
|
|
|
1050
1050
|
|
|
1051
1051
|
|
|
1052
|
-
}
|
|
1052
|
+
}
|
|
@@ -1,62 +1,65 @@
|
|
|
1
1
|
<div class="relative w-full h-full" #container>
|
|
2
2
|
|
|
3
|
-
<div class="absolute flex
|
|
3
|
+
<div class="absolute flex gap-1 z-2" [ngClass]="{ 'top-3 left-3': isFullscreen, 'top-0 left-0': !isFullscreen }">
|
|
4
4
|
<JButton
|
|
5
|
-
|
|
5
|
+
[icon]="iconsService.icons.rotateLeft"
|
|
6
|
+
[iconSize]="20"
|
|
7
|
+
(clicked)="rotateLeftImg()"
|
|
6
8
|
classes="secondary w-[35px] h-[35px]"
|
|
7
|
-
|
|
8
|
-
<lucide-icon [name]="iconsService.icons.reset" size="20" />
|
|
9
|
-
</JButton>
|
|
9
|
+
/>
|
|
10
10
|
|
|
11
11
|
<JButton
|
|
12
|
-
|
|
12
|
+
[icon]="iconsService.icons.reset"
|
|
13
|
+
[iconSize]="20"
|
|
14
|
+
(clicked)="reset()"
|
|
13
15
|
classes="secondary w-[35px] h-[35px]"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
</JButton>
|
|
16
|
+
/>
|
|
17
|
+
</div>
|
|
17
18
|
|
|
19
|
+
|
|
20
|
+
<div class="absolute flex flex-col gap-1 z-2" [ngClass]="{ 'top-3 left-3': isFullscreen, 'top-10 left-0': !isFullscreen }">
|
|
18
21
|
<JButton
|
|
22
|
+
[icon]="iconsService.icons.rotateRight"
|
|
23
|
+
[iconSize]="20"
|
|
19
24
|
(clicked)="rotateRightImg()"
|
|
20
25
|
classes="secondary w-[35px] h-[35px]"
|
|
21
|
-
|
|
22
|
-
<lucide-icon [name]="iconsService.icons.rotateRight" size="20" />
|
|
23
|
-
</JButton>
|
|
26
|
+
/>
|
|
24
27
|
</div>
|
|
25
28
|
|
|
26
29
|
|
|
27
30
|
<div class="absolute flex gap-1 z-2" [ngClass]="{ 'top-3 right-3': isFullscreen, 'top-0 right-0': !isFullscreen }">
|
|
28
31
|
<JButton
|
|
29
|
-
[
|
|
32
|
+
[icon]="iconsService.icons.download"
|
|
33
|
+
[iconSize]="20"
|
|
30
34
|
(clicked)="download()"
|
|
35
|
+
[isLoading]="isDownloading"
|
|
31
36
|
classes="secondary w-[35px] h-[35px]"
|
|
32
|
-
|
|
33
|
-
<lucide-icon [name]="iconsService.icons.download" size="20" />
|
|
34
|
-
</JButton>
|
|
37
|
+
/>
|
|
35
38
|
|
|
36
39
|
<JButton
|
|
40
|
+
[icon]="isFullscreen ? iconsService.icons.exitFullscreen : iconsService.icons.fullscreen"
|
|
41
|
+
[iconSize]="20"
|
|
37
42
|
(clicked)="toggleFullscreen(container)"
|
|
38
43
|
classes="secondary w-[35px] h-[35px]"
|
|
39
|
-
|
|
40
|
-
@if (isFullscreen) {
|
|
41
|
-
<lucide-icon [name]="iconsService.icons.exitFullscreen" size="20" />
|
|
42
|
-
} @else {
|
|
43
|
-
<lucide-icon [name]="iconsService.icons.fullscreen" size="20" />
|
|
44
|
-
}
|
|
45
|
-
</JButton>
|
|
44
|
+
/>
|
|
46
45
|
</div>
|
|
47
46
|
|
|
48
47
|
<div class="absolute flex flex-col gap-1 z-2" [ngClass]="{ 'top-13 right-3': isFullscreen, 'top-10 right-0': !isFullscreen }">
|
|
49
48
|
<JButton
|
|
49
|
+
[icon]="iconsService.icons.zoomIn"
|
|
50
|
+
[iconSize]="20"
|
|
50
51
|
[disabled]="zoom === 3"
|
|
51
52
|
(clicked)="zoomIn()"
|
|
52
53
|
classes="secondary w-[35px] h-[35px]"
|
|
53
|
-
|
|
54
|
-
<lucide-icon [name]="iconsService.icons.zoomIn" size="20" />
|
|
55
|
-
</JButton>
|
|
54
|
+
/>
|
|
56
55
|
|
|
57
|
-
<JButton
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
<JButton
|
|
57
|
+
[icon]="iconsService.icons.zoomOut"
|
|
58
|
+
[iconSize]="20"
|
|
59
|
+
[disabled]="zoom === 0.5"
|
|
60
|
+
(clicked)="zoomOut()"
|
|
61
|
+
classes="secondary w-[35px] h-[35px]"
|
|
62
|
+
/>
|
|
60
63
|
</div>
|
|
61
64
|
|
|
62
65
|
|
|
@@ -6,12 +6,12 @@ import { JIconsService } from 'tailjng';
|
|
|
6
6
|
import { JButtonComponent } from '../../button/button.component';
|
|
7
7
|
|
|
8
8
|
@Component({
|
|
9
|
-
selector: '
|
|
9
|
+
selector: 'JImageViewer',
|
|
10
10
|
imports: [CommonModule, LucideAngularModule, JButtonComponent],
|
|
11
|
-
templateUrl: './viewer
|
|
12
|
-
styleUrl: './viewer
|
|
11
|
+
templateUrl: './image-viewer.component.html',
|
|
12
|
+
styleUrl: './image-viewer.component.css'
|
|
13
13
|
})
|
|
14
|
-
export class
|
|
14
|
+
export class JImageViewerComponent implements OnChanges {
|
|
15
15
|
|
|
16
16
|
@Input() src!: string | SafeUrl;
|
|
17
17
|
@Input() alt: string = 'Imagen';
|
|
@@ -86,50 +86,80 @@ export class JViewerImageComponent implements OnChanges {
|
|
|
86
86
|
/**
|
|
87
87
|
* Downloads the currently displayed image.
|
|
88
88
|
*/
|
|
89
|
+
isDownloading = false;
|
|
90
|
+
|
|
91
|
+
private getImageUrl(): string {
|
|
92
|
+
if (typeof this.src === 'string') {
|
|
93
|
+
return this.src;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// SafeUrlImpl → sacar la propiedad interna
|
|
97
|
+
const anySrc = this.src as any;
|
|
98
|
+
if (anySrc.changingThisBreaksApplicationSecurity) {
|
|
99
|
+
return anySrc.changingThisBreaksApplicationSecurity as string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// fallback (por si acaso)
|
|
103
|
+
return this.src.toString();
|
|
104
|
+
}
|
|
105
|
+
|
|
89
106
|
download() {
|
|
90
107
|
try {
|
|
91
108
|
if (!this.src) return;
|
|
92
109
|
|
|
93
|
-
|
|
94
|
-
const imageUrl = this.src.toString();
|
|
110
|
+
this.isDownloading = true;
|
|
95
111
|
|
|
96
|
-
|
|
112
|
+
const imageUrl = this.getImageUrl();
|
|
97
113
|
const fileName = this.alt?.replace(/\s+/g, '') || 'imagen';
|
|
98
114
|
|
|
99
|
-
//
|
|
115
|
+
// Si es base64
|
|
100
116
|
if (imageUrl.startsWith('data:image')) {
|
|
101
|
-
// Base64 → descarga directa
|
|
102
117
|
const link = document.createElement('a');
|
|
103
118
|
link.href = imageUrl;
|
|
104
119
|
link.download = `${fileName}.png`;
|
|
105
120
|
document.body.appendChild(link);
|
|
106
121
|
link.click();
|
|
107
122
|
document.body.removeChild(link);
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
fetch(imageUrl)
|
|
111
|
-
.then((response) => response.blob())
|
|
112
|
-
.then((blob) => {
|
|
113
|
-
const url = URL.createObjectURL(blob);
|
|
114
|
-
const link = document.createElement('a');
|
|
115
|
-
link.href = url;
|
|
116
|
-
link.download = `${fileName}.${blob.type.split('/')[1] || 'png'}`;
|
|
117
|
-
document.body.appendChild(link);
|
|
118
|
-
link.click();
|
|
119
|
-
document.body.removeChild(link);
|
|
120
|
-
URL.revokeObjectURL(url);
|
|
121
|
-
})
|
|
122
|
-
.catch((error) => {
|
|
123
|
-
console.error('Error al descargar la imagen:', error);
|
|
124
|
-
});
|
|
123
|
+
this.isDownloading = false;
|
|
124
|
+
return;
|
|
125
125
|
}
|
|
126
|
+
|
|
127
|
+
// Si es URL normal
|
|
128
|
+
fetch(imageUrl)
|
|
129
|
+
.then((response) => {
|
|
130
|
+
if (!response.ok) {
|
|
131
|
+
throw new Error(`HTTP ${response.status}`);
|
|
132
|
+
}
|
|
133
|
+
return response.blob();
|
|
134
|
+
})
|
|
135
|
+
.then((blob) => {
|
|
136
|
+
const url = URL.createObjectURL(blob);
|
|
137
|
+
const link = document.createElement('a');
|
|
138
|
+
const ext = blob.type.split('/')[1] || 'png';
|
|
139
|
+
|
|
140
|
+
link.href = url;
|
|
141
|
+
link.download = `${fileName}.${ext}`;
|
|
142
|
+
document.body.appendChild(link);
|
|
143
|
+
link.click();
|
|
144
|
+
document.body.removeChild(link);
|
|
145
|
+
URL.revokeObjectURL(url);
|
|
146
|
+
})
|
|
147
|
+
.catch((error) => {
|
|
148
|
+
console.error('Error al descargar la imagen:', error);
|
|
149
|
+
})
|
|
150
|
+
.finally(() => {
|
|
151
|
+
this.isDownloading = false;
|
|
152
|
+
});
|
|
153
|
+
|
|
126
154
|
} catch (err) {
|
|
155
|
+
this.isDownloading = false;
|
|
127
156
|
console.error('Error general en download():', err);
|
|
128
157
|
}
|
|
129
158
|
}
|
|
130
159
|
|
|
131
160
|
|
|
132
161
|
|
|
162
|
+
|
|
133
163
|
/**
|
|
134
164
|
* Zooms in the image.
|
|
135
165
|
*/
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<div class="flex-1 flex flex-col items-center justify-center">
|
|
2
|
+
<object
|
|
3
|
+
[data]="safeUrl"
|
|
4
|
+
type="application/pdf"
|
|
5
|
+
class="w-full h-full min-h-[300px] md:h-[75vh] overflow-auto"
|
|
6
|
+
>
|
|
7
|
+
<div class="p-6 flex flex-col items-center justify-center text-center gap-3">
|
|
8
|
+
<!-- Icono -->
|
|
9
|
+
<lucide-icon [name]="icons.circleAlert" size="35" class="text-primary"></lucide-icon>
|
|
10
|
+
|
|
11
|
+
<!-- Texto -->
|
|
12
|
+
<div class="space-y-1">
|
|
13
|
+
<h3 class="text-sm font-semibold text-slate-800 dark:text-slate-100">
|
|
14
|
+
No se pudo mostrar el PDF
|
|
15
|
+
</h3>
|
|
16
|
+
<p class="text-xs text-slate-500 dark:text-slate-400 max-w-xs">
|
|
17
|
+
Tu navegador no es compatible con la visualización embebida de este documento.
|
|
18
|
+
Puedes descargarlo y verlo en tu visor de PDF preferido.
|
|
19
|
+
</p>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<!-- Botón -->
|
|
23
|
+
<a [href]="safeUrl" target="_blank" class="inline-flex items-center justify-center px-4 py-2 rounded-full text-xs font-medium bg-blue-600 text-white hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 transition-colors">
|
|
24
|
+
Descargar archivo
|
|
25
|
+
</a>
|
|
26
|
+
</div>
|
|
27
|
+
</object>
|
|
28
|
+
</div>
|
|
File without changes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
|
|
2
|
+
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
|
|
3
|
+
import { CircleAlert, LucideAngularModule } from 'lucide-angular';
|
|
4
|
+
|
|
5
|
+
@Component({
|
|
6
|
+
selector: 'JPdfViewer',
|
|
7
|
+
standalone: true,
|
|
8
|
+
imports: [LucideAngularModule],
|
|
9
|
+
templateUrl: './pdf-viewer.component.html',
|
|
10
|
+
styleUrls: ['./pdf-viewer.component.scss']
|
|
11
|
+
})
|
|
12
|
+
export class JViewerPdfComponent implements OnChanges {
|
|
13
|
+
|
|
14
|
+
icons = {
|
|
15
|
+
circleAlert: CircleAlert
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// URLs normales (strings) para usarlas en <a href="">
|
|
19
|
+
@Input() url = 'http://example/example.pdf';
|
|
20
|
+
|
|
21
|
+
// URLs “seguras” para <object>
|
|
22
|
+
safeUrl: SafeResourceUrl | null = null;
|
|
23
|
+
|
|
24
|
+
constructor(private sanitizer: DomSanitizer) { }
|
|
25
|
+
|
|
26
|
+
ngOnChanges(changes: SimpleChanges): void {
|
|
27
|
+
if (changes['url']) {
|
|
28
|
+
this.safeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.url);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|