@brightspot/ui 3.0.1-cms-ui-migration.2 → 3.0.1-cms-ui-migration.3

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 (297) hide show
  1. package/README.md +54 -35
  2. package/dist/custom-elements.json +1924 -1924
  3. package/dist/storybook/assets/{ActionBar.stories-hJ_5cm-P.js → ActionBar.stories-CSxtZl7v.js} +1 -1
  4. package/dist/storybook/assets/{ActionItem.stories-Bjx2803w.js → ActionItem.stories-BWcMRMP3.js} +1 -1
  5. package/dist/storybook/assets/{Avatar.stories-Cj0YgZ6f.js → Avatar.stories-CYTUGXzH.js} +1 -1
  6. package/dist/storybook/assets/{AvatarGroup.stories-Lh_sQFCU.js → AvatarGroup.stories-CSYBYo_5.js} +1 -1
  7. package/dist/storybook/assets/{Badge.stories-BL7RUibx.js → Badge.stories-LuF4BuVr.js} +1 -1
  8. package/dist/storybook/assets/{Button-BPHNcxqK.js → Button-CrXCMxHb.js} +1 -1
  9. package/dist/storybook/assets/{Button.stories-CAYO4gdU.js → Button.stories-JVwxdrdM.js} +1 -1
  10. package/dist/storybook/assets/{ButtonGroup.stories-Cd13Us5K.js → ButtonGroup.stories-qtq64a1H.js} +1 -1
  11. package/dist/storybook/assets/{Celebrate.stories-D_KE3Qze.js → Celebrate.stories-DjTtaSd6.js} +1 -1
  12. package/dist/storybook/assets/{Checkbox.stories-Aj1xgZVn.js → Checkbox.stories-v1Pr5mL6.js} +1 -1
  13. package/dist/storybook/assets/{CircularProgress.stories-BecV_v6d.js → CircularProgress.stories-DkV7PJ4D.js} +1 -1
  14. package/dist/storybook/assets/{ClipboardMixin.stories-DU-WiZ2f.js → ClipboardMixin.stories-Dh5c7uSM.js} +1 -1
  15. package/dist/storybook/assets/{Color-6BZIO3FS-BYl4KZZn.js → Color-6BZIO3FS-B1gcREt6.js} +1 -1
  16. package/dist/storybook/assets/{Colors.stories-BMUVUy2q.js → Colors.stories-DnubtRqn.js} +1 -1
  17. package/dist/storybook/assets/{CombinedEffects.stories-FHcPKFm6.js → CombinedEffects.stories-By6akSve.js} +1 -1
  18. package/dist/storybook/assets/{ComponentStatesMixin-EMUnfT5y.js → ComponentStatesMixin-CPLGv3h-.js} +1 -1
  19. package/dist/storybook/assets/{ComponentStatesMixin.stories-SXq0kzS9.js → ComponentStatesMixin.stories-CiQ_lyhm.js} +1 -1
  20. package/dist/storybook/assets/{CopyToClipboard.stories-u43lhvcI.js → CopyToClipboard.stories-cJ7rl3mj.js} +1 -1
  21. package/dist/storybook/assets/{Debounce.stories-BdCn5qgO.js → Debounce.stories-Bw-goobU.js} +1 -1
  22. package/dist/storybook/assets/{DocsRenderer-LL677BLK-DxiEJ_jx.js → DocsRenderer-LL677BLK-CNW57dGQ.js} +3 -3
  23. package/dist/storybook/assets/{Dropdown.stories-BWSRwjIF.js → Dropdown.stories-CwC3HXXd.js} +1 -1
  24. package/dist/storybook/assets/{EmptyState.stories-BpobeZL5.js → EmptyState.stories-BUEJwuhP.js} +1 -1
  25. package/dist/storybook/assets/{Events.stories-CP5kMzpr.js → Events.stories-D1mf9buv.js} +1 -1
  26. package/dist/storybook/assets/{Heading.stories-CbJD-oTB.js → Heading.stories-DO906G4P.js} +1 -1
  27. package/dist/storybook/assets/{HueRipple.stories-BOABJ7zw.js → HueRipple.stories-BOySRSZB.js} +1 -1
  28. package/dist/storybook/assets/{Icon.stories-CWlUHL4j.js → Icon.stories-BsqgTbdG.js} +1 -1
  29. package/dist/storybook/assets/{IconButton.stories-BWBs-OLT.js → IconButton.stories-aza1AAKk.js} +1 -1
  30. package/dist/storybook/assets/{LinearProgress.stories-LZ0GZoxF.js → LinearProgress.stories-CDYmiiex.js} +1 -1
  31. package/dist/storybook/assets/{Pagination.stories-CbLaR3P9.js → Pagination.stories-LXB-x7YB.js} +1 -1
  32. package/dist/storybook/assets/{Popover.stories-JHrWqYZw.js → Popover.stories-DY1HesPJ.js} +1 -1
  33. package/dist/storybook/assets/{ReadyMixin-B1H2a9x8.js → ReadyMixin-DmOC67IJ.js} +1 -1
  34. package/dist/storybook/assets/{RovingTabindexMixin.stories-UMHpYG73.js → RovingTabindexMixin.stories-BWy0Rq8d.js} +1 -1
  35. package/dist/storybook/assets/{Rtc.stories-VQtNSlls.js → Rtc.stories-DrmdqqbI.js} +1 -1
  36. package/dist/storybook/assets/{ScrollShadow.stories-CYdi8Tgp.js → ScrollShadow.stories-DiKFPazh.js} +1 -1
  37. package/dist/storybook/assets/{Switch.stories-D8F2hZCf.js → Switch.stories-BgFnw8_z.js} +1 -1
  38. package/dist/storybook/assets/{Tab.stories-wgBP0lTj.js → Tab.stories-c2wLMooF.js} +1 -1
  39. package/dist/storybook/assets/{Tabs.stories-C00rr5sf.js → Tabs.stories-CjH8seNP.js} +1 -1
  40. package/dist/storybook/assets/{Throttle.stories-BhQEfJbS.js → Throttle.stories-CXysc_QE.js} +1 -1
  41. package/dist/storybook/assets/{Tooltip.stories-CGoZ5qTn.js → Tooltip.stories-6qzyByPm.js} +1 -1
  42. package/dist/storybook/assets/{Upload.stories-B3K-HAXw.js → Upload.stories-BLxykydQ.js} +1 -1
  43. package/dist/storybook/assets/{UploadItem.stories-71ArSoUh.js → UploadItem.stories-CcfcqdJW.js} +1 -1
  44. package/dist/storybook/assets/{Welcome.stories-CihlfFXS.js → Welcome.stories-CzYS_3fH.js} +1 -1
  45. package/dist/storybook/assets/{Widget.stories-1u4KbiJM.js → Widget.stories-Du7T4LAI.js} +1 -1
  46. package/dist/storybook/assets/{WithTooltip-65CFNBJE-B3Jitxw9.js → WithTooltip-65CFNBJE-D4LfXdYt.js} +1 -1
  47. package/dist/storybook/assets/{blocks-C1HaXuQB.js → blocks-DR3fGePu.js} +5 -5
  48. package/dist/storybook/assets/{formatter-EIJCOSYU-Dy9Lt9fs.js → formatter-EIJCOSYU-Cf01216O.js} +1 -1
  49. package/dist/storybook/assets/if-defined-DaMmbcIU.js +1 -0
  50. package/dist/storybook/assets/{iframe-Dx6IxWXF.js → iframe-CQArUhO8.js} +4 -4
  51. package/dist/storybook/assets/{index-OrjedSVh.js → index-B1uI_67G.js} +1 -1
  52. package/dist/storybook/assets/{onFind-YTqjw6W0.js → onFind-BpFkN2Rh.js} +1 -1
  53. package/dist/storybook/assets/{onFind.stories-DEvwTrmx.js → onFind.stories-Cxdeg69X.js} +1 -1
  54. package/dist/storybook/assets/{onRemove.stories-D5mO-Lin.js → onRemove.stories-BHEWpNrE.js} +1 -1
  55. package/dist/storybook/assets/{onVisible.stories-C3Rcz0Eb.js → onVisible.stories-DFJ3S_ZS.js} +1 -1
  56. package/dist/storybook/assets/{style-map-CiMHry7H.js → style-map-DJ83UC3V.js} +1 -1
  57. package/dist/storybook/assets/{syntaxhighlighter-ED5Y7EFY-DIZnuhb2.js → syntaxhighlighter-ED5Y7EFY-zYN83mxK.js} +1 -1
  58. package/dist/storybook/iframe.html +1 -1
  59. package/dist/storybook/project.json +1 -1
  60. package/package.json +16 -2
  61. package/dist/storybook/assets/if-defined-CA2KmTqA.js +0 -1
  62. package/src/legacy/tool-ui/src/main/resources/settings.properties +0 -1
  63. package/src/legacy/tool-ui/src/main/webapp/WEB-INF/web.xml +0 -81
  64. package/src/legacy/tool-ui/src/main/webapp/script/bsp-uploader.js +0 -170
  65. package/src/legacy/tool-ui/src/main/webapp/script/bsp-utils.js +0 -393
  66. package/src/legacy/tool-ui/src/main/webapp/script/content/layout-element.js +0 -141
  67. package/src/legacy/tool-ui/src/main/webapp/script/input/query.js +0 -78
  68. package/src/legacy/tool-ui/src/main/webapp/script/input/workflow.js +0 -718
  69. package/src/legacy/tool-ui/src/main/webapp/script/jquery.extra.js +0 -633
  70. package/src/legacy/tool-ui/src/main/webapp/script/v3/Dropbox.js +0 -18
  71. package/src/legacy/tool-ui/src/main/webapp/script/v3/EditFieldUpdate.js +0 -406
  72. package/src/legacy/tool-ui/src/main/webapp/script/v3/EditFieldUpdateCache.js +0 -1
  73. package/src/legacy/tool-ui/src/main/webapp/script/v3/Notification.js +0 -151
  74. package/src/legacy/tool-ui/src/main/webapp/script/v3/content/edit.js +0 -194
  75. package/src/legacy/tool-ui/src/main/webapp/script/v3/content/state.js +0 -785
  76. package/src/legacy/tool-ui/src/main/webapp/script/v3/csrf.js +0 -35
  77. package/src/legacy/tool-ui/src/main/webapp/script/v3/dashboard.js +0 -65
  78. package/src/legacy/tool-ui/src/main/webapp/script/v3/input/dataTransfer.js +0 -129
  79. package/src/legacy/tool-ui/src/main/webapp/script/v3/input/file.js +0 -433
  80. package/src/legacy/tool-ui/src/main/webapp/script/v3/input/object.js +0 -743
  81. package/src/legacy/tool-ui/src/main/webapp/script/v3/input/read-only.js +0 -17
  82. package/src/legacy/tool-ui/src/main/webapp/script/v3/jquery.frame.js +0 -478
  83. package/src/legacy/tool-ui/src/main/webapp/script/v3/jquery.repeatable.js +0 -2406
  84. package/src/legacy/tool-ui/src/main/webapp/script/v3/plugin/popup.d.ts +0 -2
  85. package/src/legacy/tool-ui/src/main/webapp/script/v3/plugin/popup.js +0 -446
  86. package/src/legacy/tool-ui/src/main/webapp/script/v3/search-filters.js +0 -62
  87. package/src/legacy/tool-ui/src/main/webapp/script/v3/search.js +0 -53
  88. package/src/legacy/tool-ui/src/main/webapp/script/v3.js +0 -1049
  89. package/src/legacy/tool-ui/src/main/webapp/v4/Admin.js +0 -16
  90. package/src/legacy/tool-ui/src/main/webapp/v4/AutoExpand.js +0 -84
  91. package/src/legacy/tool-ui/src/main/webapp/v4/AutoSubmit.js +0 -68
  92. package/src/legacy/tool-ui/src/main/webapp/v4/Bridge.js +0 -536
  93. package/src/legacy/tool-ui/src/main/webapp/v4/CheckboxInput.js +0 -22
  94. package/src/legacy/tool-ui/src/main/webapp/v4/ColorInput.js +0 -5
  95. package/src/legacy/tool-ui/src/main/webapp/v4/ColorInputSpectrum.js +0 -107
  96. package/src/legacy/tool-ui/src/main/webapp/v4/ComboInput.js +0 -1491
  97. package/src/legacy/tool-ui/src/main/webapp/v4/CommunityWidget.js +0 -29
  98. package/src/legacy/tool-ui/src/main/webapp/v4/ContentEdit.js +0 -2427
  99. package/src/legacy/tool-ui/src/main/webapp/v4/ContentLock.js +0 -470
  100. package/src/legacy/tool-ui/src/main/webapp/v4/ContentReporting.js +0 -32
  101. package/src/legacy/tool-ui/src/main/webapp/v4/DataTable.js +0 -31
  102. package/src/legacy/tool-ui/src/main/webapp/v4/DateStringField.js +0 -485
  103. package/src/legacy/tool-ui/src/main/webapp/v4/Entry.js +0 -264
  104. package/src/legacy/tool-ui/src/main/webapp/v4/ExternalItemAuth.js +0 -16
  105. package/src/legacy/tool-ui/src/main/webapp/v4/Form.js +0 -31
  106. package/src/legacy/tool-ui/src/main/webapp/v4/Hierarchy.js +0 -100
  107. package/src/legacy/tool-ui/src/main/webapp/v4/Icon.ts +0 -49
  108. package/src/legacy/tool-ui/src/main/webapp/v4/ImageEditor.js +0 -2403
  109. package/src/legacy/tool-ui/src/main/webapp/v4/ImageEditorBundle.js +0 -5
  110. package/src/legacy/tool-ui/src/main/webapp/v4/LinkCarousel.js +0 -40
  111. package/src/legacy/tool-ui/src/main/webapp/v4/LinkList.js +0 -14
  112. package/src/legacy/tool-ui/src/main/webapp/v4/LinkTable.js +0 -123
  113. package/src/legacy/tool-ui/src/main/webapp/v4/Location.js +0 -19
  114. package/src/legacy/tool-ui/src/main/webapp/v4/LocationMap.js +0 -148
  115. package/src/legacy/tool-ui/src/main/webapp/v4/LookingGlass.js +0 -24
  116. package/src/legacy/tool-ui/src/main/webapp/v4/Message.js +0 -14
  117. package/src/legacy/tool-ui/src/main/webapp/v4/NumberBar.js +0 -32
  118. package/src/legacy/tool-ui/src/main/webapp/v4/Page.js +0 -890
  119. package/src/legacy/tool-ui/src/main/webapp/v4/Preview.js +0 -758
  120. package/src/legacy/tool-ui/src/main/webapp/v4/PreviewEditor.js +0 -86
  121. package/src/legacy/tool-ui/src/main/webapp/v4/PreviewOverlay.js +0 -1005
  122. package/src/legacy/tool-ui/src/main/webapp/v4/PubSub.js +0 -47
  123. package/src/legacy/tool-ui/src/main/webapp/v4/QueryField.js +0 -211
  124. package/src/legacy/tool-ui/src/main/webapp/v4/RegionMap.js +0 -215
  125. package/src/legacy/tool-ui/src/main/webapp/v4/RepeatableContentInputGroup.js +0 -160
  126. package/src/legacy/tool-ui/src/main/webapp/v4/RichTextEditor.js +0 -154
  127. package/src/legacy/tool-ui/src/main/webapp/v4/SearchFields.js +0 -281
  128. package/src/legacy/tool-ui/src/main/webapp/v4/SearchResult.js +0 -255
  129. package/src/legacy/tool-ui/src/main/webapp/v4/SharePreview.js +0 -56
  130. package/src/legacy/tool-ui/src/main/webapp/v4/Sortable.js +0 -874
  131. package/src/legacy/tool-ui/src/main/webapp/v4/StyleEmbeddedContent.js +0 -100
  132. package/src/legacy/tool-ui/src/main/webapp/v4/StyleguidePresets.js +0 -357
  133. package/src/legacy/tool-ui/src/main/webapp/v4/TabContainer.js +0 -360
  134. package/src/legacy/tool-ui/src/main/webapp/v4/Taxonomy.js +0 -27
  135. package/src/legacy/tool-ui/src/main/webapp/v4/ThemeBundleEditor.js +0 -224
  136. package/src/legacy/tool-ui/src/main/webapp/v4/TimedContent.js +0 -147
  137. package/src/legacy/tool-ui/src/main/webapp/v4/TimedContentBundle.js +0 -8
  138. package/src/legacy/tool-ui/src/main/webapp/v4/VideoEditor.js +0 -2417
  139. package/src/legacy/tool-ui/src/main/webapp/v4/VideoEditorBundle.js +0 -8
  140. package/src/legacy/tool-ui/src/main/webapp/v4/ViewMirror.js +0 -52
  141. package/src/legacy/tool-ui/src/main/webapp/v4/ViewPreview.d.ts +0 -13
  142. package/src/legacy/tool-ui/src/main/webapp/v4/ViewPreview.js +0 -177
  143. package/src/legacy/tool-ui/src/main/webapp/v4/Widget.js +0 -90
  144. package/src/legacy/tool-ui/src/main/webapp/v4/__mocks__/fileMock.js +0 -1
  145. package/src/legacy/tool-ui/src/main/webapp/v4/__mocks__/styleMock.js +0 -1
  146. package/src/legacy/tool-ui/src/main/webapp/v4/__mocks__/textArea.mock.js +0 -20
  147. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/globals.js +0 -770
  148. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/ProseMirror.test.js +0 -16
  149. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/index.html +0 -54
  150. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/comment_manager/CommentManager.test.js +0 -29
  151. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/comment_manager/index.html +0 -35
  152. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/custom_keyboard/CustomKeyboard.js +0 -42
  153. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/custom_keyboard/index.html +0 -37
  154. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/enhancement_manager/EnhancementManager.test.js +0 -288
  155. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/enhancement_manager/block.html +0 -38
  156. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/enhancement_manager/inline.html +0 -38
  157. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/enhancement_manager/no-popups.html +0 -38
  158. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/list_manager/ListManager.js +0 -257
  159. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/list_manager/index.html +0 -38
  160. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/menubar/hierarchal.html +0 -33
  161. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/menubar/index.html +0 -33
  162. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/menubar/menubar.test.js +0 -195
  163. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/menubar/small.html +0 -34
  164. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/menubar/tags.html +0 -34
  165. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/placeholder_manager/PlaceholderManager.test.js +0 -134
  166. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/placeholder_manager/has-editable-placeholder.html +0 -32
  167. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/placeholder_manager/has-text.html +0 -34
  168. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/placeholder_manager/index.html +0 -31
  169. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/table_manager/TableManager.test.js +0 -63
  170. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/table_manager/existing.html +0 -48
  171. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/track_manager/TrackManager.test.js +0 -291
  172. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/track_manager/existing.html +0 -39
  173. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/track_manager/insert.html +0 -37
  174. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/Sortable.test.js +0 -105
  175. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/rte/ProseMirror.test.js +0 -41
  176. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/rte/codemirror-shim.test.js +0 -72
  177. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/rte/plugins/collab_manager/CollabManager.test.js +0 -46
  178. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/rte/plugins/enhancement_manager/EnhancementManager.test.js +0 -84
  179. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/rte/plugins/list_manager/ListManager.test.js +0 -54
  180. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/rte/plugins/menubar/menubar.test.js +0 -183
  181. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/rte/plugins/spellcheck/SpellCheck.test.js +0 -45
  182. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/rte/settings/BSSerializer.test.js +0 -346
  183. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/rte/settings/menuItemsBuilder.test.js +0 -226
  184. package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/rte/utilities.test.js +0 -118
  185. package/src/legacy/tool-ui/src/main/webapp/v4/appetizeio/Appetizeio.js +0 -5
  186. package/src/legacy/tool-ui/src/main/webapp/v4/appetizeio/AppetizeioEmbedded.js +0 -113
  187. package/src/legacy/tool-ui/src/main/webapp/v4/compat/Fetch.js +0 -16
  188. package/src/legacy/tool-ui/src/main/webapp/v4/compat/jquery.js +0 -32
  189. package/src/legacy/tool-ui/src/main/webapp/v4/compat/requirejs.js +0 -13
  190. package/src/legacy/tool-ui/src/main/webapp/v4/dom/Tether.js +0 -1
  191. package/src/legacy/tool-ui/src/main/webapp/v4/dom/TetherLayout.js +0 -1
  192. package/src/legacy/tool-ui/src/main/webapp/v4/dom/closest.js +0 -1
  193. package/src/legacy/tool-ui/src/main/webapp/v4/dom/create.js +0 -1
  194. package/src/legacy/tool-ui/src/main/webapp/v4/dom/find.js +0 -1
  195. package/src/legacy/tool-ui/src/main/webapp/v4/dom/findAll.js +0 -1
  196. package/src/legacy/tool-ui/src/main/webapp/v4/dom/ifClick.js +0 -1
  197. package/src/legacy/tool-ui/src/main/webapp/v4/dom/ifMatches.js +0 -1
  198. package/src/legacy/tool-ui/src/main/webapp/v4/dom/ifUnmodified.js +0 -1
  199. package/src/legacy/tool-ui/src/main/webapp/v4/dom/index.js +0 -5
  200. package/src/legacy/tool-ui/src/main/webapp/v4/dom/insertBefore.js +0 -1
  201. package/src/legacy/tool-ui/src/main/webapp/v4/dom/insertFirst.js +0 -1
  202. package/src/legacy/tool-ui/src/main/webapp/v4/dom/insertLast.js +0 -1
  203. package/src/legacy/tool-ui/src/main/webapp/v4/dom/onFind.js +0 -1
  204. package/src/legacy/tool-ui/src/main/webapp/v4/dom/onFindOnce.js +0 -1
  205. package/src/legacy/tool-ui/src/main/webapp/v4/dom/onRTEReady.js +0 -1
  206. package/src/legacy/tool-ui/src/main/webapp/v4/dom/onRemove.js +0 -1
  207. package/src/legacy/tool-ui/src/main/webapp/v4/dom/onVisible.js +0 -1
  208. package/src/legacy/tool-ui/src/main/webapp/v4/dom/previousUntil.js +0 -1
  209. package/src/legacy/tool-ui/src/main/webapp/v4/dom/resolveIconCompat.js +0 -40
  210. package/src/legacy/tool-ui/src/main/webapp/v4/rtc/Socket.js +0 -1
  211. package/src/legacy/tool-ui/src/main/webapp/v4/rtc/index.js +0 -1
  212. package/src/legacy/tool-ui/src/main/webapp/v4/rte/ProseMirror.js +0 -909
  213. package/src/legacy/tool-ui/src/main/webapp/v4/rte/README.md +0 -68
  214. package/src/legacy/tool-ui/src/main/webapp/v4/rte/codemirror-shim.d.ts +0 -8
  215. package/src/legacy/tool-ui/src/main/webapp/v4/rte/codemirror-shim.js +0 -274
  216. package/src/legacy/tool-ui/src/main/webapp/v4/rte/collab-workflow.jpeg +0 -0
  217. package/src/legacy/tool-ui/src/main/webapp/v4/rte/interchangeable.ts +0 -250
  218. package/src/legacy/tool-ui/src/main/webapp/v4/rte/mention.js +0 -90
  219. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/PluginProvider.js +0 -124
  220. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/README.md +0 -46
  221. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/ai_inline_manager/AIInlineManager.ts +0 -124
  222. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/ai_inline_manager/views/AIInlineView.ts +0 -1019
  223. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/ai_manager/AiManager.ts +0 -199
  224. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/collab_manager/CollabManager.js +0 -339
  225. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/collab_manager/views/AvatarView.js +0 -96
  226. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/comment_manager/CommentManager.js +0 -348
  227. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/custom_keyboard/CustomKeyboard.js +0 -110
  228. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/custom_keyboard/README.md +0 -29
  229. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/EnhancementManager.js +0 -428
  230. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/README.md +0 -63
  231. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/commands.js +0 -690
  232. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/constants.js +0 -12
  233. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/enhancement-creation.jpeg +0 -0
  234. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/index.js +0 -15
  235. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/rte-flow.jpeg +0 -0
  236. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/views/ActionButtonView.js +0 -86
  237. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/views/BlockSubmenuView.js +0 -60
  238. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/views/EnhancementView.js +0 -208
  239. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/views/PreviewView.js +0 -102
  240. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/views/SubmenuView.js +0 -365
  241. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/find_replace_manager/FindReplaceManager.js +0 -239
  242. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/find_replace_manager/views/FindView.js +0 -604
  243. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/fullscreen_manager/FullscreenManager.js +0 -57
  244. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/fullscreen_manager/README.md +0 -26
  245. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/fullscreen_manager/commands.js +0 -16
  246. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/fullscreen_manager/index.js +0 -4
  247. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/fullscreen_manager/views/FullscreenView.js +0 -474
  248. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/html_editor_manager/htmlEditorManager.js +0 -66
  249. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/html_editor_manager/views/HtmlEditorView.js +0 -97
  250. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/list_manager/ListManager.js +0 -342
  251. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/list_manager/README.md +0 -50
  252. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/list_manager/commands.js +0 -207
  253. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/list_manager/constants.js +0 -26
  254. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/list_manager/index.js +0 -4
  255. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/menubar/Menubar.js +0 -485
  256. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/menubar/README.md +0 -40
  257. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/menubar/views/MenuView.js +0 -842
  258. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/paste_manager/PasteManager.js +0 -368
  259. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/placeholder_manager/PlaceHolderManager.js +0 -128
  260. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/raw_text_manager/README.md +0 -13
  261. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/raw_text_manager/RawTextManager.js +0 -96
  262. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/spellcheck/index.js +0 -3
  263. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/spellcheck/spellcheck-plugin.js +0 -280
  264. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/spellcheck/spellcheck-service.js +0 -94
  265. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/table_manager/TableManager.js +0 -57
  266. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/table_manager/commands.js +0 -97
  267. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/table_manager/views/TableSizerView.js +0 -88
  268. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/table_manager/views/TableView.js +0 -613
  269. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/track_manager/README.md +0 -13
  270. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/track_manager/TrackManager.js +0 -905
  271. package/src/legacy/tool-ui/src/main/webapp/v4/rte/settings/BSSerializer.js +0 -819
  272. package/src/legacy/tool-ui/src/main/webapp/v4/rte/settings/README.md +0 -80
  273. package/src/legacy/tool-ui/src/main/webapp/v4/rte/settings/commands.js +0 -98
  274. package/src/legacy/tool-ui/src/main/webapp/v4/rte/settings/constants.d.ts +0 -84
  275. package/src/legacy/tool-ui/src/main/webapp/v4/rte/settings/constants.js +0 -87
  276. package/src/legacy/tool-ui/src/main/webapp/v4/rte/settings/index.js +0 -13
  277. package/src/legacy/tool-ui/src/main/webapp/v4/rte/settings/keymapBuilder.js +0 -223
  278. package/src/legacy/tool-ui/src/main/webapp/v4/rte/settings/menuItemsBuilder.js +0 -559
  279. package/src/legacy/tool-ui/src/main/webapp/v4/rte/settings/schemaBuilder.js +0 -1281
  280. package/src/legacy/tool-ui/src/main/webapp/v4/rte/utilities.d.ts +0 -4
  281. package/src/legacy/tool-ui/src/main/webapp/v4/rte/utilities.js +0 -359
  282. package/src/legacy/tool-ui/src/main/webapp/v4/theme/ColorRotator.js +0 -1
  283. package/src/legacy/tool-ui/src/main/webapp/v4/util/debounce.js +0 -1
  284. package/src/legacy/tool-ui/src/main/webapp/v4/util/getComponentKey.js +0 -1
  285. package/src/legacy/tool-ui/src/main/webapp/v4/util/noise.js +0 -1
  286. package/src/legacy/tool-ui/src/main/webapp/v4/util/repaint.js +0 -1
  287. package/src/legacy/tool-ui/src/main/webapp/v4/util/storage.js +0 -1
  288. package/src/legacy/tool-ui/src/main/webapp/v4/util/throttle.js +0 -1
  289. package/src/legacy/tool-ui/src/main/webapp/v4/widget/AssignmentContent.js +0 -33
  290. package/src/legacy/tool-ui/src/main/webapp/v4/widget/AssignmentDeskDashboard.js +0 -217
  291. package/src/legacy/tool-ui/src/main/webapp/v4/widget/AssociatedContentWidget.js +0 -7
  292. package/src/legacy/tool-ui/src/main/webapp/v4/widget/BulkUpload.js +0 -19
  293. package/src/legacy/tool-ui/src/main/webapp/v4/widget/Calendar.js +0 -7
  294. package/src/legacy/tool-ui/src/main/webapp/v4/widget/ClosableWindow.js +0 -13
  295. package/src/legacy/tool-ui/src/main/webapp/v4/widget/PitchAssignments.js +0 -25
  296. package/src/legacy/tool-ui/src/main/webapp/v4/widget/PitchContent.js +0 -33
  297. package/src/legacy/tool-ui/src/main/webapp/v4/widget/Revisions.js +0 -61
@@ -1,2417 +0,0 @@
1
- import closest from './dom/closest'
2
- import create from './dom/create'
3
- import noUiSlider from 'nouislider'
4
- import 'nouislider/distribute/nouislider.css'
5
- import onFind from './dom/onFind'
6
- import onFindOnce from './dom/onFindOnce'
7
- import Plyr from 'plyr'
8
- import 'plyr/dist/plyr.css'
9
- import TabContainer from './TabContainer'
10
-
11
- const STATE_VIDEO_LOADING = 'is-VideoEditorLoading'
12
- const STATE_VIDEO_LOADED = 'is-VideoEditorLoaded'
13
- const STATE_OPEN = 'is-VideoEditorOpen'
14
- const COUNT_FILMSTRIP_CELL = 10
15
- const CROSS_ORIGIN_KEYWORD = 'use-credentials'
16
-
17
- export default class VideoEditor {
18
- constructor(element) {
19
- // The element which the video editor UI originated from.
20
- // For instance the original form UI where all the inputs originate from.
21
- this.originEl = element
22
- // The element that the Video Editor UI becomes a sibling of.
23
- // For instance a popup UI where the new editor elements are created.
24
- this.rootEl = this.originEl.closest('form')
25
-
26
- const asideTop = create('div', { class: 'VideoEditorAside-top' }, [
27
- create('h2', 'Edit Video'),
28
- create(
29
- 'button',
30
- {
31
- className: 'VideoEditor-stage-closeButton',
32
- name: 'action-done',
33
- onclick: (event) => {
34
- this._VideoPlayer.pause()
35
- this.rootEl.classList.remove(STATE_OPEN)
36
- event.preventDefault()
37
- },
38
- },
39
- 'Done',
40
- ),
41
- ])
42
- const asideBody = create('div', { class: 'VideoEditorAside-body' })
43
- const aside = create('div', { class: 'VideoEditor-stage-aside' }, [
44
- asideTop,
45
- asideBody,
46
- ])
47
-
48
- // This Clip Model serves as a central point for multiple UIs to sync their view of the data.
49
- this.ClipModel = {
50
- clips: new Map(),
51
- observers: [],
52
- observersOnRemove: [],
53
- addClip: function (data) {
54
- const { key, placeholder } = data
55
-
56
- // When both key and placeholder are present
57
- // the placeholder data structure needs to be updated internally
58
- // since it will be missing properties unavailable at the time of creation.
59
- if (key && placeholder) {
60
- if (this.clips.has(placeholder)) {
61
- const clip = { ...this.clips.get(placeholder) }
62
- this.clips.set(key, { ...data, ...clip })
63
- this.clips.delete(placeholder)
64
- } else if (this.clips.has(key)) {
65
- const merged = { ...this.clips.get(key), ...data }
66
- this.clips.delete(key)
67
- this.clips.set(key, merged)
68
- }
69
- }
70
-
71
- if (!this.clips.has(key)) {
72
- this.clips.set(key || placeholder, data)
73
- }
74
-
75
- this.notify()
76
- },
77
- removeClip: function (key) {
78
- if (this.clips.has(key)) {
79
- this.clips.delete(key)
80
- this.notifyOnRemove(key)
81
- }
82
- },
83
- getClips: function () {
84
- return this.clips
85
- },
86
- observe: function (callback) {
87
- this.observers.push(callback)
88
- },
89
- observeOnRemove: function (callback) {
90
- this.observersOnRemove.push(callback)
91
- },
92
- notifyOnRemove: function (removedKey) {
93
- this.observersOnRemove.forEach((callback) => callback(removedKey))
94
- },
95
- notify: function () {
96
- this.observers.forEach((callback) => callback(this.clips))
97
- },
98
- }
99
-
100
- // Opener element.
101
- onFind(this.originEl, '.FilePreview-actions', (target) => {
102
- target.insertAdjacentElement(
103
- 'beforeend',
104
- create(
105
- 'a',
106
- {
107
- class: 'icon icon-action-edit',
108
- href: '#',
109
- onclick: (event) => {
110
- // Pause video that could be playing before showing the overlay.
111
- this.rootEl
112
- .querySelectorAll('video')
113
- .forEach((videoPlayer) => videoPlayer.pause())
114
- this.rootEl.classList.add(STATE_OPEN)
115
- },
116
- },
117
- 'Edit',
118
- ),
119
- )
120
- })
121
-
122
- // Popup UI.
123
- this.rootEl.insertAdjacentElement(
124
- 'beforeend',
125
- create(
126
- 'div',
127
- {
128
- class: 'VideoEditor-stage',
129
- },
130
- aside,
131
- ),
132
- )
133
-
134
- this._initVideo().then(async (sourceVideo) => {
135
- this._updateVideoState(STATE_VIDEO_LOADED)
136
-
137
- await this._createVideoPlayer(sourceVideo)
138
-
139
- // Move the video player into the overlay.
140
- aside.insertAdjacentElement(
141
- 'beforebegin',
142
- create(
143
- 'div',
144
- { class: 'VideoEditor-stage-main' },
145
- this._VideoPlayer.elements.container,
146
- ),
147
- )
148
-
149
- aside.insertAdjacentElement(
150
- 'beforebegin',
151
- create(
152
- 'div',
153
- { className: 'VideoEditor-stage-bottom' },
154
- create('div', { className: 'VideoEditor-minimap' }, [
155
- create('div', { className: 'VideoEditor-overlays' }),
156
- create('div', { className: 'VideoEditor-filmstrip' }, [
157
- create('div', { className: 'VideoEditorFilmstrip-images' }),
158
- create('div', { className: 'VideoEditorFilmstrip-clips' }),
159
- ]),
160
- ]),
161
- ),
162
- )
163
-
164
- this._ColorUI = this._createColorUI()
165
-
166
- // Creates the Clipping UI once the reference form elements exist.
167
- await new Promise((resolve, reject) => {
168
- onFind(this.originEl, '[data-field-name="clippings"]', (el) => {
169
- resolve(el)
170
- })
171
- })
172
- .then((referenceEl) => {
173
- this._ClippingUI = this._createClippingUI(referenceEl)
174
- })
175
- .catch((error) => {
176
- console.warn(`Failed to create the Clipping UI due to: ${error}`)
177
- })
178
-
179
- this._OverlayUI = this._createOverlayUI(
180
- this._VideoPlayer,
181
- sourceVideo.videoWidth,
182
- sourceVideo.videoHeight,
183
- )
184
-
185
- this._MiniMap = this._createMiniMap(sourceVideo, this._OverlayUI)
186
-
187
- if (this._ColorUI) asideBody.append(this._ColorUI.element)
188
- if (this._OverlayUI) asideBody.append(this._OverlayUI.element)
189
- if (this._ClippingUI) asideBody.append(this._ClippingUI.element)
190
-
191
- new TabContainer(asideBody) // eslint-disable-line no-new
192
-
193
- this._applyInitialValues([
194
- this._ColorUI,
195
- this._OverlayUI,
196
- this._ClippingUI,
197
- ])
198
- })
199
- }
200
-
201
- getState = () => {
202
- return this._currentVideoState
203
- }
204
-
205
- SMPTEToSeconds = (timecode, frameRate = 24) => {
206
- const time = timecode.split(':')
207
- const frames = Number(time[3])
208
- const milliseconds = (1 / frameRate) * (isNaN(frames) ? 0 : frames)
209
- return (
210
- Number(time[0]) * 60 * 60 +
211
- Number(time[1]) * 60 +
212
- Number(time[2]) +
213
- milliseconds
214
- )
215
- }
216
-
217
- /** @preserve
218
- VideoFrame: HTML5 Video - SMTPE Time Code capturing and Frame Seeking API
219
- @version 0.2.2
220
- @author Allen Sarkisyan
221
- @copyright (c) 2013 Allen Sarkisyan
222
- @license Released under the Open Source MIT License
223
- Contributors:
224
- Allen Sarkisyan - Lead engineer
225
- Paige Raynes - Product Development
226
- Dan Jacinto - Video Asset Quality Analyst
227
- Permission is hereby granted, free of charge, to any person obtaining a copy
228
- of this software and associated documentation files (the "Software"), to deal
229
- in the Software without restriction, including without limitation the rights
230
- to use, copy, modify, merge, publish, and/or distribute copies of the
231
- Software, and to permit persons to whom the Software is furnished to do so,
232
- subject to the following conditions:
233
- - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
234
- - Attribution must be credited to the original authors in derivative works.
235
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
236
- OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
237
- HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
238
- OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
239
- */
240
- secondsToSMPTE = (seconds, frameRate = 24) => {
241
- const dt = new Date()
242
- const format = 'hh:mm:ss:ff'
243
- dt.setHours(0)
244
- dt.setMinutes(0)
245
- dt.setSeconds(0)
246
- dt.setMilliseconds(seconds * 1000)
247
- function wrap(n) {
248
- return n < 10 ? '0' + n : n
249
- }
250
- return format.replace(/hh|mm|ss|ff/g, function (format) {
251
- switch (format) {
252
- case 'hh':
253
- return wrap(dt.getHours() < 13 ? dt.getHours() : dt.getHours() - 12)
254
- case 'mm':
255
- return wrap(dt.getMinutes())
256
- case 'ss':
257
- return wrap(dt.getSeconds())
258
- case 'ff':
259
- return wrap(Math.floor((seconds % 1) * frameRate))
260
- }
261
- })
262
- }
263
-
264
- isIntersecting = (a, b) => {
265
- return (
266
- a.left <= b.right &&
267
- b.left <= a.right &&
268
- a.top <= b.bottom &&
269
- b.top <= a.bottom
270
- )
271
- }
272
-
273
- // Converts from an SMPTE time (HH:MM:SS:FF) to an x coordinate given:
274
- // @SMPTE: the time expressed as SMPTE
275
- // @secondsTotal: the total time in seconds
276
- // @boundingEl: the DOM element that represents the total surface area from which the "x" coordinate will be determined.
277
- fromSMPTEToXCoordinate = (SMPTE, secondsTotal, boundingEl) => {
278
- const millis = this.SMPTEToSeconds(SMPTE) * 1000
279
- const percent = millis / (secondsTotal * 1000)
280
- const { width } = boundingEl.getBoundingClientRect()
281
- return width * percent
282
- }
283
-
284
- combine = (r1, r2) => {
285
- const x1 = Math.max(r1.left, r2.left)
286
- const y1 = Math.max(r1.top, r2.top)
287
- const x2 = Math.min(r1.left + r1.width, r2.left + r2.width)
288
- const y2 = Math.min(r1.top + r1.height, r2.top + r2.height)
289
- return { x: x1, y: y1, intersectionWidth: x2 - x1, height: y2 - y1 }
290
- }
291
-
292
- createSlider = (
293
- root,
294
- inputs,
295
- onUpdate,
296
- startvalueOverrides = null,
297
- rangeOverrides = null,
298
- formatOverrides = null,
299
- ) => {
300
- let startvalueOverridesArray = null
301
- const inputArray = Array.isArray(inputs) ? inputs : [inputs]
302
- if (startvalueOverrides) {
303
- startvalueOverridesArray = Array.isArray(startvalueOverrides)
304
- ? startvalueOverrides
305
- : [startvalueOverrides]
306
- }
307
-
308
- const startValues = inputArray.map((obj) => {
309
- return obj.value
310
- })
311
- const minRanges = inputArray.map((obj) => {
312
- return +obj.getAttribute('min')
313
- })
314
- const maxRanges = inputArray.map((obj) => {
315
- return +obj.getAttribute('max')
316
- })
317
-
318
- const defaultOptions = {
319
- start: startvalueOverridesArray || startValues,
320
- connect: true,
321
- range: rangeOverrides || {
322
- min: minRanges,
323
- max: maxRanges,
324
- },
325
- }
326
-
327
- let mergedOptions = { ...defaultOptions }
328
- if (formatOverrides) {
329
- mergedOptions.format = formatOverrides
330
- }
331
-
332
- noUiSlider.create(root, mergedOptions)
333
-
334
- root.noUiSlider.on('update', (values, handle) => {
335
- const value = values[handle]
336
- onUpdate(value)
337
- })
338
-
339
- // Updates the original form input only after a change was made.
340
- root.noUiSlider.on('set', (values, handle) => {
341
- inputArray[handle].value = values[handle]
342
- inputArray[handle].dispatchEvent(new Event('change', { bubbles: true }))
343
- })
344
- }
345
-
346
- async _initVideo() {
347
- this._updateVideoState(STATE_VIDEO_LOADING)
348
- return new Promise((resolve, reject) => {
349
- const videoSelector =
350
- '.VideoEditor-fileContainer video:not(.VideoEditor-captureVideo):not(.awsElementalVideo)'
351
- // Finds only the reference HTML5 video one time.
352
- // This should ignore all other video elements. eg: clipping video elements and the temp video for filmstrip capture.
353
- onFindOnce(videoSelector, () => {
354
- const videoEl = document.querySelector(videoSelector)
355
- videoEl.readyState >= 2
356
- ? resolve(videoEl)
357
- : videoEl.addEventListener('loadedmetadata', (event) => {
358
- resolve(videoEl)
359
- })
360
- })
361
- })
362
- }
363
-
364
- async _createMiniMap(video, OverlayUI) {
365
- let frameLoop = null
366
- let sourceVideo = video
367
- const offset = 24
368
- const STATE_SELECTING = 'is-selecting'
369
- const filmstripEl = this.rootEl.querySelector('.VideoEditor-filmstrip')
370
- const minimapEl = this.rootEl.querySelector('.VideoEditor-minimap')
371
-
372
- this.createFilmstripPlaceholder(sourceVideo)
373
-
374
- await this.createFilmstrip(
375
- this.originEl.querySelector('.FilePreview-actions + video'),
376
- sourceVideo.videoWidth,
377
- sourceVideo.videoHeight,
378
- sourceVideo.duration,
379
- )
380
-
381
- let resizingElement = false
382
- let currentSelection = null
383
- let _isUserClipping = false
384
- let startX = 0
385
- let startMillis, stopMillis // eslint-disable-line no-unused-vars
386
-
387
- const playhead = create('span', { className: 'playhead' })
388
- minimapEl.appendChild(playhead)
389
-
390
- const playHeadWidthHalved = parseInt(playhead.offsetWidth) / 2
391
-
392
- const _fromXCoordinateToSMPTE = (x) => {
393
- const total = this.rootEl.querySelector(
394
- '.VideoEditor-filmstrip',
395
- ).offsetWidth
396
- const percent = x / total
397
- const durationMillis = sourceVideo.duration * 1000
398
- return durationMillis * percent
399
- }
400
-
401
- const mouse = {
402
- _x: 0,
403
- x: 0,
404
-
405
- setOrigin: (e) => {
406
- this._x = e.offsetLeft
407
- },
408
- }
409
-
410
- mouse.setOrigin(minimapEl)
411
-
412
- const _drawSelection = (cursorX) => {
413
- if (currentSelection) {
414
- const left = parseInt(currentSelection.getAttribute('x'))
415
-
416
- // Cursor is moving to the left of the starting coordinate.
417
- if (cursorX < startX) {
418
- currentSelection.setAttribute('x', `${cursorX}px`)
419
- currentSelection.setAttribute(
420
- 'width',
421
- `${Math.abs(cursorX - startX)}px`,
422
- )
423
- // Cursor is moving to the right of the starting coordinate.
424
- } else {
425
- currentSelection.setAttribute('x', `${startX}px`)
426
- currentSelection.setAttribute(
427
- 'width',
428
- `${Math.abs(cursorX - left)}px`,
429
- )
430
- }
431
- }
432
- }
433
-
434
- const _cancelSelection = () => {
435
- minimapEl.classList.remove(STATE_SELECTING)
436
- if (currentSelection) currentSelection.remove()
437
- // Cancel the frameloop to ensure the video playback is smooth.
438
- window.cancelAnimationFrame(frameLoop)
439
- currentSelection = null
440
- _isUserClipping = false
441
- }
442
-
443
- const _createClippingSurface = (target, x, width) => {
444
- const { width: containerWidth } = target.getBoundingClientRect()
445
- let xPercent = ((x - offset) / containerWidth) * 100
446
- let widthPercent = (width / containerWidth) * 100
447
-
448
- const ClipSurfaces = this.rootEl.querySelectorAll(
449
- `.VideoEditor-filmstrip .VideoEditorClip-surface`,
450
- )
451
-
452
- // Generate a unique placeholder key.
453
- // This key is important as the model uses it to determine
454
- // if an object in the model needs an update later once the CMS has generated a UUID.
455
- const placeholder = `placeholder-${Date.now()}`
456
- const surface = create('div', {
457
- classList: 'VideoEditorClip-surface',
458
- 'data-placeholder': placeholder,
459
- })
460
- surface.style.left = `${xPercent}%`
461
- surface.style.width = `${widthPercent}%`
462
- target.appendChild(surface)
463
-
464
- // Check if any existing Clipping surfaces are intersecting this new one and combine them.
465
- let combinedSurfaceWidth
466
- ClipSurfaces.forEach((ClipSurface) => {
467
- // Don't process the same surface we just resized
468
- if (surface === ClipSurface) {
469
- return
470
- }
471
-
472
- const newSurfaceRect = surface.getBoundingClientRect()
473
- const existingSurfaceRect = ClipSurface.getBoundingClientRect()
474
-
475
- // Check if this new clipping rect intersects an existing one and combine them.
476
- if (this.isIntersecting(newSurfaceRect, existingSurfaceRect)) {
477
- const combinedRect = this.combine(newSurfaceRect, existingSurfaceRect)
478
- // User is combining a clipping surface into one that exists "to the left"
479
- // which requires shifting the element left before we change the width.
480
- if (existingSurfaceRect.left < newSurfaceRect.left) {
481
- xPercent =
482
- ((existingSurfaceRect.left - offset) / containerWidth) * 100
483
- surface.style.left = `${xPercent}%`
484
- }
485
-
486
- // Calculate the new width of the combined surfaces and convert to percentage of the whole.
487
- combinedSurfaceWidth =
488
- newSurfaceRect.width +
489
- existingSurfaceRect.width -
490
- combinedRect.intersectionWidth
491
- widthPercent = (combinedSurfaceWidth / containerWidth) * 100
492
- surface.style.width = `${widthPercent}%`
493
-
494
- // Remove the merged Clip data from the model.
495
- this.ClipModel.removeClip(ClipSurface.getAttribute('data-ref-key'))
496
- }
497
- })
498
-
499
- return surface
500
- }
501
-
502
- const _createSelection = (left) => {
503
- const svgns = 'http://www.w3.org/2000/svg'
504
- const svg = document.createElementNS(svgns, 'svg')
505
- svg.setAttribute('class', 'VideoEditorFilmstrip-selectionContainer')
506
- svg.setAttribute('preserveAspectRatio', 'none')
507
-
508
- const rect = document.createElementNS(svgns, 'rect')
509
- rect.setAttribute('class', 'VideoEditorFilmstrip-selection')
510
- rect.setAttribute('x', `${left}`)
511
- rect.setAttribute('height', '100%')
512
- svg.appendChild(rect)
513
-
514
- return rect
515
- }
516
-
517
- const _removeSelection = (selectionBox) => {
518
- selectionBox.parentElement.remove()
519
- }
520
-
521
- const _movePlayhead = (x) => {
522
- playhead.style.transform = `translateX(${x}px)`
523
- }
524
-
525
- const minimumSize = 1
526
- let downTarget = null
527
- let originalWidth = 0
528
- let originalX = 0
529
- let originalMouseX = 0
530
-
531
- const _resizeClippingSurface = (evt) => {
532
- const containerRect = filmstripEl.getBoundingClientRect()
533
- const { width: containerWidth } = containerRect
534
- const relativeX = mouse.x - containerRect.left
535
- const element = downTarget.closest('.VideoEditorClip-surface')
536
-
537
- // Snaps to the edges of the container in either direction.
538
- if (relativeX <= 0) {
539
- element.style.left = '0%'
540
- return
541
- } else if (relativeX >= containerRect.width) {
542
- const remainingPercent = 100 - parseFloat(element.style.left)
543
- element.style.width = `${remainingPercent}%`
544
- return
545
- }
546
-
547
- // Adjusting the right side of the clip surface
548
- if (downTarget.classList.contains('VideoEditorClip-resizerRight')) {
549
- const width = originalWidth + (evt.pageX - originalMouseX)
550
- let widthPercent = (width / containerWidth) * 100
551
-
552
- if (width > minimumSize) {
553
- element.style.width = `${widthPercent}%`
554
- }
555
- // Adjusting the left side of the clip surface
556
- } else {
557
- const width = originalWidth - (evt.pageX - originalMouseX)
558
- let widthPercent = (width / containerWidth) * 100
559
-
560
- if (width > minimumSize) {
561
- element.style.width = `${widthPercent}%`
562
- const newX = originalX + (evt.pageX - offset - originalMouseX)
563
- let xPercent = (newX / containerWidth) * 100
564
- element.style.left = `${xPercent}%`
565
- }
566
- }
567
- }
568
-
569
- const onMouseEnterHandler = (evt) => {
570
- this._VideoPlayer.stop()
571
- window.cancelAnimationFrame(frameLoop)
572
- frameLoop = window.requestAnimationFrame(doFrameLoop)
573
- playhead.classList.remove('is-hidden')
574
- }
575
-
576
- const onMouseLeaveHandler = (evt) => {
577
- window.cancelAnimationFrame(frameLoop)
578
- playhead.classList.add('is-hidden')
579
- }
580
-
581
- const onMouseDownHandler = (evt) => {
582
- downTarget = evt.target
583
-
584
- const handleResize = (element) => {
585
- resizingElement = element
586
-
587
- originalWidth = parseFloat(
588
- window
589
- .getComputedStyle(element, null)
590
- .getPropertyValue('width')
591
- .replace('px', ''),
592
- )
593
- originalX = element.getBoundingClientRect().left
594
- originalMouseX = mouse.x
595
- }
596
-
597
- if (downTarget.classList.contains('VideoEditorClip-resizerLeft')) {
598
- evt.preventDefault()
599
- handleResize(downTarget.closest('.VideoEditorClip-surface'))
600
- } else if (
601
- downTarget.classList.contains('VideoEditorClip-resizerRight')
602
- ) {
603
- evt.preventDefault()
604
- handleResize(downTarget.closest('.VideoEditorClip-surface'))
605
- } else {
606
- _handleSelection(evt)
607
- }
608
- }
609
-
610
- const onMouseUpHandler = (evt) => {
611
- if (resizingElement) {
612
- const surface = resizingElement
613
- const placeholder = `placeholder-${Date.now()}`
614
- surface.setAttribute('data-placeholder', placeholder)
615
-
616
- resizingElement = false
617
-
618
- const ClipSurfaces = this.rootEl.querySelectorAll(
619
- `.VideoEditor-filmstrip .VideoEditorClip-surface`,
620
- )
621
-
622
- let combinedSurfaceWidth
623
- ClipSurfaces.forEach((ClipSurface) => {
624
- // (Exit Early) Don't process the same surface that triggered the handler
625
- if (surface === ClipSurface) {
626
- return
627
- }
628
-
629
- const newSurfaceRect = surface.getBoundingClientRect()
630
- const existingSurfaceRect = ClipSurface.getBoundingClientRect()
631
-
632
- // Check if this new clipping rect intersects an existing one and combine them.
633
- if (this.isIntersecting(newSurfaceRect, existingSurfaceRect)) {
634
- const combinedRect = this.combine(
635
- newSurfaceRect,
636
- existingSurfaceRect,
637
- )
638
- // User is combining a clipping surface into one that exists "to the left"
639
- // which requires shifting the element left before we change the width.
640
- if (existingSurfaceRect.left < newSurfaceRect.left) {
641
- surface.style.left = `${existingSurfaceRect.left - offset}px`
642
- }
643
-
644
- combinedSurfaceWidth =
645
- newSurfaceRect.width +
646
- existingSurfaceRect.width -
647
- combinedRect.intersectionWidth
648
-
649
- surface.style.width = `${combinedSurfaceWidth}px`
650
-
651
- // Remove the merged Clip data from the model.
652
- this.ClipModel.removeClip(ClipSurface.getAttribute('data-ref-key'))
653
- }
654
- })
655
-
656
- const { x: surfaceX, width: surfaceWidth } =
657
- surface.getBoundingClientRect()
658
-
659
- // Recalculates the coordinates as timecodes since clipping surfaces may have been combined.
660
- startMillis = _fromXCoordinateToSMPTE(surfaceX - offset)
661
- stopMillis = _fromXCoordinateToSMPTE(surfaceX + surfaceWidth - offset)
662
- const startTimeSMPTE = this.secondsToSMPTE(startMillis / 1000)
663
- const endTimeSMPTE = this.secondsToSMPTE(stopMillis / 1000)
664
-
665
- // Updates the model to notify all observing views.
666
- this.ClipModel.addClip({
667
- key: surface.getAttribute('data-ref-key'),
668
- placeholder: surface.getAttribute('data-placeholder'),
669
- startTimeSMPTE,
670
- endTimeSMPTE,
671
- })
672
- }
673
- }
674
-
675
- const onMouseLeaveBottomHandler = (evt) => {
676
- _cancelSelection()
677
- }
678
-
679
- const onMouseMoveHandler = (evt) => {
680
- mouse.x = evt.pageX
681
- mouse.clientX = evt.clientX
682
-
683
- if (resizingElement) {
684
- _resizeClippingSurface(evt)
685
- }
686
- }
687
-
688
- const onKeyDownHandler = (evt) => {
689
- if (evt.key === 'Escape') {
690
- if (_isUserClipping) {
691
- _cancelSelection()
692
- }
693
- }
694
- }
695
-
696
- const _makeResizeable = (targetEl) => {
697
- const resizeLeft = create('div', {
698
- className: 'VideoEditorClip-resizerLeft',
699
- })
700
- const resizeRight = create('div', {
701
- className: 'VideoEditorClip-resizerRight',
702
- })
703
-
704
- targetEl.appendChild(
705
- create('div', { className: 'VideoEditorClip-resizeUI' }, [
706
- resizeLeft,
707
- resizeRight,
708
- ]),
709
- )
710
- }
711
-
712
- const _handleSelection = (evt) => {
713
- startX = evt.x - offset
714
- const target = this.rootEl.querySelector('.VideoEditor-filmstrip')
715
- const percent = startX / minimapEl.offsetWidth
716
- const millis = sourceVideo.duration * percent * 1000
717
-
718
- // There are 2 possible states when clicking:
719
- // A: A clip surface isn't defined yet and the User is creating one.
720
- // B: A clip surface is defined and the User is finishing it.
721
- if (!currentSelection) {
722
- _isUserClipping = true
723
- minimapEl.classList.add(STATE_SELECTING)
724
-
725
- const selection = _createSelection(startX)
726
- target.appendChild(selection.parentElement)
727
-
728
- currentSelection = selection
729
- startMillis = millis
730
- } else {
731
- const selection = currentSelection
732
- _isUserClipping = false
733
- currentSelection = false
734
- stopMillis = millis
735
-
736
- const { x: selectionX, width: selectionWidth } =
737
- selection.getBoundingClientRect()
738
-
739
- const surface = _createClippingSurface(
740
- target,
741
- selectionX,
742
- selectionWidth,
743
- )
744
-
745
- _makeResizeable(surface)
746
- _removeSelection(selection)
747
-
748
- minimapEl.classList.remove(STATE_SELECTING)
749
-
750
- const { x: surfaceX, width: surfaceWidth } =
751
- surface.getBoundingClientRect()
752
-
753
- // Recalculates the coordinates as timecodes since clipping surfaces may have been combined.
754
- startMillis = _fromXCoordinateToSMPTE(surfaceX - offset)
755
- stopMillis = _fromXCoordinateToSMPTE(surfaceX + surfaceWidth - offset)
756
- const placeholder = surface.getAttribute('data-placeholder')
757
- const startTimeSMPTE = this.secondsToSMPTE(startMillis / 1000)
758
- const endTimeSMPTE = this.secondsToSMPTE(stopMillis / 1000)
759
-
760
- // Updates the model to notify all observing views.
761
- this.ClipModel.addClip({
762
- placeholder,
763
- startTimeSMPTE,
764
- endTimeSMPTE,
765
- })
766
- }
767
- }
768
-
769
- const stageBottom = this.rootEl.querySelector('.VideoEditor-stage-bottom')
770
-
771
- stageBottom.onmouseleave = onMouseLeaveBottomHandler
772
- minimapEl.onmousedown = onMouseDownHandler
773
- minimapEl.onmouseup = onMouseUpHandler
774
- minimapEl.onmouseenter = onMouseEnterHandler
775
- minimapEl.onmouseleave = onMouseLeaveHandler
776
- minimapEl.onmousemove = onMouseMoveHandler
777
- document.onkeydown = onKeyDownHandler
778
-
779
- const doFrameLoop = (time) => {
780
- const rect = minimapEl.getBoundingClientRect()
781
- const newX = mouse.lastX - minimapEl.offsetLeft
782
-
783
- _movePlayhead(newX - playHeadWidthHalved)
784
- _drawSelection(newX)
785
-
786
- this._VideoPlayer.currentTime = sourceVideo.duration * (newX / rect.width)
787
-
788
- mouse.lastX = mouse.x
789
- mouse.lastY = mouse.y
790
- frameLoop = window.requestAnimationFrame(doFrameLoop) // get next frame
791
- }
792
-
793
- // Store the SVG namespace for easy reuse.
794
- const svgns = 'http://www.w3.org/2000/svg'
795
-
796
- // Create <svg>, <defs>, <linearGradient> and <rect> elements using createElementNS to apply the SVG namespace.
797
- const svg = document.createElementNS(svgns, 'svg')
798
- const GRADIENT_ID = `gradient-${Date.now()}`
799
- const gradient = document.createElementNS(svgns, 'linearGradient')
800
- const rect = document.createElementNS(svgns, 'rect')
801
-
802
- // Assign an id, classname, width and height
803
- svg.setAttribute('width', '100%')
804
- svg.setAttribute('height', '100%')
805
- svg.setAttribute('version', '1.1')
806
- svg.setAttribute('xmlns', svgns)
807
- svg.setAttribute('class', 'VideoOverlays-clipBackground')
808
-
809
- // Apply the <lineargradient> to <defs>
810
- gradient.id = GRADIENT_ID
811
- gradient.setAttribute('x1', '0')
812
- gradient.setAttribute('x2', '100%')
813
- gradient.setAttribute('y1', '0')
814
- gradient.setAttribute('y2', '0')
815
- svg.appendChild(gradient)
816
-
817
- // Setup the <rect> element.
818
- rect.setAttribute('fill', `url(#${GRADIENT_ID})`)
819
- rect.setAttribute('width', '100%')
820
- rect.setAttribute('height', '100%')
821
- svg.appendChild(rect)
822
-
823
- this.rootEl.querySelector('.VideoEditor-overlays').appendChild(svg)
824
-
825
- const clipSvg = document.createElementNS(svgns, 'svg')
826
- const clipDefs = document.createElementNS(svgns, 'defs')
827
- const clipPath = document.createElementNS(svgns, 'clipPath')
828
- clipSvg.setAttribute('version', '1.1')
829
- clipSvg.setAttribute('xmlns', svgns)
830
- clipSvg.setAttribute('class', 'VideoOverlays-clippingSvg')
831
-
832
- clipPath.setAttribute('id', 'VideoEditorOverlays-svgPath')
833
- clipDefs.appendChild(clipPath)
834
- clipSvg.appendChild(clipDefs)
835
-
836
- this.rootEl.querySelector('.VideoEditor-overlays').appendChild(clipSvg)
837
-
838
- const _createStop = (offset, color) => {
839
- const stop = document.createElementNS(svgns, 'stop')
840
- stop.setAttribute('offset', `${offset}%`)
841
- stop.setAttribute('stop-color', `${color}`)
842
- return stop
843
- }
844
-
845
- // Forces the browser to repaint the SVG otherwise it seems to cache the fill.
846
- const _repaintMinimap = () => {
847
- const GRADIENT_ID = `gradient-${Date.now()}`
848
- gradient.id = GRADIENT_ID
849
- rect.setAttribute('fill', `url(#${GRADIENT_ID})`)
850
- }
851
-
852
- const redrawOverlays = () => {
853
- const HEIGHT = 4
854
- const GAP = 4
855
- const Overlays = OverlayUI.getOverlays()
856
- const duration = sourceVideo.duration
857
-
858
- // Always start with a blank slate.
859
- clipPath.innerHTML = ''
860
-
861
- let i = 1
862
- Overlays.forEach((Overlay) => {
863
- const startPercent = (Overlay.getStartSeconds() / duration) * 100
864
- const stopPercent = (Overlay.getStopSeconds() / duration) * 100
865
- const clipRect = document.createElementNS(svgns, 'rect')
866
- const offset = i === 1 ? HEIGHT : HEIGHT * i + GAP * (i - 1)
867
-
868
- clipRect.setAttribute('x', `${startPercent}%`)
869
- clipRect.setAttribute('width', `${stopPercent - startPercent}%`)
870
- clipRect.setAttribute('y', `calc(100% - ${offset})`)
871
- clipRect.setAttribute('height', `${HEIGHT}`)
872
- clipPath.appendChild(clipRect)
873
- i++
874
- })
875
- }
876
-
877
- const redrawClips = (Clips) => {
878
- const stops = new Set()
879
- const duration = sourceVideo.duration
880
-
881
- // Always start with a blank slate.
882
- gradient.innerHTML = ''
883
-
884
- // (Exits early) Uses the default appearance when there aren't any Clips.
885
- if (!Clips.size) {
886
- gradient.append(_createStop('0', 'white'), _createStop('100', 'white'))
887
- return
888
- }
889
-
890
- Clips.forEach((Clip) => {
891
- const { startTimeSMPTE, endTimeSMPTE } = Clip
892
-
893
- const startMillis =
894
- startTimeSMPTE === '' ? 0 : this.SMPTEToSeconds(startTimeSMPTE) * 1000
895
- const stopMillis =
896
- endTimeSMPTE === '' ? 0 : this.SMPTEToSeconds(endTimeSMPTE) * 1000
897
-
898
- const startPercent = (startMillis / 1000 / duration) * 100
899
- const stopPercent = (stopMillis / 1000 / duration) * 100
900
-
901
- stops.add(startPercent)
902
- stops.add(stopPercent)
903
- })
904
-
905
- // Sort the stops least to greatest.
906
- const s = [...stops].sort((a, b) => a - b)
907
-
908
- s.forEach((stop, index) => {
909
- // This is the "startTime" stop:
910
- if (index % 2 === 0 || index === 0) {
911
- gradient.append(_createStop(stop, 'white'))
912
- gradient.append(_createStop(stop, 'rgb(113, 133, 162)'))
913
- } else {
914
- // This is the "stopTime" stop:
915
- gradient.append(_createStop(stop, 'rgb(113, 133, 162)'))
916
- gradient.append(_createStop(stop, 'white'))
917
- }
918
- })
919
- }
920
-
921
- // Paints the entire Clip background white initially.
922
- gradient.append(_createStop('0', 'white'), _createStop('100', 'white'))
923
-
924
- const onClipModelUpdate = (data) => {
925
- data.forEach((ClipData) => {
926
- const { key, placeholder } = ClipData
927
- const ClipSurface = this.rootEl.querySelector(
928
- `.VideoEditor-filmstrip .VideoEditorClip-surface[data-placeholder="${placeholder}"]`,
929
- )
930
-
931
- if (key && ClipSurface) {
932
- // Syncronize the DOM view with the model.
933
- ClipSurface.removeAttribute('data-placeholder')
934
- ClipSurface.setAttribute('data-ref-key', key)
935
- }
936
- })
937
-
938
- // Updates the minimap clip view
939
- redrawClips(data)
940
- _repaintMinimap()
941
- }
942
-
943
- const onClipModelRemoveClip = (key) => {
944
- const ClipSurface = this.rootEl.querySelector(
945
- `.VideoEditor-filmstrip .VideoEditorClip-surface[data-ref-key="${key}"]`,
946
- )
947
-
948
- if (ClipSurface) {
949
- ClipSurface.remove()
950
- redrawClips(this.ClipModel.getClips())
951
- }
952
-
953
- _repaintMinimap()
954
- }
955
-
956
- const reset = () => {
957
- redrawOverlays()
958
- redrawClips(this.ClipModel.getClips())
959
-
960
- const path = this.rootEl.querySelector(
961
- '.VideoOverlays-clippingSvg clipPath',
962
- )
963
- if (path) {
964
- path.innerHTML = ''
965
- } else {
966
- console.warn(
967
- `querySelector('.VideoOverlays-clippingSvg clipPath') returned null. Overlay path may not have reset properly.`,
968
- )
969
- }
970
-
971
- _repaintMinimap()
972
- }
973
-
974
- const setVideo = (video) => {
975
- sourceVideo = video
976
- }
977
-
978
- // Display any initial clip model data prior to notification of user changes.
979
- this.ClipModel.getClips().forEach((ClipModel) => {
980
- const x1 = this.fromSMPTEToXCoordinate(
981
- ClipModel.startTimeSMPTE,
982
- sourceVideo.duration,
983
- filmstripEl,
984
- )
985
- const x2 = this.fromSMPTEToXCoordinate(
986
- ClipModel.endTimeSMPTE,
987
- sourceVideo.duration,
988
- filmstripEl,
989
- )
990
-
991
- const width = x2 - x1
992
- const { key } = ClipModel
993
-
994
- const clipsurface = _createClippingSurface(
995
- filmstripEl,
996
- x1 + offset,
997
- width,
998
- )
999
- clipsurface.removeAttribute('data-placeholder')
1000
- clipsurface.setAttribute('data-ref-key', key)
1001
- _makeResizeable(clipsurface)
1002
- })
1003
-
1004
- // Display the minimap's clip ui according to the initial data.
1005
- redrawClips(this.ClipModel.getClips())
1006
-
1007
- this.ClipModel.observe(onClipModelUpdate)
1008
- this.ClipModel.observeOnRemove(onClipModelRemoveClip)
1009
-
1010
- doFrameLoop()
1011
-
1012
- this._VideoPlayer.on('playing', (event) => {
1013
- window.cancelAnimationFrame(frameLoop)
1014
- })
1015
-
1016
- return {
1017
- element: minimapEl,
1018
- redrawClips,
1019
- redrawOverlays,
1020
- reset,
1021
- setVideo,
1022
- }
1023
- }
1024
-
1025
- // Renders a filmstrip placeholder graphic while the filmstrip images are created.
1026
- createFilmstripPlaceholder(sourceVideo) {
1027
- const filmstripImagesContainer = this.rootEl.querySelector(
1028
- '.VideoEditorFilmstrip-images',
1029
- )
1030
- const filmstripPlaceholders = []
1031
- const step = sourceVideo.duration / COUNT_FILMSTRIP_CELL
1032
- for (let i = 1; i <= COUNT_FILMSTRIP_CELL; i++) {
1033
- const placeholder = create(
1034
- 'div',
1035
- { className: 'VideoEditorFilmstrip-placeholder' },
1036
- create(
1037
- 'span',
1038
- { className: 'VideoEditorFilmstrip-placeholderLabel' },
1039
- `${this.secondsToSMPTE(i * step)}`,
1040
- ),
1041
- )
1042
- filmstripPlaceholders.push(placeholder)
1043
- }
1044
-
1045
- filmstripPlaceholders.forEach((placeholder) =>
1046
- filmstripImagesContainer.appendChild(placeholder),
1047
- )
1048
- }
1049
-
1050
- async createFilmstrip(video, width, height, duration) {
1051
- const count = 10 // number of filmstrip images to create.
1052
- const scale = 0.2 // resize amount to create images at a resolution that is close to the target size.
1053
- const step = duration / count // size of the jumps made while seeking through the video.
1054
- const target = this.rootEl.querySelector('.VideoEditorFilmstrip-images')
1055
- const filmstripEl = this.rootEl.querySelector('.VideoEditor-filmstrip')
1056
- const captureVideo = video.cloneNode(true)
1057
- captureVideo.crossOrigin = CROSS_ORIGIN_KEYWORD
1058
-
1059
- // Wait for the capture video data.
1060
- await new Promise((resolve, reject) => {
1061
- captureVideo.readyState >= 2
1062
- ? resolve()
1063
- : captureVideo.addEventListener('loadedmetadata', (event) => {
1064
- resolve()
1065
- })
1066
- })
1067
-
1068
- captureVideo.className = 'VideoEditor-captureVideo' // overwrites any copied classnames.
1069
- captureVideo.removeAttribute('controls')
1070
- captureVideo.setAttribute('muted', true)
1071
- captureVideo.setAttribute('playsinline', true)
1072
- video.insertAdjacentElement('afterend', captureVideo)
1073
-
1074
- const svgns = 'http://www.w3.org/2000/svg'
1075
- const clipSvg = document.createElementNS(svgns, 'svg')
1076
- const clipDefs = document.createElementNS(svgns, 'defs')
1077
- const clipPath = document.createElementNS(svgns, 'clipPath')
1078
- clipSvg.setAttribute('version', '1.1')
1079
- clipSvg.setAttribute('xmlns', svgns)
1080
- clipSvg.setAttribute('class', 'VideoEditorFilmstrip-clippingSvg')
1081
-
1082
- clipPath.setAttribute('id', 'VideoEditorFilmstrip-svgPath')
1083
- clipDefs.appendChild(clipPath)
1084
- clipSvg.appendChild(clipDefs)
1085
-
1086
- const clipRect = document.createElementNS(svgns, 'rect')
1087
-
1088
- clipRect.setAttribute('x', `0`)
1089
- clipRect.setAttribute('width', `50%`)
1090
- clipRect.setAttribute('height', `100%`)
1091
- clipPath.appendChild(clipRect)
1092
-
1093
- target.parentElement.appendChild(clipSvg)
1094
-
1095
- const seek = (time) => {
1096
- return new Promise((resolve, reject) => {
1097
- const seekedCallback = (event) => {
1098
- captureVideo.removeEventListener('seeked', seekedCallback)
1099
- resolve()
1100
- }
1101
-
1102
- captureVideo.addEventListener('seeked', seekedCallback)
1103
- captureVideo.currentTime = time
1104
- })
1105
- }
1106
-
1107
- let index = 0
1108
- let captureAbort = false
1109
- const capture = (time) => {
1110
- // Reduce the initialization time by aborting the whole capture process once the first error is thrown.
1111
- if (captureAbort) return
1112
- return seek(time)
1113
- .then(() => {
1114
- const canvas = create('canvas', {
1115
- width: width * scale,
1116
- height: height * scale,
1117
- })
1118
- canvas
1119
- .getContext('2d')
1120
- .drawImage(captureVideo, 0, 0, canvas.width, canvas.height)
1121
- const img = create('img')
1122
- img.crossOrigin = CROSS_ORIGIN_KEYWORD
1123
- img.src = canvas.toDataURL()
1124
- const placeholder = target.children[index]
1125
- placeholder.replaceWith(img)
1126
- ++index
1127
- })
1128
- .catch((err) => {
1129
- captureAbort = true
1130
- console.warn(`Video screen shot failed due to:\n${err}`)
1131
- })
1132
- }
1133
-
1134
- // Creates a clone of each filmstrip reference node giving the appearance of highlighted sections of the filmstrip.
1135
- const _createFilmstripClips = () => {
1136
- filmstripEl.classList.add('is-clipped')
1137
-
1138
- let referenceNodes = target.querySelectorAll('img')
1139
- if (!referenceNodes.length) {
1140
- referenceNodes = target.querySelectorAll(
1141
- 'img, .VideoEditorFilmstrip-placeholder',
1142
- )
1143
- }
1144
-
1145
- referenceNodes.forEach((referenceNode) => {
1146
- this.rootEl
1147
- .querySelector('.VideoEditorFilmstrip-clips')
1148
- .append(referenceNode.cloneNode(true))
1149
- })
1150
- }
1151
-
1152
- // Draw "cutouts" in the mask to reveal the clipped visualization of the filmstrip.
1153
- const _redrawClippedFrames = (Clips) => {
1154
- const duration = video.duration
1155
-
1156
- // Always start with a blank slate.
1157
- clipPath.innerHTML = ''
1158
-
1159
- Clips.forEach((Clip) => {
1160
- const clipRect = document.createElementNS(svgns, 'rect')
1161
- const x1 = this.fromSMPTEToXCoordinate(
1162
- Clip.startTimeSMPTE,
1163
- duration,
1164
- filmstripEl,
1165
- )
1166
- const x2 = this.fromSMPTEToXCoordinate(
1167
- Clip.endTimeSMPTE,
1168
- duration,
1169
- filmstripEl,
1170
- )
1171
- const width = x2 - x1
1172
-
1173
- // Converts pixels to percentage of whole.
1174
- const { width: containerWidth } = filmstripEl.getBoundingClientRect()
1175
- const xPercent = (x1 / containerWidth) * 100
1176
- const widthPercent = (width / containerWidth) * 100
1177
-
1178
- clipRect.setAttribute('x', `${xPercent}%`)
1179
- clipRect.setAttribute('width', `${widthPercent}%`)
1180
- clipRect.setAttribute('height', `100%`)
1181
- clipRect.setAttribute('data-ref-key', Clip.key || Clip.placeholder)
1182
- clipPath.appendChild(clipRect)
1183
- })
1184
- }
1185
-
1186
- const onClipModelUpdate = (data) => {
1187
- // Once the first clip is added construct the filmstrip clip UI.
1188
- if (
1189
- this.rootEl.querySelector('.VideoEditorFilmstrip-clips')
1190
- .childElementCount === 0
1191
- ) {
1192
- _createFilmstripClips()
1193
- }
1194
-
1195
- _redrawClippedFrames(data)
1196
- }
1197
-
1198
- const onClipModelRemoveClip = (data) => {
1199
- const match = clipPath.querySelector(`rect[data-ref-key="${data}"]`)
1200
- if (match) {
1201
- match.remove()
1202
- }
1203
-
1204
- // When all Clips have been removed from the Model, return the filmstrip to the intial UI.
1205
- if (!this.ClipModel.getClips().size) {
1206
- filmstripEl.classList.remove('is-clipped')
1207
- this.rootEl.querySelector('.VideoEditorFilmstrip-clips').innerHTML = ''
1208
- }
1209
- }
1210
-
1211
- // Take pictures of the frames.
1212
- for (let i = 1; i <= count; i++) {
1213
- await capture(i * step)
1214
- }
1215
-
1216
- // Remove the temp video now that we've taken the pictures.
1217
- captureVideo.remove()
1218
-
1219
- // If Clips are initially provided, clone the images into the filmstrip
1220
- // clip area so they will appear as "clipped".
1221
- if (this.ClipModel.getClips().size) {
1222
- _createFilmstripClips()
1223
- }
1224
-
1225
- // Display the clipped frames according to the initial data.
1226
- _redrawClippedFrames(this.ClipModel.getClips())
1227
-
1228
- this.ClipModel.observe(onClipModelUpdate)
1229
- this.ClipModel.observeOnRemove(onClipModelRemoveClip)
1230
- }
1231
-
1232
- resetFilmstrip() {
1233
- const clipSvg = this.rootEl.querySelector(
1234
- '.VideoEditorFilmstrip-clippingSvg',
1235
- )
1236
- if (clipSvg) {
1237
- clipSvg.remove()
1238
- } else {
1239
- console.warn(
1240
- `querySelector('.VideoEditorFilmstrip-clippingSvg') returned null. Filmstrip clip-path may not have reset properly.`,
1241
- )
1242
- }
1243
-
1244
- const imageContainer = this.rootEl.querySelector(
1245
- '.VideoEditorFilmstrip-images',
1246
- )
1247
- imageContainer.innerHTML = ''
1248
- }
1249
-
1250
- _applyInitialValues(UIs) {
1251
- const getEditValues = (inputs) => {
1252
- return [...inputs].reduce((accum, val) => {
1253
- const propname = val.name.substring(val.name.lastIndexOf('/') + 1)
1254
- Object.assign(accum, {
1255
- [propname]: val.type === 'checkbox' ? val.checked : val.value,
1256
- })
1257
- return accum
1258
- }, {})
1259
- }
1260
-
1261
- UIs.forEach((ui) => {
1262
- const initialValues = getEditValues(
1263
- ui.element.querySelectorAll('input[name]'),
1264
- )
1265
- ui.handleUpdate(initialValues)
1266
- })
1267
- }
1268
-
1269
- _createClippingUI(sourceEl) {
1270
- const Clips = new Map()
1271
-
1272
- if (sourceEl.classList.contains('is-inHiddenCluster')) {
1273
- sourceEl.classList.remove('is-inHiddenCluster')
1274
- }
1275
-
1276
- const element = create(
1277
- 'div',
1278
- {
1279
- class: 'VideoEditor-ClippingUI',
1280
- 'data-tab': 'Clip',
1281
- },
1282
- create('h2', 'Click on the filmstrip area to clip your video.'),
1283
- create(
1284
- 'em',
1285
- {},
1286
- 'Tip: Creating a clip can be cancelled by pressing the escape key.',
1287
- ),
1288
- this.originEl.querySelector('[data-name$="/frameRate"]'),
1289
- sourceEl,
1290
- )
1291
-
1292
- // Returns a Promise that resolves the key value.
1293
- const _getKey = async (item) => {
1294
- return new Promise((resolve, reject) => {
1295
- onFind(item, '.CIG', (cig) => {
1296
- const key = cig.getAttribute('data-id').split('/')[0]
1297
- resolve(key)
1298
- })
1299
- })
1300
- }
1301
-
1302
- // Preload the Clip RCIG item contents (step #1).
1303
- onFind(sourceEl, '.RCIG-list > li:not(.is-removing)', (item) => {
1304
- // collapsed items lazy-load their contents so they need to be triggered to load the content.
1305
- if (item.classList.contains('is-collapsed')) {
1306
- const evt = new MouseEvent('click', {
1307
- bubbles: true,
1308
- cancelable: true,
1309
- view: window,
1310
- })
1311
-
1312
- const title = item.querySelector('.RCIG-title')
1313
- // click on the title to trigger the lazy-load.
1314
- title.dispatchEvent(evt)
1315
- // click again to collapse the expanded item so no one notices what was done :)
1316
- title.dispatchEvent(evt)
1317
- }
1318
- })
1319
-
1320
- // Sync removal of an RCIG item to remove the clips as well.
1321
- onFind(sourceEl, '.RCIG-item.is-removing', async (item) => {
1322
- const key = await _getKey(item)
1323
- Clips.delete(key)
1324
- })
1325
-
1326
- // The RCIG item content is loaded. (step #2)
1327
- // const clipTarget = this.rootEl.querySelector('.VideoEditor-filmstrip')
1328
- onFind(sourceEl, '.RCIG-list > li:not(.is-removing) .CIG', (clipItem) => {
1329
- const key = clipItem.getAttribute('data-id').split('/')[0]
1330
- const startTimeSMPTE = clipItem.querySelector(
1331
- '[data-name$="startTimecode"] textarea',
1332
- ).value
1333
- const endTimeSMPTE = clipItem.querySelector(
1334
- '[data-name$="endTimecode"] textarea',
1335
- ).value
1336
-
1337
- // Default to a unique placeholder unless there is already an object in the Clip model that is a "placeholder".
1338
- // Which means it was the last object the User generated and should be used as the current placeholder in this case.
1339
- let placeholder = `placeholder-${Date.now()}`
1340
- this.ClipModel.getClips().forEach((val, key) => {
1341
- if (key.includes('placeholder')) {
1342
- placeholder = key
1343
- }
1344
- })
1345
-
1346
- this.ClipModel.addClip({
1347
- placeholder,
1348
- key,
1349
- startTimeSMPTE,
1350
- endTimeSMPTE,
1351
- })
1352
- })
1353
-
1354
- onFind(sourceEl, '.RCIG-list > li .RCIG-remove', (removeButton) => {
1355
- removeButton.onclick = (evt) => {
1356
- const CIG = evt.currentTarget.parentNode.querySelector('.CIG')
1357
- const key = CIG.getAttribute('data-id').split('/')[0]
1358
- this.ClipModel.removeClip(key)
1359
- }
1360
- })
1361
-
1362
- const handleUpdate = (data) => {}
1363
-
1364
- const createClip = () => {
1365
- const button = element.querySelector('.RCIG-add')
1366
- button.click()
1367
- }
1368
-
1369
- const onClipModelUpdate = (data) => {
1370
- data.forEach((ClipData) => {
1371
- const { key, placeholder, startTimeSMPTE, endTimeSMPTE } = ClipData
1372
- // This is the scenario where a clip was created outside this component (for example via the "minimap UI").
1373
- if (!key && placeholder) {
1374
- createClip()
1375
- } else {
1376
- // Update the DOM inputs to reflect the changes made to the data model.
1377
- const list = element.querySelector('[data-name$="/clippings"]')
1378
- const inputGroup = list.querySelector(`.CIG[data-id="${key}"]`)
1379
-
1380
- const inputStartTime = inputGroup.querySelector(
1381
- '[data-name$="startTimecode"] textarea',
1382
- )
1383
- const inputStopTime = inputGroup.querySelector(
1384
- '[data-name$="endTimecode"] textarea',
1385
- )
1386
-
1387
- inputStartTime.value = startTimeSMPTE
1388
- inputStopTime.value = endTimeSMPTE
1389
-
1390
- // Trigger a content state change.
1391
- inputStartTime.dispatchEvent(new Event('change', { bubbles: true }))
1392
- inputStopTime.dispatchEvent(new Event('change', { bubbles: true }))
1393
- }
1394
- })
1395
- }
1396
-
1397
- const onClipModelRemoveClip = (key) => {
1398
- const item = sourceEl.querySelector(`.RCIG-list .CIG[data-id="${key}"]`)
1399
- const listItem = item.closest('li')
1400
- if (item && listItem && !listItem.classList.contains('is-removing')) {
1401
- // Trigger the CIG's built-in removal logic.
1402
- const removeButton = item.parentNode.querySelector('.RCIG-remove')
1403
- if (removeButton) {
1404
- removeButton.click()
1405
- }
1406
- }
1407
- }
1408
-
1409
- const reset = () => {
1410
- // Removes any clip CIGs that have been added to this video.
1411
- const clipRemoveButtons = sourceEl.querySelectorAll(
1412
- '.RCIG-list > li:not(.is-removing) .RCIG-remove',
1413
- )
1414
-
1415
- clipRemoveButtons.forEach((button) => {
1416
- button.click()
1417
- })
1418
- }
1419
-
1420
- this.ClipModel.observe(onClipModelUpdate)
1421
- this.ClipModel.observeOnRemove(onClipModelRemoveClip)
1422
-
1423
- return {
1424
- createClip,
1425
- element,
1426
- handleUpdate,
1427
- reset,
1428
- }
1429
- }
1430
-
1431
- _createColorUI() {
1432
- const defaults = {
1433
- hue: 0,
1434
- saturation: 50,
1435
- brightness: 50,
1436
- contrast: 50,
1437
- }
1438
-
1439
- const filtersApplied = new Map()
1440
- const FILTERABLE_SELECTORS = '.VideoEditor-stage-main video'
1441
-
1442
- const applyAllEdits = (data) => {
1443
- const { hue, saturation, brightness, contrast } = { ...data }
1444
-
1445
- if (hue !== '') {
1446
- hueSlider.noUiSlider.set(hue)
1447
- }
1448
- if (saturation !== '') {
1449
- saturationSlider.noUiSlider.set(saturation)
1450
- }
1451
- if (brightness !== '') {
1452
- brightnessSlider.noUiSlider.set(brightness)
1453
- }
1454
- if (contrast !== '') {
1455
- contrastSlider.noUiSlider.set(contrast)
1456
- }
1457
- }
1458
-
1459
- // Server values range from 1 to 100 and for CSS should be converted to 0 - 200 (max).
1460
- const convertValueToPercent = (val, max = 100) => {
1461
- let amount = ((2 * parseFloat(val)) / 100) * max
1462
- return amount
1463
- }
1464
-
1465
- // Applies a CSS filter to the "filterable" elements.
1466
- // Combines filters if multiple are used in the UI.
1467
- // @name: the CSS filter function to use.
1468
- // @val: the CSS filter function percentage to use. *Note: passing null will remove the filter.
1469
- const applyFilter = (name, val) => {
1470
- let cssFilterProp = ''
1471
- const elements = [...this.rootEl.querySelectorAll(FILTERABLE_SELECTORS)]
1472
-
1473
- val ? filtersApplied.set(name, val) : filtersApplied.delete(name)
1474
-
1475
- filtersApplied.forEach((val, name) => {
1476
- cssFilterProp += ` ${name}(${val}) `
1477
- })
1478
-
1479
- elements.forEach(
1480
- (element) => (element.style.filter = `${cssFilterProp.trim()}`),
1481
- )
1482
- }
1483
-
1484
- const sourceEl = this.originEl.querySelector('.VideoEditor-InputColors')
1485
-
1486
- const hueInput = sourceEl.querySelector('input[name$="hue"]')
1487
- const hueContainer = hueInput.closest('.inputContainer')
1488
- hueInput.type = 'range'
1489
- hueInput.min = '-180'
1490
- hueInput.max = '180'
1491
- hueInput.value = hueContainer.hasAttribute('data-interacted')
1492
- ? hueInput.value
1493
- : defaults.hue
1494
-
1495
- const hueSlider = create('div', { className: 'is-slideable' })
1496
- hueInput.insertAdjacentElement('beforebegin', hueSlider)
1497
-
1498
- this.createSlider(hueSlider, hueInput, (value) => {
1499
- applyFilter('hue-rotate', `${value}deg`)
1500
- })
1501
-
1502
- const saturationInput = sourceEl.querySelector('input[name$="saturation"]')
1503
- saturationInput.type = 'range'
1504
- saturationInput.min = '0'
1505
- saturationInput.max = '100'
1506
-
1507
- const saturationSlider = create('div', { className: 'is-slideable' })
1508
- saturationInput.insertAdjacentElement('beforebegin', saturationSlider)
1509
-
1510
- this.createSlider(saturationSlider, saturationInput, (value) => {
1511
- applyFilter('saturate', `${convertValueToPercent(value)}%`)
1512
- })
1513
-
1514
- const brightnessInput = sourceEl.querySelector('input[name$="brightness"]')
1515
- brightnessInput.type = 'range'
1516
- brightnessInput.min = '0'
1517
- brightnessInput.max = '100'
1518
-
1519
- const brightnessSlider = create('div', { className: 'is-slideable' })
1520
- brightnessInput.insertAdjacentElement('beforebegin', brightnessSlider)
1521
-
1522
- this.createSlider(brightnessSlider, brightnessInput, (value) => {
1523
- applyFilter('brightness', `${convertValueToPercent(value)}%`)
1524
- })
1525
-
1526
- const contrastInput = sourceEl.querySelector('input[name$="contrast"]')
1527
- contrastInput.type = 'range'
1528
- contrastInput.min = '0'
1529
- contrastInput.max = '100'
1530
-
1531
- const contrastSlider = create('div', { className: 'is-slideable' })
1532
- contrastInput.insertAdjacentElement('beforebegin', contrastSlider)
1533
-
1534
- this.createSlider(contrastSlider, contrastInput, (value) => {
1535
- applyFilter('contrast', `${convertValueToPercent(value)}%`)
1536
- })
1537
-
1538
- const handleUpdate = (data) => {
1539
- const { saturation, brightness, contrast, hue } = { ...data }
1540
-
1541
- applyFilter(
1542
- 'saturate',
1543
- saturation ? `${convertValueToPercent(saturation)}%` : null,
1544
- )
1545
- applyFilter(
1546
- 'brightness',
1547
- brightness ? `${convertValueToPercent(brightness)}%` : null,
1548
- )
1549
- applyFilter(
1550
- 'contrast',
1551
- contrast ? `${convertValueToPercent(contrast)}%` : null,
1552
- )
1553
- applyFilter('hue-rotate', hue ? `${hue}deg` : null)
1554
- }
1555
- const element = create(
1556
- 'div',
1557
- {
1558
- class: 'VideoEditor-ColorUI',
1559
- 'data-tab': 'Color',
1560
- },
1561
- create('h2', 'Adjust Color'),
1562
- create(
1563
- 'em',
1564
- 'Tip: Increment/decrement with up and down keys after clicking the slider.',
1565
- ),
1566
- sourceEl.querySelectorAll('.CIG-row'),
1567
- )
1568
-
1569
- const reset = () => {
1570
- applyAllEdits(defaults)
1571
- }
1572
-
1573
- const resetButton = create(
1574
- 'a',
1575
- {
1576
- class: 'VideoEditor-resetButton',
1577
- onclick: (event) => {
1578
- event.preventDefault()
1579
- event.stopPropagation()
1580
- applyAllEdits(defaults)
1581
- },
1582
- },
1583
- 'Reset',
1584
- )
1585
-
1586
- element.append(resetButton)
1587
-
1588
- return {
1589
- element,
1590
- handleUpdate,
1591
- reset,
1592
- }
1593
- }
1594
-
1595
- _createOverlayUI(videoPlayer, nativeVideoWidth, nativeVideoHeight) {
1596
- const Overlays = new Map()
1597
- const sourceEl = this.originEl.querySelector(
1598
- '[data-field-name="imageOverlayOptions"]',
1599
- )
1600
-
1601
- sourceEl.removeAttribute('hidden')
1602
-
1603
- // Handles removing the Overlay layer from the video player when overlay CIGs are removed.
1604
- onFind(sourceEl, '.RCIG-list > li .RCIG-remove', (removeButton) => {
1605
- removeButton.onclick = (evt) => {
1606
- const CIG = evt.currentTarget.parentNode.querySelector(
1607
- '.VideoEditor-InputOverlay',
1608
- )
1609
- const key = CIG.getAttribute('data-id').split('/')[0]
1610
- const overlayLayer = this.rootEl.querySelector(
1611
- `.VideoOverlay-layer[data-name-ref="${key}"]`,
1612
- )
1613
- if (overlayLayer) {
1614
- overlayLayer.remove()
1615
- } else {
1616
- console.warn(
1617
- `querySelector('.VideoOverlay-layer[data-name-ref="${key}"]') returned null. Overlay layer may not have been removed.`,
1618
- )
1619
- }
1620
-
1621
- // Update the Minimap view.
1622
- this._MiniMap.then((minimap) => {
1623
- minimap.redrawOverlays()
1624
- })
1625
- }
1626
- })
1627
-
1628
- const element = create(
1629
- 'div',
1630
- {
1631
- class: 'VideoEditor-OverlayUI',
1632
- 'data-tab': 'Overlay',
1633
- },
1634
- create('h2', {}, 'Add Image Overlays'),
1635
- create('em', {}, 'Up to 8 images can be added and overlapped.'),
1636
- sourceEl,
1637
- )
1638
-
1639
- // This is a candidate for optimization as it is called all the time while the video is seeked or is playing.
1640
- videoPlayer.on('timeupdate', (event) => {
1641
- const secondsCurr = +event.detail.plyr.currentTime
1642
- Overlays.forEach((overlay) => {
1643
- const secondsStart = overlay.getStartSeconds()
1644
- const secondsEnd = overlay.getStopSeconds()
1645
- if (secondsStart <= secondsCurr && secondsEnd >= secondsCurr) {
1646
- overlay.show()
1647
- } else {
1648
- overlay.hide()
1649
- }
1650
- })
1651
- })
1652
-
1653
- // Returns the overlay image that matches the provided key.
1654
- const _getOverlayImage = (key) => {
1655
- const target = this.rootEl.querySelector(
1656
- `.VideoEditor-stage-main .plyr__video-wrapper [data-name-ref="${key}"]`,
1657
- )
1658
- return target
1659
- }
1660
-
1661
- const handleUpdate = (data) => {}
1662
-
1663
- const _changeOverlayOrder = (key, newIndex) => {
1664
- const target = this.rootEl.querySelector(
1665
- '.VideoEditor-stage-main .plyr__video-wrapper',
1666
- )
1667
- const layer = target.querySelector(`[data-name-ref="${key}"]`)
1668
- layer.style.setProperty('--index', newIndex)
1669
- }
1670
-
1671
- const _initOverlay = (item) => {
1672
- const MARGIN = 10
1673
-
1674
- /* eslint-disable-next-line no-async-promise-executor */
1675
- return new Promise(async (resolve, reject) => {
1676
- const imageData = await _getImage(item)
1677
- const id = imageData.id
1678
- // Native width / height are necessary so that the browser knows how to size the bitmaps drawn into the canvas.
1679
- // Using a canvas allows the browser to match the letterbox and windowbox scenarios which can occur with responsive video sizes.
1680
- // This is important so that the overlay(s) are positioned as they would be in the video frame and not in relationship to its edges.
1681
- const layer = create('canvas', {
1682
- class: 'VideoOverlay-layer is-hidden',
1683
- width: nativeVideoWidth,
1684
- height: nativeVideoHeight,
1685
- })
1686
-
1687
- const ctx = layer.getContext('2d')
1688
- ctx.drawImage(imageData.element, MARGIN, MARGIN)
1689
-
1690
- const nativeImageWidth = imageData.element.naturalWidth
1691
- const nativeImageHeight = imageData.element.naturalHeight
1692
-
1693
- layer.setAttribute('data-name-ref', id)
1694
- const target = this.rootEl.querySelector(
1695
- '.VideoEditor-stage-main .plyr__video-wrapper',
1696
- )
1697
-
1698
- const existingLayer = target.querySelector(`[data-name-ref="${id}"]`)
1699
- existingLayer
1700
- ? target.replaceChild(layer, existingLayer)
1701
- : target.appendChild(layer)
1702
-
1703
- const remove = () => {
1704
- layer.classList.add('is-removing')
1705
- }
1706
-
1707
- // @percent: 0 - 1
1708
- const updateOpacity = (percent) => {
1709
- recipientEl.style.opacity = percent
1710
- }
1711
-
1712
- const _hideUnusedInputs = () => {
1713
- item
1714
- .querySelectorAll(
1715
- '.CIG-row[data-name$="/xPercent"], .CIG-row[data-name$="/yPercent"], .CIG-row[data-name$="/widthPercent"], .CIG-row[data-name$="/heightPercent"], .CIG-row[data-name$="/startTime"], .CIG-row[data-name$="/duration"], .CIG-row[data-name$="/fadeIn"], .CIG-row[data-name$="/fadeOut"]',
1716
- )
1717
- .forEach((input) => {
1718
- input.classList.add('is-hidden')
1719
- })
1720
- }
1721
-
1722
- const inputOpacity = item.querySelector('input[name$="/opacity"]')
1723
- inputOpacity.type = 'range'
1724
- inputOpacity.min = 0
1725
- inputOpacity.max = 100
1726
-
1727
- const opacitySlider = create('div', { className: 'is-slideable' })
1728
- inputOpacity.insertAdjacentElement('beforebegin', opacitySlider)
1729
-
1730
- const key = inputOpacity.getAttribute('name').split('/')[0]
1731
- const recipientEl = _getOverlayImage(key)
1732
-
1733
- this.createSlider(opacitySlider, inputOpacity, (value) => {
1734
- updateOpacity(value / 100)
1735
- })
1736
-
1737
- const inputWidth = item.querySelector('input[name$="/widthPercent"]')
1738
- const inputHeight = item.querySelector('input[name$="/heightPercent"]')
1739
- // @percent: 1 - 100%
1740
- const updateScale = (percent) => {
1741
- // recipientEl.querySelector('img').style.width = `${percent}%`
1742
- // TODO: Resizing the canvas data.
1743
- inputWidth.value = parseInt(percent)
1744
- inputHeight.value = parseInt(percent)
1745
- inputWidth.dispatchEvent(new Event('change', { bubbles: true }))
1746
- inputHeight.dispatchEvent(new Event('change', { bubbles: true }))
1747
- }
1748
-
1749
- const inputScale = create('input')
1750
- inputScale.type = 'range'
1751
- inputScale.min = 1
1752
- inputScale.max = 100
1753
- inputScale.step = '1'
1754
-
1755
- const scaleSlider = create('div', { className: 'is-slideable' })
1756
- inputScale.insertAdjacentElement('beforebegin', scaleSlider)
1757
-
1758
- this.createSlider(
1759
- scaleSlider,
1760
- inputScale,
1761
- (value) => {
1762
- updateScale(value)
1763
- },
1764
- +inputWidth.value,
1765
- )
1766
-
1767
- // Positioning UI:
1768
- const inputX = item.querySelector('input[name$="/xPercent"]')
1769
- const inputY = item.querySelector('input[name$="/yPercent"]')
1770
-
1771
- const getPositionAsPercent = (x, y) => {
1772
- // Convert back to percentages using the top-left of the image as the 0,0 coordinate.
1773
- let left = x || 0
1774
- let top = y || 0
1775
-
1776
- // Convert negative values to "0"
1777
- left = Math.sign(left) === -1 ? 0 : left
1778
- top = Math.sign(top) === -1 ? 0 : top
1779
-
1780
- return {
1781
- xPercent: parseInt((left / nativeVideoWidth) * 100),
1782
- yPercent: parseInt((top / nativeVideoHeight) * 100),
1783
- }
1784
- }
1785
-
1786
- // Moving left-to-right, top-to-bottom.
1787
- const LAYOUTS_NORMAL = new Map([
1788
- ['0,0', '0,0'],
1789
- ['50,0', `${nativeVideoWidth / 2 - nativeImageWidth / 2},0`],
1790
- ['100,0', `${nativeVideoWidth - nativeImageWidth},0`],
1791
- ['0,50', `0,${nativeVideoHeight / 2 - nativeImageHeight / 2}`],
1792
- [
1793
- '50,50',
1794
- `${nativeVideoWidth / 2 - nativeImageWidth / 2},${
1795
- nativeVideoHeight / 2 - nativeImageHeight / 2
1796
- }`,
1797
- ],
1798
- [
1799
- '100,50',
1800
- `${nativeVideoWidth - nativeImageWidth},${
1801
- nativeVideoHeight / 2 - nativeImageHeight / 2
1802
- }`,
1803
- ],
1804
- ['0,100', `0,${nativeVideoHeight - nativeImageHeight}`],
1805
- [
1806
- '50,100',
1807
- `${nativeVideoWidth / 2 - nativeImageWidth / 2},${
1808
- nativeVideoHeight - nativeImageHeight
1809
- }`,
1810
- ],
1811
- [
1812
- '100,100',
1813
- `${nativeVideoWidth - nativeImageWidth},${
1814
- nativeVideoHeight - nativeImageHeight
1815
- }`,
1816
- ],
1817
- ])
1818
-
1819
- const layoutParent = create('div', {
1820
- class: 'VideoOverlay-layoutUI',
1821
- })
1822
-
1823
- const updatePosition = (x, y) => {
1824
- const intX = parseInt(x)
1825
- const intY = parseInt(y)
1826
- ctx.clearRect(0, 0, layer.width, layer.height)
1827
- ctx.drawImage(imageData.element, intX, intY)
1828
- }
1829
-
1830
- const updatePositionInputs = (x, y) => {
1831
- const { xPercent, yPercent } = getPositionAsPercent(x, y)
1832
-
1833
- inputX.value = xPercent
1834
- inputY.value = yPercent
1835
-
1836
- // Trigger a content state change.
1837
- inputX.dispatchEvent(new Event('change', { bubbles: true }))
1838
- inputY.dispatchEvent(new Event('change', { bubbles: true }))
1839
- }
1840
-
1841
- const selectLayout = (key) => {
1842
- const layoutEl = layoutParent.querySelector(`[data-coords="${key}"]`)
1843
- layoutParent
1844
- .querySelectorAll('* > .is-selected')
1845
- .forEach((layout) => layout.classList.remove('is-selected'))
1846
- layoutEl.classList.add('is-selected')
1847
- }
1848
-
1849
- LAYOUTS_NORMAL.forEach((origin, key) => {
1850
- const { 0: x, 1: y } = origin.split(',')
1851
- const { xPercent, yPercent } = getPositionAsPercent(x, y)
1852
-
1853
- layoutParent.appendChild(
1854
- create('div', {
1855
- className: 'VideoOverlay-layout-item',
1856
- 'data-coords': `${xPercent},${yPercent}`,
1857
- onclick: (event) => {
1858
- updatePosition(x, y)
1859
- updatePositionInputs(x, y)
1860
- selectLayout(`${xPercent},${yPercent}`)
1861
- event.preventDefault()
1862
- },
1863
- }),
1864
- )
1865
- })
1866
-
1867
- // Duration Start:
1868
- const inputStart = item.querySelector('textarea[name$="/startTime"]') // SMPTE format
1869
- // Create a new duration start (hidden) input so that the start value and end value can both be the same format (millis).
1870
- // When this hidden input changes, we convert the value to SMPTE and update the startTime input.
1871
- const inputDurationStart = create('input')
1872
- inputDurationStart.style.display = 'none'
1873
- inputDurationStart.setAttribute('hidden', 'true')
1874
- inputDurationStart.value = inputStart.value
1875
- ? this.SMPTEToSeconds(inputStart.value) * 1000
1876
- : 0
1877
- inputDurationStart.onchange = (evt) => {
1878
- const seconds = inputDurationStart.value / 1000
1879
- inputStart.value = this.secondsToSMPTE(seconds)
1880
- this._MiniMap.then((minimap) => {
1881
- minimap.redrawOverlays()
1882
- })
1883
- gotoAndPlay(seconds)
1884
- }
1885
-
1886
- // Duration Stop:
1887
- const inputDurationStop = item.querySelector('input[name$="/duration"]') // Milliseconds format
1888
- const durationSlider = create('div', { className: 'is-slideable' })
1889
- inputDurationStop.insertAdjacentElement('beforebegin', durationSlider)
1890
- inputDurationStop.onchange = (evt) => {
1891
- const seconds = inputDurationStop.value / 1000
1892
- this._MiniMap.then((minimap) => {
1893
- minimap.redrawOverlays()
1894
- })
1895
- gotoAndPlay(seconds)
1896
- }
1897
-
1898
- this.createSlider(
1899
- durationSlider,
1900
- [inputDurationStart, inputDurationStop],
1901
- (value) => {},
1902
- [+inputDurationStart.value, +inputDurationStop.value],
1903
- {
1904
- min: 0,
1905
- max: this._VideoPlayer.duration * 1000,
1906
- },
1907
- {
1908
- to: function (value) {
1909
- return parseInt(value)
1910
- },
1911
- from: function (value) {
1912
- return parseInt(value)
1913
- },
1914
- },
1915
- )
1916
-
1917
- const gotoAndPlay = (seconds) => {
1918
- // Isn't guaranteed to work:
1919
- // https://github.com/sampotts/plyr/issues/208#issuecomment-400539990
1920
- this._VideoPlayer.currentTime = parseFloat(seconds)
1921
- }
1922
-
1923
- // Fade Effect selector:
1924
-
1925
- const inputFadeInMillis = item.querySelector('input[name$="/fadeIn"]')
1926
- const inputFadeOutMillis = item.querySelector('input[name$="/fadeOut"]')
1927
-
1928
- const updateFadeEffect = (millis) => {
1929
- inputFadeInMillis.value = millis
1930
- inputFadeOutMillis.value = millis
1931
- layer.style.setProperty('--transitionDuration-millis', `${millis}ms`)
1932
- }
1933
-
1934
- const inputFadeNone = create('input', {
1935
- type: 'radio',
1936
- name: `effect-for-${id}`,
1937
- className: 'radio-button',
1938
- value: 0,
1939
- onchange: (event) => {
1940
- updateFadeEffect(0)
1941
- },
1942
- })
1943
-
1944
- const inputFadeFast = create('input', {
1945
- type: 'radio',
1946
- name: `effect-for-${id}`,
1947
- className: 'radio-button',
1948
- value: 350,
1949
- onchange: (event) => {
1950
- updateFadeEffect(350)
1951
- },
1952
- })
1953
-
1954
- const inputFadeSlow = create('input', {
1955
- type: 'radio',
1956
- name: `effect-for-${id}`,
1957
- className: 'radio-button',
1958
- value: 1000,
1959
- onchange: (event) => {
1960
- updateFadeEffect(1000)
1961
- },
1962
- })
1963
-
1964
- item
1965
- .querySelector('.VideoEditor-InputOverlay > [data-field$="file"]')
1966
- .insertAdjacentElement(
1967
- 'afterend',
1968
- create('div', { className: 'CIG-row', visible: true }, [
1969
- create('div', { className: 'CIG-label' }, 'Position'),
1970
- layoutParent,
1971
- ]),
1972
- )
1973
-
1974
- item
1975
- .querySelector('.VideoEditor-InputOverlay > [data-field$="opacity"]')
1976
- .insertAdjacentElement(
1977
- 'afterend',
1978
- create('div', { className: 'CIG-row scale', visible: true }, [
1979
- create('div', { className: 'CIG-label' }, 'Scale'),
1980
- create('div', { className: 'CIG-small' }, scaleSlider),
1981
- ]),
1982
- )
1983
-
1984
- item
1985
- .querySelector('.VideoEditor-InputOverlay > [data-field$="xPercent"]')
1986
- .insertAdjacentElement(
1987
- 'beforebegin',
1988
- create('div', { className: 'CIG-row', visible: true }, [
1989
- create('div', { className: 'CIG-label' }, 'Duration'),
1990
- create(
1991
- 'div',
1992
- {
1993
- className: 'CIG-small',
1994
- style: 'position:relative',
1995
- },
1996
- [inputDurationStart, durationSlider],
1997
- ),
1998
- ]),
1999
- )
2000
-
2001
- item
2002
- .querySelector(
2003
- '.VideoEditor-InputOverlay > [data-field$="widthPercent"]',
2004
- )
2005
- .insertAdjacentElement(
2006
- 'beforebegin',
2007
- create('div', { className: 'CIG-row', visible: true }, [
2008
- create('div', { className: 'CIG-label' }, 'Fade Effect'),
2009
- create(
2010
- 'div',
2011
- { className: 'CIG-small VideoOverlay-fadeEffect-group' },
2012
- [
2013
- create(
2014
- 'label',
2015
- {
2016
- for: inputFadeNone.id,
2017
- className: 'radio-button-click-target',
2018
- },
2019
- inputFadeNone,
2020
- 'None',
2021
- ),
2022
-
2023
- create(
2024
- 'label',
2025
- {
2026
- for: inputFadeFast.id,
2027
- className: 'radio-button-click-target',
2028
- },
2029
- inputFadeFast,
2030
- 'Brief',
2031
- ),
2032
-
2033
- create(
2034
- 'label',
2035
- {
2036
- for: inputFadeSlow.id,
2037
- className: 'radio-button-click-target',
2038
- },
2039
- inputFadeSlow,
2040
- 'Gradual',
2041
- ),
2042
- ],
2043
- ),
2044
- ]),
2045
- )
2046
-
2047
- const getInputValues = () => {
2048
- return [
2049
- item.querySelector('input[name$="/opacity"]'),
2050
- item.querySelector('input[name$="/xPercent"]'),
2051
- item.querySelector('input[name$="/yPercent"]'),
2052
- item.querySelector('input[name$="/widthPercent"]'),
2053
- item.querySelector('input[name$="/heightPercent"]'),
2054
- item.querySelector('textarea[name$="/startTime"]'),
2055
- item.querySelector('input[name$="/duration"]'),
2056
- item.querySelector('input[name$="/fadeIn"]'),
2057
- item.querySelector('input[name$="/fadeOut"]'),
2058
- ].reduce((accum, val) => {
2059
- const propname = val.name.substring(val.name.lastIndexOf('/') + 1)
2060
- Object.assign(accum, {
2061
- [propname]: val.type === 'checkbox' ? val.checked : val.value,
2062
- })
2063
- return accum
2064
- }, {})
2065
- }
2066
-
2067
- const applyAllEdits = (data) => {
2068
- const {
2069
- opacity,
2070
- xPercent,
2071
- yPercent,
2072
- widthPercent,
2073
- heightPercent,
2074
- startTime,
2075
- duration,
2076
- fadeIn,
2077
- } = { ...data }
2078
-
2079
- if (opacity !== '') {
2080
- opacitySlider.noUiSlider.set(opacity)
2081
- }
2082
-
2083
- if (xPercent !== '' && yPercent !== '') {
2084
- // Convert to pixels using the top-left of the image as the 0,0 coordinate.
2085
- let x = nativeVideoWidth * (xPercent / 100)
2086
- let y = nativeVideoHeight * (yPercent / 100)
2087
-
2088
- x = Math.sign(x) === -1 ? 0 : x || 0
2089
- y = Math.sign(y) === -1 ? 0 : y || 0
2090
-
2091
- updatePosition(x, y)
2092
- selectLayout(`${xPercent},${yPercent}`)
2093
- }
2094
-
2095
- if (widthPercent !== '' && heightPercent !== '') {
2096
- scaleSlider.noUiSlider.set(widthPercent)
2097
- } else {
2098
- scaleSlider.noUiSlider.set(50)
2099
- }
2100
-
2101
- const startSeconds =
2102
- startTime !== '' ? this.SMPTEToSeconds(startTime) : 0
2103
- const durationStart = startTime !== '' ? startSeconds * 1000 : 0
2104
- const durationStop =
2105
- duration || parseFloat(this._VideoPlayer.duration * 1000)
2106
-
2107
- durationSlider.noUiSlider.set([+durationStart, +durationStop])
2108
-
2109
- const fadeValue = fadeIn
2110
- if (fadeValue !== '') {
2111
- const matchedFadeInput = item.querySelector(
2112
- `input[name="effect-for-${id}"][value="${fadeValue}"]`,
2113
- )
2114
-
2115
- if (matchedFadeInput) {
2116
- matchedFadeInput.checked = true
2117
- updateFadeEffect(fadeValue)
2118
- }
2119
- }
2120
-
2121
- show()
2122
- }
2123
-
2124
- const hide = () => {
2125
- layer.classList.add('is-hidden')
2126
- layer.style.opacity = 0
2127
- }
2128
- const show = () => {
2129
- layer.classList.remove('is-hidden')
2130
- layer.style.opacity = opacitySlider.noUiSlider.get() / 100
2131
- }
2132
-
2133
- const getStartSeconds = () => inputDurationStart.value / 1000
2134
- const getStopSeconds = () => inputDurationStop.value / 1000
2135
-
2136
- applyAllEdits(getInputValues())
2137
- _hideUnusedInputs()
2138
-
2139
- this._MiniMap.then((minimap) => {
2140
- minimap.redrawOverlays()
2141
- })
2142
-
2143
- resolve({
2144
- item,
2145
- hide,
2146
- show,
2147
- getStartSeconds,
2148
- getStopSeconds,
2149
- remove,
2150
- })
2151
- }).catch((err) => {
2152
- console.warn(`Overlay failed to initialize due to: \n${err}`)
2153
- })
2154
- }
2155
-
2156
- onFind(sourceEl, '.RCIG-list', (list) => {
2157
- // Respond to overlay objects being re-ordered.
2158
- const onSourceOrderChanged = (mutations) => {
2159
- mutations.forEach((mutation) => {
2160
- if (mutation.addedNodes.length) {
2161
- const CIG = mutation.addedNodes[0].querySelector('.CIG')
2162
- if (CIG) {
2163
- // sync the stacking order of all overlay items.
2164
- CIG.closest('ol').childNodes.forEach(async (item, index) => {
2165
- const key = await _getOverlayKey(item)
2166
- _changeOverlayOrder(key, index)
2167
- // Redraw the minimap each time an Overlay is found.
2168
- this._MiniMap.then((minimap) => {
2169
- minimap.redrawOverlays()
2170
- })
2171
- })
2172
- }
2173
- }
2174
- })
2175
- }
2176
- const observer = new MutationObserver(onSourceOrderChanged)
2177
- observer.observe(list, {
2178
- attributes: false,
2179
- childList: true,
2180
- })
2181
- })
2182
-
2183
- // React to the addition of a file preview node.
2184
- // Returns an Object with:
2185
- // - key: The unique data-name of the file element
2186
- // - element: The image element
2187
- const _getImage = (item) => {
2188
- return new Promise((resolve, reject) => {
2189
- onFind(item, '.filePreview', (filePreview) => {
2190
- if (filePreview) {
2191
- // setup an inner-observer to determine when the file preview image is rendered.
2192
- const previewObserver = new MutationObserver((mutations) => {
2193
- mutations.some((mutation) => {
2194
- if (
2195
- mutation.target.classList.contains('is-imageEditorLoaded')
2196
- ) {
2197
- previewObserver.disconnect()
2198
- const element = filePreview.querySelector('img.cropper-hide')
2199
- if (!element) return
2200
-
2201
- const id = filePreview.parentElement
2202
- .getAttribute('data-name')
2203
- .split('/')[0]
2204
-
2205
- element.setAttribute('class', 'VideoOverlay-image')
2206
- element.setAttribute(
2207
- 'style',
2208
- `filter:${element.style.filter}`,
2209
- )
2210
- element.setAttribute('data-name-ref', id)
2211
- element
2212
- .closest('.VideoEditor-InputOverlay')
2213
- .classList.add('is-overlay-ready')
2214
-
2215
- resolve({
2216
- id,
2217
- element,
2218
- })
2219
- }
2220
- })
2221
- })
2222
- previewObserver.observe(filePreview, {
2223
- attributeFilter: ['class'],
2224
- })
2225
- return true
2226
- }
2227
- })
2228
- })
2229
- }
2230
-
2231
- // Returns a Promise that resolves the key value.
2232
- const _getOverlayKey = async (overlay) => {
2233
- return new Promise((resolve, reject) => {
2234
- onFind(overlay, '.filePreview', (preview) => {
2235
- const key = preview.parentElement
2236
- .getAttribute('data-name')
2237
- .split('/')[0]
2238
- resolve(key)
2239
- })
2240
- })
2241
- }
2242
-
2243
- // Returns the CIG element's numeric child index.
2244
- const _getSourceIndexByOverlayCIG = (cig) => {
2245
- const rcig = cig.closest('li')
2246
- return [...rcig.parentNode.children].indexOf(rcig)
2247
- }
2248
-
2249
- // Wait for the overlay object's preview image to be ready then initialize the overlay layer. (step #2)
2250
- onFind(sourceEl, '.RCIG-list .VideoEditor-InputOverlay', (item) => {
2251
- _initOverlay(item)
2252
- .then(async (overlay) => {
2253
- const key = await _getOverlayKey(overlay.item)
2254
- Overlays.set(key, overlay)
2255
- _changeOverlayOrder(key, _getSourceIndexByOverlayCIG(overlay.item))
2256
- // Redraw the minimap each time an Overlay is found.
2257
- this._MiniMap.then((minimap) => {
2258
- minimap.redrawOverlays()
2259
- })
2260
- })
2261
- .catch((err) => {
2262
- console.warn(`Failed to initialize Overlay due to: \n${err}`)
2263
- })
2264
- })
2265
-
2266
- onFind(sourceEl, '.RCIG-item.is-removing', async (item) => {
2267
- // remove this from the Map of Overlays using it's key
2268
- const key = await _getOverlayKey(item)
2269
- const overlay = Overlays.get(key)
2270
- overlay.remove()
2271
- Overlays.delete(key)
2272
- })
2273
-
2274
- // Preload the overlay objects (step #1).
2275
- onFind(sourceEl, '.RCIG-list > li:not(.is-removing)', (item) => {
2276
- // collapsed items lazy-load their contents so let's open them to load the preview images.
2277
- if (item.classList.contains('is-collapsed')) {
2278
- const evt = new MouseEvent('click', {
2279
- bubbles: true,
2280
- cancelable: true,
2281
- view: window,
2282
- })
2283
-
2284
- const title = item.querySelector('.RCIG-title')
2285
- // click on the title to trigger the lazy-load.
2286
- title.dispatchEvent(evt)
2287
- // click again to collapse the expanded item so no one notices what was done :)
2288
- title.dispatchEvent(evt)
2289
- }
2290
- })
2291
-
2292
- const getOverlays = () => {
2293
- // Finds each matching CIG Overlay object that isn't in a "removing" state by key.
2294
- // This guarantees that the returned Overlay elements are in the same order as the DOM.
2295
- let overlayItems = []
2296
- sourceEl
2297
- .querySelectorAll(
2298
- '.RCIG-list .RCIG-item:not(.is-removing) .VideoEditor-InputOverlay.is-overlay-ready',
2299
- )
2300
- .forEach((item) => {
2301
- const Overlay = Overlays.get(item.getAttribute('data-object-id'))
2302
- if (Overlay) {
2303
- overlayItems.push(Overlay)
2304
- }
2305
- })
2306
- return overlayItems
2307
- }
2308
-
2309
- const reset = () => {
2310
- // Removes any overlay CIGs that have been added to this video.
2311
- const overlayRemoveButtons = sourceEl.querySelectorAll(
2312
- '.RCIG-list > li:not(.is-removing) .RCIG-remove',
2313
- )
2314
-
2315
- overlayRemoveButtons.forEach((button) => {
2316
- button.click()
2317
- })
2318
- }
2319
-
2320
- return {
2321
- element,
2322
- handleUpdate,
2323
- getOverlays,
2324
- reset,
2325
- }
2326
- }
2327
-
2328
- _createVideoPlayer(videoEl) {
2329
- const clone = videoEl.cloneNode(true)
2330
- this.originEl.appendChild(clone)
2331
-
2332
- // Instantiate the Plyr UI.
2333
- return new Promise((resolve, reject) => {
2334
- this._VideoPlayer = new Plyr(clone, {
2335
- hideControls: false,
2336
- invertTime: false,
2337
- controls: ['play', 'progress', 'current-time', 'mute', 'volume'],
2338
- })
2339
-
2340
- this._VideoPlayer.on('ready', (event) => {
2341
- resolve()
2342
- })
2343
- })
2344
- }
2345
-
2346
- _updateVideoState(state) {
2347
- const stateEl = document.querySelector('.Page-content .content-edit')
2348
- if (this._currentVideoState) {
2349
- stateEl.classList.remove(this._currentVideoState)
2350
- }
2351
- stateEl.classList.add(state)
2352
- this._currentVideoState = state
2353
- }
2354
- }
2355
-
2356
- let Editor = null
2357
-
2358
- const destroyEditor = (targetEl) => {
2359
- const stateEl = document.querySelector('.Page-content .content-edit')
2360
- stateEl.classList.remove(Editor.getState())
2361
-
2362
- Editor._VideoPlayer.destroy()
2363
-
2364
- const referenceVideos = targetEl.querySelectorAll('video')
2365
- referenceVideos.forEach((video) => video.remove())
2366
-
2367
- const stageVideo = Editor.rootEl.querySelector('.VideoEditor-stage video')
2368
- stageVideo.remove()
2369
- }
2370
-
2371
- const observeOnVideoSourceChanged = (target) => {
2372
- const row = closest(target, '.CIG-row')
2373
- onFind(row, '.fileSelector', (fileSelector) => {
2374
- const select = fileSelector.querySelector('select')
2375
- if (select) {
2376
- select.addEventListener('change', (e) => {
2377
- if (Editor) {
2378
- destroyEditor(target)
2379
- Editor._initVideo().then(async (sourceVideo) => {
2380
- const target = Editor.rootEl.querySelector(
2381
- '.VideoEditor-stage-main',
2382
- )
2383
- Editor._updateVideoState(STATE_VIDEO_LOADED)
2384
- await Editor._createVideoPlayer(sourceVideo)
2385
- target.appendChild(Editor._VideoPlayer.elements.container)
2386
-
2387
- if (Editor._ColorUI) Editor._ColorUI.reset()
2388
- if (Editor._OverlayUI) Editor._OverlayUI.reset()
2389
- if (Editor._ClippingUI) Editor._ClippingUI.reset()
2390
-
2391
- Editor._MiniMap.then((minimap) => {
2392
- minimap.reset()
2393
- minimap.setVideo(sourceVideo)
2394
- })
2395
-
2396
- Editor.resetFilmstrip()
2397
- Editor.createFilmstripPlaceholder(sourceVideo)
2398
-
2399
- await Editor.createFilmstrip(
2400
- sourceVideo,
2401
- sourceVideo.videoWidth,
2402
- sourceVideo.videoHeight,
2403
- sourceVideo.duration,
2404
- )
2405
- })
2406
- }
2407
- })
2408
- } else {
2409
- console.warn(`Missing <select> in .fileSelector element!`)
2410
- }
2411
- })
2412
- }
2413
-
2414
- onFind('.VideoEditor', (target) => {
2415
- Editor = new VideoEditor(target)
2416
- observeOnVideoSourceChanged(target)
2417
- })