@ndla/ui 36.0.2 → 37.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/es/Article/Article.js +7 -13
- package/es/Article/ArticleByline.js +79 -123
- package/es/Article/ArticleFootNotes.js +16 -11
- package/es/AudioPlayer/AudioPlayer.js +33 -35
- package/es/AudioPlayer/initAudioPlayers.js +6 -1
- package/es/ContentTypeBadge/ContentTypeBadge.js +27 -6
- package/es/Embed/AudioEmbed.js +44 -188
- package/es/Embed/BrightcoveEmbed.js +32 -127
- package/es/Embed/ConceptEmbed.js +53 -75
- package/es/Embed/EmbedErrorPlaceholder.js +41 -0
- package/es/Embed/ExternalEmbed.js +5 -12
- package/es/Embed/H5pEmbed.js +5 -15
- package/es/Embed/IframeEmbed.js +4 -4
- package/es/Embed/ImageEmbed.js +41 -153
- package/es/Embed/RelatedContentEmbed.js +3 -3
- package/es/Embed/conceptComponents.js +62 -228
- package/es/Embed/types.js +1 -0
- package/es/KeyFigure/KeyFigure.js +57 -0
- package/es/{KeyPerformanceIndicator → KeyFigure}/index.js +1 -1
- package/es/LicenseByline/EmbedByline.js +33 -8
- package/es/LicenseByline/LicenseDescription.js +16 -14
- package/es/List/OrderedList.js +48 -0
- package/es/List/UnOrderedList.js +36 -0
- package/es/List/index.js +10 -0
- package/es/Navigation/NavigationBox.js +41 -48
- package/es/Navigation/NavigationHeading.js +18 -29
- package/es/Notion/Notion.js +5 -5
- package/es/Resource/ListResource.js +9 -9
- package/es/Resource/resourceComponents.js +12 -11
- package/es/Typography/Heading.js +38 -0
- package/es/Typography/index.js +9 -0
- package/es/all.css +1 -1
- package/es/index.js +4 -2
- package/es/locale/messages-en.js +6 -2
- package/es/locale/messages-nb.js +6 -2
- package/es/locale/messages-nn.js +6 -2
- package/es/locale/messages-se.js +6 -2
- package/es/locale/messages-sma.js +6 -2
- package/es/model/ContentType.js +7 -1
- package/lib/Article/Article.d.ts +1 -3
- package/lib/Article/Article.js +7 -13
- package/lib/Article/ArticleByline.d.ts +3 -5
- package/lib/Article/ArticleByline.js +83 -126
- package/lib/Article/ArticleFootNotes.js +16 -11
- package/lib/AudioPlayer/AudioPlayer.d.ts +1 -2
- package/lib/AudioPlayer/AudioPlayer.js +33 -36
- package/lib/AudioPlayer/initAudioPlayers.d.ts +1 -0
- package/lib/AudioPlayer/initAudioPlayers.js +9 -3
- package/lib/ContentTypeBadge/ContentTypeBadge.js +27 -6
- package/lib/Embed/AudioEmbed.d.ts +3 -2
- package/lib/Embed/AudioEmbed.js +53 -192
- package/lib/Embed/BrightcoveEmbed.d.ts +3 -1
- package/lib/Embed/BrightcoveEmbed.js +32 -126
- package/lib/Embed/ConceptEmbed.d.ts +7 -2
- package/lib/Embed/ConceptEmbed.js +51 -73
- package/lib/Embed/EmbedErrorPlaceholder.d.ts +17 -0
- package/lib/Embed/EmbedErrorPlaceholder.js +48 -0
- package/lib/Embed/ExternalEmbed.js +5 -11
- package/lib/Embed/H5pEmbed.js +5 -14
- package/lib/Embed/IframeEmbed.d.ts +2 -2
- package/lib/Embed/IframeEmbed.js +4 -4
- package/lib/Embed/ImageEmbed.d.ts +3 -10
- package/lib/Embed/ImageEmbed.js +48 -161
- package/lib/Embed/RelatedContentEmbed.js +3 -3
- package/lib/Embed/conceptComponents.d.ts +4 -2
- package/lib/Embed/conceptComponents.js +67 -231
- package/lib/Embed/index.d.ts +1 -0
- package/lib/Embed/types.d.ts +14 -0
- package/lib/Embed/types.js +5 -0
- package/lib/KeyFigure/KeyFigure.d.ts +10 -0
- package/lib/KeyFigure/KeyFigure.js +62 -0
- package/lib/KeyFigure/index.d.ts +1 -0
- package/lib/KeyFigure/index.js +13 -0
- package/lib/LicenseByline/EmbedByline.d.ts +10 -2
- package/lib/LicenseByline/EmbedByline.js +32 -7
- package/lib/LicenseByline/LicenseDescription.d.ts +3 -1
- package/lib/LicenseByline/LicenseDescription.js +14 -13
- package/lib/List/OrderedList.d.ts +15 -0
- package/lib/List/OrderedList.js +56 -0
- package/lib/List/UnOrderedList.d.ts +10 -0
- package/lib/List/UnOrderedList.js +43 -0
- package/lib/List/index.d.ts +9 -0
- package/lib/List/index.js +20 -0
- package/lib/Navigation/NavigationBox.js +40 -47
- package/lib/Navigation/NavigationHeading.js +17 -28
- package/lib/Notion/Notion.js +5 -5
- package/lib/Resource/ListResource.js +8 -8
- package/lib/Resource/resourceComponents.js +12 -11
- package/lib/Typography/Heading.d.ts +26 -0
- package/lib/Typography/Heading.js +45 -0
- package/lib/Typography/index.d.ts +8 -0
- package/lib/Typography/index.js +13 -0
- package/lib/all.css +1 -1
- package/lib/index.d.ts +4 -1
- package/lib/index.js +23 -3
- package/lib/locale/messages-en.d.ts +4 -0
- package/lib/locale/messages-en.js +6 -2
- package/lib/locale/messages-nb.d.ts +4 -0
- package/lib/locale/messages-nb.js +6 -2
- package/lib/locale/messages-nn.d.ts +4 -0
- package/lib/locale/messages-nn.js +6 -2
- package/lib/locale/messages-se.d.ts +4 -0
- package/lib/locale/messages-se.js +6 -2
- package/lib/locale/messages-sma.d.ts +4 -0
- package/lib/locale/messages-sma.js +6 -2
- package/lib/model/ContentType.d.ts +1 -0
- package/lib/model/ContentType.js +9 -2
- package/package.json +15 -15
- package/src/Article/Article.tsx +1 -8
- package/src/Article/ArticleByline.tsx +78 -127
- package/src/Article/ArticleFootNotes.tsx +33 -10
- package/src/Article/component.article.scss +1 -52
- package/src/Article/component.footnotes.scss +2 -2
- package/src/Aside/component.aside.scss +3 -3
- package/src/AudioPlayer/AudioPlayer.tsx +11 -24
- package/src/AudioPlayer/initAudioPlayers.tsx +7 -2
- package/src/ContentTypeBadge/ContentTypeBadge.tsx +29 -6
- package/src/ContentTypeBadge/component.content-type-badge.scss +9 -3
- package/src/Dialog/component.dialog.scss +4 -5
- package/src/Embed/AudioEmbed.stories.tsx +5 -3
- package/src/Embed/AudioEmbed.tsx +45 -192
- package/src/Embed/BrightcoveEmbed.stories.tsx +5 -1
- package/src/Embed/BrightcoveEmbed.tsx +24 -98
- package/src/Embed/ConceptEmbed.stories.tsx +5 -0
- package/src/Embed/ConceptEmbed.tsx +43 -54
- package/src/Embed/EmbedErrorPlaceholder.tsx +59 -0
- package/src/Embed/ExternalEmbed.stories.tsx +86 -0
- package/src/Embed/ExternalEmbed.tsx +3 -8
- package/src/Embed/H5pEmbed.stories.tsx +92 -0
- package/src/Embed/H5pEmbed.tsx +3 -11
- package/src/Embed/IframeEmbed.stories.tsx +130 -0
- package/src/Embed/IframeEmbed.tsx +3 -3
- package/src/Embed/ImageEmbed.stories.tsx +3 -1
- package/src/Embed/ImageEmbed.tsx +21 -116
- package/src/Embed/RelatedContentEmbed.tsx +3 -1
- package/src/Embed/conceptComponents.tsx +67 -257
- package/src/Embed/index.ts +1 -0
- package/src/Embed/types.ts +12 -0
- package/src/FactBox/component.factbox.scss +3 -3
- package/src/Figure/component.figure-license.scss +4 -4
- package/src/Figure/component.figure.scss +1 -1
- package/src/KeyFigure/KeyFigure.stories.tsx +36 -0
- package/src/{KeyPerformanceIndicator/KeyPerformanceIndicator.tsx → KeyFigure/KeyFigure.tsx} +9 -7
- package/src/{KeyPerformanceIndicator → KeyFigure}/index.ts +1 -1
- package/src/LicenseByline/EmbedByline.stories.tsx +1 -0
- package/src/LicenseByline/EmbedByline.tsx +57 -9
- package/src/LicenseByline/LicenseDescription.tsx +9 -3
- package/src/List/OrderedList.tsx +115 -0
- package/src/List/UnOrderedList.tsx +49 -0
- package/src/List/index.ts +10 -0
- package/src/MediaList/component.medialist.scss +2 -2
- package/src/Navigation/NavigationBox.tsx +10 -14
- package/src/Navigation/NavigationHeading.tsx +15 -24
- package/src/Notion/Notion.tsx +1 -1
- package/src/RelatedArticleList/component.related-articles.scss +3 -13
- package/src/Resource/ListResource.tsx +6 -2
- package/src/Resource/resourceComponents.tsx +4 -2
- package/src/Table/component.tables.scss +0 -46
- package/src/Translation/component.translation.scss +3 -5
- package/src/Typography/Heading.tsx +96 -0
- package/src/Typography/index.ts +9 -0
- package/src/index.ts +5 -1
- package/src/locale/messages-en.ts +4 -0
- package/src/locale/messages-nb.ts +4 -0
- package/src/locale/messages-nn.ts +4 -0
- package/src/locale/messages-se.ts +4 -0
- package/src/locale/messages-sma.ts +4 -0
- package/src/model/ContentType.ts +7 -0
- package/es/KeyPerformanceIndicator/KeyPerformanceIndicator.js +0 -57
- package/lib/KeyPerformanceIndicator/KeyPerformanceIndicator.d.ts +0 -8
- package/lib/KeyPerformanceIndicator/KeyPerformanceIndicator.js +0 -62
- package/lib/KeyPerformanceIndicator/index.d.ts +0 -1
- package/lib/KeyPerformanceIndicator/index.js +0 -13
- package/src/KeyPerformanceIndicator/KeyPerformanceIndicator.stories.tsx +0 -79
|
@@ -11,6 +11,7 @@ import { Meta, StoryObj } from '@storybook/react';
|
|
|
11
11
|
import { AudioEmbedData, AudioMeta } from '@ndla/types-embed';
|
|
12
12
|
import AudioEmbed from './AudioEmbed';
|
|
13
13
|
import { defaultParameters } from '../../../../stories/defaults';
|
|
14
|
+
import StoryFavoriteButton from '../../../../stories/StoryFavoriteButton';
|
|
14
15
|
|
|
15
16
|
const embedData: AudioEmbedData = {
|
|
16
17
|
resource: 'audio',
|
|
@@ -156,9 +157,6 @@ const meta: Meta<typeof AudioEmbed> = {
|
|
|
156
157
|
title: 'Enkle komponenter/Embeds/AudioEmbed',
|
|
157
158
|
component: AudioEmbed,
|
|
158
159
|
tags: ['autodocs'],
|
|
159
|
-
args: {
|
|
160
|
-
articlePath: undefined,
|
|
161
|
-
},
|
|
162
160
|
decorators: [
|
|
163
161
|
(Story) => (
|
|
164
162
|
<div className="o-wrapper">
|
|
@@ -179,6 +177,7 @@ export default meta;
|
|
|
179
177
|
|
|
180
178
|
export const AudioEmbedStory: StoryObj<typeof AudioEmbed> = {
|
|
181
179
|
args: {
|
|
180
|
+
heartButton: StoryFavoriteButton,
|
|
182
181
|
embed: {
|
|
183
182
|
resource: 'audio',
|
|
184
183
|
status: 'success',
|
|
@@ -191,6 +190,7 @@ export const AudioEmbedStory: StoryObj<typeof AudioEmbed> = {
|
|
|
191
190
|
|
|
192
191
|
export const AudioEmbedFailed: StoryObj<typeof AudioEmbed> = {
|
|
193
192
|
args: {
|
|
193
|
+
heartButton: StoryFavoriteButton,
|
|
194
194
|
embed: {
|
|
195
195
|
resource: 'audio',
|
|
196
196
|
status: 'error',
|
|
@@ -202,6 +202,7 @@ export const AudioEmbedFailed: StoryObj<typeof AudioEmbed> = {
|
|
|
202
202
|
|
|
203
203
|
export const Podcast: StoryObj<typeof AudioEmbed> = {
|
|
204
204
|
args: {
|
|
205
|
+
heartButton: StoryFavoriteButton,
|
|
205
206
|
embed: {
|
|
206
207
|
resource: 'audio',
|
|
207
208
|
status: 'success',
|
|
@@ -214,6 +215,7 @@ export const Podcast: StoryObj<typeof AudioEmbed> = {
|
|
|
214
215
|
|
|
215
216
|
export const PodcastFailed: StoryObj<typeof AudioEmbed> = {
|
|
216
217
|
args: {
|
|
218
|
+
heartButton: StoryFavoriteButton,
|
|
217
219
|
embed: {
|
|
218
220
|
resource: 'audio',
|
|
219
221
|
status: 'error',
|
package/src/Embed/AudioEmbed.tsx
CHANGED
|
@@ -6,30 +6,21 @@
|
|
|
6
6
|
*
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { AudioMetaData } from '@ndla/types-embed';
|
|
10
|
-
import { ICopyright } from '@ndla/types-backend/image-api';
|
|
11
|
-
import {
|
|
12
|
-
figureApa7CopyString,
|
|
13
|
-
getGroupedContributorDescriptionList,
|
|
14
|
-
getLicenseByAbbreviation,
|
|
15
|
-
getLicenseCredits,
|
|
16
|
-
} from '@ndla/licenses';
|
|
17
|
-
import { ModalV2 } from '@ndla/modal';
|
|
18
|
-
import { useState } from 'react';
|
|
19
|
-
import { useTranslation } from 'react-i18next';
|
|
9
|
+
import { AudioMetaData, ImageMetaData } from '@ndla/types-embed';
|
|
20
10
|
//@ts-ignore
|
|
21
11
|
import { Remarkable } from 'remarkable';
|
|
22
|
-
import { ButtonV2, CopyButton } from '@ndla/button';
|
|
23
|
-
import { SafeLinkButton } from '@ndla/safelink';
|
|
24
12
|
import AudioPlayer from '../AudioPlayer';
|
|
25
|
-
import { Figure
|
|
26
|
-
import { FigureLicenseDialogContent } from '../Figure/FigureLicenseDialogContent';
|
|
13
|
+
import { Figure } from '../Figure';
|
|
27
14
|
import { Author } from './ImageEmbed';
|
|
15
|
+
import { EmbedByline } from '../LicenseByline';
|
|
16
|
+
import EmbedErrorPlaceholder from './EmbedErrorPlaceholder';
|
|
17
|
+
import { HeartButtonType } from './types';
|
|
28
18
|
|
|
29
19
|
interface Props {
|
|
30
20
|
embed: AudioMetaData;
|
|
31
|
-
|
|
21
|
+
heartButton?: HeartButtonType;
|
|
32
22
|
}
|
|
23
|
+
|
|
33
24
|
export const getFirstNonEmptyLicenseCredits = (authors: {
|
|
34
25
|
creators: Author[];
|
|
35
26
|
rightsholders: Author[];
|
|
@@ -42,29 +33,25 @@ const renderMarkdown = (text: string) => {
|
|
|
42
33
|
return <span dangerouslySetInnerHTML={{ __html: rendered }} />;
|
|
43
34
|
};
|
|
44
35
|
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
36
|
+
const imageMetaToMockEmbed = (
|
|
37
|
+
imageMeta: Extract<AudioMetaData, { status: 'success' }>,
|
|
38
|
+
): Extract<ImageMetaData, { status: 'success' }> => ({
|
|
39
|
+
resource: 'image',
|
|
40
|
+
status: 'success',
|
|
41
|
+
// Make sure the seq is unused. It's rarely used, but it's nice to ensure uniqueness.
|
|
42
|
+
seq: imageMeta.seq + 0.1,
|
|
43
|
+
// We check that this exists where the function is used.
|
|
44
|
+
data: imageMeta.data.imageMeta!,
|
|
45
|
+
embedData: {
|
|
46
|
+
resource: 'image',
|
|
47
|
+
resourceId: imageMeta.data.imageMeta?.id?.toString() || '',
|
|
48
|
+
alt: imageMeta.data.imageMeta?.alttext.alttext ?? '',
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const AudioEmbed = ({ embed, heartButton: HeartButton }: Props) => {
|
|
48
53
|
if (embed.status === 'error') {
|
|
49
|
-
return
|
|
50
|
-
<Figure>
|
|
51
|
-
<svg
|
|
52
|
-
fill="#8A8888"
|
|
53
|
-
height="50"
|
|
54
|
-
viewBox="0 0 24 12"
|
|
55
|
-
width="100%"
|
|
56
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
57
|
-
style={{ backgroundColor: '#EFF0F2' }}
|
|
58
|
-
>
|
|
59
|
-
<path d="M0 0h24v24H0V0z" fill="none" />
|
|
60
|
-
<path
|
|
61
|
-
transform="scale(0.3) translate(28, 8.5)"
|
|
62
|
-
d="M11 15h2v2h-2zm0-8h2v6h-2zm.99-5C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"
|
|
63
|
-
/>
|
|
64
|
-
</svg>
|
|
65
|
-
<figcaption>{t('audio.error.caption')}</figcaption>
|
|
66
|
-
</Figure>
|
|
67
|
-
);
|
|
54
|
+
return <EmbedErrorPlaceholder type={embed.embedData.type === 'standard' ? 'audio' : 'podcast'} />;
|
|
68
55
|
}
|
|
69
56
|
|
|
70
57
|
const { data, embedData, seq } = embed;
|
|
@@ -75,181 +62,47 @@ const AudioEmbed = ({ embed, articlePath }: Props) => {
|
|
|
75
62
|
|
|
76
63
|
const subtitle = data.series ? { title: data.series.title.title, url: `/podkast/${data.series.id}` } : undefined;
|
|
77
64
|
|
|
78
|
-
const textVersion = data.manuscript
|
|
79
|
-
const description = renderMarkdown(data.podcastMeta?.introduction ?? '');
|
|
65
|
+
const textVersion = data.manuscript?.manuscript.length ? renderMarkdown(data.manuscript.manuscript) : undefined;
|
|
80
66
|
|
|
81
67
|
const coverPhoto = data.podcastMeta?.coverPhoto;
|
|
82
68
|
|
|
83
69
|
const img = coverPhoto && { url: coverPhoto.url, alt: coverPhoto.altText };
|
|
84
70
|
|
|
85
|
-
const authors = getLicenseCredits(data.copyright);
|
|
86
|
-
|
|
87
|
-
const license = getLicenseByAbbreviation(data.copyright.license.license, i18n.language);
|
|
88
|
-
|
|
89
|
-
const contributors = getGroupedContributorDescriptionList(data.copyright, i18n.language).map((item) => ({
|
|
90
|
-
name: item.description,
|
|
91
|
-
type: item.label,
|
|
92
|
-
}));
|
|
93
|
-
|
|
94
71
|
const figureId = `figure-${seq}-${data.id}`;
|
|
95
72
|
|
|
96
|
-
const copyString =
|
|
97
|
-
data.audioType === 'podcast'
|
|
98
|
-
? figureApa7CopyString(
|
|
99
|
-
data.title.title,
|
|
100
|
-
undefined,
|
|
101
|
-
data.audioFile.url,
|
|
102
|
-
articlePath,
|
|
103
|
-
data.copyright,
|
|
104
|
-
data.copyright.license.license,
|
|
105
|
-
'',
|
|
106
|
-
(id: string) => t(id),
|
|
107
|
-
i18n.language,
|
|
108
|
-
)
|
|
109
|
-
: undefined;
|
|
110
|
-
const captionAuthors = getFirstNonEmptyLicenseCredits(authors);
|
|
111
73
|
return (
|
|
112
74
|
<Figure id={figureId} type="full">
|
|
113
75
|
<AudioPlayer
|
|
114
|
-
description={
|
|
76
|
+
description={data.podcastMeta?.introduction ?? ''}
|
|
115
77
|
img={img}
|
|
116
78
|
src={data.audioFile.url}
|
|
117
79
|
textVersion={textVersion}
|
|
118
80
|
title={data.title.title}
|
|
119
81
|
subtitle={subtitle}
|
|
120
82
|
/>
|
|
121
|
-
<
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
authors={captionAuthors}
|
|
131
|
-
locale={i18n.language}
|
|
132
|
-
/>
|
|
133
|
-
<ModalV2 controlled isOpen={isOpen} onClose={() => setIsOpen(false)} labelledBy="license-dialog-rules-heading">
|
|
134
|
-
{(close) => (
|
|
135
|
-
<FigureLicenseDialogContent
|
|
136
|
-
onClose={close}
|
|
137
|
-
title={data.title.title}
|
|
138
|
-
license={license}
|
|
139
|
-
authors={contributors}
|
|
140
|
-
origin={data.copyright.origin}
|
|
141
|
-
locale={i18n.language}
|
|
142
|
-
type="audio"
|
|
143
|
-
>
|
|
144
|
-
{data.copyright.license.license !== 'COPYRIGHT' && (
|
|
145
|
-
<>
|
|
146
|
-
{copyString && (
|
|
147
|
-
<CopyButton
|
|
148
|
-
variant="outline"
|
|
149
|
-
copyNode={t('license.hasCopiedTitle')}
|
|
150
|
-
onClick={() => navigator.clipboard.writeText(copyString)}
|
|
151
|
-
>
|
|
152
|
-
{t('license.copyTitle')}
|
|
153
|
-
</CopyButton>
|
|
154
|
-
)}
|
|
155
|
-
<SafeLinkButton download to={data.audioFile.url} variant="outline">
|
|
156
|
-
{t('audio.download')}
|
|
157
|
-
</SafeLinkButton>
|
|
158
|
-
</>
|
|
159
|
-
)}
|
|
160
|
-
</FigureLicenseDialogContent>
|
|
161
|
-
)}
|
|
162
|
-
</ModalV2>
|
|
83
|
+
<EmbedByline
|
|
84
|
+
error={false}
|
|
85
|
+
type={embedData.type === 'standard' ? 'audio' : 'podcast'}
|
|
86
|
+
topRounded={false}
|
|
87
|
+
bottomRounded={!data.imageMeta}
|
|
88
|
+
copyright={embed.data.copyright}
|
|
89
|
+
>
|
|
90
|
+
{HeartButton && <HeartButton embed={embed} />}
|
|
91
|
+
</EmbedByline>
|
|
163
92
|
{data.imageMeta && (
|
|
164
|
-
<
|
|
165
|
-
|
|
166
|
-
|
|
93
|
+
<EmbedByline
|
|
94
|
+
error={false}
|
|
95
|
+
first={false}
|
|
96
|
+
type="image"
|
|
97
|
+
topRounded={false}
|
|
98
|
+
bottomRounded
|
|
167
99
|
copyright={data.imageMeta.copyright}
|
|
168
|
-
|
|
169
|
-
|
|
100
|
+
>
|
|
101
|
+
{HeartButton && <HeartButton embed={imageMetaToMockEmbed(embed)} />}
|
|
102
|
+
</EmbedByline>
|
|
170
103
|
)}
|
|
171
104
|
</Figure>
|
|
172
105
|
);
|
|
173
106
|
};
|
|
174
107
|
|
|
175
|
-
interface ImageLicenseProps {
|
|
176
|
-
title: string;
|
|
177
|
-
imageUrl: string;
|
|
178
|
-
copyright: ICopyright;
|
|
179
|
-
articlePath?: string;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const ImageLicense = ({ articlePath, title, imageUrl, copyright }: ImageLicenseProps) => {
|
|
183
|
-
const { t, i18n } = useTranslation();
|
|
184
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
185
|
-
const copyString = figureApa7CopyString(
|
|
186
|
-
title,
|
|
187
|
-
undefined,
|
|
188
|
-
imageUrl,
|
|
189
|
-
articlePath,
|
|
190
|
-
copyright,
|
|
191
|
-
copyright.license.license,
|
|
192
|
-
undefined,
|
|
193
|
-
(id: string) => t(id),
|
|
194
|
-
i18n.language,
|
|
195
|
-
);
|
|
196
|
-
const license = getLicenseByAbbreviation(copyright.license.license, i18n.language);
|
|
197
|
-
const authors = getLicenseCredits(copyright);
|
|
198
|
-
|
|
199
|
-
const contributors = getGroupedContributorDescriptionList(copyright, i18n.language).map((item) => ({
|
|
200
|
-
name: item.description,
|
|
201
|
-
type: item.label,
|
|
202
|
-
}));
|
|
203
|
-
|
|
204
|
-
const captionAuthors = getFirstNonEmptyLicenseCredits(authors);
|
|
205
|
-
|
|
206
|
-
return (
|
|
207
|
-
<>
|
|
208
|
-
<FigureCaption
|
|
209
|
-
figureId=""
|
|
210
|
-
id=""
|
|
211
|
-
licenseRights={license.rights}
|
|
212
|
-
modalButton={
|
|
213
|
-
<ButtonV2 variant="outline" shape="pill" size="small" onClick={() => setIsOpen(true)}>
|
|
214
|
-
{t('image.reuse')}
|
|
215
|
-
</ButtonV2>
|
|
216
|
-
}
|
|
217
|
-
authors={captionAuthors}
|
|
218
|
-
locale={i18n.language}
|
|
219
|
-
>
|
|
220
|
-
<ModalV2 controlled isOpen={isOpen} onClose={() => setIsOpen(false)}>
|
|
221
|
-
{(close) => (
|
|
222
|
-
<FigureLicenseDialogContent
|
|
223
|
-
onClose={close}
|
|
224
|
-
title={title}
|
|
225
|
-
license={license}
|
|
226
|
-
authors={contributors}
|
|
227
|
-
origin={copyright.origin}
|
|
228
|
-
locale={i18n.language}
|
|
229
|
-
type="image"
|
|
230
|
-
>
|
|
231
|
-
{copyright.license.license !== 'COPYRIGHTED' && (
|
|
232
|
-
<>
|
|
233
|
-
{copyString && (
|
|
234
|
-
<CopyButton
|
|
235
|
-
variant="outline"
|
|
236
|
-
copyNode={t('license.hasCopiedTitle')}
|
|
237
|
-
onClick={() => navigator.clipboard.writeText(copyString)}
|
|
238
|
-
>
|
|
239
|
-
{t('license.copyTitle')}
|
|
240
|
-
</CopyButton>
|
|
241
|
-
)}
|
|
242
|
-
<SafeLinkButton download to={imageUrl} variant="outline">
|
|
243
|
-
{t('image.download')}
|
|
244
|
-
</SafeLinkButton>
|
|
245
|
-
</>
|
|
246
|
-
)}
|
|
247
|
-
</FigureLicenseDialogContent>
|
|
248
|
-
)}
|
|
249
|
-
</ModalV2>
|
|
250
|
-
</FigureCaption>
|
|
251
|
-
</>
|
|
252
|
-
);
|
|
253
|
-
};
|
|
254
|
-
|
|
255
108
|
export default AudioEmbed;
|
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
|
|
9
9
|
import React from 'react';
|
|
10
10
|
import { Meta, StoryObj } from '@storybook/react';
|
|
11
|
-
import { BrightcoveData, BrightcoveEmbedData, BrightcoveMetaData } from '@ndla/types-embed';
|
|
11
|
+
import { BrightcoveData, BrightcoveEmbedData, BrightcoveMetaData, EmbedMetaData } from '@ndla/types-embed';
|
|
12
12
|
import BrightcoveEmbed from './BrightcoveEmbed';
|
|
13
13
|
import { defaultParameters } from '../../../../stories/defaults';
|
|
14
|
+
import StoryFavoriteButton from '../../../../stories/StoryFavoriteButton';
|
|
14
15
|
|
|
15
16
|
const embedData: BrightcoveEmbedData = {
|
|
16
17
|
resource: 'brightcove',
|
|
@@ -179,6 +180,7 @@ export default meta;
|
|
|
179
180
|
|
|
180
181
|
export const BrightcoveEmbedStory: StoryObj<typeof BrightcoveEmbed> = {
|
|
181
182
|
args: {
|
|
183
|
+
heartButton: StoryFavoriteButton,
|
|
182
184
|
embed: {
|
|
183
185
|
resource: 'brightcove',
|
|
184
186
|
status: 'success',
|
|
@@ -191,12 +193,14 @@ export const BrightcoveEmbedStory: StoryObj<typeof BrightcoveEmbed> = {
|
|
|
191
193
|
|
|
192
194
|
export const VisuallyInterpreted: StoryObj<typeof BrightcoveEmbed> = {
|
|
193
195
|
args: {
|
|
196
|
+
heartButton: StoryFavoriteButton,
|
|
194
197
|
embed: visuallyInterpretedEmbedMetaData,
|
|
195
198
|
},
|
|
196
199
|
};
|
|
197
200
|
|
|
198
201
|
export const BrightcoveFailed: StoryObj<typeof BrightcoveEmbed> = {
|
|
199
202
|
args: {
|
|
203
|
+
heartButton: StoryFavoriteButton,
|
|
200
204
|
embed: {
|
|
201
205
|
resource: 'brightcove',
|
|
202
206
|
status: 'error',
|
|
@@ -10,20 +10,19 @@ import sortBy from 'lodash/sortBy';
|
|
|
10
10
|
import isNumber from 'lodash/isNumber';
|
|
11
11
|
import styled from '@emotion/styled';
|
|
12
12
|
import { spacing } from '@ndla/core';
|
|
13
|
-
import { getGroupedContributorDescriptionList, getLicenseByAbbreviation } from '@ndla/licenses';
|
|
14
13
|
import { useEffect, useRef, useState } from 'react';
|
|
15
|
-
import { ModalV2 } from '@ndla/modal';
|
|
16
|
-
import { SafeLinkButton } from '@ndla/safelink';
|
|
17
14
|
import { BrightcoveEmbedData, BrightcoveMetaData, BrightcoveVideoSource } from '@ndla/types-embed';
|
|
18
15
|
import { useTranslation } from 'react-i18next';
|
|
19
|
-
import { ButtonV2
|
|
20
|
-
import { Figure
|
|
21
|
-
import {
|
|
22
|
-
import
|
|
16
|
+
import { ButtonV2 } from '@ndla/button';
|
|
17
|
+
import { Figure } from '../Figure';
|
|
18
|
+
import { EmbedByline } from '../LicenseByline';
|
|
19
|
+
import EmbedErrorPlaceholder from './EmbedErrorPlaceholder';
|
|
20
|
+
import { HeartButtonType } from './types';
|
|
23
21
|
|
|
24
22
|
interface Props {
|
|
25
23
|
embed: BrightcoveMetaData;
|
|
26
24
|
isConcept?: boolean;
|
|
25
|
+
heartButton?: HeartButtonType;
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
const LinkedVideoButton = styled(ButtonV2)`
|
|
@@ -57,12 +56,12 @@ const getIframeProps = (data: BrightcoveEmbedData, sources: BrightcoveVideoSourc
|
|
|
57
56
|
width: source?.width ?? '640',
|
|
58
57
|
};
|
|
59
58
|
};
|
|
60
|
-
const BrightcoveEmbed = ({ embed, isConcept }: Props) => {
|
|
61
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
59
|
+
const BrightcoveEmbed = ({ embed, isConcept, heartButton: HeartButton }: Props) => {
|
|
62
60
|
const [showOriginalVideo, setShowOriginalVideo] = useState(true);
|
|
63
|
-
const { t
|
|
61
|
+
const { t } = useTranslation();
|
|
64
62
|
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
65
63
|
const { embedData } = embed;
|
|
64
|
+
const fallbackTitle = `${t('embed.type.video')}: ${embedData.videoid}`;
|
|
66
65
|
|
|
67
66
|
useEffect(() => {
|
|
68
67
|
const iframe = iframeRef.current;
|
|
@@ -75,44 +74,27 @@ const BrightcoveEmbed = ({ embed, isConcept }: Props) => {
|
|
|
75
74
|
}, []);
|
|
76
75
|
if (embed.status === 'error') {
|
|
77
76
|
return (
|
|
78
|
-
<
|
|
77
|
+
<EmbedErrorPlaceholder type="video">
|
|
79
78
|
<BrightcoveIframe
|
|
80
79
|
ref={iframeRef}
|
|
81
|
-
title={
|
|
82
|
-
aria-label={
|
|
80
|
+
title={embedData.alt ?? fallbackTitle}
|
|
81
|
+
aria-label={embedData.alt ?? fallbackTitle}
|
|
83
82
|
frameBorder="0"
|
|
84
83
|
{...getIframeProps(embedData, [])}
|
|
85
84
|
allowFullScreen
|
|
86
85
|
/>
|
|
87
|
-
|
|
88
|
-
</Figure>
|
|
86
|
+
</EmbedErrorPlaceholder>
|
|
89
87
|
);
|
|
90
88
|
}
|
|
91
89
|
const { data, seq } = embed;
|
|
92
90
|
|
|
93
91
|
const linkedVideoId = isNumeric(data.link?.text) ? data.link?.text : undefined;
|
|
94
92
|
|
|
95
|
-
const license = getLicenseByAbbreviation(data.copyright?.license.license ?? '', i18n.language);
|
|
96
|
-
const contributors = data.copyright
|
|
97
|
-
? getGroupedContributorDescriptionList(data.copyright, i18n.language).map((item) => ({
|
|
98
|
-
name: item.description,
|
|
99
|
-
type: item.label,
|
|
100
|
-
}))
|
|
101
|
-
: [];
|
|
102
|
-
|
|
103
|
-
const { rightsholders = [], creators = [], processors = [] } = data.copyright ?? {};
|
|
104
|
-
|
|
105
|
-
const download = sortBy(
|
|
106
|
-
data.sources.filter((src) => src.container === 'MP4' && src.src),
|
|
107
|
-
(src) => src.size,
|
|
108
|
-
)?.[0]?.src;
|
|
109
|
-
|
|
110
93
|
const figureId = `figure-${seq}-${data.id}`;
|
|
111
94
|
const originalVideoProps = getIframeProps(embedData, data.sources);
|
|
112
95
|
const alternativeVideoProps = linkedVideoId
|
|
113
96
|
? getIframeProps({ ...embedData, videoid: linkedVideoId }, data.sources)
|
|
114
97
|
: undefined;
|
|
115
|
-
const captionAuthors = getFirstNonEmptyLicenseCredits({ rightsholders, creators, processors });
|
|
116
98
|
|
|
117
99
|
return (
|
|
118
100
|
<Figure id={figureId} type={isConcept ? 'full-column' : 'full'} resizeIframe>
|
|
@@ -120,24 +102,20 @@ const BrightcoveEmbed = ({ embed, isConcept }: Props) => {
|
|
|
120
102
|
<BrightcoveIframe
|
|
121
103
|
ref={iframeRef}
|
|
122
104
|
className="original"
|
|
123
|
-
title={
|
|
124
|
-
aria-label={
|
|
105
|
+
title={embedData.alt ?? data.name ?? fallbackTitle}
|
|
106
|
+
aria-label={embedData.alt ?? data.name ?? fallbackTitle}
|
|
125
107
|
frameBorder="0"
|
|
126
108
|
{...(alternativeVideoProps && !showOriginalVideo ? alternativeVideoProps : originalVideoProps)}
|
|
127
109
|
allowFullScreen
|
|
128
110
|
/>
|
|
129
111
|
</div>
|
|
130
|
-
<
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
{t('video.reuse')}
|
|
138
|
-
</ButtonV2>
|
|
139
|
-
}
|
|
140
|
-
linkedVideoButton={
|
|
112
|
+
<EmbedByline
|
|
113
|
+
type="video"
|
|
114
|
+
copyright={data.copyright!}
|
|
115
|
+
description={embedData.caption ?? data.description ?? ''}
|
|
116
|
+
bottomRounded
|
|
117
|
+
>
|
|
118
|
+
{!!linkedVideoId && (
|
|
141
119
|
<LinkedVideoButton
|
|
142
120
|
variant="outline"
|
|
143
121
|
shape="pill"
|
|
@@ -146,63 +124,11 @@ const BrightcoveEmbed = ({ embed, isConcept }: Props) => {
|
|
|
146
124
|
>
|
|
147
125
|
{t(`figure.button.${!showOriginalVideo ? 'original' : 'alternative'}`)}
|
|
148
126
|
</LinkedVideoButton>
|
|
149
|
-
}
|
|
150
|
-
licenseRights={license.rights}
|
|
151
|
-
authors={captionAuthors}
|
|
152
|
-
hasLinkedVideo={!!linkedVideoId}
|
|
153
|
-
/>
|
|
154
|
-
<ModalV2 controlled isOpen={isOpen} onClose={() => setIsOpen(false)} labelledBy="license-dialog-rules-heading">
|
|
155
|
-
{(close) => (
|
|
156
|
-
<FigureLicenseDialogContent
|
|
157
|
-
onClose={close}
|
|
158
|
-
title={data.name}
|
|
159
|
-
locale={i18n.language}
|
|
160
|
-
license={license}
|
|
161
|
-
authors={contributors}
|
|
162
|
-
type="video"
|
|
163
|
-
>
|
|
164
|
-
<VideoLicenseButtons
|
|
165
|
-
download={download}
|
|
166
|
-
licenseCode={data.copyright?.license.license}
|
|
167
|
-
src={originalVideoProps.src}
|
|
168
|
-
width={originalVideoProps.width}
|
|
169
|
-
height={originalVideoProps.height}
|
|
170
|
-
name={data.name}
|
|
171
|
-
/>
|
|
172
|
-
</FigureLicenseDialogContent>
|
|
173
127
|
)}
|
|
174
|
-
|
|
128
|
+
{HeartButton && <HeartButton embed={embed} />}
|
|
129
|
+
</EmbedByline>
|
|
175
130
|
</Figure>
|
|
176
131
|
);
|
|
177
132
|
};
|
|
178
133
|
|
|
179
|
-
interface VideoLicenseButtonsProps {
|
|
180
|
-
download: string;
|
|
181
|
-
licenseCode?: string;
|
|
182
|
-
src: string;
|
|
183
|
-
width: string | number;
|
|
184
|
-
height: string | number;
|
|
185
|
-
name?: string;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const VideoLicenseButtons = ({ download, src, width, height, name, licenseCode }: VideoLicenseButtonsProps) => {
|
|
189
|
-
const { t } = useTranslation();
|
|
190
|
-
return (
|
|
191
|
-
<>
|
|
192
|
-
{licenseCode !== 'COPYRIGHTED' && (
|
|
193
|
-
<SafeLinkButton key="download" to={download} variant="outline" download>
|
|
194
|
-
{t('video.download')}
|
|
195
|
-
</SafeLinkButton>
|
|
196
|
-
)}
|
|
197
|
-
<CopyButton
|
|
198
|
-
variant="outline"
|
|
199
|
-
copyNode={t('license.hasCopiedTitle')}
|
|
200
|
-
onClick={() => navigator.clipboard.writeText(makeIframeString(src, width, height, name))}
|
|
201
|
-
>
|
|
202
|
-
{t('license.embed')}
|
|
203
|
-
</CopyButton>
|
|
204
|
-
</>
|
|
205
|
-
);
|
|
206
|
-
};
|
|
207
|
-
|
|
208
134
|
export default BrightcoveEmbed;
|
|
@@ -11,6 +11,7 @@ import { Meta, StoryObj } from '@storybook/react';
|
|
|
11
11
|
import { ConceptData, ConceptEmbedData } from '@ndla/types-embed';
|
|
12
12
|
import ConceptEmbed from './ConceptEmbed';
|
|
13
13
|
import { defaultParameters } from '../../../../stories/defaults';
|
|
14
|
+
import StoryFavoriteButton from '../../../../stories/StoryFavoriteButton';
|
|
14
15
|
|
|
15
16
|
const blockEmbedData: ConceptEmbedData = {
|
|
16
17
|
contentId: '35',
|
|
@@ -145,6 +146,7 @@ export default meta;
|
|
|
145
146
|
|
|
146
147
|
export const Block: StoryObj<typeof ConceptEmbed> = {
|
|
147
148
|
args: {
|
|
149
|
+
heartButton: StoryFavoriteButton,
|
|
148
150
|
embed: {
|
|
149
151
|
resource: 'concept',
|
|
150
152
|
status: 'success',
|
|
@@ -157,6 +159,7 @@ export const Block: StoryObj<typeof ConceptEmbed> = {
|
|
|
157
159
|
|
|
158
160
|
export const BlockFailed: StoryObj<typeof ConceptEmbed> = {
|
|
159
161
|
args: {
|
|
162
|
+
heartButton: StoryFavoriteButton,
|
|
160
163
|
embed: {
|
|
161
164
|
resource: 'concept',
|
|
162
165
|
status: 'error',
|
|
@@ -168,6 +171,7 @@ export const BlockFailed: StoryObj<typeof ConceptEmbed> = {
|
|
|
168
171
|
|
|
169
172
|
export const Inline: StoryObj<typeof ConceptEmbed> = {
|
|
170
173
|
args: {
|
|
174
|
+
heartButton: StoryFavoriteButton,
|
|
171
175
|
embed: {
|
|
172
176
|
resource: 'concept',
|
|
173
177
|
status: 'success',
|
|
@@ -180,6 +184,7 @@ export const Inline: StoryObj<typeof ConceptEmbed> = {
|
|
|
180
184
|
|
|
181
185
|
export const InlineFailed: StoryObj<typeof ConceptEmbed> = {
|
|
182
186
|
args: {
|
|
187
|
+
heartButton: StoryFavoriteButton,
|
|
183
188
|
embed: {
|
|
184
189
|
resource: 'concept',
|
|
185
190
|
status: 'error',
|