Error executing template "Designs/Swift/Paragraph/Swift_ProductDetailsGallery.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_05323ca0871143c38c591890d2581abd.Execute() in C:\Projects\Solutions\mountainbikemuseum.nl\Files\Templates\Designs\Swift\Paragraph\Swift_ProductDetailsGallery.cshtml:line 116
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 {
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 {
24 var ratioSettings = new RatioSettings();
25
26 string ratio = Model.Item.GetRawValueString("ImageAspectRatio", "");
27 ratio = ratio != "0" ? ratio : "";
28 string cssClass = ratio != "" && ratio != "fill" ? " ratio" : "";
29 string cssVariable = ratio != "" && ratio != "fill" ? "--bs-aspect-ratio: " + ratio : "";
30 cssClass = ratio != "" && ratio == "fill" && size == "mobile" ? " ratio" : cssClass;
31 cssVariable = ratio != "" && ratio == "fill" && size == "mobile" ? "--bs-aspect-ratio: 66%" : cssVariable;
32
33 ratioSettings.Ratio = ratio;
34 ratioSettings.CssClass = cssClass;
35 ratioSettings.CssVariable = cssVariable;
36 ratioSettings.Fill = ratio == "fill" ? " h-100" : "";
37
38 return ratioSettings;
39 }
40
41 public string GetColumnClass(int total, int assetNumber)
42 {
43 string colClass = total > 1 ? "g-col-lg-6" : "g-col-12";
44 colClass = galleryLayout == "full-first" && assetNumber == 0 ? "g-col-12" : colClass;
45 colClass = galleryLayout == "full-last" && assetNumber == (total - 1) ? "g-col-12" : colClass;
46 colClass = galleryLayout == "advanced-grid" && assetNumber > 1 ? "g-col-4" : colClass;
47
48 colClass = galleryLayout == "advanced-grid" && total == 1 ? "g-col-12" : colClass;
49 colClass = galleryLayout == "advanced-grid" && total == 3 && assetNumber == 2 ? "g-col-12" : colClass;
50 colClass = galleryLayout == "advanced-grid" && total == 4 && assetNumber == 2 ? "g-col-6" : colClass;
51 colClass = galleryLayout == "advanced-grid" && total == 4 && assetNumber == 3 ? "g-col-6" : colClass;
52 colClass = galleryLayout == "advanced-grid" && total == 6 && assetNumber == 5 ? "g-col-12" : colClass;
53 colClass = galleryLayout == "advanced-grid" && total == 7 && assetNumber == 5 ? "g-col-6" : colClass;
54 colClass = galleryLayout == "advanced-grid" && total == 7 && assetNumber == 6 ? "g-col-6" : colClass;
55 colClass = galleryLayout == "advanced-grid" && total == 9 && assetNumber == 8 ? "g-col-12" : colClass;
56
57 return colClass;
58 }
59
60 public string GetArrowsColor()
61 {
62 var invertColor = Model.Item.GetBoolean("InvertModalArrowsColor");
63 var arrowsColor = invertColor ? " carousel-dark" : string.Empty;
64 return arrowsColor;
65 }
66 }
67
68 @{
69 @* Get the product data *@
70
71 ProductViewModelSettings productSetting = new ProductViewModelSettings
72 {
73 LanguageId = Dynamicweb.Ecommerce.Common.Context.LanguageID,
74 CurrencyCode = Dynamicweb.Ecommerce.Common.Context.Currency.Code,
75 CountryCode = Dynamicweb.Ecommerce.Common.Context.Country.Code2,
76 ShopId = Pageview.Area.EcomShopId
77 };
78
79 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails"))
80 {
81 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"];
82 }
83 else if (Pageview.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode)
84 {
85 string dummyProductId = "";
86 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page);
87 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel();
88 if (productList.Products != null)
89 {
90 foreach (var p in productList.Products) { dummyProductId = p.Id; }
91 ProductViewModel dummyProduct = dummyProductId != "" ? ViewModelFactory.CreateView(productSetting, dummyProductId) : new ProductViewModel();
92 product = dummyProduct;
93 }
94 else
95 {
96 product = ViewModelFactory.CreateView(productSetting, Dynamicweb.Ecommerce.Services.Products.GetLastActiveProducts(1, Dynamicweb.Ecommerce.Common.Context.LanguageID, false).FirstOrDefault().Id);
97 }
98 }
99 else if (Pageview.Item["DummyProduct"] == null)
100 {
101 product = ViewModelFactory.CreateView(productSetting, Dynamicweb.Ecommerce.Services.Products.GetLastActiveProducts(1, Dynamicweb.Ecommerce.Common.Context.LanguageID, false).FirstOrDefault().Id);
102 }
103
104 @* Supported formats *@
105 supportedImageFormats = new string[] { ".jpg", ".jpeg", ".webp", ".png", ".gif", ".bmp", ".tiff" };
106 supportedVideoFormats = new string[] { "youtu.be", "youtube", "vimeo", ".mp4", ".webm" };
107 supportedDocumentFormats = new string[] { ".pdf", ".docx", ".xlsx", ".ppt", "pptx" };
108 allSupportedFormats = supportedImageFormats.Concat(supportedVideoFormats).Concat(supportedDocumentFormats).ToArray();
109
110 @* Collect the assets *@
111 var selectedAssetCategories = Model.Item.GetRawValueString("ImageAssets").Split(',').ToList();
112 bool includeImagePatternImages = Model.Item.GetBoolean("ImagePatternImages");
113
114 @* Needed image data collection to support both DefaultImage, ImagePatterns and Image Assets *@
115 string defaultImage = product.DefaultImage != null ? product.DefaultImage.Value : "";
116 IEnumerable<MediaViewModel> assetsImages = product.AssetCategories.Where(x => selectedAssetCategories.Contains(x.SystemName)).SelectMany(x => x.Assets);
117 assetsImages = assetsImages.OrderByDescending(x => x.Value.Equals(defaultImage));
118 IEnumerable<MediaViewModel> assetsList = new MediaViewModel[] { };
119 assetsList = assetsList.Union(assetsImages);
120 assetsList = includeImagePatternImages ? assetsList.Union(product.ImagePatternImages) : assetsList;
121 assetsList = includeImagePatternImages && assetsList.Count() == 0 ? assetsList.Append(product.DefaultImage) : assetsList;
122
123 bool defaultImageFallback = Model.Item.GetBoolean("DefaultImageFallback");
124
125 int totalAssets = 0;
126 foreach (MediaViewModel asset in assetsList)
127 {
128 var assetValue = asset.Value.ToLower();
129 foreach (string format in allSupportedFormats)
130 {
131 if (assetValue.Contains(format))
132 {
133 totalAssets++;
134 }
135 }
136 }
137
138 if (totalAssets == 0)
139 {
140 if (defaultImageFallback)
141 {
142 assetsList = new List<MediaViewModel>() { product.DefaultImage };
143 totalAssets = 1;
144 }
145 else
146 {
147 assetsList = new List<MediaViewModel>() { };
148 totalAssets = 0;
149 }
150 }
151
152 @* Layout settings *@
153 string spacing = Model.Item.GetRawValueString("Spacing", "4");
154 spacing = spacing == "none" ? "gap-0" : spacing;
155 spacing = spacing == "small" ? "gap-3" : spacing;
156 spacing = spacing == "large" ? "gap-4" : spacing;
157
158 galleryLayout = Model.Item.GetRawValueString("Layout", "grid");
159 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : "";
160
161 var badgeParms = new Dictionary<string, object>();
162 badgeParms.Add("size", "h5");
163 badgeParms.Add("saleBadgeType", Model.Item.GetRawValue("SaleBadgeType"));
164 badgeParms.Add("saleBadgeCssClassName", Model.Item.GetRawValue("SaleBadgeDesign"));
165 badgeParms.Add("newBadgeCssClassName", Model.Item.GetRawValue("NewBadgeDesign"));
166 badgeParms.Add("newPublicationDays", Model.Item.GetInt32("NewPublicationDays"));
167 badgeParms.Add("campaignBadgesValues", Model.Item.GetRawValueString("CampaignBadges"));
168
169 bool saleBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("SaleBadgeDesign")) && Model.Item.GetRawValueString("SaleBadgeDesign") != "none" ? true : false;
170 bool newBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("NewBadgeDesign")) && Model.Item.GetRawValueString("NewBadgeDesign") != "none" ? true : false;
171 DateTime createdDate = product.Created.Value;
172 bool showBadges = saleBadgeEnabled && product.Discount.Price != 0 ? true : false;
173 showBadges = (newBadgeEnabled && Model.Item.GetInt32("NewPublicationDays") == 0) || (newBadgeEnabled && (createdDate.AddDays(Model.Item.GetInt32("NewPublicationDays")) > DateTime.Now)) ? true : showBadges;
174 showBadges = !string.IsNullOrEmpty(Model.Item.GetRawValueString("CampaignBadges")) ? true : showBadges;
175 }
176
177 @* Get assets from selected categories or get all assets *@
178 @if (totalAssets != 0 && assetsList.Count() != 0)
179 {
180 int desktopAssetNumber = 0;
181 int mobileAssetNumber = 0;
182 int mobileAssetThumbnailNumber = 0;
183 int modalAssetNumber = 0;
184
185 @* Desktop: Show the gallery on large screens *@
186 <div class="d-none d-lg-block h-100 position-relative @theme item_@Model.Item.SystemName.ToLower() desktop">
187 <div class="grid @spacing">
188 @foreach (MediaViewModel asset in assetsList)
189 {
190 var assetName = asset.Value.ToLower();
191 foreach (string format in allSupportedFormats)
192 {
193 if (assetName.Contains(format))
194 {
195 <div class="@GetColumnClass(totalAssets, desktopAssetNumber)">
196 @{@RenderAsset(asset, desktopAssetNumber)}
197 </div>
198 desktopAssetNumber++;
199 }
200 }
201 }
202 </div>
203
204 @if (showBadges)
205 {
206 <div class="position-absolute top-0 left-0 p-2 p-lg-3 w-100">
207 @RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms)
208 </div>
209 }
210 </div>
211
212 @* Mobile: Show the thumbs on small screens *@
213 <div class="d-block d-lg-none mx-lg-0 position-relative @theme item_@Model.Item.SystemName.ToLower() mobile">
214 <div id="SmallScreenImages_@Model.ID" class="carousel@(GetArrowsColor())" data-bs-ride="carousel">
215 <div class="carousel-inner h-100">
216 @foreach (MediaViewModel asset in assetsList)
217 {
218 var assetValue = asset.Value.ToLower();
219 foreach (string format in allSupportedFormats)
220 {
221 if (assetValue.Contains(format))
222 {
223 string activeSlide = mobileAssetNumber == 0 ? "active" : "";
224
225 <div class="carousel-item @activeSlide" data-bs-interval="99999">
226 @{@RenderAsset(asset, mobileAssetNumber, "mobile")}
227 </div>
228 mobileAssetNumber++;
229 }
230 }
231 }
232 </div>
233 </div>
234
235 @if (totalAssets > 1)
236 {
237 <div id="SmallScreenImagesThumbnails_@Model.ID" class="d-flex flex-nowrap gap-2 overflow-x-auto my-3">
238 @foreach (MediaViewModel asset in assetsList)
239 {
240 var assetValue = asset.Value;
241 foreach (string format in allSupportedFormats)
242 {
243 if (assetValue.Contains(format))
244 {
245 string imagePath = Dynamicweb.Context.Current.Server.UrlEncode(assetValue);
246 imagePath = assetValue.Contains("youtu.be") || assetValue.Contains("youtube") ? "https://img.youtube.com/vi/" + assetValue.Substring(assetValue.LastIndexOf('/') + 1) + "/default.jpg" : imagePath;
247 string imagePathThumb = !imagePath.Contains("youtube") ? $"/Admin/Public/GetImage.ashx?image={imagePath}&width=160&format=webp" : imagePath;
248 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/";
249
250 string videoId = assetValue.Substring(assetValue.LastIndexOf('/') + 1);
251 string vimeoJsClass = assetValue.Contains("vimeo") ? "js-vimeo-video-thumbnail" : "";
252
253 string productName = product.Name;
254 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : "";
255 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : "";
256
257 <div class="ratio ratio-4x3 border outline-none" style="flex:0 0 80px" data-bs-target="#SmallScreenImages_@Model.ID" data-bs-slide-to="@mobileAssetThumbnailNumber">
258 @foreach (string videoFormat in supportedVideoFormats)
259 {
260 if (assetValue.Contains(videoFormat))
261 {
262 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100">
263 <div class="icon-3 position-absolute text-light" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div>
264 </div>
265 }
266 }
267 <img src="@imagePathThumb" class="p-1 @vimeoJsClass mw-100 mh-100" data-video-id="@videoId" style="object-fit: cover;" alt="@productName" @assetTitle>
268 </div>
269
270 mobileAssetThumbnailNumber++;
271 }
272 }
273 }
274 </div>
275 }
276
277 @if (showBadges)
278 {
279 <div class="position-absolute top-0 left-0 p-2 p-lg-3">
280 @RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms)
281 </div>
282 }
283 </div>
284
285 @* Modal with slides *@
286 <div class="modal fade swift_products-details-images-modal" id="modal_@Model.ID" tabindex="-1" aria-labelledby="productDetailsGalleryModalTitle_@Model.ID" aria-hidden="true">
287 <div class="modal-dialog modal-dialog-centered modal-xl">
288 <div class="modal-content">
289 <div class="modal-header visually-hidden">
290 <h5 class="modal-title" id="productDetailsGalleryModalTitle_@Model.ID">@product.Title</h5>
291 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
292 </div>
293 <div class="modal-body p-2 p-lg-3 h-100">
294 <div id="ModalCarousel_@Model.ID" class="carousel@(GetArrowsColor()) h-100" data-bs-ride="carousel">
295 <div class="carousel-inner h-100 @theme">
296 @foreach (MediaViewModel asset in assetsList)
297 {
298 var assetValue = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value;
299 foreach (string format in allSupportedFormats)
300 {
301 if (assetValue.Contains(format))
302 {
303 string imagePath = assetValue;
304 string activeSlide = modalAssetNumber == 0 ? "active" : "";
305
306 var parms = new Dictionary<string, object>();
307 parms.Add("cssClass", "d-block mw-100 mh-100 m-auto");
308 parms.Add("fullwidth", true);
309 parms.Add("columns", Model.GridRowColumnCount);
310
311 <div class="carousel-item @activeSlide h-100" data-bs-interval="99999">
312 @foreach (string imageFormat in supportedImageFormats)
313 {
314 if (assetValue.Contains(imageFormat))
315 {
316 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms)
317 }
318 }
319
320 @foreach (string videoFormat in supportedVideoFormats)
321 {
322 if (assetValue.Contains(videoFormat))
323 {
324 {@RenderVideoPlayer(asset, "modal")}
325 }
326 }
327 </div>
328
329 modalAssetNumber++;
330 }
331 }
332 }
333 <button class="carousel-control-prev" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="prev">
334 <span class="carousel-control-prev-icon" aria-hidden="true"></span>
335 <span class="visually-hidden">@Translate("Previous")</span>
336 </button>
337 <button class="carousel-control-next" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="next">
338 <span class="carousel-control-next-icon" aria-hidden="true"></span>
339 <span class="visually-hidden">@Translate("Next")</span>
340 </button>
341 </div>
342 </div>
343 </div>
344 </div>
345 </div>
346 </div>
347 }
348 else if (Pageview.IsVisualEditorMode)
349 {
350 RatioSettings ratioSettings = GetRatioSettings("desktop");
351
352 <div class="h-100 @theme">
353 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)">
354 <img src="/Files/Images/missing_image.jpg" loading="lazy" decoding="async" class="mh-100 mw-100" style="object-fit: cover;" alt="@Translate("Missing image")">
355 </div>
356 </div>
357 }
358
359 @helper RenderAsset(MediaViewModel asset, int assetNumber, string size = "desktop")
360 {
361 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : "";
362 string assetValue = asset.Value;
363
364 <div class="h-100 @(theme)">
365 @foreach (string format in supportedImageFormats)
366 { //Images
367 if (assetValue.Contains(format))
368 {
369 {@RenderImage(asset, assetNumber, size)}
370 }
371 }
372 @foreach (string format in supportedVideoFormats)
373 { //Videos
374 if (assetValue.Contains(format))
375 {
376 if (Model.Item.GetString("OpenVideoInModal") == "true")
377 {
378 {@RenderVideoScreendump(asset, assetNumber, size)}
379 }
380 else
381 {
382 {@RenderVideoPlayer(asset, size)}
383 }
384 }
385 }
386 @foreach (string format in supportedDocumentFormats)
387 { //Documents
388 if (assetValue.Contains(format))
389 {
390 {@RenderDocument(asset, assetNumber, size)}
391 }
392 }
393 </div>
394 }
395
396 @helper RenderImage(MediaViewModel asset, int number, string size = "desktop")
397 {
398 string productName = product.Name;
399 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value;
400 string imageLinkPath = Dynamicweb.Context.Current.Server.UrlEncode(imagePath);
401
402 RatioSettings ratioSettings = GetRatioSettings(size);
403
404 var parms = new Dictionary<string, object>();
405 parms.Add("alt", productName);
406 parms.Add("itemprop", "image");
407 parms.Add("fullwidth", true);
408 parms.Add("columns", Model.GridRowColumnCount);
409 if (!string.IsNullOrEmpty(asset.DisplayName))
410 {
411 parms.Add("title", asset.DisplayName);
412 }
413
414 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid")
415 {
416 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover");
417 }
418 else
419 {
420 parms.Add("cssClass", "mw-100 mh-100");
421 }
422
423 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID">
424 <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">
425 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms)
426 </div>
427 </a>
428 }
429
430 @helper RenderVideoScreendump(MediaViewModel asset, int number, string size = "desktop")
431 {
432 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/";
433
434 string videoScreendumpPath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : "";
435 string videoId = videoScreendumpPath.Substring(videoScreendumpPath.LastIndexOf('/') + 1);
436 videoScreendumpPath = videoScreendumpPath.Contains("youtu.be") || videoScreendumpPath.Contains("youtube") ? "https://img.youtube.com/vi/" + videoId + "/maxresdefault.jpg" : videoScreendumpPath;
437
438 string vimeoJsClass = videoScreendumpPath.Contains("vimeo") ? "js-vimeo-video-thumbnail" : "";
439 videoScreendumpPath = videoScreendumpPath.Contains("vimeo") ? "" : videoScreendumpPath;
440
441 string productName = product.Name;
442 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : "";
443 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : "";
444
445 RatioSettings ratioSettings = GetRatioSettings(size);
446
447 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable); cursor: pointer" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID">
448 <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">
449 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div>
450 @if (!videoScreendumpPath.Contains(".mp4"))
451 {
452 <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)">
453 }
454 else
455 {
456 string videoType = Path.GetExtension(asset.Value).ToLower();
457
458 <video preload="auto" class="h-100 w-100" style="object-fit: contain;">
459 <source src="@asset.Value" type="video/@videoType.Replace(".", "")">
460 </video>
461 }
462 </div>
463 </div>
464
465 <script>
466 function CheckIfVideoThumbnailExist(image) {
467 if (image.width == 120) {
468 const lowQualityImage = "https://img.youtube.com/vi/@(videoId)/hqdefault.jpg"
469 image.src = lowQualityImage;
470 }
471 }
472 </script>
473 }
474
475 @helper RenderVideoPlayer(MediaViewModel asset, string size = "desktop")
476 {
477 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Name;
478 string assetValue = asset.Value;
479 string videoId = asset.Value.Substring(asset.Value.LastIndexOf('/') + 1);
480 string type = assetValue.Contains("youtu.be") || assetValue.Contains("youtube") ? "youtube" : "";
481 type = assetValue.Contains("vimeo") ? "vimeo" : type;
482 type = assetValue.Contains(".mp4") || assetValue.Contains(".webm") ? "selfhosted" : type;
483
484 string openInModal = Model.Item.GetString("OpenVideoInModal");
485 bool autoPlay = Model.Item.GetBoolean("VideoAutoPlay");
486
487 <div class="h-100" itemscope itemtype="https://schema.org/VideoObject">
488 <span class="visually-hidden" itemprop="name">@assetName</span>
489 <span class="visually-hidden" itemprop="contentUrl">@asset.Value</span>
490 <span class="visually-hidden" itemprop="thumbnailUrl">@asset.Value</span>
491 @if (type != "selfhosted")
492 {
493 <div id="player_@(Pageview.CurrentParagraph.ID)_@(videoId)_@size"
494 class="plyr__video-embed"
495 data-plyr-provider="@(type)"
496 data-plyr-embed-id="@videoId"
497 style="--plyr-color-main: var(--swift-foreground-color); height: 100%">
498 </div>
499
500 <script type="module" src="/Files/Templates/Designs/Swift/Assets/js/plyr.js"></script>
501
502 <script type="module">
503 var player = new Plyr('#player_@(Pageview.CurrentParagraph.ID)_@(videoId)_@size', {
504 type: 'video',
505 youtube: {
506 noCookie: true,
507 showinfo: 0
508 },
509 fullscreen: {
510 enabled: true,
511 iosNative: true,
512 }
513 });
514
515 @if (autoPlay && openInModal == "false") {
516 <text>
517 player.config.autoplay = true;
518 player.config.muted = true;
519 player.config.volume = 0;
520 player.media.loop = true;
521
522 player.on('ready', function() {
523 if (player.config.autoplay === true) {
524 player.media.play();
525 }
526 });
527 </text>
528 }
529
530 @if (openInModal == "true") {
531 <text>
532 var productDetailsGalleryModal = document.querySelector('#modal_@Model.ID')
533 productDetailsGalleryModal.addEventListener('hidden.bs.modal', function (event) {
534 player.media.pause();
535 })
536 </text>
537 }
538 </script>
539 }
540 else
541 {
542 string autoPlayAttributes = (autoPlay && openInModal == "false") ? "loop autoplay muted playsinline" : "";
543 string videoType = Path.GetExtension(assetValue).ToLower();
544
545 <video preload="auto" @autoPlayAttributes class="h-100 w-100" style="object-fit: cover;" controls>
546 <source src="@assetValue" type="video/@videoType.Replace(".", "")">
547 </video>
548 }
549 </div>
550 }
551
552 @helper RenderDocument(MediaViewModel asset, int number, string size = "desktop")
553 {
554 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/";
555
556 string productName = product.Name;
557 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value;
558 string imageLinkPath = imagePath;
559
560 RatioSettings ratioSettings = GetRatioSettings(size);
561
562 var parms = new Dictionary<string, object>();
563 parms.Add("alt", productName);
564 parms.Add("itemprop", "image");
565 parms.Add("fullwidth", true);
566 parms.Add("columns", Model.GridRowColumnCount);
567 if (!string.IsNullOrEmpty(asset.DisplayName))
568 {
569 parms.Add("title", asset.DisplayName);
570 }
571
572 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid")
573 {
574 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover");
575 }
576 else
577 {
578 parms.Add("cssClass", "mw-100 mh-100");
579 }
580
581 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download title="@Translate("Download")">
582 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100">
583 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div>
584 @if (asset.Value.Contains(".pdf"))
585 {
586 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms)
587 }
588 else
589 {
590
591 }
592 </div>
593 </a>
594 }
595