@ndla/ui 36.0.2 → 37.0.0

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.
Files changed (164) hide show
  1. package/es/Article/Article.js +7 -13
  2. package/es/Article/ArticleByline.js +79 -123
  3. package/es/Article/ArticleFootNotes.js +16 -11
  4. package/es/AudioPlayer/AudioPlayer.js +33 -35
  5. package/es/AudioPlayer/initAudioPlayers.js +6 -1
  6. package/es/Embed/AudioEmbed.js +44 -188
  7. package/es/Embed/BrightcoveEmbed.js +26 -122
  8. package/es/Embed/ConceptEmbed.js +53 -75
  9. package/es/Embed/EmbedErrorPlaceholder.js +41 -0
  10. package/es/Embed/ExternalEmbed.js +5 -12
  11. package/es/Embed/H5pEmbed.js +4 -14
  12. package/es/Embed/IframeEmbed.js +4 -4
  13. package/es/Embed/ImageEmbed.js +41 -153
  14. package/es/Embed/conceptComponents.js +62 -228
  15. package/es/Embed/types.js +1 -0
  16. package/es/KeyFigure/KeyFigure.js +57 -0
  17. package/es/{KeyPerformanceIndicator → KeyFigure}/index.js +1 -1
  18. package/es/LicenseByline/EmbedByline.js +33 -8
  19. package/es/LicenseByline/LicenseDescription.js +16 -14
  20. package/es/List/OrderedList.js +48 -0
  21. package/es/List/UnOrderedList.js +36 -0
  22. package/es/List/index.js +10 -0
  23. package/es/Navigation/NavigationBox.js +41 -48
  24. package/es/Navigation/NavigationHeading.js +18 -29
  25. package/es/Notion/Notion.js +5 -5
  26. package/es/Resource/resourceComponents.js +12 -11
  27. package/es/Typography/Heading.js +38 -0
  28. package/es/Typography/index.js +9 -0
  29. package/es/all.css +1 -1
  30. package/es/index.js +4 -2
  31. package/es/locale/messages-en.js +6 -2
  32. package/es/locale/messages-nb.js +6 -2
  33. package/es/locale/messages-nn.js +6 -2
  34. package/es/locale/messages-se.js +6 -2
  35. package/es/locale/messages-sma.js +6 -2
  36. package/es/model/ContentType.js +7 -1
  37. package/lib/Article/Article.d.ts +1 -3
  38. package/lib/Article/Article.js +7 -13
  39. package/lib/Article/ArticleByline.d.ts +3 -5
  40. package/lib/Article/ArticleByline.js +83 -126
  41. package/lib/Article/ArticleFootNotes.js +16 -11
  42. package/lib/AudioPlayer/AudioPlayer.d.ts +1 -2
  43. package/lib/AudioPlayer/AudioPlayer.js +33 -36
  44. package/lib/AudioPlayer/initAudioPlayers.d.ts +1 -0
  45. package/lib/AudioPlayer/initAudioPlayers.js +9 -3
  46. package/lib/Embed/AudioEmbed.d.ts +3 -2
  47. package/lib/Embed/AudioEmbed.js +53 -192
  48. package/lib/Embed/BrightcoveEmbed.d.ts +3 -1
  49. package/lib/Embed/BrightcoveEmbed.js +26 -121
  50. package/lib/Embed/ConceptEmbed.d.ts +7 -2
  51. package/lib/Embed/ConceptEmbed.js +51 -73
  52. package/lib/Embed/EmbedErrorPlaceholder.d.ts +17 -0
  53. package/lib/Embed/EmbedErrorPlaceholder.js +48 -0
  54. package/lib/Embed/ExternalEmbed.js +5 -11
  55. package/lib/Embed/H5pEmbed.js +4 -13
  56. package/lib/Embed/IframeEmbed.d.ts +2 -2
  57. package/lib/Embed/IframeEmbed.js +4 -4
  58. package/lib/Embed/ImageEmbed.d.ts +3 -10
  59. package/lib/Embed/ImageEmbed.js +48 -161
  60. package/lib/Embed/conceptComponents.d.ts +4 -2
  61. package/lib/Embed/conceptComponents.js +67 -231
  62. package/lib/Embed/index.d.ts +1 -0
  63. package/lib/Embed/types.d.ts +14 -0
  64. package/lib/Embed/types.js +5 -0
  65. package/lib/KeyFigure/KeyFigure.d.ts +10 -0
  66. package/lib/KeyFigure/KeyFigure.js +62 -0
  67. package/lib/KeyFigure/index.d.ts +1 -0
  68. package/lib/KeyFigure/index.js +13 -0
  69. package/lib/LicenseByline/EmbedByline.d.ts +10 -2
  70. package/lib/LicenseByline/EmbedByline.js +32 -7
  71. package/lib/LicenseByline/LicenseDescription.d.ts +3 -1
  72. package/lib/LicenseByline/LicenseDescription.js +14 -13
  73. package/lib/List/OrderedList.d.ts +15 -0
  74. package/lib/List/OrderedList.js +56 -0
  75. package/lib/List/UnOrderedList.d.ts +10 -0
  76. package/lib/List/UnOrderedList.js +43 -0
  77. package/lib/List/index.d.ts +9 -0
  78. package/lib/List/index.js +20 -0
  79. package/lib/Navigation/NavigationBox.js +40 -47
  80. package/lib/Navigation/NavigationHeading.js +17 -28
  81. package/lib/Notion/Notion.js +5 -5
  82. package/lib/Resource/resourceComponents.js +12 -11
  83. package/lib/Typography/Heading.d.ts +26 -0
  84. package/lib/Typography/Heading.js +45 -0
  85. package/lib/Typography/index.d.ts +8 -0
  86. package/lib/Typography/index.js +13 -0
  87. package/lib/all.css +1 -1
  88. package/lib/index.d.ts +4 -1
  89. package/lib/index.js +23 -3
  90. package/lib/locale/messages-en.d.ts +4 -0
  91. package/lib/locale/messages-en.js +6 -2
  92. package/lib/locale/messages-nb.d.ts +4 -0
  93. package/lib/locale/messages-nb.js +6 -2
  94. package/lib/locale/messages-nn.d.ts +4 -0
  95. package/lib/locale/messages-nn.js +6 -2
  96. package/lib/locale/messages-se.d.ts +4 -0
  97. package/lib/locale/messages-se.js +6 -2
  98. package/lib/locale/messages-sma.d.ts +4 -0
  99. package/lib/locale/messages-sma.js +6 -2
  100. package/lib/model/ContentType.d.ts +1 -0
  101. package/lib/model/ContentType.js +9 -2
  102. package/package.json +15 -15
  103. package/src/Article/Article.tsx +1 -8
  104. package/src/Article/ArticleByline.tsx +78 -127
  105. package/src/Article/ArticleFootNotes.tsx +33 -10
  106. package/src/Article/component.article.scss +1 -52
  107. package/src/Article/component.footnotes.scss +2 -2
  108. package/src/Aside/component.aside.scss +3 -3
  109. package/src/AudioPlayer/AudioPlayer.tsx +11 -24
  110. package/src/AudioPlayer/initAudioPlayers.tsx +7 -2
  111. package/src/Dialog/component.dialog.scss +4 -5
  112. package/src/Embed/AudioEmbed.stories.tsx +5 -3
  113. package/src/Embed/AudioEmbed.tsx +45 -192
  114. package/src/Embed/BrightcoveEmbed.stories.tsx +5 -1
  115. package/src/Embed/BrightcoveEmbed.tsx +19 -94
  116. package/src/Embed/ConceptEmbed.stories.tsx +5 -0
  117. package/src/Embed/ConceptEmbed.tsx +43 -54
  118. package/src/Embed/EmbedErrorPlaceholder.tsx +59 -0
  119. package/src/Embed/ExternalEmbed.stories.tsx +86 -0
  120. package/src/Embed/ExternalEmbed.tsx +3 -8
  121. package/src/Embed/H5pEmbed.stories.tsx +92 -0
  122. package/src/Embed/H5pEmbed.tsx +2 -10
  123. package/src/Embed/IframeEmbed.stories.tsx +130 -0
  124. package/src/Embed/IframeEmbed.tsx +3 -3
  125. package/src/Embed/ImageEmbed.stories.tsx +3 -1
  126. package/src/Embed/ImageEmbed.tsx +21 -116
  127. package/src/Embed/conceptComponents.tsx +67 -257
  128. package/src/Embed/index.ts +1 -0
  129. package/src/Embed/types.ts +12 -0
  130. package/src/FactBox/component.factbox.scss +3 -3
  131. package/src/Figure/component.figure-license.scss +4 -4
  132. package/src/Figure/component.figure.scss +1 -1
  133. package/src/KeyFigure/KeyFigure.stories.tsx +36 -0
  134. package/src/{KeyPerformanceIndicator/KeyPerformanceIndicator.tsx → KeyFigure/KeyFigure.tsx} +9 -7
  135. package/src/{KeyPerformanceIndicator → KeyFigure}/index.ts +1 -1
  136. package/src/LicenseByline/EmbedByline.stories.tsx +1 -0
  137. package/src/LicenseByline/EmbedByline.tsx +57 -9
  138. package/src/LicenseByline/LicenseDescription.tsx +9 -3
  139. package/src/List/OrderedList.tsx +115 -0
  140. package/src/List/UnOrderedList.tsx +49 -0
  141. package/src/List/index.ts +10 -0
  142. package/src/MediaList/component.medialist.scss +2 -2
  143. package/src/Navigation/NavigationBox.tsx +10 -14
  144. package/src/Navigation/NavigationHeading.tsx +15 -24
  145. package/src/Notion/Notion.tsx +1 -1
  146. package/src/RelatedArticleList/component.related-articles.scss +3 -13
  147. package/src/Resource/resourceComponents.tsx +4 -2
  148. package/src/Table/component.tables.scss +0 -46
  149. package/src/Translation/component.translation.scss +3 -5
  150. package/src/Typography/Heading.tsx +96 -0
  151. package/src/Typography/index.ts +9 -0
  152. package/src/index.ts +5 -1
  153. package/src/locale/messages-en.ts +4 -0
  154. package/src/locale/messages-nb.ts +4 -0
  155. package/src/locale/messages-nn.ts +4 -0
  156. package/src/locale/messages-se.ts +4 -0
  157. package/src/locale/messages-sma.ts +4 -0
  158. package/src/model/ContentType.ts +7 -0
  159. package/es/KeyPerformanceIndicator/KeyPerformanceIndicator.js +0 -57
  160. package/lib/KeyPerformanceIndicator/KeyPerformanceIndicator.d.ts +0 -8
  161. package/lib/KeyPerformanceIndicator/KeyPerformanceIndicator.js +0 -62
  162. package/lib/KeyPerformanceIndicator/index.d.ts +0 -1
  163. package/lib/KeyPerformanceIndicator/index.js +0 -13
  164. package/src/KeyPerformanceIndicator/KeyPerformanceIndicator.stories.tsx +0 -79
@@ -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, FigureCaption } from '../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
- articlePath?: string;
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 AudioEmbed = ({ embed, articlePath }: Props) => {
46
- const { t, i18n } = useTranslation();
47
- const [isOpen, setIsOpen] = useState(false);
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 && renderMarkdown(data.manuscript.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={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
- <FigureCaption
122
- id=""
123
- figureId=""
124
- modalButton={
125
- <ButtonV2 variant="outline" shape="pill" size="small" onClick={() => setIsOpen(true)}>
126
- {t('audio.reuse')}
127
- </ButtonV2>
128
- }
129
- licenseRights={license.rights}
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
- <ImageLicense
165
- title={data.imageMeta.title.title}
166
- imageUrl={data.imageMeta.imageUrl}
93
+ <EmbedByline
94
+ error={false}
95
+ first={false}
96
+ type="image"
97
+ topRounded={false}
98
+ bottomRounded
167
99
  copyright={data.imageMeta.copyright}
168
- articlePath={articlePath}
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, CopyButton } from '@ndla/button';
20
- import { Figure, FigureCaption } from '../Figure';
21
- import { FigureLicenseDialogContent } from '../Figure/FigureLicenseDialogContent';
22
- import { getFirstNonEmptyLicenseCredits } from './AudioEmbed';
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,10 +56,9 @@ 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, i18n } = useTranslation();
61
+ const { t } = useTranslation();
64
62
  const iframeRef = useRef<HTMLIFrameElement>(null);
65
63
  const { embedData } = embed;
66
64
 
@@ -75,7 +73,7 @@ const BrightcoveEmbed = ({ embed, isConcept }: Props) => {
75
73
  }, []);
76
74
  if (embed.status === 'error') {
77
75
  return (
78
- <Figure type={isConcept ? 'full-column' : 'full'} resizeIframe>
76
+ <EmbedErrorPlaceholder type="video">
79
77
  <BrightcoveIframe
80
78
  ref={iframeRef}
81
79
  title={`Video: ${embedData.videoid ?? ''}`}
@@ -84,35 +82,18 @@ const BrightcoveEmbed = ({ embed, isConcept }: Props) => {
84
82
  {...getIframeProps(embedData, [])}
85
83
  allowFullScreen
86
84
  />
87
- <figcaption>{t('video.error')}</figcaption>
88
- </Figure>
85
+ </EmbedErrorPlaceholder>
89
86
  );
90
87
  }
91
88
  const { data, seq } = embed;
92
89
 
93
90
  const linkedVideoId = isNumeric(data.link?.text) ? data.link?.text : undefined;
94
91
 
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
92
  const figureId = `figure-${seq}-${data.id}`;
111
93
  const originalVideoProps = getIframeProps(embedData, data.sources);
112
94
  const alternativeVideoProps = linkedVideoId
113
95
  ? getIframeProps({ ...embedData, videoid: linkedVideoId }, data.sources)
114
96
  : undefined;
115
- const captionAuthors = getFirstNonEmptyLicenseCredits({ rightsholders, creators, processors });
116
97
 
117
98
  return (
118
99
  <Figure id={figureId} type={isConcept ? 'full-column' : 'full'} resizeIframe>
@@ -127,17 +108,13 @@ const BrightcoveEmbed = ({ embed, isConcept }: Props) => {
127
108
  allowFullScreen
128
109
  />
129
110
  </div>
130
- <FigureCaption
131
- figureId={figureId}
132
- id={data.id}
133
- locale={i18n.language}
134
- caption={embedData.caption ?? ''}
135
- modalButton={
136
- <ButtonV2 variant="outline" shape="pill" size="small" onClick={() => setIsOpen(true)}>
137
- {t('video.reuse')}
138
- </ButtonV2>
139
- }
140
- linkedVideoButton={
111
+ <EmbedByline
112
+ type="video"
113
+ copyright={data.copyright!}
114
+ description={embedData.caption ?? data.description ?? ''}
115
+ bottomRounded
116
+ >
117
+ {!!linkedVideoId && (
141
118
  <LinkedVideoButton
142
119
  variant="outline"
143
120
  shape="pill"
@@ -146,63 +123,11 @@ const BrightcoveEmbed = ({ embed, isConcept }: Props) => {
146
123
  >
147
124
  {t(`figure.button.${!showOriginalVideo ? 'original' : 'alternative'}`)}
148
125
  </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
126
  )}
174
- </ModalV2>
127
+ {HeartButton && <HeartButton embed={embed} />}
128
+ </EmbedByline>
175
129
  </Figure>
176
130
  );
177
131
  };
178
132
 
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
133
  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',