@sneat/space-components 0.6.1 → 0.8.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 +18 -26
- 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 +3 -2
- package/lib/spaces-list/spaces-list.component.d.ts +4 -1
- package/package.json +1 -1
- package/tsconfig.lib.prod.tsbuildinfo +1 -1
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { ChangeDetectionStrategy, Component, computed, effect, inject, signal, viewChild, } from '@angular/core';
|
|
2
2
|
import { toSignal } from '@angular/core/rxjs-interop';
|
|
3
3
|
import { FormsModule } from '@angular/forms';
|
|
4
|
-
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';
|
|
5
5
|
import { AnalyticsService } from '@sneat/core';
|
|
6
6
|
import { ErrorLogger } from '@sneat/core';
|
|
7
7
|
import { SpaceNavService, SpaceService } from '@sneat/space-services';
|
|
8
8
|
import { SneatUserService } from '@sneat/auth-core';
|
|
9
|
+
import { SpacesListComponent } from '../spaces-list';
|
|
9
10
|
import * as i0 from "@angular/core";
|
|
10
11
|
import * as i1 from "@angular/forms";
|
|
11
12
|
// Signal-based + OnPush so the card repaints reactively when the user record
|
|
@@ -15,6 +16,9 @@ import * as i1 from "@angular/forms";
|
|
|
15
16
|
// record loaded but the view never repainted). toSignal()/computed() repaint
|
|
16
17
|
// correctly under zone.js too — this is not a zoneless change.
|
|
17
18
|
export class SpacesCardComponent {
|
|
19
|
+
get currentUserID() {
|
|
20
|
+
return this.userService.currentUserID ?? '';
|
|
21
|
+
}
|
|
18
22
|
constructor() {
|
|
19
23
|
this.errorLogger = inject(ErrorLogger);
|
|
20
24
|
this.navService = inject(SpaceNavService);
|
|
@@ -34,6 +38,14 @@ export class SpacesCardComponent {
|
|
|
34
38
|
.map(([id, brief]) => ({ id, brief }))
|
|
35
39
|
.sort((a, b) => (a.brief.title > b.brief.title ? 1 : -1));
|
|
36
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" }] : []));
|
|
37
49
|
this.loadingState = computed(() => this.userState()?.status === 'authenticated' ? 'Loading' : 'Authenticating', ...(ngDevMode ? [{ debugName: "loadingState" }] : []));
|
|
38
50
|
this.showAdd = signal(false, ...(ngDevMode ? [{ debugName: "showAdd" }] : []));
|
|
39
51
|
this.spaceName = signal('', ...(ngDevMode ? [{ debugName: "spaceName" }] : []));
|
|
@@ -46,7 +58,7 @@ export class SpacesCardComponent {
|
|
|
46
58
|
}
|
|
47
59
|
});
|
|
48
60
|
}
|
|
49
|
-
|
|
61
|
+
navigateToSpace(space) {
|
|
50
62
|
this.navService
|
|
51
63
|
.navigateToSpace(space, 'forward')
|
|
52
64
|
.catch(this.errorLogger.logError);
|
|
@@ -73,7 +85,7 @@ export class SpacesCardComponent {
|
|
|
73
85
|
this.adding.set(false);
|
|
74
86
|
this.spaceName.set('');
|
|
75
87
|
// The user record updates via Firestore, which recomputes `spaces`.
|
|
76
|
-
this.
|
|
88
|
+
this.navigateToSpace(space);
|
|
77
89
|
},
|
|
78
90
|
error: (err) => {
|
|
79
91
|
this.errorLogger.logError(err, 'Failed to create new team record');
|
|
@@ -111,26 +123,8 @@ export class SpacesCardComponent {
|
|
|
111
123
|
cancelAdd() {
|
|
112
124
|
this.showAdd.set(false);
|
|
113
125
|
}
|
|
114
|
-
leaveSpace(space, event) {
|
|
115
|
-
if (event) {
|
|
116
|
-
event.stopPropagation();
|
|
117
|
-
event.preventDefault();
|
|
118
|
-
}
|
|
119
|
-
if (!confirm(`Are you sure you want to leave team ${space.brief.title}?`)) {
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
const userID = this.userService.currentUserID;
|
|
123
|
-
if (!userID) {
|
|
124
|
-
this.errorLogger.logError('Failed to get current user ID');
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
this.spaceService.leaveSpace({ spaceID: space.id }).subscribe({
|
|
128
|
-
next: () => console.log('left space'),
|
|
129
|
-
error: (err) => this.errorLogger.logError(err, `Failed to leave a space: ${space.brief.title}`),
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
126
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SpacesCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
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 (
|
|
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 }); }
|
|
134
128
|
}
|
|
135
129
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SpacesCardComponent, decorators: [{
|
|
136
130
|
type: Component,
|
|
@@ -145,12 +139,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
145
139
|
IonButton,
|
|
146
140
|
IonIcon,
|
|
147
141
|
IonList,
|
|
148
|
-
IonItemSliding,
|
|
149
|
-
IonItemOptions,
|
|
150
|
-
IonItemOption,
|
|
151
142
|
IonSpinner,
|
|
152
143
|
IonSkeletonText,
|
|
153
144
|
IonCardContent,
|
|
154
|
-
|
|
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" }]
|
|
155
147
|
}], ctorParameters: () => [], propDecorators: { addSpaceInput: [{ type: i0.ViewChild, args: ['addTeamInput', { isSignal: true }] }] } });
|
|
156
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 {\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
|
+
{"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":"AAAA,OAAO,EACL,uBAAuB,EACvB,SAAS,EACT,QAAQ,EACR,MAAM,EACN,MAAM,EACN,MAAM,EACN,SAAS,GACV,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EACL,SAAS,EACT,UAAU,EACV,OAAO,EACP,cAAc,EACd,YAAY,EACZ,OAAO,EACP,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,OAAO,EACP,eAAe,EACf,UAAU,EACV,eAAe,GAChB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,gBAAgB,EAAqB,MAAM,aAAa,CAAC;AAGlE,OAAO,EAAE,WAAW,EAAgB,MAAM,aAAa,CAAC;AAExD,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;;;AAErD,6EAA6E;AAC7E,+EAA+E;AAC/E,kFAAkF;AAClF,4EAA4E;AAC5E,6EAA6E;AAC7E,+DAA+D;AAsB/D,MAAM,OAAO,mBAAmB;IAsC9B,IAAc,aAAa;QACzB,OAAO,IAAI,CAAC,WAAW,CAAC,aAAa,IAAI,EAAE,CAAC;IAC9C,CAAC;IAUD;QAjDiB,gBAAW,GAAG,MAAM,CAAe,WAAW,CAAC,CAAC;QAChD,eAAU,GAAG,MAAM,CAAC,eAAe,CAAC,CAAC;QACrC,gBAAW,GAAG,MAAM,CAAC,gBAAgB,CAAC,CAAC;QACvC,iBAAY,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;QACpC,qBAAgB,GAC/B,MAAM,CAAoB,gBAAgB,CAAC,CAAC;QAC7B,oBAAe,GAAG,MAAM,CAAC,eAAe,CAAC,CAAC;QAE1C,kBAAa,GAAG,SAAS,CAAW,cAAc,yDAAC,CAAC;QAEpD,cAAS,GAAG,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;QAElE,oEAAoE;QACjD,WAAM,GAAG,QAAQ,CAElC,GAAG,EAAE;YACL,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,EAAE,MAAM,CAAC;YACxC,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,OAAO,SAAS,CAAC;YACnB,CAAC;YACD,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC;iBACvC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;iBACrC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9D,CAAC,kDAAC,CAAC;QAEH,4EAA4E;QAC5E,2EAA2E;QAC3E,8BAA8B;QACX,kBAAa,GAAG,QAAQ,CACzC,GAAG,EAAE,CACH,IAAI,CAAC,MAAM,EAAE,EAAE,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;YACrC,EAAE;YACF,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE;SACpE,CAAC,CAAC,yDACN,CAAC;QAMiB,iBAAY,GAAG,QAAQ,CAAC,GAAG,EAAE,CAC9C,IAAI,CAAC,SAAS,EAAE,EAAE,MAAM,KAAK,eAAe,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,gBAAgB,wDAC5E,CAAC;QAEiB,YAAO,GAAG,MAAM,CAAC,KAAK,mDAAC,CAAC;QACxB,cAAS,GAAG,MAAM,CAAC,EAAE,qDAAC,CAAC;QACvB,WAAM,GAAG,MAAM,CAAC,KAAK,kDAAC,CAAC;QAGxC,sEAAsE;QACtE,MAAM,CAAC,GAAG,EAAE;YACV,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YAC7B,IAAI,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAClC,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC1B,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,eAAe,CAAC,KAAoB;QAC1C,IAAI,CAAC,UAAU;aACZ,eAAe,CAAC,KAAK,EAAE,SAAS,CAAC;aACjC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;IACtC,CAAC;IAES,QAAQ;QAChB,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;QAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC;QACtC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,IAAI,CAAC,YAAY,CAAC,wBAAwB,EAAE,UAAU,CAAC,CAAC;YACxD,OAAO;QACT,CAAC;QACD,IAAI,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,KAAK,KAAK,CAAC,EAAE,CAAC;YACxD,IAAI,CAAC,YAAY,CAAC,4CAA4C,EAAE,QAAQ,CAAC,CAAC;YAC1E,OAAO;QACT,CAAC;QACD,MAAM,OAAO,GAAwB;YACnC,IAAI,EAAE,MAAM;YACZ,KAAK;SACN,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACtB,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC;YAC/C,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE;gBACd,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,cAAc,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;gBACpE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;gBACvB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBACvB,oEAAoE;gBACpE,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;YAC9B,CAAC;YACD,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE;gBACb,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,EAAE,kCAAkC,CAAC,CAAC;gBACnE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACzB,CAAC;SACF,CAAC,CAAC;IACL,CAAC;IAEO,YAAY,CAAC,OAAe,EAAE,KAAa;QACjD,IAAI,CAAC,eAAe;aACjB,MAAM,CAAC;YACN,QAAQ,EAAE,QAAQ;YAClB,OAAO;YACP,KAAK;YACL,QAAQ,EAAE,IAAI;YACd,aAAa,EAAE,IAAI;YACnB,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;SAC1C,CAAC;aACD,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CACd,KAAK;aACF,OAAO,EAAE;aACT,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CACb,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,EAAE,yBAAyB,CAAC,CAC1D,CACJ;aACA,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,EAAE,wBAAwB,CAAC,CAAC,CAAC;IAC9E,CAAC;IAEM,gBAAgB;QACrB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACvB,UAAU,CAAC,GAAG,EAAE;YACd,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;YACnC,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,OAAO;YACT,CAAC;YACD,KAAK;iBACF,QAAQ,EAAE;iBACV,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CACb,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,EAAE,qCAAqC,CAAC,CACtE,CAAC;QACN,CAAC,EAAE,GAAG,CAAC,CAAC;IACV,CAAC;IAES,SAAS;QACjB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;8GAtIU,mBAAmB;kGAAnB,mBAAmB,4MC9DhC,g/DAoEA,2CDtBI,WAAW,+VACX,QAAQ,8eACR,OAAO,yLACP,OAAO,0NACP,QAAQ,6FACR,YAAY,sFACZ,UAAU,8EACV,SAAS,oPACT,OAAO,2JACP,OAAO,yFACP,UAAU,yGACV,eAAe,oFACf,cAAc,+EACd,mBAAmB;;2FAGV,mBAAmB;kBArB/B,SAAS;+BACE,mBAAmB,mBAEZ,uBAAuB,CAAC,MAAM,WACtC;wBACP,WAAW;wBACX,QAAQ;wBACR,OAAO;wBACP,OAAO;wBACP,QAAQ;wBACR,YAAY;wBACZ,UAAU;wBACV,SAAS;wBACT,OAAO;wBACP,OAAO;wBACP,UAAU;wBACV,eAAe;wBACf,cAAc;wBACd,mBAAmB;qBACpB;qGAWoD,cAAc","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":"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","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,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spaces-menu.component.js","sourceRoot":"","sources":["../../../../../../../libs/space/components/src/lib/spaces-menu/spaces-menu.component.ts","../../../../../../../libs/space/components/src/lib/spaces-menu/spaces-menu.component.html"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"spaces-menu.component.js","sourceRoot":"","sources":["../../../../../../../libs/space/components/src/lib/spaces-menu/spaces-menu.component.ts","../../../../../../../libs/space/components/src/lib/spaces-menu/spaces-menu.component.html"],"names":[],"mappings":"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","sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n computed,\n Input,\n signal,\n inject,\n} from '@angular/core';\nimport {\n IonIcon,\n IonItem,\n IonLabel,\n MenuController,\n NavController,\n} from '@ionic/angular/standalone';\nimport { ISneatUserState, SneatUserService } from '@sneat/auth-core';\nimport { UserRequiredFieldsService } from '@sneat/auth-ui';\nimport { SpaceType } from '@sneat/core';\nimport {\n ISpaceContext,\n spaceContextFromBrief,\n zipMapBriefsWithIDs,\n} from '@sneat/space-models';\nimport { ClassName, SneatBaseComponent } from '@sneat/ui';\nimport { SpacesListComponent } from '../spaces-list';\n\n@Component({\n selector: 'sneat-spaces-menu',\n templateUrl: './spaces-menu.component.html',\n imports: [SpacesListComponent, IonItem, IonLabel, IonIcon],\n changeDetection: ChangeDetectionStrategy.OnPush,\n providers: [\n {\n provide: ClassName,\n useValue: 'SpacesMenuComponent',\n },\n UserRequiredFieldsService,\n ],\n})\nexport class SpacesMenuComponent extends SneatBaseComponent {\n readonly userService = inject(SneatUserService);\n private readonly navController = inject(NavController);\n private readonly menuController = inject(MenuController);\n\n @Input() spacesLabel = 'Spaces';\n @Input() pathPrefix = '/space';\n\n @Input() spaceType?: SpaceType;\n\n protected readonly $userSpaces = signal<ISpaceContext[] | undefined>(\n undefined,\n );\n\n protected readonly $userID = signal<string>('');\n\n protected readonly $spacesToShow = computed<ISpaceContext[]>(() => {\n const userSpaces = this.$userSpaces();\n const spaces =\n (this.spaceType\n ? userSpaces?.filter((t) => t.type === this.spaceType)\n : userSpaces) || [];\n if (!this.spaceType) {\n const addPseudoSpace = (type: 'family' | 'private'): void => {\n if (!spaces.some((t) => t.type === type)) {\n spaces.push({\n id: '',\n type,\n brief: userSpaces\n ? {\n type,\n title: '',\n }\n : undefined, // define brief indicates we have user record loaded\n });\n }\n };\n addPseudoSpace('family');\n addPseudoSpace('private');\n }\n const sortOrder: Record<string, number> = {\n family: 1,\n private: 2,\n };\n spaces.sort((a: ISpaceContext, b: ISpaceContext) => {\n // Determine the sorting priority (lower values mean higher priority)\n const priorityA = (a.type && sortOrder[a.type]) ?? 3; // Default to 3 for all other types\n const priorityB = (b.type && sortOrder[b.type]) ?? 3;\n\n // Compare priority first\n if (priorityA !== priorityB) {\n return priorityA - priorityB;\n }\n\n // If same priority, sort by title alphabetically\n return a.brief?.title.localeCompare(b.brief?.title || '') || 0;\n });\n return spaces;\n });\n\n private onUserStateChanged = (user: ISneatUserState): void => {\n this.$userID.set(user.user?.uid || '');\n if (!user?.record) {\n this.$userSpaces.set(undefined);\n return;\n }\n\n this.$userSpaces.set(\n user?.record?.spaces\n ? zipMapBriefsWithIDs(user?.record?.spaces).map((t) =>\n spaceContextFromBrief(t.id, t.brief),\n )\n : [],\n );\n };\n\n protected readonly $familySpace = signal<ISpaceContext | undefined>(\n undefined,\n );\n\n constructor() {\n super();\n const userService = this.userService;\n\n userService.userState.pipe(this.takeUntilDestroyed()).subscribe({\n next: this.onUserStateChanged,\n });\n }\n\n protected closeMenu(): void {\n this.menuController\n .close()\n .catch(this.errorLogger.logErrorHandler('Failed to close teams menu'));\n }\n}\n","<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"]}
|
|
@@ -12,17 +12,18 @@ export declare class SpacesCardComponent {
|
|
|
12
12
|
private readonly addSpaceInput;
|
|
13
13
|
private readonly userState;
|
|
14
14
|
protected readonly spaces: import("@angular/core").Signal<IIdAndBrief<IUserSpaceBrief>[] | undefined>;
|
|
15
|
+
protected readonly spaceContexts: import("@angular/core").Signal<ISpaceContext[] | undefined>;
|
|
16
|
+
protected get currentUserID(): string;
|
|
15
17
|
protected readonly loadingState: import("@angular/core").Signal<"Loading" | "Authenticating">;
|
|
16
18
|
protected readonly showAdd: import("@angular/core").WritableSignal<boolean>;
|
|
17
19
|
protected readonly spaceName: import("@angular/core").WritableSignal<string>;
|
|
18
20
|
protected readonly adding: import("@angular/core").WritableSignal<boolean>;
|
|
19
21
|
constructor();
|
|
20
|
-
|
|
22
|
+
private navigateToSpace;
|
|
21
23
|
protected addSpace(): void;
|
|
22
24
|
private presentToast;
|
|
23
25
|
startAddingSpace(): void;
|
|
24
26
|
protected cancelAdd(): void;
|
|
25
|
-
protected leaveSpace(space: IIdAndBrief<IUserSpaceBrief>, event?: Event): void;
|
|
26
27
|
static ɵfac: i0.ɵɵFactoryDeclaration<SpacesCardComponent, never>;
|
|
27
28
|
static ɵcmp: i0.ɵɵComponentDeclaration<SpacesCardComponent, "sneat-spaces-card", never, {}, {}, never, never, true, never>;
|
|
28
29
|
}
|
|
@@ -12,11 +12,14 @@ export declare class SpacesListComponent extends SneatBaseComponent {
|
|
|
12
12
|
userID?: string;
|
|
13
13
|
spaces?: ISpaceContext[];
|
|
14
14
|
pathPrefix: string;
|
|
15
|
+
allowLeave: boolean;
|
|
15
16
|
readonly beforeNavigateToSpace: EventEmitter<ISpaceContext>;
|
|
17
|
+
readonly leftSpace: EventEmitter<ISpaceContext>;
|
|
16
18
|
protected goSpace(event: Event, space: ISpaceContext): boolean;
|
|
17
19
|
private navigateToSpace;
|
|
18
20
|
protected createNewSpace(type: SpaceType): boolean;
|
|
19
21
|
private createSpace;
|
|
22
|
+
protected leaveSpace(space: ISpaceContext, event?: Event): void;
|
|
20
23
|
static ɵfac: i0.ɵɵFactoryDeclaration<SpacesListComponent, never>;
|
|
21
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<SpacesListComponent, "sneat-spaces-list", never, { "userID": { "alias": "userID"; "required": true; }; "spaces": { "alias": "spaces"; "required": true; }; "pathPrefix": { "alias": "pathPrefix"; "required": false; }; }, { "beforeNavigateToSpace": "beforeNavigateToSpace"; }, never, never, true, never>;
|
|
24
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<SpacesListComponent, "sneat-spaces-list", never, { "userID": { "alias": "userID"; "required": true; }; "spaces": { "alias": "spaces"; "required": true; }; "pathPrefix": { "alias": "pathPrefix"; "required": false; }; "allowLeave": { "alias": "allowLeave"; "required": false; }; }, { "beforeNavigateToSpace": "beforeNavigateToSpace"; "leftSpace": "leftSpace"; }, never, never, true, never>;
|
|
22
25
|
}
|