Skip to content

Commit

Permalink
Merge pull request #9 from reallistic/update_ui
Browse files Browse the repository at this point in the history
Fixes #1 and #2. Make the objects page searchable and show number of …
  • Loading branch information
reallistic committed Jun 11, 2020
2 parents 190c1b6 + 07212ce commit b2a79ac
Show file tree
Hide file tree
Showing 8 changed files with 293 additions and 266 deletions.
19 changes: 0 additions & 19 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@
"webpack-merge": "^4.2.2"
},
"dependencies": {
"@babel/polyfill": "^7.10.1",
"@fortawesome/fontawesome-free": "^5.13.0",
"bulma": "^0.8.2",
"highcharts": "^8.1.0",
"highcharts-react-official": "^3.0.0",
Expand Down
130 changes: 32 additions & 98 deletions pyloot-web/HistoryPage.jsx
Original file line number Diff line number Diff line change
@@ -1,109 +1,43 @@
import React, { useMemo, useState } from "react";
import React from "react";

import { Link } from "react-router-dom";

import { useDataLoader } from "./dataLoader";
import { SimpleLineChart } from "./LineChart";
import { objectGroupName, useQuery } from "./utils";
import { Pagination } from "./Pagination";
import { objectGroupName } from "./utils";
import { ListPage } from "./ListPage";

const DEFAULT_PAGE_LIMIT = 100;

export function HistoryPage() {
const queryParams = useQuery();

let pageLimit = parseInt(queryParams.get("pageLimit"));
if (isNaN(pageLimit)) {
pageLimit = DEFAULT_PAGE_LIMIT;
}
const [apiData, loading] = useDataLoader("api/history", [], true);
const [page, setPage] = useState(0);
const onPageUpdate = useMemo(() => {
return (nextPage) => {
setPage(nextPage);
};
}, []);
function historySearchFunction(search, item) {
const groupName = objectGroupName(item);
return groupName.toLowerCase().indexOf(search.toLowerCase()) !== -1;
}

const [search, setSearch] = useState("");
const onSearchHistory = useMemo(() => {
return (event) => {
setSearch(event.target.value);
};
}, []);
function historyItemKey(item) {
return objectGroupName(item);
}

const data = apiData.filter((historyItem) => {
if (search == null || search.trim() === "") {
return true;
}
const groupName = objectGroupName(historyItem);
return groupName.toLowerCase().indexOf(search.toLowerCase()) !== -1;
});
function HistoryPageItem({ item }) {
const groupName = objectGroupName(item);
return (
<div className={"column is-one-quarter"} title={groupName} key={groupName}>
<h5 className={"title is-5"}>
<Link to={`/objects/${groupName}`}>{groupName}</Link>
</h5>
<SimpleLineChart data={item["counts"]} />
</div>
);
}

const totalPages = Math.ceil(data.length / pageLimit);
const start = page * pageLimit;
const end = Math.min(start + pageLimit, data.length);
let showLoading = loading && data.length === 0;
let showEmpty = !loading && data.length === 0;
export function HistoryPage() {
return (
<section className="history">
<div className="level">
<div className="level-left">
<div className="level-item">
<p className="subtitle is-4">Object counts by type</p>
</div>
<div className="level-item">
<input
className="input"
type="text"
placeholder="Search"
onChange={onSearchHistory}
onKeyUp={onSearchHistory}
value={search}
/>
</div>
</div>
<div className="level-right">
<div className="level-item">
<Pagination
totalPages={totalPages}
page={page}
onPageUpdate={onPageUpdate}
/>
</div>
</div>
</div>
{showLoading ? (
<span className="loading">Loading history....</span>
) : null}
{showEmpty ? <span className="loading">Empty history</span> : null}
<div className={"columns is-mobile is-multiline"}>
{data.slice(start, end).map((historyItem) => {
const groupName = objectGroupName(historyItem);
return (
<div
className={"column is-one-quarter"}
title={groupName}
key={groupName}
>
<h5 className={"title is-5"}>
<Link to={`/objects/${groupName}`}>{groupName}</Link>
</h5>
<SimpleLineChart data={historyItem["counts"]} />
</div>
);
})}
</div>
<div className="level" style={{flexDirection: "column"}}>
<div className="level-right" style={{alignSelf: "flex-end"}}>
<div className="level-item">
<Pagination
totalPages={totalPages}
page={page}
onPageUpdate={onPageUpdate}
/>
</div>
</div>
</div>
</section>
<ListPage
title="Object counts by type"
emptyMessage="No history to show. Maybe loosen the search?"
listItemComponent={HistoryPageItem}
loadingMessage="Loading history...."
searchFilterFunc={historySearchFunction}
url="api/history"
keyFunc={historyItemKey}
listContainerClassName="columns is-mobile is-multiline"
/>
);
}
145 changes: 145 additions & 0 deletions pyloot-web/ListPage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import React, { useEffect, useMemo, useState } from "react";

import { useQuery } from "./utils";
import { useDataLoader } from "./dataLoader";
import { Pagination } from "./Pagination";

const DEFAULT_PAGE_LIMIT = 100;

export function ListPage({
url,
listItemComponent,
title,
searchFilterFunc,
loadingMessage,
emptyMessage,
keyFunc,
listContainerClassName
}) {
const queryParams = useQuery();

let pageLimit = parseInt(queryParams.get("pageLimit"));
if (isNaN(pageLimit)) {
pageLimit = DEFAULT_PAGE_LIMIT;
}
const [apiData, loading] = useDataLoader(url, [], true);
const [page, setPage] = useState(0);
const onPageUpdate = useMemo(() => {
return (nextPage) => {
setPage(nextPage);
};
}, []);

const [search, setSearch] = useState("");
const [searchInput, setSearchInput] = useState("");
const onSearchHistory = useMemo(() => {
return (event) => {
const value = event.target.value;
console.log("calling setSearchInput", value);
setSearchInput(value);
};
}, []);

useEffect(() => {
let searchDebounceTimeout = null;
if (search !== searchInput) {
console.log("searchInput/search mismatch", searchInput, search);
searchDebounceTimeout = setTimeout(() => {
console.log("calling setSearch", searchInput);
setSearch(searchInput);
searchDebounceTimeout = null;
}, 1000);
}
return () => {
if (searchDebounceTimeout != null) {
console.log("clearing searchDebounceTimeout");
clearTimeout(searchDebounceTimeout);
}
};
}, [search, searchInput]);

const data = apiData.filter((item, index) => {
if (search == null || search.trim() === "") {
return true;
}
return searchFilterFunc(search, item, index);
});

const totalPages = Math.ceil(data.length / pageLimit);
const start = page * pageLimit;
const end = Math.min(start + pageLimit, data.length);
const dataSlice = data.slice(start, end);

let showLoading = loading && data.length === 0;
let showEmpty = !loading && dataSlice.length === 0;
return (
<section className="list-page">
<progress
className="progress is-small is-info"
style={{ visibility: loading ? "visible" : "hidden", marginBottom: "0.25rem" }}
max="100"
>
Loading
</progress>
<div className="level">
<div className="level-left">
<div className="level-item">
<p className="subtitle is-4">{title}</p>
</div>
<div className="level-item">
<input
className="input"
type="text"
placeholder="Search"
onChange={onSearchHistory}
onKeyUp={onSearchHistory}
value={searchInput}
/>
</div>
</div>
<div className="level-right">
<div className="level-item">
<small>
{Math.min(start + 1, data.length)}-{start + dataSlice.length} of{" "}
{data.length}
</small>
&nbsp;
<Pagination
totalPages={totalPages}
page={page}
onPageUpdate={onPageUpdate}
/>
</div>
</div>
</div>
{showLoading ? (
<span className="loading">
{loadingMessage || "Loading history...."}
</span>
) : null}
{showEmpty ? (
<span className="loading">{emptyMessage || "Empty history"}</span>
) : null}
<div className={listContainerClassName}>
{dataSlice.map((item, index) =>
React.createElement(listItemComponent, {
item,
index,
key: (keyFunc && keyFunc(item)) || index,
})
)}
</div>
<div className="level" style={{ flexDirection: "column" }}>
<div className="level-right" style={{ alignSelf: "flex-end" }}>
<div className="level-item">
<Pagination
totalPages={totalPages}
page={page}
onPageUpdate={onPageUpdate}
/>
</div>
</div>
</div>
</section>
);
}
Loading

0 comments on commit b2a79ac

Please sign in to comment.