@instructure/canvas-rce 7.0.0 → 7.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.
Files changed (108) hide show
  1. package/CHANGELOG.md +33 -1
  2. package/__tests__/common/indicate.test.js +5 -6
  3. package/es/bridge/Bridge.js +2 -4
  4. package/es/canvasFileBrowser/FileBrowser.js +2 -4
  5. package/es/defaultTinymceConfig.d.ts +1 -1
  6. package/es/defaultTinymceConfig.js +149 -114
  7. package/es/enhance-user-content/enhance_user_content.js +7 -1
  8. package/es/enhance-user-content/instructure_helper.js +4 -0
  9. package/es/enhance-user-content/youtube_overlay.d.ts +1 -0
  10. package/es/enhance-user-content/youtube_overlay.js +87 -0
  11. package/es/format-message.d.js +1 -0
  12. package/es/format-message.js +5 -0
  13. package/es/index.d.ts +1 -1
  14. package/es/rce/RCE.d.ts +0 -1
  15. package/es/rce/RCE.js +5 -10
  16. package/es/rce/RCEVariants.d.ts +8 -3
  17. package/es/rce/RCEVariants.js +31 -5
  18. package/es/rce/RCEWrapper.d.ts +3 -3
  19. package/es/rce/RCEWrapper.js +66 -57
  20. package/es/rce/RCEWrapperProps.d.ts +1 -1
  21. package/es/rce/ShowOnFocusButton/index.js +4 -2
  22. package/es/rce/StatusBar.js +61 -15
  23. package/es/rce/alertHandler.js +6 -7
  24. package/es/rce/plugins/instructure-ui-icons/plugin.js +2 -2
  25. package/es/rce/plugins/instructure_equation/EquationEditorModal/index.js +6 -10
  26. package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/ImageSection/Course.d.ts +5 -15
  27. package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/ImageSection/Course.js +4 -10
  28. package/es/rce/plugins/instructure_image/ImageEmbedOptions.d.ts +7 -0
  29. package/es/rce/plugins/instructure_image/ImageEmbedOptions.js +45 -2
  30. package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolDialog/ExternalToolDialog.d.ts +1 -8
  31. package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolDialog/ExternalToolDialog.js +13 -33
  32. package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolDialog/ExternalToolDialogModal.d.ts +1 -2
  33. package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolDialog/ExternalToolDialogModal.js +2 -1
  34. package/es/rce/plugins/instructure_record/VideoOptionsTray/index.js +10 -7
  35. package/es/rce/plugins/shared/DimensionsInput/index.js +3 -3
  36. package/es/rce/plugins/shared/FixedContentTray.d.ts +7 -23
  37. package/es/rce/plugins/shared/FixedContentTray.js +7 -16
  38. package/es/rce/plugins/shared/ImageCropper/constants.d.ts +1 -1
  39. package/es/rce/plugins/shared/ImageCropper/constants.js +1 -1
  40. package/es/rce/plugins/shared/ImageCropper/controls/CustomNumberInput.js +1 -1
  41. package/es/rce/plugins/shared/PreviewIcon.js +1 -1
  42. package/es/rce/plugins/shared/ai_tools/aiicons.d.ts +3 -3
  43. package/es/rce/plugins/shared/ai_tools/aiicons.js +11 -11
  44. package/es/rce/plugins/tinymce-a11y-checker/components/checker.js +7 -1
  45. package/es/rce/plugins/tinymce-a11y-checker/utils/indicate.d.ts +1 -1
  46. package/es/rce/plugins/tinymce-a11y-checker/utils/indicate.js +1 -1
  47. package/es/rce/style.js +19 -17
  48. package/es/translations/locales/ar.js +29 -5
  49. package/es/translations/locales/ca.js +32 -8
  50. package/es/translations/locales/cy.js +29 -5
  51. package/es/translations/locales/da-x-k12.js +29 -5
  52. package/es/translations/locales/da.js +29 -5
  53. package/es/translations/locales/de.js +29 -5
  54. package/es/translations/locales/en-AU-x-unimelb.js +29 -5
  55. package/es/translations/locales/en-GB-x-ukhe.js +29 -5
  56. package/es/translations/locales/en.js +17 -5
  57. package/es/translations/locales/en_AU.js +29 -5
  58. package/es/translations/locales/en_CA.js +29 -5
  59. package/es/translations/locales/en_CY.js +29 -5
  60. package/es/translations/locales/en_GB.js +29 -5
  61. package/es/translations/locales/es.js +29 -5
  62. package/es/translations/locales/es_ES.js +29 -5
  63. package/es/translations/locales/fa_IR.js +0 -3
  64. package/es/translations/locales/fi.js +29 -5
  65. package/es/translations/locales/fr.js +29 -5
  66. package/es/translations/locales/fr_CA.js +30 -6
  67. package/es/translations/locales/ga.js +46 -22
  68. package/es/translations/locales/hi.js +29 -5
  69. package/es/translations/locales/ht.js +29 -5
  70. package/es/translations/locales/hu.js +0 -6
  71. package/es/translations/locales/id.js +29 -5
  72. package/es/translations/locales/is.js +23 -5
  73. package/es/translations/locales/it.js +29 -5
  74. package/es/translations/locales/ja.js +29 -5
  75. package/es/translations/locales/mi.js +29 -5
  76. package/es/translations/locales/ms.js +29 -5
  77. package/es/translations/locales/nb-x-k12.js +29 -5
  78. package/es/translations/locales/nb.js +29 -5
  79. package/es/translations/locales/nl.js +29 -5
  80. package/es/translations/locales/nn.js +0 -6
  81. package/es/translations/locales/pl.js +29 -5
  82. package/es/translations/locales/pt.js +29 -5
  83. package/es/translations/locales/pt_BR.js +29 -5
  84. package/es/translations/locales/ru.js +29 -5
  85. package/es/translations/locales/sl.js +29 -5
  86. package/es/translations/locales/sv-x-k12.js +29 -5
  87. package/es/translations/locales/sv.js +29 -5
  88. package/es/translations/locales/th.js +29 -5
  89. package/es/translations/locales/uk_UA.js +0 -6
  90. package/es/translations/locales/vi.js +29 -5
  91. package/es/translations/locales/zh-Hans.js +29 -5
  92. package/es/translations/locales/zh-Hant.js +29 -5
  93. package/es/translations/locales/zh.js +29 -5
  94. package/es/translations/locales/zh_HK.js +29 -5
  95. package/es/util/contextHelper.d.ts +7 -0
  96. package/{testcafe/axe.test.js → es/util/contextHelper.js} +10 -21
  97. package/es/util/loadingPlaceholder.js +11 -11
  98. package/eslint.config.js +3 -25
  99. package/jest/jest-setup.js +27 -2
  100. package/jest.config.js +5 -1
  101. package/package.json +61 -84
  102. package/testcafe/RCEWrapper.test.js +0 -319
  103. package/testcafe/StatusBar.test.js +0 -108
  104. package/testcafe/enhanceUserContent.html +0 -58
  105. package/testcafe/enhanceUserContent.test.js +0 -44
  106. package/testcafe/entry.jsx +0 -77
  107. package/testcafe/testcafe.html +0 -14
  108. package/webpack.testcafe.config.js +0 -61
@@ -28,7 +28,7 @@ import { Badge } from '@instructure/ui-badge';
28
28
  import { InstUISettingsProvider } from '@instructure/emotion';
29
29
  import { Text } from '@instructure/ui-text';
30
30
  import { SVGIcon } from '@instructure/ui-svg-images';
31
- import { IconA11yLine, IconKeyboardShortcutsLine, IconMiniArrowEndLine, IconFullScreenLine, IconExitFullScreenLine } from '@instructure/ui-icons';
31
+ import { IconA11yLine, IconKeyboardShortcutsLine, IconMiniArrowEndLine, IconFullScreenLine, IconExitFullScreenLine, IconAddLine, IconCheckMarkIndeterminateLine } from '@instructure/ui-icons';
32
32
  import formatMessage from '../format-message';
33
33
  import ResizeHandle from './ResizeHandle';
34
34
  import { FS_ENABLED } from '../util/fullscreenHelpers';
@@ -62,7 +62,7 @@ StatusBar.propTypes = {
62
62
  onAI: func
63
63
  };
64
64
  StatusBar.defaultProps = {
65
- a11yBadgeColor: '#0374B5',
65
+ a11yBadgeColor: '#2B7ABC',
66
66
  a11yErrorsCount: 0,
67
67
  disabledPlugins: []
68
68
  };
@@ -80,16 +80,15 @@ function renderPathString({
80
80
  }
81
81
  function emptyTagIcon() {
82
82
  return /*#__PURE__*/React.createElement(SVGIcon, {
83
- viewBox: "0 0 24 24",
84
- fontSize: "24px"
83
+ viewBox: "0 0 1920 1920",
84
+ width: "1em",
85
+ height: "1em"
85
86
  }, /*#__PURE__*/React.createElement("g", {
86
- role: "presentation"
87
- }, /*#__PURE__*/React.createElement("text", {
88
- textAnchor: "middle",
89
- x: "12px",
90
- y: "18px",
91
- fontSize: "16"
92
- }, "</>")));
87
+ role: "presentation",
88
+ transform: "scale(28.7) translate(0, 8)"
89
+ }, /*#__PURE__*/React.createElement("path", {
90
+ d: "M0 29.61L0 25.51L23.71 15.50L23.71 19.87L4.91 27.59L23.71 35.38L23.71 39.75L0 29.61ZM26.46 45.87L36.84 8.86L40.36 8.86L30.00 45.87L26.46 45.87ZM66.80 29.61L43.09 39.75L43.09 35.38L61.87 27.59L43.09 19.87L43.09 15.50L66.80 25.51L66.80 29.61Z"
91
+ })));
93
92
  }
94
93
  function findFocusable(el) {
95
94
  // eslint-disable-next-line react/no-find-dom-node
@@ -287,10 +286,15 @@ export default function StatusBar(props) {
287
286
  title: formatMessage('View word and character counts')
288
287
  }, wordCount)));
289
288
  }
290
- function renderSection3(html_view, fullscreen, resize_handle) {
289
+ function renderSection3({
290
+ html_view,
291
+ fullscreen,
292
+ resize_handle,
293
+ a11y_resize_handlers
294
+ }) {
291
295
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
292
296
  className: css(styles.separator)
293
- }), html_view && renderToggleHtml(), fullscreen && renderFullscreen(), resize_handle && renderResizeHandle());
297
+ }), html_view && renderToggleHtml(), a11y_resize_handlers && renderAccessibleResizeHandle(), fullscreen && renderFullscreen(), resize_handle && renderResizeHandle());
294
298
  }
295
299
  function descMsg() {
296
300
  return preferredHtmlEditor() === RAW_HTML_EDITOR_VIEW ? formatMessage('Shift-O to open the pretty html editor.') : formatMessage('The pretty html editor is not keyboard accessible. Press Shift O to open the raw html editor.');
@@ -351,7 +355,11 @@ export default function StatusBar(props) {
351
355
  screenReaderLabel: fullscreen,
352
356
  withBackground: false,
353
357
  withBorder: false
354
- }, props.rceIsFullscreen ? /*#__PURE__*/React.createElement(IconExitFullScreenLine, null) : /*#__PURE__*/React.createElement(IconFullScreenLine, null));
358
+ }, /*#__PURE__*/React.createElement("div", {
359
+ style: {
360
+ fontSize: '0.9rem'
361
+ }
362
+ }, props.rceIsFullscreen ? /*#__PURE__*/React.createElement(IconExitFullScreenLine, null) : /*#__PURE__*/React.createElement(IconFullScreenLine, null)));
355
363
  }
356
364
  function renderResizeHandle() {
357
365
  if (props.rceIsFullscreen) return null;
@@ -364,10 +372,43 @@ export default function StatusBar(props) {
364
372
  }
365
373
  });
366
374
  }
375
+ function renderAccessibleResizeHandle() {
376
+ if (props.rceIsFullscreen) return null;
377
+ const increaseBtnId = 'rce-resize-increase-btn';
378
+ const decreaseBtnId = 'rce-resize-decrease-btn';
379
+ const handleResize = deltaY => {
380
+ props.onResize(null, {
381
+ deltaY,
382
+ deltaX: 0
383
+ });
384
+ };
385
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(IconButton, {
386
+ "data-btn-id": increaseBtnId,
387
+ "data-testid": increaseBtnId,
388
+ color: "secondary",
389
+ title: formatMessage('Increase Rich Content Area'),
390
+ tabIndex: tabIndexForBtn(increaseBtnId),
391
+ onFocus: () => setFocusedBtnId(increaseBtnId),
392
+ withBackground: false,
393
+ withBorder: false,
394
+ onClick: () => handleResize(5)
395
+ }, /*#__PURE__*/React.createElement(IconAddLine, null)), /*#__PURE__*/React.createElement(IconButton, {
396
+ "data-btn-id": decreaseBtnId,
397
+ "data-testid": decreaseBtnId,
398
+ color: "secondary",
399
+ title: formatMessage('Decrease Rich Content Area'),
400
+ tabIndex: tabIndexForBtn(decreaseBtnId),
401
+ onFocus: () => setFocusedBtnId(decreaseBtnId),
402
+ withBackground: false,
403
+ withBorder: false,
404
+ onClick: () => handleResize(-5)
405
+ }, /*#__PURE__*/React.createElement(IconCheckMarkIndeterminateLine, null)));
406
+ }
367
407
  const flexJustify = isHtmlView() ? 'end' : 'start';
368
408
  const html_view = isFeature('html_view') && isAvailable('instructure_html_view');
369
409
  const fullscreen = isFeature('fullscreen') && isAvailable('instructure_fullscreen');
370
410
  const resize_handle = isFeature('resize_handle');
411
+ const a11y_resize_handlers = isFeature('a11y_resize_handlers');
371
412
  return /*#__PURE__*/React.createElement(InstUISettingsProvider, {
372
413
  theme: {
373
414
  componentOverrides: {
@@ -388,7 +429,12 @@ export default function StatusBar(props) {
388
429
  }, isHtmlView() ? renderHtmlEditorMessage() : renderPath()), /*#__PURE__*/React.createElement(Flex.Item, {
389
430
  role: "toolbar",
390
431
  title: formatMessage('Editor Status Bar')
391
- }, renderIconButtons(), isFeature('word_count') && isAvailable('instructure_wordcount') && renderWordCount(), (html_view || fullscreen || resize_handle) && renderSection3(html_view, fullscreen, resize_handle))));
432
+ }, renderIconButtons(), isFeature('word_count') && isAvailable('instructure_wordcount') && renderWordCount(), (html_view || fullscreen || resize_handle) && renderSection3({
433
+ html_view,
434
+ fullscreen,
435
+ resize_handle,
436
+ a11y_resize_handlers
437
+ }))));
392
438
  }
393
439
  const styles = StyleSheet.create({
394
440
  separator: {
@@ -24,6 +24,12 @@
24
24
  */
25
25
  export class AlertHandler {
26
26
  constructor(alertFunc) {
27
+ /**
28
+ * Calls the registered alertFunc assuming one has been set, otherwise
29
+ * it throws.
30
+ *
31
+ * @memberof AlertHandler
32
+ */
27
33
  this.handleAlert = alert => {
28
34
  if (this.alertFunc == null) {
29
35
  throw new Error('Tried to alert without alertFunc being set first');
@@ -32,12 +38,5 @@ export class AlertHandler {
32
38
  };
33
39
  this.alertFunc = alertFunc;
34
40
  }
35
-
36
- /**
37
- * Calls the registered alertFunc assuming one has been set, otherwise
38
- * it throws.
39
- *
40
- * @memberof AlertHandler
41
- */
42
41
  }
43
42
  export default new AlertHandler();
@@ -112,13 +112,13 @@ tinymce.PluginManager.add('instructure-ui-icons', function (editor) {
112
112
  embed: {
113
113
  src: `<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
114
114
  <path fill-rule="evenodd" clip-rule="evenodd" d="M13.7647 5.21417C13.6694 5.21417 13.5773 5.23988 13.482 5.24631C12.8329 3.36281 11.0795 2 9 2C6.53506 2 4.52435 3.91029 4.28294 6.34234C4.09341 6.31127 3.90176 6.28556 3.70588 6.28556C1.66235 6.28556 0 7.96764 0 10.0354C0 12.1032 1.66235 13.7853 3.70588 13.7853L5 13.7853V12.7139L3.70588 12.7139C2.24682 12.7139 1.05882 11.5129 1.05882 10.0354C1.05882 8.55798 2.24682 7.35695 3.70588 7.35695C4.40259 7.35695 5.06012 7.62908 5.55882 8.12192L6.29894 7.35588C6.00565 7.0666 5.66788 6.84483 5.30894 6.66912C5.38941 4.67419 7.00835 3.07139 9 3.07139C11.0435 3.07139 12.7059 4.75347 12.7059 6.82126C12.7059 7.1491 12.6635 7.47266 12.582 7.78658L13.6059 8.06085C13.7107 7.65801 13.7647 7.24124 13.7647 6.82126C13.7647 6.64019 13.7308 6.4677 13.7118 6.29199C13.7298 6.29199 13.7467 6.28556 13.7647 6.28556C15.516 6.28556 16.9412 7.72765 16.9412 9.49973C16.9412 11.2718 15.516 12.7139 13.7647 12.7139L13 12.7139V13.7853L13.7647 13.7853C16.1005 13.7853 18 11.8632 18 9.49973C18 7.13624 16.1005 5.21417 13.7647 5.21417Z" fill="#2B3B46"/>
115
- <path fill-rule="evenodd" clip-rule="evenodd" d="M7.72039 10.6479L8.3603 11.1813L6.75882 13.1025L8.36029 15.0239L7.72038 15.5573L5.6748 13.1024L7.72039 10.6479ZM10.2802 10.6479L12.3258 13.1024L10.2802 15.5573L9.64031 15.0239L11.2418 13.1025L9.6403 11.1813L10.2802 10.6479Z" fill="#2D3B45"/>
115
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M7.72039 10.6479L8.3603 11.1813L6.75882 13.1025L8.36029 15.0239L7.72038 15.5573L5.6748 13.1024L7.72039 10.6479ZM10.2802 10.6479L12.3258 13.1024L10.2802 15.5573L9.64031 15.0239L11.2418 13.1025L9.6403 11.1813L10.2802 10.6479Z" fill="#273540"/>
116
116
  </svg>`
117
117
  },
118
118
  buttons: {
119
119
  src: `
120
120
  <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 18">
121
- <g fill="#2D3B45" clip-path="url(#clip0)">
121
+ <g fill="#273540" clip-path="url(#clip0)">
122
122
  <path fill-rule="evenodd" d="M.999993 11l.000001 6H6.99999v-6H.999993zm-.99999933 7H7.99999v-8H-.00000668l3.5e-7 8zM4 7c1.65685 0 3-1.34315 3-3S5.65685 1 4 1 1 2.34315 1 4s1.34315 3 3 3zm0 1c2.20914 0 4-1.79086 4-4S6.20914 0 4 0-3e-7 1.79086-3e-7 4 1.79086 8 4 8z" clip-rule="evenodd"/>
123
123
  <path d="M12.5 10h1v8h-1v-8z"/>
124
124
  <path d="M17 13.5v1H9v-1h8z"/>
@@ -49,6 +49,9 @@ export default class EquationEditorModal extends Component {
49
49
  };
50
50
  this.previewElement = /*#__PURE__*/React.createRef();
51
51
  this.advancedEditor = /*#__PURE__*/React.createRef();
52
+ // ********* //
53
+ // Callbacks //
54
+ // ********* //
52
55
  this.executeCommand = (cmd, advancedCmd) => {
53
56
  if (this.state.advanced) {
54
57
  const insertionText = advancedCmd || cmd;
@@ -123,6 +126,9 @@ export default class EquationEditorModal extends Component {
123
126
  this.handleFieldRef = node => {
124
127
  this.mathField = node;
125
128
  };
129
+ // ******************* //
130
+ // Rendering functions //
131
+ // ******************* //
126
132
  this.renderFooter = () => {
127
133
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Button, {
128
134
  "data-testid": "equation-editor-modal-cancel",
@@ -247,11 +253,6 @@ export default class EquationEditorModal extends Component {
247
253
  const normalizedLatex = latex.replace(/\s+/, '');
248
254
  return containsAdvancedSyntax(normalizedLatex);
249
255
  }
250
-
251
- // ********* //
252
- // Callbacks //
253
- // ********* //
254
-
255
256
  setPreviewElementContent() {
256
257
  if (this.state.workingFormula) {
257
258
  this.renderMathInAdvancedPreview();
@@ -259,11 +260,6 @@ export default class EquationEditorModal extends Component {
259
260
  this.previewElement.current.innerHTML = '';
260
261
  }
261
262
  }
262
-
263
- // ******************* //
264
- // Rendering functions //
265
- // ******************* //
266
-
267
263
  componentDidMount() {
268
264
  this.registerBasicEditorListener();
269
265
  this.setPreviewElementContent();
@@ -1,9 +1,9 @@
1
1
  export default Course;
2
- declare function Course({ dispatch, onChange, onLoading, onLoaded, canvasOrigin }: {
3
- dispatch: any;
4
- onChange: any;
5
- onLoading: any;
6
- onLoaded: any;
2
+ declare function Course({ dispatch, onChange, onLoading, onLoaded, canvasOrigin, }: {
3
+ dispatch?: (() => void) | undefined;
4
+ onChange?: (() => void) | undefined;
5
+ onLoading?: (() => void) | undefined;
6
+ onLoaded?: (() => void) | undefined;
7
7
  canvasOrigin: any;
8
8
  }): React.JSX.Element;
9
9
  declare namespace Course {
@@ -14,16 +14,6 @@ declare namespace Course {
14
14
  let onLoaded: PropTypes.Requireable<(...args: any[]) => any>;
15
15
  let canvasOrigin: PropTypes.Validator<string>;
16
16
  }
17
- namespace defaultProps {
18
- export function dispatch_1(): void;
19
- export { dispatch_1 as dispatch };
20
- export function onChange_1(): void;
21
- export { onChange_1 as onChange };
22
- export function onLoading_1(): void;
23
- export { onLoading_1 as onLoading };
24
- export function onLoaded_1(): void;
25
- export { onLoaded_1 as onLoaded };
26
- }
27
17
  }
28
18
  import React from 'react';
29
19
  import PropTypes from 'prop-types';
@@ -80,10 +80,10 @@ const dispatchImage = async (dispatch, onChange, dataUrl, dataBlob) => {
80
80
  });
81
81
  };
82
82
  const Course = ({
83
- dispatch,
84
- onChange,
85
- onLoading,
86
- onLoaded,
83
+ dispatch = () => {},
84
+ onChange = () => {},
85
+ onLoading = () => {},
86
+ onLoaded = () => {},
87
87
  canvasOrigin
88
88
  }) => {
89
89
  const storeProps = useStoreProps();
@@ -161,10 +161,4 @@ Course.propTypes = {
161
161
  onLoaded: PropTypes.func,
162
162
  canvasOrigin: PropTypes.string.isRequired
163
163
  };
164
- Course.defaultProps = {
165
- dispatch: () => {},
166
- onChange: () => {},
167
- onLoading: () => {},
168
- onLoaded: () => {}
169
- };
170
164
  export default Course;
@@ -35,6 +35,10 @@ export function scaleToSize(imageSize: any, naturalWidth: any, naturalHeight: an
35
35
  width: any;
36
36
  height: any;
37
37
  };
38
+ export function scaleVideoSize(videoSize: any, naturalWidth: any, naturalHeight: any): {
39
+ width: any;
40
+ height: any;
41
+ };
38
42
  export function labelForImageSize(imageSize: any): string;
39
43
  export const MIN_HEIGHT: 10;
40
44
  export const MIN_WIDTH: 10;
@@ -47,3 +51,6 @@ export const EXTRA_LARGE: "extra-large";
47
51
  export const CUSTOM: "custom";
48
52
  export const imageSizes: string[];
49
53
  export const videoSizes: string[];
54
+ export const studioPlayerSizes: string[];
55
+ export const MIN_WIDTH_STUDIO_PLAYER: number;
56
+ export const MIN_HEIGHT_STUDIO_PLAYER: number;
@@ -18,6 +18,7 @@
18
18
 
19
19
  import formatMessage from '../../../format-message';
20
20
  import { scaleForHeight, scaleForWidth } from '../shared/DimensionUtils';
21
+ import RCEGlobals from '../../../rce/RCEGlobals';
21
22
  export const MIN_HEIGHT = 10;
22
23
  export const MIN_WIDTH = 10;
23
24
  export const MIN_WIDTH_VIDEO = 320;
@@ -29,12 +30,29 @@ export const EXTRA_LARGE = 'extra-large';
29
30
  export const CUSTOM = 'custom';
30
31
  export const imageSizes = [SMALL, MEDIUM, LARGE, EXTRA_LARGE, CUSTOM];
31
32
  export const videoSizes = [MEDIUM, LARGE, EXTRA_LARGE, CUSTOM];
33
+ export const studioPlayerSizes = [SMALL, MEDIUM, LARGE, CUSTOM];
32
34
  const sizeByMaximumDimension = {
33
35
  200: SMALL,
34
36
  320: MEDIUM,
35
37
  400: LARGE,
36
38
  640: EXTRA_LARGE
37
39
  };
40
+ const studioPlayerDimensions = {
41
+ [SMALL]: {
42
+ width: 320,
43
+ height: 254
44
+ },
45
+ [MEDIUM]: {
46
+ width: 480,
47
+ height: 300
48
+ },
49
+ [LARGE]: {
50
+ width: 700,
51
+ height: 441
52
+ }
53
+ };
54
+ export const MIN_WIDTH_STUDIO_PLAYER = studioPlayerDimensions[SMALL].width;
55
+ export const MIN_HEIGHT_STUDIO_PLAYER = studioPlayerDimensions[SMALL].height;
38
56
  function parsedOrNull($element, attribute) {
39
57
  // when the image is first inserted into the rce, it's size
40
58
  // is constrained by a style attribute with max-width, max-height.
@@ -49,6 +67,9 @@ function imageSizeFromKnownOptions(imageOptions) {
49
67
  const largestDimension = Math.max(intendedWidth, intendedHeight);
50
68
  return sizeByMaximumDimension[largestDimension] || CUSTOM;
51
69
  }
70
+ function hasHeightAndWidth($element) {
71
+ return $element.hasAttribute('width') && $element.hasAttribute('height');
72
+ }
52
73
  function getPercentageUnitsFromAttributes($element) {
53
74
  const getAttribute = attribute => $element.hasAttribute(attribute) ? $element.getAttribute(attribute) : $element[attribute];
54
75
  const widthValue = getAttribute('width');
@@ -65,7 +86,8 @@ export function fromImageEmbed($element) {
65
86
  naturalWidth: $element.naturalWidth,
66
87
  naturalHeight: $element.naturalHeight,
67
88
  appliedPercentage: percentageUnits || 100,
68
- usePercentageUnits: !!percentageUnits,
89
+ // by default use percentage units
90
+ usePercentageUnits: hasHeightAndWidth($element) ? !!percentageUnits : true,
69
91
  altText: altText || '',
70
92
  isDecorativeImage: altText !== null && altText.replace(/\s/g, '') === '',
71
93
  url: $element.src
@@ -115,7 +137,12 @@ export function fromVideoEmbed($element) {
115
137
  } catch (_ignore) {
116
138
  // bad json?
117
139
  }
118
- videoOptions.videoSize = imageSizeFromKnownOptions(videoOptions);
140
+ if (RCEGlobals.getFeatures()?.consolidated_media_player) {
141
+ const width = videoOptions.appliedWidth || videoOptions.naturalWidth;
142
+ videoOptions.videoSize = [SMALL, MEDIUM, LARGE].find(size => width === studioPlayerDimensions[size].width) || CUSTOM;
143
+ } else {
144
+ videoOptions.videoSize = imageSizeFromKnownOptions(videoOptions);
145
+ }
119
146
  const source = $videoIframe.getAttribute('src');
120
147
  const matches = source?.match(/\/media_attachments_iframe\/(\d+)/);
121
148
  if (matches) {
@@ -151,6 +178,22 @@ export function scaleToSize(imageSize, naturalWidth, naturalHeight) {
151
178
  width: Math.round(naturalWidth * scaleFactor)
152
179
  };
153
180
  }
181
+ export function scaleVideoSize(videoSize, naturalWidth, naturalHeight) {
182
+ if (videoSize === CUSTOM) {
183
+ return {
184
+ width: naturalWidth,
185
+ height: naturalHeight
186
+ };
187
+ }
188
+ const {
189
+ width,
190
+ height
191
+ } = studioPlayerDimensions[videoSize];
192
+ return {
193
+ width,
194
+ height
195
+ };
196
+ }
154
197
  export function labelForImageSize(imageSize) {
155
198
  switch (imageSize) {
156
199
  case SMALL:
@@ -10,29 +10,22 @@ export default class ExternalToolDialog extends React.Component<ExternalToolDial
10
10
  static defaultProps: Partial<ExternalToolDialogProps>;
11
11
  state: ExternalToolDialogState;
12
12
  formRef: React.RefObject<HTMLFormElement>;
13
- beforeInfoAlertRef: React.RefObject<HTMLDivElement>;
14
- afterInfoAlertRef: React.RefObject<HTMLDivElement>;
15
13
  iframeRef: React.RefObject<HTMLIFrameElement>;
16
14
  open(button: RceToolWrapper): void;
17
15
  close(): void;
18
16
  handleBeforeUnload: (ev: Event) => string;
19
17
  private handleExternalContentReady;
20
18
  get resourceSelectionOrigin(): string;
21
- handlePostedMessage: (ev: Pick<MessageEvent, "origin" | "data">) => void;
19
+ handlePostedMessage: (ev: Pick<MessageEvent, "origin" | "data" | "source">) => void;
22
20
  handleClose: () => void;
23
21
  handleOpen: () => void;
24
22
  handleRemove: () => void;
25
- handleInfoAlertFocus: (ev: {
26
- target: Element;
27
- }) => void;
28
- handleInfoAlertBlur: () => void;
29
23
  calcIFrameHeight: () => string;
30
24
  render(): React.JSX.Element;
31
25
  }
32
26
  interface ExternalToolDialogState {
33
27
  open: boolean;
34
28
  button: RceToolWrapper | null;
35
- infoAlert: Element | null;
36
29
  form: ExternalToolDialogForm;
37
30
  iframeLoaded: boolean;
38
31
  }
@@ -21,7 +21,6 @@ import _pt from "prop-types";
21
21
 
22
22
  import { replaceTags } from '../../helpers/tags';
23
23
  import React, { createRef } from 'react';
24
- import { Alert } from '@instructure/ui-alerts';
25
24
  import { Spinner } from '@instructure/ui-spinner';
26
25
  import { Flex } from '@instructure/ui-flex';
27
26
  import ToolLaunchIframe from '../util/ToolLaunchIframe';
@@ -39,13 +38,10 @@ export default class ExternalToolDialog extends React.Component {
39
38
  this.state = {
40
39
  open: false,
41
40
  button: null,
42
- infoAlert: null,
43
41
  form: EMPTY_FORM,
44
42
  iframeLoaded: false
45
43
  };
46
44
  this.formRef = /*#__PURE__*/createRef();
47
- this.beforeInfoAlertRef = /*#__PURE__*/createRef();
48
- this.afterInfoAlertRef = /*#__PURE__*/createRef();
49
45
  this.iframeRef = /*#__PURE__*/createRef();
50
46
  this.handleBeforeUnload = ev => ev.returnValue = formatMessage('Changes you made may not be saved.');
51
47
  this.handleExternalContentReady = data => {
@@ -78,6 +74,7 @@ export default class ExternalToolDialog extends React.Component {
78
74
  this.close();
79
75
  };
80
76
  this.handlePostedMessage = ev => {
77
+ // messages from Canvas in the tool launch frame
81
78
  if (ev.origin === this.resourceSelectionOrigin) {
82
79
  const data = ev.data;
83
80
  if (data?.subject === 'LtiDeepLinkingResponse') {
@@ -89,6 +86,16 @@ export default class ExternalToolDialog extends React.Component {
89
86
  this.handleExternalContentReady(ev.data);
90
87
  }
91
88
  }
89
+ // messages from the tool
90
+ const data = ev.data;
91
+ if (data?.subject === 'lti.close') {
92
+ // Note we currently don't support this message from the forwarder
93
+ // iframe as it's not required by 1EdTech spec and requires more
94
+ // complicated source checking here (see INTEROP-9213)
95
+ if (ev.source === this.iframeRef?.current?.contentWindow) {
96
+ this.handleClose();
97
+ }
98
+ }
92
99
  };
93
100
  this.handleClose = () => {
94
101
  const msg = formatMessage('Are you sure you want to cancel? Changes you made may not be saved.');
@@ -108,12 +115,6 @@ export default class ExternalToolDialog extends React.Component {
108
115
  // force tinyMCE to redraw sticky toolbar otherwise it never goes away
109
116
  window.dispatchEvent(new Event('resize'));
110
117
  };
111
- this.handleInfoAlertFocus = ev => this.setState({
112
- infoAlert: ev.target
113
- });
114
- this.handleInfoAlertBlur = () => this.setState({
115
- infoAlert: null
116
- });
117
118
  this.calcIFrameHeight = () => {
118
119
  if (this.state.button?.use_tray) {
119
120
  return '100%';
@@ -239,16 +240,7 @@ export default class ExternalToolDialog extends React.Component {
239
240
  onClose: this.handleRemove,
240
241
  onCloseButton: this.handleClose,
241
242
  name: (_state$button$title = state.button?.title) !== null && _state$button$title !== void 0 ? _state$button$title : ' '
242
- }, /*#__PURE__*/React.createElement("div", {
243
- ref: this.beforeInfoAlertRef,
244
- tabIndex: 0 // eslint-disable-line jsx-a11y/no-noninteractive-tabindex
245
- ,
246
- onFocus: this.handleInfoAlertFocus,
247
- onBlur: this.handleInfoAlertBlur,
248
- className: this.beforeInfoAlertRef.current != null && state.infoAlert === this.beforeInfoAlertRef.current ? '' : 'screenreader-only'
249
- }, /*#__PURE__*/React.createElement(Alert, {
250
- margin: "small"
251
- }, formatMessage('The following content is partner provided'))), !state.iframeLoaded && /*#__PURE__*/React.createElement(Flex, {
243
+ }, !state.iframeLoaded && /*#__PURE__*/React.createElement(Flex, {
252
244
  alignItems: "center",
253
245
  justifyItems: "center"
254
246
  }, /*#__PURE__*/React.createElement(Flex.Item, null, /*#__PURE__*/React.createElement(Spinner, {
@@ -272,19 +264,7 @@ export default class ExternalToolDialog extends React.Component {
272
264
  onLoad: () => this.setState({
273
265
  iframeLoaded: true
274
266
  })
275
- }), /*#__PURE__*/React.createElement("div", {
276
- ref: this.afterInfoAlertRef,
277
- tabIndex: 0 // eslint-disable-line jsx-a11y/no-noninteractive-tabindex
278
- ,
279
- onFocus: this.handleInfoAlertFocus,
280
- onBlur: this.handleInfoAlertBlur,
281
- style: this.afterInfoAlertRef.current != null && state.infoAlert === this.afterInfoAlertRef.current ? {} : {
282
- bottom: '0'
283
- },
284
- className: this.afterInfoAlertRef.current != null && state.infoAlert === this.afterInfoAlertRef.current ? '' : 'screenreader-only'
285
- }, /*#__PURE__*/React.createElement(Alert, {
286
- margin: "small"
287
- }, formatMessage('The preceding content is partner provided')))));
267
+ })));
288
268
  }
289
269
  }
290
270
  ExternalToolDialog.propTypes = {
@@ -1,8 +1,7 @@
1
1
  import React from 'react';
2
- import { ReactNodeLike } from 'prop-types';
3
2
  import { ModalProps } from '@instructure/ui-modal/types';
4
3
  export declare function ExternalToolDialogModal(props: Pick<ModalProps, 'label' | 'open' | 'onOpen' | 'onClose' | 'mountNode'> & {
5
4
  onCloseButton: () => void;
6
5
  name: string;
7
- children: ReactNodeLike;
6
+ children: React.ReactNode;
8
7
  }): React.JSX.Element;
@@ -30,7 +30,8 @@ export function ExternalToolDialogModal(props) {
30
30
  open: props.open,
31
31
  onOpen: props.onOpen,
32
32
  onClose: props.onClose,
33
- mountNode: props.mountNode
33
+ mountNode: props.mountNode,
34
+ shouldCloseOnDocumentClick: false
34
35
  }, /*#__PURE__*/React.createElement(Modal.Header, null, /*#__PURE__*/React.createElement(Heading, null, props.name), /*#__PURE__*/React.createElement(CloseButton, {
35
36
  placement: "end",
36
37
  offset: "medium",
@@ -32,7 +32,7 @@ import { Tooltip } from '@instructure/ui-tooltip';
32
32
  import { Tray } from '@instructure/ui-tray';
33
33
  import { StoreProvider } from '../../shared/StoreContext';
34
34
  import { ClosedCaptionPanel } from '@instructure/canvas-media';
35
- import { CUSTOM, MIN_WIDTH_VIDEO, MIN_PERCENTAGE, videoSizes, labelForImageSize, scaleToSize } from '../../instructure_image/ImageEmbedOptions';
35
+ import { CUSTOM, MIN_WIDTH_VIDEO, MIN_PERCENTAGE, videoSizes, studioPlayerSizes, labelForImageSize, scaleVideoSize, scaleToSize, MIN_WIDTH_STUDIO_PLAYER, MIN_HEIGHT_STUDIO_PLAYER } from '../../instructure_image/ImageEmbedOptions';
36
36
  import Bridge from '../../../../bridge';
37
37
  import RceApiSource from '../../../../rcs/api';
38
38
  import formatMessage from '../../../../format-message';
@@ -40,6 +40,7 @@ import DimensionsInput, { useDimensionsState } from '../../shared/DimensionsInpu
40
40
  import { getTrayHeight } from '../../shared/trayUtils';
41
41
  import { instuiPopupMountNodeFn } from '../../../../util/fullscreenHelpers';
42
42
  import { parsedStudioOptionsPropType } from '../../shared/StudioLtiSupportUtils';
43
+ import RCEGlobals from '../../../../rce/RCEGlobals';
43
44
  const getLiveRegion = () => document.getElementById('flash_screenreader_holder');
44
45
  export default function VideoOptionsTray({
45
46
  videoOptions,
@@ -54,6 +55,7 @@ export default function VideoOptionsTray({
54
55
  studioOptions = null,
55
56
  forBlockEditorUse = false
56
57
  }) {
58
+ const isConsolidatedMediaPlayer = RCEGlobals.getFeatures()?.consolidated_media_player;
57
59
  const {
58
60
  naturalHeight,
59
61
  naturalWidth
@@ -66,8 +68,8 @@ export default function VideoOptionsTray({
66
68
  const [videoHeight, setVideoHeight] = useState(currentHeight);
67
69
  const [videoWidth, setVideoWidth] = useState(currentWidth);
68
70
  const [subtitles, setSubtitles] = useState(videoOptions.tracks || []);
69
- const [minWidth] = useState(MIN_WIDTH_VIDEO);
70
- const [minHeight] = useState(Math.round(videoHeight / videoWidth * MIN_WIDTH_VIDEO));
71
+ const [minWidth] = useState(isConsolidatedMediaPlayer ? MIN_WIDTH_STUDIO_PLAYER : MIN_WIDTH_VIDEO);
72
+ const [minHeight] = useState(isConsolidatedMediaPlayer ? MIN_HEIGHT_STUDIO_PLAYER : Math.round(videoHeight / videoWidth * MIN_WIDTH_VIDEO));
71
73
  const [minPercentage] = useState(MIN_PERCENTAGE);
72
74
  const [editLocked, setEditLocked] = useState(null);
73
75
  const [loading, setLoading] = useState(true);
@@ -80,6 +82,7 @@ export default function VideoOptionsTray({
80
82
  minPercentage
81
83
  });
82
84
  const api = new RceApiSource(trayProps);
85
+ const videoSizeOptions = isConsolidatedMediaPlayer ? studioPlayerSizes : videoSizes;
83
86
  useEffect(() => {
84
87
  if (videoOptions.attachmentId) {
85
88
  api.getFile(videoOptions.attachmentId, {
@@ -87,7 +90,7 @@ export default function VideoOptionsTray({
87
90
  }).then(response => {
88
91
  setEditLocked(response?.restricted_by_master_course && response?.is_master_course_child_content);
89
92
  setLoading(false);
90
- }).catch(error => {
93
+ }).catch(_error => {
91
94
  setLoading(false);
92
95
  });
93
96
  }
@@ -102,7 +105,7 @@ export default function VideoOptionsTray({
102
105
  event.target.focus();
103
106
  setDisplayAs(event.target.value);
104
107
  }
105
- function handleVideoSizeChange(event, selectedOption) {
108
+ function handleVideoSizeChange(_event, selectedOption) {
106
109
  setVideoSize(selectedOption.value);
107
110
  if (selectedOption.value === CUSTOM) {
108
111
  setVideoHeight(currentHeight);
@@ -111,7 +114,7 @@ export default function VideoOptionsTray({
111
114
  const {
112
115
  height,
113
116
  width
114
- } = scaleToSize(selectedOption.value, naturalWidth, naturalHeight);
117
+ } = isConsolidatedMediaPlayer ? scaleVideoSize(selectedOption.value, naturalWidth, naturalHeight) : scaleToSize(selectedOption.value, naturalWidth, naturalHeight);
115
118
  setVideoHeight(height);
116
119
  setVideoWidth(width);
117
120
  }
@@ -267,7 +270,7 @@ export default function VideoOptionsTray({
267
270
  assistiveText: formatMessage('Use arrow keys to navigate options.'),
268
271
  onChange: handleVideoSizeChange,
269
272
  value: videoSize
270
- }, videoSizes.map(size => /*#__PURE__*/React.createElement(SimpleSelect.Option, {
273
+ }, videoSizeOptions.map(size => /*#__PURE__*/React.createElement(SimpleSelect.Option, {
271
274
  id: `${id}-size-${size}`,
272
275
  key: size,
273
276
  value: size
@@ -113,11 +113,11 @@ export default function DimensionsInput(props) {
113
113
  onChange: handleDimensionTypeChange,
114
114
  value: dimensionsState.usePercentageUnits ? 'percentage' : 'pixels'
115
115
  }, /*#__PURE__*/React.createElement(RadioInput, {
116
- label: formatMessage('Pixels'),
117
- value: "pixels"
118
- }), /*#__PURE__*/React.createElement(RadioInput, {
119
116
  label: formatMessage('Percentage'),
120
117
  value: "percentage"
118
+ }), /*#__PURE__*/React.createElement(RadioInput, {
119
+ label: formatMessage('Pixels'),
120
+ value: "pixels"
121
121
  }))), /*#__PURE__*/React.createElement(Flex.Item, {
122
122
  padding: "small"
123
123
  }, /*#__PURE__*/React.createElement(FormFieldGroup, {