Refactor, move all pages to separate folder
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
import moment from "moment";
|
||||
import { createSignal } from "solid-js";
|
||||
|
||||
import { SelectCity } from "../../components/SelectCity";
|
||||
import { SelectTimeRange } from "../../components/SelectTimeRange";
|
||||
import { SelectField } from "../../components/SelectField";
|
||||
|
||||
import { SelectGranularity } from "./SelectGranularity";
|
||||
import { SelectKey } from "./SelectKey";
|
||||
import { QueryView } from "./result/QueryView";
|
||||
|
||||
const nowRounded = new Date(new Date().setMinutes(30));
|
||||
const dayAgo = moment(nowRounded).subtract(1, "days").subtract(30, "minutes").toDate();
|
||||
|
||||
export function Cities() {
|
||||
const [getCities, setCities] = createSignal<Set<string>>(new Set([]));
|
||||
const [getStart, setStart] = createSignal(dayAgo);
|
||||
const [getEnd, setEnd] = createSignal(nowRounded);
|
||||
const [getField, setField] = createSignal("tempMax");
|
||||
const [getKey, setKey] = createSignal("max");
|
||||
const [getGranularity, setGranularity] = createSignal("hour");
|
||||
|
||||
return (
|
||||
<div class="aggregator">
|
||||
<div class="container">
|
||||
<div class="column">
|
||||
<SelectCity getCities={getCities} setCities={setCities} />
|
||||
</div>
|
||||
<div class="column">
|
||||
<div>
|
||||
<SelectTimeRange
|
||||
getStart={getStart}
|
||||
setStart={setStart}
|
||||
getEnd={getEnd}
|
||||
setEnd={setEnd}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<h4>Select field</h4>
|
||||
<SelectField getField={getField} setField={setField} />
|
||||
</div>
|
||||
<SelectKey getKey={getKey} setKey={setKey} />
|
||||
<SelectGranularity getGranularity={getGranularity} setGranularity={setGranularity} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<QueryView
|
||||
getCities={getCities}
|
||||
getStart={getStart}
|
||||
getEnd={getEnd}
|
||||
getField={getField}
|
||||
getKey={getKey}
|
||||
getGranularity={getGranularity}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Accessor, Component, Setter } from "solid-js";
|
||||
|
||||
import { aggregateGranularity } from "../../consts";
|
||||
|
||||
export const SelectGranularity: Component<{
|
||||
getGranularity: Accessor<string>,
|
||||
setGranularity: Setter<string>,
|
||||
}> = ({ getGranularity, setGranularity }) => {
|
||||
return (
|
||||
<div>
|
||||
<h4>Select granularity</h4>
|
||||
<select onChange={(e) => setGranularity(e.target.value)}>
|
||||
{ aggregateGranularity.map(granularity =>
|
||||
<option
|
||||
value={granularity}
|
||||
selected={granularity === getGranularity() ? true : false}
|
||||
>{granularity}</option>
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Accessor, Component, Setter } from "solid-js";
|
||||
|
||||
import { aggregateKey } from "../../consts";
|
||||
|
||||
export const SelectKey: Component<{
|
||||
getKey: Accessor<string>,
|
||||
setKey: Setter<string>,
|
||||
}> = (props) => {
|
||||
return (
|
||||
<div>
|
||||
<h4>Select key</h4>
|
||||
<ul>
|
||||
{ aggregateKey.map(key =>
|
||||
<label><li><input
|
||||
type="checkbox"
|
||||
name="aggregateKey"
|
||||
value={key}
|
||||
checked={key === props.getKey() ? true : false}
|
||||
onClick={() => props.setKey(key)}
|
||||
/>{key}</li></label>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Accessor, Component, Setter } from "solid-js";
|
||||
import { resultOrder, ResultOrderKeys } from "../../consts";
|
||||
|
||||
export const SelectOrder: Component<{
|
||||
getter: Accessor<ResultOrderKeys>,
|
||||
setter: Setter<ResultOrderKeys>,
|
||||
}> = ({ getter, setter }) => {
|
||||
return (
|
||||
<div>
|
||||
Order by:
|
||||
<select onChange={e => setter(e.target.value as ResultOrderKeys)}>
|
||||
{ Object.keys(resultOrder).map(orderKey =>
|
||||
<option
|
||||
value={orderKey}
|
||||
selected={orderKey === getter()}
|
||||
>
|
||||
{orderKey}
|
||||
</option>
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
import moment from "moment";
|
||||
|
||||
export function formatDateString(str: string): string {
|
||||
const date = moment(new Date(str));
|
||||
switch (str.length) {
|
||||
// year: 2023
|
||||
case 4: return date.format("yyyy");
|
||||
// year-month: 2023-06
|
||||
case 7: return date.format("yyyy-MM");
|
||||
// year-month-day: 2023-06-23
|
||||
case 10: return date.format("yyyy-MM-DD");
|
||||
// year-month-day hour:minute 2023-06-23T23:59
|
||||
default: return date.format("HH:mm");
|
||||
}
|
||||
}
|
||||
|
||||
export interface QueryResult {
|
||||
query: DataQuery;
|
||||
result: {[key: string]: any};
|
||||
}
|
||||
|
||||
export interface DataQuery {
|
||||
cities: string[];
|
||||
field: string;
|
||||
granularity: string;
|
||||
key: string;
|
||||
}
|
||||
|
||||
export function isQueryResult(variable: any): variable is QueryResult {
|
||||
return variable
|
||||
&& variable.query
|
||||
&& isDataQuery(variable.query)
|
||||
&& variable.result
|
||||
&& (
|
||||
typeof variable.result === "object"
|
||||
&& !Array.isArray(variable.result)
|
||||
&& variable.result !== null
|
||||
);
|
||||
}
|
||||
|
||||
export function isDataQuery(variable: any): variable is DataQuery {
|
||||
return variable &&
|
||||
Array.isArray(variable.cities) &&
|
||||
typeof variable.field === 'string' &&
|
||||
typeof variable.granularity === 'string' &&
|
||||
typeof variable.key === 'string';
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Component, Match, Switch } from "solid-js";
|
||||
|
||||
import { CityChart } from "../../../components/chart/CityChart";
|
||||
import { DataQuery } from "../helpers";
|
||||
|
||||
export const GridResult: Component<{ city: string, query: DataQuery, result: any }> = ({ city, query, result }) => {
|
||||
return (
|
||||
<div class="item">
|
||||
<h4>{ city }</h4>
|
||||
<Switch fallback={<p>{ result }</p>}>
|
||||
<Match when={query.field === "phenomena"}>
|
||||
{ result.join(", ") }
|
||||
</Match>
|
||||
<Match when={isDateNumber(result)}>
|
||||
<CityChart city={()=>city} data={()=>result} query={()=>query}/>
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function isDateNumber(value: unknown): value is [string, number | null][] {
|
||||
if (!Array.isArray(value)) return false;
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
let item = value[i];
|
||||
if (
|
||||
!Array.isArray(item)
|
||||
|| item.length !== 2
|
||||
|| typeof item[0] !== "string"
|
||||
|| (typeof item[1] !== "number" && item[1] !== null)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import moment from 'moment'
|
||||
import { Accessor, Component, createResource, createSignal } from "solid-js";
|
||||
|
||||
import "../../../css/Result.css"
|
||||
import { FETCH_DELAY_MS, apiHost } from "../../../consts";
|
||||
import { isQueryResult } from "../helpers";
|
||||
import { LoadingSpinner } from "../../../components/spinner/LoadingSpinner";
|
||||
import { ResultView } from "./ResultView";
|
||||
|
||||
export const QueryView: Component<{
|
||||
getCities: Accessor<Set<string>>,
|
||||
getStart: Accessor<Date>,
|
||||
getEnd: Accessor<Date>,
|
||||
getField: Accessor<string>,
|
||||
getKey: Accessor<string>,
|
||||
getGranularity: Accessor<string>,
|
||||
}> = (props) => {
|
||||
const cities = () => [...props.getCities()].join(",");
|
||||
const queryStart = () => moment(props.getStart()).format("YYYYMMDD_HHmm");
|
||||
const queryEnd = () => moment(props.getEnd()).format("YYYYMMDD_HHmm");
|
||||
const [getTimestamp, setTimestamp] = createSignal<number>(0);
|
||||
|
||||
const fetchQuery = async (timestamp: number) => {
|
||||
if (cities() === "") return new Error("ERROR: Select cities!");
|
||||
await new Promise(resolve => setTimeout(resolve, FETCH_DELAY_MS))
|
||||
const response = await fetch(`${apiHost}/api/query/city/${cities()}/${queryStart()}-${queryEnd()}/${props.getGranularity()}/${props.getField()}/${props.getKey()}`);
|
||||
const json = await response.json();
|
||||
return json;
|
||||
}
|
||||
|
||||
const [queryResource] = createResource(getTimestamp, fetchQuery);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
type="button"
|
||||
class="primary"
|
||||
value="Query Data"
|
||||
onClick={() => setTimestamp(Date.now())}
|
||||
/>
|
||||
{ queryResource.loading && <LoadingSpinner text="Loading query" /> }
|
||||
{ queryResource.error && (
|
||||
<div>Error while querying: ${queryResource.error}</div>
|
||||
)}
|
||||
{ queryResource() && queryResource() instanceof Error &&
|
||||
<div>{ queryResource().message }</div>
|
||||
}
|
||||
{ queryResource() && isQueryResult( queryResource() ) &&
|
||||
<ResultView result={queryResource} />
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { Component, createSignal, JSXElement, Resource, Show } from "solid-js";
|
||||
|
||||
import "../../../css/Result.css"
|
||||
import { ResultKeyVal, resultOrder, ResultOrderKeys } from "../../../consts";
|
||||
import { SelectOrder } from "../SelectOrder";
|
||||
import { QueryResult } from "../helpers";
|
||||
import { MapView } from "../../../components/map/MapView";
|
||||
import { GridResult } from "./GridResult";
|
||||
|
||||
type ResultView = "grid" | "list" | "map_1920x1080" | "map_1920x1080_wind" | "map_3840x1440" | "map_3840x1440_wind";
|
||||
const resultViews: Array<ResultView> = ["grid", "list", "map_1920x1080", "map_1920x1080_wind", "map_3840x1440", "map_3840x1440_wind"];
|
||||
|
||||
export const ResultView: Component<{
|
||||
result: Resource<QueryResult>
|
||||
}> = ({ result: resultResource }) => {
|
||||
const [getOrderKey, setOrderKey] = createSignal<ResultOrderKeys>("A -> Z");
|
||||
const [getResultView, setResultView] = createSignal<ResultView>("grid");
|
||||
|
||||
const cityData = () =>
|
||||
Object.entries(resultResource()!.result)
|
||||
.map(keyVal => [...keyVal] as ResultKeyVal)
|
||||
.sort((a, b) => resultOrder[getOrderKey()](a, b));
|
||||
|
||||
function showViewButton(viewName: string): boolean {
|
||||
if (!resultResource()) return false;
|
||||
if (["grid", "list"].includes(viewName) && resultResource()!.query.field === "phenomena") return true;
|
||||
if (["grid", "list"].includes(viewName) && resultResource()!.query.key !== "List") return true;
|
||||
if (!["grid", "list"].includes(viewName) && resultResource()!.query.key === "List") return false;
|
||||
if (resultResource()!.query.field === "phenomena") return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div class="resultTitle">
|
||||
<span>
|
||||
{resultViews.map(viewName =>
|
||||
<>
|
||||
<input
|
||||
type="button"
|
||||
class="secondary"
|
||||
value={viewName}
|
||||
disabled={!showViewButton(viewName)}
|
||||
onClick={() => setResultView(viewName)}
|
||||
/>
|
||||
|
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
<span style={{ visibility: ["grid", "list"].includes(getResultView()) ? "visible" : "hidden" }}>
|
||||
<SelectOrder getter={getOrderKey} setter={setOrderKey} />
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<Show when={getResultView() === "grid"}>
|
||||
<div class="grid-view">
|
||||
{cityData().map(([city, data]) =>
|
||||
<GridResult
|
||||
city={city}
|
||||
query={resultResource()!.query}
|
||||
result={data}
|
||||
/>)}
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={getResultView() === "list"}>
|
||||
<div class="list-view">
|
||||
{cityData().map(([city, data]) =>
|
||||
<GridResult
|
||||
city={city}
|
||||
query={resultResource()!.query}
|
||||
result={data}
|
||||
/>)}
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={getResultView() === "map_1920x1080"}>
|
||||
<MapView type="map_1920x1080" data={cityData} />
|
||||
</Show>
|
||||
<Show when={getResultView() === "map_1920x1080_wind"}>
|
||||
<MapView type="map_1920x1080_wind" data={cityData} />
|
||||
</Show>
|
||||
<Show when={getResultView() === "map_3840x1440"}>
|
||||
<MapView type="map_3840x1440" data={cityData} />
|
||||
</Show>
|
||||
<Show when={getResultView() === "map_3840x1440_wind"}>
|
||||
<MapView type="map_3840x1440_wind" data={cityData} />
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user