@brightspot/ui 3.0.1-cms-ui-migration.0 → 3.0.1-cms-ui-migration.2
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.
- package/dist/custom-elements.json +1842 -1842
- package/dist/storybook/assets/{ActionBar.stories-CXEvFUN5.js → ActionBar.stories-hJ_5cm-P.js} +1 -1
- package/dist/storybook/assets/{ActionItem.stories-8-qQmVGz.js → ActionItem.stories-Bjx2803w.js} +1 -1
- package/dist/storybook/assets/{Avatar.stories-LOTCiTgV.js → Avatar.stories-Cj0YgZ6f.js} +1 -1
- package/dist/storybook/assets/{AvatarGroup.stories-N17TVn4O.js → AvatarGroup.stories-Lh_sQFCU.js} +1 -1
- package/dist/storybook/assets/{Badge.stories-2IRfk8Ri.js → Badge.stories-BL7RUibx.js} +1 -1
- package/dist/storybook/assets/{Button-CQ2CjiFm.js → Button-BPHNcxqK.js} +1 -1
- package/dist/storybook/assets/{Button.stories-DwyviUHj.js → Button.stories-CAYO4gdU.js} +1 -1
- package/dist/storybook/assets/{ButtonGroup.stories-D6nkkXxD.js → ButtonGroup.stories-Cd13Us5K.js} +1 -1
- package/dist/storybook/assets/{Celebrate.stories-DtKNptXA.js → Celebrate.stories-D_KE3Qze.js} +1 -1
- package/dist/storybook/assets/{Checkbox.stories--fetKLgV.js → Checkbox.stories-Aj1xgZVn.js} +1 -1
- package/dist/storybook/assets/{CircularProgress.stories-vK3MWdEg.js → CircularProgress.stories-BecV_v6d.js} +1 -1
- package/dist/storybook/assets/{ClipboardMixin.stories-BXOX2e9A.js → ClipboardMixin.stories-DU-WiZ2f.js} +1 -1
- package/dist/storybook/assets/{Color-6BZIO3FS-DIO6ExZa.js → Color-6BZIO3FS-BYl4KZZn.js} +1 -1
- package/dist/storybook/assets/{Colors.stories-Z-o0Dmyi.js → Colors.stories-BMUVUy2q.js} +1 -1
- package/dist/storybook/assets/{CombinedEffects.stories-BIvRmEL2.js → CombinedEffects.stories-FHcPKFm6.js} +1 -1
- package/dist/storybook/assets/{ComponentStatesMixin-BeLIKOsg.js → ComponentStatesMixin-EMUnfT5y.js} +1 -1
- package/dist/storybook/assets/{ComponentStatesMixin.stories-u03jgLUU.js → ComponentStatesMixin.stories-SXq0kzS9.js} +1 -1
- package/dist/storybook/assets/{CopyToClipboard.stories-D23guAYu.js → CopyToClipboard.stories-u43lhvcI.js} +1 -1
- package/dist/storybook/assets/{Debounce.stories-Bz3JaQJY.js → Debounce.stories-BdCn5qgO.js} +1 -1
- package/dist/storybook/assets/{DocsRenderer-LL677BLK-CaEKjMeC.js → DocsRenderer-LL677BLK-DxiEJ_jx.js} +3 -3
- package/dist/storybook/assets/{Dropdown.stories-CNxtx8UX.js → Dropdown.stories-BWSRwjIF.js} +1 -1
- package/dist/storybook/assets/{EmptyState.stories-Ck5T6q1T.js → EmptyState.stories-BpobeZL5.js} +1 -1
- package/dist/storybook/assets/{Events.stories-Cx10pvVB.js → Events.stories-CP5kMzpr.js} +1 -1
- package/dist/storybook/assets/{Heading.stories-mbkSoA5r.js → Heading.stories-CbJD-oTB.js} +1 -1
- package/dist/storybook/assets/{HueRipple.stories-B-DDviMh.js → HueRipple.stories-BOABJ7zw.js} +1 -1
- package/dist/storybook/assets/{Icon.stories-Dodo1jjZ.js → Icon.stories-CWlUHL4j.js} +1 -1
- package/dist/storybook/assets/{IconButton.stories-eAU6vNiX.js → IconButton.stories-BWBs-OLT.js} +1 -1
- package/dist/storybook/assets/{LinearProgress.stories-BqAUCgwy.js → LinearProgress.stories-LZ0GZoxF.js} +1 -1
- package/dist/storybook/assets/{Pagination.stories-CVK8mlEU.js → Pagination.stories-CbLaR3P9.js} +1 -1
- package/dist/storybook/assets/{Popover.stories-CC2R-jiv.js → Popover.stories-JHrWqYZw.js} +1 -1
- package/dist/storybook/assets/{ReadyMixin-CAkCCAOH.js → ReadyMixin-B1H2a9x8.js} +1 -1
- package/dist/storybook/assets/{RovingTabindexMixin.stories-Dgm5izU_.js → RovingTabindexMixin.stories-UMHpYG73.js} +1 -1
- package/dist/storybook/assets/{Rtc.stories-D9uVCq71.js → Rtc.stories-VQtNSlls.js} +1 -1
- package/dist/storybook/assets/{ScrollShadow.stories-D5GJ6Zes.js → ScrollShadow.stories-CYdi8Tgp.js} +1 -1
- package/dist/storybook/assets/{Switch.stories-D7HV0v6i.js → Switch.stories-D8F2hZCf.js} +1 -1
- package/dist/storybook/assets/{Tab.stories-CwHBngmO.js → Tab.stories-wgBP0lTj.js} +1 -1
- package/dist/storybook/assets/{Tabs.stories-D87ILflp.js → Tabs.stories-C00rr5sf.js} +1 -1
- package/dist/storybook/assets/{Throttle.stories-C13Vl7yt.js → Throttle.stories-BhQEfJbS.js} +1 -1
- package/dist/storybook/assets/{Tooltip.stories-BRmAKppC.js → Tooltip.stories-CGoZ5qTn.js} +1 -1
- package/dist/storybook/assets/{Upload.stories-CvceXasD.js → Upload.stories-B3K-HAXw.js} +1 -1
- package/dist/storybook/assets/{UploadItem.stories-BF6c7tVM.js → UploadItem.stories-71ArSoUh.js} +1 -1
- package/dist/storybook/assets/{Welcome.stories-Ciht_H8A.js → Welcome.stories-CihlfFXS.js} +1 -1
- package/dist/storybook/assets/{Widget.stories-30UYw1gn.js → Widget.stories-1u4KbiJM.js} +1 -1
- package/dist/storybook/assets/{WithTooltip-65CFNBJE-Q8AG_oZS.js → WithTooltip-65CFNBJE-B3Jitxw9.js} +1 -1
- package/dist/storybook/assets/{blocks-DIrsUt2U.js → blocks-C1HaXuQB.js} +5 -5
- package/dist/storybook/assets/{formatter-EIJCOSYU-BcNb3EA1.js → formatter-EIJCOSYU-Dy9Lt9fs.js} +1 -1
- package/dist/storybook/assets/if-defined-CA2KmTqA.js +1 -0
- package/dist/storybook/assets/{iframe-BgFj0b5u.css → iframe-D0roG0J-.css} +1 -1
- package/dist/storybook/assets/{iframe-D5Uwm8Zd.js → iframe-Dx6IxWXF.js} +4 -4
- package/dist/storybook/assets/{index-BOlx3-q7.js → index-OrjedSVh.js} +1 -1
- package/dist/storybook/assets/{onFind-Cp2DO8R2.js → onFind-YTqjw6W0.js} +1 -1
- package/dist/storybook/assets/{onFind.stories-BDK06xaW.js → onFind.stories-DEvwTrmx.js} +1 -1
- package/dist/storybook/assets/{onRemove.stories-TSKvqih3.js → onRemove.stories-D5mO-Lin.js} +1 -1
- package/dist/storybook/assets/{onVisible.stories-DiOPobn8.js → onVisible.stories-C3Rcz0Eb.js} +1 -1
- package/dist/storybook/assets/{style-map-Cy6U2K_U.js → style-map-CiMHry7H.js} +1 -1
- package/dist/storybook/assets/{syntaxhighlighter-ED5Y7EFY-NNCQVsSv.js → syntaxhighlighter-ED5Y7EFY-DIZnuhb2.js} +1 -1
- package/dist/storybook/iframe.html +2 -2
- package/dist/storybook/project.json +1 -1
- package/dist/tailwind.config.d.ts +4 -1
- package/dist/tailwind.config.d.ts.map +1 -1
- package/dist/tailwind.config.js +0 -2
- package/dist/tailwind.config.js.map +1 -1
- package/dist/tailwind.config.ts +0 -2
- package/package.json +2 -2
- package/src/legacy/tool-ui/src/AIInline.css +123 -0
- package/src/legacy/tool-ui/src/ActionBar.css +41 -0
- package/src/legacy/tool-ui/src/Admin.css +258 -0
- package/src/legacy/tool-ui/src/AdobeStock.css +3 -0
- package/src/legacy/tool-ui/src/AnalyticsWidget.css +34 -0
- package/src/legacy/tool-ui/src/AnalyticsWidget.ts +158 -0
- package/src/legacy/tool-ui/src/Apis.css +31 -0
- package/src/legacy/tool-ui/src/AppetizeioEmbedded.css +58 -0
- package/src/legacy/tool-ui/src/AssociatedContentWidget.css +68 -0
- package/src/legacy/tool-ui/src/AutoExpand.css +33 -0
- package/src/legacy/tool-ui/src/Avatar.css +31 -0
- package/src/legacy/tool-ui/src/BackgroundTasks.css +29 -0
- package/src/legacy/tool-ui/src/Base.css +80 -0
- package/src/legacy/tool-ui/src/Board.css +160 -0
- package/src/legacy/tool-ui/src/Board.ts +515 -0
- package/src/legacy/tool-ui/src/BroadcastAlertBanner.ts +93 -0
- package/src/legacy/tool-ui/src/BulkUpload.css +51 -0
- package/src/legacy/tool-ui/src/BulkWorkflow.css +47 -0
- package/src/legacy/tool-ui/src/BulkWorkflow.ts +110 -0
- package/src/legacy/tool-ui/src/BulletedList.css +11 -0
- package/src/legacy/tool-ui/src/CIGCluster/README.md +32 -0
- package/src/legacy/tool-ui/src/CIGCluster/index.ts +144 -0
- package/src/legacy/tool-ui/src/CIGCluster.css +60 -0
- package/src/legacy/tool-ui/src/Calendar.css +110 -0
- package/src/legacy/tool-ui/src/Card.css +28 -0
- package/src/legacy/tool-ui/src/Celebrate.ts +87 -0
- package/src/legacy/tool-ui/src/Chart.ts +53 -0
- package/src/legacy/tool-ui/src/ChartCompat.ts +413 -0
- package/src/legacy/tool-ui/src/ChartTable.ts +22 -0
- package/src/legacy/tool-ui/src/Checkbox.css +22 -0
- package/src/legacy/tool-ui/src/CodeMirror.css +161 -0
- package/src/legacy/tool-ui/src/Collections.css +74 -0
- package/src/legacy/tool-ui/src/ColorInputSpectrum.css +140 -0
- package/src/legacy/tool-ui/src/ComboInput.css +253 -0
- package/src/legacy/tool-ui/src/Compat.css +383 -0
- package/src/legacy/tool-ui/src/ComponentStatesMixin.ts +27 -0
- package/src/legacy/tool-ui/src/ContentColors.css +19 -0
- package/src/legacy/tool-ui/src/ContentEdit.css +1065 -0
- package/src/legacy/tool-ui/src/ContentEditDrawer.css +274 -0
- package/src/legacy/tool-ui/src/ContentEditDrawer.ts +217 -0
- package/src/legacy/tool-ui/src/ContentEditPanel.ts +188 -0
- package/src/legacy/tool-ui/src/ContentEditSites.ts +101 -0
- package/src/legacy/tool-ui/src/ContentForm.css +87 -0
- package/src/legacy/tool-ui/src/ContentForm.ts +104 -0
- package/src/legacy/tool-ui/src/ContentInputGroup.css +678 -0
- package/src/legacy/tool-ui/src/ContentInputGroup.ts +86 -0
- package/src/legacy/tool-ui/src/ContentReporting.css +54 -0
- package/src/legacy/tool-ui/src/ContentSelector.css +215 -0
- package/src/legacy/tool-ui/src/ContentSelectorActions.ts +428 -0
- package/src/legacy/tool-ui/src/ContentSummary.css +176 -0
- package/src/legacy/tool-ui/src/ContentSummary.ts +223 -0
- package/src/legacy/tool-ui/src/ContentTemplatesWidget.css +54 -0
- package/src/legacy/tool-ui/src/ContentTools.css +109 -0
- package/src/legacy/tool-ui/src/ContentType.css +27 -0
- package/src/legacy/tool-ui/src/Conversation.css +325 -0
- package/src/legacy/tool-ui/src/Conversation.ts +836 -0
- package/src/legacy/tool-ui/src/CopySiteWidget.css +8 -0
- package/src/legacy/tool-ui/src/CopyToClipboard/README.md +23 -0
- package/src/legacy/tool-ui/src/CopyToClipboard/index.ts +54 -0
- package/src/legacy/tool-ui/src/CopyToClipboard.css +10 -0
- package/src/legacy/tool-ui/src/Crosslinker.css +486 -0
- package/src/legacy/tool-ui/src/Crosslinker.ts +46 -0
- package/src/legacy/tool-ui/src/CustomKeyboard.css +25 -0
- package/src/legacy/tool-ui/src/DashboardRow.css +54 -0
- package/src/legacy/tool-ui/src/DashboardWidget.css +444 -0
- package/src/legacy/tool-ui/src/DateStringField.css +78 -0
- package/src/legacy/tool-ui/src/DateTimeInput.css +11 -0
- package/src/legacy/tool-ui/src/Dialog.css +441 -0
- package/src/legacy/tool-ui/src/Diff.css +280 -0
- package/src/legacy/tool-ui/src/Diff.ts +89 -0
- package/src/legacy/tool-ui/src/Downloads.css +7 -0
- package/src/legacy/tool-ui/src/Dropdown.css +63 -0
- package/src/legacy/tool-ui/src/EditFieldUpdateCache.ts +129 -0
- package/src/legacy/tool-ui/src/EmbeddedInputGroup/README.md +3 -0
- package/src/legacy/tool-ui/src/EmbeddedInputGroup/index.ts +189 -0
- package/src/legacy/tool-ui/src/EmbeddedInputGroup.css +75 -0
- package/src/legacy/tool-ui/src/Enhancement.css +135 -0
- package/src/legacy/tool-ui/src/Entry.ts +875 -0
- package/src/legacy/tool-ui/src/EventEmitterMixin.ts +71 -0
- package/src/legacy/tool-ui/src/ExternalCalendars.ts +34 -0
- package/src/legacy/tool-ui/src/ExternalItemObjects.ts +36 -0
- package/src/legacy/tool-ui/src/FileInput.css +150 -0
- package/src/legacy/tool-ui/src/FocusRegions.ts +530 -0
- package/src/legacy/tool-ui/src/FormFilter.css +93 -0
- package/src/legacy/tool-ui/src/FullscreenView.css +152 -0
- package/src/legacy/tool-ui/src/GCA.css +9 -0
- package/src/legacy/tool-ui/src/Global.d.ts +219 -0
- package/src/legacy/tool-ui/src/GraphQL.css +74 -0
- package/src/legacy/tool-ui/src/GraphQLApis.css +4 -0
- package/src/legacy/tool-ui/src/Guide.css +193 -0
- package/src/legacy/tool-ui/src/GuideField.css +25 -0
- package/src/legacy/tool-ui/src/Hierarchy.css +68 -0
- package/src/legacy/tool-ui/src/Icon/index.css +11 -0
- package/src/legacy/tool-ui/src/Icon/index.ts +69 -0
- package/src/legacy/tool-ui/src/ImageEditor.css +522 -0
- package/src/legacy/tool-ui/src/ImageRecognition.css +11 -0
- package/src/legacy/tool-ui/src/Incompatible.css +42 -0
- package/src/legacy/tool-ui/src/InputRow.css +26 -0
- package/src/legacy/tool-ui/src/Label.css +33 -0
- package/src/legacy/tool-ui/src/LabeledCheckbox.css +13 -0
- package/src/legacy/tool-ui/src/Link.css +4 -0
- package/src/legacy/tool-ui/src/LinkCarousel.css +77 -0
- package/src/legacy/tool-ui/src/LinkList.css +53 -0
- package/src/legacy/tool-ui/src/LinkTable.css +105 -0
- package/src/legacy/tool-ui/src/ListManager.css +27 -0
- package/src/legacy/tool-ui/src/LiveBlog.css +290 -0
- package/src/legacy/tool-ui/src/LiveBlog.ts +47 -0
- package/src/legacy/tool-ui/src/Loader.svg +16 -0
- package/src/legacy/tool-ui/src/LocationMap.css +25 -0
- package/src/legacy/tool-ui/src/LookingGlass.css +31 -0
- package/src/legacy/tool-ui/src/LucideDynamicLoader.ts +31 -0
- package/src/legacy/tool-ui/src/MailPublishing.css +32 -0
- package/src/legacy/tool-ui/src/Mention.css +34 -0
- package/src/legacy/tool-ui/src/MenuView.css +659 -0
- package/src/legacy/tool-ui/src/Message.css +239 -0
- package/src/legacy/tool-ui/src/Month.css +65 -0
- package/src/legacy/tool-ui/src/NavRail/index.css +47 -0
- package/src/legacy/tool-ui/src/NavRail/index.ts +40 -0
- package/src/legacy/tool-ui/src/Notification.css +197 -0
- package/src/legacy/tool-ui/src/Notifications.ts +139 -0
- package/src/legacy/tool-ui/src/Page.css +884 -0
- package/src/legacy/tool-ui/src/PageNav.ts +108 -0
- package/src/legacy/tool-ui/src/PaginatedResult.css +20 -0
- package/src/legacy/tool-ui/src/Pagination.css +119 -0
- package/src/legacy/tool-ui/src/PastePopup.css +11 -0
- package/src/legacy/tool-ui/src/PlaceholderEditableMixin.ts +86 -0
- package/src/legacy/tool-ui/src/Popup.css +413 -0
- package/src/legacy/tool-ui/src/PostPublish.ts +12 -0
- package/src/legacy/tool-ui/src/PrePublish.ts +63 -0
- package/src/legacy/tool-ui/src/Preview.css +134 -0
- package/src/legacy/tool-ui/src/PrivilegeAccessWidget.css +27 -0
- package/src/legacy/tool-ui/src/ProfileDropdown.css +95 -0
- package/src/legacy/tool-ui/src/ProseMirror-table.css +159 -0
- package/src/legacy/tool-ui/src/ProseMirror.css +180 -0
- package/src/legacy/tool-ui/src/ProseMirrorContainer.css +40 -0
- package/src/legacy/tool-ui/src/ProseMirrorEnhancementMenu.css +68 -0
- package/src/legacy/tool-ui/src/ProseMirrorFindReplace.css +132 -0
- package/src/legacy/tool-ui/src/QueryField.css +77 -0
- package/src/legacy/tool-ui/src/README.md +235 -0
- package/src/legacy/tool-ui/src/RadialProgressBar.css +11 -0
- package/src/legacy/tool-ui/src/RadialProgressBar.ts +73 -0
- package/src/legacy/tool-ui/src/Radio.css +22 -0
- package/src/legacy/tool-ui/src/RepeatableContentInputGroup.css +470 -0
- package/src/legacy/tool-ui/src/RepeatableContentSelector.css +177 -0
- package/src/legacy/tool-ui/src/RepeatableTextInput.css +71 -0
- package/src/legacy/tool-ui/src/Revision.css +70 -0
- package/src/legacy/tool-ui/src/Revisions.css +292 -0
- package/src/legacy/tool-ui/src/RichText.css +15 -0
- package/src/legacy/tool-ui/src/SearchControlsToggle.ts +90 -0
- package/src/legacy/tool-ui/src/SearchFields.css +109 -0
- package/src/legacy/tool-ui/src/SearchInput.css +27 -0
- package/src/legacy/tool-ui/src/SearchResult.css +523 -0
- package/src/legacy/tool-ui/src/SearchWidget.css +441 -0
- package/src/legacy/tool-ui/src/SearchWidget.ts +154 -0
- package/src/legacy/tool-ui/src/SearchWidgetAdvanced.css +109 -0
- package/src/legacy/tool-ui/src/SharePreview.css +71 -0
- package/src/legacy/tool-ui/src/SkipLinks.css +23 -0
- package/src/legacy/tool-ui/src/SkipLinks.ts +112 -0
- package/src/legacy/tool-ui/src/SlackAuthentication.css +9 -0
- package/src/legacy/tool-ui/src/Sortable.css +50 -0
- package/src/legacy/tool-ui/src/Spellcheck.css +34 -0
- package/src/legacy/tool-ui/src/StyleEmbeddedContent.css +62 -0
- package/src/legacy/tool-ui/src/Suggestions.css +53 -0
- package/src/legacy/tool-ui/src/Suggestions.ts +209 -0
- package/src/legacy/tool-ui/src/TabBar.css +174 -0
- package/src/legacy/tool-ui/src/TabContainer.css +40 -0
- package/src/legacy/tool-ui/src/Table.css +107 -0
- package/src/legacy/tool-ui/src/TableSizerPopup.css +42 -0
- package/src/legacy/tool-ui/src/Taxonomy.css +60 -0
- package/src/legacy/tool-ui/src/TextInput.css +40 -0
- package/src/legacy/tool-ui/src/Theme.css +64 -0
- package/src/legacy/tool-ui/src/ThemeBundleEditor.css +93 -0
- package/src/legacy/tool-ui/src/TimedContent.css +74 -0
- package/src/legacy/tool-ui/src/Toggleable/README.md +90 -0
- package/src/legacy/tool-ui/src/Toggleable/index.ts +230 -0
- package/src/legacy/tool-ui/src/Translation.css +115 -0
- package/src/legacy/tool-ui/src/Translation.ts +20 -0
- package/src/legacy/tool-ui/src/TreeList.css +45 -0
- package/src/legacy/tool-ui/src/Types.ts +44 -0
- package/src/legacy/tool-ui/src/Utilities.css +7 -0
- package/src/legacy/tool-ui/src/ViewMirror.css +7 -0
- package/src/legacy/tool-ui/src/ViewPreview.css +59 -0
- package/src/legacy/tool-ui/src/ViewWatchers.css +69 -0
- package/src/legacy/tool-ui/src/Viewers.css +21 -0
- package/src/legacy/tool-ui/src/Week.css +23 -0
- package/src/legacy/tool-ui/src/Widget.css +622 -0
- package/src/legacy/tool-ui/src/WorkStreams.css +97 -0
- package/src/legacy/tool-ui/src/Workflow.css +100 -0
- package/src/legacy/tool-ui/src/Workstreams.ts +52 -0
- package/src/legacy/tool-ui/src/WrappingMixin.ts +77 -0
- package/src/legacy/tool-ui/src/additional-tailwind-classes.txt +205 -0
- package/src/legacy/tool-ui/src/bsp-tracking.d.ts +27 -0
- package/src/legacy/tool-ui/src/dialog/README.md +83 -0
- package/src/legacy/tool-ui/src/dialog/index.ts +465 -0
- package/src/legacy/tool-ui/src/dom/README.md +63 -0
- package/src/legacy/tool-ui/src/dom/Tether.ts +135 -0
- package/src/legacy/tool-ui/src/dom/TetherLayout.ts +149 -0
- package/src/legacy/tool-ui/src/dom/aria.ts +123 -0
- package/src/legacy/tool-ui/src/dom/closest.ts +5 -0
- package/src/legacy/tool-ui/src/dom/create.ts +46 -0
- package/src/legacy/tool-ui/src/dom/find.ts +5 -0
- package/src/legacy/tool-ui/src/dom/findAll.ts +5 -0
- package/src/legacy/tool-ui/src/dom/focusable.ts +29 -0
- package/src/legacy/tool-ui/src/dom/ifClick.ts +18 -0
- package/src/legacy/tool-ui/src/dom/ifMatches.ts +17 -0
- package/src/legacy/tool-ui/src/dom/ifUnmodified.ts +16 -0
- package/src/legacy/tool-ui/src/dom/insertBefore.ts +4 -0
- package/src/legacy/tool-ui/src/dom/insertFirst.ts +4 -0
- package/src/legacy/tool-ui/src/dom/insertLast.ts +4 -0
- package/src/legacy/tool-ui/src/dom/keyboard.ts +176 -0
- package/src/legacy/tool-ui/src/dom/onFind.ts +277 -0
- package/src/legacy/tool-ui/src/dom/onFindOnce.ts +31 -0
- package/src/legacy/tool-ui/src/dom/onRTEReady.ts +105 -0
- package/src/legacy/tool-ui/src/dom/onRemove.ts +27 -0
- package/src/legacy/tool-ui/src/dom/onVisible.ts +29 -0
- package/src/legacy/tool-ui/src/dom/popupMenu.d.ts +6 -0
- package/src/legacy/tool-ui/src/dom/popupMenu.js +282 -0
- package/src/legacy/tool-ui/src/dom/previousUntil.ts +22 -0
- package/src/legacy/tool-ui/src/dropdown/README.md +9 -0
- package/src/legacy/tool-ui/src/dropdown/index.ts +105 -0
- package/src/legacy/tool-ui/src/form/CodeInput/CodeMirror.less +168 -0
- package/src/legacy/tool-ui/src/form/CodeInput/CodeMirror.ts +15 -0
- package/src/legacy/tool-ui/src/form/CodeInput/index.ts +86 -0
- package/src/legacy/tool-ui/src/form/FormFilter/README.md +31 -0
- package/src/legacy/tool-ui/src/form/FormFilter/index.ts +340 -0
- package/src/legacy/tool-ui/src/form/Input/index.less +43 -0
- package/src/legacy/tool-ui/src/form/Input/index.ts +92 -0
- package/src/legacy/tool-ui/src/form/SearchInput/README.md +23 -0
- package/src/legacy/tool-ui/src/form/SearchInput/index.ts +89 -0
- package/src/legacy/tool-ui/src/form/TextAreaInput/index.less +11 -0
- package/src/legacy/tool-ui/src/form/TextAreaInput/index.ts +36 -0
- package/src/legacy/tool-ui/src/graphql/GraphQL.ts +49 -0
- package/src/legacy/tool-ui/src/graphql/GraphQLPreview.ts +23 -0
- package/src/legacy/tool-ui/src/main/resources/settings.properties +1 -0
- package/src/legacy/tool-ui/src/main/webapp/WEB-INF/web.xml +81 -0
- package/src/legacy/tool-ui/src/main/webapp/script/bsp-uploader.js +170 -0
- package/src/legacy/tool-ui/src/main/webapp/script/bsp-utils.js +393 -0
- package/src/legacy/tool-ui/src/main/webapp/script/content/layout-element.js +141 -0
- package/src/legacy/tool-ui/src/main/webapp/script/input/query.js +78 -0
- package/src/legacy/tool-ui/src/main/webapp/script/input/workflow.js +718 -0
- package/src/legacy/tool-ui/src/main/webapp/script/jquery.extra.js +633 -0
- package/src/legacy/tool-ui/src/main/webapp/script/v3/Dropbox.js +18 -0
- package/src/legacy/tool-ui/src/main/webapp/script/v3/EditFieldUpdate.js +406 -0
- package/src/legacy/tool-ui/src/main/webapp/script/v3/EditFieldUpdateCache.js +1 -0
- package/src/legacy/tool-ui/src/main/webapp/script/v3/Notification.js +151 -0
- package/src/legacy/tool-ui/src/main/webapp/script/v3/content/edit.js +194 -0
- package/src/legacy/tool-ui/src/main/webapp/script/v3/content/state.js +785 -0
- package/src/legacy/tool-ui/src/main/webapp/script/v3/csrf.js +35 -0
- package/src/legacy/tool-ui/src/main/webapp/script/v3/dashboard.js +65 -0
- package/src/legacy/tool-ui/src/main/webapp/script/v3/input/dataTransfer.js +129 -0
- package/src/legacy/tool-ui/src/main/webapp/script/v3/input/file.js +433 -0
- package/src/legacy/tool-ui/src/main/webapp/script/v3/input/object.js +743 -0
- package/src/legacy/tool-ui/src/main/webapp/script/v3/input/read-only.js +17 -0
- package/src/legacy/tool-ui/src/main/webapp/script/v3/jquery.frame.js +478 -0
- package/src/legacy/tool-ui/src/main/webapp/script/v3/jquery.repeatable.js +2406 -0
- package/src/legacy/tool-ui/src/main/webapp/script/v3/plugin/popup.d.ts +2 -0
- package/src/legacy/tool-ui/src/main/webapp/script/v3/plugin/popup.js +446 -0
- package/src/legacy/tool-ui/src/main/webapp/script/v3/search-filters.js +62 -0
- package/src/legacy/tool-ui/src/main/webapp/script/v3/search.js +53 -0
- package/src/legacy/tool-ui/src/main/webapp/script/v3.js +1049 -0
- package/src/legacy/tool-ui/src/main/webapp/style/v3/ExternalPreviewFrame.less +6 -0
- package/src/legacy/tool-ui/src/main/webapp/style/v3/WidgetSearchAdvancedQuery.less +24 -0
- package/src/legacy/tool-ui/src/main/webapp/style/v3/element.less +11 -0
- package/src/legacy/tool-ui/src/main/webapp/style/v3/icon/ajax-loader.gif +0 -0
- package/src/legacy/tool-ui/src/main/webapp/style/v3/index.less +98 -0
- package/src/legacy/tool-ui/src/main/webapp/style/v3/repeatable.less +89 -0
- package/src/legacy/tool-ui/src/main/webapp/style/v3/variables.less +53 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ActionBar.less +44 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Admin.js +16 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Admin.less +253 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/AdobeStock.less +20 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Apis.less +43 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/AutoExpand.js +84 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/AutoExpand.less +36 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/AutoSubmit.js +68 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Avatar.less +30 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/BackgroundTasks.less +52 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Badge.less +10 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Board.less +311 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Bridge.js +536 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/BulletedList.less +17 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Button.less +105 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/CIGCluster.less +55 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Card.less +53 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Checkbox.less +99 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/CheckboxInput.js +22 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Collections.less +49 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ColorInput.js +5 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ColorInputSpectrum.js +107 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ColorInputSpectrum.less +319 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ComboInput.js +1491 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ComboInput.less +403 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/CommunityWidget.js +29 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/CommunityWidget.less +73 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Compat.less +7 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ContentEdit.js +2427 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ContentEdit.less +1512 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ContentEditDrawer.less +329 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ContentEditSites.less +5 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ContentForm.less +174 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ContentInputGroup.less +669 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ContentLock.js +470 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ContentReporting.js +32 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ContentReporting.less +39 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ContentSelector.less +268 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ContentSummary.less +238 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ContentTools.less +73 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ContentType.less +24 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/CopyToClipboard.less +20 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/DashboardRow.less +40 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/DashboardWidget.less +193 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/DataTable.js +31 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/DataTable.less +52 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/DateStringField.js +485 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/DateStringField.less +148 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/DateTimeInput.less +58 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Diff.less +322 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Downloads.css +11 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Draggable.less +42 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Dropdown.less +76 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/EmbeddedInputGroup.less +141 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Entry.js +264 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Entry.less +157 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ExternalItemAuth.js +16 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ExternalItemImport.less +3 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/FileInput.less +194 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Form.js +31 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/FormFilter.less +189 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Frame.less +26 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/GraphQLApis.less +4 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Grid.less +48 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Guide.less +209 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/GuideField.less +108 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Hierarchy.js +100 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Hierarchy.less +89 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Highlight.less +5 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Icon.less +53 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Icon.ts +49 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/IconButton.less +111 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ImageEditor.js +2403 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ImageEditor.less +922 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ImageEditorBundle.js +5 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Input.less +15 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/InputRow.less +35 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Label.less +12 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/LabeledCheckbox.less +35 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Link.less +93 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/LinkCarousel.js +40 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/LinkCarousel.less +143 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/LinkList.js +14 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/LinkList.less +150 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/LinkTable.js +123 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/LinkTable.less +112 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Location.js +19 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/LocationMap.js +148 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/LocationMap.less +62 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/LookingGlass.js +24 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/LookingGlass.less +30 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Message.js +14 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Message.less +189 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Month.less +102 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Notification.less +278 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/NumberBar.js +32 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/NumberBar.less +21 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/NumberedList.less +17 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Page.js +890 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Page.less +922 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/PaginatedResult.less +89 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Pagination.less +80 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Popup.less +415 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/PrePublish.less +5 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Preview.js +758 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Preview.less +495 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/PreviewEditor.js +86 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/PreviewOverlay.js +1005 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ProfileDropdown.less +66 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/PubSub.js +47 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/QueryField.js +211 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/QueryField.less +147 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/RadialProgressBar.less +20 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/RegionMap.js +215 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Repeatable.less +102 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/RepeatableContentInputGroup.js +160 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/RepeatableContentInputGroup.less +731 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/RepeatableContentSelector.less +296 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/RepeatableTextInput.less +156 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Reset.less +373 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Revision.less +50 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/RichText.less +32 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/RichTextEditor.js +154 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/RichTextEditor.less +197 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/SearchFields.js +281 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/SearchFields.less +403 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/SearchInput.less +65 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/SearchResult.js +255 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/SearchResult.less +479 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/SearchWidget.less +697 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/SearchWidgetAdvanced.less +115 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/SharePreview.js +56 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/SharePreview.less +95 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Sortable.js +874 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Sortable.less +242 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/StandardForm.less +12 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/StyleEmbeddedContent.js +100 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/StyleEmbeddedContent.less +131 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/StyleguidePresets.js +357 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Suggestions.less +121 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/TabBar.less +258 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/TabContainer.js +360 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/TabContainer.less +26 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Taxonomy.js +27 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Taxonomy.less +84 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Text.less +41 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/TextInput.less +32 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ThemeBundleEditor.js +224 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ThemeBundleEditor.less +106 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/TimedContent.js +147 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/TimedContent.less +202 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/TimedContentBundle.js +8 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/TreeList.less +93 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Utils.css +14 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/VideoEditor.js +2417 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/VideoEditor.less +737 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/VideoEditorBundle.js +8 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ViewMirror.js +52 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ViewMirror.less +42 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ViewPreview.d.ts +13 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ViewPreview.js +177 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/ViewPreview.less +147 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Viewers.less +70 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Week.less +32 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Widget.js +90 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Widget.less +224 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/Workflow.less +153 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__mocks__/fileMock.js +1 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__mocks__/styleMock.js +1 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__mocks__/textArea.mock.js +20 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/globals.js +770 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/ProseMirror.test.js +16 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/index.html +54 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/comment_manager/CommentManager.test.js +29 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/comment_manager/index.html +35 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/custom_keyboard/CustomKeyboard.js +42 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/custom_keyboard/index.html +37 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/enhancement_manager/EnhancementManager.test.js +288 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/enhancement_manager/block.html +38 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/enhancement_manager/inline.html +38 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/enhancement_manager/no-popups.html +38 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/list_manager/ListManager.js +257 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/list_manager/index.html +38 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/menubar/hierarchal.html +33 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/menubar/index.html +33 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/menubar/menubar.test.js +195 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/menubar/small.html +34 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/menubar/tags.html +34 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/placeholder_manager/PlaceholderManager.test.js +134 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/placeholder_manager/has-editable-placeholder.html +32 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/placeholder_manager/has-text.html +34 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/placeholder_manager/index.html +31 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/table_manager/TableManager.test.js +63 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/table_manager/existing.html +48 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/track_manager/TrackManager.test.js +291 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/track_manager/existing.html +39 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/integration/rte/plugins/track_manager/insert.html +37 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/Sortable.test.js +105 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/rte/ProseMirror.test.js +41 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/rte/codemirror-shim.test.js +72 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/rte/plugins/collab_manager/CollabManager.test.js +46 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/rte/plugins/enhancement_manager/EnhancementManager.test.js +84 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/rte/plugins/list_manager/ListManager.test.js +54 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/rte/plugins/menubar/menubar.test.js +183 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/rte/plugins/spellcheck/SpellCheck.test.js +45 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/rte/settings/BSSerializer.test.js +346 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/rte/settings/menuItemsBuilder.test.js +226 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/__tests__/unit/rte/utilities.test.js +118 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/appetizeio/Appetizeio.js +5 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/appetizeio/AppetizeioEmbedded.js +113 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/appetizeio/AppetizeioEmbedded.less +77 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/bsp-rings-bg.svg +9 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/compat/Fetch.js +16 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/compat/jquery.js +32 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/compat/requirejs.js +13 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/dom/Tether.js +1 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/dom/TetherLayout.js +1 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/dom/closest.js +1 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/dom/create.js +1 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/dom/find.js +1 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/dom/findAll.js +1 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/dom/ifClick.js +1 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/dom/ifMatches.js +1 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/dom/ifUnmodified.js +1 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/dom/index.js +5 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/dom/insertBefore.js +1 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/dom/insertFirst.js +1 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/dom/insertLast.js +1 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/dom/onFind.js +1 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/dom/onFindOnce.js +1 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/dom/onRTEReady.js +1 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/dom/onRemove.js +1 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/dom/onVisible.js +1 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/dom/previousUntil.js +1 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/dom/resolveIconCompat.js +40 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/graphql/GraphQL.less +171 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/mail-publishing/MailPublishing.less +38 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rtc/Socket.js +1 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rtc/index.js +1 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/Mention.less +35 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/ProseMirror.js +909 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/ProseMirror.less +759 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/ProseMirrorContainer.less +29 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/README.md +68 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/codemirror-shim.d.ts +8 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/codemirror-shim.js +274 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/collab-workflow.jpeg +0 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/interchangeable.ts +250 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/mention.js +90 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/PluginProvider.js +124 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/README.md +46 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/ai_inline_manager/AIInlineManager.ts +124 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/ai_inline_manager/views/AIInline.less +244 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/ai_inline_manager/views/AIInlineView.ts +1019 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/ai_manager/AiManager.ts +199 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/collab_manager/CollabManager.js +339 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/collab_manager/views/AvatarView.js +96 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/collab_manager/views/AvatarView.less +11 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/collab_manager/views/CollabEditing.less +18 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/comment_manager/CommentManager.js +348 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/custom_keyboard/CustomKeyboard.js +110 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/custom_keyboard/CustomKeyboard.less +21 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/custom_keyboard/README.md +29 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/EnhancementManager.js +428 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/README.md +63 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/commands.js +690 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/constants.js +12 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/enhancement-creation.jpeg +0 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/index.js +15 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/rte-flow.jpeg +0 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/views/ActionButtonView.js +86 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/views/BlockSubmenuView.js +60 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/views/Enhancement.less +67 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/views/EnhancementView.js +208 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/views/PreviewView.js +102 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/views/PreviewView.less +45 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/views/ProsemirrorEnhancementMenu.less +113 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/enhancement_manager/views/SubmenuView.js +365 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/find_replace_manager/FindReplaceManager.js +239 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/find_replace_manager/views/FindView.js +604 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/find_replace_manager/views/ProseMirrorFindReplace.less +174 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/fullscreen_manager/FullscreenManager.js +57 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/fullscreen_manager/README.md +26 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/fullscreen_manager/commands.js +16 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/fullscreen_manager/index.js +4 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/fullscreen_manager/views/FullscreenView.js +474 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/fullscreen_manager/views/FullscreenView.less +383 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/html_editor_manager/htmlEditorManager.js +66 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/html_editor_manager/views/HtmlEditorView.js +97 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/list_manager/ListManager.js +342 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/list_manager/ListManager.less +42 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/list_manager/README.md +50 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/list_manager/commands.js +207 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/list_manager/constants.js +26 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/list_manager/index.js +4 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/menubar/Menubar.js +485 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/menubar/README.md +40 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/menubar/views/MenuView.js +842 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/menubar/views/MenuView.less +821 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/paste_manager/PasteManager.js +368 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/paste_manager/views/PastePopup.less +5 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/placeholder_manager/PlaceHolderManager.js +128 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/raw_text_manager/README.md +13 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/raw_text_manager/RawTextManager.js +96 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/spellcheck/index.js +3 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/spellcheck/spellcheck-plugin.js +280 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/spellcheck/spellcheck-service.js +94 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/spellcheck/spellcheck.less +73 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/table_manager/TableManager.js +57 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/table_manager/commands.js +97 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/table_manager/views/ProseMirror-table.less +137 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/table_manager/views/TableSizerPopup.less +19 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/table_manager/views/TableSizerView.js +88 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/table_manager/views/TableView.js +613 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/track_manager/README.md +13 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/track_manager/TrackManager.js +905 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/settings/BSSerializer.js +819 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/settings/README.md +80 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/settings/commands.js +98 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/settings/constants.d.ts +84 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/settings/constants.js +87 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/settings/index.js +13 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/settings/keymapBuilder.js +223 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/settings/menuItemsBuilder.js +559 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/settings/schemaBuilder.js +1281 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/utilities.d.ts +4 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/rte/utilities.js +359 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/theme/ColorRotator.js +1 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/theme/Theme.less +224 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/translation/Translation.less +114 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/util/debounce.js +1 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/util/getComponentKey.js +1 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/util/noise.js +1 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/util/repaint.js +1 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/util/storage.js +1 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/util/throttle.js +1 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/AnalyticsWidget.less +18 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/AssignmentContent.js +33 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/AssignmentContent.less +19 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/AssignmentDeskDashboard.js +217 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/AssignmentDeskDashboard.less +410 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/AssignmentDeskRelatedWidget.less +3 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/AssignmentFilters.less +23 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/AssociatedContentWidget.js +7 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/AssociatedContentWidget.less +92 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/BulkUpload.js +19 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/BulkUpload.less +65 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/BulkWorkflow.less +103 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/Calendar.js +7 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/Calendar.less +109 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/CalendarAccessOverview.less +26 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/CalendarEventSummary.less +15 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/ClosableWindow.js +13 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/ClosableWindow.less +3 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/ContentColors.less +17 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/ContentTemplatesWidget.less +123 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/Conversation.less +537 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/CopySiteWidget.less +13 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/FormSubmission.less +3 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/LiveBlog.less +158 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/PitchAssignments.js +25 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/PitchAssignments.less +16 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/PitchContent.js +33 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/PitchContent.less +10 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/Revisions.js +61 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/Revisions.less +280 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/SavedSearch.less +13 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/SemRushDashboardWidget.less +6 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/SlackAuthentication.less +13 -0
- package/src/legacy/tool-ui/src/main/webapp/v4/widget/WorkStreams.less +39 -0
- package/src/legacy/tool-ui/src/platform.ts +25 -0
- package/src/legacy/tool-ui/src/rtc/Socket.ts +205 -0
- package/src/legacy/tool-ui/src/rtc/index.ts +276 -0
- package/src/legacy/tool-ui/src/table/README.md +3 -0
- package/src/legacy/tool-ui/src/table/index.ts +107 -0
- package/src/legacy/tool-ui/src/theme/ColorRotator.ts +32 -0
- package/src/legacy/tool-ui/src/theme/Theme.ts +516 -0
- package/src/legacy/tool-ui/src/theme/index.ts +7 -0
- package/src/legacy/tool-ui/src/util/README.md +42 -0
- package/src/legacy/tool-ui/src/util/debounce.ts +22 -0
- package/src/legacy/tool-ui/src/util/getComponentKey.ts +25 -0
- package/src/legacy/tool-ui/src/util/noise.ts +51 -0
- package/src/legacy/tool-ui/src/util/repaint.ts +17 -0
- package/src/legacy/tool-ui/src/util/storage.ts +21 -0
- package/src/legacy/tool-ui/src/util/string.ts +15 -0
- package/src/legacy/tool-ui/src/util/svg.ts +46 -0
- package/src/legacy/tool-ui/src/util/throttle.ts +37 -0
- package/src/legacy/tool-ui/src/util/transition.ts +5 -0
- package/src/legacy/tool-ui/src/v5.css +149 -0
- package/src/legacy/tool-ui/src/v5.ts +2059 -0
- package/src/legacy/tool-ui/src/widget/README.md +27 -0
- package/src/legacy/tool-ui/src/widget/index.ts +160 -0
- package/dist/storybook/assets/if-defined-C1_PRAyA.js +0 -1
|
@@ -0,0 +1,2417 @@
|
|
|
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
|
+
})
|