Adds new column for nfmdb
[wrfxweb.git] / fdds / js / components / CatalogMenu / catalogMenu.js
blob8c6d81f41bce44981b21db947ae3f5ae54247a98
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
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/search 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='Satellite Data'>Satellite Data</option>
58 </select>
59 <div id='fires-column' class='column'>
60 <div class='column-header'>Fires</div>
61 <ul id='catalog-fires' class='catalog-list'> </ul>
62 </div>
63 <div id='fuel-moisture-column' class='column'>
64 <div class='column-header'>Fuel moisture</div>
65 <ul id='catalog-fuel-moisture' class='catalog-list'> </ul>
66 </div>
67 <div id='lidar-profiles' class='column'>
68 <div class='column-header'>Lidar Profiles</div>
69 <ul id='catalog-lidar-data' class='catalog-list'> </ul>
70 </div>
71 <div id='nfmdb-links' class='column'>
72 <div class='column-header'>NFMDB Links</div>
73 <ul id='catalog-nfmdb-links' class='catalog-list'>
74 <li class='catalog-entry'>
75 <div id='entry'>
76 <a href='https://nfmdb.org'>
77 <h3>FMDB Home Page</h3>
78 </a>
79 </div>
80 </li>
81 <li class='catalog-entry'>
82 <div id='entry'>
83 <a href='https://nfmdb.com/?usState=CA&zoom=6&coordinates=37.821_-120.724&startYear=2022&endYear=2024&liveOrDeadFuel=liveFuel&plotType=fmcClimo'>
84 <h3>FMDB California</h3>
85 </a>
86 </div>
87 </li>
88 </ul>
89 </div>
90 </div>
91 </div>
92 </div>
96 connectedCallback() {
97 const catalogMenu = this.querySelector('.catalog-menu');
98 L.DomEvent.disableClickPropagation(catalogMenu);
100 dragElement(catalogMenu, 'menu-title');
101 this.hideShowMenu();
102 this.responsiveUI();
103 window.addEventListener('resize', () => {
104 this.responsiveUI();
106 this.initializeMenuSearching();
107 this.createMenuEntries();
110 hideShowMenu() {
111 const catalogMenu = this.querySelector('.catalog-menu');
112 const catalogButton = this.querySelector('#catalog-button');
113 L.DomEvent.disableClickPropagation(catalogButton);
114 catalogButton.onpointerdown = () => {
115 if (catalogMenu.classList.contains('hidden')) {
116 catalogMenu.classList.remove('hidden');
117 } else {
118 catalogMenu.classList.add('hidden');
121 this.querySelector('#menu-close').onclick = () => {
122 catalogMenu.classList.add('hidden');
126 responsiveUI() {
128 const catalogMenu = this.querySelector('.catalog-menu');
129 const reverseLabel = this.querySelector('#reverse-label');
130 const menuSearch = this.querySelector('#search-for');
132 reverseLabel.innerText = IS_MOBILE ? 'Reverse' : 'Reverse Order';
133 catalogMenu.style.right = ((CLIENT_WIDTH - catalogMenu.clientWidth)/ 2) + 'px';
134 let searchDescription = IS_MOBILE ? 'Search...' : 'Search for Simulation...';
135 menuSearch.placeholder = searchDescription;
136 if (IS_MOBILE) {
137 this.selectCategory('Fires');
138 } else {
139 const firesListDOM = this.querySelector('#fires-column');
140 const fuelMoistureListDOM = this.querySelector('#fuel-moisture-column');
141 const lidarProfilesDOM = this.querySelector('#lidar-profiles');
142 firesListDOM.classList.remove('hidden');
143 fuelMoistureListDOM.classList.remove('hidden');
144 lidarProfilesDOM.classList.remove('hidden');
148 initializeMenuSearching() {
149 const sortBy = this.querySelector('#sort-by');
150 const reverseOrder = this.querySelector('#reverse-order');
151 const menuSearch = this.querySelector('#search-for');
152 const menuSelect = this.querySelector('#mobile-selector');
154 menuSearch.onpointerdown = (e) => {
155 e.stopPropagation();
157 menuSearch.oninput = () => {
158 this.searchCatalog(menuSearch.value.toLowerCase(), sortBy.value, reverseOrder.checked);
160 sortBy.onchange = () => {
161 this.sortMenu(sortBy.value, reverseOrder.checked);
163 reverseOrder.onclick = () => {
164 this.searchCatalog(menuSearch.value.toLowerCase(), sortBy.value, reverseOrder.checked);
166 menuSelect.onchange = () => {
167 this.selectCategory(menuSelect.value);
171 async createMenuEntries() {
172 const urlParams = new URLSearchParams(window.location.search);
173 const navJobId = urlParams.get('job_id');
174 const firesListDOM = this.querySelector('#catalog-fires');
175 const fuelMoistureListDOM = this.querySelector('#catalog-fuel-moisture');
176 const lidarProfilesDOM = this.querySelector('#catalog-lidar-data');
177 const catalogEntries = await getCatalogEntries();
178 for (let catName in catalogEntries) {
179 let catEntry = catalogEntries[catName];
180 this.addOrder.push(catEntry.job_id);
181 let desc = catEntry.description;
182 let newLI = new CatalogItem(catEntry, navJobId);
183 if(desc.indexOf('GACC') >= 0 || desc.indexOf(' FM') >= 0) {
184 this.fuelMoistureList.push(catEntry);
185 fuelMoistureListDOM.appendChild(newLI);
186 } else if(desc.indexOf('Lidar') >= 0) {
187 this.satelliteList.push(catEntry);
188 lidarProfilesDOM.appendChild(newLI);
189 } else {
190 this.firesList.push(catEntry);
191 firesListDOM.appendChild(newLI);
194 this.sortMenu('start-date', false);
195 this.clickMostRecent(navJobId);
198 clickMostRecent(navJobId) {
199 const firesListDOM = this.querySelector('#catalog-fires');
200 if (!navJobId || !navJobId.includes('recent')) {
201 return;
203 let descSearchTerm = navJobId.split('-')[0].toLowerCase();
204 let mostRecentItem = null;
205 let secondMostRecentItem = null;
206 for (let fire of firesListDOM.childNodes) {
207 let fireDesc = fire.catEntry.description;
208 if (fireDesc.toLowerCase().includes(descSearchTerm)) {
209 if (!mostRecentItem || (fire.catEntry.from_utc > mostRecentItem.catEntry.from_utc)) {
210 secondMostRecentItem = mostRecentItem;
211 mostRecentItem = fire;
212 } else if (!secondMostRecentItem || (fire.catEntry.from_utc > secondMostRecentItem.catEntry.from_utc)) {
213 secondMostRecentItem = fire;
217 let itemToNavigateTo = mostRecentItem;
218 if (navJobId.includes('second-recent')) {
219 itemToNavigateTo = secondMostRecentItem;
222 if (itemToNavigateTo != null) {
223 itemToNavigateTo.clickItem();
227 /** ===== Searching block ===== */
228 searchCatalog(searchText, sortBy, reverseOrder) {
229 const filterFunction = (catalogEntry) => {
230 if (sortBy == 'original-order' || sortBy == 'description') {
231 return catalogEntry.description.toLowerCase().includes(searchText);
233 if (sortBy.includes('start-date')) {
234 return utcToLocal(catalogEntry.from_utc).toLowerCase().includes(searchText);
236 if (sortBy.includes('end-date')) {
237 return utcToLocal(catalogEntry.to_utc).toLowerCase().includes(searchText);
240 const createList = (list) => {
241 let filteredList = list.filter(filterFunction);
242 if (reverseOrder) {
243 filteredList.reverse();
245 return filteredList;
247 this.filterColumns(createList);
250 sortMenu(sortBy, reverseOrder) {
251 const catalogSearch = this.querySelector('#search-for');
252 catalogSearch.value = '';
253 const sortingFunction = (listElem1, listElem2) => {
254 let result;
255 switch(sortBy) {
256 case 'original-order':
257 result = this.sortByOriginalOrder(listElem1, listElem2);
258 break;
259 case 'description':
260 result = this.sortByDescription(listElem1, listElem2);
261 break;
262 case 'start-date':
263 result = this.sortByStartDate(listElem1, listElem2);
264 break;
265 case 'end-date':
266 result = this.sortByEndDate(listElem1, listElem2);
267 break;
268 default:
269 result = false;
271 if (reverseOrder) {
272 result = !result;
274 return result ? 1 : -1;
276 const createList = (list) => {
277 return list.sort(sortingFunction);
279 this.filterColumns(createList);
282 sortByOriginalOrder(listElem1, listElem2) {
283 let desc = listElem1.description;
284 if (desc.indexOf('GACC') >= 0 || desc.indexOf(' FM') >= 0) {
285 return listElem1.description.toLowerCase() > listElem2.description.toLowerCase();
286 } else {
287 return this.addOrder.indexOf(listElem1.job_id) > this.addOrder.indexOf(listElem2.job_id);
291 sortByDescription(listElem1, listElem2) {
292 return listElem1.description.toLowerCase() > listElem2.description.toLowerCase();
295 sortByStartDate(listElem1, listElem2) {
296 if (listElem1.from_utc == listElem2.from_utc) {
297 return listElem1.description.toLowerCase() > listElem2.description.toLowerCase();
298 } else {
299 return listElem1.from_utc < listElem2.from_utc;
303 sortByEndDate(listElem1, listElem2) {
304 if (listElem1.to_utc == listElem2.to_utc) {
305 return listElem1.description.toLowerCase() > listElem2.description.toLowerCase();
306 } else {
307 return listElem1.to_utc < listElem2.to_utc;
311 filterColumns(listCreator) {
312 const firesListDOM = this.querySelector('#catalog-fires');
313 const fuelMoistureListDOM = this.querySelector('#catalog-fuel-moisture');
314 const lidarProfilesDOM = this.querySelector('#catalog-lidar-data');
316 this.filterColumn(firesListDOM, this.firesList, listCreator);
317 this.filterColumn(fuelMoistureListDOM, this.fuelMoistureList, listCreator);
318 this.filterColumn(lidarProfilesDOM, this.satelliteList, listCreator);
321 filterColumn(categoryDOM, categoryList, listCreator) {
322 categoryDOM.innerHTML = '';
323 let newList = listCreator(categoryList);
324 for (let catalogEntry of newList) {
325 let newLI = new CatalogItem(catalogEntry, null);
326 categoryDOM.append(newLI);
330 /** 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.
331 * Hides all columns and then shows the selected column. */
332 selectCategory(selection) {
333 const firesListDOM = this.querySelector('#fires-column');
334 const fuelMoistureListDOM = this.querySelector('#fuel-moisture-column');
335 const lidarProfilesDOM = this.querySelector('#lidar-profiles');
336 firesListDOM.classList.add('hidden');
337 fuelMoistureListDOM.classList.add('hidden');
338 lidarProfilesDOM.classList.add('hidden');
339 if (selection == 'Fires') {
340 firesListDOM.classList.remove('hidden');
341 } else if (selection == 'Fuel Moisture') {
342 fuelMoistureListDOM.classList.remove('hidden');
343 } else {
344 lidarProfilesDOM.classList.remove('hidden');
349 window.customElements.define('catalog-menu', CatalogMenu);