Skip to content

Commit

Permalink
Add the ability to convert Media Picker v2 to v3 progressively (#10517)
Browse files Browse the repository at this point in the history
Co-authored-by: Ronald Barendse <[email protected]>
  • Loading branch information
nul800sebastiaan and ronaldbarendse authored Jun 23, 2021
1 parent 10781b8 commit 9382f6c
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 93 deletions.

This file was deleted.

93 changes: 76 additions & 17 deletions src/Umbraco.Web/PropertyEditors/MediaPicker3PropertyEditor.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
using Umbraco.Core.PropertyEditors;
using Umbraco.Web.PropertyEditors.ValueConverters;
using Umbraco.Core.PropertyEditors.ValueConverters;
using Umbraco.Core.Services;

namespace Umbraco.Web.PropertyEditors
{
Expand All @@ -22,43 +27,97 @@ namespace Umbraco.Web.PropertyEditors
public class MediaPicker3PropertyEditor : DataEditor
{
/// <summary>
/// Initializes a new instance of the <see cref="MediaPicker3PropertyEditor"/> class.
/// Initializes a new instance of the <see cref="MediaPicker3PropertyEditor" /> class.
/// </summary>
/// <param name="logger">The logger.</param>
public MediaPicker3PropertyEditor(ILogger logger)
: base(logger)
{
}
{ }

/// <inheritdoc />
protected override IConfigurationEditor CreateConfigurationEditor() => new MediaPicker3ConfigurationEditor();

/// <inheritdoc />
protected override IDataValueEditor CreateValueEditor() => new MediaPicker3PropertyValueEditor(Attribute);

internal class MediaPicker3PropertyValueEditor : DataValueEditor, IDataValueReference
{
///<remarks>
/// Note: no FromEditor() and ToEditor() methods
/// We do not want to transform the way the data is stored in the DB and would like to keep a raw JSON string
/// </remarks>
public MediaPicker3PropertyValueEditor(DataEditorAttribute attribute) : base(attribute)
public MediaPicker3PropertyValueEditor(DataEditorAttribute attribute)
: base(attribute)
{ }

public override object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null)
{
var value = property.GetValue(culture, segment);

return Deserialize(value);
}

public IEnumerable<UmbracoEntityReference> GetReferences(object value)
{
var rawJson = value == null ? string.Empty : value is string str ? str : value.ToString();
foreach (var dto in Deserialize(value))
{
yield return new UmbracoEntityReference(Udi.Create(Constants.UdiEntityType.Media, dto.MediaKey));
}
}

if (rawJson.IsNullOrWhiteSpace())
internal static IEnumerable<MediaWithCropsDto> Deserialize(object value)
{
var rawJson = value is string str ? str : value?.ToString();
if (string.IsNullOrWhiteSpace(rawJson))
{
yield break;
}

var mediaWithCropsDtos = JsonConvert.DeserializeObject<MediaPickerWithCropsValueConverter.MediaWithCropsDto[]>(rawJson);

foreach (var mediaWithCropsDto in mediaWithCropsDtos)
if (!rawJson.DetectIsJson())
{
yield return new UmbracoEntityReference(GuidUdi.Create(Constants.UdiEntityType.Media, mediaWithCropsDto.MediaKey));
// Old comma seperated UDI format
foreach (var udiStr in rawJson.Split(Constants.CharArrays.Comma))
{
if (GuidUdi.TryParse(udiStr, out var udi))
{
yield return new MediaWithCropsDto
{
Key = Guid.NewGuid(),
MediaKey = udi.Guid,
Crops = Enumerable.Empty<ImageCropperValue.ImageCropperCrop>(),
FocalPoint = new ImageCropperValue.ImageCropperFocalPoint
{
Left = 0.5m,
Top = 0.5m
}
};
}
}
}
else
{
// New JSON format
foreach (var dto in JsonConvert.DeserializeObject<IEnumerable<MediaWithCropsDto>>(rawJson))
{
yield return dto;
}
}
}

/// <summary>
/// Model/DTO that represents the JSON that the MediaPicker3 stores.
/// </summary>
[DataContract]
internal class MediaWithCropsDto
{
[DataMember(Name = "key")]
public Guid Key { get; set; }

[DataMember(Name = "mediaKey")]
public Guid MediaKey { get; set; }

[DataMember(Name = "crops")]
public IEnumerable<ImageCropperValue.ImageCropperCrop> Crops { get; set; }

[DataMember(Name = "focalPoint")]
public ImageCropperValue.ImageCropperFocalPoint FocalPoint { get; set; }
}
}
}
}
3 changes: 0 additions & 3 deletions src/Umbraco.Web/PropertyEditors/MediaPickerConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ namespace Umbraco.Web.PropertyEditors
/// </summary>
public class MediaPickerConfiguration : IIgnoreUserStartNodesConfig
{
[ConfigurationField("notice", "You can NOT change the property editor", "obsoletemediapickernotice")]
public bool Notice { get; set; }

[ConfigurationField("multiPicker", "Pick multiple items", "boolean")]
public bool Multiple { get; set; }

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using Newtonsoft.Json;
using System;
using System.Collections;
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Linq;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PropertyEditors;
Expand All @@ -14,106 +12,73 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
[DefaultPropertyValueConverter]
public class MediaPickerWithCropsValueConverter : PropertyValueConverterBase
{

private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;

public MediaPickerWithCropsValueConverter(IPublishedSnapshotAccessor publishedSnapshotAccessor)
{
_publishedSnapshotAccessor = publishedSnapshotAccessor ?? throw new ArgumentNullException(nameof(publishedSnapshotAccessor));
}

public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Snapshot;

/// <summary>
/// Enusre this property value convertor is for the New Media Picker with Crops aka MediaPicker 3
/// </summary>
public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.Equals(Core.Constants.PropertyEditors.Aliases.MediaPicker3);

/// <summary>
/// Check if the raw JSON value is not an empty array
/// </summary>
public override bool? IsValue(object value, PropertyValueLevel level) => value?.ToString() != "[]";

/// <summary>
/// What C# model type does the raw JSON return for Models & Views
/// </summary>
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
public override bool? IsValue(object value, PropertyValueLevel level)
{
// Check do we want to return IPublishedContent collection still or a NEW model ?
var isMultiple = IsMultipleDataType(propertyType.DataType);
return isMultiple
? typeof(IEnumerable<MediaWithCrops>)
: typeof(MediaWithCrops);
var isValue = base.IsValue(value, level);
if (isValue != false && level == PropertyValueLevel.Source)
{
// Empty JSON array is not a value
isValue = value?.ToString() != "[]";
}

return isValue;
}

public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) => source?.ToString();
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
=> IsMultipleDataType(propertyType.DataType)
? typeof(IEnumerable<MediaWithCrops>)
: typeof(MediaWithCrops);

public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Snapshot;

public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview)
{
var mediaItems = new List<MediaWithCrops>();
var isMultiple = IsMultipleDataType(propertyType.DataType);
if (inter == null)
if (string.IsNullOrEmpty(inter?.ToString()))
{
return isMultiple ? mediaItems: null;
// Short-circuit on empty value
return isMultiple ? Enumerable.Empty<MediaWithCrops>() : null;
}

var dtos = JsonConvert.DeserializeObject<IEnumerable<MediaWithCropsDto>>(inter.ToString());
var mediaItems = new List<MediaWithCrops>();
var dtos = MediaPicker3PropertyEditor.MediaPicker3PropertyValueEditor.Deserialize(inter);

foreach(var media in dtos)
foreach (var dto in dtos)
{
var item = _publishedSnapshotAccessor.PublishedSnapshot.Media.GetById(media.MediaKey);
if (item != null)
var mediaItem = _publishedSnapshotAccessor.PublishedSnapshot.Media.GetById(preview, dto.MediaKey);
if (mediaItem != null)
{
mediaItems.Add(new MediaWithCrops
{
MediaItem = item,
MediaItem = mediaItem,
LocalCrops = new ImageCropperValue
{
Crops = media.Crops,
FocalPoint = media.FocalPoint,
Src = item.Url()
Crops = dto.Crops,
FocalPoint = dto.FocalPoint,
Src = mediaItem.Url()
}
});

if (!isMultiple)
{
// Short-circuit on single item
break;
}
}
}

return isMultiple ? mediaItems : FirstOrDefault(mediaItems);
}

/// <summary>
/// Is the media picker configured to pick multiple media items
/// </summary>
/// <param name="dataType"></param>
/// <returns></returns>
private bool IsMultipleDataType(PublishedDataType dataType)
{
var config = dataType.ConfigurationAs<MediaPicker3Configuration>();
return config.Multiple;
}

private object FirstOrDefault(IList mediaItems)
{
return mediaItems.Count == 0 ? null : mediaItems[0];
return isMultiple ? mediaItems : mediaItems.FirstOrDefault();
}


/// <summary>
/// Model/DTO that represents the JSON that the MediaPicker3 stores
/// </summary>
[DataContract]
internal class MediaWithCropsDto
{
[DataMember(Name = "key")]
public Guid Key { get; set; }

[DataMember(Name = "mediaKey")]
public Guid MediaKey { get; set; }

[DataMember(Name = "crops")]
public IEnumerable<ImageCropperValue.ImageCropperCrop> Crops { get; set; }

[DataMember(Name = "focalPoint")]
public ImageCropperValue.ImageCropperFocalPoint FocalPoint { get; set; }
}
private bool IsMultipleDataType(PublishedDataType dataType) => dataType.ConfigurationAs<MediaPicker3Configuration>().Multiple;
}
}

0 comments on commit 9382f6c

Please sign in to comment.