@haloduck/ui 2.0.1 → 2.0.2
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/fesm2022/haloduck-ui.mjs +2857 -0
- package/fesm2022/haloduck-ui.mjs.map +1 -0
- package/index.d.ts +662 -0
- package/package.json +8 -10
- package/src/tailwind.css +1817 -0
- package/documentation.json +0 -2104
- package/haloduck-ui-1.0.0.tgz +0 -0
- package/public/i18n/en.json +0 -1
- package/public/i18n/icons/en.png +0 -0
- package/public/i18n/icons/ko.png +0 -0
- package/public/i18n/ko.json +0 -1
- package/public/image/search.svg +0 -3
|
@@ -0,0 +1,2857 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { Injectable, Input, Component, inject, ChangeDetectorRef, forwardRef, ViewChild, signal, EventEmitter, Output, ViewContainerRef, NgZone, isDevMode, ChangeDetectionStrategy } from '@angular/core';
|
|
3
|
+
import { signIn, confirmSignIn, resetPassword, confirmResetPassword } from 'aws-amplify/auth';
|
|
4
|
+
import * as i1$1 from '@angular/forms';
|
|
5
|
+
import { NG_VALUE_ACCESSOR, Validators, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
6
|
+
import { BehaviorSubject, zip, Subject, takeUntil, tap, combineLatest, switchMap, of, Observable, distinctUntilChanged, map, take } from 'rxjs';
|
|
7
|
+
import { ulid } from 'ulid';
|
|
8
|
+
import * as i1 from '@angular/common';
|
|
9
|
+
import { CommonModule, DecimalPipe, DatePipe } from '@angular/common';
|
|
10
|
+
import * as i1$2 from '@jsverse/transloco';
|
|
11
|
+
import { provideTranslocoScope, TranslocoModule, TranslocoService } from '@jsverse/transloco';
|
|
12
|
+
import * as i2 from '@angular/common/http';
|
|
13
|
+
import * as i1$3 from '@angular/cdk/overlay';
|
|
14
|
+
import { Overlay } from '@angular/cdk/overlay';
|
|
15
|
+
import { ComponentPortal } from '@angular/cdk/portal';
|
|
16
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
17
|
+
import { CoreService } from '@haloduck/core';
|
|
18
|
+
import * as i1$5 from '@angular/router';
|
|
19
|
+
import { Router, NavigationEnd, RouterLink } from '@angular/router';
|
|
20
|
+
import * as i1$4 from '@angular/platform-browser';
|
|
21
|
+
import { Title } from '@angular/platform-browser';
|
|
22
|
+
import * as THREE from 'three';
|
|
23
|
+
import { STLLoader, OrbitControls } from 'three-stdlib';
|
|
24
|
+
import { download } from '@haloduck/util';
|
|
25
|
+
import { filter } from 'rxjs/operators';
|
|
26
|
+
|
|
27
|
+
class NotificationService {
|
|
28
|
+
listNotification$ = new BehaviorSubject([]);
|
|
29
|
+
listNotification = [];
|
|
30
|
+
showNotification(title, body, payload = null, timeout) {
|
|
31
|
+
if (window.FlutterApp) {
|
|
32
|
+
window.FlutterApp.postMessage(JSON.stringify({
|
|
33
|
+
action: 'showNotification',
|
|
34
|
+
title: title,
|
|
35
|
+
body: body,
|
|
36
|
+
payload: payload,
|
|
37
|
+
timeout: timeout,
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
else if ('Notification' in window &&
|
|
41
|
+
Notification.permission === 'granted') {
|
|
42
|
+
// 웹브라우저에서 실행될 때는 기본 브라우저 알림 사용
|
|
43
|
+
new Notification(title, { body: body });
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// 기본 내부 알림 시스템 사용
|
|
47
|
+
this._showNotification('info', body, timeout ? timeout : undefined);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
_showNotification(type, message, timeOut) {
|
|
51
|
+
const id = ulid();
|
|
52
|
+
const notification = { id, type, message, timeOut };
|
|
53
|
+
this.listNotification.push(notification);
|
|
54
|
+
this.listNotification$.next(this.listNotification);
|
|
55
|
+
if (timeOut) {
|
|
56
|
+
setTimeout(() => {
|
|
57
|
+
this.listNotification = this.listNotification.filter((n) => n.id !== id);
|
|
58
|
+
this.listNotification$.next(this.listNotification);
|
|
59
|
+
}, timeOut);
|
|
60
|
+
}
|
|
61
|
+
return id;
|
|
62
|
+
}
|
|
63
|
+
getListNotification() {
|
|
64
|
+
return this.listNotification$.asObservable();
|
|
65
|
+
}
|
|
66
|
+
removeNotificationById(id) {
|
|
67
|
+
this.listNotification = this.listNotification.filter((n) => n.id !== id);
|
|
68
|
+
this.listNotification$.next(this.listNotification);
|
|
69
|
+
}
|
|
70
|
+
constructor() { }
|
|
71
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: NotificationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
72
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: NotificationService, providedIn: 'root' });
|
|
73
|
+
}
|
|
74
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: NotificationService, decorators: [{
|
|
75
|
+
type: Injectable,
|
|
76
|
+
args: [{
|
|
77
|
+
providedIn: 'root',
|
|
78
|
+
}]
|
|
79
|
+
}], ctorParameters: () => [] });
|
|
80
|
+
|
|
81
|
+
class ButtonComponent {
|
|
82
|
+
disabled = false;
|
|
83
|
+
variant = 'primary';
|
|
84
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
85
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: ButtonComponent, isStandalone: true, selector: "haloduck-button", inputs: { disabled: "disabled", variant: "variant" }, ngImport: i0, template: "<button type=\"button\"\n [disabled]=\"disabled\"\n [ngClass]=\"{\n 'bg-light-primary text-light-on-primary dark:bg-dark-primary dark:text-dark-on-primary': variant === 'primary' && !disabled,\n 'bg-light-primary-light text-light-on-primary-light dark:bg-dark-primary-light dark:text-dark-on-primary-light': variant === 'secondary' && !disabled,\n 'bg-light-danger text-light-on-danger dark:bg-dark-danger dark:text-dark-on-danger': variant === 'danger' && !disabled,\n 'bg-light-background text-light-on-background dark:bg-dark-background dark:text-dark-on-background border border-light-on-background dark:border-dark-on-background' : variant === 'none' && !disabled,\n 'bg-light-inactive text-light-on-inactive dark:bg-dark-inactive dark:text-dark-on-inactive active:animate-bounce hover:cursor-not-allowed': disabled,\n 'active:scale-95 transition-transform': !disabled,\n 'hover:cursor-pointer': !disabled\n }\"\n class=\"px-4 py-1.5 rounded-lg text-sm/6 w-full text-nowrap\">\n <ng-content></ng-content>\n</button>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
|
|
86
|
+
}
|
|
87
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ButtonComponent, decorators: [{
|
|
88
|
+
type: Component,
|
|
89
|
+
args: [{ selector: 'haloduck-button', imports: [CommonModule], template: "<button type=\"button\"\n [disabled]=\"disabled\"\n [ngClass]=\"{\n 'bg-light-primary text-light-on-primary dark:bg-dark-primary dark:text-dark-on-primary': variant === 'primary' && !disabled,\n 'bg-light-primary-light text-light-on-primary-light dark:bg-dark-primary-light dark:text-dark-on-primary-light': variant === 'secondary' && !disabled,\n 'bg-light-danger text-light-on-danger dark:bg-dark-danger dark:text-dark-on-danger': variant === 'danger' && !disabled,\n 'bg-light-background text-light-on-background dark:bg-dark-background dark:text-dark-on-background border border-light-on-background dark:border-dark-on-background' : variant === 'none' && !disabled,\n 'bg-light-inactive text-light-on-inactive dark:bg-dark-inactive dark:text-dark-on-inactive active:animate-bounce hover:cursor-not-allowed': disabled,\n 'active:scale-95 transition-transform': !disabled,\n 'hover:cursor-pointer': !disabled\n }\"\n class=\"px-4 py-1.5 rounded-lg text-sm/6 w-full text-nowrap\">\n <ng-content></ng-content>\n</button>\n" }]
|
|
90
|
+
}], propDecorators: { disabled: [{
|
|
91
|
+
type: Input
|
|
92
|
+
}], variant: [{
|
|
93
|
+
type: Input
|
|
94
|
+
}] } });
|
|
95
|
+
|
|
96
|
+
class InputComponent {
|
|
97
|
+
cdr = inject(ChangeDetectorRef);
|
|
98
|
+
label;
|
|
99
|
+
inputElement;
|
|
100
|
+
placeholder = '';
|
|
101
|
+
type = 'text';
|
|
102
|
+
disabled = false;
|
|
103
|
+
rows = 1;
|
|
104
|
+
autofocus = false;
|
|
105
|
+
value = '';
|
|
106
|
+
onChange = (value) => { };
|
|
107
|
+
onTouched = () => { };
|
|
108
|
+
writeValue(value) {
|
|
109
|
+
this.value = value;
|
|
110
|
+
}
|
|
111
|
+
registerOnChange(fn) {
|
|
112
|
+
this.onChange = fn;
|
|
113
|
+
}
|
|
114
|
+
registerOnTouched(fn) {
|
|
115
|
+
this.onTouched = fn;
|
|
116
|
+
}
|
|
117
|
+
setDisabledState(isDisabled) {
|
|
118
|
+
this.disabled = isDisabled;
|
|
119
|
+
}
|
|
120
|
+
focus() {
|
|
121
|
+
this.inputElement?.nativeElement?.focus();
|
|
122
|
+
}
|
|
123
|
+
onInput(event) {
|
|
124
|
+
const input = event.target;
|
|
125
|
+
this.value = input.value;
|
|
126
|
+
this.onChange(this.value);
|
|
127
|
+
this.onTouched();
|
|
128
|
+
if (this.rows > 1) {
|
|
129
|
+
input.style.height = 'auto';
|
|
130
|
+
input.style.height = input.scrollHeight + 'px';
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
onKeydown($event) {
|
|
134
|
+
if (this.rows === 1 && $event.key === 'Enter') {
|
|
135
|
+
$event.preventDefault();
|
|
136
|
+
$event.stopPropagation();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
ngAfterViewInit() {
|
|
140
|
+
// hide label if no content.
|
|
141
|
+
if (!this.label.nativeElement.innerText.trim()) {
|
|
142
|
+
this.label.nativeElement.style.display = 'none';
|
|
143
|
+
}
|
|
144
|
+
if (this.autofocus) {
|
|
145
|
+
// Ensure change detection is complete and view is stable
|
|
146
|
+
this.cdr.detectChanges();
|
|
147
|
+
setTimeout(() => {
|
|
148
|
+
if (this.inputElement?.nativeElement instanceof HTMLElement) {
|
|
149
|
+
this.inputElement.nativeElement.focus();
|
|
150
|
+
}
|
|
151
|
+
}, 100);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: InputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
155
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: InputComponent, isStandalone: true, selector: "haloduck-input", inputs: { placeholder: "placeholder", type: "type", disabled: "disabled", rows: "rows", autofocus: "autofocus", value: "value" }, providers: [
|
|
156
|
+
{
|
|
157
|
+
provide: NG_VALUE_ACCESSOR,
|
|
158
|
+
useExisting: forwardRef(() => InputComponent),
|
|
159
|
+
multi: true,
|
|
160
|
+
},
|
|
161
|
+
provideTranslocoScope('haloduck'),
|
|
162
|
+
], viewQueries: [{ propertyName: "label", first: true, predicate: ["label"], descendants: true }, { propertyName: "inputElement", first: true, predicate: ["input"], descendants: true }], ngImport: i0, template: "<div class=\"flex flex-col gap-2\">\n <label #label\n class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control text-left\">\n <ng-content></ng-content>\n </label>\n @if (rows > 1) {\n <textarea #input\n [value]=\"value\"\n (input)=\"onInput($event)\"\n [disabled]=\"disabled\"\n [placeholder]=\"placeholder\"\n [rows]=\"rows\"\n (keydown)=\"onKeydown($event)\"\n class=\"block w-full rounded-md bg-light-control dark:bg-dark-control disabled:bg-light-control/60 dark:disabled:bg-dark-control/80 px-3 py-1.5 text-base text-light-on-control dark:text-dark-on-control disabled:cursor-not-allowed disabled:text-light-on-control/60 dark:disabled:text-dark-on-control/80 outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive placeholder:text-light-inactive dark:placeholder:text-dark-inactive focus:outline-2 focus:outline-offset-2 focus:outline-light-primary dark:focus:outline-dark-primary sm:text-sm/6\"></textarea>\n }\n @else {\n <input #input\n [type]=\"type\"\n [value]=\"value\"\n (input)=\"onInput($event)\"\n [disabled]=\"disabled\"\n [placeholder]=\"placeholder\"\n (keydown)=\"onKeydown($event)\"\n class=\"block w-full rounded-md bg-light-control dark:bg-dark-control disabled:bg-light-control/60 dark:disabled:bg-dark-control/80 px-3 py-1.5 text-base text-light-on-control dark:text-dark-on-control disabled:cursor-not-allowed disabled:text-light-on-control/60 dark:disabled:text-dark-on-control/80 outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive placeholder:text-light-inactive dark:placeholder:text-dark-inactive focus:outline-2 focus:outline-offset-2 focus:outline-light-primary dark:focus:outline-dark-primary sm:text-sm/6\">\n }\n</div>\n", styles: [""] });
|
|
163
|
+
}
|
|
164
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: InputComponent, decorators: [{
|
|
165
|
+
type: Component,
|
|
166
|
+
args: [{ selector: 'haloduck-input', providers: [
|
|
167
|
+
{
|
|
168
|
+
provide: NG_VALUE_ACCESSOR,
|
|
169
|
+
useExisting: forwardRef(() => InputComponent),
|
|
170
|
+
multi: true,
|
|
171
|
+
},
|
|
172
|
+
provideTranslocoScope('haloduck'),
|
|
173
|
+
], template: "<div class=\"flex flex-col gap-2\">\n <label #label\n class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control text-left\">\n <ng-content></ng-content>\n </label>\n @if (rows > 1) {\n <textarea #input\n [value]=\"value\"\n (input)=\"onInput($event)\"\n [disabled]=\"disabled\"\n [placeholder]=\"placeholder\"\n [rows]=\"rows\"\n (keydown)=\"onKeydown($event)\"\n class=\"block w-full rounded-md bg-light-control dark:bg-dark-control disabled:bg-light-control/60 dark:disabled:bg-dark-control/80 px-3 py-1.5 text-base text-light-on-control dark:text-dark-on-control disabled:cursor-not-allowed disabled:text-light-on-control/60 dark:disabled:text-dark-on-control/80 outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive placeholder:text-light-inactive dark:placeholder:text-dark-inactive focus:outline-2 focus:outline-offset-2 focus:outline-light-primary dark:focus:outline-dark-primary sm:text-sm/6\"></textarea>\n }\n @else {\n <input #input\n [type]=\"type\"\n [value]=\"value\"\n (input)=\"onInput($event)\"\n [disabled]=\"disabled\"\n [placeholder]=\"placeholder\"\n (keydown)=\"onKeydown($event)\"\n class=\"block w-full rounded-md bg-light-control dark:bg-dark-control disabled:bg-light-control/60 dark:disabled:bg-dark-control/80 px-3 py-1.5 text-base text-light-on-control dark:text-dark-on-control disabled:cursor-not-allowed disabled:text-light-on-control/60 dark:disabled:text-dark-on-control/80 outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive placeholder:text-light-inactive dark:placeholder:text-dark-inactive focus:outline-2 focus:outline-offset-2 focus:outline-light-primary dark:focus:outline-dark-primary sm:text-sm/6\">\n }\n</div>\n" }]
|
|
174
|
+
}], propDecorators: { label: [{
|
|
175
|
+
type: ViewChild,
|
|
176
|
+
args: ['label']
|
|
177
|
+
}], inputElement: [{
|
|
178
|
+
type: ViewChild,
|
|
179
|
+
args: ['input']
|
|
180
|
+
}], placeholder: [{
|
|
181
|
+
type: Input
|
|
182
|
+
}], type: [{
|
|
183
|
+
type: Input
|
|
184
|
+
}], disabled: [{
|
|
185
|
+
type: Input
|
|
186
|
+
}], rows: [{
|
|
187
|
+
type: Input
|
|
188
|
+
}], autofocus: [{
|
|
189
|
+
type: Input
|
|
190
|
+
}], value: [{
|
|
191
|
+
type: Input
|
|
192
|
+
}] } });
|
|
193
|
+
|
|
194
|
+
// auth.component.ts
|
|
195
|
+
class AuthenticateComponent {
|
|
196
|
+
fb;
|
|
197
|
+
http;
|
|
198
|
+
notificationService = inject(NotificationService);
|
|
199
|
+
loginForm;
|
|
200
|
+
signupForm;
|
|
201
|
+
resetForm;
|
|
202
|
+
newPasswordForm;
|
|
203
|
+
otpForm;
|
|
204
|
+
stage = 'login';
|
|
205
|
+
emailForReset = '';
|
|
206
|
+
constructor(fb, http) {
|
|
207
|
+
this.fb = fb;
|
|
208
|
+
this.http = http;
|
|
209
|
+
this.loginForm = fb.group({
|
|
210
|
+
email: ['', [Validators.required, Validators.email]],
|
|
211
|
+
password: ['', Validators.required],
|
|
212
|
+
});
|
|
213
|
+
this.signupForm = fb.group({
|
|
214
|
+
email: ['', [Validators.required, Validators.email]],
|
|
215
|
+
password: ['', Validators.required],
|
|
216
|
+
});
|
|
217
|
+
this.resetForm = fb.group({
|
|
218
|
+
email: ['', [Validators.required, Validators.email]],
|
|
219
|
+
});
|
|
220
|
+
this.newPasswordForm = fb.group({
|
|
221
|
+
newPassword: ['', Validators.required],
|
|
222
|
+
});
|
|
223
|
+
this.otpForm = fb.group({
|
|
224
|
+
code: ['', Validators.required],
|
|
225
|
+
newPassword: ['', Validators.required],
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
switchStage(stage) {
|
|
229
|
+
this.stage = stage;
|
|
230
|
+
}
|
|
231
|
+
login() {
|
|
232
|
+
signIn({
|
|
233
|
+
username: this.loginForm.value.email,
|
|
234
|
+
password: this.loginForm.value.password,
|
|
235
|
+
options: {
|
|
236
|
+
authFlowType: 'USER_PASSWORD_AUTH',
|
|
237
|
+
},
|
|
238
|
+
})
|
|
239
|
+
.then((res) => {
|
|
240
|
+
console.log('res', res);
|
|
241
|
+
console.log('res.nextStep.signInStep', res.nextStep.signInStep);
|
|
242
|
+
if (res.nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_NEW_PASSWORD_REQUIRED') {
|
|
243
|
+
this.switchStage('newPassword');
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
this.notificationService.showNotification('success', 'Successfully signed in.', 3000);
|
|
247
|
+
}
|
|
248
|
+
})
|
|
249
|
+
.catch((err) => {
|
|
250
|
+
if (err.code === 'NotAuthorizedException') {
|
|
251
|
+
this.switchStage('reset');
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
this.notificationService.showNotification('error', 'Failed to sign in.');
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
confirmNewPassword() {
|
|
259
|
+
confirmSignIn({
|
|
260
|
+
challengeResponse: this.newPasswordForm.value.newPassword,
|
|
261
|
+
})
|
|
262
|
+
.then(() => {
|
|
263
|
+
this.notificationService.showNotification('success', 'Successfully signed in.', 3000);
|
|
264
|
+
})
|
|
265
|
+
.catch((err) => {
|
|
266
|
+
console.log('err', JSON.stringify(err));
|
|
267
|
+
if (err.code === 'NotAuthorizedException') {
|
|
268
|
+
this.notificationService.showNotification('error', 'Failed to sign in.');
|
|
269
|
+
}
|
|
270
|
+
else if (err.code === 'InvalidPasswordException') {
|
|
271
|
+
this.notificationService.showNotification('error', 'Invalid password.');
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
this.notificationService.showNotification('error', 'Failed to confirm new password.');
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
signup() {
|
|
279
|
+
this.http.post('/api/signup', this.signupForm.value).subscribe(console.log);
|
|
280
|
+
}
|
|
281
|
+
reset() {
|
|
282
|
+
resetPassword({ username: this.resetForm.value.email })
|
|
283
|
+
.then(() => {
|
|
284
|
+
this.switchStage('otpVerify');
|
|
285
|
+
})
|
|
286
|
+
.catch((err) => {
|
|
287
|
+
this.notificationService.showNotification('error', 'Failed to request verification code.');
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
verifyOtp() {
|
|
291
|
+
confirmResetPassword({
|
|
292
|
+
username: this.resetForm.value.email,
|
|
293
|
+
confirmationCode: this.otpForm.value.code,
|
|
294
|
+
newPassword: this.otpForm.value.newPassword,
|
|
295
|
+
})
|
|
296
|
+
.then(() => {
|
|
297
|
+
this.notificationService.showNotification('success', 'Successfully reset password.', 3000);
|
|
298
|
+
this.switchStage('login');
|
|
299
|
+
})
|
|
300
|
+
.catch((err) => {
|
|
301
|
+
this.notificationService.showNotification('error', 'Failed to reset password.');
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
get passwordValue() {
|
|
305
|
+
return this.newPasswordForm.get('newPassword')?.value || '';
|
|
306
|
+
}
|
|
307
|
+
get isMinLength() {
|
|
308
|
+
return this.passwordValue.length >= 8;
|
|
309
|
+
}
|
|
310
|
+
get hasUppercase() {
|
|
311
|
+
return /[A-Z]/.test(this.passwordValue);
|
|
312
|
+
}
|
|
313
|
+
get hasLowercase() {
|
|
314
|
+
return /[a-z]/.test(this.passwordValue);
|
|
315
|
+
}
|
|
316
|
+
get hasNumber() {
|
|
317
|
+
return /[0-9]/.test(this.passwordValue);
|
|
318
|
+
}
|
|
319
|
+
get hasSpecialChar() {
|
|
320
|
+
return /[^A-Za-z0-9]/.test(this.passwordValue);
|
|
321
|
+
}
|
|
322
|
+
get isPasswordValid() {
|
|
323
|
+
return this.isMinLength && this.hasUppercase && this.hasLowercase && this.hasNumber && this.hasSpecialChar;
|
|
324
|
+
}
|
|
325
|
+
get passwordRules() {
|
|
326
|
+
return [
|
|
327
|
+
{ label: 'minimum of 8 characters', valid: this.isMinLength },
|
|
328
|
+
{ label: 'one uppercase letter', valid: this.hasUppercase },
|
|
329
|
+
{ label: 'one lowercase letter', valid: this.hasLowercase },
|
|
330
|
+
{ label: 'one numeric digit', valid: this.hasNumber },
|
|
331
|
+
{ label: 'one special character', valid: this.hasSpecialChar },
|
|
332
|
+
];
|
|
333
|
+
}
|
|
334
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: AuthenticateComponent, deps: [{ token: i1$1.FormBuilder }, { token: i2.HttpClient }], target: i0.ɵɵFactoryTarget.Component });
|
|
335
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: AuthenticateComponent, isStandalone: true, selector: "haloduck-authenticate", ngImport: i0, template: "<div class=\"flex flex-col items-center justify-center h-full\">\n <!-- auth.component.html -->\n <div class=\"w-full max-w-md mx-auto mt-20 p-8 shadow-lg rounded-2xl bg-light-background dark:bg-dark-background\">\n\n @switch (stage) {\n @case ('login') {\n <!-- \uB85C\uADF8\uC778 -->\n <form [formGroup]=\"loginForm\"\n (ngSubmit)=\"login()\"\n class=\"flex flex-col gap-4\">\n <haloduck-input data-testid=\"authenticate-login-email\"\n type=\"email\"\n formControlName=\"email\"\n placeholder=\"Email\" />\n <haloduck-input data-testid=\"authenticate-login-password\"\n type=\"password\"\n formControlName=\"password\"\n placeholder=\"Password\" />\n <haloduck-button data-testid=\"authenticate-login-submit\"\n type=\"submit\"\n variant=\"primary\"\n (click)=\"login()\">Sign In</haloduck-button>\n </form>\n }\n @case ('signup') {\n <!-- \uD68C\uC6D0\uAC00\uC785 -->\n <form [formGroup]=\"signupForm\"\n (ngSubmit)=\"signup()\"\n class=\"flex flex-col gap-4\">\n <haloduck-input data-testid=\"authenticate-signup-email\"\n type=\"email\"\n formControlName=\"email\"\n placeholder=\"Email\" />\n <haloduck-input data-testid=\"authenticate-signup-password\"\n type=\"password\"\n formControlName=\"password\"\n placeholder=\"Password\" />\n <haloduck-button data-testid=\"authenticate-signup-submit\"\n type=\"submit\"\n variant=\"primary\">Sign Up</haloduck-button>\n </form>\n }\n @case ('reset') {\n <!-- \uBE44\uBC00\uBC88\uD638 \uC7AC\uC124\uC815 \uC694\uCCAD -->\n <form [formGroup]=\"resetForm\"\n (ngSubmit)=\"reset()\"\n class=\"flex flex-col gap-4\">\n <haloduck-input data-testid=\"authenticate-reset-email\"\n type=\"email\"\n formControlName=\"email\"\n placeholder=\"Email\" />\n <haloduck-button data-testid=\"authenticate-reset-submit\"\n type=\"submit\"\n variant=\"primary\"\n (click)=\"reset()\">Send Verification Code</haloduck-button>\n </form>\n }\n @case ('otpVerify') {\n <!-- OTP \uC778\uC99D \uD6C4 \uC0C8 \uBE44\uBC00\uBC88\uD638 \uC785\uB825 -->\n <form [formGroup]=\"otpForm\"\n (ngSubmit)=\"verifyOtp()\"\n class=\"flex flex-col gap-4\">\n <haloduck-input data-testid=\"authenticate-otp-code\"\n type=\"text\"\n formControlName=\"code\"\n placeholder=\"Verification Code\" />\n <haloduck-input data-testid=\"authenticate-otp-password\"\n type=\"password\"\n formControlName=\"newPassword\"\n placeholder=\"New password\" />\n <haloduck-button data-testid=\"authenticate-otp-submit\"\n type=\"submit\"\n variant=\"primary\"\n (click)=\"verifyOtp()\">Reset Password</haloduck-button>\n </form>\n\n }\n @case ('newPassword') {\n <!-- \uC784\uC2DC \uBE44\uBC00\uBC88\uD638 \u2192 \uC0C8 \uBE44\uBC00\uBC88\uD638 \uBCC0\uACBD -->\n <form [formGroup]=\"newPasswordForm\"\n (ngSubmit)=\"confirmNewPassword()\"\n class=\"flex flex-col gap-4\">\n <haloduck-input data-testid=\"authenticate-new-password-password\"\n type=\"password\"\n formControlName=\"newPassword\"\n placeholder=\"New password\">\n </haloduck-input>\n <div class=\"text-sm flex flex-col\">\n @for (rule of passwordRules; track rule.label) {\n <span [class.text-light-secondary]=\"rule.valid && passwordValue !== ''\"\n [class.line-through]=\"rule.valid && passwordValue !== ''\"\n [class.text-light-danger]=\"!rule.valid && passwordValue !== ''\"\n [class.text-light-inactive]=\"passwordValue === ''\">\n {{ rule.valid ? '\u2714' : '\u2717' }} {{ rule.label }}\n </span>\n }\n </div>\n <haloduck-button data-testid=\"authenticate-new-password-submit\"\n type=\"submit\"\n (click)=\"confirmNewPassword()\"\n variant=\"primary\"\n [disabled]=\"!isPasswordValid\">Change Password</haloduck-button>\n </form>\n }\n }\n\n <!-- \uB2E8\uACC4 \uC804\uD658 \uD0ED -->\n <div class=\"flex justify-center mt-6 gap-4\">\n @if (stage !== 'login') {\n <haloduck-button data-testid=\"authenticate-to-signin\"\n variant=\"none\"\n (click)=\"switchStage('login')\">to Sign In</haloduck-button>\n }\n <!-- <haloduck-button class=\"text-sm text-blue-600 hover:underline\"\n (click)=\"switchStage('signup')\">\uD68C\uC6D0\uAC00\uC785</haloduck-button> -->\n @if (stage !== 'reset' && stage !== 'otpVerify' && stage !== 'newPassword') {\n <haloduck-button data-testid=\"authenticate-to-reset-password\"\n variant=\"secondary\"\n (click)=\"switchStage('reset')\">Reset Password</haloduck-button>\n }\n </div>\n\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: InputComponent, selector: "haloduck-input", inputs: ["placeholder", "type", "disabled", "rows", "autofocus", "value"] }, { kind: "component", type: ButtonComponent, selector: "haloduck-button", inputs: ["disabled", "variant"] }] });
|
|
336
|
+
}
|
|
337
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: AuthenticateComponent, decorators: [{
|
|
338
|
+
type: Component,
|
|
339
|
+
args: [{ selector: 'haloduck-authenticate', imports: [FormsModule, ReactiveFormsModule, InputComponent, ButtonComponent], template: "<div class=\"flex flex-col items-center justify-center h-full\">\n <!-- auth.component.html -->\n <div class=\"w-full max-w-md mx-auto mt-20 p-8 shadow-lg rounded-2xl bg-light-background dark:bg-dark-background\">\n\n @switch (stage) {\n @case ('login') {\n <!-- \uB85C\uADF8\uC778 -->\n <form [formGroup]=\"loginForm\"\n (ngSubmit)=\"login()\"\n class=\"flex flex-col gap-4\">\n <haloduck-input data-testid=\"authenticate-login-email\"\n type=\"email\"\n formControlName=\"email\"\n placeholder=\"Email\" />\n <haloduck-input data-testid=\"authenticate-login-password\"\n type=\"password\"\n formControlName=\"password\"\n placeholder=\"Password\" />\n <haloduck-button data-testid=\"authenticate-login-submit\"\n type=\"submit\"\n variant=\"primary\"\n (click)=\"login()\">Sign In</haloduck-button>\n </form>\n }\n @case ('signup') {\n <!-- \uD68C\uC6D0\uAC00\uC785 -->\n <form [formGroup]=\"signupForm\"\n (ngSubmit)=\"signup()\"\n class=\"flex flex-col gap-4\">\n <haloduck-input data-testid=\"authenticate-signup-email\"\n type=\"email\"\n formControlName=\"email\"\n placeholder=\"Email\" />\n <haloduck-input data-testid=\"authenticate-signup-password\"\n type=\"password\"\n formControlName=\"password\"\n placeholder=\"Password\" />\n <haloduck-button data-testid=\"authenticate-signup-submit\"\n type=\"submit\"\n variant=\"primary\">Sign Up</haloduck-button>\n </form>\n }\n @case ('reset') {\n <!-- \uBE44\uBC00\uBC88\uD638 \uC7AC\uC124\uC815 \uC694\uCCAD -->\n <form [formGroup]=\"resetForm\"\n (ngSubmit)=\"reset()\"\n class=\"flex flex-col gap-4\">\n <haloduck-input data-testid=\"authenticate-reset-email\"\n type=\"email\"\n formControlName=\"email\"\n placeholder=\"Email\" />\n <haloduck-button data-testid=\"authenticate-reset-submit\"\n type=\"submit\"\n variant=\"primary\"\n (click)=\"reset()\">Send Verification Code</haloduck-button>\n </form>\n }\n @case ('otpVerify') {\n <!-- OTP \uC778\uC99D \uD6C4 \uC0C8 \uBE44\uBC00\uBC88\uD638 \uC785\uB825 -->\n <form [formGroup]=\"otpForm\"\n (ngSubmit)=\"verifyOtp()\"\n class=\"flex flex-col gap-4\">\n <haloduck-input data-testid=\"authenticate-otp-code\"\n type=\"text\"\n formControlName=\"code\"\n placeholder=\"Verification Code\" />\n <haloduck-input data-testid=\"authenticate-otp-password\"\n type=\"password\"\n formControlName=\"newPassword\"\n placeholder=\"New password\" />\n <haloduck-button data-testid=\"authenticate-otp-submit\"\n type=\"submit\"\n variant=\"primary\"\n (click)=\"verifyOtp()\">Reset Password</haloduck-button>\n </form>\n\n }\n @case ('newPassword') {\n <!-- \uC784\uC2DC \uBE44\uBC00\uBC88\uD638 \u2192 \uC0C8 \uBE44\uBC00\uBC88\uD638 \uBCC0\uACBD -->\n <form [formGroup]=\"newPasswordForm\"\n (ngSubmit)=\"confirmNewPassword()\"\n class=\"flex flex-col gap-4\">\n <haloduck-input data-testid=\"authenticate-new-password-password\"\n type=\"password\"\n formControlName=\"newPassword\"\n placeholder=\"New password\">\n </haloduck-input>\n <div class=\"text-sm flex flex-col\">\n @for (rule of passwordRules; track rule.label) {\n <span [class.text-light-secondary]=\"rule.valid && passwordValue !== ''\"\n [class.line-through]=\"rule.valid && passwordValue !== ''\"\n [class.text-light-danger]=\"!rule.valid && passwordValue !== ''\"\n [class.text-light-inactive]=\"passwordValue === ''\">\n {{ rule.valid ? '\u2714' : '\u2717' }} {{ rule.label }}\n </span>\n }\n </div>\n <haloduck-button data-testid=\"authenticate-new-password-submit\"\n type=\"submit\"\n (click)=\"confirmNewPassword()\"\n variant=\"primary\"\n [disabled]=\"!isPasswordValid\">Change Password</haloduck-button>\n </form>\n }\n }\n\n <!-- \uB2E8\uACC4 \uC804\uD658 \uD0ED -->\n <div class=\"flex justify-center mt-6 gap-4\">\n @if (stage !== 'login') {\n <haloduck-button data-testid=\"authenticate-to-signin\"\n variant=\"none\"\n (click)=\"switchStage('login')\">to Sign In</haloduck-button>\n }\n <!-- <haloduck-button class=\"text-sm text-blue-600 hover:underline\"\n (click)=\"switchStage('signup')\">\uD68C\uC6D0\uAC00\uC785</haloduck-button> -->\n @if (stage !== 'reset' && stage !== 'otpVerify' && stage !== 'newPassword') {\n <haloduck-button data-testid=\"authenticate-to-reset-password\"\n variant=\"secondary\"\n (click)=\"switchStage('reset')\">Reset Password</haloduck-button>\n }\n </div>\n\n </div>\n</div>\n" }]
|
|
340
|
+
}], ctorParameters: () => [{ type: i1$1.FormBuilder }, { type: i2.HttpClient }] });
|
|
341
|
+
|
|
342
|
+
class SelectDropdownComponent {
|
|
343
|
+
_filteredOptions = signal([], ...(ngDevMode ? [{ debugName: "_filteredOptions" }] : []));
|
|
344
|
+
_options = [];
|
|
345
|
+
_selectedOptionIds = [];
|
|
346
|
+
selectedChange = new EventEmitter();
|
|
347
|
+
useFilter = false;
|
|
348
|
+
multiselect = true;
|
|
349
|
+
atLeastOne = false;
|
|
350
|
+
asButton = false;
|
|
351
|
+
set options(value) {
|
|
352
|
+
this._options = value || [];
|
|
353
|
+
this._filteredOptions.set(this._options);
|
|
354
|
+
}
|
|
355
|
+
get options() {
|
|
356
|
+
return this._filteredOptions();
|
|
357
|
+
}
|
|
358
|
+
set selectedOptionIds(value) {
|
|
359
|
+
this._selectedOptionIds = value || [];
|
|
360
|
+
}
|
|
361
|
+
get selectedOptions() {
|
|
362
|
+
return this._selectedOptionIds;
|
|
363
|
+
}
|
|
364
|
+
onFilterInput(event) {
|
|
365
|
+
const value = event.target.value.toLowerCase();
|
|
366
|
+
this._filteredOptions.set(this._options.filter((opt) => opt.value.toLowerCase().includes(value)));
|
|
367
|
+
}
|
|
368
|
+
onToggleOption(option) {
|
|
369
|
+
if (this.asButton) {
|
|
370
|
+
this._selectedOptionIds = [option.id || option.value];
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
if (this.multiselect) {
|
|
374
|
+
if (!this._selectedOptionIds.includes(option.id || option.value)) {
|
|
375
|
+
this._selectedOptionIds.push(option.id || option.value);
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
this._selectedOptionIds.splice(this._selectedOptionIds.indexOf(option.id || option.value), 1);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
if (this.atLeastOne) {
|
|
383
|
+
this._selectedOptionIds = [option.id || option.value];
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
if (this._selectedOptionIds.includes(option.id || option.value)) {
|
|
387
|
+
this._selectedOptionIds = [];
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
this._selectedOptionIds = [option.id || option.value];
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
// prevent [null]
|
|
396
|
+
this._selectedOptionIds = this._selectedOptionIds.filter((id) => id !== null);
|
|
397
|
+
this.selectedChange.emit(this._selectedOptionIds);
|
|
398
|
+
}
|
|
399
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: SelectDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
400
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: SelectDropdownComponent, isStandalone: true, selector: "haloduck-select-dropdown", inputs: { useFilter: "useFilter", multiselect: "multiselect", atLeastOne: "atLeastOne", asButton: "asButton", options: "options", selectedOptionIds: "selectedOptionIds" }, outputs: { selectedChange: "selectedChange" }, providers: [provideTranslocoScope('haloduck')], ngImport: i0, template: "<div id=\"dropdown\"\n class=\"max-w-full mt-2 absolute z-40 bg-light-background dark:bg-dark-background text-light-on-background dark:text-dark-on-background border border-light-inactive dark:border-dark-inactive rounded max-h-60 flex flex-col gap-2\">\n @if (useFilter && _options.length >= 5) {\n <input #inputFilter\n id=\"inputFilter\"\n type=\"text\"\n [placeholder]=\"'haloduck.ui.select.Keyword...' | transloco\"\n (input)=\"onFilterInput($event)\"\n class=\"text-light-inactive dark:text-dark-inactive rounded-md outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive focus:outline-2 focus:outline-offset-2 focus:outline-light-primary dark:focus:outline-dark-primary px-3 py-1.5 text-sm/6 bg-light-control dark:bg-dark-control m-2\" />\n }\n <div class=\"overflow-y-auto\">\n @for ( option of _filteredOptions(); track (option.id) ? option.id : option.value) {\n <div class=\"px-3 py-2 text-sm/6 cursor-pointer hover:bg-light-secondary/60 dark:hover:bg-dark-secondary/60 flex items-center justify-start whitespace-nowrap\"\n (click)=\"onToggleOption(option)\">\n @if(!asButton) {\n @if( _selectedOptionIds.includes(option.id || option.value)) {\n <svg class=\"w-4 h-4 text-light-primary dark:text-dark-primary inline-block mr-2\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M16.707 5.293a1 1 0 00-1.414 0L8 12.586 4.707 9.293a1 1 0 00-1.414 1.414l4 4a1 1 0 001.414 0l8-8a1 1 0 000-1.414z\"\n clip-rule=\"evenodd\" />\n </svg>\n } @else {\n <div class=\"w-4 h-4 inline-block mr-2\"></div>\n }\n }\n {{ option.value }}\n </div>\n }\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i1$2.TranslocoPipe, name: "transloco" }] });
|
|
401
|
+
}
|
|
402
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: SelectDropdownComponent, decorators: [{
|
|
403
|
+
type: Component,
|
|
404
|
+
args: [{ selector: 'haloduck-select-dropdown', imports: [TranslocoModule], providers: [provideTranslocoScope('haloduck')], template: "<div id=\"dropdown\"\n class=\"max-w-full mt-2 absolute z-40 bg-light-background dark:bg-dark-background text-light-on-background dark:text-dark-on-background border border-light-inactive dark:border-dark-inactive rounded max-h-60 flex flex-col gap-2\">\n @if (useFilter && _options.length >= 5) {\n <input #inputFilter\n id=\"inputFilter\"\n type=\"text\"\n [placeholder]=\"'haloduck.ui.select.Keyword...' | transloco\"\n (input)=\"onFilterInput($event)\"\n class=\"text-light-inactive dark:text-dark-inactive rounded-md outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive focus:outline-2 focus:outline-offset-2 focus:outline-light-primary dark:focus:outline-dark-primary px-3 py-1.5 text-sm/6 bg-light-control dark:bg-dark-control m-2\" />\n }\n <div class=\"overflow-y-auto\">\n @for ( option of _filteredOptions(); track (option.id) ? option.id : option.value) {\n <div class=\"px-3 py-2 text-sm/6 cursor-pointer hover:bg-light-secondary/60 dark:hover:bg-dark-secondary/60 flex items-center justify-start whitespace-nowrap\"\n (click)=\"onToggleOption(option)\">\n @if(!asButton) {\n @if( _selectedOptionIds.includes(option.id || option.value)) {\n <svg class=\"w-4 h-4 text-light-primary dark:text-dark-primary inline-block mr-2\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M16.707 5.293a1 1 0 00-1.414 0L8 12.586 4.707 9.293a1 1 0 00-1.414 1.414l4 4a1 1 0 001.414 0l8-8a1 1 0 000-1.414z\"\n clip-rule=\"evenodd\" />\n </svg>\n } @else {\n <div class=\"w-4 h-4 inline-block mr-2\"></div>\n }\n }\n {{ option.value }}\n </div>\n }\n </div>\n</div>\n" }]
|
|
405
|
+
}], propDecorators: { selectedChange: [{
|
|
406
|
+
type: Output
|
|
407
|
+
}], useFilter: [{
|
|
408
|
+
type: Input
|
|
409
|
+
}], multiselect: [{
|
|
410
|
+
type: Input
|
|
411
|
+
}], atLeastOne: [{
|
|
412
|
+
type: Input
|
|
413
|
+
}], asButton: [{
|
|
414
|
+
type: Input
|
|
415
|
+
}], options: [{
|
|
416
|
+
type: Input
|
|
417
|
+
}], selectedOptionIds: [{
|
|
418
|
+
type: Input
|
|
419
|
+
}] } });
|
|
420
|
+
|
|
421
|
+
class SelectComponent {
|
|
422
|
+
translateService = inject(TranslocoService);
|
|
423
|
+
overlay = inject(Overlay);
|
|
424
|
+
viewContainerRef = inject(ViewContainerRef);
|
|
425
|
+
overlayRef = null;
|
|
426
|
+
selectedChange = new EventEmitter();
|
|
427
|
+
disabled = false;
|
|
428
|
+
loading = false;
|
|
429
|
+
variant = 'none';
|
|
430
|
+
asButton = false;
|
|
431
|
+
useIcon = false;
|
|
432
|
+
useFilter = false;
|
|
433
|
+
multiselect = true;
|
|
434
|
+
placeholder = '';
|
|
435
|
+
atLeastOne = false;
|
|
436
|
+
showAll = false;
|
|
437
|
+
options = null;
|
|
438
|
+
set value(value) {
|
|
439
|
+
this.writeValue(value);
|
|
440
|
+
this.onChange(this.getSelectedValue());
|
|
441
|
+
}
|
|
442
|
+
get value() {
|
|
443
|
+
if (this.multiselect) {
|
|
444
|
+
return this.selectedOptionIds;
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
if (this.selectedOptionIds.length === 0) {
|
|
448
|
+
return null;
|
|
449
|
+
}
|
|
450
|
+
return this.selectedOptionIds[0];
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
origin;
|
|
454
|
+
label;
|
|
455
|
+
isDropdownOpen = signal(false, ...(ngDevMode ? [{ debugName: "isDropdownOpen" }] : []));
|
|
456
|
+
selectedOptionIds = [];
|
|
457
|
+
get selectedOptions() {
|
|
458
|
+
if (this.options) {
|
|
459
|
+
return this.options.filter((option) => this.selectedOptionIds.includes(option.id || option.value));
|
|
460
|
+
}
|
|
461
|
+
return [];
|
|
462
|
+
}
|
|
463
|
+
onClick(event) {
|
|
464
|
+
if (this.disabled) {
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
const originWidth = this.origin?.nativeElement?.offsetWidth || 0;
|
|
468
|
+
if (!this.overlayRef) {
|
|
469
|
+
this.overlayRef = this.overlay.create({
|
|
470
|
+
positionStrategy: this.overlay
|
|
471
|
+
.position()
|
|
472
|
+
.flexibleConnectedTo(this.origin.nativeElement)
|
|
473
|
+
.withPositions([
|
|
474
|
+
{
|
|
475
|
+
originX: 'start',
|
|
476
|
+
originY: 'bottom',
|
|
477
|
+
overlayX: 'start',
|
|
478
|
+
overlayY: 'top',
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
originX: 'start',
|
|
482
|
+
originY: 'top',
|
|
483
|
+
overlayX: 'start',
|
|
484
|
+
overlayY: 'bottom',
|
|
485
|
+
},
|
|
486
|
+
]),
|
|
487
|
+
scrollStrategy: this.overlay.scrollStrategies.reposition(),
|
|
488
|
+
hasBackdrop: true,
|
|
489
|
+
backdropClass: 'cdk-overlay-transparent-backdrop',
|
|
490
|
+
minWidth: originWidth,
|
|
491
|
+
});
|
|
492
|
+
this.overlayRef.backdropClick().subscribe(() => {
|
|
493
|
+
this.isDropdownOpen.set(false);
|
|
494
|
+
this.overlayRef?.detach();
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
if (!this.overlayRef.hasAttached()) {
|
|
498
|
+
const portal = new ComponentPortal(SelectDropdownComponent, this.viewContainerRef);
|
|
499
|
+
const componentRef = this.overlayRef.attach(portal);
|
|
500
|
+
componentRef.instance.options = this.options;
|
|
501
|
+
componentRef.instance.selectedOptionIds = this.selectedOptionIds;
|
|
502
|
+
componentRef.instance.useFilter = this.useFilter;
|
|
503
|
+
componentRef.instance.multiselect = this.multiselect;
|
|
504
|
+
componentRef.instance.atLeastOne = this.atLeastOne;
|
|
505
|
+
componentRef.instance.asButton = this.asButton;
|
|
506
|
+
componentRef.instance.selectedChange.subscribe((selectedOptionIds) => {
|
|
507
|
+
this.selectedChange.emit(selectedOptionIds);
|
|
508
|
+
this.selectedOptionIds = selectedOptionIds;
|
|
509
|
+
this.onChange(this.getSelectedValue());
|
|
510
|
+
if (!this.multiselect) {
|
|
511
|
+
this.isDropdownOpen.set(false);
|
|
512
|
+
this.overlayRef?.detach();
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
this.isDropdownOpen.set(true);
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
this.isDropdownOpen.set(false);
|
|
519
|
+
this.overlayRef.detach();
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
onKeyDown(event) {
|
|
523
|
+
if (this.disabled) {
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
if (event.key === 'Enter') {
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
onDeselectOption(event, option) {
|
|
530
|
+
if (this.disabled) {
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
event.stopPropagation();
|
|
534
|
+
event.preventDefault();
|
|
535
|
+
this.selectedOptionIds = this.selectedOptionIds.filter((selected) => selected !== (option.id || option.value));
|
|
536
|
+
this.onChange(this.getSelectedValue());
|
|
537
|
+
this.selectedChange.emit(this.selectedOptionIds);
|
|
538
|
+
this.isDropdownOpen.set(false);
|
|
539
|
+
}
|
|
540
|
+
getSelectedValue() {
|
|
541
|
+
if (!this.multiselect) {
|
|
542
|
+
if (this.selectedOptionIds.length > 0) {
|
|
543
|
+
return this.selectedOptionIds[0];
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
return null;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
return this.selectedOptionIds;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
onChange = (value) => { };
|
|
554
|
+
onTouched = () => { };
|
|
555
|
+
writeValue(value) {
|
|
556
|
+
if (Array.isArray(value)) {
|
|
557
|
+
this.selectedOptionIds = value;
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
this.selectedOptionIds = [value];
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
registerOnChange(fn) {
|
|
564
|
+
this.onChange = fn;
|
|
565
|
+
}
|
|
566
|
+
registerOnTouched(fn) {
|
|
567
|
+
this.onTouched = fn;
|
|
568
|
+
}
|
|
569
|
+
setDisabledState(isDisabled) {
|
|
570
|
+
this.disabled = isDisabled;
|
|
571
|
+
}
|
|
572
|
+
ngAfterViewInit() {
|
|
573
|
+
// hide label if no content.
|
|
574
|
+
if (!this.label.nativeElement.innerText.trim()) {
|
|
575
|
+
this.label.nativeElement.style.display = 'none';
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
constructor() {
|
|
579
|
+
this.translateService
|
|
580
|
+
.selectTranslate('ui.select.Select...', {}, 'haloduck')
|
|
581
|
+
.subscribe((translation) => {
|
|
582
|
+
if (!this.placeholder || this.placeholder === '') {
|
|
583
|
+
this.placeholder = translation;
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: SelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
588
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: SelectComponent, isStandalone: true, selector: "haloduck-select", inputs: { disabled: "disabled", loading: "loading", variant: "variant", asButton: "asButton", useIcon: "useIcon", useFilter: "useFilter", multiselect: "multiselect", placeholder: "placeholder", atLeastOne: "atLeastOne", showAll: "showAll", options: "options", value: "value" }, outputs: { selectedChange: "selectedChange" }, providers: [
|
|
589
|
+
{
|
|
590
|
+
provide: NG_VALUE_ACCESSOR,
|
|
591
|
+
useExisting: forwardRef(() => SelectComponent),
|
|
592
|
+
multi: true,
|
|
593
|
+
},
|
|
594
|
+
provideTranslocoScope('haloduck'),
|
|
595
|
+
], viewQueries: [{ propertyName: "origin", first: true, predicate: ["origin"], descendants: true }, { propertyName: "label", first: true, predicate: ["label"], descendants: true }], ngImport: i0, template: "<div class=\"flex flex-col gap-2 items-start w-full\">\n <label #label\n class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control w-full\">\n <ng-content></ng-content>\n </label>\n <div #origin\n class=\"w-full flex-1 relative overflow-visible rounded-md outline outline-light-inactive dark:outline-dark-inactive text-sm/6\"\n [ngClass]=\"{\n 'bg-light-control dark:bg-dark-control focus:outline-2 focus:outline-offset-2 focus:outline-light-primary focus:dark:outline-dark-primary': !disabled,\n 'bg-light-primary dark:bg-dark-primary text-light-on-primary dark:text-dark-on-primary' : variant === 'primary' && !disabled,\n 'bg-light-secondary dark:bg-dark-secondary text-light-on-secondary dark:text-dark-on-secondary' : variant === 'secondary' && !disabled,\n 'bg-light-danger dark:bg-dark-danger text-light-on-danger dark:text-dark-on-danger' : variant === 'danger' && !disabled,\n 'bg-light-control/60 dark:bg-dark-control/60 text-light-on-control/60 dark:text-dark-on-control/60' : disabled,\n }\"\n [tabindex]=\"(disabled) ? -1 : 0\"\n (click)=\"onClick($event)\"\n (keydown)=\"onKeyDown($event)\">\n <div class=\"px-3 py-1.5 text-sm/6 cursor-pointer flex flex-nowrap items-center justify-between overflow-hidden\">\n @if(loading){\n <div class=\"flex-1 flex items-center gap-2 w-full h-3\">\n <div class=\"animate-pulse bg-light-inactive dark:bg-dark-inactive rounded-md h-5 w-full\"> </div>\n </div>\n }\n @else{\n <div class=\"flex-1\">\n @if (!asButton && selectedOptions && selectedOptions.length > 0) {\n @if (multiselect) {\n <div class=\"flex flex-wrap gap-x-2 gap-y-1 items-start\">\n @for (option of selectedOptions; track option; let i = $index) {\n @if (showAll || i === 0) {\n <span class=\"bg-light-secondary dark:bg-dark-secondary rounded-md flex items-center text-xs/6 text-light-on-secondary dark:text-dark-on-secondary\"\n [ngClass]=\"{'w-full h-full px-4': !multiselect, 'px-1': multiselect}\">\n {{ option.value }}\n @if (showAll || i === 0) {\n <div (click)=\"onDeselectOption($event, option)\"\n class=\"ml-2 text-light-danger dark:text-dark-danger hover:cursor-pointer\">\n <svg class=\"w-3 h-3\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M10 8.586l-2.293-2.293a1 1 0 00-1.414 1.414L8.586 10l-2.293 2.293a1 1 0 001.414 1.414L10 11.414l2.293 2.293a1 1 0 001.414-1.414L11.414 10l2.293-2.293a1 1 0 00-1.414-1.414L10 8.586z\"\n clip-rule=\"evenodd\" />\n </svg>\n </div>\n }\n </span>\n @if (!showAll && selectedOptions.length > 1) {\n <span class=\"text-light-on-control/80 dark:text-dark-on-control/80 text-xs/6\">\n +{{ selectedOptions.length - 1 }}\n </span>\n }\n }\n }\n </div>\n } @else {\n <span class=\"text-light-on-control dark:text-dark-on-control overflow-hidden overflow-ellipsis\"\n [ngClass]=\"{\n 'text-light-on-control/60 dark:text-dark-on-control/60': disabled,\n 'text-light-on-primary dark:text-dark-on-primary': variant === 'primary' && !disabled,\n 'text-light-on-secondary dark:text-dark-on-secondary': variant === 'secondary' && !disabled,\n 'text-light-on-danger dark:text-dark-on-danger': variant === 'danger' && !disabled,\n }\">{{ selectedOptions[0].value }}</span>\n }\n } @else {\n <span class=\"text-inactive overflow-hidden overflow-ellipsis\"\n [ngClass]=\"{\n 'text-light-on-control/60 dark:text-dark-on-control/60': disabled,\n 'text-light-on-primary/80 dark:text-dark-on-primary/80': variant === 'primary' && !disabled,\n 'text-light-on-secondary/80 dark:text-dark-on-secondary/80': variant === 'secondary' && !disabled,\n 'text-light-on-danger/80 dark:text-dark-on-danger/80': variant === 'danger' && !disabled,\n }\">\n @if (useIcon) {\n <ng-content selector=\"buttonIcon\"></ng-content>\n } @else {\n {{ placeholder }}\n }\n </span>\n }\n </div>\n }\n <div class=\"text-light-on-control dark:text-dark-on-control\"\n [ngClass]=\"{\n 'text-light-on-control/60 dark:text-dark-on-control/60': disabled,\n 'text-light-on-primary/80 dark:text-dark-on-primary/80': variant === 'primary' && !disabled,\n 'text-light-on-secondary/80 dark:text-dark-on-secondary/80': variant === 'secondary' && !disabled,\n 'text-light-on-danger/80 dark:text-dark-on-danger/80': variant === 'danger' && !disabled,\n }\">\n @if (isDropdownOpen()) {\n <svg class=\"w-4 h-4 ml-2 inline-block\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z\"\n clip-rule=\"evenodd\" />\n </svg>\n } @else {\n <svg class=\"w-4 h-4 ml-2 inline-block\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 011.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z\"\n clip-rule=\"evenodd\" />\n </svg>\n }\n </div>\n </div>\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
|
|
596
|
+
}
|
|
597
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: SelectComponent, decorators: [{
|
|
598
|
+
type: Component,
|
|
599
|
+
args: [{ selector: 'haloduck-select', imports: [CommonModule], providers: [
|
|
600
|
+
{
|
|
601
|
+
provide: NG_VALUE_ACCESSOR,
|
|
602
|
+
useExisting: forwardRef(() => SelectComponent),
|
|
603
|
+
multi: true,
|
|
604
|
+
},
|
|
605
|
+
provideTranslocoScope('haloduck'),
|
|
606
|
+
], template: "<div class=\"flex flex-col gap-2 items-start w-full\">\n <label #label\n class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control w-full\">\n <ng-content></ng-content>\n </label>\n <div #origin\n class=\"w-full flex-1 relative overflow-visible rounded-md outline outline-light-inactive dark:outline-dark-inactive text-sm/6\"\n [ngClass]=\"{\n 'bg-light-control dark:bg-dark-control focus:outline-2 focus:outline-offset-2 focus:outline-light-primary focus:dark:outline-dark-primary': !disabled,\n 'bg-light-primary dark:bg-dark-primary text-light-on-primary dark:text-dark-on-primary' : variant === 'primary' && !disabled,\n 'bg-light-secondary dark:bg-dark-secondary text-light-on-secondary dark:text-dark-on-secondary' : variant === 'secondary' && !disabled,\n 'bg-light-danger dark:bg-dark-danger text-light-on-danger dark:text-dark-on-danger' : variant === 'danger' && !disabled,\n 'bg-light-control/60 dark:bg-dark-control/60 text-light-on-control/60 dark:text-dark-on-control/60' : disabled,\n }\"\n [tabindex]=\"(disabled) ? -1 : 0\"\n (click)=\"onClick($event)\"\n (keydown)=\"onKeyDown($event)\">\n <div class=\"px-3 py-1.5 text-sm/6 cursor-pointer flex flex-nowrap items-center justify-between overflow-hidden\">\n @if(loading){\n <div class=\"flex-1 flex items-center gap-2 w-full h-3\">\n <div class=\"animate-pulse bg-light-inactive dark:bg-dark-inactive rounded-md h-5 w-full\"> </div>\n </div>\n }\n @else{\n <div class=\"flex-1\">\n @if (!asButton && selectedOptions && selectedOptions.length > 0) {\n @if (multiselect) {\n <div class=\"flex flex-wrap gap-x-2 gap-y-1 items-start\">\n @for (option of selectedOptions; track option; let i = $index) {\n @if (showAll || i === 0) {\n <span class=\"bg-light-secondary dark:bg-dark-secondary rounded-md flex items-center text-xs/6 text-light-on-secondary dark:text-dark-on-secondary\"\n [ngClass]=\"{'w-full h-full px-4': !multiselect, 'px-1': multiselect}\">\n {{ option.value }}\n @if (showAll || i === 0) {\n <div (click)=\"onDeselectOption($event, option)\"\n class=\"ml-2 text-light-danger dark:text-dark-danger hover:cursor-pointer\">\n <svg class=\"w-3 h-3\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M10 8.586l-2.293-2.293a1 1 0 00-1.414 1.414L8.586 10l-2.293 2.293a1 1 0 001.414 1.414L10 11.414l2.293 2.293a1 1 0 001.414-1.414L11.414 10l2.293-2.293a1 1 0 00-1.414-1.414L10 8.586z\"\n clip-rule=\"evenodd\" />\n </svg>\n </div>\n }\n </span>\n @if (!showAll && selectedOptions.length > 1) {\n <span class=\"text-light-on-control/80 dark:text-dark-on-control/80 text-xs/6\">\n +{{ selectedOptions.length - 1 }}\n </span>\n }\n }\n }\n </div>\n } @else {\n <span class=\"text-light-on-control dark:text-dark-on-control overflow-hidden overflow-ellipsis\"\n [ngClass]=\"{\n 'text-light-on-control/60 dark:text-dark-on-control/60': disabled,\n 'text-light-on-primary dark:text-dark-on-primary': variant === 'primary' && !disabled,\n 'text-light-on-secondary dark:text-dark-on-secondary': variant === 'secondary' && !disabled,\n 'text-light-on-danger dark:text-dark-on-danger': variant === 'danger' && !disabled,\n }\">{{ selectedOptions[0].value }}</span>\n }\n } @else {\n <span class=\"text-inactive overflow-hidden overflow-ellipsis\"\n [ngClass]=\"{\n 'text-light-on-control/60 dark:text-dark-on-control/60': disabled,\n 'text-light-on-primary/80 dark:text-dark-on-primary/80': variant === 'primary' && !disabled,\n 'text-light-on-secondary/80 dark:text-dark-on-secondary/80': variant === 'secondary' && !disabled,\n 'text-light-on-danger/80 dark:text-dark-on-danger/80': variant === 'danger' && !disabled,\n }\">\n @if (useIcon) {\n <ng-content selector=\"buttonIcon\"></ng-content>\n } @else {\n {{ placeholder }}\n }\n </span>\n }\n </div>\n }\n <div class=\"text-light-on-control dark:text-dark-on-control\"\n [ngClass]=\"{\n 'text-light-on-control/60 dark:text-dark-on-control/60': disabled,\n 'text-light-on-primary/80 dark:text-dark-on-primary/80': variant === 'primary' && !disabled,\n 'text-light-on-secondary/80 dark:text-dark-on-secondary/80': variant === 'secondary' && !disabled,\n 'text-light-on-danger/80 dark:text-dark-on-danger/80': variant === 'danger' && !disabled,\n }\">\n @if (isDropdownOpen()) {\n <svg class=\"w-4 h-4 ml-2 inline-block\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z\"\n clip-rule=\"evenodd\" />\n </svg>\n } @else {\n <svg class=\"w-4 h-4 ml-2 inline-block\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 011.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z\"\n clip-rule=\"evenodd\" />\n </svg>\n }\n </div>\n </div>\n </div>\n</div>\n" }]
|
|
607
|
+
}], ctorParameters: () => [], propDecorators: { selectedChange: [{
|
|
608
|
+
type: Output
|
|
609
|
+
}], disabled: [{
|
|
610
|
+
type: Input
|
|
611
|
+
}], loading: [{
|
|
612
|
+
type: Input
|
|
613
|
+
}], variant: [{
|
|
614
|
+
type: Input
|
|
615
|
+
}], asButton: [{
|
|
616
|
+
type: Input
|
|
617
|
+
}], useIcon: [{
|
|
618
|
+
type: Input
|
|
619
|
+
}], useFilter: [{
|
|
620
|
+
type: Input
|
|
621
|
+
}], multiselect: [{
|
|
622
|
+
type: Input
|
|
623
|
+
}], placeholder: [{
|
|
624
|
+
type: Input
|
|
625
|
+
}], atLeastOne: [{
|
|
626
|
+
type: Input
|
|
627
|
+
}], showAll: [{
|
|
628
|
+
type: Input
|
|
629
|
+
}], options: [{
|
|
630
|
+
type: Input
|
|
631
|
+
}], value: [{
|
|
632
|
+
type: Input
|
|
633
|
+
}], origin: [{
|
|
634
|
+
type: ViewChild,
|
|
635
|
+
args: ['origin', { static: false }]
|
|
636
|
+
}], label: [{
|
|
637
|
+
type: ViewChild,
|
|
638
|
+
args: ['label']
|
|
639
|
+
}] } });
|
|
640
|
+
|
|
641
|
+
class CalendarComponent {
|
|
642
|
+
translateService = inject(TranslocoService);
|
|
643
|
+
monthSelect;
|
|
644
|
+
singleDate = false;
|
|
645
|
+
firstDayOfWeek = 1; // monday
|
|
646
|
+
selectedDate;
|
|
647
|
+
selectedDateRange;
|
|
648
|
+
dateChange = new EventEmitter();
|
|
649
|
+
dateRangeChange = new EventEmitter();
|
|
650
|
+
currentYear = new Date().getFullYear();
|
|
651
|
+
currentMonth = new Date().getMonth();
|
|
652
|
+
listCalendarDate = [];
|
|
653
|
+
isFromSelected = false;
|
|
654
|
+
yearList = [];
|
|
655
|
+
monthList = [];
|
|
656
|
+
onAdjustYear(amount) {
|
|
657
|
+
this.currentYear += amount;
|
|
658
|
+
this.calculateDateRangeDropdown();
|
|
659
|
+
}
|
|
660
|
+
onAdjustMonth(amount) {
|
|
661
|
+
this.currentMonth += amount;
|
|
662
|
+
if (this.currentMonth < 0) {
|
|
663
|
+
this.currentYear--;
|
|
664
|
+
this.currentMonth = 11;
|
|
665
|
+
}
|
|
666
|
+
else if (this.currentMonth > 11) {
|
|
667
|
+
this.currentYear++;
|
|
668
|
+
this.currentMonth = 0;
|
|
669
|
+
}
|
|
670
|
+
this.calculateDateRangeDropdown();
|
|
671
|
+
}
|
|
672
|
+
onSelectCalendarDate(date) {
|
|
673
|
+
if (this.singleDate) {
|
|
674
|
+
this.dateChange.emit(date);
|
|
675
|
+
}
|
|
676
|
+
else {
|
|
677
|
+
if (!this.isFromSelected) {
|
|
678
|
+
this.selectedDateRange = {
|
|
679
|
+
from: date.datetime,
|
|
680
|
+
to: date.datetime,
|
|
681
|
+
};
|
|
682
|
+
this.isFromSelected = true;
|
|
683
|
+
}
|
|
684
|
+
else {
|
|
685
|
+
if (this.selectedDateRange?.from) {
|
|
686
|
+
const from = this.selectedDateRange.from < date.datetime
|
|
687
|
+
? this.selectedDateRange.from
|
|
688
|
+
: date.datetime;
|
|
689
|
+
const to = this.selectedDateRange.from < date.datetime
|
|
690
|
+
? date.datetime
|
|
691
|
+
: this.selectedDateRange.from;
|
|
692
|
+
this.dateRangeChange.emit({
|
|
693
|
+
from,
|
|
694
|
+
to,
|
|
695
|
+
});
|
|
696
|
+
this.isFromSelected = false;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
onCalenderOpen(value) {
|
|
702
|
+
// const currentDate = (this.singleDate) ? () : ( value && value.from && value.to ? value.from :dateToString(new Date()));
|
|
703
|
+
let currentDate;
|
|
704
|
+
if (this.singleDate) {
|
|
705
|
+
const date = value;
|
|
706
|
+
currentDate = date ? date : dateToString(new Date());
|
|
707
|
+
}
|
|
708
|
+
else {
|
|
709
|
+
const dateRange = value;
|
|
710
|
+
currentDate =
|
|
711
|
+
dateRange && dateRange.from && dateRange.to
|
|
712
|
+
? dateRange.from
|
|
713
|
+
: dateToString(new Date());
|
|
714
|
+
}
|
|
715
|
+
this.currentYear = parseInt(currentDate.slice(0, 4), 10);
|
|
716
|
+
this.currentMonth = parseInt(currentDate.slice(5, 7), 10) - 1;
|
|
717
|
+
this.calculateDateRangeDropdown();
|
|
718
|
+
}
|
|
719
|
+
calculateDateRangeDropdown() {
|
|
720
|
+
const weekdayOfFirstOfMonth = new Date(this.currentYear, this.currentMonth, 1).getDay();
|
|
721
|
+
const firstOfCalendar = new Date(this.currentYear, this.currentMonth, 1 - weekdayOfFirstOfMonth + this.firstDayOfWeek);
|
|
722
|
+
if (firstOfCalendar > new Date(this.currentYear, this.currentMonth, 1)) {
|
|
723
|
+
firstOfCalendar.setDate(firstOfCalendar.getDate() - 7);
|
|
724
|
+
}
|
|
725
|
+
const lastOfCalendar = new Date(firstOfCalendar);
|
|
726
|
+
lastOfCalendar.setDate(lastOfCalendar.getDate() + 41);
|
|
727
|
+
if (lastOfCalendar.getDate() > 6) {
|
|
728
|
+
lastOfCalendar.setDate(lastOfCalendar.getDate() - 7);
|
|
729
|
+
}
|
|
730
|
+
const dateDiff = Math.ceil((lastOfCalendar.getTime() - firstOfCalendar.getTime()) /
|
|
731
|
+
(1000 * 60 * 60 * 24)) + 1;
|
|
732
|
+
this.listCalendarDate = [];
|
|
733
|
+
for (let i = 0; i < dateDiff; i++) {
|
|
734
|
+
const datetime = dateToString(firstOfCalendar);
|
|
735
|
+
const date = firstOfCalendar.getDate().toString();
|
|
736
|
+
const isToday = firstOfCalendar.toDateString() === new Date().toDateString();
|
|
737
|
+
const isCurrentMonth = firstOfCalendar.getMonth() === this.currentMonth;
|
|
738
|
+
const isTheDate = (this.singleDate && this.selectedDate
|
|
739
|
+
? datetime === this.selectedDate
|
|
740
|
+
: this.selectedDateRange &&
|
|
741
|
+
this.selectedDateRange.from &&
|
|
742
|
+
this.selectedDateRange.to &&
|
|
743
|
+
datetime >= this.selectedDateRange.from &&
|
|
744
|
+
datetime <= this.selectedDateRange.to) || false;
|
|
745
|
+
this.listCalendarDate.push({
|
|
746
|
+
datetime,
|
|
747
|
+
date,
|
|
748
|
+
isToday,
|
|
749
|
+
isTheDate,
|
|
750
|
+
isCurrentMonth,
|
|
751
|
+
});
|
|
752
|
+
firstOfCalendar.setDate(firstOfCalendar.getDate() + 1);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
onSelectYear(year) {
|
|
756
|
+
year = year;
|
|
757
|
+
this.currentYear = parseInt(year, 10);
|
|
758
|
+
this.calculateDateRangeDropdown();
|
|
759
|
+
}
|
|
760
|
+
onSelectMonth(month) {
|
|
761
|
+
month = month;
|
|
762
|
+
this.currentMonth = parseInt(month, 10);
|
|
763
|
+
this.calculateDateRangeDropdown();
|
|
764
|
+
}
|
|
765
|
+
ngOnInit() {
|
|
766
|
+
if (this.singleDate) {
|
|
767
|
+
this.onCalenderOpen(this.selectedDate ?? dateToString(new Date()));
|
|
768
|
+
}
|
|
769
|
+
else {
|
|
770
|
+
this.onCalenderOpen(this.selectedDateRange ?? {
|
|
771
|
+
from: dateToString(new Date()),
|
|
772
|
+
to: dateToString(new Date()),
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
constructor() {
|
|
777
|
+
const currentYear = new Date().getFullYear();
|
|
778
|
+
this.translateService
|
|
779
|
+
.selectTranslate('ui.calendar._year_', { year: '_year_' }, 'haloduck')
|
|
780
|
+
.pipe(takeUntilDestroyed())
|
|
781
|
+
.subscribe((translation) => {
|
|
782
|
+
this.yearList = [];
|
|
783
|
+
for (let i = currentYear; i >= currentYear - 150; i--) {
|
|
784
|
+
this.yearList.push({
|
|
785
|
+
id: i.toString(),
|
|
786
|
+
value: translation.replace('_year_', i.toString()),
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
zip(this.translateService.selectTranslate('ui.calendar.January', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.February', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.March', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.April', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.May', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.June', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.July', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.August', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.September', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.October', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.November', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.December', {}, 'haloduck'))
|
|
791
|
+
.pipe(takeUntilDestroyed())
|
|
792
|
+
.subscribe((translations) => {
|
|
793
|
+
this.monthList = [
|
|
794
|
+
{
|
|
795
|
+
id: '0',
|
|
796
|
+
value: translations[0],
|
|
797
|
+
},
|
|
798
|
+
{
|
|
799
|
+
id: '1',
|
|
800
|
+
value: translations[1],
|
|
801
|
+
},
|
|
802
|
+
{
|
|
803
|
+
id: '2',
|
|
804
|
+
value: translations[2],
|
|
805
|
+
},
|
|
806
|
+
{
|
|
807
|
+
id: '3',
|
|
808
|
+
value: translations[3],
|
|
809
|
+
},
|
|
810
|
+
{
|
|
811
|
+
id: '4',
|
|
812
|
+
value: translations[4],
|
|
813
|
+
},
|
|
814
|
+
{
|
|
815
|
+
id: '5',
|
|
816
|
+
value: translations[5],
|
|
817
|
+
},
|
|
818
|
+
{
|
|
819
|
+
id: '6',
|
|
820
|
+
value: translations[6],
|
|
821
|
+
},
|
|
822
|
+
{
|
|
823
|
+
id: '7',
|
|
824
|
+
value: translations[7],
|
|
825
|
+
},
|
|
826
|
+
{
|
|
827
|
+
id: '8',
|
|
828
|
+
value: translations[8],
|
|
829
|
+
},
|
|
830
|
+
{
|
|
831
|
+
id: '9',
|
|
832
|
+
value: translations[9],
|
|
833
|
+
},
|
|
834
|
+
{
|
|
835
|
+
id: '10',
|
|
836
|
+
value: translations[10],
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
id: '11',
|
|
840
|
+
value: translations[11],
|
|
841
|
+
},
|
|
842
|
+
];
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: CalendarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
846
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: CalendarComponent, isStandalone: true, selector: "haloduck-calendar", inputs: { singleDate: "singleDate", firstDayOfWeek: "firstDayOfWeek", selectedDate: "selectedDate", selectedDateRange: "selectedDateRange" }, outputs: { dateChange: "dateChange", dateRangeChange: "dateRangeChange" }, providers: [provideTranslocoScope('haloduck')], viewQueries: [{ propertyName: "monthSelect", first: true, predicate: ["monthSelect"], descendants: true }], ngImport: i0, template: "<div id=\"dateRangeCalendar\"\n class=\"min-w-96 brightness-125 bg-light-background dark:bg-dark-background border border-light-inactive dark:border-dark-inactive rounded text-center p-4 flex flex-col gap-4\"\n tabindex=\"1\">\n\n <div class=\"flex items-center\">\n <button type=\"button\"\n class=\"-m-1.5 flex flex-none items-center justify-center p-1.5 text-light-on-background dark:text-dark-on-background hover:scale-105 hover:cursor-pointer active:scale-95\"\n (click)=\"onAdjustYear(-1)\">\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </button>\n <div class=\"flex-auto text-sm font-semibold flex justify-center\">\n <div class=\"w-32\">\n <haloduck-select [multiselect]=\"false\"\n [atLeastOne]=\"true\"\n [options]=\"yearList\"\n [value]=\"currentYear + ''\"\n (selectedChange)=\"onSelectYear($event)\">\n </haloduck-select>\n </div>\n </div>\n <button type=\"button\"\n class=\"-m-1.5 flex flex-none items-center justify-center p-1.5 text-light-on-background dark:text-dark-on-background hover:scale-105 hover:cursor-pointer active:scale-95\"\n (click)=\"onAdjustYear(1)\">\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </button>\n </div>\n\n <div class=\"flex items-center\">\n <button type=\"button\"\n class=\"-m-1.5 flex flex-none items-center justify-center p-1.5 text-light-on-background dark:text-dark-on-background hover:scale-105 hover:cursor-pointer active:scale-95\n\"\n (click)=\"onAdjustMonth(-1)\">\n <span class=\"sr-only\">\uC774\uC804 \uB2EC</span>\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </button>\n <div class=\"flex-auto text-sm font-semibold flex justify-center\">\n <div class=\"w-32\">\n <haloduck-select [multiselect]=\"false\"\n [atLeastOne]=\"true\"\n [options]=\"monthList\"\n [value]=\"currentMonth + ''\"\n (selectedChange)=\"onSelectMonth($event)\">\n </haloduck-select>\n </div>\n </div>\n <button type=\"button\"\n class=\"-m-1.5 flex flex-none items-center justify-center p-1.5 text-light-on-background dark:text-dark-on-background hover:scale-105 hover:cursor-pointer active:scale-95\n\"\n (click)=\"onAdjustMonth(1)\">\n <span class=\"sr-only\">\uB2E4\uC74C \uB2EC</span>\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </button>\n </div>\n\n <div>\n <div\n class=\"grid grid-cols-7 gap-[1px] text-xs/6 font-semibold text-light-on-background dark:text-dark-on-background\">\n <div>{{ 'haloduck.ui.calendar.Mon' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Tue' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Wed' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Thu' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Fri' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Sat' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Sun' | transloco }}</div>\n </div>\n\n <div class=\"isolate mt-2 grid grid-cols-7 gap-[1px] rounded-lg text-sm shadow overflow-hidden\">\n @for (date of listCalendarDate; track date.datetime) {\n <button type=\"button\"\n (click)=\"onSelectCalendarDate(date)\"\n class=\"py-1.5 hover:cursor-pointer bg-light-alternative dark:bg-dark-alternative text-light-on-alternative dark:text-dark-on-alternative\"\n [ngClass]=\"{'bg-light-control dark:bg-dark-control text-light-on-control dark:text-dark-on-control' : date.isCurrentMonth}\">\n <time [ngClass]=\"{'font-semibold bg-light-primary-light dark:bg-dark-primary-light text-on-light-primary-light dark:text-dark-on-primary-light': date.isToday && date.datetime !== selectedDateRange?.from,\n 'bg-light-secondary dark:bg-dark-secondary text-light-on-secondary dark:text-dark-on-secondary': date.datetime === selectedDateRange?.from,\n 'bg-gray-500 font-semibold text-white' : date.isTheDate}\"\n class=\"mx-auto flex size-7 items-center justify-center rounded-full hover:font-bold\">{{date.date}}</time>\n </button>\n }\n </div>\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: SelectComponent, selector: "haloduck-select", inputs: ["disabled", "loading", "variant", "asButton", "useIcon", "useFilter", "multiselect", "placeholder", "atLeastOne", "showAll", "options", "value"], outputs: ["selectedChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i1$2.TranslocoPipe, name: "transloco" }] });
|
|
847
|
+
}
|
|
848
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: CalendarComponent, decorators: [{
|
|
849
|
+
type: Component,
|
|
850
|
+
args: [{ selector: 'haloduck-calendar', imports: [CommonModule, SelectComponent, FormsModule, TranslocoModule], providers: [provideTranslocoScope('haloduck')], template: "<div id=\"dateRangeCalendar\"\n class=\"min-w-96 brightness-125 bg-light-background dark:bg-dark-background border border-light-inactive dark:border-dark-inactive rounded text-center p-4 flex flex-col gap-4\"\n tabindex=\"1\">\n\n <div class=\"flex items-center\">\n <button type=\"button\"\n class=\"-m-1.5 flex flex-none items-center justify-center p-1.5 text-light-on-background dark:text-dark-on-background hover:scale-105 hover:cursor-pointer active:scale-95\"\n (click)=\"onAdjustYear(-1)\">\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </button>\n <div class=\"flex-auto text-sm font-semibold flex justify-center\">\n <div class=\"w-32\">\n <haloduck-select [multiselect]=\"false\"\n [atLeastOne]=\"true\"\n [options]=\"yearList\"\n [value]=\"currentYear + ''\"\n (selectedChange)=\"onSelectYear($event)\">\n </haloduck-select>\n </div>\n </div>\n <button type=\"button\"\n class=\"-m-1.5 flex flex-none items-center justify-center p-1.5 text-light-on-background dark:text-dark-on-background hover:scale-105 hover:cursor-pointer active:scale-95\"\n (click)=\"onAdjustYear(1)\">\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </button>\n </div>\n\n <div class=\"flex items-center\">\n <button type=\"button\"\n class=\"-m-1.5 flex flex-none items-center justify-center p-1.5 text-light-on-background dark:text-dark-on-background hover:scale-105 hover:cursor-pointer active:scale-95\n\"\n (click)=\"onAdjustMonth(-1)\">\n <span class=\"sr-only\">\uC774\uC804 \uB2EC</span>\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </button>\n <div class=\"flex-auto text-sm font-semibold flex justify-center\">\n <div class=\"w-32\">\n <haloduck-select [multiselect]=\"false\"\n [atLeastOne]=\"true\"\n [options]=\"monthList\"\n [value]=\"currentMonth + ''\"\n (selectedChange)=\"onSelectMonth($event)\">\n </haloduck-select>\n </div>\n </div>\n <button type=\"button\"\n class=\"-m-1.5 flex flex-none items-center justify-center p-1.5 text-light-on-background dark:text-dark-on-background hover:scale-105 hover:cursor-pointer active:scale-95\n\"\n (click)=\"onAdjustMonth(1)\">\n <span class=\"sr-only\">\uB2E4\uC74C \uB2EC</span>\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </button>\n </div>\n\n <div>\n <div\n class=\"grid grid-cols-7 gap-[1px] text-xs/6 font-semibold text-light-on-background dark:text-dark-on-background\">\n <div>{{ 'haloduck.ui.calendar.Mon' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Tue' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Wed' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Thu' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Fri' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Sat' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Sun' | transloco }}</div>\n </div>\n\n <div class=\"isolate mt-2 grid grid-cols-7 gap-[1px] rounded-lg text-sm shadow overflow-hidden\">\n @for (date of listCalendarDate; track date.datetime) {\n <button type=\"button\"\n (click)=\"onSelectCalendarDate(date)\"\n class=\"py-1.5 hover:cursor-pointer bg-light-alternative dark:bg-dark-alternative text-light-on-alternative dark:text-dark-on-alternative\"\n [ngClass]=\"{'bg-light-control dark:bg-dark-control text-light-on-control dark:text-dark-on-control' : date.isCurrentMonth}\">\n <time [ngClass]=\"{'font-semibold bg-light-primary-light dark:bg-dark-primary-light text-on-light-primary-light dark:text-dark-on-primary-light': date.isToday && date.datetime !== selectedDateRange?.from,\n 'bg-light-secondary dark:bg-dark-secondary text-light-on-secondary dark:text-dark-on-secondary': date.datetime === selectedDateRange?.from,\n 'bg-gray-500 font-semibold text-white' : date.isTheDate}\"\n class=\"mx-auto flex size-7 items-center justify-center rounded-full hover:font-bold\">{{date.date}}</time>\n </button>\n }\n </div>\n </div>\n</div>\n" }]
|
|
851
|
+
}], ctorParameters: () => [], propDecorators: { monthSelect: [{
|
|
852
|
+
type: ViewChild,
|
|
853
|
+
args: ['monthSelect', { static: false }]
|
|
854
|
+
}], singleDate: [{
|
|
855
|
+
type: Input
|
|
856
|
+
}], firstDayOfWeek: [{
|
|
857
|
+
type: Input
|
|
858
|
+
}], selectedDate: [{
|
|
859
|
+
type: Input
|
|
860
|
+
}], selectedDateRange: [{
|
|
861
|
+
type: Input
|
|
862
|
+
}], dateChange: [{
|
|
863
|
+
type: Output
|
|
864
|
+
}], dateRangeChange: [{
|
|
865
|
+
type: Output
|
|
866
|
+
}] } });
|
|
867
|
+
// return yyyy-mm-dd
|
|
868
|
+
function dateToString(date) {
|
|
869
|
+
return (date.getFullYear() +
|
|
870
|
+
'-' +
|
|
871
|
+
(date.getMonth() + 1).toString().padStart(2, '0') +
|
|
872
|
+
'-' +
|
|
873
|
+
date.getDate().toString().padStart(2, '0'));
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
class DialogContainerComponent {
|
|
877
|
+
childComponent;
|
|
878
|
+
data;
|
|
879
|
+
close = new EventEmitter();
|
|
880
|
+
dialogContainer;
|
|
881
|
+
ngAfterViewInit() {
|
|
882
|
+
if (this.childComponent) {
|
|
883
|
+
const componentRef = this.dialogContainer.createComponent(this.childComponent);
|
|
884
|
+
if (this.data) {
|
|
885
|
+
Object.assign(componentRef.instance, { context: this.data });
|
|
886
|
+
}
|
|
887
|
+
if (this.data.classList) {
|
|
888
|
+
this.data.classList.forEach((className) => {
|
|
889
|
+
componentRef.location.nativeElement.classList.add(className);
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
componentRef.location.nativeElement.classList.add('max-w-full');
|
|
893
|
+
componentRef.location.nativeElement.classList.add('max-h-full');
|
|
894
|
+
componentRef.location.nativeElement.classList.add('text-light-on-background');
|
|
895
|
+
componentRef.location.nativeElement.classList.add('dark:text-dark-on-background');
|
|
896
|
+
componentRef.location.nativeElement.classList.add('bg-light-background');
|
|
897
|
+
componentRef.location.nativeElement.classList.add('dark:bg-dark-background');
|
|
898
|
+
componentRef.location.nativeElement.classList.add('p-4');
|
|
899
|
+
componentRef.location.nativeElement.classList.add('rounded-lg');
|
|
900
|
+
componentRef.location.nativeElement.classList.add('overflow-auto');
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
ngOnInit() { }
|
|
904
|
+
closeDialog() {
|
|
905
|
+
this.close.emit();
|
|
906
|
+
}
|
|
907
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DialogContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
908
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: DialogContainerComponent, isStandalone: true, selector: "haloduck-dialog-container", inputs: { childComponent: "childComponent", data: "data" }, outputs: { close: "close" }, viewQueries: [{ propertyName: "dialogContainer", first: true, predicate: ["dialogContainer"], descendants: true, read: ViewContainerRef }], ngImport: i0, template: "<div class=\"h-full w-full max-h-full max-w-full fixed inset-0 bg-black/50 flex items-center justify-center overflow-hidden\">\n <!-- <div class=\"w-full h-full max-w-full max-h-full bg-white p-5 rounded-lg overflow-y-auto\"\n (click)=\"$event.stopPropagation()\"> -->\n <ng-container id=\"dialogContainer\"\n #dialogContainer></ng-container>\n <!-- </div> -->\n</div>\n", styles: [".custom-dialog-panel{width:100%;max-width:100%;height:100%;max-height:100%;margin:0;padding:0}\n"] });
|
|
909
|
+
}
|
|
910
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DialogContainerComponent, decorators: [{
|
|
911
|
+
type: Component,
|
|
912
|
+
args: [{ selector: 'haloduck-dialog-container', template: "<div class=\"h-full w-full max-h-full max-w-full fixed inset-0 bg-black/50 flex items-center justify-center overflow-hidden\">\n <!-- <div class=\"w-full h-full max-w-full max-h-full bg-white p-5 rounded-lg overflow-y-auto\"\n (click)=\"$event.stopPropagation()\"> -->\n <ng-container id=\"dialogContainer\"\n #dialogContainer></ng-container>\n <!-- </div> -->\n</div>\n", styles: [".custom-dialog-panel{width:100%;max-width:100%;height:100%;max-height:100%;margin:0;padding:0}\n"] }]
|
|
913
|
+
}], propDecorators: { childComponent: [{
|
|
914
|
+
type: Input
|
|
915
|
+
}], data: [{
|
|
916
|
+
type: Input
|
|
917
|
+
}], close: [{
|
|
918
|
+
type: Output
|
|
919
|
+
}], dialogContainer: [{
|
|
920
|
+
type: ViewChild,
|
|
921
|
+
args: ['dialogContainer', { read: ViewContainerRef }]
|
|
922
|
+
}] } });
|
|
923
|
+
|
|
924
|
+
class DialogService {
|
|
925
|
+
overlay;
|
|
926
|
+
injector;
|
|
927
|
+
dialogStack = []; // 다이얼로그 스택
|
|
928
|
+
constructor(overlay, injector) {
|
|
929
|
+
this.overlay = overlay;
|
|
930
|
+
this.injector = injector;
|
|
931
|
+
}
|
|
932
|
+
open(component, data, classList) {
|
|
933
|
+
const overlayRef = this.overlay.create({
|
|
934
|
+
hasBackdrop: true,
|
|
935
|
+
panelClass: 'custom-dialog-panel',
|
|
936
|
+
scrollStrategy: this.overlay.scrollStrategies.block(),
|
|
937
|
+
});
|
|
938
|
+
const portal = new ComponentPortal(DialogContainerComponent);
|
|
939
|
+
const containerRef = overlayRef.attach(portal);
|
|
940
|
+
containerRef.instance.childComponent = component;
|
|
941
|
+
containerRef.instance.data = { ...data, overlayRef, classList };
|
|
942
|
+
containerRef.instance.close.subscribe(() => this.close(overlayRef));
|
|
943
|
+
this.dialogStack.push(overlayRef);
|
|
944
|
+
return overlayRef;
|
|
945
|
+
}
|
|
946
|
+
close(overlayRef) {
|
|
947
|
+
if (overlayRef) {
|
|
948
|
+
overlayRef.dispose();
|
|
949
|
+
this.dialogStack = this.dialogStack.filter(ref => ref !== overlayRef);
|
|
950
|
+
}
|
|
951
|
+
else {
|
|
952
|
+
// 마지막 다이얼로그 닫기
|
|
953
|
+
const lastOverlay = this.dialogStack.pop();
|
|
954
|
+
lastOverlay?.dispose();
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
closeAll() {
|
|
958
|
+
while (this.dialogStack.length) {
|
|
959
|
+
this.dialogStack.pop()?.dispose();
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DialogService, deps: [{ token: i1$3.Overlay }, { token: i0.Injector }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
963
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DialogService, providedIn: 'root' });
|
|
964
|
+
}
|
|
965
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DialogService, decorators: [{
|
|
966
|
+
type: Injectable,
|
|
967
|
+
args: [{ providedIn: 'root' }]
|
|
968
|
+
}], ctorParameters: () => [{ type: i1$3.Overlay }, { type: i0.Injector }] });
|
|
969
|
+
|
|
970
|
+
class ConfirmDialogComponent {
|
|
971
|
+
dialogService = inject(DialogService);
|
|
972
|
+
context;
|
|
973
|
+
onButtonClicked(id) {
|
|
974
|
+
this.context.clickedButton$.next(id);
|
|
975
|
+
this.context.clickedButton$.complete();
|
|
976
|
+
this.dialogService.close(this.context.overlayRef);
|
|
977
|
+
}
|
|
978
|
+
ngOnInit() {
|
|
979
|
+
}
|
|
980
|
+
constructor() {
|
|
981
|
+
}
|
|
982
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ConfirmDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
983
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: ConfirmDialogComponent, isStandalone: true, selector: "haloduck-confirm-dialog", inputs: { context: "context" }, ngImport: i0, template: "<div class=\"w-120 max-w-full flex flex-col gap-4 text-light-on-background dark:text-dark-on-background bg-light-background dark:bg-dark-background\">\n <div class=\"font-bold\">{{ context.title }}</div>\n <p>{{ context.message }}</p>\n <div class=\"flex items-center justify-center gap-4\">\n @for (button of context.buttons; track button.id) {\n <haloduck-button [variant]=\"button.variant\"\n (click)=\"onButtonClicked(button.id)\">{{ button.label }}</haloduck-button>\n }\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: ButtonComponent, selector: "haloduck-button", inputs: ["disabled", "variant"] }] });
|
|
984
|
+
}
|
|
985
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ConfirmDialogComponent, decorators: [{
|
|
986
|
+
type: Component,
|
|
987
|
+
args: [{ selector: 'haloduck-confirm-dialog', imports: [ButtonComponent], template: "<div class=\"w-120 max-w-full flex flex-col gap-4 text-light-on-background dark:text-dark-on-background bg-light-background dark:bg-dark-background\">\n <div class=\"font-bold\">{{ context.title }}</div>\n <p>{{ context.message }}</p>\n <div class=\"flex items-center justify-center gap-4\">\n @for (button of context.buttons; track button.id) {\n <haloduck-button [variant]=\"button.variant\"\n (click)=\"onButtonClicked(button.id)\">{{ button.label }}</haloduck-button>\n }\n </div>\n</div>\n" }]
|
|
988
|
+
}], ctorParameters: () => [], propDecorators: { context: [{
|
|
989
|
+
type: Input
|
|
990
|
+
}] } });
|
|
991
|
+
|
|
992
|
+
class ConfirmDialogService {
|
|
993
|
+
dialogService = inject(DialogService);
|
|
994
|
+
clickedButton$;
|
|
995
|
+
confirm(title, message, buttons) {
|
|
996
|
+
this.clickedButton$ = new Subject();
|
|
997
|
+
const dialogRef = this.dialogService.open(ConfirmDialogComponent, {
|
|
998
|
+
title,
|
|
999
|
+
message,
|
|
1000
|
+
buttons,
|
|
1001
|
+
clickedButton$: this.clickedButton$,
|
|
1002
|
+
});
|
|
1003
|
+
return this.clickedButton$.asObservable();
|
|
1004
|
+
}
|
|
1005
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ConfirmDialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1006
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ConfirmDialogService, providedIn: 'root' });
|
|
1007
|
+
}
|
|
1008
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ConfirmDialogService, decorators: [{
|
|
1009
|
+
type: Injectable,
|
|
1010
|
+
args: [{
|
|
1011
|
+
providedIn: 'root',
|
|
1012
|
+
}]
|
|
1013
|
+
}] });
|
|
1014
|
+
|
|
1015
|
+
class CopyButtonComponent {
|
|
1016
|
+
text = '';
|
|
1017
|
+
isAnimating = false;
|
|
1018
|
+
copyToClipboard($event) {
|
|
1019
|
+
$event.preventDefault();
|
|
1020
|
+
$event.stopPropagation();
|
|
1021
|
+
this.isAnimating = true;
|
|
1022
|
+
setTimeout(() => {
|
|
1023
|
+
this.isAnimating = false;
|
|
1024
|
+
}, 500); // Animation duration
|
|
1025
|
+
if (navigator.clipboard && window.isSecureContext) {
|
|
1026
|
+
navigator.clipboard
|
|
1027
|
+
.writeText(this.text)
|
|
1028
|
+
.then(() => {
|
|
1029
|
+
console.log('Text copied to clipboard');
|
|
1030
|
+
})
|
|
1031
|
+
.catch((err) => {
|
|
1032
|
+
console.error('Failed to copy text: ', err);
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
else {
|
|
1036
|
+
const textArea = document.createElement('textarea');
|
|
1037
|
+
textArea.value = this.text;
|
|
1038
|
+
textArea.style.position = 'fixed'; // Avoid scrolling to bottom
|
|
1039
|
+
document.body.appendChild(textArea);
|
|
1040
|
+
textArea.focus();
|
|
1041
|
+
textArea.select();
|
|
1042
|
+
try {
|
|
1043
|
+
document.execCommand('copy');
|
|
1044
|
+
console.log('Text copied to clipboard');
|
|
1045
|
+
}
|
|
1046
|
+
catch (err) {
|
|
1047
|
+
console.error('Failed to copy text: ', err);
|
|
1048
|
+
}
|
|
1049
|
+
document.body.removeChild(textArea);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: CopyButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1053
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: CopyButtonComponent, isStandalone: true, selector: "haloduck-copy-button", inputs: { text: "text" }, providers: [provideTranslocoScope('haloduck')], ngImport: i0, template: "<ng-container *transloco=\"let _ts; read: 'haloduck'\">\n <button type=\"button\"\n class=\"group inline-flex items-center px-2 py-1 bg-light-alternative dark:bg-dark-alternative hover:brightness-125 rounded text-xs text-light-on-alternative dark:text-dark-on-alternative hover:cursor-pointer active:scale-95 transition-transform\"\n (click)=\"copyToClipboard($event)\"\n title=\"Copy to clipboard\">\n <svg xmlns=\"http://www.w3.org/2000/svg\"\n [class]=\"'h-4 w-4 mr-1 transition-all duration-500 ' + (isAnimating ? 'scale-150 opacity-0' : 'scale-100 opacity-100')\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\">\n <rect x=\"9\"\n y=\"9\"\n width=\"13\"\n height=\"13\"\n rx=\"2\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n fill=\"none\" />\n <rect x=\"3\"\n y=\"3\"\n width=\"13\"\n height=\"13\"\n rx=\"2\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n fill=\"none\" />\n </svg>\n <span class=\"hidden group-hover:inline\">\n @if (isAnimating) {\n {{ _ts('ui.button.Copied') }}\n } @else {\n {{ _ts('ui.button.Copy') }}\n }\n </span>\n </button>\n</ng-container>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: TranslocoModule }, { kind: "directive", type: i1$2.TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] });
|
|
1054
|
+
}
|
|
1055
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: CopyButtonComponent, decorators: [{
|
|
1056
|
+
type: Component,
|
|
1057
|
+
args: [{ selector: 'haloduck-copy-button', imports: [TranslocoModule], providers: [provideTranslocoScope('haloduck')], template: "<ng-container *transloco=\"let _ts; read: 'haloduck'\">\n <button type=\"button\"\n class=\"group inline-flex items-center px-2 py-1 bg-light-alternative dark:bg-dark-alternative hover:brightness-125 rounded text-xs text-light-on-alternative dark:text-dark-on-alternative hover:cursor-pointer active:scale-95 transition-transform\"\n (click)=\"copyToClipboard($event)\"\n title=\"Copy to clipboard\">\n <svg xmlns=\"http://www.w3.org/2000/svg\"\n [class]=\"'h-4 w-4 mr-1 transition-all duration-500 ' + (isAnimating ? 'scale-150 opacity-0' : 'scale-100 opacity-100')\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\">\n <rect x=\"9\"\n y=\"9\"\n width=\"13\"\n height=\"13\"\n rx=\"2\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n fill=\"none\" />\n <rect x=\"3\"\n y=\"3\"\n width=\"13\"\n height=\"13\"\n rx=\"2\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n fill=\"none\" />\n </svg>\n <span class=\"hidden group-hover:inline\">\n @if (isAnimating) {\n {{ _ts('ui.button.Copied') }}\n } @else {\n {{ _ts('ui.button.Copy') }}\n }\n </span>\n </button>\n</ng-container>\n" }]
|
|
1058
|
+
}], propDecorators: { text: [{
|
|
1059
|
+
type: Input
|
|
1060
|
+
}] } });
|
|
1061
|
+
|
|
1062
|
+
class DatePickerComponent {
|
|
1063
|
+
destroy$ = new Subject();
|
|
1064
|
+
translateService = inject(TranslocoService);
|
|
1065
|
+
overlay = inject(Overlay);
|
|
1066
|
+
disabled = false;
|
|
1067
|
+
placeholder = '';
|
|
1068
|
+
firstDayOfWeek = 1;
|
|
1069
|
+
origin;
|
|
1070
|
+
selectedDate;
|
|
1071
|
+
overlayRef;
|
|
1072
|
+
onClick() {
|
|
1073
|
+
if (this.disabled) {
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
this.openCalendar();
|
|
1077
|
+
}
|
|
1078
|
+
onDateChange(date) {
|
|
1079
|
+
this.selectedDate = date.datetime;
|
|
1080
|
+
this.onChange(this.selectedDate);
|
|
1081
|
+
}
|
|
1082
|
+
openCalendar() {
|
|
1083
|
+
if (!this.overlayRef) {
|
|
1084
|
+
this.overlayRef = this.overlay.create({
|
|
1085
|
+
positionStrategy: this.overlay
|
|
1086
|
+
.position()
|
|
1087
|
+
.flexibleConnectedTo(this.origin.nativeElement)
|
|
1088
|
+
.withPositions([
|
|
1089
|
+
{
|
|
1090
|
+
originX: 'start',
|
|
1091
|
+
originY: 'bottom',
|
|
1092
|
+
overlayX: 'start',
|
|
1093
|
+
overlayY: 'top',
|
|
1094
|
+
},
|
|
1095
|
+
{
|
|
1096
|
+
originX: 'start',
|
|
1097
|
+
originY: 'top',
|
|
1098
|
+
overlayX: 'start',
|
|
1099
|
+
overlayY: 'bottom',
|
|
1100
|
+
},
|
|
1101
|
+
]),
|
|
1102
|
+
scrollStrategy: this.overlay.scrollStrategies.reposition(),
|
|
1103
|
+
hasBackdrop: true,
|
|
1104
|
+
backdropClass: 'cdk-overlay-transparent-backdrop',
|
|
1105
|
+
});
|
|
1106
|
+
const calendarPortal = new ComponentPortal(CalendarComponent);
|
|
1107
|
+
const componentRef = this.overlayRef.attach(calendarPortal);
|
|
1108
|
+
// Pass values directly to the CalendarComponent instance
|
|
1109
|
+
componentRef.instance.firstDayOfWeek = this.firstDayOfWeek;
|
|
1110
|
+
componentRef.instance.singleDate = true;
|
|
1111
|
+
componentRef.instance.selectedDate = this.selectedDate;
|
|
1112
|
+
// Subscribe to events emitted by the CalendarComponent
|
|
1113
|
+
componentRef.instance.dateChange
|
|
1114
|
+
.pipe(takeUntil(this.destroy$))
|
|
1115
|
+
.subscribe((date) => {
|
|
1116
|
+
this.onDateChange(date);
|
|
1117
|
+
this.closeCalendar();
|
|
1118
|
+
});
|
|
1119
|
+
// Handle clicks outside the overlay
|
|
1120
|
+
this.overlayRef
|
|
1121
|
+
.backdropClick()
|
|
1122
|
+
.pipe(takeUntil(this.destroy$))
|
|
1123
|
+
.subscribe(() => {
|
|
1124
|
+
this.closeCalendar();
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
closeCalendar() {
|
|
1129
|
+
if (this.overlayRef) {
|
|
1130
|
+
this.overlayRef.detach();
|
|
1131
|
+
this.overlayRef = undefined;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
onChange = (value) => { };
|
|
1135
|
+
onTouched = () => { };
|
|
1136
|
+
writeValue(value) {
|
|
1137
|
+
this.selectedDate = value;
|
|
1138
|
+
}
|
|
1139
|
+
registerOnChange(fn) {
|
|
1140
|
+
this.onChange = fn;
|
|
1141
|
+
}
|
|
1142
|
+
registerOnTouched(fn) {
|
|
1143
|
+
this.onTouched = fn;
|
|
1144
|
+
}
|
|
1145
|
+
setDisabledState(isDisabled) {
|
|
1146
|
+
this.disabled = isDisabled;
|
|
1147
|
+
}
|
|
1148
|
+
ngOnInit() { }
|
|
1149
|
+
ngOnDestroy() {
|
|
1150
|
+
this.destroy$.next();
|
|
1151
|
+
this.destroy$.complete();
|
|
1152
|
+
}
|
|
1153
|
+
constructor() {
|
|
1154
|
+
this.translateService
|
|
1155
|
+
.selectTranslate('ui.calendar.Pick a date.', {}, 'haloduck')
|
|
1156
|
+
.pipe(takeUntilDestroyed())
|
|
1157
|
+
.subscribe((translation) => {
|
|
1158
|
+
if (!this.placeholder || this.placeholder === '') {
|
|
1159
|
+
this.placeholder = translation;
|
|
1160
|
+
}
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DatePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1164
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: DatePickerComponent, isStandalone: true, selector: "haloduck-date-picker", inputs: { disabled: "disabled", placeholder: "placeholder", firstDayOfWeek: "firstDayOfWeek" }, providers: [
|
|
1165
|
+
{
|
|
1166
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1167
|
+
useExisting: forwardRef(() => DatePickerComponent),
|
|
1168
|
+
multi: true,
|
|
1169
|
+
},
|
|
1170
|
+
provideTranslocoScope('haloduck'),
|
|
1171
|
+
], viewQueries: [{ propertyName: "origin", first: true, predicate: ["origin"], descendants: true }], ngImport: i0, template: "<label class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control\">\n <ng-content></ng-content>\n</label>\n<div #origin\n class=\"py-1.5 relative mt-2 gap-2 rounded-md outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive placeholder:text-light-inactive dark:placeholder:text-dark-inactive\"\n [ngClass]=\"{\n 'bg-light-control dark:bg-dark-control focus:outline-2 focus:outline-offset-2 focus:outline-light-primary dark:focus:outline-dark-primary': !disabled,\n 'bg-light-control/60 dark:bg-dark-control/60 text-light-on-control/60 dark:text-dark-on-control/60' : disabled,\n }\"\n [tabindex]=\"(disabled) ? -1 : 0\"\n (click)=\"onClick()\">\n <div class=\"flex items-center\">\n <div class=\"flex-1 text-light-on-control dark:text-dark-on-control sm:text-sm/6 text-center\"\n [ngClass]=\"{\n 'text-light-inactive dark:text-dark-inactive': !selectedDate && !disabled,\n 'text-light-on-control dark:text-dark-on-control': selectedDate && !disabled,\n }\"> {{ selectedDate || placeholder }}</div>\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }] });
|
|
1172
|
+
}
|
|
1173
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DatePickerComponent, decorators: [{
|
|
1174
|
+
type: Component,
|
|
1175
|
+
args: [{ selector: 'haloduck-date-picker', imports: [CommonModule, FormsModule], providers: [
|
|
1176
|
+
{
|
|
1177
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1178
|
+
useExisting: forwardRef(() => DatePickerComponent),
|
|
1179
|
+
multi: true,
|
|
1180
|
+
},
|
|
1181
|
+
provideTranslocoScope('haloduck'),
|
|
1182
|
+
], template: "<label class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control\">\n <ng-content></ng-content>\n</label>\n<div #origin\n class=\"py-1.5 relative mt-2 gap-2 rounded-md outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive placeholder:text-light-inactive dark:placeholder:text-dark-inactive\"\n [ngClass]=\"{\n 'bg-light-control dark:bg-dark-control focus:outline-2 focus:outline-offset-2 focus:outline-light-primary dark:focus:outline-dark-primary': !disabled,\n 'bg-light-control/60 dark:bg-dark-control/60 text-light-on-control/60 dark:text-dark-on-control/60' : disabled,\n }\"\n [tabindex]=\"(disabled) ? -1 : 0\"\n (click)=\"onClick()\">\n <div class=\"flex items-center\">\n <div class=\"flex-1 text-light-on-control dark:text-dark-on-control sm:text-sm/6 text-center\"\n [ngClass]=\"{\n 'text-light-inactive dark:text-dark-inactive': !selectedDate && !disabled,\n 'text-light-on-control dark:text-dark-on-control': selectedDate && !disabled,\n }\"> {{ selectedDate || placeholder }}</div>\n </div>\n</div>\n" }]
|
|
1183
|
+
}], ctorParameters: () => [], propDecorators: { disabled: [{
|
|
1184
|
+
type: Input
|
|
1185
|
+
}], placeholder: [{
|
|
1186
|
+
type: Input
|
|
1187
|
+
}], firstDayOfWeek: [{
|
|
1188
|
+
type: Input
|
|
1189
|
+
}], origin: [{
|
|
1190
|
+
type: ViewChild,
|
|
1191
|
+
args: ['origin', { static: false }]
|
|
1192
|
+
}] } });
|
|
1193
|
+
|
|
1194
|
+
class DateRangeComponent {
|
|
1195
|
+
destroy$ = new Subject();
|
|
1196
|
+
translateService = inject(TranslocoService);
|
|
1197
|
+
overlay = inject(Overlay);
|
|
1198
|
+
dateRangeOptions = [];
|
|
1199
|
+
disabled = false;
|
|
1200
|
+
placeholder = '';
|
|
1201
|
+
firstDayOfWeek = 1;
|
|
1202
|
+
origin;
|
|
1203
|
+
selectedDateRangeOptionId;
|
|
1204
|
+
dateRange = undefined;
|
|
1205
|
+
overlayRef;
|
|
1206
|
+
getSelectedDateRangeString() {
|
|
1207
|
+
return (this.dateRange &&
|
|
1208
|
+
this.dateRange.from &&
|
|
1209
|
+
this.dateRange.to &&
|
|
1210
|
+
this.dateRange.from + ' ~ ' + this.dateRange.to);
|
|
1211
|
+
}
|
|
1212
|
+
onClick() {
|
|
1213
|
+
if (this.disabled) {
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
this.openCalendar();
|
|
1217
|
+
}
|
|
1218
|
+
onDateRangeOptionChange(selectedOptionId) {
|
|
1219
|
+
this.selectedDateRangeOptionId = selectedOptionId;
|
|
1220
|
+
if (this.selectedDateRangeOptionId !== 'custom') {
|
|
1221
|
+
this.calculateDateRange();
|
|
1222
|
+
}
|
|
1223
|
+
else {
|
|
1224
|
+
this.openCalendar();
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
onDateRangeChange(dateRange) {
|
|
1228
|
+
this.dateRange = dateRange;
|
|
1229
|
+
this.selectedDateRangeOptionId = 'custom';
|
|
1230
|
+
this.onChange(this.dateRange);
|
|
1231
|
+
}
|
|
1232
|
+
calculateDateRange() {
|
|
1233
|
+
if (!this.selectedDateRangeOptionId) {
|
|
1234
|
+
}
|
|
1235
|
+
else if (this.selectedDateRangeOptionId !== 'custom') {
|
|
1236
|
+
const unit = this.selectedDateRangeOptionId.charAt(0);
|
|
1237
|
+
const amount = parseInt(this.selectedDateRangeOptionId.slice(1), 10);
|
|
1238
|
+
let from = new Date();
|
|
1239
|
+
let to = new Date();
|
|
1240
|
+
switch (unit) {
|
|
1241
|
+
case 'd':
|
|
1242
|
+
from = new Date(to);
|
|
1243
|
+
from.setDate(to.getDate() - amount);
|
|
1244
|
+
break;
|
|
1245
|
+
case 'm':
|
|
1246
|
+
from = new Date(to);
|
|
1247
|
+
from.setMonth(to.getMonth() - amount);
|
|
1248
|
+
break;
|
|
1249
|
+
case 'y':
|
|
1250
|
+
from = new Date(to);
|
|
1251
|
+
from.setFullYear(to.getFullYear() - amount);
|
|
1252
|
+
break;
|
|
1253
|
+
default:
|
|
1254
|
+
break;
|
|
1255
|
+
}
|
|
1256
|
+
this.dateRange = {
|
|
1257
|
+
from: dateToString(from),
|
|
1258
|
+
to: dateToString(to),
|
|
1259
|
+
};
|
|
1260
|
+
this.onChange(this.dateRange);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
getDateRangeOptionId(dateRange) {
|
|
1264
|
+
const selectedOption = this.dateRangeOptions.find((option) => {
|
|
1265
|
+
const unit = (option.id || option.value).charAt(0);
|
|
1266
|
+
const amount = parseInt((option.id || option.value).slice(1), 10);
|
|
1267
|
+
let from = new Date();
|
|
1268
|
+
let to = new Date();
|
|
1269
|
+
switch (unit) {
|
|
1270
|
+
case 'd':
|
|
1271
|
+
from = new Date(to);
|
|
1272
|
+
from.setDate(to.getDate() - amount);
|
|
1273
|
+
break;
|
|
1274
|
+
case 'm':
|
|
1275
|
+
from = new Date(to);
|
|
1276
|
+
from.setMonth(to.getMonth() - amount);
|
|
1277
|
+
break;
|
|
1278
|
+
case 'y':
|
|
1279
|
+
from = new Date(to);
|
|
1280
|
+
from.setFullYear(to.getFullYear() - amount);
|
|
1281
|
+
break;
|
|
1282
|
+
default:
|
|
1283
|
+
break;
|
|
1284
|
+
}
|
|
1285
|
+
return (dateRange.from === dateToString(from) &&
|
|
1286
|
+
dateRange.to === dateToString(to));
|
|
1287
|
+
});
|
|
1288
|
+
if (selectedOption) {
|
|
1289
|
+
return selectedOption.id || selectedOption.value;
|
|
1290
|
+
}
|
|
1291
|
+
return 'custom';
|
|
1292
|
+
}
|
|
1293
|
+
openCalendar() {
|
|
1294
|
+
if (!this.overlayRef) {
|
|
1295
|
+
this.overlayRef = this.overlay.create({
|
|
1296
|
+
positionStrategy: this.overlay
|
|
1297
|
+
.position()
|
|
1298
|
+
.flexibleConnectedTo(this.origin.nativeElement)
|
|
1299
|
+
.withPositions([
|
|
1300
|
+
{
|
|
1301
|
+
originX: 'start',
|
|
1302
|
+
originY: 'bottom',
|
|
1303
|
+
overlayX: 'start',
|
|
1304
|
+
overlayY: 'top',
|
|
1305
|
+
},
|
|
1306
|
+
{
|
|
1307
|
+
originX: 'start',
|
|
1308
|
+
originY: 'top',
|
|
1309
|
+
overlayX: 'start',
|
|
1310
|
+
overlayY: 'bottom',
|
|
1311
|
+
},
|
|
1312
|
+
]),
|
|
1313
|
+
scrollStrategy: this.overlay.scrollStrategies.reposition(),
|
|
1314
|
+
hasBackdrop: true,
|
|
1315
|
+
backdropClass: 'cdk-overlay-transparent-backdrop',
|
|
1316
|
+
});
|
|
1317
|
+
const calendarPortal = new ComponentPortal(CalendarComponent);
|
|
1318
|
+
const componentRef = this.overlayRef.attach(calendarPortal);
|
|
1319
|
+
componentRef.instance.firstDayOfWeek = this.firstDayOfWeek;
|
|
1320
|
+
componentRef.instance.selectedDateRange = this.dateRange;
|
|
1321
|
+
// Subscribe to events emitted by the CalendarComponent
|
|
1322
|
+
componentRef.instance.dateRangeChange
|
|
1323
|
+
.pipe(takeUntil(this.destroy$))
|
|
1324
|
+
.subscribe((date) => {
|
|
1325
|
+
this.onDateRangeChange(date);
|
|
1326
|
+
this.closeCalendar();
|
|
1327
|
+
});
|
|
1328
|
+
// Handle clicks outside the overlay
|
|
1329
|
+
this.overlayRef
|
|
1330
|
+
.backdropClick()
|
|
1331
|
+
.pipe(takeUntil(this.destroy$))
|
|
1332
|
+
.subscribe(() => {
|
|
1333
|
+
this.closeCalendar();
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
closeCalendar() {
|
|
1338
|
+
if (this.overlayRef) {
|
|
1339
|
+
this.overlayRef.detach();
|
|
1340
|
+
this.overlayRef = undefined;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
onChange = (value) => { };
|
|
1344
|
+
onTouched = () => { };
|
|
1345
|
+
writeValue(value) {
|
|
1346
|
+
this.dateRange = value;
|
|
1347
|
+
if (!value) {
|
|
1348
|
+
this.selectedDateRangeOptionId = undefined;
|
|
1349
|
+
}
|
|
1350
|
+
else {
|
|
1351
|
+
this.selectedDateRangeOptionId = this.getDateRangeOptionId(value);
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
registerOnChange(fn) {
|
|
1355
|
+
this.onChange = fn;
|
|
1356
|
+
}
|
|
1357
|
+
registerOnTouched(fn) {
|
|
1358
|
+
this.onTouched = fn;
|
|
1359
|
+
}
|
|
1360
|
+
setDisabledState(isDisabled) {
|
|
1361
|
+
this.disabled = isDisabled;
|
|
1362
|
+
}
|
|
1363
|
+
ngOnInit() {
|
|
1364
|
+
console.log('DateRangeComponent ngOnInit', this.dateRangeOptions);
|
|
1365
|
+
}
|
|
1366
|
+
ngOnDestroy() {
|
|
1367
|
+
this.destroy$.next();
|
|
1368
|
+
this.destroy$.complete();
|
|
1369
|
+
}
|
|
1370
|
+
constructor() {
|
|
1371
|
+
this.translateService
|
|
1372
|
+
.selectTranslate('ui.calendar.Pick a period.', {}, 'haloduck')
|
|
1373
|
+
.pipe(takeUntilDestroyed())
|
|
1374
|
+
.subscribe((translation) => {
|
|
1375
|
+
if (!this.placeholder || this.placeholder === '') {
|
|
1376
|
+
this.placeholder = translation;
|
|
1377
|
+
}
|
|
1378
|
+
});
|
|
1379
|
+
console.log('DateRangeComponent constructor', this.dateRangeOptions);
|
|
1380
|
+
zip(this.translateService.selectTranslate('ui.calendar.today', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.yesterday', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.2 Days ago', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.3 Days ago', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.a week ago', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.15 Days ago', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.a month ago', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.2 months ago', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.3 months ago', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.6 months ago', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.a year ago', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.pick manually', {}, 'haloduck'))
|
|
1381
|
+
.pipe(takeUntilDestroyed())
|
|
1382
|
+
.subscribe(([today, yesterday, twoDaysAgo, threeDaysAgo, aWeekAgo, fifteenDaysAgo, aMonthAgo, twoMonthsAgo, threeMonthsAgo, sixMonthsAgo, aYearAgo, pickManually,]) => {
|
|
1383
|
+
if (this.dateRangeOptions.length === 0) {
|
|
1384
|
+
this.dateRangeOptions = [
|
|
1385
|
+
{
|
|
1386
|
+
id: 'd0',
|
|
1387
|
+
value: today,
|
|
1388
|
+
},
|
|
1389
|
+
{
|
|
1390
|
+
id: 'd1',
|
|
1391
|
+
value: yesterday,
|
|
1392
|
+
},
|
|
1393
|
+
{
|
|
1394
|
+
id: 'd2',
|
|
1395
|
+
value: twoDaysAgo,
|
|
1396
|
+
},
|
|
1397
|
+
{
|
|
1398
|
+
id: 'd3',
|
|
1399
|
+
value: threeDaysAgo,
|
|
1400
|
+
},
|
|
1401
|
+
{
|
|
1402
|
+
id: 'd7',
|
|
1403
|
+
value: aWeekAgo,
|
|
1404
|
+
},
|
|
1405
|
+
{
|
|
1406
|
+
id: 'd15',
|
|
1407
|
+
value: fifteenDaysAgo,
|
|
1408
|
+
},
|
|
1409
|
+
{
|
|
1410
|
+
id: 'm1',
|
|
1411
|
+
value: aMonthAgo,
|
|
1412
|
+
},
|
|
1413
|
+
{
|
|
1414
|
+
id: 'm2',
|
|
1415
|
+
value: twoMonthsAgo,
|
|
1416
|
+
},
|
|
1417
|
+
{
|
|
1418
|
+
id: 'm3',
|
|
1419
|
+
value: threeMonthsAgo,
|
|
1420
|
+
},
|
|
1421
|
+
{
|
|
1422
|
+
id: 'm6',
|
|
1423
|
+
value: sixMonthsAgo,
|
|
1424
|
+
},
|
|
1425
|
+
{
|
|
1426
|
+
id: 'y1',
|
|
1427
|
+
value: aYearAgo,
|
|
1428
|
+
},
|
|
1429
|
+
{
|
|
1430
|
+
id: 'custom',
|
|
1431
|
+
value: pickManually,
|
|
1432
|
+
},
|
|
1433
|
+
];
|
|
1434
|
+
}
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DateRangeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1438
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: DateRangeComponent, isStandalone: true, selector: "haloduck-date-range", inputs: { dateRangeOptions: "dateRangeOptions", disabled: "disabled", placeholder: "placeholder", firstDayOfWeek: "firstDayOfWeek" }, providers: [
|
|
1439
|
+
{
|
|
1440
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1441
|
+
useExisting: forwardRef(() => DateRangeComponent),
|
|
1442
|
+
multi: true,
|
|
1443
|
+
},
|
|
1444
|
+
provideTranslocoScope('haloduck'),
|
|
1445
|
+
], viewQueries: [{ propertyName: "origin", first: true, predicate: ["origin"], descendants: true }], ngImport: i0, template: "<label class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control\">\n <ng-content></ng-content>\n</label>\n<div #origin\n class=\"relative mt-2 gap-2 rounded-md outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive placeholder:text-light-inactive dark:placeholder:text-dark-inactive\"\n [ngClass]=\"{\n 'bg-light-control dark:bg-dark-control focus:outline-2 focus:outline-offset-2 focus:outline-light-primary dark:focus:outline-dark-primary': !disabled,\n 'bg-light-control/60 dark:bg-dark-control/60 text-light-on-control/60 dark:text-dark-on-control/60' : disabled,\n }\"\n [tabindex]=\"(disabled) ? -1 : 0\"\n (click)=\"onClick()\">\n <div class=\"flex items-center\">\n <haloduck-select [multiselect]=\"false\"\n [disabled]=\"disabled\"\n [options]=\"dateRangeOptions\"\n [atLeastOne]=\"true\"\n (click)=\"$event.stopPropagation()\"\n variant=\"primary\"\n placeholder=\"{{ 'haloduck.ui.calendar.Period' | transloco }}\"\n [(ngModel)]=\"selectedDateRangeOptionId\"\n (ngModelChange)=\"onDateRangeOptionChange($event)\">\n </haloduck-select>\n @if (getSelectedDateRangeString()) {\n <div class=\"flex-1 sm:text-sm/6 text-center\"\n [ngClass]=\"{\n 'text-light-on-control dark:text-dark-on-control': !disabled,\n 'text-light-on-control/60 dark:text-light-on-control/60': disabled,\n }\"> {{ getSelectedDateRangeString() }}</div>\n } @else {\n <div class=\"flex-1 sm:text-sm/6 text-center\"\n [ngClass]=\"{\n 'text-light-inactive dark:text-dark-inactive': !disabled,\n 'text-light-on-control/60 dark:text-dark-on-control/60': disabled,\n }\"> {{ placeholder }}</div>\n }\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: SelectComponent, selector: "haloduck-select", inputs: ["disabled", "loading", "variant", "asButton", "useIcon", "useFilter", "multiselect", "placeholder", "atLeastOne", "showAll", "options", "value"], outputs: ["selectedChange"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i1$2.TranslocoPipe, name: "transloco" }] });
|
|
1446
|
+
}
|
|
1447
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DateRangeComponent, decorators: [{
|
|
1448
|
+
type: Component,
|
|
1449
|
+
args: [{ selector: 'haloduck-date-range', imports: [SelectComponent, CommonModule, FormsModule, TranslocoModule], providers: [
|
|
1450
|
+
{
|
|
1451
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1452
|
+
useExisting: forwardRef(() => DateRangeComponent),
|
|
1453
|
+
multi: true,
|
|
1454
|
+
},
|
|
1455
|
+
provideTranslocoScope('haloduck'),
|
|
1456
|
+
], template: "<label class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control\">\n <ng-content></ng-content>\n</label>\n<div #origin\n class=\"relative mt-2 gap-2 rounded-md outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive placeholder:text-light-inactive dark:placeholder:text-dark-inactive\"\n [ngClass]=\"{\n 'bg-light-control dark:bg-dark-control focus:outline-2 focus:outline-offset-2 focus:outline-light-primary dark:focus:outline-dark-primary': !disabled,\n 'bg-light-control/60 dark:bg-dark-control/60 text-light-on-control/60 dark:text-dark-on-control/60' : disabled,\n }\"\n [tabindex]=\"(disabled) ? -1 : 0\"\n (click)=\"onClick()\">\n <div class=\"flex items-center\">\n <haloduck-select [multiselect]=\"false\"\n [disabled]=\"disabled\"\n [options]=\"dateRangeOptions\"\n [atLeastOne]=\"true\"\n (click)=\"$event.stopPropagation()\"\n variant=\"primary\"\n placeholder=\"{{ 'haloduck.ui.calendar.Period' | transloco }}\"\n [(ngModel)]=\"selectedDateRangeOptionId\"\n (ngModelChange)=\"onDateRangeOptionChange($event)\">\n </haloduck-select>\n @if (getSelectedDateRangeString()) {\n <div class=\"flex-1 sm:text-sm/6 text-center\"\n [ngClass]=\"{\n 'text-light-on-control dark:text-dark-on-control': !disabled,\n 'text-light-on-control/60 dark:text-light-on-control/60': disabled,\n }\"> {{ getSelectedDateRangeString() }}</div>\n } @else {\n <div class=\"flex-1 sm:text-sm/6 text-center\"\n [ngClass]=\"{\n 'text-light-inactive dark:text-dark-inactive': !disabled,\n 'text-light-on-control/60 dark:text-dark-on-control/60': disabled,\n }\"> {{ placeholder }}</div>\n }\n </div>\n</div>\n" }]
|
|
1457
|
+
}], ctorParameters: () => [], propDecorators: { dateRangeOptions: [{
|
|
1458
|
+
type: Input
|
|
1459
|
+
}], disabled: [{
|
|
1460
|
+
type: Input
|
|
1461
|
+
}], placeholder: [{
|
|
1462
|
+
type: Input
|
|
1463
|
+
}], firstDayOfWeek: [{
|
|
1464
|
+
type: Input
|
|
1465
|
+
}], origin: [{
|
|
1466
|
+
type: ViewChild,
|
|
1467
|
+
args: ['origin', { static: false }]
|
|
1468
|
+
}] } });
|
|
1469
|
+
|
|
1470
|
+
class DrawCanvasComponent {
|
|
1471
|
+
imagePath;
|
|
1472
|
+
lineColor = '#DD0000'; // Default color is black
|
|
1473
|
+
canvasRef;
|
|
1474
|
+
ctx;
|
|
1475
|
+
isDrawing = false;
|
|
1476
|
+
onChange = () => { };
|
|
1477
|
+
onTouched = () => { };
|
|
1478
|
+
ngAfterViewInit() {
|
|
1479
|
+
const canvas = this.canvasRef.nativeElement;
|
|
1480
|
+
this.ctx = canvas.getContext('2d');
|
|
1481
|
+
this.ctx.strokeStyle = this.lineColor; // Set the initial line color
|
|
1482
|
+
this.loadImage();
|
|
1483
|
+
this.setupCanvas();
|
|
1484
|
+
}
|
|
1485
|
+
loadImage() {
|
|
1486
|
+
console.log(this.canvasRef.nativeElement.width, this.canvasRef.nativeElement.height, this.canvasRef.nativeElement.getBoundingClientRect());
|
|
1487
|
+
const image = new Image();
|
|
1488
|
+
image.src = this.imagePath;
|
|
1489
|
+
image.onload = () => {
|
|
1490
|
+
this.ctx.drawImage(image, 0, 0, this.canvasRef.nativeElement.width, this.canvasRef.nativeElement.height);
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
setupCanvas() {
|
|
1494
|
+
const canvas = this.canvasRef.nativeElement;
|
|
1495
|
+
// Mouse events
|
|
1496
|
+
canvas.addEventListener('mousedown', (event) => this.startMouseDrawing(event));
|
|
1497
|
+
canvas.addEventListener('mousemove', (event) => this.mouseDraw(event));
|
|
1498
|
+
canvas.addEventListener('mouseup', () => this.stopDrawing());
|
|
1499
|
+
canvas.addEventListener('mouseleave', () => this.stopDrawing());
|
|
1500
|
+
// Touch events
|
|
1501
|
+
canvas.addEventListener('touchstart', (event) => this.startTouchDrawing(event));
|
|
1502
|
+
canvas.addEventListener('touchmove', (event) => {
|
|
1503
|
+
event.preventDefault(); // Prevent scrolling
|
|
1504
|
+
this.touchDraw(event);
|
|
1505
|
+
});
|
|
1506
|
+
canvas.addEventListener('touchend', () => this.stopDrawing());
|
|
1507
|
+
canvas.addEventListener('touchcancel', () => this.stopDrawing());
|
|
1508
|
+
}
|
|
1509
|
+
startMouseDrawing(event) {
|
|
1510
|
+
const rect = this.canvasRef.nativeElement.getBoundingClientRect();
|
|
1511
|
+
this.isDrawing = true;
|
|
1512
|
+
this.ctx.beginPath();
|
|
1513
|
+
this.ctx.moveTo(event.offsetX * (this.canvasRef.nativeElement.width / rect.width), event.offsetY * (this.canvasRef.nativeElement.height / rect.height));
|
|
1514
|
+
}
|
|
1515
|
+
mouseDraw(event) {
|
|
1516
|
+
const rect = this.canvasRef.nativeElement.getBoundingClientRect();
|
|
1517
|
+
if (!this.isDrawing)
|
|
1518
|
+
return;
|
|
1519
|
+
this.ctx.lineTo(event.offsetX * (this.canvasRef.nativeElement.width / rect.width), event.offsetY * (this.canvasRef.nativeElement.height / rect.height));
|
|
1520
|
+
this.ctx.stroke();
|
|
1521
|
+
}
|
|
1522
|
+
startTouchDrawing(event) {
|
|
1523
|
+
this.isDrawing = true;
|
|
1524
|
+
const touch = event.touches[0];
|
|
1525
|
+
const rect = this.canvasRef.nativeElement.getBoundingClientRect();
|
|
1526
|
+
this.ctx.beginPath();
|
|
1527
|
+
this.ctx.moveTo((touch.clientX - rect.left) *
|
|
1528
|
+
(this.canvasRef.nativeElement.width / rect.width), (touch.clientY - rect.top) *
|
|
1529
|
+
(this.canvasRef.nativeElement.height / rect.height));
|
|
1530
|
+
}
|
|
1531
|
+
touchDraw(event) {
|
|
1532
|
+
if (!this.isDrawing)
|
|
1533
|
+
return;
|
|
1534
|
+
const touch = event.touches[0];
|
|
1535
|
+
const rect = this.canvasRef.nativeElement.getBoundingClientRect();
|
|
1536
|
+
this.ctx.lineTo((touch.clientX - rect.left) *
|
|
1537
|
+
(this.canvasRef.nativeElement.width / rect.width), (touch.clientY - rect.top) *
|
|
1538
|
+
(this.canvasRef.nativeElement.height / rect.height));
|
|
1539
|
+
this.ctx.stroke();
|
|
1540
|
+
}
|
|
1541
|
+
stopDrawing() {
|
|
1542
|
+
this.isDrawing = false;
|
|
1543
|
+
this.ctx.closePath();
|
|
1544
|
+
this.saveDrawing();
|
|
1545
|
+
}
|
|
1546
|
+
saveDrawing() {
|
|
1547
|
+
const canvas = this.canvasRef.nativeElement;
|
|
1548
|
+
const dataUrl = canvas.toDataURL('image/png');
|
|
1549
|
+
// Convert data URL to Blob
|
|
1550
|
+
const blob = this.dataURLToBlob(dataUrl);
|
|
1551
|
+
const file = new File([blob], 'teeth.png', { type: 'image/png' });
|
|
1552
|
+
// Emit the file value to the form control
|
|
1553
|
+
this.onChange(file);
|
|
1554
|
+
}
|
|
1555
|
+
dataURLToBlob(dataUrl) {
|
|
1556
|
+
const byteString = atob(dataUrl.split(',')[1]);
|
|
1557
|
+
const mimeString = dataUrl.split(',')[0].split(':')[1].split(';')[0];
|
|
1558
|
+
const buffer = new ArrayBuffer(byteString.length);
|
|
1559
|
+
const dataView = new Uint8Array(buffer);
|
|
1560
|
+
for (let i = 0; i < byteString.length; i++) {
|
|
1561
|
+
dataView[i] = byteString.charCodeAt(i);
|
|
1562
|
+
}
|
|
1563
|
+
return new Blob([buffer], { type: mimeString });
|
|
1564
|
+
}
|
|
1565
|
+
// ControlValueAccessor methods
|
|
1566
|
+
writeValue(file) {
|
|
1567
|
+
// No-op: The component does not need to set the file value directly
|
|
1568
|
+
}
|
|
1569
|
+
registerOnChange(fn) {
|
|
1570
|
+
this.onChange = fn;
|
|
1571
|
+
}
|
|
1572
|
+
registerOnTouched(fn) {
|
|
1573
|
+
this.onTouched = fn;
|
|
1574
|
+
}
|
|
1575
|
+
setDisabledState(isDisabled) {
|
|
1576
|
+
const canvas = this.canvasRef.nativeElement;
|
|
1577
|
+
canvas.style.pointerEvents = isDisabled ? 'none' : 'auto';
|
|
1578
|
+
}
|
|
1579
|
+
onReset() {
|
|
1580
|
+
this.loadImage();
|
|
1581
|
+
}
|
|
1582
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DrawCanvasComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1583
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: DrawCanvasComponent, isStandalone: true, selector: "haloduck-draw-canvas", inputs: { imagePath: "imagePath", lineColor: "lineColor" }, providers: [
|
|
1584
|
+
{
|
|
1585
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1586
|
+
useExisting: forwardRef(() => DrawCanvasComponent),
|
|
1587
|
+
multi: true,
|
|
1588
|
+
},
|
|
1589
|
+
provideTranslocoScope('haloduck'),
|
|
1590
|
+
], viewQueries: [{ propertyName: "canvasRef", first: true, predicate: ["canvas"], descendants: true, static: true }], ngImport: i0, template: "<div class=\"draw-canvas-container flex flex-col gap-2\">\n <canvas #canvas width=\"800\" height=\"400\" class=\"max-w-full draw-canvas select-none\"></canvas>\n <haloduck-button variant=\"danger\" (click)=\"onReset()\">{{ 'haloduck.ui.draw.Clear' | transloco }}</haloduck-button>\n</div>\n", styles: [".draw-canvas-container{display:flex;flex-direction:column;align-items:center}.draw-canvas{border:1px solid #ccc;cursor:crosshair}\n"], dependencies: [{ kind: "component", type: ButtonComponent, selector: "haloduck-button", inputs: ["disabled", "variant"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i1$2.TranslocoPipe, name: "transloco" }] });
|
|
1591
|
+
}
|
|
1592
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DrawCanvasComponent, decorators: [{
|
|
1593
|
+
type: Component,
|
|
1594
|
+
args: [{ selector: 'haloduck-draw-canvas', providers: [
|
|
1595
|
+
{
|
|
1596
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1597
|
+
useExisting: forwardRef(() => DrawCanvasComponent),
|
|
1598
|
+
multi: true,
|
|
1599
|
+
},
|
|
1600
|
+
provideTranslocoScope('haloduck'),
|
|
1601
|
+
], imports: [ButtonComponent, TranslocoModule], template: "<div class=\"draw-canvas-container flex flex-col gap-2\">\n <canvas #canvas width=\"800\" height=\"400\" class=\"max-w-full draw-canvas select-none\"></canvas>\n <haloduck-button variant=\"danger\" (click)=\"onReset()\">{{ 'haloduck.ui.draw.Clear' | transloco }}</haloduck-button>\n</div>\n", styles: [".draw-canvas-container{display:flex;flex-direction:column;align-items:center}.draw-canvas{border:1px solid #ccc;cursor:crosshair}\n"] }]
|
|
1602
|
+
}], propDecorators: { imagePath: [{
|
|
1603
|
+
type: Input
|
|
1604
|
+
}], lineColor: [{
|
|
1605
|
+
type: Input
|
|
1606
|
+
}], canvasRef: [{
|
|
1607
|
+
type: ViewChild,
|
|
1608
|
+
args: ['canvas', { static: true }]
|
|
1609
|
+
}] } });
|
|
1610
|
+
|
|
1611
|
+
const ERROR_NOT_ACCEPTABLE_FILE_TYPE = 'NOT_ACCEPTABLE_FILE_TYPE';
|
|
1612
|
+
const ERROR_OVER_SIZE = 'OVER_SIZE';
|
|
1613
|
+
const ERROR_OVER_COUNT = 'OVER_COUNT';
|
|
1614
|
+
const ERROR_UPLOAD = 'UPLOAD';
|
|
1615
|
+
class FileUploaderComponent {
|
|
1616
|
+
disabled = false;
|
|
1617
|
+
urlPrefix;
|
|
1618
|
+
keyPrefix;
|
|
1619
|
+
uploadApi;
|
|
1620
|
+
multiple = true;
|
|
1621
|
+
maxCount = 0;
|
|
1622
|
+
maxSize = 0;
|
|
1623
|
+
accept = [''];
|
|
1624
|
+
filesAdded = new EventEmitter();
|
|
1625
|
+
fileRemoved = new EventEmitter();
|
|
1626
|
+
error = new EventEmitter();
|
|
1627
|
+
cdr = inject(ChangeDetectorRef);
|
|
1628
|
+
zone = inject(NgZone);
|
|
1629
|
+
files = [];
|
|
1630
|
+
isUploading = false;
|
|
1631
|
+
isDragOver = false;
|
|
1632
|
+
onChange = () => { };
|
|
1633
|
+
onTouched = () => { };
|
|
1634
|
+
writeValue(files) {
|
|
1635
|
+
this.files = files || [];
|
|
1636
|
+
}
|
|
1637
|
+
registerOnChange(fn) {
|
|
1638
|
+
this.onChange = fn;
|
|
1639
|
+
}
|
|
1640
|
+
registerOnTouched(fn) {
|
|
1641
|
+
this.onTouched = fn;
|
|
1642
|
+
}
|
|
1643
|
+
setDisabledState(isDisabled) {
|
|
1644
|
+
this.disabled = isDisabled;
|
|
1645
|
+
}
|
|
1646
|
+
onFileSelected(event) {
|
|
1647
|
+
const input = event.target;
|
|
1648
|
+
if (input.files) {
|
|
1649
|
+
const selectedFiles = this.multiple
|
|
1650
|
+
? Array.from(input.files)
|
|
1651
|
+
: [input.files[0]];
|
|
1652
|
+
this.addFiles(selectedFiles);
|
|
1653
|
+
}
|
|
1654
|
+
this.onTouched();
|
|
1655
|
+
}
|
|
1656
|
+
removeFile(index) {
|
|
1657
|
+
const removedFile = this.files[index];
|
|
1658
|
+
this.files.splice(index, 1);
|
|
1659
|
+
this.fileRemoved.emit(removedFile);
|
|
1660
|
+
this.onChange(this.files);
|
|
1661
|
+
}
|
|
1662
|
+
onDragOver(event) {
|
|
1663
|
+
event.preventDefault();
|
|
1664
|
+
this.isDragOver = true;
|
|
1665
|
+
}
|
|
1666
|
+
onDragLeave(event) {
|
|
1667
|
+
event.preventDefault();
|
|
1668
|
+
this.isDragOver = false;
|
|
1669
|
+
}
|
|
1670
|
+
onDrop(event) {
|
|
1671
|
+
event.preventDefault();
|
|
1672
|
+
this.isDragOver = false;
|
|
1673
|
+
if (event.dataTransfer?.files) {
|
|
1674
|
+
const droppedFiles = this.multiple
|
|
1675
|
+
? Array.from(event.dataTransfer.files)
|
|
1676
|
+
: [event.dataTransfer.files[0]];
|
|
1677
|
+
this.addFiles(droppedFiles);
|
|
1678
|
+
}
|
|
1679
|
+
this.onTouched();
|
|
1680
|
+
}
|
|
1681
|
+
getFilePreview(file) {
|
|
1682
|
+
return file.previewUrl || ''; // Return the precomputed preview URL
|
|
1683
|
+
}
|
|
1684
|
+
addFiles(addedFiles) {
|
|
1685
|
+
const notAcceptedFiles = [];
|
|
1686
|
+
let filteredFiles = addedFiles.filter((file) => {
|
|
1687
|
+
const acceptableFileType = this.isAcceptableFileType(file);
|
|
1688
|
+
const acceptableFileSize = this.maxSize > 0 ? file.size <= this.maxSize : true;
|
|
1689
|
+
if (!acceptableFileType) {
|
|
1690
|
+
notAcceptedFiles.push({ error: ERROR_NOT_ACCEPTABLE_FILE_TYPE, file });
|
|
1691
|
+
}
|
|
1692
|
+
if (!acceptableFileSize) {
|
|
1693
|
+
notAcceptedFiles.push({ error: ERROR_OVER_SIZE, file });
|
|
1694
|
+
}
|
|
1695
|
+
return acceptableFileType && acceptableFileSize;
|
|
1696
|
+
});
|
|
1697
|
+
if (this.maxCount > 0 &&
|
|
1698
|
+
this.files.length + filteredFiles.length > this.maxCount) {
|
|
1699
|
+
for (let i = this.maxCount; i < this.files.length + filteredFiles.length; i++) {
|
|
1700
|
+
notAcceptedFiles.push({
|
|
1701
|
+
error: ERROR_OVER_COUNT,
|
|
1702
|
+
file: filteredFiles[i - this.files.length],
|
|
1703
|
+
});
|
|
1704
|
+
}
|
|
1705
|
+
filteredFiles = filteredFiles.splice(this.files.length + filteredFiles.length - this.maxCount);
|
|
1706
|
+
}
|
|
1707
|
+
if (notAcceptedFiles.length > 0) {
|
|
1708
|
+
this.error.emit(notAcceptedFiles);
|
|
1709
|
+
}
|
|
1710
|
+
// Compute preview URLs for the new files
|
|
1711
|
+
filteredFiles.forEach((file) => {
|
|
1712
|
+
if (file.type.startsWith('image')) {
|
|
1713
|
+
file.previewUrl = URL.createObjectURL(file);
|
|
1714
|
+
}
|
|
1715
|
+
});
|
|
1716
|
+
this.files = this.files.concat(filteredFiles);
|
|
1717
|
+
this.filesAdded.emit(filteredFiles);
|
|
1718
|
+
this.onChange(this.files);
|
|
1719
|
+
}
|
|
1720
|
+
isAcceptableFileType(file) {
|
|
1721
|
+
// Check if the file type is acceptable
|
|
1722
|
+
return this.accept.some((type) => {
|
|
1723
|
+
if (type.startsWith('.')) {
|
|
1724
|
+
// Check if the file type matches any of the accepted types
|
|
1725
|
+
return file.name.endsWith(type);
|
|
1726
|
+
}
|
|
1727
|
+
else if (type.includes('/')) {
|
|
1728
|
+
if (type.includes('/*')) {
|
|
1729
|
+
// Check if the file type matches the wildcard MIME type
|
|
1730
|
+
return file.type.startsWith(type.split('/')[0]);
|
|
1731
|
+
}
|
|
1732
|
+
else {
|
|
1733
|
+
// Check if the file type matches the specific MIME type
|
|
1734
|
+
return file.type === type;
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
else {
|
|
1738
|
+
return file.type === type;
|
|
1739
|
+
}
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1742
|
+
startUpload() {
|
|
1743
|
+
// Placeholder for upload logic
|
|
1744
|
+
this.isUploading = true;
|
|
1745
|
+
const uploadTrigger$ = [];
|
|
1746
|
+
this.files.forEach((file) => {
|
|
1747
|
+
file.isUploading = true;
|
|
1748
|
+
file.isUploaded = false;
|
|
1749
|
+
uploadTrigger$.push(this.uploadApi(file, this.keyPrefix).pipe(
|
|
1750
|
+
// Handle the upload API response
|
|
1751
|
+
tap((key) => {
|
|
1752
|
+
file.isUploading = false;
|
|
1753
|
+
file.isUploaded = true;
|
|
1754
|
+
file.key = key;
|
|
1755
|
+
})));
|
|
1756
|
+
});
|
|
1757
|
+
// Wait for all uploads to complete
|
|
1758
|
+
return combineLatest(uploadTrigger$).pipe(switchMap((keys) => {
|
|
1759
|
+
this.files.forEach((file) => {
|
|
1760
|
+
if ('' === file.key) {
|
|
1761
|
+
this.isUploading = false;
|
|
1762
|
+
file.isUploaded = false;
|
|
1763
|
+
this.error.emit([{ error: ERROR_UPLOAD, file }]);
|
|
1764
|
+
}
|
|
1765
|
+
});
|
|
1766
|
+
this.onChange(this.files);
|
|
1767
|
+
this.onTouched();
|
|
1768
|
+
return of(this.files);
|
|
1769
|
+
}));
|
|
1770
|
+
}
|
|
1771
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: FileUploaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1772
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: FileUploaderComponent, isStandalone: true, selector: "haloduck-file-uploader", inputs: { disabled: "disabled", urlPrefix: "urlPrefix", keyPrefix: "keyPrefix", uploadApi: "uploadApi", multiple: "multiple", maxCount: "maxCount", maxSize: "maxSize", accept: "accept" }, outputs: { filesAdded: "filesAdded", fileRemoved: "fileRemoved", error: "error" }, providers: [
|
|
1773
|
+
{
|
|
1774
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1775
|
+
useExisting: forwardRef(() => FileUploaderComponent),
|
|
1776
|
+
multi: true,
|
|
1777
|
+
},
|
|
1778
|
+
provideTranslocoScope('haloduck'),
|
|
1779
|
+
], ngImport: i0, template: "<div class=\"p-4 border border-light-inactive dark:border-dark-inactive rounded-md\"\n [class.drag-over]=\"isDragOver\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\">\n @if (!isUploading) {\n <label for=\"file-upload\"\n class=\"flex flex-col items-center justify-center w-full border-2 border-dashed border-light-inactive dark:border-dark-inactive rounded-md cursor-pointer hover:border-light-secondary dark:border-dark-secondary p-4\">\n <span class=\"text-light-inactive dark:text-dark-inactive\">{{ 'haloduck.ui.file.Drag and drop files here, or click to select files' | transloco }}</span>\n <input id=\"file-upload\"\n type=\"file\"\n class=\"hidden\"\n [attr.accept]=\"accept ? accept.join(',') : null\"\n [attr.multiple]=\"multiple ? '' : null\"\n (cancel)=\"$event.stopPropagation()\"\n (change)=\"onFileSelected($event)\" />\n </label>\n }\n <!-- Display file list -->\n @if (files.length > 0) {\n <ul class=\"mt-4 space-y-2\">\n @for (file of files; track file.name; let i = $index) {\n <li class=\"flex items-center justify-between p-2 border border-light-inactive dark:border-dark-inactive rounded-md\">\n <!-- Check if the file is an image -->\n <div class=\"flex items-center space-x-4\">\n @if (file.previewUrl){\n <img [src]=\"file.previewUrl\"\n alt=\"{{ file.name }}\"\n class=\"w-12 h-12 object-cover rounded-md\" />\n }\n <span class=\"text-sm text-light-inactive dark:text-dark-inactive\">{{ file.name }}</span>\n </div>\n @if (isUploading) {\n @if (file.isUploaded) {\n <span class=\"text-sm text-light-secondary dark:text-dark-secondary\">{{ 'haloduck.ui.file.Uploaded' | transloco }}</span>\n }@else{\n <span class=\"text-sm text-light-primary dark:text-dark-primary\">{{ 'haloduck.ui.file.Uploading...' | transloco }}</span>\n }\n }@else{\n <button type=\"button\"\n class=\"text-light-danger dark:text-dark-danger hover:brightness-125\"\n (click)=\"removeFile(i)\">\n {{ 'haloduck.ui.file.Remove' | transloco }}\n </button>\n }\n </li>\n }\n </ul>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i1$2.TranslocoPipe, name: "transloco" }] });
|
|
1780
|
+
}
|
|
1781
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: FileUploaderComponent, decorators: [{
|
|
1782
|
+
type: Component,
|
|
1783
|
+
args: [{ selector: 'haloduck-file-uploader', imports: [CommonModule, TranslocoModule], providers: [
|
|
1784
|
+
{
|
|
1785
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1786
|
+
useExisting: forwardRef(() => FileUploaderComponent),
|
|
1787
|
+
multi: true,
|
|
1788
|
+
},
|
|
1789
|
+
provideTranslocoScope('haloduck'),
|
|
1790
|
+
], template: "<div class=\"p-4 border border-light-inactive dark:border-dark-inactive rounded-md\"\n [class.drag-over]=\"isDragOver\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\">\n @if (!isUploading) {\n <label for=\"file-upload\"\n class=\"flex flex-col items-center justify-center w-full border-2 border-dashed border-light-inactive dark:border-dark-inactive rounded-md cursor-pointer hover:border-light-secondary dark:border-dark-secondary p-4\">\n <span class=\"text-light-inactive dark:text-dark-inactive\">{{ 'haloduck.ui.file.Drag and drop files here, or click to select files' | transloco }}</span>\n <input id=\"file-upload\"\n type=\"file\"\n class=\"hidden\"\n [attr.accept]=\"accept ? accept.join(',') : null\"\n [attr.multiple]=\"multiple ? '' : null\"\n (cancel)=\"$event.stopPropagation()\"\n (change)=\"onFileSelected($event)\" />\n </label>\n }\n <!-- Display file list -->\n @if (files.length > 0) {\n <ul class=\"mt-4 space-y-2\">\n @for (file of files; track file.name; let i = $index) {\n <li class=\"flex items-center justify-between p-2 border border-light-inactive dark:border-dark-inactive rounded-md\">\n <!-- Check if the file is an image -->\n <div class=\"flex items-center space-x-4\">\n @if (file.previewUrl){\n <img [src]=\"file.previewUrl\"\n alt=\"{{ file.name }}\"\n class=\"w-12 h-12 object-cover rounded-md\" />\n }\n <span class=\"text-sm text-light-inactive dark:text-dark-inactive\">{{ file.name }}</span>\n </div>\n @if (isUploading) {\n @if (file.isUploaded) {\n <span class=\"text-sm text-light-secondary dark:text-dark-secondary\">{{ 'haloduck.ui.file.Uploaded' | transloco }}</span>\n }@else{\n <span class=\"text-sm text-light-primary dark:text-dark-primary\">{{ 'haloduck.ui.file.Uploading...' | transloco }}</span>\n }\n }@else{\n <button type=\"button\"\n class=\"text-light-danger dark:text-dark-danger hover:brightness-125\"\n (click)=\"removeFile(i)\">\n {{ 'haloduck.ui.file.Remove' | transloco }}\n </button>\n }\n </li>\n }\n </ul>\n }\n</div>\n" }]
|
|
1791
|
+
}], propDecorators: { disabled: [{
|
|
1792
|
+
type: Input
|
|
1793
|
+
}], urlPrefix: [{
|
|
1794
|
+
type: Input
|
|
1795
|
+
}], keyPrefix: [{
|
|
1796
|
+
type: Input
|
|
1797
|
+
}], uploadApi: [{
|
|
1798
|
+
type: Input
|
|
1799
|
+
}], multiple: [{
|
|
1800
|
+
type: Input
|
|
1801
|
+
}], maxCount: [{
|
|
1802
|
+
type: Input
|
|
1803
|
+
}], maxSize: [{
|
|
1804
|
+
type: Input
|
|
1805
|
+
}], accept: [{
|
|
1806
|
+
type: Input
|
|
1807
|
+
}], filesAdded: [{
|
|
1808
|
+
type: Output
|
|
1809
|
+
}], fileRemoved: [{
|
|
1810
|
+
type: Output
|
|
1811
|
+
}], error: [{
|
|
1812
|
+
type: Output
|
|
1813
|
+
}] } });
|
|
1814
|
+
|
|
1815
|
+
class FlipComponent {
|
|
1816
|
+
title = '';
|
|
1817
|
+
set isOpen(value) {
|
|
1818
|
+
this._isOpen.set(value);
|
|
1819
|
+
}
|
|
1820
|
+
_isOpen = signal(false, ...(ngDevMode ? [{ debugName: "_isOpen" }] : []));
|
|
1821
|
+
toggle() {
|
|
1822
|
+
this._isOpen.set(!this._isOpen());
|
|
1823
|
+
}
|
|
1824
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: FlipComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1825
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: FlipComponent, isStandalone: true, selector: "haloduck-flip", inputs: { title: "title", isOpen: "isOpen" }, ngImport: i0, template: "<div class=\"relative w-full shadow ring-1 ring-light-inactive/50 dark:ring-dark-inactive/80 sm:rounded-lg bg-light-inactive/20 dark:bg-dark-inactive/20 p-2 flex flex-col gap-2\">\n <label class=\"text-sm text-light-on-inactive dark:text-dark-on-inactive\">{{ title }}</label>\n @if(_isOpen()) {\n <ng-content></ng-content>\n }\n <button variant=\"secondary\"\n class=\"absolute rounded-md border border-inactive px-1 py-0.5 top-1 right-1 hover:cursor-pointer bg-white\"\n (click)=\"toggle()\">\n @if(_isOpen()) {\n <svg xmlns=\"http://www.w3.org/2000/svg\"\n class=\"h-4 w-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\">\n <path stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M18 15l-6-6-6 6\" />\n </svg>\n }\n @else {\n <svg xmlns=\"http://www.w3.org/2000/svg\"\n class=\"h-4 w-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\">\n <path stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M6 9l6 6 6-6\" />\n </svg>\n }\n </button>\n</div>\n", styles: [""] });
|
|
1826
|
+
}
|
|
1827
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: FlipComponent, decorators: [{
|
|
1828
|
+
type: Component,
|
|
1829
|
+
args: [{ selector: 'haloduck-flip', imports: [], template: "<div class=\"relative w-full shadow ring-1 ring-light-inactive/50 dark:ring-dark-inactive/80 sm:rounded-lg bg-light-inactive/20 dark:bg-dark-inactive/20 p-2 flex flex-col gap-2\">\n <label class=\"text-sm text-light-on-inactive dark:text-dark-on-inactive\">{{ title }}</label>\n @if(_isOpen()) {\n <ng-content></ng-content>\n }\n <button variant=\"secondary\"\n class=\"absolute rounded-md border border-inactive px-1 py-0.5 top-1 right-1 hover:cursor-pointer bg-white\"\n (click)=\"toggle()\">\n @if(_isOpen()) {\n <svg xmlns=\"http://www.w3.org/2000/svg\"\n class=\"h-4 w-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\">\n <path stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M18 15l-6-6-6 6\" />\n </svg>\n }\n @else {\n <svg xmlns=\"http://www.w3.org/2000/svg\"\n class=\"h-4 w-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\">\n <path stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M6 9l6 6 6-6\" />\n </svg>\n }\n </button>\n</div>\n" }]
|
|
1830
|
+
}], propDecorators: { title: [{
|
|
1831
|
+
type: Input
|
|
1832
|
+
}], isOpen: [{
|
|
1833
|
+
type: Input
|
|
1834
|
+
}] } });
|
|
1835
|
+
|
|
1836
|
+
class ImageViewerComponent {
|
|
1837
|
+
context;
|
|
1838
|
+
imageUrl = '';
|
|
1839
|
+
imageAlt = '';
|
|
1840
|
+
dialogService = inject(DialogService);
|
|
1841
|
+
onClose() {
|
|
1842
|
+
if (this.context?.overlayRef) {
|
|
1843
|
+
this.dialogService.close(this.context.overlayRef);
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ImageViewerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1847
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: ImageViewerComponent, isStandalone: true, selector: "haloduck-image-viewer", inputs: { context: "context", imageUrl: "imageUrl", imageAlt: "imageAlt" }, ngImport: i0, template: "<img class=\"max-w-full max-h-full object-cover cursor-pointer\"\n [src]=\"context?.imageUrl\"\n [alt]=\"context?.imageAlt\"\n (click)=\"onClose()\" />", styles: [""] });
|
|
1848
|
+
}
|
|
1849
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ImageViewerComponent, decorators: [{
|
|
1850
|
+
type: Component,
|
|
1851
|
+
args: [{ selector: 'haloduck-image-viewer', imports: [], template: "<img class=\"max-w-full max-h-full object-cover cursor-pointer\"\n [src]=\"context?.imageUrl\"\n [alt]=\"context?.imageAlt\"\n (click)=\"onClose()\" />" }]
|
|
1852
|
+
}], propDecorators: { context: [{
|
|
1853
|
+
type: Input
|
|
1854
|
+
}], imageUrl: [{
|
|
1855
|
+
type: Input
|
|
1856
|
+
}], imageAlt: [{
|
|
1857
|
+
type: Input
|
|
1858
|
+
}] } });
|
|
1859
|
+
|
|
1860
|
+
// 전역 변수로 드래그 데이터를 임시 저장
|
|
1861
|
+
let draggedBase64File = null;
|
|
1862
|
+
let originDrag = null;
|
|
1863
|
+
class ImageUploaderComponent {
|
|
1864
|
+
disabled = false;
|
|
1865
|
+
id;
|
|
1866
|
+
alt;
|
|
1867
|
+
urlPrefix;
|
|
1868
|
+
keyPrefix;
|
|
1869
|
+
uploadApi;
|
|
1870
|
+
backgroundImage;
|
|
1871
|
+
shouldPassToSibling = false;
|
|
1872
|
+
passToSibling = new EventEmitter();
|
|
1873
|
+
fileAdded = new EventEmitter();
|
|
1874
|
+
fileRemoved = new EventEmitter();
|
|
1875
|
+
error = new EventEmitter();
|
|
1876
|
+
imageUrl = null;
|
|
1877
|
+
file = null;
|
|
1878
|
+
isDragOver = false;
|
|
1879
|
+
isUploading = false;
|
|
1880
|
+
dialogService = inject(DialogService);
|
|
1881
|
+
onChange = () => { };
|
|
1882
|
+
onTouched = () => { };
|
|
1883
|
+
writeValue(key) {
|
|
1884
|
+
if (!key) {
|
|
1885
|
+
this.imageUrl = null;
|
|
1886
|
+
return;
|
|
1887
|
+
}
|
|
1888
|
+
this.imageUrl = `${this.urlPrefix}/${key}`;
|
|
1889
|
+
this.file = null;
|
|
1890
|
+
}
|
|
1891
|
+
registerOnChange(fn) {
|
|
1892
|
+
this.onChange = fn;
|
|
1893
|
+
}
|
|
1894
|
+
registerOnTouched(fn) {
|
|
1895
|
+
this.onTouched = fn;
|
|
1896
|
+
}
|
|
1897
|
+
setDisabledState(isDisabled) {
|
|
1898
|
+
this.disabled = isDisabled;
|
|
1899
|
+
}
|
|
1900
|
+
startUpload() {
|
|
1901
|
+
// Placeholder for upload logic
|
|
1902
|
+
if (!this.file) {
|
|
1903
|
+
return new Observable();
|
|
1904
|
+
}
|
|
1905
|
+
this.isUploading = true;
|
|
1906
|
+
this.file.isUploading = true;
|
|
1907
|
+
this.file.isUploaded = false;
|
|
1908
|
+
return this.uploadApi(this.file, this.keyPrefix).pipe(switchMap((key) => {
|
|
1909
|
+
if (this.file) {
|
|
1910
|
+
this.file.isUploading = false;
|
|
1911
|
+
this.file.isUploaded = true;
|
|
1912
|
+
this.file.key = key;
|
|
1913
|
+
}
|
|
1914
|
+
return of(this.file);
|
|
1915
|
+
}));
|
|
1916
|
+
}
|
|
1917
|
+
onFileSelected(event) {
|
|
1918
|
+
const input = event.target;
|
|
1919
|
+
if (input.files && input.files[0]) {
|
|
1920
|
+
const listFile = Array.from(input.files);
|
|
1921
|
+
this.setFiles(listFile);
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
onDrop(event) {
|
|
1925
|
+
event.preventDefault();
|
|
1926
|
+
this.isDragOver = false;
|
|
1927
|
+
if (event.dataTransfer?.files && event.dataTransfer.files[0]) {
|
|
1928
|
+
const listFile = Array.from(event.dataTransfer.files);
|
|
1929
|
+
this.setFiles(listFile);
|
|
1930
|
+
}
|
|
1931
|
+
else if (draggedBase64File) {
|
|
1932
|
+
if (this.file) {
|
|
1933
|
+
originDrag?.setFiles([this.file]); // 타켓에 파일이 있을 경우 원래 드래그한 컴포넌트에 파일 전달
|
|
1934
|
+
}
|
|
1935
|
+
else {
|
|
1936
|
+
originDrag?.removeImage();
|
|
1937
|
+
}
|
|
1938
|
+
fetch(draggedBase64File)
|
|
1939
|
+
.then((res) => res.blob())
|
|
1940
|
+
.then((blob) => {
|
|
1941
|
+
const file = new File([blob], 'dropped-file', { type: blob.type });
|
|
1942
|
+
this.setFiles([file]);
|
|
1943
|
+
});
|
|
1944
|
+
draggedBase64File = null; // 전역 변수 초기화
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
onDragOver(event) {
|
|
1948
|
+
event.preventDefault();
|
|
1949
|
+
this.isDragOver = true;
|
|
1950
|
+
}
|
|
1951
|
+
onDragLeave(event) {
|
|
1952
|
+
event.preventDefault();
|
|
1953
|
+
this.isDragOver = false;
|
|
1954
|
+
}
|
|
1955
|
+
onDragStart(event) {
|
|
1956
|
+
if (event.target instanceof HTMLImageElement) {
|
|
1957
|
+
const img = event.target;
|
|
1958
|
+
if (img.id && this.file) {
|
|
1959
|
+
const reader = new FileReader();
|
|
1960
|
+
reader.onload = () => {
|
|
1961
|
+
const base64File = reader.result;
|
|
1962
|
+
draggedBase64File = base64File; // 전역 변수에 저장
|
|
1963
|
+
originDrag = this; // 드래그 시작한 컴포넌트 저장
|
|
1964
|
+
if (event.dataTransfer) {
|
|
1965
|
+
event.dataTransfer.setData('text/plain', 'dragged-file'); // 최소한의 데이터 설정
|
|
1966
|
+
}
|
|
1967
|
+
};
|
|
1968
|
+
reader.readAsDataURL(this.file);
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
onDragEnd(event) {
|
|
1973
|
+
draggedBase64File = null;
|
|
1974
|
+
originDrag = null;
|
|
1975
|
+
}
|
|
1976
|
+
removeImage() {
|
|
1977
|
+
const removedFile = this.file;
|
|
1978
|
+
this.imageUrl = null;
|
|
1979
|
+
this.file = null;
|
|
1980
|
+
this.fileRemoved.emit(removedFile);
|
|
1981
|
+
this.onChange(null); // Notify form control
|
|
1982
|
+
}
|
|
1983
|
+
setFiles(listFile) {
|
|
1984
|
+
listFile = listFile.filter((file) => {
|
|
1985
|
+
const isValid = file.type.startsWith('image/');
|
|
1986
|
+
if (!isValid) {
|
|
1987
|
+
console.error('Invalid file type:', file.type);
|
|
1988
|
+
}
|
|
1989
|
+
return isValid;
|
|
1990
|
+
});
|
|
1991
|
+
this.file = listFile[0];
|
|
1992
|
+
this.imageUrl = URL.createObjectURL(listFile[0]);
|
|
1993
|
+
if (listFile.length > 1 && this.shouldPassToSibling) {
|
|
1994
|
+
listFile.shift();
|
|
1995
|
+
this.passToSibling.emit(listFile);
|
|
1996
|
+
}
|
|
1997
|
+
this.fileAdded.emit(this.file);
|
|
1998
|
+
this.onChange(this.file.name);
|
|
1999
|
+
this.onTouched();
|
|
2000
|
+
}
|
|
2001
|
+
previewImage(imageUrl) {
|
|
2002
|
+
this.dialogService.open(ImageViewerComponent, {
|
|
2003
|
+
imageUrl,
|
|
2004
|
+
imageAlt: this.alt,
|
|
2005
|
+
});
|
|
2006
|
+
}
|
|
2007
|
+
ngOnInit() { }
|
|
2008
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ImageUploaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2009
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: ImageUploaderComponent, isStandalone: true, selector: "haloduck-image-uploader", inputs: { disabled: "disabled", id: "id", alt: "alt", urlPrefix: "urlPrefix", keyPrefix: "keyPrefix", uploadApi: "uploadApi", backgroundImage: "backgroundImage", shouldPassToSibling: "shouldPassToSibling" }, outputs: { passToSibling: "passToSibling", fileAdded: "fileAdded", fileRemoved: "fileRemoved", error: "error" }, providers: [
|
|
2010
|
+
{
|
|
2011
|
+
provide: NG_VALUE_ACCESSOR,
|
|
2012
|
+
useExisting: forwardRef(() => ImageUploaderComponent),
|
|
2013
|
+
multi: true,
|
|
2014
|
+
},
|
|
2015
|
+
provideTranslocoScope('haloduck'),
|
|
2016
|
+
], ngImport: i0, template: "<div class=\"w-full h-full flex flex-col gap-1\">\n <label class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control\">\n <ng-content></ng-content>\n </label>\n <div class=\"h-full w-full p-2 border border-light-inactive dark:border-dark-inactive rounded-md\"\n [class.drag-over]=\"isDragOver\"\n (dragstart)=\"onDragStart($event)\"\n (dragend)=\"onDragEnd($event)\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\">\n <!-- Show image preview if available -->\n @if (imageUrl) {\n @if (!disabled) {\n <div class=\"relative\">\n <button type=\"button\"\n class=\"absolute -top-1 -right-1 bg-light-danger dark:bg-dark-danger text-light-on-danger dark:text-dark-on-danger rounded-full px-1 text-xs\"\n (click)=\"removeImage()\">\n x\n </button>\n </div>\n }\n <img [src]=\"imageUrl\"\n [id]=\"id + '-image'\"\n [alt]=\"alt || ''\"\n class=\"w-full h-full rounded-md aspect-square\"\n [ngClass]=\"{\n 'filter grayscale': disabled,\n }\"\n (click)=\"previewImage(imageUrl)\" />\n } @else {\n <!-- Show upload area -->\n <label for=\"{{id + '-input'}}\"\n class=\"flex flex-col items-center justify-center w-full h-full border-2 border-dashed border-light-inactive dark:border-dark-inactive rounded-md\"\n [ngClass]=\"{'cursor-pointer hover:border-light-primary dark:hover:border-dark-primary': !disabled}\">\n @if (backgroundImage) {\n <img [src]=\"backgroundImage\"\n [alt]=\"alt || ''\"\n class=\"w-full h-full rounded-md aspect-square\" />\n }@else {\n <span class=\"text-light-inactive dark:text-dark-inactive text-sm p-2\">{{ 'haloduck.ui.file.Drag and drop an image here, or click to select' | transloco }}</span>\n }\n\n @if(!disabled){\n <input [id]=\"id + '-input'\"\n type=\"file\"\n class=\"hidden\"\n accept=\"image/*\"\n [multiple]=\"passToSibling\"\n (cancel)=\"$event.stopPropagation()\"\n (change)=\"onFileSelected($event)\" />\n }\n </label>\n }\n </div>\n</div>\n", styles: [".drag-over{border-color:#3b82f6}.relative{position:relative}button{cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i1$2.TranslocoPipe, name: "transloco" }] });
|
|
2017
|
+
}
|
|
2018
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ImageUploaderComponent, decorators: [{
|
|
2019
|
+
type: Component,
|
|
2020
|
+
args: [{ selector: 'haloduck-image-uploader', imports: [CommonModule, TranslocoModule], providers: [
|
|
2021
|
+
{
|
|
2022
|
+
provide: NG_VALUE_ACCESSOR,
|
|
2023
|
+
useExisting: forwardRef(() => ImageUploaderComponent),
|
|
2024
|
+
multi: true,
|
|
2025
|
+
},
|
|
2026
|
+
provideTranslocoScope('haloduck'),
|
|
2027
|
+
], template: "<div class=\"w-full h-full flex flex-col gap-1\">\n <label class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control\">\n <ng-content></ng-content>\n </label>\n <div class=\"h-full w-full p-2 border border-light-inactive dark:border-dark-inactive rounded-md\"\n [class.drag-over]=\"isDragOver\"\n (dragstart)=\"onDragStart($event)\"\n (dragend)=\"onDragEnd($event)\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\">\n <!-- Show image preview if available -->\n @if (imageUrl) {\n @if (!disabled) {\n <div class=\"relative\">\n <button type=\"button\"\n class=\"absolute -top-1 -right-1 bg-light-danger dark:bg-dark-danger text-light-on-danger dark:text-dark-on-danger rounded-full px-1 text-xs\"\n (click)=\"removeImage()\">\n x\n </button>\n </div>\n }\n <img [src]=\"imageUrl\"\n [id]=\"id + '-image'\"\n [alt]=\"alt || ''\"\n class=\"w-full h-full rounded-md aspect-square\"\n [ngClass]=\"{\n 'filter grayscale': disabled,\n }\"\n (click)=\"previewImage(imageUrl)\" />\n } @else {\n <!-- Show upload area -->\n <label for=\"{{id + '-input'}}\"\n class=\"flex flex-col items-center justify-center w-full h-full border-2 border-dashed border-light-inactive dark:border-dark-inactive rounded-md\"\n [ngClass]=\"{'cursor-pointer hover:border-light-primary dark:hover:border-dark-primary': !disabled}\">\n @if (backgroundImage) {\n <img [src]=\"backgroundImage\"\n [alt]=\"alt || ''\"\n class=\"w-full h-full rounded-md aspect-square\" />\n }@else {\n <span class=\"text-light-inactive dark:text-dark-inactive text-sm p-2\">{{ 'haloduck.ui.file.Drag and drop an image here, or click to select' | transloco }}</span>\n }\n\n @if(!disabled){\n <input [id]=\"id + '-input'\"\n type=\"file\"\n class=\"hidden\"\n accept=\"image/*\"\n [multiple]=\"passToSibling\"\n (cancel)=\"$event.stopPropagation()\"\n (change)=\"onFileSelected($event)\" />\n }\n </label>\n }\n </div>\n</div>\n", styles: [".drag-over{border-color:#3b82f6}.relative{position:relative}button{cursor:pointer}\n"] }]
|
|
2028
|
+
}], propDecorators: { disabled: [{
|
|
2029
|
+
type: Input
|
|
2030
|
+
}], id: [{
|
|
2031
|
+
type: Input
|
|
2032
|
+
}], alt: [{
|
|
2033
|
+
type: Input
|
|
2034
|
+
}], urlPrefix: [{
|
|
2035
|
+
type: Input
|
|
2036
|
+
}], keyPrefix: [{
|
|
2037
|
+
type: Input
|
|
2038
|
+
}], uploadApi: [{
|
|
2039
|
+
type: Input
|
|
2040
|
+
}], backgroundImage: [{
|
|
2041
|
+
type: Input
|
|
2042
|
+
}], shouldPassToSibling: [{
|
|
2043
|
+
type: Input
|
|
2044
|
+
}], passToSibling: [{
|
|
2045
|
+
type: Output
|
|
2046
|
+
}], fileAdded: [{
|
|
2047
|
+
type: Output
|
|
2048
|
+
}], fileRemoved: [{
|
|
2049
|
+
type: Output
|
|
2050
|
+
}], error: [{
|
|
2051
|
+
type: Output
|
|
2052
|
+
}] } });
|
|
2053
|
+
|
|
2054
|
+
class LanguageSelectorOptionComponent {
|
|
2055
|
+
languageSelected = new EventEmitter();
|
|
2056
|
+
translateService = inject(TranslocoService);
|
|
2057
|
+
selectLanguage(language) {
|
|
2058
|
+
this.languageSelected.emit(language);
|
|
2059
|
+
}
|
|
2060
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: LanguageSelectorOptionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2061
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: LanguageSelectorOptionComponent, isStandalone: true, selector: "haloduck-language-selector-option", outputs: { languageSelected: "languageSelected" }, providers: [TranslocoService], ngImport: i0, template: "<div class=\"right-0 z-10 mt-2.5 origin-top-right rounded-md bg-light-background dark:bg-dark-background py-2 shadow-lg ring-1 ring-light-inactive dark:ring-dark-inactive\"\n role=\"menu\"\n aria-orientation=\"vertical\"\n aria-labelledby=\"user-menu-button\"\n tabindex=\"0\">\n @for (language of translateService.getAvailableLangs(); track language) {\n <a href=\"javascript:void(0)\"\n class=\"px-3 py-2 flex items-center flex-nowrap gap-2\"\n role=\"menuitem\"\n tabindex=\"-1\"\n id=\"language-item-{{language}}\"\n (click)=\"selectLanguage(language)\">\n <img class=\"w-6 h-6\"\n [src]=\"'i18n/icons/' + language + '.png'\">\n </a>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
2062
|
+
}
|
|
2063
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: LanguageSelectorOptionComponent, decorators: [{
|
|
2064
|
+
type: Component,
|
|
2065
|
+
args: [{ selector: 'haloduck-language-selector-option', imports: [CommonModule], providers: [TranslocoService], template: "<div class=\"right-0 z-10 mt-2.5 origin-top-right rounded-md bg-light-background dark:bg-dark-background py-2 shadow-lg ring-1 ring-light-inactive dark:ring-dark-inactive\"\n role=\"menu\"\n aria-orientation=\"vertical\"\n aria-labelledby=\"user-menu-button\"\n tabindex=\"0\">\n @for (language of translateService.getAvailableLangs(); track language) {\n <a href=\"javascript:void(0)\"\n class=\"px-3 py-2 flex items-center flex-nowrap gap-2\"\n role=\"menuitem\"\n tabindex=\"-1\"\n id=\"language-item-{{language}}\"\n (click)=\"selectLanguage(language)\">\n <img class=\"w-6 h-6\"\n [src]=\"'i18n/icons/' + language + '.png'\">\n </a>\n }\n</div>\n" }]
|
|
2066
|
+
}], propDecorators: { languageSelected: [{
|
|
2067
|
+
type: Output
|
|
2068
|
+
}] } });
|
|
2069
|
+
|
|
2070
|
+
class LanguageSelectorComponent {
|
|
2071
|
+
origin;
|
|
2072
|
+
destroy$ = new Subject();
|
|
2073
|
+
translateService = inject(TranslocoService);
|
|
2074
|
+
overlay = inject(Overlay);
|
|
2075
|
+
language = signal(this.translateService.getActiveLang(), ...(ngDevMode ? [{ debugName: "language" }] : []));
|
|
2076
|
+
overlayRef;
|
|
2077
|
+
showLanguageOptions() {
|
|
2078
|
+
if (!this.overlayRef) {
|
|
2079
|
+
this.overlayRef = this.overlay.create({
|
|
2080
|
+
positionStrategy: this.overlay
|
|
2081
|
+
.position()
|
|
2082
|
+
.flexibleConnectedTo(this.origin.nativeElement)
|
|
2083
|
+
.withPositions([
|
|
2084
|
+
{
|
|
2085
|
+
originX: 'start',
|
|
2086
|
+
originY: 'bottom',
|
|
2087
|
+
overlayX: 'start',
|
|
2088
|
+
overlayY: 'top',
|
|
2089
|
+
},
|
|
2090
|
+
{
|
|
2091
|
+
originX: 'start',
|
|
2092
|
+
originY: 'top',
|
|
2093
|
+
overlayX: 'start',
|
|
2094
|
+
overlayY: 'bottom',
|
|
2095
|
+
},
|
|
2096
|
+
]),
|
|
2097
|
+
scrollStrategy: this.overlay.scrollStrategies.reposition(),
|
|
2098
|
+
hasBackdrop: true,
|
|
2099
|
+
backdropClass: 'cdk-overlay-transparent-backdrop',
|
|
2100
|
+
});
|
|
2101
|
+
const calendarPortal = new ComponentPortal(LanguageSelectorOptionComponent);
|
|
2102
|
+
const componentRef = this.overlayRef.attach(calendarPortal);
|
|
2103
|
+
// Pass values directly to the CalendarComponent instance
|
|
2104
|
+
// componentRef.instance.firstDayOfWeek = this.firstDayOfWeek;
|
|
2105
|
+
// componentRef.instance.singleDate = true;
|
|
2106
|
+
// componentRef.instance.selectedDate = this.selectedDate;
|
|
2107
|
+
// Subscribe to events emitted
|
|
2108
|
+
componentRef.instance.languageSelected
|
|
2109
|
+
.pipe(takeUntil(this.destroy$))
|
|
2110
|
+
.subscribe((language) => {
|
|
2111
|
+
this.translateService.setActiveLang(language);
|
|
2112
|
+
this.hideLanguageOptions();
|
|
2113
|
+
});
|
|
2114
|
+
// Handle clicks outside the overlay
|
|
2115
|
+
this.overlayRef
|
|
2116
|
+
.backdropClick()
|
|
2117
|
+
.pipe(takeUntil(this.destroy$))
|
|
2118
|
+
.subscribe(() => {
|
|
2119
|
+
this.hideLanguageOptions();
|
|
2120
|
+
});
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
hideLanguageOptions() {
|
|
2124
|
+
if (this.overlayRef) {
|
|
2125
|
+
this.overlayRef.detach();
|
|
2126
|
+
this.overlayRef = undefined;
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
ngOnInit() {
|
|
2130
|
+
this.translateService.langChanges$
|
|
2131
|
+
.pipe(distinctUntilChanged(), takeUntil(this.destroy$))
|
|
2132
|
+
.subscribe((lang) => {
|
|
2133
|
+
this.language.set(lang);
|
|
2134
|
+
});
|
|
2135
|
+
}
|
|
2136
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: LanguageSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2137
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: LanguageSelectorComponent, isStandalone: true, selector: "haloduck-language-selector", viewQueries: [{ propertyName: "origin", first: true, predicate: ["origin"], descendants: true }], ngImport: i0, template: "<button #origin\n type=\"button\"\n (click)=\"showLanguageOptions()\"\n class=\"-m-1.5 flex items-center p-1.5 hover:cursor-pointer\"\n aria-expanded=\"false\"\n aria-haspopup=\"true\">\n <span class=\"max-lg:hidden flex lg:items-center\">\n <img class=\"w-6 h-6\"\n [src]=\"'i18n/icons/' + language() + '.png'\">\n </span>\n</button>\n", styles: [""] });
|
|
2138
|
+
}
|
|
2139
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: LanguageSelectorComponent, decorators: [{
|
|
2140
|
+
type: Component,
|
|
2141
|
+
args: [{ selector: 'haloduck-language-selector', imports: [], template: "<button #origin\n type=\"button\"\n (click)=\"showLanguageOptions()\"\n class=\"-m-1.5 flex items-center p-1.5 hover:cursor-pointer\"\n aria-expanded=\"false\"\n aria-haspopup=\"true\">\n <span class=\"max-lg:hidden flex lg:items-center\">\n <img class=\"w-6 h-6\"\n [src]=\"'i18n/icons/' + language() + '.png'\">\n </span>\n</button>\n" }]
|
|
2142
|
+
}], propDecorators: { origin: [{
|
|
2143
|
+
type: ViewChild,
|
|
2144
|
+
args: ['origin', { static: false }]
|
|
2145
|
+
}] } });
|
|
2146
|
+
|
|
2147
|
+
class MapToAddressComponent {
|
|
2148
|
+
coreService = inject(CoreService);
|
|
2149
|
+
mapId = this.coreService.getMapId();
|
|
2150
|
+
defaultLngLat = this.coreService.getDefaultLngLat();
|
|
2151
|
+
disabled = false;
|
|
2152
|
+
currentLngLat;
|
|
2153
|
+
_currentAddress;
|
|
2154
|
+
set currentAddress(value) {
|
|
2155
|
+
if (value) {
|
|
2156
|
+
this._currentAddress = value;
|
|
2157
|
+
this.setMapToAddress();
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
locationChanged = new EventEmitter();
|
|
2161
|
+
map;
|
|
2162
|
+
marker;
|
|
2163
|
+
location;
|
|
2164
|
+
onChange = () => { };
|
|
2165
|
+
onTouched = () => { };
|
|
2166
|
+
async ngOnInit() {
|
|
2167
|
+
await this.initScript();
|
|
2168
|
+
this.initMap();
|
|
2169
|
+
}
|
|
2170
|
+
initScript() {
|
|
2171
|
+
return new Promise((resolve, reject) => {
|
|
2172
|
+
if (typeof google !== 'undefined') {
|
|
2173
|
+
resolve();
|
|
2174
|
+
return;
|
|
2175
|
+
}
|
|
2176
|
+
const script = document.createElement('script');
|
|
2177
|
+
script.src = `https://maps.googleapis.com/maps/api/js?key=${this.coreService.getGoogleApiKey()}&libraries=places,marker&loading=sync`;
|
|
2178
|
+
script.async = false;
|
|
2179
|
+
script.defer = true;
|
|
2180
|
+
script.onload = () => resolve();
|
|
2181
|
+
script.onerror = (error) => reject(error);
|
|
2182
|
+
document.head.appendChild(script);
|
|
2183
|
+
console.log('Google Maps script loaded', script);
|
|
2184
|
+
});
|
|
2185
|
+
}
|
|
2186
|
+
initMap() {
|
|
2187
|
+
if (this.currentLngLat) {
|
|
2188
|
+
isDevMode() &&
|
|
2189
|
+
console.log('Using provided location:', this.currentLngLat);
|
|
2190
|
+
this.initializeMap(this.currentLngLat);
|
|
2191
|
+
}
|
|
2192
|
+
else {
|
|
2193
|
+
navigator.geolocation.getCurrentPosition((position) => {
|
|
2194
|
+
const currentLocation = {
|
|
2195
|
+
lat: position.coords.latitude,
|
|
2196
|
+
lng: position.coords.longitude,
|
|
2197
|
+
};
|
|
2198
|
+
isDevMode() &&
|
|
2199
|
+
console.log('Using current location:', currentLocation);
|
|
2200
|
+
this.initializeMap(currentLocation);
|
|
2201
|
+
}, (error) => {
|
|
2202
|
+
if (error.code === error.PERMISSION_DENIED) {
|
|
2203
|
+
isDevMode() &&
|
|
2204
|
+
console.log('Location permission denied. Using default location.:', this.defaultLngLat);
|
|
2205
|
+
}
|
|
2206
|
+
else if (error.code === error.POSITION_UNAVAILABLE ||
|
|
2207
|
+
error.code === error.TIMEOUT) {
|
|
2208
|
+
isDevMode() &&
|
|
2209
|
+
console.log('Unable to determine location. Using default location.:', this.defaultLngLat);
|
|
2210
|
+
}
|
|
2211
|
+
this.initializeMap(this.defaultLngLat);
|
|
2212
|
+
});
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
initializeMap(lngLat) {
|
|
2216
|
+
this.map = new google.maps.Map(document.getElementById('map'), {
|
|
2217
|
+
center: lngLat,
|
|
2218
|
+
zoom: 15,
|
|
2219
|
+
mapId: this.mapId,
|
|
2220
|
+
});
|
|
2221
|
+
this.marker = new google.maps.marker.AdvancedMarkerElement({
|
|
2222
|
+
position: lngLat,
|
|
2223
|
+
map: this.map,
|
|
2224
|
+
draggable: true,
|
|
2225
|
+
});
|
|
2226
|
+
// Listen for marker drag end to update address immediately
|
|
2227
|
+
this.marker.addListener('dragend', () => {
|
|
2228
|
+
const position = this.marker.position;
|
|
2229
|
+
if (position) {
|
|
2230
|
+
isDevMode() && console.log('Marker dragged to:', position.toJSON());
|
|
2231
|
+
this.getAddress(); // Fetch address after dragging
|
|
2232
|
+
}
|
|
2233
|
+
});
|
|
2234
|
+
this.map.addListener('click', (event) => this.onClick(event));
|
|
2235
|
+
if (!this.currentLngLat) {
|
|
2236
|
+
this.getAddress();
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
onClick(event) {
|
|
2240
|
+
isDevMode() && console.log('Map clicked at:', event.latLng);
|
|
2241
|
+
if (event.latLng) {
|
|
2242
|
+
// Explicitly update marker position
|
|
2243
|
+
this.marker.position = event.latLng;
|
|
2244
|
+
this.marker.map = this.map; // Ensure marker is attached to the map
|
|
2245
|
+
isDevMode() &&
|
|
2246
|
+
console.log('Marker position updated to:', event.latLng.toJSON());
|
|
2247
|
+
this.getAddress(); // Fetch address immediately after click
|
|
2248
|
+
this.onTouched(); // Mark as touched
|
|
2249
|
+
}
|
|
2250
|
+
else {
|
|
2251
|
+
isDevMode() &&
|
|
2252
|
+
console.error('Failed to update marker position: event.latLng is undefined');
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
setMapToAddress() {
|
|
2256
|
+
const geocoder = new google.maps.Geocoder();
|
|
2257
|
+
geocoder.geocode({ address: this._currentAddress }, (results, status) => {
|
|
2258
|
+
if (status === 'OK' && results[0]) {
|
|
2259
|
+
const location = results[0].geometry.location;
|
|
2260
|
+
isDevMode() && console.log('Geocoded location:', location.toJSON());
|
|
2261
|
+
this.initializeMap(location.toJSON());
|
|
2262
|
+
}
|
|
2263
|
+
else {
|
|
2264
|
+
isDevMode() && console.error('Geocoding failed:', status);
|
|
2265
|
+
}
|
|
2266
|
+
});
|
|
2267
|
+
}
|
|
2268
|
+
getAddress() {
|
|
2269
|
+
const geocoder = new google.maps.Geocoder();
|
|
2270
|
+
const position = this.marker.position;
|
|
2271
|
+
if (!position) {
|
|
2272
|
+
isDevMode() && console.error('Position update is unavailable');
|
|
2273
|
+
return;
|
|
2274
|
+
}
|
|
2275
|
+
geocoder.geocode({ location: position }, (results, status) => {
|
|
2276
|
+
isDevMode() && console.log('Geocoded results:', status, results);
|
|
2277
|
+
if (status === 'OK' && results[0]) {
|
|
2278
|
+
this.location = this.parseAddress(results);
|
|
2279
|
+
this.locationChanged.emit(this.location);
|
|
2280
|
+
this.onChange(this.location); // Notify the form control
|
|
2281
|
+
}
|
|
2282
|
+
});
|
|
2283
|
+
}
|
|
2284
|
+
parseAddress(results) {
|
|
2285
|
+
return {
|
|
2286
|
+
plusCode: this.getComponentLongName(results, 'plus_code'),
|
|
2287
|
+
postalCode: this.getComponentLongName(results, 'postal_code'),
|
|
2288
|
+
countryCode: this.getComponentShortName(results, 'country').toUpperCase(),
|
|
2289
|
+
country: this.getComponentLongName(results, 'country'),
|
|
2290
|
+
area1: this.getComponentLongName(results, 'administrative_area_level_1'),
|
|
2291
|
+
area2: this.getComponentLongName(results, 'sublocality_level_1'),
|
|
2292
|
+
area3: this.getComponentLongName(results, 'sublocality_level_2'),
|
|
2293
|
+
formatted: results[0]?.formatted_address,
|
|
2294
|
+
lat: results[0]?.geometry?.location?.lat(),
|
|
2295
|
+
lng: results[0]?.geometry?.location?.lng(),
|
|
2296
|
+
};
|
|
2297
|
+
}
|
|
2298
|
+
getComponentLongName(results, type) {
|
|
2299
|
+
for (let i = 0; i < results.length; i++) {
|
|
2300
|
+
const component = results[i].address_components.find((c) => c.types.includes(type));
|
|
2301
|
+
if (component) {
|
|
2302
|
+
return component.long_name;
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
return '';
|
|
2306
|
+
}
|
|
2307
|
+
getComponentShortName(results, type) {
|
|
2308
|
+
for (let i = 0; i < results.length; i++) {
|
|
2309
|
+
const component = results[i].address_components.find((c) => c.types.includes(type));
|
|
2310
|
+
if (component) {
|
|
2311
|
+
return component.short_name;
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
return '';
|
|
2315
|
+
}
|
|
2316
|
+
writeValue(value) {
|
|
2317
|
+
if (value) {
|
|
2318
|
+
this.location = value;
|
|
2319
|
+
this.currentLngLat = { lat: value.lat, lng: value.lng };
|
|
2320
|
+
this.initializeMap(this.currentLngLat);
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
registerOnChange(fn) {
|
|
2324
|
+
this.onChange = fn;
|
|
2325
|
+
}
|
|
2326
|
+
registerOnTouched(fn) {
|
|
2327
|
+
this.onTouched = fn;
|
|
2328
|
+
}
|
|
2329
|
+
setDisabledState(isDisabled) {
|
|
2330
|
+
this.disabled = isDisabled;
|
|
2331
|
+
}
|
|
2332
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: MapToAddressComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2333
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: MapToAddressComponent, isStandalone: true, selector: "haloduck-map-to-address", inputs: { disabled: "disabled", currentLngLat: "currentLngLat", currentAddress: "currentAddress" }, outputs: { locationChanged: "locationChanged" }, providers: [
|
|
2334
|
+
{
|
|
2335
|
+
provide: NG_VALUE_ACCESSOR,
|
|
2336
|
+
useExisting: MapToAddressComponent,
|
|
2337
|
+
multi: true,
|
|
2338
|
+
},
|
|
2339
|
+
], ngImport: i0, template: "<div id=\"map\" class=\"w-full h-full\"></div>", styles: ["#map{border:1px solid #ccc;margin-bottom:10px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }] });
|
|
2340
|
+
}
|
|
2341
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: MapToAddressComponent, decorators: [{
|
|
2342
|
+
type: Component,
|
|
2343
|
+
args: [{ selector: 'haloduck-map-to-address', imports: [CommonModule, FormsModule], providers: [
|
|
2344
|
+
{
|
|
2345
|
+
provide: NG_VALUE_ACCESSOR,
|
|
2346
|
+
useExisting: MapToAddressComponent,
|
|
2347
|
+
multi: true,
|
|
2348
|
+
},
|
|
2349
|
+
], template: "<div id=\"map\" class=\"w-full h-full\"></div>", styles: ["#map{border:1px solid #ccc;margin-bottom:10px}\n"] }]
|
|
2350
|
+
}], propDecorators: { disabled: [{
|
|
2351
|
+
type: Input
|
|
2352
|
+
}], currentLngLat: [{
|
|
2353
|
+
type: Input
|
|
2354
|
+
}], currentAddress: [{
|
|
2355
|
+
type: Input
|
|
2356
|
+
}], locationChanged: [{
|
|
2357
|
+
type: Output
|
|
2358
|
+
}] } });
|
|
2359
|
+
|
|
2360
|
+
class SideMenuItemComponent {
|
|
2361
|
+
sanitizer;
|
|
2362
|
+
router = inject(Router);
|
|
2363
|
+
menuItem;
|
|
2364
|
+
depth = 0;
|
|
2365
|
+
menuItemSelectHandler;
|
|
2366
|
+
iconSvg;
|
|
2367
|
+
constructor(sanitizer) {
|
|
2368
|
+
this.sanitizer = sanitizer;
|
|
2369
|
+
}
|
|
2370
|
+
ngOnInit() {
|
|
2371
|
+
if (this.menuItem.iconType === 'svg') {
|
|
2372
|
+
fetch(this.menuItem.icon || '')
|
|
2373
|
+
.then(response => { return response.ok ? response : Promise.reject(response); })
|
|
2374
|
+
.then(response => response.text())
|
|
2375
|
+
.then(svg => this.iconSvg = this.sanitizer.bypassSecurityTrustHtml(svg));
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
onMenuItemSelected(menuItem) {
|
|
2379
|
+
if (this.menuItem.link) {
|
|
2380
|
+
this.navigateTo([this.menuItem.link]);
|
|
2381
|
+
}
|
|
2382
|
+
if (this.menuItemSelectHandler) {
|
|
2383
|
+
this.menuItemSelectHandler.emit(this.menuItem);
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
navigateTo(link) {
|
|
2387
|
+
this.router.navigate(link);
|
|
2388
|
+
}
|
|
2389
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: SideMenuItemComponent, deps: [{ token: i1$4.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component });
|
|
2390
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: SideMenuItemComponent, isStandalone: true, selector: "haloduck-side-menu-item", inputs: { menuItem: "menuItem", depth: "depth", menuItemSelectHandler: "menuItemSelectHandler" }, ngImport: i0, template: "<a href=\"javascript:void(0)\"\n (click)=\"onMenuItemSelected(menuItem)\"\n class=\"flex items-center gap-2 px-4 py-2 rounded-md cursor-pointer hover:text-light-on-primary dark:hover:text-dark-on-primary hover:bg-light-primary dark:hover:bg-dark-primary hover:cursor-pointer\"\n [ngClass]=\"{\n 'font-bold text-light-on-secondary dark:bg-dark-on-secondary bg-light-secondary dark:bg-dark-secondary': menuItem.isSelected,\n }\">\n @if (menuItem.iconType === 'svg') {\n <div [innerHTML]=\"iconSvg\"></div>\n } @else {\n @if (menuItem.icon) {\n <img class=\"w-5 h-5\"\n [src]=\"menuItem.icon\"\n [alt]=\"menuItem.title\"\n [title]=\"menuItem.title\"\n [loading]=\"'lazy'\" />\n }\n }\n <span class=\"overflow-hidden text-ellipsis whitespace-nowrap\"\n title=\"{{ menuItem.title }}\"\n [ngStyle]=\"{'margin-left.px': depth ? depth * 16 : 0}\">\n {{ menuItem.title }}\n </span>\n</a>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
|
|
2391
|
+
}
|
|
2392
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: SideMenuItemComponent, decorators: [{
|
|
2393
|
+
type: Component,
|
|
2394
|
+
args: [{ selector: 'haloduck-side-menu-item', imports: [CommonModule], template: "<a href=\"javascript:void(0)\"\n (click)=\"onMenuItemSelected(menuItem)\"\n class=\"flex items-center gap-2 px-4 py-2 rounded-md cursor-pointer hover:text-light-on-primary dark:hover:text-dark-on-primary hover:bg-light-primary dark:hover:bg-dark-primary hover:cursor-pointer\"\n [ngClass]=\"{\n 'font-bold text-light-on-secondary dark:bg-dark-on-secondary bg-light-secondary dark:bg-dark-secondary': menuItem.isSelected,\n }\">\n @if (menuItem.iconType === 'svg') {\n <div [innerHTML]=\"iconSvg\"></div>\n } @else {\n @if (menuItem.icon) {\n <img class=\"w-5 h-5\"\n [src]=\"menuItem.icon\"\n [alt]=\"menuItem.title\"\n [title]=\"menuItem.title\"\n [loading]=\"'lazy'\" />\n }\n }\n <span class=\"overflow-hidden text-ellipsis whitespace-nowrap\"\n title=\"{{ menuItem.title }}\"\n [ngStyle]=\"{'margin-left.px': depth ? depth * 16 : 0}\">\n {{ menuItem.title }}\n </span>\n</a>\n" }]
|
|
2395
|
+
}], ctorParameters: () => [{ type: i1$4.DomSanitizer }], propDecorators: { menuItem: [{
|
|
2396
|
+
type: Input
|
|
2397
|
+
}], depth: [{
|
|
2398
|
+
type: Input
|
|
2399
|
+
}], menuItemSelectHandler: [{
|
|
2400
|
+
type: Input
|
|
2401
|
+
}] } });
|
|
2402
|
+
|
|
2403
|
+
class SideMenuComponent {
|
|
2404
|
+
menuItems = [];
|
|
2405
|
+
depth = 0;
|
|
2406
|
+
menuItemSelected = new EventEmitter();
|
|
2407
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: SideMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2408
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: SideMenuComponent, isStandalone: true, selector: "haloduck-side-menu", inputs: { menuItems: "menuItems", depth: "depth" }, outputs: { menuItemSelected: "menuItemSelected" }, ngImport: i0, template: "<div class=\"flex flex-col text-sm text-light-on-background dark:text-dark-on-background\">\n @for (menuItem of menuItems; track menuItem.link) {\n <haloduck-side-menu-item [menuItemSelectHandler]=\"menuItemSelected\"\n [menuItem]=\"menuItem\"\n [depth]=\"depth\"></haloduck-side-menu-item>\n @if (menuItem.children && menuItem.children.length > 0) {\n <haloduck-side-menu [menuItems]=\"menuItem.children\"\n [depth]=\"depth + 1\"\n (menuItemSelected)=\"menuItemSelected.emit($event)\"></haloduck-side-menu>\n }\n }\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: SideMenuComponent, selector: "haloduck-side-menu", inputs: ["menuItems", "depth"], outputs: ["menuItemSelected"] }, { kind: "component", type: SideMenuItemComponent, selector: "haloduck-side-menu-item", inputs: ["menuItem", "depth", "menuItemSelectHandler"] }] });
|
|
2409
|
+
}
|
|
2410
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: SideMenuComponent, decorators: [{
|
|
2411
|
+
type: Component,
|
|
2412
|
+
args: [{ selector: 'haloduck-side-menu', imports: [SideMenuItemComponent], template: "<div class=\"flex flex-col text-sm text-light-on-background dark:text-dark-on-background\">\n @for (menuItem of menuItems; track menuItem.link) {\n <haloduck-side-menu-item [menuItemSelectHandler]=\"menuItemSelected\"\n [menuItem]=\"menuItem\"\n [depth]=\"depth\"></haloduck-side-menu-item>\n @if (menuItem.children && menuItem.children.length > 0) {\n <haloduck-side-menu [menuItems]=\"menuItem.children\"\n [depth]=\"depth + 1\"\n (menuItemSelected)=\"menuItemSelected.emit($event)\"></haloduck-side-menu>\n }\n }\n</div>\n" }]
|
|
2413
|
+
}], propDecorators: { menuItems: [{
|
|
2414
|
+
type: Input
|
|
2415
|
+
}], depth: [{
|
|
2416
|
+
type: Input
|
|
2417
|
+
}], menuItemSelected: [{
|
|
2418
|
+
type: Output
|
|
2419
|
+
}] } });
|
|
2420
|
+
|
|
2421
|
+
class StlViewerComponent {
|
|
2422
|
+
fileUrl;
|
|
2423
|
+
filename;
|
|
2424
|
+
context;
|
|
2425
|
+
containerRef;
|
|
2426
|
+
dialogService = inject(DialogService);
|
|
2427
|
+
isPopup = false;
|
|
2428
|
+
scene;
|
|
2429
|
+
camera;
|
|
2430
|
+
renderer;
|
|
2431
|
+
loader = new STLLoader();
|
|
2432
|
+
controls;
|
|
2433
|
+
loadingPercentage = 0;
|
|
2434
|
+
showStl() {
|
|
2435
|
+
if (this.isPopup) {
|
|
2436
|
+
return;
|
|
2437
|
+
}
|
|
2438
|
+
this.dialogService.open(StlViewerComponent, {
|
|
2439
|
+
fileUrl: this.fileUrl,
|
|
2440
|
+
filename: this.filename,
|
|
2441
|
+
}, ['w-full', 'h-full']);
|
|
2442
|
+
}
|
|
2443
|
+
download() {
|
|
2444
|
+
download(this.fileUrl);
|
|
2445
|
+
}
|
|
2446
|
+
closeDialog() {
|
|
2447
|
+
this.dialogService.close(this.context.overlayRef);
|
|
2448
|
+
}
|
|
2449
|
+
animate = () => {
|
|
2450
|
+
requestAnimationFrame(this.animate);
|
|
2451
|
+
this.controls.update();
|
|
2452
|
+
this.renderer.render(this.scene, this.camera);
|
|
2453
|
+
};
|
|
2454
|
+
ngAfterViewInit() {
|
|
2455
|
+
const container = this.containerRef.nativeElement;
|
|
2456
|
+
this.scene = new THREE.Scene();
|
|
2457
|
+
this.scene.background = new THREE.Color(0xffffff);
|
|
2458
|
+
this.camera = new THREE.PerspectiveCamera(50, container.clientWidth / container.clientHeight, 0.1, 1000);
|
|
2459
|
+
this.camera.position.z = 100;
|
|
2460
|
+
this.renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
2461
|
+
this.renderer.setSize(container.clientWidth, container.clientHeight);
|
|
2462
|
+
container.appendChild(this.renderer.domElement);
|
|
2463
|
+
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
|
|
2464
|
+
this.controls.enableDamping = true;
|
|
2465
|
+
this.controls.dampingFactor = 0.05;
|
|
2466
|
+
// Adjust lighting
|
|
2467
|
+
const light = new THREE.DirectionalLight(0xffffff, 1.5); // Increased intensity
|
|
2468
|
+
light.position.set(0, 0, 1).normalize();
|
|
2469
|
+
const light2 = new THREE.DirectionalLight(0xffffff, 1.5); // Increased intensity
|
|
2470
|
+
light2.position.set(0, 0, -1).normalize();
|
|
2471
|
+
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); // Added ambient light
|
|
2472
|
+
this.scene.add(light, light2, ambientLight);
|
|
2473
|
+
this.loader.load(this.fileUrl || this.context?.fileUrl, (geometry) => {
|
|
2474
|
+
// Use MeshStandardMaterial with DoubleSide to render both sides
|
|
2475
|
+
const material = new THREE.MeshStandardMaterial({
|
|
2476
|
+
color: 0xffffff,
|
|
2477
|
+
metalness: 0.3,
|
|
2478
|
+
roughness: 0.5,
|
|
2479
|
+
side: THREE.DoubleSide, // Render both front and back faces
|
|
2480
|
+
});
|
|
2481
|
+
const mesh = new THREE.Mesh(geometry, material);
|
|
2482
|
+
geometry.computeBoundingBox();
|
|
2483
|
+
const boundingBox = geometry.boundingBox;
|
|
2484
|
+
const center = new THREE.Vector3();
|
|
2485
|
+
boundingBox?.getCenter(center);
|
|
2486
|
+
mesh.geometry.translate(-center.x, -center.y, -center.z);
|
|
2487
|
+
this.scene.add(mesh);
|
|
2488
|
+
const size = new THREE.Vector3();
|
|
2489
|
+
boundingBox?.getSize(size);
|
|
2490
|
+
const maxDim = Math.max(size.x, size.y, size.z);
|
|
2491
|
+
this.camera.position.z = maxDim * 2;
|
|
2492
|
+
}, (xhr) => {
|
|
2493
|
+
this.loadingPercentage = (xhr.loaded / xhr.total) * 100;
|
|
2494
|
+
isDevMode() && console.log(this.loadingPercentage + '% loaded');
|
|
2495
|
+
}, (error) => {
|
|
2496
|
+
isDevMode() && console.error('Error loading STL:', error);
|
|
2497
|
+
});
|
|
2498
|
+
this.animate();
|
|
2499
|
+
}
|
|
2500
|
+
ngOnDestroy() {
|
|
2501
|
+
this.renderer.dispose();
|
|
2502
|
+
this.controls.dispose();
|
|
2503
|
+
}
|
|
2504
|
+
ngOnInit() {
|
|
2505
|
+
if (this.context) {
|
|
2506
|
+
this.isPopup = true;
|
|
2507
|
+
this.fileUrl = this.context.fileUrl;
|
|
2508
|
+
this.filename = this.context.filename;
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: StlViewerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2512
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: StlViewerComponent, isStandalone: true, selector: "haloduck-stl-viewer", inputs: { fileUrl: "fileUrl", filename: "filename", context: "context" }, providers: [provideTranslocoScope('haloduck')], viewQueries: [{ propertyName: "containerRef", first: true, predicate: ["viewerContainer"], descendants: true }], ngImport: i0, template: "<div class=\"relative w-full h-full flex flex-col items-end\">\n <div class=\"w-full h-full flex items-start flex-col\">\n <div #viewerContainer\n class=\"w-full h-full rounded-md\"></div>\n @if (loadingPercentage < 100)\n {\n <div\n class=\"h-2 bg-light-primary-light dark:bg-dark-primary-light\"\n [ngStyle]=\"{ 'width': loadingPercentage + '%' }\">\n </div>\n }\n</div>\n<div class=\"absolute bottom-2 right-0 flex justify-end gap-2\">\n @if (context) {\n <haloduck-button variant=\"primary\"\n (click)=\"download()\">{{ 'haloduck.ui.button.Download' | transloco }}</haloduck-button>\n <haloduck-button variant=\"danger\"\n (click)=\"closeDialog()\">{{ 'haloduck.ui.button.Close' | transloco }}</haloduck-button>\n } @else {\n <haloduck-button variant=\"primary\"\n (click)=\"download()\">{{ 'haloduck.ui.button.Download' | transloco }}</haloduck-button>\n <haloduck-button variant=\"secondary\"\n (click)=\"showStl()\">{{ 'haloduck.ui.button.View' | transloco }}</haloduck-button>\n }\n</div>\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: ButtonComponent, selector: "haloduck-button", inputs: ["disabled", "variant"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i1$2.TranslocoPipe, name: "transloco" }] });
|
|
2513
|
+
}
|
|
2514
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: StlViewerComponent, decorators: [{
|
|
2515
|
+
type: Component,
|
|
2516
|
+
args: [{ selector: 'haloduck-stl-viewer', imports: [CommonModule, ButtonComponent, TranslocoModule], providers: [provideTranslocoScope('haloduck')], template: "<div class=\"relative w-full h-full flex flex-col items-end\">\n <div class=\"w-full h-full flex items-start flex-col\">\n <div #viewerContainer\n class=\"w-full h-full rounded-md\"></div>\n @if (loadingPercentage < 100)\n {\n <div\n class=\"h-2 bg-light-primary-light dark:bg-dark-primary-light\"\n [ngStyle]=\"{ 'width': loadingPercentage + '%' }\">\n </div>\n }\n</div>\n<div class=\"absolute bottom-2 right-0 flex justify-end gap-2\">\n @if (context) {\n <haloduck-button variant=\"primary\"\n (click)=\"download()\">{{ 'haloduck.ui.button.Download' | transloco }}</haloduck-button>\n <haloduck-button variant=\"danger\"\n (click)=\"closeDialog()\">{{ 'haloduck.ui.button.Close' | transloco }}</haloduck-button>\n } @else {\n <haloduck-button variant=\"primary\"\n (click)=\"download()\">{{ 'haloduck.ui.button.Download' | transloco }}</haloduck-button>\n <haloduck-button variant=\"secondary\"\n (click)=\"showStl()\">{{ 'haloduck.ui.button.View' | transloco }}</haloduck-button>\n }\n</div>\n</div>\n" }]
|
|
2517
|
+
}], propDecorators: { fileUrl: [{
|
|
2518
|
+
type: Input
|
|
2519
|
+
}], filename: [{
|
|
2520
|
+
type: Input
|
|
2521
|
+
}], context: [{
|
|
2522
|
+
type: Input
|
|
2523
|
+
}], containerRef: [{
|
|
2524
|
+
type: ViewChild,
|
|
2525
|
+
args: ['viewerContainer']
|
|
2526
|
+
}] } });
|
|
2527
|
+
|
|
2528
|
+
class TableComponent {
|
|
2529
|
+
coreService = inject(CoreService);
|
|
2530
|
+
tableLayout = 'auto';
|
|
2531
|
+
showHeader = true;
|
|
2532
|
+
useLoadMore = true;
|
|
2533
|
+
columns = [];
|
|
2534
|
+
rows = new Observable();
|
|
2535
|
+
isLoading = new Observable();
|
|
2536
|
+
isPaging = new Observable();
|
|
2537
|
+
sort = new Observable();
|
|
2538
|
+
expandedTemplate; // TODO
|
|
2539
|
+
customTemplates = {}; // Add this line
|
|
2540
|
+
onSortChange = new EventEmitter();
|
|
2541
|
+
lastEvaluatedKey = new Observable();
|
|
2542
|
+
onLoadMore = new EventEmitter();
|
|
2543
|
+
onRowClick = new EventEmitter();
|
|
2544
|
+
onRowDblClick = new EventEmitter();
|
|
2545
|
+
getColumnValue(row, column) {
|
|
2546
|
+
const value = this.getColumnValueRaw(row, column);
|
|
2547
|
+
if (column.customRenderFn) {
|
|
2548
|
+
return column.customRenderFn(value);
|
|
2549
|
+
}
|
|
2550
|
+
switch (column.type) {
|
|
2551
|
+
case 'date':
|
|
2552
|
+
return this.getDate(value);
|
|
2553
|
+
case 'datetime':
|
|
2554
|
+
return this.getDateTime(value);
|
|
2555
|
+
case 'time':
|
|
2556
|
+
return this.getTime(value);
|
|
2557
|
+
default:
|
|
2558
|
+
return of(value);
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
getColumnValueRaw(row, column) {
|
|
2562
|
+
if (Array.isArray(column.key)) {
|
|
2563
|
+
const value = this.getColumnValueRawWithArrayKey(row, column);
|
|
2564
|
+
return value;
|
|
2565
|
+
}
|
|
2566
|
+
else {
|
|
2567
|
+
return row[column.key];
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
getColumnValueRawWithArrayKey(row, column) {
|
|
2571
|
+
let value = row;
|
|
2572
|
+
let invalidKey = false;
|
|
2573
|
+
for (const key of column.key) {
|
|
2574
|
+
if (Array.isArray(column.key) && column.key.length > 1) {
|
|
2575
|
+
const [first, ...remainingKeys] = column.key;
|
|
2576
|
+
return this.getColumnValueRawWithArrayKey(value[first], {
|
|
2577
|
+
...column,
|
|
2578
|
+
key: remainingKeys,
|
|
2579
|
+
});
|
|
2580
|
+
}
|
|
2581
|
+
else {
|
|
2582
|
+
if (invalidKey || value[key] === undefined) {
|
|
2583
|
+
invalidKey = true;
|
|
2584
|
+
break;
|
|
2585
|
+
}
|
|
2586
|
+
value = value[key];
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
if (invalidKey) {
|
|
2590
|
+
value = '-';
|
|
2591
|
+
}
|
|
2592
|
+
return value;
|
|
2593
|
+
}
|
|
2594
|
+
getNumber(data) {
|
|
2595
|
+
const numberPipe = new DecimalPipe('en-US');
|
|
2596
|
+
return of(numberPipe.transform(data) || '');
|
|
2597
|
+
}
|
|
2598
|
+
getDate(data) {
|
|
2599
|
+
const datePipe = new DatePipe('en-US');
|
|
2600
|
+
return of(datePipe.transform(data, 'shortDate') || '');
|
|
2601
|
+
}
|
|
2602
|
+
getDateTime(data) {
|
|
2603
|
+
const datePipe = new DatePipe('en-US');
|
|
2604
|
+
return of(datePipe.transform(data, this.coreService.getDateFormatLong()) || '');
|
|
2605
|
+
}
|
|
2606
|
+
getTime(data) {
|
|
2607
|
+
const datePipe = new DatePipe('en-US');
|
|
2608
|
+
return of(datePipe.transform(data, 'Time') || '');
|
|
2609
|
+
}
|
|
2610
|
+
onUpdateSort(field, direction) {
|
|
2611
|
+
this.onSortChange.emit({ field, direction });
|
|
2612
|
+
}
|
|
2613
|
+
getSortDirection(field) {
|
|
2614
|
+
return this.sort.pipe(map((sort) => {
|
|
2615
|
+
const sortField = sort.find((s) => s.field === field);
|
|
2616
|
+
return sortField?.direction;
|
|
2617
|
+
}));
|
|
2618
|
+
}
|
|
2619
|
+
onLoadMoreClicked() {
|
|
2620
|
+
this.lastEvaluatedKey.pipe(take(1)).subscribe((lastEvaluatedKey) => {
|
|
2621
|
+
this.onLoadMore.emit(lastEvaluatedKey);
|
|
2622
|
+
});
|
|
2623
|
+
}
|
|
2624
|
+
onRowClicked(row) {
|
|
2625
|
+
this.onRowClick.emit(row);
|
|
2626
|
+
}
|
|
2627
|
+
onRowDblClicked(row) {
|
|
2628
|
+
this.onRowDblClick.emit(row);
|
|
2629
|
+
}
|
|
2630
|
+
ngOnInit() { }
|
|
2631
|
+
constructor() { }
|
|
2632
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: TableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2633
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: TableComponent, isStandalone: true, selector: "haloduck-table", inputs: { tableLayout: "tableLayout", showHeader: "showHeader", useLoadMore: "useLoadMore", columns: "columns", rows: "rows", isLoading: "isLoading", isPaging: "isPaging", sort: "sort", expandedTemplate: "expandedTemplate", customTemplates: "customTemplates", lastEvaluatedKey: "lastEvaluatedKey" }, outputs: { onSortChange: "onSortChange", onLoadMore: "onLoadMore", onRowClick: "onRowClick", onRowDblClick: "onRowDblClick" }, providers: [provideTranslocoScope('haloduck')], ngImport: i0, template: "<div class=\"w-full h-full shadow border border-light-inactive dark:border-dark-inactive rounded-lg overflow-auto\">\n <table class=\"w-full h-full\"\n [ngClass]=\"{\n 'table-fixed': tableLayout === 'fixed',\n 'table-auto': tableLayout === 'auto',\n }\">\n <colgroup>\n @for (column of columns; track column.key) {\n @if (!column.hidden || !(column.hidden | async)) {\n <col class=\"{{ column.width || '' }}\" />\n }\n }\n </colgroup>\n @if (showHeader) {\n <thead class=\"bg-light-inactive/50 dark:bg-dark-inactive/50 rounded-tl-lg rounded-tr-lg\">\n <tr>\n @for (column of columns; track column.key) {\n @if (!column.hidden || !(column.hidden | async)) {\n <th scope=\"col\"\n class=\"{{ column.align || '' }} py-3.5 pl-4 pr-3 text-sm font-semibold text-light-on-background dark:text-dark-on-background whitespace-nowrap\">\n <span class=\"group inline-flex\">\n {{ column.label }}\n @if (column.sortable) {\n @switch (getSortDirection(column.sort?.field || column.key) | async)\n {\n @case('desc') {\n <span (click)=\"onUpdateSort(column.sort?.field || column.key, 'asc')\"\n class=\"ml-2 flex-none rounded text-light-on-inactive dark:text-dark-on-inactive\">\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </span>\n }\n @case('asc') {\n <span (click)=\"onUpdateSort(column.sort?.field || column.key, 'desc')\"\n class=\"ml-2 flex-none rounded text-light-on-inactive dark:text-dark-on-inactive\">\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M14.78 11.78a.75.75 0 0 1-1.06 0L10 8.06l-3.72 3.72a.75.75 0 1 1-1.06-1.06l4.25-4.25a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </span>\n }\n @default {\n <span (click)=\"onUpdateSort(column.sort?.field || column.key, 'asc')\"\n class=\"invisible ml-2 flex-none rounded text-light-on-inactive dark:text-dark-on-inactive group-hover:visible group-focus:visible\">\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M14.78 11.78a.75.75 0 0 1-1.06 0L10 8.06l-3.72 3.72a.75.75 0 1 1-1.06-1.06l4.25-4.25a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </span>\n }\n }\n }\n </span>\n </th>\n }\n }\n </tr>\n </thead>\n }\n <tbody class=\"overflow-scroll\">\n @for (row of rows | async; track row['id']; let lastRow = $last) {\n <tr class=\"border-t border-light-inactive dark:border-dark-inactive {{ row.bgColor || '' }}\"\n (click)=\"onRowClicked(row)\"\n (dblclick)=\"onRowDblClicked(row)\"\n [ngClass]=\"{'rounded-b-lg': lastRow && (lastEvaluatedKey | async) === null, 'even:bg-light-alternative dark:even:bg-dark-alternative odd:bg-light-background dark:odd:bg-dark-background': !row.bgColor }\"\n [ngStyle]=\"{ 'background-color': row.bgColor || '' }\">\n @for (column of columns; track column.key) {\n @if (!column.hidden || !(column.hidden | async)) {\n <td class=\"relative overflow-visible {{ column.align || '' }} whitespace-nowrap text-left px-3 text-sm text-light-on-background dark:text-dark-on-background\"\n [ngClass]=\"{\n 'first:rounded-bl-lg last:rounded-br-lg': lastRow && (lastEvaluatedKey | async) === null,\n 'text-wrap': row.isExpanded && column.type !== 'custom',\n 'overflow-x-hidden text-ellipsis' : !row.isExpanded && column.type !== 'custom',\n 'py-2': column.type === 'custom',\n 'py-4': column.type !== 'custom'\n }\">\n @if (column.type === 'custom') {\n @if (column.customRenderTemplate) {\n <ng-container [ngTemplateOutlet]=\"customTemplates[column.customRenderTemplate]\"\n [ngTemplateOutletContext]=\"{ $implicit: row }\"></ng-container>\n }\n }\n @else {\n {{ getColumnValue(row, column) | async }}\n }\n </td>\n }\n }\n </tr>\n\n @if (row.isExpanded && expandedTemplate) {\n <tr>\n <td [attr.colspan]=\"columns.length\">\n </td>\n </tr>\n <tr class=\"even:bg-light-alternative dark:even:bg-dark-alternative odd:bg-light-background dark:odd:bg-dark-background\">\n <td [attr.colspan]=\"columns.length\"\n class=\"whitespace-nowrap px-3 pb-4 text-sm text-center\">\n <ng-container [ngTemplateOutlet]=\"expandedTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: row }\"></ng-container>\n </td>\n </tr>\n }\n\n @if (lastRow && (lastEvaluatedKey | async) && !(isLoading | async) && useLoadMore) {\n <tr #loadMoreRow class=\"border-t border-light-inactive dark:border-dark-inactive bg-light-background dark:bg-dark-background\">\n <td [attr.colspan]=\"columns.length\"\n class=\"whitespace-nowrap px-3 py-4 text-sm text-light-on-background dark:text-dark-on-background text-center h-16 bg-light-background dark:bg-dark-background rounded-bl-lg rounded-br-lg\">\n <div (click)=\"onLoadMoreClicked()\"\n class=\"cursor-pointer\">\n {{ 'haloduck.ui.table.Load More...' | transloco }}\n </div>\n </td>\n </tr>\n }\n\n } @empty {\n @if(!(isLoading | async)) {\n <tr>\n <td [attr.colspan]=\"columns.length\"\n class=\"whitespace-nowrap px-3 py-4 text-sm text-light-on-background dark:text-dark-on-background text-center h-16 bg-light-background dark:bg-dark-background rounded-bl-lg rounded-br-lg\">\n {{ 'haloduck.ui.table.No data available.' | transloco }}\n </td>\n </tr>\n }\n }\n\n @if(isLoading | async) {\n @for ( i of [0,1,2,3,4]; track i; let lastRow = $last)\n {\n <tr class=\"bg-light-background dark:bg-dark-background border-t border-light-inactive/50 dark:border-dark-inactive/50\">\n @for (column of columns; track column.key) {\n <td class=\"whitespace-nowrap py-4 pl-4 pr-3 text-sm\"\n [ngClass]=\"{'first:rounded-bl-lg last:rounded-br-lg': lastRow}\">\n <div class=\"h-4 bg-light-inactive/50 dark:bg-dark-inactive/50 rounded-md animate-pulse\"></div>\n </td>\n }\n </tr>\n }\n }\n </tbody>\n </table>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$2.TranslocoPipe, name: "transloco" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2634
|
+
}
|
|
2635
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: TableComponent, decorators: [{
|
|
2636
|
+
type: Component,
|
|
2637
|
+
args: [{ selector: 'haloduck-table', imports: [CommonModule, TranslocoModule], providers: [provideTranslocoScope('haloduck')], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"w-full h-full shadow border border-light-inactive dark:border-dark-inactive rounded-lg overflow-auto\">\n <table class=\"w-full h-full\"\n [ngClass]=\"{\n 'table-fixed': tableLayout === 'fixed',\n 'table-auto': tableLayout === 'auto',\n }\">\n <colgroup>\n @for (column of columns; track column.key) {\n @if (!column.hidden || !(column.hidden | async)) {\n <col class=\"{{ column.width || '' }}\" />\n }\n }\n </colgroup>\n @if (showHeader) {\n <thead class=\"bg-light-inactive/50 dark:bg-dark-inactive/50 rounded-tl-lg rounded-tr-lg\">\n <tr>\n @for (column of columns; track column.key) {\n @if (!column.hidden || !(column.hidden | async)) {\n <th scope=\"col\"\n class=\"{{ column.align || '' }} py-3.5 pl-4 pr-3 text-sm font-semibold text-light-on-background dark:text-dark-on-background whitespace-nowrap\">\n <span class=\"group inline-flex\">\n {{ column.label }}\n @if (column.sortable) {\n @switch (getSortDirection(column.sort?.field || column.key) | async)\n {\n @case('desc') {\n <span (click)=\"onUpdateSort(column.sort?.field || column.key, 'asc')\"\n class=\"ml-2 flex-none rounded text-light-on-inactive dark:text-dark-on-inactive\">\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </span>\n }\n @case('asc') {\n <span (click)=\"onUpdateSort(column.sort?.field || column.key, 'desc')\"\n class=\"ml-2 flex-none rounded text-light-on-inactive dark:text-dark-on-inactive\">\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M14.78 11.78a.75.75 0 0 1-1.06 0L10 8.06l-3.72 3.72a.75.75 0 1 1-1.06-1.06l4.25-4.25a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </span>\n }\n @default {\n <span (click)=\"onUpdateSort(column.sort?.field || column.key, 'asc')\"\n class=\"invisible ml-2 flex-none rounded text-light-on-inactive dark:text-dark-on-inactive group-hover:visible group-focus:visible\">\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M14.78 11.78a.75.75 0 0 1-1.06 0L10 8.06l-3.72 3.72a.75.75 0 1 1-1.06-1.06l4.25-4.25a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </span>\n }\n }\n }\n </span>\n </th>\n }\n }\n </tr>\n </thead>\n }\n <tbody class=\"overflow-scroll\">\n @for (row of rows | async; track row['id']; let lastRow = $last) {\n <tr class=\"border-t border-light-inactive dark:border-dark-inactive {{ row.bgColor || '' }}\"\n (click)=\"onRowClicked(row)\"\n (dblclick)=\"onRowDblClicked(row)\"\n [ngClass]=\"{'rounded-b-lg': lastRow && (lastEvaluatedKey | async) === null, 'even:bg-light-alternative dark:even:bg-dark-alternative odd:bg-light-background dark:odd:bg-dark-background': !row.bgColor }\"\n [ngStyle]=\"{ 'background-color': row.bgColor || '' }\">\n @for (column of columns; track column.key) {\n @if (!column.hidden || !(column.hidden | async)) {\n <td class=\"relative overflow-visible {{ column.align || '' }} whitespace-nowrap text-left px-3 text-sm text-light-on-background dark:text-dark-on-background\"\n [ngClass]=\"{\n 'first:rounded-bl-lg last:rounded-br-lg': lastRow && (lastEvaluatedKey | async) === null,\n 'text-wrap': row.isExpanded && column.type !== 'custom',\n 'overflow-x-hidden text-ellipsis' : !row.isExpanded && column.type !== 'custom',\n 'py-2': column.type === 'custom',\n 'py-4': column.type !== 'custom'\n }\">\n @if (column.type === 'custom') {\n @if (column.customRenderTemplate) {\n <ng-container [ngTemplateOutlet]=\"customTemplates[column.customRenderTemplate]\"\n [ngTemplateOutletContext]=\"{ $implicit: row }\"></ng-container>\n }\n }\n @else {\n {{ getColumnValue(row, column) | async }}\n }\n </td>\n }\n }\n </tr>\n\n @if (row.isExpanded && expandedTemplate) {\n <tr>\n <td [attr.colspan]=\"columns.length\">\n </td>\n </tr>\n <tr class=\"even:bg-light-alternative dark:even:bg-dark-alternative odd:bg-light-background dark:odd:bg-dark-background\">\n <td [attr.colspan]=\"columns.length\"\n class=\"whitespace-nowrap px-3 pb-4 text-sm text-center\">\n <ng-container [ngTemplateOutlet]=\"expandedTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: row }\"></ng-container>\n </td>\n </tr>\n }\n\n @if (lastRow && (lastEvaluatedKey | async) && !(isLoading | async) && useLoadMore) {\n <tr #loadMoreRow class=\"border-t border-light-inactive dark:border-dark-inactive bg-light-background dark:bg-dark-background\">\n <td [attr.colspan]=\"columns.length\"\n class=\"whitespace-nowrap px-3 py-4 text-sm text-light-on-background dark:text-dark-on-background text-center h-16 bg-light-background dark:bg-dark-background rounded-bl-lg rounded-br-lg\">\n <div (click)=\"onLoadMoreClicked()\"\n class=\"cursor-pointer\">\n {{ 'haloduck.ui.table.Load More...' | transloco }}\n </div>\n </td>\n </tr>\n }\n\n } @empty {\n @if(!(isLoading | async)) {\n <tr>\n <td [attr.colspan]=\"columns.length\"\n class=\"whitespace-nowrap px-3 py-4 text-sm text-light-on-background dark:text-dark-on-background text-center h-16 bg-light-background dark:bg-dark-background rounded-bl-lg rounded-br-lg\">\n {{ 'haloduck.ui.table.No data available.' | transloco }}\n </td>\n </tr>\n }\n }\n\n @if(isLoading | async) {\n @for ( i of [0,1,2,3,4]; track i; let lastRow = $last)\n {\n <tr class=\"bg-light-background dark:bg-dark-background border-t border-light-inactive/50 dark:border-dark-inactive/50\">\n @for (column of columns; track column.key) {\n <td class=\"whitespace-nowrap py-4 pl-4 pr-3 text-sm\"\n [ngClass]=\"{'first:rounded-bl-lg last:rounded-br-lg': lastRow}\">\n <div class=\"h-4 bg-light-inactive/50 dark:bg-dark-inactive/50 rounded-md animate-pulse\"></div>\n </td>\n }\n </tr>\n }\n }\n </tbody>\n </table>\n</div>\n" }]
|
|
2638
|
+
}], ctorParameters: () => [], propDecorators: { tableLayout: [{
|
|
2639
|
+
type: Input
|
|
2640
|
+
}], showHeader: [{
|
|
2641
|
+
type: Input
|
|
2642
|
+
}], useLoadMore: [{
|
|
2643
|
+
type: Input
|
|
2644
|
+
}], columns: [{
|
|
2645
|
+
type: Input
|
|
2646
|
+
}], rows: [{
|
|
2647
|
+
type: Input
|
|
2648
|
+
}], isLoading: [{
|
|
2649
|
+
type: Input
|
|
2650
|
+
}], isPaging: [{
|
|
2651
|
+
type: Input
|
|
2652
|
+
}], sort: [{
|
|
2653
|
+
type: Input
|
|
2654
|
+
}], expandedTemplate: [{
|
|
2655
|
+
type: Input
|
|
2656
|
+
}], customTemplates: [{
|
|
2657
|
+
type: Input
|
|
2658
|
+
}], onSortChange: [{
|
|
2659
|
+
type: Output
|
|
2660
|
+
}], lastEvaluatedKey: [{
|
|
2661
|
+
type: Input
|
|
2662
|
+
}], onLoadMore: [{
|
|
2663
|
+
type: Output
|
|
2664
|
+
}], onRowClick: [{
|
|
2665
|
+
type: Output
|
|
2666
|
+
}], onRowDblClick: [{
|
|
2667
|
+
type: Output
|
|
2668
|
+
}] } });
|
|
2669
|
+
|
|
2670
|
+
class ToggleComponent {
|
|
2671
|
+
layout = 'vertical';
|
|
2672
|
+
value = false;
|
|
2673
|
+
disabled = false;
|
|
2674
|
+
nullable = false;
|
|
2675
|
+
toggled = new EventEmitter();
|
|
2676
|
+
label;
|
|
2677
|
+
onChange = (value) => { };
|
|
2678
|
+
onTouched = () => { };
|
|
2679
|
+
writeValue(value) {
|
|
2680
|
+
if (!this.nullable && (value === null || value === undefined)) {
|
|
2681
|
+
value = false;
|
|
2682
|
+
}
|
|
2683
|
+
this.value = value;
|
|
2684
|
+
}
|
|
2685
|
+
registerOnChange(fn) {
|
|
2686
|
+
this.onChange = fn;
|
|
2687
|
+
}
|
|
2688
|
+
registerOnTouched(fn) {
|
|
2689
|
+
this.onTouched = fn;
|
|
2690
|
+
}
|
|
2691
|
+
setDisabledState(isDisabled) {
|
|
2692
|
+
this.disabled = isDisabled;
|
|
2693
|
+
}
|
|
2694
|
+
onToggle() {
|
|
2695
|
+
if (this.nullable) {
|
|
2696
|
+
this.value = this.value ? false : this.value === null ? true : null;
|
|
2697
|
+
}
|
|
2698
|
+
else {
|
|
2699
|
+
this.value = !this.value;
|
|
2700
|
+
}
|
|
2701
|
+
this.onChange(this.value);
|
|
2702
|
+
this.onTouched();
|
|
2703
|
+
this.toggled.emit(this.value);
|
|
2704
|
+
}
|
|
2705
|
+
ngAfterViewInit() {
|
|
2706
|
+
// hide label if no content.
|
|
2707
|
+
if (!this.label.nativeElement.innerText.trim()) {
|
|
2708
|
+
this.label.nativeElement.style.display = 'none';
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ToggleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2712
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: ToggleComponent, isStandalone: true, selector: "haloduck-toggle", inputs: { layout: "layout", value: "value", disabled: "disabled", nullable: "nullable" }, outputs: { toggled: "toggled" }, providers: [
|
|
2713
|
+
{
|
|
2714
|
+
provide: NG_VALUE_ACCESSOR,
|
|
2715
|
+
useExisting: forwardRef(() => ToggleComponent),
|
|
2716
|
+
multi: true,
|
|
2717
|
+
},
|
|
2718
|
+
], viewQueries: [{ propertyName: "label", first: true, predicate: ["label"], descendants: true }], ngImport: i0, template: "<div class=\"flex gap-2\"\n [ngClass]=\"{'items-center justify-start': 'horizontal' === layout, 'flex-col items-start justify-center ': 'vertical' === layout}\">\n <label #label class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control text-left\">\n <ng-content></ng-content>\n </label>\n <button class=\"w-12 p-1 rounded-l-full rounded-r-full outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive focus:outline-2 focus:outline-light-primary dark:foucs:outline-dark-primary bg-light-control dark:bg-dark-control disabled:bg-light-control/60 dark:disabled:bg-dark-control/60 flex hover:cursor-pointer\"\n (click)=\"onToggle()\"\n [disabled]=\"disabled\"\n [ngClass]=\"{'justify-end': true === value, 'justify-center': null === value, 'justify-start': false === value}\">\n <div class=\"shrink-0 h-[1.5rem] rounded-l-full rounded-r-full\"\n [ngClass]=\"{'bg-light-primary dark:bg-dark-primary disabled:bg-light-primary/60 dark:disabled:bg-dark-primary/60': true === value, 'bg-light-inactive dark:bg-dark-inactive disabled:bg-light-inactive/60 dark:disabled:bg-dark-inactive/60': false === value,\n 'w-[1.5rem]': true === value || false === value, 'bg-light-primary-light dark:bg-dark-primary-light disabled:bg-light-primary-light/60 dark:disabled:bg-dark-primary-light/60 w-[2.4rem]': null === value}\">\n </div>\n </button>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
|
|
2719
|
+
}
|
|
2720
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ToggleComponent, decorators: [{
|
|
2721
|
+
type: Component,
|
|
2722
|
+
args: [{ selector: 'haloduck-toggle', imports: [CommonModule], providers: [
|
|
2723
|
+
{
|
|
2724
|
+
provide: NG_VALUE_ACCESSOR,
|
|
2725
|
+
useExisting: forwardRef(() => ToggleComponent),
|
|
2726
|
+
multi: true,
|
|
2727
|
+
},
|
|
2728
|
+
], template: "<div class=\"flex gap-2\"\n [ngClass]=\"{'items-center justify-start': 'horizontal' === layout, 'flex-col items-start justify-center ': 'vertical' === layout}\">\n <label #label class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control text-left\">\n <ng-content></ng-content>\n </label>\n <button class=\"w-12 p-1 rounded-l-full rounded-r-full outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive focus:outline-2 focus:outline-light-primary dark:foucs:outline-dark-primary bg-light-control dark:bg-dark-control disabled:bg-light-control/60 dark:disabled:bg-dark-control/60 flex hover:cursor-pointer\"\n (click)=\"onToggle()\"\n [disabled]=\"disabled\"\n [ngClass]=\"{'justify-end': true === value, 'justify-center': null === value, 'justify-start': false === value}\">\n <div class=\"shrink-0 h-[1.5rem] rounded-l-full rounded-r-full\"\n [ngClass]=\"{'bg-light-primary dark:bg-dark-primary disabled:bg-light-primary/60 dark:disabled:bg-dark-primary/60': true === value, 'bg-light-inactive dark:bg-dark-inactive disabled:bg-light-inactive/60 dark:disabled:bg-dark-inactive/60': false === value,\n 'w-[1.5rem]': true === value || false === value, 'bg-light-primary-light dark:bg-dark-primary-light disabled:bg-light-primary-light/60 dark:disabled:bg-dark-primary-light/60 w-[2.4rem]': null === value}\">\n </div>\n </button>\n</div>\n" }]
|
|
2729
|
+
}], propDecorators: { layout: [{
|
|
2730
|
+
type: Input
|
|
2731
|
+
}], value: [{
|
|
2732
|
+
type: Input
|
|
2733
|
+
}], disabled: [{
|
|
2734
|
+
type: Input
|
|
2735
|
+
}], nullable: [{
|
|
2736
|
+
type: Input
|
|
2737
|
+
}], toggled: [{
|
|
2738
|
+
type: Output
|
|
2739
|
+
}], label: [{
|
|
2740
|
+
type: ViewChild,
|
|
2741
|
+
args: ['label']
|
|
2742
|
+
}] } });
|
|
2743
|
+
|
|
2744
|
+
class BreadcrumbService {
|
|
2745
|
+
router;
|
|
2746
|
+
coreService = inject(CoreService);
|
|
2747
|
+
breadcrumbs = new BehaviorSubject([]);
|
|
2748
|
+
breadcrumbs$ = this.breadcrumbs.asObservable();
|
|
2749
|
+
titleService = inject(Title);
|
|
2750
|
+
constructor(router) {
|
|
2751
|
+
this.router = router;
|
|
2752
|
+
this.router.events
|
|
2753
|
+
.pipe(filter((event) => event instanceof NavigationEnd))
|
|
2754
|
+
.subscribe(() => {
|
|
2755
|
+
const root = this.router.routerState.snapshot.root;
|
|
2756
|
+
const breadcrumbs = this.createBreadcrumbs(root);
|
|
2757
|
+
this.breadcrumbs.next(breadcrumbs);
|
|
2758
|
+
setTimeout(() => {
|
|
2759
|
+
const titlePrefix = this.coreService.getStage() === 'prod'
|
|
2760
|
+
? ''
|
|
2761
|
+
: `[${this.coreService.getStage()}] ` +
|
|
2762
|
+
this.coreService.getAppName() +
|
|
2763
|
+
'::';
|
|
2764
|
+
const title = titlePrefix +
|
|
2765
|
+
breadcrumbs.reduce((acc, breadcrumb) => {
|
|
2766
|
+
return acc ? `${acc}>${breadcrumb.label}` : breadcrumb.label;
|
|
2767
|
+
}, '');
|
|
2768
|
+
this.titleService.setTitle(title || 'Default Title');
|
|
2769
|
+
});
|
|
2770
|
+
});
|
|
2771
|
+
}
|
|
2772
|
+
createBreadcrumbs(route, url = '', breadcrumbs = []) {
|
|
2773
|
+
const children = route.children;
|
|
2774
|
+
if (children.length === 0) {
|
|
2775
|
+
return breadcrumbs;
|
|
2776
|
+
}
|
|
2777
|
+
for (const child of children) {
|
|
2778
|
+
const routeURL = child.url
|
|
2779
|
+
.map((segment) => segment.path)
|
|
2780
|
+
.join('/');
|
|
2781
|
+
if (routeURL !== '') {
|
|
2782
|
+
url += `/${routeURL}`;
|
|
2783
|
+
}
|
|
2784
|
+
if (child.data['breadcrumb']) {
|
|
2785
|
+
breadcrumbs.push({ label: child.data['breadcrumb'], url });
|
|
2786
|
+
}
|
|
2787
|
+
return this.createBreadcrumbs(child, url, breadcrumbs);
|
|
2788
|
+
}
|
|
2789
|
+
return breadcrumbs;
|
|
2790
|
+
}
|
|
2791
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: BreadcrumbService, deps: [{ token: i1$5.Router }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2792
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: BreadcrumbService, providedIn: 'root' });
|
|
2793
|
+
}
|
|
2794
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: BreadcrumbService, decorators: [{
|
|
2795
|
+
type: Injectable,
|
|
2796
|
+
args: [{ providedIn: 'root' }]
|
|
2797
|
+
}], ctorParameters: () => [{ type: i1$5.Router }] });
|
|
2798
|
+
|
|
2799
|
+
class BreadcrumbComponent {
|
|
2800
|
+
breadcrumbService = inject(BreadcrumbService);
|
|
2801
|
+
listBreadcrumb = [];
|
|
2802
|
+
ngOnInit() {
|
|
2803
|
+
this.breadcrumbService.breadcrumbs$.subscribe((listBreadcrumb) => {
|
|
2804
|
+
this.listBreadcrumb = listBreadcrumb;
|
|
2805
|
+
});
|
|
2806
|
+
}
|
|
2807
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: BreadcrumbComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2808
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: BreadcrumbComponent, isStandalone: true, selector: "haloduck-breadcrumb", ngImport: i0, template: "<nav class=\"flex border-b border-light-inactive dark:border-dark-inactive bg-light-background dark:bg-dark-background\"\n aria-label=\"Breadcrumb\">\n <ol role=\"list\"\n class=\"flex w-full max-w-(--breakpoint-xl) space-x-4 px-4 sm:px-6 lg:px-8\">\n <li class=\"flex\">\n <div class=\"flex items-center\">\n <a routerLink=\"/\"\n class=\"text-light-on-background dark:text-dark-on-background hover:scale-105\">\n <svg class=\"size-5 shrink-0\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M9.293 2.293a1 1 0 0 1 1.414 0l7 7A1 1 0 0 1 17 11h-1v6a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-3a1 1 0 0 0-1-1H9a1 1 0 0 0-1 1v3a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-6H3a1 1 0 0 1-.707-1.707l7-7Z\"\n clip-rule=\"evenodd\" />\n </svg>\n <span class=\"sr-only\">Home</span>\n </a>\n </div>\n </li>\n @for (breadcrumb of listBreadcrumb; track breadcrumb.label; let last = $last) {\n <li class=\"flex\">\n <div class=\"flex items-center\">\n <svg class=\"h-full w-6 shrink-0 text-light-inactive dark:text-dark-inactive\"\n viewBox=\"0 0 24 44\"\n preserveAspectRatio=\"none\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path d=\"M.293 0l22 22-22 22h1.414l22-22-22-22H.293z\" />\n </svg>\n @if (!last) {\n <a href=\"javascript:void(0)\"\n [routerLink]=\"breadcrumb.url\"\n class=\"ml-4 text-sm font-medium hover:font-bold text-light-on-background dark:text-dark-on-background\">{{ breadcrumb.label | transloco }}</a>\n } @else {\n <span class=\"ml-4 text-sm font-medium text-light-on-background dark:text-dark-on-background\">{{ breadcrumb.label | transloco }}</span>\n }\n </div>\n </li>\n }\n </ol>\n</nav>\n", styles: [""], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i1$2.TranslocoPipe, name: "transloco" }] });
|
|
2809
|
+
}
|
|
2810
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: BreadcrumbComponent, decorators: [{
|
|
2811
|
+
type: Component,
|
|
2812
|
+
args: [{ selector: 'haloduck-breadcrumb', imports: [RouterLink, TranslocoModule], template: "<nav class=\"flex border-b border-light-inactive dark:border-dark-inactive bg-light-background dark:bg-dark-background\"\n aria-label=\"Breadcrumb\">\n <ol role=\"list\"\n class=\"flex w-full max-w-(--breakpoint-xl) space-x-4 px-4 sm:px-6 lg:px-8\">\n <li class=\"flex\">\n <div class=\"flex items-center\">\n <a routerLink=\"/\"\n class=\"text-light-on-background dark:text-dark-on-background hover:scale-105\">\n <svg class=\"size-5 shrink-0\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M9.293 2.293a1 1 0 0 1 1.414 0l7 7A1 1 0 0 1 17 11h-1v6a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-3a1 1 0 0 0-1-1H9a1 1 0 0 0-1 1v3a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-6H3a1 1 0 0 1-.707-1.707l7-7Z\"\n clip-rule=\"evenodd\" />\n </svg>\n <span class=\"sr-only\">Home</span>\n </a>\n </div>\n </li>\n @for (breadcrumb of listBreadcrumb; track breadcrumb.label; let last = $last) {\n <li class=\"flex\">\n <div class=\"flex items-center\">\n <svg class=\"h-full w-6 shrink-0 text-light-inactive dark:text-dark-inactive\"\n viewBox=\"0 0 24 44\"\n preserveAspectRatio=\"none\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path d=\"M.293 0l22 22-22 22h1.414l22-22-22-22H.293z\" />\n </svg>\n @if (!last) {\n <a href=\"javascript:void(0)\"\n [routerLink]=\"breadcrumb.url\"\n class=\"ml-4 text-sm font-medium hover:font-bold text-light-on-background dark:text-dark-on-background\">{{ breadcrumb.label | transloco }}</a>\n } @else {\n <span class=\"ml-4 text-sm font-medium text-light-on-background dark:text-dark-on-background\">{{ breadcrumb.label | transloco }}</span>\n }\n </div>\n </li>\n }\n </ol>\n</nav>\n" }]
|
|
2813
|
+
}] });
|
|
2814
|
+
|
|
2815
|
+
class NotificationComponent {
|
|
2816
|
+
notificationService = inject(NotificationService);
|
|
2817
|
+
listNotification$ = this.notificationService.getListNotification();
|
|
2818
|
+
removeNotification(id) {
|
|
2819
|
+
this.notificationService.removeNotificationById(id);
|
|
2820
|
+
}
|
|
2821
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: NotificationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2822
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: NotificationComponent, isStandalone: true, selector: "haloduck-notification", ngImport: i0, template: "@for (notification of listNotification$ |async; track notification.id) {\n<div class=\"relative rounded-lg m-4 px-8 py-4 bg-light-primary dark:bg-dark-primary text-light-on-primary dark:text-dark-on-primary animate-fadeIn\"\n [ngClass]=\"{ 'bg-light-primary-dark dark:bg-dark-primary-dark text-light-on-primary-dark dark:text-dark-on-primary-dark': notification.type === 'info',\n 'bg-light-primary dark:bg-dark-primary text-light-on-primary dark:text-dark-on-primary': notification.type === 'success',\n 'bg-light-danger dark:bg-dark-danger text-light-on-danger dark:text-dark-on-danger': notification.type === 'error',\n 'bg-light-secondary dark:bg-dark-secondary text-light-on-secondary dark:text-dark-on-secondary': notification.type === 'warning' }\">\n {{notification.message}}\n <div class=\"absolute top-0 right-0\">\n <button type=\"button\"\n (click)=\"removeNotification(notification.id)\">\n <span class=\"sr-only\">Close</span>\n <svg class=\"size-6 text-white\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke-width=\"1.5\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"M6 18 18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n</div>\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }] });
|
|
2823
|
+
}
|
|
2824
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: NotificationComponent, decorators: [{
|
|
2825
|
+
type: Component,
|
|
2826
|
+
args: [{ selector: 'haloduck-notification', standalone: true, imports: [CommonModule], template: "@for (notification of listNotification$ |async; track notification.id) {\n<div class=\"relative rounded-lg m-4 px-8 py-4 bg-light-primary dark:bg-dark-primary text-light-on-primary dark:text-dark-on-primary animate-fadeIn\"\n [ngClass]=\"{ 'bg-light-primary-dark dark:bg-dark-primary-dark text-light-on-primary-dark dark:text-dark-on-primary-dark': notification.type === 'info',\n 'bg-light-primary dark:bg-dark-primary text-light-on-primary dark:text-dark-on-primary': notification.type === 'success',\n 'bg-light-danger dark:bg-dark-danger text-light-on-danger dark:text-dark-on-danger': notification.type === 'error',\n 'bg-light-secondary dark:bg-dark-secondary text-light-on-secondary dark:text-dark-on-secondary': notification.type === 'warning' }\">\n {{notification.message}}\n <div class=\"absolute top-0 right-0\">\n <button type=\"button\"\n (click)=\"removeNotification(notification.id)\">\n <span class=\"sr-only\">Close</span>\n <svg class=\"size-6 text-white\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke-width=\"1.5\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"M6 18 18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n</div>\n}\n" }]
|
|
2827
|
+
}] });
|
|
2828
|
+
|
|
2829
|
+
class PictureNameComponent {
|
|
2830
|
+
name = '';
|
|
2831
|
+
pictureKey;
|
|
2832
|
+
cdnUrl = '';
|
|
2833
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: PictureNameComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2834
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: PictureNameComponent, isStandalone: true, selector: "haloduck-picture-name", inputs: { name: "name", pictureKey: "pictureKey", cdnUrl: "cdnUrl" }, ngImport: i0, template: "<div class=\"flex items-center justify-start gap-2\">\n @if(pictureKey) {\n <img src=\"{{cdnUrl}}/{{pictureKey}}\"\n alt=\"\uC0AC\uC9C4\"\n class=\"w-8 h-8 rounded-full\" />\n } @else {\n <div class=\"w-8 h-8 bg-light-inactive dark:bg-dark-inactive rounded-full\"></div>\n }\n {{ name }}\n @if (name) {\n \n <!-- TODO -->\n }\n</div>\n", styles: [""] });
|
|
2835
|
+
}
|
|
2836
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: PictureNameComponent, decorators: [{
|
|
2837
|
+
type: Component,
|
|
2838
|
+
args: [{ selector: 'haloduck-picture-name', imports: [], template: "<div class=\"flex items-center justify-start gap-2\">\n @if(pictureKey) {\n <img src=\"{{cdnUrl}}/{{pictureKey}}\"\n alt=\"\uC0AC\uC9C4\"\n class=\"w-8 h-8 rounded-full\" />\n } @else {\n <div class=\"w-8 h-8 bg-light-inactive dark:bg-dark-inactive rounded-full\"></div>\n }\n {{ name }}\n @if (name) {\n \n <!-- TODO -->\n }\n</div>\n" }]
|
|
2839
|
+
}], propDecorators: { name: [{
|
|
2840
|
+
type: Input
|
|
2841
|
+
}], pictureKey: [{
|
|
2842
|
+
type: Input
|
|
2843
|
+
}], cdnUrl: [{
|
|
2844
|
+
type: Input
|
|
2845
|
+
}] } });
|
|
2846
|
+
|
|
2847
|
+
const provideHaloduckTransloco = () => provideTranslocoScope({
|
|
2848
|
+
scope: 'haloduck',
|
|
2849
|
+
alias: 'haloduck',
|
|
2850
|
+
});
|
|
2851
|
+
|
|
2852
|
+
/**
|
|
2853
|
+
* Generated bundle index. Do not edit.
|
|
2854
|
+
*/
|
|
2855
|
+
|
|
2856
|
+
export { AuthenticateComponent, BreadcrumbComponent, ButtonComponent, CalendarComponent, ConfirmDialogService, CopyButtonComponent, DatePickerComponent, DateRangeComponent, DialogService, DrawCanvasComponent, ERROR_NOT_ACCEPTABLE_FILE_TYPE, ERROR_OVER_COUNT, ERROR_OVER_SIZE, ERROR_UPLOAD, FileUploaderComponent, FlipComponent, ImageUploaderComponent, ImageViewerComponent, InputComponent, LanguageSelectorComponent, MapToAddressComponent, NotificationComponent, NotificationService, PictureNameComponent, SelectComponent, SelectDropdownComponent, SideMenuComponent, SideMenuItemComponent, StlViewerComponent, TableComponent, ToggleComponent, dateToString, provideHaloduckTransloco };
|
|
2857
|
+
//# sourceMappingURL=haloduck-ui.mjs.map
|