From e7bc4986a5b7f4d84240809f688fdc8af5b651da Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 3 Apr 2018 19:25:43 +1000 Subject: [PATCH] Start updating the model/controller for editing variants in the back office and wiring up the angular side of things --- src/Umbraco.Core/Models/Content.cs | 14 +---- src/Umbraco.Core/Models/IContent.cs | 8 +-- src/Umbraco.Tests/Models/ContentTests.cs | 4 -- .../components/content/edit.controller.js | 55 ----------------- .../content/umbcontentnodeinfo.directive.js | 4 +- .../components/editor/umb-editor-header.html | 8 +-- .../Editors/ContentControllerBase.cs | 2 +- .../Models/ContentEditing/ContentItemBasic.cs | 10 ++- .../ContentEditing/ContentItemDisplay.cs | 8 ++- .../Models/ContentEditing/ContentVariation.cs | 52 ++++++++++++++++ src/Umbraco.Web/Models/ContentExtensions.cs | 7 +++ .../Models/Mapping/ContentMapperProfile.cs | 4 +- .../Models/Mapping/VariationResolver.cs | 61 +++++++++++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 2 + 14 files changed, 151 insertions(+), 88 deletions(-) create mode 100644 src/Umbraco.Web/Models/ContentEditing/ContentVariation.cs create mode 100644 src/Umbraco.Web/Models/Mapping/VariationResolver.cs diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index 0dad772a91..f468d8be90 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -17,7 +17,6 @@ namespace Umbraco.Core.Models private ITemplate _template; private bool _published; private PublishedState _publishedState; - private string _language; private DateTime? _releaseDate; private DateTime? _expireDate; private string _nodeName;//NOTE Once localization is introduced this will be the non-localized Node Name. @@ -79,7 +78,6 @@ namespace Umbraco.Core.Models { public readonly PropertyInfo TemplateSelector = ExpressionHelper.GetPropertyInfo(x => x.Template); public readonly PropertyInfo PublishedSelector = ExpressionHelper.GetPropertyInfo(x => x.Published); - public readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.Language); public readonly PropertyInfo ReleaseDateSelector = ExpressionHelper.GetPropertyInfo(x => x.ReleaseDate); public readonly PropertyInfo ExpireDateSelector = ExpressionHelper.GetPropertyInfo(x => x.ExpireDate); public readonly PropertyInfo NodeNameSelector = ExpressionHelper.GetPropertyInfo(x => x.NodeName); @@ -149,7 +147,7 @@ namespace Umbraco.Core.Models /// is true or false, but can also temporarily be Publishing or Unpublishing when the /// content item is about to be saved. [DataMember] - internal PublishedState PublishedState + public PublishedState PublishedState { get => _publishedState; set @@ -163,16 +161,6 @@ namespace Umbraco.Core.Models [IgnoreDataMember] public bool Edited { get; internal set; } - /// - /// Language of the data contained within this Content object. - /// - [Obsolete("This is not used and will be removed from the codebase in future versions")] - [EditorBrowsable(EditorBrowsableState.Never)] - public string Language - { - get => _language; - set => SetPropertyValueAndDetectChanges(value, ref _language, Ps.Value.LanguageSelector); - } /// /// The date this Content should be released and thus be published diff --git a/src/Umbraco.Core/Models/IContent.cs b/src/Umbraco.Core/Models/IContent.cs index 06735023aa..d551421c00 100644 --- a/src/Umbraco.Core/Models/IContent.cs +++ b/src/Umbraco.Core/Models/IContent.cs @@ -18,6 +18,8 @@ namespace Umbraco.Core.Models /// bool Published { get; } + PublishedState PublishedState { get; } + /// /// Gets a value indicating whether the content has been edited. /// @@ -54,11 +56,7 @@ namespace Umbraco.Core.Models /// Gets the date and time the content was published. /// DateTime? PublishDate { get; } - - [Obsolete("This will be removed in future versions")] - [EditorBrowsable(EditorBrowsableState.Never)] - string Language { get; set; } - + /// /// Gets or sets the date and time the content item should be published. /// diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index 8244cb75ef..3277a1cd4e 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -194,7 +194,6 @@ namespace Umbraco.Tests.Models content.CreatorId = 22; content.ExpireDate = DateTime.Now; content.Key = Guid.NewGuid(); - content.Language = "en"; content.Level = 3; content.Path = "-1,4,10"; content.ReleaseDate = DateTime.Now; @@ -254,7 +253,6 @@ namespace Umbraco.Tests.Models content.CreatorId = 22; content.ExpireDate = DateTime.Now; content.Key = Guid.NewGuid(); - content.Language = "en"; content.Level = 3; content.Path = "-1,4,10"; content.ReleaseDate = DateTime.Now; @@ -294,7 +292,6 @@ namespace Umbraco.Tests.Models Assert.AreEqual(clone.CreatorId, content.CreatorId); Assert.AreEqual(clone.ExpireDate, content.ExpireDate); Assert.AreEqual(clone.Key, content.Key); - Assert.AreEqual(clone.Language, content.Language); Assert.AreEqual(clone.Level, content.Level); Assert.AreEqual(clone.Path, content.Path); Assert.AreEqual(clone.ReleaseDate, content.ReleaseDate); @@ -355,7 +352,6 @@ namespace Umbraco.Tests.Models content.CreatorId = 22; content.ExpireDate = DateTime.Now; content.Key = Guid.NewGuid(); - content.Language = "en"; content.Level = 3; content.Path = "-1,4,10"; content.ReleaseDate = DateTime.Now; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index c8ec281fb2..ead0f2be27 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -53,61 +53,6 @@ // We don't get the info tab from the server from version 7.8 so we need to manually add it //contentEditingHelper.addInfoTab($scope.content.tabs); - // prototype variants - $scope.content.variants = [ - { - "cultureDisplayName": "English (United States)", - "culture": "en-US", - "current": true, - "segments" : [ - { - "name": "Mobile" - }, - { - "name": "Job campign" - } - ], - "state": "Published" - }, - { - "cultureDisplayName": "Danish", - "culture": "da-DK", - "current": false, - "segments" : [ - { - "name": "Mobile" - } - ], - "state": "Published" - }, - { - "cultureDisplayName": "Spanish (Spain)", - "culture": "es-ES", - "current": false, - "state": "Published (pending changes)" - }, - { - "cultureDisplayName": "French (France)", - "culture": "fr-FR", - "current": false, - "segments" : [ - { - "name": "Mobile" - }, - { - "name": "Job campign" - } - ], - "state": "Draft" - }, - { - "cultureDisplayName": "German (Germany)", - "culture": "de-DE", - "current": false, - "state": "Draft" - } - ]; - // prototype content and info apps var contentApp = { "name": "Content", diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index b2bbd9006a..93b9685155 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -136,13 +136,13 @@ } // published node - if(node.hasPublishedVersion === true && node.publishDate && node.published === true) { + if(node.publishDate && node.published === true) { scope.publishStatus.label = localizationService.localize("content_published"); scope.publishStatus.color = "success"; } // published node with pending changes - if(node.hasPublishedVersion === true && node.publishDate && node.published === false) { + if (node.edited === true && node.publishDate) { scope.publishStatus.label = localizationService.localize("content_publishedPendingChanges"); scope.publishStatus.color = "success" } diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html index b436af06f6..2ce16c5609 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html @@ -50,15 +50,15 @@ server-validation-field="Alias"> - - {{vm.currentVariant.cultureDisplayName}} + + {{vm.currentVariant.language.name}}   - {{variant.cultureDisplayName}} + {{variant.language.name}} {{variant.state}}
Open in split view
@@ -115,4 +115,4 @@ view="dialogModel.view"> - \ No newline at end of file + diff --git a/src/Umbraco.Web/Editors/ContentControllerBase.cs b/src/Umbraco.Web/Editors/ContentControllerBase.cs index a020de1468..5e1ceb878a 100644 --- a/src/Umbraco.Web/Editors/ContentControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentControllerBase.cs @@ -25,7 +25,7 @@ namespace Umbraco.Web.Editors { protected HttpResponseMessage HandleContentNotFound(object id, bool throwException = true) { - ModelState.AddModelError("id", string.Format("content with id: {0} was not found", id)); + ModelState.AddModelError("id", $"content with id: {id} was not found"); var errorResponse = Request.CreateErrorResponse( HttpStatusCode.NotFound, ModelState); diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs index ca697e2677..c04dd9ab8d 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs @@ -23,6 +23,12 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "published")] public bool Published { get; set; } + + /// + /// Determines if the content item is a draft + /// + [DataMember(Name = "edited")] + public bool Edited { get; set; } [DataMember(Name = "owner")] public UserProfile Owner { get; set; } @@ -75,8 +81,8 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "properties")] public virtual IEnumerable Properties { - get { return _properties; } - set { _properties = value; } + get => _properties; + set => _properties = value; } /// diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs index cdf13fee7c..937414cf6a 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs @@ -23,8 +23,14 @@ namespace Umbraco.Web.Models.ContentEditing public DateTime? ReleaseDate { get; set; } [DataMember(Name = "removeDate")] - public DateTime? ExpireDate { get; set; } + public DateTime? ExpireDate { get; set; } + /// + /// Represents the variant info for a content item + /// + [DataMember(Name = "variants")] + public IEnumerable Variants { get; set; } + [DataMember(Name = "template")] public string TemplateAlias { get; set; } diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentVariation.cs b/src/Umbraco.Web/Models/ContentEditing/ContentVariation.cs new file mode 100644 index 0000000000..917b701cec --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/ContentVariation.cs @@ -0,0 +1,52 @@ +using System; +using System.Runtime.Serialization; +using Umbraco.Core.Models; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// Represents the variant info for a content item + /// + [DataContract(Name = "contentVariant", Namespace = "")] + public class ContentVariation + { + /// + /// The content name of the variant + /// + [DataMember(Name = "name")] + public string Name { get; set; } + + [DataMember(Name = "language")] + public Language Language { get; set; } + + [DataMember(Name = "segment")] + public string Segment { get; set; } + + //fixme not sure if we need these dates as metadata for displaying the variant info in the drop down? + // when we move to being able to edit all variants and switching then this might be irrelevant + + [DataMember(Name = "publishDate")] + public DateTime? PublishDate { get; set; } + + [DataMember(Name = "releaseDate")] + public DateTime? ReleaseDate { get; set; } + + [DataMember(Name = "removeDate")] + public DateTime? ExpireDate { get; set; } + + [DataMember(Name = "state")] + public string PublishedState { get; set; } + + /// + /// Determines if the content variant for this culture has been created + /// + [DataMember(Name = "exists")] + public bool Exists { get; set; } + + /// + /// Determines if this is the variant currently being edited + /// + [DataMember(Name = "current")] + public bool IsCurrent { get; set; } + } +} diff --git a/src/Umbraco.Web/Models/ContentExtensions.cs b/src/Umbraco.Web/Models/ContentExtensions.cs index 4a016a895b..defd7cccc9 100644 --- a/src/Umbraco.Web/Models/ContentExtensions.cs +++ b/src/Umbraco.Web/Models/ContentExtensions.cs @@ -11,6 +11,13 @@ namespace Umbraco.Web.Models { public static class ContentExtensions { + public static bool HasVariation(this IContent content, int langId, string segment = null) + { + //TODO: Wire up with new APIs + return false; + //return content.Languages.FirstOrDefault(x => x == langId); + } + /// /// Gets the culture that would be selected to render a specified content, /// within the context of a specified current request. diff --git a/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs index 1900296e3f..6791afb921 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs @@ -13,7 +13,7 @@ namespace Umbraco.Web.Models.Mapping /// internal class ContentMapperProfile : Profile { - public ContentMapperProfile(IUserService userService, ILocalizedTextService textService, IContentService contentService, IContentTypeService contentTypeService, IDataTypeService dataTypeService) + public ContentMapperProfile(IUserService userService, ILocalizedTextService textService, IContentService contentService, IContentTypeService contentTypeService, IDataTypeService dataTypeService, ILocalizationService localizationService) { // create, capture, cache var contentOwnerResolver = new OwnerResolver(userService); @@ -25,12 +25,14 @@ namespace Umbraco.Web.Models.Mapping var contentTreeNodeUrlResolver = new ContentTreeNodeUrlResolver(); var defaultTemplateResolver = new DefaultTemplateResolver(); var contentUrlResolver = new ContentUrlResolver(); + var variantResolver = new VariationResolver(localizationService); //FROM IContent TO ContentItemDisplay CreateMap() .ForMember(dest => dest.Udi, opt => opt.MapFrom(src => Udi.Create(src.Blueprint ? Constants.UdiEntityType.DocumentBlueprint : Constants.UdiEntityType.Document, src.Key))) .ForMember(dest => dest.Owner, opt => opt.ResolveUsing(src => contentOwnerResolver.Resolve(src))) .ForMember(dest => dest.Updater, opt => opt.ResolveUsing(src => creatorResolver.Resolve(src))) + .ForMember(dest => dest.Variants, opt => opt.ResolveUsing(variantResolver)) .ForMember(dest => dest.Icon, opt => opt.MapFrom(src => src.ContentType.Icon)) .ForMember(dest => dest.ContentTypeAlias, opt => opt.MapFrom(src => src.ContentType.Alias)) .ForMember(dest => dest.ContentTypeName, opt => opt.MapFrom(src => src.ContentType.Name)) diff --git a/src/Umbraco.Web/Models/Mapping/VariationResolver.cs b/src/Umbraco.Web/Models/Mapping/VariationResolver.cs new file mode 100644 index 0000000000..94588f722e --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/VariationResolver.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; +using ContentVariation = Umbraco.Web.Models.ContentEditing.ContentVariation; +using Language = Umbraco.Web.Models.ContentEditing.Language; + +namespace Umbraco.Web.Models.Mapping +{ + internal class VariationResolver : IValueResolver> + { + private readonly ILocalizationService _localizationService; + + public VariationResolver(ILocalizationService localizationService) + { + _localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService)); + } + + public IEnumerable Resolve(IContent source, ContentItemDisplay destination, IEnumerable destMember, ResolutionContext context) + { + var allLanguages = _localizationService.GetAllLanguages().OrderBy(x => x.Id).ToList(); + if (allLanguages.Count == 0) return Enumerable.Empty(); //there's only 1 language defined so we don't have language variants enabled + + var langs = Mapper.Map>(allLanguages).ToList(); + var variants = langs.Select(x => new ContentVariation + { + Language = x, + //fixme these all need to the variant values but we need to wait for the db/service changes + Name = source.Name , + ExpireDate = source.ExpireDate, + PublishDate = source.PublishDate, + ReleaseDate = source.ReleaseDate, + Exists = source.HasVariation(x.Id), + PublishedState = source.PublishedState.ToString() + }).ToList(); + + //if there's only one language, by default it is the default + if (langs.Count == 1) + { + langs[0].IsDefaultVariantLanguage = true; + langs[0].Mandatory = true; + } + else if (allLanguages.All(x => !x.IsDefaultVariantLanguage)) + { + //if no language has the default flag, then the defaul language is the one with the lowest id + langs[0].IsDefaultVariantLanguage = true; + langs[0].Mandatory = true; + } + + //TODO: Not sure if this is required right now, IsCurrent could purely be a UI thing, we'll see + //set the 'current' + variants.First(x => x.Language.IsDefaultVariantLanguage).IsCurrent = true; + + return variants; + } + + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 06a7e4dc57..a060acdef4 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -224,6 +224,7 @@ + @@ -266,6 +267,7 @@ +