@meshmakers/octo-ui 3.3.920 → 3.3.930
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/README.md +52 -0
- package/fesm2022/meshmakers-octo-ui-branding-settings.mjs +324 -0
- package/fesm2022/meshmakers-octo-ui-branding-settings.mjs.map +1 -0
- package/fesm2022/meshmakers-octo-ui-branding.mjs +1026 -0
- package/fesm2022/meshmakers-octo-ui-branding.mjs.map +1 -0
- package/fesm2022/meshmakers-octo-ui.mjs.map +1 -1
- package/package.json +9 -1
- package/types/meshmakers-octo-ui-branding-settings.d.ts +107 -0
- package/types/meshmakers-octo-ui-branding.d.ts +285 -0
package/README.md
CHANGED
|
@@ -155,6 +155,58 @@ export class CustomerDataSourceDirective extends OctoGraphQlDataSource<CustomerD
|
|
|
155
155
|
| `OctoGraphQlHierarchyDataSource` | Base class for tree/hierarchy data sources |
|
|
156
156
|
| `FetchResultTyped` | Typed result wrapper with items and totalCount |
|
|
157
157
|
|
|
158
|
+
## Branding
|
|
159
|
+
|
|
160
|
+
Per-tenant branding (logo, title, palette) with light/dark mode and a configurable
|
|
161
|
+
Settings page. See [`src/lib/branding/BRANDING_USAGE.md`](./src/lib/branding/BRANDING_USAGE.md).
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
// app.config.ts
|
|
165
|
+
import { provideOctoBranding } from '@meshmakers/octo-ui';
|
|
166
|
+
|
|
167
|
+
providers: [
|
|
168
|
+
provideOctoBranding({
|
|
169
|
+
defaults: { appName: 'MyApp', appTitle: 'MyApp' },
|
|
170
|
+
fallbackAssets: { headerLogo: '/logo.svg', favicon: '/favicon.ico' },
|
|
171
|
+
}),
|
|
172
|
+
];
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Wire `/settings` route:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
// app.routes.ts
|
|
179
|
+
import { BRANDING_ROUTES } from '@meshmakers/octo-ui';
|
|
180
|
+
|
|
181
|
+
{ path: 'settings', canActivate: [adminGuard], children: BRANDING_ROUTES }
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Drop the standalone components into your shell wherever they belong, and
|
|
185
|
+
bind your header/footer chrome to the CSS vars the library writes:
|
|
186
|
+
|
|
187
|
+
```html
|
|
188
|
+
<header class="my-header">
|
|
189
|
+
<mm-theme-switcher />
|
|
190
|
+
<!-- Render the logo inline; inject BrandingDataSource and bind .branding().headerLogoUrl -->
|
|
191
|
+
<img [src]="logoUrl()" alt="" class="my-header-logo" />
|
|
192
|
+
</header>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
```scss
|
|
196
|
+
.my-header {
|
|
197
|
+
background: linear-gradient(
|
|
198
|
+
to right,
|
|
199
|
+
var(--app-header-gradient-start),
|
|
200
|
+
var(--app-header-gradient-end)
|
|
201
|
+
);
|
|
202
|
+
color: var(--app-header-text);
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
See [`src/lib/branding/BRANDING_USAGE.md`](./src/lib/branding/BRANDING_USAGE.md)
|
|
207
|
+
for the full list of CSS variables the library updates and the host-app
|
|
208
|
+
contract for the surface ladder.
|
|
209
|
+
|
|
158
210
|
## Build
|
|
159
211
|
|
|
160
212
|
```bash
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, inject, signal, Component } from '@angular/core';
|
|
3
|
+
import * as i5 from '@angular/forms';
|
|
4
|
+
import { FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
|
5
|
+
import { BrandingDataSource, OCTO_BRANDING_DEFAULTS } from '@meshmakers/octo-ui/branding';
|
|
6
|
+
import { MessageService } from '@meshmakers/shared-services';
|
|
7
|
+
import * as i3 from '@progress/kendo-angular-buttons';
|
|
8
|
+
import { ButtonsModule } from '@progress/kendo-angular-buttons';
|
|
9
|
+
import * as i1 from '@progress/kendo-angular-inputs';
|
|
10
|
+
import { InputsModule } from '@progress/kendo-angular-inputs';
|
|
11
|
+
import * as i2 from '@progress/kendo-angular-label';
|
|
12
|
+
import { LabelModule } from '@progress/kendo-angular-label';
|
|
13
|
+
import * as i4 from '@progress/kendo-angular-layout';
|
|
14
|
+
import { LayoutModule } from '@progress/kendo-angular-layout';
|
|
15
|
+
import { saveIcon, undoIcon, xIcon } from '@progress/kendo-svg-icons';
|
|
16
|
+
|
|
17
|
+
const EMPTY_LOGO_SLOT = {
|
|
18
|
+
file: null,
|
|
19
|
+
preview: null,
|
|
20
|
+
cleared: false,
|
|
21
|
+
};
|
|
22
|
+
class SettingsPageComponent {
|
|
23
|
+
messages = input.required(...(ngDevMode ? [{ debugName: "messages" }] : /* istanbul ignore next */ []));
|
|
24
|
+
fb = inject(FormBuilder);
|
|
25
|
+
branding = inject(BrandingDataSource);
|
|
26
|
+
messageService = inject(MessageService);
|
|
27
|
+
defaults = inject(OCTO_BRANDING_DEFAULTS);
|
|
28
|
+
saveIcon = saveIcon;
|
|
29
|
+
undoIcon = undoIcon;
|
|
30
|
+
xIcon = xIcon;
|
|
31
|
+
logoSlotNames = [
|
|
32
|
+
'header',
|
|
33
|
+
'footer',
|
|
34
|
+
'favicon',
|
|
35
|
+
];
|
|
36
|
+
saving = signal(false, ...(ngDevMode ? [{ debugName: "saving" }] : /* istanbul ignore next */ []));
|
|
37
|
+
settingsForm;
|
|
38
|
+
slotStates = {
|
|
39
|
+
header: signal({ ...EMPTY_LOGO_SLOT }),
|
|
40
|
+
footer: signal({ ...EMPTY_LOGO_SLOT }),
|
|
41
|
+
favicon: signal({ ...EMPTY_LOGO_SLOT }),
|
|
42
|
+
};
|
|
43
|
+
persistedUrls = {
|
|
44
|
+
header: signal(null),
|
|
45
|
+
footer: signal(null),
|
|
46
|
+
favicon: signal(null),
|
|
47
|
+
};
|
|
48
|
+
constructor() {
|
|
49
|
+
const { lightTheme, darkTheme } = this.defaults;
|
|
50
|
+
const dark = darkTheme ?? lightTheme;
|
|
51
|
+
// Seeding color controls with valid hex defaults is required: kendo-colorpicker
|
|
52
|
+
// binds as a ControlValueAccessor and doesn't handle initial empty strings
|
|
53
|
+
// cleanly inside kendo-formfield.
|
|
54
|
+
this.settingsForm = this.fb.group({
|
|
55
|
+
appName: [this.defaults.appName, Validators.required],
|
|
56
|
+
appTitle: [this.defaults.appTitle],
|
|
57
|
+
lightPrimaryColor: [lightTheme.primaryColor, Validators.required],
|
|
58
|
+
lightSecondaryColor: [lightTheme.secondaryColor, Validators.required],
|
|
59
|
+
lightTertiaryColor: [lightTheme.tertiaryColor, Validators.required],
|
|
60
|
+
lightNeutralColor: [lightTheme.neutralColor, Validators.required],
|
|
61
|
+
lightBackgroundColor: [lightTheme.backgroundColor],
|
|
62
|
+
lightHeaderGradientStart: [lightTheme.headerGradient.startColor],
|
|
63
|
+
lightHeaderGradientEnd: [lightTheme.headerGradient.endColor],
|
|
64
|
+
lightFooterGradientStart: [lightTheme.footerGradient.startColor],
|
|
65
|
+
lightFooterGradientEnd: [lightTheme.footerGradient.endColor],
|
|
66
|
+
darkThemeEnabled: [darkTheme !== null],
|
|
67
|
+
darkPrimaryColor: [dark.primaryColor, Validators.required],
|
|
68
|
+
darkSecondaryColor: [dark.secondaryColor, Validators.required],
|
|
69
|
+
darkTertiaryColor: [dark.tertiaryColor, Validators.required],
|
|
70
|
+
darkNeutralColor: [dark.neutralColor, Validators.required],
|
|
71
|
+
darkBackgroundColor: [dark.backgroundColor],
|
|
72
|
+
darkHeaderGradientStart: [dark.headerGradient.startColor],
|
|
73
|
+
darkHeaderGradientEnd: [dark.headerGradient.endColor],
|
|
74
|
+
darkFooterGradientStart: [dark.footerGradient.startColor],
|
|
75
|
+
darkFooterGradientEnd: [dark.footerGradient.endColor],
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
darkThemeEnabled() {
|
|
79
|
+
return this.settingsForm.get('darkThemeEnabled')?.value === true;
|
|
80
|
+
}
|
|
81
|
+
async ngOnInit() {
|
|
82
|
+
try {
|
|
83
|
+
await this.branding.load();
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
console.error('[SettingsPageComponent] Failed to load branding', error);
|
|
87
|
+
this.messageService.showError(this.messages().loadError);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
this.hydrate(this.branding.branding());
|
|
91
|
+
}
|
|
92
|
+
dragOverSlot = signal(null, ...(ngDevMode ? [{ debugName: "dragOverSlot" }] : /* istanbul ignore next */ []));
|
|
93
|
+
isDragOver(slot) {
|
|
94
|
+
return this.dragOverSlot() === slot;
|
|
95
|
+
}
|
|
96
|
+
onLogoSelected(slot, event) {
|
|
97
|
+
const input = event.target;
|
|
98
|
+
if (!(input instanceof HTMLInputElement))
|
|
99
|
+
return;
|
|
100
|
+
const file = input.files?.[0] ?? null;
|
|
101
|
+
this.applyLogoFile(slot, file);
|
|
102
|
+
input.value = '';
|
|
103
|
+
}
|
|
104
|
+
onLogoDragOver(slot, event) {
|
|
105
|
+
event.preventDefault();
|
|
106
|
+
if (event.dataTransfer)
|
|
107
|
+
event.dataTransfer.dropEffect = 'copy';
|
|
108
|
+
this.dragOverSlot.set(slot);
|
|
109
|
+
}
|
|
110
|
+
onLogoDragLeave(slot) {
|
|
111
|
+
if (this.dragOverSlot() === slot)
|
|
112
|
+
this.dragOverSlot.set(null);
|
|
113
|
+
}
|
|
114
|
+
onLogoDropped(slot, event) {
|
|
115
|
+
event.preventDefault();
|
|
116
|
+
this.dragOverSlot.set(null);
|
|
117
|
+
const file = event.dataTransfer?.files?.[0];
|
|
118
|
+
if (!file || !file.type.startsWith('image/'))
|
|
119
|
+
return;
|
|
120
|
+
this.applyLogoFile(slot, file);
|
|
121
|
+
}
|
|
122
|
+
applyLogoFile(slot, file) {
|
|
123
|
+
if (!file)
|
|
124
|
+
return;
|
|
125
|
+
const reader = new FileReader();
|
|
126
|
+
reader.onload = () => {
|
|
127
|
+
const result = reader.result;
|
|
128
|
+
this.slotStates[slot].set({
|
|
129
|
+
file,
|
|
130
|
+
preview: typeof result === 'string' ? result : null,
|
|
131
|
+
cleared: false,
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
reader.readAsDataURL(file);
|
|
135
|
+
}
|
|
136
|
+
clearLogo(slot) {
|
|
137
|
+
this.slotStates[slot].set({ file: null, preview: null, cleared: true });
|
|
138
|
+
this.persistedUrls[slot].set(null);
|
|
139
|
+
}
|
|
140
|
+
previewUrl(slot) {
|
|
141
|
+
return this.slotStates[slot]().preview ?? this.persistedUrls[slot]();
|
|
142
|
+
}
|
|
143
|
+
async onSubmit() {
|
|
144
|
+
if (this.settingsForm.invalid) {
|
|
145
|
+
this.settingsForm.markAllAsTouched();
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
this.saving.set(true);
|
|
149
|
+
try {
|
|
150
|
+
await this.branding.save({
|
|
151
|
+
appName: this.strValue('appName'),
|
|
152
|
+
appTitle: this.strValue('appTitle'),
|
|
153
|
+
headerLogoFile: this.logoFileForSave('header'),
|
|
154
|
+
footerLogoFile: this.logoFileForSave('footer'),
|
|
155
|
+
faviconFile: this.logoFileForSave('favicon'),
|
|
156
|
+
lightTheme: this.paletteFromForm('light'),
|
|
157
|
+
darkTheme: this.darkThemeEnabled() ? this.paletteFromForm('dark') : null,
|
|
158
|
+
});
|
|
159
|
+
this.messageService.showInformation(this.messages().saveSuccess);
|
|
160
|
+
this.hydrate(this.branding.branding());
|
|
161
|
+
this.settingsForm.markAsPristine();
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
console.error('[SettingsPageComponent] Failed to save branding', error);
|
|
165
|
+
this.messageService.showError(this.messages().saveError);
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
this.saving.set(false);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async onResetDefaults() {
|
|
172
|
+
this.saving.set(true);
|
|
173
|
+
try {
|
|
174
|
+
await this.branding.resetToDefaults();
|
|
175
|
+
this.hydrate(this.branding.branding());
|
|
176
|
+
this.settingsForm.markAsPristine();
|
|
177
|
+
this.messageService.showInformation(this.messages().resetSuccess);
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
console.error('[SettingsPageComponent] Failed to reset branding', error);
|
|
181
|
+
this.messageService.showError(this.messages().saveError);
|
|
182
|
+
}
|
|
183
|
+
finally {
|
|
184
|
+
this.saving.set(false);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
hydrate(data) {
|
|
188
|
+
this.persistedUrls.header.set(data.headerLogoUrl);
|
|
189
|
+
this.persistedUrls.footer.set(data.footerLogoUrl);
|
|
190
|
+
this.persistedUrls.favicon.set(data.faviconUrl);
|
|
191
|
+
for (const slot of this.logoSlotNames) {
|
|
192
|
+
this.slotStates[slot].set({ ...EMPTY_LOGO_SLOT });
|
|
193
|
+
}
|
|
194
|
+
const dark = data.darkTheme ?? this.defaults.darkTheme ?? this.defaults.lightTheme;
|
|
195
|
+
this.settingsForm.patchValue({
|
|
196
|
+
appName: data.appName,
|
|
197
|
+
appTitle: data.appTitle,
|
|
198
|
+
darkThemeEnabled: data.darkTheme !== null,
|
|
199
|
+
lightPrimaryColor: data.lightTheme.primaryColor,
|
|
200
|
+
lightSecondaryColor: data.lightTheme.secondaryColor,
|
|
201
|
+
lightTertiaryColor: data.lightTheme.tertiaryColor,
|
|
202
|
+
lightNeutralColor: data.lightTheme.neutralColor,
|
|
203
|
+
lightBackgroundColor: data.lightTheme.backgroundColor,
|
|
204
|
+
lightHeaderGradientStart: data.lightTheme.headerGradient.startColor,
|
|
205
|
+
lightHeaderGradientEnd: data.lightTheme.headerGradient.endColor,
|
|
206
|
+
lightFooterGradientStart: data.lightTheme.footerGradient.startColor,
|
|
207
|
+
lightFooterGradientEnd: data.lightTheme.footerGradient.endColor,
|
|
208
|
+
darkPrimaryColor: dark.primaryColor,
|
|
209
|
+
darkSecondaryColor: dark.secondaryColor,
|
|
210
|
+
darkTertiaryColor: dark.tertiaryColor,
|
|
211
|
+
darkNeutralColor: dark.neutralColor,
|
|
212
|
+
darkBackgroundColor: dark.backgroundColor,
|
|
213
|
+
darkHeaderGradientStart: dark.headerGradient.startColor,
|
|
214
|
+
darkHeaderGradientEnd: dark.headerGradient.endColor,
|
|
215
|
+
darkFooterGradientStart: dark.footerGradient.startColor,
|
|
216
|
+
darkFooterGradientEnd: dark.footerGradient.endColor,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
logoFileForSave(slot) {
|
|
220
|
+
const state = this.slotStates[slot]();
|
|
221
|
+
if (state.file)
|
|
222
|
+
return state.file;
|
|
223
|
+
if (state.cleared)
|
|
224
|
+
return null;
|
|
225
|
+
return undefined;
|
|
226
|
+
}
|
|
227
|
+
strValue(name) {
|
|
228
|
+
const value = this.settingsForm.get(name)?.value;
|
|
229
|
+
return typeof value === 'string' ? value : '';
|
|
230
|
+
}
|
|
231
|
+
paletteFromForm(kind) {
|
|
232
|
+
return {
|
|
233
|
+
primaryColor: this.strValue(`${kind}PrimaryColor`),
|
|
234
|
+
secondaryColor: this.strValue(`${kind}SecondaryColor`),
|
|
235
|
+
tertiaryColor: this.strValue(`${kind}TertiaryColor`),
|
|
236
|
+
neutralColor: this.strValue(`${kind}NeutralColor`),
|
|
237
|
+
backgroundColor: this.strValue(`${kind}BackgroundColor`),
|
|
238
|
+
headerGradient: {
|
|
239
|
+
startColor: this.strValue(`${kind}HeaderGradientStart`),
|
|
240
|
+
endColor: this.strValue(`${kind}HeaderGradientEnd`),
|
|
241
|
+
},
|
|
242
|
+
footerGradient: {
|
|
243
|
+
startColor: this.strValue(`${kind}FooterGradientStart`),
|
|
244
|
+
endColor: this.strValue(`${kind}FooterGradientEnd`),
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
// Used with `[formControl]` (not `formControlName`) on kendo-colorpicker:
|
|
249
|
+
// FormControl directive accepts the control instance directly and avoids
|
|
250
|
+
// FormControlName's @Self() injection that historically tripped on the
|
|
251
|
+
// colorpicker's forwardRef NG_VALUE_ACCESSOR. Goes through Reactive Forms
|
|
252
|
+
// properly (tracks dirty/touched/validators), unlike the old [value] +
|
|
253
|
+
// (valueChange) workaround.
|
|
254
|
+
colorCtrl(controlName) {
|
|
255
|
+
return this.settingsForm.get(controlName);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Resolves a localized label for a given logo slot. The maco-app source used
|
|
259
|
+
* dynamic translate-key concatenation (`'Settings_Logo_' + slot | translate`);
|
|
260
|
+
* the library expresses the same idea by mapping slot names to fields on the
|
|
261
|
+
* `BrandingSettingsMessages` interface.
|
|
262
|
+
*/
|
|
263
|
+
logoLabel(slot) {
|
|
264
|
+
const m = this.messages();
|
|
265
|
+
switch (slot) {
|
|
266
|
+
case 'header':
|
|
267
|
+
return m.logoHeader;
|
|
268
|
+
case 'footer':
|
|
269
|
+
return m.logoFooter;
|
|
270
|
+
case 'favicon':
|
|
271
|
+
return m.logoFavicon;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: SettingsPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
275
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: SettingsPageComponent, isStandalone: true, selector: "mm-branding-settings", inputs: { messages: { classPropertyName: "messages", publicName: "messages", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"settings-page\">\n <form [formGroup]=\"settingsForm\" (ngSubmit)=\"onSubmit()\">\n\n <!-- General -->\n <kendo-card class=\"settings-card\">\n <kendo-card-header>\n <h3 class=\"card-title\">{{ messages().sectionGeneral }}</h3>\n </kendo-card-header>\n <kendo-card-body>\n <div class=\"form-row\">\n <kendo-formfield [title]=\"messages().appName\">\n <kendo-label [text]=\"messages().appName\" [optional]=\"false\" />\n <input kendoTextBox formControlName=\"appName\" [required]=\"true\" />\n <kendo-formerror>\n {{ messages().appName }} {{ messages().required }}\n </kendo-formerror>\n </kendo-formfield>\n <kendo-formfield [title]=\"messages().appTitle\">\n <kendo-label [text]=\"messages().appTitle\" [optional]=\"true\" />\n <input kendoTextBox formControlName=\"appTitle\" />\n </kendo-formfield>\n </div>\n </kendo-card-body>\n </kendo-card>\n\n <!-- Logos -->\n <kendo-card class=\"settings-card\">\n <kendo-card-header>\n <h3 class=\"card-title\">{{ messages().sectionLogos }}</h3>\n </kendo-card-header>\n <kendo-card-body>\n <div class=\"logo-upload-row\">\n @for (slot of logoSlotNames; track slot) {\n <div class=\"file-upload-section\">\n <label class=\"upload-label\" [attr.for]=\"'logo-' + slot\">\n {{ logoLabel(slot) }}\n </label>\n <div\n class=\"logo-dropzone\"\n [class.logo-dropzone--active]=\"isDragOver(slot)\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"logoLabel(slot)\"\n (click)=\"logoInput.click()\"\n (keydown.enter)=\"logoInput.click()\"\n (keydown.space)=\"$event.preventDefault(); logoInput.click()\"\n (dragover)=\"onLogoDragOver(slot, $event)\"\n (dragleave)=\"onLogoDragLeave(slot)\"\n (drop)=\"onLogoDropped(slot, $event)\"\n >\n <span class=\"logo-dropzone__label\">\n {{ slot === 'favicon' ? messages().uploadFavicon : messages().uploadLogo }}\n </span>\n <input\n #logoInput\n type=\"file\"\n accept=\"image/*\"\n [id]=\"'logo-' + slot\"\n (change)=\"onLogoSelected(slot, $event)\"\n class=\"logo-dropzone__input\"\n />\n </div>\n @let url = previewUrl(slot);\n @if (url) {\n <div class=\"logo-preview\">\n <img [src]=\"url\" [alt]=\"logoLabel(slot) + ' preview'\" />\n <button\n kendoButton\n type=\"button\"\n fillMode=\"flat\"\n [svgIcon]=\"xIcon\"\n (click)=\"clearLogo(slot)\"\n [attr.aria-label]=\"messages().logoRemove\"\n ></button>\n </div>\n }\n </div>\n }\n </div>\n </kendo-card-body>\n </kendo-card>\n\n <!-- Light Theme -->\n <kendo-card class=\"settings-card\">\n <kendo-card-header>\n <h3 class=\"card-title\">{{ messages().sectionLightTheme }}</h3>\n </kendo-card-header>\n <kendo-card-body>\n <div class=\"color-grid\">\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().colorPrimary }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('lightPrimaryColor')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().colorSecondary }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('lightSecondaryColor')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().colorTertiary }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('lightTertiaryColor')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().colorNeutral }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('lightNeutralColor')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().colorBackground }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('lightBackgroundColor')\"\n ></kendo-colorpicker>\n </div>\n </div>\n\n <div class=\"gradient-section\">\n <h4 class=\"subsection-title\">{{ messages().gradientHeader }}</h4>\n <div class=\"gradient-row\">\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().gradientStart }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('lightHeaderGradientStart')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field gradient-preview-col\">\n <span class=\"color-label\" aria-hidden=\"true\"> </span>\n <div\n class=\"gradient-preview\"\n [style.background]=\"'linear-gradient(to right, ' + settingsForm.get('lightHeaderGradientStart')?.value + ', ' + settingsForm.get('lightHeaderGradientEnd')?.value + ')'\"\n ></div>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().gradientEnd }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('lightHeaderGradientEnd')\"\n ></kendo-colorpicker>\n </div>\n </div>\n </div>\n\n <div class=\"gradient-section\">\n <h4 class=\"subsection-title\">{{ messages().gradientFooter }}</h4>\n <div class=\"gradient-row\">\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().gradientStart }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('lightFooterGradientStart')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field gradient-preview-col\">\n <span class=\"color-label\" aria-hidden=\"true\"> </span>\n <div\n class=\"gradient-preview\"\n [style.background]=\"'linear-gradient(to right, ' + settingsForm.get('lightFooterGradientStart')?.value + ', ' + settingsForm.get('lightFooterGradientEnd')?.value + ')'\"\n ></div>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().gradientEnd }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('lightFooterGradientEnd')\"\n ></kendo-colorpicker>\n </div>\n </div>\n </div>\n </kendo-card-body>\n </kendo-card>\n\n <!-- Dark Theme -->\n <kendo-card class=\"settings-card\">\n <kendo-card-header class=\"dark-theme-header\">\n <h3 class=\"card-title\">{{ messages().sectionDarkTheme }}</h3>\n <label class=\"dark-theme-toggle\">\n <kendo-switch formControlName=\"darkThemeEnabled\"></kendo-switch>\n <span>{{ messages().enableDarkTheme }}</span>\n </label>\n </kendo-card-header>\n @if (darkThemeEnabled()) {\n <kendo-card-body>\n <div class=\"color-grid\">\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().colorPrimary }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('darkPrimaryColor')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().colorSecondary }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('darkSecondaryColor')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().colorTertiary }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('darkTertiaryColor')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().colorNeutral }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('darkNeutralColor')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().colorBackground }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('darkBackgroundColor')\"\n ></kendo-colorpicker>\n </div>\n </div>\n\n <div class=\"gradient-section\">\n <h4 class=\"subsection-title\">{{ messages().gradientHeader }}</h4>\n <div class=\"gradient-row\">\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().gradientStart }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('darkHeaderGradientStart')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field gradient-preview-col\">\n <span class=\"color-label\" aria-hidden=\"true\"> </span>\n <div\n class=\"gradient-preview\"\n [style.background]=\"'linear-gradient(to right, ' + settingsForm.get('darkHeaderGradientStart')?.value + ', ' + settingsForm.get('darkHeaderGradientEnd')?.value + ')'\"\n ></div>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().gradientEnd }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('darkHeaderGradientEnd')\"\n ></kendo-colorpicker>\n </div>\n </div>\n </div>\n\n <div class=\"gradient-section\">\n <h4 class=\"subsection-title\">{{ messages().gradientFooter }}</h4>\n <div class=\"gradient-row\">\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().gradientStart }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('darkFooterGradientStart')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field gradient-preview-col\">\n <span class=\"color-label\" aria-hidden=\"true\"> </span>\n <div\n class=\"gradient-preview\"\n [style.background]=\"'linear-gradient(to right, ' + settingsForm.get('darkFooterGradientStart')?.value + ', ' + settingsForm.get('darkFooterGradientEnd')?.value + ')'\"\n ></div>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().gradientEnd }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('darkFooterGradientEnd')\"\n ></kendo-colorpicker>\n </div>\n </div>\n </div>\n </kendo-card-body>\n }\n </kendo-card>\n\n <div class=\"form-actions-bar\">\n <button\n kendoButton\n type=\"button\"\n themeColor=\"base\"\n [svgIcon]=\"undoIcon\"\n [disabled]=\"saving()\"\n (click)=\"onResetDefaults()\"\n >\n {{ messages().resetDefaults }}\n </button>\n <button\n kendoButton\n type=\"submit\"\n themeColor=\"primary\"\n [svgIcon]=\"saveIcon\"\n [disabled]=\"!settingsForm.valid || saving()\"\n >\n {{ messages().save }}\n </button>\n </div>\n </form>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;width:100%}.settings-page{width:100%;padding:16px;box-sizing:border-box}kendo-card.settings-card{margin-bottom:16px;width:100%!important}.card-title{margin:0;font-size:1.1rem;font-weight:500}.form-row{display:flex;gap:16px;flex-wrap:wrap}.form-row kendo-formfield{flex:1 1 240px}.form-actions-bar{position:sticky;bottom:12px;display:flex;justify-content:flex-end;gap:8px;margin-top:16px;padding:12px 16px;background:var(--kendo-color-surface, #fff);border:1px solid var(--kendo-color-border, #e0e0e0);border-radius:12px;box-shadow:0 6px 18px -8px #0f172a2e;z-index:1}.logo-upload-row{display:flex;gap:16px;flex-wrap:wrap}.file-upload-section{flex:1 1 220px}.upload-label{display:block;margin-bottom:4px;font-size:12px;color:var(--kendo-color-subtle, rgba(0, 0, 0, .6))}.logo-dropzone{margin-top:4px;height:80px;display:flex;align-items:center;justify-content:center;border:2px dashed var(--kendo-color-border, #d5d5d5);border-radius:4px;background:var(--kendo-color-surface-alt, transparent);cursor:pointer;transition:border-color .12s ease,background .12s ease}.logo-dropzone:hover,.logo-dropzone:focus-visible{border-color:var(--kendo-color-primary, #5ac4be);outline:none}.logo-dropzone--active{border-color:var(--kendo-color-primary, #5ac4be);background:color-mix(in srgb,var(--kendo-color-primary, #5ac4be) 8%,transparent)}.logo-dropzone__label{font-size:.9rem;color:var(--kendo-color-subtle, rgba(0, 0, 0, .55));pointer-events:none}.logo-dropzone__input{display:none}.logo-preview{display:flex;align-items:center;gap:10px;margin-top:8px;position:relative}.logo-preview img{max-width:200px;max-height:80px;object-fit:contain;border:1px solid var(--kendo-color-border, #ddd);border-radius:4px;padding:4px}.logo-preview button{position:absolute;top:-8px;right:-8px}.color-grid{display:flex;gap:32px;flex-wrap:wrap}.color-field{display:flex;flex-direction:column;gap:4px}.color-label{font-size:12px;color:var(--kendo-color-subtle, rgba(0, 0, 0, .6))}.subsection-title{margin:16px 0 8px;font-size:.95rem;font-weight:500;color:var(--kendo-color-subtle, #666)}.gradient-section{border-top:1px solid var(--kendo-color-border, #e0e0e0);margin-top:16px;padding-top:4px}.gradient-row{display:flex;align-items:stretch;gap:16px}.gradient-row .color-field{flex:0 0 auto}.gradient-row .color-field.gradient-preview-col{flex:1 1 auto;min-width:0}.gradient-preview-col .color-label{visibility:hidden}.gradient-preview{flex:1;width:100%;border-radius:4px;border:1px solid var(--kendo-color-border, #e0e0e0)}.dark-theme-header{display:flex;align-items:center;justify-content:space-between;gap:16px}.dark-theme-toggle{display:inline-flex;align-items:center;gap:8px;font-size:.95rem;cursor:pointer;-webkit-user-select:none;user-select:none}\n"], dependencies: [{ kind: "ngmodule", type: InputsModule }, { kind: "directive", type: i1.TextBoxDirective, selector: "input[kendoTextBox]", inputs: ["value"] }, { kind: "component", type: i1.SwitchComponent, selector: "kendo-switch", inputs: ["focusableId", "onLabel", "offLabel", "checked", "disabled", "readonly", "tabindex", "size", "thumbRounded", "trackRounded", "tabIndex"], outputs: ["focus", "blur", "valueChange"], exportAs: ["kendoSwitch"] }, { kind: "component", type: i1.FormFieldComponent, selector: "kendo-formfield", inputs: ["showHints", "orientation", "showErrors", "colSpan"] }, { kind: "component", type: i1.ErrorComponent, selector: "kendo-formerror", inputs: ["align"] }, { kind: "component", type: i1.ColorPickerComponent, selector: "kendo-colorpicker", inputs: ["views", "view", "adaptiveMode", "activeView", "readonly", "disabled", "format", "value", "popupSettings", "paletteSettings", "gradientSettings", "icon", "iconClass", "svgIcon", "adaptiveTitle", "adaptiveSubtitle", "clearButton", "tabindex", "preview", "actionsLayout", "size", "rounded", "fillMode"], outputs: ["valueChange", "open", "close", "focus", "blur", "cancel", "activeColorClick", "clearButtonClick", "activeViewChange"], exportAs: ["kendoColorPicker"] }, { kind: "ngmodule", type: LabelModule }, { kind: "component", type: i2.LabelComponent, selector: "kendo-label", inputs: ["text", "for", "optional", "labelCssStyle", "labelCssClass"], exportAs: ["kendoLabel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i3.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: LayoutModule }, { kind: "component", type: i4.CardComponent, selector: "kendo-card", inputs: ["orientation", "width"] }, { kind: "component", type: i4.CardBodyComponent, selector: "kendo-card-body" }, { kind: "component", type: i4.CardHeaderComponent, selector: "kendo-card-header" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i5.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i5.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i5.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i5.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i5.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i5.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i5.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i5.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }] });
|
|
276
|
+
}
|
|
277
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: SettingsPageComponent, decorators: [{
|
|
278
|
+
type: Component,
|
|
279
|
+
args: [{ selector: 'mm-branding-settings', standalone: true, imports: [
|
|
280
|
+
InputsModule,
|
|
281
|
+
LabelModule,
|
|
282
|
+
ButtonsModule,
|
|
283
|
+
LayoutModule,
|
|
284
|
+
ReactiveFormsModule,
|
|
285
|
+
], template: "<div class=\"settings-page\">\n <form [formGroup]=\"settingsForm\" (ngSubmit)=\"onSubmit()\">\n\n <!-- General -->\n <kendo-card class=\"settings-card\">\n <kendo-card-header>\n <h3 class=\"card-title\">{{ messages().sectionGeneral }}</h3>\n </kendo-card-header>\n <kendo-card-body>\n <div class=\"form-row\">\n <kendo-formfield [title]=\"messages().appName\">\n <kendo-label [text]=\"messages().appName\" [optional]=\"false\" />\n <input kendoTextBox formControlName=\"appName\" [required]=\"true\" />\n <kendo-formerror>\n {{ messages().appName }} {{ messages().required }}\n </kendo-formerror>\n </kendo-formfield>\n <kendo-formfield [title]=\"messages().appTitle\">\n <kendo-label [text]=\"messages().appTitle\" [optional]=\"true\" />\n <input kendoTextBox formControlName=\"appTitle\" />\n </kendo-formfield>\n </div>\n </kendo-card-body>\n </kendo-card>\n\n <!-- Logos -->\n <kendo-card class=\"settings-card\">\n <kendo-card-header>\n <h3 class=\"card-title\">{{ messages().sectionLogos }}</h3>\n </kendo-card-header>\n <kendo-card-body>\n <div class=\"logo-upload-row\">\n @for (slot of logoSlotNames; track slot) {\n <div class=\"file-upload-section\">\n <label class=\"upload-label\" [attr.for]=\"'logo-' + slot\">\n {{ logoLabel(slot) }}\n </label>\n <div\n class=\"logo-dropzone\"\n [class.logo-dropzone--active]=\"isDragOver(slot)\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"logoLabel(slot)\"\n (click)=\"logoInput.click()\"\n (keydown.enter)=\"logoInput.click()\"\n (keydown.space)=\"$event.preventDefault(); logoInput.click()\"\n (dragover)=\"onLogoDragOver(slot, $event)\"\n (dragleave)=\"onLogoDragLeave(slot)\"\n (drop)=\"onLogoDropped(slot, $event)\"\n >\n <span class=\"logo-dropzone__label\">\n {{ slot === 'favicon' ? messages().uploadFavicon : messages().uploadLogo }}\n </span>\n <input\n #logoInput\n type=\"file\"\n accept=\"image/*\"\n [id]=\"'logo-' + slot\"\n (change)=\"onLogoSelected(slot, $event)\"\n class=\"logo-dropzone__input\"\n />\n </div>\n @let url = previewUrl(slot);\n @if (url) {\n <div class=\"logo-preview\">\n <img [src]=\"url\" [alt]=\"logoLabel(slot) + ' preview'\" />\n <button\n kendoButton\n type=\"button\"\n fillMode=\"flat\"\n [svgIcon]=\"xIcon\"\n (click)=\"clearLogo(slot)\"\n [attr.aria-label]=\"messages().logoRemove\"\n ></button>\n </div>\n }\n </div>\n }\n </div>\n </kendo-card-body>\n </kendo-card>\n\n <!-- Light Theme -->\n <kendo-card class=\"settings-card\">\n <kendo-card-header>\n <h3 class=\"card-title\">{{ messages().sectionLightTheme }}</h3>\n </kendo-card-header>\n <kendo-card-body>\n <div class=\"color-grid\">\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().colorPrimary }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('lightPrimaryColor')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().colorSecondary }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('lightSecondaryColor')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().colorTertiary }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('lightTertiaryColor')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().colorNeutral }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('lightNeutralColor')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().colorBackground }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('lightBackgroundColor')\"\n ></kendo-colorpicker>\n </div>\n </div>\n\n <div class=\"gradient-section\">\n <h4 class=\"subsection-title\">{{ messages().gradientHeader }}</h4>\n <div class=\"gradient-row\">\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().gradientStart }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('lightHeaderGradientStart')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field gradient-preview-col\">\n <span class=\"color-label\" aria-hidden=\"true\"> </span>\n <div\n class=\"gradient-preview\"\n [style.background]=\"'linear-gradient(to right, ' + settingsForm.get('lightHeaderGradientStart')?.value + ', ' + settingsForm.get('lightHeaderGradientEnd')?.value + ')'\"\n ></div>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().gradientEnd }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('lightHeaderGradientEnd')\"\n ></kendo-colorpicker>\n </div>\n </div>\n </div>\n\n <div class=\"gradient-section\">\n <h4 class=\"subsection-title\">{{ messages().gradientFooter }}</h4>\n <div class=\"gradient-row\">\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().gradientStart }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('lightFooterGradientStart')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field gradient-preview-col\">\n <span class=\"color-label\" aria-hidden=\"true\"> </span>\n <div\n class=\"gradient-preview\"\n [style.background]=\"'linear-gradient(to right, ' + settingsForm.get('lightFooterGradientStart')?.value + ', ' + settingsForm.get('lightFooterGradientEnd')?.value + ')'\"\n ></div>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().gradientEnd }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('lightFooterGradientEnd')\"\n ></kendo-colorpicker>\n </div>\n </div>\n </div>\n </kendo-card-body>\n </kendo-card>\n\n <!-- Dark Theme -->\n <kendo-card class=\"settings-card\">\n <kendo-card-header class=\"dark-theme-header\">\n <h3 class=\"card-title\">{{ messages().sectionDarkTheme }}</h3>\n <label class=\"dark-theme-toggle\">\n <kendo-switch formControlName=\"darkThemeEnabled\"></kendo-switch>\n <span>{{ messages().enableDarkTheme }}</span>\n </label>\n </kendo-card-header>\n @if (darkThemeEnabled()) {\n <kendo-card-body>\n <div class=\"color-grid\">\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().colorPrimary }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('darkPrimaryColor')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().colorSecondary }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('darkSecondaryColor')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().colorTertiary }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('darkTertiaryColor')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().colorNeutral }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('darkNeutralColor')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().colorBackground }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('darkBackgroundColor')\"\n ></kendo-colorpicker>\n </div>\n </div>\n\n <div class=\"gradient-section\">\n <h4 class=\"subsection-title\">{{ messages().gradientHeader }}</h4>\n <div class=\"gradient-row\">\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().gradientStart }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('darkHeaderGradientStart')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field gradient-preview-col\">\n <span class=\"color-label\" aria-hidden=\"true\"> </span>\n <div\n class=\"gradient-preview\"\n [style.background]=\"'linear-gradient(to right, ' + settingsForm.get('darkHeaderGradientStart')?.value + ', ' + settingsForm.get('darkHeaderGradientEnd')?.value + ')'\"\n ></div>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().gradientEnd }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('darkHeaderGradientEnd')\"\n ></kendo-colorpicker>\n </div>\n </div>\n </div>\n\n <div class=\"gradient-section\">\n <h4 class=\"subsection-title\">{{ messages().gradientFooter }}</h4>\n <div class=\"gradient-row\">\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().gradientStart }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('darkFooterGradientStart')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field gradient-preview-col\">\n <span class=\"color-label\" aria-hidden=\"true\"> </span>\n <div\n class=\"gradient-preview\"\n [style.background]=\"'linear-gradient(to right, ' + settingsForm.get('darkFooterGradientStart')?.value + ', ' + settingsForm.get('darkFooterGradientEnd')?.value + ')'\"\n ></div>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().gradientEnd }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('darkFooterGradientEnd')\"\n ></kendo-colorpicker>\n </div>\n </div>\n </div>\n </kendo-card-body>\n }\n </kendo-card>\n\n <div class=\"form-actions-bar\">\n <button\n kendoButton\n type=\"button\"\n themeColor=\"base\"\n [svgIcon]=\"undoIcon\"\n [disabled]=\"saving()\"\n (click)=\"onResetDefaults()\"\n >\n {{ messages().resetDefaults }}\n </button>\n <button\n kendoButton\n type=\"submit\"\n themeColor=\"primary\"\n [svgIcon]=\"saveIcon\"\n [disabled]=\"!settingsForm.valid || saving()\"\n >\n {{ messages().save }}\n </button>\n </div>\n </form>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;width:100%}.settings-page{width:100%;padding:16px;box-sizing:border-box}kendo-card.settings-card{margin-bottom:16px;width:100%!important}.card-title{margin:0;font-size:1.1rem;font-weight:500}.form-row{display:flex;gap:16px;flex-wrap:wrap}.form-row kendo-formfield{flex:1 1 240px}.form-actions-bar{position:sticky;bottom:12px;display:flex;justify-content:flex-end;gap:8px;margin-top:16px;padding:12px 16px;background:var(--kendo-color-surface, #fff);border:1px solid var(--kendo-color-border, #e0e0e0);border-radius:12px;box-shadow:0 6px 18px -8px #0f172a2e;z-index:1}.logo-upload-row{display:flex;gap:16px;flex-wrap:wrap}.file-upload-section{flex:1 1 220px}.upload-label{display:block;margin-bottom:4px;font-size:12px;color:var(--kendo-color-subtle, rgba(0, 0, 0, .6))}.logo-dropzone{margin-top:4px;height:80px;display:flex;align-items:center;justify-content:center;border:2px dashed var(--kendo-color-border, #d5d5d5);border-radius:4px;background:var(--kendo-color-surface-alt, transparent);cursor:pointer;transition:border-color .12s ease,background .12s ease}.logo-dropzone:hover,.logo-dropzone:focus-visible{border-color:var(--kendo-color-primary, #5ac4be);outline:none}.logo-dropzone--active{border-color:var(--kendo-color-primary, #5ac4be);background:color-mix(in srgb,var(--kendo-color-primary, #5ac4be) 8%,transparent)}.logo-dropzone__label{font-size:.9rem;color:var(--kendo-color-subtle, rgba(0, 0, 0, .55));pointer-events:none}.logo-dropzone__input{display:none}.logo-preview{display:flex;align-items:center;gap:10px;margin-top:8px;position:relative}.logo-preview img{max-width:200px;max-height:80px;object-fit:contain;border:1px solid var(--kendo-color-border, #ddd);border-radius:4px;padding:4px}.logo-preview button{position:absolute;top:-8px;right:-8px}.color-grid{display:flex;gap:32px;flex-wrap:wrap}.color-field{display:flex;flex-direction:column;gap:4px}.color-label{font-size:12px;color:var(--kendo-color-subtle, rgba(0, 0, 0, .6))}.subsection-title{margin:16px 0 8px;font-size:.95rem;font-weight:500;color:var(--kendo-color-subtle, #666)}.gradient-section{border-top:1px solid var(--kendo-color-border, #e0e0e0);margin-top:16px;padding-top:4px}.gradient-row{display:flex;align-items:stretch;gap:16px}.gradient-row .color-field{flex:0 0 auto}.gradient-row .color-field.gradient-preview-col{flex:1 1 auto;min-width:0}.gradient-preview-col .color-label{visibility:hidden}.gradient-preview{flex:1;width:100%;border-radius:4px;border:1px solid var(--kendo-color-border, #e0e0e0)}.dark-theme-header{display:flex;align-items:center;justify-content:space-between;gap:16px}.dark-theme-toggle{display:inline-flex;align-items:center;gap:8px;font-size:.95rem;cursor:pointer;-webkit-user-select:none;user-select:none}\n"] }]
|
|
286
|
+
}], ctorParameters: () => [], propDecorators: { messages: [{ type: i0.Input, args: [{ isSignal: true, alias: "messages", required: true }] }] } });
|
|
287
|
+
|
|
288
|
+
var settingsPage_component = /*#__PURE__*/Object.freeze({
|
|
289
|
+
__proto__: null,
|
|
290
|
+
SettingsPageComponent: SettingsPageComponent
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Routes for the branding/settings UI. Mount under any path:
|
|
295
|
+
*
|
|
296
|
+
* ```ts
|
|
297
|
+
* { path: 'settings', canActivate: [adminGuard], children: BRANDING_ROUTES }
|
|
298
|
+
* ```
|
|
299
|
+
*
|
|
300
|
+
* The `SettingsPageComponent` is lazy-loaded on first navigation regardless of
|
|
301
|
+
* how the host wires this — `loadComponent` returns a dynamic import.
|
|
302
|
+
*/
|
|
303
|
+
const BRANDING_ROUTES = [
|
|
304
|
+
{
|
|
305
|
+
path: '',
|
|
306
|
+
loadComponent: () => Promise.resolve().then(function () { return settingsPage_component; }).then((m) => m.SettingsPageComponent),
|
|
307
|
+
},
|
|
308
|
+
];
|
|
309
|
+
|
|
310
|
+
/*
|
|
311
|
+
* Public API Surface of @meshmakers/octo-ui/branding-settings
|
|
312
|
+
*
|
|
313
|
+
* Heavy admin-only branding editor. Lives in its own secondary entry point so
|
|
314
|
+
* host applications that only need the lightweight branding pieces (header
|
|
315
|
+
* logo, theme switcher, services) — exposed by the primary `@meshmakers/octo-ui`
|
|
316
|
+
* entry — do not pay for the form/Kendo modules pulled in by the editor itself.
|
|
317
|
+
*/
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Generated bundle index. Do not edit.
|
|
321
|
+
*/
|
|
322
|
+
|
|
323
|
+
export { BRANDING_ROUTES, SettingsPageComponent };
|
|
324
|
+
//# sourceMappingURL=meshmakers-octo-ui-branding-settings.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meshmakers-octo-ui-branding-settings.mjs","sources":["../../../../projects/meshmakers/octo-ui/branding-settings/src/settings-page.component.ts","../../../../projects/meshmakers/octo-ui/branding-settings/src/settings-page.component.html","../../../../projects/meshmakers/octo-ui/branding-settings/src/branding-settings.routes.ts","../../../../projects/meshmakers/octo-ui/branding-settings/src/public-api.ts","../../../../projects/meshmakers/octo-ui/branding-settings/src/meshmakers-octo-ui-branding-settings.ts"],"sourcesContent":["import { Component, inject, input, OnInit, signal } from '@angular/core';\nimport {\n FormBuilder,\n FormControl,\n FormGroup,\n ReactiveFormsModule,\n Validators,\n} from '@angular/forms';\nimport {\n BrandingData,\n BrandingDataSource,\n OCTO_BRANDING_DEFAULTS,\n ThemePalette,\n} from '@meshmakers/octo-ui/branding';\nimport { MessageService } from '@meshmakers/shared-services';\nimport { ButtonsModule } from '@progress/kendo-angular-buttons';\nimport { InputsModule } from '@progress/kendo-angular-inputs';\nimport { LabelModule } from '@progress/kendo-angular-label';\nimport { LayoutModule } from '@progress/kendo-angular-layout';\nimport { saveIcon, undoIcon, xIcon } from '@progress/kendo-svg-icons';\nimport { BrandingSettingsMessages } from './branding-settings.messages';\n\ntype LogoSlotName = 'header' | 'footer' | 'favicon';\n\ninterface LogoSlot {\n file: File | null;\n preview: string | null;\n cleared: boolean;\n}\n\nconst EMPTY_LOGO_SLOT: LogoSlot = {\n file: null,\n preview: null,\n cleared: false,\n};\n\n@Component({\n selector: 'mm-branding-settings',\n standalone: true,\n imports: [\n InputsModule,\n LabelModule,\n ButtonsModule,\n LayoutModule,\n ReactiveFormsModule,\n ],\n templateUrl: './settings-page.component.html',\n styleUrl: './settings-page.component.scss',\n})\nexport class SettingsPageComponent implements OnInit {\n readonly messages = input.required<BrandingSettingsMessages>();\n\n private readonly fb = inject(FormBuilder);\n private readonly branding = inject(BrandingDataSource);\n private readonly messageService = inject(MessageService);\n private readonly defaults = inject(OCTO_BRANDING_DEFAULTS);\n\n protected readonly saveIcon = saveIcon;\n protected readonly undoIcon = undoIcon;\n protected readonly xIcon = xIcon;\n\n protected readonly logoSlotNames: readonly LogoSlotName[] = [\n 'header',\n 'footer',\n 'favicon',\n ];\n\n protected readonly saving = signal(false);\n\n protected readonly settingsForm: FormGroup;\n\n private readonly slotStates = {\n header: signal<LogoSlot>({ ...EMPTY_LOGO_SLOT }),\n footer: signal<LogoSlot>({ ...EMPTY_LOGO_SLOT }),\n favicon: signal<LogoSlot>({ ...EMPTY_LOGO_SLOT }),\n } as const satisfies Record<LogoSlotName, unknown>;\n\n private readonly persistedUrls = {\n header: signal<string | null>(null),\n footer: signal<string | null>(null),\n favicon: signal<string | null>(null),\n } as const satisfies Record<LogoSlotName, unknown>;\n\n constructor() {\n const { lightTheme, darkTheme } = this.defaults;\n const dark = darkTheme ?? lightTheme;\n\n // Seeding color controls with valid hex defaults is required: kendo-colorpicker\n // binds as a ControlValueAccessor and doesn't handle initial empty strings\n // cleanly inside kendo-formfield.\n this.settingsForm = this.fb.group({\n appName: [this.defaults.appName, Validators.required],\n appTitle: [this.defaults.appTitle],\n\n lightPrimaryColor: [lightTheme.primaryColor, Validators.required],\n lightSecondaryColor: [lightTheme.secondaryColor, Validators.required],\n lightTertiaryColor: [lightTheme.tertiaryColor, Validators.required],\n lightNeutralColor: [lightTheme.neutralColor, Validators.required],\n lightBackgroundColor: [lightTheme.backgroundColor],\n lightHeaderGradientStart: [lightTheme.headerGradient.startColor],\n lightHeaderGradientEnd: [lightTheme.headerGradient.endColor],\n lightFooterGradientStart: [lightTheme.footerGradient.startColor],\n lightFooterGradientEnd: [lightTheme.footerGradient.endColor],\n\n darkThemeEnabled: [darkTheme !== null],\n\n darkPrimaryColor: [dark.primaryColor, Validators.required],\n darkSecondaryColor: [dark.secondaryColor, Validators.required],\n darkTertiaryColor: [dark.tertiaryColor, Validators.required],\n darkNeutralColor: [dark.neutralColor, Validators.required],\n darkBackgroundColor: [dark.backgroundColor],\n darkHeaderGradientStart: [dark.headerGradient.startColor],\n darkHeaderGradientEnd: [dark.headerGradient.endColor],\n darkFooterGradientStart: [dark.footerGradient.startColor],\n darkFooterGradientEnd: [dark.footerGradient.endColor],\n });\n }\n\n protected darkThemeEnabled(): boolean {\n return this.settingsForm.get('darkThemeEnabled')?.value === true;\n }\n\n async ngOnInit(): Promise<void> {\n try {\n await this.branding.load();\n } catch (error) {\n console.error('[SettingsPageComponent] Failed to load branding', error);\n this.messageService.showError(this.messages().loadError);\n return;\n }\n this.hydrate(this.branding.branding());\n }\n\n private readonly dragOverSlot = signal<LogoSlotName | null>(null);\n\n protected isDragOver(slot: LogoSlotName): boolean {\n return this.dragOverSlot() === slot;\n }\n\n protected onLogoSelected(slot: LogoSlotName, event: Event): void {\n const input = event.target;\n if (!(input instanceof HTMLInputElement)) return;\n const file = input.files?.[0] ?? null;\n this.applyLogoFile(slot, file);\n input.value = '';\n }\n\n protected onLogoDragOver(slot: LogoSlotName, event: DragEvent): void {\n event.preventDefault();\n if (event.dataTransfer) event.dataTransfer.dropEffect = 'copy';\n this.dragOverSlot.set(slot);\n }\n\n protected onLogoDragLeave(slot: LogoSlotName): void {\n if (this.dragOverSlot() === slot) this.dragOverSlot.set(null);\n }\n\n protected onLogoDropped(slot: LogoSlotName, event: DragEvent): void {\n event.preventDefault();\n this.dragOverSlot.set(null);\n const file = event.dataTransfer?.files?.[0];\n if (!file || !file.type.startsWith('image/')) return;\n this.applyLogoFile(slot, file);\n }\n\n private applyLogoFile(slot: LogoSlotName, file: File | null): void {\n if (!file) return;\n const reader = new FileReader();\n reader.onload = (): void => {\n const result = reader.result;\n this.slotStates[slot].set({\n file,\n preview: typeof result === 'string' ? result : null,\n cleared: false,\n });\n };\n reader.readAsDataURL(file);\n }\n\n protected clearLogo(slot: LogoSlotName): void {\n this.slotStates[slot].set({ file: null, preview: null, cleared: true });\n this.persistedUrls[slot].set(null);\n }\n\n protected previewUrl(slot: LogoSlotName): string | null {\n return this.slotStates[slot]().preview ?? this.persistedUrls[slot]();\n }\n\n protected async onSubmit(): Promise<void> {\n if (this.settingsForm.invalid) {\n this.settingsForm.markAllAsTouched();\n return;\n }\n this.saving.set(true);\n try {\n await this.branding.save({\n appName: this.strValue('appName'),\n appTitle: this.strValue('appTitle'),\n headerLogoFile: this.logoFileForSave('header'),\n footerLogoFile: this.logoFileForSave('footer'),\n faviconFile: this.logoFileForSave('favicon'),\n lightTheme: this.paletteFromForm('light'),\n darkTheme: this.darkThemeEnabled() ? this.paletteFromForm('dark') : null,\n });\n this.messageService.showInformation(this.messages().saveSuccess);\n this.hydrate(this.branding.branding());\n this.settingsForm.markAsPristine();\n } catch (error) {\n console.error('[SettingsPageComponent] Failed to save branding', error);\n this.messageService.showError(this.messages().saveError);\n } finally {\n this.saving.set(false);\n }\n }\n\n protected async onResetDefaults(): Promise<void> {\n this.saving.set(true);\n try {\n await this.branding.resetToDefaults();\n this.hydrate(this.branding.branding());\n this.settingsForm.markAsPristine();\n this.messageService.showInformation(this.messages().resetSuccess);\n } catch (error) {\n console.error('[SettingsPageComponent] Failed to reset branding', error);\n this.messageService.showError(this.messages().saveError);\n } finally {\n this.saving.set(false);\n }\n }\n\n private hydrate(data: BrandingData): void {\n this.persistedUrls.header.set(data.headerLogoUrl);\n this.persistedUrls.footer.set(data.footerLogoUrl);\n this.persistedUrls.favicon.set(data.faviconUrl);\n\n for (const slot of this.logoSlotNames) {\n this.slotStates[slot].set({ ...EMPTY_LOGO_SLOT });\n }\n\n const dark =\n data.darkTheme ?? this.defaults.darkTheme ?? this.defaults.lightTheme;\n\n this.settingsForm.patchValue({\n appName: data.appName,\n appTitle: data.appTitle,\n darkThemeEnabled: data.darkTheme !== null,\n\n lightPrimaryColor: data.lightTheme.primaryColor,\n lightSecondaryColor: data.lightTheme.secondaryColor,\n lightTertiaryColor: data.lightTheme.tertiaryColor,\n lightNeutralColor: data.lightTheme.neutralColor,\n lightBackgroundColor: data.lightTheme.backgroundColor,\n lightHeaderGradientStart: data.lightTheme.headerGradient.startColor,\n lightHeaderGradientEnd: data.lightTheme.headerGradient.endColor,\n lightFooterGradientStart: data.lightTheme.footerGradient.startColor,\n lightFooterGradientEnd: data.lightTheme.footerGradient.endColor,\n\n darkPrimaryColor: dark.primaryColor,\n darkSecondaryColor: dark.secondaryColor,\n darkTertiaryColor: dark.tertiaryColor,\n darkNeutralColor: dark.neutralColor,\n darkBackgroundColor: dark.backgroundColor,\n darkHeaderGradientStart: dark.headerGradient.startColor,\n darkHeaderGradientEnd: dark.headerGradient.endColor,\n darkFooterGradientStart: dark.footerGradient.startColor,\n darkFooterGradientEnd: dark.footerGradient.endColor,\n });\n }\n\n private logoFileForSave(slot: LogoSlotName): File | null | undefined {\n const state = this.slotStates[slot]();\n if (state.file) return state.file;\n if (state.cleared) return null;\n return undefined;\n }\n\n private strValue(name: string): string {\n const value = this.settingsForm.get(name)?.value;\n return typeof value === 'string' ? value : '';\n }\n\n private paletteFromForm(kind: 'light' | 'dark'): ThemePalette {\n return {\n primaryColor: this.strValue(`${kind}PrimaryColor`),\n secondaryColor: this.strValue(`${kind}SecondaryColor`),\n tertiaryColor: this.strValue(`${kind}TertiaryColor`),\n neutralColor: this.strValue(`${kind}NeutralColor`),\n backgroundColor: this.strValue(`${kind}BackgroundColor`),\n headerGradient: {\n startColor: this.strValue(`${kind}HeaderGradientStart`),\n endColor: this.strValue(`${kind}HeaderGradientEnd`),\n },\n footerGradient: {\n startColor: this.strValue(`${kind}FooterGradientStart`),\n endColor: this.strValue(`${kind}FooterGradientEnd`),\n },\n };\n }\n\n // Used with `[formControl]` (not `formControlName`) on kendo-colorpicker:\n // FormControl directive accepts the control instance directly and avoids\n // FormControlName's @Self() injection that historically tripped on the\n // colorpicker's forwardRef NG_VALUE_ACCESSOR. Goes through Reactive Forms\n // properly (tracks dirty/touched/validators), unlike the old [value] +\n // (valueChange) workaround.\n protected colorCtrl(controlName: string): FormControl<string> {\n return this.settingsForm.get(controlName) as FormControl<string>;\n }\n\n /**\n * Resolves a localized label for a given logo slot. The maco-app source used\n * dynamic translate-key concatenation (`'Settings_Logo_' + slot | translate`);\n * the library expresses the same idea by mapping slot names to fields on the\n * `BrandingSettingsMessages` interface.\n */\n protected logoLabel(slot: LogoSlotName): string {\n const m = this.messages();\n switch (slot) {\n case 'header':\n return m.logoHeader;\n case 'footer':\n return m.logoFooter;\n case 'favicon':\n return m.logoFavicon;\n }\n }\n}\n","<div class=\"settings-page\">\n <form [formGroup]=\"settingsForm\" (ngSubmit)=\"onSubmit()\">\n\n <!-- General -->\n <kendo-card class=\"settings-card\">\n <kendo-card-header>\n <h3 class=\"card-title\">{{ messages().sectionGeneral }}</h3>\n </kendo-card-header>\n <kendo-card-body>\n <div class=\"form-row\">\n <kendo-formfield [title]=\"messages().appName\">\n <kendo-label [text]=\"messages().appName\" [optional]=\"false\" />\n <input kendoTextBox formControlName=\"appName\" [required]=\"true\" />\n <kendo-formerror>\n {{ messages().appName }} {{ messages().required }}\n </kendo-formerror>\n </kendo-formfield>\n <kendo-formfield [title]=\"messages().appTitle\">\n <kendo-label [text]=\"messages().appTitle\" [optional]=\"true\" />\n <input kendoTextBox formControlName=\"appTitle\" />\n </kendo-formfield>\n </div>\n </kendo-card-body>\n </kendo-card>\n\n <!-- Logos -->\n <kendo-card class=\"settings-card\">\n <kendo-card-header>\n <h3 class=\"card-title\">{{ messages().sectionLogos }}</h3>\n </kendo-card-header>\n <kendo-card-body>\n <div class=\"logo-upload-row\">\n @for (slot of logoSlotNames; track slot) {\n <div class=\"file-upload-section\">\n <label class=\"upload-label\" [attr.for]=\"'logo-' + slot\">\n {{ logoLabel(slot) }}\n </label>\n <div\n class=\"logo-dropzone\"\n [class.logo-dropzone--active]=\"isDragOver(slot)\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"logoLabel(slot)\"\n (click)=\"logoInput.click()\"\n (keydown.enter)=\"logoInput.click()\"\n (keydown.space)=\"$event.preventDefault(); logoInput.click()\"\n (dragover)=\"onLogoDragOver(slot, $event)\"\n (dragleave)=\"onLogoDragLeave(slot)\"\n (drop)=\"onLogoDropped(slot, $event)\"\n >\n <span class=\"logo-dropzone__label\">\n {{ slot === 'favicon' ? messages().uploadFavicon : messages().uploadLogo }}\n </span>\n <input\n #logoInput\n type=\"file\"\n accept=\"image/*\"\n [id]=\"'logo-' + slot\"\n (change)=\"onLogoSelected(slot, $event)\"\n class=\"logo-dropzone__input\"\n />\n </div>\n @let url = previewUrl(slot);\n @if (url) {\n <div class=\"logo-preview\">\n <img [src]=\"url\" [alt]=\"logoLabel(slot) + ' preview'\" />\n <button\n kendoButton\n type=\"button\"\n fillMode=\"flat\"\n [svgIcon]=\"xIcon\"\n (click)=\"clearLogo(slot)\"\n [attr.aria-label]=\"messages().logoRemove\"\n ></button>\n </div>\n }\n </div>\n }\n </div>\n </kendo-card-body>\n </kendo-card>\n\n <!-- Light Theme -->\n <kendo-card class=\"settings-card\">\n <kendo-card-header>\n <h3 class=\"card-title\">{{ messages().sectionLightTheme }}</h3>\n </kendo-card-header>\n <kendo-card-body>\n <div class=\"color-grid\">\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().colorPrimary }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('lightPrimaryColor')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().colorSecondary }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('lightSecondaryColor')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().colorTertiary }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('lightTertiaryColor')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().colorNeutral }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('lightNeutralColor')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().colorBackground }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('lightBackgroundColor')\"\n ></kendo-colorpicker>\n </div>\n </div>\n\n <div class=\"gradient-section\">\n <h4 class=\"subsection-title\">{{ messages().gradientHeader }}</h4>\n <div class=\"gradient-row\">\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().gradientStart }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('lightHeaderGradientStart')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field gradient-preview-col\">\n <span class=\"color-label\" aria-hidden=\"true\"> </span>\n <div\n class=\"gradient-preview\"\n [style.background]=\"'linear-gradient(to right, ' + settingsForm.get('lightHeaderGradientStart')?.value + ', ' + settingsForm.get('lightHeaderGradientEnd')?.value + ')'\"\n ></div>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().gradientEnd }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('lightHeaderGradientEnd')\"\n ></kendo-colorpicker>\n </div>\n </div>\n </div>\n\n <div class=\"gradient-section\">\n <h4 class=\"subsection-title\">{{ messages().gradientFooter }}</h4>\n <div class=\"gradient-row\">\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().gradientStart }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('lightFooterGradientStart')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field gradient-preview-col\">\n <span class=\"color-label\" aria-hidden=\"true\"> </span>\n <div\n class=\"gradient-preview\"\n [style.background]=\"'linear-gradient(to right, ' + settingsForm.get('lightFooterGradientStart')?.value + ', ' + settingsForm.get('lightFooterGradientEnd')?.value + ')'\"\n ></div>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().gradientEnd }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('lightFooterGradientEnd')\"\n ></kendo-colorpicker>\n </div>\n </div>\n </div>\n </kendo-card-body>\n </kendo-card>\n\n <!-- Dark Theme -->\n <kendo-card class=\"settings-card\">\n <kendo-card-header class=\"dark-theme-header\">\n <h3 class=\"card-title\">{{ messages().sectionDarkTheme }}</h3>\n <label class=\"dark-theme-toggle\">\n <kendo-switch formControlName=\"darkThemeEnabled\"></kendo-switch>\n <span>{{ messages().enableDarkTheme }}</span>\n </label>\n </kendo-card-header>\n @if (darkThemeEnabled()) {\n <kendo-card-body>\n <div class=\"color-grid\">\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().colorPrimary }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('darkPrimaryColor')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().colorSecondary }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('darkSecondaryColor')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().colorTertiary }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('darkTertiaryColor')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().colorNeutral }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('darkNeutralColor')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().colorBackground }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('darkBackgroundColor')\"\n ></kendo-colorpicker>\n </div>\n </div>\n\n <div class=\"gradient-section\">\n <h4 class=\"subsection-title\">{{ messages().gradientHeader }}</h4>\n <div class=\"gradient-row\">\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().gradientStart }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('darkHeaderGradientStart')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field gradient-preview-col\">\n <span class=\"color-label\" aria-hidden=\"true\"> </span>\n <div\n class=\"gradient-preview\"\n [style.background]=\"'linear-gradient(to right, ' + settingsForm.get('darkHeaderGradientStart')?.value + ', ' + settingsForm.get('darkHeaderGradientEnd')?.value + ')'\"\n ></div>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().gradientEnd }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('darkHeaderGradientEnd')\"\n ></kendo-colorpicker>\n </div>\n </div>\n </div>\n\n <div class=\"gradient-section\">\n <h4 class=\"subsection-title\">{{ messages().gradientFooter }}</h4>\n <div class=\"gradient-row\">\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().gradientStart }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('darkFooterGradientStart')\"\n ></kendo-colorpicker>\n </div>\n <div class=\"color-field gradient-preview-col\">\n <span class=\"color-label\" aria-hidden=\"true\"> </span>\n <div\n class=\"gradient-preview\"\n [style.background]=\"'linear-gradient(to right, ' + settingsForm.get('darkFooterGradientStart')?.value + ', ' + settingsForm.get('darkFooterGradientEnd')?.value + ')'\"\n ></div>\n </div>\n <div class=\"color-field\">\n <label class=\"color-label\">{{ messages().gradientEnd }}</label>\n <kendo-colorpicker\n [formControl]=\"colorCtrl('darkFooterGradientEnd')\"\n ></kendo-colorpicker>\n </div>\n </div>\n </div>\n </kendo-card-body>\n }\n </kendo-card>\n\n <div class=\"form-actions-bar\">\n <button\n kendoButton\n type=\"button\"\n themeColor=\"base\"\n [svgIcon]=\"undoIcon\"\n [disabled]=\"saving()\"\n (click)=\"onResetDefaults()\"\n >\n {{ messages().resetDefaults }}\n </button>\n <button\n kendoButton\n type=\"submit\"\n themeColor=\"primary\"\n [svgIcon]=\"saveIcon\"\n [disabled]=\"!settingsForm.valid || saving()\"\n >\n {{ messages().save }}\n </button>\n </div>\n </form>\n</div>\n","import { Routes } from '@angular/router';\n\n/**\n * Routes for the branding/settings UI. Mount under any path:\n *\n * ```ts\n * { path: 'settings', canActivate: [adminGuard], children: BRANDING_ROUTES }\n * ```\n *\n * The `SettingsPageComponent` is lazy-loaded on first navigation regardless of\n * how the host wires this — `loadComponent` returns a dynamic import.\n */\nexport const BRANDING_ROUTES: Routes = [\n {\n path: '',\n loadComponent: () =>\n import('./settings-page.component').then(\n (m) => m.SettingsPageComponent,\n ),\n },\n];\n","/*\n * Public API Surface of @meshmakers/octo-ui/branding-settings\n *\n * Heavy admin-only branding editor. Lives in its own secondary entry point so\n * host applications that only need the lightweight branding pieces (header\n * logo, theme switcher, services) — exposed by the primary `@meshmakers/octo-ui`\n * entry — do not pay for the form/Kendo modules pulled in by the editor itself.\n */\nexport * from './settings-page.component';\nexport * from './branding-settings.messages';\nexport * from './branding-settings.routes';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;AA8BA,MAAM,eAAe,GAAa;AAChC,IAAA,IAAI,EAAE,IAAI;AACV,IAAA,OAAO,EAAE,IAAI;AACb,IAAA,OAAO,EAAE,KAAK;CACf;MAeY,qBAAqB,CAAA;AACvB,IAAA,QAAQ,GAAG,KAAK,CAAC,QAAQ,8EAA4B;AAE7C,IAAA,EAAE,GAAG,MAAM,CAAC,WAAW,CAAC;AACxB,IAAA,QAAQ,GAAG,MAAM,CAAC,kBAAkB,CAAC;AACrC,IAAA,cAAc,GAAG,MAAM,CAAC,cAAc,CAAC;AACvC,IAAA,QAAQ,GAAG,MAAM,CAAC,sBAAsB,CAAC;IAEvC,QAAQ,GAAG,QAAQ;IACnB,QAAQ,GAAG,QAAQ;IACnB,KAAK,GAAG,KAAK;AAEb,IAAA,aAAa,GAA4B;QAC1D,QAAQ;QACR,QAAQ;QACR,SAAS;KACV;AAEkB,IAAA,MAAM,GAAG,MAAM,CAAC,KAAK,6EAAC;AAEtB,IAAA,YAAY;AAEd,IAAA,UAAU,GAAG;AAC5B,QAAA,MAAM,EAAE,MAAM,CAAW,EAAE,GAAG,eAAe,EAAE,CAAC;AAChD,QAAA,MAAM,EAAE,MAAM,CAAW,EAAE,GAAG,eAAe,EAAE,CAAC;AAChD,QAAA,OAAO,EAAE,MAAM,CAAW,EAAE,GAAG,eAAe,EAAE,CAAC;KACD;AAEjC,IAAA,aAAa,GAAG;AAC/B,QAAA,MAAM,EAAE,MAAM,CAAgB,IAAI,CAAC;AACnC,QAAA,MAAM,EAAE,MAAM,CAAgB,IAAI,CAAC;AACnC,QAAA,OAAO,EAAE,MAAM,CAAgB,IAAI,CAAC;KACY;AAElD,IAAA,WAAA,GAAA;QACE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC,QAAQ;AAC/C,QAAA,MAAM,IAAI,GAAG,SAAS,IAAI,UAAU;;;;QAKpC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC;YAChC,OAAO,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC,QAAQ,CAAC;AACrD,YAAA,QAAQ,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAElC,iBAAiB,EAAE,CAAC,UAAU,CAAC,YAAY,EAAE,UAAU,CAAC,QAAQ,CAAC;YACjE,mBAAmB,EAAE,CAAC,UAAU,CAAC,cAAc,EAAE,UAAU,CAAC,QAAQ,CAAC;YACrE,kBAAkB,EAAE,CAAC,UAAU,CAAC,aAAa,EAAE,UAAU,CAAC,QAAQ,CAAC;YACnE,iBAAiB,EAAE,CAAC,UAAU,CAAC,YAAY,EAAE,UAAU,CAAC,QAAQ,CAAC;AACjE,YAAA,oBAAoB,EAAE,CAAC,UAAU,CAAC,eAAe,CAAC;AAClD,YAAA,wBAAwB,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC,UAAU,CAAC;AAChE,YAAA,sBAAsB,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC,QAAQ,CAAC;AAC5D,YAAA,wBAAwB,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC,UAAU,CAAC;AAChE,YAAA,sBAAsB,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC,QAAQ,CAAC;AAE5D,YAAA,gBAAgB,EAAE,CAAC,SAAS,KAAK,IAAI,CAAC;YAEtC,gBAAgB,EAAE,CAAC,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,QAAQ,CAAC;YAC1D,kBAAkB,EAAE,CAAC,IAAI,CAAC,cAAc,EAAE,UAAU,CAAC,QAAQ,CAAC;YAC9D,iBAAiB,EAAE,CAAC,IAAI,CAAC,aAAa,EAAE,UAAU,CAAC,QAAQ,CAAC;YAC5D,gBAAgB,EAAE,CAAC,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,QAAQ,CAAC;AAC1D,YAAA,mBAAmB,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC;AAC3C,YAAA,uBAAuB,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC;AACzD,YAAA,qBAAqB,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC;AACrD,YAAA,uBAAuB,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC;AACzD,YAAA,qBAAqB,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC;AACtD,SAAA,CAAC;IACJ;IAEU,gBAAgB,GAAA;AACxB,QAAA,OAAO,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,kBAAkB,CAAC,EAAE,KAAK,KAAK,IAAI;IAClE;AAEA,IAAA,MAAM,QAAQ,GAAA;AACZ,QAAA,IAAI;AACF,YAAA,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE;QAC5B;QAAE,OAAO,KAAK,EAAE;AACd,YAAA,OAAO,CAAC,KAAK,CAAC,iDAAiD,EAAE,KAAK,CAAC;AACvE,YAAA,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,SAAS,CAAC;YACxD;QACF;QACA,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;IACxC;AAEiB,IAAA,YAAY,GAAG,MAAM,CAAsB,IAAI,mFAAC;AAEvD,IAAA,UAAU,CAAC,IAAkB,EAAA;AACrC,QAAA,OAAO,IAAI,CAAC,YAAY,EAAE,KAAK,IAAI;IACrC;IAEU,cAAc,CAAC,IAAkB,EAAE,KAAY,EAAA;AACvD,QAAA,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM;AAC1B,QAAA,IAAI,EAAE,KAAK,YAAY,gBAAgB,CAAC;YAAE;QAC1C,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,IAAI;AACrC,QAAA,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC;AAC9B,QAAA,KAAK,CAAC,KAAK,GAAG,EAAE;IAClB;IAEU,cAAc,CAAC,IAAkB,EAAE,KAAgB,EAAA;QAC3D,KAAK,CAAC,cAAc,EAAE;QACtB,IAAI,KAAK,CAAC,YAAY;AAAE,YAAA,KAAK,CAAC,YAAY,CAAC,UAAU,GAAG,MAAM;AAC9D,QAAA,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC;IAC7B;AAEU,IAAA,eAAe,CAAC,IAAkB,EAAA;AAC1C,QAAA,IAAI,IAAI,CAAC,YAAY,EAAE,KAAK,IAAI;AAAE,YAAA,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC;IAC/D;IAEU,aAAa,CAAC,IAAkB,EAAE,KAAgB,EAAA;QAC1D,KAAK,CAAC,cAAc,EAAE;AACtB,QAAA,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC;QAC3B,MAAM,IAAI,GAAG,KAAK,CAAC,YAAY,EAAE,KAAK,GAAG,CAAC,CAAC;QAC3C,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE;AAC9C,QAAA,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC;IAChC;IAEQ,aAAa,CAAC,IAAkB,EAAE,IAAiB,EAAA;AACzD,QAAA,IAAI,CAAC,IAAI;YAAE;AACX,QAAA,MAAM,MAAM,GAAG,IAAI,UAAU,EAAE;AAC/B,QAAA,MAAM,CAAC,MAAM,GAAG,MAAW;AACzB,YAAA,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM;AAC5B,YAAA,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC;gBACxB,IAAI;AACJ,gBAAA,OAAO,EAAE,OAAO,MAAM,KAAK,QAAQ,GAAG,MAAM,GAAG,IAAI;AACnD,gBAAA,OAAO,EAAE,KAAK;AACf,aAAA,CAAC;AACJ,QAAA,CAAC;AACD,QAAA,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC;IAC5B;AAEU,IAAA,SAAS,CAAC,IAAkB,EAAA;QACpC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACvE,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC;IACpC;AAEU,IAAA,UAAU,CAAC,IAAkB,EAAA;AACrC,QAAA,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,IAAI,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE;IACtE;AAEU,IAAA,MAAM,QAAQ,GAAA;AACtB,QAAA,IAAI,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE;AAC7B,YAAA,IAAI,CAAC,YAAY,CAAC,gBAAgB,EAAE;YACpC;QACF;AACA,QAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;AACrB,QAAA,IAAI;AACF,YAAA,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;AACvB,gBAAA,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;AACjC,gBAAA,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC;AACnC,gBAAA,cAAc,EAAE,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC;AAC9C,gBAAA,cAAc,EAAE,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC;AAC9C,gBAAA,WAAW,EAAE,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC;AAC5C,gBAAA,UAAU,EAAE,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC;AACzC,gBAAA,SAAS,EAAE,IAAI,CAAC,gBAAgB,EAAE,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,GAAG,IAAI;AACzE,aAAA,CAAC;AACF,YAAA,IAAI,CAAC,cAAc,CAAC,eAAe,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,WAAW,CAAC;YAChE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;AACtC,YAAA,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE;QACpC;QAAE,OAAO,KAAK,EAAE;AACd,YAAA,OAAO,CAAC,KAAK,CAAC,iDAAiD,EAAE,KAAK,CAAC;AACvE,YAAA,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,SAAS,CAAC;QAC1D;gBAAU;AACR,YAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;QACxB;IACF;AAEU,IAAA,MAAM,eAAe,GAAA;AAC7B,QAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;AACrB,QAAA,IAAI;AACF,YAAA,MAAM,IAAI,CAAC,QAAQ,CAAC,eAAe,EAAE;YACrC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;AACtC,YAAA,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE;AAClC,YAAA,IAAI,CAAC,cAAc,CAAC,eAAe,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,YAAY,CAAC;QACnE;QAAE,OAAO,KAAK,EAAE;AACd,YAAA,OAAO,CAAC,KAAK,CAAC,kDAAkD,EAAE,KAAK,CAAC;AACxE,YAAA,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,SAAS,CAAC;QAC1D;gBAAU;AACR,YAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;QACxB;IACF;AAEQ,IAAA,OAAO,CAAC,IAAkB,EAAA;QAChC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC;QACjD,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC;QACjD,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC;AAE/C,QAAA,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,aAAa,EAAE;AACrC,YAAA,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,eAAe,EAAE,CAAC;QACnD;AAEA,QAAA,MAAM,IAAI,GACR,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU;AAEvE,QAAA,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC;YAC3B,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ;AACvB,YAAA,gBAAgB,EAAE,IAAI,CAAC,SAAS,KAAK,IAAI;AAEzC,YAAA,iBAAiB,EAAE,IAAI,CAAC,UAAU,CAAC,YAAY;AAC/C,YAAA,mBAAmB,EAAE,IAAI,CAAC,UAAU,CAAC,cAAc;AACnD,YAAA,kBAAkB,EAAE,IAAI,CAAC,UAAU,CAAC,aAAa;AACjD,YAAA,iBAAiB,EAAE,IAAI,CAAC,UAAU,CAAC,YAAY;AAC/C,YAAA,oBAAoB,EAAE,IAAI,CAAC,UAAU,CAAC,eAAe;AACrD,YAAA,wBAAwB,EAAE,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,UAAU;AACnE,YAAA,sBAAsB,EAAE,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,QAAQ;AAC/D,YAAA,wBAAwB,EAAE,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,UAAU;AACnE,YAAA,sBAAsB,EAAE,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,QAAQ;YAE/D,gBAAgB,EAAE,IAAI,CAAC,YAAY;YACnC,kBAAkB,EAAE,IAAI,CAAC,cAAc;YACvC,iBAAiB,EAAE,IAAI,CAAC,aAAa;YACrC,gBAAgB,EAAE,IAAI,CAAC,YAAY;YACnC,mBAAmB,EAAE,IAAI,CAAC,eAAe;AACzC,YAAA,uBAAuB,EAAE,IAAI,CAAC,cAAc,CAAC,UAAU;AACvD,YAAA,qBAAqB,EAAE,IAAI,CAAC,cAAc,CAAC,QAAQ;AACnD,YAAA,uBAAuB,EAAE,IAAI,CAAC,cAAc,CAAC,UAAU;AACvD,YAAA,qBAAqB,EAAE,IAAI,CAAC,cAAc,CAAC,QAAQ;AACpD,SAAA,CAAC;IACJ;AAEQ,IAAA,eAAe,CAAC,IAAkB,EAAA;QACxC,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE;QACrC,IAAI,KAAK,CAAC,IAAI;YAAE,OAAO,KAAK,CAAC,IAAI;QACjC,IAAI,KAAK,CAAC,OAAO;AAAE,YAAA,OAAO,IAAI;AAC9B,QAAA,OAAO,SAAS;IAClB;AAEQ,IAAA,QAAQ,CAAC,IAAY,EAAA;AAC3B,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK;AAChD,QAAA,OAAO,OAAO,KAAK,KAAK,QAAQ,GAAG,KAAK,GAAG,EAAE;IAC/C;AAEQ,IAAA,eAAe,CAAC,IAAsB,EAAA;QAC5C,OAAO;YACL,YAAY,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA,EAAG,IAAI,cAAc,CAAC;YAClD,cAAc,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA,EAAG,IAAI,gBAAgB,CAAC;YACtD,aAAa,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA,EAAG,IAAI,eAAe,CAAC;YACpD,YAAY,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA,EAAG,IAAI,cAAc,CAAC;YAClD,eAAe,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA,EAAG,IAAI,iBAAiB,CAAC;AACxD,YAAA,cAAc,EAAE;gBACd,UAAU,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA,EAAG,IAAI,qBAAqB,CAAC;gBACvD,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA,EAAG,IAAI,mBAAmB,CAAC;AACpD,aAAA;AACD,YAAA,cAAc,EAAE;gBACd,UAAU,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA,EAAG,IAAI,qBAAqB,CAAC;gBACvD,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA,EAAG,IAAI,mBAAmB,CAAC;AACpD,aAAA;SACF;IACH;;;;;;;AAQU,IAAA,SAAS,CAAC,WAAmB,EAAA;QACrC,OAAO,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,CAAwB;IAClE;AAEA;;;;;AAKG;AACO,IAAA,SAAS,CAAC,IAAkB,EAAA;AACpC,QAAA,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,EAAE;QACzB,QAAQ,IAAI;AACV,YAAA,KAAK,QAAQ;gBACX,OAAO,CAAC,CAAC,UAAU;AACrB,YAAA,KAAK,QAAQ;gBACX,OAAO,CAAC,CAAC,UAAU;AACrB,YAAA,KAAK,SAAS;gBACZ,OAAO,CAAC,CAAC,WAAW;;IAE1B;uGApRW,qBAAqB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAArB,qBAAqB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,sBAAA,EAAA,MAAA,EAAA,EAAA,QAAA,EAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,ECjDlC,u/XAqSA,EAAA,MAAA,EAAA,CAAA,+sFAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,ED7PI,YAAY,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,gBAAA,EAAA,QAAA,EAAA,qBAAA,EAAA,MAAA,EAAA,CAAA,OAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,eAAA,EAAA,QAAA,EAAA,cAAA,EAAA,MAAA,EAAA,CAAA,aAAA,EAAA,SAAA,EAAA,UAAA,EAAA,SAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,MAAA,EAAA,cAAA,EAAA,cAAA,EAAA,UAAA,CAAA,EAAA,OAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,aAAA,CAAA,EAAA,QAAA,EAAA,CAAA,aAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,kBAAA,EAAA,QAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,CAAA,WAAA,EAAA,aAAA,EAAA,YAAA,EAAA,SAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,cAAA,EAAA,QAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,CAAA,OAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,oBAAA,EAAA,QAAA,EAAA,mBAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,cAAA,EAAA,YAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,eAAA,EAAA,iBAAA,EAAA,kBAAA,EAAA,MAAA,EAAA,WAAA,EAAA,SAAA,EAAA,eAAA,EAAA,kBAAA,EAAA,aAAA,EAAA,UAAA,EAAA,SAAA,EAAA,eAAA,EAAA,MAAA,EAAA,SAAA,EAAA,UAAA,CAAA,EAAA,OAAA,EAAA,CAAA,aAAA,EAAA,MAAA,EAAA,OAAA,EAAA,OAAA,EAAA,MAAA,EAAA,QAAA,EAAA,kBAAA,EAAA,kBAAA,EAAA,kBAAA,CAAA,EAAA,QAAA,EAAA,CAAA,kBAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EACZ,WAAW,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,cAAA,EAAA,QAAA,EAAA,aAAA,EAAA,MAAA,EAAA,CAAA,MAAA,EAAA,KAAA,EAAA,UAAA,EAAA,eAAA,EAAA,eAAA,CAAA,EAAA,QAAA,EAAA,CAAA,YAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EACX,aAAa,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,eAAA,EAAA,QAAA,EAAA,qBAAA,EAAA,MAAA,EAAA,CAAA,WAAA,EAAA,YAAA,EAAA,WAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,WAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,SAAA,EAAA,UAAA,EAAA,YAAA,EAAA,SAAA,EAAA,SAAA,EAAA,MAAA,CAAA,EAAA,OAAA,EAAA,CAAA,gBAAA,EAAA,OAAA,CAAA,EAAA,QAAA,EAAA,CAAA,aAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EACb,YAAY,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,aAAA,EAAA,QAAA,EAAA,YAAA,EAAA,MAAA,EAAA,CAAA,aAAA,EAAA,OAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,iBAAA,EAAA,QAAA,EAAA,iBAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,mBAAA,EAAA,QAAA,EAAA,mBAAA,EAAA,EAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EACZ,mBAAmB,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,aAAA,EAAA,QAAA,EAAA,8CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,oBAAA,EAAA,QAAA,EAAA,8MAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,eAAA,EAAA,QAAA,EAAA,2CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,oBAAA,EAAA,QAAA,EAAA,sGAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,iBAAA,EAAA,QAAA,EAAA,wIAAA,EAAA,MAAA,EAAA,CAAA,UAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,oBAAA,EAAA,QAAA,EAAA,eAAA,EAAA,MAAA,EAAA,CAAA,aAAA,EAAA,UAAA,EAAA,SAAA,CAAA,EAAA,OAAA,EAAA,CAAA,eAAA,CAAA,EAAA,QAAA,EAAA,CAAA,QAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,kBAAA,EAAA,QAAA,EAAA,aAAA,EAAA,MAAA,EAAA,CAAA,WAAA,CAAA,EAAA,OAAA,EAAA,CAAA,UAAA,CAAA,EAAA,QAAA,EAAA,CAAA,QAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,eAAA,EAAA,QAAA,EAAA,mBAAA,EAAA,MAAA,EAAA,CAAA,iBAAA,EAAA,UAAA,EAAA,SAAA,CAAA,EAAA,OAAA,EAAA,CAAA,eAAA,CAAA,EAAA,CAAA,EAAA,CAAA;;2FAKV,qBAAqB,EAAA,UAAA,EAAA,CAAA;kBAbjC,SAAS;+BACE,sBAAsB,EAAA,UAAA,EACpB,IAAI,EAAA,OAAA,EACP;wBACP,YAAY;wBACZ,WAAW;wBACX,aAAa;wBACb,YAAY;wBACZ,mBAAmB;AACpB,qBAAA,EAAA,QAAA,EAAA,u/XAAA,EAAA,MAAA,EAAA,CAAA,+sFAAA,CAAA,EAAA;;;;;;;;AE3CH;;;;;;;;;AASG;AACI,MAAM,eAAe,GAAW;AACrC,IAAA;AACE,QAAA,IAAI,EAAE,EAAE;AACR,QAAA,aAAa,EAAE,MACb,sEAAmC,CAAC,IAAI,CACtC,CAAC,CAAC,KAAK,CAAC,CAAC,qBAAqB,CAC/B;AACJ,KAAA;;;ACnBH;;;;;;;AAOG;;ACPH;;AAEG;;;;"}
|