Enable Block List Editor settings editing

This commit is contained in:
Benjamin Carleski
2020-03-27 15:41:48 -07:00
parent 5ed727df02
commit 09be6b8ee4
8 changed files with 200 additions and 73 deletions
@@ -56,7 +56,21 @@
* @param {Object} toModel ElementTypeModel to recive property values from.
*/
function mapElementTypeValues(fromModel, toModel) {
if (!fromModel || !fromModel.variants) {
toModel.variants = null;
return;
}
if (!fromModel.variants.length) {
toModel.variants = [];
return;
}
var fromVariant = fromModel.variants[0];
if (!fromVariant) {
toModel.variants = [null];
return;
}
var toVariant = toModel.variants[0];
for (var t = 0; t < fromVariant.tabs.length; t++) {
@@ -81,6 +95,31 @@
}
/**
* Used to add watchers on all properties in a content or settings model
*/
function addWatchers(blockModel, isolatedScope, forSettings) {
var model = forSettings ? blockModel.settings : blockModel.content;
if (!model || !model.variants || !model.variants.length) { return; }
// Start watching each property value.
var variant = model.variants[0];
var field = forSettings ? "settings" : "content";
var watcherCreator = forSettings ? createSettingsModelPropWatcher : createContentModelPropWatcher;
for (var t = 0; t < variant.tabs.length; t++) {
var tab = variant.tabs[t];
for (var p = 0; p < tab.properties.length; p++) {
var prop = tab.properties[p];
// Watch value of property since this is the only value we want to keep synced.
// Do notice that it is not performing a deep watch, meaning that we are only watching primatives and changes directly to the object of property-value.
// But we like to sync non-primative values as well! Yes, and this does happen, just not through this code, but through the nature of JavaScript.
// Non-primative values act as references to the same data and are therefor synced.
blockModel.watchers.push(isolatedScope.$watch("blockModels._" + blockModel.key + "." + field + ".variants[0].tabs[" + t + "].properties[" + p + "].value", watcherCreator(blockModel, prop)));
}
}
}
/**
* Used to create a scoped watcher for a content property on a blockModel.
*/
@@ -225,13 +264,13 @@
blockModel.config = angular.copy(blockConfiguration);
blockModel.labelInterpolator = $interpolate(blockModel.config.label);
var scaffold = this.getScaffoldFor(blockConfiguration.contentTypeAlias);
if(scaffold === null) {
var contentScaffold = this.getScaffoldFor(blockConfiguration.contentTypeAlias);
if(contentScaffold === null) {
return null;
}
// make basics from scaffold
blockModel.content = angular.copy(scaffold);
blockModel.content = angular.copy(contentScaffold);
blockModel.content.udi = udi;
mapToElementTypeModel(blockModel.content, contentModel);
@@ -240,37 +279,32 @@
blockModel.layoutModel = layoutEntry;
blockModel.watchers = [];
// TODO: implement settings
// create ElementTypeModel of settings
// store ElementTypeModel in blockModel.settings
// setup watchers for mapping
if (blockConfiguration.settingsElementTypeAlias) {
var settingsScaffold = this.getScaffoldFor(blockConfiguration.settingsElementTypeAlias);
if (settingsScaffold === null) {
return null;
}
// make basics from scaffold
blockModel.settings = angular.copy(settingsScaffold);
layoutEntry.settings = layoutEntry.settings || { key: String.CreateGuid(), contentTypeAlias: blockConfiguration.settingsElementTypeAlias };
if (!layoutEntry.settings.key) { layoutEntry.settings.key = String.CreateGuid(); }
if (!layoutEntry.settings.contentTypeAlias) { layoutEntry.settings.contentTypeAlias = blockConfiguration.settingsElementTypeAlias; }
mapToElementTypeModel(blockModel.settings, layoutEntry.settings);
} else {
layoutEntry.settings = null;
}
// Add blockModel to our isolated scope to enable watching its values:
this.isolatedScope.blockModels["_"+blockModel.key] = blockModel;
// Start watching each property value.
var variant = blockModel.content.variants[0];
for (var t = 0; t < variant.tabs.length; t++) {
var tab = variant.tabs[t];
for (var p = 0; p < tab.properties.length; p++) {
var prop = tab.properties[p];
// Watch value of property since this is the only value we want to keep synced.
// Do notice that it is not performing a deep watch, meaning that we are only watching primatives and changes directly to the object of property-value.
// But we like to sync non-primative values as well! Yes, and this does happen, just not through this code, but through the nature of JavaScript.
// Non-primative values act as references to the same data and are therefor synced.
blockModel.watchers.push(this.isolatedScope.$watch("blockModels._"+blockModel.key+".content.variants[0].tabs["+t+"].properties["+p+"].value", createContentModelPropWatcher(blockModel, prop)));
}
}
addWatchers(blockModel, this.isolatedScope);
addWatchers(blockModel, this.isolatedScope, true);
return blockModel;
},
removeDataAndDestroyModel: function(blockModel) {
removeDataAndDestroyModel: function (blockModel) {
this.destroyBlockModel(blockModel);
this.removeDataByUdi(blockModel.content.udi);
},
@@ -331,7 +365,7 @@
}
if (blockConfiguration.settingsElementTypeAlias != null) {
entry.settings = {};
entry.settings = { key: String.CreateGuid(), contentTypeAlias: blockConfiguration.settingsElementTypeAlias };
}
return entry;
@@ -2,17 +2,49 @@
angular.module("umbraco")
.controller("Umbraco.Editors.BlockEditorController",
function ($scope) {
var vm = this;
function showContent() {
if (vm.settingsTab) vm.settingsTab.active = false;
vm.contentTab.active = true;
}
function showSettings() {
if (vm.settingsTab) vm.settingsTab.active = true;
vm.contentTab.active = false;
}
vm.content = $scope.model.content;
vm.settings = $scope.model.settings;
vm.tabs = [];
var settingsOnly = vm.content && vm.content.variants ? false : true;
// TODO: implement settings — do notice that settings is optional.
//vm.settings = $scope.model.settings;
if (!settingsOnly) {
vm.contentTab = {
"name": "Content",
"alias": "content",
"icon": "icon-document",
"action": showContent,
"active": true
};
vm.title = $scope.model.title;
vm.tabs.push(vm.contentTab);
}
vm.saveAndClose = function() {
if (vm.settings && vm.settings.variants) {
vm.settingsTab = {
"name": "Settings",
"alias": "settings",
"icon": "icon-settings",
"action": showSettings,
"active": settingsOnly
};
vm.tabs.push(vm.settingsTab);
}
vm.title = (settingsOnly ? 'SETTINGS: ' : '') + $scope.model.title;
vm.saveAndClose = function () {
if ($scope.model && $scope.model.submit) {
$scope.model.submit($scope.model);
}
@@ -1,15 +1,16 @@
<div class="umb-block-editor" ng-controller="Umbraco.Editors.BlockEditorController as vm">
<ng-form name="blockForm" val-form-manager>
<umb-editor-view ng-if="!page.loading">
<umb-editor-view umb-tabs ng-if="!page.loading">
<umb-editor-header
name="vm.title"
name-locked="true"
navigation="vm.content.apps"
navigation="vm.tabs"
hide-alias="true"
hide-icon="true"
hide-description="true">
hide-description="true"
on-select-navigation-item="vm.selectTab">
</umb-editor-header>
<div class="umb-editor-container umb-panel-body umb-scrollable row-fluid">
@@ -21,12 +22,19 @@
-->
<!-- Missing content apps? — but i'm not sure content apps will or can work for ElementTypes? -->
<div ng-show="vm.contentTab.active">
<div class="umb-pane">
<div class="umb-editor-sub-views" val-sub-view>
<umb-element-content-editor content="vm.content"></umb-element-content-editor>
</div>
</div>
</div>
<!-- TODO: implement settings -->
<div class="umb-pane">
<div class="umb-editor-sub-views" val-sub-view>
<umb-element-content-editor content="vm.content"></umb-element-content-editor>
<div ng-show="vm.settingsTab.active">
<div class="umb-pane" ng-if="vm.settings">
<div class="umb-editor-sub-views" val-sub-view>
<umb-element-content-editor content="vm.settings"></umb-element-content-editor>
</div>
</div>
</div>
</div>
@@ -182,24 +182,29 @@
vm.blocks.forEach(deleteBlock);
}
function editBlock(blockModel) {
function editBlock(blockModel, hideContent) {
if (hideContent && !blockModel.config.settingsElementTypeAlias) {
return;
}
// make a clone to avoid editing model directly.
var blockContentModelClone = angular.copy(blockModel.content);
// TODO: implement settings
// Settings should be available as a tab in this overlay:
//var blockSettingsModelClone = angular.copy(blockModel.settings);
var blockContentModelClone = angular.copy(hideContent ? null : blockModel.content);
var blockSettingsModelClone = angular.copy(blockModel.settings);
var blockEditorModel = {
content: blockContentModelClone,
//settings: blockSettingsModelClone,
settings: blockSettingsModelClone,
title: blockModel.label,
view: "views/common/infiniteeditors/blockeditor/blockeditor.html",
size: blockModel.config.overlaySize || "medium",
submit: function(blockEditorModel) {
// To ensure syncronization gets tricked we transfer
blockEditorService.mapElementTypeValues(blockEditorModel.content, blockModel.content)
if (!hideContent) {
blockEditorService.mapElementTypeValues(blockEditorModel.content, blockModel.content)
}
if (blockModel.config.settingsElementTypeAlias) {
blockEditorService.mapElementTypeValues(blockEditorModel.settings, blockModel.settings)
}
editorService.close();
},
close: function() {
@@ -244,10 +249,14 @@
vm.blockTypePicker.close();
},
submit: function (model) {
var added = false;
if (model && model.selectedItem) {
addNewBlock(createIndex, model.selectedItem.alias);
added = addNewBlock(createIndex, model.selectedItem.alias);
}
vm.blockTypePicker.close();
if (added && vm.blocks.length > createIndex) {
editBlock(vm.blocks[createIndex]);
}
},
close: function () {
vm.blockTypePicker.show = false;
@@ -379,8 +388,8 @@
});
}
function openSettingsForBlock() {
alert("settings not implemented jet.");
function openSettingsForBlock(block) {
editBlock(block, true);
}
@@ -32,13 +32,17 @@ function MainController($scope, $location, appState, treeService, notificationsS
// For more information about this approach, see https://hackernoon.com/removing-that-ugly-focus-ring-and-keeping-it-too-6c8727fefcd2
function handleFirstTab(evt) {
if (evt.keyCode === 9) {
$scope.tabbingActive = true;
$scope.$digest();
window.removeEventListener('keydown', handleFirstTab);
window.addEventListener('mousedown', disableTabbingActive);
enableTabbingActive();
}
}
function enableTabbingActive() {
$scope.tabbingActive = true;
$scope.$digest();
window.addEventListener('mousedown', disableTabbingActive);
window.removeEventListener("keydown", handleFirstTab);
}
function disableTabbingActive(evt) {
$scope.tabbingActive = false;
$scope.$digest();
@@ -48,6 +52,12 @@ function MainController($scope, $location, appState, treeService, notificationsS
window.addEventListener("keydown", handleFirstTab);
$scope.$on("showFocusOutline", function() {
$scope.tabbingActive = true;
window.addEventListener('mousedown', disableTabbingActive);
window.removeEventListener("keydown", handleFirstTab);
});
$scope.removeNotification = function (index) {
notificationsService.remove(index);
@@ -257,6 +257,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar
evts.push(eventsService.on("app.ready", function (evt, data) {
$scope.authenticated = true;
ensureInit();
ensureMainCulture();
}));
// event for infinite editors
@@ -279,8 +280,22 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar
}
}));
/**
* For multi language sites, this ensures that mculture is set to either the last selected language or the default one
*/
function ensureMainCulture() {
if ($location.search().mculture) {
return;
}
var language = lastLanguageOrDefault();
if (!language) {
return;
}
// trigger a language selection in the next digest cycle
$timeout(function () {
$scope.selectLanguage(language);
});
}
/**
* Based on the current state of the application, this configures the scope variables that control the main tree and language drop down
@@ -385,28 +400,19 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar
if ($scope.languages.length > 1) {
//if there's already one set, check if it exists
var currCulture = null;
var language = null;
var mainCulture = $location.search().mculture;
if (mainCulture) {
currCulture = _.find($scope.languages, function (l) {
language = _.find($scope.languages, function (l) {
return l.culture.toLowerCase() === mainCulture.toLowerCase();
});
}
if (!currCulture) {
// no culture in the request, let's look for one in the cookie that's set when changing language
var defaultCulture = $cookies.get("UMB_MCULTURE");
if (!defaultCulture || !_.find($scope.languages, function (l) {
return l.culture.toLowerCase() === defaultCulture.toLowerCase();
})) {
// no luck either, look for the default language
var defaultLang = _.find($scope.languages, function (l) {
return l.isDefault;
});
if (defaultLang) {
defaultCulture = defaultLang.culture;
}
if (!language) {
language = lastLanguageOrDefault();
if (language) {
$location.search("mculture", language.culture);
}
$location.search("mculture", defaultCulture ? defaultCulture : null);
}
}
@@ -431,6 +437,25 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar
});
});
}
function lastLanguageOrDefault() {
if (!$scope.languages || $scope.languages.length <= 1) {
return null;
}
// see if we can find a culture in the cookie set when changing language
var lastCulture = $cookies.get("UMB_MCULTURE");
var language = lastCulture ? _.find($scope.languages, function (l) {
return l.culture.toLowerCase() === lastCulture.toLowerCase();
}) : null;
if (!language) {
// no luck, look for the default language
language = _.find($scope.languages, function (l) {
return l.isDefault;
});
}
return language;
}
function nodeExpandedHandler(args) {
//store the reference to the expanded node path
if (args.node) {
@@ -1,4 +1,5 @@
@inherits UmbracoViewPage<BlockListModel>
@using ContentModels = Umbraco.Web.PublishedModels;
@using Umbraco.Core.Models.Blocks
@{
if (Model?.Layout == null || !Model.Layout.Any()) { return; }
@@ -8,6 +9,6 @@
{
if (layout?.Udi == null) { continue; }
var data = Model.GetData(layout.Udi);
@Html.Partial("BlockList/" + data.ContentType.Alias, data);
@Html.Partial("BlockList/" + data.ContentType.Alias, (data, layout.Settings))
}
</div>
@@ -59,6 +59,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
{
var configuration = propertyType.DataType.ConfigurationAs<BlockListConfiguration>();
var contentTypes = configuration.Blocks;
var contentTypeMap = contentTypes.ToDictionary(x => x.Alias);
var elements = (contentTypes.Length == 1
? (IList<IPublishedElement>)_publishedModelFactory.CreateModelList(contentTypes[0].Alias)
: new List<IPublishedElement>())
@@ -106,6 +107,13 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
if (!elements.TryGetValue(guidUdi.Guid, out var data))
continue;
if (!contentTypeMap.TryGetValue(data.ContentType.Alias, out var blockConfig))
continue;
// this can happen if they have a settings type, save content, remove the settings type, and display the front-end page before saving the content again
if (element != null && string.IsNullOrWhiteSpace(blockConfig.SettingsElementTypeAlias))
element = null;
var layoutRef = new BlockListLayoutReference(udi, element);
layout.Add(layoutRef);
}