Komponent editor til: Cenger - Productdetail info

Error executing template "/Designs/Swift/Paragraph/Swift_ProductDetailsMediaTable_Custom.cshtml"
System.ArgumentNullException: Value cannot be null.
Parameter name: source
   at System.Linq.Enumerable.Where[TSource](IEnumerable`1 source, Func`2 predicate)
   at CompiledRazorTemplates.Dynamic.RazorEngine_c68dff46498642219b7ecec5dd8576cd.Execute() in E:\Solutions\Cenger\Files\Templates\Designs\Swift\Paragraph\Swift_ProductDetailsMediaTable_Custom.cshtml:line 58
   at RazorEngine.Templating.TemplateBase.RazorEngine.Templating.ITemplate.Run(ExecuteContext context, TextWriter reader)
   at RazorEngine.Templating.RazorEngineService.RunCompile(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass16_0.<RunCompile>b__0(TextWriter writer)
   at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter)
   at Dynamicweb.Rendering.RazorTemplateRenderingProvider.Render(Template template)
   at Dynamicweb.Rendering.TemplateRenderingService.Render(Template template)
   at Dynamicweb.Rendering.Template.RenderRazorTemplate()

1 @inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.ParagraphViewModel> 2 @using Dynamicweb.Ecommerce.ProductCatalog 3 @using Dynamicweb.Frontend 4 @using System.IO 5 6 @functions { 7 public ProductViewModel product { get; set; } = new ProductViewModel(); 8 public string[] supportedImageFormats { get; set; } 9 public string[] supportedVideoFormats { get; set; } 10 public string[] supportedDocumentFormats { get; set; } 11 public string[] allSupportedFormats { get; set; } 12 13 public class RatioSettings 14 { 15 public string Ratio { get; set; } 16 public string CssClass { get; set; } 17 public string CssVariable { get; set; } 18 public string Fill { get; set; } 19 } 20 21 public RatioSettings GetRatioSettings() 22 { 23 var ratioSettings = new RatioSettings(); 24 25 string ratio = Model.Item.GetRawValueString("ImageAspectRatio", ""); 26 ratio = ratio != "0" ? ratio : ""; 27 string cssClass = ratio != "" && ratio != "fill" ? " ratio" : ""; 28 string cssVariable = ratio != "" && ratio != "fill" ? "--bs-aspect-ratio: " + ratio : ""; 29 30 ratioSettings.Ratio = ratio; 31 ratioSettings.CssClass = cssClass; 32 ratioSettings.CssVariable = cssVariable; 33 ratioSettings.Fill = ratio == "fill" ? " h-100" : ""; 34 35 return ratioSettings; 36 } 37 } 38 39 @{ 40 @* Get the product data *@ 41 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 42 { 43 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 44 } 45 46 @* Supported formats *@ 47 supportedImageFormats = new string[] { ".jpg", ".jpeg", ".webp", ".png", ".gif", ".bmp", ".tiff" }; 48 supportedVideoFormats = new string[] { "youtu.be", "youtube", "vimeo", ".mp4", ".webm" }; 49 supportedDocumentFormats = new string[] { ".pdf", ".docx", ".xlsx", ".ppt", "pptx" }; 50 allSupportedFormats = supportedImageFormats.Concat(supportedVideoFormats).Concat(supportedDocumentFormats).ToArray(); 51 52 @* Collect the assets *@ 53 var selectedAssetCategories = Model.Item.GetRawValueString("ImageAssets").Split(',').ToList(); 54 bool includeImagePatternImages = Model.Item.GetBoolean("ImagePatternImages"); 55 56 @* Needed image data collection to support both DefaultImage, ImagePatterns and Image Assets *@ 57 string defaultImage = product.DefaultImage != null ? product.DefaultImage.Value : ""; 58 IEnumerable<MediaViewModel> assetsImages = product.AssetCategories.Where(x => selectedAssetCategories.Contains(x.SystemName)).SelectMany(x => x.Assets); 59 assetsImages = assetsImages.OrderByDescending(x => x.Value.Equals(defaultImage)); 60 IEnumerable<MediaViewModel> assetsList = new MediaViewModel[] { }; 61 62 assetsList = assetsList.Union(assetsImages); 63 assetsList = includeImagePatternImages ? assetsList.Union(product.ImagePatternImages) : assetsList; 64 assetsList = includeImagePatternImages && assetsList.Count() == 0 ? assetsList.Append(product.DefaultImage) : assetsList; 65 66 bool defaultImageFallback = Model.Item.GetBoolean("DefaultImageFallback"); 67 bool showOnlyPrimaryImage = Model.Item.GetBoolean("ShowOnlyPrimaryImage"); 68 69 bool hasVismaDocumentsAssets = selectedAssetCategories.Contains("VismaDocuments"); 70 71 int totalAssets = 0; 72 if (showOnlyPrimaryImage == false) 73 { 74 foreach (MediaViewModel asset in assetsList) 75 { 76 var assetValue = asset.Value.ToLower(); 77 foreach (string format in allSupportedFormats) 78 { 79 if (assetValue.Contains(format)) 80 { 81 totalAssets++; 82 } 83 } 84 } 85 } 86 87 if ((totalAssets == 0 && product.DefaultImage != null && selectedAssetCategories.Count() == 0) || (showOnlyPrimaryImage == true && product.DefaultImage != null)) 88 { 89 assetsList = new List<MediaViewModel>() { product.DefaultImage }; 90 totalAssets = 1; 91 } 92 93 int videoNumber = 0; 94 95 @* Layout settings *@ 96 string spacing = Model.Item.GetRawValueString("Spacing", "p-0"); 97 spacing = spacing == "none" ? "p-0" : spacing; 98 spacing = spacing == "small" ? "p-3" : spacing; 99 spacing = spacing == "large" ? "p-5" : spacing; 100 101 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 102 103 bool hideThumbnails = Model.Item.GetBoolean("HideThumbnails"); 104 105 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 106 } 107 108 @* Get assets from selected categories or get all assets *@ 109 110 111 <div class="@spacing@(theme) item_@Model.Item.SystemName.ToLower()"> 112 @if (totalAssets != 0 && assetsList.Count() != 0) 113 { 114 if (!string.IsNullOrEmpty(Model.Item.GetString("Title")) && !Model.Item.GetBoolean("HideTitle")) 115 { 116 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "h3"); 117 118 <h3 class="@titleFontSize mb-3"> 119 @Model.Item.GetString("Title") 120 </h3> 121 } 122 123 <div class="table-responsive"> 124 <table class="table table-hover align-middle mb-0" style="table-layout: fixed;"> 125 @if (!hasVismaDocumentsAssets) 126 { 127 <thead> 128 <tr> 129 130 @if (!hideThumbnails) 131 { 132 <th style="width:60px">&nbsp;</th> 133 } 134 <th>@Translate("Name")</th> 135 <th class="text-end d-none d-lg-table-cell">@Translate("Download")</th> 136 <th class="text-end" style="width:100px">@Translate("File type")</th> 137 138 </tr> 139 </thead> 140 } 141 <tbody class="border-top-0"> 142 @foreach (MediaViewModel asset in assetsList) 143 { 144 var assetValue = asset.Value.ToLower(); 145 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Value.Substring(asset.Value.LastIndexOf('/') + 1); 146 147 bool isVideo = false; 148 bool isVismaDocument = false; 149 foreach (string format in supportedVideoFormats) 150 { //Videos 151 if (assetValue.Contains(format)) 152 { 153 isVideo = true; 154 } 155 } 156 157 if (assetValue.Contains("getdocument=true")) 158 { 159 isVismaDocument = true; 160 } 161 if (isVismaDocument) 162 { 163 164 <tr class="position-relative"> 165 <td class="@(theme) px-0" style="width:60px"> 166 <a href="@asset.Value.Replace("https://cenger.dk","")" class="text-decoration-none text-break" download title="@assetName"> 167 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 168 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 169 </div> 170 </a> 171 </td> 172 173 <td> 174 <a href="@asset.Value.Replace("https://cenger.dk","")" class="text-decoration-none text-break" download title="@assetName"> 175 @assetName 176 </a> 177 </td> 178 </tr> 179 180 } 181 182 else if (!isVideo) 183 { 184 string filePath = Dynamicweb.Context.Current.Server.MapPath(assetValue); 185 long fileSize = 0; 186 187 if (File.Exists(filePath)) 188 { 189 fileSize = new System.IO.FileInfo(filePath) != null ? new System.IO.FileInfo(filePath).Length / 1024 : 0; 190 191 foreach (string format in allSupportedFormats) 192 { 193 if (assetValue.Contains(format)) 194 { 195 <tr class="position-relative"> 196 @if (!hideThumbnails) 197 { 198 @RenderAsset(asset) 199 } 200 <td> 201 <a href="@assetValue" class="text-decoration-none text-break" download title="@assetName"> 202 @assetName 203 </a> 204 </td> 205 <td class="text-end d-none d-lg-table-cell"> 206 <a href="@assetValue" class="text-decoration-none stretched-link" download title="@assetName"> 207 @fileSize KB <div class="icon-2" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 208 </a> 209 </td> 210 <td class="text-end">@format</td> 211 </tr> 212 } 213 } 214 } 215 } 216 else 217 { 218 string videoType = asset.Value.Contains("youtu.be") || asset.Value.Contains("youtube") ? "Youtube" : ""; 219 videoType = asset.Value.Contains("vimeo") ? "Vimeo" : videoType; 220 221 <tr data-bs-toggle="modal" data-bs-target="#modal_@(Model.ID)_@videoNumber" style="cursor: pointer"> 222 @if (!hideThumbnails) 223 { 224 @RenderAsset(asset) 225 } 226 <td> 227 @assetName 228 </td> 229 <td class="d-none d-lg-table-cell">&nbsp;</td> 230 <td align="right">@videoType</td> 231 </tr> 232 233 videoNumber++; 234 } 235 } 236 </tbody> 237 </table> 238 </div> 239 240 int modalVideoNumber = 0; 241 foreach (MediaViewModel asset in assetsList) 242 { 243 var assetName = asset.Value.ToLower(); 244 245 foreach (string format in supportedVideoFormats) 246 { //Videos 247 if (assetName.Contains(format)) 248 { 249 <div class="modal fade js-video-modal" id="modal_@(Model.ID)_@modalVideoNumber" tabindex="-1" aria-labelledby="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber" aria-hidden="true"> 250 <div class="modal-dialog modal-dialog-centered modal-xl"> 251 <div class="modal-content"> 252 <div class="modal-header visually-hidden"> 253 <h5 class="modal-title" id="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber">@product.Title</h5> 254 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 255 </div> 256 <div class="modal-body p-2 p-lg-3 h-100"> 257 @RenderVideoPlayer(asset) 258 </div> 259 </div> 260 </div> 261 </div> 262 263 modalVideoNumber++; 264 } 265 } 266 } 267 } 268 else if (Pageview.IsVisualEditorMode) 269 { 270 RatioSettings ratioSettings = GetRatioSettings(); 271 272 <div class="h-100 @theme"> 273 <div class="alert alert-dark m-0"> 274 @Translate("No assets are available") 275 </div> 276 </div> 277 } 278 279 </div> 280 281 @helper RenderAsset(MediaViewModel asset) 282 { 283 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 284 string assetValue = asset.Value; 285 286 <td class="@(theme) px-0"> 287 @foreach (string format in supportedImageFormats) 288 { //Images 289 if (assetValue.Contains(format)) 290 { 291 @RenderImage(asset) 292 } 293 } 294 @foreach (string format in supportedVideoFormats) 295 { //Videos 296 if (assetValue.Contains(format)) 297 { 298 @RenderVideoScreendump(asset) 299 } 300 } 301 @foreach (string format in supportedDocumentFormats) 302 { //Documents 303 if (assetValue.Contains(format)) 304 { 305 @RenderDocument(asset) 306 } 307 } 308 </td> 309 } 310 311 @helper RenderImage(MediaViewModel asset) 312 { 313 string productName = product.Name; 314 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 315 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 316 string imageLinkPath = imagePath; 317 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 318 319 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 320 321 RatioSettings ratioSettings = GetRatioSettings(); 322 323 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download> 324 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 325 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle itemprop="image"> 326 </div> 327 </a> 328 } 329 330 @helper RenderVideoScreendump(MediaViewModel asset) 331 { 332 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 333 334 string videoScreendumpPath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : ""; 335 string videoId = videoScreendumpPath.Substring(videoScreendumpPath.LastIndexOf('/') + 1); 336 videoScreendumpPath = videoScreendumpPath.Contains("youtu.be") || videoScreendumpPath.Contains("youtube") ? "https://img.youtube.com/vi/" + videoId + "/maxresdefault.jpg" : videoScreendumpPath; 337 338 string vimeoJsClass = videoScreendumpPath.Contains("vimeo") ? "js-vimeo-video-thumbnail" : ""; 339 videoScreendumpPath = videoScreendumpPath.Contains("vimeo") ? "" : videoScreendumpPath; 340 341 string productName = product.Name; 342 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 343 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 344 345 RatioSettings ratioSettings = GetRatioSettings(); 346 347 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 348 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 349 <div class="icon-2 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 350 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@productName" @assetTitle class="@vimeoJsClass mw-100 mh-100" data-video-id="@videoId" style="object-fit: cover;"> 351 </div> 352 </div> 353 } 354 355 @helper RenderDocument(MediaViewModel asset) 356 { 357 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 358 string productName = product.Name; 359 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 360 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 361 string imageLinkPath = imagePath; 362 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 363 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 364 365 RatioSettings ratioSettings = GetRatioSettings(); 366 367 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@productName"> 368 @if (asset.Value.Contains(".pdf")) 369 { 370 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 371 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle /> 372 </div> 373 } 374 else 375 { 376 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 377 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 378 </div> 379 } 380 </a> 381 } 382 383 @helper RenderVideoPlayer(MediaViewModel asset) 384 { 385 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Name; 386 string assetValue = asset.Value; 387 string videoId = asset.Value.Substring(asset.Value.LastIndexOf('/') + 1); 388 string type = assetValue.Contains("youtu.be") || assetValue.Contains("youtube") ? "youtube" : ""; 389 type = assetValue.Contains("vimeo") ? "vimeo" : type; 390 type = assetValue.Contains(".mp4") || assetValue.Contains(".webm") ? "selfhosted" : type; 391 392 <div class="h-100" itemscope itemtype="https://schema.org/VideoObject"> 393 <span class="visually-hidden" itemprop="name">@assetName</span> 394 <span class="visually-hidden" itemprop="contentUrl">@asset.Value</span> 395 <span class="visually-hidden" itemprop="thumbnailUrl">@asset.Value</span> 396 @if (type != "selfhosted") 397 { 398 <div id="player_@(Pageview.CurrentParagraph.ID)_@(videoId)" 399 class="plyr__video-embed" 400 data-plyr-provider="@(type)" 401 data-plyr-embed-id="@videoId" 402 style="--plyr-color-main: var(--swift-foreground-color); height: 100%"> 403 </div> 404 405 <script type="module" src="~/Files/Templates/Designs/Swift/Assets/js/plyr.js"></script> 406 <script type="module"> 407 var player = new Plyr('#player_@(Pageview.CurrentParagraph.ID)_@(videoId)', { 408 type: 'video', 409 youtube: { 410 noCookie: true, 411 showinfo: 0 412 }, 413 fullscreen: { 414 enabled: true, 415 iosNative: true, 416 } 417 }); 418 419 document.querySelectorAll('.js-video-modal').forEach(function (modal) { 420 modal.addEventListener('hidden.bs.modal', function (event) { 421 player.media.pause(); 422 }) 423 }); 424 </script> 425 } 426 else 427 { 428 string videoType = Path.GetExtension(assetValue).ToLower(); 429 430 <video preload="auto" class="h-100 w-100" style="object-fit: cover;"> 431 <source src="@assetValue" type="video/@videoType.Replace(".", "")"> 432 </video> 433 } 434 </div> 435 } 436
Ved at klikke 'Acceptér Alle' så giver til tiladelse til at vi må indsamle information om dig til forskellige formål, hvilket inkluderer: Funktionalitet, Statestik og Marketing