Feature/function of the day (#9)

* add: seedrandom dependency
* add: function of the day
This commit is contained in:
Johannes Kirschbauer 2023-01-20 13:08:20 +01:00 committed by GitHub
parent f306eb3c78
commit e4c612674c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 289 additions and 29 deletions

View File

@ -24,6 +24,8 @@ import ClearIcon from "@mui/icons-material/Clear";
import { NixType, nixTypes } from "../../types/nix";
import { Filter } from "../searchInput/searchInput";
import { useRouter } from "next/router";
import { FunctionOfTheDay } from "../functionOfTheDay";
import { EmptyRecordsPlaceholder } from "../emptyRecordsPlaceholder";
export type BasicListItem = {
item: React.ReactNode;
@ -31,10 +33,13 @@ export type BasicListItem = {
};
export type BasicListProps = BasicDataViewProps & {
handleFilter: (filter: Filter | ((curr: Filter) => Filter)) => void;
filter: Filter;
term: string;
selected?: string | null;
itemsPerPage: number;
};
type ViewMode = "explore" | "browse";
export function BasicList(props: BasicListProps) {
const {
items,
@ -43,11 +48,13 @@ export function BasicList(props: BasicListProps) {
handleSearch,
handleFilter,
selected = "",
filter,
term,
} = props;
// const [from, setFrom] = useState<NixType>("any");
// const [to, setTo] = useState<NixType>("any");
const [page, setPage] = useState<number>(1);
const [mode, setMode] = useState<ViewMode>("explore");
const router = useRouter();
useEffect(() => {
const { query } = router;
@ -57,7 +64,6 @@ export function BasicList(props: BasicListProps) {
setPage(page);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [router]);
@ -67,8 +73,6 @@ export function BasicList(props: BasicListProps) {
return items.slice(startIdx, endIdx);
}, [page, items, itemsPerPage]);
const [searchTerm, setSearchTerm] = useState("");
const handlePageChange = (
_event: React.ChangeEvent<unknown>,
value: number
@ -77,16 +81,23 @@ export function BasicList(props: BasicListProps) {
};
const _handleFilter = (filter: Filter | ((curr: Filter) => Filter)) => {
setMode("browse");
handleFilter(filter);
setPage(1);
};
const _handleSearch = (term: string) => {
setMode("browse");
handleSearch && handleSearch(term);
setSearchTerm(term);
setPage(1);
};
const showFunctionExplore =
mode === "explore" &&
filter.to === "any" &&
filter.from === "any" &&
term === "";
return (
<Stack>
<SearchInput
@ -96,26 +107,45 @@ export function BasicList(props: BasicListProps) {
page={page}
clearSearch={() => _handleSearch("")}
/>
{showFunctionExplore ? (
<FunctionOfTheDay handleClose={() => setMode("browse")} />
) : (
<List aria-label="basic-list" sx={{ pt: 0 }}>
{items.length ? (
pageItems.map(({ item, key }, idx) => (
<Box key={`${key}-${idx}`}>
<ListItem sx={{ px: 0 }} key={key} aria-label={`item-${key}`}>
{item}
</ListItem>
</Box>
))
) : (
<Box sx={{ mt: 3 }}>
<EmptyRecordsPlaceholder
CardProps={{
sx: { backgroundColor: "inherit" },
}}
title={"No search results found"}
subtitle={
"Maybe the function does not exist or is not documented."
}
/>
</Box>
)}
</List>
)}
<List aria-label="basic-list" sx={{ pt: 0 }}>
{pageItems.map(({ item, key }, idx) => (
<Box key={`${key}-${idx}`}>
<ListItem sx={{ px: 0 }} key={key} aria-label={`item-${key}`}>
{item}
</ListItem>
</Box>
))}
</List>
<Pagination
hideNextButton
hidePrevButton
sx={{ display: "flex", justifyContent: "center", mt: 1, mb: 10 }}
count={Math.ceil(items.length / itemsPerPage) || 1}
color="primary"
page={page}
onChange={handlePageChange}
/>
{Math.ceil(items.length / itemsPerPage) > 0 && !showFunctionExplore && (
<Pagination
hideNextButton
hidePrevButton
sx={{ display: "flex", justifyContent: "center", mt: 1, mb: 10 }}
count={Math.ceil(items.length / itemsPerPage) || 1}
color="primary"
page={page}
onChange={handlePageChange}
/>
)}
</Stack>
);
}

View File

@ -0,0 +1,61 @@
import { Card, CardContent, CardProps, Typography } from "@mui/material";
import InboxIcon from "@mui/icons-material/Inbox";
interface EmptyRecordsPlaceholderProps {
title: string;
subtitle?: string;
icon?: React.ReactNode;
CardProps?: CardProps;
}
export const EmptyRecordsPlaceholder = (
props: EmptyRecordsPlaceholderProps
) => {
const { title, subtitle, icon, CardProps = {} } = props;
return (
<Card
elevation={0}
sx={{
pt: 4,
my: 3,
width: "100%",
border: "none",
}}
{...CardProps}
>
<CardContent
sx={{
backgroundColor: "inherit",
display: "flex",
justifyContent: "center",
alignItems: "center",
flexDirection: "column",
height: "100%",
}}
>
<Typography variant="h2" component="div">
{icon || (
<InboxIcon fontSize={"inherit"} sx={{ color: "text.secondary" }} />
)}
</Typography>
<Typography
variant={"subtitle1"}
color="text.secondary"
aria-label={"placeholder-title"}
>
{title}
</Typography>
<Typography
variant={"subtitle2"}
color="text.secondary"
aria-label={"placeholder-subtitle"}
>
{subtitle}
</Typography>
</CardContent>
</Card>
);
};

View File

@ -0,0 +1 @@
export { EmptyRecordsPlaceholder } from "./emptyRecordsPlaceholder";

View File

@ -0,0 +1,140 @@
import {
Button,
Card,
CardActions,
CardContent,
CardHeader,
Divider,
IconButton,
useTheme,
} from "@mui/material";
import { useMemo, useState } from "react";
import seedrandom from "seedrandom";
import { data } from "../../models/data";
import { DocItem } from "../../types/nix";
import { Preview } from "../preview/preview";
import ClearIcon from "@mui/icons-material/Clear";
const date = new Date();
const dayOfYear = (date: Date) => {
const diff = Number(date) - Number(new Date(date.getFullYear(), 0, 0));
return Math.floor(diff / 1000 / 60 / 60 / 24);
};
const seed = dayOfYear(date).toString() + date.getFullYear().toString();
const rng = seedrandom(seed);
function getRandomIntInclusive(
min: number,
max: number,
generator: () => number
) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(generator() * (max - min + 1) + min); // The maximum is inclusive and the minimum is inclusive
}
// const todaysFunction = data.at(
const todaysIdx = getRandomIntInclusive(0, data.length - 1, rng);
// ) as DocItem;
interface FunctionOfTheDayProps {
handleClose: () => void;
}
export const FunctionOfTheDay = (props: FunctionOfTheDayProps) => {
const { handleClose } = props;
const {
palette: { info, error },
} = useTheme();
const [idx, setIdx] = useState<number>(todaysIdx);
const slectedFunction = useMemo(() => data.at(idx) as DocItem, [idx]);
const setNext = () => {
setIdx((curr) => {
if (curr >= data.length - 1) {
return data.length - 1;
}
return curr + 1;
});
};
const setPrev = () => {
setIdx((curr) => {
if (curr <= 0) {
return 0;
}
return curr - 1;
});
};
const setRandom = () => {
setIdx(getRandomIntInclusive(0, data.length - 1, Math.random));
};
const setFunctionOfTheDay = () => {
setIdx(todaysIdx);
};
return (
<>
<Card
elevation={0}
sx={{
my: 5,
borderImageSlice: 1,
borderImageSource:
idx === todaysIdx
? `linear-gradient(to left, ${info.light},${error.main})`
: `linear-gradient(to left, ${info.light},${info.dark})`,
borderWidth: 4,
borderStyle: "solid",
}}
>
<CardHeader
title={
idx === todaysIdx
? "Function of the day"
: "Did you know the function?"
}
action={
<IconButton onClick={() => handleClose()}>
<ClearIcon fontSize="large" />
</IconButton>
}
/>
<CardContent>
<Preview docItem={slectedFunction} closeComponent={<></>} />
</CardContent>
<CardActions
sx={{
display: "flex",
justifyContent: "space-evenly",
}}
>
<Button
onClick={() => setPrev()}
disabled={idx === 0}
sx={{ width: "100%" }}
>
Prev
</Button>
<Divider flexItem orientation="vertical" />
<Button sx={{ width: "100%" }} onClick={() => setRandom()}>
Random
</Button>
<Button sx={{ width: "100%" }} onClick={() => setFunctionOfTheDay()}>
Todays function
</Button>
<Divider flexItem orientation="vertical" />
<Button
sx={{ width: "100%" }}
onClick={() => setNext()}
disabled={idx === data.length - 1}
>
Next
</Button>
</CardActions>
</Card>
</>
);
};

View File

@ -0,0 +1 @@
export {FunctionOfTheDay} from "./functionOfTheDay"

26
package-lock.json generated
View File

@ -26,11 +26,13 @@
"react-highlight": "^0.15.0",
"react-markdown": "^8.0.4",
"rehype-highlight": "^6.0.0",
"seedrandom": "^3.0.5",
"typescript": "4.8.4",
"usehooks-ts": "^2.9.1"
},
"devDependencies": {
"@types/react-highlight": "^0.12.5"
"@types/react-highlight": "^0.12.5",
"@types/seedrandom": "^3.0.4"
}
},
"node_modules/@ampproject/remapping": {
@ -1378,6 +1380,12 @@
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
},
"node_modules/@types/seedrandom": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-3.0.4.tgz",
"integrity": "sha512-/rWdxeiuZenlawrHU+XV6ZHMTKOqrC2hMfeDfLTIWJhDZP5aVqXRysduYHBbhD7CeJO6FJr/D2uBVXB7GT6v7w==",
"dev": true
},
"node_modules/@types/unist": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
@ -4519,6 +4527,11 @@
"loose-envify": "^1.1.0"
}
},
"node_modules/seedrandom": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
"integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg=="
},
"node_modules/semver": {
"version": "7.3.8",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
@ -6039,6 +6052,12 @@
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
},
"@types/seedrandom": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-3.0.4.tgz",
"integrity": "sha512-/rWdxeiuZenlawrHU+XV6ZHMTKOqrC2hMfeDfLTIWJhDZP5aVqXRysduYHBbhD7CeJO6FJr/D2uBVXB7GT6v7w==",
"dev": true
},
"@types/unist": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
@ -8123,6 +8142,11 @@
"loose-envify": "^1.1.0"
}
},
"seedrandom": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
"integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg=="
},
"semver": {
"version": "7.3.8",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",

View File

@ -28,10 +28,12 @@
"react-highlight": "^0.15.0",
"react-markdown": "^8.0.4",
"rehype-highlight": "^6.0.0",
"seedrandom": "^3.0.5",
"typescript": "4.8.4",
"usehooks-ts": "^2.9.1"
},
"devDependencies": {
"@types/react-highlight": "^0.12.5"
"@types/react-highlight": "^0.12.5",
"@types/seedrandom": "^3.0.4"
}
}

View File

@ -64,10 +64,11 @@ export default function FunctionsPage() {
};
}
);
return (
<Box sx={{ ml: { xs: 0, md: 2 } }}>
<Box sx={{ ml: { xs: 0, md: 2 }, mb: 5 }}>
<BasicList
term={term}
filter={filter}
selected={selected}
itemsPerPage={8}
items={preRenderedItems}