Fixes mobile with lidar changes
[wrfxweb.git] / fdds / js / components / CatalogMenu / catalogMenu.js
blobb723431f7e31874cf01f3967fa6535fc7e388916
1 import { CLIENT_WIDTH, dragElement, IS_MOBILE, utcToLocal } from '../../util.js';
2 import { getCatalogEntries } from '../../services.js';
3 import { CatalogItem } from './catalogItem.js';
5 /** Component for menu. Includes three different columns for data related to fires, fuel moisture, and satellite data. 
6  * Can be moved around by clicking the title bar, can be closed by clicking x in top right corner, and 
7  * supports searching columns for data that matches a description.
8  * 
9  *                  Contents
10  *  1. Initialization block
11  *  2. Searching block
12  * 
13  */
14 export class CatalogMenu extends HTMLElement {
15     /** ===== Initialization block ===== */
16     constructor() {
17         super();
18         this.firesList = [];
19         this.fuelMoistureList = [];
20         this.satelliteList = [];
21         this.addOrder = [];
22         this.innerHTML = `
23             <div>
24                 <div id='catalog-button' class='feature-controller catalog-button'>
25                     <div id='catalog-menu-icon-container'>
26                         <svg id='catalog-menu-icon' class='interactive-button svgIcon'>
27                             <use href='#menu-24px'></use>
28                         </svg>
29                     </div>
30                     <div id='menu-label'>Catalog</div>
31                 </div>
32                 <div class='catalog-menu round-border'>
33                     <div id='menu-title' class='menu-title round-border'>
34                         <div>Select Simulation...</div>
35                         <div id='menu-close' class='round-border'>x</div>
36                     </div>
37                     <div class='search-header'>
38                         <div>
39                             <label for='sort-by' style='display: block; font-size:.75rem'>order by</label>
40                             <select id='sort-by'>
41                                 <option value='start-date'>start date</option>
42                                 <option value='end-date'>end date</option>
43                                 <option value='original-order'>original order</option>
44                                 <option value='description'>description</option>
45                             </select>
46                         </div>
47                         <div class='sorting-column'>
48                             <label id='reverse-label' for='reverse-order'>Reverse Order</label>
49                             <input type='checkbox' id='reverse-order'></input>
50                         </div>
51                         <input id='search-for' type='text'></input>
52                     </div>
53                     <div class='menu-columns'>
54                         <select id='mobile-selector'>
55                             <option value='Fires'>Fires</option>
56                             <option value='Fuel Moisture'>Fuel Moisture</option>
57                             <option value='Lidar Data'>Lidar Data</option>
58                             <option value='NFMDB Links'>NFMDB Links</option>
59                         </select>
60                         <div id='fires-column' class='column'>
61                             <div class='column-header'>Fires</div>
62                             <ul id='catalog-fires' class='catalog-list'> </ul>
63                         </div>
64                         <div id='fuel-moisture-column' class='column'>
65                             <div class='column-header'>Fuel moisture</div>
66                             <ul id='catalog-fuel-moisture' class='catalog-list'> </ul>
67                         </div>
68                         <div id='lidar-profiles' class='column'>
69                             <div class='column-header'>Lidar Profiles</div>
70                             <ul id='catalog-lidar-data' class='catalog-list'> </ul>
71                         </div>
72                         <div id='nfmdb-links' class='column'>
73                             <div class='column-header'>NFMDB Links</div>
74                             <ul id='catalog-nfmdb-links' class='catalog-list'> 
75                               <li class='catalog-entry'>
76                                 <div id='entry'>
77                                   <a href='https://nfmdb.org'>
78                                     <h3>FMDB Home Page</h3>
79                                   </a>
80                                 </div>
81                               </li>
82                               <li class='catalog-entry'>
83                                 <div id='entry'>
84                                   <a href='https://nfmdb.com/?usState=CA&zoom=6&coordinates=37.821_-120.724&startYear=2022&endYear=2024&liveOrDeadFuel=liveFuel&plotType=fmcClimo'>
85                                     <h3>FMDB California</h3>
86                                   </a>
87                                 </div>
88                               </li>
89                             </ul>
90                         </div>
91                     </div>
92                 </div>
93             </div>
94         `;
95     }
97     connectedCallback() {
98         const catalogMenu = this.querySelector('.catalog-menu');
99         L.DomEvent.disableClickPropagation(catalogMenu);
101         dragElement(catalogMenu, 'menu-title');
102         this.hideShowMenu();
103         this.responsiveUI();
104         window.addEventListener('resize', () => { 
105             this.responsiveUI();
106         });
107         this.initializeMenuSearching();
108         this.createMenuEntries();
109     }
111     hideShowMenu() {
112         const catalogMenu = this.querySelector('.catalog-menu');
113         const catalogButton = this.querySelector('#catalog-button');
114         L.DomEvent.disableClickPropagation(catalogButton);
115         catalogButton.onpointerdown = () => {
116             if (catalogMenu.classList.contains('hidden')) {
117                 catalogMenu.classList.remove('hidden');
118             } else {
119                 catalogMenu.classList.add('hidden');
120             }
121         };
122         this.querySelector('#menu-close').onclick = () => {
123             catalogMenu.classList.add('hidden');
124         }
125     }
127     responsiveUI() {
129         const catalogMenu = this.querySelector('.catalog-menu');
130         const reverseLabel = this.querySelector('#reverse-label');
131         const menuSearch = this.querySelector('#search-for');
133         reverseLabel.innerText = IS_MOBILE ? 'Reverse' : 'Reverse Order';
134         catalogMenu.style.right = ((CLIENT_WIDTH - catalogMenu.clientWidth)/ 2) + 'px';
135         let searchDescription = IS_MOBILE ? 'Search...' : 'Search for Simulation...';
136         menuSearch.placeholder = searchDescription;
137         if (IS_MOBILE) {
138             this.selectCategory('Fires');
139         } else {
140             const firesListDOM = this.querySelector('#fires-column');
141             const fuelMoistureListDOM = this.querySelector('#fuel-moisture-column');
142             const lidarProfilesDOM = this.querySelector('#lidar-profiles');
143             firesListDOM.classList.remove('hidden');
144             fuelMoistureListDOM.classList.remove('hidden');
145             lidarProfilesDOM.classList.remove('hidden');
146         }
147     }
149     initializeMenuSearching() {
150         const sortBy = this.querySelector('#sort-by');
151         const reverseOrder = this.querySelector('#reverse-order');
152         const menuSearch = this.querySelector('#search-for');
153         const menuSelect = this.querySelector('#mobile-selector');
155         menuSearch.onpointerdown = (e) => {
156             e.stopPropagation();
157         }
158         menuSearch.oninput = () => {
159             this.searchCatalog(menuSearch.value.toLowerCase(), sortBy.value, reverseOrder.checked);
160         }
161         sortBy.onchange = () => {
162             this.sortMenu(sortBy.value, reverseOrder.checked);
163         }
164         reverseOrder.onclick = () => {
165             this.searchCatalog(menuSearch.value.toLowerCase(), sortBy.value, reverseOrder.checked);
166         }
167         menuSelect.onchange = () => {
168             this.selectCategory(menuSelect.value);
169         }
170     }
172     async createMenuEntries() {
173         const urlParams = new URLSearchParams(window.location.search);
174         const navJobId = urlParams.get('job_id');
175         const firesListDOM = this.querySelector('#catalog-fires');
176         const fuelMoistureListDOM = this.querySelector('#catalog-fuel-moisture');
177         const lidarProfilesDOM = this.querySelector('#catalog-lidar-data');
178         const catalogEntries = await getCatalogEntries();
179         for (let catName in catalogEntries) {
180             let catEntry = catalogEntries[catName];
181             this.addOrder.push(catEntry.job_id);
182             let desc = catEntry.description;
183             let newLI = new CatalogItem(catEntry, navJobId);
184             if(desc.indexOf('GACC') >= 0 || desc.indexOf(' FM') >= 0) {
185                 this.fuelMoistureList.push(catEntry);
186                 fuelMoistureListDOM.appendChild(newLI);
187             } else if(desc.indexOf('Lidar') >= 0) {
188                 this.satelliteList.push(catEntry);
189                 lidarProfilesDOM.appendChild(newLI);
190             } else {
191                 this.firesList.push(catEntry);
192                 firesListDOM.appendChild(newLI);
193             }
194         }
195         this.sortMenu('start-date', false);
196         this.clickMostRecent(navJobId);
197     }
199     clickMostRecent(navJobId) {
200         const firesListDOM = this.querySelector('#catalog-fires');
201         if (!navJobId || !navJobId.includes('recent')) {
202             return;
203         }
204         let descSearchTerm = navJobId.split('-')[0].toLowerCase();
205         let mostRecentItem = null;
206         let secondMostRecentItem = null;
207         for (let fire of firesListDOM.childNodes) {
208             let fireDesc = fire.catEntry.description;
209             if (fireDesc.toLowerCase().includes(descSearchTerm)) {
210                 if (!mostRecentItem || (fire.catEntry.from_utc > mostRecentItem.catEntry.from_utc)) {
211                     secondMostRecentItem = mostRecentItem;
212                     mostRecentItem = fire;
213                 } else if (!secondMostRecentItem || (fire.catEntry.from_utc > secondMostRecentItem.catEntry.from_utc)) {
214                     secondMostRecentItem = fire;
215                 }
216             }
217         }
218         let itemToNavigateTo = mostRecentItem;
219         if (navJobId.includes('second-recent')) {
220             itemToNavigateTo = secondMostRecentItem;
221         }
222         
223         if (itemToNavigateTo != null) {
224             itemToNavigateTo.clickItem();
225         }
226     }
228     /** ===== Searching block ===== */
229     searchCatalog(searchText, sortBy, reverseOrder) {
230         const filterFunction = (catalogEntry) => {
231             let descIncludes = catalogEntry.description.toLowerCase().includes(searchText);
232             let startIncludes = utcToLocal(catalogEntry.from_utc).toLowerCase().includes(searchText);
233             let endIncludes = utcToLocal(catalogEntry.to_utc).toLowerCase().includes(searchText);
234             return descIncludes || startIncludes || endIncludes;
235         }
236         const createList = (list) => {
237             let filteredList = list.filter(filterFunction);
238             if (reverseOrder) {
239                 filteredList.reverse();
240             }
241             return filteredList;
242         }
243         this.filterColumns(createList);
244     }
246     sortMenu(sortBy, reverseOrder) {
247         const catalogSearch = this.querySelector('#search-for');
248         catalogSearch.value = '';
249         const sortingFunction = (listElem1, listElem2) => {
250             let result;
251             switch(sortBy) {
252                 case 'original-order':
253                     result = this.sortByOriginalOrder(listElem1, listElem2);
254                     break;
255                 case 'description':
256                     result = this.sortByDescription(listElem1, listElem2);
257                     break;
258                 case 'start-date':
259                     result = this.sortByStartDate(listElem1, listElem2);
260                     break;
261                 case 'end-date':
262                     result = this.sortByEndDate(listElem1, listElem2);
263                     break;
264                 default: 
265                     result = false;
266             }
267             if (reverseOrder) {
268                 result = !result;
269             }
270             return result ? 1 : -1;
271         }
272         const createList = (list) => { 
273             return list.sort(sortingFunction);
274         }
275         this.filterColumns(createList);
276     }
278     sortByOriginalOrder(listElem1, listElem2) {
279         let desc = listElem1.description;
280         if (desc.indexOf('GACC') >= 0 || desc.indexOf(' FM') >= 0) {
281             return listElem1.description.toLowerCase() > listElem2.description.toLowerCase(); 
282         } else {
283             return this.addOrder.indexOf(listElem1.job_id) > this.addOrder.indexOf(listElem2.job_id);
284         }
285     }
287     sortByDescription(listElem1, listElem2) {
288         return listElem1.description.toLowerCase() > listElem2.description.toLowerCase(); 
289     }
291     sortByStartDate(listElem1, listElem2) {
292         if (listElem1.from_utc == listElem2.from_utc) {
293             return listElem1.description.toLowerCase() > listElem2.description.toLowerCase(); 
294         } else {
295             return listElem1.from_utc < listElem2.from_utc;
296         }
297     }
299     sortByEndDate(listElem1, listElem2) {
300         if (listElem1.to_utc == listElem2.to_utc) {
301             return listElem1.description.toLowerCase() > listElem2.description.toLowerCase(); 
302         } else {
303             return listElem1.to_utc < listElem2.to_utc;
304         }
305     }
307     filterColumns(listCreator) {
308         const firesListDOM = this.querySelector('#catalog-fires');
309         const fuelMoistureListDOM = this.querySelector('#catalog-fuel-moisture');
310         const lidarProfilesDOM = this.querySelector('#catalog-lidar-data');
312         this.filterColumn(firesListDOM, this.firesList, listCreator);
313         this.filterColumn(fuelMoistureListDOM, this.fuelMoistureList, listCreator);
314         this.filterColumn(lidarProfilesDOM, this.satelliteList, listCreator);
315     }
317     filterColumn(categoryDOM, categoryList, listCreator) {
318         categoryDOM.innerHTML = '';
319         let newList = listCreator(categoryList);
320         for (let catalogEntry of newList) {
321             let newLI = new CatalogItem(catalogEntry, null);
322             categoryDOM.append(newLI);
323         }
324     }
326     /** Function used only in mobile versions. Mobile shows only one column at a time and this function is called when a user switches between columns. 
327      * Hides all columns and then shows the selected column. */
328     selectCategory(selection) {
329         const firesListDOM = this.querySelector('#fires-column');
330         const fuelMoistureListDOM = this.querySelector('#fuel-moisture-column');
331         const lidarProfilesDOM = this.querySelector('#lidar-profiles');
332         const nfmdbLinksDOM = this.querySelector('#nfmdb-links');
333         firesListDOM.classList.add('hidden');
334         fuelMoistureListDOM.classList.add('hidden');
335         lidarProfilesDOM.classList.add('hidden');
336         nfmdbLinksDOM.classList.add('hidden');
337         if (selection == 'Fires') {
338             firesListDOM.classList.remove('hidden');
339         } else if (selection == 'Fuel Moisture') {
340             fuelMoistureListDOM.classList.remove('hidden');
341         } else if (selection == 'Lidar Data') {
342             lidarProfilesDOM.classList.remove('hidden');
343         } else {
344             nfmdbLinksDOM.classList.remove('hidden');
345         }
346     }
349 window.customElements.define('catalog-menu', CatalogMenu);