Updates the placement editor screen to support moving shapes into tabs (#7795)

Fixes #7791
This commit is contained in:
Hazzamanic
2017-08-10 20:14:33 +01:00
committed by Sébastien Ros
parent 7373c11a1e
commit af922a01be
8 changed files with 310 additions and 164 deletions

View File

@@ -148,9 +148,21 @@ namespace Orchard.ContentTypes.Controllers {
if (contentTypeDefinition == null) if (contentTypeDefinition == null)
return HttpNotFound(); return HttpNotFound();
var grouped = _placementService.GetEditorPlacement(id)
.OrderBy(x => x.PlacementInfo.GetPosition(), new FlatPositionComparer())
.ThenBy(x => x.PlacementSettings.ShapeType)
.Where(e => e.PlacementSettings.Zone == "Content")
.GroupBy(x => x.PlacementInfo.GetTab())
.ToDictionary(x => x.Key, y => y.ToList());
var content = grouped.ContainsKey("") ? grouped[""] : new List<DriverResultPlacement>();
var listPlacements = grouped.Values.SelectMany(e => e).ToList();
grouped.Remove("");
var placementModel = new EditPlacementViewModel { var placementModel = new EditPlacementViewModel {
PlacementSettings = contentTypeDefinition.GetPlacement(PlacementType.Editor), Content = content,
AllPlacements = _placementService.GetEditorPlacement(id).OrderBy(x => x.PlacementSettings.Position, new FlatPositionComparer()).ThenBy(x => x.PlacementSettings.ShapeType).ToList(), AllPlacements = listPlacements,
Tabs = grouped,
ContentTypeDefinition = contentTypeDefinition, ContentTypeDefinition = contentTypeDefinition,
}; };
@@ -168,20 +180,10 @@ namespace Orchard.ContentTypes.Controllers {
if (contentTypeDefinition == null) if (contentTypeDefinition == null)
return HttpNotFound(); return HttpNotFound();
var allPlacements = _placementService.GetEditorPlacement(id).ToList();
var result = new List<PlacementSettings>(contentTypeDefinition.GetPlacement(PlacementType.Editor));
contentTypeDefinition.ResetPlacement(PlacementType.Editor); contentTypeDefinition.ResetPlacement(PlacementType.Editor);
foreach(var driverPlacement in viewModel.AllPlacements) { foreach (var placement in viewModel.AllPlacements) {
// if the placement has changed, persist it var placementSetting = placement.PlacementSettings;
if (!allPlacements.Any(x => x.PlacementSettings.Equals(driverPlacement.PlacementSettings))) {
result = result.Where(x => !x.IsSameAs(driverPlacement.PlacementSettings)).ToList();
result.Add(driverPlacement.PlacementSettings);
}
}
foreach(var placementSetting in result) {
contentTypeDefinition.Placement(PlacementType.Editor, contentTypeDefinition.Placement(PlacementType.Editor,
placementSetting.ShapeType, placementSetting.ShapeType,
placementSetting.Differentiator, placementSetting.Differentiator,
@@ -447,8 +449,7 @@ namespace Orchard.ContentTypes.Controllers {
[HttpPost, ActionName("EditPart")] [HttpPost, ActionName("EditPart")]
[FormValueRequired("submit.Delete")] [FormValueRequired("submit.Delete")]
public ActionResult DeletePart(string id) public ActionResult DeletePart(string id) {
{
if (!Services.Authorizer.Authorize(Permissions.EditContentTypes, T("Not allowed to delete a content part."))) if (!Services.Authorizer.Authorize(Permissions.EditContentTypes, T("Not allowed to delete a content part.")))
return new HttpUnauthorizedResult(); return new HttpUnauthorizedResult();

View File

@@ -147,6 +147,7 @@
<ItemGroup> <ItemGroup>
<Content Include="Module.txt" /> <Content Include="Module.txt" />
<Content Include="Scripts\admin-contenttypes.js" /> <Content Include="Scripts\admin-contenttypes.js" />
<Content Include="Scripts\admin-placementeditor.js" />
<Content Include="Styles\Images\menu.contenttypes.png" /> <Content Include="Styles\Images\menu.contenttypes.png" />
<Content Include="Styles\Images\move.gif" /> <Content Include="Styles\Images\move.gif" />
<Content Include="Styles\menu.contenttypes-admin.css" /> <Content Include="Styles\menu.contenttypes-admin.css" />

View File

@@ -4,6 +4,10 @@ namespace Orchard.ContentTypes {
public class ResourceManifest : IResourceManifestProvider { public class ResourceManifest : IResourceManifestProvider {
public void BuildManifests(ResourceManifestBuilder builder) { public void BuildManifests(ResourceManifestBuilder builder) {
builder.Add().DefineStyle("ContentTypesAdmin").SetUrl("orchard-contenttypes-admin.css"); builder.Add().DefineStyle("ContentTypesAdmin").SetUrl("orchard-contenttypes-admin.css");
builder.Add().DefineScript("PlacementEditor")
.SetUrl("admin-placementeditor.js")
.SetDependencies("jQueryUI_Sortable");
} }
} }
} }

View File

@@ -0,0 +1,130 @@
(function ($) {
var assignPositions = function () {
var position = 0;
$('.type').each(function () {
var input = $(this);
var tab = input.closest(".zone-container").data("tab");
//input = input.next();
var postab = tab != "" ? position + "#" + tab : position + "";
reAssignIdName(input, position); // type
input = input.next();
reAssignIdName(input, position); // differentiator
input = input.next();
reAssignIdName(input, position); // zone
input = input.next();
reAssignIdName(input, position); // position
input.val(postab);
position++;
});
};
var reAssignIdName = function (input, pos) {
var name = input.attr('name');
input.attr('name', name.replace(new RegExp("\\[.*\\]", 'gi'), '[' + pos + ']'));
var id = input.attr('id');
input.attr('id', id.replace(new RegExp('_.*__', 'i'), '_' + pos + '__'));
};
var startPos;
function initTab() {
$(".tabdrag").sortable({
placeholder: "placement-placeholder",
connectWith: ".tabdrag",
stop: function (event, ui) {
assignPositions();
}
});
}
$('#sortableTabs').sortable({
placeholder: "tab-placeholder",
start: function (event, ui) {
var self = $(ui.item);
startPos = self.prevAll().size();
},
stop: function (event, ui) {
assignPositions();
$('#save-message').show();
}
});
$("#newTab").click(function (e) {
e.preventDefault();
// get the new tab name, cancel if blank
var tab = $("#tabName").val().replace(/\s/g, "");
if (!tab.length) {
return;
}
if (tab.toLowerCase() === "content") {
$("#tabName").val("");
return;
}
// in tabs already
var tabs = getTabs(true);
if ($.inArray(tab, tabs) >= 0) {
$("#tabName").val("");
return;
}
$("#sortableTabs").append('<div data-tab="' + tab + '" class="zone-container tab-container"><h2><a class="delete">Delete</a>'
+ tab + '</h2><ul class="tabdrag"></ul></div>'
);
// make it sortable
initTab();
$("#sortableTabs").sortable("refresh");
// clear the textbox
$("#tabName").val("");
});
// remove tabs
// append items to content, create content if not there
$("#placement").on("click", ".delete", function (e) {
var me = $(this);
var parent = me.parent(".zone-container");
var list = parent.children(".tabdrag").html();
// get first tab
var newList = $("#placement .tabdrag").first();
if (newList.length) {
parent.remove();
newList.append(list);
}
assignPositions();
});
// toggle editor shapes
$("#placement").on("click", ".toggle", function (e) {
var $this = $(this);
var shape = $(this).next().next();
shape.toggle();
if ($this.text() == 'Show Editor Shape') {
$this.text('Hide Editor Shape');
} else {
$this.text('Show Editor Shape');
}
})
// returns all the tabs
function getTabs(header) {
var tabs = [];
$(".zone-container").each(function (index, e) {
tabs.push($(e).data("tab"));
});
return tabs;
}
initTab();
assignPositions();
$('.shape-editor *').attr('disabled', 'disabled');
$("#placement").disableSelection();
})(jQuery);

View File

@@ -18,6 +18,7 @@ using Orchard.UI.Zones;
namespace Orchard.ContentTypes.Services { namespace Orchard.ContentTypes.Services {
public class DriverResultPlacement { public class DriverResultPlacement {
public PlacementInfo PlacementInfo { get; set; }
public PlacementSettings PlacementSettings { get; set; } public PlacementSettings PlacementSettings { get; set; }
public DriverResult ShapeResult { get; set; } public DriverResult ShapeResult { get; set; }
public dynamic Shape { get; set; } public dynamic Shape { get; set; }
@@ -201,6 +202,7 @@ namespace Orchard.ContentTypes.Services {
yield return new DriverResultPlacement { yield return new DriverResultPlacement {
PlacementInfo = placement,
Shape = itemShape.Content, Shape = itemShape.Content,
ShapeResult = contentShapeResult, ShapeResult = contentShapeResult,
PlacementSettings = new PlacementSettings { PlacementSettings = new PlacementSettings {

View File

@@ -176,24 +176,56 @@ fieldset.action {
/* PLACEMENT EDITOR */ /* PLACEMENT EDITOR */
.tabdrag {
min-height: 20px;
}
#placement li { .zone-container {
border: 1px dotted #6F6F6F;
padding: 10px;
margin-bottom: 10px;
}
.zone-container li {
margin-bottom: 10px; margin-bottom: 10px;
padding: 0px; padding: 0px;
border: 1px solid #eee; border: 1px solid #eee;
} }
#placement li .shape-type { #placement .zone-container h2 {
cursor: move; cursor: move;
background-color: #eee; margin-top: 0px;
background: #EEE url(images/move.gif) no-repeat 10px 15px; padding-left: 20px;
height: 30px; padding-bottom: 15px;
padding: 10px 0px 0px 30px;
} }
#placement li .shape-editor { #placement .tab-container {
background: white url(images/move.gif) no-repeat 10px 15px;
}
.zone-container li .shape-type {
cursor: move;
background-color: #eee;
background: #EEE url(images/move.gif) no-repeat 10px 10px;
height: 30px;
padding: 5px 0px 0px 30px;
}
#placement #content-tab {
cursor: default;
}
.zone-container li .toggle {
float: right;
padding-top: 5px;
padding-right: 5px;
cursor: pointer;
}
.zone-container li .shape-editor {
padding: 10px; padding: 10px;
background: white; background: white;
display: none;
} }
#save-message { #save-message {
@@ -201,13 +233,24 @@ fieldset.action {
margin-bottom: 10px; margin-bottom: 10px;
} }
#placement fieldset { .zone-container fieldset {
float: inherit; /* prevent bad layout if float is defined to left in specific parts, e.g. datetimepicker */ float: inherit; /* prevent bad layout if float is defined to left in specific parts, e.g. datetimepicker */
height: auto; height: auto;
} }
.tab-placeholder {
background: #FDF5BC;
border: 1px solid #FDF5BC;
height: 100px;
}
.placement-placeholder { .placement-placeholder {
background: #FDF5BC; background: #FDF5BC;
border: 1px solid #FDF5BC; border: 1px solid #FDF5BC;
height: 100px; height: 40px;
}
.zone-container .delete {
float: right;
cursor: pointer;
} }

View File

@@ -6,7 +6,8 @@ using Orchard.ContentTypes.Settings;
namespace Orchard.ContentTypes.ViewModels { namespace Orchard.ContentTypes.ViewModels {
public class EditPlacementViewModel { public class EditPlacementViewModel {
public ContentTypeDefinition ContentTypeDefinition { get; set; } public ContentTypeDefinition ContentTypeDefinition { get; set; }
public PlacementSettings[] PlacementSettings { get; set; }
public List<DriverResultPlacement> AllPlacements { get; set; } public List<DriverResultPlacement> AllPlacements { get; set; }
public Dictionary<string, List<DriverResultPlacement>> Tabs { get; set; }
public List<DriverResultPlacement> Content { get; set; }
} }
} }

View File

@@ -1,45 +1,67 @@
@using Orchard.ContentTypes.Services
@model Orchard.ContentTypes.ViewModels.EditPlacementViewModel @model Orchard.ContentTypes.ViewModels.EditPlacementViewModel
@{ @{
Style.Require("ContentTypesAdmin"); Style.Require("ContentTypesAdmin");
Script.Require("jQueryUI_Sortable"); Script.Require("PlacementEditor").AtFoot();
Layout.Title = T("Edit Placement - {0}", Model.ContentTypeDefinition.DisplayName).ToString(); Layout.Title = T("Edit Placement - {0}", Model.ContentTypeDefinition.DisplayName).ToString();
var hiddenShapes = Model.AllPlacements.Where(x => String.IsNullOrEmpty(x.PlacementSettings.Zone) && (String.IsNullOrWhiteSpace(x.PlacementSettings.Position) || x.PlacementSettings.Position == "-")); int i = 0;
} }
<div id="save-message" class="message message-Warning">@T("You need to hit \"Save\" in order to save your changes.")</div> @helper RenderPlacement(DriverResultPlacement p, int i) {
var placement = p.PlacementSettings;
@using (Html.BeginFormAntiForgeryPost()) { <li class="place" data-shape-type="@placement.ShapeType" data-shape-differentiator="@placement.Differentiator" data-shape-zone="Content" data-shape-position="@placement.Position">
@Html.ValidationSummary() <span class="toggle">Show Editor Shape</span>
<ul id="placement">
@for (int i = 0; i < Model.AllPlacements.Count; i++ ) {
var placement = Model.AllPlacements[i].PlacementSettings;
if(placement.Zone != "Content") {
continue;
}
<li data-shape-type="@placement.ShapeType" data-shape-differentiator="@placement.Differentiator" data-shape-zone="Content" data-shape-position="@placement.Position">
<div class="shape-type"><h3>@placement.ShapeType @placement.Differentiator</h3></div> <div class="shape-type"><h3>@placement.ShapeType @placement.Differentiator</h3></div>
<div class="shape-editor"> <div class="shape-editor">
@try { @try {
@Display(Model.AllPlacements[i].Shape) @Display(p.Shape)
} }
catch { catch {
} }
</div> </div>
@* @shape.Position @(Model.PlacementSettings.Any(x => x.Equals(shape)))*@
@Html.HiddenFor(m => m.AllPlacements[i].PlacementSettings.ShapeType, new { @class = "type" }) @Html.HiddenFor(m => m.AllPlacements[i].PlacementSettings.ShapeType, new { @class = "type" })
@Html.HiddenFor(m => m.AllPlacements[i].PlacementSettings.Differentiator, new { @class = "differentiator" }) @Html.HiddenFor(m => m.AllPlacements[i].PlacementSettings.Differentiator, new { @class = "differentiator" })
@Html.HiddenFor(m => m.AllPlacements[i].PlacementSettings.Zone, new { @class = "zone" }) @Html.HiddenFor(m => m.AllPlacements[i].PlacementSettings.Zone, new { @class = "zone" })
@Html.HiddenFor(m => m.AllPlacements[i].PlacementSettings.Position, new { @class = "position" }) @Html.HiddenFor(m => m.AllPlacements[i].PlacementSettings.Position, new { @class = "position" })
</li> </li>
} }
<div id="save-message" class="message message-Warning">@T("You need to hit \"Save\" in order to save your changes.")</div>
@using (Html.BeginFormAntiForgeryPost()) {
@Html.ValidationSummary()
<div id="placement">
<div data-tab="" class="zone-container" id="content-tab">
<h2>Content</h2>
<ul class="tabdrag">
@foreach (var p in Model.Content) {
@RenderPlacement(p, i);
i++;
}
</ul> </ul>
</div>
<div id="sortableTabs">
@foreach (var tab in Model.Tabs) {
<div data-tab="@tab.Key" class="zone-container tab-container">
<a class="delete">Delete</a>
<h2>@tab.Key</h2>
<ul class="tabdrag">
@foreach (var p in tab.Value) {
@RenderPlacement(p, i);
i++;
}
</ul>
</div>
}
</div>
</div>
<div>
<input type="text" id="tabName" />
<button class="primaryAction" id="newTab">@T("New Tab")</button>
</div>
<fieldset class="action"> <fieldset class="action">
<button class="primaryAction" type="submit" name="submit.Save" value="Save">@T("Save")</button> <button class="primaryAction" type="submit" name="submit.Save" value="Save">@T("Save")</button>
@@ -47,61 +69,3 @@
</fieldset> </fieldset>
} }
@using (Script.Foot()) {
<script type="text/javascript">
//<![CDATA[
(function ($) {
var assignPositions = function () {
var position = 0;
$('.type').each(function () {
var input = $(this);
reAssignIdName(input, position); // type
input = input.next();
reAssignIdName(input, position); // differentiator
input = input.next();
reAssignIdName(input, position); // zone
input = input.next();
reAssignIdName(input, position); // position
input.val(++position);
});
};
var reAssignIdName = function (input, pos) {
var name = input.attr('name');
input.attr('name', name.replace(new RegExp("\\[.*\\]", 'gi'), '[' + pos + ']'));
var id = input.attr('id');
input.attr('id', id.replace(new RegExp('_.*__', 'i'), '_' + pos + '__'));
};
assignPositions();
var startPos;
$('#placement').sortable({
placeholder: "placement-placeholder",
start: function (event, ui) {
var self = $(ui.item);
startPos = self.prevAll().size();
},
stop: function (event, ui) {
assignPositions();
$('#save-message').show();
}
});
$('.shape-editor *').attr('disabled', 'disabled');
$("#placement").disableSelection();
})(jQuery);
//]]>
</script>
}