@meshmakers/octo-ui 3.3.910 → 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 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\">&nbsp;</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\">&nbsp;</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\">&nbsp;</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\">&nbsp;</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\">&nbsp;</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\">&nbsp;</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\">&nbsp;</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\">&nbsp;</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\">&nbsp;</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\">&nbsp;</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\">&nbsp;</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\">&nbsp;</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":"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"}