diff --git a/src/assets/storybook/sites/companylisa.ts b/src/assets/storybook/sites/companylisa.ts new file mode 100644 index 00000000000..3689523d7d7 --- /dev/null +++ b/src/assets/storybook/sites/companylisa.ts @@ -0,0 +1,32 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { CoreSiteFixture } from '@/storybook/stubs/classes/site'; + +export const companyLisaSite: CoreSiteFixture = { + id: 'companylisasite', + info: { + version: '2022041900', + sitename: 'Company', + username: 'lisa', + firstname: 'Lisa', + lastname: 'Díaz', + fullname: 'Lisa Díaz', + lang: 'en', + userid: 1, + siteurl: 'https://company.example.edu', + userpictureurl: 'https://i.pravatar.cc/300?user=companylisa', + functions: [], + }, +}; diff --git a/src/assets/storybook/sites/school.json b/src/assets/storybook/sites/school.json deleted file mode 100644 index 1260527ba16..00000000000 --- a/src/assets/storybook/sites/school.json +++ /dev/null @@ -1 +0,0 @@ -{"id":"123456","info":{"version":"2022041900","sitename":"School","username":"barbara","firstname":"Barbara","lastname":"Gardner","fullname":"Barbara Gardner","lang":"en","userid":1,"siteurl":"https://campus.example.edu","userpictureurl":"","functions":[]}} diff --git a/src/assets/storybook/sites/schoolbarbara.ts b/src/assets/storybook/sites/schoolbarbara.ts new file mode 100644 index 00000000000..c4fa3eda3a7 --- /dev/null +++ b/src/assets/storybook/sites/schoolbarbara.ts @@ -0,0 +1,32 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { CoreSiteFixture } from '@/storybook/stubs/classes/site'; + +export const schoolBarbaraSite: CoreSiteFixture = { + id: 'schoolbarbarasite', + info: { + version: '2022041900', + sitename: 'School', + username: 'barbara', + firstname: 'Barbara', + lastname: 'Gardner', + fullname: 'Barbara Gardner', + lang: 'en', + userid: 1, + siteurl: 'https://campus.example.edu', + userpictureurl: 'https://i.pravatar.cc/300?user=schoolbarbara', + functions: [], + }, +}; diff --git a/src/assets/storybook/sites/schooljeffery.ts b/src/assets/storybook/sites/schooljeffery.ts new file mode 100644 index 00000000000..c7823f2b069 --- /dev/null +++ b/src/assets/storybook/sites/schooljeffery.ts @@ -0,0 +1,32 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { CoreSiteFixture } from '@/storybook/stubs/classes/site'; + +export const schoolJefferySite: CoreSiteFixture = { + id: 'schooljefferysite', + info: { + version: '2022041900', + sitename: 'School', + username: 'jeffery', + firstname: 'Jeffery', + lastname: 'Sanders', + fullname: 'Jeffery Sanders', + lang: 'en', + userid: 2, + siteurl: 'https://campus.example.edu', + userpictureurl: 'https://i.pravatar.cc/300?user=schooljeffery', + functions: [], + }, +}; diff --git a/src/core/components/stories/components/components.module.ts b/src/core/components/stories/components/components.module.ts index e70834d304c..d0648467ee1 100644 --- a/src/core/components/stories/components/components.module.ts +++ b/src/core/components/stories/components/components.module.ts @@ -21,6 +21,8 @@ import { CoreComponentsModule } from '@components/components.module'; import { CommonModule } from '@angular/common'; import { CoreCourseImageCardsPageComponent } from '@components/stories/components/course-image-cards-page/course-image-cards-page'; import { CoreCourseImageListPageComponent } from '@components/stories/components/course-image-list-page/course-image-list-page'; +import { CoreSitesListWrapperComponent } from './sites-list-wrapper/sites-list-wrapper'; +import { CoreDirectivesModule } from '@directives/directives.module'; @NgModule({ declarations: [ @@ -28,10 +30,12 @@ import { CoreCourseImageListPageComponent } from '@components/stories/components CoreCourseImageListPageComponent, CoreEmptyBoxPageComponent, CoreEmptyBoxWrapperComponent, + CoreSitesListWrapperComponent, ], imports: [ CommonModule, StorybookModule, + CoreDirectivesModule, CoreComponentsModule, CoreSearchComponentsModule, ], diff --git a/src/core/components/stories/components/sites-list-wrapper/sites-list-wrapper.html b/src/core/components/stories/components/sites-list-wrapper/sites-list-wrapper.html new file mode 100644 index 00000000000..c51b145ec1c --- /dev/null +++ b/src/core/components/stories/components/sites-list-wrapper/sites-list-wrapper.html @@ -0,0 +1,23 @@ + + + + + +

Extra text for user {{ site.fullname }}

+ {{ site.badge }} MB +
+ + + + + + + + {{site.badge}} + + + +
+
+
diff --git a/src/core/components/stories/components/sites-list-wrapper/sites-list-wrapper.ts b/src/core/components/stories/components/sites-list-wrapper/sites-list-wrapper.ts new file mode 100644 index 00000000000..06dc02b22bb --- /dev/null +++ b/src/core/components/stories/components/sites-list-wrapper/sites-list-wrapper.ts @@ -0,0 +1,60 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; +import { CoreAccountsList, CoreLoginHelper } from '@features/login/services/login-helper'; +import { CoreSiteBasicInfo } from '@services/sites'; + +@Component({ + selector: 'core-sites-list-wrapper', + templateUrl: 'sites-list-wrapper.html', +}) +export class CoreSitesListWrapperComponent implements OnInit, OnChanges { + + @Input() sitesClickable = false; + @Input() currentSiteClickableSelect = 'undefined'; + @Input() extraText: 'text' | 'badge' | 'none' = 'none'; + @Input() extraDetails: 'delete-button' | 'badge' | 'none' = 'none'; + + accountsList?: CoreAccountsList; + currentSiteClickable?: boolean; + + /** + * @inheritdoc + */ + async ngOnInit(): Promise { + this.accountsList = await CoreLoginHelper.getAccountsList(); + } + + /** + * @inheritdoc + */ + async ngOnChanges(changes: SimpleChanges): Promise { + if (changes.currentSiteClickableSelect) { + this.currentSiteClickable = this.currentSiteClickableSelect === 'undefined' ? + undefined : + this.currentSiteClickableSelect === 'true'; + } + } + + /** + * Site clicked. + * + * @param site Site. + */ + siteClicked(site: CoreSiteBasicInfo): void { + alert(`clicked on ${site.id} - ${site.fullname}`); + } + +} diff --git a/src/core/components/stories/site-list.stories.ts b/src/core/components/stories/site-list.stories.ts new file mode 100644 index 00000000000..19dcb759fc3 --- /dev/null +++ b/src/core/components/stories/site-list.stories.ts @@ -0,0 +1,78 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Meta, moduleMetadata } from '@storybook/angular'; + +import { story } from '@/storybook/utils/helpers'; +import { CoreSitesListComponent } from '@components/sites-list/sites-list'; +import { CoreSitesListWrapperComponent } from './components/sites-list-wrapper/sites-list-wrapper'; +import { CoreComponentsStorybookModule } from './components/components.module'; + +interface Args { + sitesClickable: boolean; + currentSiteClickable: 'true' | 'false' | 'undefined'; + extraText: 'text' | 'badge' | 'none'; + extraDetails: 'delete-button' | 'badge' | 'none'; +} + +export default > { + title: 'Core/Site List', + component: CoreSitesListComponent, + decorators: [ + moduleMetadata({ imports: [CoreComponentsStorybookModule] }), + ], + argTypes: { + sitesClickable: { + control: { + type: 'boolean', + }, + }, + currentSiteClickable: { + control: { + type: 'select', + options: ['true', 'false', 'undefined'], + }, + }, + extraText: { + control: { + type: 'select', + options: ['text', 'badge', 'none'], + }, + }, + extraDetails: { + control: { + type: 'select', + options: ['delete-button', 'badge', 'none'], + }, + }, + }, + args: { + sitesClickable: false, + currentSiteClickable: 'undefined', + extraText: 'none', + extraDetails: 'none', + }, +}; + +const Template = story(({ sitesClickable, currentSiteClickable, extraText, extraDetails }) => ({ + component: CoreSitesListWrapperComponent, + props: { + sitesClickable, + currentSiteClickableSelect: currentSiteClickable, + extraText, + extraDetails, + }, +})); + +export const Primary = story(Template); diff --git a/src/core/features/login/components/sites-modal/sites-modal.ts b/src/core/features/login/components/sites-modal/sites-modal.ts index ca3ed546d09..e938c4f7eda 100644 --- a/src/core/features/login/components/sites-modal/sites-modal.ts +++ b/src/core/features/login/components/sites-modal/sites-modal.ts @@ -50,7 +50,7 @@ export class CoreLoginSitesModalComponent implements OnInit { * @inheritdoc */ async ngOnInit(): Promise { - this.accountsList = await CoreLoginHelper.getAccountsList(this.currentSiteId); + this.accountsList = await CoreLoginHelper.getAccountsList(); this.loaded = true; } diff --git a/src/core/features/login/services/login-helper.ts b/src/core/features/login/services/login-helper.ts index d2b1abf649f..7d003b67a08 100644 --- a/src/core/features/login/services/login-helper.ts +++ b/src/core/features/login/services/login-helper.ts @@ -1301,10 +1301,9 @@ export class CoreLoginHelperProvider { /** * Get the accounts list classified per site. * - * @param currentSiteId If loggedin, current Site Id. * @returns Promise resolved with account list. */ - async getAccountsList(currentSiteId?: string): Promise { + async getAccountsList(): Promise { const sites = await CoreUtils.ignoreErrors(CoreSites.getSortedSites(), [] as CoreSiteBasicInfo[]); const accountsList: CoreAccountsList = { @@ -1312,14 +1311,11 @@ export class CoreLoginHelperProvider { otherSites: [], count: sites.length, }; - + const currentSiteId = CoreSites.getCurrentSiteId(); let siteUrl = ''; if (currentSiteId) { - const index = sites.findIndex((site) => site.id == currentSiteId); - - accountsList.currentSite = sites.splice(index, 1)[0]; - siteUrl = accountsList.currentSite.siteUrlWithoutProtocol; + siteUrl = sites.find((site) => site.id == currentSiteId)?.siteUrlWithoutProtocol ?? ''; } const otherSites: Record = {}; @@ -1328,7 +1324,9 @@ export class CoreLoginHelperProvider { await Promise.all(sites.map(async (site) => { site.badge = await CoreUtils.ignoreErrors(CorePushNotifications.getSiteCounter(site.id)) || 0; - if (site.siteUrlWithoutProtocol == siteUrl) { + if (site.id === currentSiteId) { + accountsList.currentSite = site; + } else if (site.siteUrlWithoutProtocol == siteUrl) { accountsList.sameSite.push(site); } else { if (!otherSites[site.siteUrlWithoutProtocol]) { diff --git a/src/core/features/settings/pages/synchronization/synchronization.ts b/src/core/features/settings/pages/synchronization/synchronization.ts index 51388517343..ad04dc3558c 100644 --- a/src/core/features/settings/pages/synchronization/synchronization.ts +++ b/src/core/features/settings/pages/synchronization/synchronization.ts @@ -104,10 +104,8 @@ export class CoreSettingsSynchronizationPage implements OnInit, OnDestroy { * @inheritdoc */ async ngOnInit(): Promise { - const currentSiteId = CoreSites.getCurrentSiteId(); - try { - this.accountsList = await CoreLoginHelper.getAccountsList(currentSiteId); + this.accountsList = await CoreLoginHelper.getAccountsList(); } catch { // Ignore errors. } diff --git a/src/core/features/sharedfiles/pages/choose-site/choose-site.ts b/src/core/features/sharedfiles/pages/choose-site/choose-site.ts index 115e190de5a..84407e8090f 100644 --- a/src/core/features/sharedfiles/pages/choose-site/choose-site.ts +++ b/src/core/features/sharedfiles/pages/choose-site/choose-site.ts @@ -18,7 +18,7 @@ import { CoreSharedFilesHelper } from '@features/sharedfiles/services/sharedfile import { FileEntry } from '@ionic-native/file/ngx'; import { CoreFile } from '@services/file'; import { CoreNavigator } from '@services/navigator'; -import { CoreSiteBasicInfo, CoreSites } from '@services/sites'; +import { CoreSiteBasicInfo } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; /** @@ -94,7 +94,7 @@ export class CoreSharedFilesChooseSitePage implements OnInit { * @returns Promise resolved when done. */ protected async loadSites(): Promise { - this.accountsList = await CoreLoginHelper.getAccountsList(CoreSites.getCurrentSiteId()); + this.accountsList = await CoreLoginHelper.getAccountsList(); } /** diff --git a/src/core/services/sites.ts b/src/core/services/sites.ts index 559069759eb..e9fc6888bcb 100644 --- a/src/core/services/sites.ts +++ b/src/core/services/sites.ts @@ -1300,7 +1300,19 @@ export class CoreSitesProvider { async getSites(ids?: string[]): Promise { const sites = await this.sitesTable.getMany(); + return this.siteDBRecordsToBasicInfo(sites, ids); + } + + /** + * Convert sites DB records to site basic info. + * + * @param sites DB records. + * @param ids IDs of sites to return, undefined to return them all. + * @returns Sites basic info. + */ + protected siteDBRecordsToBasicInfo(sites: SiteDBEntry[], ids?: string[]): CoreSiteBasicInfo[] { const formattedSites: CoreSiteBasicInfo[] = []; + sites.forEach((site) => { if (!ids || ids.indexOf(site.id) > -1) { const isDemoModeSite = CoreLoginHelper.isDemoModeSite(site.siteUrl); diff --git a/src/storybook/storybook.module.ts b/src/storybook/storybook.module.ts index f4bf199c691..b8aa614d730 100644 --- a/src/storybook/storybook.module.ts +++ b/src/storybook/storybook.module.ts @@ -28,6 +28,8 @@ import { CoreFilepoolProviderStub } from '@/storybook/stubs/services/filepool'; import { CoreFilepoolProvider } from '@services/filepool'; import { HttpClientStub } from '@/storybook/stubs/services/http'; import { HttpClient } from '@angular/common/http'; +import { CorePushNotificationsProvider } from '@features/pushnotifications/services/pushnotifications'; +import { CorePushNotificationsProviderStub } from './stubs/services/pushnotifications'; // For translate loader. AoT requires an exported function for factories. export class StaticTranslateLoader extends TranslateLoader { @@ -56,6 +58,7 @@ export class StaticTranslateLoader extends TranslateLoader { { provide: CoreSitesProvider, useClass: CoreSitesProviderStub }, { provide: CoreDbProvider, useClass: CoreDbProviderStub }, { provide: CoreFilepoolProvider, useClass: CoreFilepoolProviderStub }, + { provide: CorePushNotificationsProvider, useClass: CorePushNotificationsProviderStub }, { provide: HttpClient, useClass: HttpClientStub }, { provide: APP_INITIALIZER, diff --git a/src/storybook/stubs/services/pushnotifications.ts b/src/storybook/stubs/services/pushnotifications.ts new file mode 100644 index 00000000000..9d380c32cb6 --- /dev/null +++ b/src/storybook/stubs/services/pushnotifications.ts @@ -0,0 +1,32 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { CorePushNotificationsProvider } from '@features/pushnotifications/services/pushnotifications'; +import { makeSingleton } from '@singletons'; + +/** + * Sites provider stub. + */ +export class CorePushNotificationsProviderStub extends CorePushNotificationsProvider { + + /** + * @inheritdoc + */ + async getSiteCounter(): Promise { + return Math.round(Math.random() * 100); + } + +} + +export const CorePushNotificationsStub = makeSingleton(CorePushNotificationsProvider); diff --git a/src/storybook/stubs/services/sites.ts b/src/storybook/stubs/services/sites.ts index 25f32541a3a..8fa901a92f1 100644 --- a/src/storybook/stubs/services/sites.ts +++ b/src/storybook/stubs/services/sites.ts @@ -12,9 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -import school from '@/assets/storybook/sites/school.json'; +import { companyLisaSite } from '@/assets/storybook/sites/companylisa'; +import { schoolBarbaraSite } from '@/assets/storybook/sites/schoolbarbara'; +import { schoolJefferySite } from '@/assets/storybook/sites/schooljeffery'; import { CoreSiteFixture, CoreSiteStub } from '@/storybook/stubs/classes/site'; -import { CoreSitesProvider } from '@services/sites'; +import { CoreError } from '@classes/errors/error'; +import { CoreSite } from '@classes/site'; +import { SiteDBEntry } from '@services/database/sites'; +import { CoreSiteBasicInfo, CoreSitesProvider } from '@services/sites'; import { makeSingleton } from '@singletons'; /** @@ -22,17 +27,55 @@ import { makeSingleton } from '@singletons'; */ export class CoreSitesProviderStub extends CoreSitesProvider { + protected static readonly SITES_FIXTURES = [schoolBarbaraSite, schoolJefferySite, companyLisaSite]; + /** * @inheritdoc */ getRequiredCurrentSite!: () => CoreSiteStub; + /** + * @inheritdoc + */ + async getSites(ids?: string[]): Promise { + const sites = CoreSitesProviderStub.SITES_FIXTURES.map(site => ( { + id: site.id, + siteUrl: site.info.siteurl, + info: JSON.stringify(site.info), + token: '', + privateToken: '', + loggedOut: 0, + })); + + return this.siteDBRecordsToBasicInfo(sites, ids); + } + + /** + * @inheritdoc + */ + async getSite(siteId?: string): Promise { + if (!siteId) { + if (this.currentSite) { + return this.currentSite; + } + + throw new CoreError('No current site found.'); + } + + const siteFixture = CoreSitesProviderStub.SITES_FIXTURES.find(site => site.id === siteId); + if (!siteFixture) { + throw new CoreError('SiteId not found.'); + } + + return new CoreSiteStub(siteFixture); + } + /** * @inheritdoc */ stubCurrentSite(fixture?: CoreSiteFixture): CoreSiteStub { if (!this.currentSite) { - this.currentSite = new CoreSiteStub(fixture ?? school); + this.currentSite = new CoreSiteStub(fixture ?? schoolBarbaraSite); } return this.getRequiredCurrentSite();