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