]> Git Repo - pico-vscode.git/blob - web/nav.js
Merge branch 'main' into main
[pico-vscode.git] / web / nav.js
1 "use strict";
2
3 const SELECTED_ITEM_BG_CLASS = 'bg-slate-500';
4 const SELECTED_ITEM_BG_OPACITY_CLASS = 'bg-opacity-50';
5 const SELECTED_ITEM_BG_CLASS_DARK = 'dark:bg-slate-600';
6 var isExampleSelected = false;
7 var clickOutsideSuggestionsListenerAdded = false;
8
9 function navItemOnClick(itemId) {
10   // needed so a element isn't hidden behind the navbar on scroll
11   const navbarOffsetHeight = document.getElementById('top-navbar').offsetHeight;
12
13   // remove the SELECTED_ITEM_BG_CLASS class from all nav items
14   const navItems = document.getElementsByClassName('nav-item');
15   const ovNavItems = document.getElementsByClassName("overlay-item");
16   [...navItems, ...ovNavItems].forEach(element => {
17     element.classList.remove(SELECTED_ITEM_BG_CLASS);
18     element.classList.remove(SELECTED_ITEM_BG_OPACITY_CLASS);
19     element.classList.remove(SELECTED_ITEM_BG_CLASS_DARK);
20   });
21
22   const item = document.getElementById(itemId);
23   item.classList.add(SELECTED_ITEM_BG_CLASS);
24   item.classList.add(SELECTED_ITEM_BG_OPACITY_CLASS);
25   item.classList.add(SELECTED_ITEM_BG_CLASS_DARK);
26   const otherItemsId = itemId.includes('ov-') ? itemId.replace('ov-', '') : 'ov-' + itemId;
27   const otherItem = document.getElementById(otherItemsId);
28   otherItem.classList.add(SELECTED_ITEM_BG_CLASS);
29   otherItem.classList.add(SELECTED_ITEM_BG_OPACITY_CLASS);
30   otherItem.classList.add(SELECTED_ITEM_BG_CLASS_DARK);
31
32   switch (itemId) {
33     case "ov-nav-basic":
34     case "nav-basic":
35       // navigate to top
36       document.body.scrollTop = 0;
37       document.documentElement.scrollTop = 0;
38       break;
39
40     case "ov-nav-features":
41     case "nav-features":
42       //document.getElementById("section-features").scrollIntoView();
43       window.scrollTo({
44         top: document.getElementById("section-features").offsetTop - navbarOffsetHeight,
45         behavior: 'smooth'
46       });
47       break;
48
49     case "ov-nav-stdio":
50     case "nav-stdio":
51       //document.getElementById("section-stdio").scrollIntoView();
52       window.scrollTo({
53         top: document.getElementById("section-stdio").offsetTop - navbarOffsetHeight,
54         behavior: 'smooth'
55       });
56       break;
57
58     case "ov-nav-pico-wireless":
59     case "nav-pico-wireless":
60       // document.getElementById("section-pico-wireless").scrollIntoView();
61       window.scrollTo({
62         top: document.getElementById("section-pico-wireless").offsetTop - navbarOffsetHeight,
63         behavior: 'smooth'
64       });
65       break;
66
67     case "ov-nav-code-gen":
68     case "nav-code-gen":
69       // document.getElementById("section-code-gen").scrollIntoView();
70       window.scrollTo({
71         top: document.getElementById("section-code-gen").offsetTop - navbarOffsetHeight,
72         behavior: 'smooth'
73       });
74       break;
75
76     case "ov-nav-debugger":
77     case "nav-debugger":
78       // document.getElementById("section-debugger").scrollIntoView();
79       window.scrollTo({
80         top: document.getElementById("section-debugger").offsetTop - navbarOffsetHeight,
81         behavior: 'smooth'
82       });
83       break;
84     default:
85       break;
86   }
87 }
88
89 window.hideCustomInputs = function (divs, disable) {
90   divs.forEach(div => {
91     //const inputAndSelects = div.querySelectorAll('input, select');
92     /*inputAndSelects.forEach(inputOrSelect => {
93       inputOrSelect.disabled = disable;
94     });*/
95     if (disable) {
96       div.classList.add('hidden');
97     } else {
98       div.classList.remove('hidden');
99     }
100   });
101 };
102
103 window.toggleCreateFromExampleMode = function (forceOn, forceOff) {
104   const createFromExampleBtn = document.getElementById('btn-create-from-example');
105   const projectNameInput = document.getElementById('inp-project-name');
106   var isExampleMode = createFromExampleBtn ? createFromExampleBtn.getAttribute('data-example-mode') === 'true' : true;
107   const projectOptionsDivs = document.querySelectorAll('.project-options');
108   const examplesList = document.getElementById('examples-list');
109   const projectNameGrid = document.getElementById('project-name-grid');
110   const projectNameDropdownButton = document.getElementById('project-name-dropdown-button');
111   const defaultBoardTypeOption = document.getElementById('sel-default');
112
113   if (isExampleMode && (forceOn === undefined || !forceOn) && (forceOff === undefined || forceOff)) {
114     // clear input to avoid crashing the webview
115     projectNameInput.value = '';
116
117     if (createFromExampleBtn) {
118       createFromExampleBtn.setAttribute('data-example-mode', 'false');
119       createFromExampleBtn.innerText = 'Example';
120       // add md:grid-cols-2 from projectNameGrid
121       projectNameGrid.classList.add('md:grid-cols-2');
122       // hide dropdown button
123       projectNameDropdownButton.classList.add('hidden');
124       // crashes the webview
125       //projectNameInput.required = true;
126
127       // crashes the webview
128       /*if (window.removeExampleItems) {
129         window.removeExampleItems();
130       }*/
131     }
132
133     if (defaultBoardTypeOption) {
134       defaultBoardTypeOption.hidden = true;
135       defaultBoardTypeOption.disabled = true;
136
137       // if selected switch selection to first not hidden option
138       if (defaultBoardTypeOption.selected) {
139         const boardTypeSelector = document.getElementById('sel-board-type');
140
141         if (boardTypeSelector) {
142           // select first not hidden option
143           for (let i = 0; i < boardTypeSelector.options.length; i++) {
144             const option = boardTypeSelector.options[i];
145
146             // Check if the option is not hidden
147             if (option.style.display !== 'none' && option.hidden === false) {
148               boardTypeSelector.selectedIndex = i;
149               break;
150             }
151           }
152         }
153       }
154     }
155
156     if (projectNameInput) {
157       // old datalist approach: projectNameInput.setAttribute('list', undefined);
158       // remove keyup event listener from projectNameInput if it exists
159       if (window.projectNameInputOnKeyup) {
160         projectNameInput.removeEventListener('keyup', window.projectNameInputOnKeyup);
161       }
162       projectNameInput.setAttribute('placeholder', 'Project name');
163     }
164
165     if (projectOptionsDivs) {
166       hideCustomInputs(projectOptionsDivs, false);
167     }
168   } else if (forceOff === undefined || !forceOff) {
169     if (createFromExampleBtn) {
170       createFromExampleBtn.setAttribute('data-example-mode', 'true');
171       createFromExampleBtn.innerText = 'Custom';
172       // remove md:grid-cols-2 from projectNameGrid
173       projectNameGrid.classList.remove('md:grid-cols-2');
174       // show dropdown button
175       projectNameDropdownButton.classList.remove('hidden');
176
177       // crashes the webview
178       // projectName required has issues with the suggestions
179       //projectNameInput.required = false;
180     }
181
182     if (projectNameInput && examplesList && typeof examples !== 'undefined') {
183       // clear input to avoid crashing the webview
184       projectNameInput.value = '';
185
186       //projectNameInput.setAttribute('list', "examples-list");
187       projectNameInput.setAttribute('placeholder', 'Select an example');
188
189       if (defaultBoardTypeOption) {
190         defaultBoardTypeOption.hidden = false;
191         defaultBoardTypeOption.disabled = false;
192         defaultBoardTypeOption.selected = true;
193       }
194
195       window.removeExampleItems = window.removeExampleItems || function () {
196         if (window.selectedSuggestion) {
197           window.selectedSuggestion.classList.remove('hovered');
198           window.selectedSuggestion.classList.remove('unhovered');
199           window.selectedSuggestion = null;
200         }
201         if (examplesList !== null) {
202           projectNameInput.removeEventListener('keydown', window.suggestionsOnKeydownNav);
203           projectNameDropdownButton.removeEventListener('keydown', window.suggestionsOnKeyupNav);
204           projectNameInput.classList.replace('rounded-t-lg', 'rounded-lg');
205           projectNameDropdownButton.classList.replace("rounded-tr-lg", "rounded-r-lg");
206           examplesList.classList.remove('border');
207
208           // clear ul
209           examplesList.innerHTML = '';
210         }
211       };
212
213       window.examplesListSelect = window.examplesListSelect || function (exampleName) {
214         projectNameInput.value = exampleName;
215         // Create and dispatch an input event, for the input event listener to be triggered
216         projectNameInput.dispatchEvent(new Event('input', {
217           bubbles: true,
218           cancelable: true,
219         }));
220         removeExampleItems();
221       };
222
223       window.removeClickOutsideSuggestionsListener = window.removeClickOutsideSuggestionsListener || function () {
224         document.body.removeEventListener('click', handleOutsideSuggestionsClick);
225         clickOutsideSuggestionsListenerAdded = false;
226       }
227
228       window.handleOutsideSuggestionsClick = window.handleOutsideSuggestionsClick || function (event) {
229         // check if the clicked element is not inside the examplesList
230         if (!examplesList.contains(event.target) && event.target !== projectNameDropdownButton && event.target !== projectNameInput) {
231           // click occurred outside the suggestions "popup" so remove the suggestions
232           removeExampleItems();
233           removeClickOutsideSuggestionsListener();
234         }
235       };
236
237       window.suggestionsOnKeydownNav = window.suggestionsOnKeydownNav || function (event, arg2) {
238         if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
239           if (arg2) {
240             return true;
241           }
242           if (examplesList.childNodes.length === 0) {
243             return;
244           }
245           const selected = window.selectedSuggestion;
246           const suggestions = document.querySelectorAll('.examples-list-suggestion');
247           if (suggestions.length === 0) {
248             return;
249           }
250           event.preventDefault();
251           event.stopPropagation();
252           const index = selected ? Array.from(suggestions).indexOf(selected) : -1;
253
254           if (selected) {
255             selected.classList.remove('hovered');
256             selected.classList.add('unhovered');
257           }
258
259           if (event.key === 'ArrowDown') {
260             if (index === suggestions.length - 1) {
261               suggestions[0].classList.add('hovered');
262               suggestions[0].classList.remove('unhovered');
263               window.selectedSuggestion = suggestions[0];
264             } else {
265               suggestions[index + 1].classList.add('hovered');
266               suggestions[index + 1].classList.remove('unhovered');
267               window.selectedSuggestion = suggestions[index + 1];
268             }
269           } else {
270             if (index <= 0) {
271               suggestions[suggestions.length - 1].classList.add('hovered');
272               suggestions[suggestions.length - 1].classList.remove('unhovered');
273               window.selectedSuggestion = suggestions[suggestions.length - 1];
274             } else {
275               suggestions[index - 1].classList.add('hovered');
276               suggestions[index - 1].classList.remove('unhovered');
277               window.selectedSuggestion = suggestions[index - 1];
278             }
279           }
280           window.isElementInView = window.isElementInView || ((el, container) => {
281             const containerRect = container.getBoundingClientRect();
282             const elementRect = el.getBoundingClientRect();
283
284             // Check if the element is outside of the visible container bounds
285             const isAbove = elementRect.top < containerRect.top;
286             const isBelow = elementRect.bottom > containerRect.bottom;
287
288             return !isAbove && !isBelow;
289           });
290           // if not in view then set window.ignoreNextMouseOver to true
291           if (!isElementInView(window.selectedSuggestion, examplesList)) {
292             window.ignoreNextMouseOver = true;
293
294             // Scroll the selected suggestion into view
295             window.selectedSuggestion.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' });
296           }
297         } else if (event.key === 'Enter') {
298           if (arg2) {
299             return true;
300           }
301           if (examplesList.childNodes.length === 0) {
302             return;
303           }
304           event.preventDefault();
305           event.stopPropagation();
306           if (window.selectedSuggestion) {
307             window.selectedSuggestion.click();
308           } else {
309             // check if one example is exactly the same as the input value and select it
310             const suggestions = document.querySelectorAll('.examples-list-suggestion');
311             const equalElement = Array.from(suggestions).find(suggestion => {
312               const innerHTML = suggestion.innerHTML.trim(); // Get innerHTML and trim whitespace
313               return innerHTML.startsWith('<b>') && innerHTML.endsWith('</b>');
314             });
315             if (equalElement) {
316               equalElement.click();
317             }
318           }
319         } else if (event.key === 'Escape') {
320           if (arg2) {
321             return true;
322           }
323           if (examplesList.childNodes.length === 0) {
324             return;
325           }
326           event.preventDefault();
327           event.stopPropagation();
328           removeExampleItems();
329         }
330
331         if (arg2) {
332           return false;
333         }
334       };
335
336       window.projectNameInputOnKeyup = window.projectNameInputOnKeyup || function (e) {
337         // call to see if this is a key that is even relevant for the suggestions
338         if (window.suggestionsOnKeydownNav(e, true)) {
339           // return as true means the event was handled
340           return;
341         }
342
343         removeExampleItems();
344
345         if (!clickOutsideSuggestionsListenerAdded) {
346           clickOutsideSuggestionsListenerAdded = true;
347
348           // add on click event listener to 
349           document.body.addEventListener('click', handleOutsideSuggestionsClick);
350         }
351
352         const isInputEmpty = projectNameInput.value === "";
353         for (let i of Object.keys(examples).sort()) {
354           // startsWith was to strict for the examples name format we use
355           if (isInputEmpty || i.toLowerCase().includes(projectNameInput.value.toLowerCase())) {
356             // TODO: check if not added multiple times
357             projectNameInput.addEventListener('keydown', suggestionsOnKeydownNav);
358             projectNameDropdownButton.addEventListener('keydown', suggestionsOnKeydownNav);
359             projectNameInput.classList.replace('rounded-lg', 'rounded-t-lg');
360             projectNameDropdownButton.classList.replace("rounded-r-lg", "rounded-tr-lg");
361             examplesList.classList.add('border');
362
363             // create li element
364             let listItem = document.createElement("li");
365             // one common class name
366             listItem.classList.add("examples-list-suggestion");
367             listItem.style.cursor = "pointer";
368             // listItem.setAttribute("onclick", "examplesListSelect('" + i + "')");
369             // added as event listener because of content security policy
370             listItem.addEventListener("click", (event) => {
371               event.stopPropagation();
372               removeClickOutsideSuggestionsListener();
373               examplesListSelect(i);
374             });
375             listItem.addEventListener('mouseover', () => {
376               if (window.ignoreNextMouseOver) {
377                 // check if element is now in view if not then don't set ignoreMouseOver to false
378                 if (window.isElementInView(window.selectedSuggestion, examplesList)) {
379                   window.ignoreNextMouseOver = false;
380                 }
381
382                 return;
383               }
384               if (window.selectedSuggestion) {
385                 if (window.selectedSuggestion === listItem) {
386                   // so it can automatically lose the hover effect
387                   listItem.classList.remove('hovered');
388                   listItem.classList.remove('unhovered');
389                   return;
390                 } else {
391                   window.selectedSuggestion.classList.remove('hovered');
392                   window.selectedSuggestion.classList.add('unhovered');
393                 }
394               }
395               window.selectedSuggestion = listItem; // Set the hovered element as "selected"
396               listItem.classList.add('hovered');
397               listItem.classList.remove('unhovered');
398             });
399             listItem.addEventListener('mouseout', () => {
400               if (window.ignoreNextMouseOver) {
401                 // check if element is now in view if not then don't set ignoreMouseOver to false
402                 if (window.isElementInView(window.selectedSuggestion, examplesList)) {
403                   window.ignoreNextMouseOver = false;
404                 }
405
406                 return;
407               }
408               listItem.classList.remove('hovered');
409               listItem.classList.add('unhovered');
410               if (window.selectedSuggestion === listItem) {
411                 window.selectedSuggestion = undefined;
412               } else {
413                 window.selectedSuggestion.classList.remove('hovered');
414                 window.selectedSuggestion.classList.add('unhovered');
415               }
416             });
417
418             // display matched part as bold text
419             // this is for .startsWith selector above 
420             //let word = isInputEmpty ? "" : "<b>" + i.substr(0, projectNameInput.value.length) + "</b>";
421             //word += i.substr(projectNameInput.value.length);
422             // this is for .includes selector above
423             const startIndex = isInputEmpty ? 0 : i.indexOf(projectNameInput.value);
424             let word = isInputEmpty ? "" : i.substring(0, startIndex);
425             word += isInputEmpty ? "" : "<b>" + i.substring(startIndex, startIndex + projectNameInput.value.length) + "</b>";
426             word += i.substring(startIndex + projectNameInput.value.length);
427
428             // set value of li elemetn
429             listItem.innerHTML = word;
430             examplesList.appendChild(listItem);
431           }
432         }
433       };
434
435       projectNameDropdownButton.addEventListener('click', (event) => {
436         // without this the webview crashes if project name input contains any text
437         event.preventDefault();
438
439         if (examplesList.childNodes.length === 0) {
440           // this is required to prevent the outside suggestions listener to fire after it has been
441           // added below
442           event.stopPropagation();
443           projectNameInputOnKeyup(event);
444         } else {
445           removeExampleItems();
446         }
447       });
448
449       projectNameInput.addEventListener('keyup', projectNameInputOnKeyup);
450     }
451
452     if (projectOptionsDivs) {
453       hideCustomInputs(projectOptionsDivs, true);
454     }
455   }
456 };
457
458 //run navItemOnClick after page loaded
459 window.onload = function () {
460   // pre-select the first nav item
461   const navItems = document.getElementsByClassName('nav-item') ?? [];
462   const ovNavItems = document.getElementsByClassName('overlay-item');
463   Array.prototype.forEach.call([...navItems, ...ovNavItems], item => {
464     item.addEventListener('click', function () {
465       navItemOnClick(item.id);
466     });
467   });
468   navItemOnClick(navItems[0].id);
469
470   const projectNameInput = document.getElementById('inp-project-name');
471   const createFromExampleBtn = document.getElementById('btn-create-from-example');
472
473   if (projectNameInput) {
474     projectNameInput.addEventListener('input', function () {
475       var isExampleMode = createFromExampleBtn ? createFromExampleBtn.getAttribute('data-example-mode') === 'true' : true;
476       if (!isExampleMode) {
477         isExampleSelected = false;
478         return;
479       }
480
481       //const examplesList = document.getElementById('examples-list');
482       //const exampleOptions = Array.from(examplesList.options).map(option => option.value);
483
484       const inputValue = projectNameInput.value;
485       const isValueInOptions = Object.keys(examples).includes(inputValue);
486
487       if (isValueInOptions) {
488         // example selected
489         isExampleSelected = true;
490       } else {
491         // No example selected
492         isExampleSelected = false;
493       }
494     });
495   }
496
497   if (createFromExampleBtn) {
498     createFromExampleBtn.addEventListener('click', () => {
499       toggleCreateFromExampleMode();
500     });
501   }
502
503   if (forceCreateFromExample !== undefined && forceCreateFromExample) {
504     if (createFromExampleBtn) {
505       // display: none; example btn
506       createFromExampleBtn.classList.add('hidden');
507     }
508
509     toggleCreateFromExampleMode(true);
510   } else {
511     // hide if not force from example
512     const defaultBoardTypeOption = document.getElementById('sel-default');
513     if (defaultBoardTypeOption) {
514       defaultBoardTypeOption.hidden = true;
515       defaultBoardTypeOption.disabled = true;
516     }
517   }
518
519   // TODO: maybe can remove if option-board-type-pico2 disable is moved into state restore
520   const sdkSelector = document.getElementById('sel-pico-sdk');
521   if (sdkSelector) {
522     if (parseInt(sdkSelector.value.split(".")[0]) < 2) {
523       const selPico2 = document.getElementById('option-board-type-pico2');
524       if (selPico2) {
525         selPico2.disabled = true;
526       }
527     }
528   }
529
530   const burgerMenu = document.getElementById("burger-menu");
531   const navOverlay = document.getElementById("nav-overlay");
532
533   function toggleOverlay() {
534     navOverlay.classList.toggle("hidden");
535   }
536
537   function closeOverlay(e) {
538     if (!navOverlay.contains(e.target) && e.target !== burgerMenu) {
539       navOverlay.classList.add("hidden");
540     }
541   }
542
543   burgerMenu.addEventListener("click", toggleOverlay);
544   window.addEventListener("click", closeOverlay);
545
546   window.addEventListener("resize", function () {
547     if (window.innerWidth >= 1024) {
548       navOverlay.classList.add("hidden");
549     }
550   });
551 };
This page took 0.055426 seconds and 4 git commands to generate.