@sneat/space-components 0.6.0 → 0.6.1
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.
|
@@ -1,14 +1,19 @@
|
|
|
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
4
|
import { IonButton, IonButtons, IonCard, IonCardContent, IonCardTitle, IonIcon, IonInput, IonItem, IonItemOption, IonItemOptions, IonItemSliding, 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 { Subject } from 'rxjs';
|
|
9
|
-
import { takeUntil } from 'rxjs/operators';
|
|
10
9
|
import * as i0 from "@angular/core";
|
|
11
10
|
import * as i1 from "@angular/forms";
|
|
11
|
+
// Signal-based + OnPush so the card repaints reactively when the user record
|
|
12
|
+
// loads, instead of mutating fields inside an rxjs subscription and relying on
|
|
13
|
+
// Zone change detection. The previous version stayed stuck on "Authenticating..."
|
|
14
|
+
// when the Firestore onSnapshot update landed outside the Angular zone (the
|
|
15
|
+
// record loaded but the view never repainted). toSignal()/computed() repaint
|
|
16
|
+
// correctly under zone.js too — this is not a zoneless change.
|
|
12
17
|
export class SpacesCardComponent {
|
|
13
18
|
constructor() {
|
|
14
19
|
this.errorLogger = inject(ErrorLogger);
|
|
@@ -17,36 +22,29 @@ export class SpacesCardComponent {
|
|
|
17
22
|
this.spaceService = inject(SpaceService);
|
|
18
23
|
this.analyticsService = inject(AnalyticsService);
|
|
19
24
|
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
|
-
}
|
|
25
|
+
this.addSpaceInput = viewChild('addTeamInput', ...(ngDevMode ? [{ debugName: "addSpaceInput" }] : []));
|
|
26
|
+
this.userState = toSignal(this.userService.userState);
|
|
27
|
+
// undefined => user record not loaded yet (render the loading row).
|
|
28
|
+
this.spaces = computed(() => {
|
|
29
|
+
const record = this.userState()?.record;
|
|
30
|
+
if (!record) {
|
|
31
|
+
return undefined;
|
|
36
32
|
}
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
return Object.entries(record.spaces ?? {})
|
|
34
|
+
.map(([id, brief]) => ({ id, brief }))
|
|
35
|
+
.sort((a, b) => (a.brief.title > b.brief.title ? 1 : -1));
|
|
36
|
+
}, ...(ngDevMode ? [{ debugName: "spaces" }] : []));
|
|
37
|
+
this.loadingState = computed(() => this.userState()?.status === 'authenticated' ? 'Loading' : 'Authenticating', ...(ngDevMode ? [{ debugName: "loadingState" }] : []));
|
|
38
|
+
this.showAdd = signal(false, ...(ngDevMode ? [{ debugName: "showAdd" }] : []));
|
|
39
|
+
this.spaceName = signal('', ...(ngDevMode ? [{ debugName: "spaceName" }] : []));
|
|
40
|
+
this.adding = signal(false, ...(ngDevMode ? [{ debugName: "adding" }] : []));
|
|
41
|
+
// Auto-open the "add space" form once we know the user has no spaces.
|
|
42
|
+
effect(() => {
|
|
43
|
+
const spaces = this.spaces();
|
|
44
|
+
if (spaces && spaces.length === 0) {
|
|
45
|
+
this.startAddingSpace();
|
|
39
46
|
}
|
|
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();
|
|
47
|
+
});
|
|
50
48
|
}
|
|
51
49
|
goSpace(space) {
|
|
52
50
|
this.navService
|
|
@@ -55,82 +53,64 @@ export class SpacesCardComponent {
|
|
|
55
53
|
}
|
|
56
54
|
addSpace() {
|
|
57
55
|
this.analyticsService.logEvent('addSpace');
|
|
58
|
-
const title = this.spaceName.trim();
|
|
56
|
+
const title = this.spaceName().trim();
|
|
59
57
|
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'));
|
|
58
|
+
this.presentToast('Space name is required', 'tertiary');
|
|
73
59
|
return;
|
|
74
60
|
}
|
|
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'));
|
|
61
|
+
if (this.spaces()?.find((t) => t.brief.title === title)) {
|
|
62
|
+
this.presentToast('You already have a team with the same name', 'danger');
|
|
91
63
|
return;
|
|
92
64
|
}
|
|
93
65
|
const request = {
|
|
94
66
|
type: 'team',
|
|
95
|
-
// memberType: TeamMemberType.creator,
|
|
96
67
|
title,
|
|
97
68
|
};
|
|
98
|
-
this.adding
|
|
69
|
+
this.adding.set(true);
|
|
99
70
|
this.spaceService.createSpace(request).subscribe({
|
|
100
71
|
next: (space) => {
|
|
101
72
|
this.analyticsService.logEvent('spaceCreated', { space: space.id });
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
roles: ['creator'],
|
|
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 = '';
|
|
73
|
+
this.adding.set(false);
|
|
74
|
+
this.spaceName.set('');
|
|
75
|
+
// The user record updates via Firestore, which recomputes `spaces`.
|
|
114
76
|
this.goSpace(space);
|
|
115
77
|
},
|
|
116
78
|
error: (err) => {
|
|
117
79
|
this.errorLogger.logError(err, 'Failed to create new team record');
|
|
118
|
-
this.adding
|
|
80
|
+
this.adding.set(false);
|
|
119
81
|
},
|
|
120
82
|
});
|
|
121
83
|
}
|
|
84
|
+
presentToast(message, color) {
|
|
85
|
+
this.toastController
|
|
86
|
+
.create({
|
|
87
|
+
position: 'middle',
|
|
88
|
+
message,
|
|
89
|
+
color,
|
|
90
|
+
duration: 5000,
|
|
91
|
+
keyboardClose: true,
|
|
92
|
+
buttons: [{ role: 'cancel', text: 'OK' }],
|
|
93
|
+
})
|
|
94
|
+
.then((toast) => toast
|
|
95
|
+
.present()
|
|
96
|
+
.catch((err) => this.errorLogger.logError(err, 'Failed to present toast')))
|
|
97
|
+
.catch((err) => this.errorLogger.logError(err, 'Failed to create toast'));
|
|
98
|
+
}
|
|
122
99
|
startAddingSpace() {
|
|
123
|
-
this.showAdd
|
|
100
|
+
this.showAdd.set(true);
|
|
124
101
|
setTimeout(() => {
|
|
125
|
-
|
|
126
|
-
|
|
102
|
+
const input = this.addSpaceInput();
|
|
103
|
+
if (!input) {
|
|
127
104
|
return;
|
|
128
105
|
}
|
|
129
|
-
|
|
106
|
+
input
|
|
130
107
|
.setFocus()
|
|
131
|
-
.catch((err) => this.errorLogger.logError(err, 'Failed to set focus to
|
|
108
|
+
.catch((err) => this.errorLogger.logError(err, 'Failed to set focus to addTeamInput'));
|
|
132
109
|
}, 200);
|
|
133
110
|
}
|
|
111
|
+
cancelAdd() {
|
|
112
|
+
this.showAdd.set(false);
|
|
113
|
+
}
|
|
134
114
|
leaveSpace(space, event) {
|
|
135
115
|
if (event) {
|
|
136
116
|
event.stopPropagation();
|
|
@@ -149,41 +129,12 @@ export class SpacesCardComponent {
|
|
|
149
129
|
error: (err) => this.errorLogger.logError(err, `Failed to leave a space: ${space.brief.title}`),
|
|
150
130
|
});
|
|
151
131
|
}
|
|
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 = [];
|
|
180
|
-
}
|
|
181
132
|
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:
|
|
133
|
+
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 (spaces(); as 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 (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: IonItemSliding, selector: "ion-item-sliding", inputs: ["disabled"] }, { kind: "component", type: IonItemOptions, selector: "ion-item-options", inputs: ["side"] }, { kind: "component", type: IonItemOption, selector: "ion-item-option", inputs: ["color", "disabled", "download", "expandable", "href", "mode", "rel", "target", "type"] }, { 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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
183
134
|
}
|
|
184
135
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SpacesCardComponent, decorators: [{
|
|
185
136
|
type: Component,
|
|
186
|
-
args: [{ selector: 'sneat-spaces-card', imports: [
|
|
137
|
+
args: [{ selector: 'sneat-spaces-card', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
187
138
|
FormsModule,
|
|
188
139
|
IonInput,
|
|
189
140
|
IonCard,
|
|
@@ -200,9 +151,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
200
151
|
IonSpinner,
|
|
201
152
|
IonSkeletonText,
|
|
202
153
|
IonCardContent,
|
|
203
|
-
], 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 (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 [
|
|
204
|
-
}], propDecorators: { addSpaceInput: [{
|
|
205
|
-
type: ViewChild,
|
|
206
|
-
args: [IonInput, { static: false }]
|
|
207
|
-
}] } });
|
|
154
|
+
], 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 (spaces(); as 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 (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" }]
|
|
155
|
+
}], ctorParameters: () => [], propDecorators: { addSpaceInput: [{ type: i0.ViewChild, args: ['addTeamInput', { isSignal: true }] }] } });
|
|
208
156
|
//# 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 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 { SneatUserService } from '@sneat/auth-core';\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 IonItemSliding,\n IonItemOptions,\n IonItemOption,\n IonSpinner,\n IonSkeletonText,\n IonCardContent,\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 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 protected goSpace(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.goSpace(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 protected leaveSpace(\n space: IIdAndBrief<IUserSpaceBrief>,\n event?: Event,\n ): 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","<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(); as 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 (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,33 +1,28 @@
|
|
|
1
|
-
import { OnDestroy, OnInit } from '@angular/core';
|
|
2
|
-
import { IonInput } from '@ionic/angular/standalone';
|
|
3
1
|
import { IUserSpaceBrief } from '@sneat/auth-models';
|
|
4
2
|
import { IIdAndBrief } from '@sneat/core';
|
|
5
3
|
import { ISpaceContext } from '@sneat/space-models';
|
|
6
4
|
import * as i0 from "@angular/core";
|
|
7
|
-
export declare class SpacesCardComponent
|
|
5
|
+
export declare class SpacesCardComponent {
|
|
8
6
|
private readonly errorLogger;
|
|
9
7
|
private readonly navService;
|
|
10
8
|
private readonly userService;
|
|
11
9
|
private readonly spaceService;
|
|
12
10
|
private readonly analyticsService;
|
|
13
11
|
private readonly toastController;
|
|
14
|
-
addSpaceInput
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
addSpace(): void;
|
|
12
|
+
private readonly addSpaceInput;
|
|
13
|
+
private readonly userState;
|
|
14
|
+
protected readonly spaces: import("@angular/core").Signal<IIdAndBrief<IUserSpaceBrief>[] | undefined>;
|
|
15
|
+
protected readonly loadingState: import("@angular/core").Signal<"Loading" | "Authenticating">;
|
|
16
|
+
protected readonly showAdd: import("@angular/core").WritableSignal<boolean>;
|
|
17
|
+
protected readonly spaceName: import("@angular/core").WritableSignal<string>;
|
|
18
|
+
protected readonly adding: import("@angular/core").WritableSignal<boolean>;
|
|
19
|
+
constructor();
|
|
20
|
+
protected goSpace(space: ISpaceContext): void;
|
|
21
|
+
protected addSpace(): void;
|
|
22
|
+
private presentToast;
|
|
26
23
|
startAddingSpace(): void;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
private unsubscribe;
|
|
30
|
-
private setUser;
|
|
24
|
+
protected cancelAdd(): void;
|
|
25
|
+
protected leaveSpace(space: IIdAndBrief<IUserSpaceBrief>, event?: Event): void;
|
|
31
26
|
static ɵfac: i0.ɵɵFactoryDeclaration<SpacesCardComponent, never>;
|
|
32
27
|
static ɵcmp: i0.ɵɵComponentDeclaration<SpacesCardComponent, "sneat-spaces-card", never, {}, {}, never, never, true, never>;
|
|
33
28
|
}
|