Block Grid Editor (#12826)

* Refactor block list/grid property value editors for code reuse

* Prettify block grid editor data conveter a bit

* correct appearance and colors

* validation should only kick in if value is higher than 0 and not null

* Add copyright notice to code, a little formatting and file scoped namespaces where applicable

* custom views

* custom views for blocks

* Refactor block list min/max validation and reuse for block grid

* Prettify extraction of nested content-and-settings references for block grid

* Fix bad naming

* fix casing

* Refactor block list notification handler and reuse for block grid

* context sensitive ui for Blocks

* Refactor notification handlers for block list and block grid incl. unit tests

* Formatting and review

* Bump number of expected data editors in unit test

* initial progress on column options editor

* column span options UI

* column span UI adjustments

* context aware ui adjustments

* minor border improvement

* get and pick layout stylesheet

* remove random x

* make highlight border standout more

* dedicated context bar

* UI improvements

* remove annoying indication

* copy paste

* UI improvements

* remove div.umb-block-grid__block--view from partial

* show block ui, only hide when hovering area.

* area actions

* UI Adjustments

* Block grid editor localization (#12915)

Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>

* fix case of empty value

* use right index

* place border on top of column indication

* heroblock

* userFlowWhenBlockWasCreated

* shorter messages

* overal improvements

* force left/right drag feature

* stylesheet picker localization

* localizations

* space

* unnesecary removal of space

* notes on masonry

* validation form parent skipping + better snapping when drag n drop

* remove icons

* make rows go as minimal as posible

* scale and drop indications

* use item width to determin forceLeft or forceRight

* clean up

* Make clipboard filtering work with block arrays

* readonly mode

* important note

* ask to revert failed paste

* dont allow for backdrop click

* changed wording

* more area actions outside grid container

* droppable indication

* implemented minimal responsive solution.

* add additional data fields about grid clumns

* improved fallback columns width + rows height

* make root element class `umb-block-grid`

* align layout with position relatives, to align visual experience.

* use clientX not screenX

* rename area classes so they dont include the word 'block'

* more renaming

* commit flexbox layout css to repo

* fixes

* fix typo

* only show a broader inline create button if the block is full width.

* simplify css

* remove headline block hover effect

* use some font, to make it look better.

* Drag and drop correctiions/limits

* clean up

* remove log

* make assumptions about proposed distance of target

* slightly white border on validation message for areas to make it standout

* drag n drop seems good at this stage

* force left and right buttons and indication

* correct if

* revert scale handler ui

* If columnSpans is empty we fall back to full width

* Access other PreValues from a PreValue Editor

* maybe temprorary turn of distance drag n' drop condition

* setDirty

* area alias was missing in razor and needed help text

* forceleft forceright translations

* forceLeft / forceRight razor

* correct translation +
remove data- on ng-click

* force placement when moving over edge

* remove unnesecary wrapper element

* show-validation

* red text, might be hard to see, lets see.

* for safety have the border-raduis correct

* better wordings

* Use mouse position to judge if the drop is good before or after.

* fit placeholder when item is begin dragged away

* initial step for finding nearest relation when drag-hovering nothing

* Much better drag n drop experience, must be tested further to prove its not breaking some cases.

* title for scale handler

* new approach + massive drag n drop clean up

* avoid complex CSS transfering of props, instead add the prop in HTML

* adjust placeAfter when in vertical direction mode

* do not scale bigger than the available space to the right

* clean css

* fix numbers

* Fix the case of flickering when hitting an empty area.

* correct placement of code

* package lock

* Adjustment for PreValue Area allowances

* comment on fit in line feature

* fixing scale and drag n drop potential issues

* clean up

* only disallow above max

* outcomment unused code

* clean up

* drag n drop above or below container

* fix for Firefox

* Do not edit block if there is no properties in content

* angularJS form for Entries, to correct validation

* parse layout columns, used to know if block is full width of the layout

* use inherited layout columns

* add this to the example html

* flexbox fix

* highlight if empty area

* comments for undefined column spans

* bring back approvedContainer lock period.

* fix

* Do not edit something without properties

* Remove Force, as thats confusing to read.

* correct localization key

* minor corrections

* Fit within context columns

* Conceptual, inline editor for Grid.

* fix casing

* consider related position in directional conditions

* set default max row span to 1

* update columnSpanOption check for sync

* move Editor to group of Rich Text

* more shift autoScroll

* assume layout columns comes as string

* fix variable name

* force left/right indication

* Inject Areas directly in slot, to enable custom view manipulations of such.

* fix sortableJS mis dropping items

* Overwrite create label

* ability to overwrite root layout create label

* Simplify PreValue editor by hidding options that is not in effect.

* Setup new areas as half width if possible

* Grouping blockTypes

* remove flexbox stylesheet

* Chose groups for area allowance

* ensure a good default width

* improve block active state

* Better contextual sizing

* Comment clean up

* Remove The StarterKit from branch

* Unique group identification for the property editor sortable

* only show avaiable block groups in picker.

* Indication of taget area

* Fallback to root grid columns

* use root layout grid columns, so dont update when layoutColumns change

* Ability to remove block group

* clean up references when removing Group or single Block

* fix drop in same group

* Block picker use contexual create label as headline

* adjust area highlight

* Prevalue editor

* structuralOptions

* Move pre values into tabs for better overview and scope

* Let area grid columns fallback to root grid columns, and let both default to "12" instead of "initial"

* remove input close tag

* Allow in areas

* Move build-in custom views

* only show property-into-button on hover when in group-panel__header

* some height for the show buttons

* filter available block type based on allowInAreas

* remove OnlySpecifiedAllowance

* unsupported block if trouble happend

* move allow at root to allowance group

* easily drop at top or below area if outside. If more than 100px outside then see if there is a parent layout to move into.

* block group validation

* shortcut for opening Areas if areas are defined

* scale label for Areas editor

* Added Legacy name on the old grid layout

* Remove files that come with the starter kit

* Sorting: takeover container detection

* Refactor models + remove unused properties

* Endpoint for creating block grid demo element types

* CTA for getting Block Grid demo blocks

* Refactor block grid sample element creation

* Fix Constants-PropertyEditors spacing

* Fixed unit tests

* Get sample configuration

* Labels for sample blocks

* Improve drag, to swap across unallowed area/root

* improved empty threshold

* hide the after inline-create-button if block is located at right side of area/layout

* clean up

* translations

* danish translations

* move outside edge for forceRight

* Update src/Umbraco.Web.Common/Extensions/BlockGridTemplateExtensions.cs

Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>

* Review comments

* Remove leftover TODO

* big cleanup

* Hide Sample CTA when installed

* clean up

* correct styling for wrapper + moving Block UI to z-index 3 for visiblity on top of other block

* slightly adjusting inline-create-buttons to avoid collisions

* correct localization for force left/right buttons

* gitignorer update

* Clean up nesting of classes in BlockGridConfiguration

* change gitignorer to include App_Plugin folder

* move default layout stylesheet

* remove specific App_plugin folder

* package-lock

* update sample custom views paths

* add custom views for sample

* Adding sample custom views

* Move sample partial Views and custom views

* Update views to not use ModelsBuilder

* Move sample custom views to wwwroot

* Updated Sample CTA text

* Use localize directory

* Ensure groupKey for items without such to work.

Co-authored-by: kjac <kja@umbraco.dk>
Co-authored-by: Warren Buckley <warren@umbraco.com>
Co-authored-by: Bjarke Berg <mail@bergmania.dk>
Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>
This commit is contained in:
Niels Lyngsø
2022-10-05 13:50:26 +02:00
committed by GitHub
parent efb994ecf7
commit 367a4b9727
137 changed files with 10847 additions and 1013 deletions

View File

@@ -0,0 +1,192 @@
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.Common.ActionsResults;
using Umbraco.Extensions;
namespace Umbraco.Cms.Web.BackOffice.Controllers;
internal class BlockGridSampleHelper
{
private const string ContainerName = "Umbraco Block Grid Demo";
private readonly IContentTypeService _contentTypeService;
private readonly IDataTypeService _dataTypeService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
public BlockGridSampleHelper(IContentTypeService contentTypeService, IDataTypeService dataTypeService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
{
_contentTypeService = contentTypeService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
_dataTypeService = dataTypeService;
}
/// <summary>
/// Creates block grid elements for sample purposes:
/// - a "Headline" block with a text box
/// - an "Image" block with a single value media picker
/// - a "Rich Text" block with an RTE
/// - an empty "Two Column Layout" (will be used for layouting nested blocks)
/// </summary>
/// <param name="createElement">The function that will perform the actual element creation</param>
/// <param name="errorMessage">If an error occurs, this message will describe that error</param>
/// <returns>A mapping table between element aliases and the created element UDIs, or null if an error occurs</returns>
public Dictionary<string, Udi>? CreateSampleElements(Func<DocumentTypeSave, ActionResult<IContentType?>> createElement, out string errorMessage)
{
errorMessage = string.Empty;
EntityContainer? container = GetOrCreateContainer();
if (container == null)
{
errorMessage = $"Unable to get or create content type container: {ContainerName}";
return null;
}
FindDataTypes(out IDataType? textBox, out IDataType? tinyMce, out IDataType? mediaPicker);
if (textBox == null || tinyMce == null || mediaPicker == null)
{
errorMessage = $"Could not find required data types - must have a text box, a rich text editor and a media picker (configured in single picker mode)";
return null;
}
// get any already created elements
IContentType[] existingContentTypes = _contentTypeService.GetChildren(container.Id).ToArray();
// this is the return value (will be populated below)
var elementUdisByAlias = new Dictionary<string, Udi>();
// these describe the block grid elements we want to create
var elementDescriptors = new[]
{
new
{
Alias = "umbBlockGridDemoHeadlineBlock",
Icon = "icon-font color-black",
Name = "Headline",
Property = new { Alias = "headline", Label = "Headline", EditorId = textBox.Id }
},
new
{
Alias = "umbBlockGridDemoImageBlock",
Icon = "icon-umb-media color-black",
Name = "Image",
Property = new { Alias = "image", Label = "Image", EditorId = mediaPicker.Id }
},
new
{
Alias = "umbBlockGridDemoRichTextBlock",
Icon = "icon-script color-black",
Name = "Rich Text",
Property = new { Alias = "richText", Label = "Text", EditorId = tinyMce.Id }
},
new
{
Alias = "umbBlockGridDemoTwoColumnLayoutBlock",
Icon = "icon-book-alt color-black",
Name = "Two Column Layout",
Property = new { Alias = string.Empty, Label = string.Empty, EditorId = -1 }
}
};
foreach (var elementDescriptor in elementDescriptors)
{
IContentType? contentType = existingContentTypes.FirstOrDefault(c => c.Alias == elementDescriptor.Alias);
if (contentType != null)
{
elementUdisByAlias[elementDescriptor.Alias] = contentType.GetUdi();
continue;
}
var documentTypeSave = new DocumentTypeSave
{
Alias = elementDescriptor.Alias,
Icon = elementDescriptor.Icon,
Name = elementDescriptor.Name,
IsElement = true,
Groups = new[]
{
new PropertyGroupBasic<PropertyTypeBasic>
{
Alias = "content",
Name = "Content",
Type = PropertyGroupType.Group,
Properties = elementDescriptor.Property.Alias.IsNullOrWhiteSpace()
? Array.Empty<PropertyTypeBasic>()
: new[]
{
new PropertyTypeBasic
{
Alias = elementDescriptor.Property.Alias,
Label = elementDescriptor.Property.Label,
Validation = new PropertyTypeValidation { Mandatory = true },
DataTypeId = elementDescriptor.Property.EditorId
}
}
}
},
ParentId = container.Id,
Thumbnail = "folder.png"
};
ActionResult<IContentType?> result = createElement(documentTypeSave);
if (result.Value != null)
{
elementUdisByAlias[elementDescriptor.Alias] = result.Value.GetUdi();
continue;
}
if (result.Result is ValidationErrorResult validationErrorResult)
{
errorMessage = validationErrorResult.Value switch
{
string error => error,
Exception exception => exception.Message,
_ => string.Empty
};
}
if(errorMessage.IsNullOrWhiteSpace())
{
errorMessage = $"Could not create element type: {elementDescriptor.Name}";
}
return null;
}
return elementUdisByAlias;
}
private EntityContainer? GetOrCreateContainer()
{
var userId = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Id ?? -1;
EntityContainer? container = _contentTypeService.GetContainers(ContainerName, 1).FirstOrDefault();
if (container == null)
{
Attempt<OperationResult<OperationResultType, EntityContainer>?> attempt =
_contentTypeService.CreateContainer(Constants.System.Root, Guid.NewGuid(), ContainerName, userId);
container = attempt.Result?.Entity;
}
return container;
}
private void FindDataTypes(out IDataType? textBox, out IDataType? tinyMce, out IDataType? mediaPicker)
{
// order by ID to prioritize default installed data types
IDataType[] dataTypes = _dataTypeService.GetAll().OrderBy(d => d.Id).ToArray();
textBox = dataTypes.FirstOrDefault(d => d.EditorAlias == Constants.PropertyEditors.Aliases.TextBox);
tinyMce = dataTypes.FirstOrDefault(d => d.EditorAlias == Constants.PropertyEditors.Aliases.TinyMce);
mediaPicker = dataTypes.Where(d =>
d.EditorAlias == Constants.PropertyEditors.Aliases.MediaPicker3
&& d.ConfigurationAs<MediaPicker3Configuration>()?.Multiple == false)
// prioritize the default "Image Media Picker" if it exists
.MinBy(d => d.Name == "Image Media Picker" ? 0 : 1);
}
}