diff --git a/src/apps/material/material.entry.tsx b/src/apps/material/material.entry.tsx index 52171f0410..e2219c24be 100644 --- a/src/apps/material/material.entry.tsx +++ b/src/apps/material/material.entry.tsx @@ -11,6 +11,7 @@ import { GlobalEntryTextProps } from "../../core/storybook/globalTextArgs"; interface MaterialEntryTextProps { alreadyReservedText: string; approveReservationText: string; + audiobookText: string; blockedButtonText: string; cantReserveText: string; cantViewReviewText: string; @@ -43,6 +44,7 @@ interface MaterialEntryTextProps { detailsListTypeText: string; detailsOfTheMaterialText: string; detailsText: string; + ebookText: string; editionsText: string; editionText: string; etAlText: string; @@ -100,6 +102,7 @@ interface MaterialEntryTextProps { okButtonText: string; onlineLimitMonthAudiobookInfoText: string; onlineLimitMonthEbookInfoText: string; + onlineMaterialTeaserText: string; openOrderAuthenticationErrorText: string; openOrderErrorMissingPincodeText: string; openOrderInvalidOrderText: string; @@ -144,13 +147,15 @@ interface MaterialEntryTextProps { outOfText: string; periodicalSelectEditionText: string; periodicalSelectYearText: string; - reservationDetailsPickUpAtTitleText: string; + playerModalCloseButtonText: string; + playerModalDescriptionText: string; queueText: string; ratingIsText: string; readArticleText: string; receiveEmailWhenMaterialReadyText: string; receiveSmsWhenMaterialReadyText: string; reservableFromAnotherLibraryText: string; + reservationDetailsPickUpAtTitleText: string; reservationErrorsDescriptionText: string; reservationErrorsTitleText: string; reservationModalCloseModalAriaLabelText: string; diff --git a/src/apps/material/material.stories.tsx b/src/apps/material/material.stories.tsx index 899ca6cb36..3f559b63e5 100644 --- a/src/apps/material/material.stories.tsx +++ b/src/apps/material/material.stories.tsx @@ -67,6 +67,14 @@ const meta: Meta = { description: "Edition/Week", control: { type: "text" } }, + playerModalCloseButtonText: { + description: "Close", + control: { type: "text" } + }, + playerModalDescriptionText: { + description: "Player modal description text", + control: { type: "text" } + }, reserveBookText: { description: "Reserve", control: { type: "text" } @@ -143,6 +151,10 @@ const meta: Meta = { description: "Details", control: { type: "text" } }, + ebookText: { + description: "Ebook", + control: { type: "text" } + }, reviewsText: { description: "Reviews", control: { type: "text" } @@ -291,10 +303,18 @@ 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" } }, + audiobookText: { + description: "Audiobook", + control: { type: "text" } + }, shiftText: { description: "Change", control: { type: "text" } @@ -692,6 +712,7 @@ const meta: Meta = { ...serviceUrlArgs, ...globalTextArgs, ...globalConfigArgs, + audiobookText: "Audiobook", searchUrl: "/search", materialUrl: "/work/:workid", wid: "work-of:870970-basis:52557240", @@ -708,6 +729,8 @@ const meta: Meta = { materialHeaderAuthorByText: "By", periodicalSelectYearText: "Year", periodicalSelectEditionText: "Edition", + playerModalDescriptionText: "Modal for player", + playerModalCloseButtonText: "Close", reserveBookText: "Reserve book", reserveText: "Reserve", reserveWithMaterialTypeText: "Reserve @materialType", @@ -728,6 +751,7 @@ const meta: Meta = { editionsText: "Editions", fictionNonfictionText: "Fictional", detailsText: "Details", + ebookText: "E-book", reviewsText: "Reviews", detailsListTypeText: "Type", detailsListLanguageText: "Language", @@ -771,6 +795,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 @materialType", 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..051f98c84d 100644 --- a/src/apps/material/material.tsx +++ b/src/apps/material/material.tsx @@ -33,11 +33,14 @@ import { getBestMaterialTypeForWork, getDetailsListData, getInfomediaIds, + getManifestationIsbn, getManifestationsOrderByTypeAndYear, isParallelReservation } from "./helper"; import MaterialDisclosure from "./MaterialDisclosure"; import ReservationFindOnShelfModals from "./ReservationFindOnShelfModals"; +import PlayerModal from "../../components/material/player-modal/PlayerModal"; +import { hasPlayerManifestation } from "../../components/reader-player/helper"; export interface MaterialProps { wid: WorkId; @@ -193,6 +196,11 @@ const Material: React.FC = ({ wid }) => { setSelectedPeriodical={setSelectedPeriodical} /> )} + {hasPlayerManifestation(selectedManifestations) && ( + + )} {/* Since we cannot trust the editions for global manifestations */} diff --git a/src/apps/reader/Reader.entry.tsx b/src/apps/reader/Reader.entry.tsx new file mode 100644 index 0000000000..049dc5190e --- /dev/null +++ b/src/apps/reader/Reader.entry.tsx @@ -0,0 +1,11 @@ +import React from "react"; +import { withConfig } from "../../core/utils/config"; +import { withUrls } from "../../core/utils/url"; +import { withText } from "../../core/utils/text"; +import Reader, { ReaderType } from "../../components/reader-player/Reader"; + +const ReaderEntry: React.FC = ({ identifier, orderid }) => { + 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/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/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/MaterialButtonOnlineExternal.tsx b/src/components/material/material-buttons/online/MaterialButtonOnlineExternal.tsx index bde9344cf0..c3adadbf39 100644 --- a/src/components/material/material-buttons/online/MaterialButtonOnlineExternal.tsx +++ b/src/components/material/material-buttons/online/MaterialButtonOnlineExternal.tsx @@ -1,3 +1,4 @@ +// TODO: Remember to clean this up for all paths that refer to eReolen import React, { useState, FC, useEffect } from "react"; import { AccessUrl, @@ -90,6 +91,7 @@ const MaterialButtonOnlineExternal: FC = ({ return t("seeOnlineText"); } }; + return ( = ({ }); }; + // Todo: Move logic for Player / Reader buttons / Links to here. + // if (condition) { + // return ; + // } + // Find 'Ereol' object or default to the first 'access' object const accessElement = manifestations[0].access.find((item) => item.__typename === "Ereol") || @@ -58,15 +64,23 @@ const MaterialButtonsOnline: FC = ({ } return ( - + <> + + {/* Todo: MaterialButtonsOnlineInternal should be in the todo at line 44 */} + + ); } diff --git a/src/components/material/material-buttons/online/MaterialButtonsOnlineInternal.tsx b/src/components/material/material-buttons/online/MaterialButtonsOnlineInternal.tsx new file mode 100644 index 0000000000..bda5d7e347 --- /dev/null +++ b/src/components/material/material-buttons/online/MaterialButtonsOnlineInternal.tsx @@ -0,0 +1,65 @@ +import React, { FC } from "react"; +import { Manifestation } from "../../../../core/utils/types/entities"; +import { + hasPlayerManifestation, + hasReaderManifestation +} from "../../../reader-player/helper"; +import MaterialSecondaryLink from "../generic/MaterialSecondaryLink"; +import MaterialSecondaryButton from "../generic/MaterialSecondaryButton"; +import { playerModalId } from "../../player-modal/helper"; +import { getManifestationIsbn } from "../../../../apps/material/helper"; +import { useModalButtonHandler } from "../../../../core/utils/modal"; +import { useText } from "../../../../core/utils/text"; +import { ButtonSize } from "../../../../core/utils/types/button"; + +type MaterialButtonsOnlineInternalType = { + size?: ButtonSize; + manifestations: Manifestation[]; + dataCy?: string; +}; + +const MaterialButtonsOnlineInternal: FC = ({ + size, + manifestations, + dataCy = "material-button-online-publizon" +}) => { + const t = useText(); + const { open } = useModalButtonHandler(); + + return ( + <> + {/* Todo: add logic for the reservation / loan / Read / Listen buttons here */} + + {hasReaderManifestation(manifestations) && ( + + )} + {hasPlayerManifestation(manifestations) && ( + { + open(playerModalId(getManifestationIsbn(manifestations[0]))); + }} + dataCy={`${dataCy}-player-teaser`} + ariaDescribedBy={t("onlineMaterialTeaserText")} + /> + )} + + ); +}; + +export default MaterialButtonsOnlineInternal; 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")} + /> ); }; diff --git a/src/components/material/player-modal/PlayerModal.tsx b/src/components/material/player-modal/PlayerModal.tsx new file mode 100644 index 0000000000..1fa71e2653 --- /dev/null +++ b/src/components/material/player-modal/PlayerModal.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import Modal from "../../../core/utils/modal"; +import { useText } from "../../../core/utils/text"; +import Player, { PlayerType } from "../../reader-player/Player"; +import { playerModalId } from "./helper"; + +const PlayerModal = ({ identifier, orderId }: PlayerType) => { + const t = useText(); + + return ( + + + + ); +}; + +export default PlayerModal; diff --git a/src/components/material/player-modal/helper.ts b/src/components/material/player-modal/helper.ts new file mode 100644 index 0000000000..0c1285c6b0 --- /dev/null +++ b/src/components/material/player-modal/helper.ts @@ -0,0 +1,7 @@ +import { constructModalId } from "../../../core/utils/helpers/modal-helpers"; + +export const playerModalId = (id: string) => { + return constructModalId("player-modal", [id]); +}; + +export default {}; diff --git a/src/components/reader-player/Player.stories.tsx b/src/components/reader-player/Player.stories.tsx new file mode 100644 index 0000000000..1d9cb6591a --- /dev/null +++ b/src/components/reader-player/Player.stories.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import type { Meta, StoryObj } from "@storybook/react"; +import Player from "./Player"; + +const meta: Meta = { + title: "ReaderPlayer / 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/reader-player/Player.tsx b/src/components/reader-player/Player.tsx new file mode 100644 index 0000000000..3449421e74 --- /dev/null +++ b/src/components/reader-player/Player.tsx @@ -0,0 +1,48 @@ +import React, { useEffect } from "react"; +import { appendAsset, playerAssets, removeAppendedAssets } from "./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 ( +