Last active
July 11, 2019 13:35
-
-
Save artemious7/e5a8f7d92e30b3b2607e9b8fad3b3d31 to your computer and use it in GitHub Desktop.
adaptive card v1.0 cloning
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Linq; | |
using Newtonsoft.Json; | |
using System; | |
using System.Collections.Generic; | |
namespace AdaptiveCards.Cloning | |
{ | |
public class MemberwiseCloneFactory : ICloneFactory | |
{ | |
public IClone ToClone(AdaptiveTypedElement element) | |
{ | |
if (element == null) | |
throw new ArgumentNullException(nameof(element)); | |
return new MemberwiseClone { Element = element }; | |
} | |
public T FromClone<T>(in IClone cloneBase) | |
where T : AdaptiveTypedElement | |
{ | |
var clone = (MemberwiseClone)cloneBase; | |
var obj = (T)Clone(clone.Element); | |
// we don't want to have a few elements with same id | |
obj.Id = null; | |
return obj; | |
} | |
private static bool EnsureCloneIsCorrect = false; | |
private readonly ICloneFactory jsonStringCloneFactory = new JsonStringCloneFactory(); | |
private AdaptiveTypedElement Clone(AdaptiveTypedElement element) | |
{ | |
var result = _Clone(element); | |
if (EnsureCloneIsCorrect) | |
{ | |
if (element == null && result != null || element != null && result == null) | |
throw new Exception("Clones don't match"); | |
else if (element != null) | |
{ | |
var cl1 = (JsonStringClone)jsonStringCloneFactory.ToClone(element); | |
var cl2 = (JsonStringClone)jsonStringCloneFactory.ToClone(result); | |
if (cl1.Json != cl2.Json) | |
throw new Exception("Clones don't match"); | |
} | |
} | |
return result; | |
} | |
private AdaptiveTypedElement _Clone(AdaptiveTypedElement element) | |
{ | |
switch (element) | |
{ | |
case AdaptiveCard card: | |
return CloneConcrete(card); | |
case AdaptiveImage image: | |
return CloneConcrete(image); | |
case AdaptiveColumn column: | |
return CloneConcrete(column); | |
case AdaptiveColumnSet columnSet: | |
return CloneConcrete(columnSet); | |
case AdaptiveContainer container: | |
return CloneConcrete(container); | |
case AdaptiveTextBlock textBlock: | |
return CloneConcrete(textBlock); | |
case AdaptiveOpenUrlAction OpenUrlAction: | |
return CloneConcrete(OpenUrlAction); | |
case null: | |
return null; | |
default: | |
throw new NotImplementedException($"unknown type: {element.GetType().FullName}"); | |
} | |
} | |
private AdaptiveCard CloneConcrete(AdaptiveCard el, AdaptiveCard copy = null) | |
{ | |
copy = copy ?? new AdaptiveCard(); | |
CloneConcrete((AdaptiveTypedElement)el, copy); | |
#pragma warning disable CS0618 // Тип или член устарел | |
copy.VerticalContentAlignment = el.VerticalContentAlignment; | |
copy.Lang = el.Lang; | |
copy.FallbackText = el.FallbackText; | |
copy.MinVersion = el.MinVersion; | |
copy.Version = el.Version; | |
if (el.BackgroundImageString != null) | |
copy.BackgroundImageString = el.BackgroundImageString; | |
copy.BackgroundImage = el.BackgroundImage; | |
copy.Title = el.Title; | |
copy.Speak = el.Speak; | |
copy.Body = el.Body.Select(Clone).Cast<AdaptiveElement>().ToList(); | |
copy.SelectAction = (AdaptiveAction)Clone(el.SelectAction); | |
copy.Actions = el.Actions.Select(Clone).Cast<AdaptiveAction>().ToList(); | |
copy.Height = el.Height; | |
#pragma warning restore CS0618 // Тип или член устарел | |
return copy; | |
} | |
private AdaptiveColumnSet CloneConcrete(AdaptiveColumnSet el, AdaptiveColumnSet copy = null) | |
{ | |
copy = copy ?? new AdaptiveColumnSet(); | |
CloneConcrete((AdaptiveElement)el, copy); | |
copy.Columns = el.Columns.Select(r => CloneConcrete(r)).ToList(); | |
copy.SelectAction = (AdaptiveAction)Clone(el.SelectAction); | |
return copy; | |
} | |
private AdaptiveTextBlock CloneConcrete(AdaptiveTextBlock el, AdaptiveTextBlock copy = null) | |
{ | |
copy = copy ?? new AdaptiveTextBlock(); | |
CloneConcrete((AdaptiveElement)el, copy); | |
copy.Size = el.Size; | |
copy.Weight = el.Weight; | |
copy.Color = el.Color; | |
copy.IsSubtle = el.IsSubtle; | |
copy.Text = el.Text; | |
copy.HorizontalAlignment = el.HorizontalAlignment; | |
copy.Wrap = el.Wrap; | |
copy.MaxLines = el.MaxLines; | |
copy.MaxWidth = el.MaxWidth; | |
return copy; | |
} | |
private AdaptiveColumn CloneConcrete(AdaptiveColumn el, AdaptiveColumn copy = null) | |
{ | |
copy = copy ?? new AdaptiveColumn(); | |
CloneConcrete((AdaptiveContainer)el, copy); | |
#pragma warning disable CS0618 // Тип или член устарел | |
copy.Size = el.Size; | |
copy.Width = el.Width; | |
copy.VerticalContentAlignment = el.VerticalContentAlignment; | |
#pragma warning restore CS0618 // Тип или член устарел | |
return copy; | |
} | |
private AdaptiveContainer CloneConcrete(AdaptiveContainer el, AdaptiveContainer copy = null) | |
{ | |
copy = copy ?? new AdaptiveContainer(); | |
CloneConcrete((AdaptiveElement)el, copy); | |
copy.SelectAction = (AdaptiveAction)Clone(el.SelectAction); | |
copy.Style = el.Style; | |
copy.VerticalContentAlignment = el.VerticalContentAlignment; | |
copy.Items = el.Items.Select(Clone).Cast<AdaptiveElement>().ToList(); | |
return copy; | |
} | |
private AdaptiveTypedElement CloneConcrete(AdaptiveTypedElement el, AdaptiveTypedElement copy) | |
{ | |
copy.AdditionalProperties = new Dictionary<string, object>(el.AdditionalProperties); | |
copy.Id = el.Id; | |
copy.Type = el.Type; | |
return copy; | |
} | |
private AdaptiveElement CloneConcrete(AdaptiveElement el, AdaptiveElement copy) | |
{ | |
CloneConcrete((AdaptiveTypedElement)el, copy); | |
#pragma warning disable CS0618 // Тип или член устарел | |
copy.Height = el.Height; | |
copy.Separation = el.Separation; | |
copy.Separator = el.Separator; | |
copy.Spacing = el.Spacing; | |
copy.Speak = el.Speak; | |
#pragma warning restore CS0618 // Тип или член устарел | |
return copy; | |
} | |
private AdaptiveImage CloneConcrete(AdaptiveImage el, AdaptiveImage copy = null) | |
{ | |
copy = copy ?? new AdaptiveImage(); | |
CloneConcrete((AdaptiveElement)el, copy); | |
copy.AltText = el.AltText; | |
copy.BackgroundColor = el.BackgroundColor; | |
copy.HorizontalAlignment = el.HorizontalAlignment; | |
copy.Size = el.Size; | |
copy.Style = el.Style; | |
copy.Url = el.Url; | |
copy.UrlString = el.UrlString; | |
copy.PixelWidth = el.PixelWidth; | |
copy.PixelHeight = el.PixelHeight; | |
copy.SelectAction = (AdaptiveAction)Clone(el.SelectAction); | |
return copy; | |
} | |
private AdaptiveOpenUrlAction CloneConcrete(AdaptiveOpenUrlAction el, AdaptiveOpenUrlAction copy = null) | |
{ | |
copy = copy ?? new AdaptiveOpenUrlAction(); | |
CloneConcrete((AdaptiveAction)el, copy); | |
copy.Url = el.Url; | |
copy.UrlString = el.UrlString; | |
return copy; | |
} | |
private AdaptiveAction CloneConcrete(AdaptiveAction el, AdaptiveAction copy) | |
{ | |
CloneConcrete((AdaptiveTypedElement)el, copy); | |
#pragma warning disable CS0618 // Тип или член устарел | |
copy.Title = el.Title; | |
copy.Speak = el.Speak; | |
copy.IconUrl = el.IconUrl; | |
#pragma warning restore CS0618 // Тип или член устарел | |
return copy; | |
} | |
} | |
internal struct MemberwiseClone : IClone | |
{ | |
public AdaptiveTypedElement Element { get; set; } | |
} | |
public class JsonStringCloneFactory : ICloneFactory | |
{ | |
public IClone ToClone(AdaptiveTypedElement element) | |
{ | |
if (element == null) | |
throw new ArgumentNullException(nameof(element)); | |
string json = JsonConvert.SerializeObject(element); | |
return new JsonStringClone { Json = json }; | |
} | |
public T FromClone<T>(in IClone cloneBase) | |
where T : AdaptiveTypedElement | |
{ | |
var clone = (JsonStringClone)cloneBase; | |
string json = clone.Json; | |
// don't return null. If we passed a null, then something must be wrong outside of this method | |
if (String.IsNullOrEmpty(json)) | |
throw new ArgumentNullException(nameof(json)); | |
var settings = new JsonSerializerSettings | |
{ | |
//ContractResolver = new WarningLoggingContractResolver(), | |
Converters = { new StrictIntConverter() } | |
}; | |
var obj = JsonConvert.DeserializeObject<T>(json, settings); | |
obj.Id = null; | |
return obj; | |
} | |
} | |
internal struct JsonStringClone : IClone | |
{ | |
public string Json { get; set; } | |
} | |
public interface ICloneFactory | |
{ | |
T FromClone<T>(in IClone clone) where T : AdaptiveTypedElement; | |
IClone ToClone(AdaptiveTypedElement element); | |
} | |
public interface IClone | |
{ | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
namespace AdaptiveCards.Extensions | |
{ | |
public static class AdaptiveTypedElementCollectionExtensions | |
{ | |
public static T GetElementById<T>(this IEnumerable<AdaptiveTypedElement> elements, string id) | |
where T : AdaptiveTypedElement | |
{ | |
if (elements == null) | |
throw new ArgumentNullException(nameof(elements)); | |
return (T)elements.SingleOrDefault(r => r.Id == id) ?? throw new KeyNotFoundException($"Element with id = \"{id}\" not found."); | |
} | |
public static AdaptiveTypedElement GetElementById(this IEnumerable<AdaptiveTypedElement> elements, string id) => GetElementById<AdaptiveTypedElement>(elements, id); | |
public static IEnumerable<AdaptiveTypedElement> GetDescendants | |
(this IEnumerable<AdaptiveTypedElement> elements) | |
{ | |
if (elements == null) | |
throw new ArgumentNullException(nameof(elements)); | |
IEnumerable<AdaptiveTypedElement> getDescendants | |
(IEnumerable<AdaptiveTypedElement> _elements) => | |
_elements.SelectMany(element => new[] { element }.Concat(getDescendants(element.GetElements()))); | |
return getDescendants(elements); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using AdaptiveCards.Cloning; | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
namespace AdaptiveCards.Extensions | |
{ | |
public static class AdaptiveTypedElementExtensions | |
{ | |
public static AdaptiveTypedElement GetElementById(this AdaptiveTypedElement element, string id) | |
{ | |
if (element == null) | |
throw new ArgumentNullException(nameof(element)); | |
return element.GetDescendants().GetElementById(id); | |
} | |
public static T GetElementById<T>(this AdaptiveTypedElement element, string id) | |
where T : AdaptiveTypedElement | |
{ | |
if (element == null) | |
throw new ArgumentNullException(nameof(element)); | |
return element.GetDescendants().GetElementById<T>(id); | |
} | |
public static Dictionary<string, AdaptiveTypedElement> GetElementsByIdMap(this AdaptiveTypedElement element) | |
{ | |
if (element == null) | |
throw new ArgumentNullException(nameof(element)); | |
return element.GetDescendants() | |
.Where(el => !string.IsNullOrEmpty(el.Id)) | |
.ToDictionary(el => el.Id); | |
} | |
public static IEnumerable<T> GetDescendants<T>(this AdaptiveTypedElement element) | |
where T : AdaptiveElement | |
{ | |
if (element == null) | |
throw new ArgumentNullException(nameof(element)); | |
return element.GetDescendants().OfType<T>(); | |
} | |
public static IEnumerable<AdaptiveTypedElement> GetDescendants(this AdaptiveTypedElement element) | |
{ | |
if (element == null) | |
throw new ArgumentNullException(nameof(element)); | |
return element.GetElements().GetDescendants(); | |
} | |
public static IEnumerable<AdaptiveTypedElement> GetElements(this AdaptiveTypedElement element) | |
{ | |
switch (element) | |
{ | |
case AdaptiveContainer container: | |
return container.Items; | |
case AdaptiveColumnSet columnSet: | |
return columnSet.Columns; | |
case AdaptiveImageSet imageSet: | |
return imageSet.Images; | |
case AdaptiveCard card: | |
return card.Body; | |
case null: | |
throw new ArgumentNullException(nameof(element)); | |
default: | |
return Enumerable.Empty<AdaptiveTypedElement>(); | |
} | |
} | |
public static AdaptiveTextBlock SetText(this AdaptiveTypedElement elementToSearchWithin, string elementId, string newText) | |
{ | |
if (elementToSearchWithin == null) | |
throw new ArgumentNullException(nameof(elementToSearchWithin)); | |
if (String.IsNullOrEmpty(elementId)) | |
throw new ArgumentNullException(nameof(elementId)); | |
var textBlock = elementToSearchWithin.GetElementById<AdaptiveTextBlock>(elementId); | |
textBlock.Text = newText; | |
return textBlock; | |
} | |
public static T DeepClone<T>(this T element) | |
where T : AdaptiveTypedElement | |
{ | |
// don't return null. If we passed a null, then something must be wrong outside of this method | |
if (element == null) | |
throw new ArgumentNullException(nameof(element)); | |
return cloneFactory.FromClone<T>(cloneFactory.ToClone(element)); | |
} | |
public static IClone ToClone(this AdaptiveTypedElement element) => cloneFactory.ToClone(element); | |
public static T FromClone<T>(this IClone clone) | |
where T : AdaptiveTypedElement => cloneFactory.FromClone<T>(clone); | |
private static readonly ICloneFactory cloneFactory = new MemberwiseCloneFactory(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#region Usings | |
using System; | |
using System.Linq; | |
using AdaptiveCards; | |
using System.Collections.Generic; | |
using AdaptiveCards.Extensions; | |
#pragma warning disable IDE1006 // Стили именования | |
#endregion | |
namespace NS | |
{ | |
public class WeekAdaptiveCardRenderer : IAdaptiveCardRenderer | |
{ | |
public WeekAdaptiveCardRenderer(WeekAdaptiveCardGeneratorPayload data) | |
{ | |
this.data = data ?? throw new ArgumentNullException(nameof(data)); | |
} | |
public string ContentType { get; } = AdaptiveCard.ContentType; | |
public AdaptiveCard Render() | |
{ | |
Card = cardTemplate.CardDeepClone(); | |
elementMap = Card.GetElementsByIdMap(); | |
// spot name | |
((AdaptiveTextBlock)elementMap["spot"]).Text = data.SpotName; | |
DateTime minDate = data.Start; | |
DateTime maxDate = data.End.AddDays(-1); | |
// set month | |
((AdaptiveTextBlock)elementMap["month"]).Text = month; | |
// days | |
weekdayContainers = Enumerable.Range(0, 7) | |
.Select(i => (AdaptiveContainer)elementMap[weekdayContainerNameFormat.FormatString(i)]) | |
.ToArray(); | |
days = Enumerable.Range(0, 7) | |
.Select(r => minDate.AddDays(r)) | |
.Select(r => new Day(r, data.Shifts.Where(s => s.Shift.Date == r), this)) | |
.ToList(); | |
// set day title before start rendering of days to avoid concurrency issues between the two operations | |
days.AsParallel() | |
.ForAll(r => r.SetDayTitle()); | |
days.AsParallel() | |
.ForAll(r => r.Render()); | |
return Card; | |
} | |
private class Day | |
{ | |
public Day(DateTime date, IEnumerable<Shift> shifts, WeekAdaptiveCardRenderer renderer) | |
{ | |
this.date = date; | |
this.renderer = renderer; | |
int weekdayCode = (int)date.DayOfWeek; | |
card = renderer.Card; | |
weekdayTitleTextBlockName = weekdayTitleTextBlockNameFormat.FormatString(weekdayCode); | |
shiftRows = shifts.Select(shift => new ShiftRow(shift, renderer)).ToList(); | |
weekdayContainer = renderer.weekdayContainers[weekdayCode]; | |
} | |
public void Render() | |
{ | |
// shift rows | |
weekdayContainer.Items.RemoveAll(r => r.Id?.StartsWith(weekdayTitleTextBlockNameFormat.Replace("{0}", "")) != true); | |
if (IsPastDate) | |
weekdayContainer.Items.Add(renderer.cardTemplate.pastDay()); | |
else if (!shiftRows.Any()) | |
weekdayContainer.Items.Add(renderer.cardTemplate.emptyDay()); | |
else foreach (var shiftRow in shiftRows) | |
shiftRow.Render(); | |
} | |
internal void SetDayTitle() | |
{ | |
// day title | |
DayTitleTextBlock = card.SetText(weekdayTitleTextBlockName, date.ToString("ddd d").ToUpper()); | |
// if today | |
if (IsToday) | |
{ | |
DayTitleTextBlock.Weight = AdaptiveTextWeight.Bolder; | |
DayTitleTextBlock.Color = AdaptiveTextColor.Accent; | |
} | |
} | |
private readonly AdaptiveCard card; | |
private readonly string weekdayTitleTextBlockName; | |
private readonly AdaptiveContainer weekdayContainer; | |
private readonly List<ShiftRow> shiftRows; | |
private const string weekdayTitleTextBlockNameFormat = "weekday{0}"; | |
private readonly DateTime date; | |
private readonly WeekAdaptiveCardRenderer renderer; | |
private AdaptiveTextBlock DayTitleTextBlock; | |
public bool IsToday => renderer.data.Now.Date == date.Date; | |
public bool IsPastDate => !IsToday && date.Date < renderer.data.Now; | |
} | |
private class ShiftRow | |
{ | |
public ShiftRow(Shift shiftMatch, WeekAdaptiveCardRenderer renderer) | |
{ | |
this.shiftMatch = shiftMatch; | |
this.renderer = renderer; | |
var date = shift.Date; | |
int weekdayCode = (int)date.DayOfWeek; | |
weekdayContainer = renderer.weekdayContainers[weekdayCode]; | |
} | |
public void Render() | |
{ | |
var shiftRow = renderer.cardTemplate.shiftRow(); | |
weekdayContainer.Items.Add(shiftRow); | |
// time | |
shiftRow.SetText("shiftTime", shift.Start.ToString("HH:mm")); | |
// partners | |
var partnersColumns = shiftRow.GetElementById<AdaptiveColumnSet>("partners"); | |
partnersColumns.Columns.Clear(); | |
foreach (var pm in shiftMatch.Partners) | |
{ | |
partnersColumns.Columns.Add(renderer.cardTemplate.vacancy()); | |
} | |
} | |
private IShift shift => shiftMatch.Shift; | |
private readonly Shift shiftMatch; | |
private readonly WeekAdaptiveCardRenderer renderer; | |
private readonly AdaptiveContainer weekdayContainer; | |
} | |
private WeekCardTemplate cardTemplate => (WeekCardTemplate)data.CardTemplate; | |
private readonly WeekAdaptiveCardGeneratorPayload data; | |
private AdaptiveCard Card; | |
private Dictionary<string, AdaptiveTypedElement> elementMap; | |
private const string weekdayContainerNameFormat = "weekday{0}Container"; | |
private AdaptiveContainer[] weekdayContainers; | |
private List<Day> days; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#region Usings | |
using System; | |
using AdaptiveCards; | |
using System.Collections.Generic; | |
using System.Collections.Concurrent; | |
using System.Collections.ObjectModel; | |
using AdaptiveCards.Cloning; | |
using AdaptiveCards.Extensions; | |
#pragma warning disable IDE1006 // Стили именования | |
#endregion | |
namespace MyNamespace | |
{ | |
public class WeekCardTemplate : ICardTemplate | |
{ | |
public WeekCardTemplate(AdaptiveCard card) | |
{ | |
this.card = card ?? throw new ArgumentNullException(nameof(card)); | |
ElementsMap = new ReadOnlyDictionary<string, AdaptiveTypedElement>(this.card.GetElementsByIdMap()); | |
} | |
private IReadOnlyDictionary<string, AdaptiveTypedElement> ElementsMap { get; set; } | |
public AdaptiveColumn vacancy() => Clone<AdaptiveColumn>("vacancy"); | |
public AdaptiveColumnSet shiftRow() => Clone<AdaptiveColumnSet>("shiftRow"); | |
public AdaptiveTextBlock pastDay() => Clone<AdaptiveTextBlock>("pastDay"); | |
public AdaptiveTextBlock emptyDay() => Clone<AdaptiveTextBlock>("emptyDay"); | |
private T Clone<T>(string id) where T : AdaptiveTypedElement => ((T)elementsToCloneById.GetOrAdd(id, _id => ElementsMap[_id])).DeepClone(); | |
public AdaptiveCard CardDeepClone() => card.DeepClone(); | |
private readonly ConcurrentDictionary<string, AdaptiveTypedElement> elementsToCloneById = new ConcurrentDictionary<string, AdaptiveTypedElement>(); | |
private AdaptiveCard card; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment