diff --git a/.storybook/main.js b/.storybook/main.js deleted file mode 100644 index 6d3a8b83bdb..00000000000 --- a/.storybook/main.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = { - framework: '@storybook/angular', - addons: [ - '@storybook/addon-controls', - '@storybook/addon-viewport', - 'storybook-addon-designs', - 'storybook-addon-rtl-direction', - 'storybook-dark-mode', - ], - stories: ['../src/**/*.stories.ts'], -} diff --git a/.storybook/preview.js b/.storybook/preview.js deleted file mode 100644 index c3b70582263..00000000000 --- a/.storybook/preview.js +++ /dev/null @@ -1,11 +0,0 @@ -import '!style-loader!css-loader!sass-loader!../src/theme/theme.design-system.scss'; -import '!style-loader!css-loader!sass-loader!./styles.scss'; - -export const parameters = { - layout: 'centered', - darkMode: { - darkClass: 'dark', - classTarget: 'html', - stylePreview: true, - }, -}; diff --git a/.storybook/styles.scss b/.storybook/styles.scss deleted file mode 100644 index 8be75bade9c..00000000000 --- a/.storybook/styles.scss +++ /dev/null @@ -1,7 +0,0 @@ -storybook-dynamic-app-root { - color: var(--ion-text-color); -} - -.core-error-info { - max-width: 300px; -} diff --git a/.storybook/tsconfig.json b/.storybook/tsconfig.json deleted file mode 100644 index a8a27caf8e6..00000000000 --- a/.storybook/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "../tsconfig.json", - "include": [ - "../src/**/*" - ], - "exclude": [ - "../src/**/tests/**", - "../src/testing/**", - "../src/**/*.test.ts" - ] -} diff --git a/package.json b/package.json index 6de7066364c..2032c4f3fd4 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,6 @@ "dev:ios": "ionic cordova run ios", "prod:android": "npm run prod --prefix cordova-plugin-moodleapp && NODE_ENV=production ionic cordova run android --prod", "prod:ios": "NODE_ENV=production ionic cordova run ios --prod", - "storybook": "start-storybook -p 6006", "test": "NODE_ENV=testing gulp && jest --verbose", "test:ci": "NODE_ENV=testing gulp && jest -ci --runInBand --verbose", "test:watch": "NODE_ENV=testing gulp watch & jest --watch", diff --git a/src/assets/storybook/courses.json b/src/assets/storybook/courses.json deleted file mode 100644 index 81b9390e0c5..00000000000 --- a/src/assets/storybook/courses.json +++ /dev/null @@ -1 +0,0 @@ -[{"id":1,"courseimage":"https://picsum.photos/500/500","shortname":"Moodle and Mountaineering","summary":"This course will introduce you to the basics of Alpine Mountaineering, while at the same time highlighting some of the great features of Moodle."},{"id":2,"courseimage":"assets/storybook/geopattern.svg","shortname":"Digital Literacy","summary":"This course explores Digital Literacy and its importance for teachers and students. The course is optimised for the Moodle App. Please try it out!"},{"id":3,"shortname":"Class and Conflict in World Cinema","summary":"In this module we will analyse two very significant films - City of God and La Haine, both of which depict violent lives in poor conditions, the former in the favelas of Brazil and the latter in a Parisian banlieue. We will look at how conflict and class are portrayed, focusing particularly on the use of mise en scène."}] diff --git a/src/assets/storybook/geopattern.svg b/src/assets/storybook/geopattern.svg deleted file mode 100644 index 60e7e8a9d98..00000000000 --- a/src/assets/storybook/geopattern.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/src/assets/storybook/sites/companylisa.ts b/src/assets/storybook/sites/companylisa.ts deleted file mode 100644 index 3689523d7d7..00000000000 --- a/src/assets/storybook/sites/companylisa.ts +++ /dev/null @@ -1,32 +0,0 @@ -// (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/schoolbarbara.ts b/src/assets/storybook/sites/schoolbarbara.ts deleted file mode 100644 index c4fa3eda3a7..00000000000 --- a/src/assets/storybook/sites/schoolbarbara.ts +++ /dev/null @@ -1,32 +0,0 @@ -// (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 deleted file mode 100644 index c7823f2b069..00000000000 --- a/src/assets/storybook/sites/schooljeffery.ts +++ /dev/null @@ -1,32 +0,0 @@ -// (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/classes/errors/ajaxwserror.ts b/src/core/classes/errors/ajaxwserror.ts index 73dcfa37d0e..53e8b10e6e4 100644 --- a/src/core/classes/errors/ajaxwserror.ts +++ b/src/core/classes/errors/ajaxwserror.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { CoreSiteError } from '@classes/errors/siteerror'; +import { CoreSiteError, CoreSiteErrorOptions } from '@classes/errors/siteerror'; /** * Error returned by WS. @@ -29,10 +29,7 @@ export class CoreAjaxWSError extends CoreSiteError { // eslint-disable-next-line @typescript-eslint/no-explicit-any constructor(error: any, available?: number) { - super({ - message: error.message || error.error, - errorcode: error.errorcode, - }); + super(getErrorOptions(error)); this.exception = error.exception; this.warningcode = error.warningcode; @@ -40,15 +37,37 @@ export class CoreAjaxWSError extends CoreSiteError { this.moreinfourl = error.moreinfourl; this.debuginfo = error.debuginfo; this.backtrace = error.backtrace; + this.available = available ?? ( + this.debug + ? (this.debug.code == 'invalidrecord' ? -1 : 1) + : 0 + ); + } + +} + +/** + * Get error options from unknown error instance. + * + * @param error The error. + * @returns Options + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function getErrorOptions(error: any): CoreSiteErrorOptions { + const options: CoreSiteErrorOptions = { + message: error.message || error.error, + }; + + if ('debug' in error) { + options.debug = error.debug; + } - this.available = available; - if (this.available === undefined) { - if (this.errorcode) { - this.available = this.errorcode == 'invalidrecord' ? -1 : 1; - } else { - this.available = 0; - } - } + if ('errorcode' in error) { + options.debug = { + code: error.errorcode, + details: error.message || error.error, + }; } + return options; } diff --git a/src/core/classes/errors/siteerror.ts b/src/core/classes/errors/siteerror.ts index bb31c41b642..3eed531e609 100644 --- a/src/core/classes/errors/siteerror.ts +++ b/src/core/classes/errors/siteerror.ts @@ -20,24 +20,39 @@ import { CoreUserSupportConfig } from '@features/user/classes/support/support-co */ export class CoreSiteError extends CoreError { - errorcode?: string; - errorDetails?: string; + debug?: CoreSiteErrorDebug; supportConfig?: CoreUserSupportConfig; constructor(options: CoreSiteErrorOptions) { super(options.message); - this.errorcode = options.errorcode; - this.errorDetails = options.errorDetails; + this.debug = options.debug; this.supportConfig = options.supportConfig; } + /** + * @deprecated This getter should not be called directly, but it's defined for backwards compatibility with many + * parts of the code that type errors as any and use it. We cannot rename those because the errors could also be + * CoreWSError instances which do have an "errorcode" property. + * + * @returns error code. + */ + get errorcode(): string | undefined { + return this.debug?.code; + } + } +export type CoreSiteErrorDebug = { + code: string; // Technical error code useful for technical assistance. + details: string; // Technical error details useful for technical assistance. +}; + export type CoreSiteErrorOptions = { message: string; - errorcode?: string; // Technical error code useful for technical assistance. - errorDetails?: string; // Technical error details useful for technical assistance. + + // Debugging information. + debug?: CoreSiteErrorDebug; // Configuration to use to contact site support. If this attribute is present, it means // that the error warrants contacting support. diff --git a/src/core/classes/sites/authenticated-site.ts b/src/core/classes/sites/authenticated-site.ts index 7769d479f6c..a71f28237f3 100644 --- a/src/core/classes/sites/authenticated-site.ts +++ b/src/core/classes/sites/authenticated-site.ts @@ -943,8 +943,10 @@ export class CoreAuthenticatedSite extends CoreUnauthenticatedSite { throw new CoreSiteError({ supportConfig: new CoreUserAuthenticatedSupportConfig(this), message: Translate.instant('core.siteunavailablehelp', { site: this.siteUrl }), - errorcode: 'invalidresponse', - errorDetails: Translate.instant('core.errorinvalidresponse', { method: 'tool_mobile_call_external_functions' }), + debug: { + code: 'invalidresponse', + details: Translate.instant('core.errorinvalidresponse', { method: 'tool_mobile_call_external_functions' }), + }, }); } diff --git a/src/core/components/error-info/core-error-info.html b/src/core/components/error-info/core-error-info.html deleted file mode 100644 index 2808ecb6819..00000000000 --- a/src/core/components/error-info/core-error-info.html +++ /dev/null @@ -1,6 +0,0 @@ - diff --git a/src/core/components/error-info/error-info.scss b/src/core/components/error-info/error-info.scss deleted file mode 100644 index a0723f21f17..00000000000 --- a/src/core/components/error-info/error-info.scss +++ /dev/null @@ -1,75 +0,0 @@ -.core-error-info { - background: var(--gray-200); - border-radius: var(--radius-xs); - font-size: var(--body-font-size-sm); - color: var(--gray-900); - - p:first-child { - margin-top: 0; - } - - p:last-child { - margin-bottom: 0; - } - - .core-error-info--code { - padding: var(--spacing-2) var(--spacing-2) 0 var(--spacing-2); - font-size: var(--body-font-size-md); - } - - .core-error-info--details p { - padding: var(--spacing-2) var(--spacing-2) 0 var(--spacing-2); - color: var(--gray-500); - } - - .core-error-info--checkbox { - display: none; - - & + .core-error-info--details, - & + .core-error-info--code + .core-error-info--details { - max-height: 0; - overflow: hidden; - transition: max-height 600ms ease-in-out; - - & + .core-error-info--toggle { - display: flex; - padding: var(--spacing-2); - min-height: var(--a11y-min-target-size); - align-items: center; - - span { - width: 100%; - display: flex; - justify-content: space-between; - } - - svg { - fill: currentColor; - width: 11px; - } - - .core-error-info--hide-content { - display: none; - } - - } - - } - - &:checked + .core-error-info--details, - &:checked + .core-error-info--code + .core-error-info--details { - max-height: 110px; - - & + .core-error-info--toggle .core-error-info--hide-content { - display: flex; - } - - & + .core-error-info--toggle .core-error-info--show-content { - display: none; - } - - } - - } - -} diff --git a/src/core/components/error-info/error-info.ts b/src/core/components/error-info/error-info.ts deleted file mode 100644 index 4104e7f3e40..00000000000 --- a/src/core/components/error-info/error-info.ts +++ /dev/null @@ -1,92 +0,0 @@ -// (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, ElementRef, Input, OnChanges, OnInit } from '@angular/core'; -import { Translate } from '@singletons'; -import { CoreForms } from '@singletons/form'; - -/** - * Component to show error details. - * - * Given that this component has to be injected dynamically in some situations (for example, error alerts), - * it can be rendered using the static render() method to get the raw HTML. - */ -@Component({ - selector: 'core-error-info', - templateUrl: 'core-error-info.html', - styleUrls: ['error-info.scss'], -}) -export class CoreErrorInfoComponent implements OnInit, OnChanges { - - /** - * Render an instance of the component into an HTML string. - * - * @param errorDetails Error details. - * @param errorCode Error code. - * @returns Component HTML. - */ - static render(errorDetails: string, errorCode?: string): string { - const toggleId = CoreForms.uniqueId('error-info-toggle'); - const errorCodeLabel = Translate.instant('core.errorcode', { errorCode }); - const hideDetailsLabel = Translate.instant('core.errordetailshide'); - const showDetailsLabel = Translate.instant('core.errordetailsshow'); - - return ` -
- - ${errorCode ? `
${errorCodeLabel}
` : ''} -
-

${errorDetails}

-
- -
- `; - } - - @Input() errorDetails!: string; - @Input() errorCode?: string; - - constructor(private element: ElementRef) {} - - /** - * @inheritdoc - */ - ngOnInit(): void { - this.render(); - } - - /** - * @inheritdoc - */ - ngOnChanges(): void { - this.render(); - } - - /** - * Render component html in the element created by Angular. - */ - private render(): void { - this.element.nativeElement.innerHTML = CoreErrorInfoComponent.render(this.errorDetails, this.errorCode); - } - -} diff --git a/src/core/components/stories/components/components.module.ts b/src/core/components/stories/components/components.module.ts deleted file mode 100644 index d0648467ee1..00000000000 --- a/src/core/components/stories/components/components.module.ts +++ /dev/null @@ -1,43 +0,0 @@ -// (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 { NgModule } from '@angular/core'; -import { CoreEmptyBoxPageComponent } from './empty-box-page/empty-box-page'; -import { CoreEmptyBoxWrapperComponent } from './empty-box-wrapper/empty-box-wrapper'; -import { StorybookModule } from '@/storybook/storybook.module'; -import { CoreSearchComponentsModule } from '@features/search/components/components.module'; -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: [ - CoreCourseImageCardsPageComponent, - CoreCourseImageListPageComponent, - CoreEmptyBoxPageComponent, - CoreEmptyBoxWrapperComponent, - CoreSitesListWrapperComponent, - ], - imports: [ - CommonModule, - StorybookModule, - CoreDirectivesModule, - CoreComponentsModule, - CoreSearchComponentsModule, - ], -}) -export class CoreComponentsStorybookModule {} diff --git a/src/core/components/stories/components/course-image-cards-page/course-image-cards-page.html b/src/core/components/stories/components/course-image-cards-page/course-image-cards-page.html deleted file mode 100644 index f1ad00bca6e..00000000000 --- a/src/core/components/stories/components/course-image-cards-page/course-image-cards-page.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - -

Course Cards

-
-
-
- - -
- -
- - {{ course.shortname }} - - - {{ course.summary }} - -
-
-
diff --git a/src/core/components/stories/components/course-image-cards-page/course-image-cards-page.scss b/src/core/components/stories/components/course-image-cards-page/course-image-cards-page.scss deleted file mode 100644 index 23f07c6fdd8..00000000000 --- a/src/core/components/stories/components/course-image-cards-page/course-image-cards-page.scss +++ /dev/null @@ -1,16 +0,0 @@ -:host { - - ion-card { - max-width: 350px; - margin-right: auto; - margin-left: auto; - } - - .course-image-wrapper { - width: 100%; - height: 0; - padding-top: 40%; - position: relative; - } - -} diff --git a/src/core/components/stories/components/course-image-cards-page/course-image-cards-page.ts b/src/core/components/stories/components/course-image-cards-page/course-image-cards-page.ts deleted file mode 100644 index f2b6b032314..00000000000 --- a/src/core/components/stories/components/course-image-cards-page/course-image-cards-page.ts +++ /dev/null @@ -1,28 +0,0 @@ -// (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 } from '@angular/core'; -import { CoreCourseListItem } from '@features/courses/services/courses'; -import courses from '@/assets/storybook/courses.json'; - -@Component({ - selector: 'core-course-image-cards-page', - templateUrl: 'course-image-cards-page.html', - styleUrls: ['./course-image-cards-page.scss'], -}) -export class CoreCourseImageCardsPageComponent { - - courses: Partial[] = courses; - -} diff --git a/src/core/components/stories/components/course-image-list-page/course-image-list-page.html b/src/core/components/stories/components/course-image-list-page/course-image-list-page.html deleted file mode 100644 index f9638bea705..00000000000 --- a/src/core/components/stories/components/course-image-list-page/course-image-list-page.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - -

Courses List

-
-
-
- - - - - - {{ course.shortname }} - - - - -
diff --git a/src/core/components/stories/components/course-image-list-page/course-image-list-page.ts b/src/core/components/stories/components/course-image-list-page/course-image-list-page.ts deleted file mode 100644 index cf66b61f0bb..00000000000 --- a/src/core/components/stories/components/course-image-list-page/course-image-list-page.ts +++ /dev/null @@ -1,27 +0,0 @@ -// (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 } from '@angular/core'; -import { CoreCourseListItem } from '@features/courses/services/courses'; -import courses from '@/assets/storybook/courses.json'; - -@Component({ - selector: 'core-course-image-list-page', - templateUrl: 'course-image-list-page.html', -}) -export class CoreCourseImageListPageComponent { - - courses: Partial[] = courses; - -} diff --git a/src/core/components/stories/components/empty-box-page/empty-box-page.html b/src/core/components/stories/components/empty-box-page/empty-box-page.html deleted file mode 100644 index 0bd2c567a17..00000000000 --- a/src/core/components/stories/components/empty-box-page/empty-box-page.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - -

Search

-
-
-
- -
- - -
-
-
diff --git a/src/core/components/stories/components/empty-box-page/empty-box-page.ts b/src/core/components/stories/components/empty-box-page/empty-box-page.ts deleted file mode 100644 index a290850b82f..00000000000 --- a/src/core/components/stories/components/empty-box-page/empty-box-page.ts +++ /dev/null @@ -1,27 +0,0 @@ -// (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 } from '@angular/core'; - -@Component({ - selector: 'core-empty-box-page', - templateUrl: 'empty-box-page.html', -}) -export class CoreEmptyBoxPageComponent { - - @Input() icon!: string; - @Input() content!: string; - @Input() dimmed!: boolean; - -} diff --git a/src/core/components/stories/components/empty-box-wrapper/empty-box-wrapper.html b/src/core/components/stories/components/empty-box-wrapper/empty-box-wrapper.html deleted file mode 100644 index 10dc9fa5fe3..00000000000 --- a/src/core/components/stories/components/empty-box-wrapper/empty-box-wrapper.html +++ /dev/null @@ -1,3 +0,0 @@ - -
-
diff --git a/src/core/components/stories/components/empty-box-wrapper/empty-box-wrapper.ts b/src/core/components/stories/components/empty-box-wrapper/empty-box-wrapper.ts deleted file mode 100644 index 6b99f2a4777..00000000000 --- a/src/core/components/stories/components/empty-box-wrapper/empty-box-wrapper.ts +++ /dev/null @@ -1,38 +0,0 @@ -// (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 } from '@angular/core'; -import { DomSanitizer } from '@singletons'; -import { SafeHtml } from '@angular/platform-browser'; - -@Component({ - selector: 'core-empty-box-wrapper', - templateUrl: 'empty-box-wrapper.html', -}) -export class CoreEmptyBoxWrapperComponent implements OnChanges { - - @Input() icon!: string; - @Input() content!: string; - @Input() dimmed!: boolean; - - html?: SafeHtml; - - /** - * @inheritdoc - */ - ngOnChanges(): void { - this.html = DomSanitizer.bypassSecurityTrustHtml(this.content); - } - -} 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 deleted file mode 100644 index a7b72397b9e..00000000000 --- a/src/core/components/stories/components/sites-list-wrapper/sites-list-wrapper.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - -

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 deleted file mode 100644 index 06dc02b22bb..00000000000 --- a/src/core/components/stories/components/sites-list-wrapper/sites-list-wrapper.ts +++ /dev/null @@ -1,60 +0,0 @@ -// (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/course-image.stories.ts b/src/core/components/stories/course-image.stories.ts deleted file mode 100644 index cd919a6fe9c..00000000000 --- a/src/core/components/stories/course-image.stories.ts +++ /dev/null @@ -1,104 +0,0 @@ -// (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 { CoreCourseImageComponent } from '@components/course-image/course-image'; -import { APP_INITIALIZER } from '@angular/core'; -import { CoreSitesStub } from '@/storybook/stubs/services/sites'; -import { CoreCourseImageListPageComponent } from '@components/stories/components/course-image-list-page/course-image-list-page'; -import { CoreComponentsStorybookModule } from '@components/stories/components/components.module'; -import { CoreCourseImageCardsPageComponent } from '@components/stories/components/course-image-cards-page/course-image-cards-page'; - -interface Args { - type: 'image' | 'geopattern' | 'color'; - fill: boolean; -} - -export default { - title: 'Core/Course Image', - component: CoreCourseImageComponent, - decorators: [ - moduleMetadata({ - imports: [CoreComponentsStorybookModule], - providers: [ - { - provide: APP_INITIALIZER, - multi: true, - useValue: () => { - const site = CoreSitesStub.getRequiredCurrentSite(); - - site.stubWSResponse('tool_mobile_get_config', { - settings: [ - { name: 'core_admin_coursecolor1', value: '#F9B000' }, - { name: 'core_admin_coursecolor2', value: '#EF4B00' }, - { name: 'core_admin_coursecolor3', value: '#4338FB' }, - { name: 'core_admin_coursecolor4', value: '#E142FB' }, - { name: 'core_admin_coursecolor5', value: '#FF0064' }, - { name: 'core_admin_coursecolor6', value: '#FF0F18' }, - { name: 'core_admin_coursecolor7', value: '#039B06' }, - { name: 'core_admin_coursecolor8', value: '#039B88' }, - { name: 'core_admin_coursecolor9', value: '#EF009B' }, - { name: 'core_admin_coursecolor10', value: '#020B6E' }, - ], - warnings: [], - }); - }, - }, - ], - }), - ], - argTypes: { - type: { - control: { - type: 'select', - options: ['image', 'geopattern', 'color'], - }, - }, - }, - args: { - type: 'image', - fill: false, - }, -}; - -const Template = story(({ type, ...args }) => { - const getImageSource = () => { - switch (type) { - case 'image': - return 'https://picsum.photos/500/500'; - case 'geopattern': - return 'assets/storybook/geopattern.svg'; - case 'color': - return undefined; - } - }; - - return { - component: CoreCourseImageComponent, - props: { - ...args, - course: { - id: 1, - courseimage: getImageSource(), - }, - }, - }; -}); - -export const Primary = story(Template); -export const ListPage = story(() => ({ component: CoreCourseImageListPageComponent })); -export const CardsPage = story(() => ({ component: CoreCourseImageCardsPageComponent })); diff --git a/src/core/components/stories/empty-box.stories.ts b/src/core/components/stories/empty-box.stories.ts deleted file mode 100644 index 8b2922fa635..00000000000 --- a/src/core/components/stories/empty-box.stories.ts +++ /dev/null @@ -1,79 +0,0 @@ -// (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 { marked } from 'marked'; - -import { story } from '@/storybook/utils/helpers'; - -import { CoreEmptyBoxComponent } from '@components/empty-box/empty-box'; -import { CoreEmptyBoxWrapperComponent } from './components/empty-box-wrapper/empty-box-wrapper'; -import { CoreEmptyBoxPageComponent } from './components/empty-box-page/empty-box-page'; -import { CoreComponentsStorybookModule } from './components/components.module'; - -interface Args { - icon: string; - content: string; - dimmed: boolean; -} - -export default > { - title: 'Core/Empty Box', - component: CoreEmptyBoxComponent, - decorators: [ - moduleMetadata({ imports: [CoreComponentsStorybookModule] }), - ], - argTypes: { - icon: { - control: { - type: 'select', - options: ['fas-magnifying-glass', 'fas-user', 'fas-check'], - }, - }, - }, - args: { - icon: 'fas-user', - content: 'No users', - dimmed: false, - }, -}; - -const WrapperTemplate = story((args) => ({ - component: CoreEmptyBoxWrapperComponent, - props: { - ...args, - content: marked(args.content), - }, -})); - -const PageTemplate = story((args) => ({ - component: CoreEmptyBoxPageComponent, - props: { - ...args, - content: marked(args.content), - }, -})); - -export const Primary = story(WrapperTemplate); - -export const Example = story(PageTemplate, { - icon: 'fas-magnifying-glass', - content: '**No results for "Test Search"**\n\nCheck for typos or try using different keywords', -}); - -export const DimmedExample = story(PageTemplate, { - icon: 'fas-magnifying-glass', - content: 'What are you searching for?', - dimmed: true, -}); diff --git a/src/core/components/stories/error-info.stories.ts b/src/core/components/stories/error-info.stories.ts deleted file mode 100644 index 9c67a6d1332..00000000000 --- a/src/core/components/stories/error-info.stories.ts +++ /dev/null @@ -1,50 +0,0 @@ -// (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, Story } from '@storybook/angular'; - -import { story } from '@/storybook/utils/helpers'; -import { StorybookModule } from '@/storybook/storybook.module'; - -import { CoreErrorInfoComponent } from '@components/error-info/error-info'; - -interface Args { - errorCode: string; - errorDetails: string; -} - -export default > { - title: 'Core/Error Info', - component: CoreErrorInfoComponent, - decorators: [ - moduleMetadata({ - declarations: [CoreErrorInfoComponent], - imports: [StorybookModule], - }), - ], - args: { - errorCode: '', - errorDetails: - 'AJAX endpoint not found. ' + - 'This can happen if the Moodle site is too old or it blocks access to this endpoint. ' + - 'The Moodle app only supports Moodle systems 3.5 onwards.', - }, -}; - -const Template: Story = (args) => ({ - component: CoreErrorInfoComponent, - props: args, -}); - -export const Primary = story(Template); diff --git a/src/core/components/stories/sites-list.stories.ts b/src/core/components/stories/sites-list.stories.ts deleted file mode 100644 index 0fa75f7e5f5..00000000000 --- a/src/core/components/stories/sites-list.stories.ts +++ /dev/null @@ -1,78 +0,0 @@ -// (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/Sites 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/components/stories/user-avatar.stories.ts b/src/core/components/stories/user-avatar.stories.ts deleted file mode 100644 index 64303d414de..00000000000 --- a/src/core/components/stories/user-avatar.stories.ts +++ /dev/null @@ -1,37 +0,0 @@ -// (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, Story } from '@storybook/angular'; - -import { story } from '@/storybook/utils/helpers'; -import { StorybookModule } from '@/storybook/storybook.module'; - -import { CoreUserAvatarComponent } from '@components/user-avatar/user-avatar'; - -export default { - title: 'Core/User Avatar', - component: CoreUserAvatarComponent, - decorators: [ - moduleMetadata({ - declarations: [CoreUserAvatarComponent], - imports: [StorybookModule], - }), - ], -}; - -const Template: Story = () => ({ - component: CoreUserAvatarComponent, -}); - -export const Primary = story(Template); diff --git a/src/core/features/login/pages/site/site.ts b/src/core/features/login/pages/site/site.ts index cbca54a66f9..9cabf031a04 100644 --- a/src/core/features/login/pages/site/site.ts +++ b/src/core/features/login/pages/site/site.ts @@ -38,9 +38,9 @@ import { CoreCustomURLSchemes, CoreCustomURLSchemesHandleError } from '@services import { CoreTextUtils } from '@services/utils/text'; import { CoreForms } from '@singletons/form'; import { AlertButton } from '@ionic/core'; -import { CoreSiteError } from '@classes/errors/siteerror'; +import { CoreSiteError, CoreSiteErrorDebug } from '@classes/errors/siteerror'; import { CoreUserSupport } from '@features/user/services/support'; -import { CoreErrorInfoComponent } from '@components/error-info/error-info'; +import { CoreErrorAccordion } from '@services/error-accordion'; import { CoreUserSupportConfig } from '@features/user/classes/support/support-config'; import { CoreUserGuestSupportConfig } from '@features/user/classes/support/guest-support-config'; import { CoreLoginError } from '@classes/errors/loginerror'; @@ -408,22 +408,20 @@ export class CoreLoginSitePage implements OnInit { let siteExists = false; let supportConfig: CoreUserSupportConfig | undefined = undefined; let errorTitle: string | undefined; - let errorDetails: string | undefined; - let errorCode: string | undefined; + let debug: CoreSiteErrorDebug | undefined; if (error instanceof CoreSiteError) { supportConfig = error.supportConfig; - errorDetails = error.errorDetails; - errorCode = error.errorcode; siteExists = supportConfig instanceof CoreUserGuestSupportConfig; + debug = error.debug; } if (error instanceof CoreLoginError) { errorTitle = error.title; } - if (errorDetails) { - errorMessage = `

${errorMessage}

`; + if (debug) { + errorMessage = `

${errorMessage}

`; } const alertSupportConfig = supportConfig; @@ -438,7 +436,7 @@ export class CoreLoginSitePage implements OnInit { handler: () => CoreUserSupport.contact({ supportConfig: alertSupportConfig, subject: Translate.instant('core.cannotconnect'), - message: `Error: ${errorCode}\n\n${errorDetails}`, + message: `Error: ${debug?.code}\n\n${debug?.details}`, }), } : ( @@ -458,11 +456,11 @@ export class CoreLoginSitePage implements OnInit { buttons: buttons as AlertButton[], }); - if (errorDetails) { - // Avoid sanitizing JS. - const containerElement = alertElement.querySelector('.core-error-info-container'); + if (debug) { + const containerElement = alertElement.querySelector('.core-error-accordion-container'); + if (containerElement) { - containerElement.innerHTML = CoreErrorInfoComponent.render(errorDetails, errorCode); + await CoreErrorAccordion.render(containerElement, debug.code, debug.details); } } } diff --git a/src/core/features/login/services/login-helper.ts b/src/core/features/login/services/login-helper.ts index 98fb1c72cee..44ca38f7a25 100644 --- a/src/core/features/login/services/login-helper.ts +++ b/src/core/features/login/services/login-helper.ts @@ -39,7 +39,6 @@ import { CorePushNotifications } from '@features/pushnotifications/services/push import { CorePath } from '@singletons/path'; import { CorePromisedValue } from '@classes/promised-value'; import { SafeHtml } from '@angular/platform-browser'; -import { CoreLoginError } from '@classes/errors/loginerror'; import { CoreSettingsHelper } from '@features/settings/services/settings-helper'; import { CoreSiteIdentityProvider, @@ -916,8 +915,8 @@ export class CoreLoginHelperProvider { /** * Show a modal warning that the credentials introduced were not correct. */ - protected showInvalidLoginModal(error: CoreLoginError): void { - CoreDomUtils.showErrorModal(error.errorDetails ?? error.message); + protected showInvalidLoginModal(error: CoreWSError): void { + CoreDomUtils.showErrorModal(error.message); } /** diff --git a/src/core/features/login/tests/credentials.test.ts b/src/core/features/login/tests/credentials.test.ts index e4ca21f8188..3e8d83fbdd7 100644 --- a/src/core/features/login/tests/credentials.test.ts +++ b/src/core/features/login/tests/credentials.test.ts @@ -124,7 +124,10 @@ describe('Credentials page', () => { getUserToken: () => { throw new CoreLoginError({ message: '', - errorcode: 'invalidlogin', + debug: { + code: 'invalidlogin', + details: 'Invalid login', + }, }); }, checkSite: async () => (siteCheck), diff --git a/src/core/features/search/stories/components/components.module.ts b/src/core/features/search/stories/components/components.module.ts deleted file mode 100644 index 1e59da78488..00000000000 --- a/src/core/features/search/stories/components/components.module.ts +++ /dev/null @@ -1,37 +0,0 @@ -// (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 { NgModule } from '@angular/core'; -import { StorybookModule } from '@/storybook/storybook.module'; -import { CoreSearchComponentsModule } from '@features/search/components/components.module'; -import { CoreComponentsModule } from '@components/components.module'; -import { CommonModule } from '@angular/common'; -import { - CoreSearchGlobalSearchResultsPageComponent, -} from '@features/search/stories/components/global-search-results-page/global-search-results-page'; -import { CoreSharedModule } from '@/core/shared.module'; - -@NgModule({ - declarations: [ - CoreSearchGlobalSearchResultsPageComponent, - ], - imports: [ - CoreSharedModule, - CommonModule, - StorybookModule, - CoreComponentsModule, - CoreSearchComponentsModule, - ], -}) -export class CoreSearchComponentsStorybookModule {} diff --git a/src/core/features/search/stories/components/global-search-results-page/global-search-results-page.html b/src/core/features/search/stories/components/global-search-results-page/global-search-results-page.html deleted file mode 100644 index eeb81da2c4e..00000000000 --- a/src/core/features/search/stories/components/global-search-results-page/global-search-results-page.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - -

Search Results

-
-
-
- -
- - - - -
-
-
diff --git a/src/core/features/search/stories/components/global-search-results-page/global-search-results-page.ts b/src/core/features/search/stories/components/global-search-results-page/global-search-results-page.ts deleted file mode 100644 index 6154c5d22d4..00000000000 --- a/src/core/features/search/stories/components/global-search-results-page/global-search-results-page.ts +++ /dev/null @@ -1,141 +0,0 @@ -// (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 } from '@angular/core'; -import { CoreCourseListItem } from '@features/courses/services/courses'; -import { CoreUserWithAvatar } from '@components/user-avatar/user-avatar'; -import { CoreSearchGlobalSearchResult } from '@features/search/services/global-search'; -import courses from '@/assets/storybook/courses.json'; - -@Component({ - selector: 'core-search-global-search-results-page', - templateUrl: 'global-search-results-page.html', -}) -export class CoreSearchGlobalSearchResultsPageComponent { - - results: CoreSearchGlobalSearchResult[] = [ - { - id: 1, - url: '', - title: 'Activity forum test', - content: 'this is a content test for a forum to see in the search result.', - context: { - courseName: 'Course 102', - userName: 'Stephania Krovalenko', - }, - module: { - name: 'forum', - iconurl: 'assets/img/mod/forum.svg', - area: 'activity', - }, - }, - { - id: 2, - url: '', - title: 'Activity assignment test', - content: 'this is a content test for a forum to see in the search result.', - context: { - courseName: 'Course 102', - }, - module: { - name: 'assign', - iconurl: 'assets/img/mod/assign.svg', - area: '', - }, - }, - { - id: 3, - url: '', - title: 'Course 101', - course: courses[0] as CoreCourseListItem, - }, - { - id: 4, - url: '', - title: 'John the Tester', - user: { - fullname: 'John Doe', - profileimageurl: 'https://placekitten.com/300/300', - } as CoreUserWithAvatar, - }, - { - id: 5, - url: '', - title: 'Search result title', - content: 'this is a content test for a forum to see in the search result.', - context: { - userName: 'Stephania Krovalenko', - }, - module: { - name: 'forum', - iconurl: 'assets/img/mod/forum.svg', - area: 'post', - }, - }, - { - id: 6, - url: '', - title: 'Side block', - context: { - courseName: 'Moodle Site', - }, - component: { - name: 'block_html', - iconurl: 'https://master.mm.moodledemo.net/theme/image.php?theme=boost&component=core&image=e%2Fanchor', - }, - }, - { - id: 7, - url: '', - title: 'Course section', - context: { - courseName: 'Course 101', - }, - component: { - name: 'core_course', - iconurl: 'https://master.mm.moodledemo.net/theme/image.php?theme=boost&component=core&image=i%2Fsection', - }, - }, - { - id: 8, - url: '', - title: 'This item has long text everywhere, so make sure that it looks good anyways. ' + - 'Even if the screen you\'re using is also big, this should still be a problem because this text is *really* long.', - content: 'You would normally see lorem ipsum here, but we decided to just write some gibberish here to make it more ' + - 'real. We all know that lorem ipsum is fabricated text, and even though it serves its purpose, it isn\'t as ' + - 'engaging as some real, hand-crafted text (not sure why this should be engaging, anyways).', - context: { - courseName: 'And it\'s not just the title, either. Other things like the Course title also take more than ' + - 'you would expect in a normal site (or even not so normal).', - userName: 'To top it off, it has a user name as well! What is this madness? Well, at some point you just have to ' + - 'get creative. Honestly, I\'m surprised if you\'re even reading this. Kudos to you for being thorough.', - }, - module: { - name: 'book', - iconurl: 'assets/img/mod/book.svg', - area: '', - }, - }, - ]; - - /** - * Result clicked. - * - * @param title Result title. - */ - resultClicked(title: string): void { - alert(`clicked on ${title}`); - } - -} diff --git a/src/core/features/search/stories/global-search-result.stories.ts b/src/core/features/search/stories/global-search-result.stories.ts deleted file mode 100644 index 8bc43c2bdf7..00000000000 --- a/src/core/features/search/stories/global-search-result.stories.ts +++ /dev/null @@ -1,136 +0,0 @@ -// (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 { CoreSearchGlobalSearchResultComponent } from '@features/search/components/global-search-result/global-search-result'; -import { CoreSearchComponentsStorybookModule } from '@features/search/stories/components/components.module'; -import { - CoreSearchGlobalSearchResultsPageComponent, -} from '@features/search/stories/components/global-search-results-page/global-search-results-page'; -import { APP_INITIALIZER } from '@angular/core'; -import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate'; -import { AddonModForumModuleHandler } from '@addons/mod/forum/services/handlers/module'; -import { AddonModAssignModuleHandler } from '@addons/mod/assign/services/handlers/module'; -import { CoreSearchGlobalSearchResult } from '@features/search/services/global-search'; -import { CoreUserWithAvatar } from '@components/user-avatar/user-avatar'; -import { CoreCourseListItem } from '@features/courses/services/courses'; -import courses from '@/assets/storybook/courses.json'; - -interface Args { - title: string; - content: string; - image: 'course' | 'user' | 'none'; - module: 'forum-activity' | 'forum-post' | 'assign' | 'none'; - courseContext: boolean; - userContext: boolean; - showCourse: boolean; -} - -export default > { - title: 'Core/Search/Global Search Result', - component: CoreSearchGlobalSearchResultComponent, - decorators: [ - moduleMetadata({ - imports: [CoreSearchComponentsStorybookModule], - providers: [ - { - provide: APP_INITIALIZER, - multi: true, - useValue() { - CoreCourseModuleDelegate.registerHandler(AddonModForumModuleHandler.instance); - CoreCourseModuleDelegate.registerHandler(AddonModAssignModuleHandler.instance); - CoreCourseModuleDelegate.updateHandlers(); - }, - }, - ], - }), - ], - argTypes: { - image: { - control: { - type: 'select', - options: ['course', 'user', 'none'], - }, - }, - module: { - control: { - type: 'select', - options: ['forum-activity', 'forum-post', 'assign', 'none'], - }, - }, - }, - args: { - title: 'Result #1', - content: 'This item seems really interesting, maybe you should click through', - image: 'none', - module: 'none', - courseContext: false, - userContext: false, - showCourse: true, - }, - parameters: { - design: { - type: 'figma', - url: 'https://www.figma.com/file/h3E7pkfgyImJPaYmTfnwuF/Global-Search?node-id=118%3A4610', - }, - }, -}; - -const Template = story(({ image, courseContext, userContext, module, showCourse, ...args }) => { - const result: CoreSearchGlobalSearchResult = { - ...args, - id: 1, - url: '', - }; - - if (courseContext || userContext) { - result.context = { - courseName: courseContext ? 'Course 101' : undefined, - userName: userContext ? 'John Doe' : undefined, - }; - } - - if (module !== 'none') { - const name = module.startsWith('forum') ? 'forum' : module; - - result.module = { - name, - iconurl: `assets/img/mod/${name}.svg`, - area: module.startsWith('forum') ? module.substring(6) : '', - }; - } - - switch (image) { - case 'course': - result.course = courses[0] as CoreCourseListItem; - break; - case 'user': - result.user = { - fullname: 'John Doe', - profileimageurl: 'https://placekitten.com/300/300', - } as CoreUserWithAvatar; - break; - } - - return { - component: CoreSearchGlobalSearchResultComponent, - props: { result, showCourse }, - }; -}); - -export const Primary = story(Template); -export const ResultsPage = story(() => ({ component: CoreSearchGlobalSearchResultsPageComponent })); diff --git a/src/core/services/error-accordion.ts b/src/core/services/error-accordion.ts new file mode 100644 index 00000000000..33c1377800b --- /dev/null +++ b/src/core/services/error-accordion.ts @@ -0,0 +1,123 @@ +// (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 { Injectable } from '@angular/core'; +import { Translate, makeSingleton } from '@singletons'; +import { CoreUtils } from '@services/utils/utils'; +import { CoreDom } from '@singletons/dom'; +import { CoreForms } from '@singletons/form'; +import { CoreLogger } from '@singletons/logger'; + +/** + * Service used to render an Error Accordion component. + * + * This is declared as a service instead of an Angular Component because the HTML + * has to be injected dynamically in alerts (only HTML and Ionic components work). + */ +@Injectable({ providedIn: 'root' }) +export class CoreErrorAccordionService { + + private logger: CoreLogger; + + constructor() { + this.logger = CoreLogger.getInstance('CoreErrorAccordion'); + } + + /** + * Render an instance of the component into an HTML string. + * + * @param element Root element. + * @param errorCode Error code. + * @param errorDetails Error details. + */ + async render(element: Element, errorCode: string, errorDetails: string): Promise { + const html = this.html(errorCode, errorDetails); + + element.innerHTML = html; + + await this.hydrate(element); + } + + /** + * Get component html. + * + * @param errorCode Error code. + * @param errorDetails Error details. + * @returns HTML. + */ + private html(errorCode: string, errorDetails: string): string { + const contentId = CoreForms.uniqueId('error-accordion-content'); + const errorCodeLabel = Translate.instant('core.errorcode', { errorCode }); + const hideDetailsLabel = Translate.instant('core.errordetailshide'); + const showDetailsLabel = Translate.instant('core.errordetailsshow'); + + return ` +
+

${errorCodeLabel}

+ + +
+ `; + } + + /** + * Hydrate component. + * + * @param element Root element. + */ + private async hydrate(element: Element): Promise { + const wrapper = element.querySelector('.core-error-accordion'); + const description = element.querySelector('.core-error-accordion--details'); + const button = element.querySelector('.core-error-accordion--toggle'); + const hideText = element.querySelector('.core-error-accordion--hide-details'); + + if (!wrapper || !description || !button || !hideText) { + this.logger.error('Couldn\'t render error-accordion, one of the child elements is missing'); + + return; + } + + await CoreDom.waitToBeVisible(wrapper); + + button.onclick = () => { + wrapper.classList.toggle('expanded'); + description.setAttribute('aria-hidden', description.getAttribute('aria-hidden') === 'true' ? 'false' : 'true'); + button.setAttribute('aria-expanded', button.getAttribute('aria-expanded') === 'true' ? 'false' : 'true'); + }; + + hideText.style.display = 'none'; + wrapper.style.setProperty('--width', `${wrapper.clientWidth}px`); + wrapper.style.setProperty('--description-height', `${description.clientHeight}px`); + wrapper.classList.add('hydrated'); + + await CoreUtils.nextTick(); + + hideText.style.display = 'revert'; + } + +} + +export const CoreErrorAccordion = makeSingleton(CoreErrorAccordionService); diff --git a/src/core/services/sites.ts b/src/core/services/sites.ts index d16dbb05387..09a27bafd9c 100644 --- a/src/core/services/sites.ts +++ b/src/core/services/sites.ts @@ -66,6 +66,7 @@ import { CoreSiteInfo, CoreSiteInfoResponse, CoreSitePublicConfigResponse } from import { CoreSiteWSPreSets } from '@classes/sites/authenticated-site'; import { firstValueFrom } from 'rxjs'; import { CoreHTMLClasses } from '@singletons/html-classes'; +import { CoreSiteErrorDebug } from '@classes/errors/siteerror'; export const CORE_SITE_SCHEMAS = new InjectionToken('CORE_SITE_SCHEMAS'); export const CORE_SITE_CURRENT_SITE_ID_CONFIG = 'current_site_id'; @@ -362,18 +363,22 @@ export class CoreSitesProvider { if (!config.enablewebservices) { throw this.createCannotConnectLoginError(config.httpswwwroot || config.wwwroot, { supportConfig: new CoreUserGuestSupportConfig(temporarySite, config), - errorcode: 'webservicesnotenabled', - errorDetails: Translate.instant('core.login.webservicesnotenabled'), critical: true, + debug: { + code: 'webservicesnotenabled', + details: Translate.instant('core.login.webservicesnotenabled'), + }, }); } if (!config.enablemobilewebservice) { throw this.createCannotConnectLoginError(config.httpswwwroot || config.wwwroot, { supportConfig: new CoreUserGuestSupportConfig(temporarySite, config), - errorcode: 'mobileservicesnotenabled', - errorDetails: Translate.instant('core.login.mobileservicesnotenabled'), critical: true, + debug: { + code: 'mobileservicesnotenabled', + details: Translate.instant('core.login.mobileservicesnotenabled'), + }, }); } @@ -421,14 +426,16 @@ export class CoreSitesProvider { siteUrl: string, error: CoreError | CoreAjaxError | CoreAjaxWSError, ): Promise { - if (error instanceof CoreAjaxError || !('errorcode' in error)) { + if (error instanceof CoreAjaxError || (!('debug' in error) && !('errorcode' in error))) { // The WS didn't return data, probably cannot connect. return new CoreLoginError({ title: Translate.instant('core.cannotconnect'), message: Translate.instant('core.siteunavailablehelp', { site: siteUrl }), - errorcode: 'publicconfigfailed', - errorDetails: error.message || '', critical: false, // Allow fallback to http if siteUrl uses https. + debug: { + code: 'publicconfigfailed', + details: error.message || 'Failed getting public config', + }, }); } @@ -437,28 +444,31 @@ export class CoreSitesProvider { critical: true, title: Translate.instant('core.cannotconnect'), message: Translate.instant('core.siteunavailablehelp', { site: siteUrl }), - errorcode: error.errorcode, supportConfig: error.supportConfig, - errorDetails: error.errorDetails ?? error.message, + debug: error.debug, }; - if (error.errorcode === 'codingerror') { + if (error.debug?.code === 'codingerror') { // This could be caused by a redirect. Check if it's the case. const redirect = await CoreUtils.checkRedirect(siteUrl); options.message = Translate.instant('core.siteunavailablehelp', { site: siteUrl }); if (redirect) { - options.errorcode = 'sitehasredirect'; - options.errorDetails = Translate.instant('core.login.sitehasredirect'); options.critical = false; // Keep checking fallback URLs. + options.debug = { + code: 'sitehasredirect', + details: Translate.instant('core.login.sitehasredirect'), + }; } - } else if (error.errorcode === 'invalidrecord') { + } else if (error.debug?.code === 'invalidrecord') { // WebService not found, site not supported. options.message = Translate.instant('core.siteunavailablehelp', { site: siteUrl }); - options.errorcode = 'invalidmoodleversion'; - options.errorDetails = Translate.instant('core.login.invalidmoodleversion', { $a: CoreSite.MINIMUM_MOODLE_VERSION }); - } else if (error.errorcode === 'redirecterrordetected') { + options.debug = { + code: 'invalidmoodleversion', + details: Translate.instant('core.login.invalidmoodleversion', { $a: CoreSite.MINIMUM_MOODLE_VERSION }), + }; + } else if (error.debug?.code === 'redirecterrordetected') { options.critical = false; // Keep checking fallback URLs. } @@ -499,19 +509,21 @@ export class CoreSitesProvider { try { data = await firstValueFrom(Http.post(loginUrl, params).pipe(timeout(CoreWS.getRequestTimeout()))); } catch (error) { - throw new CoreError( - this.isLoggedIn() - ? Translate.instant('core.siteunavailablehelp', { site: this.currentSite?.siteUrl }) - : Translate.instant('core.sitenotfoundhelp'), - ); + throw this.createCannotConnectLoginError(siteUrl, { + debug: { + code: 'logintokenerror', + details: error.message, + }, + }); } if (data === undefined) { - throw new CoreError( - this.isLoggedIn() - ? Translate.instant('core.siteunavailablehelp', { site: this.currentSite?.siteUrl }) - : Translate.instant('core.sitenotfoundhelp'), - ); + throw this.createCannotConnectLoginError(siteUrl, { + debug: { + code: 'logintokenempty', + details: 'The request to /login/token.php returned an empty response', + }, + }); } if (data.token !== undefined) { @@ -536,16 +548,20 @@ export class CoreSitesProvider { if (redirect) { throw this.createCannotConnectLoginError(siteUrl, { supportConfig: await CoreUserGuestSupportConfig.forSite(siteUrl), - errorcode: 'sitehasredirect', - errorDetails: Translate.instant('core.login.sitehasredirect'), + debug: { + code: 'sitehasredirect', + details: Translate.instant('core.login.sitehasredirect'), + }, }); } } throw this.createCannotConnectLoginError(siteUrl, { supportConfig: await CoreUserGuestSupportConfig.forSite(siteUrl), - errorcode: data.errorcode, - errorDetails: data.error, + debug: { + code: data.errorcode ?? 'loginfailed', + details: data.error ?? 'Could not get a user token in /login/token.php', + }, }); } @@ -657,23 +673,33 @@ export class CoreSitesProvider { * @returns A promise rejected with the error info. */ protected async treatInvalidAppVersion(result: number, siteId?: string): Promise { - let errorCode: string | undefined; + let debug: CoreSiteErrorDebug | undefined; let errorKey: string | undefined; let translateParams = {}; switch (result) { case CoreSitesProvider.MOODLE_APP: errorKey = 'core.login.connecttomoodleapp'; - errorCode = 'connecttomoodleapp'; + debug = { + code: 'connecttomoodleapp', + details: 'Cannot connect to app', + }; break; case CoreSitesProvider.WORKPLACE_APP: errorKey = 'core.login.connecttoworkplaceapp'; - errorCode = 'connecttoworkplaceapp'; + debug = { + code: 'connecttoworkplaceapp', + details: 'Cannot connect to app', + }; break; default: - errorCode = 'invalidmoodleversion'; errorKey = 'core.login.invalidmoodleversion'; translateParams = { $a: CoreSite.MINIMUM_MOODLE_VERSION }; + debug = { + code: 'invalidmoodleversion', + details: 'Cannot connect to app', + }; + break; } if (siteId) { @@ -681,8 +707,8 @@ export class CoreSitesProvider { } throw new CoreLoginError({ + debug, message: Translate.instant(errorKey, translateParams), - errorcode: errorCode, loggedOut: true, }); } diff --git a/src/core/services/utils/dom.ts b/src/core/services/utils/dom.ts index bb986499232..480f7dd232f 100644 --- a/src/core/services/utils/dom.ts +++ b/src/core/services/utils/dom.ts @@ -52,7 +52,7 @@ import { Subscription } from 'rxjs'; import { CoreNetwork } from '@services/network'; import { CoreSiteError } from '@classes/errors/siteerror'; import { CoreUserSupport } from '@features/user/services/support'; -import { CoreErrorInfoComponent } from '@components/error-info/error-info'; +import { CoreErrorAccordion } from '@services/error-accordion'; import { CorePlatform } from '@services/platform'; import { CoreCancellablePromise } from '@classes/cancellable-promise'; import { CoreLang } from '@services/lang'; @@ -1037,8 +1037,8 @@ export class CoreDomUtilsProvider { if (typeof error !== 'string' && 'buttons' in error && typeof error.buttons !== 'undefined') { alertOptions.buttons = error.buttons; } else if (error instanceof CoreSiteError) { - if (error.errorDetails) { - alertOptions.message = `

${alertOptions.message}

`; + if (error.debug) { + alertOptions.message = `

${alertOptions.message}

`; } const supportConfig = error.supportConfig; @@ -1051,7 +1051,7 @@ export class CoreDomUtilsProvider { handler: () => CoreUserSupport.contact({ supportConfig, subject: alertOptions.header, - message: `${error.errorcode}\n\n${error.errorDetails}`, + message: `${error.debug?.code}\n\n${error.debug?.details}`, }), }); } @@ -1061,11 +1061,11 @@ export class CoreDomUtilsProvider { const alertElement = await this.showAlertWithOptions(alertOptions, autocloseTime); - if (error instanceof CoreSiteError && error.errorDetails) { - const containerElement = alertElement.querySelector('.core-error-info-container'); + if (error instanceof CoreSiteError && error.debug) { + const containerElement = alertElement.querySelector('.core-error-accordion-container'); if (containerElement) { - containerElement.innerHTML = CoreErrorInfoComponent.render(error.errorDetails, error.errorcode); + await CoreErrorAccordion.render(containerElement, error.debug.code, error.debug.details); } } diff --git a/src/core/services/ws.ts b/src/core/services/ws.ts index b1084347eac..004da122c96 100644 --- a/src/core/services/ws.ts +++ b/src/core/services/ws.ts @@ -497,10 +497,12 @@ export class CoreWSProvider { throw new CoreAjaxError({ message, supportConfig: await CoreUserGuestSupportConfig.forSite(preSets.siteUrl), - errorcode: 'invalidresponse', - errorDetails: Translate.instant('core.serverconnection', { - details: Translate.instant('core.errorinvalidresponse', { method }), - }), + debug: { + code: 'invalidresponse', + details: Translate.instant('core.serverconnection', { + details: Translate.instant('core.errorinvalidresponse', { method }), + }), + }, }); } else if (data.error) { throw new CoreAjaxWSError(data); @@ -527,54 +529,72 @@ export class CoreWSProvider { if (CorePlatform.isMobile()) { switch (data.status) { case NativeHttp.ErrorCode.SSL_EXCEPTION: - options.errorcode = 'invalidcertificate'; - options.errorDetails = Translate.instant('core.certificaterror', { - details: CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Invalid certificate', - }); + options.debug = { + code: 'invalidcertificate', + details: Translate.instant('core.certificaterror', { + details: CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Invalid certificate', + }), + }; break; case NativeHttp.ErrorCode.SERVER_NOT_FOUND: - options.errorcode = 'servernotfound'; - options.errorDetails = CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Server could not be found'; + options.debug = { + code: 'servernotfound', + details: CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Server could not be found', + }; break; case NativeHttp.ErrorCode.TIMEOUT: - options.errorcode = 'requesttimeout'; - options.errorDetails = CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Request timed out'; + options.debug = { + code: 'requesttimeout', + details: CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Request timed out', + }; break; case NativeHttp.ErrorCode.UNSUPPORTED_URL: - options.errorcode = 'unsupportedurl'; - options.errorDetails = CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Url not supported'; + options.debug = { + code: 'unsupportedurl', + details: CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Url not supported', + }; break; case NativeHttp.ErrorCode.NOT_CONNECTED: - options.errorcode = 'connectionerror'; - options.errorDetails = CoreTextUtils.getErrorMessageFromError(data.error) - ?? 'Connection error, is network available?'; + options.debug = { + code: 'connectionerror', + details: CoreTextUtils.getErrorMessageFromError(data.error) + ?? 'Connection error, is network available?', + }; break; case NativeHttp.ErrorCode.ABORTED: - options.errorcode = 'requestaborted'; - options.errorDetails = CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Request aborted'; + options.debug = { + code: 'requestaborted', + details: CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Request aborted', + }; break; case NativeHttp.ErrorCode.POST_PROCESSING_FAILED: - options.errorcode = 'requestprocessingfailed'; - options.errorDetails = CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Request processing failed'; + options.debug = { + code: 'requestprocessingfailed', + details: CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Request processing failed', + }; break; } } - if (!options.errorcode) { + if (!options.debug) { switch (data.status) { case 404: - options.errorcode = 'endpointnotfound'; - options.errorDetails = Translate.instant('core.ajaxendpointnotfound', { - $a: CoreSite.MINIMUM_MOODLE_VERSION, - }); + options.debug = { + code: 'endpointnotfound', + details: Translate.instant('core.ajaxendpointnotfound', { + $a: CoreSite.MINIMUM_MOODLE_VERSION, + }), + }; break; default: { const details = CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Unknown error'; - options.errorcode = 'serverconnectionajax'; - options.errorDetails = Translate.instant('core.serverconnection', { - details: `[Response status code: ${data.status}] ${details}`, - }); + options.debug = { + code: 'serverconnectionajax', + details: Translate.instant('core.serverconnection', { + details: `[Response status code: ${data.status}] ${details}`, + }), + }; } break; } @@ -716,10 +736,12 @@ export class CoreWSProvider { if (!data) { throw await this.createCannotConnectSiteError(preSets.siteUrl, { - errorcode: 'serverconnectionpost', - errorDetails: Translate.instant('core.serverconnection', { - details: Translate.instant('core.errorinvalidresponse', { method }), - }), + debug: { + code: 'serverconnectionpost', + details: Translate.instant('core.serverconnection', { + details: Translate.instant('core.errorinvalidresponse', { method }), + }), + }, }); } else if (typeof data !== typeExpected) { // If responseType is text an string will be returned, parse before returning. @@ -730,8 +752,10 @@ export class CoreWSProvider { this.logger.warn(`Response expected type "${typeExpected}" cannot be parsed to number`); throw await this.createCannotConnectSiteError(preSets.siteUrl, { - errorcode: 'invalidresponse', - errorDetails: Translate.instant('core.errorinvalidresponse', { method }), + debug: { + code: 'invalidresponse', + details: Translate.instant('core.errorinvalidresponse', { method }), + }, }); } } else if (typeExpected === 'boolean') { @@ -743,24 +767,30 @@ export class CoreWSProvider { this.logger.warn(`Response expected type "${typeExpected}" is not true or false`); throw await this.createCannotConnectSiteError(preSets.siteUrl, { - errorcode: 'invalidresponse', - errorDetails: Translate.instant('core.errorinvalidresponse', { method }), + debug: { + code: 'invalidresponse', + details: Translate.instant('core.errorinvalidresponse', { method }), + }, }); } } else { this.logger.warn('Response of type "' + typeof data + `" received, expecting "${typeExpected}"`); throw await this.createCannotConnectSiteError(preSets.siteUrl, { - errorcode: 'invalidresponse', - errorDetails: Translate.instant('core.errorinvalidresponse', { method }), + debug: { + code: 'invalidresponse', + details: Translate.instant('core.errorinvalidresponse', { method }), + }, }); } } else { this.logger.warn('Response of type "' + typeof data + `" received, expecting "${typeExpected}"`); throw await this.createCannotConnectSiteError(preSets.siteUrl, { - errorcode: 'invalidresponse', - errorDetails: Translate.instant('core.errorinvalidresponse', { method }), + debug: { + code: 'invalidresponse', + details: Translate.instant('core.errorinvalidresponse', { method }), + }, }); } } @@ -803,10 +833,12 @@ export class CoreWSProvider { return retryPromise; } else if (error.status === -2) { throw await this.createCannotConnectSiteError(preSets.siteUrl, { - errorcode: 'invalidcertificate', - errorDetails: Translate.instant('core.certificaterror', { - details: CoreTextUtils.getErrorMessageFromError(error) ?? 'Unknown error', - }), + debug: { + code: 'invalidcertificate', + details: Translate.instant('core.certificaterror', { + details: CoreTextUtils.getErrorMessageFromError(error) ?? 'Unknown error', + }), + }, }); } else if (error.status > 0) { throw this.createHttpError(error, error.status); @@ -1033,24 +1065,30 @@ export class CoreWSProvider { if (data === null) { throw await this.createCannotConnectSiteError(preSets.siteUrl, { - errorcode: 'invalidresponse', - errorDetails: Translate.instant('core.errorinvalidresponse', { method: 'upload.php' }), + debug: { + code: 'invalidresponse', + details: Translate.instant('core.errorinvalidresponse', { method: 'upload.php' }), + }, }); } if (!data) { throw await this.createCannotConnectSiteError(preSets.siteUrl, { - errorcode: 'serverconnectionupload', - errorDetails: Translate.instant('core.serverconnection', { - details: Translate.instant('core.errorinvalidresponse', { method: 'upload.php' }), - }), + debug: { + code: 'serverconnectionupload', + details: Translate.instant('core.serverconnection', { + details: Translate.instant('core.errorinvalidresponse', { method: 'upload.php' }), + }), + }, }); } else if (typeof data != 'object') { this.logger.warn('Upload file: Response of type "' + typeof data + '" received, expecting "object"'); throw await this.createCannotConnectSiteError(preSets.siteUrl, { - errorcode: 'invalidresponse', - errorDetails: Translate.instant('core.errorinvalidresponse', { method: 'upload.php' }), + debug: { + code: 'invalidresponse', + details: Translate.instant('core.errorinvalidresponse', { method: 'upload.php' }), + }, }); } diff --git a/src/storybook/storybook.module.ts b/src/storybook/storybook.module.ts deleted file mode 100644 index b8aa614d730..00000000000 --- a/src/storybook/storybook.module.ts +++ /dev/null @@ -1,78 +0,0 @@ -// (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 { IonicModule } from '@ionic/angular'; -import { NgModule, ApplicationInitStatus, APP_INITIALIZER } from '@angular/core'; -import { Observable, of } from 'rxjs'; -import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; - -import englishTranslations from '@/assets/lang/en.json'; -import { CoreApplicationInitStatus } from '@classes/application-init-status'; -import { Translate } from '@singletons'; -import { CoreSitesProviderStub, CoreSitesStub } from '@/storybook/stubs/services/sites'; -import { CoreSitesProvider } from '@services/sites'; -import { CoreDbProviderStub } from '@/storybook/stubs/services/db'; -import { CoreDbProvider } from '@services/db'; -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 { - - getTranslation(): Observable { - return of(englishTranslations); - } - -} - -/** - * Module declaring dependencies for Storybook components. - */ -@NgModule({ - imports: [ - IonicModule.forRoot(), - TranslateModule.forRoot({ - loader: { - provide: TranslateLoader, - useClass: StaticTranslateLoader, - }, - }), - ], - providers: [ - { provide: ApplicationInitStatus, useClass: CoreApplicationInitStatus }, - { provide: CoreSitesProvider, useClass: CoreSitesProviderStub }, - { provide: CoreDbProvider, useClass: CoreDbProviderStub }, - { provide: CoreFilepoolProvider, useClass: CoreFilepoolProviderStub }, - { provide: CorePushNotificationsProvider, useClass: CorePushNotificationsProviderStub }, - { provide: HttpClient, useClass: HttpClientStub }, - { - provide: APP_INITIALIZER, - multi: true, - useValue: () => { - Translate.setDefaultLang('en'); - Translate.use('en'); - CoreSitesStub.stubCurrentSite(); - }, - }, - ], - exports: [ - IonicModule, - TranslateModule, - ], -}) -export class StorybookModule {} diff --git a/src/storybook/stubs/classes/site.ts b/src/storybook/stubs/classes/site.ts deleted file mode 100644 index 4d266913b08..00000000000 --- a/src/storybook/stubs/classes/site.ts +++ /dev/null @@ -1,59 +0,0 @@ -// (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 { CoreSiteWSPreSets, WSObservable } from '@classes/sites/authenticated-site'; -import { CoreSite, CoreSiteConfigResponse } from '@classes/sites/site'; -import { CoreSiteInfo } from '@classes/sites/unauthenticated-site'; -import { of } from 'rxjs'; - -export interface CoreSiteFixture { - id: string; - info: CoreSiteInfo; -} - -export class CoreSiteStub extends CoreSite { - - protected wsStubs: Record = {}; - - constructor (fixture: CoreSiteFixture) { - super(fixture.id, fixture.info.siteurl, '', { info: fixture.info }); - - this.stubWSResponse('tool_mobile_get_config', { - settings: [], - warnings: [], - }); - } - - /** - * @inheritdoc - */ - readObservable(wsFunction: string, data: unknown, preSets?: CoreSiteWSPreSets): WSObservable { - if (wsFunction in this.wsStubs) { - return of(this.wsStubs[wsFunction] as T); - } - - return super.readObservable(wsFunction, data, preSets); - } - - /** - * Prepare as stubbed response for a given WS. - * - * @param wsFunction WS function. - * @param response Response. - */ - stubWSResponse(wsFunction: string, response: T): void { - this.wsStubs[wsFunction] = response; - } - -} diff --git a/src/storybook/stubs/classes/sqlitedb.ts b/src/storybook/stubs/classes/sqlitedb.ts deleted file mode 100644 index f35345951f0..00000000000 --- a/src/storybook/stubs/classes/sqlitedb.ts +++ /dev/null @@ -1,32 +0,0 @@ -// (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 { SQLiteDB } from '@classes/sqlitedb'; -import { SQLiteObject } from '@awesome-cordova-plugins/sqlite/ngx'; - -/** - * SQlite database stub. - */ -export class SQLiteDBStub extends SQLiteDB { - - /** - * @inheritdoc - */ - async createDatabase(): Promise { - return new Proxy({ - executeSql: () => Promise.resolve({ insertId: Math.random().toString() }), - }, {}) as unknown as SQLiteObject; - } - -} diff --git a/src/storybook/stubs/services/db.ts b/src/storybook/stubs/services/db.ts deleted file mode 100644 index e74c90bb23d..00000000000 --- a/src/storybook/stubs/services/db.ts +++ /dev/null @@ -1,35 +0,0 @@ -// (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 { SQLiteDBStub } from '@/storybook/stubs/classes/sqlitedb'; -import { SQLiteDB } from '@classes/sqlitedb'; -import { CoreDbProvider } from '@services/db'; - -/** - * Database provider stub. - */ -export class CoreDbProviderStub extends CoreDbProvider { - - /** - * @inheritdoc - */ - getDB(name: string, forceNew?: boolean): SQLiteDB { - if (this.dbInstances[name] === undefined || forceNew) { - this.dbInstances[name] = new SQLiteDBStub(name); - } - - return this.dbInstances[name]; - } - -} diff --git a/src/storybook/stubs/services/filepool.ts b/src/storybook/stubs/services/filepool.ts deleted file mode 100644 index d5c604aadcf..00000000000 --- a/src/storybook/stubs/services/filepool.ts +++ /dev/null @@ -1,32 +0,0 @@ -// (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 { makeSingleton } from '@singletons'; -import { CoreFilepoolProvider } from '@services/filepool'; - -/** - * Filepool provider stub. - */ -export class CoreFilepoolProviderStub extends CoreFilepoolProvider { - - /** - * @inheritdoc - */ - async getSrcByUrl(siteId: string, fileUrl: string): Promise { - return fileUrl; - } - -} - -export const CoreFilepoolStub = makeSingleton(CoreFilepoolProvider); diff --git a/src/storybook/stubs/services/http.ts b/src/storybook/stubs/services/http.ts deleted file mode 100644 index 55e397d72f3..00000000000 --- a/src/storybook/stubs/services/http.ts +++ /dev/null @@ -1,38 +0,0 @@ -// (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 { makeSingleton } from '@singletons'; -import { HttpClient, HttpHandler } from '@angular/common/http'; -import { from, Observable } from 'rxjs'; - -/** - * Http service stub. - */ -export class HttpClientStub extends HttpClient { - - constructor() { - super(null as unknown as HttpHandler); - } - - /** - * @inheritdoc - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - get(url: string): Observable { - return from(fetch(url).then(response => response.text())); - } - -} - -export const HttpStub = makeSingleton(HttpClient); diff --git a/src/storybook/stubs/services/pushnotifications.ts b/src/storybook/stubs/services/pushnotifications.ts deleted file mode 100644 index 9d380c32cb6..00000000000 --- a/src/storybook/stubs/services/pushnotifications.ts +++ /dev/null @@ -1,32 +0,0 @@ -// (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 deleted file mode 100644 index 52a60f3515b..00000000000 --- a/src/storybook/stubs/services/sites.ts +++ /dev/null @@ -1,86 +0,0 @@ -// (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 { 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 { CoreError } from '@classes/errors/error'; -import { CoreSite } from '@classes/sites/site'; -import { SiteDBEntry } from '@services/database/sites'; -import { CoreSiteBasicInfo, CoreSitesProvider } from '@services/sites'; -import { makeSingleton } from '@singletons'; - -/** - * Sites provider stub. - */ -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 ?? schoolBarbaraSite); - } - - return this.getRequiredCurrentSite(); - } - -} - -export const CoreSitesStub = makeSingleton(CoreSitesProvider); diff --git a/src/storybook/utils/helpers.ts b/src/storybook/utils/helpers.ts deleted file mode 100644 index 5ed8ae7b997..00000000000 --- a/src/storybook/utils/helpers.ts +++ /dev/null @@ -1,30 +0,0 @@ -// (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 { Story } from '@storybook/angular'; - -/** - * Define story. - * - * @param template Story template. - * @param defaultArgs Default arguments. - * @returns Story. - */ -export function story(template: Story, defaultArgs: Partial = {}): Story { - const story = template.bind({}); - - story.args = defaultArgs; - - return story; -} diff --git a/src/theme/components/error-accordion.scss b/src/theme/components/error-accordion.scss new file mode 100644 index 00000000000..4bc7f20af8a --- /dev/null +++ b/src/theme/components/error-accordion.scss @@ -0,0 +1,151 @@ +.core-error-accordion { + --toggle-icon-animation-duration: 300ms; + --toggle-icon-animation-function: ease-in; + --background-color: var(--gray-300); + --toggle-icon-size: 16px; + + background: var(--background-color); + border-radius: var(--radius-xs); + + .core-error-accordion--code { + margin: 0; + text-align: start; + color: var(--text-color-main); + font: var(--subtitle-md-font); + padding-top: var(--spacing-2); + padding-bottom: var(--spacing-2); + padding-inline-start: var(--spacing-3); + padding-inline-end: var(--spacing-3); + } + + .core-error-accordion--details { + opacity: 0; + display: flex; + overflow: hidden; + flex-direction: column; + justify-content: center; + transition-property: opacity, height; + transition-duration: var(--toggle-icon-animation-duration); + transition-timing-function: var(--toggle-icon-animation-function); + + p { + margin: 0; + padding-top: var(--spacing-2); + padding-bottom: var(--spacing-2); + padding-inline-start: var(--spacing-3); + padding-inline-end: var(--spacing-3); + text-align: start; + font: var(--body-md-font); + color: var(--text-color-secondary); + } + + } + + .core-error-accordion--toggle { + display: flex; + width: 100%; + align-items: center; + background: transparent; + justify-content: space-between; + color: var(--text-color-secondary); + font: var(--label-lg-font); + padding-top: var(--spacing-2); + padding-bottom: var(--spacing-2); + padding-inline-start: var(--spacing-3); + padding-inline-end: var(--spacing-3); + + .core-error-accordion--toggle-text { + display: flex; + flex-direction: column; + } + + .core-error-accordion--show-details, + .core-error-accordion--hide-details { + text-align: start; + transition: opacity var(--toggle-icon-animation-duration) var(--toggle-icon-animation-function); + } + + ion-icon { + width: var(--toggle-icon-size); + margin-inline-start: var(--spacing-4); + transition: transform var(--toggle-icon-animation-duration) var(--toggle-icon-animation-function); + transform: rotate(0); + } + + &:hover { + background: var(--state-color-hover); + } + + &:focus { + box-shadow: none; + background: var(--state-color-focused); + } + + &:focus-visible { + box-shadow: none; + outline: 2px solid var(--state-color-keyboard-focus); + } + + &:active { + background: var(--state-color-pressed); + } + + } + + &.hydrated { + width: var(--width); + + .core-error-accordion--details { + height: 0; + } + + .core-error-accordion--toggle { + + .core-error-accordion--toggle-text { + flex-grow: 1; + position: relative; + } + + .core-error-accordion--hide-details { + position: absolute; + opacity: 0; + top: 0; + bottom: 0; + left: 0; + right: 0; + } + + } + + } + + &.expanded { + + .core-error-accordion--details { + opacity: 1; + height: var(--description-height); + } + + .core-error-accordion--toggle { + + .core-error-accordion--hide-details { + opacity: 1; + } + + .core-error-accordion--show-details { + opacity: 0; + } + + ion-icon { + transform: rotate(180deg); + } + + } + + } + +} + +html.dark .core-error-accordion { + --background-color: var(--gray-700); +} diff --git a/src/theme/theme.design-system.scss b/src/theme/theme.design-system.scss index 06fe09b70c6..3ef11bc87ad 100644 --- a/src/theme/theme.design-system.scss +++ b/src/theme/theme.design-system.scss @@ -5,9 +5,7 @@ html { --spacing-#{$i}: #{$i*4}px; } - // Font sizes - - // Body font size + // Typography --font-size-sm: 12px; --font-size-md: 14px; --font-size-lg: 16px; @@ -15,19 +13,29 @@ html { --font-weight-normal: 400; --font-weight-medium: 500; + // Typography - Body --body-font-size-sm: var(--font-size-sm); --body-font-size-md: var(--font-size-md); --body-font-size-lg: var(--font-size-lg); --body-font-weight: var(--font-weight-normal); --body-line-height: 150%; + --body-sm-font: normal normal var(--body-font-weight) var(--body-font-size-sm)/var(--body-line-height) var(--ion-font-family); + --body-md-font: normal normal var(--body-font-weight) var(--body-font-size-md)/var(--body-line-height) var(--ion-font-family); + --body-lg-font: normal normal var(--body-font-weight) var(--body-font-size-lg)/var(--body-line-height) var(--ion-font-family); + + // Typography - Links --link-sm-font-size: var(--font-size-sm); --link-md-font-size: var(--font-size-md); --link-lg-font-size: var(--font-size-lg); --link-font-weight: var(--font-weight-normal); --link-line-height: 150%; - // Labels + --link-sm-font: normal normal var(--link-font-weight) var(--link-sm-font-size)/var(--link-line-height) var(--ion-font-family); + --link-md-font: normal normal var(--link-font-weight) var(--link-md-font-size)/var(--link-line-height) var(--ion-font-family); + --link-lg-font: normal normal var(--link-font-weight) var(--link-lg-font-size)/var(--link-line-height) var(--ion-font-family); + + // Typography - Labels --label-sm-font-size: 10px; --label-md-font-size: 12px; --label-lg-font-size: 14px; @@ -37,7 +45,11 @@ html { --label-md-line-height: 16px; --label-lg-line-height: 20px; - // Subtitles + --label-sm-font: normal normal var(--label-font-weight) var(--label-sm-font-size)/var(--label-sm-line-height) var(--ion-font-family); + --label-md-font: normal normal var(--label-font-weight) var(--label-md-font-size)/var(--label-md-line-height) var(--ion-font-family); + --label-lg-font: normal normal var(--label-font-weight) var(--label-lg-font-size)/var(--label-lg-line-height) var(--ion-font-family); + + // Typography - Subtitles --subtitle-sm-font-size: 14px; --subtitle-md-font-size: 16px; --subtitle-lg-font-size: 20px; @@ -45,7 +57,11 @@ html { --subtitle-font-weight: var(--font-weight-medium); --subtitle-line-height: 150%; - // Headings + --subtitle-sm-font: normal normal var(--subtitle-font-weight) var(--subtitle-sm-font-size)/var(--subtitle-line-height) var(--ion-font-family); + --subtitle-md-font: normal normal var(--subtitle-font-weight) var(--subtitle-md-font-size)/var(--subtitle-line-height) var(--ion-font-family); + --subtitle-lg-font: normal normal var(--subtitle-font-weight) var(--subtitle-lg-font-size)/var(--subtitle-line-height) var(--ion-font-family); + + // Typography - Headings --heading-1-font-size: 28px; --heading-2-font-size: 24px; --heading-3-font-size: 22px; @@ -94,6 +110,26 @@ html { // A11y --a11y-min-target-size: 44px; + + // Colors + --blue: #0f6cbf; + + --text-color-main: var(--gray-900); + --text-color-secondary: var(--gray-800); + + --state-color-hover: rgb(40 40 40, 4%); // --gray-900 4% + --state-color-pressed: rgb(40 40 40, 12%); // --gray-900 12% + --state-color-focused: rgb(40 40 40, 12%); // --gray-900 12% + --state-color-keyboard-focus: var(--blue); + +} + +html.dark { + + // Colors + --text-color-main: var(--gray-200); + --text-color-secondary: var(--gray-300); + } /** @deprecated since 4.3 **/ diff --git a/src/theme/theme.scss b/src/theme/theme.scss index 34177698918..322b5840a5f 100644 --- a/src/theme/theme.scss +++ b/src/theme/theme.scss @@ -22,11 +22,11 @@ /* Components */ @import "components/collapsible-header.scss"; @import "components/collapsible-item.scss"; +@import "components/error-accordion.scss"; @import "components/format-text.scss"; -@import "components/rubrics.scss"; -@import "components/mod-label.scss"; @import "components/ion-icon.scss"; -@import "../core/components/error-info/error-info.scss"; +@import "components/mod-label.scss"; +@import "components/rubrics.scss"; @import "components/videojs.scss"; /* Some styles from 3rd party libraries. */ diff --git a/tsconfig.app.json b/tsconfig.app.json index addf75e8c95..411dc5e5b01 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -25,7 +25,6 @@ "src/**/tests/**", "src/**/stories/**", "src/testing/**", - "src/storybook/**", "src/**/*.test.ts", "src/**/*.stories.*" ]