using System ;
using System.Collections.Generic ;
using Windows.UI.Xaml ;
using Windows.UI.Xaml.Controls ;
using AdaptiveCards.Rendering.Uwp ;
using Monaco ;
using Monaco.Helpers ;
using Newtonsoft.Json ;
// probs don't need these
using System.IO ;
using System.Linq ;
using System.Runtime.InteropServices.WindowsRuntime ;
using System.Threading.Tasks ;
using Windows.Foundation ;
using Windows.Foundation.Collections ;
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 Monaco.Editor ;
using Monaco.Languages ;
using Newtonsoft.Json.Linq ;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
namespace Adaptive_Card_Editor_UWP
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
/// <summary>
/// Create DispatcherTimer for handling input delay logic
/// </summary>
public DispatcherTimer keyTimer = new DispatcherTimer ( ) ;
/// <summary>
/// timestamp in ticks (hundred nanoseconds) of last captured KeyUp event
/// </summary>
public long lastKeyUp ;
/// <summary>
/// boolean indicating whether input has changed since last tick
/// </summary>
public bool isRendered ;
/// <summary>
/// timer interval in ticks (hundred nanoseconds)
/// </summary>
public long interval = 5 0 0 0 0 0 0 ;
/// <summary>
/// list of known entities that contain templated content
/// </summary>
/// <remarks>
/// If the data referenced in the container doesn't exist, the container shouldn't be included in the rendered output
/// </remarks>
public List < string > Containers = new List < string >
{
"ActionSet" ,
"Container" ,
"ColumnSet" ,
"Column" ,
"FactSheet" ,
"Fact" ,
"ImageSet"
} ;
public MainPage ( )
{
// set up a window timer for handling the keyup delay
TimerSetup ( ) ;
this . InitializeComponent ( ) ;
}
/// <summary>
/// Sets up a window timer with a 500ms tick interval and starts the timer
/// </summary>
public void TimerSetup ( )
{
// on every timer tick, run TimerTick()
keyTimer . Tick + = TimerTick ;
// set the timer interval to half a second
keyTimer . Interval = TimeSpan . Parse ( "00:00:00.05" ) ;
// start the timer
keyTimer . Start ( ) ;
}
/// <summary>
/// fires when txtInput sees keyboard input
/// </summary>
private void txtInput_KeyDown ( CodeEditor sender , WebKeyEventArgs e )
{
// last key up event = integer value of current time
lastKeyUp = DateTime . Now . Ticks ;
// user is inputting text, so we're going to rerender
isRendered = false ;
}
/// <summary>
/// fires when txtData sees keyboard input
/// </summary>
private void txtData_KeyDown ( CodeEditor sender , WebKeyEventArgs args )
{
// last key up event = integer value of current time
lastKeyUp = DateTime . Now . Ticks ;
// user is inputting text, so we're going to rerender
isRendered = false ;
}
/// <summary>
/// Fires when the timer ticks
/// </summary>
void TimerTick ( object sender , object args )
{
// if isRendered is true, there have been no changes to input since the last tick
if ( isRendered )
{
return ;
}
// otherwise, we done got input, so do the thing
else
{
if ( DateTime . Now . Ticks > = lastKeyUp + interval )
{
// we done got some input! let's render it.
// we don't care what the input is; if it's not valid JSON ignore this keystroke
// render the text as a plain textbox
TextBlock cardOutput = new TextBlock ( ) ;
cardOutput . TextWrapping = TextWrapping . Wrap ;
cardOutput . Padding = new Thickness ( 1 0 ) ;
cardOutput . Text = txtInput . Text ;
// clear the grid of existing content
grdCard . Children . Clear ( ) ;
grdTemplated . Children . Clear ( ) ;
// render the adaptive card content
if ( txtInput . Text . Length > 0 )
{
// render an adaptive card
try
{
AdaptiveCardRenderer cardRenderer = new AdaptiveCardRenderer ( ) ;
AdaptiveCardParseResult parsedCard = AdaptiveCard . FromJsonString ( txtInput . Text ) ;
RenderedAdaptiveCard theCard = cardRenderer . RenderAdaptiveCard ( parsedCard . AdaptiveCard ) ;
grdCard . Children . Add ( theCard . FrameworkElement ) ;
}
catch ( Exception ex )
{
// this means bad data was ingested by the adaptive card renderer
// so just display the exception details
cardOutput . Text = ex . ToString ( ) ;
grdCard . Children . Add ( cardOutput ) ;
}
// render a card using template and data
try
{
string template = txtInput . Text ;
string data = txtData . Text ;
string rendered = JsonFromTemplate ( template , data ) ;
// render the card from the rendered template + data
AdaptiveCardRenderer cardRenderer = new AdaptiveCardRenderer ( ) ;
AdaptiveCardParseResult parsedCard = AdaptiveCard . FromJsonString ( rendered ) ;
RenderedAdaptiveCard theCard = cardRenderer . RenderAdaptiveCard ( parsedCard . AdaptiveCard ) ;
grdTemplated . Children . Add ( theCard . FrameworkElement ) ;
}
catch ( Exception ex )
{
// render the text as a plain textbox
TextBlock templateOutput = new TextBlock ( ) ;
templateOutput . TextWrapping = TextWrapping . Wrap ;
templateOutput . Padding = new Thickness ( 1 0 ) ;
templateOutput . Text = txtInput . Text ;
// this means bad data was ingested by the adaptive card renderer
// so just display the exception details
templateOutput . Text = ex . ToString ( ) ;
grdTemplated . Children . Add ( templateOutput ) ;
}
}
// update the XAML layout
grdCard . UpdateLayout ( ) ;
isRendered = true ;
}
else
{
return ;
}
}
}
/// <summary>
/// Creates an adaptive card JSON payload from a template and data model. Follows the official templating language.
/// </summary>
/// <param name="Template">JSON adaptive card template</param>
/// <param name="Data">JSON data model</param>
/// <returns>JSON string to be ingested by adaptive card renderer</returns>
public string JsonFromTemplate ( string strTemplate , string strData )
{
string output = "" ;
// first create JSON objects out of the input
JObject Template = JObject . Parse ( strTemplate ) ;
JObject Data = JObject . Parse ( strData ) ;
// replace instances of $data with x-data
// this is to work around JSON Path limitations
strTemplate = strTemplate . Replace ( "$" , "x-" ) ;
// first create JSON objects out of the input
JObject template = JObject . Parse ( strTemplate ) ;
JObject data = JObject . Parse ( strData ) ;
// this is the old way - regex against the string
// maybe do it a better way
string queryString = @"..[?(@ =~ /\{(.+)?\}/)]" ;
List < JToken > nodeList = template . SelectTokens ( queryString ) . ToList ( ) ;
// for each templated entity
// retrieve entity from data JSON
// if empty, remove entity
foreach ( JValue node in nodeList )
{
// get string literal for template entity
string fulltext = node . Value < string > ( ) ;
// find the matching element in the data JSON
// strip { and } from fulltext
string cleantext = fulltext . Substring ( 1 , fulltext . Length - 2 ) ;
string searchString = "$." + cleantext ;
// find the token in Data JSON
// can be JValue or JArray
JToken searchResult = data . SelectToken ( searchString ) ;
// if this element exists in the data
// and isn't $data
// replace element with text from Data JSON
// otherwise remove it
if ( searchResult ! = null )
{
if ( searchResult . GetType ( ) = = typeof ( JValue ) )
{
string oldValue = node . Value < string > ( ) ;
string newValue = oldValue . Replace ( fulltext , ( ( JValue ) searchResult . Value < string > ( ) ) . ToString ( ) ) ;
node . Value = node . Value < string > ( ) . ToString ( ) . Replace ( oldValue , newValue ) ;
}
else if ( searchResult . GetType ( ) = = typeof ( JArray ) )
{
// it's a JArray
JArray searchArray = ( JArray ) searchResult ;
foreach ( string item in searchArray )
{
JObject newItem = new JObject ( ( JObject ) node . Parent . Parent ) ;
newItem [ "x-data" ] . Parent . Remove ( ) ;
newItem [ "text" ] = item ;
node . Parent . Parent . Parent . Add ( newItem ) ;
}
node . Parent . Parent . Remove ( ) ;
}
}
else if ( fulltext . Contains ( "{x-" ) )
{
// this is handled differently
}
else
{
// remove this node
node . Parent . Parent . Remove ( ) ;
}
}
// now find any empty arrays and nuke their parents
// keep doing this until there are no more [] left in the JSON
while ( template . Descendants ( ) . OfType < JArray > ( ) . Where ( v = > v . Type = = JTokenType . Array & & v . Count ( ) = = 0 ) . ToList ( ) . Count ( ) > 0 )
{
List < JArray > EmptyNodes = template . Descendants ( ) . OfType < JArray > ( ) . Where ( v = > v . Type = = JTokenType . Array & & v . Count ( ) = = 0 ) . ToList ( ) ;
foreach ( JArray Node in EmptyNodes )
{
Node . Parent . Parent . Remove ( ) ;
}
}
output = JsonConvert . SerializeObject ( template ) ;
return output ;
}
}
}