diff --git a/web/app/components/Input.tsx b/web/app/components/Input.tsx index b5825e3..2b1a775 100644 --- a/web/app/components/Input.tsx +++ b/web/app/components/Input.tsx @@ -3,6 +3,7 @@ import { InputHTMLAttributes } from "react"; interface InputProps extends InputHTMLAttributes { name: string; className?: string; + inputSize?: "small" | "medium"; } export const Input = ({ @@ -14,6 +15,7 @@ export const Input = ({ onChange, placeholder, className: customClassName, + inputSize = "medium", }: InputProps) => { return ( ); }; diff --git a/web/app/components/RootSection.tsx b/web/app/components/RootSection.tsx index 1b571b9..ca4eedb 100644 --- a/web/app/components/RootSection.tsx +++ b/web/app/components/RootSection.tsx @@ -1,17 +1,20 @@ +import { useLocation } from "@remix-run/react"; import { Section } from "napi-pallas"; import { useState } from "react"; -import { IValidation } from "../interfaces"; -import { getTopicMeta } from "../utils"; +import { SearchParams, getTopicMeta } from "../utils"; import { HexBlock, PropBlock, TopicMeta } from "./constructors"; -import { DataSection, ValidationAccordion } from "./index"; +import { DataSection, ValidationInformation } from "./index"; export function RootSection(props: { data: Section; topics: Record; - validations: IValidation[]; era: string; }) { - const [open, setOpen] = useState(false); + const location = useLocation(); + const searchParams = new URLSearchParams(location.search); + const initialOpen = searchParams.get(SearchParams.OPEN) === "true"; + const goesBeginning = searchParams.get(SearchParams.BEGINNING) === "true"; + const [open, setOpen] = useState(undefined); const handleClick = () => setOpen(!open); const topic = getTopicMeta(props.data.topic, props.topics); @@ -25,22 +28,24 @@ export function RootSection(props: { return (
-
-
-
-

Tx Validations - {props.era}

-
- - {open && } -
+
+ {open ?? initialOpen ? "▼" : "▶"} +
+
+

Tx Validations - {props.era}

+
+ + {open ?? (initialOpen && )} + + )}

{topic.title}

{!!props.data.bytes && ( @@ -51,6 +56,24 @@ export function RootSection(props: { {props.data.children?.map((c) => ( ))} + {!goesBeginning && ( +
+ + {open ?? (initialOpen && )} +
+ )} ); } diff --git a/web/app/components/Validations/Configurations/Configurations.tsx b/web/app/components/Validations/Configurations/Configurations.tsx index e46178e..a5204c0 100644 --- a/web/app/components/Validations/Configurations/Configurations.tsx +++ b/web/app/components/Validations/Configurations/Configurations.tsx @@ -1,23 +1,12 @@ import { useEffect } from "react"; import { Button } from "../../../components"; -import { IContext, ProtocolType } from "../../../interfaces"; import { Tabs } from "./Tabs"; -export function ConfigsModal({ - closeModal, - protocolParams, - changeParam, - otherContext, - setOtherContext, -}: { +interface ConfigsModalProps { closeModal: () => void; - protocolParams: ProtocolType[]; - changeParam: ( - index: number - ) => (e: React.ChangeEvent) => void; - otherContext: IContext; - setOtherContext: (c: IContext) => void; -}) { +} + +export function ConfigsModal({ closeModal }: ConfigsModalProps) { // To close config modal on esc press useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { @@ -44,14 +33,9 @@ export function ConfigsModal({ > + - + diff --git a/web/app/components/Validations/Configurations/ContextTab.tsx b/web/app/components/Validations/Configurations/ContextTab.tsx new file mode 100644 index 0000000..c540bff --- /dev/null +++ b/web/app/components/Validations/Configurations/ContextTab.tsx @@ -0,0 +1,136 @@ +import { useContext } from "react"; +import { Button } from "../../../components/Button"; +import { Input } from "../../../components/Input"; +import { ValidationsContext } from "../../../contexts/validations.context"; +import { EraType, Eras, NetworkType, Networks } from "../../../interfaces"; +import { ByronPptParams } from "../../../utils"; + +export const ContextTab = () => { + const { context, setContext } = useContext(ValidationsContext); + const isByron = context.selectedEra === Eras.Byron; + const paramsList = isByron ? ByronPptParams : context.pptParams; + const networks: NetworkType[] = [ + Networks.Mainnet, + Networks.Preprod, + Networks.Preview, + ]; + const eras = [ + Eras.Byron, + Eras.ShelleyMA, + Eras.Alonzo, + Eras.Babbage, + Eras.Conway, + ]; + const changeParam = + (index: number) => (e: React.ChangeEvent) => { + setContext((prev) => { + const updatedParams = [...prev.pptParams]; + updatedParams[index] = { + ...updatedParams[index], + value: + Number(e.target.value) >= 0 + ? Number(e.target.value) + : updatedParams[index].value, + }; + return { ...prev, pptParams: updatedParams }; + }); + }; + const changeNetwork = (value: NetworkType) => () => + setContext({ ...context, selectedNetwork: value }); + const changeEra = (value: EraType) => () => + setContext({ ...context, selectedEra: value }); + const changeBlockSlot = (e: React.ChangeEvent) => { + setContext({ ...context, blockSlot: Number(e.target.value) }); + }; + return ( +
+
Protocol Parameters
+
+ {paramsList.map((param, index) => ( +
+ + +
+ ))} +
+
+
+
Select a Network
+ {/* To get the network from the form*/} + +
+ {networks.map((net) => ( + + ))} +
+
+
+
+
Select an Era
+ {/* To get the era from the form*/} + +
+ {eras.map((era) => ( + + ))} +
+
+
+
+
Select a Block Slot
+ +
+
+ ); +}; diff --git a/web/app/components/Validations/Configurations/Tabs.tsx b/web/app/components/Validations/Configurations/Tabs.tsx index 00bde60..83b2625 100644 --- a/web/app/components/Validations/Configurations/Tabs.tsx +++ b/web/app/components/Validations/Configurations/Tabs.tsx @@ -1,52 +1,14 @@ import { useState } from "react"; -import { Button, Input } from "../../../components"; -import { - EraType, - Eras, - IContext, - NetworkType, - Networks, - ProtocolType, - TabNames, - TabType, -} from "../../../interfaces"; -import { ByronPptParams } from "../../../utils"; +import { Button } from "../../../components"; +import { TabNames, TabType } from "../../../interfaces"; +import { ContextTab } from "./ContextTab"; +import { UITab } from "./UITab"; -export const Tabs = ({ - otherContext, - protocolParams, - changeParam, - setOtherContext, -}: { - otherContext: IContext; - protocolParams: ProtocolType[]; - changeParam: ( - index: number - ) => (e: React.ChangeEvent) => void; - setOtherContext: (c: IContext) => void; -}) => { - const tabs: TabType[] = [TabNames.ProtocolParameters, TabNames.Others]; - const networks: NetworkType[] = [ - Networks.Mainnet, - Networks.Preprod, - Networks.Preview, - ]; - const eras = ["Byron", "Shelley MA", "Alonzo", "Babbage", "Conway"]; - const [selected, setSelected] = useState( - TabNames.ProtocolParameters - ); +export const Tabs = () => { + const tabs: TabType[] = [TabNames.Context, TabNames.UI_Options]; + const [selected, setSelected] = useState(TabNames.Context); const changeSelected = (tab: TabType) => () => setSelected(tab); - const changeNetwork = (value: NetworkType) => () => - setOtherContext({ ...otherContext, selectedNetwork: value }); - const changeEra = (value: EraType) => () => - setOtherContext({ ...otherContext, selectedEra: value }); - const changeBlockSlot = (value: string) => { - setOtherContext({ ...otherContext, blockSlot: Number(value) }); - }; - - const isByron = otherContext.selectedEra === Eras.Byron; - const paramsList = isByron ? ByronPptParams : protocolParams; return (
@@ -68,98 +30,14 @@ export const Tabs = ({ ))}
-
- {paramsList.map((param, index) => ( -
- - -
- ))} +
+
-
-
Select a Network
- {/* To get the network from the form*/} - -
- {networks.map((net) => ( - - ))} -
-
-
-
Select an Era
- {/* To get the era from the form*/} - -
- {eras.map((era) => ( - - ))} -
-
-
-
Select a Block Slot
- changeBlockSlot(e.target.value)} - className="focus:bg-pink-200" - /> -
+
diff --git a/web/app/components/Validations/Configurations/UITab.tsx b/web/app/components/Validations/Configurations/UITab.tsx new file mode 100644 index 0000000..3e961cd --- /dev/null +++ b/web/app/components/Validations/Configurations/UITab.tsx @@ -0,0 +1,101 @@ +import { useLocation, useNavigate } from "@remix-run/react"; +import { useContext } from "react"; +import { ValidationsContext } from "../../../contexts/validations.context"; +import { SearchParams } from "../../../utils"; + +export const UITab = () => { + const { validations } = useContext(ValidationsContext); + const navigate = useNavigate(); + const location = useLocation(); + const shownValidations = + new URLSearchParams(location.search).get(SearchParams.LIST)?.split(",") ?? + []; + + const changeValidations = + (validation: string) => (event: React.ChangeEvent) => { + const isChecked = event.target.checked; + const newSearchParams = new URLSearchParams(location.search); + const currentValues = + newSearchParams.get(SearchParams.LIST)?.split(",") ?? []; + + if (isChecked && !currentValues.includes(validation)) { + currentValues.push(validation); + } else { + const index = currentValues.indexOf(validation); + if (index > -1) currentValues.splice(index, 1); + } + + if (currentValues.length > 0) + newSearchParams.set(SearchParams.LIST, currentValues.join(",")); + else newSearchParams.delete(SearchParams.LIST); + + navigate(`?${newSearchParams.toString()}`); + }; + + const changeQuery = + (q: string) => (e: React.ChangeEvent) => { + const isChecked = e.target.checked; + const newSearchParams = new URLSearchParams(location.search); + + newSearchParams.set(q, `${isChecked}`); + + navigate(`?${newSearchParams.toString()}`, { replace: true }); + }; + + const searchParams = new URLSearchParams(location.search); + const initialOpen = searchParams.get(SearchParams.OPEN) === "true"; + const beginning = searchParams.get(SearchParams.BEGINNING) === "true"; + + return ( +
+
Select validations to show
+ {validations.map((validation, index, arr) => ( +
+
+ + +
+ {index !== arr.length - 1 &&
} +
+ ))} +
+
+
Others
+ +
+ + +
+
+ + +
+
+
+ ); +}; diff --git a/web/app/components/Validations/Information.tsx b/web/app/components/Validations/Information.tsx index 906b97d..ae6c090 100644 --- a/web/app/components/Validations/Information.tsx +++ b/web/app/components/Validations/Information.tsx @@ -1,9 +1,13 @@ -import { useState } from "react"; +import { useLocation } from "@remix-run/react"; +import { useContext, useState } from "react"; +import { ValidationsContext } from "../../contexts/validations.context"; import { IValidation } from "../../interfaces"; +import { SearchParams } from "../../utils"; function AccordionItem({ validation }: { validation: IValidation }) { const [open, setOpen] = useState(false); const handleClick = () => setOpen(!open); + return (
- {props.validations.map((v) => ( - - ))} + {validations + .filter((v) => shownValidations.includes(v.name)) + .map((v) => ( + + ))}
); } diff --git a/web/app/components/index.tsx b/web/app/components/index.tsx index aeb3225..8529628 100644 --- a/web/app/components/index.tsx +++ b/web/app/components/index.tsx @@ -3,7 +3,7 @@ export { DataSection } from "./DataSection"; export { Input } from "./Input"; export { RootSection } from "./RootSection"; export { ConfigsModal } from "./Validations/Configurations/Configurations"; -export { ValidationAccordion } from "./Validations/Information"; +export { ValidationInformation } from "./Validations/Information"; export { EmptyBlock, HexBlock, diff --git a/web/app/contexts/validations.context.ts b/web/app/contexts/validations.context.ts new file mode 100644 index 0000000..c52b7d1 --- /dev/null +++ b/web/app/contexts/validations.context.ts @@ -0,0 +1,29 @@ +import { Dispatch, SetStateAction, createContext } from "react"; +import { + Eras, + IContext, + IProtocolParam, + IValidation, + Networks, +} from "../interfaces"; + +export interface ValidationsContextType { + validations: IValidation[]; + setValidations: Dispatch>; + context: { pptParams: IProtocolParam[] } & IContext; + setContext: Dispatch< + SetStateAction<{ pptParams: IProtocolParam[] } & IContext> + >; +} + +export const ValidationsContext = createContext({ + validations: [], + setValidations: () => {}, + context: { + pptParams: [], + blockSlot: 72316896, + selectedEra: Eras.Babbage, + selectedNetwork: Networks.Mainnet, + }, + setContext: () => {}, +}); diff --git a/web/app/interfaces.ts b/web/app/interfaces.ts index 43895fb..d3a5d3b 100644 --- a/web/app/interfaces.ts +++ b/web/app/interfaces.ts @@ -17,14 +17,14 @@ export interface DataProps extends server.Section { raw?: string; } -export interface ProtocolType { +export interface IProtocolParam { name: string; value: number; } export const TabNames = { - ProtocolParameters: "Protocol Parameters", - Others: "Other Context", + Context: "Context", + UI_Options: "UI Options", } as const; export type TabType = (typeof TabNames)[keyof typeof TabNames]; diff --git a/web/app/root.tsx b/web/app/root.tsx index 904fcad..5926fdd 100644 --- a/web/app/root.tsx +++ b/web/app/root.tsx @@ -3,13 +3,23 @@ import { Links, LiveReload, Meta, + NavLink, Outlet, Scripts, ScrollRestoration, - NavLink, } from "@remix-run/react"; +import { useState } from "react"; +import { ValidationsContext } from "./contexts/validations.context"; +import { + Eras, + IContext, + IProtocolParam, + IValidation, + Networks, +} from "./interfaces"; import styles from "./tailwind.css"; +import { initialProtPps, initialValidations } from "./utils"; export const links: LinksFunction = () => [ { rel: "stylesheet", href: styles }, @@ -36,6 +46,19 @@ function MenuItem(props: { to: string; label: string }) { // https://flabbergasted.lexingtonthemes.com export default function App() { + const [validations, setValidations] = + useState(initialValidations); + const [context, setContext] = useState< + { + pptParams: IProtocolParam[]; + } & IContext + >({ + pptParams: initialProtPps, + blockSlot: 72316896, + selectedEra: Eras.Babbage, + selectedNetwork: Networks.Mainnet, + }); + return ( @@ -68,7 +91,16 @@ export default function App() { - + + +