Initial work on Block List Prevalue Editor

This commit is contained in:
Niels Lyngsø
2020-02-12 17:17:04 +01:00
parent 9733c64b20
commit bc1f5f2086
12 changed files with 534 additions and 3 deletions
@@ -0,0 +1,32 @@
/**
* @ngdoc service
* @name umbraco.resources.elementTypeResource
* @description Loads in data for element types
**/
function elementTypeResource($q, $http, umbRequestHelper) {
return {
getAll: function () {
// TODO: Change this into a real api (ElementTypeApi). This is a temporary fix to get data.
var url = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + "/backoffice/UmbracoApi/NestedContent/GetContentTypes";
return umbRequestHelper.resourcePromise(
$http.get(url),
'Failed to retrieve content types'
);
/*
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"elementTypeApiBaseUrl",
"GetAll")),
"Failed to retrieve data");
*/
}
};
}
angular.module("umbraco.resources").factory("elementTypeResource", elementTypeResource);
@@ -199,6 +199,7 @@
// Property Editors
@import "../views/propertyeditors/blocklist/blocklist.component.less";
@import "../views/propertyeditors/blocklist/prevalue/blocklist.elementtypepicker.less";
// Utilities
@@ -42,5 +42,13 @@
</span>
</button>
</li>
<li ng-if="model.createNewItem">
<button class="umb-card-grid-item btn-reset --creator" ng-click="model.createNewItem.action()">
<span>
<i class="{{ model.createNewItem.icon }}"></i>
{{ model.createNewItem.name }}
</span>
</button>
</li>
</ul>
</div>
@@ -0,0 +1,236 @@
/**
* @ngdoc controller
* @name Umbraco.Editors.PropertySettingsController
* @function
*
* @description
* The controller for the content type editor property settings dialog
*/
(function () {
"use strict";
function ElementTypePickerController($scope, elementTypeResource, overlayService, localizationService, editorService) {
var vm = this;
vm.enableAddEntry = true;
function evaluateStatus() {
if (!vm.elementTypes) return;// cancel if elementTypes isnt loaded jet.
vm.enableAddEntry = vm.getAvailableElementTypes().length > 0;
}
function onInit() {
if (!$scope.model.value) {
$scope.model.value = [];
}
localizationService.localize("content_nestedContentSelectElementTypeModalTitle").then(function (value) {
//selectElementTypeModalTitle = value;
});
loadElementTypes();
}
function loadElementTypes() {
return elementTypeResource.getAll().then(function (elementTypes) {
vm.elementTypes = elementTypes;
console.log("vm.elementTypes:", vm.elementTypes)
evaluateStatus();
});
}
vm.removeEntryByIndex = function (index) {
$scope.model.value.splice(index, 1);
};
vm.sortableOptions = {
axis: "y",
cursor: "grabbing",
placeholder: 'sortable-placeholder',
forcePlaceholderSize: true
};
vm.getAvailableElementTypes = function () {
return vm.elementTypes.filter(function (type) {
return !$scope.model.value.find(function (entry) {
return type.alias === entry.elementTypeAlias;
});
});
};
vm.getElementTypeByAlias = function(alias) {
return _.find(vm.elementTypes, function (type) {
return type.alias === alias;
});
};
vm.openAddDialog = function ($event, entry) {
//we have to add the alias to the objects (they are stored as elementTypeAlias)
var selectedItems = _.each($scope.model.value, function (obj) {
obj.alias = obj.elementTypeAlias;
return obj;
});
var availableItems = vm.getAvailableElementTypes()
var elemTypeSelectorOverlay = {
view: "itempicker",
title: "no title jet",
availableItems: availableItems,
selectedItems: selectedItems,
createNewItem: {
action: function() {
overlayService.close();
vm.createElementTypeAndAdd(vm.addEntryFromElementTypeAlias);
},
icon: "icon-add",
name: "Create new"
},
position: "target",
event: $event,
size: availableItems.length < 7 ? "small" : "medium",
submit: function (overlay) {
vm.addEntryFromElementTypeAlias(overlay.selectedItem.alias);
overlayService.close();
},
close: function () {
overlayService.close();
}
};
overlayService.open(elemTypeSelectorOverlay);
};
vm.createElementTypeAndAdd = function(callback) {
const editor = {
create: true,
infiniteMode: true,
isElement: true,
submit: function (model) {
console.log(model)
loadElementTypes().then( function () {
callback(model.documentTypeAlias);
});
editorService.close();
},
close: function () {
editorService.close();
}
};
editorService.documentTypeEditor(editor);
}
vm.addEntryFromElementTypeAlias = function(alias) {
var entry = {
"elementTypeAlias": alias,
"view": null,
"labelTemplate": "",
"settingsElementTypeAlias": null
};
$scope.model.value.push(entry);
};
vm.removeSettingsForEntry = function(entry) {
entry.settingsElementTypeAlias = null;
};
vm.openPickSettingsDialog = function ($event, entry) {
var elemTypeSelectorOverlay = {
view: "itempicker",
title: "Pick settings (missing translation)",
availableItems: vm.elementTypes,
position: "target",
event: $event,
size: vm.elementTypes.length < 7 ? "small" : "medium",
createNewItem: {
action: function() {
overlayService.close();
vm.createElementTypeAndAdd((alias) => {
vm.addSettingsAtEntry(entry, alias);
});
},
icon: "icon-add",
name: "Create new"
},
submit: function (overlay) {
vm.addSettingsAtEntry(entry, overlay.selectedItem.alias);
overlayService.close();
},
close: function () {
overlayService.close();
}
};
overlayService.open(elemTypeSelectorOverlay);
};
vm.addSettingsAtEntry = function(entry, alias) {
entry.settingsElementTypeAlias = alias;
};
vm.openElementType = function(elementTypeAlias) {
var elementTypeId = vm.getElementTypeByAlias(elementTypeAlias).id;
const editor = {
id: elementTypeId,
submit: function (model) {
loadElementTypes();
editorService.close();
},
close: function () {
editorService.close();
}
};
editorService.documentTypeEditor(editor);
}
vm.removeViewForEntry = function(entry) {
entry.view = null;
};
vm.addViewForEntry = function(entry) {
const viewPicker = {
title: "Pick view (TODO need translation)",
section: "settings",
treeAlias: "partialView",
entityType: "partialView",
onlyInitialized: false,
filter: function (i) {
if (i.name.indexOf(".cshtml") === -1 && i.name.indexOf(".vbhtml") === -1) {
return true;
}
},
filterCssClass: "not-allowed",
select: function (node) {
console.log(node);
//entry.view = node.name;
editorService.close();
},
close: function () {
editorService.close();
}
};
editorService.treePicker(viewPicker);
}
onInit();
$scope.$watchCollection('model.value', function(newVal, oldVal) {
evaluateStatus();
});
}
angular.module("umbraco").controller("Umbraco.PropertyEditors.BlockList.ElementTypePickerController", ElementTypePickerController);
})();
@@ -0,0 +1,81 @@
<div id="{{model.alias}}" class="umb-block-list-element-type-picker" ng-controller="Umbraco.PropertyEditors.BlockList.ElementTypePickerController as vm">
<div>
<div class="umb-table">
<div class="umb-table-head">
<div class="umb-table-row">
<div class="umb-table-cell not-fixed umb-table__name">
<localize key="blockEditor_labelContentElementType">Custom view</localize>
</div>
<div class="umb-table-cell">
<localize key="blockEditor_labelLabelTemplate">Label</localize>
<button type="button" class="btn-reset">
<i class="icon icon-help-alt"></i>
</button>
</div>
<div class="umb-table-cell">
<localize key="blockEditor_labelCustomView">Custom view</localize>
</div>
<div class="umb-table-cell">
<localize key="blockEditor_labelSettingsElementType">Custom view</localize>
</div>
<div class="umb-table-cell action-cell">
</div>
</div>
</div>
<div class="umb-table-body" ui-sortable="vm.sortableOptions" ng-model="model.value">
<div class="umb-table-row block-entry" ng-repeat="entry in model.value">
<div class="umb-table-cell not-fixed umb-table__name">
{{ contentPreview = vm.getElementTypeByAlias(entry.elementTypeAlias); "" }}
<umb-node-preview icon="contentPreview.icon" name="contentPreview.name"></umb-node-preview>
<button type="button" class="btn-reset cell-btn --open umb-outline" ng-click="vm.openElementType(entry.elementTypeAlias)">
<i class="icon icon-edit"></i>
</button>
</div>
<div class="umb-table-cell">
<input type="text" ng-model="entry.labelTemplate" class="text-input"/>
</div>
<div class="umb-table-cell">
<div class="settings-input --hasValue" ng-if="entry.view !== null" >
<umb-node-preview icon="'icon-document'" name="entry.view"></umb-node-preview>
<button type="button" class="btn-reset cell-btn --remove umb-outline" ng-click="vm.removeViewForEntry(entry)">
<i class="icon icon-wrong"></i>
</button>
</div>
<button type="button" class="btn-reset settings-input --noValue umb-outline" ng-if="entry.view === null" ng-click="vm.addViewForEntry(entry)">
<localize key="blockEditor_addCustomView">Add custom view</localize>
</button>
</div>
<div class="umb-table-cell">
<div class="settings-input --hasValue" ng-if="entry.settingsElementTypeAlias !== null" >
{{ settingsPreview = vm.getElementTypeByAlias(entry.settingsElementTypeAlias); "" }}
<umb-node-preview icon="settingsPreview.icon" name="settingsPreview.name"></umb-node-preview>
<button type="button" class="btn-reset cell-btn --open umb-outline" ng-click="vm.openElementType(entry.settingsElementTypeAlias)">
<i class="icon icon-edit"></i>
</button>
<button type="button" class="btn-reset cell-btn --remove umb-outline" ng-click="vm.removeSettingsForEntry(entry)">
<i class="icon icon-wrong"></i>
</button>
</div>
<button type="button" class="btn-reset settings-input --noValue umb-outline" ng-if="entry.settingsElementTypeAlias === null" ng-click="vm.openPickSettingsDialog($event, entry)">
<localize key="blockEditor_addSettingsElementType">Add settings</localize>
</button>
</div>
<div class="umb-table-cell action-cell --noOverflow">
<button type="button" class="btn-icon umb-outline" ng-click="vm.removeEntryByIndex($index)">
<i class="icon icon-trash" aria-hidden="true"></i>
<localize key="general_delete" class="sr-only">Delete</localize>
</button>
</div>
</div>
</div>
</div>
<div>
<button type="button" class="btn-reset add-button" ng-click="vm.openAddDialog($event)" ng-disabled="!vm.enableAddEntry">
<localize key="general_add">Add</localize>
</button>
</div>
</div>
</div>
@@ -0,0 +1,114 @@
.umb-block-list-element-type-picker {
.block-entry {
cursor: grab;
background-color: white;
border-radius: @baseBorderRadius;
}
.umb-table-head {
button {
margin-left: 5px;
color: @ui-action-discreet-type;
&:hover {
color: @ui-action-discreet-type-hover;
}
}
}
.umb-table-cell {
padding-left: 10px;
padding-right: 0;
&.action-cell {
padding-right: 15px;
}
}
.action-cell {
flex: 0 0 30px;
}
.text-input {
width: 100%;
}
.umb-node-preview {
flex-grow: 1;
}
.cell-btn {
position: relative;
opacity: 0;
color: @ui-action-discreet-type;
height: 30px;
width: 26px;
margin-top: 1px;
&:hover {
color: @ui-action-discreet-type-hover;
}
&:last-of-type {
margin-right: 7px;
}
}
.umb-table-cell:hover,
.umb-table-cell:focus,
.umb-table-cell:focus-within {
.cell-btn {
opacity: 1;
}
}
.settings-input {
position: relative;
padding: 5px 8px;
color: @ui-action-discreet-type;
border: 1px dashed @ui-action-discreet-border;
width: 100%;
font-weight: bold;
display: flex;
flex-flow: row nowrap;
localize {
width: 100%;
}
.umb-node-preview {
padding: 3px 0;
margin-left: 5px;
}
&.--noValue {
text-align: center;
border-radius: @baseBorderRadius;
&:hover {
color: @ui-action-discreet-type-hover;
border-color: @ui-action-discreet-border-hover;
}
}
&.--hasValue {
border: 1px solid @inputBorder;
padding: 0;
}
}
.add-button {
width:100%;
color: @ui-action-discreet-type;
border: 1px dashed @ui-action-discreet-border;
border-radius: @baseBorderRadius;
display: flex;
align-items: center;
justify-content: center;
padding: 5px 15px;
box-sizing: border-box;
margin: 10px 0;
font-weight: bold;
}
.add-button:hover {
color: @ui-action-discreet-type-hover;
border-color: @ui-action-discreet-border-hover;
}
}
@@ -2393,4 +2393,13 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="formsHeadline">Umbraco Forms</key>
<key alias="formsDescription">Create forms using an intuitive drag and drop interface. From simple contact forms that sends e-mails to advanced questionaires that integrate with CRM systems. Your clients will love it!</key>
</area>
<area alias="blockEditor">
<key alias="labelContentElementType">Content model</key>
<key alias="labelLabelTemplate">Label</key>
<key alias="labelCustomView">Custom view</key>
<key alias="labelSettingsElementType">Settings model</key>
<key alias="addCustomView">Add custom view</key>
<key alias="addSettingsElementType">Add settings</key>
<key alias="labelTemplatePlaceholder">Overwrite label template</key>
</area>
</language>
@@ -2405,4 +2405,13 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="formsHeadline">Umbraco Forms</key>
<key alias="formsDescription">Create forms using an intuitive drag and drop interface. From simple contact forms that sends e-mails to advanced questionaires that integrate with CRM systems. Your clients will love it!</key>
</area>
<area alias="blockEditor">
<key alias="labelContentElementType">Content model</key>
<key alias="labelLabelTemplate">Label</key>
<key alias="labelCustomView">Custom view</key>
<key alias="labelSettingsElementType">Settings model</key>
<key alias="addCustomView">Add custom view</key>
<key alias="addSettingsElementType">Add settings</key>
<key alias="labelTemplatePlaceholder">Overwrite label template</key>
</area>
</language>
@@ -9,16 +9,35 @@ namespace Umbraco.Web.PropertyEditors
/// </summary>
public class BlockListConfiguration
{
[ConfigurationField("elementTypes", "Element Types", "views/propertyeditors/blocklist/blocklist.elementtypepicker.html", Description = "Select the Element Types to use as models for the items.")]
// TODO: rename this to blockDefinitions, cause its not elementTypes, its a dictionary of objects that define blocks, part of a block is the elementType used as content model.
[ConfigurationField("elementTypes", "Available Blocks", "views/propertyeditors/blocklist/prevalue/blocklist.elementtypepicker.html", Description = "Define the available blocks.")]
public ElementType[] ElementTypes { get; set; }
// TODO: Fill me in
[ConfigurationField("minNumber", "Minimum amount", "number")]
public int MinNumber { get; set; }
[ConfigurationField("maxNumber", "Maximum amount", "number")]
public int MaxNumber { get; set; }
public class ElementType
{
// TODO: rename this to contentElementTypeAlias, I would like this to be specific, since we have the settings.
[JsonProperty("elementTypeAlias")]
public string Alias { get; set; }
[JsonProperty("settingsElementTypeAlias")]
public string SettingsElementTypeAlias { get; set; }
[JsonProperty("view")]
public string View { get; set; }
[JsonProperty("labelTemplate")]
public string Template { get; set; }
}
[ConfigurationField("useAccordionsAsDefault", "Inline editing mode", "boolean", Description = "Use the inline editor as the default block view")]
public bool useInlineEditingAsDefault { get; set; }
}
}
@@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Umbraco.Core;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
internal class BlockListConfigurationEditor : ConfigurationEditor<BlockListConfiguration>
{
public BlockListConfigurationEditor()
{
}
}
}
@@ -22,7 +22,8 @@ namespace Umbraco.Web.PropertyEditors
{ }
#region Pre Value Editor
//protected override IConfigurationEditor CreateConfigurationEditor() => new BlockEditorListConfigurationEditor();
protected override IConfigurationEditor CreateConfigurationEditor() => new BlockListConfigurationEditor();
#endregion
+1
View File
@@ -235,6 +235,7 @@
<Compile Include="Profiling\WebProfilingController.cs" />
<Compile Include="PropertyEditors\BlockEditorPropertyEditor.cs" />
<Compile Include="PropertyEditors\BlockListConfiguration.cs" />
<Compile Include="PropertyEditors\BlockListConfigurationEditor.cs" />
<Compile Include="PropertyEditors\BlockListPropertyEditor.cs" />
<Compile Include="PropertyEditors\ParameterEditors\MultipleMediaPickerParameterEditor.cs" />
<Compile Include="PropertyEditors\RichTextEditorPastedImages.cs" />