Component builder for: Product detail card

Error executing template "Designs/Swift/Paragraph/Swift_ProductDetailsGallery.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_05323ca0871143c38c591890d2581abd.Execute() in C:\Projects\Solutions\mountainbikemuseum.nl\Files\Templates\Designs\Swift\Paragraph\Swift_ProductDetailsGallery.cshtml:line 116
   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 galleryLayout { get; set; } 9 public string[] supportedImageFormats { get; set; } 10 public string[] supportedVideoFormats { get; set; } 11 public string[] supportedDocumentFormats { get; set; } 12 public string[] allSupportedFormats { get; set; } 13 14 public class RatioSettings 15 { 16 public string Ratio { get; set; } 17 public string CssClass { get; set; } 18 public string CssVariable { get; set; } 19 public string Fill { get; set; } 20 } 21 22 public RatioSettings GetRatioSettings(string size = "desktop") 23 { 24 var ratioSettings = new RatioSettings(); 25 26 string ratio = Model.Item.GetRawValueString("ImageAspectRatio", ""); 27 ratio = ratio != "0" ? ratio : ""; 28 string cssClass = ratio != "" && ratio != "fill" ? " ratio" : ""; 29 string cssVariable = ratio != "" && ratio != "fill" ? "--bs-aspect-ratio: " + ratio : ""; 30 cssClass = ratio != "" && ratio == "fill" && size == "mobile" ? " ratio" : cssClass; 31 cssVariable = ratio != "" && ratio == "fill" && size == "mobile" ? "--bs-aspect-ratio: 66%" : cssVariable; 32 33 ratioSettings.Ratio = ratio; 34 ratioSettings.CssClass = cssClass; 35 ratioSettings.CssVariable = cssVariable; 36 ratioSettings.Fill = ratio == "fill" ? " h-100" : ""; 37 38 return ratioSettings; 39 } 40 41 public string GetColumnClass(int total, int assetNumber) 42 { 43 string colClass = total > 1 ? "g-col-lg-6" : "g-col-12"; 44 colClass = galleryLayout == "full-first" && assetNumber == 0 ? "g-col-12" : colClass; 45 colClass = galleryLayout == "full-last" && assetNumber == (total - 1) ? "g-col-12" : colClass; 46 colClass = galleryLayout == "advanced-grid" && assetNumber > 1 ? "g-col-4" : colClass; 47 48 colClass = galleryLayout == "advanced-grid" && total == 1 ? "g-col-12" : colClass; 49 colClass = galleryLayout == "advanced-grid" && total == 3 && assetNumber == 2 ? "g-col-12" : colClass; 50 colClass = galleryLayout == "advanced-grid" && total == 4 && assetNumber == 2 ? "g-col-6" : colClass; 51 colClass = galleryLayout == "advanced-grid" && total == 4 && assetNumber == 3 ? "g-col-6" : colClass; 52 colClass = galleryLayout == "advanced-grid" && total == 6 && assetNumber == 5 ? "g-col-12" : colClass; 53 colClass = galleryLayout == "advanced-grid" && total == 7 && assetNumber == 5 ? "g-col-6" : colClass; 54 colClass = galleryLayout == "advanced-grid" && total == 7 && assetNumber == 6 ? "g-col-6" : colClass; 55 colClass = galleryLayout == "advanced-grid" && total == 9 && assetNumber == 8 ? "g-col-12" : colClass; 56 57 return colClass; 58 } 59 60 public string GetArrowsColor() 61 { 62 var invertColor = Model.Item.GetBoolean("InvertModalArrowsColor"); 63 var arrowsColor = invertColor ? " carousel-dark" : string.Empty; 64 return arrowsColor; 65 } 66 } 67 68 @{ 69 @* Get the product data *@ 70 71 ProductViewModelSettings productSetting = new ProductViewModelSettings 72 { 73 LanguageId = Dynamicweb.Ecommerce.Common.Context.LanguageID, 74 CurrencyCode = Dynamicweb.Ecommerce.Common.Context.Currency.Code, 75 CountryCode = Dynamicweb.Ecommerce.Common.Context.Country.Code2, 76 ShopId = Pageview.Area.EcomShopId 77 }; 78 79 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 80 { 81 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 82 } 83 else if (Pageview.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 84 { 85 string dummyProductId = ""; 86 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 87 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 88 if (productList.Products != null) 89 { 90 foreach (var p in productList.Products) { dummyProductId = p.Id; } 91 ProductViewModel dummyProduct = dummyProductId != "" ? ViewModelFactory.CreateView(productSetting, dummyProductId) : new ProductViewModel(); 92 product = dummyProduct; 93 } 94 else 95 { 96 product = ViewModelFactory.CreateView(productSetting, Dynamicweb.Ecommerce.Services.Products.GetLastActiveProducts(1, Dynamicweb.Ecommerce.Common.Context.LanguageID, false).FirstOrDefault().Id); 97 } 98 } 99 else if (Pageview.Item["DummyProduct"] == null) 100 { 101 product = ViewModelFactory.CreateView(productSetting, Dynamicweb.Ecommerce.Services.Products.GetLastActiveProducts(1, Dynamicweb.Ecommerce.Common.Context.LanguageID, false).FirstOrDefault().Id); 102 } 103 104 @* Supported formats *@ 105 supportedImageFormats = new string[] { ".jpg", ".jpeg", ".webp", ".png", ".gif", ".bmp", ".tiff" }; 106 supportedVideoFormats = new string[] { "youtu.be", "youtube", "vimeo", ".mp4", ".webm" }; 107 supportedDocumentFormats = new string[] { ".pdf", ".docx", ".xlsx", ".ppt", "pptx" }; 108 allSupportedFormats = supportedImageFormats.Concat(supportedVideoFormats).Concat(supportedDocumentFormats).ToArray(); 109 110 @* Collect the assets *@ 111 var selectedAssetCategories = Model.Item.GetRawValueString("ImageAssets").Split(',').ToList(); 112 bool includeImagePatternImages = Model.Item.GetBoolean("ImagePatternImages"); 113 114 @* Needed image data collection to support both DefaultImage, ImagePatterns and Image Assets *@ 115 string defaultImage = product.DefaultImage != null ? product.DefaultImage.Value : ""; 116 IEnumerable<MediaViewModel> assetsImages = product.AssetCategories.Where(x => selectedAssetCategories.Contains(x.SystemName)).SelectMany(x => x.Assets); 117 assetsImages = assetsImages.OrderByDescending(x => x.Value.Equals(defaultImage)); 118 IEnumerable<MediaViewModel> assetsList = new MediaViewModel[] { }; 119 assetsList = assetsList.Union(assetsImages); 120 assetsList = includeImagePatternImages ? assetsList.Union(product.ImagePatternImages) : assetsList; 121 assetsList = includeImagePatternImages && assetsList.Count() == 0 ? assetsList.Append(product.DefaultImage) : assetsList; 122 123 bool defaultImageFallback = Model.Item.GetBoolean("DefaultImageFallback"); 124 125 int totalAssets = 0; 126 foreach (MediaViewModel asset in assetsList) 127 { 128 var assetValue = asset.Value.ToLower(); 129 foreach (string format in allSupportedFormats) 130 { 131 if (assetValue.Contains(format)) 132 { 133 totalAssets++; 134 } 135 } 136 } 137 138 if (totalAssets == 0) 139 { 140 if (defaultImageFallback) 141 { 142 assetsList = new List<MediaViewModel>() { product.DefaultImage }; 143 totalAssets = 1; 144 } 145 else 146 { 147 assetsList = new List<MediaViewModel>() { }; 148 totalAssets = 0; 149 } 150 } 151 152 @* Layout settings *@ 153 string spacing = Model.Item.GetRawValueString("Spacing", "4"); 154 spacing = spacing == "none" ? "gap-0" : spacing; 155 spacing = spacing == "small" ? "gap-3" : spacing; 156 spacing = spacing == "large" ? "gap-4" : spacing; 157 158 galleryLayout = Model.Item.GetRawValueString("Layout", "grid"); 159 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 160 161 var badgeParms = new Dictionary<string, object>(); 162 badgeParms.Add("size", "h5"); 163 badgeParms.Add("saleBadgeType", Model.Item.GetRawValue("SaleBadgeType")); 164 badgeParms.Add("saleBadgeCssClassName", Model.Item.GetRawValue("SaleBadgeDesign")); 165 badgeParms.Add("newBadgeCssClassName", Model.Item.GetRawValue("NewBadgeDesign")); 166 badgeParms.Add("newPublicationDays", Model.Item.GetInt32("NewPublicationDays")); 167 badgeParms.Add("campaignBadgesValues", Model.Item.GetRawValueString("CampaignBadges")); 168 169 bool saleBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("SaleBadgeDesign")) && Model.Item.GetRawValueString("SaleBadgeDesign") != "none" ? true : false; 170 bool newBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("NewBadgeDesign")) && Model.Item.GetRawValueString("NewBadgeDesign") != "none" ? true : false; 171 DateTime createdDate = product.Created.Value; 172 bool showBadges = saleBadgeEnabled && product.Discount.Price != 0 ? true : false; 173 showBadges = (newBadgeEnabled && Model.Item.GetInt32("NewPublicationDays") == 0) || (newBadgeEnabled && (createdDate.AddDays(Model.Item.GetInt32("NewPublicationDays")) > DateTime.Now)) ? true : showBadges; 174 showBadges = !string.IsNullOrEmpty(Model.Item.GetRawValueString("CampaignBadges")) ? true : showBadges; 175 } 176 177 @* Get assets from selected categories or get all assets *@ 178 @if (totalAssets != 0 && assetsList.Count() != 0) 179 { 180 int desktopAssetNumber = 0; 181 int mobileAssetNumber = 0; 182 int mobileAssetThumbnailNumber = 0; 183 int modalAssetNumber = 0; 184 185 @* Desktop: Show the gallery on large screens *@ 186 <div class="d-none d-lg-block h-100 position-relative @theme item_@Model.Item.SystemName.ToLower() desktop"> 187 <div class="grid @spacing"> 188 @foreach (MediaViewModel asset in assetsList) 189 { 190 var assetName = asset.Value.ToLower(); 191 foreach (string format in allSupportedFormats) 192 { 193 if (assetName.Contains(format)) 194 { 195 <div class="@GetColumnClass(totalAssets, desktopAssetNumber)"> 196 @{@RenderAsset(asset, desktopAssetNumber)} 197 </div> 198 desktopAssetNumber++; 199 } 200 } 201 } 202 </div> 203 204 @if (showBadges) 205 { 206 <div class="position-absolute top-0 left-0 p-2 p-lg-3 w-100"> 207 @RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms) 208 </div> 209 } 210 </div> 211 212 @* Mobile: Show the thumbs on small screens *@ 213 <div class="d-block d-lg-none mx-lg-0 position-relative @theme item_@Model.Item.SystemName.ToLower() mobile"> 214 <div id="SmallScreenImages_@Model.ID" class="carousel@(GetArrowsColor())" data-bs-ride="carousel"> 215 <div class="carousel-inner h-100"> 216 @foreach (MediaViewModel asset in assetsList) 217 { 218 var assetValue = asset.Value.ToLower(); 219 foreach (string format in allSupportedFormats) 220 { 221 if (assetValue.Contains(format)) 222 { 223 string activeSlide = mobileAssetNumber == 0 ? "active" : ""; 224 225 <div class="carousel-item @activeSlide" data-bs-interval="99999"> 226 @{@RenderAsset(asset, mobileAssetNumber, "mobile")} 227 </div> 228 mobileAssetNumber++; 229 } 230 } 231 } 232 </div> 233 </div> 234 235 @if (totalAssets > 1) 236 { 237 <div id="SmallScreenImagesThumbnails_@Model.ID" class="d-flex flex-nowrap gap-2 overflow-x-auto my-3"> 238 @foreach (MediaViewModel asset in assetsList) 239 { 240 var assetValue = asset.Value; 241 foreach (string format in allSupportedFormats) 242 { 243 if (assetValue.Contains(format)) 244 { 245 string imagePath = Dynamicweb.Context.Current.Server.UrlEncode(assetValue); 246 imagePath = assetValue.Contains("youtu.be") || assetValue.Contains("youtube") ? "https://img.youtube.com/vi/" + assetValue.Substring(assetValue.LastIndexOf('/') + 1) + "/default.jpg" : imagePath; 247 string imagePathThumb = !imagePath.Contains("youtube") ? $"/Admin/Public/GetImage.ashx?image={imagePath}&width=160&format=webp" : imagePath; 248 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 249 250 string videoId = assetValue.Substring(assetValue.LastIndexOf('/') + 1); 251 string vimeoJsClass = assetValue.Contains("vimeo") ? "js-vimeo-video-thumbnail" : ""; 252 253 string productName = product.Name; 254 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 255 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 256 257 <div class="ratio ratio-4x3 border outline-none" style="flex:0 0 80px" data-bs-target="#SmallScreenImages_@Model.ID" data-bs-slide-to="@mobileAssetThumbnailNumber"> 258 @foreach (string videoFormat in supportedVideoFormats) 259 { 260 if (assetValue.Contains(videoFormat)) 261 { 262 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 263 <div class="icon-3 position-absolute text-light" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 264 </div> 265 } 266 } 267 <img src="@imagePathThumb" class="p-1 @vimeoJsClass mw-100 mh-100" data-video-id="@videoId" style="object-fit: cover;" alt="@productName" @assetTitle> 268 </div> 269 270 mobileAssetThumbnailNumber++; 271 } 272 } 273 } 274 </div> 275 } 276 277 @if (showBadges) 278 { 279 <div class="position-absolute top-0 left-0 p-2 p-lg-3"> 280 @RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms) 281 </div> 282 } 283 </div> 284 285 @* Modal with slides *@ 286 <div class="modal fade swift_products-details-images-modal" id="modal_@Model.ID" tabindex="-1" aria-labelledby="productDetailsGalleryModalTitle_@Model.ID" aria-hidden="true"> 287 <div class="modal-dialog modal-dialog-centered modal-xl"> 288 <div class="modal-content"> 289 <div class="modal-header visually-hidden"> 290 <h5 class="modal-title" id="productDetailsGalleryModalTitle_@Model.ID">@product.Title</h5> 291 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 292 </div> 293 <div class="modal-body p-2 p-lg-3 h-100"> 294 <div id="ModalCarousel_@Model.ID" class="carousel@(GetArrowsColor()) h-100" data-bs-ride="carousel"> 295 <div class="carousel-inner h-100 @theme"> 296 @foreach (MediaViewModel asset in assetsList) 297 { 298 var assetValue = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 299 foreach (string format in allSupportedFormats) 300 { 301 if (assetValue.Contains(format)) 302 { 303 string imagePath = assetValue; 304 string activeSlide = modalAssetNumber == 0 ? "active" : ""; 305 306 var parms = new Dictionary<string, object>(); 307 parms.Add("cssClass", "d-block mw-100 mh-100 m-auto"); 308 parms.Add("fullwidth", true); 309 parms.Add("columns", Model.GridRowColumnCount); 310 311 <div class="carousel-item @activeSlide h-100" data-bs-interval="99999"> 312 @foreach (string imageFormat in supportedImageFormats) 313 { 314 if (assetValue.Contains(imageFormat)) 315 { 316 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 317 } 318 } 319 320 @foreach (string videoFormat in supportedVideoFormats) 321 { 322 if (assetValue.Contains(videoFormat)) 323 { 324 {@RenderVideoPlayer(asset, "modal")} 325 } 326 } 327 </div> 328 329 modalAssetNumber++; 330 } 331 } 332 } 333 <button class="carousel-control-prev" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="prev"> 334 <span class="carousel-control-prev-icon" aria-hidden="true"></span> 335 <span class="visually-hidden">@Translate("Previous")</span> 336 </button> 337 <button class="carousel-control-next" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="next"> 338 <span class="carousel-control-next-icon" aria-hidden="true"></span> 339 <span class="visually-hidden">@Translate("Next")</span> 340 </button> 341 </div> 342 </div> 343 </div> 344 </div> 345 </div> 346 </div> 347 } 348 else if (Pageview.IsVisualEditorMode) 349 { 350 RatioSettings ratioSettings = GetRatioSettings("desktop"); 351 352 <div class="h-100 @theme"> 353 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 354 <img src="/Files/Images/missing_image.jpg" loading="lazy" decoding="async" class="mh-100 mw-100" style="object-fit: cover;" alt="@Translate("Missing image")"> 355 </div> 356 </div> 357 } 358 359 @helper RenderAsset(MediaViewModel asset, int assetNumber, string size = "desktop") 360 { 361 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 362 string assetValue = asset.Value; 363 364 <div class="h-100 @(theme)"> 365 @foreach (string format in supportedImageFormats) 366 { //Images 367 if (assetValue.Contains(format)) 368 { 369 {@RenderImage(asset, assetNumber, size)} 370 } 371 } 372 @foreach (string format in supportedVideoFormats) 373 { //Videos 374 if (assetValue.Contains(format)) 375 { 376 if (Model.Item.GetString("OpenVideoInModal") == "true") 377 { 378 {@RenderVideoScreendump(asset, assetNumber, size)} 379 } 380 else 381 { 382 {@RenderVideoPlayer(asset, size)} 383 } 384 } 385 } 386 @foreach (string format in supportedDocumentFormats) 387 { //Documents 388 if (assetValue.Contains(format)) 389 { 390 {@RenderDocument(asset, assetNumber, size)} 391 } 392 } 393 </div> 394 } 395 396 @helper RenderImage(MediaViewModel asset, int number, string size = "desktop") 397 { 398 string productName = product.Name; 399 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 400 string imageLinkPath = Dynamicweb.Context.Current.Server.UrlEncode(imagePath); 401 402 RatioSettings ratioSettings = GetRatioSettings(size); 403 404 var parms = new Dictionary<string, object>(); 405 parms.Add("alt", productName); 406 parms.Add("itemprop", "image"); 407 parms.Add("fullwidth", true); 408 parms.Add("columns", Model.GridRowColumnCount); 409 if (!string.IsNullOrEmpty(asset.DisplayName)) 410 { 411 parms.Add("title", asset.DisplayName); 412 } 413 414 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") 415 { 416 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); 417 } 418 else 419 { 420 parms.Add("cssClass", "mw-100 mh-100"); 421 } 422 423 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> 424 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide-to="@number"> 425 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 426 </div> 427 </a> 428 } 429 430 @helper RenderVideoScreendump(MediaViewModel asset, int number, string size = "desktop") 431 { 432 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 433 434 string videoScreendumpPath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : ""; 435 string videoId = videoScreendumpPath.Substring(videoScreendumpPath.LastIndexOf('/') + 1); 436 videoScreendumpPath = videoScreendumpPath.Contains("youtu.be") || videoScreendumpPath.Contains("youtube") ? "https://img.youtube.com/vi/" + videoId + "/maxresdefault.jpg" : videoScreendumpPath; 437 438 string vimeoJsClass = videoScreendumpPath.Contains("vimeo") ? "js-vimeo-video-thumbnail" : ""; 439 videoScreendumpPath = videoScreendumpPath.Contains("vimeo") ? "" : videoScreendumpPath; 440 441 string productName = product.Name; 442 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 443 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 444 445 RatioSettings ratioSettings = GetRatioSettings(size); 446 447 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable); cursor: pointer" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> 448 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide-to="@number"> 449 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 450 @if (!videoScreendumpPath.Contains(".mp4")) 451 { 452 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@productName" @assetTitle class="@vimeoJsClass mw-100 mh-100" data-video-id="@videoId" style="object-fit: cover;" onload="CheckIfVideoThumbnailExist(this)"> 453 } 454 else 455 { 456 string videoType = Path.GetExtension(asset.Value).ToLower(); 457 458 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 459 <source src="@asset.Value" type="video/@videoType.Replace(".", "")"> 460 </video> 461 } 462 </div> 463 </div> 464 465 <script> 466 function CheckIfVideoThumbnailExist(image) { 467 if (image.width == 120) { 468 const lowQualityImage = "https://img.youtube.com/vi/@(videoId)/hqdefault.jpg" 469 image.src = lowQualityImage; 470 } 471 } 472 </script> 473 } 474 475 @helper RenderVideoPlayer(MediaViewModel asset, string size = "desktop") 476 { 477 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Name; 478 string assetValue = asset.Value; 479 string videoId = asset.Value.Substring(asset.Value.LastIndexOf('/') + 1); 480 string type = assetValue.Contains("youtu.be") || assetValue.Contains("youtube") ? "youtube" : ""; 481 type = assetValue.Contains("vimeo") ? "vimeo" : type; 482 type = assetValue.Contains(".mp4") || assetValue.Contains(".webm") ? "selfhosted" : type; 483 484 string openInModal = Model.Item.GetString("OpenVideoInModal"); 485 bool autoPlay = Model.Item.GetBoolean("VideoAutoPlay"); 486 487 <div class="h-100" itemscope itemtype="https://schema.org/VideoObject"> 488 <span class="visually-hidden" itemprop="name">@assetName</span> 489 <span class="visually-hidden" itemprop="contentUrl">@asset.Value</span> 490 <span class="visually-hidden" itemprop="thumbnailUrl">@asset.Value</span> 491 @if (type != "selfhosted") 492 { 493 <div id="player_@(Pageview.CurrentParagraph.ID)_@(videoId)_@size" 494 class="plyr__video-embed" 495 data-plyr-provider="@(type)" 496 data-plyr-embed-id="@videoId" 497 style="--plyr-color-main: var(--swift-foreground-color); height: 100%"> 498 </div> 499 500 <script type="module" src="/Files/Templates/Designs/Swift/Assets/js/plyr.js"></script> 501 502 <script type="module"> 503 var player = new Plyr('#player_@(Pageview.CurrentParagraph.ID)_@(videoId)_@size', { 504 type: 'video', 505 youtube: { 506 noCookie: true, 507 showinfo: 0 508 }, 509 fullscreen: { 510 enabled: true, 511 iosNative: true, 512 } 513 }); 514 515 @if (autoPlay && openInModal == "false") { 516 <text> 517 player.config.autoplay = true; 518 player.config.muted = true; 519 player.config.volume = 0; 520 player.media.loop = true; 521 522 player.on('ready', function() { 523 if (player.config.autoplay === true) { 524 player.media.play(); 525 } 526 }); 527 </text> 528 } 529 530 @if (openInModal == "true") { 531 <text> 532 var productDetailsGalleryModal = document.querySelector('#modal_@Model.ID') 533 productDetailsGalleryModal.addEventListener('hidden.bs.modal', function (event) { 534 player.media.pause(); 535 }) 536 </text> 537 } 538 </script> 539 } 540 else 541 { 542 string autoPlayAttributes = (autoPlay && openInModal == "false") ? "loop autoplay muted playsinline" : ""; 543 string videoType = Path.GetExtension(assetValue).ToLower(); 544 545 <video preload="auto" @autoPlayAttributes class="h-100 w-100" style="object-fit: cover;" controls> 546 <source src="@assetValue" type="video/@videoType.Replace(".", "")"> 547 </video> 548 } 549 </div> 550 } 551 552 @helper RenderDocument(MediaViewModel asset, int number, string size = "desktop") 553 { 554 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 555 556 string productName = product.Name; 557 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 558 string imageLinkPath = imagePath; 559 560 RatioSettings ratioSettings = GetRatioSettings(size); 561 562 var parms = new Dictionary<string, object>(); 563 parms.Add("alt", productName); 564 parms.Add("itemprop", "image"); 565 parms.Add("fullwidth", true); 566 parms.Add("columns", Model.GridRowColumnCount); 567 if (!string.IsNullOrEmpty(asset.DisplayName)) 568 { 569 parms.Add("title", asset.DisplayName); 570 } 571 572 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") 573 { 574 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); 575 } 576 else 577 { 578 parms.Add("cssClass", "mw-100 mh-100"); 579 } 580 581 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download title="@Translate("Download")"> 582 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 583 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 584 @if (asset.Value.Contains(".pdf")) 585 { 586 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 587 } 588 else 589 { 590 591 } 592 </div> 593 </a> 594 } 595