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.
10 * 1. Initialization block
14 export class CatalogMenu extends HTMLElement {
15 /** ===== Initialization block ===== */
19 this.fuelMoistureList = [];
20 this.satelliteList = [];
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>
30 <div id='menu-label'>Catalog</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>
37 <div class='search-header'>
39 <label for='sort-by' style='display: block; font-size:.75rem'>order by</label>
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>
47 <div class='sorting-column'>
48 <label id='reverse-label' for='reverse-order'>Reverse Order</label>
49 <input type='checkbox' id='reverse-order'></input>
51 <input id='search-for' type='text'></input>
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>
60 <div id='fires-column' class='column'>
61 <div class='column-header'>Fires</div>
62 <ul id='catalog-fires' class='catalog-list'> </ul>
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>
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>
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'>
77 <a href='https://nfmdb.org'>
78 <h3>FMDB Home Page</h3>
82 <li class='catalog-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>
98 const catalogMenu = this.querySelector('.catalog-menu');
99 L.DomEvent.disableClickPropagation(catalogMenu);
101 dragElement(catalogMenu, 'menu-title');
104 window.addEventListener('resize', () => {
107 this.initializeMenuSearching();
108 this.createMenuEntries();
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');
119 catalogMenu.classList.add('hidden');
122 this.querySelector('#menu-close').onclick = () => {
123 catalogMenu.classList.add('hidden');
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;
138 this.selectCategory('Fires');
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');
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) => {
158 menuSearch.oninput = () => {
159 this.searchCatalog(menuSearch.value.toLowerCase(), sortBy.value, reverseOrder.checked);
161 sortBy.onchange = () => {
162 this.sortMenu(sortBy.value, reverseOrder.checked);
164 reverseOrder.onclick = () => {
165 this.searchCatalog(menuSearch.value.toLowerCase(), sortBy.value, reverseOrder.checked);
167 menuSelect.onchange = () => {
168 this.selectCategory(menuSelect.value);
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);
191 this.firesList.push(catEntry);
192 firesListDOM.appendChild(newLI);
195 this.sortMenu('start-date', false);
196 this.clickMostRecent(navJobId);
199 clickMostRecent(navJobId) {
200 const firesListDOM = this.querySelector('#catalog-fires');
201 if (!navJobId || !navJobId.includes('recent')) {
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;
218 let itemToNavigateTo = mostRecentItem;
219 if (navJobId.includes('second-recent')) {
220 itemToNavigateTo = secondMostRecentItem;
223 if (itemToNavigateTo != null) {
224 itemToNavigateTo.clickItem();
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;
236 const createList = (list) => {
237 let filteredList = list.filter(filterFunction);
239 filteredList.reverse();
243 this.filterColumns(createList);
246 sortMenu(sortBy, reverseOrder) {
247 const catalogSearch = this.querySelector('#search-for');
248 catalogSearch.value = '';
249 const sortingFunction = (listElem1, listElem2) => {
252 case 'original-order':
253 result = this.sortByOriginalOrder(listElem1, listElem2);
256 result = this.sortByDescription(listElem1, listElem2);
259 result = this.sortByStartDate(listElem1, listElem2);
262 result = this.sortByEndDate(listElem1, listElem2);
270 return result ? 1 : -1;
272 const createList = (list) => {
273 return list.sort(sortingFunction);
275 this.filterColumns(createList);
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();
283 return this.addOrder.indexOf(listElem1.job_id) > this.addOrder.indexOf(listElem2.job_id);
287 sortByDescription(listElem1, listElem2) {
288 return listElem1.description.toLowerCase() > listElem2.description.toLowerCase();
291 sortByStartDate(listElem1, listElem2) {
292 if (listElem1.from_utc == listElem2.from_utc) {
293 return listElem1.description.toLowerCase() > listElem2.description.toLowerCase();
295 return listElem1.from_utc < listElem2.from_utc;
299 sortByEndDate(listElem1, listElem2) {
300 if (listElem1.to_utc == listElem2.to_utc) {
301 return listElem1.description.toLowerCase() > listElem2.description.toLowerCase();
303 return listElem1.to_utc < listElem2.to_utc;
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);
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);
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');
344 nfmdbLinksDOM.classList.remove('hidden');
349 window.customElements.define('catalog-menu', CatalogMenu);