@sneat/space-components 0.27.13 → 0.27.14
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.
|
@@ -34,7 +34,7 @@ export class SpacesCardComponent {
|
|
|
34
34
|
this.spaceService = inject(SpaceService);
|
|
35
35
|
this.analyticsService = inject(AnalyticsService);
|
|
36
36
|
this.toastController = inject(ToastController);
|
|
37
|
-
this.addSpaceInput = viewChild('
|
|
37
|
+
this.addSpaceInput = viewChild('addSpaceInput', /* @ts-ignore */
|
|
38
38
|
...(ngDevMode ? [{ debugName: "addSpaceInput" }] : /* istanbul ignore next */ []));
|
|
39
39
|
this.userState = toSignal(this.userService.userState);
|
|
40
40
|
// undefined => user record not loaded yet (render the loading row).
|
|
@@ -72,6 +72,8 @@ export class SpacesCardComponent {
|
|
|
72
72
|
...(ngDevMode ? [{ debugName: "spaceName" }] : /* istanbul ignore next */ []));
|
|
73
73
|
this.adding = signal(false, /* @ts-ignore */
|
|
74
74
|
...(ngDevMode ? [{ debugName: "adding" }] : /* istanbul ignore next */ []));
|
|
75
|
+
this.newSpaceNamePlaceholder = 'New space name';
|
|
76
|
+
this.addSpaceHelp = 'Enter a space name and click "Create" to add it.';
|
|
75
77
|
// Auto-open the "add space" form once we know the user has no spaces —
|
|
76
78
|
// unless adding is disabled (a registration-flow product supplies its own
|
|
77
79
|
// call to action).
|
|
@@ -95,7 +97,7 @@ export class SpacesCardComponent {
|
|
|
95
97
|
return;
|
|
96
98
|
}
|
|
97
99
|
if (this.spaces()?.find((t) => t.brief.title === title)) {
|
|
98
|
-
this.presentToast('You already have a
|
|
100
|
+
this.presentToast('You already have a space with the same name', 'danger');
|
|
99
101
|
return;
|
|
100
102
|
}
|
|
101
103
|
const request = {
|
|
@@ -113,7 +115,7 @@ export class SpacesCardComponent {
|
|
|
113
115
|
this.navigateToSpace(space);
|
|
114
116
|
},
|
|
115
117
|
error: (err) => {
|
|
116
|
-
this.errorLogger.logError(err, 'Failed to create new
|
|
118
|
+
this.errorLogger.logError(err, 'Failed to create new space record');
|
|
117
119
|
this.adding.set(false);
|
|
118
120
|
},
|
|
119
121
|
});
|
|
@@ -142,14 +144,14 @@ export class SpacesCardComponent {
|
|
|
142
144
|
}
|
|
143
145
|
input
|
|
144
146
|
.setFocus()
|
|
145
|
-
.catch((err) => this.errorLogger.logError(err, 'Failed to set focus to
|
|
147
|
+
.catch((err) => this.errorLogger.logError(err, 'Failed to set focus to addSpaceInput'));
|
|
146
148
|
}, 200);
|
|
147
149
|
}
|
|
148
150
|
cancelAdd() {
|
|
149
151
|
this.showAdd.set(false);
|
|
150
152
|
}
|
|
151
153
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SpacesCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
152
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: SpacesCardComponent, isStandalone: true, selector: "sneat-spaces-card", inputs: { title: "title", spaceType: "spaceType", groupKind: "groupKind", canAdd: "canAdd" }, viewQueries: [{ propertyName: "addSpaceInput", first: true, predicate: ["
|
|
154
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: SpacesCardComponent, isStandalone: true, selector: "sneat-spaces-card", inputs: { title: "title", spaceType: "spaceType", groupKind: "groupKind", canAdd: "canAdd" }, viewQueries: [{ propertyName: "addSpaceInput", first: true, predicate: ["addSpaceInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<ion-card>\n <ion-item class=\"sneat-card-header\" lines=\"full\">\n <ion-label>\n <ion-card-title color=\"medium\">{{ title }}</ion-card-title>\n </ion-label>\n <ion-buttons slot=\"end\" (click)=\"startAddingSpace()\">\n @if (canAdd && !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 #addSpaceInput\n [ngModel]=\"spaceName()\"\n (ngModelChange)=\"spaceName.set($event)\"\n (keyup.escape)=\"cancelAdd()\"\n [placeholder]=\"newSpaceNamePlaceholder\"\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>{{ addSpaceHelp }}</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"], outputs: ["ionFocus", "ionBlur"] }, { 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 }); }
|
|
153
155
|
}
|
|
154
156
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SpacesCardComponent, decorators: [{
|
|
155
157
|
type: Component,
|
|
@@ -168,7 +170,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
168
170
|
IonSkeletonText,
|
|
169
171
|
IonCardContent,
|
|
170
172
|
SpacesListComponent,
|
|
171
|
-
], template: "<ion-card>\n <ion-item class=\"sneat-card-header\" lines=\"full\">\n <ion-label>\n <ion-card-title color=\"medium\">{{ title }}</ion-card-title>\n </ion-label>\n <ion-buttons slot=\"end\" (click)=\"startAddingSpace()\">\n @if (canAdd && !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 #
|
|
173
|
+
], template: "<ion-card>\n <ion-item class=\"sneat-card-header\" lines=\"full\">\n <ion-label>\n <ion-card-title color=\"medium\">{{ title }}</ion-card-title>\n </ion-label>\n <ion-buttons slot=\"end\" (click)=\"startAddingSpace()\">\n @if (canAdd && !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 #addSpaceInput\n [ngModel]=\"spaceName()\"\n (ngModelChange)=\"spaceName.set($event)\"\n (keyup.escape)=\"cancelAdd()\"\n [placeholder]=\"newSpaceNamePlaceholder\"\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>{{ addSpaceHelp }}</p>\n </ion-card-content>\n }\n</ion-card>\n" }]
|
|
172
174
|
}], ctorParameters: () => [], propDecorators: { title: [{
|
|
173
175
|
type: Input
|
|
174
176
|
}], spaceType: [{
|
|
@@ -177,5 +179,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
177
179
|
type: Input
|
|
178
180
|
}], canAdd: [{
|
|
179
181
|
type: Input
|
|
180
|
-
}], addSpaceInput: [{ type: i0.ViewChild, args: ['
|
|
182
|
+
}], addSpaceInput: [{ type: i0.ViewChild, args: ['addSpaceInput', { isSignal: true }] }] } });
|
|
181
183
|
//# 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":"AAAA,OAAO,EACL,uBAAuB,EACvB,SAAS,EACT,QAAQ,EACR,MAAM,EACN,MAAM,EACN,KAAK,EACL,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,gBAAgB,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAqB,MAAM,aAAa,CAAC;AAGlE,OAAO,EAAE,WAAW,EAAgB,MAAM,aAAa,CAAC;AAGxD,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;IAqE9B,IAAc,aAAa;QACzB,OAAO,IAAI,CAAC,WAAW,CAAC,aAAa,IAAI,EAAE,CAAC;IAC9C,CAAC;IAUD;QAhFA,sEAAsE;QAC7D,UAAK,GAAG,QAAQ,CAAC;QAa1B;;;;WAIG;QACM,WAAM,GAAG,IAAI,CAAC;QAEN,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;0FAAC,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,MAAM,CACL,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CACZ,CAAC,CAAC,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,SAAS,CAAC;gBAClD,CAAC,CAAC,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC,SAAS,KAAK,IAAI,CAAC,SAAS,CAAC,CAC1D;iBACA,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;mFAAC,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;gBACL,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,KAAK,EAAE,KAAK,CAAC,KAAK;aACnB;SACF,CAAC,CAAC;0FACN,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;yFAC5E,CAAC;QAEiB,YAAO,GAAG,MAAM,CAAC,KAAK;oFAAC,CAAC;QACxB,cAAS,GAAG,MAAM,CAAC,EAAE;sFAAC,CAAC;QACvB,WAAM,GAAG,MAAM,CAAC,KAAK;mFAAC,CAAC;QAGxC,uEAAuE;QACvE,0EAA0E;QAC1E,mBAAmB;QACnB,MAAM,CAAC,GAAG,EAAE;YACV,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YAC7B,IAAI,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBACjD,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,IAAI,CAAC,SAAS,IAAI,MAAM;YAC9B,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,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;8GAxKU,mBAAmB;kGAAnB,mBAAmB,0SChEhC,0iEAoEA,2CDpBI,WAAW,+VACX,QAAQ,8eACR,OAAO,yLACP,OAAO,0NACP,QAAQ,6FACR,YAAY,sFACZ,UAAU,8EACV,SAAS,sRACT,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;;sBAIA,KAAK;;sBAQL,KAAK;;sBAGL,KAAK;;sBAOL,KAAK;6DAU+C,cAAc","sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n computed,\n effect,\n inject,\n Input,\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';\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 { GroupKind, SpaceType } from '@sneat/core';\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 /** Card heading — a product names its spaces (\"Clubs\", \"Schools\"). */\n @Input() title = 'Spaces';\n\n /**\n * When set, only spaces of this type are listed. A Sneat account holds\n * spaces from every product (family, work, clubs...), and a product home\n * that lists them all reads as a generic space browser rather than the\n * product — sneat.club wants exactly the `club` spaces.\n */\n @Input() spaceType?: SpaceType;\n\n /** Narrows group spaces and tags new groups with a product-specific purpose. */\n @Input() groupKind?: GroupKind;\n\n /**\n * Whether the card offers its quick-add form. A product whose spaces are\n * created by a registration flow (decision 0006) passes false and offers\n * its own \"Register ...\" call to action instead.\n */\n @Input() canAdd = true;\n\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 .filter(\n ({ brief }) =>\n (!this.spaceType || brief.type === this.spaceType) &&\n (!this.groupKind || brief.groupKind === this.groupKind),\n )\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: {\n title: brief.title,\n type: brief.type,\n groupKind: brief.groupKind,\n roles: brief.roles,\n },\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 // unless adding is disabled (a registration-flow product supplies its own\n // call to action).\n effect(() => {\n const spaces = this.spaces();\n if (spaces && spaces.length === 0 && this.canAdd) {\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: this.spaceType ?? 'team',\n groupKind: this.groupKind,\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 class=\"sneat-card-header\" lines=\"full\">\n <ion-label>\n <ion-card-title color=\"medium\">{{ title }}</ion-card-title>\n </ion-label>\n <ion-buttons slot=\"end\" (click)=\"startAddingSpace()\">\n @if (canAdd && !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
|
+
{"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 Input,\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';\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 { GroupKind, SpaceType } from '@sneat/core';\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 /** Card heading — a product names its spaces (\"Clubs\", \"Schools\"). */\n @Input() title = 'Spaces';\n\n /**\n * When set, only spaces of this type are listed. A Sneat account holds\n * spaces from every product (family, work, clubs...), and a product home\n * that lists them all reads as a generic space browser rather than the\n * product — sneat.club wants exactly the `club` spaces.\n */\n @Input() spaceType?: SpaceType;\n\n /** Narrows group spaces and tags new groups with a product-specific purpose. */\n @Input() groupKind?: GroupKind;\n\n /**\n * Whether the card offers its quick-add form. A product whose spaces are\n * created by a registration flow (decision 0006) passes false and offers\n * its own \"Register ...\" call to action instead.\n */\n @Input() canAdd = true;\n\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>('addSpaceInput');\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 .filter(\n ({ brief }) =>\n (!this.spaceType || brief.type === this.spaceType) &&\n (!this.groupKind || brief.groupKind === this.groupKind),\n )\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: {\n title: brief.title,\n type: brief.type,\n groupKind: brief.groupKind,\n roles: brief.roles,\n },\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 protected readonly newSpaceNamePlaceholder = 'New space name';\n protected readonly addSpaceHelp =\n 'Enter a space name and click \"Create\" to add it.';\n\n public constructor() {\n // Auto-open the \"add space\" form once we know the user has no spaces —\n // unless adding is disabled (a registration-flow product supplies its own\n // call to action).\n effect(() => {\n const spaces = this.spaces();\n if (spaces && spaces.length === 0 && this.canAdd) {\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 space with the same name', 'danger');\n return;\n }\n const request: ICreateSpaceRequest = {\n type: this.spaceType ?? 'team',\n groupKind: this.groupKind,\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 space 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(\n err,\n 'Failed to set focus to addSpaceInput',\n ),\n );\n }, 200);\n }\n\n protected cancelAdd(): void {\n this.showAdd.set(false);\n }\n}\n","<ion-card>\n <ion-item class=\"sneat-card-header\" lines=\"full\">\n <ion-label>\n <ion-card-title color=\"medium\">{{ title }}</ion-card-title>\n </ion-label>\n <ion-buttons slot=\"end\" (click)=\"startAddingSpace()\">\n @if (canAdd && !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 #addSpaceInput\n [ngModel]=\"spaceName()\"\n (ngModelChange)=\"spaceName.set($event)\"\n (keyup.escape)=\"cancelAdd()\"\n [placeholder]=\"newSpaceNamePlaceholder\"\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>{{ addSpaceHelp }}</p>\n </ion-card-content>\n }\n</ion-card>\n"]}
|
|
@@ -36,6 +36,8 @@ export declare class SpacesCardComponent {
|
|
|
36
36
|
protected readonly showAdd: import("@angular/core").WritableSignal<boolean>;
|
|
37
37
|
protected readonly spaceName: import("@angular/core").WritableSignal<string>;
|
|
38
38
|
protected readonly adding: import("@angular/core").WritableSignal<boolean>;
|
|
39
|
+
protected readonly newSpaceNamePlaceholder = "New space name";
|
|
40
|
+
protected readonly addSpaceHelp = "Enter a space name and click \"Create\" to add it.";
|
|
39
41
|
constructor();
|
|
40
42
|
private navigateToSpace;
|
|
41
43
|
protected addSpace(): void;
|