From e0f5660ddd52dcd22693fb79284b98327bb31298 Mon Sep 17 00:00:00 2001 From: Kasper Birch Date: Tue, 19 Nov 2024 14:29:14 +0100 Subject: [PATCH 01/24] Add `Reader` + Story The `Reader` component supports both teaser and reserved material. Use the `identifier` for teaser and `orderId` for reserved. To load the reader, it is necessary to attach `readerAssets` to the `` dynamically. A function called `appendAsset` has been introduced to manage this process. Currently, there is a problem where `readerAssets` need to be reloaded each time the reader is initialized. As a temporary workaround, `window.location.reload();` is being used. A better solution will be implemented in the future. --- .../reader-player/Reader.stories.tsx | 45 +++++++++++++ src/components/reader-player/Reader.tsx | 46 ++++++++++++++ src/components/reader-player/helper.ts | 63 +++++++++++++++++++ 3 files changed, 154 insertions(+) create mode 100644 src/components/reader-player/Reader.stories.tsx create mode 100644 src/components/reader-player/Reader.tsx create mode 100644 src/components/reader-player/helper.ts diff --git a/src/components/reader-player/Reader.stories.tsx b/src/components/reader-player/Reader.stories.tsx new file mode 100644 index 0000000000..f7c9ccf6b3 --- /dev/null +++ b/src/components/reader-player/Reader.stories.tsx @@ -0,0 +1,45 @@ +import React from "react"; +import type { Meta, StoryObj } from "@storybook/react"; +import Store from "../store"; +import Reader from "./Reader"; + +const meta: Meta = { + title: "Components/Reader", + component: Reader, + parameters: { + layout: "centered" + }, + argTypes: { + identifier: { + control: { type: "text" } + }, + orderId: { + control: { type: "text" } + } + } +}; + +export default meta; + +type Story = StoryObj; + +export const WithIdentifier: Story = { + render: () => ( + +
+ +
+
+ ) +}; + +// Works only if the matrial is reserved +export const WithOrderId: Story = { + render: () => ( + +
+ +
+
+ ) +}; diff --git a/src/components/reader-player/Reader.tsx b/src/components/reader-player/Reader.tsx new file mode 100644 index 0000000000..acb78dfcd7 --- /dev/null +++ b/src/components/reader-player/Reader.tsx @@ -0,0 +1,46 @@ +import React, { useEffect } from "react"; +import { appendAsset, readerAssets, removeAppendedAssets } from "./helper"; + +type ReaderType = + | { identifier: string; orderId?: never } + | { identifier?: never; orderId: string }; + +const Reader = ({ identifier, orderId }: ReaderType) => { + useEffect(() => { + readerAssets.forEach(appendAsset); + + return () => { + removeAppendedAssets(); + // TODO: Temporary workaround to reload the page when the reader is closed. + // The issue seems to be related to browser caching the reader, preventing it from properly refreshing. + // Replace this with a better solution in the future. + window.location.reload(); + }; + }, [identifier, orderId]); + + if (orderId) { + return ( +
+ ); + } + + if (identifier) { + return ( +
+ ); + } + + return null; +}; + +export default Reader; diff --git a/src/components/reader-player/helper.ts b/src/components/reader-player/helper.ts new file mode 100644 index 0000000000..ddf6e72f6e --- /dev/null +++ b/src/components/reader-player/helper.ts @@ -0,0 +1,63 @@ +type AssetType = { + src: string; + type: "script" | "link"; + id: string; +}; + +export const readerAssets: AssetType[] = [ + { + src: "https://reader.pubhub.dk/2.2.0/js/chunk-vendors.js", + type: "script", + id: "reader-chunk-vendors-js" + }, + { + src: "https://reader.pubhub.dk/2.2.0/js/app.js", + type: "script", + id: "reader-app-js" + }, + { + src: "https://reader.pubhub.dk/2.2.0/css/chunk-vendors.css", + type: "link", + id: "reader-chunk-vendors-css" + }, + { + src: "https://reader.pubhub.dk/2.2.0/css/app.css", + type: "link", + id: "reader-app-css" + } +]; + +const appendedAssets = new Set(); + +export const appendAsset = ({ src, type, id }: AssetType) => { + if (type === "script") { + const scriptElement = document.createElement("script"); + scriptElement.src = src; + scriptElement.defer = true; + scriptElement.async = false; + scriptElement.type = "module"; + scriptElement.id = id; + document.head.appendChild(scriptElement); + appendedAssets.add(scriptElement); + } + + if (type === "link") { + const linkElement = document.createElement("link"); + linkElement.href = src; + linkElement.rel = "stylesheet"; + linkElement.id = id; + document.head.appendChild(linkElement); + appendedAssets.add(linkElement); + } +}; + +export const removeAppendedAssets = () => { + appendedAssets.forEach((element) => { + if (document.head.contains(element)) { + document.head.removeChild(element); + } + appendedAssets.delete(element); + }); +}; + +export default {}; From b22cd7b6c8d1879615d47e4dc29c8204c82751de Mon Sep 17 00:00:00 2001 From: Kasper Birch Date: Tue, 19 Nov 2024 14:49:51 +0100 Subject: [PATCH 02/24] Add "full screen" modal The `Reader` needs to be shown in a full-screen modal. Note: It does not work with or need `FocusTrap`. I have tested a proof-of-concept where it functions as expected. You can navigate pages using the arrow keys and close the modal with the Escape key. --- src/core/utils/modal.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/core/utils/modal.tsx b/src/core/utils/modal.tsx index 69c5cc01cb..e81307910c 100644 --- a/src/core/utils/modal.tsx +++ b/src/core/utils/modal.tsx @@ -24,6 +24,7 @@ type ModalProps = { eventCallbacks?: { close?: () => void; }; + isFullScreen?: boolean; }; export interface ModalIdsProps { @@ -40,11 +41,13 @@ function Modal({ classNames, isSlider, dataCy = "modal", - eventCallbacks + eventCallbacks, + isFullScreen }: ModalProps) { const dispatch = useDispatch(); const { modalIds } = useSelector((s: ModalIdsProps) => s.modal); + useEffect(() => { const searchParams = new URLSearchParams(window.location.search); // Deep link stuff: if the id is in the url, open the modal @@ -80,6 +83,10 @@ function Modal({ dispatch(closeModal({ modalId })); }; + if (isFullScreen) { + return
{children}
; + } + return ( Date: Tue, 19 Nov 2024 15:11:27 +0100 Subject: [PATCH 03/24] Implement "Try E-Book" Reader for E-Book Materials This commit introduces functionality to try e-books. To support closing the reader via the close/back button, the `closeModal` method was added to the `window` object and invoked using `javascript:window.closeModal('reader-modal-${identifier}')`. --- src/apps/material/material.entry.tsx | 2 ++ src/apps/material/material.stories.tsx | 10 +++++++ src/apps/material/material.tsx | 5 ++++ .../material/Reader-modal/ReaderModal.tsx | 27 +++++++++++++++++ .../online/MaterialButtonReaderTeaser.tsx | 30 +++++++++++++++++++ .../online/MaterialButtonsOnline.tsx | 25 ++++++++++------ src/components/reader-player/Reader.tsx | 4 +++ src/core/utils/modal.tsx | 16 ++++++++++ 8 files changed, 110 insertions(+), 9 deletions(-) create mode 100644 src/components/material/Reader-modal/ReaderModal.tsx create mode 100644 src/components/material/material-buttons/online/MaterialButtonReaderTeaser.tsx diff --git a/src/apps/material/material.entry.tsx b/src/apps/material/material.entry.tsx index 52171f0410..248c5815d4 100644 --- a/src/apps/material/material.entry.tsx +++ b/src/apps/material/material.entry.tsx @@ -100,6 +100,7 @@ interface MaterialEntryTextProps { okButtonText: string; onlineLimitMonthAudiobookInfoText: string; onlineLimitMonthEbookInfoText: string; + onlineMaterialTeaserText: string; openOrderAuthenticationErrorText: string; openOrderErrorMissingPincodeText: string; openOrderInvalidOrderText: string; @@ -148,6 +149,7 @@ interface MaterialEntryTextProps { queueText: string; ratingIsText: string; readArticleText: string; + readerModalDescriptionText: string; receiveEmailWhenMaterialReadyText: string; receiveSmsWhenMaterialReadyText: string; reservableFromAnotherLibraryText: string; diff --git a/src/apps/material/material.stories.tsx b/src/apps/material/material.stories.tsx index 899ca6cb36..3c25fb3ca8 100644 --- a/src/apps/material/material.stories.tsx +++ b/src/apps/material/material.stories.tsx @@ -231,6 +231,10 @@ const meta: Meta = { description: "Read article", control: { type: "text" } }, + readerModalDescriptionText: { + description: "Reader modal description text", + control: { type: "text" } + }, loadingText: { description: "Loading", control: { type: "text" } @@ -291,6 +295,10 @@ const meta: Meta = { description: "Online limit info text", control: { type: "text" } }, + onlineMaterialTeaserText: { + description: "Material button online teaser text", + control: { type: "text" } + }, approveReservationText: { description: "Approve reservation", control: { type: "text" } @@ -750,6 +758,7 @@ const meta: Meta = { detailsListPartsText: "Contents", editionText: "Edition", readArticleText: "Read article", + readerModalDescriptionText: "Modal for Reader", loadingText: "Loading", getOnlineText: "Get online", seeOnlineText: "See online", @@ -771,6 +780,7 @@ const meta: Meta = { "You have borrowed @count out of @limit possible e-books this month", onlineLimitMonthAudiobookInfoText: "You have borrowed @count out of @limit possible audio-books this month", + onlineMaterialTeaserText: "Try e-book", approveReservationText: "Approve reservation", shiftText: "Change", reservationDetailsPickUpAtTitleText: "Pick up at", diff --git a/src/apps/material/material.tsx b/src/apps/material/material.tsx index 5281d969c4..ba9a7e2750 100644 --- a/src/apps/material/material.tsx +++ b/src/apps/material/material.tsx @@ -33,11 +33,13 @@ import { getBestMaterialTypeForWork, getDetailsListData, getInfomediaIds, + getManifestationIsbn, getManifestationsOrderByTypeAndYear, isParallelReservation } from "./helper"; import MaterialDisclosure from "./MaterialDisclosure"; import ReservationFindOnShelfModals from "./ReservationFindOnShelfModals"; +import ReaderModal from "../../components/material/Reader-modal/ReaderModal"; export interface MaterialProps { wid: WorkId; @@ -193,6 +195,9 @@ const Material: React.FC = ({ wid }) => { setSelectedPeriodical={setSelectedPeriodical} /> )} + {/* Since we cannot trust the editions for global manifestations */} diff --git a/src/components/material/Reader-modal/ReaderModal.tsx b/src/components/material/Reader-modal/ReaderModal.tsx new file mode 100644 index 0000000000..8c49982b4a --- /dev/null +++ b/src/components/material/Reader-modal/ReaderModal.tsx @@ -0,0 +1,27 @@ +import React from "react"; +import Modal from "../../../core/utils/modal"; +import { useText } from "../../../core/utils/text"; +import Reader from "../../reader-player/Reader"; + +type ReaderModalType = { + identifier: string; +}; + +const ReaderModal = ({ identifier }: ReaderModalType) => { + const t = useText(); + return ( + + + + ); +}; + +export default ReaderModal; diff --git a/src/components/material/material-buttons/online/MaterialButtonReaderTeaser.tsx b/src/components/material/material-buttons/online/MaterialButtonReaderTeaser.tsx new file mode 100644 index 0000000000..483631929e --- /dev/null +++ b/src/components/material/material-buttons/online/MaterialButtonReaderTeaser.tsx @@ -0,0 +1,30 @@ +import React from "react"; +import { useModalButtonHandler } from "../../../../core/utils/modal"; +import { useText } from "../../../../core/utils/text"; + +type MaterialButtonOnlineTeaserType = { + identifier: string; +}; + +const MaterialButtonReaderTeaser = ({ + identifier +}: MaterialButtonOnlineTeaserType) => { + const t = useText(); + const { open } = useModalButtonHandler(); + + const onClick = () => { + open(`reader-modal-${identifier}`); + }; + + return ( + + ); +}; + +export default MaterialButtonReaderTeaser; diff --git a/src/components/material/material-buttons/online/MaterialButtonsOnline.tsx b/src/components/material/material-buttons/online/MaterialButtonsOnline.tsx index 4acb169a0b..b5e4130194 100644 --- a/src/components/material/material-buttons/online/MaterialButtonsOnline.tsx +++ b/src/components/material/material-buttons/online/MaterialButtonsOnline.tsx @@ -13,6 +13,8 @@ import MaterialButtonOnlineDigitalArticle from "./MaterialButtonOnlineDigitalArt import MaterialButtonOnlineExternal from "./MaterialButtonOnlineExternal"; import MaterialButtonOnlineInfomediaArticle from "./MaterialButtonOnlineInfomediaArticle"; import { ManifestationMaterialType } from "../../../../core/utils/types/material-type"; +import { getManifestationIsbn } from "../../../../apps/material/helper"; +import MaterialButtonReaderTeaser from "./MaterialButtonReaderTeaser"; export interface MaterialButtonsOnlineProps { manifestations: Manifestation[]; @@ -58,15 +60,20 @@ const MaterialButtonsOnline: FC = ({ } return ( - + <> + + + ); } diff --git a/src/components/reader-player/Reader.tsx b/src/components/reader-player/Reader.tsx index acb78dfcd7..d0717ed975 100644 --- a/src/components/reader-player/Reader.tsx +++ b/src/components/reader-player/Reader.tsx @@ -36,6 +36,10 @@ const Reader = ({ identifier, orderId }: ReaderType) => { // @ts-ignore // eslint-disable-next-line react/no-unknown-property identifier={identifier} + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + // eslint-disable-next-line react/no-unknown-property + close-href={`javascript:window.closeModal('reader-modal-${identifier}')`} /> ); } diff --git a/src/core/utils/modal.tsx b/src/core/utils/modal.tsx index e81307910c..1f22804afe 100644 --- a/src/core/utils/modal.tsx +++ b/src/core/utils/modal.tsx @@ -47,6 +47,22 @@ function Modal({ const dispatch = useDispatch(); const { modalIds } = useSelector((s: ModalIdsProps) => s.modal); + useEffect(() => { + // Attach the closeModal function to the window object to be able to close the modal + // from the reader / player. + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-shadow + window.closeModal = (modalId: string) => { + dispatch(closeModal({ modalId })); + }; + + return () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + delete window.closeModal; + }; + }, [isFullScreen, modalId, dispatch]); useEffect(() => { const searchParams = new URLSearchParams(window.location.search); From 0cc65027975b23c77084227f943e7febe2189a33 Mon Sep 17 00:00:00 2001 From: Kasper Birch Date: Tue, 19 Nov 2024 17:30:45 +0100 Subject: [PATCH 04/24] Move `MaterialButtonReaderTeaser` logic to `MaterialButtonOnlineExternal` Consolidated the "Reader Teaser" logic into `MaterialButtonOnlineExternal` since not all online materials have a teaser. --- .../online/MaterialButtonOnlineExternal.tsx | 35 ++++++++++++------- .../online/MaterialButtonsOnline.tsx | 25 +++++-------- src/components/reader-player/helper.ts | 15 ++++++++ src/core/utils/types/material-type.ts | 5 ++- 4 files changed, 51 insertions(+), 29 deletions(-) diff --git a/src/components/material/material-buttons/online/MaterialButtonOnlineExternal.tsx b/src/components/material/material-buttons/online/MaterialButtonOnlineExternal.tsx index bde9344cf0..198a011ee3 100644 --- a/src/components/material/material-buttons/online/MaterialButtonOnlineExternal.tsx +++ b/src/components/material/material-buttons/online/MaterialButtonOnlineExternal.tsx @@ -10,6 +10,9 @@ import { ButtonSize } from "../../../../core/utils/types/button"; import { Manifestation } from "../../../../core/utils/types/entities"; import { ManifestationMaterialType } from "../../../../core/utils/types/material-type"; import LinkButton from "../../../Buttons/LinkButton"; +import MaterialButtonReaderTeaser from "./MaterialButtonReaderTeaser"; +import { getManifestationIsbn } from "../../../../apps/material/helper"; +import { hasReaderTeaser } from "../../../reader-player/helper"; export interface MaterialButtonOnlineExternalProps { externalUrl: string; @@ -91,18 +94,26 @@ const MaterialButtonOnlineExternal: FC = ({ } }; return ( - - {label(origin, getMaterialTypes(manifestations))} - + <> + + {label(origin, getMaterialTypes(manifestations))} + + + {hasReaderTeaser(manifestations) && ( + + )} + ); }; diff --git a/src/components/material/material-buttons/online/MaterialButtonsOnline.tsx b/src/components/material/material-buttons/online/MaterialButtonsOnline.tsx index b5e4130194..4acb169a0b 100644 --- a/src/components/material/material-buttons/online/MaterialButtonsOnline.tsx +++ b/src/components/material/material-buttons/online/MaterialButtonsOnline.tsx @@ -13,8 +13,6 @@ import MaterialButtonOnlineDigitalArticle from "./MaterialButtonOnlineDigitalArt import MaterialButtonOnlineExternal from "./MaterialButtonOnlineExternal"; import MaterialButtonOnlineInfomediaArticle from "./MaterialButtonOnlineInfomediaArticle"; import { ManifestationMaterialType } from "../../../../core/utils/types/material-type"; -import { getManifestationIsbn } from "../../../../apps/material/helper"; -import MaterialButtonReaderTeaser from "./MaterialButtonReaderTeaser"; export interface MaterialButtonsOnlineProps { manifestations: Manifestation[]; @@ -60,20 +58,15 @@ const MaterialButtonsOnline: FC = ({ } return ( - <> - - - + ); } diff --git a/src/components/reader-player/helper.ts b/src/components/reader-player/helper.ts index ddf6e72f6e..bc3ab99bcb 100644 --- a/src/components/reader-player/helper.ts +++ b/src/components/reader-player/helper.ts @@ -1,3 +1,7 @@ +import { getMaterialTypes } from "../../core/utils/helpers/general"; +import { Manifestation } from "../../core/utils/types/entities"; +import { ManifestationMaterialType } from "../../core/utils/types/material-type"; + type AssetType = { src: string; type: "script" | "link"; @@ -60,4 +64,15 @@ export const removeAppendedAssets = () => { }); }; +export const hasReaderTeaser = (manifestations: Manifestation[]) => { + const materialTypes = getMaterialTypes(manifestations); + return materialTypes.some( + (type) => + type === ManifestationMaterialType.ebook || + type === ManifestationMaterialType.pictureBookOnline || + type === ManifestationMaterialType.animatedSeriesOnline || + type === ManifestationMaterialType.yearBookOnline + ); +}; + export default {}; diff --git a/src/core/utils/types/material-type.ts b/src/core/utils/types/material-type.ts index 7b075a5d16..dc8841a6d1 100644 --- a/src/core/utils/types/material-type.ts +++ b/src/core/utils/types/material-type.ts @@ -13,7 +13,10 @@ export const enum ManifestationMaterialType { earticle = "artikel", boardGame = "spil", cdRom = "cd", - magazine = "tidsskrift" + magazine = "tidsskrift", + pictureBookOnline = "billedbog (online)", + animatedSeriesOnline = "tegneserie (online)", + yearBookOnline = "årbog (online)" } export const enum AutosuggestCategory { From 0c672c14175c51b61abd618beb41b963074347e0 Mon Sep 17 00:00:00 2001 From: Kasper Birch Date: Wed, 20 Nov 2024 11:06:34 +0100 Subject: [PATCH 05/24] Make `MaterialButtonReaderTeaser` follow the same structure as `MaterialButtonsFindOnShelf` These two should follow the same structure to ensure consistency in appearance. --- .../online/MaterialButtonOnlineExternal.tsx | 2 + .../online/MaterialButtonReaderTeaser.tsx | 38 +++++++++++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/components/material/material-buttons/online/MaterialButtonOnlineExternal.tsx b/src/components/material/material-buttons/online/MaterialButtonOnlineExternal.tsx index 198a011ee3..23fdee1418 100644 --- a/src/components/material/material-buttons/online/MaterialButtonOnlineExternal.tsx +++ b/src/components/material/material-buttons/online/MaterialButtonOnlineExternal.tsx @@ -110,7 +110,9 @@ const MaterialButtonOnlineExternal: FC = ({ {hasReaderTeaser(manifestations) && ( )} diff --git a/src/components/material/material-buttons/online/MaterialButtonReaderTeaser.tsx b/src/components/material/material-buttons/online/MaterialButtonReaderTeaser.tsx index 483631929e..8a6ef44c0d 100644 --- a/src/components/material/material-buttons/online/MaterialButtonReaderTeaser.tsx +++ b/src/components/material/material-buttons/online/MaterialButtonReaderTeaser.tsx @@ -1,13 +1,19 @@ import React from "react"; import { useModalButtonHandler } from "../../../../core/utils/modal"; import { useText } from "../../../../core/utils/text"; +import { ButtonSize } from "../../../../core/utils/types/button"; +import { Button } from "../../../Buttons/Button"; type MaterialButtonOnlineTeaserType = { identifier: string; + size: ButtonSize; + dataCy?: string; }; const MaterialButtonReaderTeaser = ({ - identifier + identifier, + size, + dataCy = "material-buttons-reader-teaser" }: MaterialButtonOnlineTeaserType) => { const t = useText(); const { open } = useModalButtonHandler(); @@ -16,11 +22,37 @@ const MaterialButtonReaderTeaser = ({ open(`reader-modal-${identifier}`); }; + // If element is currently focused on, we would like to let users open it using enter + const onKeyUp = (key: string) => { + if (key === "Enter") { + onClick(); + } + }; + + if (size !== "small") { + return ( + From ad50bec7d415a151b30668ffba5043101c8a9bbf Mon Sep 17 00:00:00 2001 From: Kasper Birch Date: Wed, 20 Nov 2024 11:25:48 +0100 Subject: [PATCH 06/24] Introduce `MaterialSecondaryButton` To ensure the `MaterialButtonReaderTeaser` and `MaterialButtonsFindOnShelf` have identical behavior, I created a shared component to eliminate code duplication. --- src/components/Buttons/Button.tsx | 5 +- .../generic/MaterialSecondaryButton.tsx | 58 +++++++++++++++++++ .../online/MaterialButtonReaderTeaser.tsx | 40 +++---------- .../physical/MaterialButtonsFindOnShelf.tsx | 43 +++----------- 4 files changed, 78 insertions(+), 68 deletions(-) create mode 100644 src/components/material/material-buttons/generic/MaterialSecondaryButton.tsx diff --git a/src/components/Buttons/Button.tsx b/src/components/Buttons/Button.tsx index 88e8ccc335..24811ada78 100644 --- a/src/components/Buttons/Button.tsx +++ b/src/components/Buttons/Button.tsx @@ -18,6 +18,7 @@ export type ButtonProps = { id?: string; classNames?: string; dataCy?: string; + ariaDescribedBy?: string; }; export const Button: React.FC = ({ @@ -31,7 +32,8 @@ export const Button: React.FC = ({ iconClassNames, id, classNames, - dataCy + dataCy, + ariaDescribedBy }) => { return ( + ); +}; + +export default MaterialSecondaryButton; diff --git a/src/components/material/material-buttons/online/MaterialButtonReaderTeaser.tsx b/src/components/material/material-buttons/online/MaterialButtonReaderTeaser.tsx index 8a6ef44c0d..c1959b6a31 100644 --- a/src/components/material/material-buttons/online/MaterialButtonReaderTeaser.tsx +++ b/src/components/material/material-buttons/online/MaterialButtonReaderTeaser.tsx @@ -2,7 +2,7 @@ import React from "react"; import { useModalButtonHandler } from "../../../../core/utils/modal"; import { useText } from "../../../../core/utils/text"; import { ButtonSize } from "../../../../core/utils/types/button"; -import { Button } from "../../../Buttons/Button"; +import MaterialSecondaryButton from "../generic/MaterialSecondaryButton"; type MaterialButtonOnlineTeaserType = { identifier: string; @@ -22,40 +22,14 @@ const MaterialButtonReaderTeaser = ({ open(`reader-modal-${identifier}`); }; - // If element is currently focused on, we would like to let users open it using enter - const onKeyUp = (key: string) => { - if (key === "Enter") { - onClick(); - } - }; - - if (size !== "small") { - return ( - + dataCy={dataCy} + ariaDescribedBy={t("onlineMaterialTeaserText")} + /> ); }; diff --git a/src/components/material/material-buttons/physical/MaterialButtonsFindOnShelf.tsx b/src/components/material/material-buttons/physical/MaterialButtonsFindOnShelf.tsx index bd3cb2e2c4..fc55a24fd0 100644 --- a/src/components/material/material-buttons/physical/MaterialButtonsFindOnShelf.tsx +++ b/src/components/material/material-buttons/physical/MaterialButtonsFindOnShelf.tsx @@ -1,10 +1,9 @@ -import * as React from "react"; -import { FC } from "react"; +import React, { FC } from "react"; import { useModalButtonHandler } from "../../../../core/utils/modal"; import { useText } from "../../../../core/utils/text"; import { ButtonSize } from "../../../../core/utils/types/button"; import { FaustId } from "../../../../core/utils/types/ids"; -import { Button } from "../../../Buttons/Button"; +import MaterialSecondaryButton from "../generic/MaterialSecondaryButton"; import { findOnShelfModalId } from "../../../find-on-shelf/FindOnShelfModal"; export interface MaterialButtonsFindOnShelfProps { @@ -21,43 +20,19 @@ const MaterialButtonsFindOnShelf: FC = ({ const t = useText(); const { open } = useModalButtonHandler(); const modalId = findOnShelfModalId(faustIds); + const onClick = () => { open(modalId); }; - // If element is currently focused on, we would like to let users open it using enter - const onKeyUp = (key: string) => { - if (key === "Enter") { - onClick(); - } - }; - - if (size !== "small") { - return ( - + dataCy={dataCy} + ariaDescribedBy={t("findOnShelfExpandButtonExplanationText")} + /> ); }; From f74624331ce244821665187285c53728f2767117 Mon Sep 17 00:00:00 2001 From: Kasper Birch Date: Wed, 20 Nov 2024 11:36:41 +0100 Subject: [PATCH 07/24] Render `ReaderModal` only when required This ensures that the `ReaderModal` is loaded only when necessary, similar to how the `MaterialButtonReaderTeaser` is rendered. --- src/apps/material/material.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/apps/material/material.tsx b/src/apps/material/material.tsx index ba9a7e2750..2bc173941a 100644 --- a/src/apps/material/material.tsx +++ b/src/apps/material/material.tsx @@ -40,6 +40,7 @@ import { import MaterialDisclosure from "./MaterialDisclosure"; import ReservationFindOnShelfModals from "./ReservationFindOnShelfModals"; import ReaderModal from "../../components/material/Reader-modal/ReaderModal"; +import { hasReaderTeaser } from "../../components/reader-player/helper"; export interface MaterialProps { wid: WorkId; @@ -195,9 +196,11 @@ const Material: React.FC = ({ wid }) => { setSelectedPeriodical={setSelectedPeriodical} /> )} - + {hasReaderTeaser(selectedManifestations) && ( + + )} {/* Since we cannot trust the editions for global manifestations */} From 2271e653dba5a75d9dc2288e9419a47ab3a8d004 Mon Sep 17 00:00:00 2001 From: Kasper Birch Date: Wed, 20 Nov 2024 12:46:48 +0100 Subject: [PATCH 08/24] Remove `window.location.reload();` from `Reader` I believe the issue isn't with the `useEffect` inside `Reader`, but rather with the modal logic itself. I have encountered similar issues with the `FindOnShelfModal`, where it doesn't load the second time. A ticket has been created for this issue: https://reload.atlassian.net/browse/DDFHER-140 --- src/components/reader-player/Reader.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/reader-player/Reader.tsx b/src/components/reader-player/Reader.tsx index d0717ed975..4d94a37330 100644 --- a/src/components/reader-player/Reader.tsx +++ b/src/components/reader-player/Reader.tsx @@ -11,10 +11,6 @@ const Reader = ({ identifier, orderId }: ReaderType) => { return () => { removeAppendedAssets(); - // TODO: Temporary workaround to reload the page when the reader is closed. - // The issue seems to be related to browser caching the reader, preventing it from properly refreshing. - // Replace this with a better solution in the future. - window.location.reload(); }; }, [identifier, orderId]); From c1ab716210bea173d2d5f8de834cd9204459363f Mon Sep 17 00:00:00 2001 From: Kasper Birch Date: Wed, 20 Nov 2024 14:33:37 +0100 Subject: [PATCH 09/24] Move `window.closeModal` logic into `ReaderModal` The addition of `closeModal` to the `window` object is only necessary in the `ReaderModal`, so it is unnecessary to keep it in the generic `Modal` component. --- .../material/Reader-modal/ReaderModal.tsx | 23 ++++++++++++++++++- src/core/utils/modal.tsx | 17 -------------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/components/material/Reader-modal/ReaderModal.tsx b/src/components/material/Reader-modal/ReaderModal.tsx index 8c49982b4a..46fa5327ae 100644 --- a/src/components/material/Reader-modal/ReaderModal.tsx +++ b/src/components/material/Reader-modal/ReaderModal.tsx @@ -1,7 +1,9 @@ -import React from "react"; +import React, { useEffect } from "react"; +import { useDispatch } from "react-redux"; import Modal from "../../../core/utils/modal"; import { useText } from "../../../core/utils/text"; import Reader from "../../reader-player/Reader"; +import { closeModal } from "../../../core/modal.slice"; type ReaderModalType = { identifier: string; @@ -9,6 +11,25 @@ type ReaderModalType = { const ReaderModal = ({ identifier }: ReaderModalType) => { const t = useText(); + const dispatch = useDispatch(); + + useEffect(() => { + // Attach the closeModal function to the window object to be able to close the modal + // from the reader / player. + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-shadow + window.closeModal = (modalId: string) => { + dispatch(closeModal({ modalId })); + }; + + return () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + delete window.closeModal; + }; + }, [dispatch]); + return ( s.modal); - useEffect(() => { - // Attach the closeModal function to the window object to be able to close the modal - // from the reader / player. - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - // eslint-disable-next-line @typescript-eslint/no-shadow - window.closeModal = (modalId: string) => { - dispatch(closeModal({ modalId })); - }; - - return () => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - delete window.closeModal; - }; - }, [isFullScreen, modalId, dispatch]); - useEffect(() => { const searchParams = new URLSearchParams(window.location.search); // Deep link stuff: if the id is in the url, open the modal From fcddcb3cd2de8c8395e2c763c3ba9508200c9859 Mon Sep 17 00:00:00 2001 From: Kasper Birch Date: Fri, 22 Nov 2024 11:14:12 +0100 Subject: [PATCH 10/24] Initial setup of `Reader app Since loading the reader scripts requires a page shift, I developed the reader as a separate app with its own route in Drupal. --- src/apps/reader/Reader.entry.tsx | 14 ++++++++++++++ src/apps/reader/Reader.mount.ts | 4 ++++ src/apps/reader/Reader.stories.tsx | 25 +++++++++++++++++++++++++ src/apps/reader/Reader.tsx | 7 +++++++ 4 files changed, 50 insertions(+) create mode 100644 src/apps/reader/Reader.entry.tsx create mode 100644 src/apps/reader/Reader.mount.ts create mode 100644 src/apps/reader/Reader.stories.tsx create mode 100644 src/apps/reader/Reader.tsx diff --git a/src/apps/reader/Reader.entry.tsx b/src/apps/reader/Reader.entry.tsx new file mode 100644 index 0000000000..b45e5ad8ac --- /dev/null +++ b/src/apps/reader/Reader.entry.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import { withConfig } from "../../core/utils/config"; +import { withUrls } from "../../core/utils/url"; +import { withText } from "../../core/utils/text"; +import Reader from "./Reader"; + +// interface ReaderEntryTextProps {} +// interface ReaderEntryConfigProps {} + +const ReaderEntry: React.FC = () => { + return ; +}; + +export default withConfig(withUrls(withText(ReaderEntry))); diff --git a/src/apps/reader/Reader.mount.ts b/src/apps/reader/Reader.mount.ts new file mode 100644 index 0000000000..33185b771e --- /dev/null +++ b/src/apps/reader/Reader.mount.ts @@ -0,0 +1,4 @@ +import addMount from "../../core/addMount"; +import Reader from "./Reader.entry"; + +addMount({ appName: "reader", app: Reader }); diff --git a/src/apps/reader/Reader.stories.tsx b/src/apps/reader/Reader.stories.tsx new file mode 100644 index 0000000000..87c486b8e0 --- /dev/null +++ b/src/apps/reader/Reader.stories.tsx @@ -0,0 +1,25 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import Reader from "./Reader.entry"; + +const meta: Meta = { + title: "Apps / Reader", + component: Reader, + argTypes: { + identifier: { + control: { type: "text" } + }, + orderId: { + control: { type: "text" } + } + } +}; + +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + args: { + ...serviceUrlArgs + } +}; diff --git a/src/apps/reader/Reader.tsx b/src/apps/reader/Reader.tsx new file mode 100644 index 0000000000..59f22c1dd9 --- /dev/null +++ b/src/apps/reader/Reader.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +const Reader = () => { + return
Reader React
; +}; + +export default Reader; From f8e72023a3340d4f894d02a1dc74768a426c52dd Mon Sep 17 00:00:00 2001 From: Kasper Birch Date: Fri, 22 Nov 2024 12:11:04 +0100 Subject: [PATCH 11/24] Remove / Move `Reader` component In the new architecture, `Reader` is now a standalone app. Additionally I also found that there is no reason to wrap the stories in `Store` --- src/apps/material/material.tsx | 2 +- src/apps/reader/Reader.entry.tsx | 9 ++-- src/apps/reader/Reader.stories.tsx | 12 +++-- src/apps/reader/Reader.tsx | 54 +++++++++++++++++-- .../reader-player => apps/reader}/helper.ts | 0 .../online/MaterialButtonOnlineExternal.tsx | 2 +- .../reader-player/Reader.stories.tsx | 45 ---------------- src/components/reader-player/Reader.tsx | 46 ---------------- 8 files changed, 64 insertions(+), 106 deletions(-) rename src/{components/reader-player => apps/reader}/helper.ts (100%) delete mode 100644 src/components/reader-player/Reader.stories.tsx delete mode 100644 src/components/reader-player/Reader.tsx diff --git a/src/apps/material/material.tsx b/src/apps/material/material.tsx index 2bc173941a..54d8c40fea 100644 --- a/src/apps/material/material.tsx +++ b/src/apps/material/material.tsx @@ -40,7 +40,7 @@ import { import MaterialDisclosure from "./MaterialDisclosure"; import ReservationFindOnShelfModals from "./ReservationFindOnShelfModals"; import ReaderModal from "../../components/material/Reader-modal/ReaderModal"; -import { hasReaderTeaser } from "../../components/reader-player/helper"; +import { hasReaderTeaser } from "../reader/helper"; export interface MaterialProps { wid: WorkId; diff --git a/src/apps/reader/Reader.entry.tsx b/src/apps/reader/Reader.entry.tsx index b45e5ad8ac..bf46d293b0 100644 --- a/src/apps/reader/Reader.entry.tsx +++ b/src/apps/reader/Reader.entry.tsx @@ -2,13 +2,10 @@ import React from "react"; import { withConfig } from "../../core/utils/config"; import { withUrls } from "../../core/utils/url"; import { withText } from "../../core/utils/text"; -import Reader from "./Reader"; +import Reader, { ReaderType } from "./Reader"; -// interface ReaderEntryTextProps {} -// interface ReaderEntryConfigProps {} - -const ReaderEntry: React.FC = () => { - return ; +const ReaderEntry: React.FC = ({ identifier, orderId }) => { + return ; }; export default withConfig(withUrls(withText(ReaderEntry))); diff --git a/src/apps/reader/Reader.stories.tsx b/src/apps/reader/Reader.stories.tsx index 87c486b8e0..763d697e46 100644 --- a/src/apps/reader/Reader.stories.tsx +++ b/src/apps/reader/Reader.stories.tsx @@ -1,3 +1,4 @@ +import React from "react"; import type { Meta, StoryObj } from "@storybook/react"; import Reader from "./Reader.entry"; @@ -18,8 +19,11 @@ export default meta; type Story = StoryObj; -export const Primary: Story = { - args: { - ...serviceUrlArgs - } +export const WithIdentifier: Story = { + render: () => +}; + +// Works only if the matrial is reserved +export const WithOrderId: Story = { + render: () => }; diff --git a/src/apps/reader/Reader.tsx b/src/apps/reader/Reader.tsx index 59f22c1dd9..71f2e127a2 100644 --- a/src/apps/reader/Reader.tsx +++ b/src/apps/reader/Reader.tsx @@ -1,7 +1,55 @@ -import React from "react"; +import React, { CSSProperties, useEffect } from "react"; +import { appendAsset, readerAssets, removeAppendedAssets } from "./helper"; -const Reader = () => { - return
Reader React
; +export type ReaderType = { + identifier?: string; + orderId?: string; +}; + +const Reader: React.FC = ({ identifier, orderId }: ReaderType) => { + useEffect(() => { + readerAssets.forEach(appendAsset); + + return () => { + removeAppendedAssets(); + }; + }, [identifier, orderId]); + + const readerStyles: CSSProperties = { + height: "100vh" + }; + + if (orderId) { + return ( +
+ ); + } + + if (identifier) { + return ( +
+ ); + } + + // eslint-disable-next-line no-console + console.warn("No identifier or orderId provided for the Reader app."); + return null; }; export default Reader; diff --git a/src/components/reader-player/helper.ts b/src/apps/reader/helper.ts similarity index 100% rename from src/components/reader-player/helper.ts rename to src/apps/reader/helper.ts diff --git a/src/components/material/material-buttons/online/MaterialButtonOnlineExternal.tsx b/src/components/material/material-buttons/online/MaterialButtonOnlineExternal.tsx index 23fdee1418..30d640cefc 100644 --- a/src/components/material/material-buttons/online/MaterialButtonOnlineExternal.tsx +++ b/src/components/material/material-buttons/online/MaterialButtonOnlineExternal.tsx @@ -12,7 +12,7 @@ import { ManifestationMaterialType } from "../../../../core/utils/types/material import LinkButton from "../../../Buttons/LinkButton"; import MaterialButtonReaderTeaser from "./MaterialButtonReaderTeaser"; import { getManifestationIsbn } from "../../../../apps/material/helper"; -import { hasReaderTeaser } from "../../../reader-player/helper"; +import { hasReaderTeaser } from "../../../../apps/reader/helper"; export interface MaterialButtonOnlineExternalProps { externalUrl: string; diff --git a/src/components/reader-player/Reader.stories.tsx b/src/components/reader-player/Reader.stories.tsx deleted file mode 100644 index f7c9ccf6b3..0000000000 --- a/src/components/reader-player/Reader.stories.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React from "react"; -import type { Meta, StoryObj } from "@storybook/react"; -import Store from "../store"; -import Reader from "./Reader"; - -const meta: Meta = { - title: "Components/Reader", - component: Reader, - parameters: { - layout: "centered" - }, - argTypes: { - identifier: { - control: { type: "text" } - }, - orderId: { - control: { type: "text" } - } - } -}; - -export default meta; - -type Story = StoryObj; - -export const WithIdentifier: Story = { - render: () => ( - -
- -
-
- ) -}; - -// Works only if the matrial is reserved -export const WithOrderId: Story = { - render: () => ( - -
- -
-
- ) -}; diff --git a/src/components/reader-player/Reader.tsx b/src/components/reader-player/Reader.tsx deleted file mode 100644 index 4d94a37330..0000000000 --- a/src/components/reader-player/Reader.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React, { useEffect } from "react"; -import { appendAsset, readerAssets, removeAppendedAssets } from "./helper"; - -type ReaderType = - | { identifier: string; orderId?: never } - | { identifier?: never; orderId: string }; - -const Reader = ({ identifier, orderId }: ReaderType) => { - useEffect(() => { - readerAssets.forEach(appendAsset); - - return () => { - removeAppendedAssets(); - }; - }, [identifier, orderId]); - - if (orderId) { - return ( -
- ); - } - - if (identifier) { - return ( -
- ); - } - - return null; -}; - -export default Reader; From 62e31b6e919f22bb5a5e273a586414f7b1ee5b39 Mon Sep 17 00:00:00 2001 From: Kasper Birch Date: Fri, 22 Nov 2024 12:51:32 +0100 Subject: [PATCH 12/24] Remove `ReaderModal` and Introduce `MaterialSecondaryLink` After the `Reader` is no longer an app, the `ReaderModal` is no longer in use and has been removed. A new component, `MaterialSecondaryLink`, has been added. It follows the same structure as `MaterialSecondaryButton` but is designed for links instead. I considered reverting the `MaterialSecondaryButton` introduced in an earlier commit, but I believe the new structure is more readable. Additionally, it may be useful for the `Player` app/component in the future. - Remove unnecessary `isFullScreen` from `Modal` With `Reader` now functioning as an app on its own page, this property is no longer required. --- src/apps/material/material.tsx | 8 ---- .../material/Reader-modal/ReaderModal.tsx | 48 ------------------- .../generic/MaterialSecondaryLink.tsx | 43 +++++++++++++++++ .../online/MaterialButtonReaderTeaser.tsx | 13 ++--- src/core/utils/modal.tsx | 8 +--- 5 files changed, 47 insertions(+), 73 deletions(-) delete mode 100644 src/components/material/Reader-modal/ReaderModal.tsx create mode 100644 src/components/material/material-buttons/generic/MaterialSecondaryLink.tsx diff --git a/src/apps/material/material.tsx b/src/apps/material/material.tsx index 54d8c40fea..5281d969c4 100644 --- a/src/apps/material/material.tsx +++ b/src/apps/material/material.tsx @@ -33,14 +33,11 @@ import { getBestMaterialTypeForWork, getDetailsListData, getInfomediaIds, - getManifestationIsbn, getManifestationsOrderByTypeAndYear, isParallelReservation } from "./helper"; import MaterialDisclosure from "./MaterialDisclosure"; import ReservationFindOnShelfModals from "./ReservationFindOnShelfModals"; -import ReaderModal from "../../components/material/Reader-modal/ReaderModal"; -import { hasReaderTeaser } from "../reader/helper"; export interface MaterialProps { wid: WorkId; @@ -196,11 +193,6 @@ const Material: React.FC = ({ wid }) => { setSelectedPeriodical={setSelectedPeriodical} /> )} - {hasReaderTeaser(selectedManifestations) && ( - - )} {/* Since we cannot trust the editions for global manifestations */} diff --git a/src/components/material/Reader-modal/ReaderModal.tsx b/src/components/material/Reader-modal/ReaderModal.tsx deleted file mode 100644 index 46fa5327ae..0000000000 --- a/src/components/material/Reader-modal/ReaderModal.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React, { useEffect } from "react"; -import { useDispatch } from "react-redux"; -import Modal from "../../../core/utils/modal"; -import { useText } from "../../../core/utils/text"; -import Reader from "../../reader-player/Reader"; -import { closeModal } from "../../../core/modal.slice"; - -type ReaderModalType = { - identifier: string; -}; - -const ReaderModal = ({ identifier }: ReaderModalType) => { - const t = useText(); - const dispatch = useDispatch(); - - useEffect(() => { - // Attach the closeModal function to the window object to be able to close the modal - // from the reader / player. - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - // eslint-disable-next-line @typescript-eslint/no-shadow - window.closeModal = (modalId: string) => { - dispatch(closeModal({ modalId })); - }; - - return () => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - delete window.closeModal; - }; - }, [dispatch]); - - return ( - - - - ); -}; - -export default ReaderModal; diff --git a/src/components/material/material-buttons/generic/MaterialSecondaryLink.tsx b/src/components/material/material-buttons/generic/MaterialSecondaryLink.tsx new file mode 100644 index 0000000000..5f4ba610db --- /dev/null +++ b/src/components/material/material-buttons/generic/MaterialSecondaryLink.tsx @@ -0,0 +1,43 @@ +import React, { FC } from "react"; +import { ButtonSize } from "../../../../core/utils/types/button"; +import LinkButton from "../../../Buttons/LinkButton"; + +interface MaterialSecondaryLinkProps { + label: string; + size: ButtonSize; + url: URL; + dataCy?: string; +} + +const MaterialSecondaryLink: FC = ({ + label, + size, + url, + dataCy +}) => { + if (size !== "small") { + return ( + + {label} + + ); + } + + return ( + + {label} + + ); +}; + +export default MaterialSecondaryLink; diff --git a/src/components/material/material-buttons/online/MaterialButtonReaderTeaser.tsx b/src/components/material/material-buttons/online/MaterialButtonReaderTeaser.tsx index c1959b6a31..7e6b450fd5 100644 --- a/src/components/material/material-buttons/online/MaterialButtonReaderTeaser.tsx +++ b/src/components/material/material-buttons/online/MaterialButtonReaderTeaser.tsx @@ -1,8 +1,7 @@ import React from "react"; -import { useModalButtonHandler } from "../../../../core/utils/modal"; import { useText } from "../../../../core/utils/text"; import { ButtonSize } from "../../../../core/utils/types/button"; -import MaterialSecondaryButton from "../generic/MaterialSecondaryButton"; +import MaterialSecondaryLink from "../generic/MaterialSecondaryLink"; type MaterialButtonOnlineTeaserType = { identifier: string; @@ -16,19 +15,13 @@ const MaterialButtonReaderTeaser = ({ dataCy = "material-buttons-reader-teaser" }: MaterialButtonOnlineTeaserType) => { const t = useText(); - const { open } = useModalButtonHandler(); - - const onClick = () => { - open(`reader-modal-${identifier}`); - }; return ( - ); }; diff --git a/src/core/utils/modal.tsx b/src/core/utils/modal.tsx index f836ba3bdc..69c5cc01cb 100644 --- a/src/core/utils/modal.tsx +++ b/src/core/utils/modal.tsx @@ -24,7 +24,6 @@ type ModalProps = { eventCallbacks?: { close?: () => void; }; - isFullScreen?: boolean; }; export interface ModalIdsProps { @@ -41,8 +40,7 @@ function Modal({ classNames, isSlider, dataCy = "modal", - eventCallbacks, - isFullScreen + eventCallbacks }: ModalProps) { const dispatch = useDispatch(); const { modalIds } = useSelector((s: ModalIdsProps) => s.modal); @@ -82,10 +80,6 @@ function Modal({ dispatch(closeModal({ modalId })); }; - if (isFullScreen) { - return
{children}
; - } - return ( Date: Fri, 22 Nov 2024 13:32:43 +0100 Subject: [PATCH 13/24] Remove `MaterialButtonReaderTeaser` This component is redundant as its functionality can be replaced with `MaterialSecondaryLink`. --- .../online/MaterialButtonOnlineExternal.tsx | 12 ++++++-- .../online/MaterialButtonReaderTeaser.tsx | 29 ------------------- 2 files changed, 9 insertions(+), 32 deletions(-) delete mode 100644 src/components/material/material-buttons/online/MaterialButtonReaderTeaser.tsx diff --git a/src/components/material/material-buttons/online/MaterialButtonOnlineExternal.tsx b/src/components/material/material-buttons/online/MaterialButtonOnlineExternal.tsx index 30d640cefc..13db90f37e 100644 --- a/src/components/material/material-buttons/online/MaterialButtonOnlineExternal.tsx +++ b/src/components/material/material-buttons/online/MaterialButtonOnlineExternal.tsx @@ -10,7 +10,7 @@ import { ButtonSize } from "../../../../core/utils/types/button"; import { Manifestation } from "../../../../core/utils/types/entities"; import { ManifestationMaterialType } from "../../../../core/utils/types/material-type"; import LinkButton from "../../../Buttons/LinkButton"; -import MaterialButtonReaderTeaser from "./MaterialButtonReaderTeaser"; +import MaterialSecondaryLink from "../generic/MaterialSecondaryLink"; import { getManifestationIsbn } from "../../../../apps/material/helper"; import { hasReaderTeaser } from "../../../../apps/reader/helper"; @@ -109,9 +109,15 @@ const MaterialButtonOnlineExternal: FC = ({ {hasReaderTeaser(manifestations) && ( - )} diff --git a/src/components/material/material-buttons/online/MaterialButtonReaderTeaser.tsx b/src/components/material/material-buttons/online/MaterialButtonReaderTeaser.tsx deleted file mode 100644 index 7e6b450fd5..0000000000 --- a/src/components/material/material-buttons/online/MaterialButtonReaderTeaser.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from "react"; -import { useText } from "../../../../core/utils/text"; -import { ButtonSize } from "../../../../core/utils/types/button"; -import MaterialSecondaryLink from "../generic/MaterialSecondaryLink"; - -type MaterialButtonOnlineTeaserType = { - identifier: string; - size: ButtonSize; - dataCy?: string; -}; - -const MaterialButtonReaderTeaser = ({ - identifier, - size, - dataCy = "material-buttons-reader-teaser" -}: MaterialButtonOnlineTeaserType) => { - const t = useText(); - - return ( - - ); -}; - -export default MaterialButtonReaderTeaser; From 3b75c58bb25137b058337a2a3435f8a8d73e902e Mon Sep 17 00:00:00 2001 From: Kasper Birch Date: Mon, 25 Nov 2024 18:16:31 +0100 Subject: [PATCH 14/24] Add `Player` Component + Story Implemented the `Player` component, which functions similarly to the `Reader`. Considering moving the `Reader` component to the `components` folder for consistency in folder structure. --- src/apps/reader/helper.ts | 8 ++++ src/components/player/Player.stories.tsx | 37 +++++++++++++++++ src/components/player/Player.tsx | 52 ++++++++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 src/components/player/Player.stories.tsx create mode 100644 src/components/player/Player.tsx diff --git a/src/apps/reader/helper.ts b/src/apps/reader/helper.ts index bc3ab99bcb..3a50af6e06 100644 --- a/src/apps/reader/helper.ts +++ b/src/apps/reader/helper.ts @@ -8,6 +8,14 @@ type AssetType = { id: string; }; +export const playerAssets: AssetType[] = [ + { + src: "https://play.pubhub.dk/1.3.0/js/player-kernel.min.js", + type: "script", + id: "player-kernel-js" + } +]; + export const readerAssets: AssetType[] = [ { src: "https://reader.pubhub.dk/2.2.0/js/chunk-vendors.js", diff --git a/src/components/player/Player.stories.tsx b/src/components/player/Player.stories.tsx new file mode 100644 index 0000000000..7df7e28ea9 --- /dev/null +++ b/src/components/player/Player.stories.tsx @@ -0,0 +1,37 @@ +import React from "react"; +import type { Meta, StoryObj } from "@storybook/react"; +import Player from "./Player"; + +const meta: Meta = { + title: "Components / Player", + component: Player, + argTypes: { + identifier: { + control: { type: "text" } + }, + orderId: { + control: { type: "text" } + } + } +}; + +export default meta; + +type Story = StoryObj; + +export const WithIdentifier: Story = { + render: () => ( +
+ +
+ ) +}; + +// Works only if the matrial is reserved +export const WithOrderId: Story = { + render: () => ( +
+ +
+ ) +}; diff --git a/src/components/player/Player.tsx b/src/components/player/Player.tsx new file mode 100644 index 0000000000..dee69b7371 --- /dev/null +++ b/src/components/player/Player.tsx @@ -0,0 +1,52 @@ +import React, { useEffect } from "react"; +import { + appendAsset, + playerAssets, + removeAppendedAssets +} from "../../apps/reader/helper"; + +export type PlayerType = + | { + identifier?: string; + orderId?: never; + } + | { + identifier?: never; + orderId?: string; + }; + +const Player: React.FC = ({ identifier, orderId }) => { + useEffect(() => { + playerAssets.forEach(appendAsset); + + return () => { + removeAppendedAssets(); + }; + }, [identifier, orderId]); + + if (orderId) { + return ( +