Move clock into shared components (#30480)

* refactor: extract `formatSeconds` from `DateUtils`

* refactor: move clock into shared-components

* refactor: update clock imports

* test(e2e): add screenshots
This commit is contained in:
Florian Duros
2025-08-05 19:04:55 +02:00
committed by GitHub
parent 24f923feac
commit 6fca4d106e
13 changed files with 133 additions and 41 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@@ -13,6 +13,8 @@ import { type Optional } from "matrix-events-sdk";
import { _t, getUserLanguage } from "./languageHandler";
import { getUserTimezone } from "./TimezoneHandler";
export { formatSeconds } from "./shared-components/utils/DateUtils";
export const MINUTE_MS = 60000;
export const HOUR_MS = MINUTE_MS * 60;
export const DAY_MS = HOUR_MS * 24;
@@ -180,31 +182,6 @@ export function formatTime(date: Date, showTwelveHour = false, locale?: string):
}).format(date);
}
export function formatSeconds(inSeconds: number): string {
const isNegative = inSeconds < 0;
inSeconds = Math.abs(inSeconds);
const hours = Math.floor(inSeconds / (60 * 60))
.toFixed(0)
.padStart(2, "0");
const minutes = Math.floor((inSeconds % (60 * 60)) / 60)
.toFixed(0)
.padStart(2, "0");
const seconds = Math.floor((inSeconds % (60 * 60)) % 60)
.toFixed(0)
.padStart(2, "0");
let output = "";
if (hours !== "00") output += `${hours}:`;
output += `${minutes}:${seconds}`;
if (isNegative) {
output = "-" + output;
}
return output;
}
export function formatTimeLeft(inSeconds: number): string {
const hours = Math.floor(inSeconds / (60 * 60)).toFixed(0);
const minutes = Math.floor((inSeconds % (60 * 60)) / 60).toFixed(0);

View File

@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React from "react";
import Clock from "./Clock";
import { Clock } from "../../../shared-components/audio/Clock";
import { type Playback } from "../../../audio/Playback";
interface IProps {

View File

@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
import React from "react";
import { type IRecordingUpdate } from "../../../audio/VoiceRecording";
import Clock from "./Clock";
import { Clock } from "../../../shared-components/audio/Clock";
import { MarkedExecution } from "../../../utils/MarkedExecution";
import { type VoiceMessageRecording } from "../../../audio/VoiceMessageRecording";

View File

@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React from "react";
import Clock from "./Clock";
import { Clock } from "../../../shared-components/audio/Clock";
import { type Playback, PlaybackState } from "../../../audio/Playback";
import { UPDATE_EVENT } from "../../../stores/AsyncStore";

View File

@@ -18,7 +18,7 @@ import { LegacyCallEventGrouperEvent } from "../../structures/LegacyCallEventGro
import AccessibleButton from "../elements/AccessibleButton";
import InfoTooltip, { InfoTooltipKind } from "../elements/InfoTooltip";
import { formatPreciseDuration } from "../../../DateUtils";
import Clock from "../audio_messages/Clock";
import { Clock } from "../../../shared-components/audio/Clock";
const MAX_NON_NARROW_WIDTH = (450 / 70) * 100;

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import type { Meta, StoryFn } from "@storybook/react-vite";
import { Clock } from "./Clock";
export default {
title: "Audio/Clock",
component: Clock,
tags: ["autodocs"],
args: {
seconds: 20,
},
} as Meta<typeof Clock>;
const Template: StoryFn<typeof Clock> = (args) => <Clock {...args} />;
export const Default = Template.bind({});
export const LotOfSeconds = Template.bind({});
LotOfSeconds.args = {
seconds: 99999999999999,
};

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
import { composeStories } from "@storybook/react-vite";
import { render } from "jest-matrix-react";
import React from "react";
import * as stories from "./Clock.stories.tsx";
const { Default, LotOfSeconds } = composeStories(stories);
describe("Clock", () => {
it("renders the clock", () => {
const { container } = render(<Default />);
expect(container).toMatchSnapshot();
});
it("renders the clock with a lot of seconds", () => {
const { container } = render(<LotOfSeconds />);
expect(container).toMatchSnapshot();
});
});

View File

@@ -1,7 +1,6 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2021-2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
@@ -9,24 +8,18 @@ Please see LICENSE files in the repository root for full details.
import React, { type HTMLProps } from "react";
import { Temporal } from "temporal-polyfill";
import { formatSeconds } from "../../../DateUtils";
import { formatSeconds } from "../../utils/DateUtils";
interface Props extends Pick<HTMLProps<HTMLSpanElement>, "aria-live" | "role"> {
export interface Props extends Pick<HTMLProps<HTMLSpanElement>, "aria-live" | "role"> {
seconds: number;
formatFn: (seconds: number) => string;
}
/**
* Clock which represents time periods rather than absolute time.
* Simply converts seconds using formatFn.
* Defaulting to formatSeconds().
* Simply converts seconds using formatSeconds().
* Note that in this case hours will not be displayed, making it possible to see "82:29".
*/
export default class Clock extends React.Component<Props> {
public static defaultProps = {
formatFn: formatSeconds,
};
export class Clock extends React.Component<Props> {
public shouldComponentUpdate(nextProps: Readonly<Props>): boolean {
const currentFloor = Math.floor(this.props.seconds);
const nextFloor = Math.floor(nextProps.seconds);
@@ -47,9 +40,10 @@ export default class Clock extends React.Component<Props> {
dateTime={this.calculateDuration(seconds)}
aria-live={this.props["aria-live"]}
role={role}
/* Keep class for backward compatibility with parent component */
className="mx_Clock"
>
{this.props.formatFn(seconds)}
{formatSeconds(seconds)}
</time>
);
}

View File

@@ -0,0 +1,23 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Clock renders the clock 1`] = `
<div>
<time
class="mx_Clock"
datetime="PT20S"
>
00:20
</time>
</div>
`;
exports[`Clock renders the clock with a lot of seconds 1`] = `
<div>
<time
class="mx_Clock"
datetime="PT27777777777H46M39S"
>
27777777777:46:39
</time>
</div>
`;

View File

@@ -0,0 +1,8 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
export { Clock } from "./Clock";

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
/**
* Formats a number of seconds into a human-readable string.
* @param inSeconds
*/
export function formatSeconds(inSeconds: number): string {
const isNegative = inSeconds < 0;
inSeconds = Math.abs(inSeconds);
const hours = Math.floor(inSeconds / (60 * 60))
.toFixed(0)
.padStart(2, "0");
const minutes = Math.floor((inSeconds % (60 * 60)) / 60)
.toFixed(0)
.padStart(2, "0");
const seconds = Math.floor((inSeconds % (60 * 60)) % 60)
.toFixed(0)
.padStart(2, "0");
let output = "";
if (hours !== "00") output += `${hours}:`;
output += `${minutes}:${seconds}`;
if (isNegative) {
output = "-" + output;
}
return output;
}