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/search 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='Satellite Data'>Satellite Data</option>
59 <div id='fires-column' class='column'>
60 <div class='column-header'>Fires</div>
61 <ul id='catalog-fires' class='catalog-list'> </ul>
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>
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>
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'>
76 <a href='https://nfmdb.org'>
77 <h3>FMDB Home Page</h3>
81 <li class='catalog-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>
97 const catalogMenu
= this.querySelector('.catalog-menu');
98 L
.DomEvent
.disableClickPropagation(catalogMenu
);
100 dragElement(catalogMenu
, 'menu-title');
103 window
.addEventListener('resize', () => {
106 this.initializeMenuSearching();
107 this.createMenuEntries();
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');
118 catalogMenu
.classList
.add('hidden');
121 this.querySelector('#menu-close').onclick
= () => {
122 catalogMenu
.classList
.add('hidden');
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
;
137 this.selectCategory('Fires');
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
) => {
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
);
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')) {
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
);
243 filteredList
.reverse();
247 this.filterColumns(createList
);
250 sortMenu(sortBy
, reverseOrder
) {
251 const catalogSearch
= this.querySelector('#search-for');
252 catalogSearch
.value
= '';
253 const sortingFunction
= (listElem1
, listElem2
) => {
256 case 'original-order':
257 result
= this.sortByOriginalOrder(listElem1
, listElem2
);
260 result
= this.sortByDescription(listElem1
, listElem2
);
263 result
= this.sortByStartDate(listElem1
, listElem2
);
266 result
= this.sortByEndDate(listElem1
, listElem2
);
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();
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();
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();
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');
344 lidarProfilesDOM
.classList
.remove('hidden');
349 window
.customElements
.define('catalog-menu', CatalogMenu
);