Error executing template "Designs/Swift/eCom/ProductCatalog/ProductViewDetail.cshtml"
System.NullReferenceException: Object reference not set to an instance of an object.
   at CompiledRazorTemplates.Dynamic.RazorEngine_d46bb8039162406a989d4f13f7a1173b.ExecuteAsync()
   at RazorEngine.Templating.TemplateBase.Run(ExecuteContext context, TextWriter reader)
   at RazorEngine.Templating.RazorEngineCore.RunTemplate(ICompiledTemplate template, TextWriter writer, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.DynamicWrapperService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.Run(IRazorEngineService service, String name, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass23_0.<Run>b__0(TextWriter writer)
   at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter)
   at RazorEngine.Templating.RazorEngineServiceExtensions.Run(IRazorEngineService service, String name, Type modelType, Object model, DynamicViewBag viewBag)
   at Dynamicweb.Rendering.RazorTemplateRenderingProvider.Render(Template template)
   at Dynamicweb.Rendering.TemplateRenderingService.Render(Template template)
   at Dynamicweb.Rendering.Template.RenderRazorTemplate()

1 @inherits ViewModelTemplate<ProductViewModel> 2 @using Dynamicweb.Rendering 3 @using Dynamicweb.Ecommerce.ProductCatalog 4 @using Dynamicweb.Core 5 6 @{ 7 string metaDescription = string.IsNullOrEmpty(Model.MetaDescription) ? Model.Name : Model.MetaDescription; 8 9 Pageview.Meta.AddTag($"<meta property=\"og:image\" content=\"{Dynamicweb.Context.Current.Request.Url.Scheme}://{Dynamicweb.Context.Current.Request.Url.Host}{Model.DefaultImage.Value}\">"); 10 Pageview.Meta.AddTag($"<meta property=\"og:image:alt\" content=\"{Model.Name}\">"); 11 Pageview.Meta.AddTag($"<meta property=\"og:description\" content=\"{metaDescription}\">"); 12 13 Pageview.Meta.AddTag("twitter:image", Model.DefaultImage.Value); 14 Pageview.Meta.AddTag("twitter:image:alt", Model.Name); 15 Pageview.Meta.AddTag("twitter:description", metaDescription); 16 } 17 18 @{ 19 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 20 { 21 Dynamicweb.Context.Current.Items["ProductDetails"] = Model; 22 } 23 else 24 { 25 Dynamicweb.Context.Current.Items.Add("ProductDetails", Model); 26 } 27 28 bool isLazyLoadingForProductInfoEnabled = Dynamicweb.Core.Converter.ToBoolean(Dynamicweb.Context.Current.Items["IsLazyLoadingForProductInfoEnabled"]); 29 if (isLazyLoadingForProductInfoEnabled) 30 { 31 string showPricesWithVat = Pageview.Area.EcomPricesWithVat.ToLower(); 32 bool neverShowVat = string.IsNullOrEmpty(showPricesWithVat); 33 bool hasVariantId = !string.IsNullOrEmpty(Model.VariantId); 34 string variantIdParam = hasVariantId ? $"/{Model.VariantId}" : ""; 35 string priceFilledProperties = $"Price,PriceFormatted{(showPricesWithVat == "false" && !neverShowVat ? ",PriceWithVat,PriceWithVatFormatted" : "")}"; 36 string productInfoFeed = $@"/dwapi/ecommerce/products/{Model.Id}{variantIdParam} 37 ?UserId={Converter.ToString(Pageview.User?.ID)} 38 &LanguageId={Pageview.Area.EcomLanguageId}&CurrencyCode={Pageview.Area.EcomCurrencyId}&CountryCode={Pageview.Area.EcomCountryCode}&ShopId={Pageview.Area.EcomShopId} 39 &FilledProperties=Id,Price,PriceBeforeDiscount,StockLevel,VariantInfo,NeverOutOfstock,Prices 40 &PriceSettings.ShowPricesWithVat={Pageview.Area.EcomPricesWithVat} 41 &PriceSettings.FilledProperties={priceFilledProperties} 42 &getproductinfo=true"; 43 Dynamicweb.Context.Current.Items["ProductInfoFeed"] = productInfoFeed; 44 45 <script type="module"> 46 swift.LiveProductInfo.init(); 47 </script> 48 } 49 } 50 51 <script> 52 gtag("event", "view_item", { 53 currency: "@Model.Price.CurrencyCode", 54 value: @PriceViewModelExtensions.ToStringInvariant(Model.Price), 55 items: [ 56 { 57 item_id: "@Model.Number", 58 item_name: "@Dynamicweb.Core.Encoders.HtmlEncoder.JavaScriptStringEncode(Model.Name)", 59 currency: "@Model.Price.CurrencyCode", 60 price: @PriceViewModelExtensions.ToStringInvariant(Model.Price) 61 } 62 ] 63 }); 64 </script> 65 66 <script> 67 window.addEventListener('load', function (event) { 68 swift.Video.init(); 69 }); 70 </script> 71
Error executing template "Designs/Swift/Paragraph/Swift_ProductDetailsImage.cshtml"
System.ArgumentNullException: Value cannot be null. (Parameter 'source')
   at System.Linq.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument)
   at System.Linq.Enumerable.Where[TSource](IEnumerable`1 source, Func`2 predicate)
   at CompiledRazorTemplates.Dynamic.RazorEngine_eeffc1cca7ca464cae240f8bde4b96f6.ExecuteAsync()
   at RazorEngine.Templating.TemplateBase.Run(ExecuteContext context, TextWriter reader)
   at RazorEngine.Templating.RazorEngineCore.RunTemplate(ICompiledTemplate template, TextWriter writer, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.DynamicWrapperService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.Run(IRazorEngineService service, String name, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass23_0.<Run>b__0(TextWriter writer)
   at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter)
   at RazorEngine.Templating.RazorEngineServiceExtensions.Run(IRazorEngineService service, String name, Type modelType, Object model, DynamicViewBag viewBag)
   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 { 17 public string Ratio { get; set; } 18 public string CssClass { get; set; } 19 public string CssVariable { get; set; } 20 public string Fill { get; set; } 21 } 22 23 public RatioSettings GetRatioSettings(string size = "desktop") 24 { 25 var ratioSettings = new RatioSettings(); 26 27 string ratio = Model.Item.GetRawValueString("ImageAspectRatio", ""); 28 ratio = ratio != "0" ? ratio : ""; 29 string cssClass = ratio != "" && ratio != "fill" ? " ratio" : ""; 30 string cssVariable = ratio != "" && ratio != "fill" ? "--bs-aspect-ratio: " + ratio : ""; 31 cssClass = ratio == "fill" && size == "mobile" ? " ratio" : cssClass; 32 cssVariable = ratio == "fill" && size == "mobile" ? "--bs-aspect-ratio: 66%" : cssVariable; 33 34 ratioSettings.Ratio = ratio; 35 ratioSettings.CssClass = cssClass; 36 ratioSettings.CssVariable = cssVariable; 37 ratioSettings.Fill = ratio == "fill" ? " h-100" : ""; 38 39 return ratioSettings; 40 } 41 42 public string GetArrowsColor() 43 { 44 var invertColor = Model.Item.GetBoolean("InvertModalArrowsColor"); 45 var arrowsColor = invertColor ? " carousel-dark" : string.Empty; 46 return arrowsColor; 47 } 48 49 public string GetThumbnailPlacement() 50 { 51 return Model.Item.GetRawValueString("ThumbnailPlacement", "bottom"); 52 } 53 54 public string GetThumbnailRowSettingCss() 55 { 56 switch (GetThumbnailPlacement()) 57 { 58 case "bottom": 59 return "d-flex flex-wrap"; 60 case "left": 61 return "d-flex flex-column order-first"; 62 case "right": 63 return "d-flex flex-column order-last"; 64 default: 65 return "d-flex flex-wrap"; 66 } 67 } 68 69 public Dictionary<string, object> GetVideoParams(MediaViewModel asset, string size) 70 { 71 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Name; 72 string type = GetVideoType(asset.Value); 73 bool openInModal = Model.Item.GetString("OpenVideoInModal") == "true" ? true : false; 74 bool autoPlay = Model.Item.GetBoolean("VideoAutoPlay"); 75 76 var videoParams = new Dictionary<string, object>(); 77 videoParams.Add("AssetName", asset.Name); 78 videoParams.Add("AssetVideoType", type); 79 videoParams.Add("AssetDisplayName", asset.DisplayName); 80 videoParams.Add("OpenVideoInModal", openInModal); 81 videoParams.Add("VideoAutoPlay", autoPlay); 82 videoParams.Add("Size", size); 83 videoParams.Add("Id", Model.ID); 84 return videoParams; 85 86 } 87 88 public string GetVideoType(string assetValue) 89 { 90 string type = assetValue.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "youtube" : string.Empty; 91 type = assetValue.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "vimeo" : type; 92 type = string.IsNullOrEmpty(type) ? "selfhosted" : type; 93 94 return type; 95 } 96 97 public string GetYoutubeScreenDump(string assetValue, string quality) 98 { 99 var regex = new Regex(@"(?:youtube\.com\/.*[\?&]v=|youtu\.be\/|youtube\.com\/embed\/)([\w-]+)(?:\?.*)?"); 100 Match match = regex.Match(assetValue); 101 string videoId = match.Success ? match.Groups[1].Value : string.Empty; 102 string youtubeThumbnail = $"https://img.youtube.com/vi/{videoId}/{quality}.jpg"; 103 return youtubeThumbnail; 104 } 105 } 106 107 @{ 108 ProductViewModel product = null; 109 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 110 { 111 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 112 } 113 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 114 { 115 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 116 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 117 118 if (productList?.Products is object) 119 { 120 product = productList.Products[0]; 121 } 122 } 123 } 124 125 @if (product is object) 126 { 127 @* Supported formats *@ 128 supportedImageFormats = new string[] { ".jpg", ".jpeg", ".webp", ".png", ".gif", ".bmp", ".tiff" }; 129 supportedVideoFormats = new string[] { "youtu.be", "youtube", "vimeo", ".mp4", ".webm" }; 130 supportedDocumentFormats = new string[] { ".pdf", ".docx", ".xlsx", ".ppt", "pptx" }; 131 allSupportedFormats = supportedImageFormats.Concat(supportedVideoFormats).Concat(supportedDocumentFormats).ToArray(); 132 133 @* Collect the assets *@ 134 var selectedAssetCategories = Model.Item.GetList("ImageAssets")?.GetRawValue().OfType<string>(); 135 bool includeImagePatternImages = Model.Item.GetBoolean("ImagePatternImages"); 136 137 @* Needed image data collection to support both DefaultImage, ImagePatterns and Image Assets *@ 138 string defaultImage = product.DefaultImage != null ? product.DefaultImage.Value : ""; 139 IEnumerable<MediaViewModel> assetsImages = product.AssetCategories.Where(x => selectedAssetCategories.Contains(x.SystemName)).SelectMany(x => x.Assets); 140 assetsImages = assetsImages.OrderByDescending(x => x.Value.Equals(defaultImage)); 141 IEnumerable<MediaViewModel> assetsList = new MediaViewModel[] { }; 142 assetsList = assetsList.Union(assetsImages); 143 assetsList = includeImagePatternImages ? assetsList.Union(product.ImagePatternImages) : assetsList; 144 assetsList = includeImagePatternImages && assetsList.Count() == 0 ? assetsList.Append(product.DefaultImage) : assetsList; 145 146 bool defaultImageFallback = Model.Item.GetBoolean("DefaultImageFallback"); 147 bool showOnlyPrimaryImage = Model.Item.GetBoolean("ShowOnlyPrimaryImage"); 148 149 int totalAssets = 0; 150 if (showOnlyPrimaryImage == false) 151 { 152 foreach (MediaViewModel asset in assetsList) 153 { 154 var assetValue = asset.Value; 155 foreach (string format in allSupportedFormats) 156 { 157 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 158 { 159 totalAssets++; 160 } 161 } 162 } 163 } 164 165 if ((totalAssets == 0 && product.DefaultImage != null && selectedAssetCategories.Count() == 0) || (showOnlyPrimaryImage == true && product.DefaultImage != null) || totalAssets == 0 && defaultImageFallback) 166 { 167 assetsList = new List<MediaViewModel>() { product.DefaultImage }; 168 totalAssets = 1; 169 } 170 171 @* Theme settings *@ 172 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 173 174 var badgeParms = new Dictionary<string, object>(); 175 badgeParms.Add("size", "h5"); 176 badgeParms.Add("saleBadgeType", Model.Item.GetRawValue("SaleBadgeType")); 177 badgeParms.Add("saleBadgeCssClassName", Model.Item.GetRawValue("SaleBadgeDesign")); 178 badgeParms.Add("newBadgeCssClassName", Model.Item.GetRawValue("NewBadgeDesign")); 179 badgeParms.Add("newPublicationDays", Model.Item.GetInt32("NewPublicationDays")); 180 badgeParms.Add("campaignBadgesValues", Model.Item.GetList("CampaignBadges")?.GetRawValue().OfType<string>().ToList()); 181 182 bool saleBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("SaleBadgeDesign")) && Model.Item.GetRawValueString("SaleBadgeDesign") != "none" ? true : false; 183 bool newBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("NewBadgeDesign")) && Model.Item.GetRawValueString("NewBadgeDesign") != "none" ? true : false; 184 DateTime createdDate = product.Created.Value; 185 bool showBadges = saleBadgeEnabled && product.Discount.Price != 0 ? true : false; 186 showBadges = (newBadgeEnabled && Model.Item.GetInt32("NewPublicationDays") == 0) || (newBadgeEnabled && (createdDate.AddDays(Model.Item.GetInt32("NewPublicationDays")) > DateTime.Now)) ? true : showBadges; 187 showBadges = !string.IsNullOrEmpty(Model.Item.GetRawValueString("CampaignBadges")) ? true : showBadges; 188 189 @* Get assets from selected categories or get all assets *@ 190 if (totalAssets != 0) 191 { 192 int assetNumber = 0; 193 int thumbnailNumber = 0; 194 int modalAssetNumber = 0; 195 string thumbnailAxisCss = GetThumbnailPlacement() == "bottom" ? "flex-column" : string.Empty; 196 197 <div class="d-flex gap-3 h-100 @(thumbnailAxisCss) @(theme) item_@Model.Item.SystemName.ToLower()"> 198 <div id="SmallScreenImages_@Model.ID" class="carousel@(GetArrowsColor()) col position-relative" data-bs-ride="carousel"> 199 <div class="carousel-inner h-100"> 200 @foreach (MediaViewModel asset in assetsList) 201 { 202 var assetValue = asset.Value; 203 foreach (string format in allSupportedFormats) 204 { 205 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 206 { 207 string activeSlide = assetNumber == 0 ? "active" : ""; 208 209 <div class="carousel-item @activeSlide" data-bs-interval="99999"> 210 @{ 211 string size = "mobile"; 212 213 string imageTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 214 215 216 <div class="h-100 @(imageTheme)"> 217 @foreach (string imageFormat in supportedImageFormats) 218 { //Images 219 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0) 220 { 221 if (product is object) 222 { 223 string productName = product.Name; 224 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 225 string imageLinkPath = Dynamicweb.Context.Current.Server.UrlEncode(imagePath); 226 227 RatioSettings ratioSettings = GetRatioSettings(size); 228 229 var parms = new Dictionary<string, object>(); 230 parms.Add("alt", productName + asset.Keywords); 231 parms.Add("itemprop", "image"); 232 parms.Add("columns", Model.GridRowColumnCount); 233 parms.Add("eagerLoadNewImages", Model.Item.GetBoolean("DisableLazyLoading")); 234 parms.Add("doNotUseGetimage", Model.Item.GetBoolean("DisableGetImage")); 235 if (!string.IsNullOrEmpty(asset.DisplayName)) 236 { 237 parms.Add("title", asset.DisplayName); 238 } 239 240 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") 241 { 242 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); 243 } 244 else 245 { 246 parms.Add("cssClass", "mw-100 mh-100"); 247 } 248 249 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> 250 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide-to="@assetNumber"> 251 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 252 </div> 253 </a> 254 } 255 } 256 } 257 @foreach (string videoFormat in supportedVideoFormats) 258 { //Videos 259 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) 260 { 261 if (Model.Item.GetString("OpenVideoInModal") == "true") 262 { 263 if (product is object) 264 { 265 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 266 267 string productName = product.Name; 268 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 269 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 270 271 RatioSettings ratioSettings = GetRatioSettings(size); 272 273 string type = GetVideoType(asset.Value); 274 275 string videoScreendumpPath = type == "youtube" ? GetYoutubeScreenDump(asset.Value, "maxresdefault") : string.Empty; 276 videoScreendumpPath = type == "selfhosted" ? System.Uri.EscapeUriString(asset.Value) : videoScreendumpPath; 277 string videoJsClass = type == "vimeo" ? "js-vimeo-video-thumbnail" : ""; 278 279 280 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable); cursor: pointer" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> 281 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide-to="@assetNumber"> 282 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 283 @if (type != "selfhosted") 284 { 285 <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;"> 286 } 287 else 288 { 289 string videoType = Path.GetExtension(asset.Value).ToLower(); 290 291 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 292 <source src="@(videoScreendumpPath)#t=0.001" type="video/@videoType.Replace(".", "")"> 293 </video> 294 } 295 </div> 296 </div> 297 298 } 299 } 300 else 301 { 302 if (product is object) 303 { 304 var videoParams = GetVideoParams(asset, size); 305 @RenderPartial("Components/VideoPlayer.cshtml", new FileViewModel { Path = asset.Value}, videoParams); 306 307 } 308 } 309 } 310 } 311 @foreach (string documentFormat in supportedDocumentFormats) 312 { //Documents 313 if (assetValue.IndexOf(documentFormat, StringComparison.OrdinalIgnoreCase) >= 0) 314 { 315 if (product is object) 316 { 317 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 318 319 string productName = product.Name; 320 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 321 string imageLinkPath = imagePath; 322 323 RatioSettings ratioSettings = GetRatioSettings(size); 324 325 var parms = new Dictionary<string, object>(); 326 parms.Add("alt", productName + asset.Keywords); 327 parms.Add("itemprop", "image"); 328 parms.Add("fullwidth", true); 329 parms.Add("columns", Model.GridRowColumnCount); 330 if (!string.IsNullOrEmpty(asset.DisplayName)) 331 { 332 parms.Add("title", asset.DisplayName); 333 } 334 335 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") 336 { 337 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); 338 } 339 else 340 { 341 parms.Add("cssClass", "mw-100 mh-100"); 342 } 343 344 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@Translate("Download")"> 345 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 346 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 347 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) 348 { 349 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 350 } 351 </div> 352 </a> 353 } 354 355 } 356 } 357 </div> 358 } 359 360 361 </div> 362 assetNumber++; 363 } 364 } 365 } 366 </div> 367 @if (showBadges) 368 { 369 <div class="position-absolute top-0 left-0 p-2 p-lg-3"> 370 @{@RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms)} 371 </div> 372 } 373 374 </div> 375 376 @if (totalAssets > 1) 377 { 378 <div class="@(GetThumbnailRowSettingCss()) gap-3" id="SmallScreenImagesThumbnails_@Model.ID"> 379 @foreach (MediaViewModel asset in assetsList) 380 { 381 var assetValue = asset.Value; 382 string assetName = asset.Name; 383 assetName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 384 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : null; 385 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 386 387 string imagePath = Dynamicweb.Context.Current.Server.UrlEncode(assetValue); 388 imagePath = assetValue.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "https://img.youtube.com/vi/" + assetValue.Substring(assetValue.LastIndexOf('/') + 1) + "/mqdefault.jpg" : imagePath; 389 string imagePathThumb = assetValue.StartsWith("/Files/", StringComparison.OrdinalIgnoreCase) ? imagePath.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) < 0 && imagePath.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) < 0 ? $"/Admin/Public/GetImage.ashx?image={imagePath}&width=180&format=webp" : imagePath : assetValue; 390 391 RatioSettings ratioSettings = GetRatioSettings("desktop"); 392 393 <div class="border outline-none @(ratioSettings.CssClass)" style="@(ratioSettings.CssVariable); cursor: pointer; min-width: 7rem; max-width: 8rem;" data-bs-target="#SmallScreenImages_@Model.ID" data-bs-slide-to="@thumbnailNumber"> 394 @foreach (string imageFormat in supportedImageFormats) 395 { //Images 396 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0) 397 { 398 <img src="@imagePathThumb" alt="@assetName" @assetTitle class="p-0 p-lg-1 w-100 h-100" style="object-fit: contain;"> 399 400 thumbnailNumber++; 401 } 402 } 403 404 @foreach (string videoFormat in supportedVideoFormats) 405 { //Videos 406 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) 407 { 408 409 string type = GetVideoType(asset.Value); 410 411 string videoScreendumpPath = type == "youtube" ? GetYoutubeScreenDump(asset.Value, "mqdefault") : ""; 412 videoScreendumpPath = type == "vimeo" ? string.Empty : videoScreendumpPath; 413 videoScreendumpPath = type == "selfhosted" ? System.Uri.EscapeUriString(asset.Value) : videoScreendumpPath; 414 string videoJsClass = type == "vimeo" ? "js-vimeo-video-thumbnail" : string.Empty; 415 416 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 417 418 if (type != "selfhosted") 419 { 420 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@assetTitle" @assetTitle class="@videoJsClass mw-100 mh-100" data-asset-value="@asset.Value" style="object-fit: cover;" /> 421 } 422 else 423 { 424 string videoType = Path.GetExtension(asset.Value).ToLower(); 425 426 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 427 <source src="@(videoScreendumpPath)#t=0.001" type="video/@videoType.Replace(".", "")"> 428 </video> 429 } 430 431 thumbnailNumber++; 432 } 433 } 434 435 @foreach (string documentFormat in supportedDocumentFormats) 436 { //Documents 437 if (assetValue.IndexOf(documentFormat, StringComparison.OrdinalIgnoreCase) >= 0) 438 { 439 <a href="@assetValue" class="ratio ratio-4x3 border outline-none" style="cursor: pointer; min-width: 7rem; max-width: 8rem;" download title="@asset.Value"> 440 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) 441 { 442 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 443 <div class="icon-3 position-absolute text-light" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 444 </div> 445 <img src="@imagePathThumb" alt="@assetName" @assetTitle class="p-0 p-lg-1 mw-100 mh-100" style="object-fit: cover;"> 446 } 447 else 448 { 449 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 450 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 451 </div> 452 } 453 </a> 454 455 thumbnailNumber++; 456 } 457 } 458 </div> 459 } 460 </div> 461 } 462 </div> 463 464 @* Modal with slides *@ 465 <div class="modal fade swift_products-details-images-modal" id="modal_@Model.ID" tabindex="-1" aria-labelledby="productDetailsGalleryModalTitle_@Model.ID" aria-hidden="true"> 466 <div class="modal-dialog modal-dialog-centered modal-xl"> 467 <div class="modal-content"> 468 <div class="modal-header visually-hidden"> 469 <h5 class="modal-title" id="productDetailsGalleryModalTitle_@Model.ID">@product.Title</h5> 470 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 471 </div> 472 <div class="modal-body p-2 p-lg-3 h-100"> 473 <div id="ModalCarousel_@Model.ID" class="carousel@(GetArrowsColor()) h-100" data-bs-ride="carousel"> 474 <div class="carousel-inner h-100 @theme"> 475 @foreach (MediaViewModel asset in assetsList) 476 { 477 var assetValue = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 478 foreach (string supportedFormat in supportedImageFormats.Concat(supportedVideoFormats).ToArray()) 479 { 480 if (assetValue.IndexOf(supportedFormat, StringComparison.OrdinalIgnoreCase) >= 0) 481 { 482 string imagePath = assetValue; 483 string activeSlide = modalAssetNumber == 0 ? "active" : ""; 484 485 var parms = new Dictionary<string, object>(); 486 parms.Add("cssClass", "d-block mw-100 mh-100 m-auto"); 487 parms.Add("fullwidth", true); 488 parms.Add("columns", Model.GridRowColumnCount); 489 490 <div class="carousel-item @activeSlide h-100" data-bs-interval="99999"> 491 @foreach (string imageFormat in supportedImageFormats) 492 { //Images 493 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0) 494 { 495 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 496 } 497 } 498 499 @foreach (string videoFormat in supportedVideoFormats) 500 { //Videos 501 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) 502 { 503 var videoParams = GetVideoParams(asset, "modal"); 504 @RenderPartial("Components/VideoPlayer.cshtml", new FileViewModel { Path = asset.Value }, videoParams) 505 } 506 } 507 </div> 508 modalAssetNumber++; 509 } 510 } 511 } 512 <button class="carousel-control-prev carousel-control-area" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="prev"> 513 <span class="carousel-control-prev-icon" aria-hidden="true"></span> 514 <span class="visually-hidden">@Translate("Previous")</span> 515 </button> 516 <button class="carousel-control-next carousel-control-area" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="next"> 517 <span class="carousel-control-next-icon" aria-hidden="true"></span> 518 <span class="visually-hidden">@Translate("Next")</span> 519 </button> 520 </div> 521 </div> 522 </div> 523 </div> 524 </div> 525 </div> 526 } 527 else if (Pageview.IsVisualEditorMode) 528 { 529 RatioSettings ratioSettings = GetRatioSettings("desktop"); 530 531 <div class="h-100 @theme"> 532 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 533 <img src="/Files/Images/missing_image.jpg" loading="lazy" decoding="async" class="mh-100 mw-100" style="object-fit: cover;"> 534 </div> 535 </div> 536 } 537 } 538 else if (Pageview.IsVisualEditorMode) 539 { 540 <div class="alert alert-dark m-0">@Translate("No products available")</div> 541 } 542 543 544 545

Product number:

Login to see prices

Error executing template "Designs/Swift/Paragraph/Swift_ProductDetailsMediaTable.cshtml"
System.ArgumentNullException: Value cannot be null. (Parameter 'source')
   at System.Linq.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument)
   at System.Linq.Enumerable.Where[TSource](IEnumerable`1 source, Func`2 predicate)
   at CompiledRazorTemplates.Dynamic.RazorEngine_e16c08c3afca406b90d51340edfab335.ExecuteAsync()
   at RazorEngine.Templating.TemplateBase.Run(ExecuteContext context, TextWriter reader)
   at RazorEngine.Templating.RazorEngineCore.RunTemplate(ICompiledTemplate template, TextWriter writer, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.DynamicWrapperService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.Run(IRazorEngineService service, String name, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass23_0.<Run>b__0(TextWriter writer)
   at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter)
   at RazorEngine.Templating.RazorEngineServiceExtensions.Run(IRazorEngineService service, String name, Type modelType, Object model, DynamicViewBag viewBag)
   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 System.Text.RegularExpressions; 4 @using System.IO 5 6 @functions { 7 public ProductViewModel product { get; set; } = new ProductViewModel(); 8 public string[] supportedImageFormats { get; set; } 9 public string[] supportedVideoFormats { get; set; } 10 public string[] supportedDocumentFormats { get; set; } 11 public string[] allSupportedFormats { get; set; } 12 13 public class RatioSettings 14 { 15 public string Ratio { get; set; } 16 public string CssClass { get; set; } 17 public string CssVariable { get; set; } 18 public string Fill { get; set; } 19 } 20 21 public RatioSettings GetRatioSettings() 22 { 23 var ratioSettings = new RatioSettings(); 24 25 string ratio = Model.Item.GetRawValueString("ImageAspectRatio", ""); 26 ratio = ratio != "0" ? ratio : ""; 27 string cssClass = ratio != "" && ratio != "fill" ? " ratio" : ""; 28 string cssVariable = ratio != "" && ratio != "fill" ? "--bs-aspect-ratio: " + ratio : ""; 29 30 ratioSettings.Ratio = ratio; 31 ratioSettings.CssClass = cssClass; 32 ratioSettings.CssVariable = cssVariable; 33 ratioSettings.Fill = ratio == "fill" ? " h-100" : ""; 34 35 return ratioSettings; 36 } 37 38 public Dictionary<string, object> GetVideoParams(MediaViewModel asset, string size) 39 { 40 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Name; 41 string type = GetVideoType(asset.Value); 42 bool openInModal = Model.Item.GetString("OpenVideoInModal") == "true" ? true : false; 43 bool autoPlay = Model.Item.GetBoolean("VideoAutoPlay"); 44 45 var videoParams = new Dictionary<string, object>(); 46 videoParams.Add("AssetName", asset.Name); 47 videoParams.Add("AssetVideoType", type); 48 videoParams.Add("AssetDisplayName", asset.DisplayName); 49 videoParams.Add("OpenVideoInModal", openInModal); 50 videoParams.Add("VideoAutoPlay", autoPlay); 51 videoParams.Add("Size", size); 52 videoParams.Add("Id", Model.ID); 53 return videoParams; 54 55 } 56 57 public string GetVideoType(string assetValue) 58 { 59 string type = assetValue.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "youtube" : string.Empty; 60 type = assetValue.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "vimeo" : type; 61 type = string.IsNullOrEmpty(type) ? "selfhosted" : type; 62 return type; 63 } 64 65 public string GetYoutubeScreenDump(string assetValue) 66 { 67 var regex = new Regex(@"(?:youtube\.com\/.*[\?&]v=|youtu\.be\/|youtube\.com\/embed\/)([\w-]+)(?:\?.*)?"); 68 Match match = regex.Match(assetValue); 69 string videoId = match.Success ? match.Groups[1].Value : string.Empty; 70 string youtubeThumbnail = $"https://img.youtube.com/vi/{videoId}/mqdefault.jpg"; 71 return youtubeThumbnail; 72 } 73 74 } 75 76 @{ 77 @* Get the product data *@ 78 ProductViewModel product = null; 79 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 80 { 81 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 82 } 83 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 84 { 85 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 86 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 87 88 if (productList?.Products is object) 89 { 90 product = productList.Products[0]; 91 } 92 } 93 } 94 95 @if (product is object) 96 { 97 @* Supported formats *@ 98 supportedImageFormats = new string[] { ".jpg", ".jpeg", ".webp", ".png", ".gif", ".bmp", ".tiff" }; 99 supportedVideoFormats = new string[] { "youtu.be", "youtube", "vimeo", ".mp4", ".webm" }; 100 supportedDocumentFormats = new string[] { ".pdf", ".docx", ".xlsx", ".ppt", ".pptx", ".igs", ".ipt", ".sat", ".stp", ".dwg", ".dxf", ".dwf" }; 101 102 allSupportedFormats = supportedImageFormats.Concat(supportedVideoFormats).Concat(supportedDocumentFormats).ToArray(); 103 104 @* Collect the assets *@ 105 var selectedAssetCategories = Model.Item.GetList("ImageAssets")?.GetRawValue().OfType<string>(); 106 bool includeImagePatternImages = Model.Item.GetBoolean("ImagePatternImages"); 107 108 @* Needed image data collection to support both DefaultImage, ImagePatterns and Image Assets *@ 109 string defaultImage = product.DefaultImage != null ? product.DefaultImage.Value : ""; 110 IEnumerable<MediaViewModel> assetsImages = product.AssetCategories.Where(x => selectedAssetCategories.Contains(x.SystemName)).SelectMany(x => x.Assets); 111 assetsImages = assetsImages.OrderByDescending(x => x.Value.Equals(defaultImage)); 112 IEnumerable<MediaViewModel> assetsList = new MediaViewModel[] { }; 113 114 assetsList = assetsList.Union(assetsImages); 115 assetsList = includeImagePatternImages ? assetsList.Union(product.ImagePatternImages) : assetsList; 116 assetsList = includeImagePatternImages && assetsList.Count() == 0 ? assetsList.Append(product.DefaultImage) : assetsList; 117 118 bool defaultImageFallback = Model.Item.GetBoolean("DefaultImageFallback"); 119 bool showOnlyPrimaryImage = Model.Item.GetBoolean("ShowOnlyPrimaryImage"); 120 121 int totalAssets = 0; 122 if (showOnlyPrimaryImage == false) { 123 foreach (MediaViewModel asset in assetsList) { 124 var assetValue = asset.Value; 125 foreach (string format in allSupportedFormats) { 126 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 127 totalAssets++; 128 } 129 } 130 } 131 } 132 133 if ((totalAssets == 0 && product.DefaultImage != null && selectedAssetCategories.Count() == 0) || (showOnlyPrimaryImage == true && product.DefaultImage != null)) 134 { 135 assetsList = new List<MediaViewModel>(){ product.DefaultImage }; 136 totalAssets = 1; 137 } 138 139 int videoNumber = 0; 140 141 @* Layout settings *@ 142 string spacing = Model.Item.GetRawValueString("Spacing", "p-0"); 143 spacing = spacing == "none" ? "p-0" : spacing; 144 spacing = spacing == "small" ? "p-3" : spacing; 145 spacing = spacing == "large" ? "p-5" : spacing; 146 147 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 148 149 bool hideThumbnails = Model.Item.GetBoolean("HideThumbnails"); 150 151 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 152 int modalVideoNumber = 0; 153 154 @* Get assets from selected categories or get all assets *@ 155 156 if (totalAssets != 0 && assetsList.Any()) 157 { 158 <div class="@spacing@(theme) item_@Model.Item.SystemName.ToLower()"> 159 @if (!string.IsNullOrEmpty(Model.Item.GetString("Title")) && !Model.Item.GetBoolean("HideTitle")) 160 { 161 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "h3"); 162 163 <h3 class="@titleFontSize mb-3"> 164 @Model.Item.GetString("Title") 165 </h3> 166 } 167 168 <div class="table-responsive"> 169 <table class="table table-hover align-middle mb-0" style="table-layout: fixed;"> 170 <thead> 171 <tr> 172 @if (!hideThumbnails) 173 { 174 <th style="width:60px">&nbsp;</th> 175 } 176 <th>@Translate("Name")</th> 177 <th class="text-end d-none d-lg-table-cell">@Translate("Download")</th> 178 <th class="text-end" style="width:100px">@Translate("File type")</th> 179 </tr> 180 </thead> 181 <tbody class="border-top-0"> 182 @foreach (MediaViewModel asset in assetsList) 183 { 184 var assetValue = asset.Value; 185 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Value.Substring(asset.Value.LastIndexOf('/') + 1); 186 187 bool isVideo = false; 188 foreach (string format in supportedVideoFormats) 189 { //Videos 190 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 191 { 192 isVideo = true; 193 } 194 } 195 196 if (!isVideo) 197 { 198 if (assetValue.StartsWith("/Files/", StringComparison.OrdinalIgnoreCase)) 199 { 200 string filePath = Dynamicweb.Context.Current.Server.MapPath(assetValue); 201 long fileSize = 0; 202 203 if (File.Exists(filePath)) 204 { 205 fileSize = new System.IO.FileInfo(filePath) != null ? new System.IO.FileInfo(filePath).Length / 1024 : 0; 206 207 foreach (string format in allSupportedFormats) 208 { 209 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 210 { 211 <tr> 212 @if (!hideThumbnails) 213 { 214 //From @RenderAsset 215 string imageTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 216 217 <td class="@(imageTheme) px-0"> 218 @foreach (string imageFormat in supportedImageFormats) 219 { //Images 220 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0) 221 { 222 string productName = product.Name; 223 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 224 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 225 string imageLinkPath = imagePath; 226 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 227 imagePath = !imageLinkPath.StartsWith("/Files/", StringComparison.OrdinalIgnoreCase) ? asset.Value : imagePath; 228 229 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 230 231 RatioSettings ratioSettings = GetRatioSettings(); 232 233 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download> 234 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 235 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle itemprop="image"> 236 </div> 237 </a> 238 } 239 } 240 @foreach (string videoFormat in supportedVideoFormats) 241 { //Videos 242 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) 243 { 244 string productName = product.Name; 245 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 246 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 247 248 RatioSettings ratioSettings = GetRatioSettings(); 249 250 string type = GetVideoType(asset.Value); 251 string videoScreendumpPath = type == "youtube" ? GetYoutubeScreenDump(asset.Value) : string.Empty; 252 videoScreendumpPath = type == "selfhosted" ? System.Uri.EscapeUriString(asset.Value) : videoScreendumpPath; 253 string videoJsClass = type == "vimeo" ? "js-vimeo-video-thumbnail" : string.Empty; 254 255 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 256 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 257 <div class="icon-2 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 258 @if (type != "selfhosted") 259 { 260 <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;"> 261 } 262 else 263 { 264 string videoType = Path.GetExtension(asset.Value).ToLower(); 265 266 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 267 <source src="@(videoScreendumpPath)#t=0.001" type="video/@videoType.Replace(".", "")"> 268 </video> 269 } 270 </div> 271 </div> 272 } 273 } 274 @foreach (string documentFormat in supportedDocumentFormats) 275 { //Documents 276 if (assetValue.IndexOf(documentFormat, StringComparison.OrdinalIgnoreCase) >= 0) 277 { 278 string productName = product.Name; 279 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 280 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 281 string imageLinkPath = imagePath; 282 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 283 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 284 285 RatioSettings ratioSettings = GetRatioSettings(); 286 287 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@productName"> 288 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) 289 { 290 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 291 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle /> 292 </div> 293 } 294 else 295 { 296 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 297 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 298 </div> 299 } 300 </a> 301 } 302 } 303 </td> 304 305 } 306 <td> 307 <a href="@assetValue" class="text-decoration-none text-break" download="@assetName" title="@assetName"> 308 @assetName 309 </a> 310 </td> 311 <td class="text-end d-none d-lg-table-cell"> 312 <a href="@assetValue" class="text-decoration-none" download="@assetName" title="@assetName"> 313 @fileSize KB <div class="icon-2" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 314 </a> 315 </td> 316 <td class="text-end">@format</td> 317 </tr> 318 } 319 } 320 } 321 } 322 else 323 { 324 <tr> 325 @if (!hideThumbnails) 326 { 327 //From @RenderAsset 328 string imageTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 329 330 <td class="@(imageTheme) px-0"> 331 @foreach (string format in supportedImageFormats) 332 { //Images 333 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 334 { 335 string productName = product.Name; 336 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 337 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 338 string imageLinkPath = imagePath; 339 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 340 imagePath = !imageLinkPath.StartsWith("/Files/", StringComparison.OrdinalIgnoreCase) ? asset.Value : imagePath; 341 342 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 343 344 RatioSettings ratioSettings = GetRatioSettings(); 345 346 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download> 347 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 348 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle itemprop="image"> 349 </div> 350 </a> 351 } 352 } 353 @foreach (string format in supportedVideoFormats) 354 { //Videos 355 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 356 { 357 string type = GetVideoType(asset.Value); 358 string videoScreendumpPath = type == "youtube" ? GetYoutubeScreenDump(asset.Value) : string.Empty; 359 videoScreendumpPath = type == "selfhosted" ? System.Uri.EscapeUriString(asset.Value) : videoScreendumpPath; 360 string videoJsClass = type == "vimeo" ? "js-vimeo-video-thumbnail" : string.Empty; 361 362 363 string productName = product.Name; 364 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 365 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 366 367 RatioSettings ratioSettings = GetRatioSettings(); 368 369 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 370 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 371 <div class="icon-2 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 372 @if (type != "selfhosted") 373 { 374 <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;"> 375 } 376 else 377 { 378 string videoType = Path.GetExtension(asset.Value).ToLower(); 379 380 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 381 <source src="@(videoScreendumpPath)#t=0.001" type="video/@videoType.Replace(".", "")"> 382 </video> 383 } 384 </div> 385 </div> 386 } 387 } 388 @foreach (string format in supportedDocumentFormats) 389 { //Documents 390 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 391 { 392 string productName = product.Name; 393 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 394 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 395 string imageLinkPath = imagePath; 396 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 397 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 398 399 RatioSettings ratioSettings = GetRatioSettings(); 400 401 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@productName"> 402 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) 403 { 404 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 405 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle /> 406 </div> 407 } 408 else 409 { 410 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 411 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 412 </div> 413 } 414 </a> 415 } 416 } 417 </td> 418 } 419 <td> 420 <a href="@assetValue" class="text-decoration-none text-break" download="@assetName" title="@assetName"> 421 @assetName 422 </a> 423 </td> 424 <td>&nbsp;</td> 425 <td>&nbsp;</td> 426 </tr> 427 } 428 } 429 else 430 { 431 string videoType = asset.Value.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || asset.Value.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "Youtube" : ""; 432 videoType = asset.Value.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "Vimeo" : videoType; 433 434 <tr data-bs-toggle="modal" data-bs-target="#modal_@(Model.ID)_@videoNumber" style="cursor: pointer"> 435 @if (!hideThumbnails) 436 { 437 //From @RenderAsset 438 string imageTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 439 440 441 <td class="@(imageTheme) px-0"> 442 @foreach (string format in supportedImageFormats) 443 { //Images 444 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 445 { 446 string productName = product.Name; 447 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 448 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 449 string imageLinkPath = imagePath; 450 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 451 imagePath = !imageLinkPath.StartsWith("/Files/", StringComparison.OrdinalIgnoreCase) ? asset.Value : imagePath; 452 453 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 454 455 RatioSettings ratioSettings = GetRatioSettings(); 456 457 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download> 458 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 459 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle itemprop="image"> 460 </div> 461 </a> 462 } 463 } 464 @foreach (string format in supportedVideoFormats) 465 { //Videos 466 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 467 { 468 469 string type = GetVideoType(asset.Value); 470 string videoScreendumpPath = type == "youtube" ? GetYoutubeScreenDump(asset.Value) : string.Empty; 471 videoScreendumpPath = type == "selfhosted" ? System.Uri.EscapeUriString(asset.Value) : videoScreendumpPath; 472 string videoJsClass = type == "vimeo" ? "js-vimeo-video-thumbnail" : string.Empty; 473 474 string productName = product.Name; 475 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 476 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 477 478 RatioSettings ratioSettings = GetRatioSettings(); 479 480 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 481 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 482 <div class="icon-2 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 483 @if (type != "selfhosted") 484 { 485 <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;"> 486 } 487 else 488 { 489 string fileExtension = Path.GetExtension(asset.Value).ToLower(); 490 491 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 492 <source src="@(videoScreendumpPath)#t=0.001" type="video/@fileExtension.Replace(".", "")"> 493 </video> 494 } 495 </div> 496 </div> 497 } 498 } 499 @foreach (string documentFormat in supportedDocumentFormats) 500 { //Documents 501 if (assetValue.IndexOf(documentFormat, StringComparison.OrdinalIgnoreCase) >= 0) 502 { 503 string productName = product.Name; 504 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 505 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 506 string imageLinkPath = imagePath; 507 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 508 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 509 510 RatioSettings ratioSettings = GetRatioSettings(); 511 512 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@productName"> 513 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) 514 { 515 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 516 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle /> 517 </div> 518 } 519 else 520 { 521 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 522 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 523 </div> 524 } 525 </a> 526 } 527 } 528 </td> 529 } 530 <td> 531 @assetName 532 </td> 533 <td class="d-none d-lg-table-cell">&nbsp;</td> 534 <td align="right">@videoType</td> 535 </tr> 536 537 videoNumber++; 538 } 539 } 540 </tbody> 541 </table> 542 </div> 543 544 @foreach (MediaViewModel asset in assetsList) 545 { 546 var assetName = asset.Value.ToLower(); 547 548 foreach (string videoFormat in supportedVideoFormats) 549 { //Videos 550 if (assetName.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) 551 { 552 <div class="modal fade js-video-modal" id="modal_@(Model.ID)_@modalVideoNumber" tabindex="-1" aria-labelledby="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber" aria-hidden="true"> 553 <div class="modal-dialog modal-dialog-centered modal-xl"> 554 <div class="modal-content"> 555 <div class="modal-header visually-hidden"> 556 <h5 class="modal-title" id="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber">@product.Title</h5> 557 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 558 </div> 559 <div class="modal-body p-2 p-lg-3 h-100"> 560 @{ 561 var videoParams = GetVideoParams(asset, "modal"); 562 @RenderPartial("Components/VideoPlayer.cshtml", new Dynamicweb.Frontend.FileViewModel { Path = asset.Value }, videoParams) 563 } 564 </div> 565 </div> 566 </div> 567 </div> 568 569 modalVideoNumber++; 570 } 571 } 572 } 573 </div> 574 } 575 else if (Pageview.IsVisualEditorMode) 576 { 577 <div class="h-100 @theme"> 578 <div class="alert alert-dark m-0"> 579 @Translate("No assets are available") 580 </div> 581 </div> 582 } 583 } 584

Specifications