mirror of
https://github.com/hsjobeki/noogle.git
synced 2024-11-24 06:35:05 +03:00
improve preview & responsive ness
This commit is contained in:
parent
521fb6e03f
commit
ae81eb869b
@ -20,7 +20,7 @@ import FormControlLabel from "@mui/material/FormControlLabel";
|
||||
import FormControl from "@mui/material/FormControl";
|
||||
import FormLabel from "@mui/material/FormLabel";
|
||||
import ClearIcon from "@mui/icons-material/Clear";
|
||||
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
||||
|
||||
import { NixType, nixTypes } from "../../types/nix";
|
||||
|
||||
export type BasicListItem = {
|
||||
@ -28,76 +28,11 @@ export type BasicListItem = {
|
||||
key: string;
|
||||
};
|
||||
export type BasicListProps = BasicDataViewProps & {
|
||||
handleFilter: (t: NixType, mode: "from" | "to") => void;
|
||||
preview: React.ReactNode;
|
||||
handleFilter: (filter: { from: NixType; to: NixType }) => void;
|
||||
selected?: string | null;
|
||||
itemsPerPage: number;
|
||||
};
|
||||
|
||||
interface SelectOptionProps {
|
||||
label: string;
|
||||
handleChange: (value: string) => void;
|
||||
|
||||
options: {
|
||||
value: string;
|
||||
label: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
const SelectOption = (props: SelectOptionProps) => {
|
||||
const { label, handleChange, options } = props;
|
||||
const [value, setValue] = React.useState<NixType>("any");
|
||||
|
||||
const _handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newVal = (event.target as HTMLInputElement).value as NixType;
|
||||
setValue(newVal);
|
||||
handleChange(newVal);
|
||||
};
|
||||
const handleClear = () => {
|
||||
setValue("any");
|
||||
handleChange("any");
|
||||
};
|
||||
|
||||
return (
|
||||
<FormControl
|
||||
sx={{
|
||||
// pl: 1.5,
|
||||
flexDirection: "row",
|
||||
}}
|
||||
>
|
||||
<FormLabel sx={{ width: "11rem", wordWrap: "unset" }}>
|
||||
<Typography>
|
||||
<IconButton aria-label="clear-button" onClick={handleClear}>
|
||||
<ClearIcon />
|
||||
</IconButton>
|
||||
{label}
|
||||
</Typography>
|
||||
</FormLabel>
|
||||
|
||||
<RadioGroup
|
||||
sx={{
|
||||
// pl: 1.5,
|
||||
width: "100%",
|
||||
"&.MuiFormGroup-root": {
|
||||
flexDirection: "row",
|
||||
},
|
||||
}}
|
||||
value={value}
|
||||
onChange={_handleChange}
|
||||
>
|
||||
{options.map(({ value, label }) => (
|
||||
<FormControlLabel
|
||||
key={value}
|
||||
value={value}
|
||||
control={<Radio />}
|
||||
label={label}
|
||||
/>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
export function BasicList(props: BasicListProps) {
|
||||
const {
|
||||
items,
|
||||
@ -105,7 +40,6 @@ export function BasicList(props: BasicListProps) {
|
||||
itemsPerPage,
|
||||
handleSearch,
|
||||
handleFilter,
|
||||
preview,
|
||||
selected = "",
|
||||
} = props;
|
||||
// const [from, setFrom] = useState<NixType>("any");
|
||||
@ -128,8 +62,8 @@ export function BasicList(props: BasicListProps) {
|
||||
setPage(value);
|
||||
};
|
||||
|
||||
const _handleFilter = (t: NixType, mode: "from" | "to") => {
|
||||
handleFilter(t, mode);
|
||||
const _handleFilter = (filter: { from: NixType; to: NixType }) => {
|
||||
handleFilter(filter);
|
||||
setPage(1);
|
||||
};
|
||||
|
||||
@ -142,76 +76,15 @@ export function BasicList(props: BasicListProps) {
|
||||
return (
|
||||
<Stack>
|
||||
<SearchInput
|
||||
handleFilter={_handleFilter}
|
||||
placeholder="search nix functions"
|
||||
handleSearch={_handleSearch}
|
||||
clearSearch={() => _handleSearch("")}
|
||||
/>
|
||||
<Box>
|
||||
{/* <Stack direction="row"> */}
|
||||
<Grid container>
|
||||
<Grid item xs={12} md={5}>
|
||||
<SelectOption
|
||||
label="from type"
|
||||
handleChange={(value) => {
|
||||
_handleFilter(value as NixType, "from");
|
||||
}}
|
||||
options={nixTypes.map((v) => ({ value: v, label: v }))}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
md={2}
|
||||
sx={{
|
||||
display: {
|
||||
md: "flex",
|
||||
xs: "none",
|
||||
},
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
// sx={{
|
||||
// width: "100%",
|
||||
// display: "flex",
|
||||
// justifyContent: "center",
|
||||
// alignItems: "center",
|
||||
// // flexDirection: "column",
|
||||
// }}
|
||||
>
|
||||
<ChevronRightIcon />
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={5}>
|
||||
<SelectOption
|
||||
label="to type"
|
||||
handleChange={(value) => {
|
||||
_handleFilter(value as NixType, "to");
|
||||
}}
|
||||
options={nixTypes.map((v) => ({ value: v, label: v }))}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
{/* </Stack> */}
|
||||
</Box>
|
||||
|
||||
<List aria-label="basic-list" sx={{ pt: 0 }}>
|
||||
{pageItems.map(({ item, key }, idx) => (
|
||||
<Box key={`${key}-${idx}`}>
|
||||
<Slide
|
||||
direction="up"
|
||||
in={key === selected}
|
||||
mountOnEnter
|
||||
unmountOnExit
|
||||
>
|
||||
<ListItem
|
||||
key={`${key}-preview`}
|
||||
aria-label={`item-${key}`}
|
||||
sx={{ px: 0 }}
|
||||
>
|
||||
{preview}
|
||||
</ListItem>
|
||||
{/* )} */}
|
||||
</Slide>
|
||||
<ListItem sx={{ px: 0 }} key={key} aria-label={`item-${key}`}>
|
||||
{item}
|
||||
</ListItem>
|
||||
|
@ -1,39 +1,74 @@
|
||||
import { ListItemText, Paper, Stack, Typography } from "@mui/material";
|
||||
import { useMemo } from "react";
|
||||
import { DocItem } from "../../types/nix";
|
||||
import { Preview } from "../preview/preview";
|
||||
|
||||
interface FunctionItemProps {
|
||||
selected: boolean;
|
||||
name: String;
|
||||
docItem: DocItem;
|
||||
handleClose: () => void;
|
||||
}
|
||||
export default function FunctionItem(props: FunctionItemProps) {
|
||||
const { name, docItem, selected } = props;
|
||||
const { fn_type, category } = docItem;
|
||||
const { name, docItem, selected, handleClose } = props;
|
||||
const { fn_type, category, description } = docItem;
|
||||
const descriptionPreview = useMemo(() => {
|
||||
const getFirstWords = (s: string) => {
|
||||
const indexOfDot = s.indexOf(".");
|
||||
if (indexOfDot) {
|
||||
return s.slice(0, indexOfDot + 1);
|
||||
}
|
||||
return s.split(" ").filter(Boolean).slice(0, 10).join(" ");
|
||||
};
|
||||
if (typeof description === "object") {
|
||||
const singleString = description.join("");
|
||||
return getFirstWords(singleString);
|
||||
} else if (description) {
|
||||
return getFirstWords(description);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}, [description]);
|
||||
return (
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
cursor: !selected ? "pointer" : "default",
|
||||
display: "flex",
|
||||
justifyContent: "left",
|
||||
px: 2,
|
||||
py: 1,
|
||||
color: selected ? "primary.main" : undefined,
|
||||
borderColor: selected ? "action.selected" : "none",
|
||||
borderColor: selected ? "primary.light" : "none",
|
||||
borderWidth: 1,
|
||||
borderStyle: selected ? "solid" : "none",
|
||||
"&:hover": {
|
||||
backgroundColor: "action.hover",
|
||||
},
|
||||
"&:hover": !selected
|
||||
? {
|
||||
backgroundColor: "action.hover",
|
||||
}
|
||||
: {},
|
||||
}}
|
||||
>
|
||||
<Stack>
|
||||
<ListItemText primary={name} secondary={category} />
|
||||
<Typography
|
||||
sx={{
|
||||
color: !fn_type ? "text.secondary" : "text.primary",
|
||||
}}
|
||||
>{`${fn_type || "No type yet provided"} `}</Typography>
|
||||
<Stack sx={{ width: "100%" }}>
|
||||
{!selected && (
|
||||
<>
|
||||
<ListItemText
|
||||
primary={`${
|
||||
category.includes(".nix") ? "lib" : "builtins"
|
||||
}.${name}`}
|
||||
secondary={category}
|
||||
/>
|
||||
<ListItemText secondary={descriptionPreview} />
|
||||
<Typography
|
||||
sx={{
|
||||
color: !fn_type ? "text.secondary" : "text.primary",
|
||||
}}
|
||||
>
|
||||
{`${fn_type || "No type yet provided"} `}
|
||||
</Typography>
|
||||
</>
|
||||
)}
|
||||
{selected && <Preview docItem={docItem} handleClose={handleClose} />}
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
|
@ -107,6 +107,8 @@ export function Layout(props: LayoutProps) {
|
||||
marginTop: "6em",
|
||||
maxHeight: "calc(100vh - 8em)",
|
||||
overflowY: "scroll",
|
||||
overflowX: "hidden",
|
||||
width: "100vw",
|
||||
}}
|
||||
>
|
||||
<Container sx={{ pt: 0, px: { xs: 0, md: 2 } }} maxWidth="xl">
|
||||
|
4
components/preview/preview.module.css
Normal file
4
components/preview/preview.module.css
Normal file
@ -0,0 +1,4 @@
|
||||
.hljs {
|
||||
padding: 0 !important;
|
||||
|
||||
}
|
@ -20,7 +20,7 @@ import { DocItem } from "../../types/nix";
|
||||
import CodeIcon from "@mui/icons-material/Code";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
|
||||
|
||||
import styles from "./preview.module.css";
|
||||
// import hljs from "highlight.js";
|
||||
|
||||
// needed for nextjs to import the classes of github theme
|
||||
@ -66,33 +66,45 @@ export const Preview = (props: PreviewProps) => {
|
||||
<Box
|
||||
sx={{
|
||||
p: 1,
|
||||
pt: 2,
|
||||
mt: 2,
|
||||
mb: -2,
|
||||
width: "100%",
|
||||
borderTop: "solid 1px",
|
||||
borderTopColor: "primary.main",
|
||||
overflow: "none",
|
||||
}}
|
||||
>
|
||||
{/* <Box sx={{ display: "flex" }}> */}
|
||||
<Typography variant="h2">{`${prefix}.${name}`}</Typography>
|
||||
{/* </Box> */}
|
||||
<Box
|
||||
sx={{
|
||||
display: { md: "flex", xs: "flex" },
|
||||
flexDirection: { md: "row", xs: "column-reverse" },
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{ wordWrap: "normal", lineBreak: "anywhere" }}
|
||||
>{`${prefix}.${name}`}</Typography>
|
||||
<Tooltip title="close details">
|
||||
<IconButton
|
||||
sx={{
|
||||
mx: { xs: "auto", md: 1 },
|
||||
left: { lg: "calc(50% - 2rem)", xs: "unset" },
|
||||
position: { lg: "absolute", xs: "relative" },
|
||||
}}
|
||||
size="small"
|
||||
onClick={() => handleClose()}
|
||||
>
|
||||
<ExpandLessIcon fontSize="large" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<List sx={{ width: "100%" }}>
|
||||
<ListItem>
|
||||
<Tooltip title="close details">
|
||||
<IconButton onClick={() => handleClose()}>
|
||||
<ExpandLessIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItem sx={{ flexDirection: { xs: "column", sm: "row" } }}>
|
||||
<ListItemIcon>
|
||||
<LocalLibraryIcon />
|
||||
<LocalLibraryIcon sx={{ m: "auto" }} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
alignSelf: "flex-start",
|
||||
}}
|
||||
primaryTypographyProps={{
|
||||
color: "text.secondary",
|
||||
@ -105,6 +117,7 @@ export const Preview = (props: PreviewProps) => {
|
||||
primary={"nixpkgs/" + category.replace("./", "")}
|
||||
secondary={
|
||||
<Container
|
||||
component={"div"}
|
||||
sx={{ ml: "0 !important", pl: "0 !important" }}
|
||||
maxWidth="lg"
|
||||
>
|
||||
@ -117,14 +130,15 @@ export const Preview = (props: PreviewProps) => {
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItem sx={{ flexDirection: { xs: "column", sm: "row" } }}>
|
||||
<ListItemIcon>
|
||||
<InputIcon />
|
||||
<InputIcon sx={{ m: "auto" }} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
alignSelf: "flex-start",
|
||||
}}
|
||||
primaryTypographyProps={{
|
||||
color: "text.secondary",
|
||||
@ -141,15 +155,17 @@ export const Preview = (props: PreviewProps) => {
|
||||
<ListItem
|
||||
sx={{
|
||||
backgroundColor: "background.paper",
|
||||
flexDirection: { xs: "column", sm: "row" },
|
||||
}}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<CodeIcon />
|
||||
<CodeIcon sx={{ m: "auto" }} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
alignSelf: "flex-start",
|
||||
}}
|
||||
disableTypography
|
||||
primary={
|
||||
@ -161,8 +177,10 @@ export const Preview = (props: PreviewProps) => {
|
||||
}
|
||||
secondary={
|
||||
finalExample ? (
|
||||
<Box sx={{ mt: -2, pl: 1.5 }}>
|
||||
<Highlight className="nix">{finalExample}</Highlight>
|
||||
<Box sx={{ mt: -2 }}>
|
||||
<Highlight className={`nix ${styles.hljs}`}>
|
||||
{finalExample}
|
||||
</Highlight>
|
||||
</Box>
|
||||
) : (
|
||||
<Typography
|
||||
|
@ -4,18 +4,92 @@ import InputBase from "@mui/material/InputBase";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import SearchIcon from "@mui/icons-material/Search";
|
||||
import ClearIcon from "@mui/icons-material/Clear";
|
||||
import { debounce } from "@mui/material";
|
||||
import {
|
||||
Box,
|
||||
debounce,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
FormLabel,
|
||||
Grid,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
||||
import { NixType, nixTypes } from "../../types/nix";
|
||||
|
||||
interface SelectOptionProps {
|
||||
label: string;
|
||||
handleChange: (value: string) => void;
|
||||
value: string;
|
||||
|
||||
options: {
|
||||
value: string;
|
||||
label: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
const SelectOption = (props: SelectOptionProps) => {
|
||||
const { label, handleChange, options, value } = props;
|
||||
|
||||
const _handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newVal = (event.target as HTMLInputElement).value as NixType;
|
||||
handleChange(newVal);
|
||||
};
|
||||
|
||||
return (
|
||||
<FormControl
|
||||
sx={{
|
||||
flexDirection: "row",
|
||||
}}
|
||||
>
|
||||
<FormLabel
|
||||
sx={{
|
||||
width: "7rem",
|
||||
wordWrap: "unset",
|
||||
margin: "auto",
|
||||
padding: 1,
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ minWidth: "max-content" }}>{label}</Typography>
|
||||
</FormLabel>
|
||||
|
||||
<RadioGroup
|
||||
sx={{
|
||||
width: "100%",
|
||||
"&.MuiFormGroup-root": {
|
||||
flexDirection: "row",
|
||||
},
|
||||
}}
|
||||
value={value}
|
||||
onChange={_handleChange}
|
||||
>
|
||||
{options.map(({ value, label }) => (
|
||||
<FormControlLabel
|
||||
key={value}
|
||||
value={value}
|
||||
control={<Radio />}
|
||||
label={label}
|
||||
/>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
export interface SearchInputProps {
|
||||
handleSearch: (term: string) => void;
|
||||
handleFilter: (filter: { to: NixType; from: NixType }) => void;
|
||||
clearSearch: () => void;
|
||||
placeholder: string;
|
||||
}
|
||||
|
||||
export function SearchInput(props: SearchInputProps) {
|
||||
const { handleSearch, clearSearch, placeholder } = props;
|
||||
const { handleSearch, clearSearch, placeholder, handleFilter } = props;
|
||||
|
||||
const [term, setTerm] = useState("");
|
||||
const [to, setTo] = useState<NixType>("any");
|
||||
const [from, setFrom] = useState<NixType>("any");
|
||||
|
||||
const handleSubmit = React.useRef((input: string) => {
|
||||
handleSearch(input);
|
||||
@ -25,8 +99,21 @@ export function SearchInput(props: SearchInputProps) {
|
||||
[handleSubmit]
|
||||
);
|
||||
|
||||
const _handleFilter = (t: NixType, mode: "from" | "to") => {
|
||||
console.log({ t, mode });
|
||||
if (mode === "to") {
|
||||
setTo(t);
|
||||
handleFilter({ to: t, from });
|
||||
} else {
|
||||
setFrom(t);
|
||||
handleFilter({ to, from: t });
|
||||
}
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
setTerm("");
|
||||
setFrom("any");
|
||||
setTo("any");
|
||||
clearSearch();
|
||||
};
|
||||
const handleChange = (
|
||||
@ -37,53 +124,95 @@ export function SearchInput(props: SearchInputProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper
|
||||
component="form"
|
||||
elevation={0}
|
||||
sx={{
|
||||
width: "100%",
|
||||
p: 1,
|
||||
my: 2,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
onSubmit={(e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
handleSubmit(term);
|
||||
}}
|
||||
>
|
||||
<IconButton aria-label="clear-button" onClick={handleClear}>
|
||||
<ClearIcon />
|
||||
</IconButton>
|
||||
<InputBase
|
||||
autoFocus
|
||||
<>
|
||||
<Paper
|
||||
component="form"
|
||||
elevation={0}
|
||||
sx={{
|
||||
ml: 1,
|
||||
flex: 1,
|
||||
backgroundColor: "paper.main",
|
||||
width: "100%",
|
||||
p: 1,
|
||||
my: 2,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
placeholder={placeholder}
|
||||
inputProps={{ "aria-label": "search-input" }}
|
||||
value={term}
|
||||
onChange={(e) => handleChange(e)}
|
||||
/>
|
||||
<IconButton
|
||||
type="submit"
|
||||
onClick={() => handleSubmit(term)}
|
||||
sx={{
|
||||
p: 1,
|
||||
bgcolor: "primary.dark",
|
||||
color: "common.white",
|
||||
"&:hover": {
|
||||
backgroundColor: "primary.main",
|
||||
opacity: [0.9, 0.8, 0.7],
|
||||
},
|
||||
onSubmit={(e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
handleSubmit(term);
|
||||
}}
|
||||
aria-label="search-button"
|
||||
>
|
||||
<SearchIcon />
|
||||
</IconButton>
|
||||
</Paper>
|
||||
<IconButton aria-label="clear-button" onClick={handleClear}>
|
||||
<ClearIcon />
|
||||
</IconButton>
|
||||
<InputBase
|
||||
autoFocus
|
||||
sx={{
|
||||
ml: 1,
|
||||
flex: 1,
|
||||
backgroundColor: "paper.main",
|
||||
p: 1,
|
||||
}}
|
||||
placeholder={placeholder}
|
||||
inputProps={{ "aria-label": "search-input" }}
|
||||
value={term}
|
||||
onChange={(e) => handleChange(e)}
|
||||
/>
|
||||
<IconButton
|
||||
type="submit"
|
||||
onClick={() => handleSubmit(term)}
|
||||
sx={{
|
||||
p: 1,
|
||||
bgcolor: "primary.dark",
|
||||
color: "common.white",
|
||||
"&:hover": {
|
||||
backgroundColor: "primary.main",
|
||||
opacity: [0.9, 0.8, 0.7],
|
||||
},
|
||||
}}
|
||||
aria-label="search-button"
|
||||
>
|
||||
<SearchIcon />
|
||||
</IconButton>
|
||||
</Paper>
|
||||
<Box>
|
||||
<Grid container>
|
||||
<Grid item xs={12} md={5}>
|
||||
<SelectOption
|
||||
value={from}
|
||||
label="from type"
|
||||
handleChange={(value) => {
|
||||
_handleFilter(value as NixType, "from");
|
||||
}}
|
||||
options={nixTypes.map((v) => ({ value: v, label: v }))}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
md={2}
|
||||
sx={{
|
||||
display: {
|
||||
md: "flex",
|
||||
xs: "none",
|
||||
},
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Typography>
|
||||
<ChevronRightIcon />
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={5}>
|
||||
<SelectOption
|
||||
value={to}
|
||||
label="to type"
|
||||
handleChange={(value) => {
|
||||
_handleFilter(value as NixType, "to");
|
||||
}}
|
||||
options={nixTypes.map((v) => ({ value: v, label: v }))}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
183
pages/index.tsx
183
pages/index.tsx
@ -3,7 +3,6 @@ import { useState, useMemo } from "react";
|
||||
import { Box } from "@mui/material";
|
||||
import FunctionItem from "../components/functionItem/functionItem";
|
||||
import { NixType, nixTypes, MetaData, DocItem } from "../types/nix";
|
||||
import { Preview } from "../components/preview/preview";
|
||||
import nixLibs from "../models/lib.json";
|
||||
import nixBuiltins from "../models/builtins.json";
|
||||
|
||||
@ -36,69 +35,81 @@ const search =
|
||||
});
|
||||
};
|
||||
|
||||
const preProcess = (a: string | undefined) => {
|
||||
if (a) {
|
||||
let b = a;
|
||||
if (a.includes("::")) {
|
||||
b = a.split("::")[1];
|
||||
function getTypes(
|
||||
fnName: string,
|
||||
fnType: string | undefined
|
||||
): { args: NixType[]; types: NixType[] } {
|
||||
if (fnType) {
|
||||
let cleanType = fnType.replace(/ /g, "").replace(`${fnName}::`, "");
|
||||
const tokens = cleanType
|
||||
.split(/(::|->|\[|\]|\{|\}|\(|\))/gm)
|
||||
.filter(Boolean);
|
||||
const lastArrowIdx = tokens.lastIndexOf("->");
|
||||
if (lastArrowIdx) {
|
||||
// Function has at least on return value
|
||||
const interpretToken = (token: string) => {
|
||||
if (token === "(" || token === ")") {
|
||||
return "function" as NixType;
|
||||
} else if (token === "[" || token === "]") {
|
||||
return "list" as NixType;
|
||||
} else if (token === "{" || token === "}") {
|
||||
return "attrset" as NixType;
|
||||
} else if (nixTypes.includes(token.toLowerCase() as NixType)) {
|
||||
return token.toLowerCase() as NixType;
|
||||
} else if (
|
||||
token.length === 1 &&
|
||||
["a", "b", "c", "d", "e"].includes(token)
|
||||
) {
|
||||
return "any" as NixType;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
const returnValueTokens = tokens.slice(lastArrowIdx + 1);
|
||||
const types = returnValueTokens
|
||||
.map(interpretToken)
|
||||
.filter(Boolean)
|
||||
.filter((e, i, s) => s.indexOf(e) === i);
|
||||
const args = tokens
|
||||
.slice(0, lastArrowIdx)
|
||||
.map(interpretToken)
|
||||
.filter(Boolean)
|
||||
.filter((e, i, s) => s.indexOf(e) === i);
|
||||
return { args, types } as { args: NixType[]; types: NixType[] };
|
||||
}
|
||||
const cleaned = b?.replace("(", "").replace(")", "").trim();
|
||||
let typ = cleaned;
|
||||
if (cleaned.match(/\[(.*)\]/)) {
|
||||
typ = "list";
|
||||
}
|
||||
if (
|
||||
cleaned.toLowerCase().includes("attrset") ||
|
||||
cleaned.trim().startsWith("{")
|
||||
) {
|
||||
typ = "attrset";
|
||||
}
|
||||
if (cleaned.length === 1 && ["a", "b", "c", "d", "e"].includes(cleaned)) {
|
||||
typ = "any";
|
||||
}
|
||||
return typ;
|
||||
}
|
||||
return a;
|
||||
};
|
||||
return { args: ["any"], types: ["any"] };
|
||||
}
|
||||
|
||||
const filterByType =
|
||||
(to: NixType[], from: NixType[]) =>
|
||||
({ to, from }: { to: NixType; from: NixType }) =>
|
||||
(data: MetaData): MetaData => {
|
||||
//if user wants any data show all
|
||||
if (to.includes("any") && from.includes("any")) {
|
||||
if (to === "any" && from === "any") {
|
||||
return data;
|
||||
} else {
|
||||
return data.filter(
|
||||
// TODO: Implement proper type matching
|
||||
({ name, fn_type }) => {
|
||||
if (fn_type) {
|
||||
const parsedType = getTypes(name, fn_type);
|
||||
return (
|
||||
parsedType.args.includes(from) && parsedType.types.includes(to)
|
||||
);
|
||||
} else {
|
||||
return to === "any" && from === "any";
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
return data.filter(
|
||||
// TODO: Implement proper type matching
|
||||
({ name, fn_type }) => {
|
||||
if (fn_type) {
|
||||
const cleanType = fn_type.replace(/ /g, "").replace(`${name}::`, "");
|
||||
const args = cleanType.split("->");
|
||||
const front = args.slice(0, -1);
|
||||
|
||||
const parsedInpTypes = front.map(preProcess);
|
||||
|
||||
const fn_to = args.at(-1);
|
||||
const parsedOutType = preProcess(fn_to);
|
||||
return (
|
||||
from.some((f) => parsedInpTypes.join(" ").includes(f)) &&
|
||||
to.some((t) => parsedOutType?.includes(t))
|
||||
);
|
||||
}
|
||||
//function type could not be detected only show those without filters
|
||||
if (fn_type === null) {
|
||||
return to.includes("any") && from.includes("any");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const initialTypes = nixTypes;
|
||||
export default function FunctionsPage() {
|
||||
const [selected, setSelected] = useState<string | null>(null);
|
||||
const [term, setTerm] = useState<string>("");
|
||||
const [to, setTo] = useState<NixType[]>(initialTypes);
|
||||
const [from, setFrom] = useState<NixType[]>(initialTypes);
|
||||
const [filter, setFilter] = useState<{ to: NixType; from: NixType }>({
|
||||
to: "any",
|
||||
from: "any",
|
||||
});
|
||||
|
||||
const handleSelect = (key: string) => {
|
||||
setSelected((curr: string | null) => {
|
||||
@ -111,55 +122,42 @@ export default function FunctionsPage() {
|
||||
};
|
||||
|
||||
const filteredData = useMemo(
|
||||
() => pipe(filterByType(to, from), search(term))(data),
|
||||
[to, from, term]
|
||||
() => pipe(filterByType(filter), search(term))(data),
|
||||
[filter, term]
|
||||
);
|
||||
|
||||
const handleSearch = (term: string) => {
|
||||
setTerm(term);
|
||||
};
|
||||
|
||||
const handleFilter = (t: NixType, mode: "to" | "from") => {
|
||||
let filterBy;
|
||||
if (t === "any") {
|
||||
filterBy = nixTypes;
|
||||
} else {
|
||||
filterBy = [t];
|
||||
}
|
||||
if (mode === "from") {
|
||||
setFrom(filterBy);
|
||||
}
|
||||
if (mode === "to") {
|
||||
setTo(filterBy);
|
||||
}
|
||||
const handleFilter = (filter: { from: NixType; to: NixType }) => {
|
||||
setFilter(filter);
|
||||
};
|
||||
const getKey = (item: DocItem) => `${item.category}/${item.name}`;
|
||||
|
||||
const preRenderedItems: BasicListItem[] = filteredData.map(
|
||||
(docItem: DocItem) => ({
|
||||
item: (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
onClick={() => handleSelect(getKey(docItem))}
|
||||
>
|
||||
<FunctionItem
|
||||
name={docItem.name}
|
||||
docItem={docItem}
|
||||
selected={selected === getKey(docItem)}
|
||||
/>
|
||||
</Box>
|
||||
),
|
||||
key: getKey(docItem),
|
||||
})
|
||||
);
|
||||
const preview = (
|
||||
<Preview
|
||||
docItem={data.find((f) => getKey(f) === selected) || data[0]}
|
||||
handleClose={() => setSelected(null)}
|
||||
/>
|
||||
(docItem: DocItem) => {
|
||||
const key = getKey(docItem);
|
||||
return {
|
||||
item: (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
onClick={!(selected === key) ? () => handleSelect(key) : undefined}
|
||||
>
|
||||
<FunctionItem
|
||||
name={docItem.name}
|
||||
docItem={docItem}
|
||||
selected={selected === key}
|
||||
handleClose={() => setSelected(null)}
|
||||
/>
|
||||
</Box>
|
||||
),
|
||||
key,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
@ -170,7 +168,6 @@ export default function FunctionsPage() {
|
||||
items={preRenderedItems}
|
||||
handleSearch={handleSearch}
|
||||
handleFilter={handleFilter}
|
||||
preview={selected ? preview : null}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
@ -1,4 +1,4 @@
|
||||
export type NixType = "attrset" | "list" | "string" | "int" | "bool" | "any";
|
||||
export type NixType = "function" | "attrset" | "list" | "string" | "int" | "bool" | "any";
|
||||
export const nixTypes: NixType[] = [
|
||||
"any",
|
||||
"attrset",
|
||||
|
Loading…
Reference in New Issue
Block a user