@pie-lib/editable-html-tip-tap 1.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 (167) hide show
  1. package/CHANGELOG.json +32 -0
  2. package/CHANGELOG.md +2280 -0
  3. package/lib/__tests__/editor.test.js +470 -0
  4. package/lib/__tests__/serialization.test.js +246 -0
  5. package/lib/__tests__/utils.js +106 -0
  6. package/lib/block-tags.js +25 -0
  7. package/lib/constants.js +16 -0
  8. package/lib/editor.js +1356 -0
  9. package/lib/extensions/MediaView.js +112 -0
  10. package/lib/extensions/characters.js +65 -0
  11. package/lib/extensions/component.js +325 -0
  12. package/lib/extensions/css.js +252 -0
  13. package/lib/extensions/custom-toolbar-wrapper.js +124 -0
  14. package/lib/extensions/image.js +106 -0
  15. package/lib/extensions/math.js +330 -0
  16. package/lib/extensions/media.js +276 -0
  17. package/lib/extensions/responseArea.js +278 -0
  18. package/lib/index.js +1213 -0
  19. package/lib/old-index.js +269 -0
  20. package/lib/parse-html.js +16 -0
  21. package/lib/plugins/characters/custom-popper.js +73 -0
  22. package/lib/plugins/characters/index.js +305 -0
  23. package/lib/plugins/characters/utils.js +381 -0
  24. package/lib/plugins/css/icons/index.js +37 -0
  25. package/lib/plugins/css/index.js +390 -0
  26. package/lib/plugins/customPlugin/index.js +114 -0
  27. package/lib/plugins/html/icons/index.js +38 -0
  28. package/lib/plugins/html/index.js +81 -0
  29. package/lib/plugins/image/__tests__/component.test.js +51 -0
  30. package/lib/plugins/image/__tests__/image-toolbar-logic.test.js +56 -0
  31. package/lib/plugins/image/__tests__/image-toolbar.test.js +26 -0
  32. package/lib/plugins/image/__tests__/index.test.js +98 -0
  33. package/lib/plugins/image/__tests__/insert-image-handler.test.js +125 -0
  34. package/lib/plugins/image/__tests__/mock-change.js +25 -0
  35. package/lib/plugins/image/alt-dialog.js +129 -0
  36. package/lib/plugins/image/component.js +419 -0
  37. package/lib/plugins/image/image-toolbar.js +177 -0
  38. package/lib/plugins/image/index.js +263 -0
  39. package/lib/plugins/image/insert-image-handler.js +117 -0
  40. package/lib/plugins/index.js +413 -0
  41. package/lib/plugins/list/__tests__/index.test.js +79 -0
  42. package/lib/plugins/list/index.js +334 -0
  43. package/lib/plugins/math/__tests__/index.test.js +300 -0
  44. package/lib/plugins/math/index.js +454 -0
  45. package/lib/plugins/media/__tests__/index.test.js +71 -0
  46. package/lib/plugins/media/index.js +387 -0
  47. package/lib/plugins/media/media-dialog.js +709 -0
  48. package/lib/plugins/media/media-toolbar.js +101 -0
  49. package/lib/plugins/media/media-wrapper.js +93 -0
  50. package/lib/plugins/rendering/index.js +46 -0
  51. package/lib/plugins/respArea/drag-in-the-blank/choice.js +289 -0
  52. package/lib/plugins/respArea/drag-in-the-blank/index.js +94 -0
  53. package/lib/plugins/respArea/explicit-constructed-response/index.js +120 -0
  54. package/lib/plugins/respArea/icons/index.js +95 -0
  55. package/lib/plugins/respArea/index.js +341 -0
  56. package/lib/plugins/respArea/inline-dropdown/index.js +126 -0
  57. package/lib/plugins/respArea/math-templated/index.js +130 -0
  58. package/lib/plugins/respArea/utils.js +125 -0
  59. package/lib/plugins/table/CustomTablePlugin.js +133 -0
  60. package/lib/plugins/table/__tests__/index.test.js +442 -0
  61. package/lib/plugins/table/__tests__/table-toolbar.test.js +54 -0
  62. package/lib/plugins/table/icons/index.js +69 -0
  63. package/lib/plugins/table/index.js +483 -0
  64. package/lib/plugins/table/table-toolbar.js +187 -0
  65. package/lib/plugins/textAlign/icons/index.js +194 -0
  66. package/lib/plugins/textAlign/index.js +34 -0
  67. package/lib/plugins/toolbar/__tests__/default-toolbar.test.js +128 -0
  68. package/lib/plugins/toolbar/__tests__/editor-and-toolbar.test.js +51 -0
  69. package/lib/plugins/toolbar/__tests__/toolbar-buttons.test.js +54 -0
  70. package/lib/plugins/toolbar/__tests__/toolbar.test.js +120 -0
  71. package/lib/plugins/toolbar/default-toolbar.js +229 -0
  72. package/lib/plugins/toolbar/done-button.js +53 -0
  73. package/lib/plugins/toolbar/editor-and-toolbar.js +286 -0
  74. package/lib/plugins/toolbar/index.js +34 -0
  75. package/lib/plugins/toolbar/toolbar-buttons.js +194 -0
  76. package/lib/plugins/toolbar/toolbar.js +376 -0
  77. package/lib/plugins/utils.js +62 -0
  78. package/lib/serialization.js +677 -0
  79. package/lib/shared/alert-dialog.js +75 -0
  80. package/lib/theme.js +9 -0
  81. package/package.json +69 -0
  82. package/src/__tests__/editor.test.jsx +363 -0
  83. package/src/__tests__/serialization.test.js +291 -0
  84. package/src/__tests__/utils.js +36 -0
  85. package/src/block-tags.js +17 -0
  86. package/src/constants.js +7 -0
  87. package/src/editor.jsx +1197 -0
  88. package/src/extensions/characters.js +46 -0
  89. package/src/extensions/component.jsx +294 -0
  90. package/src/extensions/css.js +217 -0
  91. package/src/extensions/custom-toolbar-wrapper.jsx +100 -0
  92. package/src/extensions/image.js +55 -0
  93. package/src/extensions/math.js +259 -0
  94. package/src/extensions/media.js +182 -0
  95. package/src/extensions/responseArea.js +205 -0
  96. package/src/index.jsx +1462 -0
  97. package/src/old-index.jsx +162 -0
  98. package/src/parse-html.js +8 -0
  99. package/src/plugins/README.md +27 -0
  100. package/src/plugins/characters/custom-popper.js +48 -0
  101. package/src/plugins/characters/index.jsx +284 -0
  102. package/src/plugins/characters/utils.js +447 -0
  103. package/src/plugins/css/icons/index.jsx +17 -0
  104. package/src/plugins/css/index.jsx +340 -0
  105. package/src/plugins/customPlugin/index.jsx +85 -0
  106. package/src/plugins/html/icons/index.jsx +19 -0
  107. package/src/plugins/html/index.jsx +72 -0
  108. package/src/plugins/image/__tests__/__snapshots__/component.test.jsx.snap +51 -0
  109. package/src/plugins/image/__tests__/__snapshots__/image-toolbar-logic.test.jsx.snap +27 -0
  110. package/src/plugins/image/__tests__/__snapshots__/image-toolbar.test.jsx.snap +44 -0
  111. package/src/plugins/image/__tests__/component.test.jsx +41 -0
  112. package/src/plugins/image/__tests__/image-toolbar-logic.test.jsx +42 -0
  113. package/src/plugins/image/__tests__/image-toolbar.test.jsx +11 -0
  114. package/src/plugins/image/__tests__/index.test.js +95 -0
  115. package/src/plugins/image/__tests__/insert-image-handler.test.js +113 -0
  116. package/src/plugins/image/__tests__/mock-change.js +15 -0
  117. package/src/plugins/image/alt-dialog.jsx +82 -0
  118. package/src/plugins/image/component.jsx +343 -0
  119. package/src/plugins/image/image-toolbar.jsx +100 -0
  120. package/src/plugins/image/index.jsx +227 -0
  121. package/src/plugins/image/insert-image-handler.js +79 -0
  122. package/src/plugins/index.jsx +377 -0
  123. package/src/plugins/list/__tests__/index.test.js +54 -0
  124. package/src/plugins/list/index.jsx +305 -0
  125. package/src/plugins/math/__tests__/__snapshots__/index.test.jsx.snap +48 -0
  126. package/src/plugins/math/__tests__/index.test.jsx +245 -0
  127. package/src/plugins/math/index.jsx +379 -0
  128. package/src/plugins/media/__tests__/index.test.js +75 -0
  129. package/src/plugins/media/index.jsx +325 -0
  130. package/src/plugins/media/media-dialog.js +624 -0
  131. package/src/plugins/media/media-toolbar.jsx +56 -0
  132. package/src/plugins/media/media-wrapper.jsx +43 -0
  133. package/src/plugins/rendering/index.js +31 -0
  134. package/src/plugins/respArea/drag-in-the-blank/choice.jsx +215 -0
  135. package/src/plugins/respArea/drag-in-the-blank/index.jsx +70 -0
  136. package/src/plugins/respArea/explicit-constructed-response/index.jsx +92 -0
  137. package/src/plugins/respArea/icons/index.jsx +71 -0
  138. package/src/plugins/respArea/index.jsx +299 -0
  139. package/src/plugins/respArea/inline-dropdown/index.jsx +108 -0
  140. package/src/plugins/respArea/math-templated/index.jsx +104 -0
  141. package/src/plugins/respArea/utils.jsx +90 -0
  142. package/src/plugins/table/CustomTablePlugin.js +113 -0
  143. package/src/plugins/table/__tests__/__snapshots__/table-toolbar.test.jsx.snap +44 -0
  144. package/src/plugins/table/__tests__/index.test.jsx +401 -0
  145. package/src/plugins/table/__tests__/table-toolbar.test.jsx +42 -0
  146. package/src/plugins/table/icons/index.jsx +53 -0
  147. package/src/plugins/table/index.jsx +427 -0
  148. package/src/plugins/table/table-toolbar.jsx +136 -0
  149. package/src/plugins/textAlign/icons/index.jsx +114 -0
  150. package/src/plugins/textAlign/index.jsx +23 -0
  151. package/src/plugins/toolbar/__tests__/__snapshots__/default-toolbar.test.jsx.snap +923 -0
  152. package/src/plugins/toolbar/__tests__/__snapshots__/editor-and-toolbar.test.jsx.snap +20 -0
  153. package/src/plugins/toolbar/__tests__/__snapshots__/toolbar-buttons.test.jsx.snap +36 -0
  154. package/src/plugins/toolbar/__tests__/__snapshots__/toolbar.test.jsx.snap +46 -0
  155. package/src/plugins/toolbar/__tests__/default-toolbar.test.jsx +94 -0
  156. package/src/plugins/toolbar/__tests__/editor-and-toolbar.test.jsx +37 -0
  157. package/src/plugins/toolbar/__tests__/toolbar-buttons.test.jsx +51 -0
  158. package/src/plugins/toolbar/__tests__/toolbar.test.jsx +106 -0
  159. package/src/plugins/toolbar/default-toolbar.jsx +206 -0
  160. package/src/plugins/toolbar/done-button.jsx +38 -0
  161. package/src/plugins/toolbar/editor-and-toolbar.jsx +257 -0
  162. package/src/plugins/toolbar/index.jsx +23 -0
  163. package/src/plugins/toolbar/toolbar-buttons.jsx +138 -0
  164. package/src/plugins/toolbar/toolbar.jsx +338 -0
  165. package/src/plugins/utils.js +31 -0
  166. package/src/serialization.jsx +621 -0
  167. package/src/theme.js +1 -0
@@ -0,0 +1,624 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import debug from 'debug';
4
+ import { color } from '@pie-lib/render-ui';
5
+ import { withStyles } from '@material-ui/core/styles';
6
+ import Button from '@material-ui/core/Button';
7
+ import Dialog from '@material-ui/core/Dialog';
8
+ import MuiTabs from '@material-ui/core/Tabs';
9
+ import MuiTab from '@material-ui/core/Tab';
10
+ import DialogTitle from '@material-ui/core/DialogTitle';
11
+ import DialogContent from '@material-ui/core/DialogContent';
12
+ import DialogContentText from '@material-ui/core/DialogContentText';
13
+ import DialogActions from '@material-ui/core/DialogActions';
14
+ import TextField from '@material-ui/core/TextField';
15
+ import Typography from '@material-ui/core/Typography';
16
+ import IconButton from '@material-ui/core/IconButton';
17
+ import ActionDelete from '@material-ui/icons/Delete';
18
+
19
+ const log = debug('@pie-lib:editable-html:plugins:media:dialog');
20
+
21
+ const matchYoutubeUrl = (url) => {
22
+ if (!url) {
23
+ return false;
24
+ }
25
+
26
+ const p = /^(?:https?:\/\/)?(?:m\.|www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})(?:\S+)?$/;
27
+ if (url.match(p)) {
28
+ return url.match(p)[1];
29
+ }
30
+ return false;
31
+ };
32
+
33
+ const matchVimeoUrl = (url) =>
34
+ url &&
35
+ /(http|https)?:\/\/(www\.)?(player\.)?vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/([^/]*)\/videos\/|)(video\/)?(\d+)(?:|\/\?)/.test(
36
+ url,
37
+ );
38
+
39
+ const matchDriveUrl = (url) =>
40
+ url && /^https:\/\/drive\.google\.com\/(?:file\/d\/|drive\/(?:u\/\d+\/)?folders\/|uc\?id=)([a-zA-Z0-9_-]+)/.test(url);
41
+
42
+ const matchSoundCloudUrl = (url) => {
43
+ if (!url) {
44
+ return false;
45
+ }
46
+
47
+ const regexp = /^https?:\/\/(soundcloud\.com|snd\.sc)\/(.*)$/;
48
+ return url.match(regexp) && url.match(regexp)[2];
49
+ };
50
+
51
+ const makeApiRequest = (url) => {
52
+ return new Promise((resolve) => {
53
+ try {
54
+ fetch(`https://soundcloud.com/oembed?format=json&url=${url}`)
55
+ .then((response) => response.json())
56
+ .then((json) => {
57
+ const d = document.createElement('div');
58
+
59
+ d.innerHTML = json.html;
60
+
61
+ const iframe = d.querySelector('iframe');
62
+
63
+ resolve(iframe.src);
64
+ })
65
+ .catch((err) => {
66
+ resolve('');
67
+ log(err);
68
+ });
69
+ } catch (err) {
70
+ resolve('');
71
+ }
72
+ });
73
+ };
74
+
75
+ const typeMap = {
76
+ video: 'Video',
77
+ audio: 'Audio',
78
+ };
79
+
80
+ const tabsTypeMap = {
81
+ uploadFile: 'upload-file',
82
+ insertUrl: 'insert-url',
83
+ };
84
+
85
+ export class MediaDialog extends React.Component {
86
+ static propTypes = {
87
+ classes: PropTypes.object.isRequired,
88
+ open: PropTypes.bool,
89
+ edit: PropTypes.bool,
90
+ disablePortal: PropTypes.bool,
91
+ handleClose: PropTypes.func,
92
+ uploadSoundSupport: PropTypes.shape({
93
+ add: PropTypes.func,
94
+ delete: PropTypes.func,
95
+ }),
96
+ type: PropTypes.string,
97
+ src: PropTypes.string,
98
+ url: PropTypes.string,
99
+ urlToUse: PropTypes.string,
100
+ starts: PropTypes.number,
101
+ ends: PropTypes.number,
102
+ height: PropTypes.number,
103
+ width: PropTypes.number,
104
+ };
105
+
106
+ constructor(props) {
107
+ super(props);
108
+
109
+ const { ends, height, src, starts, type, uploadSoundSupport, url, urlToUse, width } = props;
110
+ const showUploadFile = uploadSoundSupport?.add && uploadSoundSupport?.delete && type !== 'video';
111
+
112
+ this.state = {
113
+ ends: ends || 0,
114
+ url: url,
115
+ urlToUse: urlToUse,
116
+ formattedUrl: src,
117
+ height: height || 315,
118
+ invalid: false,
119
+ starts: starts || 0,
120
+ width: width || 560,
121
+ // default selected tab should be upload file if available
122
+ tabValue: showUploadFile ? tabsTypeMap.uploadFile : tabsTypeMap.insertUrl,
123
+ fileUpload: {
124
+ error: null,
125
+ loading: false,
126
+ scheduled: false,
127
+ url: '',
128
+ mimeType: '',
129
+ },
130
+ };
131
+ }
132
+
133
+ componentDidMount() {
134
+ if (this.props.url) {
135
+ this.urlChange({
136
+ target: {
137
+ value: this.props.url,
138
+ },
139
+ });
140
+ }
141
+ }
142
+
143
+ formatUrl = () => {
144
+ const { url, urlToUse, starts, ends } = this.state;
145
+ const isYoutube = matchYoutubeUrl(url);
146
+ const isVimeo = matchVimeoUrl(url);
147
+ let formattedUrl = urlToUse;
148
+
149
+ if ((isYoutube || isVimeo) && urlToUse) {
150
+ const params = [];
151
+
152
+ let paramName;
153
+ let paramStart;
154
+
155
+ switch (true) {
156
+ case isVimeo:
157
+ paramName = 't';
158
+ paramStart = '#';
159
+ break;
160
+ case isYoutube:
161
+ paramName = 'start';
162
+ paramStart = '?';
163
+ break;
164
+ default:
165
+ paramName = 'start';
166
+ paramStart = '?';
167
+ }
168
+
169
+ if (starts) {
170
+ params.push(`${paramName}=${starts}`);
171
+ }
172
+
173
+ if (ends) {
174
+ params.push(`end=${ends}`);
175
+ }
176
+
177
+ formattedUrl = `${urlToUse}${params.length ? paramStart : ''}${params.join('&')}`;
178
+ }
179
+
180
+ const callback = () => this.setState({ formattedUrl, updating: false });
181
+
182
+ this.setState({ formattedUrl: null, updating: true }, callback);
183
+ };
184
+
185
+ handleStateChange = (newState) => this.setState(newState, this.formatUrl);
186
+
187
+ checkAudio = (value) => {
188
+ if (matchSoundCloudUrl(value)) {
189
+ makeApiRequest(value)
190
+ .then((urlToUse) => {
191
+ this.handleStateChange({
192
+ urlToUse,
193
+ invalid: !urlToUse,
194
+ url: value,
195
+ });
196
+ })
197
+ .catch(log);
198
+ }
199
+ };
200
+
201
+ checkVideo = (value) => {
202
+ if (matchYoutubeUrl(value)) {
203
+ const regExp = /^.*(youtu\.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/;
204
+ const match = value.match(regExp);
205
+ const id = match[2];
206
+ const urlToUse = `https://youtube.com/embed/${id}`;
207
+
208
+ log('is youtube');
209
+
210
+ this.handleStateChange({
211
+ urlToUse,
212
+ url: value,
213
+ invalid: false,
214
+ });
215
+ }
216
+
217
+ if (matchVimeoUrl(value)) {
218
+ const id = value.replace(/.*vimeo.com\/(.*)/g, '$1');
219
+ const urlToUse = `https://player.vimeo.com/video/${id}`;
220
+
221
+ log('is vimeo');
222
+
223
+ this.handleStateChange({
224
+ urlToUse,
225
+ url: value,
226
+ ends: null,
227
+ invalid: false,
228
+ });
229
+ }
230
+
231
+ if (matchDriveUrl(value)) {
232
+ // https://drive.google.com/file/d/11QkK_gUO068amNvZBm9cxZpKX74WYb8q/view
233
+ const id = value.replace(
234
+ /^https:\/\/drive\.google\.com\/(?:file\/d\/|drive\/(?:u\/\d+\/)?folders\/|uc\?id=)([a-zA-Z0-9_-]+)\/.*/,
235
+ '$1',
236
+ );
237
+ const urlToUse = `https://drive.google.com/file/d/${id}/preview`;
238
+
239
+ log('is google drive');
240
+
241
+ this.handleStateChange({
242
+ urlToUse,
243
+ url: value,
244
+ ends: null,
245
+ invalid: false,
246
+ });
247
+ }
248
+ };
249
+
250
+ urlChange = (e) => {
251
+ const { value } = e.target || {};
252
+ const { type } = this.props;
253
+
254
+ if (type && type === 'audio') {
255
+ this.checkAudio();
256
+ return;
257
+ } else if (type && type === 'video') {
258
+ this.checkVideo(value);
259
+ return;
260
+ }
261
+
262
+ this.handleStateChange({
263
+ urlToUse: null,
264
+ url: null,
265
+ invalid: true,
266
+ });
267
+ };
268
+
269
+ changeHandler = (type) => (e) => this.handleStateChange({ [type]: e.target.value });
270
+
271
+ handleDone = (val) => {
272
+ const { handleClose } = this.props;
273
+ const { tabValue, fileUpload } = this.state;
274
+ const isInsertURL = tabValue === tabsTypeMap.insertUrl;
275
+
276
+ if (!val) {
277
+ if (fileUpload.url) {
278
+ this.handleRemoveFile();
279
+ }
280
+
281
+ handleClose(val);
282
+ return;
283
+ }
284
+
285
+ if (isInsertURL) {
286
+ const { ends, height, url, urlToUse, formattedUrl, starts, width } = this.state;
287
+
288
+ handleClose(val, {
289
+ tag: 'iframe',
290
+ ends,
291
+ height,
292
+ starts,
293
+ width,
294
+ url,
295
+ urlToUse,
296
+ src: formattedUrl,
297
+ });
298
+ return;
299
+ }
300
+
301
+ if (!fileUpload.loading) {
302
+ handleClose(val, {
303
+ tag: 'audio',
304
+ src: fileUpload.url,
305
+ });
306
+ return;
307
+ }
308
+
309
+ this.setState({
310
+ fileUpload: {
311
+ ...fileUpload,
312
+ scheduled: true,
313
+ },
314
+ });
315
+ };
316
+
317
+ handleUploadFile = async (e) => {
318
+ e.preventDefault();
319
+
320
+ this.setState({
321
+ fileUpload: {
322
+ ...this.state.fileUpload,
323
+ error: null,
324
+ loading: true,
325
+ },
326
+ });
327
+
328
+ const fileChosen = e.target.files[0];
329
+
330
+ const reader = new FileReader();
331
+
332
+ reader.onload = () => {
333
+ const dataURL = reader.result;
334
+
335
+ this.setState({
336
+ fileUpload: {
337
+ ...this.state.fileUpload,
338
+ url: dataURL,
339
+ mimeType: fileChosen.type,
340
+ },
341
+ });
342
+ };
343
+ reader.readAsDataURL(fileChosen);
344
+
345
+ this.props.uploadSoundSupport.add({
346
+ fileChosen,
347
+ done: (err, src) => {
348
+ log('done: err:', err);
349
+ if (err) {
350
+ //eslint-disable-next-line
351
+ console.log(err);
352
+ this.setState({
353
+ fileUpload: {
354
+ ...this.state.fileUpload,
355
+ scheduled: false,
356
+ loading: false,
357
+ error: err,
358
+ },
359
+ });
360
+ return;
361
+ }
362
+
363
+ const { fileUpload } = this.state;
364
+ const callback = fileUpload && fileUpload.scheduled ? this.handleDone.bind(this, true) : undefined;
365
+
366
+ this.setState(
367
+ {
368
+ fileUpload: {
369
+ ...fileUpload,
370
+ scheduled: false,
371
+ loading: false,
372
+ url: src,
373
+ },
374
+ },
375
+ callback,
376
+ );
377
+ },
378
+ });
379
+ };
380
+
381
+ handleRemoveFile = async () => {
382
+ this.props.uploadSoundSupport.delete(this.state.fileUpload.url, (err) => {
383
+ if (err) {
384
+ //eslint-disable-next-line
385
+ console.log(err);
386
+ this.setState({
387
+ fileUpload: {
388
+ ...this.state.fileUpload,
389
+ error: err,
390
+ },
391
+ });
392
+ }
393
+ });
394
+
395
+ // we should put it inside uploadSoundSupport.delete but we can leave it here for testing purposes
396
+ this.setState({
397
+ fileUpload: {
398
+ ...this.state.fileUpload,
399
+ loading: false,
400
+ url: '',
401
+ mimeType: '',
402
+ },
403
+ });
404
+ };
405
+
406
+ render() {
407
+ const { classes, open, disablePortal, type, edit, uploadSoundSupport } = this.props;
408
+ const {
409
+ ends,
410
+ height,
411
+ invalid,
412
+ starts,
413
+ width,
414
+ url,
415
+ mimeType,
416
+ formattedUrl,
417
+ updating,
418
+ tabValue,
419
+ fileUpload,
420
+ } = this.state;
421
+ const isYoutube = matchYoutubeUrl(url);
422
+ const isInsertURL = tabValue === tabsTypeMap.insertUrl;
423
+ const isUploadMedia = tabValue === tabsTypeMap.uploadFile;
424
+ const submitIsDisabled = isInsertURL
425
+ ? invalid || url === null || url === undefined
426
+ : !fileUpload.url || fileUpload.scheduled;
427
+ const showUploadFile = uploadSoundSupport?.add && uploadSoundSupport?.delete && type !== 'video';
428
+
429
+ return (
430
+ <Dialog
431
+ classes={{
432
+ paper: classes.paper,
433
+ }}
434
+ disablePortal={disablePortal}
435
+ open={open}
436
+ onClose={() => this.handleDone(false)}
437
+ aria-labelledby="form-dialog-title"
438
+ >
439
+ <DialogTitle id="form-dialog-title">Insert {typeMap[type]}</DialogTitle>
440
+ <DialogContent>
441
+ <div>
442
+ <div className={classes.row}>
443
+ <MuiTabs
444
+ indicatorColor="primary"
445
+ value={tabValue}
446
+ onChange={(event, value) => {
447
+ this.setState({ tabValue: value });
448
+ }}
449
+ >
450
+ {showUploadFile ? <MuiTab value={tabsTypeMap.uploadFile} label="Upload file" /> : null}
451
+ <MuiTab
452
+ value={tabsTypeMap.insertUrl}
453
+ label={type === 'video' ? 'Insert YouTube, Vimeo, or Google Drive URL' : 'Insert SoundCloud URL'}
454
+ />
455
+ </MuiTabs>
456
+ </div>
457
+ {isInsertURL && (
458
+ <div>
459
+ <TextField
460
+ autoFocus
461
+ error={invalid}
462
+ helperText={invalid ? 'Invalid URL' : ''}
463
+ margin="dense"
464
+ id="name"
465
+ label="URL"
466
+ placeholder={`Paste URL of ${type}...`}
467
+ type="text"
468
+ onChange={this.urlChange}
469
+ value={url}
470
+ fullWidth
471
+ />
472
+ {type === 'video' && (
473
+ <DialogContent
474
+ classes={{
475
+ root: classes.properties,
476
+ }}
477
+ >
478
+ <DialogContentText>Video Properties</DialogContentText>
479
+ <TextField
480
+ autoFocus
481
+ margin="dense"
482
+ id="width"
483
+ label="Width"
484
+ type="number"
485
+ placeholder="Width"
486
+ value={width}
487
+ onChange={this.changeHandler('width')}
488
+ />
489
+ <TextField
490
+ autoFocus
491
+ margin="dense"
492
+ id="height"
493
+ label="Height"
494
+ type="number"
495
+ placeholder="Height"
496
+ value={height}
497
+ onChange={this.changeHandler('height')}
498
+ />
499
+ </DialogContent>
500
+ )}
501
+ {formattedUrl && (
502
+ <iframe
503
+ width={width}
504
+ height={height}
505
+ src={formattedUrl}
506
+ frameBorder="0"
507
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
508
+ allowFullScreen
509
+ />
510
+ )}
511
+ {type === 'video' && (formattedUrl || updating) && !invalid && (
512
+ <React.Fragment>
513
+ <DialogContent
514
+ classes={{
515
+ root: classes.properties,
516
+ }}
517
+ >
518
+ <TextField
519
+ autoFocus
520
+ margin="dense"
521
+ id="starts"
522
+ label="Starts"
523
+ type="number"
524
+ placeholder="Starts"
525
+ value={starts}
526
+ onChange={this.changeHandler('starts')}
527
+ />
528
+ {isYoutube && (
529
+ <TextField
530
+ autoFocus
531
+ margin="dense"
532
+ id="ends"
533
+ label="Ends"
534
+ type="number"
535
+ placeholder="Ends"
536
+ value={ends}
537
+ onChange={this.changeHandler('ends')}
538
+ />
539
+ )}
540
+ </DialogContent>
541
+ </React.Fragment>
542
+ )}
543
+ </div>
544
+ )}
545
+ {isUploadMedia && (
546
+ <div className={classes.uploadInput}>
547
+ <div>
548
+ {fileUpload.url ? (
549
+ <>
550
+ <div className={classes.row}>
551
+ <audio controls="controls" controlsList="nodownload">
552
+ <source type={mimeType} src={fileUpload.url} />
553
+ </audio>
554
+ <IconButton aria-label="delete" className={classes.deleteIcon} onClick={this.handleRemoveFile}>
555
+ <ActionDelete />
556
+ </IconButton>
557
+ </div>
558
+ {!fileUpload.scheduled && fileUpload.loading ? (
559
+ <Typography variant="subheading">Loading...</Typography>
560
+ ) : null}
561
+ {fileUpload.scheduled ? (
562
+ <Typography variant="subheading">
563
+ Waiting for Upload to finish, then inserting item...
564
+ </Typography>
565
+ ) : null}
566
+ </>
567
+ ) : !fileUpload.loading ? (
568
+ <input accept="audio/*" className={classes.input} onChange={this.handleUploadFile} type="file" />
569
+ ) : null}
570
+ {!!fileUpload.error && (
571
+ <Typography className={classes.error} variant="caption">
572
+ {fileUpload.error}
573
+ </Typography>
574
+ )}
575
+ </div>
576
+ </div>
577
+ )}
578
+ </div>
579
+ </DialogContent>
580
+ <DialogActions>
581
+ <Button onClick={() => this.handleDone(false)} color="primary">
582
+ Cancel
583
+ </Button>
584
+ <Button disabled={submitIsDisabled} onClick={() => this.handleDone(true)} color="primary">
585
+ {edit ? 'Update' : 'Insert'}
586
+ </Button>
587
+ </DialogActions>
588
+ </Dialog>
589
+ );
590
+ }
591
+ }
592
+
593
+ const styles = (theme) => ({
594
+ paper: {
595
+ minWidth: '500px',
596
+ },
597
+ properties: {
598
+ padding: 0,
599
+ },
600
+ row: {
601
+ display: 'flex',
602
+ flexDirection: 'space-between',
603
+ },
604
+ rowItem: {
605
+ marginRight: theme.spacing.unit * 1.5,
606
+ cursor: 'pointer',
607
+ },
608
+ active: {
609
+ color: color.primary(),
610
+ borderBottom: `2px solid ${color.primary()}`,
611
+ },
612
+ uploadInput: {
613
+ marginTop: theme.spacing.unit * 1.5,
614
+ },
615
+ error: {
616
+ marginTop: theme.spacing.unit * 1.5,
617
+ color: theme.palette.error.main,
618
+ },
619
+ deleteIcon: {
620
+ marginLeft: theme.spacing.unit * 1.5,
621
+ },
622
+ });
623
+
624
+ export default withStyles(styles)(MediaDialog);
@@ -0,0 +1,56 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { withStyles } from '@material-ui/core/styles';
4
+
5
+ const useStyles = withStyles((theme) => ({
6
+ root: {
7
+ position: 'relative',
8
+ bottom: '5px',
9
+ left: 0,
10
+ width: '100%',
11
+ background: theme.palette.common.white,
12
+ display: 'inline-flex',
13
+ padding: '5px',
14
+ boxShadow: '0px 4px 4px rgba(0, 0, 0, 0.25)',
15
+ },
16
+ editContainer: {
17
+ cursor: 'pointer',
18
+ flex: 3,
19
+ border: `solid ${theme.palette.common.black}`,
20
+ textAlign: 'right',
21
+ borderWidth: '0 2px 0 0',
22
+ marginRight: '5px',
23
+ paddingRight: '5px',
24
+ },
25
+ removeContainer: {
26
+ cursor: 'pointer',
27
+ },
28
+ }));
29
+
30
+ class MediaToolbar extends React.Component {
31
+ static propTypes = {
32
+ classes: PropTypes.object,
33
+ onEdit: PropTypes.func,
34
+ hideEdit: PropTypes.bool,
35
+ onRemove: PropTypes.func,
36
+ };
37
+
38
+ render() {
39
+ const { classes, hideEdit, onEdit, onRemove } = this.props;
40
+
41
+ return (
42
+ <span className={classes.root}>
43
+ {hideEdit ? null : (
44
+ <span className={classes.editContainer} onClick={onEdit}>
45
+ Edit Settings
46
+ </span>
47
+ )}
48
+ <span className={classes.removeContainer} onClick={onRemove}>
49
+ Remove
50
+ </span>
51
+ </span>
52
+ );
53
+ }
54
+ }
55
+
56
+ export default useStyles(MediaToolbar);