diff --git a/components/basicList/basicList.tsx b/components/basicList/basicList.tsx index 086ad60..3bb6fc6 100644 --- a/components/basicList/basicList.tsx +++ b/components/basicList/basicList.tsx @@ -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("any"); - - const _handleChange = (event: React.ChangeEvent) => { - const newVal = (event.target as HTMLInputElement).value as NixType; - setValue(newVal); - handleChange(newVal); - }; - const handleClear = () => { - setValue("any"); - handleChange("any"); - }; - - return ( - - - - - - - {label} - - - - - {options.map(({ value, label }) => ( - } - label={label} - /> - ))} - - - ); -}; - 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("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 ( _handleSearch("")} /> - - {/* */} - - - { - _handleFilter(value as NixType, "from"); - }} - options={nixTypes.map((v) => ({ value: v, label: v }))} - /> - - - - - - - - { - _handleFilter(value as NixType, "to"); - }} - options={nixTypes.map((v) => ({ value: v, label: v }))} - /> - - - {/* */} - + {pageItems.map(({ item, key }, idx) => ( - - - {preview} - - {/* )} */} - {item} diff --git a/components/functionItem/functionItem.tsx b/components/functionItem/functionItem.tsx index 623ea9a..e27bc98 100644 --- a/components/functionItem/functionItem.tsx +++ b/components/functionItem/functionItem.tsx @@ -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 ( - - - {`${fn_type || "No type yet provided"} `} + + {!selected && ( + <> + + + + {`${fn_type || "No type yet provided"} `} + + + )} + {selected && } ); diff --git a/components/layout/layout.tsx b/components/layout/layout.tsx index d63fa1c..3c8b314 100644 --- a/components/layout/layout.tsx +++ b/components/layout/layout.tsx @@ -107,6 +107,8 @@ export function Layout(props: LayoutProps) { marginTop: "6em", maxHeight: "calc(100vh - 8em)", overflowY: "scroll", + overflowX: "hidden", + width: "100vw", }} > diff --git a/components/preview/preview.module.css b/components/preview/preview.module.css new file mode 100644 index 0000000..ceedf6d --- /dev/null +++ b/components/preview/preview.module.css @@ -0,0 +1,4 @@ +.hljs { + padding: 0 !important; + +} diff --git a/components/preview/preview.tsx b/components/preview/preview.tsx index 99b838d..24d3e1f 100644 --- a/components/preview/preview.tsx +++ b/components/preview/preview.tsx @@ -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) => { - {/* */} - {`${prefix}.${name}`} - {/* */} + + {`${prefix}.${name}`} + + handleClose()} + > + + + + - - - handleClose()}> - - - - - + - + { primary={"nixpkgs/" + category.replace("./", "")} secondary={ @@ -117,14 +130,15 @@ export const Preview = (props: PreviewProps) => { } /> - + - + { - + { } secondary={ finalExample ? ( - - {finalExample} + + + {finalExample} + ) : ( void; + value: string; + + options: { + value: string; + label: string; + }[]; +} + +const SelectOption = (props: SelectOptionProps) => { + const { label, handleChange, options, value } = props; + + const _handleChange = (event: React.ChangeEvent) => { + const newVal = (event.target as HTMLInputElement).value as NixType; + handleChange(newVal); + }; + + return ( + + + {label} + + + + {options.map(({ value, label }) => ( + } + label={label} + /> + ))} + + + ); +}; 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("any"); + const [from, setFrom] = useState("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 ( - { - e.preventDefault(); - handleSubmit(term); - }} - > - - - - + handleChange(e)} - /> - 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" > - - - + + + + handleChange(e)} + /> + 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" + > + + + + + + + { + _handleFilter(value as NixType, "from"); + }} + options={nixTypes.map((v) => ({ value: v, label: v }))} + /> + + + + + + + + { + _handleFilter(value as NixType, "to"); + }} + options={nixTypes.map((v) => ({ value: v, label: v }))} + /> + + + + ); } diff --git a/pages/index.tsx b/pages/index.tsx index 130f4dd..f91150d 100644 --- a/pages/index.tsx +++ b/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(null); const [term, setTerm] = useState(""); - const [to, setTo] = useState(initialTypes); - const [from, setFrom] = useState(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: ( - handleSelect(getKey(docItem))} - > - - - ), - key: getKey(docItem), - }) - ); - const preview = ( - getKey(f) === selected) || data[0]} - handleClose={() => setSelected(null)} - /> + (docItem: DocItem) => { + const key = getKey(docItem); + return { + item: ( + handleSelect(key) : undefined} + > + setSelected(null)} + /> + + ), + key, + }; + } ); return ( @@ -170,7 +168,6 @@ export default function FunctionsPage() { items={preRenderedItems} handleSearch={handleSearch} handleFilter={handleFilter} - preview={selected ? preview : null} /> ); diff --git a/types/nix.ts b/types/nix.ts index 752a632..2ab93be 100644 --- a/types/nix.ts +++ b/types/nix.ts @@ -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",