Komponent editor til: Image + video gallery

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