diff --git a/client/src/app/pages/dashboard/utils/SeedsViewer.tsx b/client/src/app/pages/dashboard/utils/SeedsViewer.tsx index 3d97d3297..f59275ff2 100644 --- a/client/src/app/pages/dashboard/utils/SeedsViewer.tsx +++ b/client/src/app/pages/dashboard/utils/SeedsViewer.tsx @@ -1,9 +1,211 @@ +import { FormatTime } from "util/time"; +import { APIFetchV1 } from "util/api"; import useSetSubheader from "components/layout/header/useSetSubheader"; -import BMSCourseLookupTable from "components/tables/seeds/BMSCourseLookupTable"; -import React from "react"; +import Loading from "components/util/Loading"; +import useApiQuery from "components/util/query/useApiQuery"; +import Select from "components/util/Select"; +import React, { useEffect, useState } from "react"; +import { Col, Row } from "react-bootstrap"; +import { useParams } from "react-router-dom"; +import Divider from "components/util/Divider"; +import Icon from "components/util/Icon"; +import { SetState } from "types/react"; +import { TachiConfig } from "lib/config"; +import ExternalLink from "components/util/ExternalLink"; export default function SeedsViewer() { - useSetSubheader(["Developer Utils", "Seeds"]); + useSetSubheader(["Developer Utils", "Database Seeds Management"]); - return ; + const { data, error: failedToGetLocalAPI } = useApiQuery>("/seeds"); + + if (!data) { + return ; + } + + return ( + <> +

{TachiConfig.name} Database Management

+ + + This tool is for viewing the database that powers {TachiConfig.name} in a more + efficient manner. +
+ To view the state of a given commit or repository, use the select boxes below. +
+ For more information what all of this is about and how it works, see{" "} + + the documentation + + . + {!failedToGetLocalAPI && ( + <> +
+
+ + Also, it seems like you're running {TachiConfig.name} in local + development! + {" "} +
+ You can use this UI to view your current changes on-your-disk before you + commit them! + + )} +
+ + + + ); + // return ; +} + +function InnerSeedsViewer({ hasLocalAPI }: { hasLocalAPI: boolean }) { + const { repo: initRepo } = useParams<{ + repo: string | undefined; + }>(); + + // a repo is one of the following: + // null - nothing has been selected yet + // "local" - we're referring to the files on the local development disk + // "gh:NAME/REPO" - we're referring to a repository on github, like gh:TNG-Dev/Tachi + const [repo, setRepo] = useState(initRepo ?? null); + + // A revision is any valid pointer to a commit that Github understands. + const [rev, setRev] = useState(null); + + return ( + + +
+ Repository: + + {rev && ( + <> + / + {rev.sha} + + )} +
+ + + {repo && !rev && ( + <> + + + + )} + +
+ ); +} + +// stolen straight from server/src/utils/git.ts +interface GitCommit { + sha: string; + commit: { + author: { + name: string; + email: string; + date: string; + }; + committer: { + name: string; + email: string; + date: string; + }; + message: string; + }; +} + +function RevSelector({ repo, setRev }: { repo: string; setRev: SetState }) { + const [revs, setRevs] = useState>([]); + + useEffect(() => { + (async () => { + if (repo.startsWith("gh:")) { + throw new Error("Unsupported..."); + } else { + // local + const res = await APIFetchV1>("/seeds/commits"); + + if (!res.success) { + throw new Error(`Failed to fetch commits? ${res.description}`); + } + + setRevs(res.body); + } + })(); + }, [repo]); + + return ( +
+
+ {revs.map((r) => ( + + ))} +
+ ); +} + +function Revision({ rev, setRev }: { rev: GitCommit; setRev: SetState }) { + const authorNotCommitter = rev.commit.author.email !== rev.commit.committer.email; + + // if there's a body then there's two newlines. + const [subject, _gap, ...maybeBodies] = rev.commit.message.split("\n"); + + const [showBody, setShowBody] = useState(false); + + const body: string | undefined = maybeBodies?.join("\n"); + + return ( +
+ +
+
+ + {rev.sha}:{" "} + setRev(rev)}> + {subject} + + + {body && ( + setShowBody(!showBody)} + > + {showBody ? ( + + ) : ( + + )} + + )} + {showBody &&
{body}
} +
+ {authorNotCommitter ? ( + <> + Authored by {rev.commit.author.name}, Committed by{" "} + {rev.commit.committer.name} + + ) : ( + <> + Authored by {rev.commit.author.name} + + )} +
+
+ + + {FormatTime(Date.parse(rev.commit.author.date))} + +
+
+ ); } diff --git a/client/src/components/util/Select.tsx b/client/src/components/util/Select.tsx new file mode 100644 index 000000000..bba9b1ce9 --- /dev/null +++ b/client/src/components/util/Select.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import { Form } from "react-bootstrap"; +import { SetState } from "types/react"; + +export default function Select({ + value, + setValue, + children, + allowNull = false, + style, + className, +}: { + value: T; + setValue: SetState; + children: React.ReactNode; + allowNull?: boolean; + style?: React.CSSProperties; + className?: string; +}) { + return ( + setValue(e.target.value as T)} + > + {allowNull && } + {children} + + ); +}