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_6a46c486d0f64eb5b8fa33a5991f7690.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/Finixa_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_a8f8483506f24eb59da91a28ef28ddc1.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("customBadgeCssClassName", Model.Item.GetRawValueString("CustomBadges")); 179 badgeParms.Add("newBadgeCssClassName", Model.Item.GetRawValue("NewBadgeDesign")); 180 badgeParms.Add("newPublicationDays", Model.Item.GetInt32("NewPublicationDays")); 181 badgeParms.Add("campaignBadgesValues", Model.Item.GetList("CampaignBadges")?.GetRawValue().OfType<string>().ToList()); 182 183 bool saleBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("SaleBadgeDesign")) && Model.Item.GetRawValueString("SaleBadgeDesign") != "none" ? true : false; 184 bool newBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("NewBadgeDesign")) && Model.Item.GetRawValueString("NewBadgeDesign") != "none" ? true : false; 185 DateTime createdDate = product.Created.Value; 186 bool showBadges = saleBadgeEnabled && product.Discount.Price != 0 ? true : false; 187 showBadges = (newBadgeEnabled && Model.Item.GetInt32("NewPublicationDays") == 0) || (newBadgeEnabled && (createdDate.AddDays(Model.Item.GetInt32("NewPublicationDays")) > DateTime.Now)) ? true : showBadges; 188 showBadges = !string.IsNullOrEmpty(Model.Item.GetRawValueString("CampaignBadges")) ? true : showBadges; 189 showBadges = !string.IsNullOrEmpty(Model.Item.GetRawValueString("CustomBadges")) ? true : showBadges; 190 191 @* Get assets from selected categories or get all assets *@ 192 if (totalAssets != 0) 193 { 194 int assetNumber = 0; 195 int thumbnailNumber = 0; 196 int modalAssetNumber = 0; 197 string thumbnailAxisCss = GetThumbnailPlacement() == "bottom" ? "flex-column" : string.Empty; 198 199 <div class="d-flex gap-3 h-100 @(thumbnailAxisCss) @(theme) item_@Model.Item.SystemName.ToLower()"> 200 <div id="SmallScreenImages_@Model.ID" class="carousel@(GetArrowsColor()) col position-relative" data-bs-ride="carousel"> 201 <div class="carousel-inner h-100"> 202 @foreach (MediaViewModel asset in assetsList) 203 { 204 var assetValue = asset.Value; 205 foreach (string format in allSupportedFormats) 206 { 207 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 208 { 209 string activeSlide = assetNumber == 0 ? "active" : ""; 210 211 <div class="carousel-item @activeSlide" data-bs-interval="99999"> 212 @{ 213 string size = "mobile"; 214 215 string imageTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 216 217 218 <div class="h-100 @(imageTheme)"> 219 @foreach (string imageFormat in supportedImageFormats) 220 { //Images 221 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0) 222 { 223 if (product is object) 224 { 225 string productName = product.Name; 226 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 227 string imageLinkPath = Dynamicweb.Context.Current.Server.UrlEncode(imagePath); 228 229 RatioSettings ratioSettings = GetRatioSettings(size); 230 231 var parms = new Dictionary<string, object>(); 232 parms.Add("alt", productName + asset.Keywords); 233 parms.Add("itemprop", "image"); 234 parms.Add("columns", Model.GridRowColumnCount); 235 parms.Add("eagerLoadNewImages", Model.Item.GetBoolean("DisableLazyLoading")); 236 parms.Add("doNotUseGetimage", Model.Item.GetBoolean("DisableGetImage")); 237 if (!string.IsNullOrEmpty(asset.DisplayName)) 238 { 239 parms.Add("title", asset.DisplayName); 240 } 241 242 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") 243 { 244 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); 245 } 246 else 247 { 248 parms.Add("cssClass", "mw-100 mh-100"); 249 } 250 251 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> 252 <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"> 253 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 254 </div> 255 </a> 256 } 257 } 258 } 259 @foreach (string videoFormat in supportedVideoFormats) 260 { //Videos 261 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) 262 { 263 if (Model.Item.GetString("OpenVideoInModal") == "true") 264 { 265 if (product is object) 266 { 267 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 268 269 string productName = product.Name; 270 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 271 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 272 273 RatioSettings ratioSettings = GetRatioSettings(size); 274 275 string type = GetVideoType(asset.Value); 276 277 string videoScreendumpPath = type == "youtube" ? GetYoutubeScreenDump(asset.Value, "maxresdefault") : string.Empty; 278 videoScreendumpPath = type == "selfhosted" ? System.Uri.EscapeUriString(asset.Value) : videoScreendumpPath; 279 string videoJsClass = type == "vimeo" ? "js-vimeo-video-thumbnail" : ""; 280 281 282 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable); cursor: pointer" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> 283 <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"> 284 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 285 @if (type != "selfhosted") 286 { 287 <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;"> 288 } 289 else 290 { 291 string videoType = Path.GetExtension(asset.Value).ToLower(); 292 293 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 294 <source src="@(videoScreendumpPath)#t=0.001" type="video/@videoType.Replace(".", "")"> 295 </video> 296 } 297 </div> 298 </div> 299 300 } 301 } 302 else 303 { 304 if (product is object) 305 { 306 var videoParams = GetVideoParams(asset, size); 307 @RenderPartial("Components/VideoPlayer.cshtml", new FileViewModel { Path = asset.Value}, videoParams); 308 309 } 310 } 311 } 312 } 313 @foreach (string documentFormat in supportedDocumentFormats) 314 { //Documents 315 if (assetValue.IndexOf(documentFormat, StringComparison.OrdinalIgnoreCase) >= 0) 316 { 317 if (product is object) 318 { 319 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 320 321 string productName = product.Name; 322 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 323 string imageLinkPath = imagePath; 324 325 RatioSettings ratioSettings = GetRatioSettings(size); 326 327 var parms = new Dictionary<string, object>(); 328 parms.Add("alt", productName + asset.Keywords); 329 parms.Add("itemprop", "image"); 330 parms.Add("fullwidth", true); 331 parms.Add("columns", Model.GridRowColumnCount); 332 if (!string.IsNullOrEmpty(asset.DisplayName)) 333 { 334 parms.Add("title", asset.DisplayName); 335 } 336 337 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") 338 { 339 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); 340 } 341 else 342 { 343 parms.Add("cssClass", "mw-100 mh-100"); 344 } 345 346 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@Translate("Download")"> 347 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 348 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 349 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) 350 { 351 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 352 } 353 </div> 354 </a> 355 } 356 357 } 358 } 359 </div> 360 } 361 362 363 </div> 364 assetNumber++; 365 } 366 } 367 } 368 </div> 369 @if (showBadges) 370 { 371 <div class="position-absolute top-0 left-0 p-2 p-lg-3"> 372 @{@RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms)} 373 </div> 374 } 375 376 </div> 377 378 @if (totalAssets > 1) 379 { 380 <div class="thumbnails-grid" id="SmallScreenImagesThumbnails_@Model.ID"> 381 @foreach (MediaViewModel asset in assetsList) 382 { 383 var assetValue = asset.Value; 384 string assetName = asset.Name; 385 assetName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 386 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : null; 387 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 388 389 string imagePath = Dynamicweb.Context.Current.Server.UrlEncode(assetValue); 390 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; 391 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; 392 393 RatioSettings ratioSettings = GetRatioSettings("desktop"); 394 395 <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"> 396 @foreach (string imageFormat in supportedImageFormats) 397 { //Images 398 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0) 399 { 400 <img src="@imagePathThumb" alt="@assetName" @assetTitle class="p-0 p-lg-1 w-100 h-100" style="object-fit: contain;"> 401 402 thumbnailNumber++; 403 } 404 } 405 406 @foreach (string videoFormat in supportedVideoFormats) 407 { //Videos 408 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) 409 { 410 411 string type = GetVideoType(asset.Value); 412 413 string videoScreendumpPath = type == "youtube" ? GetYoutubeScreenDump(asset.Value, "mqdefault") : ""; 414 videoScreendumpPath = type == "vimeo" ? string.Empty : videoScreendumpPath; 415 videoScreendumpPath = type == "selfhosted" ? System.Uri.EscapeUriString(asset.Value) : videoScreendumpPath; 416 string videoJsClass = type == "vimeo" ? "js-vimeo-video-thumbnail" : string.Empty; 417 418 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 419 420 if (type != "selfhosted") 421 { 422 <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;" /> 423 } 424 else 425 { 426 string videoType = Path.GetExtension(asset.Value).ToLower(); 427 428 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 429 <source src="@(videoScreendumpPath)#t=0.001" type="video/@videoType.Replace(".", "")"> 430 </video> 431 } 432 433 thumbnailNumber++; 434 } 435 } 436 437 @foreach (string documentFormat in supportedDocumentFormats) 438 { //Documents 439 if (assetValue.IndexOf(documentFormat, StringComparison.OrdinalIgnoreCase) >= 0) 440 { 441 <a href="@assetValue" class="ratio ratio-4x3 border outline-none" style="cursor: pointer; min-width: 7rem; max-width: 8rem;" download title="@asset.Value"> 442 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) 443 { 444 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 445 <div class="icon-3 position-absolute text-light" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 446 </div> 447 <img src="@imagePathThumb" alt="@assetName" @assetTitle class="p-0 p-lg-1 mw-100 mh-100" style="object-fit: cover;"> 448 } 449 else 450 { 451 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 452 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 453 </div> 454 } 455 </a> 456 457 thumbnailNumber++; 458 } 459 } 460 </div> 461 } 462 </div> 463 } 464 </div> 465 466 @* Modal with slides *@ 467 <div class="modal fade swift_products-details-images-modal" id="modal_@Model.ID" tabindex="-1" aria-labelledby="productDetailsGalleryModalTitle_@Model.ID" aria-hidden="true"> 468 <div class="modal-dialog modal-dialog-centered modal-xl"> 469 <div class="modal-content"> 470 <div class="modal-header visually-hidden"> 471 <h5 class="modal-title" id="productDetailsGalleryModalTitle_@Model.ID">@product.Title</h5> 472 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 473 </div> 474 <div class="modal-body p-2 p-lg-3 h-100"> 475 <div id="ModalCarousel_@Model.ID" class="carousel@(GetArrowsColor()) h-100" data-bs-ride="carousel"> 476 <div class="carousel-inner h-100 @theme"> 477 @foreach (MediaViewModel asset in assetsList) 478 { 479 var assetValue = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 480 foreach (string supportedFormat in supportedImageFormats.Concat(supportedVideoFormats).ToArray()) 481 { 482 if (assetValue.IndexOf(supportedFormat, StringComparison.OrdinalIgnoreCase) >= 0) 483 { 484 string imagePath = assetValue; 485 string activeSlide = modalAssetNumber == 0 ? "active" : ""; 486 487 var parms = new Dictionary<string, object>(); 488 parms.Add("cssClass", "d-block mw-100 mh-100 m-auto"); 489 parms.Add("fullwidth", true); 490 parms.Add("columns", Model.GridRowColumnCount); 491 492 <div class="carousel-item @activeSlide h-100" data-bs-interval="99999"> 493 @foreach (string imageFormat in supportedImageFormats) 494 { //Images 495 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0) 496 { 497 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 498 } 499 } 500 501 @foreach (string videoFormat in supportedVideoFormats) 502 { //Videos 503 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) 504 { 505 var videoParams = GetVideoParams(asset, "modal"); 506 @RenderPartial("Components/VideoPlayer.cshtml", new FileViewModel { Path = asset.Value }, videoParams) 507 } 508 } 509 </div> 510 modalAssetNumber++; 511 } 512 } 513 } 514 <button class="carousel-control-prev carousel-control-area" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="prev"> 515 <span class="carousel-control-prev-icon" aria-hidden="true"></span> 516 <span class="visually-hidden">@Translate("Previous")</span> 517 </button> 518 <button class="carousel-control-next carousel-control-area" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="next"> 519 <span class="carousel-control-next-icon" aria-hidden="true"></span> 520 <span class="visually-hidden">@Translate("Next")</span> 521 </button> 522 </div> 523 </div> 524 </div> 525 </div> 526 </div> 527 </div> 528 } 529 else if (Pageview.IsVisualEditorMode) 530 { 531 RatioSettings ratioSettings = GetRatioSettings("desktop"); 532 533 <div class="h-100 @theme"> 534 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 535 <img src="/Files/Images/missing_image.jpg" loading="lazy" decoding="async" class="mh-100 mw-100" style="object-fit: cover;"> 536 </div> 537 </div> 538 } 539 } 540 else if (Pageview.IsVisualEditorMode) 541 { 542 <div class="alert alert-dark m-0">@Translate("No products available")</div> 543 } 544
Productdetails
Error executing template "Designs/Swift/Paragraph/Swift_ProductDetailsGallery.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_6f2e3a37b6df4b31914a31fd8e32d334.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 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 @RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms) 354 </div> 355 } 356 </div> 357 358 @* Mobile: Show the thumbs on small screens *@ 359 <div class="d-block d-lg-none mx-lg-0 position-relative @theme item_@Model.Item.SystemName.ToLower() mobile"> 360 <div id="SmallScreenImages_@Model.ID" class="carousel@(GetArrowsColor())" data-bs-ride="carousel"> 361 <div class="carousel-inner h-100"> 362 @foreach (MediaViewModel asset in assetsList) { 363 var assetValue = asset.Value; 364 foreach (string format in allSupportedFormats) { 365 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 366 string activeSlide = mobileAssetNumber == 0 ? "active" : ""; 367 string size = "mobile"; 368 string imageTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 369 370 <div class="carousel-item @activeSlide" data-bs-interval="99999"> 371 <div class="h-100 @(imageTheme)"> 372 @foreach (string imageFormat in supportedImageFormats) 373 { //Images 374 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0) 375 { 376 string productName = product.Name; 377 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 378 string imageLinkPath = Uri.EscapeDataString(imagePath); 379 380 RatioSettings ratioSettings = GetRatioSettings(size); 381 382 var parms = new Dictionary<string, object>(); 383 parms.Add("alt", productName); 384 parms.Add("itemprop", "image"); 385 if (totalAssets > 1) 386 { 387 parms.Add("columns", 2); 388 } 389 else 390 { 391 parms.Add("columns", 1); 392 } 393 parms.Add("eagerLoadNewImages", Model.Item.GetBoolean("DisableLazyLoading")); 394 parms.Add("doNotUseGetimage", Model.Item.GetBoolean("DisableGetImage")); 395 396 if (!string.IsNullOrEmpty(asset.DisplayName)) 397 { 398 parms.Add("title", asset.DisplayName); 399 } 400 401 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") 402 { 403 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); 404 } 405 else 406 { 407 parms.Add("cssClass", "mw-100 mh-100"); 408 } 409 410 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> 411 <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"> 412 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 413 </div> 414 </a> 415 } 416 } 417 @foreach (string videoFormat in supportedVideoFormats) 418 { //Videos 419 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) 420 { 421 if (Model.Item.GetString("OpenVideoInModal") == "true") 422 { 423 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 424 425 string type = GetVideoType(asset.Value); 426 427 string videoScreendumpPath = type == "selfhosted" ? System.Uri.EscapeUriString(asset.Value) : string.Empty; 428 videoScreendumpPath = type == "youtube" ? GetYoutubeScreenDump(asset.Value, "maxresdefault") : videoScreendumpPath; 429 string videoJsClass = type == "vimeo" ? "js-vimeo-video-thumbnail" : ""; 430 431 string productName = product.Name; 432 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 433 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 434 435 RatioSettings ratioSettings = GetRatioSettings(size); 436 437 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable); cursor: pointer" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> 438 <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"> 439 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 440 @if (type != "selfhosted") 441 { 442 <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;" > 443 } 444 else 445 { 446 string videoType = Path.GetExtension(asset.Value).ToLower(); 447 448 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 449 <source src="@(videoScreendumpPath)#t=0.001" type="video/@videoType.Replace(".", "")"> 450 </video> 451 } 452 </div> 453 </div> 454 } 455 else 456 { 457 Dictionary<string, object> videoParams = GetVideoParams(asset, size); 458 @RenderPartial("Components/VideoPlayer.cshtml", new FileViewModel { Path = asset.Value }, videoParams); 459 } 460 } 461 } 462 </div> 463 </div> 464 mobileAssetNumber++; 465 } 466 } 467 } 468 </div> 469 </div> 470 471 @if (totalAssets > 1) { 472 <div id="SmallScreenImagesThumbnails_@Model.ID" class="d-flex flex-nowrap gap-2 overflow-x-auto my-3"> 473 @foreach (MediaViewModel asset in assetsList) { 474 var assetValue = asset.Value; 475 foreach (string format in allSupportedFormats) { 476 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 477 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 478 string type = GetVideoType(asset.Value); 479 480 string videoScreendumpPath = type == "youtube" ? GetYoutubeScreenDump(asset.Value, "maxresdefault") : string.Empty; 481 videoScreendumpPath = type == "selfhosted" ? System.Uri.EscapeUriString(asset.Value) : videoScreendumpPath; 482 string videoJsClass = type == "vimeo" ? "js-vimeo-video-thumbnail" : string.Empty; 483 484 string productName = product.Name; 485 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 486 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 487 488 <div class="ratio ratio-4x3 border outline-none" style="flex:0 0 80px" data-bs-target="#SmallScreenImages_@Model.ID" data-bs-slide-to="@mobileAssetThumbnailNumber"> 489 @foreach (string imageFormat in supportedImageFormats) 490 { //Images 491 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0) 492 { 493 string imagePath = !string.IsNullOrEmpty(asset.Value) ? $"/Admin/Public/GetImage.ashx?image={asset.Value}&width=180&format=webp" : string.Empty; 494 <img src="@imagePath" class="p-1 mw-100 mh-100" style="object-fit: cover;" alt="@productName" @assetTitle > 495 } 496 } 497 @foreach (string videoFormat in supportedVideoFormats) 498 { //Videos 499 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) 500 { 501 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 502 <div class="icon-3 position-absolute text-light" style="z-index: 1">@ReadFile(iconPath+"play-circle.svg")</div> 503 </div> 504 if (type != "selfhosted") 505 { 506 507 <img src="@(videoScreendumpPath)" class="p-1 @videoJsClass mw-100 mh-100" data-asset-value="@asset.Value" style="object-fit: cover;" alt="@productName" @assetTitle> 508 509 } 510 else 511 { 512 string videoType = Path.GetExtension(asset.Value).ToLower(); 513 514 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 515 <source src="@(videoScreendumpPath)#t=0.001" type="video/@videoType.Replace(".", "")"> 516 </video> 517 } 518 } 519 } 520 @foreach (string documentFormat in supportedDocumentFormats) 521 { //Documents 522 if (assetValue.IndexOf(documentFormat, StringComparison.OrdinalIgnoreCase) >= 0) 523 { 524 string imagePath = !string.IsNullOrEmpty(asset.Value) ? $"/Admin/Public/GetImage.ashx?image={asset.Value}&width=180&format=webp" : string.Empty; 525 526 <a href="@Uri.EscapeDataString(assetValue)" style="cursor: pointer; min-width: 7rem; max-width: 8rem;" download title="@asset.Value"> 527 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) 528 { 529 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 530 <div class="icon-3 position-absolute text-light" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 531 </div> 532 <img src="@imagePath" alt="@productName" @assetTitle class="p-0 p-lg-1 mw-100 mh-100" style="object-fit: cover;"> 533 } 534 else 535 { 536 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 537 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 538 </div> 539 } 540 </a> 541 } 542 } 543 </div> 544 545 mobileAssetThumbnailNumber++; 546 } 547 } 548 } 549 </div> 550 } 551 552 @if (showBadges) { 553 <div class="position-absolute top-0 left-0 p-2 p-lg-3"> 554 @RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms) 555 </div> 556 } 557 </div> 558 559 @* Modal with slides *@ 560 <div class="modal fade swift_products-details-images-modal" id="modal_@Model.ID" tabindex="-1" aria-labelledby="productDetailsGalleryModalTitle_@Model.ID" aria-hidden="true"> 561 <div class="modal-dialog modal-dialog-centered modal-xl"> 562 <div class="modal-content"> 563 <div class="modal-header visually-hidden"> 564 <h5 class="modal-title" id="productDetailsGalleryModalTitle_@Model.ID">@product.Title</h5> 565 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 566 </div> 567 <div class="modal-body p-2 p-lg-3 h-100"> 568 <div id="ModalCarousel_@Model.ID" class="carousel@(GetArrowsColor()) h-100" data-bs-ride="carousel"> 569 <div class="carousel-inner h-100 @theme"> 570 @foreach (MediaViewModel asset in assetsList) { 571 var assetValue = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 572 foreach (string format in allSupportedFormats) { 573 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 574 string imagePath = assetValue; 575 string activeSlide = modalAssetNumber == 0 ? "active" : ""; 576 577 var parms = new Dictionary<string, object>(); 578 parms.Add("cssClass", "d-block mw-100 mh-100 m-auto"); 579 parms.Add("columns", Model.GridRowColumnCount); 580 parms.Add("eagerLoadNewImages", Model.Item.GetBoolean("DisableLazyLoading")); 581 parms.Add("doNotUseGetimage", Model.Item.GetBoolean("DisableGetImage")); 582 583 <div class="carousel-item @activeSlide h-100" data-bs-interval="99999"> 584 @foreach (string imageFormat in supportedImageFormats) { 585 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0) { 586 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 587 } 588 } 589 590 @foreach (string videoFormat in supportedVideoFormats) { 591 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) { 592 593 Dictionary<string, object> videoParams = GetVideoParams(asset, "modal"); 594 @RenderPartial("Components/VideoPlayer.cshtml", new FileViewModel { Path = asset.Value }, videoParams); 595 596 } 597 } 598 </div> 599 600 modalAssetNumber++; 601 } 602 } 603 } 604 <button class="carousel-control-prev carousel-control-area" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="prev"> 605 <span class="carousel-control-prev-icon" aria-hidden="true"></span> 606 <span class="visually-hidden">@Translate("Previous")</span> 607 </button> 608 <button class="carousel-control-next carousel-control-area" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="next"> 609 <span class="carousel-control-next-icon" aria-hidden="true"></span> 610 <span class="visually-hidden">@Translate("Next")</span> 611 </button> 612 </div> 613 </div> 614 </div> 615 </div> 616 </div> 617 </div> 618 } else if (Pageview.IsVisualEditorMode) { 619 RatioSettings ratioSettings = GetRatioSettings("desktop"); 620 621 <div class="h-100 @theme"> 622 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 623 <img src="/Files/Images/missing_image.jpg" loading="lazy" decoding="async" class="mh-100 mw-100" style="object-fit: cover;" alt="@Translate("Missing image")"> 624 </div> 625 </div> 626 } 627 } 628
Error executing template "Designs/Swift/Paragraph/Finixa_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_59939c88d11c4f688c7071ed4e978d5d.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 @functions { 6 public ProductViewModel product { get; set; } = new ProductViewModel(); 7 public string[] supportedImageFormats { get; set; } 8 public string[] supportedVideoFormats { get; set; } 9 public string[] supportedDocumentFormats { get; set; } 10 public string[] allSupportedFormats { get; set; } 11 public class RatioSettings 12 { 13 public string Ratio { get; set; } 14 public string CssClass { get; set; } 15 public string CssVariable { get; set; } 16 public string Fill { get; set; } 17 } 18 public RatioSettings GetRatioSettings() 19 { 20 var ratioSettings = new RatioSettings(); 21 string ratio = Model.Item.GetRawValueString("ImageAspectRatio", ""); 22 ratio = ratio != "0" ? ratio : ""; 23 string cssClass = ratio != "" && ratio != "fill" ? " ratio" : ""; 24 string cssVariable = ratio != "" && ratio != "fill" ? "--bs-aspect-ratio: " + ratio : ""; 25 ratioSettings.Ratio = ratio; 26 ratioSettings.CssClass = cssClass; 27 ratioSettings.CssVariable = cssVariable; 28 ratioSettings.Fill = ratio == "fill" ? " h-100" : ""; 29 return ratioSettings; 30 } 31 // PDB: eWings MediaViewModel extender 32 public class ewiMediaViewModel : MediaViewModel 33 { 34 public string LanguageId { get; set; } 35 public string LanguageName { get; set; } 36 public string AssetSystemName { get; set; } 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 var videoParams = new Dictionary<string, object>(); 45 videoParams.Add("AssetName", asset.Name); 46 videoParams.Add("AssetVideoType", type); 47 videoParams.Add("AssetDisplayName", asset.DisplayName); 48 videoParams.Add("OpenVideoInModal", openInModal); 49 videoParams.Add("VideoAutoPlay", autoPlay); 50 videoParams.Add("Size", size); 51 videoParams.Add("Id", Model.ID); 52 return videoParams; 53 } 54 public string GetVideoType(string assetValue) 55 { 56 string type = assetValue.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "youtube" : string.Empty; 57 type = assetValue.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "vimeo" : type; 58 type = string.IsNullOrEmpty(type) ? "selfhosted" : type; 59 return type; 60 } 61 public string GetYoutubeScreenDump(string assetValue) 62 { 63 var regex = new Regex(@"(?:youtube\.com\/.*[\?&]v=|youtu\.be\/|youtube\.com\/embed\/)([\w-]+)(?:\?.*)?"); 64 Match match = regex.Match(assetValue); 65 string videoId = match.Success ? match.Groups[1].Value : string.Empty; 66 string youtubeThumbnail = $"https://img.youtube.com/vi/{videoId}/mqdefault.jpg"; 67 return youtubeThumbnail; 68 } 69 } 70 @{ 71 @* Get the product data *@ 72 ProductViewModel product = null; 73 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 74 { 75 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 76 } 77 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 78 { 79 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 80 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 81 if (productList?.Products is object) 82 { 83 product = productList.Products[0]; 84 } 85 } 86 string siteLanguage = Pageview.Area.CultureInfo.Name; 87 @* Get active website languages first *@ 88 int pageId = Dynamicweb.Context.Current.Request["CurrentPageID"] != null ? Convert.ToInt32(Dynamicweb.Context.Current.Request["CurrentPageID"]) : Pageview.ID; 89 var currentPage = Dynamicweb.Content.Services.Pages.GetPage(pageId); 90 List<Dynamicweb.Content.Page> websiteLanguages = new List<Dynamicweb.Content.Page>(); 91 if (currentPage.Area.IsMaster) 92 { 93 websiteLanguages.Add(currentPage); // Add master language 94 if (currentPage.Languages != null) 95 { 96 foreach (var lang in currentPage.Languages) 97 { 98 if (lang.Area.Active == true) // Filter active languages 99 { 100 websiteLanguages.Add(lang); 101 } 102 } 103 } 104 } 105 else 106 { 107 websiteLanguages.Add(currentPage.MasterPage); // Add master page 108 if (currentPage.MasterPage != null && currentPage.MasterPage.Languages != null) 109 { 110 foreach (var lang in currentPage.MasterPage.Languages) 111 { 112 if (lang.Area.Active == true) 113 { 114 websiteLanguages.Add(lang); 115 } 116 } 117 } 118 } 119 @* Get ecommerce languages and filter by active website languages *@ 120 var ecomLanguages = Dynamicweb.Ecommerce.Services.Languages.GetLanguages(); 121 List<Dynamicweb.Ecommerce.International.Language> activeLanguages = new List<Dynamicweb.Ecommerce.International.Language>(); 122 @* Filter ecommerce languages to only include those corresponding to active website languages *@ 123 foreach (var websiteLanguage in websiteLanguages) 124 { 125 var matchingEcomLanguage = ecomLanguages.FirstOrDefault(ecomLang => 126 string.Equals(ecomLang.Culture, websiteLanguage.Area.CultureInfo.Name, StringComparison.OrdinalIgnoreCase) || 127 string.Equals(ecomLang.Code, websiteLanguage.Area.CultureInfo.TwoLetterISOLanguageName, StringComparison.OrdinalIgnoreCase)); 128 129 if (matchingEcomLanguage != null && !activeLanguages.Contains(matchingEcomLanguage)) 130 { 131 activeLanguages.Add(matchingEcomLanguage); 132 } 133 } 134 @* Fallback: if no matches found, use all ecommerce languages *@ 135 if (activeLanguages.Count == 0) 136 { 137 activeLanguages = ecomLanguages.ToList(); 138 } 139 } 140 @if (product is object) 141 { 142 @* Supported formats *@ 143 supportedImageFormats = new string[] { ".jpg", ".jpeg", ".webp", ".png", ".gif", ".bmp", ".tiff" }; 144 supportedVideoFormats = new string[] { "youtu.be", "youtube", "vimeo", ".mp4", ".webm" }; 145 supportedDocumentFormats = new string[] { ".pdf", ".docx", ".xlsx", ".ppt", ".pptx", ".igs", ".ipt", ".sat", ".stp", ".dwg", ".dxf", ".dwf" }; 146 allSupportedFormats = supportedImageFormats.Concat(supportedVideoFormats).Concat(supportedDocumentFormats).ToArray(); 147 @* Collect the assets *@ 148 var selectedAssetCategories = Model.Item.GetList("ImageAssets")?.GetRawValue().OfType<string>(); 149 bool includeImagePatternImages = Model.Item.GetBoolean("ImagePatternImages"); 150 @* PDB: Collect the valid assets and languages *@ 151 Dynamicweb.Ecommerce.Products.ProductService ps = new Dynamicweb.Ecommerce.Products.ProductService(); 152 var dgs = Dynamicweb.Ecommerce.Services.DetailsGroups; // ✅ nieuwe manier (geen new ...DetailsGroupService) 153 154 List<ewiMediaViewModel> productAssets = new List<ewiMediaViewModel>(); 155 List<Dynamicweb.Ecommerce.Products.Product> products = new List<Dynamicweb.Ecommerce.Products.Product>(); 156 foreach (var language in ecomLanguages) 157 { 158 var productitem = ps.GetProductById(product.Id, product.VariantId, language.LanguageId); 159 if (productitem is object) 160 { 161 Dynamicweb.Ecommerce.Products.DetailService detailService = new Dynamicweb.Ecommerce.Products.DetailService(); 162 foreach (var detail in detailService.GetDetails(productitem)) 163 { 164 if (detail.LanguageId == language.LanguageId) 165 { 166 Dynamicweb.Ecommerce.Products.DetailsGroup detailsGroup = dgs.GetById(detail.GroupId); 167 if (selectedAssetCategories.Contains(detailsGroup.SystemName)) 168 { 169 // Check if this language is already in activeLanguages before adding 170 if (!activeLanguages.Any(al => al.LanguageId == language.LanguageId)) 171 { 172 activeLanguages.Add(language); 173 } 174 ewiMediaViewModel productAsset = new ewiMediaViewModel(); 175 productAsset.Value = detail.Value; 176 productAsset.AssetSystemName = detailsGroup.SystemName; 177 productAsset.LanguageId = detail.LanguageId; 178 productAsset.LanguageName = language.NativeName; 179 productAsset.DisplayName = detail.Name; 180 productAssets.Add(productAsset); 181 } 182 } 183 } 184 } 185 } 186 187 @* Standard asset collection (existing logic) *@ 188 string defaultImage = product.DefaultImage != null ? product.DefaultImage.Value : ""; 189 IEnumerable<MediaViewModel> assetsImages = selectedAssetCategories != null ? 190 product.AssetCategories.Where(x => selectedAssetCategories.Contains(x.SystemName)).SelectMany(x => x.Assets) : 191 product.AssetCategories.SelectMany(x => x.Assets); 192 assetsImages = assetsImages.OrderByDescending(x => x.Value.Equals(defaultImage)); 193 IEnumerable<MediaViewModel> assetsList = new MediaViewModel[] { }; 194 assetsList = productAssets.Cast<MediaViewModel>(); 195 assetsList = includeImagePatternImages ? assetsList.Union(product.ImagePatternImages) : assetsList; 196 assetsList = includeImagePatternImages && assetsList.Count() == 0 ? assetsList.Append(product.DefaultImage) : assetsList; 197 bool defaultImageFallback = Model.Item.GetBoolean("DefaultImageFallback"); 198 bool showOnlyPrimaryImage = Model.Item.GetBoolean("ShowOnlyPrimaryImage"); 199 int totalAssets = 0; 200 if (showOnlyPrimaryImage == false) { 201 foreach (MediaViewModel asset in assetsList) { 202 var assetValue = asset.Value; 203 foreach (string format in allSupportedFormats) { 204 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 205 totalAssets++; 206 } 207 } 208 } 209 } 210 if ((totalAssets == 0 && product.DefaultImage != null && selectedAssetCategories?.Count() == 0) || (showOnlyPrimaryImage == true && product.DefaultImage != null)) 211 { 212 assetsList = new List<MediaViewModel>(){ product.DefaultImage }; 213 totalAssets = 1; 214 } 215 @* Layout settings *@ 216 string spacing = Model.Item.GetRawValueString("Spacing", "p-0"); 217 spacing = spacing == "none" ? "p-0" : spacing; 218 spacing = spacing == "small" ? "p-3" : spacing; 219 spacing = spacing == "large" ? "p-5" : spacing; 220 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 221 bool hideThumbnails = Model.Item.GetBoolean("HideThumbnails"); 222 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 223 int modalVideoNumber = 0; 224 @* Get assets from selected categories or get all assets *@ 225 if (totalAssets != 0 && assetsList.Any()) 226 { 227 <div class="@spacing@(theme) item_@Model.Item.SystemName.ToLower()"> 228 @if (!string.IsNullOrEmpty(Model.Item.GetString("Title")) && !Model.Item.GetBoolean("HideTitle")) 229 { 230 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "h3"); 231 <h3 class="@titleFontSize mb-3"> 232 @Model.Item.GetString("Title") 233 </h3> 234 } 235 @if (activeLanguages.Count > 1) 236 { 237 238 // Filter activeLanguages als deze taal assets bevat 239 var languagesWithAssets = new List<Dynamicweb.Ecommerce.International.Language>(); 240 241 foreach (var lang in activeLanguages) 242 { 243 bool hasAssets = false; 244 foreach (MediaViewModel asset in assetsList) 245 { 246 try { 247 if (asset.GetType().GetProperty("LanguageId") != null) 248 { 249 var assetLangId = asset.GetType().GetProperty("LanguageId").GetValue(asset)?.ToString() ?? ""; 250 if (assetLangId == lang.LanguageId) 251 { 252 hasAssets = true; 253 break; 254 } 255 } 256 } catch { } 257 } 258 259 if (hasAssets) 260 { 261 languagesWithAssets.Add(lang); 262 } 263 } 264 265 Dynamicweb.Ecommerce.International.Language currentLanguage = null; 266 string currentLanguageValue = "ALL"; 267 268 // Toon enkel de taal dropdown als er meer dan 1 taal is met assets 269 if (languagesWithAssets.Count > 1) 270 { 271 currentLanguage = languagesWithAssets.FirstOrDefault(lang => 272 string.Equals(lang.Culture, siteLanguage, StringComparison.OrdinalIgnoreCase)) ?? 273 languagesWithAssets.FirstOrDefault(lang => 274 lang.NativeName.StartsWith("English", StringComparison.OrdinalIgnoreCase)) ?? 275 languagesWithAssets.FirstOrDefault(); 276 277 // Map any English variant to LANG1 278 currentLanguageValue = currentLanguage != null && 279 currentLanguage.NativeName.StartsWith("English", StringComparison.OrdinalIgnoreCase) 280 ? "LANG1" : currentLanguage?.LanguageId ?? "ALL"; 281 } 282 283 284 @if (languagesWithAssets.Count > 1) 285 { 286 <div class="mb-3"> 287 <div class="mb-1">@Translate("Language")</div> 288 <div class="dropdown js-dropdown"> 289 <button class="form-select text-start w-100 js-dropdown-btn" type="button" data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-expanded="false"> 290 @(currentLanguage?.NativeName ?? "All") 291 </button> 292 <div class="dropdown-menu w-100 p-3"> 293 <div class="form-check"> 294 <input class="form-check-input" type="checkbox" name="LanguageId" value="ALL" id="LanguageCheckAll" onchange="filterResults('ALL', event);"> 295 <label class="form-check-label" for="LanguageCheckAll"> 296 @Translate("All") 297 </label> 298 </div> 299 @foreach (var language in languagesWithAssets 300 .GroupBy(l => l.NativeName.StartsWith("English", StringComparison.OrdinalIgnoreCase) ? "English" : l.NativeName) 301 .Select(g => g.First())) 302 { 303 bool isEnglishGroup = language.NativeName.StartsWith("English", StringComparison.OrdinalIgnoreCase); 304 string languageValue = isEnglishGroup ? "LANG1" : language.LanguageId; 305 string displayName = isEnglishGroup ? "English" : language.NativeName; 306 bool isCurrentLanguage = (currentLanguageValue == languageValue); 307 <div class="form-check"> 308 <input class="form-check-input" type="checkbox" name="LanguageId" data-short-name="@displayName" value="@languageValue" id="Check_@languageValue" @(isCurrentLanguage ? "checked" : "") onchange="filterResults('@languageValue', event);"> 309 <label class="form-check-label" for="Check_@languageValue"> 310 @displayName 311 </label> 312 </div> 313 } 314 </div> 315 </div> 316 </div> 317 } 318 } 319 <div class="table-responsive"> 320 <table class="table table-hover align-middle mb-0" style="table-layout: fixed;" id="download-table"> 321 <thead> 322 <tr> 323 @* @if (!hideThumbnails) 324 { 325 <th style="width:60px"> </th> 326 } *@ 327 <th>@Translate("Name")</th> 328 <th>@Translate("Asset type")</th> 329 <th>@Translate("Language")</th> 330 <th class="text-end" style="width:100px">@Translate("File type")</th> 331 <th class="text-end d-none d-lg-table-cell">@Translate("Download")</th> 332 </tr> 333 </thead> 334 <tbody class="border-top-0"> 335 @foreach (ewiMediaViewModel asset in productAssets) 336 { 337 var assetValue = asset.Value; 338 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Value.Substring(asset.Value.LastIndexOf('/') + 1); 339 340 // Skip videos 341 bool isVideo = false; 342 foreach (string format in supportedVideoFormats) 343 { 344 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 345 { 346 isVideo = true; 347 } 348 } 349 350 // Only process non-video files 351 if (!isVideo) 352 { 353 // Check if file exists 354 long fileSize = 0; 355 string filePath = Dynamicweb.Context.Current.Server.MapPath(assetValue); 356 if (File.Exists(filePath)) 357 { 358 fileSize = new System.IO.FileInfo(filePath) != null ? new System.IO.FileInfo(filePath).Length / 1024 : 0; 359 // Check if it's a supported format 360 if (allSupportedFormats.Any(format => assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0)) 361 { 362 // Get file extension 363 string fileExtension = Path.GetExtension(assetValue).ToLower(); 364 365 // Map any English variant (English, English (US), etc.) to LANG1 for filtering 366 string languageId = asset.LanguageName.StartsWith("English", StringComparison.OrdinalIgnoreCase) ? "LANG1" : asset.LanguageId; 367 368 <tr data-language-ids="@languageId" data-language-name="@asset.LanguageName"> 369 @* @if (!hideThumbnails) 370 { 371 <td class="px-0"> 372 @{ 373 string productName = product.Name; 374 string imagePath = asset.Value; 375 string imageLinkPath = imagePath; 376 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 377 RatioSettings ratioSettings = GetRatioSettings(); 378 } 379 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@productName"> 380 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 381 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" /> 382 </div> 383 </a> 384 </td> 385 } *@ 386 <td> 387 <span class="hide-on-desktop">@Translate("Name")</span> 388 <span>@assetName</span> 389 </td> 390 <td> 391 <span class="hide-on-desktop">@Translate("Asset type")</span> 392 <span>@asset.AssetSystemName</span> 393 </td> 394 <td> 395 <span class="hide-on-desktop">@Translate("Language")</span> 396 <span>@asset.LanguageName</span> 397 </td> 398 <td class="no-border-bottom"> 399 <span class="hide-on-desktop">@Translate("File type")</span> 400 <span>@fileExtension</span> 401 </td> 402 <td class="text-end"> 403 <a href="@assetValue" class="text-decoration-none" download="@assetName" title="@assetName"> 404 @fileSize KB <div class="icon-2" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 405 </a> 406 </td> 407 </tr> 408 } 409 } 410 } 411 } 412 </tbody> 413 </table> 414 </div> 415 <script> 416 // Global (site-wide) key + legacy per-paragraph key 417 const masterLangKey = 'finixa_assets_lang_master'; 418 const paragraphLangKey = 'finixa_assets_lang_@Model.ID'; 419 420 function applyButtonLabel(values) { 421 const button = document.querySelector('.js-dropdown-btn'); 422 if (!button) return; 423 if (!values.length || values.includes('ALL')) { 424 button.textContent = '@Translate("All")'; 425 } else { 426 const cb = document.querySelector(`input[name="LanguageId"][value="${values[0]}"]`); 427 button.textContent = cb ? (cb.getAttribute('data-short-name') || '@Translate("All")') : '@Translate("All")'; 428 } 429 } 430 431 function filterRows(values) { 432 const table = document.getElementById('download-table'); 433 if (!table) return; 434 const rows = table.querySelectorAll('tbody tr'); 435 if (!values.length || values.includes('ALL')) { 436 rows.forEach(r => r.style.display = ''); 437 } else { 438 rows.forEach(r => { 439 const rowLanguageId = r.getAttribute('data-language-ids'); 440 r.style.display = values.includes(rowLanguageId) ? '' : 'none'; 441 }); 442 } 443 } 444 445 function persistSelection(val) { 446 try { 447 if (!val) { 448 localStorage.removeItem(masterLangKey); 449 localStorage.removeItem(paragraphLangKey); 450 } else { 451 localStorage.setItem(masterLangKey, val); // global 452 localStorage.setItem(paragraphLangKey, val); // legacy / per paragraph 453 } 454 } catch(e){} 455 } 456 457 function filterResults(selectedLang, event) { 458 if (event) event.stopPropagation(); 459 460 // Checkbox logic (single select + ALL) 461 if (selectedLang === 'ALL') { 462 const allCb = document.querySelector('input[name="LanguageId"][value="ALL"]'); 463 if (allCb && allCb.checked) { 464 document.querySelectorAll('input[name="LanguageId"]:not([value="ALL"])').forEach(cb => cb.checked = false); 465 } 466 } else { 467 const allCb = document.querySelector('input[name="LanguageId"][value="ALL"]'); 468 if (allCb) allCb.checked = false; 469 document.querySelectorAll('input[name="LanguageId"]:not([value="ALL"])').forEach(cb => { 470 if (cb.value !== selectedLang) cb.checked = false; 471 }); 472 } 473 474 const checked = Array.from(document.querySelectorAll('input[name="LanguageId"]:checked')).map(cb => cb.value); 475 476 // Persist (store ONLY the first or ALL) 477 persistSelection(checked.length ? checked[0] : null); 478 479 applyButtonLabel(checked); 480 filterRows(checked); 481 } 482 483 (function initAssetLanguageFilter() { 484 let stored = null; 485 try { 486 // Prefer global master; fallback to paragraph key 487 stored = localStorage.getItem(masterLangKey) || localStorage.getItem(paragraphLangKey); 488 } catch(e){} 489 490 // If stored language exists & checkbox present, use it 491 if (stored) { 492 const target = document.querySelector(`input[name="LanguageId"][value="${stored}"]`); 493 if (target) { 494 document.querySelectorAll('input[name="LanguageId"]').forEach(cb => cb.checked = false); 495 target.checked = true; 496 } else { 497 // Stored value no longer available 498 try { 499 if (localStorage.getItem(masterLangKey) === stored) localStorage.removeItem(masterLangKey); 500 if (localStorage.getItem(paragraphLangKey) === stored) localStorage.removeItem(paragraphLangKey); 501 } catch(e){} 502 } 503 } 504 505 // If nothing checked, keep server default OR fallback to ALL 506 let anyChecked = document.querySelector('input[name="LanguageId"]:checked'); 507 if (!anyChecked) { 508 const fallback = document.querySelector('input[name="LanguageId"][value="ALL"]') || 509 document.querySelector('input[name="LanguageId"]'); 510 if (fallback) { 511 fallback.checked = true; 512 anyChecked = fallback; 513 } 514 } 515 516 const initialVal = anyChecked ? anyChecked.value : 'ALL'; 517 // Ensure global master reflects current (for first run / migration) 518 persistSelection(initialVal); 519 520 filterResults(initialVal); 521 })(); 522 </script> 523 @foreach (MediaViewModel asset in assetsList) 524 { 525 var assetName = asset.Value.ToLower(); 526 foreach (string videoFormat in supportedVideoFormats) 527 { //Videos 528 if (assetName.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) 529 { 530 <div class="modal fade js-video-modal" id="modal_@(Model.ID)_@modalVideoNumber" tabindex="-1" aria-labelledby="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber" aria-hidden="true"> 531 <div class="modal-dialog modal-dialog-centered modal-xl"> 532 <div class="modal-content"> 533 <div class="modal-header visually-hidden"> 534 <h5 class="modal-title" id="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber">@product.Title</h5> 535 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 536 </div> 537 <div class="modal-body p-2 p-lg-3 h-100"> 538 @{ 539 var videoParams = GetVideoParams(asset, "modal"); 540 @RenderPartial("Components/VideoPlayer.cshtml", new Dynamicweb.Frontend.FileViewModel { Path = asset.Value }, videoParams) 541 } 542 </div> 543 </div> 544 </div> 545 </div> 546 modalVideoNumber++; 547 } 548 } 549 } 550 </div> 551 } 552 else if (Pageview.IsVisualEditorMode) 553 { 554 <div class="h-100 @theme"> 555 <div class="alert alert-dark m-0"> 556 @Translate("No assets are available") 557 </div> 558 </div> 559 } 560 } 561
Gerelateerde producten
Sorry. There is nothing to view here