mirror of
https://github.com/hsjobeki/noogle.git
synced 2024-12-19 11:51:37 +03:00
Feature/function of the day (#9)
* add: seedrandom dependency * add: function of the day
This commit is contained in:
parent
f306eb3c78
commit
e4c612674c
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
1
components/emptyRecordsPlaceholder/index.tsx
Normal file
1
components/emptyRecordsPlaceholder/index.tsx
Normal file
@ -0,0 +1 @@
|
||||
export { EmptyRecordsPlaceholder } from "./emptyRecordsPlaceholder";
|
140
components/functionOfTheDay/functionOfTheDay.tsx
Normal file
140
components/functionOfTheDay/functionOfTheDay.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
};
|
1
components/functionOfTheDay/index.ts
Normal file
1
components/functionOfTheDay/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export {FunctionOfTheDay} from "./functionOfTheDay"
|
26
package-lock.json
generated
26
package-lock.json
generated
@ -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",
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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}
|
||||
|
Loading…
Reference in New Issue
Block a user