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_1f3f1e1d4bd64ca1bd02f7e2ea4cac36.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: @Model.Price.ToStringInvariant(), 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: @Model.Price.ToStringInvariant(), 61 discount: @Model.Discount.ToStringInvariant() 62 } 63 ] 64 }); 65 </script> 66 67 <script> 68 window.addEventListener('load', function (event) { 69 swift.Video.init(); 70 }); 71 </script> 72
Error executing template "/Designs/Swift/Paragraph/Swift_ProductDetailsImage_Custom.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_9657692fc08f4eb3958effca089d8872.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 bool useThumbnailSlider = totalAssets > 5; 190 string carouselId = "SmallScreenImages_" + Model.ID; 191 string thumbnailsId = "SmallScreenImagesThumbnails_" + Model.ID; 192 string thumbnailAxisCss = GetThumbnailPlacement() == "bottom" ? "flex-column" : string.Empty; 193 194 @* Get assets from selected categories or get all assets *@ 195 if (totalAssets != 0) 196 { 197 int assetNumber = 0; 198 int thumbnailNumber = 0; 199 int modalAssetNumber = 0; 200 201 <div class="d-flex gap-3 h-100 swift-pdi @(thumbnailAxisCss) @(theme) item_@Model.Item.SystemName.ToLower()"> 202 <div id="@carouselId" class="carousel@(GetArrowsColor()) col position-relative" data-bs-ride="carousel"> 203 <div class="carousel-inner h-100"> 204 @foreach (MediaViewModel asset in assetsList) 205 { 206 var assetValue = asset.Value; 207 foreach (string format in allSupportedFormats) 208 { 209 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 210 { 211 string activeSlide = assetNumber == 0 ? "active" : ""; 212 213 <div class="carousel-item @activeSlide" data-bs-interval="99999"> 214 @{ 215 string size = "mobile"; 216 217 string imageTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 218 219 220 <div class="h-100 @(imageTheme)"> 221 @foreach (string imageFormat in supportedImageFormats) 222 { //Images 223 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0) 224 { 225 if (product is object) 226 { 227 string productName = product.Name; 228 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 229 string imageLinkPath = Uri.EscapeDataString(imagePath); 230 231 RatioSettings ratioSettings = GetRatioSettings(size); 232 233 var parms = new Dictionary<string, object>(); 234 parms.Add("alt", productName + asset.Keywords); 235 parms.Add("itemprop", "image"); 236 parms.Add("columns", Model.GridRowColumnCount); 237 parms.Add("eagerLoadNewImages", Model.Item.GetBoolean("DisableLazyLoading")); 238 parms.Add("doNotUseGetimage", Model.Item.GetBoolean("DisableGetImage")); 239 if (!string.IsNullOrEmpty(asset.DisplayName)) 240 { 241 parms.Add("title", asset.DisplayName); 242 } 243 244 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") 245 { 246 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); 247 } 248 else 249 { 250 parms.Add("cssClass", "mw-100 mh-100"); 251 } 252 253 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> 254 <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"> 255 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 256 </div> 257 </a> 258 } 259 } 260 } 261 @foreach (string videoFormat in supportedVideoFormats) 262 { //Videos 263 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) 264 { 265 if (Model.Item.GetString("OpenVideoInModal") == "true") 266 { 267 if (product is object) 268 { 269 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 270 271 string productName = product.Name; 272 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 273 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 274 275 RatioSettings ratioSettings = GetRatioSettings(size); 276 277 string type = GetVideoType(asset.Value); 278 279 string videoScreendumpPath = type == "youtube" ? GetYoutubeScreenDump(asset.Value, "maxresdefault") : string.Empty; 280 videoScreendumpPath = type == "selfhosted" ? System.Uri.EscapeUriString(asset.Value) : videoScreendumpPath; 281 string videoJsClass = type == "vimeo" ? "js-vimeo-video-thumbnail" : ""; 282 283 284 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable); cursor: pointer" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> 285 <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"> 286 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 287 @if (type != "selfhosted") 288 { 289 <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;"> 290 } 291 else 292 { 293 string videoType = Path.GetExtension(asset.Value).ToLower(); 294 295 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 296 <source src="@(videoScreendumpPath)#t=0.001" type="video/@videoType.Replace(".", "")"> 297 </video> 298 } 299 </div> 300 </div> 301 302 } 303 } 304 else 305 { 306 if (product is object) 307 { 308 var videoParams = GetVideoParams(asset, size); 309 @RenderPartial("Components/VideoPlayer.cshtml", new FileViewModel { Path = asset.Value}, videoParams); 310 311 } 312 } 313 } 314 } 315 @foreach (string documentFormat in supportedDocumentFormats) 316 { //Documents 317 if (assetValue.IndexOf(documentFormat, StringComparison.OrdinalIgnoreCase) >= 0) 318 { 319 if (product is object) 320 { 321 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 322 323 string productName = product.Name; 324 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 325 string imageLinkPath = Uri.EscapeDataString(imagePath); 326 327 RatioSettings ratioSettings = GetRatioSettings(size); 328 329 var parms = new Dictionary<string, object>(); 330 parms.Add("alt", productName + asset.Keywords); 331 parms.Add("itemprop", "image"); 332 parms.Add("fullwidth", true); 333 parms.Add("columns", Model.GridRowColumnCount); 334 if (!string.IsNullOrEmpty(asset.DisplayName)) 335 { 336 parms.Add("title", asset.DisplayName); 337 } 338 339 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") 340 { 341 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); 342 } 343 else 344 { 345 parms.Add("cssClass", "mw-100 mh-100"); 346 } 347 348 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@Translate("Download")"> 349 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 350 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 351 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) 352 { 353 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 354 } 355 </div> 356 </a> 357 } 358 359 } 360 } 361 </div> 362 } 363 364 365 </div> 366 assetNumber++; 367 } 368 } 369 } 370 </div> 371 @if (showBadges) 372 { 373 <div class="position-absolute top-0 left-0 p-2 p-lg-3"> 374 @{@RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms)} 375 </div> 376 } 377 378 </div> 379 380 @if (totalAssets > 1) 381 { 382 @if (useThumbnailSlider) 383 { 384 <div class="product-thumb-slider-wrapper" data-carousel-id="@carouselId"> 385 <button class="product-thumb-btn product-thumb-prev" type="button" data-target="@thumbnailsId" aria-label="Previous">‹</button> 386 <div class="@(GetThumbnailRowSettingCss()) gap-2 product-thumb-track" id="@thumbnailsId"> 387 @foreach (MediaViewModel asset in assetsList) 388 { 389 var assetValue = asset.Value; 390 string assetName = asset.Name; 391 assetName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 392 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : null; 393 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 394 395 string imagePath = Dynamicweb.Context.Current.Server.UrlEncode(assetValue); 396 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; 397 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; 398 399 RatioSettings ratioSettings = GetRatioSettings("desktop"); 400 401 <div class="border outline-none product-thumbnail-item @(ratioSettings.CssClass)" style="@(ratioSettings.CssVariable); cursor: pointer; min-width: 7rem; max-width: 8rem; flex-shrink: 0;" data-bs-target="#@carouselId" data-bs-slide-to="@thumbnailNumber" data-thumb-index="@thumbnailNumber"> 402 @foreach (string imageFormat in supportedImageFormats) 403 { //Images 404 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0) 405 { 406 <img src="@imagePathThumb" alt="@assetName" @(assetTitle != null ? $"title=\"{assetTitle}\"" : "") class="p-0 p-lg-1 w-100 h-100" style="object-fit: contain;"> 407 thumbnailNumber++; 408 } 409 } 410 411 @foreach (string videoFormat in supportedVideoFormats) 412 { //Videos 413 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) 414 { 415 string type = GetVideoType(asset.Value); 416 417 string videoScreendumpPath = type == "youtube" ? GetYoutubeScreenDump(asset.Value, "mqdefault") : ""; 418 videoScreendumpPath = type == "vimeo" ? string.Empty : videoScreendumpPath; 419 videoScreendumpPath = type == "selfhosted" ? System.Uri.EscapeUriString(asset.Value) : videoScreendumpPath; 420 string videoJsClass = type == "vimeo" ? "js-vimeo-video-thumbnail" : string.Empty; 421 422 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 423 424 if (type != "selfhosted") 425 { 426 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@assetTitle" @(assetTitle != null ? $"title=\"{assetTitle}\"" : "") class="@videoJsClass mw-100 mh-100" data-asset-value="@asset.Value" style="object-fit: cover;" /> 427 } 428 else 429 { 430 string videoType = Path.GetExtension(asset.Value).ToLower(); 431 432 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 433 <source src="@(videoScreendumpPath)#t=0.001" type="video/@videoType.Replace(".", "")"> 434 </video> 435 } 436 437 thumbnailNumber++; 438 } 439 } 440 441 @foreach (string documentFormat in supportedDocumentFormats) 442 { //Documents 443 if (assetValue.IndexOf(documentFormat, StringComparison.OrdinalIgnoreCase) >= 0) 444 { 445 <a href="@Uri.EscapeDataString(assetValue)" class="ratio ratio-4x3 border outline-none" style="cursor: pointer; min-width: 7rem; max-width: 8rem;" download title="@asset.Value"> 446 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) 447 { 448 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 449 <div class="icon-3 position-absolute text-light" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 450 </div> 451 <img src="@imagePathThumb" alt="@assetName" @(assetTitle != null ? $"title=\"{assetTitle}\"" : "") class="p-0 p-lg-1 mw-100 mh-100" style="object-fit: cover;"> 452 } 453 else 454 { 455 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 456 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 457 </div> 458 } 459 </a> 460 461 thumbnailNumber++; 462 } 463 } 464 </div> 465 } 466 </div> 467 <button class="product-thumb-btn product-thumb-next" type="button" data-target="@thumbnailsId" aria-label="Next">›</button> 468 </div> 469 } 470 else 471 { 472 <div class="@(GetThumbnailRowSettingCss()) gap-3" id="@thumbnailsId"> 473 @foreach (MediaViewModel asset in assetsList) 474 { 475 var assetValue = asset.Value; 476 string assetName = asset.Name; 477 assetName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 478 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : null; 479 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 480 481 string imagePath = Dynamicweb.Context.Current.Server.UrlEncode(assetValue); 482 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; 483 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; 484 485 RatioSettings ratioSettings = GetRatioSettings("desktop"); 486 487 <div class="border outline-none product-thumbnail-item @(ratioSettings.CssClass)" style="@(ratioSettings.CssVariable); cursor: pointer; min-width: 7rem; max-width: 8rem;" data-bs-target="#@carouselId" data-bs-slide-to="@thumbnailNumber" data-thumb-index="@thumbnailNumber"> 488 @foreach (string imageFormat in supportedImageFormats) 489 { //Images 490 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0) 491 { 492 <img src="@imagePathThumb" alt="@assetName" @(assetTitle != null ? $"title=\"{assetTitle}\"" : "") class="p-0 p-lg-1 w-100 h-100" style="object-fit: contain;"> 493 thumbnailNumber++; 494 } 495 } 496 497 @foreach (string videoFormat in supportedVideoFormats) 498 { //Videos 499 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) 500 { 501 string type = GetVideoType(asset.Value); 502 503 string videoScreendumpPath = type == "youtube" ? GetYoutubeScreenDump(asset.Value, "mqdefault") : ""; 504 videoScreendumpPath = type == "vimeo" ? string.Empty : videoScreendumpPath; 505 videoScreendumpPath = type == "selfhosted" ? System.Uri.EscapeUriString(asset.Value) : videoScreendumpPath; 506 string videoJsClass = type == "vimeo" ? "js-vimeo-video-thumbnail" : string.Empty; 507 508 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 509 510 if (type != "selfhosted") 511 { 512 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@assetTitle" @(assetTitle != null ? $"title=\"{assetTitle}\"" : "") class="@videoJsClass mw-100 mh-100" data-asset-value="@asset.Value" style="object-fit: cover;" /> 513 } 514 else 515 { 516 string videoType = Path.GetExtension(asset.Value).ToLower(); 517 518 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 519 <source src="@(videoScreendumpPath)#t=0.001" type="video/@videoType.Replace(".", "")"> 520 </video> 521 } 522 523 thumbnailNumber++; 524 } 525 } 526 527 @foreach (string documentFormat in supportedDocumentFormats) 528 { //Documents 529 if (assetValue.IndexOf(documentFormat, StringComparison.OrdinalIgnoreCase) >= 0) 530 { 531 <a href="@Uri.EscapeDataString(assetValue)" class="ratio ratio-4x3 border outline-none" style="cursor: pointer; min-width: 7rem; max-width: 8rem;" download title="@asset.Value"> 532 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) 533 { 534 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 535 <div class="icon-3 position-absolute text-light" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 536 </div> 537 <img src="@imagePathThumb" alt="@assetName" @(assetTitle != null ? $"title=\"{assetTitle}\"" : "") class="p-0 p-lg-1 mw-100 mh-100" style="object-fit: cover;"> 538 } 539 else 540 { 541 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 542 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 543 </div> 544 } 545 </a> 546 547 thumbnailNumber++; 548 } 549 } 550 </div> 551 } 552 </div> 553 } 554 } 555 </div> 556 557 @* Modal with slides *@ 558 <div class="modal fade swift_products-details-images-modal" id="modal_@Model.ID" tabindex="-1" aria-labelledby="productDetailsGalleryModalTitle_@Model.ID" aria-hidden="true"> 559 <div class="modal-dialog modal-dialog-centered modal-xl"> 560 <div class="modal-content"> 561 <div class="modal-header visually-hidden"> 562 <h5 class="modal-title" id="productDetailsGalleryModalTitle_@Model.ID">@product.Title</h5> 563 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 564 </div> 565 <div class="modal-body p-2 p-lg-3 h-100"> 566 <div id="ModalCarousel_@Model.ID" class="carousel@(GetArrowsColor()) h-100" data-bs-ride="carousel"> 567 <div class="carousel-inner h-100 @theme"> 568 @foreach (MediaViewModel asset in assetsList) 569 { 570 var assetValue = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 571 foreach (string supportedFormat in supportedImageFormats.Concat(supportedVideoFormats).ToArray()) 572 { 573 if (assetValue.IndexOf(supportedFormat, StringComparison.OrdinalIgnoreCase) >= 0) 574 { 575 string imagePath = assetValue; 576 string activeSlide = modalAssetNumber == 0 ? "active" : ""; 577 578 var parms = new Dictionary<string, object>(); 579 parms.Add("cssClass", "d-block mw-100 mh-100 m-auto"); 580 parms.Add("fullwidth", true); 581 parms.Add("columns", Model.GridRowColumnCount); 582 583 <div class="carousel-item @activeSlide h-100" data-bs-interval="99999"> 584 @foreach (string imageFormat in supportedImageFormats) 585 { //Images 586 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0) 587 { 588 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 589 } 590 } 591 592 @foreach (string videoFormat in supportedVideoFormats) 593 { //Videos 594 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) 595 { 596 var videoParams = GetVideoParams(asset, "modal"); 597 @RenderPartial("Components/VideoPlayer.cshtml", new FileViewModel { Path = asset.Value }, videoParams) 598 } 599 } 600 </div> 601 modalAssetNumber++; 602 } 603 } 604 } 605 <button class="carousel-control-prev carousel-control-area" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="prev"> 606 <span class="carousel-control-prev-icon" aria-hidden="true"></span> 607 <span class="visually-hidden">@Translate("Previous")</span> 608 </button> 609 <button class="carousel-control-next carousel-control-area" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="next"> 610 <span class="carousel-control-next-icon" aria-hidden="true"></span> 611 <span class="visually-hidden">@Translate("Next")</span> 612 </button> 613 </div> 614 </div> 615 </div> 616 </div> 617 </div> 618 </div> 619 } 620 else if (Pageview.IsVisualEditorMode) 621 { 622 RatioSettings ratioSettings = GetRatioSettings("desktop"); 623 624 <div class="h-100 @theme"> 625 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 626 <img src="/Files/Images/missing_image.jpg" loading="lazy" decoding="async" class="mh-100 mw-100" style="object-fit: cover;"> 627 </div> 628 </div> 629 } 630 } 631 else if (Pageview.IsVisualEditorMode) 632 { 633 <div class="alert alert-dark m-0">@Translate("No products available")</div> 634 } 635
Logga in för att se saldo och pris
För att kunna se lagerstatus och dina priser behöver du logga in på ditt konto.
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_33ac11991696431faaf48aac59e3eec2.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"> </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> </td> 425 <td> </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"> </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
Error executing template "Designs/Swift/Paragraph/Swift_ProductDetailsMediaTable_Custom.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_1693e9778af843c397a5e471e53d7f97.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 { 124 foreach (MediaViewModel asset in assetsList) 125 { 126 var assetValue = asset.Value; 127 foreach (string format in allSupportedFormats) 128 { 129 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 130 { 131 totalAssets++; 132 } 133 } 134 } 135 } 136 137 if ((totalAssets == 0 && product.DefaultImage != null && selectedAssetCategories.Count() == 0) || (showOnlyPrimaryImage == true && product.DefaultImage != null)) 138 { 139 assetsList = new List<MediaViewModel>() { product.DefaultImage }; 140 totalAssets = 1; 141 } 142 143 int videoNumber = 0; 144 145 @* Layout settings *@ 146 string spacing = Model.Item.GetRawValueString("Spacing", "p-0"); 147 spacing = spacing == "none" ? "p-0" : spacing; 148 spacing = spacing == "small" ? "p-3" : spacing; 149 spacing = spacing == "large" ? "p-5" : spacing; 150 151 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 152 153 bool hideThumbnails = Model.Item.GetBoolean("HideThumbnails"); 154 155 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 156 int modalVideoNumber = 0; 157 158 @* Get assets from selected categories or get all assets *@ 159 160 if (totalAssets != 0 && assetsList.Any()) 161 { 162 <div class="accordion-item" style="border: 0;"> 163 <h2 class="accordion-header" id="heading-media"> 164 <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-media" aria-expanded="false" aria-controls="collapse-media"> 165 @Model.Item.GetString("Title") 166 </button> 167 </h2> 168 <div id="collapse-media" class="accordion-collapse collapse" aria-labelledby="heading-media" data-bs-parent="#accordion-media"> 169 <div class="accordion-body mb-0-last-child opacity-75"> 170 171 172 <div class="@spacing@(theme) item_@Model.Item.SystemName.ToLower()"> 173 @if (!string.IsNullOrEmpty(Model.Item.GetString("Title")) && !Model.Item.GetBoolean("HideTitle")) 174 { 175 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "h3"); 176 } 177 178 <div class="table-responsive"> 179 <table class="table table-hover align-middle mb-0" style="table-layout: fixed;"> 180 <thead> 181 <tr> 182 @if (!hideThumbnails) 183 { 184 <th style="width:60px"> </th> 185 } 186 <th>@Translate("Name")</th> 187 <th class="text-end d-none d-lg-table-cell">@Translate("Download")</th> 188 <th class="text-end" style="width:100px">@Translate("File type")</th> 189 </tr> 190 </thead> 191 <tbody class="border-top-0"> 192 @foreach (MediaViewModel asset in assetsList) 193 { 194 var assetValue = asset.Value; 195 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Value.Substring(asset.Value.LastIndexOf('/') + 1); 196 197 bool isVideo = false; 198 foreach (string format in supportedVideoFormats) 199 { //Videos 200 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 201 { 202 isVideo = true; 203 } 204 } 205 206 if (!isVideo) 207 { 208 if (assetValue.StartsWith("/Files/", StringComparison.OrdinalIgnoreCase)) 209 { 210 string filePath = Dynamicweb.Context.Current.Server.MapPath(assetValue); 211 long fileSize = 0; 212 213 if (File.Exists(filePath)) 214 { 215 fileSize = new System.IO.FileInfo(filePath) != null ? new System.IO.FileInfo(filePath).Length / 1024 : 0; 216 217 foreach (string format in allSupportedFormats) 218 { 219 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 220 { 221 <tr> 222 @if (!hideThumbnails) 223 { 224 //From @RenderAsset 225 string imageTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 226 227 <td class="@(imageTheme) px-0"> 228 @foreach (string imageFormat in supportedImageFormats) 229 { //Images 230 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0) 231 { 232 string productName = product.Name; 233 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 234 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 235 string imageLinkPath = imagePath; 236 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 237 imagePath = !imageLinkPath.StartsWith("/Files/", StringComparison.OrdinalIgnoreCase) ? asset.Value : imagePath; 238 239 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 240 241 RatioSettings ratioSettings = GetRatioSettings(); 242 243 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download> 244 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 245 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle itemprop="image"> 246 </div> 247 </a> 248 } 249 } 250 @foreach (string videoFormat in supportedVideoFormats) 251 { //Videos 252 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) 253 { 254 string productName = product.Name; 255 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 256 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 257 258 RatioSettings ratioSettings = GetRatioSettings(); 259 260 string type = GetVideoType(asset.Value); 261 string videoScreendumpPath = type == "youtube" ? GetYoutubeScreenDump(asset.Value) : string.Empty; 262 videoScreendumpPath = type == "selfhosted" ? System.Uri.EscapeUriString(asset.Value) : videoScreendumpPath; 263 string videoJsClass = type == "vimeo" ? "js-vimeo-video-thumbnail" : string.Empty; 264 265 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 266 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 267 <div class="icon-2 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 268 @if (type != "selfhosted") 269 { 270 <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;"> 271 } 272 else 273 { 274 string videoType = Path.GetExtension(asset.Value).ToLower(); 275 276 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 277 <source src="@(videoScreendumpPath)#t=0.001" type="video/@videoType.Replace(".", "")"> 278 </video> 279 } 280 </div> 281 </div> 282 } 283 } 284 @foreach (string documentFormat in supportedDocumentFormats) 285 { //Documents 286 if (assetValue.IndexOf(documentFormat, StringComparison.OrdinalIgnoreCase) >= 0) 287 { 288 string productName = product.Name; 289 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 290 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 291 string imageLinkPath = imagePath; 292 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 293 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 294 295 RatioSettings ratioSettings = GetRatioSettings(); 296 297 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@productName"> 298 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) 299 { 300 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 301 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle /> 302 </div> 303 } 304 else 305 { 306 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 307 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 308 </div> 309 } 310 </a> 311 } 312 } 313 </td> 314 315 } 316 <td> 317 <a href="@assetValue" class="text-decoration-none text-break" download="@assetName" title="@assetName"> 318 @assetName 319 </a> 320 </td> 321 <td class="text-end d-none d-lg-table-cell"> 322 <a href="@assetValue" class="text-decoration-none" download="@assetName" title="@assetName"> 323 @fileSize KB <div class="icon-2" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 324 </a> 325 </td> 326 <td class="text-end">@format</td> 327 </tr> 328 } 329 } 330 } 331 } 332 else 333 { 334 <tr> 335 @if (!hideThumbnails) 336 { 337 //From @RenderAsset 338 string imageTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 339 340 <td class="@(imageTheme) px-0"> 341 @foreach (string format in supportedImageFormats) 342 { //Images 343 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 344 { 345 string productName = product.Name; 346 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 347 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 348 string imageLinkPath = imagePath; 349 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 350 imagePath = !imageLinkPath.StartsWith("/Files/", StringComparison.OrdinalIgnoreCase) ? asset.Value : imagePath; 351 352 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 353 354 RatioSettings ratioSettings = GetRatioSettings(); 355 356 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download> 357 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 358 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle itemprop="image"> 359 </div> 360 </a> 361 } 362 } 363 @foreach (string format in supportedVideoFormats) 364 { //Videos 365 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 366 { 367 string type = GetVideoType(asset.Value); 368 string videoScreendumpPath = type == "youtube" ? GetYoutubeScreenDump(asset.Value) : string.Empty; 369 videoScreendumpPath = type == "selfhosted" ? System.Uri.EscapeUriString(asset.Value) : videoScreendumpPath; 370 string videoJsClass = type == "vimeo" ? "js-vimeo-video-thumbnail" : string.Empty; 371 372 373 string productName = product.Name; 374 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 375 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 376 377 RatioSettings ratioSettings = GetRatioSettings(); 378 379 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 380 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 381 <div class="icon-2 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 382 @if (type != "selfhosted") 383 { 384 <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;"> 385 } 386 else 387 { 388 string videoType = Path.GetExtension(asset.Value).ToLower(); 389 390 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 391 <source src="@(videoScreendumpPath)#t=0.001" type="video/@videoType.Replace(".", "")"> 392 </video> 393 } 394 </div> 395 </div> 396 } 397 } 398 @foreach (string format in supportedDocumentFormats) 399 { //Documents 400 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 401 { 402 string productName = product.Name; 403 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 404 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 405 string imageLinkPath = imagePath; 406 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 407 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 408 409 RatioSettings ratioSettings = GetRatioSettings(); 410 411 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@productName"> 412 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) 413 { 414 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 415 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle /> 416 </div> 417 } 418 else 419 { 420 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 421 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 422 </div> 423 } 424 </a> 425 } 426 } 427 </td> 428 } 429 <td> 430 <a href="@assetValue" class="text-decoration-none text-break" download="@assetName" title="@assetName"> 431 @assetName 432 </a> 433 </td> 434 <td> </td> 435 <td> </td> 436 </tr> 437 } 438 } 439 else 440 { 441 string videoType = asset.Value.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || asset.Value.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "Youtube" : ""; 442 videoType = asset.Value.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "Vimeo" : videoType; 443 444 <tr data-bs-toggle="modal" data-bs-target="#modal_@(Model.ID)_@videoNumber" style="cursor: pointer"> 445 @if (!hideThumbnails) 446 { 447 //From @RenderAsset 448 string imageTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 449 450 451 <td class="@(imageTheme) px-0"> 452 @foreach (string format in supportedImageFormats) 453 { //Images 454 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 455 { 456 string productName = product.Name; 457 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 458 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 459 string imageLinkPath = imagePath; 460 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 461 imagePath = !imageLinkPath.StartsWith("/Files/", StringComparison.OrdinalIgnoreCase) ? asset.Value : imagePath; 462 463 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 464 465 RatioSettings ratioSettings = GetRatioSettings(); 466 467 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download> 468 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 469 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle itemprop="image"> 470 </div> 471 </a> 472 } 473 } 474 @foreach (string format in supportedVideoFormats) 475 { //Videos 476 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 477 { 478 479 string type = GetVideoType(asset.Value); 480 string videoScreendumpPath = type == "youtube" ? GetYoutubeScreenDump(asset.Value) : 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 RatioSettings ratioSettings = GetRatioSettings(); 489 490 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 491 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 492 <div class="icon-2 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 493 @if (type != "selfhosted") 494 { 495 <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;"> 496 } 497 else 498 { 499 string fileExtension = Path.GetExtension(asset.Value).ToLower(); 500 501 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 502 <source src="@(videoScreendumpPath)#t=0.001" type="video/@fileExtension.Replace(".", "")"> 503 </video> 504 } 505 </div> 506 </div> 507 } 508 } 509 @foreach (string documentFormat in supportedDocumentFormats) 510 { //Documents 511 if (assetValue.IndexOf(documentFormat, StringComparison.OrdinalIgnoreCase) >= 0) 512 { 513 string productName = product.Name; 514 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 515 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 516 string imageLinkPath = imagePath; 517 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 518 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 519 520 RatioSettings ratioSettings = GetRatioSettings(); 521 522 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@productName"> 523 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) 524 { 525 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 526 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle /> 527 </div> 528 } 529 else 530 { 531 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 532 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 533 </div> 534 } 535 </a> 536 } 537 } 538 </td> 539 } 540 <td> 541 @assetName 542 </td> 543 <td class="d-none d-lg-table-cell"> </td> 544 <td align="right">@videoType</td> 545 </tr> 546 547 videoNumber++; 548 } 549 } 550 </tbody> 551 </table> 552 </div> 553 554 @foreach (MediaViewModel asset in assetsList) 555 { 556 var assetName = asset.Value.ToLower(); 557 558 foreach (string videoFormat in supportedVideoFormats) 559 { //Videos 560 if (assetName.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) 561 { 562 <div class="modal fade js-video-modal" id="modal_@(Model.ID)_@modalVideoNumber" tabindex="-1" aria-labelledby="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber" aria-hidden="true"> 563 <div class="modal-dialog modal-dialog-centered modal-xl"> 564 <div class="modal-content"> 565 <div class="modal-header visually-hidden"> 566 <h5 class="modal-title" id="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber">@product.Title</h5> 567 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 568 </div> 569 <div class="modal-body p-2 p-lg-3 h-100"> 570 @{ 571 var videoParams = GetVideoParams(asset, "modal"); 572 @RenderPartial("Components/VideoPlayer.cshtml", new Dynamicweb.Frontend.FileViewModel { Path = asset.Value }, videoParams) 573 } 574 </div> 575 </div> 576 </div> 577 </div> 578 579 modalVideoNumber++; 580 } 581 } 582 } 583 </div> 584 </div> 585 </div> 586 </div> 587 } 588 else if (Pageview.IsVisualEditorMode) 589 { 590 <div class="h-100 @theme"> 591 <div class="alert alert-dark m-0"> 592 @Translate("No assets are available") 593 </div> 594 </div> 595 } 596 } 597
Error executing template "Designs/Swift/Paragraph/Swift_RelatedProductsList.cshtml" System.NullReferenceException: Object reference not set to an instance of an object. at CompiledRazorTemplates.Dynamic.RazorEngine_f72ccf56531d4bc7ac954f319851fbe5.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 5 @{ 6 ProductViewModel product = new ProductViewModel(); 7 8 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 9 { 10 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 11 } 12 13 string servicePageId = Model.Item.GetLink("ServicePage") != null ? Model.Item.GetLink("ServicePage").PageId.ToString() : ""; 14 servicePageId = servicePageId == "" && GetPageIdByNavigationTag("RelatedProductsListService") != 0 ? GetPageIdByNavigationTag("RelatedProductsListService").ToString() : servicePageId; 15 string pageSize = Model.Item.GetInt32("PageSize") != 0 ? Model.Item.GetString("PageSize") : "10"; 16 17 IEnumerable<string> selectedDisplayGroups = Model.Item.GetList("Fields")?.GetRawValue().OfType<string>().ToList(); 18 string selectedDisplayGroupsString = selectedDisplayGroups.Count() > 0 ? (string.Join(",", selectedDisplayGroups.Select(x => x.ToString()).ToArray())) : ""; 19 20 string title = !string.IsNullOrEmpty(Model.Item.GetString("Title")) ? Model.Item.GetString("Title") : ""; 21 string hideTitle = Model.Item.GetBoolean("HideTitle").ToString(); 22 string hideImage = Model.Item.GetBoolean("HideImage").ToString(); 23 string hideProductNumber = Model.Item.GetBoolean("HideProductNumber").ToString(); 24 string hideProductName = Model.Item.GetBoolean("HideProductName").ToString(); 25 string hideStock = (Model.Item.GetBoolean("HideStock") || Pageview.AreaSettings.GetBoolean("ErpDownHideStock") && !Dynamicweb.Core.Converter.ToBoolean(Dynamicweb.Context.Current.Items["IsWebServiceConnectionAvailable"])).ToString(); 26 string quantitySelector = Model.Item.GetBoolean("QuantitySelector").ToString(); 27 28 string campaignValuesString = string.Join(",", Model.Item.GetList("CampaignBadges")?.GetRawValue().OfType<string>().ToList()); 29 30 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 31 string modalTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ModalTheme")) ? " theme " + Model.Item.GetRawValueString("ModalTheme").Replace(" ", "").Trim().ToLower() : ""; 32 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "display-6"); 33 34 string contentPadding = Model.Item.GetRawValueString("ContentPadding", ""); 35 contentPadding = contentPadding == "small" ? "p-2 p-md-3" : contentPadding; 36 contentPadding = contentPadding == "large" ? "p-4 p-md-5" : contentPadding; 37 38 //Source type 39 string sourceType = Model.Item.GetRawValueString("SourceType", "variants"); 40 IList<string> relateFromProductIds = new List<string>{}; 41 42 //--- RELATED PRODUCTS --- 43 if (sourceType == "related-products" && product?.RelatedGroups != null) { 44 foreach (var group in product.RelatedGroups) 45 { 46 foreach (var relatedProduct in group.Products) 47 { 48 if (string.IsNullOrEmpty(relatedProduct.VariantId)) 49 { 50 relateFromProductIds.Add($"{relatedProduct.ProductId}"); 51 } 52 else 53 { 54 relateFromProductIds.Add($"{relatedProduct.ProductId} {relatedProduct.VariantId}"); 55 } 56 } 57 } 58 } 59 60 //Create group id collection and products id collection strings 61 string groupId = product.PrimaryOrDefaultGroup.Id; 62 string productIds = sourceType == "related-products" ? string.Join(",", relateFromProductIds) : product.Id; 63 64 bool isLazyLoadingForProductInfoEnabled = Dynamicweb.Core.Converter.ToBoolean(Dynamicweb.Context.Current.Items["IsLazyLoadingForProductInfoEnabled"]); 65 } 66 67 @if (!Pageview.IsVisualEditorMode) 68 { 69 <div id="RelatedProductsListContainer_@Pageview.CurrentParagraph.ID" class="item_@Model.Item.SystemName.ToLower()"> 70 @* Initial form for first load (We pass all the settings to the actual template) *@ 71 <form action="/Default.aspx?ID=@servicePageId" id="RelatedProductsListForm_@Pageview.CurrentParagraph.ID" data-response-target-element="RelatedProductsListContainer_@Pageview.CurrentParagraph.ID"> 72 <input type="hidden" name="SortOrder" value="DESC"> 73 <input type="hidden" name="ID" value="@servicePageId"> 74 <input type="hidden" name="SourceType" value="@sourceType"> 75 <input type="hidden" name="ParagraphID" value="@Pageview.CurrentParagraph.ID"> 76 <input type="hidden" name="PageSize" value="@pageSize" id="RelatedProductsListPageSize_@Pageview.CurrentParagraph.ID"> 77 <input type="hidden" name="PageSizeSetting" value="@pageSize"> 78 <input type="hidden" name="Title" value="@title"> 79 <input type="hidden" name="HideTitle" value="@hideTitle"> 80 <input type="hidden" name="SelectedDisplayGroups" value="@selectedDisplayGroupsString"> 81 <input type="hidden" name="HideImage" value="@hideImage"> 82 <input type="hidden" name="HideProductNumber" value="@hideProductNumber"> 83 <input type="hidden" name="HideProductName" value="@hideProductName"> 84 <input type="hidden" name="HideStock" value="@hideStock"> 85 <input type="hidden" name="QuantitySelector" value="@quantitySelector"> 86 <input type="hidden" name="Theme" value="@theme"> 87 <input type="hidden" name="ModalTheme" value="@modalTheme"> 88 <input type="hidden" name="TitleFontSize" value="@titleFontSize"> 89 <input type="hidden" name="ContentPadding" value="@contentPadding"> 90 @if (isLazyLoadingForProductInfoEnabled) 91 { 92 <input type="hidden" name="getproductinfo" value="true"> 93 } 94 95 @*--- VARIANTS ---*@ 96 @if (sourceType == "variants") 97 { 98 <input type="hidden" name="MainProductID" value="@productIds"> 99 } 100 101 @*--- MOST SOLD ---*@ 102 @if (sourceType == "most-sold") 103 { 104 <input type="hidden" name="SortBy" value="OrderCount"> 105 <input type="hidden" name="GroupId" value="@groupId"> 106 <input type="hidden" name="isVariant" value="false"> 107 } 108 109 @*--- TRENDING ---*@ 110 @if (sourceType == "trending") 111 { 112 <input type="hidden" name="SortBy" value="OrderCountGrowth"> 113 <input type="hidden" name="GroupId" value="@groupId"> 114 <input type="hidden" name="isVariant" value="false"> 115 } 116 117 @*--- LATEST ---*@ 118 @if (sourceType == "latest") 119 { 120 <input type="hidden" name="SortBy" value="Created"> 121 <input type="hidden" name="GroupId" value="@groupId"> 122 <input type="hidden" name="isVariant" value="false"> 123 } 124 125 @*--- FREQUENTLY BOUGHT ---*@ 126 @if (sourceType == "frequently") 127 { 128 <input type="hidden" name="BoughtWithProductIds" value="[@productIds]"> 129 <input type="hidden" name="isVariant" value="false"> 130 } 131 132 @*--- RELATED PRODUCTS ---*@ 133 @if (sourceType == "related-products") 134 { 135 <input type="hidden" name="ProductvariantId" value="@productIds"> 136 } 137 138 <input type="hidden" name="SaleBadgeType" value="@Model.Item.GetRawValue("SaleBadgeType")"> 139 <input type="hidden" name="SaleBadgeCssClassName" value="@Model.Item.GetRawValue("SaleBadgeDesign")"> 140 <input type="hidden" name="NewBadgeCssClassName" value="@Model.Item.GetRawValue("NewBadgeDesign")"> 141 <input type="hidden" name="NewPublicationDays" value="@Model.Item.GetInt32("NewPublicationDays")"> 142 143 @if (campaignValuesString != string.Empty) 144 { 145 <input type="hidden" name="CampaignBadgesValues" value="@campaignValuesString"> 146 } 147 </form> 148 </div> 149 150 <script type="module"> 151 swift.PageUpdater.Update(document.querySelector("#RelatedProductsListForm_@Pageview.CurrentParagraph.ID")); 152 </script> 153 154 <script> 155 var UpdateRelatedProductsList = function(event, type, id) { 156 var targetElement = "RelatedProductsListContainer_" + id; 157 158 if (type == "UpdateFacets") { 159 targetElement = "RelatedProductsListFacets_" + id; 160 } 161 162 document.querySelector('#RelatedProductsListRequestType_' + id).value = type; 163 document.querySelector('#RelatedProductsListForm_' + id).setAttribute("data-response-target-element", targetElement); 164 165 swift.PageUpdater.Update(document.querySelector("#RelatedProductsListForm_" + id)); 166 }; 167 </script> 168 } else { 169 <div class="alert alert-info" role="alert"> 170 <span>@Translate("Related products list")</span> 171 </div> 172 } 173
Error executing template "Designs/Swift/Paragraph/Swift_RelatedProducts.cshtml" System.NullReferenceException: Object reference not set to an instance of an object. at CompiledRazorTemplates.Dynamic.RazorEngine_99146c41d9f14f39937ea11016ac5108.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.Core 3 @using Dynamicweb.Ecommerce.ProductCatalog 4 5 @{ 6 ProductViewModel product = null; 7 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 8 { 9 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 10 } 11 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 12 { 13 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 14 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 15 16 if (productList?.Products is object) 17 { 18 product = productList.Products[0]; 19 } 20 } 21 22 string title = Model?.Item?.GetRawValueString("Title", Translate("Products")); 23 string campaignValues = string.Join(",", Model.Item.GetList("CampaignBadges")?.GetRawValue().OfType<string>().ToList()); 24 25 //Styling 26 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "h3"); 27 string subtitleFontSize = Model.Item.GetRawValueString("SubtitleFontSize", "fs-5"); 28 string buttonStyle = Model.Item.GetRawValueString("ButtonStyle", ""); 29 buttonStyle = buttonStyle == "primary" ? " btn-primary" : buttonStyle; 30 buttonStyle = buttonStyle == "secondary" ? " btn-secondary" : buttonStyle; 31 buttonStyle = buttonStyle == "link" ? " btn-link" : buttonStyle; 32 string maxWidth = Model.Item.GetRawValueString("TextReadability", ""); 33 maxWidth = maxWidth == "max-width-on" ? " mw-75ch" : maxWidth; 34 maxWidth = maxWidth == "max-width-off" ? "" : maxWidth; 35 36 string generalTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("GeneralTheme")) ? " theme " + Model.Item.GetRawValueString("GeneralTheme").Replace(" ", "").Trim().ToLower() : ""; 37 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 38 string imageTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 39 40 //Link generation 41 string pageId = !string.IsNullOrEmpty(Model.Item.GetRawValueString("ProductSliderServicePage")) ? Model.Item.GetLink("ProductSliderServicePage").PageId.ToString() : ""; 42 if (string.IsNullOrEmpty(pageId)) 43 { 44 pageId = GetPageIdByNavigationTag("ProductSliderService").ToString(); 45 } 46 47 string url = "/Default.aspx?ID=" + pageId; 48 if (!url.Contains("LayoutTemplate", StringComparison.OrdinalIgnoreCase)) 49 { 50 url += url.Contains("?") ? "&LayoutTemplate=Designs/Swift/Swift_PageClean.cshtml" : "?LayoutTemplate=Designs/Swift/Swift_PageClean.cshtml"; 51 } 52 if (Pageview.IsVisualEditorMode) 53 { 54 url += "&VisualEdit=True"; 55 } 56 57 bool isLazyLoadingForProductInfoEnabled = Dynamicweb.Core.Converter.ToBoolean(Dynamicweb.Context.Current.Items["IsLazyLoadingForProductInfoEnabled"]); 58 if (isLazyLoadingForProductInfoEnabled) 59 { 60 url += "&getproductinfo=true"; 61 } 62 63 //Source type 64 string sourceType = Model.Item.GetRawValueString("RelationType", "trending"); 65 IList<string> relateFromGroupIds = new List<string> { }; 66 IList<string> relateFromProductVariantIds = new List<string> { }; 67 IList<string> relateFromProductIds = new List<string> { }; 68 bool hasVariants = false; 69 70 //--- VARIANTS --- 71 if (sourceType == "variants" && Model.Item.GetValue<ProductListViewModel>("ProductsToRelateToVariants") is ProductListViewModel productsToRelateToVariants) 72 { 73 foreach (var productSelection in productsToRelateToVariants.Products) 74 { 75 relateFromProductIds.Add(productSelection.Id); 76 } 77 } 78 79 //--- MOST SOLD --- 80 if (sourceType == "most-sold" && Model.Item.GetValue<IList<ProductGroupViewModel>>("GroupsToRelateToMostSold") is IList<ProductGroupViewModel> groupsToRelateToMostSold) 81 { 82 foreach (var fromGroup in groupsToRelateToMostSold) 83 { 84 relateFromGroupIds.Add(fromGroup.Id); 85 } 86 } 87 88 //--- TRENDING --- 89 if (sourceType == "trending" && Model.Item.GetValue<IList<ProductGroupViewModel>>("GroupsToRelateToTrending") is IList<ProductGroupViewModel> groupsToRelateToTrending) 90 { 91 foreach (var fromGroup in groupsToRelateToTrending) 92 { 93 relateFromGroupIds.Add(fromGroup.Id); 94 } 95 } 96 97 //--- LATEST --- 98 if (sourceType == "latest" && Model.Item.GetValue<IList<ProductGroupViewModel>>("GroupsToRelateToLatest") is IList<ProductGroupViewModel> groupsToRelateToLatest) 99 { 100 foreach (var fromGroup in groupsToRelateToLatest) 101 { 102 relateFromGroupIds.Add(fromGroup.Id); 103 } 104 } 105 106 //--- FREQUENTLY BOUGHT --- 107 if (sourceType == "frequently" && Model.Item.GetValue<ProductListViewModel>("ProductsToRelateTo") is ProductListViewModel productsToRelateTo) 108 { 109 foreach (var fromProduct in productsToRelateTo.Products) 110 { 111 relateFromProductIds.Add(fromProduct.Id); 112 } 113 } 114 115 //--- SELECTED PRODUCTS --- 116 if ((sourceType == "selected" || sourceType == "frequently") && Model.Item.GetValue<ProductListViewModel>("Products") is ProductListViewModel products) 117 { 118 hasVariants = products.Products.Any(p => !string.IsNullOrEmpty(p.VariantId)); 119 foreach (var productSelection in products.Products) 120 { 121 if (hasVariants) 122 { 123 if (!string.IsNullOrEmpty(productSelection.VariantId)) 124 { 125 relateFromProductVariantIds.Add($"{productSelection.Id} {productSelection.VariantId}"); 126 } 127 else 128 { 129 relateFromProductVariantIds.Add($"{productSelection.Id}"); 130 } 131 } 132 relateFromProductIds.Add($"{productSelection.Id}"); 133 } 134 } 135 136 //--- RELATED PRODUCTS --- 137 if (sourceType == "related-products" && Model.Item.GetValue<ProductListViewModel>("ProductsToRelateTo2") is ProductListViewModel selectedRelationProduct) 138 { 139 if (selectedRelationProduct.Products.Any()) 140 { 141 product = selectedRelationProduct.Products.FirstOrDefault(); 142 } 143 144 if (product?.RelatedGroups != null) 145 { 146 foreach (var group in product.RelatedGroups) 147 { 148 foreach (var relatedProduct in group.Products) 149 { 150 if (!string.IsNullOrEmpty(relatedProduct.VariantId)) 151 { 152 relateFromProductVariantIds.Add($"{relatedProduct.ProductId} {relatedProduct.VariantId}"); 153 } 154 else 155 { 156 relateFromProductVariantIds.Add($"{relatedProduct.ProductId}"); 157 } 158 } 159 } 160 } 161 } 162 163 //Create group id collection and products id collection strings 164 string groupIds = product is object ? product.PrimaryOrDefaultGroup.Id : string.Join(",", relateFromGroupIds); 165 string productVariantIds = relateFromProductVariantIds.Count > 0 ? string.Join(",", relateFromProductVariantIds) : ""; 166 string productIds = product is object && relateFromProductIds.Count == 0 ? product.Id : string.Join(",", relateFromProductIds); 167 168 //Set the parameters to the url 169 string linkParameters = ""; 170 linkParameters += sourceType != "related-products" && sourceType != "frequently" && sourceType != "selected" ? "&GroupId=" + groupIds : ""; 171 linkParameters += !string.IsNullOrEmpty(productIds) && sourceType != "most-sold" && sourceType != "trending" && sourceType != "latest" && sourceType != "frequently" && sourceType != "related-products" ? "&MainProductId=" + productIds : ""; 172 linkParameters += !string.IsNullOrEmpty(productVariantIds) && sourceType == "related-products" ? "&ProductVariantId=" + productVariantIds : ""; 173 linkParameters += sourceType == "variants" ? "&IsVariant=true" : ""; 174 linkParameters += sourceType == "latest" ? "&SortBy=Created" : ""; 175 linkParameters += sourceType == "most-sold" ? "&SortBy=OrderCount" : ""; 176 linkParameters += sourceType == "trending" ? "&SortBy=OrderCountGrowth" : ""; 177 linkParameters += !string.IsNullOrEmpty(productIds) && sourceType == "frequently" ? $"&BoughtWithProductIds=[{productIds}]" : ""; 178 var productListPageId = GetPageIdByNavigationTag("Shop"); 179 string link = "/Default.aspx?ID=" + productListPageId + linkParameters; 180 181 // Slider settings (documentation: swiffyslider.com/configuration) 182 string navigationStyle = $"{Model.Item.GetRawValueString("NavigationStyle", "slider-nav-round")}"; 183 string navigationPlacement = $"{Model.Item.GetRawValueString("NavigationPlacement", "slider-nav-on-slides")}"; 184 string indicatorStyle = $"{Model.Item.GetRawValueString("IndicatorStyle", "slider-indicators-hidden")}"; 185 string revealSlides = Model.Item.GetRawValueString("RevealSlides", "no-reveal") == "reveal" ? "slider-item-reveal" : string.Empty; 186 string navigationAlwaysVisible = (Model.Item.GetBoolean("NavigationAlwaysVisible")) ? "slider-nav-visible" : string.Empty; 187 string navigationVisibleOnTouch = (Model.Item.GetBoolean("NavigationVisibleOnTouch")) ? "slider-nav-touch" : string.Empty; 188 string navigationShowScrollbar = (Model.Item.GetBoolean("NavigationShowScrollbar")) ? "slider-nav-scrollbar" : string.Empty; 189 string navigationSmall = (Model.Item.GetBoolean("NavigationSmall")) ? "slider-nav-sm" : string.Empty; 190 string navigationInvertColors = (Model.Item.GetBoolean("NavigationInvertColors")) ? "slider-nav-dark" : string.Empty; 191 string navigationSlideEntirePage = (Model.Item.GetBoolean("NavigationSlideEntirePage")) ? "slider-nav-page" : string.Empty; 192 string navigationNoLoop = (Model.Item.GetBoolean("NavigationNoLoop")) ? "slider-nav-noloop" : string.Empty; 193 string indicatorsOutsideSlider = (Model.Item.GetBoolean("IndicatorsOutsideSlider") && indicatorStyle != string.Empty) ? "slider-indicators-outside" : string.Empty; 194 string indicatorsHighlightActive = (Model.Item.GetBoolean("IndicatorsHighlightActive")) ? "slider-indicators-highlight" : string.Empty; 195 string indicatorsInvertColors = (Model.Item.GetBoolean("IndicatorsInvertedColors")) ? "slider-indicators-dark" : string.Empty; 196 string indicatorsVisibleOnSmallDevices = (Model.Item.GetBoolean("IndicatorsVisibleOnSmallDevices")) ? "slider-indicators-sm" : string.Empty; 197 198 bool productsFound = true; 199 if (string.IsNullOrEmpty(groupIds) && string.IsNullOrEmpty(productIds) && string.IsNullOrEmpty(productVariantIds)) 200 { 201 if (Pageview.IsVisualEditorMode) 202 { 203 productIds = product.Id; 204 sourceType = "selected"; 205 } 206 else 207 { 208 productsFound = false; 209 } 210 } 211 } 212 213 @*Container element for the request*@ 214 @if (productsFound) 215 { 216 <form method="post" action="@url" id="RelatedProductsForm_@Model.ID" data-response-target-element="RelatedProducts_@Model.ID" data-preloader="inline" data-update-url="false" class="item_@Model.Item.SystemName.ToLower()"> 217 <input type="hidden" name="ModelID" value="@Model.ID"> 218 <input type="hidden" name="SourceType" value="@sourceType"> 219 220 @*--- SLIDER SETTINGS ---*@ 221 <input type="hidden" name="NavigationStyle" value="@navigationStyle"> 222 <input type="hidden" name="NavigationPlacement" value="@navigationPlacement"> 223 <input type="hidden" name="IndicatorStyle" value="@indicatorStyle"> 224 <input type="hidden" name="RevealSlides" value="@revealSlides"> 225 <input type="hidden" name="NavigationAlwaysVisible" value="@(navigationAlwaysVisible)"> 226 <input type="hidden" name="NavigationVisibleOnTouch" value="@(navigationVisibleOnTouch)"> 227 <input type="hidden" name="NavigationShowScrollbar" value="@(navigationShowScrollbar)"> 228 <input type="hidden" name="NavigationSmall" value="@(navigationSmall)"> 229 <input type="hidden" name="NavigationInvertColors" value="@(navigationInvertColors)"> 230 <input type="hidden" name="NavigationNoLoop" value="@(navigationNoLoop)"> 231 <input type="hidden" name="NavigationSlideEntirePage" value="@(navigationSlideEntirePage)"> 232 <input type="hidden" name="IndicatorsOutsideSlider" value="@(indicatorsOutsideSlider)"> 233 <input type="hidden" name="IndicatorsHighlightActive" value="@(indicatorsHighlightActive)"> 234 <input type="hidden" name="IndicatorsInvertColors" value="@(indicatorsInvertColors)"> 235 <input type="hidden" name="IndicatorsVisibleOnSmallDevices" value="@(indicatorsVisibleOnSmallDevices)"> 236 237 @*--- VARIANTS ---*@ 238 @if (sourceType == "variants") 239 { 240 <input type="hidden" name="isVariant" value="true"> 241 <input type="hidden" name="MainProductID" id="MainProductID_@Model.ID" value="@productIds"> 242 } 243 244 @*--- MOST SOLD ---*@ 245 @if (sourceType == "most-sold") 246 { 247 <input type="hidden" name="SortBy" value="OrderCount"> 248 if (groupIds != "") 249 { 250 <input type="hidden" name="GroupId" value="@groupIds"> 251 } 252 } 253 254 @*--- TRENDING ---*@ 255 @if (sourceType == "trending") 256 { 257 <input type="hidden" name="SortBy" value="OrderCountGrowth"> 258 if (groupIds != "") 259 { 260 <input type="hidden" name="GroupId" value="@groupIds"> 261 } 262 } 263 264 @*--- FREQUENTLY BOUGHT ---*@ 265 @if (sourceType == "frequently" && !string.IsNullOrEmpty(productIds)) 266 { 267 <input type="hidden" name="BoughtWithProductIds" value="[@productIds]"> 268 } 269 @if (sourceType != "frequently" && hasVariants) 270 { 271 <input type="hidden" name="ProductVariantId" value="@productVariantIds"> 272 } 273 274 @*--- LATEST ---*@ 275 @if (sourceType == "latest") 276 { 277 <input type="hidden" name="SortBy" value="Created"> 278 <input type="hidden" name="GroupId" value="@groupIds"> 279 } 280 281 @*--- SELECTED PRODUCTS ---*@ 282 @if (sourceType == "selected" && !string.IsNullOrEmpty(productIds) && !hasVariants) 283 { 284 <input type="hidden" name="MainProductId" id="MainProductID_@Model.ID" value="@productIds"> 285 } 286 @if (sourceType == "selected" && hasVariants) 287 { 288 <input type="hidden" name="ProductVariantId" value="@productVariantIds"> 289 } 290 291 @*--- RELATED PRODUCTS ---*@ 292 @if (sourceType == "related-products") 293 { 294 <input type="hidden" name="ProductVariantId" id="MainProductID_@Model.ID" value="@productVariantIds"> 295 } 296 297 @* General parameters *@ 298 <input type="hidden" name="Link" value="@link"> 299 <input type="hidden" name="HideTitle" value="@Model.Item.GetString("HideTitle")"> 300 301 @if (Model.Item.GetInt32("ProductsCount") != 0) 302 { 303 <input type="hidden" name="PageSize" value="@Model.Item.GetInt32("ProductsCount")"> 304 } 305 <input type="hidden" name="HeadingTitle" id="RelatedProductsTitle_@Model.ID" value="@title"> 306 @if (!string.IsNullOrEmpty(Model.Item.GetString("Subtitle"))) 307 { 308 <input type="hidden" name="Subtitle" value="@Model.Item.GetString("Subtitle")"> 309 } 310 @if (!string.IsNullOrEmpty(Model.Item.GetString("LinkText"))) 311 { 312 <input type="hidden" name="LinkText" value="@Model.Item.GetString("LinkText")"> 313 } 314 @if (!string.IsNullOrEmpty(Model.Item.GetString("ImageAspectRatio"))) 315 { 316 string ratio = Model.Item.GetRawValueString("ImageAspectRatio", ""); 317 ratio = ratio != "0" ? ratio : ""; 318 <input type="hidden" name="ImageAspectRatio" value="@ratio"> 319 } 320 @if (!string.IsNullOrEmpty(Model.Item.GetString("Layout"))) 321 { 322 <input type="hidden" name="Layout" value="@Model.Item.GetRawValueString("Layout")"> 323 } 324 @if (titleFontSize != "") 325 { 326 <input type="hidden" name="TitleFontSize" value="@titleFontSize"> 327 } 328 @if (subtitleFontSize != "") 329 { 330 <input type="hidden" name="SubtitleFontSize" value="@subtitleFontSize"> 331 } 332 @if (buttonStyle != "") 333 { 334 <input type="hidden" name="ButtonStyle" value="@buttonStyle"> 335 } 336 @if (generalTheme != "") 337 { 338 <input type="hidden" name="GeneralTheme" value="@generalTheme"> 339 } 340 @if (theme != "") 341 { 342 <input type="hidden" name="Theme" value="@theme"> 343 } 344 @if (imageTheme != "") 345 { 346 <input type="hidden" name="ImageTheme" value="@imageTheme"> 347 } 348 @if (!string.IsNullOrEmpty(Model.Item.GetString("ContentPadding"))) 349 { 350 string contentPadding = Model.Item.GetRawValueString("ContentPadding"); 351 <input type="hidden" name="ContentPadding" value="@contentPadding"> 352 } 353 <input type="hidden" name="TextReadability" value="@maxWidth"> 354 <input type="hidden" name="ParentColumnSize" id="ParentColumnSize_@Model.ID" value="12"> 355 356 <input type="hidden" name="SaleBadgeType" value="@Model.Item.GetRawValue("SaleBadgeType")"> 357 <input type="hidden" name="SaleBadgeCssClassName" value="@Model.Item.GetRawValue("SaleBadgeDesign")"> 358 <input type="hidden" name="NewBadgeCssClassName" value="@Model.Item.GetRawValue("NewBadgeDesign")"> 359 <input type="hidden" name="NewPublicationDays" value="@Model.Item.GetInt32("NewPublicationDays")"> 360 361 @if (campaignValues != string.Empty) 362 { 363 <input type="hidden" name="CampaignBadgesValues" value="@campaignValues"> 364 } 365 </form> 366 367 <script type="module" src="/Files/Templates/Designs/Swift/Assets/js/swiffy-slider.js"></script> 368 <script> 369 window.addEventListener("load", () => { 370 swift.AssetLoader.Load('/Files/Templates/Designs/Swift/Assets/css/swiffy-slider.min.css', 'css'); 371 }); 372 </script> 373 374 if (Pageview.IsVisualEditorMode) 375 { 376 <div class="alert alert-info" role="alert"> 377 <span>@Translate("Product slider: Edit this column to configure")</span> 378 </div> 379 } 380 381 if (sourceType != "related-products") 382 { 383 <div class="w-100 h-100"> 384 <div id="@Model.ID" class="user-select-none" style="scroll-margin-top:var(--header-height,150px)"></div> 385 <div id="RelatedProducts_@Model.ID" class="h-100 swift_product_slider_container"></div> 386 </div> 387 } 388 else if (product?.RelatedGroups != null) 389 { 390 @* Create multiple slider containers, if type is Product relation *@ 391 <div class="grid w-100 h-100@(generalTheme)" style="grid-row-gap: 4rem"> 392 <div id="@Model.ID" class="user-select-none" style="scroll-margin-top:var(--header-height,150px)"></div> 393 @foreach (var group in product.RelatedGroups) 394 { 395 <div id="RelatedProducts_@(Model.ID)_@group.Id" class="g-col-12 h-100 swift_product_slider_container"></div> 396 } 397 </div> 398 } 399 400 @* Initialize *@ 401 if (sourceType != "related-products") 402 { 403 <script type="module"> 404 if (document.querySelector("#RelatedProducts_@Model.ID").closest("[data-col-size]")) { 405 document.querySelector("#ParentColumnSize_@Model.ID").value = document.querySelector("#RelatedProducts_@Model.ID").closest("[data-col-size]").getAttribute("data-col-size"); 406 } 407 swift.PageUpdater.Update(document.querySelector("#RelatedProductsForm_@Model.ID")).then(function () { 408 setTimeout(function() { 409 const isVisualEditor = @(Converter.ToString(Pageview.IsVisualEditorMode).ToLowerInvariant()); 410 const productSliderContainer = document.querySelector(".swift_product_slider_container"); 411 412 if (productSliderContainer && productSliderContainer.innerHTML !== "") { 413 productSliderContainer.classList.remove("d-none"); 414 } 415 else if (!isVisualEditor) { 416 productSliderContainer.closest("[class*=column]").classList.add("d-none"); 417 } 418 }, 150); 419 }); 420 </script> 421 } 422 else if (product?.RelatedGroups != null) 423 { 424 @* Create multiple sliders, if type is Product relation *@ 425 foreach (var group in product.RelatedGroups) 426 { 427 IList<string> fromProductIds = new List<string> { }; 428 429 foreach (var relatedProduct in group.Products) 430 { 431 if (!string.IsNullOrEmpty(relatedProduct.VariantId)) 432 { 433 fromProductIds.Add($"{relatedProduct.ProductId} {relatedProduct.VariantId}"); 434 } 435 else 436 { 437 fromProductIds.Add($"{relatedProduct.ProductId}"); 438 } 439 } 440 <script type="module"> 441 document.querySelector("#ParentColumnSize_@Model.ID").value = document.querySelector("#RelatedProducts_@(Model.ID)_@group.Id").closest("[data-col-size]").getAttribute("data-col-size"); 442 document.querySelector("#MainProductID_@Model.ID").value = "@string.Join(",", fromProductIds)"; 443 document.querySelector("#RelatedProductsTitle_@Model.ID").value = "@group.Name"; 444 document.querySelector("#RelatedProductsForm_@Model.ID").setAttribute("data-response-target-element", "RelatedProducts_@(Model.ID)_@group.Id"); 445 446 swift.PageUpdater.Update(document.querySelector("#RelatedProductsForm_@Model.ID")); 447 </script> 448 } 449 } 450 } 451
Sorry. There is nothing to view here