| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112 | 
							- using System;
 
- using System.Collections.Generic;
 
- using System.Drawing;
 
- using System.Drawing.Imaging;
 
- using System.IO;
 
- using System.Linq;
 
- using System.Linq.Expressions;
 
- using System.Reflection;
 
- using System.Text;
 
- using System.Text.RegularExpressions;
 
- using Comal.Classes;
 
- using InABox.Clients;
 
- using InABox.Core;
 
- using PRSClasses;
 
- using PRSServer.Properties;
 
- using RazorEngine.Templating;
 
- using RazorEngine.Text;
 
- using Syncfusion.Pdf.Parsing;
 
- using Color = System.Windows.Media.Color;
 
- using ColorConverter = System.Windows.Media.ColorConverter;
 
- using Encoder = System.Drawing.Imaging.Encoder;
 
- namespace PRSServer
 
- {
 
-     /// <summary>
 
-     ///     Provides utility functions for writing Razor template pages
 
-     /// </summary>
 
-     public class WebUtils
 
-     {
 
-         #region Entity Editor
 
-         /// <summary>
 
-         ///     Builds an editor for a given entity.
 
-         /// </summary>
 
-         /// <remarks>To get reasonable formatting, import <see cref="WebUtils.DigitalFormViewerStyle" /> in a <style> tag</remarks>
 
-         /// <typeparam name="T">The type of <paramref name="entity" /></typeparam>
 
-         /// <param name="entity">The entity which contains the data to be pre-inserted into the editor</param>
 
-         /// <param name="id">An optional HTML id to add to root <div> of the generated item.</param>
 
-         /// <param name="classList">An optional list of HTML classes to add to root <div> of the generated item.</param>
 
-         /// <returns>A string containing raw HTML markup</returns>
 
-         public static string BuildEntityEditor<T>(T entity, string? id = null, string[]? classList = null) where T : Entity
 
-         {
 
-             var builder = new StringBuilder();
 
-             builder.AppendFormat(
 
-                 @"<div class=""prs-web_utils-entity_editor{0}""{1}>",
 
-                 classList != null ? " " + string.Join(" ", classList) : "",
 
-                 id != null ? " id=\"" + id + "\"" : ""
 
-             );
 
-             var layout = DFLayout.GenerateEntityLayout<T>();
 
-             var data = WebHandler.SerializeEntityForEditing(typeof(T), entity);
 
-             var markup = BuildDigitalFormViewer(layout, data, false, submitLabel: "Save", saveLabel: null);
 
-             builder.Append(markup).Append("</div>");
 
-             return builder.ToString();
 
-         }
 
-         #endregion
 
-         #region CoreTable Viewer
 
-         /// <summary>
 
-         ///     Used for determining which columns to show in an HTML representation of a CoreTable.
 
-         ///     EntityTableColumns.ALL means to show every column
 
-         ///     EntityTableColumns.VISIBLE means only show columns which do not have their visiblity set to hidden
 
-         ///     EntityTableColumns.REQUIRED means only show columns which have their visibility set to visible.
 
-         ///     EntityTableColumns.LOOK_UP means to use the Lookup associated with the entity to generate the columns.
 
-         /// </summary>
 
-         public enum EntityTableColumns
 
-         {
 
-             ALL,
 
-             VISIBLE,
 
-             LOOKUP,
 
-             REQUIRED
 
-         }
 
-         private static List<string> GetColumnsForTable<T>(EntityTableColumns showType = EntityTableColumns.ALL) where T : Entity
 
-         {
 
-             if (showType == EntityTableColumns.LOOKUP) return LookupFactory.DefineColumns(typeof(T)).ColumnNames().ToList<string>();
 
-             var columns = new List<string>();
 
-             var properties = CoreUtils.PropertyInfoList(
 
-                 typeof(T),
 
-                 x => x.GetCustomAttribute<DoNotPersist>() == null &&
 
-                      x.GetCustomAttribute<DoNotSerialize>() == null &&
 
-                      x.PropertyType != typeof(UserProperties), true);
 
-             switch (showType)
 
-             {
 
-                 case EntityTableColumns.VISIBLE:
 
-                     foreach (var property in properties)
 
-                     {
 
-                         var editor = property.Value.GetEditor();
 
-                         if (editor != null && editor.Visible != Visible.Hidden) columns.Add(property.Key);
 
-                     }
 
-                     break;
 
-                 case EntityTableColumns.REQUIRED:
 
-                     foreach (var property in properties)
 
-                     {
 
-                         var editor = property.Value.GetEditor();
 
-                         if (editor != null && editor.Visible == Visible.Default) columns.Add(property.Key);
 
-                     }
 
-                     break;
 
-                 case EntityTableColumns.ALL:
 
-                     foreach (var property in properties.Keys) columns.Add(property);
 
-                     break;
 
-             }
 
-             return columns;
 
-         }
 
-         /// <summary>
 
-         ///     Returns a string containing an HTML table representing the data present in a CoreTable.
 
-         /// </summary>
 
-         /// <typeparam name="T">The type of the entity that the table represents.</typeparam>
 
-         /// <param name="table">The table to be converted to HTML</param>
 
-         /// <param name="showType">A flag to determine which columns to show.</param>
 
-         /// <returns>The HTML string</returns>
 
-         public static string BuildHTMLTableFromCoreTable<T>(CoreTable table, EntityTableColumns showType = EntityTableColumns.ALL) where T : Entity
 
-         {
 
-             var columns = GetColumnsForTable<T>(showType);
 
-             var html = new List<string>();
 
-             html.Add("<table class=\"prs-web_utils-entity_table entity-" + typeof(T).Name + "\"><caption>");
 
-             html.Add(typeof(T).Name);
 
-             html.Add("</caption><thead><tr>");
 
-             foreach (var column in columns)
 
-             {
 
-                 html.Add("<th>");
 
-                 html.Add(column);
 
-                 html.Add("</th>");
 
-             }
 
-             html.Add("</tr></thead>");
 
-             foreach (var row in table.Rows)
 
-             {
 
-                 html.Add("<tr>");
 
-                 foreach (var column in columns)
 
-                 {
 
-                     html.Add("<td>");
 
-                     html.Add(row.Get<object?>(column)?.ToString() ?? "NULL");
 
-                     html.Add("</td>");
 
-                 }
 
-                 html.Add("</tr>");
 
-             }
 
-             html.Add("</table>");
 
-             html.Add("</table>");
 
-             return string.Concat(html);
 
-         }
 
-         /// <summary>
 
-         ///     Returns a string containing an HTML table representing the data present in a CoreTable.
 
-         /// </summary>
 
-         /// <param name="entityType">The type of the entity that the table represents</param>
 
-         /// <param name="table">The table to be converted to HTML</param>
 
-         /// <param name="showType">A flag to determine which columns to show.</param>
 
-         /// <returns>The HTML string</returns>
 
-         public static string BuildHTMLTableFromCoreTable(Type entityType, CoreTable table, EntityTableColumns showType = EntityTableColumns.ALL)
 
-         {
 
-             var fnc = typeof(WebUtils).GetMethod(nameof(BuildHTMLTableFromCoreTable), 1, new[] { typeof(CoreTable), typeof(EntityTableColumns) })!
 
-                 .MakeGenericMethod(entityType);
 
-             return (fnc.Invoke(null, new object[] { table, showType }) as string)!;
 
-         }
 
-         #endregion
 
-         #region Extra Utilities
 
-         public static string ConvertStringToPlainHTML(string text)
 
-         {
 
-             return text.Replace("&", "&").Replace("<", "<").Replace(">", ">").Replace("\"", """).ReplaceLineEndings("<br/>");
 
-         }
 
-         public enum ImageEncoding
 
-         {
 
-             JPEG
 
-         }
 
-         private static ImageCodecInfo? GetEncoder(ImageFormat format)
 
-         {
 
-             ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
 
-             foreach (ImageCodecInfo codec in codecs)
 
-             {
 
-                 if (codec.FormatID == format.Guid)
 
-                 {
 
-                     return codec;
 
-                 }
 
-             }
 
-             return null;
 
-         }
 
-         public static List<byte[]> RenderPDFToImages(byte[] pdfData, ImageEncoding encoding = ImageEncoding.JPEG)
 
-         {
 
-             var rendered = new List<byte[]>();
 
-             PdfLoadedDocument loadeddoc = new PdfLoadedDocument(pdfData);
 
-             Bitmap[] images = loadeddoc.ExportAsImage(0, loadeddoc.Pages.Count - 1);
 
-             var jpgEncoder = GetEncoder(ImageFormat.Jpeg)!;
 
-             var quality = Encoder.Quality;
 
-             var encodeParams = new EncoderParameters(1);
 
-             encodeParams.Param[0] = new EncoderParameter(quality, 100L);
 
-             if (images != null)
 
-                 foreach (var image in images)
 
-                 {
 
-                     using(var data = new MemoryStream())
 
-                     {
 
-                         image.Save(data, jpgEncoder, encodeParams);
 
-                         rendered.Add(data.ToArray());
 
-                     }
 
-                 }
 
-             return rendered;
 
-         }
 
-         private static readonly ImageConverter converter = new();
 
-         public static string WebDocumentToDataURL(string code)
 
-         {
 
-             var document = new Client<Document>().Query(
 
-                 new Filter<Document>(x => x.ID)
 
-                     .InQuery(new SubQuery<WebDocument>(
 
-                         new Filter<WebDocument>(x => x.Code).IsEqualTo(code),
 
-                         new Column<WebDocument>(x => x.Document.ID))),
 
-                 new Columns<Document>(x => x.Data));
 
-             var data = (byte[]?)document.Rows.FirstOrDefault()?["Data"];
 
-             return "data:;base64," + (data != null ? Convert.ToBase64String(data) : "");
 
-         }
 
-         public static Guid? WebDocumentID(string code)
 
-         {
 
-             var document = new Client<Document>().Query(
 
-                 new Filter<Document>(x => x.ID)
 
-                     .InQuery(new SubQuery<WebDocument>(
 
-                         new Filter<WebDocument>(x => x.Code).IsEqualTo(code),
 
-                         new Column<WebDocument>(x => x.Document.ID))),
 
-                 new Columns<Document>(x => x.Data));
 
-             return (Guid?)document.Rows.FirstOrDefault()?["ID"];
 
-         }
 
-         public static string ResourceToDataURL(Bitmap resource)
 
-         {
 
-             return "data:;base64," + Convert.ToBase64String((byte[])converter.ConvertTo(resource, typeof(byte[]))!);
 
-         }
 
-         #endregion
 
-         #region Security
 
-         public static bool UserCanAccess<T>(IDataModel model) where T : ISecurityDescriptor, new()
 
-         {
 
-             var userTable = model.GetTable<User>();
 
-             if (userTable.Rows.Count == 0) return false;
 
-             var user = userTable.Rows[0].ToObject<User>();
 
-             return Security.IsAllowed<T>(user.ID, user.SecurityGroup.ID);
 
-         }
 
-         public static bool IsLoggedIn(IDataModel model)
 
-         {
 
-             var userTable = model.GetTable<User>();
 
-             return userTable.Rows.Count > 0;
 
-         }
 
-         public static User? GetUser(IDataModel model)
 
-         {
 
-             var userTable = model.GetTable<User>();
 
-             if (userTable.Rows.Count == 0) return null;
 
-             return userTable.Rows[0].ToObject<User>();
 
-         }
 
-         #endregion
 
-         #region Directory Viewer
 
-         public static string DirectoryViewerStyle { get; } = @"<style>
 
- .prs-web_utils-file_manager {
 
-     width: 30em;
 
-     height: 40em;
 
-     padding: 0.5em; 
 
-     text-align: left;
 
-     overflow: auto;
 
-     border: 1px solid black;
 
-     background-color: white;
 
-     box-sizing: border-box;
 
- }
 
- .prs-web_utils-file_manager * {
 
-     box-sizing: border-box;
 
- }
 
- .prs-web_utils-file_manager-file,.prs-web_utils-file_manager-directory_head {
 
-     font-size: 1em;
 
-     color: black;
 
-     width: 100%;
 
-     overflow: hidden;
 
-     white-space: nowrap;
 
-     text-overflow: ellipsis;
 
-     
 
-     text-align: left;
 
-     
 
-     padding-left: 1.5em;
 
-     background-position: left;
 
-     background-repeat: no-repeat;
 
-     background-size: contain;
 
- }
 
- .prs-web_utils-file_manager-file {
 
-     border: #0000 solid 1px;
 
-     background-image: url(" + ResourceToDataURL(Resources.file) + @");
 
- }
 
- .prs-web_utils-file_manager-directory_head {
 
-     background-color: white;
 
-     background-image: url(" + ResourceToDataURL(Resources.folder) + @");
 
- }
 
- .prs-web_utils-file_manager-pdf_type {
 
-     background-image: url(" + ResourceToDataURL(Resources.pdf) + @");
 
- }
 
- .prs-web_utils-file_manager-png_type {
 
-     background-image: url(" + ResourceToDataURL(Resources.png) + @");
 
- }
 
- .prs-web_utils-file_manager-jpg_type {
 
-     background-image: url(" + ResourceToDataURL(Resources.jpg) + @");
 
- }
 
- .prs-web_utils-file_manager-directory_head:active {
 
-     cursor: pointer;
 
-     background-color: #CCCCCC
 
- }
 
- @media (hover:hover) {
 
-     .prs-web_utils-file_manager-directory_head:hover {
 
-         cursor: pointer;
 
-         background-color: #DDDDDD
 
-     }
 
-     .prs-web_utils-file_manager-file:hover {
 
-         cursor: pointer;
 
-         background-color: #b8d9e7;
 
-         border: #79c4e5 solid 1px;
 
-     }
 
- }
 
- .prs-web_utils-file_manager-download {
 
-     display: inline-block;
 
-     width: 1.5em;
 
-     height: 1.5em;
 
-     padding: 0.5em;
 
-     float: right;
 
-     border: solid 1px black;
 
-     background-image: url(" + ResourceToDataURL(Resources.download) + @");
 
-     background-size: 100% 100%;
 
- }
 
- .prs-web_utils-file_manager-directory {
 
-     width: 100%;
 
-     padding-left: 0.5em;
 
-     border-left: dashed 1px #AAA;
 
-     border-bottom: dashed 1px #AAA;
 
- }
 
- .prs-web_utils-file_manager-collapsed {
 
-     display: none;
 
- }
 
- </style>";
 
-         private interface IFileItem
 
-         {
 
-             public string FilePath { get; }
 
-             public string FileName { get; }
 
-         }
 
-         private class FileItem : IFileItem
 
-         {
 
-             public FileItem(string filePath, string fileName)
 
-             {
 
-                 FilePath = filePath;
 
-                 FileName = fileName;
 
-             }
 
-             public string FilePath { get; }
 
-             public string FileName { get; }
 
-         }
 
-         private class DirectoryItem : IFileItem
 
-         {
 
-             public readonly List<DirectoryItem> Directories = new();
 
-             public readonly List<FileItem> Files = new();
 
-             public DirectoryItem(string filePath, string fileName)
 
-             {
 
-                 FilePath = filePath;
 
-                 FileName = fileName;
 
-             }
 
-             public string FilePath { get; }
 
-             public string FileName { get; }
 
-         }
 
-         private static DirectoryItem? GetDirectoryStructure(string path, Func<string, bool>? fileFilter, Func<string, bool>? directoryFilter,
 
-             string? name = null, int depth = 2)
 
-         {
 
-             var fileName = name ?? path;
 
-             if (depth == 0) return new DirectoryItem(path, fileName);
 
-             if (!Directory.Exists(path)) return null;
 
-             var directory = new DirectoryItem(path, fileName);
 
-             foreach (var item in Directory.GetDirectories(path))
 
-                 if (directoryFilter?.Invoke(item) != false)
 
-                     try
 
-                     {
 
-                         var subDirectory = GetDirectoryStructure(item, fileFilter, directoryFilter, Path.GetFileName(item), depth - 1);
 
-                         if (subDirectory != null) directory.Directories.Add(subDirectory);
 
-                     }
 
-                     catch (UnauthorizedAccessException)
 
-                     {
 
-                     }
 
-             foreach (var item in Directory.GetFiles(path))
 
-                 if (fileFilter?.Invoke(item) != false)
 
-                     directory.Files.Add(new FileItem(item, Path.GetFileName(item)));
 
-             return directory;
 
-         }
 
-         private static DirectoryItem? GetDirectoryStructure(string path, string? fileFilter, string? directoryFilter, string? name = null,
 
-             int depth = 2)
 
-         {
 
-             var fileFilterRgx = fileFilter != null ? new Regex(fileFilter) : null;
 
-             var dirFilterRgx = directoryFilter != null ? new Regex(directoryFilter) : null;
 
-             return GetDirectoryStructure(
 
-                 path,
 
-                 fileFilterRgx != null ? x => fileFilterRgx.IsMatch(x) : null,
 
-                 dirFilterRgx != null ? x => dirFilterRgx.IsMatch(x) : null,
 
-                 name, depth
 
-             );
 
-         }
 
-         private static readonly string DirectoryViewerScript = @"<script>
 
- function headClickEventListener(e){
 
-     var next = e.target.nextSibling;
 
-     next.classList.toggle(""prs-web_utils-file_manager-collapsed"");
 
- }
 
- var heads = document.getElementsByClassName(""prs-web_utils-file_manager-directory_head"");
 
- for(var head of heads){
 
-     head.addEventListener(""click"", headClickEventListener);
 
- }
 
- </script>";
 
-         private static void BuildDirectory(StringBuilder builder, DirectoryItem directory)
 
-         {
 
-             builder.Append(@"<div class=""prs-web_utils-file_manager-directory_head"">");
 
-             builder.Append(directory.FileName);
 
-             builder.Append(@"</div>");
 
-             builder.Append(@"<div class=""prs-web_utils-file_manager-directory prs-web_utils-file_manager-collapsed""/>");
 
-             if (directory.Directories.Count == 0 && directory.Files.Count == 0)
 
-             {
 
-                 builder.Append(@"Folder is empty");
 
-             }
 
-             else
 
-             {
 
-                 foreach (var subDirectory in directory.Directories) BuildDirectory(builder, subDirectory);
 
-                 foreach (var file in directory.Files)
 
-                 {
 
-                     var typeClass = Path.GetExtension(file.FileName) switch
 
-                     {
 
-                         ".png" => "prs-web_utils-file_manager-png_type",
 
-                         ".pdf" => "prs-web_utils-file_manager-pdf_type",
 
-                         ".jpg" => "prs-web_utils-file_manager-jpg_type",
 
-                         ".jpeg" => "prs-web_utils-file_manager-jpg_type",
 
-                         _ => null
 
-                     };
 
-                     builder.Append(@"<div class=""prs-web_utils-file_manager-file");
 
-                     if (typeClass != null) builder.Append(" " + typeClass);
 
-                     builder.Append(string.Format(@""" data-file=""{0}"">", file.FilePath));
 
-                     builder.Append(@"<div class=""prs-web_utils-file_manager-download""></div>");
 
-                     builder.Append(file.FileName);
 
-                     builder.Append(@"</div>");
 
-                 }
 
-             }
 
-             builder.Append(@"</div>");
 
-         }
 
-         /// <summary>
 
-         /// </summary>
 
-         /// <param name="path">The path to the folder in which to initialise the directory viewer</param>
 
-         /// <param name="fileFilter">A file filter which is compiled to a Regex. Regex.IsMatch is used to filter files</param>
 
-         /// <param name="directoryFilter">
 
-         ///     A directory filter which is compiled to a Regex. Regex.IsMatch is used to filter
 
-         ///     directories
 
-         /// </param>
 
-         /// <param name="alias">The name to call the root folder. If null, defaults to the directory path.</param>
 
-         /// <param name="id">An HTML id attribute for the viewer</param>
 
-         /// <param name="classList">An HTML class list for the viewer</param>
 
-         /// <param name="depth">The depth of directories to search</param>
 
-         /// <returns></returns>
 
-         public static string BuildDirectoryViewer(string path, Func<string, bool> fileFilter, Func<string, bool>? directoryFilter = null,
 
-             string? alias = null, string? id = null, string[]? classList = null, int depth = 2)
 
-         {
 
-             var stringBuilder = new StringBuilder();
 
-             stringBuilder.Append(@"<div ");
 
-             if (id != null) stringBuilder.Append("id=\"" + id + "\" ");
 
-             stringBuilder.Append(@"class=""prs-web_utils-file_manager");
 
-             if (classList != null)
 
-                 foreach (var className in classList)
 
-                     stringBuilder.Append(" " + className);
 
-             stringBuilder.Append(@""">");
 
-             var structure = GetDirectoryStructure(path, fileFilter, directoryFilter, alias ?? path, depth);
 
-             if (structure != null) BuildDirectory(stringBuilder, structure);
 
-             stringBuilder.Append(@"</div>");
 
-             stringBuilder.Append(DirectoryViewerScript);
 
-             return stringBuilder.ToString();
 
-         }
 
-         public static string BuildDirectoryViewer(string path, string fileFilter, string? directoryFilter = null, string? alias = null,
 
-             string? id = null,
 
-             string[]? classList = null, int depth = 2)
 
-         {
 
-             var fileFilterRgx = new Regex(fileFilter);
 
-             var dirFilterRgx = directoryFilter != null ? new Regex(directoryFilter) : null;
 
-             return BuildDirectoryViewer(
 
-                 path,
 
-                 x => fileFilterRgx.IsMatch(x),
 
-                 dirFilterRgx != null ? x => dirFilterRgx.IsMatch(x) : null,
 
-                 alias, id, classList, depth
 
-             );
 
-         }
 
-         #endregion
 
-         #region JSON Entities
 
-         public static Dictionary<string, object> SerializeEntity(Type entityType, Entity entity)
 
-         {
 
-             var data = new Dictionary<string, object>();
 
-             foreach (var property in DatabaseSchema.Properties(entityType))
 
-             {
 
-                 data[property.Name] = CoreUtils.GetPropertyValue(entity, property.Name);
 
-             }
 
-             return data;
 
-         }
 
-         /// <summary>
 
-         /// Generates a JSON object for javascript
 
-         /// </summary>
 
-         /// <typeparam name="T"></typeparam>
 
-         /// <param name="entity"></param>
 
-         /// <returns></returns>
 
-         public static string EntityToJSON<T>(T entity) where T : Entity
 
-         {
 
-             var serializedEntity = SerializeEntity(typeof(T), entity);
 
-             var serializedJSON = Serialization.Serialize(serializedEntity);
 
-             return serializedJSON;
 
-         }
 
-         public static string CreateJSONEntity<T>() where T : Entity, new()
 
-         {
 
-             var serializedEntity = SerializeEntity(typeof(T), new T());
 
-             var serializedJSON = Serialization.Serialize(serializedEntity);
 
-             return serializedJSON;
 
-         }
 
-         /// <summary>
 
-         /// Generate code for setting a property of a JSON entity
 
-         /// </summary>
 
-         /// <typeparam name="T"></typeparam>
 
-         /// <param name="entityExpr">A Javascript expression that evaluates to the entity JSON</param>
 
-         /// <param name="expression"></param>
 
-         /// <returns></returns>
 
-         public static string JSONSetProperty<T>(string entityExpr, Expression<Func<T, object>> expression, string value) where T : Entity
 
-         {
 
-             var propName = CoreUtils.GetFullPropertyName(expression, ".");
 
-             return $"({entityExpr})[\"{propName}\"]={value}";
 
-         }
 
-         /// <summary>
 
-         /// Generate code for getting a property of a JSON entity
 
-         /// </summary>
 
-         /// <typeparam name="T"></typeparam>
 
-         /// <param name="entityExpr">A Javascript expression that evaluates to the entity JSON</param>
 
-         /// <param name="expression"></param>
 
-         /// <returns></returns>
 
-         public static string JSONGetProperty<T>(string entityExpr, Expression<Func<T, object>> expression) where T : Entity
 
-         {
 
-             var propName = CoreUtils.GetFullPropertyName(expression, ".");
 
-             return $"({entityExpr})[\"{propName}\"]";
 
-         }
 
-         #endregion
 
-         #region Digital Form Viewer
 
-         private const string _multiImageStyle = @"
 
- .prs-web_utils-digital_form-multi_image {
 
-     
 
- }
 
- .prs-web_utils-digital_form-multi_image_cont {
 
-     overflow: auto;
 
-     position: relative;
 
-     height: 10em;
 
-     background: #999;
 
-     box-shadow: black inset 0 0 4px;
 
- }
 
- .prs-web_utils-digital_form-multi_image_cont > div {
 
-     position: absolute;
 
-     top: 0;
 
-     bottom: 0;
 
-     display: flex;
 
-     gap: 1em;
 
-     padding: 0.4em;
 
- }
 
- .prs-web_utils-digital_form-multi_image_cont img {
 
-     max-height: 100%;
 
- }
 
- .prs-web_utils-digital_form-multi_image_cont img.selected {
 
-     border: dashed 2px black;
 
- }
 
- .prs-web_utils-digital_form-multi_image_btns {
 
-     display: flex;
 
-     gap: 0.2em;
 
- }
 
- .prs-web_utils-digital_form-multi_image_btns .prs-button {
 
-     flex: 1;
 
-     height: 4em;
 
-     display: flex;
 
-     justify-content: center;
 
-     align-items: center;
 
- }
 
- ";
 
-         private static string _digitalFormViewerStyle = @"
 
- .prs-web_utils-digital_form {
 
-     display: grid;
 
-     column-gap: 0.4em;
 
-     row-gap: 0.4em;
 
- }
 
- .prs-web_utils-digital_form-field {
 
-     min-height: 2em;
 
- }
 
- @media (pointer:coarse), (pointer:none) {
 
-     .prs-web_utils-digital_form-field {
 
-         min-height: 4em;
 
-     }
 
- }
 
- .prs-web_utils-digital_form * {
 
-     box-sizing: border-box;
 
- }
 
- .prs-web_utils-digital_form-label {
 
-     text-align: left;
 
-     padding: 1.5em 0.3em;
 
-     align-self: center;
 
-     overflow-wrap: anywhere;
 
- }
 
- .prs-web_utils-digital_form-image {
 
-     max-width: 100%;
 
-     max-height: 100%;
 
- }
 
- .prs-web_utils-digital_form-color,
 
- .prs-web_utils-digital_form-integer,
 
- .prs-web_utils-digital_form-text,
 
- .prs-web_utils-digital_form-string,
 
- .prs-web_utils-digital_form-url,
 
- .prs-web_utils-digital_form-password,
 
- .prs-web_utils-digital_form-date,
 
- .prs-web_utils-digital_form-datetime {
 
-     background-color: white;
 
- }
 
- .prs-web_utils-digital_form-time {
 
-     display: flex;
 
-     align-items: center;
 
-     width: min-content;
 
-     
 
-     background: white;
 
-     border: solid 1px gray;
 
- }
 
- .prs-web_utils-digital_form-time_hours {
 
-     text-align: right;
 
- }
 
- .prs-web_utils-digital_form-time_mins {
 
-     text-align: left;
 
- }
 
- .prs-web_utils-digital_form-time span {
 
-     margin: 0.1em;
 
- }
 
- .prs-web_utils-digital_form-time input {
 
-     height: 100%;
 
-     width: 3em;
 
-     font-family: monospace;
 
-     border: none;
 
-     background: none;
 
- }
 
- .prs-web_utils-digital_form-time input:active {
 
-     background: #ddd;
 
- }
 
- .prs-web_utils-digital_form-timestamp {
 
-     display: flex;
 
-     justify-content: space-between;
 
-     align-items: center;
 
-     gap: 0.4em;
 
- }
 
- .prs-web_utils-digital_form-timestamp input {
 
-     flex: 1;
 
-     height: 100%;
 
- }
 
- .prs-web_utils-digital_form-timestamp .prs-button {
 
-     height: 100%;
 
-     padding: 1em;
 
-     display: flex;
 
-     align-items: center;
 
- }
 
- .prs-web_utils-digital_form-pin {
 
-     display: flex;
 
-     justify-content: space-between;
 
-     align-items: center;
 
-     gap: 0.4em;
 
- }
 
- .prs-web_utils-digital_form-pin input {
 
-     flex: 1;
 
-     height: 100%;
 
- }
 
- .prs-web_utils-digital_form-pin .prs-button {
 
-     height: 100%;
 
-     padding: 1em;
 
-     display: flex;
 
-     align-items: center;
 
- }
 
- .prs-web_utils-digital_form-document {
 
-     display: flex;
 
-     justify-content: space-between;
 
-     align-items: center;
 
-     gap: 0.4em;
 
- }
 
- .prs-web_utils-digital_form-document input {
 
-     flex: 1;
 
-     height: 100%;
 
- }
 
- .prs-web_utils-digital_form-document .prs-button {
 
-     height: 100%;
 
-     padding: 1em;
 
-     display: flex;
 
-     align-items: center;
 
- }
 
- .prs-web_utils-digital_form-notes {
 
-     display: flex;
 
-     gap: 0.2em;
 
- }
 
- .prs-web_utils-digital_form-note_cont {
 
-     flex: 1;
 
-     
 
-     display: flex;
 
-     flex-direction: column;
 
-     gap: 0.2em;
 
- }
 
- .prs-web_utils-digital_form-note {
 
-     resize: none;
 
-     width: 100%;
 
-     min-height: 6em;
 
-     
 
-     background-color: #cdcdcd;
 
- }
 
- .prs-web_utils-digital_form-note.active {
 
-     background-color: lightgoldenrodyellow;
 
- }
 
- .prs-web_utils-digital_form-note_error {
 
-     color: black;
 
-     text-align: left;
 
- }
 
- .prs-web_utils-digital_form-note_add {
 
-     display: flex;
 
-     justify-content: center;
 
-     align-items: center;
 
-     text-align: center;
 
-     background-color: #dedede;
 
-     border: solid 1px black;
 
-     border-radius: 3px;
 
-     user-select: none;
 
-     
 
-     width: 2em;
 
- }
 
- @media (hover:hover) {
 
-     .prs-web_utils-digital_form-note_add:hover {
 
-         cursor: pointer;
 
-         background-color: #cacaca;
 
-     }
 
- }
 
- .prs-web_utils-digital_form-note_add:active {
 
-     background-color: #ccc;
 
- }
 
- .prs-web_utils-digital_form-embedded_image, .prs-web_utils-digital_form-signature {
 
-     max-width: 100%;
 
-     border-radius: 3px;
 
-     min-height: 2em;
 
-     display: flex;
 
-     flex-direction: column;
 
- }
 
- .prs-web_utils-digital_form-img_cont {
 
-     flex: 1;
 
-     overflow: hidden;
 
- }
 
- .prs-web_utils-digital_form-clear_img {
 
-     height: 4em;
 
-     display: flex;
 
-     justify-content: center;
 
-     align-items: center;
 
- }
 
- .prs-web_utils-digital_form-img {
 
-     max-width: 100%;
 
-     max-height: 100%;
 
- }
 
- div.prs-web_utils-digital_form-boolean {
 
-     display: flex;
 
-     align-items: center;
 
-     gap: 0.2em;
 
- }
 
- .prs-web_utils-digital_form-boolean label {
 
-     flex: 1;
 
-     text-align: left;
 
- }
 
- input.prs-web_utils-digital_form-boolean {
 
-     width: min-content;
 
-     height: min-content;
 
-     margin: auto;
 
- }
 
- .prs-web_utils-digital_form-lookup, .prs-web_utils-digital_form-option {
 
-     min-width: 0;
 
-     min-height: 2em;
 
-     background-color: white;
 
- }
 
- .prs-web_utils-digital_form-option_fake {
 
-     visibility:hidden;
 
-     margin-left:1em;
 
-     overflow:hidden;
 
-     min-height: 2em;
 
- }
 
- .prs-web_utils-digital_form-boolean .prs-web_utils-digital_form-button {
 
-     display: inline-block;
 
-     flex: 1;
 
-     height: 100%;
 
-     position: relative;
 
-     height: 2em;
 
-     
 
-     background: none;
 
-     border: none;
 
- }
 
- .prs-web_utils-digital_form-boolean .prs-web_utils-digital_form-button input {
 
-     appearance: none;
 
-     background-color: #a7a7a7;
 
-     border-radius: 3px;
 
-     border: dashed 1px black;
 
-     margin: 0;
 
-     
 
-     width: 100%;
 
-     height: 100%;
 
- }
 
- .prs-web_utils-digital_form-boolean .prs-web_utils-digital_form-button input:checked {
 
-     background-color: #e7e7e7;
 
-     border: solid 1px black;
 
- }
 
- @media (hover:hover) {
 
-     .prs-web_utils-digital_form-boolean .prs-web_utils-digital_form-button input:hover {
 
-         cursor:pointer;
 
-         background-color: #a3a3a3;
 
-     }
 
-     .prs-web_utils-digital_form-boolean .prs-web_utils-digital_form-button input:checked:hover {
 
-         background-color: #e0e0e0;
 
-     }
 
- }
 
- .prs-web_utils-digital_form-boolean .prs-web_utils-digital_form-button input:active {
 
-     background-color: #979797;
 
- }
 
- .prs-web_utils-digital_form-boolean .prs-web_utils-digital_form-button input:checked:active {
 
-     background-color: #c6c6c6;
 
- }
 
- .prs-web_utils-digital_form-boolean .prs-web_utils-digital_form-button span {
 
-     position: absolute;
 
-     left: 0;
 
-     right: 0;
 
-     top: 0;
 
-     bottom: 0;
 
-     pointer-events: none;
 
-     
 
-     line-height: 2em;
 
- }
 
- .prs-web_utils-digital_form-radio_cont {
 
-     display: flex;
 
-     justify-content: stretch;
 
-     align-items: center;
 
- }
 
- .prs-web_utils-digital_form-radio_cont label {
 
-     flex: 1;
 
-     text-align: left;
 
- }
 
- .prs-web_utils-digital_form-save_cont {
 
-     margin-top: 2em;
 
-     display: flex;
 
-     gap: 0.2em;
 
- }
 
- .prs-web_utils-digital_form-save_cont .prs-button {
 
-     height: 4em;
 
-     flex: 1;
 
-     display: flex;
 
-     align-items: center;
 
-     justify-content: center;
 
- }
 
- .prs-web_utils-digital_form-add_task {
 
-     display: flex;
 
-     gap: 0.4em;
 
- }
 
- .prs-web_utils-digital_form-add_task .prs-button {
 
-     height: 4em;
 
-     padding: 0 1em;
 
-     
 
-     display: flex;
 
-     align-items: center;
 
-     justify-content: center;
 
- }
 
- .prs-web_utils-digital_form-add_task input {
 
-     flex: 1;
 
- }
 
- .prs-web_utils-digital_form input:invalid {
 
-     background-color: #FFDDDD;
 
- }
 
- .prs-web_utils-digital_form-field:required {
 
-     border: solid 1px #edaf58;
 
- }
 
- .prs-web_utils-digital_form-validate {
 
-     position: absolute;
 
-     top: 1em;
 
-     left: 1em;
 
-     height: 3em;
 
-     line-height: 3em;
 
-     padding: 0 1em;
 
-     background: white;
 
-     border: black solid 1px;
 
-     border-radius: 3px;
 
-     box-shadow: grey 0 0 10px;
 
-     transition: opacity 1s linear 0s;
 
- }
 
- " + _multiImageStyle;
 
-         /// <summary>
 
-         ///     A string which contains a stylesheet used for formatting digital forms. This is not necessary, but can be useful as
 
-         ///     a base.<br />
 
-         ///     Include within the <style> tag in your document head.
 
-         /// </summary>
 
-         public static string DigitalFormViewerStyle => _digitalFormViewerStyle;
 
-         private const string _submitScript = @"
 
- var submitButtons = document.getElementsByClassName(""prs-web_utils-digital_form-submit"");
 
- var saveButtons = document.getElementsByClassName(""prs-web_utils-digital_form-save"");
 
- function getData(element){
 
-     if(element.classList.contains(""prs-web_utils-digital_form-double"")
 
-             || element.classList.contains(""prs-web_utils-digital_form-integer"")){
 
-         var res = element.valueAsNumber;
 
-         if(Number.isNaN(res)){
 
-             return null;
 
-         }
 
-         return res;
 
-     } else if(element.classList.contains(""prs-web_utils-digital_form-color"")){
 
-         return ""#FF"" + element.value.substring(1).toUpperCase();
 
-     } else if(element.classList.contains(""prs-web_utils-digital_form-multi_image"")){
 
-         let imgCont = element.getElementsByClassName(""prs-web_utils-digital_form-multi_image_cont"")[0];
 
-         let imgContDiv = imgCont.children[0];
 
-         
 
-         let imgs = [];
 
-         for(let img of imgContDiv.children){
 
-             imgs.push(img.src.replace(/^data:(.*,)?/, ''));
 
-         }
 
-         return imgs;
 
-     } else if(element.classList.contains(""prs-web_utils-digital_form-notes"")){
 
-         let cont = element.getElementsByClassName(""prs-web_utils-digital_form-note_cont"")[0];
 
-         let retVal = [];
 
-         for(let note of cont.children){
 
-             retVal.push(note.value);
 
-         }
 
-         if(!(/\S/.test(retVal[retVal.length - 1]))){
 
-             retVal.pop();
 
-         }
 
-         return retVal;
 
-     } else if(element.classList.contains(""prs-web_utils-digital_form-time"")){
 
-         let hours = element.getElementsByClassName(""prs-web_utils-digital_form-time_hours"")[0];
 
-         let mins = element.getElementsByClassName(""prs-web_utils-digital_form-time_mins"")[0];
 
-         return hours.value + "":"" + mins.value;
 
-     } else if(element.classList.contains(""prs-web_utils-digital_form-date"")
 
-             || element.classList.contains(""prs-web_utils-digital_form-datetime"")){
 
-         return element.value;
 
-     } else if(element.classList.contains(""prs-web_utils-digital_form-pin"")){
 
-         let pinText = element.getElementsByClassName(""prs-web_utils-digital_form-pin_text"")[0];
 
-         return pinText.value;
 
-     } else if(element.classList.contains(""prs-web_utils-digital_form-timestamp"")){
 
-         let dateText = element.getElementsByClassName(""prs-web_utils-digital_form-timestamp_date"")[0];
 
-         return dateText.value;
 
-     } else if(element.classList.contains(""prs-web_utils-digital_form-text"")
 
-             || element.classList.contains(""prs-web_utils-digital_form-string"")
 
-             || element.classList.contains(""prs-web_utils-digital_form-password"")
 
-             || element.classList.contains(""prs-web_utils-digital_form-url"")
 
-             || element.classList.contains(""prs-web_utils-digital_form-code"")
 
-             || element.classList.contains(""prs-web_utils-digital_form-option"")){
 
-         return element.value;
 
-     } else if(element.classList.contains(""prs-web_utils-digital_form-radio_cont"")){
 
-         var options = element.getElementsByTagName(""input"");
 
-         for(let option of options){
 
-             if(option.checked){
 
-                 return option.value;
 
-             }
 
-         }
 
-         return null;
 
-     } else if(element.classList.contains(""prs-web_utils-digital_form-lookup"")){
 
-         return element.value;
 
-     } else if(element.classList.contains(""prs-web_utils-digital_form-embedded_image"")
 
-             || element.classList.contains(""prs-web_utils-digital_form-signature"")){
 
-         var imgs = element.getElementsByClassName(""prs-web_utils-digital_form-img"");
 
-         if(imgs.length == 0){
 
-             return null;
 
-         } else {
 
-             var img = imgs[0];
 
-             var src = img.getAttribute(""src"");
 
-             var match = /data:[^;]*;base64,/d.exec(src);
 
-             if(match){
 
-                 return src.substring(match.indices[0][1]);
 
-             }
 
-             return null;
 
-         }
 
-     } else if(element.classList.contains(""prs-web_utils-digital_form-document"")){
 
-         return element.getAttribute(""data-id"");
 
-     } else if(element.classList.contains(""prs-web_utils-digital_form-boolean"")){
 
-         if(element.tagName == ""INPUT""){
 
-             return element.checked;
 
-         } else if(element.tagName == ""SELECT""){
 
-             if(element.selectedOptions.length > 0){
 
-                 return element.selectedOptions[0].hasAttribute(""data-true"");
 
-             }
 
-             return null;
 
-         } else if(element.tagName == ""DIV""){
 
-             for(var element of element.getElementsByTagName(""input"")){
 
-                 if(element.checked){
 
-                     return element.hasAttribute(""data-true"");
 
-                 }
 
-             }
 
-             return null;
 
-         }
 
-     } else if(element.classList.contains(""prs-web_utils-digital_form-add_task"")){
 
-         var input = element.getElementsByTagName(""input"")[0];
 
-         return input.value;
 
-     }
 
- }
 
- function validate(element){
 
-     let valid = true;
 
-     let err = """";
 
-     if(element.classList.contains(""prs-web_utils-digital_form-embedded_image"")
 
-             || element.classList.contains(""prs-web_utils-digital_form-signature"")){
 
-         let imgCont = element.getElementsByClassName(""prs-web_utils-digital_form-img_cont"")[0];
 
-         let imgs = imgCont.getElementsByTagName(""img"");
 
-         if(imgs.length == 0){
 
-             valid = false;
 
-             err = ""Please provide an image"";
 
-         }
 
-     } else if(element.classList.contains(""prs-web_utils-digital_form-multi_image"")){
 
-         let imgCont = element.getElementsByClassName(""prs-web_utils-digital_form-multi_image_cont"")[0];
 
-         let imgContDiv = imgCont.children[0];
 
-         if(imgContDiv.children.length == 0){
 
-             valid = false;
 
-             err = ""Please provide at least one image"";
 
-         }
 
-     }
 
-     return [valid, err];
 
- }
 
- function doValidationCheck(form){
 
-     var validations = form.getElementsByClassName(""prs-web_utils-digital_form-validate"");
 
-     for(let validation of validations){
 
-         validation.remove();
 
-     }
 
-     if(!form.reportValidity()){ return false; }
 
-     
 
-     let valid = true;
 
-     var elements = form.getElementsByClassName(""prs-web_utils-digital_form-field"");
 
-     for(var element of elements){
 
-         if(element.hasAttribute(""required"")){
 
-             var [elValid, err] = validate(element);
 
-             valid = elValid && valid;
 
-             if(!elValid){
 
-                 element.scrollIntoView();
 
-                 let validation = document.createElement(""div"");
 
-                 validation.classList.add(""prs-web_utils-digital_form-validate"");
 
-                 validation.textContent = err;
 
-                 element.style.position = ""relative"";
 
-                 element.appendChild(validation);
 
-                 setTimeout(() => {validation.style.opacity = 0;}, 5000);
 
-             }
 
-         }
 
-     }
 
-     return valid;
 
- }
 
- function submitForm(form, save, validate = true){
 
-     if(!validate || doValidationCheck(form)){
 
-         var formData = {};
 
-     
 
-         var elements = form.getElementsByClassName(""prs-web_utils-digital_form-field"");
 
-         for(var element of elements){
 
-             formData[element.getAttribute(""name"")] = getData(element);
 
-             if(element.classList.contains(""prs-web_utils-digital_form-lookup"")){
 
-                 for(var option of element.getElementsByTagName(""option"")){
 
-                     if(option.value == element.value){
 
-                         formData[element.getAttribute(""name"") + ""$ID""] = option.getAttribute(""data-id"");
 
-                         break;
 
-                     }
 
-                 }
 
-             }
 
-         }
 
-         let eventName = save ? ""prs-web_utils-df_save"" : ""prs-web_utils-df_submit"";
 
-         const submitFormEvent = new CustomEvent(eventName, { detail: { formData: formData } });
 
-         form.dispatchEvent(submitFormEvent);
 
-     }
 
- }
 
- for(var btn of submitButtons){
 
-     btn.addEventListener(""click"", function(e) {
 
-         var form = e.target.parentNode.parentNode;
 
-         submitForm(form, false, true);
 
-     });
 
- }
 
- for(var btn of saveButtons){
 
-     btn.addEventListener(""click"", function(e) {
 
-         var form = e.target.parentNode.parentNode;
 
-         submitForm(form, true, false);
 
-     });
 
- }
 
- ";
 
-         private static string GetDigitalFormViewerScript()
 
-         {
 
-             return @"<script>
 
- function checkDisabled(target, fnc){
 
-     target.addEventListener(""click"", function(e){
 
-         return !target.hasAttribute(""disabled"") ? fnc(e) : null;
 
-     });
 
- }
 
- var integerFields = document.getElementsByClassName(""prs-web_utils-digital_form-integer"");
 
- function integerValidator(e)
 
- {
 
-     e.target.value = Math.floor(e.target.value);
 
- }
 
- for(var field of integerFields){
 
-     field.addEventListener(""change"", integerValidator);
 
- }
 
- function changeSelect(e){
 
-     var parent = e.target.parentNode;
 
-     var label = parent.getElementsByTagName(""div"")[0];
 
-     label.textContent = e.target.value;
 
- }
 
- var optionCont = document.getElementsByClassName(""prs-web_utils-digital_form-option_cont"");
 
- for(var option of optionCont){
 
-     var select = option.getElementsByTagName(""select"")[0];
 
-     select.setAttribute(""style"", ""position: absolute;top:0;left:0;right:0;bottom:0;"");
 
-     select.addEventListener(""change"", changeSelect);
 
- }
 
- function saveDocument(filename, data, onResponse){
 
-     var xhr = new XMLHttpRequest();
 
-     xhr.open(""POST"", ""/save/Document"");
 
-     xhr.setRequestHeader(""Content-Type"", ""application/json"");
 
-     xhr.responseType = ""json"";
 
-     xhr.onreadystatechange = function(){
 
-         if(xhr.readyState === 4){
 
-             onResponse(xhr.response);
 
-         }
 
-     }
 
-     xhr.send(JSON.stringify({
 
-         ""FileName"": filename,
 
-         ""Data"": data
 
-     }));
 
- }
 
- function selectDocument(e){
 
-     var editor = e.target.parentNode;
 
-     var filename = editor.getElementsByClassName(""prs-web_utils-digital_form-filename"")[0];
 
-     var accept = filename.getAttribute(""accept"");
 
-     
 
-     var input = document.createElement(""input"");
 
-     input.type = ""file"";
 
-     input.setAttribute(""accept"", accept);
 
-     input.onchange = e => {
 
-         var file = e.target.files[0];
 
-         var reader = new FileReader();
 
-         reader.onloadend = (re) => {
 
-             saveDocument(file.name, reader.result.replace(/^data:(.*,)?/, ''), function(data){
 
-                 filename.value = data[""FileName""];
 
-                 editor.setAttribute(""data-id"", data[""ID""]);
 
-             });
 
-         };
 
-         reader.readAsDataURL(file);
 
-     };
 
-     input.click();
 
- }
 
- function clearDocument(e){
 
-     var editor = e.target.parentNode;
 
-     editor.removeAttribute(""data-id"");
 
-     var filenameEl = editor.getElementsByClassName(""prs-web_utils-digital_form-filename"")[0];
 
-     filenameEl.value = """";
 
- }
 
- function viewDocument(e){
 
-     var editor = e.target.parentNode;
 
-     if(editor.hasAttribute(""data-id"")){
 
-         var id = editor.getAttribute(""data-id"");
 
-         var filenameEl = editor.getElementsByClassName(""prs-web_utils-digital_form-filename"")[0];
 
-         console.log(filenameEl.value);
 
-         const viewDocEvent = new CustomEvent(""prs-web_utils-df_view_doc"", { detail: { id: id, filename: filenameEl.value } });
 
-         editor.dispatchEvent(viewDocEvent);
 
-     }
 
- }
 
- var docSelectBtns = document.getElementsByClassName(""prs-web_utils-digital_form-select_doc"");
 
- var docClearBtns = document.getElementsByClassName(""prs-web_utils-digital_form-clear_doc"");
 
- var docViewBtns = document.getElementsByClassName(""prs-web_utils-digital_form-view_doc"");
 
- for(var docSelect of docSelectBtns){
 
-     checkDisabled(docSelect, selectDocument);
 
- }
 
- for(var docClear of docClearBtns){
 
-     checkDisabled(docClear, clearDocument);
 
- }
 
- for(var docView of docViewBtns){
 
-     checkDisabled(docView, viewDocument);
 
- }
 
- function createPIN(e){
 
-     var editor = e.target.parentNode;
 
-    
 
-     var length = parseInt(editor.getAttribute(""data-length""), 10);
 
-     var pinEl = editor.getElementsByClassName(""prs-web_utils-digital_form-pin_text"")[0];
 
-     pinEl.value = """";
 
-     for(let i = 0; i < length; i++){
 
-         pinEl.value += Math.floor(Math.random() * 10).toString();
 
-     }
 
- }
 
- function clearPIN(e){
 
-     var editor = e.target.parentNode;
 
-     var pinEl = editor.getElementsByClassName(""prs-web_utils-digital_form-pin_text"")[0];
 
-     pinEl.value = """";
 
- }
 
- var pinCreateBtns = document.getElementsByClassName(""prs-web_utils-digital_form-create_pin"");
 
- var pinClearBtns = document.getElementsByClassName(""prs-web_utils-digital_form-clear_pin"");
 
- for(var pinCreate of pinCreateBtns){
 
-     checkDisabled(pinCreate, createPIN);
 
- }
 
- for(var pinClear of pinClearBtns){
 
-     checkDisabled(pinClear, clearPIN);
 
- }
 
- function toggleStamp(e){
 
-     var editor = e.target.parentNode;
 
-     var stampEl = editor.getElementsByClassName(""prs-web_utils-digital_form-timestamp_date"")[0];
 
-     if(/\S/.test(stampEl.value)){
 
-         stampEl.value = """";
 
-         e.target.textContent = ""Set"";
 
-     } else {
 
-         var dateString = (new Date()).toISOString();
 
-         stampEl.value = dateString.substring(0, dateString.length - 1);
 
-         e.target.textContent = ""Clear"";
 
-     }
 
- }
 
- var pinToggleBtns = document.getElementsByClassName(""prs-web_utils-digital_form-toggle_stamp"");
 
- for(var pinToggle of pinToggleBtns){
 
-     checkDisabled(pinToggle, toggleStamp);
 
- }
 
- var embedImageFields = document.getElementsByClassName(""prs-web_utils-digital_form-embedded_image"");
 
- var signatureFields = document.getElementsByClassName(""prs-web_utils-digital_form-signature"");
 
- function fileSelector(e)
 
- {
 
-     var selectedFile = e.target.files[0];
 
-     var target = e.target;
 
-     var parent = e.target.parentNode;
 
-     let clearButton = parent.nextSibling;
 
-     
 
-     var img = document.createElement(""img"");
 
-     img.classList.add(""prs-web_utils-digital_form-img"");
 
-     const reader = new FileReader();
 
-     reader.onload = (e) => {
 
-         img.src = e.target.result;
 
-         clearButton.removeAttribute(""disabled"");
 
-     }
 
-     reader.readAsDataURL(selectedFile);
 
-     
 
-     parent.replaceChildren(img, target);
 
- }
 
- function fileButton(field, fileElement){
 
-     field.addEventListener(""click"", (e) => {
 
-         fileElement.click();
 
-     });
 
- }
 
- function clearEmbeddedImage(e){
 
-     let imgField = e.target.parentNode;
 
-     let imgCont = imgField.children[0];
 
-     let span = document.createElement(""span"");
 
-     span.textContent = ""Choose a file"";
 
-     imgCont.replaceChildren(span, imgCont.children[1]);
 
-     e.target.setAttribute(""disabled"", """");
 
- }
 
- var clearButtons = [];
 
- for(var field of embedImageFields){
 
-     var imgElement = field.getElementsByClassName(""prs-web_utils-digital_form-img_cont"")[0];
 
-     var fileElement = field.getElementsByTagName(""input"")[0];
 
-     fileButton(imgElement, fileElement);
 
-     fileElement.addEventListener(""change"", fileSelector);
 
-     clearButtons.push(field.getElementsByClassName(""prs-web_utils-digital_form-clear_img"")[0]);
 
- }
 
- for(var field of signatureFields){
 
-     var imgElement = field.getElementsByClassName(""prs-web_utils-digital_form-img_cont"")[0];
 
-     var fileElement = field.getElementsByTagName(""input"")[0];
 
-     fileButton(imgElement, fileElement);
 
-     fileElement.addEventListener(""change"", fileSelector);
 
-     clearButtons.push(field.getElementsByClassName(""prs-web_utils-digital_form-clear_img"")[0]);
 
- }
 
- for(let clearButton of clearButtons){
 
-     checkDisabled(clearButton, clearEmbeddedImage);
 
- }
 
- function enableMultiImageRem(multiImage, enabled = true){
 
-     if(!multiImage.hasAttribute(""disabled"")){
 
-         var btn = multiImage.getElementsByClassName(""prs-web_utils-digital_form-multi_image_remove"")[0];
 
-         if(enabled){
 
-             btn.removeAttribute(""disabled"");
 
-         } else {
 
-             btn.setAttribute(""disabled"", """");
 
-         }
 
-     }
 
- }
 
- function multiImageClick(e){
 
-     var multiImage = e.target.parentNode.parentNode.parentNode;
 
-     for(let img of e.target.parentNode.children){
 
-         img.classList.remove(""selected"");
 
-     }
 
-     e.target.classList.add(""selected"");
 
-     enableMultiImageRem(multiImage);
 
- }
 
- var multiImages = document.getElementsByClassName(""prs-web_utils-digital_form-multi_image"");
 
- for(let multiImg of multiImages){
 
-     var imgs = multiImg.getElementsByClassName(""prs-web_utils-digital_form-multi_image_cont"")[0].getElementsByTagName(""img"");
 
-     for(let img of imgs){
 
-         img.addEventListener(""click"", multiImageClick);
 
-     }
 
- }
 
- function multiImageRemove(e){
 
-     let multiImage = e.target.parentNode.parentNode;
 
-     let imgList = multiImage.getElementsByClassName(""prs-web_utils-digital_form-multi_image_cont"")[0].children[0];
 
-     for(let img of imgList.children){
 
-         if(img.classList.contains(""selected"")){
 
-             imgList.removeChild(img);
 
-         }
 
-     }
 
-     enableMultiImageRem(multiImage, false);
 
- }
 
- function multiImageAdd(e){
 
-     let multiImage = e.target.parentNode.parentNode;
 
-     let imgCont = multiImage.getElementsByClassName(""prs-web_utils-digital_form-multi_image_cont"")[0];
 
-     let imgContDiv = imgCont.children[0];
 
-     var input = document.createElement(""input"");
 
-     input.type = ""file"";
 
-     input.setAttribute(""accept"", ""image/*"");
 
-     input.onchange = e => {
 
-         var file = e.target.files[0];
 
-         var reader = new FileReader();
 
-         reader.onloadend = (re) => {
 
-             var img = document.createElement(""img"");
 
-             img.src = reader.result;
 
-             img.addEventListener(""click"", multiImageClick);
 
-             imgContDiv.appendChild(img);
 
-         };
 
-         reader.readAsDataURL(file);
 
-     };
 
-     input.click();
 
- }
 
- var multiImageAddBtns = document.getElementsByClassName(""prs-web_utils-digital_form-multi_image_add"");
 
- var multiImageRemBtns = document.getElementsByClassName(""prs-web_utils-digital_form-multi_image_remove"");
 
- for(var multiImageBtn of multiImageAddBtns){
 
-     checkDisabled(multiImageBtn, multiImageAdd);
 
- }
 
- for(var multiImageBtn of multiImageRemBtns){
 
-     checkDisabled(multiImageBtn, multiImageRemove);
 
- }
 
- function addNote(e){
 
-     let notesCont = e.target.parentNode.getElementsByClassName(""prs-web_utils-digital_form-note_cont"")[0];
 
-     let noteChildren = notesCont.getElementsByClassName(""prs-web_utils-digital_form-note"");
 
-     let lastChild = noteChildren[noteChildren.length - 1];
 
-     let error = notesCont.getElementsByClassName(""prs-web_utils-digital_form-note_error"")[0];
 
-     if(/\S/.test(lastChild.value)){
 
-         lastChild.classList.remove(""active"");
 
-         lastChild.setAttribute(""disabled"", """");
 
-             
 
-         let newNotes = document.createElement(""textarea"");
 
-         newNotes.classList.add(""prs-web_utils-digital_form-note"");
 
-         newNotes.classList.add(""active"");
 
-         notesCont.insertBefore(newNotes, error);
 
-             
 
-         error.textContent = """";
 
-     } else {
 
-         error.textContent = ""Please enter a note!"";
 
-     }
 
- }
 
- var notesAddButtons = document.getElementsByClassName(""prs-web_utils-digital_form-note_add"");
 
- for(let noteAdd of notesAddButtons){
 
-     checkDisabled(noteAdd, addNote);
 
- }
 
- function addTask(e){
 
-     let field = e.target.parentNode;
 
-     let kanbanType = field.getAttribute(""data-tasktype"");
 
-     let kanban = " + WebUtils.CreateJSONEntity<Kanban>() + ";" + 
 
-     WebUtils.JSONSetProperty<Kanban>("kanban", x => x.Type.ID, "kanbanType") + @";
 
-     let eventName = ""prs-web_utils-df_add_task"";
 
-     const addTaskEvent = new CustomEvent(eventName, { detail: {
 
-         kanban: kanban,
 
-         setTaskNumber: function(number){" +
 
-             WebUtils.JSONSetProperty<Kanban>("kanban", x => x.Number, "number") + @";
 
-             var input = field.getElementsByTagName(""input"")[0];
 
-             input.value = number.toString();
 
-             var btn = field.getElementsByClassName(""prs-button"")[0];
 
-             btn.setAttribute(""disabled"", """");
 
-             submitForm(field.closest("".prs-web_utils-digital_form""), true, false);
 
-         }
 
-     }});
 
-     form.dispatchEvent(addTaskEvent);
 
- }
 
- var addTaskFields = document.getElementsByClassName(""prs-web_utils-digital_form-add_task"");
 
- for(let addTaskField of addTaskFields){
 
-     let addTaskBtn = addTaskField.getElementsByClassName(""prs-button"")[0];
 
-     checkDisabled(addTaskBtn, addTask);
 
- }
 
- " + _submitScript + "</script>";
 
-         }
 
-         public static string DigitalFormViewerScript => GetDigitalFormViewerScript();
 
-         private static string BuildLookupField(
 
-             IEnumerable<Tuple<string, string?>> options,
 
-             string? selectedString,
 
-             string style,
 
-             string className,
 
-             bool readOnly,
 
-             bool required,
 
-             string name
 
-         )
 
-         {
 
-             var element = new StringBuilder(
 
-                 string.Format(@"<div class=""prs-web_utils-digital_form-option_cont"" style=""{0}position: relative;font-size: 1.2em;"">", style));
 
-             var childOptions = new StringBuilder();
 
-             var display = selectedString ?? "";
 
-             foreach (var option in options)
 
-             {
 
-                 var idString = option.Item2 != null ? $" data-id=\"{option.Item2}\"" : "";
 
-                 childOptions.Append($"<option{(option.Item1 == selectedString ? " selected" : "")} value=\"{option.Item1}\"{idString}>{option.Item1}</option>");
 
-             }
 
-             element.AppendFormat(
 
-                 @"<div class=""prs-web_utils-digital_form-option_fake"">{0}</div><select name=""{1}"" class=""{2}""{3}{4}><option></option>",
 
-                 display,
 
-                 name,
 
-                 className,
 
-                 readOnly ? " disabled" : "",
 
-                 required ? " required" : ""
 
-             ).Append(childOptions).Append("</select></div>");
 
-             return element.ToString();
 
-         }
 
-         private static string BuildRadioButtons(
 
-             IEnumerable<string> options,
 
-             string? selectedString,
 
-             string style,
 
-             string className,
 
-             bool readOnly,
 
-             bool required,
 
-             string name
 
-         )
 
-         {
 
-             var element = new StringBuilder(
 
-                 string.Format(@"<div name=""{0}"" class=""prs-web_utils-digital_form-radio_cont {1}"" style=""{2}position: relative;font-size: 1.2em;"">", name, className, style));
 
-             var childOptions = new StringBuilder();
 
-             var radioOptions = string.Format(
 
-                 @"name=""{0}"" type=""radio""{1}{2}",
 
-                 name,
 
-                 required ? " required" : "",
 
-                 readOnly ? " disabled" : ""
 
-             );
 
-             foreach (var option in options)
 
-             {
 
-                 var selected = option == selectedString;
 
-                 var valueStr = $" value=\"{option}\"";
 
-                 var optionID = Guid.NewGuid();
 
-                 childOptions.Append($"<input id=\"{optionID}\"{valueStr} {radioOptions} {(selected ? " checked" : "")}/>"
 
-                     + $"<label for=\"{optionID}\">{option}</label>");
 
-             }
 
-             element.Append(childOptions).Append("</div>");
 
-             return element.ToString();
 
-         }
 
-         public static string BuildDigitalFormViewer(
 
-             DFLayout layout,
 
-             Dictionary<string, object>? data,
 
-             bool readOnly,
 
-             string? id = null, string[]? classList = null,
 
-             string submitLabel = "Submit Form", string? saveLabel = "Save Form"
 
-         )
 
-         {
 
-             StringBuilder stringBuilder = new();
 
-             stringBuilder.Append("<form ");
 
-             if (id != null) stringBuilder.Append("id=\"" + id + "\" ");
 
-             stringBuilder.Append(@"class=""prs-web_utils-digital_form");
 
-             if (classList != null)
 
-                 foreach (var className in classList)
 
-                     stringBuilder.Append(" " + className);
 
-             stringBuilder.Append(@""" style=""grid-template-columns:");
 
-             foreach (var width in layout.ColumnWidths)
 
-                 if (width == "Auto")
 
-                 {
 
-                     stringBuilder.Append("auto ");
 
-                 }
 
-                 else if (width == "*")
 
-                 {
 
-                     stringBuilder.Append("1fr ");
 
-                 }
 
-                 else
 
-                 {
 
-                     stringBuilder.Append(width);
 
-                     stringBuilder.Append("px ");
 
-                 }
 
-             stringBuilder.Append(@";grid-template-rows:");
 
-             foreach (var height in layout.RowHeights)
 
-                 if (height == "Auto")
 
-                 {
 
-                     stringBuilder.Append("auto ");
 
-                 }
 
-                 else if (height == "*")
 
-                 {
 
-                     stringBuilder.Append("1fr ");
 
-                 }
 
-                 else
 
-                 {
 
-                     stringBuilder.Append(height);
 
-                     stringBuilder.Append("px ");
 
-                 }
 
-             stringBuilder.Append(@"min-content;"">");
 
-             var lastRowNum = layout.RowHeights.Count + 1;
 
-             foreach (var element in layout.Elements)
 
-             {
 
-                 var style = string.Format("grid-column: {0} / span {1};grid-row: {2} / span {3};", element.Column, element.ColumnSpan, element.Row,
 
-                     element.RowSpan);
 
-                 if (element is DFLayoutField fieldElement)
 
-                 {
 
-                     var required = fieldElement.GetPropertyValue<bool>("Required");
 
-                     var fieldReadOnly = readOnly || fieldElement.GetPropertyValue<bool>("Secure");
 
-                     object? fieldData = null;
 
-                     data?.TryGetValue(fieldElement.Name, out fieldData);
 
-                     var tagType = "input";
 
-                     var classes = "";
 
-                     string? value = null;
 
-                     var properties = "";
 
-                     string? children = null;
 
-                     // TODO: DocumentField, MultiSignaturePad
 
-                     if(element is DFLayoutAddTaskField addTaskField)
 
-                     {
 
-                         var kanbanType = addTaskField.Properties.TaskType;
 
-                         var kanbanNumber = (int?)fieldData;
 
-                         tagType = "div";
 
-                         classes = "prs-web_utils-digital_form-add_task";
 
-                         properties = $"data-tasktype=\"{kanbanType.ID}\"";
 
-                         children = $"<input type=\"number\" step=\"1\" disabled{(kanbanNumber != null ? $" value=\"{kanbanNumber}\"" : "")}/>" +
 
-                             $"<div class=\"prs-button\"{(fieldReadOnly || kanbanNumber != null ? " disabled" : "")}>Create Task</div>";
 
-                     }
 
-                     else if (element is DFLayoutBooleanField)
 
-                     {
 
-                         var fieldType = fieldElement.GetPropertyValue<DesignBooleanFieldType>("Type");
 
-                         var trueValue = fieldElement.GetPropertyValue<string>("TrueValue");
 
-                         var falseValue = fieldElement.GetPropertyValue<string>("FalseValue");
 
-                         bool checkTrue = (bool?)fieldData == true;
 
-                         bool checkFalse = (bool?)fieldData == false;
 
-                         if (fieldType == DesignBooleanFieldType.Checkbox)
 
-                         {
 
-                             tagType = "div";
 
-                             classes = "prs-web_utils-digital_form-boolean";
 
-                             var options = string.Format(
 
-                                 @"name=""{0}"" type=""radio""{1}{2}",
 
-                                 fieldElement.Name,
 
-                                 required ? " required" : "",
 
-                                 fieldReadOnly ? " disabled" : ""
 
-                             );
 
-                             var disabled = fieldReadOnly ? " disabled" : "";
 
-                             var idTrue = Guid.NewGuid();
 
-                             var idFalse = Guid.NewGuid();
 
-                             children =
 
-                                 $"<input value=\"{trueValue}\" id=\"{idTrue}\" {(checkTrue ? " checked" : "")} data-true {options}/>" +
 
-                                     $"<label for=\"{idTrue}\">{trueValue}</label>" +
 
-                                 $"<input value=\"{falseValue}\" id=\"{idFalse}\" \"{(checkFalse ? " checked" : "")} data-false {options}/>" +
 
-                                     $"<label for=\"{idFalse}\">{falseValue}</label>";
 
-                         }
 
-                         else if (fieldType == DesignBooleanFieldType.ComboBox)
 
-                         {
 
-                             classes = "prs-web_utils-digital_form-boolean";
 
-                             tagType = "select";
 
-                             children = string.Format(
 
-                                 @"<option{0} data-true>{1}</option><option{2} data-false>{3}</option>",
 
-                                 checkTrue ? " selected" : "",
 
-                                 trueValue,
 
-                                 checkFalse ? " selected" : "",
 
-                                 falseValue
 
-                             );
 
-                         }
 
-                         else if (fieldType == DesignBooleanFieldType.Buttons)
 
-                         {
 
-                             classes = "prs-web_utils-digital_form-boolean";
 
-                             tagType = "div";
 
-                             var options = string.Format(
 
-                                 @"name=""{0}"" type=""radio""{1}{2}",
 
-                                 fieldElement.Name,
 
-                                 required ? " required" : "",
 
-                                 fieldReadOnly ? " disabled" : ""
 
-                             );
 
-                             children = string.Format(
 
-                                 @"<div class=""prs-web_utils-digital_form-button""><input {0} value=""{1}""{3} data-true></input><span>{1}</span></div>" +
 
-                                 @"<div class=""prs-web_utils-digital_form-button""><input {0} value=""{2}""{4} data-false></input><span>{2}</span></div>",
 
-                                 options,
 
-                                 trueValue,
 
-                                 falseValue,
 
-                                 checkTrue ? " checked" : "",
 
-                                 checkFalse ? " checked" : ""
 
-                             );
 
-                         }
 
-                     }
 
-                     else if (element is DFLayoutCodeField)
 
-                     {
 
-                         properties = @"type=""text"" pattern=""[^a-z]+""";
 
-                         classes = "prs-web_utils-digital_form-code";
 
-                         value = fieldData?.ToString() ?? "";
 
-                     }
 
-                     else if (element is DFLayoutColorField)
 
-                     {
 
-                         properties = @"type=""color""";
 
-                         classes = "prs-web_utils-digital_form-color";
 
-                         if (fieldData != null)
 
-                             try
 
-                             {
 
-                                 var color =
 
-                                     (Color)ColorConverter.ConvertFromString((string)fieldData);
 
-                                 value = string.Format("#{0:x}{1:x}{2:x}", color.R, color.G, color.B);
 
-                             }
 
-                             catch (FormatException)
 
-                             {
 
-                             }
 
-                     }
 
-                     else if (element is DFLayoutDateField)
 
-                     {
 
-                         properties = @"type=""date""";
 
-                         classes = "prs-web_utils-digital_form-date";
 
-                         value = ((DateTime?)fieldData)?.ToString("yyyy-MM-dd") ?? "";
 
-                     }
 
-                     else if (element is DFLayoutDateTimeField)
 
-                     {
 
-                         properties = @"type=""datetime-local""";
 
-                         classes = "prs-web_utils-digital_form-datetime";
 
-                         value = fieldData != null ? string.Format("{0:yyyy-MM-dd}T{0:HH:mm}", fieldData) : "";
 
-                     }
 
-                     else if (element is DFLayoutDoubleField)
 
-                     {
 
-                         properties = @"type=""number""";
 
-                         classes = "prs-web_utils-digital_form-double";
 
-                         value = fieldData?.ToString() ?? "";
 
-                     }
 
-                     else if (element is DFLayoutIntegerField)
 
-                     {
 
-                         properties = @"type=""number"" step=""1""";
 
-                         classes = "prs-web_utils-digital_form-integer";
 
-                         value = fieldData?.ToString() ?? "";
 
-                     }
 
-                     else if (element is DFLayoutLookupField)
 
-                     {
 
-                         var type = CoreUtils.GetEntityOrNull((element as DFLayoutLookupField).Properties.LookupType);
 
-                         if(type is not null)
 
-                         {
 
-                             var table = ClientFactory.CreateClient(type).Query(
 
-                                 LookupFactory.DefineFilter(type),
 
-                                 LookupFactory.DefineColumns(type),
 
-                                 LookupFactory.DefineSort(type)
 
-                             );
 
-                             data.TryGetValue(fieldElement.Name + "$ID", out var fieldID);
 
-                             data.TryGetValue(fieldElement.Name, out var fieldFormat);
 
-                             var options = table.Rows.Select(x => new Tuple<string, string?>(
 
-                                 LookupFactory.FormatLookup(type, x.ToDictionary(new[] { "ID" }), Array.Empty<string>()),
 
-                                 x["ID"].ToString())).ToList();
 
-                             fieldFormat ??= options.Where(x => x.Item2 == fieldID?.ToString()).FirstOrDefault()?.Item1;
 
-                             tagType = null;
 
-                             stringBuilder.Append(BuildLookupField(
 
-                                 options,
 
-                                 fieldFormat?.ToString(),
 
-                                 style,
 
-                                 "prs-web_utils-digital_form-lookup prs-web_utils-digital_form-field",
 
-                                 fieldReadOnly,
 
-                                 required,
 
-                                 fieldElement.Name
 
-                             ));
 
-                         }
 
-                     }
 
-                     else if (element is DFLayoutMultiImage)
 
-                     {
 
-                         tagType = "div";
 
-                         classes = "prs-web_utils-digital_form-multi_image";
 
-                         var childBuild = new StringBuilder();
 
-                         childBuild.Append(@"<div class=""prs-web_utils-digital_form-multi_image_cont""><div>");
 
-                         if (fieldData is List<byte[]> images)
 
-                             foreach (var image in images)
 
-                             {
 
-                                 var imgStr = Convert.ToBase64String(image);
 
-                                 childBuild.AppendFormat(@"<img src=""data:;base64,{0}""/>", imgStr);
 
-                             }
 
-                         childBuild.AppendFormat(@"</div></div><div class=""prs-web_utils-digital_form-multi_image_btns"">" +
 
-                                                 @"<div class=""prs-button prs-web_utils-digital_form-multi_image_add""{0}>Add Image</div>" +
 
-                                                 @"<div class=""prs-button prs-web_utils-digital_form-multi_image_remove"" disabled>Remove Image</div>" +
 
-                                                 @"</div>",
 
-                             fieldReadOnly ? " disabled" : ""
 
-                         );
 
-                         children = childBuild.ToString();
 
-                     }
 
-                     else if (element is DFLayoutNotesField)
 
-                     {
 
-                         tagType = "div";
 
-                         classes = "prs-web_utils-digital_form-notes";
 
-                         var notes = (string[]?)fieldData ?? Array.Empty<string>();
 
-                         var childrenBuilder = new StringBuilder(@"<div class=""prs-web_utils-digital_form-note_cont"">");
 
-                         foreach (var note in notes)
 
-                             childrenBuilder.AppendFormat(@"<textarea class=""prs-web_utils-digital_form-note"" disabled>{0}</textarea>", note);
 
-                         var options = fieldReadOnly ? "disabled" : "";
 
-                         childrenBuilder.Append(
 
-                             @"<textarea class=""prs-web_utils-digital_form-note active""></textarea><div class=""prs-web_utils-digital_form-note_error""></div></div>"
 
-                             + $"<div class=\"prs-web_utils-digital_form-note_add prs-button\" {options}>+</div>");
 
-                         children = childrenBuilder.ToString();
 
-                     }
 
-                     else if (element is DFLayoutOptionField optionField)
 
-                     {
 
-                         var optionType = optionField.Properties.OptionType;
 
-                         tagType = null;
 
-                         if (optionType == DFLayoutOptionType.Radio)
 
-                         {
 
-                             stringBuilder.Append(BuildRadioButtons(
 
-                                 optionField.Properties.Options.Split(','),
 
-                                 fieldData?.ToString(),
 
-                                 style,
 
-                                 "prs-web_utils-digital_form-field",
 
-                                 fieldReadOnly,
 
-                                 required,
 
-                                 fieldElement.Name
 
-                             ));
 
-                         }
 
-                         else
 
-                         {
 
-                             stringBuilder.Append(BuildLookupField(
 
-                                 optionField.Properties.Options.Split(',').Select(x => new Tuple<string, string?>(x, null)),
 
-                                 fieldData?.ToString(),
 
-                                 style,
 
-                                 "prs-web_utils-digital_form-option prs-web_utils-digital_form-field",
 
-                                 fieldReadOnly,
 
-                                 required,
 
-                                 fieldElement.Name
 
-                             ));
 
-                         }
 
-                     }
 
-                     else if (element is DFLayoutPasswordField)
 
-                     {
 
-                         properties = @"type=""password""";
 
-                         classes = "prs-web_utils-digital_form-password";
 
-                         value = fieldData?.ToString() ?? "";
 
-                     }
 
-                     else if (element is DFLayoutPINField)
 
-                     {
 
-                         tagType = "div";
 
-                         classes = "prs-web_utils-digital_form-pin";
 
-                         var pinLength = fieldElement.GetPropertyValue<int>("Length");
 
-                         properties += $" data-length=\"{pinLength}\"";
 
-                         var options = fieldReadOnly ? "disabled" : "";
 
-                         children +=
 
-                             $"<input type=\"text\" disabled value=\"{fieldData?.ToString() ?? ""}\" class=\"prs-web_utils-digital_form-pin_text\"{(required ? " required" : "")}/>"
 
-                             + $"<div class=\"prs-button prs-web_utils-digital_form-create_pin\" {options}>Create</div>"
 
-                             + $"<div class=\"prs-button prs-web_utils-digital_form-clear_pin\" {options}>Clear</div>";
 
-                     }
 
-                     else if (element is DFLayoutStringField)
 
-                     {
 
-                         properties = @"type=""text""";
 
-                         classes = "prs-web_utils-digital_form-string";
 
-                         value = fieldData?.ToString() ?? "";
 
-                     }
 
-                     else if (element is DFLayoutTextField)
 
-                     {
 
-                         tagType = "textarea";
 
-                         classes = "prs-web_utils-digital_form-text";
 
-                         style += "resize:none;";
 
-                         children += fieldData?.ToString();
 
-                     }
 
-                     else if (element is DFLayoutTimeField)
 
-                     {
 
-                         tagType = "div";
 
-                         classes = "prs-web_utils-digital_form-time";
 
-                         var timeSpan = (TimeSpan?)fieldData;
 
-                         var hours = Math.Truncate(timeSpan?.TotalHours ?? 0.0);
 
-                         var mins = timeSpan?.Minutes ?? 0.0;
 
-                         var requiredStr = required ? " required" : "";
 
-                         children = $"<input class=\"prs-web_utils-digital_form-time_hours\" type=\"number\" min=\"0\" step=\"1\" value=\"{hours}\"{requiredStr}/>" +
 
-                             @"<span>:</span>"
 
-                             + $"<input class=\"prs-web_utils-digital_form-time_mins\" type=\"number\" min=\"0\" step=\"1\" max=\"60\" value=\"{mins}\"{requiredStr}/>";
 
-                     }
 
-                     else if (element is DFLayoutTimeStampField)
 
-                     {
 
-                         tagType = "div";
 
-                         classes = "prs-web_utils-digital_form-timestamp";
 
-                         var exists = fieldData != null && (DateTime)fieldData != DateTime.MinValue;
 
-                         var valueStr = exists ? string.Format("{0:yyyy-MM-dd}T{0:HH:mm}", fieldData) : "";
 
-                         var btnStr = exists ? "Clear" : "Set";
 
-                         var disabled = fieldReadOnly ? " disabled" : "";
 
-                         children +=
 
-                             $"<input type=\"datetime-local\" disabled value=\"{valueStr}\" class=\"prs-web_utils-digital_form-timestamp_date\"{(required ? " required" : "")}/>"
 
-                             + $"<div class=\"prs-button prs-web_utils-digital_form-toggle_stamp\"{disabled}>{btnStr}</div>";
 
-                     }
 
-                     else if (element is DFLayoutURLField)
 
-                     {
 
-                         properties = @"type=""url""";
 
-                         classes = "prs-web_utils-digital_form-url";
 
-                         value = fieldData?.ToString();
 
-                     }
 
-                     else if (element is DFLayoutEmbeddedImage || element is DFLayoutSignaturePad)
 
-                     {
 
-                         // TODO: Perform validation for images
 
-                         tagType = "div";
 
-                         classes = element is DFLayoutEmbeddedImage
 
-                             ? "prs-web_utils-digital_form-embedded_image"
 
-                             : "prs-web_utils-digital_form-signature";
 
-                         var content = "";
 
-                         var hasContent = fieldData != null && ((byte[])fieldData).Length > 0;
 
-                         if (hasContent)
 
-                         {
 
-                             content = string.Format(
 
-                                 @"<img class=""prs-web_utils-digital_form-img"" src=""data:;base64,{0}"">",
 
-                                 Convert.ToBase64String((byte[])fieldData)
 
-                             );
 
-                         }
 
-                         else
 
-                         {
 
-                             if (fieldReadOnly)
 
-                                 content = "<span>No image</span>";
 
-                             else
 
-                                 content = "<span>Choose a file</span>";
 
-                         }
 
-                         children = String.Format(@"<div class=""prs-button prs-web_utils-digital_form-img_cont""{1}>{0}<input type=""file"" accept=""image/*"" style=""display: none"" {1}></div><div class=""prs-button prs-web_utils-digital_form-clear_img""{2}>Clear Image</div>",
 
-                             content,
 
-                             fieldReadOnly ? " disabled" : "",
 
-                             (hasContent && !fieldReadOnly) ? "" : " disabled"
 
-                         );
 
-                     }
 
-                     
 
-                     if (tagType != null)
 
-                         stringBuilder.Append(string.Format(
 
-                             @"<{0} style=""{1}"" class=""{2} prs-web_utils-digital_form-field""{3}{4}{5} name=""{6}"" {7}>{8}</{0}>",
 
-                             tagType,
 
-                             style,
 
-                             classes,
 
-                             required ? " required" : "",
 
-                             fieldReadOnly ? " disabled" : "",
 
-                             value != null ? " value=\"" + value + "\"" : "",
 
-                             fieldElement.Name,
 
-                             properties,
 
-                             children ?? ""
 
-                         ));
 
-                 }
 
-                 else if (element is DFLayoutLabel)
 
-                 {
 
-                     stringBuilder.Append(string.Format(@"<label class=""prs-web_utils-digital_form-label"" style=""{0}"">{1}</label>", style,
 
-                         (element as DFLayoutLabel).Caption));
 
-                 }
 
-                 else if (element is DFLayoutImage)
 
-                 {
 
-                     stringBuilder.Append(string.Format(@"<img src=""/document?id={0}"" class=""prs-web_utils-digital_form-image"" style=""{1}"">",
 
-                         (element as DFLayoutImage).Image.ID, style));
 
-                 }
 
-             }
 
-             if (!readOnly)
 
-             {
 
-                 stringBuilder.Append($"<div class=\"prs-web_utils-digital_form-save_cont\" style=\"grid-column: 1 / -1; grid-row: {lastRowNum}\">");
 
-                 if (saveLabel != null) stringBuilder.Append($"<div class=\"prs-button prs-web_utils-digital_form-save\">{saveLabel}</div>");
 
-                 stringBuilder.Append($"<div class=\"prs-button prs-web_utils-digital_form-submit\">{submitLabel}</div>");
 
-                 stringBuilder.Append(@"</div>");
 
-             }
 
-             stringBuilder.Append("</form>");
 
-             stringBuilder.Append(DigitalFormViewerScript);
 
-             return stringBuilder.ToString();
 
-         }
 
-         /// <summary>
 
-         ///     Builds a layout for viewing and optionally editing digital forms.
 
-         /// </summary>
 
-         /// <note>
 
-         ///     <para>
 
-         ///         In addition to outputting an HTML representation of the form, a script tag is outputted, which provides form
 
-         ///         submission functionality and some extra layouting functionality.
 
-         ///         The submit form button has the class of "prs-web_utils-digital_form-submit", and when clicked calls an event
 
-         ///         named "prs-web_utils-df_submit" on the root form tag itself.
 
-         ///         This event is a JavaScript CustomEvent object with detail.formData set to a JSON object containing data which
 
-         ///         can be sent directly to www.domain.com/form_submission/XXXXForm?id=ID, allowing for the form to be submitted.
 
-         ///     </para>
 
-         /// </note>
 
-         /// <param name="form">The instance of the form</param>
 
-         /// <param name="entity">The entity associated with the form</param>
 
-         /// <param name="readOnly">If set to true, causes all elements to be disabled and the Submit Form button to be omitted</param>
 
-         /// <param name="id">An optional HTML id to add to root <div> of the generated item.</param>
 
-         /// <param name="classList">An optional list of HTML classes to add to root <div> of the generated item.</param>
 
-         /// <returns>A string containing raw HTML markup</returns>
 
-         public static string BuildDigitalFormViewer(IBaseDigitalFormInstance form, Entity entity, bool readOnly, string? id = null,
 
-             string[]? classList = null)
 
-         {
 
-             var formLayoutTable = new Client<DigitalFormLayout>().Query(
 
-                 new Filter<DigitalFormLayout>(x => x.Form.ID).IsEqualTo(form.Form.ID),
 
-                 new Columns<DigitalFormLayout>(x => x.Type).Add(x => x.Layout)
 
-             );
 
-             var variables = new Client<DigitalFormVariable>().Load(new Filter<DigitalFormVariable>(x => x.Form.ID).IsEqualTo(form.Form.ID));
 
-             var digitalFormLayout =
 
-                 (formLayoutTable.Rows.FirstOrDefault(x => (DFLayoutType)x["Type"] == DFLayoutType.Mobile)
 
-                  ?? formLayoutTable.Rows.FirstOrDefault(x => (DFLayoutType)x["Type"] == DFLayoutType.Desktop))?.ToObject<DigitalFormLayout>();
 
-             var layout = digitalFormLayout != null
 
-                 ? DFLayout.FromLayoutString(digitalFormLayout.Layout)
 
-                 : DFLayout.GenerateAutoMobileLayout(variables);
 
-             layout.LoadVariables(variables);
 
-             var data = DigitalForm.ParseFormData(form.FormData, variables, entity);
 
-             return BuildDigitalFormViewer(layout, data, readOnly, id, classList);
 
-         }
 
-         #endregion
 
-     }
 
-     public abstract class WebTemplateBase<T> : TemplateBase<T>
 
-     {
 
-         public IEncodedString JSON<U>() where U : Entity, new()
 
-         {
 
-             return new RawString(WebUtils.CreateJSONEntity<U>());
 
-         }
 
-         public IEncodedString JSON<U>(U entity) where U : Entity
 
-         {
 
-             return new RawString(WebUtils.EntityToJSON(entity));
 
-         }
 
-         public IEncodedString Get<U>(string entity, Expression<Func<U, object>> expression) where U : Entity
 
-         {
 
-             return new RawString(WebUtils.JSONGetProperty(entity, expression));
 
-         }
 
-         public IEncodedString Set<U>(string entity, Expression<Func<U, object>> expression, string value) where U : Entity
 
-         {
 
-             return new RawString(WebUtils.JSONSetProperty(entity, expression, value));
 
-         }
 
-     }
 
- }
 
 
  |