Error executing template "Designs/Swift/Paragraph/Swift_ProductDetailsMediaTable.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_e2afa107fd814b61adb7f711fa214d86.Execute() in D:\dynamicweb.net\Solutions\Dynamicweb\bomedys.staging.dynamicweb-cms.com\files\Templates\Designs\Swift\Paragraph\Swift_ProductDetailsMediaTable.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 int totalAssets = 0; 70 if (showOnlyPrimaryImage == false) { 71 foreach (MediaViewModel asset in assetsList) { 72 var assetValue = asset.Value.ToLower(); 73 foreach (string format in allSupportedFormats) { 74 if (assetValue.Contains(format) ) { 75 totalAssets++; 76 } 77 } 78 } 79 } 80 81 if((totalAssets == 0 && product.DefaultImage != null && selectedAssetCategories.Count() == 0) || (showOnlyPrimaryImage == true && product.DefaultImage != null)) 82 { 83 assetsList = new List<MediaViewModel>(){ product.DefaultImage }; 84 totalAssets = 1; 85 } 86 87 int videoNumber = 0; 88 89 @* Layout settings *@ 90 string spacing = Model.Item.GetRawValueString("Spacing", "p-0"); 91 spacing = spacing == "none" ? "p-0" : spacing; 92 spacing = spacing == "small" ? "p-3" : spacing; 93 spacing = spacing == "large" ? "p-5" : spacing; 94 95 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 96 97 bool hideThumbnails = Model.Item.GetBoolean("HideThumbnails"); 98 99 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 100 } 101 102 @* Get assets from selected categories or get all assets *@ 103 104 105 <div class="@spacing@(theme) item_@Model.Item.SystemName.ToLower()"> 106 @if (totalAssets != 0 && assetsList.Count() != 0) 107 { 108 if (!string.IsNullOrEmpty(Model.Item.GetString("Title")) && !Model.Item.GetBoolean("HideTitle")) 109 { 110 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "h3"); 111 112 <h3 class="@titleFontSize mb-3"> 113 @Model.Item.GetString("Title") 114 </h3> 115 } 116 117 <div class="table-responsive"> 118 <table class="table table-hover align-middle mb-0" style="table-layout: fixed;"> 119 <thead> 120 <tr> 121 @if (!hideThumbnails) 122 { 123 <th style="width:60px"> </th> 124 } 125 <th>@Translate("Name")</th> 126 <th class="text-end d-none d-lg-table-cell">@Translate("Download")</th> 127 <th class="text-end" style="width:100px">@Translate("File type")</th> 128 </tr> 129 </thead> 130 <tbody class="border-top-0"> 131 @foreach (MediaViewModel asset in assetsList) 132 { 133 var assetValue = asset.Value.ToLower(); 134 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Value.Substring( asset.Value.LastIndexOf('/') + 1); 135 136 bool isVideo = false; 137 foreach (string format in supportedVideoFormats) 138 { //Videos 139 if (assetValue.Contains(format)) 140 { 141 isVideo = true; 142 } 143 } 144 145 if (!isVideo) 146 { 147 string filePath = Dynamicweb.Context.Current.Server.MapPath(assetValue); 148 long fileSize = 0; 149 150 if (File.Exists(filePath)) { 151 fileSize = new System.IO.FileInfo(filePath) != null ? new System.IO.FileInfo(filePath).Length / 1024 : 0; 152 153 foreach (string format in allSupportedFormats) 154 { 155 if (assetValue.Contains(format)) 156 { 157 <tr class="position-relative"> 158 @if (!hideThumbnails) 159 { 160 @RenderAsset(asset) 161 } 162 <td> 163 <a href="@assetValue" class="text-decoration-none text-break" download title="@assetName"> 164 @assetName 165 </a> 166 </td> 167 <td class="text-end d-none d-lg-table-cell"> 168 <a href="@assetValue" class="text-decoration-none stretched-link" download title="@assetName"> 169 @fileSize KB <div class="icon-2" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 170 </a> 171 </td> 172 <td class="text-end">@format</td> 173 </tr> 174 } 175 } 176 } 177 } 178 else 179 { 180 string videoType = asset.Value.Contains("youtu.be") || asset.Value.Contains("youtube") ? "Youtube" : ""; 181 videoType = asset.Value.Contains("vimeo") ? "Vimeo" : videoType; 182 183 <tr data-bs-toggle="modal" data-bs-target="#modal_@(Model.ID)_@videoNumber" style="cursor: pointer"> 184 @if (!hideThumbnails) 185 { 186 @RenderAsset(asset) 187 } 188 <td> 189 @assetName 190 </td> 191 <td class="d-none d-lg-table-cell"> </td> 192 <td align="right">@videoType</td> 193 </tr> 194 195 videoNumber++; 196 } 197 } 198 </tbody> 199 </table> 200 </div> 201 202 int modalVideoNumber = 0; 203 foreach (MediaViewModel asset in assetsList) 204 { 205 var assetName = asset.Value.ToLower(); 206 207 foreach (string format in supportedVideoFormats) 208 { //Videos 209 if (assetName.Contains(format)) 210 { 211 <div class="modal fade js-video-modal" id="modal_@(Model.ID)_@modalVideoNumber" tabindex="-1" aria-labelledby="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber" aria-hidden="true"> 212 <div class="modal-dialog modal-dialog-centered modal-xl"> 213 <div class="modal-content"> 214 <div class="modal-header visually-hidden"> 215 <h5 class="modal-title" id="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber">@product.Title</h5> 216 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 217 </div> 218 <div class="modal-body p-2 p-lg-3 h-100"> 219 @{ @RenderVideoPlayer(asset) } 220 </div> 221 </div> 222 </div> 223 </div> 224 225 modalVideoNumber++; 226 } 227 } 228 } 229 } 230 else if (Pageview.IsVisualEditorMode) 231 { 232 RatioSettings ratioSettings = GetRatioSettings(); 233 234 <div class="h-100 @theme"> 235 <div class="alert alert-dark m-0"> 236 @Translate("No assets are available") 237 </div> 238 </div> 239 } 240 241 </div> 242 243 @helper RenderAsset(MediaViewModel asset) 244 { 245 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 246 string assetValue = asset.Value; 247 248 <td class="@(theme) px-0"> 249 @foreach (string format in supportedImageFormats) 250 { //Images 251 if (assetValue.Contains(format)) 252 { 253 @RenderImage(asset) 254 } 255 } 256 @foreach (string format in supportedVideoFormats) 257 { //Videos 258 if (assetValue.Contains(format)) 259 { 260 @RenderVideoScreendump(asset) 261 } 262 } 263 @foreach (string format in supportedDocumentFormats) 264 { //Documents 265 if (assetValue.Contains(format)) 266 { 267 @RenderDocument(asset) 268 } 269 } 270 </td> 271 } 272 273 @helper RenderImage(MediaViewModel asset) 274 { 275 string productName = product.Name; 276 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 277 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 278 string imageLinkPath = imagePath; 279 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 280 281 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 282 283 RatioSettings ratioSettings = GetRatioSettings(); 284 285 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download> 286 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 287 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle itemprop="image"> 288 </div> 289 </a> 290 } 291 292 @helper RenderVideoScreendump(MediaViewModel asset) 293 { 294 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 295 296 string videoScreendumpPath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : ""; 297 string videoId = videoScreendumpPath.Substring(videoScreendumpPath.LastIndexOf('/') + 1); 298 videoScreendumpPath = videoScreendumpPath.Contains("youtu.be") || videoScreendumpPath.Contains("youtube") ? "https://img.youtube.com/vi/" + videoId + "/mqdefault.jpg" : videoScreendumpPath; 299 300 string vimeoJsClass = videoScreendumpPath.Contains("vimeo") ? "js-vimeo-video-thumbnail" : ""; 301 videoScreendumpPath = videoScreendumpPath.Contains("vimeo") ? "" : videoScreendumpPath; 302 303 string productName = product.Name; 304 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 305 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 306 307 RatioSettings ratioSettings = GetRatioSettings(); 308 309 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 310 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 311 <div class="icon-2 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 312 @if (!videoScreendumpPath.Contains(".mp4")) { 313 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@productName" @assetTitle class="@vimeoJsClass mw-100 mh-100" data-video-id="@videoId" style="object-fit: cover;"> 314 } else { 315 string videoType = Path.GetExtension(asset.Value).ToLower(); 316 317 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 318 <source src="@asset.Value" type="video/@videoType.Replace(".", "")"> 319 </video> 320 } 321 </div> 322 </div> 323 } 324 325 @helper RenderDocument(MediaViewModel asset) 326 { 327 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 328 string productName = product.Name; 329 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 330 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 331 string imageLinkPath = imagePath; 332 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 333 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 334 335 RatioSettings ratioSettings = GetRatioSettings(); 336 337 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@productName"> 338 @if (asset.Value.Contains(".pdf")) { 339 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 340 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle /> 341 </div> 342 } else { 343 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 344 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 345 </div> 346 } 347 </a> 348 } 349 350 @helper RenderVideoPlayer(MediaViewModel asset) 351 { 352 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Name; 353 string assetValue = asset.Value; 354 string videoId = asset.Value.Substring(asset.Value.LastIndexOf('/') + 1); 355 string type = assetValue.Contains("youtu.be") || assetValue.Contains("youtube") ? "youtube" : ""; 356 type = assetValue.Contains("vimeo") ? "vimeo" : type; 357 type = assetValue.Contains(".mp4") || assetValue.Contains(".webm") ? "selfhosted" : type; 358 359 <div class="h-100" itemscope itemtype="https://schema.org/VideoObject"> 360 <span class="visually-hidden" itemprop="name">@assetName</span> 361 <span class="visually-hidden" itemprop="contentUrl">@asset.Value</span> 362 <span class="visually-hidden" itemprop="thumbnailUrl">@asset.Value</span> 363 @if (type != "selfhosted") 364 { 365 <div id="player_@(Pageview.CurrentParagraph.ID)_@(videoId)" 366 class="plyr__video-embed" 367 data-plyr-provider="@(type)" 368 data-plyr-embed-id="@videoId" 369 style="--plyr-color-main: var(--swift-foreground-color); height: 100%"> 370 </div> 371 372 <script type="module" src="~/Files/Templates/Designs/Swift/Assets/js/plyr.js"></script> 373 <script type="module"> 374 var player = new Plyr('#player_@(Pageview.CurrentParagraph.ID)_@(videoId)', { 375 type: 'video', 376 youtube: { 377 noCookie: true, 378 showinfo: 0 379 }, 380 fullscreen: { 381 enabled: true, 382 iosNative: true, 383 } 384 }); 385 386 document.querySelectorAll('.js-video-modal').forEach(function (modal) { 387 modal.addEventListener('hidden.bs.modal', function (event) { 388 player.media.pause(); 389 }) 390 }); 391 </script> 392 } 393 else 394 { 395 string videoType = Path.GetExtension(assetValue).ToLower(); 396 397 <video preload="auto" class="h-100 w-100" style="object-fit: cover;" controls> 398 <source src="@assetValue" type="video/@videoType.Replace(".", "")"> 399 </video> 400 } 401 </div> 402 } 403
Error executing template "Designs/Swift/Paragraph/Swift_ProductDetailsMediaTable.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_e2afa107fd814b61adb7f711fa214d86.Execute() in D:\dynamicweb.net\Solutions\Dynamicweb\bomedys.staging.dynamicweb-cms.com\files\Templates\Designs\Swift\Paragraph\Swift_ProductDetailsMediaTable.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 int totalAssets = 0; 70 if (showOnlyPrimaryImage == false) { 71 foreach (MediaViewModel asset in assetsList) { 72 var assetValue = asset.Value.ToLower(); 73 foreach (string format in allSupportedFormats) { 74 if (assetValue.Contains(format) ) { 75 totalAssets++; 76 } 77 } 78 } 79 } 80 81 if((totalAssets == 0 && product.DefaultImage != null && selectedAssetCategories.Count() == 0) || (showOnlyPrimaryImage == true && product.DefaultImage != null)) 82 { 83 assetsList = new List<MediaViewModel>(){ product.DefaultImage }; 84 totalAssets = 1; 85 } 86 87 int videoNumber = 0; 88 89 @* Layout settings *@ 90 string spacing = Model.Item.GetRawValueString("Spacing", "p-0"); 91 spacing = spacing == "none" ? "p-0" : spacing; 92 spacing = spacing == "small" ? "p-3" : spacing; 93 spacing = spacing == "large" ? "p-5" : spacing; 94 95 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 96 97 bool hideThumbnails = Model.Item.GetBoolean("HideThumbnails"); 98 99 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 100 } 101 102 @* Get assets from selected categories or get all assets *@ 103 104 105 <div class="@spacing@(theme) item_@Model.Item.SystemName.ToLower()"> 106 @if (totalAssets != 0 && assetsList.Count() != 0) 107 { 108 if (!string.IsNullOrEmpty(Model.Item.GetString("Title")) && !Model.Item.GetBoolean("HideTitle")) 109 { 110 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "h3"); 111 112 <h3 class="@titleFontSize mb-3"> 113 @Model.Item.GetString("Title") 114 </h3> 115 } 116 117 <div class="table-responsive"> 118 <table class="table table-hover align-middle mb-0" style="table-layout: fixed;"> 119 <thead> 120 <tr> 121 @if (!hideThumbnails) 122 { 123 <th style="width:60px"> </th> 124 } 125 <th>@Translate("Name")</th> 126 <th class="text-end d-none d-lg-table-cell">@Translate("Download")</th> 127 <th class="text-end" style="width:100px">@Translate("File type")</th> 128 </tr> 129 </thead> 130 <tbody class="border-top-0"> 131 @foreach (MediaViewModel asset in assetsList) 132 { 133 var assetValue = asset.Value.ToLower(); 134 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Value.Substring( asset.Value.LastIndexOf('/') + 1); 135 136 bool isVideo = false; 137 foreach (string format in supportedVideoFormats) 138 { //Videos 139 if (assetValue.Contains(format)) 140 { 141 isVideo = true; 142 } 143 } 144 145 if (!isVideo) 146 { 147 string filePath = Dynamicweb.Context.Current.Server.MapPath(assetValue); 148 long fileSize = 0; 149 150 if (File.Exists(filePath)) { 151 fileSize = new System.IO.FileInfo(filePath) != null ? new System.IO.FileInfo(filePath).Length / 1024 : 0; 152 153 foreach (string format in allSupportedFormats) 154 { 155 if (assetValue.Contains(format)) 156 { 157 <tr class="position-relative"> 158 @if (!hideThumbnails) 159 { 160 @RenderAsset(asset) 161 } 162 <td> 163 <a href="@assetValue" class="text-decoration-none text-break" download title="@assetName"> 164 @assetName 165 </a> 166 </td> 167 <td class="text-end d-none d-lg-table-cell"> 168 <a href="@assetValue" class="text-decoration-none stretched-link" download title="@assetName"> 169 @fileSize KB <div class="icon-2" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 170 </a> 171 </td> 172 <td class="text-end">@format</td> 173 </tr> 174 } 175 } 176 } 177 } 178 else 179 { 180 string videoType = asset.Value.Contains("youtu.be") || asset.Value.Contains("youtube") ? "Youtube" : ""; 181 videoType = asset.Value.Contains("vimeo") ? "Vimeo" : videoType; 182 183 <tr data-bs-toggle="modal" data-bs-target="#modal_@(Model.ID)_@videoNumber" style="cursor: pointer"> 184 @if (!hideThumbnails) 185 { 186 @RenderAsset(asset) 187 } 188 <td> 189 @assetName 190 </td> 191 <td class="d-none d-lg-table-cell"> </td> 192 <td align="right">@videoType</td> 193 </tr> 194 195 videoNumber++; 196 } 197 } 198 </tbody> 199 </table> 200 </div> 201 202 int modalVideoNumber = 0; 203 foreach (MediaViewModel asset in assetsList) 204 { 205 var assetName = asset.Value.ToLower(); 206 207 foreach (string format in supportedVideoFormats) 208 { //Videos 209 if (assetName.Contains(format)) 210 { 211 <div class="modal fade js-video-modal" id="modal_@(Model.ID)_@modalVideoNumber" tabindex="-1" aria-labelledby="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber" aria-hidden="true"> 212 <div class="modal-dialog modal-dialog-centered modal-xl"> 213 <div class="modal-content"> 214 <div class="modal-header visually-hidden"> 215 <h5 class="modal-title" id="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber">@product.Title</h5> 216 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 217 </div> 218 <div class="modal-body p-2 p-lg-3 h-100"> 219 @{ @RenderVideoPlayer(asset) } 220 </div> 221 </div> 222 </div> 223 </div> 224 225 modalVideoNumber++; 226 } 227 } 228 } 229 } 230 else if (Pageview.IsVisualEditorMode) 231 { 232 RatioSettings ratioSettings = GetRatioSettings(); 233 234 <div class="h-100 @theme"> 235 <div class="alert alert-dark m-0"> 236 @Translate("No assets are available") 237 </div> 238 </div> 239 } 240 241 </div> 242 243 @helper RenderAsset(MediaViewModel asset) 244 { 245 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 246 string assetValue = asset.Value; 247 248 <td class="@(theme) px-0"> 249 @foreach (string format in supportedImageFormats) 250 { //Images 251 if (assetValue.Contains(format)) 252 { 253 @RenderImage(asset) 254 } 255 } 256 @foreach (string format in supportedVideoFormats) 257 { //Videos 258 if (assetValue.Contains(format)) 259 { 260 @RenderVideoScreendump(asset) 261 } 262 } 263 @foreach (string format in supportedDocumentFormats) 264 { //Documents 265 if (assetValue.Contains(format)) 266 { 267 @RenderDocument(asset) 268 } 269 } 270 </td> 271 } 272 273 @helper RenderImage(MediaViewModel asset) 274 { 275 string productName = product.Name; 276 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 277 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 278 string imageLinkPath = imagePath; 279 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 280 281 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 282 283 RatioSettings ratioSettings = GetRatioSettings(); 284 285 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download> 286 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 287 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle itemprop="image"> 288 </div> 289 </a> 290 } 291 292 @helper RenderVideoScreendump(MediaViewModel asset) 293 { 294 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 295 296 string videoScreendumpPath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : ""; 297 string videoId = videoScreendumpPath.Substring(videoScreendumpPath.LastIndexOf('/') + 1); 298 videoScreendumpPath = videoScreendumpPath.Contains("youtu.be") || videoScreendumpPath.Contains("youtube") ? "https://img.youtube.com/vi/" + videoId + "/mqdefault.jpg" : videoScreendumpPath; 299 300 string vimeoJsClass = videoScreendumpPath.Contains("vimeo") ? "js-vimeo-video-thumbnail" : ""; 301 videoScreendumpPath = videoScreendumpPath.Contains("vimeo") ? "" : videoScreendumpPath; 302 303 string productName = product.Name; 304 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 305 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 306 307 RatioSettings ratioSettings = GetRatioSettings(); 308 309 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 310 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 311 <div class="icon-2 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 312 @if (!videoScreendumpPath.Contains(".mp4")) { 313 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@productName" @assetTitle class="@vimeoJsClass mw-100 mh-100" data-video-id="@videoId" style="object-fit: cover;"> 314 } else { 315 string videoType = Path.GetExtension(asset.Value).ToLower(); 316 317 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 318 <source src="@asset.Value" type="video/@videoType.Replace(".", "")"> 319 </video> 320 } 321 </div> 322 </div> 323 } 324 325 @helper RenderDocument(MediaViewModel asset) 326 { 327 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 328 string productName = product.Name; 329 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 330 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 331 string imageLinkPath = imagePath; 332 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 333 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 334 335 RatioSettings ratioSettings = GetRatioSettings(); 336 337 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@productName"> 338 @if (asset.Value.Contains(".pdf")) { 339 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 340 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle /> 341 </div> 342 } else { 343 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 344 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 345 </div> 346 } 347 </a> 348 } 349 350 @helper RenderVideoPlayer(MediaViewModel asset) 351 { 352 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Name; 353 string assetValue = asset.Value; 354 string videoId = asset.Value.Substring(asset.Value.LastIndexOf('/') + 1); 355 string type = assetValue.Contains("youtu.be") || assetValue.Contains("youtube") ? "youtube" : ""; 356 type = assetValue.Contains("vimeo") ? "vimeo" : type; 357 type = assetValue.Contains(".mp4") || assetValue.Contains(".webm") ? "selfhosted" : type; 358 359 <div class="h-100" itemscope itemtype="https://schema.org/VideoObject"> 360 <span class="visually-hidden" itemprop="name">@assetName</span> 361 <span class="visually-hidden" itemprop="contentUrl">@asset.Value</span> 362 <span class="visually-hidden" itemprop="thumbnailUrl">@asset.Value</span> 363 @if (type != "selfhosted") 364 { 365 <div id="player_@(Pageview.CurrentParagraph.ID)_@(videoId)" 366 class="plyr__video-embed" 367 data-plyr-provider="@(type)" 368 data-plyr-embed-id="@videoId" 369 style="--plyr-color-main: var(--swift-foreground-color); height: 100%"> 370 </div> 371 372 <script type="module" src="~/Files/Templates/Designs/Swift/Assets/js/plyr.js"></script> 373 <script type="module"> 374 var player = new Plyr('#player_@(Pageview.CurrentParagraph.ID)_@(videoId)', { 375 type: 'video', 376 youtube: { 377 noCookie: true, 378 showinfo: 0 379 }, 380 fullscreen: { 381 enabled: true, 382 iosNative: true, 383 } 384 }); 385 386 document.querySelectorAll('.js-video-modal').forEach(function (modal) { 387 modal.addEventListener('hidden.bs.modal', function (event) { 388 player.media.pause(); 389 }) 390 }); 391 </script> 392 } 393 else 394 { 395 string videoType = Path.GetExtension(assetValue).ToLower(); 396 397 <video preload="auto" class="h-100 w-100" style="object-fit: cover;" controls> 398 <source src="@assetValue" type="video/@videoType.Replace(".", "")"> 399 </video> 400 } 401 </div> 402 } 403
Error executing template "Designs/Swift/Paragraph/Swift_ProductDetailsMediaTable.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_e2afa107fd814b61adb7f711fa214d86.Execute() in D:\dynamicweb.net\Solutions\Dynamicweb\bomedys.staging.dynamicweb-cms.com\files\Templates\Designs\Swift\Paragraph\Swift_ProductDetailsMediaTable.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 int totalAssets = 0; 70 if (showOnlyPrimaryImage == false) { 71 foreach (MediaViewModel asset in assetsList) { 72 var assetValue = asset.Value.ToLower(); 73 foreach (string format in allSupportedFormats) { 74 if (assetValue.Contains(format) ) { 75 totalAssets++; 76 } 77 } 78 } 79 } 80 81 if((totalAssets == 0 && product.DefaultImage != null && selectedAssetCategories.Count() == 0) || (showOnlyPrimaryImage == true && product.DefaultImage != null)) 82 { 83 assetsList = new List<MediaViewModel>(){ product.DefaultImage }; 84 totalAssets = 1; 85 } 86 87 int videoNumber = 0; 88 89 @* Layout settings *@ 90 string spacing = Model.Item.GetRawValueString("Spacing", "p-0"); 91 spacing = spacing == "none" ? "p-0" : spacing; 92 spacing = spacing == "small" ? "p-3" : spacing; 93 spacing = spacing == "large" ? "p-5" : spacing; 94 95 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 96 97 bool hideThumbnails = Model.Item.GetBoolean("HideThumbnails"); 98 99 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 100 } 101 102 @* Get assets from selected categories or get all assets *@ 103 104 105 <div class="@spacing@(theme) item_@Model.Item.SystemName.ToLower()"> 106 @if (totalAssets != 0 && assetsList.Count() != 0) 107 { 108 if (!string.IsNullOrEmpty(Model.Item.GetString("Title")) && !Model.Item.GetBoolean("HideTitle")) 109 { 110 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "h3"); 111 112 <h3 class="@titleFontSize mb-3"> 113 @Model.Item.GetString("Title") 114 </h3> 115 } 116 117 <div class="table-responsive"> 118 <table class="table table-hover align-middle mb-0" style="table-layout: fixed;"> 119 <thead> 120 <tr> 121 @if (!hideThumbnails) 122 { 123 <th style="width:60px"> </th> 124 } 125 <th>@Translate("Name")</th> 126 <th class="text-end d-none d-lg-table-cell">@Translate("Download")</th> 127 <th class="text-end" style="width:100px">@Translate("File type")</th> 128 </tr> 129 </thead> 130 <tbody class="border-top-0"> 131 @foreach (MediaViewModel asset in assetsList) 132 { 133 var assetValue = asset.Value.ToLower(); 134 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Value.Substring( asset.Value.LastIndexOf('/') + 1); 135 136 bool isVideo = false; 137 foreach (string format in supportedVideoFormats) 138 { //Videos 139 if (assetValue.Contains(format)) 140 { 141 isVideo = true; 142 } 143 } 144 145 if (!isVideo) 146 { 147 string filePath = Dynamicweb.Context.Current.Server.MapPath(assetValue); 148 long fileSize = 0; 149 150 if (File.Exists(filePath)) { 151 fileSize = new System.IO.FileInfo(filePath) != null ? new System.IO.FileInfo(filePath).Length / 1024 : 0; 152 153 foreach (string format in allSupportedFormats) 154 { 155 if (assetValue.Contains(format)) 156 { 157 <tr class="position-relative"> 158 @if (!hideThumbnails) 159 { 160 @RenderAsset(asset) 161 } 162 <td> 163 <a href="@assetValue" class="text-decoration-none text-break" download title="@assetName"> 164 @assetName 165 </a> 166 </td> 167 <td class="text-end d-none d-lg-table-cell"> 168 <a href="@assetValue" class="text-decoration-none stretched-link" download title="@assetName"> 169 @fileSize KB <div class="icon-2" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 170 </a> 171 </td> 172 <td class="text-end">@format</td> 173 </tr> 174 } 175 } 176 } 177 } 178 else 179 { 180 string videoType = asset.Value.Contains("youtu.be") || asset.Value.Contains("youtube") ? "Youtube" : ""; 181 videoType = asset.Value.Contains("vimeo") ? "Vimeo" : videoType; 182 183 <tr data-bs-toggle="modal" data-bs-target="#modal_@(Model.ID)_@videoNumber" style="cursor: pointer"> 184 @if (!hideThumbnails) 185 { 186 @RenderAsset(asset) 187 } 188 <td> 189 @assetName 190 </td> 191 <td class="d-none d-lg-table-cell"> </td> 192 <td align="right">@videoType</td> 193 </tr> 194 195 videoNumber++; 196 } 197 } 198 </tbody> 199 </table> 200 </div> 201 202 int modalVideoNumber = 0; 203 foreach (MediaViewModel asset in assetsList) 204 { 205 var assetName = asset.Value.ToLower(); 206 207 foreach (string format in supportedVideoFormats) 208 { //Videos 209 if (assetName.Contains(format)) 210 { 211 <div class="modal fade js-video-modal" id="modal_@(Model.ID)_@modalVideoNumber" tabindex="-1" aria-labelledby="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber" aria-hidden="true"> 212 <div class="modal-dialog modal-dialog-centered modal-xl"> 213 <div class="modal-content"> 214 <div class="modal-header visually-hidden"> 215 <h5 class="modal-title" id="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber">@product.Title</h5> 216 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 217 </div> 218 <div class="modal-body p-2 p-lg-3 h-100"> 219 @{ @RenderVideoPlayer(asset) } 220 </div> 221 </div> 222 </div> 223 </div> 224 225 modalVideoNumber++; 226 } 227 } 228 } 229 } 230 else if (Pageview.IsVisualEditorMode) 231 { 232 RatioSettings ratioSettings = GetRatioSettings(); 233 234 <div class="h-100 @theme"> 235 <div class="alert alert-dark m-0"> 236 @Translate("No assets are available") 237 </div> 238 </div> 239 } 240 241 </div> 242 243 @helper RenderAsset(MediaViewModel asset) 244 { 245 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 246 string assetValue = asset.Value; 247 248 <td class="@(theme) px-0"> 249 @foreach (string format in supportedImageFormats) 250 { //Images 251 if (assetValue.Contains(format)) 252 { 253 @RenderImage(asset) 254 } 255 } 256 @foreach (string format in supportedVideoFormats) 257 { //Videos 258 if (assetValue.Contains(format)) 259 { 260 @RenderVideoScreendump(asset) 261 } 262 } 263 @foreach (string format in supportedDocumentFormats) 264 { //Documents 265 if (assetValue.Contains(format)) 266 { 267 @RenderDocument(asset) 268 } 269 } 270 </td> 271 } 272 273 @helper RenderImage(MediaViewModel asset) 274 { 275 string productName = product.Name; 276 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 277 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 278 string imageLinkPath = imagePath; 279 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 280 281 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 282 283 RatioSettings ratioSettings = GetRatioSettings(); 284 285 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download> 286 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 287 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle itemprop="image"> 288 </div> 289 </a> 290 } 291 292 @helper RenderVideoScreendump(MediaViewModel asset) 293 { 294 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 295 296 string videoScreendumpPath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : ""; 297 string videoId = videoScreendumpPath.Substring(videoScreendumpPath.LastIndexOf('/') + 1); 298 videoScreendumpPath = videoScreendumpPath.Contains("youtu.be") || videoScreendumpPath.Contains("youtube") ? "https://img.youtube.com/vi/" + videoId + "/mqdefault.jpg" : videoScreendumpPath; 299 300 string vimeoJsClass = videoScreendumpPath.Contains("vimeo") ? "js-vimeo-video-thumbnail" : ""; 301 videoScreendumpPath = videoScreendumpPath.Contains("vimeo") ? "" : videoScreendumpPath; 302 303 string productName = product.Name; 304 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 305 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 306 307 RatioSettings ratioSettings = GetRatioSettings(); 308 309 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 310 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 311 <div class="icon-2 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 312 @if (!videoScreendumpPath.Contains(".mp4")) { 313 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@productName" @assetTitle class="@vimeoJsClass mw-100 mh-100" data-video-id="@videoId" style="object-fit: cover;"> 314 } else { 315 string videoType = Path.GetExtension(asset.Value).ToLower(); 316 317 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 318 <source src="@asset.Value" type="video/@videoType.Replace(".", "")"> 319 </video> 320 } 321 </div> 322 </div> 323 } 324 325 @helper RenderDocument(MediaViewModel asset) 326 { 327 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 328 string productName = product.Name; 329 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 330 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 331 string imageLinkPath = imagePath; 332 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 333 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 334 335 RatioSettings ratioSettings = GetRatioSettings(); 336 337 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@productName"> 338 @if (asset.Value.Contains(".pdf")) { 339 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 340 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle /> 341 </div> 342 } else { 343 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 344 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 345 </div> 346 } 347 </a> 348 } 349 350 @helper RenderVideoPlayer(MediaViewModel asset) 351 { 352 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Name; 353 string assetValue = asset.Value; 354 string videoId = asset.Value.Substring(asset.Value.LastIndexOf('/') + 1); 355 string type = assetValue.Contains("youtu.be") || assetValue.Contains("youtube") ? "youtube" : ""; 356 type = assetValue.Contains("vimeo") ? "vimeo" : type; 357 type = assetValue.Contains(".mp4") || assetValue.Contains(".webm") ? "selfhosted" : type; 358 359 <div class="h-100" itemscope itemtype="https://schema.org/VideoObject"> 360 <span class="visually-hidden" itemprop="name">@assetName</span> 361 <span class="visually-hidden" itemprop="contentUrl">@asset.Value</span> 362 <span class="visually-hidden" itemprop="thumbnailUrl">@asset.Value</span> 363 @if (type != "selfhosted") 364 { 365 <div id="player_@(Pageview.CurrentParagraph.ID)_@(videoId)" 366 class="plyr__video-embed" 367 data-plyr-provider="@(type)" 368 data-plyr-embed-id="@videoId" 369 style="--plyr-color-main: var(--swift-foreground-color); height: 100%"> 370 </div> 371 372 <script type="module" src="~/Files/Templates/Designs/Swift/Assets/js/plyr.js"></script> 373 <script type="module"> 374 var player = new Plyr('#player_@(Pageview.CurrentParagraph.ID)_@(videoId)', { 375 type: 'video', 376 youtube: { 377 noCookie: true, 378 showinfo: 0 379 }, 380 fullscreen: { 381 enabled: true, 382 iosNative: true, 383 } 384 }); 385 386 document.querySelectorAll('.js-video-modal').forEach(function (modal) { 387 modal.addEventListener('hidden.bs.modal', function (event) { 388 player.media.pause(); 389 }) 390 }); 391 </script> 392 } 393 else 394 { 395 string videoType = Path.GetExtension(assetValue).ToLower(); 396 397 <video preload="auto" class="h-100 w-100" style="object-fit: cover;" controls> 398 <source src="@assetValue" type="video/@videoType.Replace(".", "")"> 399 </video> 400 } 401 </div> 402 } 403
Error executing template "Designs/Swift/Paragraph/Swift_ProductDetailsMediaTable.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_e2afa107fd814b61adb7f711fa214d86.Execute() in D:\dynamicweb.net\Solutions\Dynamicweb\bomedys.staging.dynamicweb-cms.com\files\Templates\Designs\Swift\Paragraph\Swift_ProductDetailsMediaTable.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 int totalAssets = 0; 70 if (showOnlyPrimaryImage == false) { 71 foreach (MediaViewModel asset in assetsList) { 72 var assetValue = asset.Value.ToLower(); 73 foreach (string format in allSupportedFormats) { 74 if (assetValue.Contains(format) ) { 75 totalAssets++; 76 } 77 } 78 } 79 } 80 81 if((totalAssets == 0 && product.DefaultImage != null && selectedAssetCategories.Count() == 0) || (showOnlyPrimaryImage == true && product.DefaultImage != null)) 82 { 83 assetsList = new List<MediaViewModel>(){ product.DefaultImage }; 84 totalAssets = 1; 85 } 86 87 int videoNumber = 0; 88 89 @* Layout settings *@ 90 string spacing = Model.Item.GetRawValueString("Spacing", "p-0"); 91 spacing = spacing == "none" ? "p-0" : spacing; 92 spacing = spacing == "small" ? "p-3" : spacing; 93 spacing = spacing == "large" ? "p-5" : spacing; 94 95 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 96 97 bool hideThumbnails = Model.Item.GetBoolean("HideThumbnails"); 98 99 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 100 } 101 102 @* Get assets from selected categories or get all assets *@ 103 104 105 <div class="@spacing@(theme) item_@Model.Item.SystemName.ToLower()"> 106 @if (totalAssets != 0 && assetsList.Count() != 0) 107 { 108 if (!string.IsNullOrEmpty(Model.Item.GetString("Title")) && !Model.Item.GetBoolean("HideTitle")) 109 { 110 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "h3"); 111 112 <h3 class="@titleFontSize mb-3"> 113 @Model.Item.GetString("Title") 114 </h3> 115 } 116 117 <div class="table-responsive"> 118 <table class="table table-hover align-middle mb-0" style="table-layout: fixed;"> 119 <thead> 120 <tr> 121 @if (!hideThumbnails) 122 { 123 <th style="width:60px"> </th> 124 } 125 <th>@Translate("Name")</th> 126 <th class="text-end d-none d-lg-table-cell">@Translate("Download")</th> 127 <th class="text-end" style="width:100px">@Translate("File type")</th> 128 </tr> 129 </thead> 130 <tbody class="border-top-0"> 131 @foreach (MediaViewModel asset in assetsList) 132 { 133 var assetValue = asset.Value.ToLower(); 134 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Value.Substring( asset.Value.LastIndexOf('/') + 1); 135 136 bool isVideo = false; 137 foreach (string format in supportedVideoFormats) 138 { //Videos 139 if (assetValue.Contains(format)) 140 { 141 isVideo = true; 142 } 143 } 144 145 if (!isVideo) 146 { 147 string filePath = Dynamicweb.Context.Current.Server.MapPath(assetValue); 148 long fileSize = 0; 149 150 if (File.Exists(filePath)) { 151 fileSize = new System.IO.FileInfo(filePath) != null ? new System.IO.FileInfo(filePath).Length / 1024 : 0; 152 153 foreach (string format in allSupportedFormats) 154 { 155 if (assetValue.Contains(format)) 156 { 157 <tr class="position-relative"> 158 @if (!hideThumbnails) 159 { 160 @RenderAsset(asset) 161 } 162 <td> 163 <a href="@assetValue" class="text-decoration-none text-break" download title="@assetName"> 164 @assetName 165 </a> 166 </td> 167 <td class="text-end d-none d-lg-table-cell"> 168 <a href="@assetValue" class="text-decoration-none stretched-link" download title="@assetName"> 169 @fileSize KB <div class="icon-2" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 170 </a> 171 </td> 172 <td class="text-end">@format</td> 173 </tr> 174 } 175 } 176 } 177 } 178 else 179 { 180 string videoType = asset.Value.Contains("youtu.be") || asset.Value.Contains("youtube") ? "Youtube" : ""; 181 videoType = asset.Value.Contains("vimeo") ? "Vimeo" : videoType; 182 183 <tr data-bs-toggle="modal" data-bs-target="#modal_@(Model.ID)_@videoNumber" style="cursor: pointer"> 184 @if (!hideThumbnails) 185 { 186 @RenderAsset(asset) 187 } 188 <td> 189 @assetName 190 </td> 191 <td class="d-none d-lg-table-cell"> </td> 192 <td align="right">@videoType</td> 193 </tr> 194 195 videoNumber++; 196 } 197 } 198 </tbody> 199 </table> 200 </div> 201 202 int modalVideoNumber = 0; 203 foreach (MediaViewModel asset in assetsList) 204 { 205 var assetName = asset.Value.ToLower(); 206 207 foreach (string format in supportedVideoFormats) 208 { //Videos 209 if (assetName.Contains(format)) 210 { 211 <div class="modal fade js-video-modal" id="modal_@(Model.ID)_@modalVideoNumber" tabindex="-1" aria-labelledby="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber" aria-hidden="true"> 212 <div class="modal-dialog modal-dialog-centered modal-xl"> 213 <div class="modal-content"> 214 <div class="modal-header visually-hidden"> 215 <h5 class="modal-title" id="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber">@product.Title</h5> 216 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 217 </div> 218 <div class="modal-body p-2 p-lg-3 h-100"> 219 @{ @RenderVideoPlayer(asset) } 220 </div> 221 </div> 222 </div> 223 </div> 224 225 modalVideoNumber++; 226 } 227 } 228 } 229 } 230 else if (Pageview.IsVisualEditorMode) 231 { 232 RatioSettings ratioSettings = GetRatioSettings(); 233 234 <div class="h-100 @theme"> 235 <div class="alert alert-dark m-0"> 236 @Translate("No assets are available") 237 </div> 238 </div> 239 } 240 241 </div> 242 243 @helper RenderAsset(MediaViewModel asset) 244 { 245 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 246 string assetValue = asset.Value; 247 248 <td class="@(theme) px-0"> 249 @foreach (string format in supportedImageFormats) 250 { //Images 251 if (assetValue.Contains(format)) 252 { 253 @RenderImage(asset) 254 } 255 } 256 @foreach (string format in supportedVideoFormats) 257 { //Videos 258 if (assetValue.Contains(format)) 259 { 260 @RenderVideoScreendump(asset) 261 } 262 } 263 @foreach (string format in supportedDocumentFormats) 264 { //Documents 265 if (assetValue.Contains(format)) 266 { 267 @RenderDocument(asset) 268 } 269 } 270 </td> 271 } 272 273 @helper RenderImage(MediaViewModel asset) 274 { 275 string productName = product.Name; 276 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 277 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 278 string imageLinkPath = imagePath; 279 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 280 281 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 282 283 RatioSettings ratioSettings = GetRatioSettings(); 284 285 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download> 286 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 287 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle itemprop="image"> 288 </div> 289 </a> 290 } 291 292 @helper RenderVideoScreendump(MediaViewModel asset) 293 { 294 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 295 296 string videoScreendumpPath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : ""; 297 string videoId = videoScreendumpPath.Substring(videoScreendumpPath.LastIndexOf('/') + 1); 298 videoScreendumpPath = videoScreendumpPath.Contains("youtu.be") || videoScreendumpPath.Contains("youtube") ? "https://img.youtube.com/vi/" + videoId + "/mqdefault.jpg" : videoScreendumpPath; 299 300 string vimeoJsClass = videoScreendumpPath.Contains("vimeo") ? "js-vimeo-video-thumbnail" : ""; 301 videoScreendumpPath = videoScreendumpPath.Contains("vimeo") ? "" : videoScreendumpPath; 302 303 string productName = product.Name; 304 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 305 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 306 307 RatioSettings ratioSettings = GetRatioSettings(); 308 309 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 310 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 311 <div class="icon-2 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 312 @if (!videoScreendumpPath.Contains(".mp4")) { 313 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@productName" @assetTitle class="@vimeoJsClass mw-100 mh-100" data-video-id="@videoId" style="object-fit: cover;"> 314 } else { 315 string videoType = Path.GetExtension(asset.Value).ToLower(); 316 317 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 318 <source src="@asset.Value" type="video/@videoType.Replace(".", "")"> 319 </video> 320 } 321 </div> 322 </div> 323 } 324 325 @helper RenderDocument(MediaViewModel asset) 326 { 327 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 328 string productName = product.Name; 329 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 330 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 331 string imageLinkPath = imagePath; 332 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 333 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 334 335 RatioSettings ratioSettings = GetRatioSettings(); 336 337 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@productName"> 338 @if (asset.Value.Contains(".pdf")) { 339 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 340 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle /> 341 </div> 342 } else { 343 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 344 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 345 </div> 346 } 347 </a> 348 } 349 350 @helper RenderVideoPlayer(MediaViewModel asset) 351 { 352 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Name; 353 string assetValue = asset.Value; 354 string videoId = asset.Value.Substring(asset.Value.LastIndexOf('/') + 1); 355 string type = assetValue.Contains("youtu.be") || assetValue.Contains("youtube") ? "youtube" : ""; 356 type = assetValue.Contains("vimeo") ? "vimeo" : type; 357 type = assetValue.Contains(".mp4") || assetValue.Contains(".webm") ? "selfhosted" : type; 358 359 <div class="h-100" itemscope itemtype="https://schema.org/VideoObject"> 360 <span class="visually-hidden" itemprop="name">@assetName</span> 361 <span class="visually-hidden" itemprop="contentUrl">@asset.Value</span> 362 <span class="visually-hidden" itemprop="thumbnailUrl">@asset.Value</span> 363 @if (type != "selfhosted") 364 { 365 <div id="player_@(Pageview.CurrentParagraph.ID)_@(videoId)" 366 class="plyr__video-embed" 367 data-plyr-provider="@(type)" 368 data-plyr-embed-id="@videoId" 369 style="--plyr-color-main: var(--swift-foreground-color); height: 100%"> 370 </div> 371 372 <script type="module" src="~/Files/Templates/Designs/Swift/Assets/js/plyr.js"></script> 373 <script type="module"> 374 var player = new Plyr('#player_@(Pageview.CurrentParagraph.ID)_@(videoId)', { 375 type: 'video', 376 youtube: { 377 noCookie: true, 378 showinfo: 0 379 }, 380 fullscreen: { 381 enabled: true, 382 iosNative: true, 383 } 384 }); 385 386 document.querySelectorAll('.js-video-modal').forEach(function (modal) { 387 modal.addEventListener('hidden.bs.modal', function (event) { 388 player.media.pause(); 389 }) 390 }); 391 </script> 392 } 393 else 394 { 395 string videoType = Path.GetExtension(assetValue).ToLower(); 396 397 <video preload="auto" class="h-100 w-100" style="object-fit: cover;" controls> 398 <source src="@assetValue" type="video/@videoType.Replace(".", "")"> 399 </video> 400 } 401 </div> 402 } 403
Error executing template "Designs/Swift/Paragraph/Swift_ProductDetailsMediaTable.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_e2afa107fd814b61adb7f711fa214d86.Execute() in D:\dynamicweb.net\Solutions\Dynamicweb\bomedys.staging.dynamicweb-cms.com\files\Templates\Designs\Swift\Paragraph\Swift_ProductDetailsMediaTable.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 int totalAssets = 0; 70 if (showOnlyPrimaryImage == false) { 71 foreach (MediaViewModel asset in assetsList) { 72 var assetValue = asset.Value.ToLower(); 73 foreach (string format in allSupportedFormats) { 74 if (assetValue.Contains(format) ) { 75 totalAssets++; 76 } 77 } 78 } 79 } 80 81 if((totalAssets == 0 && product.DefaultImage != null && selectedAssetCategories.Count() == 0) || (showOnlyPrimaryImage == true && product.DefaultImage != null)) 82 { 83 assetsList = new List<MediaViewModel>(){ product.DefaultImage }; 84 totalAssets = 1; 85 } 86 87 int videoNumber = 0; 88 89 @* Layout settings *@ 90 string spacing = Model.Item.GetRawValueString("Spacing", "p-0"); 91 spacing = spacing == "none" ? "p-0" : spacing; 92 spacing = spacing == "small" ? "p-3" : spacing; 93 spacing = spacing == "large" ? "p-5" : spacing; 94 95 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 96 97 bool hideThumbnails = Model.Item.GetBoolean("HideThumbnails"); 98 99 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 100 } 101 102 @* Get assets from selected categories or get all assets *@ 103 104 105 <div class="@spacing@(theme) item_@Model.Item.SystemName.ToLower()"> 106 @if (totalAssets != 0 && assetsList.Count() != 0) 107 { 108 if (!string.IsNullOrEmpty(Model.Item.GetString("Title")) && !Model.Item.GetBoolean("HideTitle")) 109 { 110 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "h3"); 111 112 <h3 class="@titleFontSize mb-3"> 113 @Model.Item.GetString("Title") 114 </h3> 115 } 116 117 <div class="table-responsive"> 118 <table class="table table-hover align-middle mb-0" style="table-layout: fixed;"> 119 <thead> 120 <tr> 121 @if (!hideThumbnails) 122 { 123 <th style="width:60px"> </th> 124 } 125 <th>@Translate("Name")</th> 126 <th class="text-end d-none d-lg-table-cell">@Translate("Download")</th> 127 <th class="text-end" style="width:100px">@Translate("File type")</th> 128 </tr> 129 </thead> 130 <tbody class="border-top-0"> 131 @foreach (MediaViewModel asset in assetsList) 132 { 133 var assetValue = asset.Value.ToLower(); 134 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Value.Substring( asset.Value.LastIndexOf('/') + 1); 135 136 bool isVideo = false; 137 foreach (string format in supportedVideoFormats) 138 { //Videos 139 if (assetValue.Contains(format)) 140 { 141 isVideo = true; 142 } 143 } 144 145 if (!isVideo) 146 { 147 string filePath = Dynamicweb.Context.Current.Server.MapPath(assetValue); 148 long fileSize = 0; 149 150 if (File.Exists(filePath)) { 151 fileSize = new System.IO.FileInfo(filePath) != null ? new System.IO.FileInfo(filePath).Length / 1024 : 0; 152 153 foreach (string format in allSupportedFormats) 154 { 155 if (assetValue.Contains(format)) 156 { 157 <tr class="position-relative"> 158 @if (!hideThumbnails) 159 { 160 @RenderAsset(asset) 161 } 162 <td> 163 <a href="@assetValue" class="text-decoration-none text-break" download title="@assetName"> 164 @assetName 165 </a> 166 </td> 167 <td class="text-end d-none d-lg-table-cell"> 168 <a href="@assetValue" class="text-decoration-none stretched-link" download title="@assetName"> 169 @fileSize KB <div class="icon-2" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 170 </a> 171 </td> 172 <td class="text-end">@format</td> 173 </tr> 174 } 175 } 176 } 177 } 178 else 179 { 180 string videoType = asset.Value.Contains("youtu.be") || asset.Value.Contains("youtube") ? "Youtube" : ""; 181 videoType = asset.Value.Contains("vimeo") ? "Vimeo" : videoType; 182 183 <tr data-bs-toggle="modal" data-bs-target="#modal_@(Model.ID)_@videoNumber" style="cursor: pointer"> 184 @if (!hideThumbnails) 185 { 186 @RenderAsset(asset) 187 } 188 <td> 189 @assetName 190 </td> 191 <td class="d-none d-lg-table-cell"> </td> 192 <td align="right">@videoType</td> 193 </tr> 194 195 videoNumber++; 196 } 197 } 198 </tbody> 199 </table> 200 </div> 201 202 int modalVideoNumber = 0; 203 foreach (MediaViewModel asset in assetsList) 204 { 205 var assetName = asset.Value.ToLower(); 206 207 foreach (string format in supportedVideoFormats) 208 { //Videos 209 if (assetName.Contains(format)) 210 { 211 <div class="modal fade js-video-modal" id="modal_@(Model.ID)_@modalVideoNumber" tabindex="-1" aria-labelledby="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber" aria-hidden="true"> 212 <div class="modal-dialog modal-dialog-centered modal-xl"> 213 <div class="modal-content"> 214 <div class="modal-header visually-hidden"> 215 <h5 class="modal-title" id="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber">@product.Title</h5> 216 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 217 </div> 218 <div class="modal-body p-2 p-lg-3 h-100"> 219 @{ @RenderVideoPlayer(asset) } 220 </div> 221 </div> 222 </div> 223 </div> 224 225 modalVideoNumber++; 226 } 227 } 228 } 229 } 230 else if (Pageview.IsVisualEditorMode) 231 { 232 RatioSettings ratioSettings = GetRatioSettings(); 233 234 <div class="h-100 @theme"> 235 <div class="alert alert-dark m-0"> 236 @Translate("No assets are available") 237 </div> 238 </div> 239 } 240 241 </div> 242 243 @helper RenderAsset(MediaViewModel asset) 244 { 245 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 246 string assetValue = asset.Value; 247 248 <td class="@(theme) px-0"> 249 @foreach (string format in supportedImageFormats) 250 { //Images 251 if (assetValue.Contains(format)) 252 { 253 @RenderImage(asset) 254 } 255 } 256 @foreach (string format in supportedVideoFormats) 257 { //Videos 258 if (assetValue.Contains(format)) 259 { 260 @RenderVideoScreendump(asset) 261 } 262 } 263 @foreach (string format in supportedDocumentFormats) 264 { //Documents 265 if (assetValue.Contains(format)) 266 { 267 @RenderDocument(asset) 268 } 269 } 270 </td> 271 } 272 273 @helper RenderImage(MediaViewModel asset) 274 { 275 string productName = product.Name; 276 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 277 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 278 string imageLinkPath = imagePath; 279 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 280 281 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 282 283 RatioSettings ratioSettings = GetRatioSettings(); 284 285 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download> 286 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 287 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle itemprop="image"> 288 </div> 289 </a> 290 } 291 292 @helper RenderVideoScreendump(MediaViewModel asset) 293 { 294 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 295 296 string videoScreendumpPath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : ""; 297 string videoId = videoScreendumpPath.Substring(videoScreendumpPath.LastIndexOf('/') + 1); 298 videoScreendumpPath = videoScreendumpPath.Contains("youtu.be") || videoScreendumpPath.Contains("youtube") ? "https://img.youtube.com/vi/" + videoId + "/mqdefault.jpg" : videoScreendumpPath; 299 300 string vimeoJsClass = videoScreendumpPath.Contains("vimeo") ? "js-vimeo-video-thumbnail" : ""; 301 videoScreendumpPath = videoScreendumpPath.Contains("vimeo") ? "" : videoScreendumpPath; 302 303 string productName = product.Name; 304 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 305 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 306 307 RatioSettings ratioSettings = GetRatioSettings(); 308 309 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 310 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 311 <div class="icon-2 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 312 @if (!videoScreendumpPath.Contains(".mp4")) { 313 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@productName" @assetTitle class="@vimeoJsClass mw-100 mh-100" data-video-id="@videoId" style="object-fit: cover;"> 314 } else { 315 string videoType = Path.GetExtension(asset.Value).ToLower(); 316 317 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 318 <source src="@asset.Value" type="video/@videoType.Replace(".", "")"> 319 </video> 320 } 321 </div> 322 </div> 323 } 324 325 @helper RenderDocument(MediaViewModel asset) 326 { 327 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 328 string productName = product.Name; 329 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 330 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 331 string imageLinkPath = imagePath; 332 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 333 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 334 335 RatioSettings ratioSettings = GetRatioSettings(); 336 337 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@productName"> 338 @if (asset.Value.Contains(".pdf")) { 339 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 340 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle /> 341 </div> 342 } else { 343 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 344 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 345 </div> 346 } 347 </a> 348 } 349 350 @helper RenderVideoPlayer(MediaViewModel asset) 351 { 352 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Name; 353 string assetValue = asset.Value; 354 string videoId = asset.Value.Substring(asset.Value.LastIndexOf('/') + 1); 355 string type = assetValue.Contains("youtu.be") || assetValue.Contains("youtube") ? "youtube" : ""; 356 type = assetValue.Contains("vimeo") ? "vimeo" : type; 357 type = assetValue.Contains(".mp4") || assetValue.Contains(".webm") ? "selfhosted" : type; 358 359 <div class="h-100" itemscope itemtype="https://schema.org/VideoObject"> 360 <span class="visually-hidden" itemprop="name">@assetName</span> 361 <span class="visually-hidden" itemprop="contentUrl">@asset.Value</span> 362 <span class="visually-hidden" itemprop="thumbnailUrl">@asset.Value</span> 363 @if (type != "selfhosted") 364 { 365 <div id="player_@(Pageview.CurrentParagraph.ID)_@(videoId)" 366 class="plyr__video-embed" 367 data-plyr-provider="@(type)" 368 data-plyr-embed-id="@videoId" 369 style="--plyr-color-main: var(--swift-foreground-color); height: 100%"> 370 </div> 371 372 <script type="module" src="~/Files/Templates/Designs/Swift/Assets/js/plyr.js"></script> 373 <script type="module"> 374 var player = new Plyr('#player_@(Pageview.CurrentParagraph.ID)_@(videoId)', { 375 type: 'video', 376 youtube: { 377 noCookie: true, 378 showinfo: 0 379 }, 380 fullscreen: { 381 enabled: true, 382 iosNative: true, 383 } 384 }); 385 386 document.querySelectorAll('.js-video-modal').forEach(function (modal) { 387 modal.addEventListener('hidden.bs.modal', function (event) { 388 player.media.pause(); 389 }) 390 }); 391 </script> 392 } 393 else 394 { 395 string videoType = Path.GetExtension(assetValue).ToLower(); 396 397 <video preload="auto" class="h-100 w-100" style="object-fit: cover;" controls> 398 <source src="@assetValue" type="video/@videoType.Replace(".", "")"> 399 </video> 400 } 401 </div> 402 } 403
Error executing template "Designs/Swift/Paragraph/Swift_ProductDetailsMediaTable.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_e2afa107fd814b61adb7f711fa214d86.Execute() in D:\dynamicweb.net\Solutions\Dynamicweb\bomedys.staging.dynamicweb-cms.com\files\Templates\Designs\Swift\Paragraph\Swift_ProductDetailsMediaTable.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 int totalAssets = 0; 70 if (showOnlyPrimaryImage == false) { 71 foreach (MediaViewModel asset in assetsList) { 72 var assetValue = asset.Value.ToLower(); 73 foreach (string format in allSupportedFormats) { 74 if (assetValue.Contains(format) ) { 75 totalAssets++; 76 } 77 } 78 } 79 } 80 81 if((totalAssets == 0 && product.DefaultImage != null && selectedAssetCategories.Count() == 0) || (showOnlyPrimaryImage == true && product.DefaultImage != null)) 82 { 83 assetsList = new List<MediaViewModel>(){ product.DefaultImage }; 84 totalAssets = 1; 85 } 86 87 int videoNumber = 0; 88 89 @* Layout settings *@ 90 string spacing = Model.Item.GetRawValueString("Spacing", "p-0"); 91 spacing = spacing == "none" ? "p-0" : spacing; 92 spacing = spacing == "small" ? "p-3" : spacing; 93 spacing = spacing == "large" ? "p-5" : spacing; 94 95 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 96 97 bool hideThumbnails = Model.Item.GetBoolean("HideThumbnails"); 98 99 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 100 } 101 102 @* Get assets from selected categories or get all assets *@ 103 104 105 <div class="@spacing@(theme) item_@Model.Item.SystemName.ToLower()"> 106 @if (totalAssets != 0 && assetsList.Count() != 0) 107 { 108 if (!string.IsNullOrEmpty(Model.Item.GetString("Title")) && !Model.Item.GetBoolean("HideTitle")) 109 { 110 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "h3"); 111 112 <h3 class="@titleFontSize mb-3"> 113 @Model.Item.GetString("Title") 114 </h3> 115 } 116 117 <div class="table-responsive"> 118 <table class="table table-hover align-middle mb-0" style="table-layout: fixed;"> 119 <thead> 120 <tr> 121 @if (!hideThumbnails) 122 { 123 <th style="width:60px"> </th> 124 } 125 <th>@Translate("Name")</th> 126 <th class="text-end d-none d-lg-table-cell">@Translate("Download")</th> 127 <th class="text-end" style="width:100px">@Translate("File type")</th> 128 </tr> 129 </thead> 130 <tbody class="border-top-0"> 131 @foreach (MediaViewModel asset in assetsList) 132 { 133 var assetValue = asset.Value.ToLower(); 134 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Value.Substring( asset.Value.LastIndexOf('/') + 1); 135 136 bool isVideo = false; 137 foreach (string format in supportedVideoFormats) 138 { //Videos 139 if (assetValue.Contains(format)) 140 { 141 isVideo = true; 142 } 143 } 144 145 if (!isVideo) 146 { 147 string filePath = Dynamicweb.Context.Current.Server.MapPath(assetValue); 148 long fileSize = 0; 149 150 if (File.Exists(filePath)) { 151 fileSize = new System.IO.FileInfo(filePath) != null ? new System.IO.FileInfo(filePath).Length / 1024 : 0; 152 153 foreach (string format in allSupportedFormats) 154 { 155 if (assetValue.Contains(format)) 156 { 157 <tr class="position-relative"> 158 @if (!hideThumbnails) 159 { 160 @RenderAsset(asset) 161 } 162 <td> 163 <a href="@assetValue" class="text-decoration-none text-break" download title="@assetName"> 164 @assetName 165 </a> 166 </td> 167 <td class="text-end d-none d-lg-table-cell"> 168 <a href="@assetValue" class="text-decoration-none stretched-link" download title="@assetName"> 169 @fileSize KB <div class="icon-2" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 170 </a> 171 </td> 172 <td class="text-end">@format</td> 173 </tr> 174 } 175 } 176 } 177 } 178 else 179 { 180 string videoType = asset.Value.Contains("youtu.be") || asset.Value.Contains("youtube") ? "Youtube" : ""; 181 videoType = asset.Value.Contains("vimeo") ? "Vimeo" : videoType; 182 183 <tr data-bs-toggle="modal" data-bs-target="#modal_@(Model.ID)_@videoNumber" style="cursor: pointer"> 184 @if (!hideThumbnails) 185 { 186 @RenderAsset(asset) 187 } 188 <td> 189 @assetName 190 </td> 191 <td class="d-none d-lg-table-cell"> </td> 192 <td align="right">@videoType</td> 193 </tr> 194 195 videoNumber++; 196 } 197 } 198 </tbody> 199 </table> 200 </div> 201 202 int modalVideoNumber = 0; 203 foreach (MediaViewModel asset in assetsList) 204 { 205 var assetName = asset.Value.ToLower(); 206 207 foreach (string format in supportedVideoFormats) 208 { //Videos 209 if (assetName.Contains(format)) 210 { 211 <div class="modal fade js-video-modal" id="modal_@(Model.ID)_@modalVideoNumber" tabindex="-1" aria-labelledby="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber" aria-hidden="true"> 212 <div class="modal-dialog modal-dialog-centered modal-xl"> 213 <div class="modal-content"> 214 <div class="modal-header visually-hidden"> 215 <h5 class="modal-title" id="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber">@product.Title</h5> 216 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 217 </div> 218 <div class="modal-body p-2 p-lg-3 h-100"> 219 @{ @RenderVideoPlayer(asset) } 220 </div> 221 </div> 222 </div> 223 </div> 224 225 modalVideoNumber++; 226 } 227 } 228 } 229 } 230 else if (Pageview.IsVisualEditorMode) 231 { 232 RatioSettings ratioSettings = GetRatioSettings(); 233 234 <div class="h-100 @theme"> 235 <div class="alert alert-dark m-0"> 236 @Translate("No assets are available") 237 </div> 238 </div> 239 } 240 241 </div> 242 243 @helper RenderAsset(MediaViewModel asset) 244 { 245 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 246 string assetValue = asset.Value; 247 248 <td class="@(theme) px-0"> 249 @foreach (string format in supportedImageFormats) 250 { //Images 251 if (assetValue.Contains(format)) 252 { 253 @RenderImage(asset) 254 } 255 } 256 @foreach (string format in supportedVideoFormats) 257 { //Videos 258 if (assetValue.Contains(format)) 259 { 260 @RenderVideoScreendump(asset) 261 } 262 } 263 @foreach (string format in supportedDocumentFormats) 264 { //Documents 265 if (assetValue.Contains(format)) 266 { 267 @RenderDocument(asset) 268 } 269 } 270 </td> 271 } 272 273 @helper RenderImage(MediaViewModel asset) 274 { 275 string productName = product.Name; 276 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 277 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 278 string imageLinkPath = imagePath; 279 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 280 281 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 282 283 RatioSettings ratioSettings = GetRatioSettings(); 284 285 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download> 286 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 287 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle itemprop="image"> 288 </div> 289 </a> 290 } 291 292 @helper RenderVideoScreendump(MediaViewModel asset) 293 { 294 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 295 296 string videoScreendumpPath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : ""; 297 string videoId = videoScreendumpPath.Substring(videoScreendumpPath.LastIndexOf('/') + 1); 298 videoScreendumpPath = videoScreendumpPath.Contains("youtu.be") || videoScreendumpPath.Contains("youtube") ? "https://img.youtube.com/vi/" + videoId + "/mqdefault.jpg" : videoScreendumpPath; 299 300 string vimeoJsClass = videoScreendumpPath.Contains("vimeo") ? "js-vimeo-video-thumbnail" : ""; 301 videoScreendumpPath = videoScreendumpPath.Contains("vimeo") ? "" : videoScreendumpPath; 302 303 string productName = product.Name; 304 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 305 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 306 307 RatioSettings ratioSettings = GetRatioSettings(); 308 309 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 310 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 311 <div class="icon-2 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 312 @if (!videoScreendumpPath.Contains(".mp4")) { 313 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@productName" @assetTitle class="@vimeoJsClass mw-100 mh-100" data-video-id="@videoId" style="object-fit: cover;"> 314 } else { 315 string videoType = Path.GetExtension(asset.Value).ToLower(); 316 317 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 318 <source src="@asset.Value" type="video/@videoType.Replace(".", "")"> 319 </video> 320 } 321 </div> 322 </div> 323 } 324 325 @helper RenderDocument(MediaViewModel asset) 326 { 327 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 328 string productName = product.Name; 329 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 330 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 331 string imageLinkPath = imagePath; 332 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 333 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 334 335 RatioSettings ratioSettings = GetRatioSettings(); 336 337 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@productName"> 338 @if (asset.Value.Contains(".pdf")) { 339 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 340 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle /> 341 </div> 342 } else { 343 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 344 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 345 </div> 346 } 347 </a> 348 } 349 350 @helper RenderVideoPlayer(MediaViewModel asset) 351 { 352 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Name; 353 string assetValue = asset.Value; 354 string videoId = asset.Value.Substring(asset.Value.LastIndexOf('/') + 1); 355 string type = assetValue.Contains("youtu.be") || assetValue.Contains("youtube") ? "youtube" : ""; 356 type = assetValue.Contains("vimeo") ? "vimeo" : type; 357 type = assetValue.Contains(".mp4") || assetValue.Contains(".webm") ? "selfhosted" : type; 358 359 <div class="h-100" itemscope itemtype="https://schema.org/VideoObject"> 360 <span class="visually-hidden" itemprop="name">@assetName</span> 361 <span class="visually-hidden" itemprop="contentUrl">@asset.Value</span> 362 <span class="visually-hidden" itemprop="thumbnailUrl">@asset.Value</span> 363 @if (type != "selfhosted") 364 { 365 <div id="player_@(Pageview.CurrentParagraph.ID)_@(videoId)" 366 class="plyr__video-embed" 367 data-plyr-provider="@(type)" 368 data-plyr-embed-id="@videoId" 369 style="--plyr-color-main: var(--swift-foreground-color); height: 100%"> 370 </div> 371 372 <script type="module" src="~/Files/Templates/Designs/Swift/Assets/js/plyr.js"></script> 373 <script type="module"> 374 var player = new Plyr('#player_@(Pageview.CurrentParagraph.ID)_@(videoId)', { 375 type: 'video', 376 youtube: { 377 noCookie: true, 378 showinfo: 0 379 }, 380 fullscreen: { 381 enabled: true, 382 iosNative: true, 383 } 384 }); 385 386 document.querySelectorAll('.js-video-modal').forEach(function (modal) { 387 modal.addEventListener('hidden.bs.modal', function (event) { 388 player.media.pause(); 389 }) 390 }); 391 </script> 392 } 393 else 394 { 395 string videoType = Path.GetExtension(assetValue).ToLower(); 396 397 <video preload="auto" class="h-100 w-100" style="object-fit: cover;" controls> 398 <source src="@assetValue" type="video/@videoType.Replace(".", "")"> 399 </video> 400 } 401 </div> 402 } 403