Skip to content

Instantly share code, notes, and snippets.

@artemious7
Last active July 11, 2019 13:35
Show Gist options
  • Save artemious7/e5a8f7d92e30b3b2607e9b8fad3b3d31 to your computer and use it in GitHub Desktop.
Save artemious7/e5a8f7d92e30b3b2607e9b8fad3b3d31 to your computer and use it in GitHub Desktop.
adaptive card v1.0 cloning
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
{
}
}
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);
}
}
}
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();
}
}
#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;
}
}
#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