MOBILE-4059 core: Separate error details in alerts
parent
238dc458fc
commit
11fea266e9
|
@ -1,4 +1,5 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
framework: '@storybook/angular',
|
framework: '@storybook/angular',
|
||||||
|
addons: ['@storybook/addon-controls'],
|
||||||
stories: ['../src/**/*.stories.ts'],
|
stories: ['../src/**/*.stories.ts'],
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
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',
|
||||||
|
};
|
|
@ -0,0 +1,3 @@
|
||||||
|
.core-error-info {
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
|
@ -13,7 +13,10 @@ module.exports = {
|
||||||
'^.+\\.(ts|html)$': 'ts-jest',
|
'^.+\\.(ts|html)$': 'ts-jest',
|
||||||
},
|
},
|
||||||
transformIgnorePatterns: ['node_modules/(?!@ionic-native|@ionic)'],
|
transformIgnorePatterns: ['node_modules/(?!@ionic-native|@ionic)'],
|
||||||
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: '<rootDir>/src/' }),
|
moduleNameMapper: {
|
||||||
|
...pathsToModuleNameMapper(compilerOptions.paths, { prefix: '<rootDir>/src/' }),
|
||||||
|
'^!raw-loader!.*': 'jest-raw-loader',
|
||||||
|
},
|
||||||
globals: {
|
globals: {
|
||||||
'ts-jest': {
|
'ts-jest': {
|
||||||
tsConfig: './tsconfig.test.json',
|
tsConfig: './tsconfig.test.json',
|
||||||
|
|
|
@ -5677,6 +5677,22 @@
|
||||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.11.0.tgz",
|
||||||
"integrity": "sha512-/IubCWhVXCguyMUp/3zGrg3c882+RJNg/zpiKfyfJL3kRCOwe+/MD8OoAXVGdd+xAohZKIi1Ik+EHFlsptsjLg=="
|
"integrity": "sha512-/IubCWhVXCguyMUp/3zGrg3c882+RJNg/zpiKfyfJL3kRCOwe+/MD8OoAXVGdd+xAohZKIi1Ik+EHFlsptsjLg=="
|
||||||
},
|
},
|
||||||
|
"@storybook/addon-controls": {
|
||||||
|
"version": "6.1.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-6.1.21.tgz",
|
||||||
|
"integrity": "sha512-IJgZWD2E9eLKj8DJLA9lT63N4jPfVneFJ05gnPco01ZJCEiDAo7babP5Ns2UTJDUaQEtX0m04UoIkidcteWKsA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@storybook/addons": "6.1.21",
|
||||||
|
"@storybook/api": "6.1.21",
|
||||||
|
"@storybook/client-api": "6.1.21",
|
||||||
|
"@storybook/components": "6.1.21",
|
||||||
|
"@storybook/node-logger": "6.1.21",
|
||||||
|
"@storybook/theming": "6.1.21",
|
||||||
|
"core-js": "^3.0.1",
|
||||||
|
"ts-dedent": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@storybook/addons": {
|
"@storybook/addons": {
|
||||||
"version": "6.1.21",
|
"version": "6.1.21",
|
||||||
"resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-6.1.21.tgz",
|
"resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-6.1.21.tgz",
|
||||||
|
@ -21473,6 +21489,12 @@
|
||||||
"ts-jest": "26.x"
|
"ts-jest": "26.x"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"jest-raw-loader": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jest-raw-loader/-/jest-raw-loader-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-g9oaAjeC4/rIJk1Wd3RxVbOfMizowM7LSjEJqa4R9qDX0OjQNABXOhH+GaznUp+DjTGVPi2vPPbQXyX87DOnYg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"jest-regex-util": {
|
"jest-regex-util": {
|
||||||
"version": "26.0.0",
|
"version": "26.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz",
|
||||||
|
|
|
@ -141,6 +141,7 @@
|
||||||
"@angular/language-service": "~10.0.14",
|
"@angular/language-service": "~10.0.14",
|
||||||
"@ionic/angular-toolkit": "^2.3.3",
|
"@ionic/angular-toolkit": "^2.3.3",
|
||||||
"@ionic/cli": "^6.19.0",
|
"@ionic/cli": "^6.19.0",
|
||||||
|
"@storybook/addon-controls": "~6.1",
|
||||||
"@storybook/angular": "~6.1",
|
"@storybook/angular": "~6.1",
|
||||||
"@types/faker": "^5.1.3",
|
"@types/faker": "^5.1.3",
|
||||||
"@types/node": "^12.12.64",
|
"@types/node": "^12.12.64",
|
||||||
|
@ -172,6 +173,7 @@
|
||||||
"gulp-slash": "^1.1.3",
|
"gulp-slash": "^1.1.3",
|
||||||
"jest": "^26.5.2",
|
"jest": "^26.5.2",
|
||||||
"jest-preset-angular": "^8.3.1",
|
"jest-preset-angular": "^8.3.1",
|
||||||
|
"jest-raw-loader": "^1.0.1",
|
||||||
"jsonc-parser": "^2.3.1",
|
"jsonc-parser": "^2.3.1",
|
||||||
"minimatch": "^5.1.0",
|
"minimatch": "^5.1.0",
|
||||||
"native-run": "^1.4.0",
|
"native-run": "^1.4.0",
|
||||||
|
|
|
@ -1472,6 +1472,7 @@
|
||||||
"core.cancel": "moodle",
|
"core.cancel": "moodle",
|
||||||
"core.cannotconnect": "local_moodlemobileapp",
|
"core.cannotconnect": "local_moodlemobileapp",
|
||||||
"core.cannotconnecttrouble": "local_moodlemobileapp",
|
"core.cannotconnecttrouble": "local_moodlemobileapp",
|
||||||
|
"core.cannotconnecttroublewithoutsupport": "local_moodlemobileapp",
|
||||||
"core.cannotconnectverify": "local_moodlemobileapp",
|
"core.cannotconnectverify": "local_moodlemobileapp",
|
||||||
"core.cannotdownloadfiles": "local_moodlemobileapp",
|
"core.cannotdownloadfiles": "local_moodlemobileapp",
|
||||||
"core.cannotinstallapk": "local_moodlemobileapp",
|
"core.cannotinstallapk": "local_moodlemobileapp",
|
||||||
|
@ -1697,7 +1698,10 @@
|
||||||
"core.endonesteptour": "tool_usertours",
|
"core.endonesteptour": "tool_usertours",
|
||||||
"core.error": "moodle",
|
"core.error": "moodle",
|
||||||
"core.errorchangecompletion": "local_moodlemobileapp",
|
"core.errorchangecompletion": "local_moodlemobileapp",
|
||||||
|
"core.errorcode": "local_moodlemobileapp",
|
||||||
"core.errordeletefile": "local_moodlemobileapp",
|
"core.errordeletefile": "local_moodlemobileapp",
|
||||||
|
"core.errordetailshide": "local_moodlemobileapp",
|
||||||
|
"core.errordetailsshow": "local_moodlemobileapp",
|
||||||
"core.errordownloading": "local_moodlemobileapp",
|
"core.errordownloading": "local_moodlemobileapp",
|
||||||
"core.errordownloadingsomefiles": "local_moodlemobileapp",
|
"core.errordownloadingsomefiles": "local_moodlemobileapp",
|
||||||
"core.errorfileexistssamename": "local_moodlemobileapp",
|
"core.errorfileexistssamename": "local_moodlemobileapp",
|
||||||
|
|
|
@ -29,7 +29,7 @@ export class CoreSiteError extends CoreError {
|
||||||
siteConfig?: CoreSitePublicConfigResponse;
|
siteConfig?: CoreSitePublicConfigResponse;
|
||||||
|
|
||||||
constructor(options: CoreSiteErrorOptions) {
|
constructor(options: CoreSiteErrorOptions) {
|
||||||
super(options.message);
|
super(getErrorMessage(options));
|
||||||
|
|
||||||
this.errorcode = options.errorcode;
|
this.errorcode = options.errorcode;
|
||||||
this.errorDetails = options.errorDetails;
|
this.errorDetails = options.errorDetails;
|
||||||
|
@ -67,8 +67,26 @@ export class CoreSiteError extends CoreError {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get message to use in the error.
|
||||||
|
*
|
||||||
|
* @param options Error options.
|
||||||
|
* @returns Error message.
|
||||||
|
*/
|
||||||
|
function getErrorMessage(options: CoreSiteErrorOptions): string {
|
||||||
|
if (
|
||||||
|
options.contactSupport &&
|
||||||
|
(!options.siteConfig || !CoreUserSupport.canContactSupport(options.siteConfig))
|
||||||
|
) {
|
||||||
|
return options.fallbackMessage ?? options.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return options.message;
|
||||||
|
}
|
||||||
|
|
||||||
export type CoreSiteErrorOptions = {
|
export type CoreSiteErrorOptions = {
|
||||||
message: string;
|
message: string;
|
||||||
|
fallbackMessage?: string; // Message to use if contacting support was intended but isn't possible.
|
||||||
errorcode?: string;
|
errorcode?: string;
|
||||||
errorDetails?: string;
|
errorDetails?: string;
|
||||||
critical?: boolean; // Whether the error is important enough to abort the operation.
|
critical?: boolean; // Whether the error is important enough to abort the operation.
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
<!--
|
||||||
|
The markup for this component is rendered dynamically using the static render() method
|
||||||
|
instead of using Angular's engine. The reason for using this approach is that this
|
||||||
|
allows injecting this component into HTML directly, rather than requiring Angular
|
||||||
|
to control its lifecycle.
|
||||||
|
-->
|
|
@ -0,0 +1,88 @@
|
||||||
|
.core-error-info {
|
||||||
|
background: var(--gray-200);
|
||||||
|
border-radius: var(--small-radius);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--gray-900);
|
||||||
|
|
||||||
|
p:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.core-error-info--content {
|
||||||
|
padding: var(--spacing-2) var(--spacing-2) 0 var(--spacing-2);
|
||||||
|
|
||||||
|
.core-error-info--code {
|
||||||
|
font-size: var(--font-size-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.core-error-info--details {
|
||||||
|
color: var(--gray-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.core-error-info--checkbox {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
& + .core-error-info--content {
|
||||||
|
max-height: calc(var(--font-size-sm) + 2 * var(--spacing-2));
|
||||||
|
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--content {
|
||||||
|
max-height: 150px;
|
||||||
|
|
||||||
|
& + .core-error-info--toggle .core-error-info--hide-content {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
& + .core-error-info--toggle .core-error-info--show-content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&.has-error-code .core-error-info--checkbox {
|
||||||
|
|
||||||
|
& + .core-error-info--content {
|
||||||
|
max-height: calc(var(--font-size-normal) + 2 * var(--spacing-2));
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked + .core-error-info--content {
|
||||||
|
max-height: 170px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
// (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';
|
||||||
|
import ChevronUpSVG from '!raw-loader!ionicons/dist/svg/chevron-up.svg';
|
||||||
|
import ChevronDownSVG from '!raw-loader!ionicons/dist/svg/chevron-down.svg';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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');
|
||||||
|
const hideDetailsLabel = Translate.instant('core.errordetailshide');
|
||||||
|
const showDetailsLabel = Translate.instant('core.errordetailsshow');
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="core-error-info ${errorCode ? 'has-error-code' : ''}">
|
||||||
|
<input id="${toggleId}" type="checkbox" class="core-error-info--checkbox" />
|
||||||
|
<div class="core-error-info--content">
|
||||||
|
${errorCode ? `<p class="core-error-info--code"><strong>${errorCodeLabel}: ${errorCode}</strong></p>` : ''}
|
||||||
|
<p class="core-error-info--details">${errorDetails}</p>
|
||||||
|
</div>
|
||||||
|
<label for="${toggleId}" class="core-error-info--toggle" aria-hidden="true">
|
||||||
|
<span class="core-error-info--hide-content">
|
||||||
|
${hideDetailsLabel}
|
||||||
|
${ChevronUpSVG}
|
||||||
|
</span>
|
||||||
|
<span class="core-error-info--show-content">
|
||||||
|
${showDetailsLabel}
|
||||||
|
${ChevronDownSVG}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
// (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 <Meta<Args>> {
|
||||||
|
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> = (args) => ({
|
||||||
|
component: CoreErrorInfoComponent,
|
||||||
|
props: args,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Primary = story<Args>(Template);
|
|
@ -42,6 +42,7 @@ import { CoreForms } from '@singletons/form';
|
||||||
import { AlertButton } from '@ionic/core';
|
import { AlertButton } from '@ionic/core';
|
||||||
import { CoreSiteError } from '@classes/errors/siteerror';
|
import { CoreSiteError } from '@classes/errors/siteerror';
|
||||||
import { CoreUserSupport } from '@features/user/services/support';
|
import { CoreUserSupport } from '@features/user/services/support';
|
||||||
|
import { CoreErrorInfoComponent } from '@components/error-info/error-info';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Site (url) chooser when adding a new site.
|
* Site (url) chooser when adding a new site.
|
||||||
|
@ -382,7 +383,7 @@ export class CoreLoginSitePage implements OnInit {
|
||||||
* @param url The URL the user was trying to connect to.
|
* @param url The URL the user was trying to connect to.
|
||||||
* @param error Error to display.
|
* @param error Error to display.
|
||||||
*/
|
*/
|
||||||
protected showLoginIssue(url: string | null, error: CoreError): void {
|
protected async showLoginIssue(url: string | null, error: CoreError): Promise<void> {
|
||||||
let errorMessage = CoreDomUtils.getErrorMessage(error);
|
let errorMessage = CoreDomUtils.getErrorMessage(error);
|
||||||
let siteExists = false;
|
let siteExists = false;
|
||||||
let supportPageUrl: string | null = null;
|
let supportPageUrl: string | null = null;
|
||||||
|
@ -396,7 +397,12 @@ export class CoreLoginSitePage implements OnInit {
|
||||||
errorCode = error.errorcode;
|
errorCode = error.errorcode;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorMessage == Translate.instant('core.cannotconnecttrouble')) {
|
if (
|
||||||
|
!siteExists && (
|
||||||
|
errorMessage === Translate.instant('core.cannotconnecttrouble') ||
|
||||||
|
errorMessage === Translate.instant('core.cannotconnecttroublewithoutsupport')
|
||||||
|
)
|
||||||
|
) {
|
||||||
const found = this.sites.find((site) => site.url == url);
|
const found = this.sites.find((site) => site.url == url);
|
||||||
|
|
||||||
if (!found) {
|
if (!found) {
|
||||||
|
@ -404,10 +410,14 @@ export class CoreLoginSitePage implements OnInit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let message = '<p>' + errorMessage + '</p>';
|
errorMessage = '<p>' + errorMessage + '</p>';
|
||||||
if (!siteExists && url) {
|
if (!siteExists && url) {
|
||||||
const fullUrl = CoreUrlUtils.isAbsoluteURL(url) ? url : 'https://' + url;
|
const fullUrl = CoreUrlUtils.isAbsoluteURL(url) ? url : 'https://' + url;
|
||||||
message += '<p padding><a href="' + fullUrl + '" core-link>' + url + '</a></p>';
|
errorMessage += '<p padding><a href="' + fullUrl + '" core-link>' + url + '</a></p>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorDetails) {
|
||||||
|
errorMessage += '<div class="core-error-info-container"></div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
const buttons: AlertButton[] = [
|
const buttons: AlertButton[] = [
|
||||||
|
@ -432,11 +442,19 @@ export class CoreLoginSitePage implements OnInit {
|
||||||
];
|
];
|
||||||
|
|
||||||
// @TODO: Remove CoreSite.MINIMUM_MOODLE_VERSION, not used on translations since 3.9.0.
|
// @TODO: Remove CoreSite.MINIMUM_MOODLE_VERSION, not used on translations since 3.9.0.
|
||||||
CoreDomUtils.showAlertWithOptions({
|
const alertElement = await CoreDomUtils.showAlertWithOptions({
|
||||||
header: Translate.instant('core.cannotconnect', { $a: CoreSite.MINIMUM_MOODLE_VERSION }),
|
header: Translate.instant('core.cannotconnect', { $a: CoreSite.MINIMUM_MOODLE_VERSION }),
|
||||||
message,
|
message: errorMessage,
|
||||||
buttons,
|
buttons,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (errorDetails) {
|
||||||
|
const containerElement = alertElement.querySelector('.core-error-info-container');
|
||||||
|
|
||||||
|
if (containerElement) {
|
||||||
|
containerElement.innerHTML = CoreErrorInfoComponent.render(errorDetails, errorCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"cannotconnect": "Cannot connect",
|
"cannotconnect": "Cannot connect",
|
||||||
"cannotconnecttrouble": "We're having trouble connecting to your site.",
|
"cannotconnecttrouble": "We're having trouble connecting to your site.",
|
||||||
|
"cannotconnecttroublewithoutsupport": "We're having trouble connecting to your site, please contact your institution.",
|
||||||
"cannotconnectverify": "<strong>Please check the address is correct.</strong>",
|
"cannotconnectverify": "<strong>Please check the address is correct.</strong>",
|
||||||
"cannotdownloadfiles": "File downloading is disabled. Please contact your site administrator.",
|
"cannotdownloadfiles": "File downloading is disabled. Please contact your site administrator.",
|
||||||
"cannotinstallapk": "For security reasons, you can't install unknown apps on your device from this app. Please open the file using a browser.",
|
"cannotinstallapk": "For security reasons, you can't install unknown apps on your device from this app. Please open the file using a browser.",
|
||||||
|
@ -101,7 +102,10 @@
|
||||||
"endonesteptour": "Got it",
|
"endonesteptour": "Got it",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"errorchangecompletion": "An error occurred while changing the completion status. Please try again.",
|
"errorchangecompletion": "An error occurred while changing the completion status. Please try again.",
|
||||||
|
"errorcode": "Error code",
|
||||||
"errordeletefile": "Error deleting the file. Please try again.",
|
"errordeletefile": "Error deleting the file. Please try again.",
|
||||||
|
"errordetailshide": "Hide error details",
|
||||||
|
"errordetailsshow": "Show error details",
|
||||||
"errordownloading": "Error downloading file.",
|
"errordownloading": "Error downloading file.",
|
||||||
"errordownloadingsomefiles": "Error downloading files. Some files might be missing.",
|
"errordownloadingsomefiles": "Error downloading files. Some files might be missing.",
|
||||||
"errorfileexistssamename": "A file with this name already exists.",
|
"errorfileexistssamename": "A file with this name already exists.",
|
||||||
|
|
|
@ -342,6 +342,7 @@ export class CoreSitesProvider {
|
||||||
errorDetails,
|
errorDetails,
|
||||||
siteConfig,
|
siteConfig,
|
||||||
message: Translate.instant('core.cannotconnecttrouble'),
|
message: Translate.instant('core.cannotconnecttrouble'),
|
||||||
|
fallbackMessage: Translate.instant('core.cannotconnecttroublewithoutsupport'),
|
||||||
critical: true,
|
critical: true,
|
||||||
contactSupport: true,
|
contactSupport: true,
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,6 +20,8 @@ import { CoreEventFormAction, CoreEvents } from '@singletons/events';
|
||||||
*/
|
*/
|
||||||
export class CoreForms {
|
export class CoreForms {
|
||||||
|
|
||||||
|
private static formIds: Record<string, number> = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the data from a form. It will only collect elements that have a name.
|
* Get the data from a form. It will only collect elements that have a name.
|
||||||
*
|
*
|
||||||
|
@ -93,6 +95,18 @@ export class CoreForms {
|
||||||
}, siteId);
|
}, siteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a unique id for a form input using the given name.
|
||||||
|
*
|
||||||
|
* @param name Form input name.
|
||||||
|
* @returns Unique id.
|
||||||
|
*/
|
||||||
|
static uniqueId(name: string): string {
|
||||||
|
const count = this.formIds[name] ?? 0;
|
||||||
|
|
||||||
|
return `${name}-${this.formIds[name] = count + 1}`;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CoreFormFields<T = unknown> = Record<string, T>;
|
export type CoreFormFields<T = unknown> = Record<string, T>;
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
html {
|
||||||
|
|
||||||
|
// Spacing
|
||||||
|
@for $i from 0 to 13 {
|
||||||
|
--spacing-#{$i}: #{$i*4}px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Font sizes
|
||||||
|
--font-size-sm: 12px;
|
||||||
|
--font-size-normal: 14px;
|
||||||
|
|
||||||
|
// Radiuses
|
||||||
|
--small-radius: 4px;
|
||||||
|
--medium-radius: 8px;
|
||||||
|
--big-radius: 16px;
|
||||||
|
--huge-radius: 24px;
|
||||||
|
|
||||||
|
// A11y
|
||||||
|
--a11y-min-target-size: 44px;
|
||||||
|
|
||||||
|
}
|
|
@ -48,18 +48,12 @@ html {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accessibility vars.
|
// Accessibility vars.
|
||||||
--a11y-min-target-size: 44px;
|
|
||||||
--a11y-focus-color: var(--primary);
|
--a11y-focus-color: var(--primary);
|
||||||
--a11y-focus-width: 2px;
|
--a11y-focus-width: 2px;
|
||||||
--zoom-level: 100%;
|
--zoom-level: 100%;
|
||||||
|
|
||||||
--small-radius: 4px;
|
|
||||||
--medium-radius: 8px;
|
|
||||||
--big-radius: 16px;
|
|
||||||
--huge-radius: 24px;
|
|
||||||
|
|
||||||
--text-color: #{$text-color};
|
--text-color: #{$text-color};
|
||||||
--text-size: 14px;
|
--text-size: var(--font-size-normal);
|
||||||
--background-color: #{$background-color};
|
--background-color: #{$background-color};
|
||||||
--stroke: var(--gray-300);
|
--stroke: var(--gray-300);
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
@import "./theme.light.scss";
|
@import "./theme.light.scss";
|
||||||
@import "./theme.dark.scss";
|
@import "./theme.dark.scss";
|
||||||
@import "./theme.custom.scss";
|
@import "./theme.custom.scss";
|
||||||
|
@import "./theme.design-system.scss";
|
||||||
@import "./theme.base.scss";
|
@import "./theme.base.scss";
|
||||||
|
|
||||||
/* Components */
|
/* Components */
|
||||||
|
@ -24,6 +25,7 @@
|
||||||
@import "./components/format-text.scss";
|
@import "./components/format-text.scss";
|
||||||
@import "./components/rubrics.scss";
|
@import "./components/rubrics.scss";
|
||||||
@import "./components/mod-label.scss";
|
@import "./components/mod-label.scss";
|
||||||
|
@import "../core/components/error-info/error-info.scss";
|
||||||
|
|
||||||
/* Some styles from 3rd party libraries. */
|
/* Some styles from 3rd party libraries. */
|
||||||
@import "./bootstrap.scss";
|
@import "./bootstrap.scss";
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
// (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.
|
||||||
|
|
||||||
|
declare module '!raw-loader!*' {
|
||||||
|
const contents: string;
|
||||||
|
|
||||||
|
export = contents;
|
||||||
|
}
|
Loading…
Reference in New Issue