Error executing template "/Designs/Swift/Paragraph/Swift_ProductDetailsMediaTable_Custom.cshtml"
System.ArgumentNullException: Value cannot be null.
Parameter name: source
at System.Linq.Enumerable.Where[TSource](IEnumerable`1 source, Func`2 predicate)
at CompiledRazorTemplates.Dynamic.RazorEngine_c68dff46498642219b7ecec5dd8576cd.Execute() in E:\Solutions\Cenger\Files\Templates\Designs\Swift\Paragraph\Swift_ProductDetailsMediaTable_Custom.cshtml:line 58
at RazorEngine.Templating.TemplateBase.RazorEngine.Templating.ITemplate.Run(ExecuteContext context, TextWriter reader)
at RazorEngine.Templating.RazorEngineService.RunCompile(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass16_0.<RunCompile>b__0(TextWriter writer)
at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter)
at Dynamicweb.Rendering.RazorTemplateRenderingProvider.Render(Template template)
at Dynamicweb.Rendering.TemplateRenderingService.Render(Template template)
at Dynamicweb.Rendering.Template.RenderRazorTemplate()
1 @inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.ParagraphViewModel>
2 @using Dynamicweb.Ecommerce.ProductCatalog
3 @using Dynamicweb.Frontend
4 @using System.IO
5
6 @functions {
7 public ProductViewModel product { get; set; } = new ProductViewModel();
8 public string[] supportedImageFormats { get; set; }
9 public string[] supportedVideoFormats { get; set; }
10 public string[] supportedDocumentFormats { get; set; }
11 public string[] allSupportedFormats { get; set; }
12
13 public class RatioSettings
14 {
15 public string Ratio { get; set; }
16 public string CssClass { get; set; }
17 public string CssVariable { get; set; }
18 public string Fill { get; set; }
19 }
20
21 public RatioSettings GetRatioSettings()
22 {
23 var ratioSettings = new RatioSettings();
24
25 string ratio = Model.Item.GetRawValueString("ImageAspectRatio", "");
26 ratio = ratio != "0" ? ratio : "";
27 string cssClass = ratio != "" && ratio != "fill" ? " ratio" : "";
28 string cssVariable = ratio != "" && ratio != "fill" ? "--bs-aspect-ratio: " + ratio : "";
29
30 ratioSettings.Ratio = ratio;
31 ratioSettings.CssClass = cssClass;
32 ratioSettings.CssVariable = cssVariable;
33 ratioSettings.Fill = ratio == "fill" ? " h-100" : "";
34
35 return ratioSettings;
36 }
37 }
38
39 @{
40 @* Get the product data *@
41 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails"))
42 {
43 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"];
44 }
45
46 @* Supported formats *@
47 supportedImageFormats = new string[] { ".jpg", ".jpeg", ".webp", ".png", ".gif", ".bmp", ".tiff" };
48 supportedVideoFormats = new string[] { "youtu.be", "youtube", "vimeo", ".mp4", ".webm" };
49 supportedDocumentFormats = new string[] { ".pdf", ".docx", ".xlsx", ".ppt", "pptx" };
50 allSupportedFormats = supportedImageFormats.Concat(supportedVideoFormats).Concat(supportedDocumentFormats).ToArray();
51
52 @* Collect the assets *@
53 var selectedAssetCategories = Model.Item.GetRawValueString("ImageAssets").Split(',').ToList();
54 bool includeImagePatternImages = Model.Item.GetBoolean("ImagePatternImages");
55
56 @* Needed image data collection to support both DefaultImage, ImagePatterns and Image Assets *@
57 string defaultImage = product.DefaultImage != null ? product.DefaultImage.Value : "";
58 IEnumerable<MediaViewModel> assetsImages = product.AssetCategories.Where(x => selectedAssetCategories.Contains(x.SystemName)).SelectMany(x => x.Assets);
59 assetsImages = assetsImages.OrderByDescending(x => x.Value.Equals(defaultImage));
60 IEnumerable<MediaViewModel> assetsList = new MediaViewModel[] { };
61
62 assetsList = assetsList.Union(assetsImages);
63 assetsList = includeImagePatternImages ? assetsList.Union(product.ImagePatternImages) : assetsList;
64 assetsList = includeImagePatternImages && assetsList.Count() == 0 ? assetsList.Append(product.DefaultImage) : assetsList;
65
66 bool defaultImageFallback = Model.Item.GetBoolean("DefaultImageFallback");
67 bool showOnlyPrimaryImage = Model.Item.GetBoolean("ShowOnlyPrimaryImage");
68
69 bool hasVismaDocumentsAssets = selectedAssetCategories.Contains("VismaDocuments");
70
71 int totalAssets = 0;
72 if (showOnlyPrimaryImage == false)
73 {
74 foreach (MediaViewModel asset in assetsList)
75 {
76 var assetValue = asset.Value.ToLower();
77 foreach (string format in allSupportedFormats)
78 {
79 if (assetValue.Contains(format))
80 {
81 totalAssets++;
82 }
83 }
84 }
85 }
86
87 if ((totalAssets == 0 && product.DefaultImage != null && selectedAssetCategories.Count() == 0) || (showOnlyPrimaryImage == true && product.DefaultImage != null))
88 {
89 assetsList = new List<MediaViewModel>() { product.DefaultImage };
90 totalAssets = 1;
91 }
92
93 int videoNumber = 0;
94
95 @* Layout settings *@
96 string spacing = Model.Item.GetRawValueString("Spacing", "p-0");
97 spacing = spacing == "none" ? "p-0" : spacing;
98 spacing = spacing == "small" ? "p-3" : spacing;
99 spacing = spacing == "large" ? "p-5" : spacing;
100
101 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : "";
102
103 bool hideThumbnails = Model.Item.GetBoolean("HideThumbnails");
104
105 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/";
106 }
107
108 @* Get assets from selected categories or get all assets *@
109
110
111 <div class="@spacing@(theme) item_@Model.Item.SystemName.ToLower()">
112 @if (totalAssets != 0 && assetsList.Count() != 0)
113 {
114 if (!string.IsNullOrEmpty(Model.Item.GetString("Title")) && !Model.Item.GetBoolean("HideTitle"))
115 {
116 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "h3");
117
118 <h3 class="@titleFontSize mb-3">
119 @Model.Item.GetString("Title")
120 </h3>
121 }
122
123 <div class="table-responsive">
124 <table class="table table-hover align-middle mb-0" style="table-layout: fixed;">
125 @if (!hasVismaDocumentsAssets)
126 {
127 <thead>
128 <tr>
129
130 @if (!hideThumbnails)
131 {
132 <th style="width:60px"> </th>
133 }
134 <th>@Translate("Name")</th>
135 <th class="text-end d-none d-lg-table-cell">@Translate("Download")</th>
136 <th class="text-end" style="width:100px">@Translate("File type")</th>
137
138 </tr>
139 </thead>
140 }
141 <tbody class="border-top-0">
142 @foreach (MediaViewModel asset in assetsList)
143 {
144 var assetValue = asset.Value.ToLower();
145 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Value.Substring(asset.Value.LastIndexOf('/') + 1);
146
147 bool isVideo = false;
148 bool isVismaDocument = false;
149 foreach (string format in supportedVideoFormats)
150 { //Videos
151 if (assetValue.Contains(format))
152 {
153 isVideo = true;
154 }
155 }
156
157 if (assetValue.Contains("getdocument=true"))
158 {
159 isVismaDocument = true;
160 }
161 if (isVismaDocument)
162 {
163
164 <tr class="position-relative">
165 <td class="@(theme) px-0" style="width:60px">
166 <a href="@asset.Value.Replace("https://cenger.dk","")" class="text-decoration-none text-break" download title="@assetName">
167 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100">
168 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div>
169 </div>
170 </a>
171 </td>
172
173 <td>
174 <a href="@asset.Value.Replace("https://cenger.dk","")" class="text-decoration-none text-break" download title="@assetName">
175 @assetName
176 </a>
177 </td>
178 </tr>
179
180 }
181
182 else if (!isVideo)
183 {
184 string filePath = Dynamicweb.Context.Current.Server.MapPath(assetValue);
185 long fileSize = 0;
186
187 if (File.Exists(filePath))
188 {
189 fileSize = new System.IO.FileInfo(filePath) != null ? new System.IO.FileInfo(filePath).Length / 1024 : 0;
190
191 foreach (string format in allSupportedFormats)
192 {
193 if (assetValue.Contains(format))
194 {
195 <tr class="position-relative">
196 @if (!hideThumbnails)
197 {
198 @RenderAsset(asset)
199 }
200 <td>
201 <a href="@assetValue" class="text-decoration-none text-break" download title="@assetName">
202 @assetName
203 </a>
204 </td>
205 <td class="text-end d-none d-lg-table-cell">
206 <a href="@assetValue" class="text-decoration-none stretched-link" download title="@assetName">
207 @fileSize KB <div class="icon-2" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div>
208 </a>
209 </td>
210 <td class="text-end">@format</td>
211 </tr>
212 }
213 }
214 }
215 }
216 else
217 {
218 string videoType = asset.Value.Contains("youtu.be") || asset.Value.Contains("youtube") ? "Youtube" : "";
219 videoType = asset.Value.Contains("vimeo") ? "Vimeo" : videoType;
220
221 <tr data-bs-toggle="modal" data-bs-target="#modal_@(Model.ID)_@videoNumber" style="cursor: pointer">
222 @if (!hideThumbnails)
223 {
224 @RenderAsset(asset)
225 }
226 <td>
227 @assetName
228 </td>
229 <td class="d-none d-lg-table-cell"> </td>
230 <td align="right">@videoType</td>
231 </tr>
232
233 videoNumber++;
234 }
235 }
236 </tbody>
237 </table>
238 </div>
239
240 int modalVideoNumber = 0;
241 foreach (MediaViewModel asset in assetsList)
242 {
243 var assetName = asset.Value.ToLower();
244
245 foreach (string format in supportedVideoFormats)
246 { //Videos
247 if (assetName.Contains(format))
248 {
249 <div class="modal fade js-video-modal" id="modal_@(Model.ID)_@modalVideoNumber" tabindex="-1" aria-labelledby="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber" aria-hidden="true">
250 <div class="modal-dialog modal-dialog-centered modal-xl">
251 <div class="modal-content">
252 <div class="modal-header visually-hidden">
253 <h5 class="modal-title" id="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber">@product.Title</h5>
254 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
255 </div>
256 <div class="modal-body p-2 p-lg-3 h-100">
257 @RenderVideoPlayer(asset)
258 </div>
259 </div>
260 </div>
261 </div>
262
263 modalVideoNumber++;
264 }
265 }
266 }
267 }
268 else if (Pageview.IsVisualEditorMode)
269 {
270 RatioSettings ratioSettings = GetRatioSettings();
271
272 <div class="h-100 @theme">
273 <div class="alert alert-dark m-0">
274 @Translate("No assets are available")
275 </div>
276 </div>
277 }
278
279 </div>
280
281 @helper RenderAsset(MediaViewModel asset)
282 {
283 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : "";
284 string assetValue = asset.Value;
285
286 <td class="@(theme) px-0">
287 @foreach (string format in supportedImageFormats)
288 { //Images
289 if (assetValue.Contains(format))
290 {
291 @RenderImage(asset)
292 }
293 }
294 @foreach (string format in supportedVideoFormats)
295 { //Videos
296 if (assetValue.Contains(format))
297 {
298 @RenderVideoScreendump(asset)
299 }
300 }
301 @foreach (string format in supportedDocumentFormats)
302 { //Documents
303 if (assetValue.Contains(format))
304 {
305 @RenderDocument(asset)
306 }
307 }
308 </td>
309 }
310
311 @helper RenderImage(MediaViewModel asset)
312 {
313 string productName = product.Name;
314 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : "";
315 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value;
316 string imageLinkPath = imagePath;
317 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp";
318
319 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : "";
320
321 RatioSettings ratioSettings = GetRatioSettings();
322
323 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download>
324 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100">
325 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle itemprop="image">
326 </div>
327 </a>
328 }
329
330 @helper RenderVideoScreendump(MediaViewModel asset)
331 {
332 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/";
333
334 string videoScreendumpPath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : "";
335 string videoId = videoScreendumpPath.Substring(videoScreendumpPath.LastIndexOf('/') + 1);
336 videoScreendumpPath = videoScreendumpPath.Contains("youtu.be") || videoScreendumpPath.Contains("youtube") ? "https://img.youtube.com/vi/" + videoId + "/maxresdefault.jpg" : videoScreendumpPath;
337
338 string vimeoJsClass = videoScreendumpPath.Contains("vimeo") ? "js-vimeo-video-thumbnail" : "";
339 videoScreendumpPath = videoScreendumpPath.Contains("vimeo") ? "" : videoScreendumpPath;
340
341 string productName = product.Name;
342 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : "";
343 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : "";
344
345 RatioSettings ratioSettings = GetRatioSettings();
346
347 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)">
348 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100">
349 <div class="icon-2 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div>
350 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@productName" @assetTitle class="@vimeoJsClass mw-100 mh-100" data-video-id="@videoId" style="object-fit: cover;">
351 </div>
352 </div>
353 }
354
355 @helper RenderDocument(MediaViewModel asset)
356 {
357 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/";
358 string productName = product.Name;
359 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : "";
360 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value;
361 string imageLinkPath = imagePath;
362 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp";
363 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : "";
364
365 RatioSettings ratioSettings = GetRatioSettings();
366
367 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@productName">
368 @if (asset.Value.Contains(".pdf"))
369 {
370 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100">
371 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle />
372 </div>
373 }
374 else
375 {
376 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100">
377 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div>
378 </div>
379 }
380 </a>
381 }
382
383 @helper RenderVideoPlayer(MediaViewModel asset)
384 {
385 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Name;
386 string assetValue = asset.Value;
387 string videoId = asset.Value.Substring(asset.Value.LastIndexOf('/') + 1);
388 string type = assetValue.Contains("youtu.be") || assetValue.Contains("youtube") ? "youtube" : "";
389 type = assetValue.Contains("vimeo") ? "vimeo" : type;
390 type = assetValue.Contains(".mp4") || assetValue.Contains(".webm") ? "selfhosted" : type;
391
392 <div class="h-100" itemscope itemtype="https://schema.org/VideoObject">
393 <span class="visually-hidden" itemprop="name">@assetName</span>
394 <span class="visually-hidden" itemprop="contentUrl">@asset.Value</span>
395 <span class="visually-hidden" itemprop="thumbnailUrl">@asset.Value</span>
396 @if (type != "selfhosted")
397 {
398 <div id="player_@(Pageview.CurrentParagraph.ID)_@(videoId)"
399 class="plyr__video-embed"
400 data-plyr-provider="@(type)"
401 data-plyr-embed-id="@videoId"
402 style="--plyr-color-main: var(--swift-foreground-color); height: 100%">
403 </div>
404
405 <script type="module" src="~/Files/Templates/Designs/Swift/Assets/js/plyr.js"></script>
406 <script type="module">
407 var player = new Plyr('#player_@(Pageview.CurrentParagraph.ID)_@(videoId)', {
408 type: 'video',
409 youtube: {
410 noCookie: true,
411 showinfo: 0
412 },
413 fullscreen: {
414 enabled: true,
415 iosNative: true,
416 }
417 });
418
419 document.querySelectorAll('.js-video-modal').forEach(function (modal) {
420 modal.addEventListener('hidden.bs.modal', function (event) {
421 player.media.pause();
422 })
423 });
424 </script>
425 }
426 else
427 {
428 string videoType = Path.GetExtension(assetValue).ToLower();
429
430 <video preload="auto" class="h-100 w-100" style="object-fit: cover;">
431 <source src="@assetValue" type="video/@videoType.Replace(".", "")">
432 </video>
433 }
434 </div>
435 }
436