diff --git a/source/uwp/Visualizer/App.xaml b/source/uwp/Visualizer/App.xaml
index 2b703c8597..607e834d4f 100644
--- a/source/uwp/Visualizer/App.xaml
+++ b/source/uwp/Visualizer/App.xaml
@@ -3,6 +3,17 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:XamlCardVisualizer"
- RequestedTheme="Light">
+ RequestedTheme="Light"
+ xmlns:converters="using:XamlCardVisualizer.Converters">
+
+
+
+
+
+
+
diff --git a/source/uwp/Visualizer/Converters/ErrorViewModelConverters.cs b/source/uwp/Visualizer/Converters/ErrorViewModelConverters.cs
new file mode 100644
index 0000000000..8ec3542f93
--- /dev/null
+++ b/source/uwp/Visualizer/Converters/ErrorViewModelConverters.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Windows.UI;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Data;
+using Windows.UI.Xaml.Media;
+using XamlCardVisualizer.ViewModel;
+
+namespace XamlCardVisualizer.Converters
+{
+ public class ErrorViewModelTypeToIconBackgroundConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, string language)
+ {
+ if (value is ErrorViewModelType)
+ {
+ switch ((ErrorViewModelType)value)
+ {
+ case ErrorViewModelType.Error:
+ case ErrorViewModelType.ErrorButRenderAllowed:
+ return new SolidColorBrush(Colors.Red);
+
+ case ErrorViewModelType.Warning:
+ return new SolidColorBrush(Colors.Orange);
+
+ default:
+ throw new NotImplementedException();
+ }
+ }
+
+ return value;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, string language)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public sealed class ErrorViewModelTypeToIconForegroundConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, string language)
+ {
+ if (value is ErrorViewModelType)
+ {
+ switch ((ErrorViewModelType)value)
+ {
+ case ErrorViewModelType.Error:
+ case ErrorViewModelType.ErrorButRenderAllowed:
+ return new SolidColorBrush(Colors.White);
+
+ case ErrorViewModelType.Warning:
+ return new SolidColorBrush(Colors.Black);
+
+ default:
+ throw new NotImplementedException();
+ }
+ }
+
+ return value;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, string language)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public sealed class ErrorViewModelTypeToSymbolConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, string language)
+ {
+ if (value is ErrorViewModelType)
+ {
+ switch ((ErrorViewModelType)value)
+ {
+ case ErrorViewModelType.Error:
+ case ErrorViewModelType.ErrorButRenderAllowed:
+ return Symbol.Cancel;
+
+ case ErrorViewModelType.Warning:
+ return Symbol.Important;
+
+ default:
+ throw new NotImplementedException();
+ }
+ }
+
+ return value;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, string language)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/source/uwp/Visualizer/Converters/NotNullToVisibilityConverter.cs b/source/uwp/Visualizer/Converters/NotNullToVisibilityConverter.cs
new file mode 100644
index 0000000000..ed2516b36e
--- /dev/null
+++ b/source/uwp/Visualizer/Converters/NotNullToVisibilityConverter.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Data;
+
+namespace XamlCardVisualizer.Converters
+{
+ public class NotNullToVisibilityConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, string language)
+ {
+ return value != null ? Visibility.Visible : Visibility.Collapsed;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, string language)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/source/uwp/Visualizer/DocumentView.xaml b/source/uwp/Visualizer/DocumentView.xaml
new file mode 100644
index 0000000000..50475a48a2
--- /dev/null
+++ b/source/uwp/Visualizer/DocumentView.xaml
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/source/uwp/Visualizer/DocumentView.xaml.cs b/source/uwp/Visualizer/DocumentView.xaml.cs
new file mode 100644
index 0000000000..4fecd17ec0
--- /dev/null
+++ b/source/uwp/Visualizer/DocumentView.xaml.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices.WindowsRuntime;
+using Windows.Foundation;
+using Windows.Foundation.Collections;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Controls.Primitives;
+using Windows.UI.Xaml.Data;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Navigation;
+using XamlCardVisualizer.Helpers;
+using XamlCardVisualizer.ViewModel;
+
+// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236
+
+namespace XamlCardVisualizer
+{
+ public sealed partial class DocumentView : UserControl
+ {
+ public DocumentViewModel ViewModel
+ {
+ get { return DataContext as DocumentViewModel; }
+ }
+
+ public DocumentView()
+ {
+ this.InitializeComponent();
+ }
+ }
+}
diff --git a/source/uwp/Visualizer/Helpers/BindableBase.cs b/source/uwp/Visualizer/Helpers/BindableBase.cs
new file mode 100644
index 0000000000..1b700c4d84
--- /dev/null
+++ b/source/uwp/Visualizer/Helpers/BindableBase.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace XamlCardVisualizer.Helpers
+{
+ public abstract class BindableBase : INotifyPropertyChanged
+ {
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ protected void SetProperty(ref T property, T value, [CallerMemberName]string propertyName = null)
+ {
+ if (object.Equals(property, value))
+ {
+ return;
+ }
+
+ property = value;
+ NotifyPropertyChanged(propertyName);
+ }
+
+ protected void NotifyPropertyChanged(string propertyName)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+}
diff --git a/source/uwp/Visualizer/Helpers/IListExtensions.cs b/source/uwp/Visualizer/Helpers/IListExtensions.cs
new file mode 100644
index 0000000000..b9fcc6c156
--- /dev/null
+++ b/source/uwp/Visualizer/Helpers/IListExtensions.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace XamlCardVisualizer.Helpers
+{
+ public static class IListExtensions
+ {
+ public static bool MakeListLike(this IList list, IList desired)
+ {
+ // If already equal, do nothing
+ if (desired.SequenceEqual(list))
+ return false;
+
+ // Remove any of the items that aren't there anymore
+ for (int i = 0; i < list.Count; i++)
+ if (!desired.Contains(list[i]))
+ {
+ list.RemoveAt(i);
+ i--;
+ }
+
+ for (int i = 0; i < desired.Count; i++)
+ {
+ if (i >= list.Count)
+ list.Add(desired[i]);
+
+ // There's a wrong item in its place
+ else if (!object.Equals(list[i], desired[i]))
+ {
+ // If it's already in the list somewhere, we remove it
+ list.Remove(desired[i]);
+
+ // No matter what we insert it into its desired spot
+ list.Insert(i, desired[i]);
+ }
+
+ // Otherwise it's already in the right place!
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/source/uwp/Visualizer/Helpers/PayloadValidator.cs b/source/uwp/Visualizer/Helpers/PayloadValidator.cs
new file mode 100644
index 0000000000..ca03b6d57d
--- /dev/null
+++ b/source/uwp/Visualizer/Helpers/PayloadValidator.cs
@@ -0,0 +1,244 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json.Schema;
+using Newtonsoft.Json.Serialization;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using Windows.Storage;
+using XamlCardVisualizer.ViewModel;
+
+namespace XamlCardVisualizer.Helpers
+{
+ public static class PayloadValidator
+ {
+ public static async Task> ValidateAsync(string payload)
+ {
+ List errors = new List();
+
+ JObject parsedObject = null;
+
+ try
+ {
+ parsedObject = JObject.Parse(payload);
+ }
+ catch (JsonReaderException ex)
+ {
+ errors.Add(new ErrorViewModel()
+ {
+ Message = "Parse error: " + ex.Message,
+ Type = ErrorViewModelType.Error
+ });
+ }
+ catch (Exception ex)
+ {
+ errors.Add(new ErrorViewModel()
+ {
+ Message = "Parse error: " + ex.ToString(),
+ Type = ErrorViewModelType.Error
+ });
+ }
+
+ if (parsedObject != null)
+ {
+ string schema = null;
+ try
+ {
+ schema = await GetSchemaAsync();
+ }
+ catch { }
+
+ if (schema != null)
+ {
+ JSchema jsonSchema = null;
+
+ try
+ {
+ jsonSchema = JSchema.Parse(schema);
+ }
+ catch
+ {
+ // We don't report this error to user, it's a coding error
+ if (Debugger.IsAttached)
+ {
+ Debugger.Break();
+ }
+ }
+
+ if (jsonSchema != null)
+ {
+ parsedObject.IsValid(jsonSchema, out IList validationErrors);
+
+ foreach (var error in validationErrors)
+ {
+
+ var err = error;
+ if (err.ChildErrors.Count > 0)
+ {
+ err = error.ChildErrors.Last();
+ }
+ var distinct = error.ChildErrors.Distinct().ToArray();
+
+ var finalErrors = GetLowestMostErrors(error).Distinct(new ErrorEqualityComparer()).ToArray();
+
+ var typeError = finalErrors.FirstOrDefault(i => i.Path.EndsWith(".type") && i.ErrorType == ErrorType.Enum);
+ if (typeError != null)
+ {
+ JObject definitions = jsonSchema.ExtensionData["definitions"] as JObject;
+ JObject definition = definitions[typeError.Value.ToString()] as JObject;
+ if (definition != null)
+ {
+ bool loggedErrorForType = false;
+ var errorsSpecificToType = finalErrors.Where(i => object.Equals(i.Schema.Description, definition["description"]?.ToString())).ToArray();
+ if (errorsSpecificToType.Length > 0)
+ {
+ foreach (var typeErrorChild in errorsSpecificToType)
+ {
+ if (typeErrorChild.ErrorType == ErrorType.Required && typeErrorChild.Message.StartsWith("Required properties are missing from object:"))
+ {
+ errors.Add(new ErrorViewModel()
+ {
+ Message = typeErrorChild.Message.Replace("Required properties are missing from object:", $"Required properties are missing from {typeError.Value}:"),
+ Type = ErrorViewModelType.ErrorButRenderAllowed,
+ Position = GetPositionInfo(typeErrorChild)
+ });
+ loggedErrorForType = true;
+ }
+ }
+ }
+
+ foreach (var parseTypeError in finalErrors.Where(i => i.ErrorType == ErrorType.Type))
+ {
+ errors.Add(new ErrorViewModel()
+ {
+ Message = parseTypeError.Message.Replace("Invalid type", $"Invalid value type on {typeError.Value}.{GetPropertyName(parseTypeError)}"),
+ Type = ErrorViewModelType.ErrorButRenderAllowed,
+ Position = GetPositionInfo(typeError)
+ });
+ loggedErrorForType = true;
+ }
+
+ if (!loggedErrorForType)
+ {
+ errors.Add(new ErrorViewModel()
+ {
+ Message = "Unknown properties or values inside " + typeError.Value.ToString(),
+ Type = ErrorViewModelType.Warning,
+ Position = GetPositionInfo(typeError)
+ });
+ }
+ continue;
+ }
+
+ errors.Add(new ErrorViewModel()
+ {
+ Message = "Invalid type: " + typeError.Value.ToString(),
+ Type = ErrorViewModelType.Warning,
+ Position = GetPositionInfo(typeError)
+ });
+ }
+ }
+ }
+ }
+ }
+
+ return errors;
+ }
+
+ private static string GetPropertyName(ValidationError error)
+ {
+ return error.Path.Split('.').LastOrDefault();
+ }
+
+ private static ErrorViewModelPositionInfo GetPositionInfo(ValidationError error)
+ {
+ return new ErrorViewModelPositionInfo()
+ {
+ LineNumber = error.LineNumber
+ };
+ }
+
+ private class ErrorEqualityComparer : IEqualityComparer
+ {
+ public bool Equals(ValidationError x, ValidationError y)
+ {
+ return x.Message.Equals(y.Message)
+ && x.LineNumber == y.LineNumber
+ && x.LinePosition == y.LinePosition
+ && x.ErrorType == y.ErrorType
+ && x.Path == y.Path
+ && x.ChildErrors.Count == y.ChildErrors.Count;
+ }
+
+ public int GetHashCode(ValidationError obj)
+ {
+ return (obj.Message
+ + obj.LineNumber.ToString()
+ + obj.LinePosition.ToString()
+ + obj.ErrorType.ToString()
+ + obj.Path
+ + obj.ChildErrors.Count.ToString()).GetHashCode();
+ }
+ }
+
+ private static IEnumerable GetLowestMostErrors(ValidationError error)
+ {
+ if (error.ChildErrors.Count == 0)
+ {
+ yield return error;
+ yield break;
+ }
+
+ foreach (var child in error.ChildErrors)
+ {
+ foreach (var lowest in GetLowestMostErrors(child))
+ {
+ yield return lowest;
+ }
+ }
+ }
+
+ private static IEnumerable GetAllErrors(ValidationError error)
+ {
+ yield return error;
+ foreach (var descendant in GetDescendantErrors(error))
+ {
+ yield return descendant;
+ }
+ }
+
+ private static IEnumerable GetDescendantErrors(ValidationError error)
+ {
+ foreach (var child in error.ChildErrors)
+ {
+ yield return child;
+
+ foreach (var descendant in GetDescendantErrors(child))
+ {
+ yield return descendant;
+ }
+ }
+ }
+
+ private static Task _getSchemaTask;
+ private static Task GetSchemaAsync()
+ {
+ if (_getSchemaTask == null)
+ {
+ _getSchemaTask = GetSchemaHelperAsync();
+ }
+
+ return _getSchemaTask;
+ }
+
+ private static async Task GetSchemaHelperAsync()
+ {
+ var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Schemas/adaptive-card.json"));
+ return await FileIO.ReadTextAsync(file);
+ }
+ }
+}
diff --git a/source/uwp/Visualizer/MainPage.xaml b/source/uwp/Visualizer/MainPage.xaml
index 63b09c3cbc..eb2f27e6e4 100644
--- a/source/uwp/Visualizer/MainPage.xaml
+++ b/source/uwp/Visualizer/MainPage.xaml
@@ -6,6 +6,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
+