UNPKG

@helioviewer/event-tree

Version:

A react component for grouping and listing and selecting solar events.

248 lines (202 loc) 6.43 kB
import React from "react"; import { useRef, useState, useEffect } from "react"; import Cache from "./Core/Cache.jsx"; import EventTree from "./Core/EventTree.jsx"; import API from "./Core/API.jsx"; import Checkbox from "./Checkbox.jsx"; import Node from "./Node.jsx"; import SourceHeader from "./SourceHeader.jsx"; import { LoadingIcon } from "./Icons.jsx"; const useIsMount = () => { const isMountRef = useRef(false); useEffect(() => { isMountRef.current = true; }, []); return isMountRef.current; }; function HelioviewerEventTree({ source, eventsDate, onEventsUpdate, onHoveredEventsUpdate, onSelectionsUpdate = null, visibility = true, labelVisibility = true, onToggleVisibility = null, onToggleLabelVisibility = null, forcedSelections = null, apiURL = "https://api.helioviewer.org", onLoad = null, onError = (err) => { console.error(err); } }) { const isMount = useIsMount(); const cache = Cache.make(source); const [selections, setSelections] = useState(forcedSelections ?? cache.getSelections()); const [loading, setLoading] = useState(false); const [showEmptyBranches, setShowEmptyBranches] = useState(cache.getShowEmptyBranches()); const [eventTree, setEventTree] = useState(null); const [events, setEvents] = useState([]); // Run initially first useEffect(() => { const controller = new AbortController(); const signal = controller.signal; async function fetchEvents() { setLoading(true); try { const api = new API(apiURL); const apiEvents = await api.getEvents(eventsDate, source, { signal }); const eventTree = EventTree.make(apiEvents, source); const adjustedSelections = eventTree.adjustSelectionsWithPath(selections); const selectedTree = eventTree.applySelections(adjustedSelections); Cache.make(source).saveSelections(adjustedSelections); if (onSelectionsUpdate != null) { onSelectionsUpdate(adjustedSelections, selectedTree.selectedEvents()); } onEventsUpdate(selectedTree.selectedEvents()); setEventTree(selectedTree); } catch (error) { if (error.name != "AbortError") { onError(error); } } finally { setLoading(false); } } fetchEvents(); return () => { controller.abort(); // Abort on cleanup }; }, [eventsDate.getTime()]); useEffect(() => { // This is only for after mount if (!isMount) { return; } Cache.make(source).saveSelections(selections); const selectedTree = eventTree.removeSelections().applySelections(selections); if (onSelectionsUpdate != null) { onSelectionsUpdate(selections, selectedTree.selectedEvents()); } onEventsUpdate(selectedTree.selectedEvents()); setEventTree(selectedTree); }, [selections]); useEffect(() => { // Only update if it is updated from outside if (!isMount || forcedSelections == null) { return; } setSelections(forcedSelections); }, [forcedSelections]); useEffect(() => { if (onLoad) { onLoad(); } }); const toggleCheckbox = function (id) { const newSelections = eventTree.toggleCheckbox(id).extractSelections(); setSelections(newSelections); }; const toggleExpand = function (id) { const newTree = eventTree.toggleExpand(id); setEventTree(newTree); }; const onHover = function (hoverId) { const hoveredEvents = eventTree.getEventsOfNode(hoverId).map((e) => { return e.id; }); onHoveredEventsUpdate(hoveredEvents); }; const offHover = function () { onHoveredEventsUpdate([]); }; const handleShowEmptyBranches = function (newShowEmptyBranchesValue) { Cache.make(source).saveShowEmptyBranches(newShowEmptyBranchesValue); setShowEmptyBranches(newShowEmptyBranchesValue); }; if (eventTree == null) { return; } let nodeContainerStyle = { margin: 0, padding: ".55em .125em .1em 1em", backgroundImage: "linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0))", MozBackgroundImage: "-moz-linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0))", OBackgroundImage: "-o-linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0))", WebkitBackgroundImage: "-webkit-linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0))" }; let hasEvents = eventTree.getEventCount(source) > 0; let hasCategories = eventTree[source].children.length > 0; if (!hasEvents) { nodeContainerStyle = { opacity: 0.5, ...nodeContainerStyle }; } const nodeChildrensStyle = { paddingLeft: 3, paddingTop: 10, paddingBottom: 10, fontSize: 13 }; if (!eventTree[source].expand) { nodeChildrensStyle["display"] = "none"; } let eventTreeRender = <span style={{ color: "red" }}>No events</span>; if (hasCategories) { eventTreeRender = eventTree[source].children.map((cid) => { return ( <Node key={cid} id={cid} toggleCheckbox={toggleCheckbox} toggleExpand={toggleExpand} onHover={onHover} offHover={offHover} eventTree={eventTree} showEmptyBranches={showEmptyBranches} /> ); }); } return ( <> <div style={nodeContainerStyle}> <SourceHeader hasEvents={hasEvents} hasCategories={hasCategories} checked={eventTree[source].state} onCheckedUpdate={() => toggleCheckbox(source)} onHeaderHover={() => { onHover(source); }} offHeaderHover={() => { offHover(); }} source={source} eventsDate={eventsDate} expand={eventTree[source].expand} toggleExpand={() => toggleExpand(source)} showEmptyBranches={showEmptyBranches} handleShowEmptyBranches={handleShowEmptyBranches} visibility={visibility} handleVisibility={onToggleVisibility} labelVisibility={labelVisibility} handleLabelVisibility={onToggleLabelVisibility} /> <div style={nodeChildrensStyle}> {loading ? ( <div className="event-tree-container-loader"> <LoadingIcon /> <span>&nbsp;Loading</span> </div> ) : ( eventTreeRender )} </div> </div> </> ); } export default HelioviewerEventTree;