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} >