@sneat/space-components 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm2022/lib/spaces-card/spaces-card.component.js +80 -140
- package/esm2022/lib/spaces-card/spaces-card.component.js.map +1 -1
- package/esm2022/lib/spaces-list/spaces-list.component.js +36 -4
- package/esm2022/lib/spaces-list/spaces-list.component.js.map +1 -1
- package/esm2022/lib/spaces-menu/spaces-menu.component.js +1 -1
- package/esm2022/lib/spaces-menu/spaces-menu.component.js.map +1 -1
- package/lib/spaces-card/spaces-card.component.d.ts +15 -19
- package/lib/spaces-list/spaces-list.component.d.ts +4 -1
- package/package.json +1 -1
- package/tsconfig.lib.prod.tsbuildinfo +1 -1
|
@@ -1,15 +1,24 @@
|
|
|
1
|
-
import { Component,
|
|
1
|
+
import { ChangeDetectionStrategy, Component, computed, effect, inject, signal, viewChild, } from '@angular/core';
|
|
2
|
+
import { toSignal } from '@angular/core/rxjs-interop';
|
|
2
3
|
import { FormsModule } from '@angular/forms';
|
|
3
|
-
import { IonButton, IonButtons, IonCard, IonCardContent, IonCardTitle, IonIcon, IonInput, IonItem,
|
|
4
|
+
import { IonButton, IonButtons, IonCard, IonCardContent, IonCardTitle, IonIcon, IonInput, IonItem, IonLabel, IonList, IonSkeletonText, IonSpinner, ToastController, } from '@ionic/angular/standalone';
|
|
4
5
|
import { AnalyticsService } from '@sneat/core';
|
|
5
6
|
import { ErrorLogger } from '@sneat/core';
|
|
6
7
|
import { SpaceNavService, SpaceService } from '@sneat/space-services';
|
|
7
8
|
import { SneatUserService } from '@sneat/auth-core';
|
|
8
|
-
import {
|
|
9
|
-
import { takeUntil } from 'rxjs/operators';
|
|
9
|
+
import { SpacesListComponent } from '../spaces-list';
|
|
10
10
|
import * as i0 from "@angular/core";
|
|
11
11
|
import * as i1 from "@angular/forms";
|
|
12
|
+
// Signal-based + OnPush so the card repaints reactively when the user record
|
|
13
|
+
// loads, instead of mutating fields inside an rxjs subscription and relying on
|
|
14
|
+
// Zone change detection. The previous version stayed stuck on "Authenticating..."
|
|
15
|
+
// when the Firestore onSnapshot update landed outside the Angular zone (the
|
|
16
|
+
// record loaded but the view never repainted). toSignal()/computed() repaint
|
|
17
|
+
// correctly under zone.js too — this is not a zoneless change.
|
|
12
18
|
export class SpacesCardComponent {
|
|
19
|
+
get currentUserID() {
|
|
20
|
+
return this.userService.currentUserID ?? '';
|
|
21
|
+
}
|
|
13
22
|
constructor() {
|
|
14
23
|
this.errorLogger = inject(ErrorLogger);
|
|
15
24
|
this.navService = inject(SpaceNavService);
|
|
@@ -17,173 +26,109 @@ export class SpacesCardComponent {
|
|
|
17
26
|
this.spaceService = inject(SpaceService);
|
|
18
27
|
this.analyticsService = inject(AnalyticsService);
|
|
19
28
|
this.toastController = inject(ToastController);
|
|
20
|
-
this.
|
|
21
|
-
this.
|
|
22
|
-
|
|
23
|
-
this.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
// console.log('SpacesCardComponent => user:', userState);
|
|
28
|
-
const user = userState.record;
|
|
29
|
-
if (user) {
|
|
30
|
-
this.spaces = Object.entries(user?.spaces ? user.spaces : {}).map(([id, team]) => ({ id, brief: team }));
|
|
31
|
-
this.spaces.sort((a, b) => (a.brief.title > b.brief.title ? 1 : -1));
|
|
32
|
-
this.showAdd = !this.spaces?.length;
|
|
33
|
-
if (this.showAdd) {
|
|
34
|
-
this.startAddingSpace();
|
|
35
|
-
}
|
|
29
|
+
this.addSpaceInput = viewChild('addTeamInput', ...(ngDevMode ? [{ debugName: "addSpaceInput" }] : []));
|
|
30
|
+
this.userState = toSignal(this.userService.userState);
|
|
31
|
+
// undefined => user record not loaded yet (render the loading row).
|
|
32
|
+
this.spaces = computed(() => {
|
|
33
|
+
const record = this.userState()?.record;
|
|
34
|
+
if (!record) {
|
|
35
|
+
return undefined;
|
|
36
36
|
}
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
return Object.entries(record.spaces ?? {})
|
|
38
|
+
.map(([id, brief]) => ({ id, brief }))
|
|
39
|
+
.sort((a, b) => (a.brief.title > b.brief.title ? 1 : -1));
|
|
40
|
+
}, ...(ngDevMode ? [{ debugName: "spaces" }] : []));
|
|
41
|
+
// Adapts the user's spaces to the shape SpacesListComponent renders, so the
|
|
42
|
+
// card reuses that component (icon + title decode + navigation) instead of
|
|
43
|
+
// duplicating the row markup.
|
|
44
|
+
this.spaceContexts = computed(() => this.spaces()?.map(({ id, brief }) => ({
|
|
45
|
+
id,
|
|
46
|
+
type: brief.type,
|
|
47
|
+
brief: { title: brief.title, type: brief.type, roles: brief.roles },
|
|
48
|
+
})), ...(ngDevMode ? [{ debugName: "spaceContexts" }] : []));
|
|
49
|
+
this.loadingState = computed(() => this.userState()?.status === 'authenticated' ? 'Loading' : 'Authenticating', ...(ngDevMode ? [{ debugName: "loadingState" }] : []));
|
|
50
|
+
this.showAdd = signal(false, ...(ngDevMode ? [{ debugName: "showAdd" }] : []));
|
|
51
|
+
this.spaceName = signal('', ...(ngDevMode ? [{ debugName: "spaceName" }] : []));
|
|
52
|
+
this.adding = signal(false, ...(ngDevMode ? [{ debugName: "adding" }] : []));
|
|
53
|
+
// Auto-open the "add space" form once we know the user has no spaces.
|
|
54
|
+
effect(() => {
|
|
55
|
+
const spaces = this.spaces();
|
|
56
|
+
if (spaces && spaces.length === 0) {
|
|
57
|
+
this.startAddingSpace();
|
|
39
58
|
}
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
ngOnDestroy() {
|
|
43
|
-
// console.log('SpacesCardComponent.ngOnDestroy()');
|
|
44
|
-
this.destroyed.next();
|
|
45
|
-
this.destroyed.complete();
|
|
46
|
-
this.unsubscribe('ngOnDestroy');
|
|
47
|
-
}
|
|
48
|
-
ngOnInit() {
|
|
49
|
-
this.watchUserRecord();
|
|
59
|
+
});
|
|
50
60
|
}
|
|
51
|
-
|
|
61
|
+
navigateToSpace(space) {
|
|
52
62
|
this.navService
|
|
53
63
|
.navigateToSpace(space, 'forward')
|
|
54
64
|
.catch(this.errorLogger.logError);
|
|
55
65
|
}
|
|
56
66
|
addSpace() {
|
|
57
67
|
this.analyticsService.logEvent('addSpace');
|
|
58
|
-
const title = this.spaceName.trim();
|
|
68
|
+
const title = this.spaceName().trim();
|
|
59
69
|
if (!title) {
|
|
60
|
-
this.
|
|
61
|
-
.create({
|
|
62
|
-
position: 'middle',
|
|
63
|
-
message: 'Space name is required',
|
|
64
|
-
color: 'tertiary',
|
|
65
|
-
duration: 5000,
|
|
66
|
-
keyboardClose: true,
|
|
67
|
-
buttons: [{ role: 'cancel', text: 'OK' }],
|
|
68
|
-
})
|
|
69
|
-
.then((toast) => toast
|
|
70
|
-
.present()
|
|
71
|
-
.catch((err) => this.errorLogger.logError(err, 'Failed to present toast')))
|
|
72
|
-
.catch((err) => this.errorLogger.logError(err, 'Faile to create toast'));
|
|
70
|
+
this.presentToast('Space name is required', 'tertiary');
|
|
73
71
|
return;
|
|
74
72
|
}
|
|
75
|
-
if (this.spaces?.find((t) => t.brief.title === title)) {
|
|
76
|
-
this.
|
|
77
|
-
.create({
|
|
78
|
-
message: 'You already have a team with the same name',
|
|
79
|
-
color: 'danger',
|
|
80
|
-
buttons: ['close'],
|
|
81
|
-
position: 'middle',
|
|
82
|
-
animated: true,
|
|
83
|
-
duration: 3000,
|
|
84
|
-
})
|
|
85
|
-
.then((toast) => {
|
|
86
|
-
toast
|
|
87
|
-
.present()
|
|
88
|
-
.catch((err) => this.errorLogger.logError(err, 'Failed to present toast'));
|
|
89
|
-
})
|
|
90
|
-
.catch((err) => this.errorLogger.logError(err, 'Failed to create toast'));
|
|
73
|
+
if (this.spaces()?.find((t) => t.brief.title === title)) {
|
|
74
|
+
this.presentToast('You already have a team with the same name', 'danger');
|
|
91
75
|
return;
|
|
92
76
|
}
|
|
93
77
|
const request = {
|
|
94
78
|
type: 'team',
|
|
95
|
-
// memberType: TeamMemberType.creator,
|
|
96
79
|
title,
|
|
97
80
|
};
|
|
98
|
-
this.adding
|
|
81
|
+
this.adding.set(true);
|
|
99
82
|
this.spaceService.createSpace(request).subscribe({
|
|
100
83
|
next: (space) => {
|
|
101
84
|
this.analyticsService.logEvent('spaceCreated', { space: space.id });
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
// memberType: request.memberType,
|
|
107
|
-
type: space?.dbo?.type || 'unknown',
|
|
108
|
-
};
|
|
109
|
-
if (userTeamBrief2 && !this.spaces?.find((t) => t.id === space.id)) {
|
|
110
|
-
this.spaces?.push({ id: space.id, brief: userTeamBrief2 });
|
|
111
|
-
}
|
|
112
|
-
this.adding = false;
|
|
113
|
-
this.spaceName = '';
|
|
114
|
-
this.goSpace(space);
|
|
85
|
+
this.adding.set(false);
|
|
86
|
+
this.spaceName.set('');
|
|
87
|
+
// The user record updates via Firestore, which recomputes `spaces`.
|
|
88
|
+
this.navigateToSpace(space);
|
|
115
89
|
},
|
|
116
90
|
error: (err) => {
|
|
117
91
|
this.errorLogger.logError(err, 'Failed to create new team record');
|
|
118
|
-
this.adding
|
|
92
|
+
this.adding.set(false);
|
|
119
93
|
},
|
|
120
94
|
});
|
|
121
95
|
}
|
|
96
|
+
presentToast(message, color) {
|
|
97
|
+
this.toastController
|
|
98
|
+
.create({
|
|
99
|
+
position: 'middle',
|
|
100
|
+
message,
|
|
101
|
+
color,
|
|
102
|
+
duration: 5000,
|
|
103
|
+
keyboardClose: true,
|
|
104
|
+
buttons: [{ role: 'cancel', text: 'OK' }],
|
|
105
|
+
})
|
|
106
|
+
.then((toast) => toast
|
|
107
|
+
.present()
|
|
108
|
+
.catch((err) => this.errorLogger.logError(err, 'Failed to present toast')))
|
|
109
|
+
.catch((err) => this.errorLogger.logError(err, 'Failed to create toast'));
|
|
110
|
+
}
|
|
122
111
|
startAddingSpace() {
|
|
123
|
-
this.showAdd
|
|
112
|
+
this.showAdd.set(true);
|
|
124
113
|
setTimeout(() => {
|
|
125
|
-
|
|
126
|
-
|
|
114
|
+
const input = this.addSpaceInput();
|
|
115
|
+
if (!input) {
|
|
127
116
|
return;
|
|
128
117
|
}
|
|
129
|
-
|
|
118
|
+
input
|
|
130
119
|
.setFocus()
|
|
131
|
-
.catch((err) => this.errorLogger.logError(err, 'Failed to set focus to
|
|
120
|
+
.catch((err) => this.errorLogger.logError(err, 'Failed to set focus to addTeamInput'));
|
|
132
121
|
}, 200);
|
|
133
122
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
event.stopPropagation();
|
|
137
|
-
event.preventDefault();
|
|
138
|
-
}
|
|
139
|
-
if (!confirm(`Are you sure you want to leave team ${space.brief.title}?`)) {
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
const userID = this.userService.currentUserID;
|
|
143
|
-
if (!userID) {
|
|
144
|
-
this.errorLogger.logError('Failed to get current user ID');
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
this.spaceService.leaveSpace({ spaceID: space.id }).subscribe({
|
|
148
|
-
next: () => console.log('left space'),
|
|
149
|
-
error: (err) => this.errorLogger.logError(err, `Failed to leave a space: ${space.brief.title}`),
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
watchUserRecord() {
|
|
153
|
-
this.userService.userState.pipe(takeUntil(this.destroyed)).subscribe({
|
|
154
|
-
next: (userState) => {
|
|
155
|
-
// console.log('SpacesCardComponent => user state changed:', userState);
|
|
156
|
-
if (userState.status === 'authenticating') {
|
|
157
|
-
if (this.loadingState === 'Authenticating') {
|
|
158
|
-
this.loadingState = 'Loading';
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
const uid = userState.user?.uid;
|
|
162
|
-
this.spaces = undefined;
|
|
163
|
-
if (!uid) {
|
|
164
|
-
this.unsubscribe('user signed out');
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
this.subscriptions.push(this.userService.userState.subscribe({
|
|
168
|
-
next: this.setUser,
|
|
169
|
-
error: (err) => this.errorLogger.logError(err, 'Failed to get user record'),
|
|
170
|
-
}));
|
|
171
|
-
},
|
|
172
|
-
error: (err) => this.errorLogger.logError(err, 'Failed to get user ID'),
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
176
|
-
unsubscribe(_reason) {
|
|
177
|
-
// console.log(`SpacesCardComponent.unsubscribe(reason: ${reason})`);
|
|
178
|
-
this.subscriptions.forEach((s) => s.unsubscribe());
|
|
179
|
-
this.subscriptions = [];
|
|
123
|
+
cancelAdd() {
|
|
124
|
+
this.showAdd.set(false);
|
|
180
125
|
}
|
|
181
126
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SpacesCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
182
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: SpacesCardComponent, isStandalone: true, selector: "sneat-spaces-card", viewQueries: [{ propertyName: "addSpaceInput", first: true, predicate:
|
|
127
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: SpacesCardComponent, isStandalone: true, selector: "sneat-spaces-card", viewQueries: [{ propertyName: "addSpaceInput", first: true, predicate: ["addTeamInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<ion-card>\n <ion-item>\n <ion-label>\n <ion-card-title color=\"medium\">Spaces</ion-card-title>\n </ion-label>\n <ion-buttons slot=\"end\" (click)=\"startAddingSpace()\">\n @if (!showAdd()) {\n <ion-button color=\"primary\">\n <ion-icon name=\"add\" slot=\"start\" />\n <ion-label>Add</ion-label>\n </ion-button>\n }\n </ion-buttons>\n </ion-item>\n @if (spaceContexts(); as spaceContexts) {\n <ion-list>\n <sneat-spaces-list\n [userID]=\"currentUserID\"\n [spaces]=\"spaceContexts\"\n [allowLeave]=\"true\"\n />\n </ion-list>\n } @else {\n <ion-list>\n <ion-item>\n <ion-spinner name=\"\" slot=\"start\" color=\"medium\" />\n <ion-buttons slot=\"start\">\n <ion-button disabled=\"disabled\" style=\"text-transform: none\"\n >{{ loadingState() }}...\n </ion-button>\n </ion-buttons>\n <ion-skeleton-text animated />\n </ion-item>\n </ion-list>\n }\n\n @if (showAdd()) {\n <ion-item [disabled]=\"adding()\">\n <ion-input\n (keyup.enter)=\"addSpace()\"\n #addTeamInput\n [ngModel]=\"spaceName()\"\n (ngModelChange)=\"spaceName.set($event)\"\n (keyup.escape)=\"cancelAdd()\"\n placeholder=\"New team name\"\n />\n <ion-buttons slot=\"end\">\n <ion-button color=\"primary\" fill=\"solid\" (click)=\"addSpace()\">\n <ion-label>Create</ion-label>\n </ion-button>\n @if (!!spaces()?.length) {\n <ion-button (click)=\"cancelAdd()\" color=\"medium\" title=\"Cancel\">\n @if (adding()) {\n <ion-spinner />\n } @else {\n <ion-icon name=\"close-outline\" />\n }\n </ion-button>\n }\n </ion-buttons>\n </ion-item>\n }\n @if (showAdd()) {\n <ion-card-content>\n <p>Enter team name and click \"Create\" button to add a new team.</p>\n </ion-card-content>\n }\n</ion-card>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: IonInput, selector: "ion-input", inputs: ["accept", "autocapitalize", "autocomplete", "autocorrect", "autofocus", "clearInput", "clearOnEdit", "color", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "max", "maxlength", "min", "minlength", "mode", "multiple", "name", "pattern", "placeholder", "readonly", "required", "shape", "size", "spellcheck", "step", "type", "value"] }, { kind: "component", type: IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonCardTitle, selector: "ion-card-title", inputs: ["color", "mode"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonSkeletonText, selector: "ion-skeleton-text", inputs: ["animated"] }, { kind: "component", type: IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: SpacesListComponent, selector: "sneat-spaces-list", inputs: ["userID", "spaces", "pathPrefix", "allowLeave"], outputs: ["beforeNavigateToSpace", "leftSpace"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
183
128
|
}
|
|
184
129
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SpacesCardComponent, decorators: [{
|
|
185
130
|
type: Component,
|
|
186
|
-
args: [{ selector: 'sneat-spaces-card', imports: [
|
|
131
|
+
args: [{ selector: 'sneat-spaces-card', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
187
132
|
FormsModule,
|
|
188
133
|
IonInput,
|
|
189
134
|
IonCard,
|
|
@@ -194,15 +139,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
194
139
|
IonButton,
|
|
195
140
|
IonIcon,
|
|
196
141
|
IonList,
|
|
197
|
-
IonItemSliding,
|
|
198
|
-
IonItemOptions,
|
|
199
|
-
IonItemOption,
|
|
200
142
|
IonSpinner,
|
|
201
143
|
IonSkeletonText,
|
|
202
144
|
IonCardContent,
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
args: [IonInput, { static: false }]
|
|
207
|
-
}] } });
|
|
145
|
+
SpacesListComponent,
|
|
146
|
+
], template: "<ion-card>\n <ion-item>\n <ion-label>\n <ion-card-title color=\"medium\">Spaces</ion-card-title>\n </ion-label>\n <ion-buttons slot=\"end\" (click)=\"startAddingSpace()\">\n @if (!showAdd()) {\n <ion-button color=\"primary\">\n <ion-icon name=\"add\" slot=\"start\" />\n <ion-label>Add</ion-label>\n </ion-button>\n }\n </ion-buttons>\n </ion-item>\n @if (spaceContexts(); as spaceContexts) {\n <ion-list>\n <sneat-spaces-list\n [userID]=\"currentUserID\"\n [spaces]=\"spaceContexts\"\n [allowLeave]=\"true\"\n />\n </ion-list>\n } @else {\n <ion-list>\n <ion-item>\n <ion-spinner name=\"\" slot=\"start\" color=\"medium\" />\n <ion-buttons slot=\"start\">\n <ion-button disabled=\"disabled\" style=\"text-transform: none\"\n >{{ loadingState() }}...\n </ion-button>\n </ion-buttons>\n <ion-skeleton-text animated />\n </ion-item>\n </ion-list>\n }\n\n @if (showAdd()) {\n <ion-item [disabled]=\"adding()\">\n <ion-input\n (keyup.enter)=\"addSpace()\"\n #addTeamInput\n [ngModel]=\"spaceName()\"\n (ngModelChange)=\"spaceName.set($event)\"\n (keyup.escape)=\"cancelAdd()\"\n placeholder=\"New team name\"\n />\n <ion-buttons slot=\"end\">\n <ion-button color=\"primary\" fill=\"solid\" (click)=\"addSpace()\">\n <ion-label>Create</ion-label>\n </ion-button>\n @if (!!spaces()?.length) {\n <ion-button (click)=\"cancelAdd()\" color=\"medium\" title=\"Cancel\">\n @if (adding()) {\n <ion-spinner />\n } @else {\n <ion-icon name=\"close-outline\" />\n }\n </ion-button>\n }\n </ion-buttons>\n </ion-item>\n }\n @if (showAdd()) {\n <ion-card-content>\n <p>Enter team name and click \"Create\" button to add a new team.</p>\n </ion-card-content>\n }\n</ion-card>\n" }]
|
|
147
|
+
}], ctorParameters: () => [], propDecorators: { addSpaceInput: [{ type: i0.ViewChild, args: ['addTeamInput', { isSignal: true }] }] } });
|
|
208
148
|
//# sourceMappingURL=spaces-card.component.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spaces-card.component.js","sourceRoot":"","sources":["../../../../../../../libs/space/components/src/lib/spaces-card/spaces-card.component.ts","../../../../../../../libs/space/components/src/lib/spaces-card/spaces-card.component.html"],"names":[],"mappings":"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","sourcesContent":["import { Component, OnDestroy, OnInit, ViewChild, inject } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport {\n IonButton,\n IonButtons,\n IonCard,\n IonCardContent,\n IonCardTitle,\n IonIcon,\n IonInput,\n IonItem,\n IonItemOption,\n IonItemOptions,\n IonItemSliding,\n IonLabel,\n IonList,\n IonSkeletonText,\n IonSpinner,\n ToastController,\n} from '@ionic/angular/standalone';\nimport { AnalyticsService, IAnalyticsService } from '@sneat/core';\nimport { IUserSpaceBrief } from '@sneat/auth-models';\nimport { IIdAndBrief } from '@sneat/core';\nimport { ErrorLogger, IErrorLogger } from '@sneat/core';\nimport { ICreateSpaceRequest, ISpaceContext } from '@sneat/space-models';\nimport { SpaceNavService, SpaceService } from '@sneat/space-services';\nimport { ISneatUserState, SneatUserService } from '@sneat/auth-core';\nimport { Subject, Subscription } from 'rxjs';\nimport { takeUntil } from 'rxjs/operators';\n\n@Component({\n selector: 'sneat-spaces-card',\n templateUrl: './spaces-card.component.html',\n imports: [\n FormsModule,\n IonInput,\n IonCard,\n IonItem,\n IonLabel,\n IonCardTitle,\n IonButtons,\n IonButton,\n IonIcon,\n IonList,\n IonItemSliding,\n IonItemOptions,\n IonItemOption,\n IonSpinner,\n IonSkeletonText,\n IonCardContent,\n ],\n})\nexport class SpacesCardComponent implements OnInit, OnDestroy {\n private readonly errorLogger = inject<IErrorLogger>(ErrorLogger);\n private readonly navService = inject(SpaceNavService);\n private readonly userService = inject(SneatUserService);\n private readonly spaceService = inject(SpaceService);\n private readonly analyticsService =\n inject<IAnalyticsService>(AnalyticsService);\n private readonly toastController = inject(ToastController);\n\n @ViewChild(IonInput, { static: false }) addSpaceInput?: IonInput; // TODO: IonInput;\n\n public spaces?: IIdAndBrief<IUserSpaceBrief>[];\n public loadingState: 'Authenticating' | 'Loading' = 'Authenticating';\n public spaceName = '';\n public adding = false;\n public showAdd = false; //\n private readonly destroyed = new Subject<void>();\n private subscriptions: Subscription[] = [];\n\n public ngOnDestroy(): void {\n // console.log('SpacesCardComponent.ngOnDestroy()');\n this.destroyed.next();\n this.destroyed.complete();\n this.unsubscribe('ngOnDestroy');\n }\n\n public ngOnInit(): void {\n this.watchUserRecord();\n }\n\n public goSpace(space: ISpaceContext) {\n this.navService\n .navigateToSpace(space, 'forward')\n .catch(this.errorLogger.logError);\n }\n\n public addSpace() {\n this.analyticsService.logEvent('addSpace');\n const title = this.spaceName.trim();\n if (!title) {\n this.toastController\n .create({\n position: 'middle',\n message: 'Space name is required',\n color: 'tertiary',\n duration: 5000,\n keyboardClose: true,\n buttons: [{ role: 'cancel', text: 'OK' }],\n })\n .then((toast) =>\n toast\n .present()\n .catch((err) =>\n this.errorLogger.logError(err, 'Failed to present toast'),\n ),\n )\n .catch((err) =>\n this.errorLogger.logError(err, 'Faile to create toast'),\n );\n return;\n }\n if (this.spaces?.find((t) => t.brief.title === title)) {\n this.toastController\n .create({\n message: 'You already have a team with the same name',\n color: 'danger',\n buttons: ['close'],\n position: 'middle',\n animated: true,\n duration: 3000,\n })\n .then((toast) => {\n toast\n .present()\n .catch((err) =>\n this.errorLogger.logError(err, 'Failed to present toast'),\n );\n })\n .catch((err) =>\n this.errorLogger.logError(err, 'Failed to create toast'),\n );\n return;\n }\n const request: ICreateSpaceRequest = {\n type: 'team',\n // memberType: TeamMemberType.creator,\n title,\n };\n this.adding = true;\n this.spaceService.createSpace(request).subscribe({\n next: (space) => {\n this.analyticsService.logEvent('spaceCreated', { space: space.id });\n const userTeamBrief2: IUserSpaceBrief = {\n userContactID: 'TODO: populate userContactID',\n title: space?.dbo?.title || space.id,\n roles: ['creator'],\n // memberType: request.memberType,\n type: space?.dbo?.type || 'unknown',\n };\n if (userTeamBrief2 && !this.spaces?.find((t) => t.id === space.id)) {\n this.spaces?.push({ id: space.id, brief: userTeamBrief2 });\n }\n this.adding = false;\n this.spaceName = '';\n this.goSpace(space);\n },\n error: (err) => {\n this.errorLogger.logError(err, 'Failed to create new team record');\n this.adding = false;\n },\n });\n }\n\n public startAddingSpace(): void {\n this.showAdd = true;\n setTimeout(() => {\n if (!this.addSpaceInput) {\n this.errorLogger.logError('addTeamInput is not set');\n return;\n }\n this.addSpaceInput\n .setFocus()\n .catch((err) =>\n this.errorLogger.logError(\n err,\n 'Failed to set focus to \"addTeamInput\"',\n ),\n );\n }, 200);\n }\n\n public leaveSpace(space: IIdAndBrief<IUserSpaceBrief>, event?: Event): void {\n if (event) {\n event.stopPropagation();\n event.preventDefault();\n }\n if (!confirm(`Are you sure you want to leave team ${space.brief.title}?`)) {\n return;\n }\n const userID = this.userService.currentUserID;\n if (!userID) {\n this.errorLogger.logError('Failed to get current user ID');\n return;\n }\n this.spaceService.leaveSpace({ spaceID: space.id }).subscribe({\n next: () => console.log('left space'),\n error: (err: unknown) =>\n this.errorLogger.logError(\n err,\n `Failed to leave a space: ${space.brief.title}`,\n ),\n });\n }\n\n private watchUserRecord(): void {\n this.userService.userState.pipe(takeUntil(this.destroyed)).subscribe({\n next: (userState) => {\n // console.log('SpacesCardComponent => user state changed:', userState);\n if (userState.status === 'authenticating') {\n if (this.loadingState === 'Authenticating') {\n this.loadingState = 'Loading';\n }\n }\n const uid = userState.user?.uid;\n this.spaces = undefined;\n if (!uid) {\n this.unsubscribe('user signed out');\n return;\n }\n this.subscriptions.push(\n this.userService.userState.subscribe({\n next: this.setUser,\n error: (err) =>\n this.errorLogger.logError(err, 'Failed to get user record'),\n }),\n );\n },\n error: (err) => this.errorLogger.logError(err, 'Failed to get user ID'),\n });\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n private unsubscribe(_reason?: string): void {\n // console.log(`SpacesCardComponent.unsubscribe(reason: ${reason})`);\n this.subscriptions.forEach((s) => s.unsubscribe());\n this.subscriptions = [];\n }\n\n private setUser = (userState: ISneatUserState): void => {\n // console.log('SpacesCardComponent => user:', userState);\n const user = userState.record;\n if (user) {\n this.spaces = Object.entries(user?.spaces ? user.spaces : {}).map(\n ([id, team]) => ({ id, brief: team }),\n );\n this.spaces.sort((a, b) => (a.brief.title > b.brief.title ? 1 : -1));\n this.showAdd = !this.spaces?.length;\n if (this.showAdd) {\n this.startAddingSpace();\n }\n } else {\n this.spaces = undefined;\n }\n };\n}\n","<ion-card>\n <ion-item>\n <ion-label>\n <ion-card-title color=\"medium\">Spaces</ion-card-title>\n </ion-label>\n <ion-buttons slot=\"end\" (click)=\"startAddingSpace()\">\n @if (!showAdd) {\n <ion-button color=\"primary\">\n <ion-icon name=\"add\" slot=\"start\" />\n <ion-label>Add</ion-label>\n </ion-button>\n }\n </ion-buttons>\n </ion-item>\n @if (spaces) {\n <ion-list>\n @for (space of spaces; track space.id) {\n <ion-item-sliding>\n <ion-item tappable detail (click)=\"goSpace(space)\">\n <ion-label>{{ space.brief.title }}</ion-label>\n <ion-buttons slot=\"end\">\n <ion-button color=\"medium\" (click)=\"leaveSpace(space, $event)\">\n <ion-icon name=\"close-outline\" />\n </ion-button>\n </ion-buttons>\n </ion-item>\n <ion-item-options side=\"start\">\n <ion-item-option color=\"danger\" (click)=\"leaveSpace(space)\"\n >Leave team\n </ion-item-option>\n </ion-item-options>\n <ion-item-options side=\"end\">\n <ion-item-option color=\"danger\" (click)=\"leaveSpace(space)\"\n >Leave team\n </ion-item-option>\n </ion-item-options>\n </ion-item-sliding>\n }\n </ion-list>\n } @else {\n <ion-list>\n <ion-item>\n <ion-spinner name=\"\" slot=\"start\" color=\"medium\" />\n <ion-buttons slot=\"start\">\n <ion-button disabled=\"disabled\" style=\"text-transform: none\"\n >{{ loadingState }}...\n </ion-button>\n </ion-buttons>\n <ion-skeleton-text animated />\n </ion-item>\n </ion-list>\n }\n\n @if (showAdd) {\n <ion-item [disabled]=\"adding\">\n <ion-input\n (keyup.enter)=\"addSpace()\"\n #addTeamInput\n [(ngModel)]=\"spaceName\"\n (keyup.escape)=\"showAdd = false\"\n placeholder=\"New team name\"\n />\n <ion-buttons slot=\"end\">\n <ion-button color=\"primary\" fill=\"solid\" (click)=\"addSpace()\">\n <ion-label>Create</ion-label>\n </ion-button>\n @if (!!spaces?.length) {\n <ion-button (click)=\"showAdd = false\" color=\"medium\" title=\"Cancel\">\n @if (adding) {\n <ion-spinner />\n } @else {\n <ion-icon name=\"close-outline\" />\n }\n </ion-button>\n }\n </ion-buttons>\n </ion-item>\n }\n @if (showAdd) {\n <ion-card-content>\n <p>Enter team name and click \"Create\" button to add a new team.</p>\n </ion-card-content>\n }\n</ion-card>\n"]}
|
|
1
|
+
{"version":3,"file":"spaces-card.component.js","sourceRoot":"","sources":["../../../../../../../libs/space/components/src/lib/spaces-card/spaces-card.component.ts","../../../../../../../libs/space/components/src/lib/spaces-card/spaces-card.component.html"],"names":[],"mappings":"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","sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n computed,\n effect,\n inject,\n signal,\n viewChild,\n} from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { FormsModule } from '@angular/forms';\nimport {\n IonButton,\n IonButtons,\n IonCard,\n IonCardContent,\n IonCardTitle,\n IonIcon,\n IonInput,\n IonItem,\n IonLabel,\n IonList,\n IonSkeletonText,\n IonSpinner,\n ToastController,\n} from '@ionic/angular/standalone';\nimport { AnalyticsService, IAnalyticsService } from '@sneat/core';\nimport { IUserSpaceBrief } from '@sneat/auth-models';\nimport { IIdAndBrief } from '@sneat/core';\nimport { ErrorLogger, IErrorLogger } from '@sneat/core';\nimport { ICreateSpaceRequest, ISpaceContext } from '@sneat/space-models';\nimport { SpaceNavService, SpaceService } from '@sneat/space-services';\nimport { SneatUserService } from '@sneat/auth-core';\nimport { SpacesListComponent } from '../spaces-list';\n\n// Signal-based + OnPush so the card repaints reactively when the user record\n// loads, instead of mutating fields inside an rxjs subscription and relying on\n// Zone change detection. The previous version stayed stuck on \"Authenticating...\"\n// when the Firestore onSnapshot update landed outside the Angular zone (the\n// record loaded but the view never repainted). toSignal()/computed() repaint\n// correctly under zone.js too — this is not a zoneless change.\n@Component({\n selector: 'sneat-spaces-card',\n templateUrl: './spaces-card.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [\n FormsModule,\n IonInput,\n IonCard,\n IonItem,\n IonLabel,\n IonCardTitle,\n IonButtons,\n IonButton,\n IonIcon,\n IonList,\n IonSpinner,\n IonSkeletonText,\n IonCardContent,\n SpacesListComponent,\n ],\n})\nexport class SpacesCardComponent {\n private readonly errorLogger = inject<IErrorLogger>(ErrorLogger);\n private readonly navService = inject(SpaceNavService);\n private readonly userService = inject(SneatUserService);\n private readonly spaceService = inject(SpaceService);\n private readonly analyticsService =\n inject<IAnalyticsService>(AnalyticsService);\n private readonly toastController = inject(ToastController);\n\n private readonly addSpaceInput = viewChild<IonInput>('addTeamInput');\n\n private readonly userState = toSignal(this.userService.userState);\n\n // undefined => user record not loaded yet (render the loading row).\n protected readonly spaces = computed<\n IIdAndBrief<IUserSpaceBrief>[] | undefined\n >(() => {\n const record = this.userState()?.record;\n if (!record) {\n return undefined;\n }\n return Object.entries(record.spaces ?? {})\n .map(([id, brief]) => ({ id, brief }))\n .sort((a, b) => (a.brief.title > b.brief.title ? 1 : -1));\n });\n\n // Adapts the user's spaces to the shape SpacesListComponent renders, so the\n // card reuses that component (icon + title decode + navigation) instead of\n // duplicating the row markup.\n protected readonly spaceContexts = computed<ISpaceContext[] | undefined>(\n () =>\n this.spaces()?.map(({ id, brief }) => ({\n id,\n type: brief.type,\n brief: { title: brief.title, type: brief.type, roles: brief.roles },\n })),\n );\n\n protected get currentUserID(): string {\n return this.userService.currentUserID ?? '';\n }\n\n protected readonly loadingState = computed(() =>\n this.userState()?.status === 'authenticated' ? 'Loading' : 'Authenticating',\n );\n\n protected readonly showAdd = signal(false);\n protected readonly spaceName = signal('');\n protected readonly adding = signal(false);\n\n public constructor() {\n // Auto-open the \"add space\" form once we know the user has no spaces.\n effect(() => {\n const spaces = this.spaces();\n if (spaces && spaces.length === 0) {\n this.startAddingSpace();\n }\n });\n }\n\n private navigateToSpace(space: ISpaceContext): void {\n this.navService\n .navigateToSpace(space, 'forward')\n .catch(this.errorLogger.logError);\n }\n\n protected addSpace(): void {\n this.analyticsService.logEvent('addSpace');\n const title = this.spaceName().trim();\n if (!title) {\n this.presentToast('Space name is required', 'tertiary');\n return;\n }\n if (this.spaces()?.find((t) => t.brief.title === title)) {\n this.presentToast('You already have a team with the same name', 'danger');\n return;\n }\n const request: ICreateSpaceRequest = {\n type: 'team',\n title,\n };\n this.adding.set(true);\n this.spaceService.createSpace(request).subscribe({\n next: (space) => {\n this.analyticsService.logEvent('spaceCreated', { space: space.id });\n this.adding.set(false);\n this.spaceName.set('');\n // The user record updates via Firestore, which recomputes `spaces`.\n this.navigateToSpace(space);\n },\n error: (err) => {\n this.errorLogger.logError(err, 'Failed to create new team record');\n this.adding.set(false);\n },\n });\n }\n\n private presentToast(message: string, color: string): void {\n this.toastController\n .create({\n position: 'middle',\n message,\n color,\n duration: 5000,\n keyboardClose: true,\n buttons: [{ role: 'cancel', text: 'OK' }],\n })\n .then((toast) =>\n toast\n .present()\n .catch((err) =>\n this.errorLogger.logError(err, 'Failed to present toast'),\n ),\n )\n .catch((err) => this.errorLogger.logError(err, 'Failed to create toast'));\n }\n\n public startAddingSpace(): void {\n this.showAdd.set(true);\n setTimeout(() => {\n const input = this.addSpaceInput();\n if (!input) {\n return;\n }\n input\n .setFocus()\n .catch((err) =>\n this.errorLogger.logError(err, 'Failed to set focus to addTeamInput'),\n );\n }, 200);\n }\n\n protected cancelAdd(): void {\n this.showAdd.set(false);\n }\n}\n","<ion-card>\n <ion-item>\n <ion-label>\n <ion-card-title color=\"medium\">Spaces</ion-card-title>\n </ion-label>\n <ion-buttons slot=\"end\" (click)=\"startAddingSpace()\">\n @if (!showAdd()) {\n <ion-button color=\"primary\">\n <ion-icon name=\"add\" slot=\"start\" />\n <ion-label>Add</ion-label>\n </ion-button>\n }\n </ion-buttons>\n </ion-item>\n @if (spaceContexts(); as spaceContexts) {\n <ion-list>\n <sneat-spaces-list\n [userID]=\"currentUserID\"\n [spaces]=\"spaceContexts\"\n [allowLeave]=\"true\"\n />\n </ion-list>\n } @else {\n <ion-list>\n <ion-item>\n <ion-spinner name=\"\" slot=\"start\" color=\"medium\" />\n <ion-buttons slot=\"start\">\n <ion-button disabled=\"disabled\" style=\"text-transform: none\"\n >{{ loadingState() }}...\n </ion-button>\n </ion-buttons>\n <ion-skeleton-text animated />\n </ion-item>\n </ion-list>\n }\n\n @if (showAdd()) {\n <ion-item [disabled]=\"adding()\">\n <ion-input\n (keyup.enter)=\"addSpace()\"\n #addTeamInput\n [ngModel]=\"spaceName()\"\n (ngModelChange)=\"spaceName.set($event)\"\n (keyup.escape)=\"cancelAdd()\"\n placeholder=\"New team name\"\n />\n <ion-buttons slot=\"end\">\n <ion-button color=\"primary\" fill=\"solid\" (click)=\"addSpace()\">\n <ion-label>Create</ion-label>\n </ion-button>\n @if (!!spaces()?.length) {\n <ion-button (click)=\"cancelAdd()\" color=\"medium\" title=\"Cancel\">\n @if (adding()) {\n <ion-spinner />\n } @else {\n <ion-icon name=\"close-outline\" />\n }\n </ion-button>\n }\n </ion-buttons>\n </ion-item>\n }\n @if (showAdd()) {\n <ion-card-content>\n <p>Enter team name and click \"Create\" button to add a new team.</p>\n </ion-card-content>\n }\n</ion-card>\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TitleCasePipe } from '@angular/common';
|
|
2
2
|
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, inject, } from '@angular/core';
|
|
3
3
|
import { RouterLink } from '@angular/router';
|
|
4
|
-
import { IonBadge, IonIcon, IonItem, IonLabel, IonSpinner, } from '@ionic/angular/standalone';
|
|
4
|
+
import { IonBadge, IonButton, IonButtons, IonIcon, IonItem, IonLabel, IonSpinner, } from '@ionic/angular/standalone';
|
|
5
5
|
import { UserRequiredFieldsService } from '@sneat/auth-ui';
|
|
6
6
|
import { SpaceNavService, SpaceService } from '@sneat/space-services';
|
|
7
7
|
import { SneatUserService } from '@sneat/auth-core';
|
|
@@ -16,8 +16,12 @@ export class SpacesListComponent extends SneatBaseComponent {
|
|
|
16
16
|
this.spaceService = inject(SpaceService);
|
|
17
17
|
this.userRequiredFieldsService = inject(UserRequiredFieldsService);
|
|
18
18
|
this.pathPrefix = '/space';
|
|
19
|
+
// Opt-in: render a per-row "leave" button. Off by default so existing
|
|
20
|
+
// consumers (spaces menu, for-space-type-card) are unchanged.
|
|
21
|
+
this.allowLeave = false;
|
|
19
22
|
// Outputs
|
|
20
23
|
this.beforeNavigateToSpace = new EventEmitter();
|
|
24
|
+
this.leftSpace = new EventEmitter();
|
|
21
25
|
}
|
|
22
26
|
goSpace(event, space) {
|
|
23
27
|
event.stopPropagation();
|
|
@@ -73,13 +77,35 @@ export class SpacesListComponent extends SneatBaseComponent {
|
|
|
73
77
|
error: this.errorLogger.logErrorHandler('failed to create a new family team'),
|
|
74
78
|
});
|
|
75
79
|
}
|
|
80
|
+
// Only reachable when [allowLeave]="true". Stops the row's navigate handler,
|
|
81
|
+
// confirms, then leaves the space; the user record update removes the row.
|
|
82
|
+
leaveSpace(space, event) {
|
|
83
|
+
if (event) {
|
|
84
|
+
event.stopPropagation();
|
|
85
|
+
event.preventDefault();
|
|
86
|
+
}
|
|
87
|
+
if (!space.id) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const title = space.brief?.title || space.id;
|
|
91
|
+
if (!confirm(`Are you sure you want to leave "${title}"?`)) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
this.spaceService
|
|
95
|
+
.leaveSpace({ spaceID: space.id })
|
|
96
|
+
.pipe(this.takeUntilDestroyed())
|
|
97
|
+
.subscribe({
|
|
98
|
+
next: () => this.leftSpace.emit(space),
|
|
99
|
+
error: this.errorLogger.logErrorHandler(`Failed to leave space: ${title}`),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
76
102
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SpacesListComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
77
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: SpacesListComponent, isStandalone: true, selector: "sneat-spaces-list", inputs: { userID: "userID", spaces: "spaces", pathPrefix: "pathPrefix" }, outputs: { beforeNavigateToSpace: "beforeNavigateToSpace" }, providers: [
|
|
103
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: SpacesListComponent, isStandalone: true, selector: "sneat-spaces-list", inputs: { userID: "userID", spaces: "spaces", pathPrefix: "pathPrefix", allowLeave: "allowLeave" }, outputs: { beforeNavigateToSpace: "beforeNavigateToSpace", leftSpace: "leftSpace" }, providers: [
|
|
78
104
|
{
|
|
79
105
|
provide: ClassName,
|
|
80
106
|
useValue: 'SpacesListComponent',
|
|
81
107
|
},
|
|
82
|
-
], usesInheritance: true, ngImport: i0, template: "@for (space of spaces; track space.id; let last = $last) {\n <ion-item\n tappable=\"true\"\n [disabled]=\"!space.brief\"\n [lines]=\"last ? 'full' : 'inset'\"\n routerLink=\"{{ pathPrefix }}/{{ space.type }}/{{ space.id }}\"\n (click)=\"goSpace($event, space)\"\n >\n <ion-icon\n [name]=\"\n space.type === 'family'\n ? 'people-outline'\n : space.type === 'private'\n ? 'person-circle-outline'\n : 'people-outline'\n \"\n slot=\"start\"\n />\n <ion-label class=\"ion-text-wrap\">\n {{ space.brief?.title || (space.type | titlecase) || space.id }}\n @if (!space.id && space.brief) {\n <ion-badge color=\"light\">(new)</ion-badge>\n }\n </ion-label>\n @if (!space.id && !space.brief) {\n <ion-spinner name=\"lines-small\" />\n }\n </ion-item>\n}\n", dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonBadge, selector: "ion-badge", inputs: ["color", "mode"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "pipe", type: TitleCasePipe, name: "titlecase" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
108
|
+
], usesInheritance: true, ngImport: i0, template: "@for (space of spaces; track space.id; let last = $last) {\n <ion-item\n tappable=\"true\"\n [disabled]=\"!space.brief\"\n [lines]=\"last ? 'full' : 'inset'\"\n routerLink=\"{{ pathPrefix }}/{{ space.type }}/{{ space.id }}\"\n (click)=\"goSpace($event, space)\"\n >\n <ion-icon\n [name]=\"\n space.type === 'family'\n ? 'people-outline'\n : space.type === 'private'\n ? 'person-circle-outline'\n : 'people-outline'\n \"\n slot=\"start\"\n />\n <ion-label class=\"ion-text-wrap\">\n {{ space.brief?.title || (space.type | titlecase) || space.id }}\n @if (!space.id && space.brief) {\n <ion-badge color=\"light\">(new)</ion-badge>\n }\n </ion-label>\n @if (!space.id && !space.brief) {\n <ion-spinner name=\"lines-small\" />\n }\n @if (allowLeave && space.id) {\n <ion-buttons slot=\"end\">\n <ion-button\n color=\"medium\"\n title=\"Leave\"\n (click)=\"leaveSpace(space, $event)\"\n >\n <ion-icon name=\"close-outline\" slot=\"icon-only\" />\n </ion-button>\n </ion-buttons>\n }\n </ion-item>\n}\n", dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonBadge, selector: "ion-badge", inputs: ["color", "mode"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "pipe", type: TitleCasePipe, name: "titlecase" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
83
109
|
}
|
|
84
110
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SpacesListComponent, decorators: [{
|
|
85
111
|
type: Component,
|
|
@@ -91,12 +117,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
91
117
|
IonLabel,
|
|
92
118
|
IonBadge,
|
|
93
119
|
IonSpinner,
|
|
120
|
+
IonButtons,
|
|
121
|
+
IonButton,
|
|
94
122
|
], providers: [
|
|
95
123
|
{
|
|
96
124
|
provide: ClassName,
|
|
97
125
|
useValue: 'SpacesListComponent',
|
|
98
126
|
},
|
|
99
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, template: "@for (space of spaces; track space.id; let last = $last) {\n <ion-item\n tappable=\"true\"\n [disabled]=\"!space.brief\"\n [lines]=\"last ? 'full' : 'inset'\"\n routerLink=\"{{ pathPrefix }}/{{ space.type }}/{{ space.id }}\"\n (click)=\"goSpace($event, space)\"\n >\n <ion-icon\n [name]=\"\n space.type === 'family'\n ? 'people-outline'\n : space.type === 'private'\n ? 'person-circle-outline'\n : 'people-outline'\n \"\n slot=\"start\"\n />\n <ion-label class=\"ion-text-wrap\">\n {{ space.brief?.title || (space.type | titlecase) || space.id }}\n @if (!space.id && space.brief) {\n <ion-badge color=\"light\">(new)</ion-badge>\n }\n </ion-label>\n @if (!space.id && !space.brief) {\n <ion-spinner name=\"lines-small\" />\n }\n </ion-item>\n}\n" }]
|
|
127
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "@for (space of spaces; track space.id; let last = $last) {\n <ion-item\n tappable=\"true\"\n [disabled]=\"!space.brief\"\n [lines]=\"last ? 'full' : 'inset'\"\n routerLink=\"{{ pathPrefix }}/{{ space.type }}/{{ space.id }}\"\n (click)=\"goSpace($event, space)\"\n >\n <ion-icon\n [name]=\"\n space.type === 'family'\n ? 'people-outline'\n : space.type === 'private'\n ? 'person-circle-outline'\n : 'people-outline'\n \"\n slot=\"start\"\n />\n <ion-label class=\"ion-text-wrap\">\n {{ space.brief?.title || (space.type | titlecase) || space.id }}\n @if (!space.id && space.brief) {\n <ion-badge color=\"light\">(new)</ion-badge>\n }\n </ion-label>\n @if (!space.id && !space.brief) {\n <ion-spinner name=\"lines-small\" />\n }\n @if (allowLeave && space.id) {\n <ion-buttons slot=\"end\">\n <ion-button\n color=\"medium\"\n title=\"Leave\"\n (click)=\"leaveSpace(space, $event)\"\n >\n <ion-icon name=\"close-outline\" slot=\"icon-only\" />\n </ion-button>\n </ion-buttons>\n }\n </ion-item>\n}\n" }]
|
|
100
128
|
}], propDecorators: { userID: [{
|
|
101
129
|
type: Input,
|
|
102
130
|
args: [{ required: true }]
|
|
@@ -105,7 +133,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
105
133
|
args: [{ required: true }]
|
|
106
134
|
}], pathPrefix: [{
|
|
107
135
|
type: Input
|
|
136
|
+
}], allowLeave: [{
|
|
137
|
+
type: Input
|
|
108
138
|
}], beforeNavigateToSpace: [{
|
|
109
139
|
type: Output
|
|
140
|
+
}], leftSpace: [{
|
|
141
|
+
type: Output
|
|
110
142
|
}] } });
|
|
111
143
|
//# sourceMappingURL=spaces-list.component.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spaces-list.component.js","sourceRoot":"","sources":["../../../../../../../libs/space/components/src/lib/spaces-list/spaces-list.component.ts","../../../../../../../libs/space/components/src/lib/spaces-list/spaces-list.component.html"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EACL,uBAAuB,EACvB,SAAS,EACT,YAAY,EACZ,KAAK,EACL,MAAM,EACN,MAAM,GACP,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EACL,QAAQ,EACR,OAAO,EACP,OAAO,EACP,QAAQ,EACR,UAAU,GACX,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAG3D,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC;;
|
|
1
|
+
{"version":3,"file":"spaces-list.component.js","sourceRoot":"","sources":["../../../../../../../libs/space/components/src/lib/spaces-list/spaces-list.component.ts","../../../../../../../libs/space/components/src/lib/spaces-list/spaces-list.component.html"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EACL,uBAAuB,EACvB,SAAS,EACT,YAAY,EACZ,KAAK,EACL,MAAM,EACN,MAAM,GACP,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EACL,QAAQ,EACR,SAAS,EACT,UAAU,EACV,OAAO,EACP,OAAO,EACP,QAAQ,EACR,UAAU,GACX,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAG3D,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC;;AAwB7B,MAAM,OAAO,mBAAoB,SAAQ,kBAAkB;IAtB3D;;QAuBkB,gBAAW,GAAG,MAAM,CAAC,gBAAgB,CAAC,CAAC;QACtC,oBAAe,GAAG,MAAM,CAAC,eAAe,CAAC,CAAC;QAC1C,iBAAY,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;QACpC,8BAAyB,GAAG,MAAM,CACjD,yBAAyB,CAC1B,CAAC;QAKO,eAAU,GAAG,QAAQ,CAAC;QAC/B,sEAAsE;QACtE,8DAA8D;QACrD,eAAU,GAAG,KAAK,CAAC;QAE5B,UAAU;QACS,0BAAqB,GAAG,IAAI,YAAY,EAAiB,CAAC;QAC1D,cAAS,GAAG,IAAI,YAAY,EAAiB,CAAC;KA6FlE;IA3FW,OAAO,CAAC,KAAY,EAAE,KAAoB;QAClD,KAAK,CAAC,eAAe,EAAE,CAAC;QACxB,KAAK,CAAC,cAAc,EAAE,CAAC;QACvB,IAAI,KAAK,CAAC,EAAE,EAAE,CAAC;YACb,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;YACtB,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAEO,eAAe,CAAC,KAAoB;QAC1C,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvC,IAAI,CAAC,eAAe;aACjB,eAAe,CAAC,KAAK,CAAC;aACtB,KAAK,CACJ,IAAI,CAAC,WAAW,CAAC,eAAe,CAC9B,2DAA2D,CAC5D,CACF,CAAC;IACN,CAAC;IAES,cAAc,CAAC,IAAe;QACtC,MAAM,OAAO,GAAwB;YACnC,IAAI;SACL,CAAC;QAEF,IAAI,CAAC,WAAW,CAAC,SAAS;aACvB,IAAI,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,kBAAkB,EAAE,CAAC;aACxC,SAAS,CAAC;YACT,IAAI,EAAE,CAAC,SAAS,EAAE,EAAE;gBAClB,IAAI,SAAS,CAAC,MAAM,EAAE,CAAC;oBACrB,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;gBAC5B,CAAC;qBAAM,CAAC;oBACN,IAAI,CAAC,yBAAyB;yBAC3B,IAAI,EAAE;yBACN,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE;wBACpB,IAAI,WAAW,EAAE,CAAC;4BAChB,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;wBAC5B,CAAC;oBACH,CAAC,CAAC;yBACD,KAAK,CACJ,IAAI,CAAC,WAAW,CAAC,eAAe,CAC9B,2CAA2C,CAC5C,CACF,CAAC;gBACN,CAAC;YACH,CAAC;SACF,CAAC,CAAC;QACL,oBAAoB;QACpB,OAAO,KAAK,CAAC;IACf,CAAC;IAEO,WAAW,CAAC,OAA4B;QAC9C,IAAI,CAAC,YAAY;aACd,WAAW,CAAC,OAAO,CAAC;aACpB,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;aAC/B,SAAS,CAAC;YACT,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE;gBACd,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;YAC9B,CAAC;YACD,KAAK,EAAE,IAAI,CAAC,WAAW,CAAC,eAAe,CACrC,oCAAoC,CACrC;SACF,CAAC,CAAC;IACP,CAAC;IAED,6EAA6E;IAC7E,2EAA2E;IACjE,UAAU,CAAC,KAAoB,EAAE,KAAa;QACtD,IAAI,KAAK,EAAE,CAAC;YACV,KAAK,CAAC,eAAe,EAAE,CAAC;YACxB,KAAK,CAAC,cAAc,EAAE,CAAC;QACzB,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;YACd,OAAO;QACT,CAAC;QACD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,KAAK,IAAI,KAAK,CAAC,EAAE,CAAC;QAC7C,IAAI,CAAC,OAAO,CAAC,mCAAmC,KAAK,IAAI,CAAC,EAAE,CAAC;YAC3D,OAAO;QACT,CAAC;QACD,IAAI,CAAC,YAAY;aACd,UAAU,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;aACjC,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;aAC/B,SAAS,CAAC;YACT,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC;YACtC,KAAK,EAAE,IAAI,CAAC,WAAW,CAAC,eAAe,CACrC,0BAA0B,KAAK,EAAE,CAClC;SACF,CAAC,CAAC;IACP,CAAC;8GA9GU,mBAAmB;kGAAnB,mBAAmB,yPARnB;YACT;gBACE,OAAO,EAAE,SAAS;gBAClB,QAAQ,EAAE,qBAAqB;aAChC;SACF,iDC9CH,4qCAwCA,4CDTI,UAAU,oOAEV,OAAO,0NACP,OAAO,2JACP,QAAQ,6FACR,QAAQ,iFACR,UAAU,yGACV,UAAU,8EACV,SAAS,+OAPT,aAAa;;2FAiBJ,mBAAmB;kBAtB/B,SAAS;+BACE,mBAAmB,WAEpB;wBACP,UAAU;wBACV,aAAa;wBACb,OAAO;wBACP,OAAO;wBACP,QAAQ;wBACR,QAAQ;wBACR,UAAU;wBACV,UAAU;wBACV,SAAS;qBACV,aACU;wBACT;4BACE,OAAO,EAAE,SAAS;4BAClB,QAAQ,EAAE,qBAAqB;yBAChC;qBACF,mBACgB,uBAAuB,CAAC,MAAM;;sBAW9C,KAAK;uBAAC,EAAE,QAAQ,EAAE,IAAI,EAAE;;sBACxB,KAAK;uBAAC,EAAE,QAAQ,EAAE,IAAI,EAAE;;sBACxB,KAAK;;sBAGL,KAAK;;sBAGL,MAAM;;sBACN,MAAM","sourcesContent":["import { TitleCasePipe } from '@angular/common';\nimport {\n ChangeDetectionStrategy,\n Component,\n EventEmitter,\n Input,\n Output,\n inject,\n} from '@angular/core';\nimport { RouterLink } from '@angular/router';\nimport {\n IonBadge,\n IonButton,\n IonButtons,\n IonIcon,\n IonItem,\n IonLabel,\n IonSpinner,\n} from '@ionic/angular/standalone';\nimport { UserRequiredFieldsService } from '@sneat/auth-ui';\nimport { SpaceType } from '@sneat/core';\nimport { ICreateSpaceRequest, ISpaceContext } from '@sneat/space-models';\nimport { SpaceNavService, SpaceService } from '@sneat/space-services';\nimport { SneatUserService } from '@sneat/auth-core';\nimport { ClassName, SneatBaseComponent } from '@sneat/ui';\nimport { first } from 'rxjs';\n\n@Component({\n selector: 'sneat-spaces-list',\n templateUrl: 'spaces-list.component.html',\n imports: [\n RouterLink,\n TitleCasePipe,\n IonItem,\n IonIcon,\n IonLabel,\n IonBadge,\n IonSpinner,\n IonButtons,\n IonButton,\n ],\n providers: [\n {\n provide: ClassName,\n useValue: 'SpacesListComponent',\n },\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class SpacesListComponent extends SneatBaseComponent {\n public readonly userService = inject(SneatUserService);\n private readonly spaceNavService = inject(SpaceNavService);\n private readonly spaceService = inject(SpaceService);\n private readonly userRequiredFieldsService = inject(\n UserRequiredFieldsService,\n );\n\n // Inputs\n @Input({ required: true }) userID?: string;\n @Input({ required: true }) spaces?: ISpaceContext[];\n @Input() pathPrefix = '/space';\n // Opt-in: render a per-row \"leave\" button. Off by default so existing\n // consumers (spaces menu, for-space-type-card) are unchanged.\n @Input() allowLeave = false;\n\n // Outputs\n @Output() readonly beforeNavigateToSpace = new EventEmitter<ISpaceContext>();\n @Output() readonly leftSpace = new EventEmitter<ISpaceContext>();\n\n protected goSpace(event: Event, space: ISpaceContext): boolean {\n event.stopPropagation();\n event.preventDefault();\n if (space.id) {\n this.navigateToSpace(space);\n } else if (space.type) {\n this.createNewSpace(space.type);\n }\n return false;\n }\n\n private navigateToSpace(space: ISpaceContext): void {\n this.beforeNavigateToSpace.emit(space);\n this.spaceNavService\n .navigateToSpace(space)\n .catch(\n this.errorLogger.logErrorHandler(\n 'Failed to navigate to teams overview page from teams menu',\n ),\n );\n }\n\n protected createNewSpace(type: SpaceType): boolean {\n const request: ICreateSpaceRequest = {\n type,\n };\n\n this.userService.userState\n .pipe(first(), this.takeUntilDestroyed())\n .subscribe({\n next: (userState) => {\n if (userState.record) {\n this.createSpace(request);\n } else {\n this.userRequiredFieldsService\n .open()\n .then((modalResult) => {\n if (modalResult) {\n this.createSpace(request);\n }\n })\n .catch(\n this.errorLogger.logErrorHandler(\n 'Failed to open user required fields modal',\n ),\n );\n }\n },\n });\n // this.closeMenu();\n return false;\n }\n\n private createSpace(request: ICreateSpaceRequest): void {\n this.spaceService\n .createSpace(request)\n .pipe(this.takeUntilDestroyed())\n .subscribe({\n next: (value) => {\n this.navigateToSpace(value);\n },\n error: this.errorLogger.logErrorHandler(\n 'failed to create a new family team',\n ),\n });\n }\n\n // Only reachable when [allowLeave]=\"true\". Stops the row's navigate handler,\n // confirms, then leaves the space; the user record update removes the row.\n protected leaveSpace(space: ISpaceContext, event?: Event): void {\n if (event) {\n event.stopPropagation();\n event.preventDefault();\n }\n if (!space.id) {\n return;\n }\n const title = space.brief?.title || space.id;\n if (!confirm(`Are you sure you want to leave \"${title}\"?`)) {\n return;\n }\n this.spaceService\n .leaveSpace({ spaceID: space.id })\n .pipe(this.takeUntilDestroyed())\n .subscribe({\n next: () => this.leftSpace.emit(space),\n error: this.errorLogger.logErrorHandler(\n `Failed to leave space: ${title}`,\n ),\n });\n }\n}\n","@for (space of spaces; track space.id; let last = $last) {\n <ion-item\n tappable=\"true\"\n [disabled]=\"!space.brief\"\n [lines]=\"last ? 'full' : 'inset'\"\n routerLink=\"{{ pathPrefix }}/{{ space.type }}/{{ space.id }}\"\n (click)=\"goSpace($event, space)\"\n >\n <ion-icon\n [name]=\"\n space.type === 'family'\n ? 'people-outline'\n : space.type === 'private'\n ? 'person-circle-outline'\n : 'people-outline'\n \"\n slot=\"start\"\n />\n <ion-label class=\"ion-text-wrap\">\n {{ space.brief?.title || (space.type | titlecase) || space.id }}\n @if (!space.id && space.brief) {\n <ion-badge color=\"light\">(new)</ion-badge>\n }\n </ion-label>\n @if (!space.id && !space.brief) {\n <ion-spinner name=\"lines-small\" />\n }\n @if (allowLeave && space.id) {\n <ion-buttons slot=\"end\">\n <ion-button\n color=\"medium\"\n title=\"Leave\"\n (click)=\"leaveSpace(space, $event)\"\n >\n <ion-icon name=\"close-outline\" slot=\"icon-only\" />\n </ion-button>\n </ion-buttons>\n }\n </ion-item>\n}\n"]}
|
|
@@ -84,7 +84,7 @@ export class SpacesMenuComponent extends SneatBaseComponent {
|
|
|
84
84
|
useValue: 'SpacesMenuComponent',
|
|
85
85
|
},
|
|
86
86
|
UserRequiredFieldsService,
|
|
87
|
-
], usesInheritance: true, ngImport: i0, template: "<ion-item>\n <ion-label color=\"medium\" style=\"font-weight: bold\">{{\n spacesLabel || \"Spaces\"\n }}</ion-label>\n</ion-item>\n\n@if ($spacesToShow().length) {\n <sneat-spaces-list\n [spaces]=\"$spacesToShow()\"\n [userID]=\"$userID()\"\n (beforeNavigateToSpace)=\"closeMenu()\"\n />\n} @else if (spaceType) {\n <ion-item>\n <ion-icon name=\"people-outline\" slot=\"start\" />\n <ion-label color=\"medium\">\n @if (spaceType === \"company\") {\n No companies\n } @else {\n No {{ spaceType }}\n }\n </ion-label>\n </ion-item>\n}\n", dependencies: [{ kind: "component", type: SpacesListComponent, selector: "sneat-spaces-list", inputs: ["userID", "spaces", "pathPrefix"], outputs: ["beforeNavigateToSpace"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
87
|
+
], usesInheritance: true, ngImport: i0, template: "<ion-item>\n <ion-label color=\"medium\" style=\"font-weight: bold\">{{\n spacesLabel || \"Spaces\"\n }}</ion-label>\n</ion-item>\n\n@if ($spacesToShow().length) {\n <sneat-spaces-list\n [spaces]=\"$spacesToShow()\"\n [userID]=\"$userID()\"\n (beforeNavigateToSpace)=\"closeMenu()\"\n />\n} @else if (spaceType) {\n <ion-item>\n <ion-icon name=\"people-outline\" slot=\"start\" />\n <ion-label color=\"medium\">\n @if (spaceType === \"company\") {\n No companies\n } @else {\n No {{ spaceType }}\n }\n </ion-label>\n </ion-item>\n}\n", dependencies: [{ kind: "component", type: SpacesListComponent, selector: "sneat-spaces-list", inputs: ["userID", "spaces", "pathPrefix", "allowLeave"], outputs: ["beforeNavigateToSpace", "leftSpace"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
88
88
|
}
|
|
89
89
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SpacesMenuComponent, decorators: [{
|
|
90
90
|
type: Component,
|