Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions Classes/Controller/IframeMediaChooserController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

declare(strict_types=1);

namespace Flowpack\Media\Ui\Controller;

/*
* This file is part of the Flowpack.Media.Ui package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

use Neos\Flow\Annotations as Flow;
use Neos\Fusion\View\FusionView;
use Neos\Neos\Controller\Module\AbstractModuleController;

/**
* This is the entry point for choosing Media by opening an iframe. This is useful for
* Flow applications or "traditional" non-React Neos Backend Modules.
*
* @Flow\Scope("singleton")
*/
class IframeMediaChooserController extends AbstractModuleController
{
/**
* @var FusionView
*/
protected $view;

/**
* @var string
*/
protected $defaultViewObjectName = FusionView::class;

/**
* @var array
*/
protected $viewFormatToObjectNameMap = [
'html' => FusionView::class,
];

/**
* Renders the media chooser
*/
public function indexAction(): void
{
}
}
57 changes: 57 additions & 0 deletions Classes/Eel/MediaChooserNeosConfigHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);

namespace Flowpack\Media\Ui\Eel;

/*
* This file is part of the Flowpack.Media.Ui package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

use Neos\Flow\Annotations as Flow;
use Neos\Eel\ProtectedContextAwareInterface;
use Neos\Neos\Service\UserService;
use Neos\Neos\Service\XliffService;

/**
* This helper is used inside the view rendering of {@see IframeMediaChooserController},
* to set up a HTML environment as close as possible to the the Fluid-based backend modules.
*
* This is a workaround and should NOT be relied upon externally.
*
* @internal
*/
class MediaChooserNeosConfigHelper implements ProtectedContextAwareInterface
{
/**
* @Flow\Inject
* @var UserService
*/
protected $userService;

/**
* @Flow\Inject
* @var XliffService
*/
protected $xliffService;

public function allowsCallOfMethod($methodName)
{
return true;
}

public function backendInterfaceLanguage()
{
return $this->userService->getInterfaceLanguage();
}

public function backendXliffCacheVersion()
{
return $this->xliffService->getCacheVersion();
}
}
2 changes: 2 additions & 0 deletions Configuration/Policy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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':
Expand Down
7 changes: 7 additions & 0 deletions Configuration/Routes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
4 changes: 4 additions & 0 deletions Configuration/Settings.Neos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
44 changes: 44 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions Resources/Private/Fusion/Root.fusion
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
include: resource://Neos.Fusion/Private/Fusion/Root.fusion
include: **/*
42 changes: 42 additions & 0 deletions Resources/Private/Fusion/Views/IframeMediaChooserController.fusion
Original file line number Diff line number Diff line change
@@ -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 = ${'<script>window.NeosCMS = {}; window.NeosCMS.Configuration = {}; window.NeosCMS.Configuration.neosJavascriptBasePath = ' + Json.stringify(this._neosJavascriptBasePath) + ';</script>'}

_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`
<html>
<head>
<link rel="stylesheet" href={StaticResource.uri('Neos.Neos', 'Public/Styles/Minimal.css')} />
<link rel="stylesheet" href={StaticResource.uri('Flowpack.Media.Ui', 'Public/Assets/main.bundle.css')} />

<link rel="neos-xliff" href={props._xliffAsJsonUri} />
</head>
<body class="neos neos-module" style="background-color: #222">
{props.mediaModule}
{props._initScript}
<script src={StaticResource.uri('Neos.Neos', 'Public/JavaScript/Main.min.js')}></script>
<script src={StaticResource.uri('Flowpack.Media.Ui', 'Public/Assets/main.bundle.js')}></script>
</body>
</html>
`
}
}
46 changes: 24 additions & 22 deletions Resources/Private/Fusion/Views/Index.fusion
Original file line number Diff line number Diff line change
@@ -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']}
[email protected] = ${Json.stringify(value)}
data-features = ${Configuration.setting('Neos.Neos.Ui.frontendConfiguration')['Flowpack.Media.Ui']}
[email protected] = ${Json.stringify(value)}

renderer = afx`
<div id="media-ui-app" {...props}>
Loading media ui...
</div>
`
}
renderer = afx`
<div id="media-ui-app" {...props}>
Loading media ui...
</div>
`
}

Flowpack.Media.Ui.MediaController {
index = Flowpack.Media.Ui:MediaModule
}
18 changes: 18 additions & 0 deletions Resources/Private/JavaScript/media-module/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,22 @@ window.onload = async (): Promise<void> => {

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(
<IntlProvider translate={translate}>
<NotifyProvider notificationApi={Notification}>
Expand All @@ -89,6 +105,8 @@ window.onload = async (): Promise<void> => {
dummyImage={dummyImage}
containerRef={containerRef}
featureFlags={featureFlags}

{...selectionModeProps}
>
<MediaUiThemeProvider>
<DndProvider backend={HTML5Backend}>
Expand Down