Merge branch 'develop' into dbkr/stateafter

This commit is contained in:
Michael Telatynski 2024-11-08 09:58:00 +00:00 committed by GitHub
commit cb0d72fc2f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 200 additions and 135 deletions

View file

@ -8,10 +8,10 @@ Please see LICENSE files in the repository root for full details.
import React, { ReactNode } from "react";
import { Tooltip } from "@vector-im/compound-web";
import { RestartIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import AccessibleButton from "../../../views/elements/AccessibleButton";
import { Icon as EMailPromptIcon } from "../../../../../res/img/element-icons/email-prompt.svg";
import { Icon as RetryIcon } from "../../../../../res/img/compound/retry-16px.svg";
import { _t } from "../../../../languageHandler";
import { useTimeoutToggle } from "../../../../hooks/useTimeoutToggle";
import { ErrorMessage } from "../../ErrorMessage";
@ -60,7 +60,7 @@ export const CheckEmail: React.FC<CheckEmailProps> = ({
<span className="mx_VerifyEMailDialog_text-light">{_t("auth|check_email_resend_prompt")}</span>
<Tooltip description={_t("auth|check_email_resend_tooltip")} placement="top" open={tooltipVisible}>
<AccessibleButton className="mx_AuthBody_resend-button" kind="link" onClick={onResendClickFn}>
<RetryIcon className="mx_Icon mx_Icon_16" />
<RestartIcon className="mx_Icon mx_Icon_16" />
{_t("action|resend")}
</AccessibleButton>
</Tooltip>

View file

@ -8,10 +8,10 @@ Please see LICENSE files in the repository root for full details.
import React, { ReactNode } from "react";
import { Tooltip } from "@vector-im/compound-web";
import { RestartIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { _t } from "../../../../languageHandler";
import AccessibleButton from "../../../views/elements/AccessibleButton";
import { Icon as RetryIcon } from "../../../../../res/img/compound/retry-16px.svg";
import { Icon as EmailPromptIcon } from "../../../../../res/img/element-icons/email-prompt.svg";
import { useTimeoutToggle } from "../../../../hooks/useTimeoutToggle";
import { ErrorMessage } from "../../ErrorMessage";
@ -59,7 +59,7 @@ export const VerifyEmailModal: React.FC<Props> = ({
<span className="mx_VerifyEMailDialog_text-light">{_t("auth|check_email_resend_prompt")}</span>
<Tooltip description={_t("auth|check_email_resend_tooltip")} placement="top" open={tooltipVisible}>
<AccessibleButton className="mx_AuthBody_resend-button" kind="link" onClick={onResendClickFn}>
<RetryIcon className="mx_Icon mx_Icon_16" />
<RestartIcon className="mx_Icon mx_Icon_16" />
{_t("action|resend")}
</AccessibleButton>
</Tooltip>

View file

@ -12,6 +12,7 @@ import { Room, EventType } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { sleep } from "matrix-js-sdk/src/utils";
import { logger } from "matrix-js-sdk/src/logger";
import { ErrorIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { _t, _td, TranslationKey } from "../../../languageHandler";
import BaseDialog from "./BaseDialog";
@ -34,7 +35,6 @@ import LazyRenderList from "../elements/LazyRenderList";
import { useSettingValue } from "../../../hooks/useSettings";
import { filterBoolean } from "../../../utils/arrays";
import { NonEmptyArray } from "../../../@types/common";
import WarningBadgeSvg from "../../../../res/img/element-icons/warning-badge.svg";
// These values match CSS
const ROW_HEIGHT = 32 + 12;
@ -229,7 +229,7 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
if (error) {
footer = (
<>
<img src={WarningBadgeSvg} height="24" width="24" alt="" />
<ErrorIcon height="24px" width="24px" />
<span className="mx_AddExistingToSpaceDialog_error">
<div className="mx_AddExistingToSpaceDialog_errorHeading">

View file

@ -22,6 +22,7 @@ import {
WidgetApiFromWidgetAction,
WidgetKind,
} from "matrix-widget-api";
import { ErrorIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import BaseDialog from "./BaseDialog";
import { _t, getUserLanguage } from "../../../languageHandler";
@ -33,7 +34,6 @@ import { arrayFastClone } from "../../../utils/arrays";
import { ElementWidget } from "../../../stores/widgets/StopGapWidget";
import { ELEMENT_CLIENT_ID } from "../../../identifiers";
import SettingsStore from "../../../settings/SettingsStore";
import WarningBadgeSvg from "../../../../res/img/element-icons/warning-badge.svg";
interface IProps {
widgetDefinition: IModalWidgetOpenRequestData;
@ -186,7 +186,7 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
onFinished={this.props.onFinished}
>
<div className="mx_ModalWidgetDialog_warning">
<img src={WarningBadgeSvg} height="16" width="16" alt="" />
<ErrorIcon width="16px" height="16px" />
{_t("widget|modal_data_warning", {
widgetDomain: parsed.hostname,
})}

View file

@ -8,9 +8,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import React, { createRef, CSSProperties } from "react";
import React, { createRef, CSSProperties, useRef, useState } from "react";
import FocusLock from "react-focus-lock";
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
import { MatrixEvent, parseErrorResponse } from "matrix-js-sdk/src/matrix";
import { _t } from "../../../languageHandler";
import MemberAvatar from "../avatars/MemberAvatar";
@ -30,6 +30,9 @@ import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
import { presentableTextForFile } from "../../../utils/FileUtils";
import AccessibleButton from "./AccessibleButton";
import Modal from "../../../Modal";
import ErrorDialog from "../dialogs/ErrorDialog";
import { FileDownloader } from "../../../utils/FileDownloader";
// Max scale to keep gaps around the image
const MAX_SCALE = 0.95;
@ -309,15 +312,6 @@ export default class ImageView extends React.Component<IProps, IState> {
this.setZoomAndRotation(cur + 90);
};
private onDownloadClick = (): void => {
const a = document.createElement("a");
a.href = this.props.src;
if (this.props.name) a.download = this.props.name;
a.target = "_blank";
a.rel = "noreferrer noopener";
a.click();
};
private onOpenContextMenu = (): void => {
this.setState({
contextMenuDisplayed: true,
@ -555,11 +549,7 @@ export default class ImageView extends React.Component<IProps, IState> {
title={_t("lightbox|rotate_right")}
onClick={this.onRotateClockwiseClick}
/>
<AccessibleButton
className="mx_ImageView_button mx_ImageView_button_download"
title={_t("action|download")}
onClick={this.onDownloadClick}
/>
<DownloadButton url={this.props.src} fileName={this.props.name} />
{contextMenuButton}
<AccessibleButton
className="mx_ImageView_button mx_ImageView_button_close"
@ -591,3 +581,61 @@ export default class ImageView extends React.Component<IProps, IState> {
);
}
}
function DownloadButton({ url, fileName }: { url: string; fileName?: string }): JSX.Element {
const downloader = useRef(new FileDownloader()).current;
const [loading, setLoading] = useState(false);
const blobRef = useRef<Blob>();
function showError(e: unknown): void {
Modal.createDialog(ErrorDialog, {
title: _t("timeline|download_failed"),
description: (
<>
<div>{_t("timeline|download_failed_description")}</div>
<div>{e instanceof Error ? e.toString() : ""}</div>
</>
),
});
setLoading(false);
}
const onDownloadClick = async (): Promise<void> => {
try {
if (loading) return;
setLoading(true);
if (blobRef.current) {
// Cheat and trigger a download, again.
return downloadBlob(blobRef.current);
}
const res = await fetch(url);
if (!res.ok) {
throw parseErrorResponse(res, await res.text());
}
const blob = await res.blob();
blobRef.current = blob;
await downloadBlob(blob);
} catch (e) {
showError(e);
}
};
async function downloadBlob(blob: Blob): Promise<void> {
await downloader.download({
blob,
name: fileName ?? _t("common|image"),
});
setLoading(false);
}
return (
<AccessibleButton
className="mx_ImageView_button mx_ImageView_button_download"
title={loading ? _t("timeline|download_action_downloading") : _t("action|download")}
onClick={onDownloadClick}
disabled={loading}
/>
);
}

View file

@ -8,8 +8,8 @@ Please see LICENSE files in the repository root for full details.
import React from "react";
import classNames from "classnames";
import { ErrorIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { Icon as WarningBadge } from "../../../../res/img/element-icons/warning-badge.svg";
import { _t } from "../../../languageHandler";
import { getLocationShareErrorMessage, LocationShareError } from "../../../utils/location";
import AccessibleButton from "../elements/AccessibleButton";
@ -29,7 +29,7 @@ export const MapError: React.FC<MapErrorProps> = ({ error, isMinimised, classNam
className={classNames("mx_MapError", className, { mx_MapError_isMinimised: isMinimised })}
onClick={onClick}
>
<WarningBadge className="mx_MapError_icon" />
<ErrorIcon className="mx_MapError_icon" />
<Heading className="mx_MapError_heading" size="3">
{_t("location_sharing|failed_load_map")}
</Heading>

View file

@ -27,11 +27,11 @@ import {
OverflowHorizontalIcon,
ReplyIcon,
DeleteIcon,
RestartIcon,
} from "@vector-im/compound-design-tokens/assets/web/icons";
import { Icon as EditIcon } from "../../../../res/img/element-icons/room/message-bar/edit.svg";
import { Icon as EmojiIcon } from "../../../../res/img/element-icons/room/message-bar/emoji.svg";
import { Icon as ResendIcon } from "../../../../res/img/element-icons/retry.svg";
import { Icon as ThreadIcon } from "../../../../res/img/element-icons/message/thread.svg";
import { Icon as ExpandMessageIcon } from "../../../../res/img/element-icons/expand-message.svg";
import { Icon as CollapseMessageIcon } from "../../../../res/img/element-icons/collapse-message.svg";
@ -475,14 +475,14 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
0,
0,
<RovingAccessibleButton
className="mx_MessageActionBar_iconButton"
className="mx_MessageActionBar_iconButton mx_MessageActionBar_retryButton"
title={_t("action|retry")}
onClick={this.onResendClick}
onContextMenu={this.onResendClick}
key="resend"
placement="left"
>
<ResendIcon />
<RestartIcon />
</RovingAccessibleButton>,
);