From 1f905e5a801dddc6ba2d6acd0eeafeef3017311f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kurf=C3=BCrst?= Date: Mon, 21 Jun 2021 21:08:34 +0200 Subject: [PATCH] FEATURE: Add IFrame Media Chooser API In Flow applications or "traditional" Neos modules which are not implemented with React, you still sometimes need to choose assets. The Media UI provides an extra entry point to allow this. The process works roughly as follows: 1) You open an iFrame, where you display the `IFrameMediaChooser` Controller. 2) The user can pick an image. 3) When the image is chosen, a JavaScript callback function in your outer frame (at location `window.NeosMediaBrowserCallbacks.assetChosen`) is called. --- .../IframeMediaChooserController.php | 52 +++++++++++++++++ Classes/Eel/MediaChooserNeosConfigHelper.php | 57 +++++++++++++++++++ Configuration/Policy.yaml | 2 + Configuration/Routes.yaml | 7 +++ Configuration/Settings.Neos.yaml | 4 ++ Readme.md | 44 ++++++++++++++ Resources/Private/Fusion/Root.fusion | 1 + .../Views/IframeMediaChooserController.fusion | 42 ++++++++++++++ Resources/Private/Fusion/Views/Index.fusion | 46 ++++++++------- .../JavaScript/media-module/src/index.tsx | 18 ++++++ 10 files changed, 251 insertions(+), 22 deletions(-) create mode 100644 Classes/Controller/IframeMediaChooserController.php create mode 100644 Classes/Eel/MediaChooserNeosConfigHelper.php create mode 100644 Resources/Private/Fusion/Views/IframeMediaChooserController.fusion diff --git a/Classes/Controller/IframeMediaChooserController.php b/Classes/Controller/IframeMediaChooserController.php new file mode 100644 index 000000000..3f110e525 --- /dev/null +++ b/Classes/Controller/IframeMediaChooserController.php @@ -0,0 +1,52 @@ + FusionView::class, + ]; + + /** + * Renders the media chooser + */ + public function indexAction(): void + { + } +} diff --git a/Classes/Eel/MediaChooserNeosConfigHelper.php b/Classes/Eel/MediaChooserNeosConfigHelper.php new file mode 100644 index 000000000..d9f9b81b5 --- /dev/null +++ b/Classes/Eel/MediaChooserNeosConfigHelper.php @@ -0,0 +1,57 @@ +userService->getInterfaceLanguage(); + } + + public function backendXliffCacheVersion() + { + return $this->xliffService->getCacheVersion(); + } +} diff --git a/Configuration/Policy.yaml b/Configuration/Policy.yaml index 278f2c68b..34a7f2e9c 100644 --- a/Configuration/Policy.yaml +++ b/Configuration/Policy.yaml @@ -4,6 +4,8 @@ privilegeTargets: matcher: 'method(public Flowpack\Media\Ui\Controller\MediaController->(.*)Action())' 'Flowpack.Media.Ui:Queries': matcher: 'method(public Flowpack\Media\Ui\GraphQL\Resolver\(.*)Resolver->.*()) || method(t3n\GraphQL\Controller\GraphQLController->queryAction())' + 'Flowpack.Media.Ui:IframeMediaChooser': + matcher: 'method(public Flowpack\Media\Ui\Controller\IframeMediaChooserController->(.*)Action())' 'Neos\Neos\Security\Authorization\Privilege\ModulePrivilege': 'Flowpack.Media.Ui:Backend.Module.Management.Media': diff --git a/Configuration/Routes.yaml b/Configuration/Routes.yaml index ce99b0e77..c0256ab9b 100644 --- a/Configuration/Routes.yaml +++ b/Configuration/Routes.yaml @@ -5,3 +5,10 @@ package: 't3n.GraphQL' variables: 'endpoint': 'media-assets' + +- name: 'IFrame Media Chooser' + uriPattern: 'neos/media/asset-choice' + defaults: + '@package': 'Flowpack.Media.Ui' + '@controller': 'IframeMediaChooser' + '@action': 'index' diff --git a/Configuration/Settings.Neos.yaml b/Configuration/Settings.Neos.yaml index b4b7efa09..07640afff 100644 --- a/Configuration/Settings.Neos.yaml +++ b/Configuration/Settings.Neos.yaml @@ -34,3 +34,7 @@ Neos: stylesheets: Flowpack.Media.Ui:AssetEditor: resource: resource://Flowpack.Media.Ui/Public/AssetEditor/Plugin.css + + Fusion: + defaultContext: + 'FlowpackMediaUi.MediaChooserNeosConfig': 'Flowpack\Media\Ui\Eel\MediaChooserNeosConfigHelper' \ No newline at end of file diff --git a/Readme.md b/Readme.md index c04b1a813..91e4bb40d 100644 --- a/Readme.md +++ b/Readme.md @@ -88,6 +88,50 @@ Neos: showSimilarAssets: true ``` +### choose assets by rendering media selection iFrame + +In Flow applications or "traditional" Neos modules which are not implemented with React, you still sometimes +need to choose assets. The Media UI provides an extra entry point to allow this. The process works roughly as follows: + +1) You open an iFrame, where you display the `IFrameMediaChooser` Controller. + +2) The user can pick an image. + +3) When the image is chosen, a JavaScript callback function in your outer frame (at location `window.NeosMediaBrowserCallbacks.assetChosen`) + is called. + + +**How to set this up in detail?** + +1) You need to grant the `Flowpack.Media.Ui:IframeMediaChooser` privilegeTarget if you want to use this feature. + Add the following to your `Policy.yaml`, and adjust the role names as you need it for your use-case: + + ```yaml + roles: + 'Neos.Neos:AbstractEditor': + privileges: + - + privilegeTarget: 'Flowpack.Media.Ui:IframeMediaChooser' + permission: GRANT + ``` + +2) Define the callback function `window.NeosMediaBrowserCallbacks.assetChosen(assetId)`: + + ```js + window.NeosMediaBrowserCallbacks = { + assetChosen: (assetId) => { + // do whatever you need to do here + } + } + ``` + +3) Open the `IFrameMediaChooser` controller in an iframe. To generate the URL to open, + you can use the following `UriBuilder` invocation: + + ```php + return $uriBuilder->uriFor('index', [], 'IframeMediaChooser', 'Flowpack.Media.Ui'); + ``` + ## Architecture ### API / GraphQL diff --git a/Resources/Private/Fusion/Root.fusion b/Resources/Private/Fusion/Root.fusion index a92a174cc..febbffaf1 100644 --- a/Resources/Private/Fusion/Root.fusion +++ b/Resources/Private/Fusion/Root.fusion @@ -1 +1,2 @@ +include: resource://Neos.Fusion/Private/Fusion/Root.fusion include: **/* diff --git a/Resources/Private/Fusion/Views/IframeMediaChooserController.fusion b/Resources/Private/Fusion/Views/IframeMediaChooserController.fusion new file mode 100644 index 000000000..525b0746a --- /dev/null +++ b/Resources/Private/Fusion/Views/IframeMediaChooserController.fusion @@ -0,0 +1,42 @@ +// This is the rendering of the stand-alone Iframe Media Selection mode. +// It emulates the Fluid-based backend layout and calls the Neos JS Bootstrap, +// so that the Media Selection UI can access e.g. the Neos translations as usual. +// +// This whole file is a WORKAROUND and should only be used until we can move some of this +// into the Neos Core. +Flowpack.Media.Ui.IframeMediaChooserController { + index = Neos.Fusion:Component { + mediaModule = Flowpack.Media.Ui:MediaModule + + _neosJavascriptBasePath = ${StaticResource.uri('Neos.Neos', 'Public/JavaScript')} + _initScript = ${''} + + _xliffAsJsonUri = Neos.Fusion:UriBuilder { + package = 'Neos.Neos' + controller = 'Backend\\Backend' + action = 'xliffAsJson' + arguments { + locale = ${FlowpackMediaUi.MediaChooserNeosConfig.backendInterfaceLanguage()} + version = ${FlowpackMediaUi.MediaChooserNeosConfig.backendXliffCacheVersion()} + } + absolute = true + } + + renderer = afx` + + + + + + + + + {props.mediaModule} + {props._initScript} + + + + + ` + } +} diff --git a/Resources/Private/Fusion/Views/Index.fusion b/Resources/Private/Fusion/Views/Index.fusion index 80a4d2507..d0c2a3f16 100644 --- a/Resources/Private/Fusion/Views/Index.fusion +++ b/Resources/Private/Fusion/Views/Index.fusion @@ -1,27 +1,29 @@ -Flowpack.Media.Ui.MediaController { - index = Neos.Fusion:Component { - data-endpoints = Neos.Fusion:DataStructure { - graphql = '/neos/graphql/media-assets' - upload = Neos.Fusion:UriBuilder { - package = 'Flowpack.Media.Ui' - controller = 'Upload' - action = 'upload' - format = 'json' - } - @process.stringify = ${Json.stringify(value)} +prototype(Flowpack.Media.Ui:MediaModule) < prototype(Neos.Fusion:Component) { + data-endpoints = Neos.Fusion:DataStructure { + graphql = '/neos/graphql/media-assets' + upload = Neos.Fusion:UriBuilder { + package = 'Flowpack.Media.Ui' + controller = 'Upload' + action = 'upload' + format = 'json' } + @process.stringify = ${Json.stringify(value)} + } - data-dummy-image = Neos.Fusion:ResourceUri { - path = 'resource://Neos.Neos/Public/Images/dummy-image.svg' - } + data-dummy-image = Neos.Fusion:ResourceUri { + path = 'resource://Neos.Neos/Public/Images/dummy-image.svg' + } - data-features = ${Configuration.setting('Neos.Neos.Ui.frontendConfiguration')['Flowpack.Media.Ui']} - data-features.@process.stringify = ${Json.stringify(value)} + data-features = ${Configuration.setting('Neos.Neos.Ui.frontendConfiguration')['Flowpack.Media.Ui']} + data-features.@process.stringify = ${Json.stringify(value)} - renderer = afx` -
- Loading media ui... -
- ` - } + renderer = afx` +
+ Loading media ui... +
+ ` +} + +Flowpack.Media.Ui.MediaController { + index = Flowpack.Media.Ui:MediaModule } diff --git a/Resources/Private/JavaScript/media-module/src/index.tsx b/Resources/Private/JavaScript/media-module/src/index.tsx index f202a2a8d..ff9d96029 100644 --- a/Resources/Private/JavaScript/media-module/src/index.tsx +++ b/Resources/Private/JavaScript/media-module/src/index.tsx @@ -79,6 +79,22 @@ window.onload = async (): Promise => { const AppWithHmr = hot(module)(App); + // If being called inside an iframe to choose an asset (as described in the section + // "choose assets by rendering media selection iFrame" in the main README.md), + // we need to set the onAssetSelection callback and set the selectionMode. + // + // To determine whether we are in this mode or not, we check the following things: + // - are we called from within an iframe? + // - on the PARENT frame, is window.NeosMediaBrowserCallbacks.assetChosen implemented and callable? + let selectionModeProps = {}; + if (window.parent && window.parent['NeosMediaBrowserCallbacks'] && window.parent['NeosMediaBrowserCallbacks']['assetChosen']) { + const NeosMediaBrowserCallbacks = window.parent['NeosMediaBrowserCallbacks']; + selectionModeProps = { + onAssetSelection: (localAssetIdentifier) => NeosMediaBrowserCallbacks.assetChosen(localAssetIdentifier), + selectionMode: true + }; + } + render( @@ -89,6 +105,8 @@ window.onload = async (): Promise => { dummyImage={dummyImage} containerRef={containerRef} featureFlags={featureFlags} + + {...selectionModeProps} >