From 6fca4d106ec9ff6189d94c76589913397b383997 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Tue, 5 Aug 2025 19:04:55 +0200 Subject: [PATCH] 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 --- .../audio-clock--default-linux.png | Bin 0 -> 5380 bytes .../audio-clock--lot-of-seconds-linux.png | Bin 0 -> 6782 bytes src/DateUtils.ts | 27 +------------- .../views/audio_messages/DurationClock.tsx | 2 +- .../audio_messages/LiveRecordingClock.tsx | 2 +- .../views/audio_messages/PlaybackClock.tsx | 2 +- .../views/messages/LegacyCallEvent.tsx | 2 +- .../audio/Clock/Clock.stories.tsx | 29 +++++++++++++++ .../audio/Clock/Clock.test.tsx | 26 +++++++++++++ .../audio/Clock}/Clock.tsx | 18 +++------ .../Clock/__snapshots__/Clock.test.tsx.snap | 23 ++++++++++++ src/shared-components/audio/Clock/index.tsx | 8 ++++ src/shared-components/utils/DateUtils.ts | 35 ++++++++++++++++++ 13 files changed, 133 insertions(+), 41 deletions(-) create mode 100644 playwright/shared-component-snapshots/audio-clock--default-linux.png create mode 100644 playwright/shared-component-snapshots/audio-clock--lot-of-seconds-linux.png create mode 100644 src/shared-components/audio/Clock/Clock.stories.tsx create mode 100644 src/shared-components/audio/Clock/Clock.test.tsx rename src/{components/views/audio_messages => shared-components/audio/Clock}/Clock.tsx (76%) create mode 100644 src/shared-components/audio/Clock/__snapshots__/Clock.test.tsx.snap create mode 100644 src/shared-components/audio/Clock/index.tsx create mode 100644 src/shared-components/utils/DateUtils.ts diff --git a/playwright/shared-component-snapshots/audio-clock--default-linux.png b/playwright/shared-component-snapshots/audio-clock--default-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..be66f4b70cbd9d2171aed4c290511a8a966a1073 GIT binary patch literal 5380 zcmeHL`%jZ+6#k%~OkkL?6bF`?=wJzG35p18PbB5UZ}JJ!5e}a z=BixU5#or^7A7cFdb3@rTtq-8Qrco^rIuSMEwrV#eYihi@WYb+@}4(e-kkHCH|Kf2 zbKa}pC&v4(30wmJfN#S7y+;AS6WqG@d%1&VH~fGb0IUTP_U`$CST`k4z9P@67wAbfC+-OrM$q=YPRGO#6Lb?EIlgjza~CVeg(nwJayq< zSl)cdiHHT^9q&f3KfNlwx{fz_#cVKMjEKJ(lcd`8xaI_`&I4hzT4cQzG#~xSPSeDk zXAR3_>p>)f)Q;R$pk2G+B8X`JS^>D<u2uk0N|pBn-2)O6aY_d*aJWS z;O2im?#Rsd-2O*a6?&FFf<0+zqH-+@CiJ>?47 zGQySccnh5y$r|bf9LWuPC=oqxAMWm`qB>qUDs)_ubEciO3pdfLo>hpo+KeO3c8u|o zy;1alVH@j0Vr|ZyqEFe}hozP%?x4t+SShuqdkhQhwED4pLeVkN_1LX}XX~iyw?l|g zhuIKR_FOpEfMCSh|4Oz}4T^luwqW1!0Nw89BYh8F(oFI;+gx_eq#>8+O6%)hZ#r`e*2>2 zgFZiFQFta+#?7*gyjGO!2WOO1LMblelT(Z&>32=5Mf?bs{A638FJd!@GCW*fWSnew zH29{XEcY8n=}cL%Sun$d(wTm%I^sn=m_vMgZiV-x;Zw@xVT*|V=18iTfiA@5*o9p; z6qbf#jmTh%5oX;YFIw1_3Wjoo|J z*eYd)iLWn2Bv%GkLNAvwVHARr?4)_~t=Fr}635T+!|5_3Nj;x?<-Z>VfaHP--u$= z*}Kl*M4Q7@|0d7pIvv*4$VV-cy4A0Ot+Hw~rCp-E;8n}I@ubB z$_YcE6r!9DKh48uOq=6pkh`3(2+S?c2~9Qautt>J!GcXoqjuWwWk{&8K`BVH*wTcy z-#sRB@^od`ljqz)J`4UPz8oJPPixv9GHOxZBE!k}XY(YYed6t&nR+X<9i9gk85Kj$ zC#u!{mrcDqvWd#In$^c^QwTj-j2JvvRc}lde>m5w8by%{Du!5`5B`3|Q%|@RphN)d zz@#q)pq-^|(!J<{3Ghod|Zz9?Ye;faM#nU4q~e1eYMJydW&cQ`rBFtL7+hmUlX;L} a3j)|Yhtiybk%8bBAYotPUe>p1rT+lu15Ypj literal 0 HcmV?d00001 diff --git a/playwright/shared-component-snapshots/audio-clock--lot-of-seconds-linux.png b/playwright/shared-component-snapshots/audio-clock--lot-of-seconds-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..b4879e1a0c7adbd86d838e3a79c5628bdd2960f3 GIT binary patch literal 6782 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 + +const Template: StoryFn = (args) => ; + +export const Default = Template.bind({}); + +export const LotOfSeconds = Template.bind({}); +LotOfSeconds.args = { + seconds: 99999999999999, +}; diff --git a/src/shared-components/audio/Clock/Clock.test.tsx b/src/shared-components/audio/Clock/Clock.test.tsx new file mode 100644 index 0000000000..fdbbf49518 --- /dev/null +++ b/src/shared-components/audio/Clock/Clock.test.tsx @@ -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(); + expect(container).toMatchSnapshot(); + }); + + it("renders the clock with a lot of seconds", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/src/components/views/audio_messages/Clock.tsx b/src/shared-components/audio/Clock/Clock.tsx similarity index 76% rename from src/components/views/audio_messages/Clock.tsx rename to src/shared-components/audio/Clock/Clock.tsx index d9f1a3d6c5..9aa114e415 100644 --- a/src/components/views/audio_messages/Clock.tsx +++ b/src/shared-components/audio/Clock/Clock.tsx @@ -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, "aria-live" | "role"> { +export interface Props extends Pick, "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 { - public static defaultProps = { - formatFn: formatSeconds, - }; - +export class Clock extends React.Component { public shouldComponentUpdate(nextProps: Readonly): 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 { 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)} ); } diff --git a/src/shared-components/audio/Clock/__snapshots__/Clock.test.tsx.snap b/src/shared-components/audio/Clock/__snapshots__/Clock.test.tsx.snap new file mode 100644 index 0000000000..2fdcedd7c3 --- /dev/null +++ b/src/shared-components/audio/Clock/__snapshots__/Clock.test.tsx.snap @@ -0,0 +1,23 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Clock renders the clock 1`] = ` +
+ +
+`; + +exports[`Clock renders the clock with a lot of seconds 1`] = ` +
+ +
+`; diff --git a/src/shared-components/audio/Clock/index.tsx b/src/shared-components/audio/Clock/index.tsx new file mode 100644 index 0000000000..bc261bb283 --- /dev/null +++ b/src/shared-components/audio/Clock/index.tsx @@ -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"; diff --git a/src/shared-components/utils/DateUtils.ts b/src/shared-components/utils/DateUtils.ts new file mode 100644 index 0000000000..146aeecbd2 --- /dev/null +++ b/src/shared-components/utils/DateUtils.ts @@ -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; +}