Commit 0ee39dde authored by Tymoteusz Motylewski's avatar Tymoteusz Motylewski Committed by Susanne Moog
Browse files

[!!!][FEATURE] Replace ExtJS page tree

The ExtJS/ExtDirect based page tree has been replaced with
new implementation based on SVG.

Refactoring and performance improvement of the PHP side
(tree data provider) will be done in the followup patch.

Releases: master
Resolves: #82426
Change-Id: I502a085da939ebe2561d2b7a17cc8347e5101623
Reviewed-on: https://review.typo3.org/51594


Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Reviewed-by: Frank Nägler's avatarFrank Naegler <frank.naegler@typo3.org>
Tested-by: Frank Nägler's avatarFrank Naegler <frank.naegler@typo3.org>
Tested-by: default avatarTYPO3com <no-reply@typo3.com>
Reviewed-by: Susanne Moog's avatarSusanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog's avatarSusanne Moog <susanne.moog@typo3.org>
parent 945f3586
$svgColors: (
border: #d7d7d7,
lines: #ddd,
nodeSelectedBg: #fff,
nodeOverBg: #f2f2f2,
dragOverBg: #d7e4f1,
dragOverBorder: transparent,
dragAlertBg: #f6d3cf,
dragAlertBorder: #d66c68,
dragAboveBg: transparent,
dragAboveBorder: transparent,
dragBetweenBg: transparent,
dragBetweenBorder: transparent,
dragBelowBg: transparent,
dragBelowBorder: transparent,
dragTooltipBg: #d7e4f1,
dragTooltipAlertBg: #f6d3cf,
dragTooltipAlertBorder: #d66c68
);
.svg-tree {
position: relative;
}
.svg-tree-loader {
display: none;
position: absolute;
width: 100%;
height: 100%;
padding-top: 200px;
top: 0;
left: 0;
text-align: center;
background: rgba(0, 0, 0, 0.3);
z-index: 3000;
& > * {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
}
}
.node-loader {
display: none;
position: absolute;
width: 100%;
height: 20px;
top: 0;
left: 0;
text-align: center;
background: rgba(0, 0, 0, 0.3);
z-index: 3000;
& > * {
position: absolute;
top: 0;
bottom: 0;
right: 10px;
margin: auto;
}
}
.svg-tree-wrapper {
position: relative;
overflow-y: scroll;
& > svg {
margin-top: 15px;
}
path.link {
fill: none;
shape-rendering: crispEdges;
stroke: #ddd;
stroke: map_get($svgColors, lines);
stroke-width: 1;
pointer-events: none;
}
.node {
.chevron,
text,
.tree-check {
&-bg {
fill: transparent;
&__border {
display: none;
pointer-events: none;
fill: #9eb2c5;
}
&.ver-element,
&.ver-versions,
&.ver-page {
fill: #f7c898 !important;
}
}
&-over:not(.node-selected) {
fill: map_get($svgColors, nodeOverBg);
stroke-width: 1px;
stroke: map_get($svgColors, border);
}
&-selected {
fill: map_get($svgColors, nodeSelectedBg);
stroke-width: 1px;
stroke: map_get($svgColors, border);
}
}
.nodes {
&-wrapper {
$b: '.nodes-wrapper';
cursor: pointer;
&--dragging {
cursor: -webkit-grabbing;
.node-over {
//it must be important because there is inline style in code that we must overwrite
fill: map_get($svgColors, dragOverBg) !important;
stroke-width: 1px;
stroke: map_get($svgColors, dragOverBorder);
}
.node-alert {
//it must be important because there is inline style in code that we must overwrite
fill: map_get($svgColors, dragAlertBg) !important;
stroke: map_get($svgColors, dragAlertBorder);
}
&#{$b}--nodrop {
.node-over {
//it must be important because there is inline style in code that we must overwrite
fill: map_get($svgColors, dragAlertBg) !important;
}
}
&#{$b}--ok-above {
.node-over {
//it must be important because there is inline style in code that we must overwrite
fill: map_get($svgColors, dragAboveBg) !important;
stroke: map_get($svgColors, dragAboveBorder);
}
}
&#{$b}--ok-between {
.node-over {
//it must be important because there is inline style in code that we must overwrite
fill: map_get($svgColors, dragBetweenBg) !important;
stroke: map_get($svgColors, dragBetweenBorder);
}
}
&#{$b}--ok-below {
.node-over {
//it must be important because there is inline style in code that we must overwrite
fill: map_get($svgColors, dragBelowBg) !important;
stroke: map_get($svgColors, dragBelowBorder);
}
}
}
&--nodrop {
cursor: no-drop;
}
}
}
}
//node drag & drop tooltip
.node-dd {
position: fixed;
display: none;
padding: 0;
margin: 0;
border: none;
background-color: map_get($svgColors, dragTooltipBg);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
z-index: 9999;
&--nodrop {
background-color: map_get($svgColors, dragTooltipAlertBg);
border: 1px solid map_get($svgColors, dragTooltipAlertBorder);
& .node-dd__ctrl-icon {
background-image: url(../../../../backend/Resources/Public/Images/pagetree-drag-place-denied.png);
}
}
&__text {
display: table;
vertical-align: middle;
opacity: 0.85;
padding: 5px 5px 5px 20px;
}
&--ok-below {
&.node-dd--copy .node-dd__ctrl-icon {
background-image: url(../../../../backend/Resources/Public/Images/pagetree-drag-copy-below.png);
}
.node-dd__ctrl-icon {
background-image: url(../../../../backend/Resources/Public/Images/pagetree-drag-move-below.png);
}
}
&--ok-between {
&.node-dd--copy .node-dd__ctrl-icon {
background-image: url(../../../../backend/Resources/Public/Images/pagetree-drag-new-between.png);
}
.node-dd__ctrl-icon {
background-image: url(../../../../backend/Resources/Public/Images/pagetree-drag-move-between.png);
}
}
&--ok-append {
&.node-dd--copy .node-dd__ctrl-icon {
background-image: url(../../../../backend/Resources/Public/Images/pagetree-drag-new-inside.png);
}
.node-dd__ctrl-icon {
background-image: url(../../../../backend/Resources/Public/Images/pagetree-drag-move-into.png);
}
}
&--ok-above {
&.node-dd--copy .node-dd__ctrl-icon {
background-image: url(../../../../backend/Resources/Public/Images/pagetree-drag-copy-above.png);
}
.node-dd__ctrl-icon {
background-image: url(../../../../backend/Resources/Public/Images/pagetree-drag-move-above.png);
}
}
&__icon {
display: table-cell;
vertical-align: top;
padding-left: 3px;
padding-right: 3px;
}
&__name {
display: table-cell;
vertical-align: top;
}
&__ctrl-icon {
position: absolute;
top: 3px;
left: 3px;
display: block;
width: 16px;
height: 16px;
background-color: transparent;
background-position: center;
background-repeat: no-repeat;
z-index: 1;
}
}
.nodes-drop-zone {
rect {
fill: map_get($svgColors, dragAlertBorder);
cursor: -webkit-grabbing;
}
text {
pointer-events: none;
}
}
.node-edit {
position: absolute;
top: 0;
left: 0;
}
.svg-toolbar {
min-height: 66px;
padding: 4px 10px 0;
border-bottom: 1px solid #c3c3c3;
background-color: #eee;
&__btn {
padding: 0;
border: none;
background: transparent;
&:focus {
outline: none;
}
}
&__menu {
margin-bottom: 4px;
.x-btn {
&:not(:last-child) {
margin-right: 4px;
}
&:last-child {
float: right;
}
}
}
&__submenu {
margin: 0 -5px;
.search-input {
max-height: 26px;
}
}
&__submenu-item {
display: none;
&.active {
display: block;
}
}
&__drag-node {
display: inline-block;
cursor: move;
padding: 5px;
border: none;
background: 0 0;
font-size: 11px;
line-height: 16px;
}
}
.node-stop {
fill: map_get($svgColors, dragAlertBorder);
}
.node-mount-point {
display: table;
width: 100%;
max-width: 265px;
margin: 10px 10px 0;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
border: 0;
background-color: #6daae0;
color: #fff;
padding: 11px;
border-radius: 2px;
&__icon {
display: table-cell;
width: 1%;
&[data-tree-icon=actions-close] {
cursor: pointer;
}
}
&__text {
display: table-cell;
padding-left: 10px;
padding-right: 10px;
& > div {
max-width: 185px;
overflow: hidden;
}
}
}
......@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "a673ef0c458da6c690377218a89aa4a8",
"content-hash": "d2ae33bbbd5129800f6125887cfa734f",
"packages": [
{
"name": "cogpowered/finediff",
......@@ -4281,16 +4281,16 @@
},
{
"name": "typo3/testing-framework",
"version": "1.2.0",
"version": "1.2.1",
"source": {
"type": "git",
"url": "https://github.com/TYPO3/testing-framework.git",
"reference": "42c16d829373a1d2cbaa9a46bb77a0290d3dec32"
"reference": "3d31fd19f2dc4b93525403ae3bbb4b560a424318"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/TYPO3/testing-framework/zipball/42c16d829373a1d2cbaa9a46bb77a0290d3dec32",
"reference": "42c16d829373a1d2cbaa9a46bb77a0290d3dec32",
"url": "https://api.github.com/repos/TYPO3/testing-framework/zipball/3d31fd19f2dc4b93525403ae3bbb4b560a424318",
"reference": "3d31fd19f2dc4b93525403ae3bbb4b560a424318",
"shasum": ""
},
"require": {
......@@ -4340,7 +4340,7 @@
"tests",
"typo3"
],
"time": "2017-11-29T12:40:03+00:00"
"time": "2017-11-30T11:12:57+00:00"
},
{
"name": "webmozart/assert",
......
......@@ -112,9 +112,5 @@
<td>CodeMirror</td>
<td><a href="http://codemirror.net" target="_blank" rel="noopener noreferrer">codemirror.net</a></td>
</tr>
<tr>
<td>ExtJS</td>
<td><a href="http://www.sencha.com" target="_blank" rel="noopener noreferrer">www.sencha.com</a></td>
</tr>
</table>
</div>
......@@ -28,7 +28,6 @@ use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Core\Type\Bitmask\Permission;
use TYPO3\CMS\Core\Type\File\ImageInfo;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\MathUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
......@@ -120,10 +119,8 @@ class BackendController
$this->moduleLoader = GeneralUtility::makeInstance(ModuleLoader::class);
$this->moduleLoader->load($GLOBALS['TBE_MODULES']);
$this->pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
$this->pageRenderer->loadExtJS();
// included for the module menu JavaScript, please note that this is subject to change
$this->pageRenderer->loadJquery();
$this->pageRenderer->addExtDirectCode();
// Add default BE javascript
$this->jsFiles = [
'md5' => 'EXT:backend/Resources/Public/JavaScript/md5.js',
......@@ -166,6 +163,7 @@ class BackendController
$this->pageRenderer->addInlineLanguageLabelFile('EXT:lang/Resources/Private/Language/locallang_core.xlf');
$this->pageRenderer->addInlineLanguageLabelFile('EXT:lang/Resources/Private/Language/locallang_misc.xlf');
$this->pageRenderer->addInlineLanguageLabelFile('EXT:backend/Resources/Private/Language/locallang_layout.xlf');
$this->pageRenderer->addInlineSetting('ShowItem', 'moduleUrl', (string)$uriBuilder->buildUriFromRoute('show_item'));
$this->pageRenderer->addInlineSetting('RecordHistory', 'moduleUrl', (string)$uriBuilder->buildUriFromRoute('record_history'));
......@@ -260,91 +258,7 @@ class BackendController
}
$this->generateJavascript();
$this->pageRenderer->addJsInlineCode('BackendInlineJavascript', $this->js, false);
$this->loadResourcesForRegisteredNavigationComponents();
// @todo: remove this when ExtJS is removed
$states = $this->getBackendUser()->uc['BackendComponents']['States'];
$this->pageRenderer->addExtOnReadyCode('
require([\'TYPO3/CMS/Backend/Storage/Persistent\'], function(PersistentStorage) {
var TYPO3ExtJSStateProviderBridge = function() {};
Ext.extend(TYPO3ExtJSStateProviderBridge, Ext.state.Provider, {
state: {},
queue: [],
dirty: false,
prefix: "BackendComponents.States.",
initState: function(state) {
if (Ext.isArray(state)) {
Ext.each(state, function(item) {
this.state[item.name] = item.value;
}, this);
} else if (Ext.isObject(state)) {
Ext.iterate(state, function(key, value) {
this.state[key] = value;
}, this);
} else {
this.state = {};
}
var me = this;
window.setInterval(function() {
me.submitState(me)
}, 750);
},
get: function(name, defaultValue) {
return PersistentStorage.isset(this.prefix + name) ? PersistentStorage.get(this.prefix + name) : defaultValue;
},
clear: function(name) {
PersistentStorage.unset(this.prefix + name);
},
set: function(name, value) {
if (!name) {
return;
}
this.queueChange(name, value);
},
queueChange: function(name, value) {
var o = {};
var i;
var found = false;
var lastValue = this.state[name];
for (i = 0; i < this.queue.length; i++) {
if (this.queue[i].name === name) {
lastValue = this.queue[i].value;
}
}
var changed = undefined === lastValue || lastValue !== value;
if (changed) {
o.name = name;
o.value = value;
for (i = 0; i < this.queue.length; i++) {
if (this.queue[i].name === o.name) {
this.queue[i] = o;
found = true;
}
}
if (false === found) {
this.queue.push(o);
}
this.dirty = true;
}
},
submitState: function(context) {
if (!context.dirty) {
return;
}
for (var i = 0; i < context.queue.length; ++i) {
PersistentStorage.set(context.prefix + context.queue[i].name, context.queue[i].value).done(function() {
if (!context.dirty) {
context.queue = [];
}
});
}
context.dirty = false;
}
});
Ext.state.Manager.setProvider(new TYPO3ExtJSStateProviderBridge());
Ext.state.Manager.getProvider().initState(' . (!empty($states) ? json_encode($states) : []) . ');
});');
// Set document title:
$title = $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ? $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . ' [TYPO3 CMS ' . TYPO3_version . ']' : 'TYPO3 CMS ' . TYPO3_version;
// Renders the module page
......@@ -399,51 +313,6 @@ class BackendController
return $view->render();
}
/**
* Loads the css and javascript files of all registered navigation widgets
*/
protected function loadResourcesForRegisteredNavigationComponents()
{
if (!is_array($GLOBALS['TBE_MODULES']['_navigationComponents'])) {
return;
}
$loadedComponents = [];
foreach ($GLOBALS['TBE_MODULES']['_navigationComponents'] as $module => $info) {
if (in_array($info['componentId'], $loadedComponents)) {
continue;
}
$loadedComponents[] = $info['componentId'];
$component = strtolower(substr($info['componentId'], strrpos($info['componentId'], '-') + 1));
$componentDirectory = 'components/' . $component . '/';
if ($info['isCoreComponent']) {
$componentDirectory = 'Resources/Public/JavaScript/extjs/' . $componentDirectory;
$info['extKey'] = 'backend';
}
$absoluteComponentPath = ExtensionManagementUtility::extPath($info['extKey']) . $componentDirectory;
$relativeComponentPath = PathUtility::getRelativePath(PATH_site . TYPO3_mainDir, $absoluteComponentPath);
$cssFiles = GeneralUtility::getFilesInDir($absoluteComponentPath . 'css/', 'css');
if (file_exists($absoluteComponentPath . 'css/loadorder.txt')) {
// Don't allow inclusion outside directory
$loadOrder = str_replace('../', '', file_get_contents($absoluteComponentPath . 'css/loadorder.txt'));
$cssFilesOrdered = GeneralUtility::trimExplode(LF, $loadOrder, true);
$cssFiles = array_merge($cssFilesOrdered, $cssFiles);
}
foreach ($cssFiles as $cssFile) {
$this->pageRenderer->addCssFile($relativeComponentPath . 'css/' . $cssFile);
}
$jsFiles = GeneralUtility::getFilesInDir($absoluteComponentPath . 'javascript/', 'js');
if (file_exists($absoluteComponentPath . 'javascript/loadorder.txt')) {