Error executing template "Designs/Swift/Paragraph/Swift_ProductDetailsImage.cshtml"
System.Data.SqlClient.SqlException (0x80131904): Transaction (Process ID 87) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
at System.Data.SqlClient.SqlDataReader.TryHasMoreRows(Boolean& moreRows)
at System.Data.SqlClient.SqlDataReader.TryReadInternal(Boolean setTimeout, Boolean& more)
at System.Data.SqlClient.SqlDataReader.Read()
at Dynamicweb.Ecommerce.Products.ProductRepository.Dynamicweb.Ecommerce.Products.IProductRepository.GetProductKeysByGroupId(String groupId, Boolean useOrderBy, Boolean includeVariants, String productLanguageId, Boolean doRefactoring, Boolean useAssortments)
at Dynamicweb.Ecommerce.Orders.Discounts.Discount.ProcessProductSelections(HashSet`1& productKeys, HashSet`1& includedQueries, HashSet`1& excludedQueries)
at Dynamicweb.Ecommerce.Orders.Discounts.Discount.GetRelevantProductKeys()
at Dynamicweb.Ecommerce.Orders.Discounts.DiscountService.StatelessSetLookup(Discount discount, ConcurrentDictionary`2 lookup)
at Dynamicweb.Ecommerce.Orders.Discounts.DiscountService.InitializeLookup()
at System.Lazy`1.CreateValue()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Lazy`1.get_Value()
at Dynamicweb.Ecommerce.Orders.Discounts.DiscountService.GetDiscounts(DiscountApplyType[] discountTypes, String[] productKeys, User user)
at Dynamicweb.Ecommerce.Orders.Discounts.DiscountInfoCollection.LoadDiscounts()
at Dynamicweb.Ecommerce.ProductCatalog.ViewEngine.GetDiscountInfo(PriceViewModelSettings settings, Product product)
at System.Lazy`1.CreateValue()
at System.Lazy`1.LazyInitValue()
at Dynamicweb.Ecommerce.ProductCatalog.ViewEngine.<>c__DisplayClass3_2.<BulkCreateView>b__46()
at System.Lazy`1.CreateValue()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Lazy`1.get_Value()
at Dynamicweb.Ecommerce.ProductCatalog.ViewEngine.GetPrice(PriceViewModelSettings settings, IList`1 products, Boolean& pricesHasBeenPrepared, Object lock, Lazy`1 priceInfo)
at Dynamicweb.Ecommerce.ProductCatalog.ViewEngine.<>c__DisplayClass3_2.<BulkCreateView>b__51()
at System.Lazy`1.CreateValue()
at System.Lazy`1.LazyInitValue()
at CompiledRazorTemplates.Dynamic.RazorEngine_fa0cf5cecdde4a9a8e4a64567db54178.Execute() in D:\dynamicweb.net\Solutions\brdklee.cloud.dynamicweb-cms.com\files\Templates\Designs\Swift\Paragraph\Swift_ProductDetailsImage.cshtml:line 121
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()
ClientConnectionId:fef7765e-6e46-4ec9-ae86-c26a77b01d5e
Error Number:1205,State:52,Class:13
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