Error parsing template "Designs/Swift/Paragraph/Swift_ProductDetailsImage.cshtml"
Line 109: (108:156) - Expected a "{" but found a "@". Block statements must be enclosed in "{" and "}". You cannot use single-statement control-flow statements in CSHTML pages. For example, the following is not allowed:
@if(isLoggedIn)
Hello, @user
Instead, wrap the contents of the block in "{}":
@if(isLoggedIn) {
Hello, @user
}
1 @inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.ParagraphViewModel>
2 @using Dynamicweb.Ecommerce.ProductCatalog
3 @using Dynamicweb.Frontend
4 @using System.IO
5 @using System.Text.RegularExpressions;
6
7 @functions {
8 public ProductViewModel product { get; set; } = new ProductViewModel();
9 public string galleryLayout { get; set; }
10 public string[] supportedImageFormats { get; set; }
11 public string[] supportedVideoFormats { get; set; }
12 public string[] supportedDocumentFormats { get; set; }
13 public string[] allSupportedFormats { get; set; }
14
15 public class RatioSettings
16 {
17 public string Ratio { get; set; }
18 public string CssClass { get; set; }
19 public string CssVariable { get; set; }
20 public string Fill { get; set; }
21 }
22
23 public RatioSettings GetRatioSettings(string size = "desktop")
24 {
25 var ratioSettings = new RatioSettings();
26
27 string ratio = Model.Item.GetRawValueString("ImageAspectRatio", "");
28 ratio = ratio != "0" ? ratio : "";
29 string cssClass = ratio != "" && ratio != "fill" ? " ratio" : "";
30 string cssVariable = ratio != "" && ratio != "fill" ? "--bs-aspect-ratio: " + ratio : "";
31 cssClass = ratio == "fill" && size == "mobile" ? " ratio" : cssClass;
32 cssVariable = ratio == "fill" && size == "mobile" ? "--bs-aspect-ratio: 66%" : cssVariable;
33
34 ratioSettings.Ratio = ratio;
35 ratioSettings.CssClass = cssClass;
36 ratioSettings.CssVariable = cssVariable;
37 ratioSettings.Fill = ratio == "fill" ? " h-100" : "";
38
39 return ratioSettings;
40 }
41
42 public string GetArrowsColor()
43 {
44 var invertColor = Model.Item.GetBoolean("InvertModalArrowsColor");
45 var arrowsColor = invertColor ? " carousel-dark" : string.Empty;
46 return arrowsColor;
47 }
48
49 public string GetThumbnailPlacement()
50 {
51 return Model.Item.GetRawValueString("ThumbnailPlacement", "bottom");
52 }
53
54 public string GetThumbnailRowSettingCss()
55 {
56 switch (GetThumbnailPlacement())
57 {
58 case "bottom":
59 return "d-flex flex-wrap";
60 case "left":
61 return "d-flex flex-column order-first";
62 case "right":
63 return "d-flex flex-column order-last";
64 default:
65 return "d-flex flex-wrap";
66 }
67 }
68
69 public Dictionary<string, object> GetVideoParams(MediaViewModel asset, string size)
70 {
71 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Name;
72 string type = GetVideoType(asset.Value);
73 bool openInModal = Model.Item.GetString("OpenVideoInModal") == "true" ? true : false;
74 bool autoPlay = Model.Item.GetBoolean("VideoAutoPlay");
75
76 var videoParams = new Dictionary<string, object>();
77 videoParams.Add("AssetName", asset.Name);
78 videoParams.Add("AssetVideoType", type);
79 videoParams.Add("AssetDisplayName", asset.DisplayName);
80 videoParams.Add("OpenVideoInModal", openInModal);
81 videoParams.Add("VideoAutoPlay", autoPlay);
82 videoParams.Add("Size", size);
83 videoParams.Add("Id", Model.ID);
84 return videoParams;
85
86 }
87
88 public string GetVideoType(string assetValue)
89 {
90 string type = assetValue.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "youtube" : string.Empty;
91 type = assetValue.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "vimeo" : type;
92 type = string.IsNullOrEmpty(type) ? "selfhosted" : type;
93
94 return type;
95 }
96
97 public string GetYoutubeScreenDump(string assetValue, string quality)
98 {
99 var regex = new Regex(@"(?:youtube\.com\/.*[\?&]v=|youtu\.be\/|youtube\.com\/embed\/)([\w-]+)(?:\?.*)?");
100 Match match = regex.Match(assetValue);
101 string videoId = match.Success ? match.Groups[1].Value : string.Empty;
102 string youtubeThumbnail = $"https://img.youtube.com/vi/{videoId}/{quality}.jpg";
103 return youtubeThumbnail;
104 }
105 }
106
107 @{
108 ProductViewModel product = null;
109 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails") && !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.QueryString.Get("ProductID"))) @*<-Custom code: null check*@
110 {
111 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"];
112 }
113 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode)
114 {
115 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page);
116 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel();
117
118 if (productList?.Products is object)
119 {
120 product = productList.Products[0];
121 }
122 }
123 }
124
125 @if (product is object && product != null)@*<-Custom code: null check*@
126 {
127 @* Supported formats *@
128 supportedImageFormats = new string[] { ".jpg", ".jpeg", ".webp", ".png", ".gif", ".bmp", ".tiff" };
129 supportedVideoFormats = new string[] { "youtu.be", "youtube", "vimeo", ".mp4", ".webm" };
130 supportedDocumentFormats = new string[] { ".pdf", ".docx", ".xlsx", ".ppt", "pptx" };
131 allSupportedFormats = supportedImageFormats.Concat(supportedVideoFormats).Concat(supportedDocumentFormats).ToArray();
132
133 @* Collect the assets *@
134 var selectedAssetCategories = Model.Item.GetList("ImageAssets")?.GetRawValue().OfType<string>();
135 bool includeImagePatternImages = Model.Item.GetBoolean("ImagePatternImages");
136
137 @* Needed image data collection to support both DefaultImage, ImagePatterns and Image Assets *@
138 string defaultImage = product.DefaultImage != null ? product.DefaultImage.Value : "";
139 IEnumerable<MediaViewModel> assetsImages = product.AssetCategories.Where(x => selectedAssetCategories.Contains(x.SystemName)).SelectMany(x => x.Assets);
140 assetsImages = assetsImages.OrderByDescending(x => x.Value.Equals(defaultImage));
141 IEnumerable<MediaViewModel> assetsList = new MediaViewModel[] { };
142 assetsList = assetsList.Union(assetsImages);
143 assetsList = includeImagePatternImages ? assetsList.Union(product.ImagePatternImages) : assetsList;
144 assetsList = includeImagePatternImages && assetsList.Count() == 0 ? assetsList.Append(product.DefaultImage) : assetsList;
145
146 bool defaultImageFallback = Model.Item.GetBoolean("DefaultImageFallback");
147 bool showOnlyPrimaryImage = Model.Item.GetBoolean("ShowOnlyPrimaryImage");
148
149 int totalAssets = 0;
150 if (showOnlyPrimaryImage == false)
151 {
152 foreach (MediaViewModel asset in assetsList)
153 {
154 var assetValue = asset.Value;
155 foreach (string format in allSupportedFormats)
156 {
157 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0)
158 {
159 totalAssets++;
160 }
161 }
162 }
163 }
164
165 if ((totalAssets == 0 && product.DefaultImage != null && selectedAssetCategories.Count() == 0) || (showOnlyPrimaryImage == true && product.DefaultImage != null) || totalAssets == 0 && defaultImageFallback)
166 {
167 assetsList = new List<MediaViewModel>() { product.DefaultImage };
168 totalAssets = 1;
169 }
170
171 @* Theme settings *@
172 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : "";
173
174 var badgeParms = new Dictionary<string, object>();
175 badgeParms.Add("size", "h5");
176 badgeParms.Add("saleBadgeType", Model.Item.GetRawValue("SaleBadgeType"));
177 badgeParms.Add("saleBadgeCssClassName", Model.Item.GetRawValue("SaleBadgeDesign"));
178 badgeParms.Add("newBadgeCssClassName", Model.Item.GetRawValue("NewBadgeDesign"));
179 badgeParms.Add("newPublicationDays", Model.Item.GetInt32("NewPublicationDays"));
180 badgeParms.Add("campaignBadgesValues", Model.Item.GetList("CampaignBadges")?.GetRawValue().OfType<string>().ToList());
181
182 bool saleBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("SaleBadgeDesign")) && Model.Item.GetRawValueString("SaleBadgeDesign") != "none" ? true : false;
183 bool newBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("NewBadgeDesign")) && Model.Item.GetRawValueString("NewBadgeDesign") != "none" ? true : false;
184 DateTime createdDate = product.Created.Value;
185 bool showBadges = saleBadgeEnabled && product.Discount.Price != 0 ? true : false;
186 showBadges = (newBadgeEnabled && Model.Item.GetInt32("NewPublicationDays") == 0) || (newBadgeEnabled && (createdDate.AddDays(Model.Item.GetInt32("NewPublicationDays")) > DateTime.Now)) ? true : showBadges;
187 showBadges = !string.IsNullOrEmpty(Model.Item.GetRawValueString("CampaignBadges")) ? true : showBadges;
188
189 @* Get assets from selected categories or get all assets *@
190 if (totalAssets != 0)
191 {
192 int assetNumber = 0;
193 int thumbnailNumber = 0;
194 int modalAssetNumber = 0;
195 string thumbnailAxisCss = GetThumbnailPlacement() == "bottom" ? "flex-column" : string.Empty;
196
197 <div class="d-flex gap-3 h-100 @(thumbnailAxisCss) @(theme) item_@Model.Item.SystemName.ToLower()">
198 <div id="SmallScreenImages_@Model.ID" class="carousel@(GetArrowsColor()) col position-relative" data-bs-ride="carousel">
199 <div class="carousel-inner h-100">
200 @foreach (MediaViewModel asset in assetsList)
201 {
202 var assetValue = asset.Value;
203 foreach (string format in allSupportedFormats)
204 {
205 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0)
206 {
207 string activeSlide = assetNumber == 0 ? "active" : "";
208
209 <div class="carousel-item @activeSlide" data-bs-interval="99999">
210 @{
211 string size = "mobile";
212
213 string imageTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : "";
214
215
216 <div class="h-100 @(imageTheme)">
217 @foreach (string imageFormat in supportedImageFormats)
218 { //Images
219 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0)
220 {
221 if (product is object)
222 {
223 string productName = product.Name;
224 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value;
225 string imageLinkPath = Uri.EscapeDataString(imagePath);
226
227 RatioSettings ratioSettings = GetRatioSettings(size);
228
229 var parms = new Dictionary<string, object>();
230 parms.Add("alt", productName + asset.Keywords);
231 parms.Add("itemprop", "image");
232 parms.Add("columns", Model.GridRowColumnCount);
233 parms.Add("eagerLoadNewImages", Model.Item.GetBoolean("DisableLazyLoading"));
234 parms.Add("doNotUseGetimage", Model.Item.GetBoolean("DisableGetImage"));
235 if (!string.IsNullOrEmpty(asset.DisplayName))
236 {
237 parms.Add("title", asset.DisplayName);
238 }
239
240 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid")
241 {
242 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover");
243 }
244 else
245 {
246 parms.Add("cssClass", "mw-100 mh-100");
247 }
248
249 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID">
250 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide-to="@assetNumber">
251 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms)
252 </div>
253 </a>
254 }
255 }
256 }
257 @foreach (string videoFormat in supportedVideoFormats)
258 { //Videos
259 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0)
260 {
261 if (Model.Item.GetString("OpenVideoInModal") == "true")
262 {
263 if (product is object)
264 {
265 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/";
266
267 string productName = product.Name;
268 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : "";
269 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : "";
270
271 RatioSettings ratioSettings = GetRatioSettings(size);
272
273 string type = GetVideoType(asset.Value);
274
275 string videoScreendumpPath = type == "youtube" ? GetYoutubeScreenDump(asset.Value, "maxresdefault") : string.Empty;
276 videoScreendumpPath = type == "selfhosted" ? System.Uri.EscapeUriString(asset.Value) : videoScreendumpPath;
277 string videoJsClass = type == "vimeo" ? "js-vimeo-video-thumbnail" : "";
278
279
280 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable); cursor: pointer" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID">
281 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide-to="@assetNumber">
282 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div>
283 @if (type != "selfhosted")
284 {
285 <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;">
286 }
287 else
288 {
289 string videoType = Path.GetExtension(asset.Value).ToLower();
290
291 <video preload="auto" class="h-100 w-100" style="object-fit: contain;">
292 <source src="@(videoScreendumpPath)#t=0.001" type="video/@videoType.Replace(".", "")">
293 </video>
294 }
295 </div>
296 </div>
297
298 }
299 }
300 else
301 {
302 if (product is object)
303 {
304 var videoParams = GetVideoParams(asset, size);
305 @RenderPartial("Components/VideoPlayer.cshtml", new FileViewModel { Path = asset.Value }, videoParams);
306
307 }
308 }
309 }
310 }
311 @foreach (string documentFormat in supportedDocumentFormats)
312 { //Documents
313 if (assetValue.IndexOf(documentFormat, StringComparison.OrdinalIgnoreCase) >= 0)
314 {
315 if (product is object)
316 {
317 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/";
318
319 string productName = product.Name;
320 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value;
321 string imageLinkPath = Uri.EscapeDataString(imagePath);
322
323 RatioSettings ratioSettings = GetRatioSettings(size);
324
325 var parms = new Dictionary<string, object>();
326 parms.Add("alt", productName + asset.Keywords);
327 parms.Add("itemprop", "image");
328 parms.Add("fullwidth", true);
329 parms.Add("columns", Model.GridRowColumnCount);
330 if (!string.IsNullOrEmpty(asset.DisplayName))
331 {
332 parms.Add("title", asset.DisplayName);
333 }
334
335 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid")
336 {
337 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover");
338 }
339 else
340 {
341 parms.Add("cssClass", "mw-100 mh-100");
342 }
343
344 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@Translate("Download")">
345 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100">
346 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div>
347 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0)
348 {
349 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms)
350 }
351 </div>
352 </a>
353 }
354
355 }
356 }
357 </div>
358 }
359
360
361 </div>
362 assetNumber++;
363 }
364 }
365 }
366 </div>
367 @if (showBadges)
368 {
369 <div class="position-absolute top-0 left-0 p-2 p-lg-3">
370 @{@RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms)}
371 </div>
372 }
373
374 </div>
375
376 @if (totalAssets > 1)
377 {
378 <div class="@(GetThumbnailRowSettingCss()) gap-3" id="SmallScreenImagesThumbnails_@Model.ID">
379 @foreach (MediaViewModel asset in assetsList)
380 {
381 var assetValue = asset.Value;
382 string assetName = asset.Name;
383 assetName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : "";
384 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : null;
385 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/";
386
387 string imagePath = Dynamicweb.Context.Current.Server.UrlEncode(assetValue);
388 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;
389 string imagePathThumb = assetValue.StartsWith("/Files/", StringComparison.OrdinalIgnoreCase) ? imagePath.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) < 0 && imagePath.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) < 0 ? $"/Admin/Public/GetImage.ashx?image={imagePath}&width=180&format=webp" : imagePath : assetValue;
390
391 RatioSettings ratioSettings = GetRatioSettings("desktop");
392
393 <div class="border outline-none @(ratioSettings.CssClass)" style="@(ratioSettings.CssVariable); cursor: pointer; min-width: 7rem; max-width: 8rem;" data-bs-target="#SmallScreenImages_@Model.ID" data-bs-slide-to="@thumbnailNumber">
394 @foreach (string imageFormat in supportedImageFormats)
395 { //Images
396 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0)
397 {
398 <img src="@imagePathThumb" alt="@assetName" @assetTitle class="p-0 p-lg-1 w-100 h-100" style="object-fit: contain;">
399
400 thumbnailNumber++;
401 }
402 }
403
404 @foreach (string videoFormat in supportedVideoFormats)
405 { //Videos
406 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0)
407 {
408
409 string type = GetVideoType(asset.Value);
410
411 string videoScreendumpPath = type == "youtube" ? GetYoutubeScreenDump(asset.Value, "mqdefault") : "";
412 videoScreendumpPath = type == "vimeo" ? string.Empty : videoScreendumpPath;
413 videoScreendumpPath = type == "selfhosted" ? System.Uri.EscapeUriString(asset.Value) : videoScreendumpPath;
414 string videoJsClass = type == "vimeo" ? "js-vimeo-video-thumbnail" : string.Empty;
415
416 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div>
417
418 if (type != "selfhosted")
419 {
420 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@assetTitle" @assetTitle class="@videoJsClass mw-100 mh-100" data-asset-value="@asset.Value" style="object-fit: cover;" />
421 }
422 else
423 {
424 string videoType = Path.GetExtension(asset.Value).ToLower();
425
426 <video preload="auto" class="h-100 w-100" style="object-fit: contain;">
427 <source src="@(videoScreendumpPath)#t=0.001" type="video/@videoType.Replace(".", "")">
428 </video>
429 }
430
431 thumbnailNumber++;
432 }
433 }
434
435 @foreach (string documentFormat in supportedDocumentFormats)
436 { //Documents
437 if (assetValue.IndexOf(documentFormat, StringComparison.OrdinalIgnoreCase) >= 0)
438 {
439 <a href="@Uri.EscapeDataString(assetValue)" class="ratio ratio-4x3 border outline-none" style="cursor: pointer; min-width: 7rem; max-width: 8rem;" download title="@asset.Value">
440 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0)
441 {
442 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100">
443 <div class="icon-3 position-absolute text-light" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div>
444 </div>
445 <img src="@imagePathThumb" alt="@assetName" @assetTitle class="p-0 p-lg-1 mw-100 mh-100" style="object-fit: cover;">
446 }
447 else
448 {
449 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100">
450 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div>
451 </div>
452 }
453 </a>
454
455 thumbnailNumber++;
456 }
457 }
458 </div>
459 }
460 </div>
461 }
462 </div>
463
464 @* Modal with slides *@
465 <div class="modal fade swift_products-details-images-modal" id="modal_@Model.ID" tabindex="-1" aria-labelledby="productDetailsGalleryModalTitle_@Model.ID" aria-hidden="true">
466 <div class="modal-dialog modal-dialog-centered modal-xl">
467 <div class="modal-content">
468 <div class="modal-header visually-hidden">
469 <h5 class="modal-title" id="productDetailsGalleryModalTitle_@Model.ID">@product.Title</h5>
470 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
471 </div>
472 <div class="modal-body p-2 p-lg-3 h-100">
473 <div id="ModalCarousel_@Model.ID" class="carousel@(GetArrowsColor()) h-100" data-bs-ride="carousel">
474 <div class="carousel-inner h-100 @theme">
475 @foreach (MediaViewModel asset in assetsList)
476 {
477 var assetValue = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value;
478 foreach (string supportedFormat in supportedImageFormats.Concat(supportedVideoFormats).ToArray())
479 {
480 if (assetValue.IndexOf(supportedFormat, StringComparison.OrdinalIgnoreCase) >= 0)
481 {
482 string imagePath = assetValue;
483 string activeSlide = modalAssetNumber == 0 ? "active" : "";
484
485 var parms = new Dictionary<string, object>();
486 parms.Add("cssClass", "d-block mw-100 mh-100 m-auto");
487 parms.Add("fullwidth", true);
488 parms.Add("columns", Model.GridRowColumnCount);
489
490 <div class="carousel-item @activeSlide h-100" data-bs-interval="99999">
491 @foreach (string imageFormat in supportedImageFormats)
492 { //Images
493 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0)
494 {
495 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms)
496 }
497 }
498
499 @foreach (string videoFormat in supportedVideoFormats)
500 { //Videos
501 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0)
502 {
503 var videoParams = GetVideoParams(asset, "modal");
504 @RenderPartial("Components/VideoPlayer.cshtml", new FileViewModel { Path = asset.Value }, videoParams)
505 }
506 }
507 </div>
508 modalAssetNumber++;
509 }
510 }
511 }
512 <button class="carousel-control-prev carousel-control-area" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="prev">
513 <span class="carousel-control-prev-icon" aria-hidden="true"></span>
514 <span class="visually-hidden">@Translate("Previous")</span>
515 </button>
516 <button class="carousel-control-next carousel-control-area" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="next">
517 <span class="carousel-control-next-icon" aria-hidden="true"></span>
518 <span class="visually-hidden">@Translate("Next")</span>
519 </button>
520 </div>
521 </div>
522 </div>
523 </div>
524 </div>
525 </div>
526 }
527 else if (Pageview.IsVisualEditorMode)
528 {
529 RatioSettings ratioSettings = GetRatioSettings("desktop");
530
531 <div class="h-100 @theme">
532 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)">
533 <img src="/Files/Images/missing_image.jpg" loading="lazy" decoding="async" class="mh-100 mw-100" style="object-fit: cover;">
534 </div>
535 </div>
536 }
537 }
538 else if (Pageview.IsVisualEditorMode)
539 {
540 <div class="alert alert-dark m-0">@Translate("No products available")</div>
541 }
542
543
544
545
Kleedrive T3A 803-6 0.75kw 900 om UL/CSA
- 945 o/min 3x230/400 V B3 Aluminium PTC
Opret dig som kunde under "Log ind" for at se dine priser,
eller send os en forespørgsel!
- CN-kode
- 85015100
- Oprindelsesland
- Kina
- Vægt, kg
- 12,5
- Nummer
- 2518000756120140
- Leverandørlager
- 0
- Kan bygges
- 86
Error parsing template "Designs/Swift/Paragraph/Swift_ProductDetailsImage.cshtml"
Line 109: (108:156) - Expected a "{" but found a "@". Block statements must be enclosed in "{" and "}". You cannot use single-statement control-flow statements in CSHTML pages. For example, the following is not allowed:
@if(isLoggedIn)
Hello, @user
Instead, wrap the contents of the block in "{}":
@if(isLoggedIn) {
Hello, @user
}
1 @inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.ParagraphViewModel>
2 @using Dynamicweb.Ecommerce.ProductCatalog
3 @using Dynamicweb.Frontend
4 @using System.IO
5 @using System.Text.RegularExpressions;
6
7 @functions {
8 public ProductViewModel product { get; set; } = new ProductViewModel();
9 public string galleryLayout { get; set; }
10 public string[] supportedImageFormats { get; set; }
11 public string[] supportedVideoFormats { get; set; }
12 public string[] supportedDocumentFormats { get; set; }
13 public string[] allSupportedFormats { get; set; }
14
15 public class RatioSettings
16 {
17 public string Ratio { get; set; }
18 public string CssClass { get; set; }
19 public string CssVariable { get; set; }
20 public string Fill { get; set; }
21 }
22
23 public RatioSettings GetRatioSettings(string size = "desktop")
24 {
25 var ratioSettings = new RatioSettings();
26
27 string ratio = Model.Item.GetRawValueString("ImageAspectRatio", "");
28 ratio = ratio != "0" ? ratio : "";
29 string cssClass = ratio != "" && ratio != "fill" ? " ratio" : "";
30 string cssVariable = ratio != "" && ratio != "fill" ? "--bs-aspect-ratio: " + ratio : "";
31 cssClass = ratio == "fill" && size == "mobile" ? " ratio" : cssClass;
32 cssVariable = ratio == "fill" && size == "mobile" ? "--bs-aspect-ratio: 66%" : cssVariable;
33
34 ratioSettings.Ratio = ratio;
35 ratioSettings.CssClass = cssClass;
36 ratioSettings.CssVariable = cssVariable;
37 ratioSettings.Fill = ratio == "fill" ? " h-100" : "";
38
39 return ratioSettings;
40 }
41
42 public string GetArrowsColor()
43 {
44 var invertColor = Model.Item.GetBoolean("InvertModalArrowsColor");
45 var arrowsColor = invertColor ? " carousel-dark" : string.Empty;
46 return arrowsColor;
47 }
48
49 public string GetThumbnailPlacement()
50 {
51 return Model.Item.GetRawValueString("ThumbnailPlacement", "bottom");
52 }
53
54 public string GetThumbnailRowSettingCss()
55 {
56 switch (GetThumbnailPlacement())
57 {
58 case "bottom":
59 return "d-flex flex-wrap";
60 case "left":
61 return "d-flex flex-column order-first";
62 case "right":
63 return "d-flex flex-column order-last";
64 default:
65 return "d-flex flex-wrap";
66 }
67 }
68
69 public Dictionary<string, object> GetVideoParams(MediaViewModel asset, string size)
70 {
71 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Name;
72 string type = GetVideoType(asset.Value);
73 bool openInModal = Model.Item.GetString("OpenVideoInModal") == "true" ? true : false;
74 bool autoPlay = Model.Item.GetBoolean("VideoAutoPlay");
75
76 var videoParams = new Dictionary<string, object>();
77 videoParams.Add("AssetName", asset.Name);
78 videoParams.Add("AssetVideoType", type);
79 videoParams.Add("AssetDisplayName", asset.DisplayName);
80 videoParams.Add("OpenVideoInModal", openInModal);
81 videoParams.Add("VideoAutoPlay", autoPlay);
82 videoParams.Add("Size", size);
83 videoParams.Add("Id", Model.ID);
84 return videoParams;
85
86 }
87
88 public string GetVideoType(string assetValue)
89 {
90 string type = assetValue.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "youtube" : string.Empty;
91 type = assetValue.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "vimeo" : type;
92 type = string.IsNullOrEmpty(type) ? "selfhosted" : type;
93
94 return type;
95 }
96
97 public string GetYoutubeScreenDump(string assetValue, string quality)
98 {
99 var regex = new Regex(@"(?:youtube\.com\/.*[\?&]v=|youtu\.be\/|youtube\.com\/embed\/)([\w-]+)(?:\?.*)?");
100 Match match = regex.Match(assetValue);
101 string videoId = match.Success ? match.Groups[1].Value : string.Empty;
102 string youtubeThumbnail = $"https://img.youtube.com/vi/{videoId}/{quality}.jpg";
103 return youtubeThumbnail;
104 }
105 }
106
107 @{
108 ProductViewModel product = null;
109 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails") && !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.QueryString.Get("ProductID"))) @*<-Custom code: null check*@
110 {
111 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"];
112 }
113 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode)
114 {
115 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page);
116 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel();
117
118 if (productList?.Products is object)
119 {
120 product = productList.Products[0];
121 }
122 }
123 }
124
125 @if (product is object && product != null)@*<-Custom code: null check*@
126 {
127 @* Supported formats *@
128 supportedImageFormats = new string[] { ".jpg", ".jpeg", ".webp", ".png", ".gif", ".bmp", ".tiff" };
129 supportedVideoFormats = new string[] { "youtu.be", "youtube", "vimeo", ".mp4", ".webm" };
130 supportedDocumentFormats = new string[] { ".pdf", ".docx", ".xlsx", ".ppt", "pptx" };
131 allSupportedFormats = supportedImageFormats.Concat(supportedVideoFormats).Concat(supportedDocumentFormats).ToArray();
132
133 @* Collect the assets *@
134 var selectedAssetCategories = Model.Item.GetList("ImageAssets")?.GetRawValue().OfType<string>();
135 bool includeImagePatternImages = Model.Item.GetBoolean("ImagePatternImages");
136
137 @* Needed image data collection to support both DefaultImage, ImagePatterns and Image Assets *@
138 string defaultImage = product.DefaultImage != null ? product.DefaultImage.Value : "";
139 IEnumerable<MediaViewModel> assetsImages = product.AssetCategories.Where(x => selectedAssetCategories.Contains(x.SystemName)).SelectMany(x => x.Assets);
140 assetsImages = assetsImages.OrderByDescending(x => x.Value.Equals(defaultImage));
141 IEnumerable<MediaViewModel> assetsList = new MediaViewModel[] { };
142 assetsList = assetsList.Union(assetsImages);
143 assetsList = includeImagePatternImages ? assetsList.Union(product.ImagePatternImages) : assetsList;
144 assetsList = includeImagePatternImages && assetsList.Count() == 0 ? assetsList.Append(product.DefaultImage) : assetsList;
145
146 bool defaultImageFallback = Model.Item.GetBoolean("DefaultImageFallback");
147 bool showOnlyPrimaryImage = Model.Item.GetBoolean("ShowOnlyPrimaryImage");
148
149 int totalAssets = 0;
150 if (showOnlyPrimaryImage == false)
151 {
152 foreach (MediaViewModel asset in assetsList)
153 {
154 var assetValue = asset.Value;
155 foreach (string format in allSupportedFormats)
156 {
157 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0)
158 {
159 totalAssets++;
160 }
161 }
162 }
163 }
164
165 if ((totalAssets == 0 && product.DefaultImage != null && selectedAssetCategories.Count() == 0) || (showOnlyPrimaryImage == true && product.DefaultImage != null) || totalAssets == 0 && defaultImageFallback)
166 {
167 assetsList = new List<MediaViewModel>() { product.DefaultImage };
168 totalAssets = 1;
169 }
170
171 @* Theme settings *@
172 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : "";
173
174 var badgeParms = new Dictionary<string, object>();
175 badgeParms.Add("size", "h5");
176 badgeParms.Add("saleBadgeType", Model.Item.GetRawValue("SaleBadgeType"));
177 badgeParms.Add("saleBadgeCssClassName", Model.Item.GetRawValue("SaleBadgeDesign"));
178 badgeParms.Add("newBadgeCssClassName", Model.Item.GetRawValue("NewBadgeDesign"));
179 badgeParms.Add("newPublicationDays", Model.Item.GetInt32("NewPublicationDays"));
180 badgeParms.Add("campaignBadgesValues", Model.Item.GetList("CampaignBadges")?.GetRawValue().OfType<string>().ToList());
181
182 bool saleBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("SaleBadgeDesign")) && Model.Item.GetRawValueString("SaleBadgeDesign") != "none" ? true : false;
183 bool newBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("NewBadgeDesign")) && Model.Item.GetRawValueString("NewBadgeDesign") != "none" ? true : false;
184 DateTime createdDate = product.Created.Value;
185 bool showBadges = saleBadgeEnabled && product.Discount.Price != 0 ? true : false;
186 showBadges = (newBadgeEnabled && Model.Item.GetInt32("NewPublicationDays") == 0) || (newBadgeEnabled && (createdDate.AddDays(Model.Item.GetInt32("NewPublicationDays")) > DateTime.Now)) ? true : showBadges;
187 showBadges = !string.IsNullOrEmpty(Model.Item.GetRawValueString("CampaignBadges")) ? true : showBadges;
188
189 @* Get assets from selected categories or get all assets *@
190 if (totalAssets != 0)
191 {
192 int assetNumber = 0;
193 int thumbnailNumber = 0;
194 int modalAssetNumber = 0;
195 string thumbnailAxisCss = GetThumbnailPlacement() == "bottom" ? "flex-column" : string.Empty;
196
197 <div class="d-flex gap-3 h-100 @(thumbnailAxisCss) @(theme) item_@Model.Item.SystemName.ToLower()">
198 <div id="SmallScreenImages_@Model.ID" class="carousel@(GetArrowsColor()) col position-relative" data-bs-ride="carousel">
199 <div class="carousel-inner h-100">
200 @foreach (MediaViewModel asset in assetsList)
201 {
202 var assetValue = asset.Value;
203 foreach (string format in allSupportedFormats)
204 {
205 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0)
206 {
207 string activeSlide = assetNumber == 0 ? "active" : "";
208
209 <div class="carousel-item @activeSlide" data-bs-interval="99999">
210 @{
211 string size = "mobile";
212
213 string imageTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : "";
214
215
216 <div class="h-100 @(imageTheme)">
217 @foreach (string imageFormat in supportedImageFormats)
218 { //Images
219 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0)
220 {
221 if (product is object)
222 {
223 string productName = product.Name;
224 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value;
225 string imageLinkPath = Uri.EscapeDataString(imagePath);
226
227 RatioSettings ratioSettings = GetRatioSettings(size);
228
229 var parms = new Dictionary<string, object>();
230 parms.Add("alt", productName + asset.Keywords);
231 parms.Add("itemprop", "image");
232 parms.Add("columns", Model.GridRowColumnCount);
233 parms.Add("eagerLoadNewImages", Model.Item.GetBoolean("DisableLazyLoading"));
234 parms.Add("doNotUseGetimage", Model.Item.GetBoolean("DisableGetImage"));
235 if (!string.IsNullOrEmpty(asset.DisplayName))
236 {
237 parms.Add("title", asset.DisplayName);
238 }
239
240 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid")
241 {
242 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover");
243 }
244 else
245 {
246 parms.Add("cssClass", "mw-100 mh-100");
247 }
248
249 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID">
250 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide-to="@assetNumber">
251 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms)
252 </div>
253 </a>
254 }
255 }
256 }
257 @foreach (string videoFormat in supportedVideoFormats)
258 { //Videos
259 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0)
260 {
261 if (Model.Item.GetString("OpenVideoInModal") == "true")
262 {
263 if (product is object)
264 {
265 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/";
266
267 string productName = product.Name;
268 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : "";
269 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : "";
270
271 RatioSettings ratioSettings = GetRatioSettings(size);
272
273 string type = GetVideoType(asset.Value);
274
275 string videoScreendumpPath = type == "youtube" ? GetYoutubeScreenDump(asset.Value, "maxresdefault") : string.Empty;
276 videoScreendumpPath = type == "selfhosted" ? System.Uri.EscapeUriString(asset.Value) : videoScreendumpPath;
277 string videoJsClass = type == "vimeo" ? "js-vimeo-video-thumbnail" : "";
278
279
280 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable); cursor: pointer" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID">
281 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide-to="@assetNumber">
282 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div>
283 @if (type != "selfhosted")
284 {
285 <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;">
286 }
287 else
288 {
289 string videoType = Path.GetExtension(asset.Value).ToLower();
290
291 <video preload="auto" class="h-100 w-100" style="object-fit: contain;">
292 <source src="@(videoScreendumpPath)#t=0.001" type="video/@videoType.Replace(".", "")">
293 </video>
294 }
295 </div>
296 </div>
297
298 }
299 }
300 else
301 {
302 if (product is object)
303 {
304 var videoParams = GetVideoParams(asset, size);
305 @RenderPartial("Components/VideoPlayer.cshtml", new FileViewModel { Path = asset.Value }, videoParams);
306
307 }
308 }
309 }
310 }
311 @foreach (string documentFormat in supportedDocumentFormats)
312 { //Documents
313 if (assetValue.IndexOf(documentFormat, StringComparison.OrdinalIgnoreCase) >= 0)
314 {
315 if (product is object)
316 {
317 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/";
318
319 string productName = product.Name;
320 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value;
321 string imageLinkPath = Uri.EscapeDataString(imagePath);
322
323 RatioSettings ratioSettings = GetRatioSettings(size);
324
325 var parms = new Dictionary<string, object>();
326 parms.Add("alt", productName + asset.Keywords);
327 parms.Add("itemprop", "image");
328 parms.Add("fullwidth", true);
329 parms.Add("columns", Model.GridRowColumnCount);
330 if (!string.IsNullOrEmpty(asset.DisplayName))
331 {
332 parms.Add("title", asset.DisplayName);
333 }
334
335 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid")
336 {
337 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover");
338 }
339 else
340 {
341 parms.Add("cssClass", "mw-100 mh-100");
342 }
343
344 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@Translate("Download")">
345 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100">
346 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div>
347 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0)
348 {
349 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms)
350 }
351 </div>
352 </a>
353 }
354
355 }
356 }
357 </div>
358 }
359
360
361 </div>
362 assetNumber++;
363 }
364 }
365 }
366 </div>
367 @if (showBadges)
368 {
369 <div class="position-absolute top-0 left-0 p-2 p-lg-3">
370 @{@RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms)}
371 </div>
372 }
373
374 </div>
375
376 @if (totalAssets > 1)
377 {
378 <div class="@(GetThumbnailRowSettingCss()) gap-3" id="SmallScreenImagesThumbnails_@Model.ID">
379 @foreach (MediaViewModel asset in assetsList)
380 {
381 var assetValue = asset.Value;
382 string assetName = asset.Name;
383 assetName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : "";
384 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : null;
385 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/";
386
387 string imagePath = Dynamicweb.Context.Current.Server.UrlEncode(assetValue);
388 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;
389 string imagePathThumb = assetValue.StartsWith("/Files/", StringComparison.OrdinalIgnoreCase) ? imagePath.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) < 0 && imagePath.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) < 0 ? $"/Admin/Public/GetImage.ashx?image={imagePath}&width=180&format=webp" : imagePath : assetValue;
390
391 RatioSettings ratioSettings = GetRatioSettings("desktop");
392
393 <div class="border outline-none @(ratioSettings.CssClass)" style="@(ratioSettings.CssVariable); cursor: pointer; min-width: 7rem; max-width: 8rem;" data-bs-target="#SmallScreenImages_@Model.ID" data-bs-slide-to="@thumbnailNumber">
394 @foreach (string imageFormat in supportedImageFormats)
395 { //Images
396 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0)
397 {
398 <img src="@imagePathThumb" alt="@assetName" @assetTitle class="p-0 p-lg-1 w-100 h-100" style="object-fit: contain;">
399
400 thumbnailNumber++;
401 }
402 }
403
404 @foreach (string videoFormat in supportedVideoFormats)
405 { //Videos
406 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0)
407 {
408
409 string type = GetVideoType(asset.Value);
410
411 string videoScreendumpPath = type == "youtube" ? GetYoutubeScreenDump(asset.Value, "mqdefault") : "";
412 videoScreendumpPath = type == "vimeo" ? string.Empty : videoScreendumpPath;
413 videoScreendumpPath = type == "selfhosted" ? System.Uri.EscapeUriString(asset.Value) : videoScreendumpPath;
414 string videoJsClass = type == "vimeo" ? "js-vimeo-video-thumbnail" : string.Empty;
415
416 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div>
417
418 if (type != "selfhosted")
419 {
420 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@assetTitle" @assetTitle class="@videoJsClass mw-100 mh-100" data-asset-value="@asset.Value" style="object-fit: cover;" />
421 }
422 else
423 {
424 string videoType = Path.GetExtension(asset.Value).ToLower();
425
426 <video preload="auto" class="h-100 w-100" style="object-fit: contain;">
427 <source src="@(videoScreendumpPath)#t=0.001" type="video/@videoType.Replace(".", "")">
428 </video>
429 }
430
431 thumbnailNumber++;
432 }
433 }
434
435 @foreach (string documentFormat in supportedDocumentFormats)
436 { //Documents
437 if (assetValue.IndexOf(documentFormat, StringComparison.OrdinalIgnoreCase) >= 0)
438 {
439 <a href="@Uri.EscapeDataString(assetValue)" class="ratio ratio-4x3 border outline-none" style="cursor: pointer; min-width: 7rem; max-width: 8rem;" download title="@asset.Value">
440 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0)
441 {
442 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100">
443 <div class="icon-3 position-absolute text-light" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div>
444 </div>
445 <img src="@imagePathThumb" alt="@assetName" @assetTitle class="p-0 p-lg-1 mw-100 mh-100" style="object-fit: cover;">
446 }
447 else
448 {
449 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100">
450 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div>
451 </div>
452 }
453 </a>
454
455 thumbnailNumber++;
456 }
457 }
458 </div>
459 }
460 </div>
461 }
462 </div>
463
464 @* Modal with slides *@
465 <div class="modal fade swift_products-details-images-modal" id="modal_@Model.ID" tabindex="-1" aria-labelledby="productDetailsGalleryModalTitle_@Model.ID" aria-hidden="true">
466 <div class="modal-dialog modal-dialog-centered modal-xl">
467 <div class="modal-content">
468 <div class="modal-header visually-hidden">
469 <h5 class="modal-title" id="productDetailsGalleryModalTitle_@Model.ID">@product.Title</h5>
470 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
471 </div>
472 <div class="modal-body p-2 p-lg-3 h-100">
473 <div id="ModalCarousel_@Model.ID" class="carousel@(GetArrowsColor()) h-100" data-bs-ride="carousel">
474 <div class="carousel-inner h-100 @theme">
475 @foreach (MediaViewModel asset in assetsList)
476 {
477 var assetValue = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value;
478 foreach (string supportedFormat in supportedImageFormats.Concat(supportedVideoFormats).ToArray())
479 {
480 if (assetValue.IndexOf(supportedFormat, StringComparison.OrdinalIgnoreCase) >= 0)
481 {
482 string imagePath = assetValue;
483 string activeSlide = modalAssetNumber == 0 ? "active" : "";
484
485 var parms = new Dictionary<string, object>();
486 parms.Add("cssClass", "d-block mw-100 mh-100 m-auto");
487 parms.Add("fullwidth", true);
488 parms.Add("columns", Model.GridRowColumnCount);
489
490 <div class="carousel-item @activeSlide h-100" data-bs-interval="99999">
491 @foreach (string imageFormat in supportedImageFormats)
492 { //Images
493 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0)
494 {
495 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms)
496 }
497 }
498
499 @foreach (string videoFormat in supportedVideoFormats)
500 { //Videos
501 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0)
502 {
503 var videoParams = GetVideoParams(asset, "modal");
504 @RenderPartial("Components/VideoPlayer.cshtml", new FileViewModel { Path = asset.Value }, videoParams)
505 }
506 }
507 </div>
508 modalAssetNumber++;
509 }
510 }
511 }
512 <button class="carousel-control-prev carousel-control-area" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="prev">
513 <span class="carousel-control-prev-icon" aria-hidden="true"></span>
514 <span class="visually-hidden">@Translate("Previous")</span>
515 </button>
516 <button class="carousel-control-next carousel-control-area" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="next">
517 <span class="carousel-control-next-icon" aria-hidden="true"></span>
518 <span class="visually-hidden">@Translate("Next")</span>
519 </button>
520 </div>
521 </div>
522 </div>
523 </div>
524 </div>
525 </div>
526 }
527 else if (Pageview.IsVisualEditorMode)
528 {
529 RatioSettings ratioSettings = GetRatioSettings("desktop");
530
531 <div class="h-100 @theme">
532 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)">
533 <img src="/Files/Images/missing_image.jpg" loading="lazy" decoding="async" class="mh-100 mw-100" style="object-fit: cover;">
534 </div>
535 </div>
536 }
537 }
538 else if (Pageview.IsVisualEditorMode)
539 {
540 <div class="alert alert-dark m-0">@Translate("No products available")</div>
541 }
542
543
544
545
Forespørg på dokumenter og tegninger
Produkttegninger, dokumenter og brochurer af enhver slags kan indeholde fejl for hvilke Brd. Klee ikke er ansvarlig.
Hos Brd. Klee A/S er vi ISO 9001:2015 certificeret
Siden 1994 har Brd. Klee været ISO certificeret efter 9001:2015 standarden hos Norske Veritas.
Derfor skal du vælge os
- Over 500.000 produkter
- Datablade, målskitser og 3D
- Vores ansatte er specialister
- Over 75 år i branchen
Vi hjælper dig 24/7
Du har mulighed for at bestille og få varer leveret 24 timer i døgnet – året rundt.
Uden for normal åbningstid koster betjening et gebyr.
Ring på tlf. 43 86 83 33