@doug-williamson/ng-rhombus 0.0.16

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.
@@ -0,0 +1,945 @@
1
+ import * as i1$3 from '@angular/cdk/layout';
2
+ import { Breakpoints } from '@angular/cdk/layout';
3
+ import * as i0 from '@angular/core';
4
+ import { inject, Injectable, PLATFORM_ID, signal, computed, input, output, EventEmitter, Output, Input, Component, ViewChild, effect } from '@angular/core';
5
+ import * as i3$1 from '@angular/material/sidenav';
6
+ import { MatSidenavModule } from '@angular/material/sidenav';
7
+ import { BehaviorSubject, filter, of, Subject, takeUntil } from 'rxjs';
8
+ import * as i1$1 from '@angular/material/button';
9
+ import { MatButtonModule } from '@angular/material/button';
10
+ import * as i2 from '@angular/material/icon';
11
+ import { MatIconModule } from '@angular/material/icon';
12
+ import * as i3 from '@angular/material/menu';
13
+ import { MatMenuModule } from '@angular/material/menu';
14
+ import * as i2$2 from '@angular/material/toolbar';
15
+ import { MatToolbar, MatToolbarModule } from '@angular/material/toolbar';
16
+ import { toSignal } from '@angular/core/rxjs-interop';
17
+ import * as i1 from '@angular/router';
18
+ import { NavigationEnd, Router, RouterModule, RouterLink } from '@angular/router';
19
+ import { MatSnackBar } from '@angular/material/snack-bar';
20
+ import { Auth, authState, signInWithEmailAndPassword } from '@angular/fire/auth';
21
+ import { switchMap } from 'rxjs/operators';
22
+ import * as i4$2 from '@angular/common';
23
+ import { isPlatformBrowser, TitleCasePipe, CommonModule, DatePipe } from '@angular/common';
24
+ import * as i4 from '@angular/material/progress-bar';
25
+ import { MatProgressBarModule } from '@angular/material/progress-bar';
26
+ import * as i1$2 from '@angular/material/list';
27
+ import { MatListModule } from '@angular/material/list';
28
+ import { MatTabsModule } from '@angular/material/tabs';
29
+ import * as i6 from '@angular/forms';
30
+ import { Validators, ReactiveFormsModule, FormBuilder } from '@angular/forms';
31
+ import * as i4$1 from '@angular/material/card';
32
+ import { MatCardModule } from '@angular/material/card';
33
+ import * as i5 from '@angular/material/form-field';
34
+ import { MatFormFieldModule } from '@angular/material/form-field';
35
+ import * as i7 from '@angular/material/input';
36
+ import { MatInputModule } from '@angular/material/input';
37
+ import * as i1$4 from '@angular/material/progress-spinner';
38
+ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
39
+ import * as i3$2 from '@angular/material/table';
40
+ import { MatTableModule } from '@angular/material/table';
41
+ import * as i2$1 from '@angular/material/dialog';
42
+ import { MatDialog, MatDialogModule, MAT_DIALOG_DATA } from '@angular/material/dialog';
43
+ import { Firestore, collection, getDocs, doc, getDoc, query, orderBy, limit, setDoc, updateDoc, deleteDoc, serverTimestamp, addDoc } from '@angular/fire/firestore';
44
+ import { Storage, ref, uploadBytesResumable, getDownloadURL } from '@angular/fire/storage';
45
+ import { deleteObject } from 'firebase/storage';
46
+ import * as i5$1 from '@angular/cdk/text-field';
47
+ import { TextFieldModule } from '@angular/cdk/text-field';
48
+ import { MatDividerModule } from '@angular/material/divider';
49
+ import { MarkdownComponent } from 'ngx-markdown';
50
+ import * as i4$3 from '@angular/material/select';
51
+ import { MatSelectModule } from '@angular/material/select';
52
+
53
+ var ThemeEnum;
54
+ (function (ThemeEnum) {
55
+ ThemeEnum["LIGHT"] = "light";
56
+ ThemeEnum["DARK"] = "dark";
57
+ })(ThemeEnum || (ThemeEnum = {}));
58
+ class WrapperService {
59
+ // triggerCreateNew = toSignal(this._triggerCreateNew.asObservable());
60
+ constructor(router, activatedRoute) {
61
+ this.router = router;
62
+ this.activatedRoute = activatedRoute;
63
+ this.snackbar = inject(MatSnackBar);
64
+ this._darkModeSubject = new BehaviorSubject(false);
65
+ this.darkMode = toSignal(this._darkModeSubject.asObservable(), { initialValue: false });
66
+ this._breadcrumbs = new BehaviorSubject([]);
67
+ this.breadcrumbs = toSignal(this._breadcrumbs.asObservable(), { initialValue: undefined });
68
+ this._triggerCreateNew = new BehaviorSubject(undefined);
69
+ this.triggerCreateNew = this._triggerCreateNew.asObservable();
70
+ this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(event => {
71
+ //set breadcrumbs
72
+ this._breadcrumbs.next(this.getBreadcrumbs(this.activatedRoute.root));
73
+ });
74
+ }
75
+ clickedCreateNew() {
76
+ this._triggerCreateNew.next(undefined);
77
+ }
78
+ getBreadcrumbs(route, url = "", breadcrumbs = []) {
79
+ const ROUTE_DATA_BREADCRUMB = 'breadcrumb';
80
+ //get the child routes
81
+ let children = route.children;
82
+ if (children.length === 0) {
83
+ // return breadcrumbs;
84
+ }
85
+ //iterate over each children
86
+ for (let child of children) {
87
+ //verify primary route
88
+ // if (child.outlet !== PRIMARY_OUTLET || child.snapshot.url.length==0) {
89
+ // continue;
90
+ // }
91
+ //verify the custom data property "breadcrumb" is specified on the route
92
+ // if (!child.snapshot.data.hasOwnProperty(ROUTE_DATA_BREADCRUMB)) {
93
+ // return this.getBreadcrumbs(child, url, breadcrumbs);
94
+ // }
95
+ //get the route's URL segment
96
+ let routeURL = child.snapshot.url.map(segment => segment.path).join("/");
97
+ //append route URL to URL
98
+ url += `${routeURL}`;
99
+ //add breadcrumb
100
+ let breadcrumb = {
101
+ label: child.snapshot.data[ROUTE_DATA_BREADCRUMB],
102
+ url: url
103
+ };
104
+ breadcrumbs.push(breadcrumb);
105
+ //recursive
106
+ return this.getBreadcrumbs(child, url, breadcrumbs);
107
+ }
108
+ return breadcrumbs;
109
+ }
110
+ openSnackbar(message) {
111
+ this.snackbar.open(message, undefined, { duration: 3000 });
112
+ }
113
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: WrapperService, deps: [{ token: i1.Router }, { token: i1.ActivatedRoute }], target: i0.ɵɵFactoryTarget.Injectable }); }
114
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: WrapperService, providedIn: 'root' }); }
115
+ }
116
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: WrapperService, decorators: [{
117
+ type: Injectable,
118
+ args: [{
119
+ providedIn: 'root'
120
+ }]
121
+ }], ctorParameters: () => [{ type: i1.Router }, { type: i1.ActivatedRoute }] });
122
+
123
+ class NgRhombusAuthenticationService {
124
+ constructor() {
125
+ this.firebaseAuth = inject(Auth);
126
+ this.currentUser$ = authState(this.firebaseAuth);
127
+ this.currentUserProfile$ = this.currentUser$.pipe(switchMap((user) => {
128
+ return user?.uid ? of(user) : of(null);
129
+ }));
130
+ this.currentUserProfile = toSignal(this.currentUserProfile$);
131
+ }
132
+ login(email, password) {
133
+ return signInWithEmailAndPassword(this.firebaseAuth, email, password).then(() => { });
134
+ }
135
+ logout() { return this.firebaseAuth.signOut(); }
136
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusAuthenticationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
137
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusAuthenticationService, providedIn: 'root' }); }
138
+ }
139
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusAuthenticationService, decorators: [{
140
+ type: Injectable,
141
+ args: [{
142
+ providedIn: 'root'
143
+ }]
144
+ }] });
145
+
146
+ class ThemeService {
147
+ getThemes() {
148
+ return this.themes;
149
+ }
150
+ setTheme(theme) {
151
+ if (isPlatformBrowser(this.platformId)) {
152
+ this.appTheme.set(theme);
153
+ const colorScheme = theme === 'system' ? 'light dark' : theme;
154
+ document.body.style.setProperty('color-scheme', colorScheme);
155
+ this.setThemeInLocalStorage(theme);
156
+ }
157
+ }
158
+ setThemeInLocalStorage(theme) {
159
+ if (isPlatformBrowser(this.platformId)) {
160
+ localStorage.setItem(this.PREFERRED_THEME_COOKIE, theme);
161
+ }
162
+ }
163
+ getThemeFromLocalStorage() {
164
+ return isPlatformBrowser(this.platformId) ? localStorage.getItem(this.PREFERRED_THEME_COOKIE) : "";
165
+ }
166
+ constructor() {
167
+ this.platformId = inject(PLATFORM_ID);
168
+ this.appTheme = signal('system', ...(ngDevMode ? [{ debugName: "appTheme" }] : []));
169
+ this.PREFERRED_THEME_COOKIE = 'preferred-theme';
170
+ this.themes = [
171
+ { name: 'light', icon: 'light_mode' },
172
+ { name: 'dark', icon: 'dark_mode' },
173
+ { name: 'system', icon: 'desktop_windows' },
174
+ ];
175
+ this.selectedTheme = computed(() => this.themes.find((t) => t.name === this.appTheme()), ...(ngDevMode ? [{ debugName: "selectedTheme" }] : []));
176
+ this.setTheme(this.getThemeFromLocalStorage());
177
+ }
178
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: ThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
179
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: ThemeService, providedIn: 'root' }); }
180
+ }
181
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: ThemeService, decorators: [{
182
+ type: Injectable,
183
+ args: [{
184
+ providedIn: 'root',
185
+ }]
186
+ }], ctorParameters: () => [] });
187
+
188
+ class NgRhombusHeaderComponent {
189
+ constructor() {
190
+ this.isLoading = input(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
191
+ this.wrapperService = inject(WrapperService);
192
+ this.authService = inject(NgRhombusAuthenticationService);
193
+ this.themeService = inject(ThemeService);
194
+ this.router = inject(Router);
195
+ this.logOut = output();
196
+ this.darkMode = this.wrapperService.darkMode;
197
+ this.user = this.authService.currentUserProfile;
198
+ this.isMobile = false;
199
+ this.menuToggled = new EventEmitter;
200
+ }
201
+ toggleSidebar() {
202
+ this.menuToggled.emit();
203
+ }
204
+ logout() {
205
+ this.logOut.emit();
206
+ }
207
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
208
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.0", type: NgRhombusHeaderComponent, isStandalone: true, selector: "ng-rhombus-header", inputs: { isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: false, transformFunction: null }, isMobile: { classPropertyName: "isMobile", publicName: "isMobile", isSignal: false, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { logOut: "logOut", menuToggled: "menuToggled" }, ngImport: i0, template: "<mat-toolbar class=\"header-theme relative\">\r\n @if (isMobile) {\r\n <button mat-icon-button (click)=\"toggleSidebar()\" color=\"red\">\r\n <mat-icon>menu</mat-icon>\r\n </button>\r\n }\r\n\r\n <span class=\"flex-1\">{{ title }}</span>\r\n\r\n <button class=\"mr-2\" mat-icon-button [mat-menu-trigger-for]=\"themeMenu\">\r\n <mat-icon>{{ themeService.selectedTheme()?.icon }}</mat-icon>\r\n </button>\r\n <mat-menu #themeMenu=\"matMenu\">\r\n @for (theme of themeService.getThemes(); track theme.name) {\r\n <button [class.selected-theme]=\"themeService.selectedTheme()?.name === theme.name\" mat-menu-item\r\n (click)=\"themeService.setTheme(theme.name)\">\r\n <mat-icon>{{ theme.icon }}</mat-icon>\r\n <span>{{ theme.name | titlecase }}</span>\r\n </button>\r\n }\r\n </mat-menu>\r\n\r\n @if (user()) {\r\n <button mat-raised-button color=\"primary\" class=\"logout-button\" (click)=\"logout()\">\r\n Logout\r\n </button>\r\n }\r\n</mat-toolbar>\r\n@if (isLoading()) {\r\n<mat-progress-bar class=\"!absolute top-[64px] z-10\" mode=\"indeterminate\" />\r\n}", styles: [".header-theme{--mat-toolbar-container-background-color: var(--mat-sys-primary);--mat-toolbar-container-text-color: var(--mat-sys-on-primary);--mat-icon-button-icon-color: var(--mat-sys-on-primary);box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.selected-theme{--mat-menu-item-icon-color: var(--mat-sys-primary);--mat-menu-item-label-text-color: var(--mat-sys-primary)}mat-progress-bar{--mat-progress-bar-active-indicator-color: var(--mat-sys-primary);--mat-progress-bar-track-color: var(--mat-sys-surface-variant)}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i1$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i3.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i3.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i3.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "ngmodule", type: MatProgressBarModule }, { kind: "component", type: i4.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "pipe", type: TitleCasePipe, name: "titlecase" }] }); }
209
+ }
210
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusHeaderComponent, decorators: [{
211
+ type: Component,
212
+ args: [{ selector: 'ng-rhombus-header', imports: [MatButtonModule, MatIconModule, MatMenuModule, MatToolbar, TitleCasePipe, MatProgressBarModule], template: "<mat-toolbar class=\"header-theme relative\">\r\n @if (isMobile) {\r\n <button mat-icon-button (click)=\"toggleSidebar()\" color=\"red\">\r\n <mat-icon>menu</mat-icon>\r\n </button>\r\n }\r\n\r\n <span class=\"flex-1\">{{ title }}</span>\r\n\r\n <button class=\"mr-2\" mat-icon-button [mat-menu-trigger-for]=\"themeMenu\">\r\n <mat-icon>{{ themeService.selectedTheme()?.icon }}</mat-icon>\r\n </button>\r\n <mat-menu #themeMenu=\"matMenu\">\r\n @for (theme of themeService.getThemes(); track theme.name) {\r\n <button [class.selected-theme]=\"themeService.selectedTheme()?.name === theme.name\" mat-menu-item\r\n (click)=\"themeService.setTheme(theme.name)\">\r\n <mat-icon>{{ theme.icon }}</mat-icon>\r\n <span>{{ theme.name | titlecase }}</span>\r\n </button>\r\n }\r\n </mat-menu>\r\n\r\n @if (user()) {\r\n <button mat-raised-button color=\"primary\" class=\"logout-button\" (click)=\"logout()\">\r\n Logout\r\n </button>\r\n }\r\n</mat-toolbar>\r\n@if (isLoading()) {\r\n<mat-progress-bar class=\"!absolute top-[64px] z-10\" mode=\"indeterminate\" />\r\n}", styles: [".header-theme{--mat-toolbar-container-background-color: var(--mat-sys-primary);--mat-toolbar-container-text-color: var(--mat-sys-on-primary);--mat-icon-button-icon-color: var(--mat-sys-on-primary);box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.selected-theme{--mat-menu-item-icon-color: var(--mat-sys-primary);--mat-menu-item-label-text-color: var(--mat-sys-primary)}mat-progress-bar{--mat-progress-bar-active-indicator-color: var(--mat-sys-primary);--mat-progress-bar-track-color: var(--mat-sys-surface-variant)}\n"] }]
213
+ }], propDecorators: { isMobile: [{
214
+ type: Input
215
+ }], title: [{
216
+ type: Input
217
+ }], menuToggled: [{
218
+ type: Output
219
+ }] } });
220
+
221
+ class NgRhombusNavListComponent {
222
+ ngOnInit() {
223
+ }
224
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusNavListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
225
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.0", type: NgRhombusNavListComponent, isStandalone: true, selector: "ng-rhombus-nav-list", inputs: { routeCollection: "routeCollection" }, ngImport: i0, template: "<mat-nav-list>\r\n @for (link of routeCollection; track link) {\r\n <a mat-list-item [routerLink]=\"link.route\" routerLinkActive=\"selected-menu-item\">\r\n <span>{{ link.label }}</span>\r\n </a>\r\n }\r\n</mat-nav-list>", styles: [".selected-menu-item{--mat-list-list-item-container-color: var(--mat-sys-primary);--mat-list-list-item-label-text-color: var(--mat-sys-on-primary);--mat-list-list-item-hover-label-text-color: var(--mat-sys-on-primary);--mat-list-list-item-focus-label-text-color: var(--mat-sys-on-primary)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i1.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "ngmodule", type: MatListModule }, { kind: "component", type: i1$2.MatNavList, selector: "mat-nav-list", exportAs: ["matNavList"] }, { kind: "component", type: i1$2.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "ngmodule", type: MatTabsModule }] }); }
226
+ }
227
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusNavListComponent, decorators: [{
228
+ type: Component,
229
+ args: [{ selector: 'ng-rhombus-nav-list', imports: [CommonModule, RouterModule, MatListModule, MatIconModule, MatTabsModule], template: "<mat-nav-list>\r\n @for (link of routeCollection; track link) {\r\n <a mat-list-item [routerLink]=\"link.route\" routerLinkActive=\"selected-menu-item\">\r\n <span>{{ link.label }}</span>\r\n </a>\r\n }\r\n</mat-nav-list>", styles: [".selected-menu-item{--mat-list-list-item-container-color: var(--mat-sys-primary);--mat-list-list-item-label-text-color: var(--mat-sys-on-primary);--mat-list-list-item-hover-label-text-color: var(--mat-sys-on-primary);--mat-list-list-item-focus-label-text-color: var(--mat-sys-on-primary)}\n"] }]
230
+ }], propDecorators: { routeCollection: [{
231
+ type: Input
232
+ }] } });
233
+
234
+ class NgRhombusWrapperComponent {
235
+ constructor(breakpointObserver, wrapperService) {
236
+ this.breakpointObserver = breakpointObserver;
237
+ this.wrapperService = wrapperService;
238
+ this.clickAddNewEvent = new EventEmitter();
239
+ this.destroyed = new Subject();
240
+ this.isMobile = false;
241
+ this.isLoading = input(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
242
+ this.logout = output();
243
+ this.breadcrumbs = computed(() => { return this.wrapperService.breadcrumbs(); }, ...(ngDevMode ? [{ debugName: "breadcrumbs" }] : []));
244
+ this.fillerContent = Array.from({ length: 50 }, () => `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
245
+ labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
246
+ laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in
247
+ voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
248
+ cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`);
249
+ this.breakpointObserver
250
+ .observe([Breakpoints.XSmall])
251
+ .pipe(takeUntil(this.destroyed))
252
+ .subscribe(result => {
253
+ this.isMobile = result.matches;
254
+ });
255
+ }
256
+ ngOnInit() {
257
+ }
258
+ ngOnDestroy() {
259
+ this.destroyed.next();
260
+ this.destroyed.complete();
261
+ }
262
+ clickedCreateNew() {
263
+ this.clickAddNewEvent.emit();
264
+ }
265
+ logOut() {
266
+ this.logout.emit();
267
+ }
268
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusWrapperComponent, deps: [{ token: i1$3.BreakpointObserver }, { token: WrapperService }], target: i0.ɵɵFactoryTarget.Component }); }
269
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.0", type: NgRhombusWrapperComponent, isStandalone: true, selector: "ng-rhombus-wrapper", inputs: { isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: false, isRequired: false, transformFunction: null }, routeCollection: { classPropertyName: "routeCollection", publicName: "routeCollection", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { clickAddNewEvent: "clickAddNewEvent", logout: "logout" }, providers: [WrapperService], ngImport: i0, template: "<ng-rhombus-header class=\"header-wrapper\" [isMobile]=\"isMobile\" [title]=\"title\" (menuToggled)=\"sidenav.toggle()\"\r\n (logOut)=\"logOut()\" [isLoading]=\"isLoading()\"></ng-rhombus-header>\r\n<mat-sidenav-container>\r\n <mat-sidenav #sidenav [opened]=\"!isMobile\" [mode]=\"isMobile ? 'over' : 'side'\">\r\n <div class=\"nav-list-wrapper\">\r\n <ng-rhombus-nav-list [routeCollection]=\"routeCollection\"></ng-rhombus-nav-list>\r\n </div>\r\n </mat-sidenav>\r\n <mat-sidenav-content class=\"main-sidenav-content\">\r\n <!-- <mat-toolbar class=\"header-theme\">\r\n <span class=\"wrapper-flex\">\r\n <ng-container *ngFor=\"let breadcrumb of breadcrumbs()\">\r\n {{ breadcrumb.label }}\r\n </ng-container>\r\n </span>\r\n <button mat-icon-button (click)=\"clickedCreateNew()\">\r\n <mat-icon>add</mat-icon>\r\n </button>\r\n </mat-toolbar> -->\r\n <router-outlet #o=\"outlet\"></router-outlet>\r\n </mat-sidenav-content>\r\n</mat-sidenav-container>", styles: [":host{position:relative}.main-sidenav-content{overflow:hidden}mat-sidenav-container{height:calc(100vh - 64px)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatSidenavModule }, { kind: "component", type: i3$1.MatSidenav, selector: "mat-sidenav", inputs: ["fixedInViewport", "fixedTopGap", "fixedBottomGap"], exportAs: ["matSidenav"] }, { kind: "component", type: i3$1.MatSidenavContainer, selector: "mat-sidenav-container", exportAs: ["matSidenavContainer"] }, { kind: "component", type: i3$1.MatSidenavContent, selector: "mat-sidenav-content" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "ngmodule", type: MatListModule }, { kind: "ngmodule", type: MatProgressBarModule }, { kind: "ngmodule", type: MatToolbarModule }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "component", type: NgRhombusHeaderComponent, selector: "ng-rhombus-header", inputs: ["isLoading", "isMobile", "title"], outputs: ["logOut", "menuToggled"] }, { kind: "component", type: NgRhombusNavListComponent, selector: "ng-rhombus-nav-list", inputs: ["routeCollection"] }] }); }
270
+ }
271
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusWrapperComponent, decorators: [{
272
+ type: Component,
273
+ args: [{ selector: 'ng-rhombus-wrapper', imports: [CommonModule, MatSidenavModule, MatButtonModule, MatIconModule, MatListModule, MatProgressBarModule, MatToolbarModule, RouterModule, NgRhombusHeaderComponent, NgRhombusNavListComponent], providers: [WrapperService], template: "<ng-rhombus-header class=\"header-wrapper\" [isMobile]=\"isMobile\" [title]=\"title\" (menuToggled)=\"sidenav.toggle()\"\r\n (logOut)=\"logOut()\" [isLoading]=\"isLoading()\"></ng-rhombus-header>\r\n<mat-sidenav-container>\r\n <mat-sidenav #sidenav [opened]=\"!isMobile\" [mode]=\"isMobile ? 'over' : 'side'\">\r\n <div class=\"nav-list-wrapper\">\r\n <ng-rhombus-nav-list [routeCollection]=\"routeCollection\"></ng-rhombus-nav-list>\r\n </div>\r\n </mat-sidenav>\r\n <mat-sidenav-content class=\"main-sidenav-content\">\r\n <!-- <mat-toolbar class=\"header-theme\">\r\n <span class=\"wrapper-flex\">\r\n <ng-container *ngFor=\"let breadcrumb of breadcrumbs()\">\r\n {{ breadcrumb.label }}\r\n </ng-container>\r\n </span>\r\n <button mat-icon-button (click)=\"clickedCreateNew()\">\r\n <mat-icon>add</mat-icon>\r\n </button>\r\n </mat-toolbar> -->\r\n <router-outlet #o=\"outlet\"></router-outlet>\r\n </mat-sidenav-content>\r\n</mat-sidenav-container>", styles: [":host{position:relative}.main-sidenav-content{overflow:hidden}mat-sidenav-container{height:calc(100vh - 64px)}\n"] }]
274
+ }], ctorParameters: () => [{ type: i1$3.BreakpointObserver }, { type: WrapperService }], propDecorators: { clickAddNewEvent: [{
275
+ type: Output
276
+ }], title: [{
277
+ type: Input
278
+ }], routeCollection: [{
279
+ type: Input
280
+ }] } });
281
+
282
+ class NgRhombusLoadingContainerComponent {
283
+ constructor() {
284
+ this.loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
285
+ this.size = input(40, ...(ngDevMode ? [{ debugName: "size" }] : []));
286
+ }
287
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusLoadingContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
288
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.0", type: NgRhombusLoadingContainerComponent, isStandalone: true, selector: "ng-rhombus-loading-container", inputs: { loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (loading()) {\r\n<mat-progress-spinner class=\"loading-spinner\" [diameter]=\"size()\" mode=\"indeterminate\" />\r\n} @else {\r\n<ng-content />\r\n}", styles: [":host{display:block;position:relative}.loading-spinner{position:absolute;top:50%;left:50%;translate:-50% -50%}\n"], dependencies: [{ kind: "ngmodule", type: MatProgressSpinnerModule }, { kind: "component", type: i1$4.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }] }); }
289
+ }
290
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusLoadingContainerComponent, decorators: [{
291
+ type: Component,
292
+ args: [{ selector: 'ng-rhombus-loading-container', imports: [MatProgressSpinnerModule], template: "@if (loading()) {\r\n<mat-progress-spinner class=\"loading-spinner\" [diameter]=\"size()\" mode=\"indeterminate\" />\r\n} @else {\r\n<ng-content />\r\n}", styles: [":host{display:block;position:relative}.loading-spinner{position:absolute;top:50%;left:50%;translate:-50% -50%}\n"] }]
293
+ }] });
294
+
295
+ class NgRhombusLoginComponent {
296
+ constructor(fb, router) {
297
+ this.fb = fb;
298
+ this.router = router;
299
+ this.authorizing = false;
300
+ this.title = input('', ...(ngDevMode ? [{ debugName: "title" }] : []));
301
+ this.loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
302
+ this.onSubmit = output();
303
+ this.hidePassword = signal(true, ...(ngDevMode ? [{ debugName: "hidePassword" }] : []));
304
+ this.authService = inject(NgRhombusAuthenticationService);
305
+ }
306
+ ngOnInit() {
307
+ this.loginForm = this.fb.group({
308
+ email: ['', [Validators.required, Validators.email]],
309
+ password: ['', [Validators.required, Validators.minLength(6)]],
310
+ });
311
+ }
312
+ onSubmitForm() {
313
+ const rawForm = this.loginForm.getRawValue();
314
+ this.onSubmit.emit(rawForm);
315
+ }
316
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusLoginComponent, deps: [{ token: i6.FormBuilder }, { token: i1.Router }], target: i0.ɵɵFactoryTarget.Component }); }
317
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.0", type: NgRhombusLoginComponent, isStandalone: true, selector: "ng-rhombus-login", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSubmit: "onSubmit" }, ngImport: i0, template: "<div class=\"flex min-h-screen items-center justify-center bg-background\">\r\n <mat-card class=\"w-full max-w-[400px] p-10\">\r\n <mat-card-header>\r\n <mat-card-title-group>\r\n <mat-card-title>{{ title() }}</mat-card-title>\r\n <mat-card-subtitle>Administration</mat-card-subtitle>\r\n </mat-card-title-group>\r\n\r\n </mat-card-header>\r\n <form class=\"flex flex-col\" novalidate [formGroup]=\"loginForm\" (ngSubmit)=\"onSubmitForm()\">\r\n <mat-card-content>\r\n <mat-form-field class=\"w-full flex\">\r\n <mat-label>Email Address</mat-label>\r\n <input matInput placeholder=\"Placeholder\" formControlName=\"email\" aria-label=\"Email Address\" />\r\n <mat-icon matSuffix>email</mat-icon>\r\n <mat-error align=\"start\">Email Address Required</mat-error>\r\n </mat-form-field>\r\n <mat-form-field class=\"w-full flex\">\r\n <mat-label>Password</mat-label>\r\n <input matInput [type]=\"hidePassword() ? 'password' : 'text'\" placeholder=\"Placeholder\"\r\n formControlName=\"password\" aria-label=\"Password\" />\r\n <button mat-icon-button matSuffix (click)=\"hidePassword.set(!hidePassword())\" type=\"button\"\r\n style=\"margin-right: 0.25rem;\">\r\n <mat-icon>{{ hidePassword() ? 'visibility_off' : 'visibility' }}</mat-icon>\r\n </button>\r\n <mat-error align=\"start\">Password Required</mat-error>\r\n </mat-form-field>\r\n </mat-card-content>\r\n <button mat-flat-button class=\"login-button\" type=\"submit\" [disabled]=\"loading() || loginForm.invalid\">\r\n <ng-rhombus-loading-container [loading]=\"loading()\" [size]=\"20\">\r\n Login\r\n </ng-rhombus-loading-container>\r\n </button>\r\n </form>\r\n </mat-card>\r\n</div>", styles: ["mat-card{box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i6.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i6.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: i6.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i6.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i6.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i6.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i1$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i4$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i4$1.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: i4$1.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i4$1.MatCardSubtitle, selector: "mat-card-subtitle, [mat-card-subtitle], [matCardSubtitle]" }, { kind: "directive", type: i4$1.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "component", type: i4$1.MatCardTitleGroup, selector: "mat-card-title-group" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "directive", type: i5.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i5.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i7.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: NgRhombusLoadingContainerComponent, selector: "ng-rhombus-loading-container", inputs: ["loading", "size"] }] }); }
318
+ }
319
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusLoginComponent, decorators: [{
320
+ type: Component,
321
+ args: [{ selector: 'ng-rhombus-login', imports: [ReactiveFormsModule, MatButtonModule, MatCardModule, MatFormFieldModule, MatIconModule, MatInputModule, NgRhombusLoadingContainerComponent], template: "<div class=\"flex min-h-screen items-center justify-center bg-background\">\r\n <mat-card class=\"w-full max-w-[400px] p-10\">\r\n <mat-card-header>\r\n <mat-card-title-group>\r\n <mat-card-title>{{ title() }}</mat-card-title>\r\n <mat-card-subtitle>Administration</mat-card-subtitle>\r\n </mat-card-title-group>\r\n\r\n </mat-card-header>\r\n <form class=\"flex flex-col\" novalidate [formGroup]=\"loginForm\" (ngSubmit)=\"onSubmitForm()\">\r\n <mat-card-content>\r\n <mat-form-field class=\"w-full flex\">\r\n <mat-label>Email Address</mat-label>\r\n <input matInput placeholder=\"Placeholder\" formControlName=\"email\" aria-label=\"Email Address\" />\r\n <mat-icon matSuffix>email</mat-icon>\r\n <mat-error align=\"start\">Email Address Required</mat-error>\r\n </mat-form-field>\r\n <mat-form-field class=\"w-full flex\">\r\n <mat-label>Password</mat-label>\r\n <input matInput [type]=\"hidePassword() ? 'password' : 'text'\" placeholder=\"Placeholder\"\r\n formControlName=\"password\" aria-label=\"Password\" />\r\n <button mat-icon-button matSuffix (click)=\"hidePassword.set(!hidePassword())\" type=\"button\"\r\n style=\"margin-right: 0.25rem;\">\r\n <mat-icon>{{ hidePassword() ? 'visibility_off' : 'visibility' }}</mat-icon>\r\n </button>\r\n <mat-error align=\"start\">Password Required</mat-error>\r\n </mat-form-field>\r\n </mat-card-content>\r\n <button mat-flat-button class=\"login-button\" type=\"submit\" [disabled]=\"loading() || loginForm.invalid\">\r\n <ng-rhombus-loading-container [loading]=\"loading()\" [size]=\"20\">\r\n Login\r\n </ng-rhombus-loading-container>\r\n </button>\r\n </form>\r\n </mat-card>\r\n</div>", styles: ["mat-card{box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}\n"] }]
322
+ }], ctorParameters: () => [{ type: i6.FormBuilder }, { type: i1.Router }] });
323
+
324
+ /*
325
+ * Public API Surface of @doug-williamson/ng-rhombus/shell
326
+ */
327
+
328
+ class NgRhombusBlogListComponent {
329
+ constructor() {
330
+ this.goToRoute = new EventEmitter();
331
+ this.dataSource = input([], ...(ngDevMode ? [{ debugName: "dataSource" }] : []));
332
+ }
333
+ goToBlogPost(id) {
334
+ this.goToRoute.emit(id);
335
+ }
336
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusBlogListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
337
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.0", type: NgRhombusBlogListComponent, isStandalone: true, selector: "ng-rhombus-blog-list", inputs: { dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { goToRoute: "goToRoute" }, ngImport: i0, template: "<mat-nav-list>\r\n\t@for (blogPost of dataSource(); track blogPost.id) {\r\n\t\t<mat-list-item (click)=\"goToRoute.emit(blogPost.id)\">\r\n\t\t\t<h3 matListItemTitle>{{ blogPost.title }}</h3>\r\n\t\t\t<p matListItemLine>{{ blogPost.description }}</p>\r\n\t\t\t<p matListItemMeta>{{ blogPost.timestamp.toMillis() | date: 'MMMM d, y' }}</p>\r\n\t\t</mat-list-item>\r\n\t}\r\n</mat-nav-list>", styles: [".mdc-list-item{border-radius:0!important}\n"], dependencies: [{ kind: "ngmodule", type: MatListModule }, { kind: "component", type: i1$2.MatNavList, selector: "mat-nav-list", exportAs: ["matNavList"] }, { kind: "component", type: i1$2.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "directive", type: i1$2.MatListItemLine, selector: "[matListItemLine]" }, { kind: "directive", type: i1$2.MatListItemTitle, selector: "[matListItemTitle]" }, { kind: "directive", type: i1$2.MatListItemMeta, selector: "[matListItemMeta]" }, { kind: "pipe", type: DatePipe, name: "date" }] }); }
338
+ }
339
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusBlogListComponent, decorators: [{
340
+ type: Component,
341
+ args: [{ selector: 'ng-rhombus-blog-list', imports: [DatePipe, MatListModule], template: "<mat-nav-list>\r\n\t@for (blogPost of dataSource(); track blogPost.id) {\r\n\t\t<mat-list-item (click)=\"goToRoute.emit(blogPost.id)\">\r\n\t\t\t<h3 matListItemTitle>{{ blogPost.title }}</h3>\r\n\t\t\t<p matListItemLine>{{ blogPost.description }}</p>\r\n\t\t\t<p matListItemMeta>{{ blogPost.timestamp.toMillis() | date: 'MMMM d, y' }}</p>\r\n\t\t</mat-list-item>\r\n\t}\r\n</mat-nav-list>", styles: [".mdc-list-item{border-radius:0!important}\n"] }]
342
+ }], propDecorators: { goToRoute: [{
343
+ type: Output
344
+ }] } });
345
+
346
+ class NgRhombusBlogPostLatestComponent {
347
+ constructor() {
348
+ this.blogPost = input(...(ngDevMode ? [undefined, { debugName: "blogPost" }] : []));
349
+ }
350
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusBlogPostLatestComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
351
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.0", type: NgRhombusBlogPostLatestComponent, isStandalone: true, selector: "ng-rhombus-blog-post-latest", inputs: { blogPost: { classPropertyName: "blogPost", publicName: "blogPost", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<mat-nav-list>\r\n <mat-list-item [routerLink]=\"['/blog', blogPost()?.id]\">\r\n <span matListItemTitle>{{ blogPost()?.title }}</span>\r\n <span matListItemLine>{{ blogPost()?.description }}</span>\r\n <span matListItemLine>{{ blogPost()?.timestamp?.toMillis() | date: 'MMMM d, y' }}</span>\r\n </mat-list-item>\r\n</mat-nav-list>", styles: [".mdc-list-item{border-radius:0!important}\n"], dependencies: [{ kind: "ngmodule", type: MatListModule }, { kind: "component", type: i1$2.MatNavList, selector: "mat-nav-list", exportAs: ["matNavList"] }, { kind: "component", type: i1$2.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "directive", type: i1$2.MatListItemLine, selector: "[matListItemLine]" }, { kind: "directive", type: i1$2.MatListItemTitle, selector: "[matListItemTitle]" }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "pipe", type: DatePipe, name: "date" }] }); }
352
+ }
353
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusBlogPostLatestComponent, decorators: [{
354
+ type: Component,
355
+ args: [{ selector: 'ng-rhombus-blog-post-latest', imports: [DatePipe, MatListModule, RouterLink], template: "<mat-nav-list>\r\n <mat-list-item [routerLink]=\"['/blog', blogPost()?.id]\">\r\n <span matListItemTitle>{{ blogPost()?.title }}</span>\r\n <span matListItemLine>{{ blogPost()?.description }}</span>\r\n <span matListItemLine>{{ blogPost()?.timestamp?.toMillis() | date: 'MMMM d, y' }}</span>\r\n </mat-list-item>\r\n</mat-nav-list>", styles: [".mdc-list-item{border-radius:0!important}\n"] }]
356
+ }] });
357
+
358
+ class NgRhombusBlogPostHelper {
359
+ static createSlug(title) {
360
+ const slug = title.toLowerCase().replace(/\s+/g, '-');
361
+ const randomThreeDigitNumber = Math.floor(Math.random() * 1000);
362
+ return `${slug}-${randomThreeDigitNumber}`;
363
+ }
364
+ }
365
+
366
+ class NgRhombusBlogService {
367
+ constructor() {
368
+ this.firestore = inject(Firestore);
369
+ this.blogCollectionRef = collection(this.firestore, 'blog');
370
+ this.blogPosts = signal([], ...(ngDevMode ? [{ debugName: "blogPosts" }] : []));
371
+ this.selectedBlogPost = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedBlogPost" }] : []));
372
+ }
373
+ async fetchBlogPosts() {
374
+ const data = await getDocs(this.blogCollectionRef);
375
+ const returnData = [...data.docs.map(d => ({ ...d.data(), id: d.id }))];
376
+ this.blogPosts.set(returnData);
377
+ return returnData;
378
+ }
379
+ async fetchBlogPost(id) {
380
+ const blogPostDocumentRef = doc(this.firestore, 'blog', id);
381
+ // const docSnap = (await getDoc(blogPostDocumentRef)).data() as IBlog
382
+ // this.selectedBlogPost.set(docSnap);
383
+ // return docSnap;
384
+ const docSnap = await getDoc(blogPostDocumentRef);
385
+ if (docSnap.exists()) {
386
+ const docData = { ...docSnap.data(), id: id };
387
+ this.selectedBlogPost.set(docData);
388
+ return docData;
389
+ }
390
+ else {
391
+ return;
392
+ }
393
+ }
394
+ async fetchLatestBlogPost() {
395
+ const latestQuery = query(this.blogCollectionRef, orderBy('timestamp', 'desc'), limit(1));
396
+ const snapshot = await getDocs(latestQuery);
397
+ if (!snapshot.empty) {
398
+ const doc = snapshot.docs[0];
399
+ const latestPost = { ...doc.data(), id: doc.id };
400
+ this.selectedBlogPost.set(latestPost);
401
+ return latestPost;
402
+ }
403
+ return undefined;
404
+ }
405
+ async createBlogPost(blogPost) {
406
+ const blogPostDocumentRef = doc(this.firestore, 'blog', NgRhombusBlogPostHelper.createSlug(blogPost.title));
407
+ setDoc(blogPostDocumentRef, {
408
+ title: blogPost.title,
409
+ description: blogPost.description,
410
+ thumbnail: blogPost.thumbnail,
411
+ content: blogPost.content,
412
+ timestamp: new Date()
413
+ });
414
+ }
415
+ async updateBlogPost(blogPost) {
416
+ const blogPostDocumentRef = doc(this.firestore, 'blog', blogPost.id);
417
+ updateDoc(blogPostDocumentRef, {
418
+ title: blogPost.title,
419
+ description: blogPost.description,
420
+ thumbnail: blogPost.thumbnail,
421
+ content: blogPost.content,
422
+ });
423
+ }
424
+ deleteBlogPost(id) {
425
+ const blogPostDocumentRef = doc(this.firestore, 'blog', id);
426
+ return deleteDoc(blogPostDocumentRef);
427
+ }
428
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusBlogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
429
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusBlogService, providedIn: 'root' }); }
430
+ }
431
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusBlogService, decorators: [{
432
+ type: Injectable,
433
+ args: [{
434
+ providedIn: 'root',
435
+ }]
436
+ }] });
437
+
438
+ class NgRhombusBlogPostThumbnailService {
439
+ constructor() {
440
+ this.firebaseStorage = inject(Storage);
441
+ }
442
+ uploadImage(imageName, image) {
443
+ const storageRef = ref(this.firebaseStorage, `thumbnails/${imageName}`);
444
+ return uploadBytesResumable(storageRef, image);
445
+ }
446
+ deleteImage(filePath) {
447
+ const storageDocRef = ref(this.firebaseStorage, filePath);
448
+ return deleteObject(storageDocRef).then(() => {
449
+ }).catch((error) => {
450
+ });
451
+ }
452
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusBlogPostThumbnailService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
453
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusBlogPostThumbnailService, providedIn: 'root' }); }
454
+ }
455
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusBlogPostThumbnailService, decorators: [{
456
+ type: Injectable,
457
+ args: [{
458
+ providedIn: 'root'
459
+ }]
460
+ }] });
461
+
462
+ class NgRhombusBlogTableComponent {
463
+ constructor() {
464
+ this.editEvent = new EventEmitter();
465
+ this.deleteEvent = new EventEmitter();
466
+ this.thumbnailService = inject(NgRhombusBlogPostThumbnailService);
467
+ this.blogService = inject(NgRhombusBlogService);
468
+ this.dialog = inject(MatDialog);
469
+ this.dataSource = input([], ...(ngDevMode ? [{ debugName: "dataSource" }] : []));
470
+ this.displayedColumns = ['timestamp', 'title', 'description', 'edit', 'delete'];
471
+ }
472
+ goToBlogPost(id) {
473
+ this.editEvent.emit(id);
474
+ }
475
+ onDeleteBlogPost(blogPost) {
476
+ this.deleteEvent.emit(blogPost);
477
+ }
478
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusBlogTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
479
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.0", type: NgRhombusBlogTableComponent, isStandalone: true, selector: "ng-rhombus-blog-table", inputs: { dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { editEvent: "editEvent", deleteEvent: "deleteEvent" }, ngImport: i0, template: "@if (dataSource().length > 0) {\r\n<table mat-table [dataSource]=\"dataSource()\" class=\"w-full h-full\">\r\n\t<!-- Timestamp Column -->\r\n\t<ng-container matColumnDef=\"timestamp\">\r\n\t\t<th mat-header-cell *matHeaderCellDef>Date</th>\r\n\t\t<td mat-cell *matCellDef=\"let element\">{{ element.timestamp.toMillis() | date: 'MMMM d, y' }}</td>\r\n\t</ng-container>\r\n\r\n\t<!-- Title Column -->\r\n\t<ng-container matColumnDef=\"title\">\r\n\t\t<th mat-header-cell *matHeaderCellDef>Title</th>\r\n\t\t<td mat-cell *matCellDef=\"let element\">{{element.title}}</td>\r\n\t</ng-container>\r\n\r\n\t<!-- Description Column -->\r\n\t<ng-container matColumnDef=\"description\">\r\n\t\t<th mat-header-cell *matHeaderCellDef>Description</th>\r\n\t\t<td mat-cell *matCellDef=\"let element\">{{element.description}}</td>\r\n\t</ng-container>\r\n\r\n\t<!-- Edit Column -->\r\n\t<ng-container matColumnDef=\"edit\">\r\n\t\t<th mat-header-cell *matHeaderCellDef aria-label=\"row actions\">&nbsp;</th>\r\n\t\t<td mat-cell *matCellDef=\"let element\">\r\n\t\t\t<button mat-icon-button (click)=\"goToBlogPost(element.id)\">\r\n\t\t\t\t<mat-icon>edit</mat-icon>\r\n\t\t\t</button>\r\n\t\t</td>\r\n\t</ng-container>\r\n\r\n\t<!-- Delete Column -->\r\n\t<ng-container matColumnDef=\"delete\">\r\n\t\t<th mat-header-cell *matHeaderCellDef aria-label=\"row actions\">&nbsp;</th>\r\n\t\t<td mat-cell *matCellDef=\"let element\">\r\n\t\t\t<button mat-icon-button (click)=\"onDeleteBlogPost(element)\">\r\n\t\t\t\t<mat-icon>delete</mat-icon>\r\n\t\t\t</button>\r\n\t\t</td>\r\n\t</ng-container>\r\n\r\n\t<tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\r\n\t<tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\r\n</table>\r\n} @else {\r\n<div class=\"empty-set\">\r\n\t<span>No blog posts created yet.</span>\r\n</div>\r\n\r\n}", styles: [".empty-set{margin:12px}.rhombus{overflow-y:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTableModule }, { kind: "component", type: i3$2.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i3$2.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i3$2.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i3$2.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i3$2.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i3$2.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i3$2.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i3$2.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i3$2.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i3$2.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "pipe", type: i4$2.DatePipe, name: "date" }] }); }
480
+ }
481
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusBlogTableComponent, decorators: [{
482
+ type: Component,
483
+ args: [{ selector: 'ng-rhombus-blog-table', imports: [CommonModule, MatButtonModule, MatIconModule, MatTableModule], template: "@if (dataSource().length > 0) {\r\n<table mat-table [dataSource]=\"dataSource()\" class=\"w-full h-full\">\r\n\t<!-- Timestamp Column -->\r\n\t<ng-container matColumnDef=\"timestamp\">\r\n\t\t<th mat-header-cell *matHeaderCellDef>Date</th>\r\n\t\t<td mat-cell *matCellDef=\"let element\">{{ element.timestamp.toMillis() | date: 'MMMM d, y' }}</td>\r\n\t</ng-container>\r\n\r\n\t<!-- Title Column -->\r\n\t<ng-container matColumnDef=\"title\">\r\n\t\t<th mat-header-cell *matHeaderCellDef>Title</th>\r\n\t\t<td mat-cell *matCellDef=\"let element\">{{element.title}}</td>\r\n\t</ng-container>\r\n\r\n\t<!-- Description Column -->\r\n\t<ng-container matColumnDef=\"description\">\r\n\t\t<th mat-header-cell *matHeaderCellDef>Description</th>\r\n\t\t<td mat-cell *matCellDef=\"let element\">{{element.description}}</td>\r\n\t</ng-container>\r\n\r\n\t<!-- Edit Column -->\r\n\t<ng-container matColumnDef=\"edit\">\r\n\t\t<th mat-header-cell *matHeaderCellDef aria-label=\"row actions\">&nbsp;</th>\r\n\t\t<td mat-cell *matCellDef=\"let element\">\r\n\t\t\t<button mat-icon-button (click)=\"goToBlogPost(element.id)\">\r\n\t\t\t\t<mat-icon>edit</mat-icon>\r\n\t\t\t</button>\r\n\t\t</td>\r\n\t</ng-container>\r\n\r\n\t<!-- Delete Column -->\r\n\t<ng-container matColumnDef=\"delete\">\r\n\t\t<th mat-header-cell *matHeaderCellDef aria-label=\"row actions\">&nbsp;</th>\r\n\t\t<td mat-cell *matCellDef=\"let element\">\r\n\t\t\t<button mat-icon-button (click)=\"onDeleteBlogPost(element)\">\r\n\t\t\t\t<mat-icon>delete</mat-icon>\r\n\t\t\t</button>\r\n\t\t</td>\r\n\t</ng-container>\r\n\r\n\t<tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\r\n\t<tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\r\n</table>\r\n} @else {\r\n<div class=\"empty-set\">\r\n\t<span>No blog posts created yet.</span>\r\n</div>\r\n\r\n}", styles: [".empty-set{margin:12px}.rhombus{overflow-y:auto}\n"] }]
484
+ }], propDecorators: { editEvent: [{
485
+ type: Output
486
+ }], deleteEvent: [{
487
+ type: Output
488
+ }] } });
489
+
490
+ class NgRhombusBlogThumbnailComponent {
491
+ constructor() {
492
+ this.width = input(0, ...(ngDevMode ? [{ debugName: "width" }] : []));
493
+ this.height = input(0, ...(ngDevMode ? [{ debugName: "height" }] : []));
494
+ this.imageSource = input(...(ngDevMode ? [undefined, { debugName: "imageSource" }] : []));
495
+ this.placeholder = computed(() => `https://placehold.co/${this.width()}x${this.height()}`, ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
496
+ this.contentSource = computed(() => { return this.imageSource() !== '' ? this.imageSource() : this.placeholder(); }, ...(ngDevMode ? [{ debugName: "contentSource" }] : []));
497
+ }
498
+ ngOnInit() {
499
+ if (this.width() === 0 && this.height() === 0) {
500
+ }
501
+ }
502
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusBlogThumbnailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
503
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.0", type: NgRhombusBlogThumbnailComponent, isStandalone: true, selector: "ng-rhombus-blog-thumbnail", inputs: { width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, imageSource: { classPropertyName: "imageSource", publicName: "imageSource", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"image-placeholder\" [style.width]=\"width() + 'px'\" [style.height]=\"height() + 'px'\">\r\n <img [src]=\"contentSource()\" [width]=\"width()\" [height]=\"height()\" />\r\n</div>", styles: [".image-placeholder{box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}img{width:100%;height:auto}\n"] }); }
504
+ }
505
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusBlogThumbnailComponent, decorators: [{
506
+ type: Component,
507
+ args: [{ selector: 'ng-rhombus-blog-thumbnail', imports: [], template: "<div class=\"image-placeholder\" [style.width]=\"width() + 'px'\" [style.height]=\"height() + 'px'\">\r\n <img [src]=\"contentSource()\" [width]=\"width()\" [height]=\"height()\" />\r\n</div>", styles: [".image-placeholder{box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}img{width:100%;height:auto}\n"] }]
508
+ }] });
509
+
510
+ class NgRhombusBlogDeleteThumbnailComponent {
511
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusBlogDeleteThumbnailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
512
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.0", type: NgRhombusBlogDeleteThumbnailComponent, isStandalone: true, selector: "ng-rhombus-Blog-delete-thumbnail", ngImport: i0, template: "<h2 mat-dialog-title>Blog Post Thumbnail - Delete</h2>\r\n<mat-dialog-content>\r\n <p>Would you like to delete this thumbnail?</p>\r\n</mat-dialog-content>\r\n<mat-dialog-actions>\r\n <button mat-button [mat-dialog-close]=\"false\" cdkFocusInitial>Cancel</button>\r\n <button mat-button [mat-dialog-close]=\"true\">Confirm</button>\r\n</mat-dialog-actions>", styles: [""], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i2$1.MatDialogClose, selector: "[mat-dialog-close], [matDialogClose]", inputs: ["aria-label", "type", "mat-dialog-close", "matDialogClose"], exportAs: ["matDialogClose"] }, { kind: "directive", type: i2$1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i2$1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i2$1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }] }); }
513
+ }
514
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusBlogDeleteThumbnailComponent, decorators: [{
515
+ type: Component,
516
+ args: [{ selector: 'ng-rhombus-Blog-delete-thumbnail', imports: [MatButtonModule, MatDialogModule], template: "<h2 mat-dialog-title>Blog Post Thumbnail - Delete</h2>\r\n<mat-dialog-content>\r\n <p>Would you like to delete this thumbnail?</p>\r\n</mat-dialog-content>\r\n<mat-dialog-actions>\r\n <button mat-button [mat-dialog-close]=\"false\" cdkFocusInitial>Cancel</button>\r\n <button mat-button [mat-dialog-close]=\"true\">Confirm</button>\r\n</mat-dialog-actions>" }]
517
+ }] });
518
+
519
+ class ThumbnailControlComponent {
520
+ constructor() {
521
+ this.width = input(0, ...(ngDevMode ? [{ debugName: "width" }] : []));
522
+ this.height = input(0, ...(ngDevMode ? [{ debugName: "height" }] : []));
523
+ this.thumbnailSrc = input('', ...(ngDevMode ? [{ debugName: "thumbnailSrc" }] : []));
524
+ this.disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
525
+ this.thumbnailService = inject(NgRhombusBlogPostThumbnailService);
526
+ this.dialog = inject(MatDialog);
527
+ this.onFileUploaded = output();
528
+ this.onFileDeleted = output();
529
+ this.uploadedFile = signal(undefined, ...(ngDevMode ? [{ debugName: "uploadedFile" }] : []));
530
+ this.placeholder = computed(() => `https://placehold.co/${this.width()}x${this.height()}`, ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
531
+ this.imageSource = computed(() => { return this.uploadedFile() ?? this.placeholder(); }, ...(ngDevMode ? [{ debugName: "imageSource" }] : []));
532
+ }
533
+ onThumbnailSelected(input) {
534
+ if (!input.files || input.files.length <= 0) {
535
+ return;
536
+ }
537
+ const file = input.files[0];
538
+ this.thumbnailService.uploadImage(file.name, file).then((snapshot) => {
539
+ getDownloadURL(snapshot.ref).then((downloadUrl) => {
540
+ this.uploadedFile.set(downloadUrl);
541
+ this.onFileUploaded.emit(downloadUrl);
542
+ });
543
+ });
544
+ }
545
+ onThumbnailDeleted() {
546
+ const dialogRef = this.dialog.open(NgRhombusBlogDeleteThumbnailComponent);
547
+ dialogRef.afterClosed().subscribe(result => {
548
+ if (result) {
549
+ this.thumbnailService.deleteImage(this.uploadedFile()).then(() => {
550
+ this.uploadedFile.set(undefined);
551
+ this.thumbnailInput.nativeElement.value = '';
552
+ this.onFileDeleted.emit();
553
+ });
554
+ }
555
+ });
556
+ }
557
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: ThumbnailControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
558
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.0", type: ThumbnailControlComponent, isStandalone: true, selector: "ng-rhombus-thumbnail-control", inputs: { width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, thumbnailSrc: { classPropertyName: "thumbnailSrc", publicName: "thumbnailSrc", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onFileUploaded: "onFileUploaded", onFileDeleted: "onFileDeleted" }, viewQueries: [{ propertyName: "thumbnailInput", first: true, predicate: ["thumbnailInput"], descendants: true }], ngImport: i0, template: "<div class=\"thumbnail-control-wrapper\">\r\n <input #thumbnailInput hidden type=\"file\" (change)=\"onThumbnailSelected(thumbnailInput)\" />\r\n <button mat-icon-button [disabled]=\"uploadedFile() || disabled()\" (click)=\"thumbnailInput.click()\" type=\"button\">\r\n <mat-icon>upload_file</mat-icon>\r\n </button>\r\n <button class=\"delete-button\" mat-icon-button [disabled]=\"!uploadedFile()\" (click)=\"onThumbnailDeleted()\"\r\n type=\"button\">\r\n <mat-icon>delete</mat-icon>\r\n </button>\r\n <div class=\"flex\"></div>\r\n <!-- <div class=\"image-placeholder\" [style.width]=\"width() + 'px'\" [style.height]=\"height() + 'px'\">\r\n <img [src]=\"imageSource()\" [width]=\"width()\" [height]=\"height()\" />\r\n </div> -->\r\n <ng-rhombus-blog-thumbnail [imageSource]=\"thumbnailSrc()\" [width]=\"width()\" [height]=\"height()\"\r\n [imageSource]=\"imageSource()\"></ng-rhombus-blog-thumbnail>\r\n</div>", styles: [".thumbnail-control-wrapper{display:flex;flex:1;align-items:center;padding-bottom:20px}.flex{flex:1 1 auto}.image-placeholder{box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.delete-button{margin-left:12px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: NgRhombusBlogThumbnailComponent, selector: "ng-rhombus-blog-thumbnail", inputs: ["width", "height", "imageSource"] }] }); }
559
+ }
560
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: ThumbnailControlComponent, decorators: [{
561
+ type: Component,
562
+ args: [{ selector: 'ng-rhombus-thumbnail-control', imports: [CommonModule, MatButtonModule, MatIconModule, NgRhombusBlogThumbnailComponent], template: "<div class=\"thumbnail-control-wrapper\">\r\n <input #thumbnailInput hidden type=\"file\" (change)=\"onThumbnailSelected(thumbnailInput)\" />\r\n <button mat-icon-button [disabled]=\"uploadedFile() || disabled()\" (click)=\"thumbnailInput.click()\" type=\"button\">\r\n <mat-icon>upload_file</mat-icon>\r\n </button>\r\n <button class=\"delete-button\" mat-icon-button [disabled]=\"!uploadedFile()\" (click)=\"onThumbnailDeleted()\"\r\n type=\"button\">\r\n <mat-icon>delete</mat-icon>\r\n </button>\r\n <div class=\"flex\"></div>\r\n <!-- <div class=\"image-placeholder\" [style.width]=\"width() + 'px'\" [style.height]=\"height() + 'px'\">\r\n <img [src]=\"imageSource()\" [width]=\"width()\" [height]=\"height()\" />\r\n </div> -->\r\n <ng-rhombus-blog-thumbnail [imageSource]=\"thumbnailSrc()\" [width]=\"width()\" [height]=\"height()\"\r\n [imageSource]=\"imageSource()\"></ng-rhombus-blog-thumbnail>\r\n</div>", styles: [".thumbnail-control-wrapper{display:flex;flex:1;align-items:center;padding-bottom:20px}.flex{flex:1 1 auto}.image-placeholder{box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.delete-button{margin-left:12px}\n"] }]
563
+ }], propDecorators: { thumbnailInput: [{
564
+ type: ViewChild,
565
+ args: ['thumbnailInput']
566
+ }] } });
567
+
568
+ class IBlog {
569
+ }
570
+
571
+ class NgRhombusBlogConfirmationComponent {
572
+ constructor() {
573
+ this.data = inject(MAT_DIALOG_DATA);
574
+ }
575
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusBlogConfirmationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
576
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.0", type: NgRhombusBlogConfirmationComponent, isStandalone: true, selector: "ng-rhombus-blog-confirmation", ngImport: i0, template: "<h2 mat-dialog-title>{{ data.header }}</h2>\r\n<mat-dialog-content>\r\n <p>{{ data.query }}</p>\r\n</mat-dialog-content>\r\n<mat-dialog-actions>\r\n <button mat-button [mat-dialog-close]=\"false\" cdkFocusInitial>Cancel</button>\r\n <button mat-button [mat-dialog-close]=\"true\">Confirm</button>\r\n</mat-dialog-actions>", styles: [""], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i2$1.MatDialogClose, selector: "[mat-dialog-close], [matDialogClose]", inputs: ["aria-label", "type", "mat-dialog-close", "matDialogClose"], exportAs: ["matDialogClose"] }, { kind: "directive", type: i2$1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i2$1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i2$1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }] }); }
577
+ }
578
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusBlogConfirmationComponent, decorators: [{
579
+ type: Component,
580
+ args: [{ selector: 'ng-rhombus-blog-confirmation', imports: [MatButtonModule, MatDialogModule], template: "<h2 mat-dialog-title>{{ data.header }}</h2>\r\n<mat-dialog-content>\r\n <p>{{ data.query }}</p>\r\n</mat-dialog-content>\r\n<mat-dialog-actions>\r\n <button mat-button [mat-dialog-close]=\"false\" cdkFocusInitial>Cancel</button>\r\n <button mat-button [mat-dialog-close]=\"true\">Confirm</button>\r\n</mat-dialog-actions>" }]
581
+ }] });
582
+
583
+ class NgRhombusBlogAddEditComponent {
584
+ constructor() {
585
+ this.blogPost = input(undefined, ...(ngDevMode ? [{ debugName: "blogPost" }] : []));
586
+ this.cancelEvent = output();
587
+ this.submitEvent = output();
588
+ this.contentData = signal('', ...(ngDevMode ? [{ debugName: "contentData" }] : []));
589
+ this.dialog = inject(MatDialog);
590
+ this.thumbnailService = inject(NgRhombusBlogPostThumbnailService);
591
+ this.formBuilder = inject(FormBuilder);
592
+ this.markdown = `## Markdown __rulez__!
593
+ ---
594
+
595
+ ### Syntax highlight
596
+ \`\`\`typescript
597
+ const language = 'typescript';
598
+ \`\`\`
599
+
600
+ ### Lists
601
+ 1. Ordered list
602
+ 2. Another bullet point
603
+ - Unordered list
604
+ - Another unordered bullet
605
+
606
+ ### Blockquote
607
+ > Blockquote to the max`;
608
+ effect(() => {
609
+ if (this.blogPost()) {
610
+ this.blogPostForm.patchValue({
611
+ title: this.blogPost()?.title,
612
+ description: this.blogPost()?.description,
613
+ thumbnail: this.blogPost()?.thumbnail,
614
+ content: this.blogPost()?.content,
615
+ });
616
+ }
617
+ this.onContentChange();
618
+ });
619
+ }
620
+ ngOnInit() {
621
+ this.blogPostForm = this.formBuilder.group({
622
+ title: [this.blogPost()?.title, Validators.required],
623
+ description: [this.blogPost()?.description, Validators.required],
624
+ thumbnail: [this.blogPost()?.thumbnail, Validators.required],
625
+ content: [this.blogPost()?.content, Validators.required]
626
+ });
627
+ }
628
+ onContentChange() {
629
+ this.contentData.set(this.blogPostForm.getRawValue().content);
630
+ }
631
+ get thumbnailSource() {
632
+ return this.blogPostForm.getRawValue().thumbnail;
633
+ }
634
+ onFileUploaded(downloadUrl) {
635
+ this.blogPostForm.patchValue({
636
+ thumbnail: downloadUrl
637
+ });
638
+ this.blogPostForm.markAsDirty();
639
+ }
640
+ onFileDeleted() {
641
+ this.blogPostForm.patchValue({
642
+ thumbnail: ''
643
+ });
644
+ }
645
+ onCancelClick() {
646
+ if (this.blogPost()) {
647
+ // EDIT form
648
+ if (this.blogPostForm.dirty) {
649
+ const dialogRef = this.dialog.open(NgRhombusBlogConfirmationComponent, {
650
+ data: {
651
+ header: 'Cancel Revision',
652
+ query: 'Are you sure? You will lose your revision(s).'
653
+ }
654
+ });
655
+ dialogRef.afterClosed().subscribe(result => {
656
+ if (result) {
657
+ this.cancelEvent.emit();
658
+ }
659
+ });
660
+ }
661
+ else {
662
+ this.cancelEvent.emit();
663
+ }
664
+ }
665
+ else {
666
+ // CREATE form
667
+ if (this.blogPostForm.dirty) {
668
+ const dialogRef = this.dialog.open(NgRhombusBlogConfirmationComponent, {
669
+ data: {
670
+ header: 'Cancel Create',
671
+ query: 'Are you sure? You will lose your progress.'
672
+ }
673
+ });
674
+ dialogRef.afterClosed().subscribe(result => {
675
+ if (result) {
676
+ if (this.blogPostForm.getRawValue().thumbnail) {
677
+ this.thumbnailService.deleteImage(this.blogPostForm.getRawValue().thumbnail).then(() => {
678
+ this.onFileDeleted();
679
+ this.cancelEvent.emit();
680
+ });
681
+ }
682
+ this.cancelEvent.emit();
683
+ }
684
+ });
685
+ }
686
+ else {
687
+ this.cancelEvent.emit();
688
+ }
689
+ }
690
+ }
691
+ onSubmit() {
692
+ if (this.blogPostForm.invalid) {
693
+ return;
694
+ }
695
+ const rawData = this.blogPostForm.getRawValue();
696
+ let submittedBlogPost = new IBlog();
697
+ if (this.blogPost()) {
698
+ submittedBlogPost.id = this.blogPost().id;
699
+ }
700
+ submittedBlogPost.title = rawData.title;
701
+ submittedBlogPost.description = rawData.description;
702
+ submittedBlogPost.thumbnail = rawData.thumbnail;
703
+ submittedBlogPost.content = rawData.content;
704
+ this.submitEvent.emit(submittedBlogPost);
705
+ }
706
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusBlogAddEditComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
707
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.0", type: NgRhombusBlogAddEditComponent, isStandalone: true, selector: "ng-rhombus-blog-form", inputs: { blogPost: { classPropertyName: "blogPost", publicName: "blogPost", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cancelEvent: "cancelEvent", submitEvent: "submitEvent" }, viewQueries: [{ propertyName: "autosize", first: true, predicate: ["autosize"], descendants: true }, { propertyName: "blogPostPreview", first: true, predicate: ["blogPost"], descendants: true }], ngImport: i0, template: "<div class=\"h-full flex flex-row\">\r\n\t<div class=\"flex flex-col w-1/2 p-4\">\r\n\t\t<form novalidate class=\"h-full flex flex-col\" [formGroup]=\"blogPostForm\">\r\n\t\t\t<mat-form-field>\r\n\t\t\t\t<mat-label>Title</mat-label>\r\n\t\t\t\t<input matInput placeholder=\"Placeholder\" formControlName=\"title\">\r\n\t\t\t</mat-form-field>\r\n\t\t\t<mat-form-field>\r\n\t\t\t\t<mat-label>Description</mat-label>\r\n\t\t\t\t<input matInput placeholder=\"Placeholder\" formControlName=\"description\">\r\n\t\t\t</mat-form-field>\r\n\t\t\t<ng-rhombus-thumbnail-control [thumbnailSrc]=\"thumbnailSource\" [width]=\"200\" [height]=\"112\"\r\n\t\t\t\t[disabled]=\"blogPost() !== undefined\" (onFileUploaded)=\"onFileUploaded($event)\"\r\n\t\t\t\t(onFileDeleted)=\"onFileDeleted()\"></ng-rhombus-thumbnail-control>\r\n\t\t\t<mat-form-field class=\"h-full\">\r\n\t\t\t\t<mat-label>Content</mat-label>\r\n\t\t\t\t<textarea matInput required cdkTextareaAutosize #autosize=\"cdkTextareaAutosize\" cdkAutosizeMinRows=\"1\"\r\n\t\t\t\t\tcdkAutosizeMaxRows=\"20\" formControlName=\"content\" (keyup)=\"onContentChange()\"></textarea>\r\n\t\t\t</mat-form-field>\r\n\t\t\t<div class=\"flex\">\r\n\t\t\t\t<button mat-raised-button class=\"\" (click)=\"onCancelClick()\" type=\"button\">Cancel</button>\r\n\t\t\t\t<div class=\"flex-1\"></div>\r\n\t\t\t\t<button mat-flat-button [disabled]=\"blogPostForm.invalid\" type=\"button\"\r\n\t\t\t\t\t(click)=\"onSubmit()\">Submit</button>\r\n\t\t\t</div>\r\n\t\t</form>\r\n\t</div>\r\n\r\n\t<div class=\"flex flex-col w-1/2 p-4 overflow-y-auto\">\r\n\t\t<mat-card>\r\n\t\t\t<mat-card-header>\r\n\t\t\t\t<mat-card-title>{{ blogPostForm.getRawValue().title }}</mat-card-title>\r\n\t\t\t\t<mat-card-subtitle>{{ blogPostForm.getRawValue().description }}</mat-card-subtitle>\r\n\t\t\t</mat-card-header>\r\n\t\t\t<img [src]=\"blogPostForm.getRawValue().thumbnail\" />\r\n\t\t\t<mat-card-content>\r\n\t\t\t\t<markdown class=\"prose\" [data]=\"contentData()\" />\r\n\t\t\t</mat-card-content>\r\n\t\t</mat-card>\r\n\t</div>\r\n</div>", styles: [""], dependencies: [{ kind: "ngmodule", type: MatListModule }, { kind: "ngmodule", type: MatDividerModule }, { kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i4$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i4$1.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: i4$1.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i4$1.MatCardSubtitle, selector: "mat-card-subtitle, [mat-card-subtitle], [matCardSubtitle]" }, { kind: "directive", type: i4$1.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i7.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "directive", type: i5$1.CdkTextareaAutosize, selector: "textarea[cdkTextareaAutosize]", inputs: ["cdkAutosizeMinRows", "cdkAutosizeMaxRows", "cdkTextareaAutosize", "placeholder"], exportAs: ["cdkTextareaAutosize"] }, { kind: "ngmodule", type: MatProgressSpinnerModule }, { kind: "ngmodule", type: MatSidenavModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i6.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i6.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: i6.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i6.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i6.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i6.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i6.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: TextFieldModule }, { kind: "component", type: ThumbnailControlComponent, selector: "ng-rhombus-thumbnail-control", inputs: ["width", "height", "thumbnailSrc", "disabled"], outputs: ["onFileUploaded", "onFileDeleted"] }, { kind: "ngmodule", type: MatToolbarModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }] }); }
708
+ }
709
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusBlogAddEditComponent, decorators: [{
710
+ type: Component,
711
+ args: [{ selector: 'ng-rhombus-blog-form', imports: [MatListModule, MatDividerModule, CommonModule, MatButtonModule, MatCardModule, MatFormFieldModule, MatInputModule, MatProgressSpinnerModule, MatSidenavModule, ReactiveFormsModule, TextFieldModule, ThumbnailControlComponent, MatToolbarModule, MatIconModule, MarkdownComponent], template: "<div class=\"h-full flex flex-row\">\r\n\t<div class=\"flex flex-col w-1/2 p-4\">\r\n\t\t<form novalidate class=\"h-full flex flex-col\" [formGroup]=\"blogPostForm\">\r\n\t\t\t<mat-form-field>\r\n\t\t\t\t<mat-label>Title</mat-label>\r\n\t\t\t\t<input matInput placeholder=\"Placeholder\" formControlName=\"title\">\r\n\t\t\t</mat-form-field>\r\n\t\t\t<mat-form-field>\r\n\t\t\t\t<mat-label>Description</mat-label>\r\n\t\t\t\t<input matInput placeholder=\"Placeholder\" formControlName=\"description\">\r\n\t\t\t</mat-form-field>\r\n\t\t\t<ng-rhombus-thumbnail-control [thumbnailSrc]=\"thumbnailSource\" [width]=\"200\" [height]=\"112\"\r\n\t\t\t\t[disabled]=\"blogPost() !== undefined\" (onFileUploaded)=\"onFileUploaded($event)\"\r\n\t\t\t\t(onFileDeleted)=\"onFileDeleted()\"></ng-rhombus-thumbnail-control>\r\n\t\t\t<mat-form-field class=\"h-full\">\r\n\t\t\t\t<mat-label>Content</mat-label>\r\n\t\t\t\t<textarea matInput required cdkTextareaAutosize #autosize=\"cdkTextareaAutosize\" cdkAutosizeMinRows=\"1\"\r\n\t\t\t\t\tcdkAutosizeMaxRows=\"20\" formControlName=\"content\" (keyup)=\"onContentChange()\"></textarea>\r\n\t\t\t</mat-form-field>\r\n\t\t\t<div class=\"flex\">\r\n\t\t\t\t<button mat-raised-button class=\"\" (click)=\"onCancelClick()\" type=\"button\">Cancel</button>\r\n\t\t\t\t<div class=\"flex-1\"></div>\r\n\t\t\t\t<button mat-flat-button [disabled]=\"blogPostForm.invalid\" type=\"button\"\r\n\t\t\t\t\t(click)=\"onSubmit()\">Submit</button>\r\n\t\t\t</div>\r\n\t\t</form>\r\n\t</div>\r\n\r\n\t<div class=\"flex flex-col w-1/2 p-4 overflow-y-auto\">\r\n\t\t<mat-card>\r\n\t\t\t<mat-card-header>\r\n\t\t\t\t<mat-card-title>{{ blogPostForm.getRawValue().title }}</mat-card-title>\r\n\t\t\t\t<mat-card-subtitle>{{ blogPostForm.getRawValue().description }}</mat-card-subtitle>\r\n\t\t\t</mat-card-header>\r\n\t\t\t<img [src]=\"blogPostForm.getRawValue().thumbnail\" />\r\n\t\t\t<mat-card-content>\r\n\t\t\t\t<markdown class=\"prose\" [data]=\"contentData()\" />\r\n\t\t\t</mat-card-content>\r\n\t\t</mat-card>\r\n\t</div>\r\n</div>" }]
712
+ }], ctorParameters: () => [], propDecorators: { autosize: [{
713
+ type: ViewChild,
714
+ args: ['autosize']
715
+ }], blogPostPreview: [{
716
+ type: ViewChild,
717
+ args: ['blogPost']
718
+ }] } });
719
+
720
+ class NgRhombusBlogPostComponent {
721
+ constructor() {
722
+ this.dataSource = input(...(ngDevMode ? [undefined, { debugName: "dataSource" }] : []));
723
+ }
724
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusBlogPostComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
725
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.0", type: NgRhombusBlogPostComponent, isStandalone: true, selector: "ng-rhombus-blog-post", inputs: { dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<<<<<<< HEAD\r\n\r\n<mat-card>\r\n\t<mat-card-header>\r\n\t\t<mat-card-title>{{ dataSource()?.title }}</mat-card-title>\r\n\t\t<mat-card-subtitle>{{ dataSource()?.description }}</mat-card-subtitle>\r\n\t</mat-card-header>\r\n\t<img [src]=\"dataSource()?.thumbnail\" />\r\n\t<mat-card-content>\r\n\t\t<markdown class=\"prose\" [data]=\"dataSource()?.content\" />\r\n\t</mat-card-content>", styles: [""], dependencies: [{ kind: "component", type: MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i4$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i4$1.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: i4$1.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i4$1.MatCardSubtitle, selector: "mat-card-subtitle, [mat-card-subtitle], [matCardSubtitle]" }, { kind: "directive", type: i4$1.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }] }); }
726
+ }
727
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusBlogPostComponent, decorators: [{
728
+ type: Component,
729
+ args: [{ selector: 'ng-rhombus-blog-post', imports: [MarkdownComponent, MatCardModule], template: "<<<<<<< HEAD\r\n\r\n<mat-card>\r\n\t<mat-card-header>\r\n\t\t<mat-card-title>{{ dataSource()?.title }}</mat-card-title>\r\n\t\t<mat-card-subtitle>{{ dataSource()?.description }}</mat-card-subtitle>\r\n\t</mat-card-header>\r\n\t<img [src]=\"dataSource()?.thumbnail\" />\r\n\t<mat-card-content>\r\n\t\t<markdown class=\"prose\" [data]=\"dataSource()?.content\" />\r\n\t</mat-card-content>" }]
730
+ }] });
731
+
732
+ class NgRhombusBlogDeletePostComponent {
733
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusBlogDeletePostComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
734
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.0", type: NgRhombusBlogDeletePostComponent, isStandalone: true, selector: "ng-rhombus-Blog-delete-post", ngImport: i0, template: "<h2 mat-dialog-title>Blog Post - Delete</h2>\r\n<mat-dialog-content>\r\n <p>Would you like to delete this blog post?</p>\r\n</mat-dialog-content>\r\n<mat-dialog-actions>\r\n <button mat-button [mat-dialog-close]=\"false\" cdkFocusInitial>Cancel</button>\r\n <button mat-button [mat-dialog-close]=\"true\">Confirm</button>\r\n</mat-dialog-actions>", styles: [""], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i2$1.MatDialogClose, selector: "[mat-dialog-close], [matDialogClose]", inputs: ["aria-label", "type", "mat-dialog-close", "matDialogClose"], exportAs: ["matDialogClose"] }, { kind: "directive", type: i2$1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i2$1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i2$1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }] }); }
735
+ }
736
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusBlogDeletePostComponent, decorators: [{
737
+ type: Component,
738
+ args: [{ selector: 'ng-rhombus-Blog-delete-post', imports: [MatButtonModule, MatDialogModule], template: "<h2 mat-dialog-title>Blog Post - Delete</h2>\r\n<mat-dialog-content>\r\n <p>Would you like to delete this blog post?</p>\r\n</mat-dialog-content>\r\n<mat-dialog-actions>\r\n <button mat-button [mat-dialog-close]=\"false\" cdkFocusInitial>Cancel</button>\r\n <button mat-button [mat-dialog-close]=\"true\">Confirm</button>\r\n</mat-dialog-actions>" }]
739
+ }] });
740
+
741
+ /*
742
+ * Public API Surface of @doug-williamson/ng-rhombus/shell
743
+ */
744
+
745
+ class IHome {
746
+ }
747
+
748
+ class NgRhombusHomeAdminComponent {
749
+ constructor() {
750
+ this.formAdminData = input(undefined, ...(ngDevMode ? [{ debugName: "formAdminData" }] : []));
751
+ this.cancelEvent = output();
752
+ this.submitEvent = output();
753
+ this.fb = inject(FormBuilder);
754
+ }
755
+ ngOnInit() {
756
+ // build once
757
+ this.homeAdminForm = this.fb.group({
758
+ title: ['', Validators.required],
759
+ description: ['', Validators.required],
760
+ // add other fields here...
761
+ });
762
+ // react to input changes (runs when data arrives)
763
+ effect(() => {
764
+ const data = this.formAdminData();
765
+ if (data) {
766
+ this.homeAdminForm.patchValue(data, { emitEvent: false });
767
+ }
768
+ });
769
+ }
770
+ onSubmit() {
771
+ if (this.homeAdminForm.invalid) {
772
+ return;
773
+ }
774
+ const rawData = this.homeAdminForm.getRawValue();
775
+ let submittedHomeAdmin = new IHome();
776
+ if (this.formAdminData()) {
777
+ submittedHomeAdmin.id = this.formAdminData().id;
778
+ }
779
+ submittedHomeAdmin.title = rawData.title;
780
+ submittedHomeAdmin.description = rawData.description;
781
+ this.submitEvent.emit(submittedHomeAdmin);
782
+ }
783
+ onCancelClick() {
784
+ console.log('cancel');
785
+ }
786
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusHomeAdminComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
787
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.0", type: NgRhombusHomeAdminComponent, isStandalone: true, selector: "ng-rhombus-home-admin", inputs: { formAdminData: { classPropertyName: "formAdminData", publicName: "formAdminData", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cancelEvent: "cancelEvent", submitEvent: "submitEvent" }, ngImport: i0, template: "<mat-toolbar class=\"header-theme\">\r\n\t<span>Admin Component</span>\r\n</mat-toolbar>\r\n<div class=\"h-full flex flex-row\">\r\n\t<div class=\"flex flex-col w-1/2 p-4\">\r\n\t\t<form novalidate class=\"h-full flex flex-col\" [formGroup]=\"homeAdminForm\">\r\n\t\t\t<mat-form-field>\r\n\t\t\t\t<mat-label>Title</mat-label>\r\n\t\t\t\t<input matInput placeholder=\"Placeholder\" formControlName=\"title\">\r\n\t\t\t</mat-form-field>\r\n\t\t\t<mat-form-field>\r\n\t\t\t\t<mat-label>Description</mat-label>\r\n\t\t\t\t<input matInput placeholder=\"Placeholder\" formControlName=\"description\">\r\n\t\t\t</mat-form-field>\r\n\r\n\t\t\t<div class=\"flex\">\r\n\t\t\t\t<button mat-raised-button class=\"\" (click)=\"onCancelClick()\" type=\"button\">Cancel</button>\r\n\t\t\t\t<div class=\"flex-1\"></div>\r\n\t\t\t\t<button mat-flat-button [disabled]=\"homeAdminForm.invalid\" type=\"button\"\r\n\t\t\t\t\t(click)=\"onSubmit()\">Submit</button>\r\n\t\t\t</div>\r\n\t\t</form>\r\n\t</div>\r\n</div>", styles: [":host{height:100%;width:100%;box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.header-theme{--mat-toolbar-container-background-color: var(--mat-sys-outline);--mat-toolbar-container-text-color: var(--mat-sys-on-primary);--mat-icon-button-icon-color: var(--mat-sys-on-primary)}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatToolbarModule }, { kind: "component", type: i2$2.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i6.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i6.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: i6.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i6.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i6.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i6.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i7.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }] }); }
788
+ }
789
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusHomeAdminComponent, decorators: [{
790
+ type: Component,
791
+ args: [{ selector: 'ng-rhombus-home-admin', imports: [MatButtonModule, MatToolbarModule, ReactiveFormsModule, MatFormFieldModule, MatInputModule], template: "<mat-toolbar class=\"header-theme\">\r\n\t<span>Admin Component</span>\r\n</mat-toolbar>\r\n<div class=\"h-full flex flex-row\">\r\n\t<div class=\"flex flex-col w-1/2 p-4\">\r\n\t\t<form novalidate class=\"h-full flex flex-col\" [formGroup]=\"homeAdminForm\">\r\n\t\t\t<mat-form-field>\r\n\t\t\t\t<mat-label>Title</mat-label>\r\n\t\t\t\t<input matInput placeholder=\"Placeholder\" formControlName=\"title\">\r\n\t\t\t</mat-form-field>\r\n\t\t\t<mat-form-field>\r\n\t\t\t\t<mat-label>Description</mat-label>\r\n\t\t\t\t<input matInput placeholder=\"Placeholder\" formControlName=\"description\">\r\n\t\t\t</mat-form-field>\r\n\r\n\t\t\t<div class=\"flex\">\r\n\t\t\t\t<button mat-raised-button class=\"\" (click)=\"onCancelClick()\" type=\"button\">Cancel</button>\r\n\t\t\t\t<div class=\"flex-1\"></div>\r\n\t\t\t\t<button mat-flat-button [disabled]=\"homeAdminForm.invalid\" type=\"button\"\r\n\t\t\t\t\t(click)=\"onSubmit()\">Submit</button>\r\n\t\t\t</div>\r\n\t\t</form>\r\n\t</div>\r\n</div>", styles: [":host{height:100%;width:100%;box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.header-theme{--mat-toolbar-container-background-color: var(--mat-sys-outline);--mat-toolbar-container-text-color: var(--mat-sys-on-primary);--mat-icon-button-icon-color: var(--mat-sys-on-primary)}\n"] }]
792
+ }] });
793
+
794
+ // ...existing code...
795
+ var SocialsSource;
796
+ (function (SocialsSource) {
797
+ SocialsSource["Twitch"] = "Twitch";
798
+ SocialsSource["YouTube"] = "YouTube";
799
+ SocialsSource["Instagram"] = "Instagram";
800
+ SocialsSource["TikTok"] = "TikTok";
801
+ SocialsSource["Bluesky"] = "Bluesky";
802
+ })(SocialsSource || (SocialsSource = {}));
803
+ class NgRhombusSocialsService {
804
+ constructor() {
805
+ this.firestore = inject(Firestore);
806
+ this.socialsRef = collection(this.firestore, 'socials');
807
+ this.socials = signal([], ...(ngDevMode ? [{ debugName: "socials" }] : []));
808
+ }
809
+ // List
810
+ async fetchAll() {
811
+ const q = query(this.socialsRef, orderBy('source', 'asc'));
812
+ const snap = await getDocs(q);
813
+ const items = snap.docs.map(d => ({ ...d.data(), id: d.id, }));
814
+ this.socials.set(items);
815
+ return items;
816
+ }
817
+ // Read by id
818
+ async fetchById(id) {
819
+ const ref = doc(this.socialsRef, id);
820
+ const d = await getDoc(ref);
821
+ return d.exists() ? ({ ...d.data(), id: d.id, }) : undefined;
822
+ }
823
+ // Create (auto-id)
824
+ async create(social) {
825
+ const payload = { ...social, updatedAt: serverTimestamp() };
826
+ const newDoc = await addDoc(this.socialsRef, payload);
827
+ // Optimistic cache update
828
+ this.socials.set([...this.socials(), { ...social, id: newDoc.id, }]);
829
+ return newDoc.id;
830
+ }
831
+ // Update (partial)
832
+ async update(id, patch) {
833
+ const ref = doc(this.socialsRef, id);
834
+ await updateDoc(ref, { ...patch, updatedAt: serverTimestamp() });
835
+ this.socials.set(this.socials().map(s => (s.id === id ? { ...s, ...patch } : s)));
836
+ }
837
+ // Delete
838
+ async remove(id) {
839
+ const ref = doc(this.socialsRef, id);
840
+ await deleteDoc(ref);
841
+ this.socials.set(this.socials().filter(s => s.id !== id));
842
+ }
843
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusSocialsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
844
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusSocialsService, providedIn: 'root' }); }
845
+ }
846
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusSocialsService, decorators: [{
847
+ type: Injectable,
848
+ args: [{ providedIn: 'root' }]
849
+ }] });
850
+
851
+ class NgRhombusSocialsListComponent {
852
+ constructor() {
853
+ this.socials = input([], ...(ngDevMode ? [{ debugName: "socials" }] : []));
854
+ }
855
+ // Map source to Material icon names (adjust as desired)
856
+ iconFor(source) {
857
+ switch (source) {
858
+ case SocialsSource.Twitch: return 'sports_esports';
859
+ case SocialsSource.YouTube: return 'ondemand_video';
860
+ case SocialsSource.Instagram: return 'camera_alt';
861
+ case SocialsSource.TikTok: return 'music_note';
862
+ case SocialsSource.Bluesky: return 'cloud';
863
+ default: return 'link';
864
+ }
865
+ }
866
+ openUrl(url) {
867
+ if (!url)
868
+ return;
869
+ window.open(url, '_blank', 'noopener');
870
+ }
871
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusSocialsListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
872
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.0", type: NgRhombusSocialsListComponent, isStandalone: true, selector: "ng-rhombus-socials-list", inputs: { socials: { classPropertyName: "socials", publicName: "socials", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if ((socials()).length === 0) {\r\n<p>No socials found.</p>\r\n} @else {\r\n<div class=\"toolbar-list\">\r\n @for (s of socials(); track s.id) {\r\n <mat-toolbar class=\"social-toolbar actionable\" color=\"primary\" role=\"button\" tabindex=\"0\" (click)=\"openUrl(s.url)\"\r\n aria-label=\"Open {{ s.source }} link\">\r\n <mat-icon class=\"left\">{{ iconFor(s.source) }}</mat-icon>\r\n <span class=\"center\">{{ s.source }}</span>\r\n @if (s.url) {\r\n <mat-icon class=\"right\">open_in_new</mat-icon>\r\n }\r\n </mat-toolbar>\r\n }\r\n</div>\r\n}", styles: [".social-toolbar{--mat-toolbar-container-background-color: var(--mat-sys-surface);--mat-toolbar-container-text-color: var(--mat-sys-on-surface);--mat-icon-button-icon-color: var(--mat-sys-on-surface);box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f;display:grid;grid-template-columns:auto 1fr auto;align-items:center;min-height:48px;width:100%}.toolbar-list{display:grid;gap:8px;margin:12px}.actionable{cursor:pointer;transition:background-color .14s ease,box-shadow .14s ease}.actionable:hover{filter:brightness(1.08);box-shadow:0 2px 6px #00000026}.actionable:focus{outline:2px solid rgba(255,255,255,.8);outline-offset:2px}.left{margin-right:8px}.center{text-align:center}.right{display:inline-flex;align-items:center}\n"], dependencies: [{ kind: "ngmodule", type: MatToolbarModule }, { kind: "component", type: i2$2.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }] }); }
873
+ }
874
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusSocialsListComponent, decorators: [{
875
+ type: Component,
876
+ args: [{ selector: 'ng-rhombus-socials-list', standalone: true, imports: [MatToolbarModule, MatIconModule], template: "@if ((socials()).length === 0) {\r\n<p>No socials found.</p>\r\n} @else {\r\n<div class=\"toolbar-list\">\r\n @for (s of socials(); track s.id) {\r\n <mat-toolbar class=\"social-toolbar actionable\" color=\"primary\" role=\"button\" tabindex=\"0\" (click)=\"openUrl(s.url)\"\r\n aria-label=\"Open {{ s.source }} link\">\r\n <mat-icon class=\"left\">{{ iconFor(s.source) }}</mat-icon>\r\n <span class=\"center\">{{ s.source }}</span>\r\n @if (s.url) {\r\n <mat-icon class=\"right\">open_in_new</mat-icon>\r\n }\r\n </mat-toolbar>\r\n }\r\n</div>\r\n}", styles: [".social-toolbar{--mat-toolbar-container-background-color: var(--mat-sys-surface);--mat-toolbar-container-text-color: var(--mat-sys-on-surface);--mat-icon-button-icon-color: var(--mat-sys-on-surface);box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f;display:grid;grid-template-columns:auto 1fr auto;align-items:center;min-height:48px;width:100%}.toolbar-list{display:grid;gap:8px;margin:12px}.actionable{cursor:pointer;transition:background-color .14s ease,box-shadow .14s ease}.actionable:hover{filter:brightness(1.08);box-shadow:0 2px 6px #00000026}.actionable:focus{outline:2px solid rgba(255,255,255,.8);outline-offset:2px}.left{margin-right:8px}.center{text-align:center}.right{display:inline-flex;align-items:center}\n"] }]
877
+ }] });
878
+
879
+ class NgRhombusSocialsTableComponent {
880
+ constructor() {
881
+ this.socials = input([], ...(ngDevMode ? [{ debugName: "socials" }] : []));
882
+ this.displayedColumns = ['id', 'source', 'url', 'actions'];
883
+ // Dropdown options
884
+ this.socialTypeOptions = [
885
+ { label: 'Twitch', value: SocialsSource.Twitch },
886
+ { label: 'YouTube', value: SocialsSource.YouTube },
887
+ { label: 'Instagram', value: SocialsSource.Instagram },
888
+ { label: 'TikTok', value: SocialsSource.TikTok },
889
+ { label: 'Bluesky', value: SocialsSource.Bluesky }
890
+ ];
891
+ this.fb = inject(FormBuilder);
892
+ this.svc = inject(NgRhombusSocialsService);
893
+ // simple inline form state
894
+ this.editingId = signal(null, ...(ngDevMode ? [{ debugName: "editingId" }] : []));
895
+ this.form = this.fb.group({
896
+ source: this.fb.control(null, { validators: [Validators.required] }),
897
+ url: this.fb.control('', { nonNullable: true, validators: [Validators.required] })
898
+ });
899
+ }
900
+ startCreate() {
901
+ this.editingId.set('');
902
+ this.form.reset({ source: null, url: '' });
903
+ }
904
+ startEdit(row) {
905
+ this.editingId.set(row.id);
906
+ this.form.reset({ source: row.source, url: row.url });
907
+ }
908
+ cancelEdit() {
909
+ this.editingId.set(null);
910
+ this.form.reset({ source: null, url: '' });
911
+ }
912
+ async save() {
913
+ const id = this.editingId();
914
+ const { source, url } = this.form.getRawValue();
915
+ if (!source || !url)
916
+ return;
917
+ if (id === '') {
918
+ await this.svc.create({ source, url });
919
+ }
920
+ else {
921
+ await this.svc.update(id, { source, url });
922
+ }
923
+ this.cancelEdit();
924
+ }
925
+ async delete(row) {
926
+ await this.svc.remove(row.id);
927
+ }
928
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusSocialsTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
929
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.0", type: NgRhombusSocialsTableComponent, isStandalone: true, selector: "ng-rhombus-socials-table", inputs: { socials: { classPropertyName: "socials", publicName: "socials", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"actions-bar\">\r\n <button mat-button color=\"primary\" (click)=\"startCreate()\">\r\n <mat-icon>add</mat-icon> Add Social\r\n </button>\r\n</div>\r\n\r\n<table mat-table [dataSource]=\"socials()\" class=\"mat-elevation-z1\">\r\n <ng-container matColumnDef=\"id\">\r\n <th mat-header-cell *matHeaderCellDef>ID</th>\r\n <td mat-cell *matCellDef=\"let s\">{{ s.id }}</td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"source\">\r\n <th mat-header-cell *matHeaderCellDef>Source</th>\r\n <td mat-cell *matCellDef=\"let s\">{{ s.source }}</td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"url\">\r\n <th mat-header-cell *matHeaderCellDef>URL</th>\r\n <td mat-cell *matCellDef=\"let s\">\r\n <a [href]=\"s.url\" target=\"_blank\" rel=\"noopener\">{{ s.url }}</a>\r\n </td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"actions\">\r\n <th mat-header-cell *matHeaderCellDef>Actions</th>\r\n <td mat-cell *matCellDef=\"let s\">\r\n <button mat-icon-button color=\"primary\" (click)=\"startEdit(s)\"><mat-icon>edit</mat-icon></button>\r\n <button mat-icon-button color=\"warn\" (click)=\"delete(s)\"><mat-icon>delete</mat-icon></button>\r\n </td>\r\n </ng-container>\r\n\r\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\r\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\r\n</table>\r\n\r\n@if (editingId() !== null) {\r\n<form class=\"edit-form\" [formGroup]=\"form\" (ngSubmit)=\"save()\">\r\n <h3>{{ editingId() === '' ? 'Create Social' : 'Edit Social' }}</h3>\r\n\r\n <mat-form-field appearance=\"fill\" class=\"full-width\">\r\n <mat-label>Source</mat-label>\r\n <mat-select formControlName=\"source\">\r\n @for (opt of socialTypeOptions; track opt.value) {\r\n <mat-option [value]=\"opt.value\">{{ opt.label }}</mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n\r\n <div class=\"form-row\">\r\n <mat-form-field appearance=\"fill\" class=\"full-width\">\r\n <mat-label>URL</mat-label>\r\n <input matInput formControlName=\"url\" placeholder=\"https://example.com/handle\">\r\n </mat-form-field>\r\n </div>\r\n\r\n <div class=\"form-actions\">\r\n <button mat-button type=\"submit\" color=\"primary\">Save</button>\r\n <button mat-button type=\"button\" (click)=\"cancelEdit()\">Cancel</button>\r\n </div>\r\n</form>\r\n}\r\n\r\n@if ((socials()).length === 0) {\r\n<p>No socials found.</p>\r\n}", styles: [".full-width{width:100%}\n"], dependencies: [{ kind: "ngmodule", type: MatTableModule }, { kind: "component", type: i3$2.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i3$2.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i3$2.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i3$2.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i3$2.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i3$2.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i3$2.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i3$2.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i3$2.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i3$2.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i1$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "component", type: i4$3.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i4$3.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i6.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i6.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: i6.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i6.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i6.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i6.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i7.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }] }); }
930
+ }
931
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: NgRhombusSocialsTableComponent, decorators: [{
932
+ type: Component,
933
+ args: [{ selector: 'ng-rhombus-socials-table', standalone: true, imports: [MatTableModule, MatButtonModule, MatSelectModule, MatIconModule, ReactiveFormsModule, MatFormFieldModule, MatInputModule], template: "<div class=\"actions-bar\">\r\n <button mat-button color=\"primary\" (click)=\"startCreate()\">\r\n <mat-icon>add</mat-icon> Add Social\r\n </button>\r\n</div>\r\n\r\n<table mat-table [dataSource]=\"socials()\" class=\"mat-elevation-z1\">\r\n <ng-container matColumnDef=\"id\">\r\n <th mat-header-cell *matHeaderCellDef>ID</th>\r\n <td mat-cell *matCellDef=\"let s\">{{ s.id }}</td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"source\">\r\n <th mat-header-cell *matHeaderCellDef>Source</th>\r\n <td mat-cell *matCellDef=\"let s\">{{ s.source }}</td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"url\">\r\n <th mat-header-cell *matHeaderCellDef>URL</th>\r\n <td mat-cell *matCellDef=\"let s\">\r\n <a [href]=\"s.url\" target=\"_blank\" rel=\"noopener\">{{ s.url }}</a>\r\n </td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"actions\">\r\n <th mat-header-cell *matHeaderCellDef>Actions</th>\r\n <td mat-cell *matCellDef=\"let s\">\r\n <button mat-icon-button color=\"primary\" (click)=\"startEdit(s)\"><mat-icon>edit</mat-icon></button>\r\n <button mat-icon-button color=\"warn\" (click)=\"delete(s)\"><mat-icon>delete</mat-icon></button>\r\n </td>\r\n </ng-container>\r\n\r\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\r\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\r\n</table>\r\n\r\n@if (editingId() !== null) {\r\n<form class=\"edit-form\" [formGroup]=\"form\" (ngSubmit)=\"save()\">\r\n <h3>{{ editingId() === '' ? 'Create Social' : 'Edit Social' }}</h3>\r\n\r\n <mat-form-field appearance=\"fill\" class=\"full-width\">\r\n <mat-label>Source</mat-label>\r\n <mat-select formControlName=\"source\">\r\n @for (opt of socialTypeOptions; track opt.value) {\r\n <mat-option [value]=\"opt.value\">{{ opt.label }}</mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n\r\n <div class=\"form-row\">\r\n <mat-form-field appearance=\"fill\" class=\"full-width\">\r\n <mat-label>URL</mat-label>\r\n <input matInput formControlName=\"url\" placeholder=\"https://example.com/handle\">\r\n </mat-form-field>\r\n </div>\r\n\r\n <div class=\"form-actions\">\r\n <button mat-button type=\"submit\" color=\"primary\">Save</button>\r\n <button mat-button type=\"button\" (click)=\"cancelEdit()\">Cancel</button>\r\n </div>\r\n</form>\r\n}\r\n\r\n@if ((socials()).length === 0) {\r\n<p>No socials found.</p>\r\n}", styles: [".full-width{width:100%}\n"] }]
934
+ }] });
935
+
936
+ /*
937
+ * Public API Surface of @doug-williamson/ng-rhombus
938
+ */
939
+
940
+ /**
941
+ * Generated bundle index. Do not edit.
942
+ */
943
+
944
+ export { IBlog, NgRhombusBlogAddEditComponent, NgRhombusBlogDeletePostComponent, NgRhombusBlogListComponent, NgRhombusBlogPostComponent, NgRhombusBlogPostLatestComponent, NgRhombusBlogPostThumbnailService, NgRhombusBlogService, NgRhombusBlogTableComponent, NgRhombusHomeAdminComponent, NgRhombusLoginComponent, NgRhombusSocialsListComponent, NgRhombusSocialsService, NgRhombusSocialsTableComponent, NgRhombusWrapperComponent, SocialsSource, ThemeEnum, WrapperService };
945
+ //# sourceMappingURL=doug-williamson-ng-rhombus.mjs.map