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