Skip to content

Commit e8e9203

Browse files
committed
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.
1 parent 720cf90 commit e8e9203

File tree

11 files changed

+251
-27
lines changed

11 files changed

+251
-27
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Flowpack\Media\Ui\Controller;
6+
7+
/*
8+
* This file is part of the Flowpack.Media.Ui package.
9+
*
10+
* (c) Contributors of the Neos Project - www.neos.io
11+
*
12+
* This package is Open Source Software. For the full copyright and license
13+
* information, please view the LICENSE file which was distributed with this
14+
* source code.
15+
*/
16+
17+
use Neos\Flow\Annotations as Flow;
18+
use Neos\Fusion\View\FusionView;
19+
use Neos\Neos\Controller\Module\AbstractModuleController;
20+
21+
/**
22+
* This is the entry point for choosing Media by opening an iframe. This is useful for
23+
* Flow applications or "traditional" non-React Neos Backend Modules.
24+
*
25+
* @Flow\Scope("singleton")
26+
*/
27+
class IframeMediaChooserController extends AbstractModuleController
28+
{
29+
/**
30+
* @var FusionView
31+
*/
32+
protected $view;
33+
34+
/**
35+
* @var string
36+
*/
37+
protected $defaultViewObjectName = FusionView::class;
38+
39+
/**
40+
* @var array
41+
*/
42+
protected $viewFormatToObjectNameMap = [
43+
'html' => FusionView::class,
44+
];
45+
46+
/**
47+
* Renders the media chooser
48+
*/
49+
public function indexAction(): void
50+
{
51+
}
52+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Flowpack\Media\Ui\Eel;
5+
6+
/*
7+
* This file is part of the Flowpack.Media.Ui package.
8+
*
9+
* (c) Contributors of the Neos Project - www.neos.io
10+
*
11+
* This package is Open Source Software. For the full copyright and license
12+
* information, please view the LICENSE file which was distributed with this
13+
* source code.
14+
*/
15+
16+
use Neos\Flow\Annotations as Flow;
17+
use Neos\Eel\ProtectedContextAwareInterface;
18+
use Neos\Neos\Service\UserService;
19+
use Neos\Neos\Service\XliffService;
20+
21+
/**
22+
* This helper is used inside the view rendering of {@see IframeMediaChooserController},
23+
* to set up a HTML environment as close as possible to the the Fluid-based backend modules.
24+
*
25+
* This is a workaround and should NOT be relied upon externally.
26+
*
27+
* @internal
28+
*/
29+
class MediaChooserNeosConfigHelper implements ProtectedContextAwareInterface
30+
{
31+
/**
32+
* @Flow\Inject
33+
* @var UserService
34+
*/
35+
protected $userService;
36+
37+
/**
38+
* @Flow\Inject
39+
* @var XliffService
40+
*/
41+
protected $xliffService;
42+
43+
public function allowsCallOfMethod($methodName)
44+
{
45+
return true;
46+
}
47+
48+
public function backendInterfaceLanguage()
49+
{
50+
return $this->userService->getInterfaceLanguage();
51+
}
52+
53+
public function backendXliffCacheVersion()
54+
{
55+
return $this->xliffService->getCacheVersion();
56+
}
57+
}

Configuration/Policy.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ privilegeTargets:
44
matcher: 'method(public Flowpack\Media\Ui\Controller\MediaController->(.*)Action())'
55
'Flowpack.Media.Ui:Queries':
66
matcher: 'method(public Flowpack\Media\Ui\GraphQL\Resolver\(.*)Resolver->.*()) || method(t3n\GraphQL\Controller\GraphQLController->queryAction())'
7+
'Flowpack.Media.Ui:IframeMediaChooser':
8+
matcher: 'method(public Flowpack\Media\Ui\Controller\IframeMediaChooserController->(.*)Action())'
79

810
'Neos\Neos\Security\Authorization\Privilege\ModulePrivilege':
911
'Flowpack.Media.Ui:Backend.Module.Management.Media':

Configuration/Routes.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,10 @@
55
package: 't3n.GraphQL'
66
variables:
77
'endpoint': 'media-assets'
8+
9+
- name: 'IFrame Media Chooser'
10+
uriPattern: 'neos/media/asset-choice'
11+
defaults:
12+
'@package': 'Flowpack.Media.Ui'
13+
'@controller': 'IframeMediaChooser'
14+
'@action': 'index'

Configuration/Settings.GraphQL.yaml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,3 @@ t3n:
1010
resolverPathPattern: 'Flowpack\Media\Ui\GraphQL\Resolver\Type\{Type}Resolver'
1111
resolvers:
1212
AssetProxy: 'Flowpack\Media\Ui\GraphQL\Resolver\Type\AssetResolver'
13-
assetUsage:
14-
typeDefs: 'resource://Flowpack.Media.Ui/Private/GraphQL/schema.root.graphql'
15-
resolverPathPattern: 'Flowpack\Media\Ui\GraphQL\Resolver\Type\{Type}Resolver'
16-
resolvers:
17-
AssetProxy: 'Flowpack\Media\Ui\GraphQL\Resolver\Type\AssetResolver'

Configuration/Settings.Neos.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,7 @@ Neos:
3434
stylesheets:
3535
Flowpack.Media.Ui:AssetEditor:
3636
resource: resource://Flowpack.Media.Ui/Public/AssetEditor/Plugin.css
37+
38+
Fusion:
39+
defaultContext:
40+
'FlowpackMediaUi.MediaChooserNeosConfig': 'Flowpack\Media\Ui\Eel\MediaChooserNeosConfigHelper'

Readme.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,50 @@ Neos:
8888
showSimilarAssets: true
8989
```
9090

91+
### choose assets by rendering media selection iFrame
92+
93+
In Flow applications or "traditional" Neos modules which are not implemented with React, you still sometimes
94+
need to choose assets. The Media UI provides an extra entry point to allow this. The process works roughly as follows:
95+
96+
1) You open an iFrame, where you display the `IFrameMediaChooser` Controller.
97+
98+
2) The user can pick an image.
99+
100+
3) When the image is chosen, a JavaScript callback function in your outer frame (at location `window.NeosMediaBrowserCallbacks.assetChosen`)
101+
is called.
102+
103+
104+
**How to set this up in detail?**
105+
106+
1) You need to grant the `Flowpack.Media.Ui:IframeMediaChooser` privilegeTarget if you want to use this feature.
107+
Add the following to your `Policy.yaml`, and adjust the role names as you need it for your use-case:
108+
109+
```yaml
110+
roles:
111+
'Neos.Neos:AbstractEditor':
112+
privileges:
113+
-
114+
privilegeTarget: 'Flowpack.Media.Ui:IframeMediaChooser'
115+
permission: GRANT
116+
```
117+
118+
2) Define the callback function `window.NeosMediaBrowserCallbacks.assetChosen(assetId)`:
119+
120+
```js
121+
window.NeosMediaBrowserCallbacks = {
122+
assetChosen: (assetId) => {
123+
// do whatever you need to do here
124+
}
125+
}
126+
```
127+
128+
3) Open the `IFrameMediaChooser` controller in an iframe. To generate the URL to open,
129+
you can use the following `UriBuilder` invocation:
130+
131+
```php
132+
return $uriBuilder->uriFor('index', [], 'IframeMediaChooser', 'Flowpack.Media.Ui');
133+
```
134+
91135
## Architecture
92136

93137
### API / GraphQL
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
include: resource://Neos.Fusion/Private/Fusion/Root.fusion
12
include: **/*
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// This is the rendering of the stand-alone Iframe Media Selection mode.
2+
// It emulates the Fluid-based backend layout and calls the Neos JS Bootstrap,
3+
// so that the Media Selection UI can access e.g. the Neos translations as usual.
4+
//
5+
// This whole file is a WORKAROUND and should only be used until we can move some of this
6+
// into the Neos Core.
7+
Flowpack.Media.Ui.IframeMediaChooserController {
8+
index = Neos.Fusion:Component {
9+
mediaModule = Flowpack.Media.Ui:MediaModule
10+
11+
_neosJavascriptBasePath = ${StaticResource.uri('Neos.Neos', 'Public/JavaScript')}
12+
_initScript = ${'<script>window.NeosCMS = {}; window.NeosCMS.Configuration = {}; window.NeosCMS.Configuration.neosJavascriptBasePath = ' + Json.stringify(this._neosJavascriptBasePath) + ';</script>'}
13+
14+
_xliffAsJsonUri = Neos.Fusion:UriBuilder {
15+
package = 'Neos.Neos'
16+
controller = 'Backend\\Backend'
17+
action = 'xliffAsJson'
18+
arguments {
19+
locale = ${FlowpackMediaUi.MediaChooserNeosConfig.backendInterfaceLanguage()}
20+
version = ${FlowpackMediaUi.MediaChooserNeosConfig.backendXliffCacheVersion()}
21+
}
22+
absolute = true
23+
}
24+
25+
renderer = afx`
26+
<html>
27+
<head>
28+
<link rel="stylesheet" href={StaticResource.uri('Neos.Neos', 'Public/Styles/Minimal.css')} />
29+
<link rel="stylesheet" href={StaticResource.uri('Flowpack.Media.Ui', 'Public/Assets/main.bundle.css')} />
30+
31+
<link rel="neos-xliff" href={props._xliffAsJsonUri} />
32+
</head>
33+
<body class="neos neos-module" style="background-color: #222">
34+
{props.mediaModule}
35+
{props._initScript}
36+
<script src={StaticResource.uri('Neos.Neos', 'Public/JavaScript/Main.min.js')}></script>
37+
<script src={StaticResource.uri('Flowpack.Media.Ui', 'Public/Assets/main.bundle.js')}></script>
38+
</body>
39+
</html>
40+
`
41+
}
42+
}
Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,29 @@
1-
Flowpack.Media.Ui.MediaController {
2-
index = Neos.Fusion:Component {
3-
data-endpoints = Neos.Fusion:DataStructure {
4-
graphql = '/neos/graphql/media-assets'
5-
upload = Neos.Fusion:UriBuilder {
6-
package = 'Flowpack.Media.Ui'
7-
controller = 'Upload'
8-
action = 'upload'
9-
format = 'json'
10-
}
11-
@process.stringify = ${Json.stringify(value)}
1+
prototype(Flowpack.Media.Ui:MediaModule) < prototype(Neos.Fusion:Component) {
2+
data-endpoints = Neos.Fusion:DataStructure {
3+
graphql = '/neos/graphql/media-assets'
4+
upload = Neos.Fusion:UriBuilder {
5+
package = 'Flowpack.Media.Ui'
6+
controller = 'Upload'
7+
action = 'upload'
8+
format = 'json'
129
}
10+
@process.stringify = ${Json.stringify(value)}
11+
}
1312

14-
data-dummy-image = Neos.Fusion:ResourceUri {
15-
path = 'resource://Neos.Neos/Public/Images/dummy-image.svg'
16-
}
13+
data-dummy-image = Neos.Fusion:ResourceUri {
14+
path = 'resource://Neos.Neos/Public/Images/dummy-image.svg'
15+
}
1716

18-
data-features = ${Configuration.setting('Neos.Neos.Ui.frontendConfiguration')['Flowpack.Media.Ui']}
19-
[email protected] = ${Json.stringify(value)}
17+
data-features = ${Configuration.setting('Neos.Neos.Ui.frontendConfiguration')['Flowpack.Media.Ui']}
18+
[email protected] = ${Json.stringify(value)}
2019

21-
renderer = afx`
22-
<div id="media-ui-app" {...props}>
23-
Loading media ui...
24-
</div>
25-
`
26-
}
20+
renderer = afx`
21+
<div id="media-ui-app" {...props}>
22+
Loading media ui...
23+
</div>
24+
`
25+
}
26+
27+
Flowpack.Media.Ui.MediaController {
28+
index = Flowpack.Media.Ui:MediaModule
2729
}

0 commit comments

Comments
 (0)