@pie-lib/editable-html 9.1.6 → 9.2.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.
@@ -1,14 +1,20 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import debug from 'debug';
4
+ import { color } from '@pie-lib/render-ui';
4
5
  import { withStyles } from '@material-ui/core/styles';
6
+ import Button from '@material-ui/core/Button';
5
7
  import Dialog from '@material-ui/core/Dialog';
8
+ import MuiTabs from '@material-ui/core/Tabs';
9
+ import MuiTab from '@material-ui/core/Tab';
6
10
  import DialogTitle from '@material-ui/core/DialogTitle';
7
11
  import DialogContent from '@material-ui/core/DialogContent';
8
12
  import DialogContentText from '@material-ui/core/DialogContentText';
9
13
  import DialogActions from '@material-ui/core/DialogActions';
10
- import Button from '@material-ui/core/Button';
11
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';
12
18
 
13
19
  const log = debug('@pie-lib:editable-html:plugins:media:dialog');
14
20
 
@@ -75,6 +81,10 @@ export class MediaDialog extends React.Component {
75
81
  edit: PropTypes.bool,
76
82
  disablePortal: PropTypes.bool,
77
83
  handleClose: PropTypes.func,
84
+ uploadSoundSupport: PropTypes.shape({
85
+ add: PropTypes.func,
86
+ delete: PropTypes.func
87
+ }),
78
88
  type: PropTypes.string,
79
89
  src: PropTypes.string,
80
90
  url: PropTypes.string,
@@ -98,7 +108,13 @@ export class MediaDialog extends React.Component {
98
108
  height: height || 315,
99
109
  invalid: false,
100
110
  starts: starts || 0,
101
- width: width || 560
111
+ width: width || 560,
112
+ tabValue: 0,
113
+ fileUpload: {
114
+ loading: false,
115
+ url: '',
116
+ error: null
117
+ }
102
118
  };
103
119
  }
104
120
 
@@ -220,13 +236,20 @@ export class MediaDialog extends React.Component {
220
236
 
221
237
  handleDone = val => {
222
238
  const { handleClose } = this.props;
239
+ const { tabValue, fileUpload } = this.state;
240
+ const isInsertURL = tabValue === 0;
223
241
 
224
242
  if (!val) {
243
+ if (fileUpload.url) {
244
+ this.handleRemoveFile();
245
+ }
246
+
225
247
  handleClose(val);
226
- } else {
248
+ } else if (isInsertURL) {
227
249
  const { ends, height, url, urlToUse, formattedUrl, starts, width } = this.state;
228
250
 
229
251
  handleClose(val, {
252
+ tag: 'iframe',
230
253
  ends,
231
254
  height,
232
255
  starts,
@@ -235,13 +258,112 @@ export class MediaDialog extends React.Component {
235
258
  urlToUse,
236
259
  src: formattedUrl
237
260
  });
261
+ } else {
262
+ handleClose(val, {
263
+ tag: 'audio',
264
+ src: fileUpload.url
265
+ });
238
266
  }
239
267
  };
240
268
 
269
+ handleUploadFile = async e => {
270
+ e.preventDefault();
271
+
272
+ this.setState({
273
+ fileUpload: {
274
+ ...this.state.fileUpload,
275
+ error: null,
276
+ loading: true
277
+ }
278
+ });
279
+
280
+ const fileChosen = e.target.files[0];
281
+
282
+ const reader = new FileReader();
283
+
284
+ reader.onload = () => {
285
+ const dataURL = reader.result;
286
+
287
+ this.setState({
288
+ fileUpload: {
289
+ ...this.state.fileUpload,
290
+ url: dataURL
291
+ }
292
+ });
293
+ };
294
+ reader.readAsDataURL(fileChosen);
295
+
296
+ this.props.uploadSoundSupport.add({
297
+ fileChosen,
298
+ done: (err, src) => {
299
+ log('done: err:', err);
300
+ if (err) {
301
+ //eslint-disable-next-line
302
+ console.log(err);
303
+ this.setState({
304
+ fileUpload: {
305
+ ...this.state.fileUpload,
306
+ loading: false,
307
+ error: err
308
+ }
309
+ });
310
+ } else {
311
+ this.setState({
312
+ fileUpload: {
313
+ ...this.state.fileUpload,
314
+ loading: false,
315
+ url: src
316
+ }
317
+ });
318
+ }
319
+ }
320
+ });
321
+ };
322
+
323
+ handleRemoveFile = async () => {
324
+ this.props.uploadSoundSupport.delete(this.state.fileUpload.url, err => {
325
+ if (err) {
326
+ //eslint-disable-next-line
327
+ console.log(err);
328
+ this.setState({
329
+ fileUpload: {
330
+ ...this.state.fileUpload,
331
+ error: err
332
+ }
333
+ });
334
+ }
335
+ });
336
+
337
+ // we should put it inside uploadSoundSupport.delete but we can leave it here for testing purposes
338
+ this.setState({
339
+ fileUpload: {
340
+ ...this.state.fileUpload,
341
+ loading: false,
342
+ url: ''
343
+ }
344
+ });
345
+ };
346
+
241
347
  render() {
242
- const { classes, open, disablePortal, type, edit } = this.props;
243
- const { ends, height, invalid, starts, width, url, formattedUrl, updating } = this.state;
348
+ const { classes, open, disablePortal, type, edit, uploadSoundSupport } = this.props;
349
+ const {
350
+ ends,
351
+ height,
352
+ invalid,
353
+ starts,
354
+ width,
355
+ url,
356
+ formattedUrl,
357
+ updating,
358
+ tabValue,
359
+ fileUpload
360
+ } = this.state;
244
361
  const isYoutube = matchYoutubeUrl(url);
362
+ const isInsertURL = tabValue === 0;
363
+ const isUploadMedia = tabValue === 1;
364
+ const submitIsDisabled = isInsertURL
365
+ ? invalid || url === null || url === undefined
366
+ : !fileUpload.url;
245
367
 
246
368
  return (
247
369
  <Dialog
@@ -255,103 +377,155 @@ export class MediaDialog extends React.Component {
255
377
  >
256
378
  <DialogTitle id="form-dialog-title">Insert {typeMap[type]}</DialogTitle>
257
379
  <DialogContent>
258
- <DialogContentText>
259
- {type === 'video' ? 'Insert YouTube or Vimeo URL' : 'Insert SoundCloud URL'}
260
- </DialogContentText>
261
- <TextField
262
- autoFocus
263
- error={invalid}
264
- helperText={invalid ? 'Invalid URL' : ''}
265
- margin="dense"
266
- id="name"
267
- label="URL"
268
- placeholder={`Paste URL of ${type}...`}
269
- type="text"
270
- onChange={this.urlChange}
271
- value={url}
272
- fullWidth
273
- />
274
- {type === 'video' && (
275
- <DialogContent
276
- classes={{
277
- root: classes.properties
278
- }}
279
- >
280
- <DialogContentText>Video Properties</DialogContentText>
281
- <TextField
282
- autoFocus
283
- margin="dense"
284
- id="width"
285
- label="Width"
286
- type="number"
287
- placeholder="Width"
288
- value={width}
289
- onChange={this.changeHandler('width')}
290
- />
291
- <TextField
292
- autoFocus
293
- margin="dense"
294
- id="height"
295
- label="Height"
296
- type="number"
297
- placeholder="Height"
298
- value={height}
299
- onChange={this.changeHandler('height')}
300
- />
301
- </DialogContent>
302
- )}
303
- {formattedUrl && (
304
- <iframe
305
- width={width}
306
- height={height}
307
- src={formattedUrl}
308
- frameBorder="0"
309
- allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
310
- allowFullScreen
311
- />
312
- )}
313
- {type === 'video' && (formattedUrl || updating) && !invalid && (
314
- <React.Fragment>
315
- <DialogContent
316
- classes={{
317
- root: classes.properties
380
+ <div>
381
+ <div className={classes.row}>
382
+ <MuiTabs
383
+ indicatorColor="primary"
384
+ value={tabValue}
385
+ onChange={(event, value) => {
386
+ this.setState({ tabValue: value });
318
387
  }}
319
388
  >
389
+ <MuiTab
390
+ label={type === 'video' ? 'Insert YouTube or Vimeo URL' : 'Insert SoundCloud URL'}
391
+ />
392
+ {uploadSoundSupport?.add && uploadSoundSupport?.delete && type !== 'video' ? (
393
+ <MuiTab label="Upload file" />
394
+ ) : null}
395
+ </MuiTabs>
396
+ </div>
397
+ {isInsertURL && (
398
+ <div>
320
399
  <TextField
321
400
  autoFocus
401
+ error={invalid}
402
+ helperText={invalid ? 'Invalid URL' : ''}
322
403
  margin="dense"
323
- id="starts"
324
- label="Starts"
325
- type="number"
326
- placeholder="Starts"
327
- value={starts}
328
- onChange={this.changeHandler('starts')}
404
+ id="name"
405
+ label="URL"
406
+ placeholder={`Paste URL of ${type}...`}
407
+ type="text"
408
+ onChange={this.urlChange}
409
+ value={url}
410
+ fullWidth
329
411
  />
330
- {isYoutube && (
331
- <TextField
332
- autoFocus
333
- margin="dense"
334
- id="ends"
335
- label="Ends"
336
- type="number"
337
- placeholder="Ends"
338
- value={ends}
339
- onChange={this.changeHandler('ends')}
412
+ {type === 'video' && (
413
+ <DialogContent
414
+ classes={{
415
+ root: classes.properties
416
+ }}
417
+ >
418
+ <DialogContentText>Video Properties</DialogContentText>
419
+ <TextField
420
+ autoFocus
421
+ margin="dense"
422
+ id="width"
423
+ label="Width"
424
+ type="number"
425
+ placeholder="Width"
426
+ value={width}
427
+ onChange={this.changeHandler('width')}
428
+ />
429
+ <TextField
430
+ autoFocus
431
+ margin="dense"
432
+ id="height"
433
+ label="Height"
434
+ type="number"
435
+ placeholder="Height"
436
+ value={height}
437
+ onChange={this.changeHandler('height')}
438
+ />
439
+ </DialogContent>
440
+ )}
441
+ {formattedUrl && (
442
+ <iframe
443
+ width={width}
444
+ height={height}
445
+ src={formattedUrl}
446
+ frameBorder="0"
447
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
448
+ allowFullScreen
340
449
  />
341
450
  )}
342
- </DialogContent>
343
- </React.Fragment>
344
- )}
451
+ {type === 'video' && (formattedUrl || updating) && !invalid && (
452
+ <React.Fragment>
453
+ <DialogContent
454
+ classes={{
455
+ root: classes.properties
456
+ }}
457
+ >
458
+ <TextField
459
+ autoFocus
460
+ margin="dense"
461
+ id="starts"
462
+ label="Starts"
463
+ type="number"
464
+ placeholder="Starts"
465
+ value={starts}
466
+ onChange={this.changeHandler('starts')}
467
+ />
468
+ {isYoutube && (
469
+ <TextField
470
+ autoFocus
471
+ margin="dense"
472
+ id="ends"
473
+ label="Ends"
474
+ type="number"
475
+ placeholder="Ends"
476
+ value={ends}
477
+ onChange={this.changeHandler('ends')}
478
+ />
479
+ )}
480
+ </DialogContent>
481
+ </React.Fragment>
482
+ )}
483
+ </div>
484
+ )}
485
+ {isUploadMedia && (
486
+ <div className={classes.uploadInput}>
487
+ <div>
488
+ {fileUpload.url ? (
489
+ <>
490
+ <div className={classes.row}>
491
+ <audio controls="controls">
492
+ <source type="audio/mp3" src={fileUpload.url} />
493
+ </audio>
494
+ <IconButton
495
+ aria-label="delete"
496
+ className={classes.deleteIcon}
497
+ onClick={this.handleRemoveFile}
498
+ >
499
+ <ActionDelete />
500
+ </IconButton>
501
+ </div>
502
+ {fileUpload.loading ? (
503
+ <Typography variant="subheading">Loading...</Typography>
504
+ ) : null}
505
+ </>
506
+ ) : !fileUpload.loading ? (
507
+ <input
508
+ accept="audio/*"
509
+ className={classes.input}
510
+ onChange={this.handleUploadFile}
511
+ type="file"
512
+ />
513
+ ) : null}
514
+ {!!fileUpload.error && (
515
+ <Typography className={classes.error} variant="caption">
516
+ {fileUpload.error}
517
+ </Typography>
518
+ )}
519
+ </div>
520
+ </div>
521
+ )}
522
+ </div>
345
523
  </DialogContent>
346
524
  <DialogActions>
347
525
  <Button onClick={() => this.handleDone(false)} color="primary">
348
526
  Cancel
349
527
  </Button>
350
- <Button
351
- disabled={invalid || url === null || url === undefined}
352
- onClick={() => this.handleDone(true)}
353
- color="primary"
354
- >
528
+ <Button disabled={submitIsDisabled} onClick={() => this.handleDone(true)} color="primary">
355
529
  {edit ? 'Update' : 'Insert'}
356
530
  </Button>
357
531
  </DialogActions>
@@ -366,6 +540,28 @@ const styles = () => ({
366
540
  },
367
541
  properties: {
368
542
  padding: 0
543
+ },
544
+ row: {
545
+ display: 'flex',
546
+ flexDirection: 'space-between'
547
+ },
548
+ rowItem: {
549
+ marginRight: '12px',
550
+ cursor: 'pointer'
551
+ },
552
+ active: {
553
+ color: color.primary(),
554
+ borderBottom: `2px solid ${color.primary()}`
555
+ },
556
+ uploadInput: {
557
+ marginTop: '12px'
558
+ },
559
+ error: {
560
+ marginTop: '12px',
561
+ color: 'red'
562
+ },
563
+ deleteIcon: {
564
+ marginLeft: '12px'
369
565
  }
370
566
  });
371
567
 
@@ -31,17 +31,20 @@ class MediaToolbar extends React.Component {
31
31
  static propTypes = {
32
32
  classes: PropTypes.object,
33
33
  onEdit: PropTypes.func,
34
+ hideEdit: PropTypes.bool,
34
35
  onRemove: PropTypes.func
35
36
  };
36
37
 
37
38
  render() {
38
- const { classes, onEdit, onRemove } = this.props;
39
+ const { classes, hideEdit, onEdit, onRemove } = this.props;
39
40
 
40
41
  return (
41
42
  <span className={classes.root}>
42
- <span className={classes.editContainer} onClick={onEdit}>
43
- Edit Settings
44
- </span>
43
+ {hideEdit ? null : (
44
+ <span className={classes.editContainer} onClick={onEdit}>
45
+ Edit Settings
46
+ </span>
47
+ )}
45
48
  <span className={classes.removeContainer} onClick={onRemove}>
46
49
  Remove
47
50
  </span>
@@ -23,6 +23,7 @@ export class EditorAndToolbar extends React.Component {
23
23
  toolbarRef: PropTypes.func,
24
24
  focusedNode: SlatePropTypes.node,
25
25
  readOnly: PropTypes.bool,
26
+ disableScrollbar: PropTypes.bool,
26
27
  disableUnderline: PropTypes.bool,
27
28
  autoWidth: PropTypes.bool,
28
29
  classes: PropTypes.object.isRequired,
@@ -55,6 +56,7 @@ export class EditorAndToolbar extends React.Component {
55
56
  focusedNode,
56
57
  autoWidth,
57
58
  readOnly,
59
+ disableScrollbar,
58
60
  disableUnderline,
59
61
  pluginProps,
60
62
  toolbarOpts,
@@ -63,12 +65,12 @@ export class EditorAndToolbar extends React.Component {
63
65
  } = this.props;
64
66
 
65
67
  const inFocus = value.isFocused || (focusedNode !== null && focusedNode !== undefined);
66
- const holderNames = classNames(
67
- classes.editorHolder,
68
- inFocus && classes.editorInFocus,
69
- readOnly && classes.readOnly,
70
- disableUnderline && classes.disabledUnderline
71
- );
68
+ const holderNames = classNames(classes.editorHolder, {
69
+ [classes.editorInFocus]: inFocus,
70
+ [classes.readOnly]: readOnly,
71
+ [classes.disabledUnderline]: disableUnderline,
72
+ [classes.disabledScrollbar]: disableScrollbar
73
+ });
72
74
  let clonedChildren = children;
73
75
 
74
76
  if (typeof children !== 'string') {
@@ -197,7 +199,13 @@ const style = {
197
199
  display: 'none'
198
200
  }
199
201
  },
200
-
202
+ disabledScrollbar: {
203
+ '&::-webkit-scrollbar': {
204
+ display: 'none'
205
+ },
206
+ scrollbarWidth: 'none',
207
+ '-ms-overflow-style': 'none'
208
+ },
201
209
  readOnly: {
202
210
  '&::before': {
203
211
  background: 'transparent',
@@ -13,6 +13,7 @@ export default function ToolbarPlugin(opts) {
13
13
  <EditorAndToolbar
14
14
  {...props}
15
15
  mainEditorRef={opts.mainEditorRef}
16
+ disableScrollbar={opts.disableScrollbar}
16
17
  disableUnderline={opts.disableUnderline}
17
18
  onDone={opts.onDone}
18
19
  />