An error occurred while attaching module (Dynamicweb.Frontend.Content)
System.Data.SqlClient.SqlException (0x80131904): A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: Named Pipes Provider, error: 40 - Could not open a connection to SQL Server) ---> System.ComponentModel.Win32Exception (0x80004005): The network path was not found
at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection)
at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal& connection)
at System.Data.ProviderBase.DbConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, DbConnectionInternal& connection)
at System.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)
at System.Data.SqlClient.SqlConnection.TryOpenInner(TaskCompletionSource`1 retry)
at System.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry)
at System.Data.SqlClient.SqlConnection.Open()
at Dynamicweb.Data.DatabaseConnectionProvider.CreateConnection(Boolean open)
at Dynamicweb.Data.Database.CreateConnection()
at Dynamicweb.Data.Database.CreateDataReader(CommandBuilder commandBuilder, IDbConnection connection, IDbTransaction transaction, Int32 commandTimeout)
at Dynamicweb.Ecommerce.Products.ProductRepository.GetProductById(String productId, String productVariantId, String productLanguageId)
at Dynamicweb.Ecommerce.Products.ProductService.FetchMissingProductsInternal(IProductRepository repo, IEnumerable`1 keys)
at Dynamicweb.Caching.ServiceCache`2.GetCache(IEnumerable`1 keys)
at Dynamicweb.Caching.ServiceCache`2.GetCache(TKey key)
at Dynamicweb.Ecommerce.Products.ProductService.GetProductById(String productId, String productVariantId, String productLanguageId, User user, Boolean showUntranslated)
at Dynamicweb.Ecommerce.Products.ProductService.GetProductById(String productId, String productVariantId, String productLanguageId, Boolean useAssortments)
at Dynamicweb.Ecommerce.ProductCatalog.ProductCatalogFrontend.IsProductInCorrectGroup(String groupId, String productId, String variantId, String languageId)
at Dynamicweb.Ecommerce.ProductCatalog.ProductCatalogFrontend.RenderProduct(String productId, String variantId, String groupId, ProductCatalogSettings settings)
at Dynamicweb.Ecommerce.ProductCatalog.ProductCatalogFrontend.GetContent()
at Dynamicweb.Frontend.Content.GetModuleOutput(Paragraph paragraph, PageView pageview)
ClientConnectionId:00000000-0000-0000-0000-000000000000
Error Number:53,State:0,Class:20
System.Data.SqlClient.SqlException (0x80131904): A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: Named Pipes Provider, error: 40 - Could not open a connection to SQL Server) ---> System.ComponentModel.Win32Exception (0x80004005): The network path was not found at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection) at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal& connection) at System.Data.ProviderBase.DbConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, DbConnectionInternal& connection) at System.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions) at System.Data.SqlClient.SqlConnection.TryOpenInner(TaskCompletionSource`1 retry) at System.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry) at System.Data.SqlClient.SqlConnection.Open() at Dynamicweb.Data.DatabaseConnectionProvider.CreateConnection(Boolean open) at Dynamicweb.Data.Database.CreateConnection() at Dynamicweb.Data.Database.CreateDataReader(CommandBuilder commandBuilder, IDbConnection connection, IDbTransaction transaction, Int32 commandTimeout) at Dynamicweb.Ecommerce.Products.ProductRepository.GetProductById(String productId, String productVariantId, String productLanguageId) at Dynamicweb.Ecommerce.Products.ProductService.FetchMissingProductsInternal(IProductRepository repo, IEnumerable`1 keys) at Dynamicweb.Caching.ServiceCache`2.GetCache(IEnumerable`1 keys) at Dynamicweb.Caching.ServiceCache`2.GetCache(TKey key) at Dynamicweb.Ecommerce.Products.ProductService.GetProductById(String productId, String productVariantId, String productLanguageId, User user, Boolean showUntranslated) at Dynamicweb.Ecommerce.Products.ProductService.GetProductById(String productId, String productVariantId, String productLanguageId, Boolean useAssortments) at Dynamicweb.Ecommerce.ProductCatalog.ProductCatalogFrontend.IsProductInCorrectGroup(String groupId, String productId, String variantId, String languageId) at Dynamicweb.Ecommerce.ProductCatalog.ProductCatalogFrontend.RenderProduct(String productId, String variantId, String groupId, ProductCatalogSettings settings) at Dynamicweb.Ecommerce.ProductCatalog.ProductCatalogFrontend.GetContent() at Dynamicweb.Frontend.Content.GetModuleOutput(Paragraph paragraph, PageView pageview) ClientConnectionId:00000000-0000-0000-0000-000000000000 Error Number:53,State:0,Class:20
Error executing template "Designs/Swift/Paragraph/Swift_ProductDetailsImage.cshtml" System.ArgumentNullException: Value cannot be null. Parameter name: source at System.Linq.Enumerable.Where[TSource](IEnumerable`1 source, Func`2 predicate) at CompiledRazorTemplates.Dynamic.RazorEngine_2af8b6348c3243ad84491cdcb4f25a41.Execute() in D:\dynamicweb.net\Solutions\brdklee.cloud.dynamicweb-cms.com\files\Templates\Designs\Swift\Paragraph\Swift_ProductDetailsImage.cshtml:line 78 at RazorEngine.Templating.TemplateBase.RazorEngine.Templating.ITemplate.Run(ExecuteContext context, TextWriter reader) at RazorEngine.Templating.RazorEngineService.RunCompile(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag) at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass16_0.<RunCompile>b__0(TextWriter writer) at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter) at Dynamicweb.Rendering.RazorTemplateRenderingProvider.Render(Template template) at Dynamicweb.Rendering.TemplateRenderingService.Render(Template template) at Dynamicweb.Rendering.Template.RenderRazorTemplate()
1 @inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.ParagraphViewModel> 2 @using Dynamicweb.Ecommerce.ProductCatalog 3 @using Dynamicweb.Frontend 4 @using System.IO 5 6 @functions { 7 public ProductViewModel product { get; set; } = new ProductViewModel(); 8 public string galleryLayout { get; set; } 9 public string[] supportedImageFormats { get; set; } 10 public string[] supportedVideoFormats { get; set; } 11 public string[] supportedDocumentFormats { get; set; } 12 public string[] allSupportedFormats { get; set; } 13 14 public class RatioSettings { 15 public string Ratio { get; set; } 16 public string CssClass { get; set; } 17 public string CssVariable { get; set; } 18 public string Fill { get; set; } 19 } 20 21 public RatioSettings GetRatioSettings(string size = "desktop") { 22 var ratioSettings = new RatioSettings(); 23 24 string ratio = Model.Item.GetRawValueString("ImageAspectRatio", ""); 25 ratio = ratio != "0" ? ratio : ""; 26 string cssClass = ratio != "" && ratio != "fill" ? " ratio" : ""; 27 string cssVariable = ratio != "" && ratio != "fill" ? "--bs-aspect-ratio: " + ratio : ""; 28 cssClass = ratio == "fill" && size == "mobile" ? " ratio" : cssClass; 29 cssVariable = ratio == "fill" && size == "mobile" ? "--bs-aspect-ratio: 66%" : cssVariable; 30 31 ratioSettings.Ratio = ratio; 32 ratioSettings.CssClass = cssClass; 33 ratioSettings.CssVariable = cssVariable; 34 ratioSettings.Fill = ratio == "fill" ? " h-100" : ""; 35 36 return ratioSettings; 37 } 38 39 public string GetArrowsColor() 40 { 41 var invertColor = Model.Item.GetBoolean("InvertModalArrowsColor"); 42 var arrowsColor = invertColor ? " carousel-dark" : string.Empty; 43 return arrowsColor; 44 } 45 } 46 47 @{ 48 ProductViewModel product = null; 49 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 50 { 51 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 52 } 53 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 54 { 55 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 56 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 57 58 if (productList?.Products is object) 59 { 60 product = productList.Products[0]; 61 } 62 } 63 } 64 65 @if (product is object) { 66 @* Supported formats *@ 67 supportedImageFormats = new string[] { ".jpg", ".jpeg", ".webp", ".png", ".gif", ".bmp", ".tiff" }; 68 supportedVideoFormats = new string[] { "youtu.be", "youtube", "vimeo", ".mp4", ".webm" }; 69 supportedDocumentFormats = new string[] { ".pdf", ".docx", ".xlsx", ".ppt", "pptx" }; 70 allSupportedFormats = supportedImageFormats.Concat(supportedVideoFormats).Concat(supportedDocumentFormats).ToArray(); 71 72 @* Collect the assets *@ 73 var selectedAssetCategories = Model.Item.GetRawValueString("ImageAssets").Split(',').ToList(); 74 bool includeImagePatternImages = Model.Item.GetBoolean("ImagePatternImages"); 75 76 @* Needed image data collection to support both DefaultImage, ImagePatterns and Image Assets *@ 77 string defaultImage = product.DefaultImage != null ? product.DefaultImage.Value : ""; 78 IEnumerable<MediaViewModel> assetsImages = product.AssetCategories.Where(x => selectedAssetCategories.Contains(x.SystemName)).SelectMany(x => x.Assets); 79 assetsImages = assetsImages.OrderByDescending(x => x.Value.Equals(defaultImage)); 80 IEnumerable<MediaViewModel> assetsList = new MediaViewModel[]{}; 81 assetsList = assetsList.Union(assetsImages); 82 assetsList = includeImagePatternImages ? assetsList.Union(product.ImagePatternImages) : assetsList; 83 assetsList = includeImagePatternImages && assetsList.Count() == 0 ? assetsList.Append(product.DefaultImage) : assetsList; 84 85 bool defaultImageFallback = Model.Item.GetBoolean("DefaultImageFallback"); 86 bool showOnlyPrimaryImage = Model.Item.GetBoolean("ShowOnlyPrimaryImage"); 87 88 int totalAssets = 0; 89 if (showOnlyPrimaryImage == false) { 90 foreach (MediaViewModel asset in assetsList) { 91 var assetValue = asset.Value; 92 foreach (string format in allSupportedFormats) { 93 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 94 totalAssets++; 95 } 96 } 97 } 98 } 99 100 if((totalAssets == 0 && product.DefaultImage != null && selectedAssetCategories.Count() == 0) || (showOnlyPrimaryImage == true && product.DefaultImage != null) || totalAssets == 0 && defaultImageFallback) 101 { 102 assetsList = new List<MediaViewModel>(){ product.DefaultImage }; 103 totalAssets = 1; 104 } 105 106 107 @* Theme settings *@ 108 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 109 110 var badgeParms = new Dictionary<string, object>(); 111 badgeParms.Add("size", "h5"); 112 badgeParms.Add("saleBadgeType", Model.Item.GetRawValue("SaleBadgeType")); 113 badgeParms.Add("saleBadgeCssClassName", Model.Item.GetRawValue("SaleBadgeDesign")); 114 badgeParms.Add("newBadgeCssClassName", Model.Item.GetRawValue("NewBadgeDesign")); 115 badgeParms.Add("newPublicationDays", Model.Item.GetInt32("NewPublicationDays")); 116 badgeParms.Add("campaignBadgesValues", Model.Item.GetRawValueString("CampaignBadges")); 117 118 bool saleBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("SaleBadgeDesign")) && Model.Item.GetRawValueString("SaleBadgeDesign") != "none" ? true : false; 119 bool newBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("NewBadgeDesign")) && Model.Item.GetRawValueString("NewBadgeDesign") != "none" ? true : false; 120 DateTime createdDate = product.Created.Value; 121 bool showBadges = saleBadgeEnabled && product.Discount.Price != 0 ? true : false; 122 showBadges = (newBadgeEnabled && Model.Item.GetInt32("NewPublicationDays") == 0) || (newBadgeEnabled && (createdDate.AddDays(Model.Item.GetInt32("NewPublicationDays")) > DateTime.Now)) ? true : showBadges; 123 showBadges = !string.IsNullOrEmpty(Model.Item.GetRawValueString("CampaignBadges")) ? true : showBadges; 124 125 @* Get assets from selected categories or get all assets *@ 126 if (totalAssets != 0) { 127 int assetNumber = 0; 128 int thumbnailNumber = 0; 129 int modalAssetNumber = 0; 130 131 <div class="h-100@(theme) position-relative item_@Model.Item.SystemName.ToLower()"> 132 <div id="SmallScreenImages_@Model.ID" class="carousel@(GetArrowsColor())" data-bs-ride="carousel"> 133 <div class="carousel-inner h-100"> 134 @foreach (MediaViewModel asset in assetsList) { 135 var assetValue = asset.Value; 136 foreach (string format in allSupportedFormats) { 137 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 138 string activeSlide = assetNumber == 0 ? "active" : ""; 139 140 <div class="carousel-item @activeSlide" data-bs-interval="99999"> 141 @{@RenderAsset(asset, assetNumber, "mobile")} 142 </div> 143 assetNumber++; 144 } 145 } 146 } 147 </div> 148 </div> 149 150 @if (totalAssets > 1) { 151 <div id="SmallScreenImagesThumbnails_@Model.ID" class="grid grid-10 gap-2 overflow-x-auto my-3"> 152 @foreach (MediaViewModel asset in assetsList) { 153 var assetValue = asset.Value; 154 foreach (string format in allSupportedFormats) { 155 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 156 string imagePath = Dynamicweb.Context.Current.Server.UrlEncode(assetValue); 157 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; 158 string imagePathThumb = imagePath.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) < 0 && imagePath.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) < 0 ? $"/Admin/Public/GetImage.ashx?image={imagePath}&width=180&format=webp" : imagePath; 159 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 160 161 string videoId = assetValue.Substring(assetValue.LastIndexOf('/') + 1); 162 string vimeoJsClass = assetValue.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "js-vimeo-video-thumbnail" : ""; 163 164 bool isDocument = false; 165 foreach (string documentFormat in supportedDocumentFormats) { 166 if (assetValue.IndexOf(documentFormat, StringComparison.OrdinalIgnoreCase) >= 0) { 167 isDocument = true; 168 } 169 } 170 171 string assetName = asset.Name; 172 assetName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 173 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 174 175 if (!isDocument) { 176 RatioSettings ratioSettings = GetRatioSettings("desktop"); 177 178 <div class="border outline-none @(ratioSettings.CssClass)" style="@(ratioSettings.CssVariable); cursor: pointer" data-bs-target="#SmallScreenImages_@Model.ID" data-bs-slide-to="@thumbnailNumber"> 179 <div class="d-flex align-items-center justify-content-center overflow-hidden position-absolute h-100"> 180 @foreach (string videoFormat in supportedVideoFormats) { //Videos 181 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) { 182 <div class="icon-3 position-absolute text-light" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 183 } 184 } 185 </div> 186 @if (imagePathThumb.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) < 0) { 187 <img src="@imagePathThumb" alt="@assetName" @assetTitle class="p-0 p-lg-1 @vimeoJsClass w-100 h-100" style="object-fit: contain" data-video-id="@videoId"> 188 } else { 189 string videoType = Path.GetExtension(asset.Value).ToLower(); 190 191 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 192 <source src="@imagePathThumb" type="video/@videoType.Replace(".", "")"> 193 </video> 194 } 195 </div> 196 } else { 197 <a href="@assetValue" class="ratio ratio-4x3 border outline-none" style="cursor: pointer" download title="@asset.Value"> 198 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) { 199 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 200 <div class="icon-3 position-absolute text-light" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 201 </div> 202 <img src="@imagePathThumb" alt="@assetName" @assetTitle class="p-0 p-lg-1 mw-100 mh-100" style="object-fit: cover;"> 203 } else { 204 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 205 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 206 </div> 207 } 208 </a> 209 } 210 211 thumbnailNumber++; 212 } 213 } 214 } 215 </div> 216 } 217 218 @if (showBadges) { 219 <div class="position-absolute top-0 left-0 p-2 p-lg-3"> 220 @RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms) 221 </div> 222 } 223 </div> 224 225 @* Modal with slides *@ 226 <div class="modal fade swift_products-details-images-modal" id="modal_@Model.ID" tabindex="-1" aria-labelledby="productDetailsGalleryModalTitle_@Model.ID" aria-hidden="true"> 227 <div class="modal-dialog modal-dialog-centered modal-xl"> 228 <div class="modal-content"> 229 <div class="modal-header visually-hidden"> 230 <h5 class="modal-title" id="productDetailsGalleryModalTitle_@Model.ID">@product.Title</h5> 231 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 232 </div> 233 <div class="modal-body p-2 p-lg-3 h-100"> 234 <div id="ModalCarousel_@Model.ID" class="carousel@(GetArrowsColor()) h-100" data-bs-ride="carousel"> 235 <div class="carousel-inner h-100"> 236 @foreach (MediaViewModel asset in assetsList) { 237 var assetValue = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 238 foreach (string format in supportedImageFormats.Concat(supportedVideoFormats).ToArray()) { 239 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 240 string imagePath = assetValue; 241 string activeSlide = modalAssetNumber == 0 ? "active" : ""; 242 243 var parms = new Dictionary<string, object>(); 244 parms.Add("cssClass", "d-block mw-100 mh-100 m-auto"); 245 parms.Add("fullwidth", true); 246 parms.Add("columns", Model.GridRowColumnCount); 247 248 <div class="carousel-item @activeSlide h-100" data-bs-interval="99999"> 249 @foreach (string imageFormat in supportedImageFormats) { //Images 250 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0) { 251 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 252 } 253 } 254 255 @foreach (string videoFormat in supportedVideoFormats) { //Videos 256 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) { 257 {@RenderVideoPlayer(asset, "modal")} 258 } 259 } 260 </div> 261 262 modalAssetNumber++; 263 } 264 } 265 } 266 <button class="carousel-control-prev" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="prev"> 267 <span class="carousel-control-prev-icon" aria-hidden="true"></span> 268 <span class="visually-hidden">@Translate("Previous")</span> 269 </button> 270 <button class="carousel-control-next" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="next"> 271 <span class="carousel-control-next-icon" aria-hidden="true"></span> 272 <span class="visually-hidden">@Translate("Next")</span> 273 </button> 274 </div> 275 </div> 276 </div> 277 </div> 278 </div> 279 </div> 280 } else if (Pageview.IsVisualEditorMode) { 281 RatioSettings ratioSettings = GetRatioSettings("desktop"); 282 283 <div class="h-100 @theme"> 284 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 285 <img src="/Files/Images/missing_image.jpg" loading="lazy" decoding="async" class="mh-100 mw-100" style="object-fit: cover;"> 286 </div> 287 </div> 288 } 289 } else if (Pageview.IsVisualEditorMode) { 290 <div class="alert alert-dark m-0">@Translate("No products available")</div> 291 } 292 293 @helper RenderAsset(MediaViewModel asset, int assetNumber, string size = "desktop") { 294 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 295 string assetValue = asset.Value; 296 297 <div class="h-100 @(theme)"> 298 @foreach (string format in supportedImageFormats) { //Images 299 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 300 {@RenderImage(asset, assetNumber, size)} 301 } 302 } 303 @foreach (string format in supportedVideoFormats) { //Videos 304 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 305 if (Model.Item.GetString("OpenVideoInModal") == "true") { 306 {@RenderVideoScreendump(asset, assetNumber, size)} 307 } else { 308 {@RenderVideoPlayer(asset, size)} 309 } 310 } 311 } 312 @foreach (string format in supportedDocumentFormats) { //Documents 313 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 314 {@RenderDocument(asset, assetNumber, size)} 315 } 316 } 317 </div> 318 } 319 320 @helper RenderImage(MediaViewModel asset, int number, string size = "desktop") { 321 if (product is object) 322 { 323 string productName = product.Name; 324 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 325 string imageLinkPath = Dynamicweb.Context.Current.Server.UrlEncode(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 parms.Add("title", asset.DisplayName); 336 } 337 338 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") { 339 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); 340 } else { 341 parms.Add("cssClass", "mw-100 mh-100"); 342 } 343 344 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> 345 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide-to="@number"> 346 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 347 </div> 348 </a> 349 } 350 } 351 352 @helper RenderVideoScreendump(MediaViewModel asset, int number, string size = "desktop") { 353 if (product is object) 354 { 355 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 356 357 string videoScreendumpPath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : ""; 358 string videoId = videoScreendumpPath.Substring(videoScreendumpPath.LastIndexOf('/') + 1); 359 videoScreendumpPath = videoScreendumpPath.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || videoScreendumpPath.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "https://img.youtube.com/vi/" + videoId + "/maxresdefault.jpg" : videoScreendumpPath; 360 361 string vimeoJsClass = videoScreendumpPath.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "js-vimeo-video-thumbnail" : ""; 362 videoScreendumpPath = videoScreendumpPath.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "" : videoScreendumpPath; 363 364 string productName = product.Name; 365 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 366 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 367 368 RatioSettings ratioSettings = GetRatioSettings(size); 369 370 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable); cursor: pointer" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> 371 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide-to="@number"> 372 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 373 @if (videoScreendumpPath.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) < 0) 374 { 375 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@productName" @assetTitle class="@vimeoJsClass mw-100 mh-100" data-video-id="@videoId" style="object-fit: cover;" onload="CheckIfVideoThumbnailExist(this)"> 376 } 377 else 378 { 379 string videoType = Path.GetExtension(asset.Value).ToLower(); 380 381 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 382 <source src="@asset.Value" type="video/@videoType.Replace(".", "")"> 383 </video> 384 } 385 </div> 386 </div> 387 388 <script> 389 function CheckIfVideoThumbnailExist(image) { 390 if (image.width == 120) { 391 const lowQualityImage = "https://img.youtube.com/vi/@(videoId)/hqdefault.jpg" 392 image.src = lowQualityImage; 393 } 394 } 395 </script> 396 } 397 } 398 399 @helper RenderVideoPlayer(MediaViewModel asset, string size = "desktop") { 400 if (product is object) 401 { 402 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Name; 403 string assetValue = asset.Value; 404 string videoId = asset.Value.Substring(asset.Value.LastIndexOf('/') + 1); 405 string type = assetValue.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "youtube" : ""; 406 type = assetValue.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "vimeo" : type; 407 type = assetValue.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf(".webm", StringComparison.OrdinalIgnoreCase) >= 0 ? "selfhosted" : type; 408 409 string openInModal = Model.Item.GetString("OpenVideoInModal"); 410 bool autoPlay = Model.Item.GetBoolean("VideoAutoPlay"); 411 412 <div class="h-100" itemscope itemtype="https://schema.org/VideoObject"> 413 <span class="visually-hidden" itemprop="name">@assetName</span> 414 <span class="visually-hidden" itemprop="contentUrl">@asset.Value</span> 415 <span class="visually-hidden" itemprop="thumbnailUrl">@asset.Value</span> 416 417 @if (type != "selfhosted") 418 { 419 <div 420 id="player_@(Pageview.CurrentParagraph.ID)_@(videoId)_@size" 421 class="plyr__video-embed" 422 data-plyr-provider="@(type)" 423 data-plyr-embed-id="@videoId" 424 style="--plyr-color-main: var(--swift-foreground-color); height: 100%"> 425 </div> 426 427 <script type="module" src="/Files/Templates/Designs/Swift/Assets/js/plyr.js"></script> 428 429 <script type="module"> 430 var player = new Plyr('#player_@(Pageview.CurrentParagraph.ID)_@(videoId)_@size', { 431 type: 'video', 432 youtube: { 433 noCookie: true, 434 showinfo: 0 435 }, 436 fullscreen: { 437 enabled: true, 438 iosNative: true, 439 } 440 }); 441 442 @if (autoPlay && openInModal == "false") 443 { 444 <text> 445 player.config.autoplay = true; 446 player.config.muted = true; 447 player.config.volume = 0; 448 player.media.loop = true; 449 450 player.on('ready', function() { 451 if (player.config.autoplay === true) { 452 player.media.play(); 453 } 454 }); 455 </text> 456 } 457 458 @if (openInModal == "true") 459 { 460 <text> 461 var productDetailsGalleryModal = document.querySelector('#modal_@Model.ID') 462 productDetailsGalleryModal.addEventListener('hidden.bs.modal', function (event) { 463 player.media.pause(); 464 }) 465 </text> 466 } 467 </script> 468 } 469 else 470 { 471 string autoPlayAttributes = (autoPlay && openInModal == "false") ? "loop autoplay muted playsinline" : ""; 472 string videoType = Path.GetExtension(assetValue).ToLower(); 473 474 <video preload="auto" @autoPlayAttributes class="h-100 w-100" style="object-fit: cover;" controls> 475 <source src="@assetValue" type="video/@videoType.Replace(".", "")"> 476 </video> 477 } 478 </div> 479 } 480 } 481 482 @helper RenderDocument(MediaViewModel asset, int number, string size = "desktop") { 483 if (product is object) 484 { 485 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 486 487 string productName = product.Name; 488 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 489 string imageLinkPath = imagePath; 490 491 RatioSettings ratioSettings = GetRatioSettings(size); 492 493 var parms = new Dictionary<string, object>(); 494 parms.Add("alt", productName + asset.Keywords); 495 parms.Add("itemprop", "image"); 496 parms.Add("fullwidth", true); 497 parms.Add("columns", Model.GridRowColumnCount); 498 if (!string.IsNullOrEmpty(asset.DisplayName)) { 499 parms.Add("title", asset.DisplayName); 500 } 501 502 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") { 503 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); 504 } else { 505 parms.Add("cssClass", "mw-100 mh-100"); 506 } 507 508 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@Translate("Download")"> 509 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 510 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 511 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) { 512 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 513 } 514 </div> 515 </a> 516 } 517 } 518
Error executing template "Designs/Swift/Paragraph/Swift_ProductSpecification.cshtml" System.NullReferenceException: Object reference not set to an instance of an object. at CompiledRazorTemplates.Dynamic.RazorEngine_5c5123684cfb462d894bd9e10d697a19.Execute() in D:\dynamicweb.net\Solutions\brdklee.cloud.dynamicweb-cms.com\files\Templates\Designs\Swift\Paragraph\Swift_ProductSpecification.cshtml:line 28 at RazorEngine.Templating.TemplateBase.RazorEngine.Templating.ITemplate.Run(ExecuteContext context, TextWriter reader) at RazorEngine.Templating.RazorEngineService.RunCompile(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag) at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass16_0.<RunCompile>b__0(TextWriter writer) at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter) at Dynamicweb.Rendering.RazorTemplateRenderingProvider.Render(Template template) at Dynamicweb.Rendering.TemplateRenderingService.Render(Template template) at Dynamicweb.Rendering.Template.RenderRazorTemplate()
1 @inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.ParagraphViewModel> 2 @using Dynamicweb.Ecommerce.ProductCatalog 3 4 @{ 5 ProductViewModel product = null; 6 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 7 { 8 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 9 } 10 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 11 { 12 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 13 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 14 15 if (productList?.Products is object) 16 { 17 product = productList.Products[0]; 18 } 19 } 20 } 21 22 @if (product is object) { 23 IEnumerable<string> selectedDisplayGroupIds = Model.Item.GetRawValueString("DisplayGroups").Split(',').ToList(); 24 List<CategoryFieldViewModel> displayGroups = new List<CategoryFieldViewModel>(); 25 26 foreach (var selection in selectedDisplayGroupIds) 27 { 28 foreach (CategoryFieldViewModel group in product.FieldDisplayGroups.Values) 29 { 30 if (selection == group.Id) 31 { 32 int fieldsWithNoValueOrZero = 0; 33 34 foreach (var field in group.Fields) 35 { 36 if (string.IsNullOrEmpty(field.Value.Value.ToString())) 37 { 38 fieldsWithNoValueOrZero++; 39 } 40 } 41 42 if (fieldsWithNoValueOrZero != group.Fields.Count) 43 { 44 displayGroups.Add(group); 45 } 46 } 47 } 48 } 49 50 bool showProductFields = Model.Item.GetBoolean("ProductFields"); 51 52 bool hideTitle = Model.Item.GetBoolean("HideTitle"); 53 54 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 55 56 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "display-4"); 57 58 string contentPadding = Model.Item.GetRawValueString("ContentPadding", ""); 59 contentPadding = contentPadding == "none" ? string.Empty : contentPadding; 60 contentPadding = contentPadding == "small" ? " p-2 p-md-3" : contentPadding; 61 contentPadding = contentPadding == "large" ? " p-4 p-md-5" : contentPadding; 62 63 string layout = Model.Item.GetRawValueString("Layout", "list"); 64 string size = Model.Item.GetRawValueString("Size", "full"); 65 string gaps = size == "full" ? " gap-4" : " gap-2"; 66 67 68 if (Pageview.IsVisualEditorMode && displayGroups.Count() == 0) 69 { 70 product.ProductFields.Clear(); 71 product.ProductFields.Add(Translate("Width"), new FieldValueViewModel { Name = Translate("Width"), Value = "99cm" }); 72 product.ProductFields.Add(Translate("Height"), new FieldValueViewModel { Name = Translate("Height"), Value = "195cm" }); 73 showProductFields = true; 74 } 75 76 if (layout == "commas") 77 { 78 gaps = size == "full" ? " gap-4" : " gap-2"; 79 80 } 81 82 <div class="h-100@(gaps)@(theme)@(contentPadding) item_@Model.Item.SystemName.ToLower()"> 83 <div class="grid"> 84 @if ((product.ProductFields != null && Model.Item.GetBoolean("ProductFields")) || (product.ProductCategories != null && Model.Item.GetBoolean("CategoryFields")) || (displayGroups.Count != 0)) { 85 if (!hideTitle) 86 { 87 <h2 class="g-col-12 @titleFontSize">@Model.Item.GetString("Title")</h2> 88 } 89 } 90 91 @if (displayGroups.Count != 0) 92 { 93 if (layout != "accordion") 94 { 95 foreach (var group in displayGroups) 96 { 97 bool hideHeader = Model.Item.GetBoolean("HideGroupHeaders"); 98 99 if (!hideHeader) { 100 <h4 class="g-col-12 h4 mb-0">@group.Name</h4> 101 } 102 103 { @RenderFieldsFromList(group.Fields, layout) } 104 105 } 106 } 107 else 108 { 109 <div class="g-col-12"> 110 <div class="accordion accordion-flush w-100" id="Specifications_@Model.ID"> 111 @foreach (var group in displayGroups) 112 { 113 <div class="accordion-item"> 114 <h2 class="accordion-header" id="SpecificationHeading_@group.Id"> 115 <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#SpecificationItem_@group.Id" aria-expanded="false" aria-controls="SpecificationItem_@group.Id"> 116 @group.Name 117 </button> 118 </h2> 119 <div id="SpecificationItem_@group.Id" class="accordion-collapse collapse" aria-labelledby="SpecificationHeading_@group.Id" data-bs-parent="#Specifications_@Model.ID"> 120 <div class="accordion-body"> 121 @{ @RenderFieldsFromList(group.Fields, "list") } 122 </div> 123 </div> 124 </div> 125 } 126 </div> 127 </div> 128 } 129 } 130 131 @if (product.ProductFields != null && showProductFields) 132 { 133 if (product.ProductFields.Count > 0) 134 { 135 if (layout != "accordion") 136 { 137 {@RenderFieldsFromList(product.ProductFields, layout) } 138 } 139 else 140 { 141 <div class="g-col-12"> 142 <div class="accordion accordion-flush w-100" id="Specifications_@Model.ID"> 143 <div class="accordion-item"> 144 <h2 class="accordion-header" id="SpecificationHeading_@Model.ID"> 145 <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#SpecificationItem_@Model.ID" aria-expanded="false" aria-controls="SpecificationItem_@Model.ID"> 146 @Translate("Specifications") 147 </button> 148 </h2> 149 <div id="SpecificationItem_@Model.ID" class="accordion-collapse" aria-labelledby="SpecificationHeading_@Model.ID" data-bs-parent="#Specifications_@Model.ID"> 150 <div class="accordion-body"> 151 @{ @RenderFieldsFromList(product.ProductFields, "List") } 152 </div> 153 </div> 154 </div> 155 </div> 156 </div> 157 } 158 } 159 } 160 161 @if (product.ProductCategories != null && Model.Item.GetBoolean("CategoryFields")) 162 { 163 if (product.ProductCategories.Count > 0) 164 { 165 if (layout != "accordion") 166 { 167 foreach (var group in product.ProductCategories) 168 { 169 CategoryFieldViewModel category = group.Value; 170 bool hideHeader = Model.Item.GetBoolean("HideGroupHeaders"); 171 172 if (!hideHeader) { 173 <h4 class="g-col-12 h4 mb-0">@group.Value.Name</h4> 174 } 175 176 { @RenderFieldsFromList(category.Fields, layout) } 177 } 178 } 179 else 180 { 181 <div class="g-col-12"> 182 <div class="accordion accordion-flush w-100" id="Specifications_@Model.ID"> 183 @foreach (var group in product.ProductCategories) 184 { 185 CategoryFieldViewModel category = group.Value; 186 187 <div class="accordion-item"> 188 <h2 class="accordion-header" id="SpecificationHeading_@group.Value.Id"> 189 <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#SpecificationItem_@group.Value.Id" aria-expanded="false" aria-controls="SpecificationItem_@group.Value.Id"> 190 @group.Value.Name 191 </button> 192 </h2> 193 <div id="SpecificationItem_@group.Value.Id" class="accordion-collapse" aria-labelledby="SpecificationHeading_@group.Value.Id" data-bs-parent="#Specifications_@Model.ID"> 194 <div class="accordion-body"> 195 @{ @RenderFieldsFromList(category.Fields, "list") } 196 </div> 197 </div> 198 </div> 199 } 200 </div> 201 </div> 202 } 203 } 204 } 205 </div> 206 </div> 207 } 208 else if (Pageview.IsVisualEditorMode) 209 { 210 <div class="alert alert-warning m-0">@Translate("No products available")</div> 211 } 212 213 @helper RenderFieldsFromList(Dictionary<string, FieldValueViewModel> fields, string layout) 214 { 215 string size = Model.Item.GetRawValueString("Size", "full"); 216 string gaps = size != "full" ? " gap-1" : string.Empty; 217 bool hideFieldLabels = Model.Item.GetBoolean("HideFieldLabels"); 218 bool hideFieldsWithZeroValue = Model.Item.GetBoolean("HideFieldsWithZeroValue"); 219 220 if (layout == "columns") { 221 <div class="g-col-12"> 222 <div class="grid@(gaps)"> 223 @foreach (var field in fields) 224 { 225 {@RenderField(field.Value, layout)} 226 } 227 </div> 228 </div> 229 } 230 if (layout == "list") { 231 <div class="g-col-12"> 232 <dl class="grid@(gaps)"> 233 @foreach (var field in fields) 234 { 235 {@RenderField(field.Value, layout)} 236 } 237 </dl> 238 </div> 239 } 240 if (layout == "table") 241 { 242 string tableSize = size == "full" ? "" : " table-sm"; 243 <div class="g-col-12"> 244 <table class="table table-striped@(tableSize)"> 245 @foreach (var field in fields) 246 { 247 {@RenderField(field.Value, layout)} 248 } 249 </table> 250 </div> 251 } 252 if (layout == "bullets") 253 { 254 string listSize = size == "full" ? "" : "m-0 p-0 lh-1 fs-7 opacity-75"; 255 string listStyle = size == "full" ? "" : "style=\"list-style-position: inside\""; 256 <div class="g-col-12"> 257 <ul class="@listSize" @listStyle> 258 @foreach (var field in fields) 259 { 260 {@RenderField(field.Value, layout)} 261 } 262 </ul> 263 </div> 264 } 265 if (layout == "commas") 266 { 267 List<string> featuresList = new List<string>(); 268 269 foreach (var field in fields) 270 { 271 string firstListItemValue = string.Empty; //Hack to support field type providers with a single value 272 273 if (field.Value?.Value != null) 274 { 275 if (field.Value.Value.GetType() == typeof(System.Collections.Generic.List<FieldOptionValueViewModel>)) 276 { 277 System.Collections.Generic.List<FieldOptionValueViewModel> values = field.Value.Value as System.Collections.Generic.List<FieldOptionValueViewModel>; 278 279 //Hack to support field type providers with a single value 280 if (values.FirstOrDefault() != null) 281 { 282 firstListItemValue = values.FirstOrDefault().Value; 283 } 284 } 285 } 286 287 if (!hideFieldsWithZeroValue || (firstListItemValue != "0" && firstListItemValue != "0.0" && field.Value.Value.ToString() != "0" && field.Value.Value.ToString() != "0.0")) 288 { 289 if (field.Value.Value is object && !string.IsNullOrEmpty(field.Value.Value.ToString())) 290 { 291 if (field.Value.Value.GetType() == typeof(System.Collections.Generic.List<FieldOptionValueViewModel>)) 292 { 293 List<string> options = new List<string>(); 294 foreach (FieldOptionValueViewModel option in field.Value.Value as System.Collections.Generic.List<FieldOptionValueViewModel>) 295 { 296 if (!string.IsNullOrWhiteSpace(option.Value)) 297 { 298 if (option.Value.ToString().Contains("#") && (Translate(field.Value.Name) == Translate("Color") || Translate(field.Value.Name) == Translate("Colour"))) 299 { 300 string colorSpan = "<span class=\"colorbox-sm\" style=\"background-color: " + option.Value + "\"></span>"; 301 options.Add(colorSpan); 302 } 303 else if (!string.IsNullOrEmpty(option.Value)) 304 { 305 options.Add(option.Name); 306 } 307 } 308 } 309 string optionsString = (string.Join(", ", options.Select(x => x.ToString()).ToArray())); 310 if ((Translate(field.Value.Name) == Translate("Color") || Translate(field.Value.Name) == Translate("Colour"))) 311 { 312 optionsString = (string.Join(" ", options.Select(x => x.ToString()).ToArray())); 313 } 314 315 if (!string.IsNullOrEmpty(optionsString)) 316 { 317 if (!hideFieldLabels) 318 { 319 featuresList.Add(field.Value.Name + ": " + optionsString); 320 } 321 else 322 { 323 featuresList.Add(optionsString); 324 } 325 } 326 } 327 else 328 { 329 if (!string.IsNullOrWhiteSpace(field.Value.Value.ToString())) 330 { 331 if (field.Value.Value.ToString().Contains("#") && (Translate(field.Value.Name) == Translate("Color") || Translate(field.Value.Name) == Translate("Colour"))) 332 { 333 string colorSpan = "<span class=\"colorbox-sm\" style=\"background-color: " + field.Value.Value + "\"></span>"; 334 335 if (!hideFieldLabels) 336 { 337 featuresList.Add(field.Value.Name + ": " + colorSpan); 338 } 339 else 340 { 341 featuresList.Add(colorSpan); 342 } 343 } 344 else 345 { 346 if (!hideFieldLabels) 347 { 348 featuresList.Add(field.Value.Name + ": " + field.Value.Value.ToString()); 349 } 350 else 351 { 352 featuresList.Add(field.Value.Value.ToString()); 353 } 354 } 355 } 356 } 357 } 358 } 359 } 360 361 string featuresString = (string.Join(", ", featuresList.Select(x => x.ToString()).ToArray())); 362 363 <div class="g-col-12 opacity-75 fs-7">@featuresString</div> 364 } 365 } 366 367 @helper RenderField(FieldValueViewModel field, string layout) 368 { 369 string size = Model.Item.GetRawValueString("Size", "full"); 370 string fieldValue = field?.Value != null ? field.Value.ToString() : ""; 371 bool hideFieldLabels = Model.Item.GetBoolean("HideFieldLabels"); 372 bool noValues = false; 373 string firstListItemValue = string.Empty; //Hack to support field type providers with a single value 374 bool hideFieldsWithZeroValue = Model.Item.GetBoolean("HideFieldsWithZeroValue"); 375 376 if (!string.IsNullOrEmpty(fieldValue)) 377 { 378 if (field.Value.GetType() == typeof(System.Collections.Generic.List<FieldOptionValueViewModel>)) 379 { 380 System.Collections.Generic.List<FieldOptionValueViewModel> values = field.Value as System.Collections.Generic.List<FieldOptionValueViewModel>; 381 noValues = values.Count > 0 ? false : true; 382 383 //Hack to support field type providers with a single value 384 if (values.FirstOrDefault() != null) 385 { 386 firstListItemValue = values.FirstOrDefault().Value; 387 } 388 } 389 } 390 391 if (!string.IsNullOrEmpty(fieldValue) && noValues == false) 392 { 393 if (!hideFieldsWithZeroValue || (firstListItemValue != "0" && firstListItemValue != "0.0" && field.Value.ToString() != "0" && field.Value.ToString() != "0.0")) 394 { 395 if (layout == "columns") 396 { 397 398 <div class="grid g-col-6 g-col-lg-4 gap-1"> 399 @if (!hideFieldLabels) 400 { 401 <dt class="g-col-12 g-col-lg-4">@field.Name</dt> 402 } 403 <dd class="g-col-12 g-col-lg-8 mb-0 text-break"> 404 @{ @RenderFieldValue(field) } 405 </dd> 406 </div> 407 } 408 if (layout == "list") 409 { 410 if (!hideFieldLabels) 411 { 412 <dt class="g-col-4">@field.Name</dt> 413 } 414 <dd class="g-col-8 mb-0 text-break"> 415 @{ @RenderFieldValue(field) } 416 </dd> 417 } 418 if (layout == "table") 419 { 420 <tr> 421 @if (!hideFieldLabels) 422 { 423 <th class="w-25 w-lg-50" scope="row">@field.Name</th> 424 } 425 <td class="text-break"> 426 @{ @RenderFieldValue(field) } 427 </td> 428 </tr> 429 } 430 if (layout == "bullets") 431 { 432 <li> 433 @if (!hideFieldLabels) 434 { 435 <strong>@field.Name</strong> 436 } 437 <span> 438 @{ @RenderFieldValue(field) } 439 </span> 440 </li> 441 } 442 } 443 } 444 } 445 446 @helper RenderFieldValue(FieldValueViewModel field) 447 { 448 string fieldValue = field?.Value != null ? field.Value.ToString() : ""; 449 450 bool isLink = field?.Type == "Link"; 451 bool isColor = false; 452 bool isBrandName = field?.SystemName == "Brand_name"; 453 454 fieldValue = fieldValue == "False" ? Translate("No") : fieldValue; 455 fieldValue = fieldValue == "True" ? Translate("Yes") : fieldValue; 456 457 458 if (field.Value.GetType() == typeof(System.Collections.Generic.List<Dynamicweb.Ecommerce.ProductCatalog.FieldOptionValueViewModel>)) 459 { 460 int valueCount = 0; 461 System.Collections.Generic.List<FieldOptionValueViewModel> values = field.Value as System.Collections.Generic.List<FieldOptionValueViewModel>; 462 int totalValues = values.Count; 463 464 foreach (FieldOptionValueViewModel option in values) 465 { 466 if (!string.IsNullOrEmpty(option.Value)) 467 { 468 if (option.Value.Substring(0, 1) == "#") 469 { 470 isColor = true; 471 } 472 } 473 474 if (!isColor) 475 { 476 @option.Name 477 } 478 else 479 { 480 <span class="colorbox-sm" style="background-color: @option.Value" title="@option.Name"></span> 481 } 482 483 if (valueCount != totalValues && valueCount < (totalValues - 1)) 484 { 485 if (isColor) 486 { 487 <text> </text> 488 } 489 else 490 { 491 <text>, </text> 492 } 493 } 494 valueCount++; 495 } 496 } 497 else 498 { 499 if (fieldValue.Substring(0, 1) == "#") 500 { 501 isColor = true; 502 } 503 504 if (!isColor) 505 { 506 if (isLink) 507 { 508 string linktTitle = !fieldValue.Contains("aspx") ? fieldValue : Translate("Go to link"); 509 string target = Pageview.AreaSettings.GetBoolean("OpenLinksInNewTab") && fieldValue.Contains("http") ? "target=\"_blank\"" : string.Empty; 510 string rel = Pageview.AreaSettings.GetBoolean("OpenLinksInNewTab") && fieldValue.Contains("http") ? "rel=\"noopener\"" : string.Empty; 511 512 <a href="@field.Value" title="@field.Name" @target @rel>@linktTitle</a> 513 } 514 else if (isBrandName) 515 { 516 <span itemprop="brand" itemtype="https://schema.org/Brand" itemscope> 517 <span itemprop="name">@fieldValue</span> 518 </span> 519 } 520 else 521 { 522 @fieldValue 523 } 524 525 } 526 else 527 { 528 <span class="colorbox-sm" style="background-color: @fieldValue" title="@fieldValue"></span> 529 } 530 } 531 } 532
Error executing template "/Designs/Swift/Paragraph/Swift_ProductSpecification_Custom.cshtml" System.NullReferenceException: Object reference not set to an instance of an object. at CompiledRazorTemplates.Dynamic.RazorEngine_7b3884b4b5fc4cbabc323126ad5f421a.Execute() in D:\dynamicweb.net\Solutions\brdklee.cloud.dynamicweb-cms.com\files\Templates\Designs\Swift\Paragraph\Swift_ProductSpecification_Custom.cshtml:line 29 at RazorEngine.Templating.TemplateBase.RazorEngine.Templating.ITemplate.Run(ExecuteContext context, TextWriter reader) at RazorEngine.Templating.RazorEngineService.RunCompile(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag) at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass16_0.<RunCompile>b__0(TextWriter writer) at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter) at Dynamicweb.Rendering.RazorTemplateRenderingProvider.Render(Template template) at Dynamicweb.Rendering.TemplateRenderingService.Render(Template template) at Dynamicweb.Rendering.Template.RenderRazorTemplate()
1 @inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.ParagraphViewModel> 2 @using Dynamicweb.Ecommerce.ProductCatalog 3 4 @{ 5 ProductViewModel product = null; 6 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 7 { 8 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 9 } 10 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 11 { 12 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 13 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 14 15 if (productList?.Products is object) 16 { 17 product = productList.Products[0]; 18 } 19 } 20 } 21 22 @if (product is object) 23 { 24 IEnumerable<string> selectedDisplayGroupIds = Model.Item.GetRawValueString("DisplayGroups").Split(',').ToList(); 25 List<CategoryFieldViewModel> displayGroups = new List<CategoryFieldViewModel>(); 26 27 foreach (var selection in selectedDisplayGroupIds) 28 { 29 foreach (CategoryFieldViewModel group in product.FieldDisplayGroups.Values) 30 { 31 if (selection == group.Id) 32 { 33 int fieldsWithNoValueOrZero = 0; 34 35 foreach (var field in group.Fields) 36 { 37 if (string.IsNullOrEmpty(field.Value.Value.ToString())) 38 { 39 fieldsWithNoValueOrZero++; 40 } 41 } 42 43 if (fieldsWithNoValueOrZero != group.Fields.Count) 44 { 45 displayGroups.Add(group); 46 } 47 } 48 } 49 } 50 51 bool showProductFields = Model.Item.GetBoolean("ProductFields"); 52 53 bool hideTitle = Model.Item.GetBoolean("HideTitle"); 54 55 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 56 57 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "display-4"); 58 59 string contentPadding = Model.Item.GetRawValueString("ContentPadding", ""); 60 contentPadding = contentPadding == "none" ? string.Empty : contentPadding; 61 contentPadding = contentPadding == "small" ? " p-2 p-md-3" : contentPadding; 62 contentPadding = contentPadding == "large" ? " p-4 p-md-5" : contentPadding; 63 64 string layout = Model.Item.GetRawValueString("Layout", "list"); 65 string size = Model.Item.GetRawValueString("Size", "full"); 66 string gaps = size == "full" ? " gap-4" : " gap-2"; 67 68 69 if (Pageview.IsVisualEditorMode && displayGroups.Count() == 0) 70 { 71 product.ProductFields.Clear(); 72 product.ProductFields.Add(Translate("Width"), new FieldValueViewModel { Name = Translate("Width"), Value = "99cm" }); 73 product.ProductFields.Add(Translate("Height"), new FieldValueViewModel { Name = Translate("Height"), Value = "195cm" }); 74 showProductFields = true; 75 } 76 77 if (layout == "commas") 78 { 79 gaps = size == "full" ? " gap-4" : " gap-2"; 80 81 } 82 83 <div class="h-100@(gaps)@(theme)@(contentPadding) item_@Model.Item.SystemName.ToLower()"> 84 <div class="grid"> 85 @if ((product.ProductFields != null && Model.Item.GetBoolean("ProductFields")) || (product.ProductCategories != null && Model.Item.GetBoolean("CategoryFields")) || (displayGroups.Count != 0)) { 86 if (!hideTitle) 87 { 88 <h2 class="g-col-12 @titleFontSize">@Model.Item.GetString("Title")</h2> 89 } 90 } 91 92 @if (displayGroups.Count != 0) 93 { 94 if (layout != "accordion") 95 { 96 foreach (var group in displayGroups) 97 { 98 bool hideHeader = Model.Item.GetBoolean("HideGroupHeaders"); 99 100 if (!hideHeader) { 101 <h4 class="g-col-12 h4 mb-0">@group.Name</h4> 102 } 103 104 { @RenderFieldsFromList(group.Fields, layout) } 105 106 } 107 } 108 else 109 { 110 <div class="g-col-12"> 111 <div class="accordion accordion-flush w-100" id="Specifications_@Model.ID"> 112 @foreach (var group in displayGroups) 113 { 114 <div class="accordion-item"> 115 <h2 class="accordion-header" id="SpecificationHeading_@group.Id"> 116 <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#SpecificationItem_@group.Id" aria-expanded="false" aria-controls="SpecificationItem_@group.Id"> 117 @group.Name 118 </button> 119 </h2> 120 <div id="SpecificationItem_@group.Id" class="accordion-collapse collapse" aria-labelledby="SpecificationHeading_@group.Id" data-bs-parent="#Specifications_@Model.ID"> 121 <div class="accordion-body"> 122 @{ @RenderFieldsFromList(group.Fields, "list") } 123 </div> 124 </div> 125 </div> 126 } 127 </div> 128 </div> 129 } 130 } 131 132 @if (product.ProductFields != null && showProductFields) 133 { 134 if (product.ProductFields.Count > 0) 135 { 136 if (layout != "accordion") 137 { 138 {@RenderFieldsFromList(product.ProductFields, layout) } 139 } 140 else 141 { 142 <div class="g-col-12"> 143 <div class="accordion accordion-flush w-100" id="Specifications_@Model.ID"> 144 <div class="accordion-item"> 145 <h2 class="accordion-header" id="SpecificationHeading_@Model.ID"> 146 <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#SpecificationItem_@Model.ID" aria-expanded="false" aria-controls="SpecificationItem_@Model.ID"> 147 @Translate("Specifications") 148 </button> 149 </h2> 150 <div id="SpecificationItem_@Model.ID" class="accordion-collapse" aria-labelledby="SpecificationHeading_@Model.ID" data-bs-parent="#Specifications_@Model.ID"> 151 <div class="accordion-body"> 152 @{ @RenderFieldsFromList(product.ProductFields, "List") } 153 </div> 154 </div> 155 </div> 156 </div> 157 </div> 158 } 159 } 160 } 161 162 @if (product.ProductCategories != null && Model.Item.GetBoolean("CategoryFields")) 163 { 164 if (product.ProductCategories.Count > 0) 165 { 166 if (layout != "accordion") 167 { 168 foreach (var group in product.ProductCategories) 169 { 170 CategoryFieldViewModel category = group.Value; 171 bool hideHeader = Model.Item.GetBoolean("HideGroupHeaders"); 172 173 if (!hideHeader) { 174 <h4 class="g-col-12 h4 mb-0">@group.Value.Name</h4> 175 } 176 177 { @RenderFieldsFromList(category.Fields, layout) } 178 } 179 } 180 else 181 { 182 <div class="g-col-12"> 183 <div class="accordion accordion-flush w-100" id="Specifications_@Model.ID"> 184 @foreach (var group in product.ProductCategories) 185 { 186 CategoryFieldViewModel category = group.Value; 187 188 <div class="accordion-item"> 189 <h2 class="accordion-header" id="SpecificationHeading_@group.Value.Id"> 190 <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#SpecificationItem_@group.Value.Id" aria-expanded="false" aria-controls="SpecificationItem_@group.Value.Id"> 191 @group.Value.Name 192 </button> 193 </h2> 194 <div id="SpecificationItem_@group.Value.Id" class="accordion-collapse" aria-labelledby="SpecificationHeading_@group.Value.Id" data-bs-parent="#Specifications_@Model.ID"> 195 <div class="accordion-body"> 196 @{ @RenderFieldsFromList(category.Fields, "list") } 197 </div> 198 </div> 199 </div> 200 } 201 </div> 202 </div> 203 } 204 } 205 } 206 </div> 207 </div> 208 } 209 else if (Pageview.IsVisualEditorMode) 210 { 211 <div class="alert alert-warning m-0">@Translate("No products available")</div> 212 } 213 214 @helper RenderFieldsFromList(Dictionary<string, FieldValueViewModel> fields, string layout) 215 { 216 string size = Model.Item.GetRawValueString("Size", "full"); 217 string gaps = size != "full" ? " gap-1" : string.Empty; 218 bool hideFieldLabels = Model.Item.GetBoolean("HideFieldLabels"); 219 bool hideFieldsWithZeroValue = Model.Item.GetBoolean("HideFieldsWithZeroValue"); 220 221 if (layout == "columns"){ 222 <div class="g-col-12"> 223 <div class="grid@(gaps)"> 224 @foreach (var field in fields) 225 { 226 {@RenderField(field.Value, layout)} 227 } 228 </div> 229 </div> 230 } 231 if (layout == "list") { 232 <div class="g-col-12"> 233 <dl class="grid@(gaps)"> 234 @foreach (var field in fields) 235 { 236 {@RenderField(field.Value, layout)} 237 } 238 </dl> 239 </div> 240 } 241 if (layout == "table") 242 { 243 string tableSize = size == "full" ? "" : " table-sm"; 244 <div class="g-col-12"> 245 <table class="table table-striped@(tableSize)"> 246 @foreach (var field in fields) 247 { 248 {@RenderField(field.Value, layout)} 249 } 250 </table> 251 </div> 252 } 253 if (layout == "bullets") 254 { 255 string listSize = size == "full" ? "" : "m-0 p-0 lh-1 fs-7 opacity-75"; 256 string listStyle = size == "full" ? "" : "style=\"list-style-position: inside\""; 257 <div class="g-col-12"> 258 <ul class="@listSize" @listStyle> 259 @foreach (var field in fields) 260 { 261 {@RenderField(field.Value, layout)} 262 } 263 </ul> 264 </div> 265 } 266 if (layout == "commas") 267 { 268 List<string> featuresList = new List<string>(); 269 270 foreach (var field in fields) 271 { 272 if (field.Value.SystemName == "MaxCanBeBuilt") 273 { 274 if (field.Value.Value is int) 275 { 276 field.Value.Value = (int)field.Value.Value > 0 ? Translate("Yes") : Translate("No"); 277 } 278 } 279 string firstListItemValue = string.Empty; //Hack to support field type providers with a single value 280 281 if (field.Value?.Value != null) 282 { 283 if (field.Value.Value.GetType() == typeof(System.Collections.Generic.List<FieldOptionValueViewModel>)) 284 { 285 System.Collections.Generic.List<FieldOptionValueViewModel> values = field.Value.Value as System.Collections.Generic.List<FieldOptionValueViewModel>; 286 287 //Hack to support field type providers with a single value 288 if (values.FirstOrDefault() != null) 289 { 290 firstListItemValue = values.FirstOrDefault().Value; 291 } 292 } 293 } 294 295 if (!hideFieldsWithZeroValue || (firstListItemValue != "0" && firstListItemValue != "0.0" && field.Value.Value.ToString() != "0" && field.Value.Value.ToString() != "0.0")) 296 { 297 if (field.Value.Value is object && !string.IsNullOrEmpty(field.Value.Value.ToString())) 298 { 299 if (field.Value.Value.GetType() == typeof(System.Collections.Generic.List<FieldOptionValueViewModel>)) 300 { 301 List<string> options = new List<string>(); 302 foreach (FieldOptionValueViewModel option in field.Value.Value as System.Collections.Generic.List<FieldOptionValueViewModel>) 303 { 304 if (!string.IsNullOrWhiteSpace(option.Value)) 305 { 306 if (option.Value.ToString().Contains("#") && (Translate(field.Value.Name) == Translate("Color") || Translate(field.Value.Name) == Translate("Colour"))) 307 { 308 string colorSpan = "<span class=\"colorbox-sm\" style=\"background-color: " + option.Value + "\"></span>"; 309 options.Add(colorSpan); 310 } 311 else if (!string.IsNullOrEmpty(option.Value)) 312 { 313 options.Add(option.Name); 314 } 315 } 316 } 317 string optionsString = (string.Join(", ", options.Select(x => x.ToString()).ToArray())); 318 if ((Translate(field.Value.Name) == Translate("Color") || Translate(field.Value.Name) == Translate("Colour"))) 319 { 320 optionsString = (string.Join(" ", options.Select(x => x.ToString()).ToArray())); 321 } 322 323 if (!string.IsNullOrEmpty(optionsString)) 324 { 325 if (!hideFieldLabels) 326 { 327 featuresList.Add(field.Value.Name + ": " + optionsString); 328 } 329 else 330 { 331 featuresList.Add(optionsString); 332 } 333 } 334 } 335 else 336 { 337 if (!string.IsNullOrWhiteSpace(field.Value.Value.ToString())) 338 { 339 if (field.Value.Value.ToString().Contains("#") && (Translate(field.Value.Name) == Translate("Color") || Translate(field.Value.Name) == Translate("Colour"))) 340 { 341 string colorSpan = "<span class=\"colorbox-sm\" style=\"background-color: " + field.Value.Value + "\"></span>"; 342 343 if (!hideFieldLabels) 344 { 345 featuresList.Add(field.Value.Name + ": " + colorSpan); 346 } 347 else 348 { 349 featuresList.Add(colorSpan); 350 } 351 } 352 else 353 { 354 if (!hideFieldLabels) 355 { 356 featuresList.Add(field.Value.Name + ": " + field.Value.Value.ToString()); 357 } 358 else 359 { 360 featuresList.Add(field.Value.Value.ToString()); 361 } 362 } 363 } 364 } 365 } 366 } 367 } 368 369 string featuresString = (string.Join(", ", featuresList.Select(x => x.ToString()).ToArray())); 370 371 <div class="g-col-12 opacity-75 fs-7">@featuresString</div> 372 } 373 } 374 375 @helper RenderField(FieldValueViewModel field, string layout) 376 { 377 378 string size = Model.Item.GetRawValueString("Size", "full"); 379 string fieldValue = field?.Value != null ? field.Value.ToString() : ""; 380 bool hideFieldLabels = Model.Item.GetBoolean("HideFieldLabels"); 381 bool noValues = false; 382 string firstListItemValue = string.Empty; //Hack to support field type providers with a single value 383 bool hideFieldsWithZeroValue = Model.Item.GetBoolean("HideFieldsWithZeroValue"); 384 385 if (!string.IsNullOrEmpty(fieldValue)) 386 { 387 if (field.Value.GetType() == typeof(System.Collections.Generic.List<FieldOptionValueViewModel>)) 388 { 389 System.Collections.Generic.List<FieldOptionValueViewModel> values = field.Value as System.Collections.Generic.List<FieldOptionValueViewModel>; 390 noValues = values.Count > 0 ? false : true; 391 392 //Hack to support field type providers with a single value 393 if (values.FirstOrDefault() != null) 394 { 395 firstListItemValue = values.FirstOrDefault().Value; 396 } 397 } 398 } 399 400 if (!string.IsNullOrEmpty(fieldValue) && noValues == false) 401 { 402 if (!hideFieldsWithZeroValue || (firstListItemValue != "0" && firstListItemValue != "0.0" && field.Value.ToString() != "0" && field.Value.ToString() != "0.0")) 403 { 404 if (layout == "columns") 405 { 406 407 <div class="grid g-col-6 g-col-lg-4 gap-1"> 408 @if (!hideFieldLabels) 409 { 410 <dt class="g-col-12 g-col-lg-4">@field.Name</dt> 411 } 412 <dd class="g-col-12 g-col-lg-8 mb-0 text-break"> 413 @{ @RenderFieldValue(field) } 414 </dd> 415 </div> 416 } 417 if (layout == "list") 418 { 419 if (!hideFieldLabels) 420 { 421 <dt class="g-col-4">@field.Name</dt> 422 } 423 <dd class="g-col-8 mb-0 text-break"> 424 @{ @RenderFieldValue(field) } 425 </dd> 426 } 427 if (layout == "table") 428 { 429 <tr> 430 @if (!hideFieldLabels) 431 { 432 <th class="w-25 w-lg-50" scope="row">@field.Name</th> 433 } 434 <td class="text-break"> 435 @{ @RenderFieldValue(field) } 436 </td> 437 </tr> 438 } 439 if (layout == "bullets") 440 { 441 <li> 442 @if (!hideFieldLabels) 443 { 444 <strong>@field.Name</strong> 445 } 446 <span> 447 @{ @RenderFieldValue(field) } 448 </span> 449 </li> 450 } 451 } 452 } 453 } 454 455 @helper RenderFieldValue(FieldValueViewModel field) 456 { 457 string fieldValue = field?.Value != null ? field.Value.ToString() : ""; 458 459 bool isLink = field?.Type == "Link"; 460 bool isColor = false; 461 bool isBrandName = field?.SystemName == "Brand_name"; 462 463 fieldValue = fieldValue == "False" ? Translate("No") : fieldValue; 464 fieldValue = fieldValue == "True" ? Translate("Yes") : fieldValue; 465 466 467 if (field.SystemName == "MaxCanBeBuilt") 468 { 469 fieldValue = (int)field.Value > 0 ? Translate("Yes") : Translate("No"); 470 } 471 472 if (field.Value.GetType() == typeof(System.Collections.Generic.List<Dynamicweb.Ecommerce.ProductCatalog.FieldOptionValueViewModel>)) 473 { 474 int valueCount = 0; 475 System.Collections.Generic.List<FieldOptionValueViewModel> values = field.Value as System.Collections.Generic.List<FieldOptionValueViewModel>; 476 int totalValues = values.Count; 477 478 foreach (FieldOptionValueViewModel option in values) 479 { 480 if (!string.IsNullOrEmpty(option.Value)) 481 { 482 if (option.Value.Substring(0, 1) == "#") 483 { 484 isColor = true; 485 } 486 } 487 488 if (!isColor) 489 { 490 @option.Name 491 } 492 else 493 { 494 <span class="colorbox-sm" style="background-color: @option.Value" title="@option.Name"></span> 495 } 496 497 if (valueCount != totalValues && valueCount < (totalValues - 1)) 498 { 499 if (isColor) 500 { 501 <text> </text> 502 } 503 else 504 { 505 <text>, </text> 506 } 507 } 508 valueCount++; 509 } 510 } 511 else 512 { 513 if (fieldValue.Substring(0, 1) == "#") 514 { 515 isColor = true; 516 } 517 518 if (!isColor) 519 { 520 if (isLink) 521 { 522 string linktTitle = !fieldValue.Contains("aspx") ? fieldValue : Translate("Go to link"); 523 string target = Pageview.AreaSettings.GetBoolean("OpenLinksInNewTab") && fieldValue.Contains("http") ? "target=\"_blank\"" : string.Empty; 524 string rel = Pageview.AreaSettings.GetBoolean("OpenLinksInNewTab") && fieldValue.Contains("http") ? "rel=\"noopener\"" : string.Empty; 525 526 <a href="@field.Value" title="@field.Name" @target @rel>@linktTitle</a> 527 } 528 else if (isBrandName) 529 { 530 <span itemprop="brand" itemtype="https://schema.org/Brand" itemscope> 531 <span itemprop="name">@fieldValue</span> 532 </span> 533 } 534 else 535 { 536 @fieldValue 537 } 538 539 } 540 else 541 { 542 <span class="colorbox-sm" style="background-color: @fieldValue" title="@fieldValue"></span> 543 } 544 } 545 } 546
Error executing template "/Designs/Swift/Paragraph/Swift_ProductDetailsMediaTable_Custom.cshtml" System.ArgumentNullException: Value cannot be null. Parameter name: source at System.Linq.Enumerable.Where[TSource](IEnumerable`1 source, Func`2 predicate) at CompiledRazorTemplates.Dynamic.RazorEngine_2bf238684d4241219833f7ea52bf2e68.Execute() in D:\dynamicweb.net\Solutions\brdklee.cloud.dynamicweb-cms.com\files\Templates\Designs\Swift\Paragraph\Swift_ProductDetailsMediaTable_Custom.cshtml:line 72 at RazorEngine.Templating.TemplateBase.RazorEngine.Templating.ITemplate.Run(ExecuteContext context, TextWriter reader) at RazorEngine.Templating.RazorEngineService.RunCompile(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag) at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass16_0.<RunCompile>b__0(TextWriter writer) at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter) at Dynamicweb.Rendering.RazorTemplateRenderingProvider.Render(Template template) at Dynamicweb.Rendering.TemplateRenderingService.Render(Template template) at Dynamicweb.Rendering.Template.RenderRazorTemplate()
1 @inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.ParagraphViewModel> 2 @using Dynamicweb.Ecommerce.ProductCatalog 3 @using Dynamicweb.Frontend 4 @using System.IO 5 6 @functions { 7 public ProductViewModel product { get; set; } = new ProductViewModel(); 8 public string[] 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 39 @{ 40 @* Get the product data *@ 41 ProductViewModel product = null; 42 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 43 { 44 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 45 } 46 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 47 { 48 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 49 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 50 51 if (productList?.Products is object) 52 { 53 product = productList.Products[0]; 54 } 55 } 56 } 57 58 @if (product is object) 59 { 60 @* Supported formats *@ 61 supportedImageFormats = new string[] { ".jpg", ".jpeg", ".webp", ".png", ".gif", ".bmp", ".tiff" }; 62 supportedVideoFormats = new string[] { "youtu.be", "youtube", "vimeo", ".mp4", ".webm" }; 63 supportedDocumentFormats = new string[] { ".pdf", ".docx", ".xlsx", ".ppt", "pptx", ".sat", ".stp", ".step", ".igs", ".iges", ".ipt", ".dwg", ".dxf", ".dwf" }; 64 allSupportedFormats = supportedImageFormats.Concat(supportedVideoFormats).Concat(supportedDocumentFormats).ToArray(); 65 66 @* Collect the assets *@ 67 var selectedAssetCategories = Model.Item.GetRawValueString("ImageAssets").Split(',').ToList(); 68 bool includeImagePatternImages = Model.Item.GetBoolean("ImagePatternImages"); 69 70 @* Needed image data collection to support both DefaultImage, ImagePatterns and Image Assets *@ 71 string defaultImage = product.DefaultImage != null ? product.DefaultImage.Value : ""; 72 IEnumerable<MediaViewModel> assetsImages = product.AssetCategories.Where(x => selectedAssetCategories.Contains(x.SystemName)).SelectMany(x => x.Assets); 73 assetsImages = assetsImages.OrderByDescending(x => x.Value.Equals(defaultImage)); 74 IEnumerable<MediaViewModel> assetsList = new MediaViewModel[] { }; 75 76 assetsList = assetsList.Union(assetsImages); 77 assetsList = includeImagePatternImages ? assetsList.Union(product.ImagePatternImages) : assetsList; 78 assetsList = includeImagePatternImages && assetsList.Count() == 0 ? assetsList.Append(product.DefaultImage) : assetsList; 79 80 bool defaultImageFallback = Model.Item.GetBoolean("DefaultImageFallback"); 81 bool showOnlyPrimaryImage = Model.Item.GetBoolean("ShowOnlyPrimaryImage"); 82 83 int totalAssets = 0; 84 if (showOnlyPrimaryImage == false) 85 { 86 foreach (MediaViewModel asset in assetsList) 87 { 88 var assetValue = asset.Value; 89 foreach (string format in allSupportedFormats) 90 { 91 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 92 { 93 totalAssets++; 94 } 95 } 96 } 97 } 98 99 if ((totalAssets == 0 && product.DefaultImage != null && selectedAssetCategories.Count() == 0) || (showOnlyPrimaryImage == true && product.DefaultImage != null)) 100 { 101 assetsList = new List<MediaViewModel>() { product.DefaultImage }; 102 totalAssets = 1; 103 } 104 105 int videoNumber = 0; 106 107 @* Layout settings *@ 108 string spacing = Model.Item.GetRawValueString("Spacing", "p-0"); 109 spacing = spacing == "none" ? "p-0" : spacing; 110 spacing = spacing == "small" ? "p-3" : spacing; 111 spacing = spacing == "large" ? "p-5" : spacing; 112 113 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 114 115 bool hideThumbnails = Model.Item.GetBoolean("HideThumbnails"); 116 117 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 118 int modalVideoNumber = 0; 119 120 @* Get assets from selected categories or get all assets *@ 121 122 if (totalAssets != 0 && assetsList.Any()) 123 { 124 <div class="@spacing@(theme) item_@Model.Item.SystemName.ToLower()"> 125 @if (!string.IsNullOrEmpty(Model.Item.GetString("Title")) && !Model.Item.GetBoolean("HideTitle")) 126 { 127 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "h3"); 128 129 <h3 class="@titleFontSize mb-3"> 130 @Model.Item.GetString("Title") 131 </h3> 132 } 133 134 <div class="table-responsive"> 135 <table class="table table-hover align-middle mb-0" style="table-layout: fixed;"> 136 <thead> 137 <tr> 138 @if (!hideThumbnails) 139 { 140 <th style="width:60px"> </th> 141 } 142 <th>@Translate("Name")</th> 143 <th class="text-end d-none d-lg-table-cell">@Translate("Download")</th> 144 <th class="text-end" style="width:100px">@Translate("File type")</th> 145 </tr> 146 </thead> 147 <tbody class="border-top-0"> 148 @foreach (MediaViewModel asset in assetsList) 149 { 150 var assetValue = asset.Value; 151 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Value.Substring(asset.Value.LastIndexOf('/') + 1); 152 153 bool isVideo = false; 154 foreach (string format in supportedVideoFormats) 155 { //Videos 156 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 157 { 158 isVideo = true; 159 } 160 } 161 162 if (!isVideo) 163 { 164 string filePath = Dynamicweb.Context.Current.Server.MapPath(assetValue); 165 long fileSize = 0; 166 167 if (File.Exists(filePath)) 168 { 169 fileSize = new System.IO.FileInfo(filePath) != null ? new System.IO.FileInfo(filePath).Length / 1024 : 0; 170 171 foreach (string format in allSupportedFormats) 172 { 173 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 174 { 175 <tr> 176 @if (!hideThumbnails) 177 { 178 @RenderAsset(asset) 179 } 180 <td> 181 <a href="@assetValue" class="text-decoration-none text-break" download="@assetName" title="@assetName"> 182 @assetName 183 </a> 184 </td> 185 <td class="text-end d-none d-lg-table-cell"> 186 <a href="@assetValue" class="text-decoration-none" download="@assetName" title="@assetName"> 187 @fileSize KB <div class="icon-2" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 188 </a> 189 </td> 190 <td class="text-end">@format</td> 191 </tr> 192 } 193 } 194 } 195 } 196 else 197 { 198 string videoType = asset.Value.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || asset.Value.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "Youtube" : ""; 199 videoType = asset.Value.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "Vimeo" : videoType; 200 201 <tr data-bs-toggle="modal" data-bs-target="#modal_@(Model.ID)_@videoNumber" style="cursor: pointer"> 202 @if (!hideThumbnails) 203 { 204 @RenderAsset(asset) 205 } 206 <td> 207 @assetName 208 </td> 209 <td class="d-none d-lg-table-cell"> </td> 210 <td align="right">@videoType</td> 211 </tr> 212 213 videoNumber++; 214 } 215 } 216 </tbody> 217 </table> 218 </div> 219 220 @foreach (MediaViewModel asset in assetsList) 221 { 222 var assetName = asset.Value.ToLower(); 223 224 foreach (string format in supportedVideoFormats) 225 { //Videos 226 if (assetName.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 227 { 228 <div class="modal fade js-video-modal" id="modal_@(Model.ID)_@modalVideoNumber" tabindex="-1" aria-labelledby="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber" aria-hidden="true"> 229 <div class="modal-dialog modal-dialog-centered modal-xl"> 230 <div class="modal-content"> 231 <div class="modal-header visually-hidden"> 232 <h5 class="modal-title" id="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber">@product.Title</h5> 233 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 234 </div> 235 <div class="modal-body p-2 p-lg-3 h-100"> 236 @{ @RenderVideoPlayer(asset) } 237 </div> 238 </div> 239 </div> 240 </div> 241 242 modalVideoNumber++; 243 } 244 } 245 } 246 </div> 247 } 248 else if (Pageview.IsVisualEditorMode) 249 { 250 <div class="h-100 @theme"> 251 <div class="alert alert-dark m-0"> 252 @Translate("No assets are available") 253 </div> 254 </div> 255 } 256 } 257 258 @helper RenderAsset(MediaViewModel asset) 259 { 260 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 261 string assetValue = asset.Value; 262 263 <td class="@(theme) px-0"> 264 @foreach (string format in supportedImageFormats) 265 { //Images 266 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 267 { 268 @RenderImage(asset) 269 } 270 } 271 @foreach (string format in supportedVideoFormats) 272 { //Videos 273 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 274 { 275 @RenderVideoScreendump(asset) 276 } 277 } 278 @foreach (string format in supportedDocumentFormats) 279 { //Documents 280 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 281 { 282 @RenderDocument(asset) 283 } 284 } 285 </td> 286 } 287 288 @helper RenderImage(MediaViewModel asset) 289 { 290 string productName = product.Name; 291 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 292 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 293 string imageLinkPath = imagePath; 294 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 295 296 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 297 298 RatioSettings ratioSettings = GetRatioSettings(); 299 300 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download> 301 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 302 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle itemprop="image"> 303 </div> 304 </a> 305 } 306 307 @helper RenderVideoScreendump(MediaViewModel asset) 308 { 309 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 310 311 string videoScreendumpPath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : ""; 312 string videoId = videoScreendumpPath.Substring(videoScreendumpPath.LastIndexOf('/') + 1); 313 videoScreendumpPath = videoScreendumpPath.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || videoScreendumpPath.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "https://img.youtube.com/vi/" + videoId + "/mqdefault.jpg" : videoScreendumpPath; 314 315 string vimeoJsClass = videoScreendumpPath.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "js-vimeo-video-thumbnail" : ""; 316 videoScreendumpPath = videoScreendumpPath.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "" : videoScreendumpPath; 317 318 string productName = product.Name; 319 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 320 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 321 322 RatioSettings ratioSettings = GetRatioSettings(); 323 324 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 325 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 326 <div class="icon-2 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 327 @if (videoScreendumpPath.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) < 0) 328 { 329 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@productName" @assetTitle class="@vimeoJsClass mw-100 mh-100" data-video-id="@videoId" style="object-fit: cover;"> 330 } 331 else 332 { 333 string videoType = Path.GetExtension(asset.Value).ToLower(); 334 335 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 336 <source src="@asset.Value" type="video/@videoType.Replace(".", "")"> 337 </video> 338 } 339 </div> 340 </div> 341 } 342 343 @helper RenderDocument(MediaViewModel asset) 344 { 345 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 346 string productName = product.Name; 347 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 348 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 349 string imageLinkPath = imagePath; 350 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 351 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 352 353 RatioSettings ratioSettings = GetRatioSettings(); 354 355 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@productName"> 356 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) 357 { 358 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 359 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle /> 360 </div> 361 } 362 else 363 { 364 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 365 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 366 </div> 367 } 368 </a> 369 } 370 371 @helper RenderVideoPlayer(MediaViewModel asset) 372 { 373 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Name; 374 string assetValue = asset.Value; 375 string videoId = asset.Value.Substring(asset.Value.LastIndexOf('/') + 1); 376 string type = assetValue.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "youtube" : ""; 377 type = assetValue.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "vimeo" : type; 378 type = assetValue.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf(".webm", StringComparison.OrdinalIgnoreCase) >= 0 ? "selfhosted" : type; 379 380 <div class="h-100" itemscope itemtype="https://schema.org/VideoObject"> 381 <span class="visually-hidden" itemprop="name">@assetName</span> 382 <span class="visually-hidden" itemprop="contentUrl">@asset.Value</span> 383 <span class="visually-hidden" itemprop="thumbnailUrl">@asset.Value</span> 384 @if (type != "selfhosted") 385 { 386 <div id="player_@(Pageview.CurrentParagraph.ID)_@(videoId)" 387 class="plyr__video-embed" 388 data-plyr-provider="@(type)" 389 data-plyr-embed-id="@videoId" 390 style="--plyr-color-main: var(--swift-foreground-color); height: 100%"> 391 </div> 392 393 <script type="module" src="/Files/Templates/Designs/Swift/Assets/js/plyr.js"></script> 394 <script type="module"> 395 var player = new Plyr('#player_@(Pageview.CurrentParagraph.ID)_@(videoId)', { 396 type: 'video', 397 youtube: { 398 noCookie: true, 399 showinfo: 0 400 }, 401 fullscreen: { 402 enabled: true, 403 iosNative: true, 404 } 405 }); 406 407 document.querySelectorAll('.js-video-modal').forEach(function (modal) { 408 modal.addEventListener('hidden.bs.modal', function (event) { 409 player.media.pause(); 410 }) 411 }); 412 </script> 413 } 414 else 415 { 416 string videoType = Path.GetExtension(assetValue).ToLower(); 417 418 <video preload="auto" class="h-100 w-100" style="object-fit: cover;" controls> 419 <source src="@assetValue" type="video/@videoType.Replace(".", "")"> 420 </video> 421 } 422 </div> 423 } 424
Error executing template "/Designs/Swift/Paragraph/Swift_ProductDetailsMediaTable_Custom.cshtml" System.ArgumentNullException: Value cannot be null. Parameter name: source at System.Linq.Enumerable.Where[TSource](IEnumerable`1 source, Func`2 predicate) at CompiledRazorTemplates.Dynamic.RazorEngine_2bf238684d4241219833f7ea52bf2e68.Execute() in D:\dynamicweb.net\Solutions\brdklee.cloud.dynamicweb-cms.com\files\Templates\Designs\Swift\Paragraph\Swift_ProductDetailsMediaTable_Custom.cshtml:line 72 at RazorEngine.Templating.TemplateBase.RazorEngine.Templating.ITemplate.Run(ExecuteContext context, TextWriter reader) at RazorEngine.Templating.RazorEngineService.RunCompile(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag) at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass16_0.<RunCompile>b__0(TextWriter writer) at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter) at Dynamicweb.Rendering.RazorTemplateRenderingProvider.Render(Template template) at Dynamicweb.Rendering.TemplateRenderingService.Render(Template template) at Dynamicweb.Rendering.Template.RenderRazorTemplate()
1 @inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.ParagraphViewModel> 2 @using Dynamicweb.Ecommerce.ProductCatalog 3 @using Dynamicweb.Frontend 4 @using System.IO 5 6 @functions { 7 public ProductViewModel product { get; set; } = new ProductViewModel(); 8 public string[] 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 39 @{ 40 @* Get the product data *@ 41 ProductViewModel product = null; 42 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 43 { 44 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 45 } 46 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 47 { 48 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 49 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 50 51 if (productList?.Products is object) 52 { 53 product = productList.Products[0]; 54 } 55 } 56 } 57 58 @if (product is object) 59 { 60 @* Supported formats *@ 61 supportedImageFormats = new string[] { ".jpg", ".jpeg", ".webp", ".png", ".gif", ".bmp", ".tiff" }; 62 supportedVideoFormats = new string[] { "youtu.be", "youtube", "vimeo", ".mp4", ".webm" }; 63 supportedDocumentFormats = new string[] { ".pdf", ".docx", ".xlsx", ".ppt", "pptx", ".sat", ".stp", ".step", ".igs", ".iges", ".ipt", ".dwg", ".dxf", ".dwf" }; 64 allSupportedFormats = supportedImageFormats.Concat(supportedVideoFormats).Concat(supportedDocumentFormats).ToArray(); 65 66 @* Collect the assets *@ 67 var selectedAssetCategories = Model.Item.GetRawValueString("ImageAssets").Split(',').ToList(); 68 bool includeImagePatternImages = Model.Item.GetBoolean("ImagePatternImages"); 69 70 @* Needed image data collection to support both DefaultImage, ImagePatterns and Image Assets *@ 71 string defaultImage = product.DefaultImage != null ? product.DefaultImage.Value : ""; 72 IEnumerable<MediaViewModel> assetsImages = product.AssetCategories.Where(x => selectedAssetCategories.Contains(x.SystemName)).SelectMany(x => x.Assets); 73 assetsImages = assetsImages.OrderByDescending(x => x.Value.Equals(defaultImage)); 74 IEnumerable<MediaViewModel> assetsList = new MediaViewModel[] { }; 75 76 assetsList = assetsList.Union(assetsImages); 77 assetsList = includeImagePatternImages ? assetsList.Union(product.ImagePatternImages) : assetsList; 78 assetsList = includeImagePatternImages && assetsList.Count() == 0 ? assetsList.Append(product.DefaultImage) : assetsList; 79 80 bool defaultImageFallback = Model.Item.GetBoolean("DefaultImageFallback"); 81 bool showOnlyPrimaryImage = Model.Item.GetBoolean("ShowOnlyPrimaryImage"); 82 83 int totalAssets = 0; 84 if (showOnlyPrimaryImage == false) 85 { 86 foreach (MediaViewModel asset in assetsList) 87 { 88 var assetValue = asset.Value; 89 foreach (string format in allSupportedFormats) 90 { 91 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 92 { 93 totalAssets++; 94 } 95 } 96 } 97 } 98 99 if ((totalAssets == 0 && product.DefaultImage != null && selectedAssetCategories.Count() == 0) || (showOnlyPrimaryImage == true && product.DefaultImage != null)) 100 { 101 assetsList = new List<MediaViewModel>() { product.DefaultImage }; 102 totalAssets = 1; 103 } 104 105 int videoNumber = 0; 106 107 @* Layout settings *@ 108 string spacing = Model.Item.GetRawValueString("Spacing", "p-0"); 109 spacing = spacing == "none" ? "p-0" : spacing; 110 spacing = spacing == "small" ? "p-3" : spacing; 111 spacing = spacing == "large" ? "p-5" : spacing; 112 113 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 114 115 bool hideThumbnails = Model.Item.GetBoolean("HideThumbnails"); 116 117 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 118 int modalVideoNumber = 0; 119 120 @* Get assets from selected categories or get all assets *@ 121 122 if (totalAssets != 0 && assetsList.Any()) 123 { 124 <div class="@spacing@(theme) item_@Model.Item.SystemName.ToLower()"> 125 @if (!string.IsNullOrEmpty(Model.Item.GetString("Title")) && !Model.Item.GetBoolean("HideTitle")) 126 { 127 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "h3"); 128 129 <h3 class="@titleFontSize mb-3"> 130 @Model.Item.GetString("Title") 131 </h3> 132 } 133 134 <div class="table-responsive"> 135 <table class="table table-hover align-middle mb-0" style="table-layout: fixed;"> 136 <thead> 137 <tr> 138 @if (!hideThumbnails) 139 { 140 <th style="width:60px"> </th> 141 } 142 <th>@Translate("Name")</th> 143 <th class="text-end d-none d-lg-table-cell">@Translate("Download")</th> 144 <th class="text-end" style="width:100px">@Translate("File type")</th> 145 </tr> 146 </thead> 147 <tbody class="border-top-0"> 148 @foreach (MediaViewModel asset in assetsList) 149 { 150 var assetValue = asset.Value; 151 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Value.Substring(asset.Value.LastIndexOf('/') + 1); 152 153 bool isVideo = false; 154 foreach (string format in supportedVideoFormats) 155 { //Videos 156 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 157 { 158 isVideo = true; 159 } 160 } 161 162 if (!isVideo) 163 { 164 string filePath = Dynamicweb.Context.Current.Server.MapPath(assetValue); 165 long fileSize = 0; 166 167 if (File.Exists(filePath)) 168 { 169 fileSize = new System.IO.FileInfo(filePath) != null ? new System.IO.FileInfo(filePath).Length / 1024 : 0; 170 171 foreach (string format in allSupportedFormats) 172 { 173 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 174 { 175 <tr> 176 @if (!hideThumbnails) 177 { 178 @RenderAsset(asset) 179 } 180 <td> 181 <a href="@assetValue" class="text-decoration-none text-break" download="@assetName" title="@assetName"> 182 @assetName 183 </a> 184 </td> 185 <td class="text-end d-none d-lg-table-cell"> 186 <a href="@assetValue" class="text-decoration-none" download="@assetName" title="@assetName"> 187 @fileSize KB <div class="icon-2" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 188 </a> 189 </td> 190 <td class="text-end">@format</td> 191 </tr> 192 } 193 } 194 } 195 } 196 else 197 { 198 string videoType = asset.Value.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || asset.Value.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "Youtube" : ""; 199 videoType = asset.Value.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "Vimeo" : videoType; 200 201 <tr data-bs-toggle="modal" data-bs-target="#modal_@(Model.ID)_@videoNumber" style="cursor: pointer"> 202 @if (!hideThumbnails) 203 { 204 @RenderAsset(asset) 205 } 206 <td> 207 @assetName 208 </td> 209 <td class="d-none d-lg-table-cell"> </td> 210 <td align="right">@videoType</td> 211 </tr> 212 213 videoNumber++; 214 } 215 } 216 </tbody> 217 </table> 218 </div> 219 220 @foreach (MediaViewModel asset in assetsList) 221 { 222 var assetName = asset.Value.ToLower(); 223 224 foreach (string format in supportedVideoFormats) 225 { //Videos 226 if (assetName.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 227 { 228 <div class="modal fade js-video-modal" id="modal_@(Model.ID)_@modalVideoNumber" tabindex="-1" aria-labelledby="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber" aria-hidden="true"> 229 <div class="modal-dialog modal-dialog-centered modal-xl"> 230 <div class="modal-content"> 231 <div class="modal-header visually-hidden"> 232 <h5 class="modal-title" id="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber">@product.Title</h5> 233 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 234 </div> 235 <div class="modal-body p-2 p-lg-3 h-100"> 236 @{ @RenderVideoPlayer(asset) } 237 </div> 238 </div> 239 </div> 240 </div> 241 242 modalVideoNumber++; 243 } 244 } 245 } 246 </div> 247 } 248 else if (Pageview.IsVisualEditorMode) 249 { 250 <div class="h-100 @theme"> 251 <div class="alert alert-dark m-0"> 252 @Translate("No assets are available") 253 </div> 254 </div> 255 } 256 } 257 258 @helper RenderAsset(MediaViewModel asset) 259 { 260 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 261 string assetValue = asset.Value; 262 263 <td class="@(theme) px-0"> 264 @foreach (string format in supportedImageFormats) 265 { //Images 266 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 267 { 268 @RenderImage(asset) 269 } 270 } 271 @foreach (string format in supportedVideoFormats) 272 { //Videos 273 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 274 { 275 @RenderVideoScreendump(asset) 276 } 277 } 278 @foreach (string format in supportedDocumentFormats) 279 { //Documents 280 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 281 { 282 @RenderDocument(asset) 283 } 284 } 285 </td> 286 } 287 288 @helper RenderImage(MediaViewModel asset) 289 { 290 string productName = product.Name; 291 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 292 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 293 string imageLinkPath = imagePath; 294 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 295 296 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 297 298 RatioSettings ratioSettings = GetRatioSettings(); 299 300 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download> 301 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 302 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle itemprop="image"> 303 </div> 304 </a> 305 } 306 307 @helper RenderVideoScreendump(MediaViewModel asset) 308 { 309 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 310 311 string videoScreendumpPath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : ""; 312 string videoId = videoScreendumpPath.Substring(videoScreendumpPath.LastIndexOf('/') + 1); 313 videoScreendumpPath = videoScreendumpPath.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || videoScreendumpPath.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "https://img.youtube.com/vi/" + videoId + "/mqdefault.jpg" : videoScreendumpPath; 314 315 string vimeoJsClass = videoScreendumpPath.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "js-vimeo-video-thumbnail" : ""; 316 videoScreendumpPath = videoScreendumpPath.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "" : videoScreendumpPath; 317 318 string productName = product.Name; 319 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 320 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 321 322 RatioSettings ratioSettings = GetRatioSettings(); 323 324 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 325 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 326 <div class="icon-2 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 327 @if (videoScreendumpPath.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) < 0) 328 { 329 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@productName" @assetTitle class="@vimeoJsClass mw-100 mh-100" data-video-id="@videoId" style="object-fit: cover;"> 330 } 331 else 332 { 333 string videoType = Path.GetExtension(asset.Value).ToLower(); 334 335 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 336 <source src="@asset.Value" type="video/@videoType.Replace(".", "")"> 337 </video> 338 } 339 </div> 340 </div> 341 } 342 343 @helper RenderDocument(MediaViewModel asset) 344 { 345 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 346 string productName = product.Name; 347 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 348 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 349 string imageLinkPath = imagePath; 350 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 351 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 352 353 RatioSettings ratioSettings = GetRatioSettings(); 354 355 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@productName"> 356 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) 357 { 358 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 359 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle /> 360 </div> 361 } 362 else 363 { 364 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 365 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 366 </div> 367 } 368 </a> 369 } 370 371 @helper RenderVideoPlayer(MediaViewModel asset) 372 { 373 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Name; 374 string assetValue = asset.Value; 375 string videoId = asset.Value.Substring(asset.Value.LastIndexOf('/') + 1); 376 string type = assetValue.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "youtube" : ""; 377 type = assetValue.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "vimeo" : type; 378 type = assetValue.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf(".webm", StringComparison.OrdinalIgnoreCase) >= 0 ? "selfhosted" : type; 379 380 <div class="h-100" itemscope itemtype="https://schema.org/VideoObject"> 381 <span class="visually-hidden" itemprop="name">@assetName</span> 382 <span class="visually-hidden" itemprop="contentUrl">@asset.Value</span> 383 <span class="visually-hidden" itemprop="thumbnailUrl">@asset.Value</span> 384 @if (type != "selfhosted") 385 { 386 <div id="player_@(Pageview.CurrentParagraph.ID)_@(videoId)" 387 class="plyr__video-embed" 388 data-plyr-provider="@(type)" 389 data-plyr-embed-id="@videoId" 390 style="--plyr-color-main: var(--swift-foreground-color); height: 100%"> 391 </div> 392 393 <script type="module" src="/Files/Templates/Designs/Swift/Assets/js/plyr.js"></script> 394 <script type="module"> 395 var player = new Plyr('#player_@(Pageview.CurrentParagraph.ID)_@(videoId)', { 396 type: 'video', 397 youtube: { 398 noCookie: true, 399 showinfo: 0 400 }, 401 fullscreen: { 402 enabled: true, 403 iosNative: true, 404 } 405 }); 406 407 document.querySelectorAll('.js-video-modal').forEach(function (modal) { 408 modal.addEventListener('hidden.bs.modal', function (event) { 409 player.media.pause(); 410 }) 411 }); 412 </script> 413 } 414 else 415 { 416 string videoType = Path.GetExtension(assetValue).ToLower(); 417 418 <video preload="auto" class="h-100 w-100" style="object-fit: cover;" controls> 419 <source src="@assetValue" type="video/@videoType.Replace(".", "")"> 420 </video> 421 } 422 </div> 423 } 424
Error executing template "Designs/Swift/Paragraph/Swift_ProductDetailsImage.cshtml" System.ArgumentNullException: Value cannot be null. Parameter name: source at System.Linq.Enumerable.Where[TSource](IEnumerable`1 source, Func`2 predicate) at CompiledRazorTemplates.Dynamic.RazorEngine_2af8b6348c3243ad84491cdcb4f25a41.Execute() in D:\dynamicweb.net\Solutions\brdklee.cloud.dynamicweb-cms.com\files\Templates\Designs\Swift\Paragraph\Swift_ProductDetailsImage.cshtml:line 78 at RazorEngine.Templating.TemplateBase.RazorEngine.Templating.ITemplate.Run(ExecuteContext context, TextWriter reader) at RazorEngine.Templating.RazorEngineService.RunCompile(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag) at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass16_0.<RunCompile>b__0(TextWriter writer) at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter) at Dynamicweb.Rendering.RazorTemplateRenderingProvider.Render(Template template) at Dynamicweb.Rendering.TemplateRenderingService.Render(Template template) at Dynamicweb.Rendering.Template.RenderRazorTemplate()
1 @inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.ParagraphViewModel> 2 @using Dynamicweb.Ecommerce.ProductCatalog 3 @using Dynamicweb.Frontend 4 @using System.IO 5 6 @functions { 7 public ProductViewModel product { get; set; } = new ProductViewModel(); 8 public string galleryLayout { get; set; } 9 public string[] supportedImageFormats { get; set; } 10 public string[] supportedVideoFormats { get; set; } 11 public string[] supportedDocumentFormats { get; set; } 12 public string[] allSupportedFormats { get; set; } 13 14 public class RatioSettings { 15 public string Ratio { get; set; } 16 public string CssClass { get; set; } 17 public string CssVariable { get; set; } 18 public string Fill { get; set; } 19 } 20 21 public RatioSettings GetRatioSettings(string size = "desktop") { 22 var ratioSettings = new RatioSettings(); 23 24 string ratio = Model.Item.GetRawValueString("ImageAspectRatio", ""); 25 ratio = ratio != "0" ? ratio : ""; 26 string cssClass = ratio != "" && ratio != "fill" ? " ratio" : ""; 27 string cssVariable = ratio != "" && ratio != "fill" ? "--bs-aspect-ratio: " + ratio : ""; 28 cssClass = ratio == "fill" && size == "mobile" ? " ratio" : cssClass; 29 cssVariable = ratio == "fill" && size == "mobile" ? "--bs-aspect-ratio: 66%" : cssVariable; 30 31 ratioSettings.Ratio = ratio; 32 ratioSettings.CssClass = cssClass; 33 ratioSettings.CssVariable = cssVariable; 34 ratioSettings.Fill = ratio == "fill" ? " h-100" : ""; 35 36 return ratioSettings; 37 } 38 39 public string GetArrowsColor() 40 { 41 var invertColor = Model.Item.GetBoolean("InvertModalArrowsColor"); 42 var arrowsColor = invertColor ? " carousel-dark" : string.Empty; 43 return arrowsColor; 44 } 45 } 46 47 @{ 48 ProductViewModel product = null; 49 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 50 { 51 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 52 } 53 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 54 { 55 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 56 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 57 58 if (productList?.Products is object) 59 { 60 product = productList.Products[0]; 61 } 62 } 63 } 64 65 @if (product is object) { 66 @* Supported formats *@ 67 supportedImageFormats = new string[] { ".jpg", ".jpeg", ".webp", ".png", ".gif", ".bmp", ".tiff" }; 68 supportedVideoFormats = new string[] { "youtu.be", "youtube", "vimeo", ".mp4", ".webm" }; 69 supportedDocumentFormats = new string[] { ".pdf", ".docx", ".xlsx", ".ppt", "pptx" }; 70 allSupportedFormats = supportedImageFormats.Concat(supportedVideoFormats).Concat(supportedDocumentFormats).ToArray(); 71 72 @* Collect the assets *@ 73 var selectedAssetCategories = Model.Item.GetRawValueString("ImageAssets").Split(',').ToList(); 74 bool includeImagePatternImages = Model.Item.GetBoolean("ImagePatternImages"); 75 76 @* Needed image data collection to support both DefaultImage, ImagePatterns and Image Assets *@ 77 string defaultImage = product.DefaultImage != null ? product.DefaultImage.Value : ""; 78 IEnumerable<MediaViewModel> assetsImages = product.AssetCategories.Where(x => selectedAssetCategories.Contains(x.SystemName)).SelectMany(x => x.Assets); 79 assetsImages = assetsImages.OrderByDescending(x => x.Value.Equals(defaultImage)); 80 IEnumerable<MediaViewModel> assetsList = new MediaViewModel[]{}; 81 assetsList = assetsList.Union(assetsImages); 82 assetsList = includeImagePatternImages ? assetsList.Union(product.ImagePatternImages) : assetsList; 83 assetsList = includeImagePatternImages && assetsList.Count() == 0 ? assetsList.Append(product.DefaultImage) : assetsList; 84 85 bool defaultImageFallback = Model.Item.GetBoolean("DefaultImageFallback"); 86 bool showOnlyPrimaryImage = Model.Item.GetBoolean("ShowOnlyPrimaryImage"); 87 88 int totalAssets = 0; 89 if (showOnlyPrimaryImage == false) { 90 foreach (MediaViewModel asset in assetsList) { 91 var assetValue = asset.Value; 92 foreach (string format in allSupportedFormats) { 93 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 94 totalAssets++; 95 } 96 } 97 } 98 } 99 100 if((totalAssets == 0 && product.DefaultImage != null && selectedAssetCategories.Count() == 0) || (showOnlyPrimaryImage == true && product.DefaultImage != null) || totalAssets == 0 && defaultImageFallback) 101 { 102 assetsList = new List<MediaViewModel>(){ product.DefaultImage }; 103 totalAssets = 1; 104 } 105 106 107 @* Theme settings *@ 108 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 109 110 var badgeParms = new Dictionary<string, object>(); 111 badgeParms.Add("size", "h5"); 112 badgeParms.Add("saleBadgeType", Model.Item.GetRawValue("SaleBadgeType")); 113 badgeParms.Add("saleBadgeCssClassName", Model.Item.GetRawValue("SaleBadgeDesign")); 114 badgeParms.Add("newBadgeCssClassName", Model.Item.GetRawValue("NewBadgeDesign")); 115 badgeParms.Add("newPublicationDays", Model.Item.GetInt32("NewPublicationDays")); 116 badgeParms.Add("campaignBadgesValues", Model.Item.GetRawValueString("CampaignBadges")); 117 118 bool saleBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("SaleBadgeDesign")) && Model.Item.GetRawValueString("SaleBadgeDesign") != "none" ? true : false; 119 bool newBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("NewBadgeDesign")) && Model.Item.GetRawValueString("NewBadgeDesign") != "none" ? true : false; 120 DateTime createdDate = product.Created.Value; 121 bool showBadges = saleBadgeEnabled && product.Discount.Price != 0 ? true : false; 122 showBadges = (newBadgeEnabled && Model.Item.GetInt32("NewPublicationDays") == 0) || (newBadgeEnabled && (createdDate.AddDays(Model.Item.GetInt32("NewPublicationDays")) > DateTime.Now)) ? true : showBadges; 123 showBadges = !string.IsNullOrEmpty(Model.Item.GetRawValueString("CampaignBadges")) ? true : showBadges; 124 125 @* Get assets from selected categories or get all assets *@ 126 if (totalAssets != 0) { 127 int assetNumber = 0; 128 int thumbnailNumber = 0; 129 int modalAssetNumber = 0; 130 131 <div class="h-100@(theme) position-relative item_@Model.Item.SystemName.ToLower()"> 132 <div id="SmallScreenImages_@Model.ID" class="carousel@(GetArrowsColor())" data-bs-ride="carousel"> 133 <div class="carousel-inner h-100"> 134 @foreach (MediaViewModel asset in assetsList) { 135 var assetValue = asset.Value; 136 foreach (string format in allSupportedFormats) { 137 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 138 string activeSlide = assetNumber == 0 ? "active" : ""; 139 140 <div class="carousel-item @activeSlide" data-bs-interval="99999"> 141 @{@RenderAsset(asset, assetNumber, "mobile")} 142 </div> 143 assetNumber++; 144 } 145 } 146 } 147 </div> 148 </div> 149 150 @if (totalAssets > 1) { 151 <div id="SmallScreenImagesThumbnails_@Model.ID" class="grid grid-10 gap-2 overflow-x-auto my-3"> 152 @foreach (MediaViewModel asset in assetsList) { 153 var assetValue = asset.Value; 154 foreach (string format in allSupportedFormats) { 155 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 156 string imagePath = Dynamicweb.Context.Current.Server.UrlEncode(assetValue); 157 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; 158 string imagePathThumb = imagePath.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) < 0 && imagePath.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) < 0 ? $"/Admin/Public/GetImage.ashx?image={imagePath}&width=180&format=webp" : imagePath; 159 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 160 161 string videoId = assetValue.Substring(assetValue.LastIndexOf('/') + 1); 162 string vimeoJsClass = assetValue.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "js-vimeo-video-thumbnail" : ""; 163 164 bool isDocument = false; 165 foreach (string documentFormat in supportedDocumentFormats) { 166 if (assetValue.IndexOf(documentFormat, StringComparison.OrdinalIgnoreCase) >= 0) { 167 isDocument = true; 168 } 169 } 170 171 string assetName = asset.Name; 172 assetName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 173 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 174 175 if (!isDocument) { 176 RatioSettings ratioSettings = GetRatioSettings("desktop"); 177 178 <div class="border outline-none @(ratioSettings.CssClass)" style="@(ratioSettings.CssVariable); cursor: pointer" data-bs-target="#SmallScreenImages_@Model.ID" data-bs-slide-to="@thumbnailNumber"> 179 <div class="d-flex align-items-center justify-content-center overflow-hidden position-absolute h-100"> 180 @foreach (string videoFormat in supportedVideoFormats) { //Videos 181 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) { 182 <div class="icon-3 position-absolute text-light" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 183 } 184 } 185 </div> 186 @if (imagePathThumb.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) < 0) { 187 <img src="@imagePathThumb" alt="@assetName" @assetTitle class="p-0 p-lg-1 @vimeoJsClass w-100 h-100" style="object-fit: contain" data-video-id="@videoId"> 188 } else { 189 string videoType = Path.GetExtension(asset.Value).ToLower(); 190 191 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 192 <source src="@imagePathThumb" type="video/@videoType.Replace(".", "")"> 193 </video> 194 } 195 </div> 196 } else { 197 <a href="@assetValue" class="ratio ratio-4x3 border outline-none" style="cursor: pointer" download title="@asset.Value"> 198 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) { 199 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 200 <div class="icon-3 position-absolute text-light" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 201 </div> 202 <img src="@imagePathThumb" alt="@assetName" @assetTitle class="p-0 p-lg-1 mw-100 mh-100" style="object-fit: cover;"> 203 } else { 204 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 205 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 206 </div> 207 } 208 </a> 209 } 210 211 thumbnailNumber++; 212 } 213 } 214 } 215 </div> 216 } 217 218 @if (showBadges) { 219 <div class="position-absolute top-0 left-0 p-2 p-lg-3"> 220 @RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms) 221 </div> 222 } 223 </div> 224 225 @* Modal with slides *@ 226 <div class="modal fade swift_products-details-images-modal" id="modal_@Model.ID" tabindex="-1" aria-labelledby="productDetailsGalleryModalTitle_@Model.ID" aria-hidden="true"> 227 <div class="modal-dialog modal-dialog-centered modal-xl"> 228 <div class="modal-content"> 229 <div class="modal-header visually-hidden"> 230 <h5 class="modal-title" id="productDetailsGalleryModalTitle_@Model.ID">@product.Title</h5> 231 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 232 </div> 233 <div class="modal-body p-2 p-lg-3 h-100"> 234 <div id="ModalCarousel_@Model.ID" class="carousel@(GetArrowsColor()) h-100" data-bs-ride="carousel"> 235 <div class="carousel-inner h-100"> 236 @foreach (MediaViewModel asset in assetsList) { 237 var assetValue = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 238 foreach (string format in supportedImageFormats.Concat(supportedVideoFormats).ToArray()) { 239 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 240 string imagePath = assetValue; 241 string activeSlide = modalAssetNumber == 0 ? "active" : ""; 242 243 var parms = new Dictionary<string, object>(); 244 parms.Add("cssClass", "d-block mw-100 mh-100 m-auto"); 245 parms.Add("fullwidth", true); 246 parms.Add("columns", Model.GridRowColumnCount); 247 248 <div class="carousel-item @activeSlide h-100" data-bs-interval="99999"> 249 @foreach (string imageFormat in supportedImageFormats) { //Images 250 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0) { 251 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 252 } 253 } 254 255 @foreach (string videoFormat in supportedVideoFormats) { //Videos 256 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) { 257 {@RenderVideoPlayer(asset, "modal")} 258 } 259 } 260 </div> 261 262 modalAssetNumber++; 263 } 264 } 265 } 266 <button class="carousel-control-prev" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="prev"> 267 <span class="carousel-control-prev-icon" aria-hidden="true"></span> 268 <span class="visually-hidden">@Translate("Previous")</span> 269 </button> 270 <button class="carousel-control-next" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="next"> 271 <span class="carousel-control-next-icon" aria-hidden="true"></span> 272 <span class="visually-hidden">@Translate("Next")</span> 273 </button> 274 </div> 275 </div> 276 </div> 277 </div> 278 </div> 279 </div> 280 } else if (Pageview.IsVisualEditorMode) { 281 RatioSettings ratioSettings = GetRatioSettings("desktop"); 282 283 <div class="h-100 @theme"> 284 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 285 <img src="/Files/Images/missing_image.jpg" loading="lazy" decoding="async" class="mh-100 mw-100" style="object-fit: cover;"> 286 </div> 287 </div> 288 } 289 } else if (Pageview.IsVisualEditorMode) { 290 <div class="alert alert-dark m-0">@Translate("No products available")</div> 291 } 292 293 @helper RenderAsset(MediaViewModel asset, int assetNumber, string size = "desktop") { 294 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 295 string assetValue = asset.Value; 296 297 <div class="h-100 @(theme)"> 298 @foreach (string format in supportedImageFormats) { //Images 299 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 300 {@RenderImage(asset, assetNumber, size)} 301 } 302 } 303 @foreach (string format in supportedVideoFormats) { //Videos 304 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 305 if (Model.Item.GetString("OpenVideoInModal") == "true") { 306 {@RenderVideoScreendump(asset, assetNumber, size)} 307 } else { 308 {@RenderVideoPlayer(asset, size)} 309 } 310 } 311 } 312 @foreach (string format in supportedDocumentFormats) { //Documents 313 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 314 {@RenderDocument(asset, assetNumber, size)} 315 } 316 } 317 </div> 318 } 319 320 @helper RenderImage(MediaViewModel asset, int number, string size = "desktop") { 321 if (product is object) 322 { 323 string productName = product.Name; 324 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 325 string imageLinkPath = Dynamicweb.Context.Current.Server.UrlEncode(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 parms.Add("title", asset.DisplayName); 336 } 337 338 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") { 339 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); 340 } else { 341 parms.Add("cssClass", "mw-100 mh-100"); 342 } 343 344 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> 345 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide-to="@number"> 346 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 347 </div> 348 </a> 349 } 350 } 351 352 @helper RenderVideoScreendump(MediaViewModel asset, int number, string size = "desktop") { 353 if (product is object) 354 { 355 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 356 357 string videoScreendumpPath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : ""; 358 string videoId = videoScreendumpPath.Substring(videoScreendumpPath.LastIndexOf('/') + 1); 359 videoScreendumpPath = videoScreendumpPath.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || videoScreendumpPath.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "https://img.youtube.com/vi/" + videoId + "/maxresdefault.jpg" : videoScreendumpPath; 360 361 string vimeoJsClass = videoScreendumpPath.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "js-vimeo-video-thumbnail" : ""; 362 videoScreendumpPath = videoScreendumpPath.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "" : videoScreendumpPath; 363 364 string productName = product.Name; 365 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 366 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 367 368 RatioSettings ratioSettings = GetRatioSettings(size); 369 370 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable); cursor: pointer" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> 371 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide-to="@number"> 372 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 373 @if (videoScreendumpPath.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) < 0) 374 { 375 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@productName" @assetTitle class="@vimeoJsClass mw-100 mh-100" data-video-id="@videoId" style="object-fit: cover;" onload="CheckIfVideoThumbnailExist(this)"> 376 } 377 else 378 { 379 string videoType = Path.GetExtension(asset.Value).ToLower(); 380 381 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 382 <source src="@asset.Value" type="video/@videoType.Replace(".", "")"> 383 </video> 384 } 385 </div> 386 </div> 387 388 <script> 389 function CheckIfVideoThumbnailExist(image) { 390 if (image.width == 120) { 391 const lowQualityImage = "https://img.youtube.com/vi/@(videoId)/hqdefault.jpg" 392 image.src = lowQualityImage; 393 } 394 } 395 </script> 396 } 397 } 398 399 @helper RenderVideoPlayer(MediaViewModel asset, string size = "desktop") { 400 if (product is object) 401 { 402 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Name; 403 string assetValue = asset.Value; 404 string videoId = asset.Value.Substring(asset.Value.LastIndexOf('/') + 1); 405 string type = assetValue.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "youtube" : ""; 406 type = assetValue.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "vimeo" : type; 407 type = assetValue.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf(".webm", StringComparison.OrdinalIgnoreCase) >= 0 ? "selfhosted" : type; 408 409 string openInModal = Model.Item.GetString("OpenVideoInModal"); 410 bool autoPlay = Model.Item.GetBoolean("VideoAutoPlay"); 411 412 <div class="h-100" itemscope itemtype="https://schema.org/VideoObject"> 413 <span class="visually-hidden" itemprop="name">@assetName</span> 414 <span class="visually-hidden" itemprop="contentUrl">@asset.Value</span> 415 <span class="visually-hidden" itemprop="thumbnailUrl">@asset.Value</span> 416 417 @if (type != "selfhosted") 418 { 419 <div 420 id="player_@(Pageview.CurrentParagraph.ID)_@(videoId)_@size" 421 class="plyr__video-embed" 422 data-plyr-provider="@(type)" 423 data-plyr-embed-id="@videoId" 424 style="--plyr-color-main: var(--swift-foreground-color); height: 100%"> 425 </div> 426 427 <script type="module" src="/Files/Templates/Designs/Swift/Assets/js/plyr.js"></script> 428 429 <script type="module"> 430 var player = new Plyr('#player_@(Pageview.CurrentParagraph.ID)_@(videoId)_@size', { 431 type: 'video', 432 youtube: { 433 noCookie: true, 434 showinfo: 0 435 }, 436 fullscreen: { 437 enabled: true, 438 iosNative: true, 439 } 440 }); 441 442 @if (autoPlay && openInModal == "false") 443 { 444 <text> 445 player.config.autoplay = true; 446 player.config.muted = true; 447 player.config.volume = 0; 448 player.media.loop = true; 449 450 player.on('ready', function() { 451 if (player.config.autoplay === true) { 452 player.media.play(); 453 } 454 }); 455 </text> 456 } 457 458 @if (openInModal == "true") 459 { 460 <text> 461 var productDetailsGalleryModal = document.querySelector('#modal_@Model.ID') 462 productDetailsGalleryModal.addEventListener('hidden.bs.modal', function (event) { 463 player.media.pause(); 464 }) 465 </text> 466 } 467 </script> 468 } 469 else 470 { 471 string autoPlayAttributes = (autoPlay && openInModal == "false") ? "loop autoplay muted playsinline" : ""; 472 string videoType = Path.GetExtension(assetValue).ToLower(); 473 474 <video preload="auto" @autoPlayAttributes class="h-100 w-100" style="object-fit: cover;" controls> 475 <source src="@assetValue" type="video/@videoType.Replace(".", "")"> 476 </video> 477 } 478 </div> 479 } 480 } 481 482 @helper RenderDocument(MediaViewModel asset, int number, string size = "desktop") { 483 if (product is object) 484 { 485 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 486 487 string productName = product.Name; 488 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 489 string imageLinkPath = imagePath; 490 491 RatioSettings ratioSettings = GetRatioSettings(size); 492 493 var parms = new Dictionary<string, object>(); 494 parms.Add("alt", productName + asset.Keywords); 495 parms.Add("itemprop", "image"); 496 parms.Add("fullwidth", true); 497 parms.Add("columns", Model.GridRowColumnCount); 498 if (!string.IsNullOrEmpty(asset.DisplayName)) { 499 parms.Add("title", asset.DisplayName); 500 } 501 502 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") { 503 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); 504 } else { 505 parms.Add("cssClass", "mw-100 mh-100"); 506 } 507 508 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@Translate("Download")"> 509 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 510 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 511 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) { 512 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 513 } 514 </div> 515 </a> 516 } 517 } 518
Beklager. Der er ikke noget at se her