mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-13 17:05:43 +01:00
Read all errors with screen reader (#1839)
Make error notifications accessible for screen readers.
This commit is contained in:
2
gradle/changelog/screenreader_errors.yaml
Normal file
2
gradle/changelog/screenreader_errors.yaml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
- type: added
|
||||||
|
description: Read all errors with screen readers ([#1839](https://github.com/scm-manager/scm-manager/pull/1839))
|
||||||
@@ -21,37 +21,19 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
import React from "react";
|
import React, { FC } from "react";
|
||||||
import { BackendError } from "@scm-manager/ui-api";
|
import { BackendError } from "@scm-manager/ui-api";
|
||||||
import Notification from "./Notification";
|
import Notification from "./Notification";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { WithTranslation, withTranslation } from "react-i18next";
|
type Props = {
|
||||||
|
|
||||||
type Props = WithTranslation & {
|
|
||||||
error: BackendError;
|
error: BackendError;
|
||||||
};
|
};
|
||||||
|
|
||||||
class BackendErrorNotification extends React.Component<Props> {
|
const BackendErrorNotification: FC<Props> = ({ error }) => {
|
||||||
constructor(props: Props) {
|
const [t] = useTranslation("plugins");
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
const renderErrorName = () => {
|
||||||
return (
|
|
||||||
<Notification type="danger">
|
|
||||||
<div className="content">
|
|
||||||
<p className="subtitle">{this.renderErrorName()}</p>
|
|
||||||
<p>{this.renderErrorDescription()}</p>
|
|
||||||
{this.renderAdditionalMessages()}
|
|
||||||
<p>{this.renderViolations()}</p>
|
|
||||||
{this.renderMetadata()}
|
|
||||||
</div>
|
|
||||||
</Notification>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderErrorName = () => {
|
|
||||||
const { error, t } = this.props;
|
|
||||||
const translation = t(`errors.${error.errorCode}.displayName`);
|
const translation = t(`errors.${error.errorCode}.displayName`);
|
||||||
if (translation === error.errorCode) {
|
if (translation === error.errorCode) {
|
||||||
return error.message;
|
return error.message;
|
||||||
@@ -59,8 +41,7 @@ class BackendErrorNotification extends React.Component<Props> {
|
|||||||
return translation;
|
return translation;
|
||||||
};
|
};
|
||||||
|
|
||||||
renderErrorDescription = () => {
|
const renderErrorDescription = () => {
|
||||||
const { error, t } = this.props;
|
|
||||||
const translation = t(`errors.${error.errorCode}.description`);
|
const translation = t(`errors.${error.errorCode}.description`);
|
||||||
if (translation === error.errorCode) {
|
if (translation === error.errorCode) {
|
||||||
return "";
|
return "";
|
||||||
@@ -68,17 +49,16 @@ class BackendErrorNotification extends React.Component<Props> {
|
|||||||
return translation;
|
return translation;
|
||||||
};
|
};
|
||||||
|
|
||||||
renderAdditionalMessages = () => {
|
const renderAdditionalMessages = () => {
|
||||||
const { error, t } = this.props;
|
|
||||||
if (error.additionalMessages) {
|
if (error.additionalMessages) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<hr />
|
<hr />
|
||||||
{error.additionalMessages
|
{error.additionalMessages
|
||||||
.map(additionalMessage =>
|
.map((additionalMessage) =>
|
||||||
additionalMessage.key ? t(`errors.${additionalMessage.key}.description`) : additionalMessage.message
|
additionalMessage.key ? t(`errors.${additionalMessage.key}.description`) : additionalMessage.message
|
||||||
)
|
)
|
||||||
.map(message => (
|
.map((message) => (
|
||||||
<p>{message}</p>
|
<p>{message}</p>
|
||||||
))}
|
))}
|
||||||
<hr />
|
<hr />
|
||||||
@@ -87,8 +67,7 @@ class BackendErrorNotification extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
renderViolations = () => {
|
const renderViolations = () => {
|
||||||
const { error, t } = this.props;
|
|
||||||
if (error.violations) {
|
if (error.violations) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -110,17 +89,16 @@ class BackendErrorNotification extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
renderMetadata = () => {
|
const renderMetadata = () => {
|
||||||
const { error, t } = this.props;
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{this.renderContext()}
|
{renderContext()}
|
||||||
{this.renderMoreInformationLink()}
|
{renderMoreInformationLink()}
|
||||||
<div className="level is-size-7">
|
<div className="level is-size-7">
|
||||||
<div className="left">
|
<div className="left" aria-hidden={true}>
|
||||||
{t("errors.transactionId")} {error.transactionId}
|
{t("errors.transactionId")} {error.transactionId}
|
||||||
</div>
|
</div>
|
||||||
<div className="right">
|
<div className="right" aria-hidden={true}>
|
||||||
{t("errors.errorCode")} {error.errorCode}
|
{t("errors.errorCode")} {error.errorCode}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -128,8 +106,7 @@ class BackendErrorNotification extends React.Component<Props> {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
renderContext = () => {
|
const renderContext = () => {
|
||||||
const { error, t } = this.props;
|
|
||||||
if (error.context) {
|
if (error.context) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -150,19 +127,34 @@ class BackendErrorNotification extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
renderMoreInformationLink = () => {
|
const renderMoreInformationLink = () => {
|
||||||
const { error, t } = this.props;
|
|
||||||
if (error.url) {
|
if (error.url) {
|
||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
{t("errors.moreInfo")}{" "}
|
{t("errors.moreInfo")}{" "}
|
||||||
<a href={error.url} target="_blank">
|
<a href={error.url} target="_blank" rel="noreferrer" aria-label={t("error.link")}>
|
||||||
{error.errorCode}
|
{error.errorCode}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
export default withTranslation("plugins")(BackendErrorNotification);
|
return (
|
||||||
|
<Notification type="danger" role="alert">
|
||||||
|
<div className="content">
|
||||||
|
<p className="subtitle">
|
||||||
|
{t("error.subtitle")}
|
||||||
|
{": "}
|
||||||
|
{renderErrorName()}
|
||||||
|
</p>
|
||||||
|
<p>{renderErrorDescription()}</p>
|
||||||
|
{renderAdditionalMessages()}
|
||||||
|
<p>{renderViolations()}</p>
|
||||||
|
{renderMetadata()}
|
||||||
|
</div>
|
||||||
|
</Notification>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BackendErrorNotification;
|
||||||
|
|||||||
@@ -40,6 +40,16 @@ const LoginLink: FC = () => {
|
|||||||
return <a href={urls.withContextPath(`/login?from=${from}`)}>{t("errorNotification.loginLink")}</a>;
|
return <a href={urls.withContextPath(`/login?from=${from}`)}>{t("errorNotification.loginLink")}</a>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const BasicErrorMessage: FC = ({ children }) => {
|
||||||
|
const [t] = useTranslation("commons");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Notification type="danger" role="alert">
|
||||||
|
<strong>{t("errorNotification.prefix")}:</strong> {children}
|
||||||
|
</Notification>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const ErrorNotification: FC<Props> = ({ error }) => {
|
const ErrorNotification: FC<Props> = ({ error }) => {
|
||||||
const [t] = useTranslation("commons");
|
const [t] = useTranslation("commons");
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -47,22 +57,14 @@ const ErrorNotification: FC<Props> = ({ error }) => {
|
|||||||
return <BackendErrorNotification error={error} />;
|
return <BackendErrorNotification error={error} />;
|
||||||
} else if (error instanceof UnauthorizedError) {
|
} else if (error instanceof UnauthorizedError) {
|
||||||
return (
|
return (
|
||||||
<Notification type="danger">
|
<BasicErrorMessage>
|
||||||
<strong>{t("errorNotification.prefix")}:</strong> {t("errorNotification.timeout")} <LoginLink />
|
{t("errorNotification.timeout")} <LoginLink />
|
||||||
</Notification>
|
</BasicErrorMessage>
|
||||||
);
|
);
|
||||||
} else if (error instanceof ForbiddenError) {
|
} else if (error instanceof ForbiddenError) {
|
||||||
return (
|
return <BasicErrorMessage>{t("errorNotification.forbidden")}</BasicErrorMessage>;
|
||||||
<Notification type="danger">
|
|
||||||
<strong>{t("errorNotification.prefix")}:</strong> {t("errorNotification.forbidden")}
|
|
||||||
</Notification>
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
return (
|
return <BasicErrorMessage>{error.message}</BasicErrorMessage>;
|
||||||
<Notification type="danger">
|
|
||||||
<strong>{t("errorNotification.prefix")}:</strong> {error.message}
|
|
||||||
</Notification>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
import React, { ReactNode } from "react";
|
import React, { FC, ReactNode } from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
type NotificationType = "primary" | "info" | "success" | "warning" | "danger" | "inherit";
|
type NotificationType = "primary" | "info" | "success" | "warning" | "danger" | "inherit";
|
||||||
@@ -31,33 +31,25 @@ type Props = {
|
|||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
className?: string;
|
className?: string;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
|
role?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Notification extends React.Component<Props> {
|
const Notification: FC<Props> = ({ type = "info", onClose, className, children, role }) => {
|
||||||
static defaultProps = {
|
const renderCloseButton = () => {
|
||||||
type: "info"
|
|
||||||
};
|
|
||||||
|
|
||||||
renderCloseButton() {
|
|
||||||
const { onClose } = this.props;
|
|
||||||
if (onClose) {
|
if (onClose) {
|
||||||
return <button className="delete" onClick={onClose} />;
|
return <button className="delete" onClick={onClose} />;
|
||||||
}
|
}
|
||||||
return "";
|
return null;
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
|
||||||
const { type, className, children } = this.props;
|
|
||||||
|
|
||||||
const color = type !== "inherit" ? "is-" + type : "";
|
const color = type !== "inherit" ? "is-" + type : "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames("notification", color, className)}>
|
<div className={classNames("notification", color, className)} role={role}>
|
||||||
{this.renderCloseButton()}
|
{renderCloseButton()}
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
export default Notification;
|
export default Notification;
|
||||||
|
|||||||
@@ -73471,7 +73471,6 @@ exports[`Storyshots Notification Danger 1`] = `
|
|||||||
<div
|
<div
|
||||||
className="notification is-danger"
|
className="notification is-danger"
|
||||||
>
|
>
|
||||||
|
|
||||||
Cleverness nuclear genuine static irresponsibility invited President Zaphod
|
Cleverness nuclear genuine static irresponsibility invited President Zaphod
|
||||||
Beeblebrox hyperspace ship. Another custard through computer-generated universe
|
Beeblebrox hyperspace ship. Another custard through computer-generated universe
|
||||||
shapes field strong disaster parties Russell’s ancestors infinite colour
|
shapes field strong disaster parties Russell’s ancestors infinite colour
|
||||||
@@ -73487,7 +73486,6 @@ exports[`Storyshots Notification Info 1`] = `
|
|||||||
<div
|
<div
|
||||||
className="notification is-info"
|
className="notification is-info"
|
||||||
>
|
>
|
||||||
|
|
||||||
Cleverness nuclear genuine static irresponsibility invited President Zaphod
|
Cleverness nuclear genuine static irresponsibility invited President Zaphod
|
||||||
Beeblebrox hyperspace ship. Another custard through computer-generated universe
|
Beeblebrox hyperspace ship. Another custard through computer-generated universe
|
||||||
shapes field strong disaster parties Russell’s ancestors infinite colour
|
shapes field strong disaster parties Russell’s ancestors infinite colour
|
||||||
@@ -73503,7 +73501,6 @@ exports[`Storyshots Notification Primary 1`] = `
|
|||||||
<div
|
<div
|
||||||
className="notification is-primary"
|
className="notification is-primary"
|
||||||
>
|
>
|
||||||
|
|
||||||
Cleverness nuclear genuine static irresponsibility invited President Zaphod
|
Cleverness nuclear genuine static irresponsibility invited President Zaphod
|
||||||
Beeblebrox hyperspace ship. Another custard through computer-generated universe
|
Beeblebrox hyperspace ship. Another custard through computer-generated universe
|
||||||
shapes field strong disaster parties Russell’s ancestors infinite colour
|
shapes field strong disaster parties Russell’s ancestors infinite colour
|
||||||
@@ -73519,7 +73516,6 @@ exports[`Storyshots Notification Success 1`] = `
|
|||||||
<div
|
<div
|
||||||
className="notification is-success"
|
className="notification is-success"
|
||||||
>
|
>
|
||||||
|
|
||||||
Cleverness nuclear genuine static irresponsibility invited President Zaphod
|
Cleverness nuclear genuine static irresponsibility invited President Zaphod
|
||||||
Beeblebrox hyperspace ship. Another custard through computer-generated universe
|
Beeblebrox hyperspace ship. Another custard through computer-generated universe
|
||||||
shapes field strong disaster parties Russell’s ancestors infinite colour
|
shapes field strong disaster parties Russell’s ancestors infinite colour
|
||||||
@@ -73535,7 +73531,6 @@ exports[`Storyshots Notification Warning 1`] = `
|
|||||||
<div
|
<div
|
||||||
className="notification is-warning"
|
className="notification is-warning"
|
||||||
>
|
>
|
||||||
|
|
||||||
Cleverness nuclear genuine static irresponsibility invited President Zaphod
|
Cleverness nuclear genuine static irresponsibility invited President Zaphod
|
||||||
Beeblebrox hyperspace ship. Another custard through computer-generated universe
|
Beeblebrox hyperspace ship. Another custard through computer-generated universe
|
||||||
shapes field strong disaster parties Russell’s ancestors infinite colour
|
shapes field strong disaster parties Russell’s ancestors infinite colour
|
||||||
@@ -89309,7 +89304,6 @@ exports[`Storyshots Table|Table Empty 1`] = `
|
|||||||
<div
|
<div
|
||||||
className="notification is-info"
|
className="notification is-info"
|
||||||
>
|
>
|
||||||
|
|
||||||
No data found.
|
No data found.
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -2294,7 +2294,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-one-third">
|
<div class="column is-one-third">
|
||||||
<div class="notification is-danger">
|
<div class="notification is-danger" role="alert">
|
||||||
<button class="delete"> </button>
|
<button class="delete"> </button>
|
||||||
<strong>Danger</strong><br>
|
<strong>Danger</strong><br>
|
||||||
Primar lorem ipsum dolor sit amet, consectetur
|
Primar lorem ipsum dolor sit amet, consectetur
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
{
|
{
|
||||||
|
"error": {
|
||||||
|
"subtitle": "Fehler",
|
||||||
|
"link": "Link zu weiteren Informationen"
|
||||||
|
},
|
||||||
"changeset": {
|
"changeset": {
|
||||||
"contributor": {
|
"contributor": {
|
||||||
"type": {
|
"type": {
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
{
|
{
|
||||||
|
"error": {
|
||||||
|
"subtitle": "Error",
|
||||||
|
"link": "Link for more information"
|
||||||
|
},
|
||||||
"changeset": {
|
"changeset": {
|
||||||
"contributor": {
|
"contributor": {
|
||||||
"type": {
|
"type": {
|
||||||
@@ -471,4 +475,3 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
{{$content}}
|
{{$content}}
|
||||||
<h2 class="subtitle">An error occurred during SCM-Manager startup.</h2>
|
<h2 class="subtitle">An error occurred during SCM-Manager startup.</h2>
|
||||||
|
|
||||||
<div class="notification is-danger">
|
<div class="notification is-danger" role="alert">
|
||||||
<pre>
|
<pre>
|
||||||
{{ error }}
|
{{ error }}
|
||||||
</pre>
|
</pre>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
{{$content}}
|
{{$content}}
|
||||||
<h2 class="subtitle">An error occurred during SCM-Manager startup.</h2>
|
<h2 class="subtitle">An error occurred during SCM-Manager startup.</h2>
|
||||||
|
|
||||||
<p class="notification is-danger">
|
<p class="notification is-danger" role="alert">
|
||||||
We cannot migrate your SCM-Manager 1 installation,
|
We cannot migrate your SCM-Manager 1 installation,
|
||||||
because the version is too old.<br />
|
because the version is too old.<br />
|
||||||
Please migrate to version 1.60 or newer, before migration to 2.x.
|
Please migrate to version 1.60 or newer, before migration to 2.x.
|
||||||
|
|||||||
Reference in New Issue
Block a user