diff options
Diffstat (limited to 'web/source/settings/components')
-rw-r--r-- | web/source/settings/components/account-list.tsx | 82 | ||||
-rw-r--r-- | web/source/settings/components/pageable-list.tsx | 113 | ||||
-rw-r--r-- | web/source/settings/components/username.tsx | 90 |
3 files changed, 203 insertions, 82 deletions
diff --git a/web/source/settings/components/account-list.tsx b/web/source/settings/components/account-list.tsx deleted file mode 100644 index c4420b5bc..000000000 --- a/web/source/settings/components/account-list.tsx +++ /dev/null @@ -1,82 +0,0 @@ -/* - GoToSocial - Copyright (C) GoToSocial Authors admin@gotosocial.org - SPDX-License-Identifier: AGPL-3.0-or-later - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -import React from "react"; -import { Link } from "wouter"; -import { Error } from "./error"; -import { AdminAccount } from "../lib/types/account"; -import { SerializedError } from "@reduxjs/toolkit"; -import { FetchBaseQueryError } from "@reduxjs/toolkit/query"; - -export interface AccountListProps { - isSuccess: boolean, - data: AdminAccount[] | undefined, - isLoading: boolean, - isError: boolean, - error: FetchBaseQueryError | SerializedError | undefined, - emptyMessage: string, -} - -export function AccountList({ - isLoading, - isSuccess, - data, - isError, - error, - emptyMessage, -}: AccountListProps) { - if (!(isSuccess || isError)) { - // Hasn't been called yet. - return null; - } - - if (isLoading) { - return <i - className="fa fa-fw fa-refresh fa-spin" - aria-hidden="true" - title="Loading..." - />; - } - - if (error) { - return <Error error={error} />; - } - - if (data == undefined || data.length == 0) { - return <b>{emptyMessage}</b>; - } - - return ( - <div className="list"> - {data.map(({ account: acc }) => ( - <Link - key={acc.acct} - className="account entry" - href={`/${acc.id}`} - > - {acc.display_name?.length > 0 - ? acc.display_name - : acc.username - } - <span id="username">(@{acc.acct})</span> - </Link> - ))} - </div> - ); -} diff --git a/web/source/settings/components/pageable-list.tsx b/web/source/settings/components/pageable-list.tsx new file mode 100644 index 000000000..918103ead --- /dev/null +++ b/web/source/settings/components/pageable-list.tsx @@ -0,0 +1,113 @@ +/* + GoToSocial + Copyright (C) GoToSocial Authors admin@gotosocial.org + SPDX-License-Identifier: AGPL-3.0-or-later + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +import React, { ReactNode } from "react"; +import { useLocation } from "wouter"; +import { Error } from "./error"; +import { SerializedError } from "@reduxjs/toolkit"; +import { FetchBaseQueryError } from "@reduxjs/toolkit/query"; +import { Links } from "parse-link-header"; +import Loading from "./loading"; + +export interface PageableListProps<T> { + isSuccess: boolean; + items?: T[]; + itemToEntry: (_item: T) => ReactNode; + isLoading: boolean; + isFetching: boolean; + isError: boolean; + error: FetchBaseQueryError | SerializedError | undefined; + emptyMessage: string; + prevNextLinks?: Links | null | undefined; +} + +export function PageableList<T>({ + isLoading, + isFetching, + isSuccess, + items, + itemToEntry, + isError, + error, + emptyMessage, + prevNextLinks, +}: PageableListProps<T>) { + const [ location, setLocation ] = useLocation(); + + if (!(isSuccess || isError)) { + // Hasn't been called yet. + return null; + } + + if (isLoading || isFetching) { + return <Loading />; + } + + if (error) { + return <Error error={error} />; + } + + // Map response to items if possible. + let content: ReactNode; + if (items == undefined || items.length == 0) { + content = <b>{emptyMessage}</b>; + } else { + content = ( + <div className="entries"> + {items.map(item => itemToEntry(item))} + </div> + ); + } + + // If it's possible to page to next and previous + // pages, instantiate button handlers for this. + let prevClick: (() => void) | undefined; + let nextClick: (() => void) | undefined; + if (prevNextLinks) { + const prev = prevNextLinks["prev"]; + if (prev) { + const prevUrl = new URL(prev.url); + const prevParams = prevUrl.search; + prevClick = () => { + setLocation(location + prevParams.toString()); + }; + } + + const next = prevNextLinks["next"]; + if (next) { + const nextUrl = new URL(next.url); + const nextParams = nextUrl.search; + nextClick = () => { + setLocation(location + nextParams.toString()); + }; + } + } + + return ( + <div className="list pageable-list"> + { content } + { prevNextLinks && + <div className="prev-next"> + { prevClick && <button onClick={prevClick}>Previous page</button> } + { nextClick && <button onClick={nextClick}>Next page</button> } + </div> + } + </div> + ); +} diff --git a/web/source/settings/components/username.tsx b/web/source/settings/components/username.tsx new file mode 100644 index 000000000..f7be1cd4a --- /dev/null +++ b/web/source/settings/components/username.tsx @@ -0,0 +1,90 @@ +/* + GoToSocial + Copyright (C) GoToSocial Authors admin@gotosocial.org + SPDX-License-Identifier: AGPL-3.0-or-later + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +import React from "react"; +import { useLocation } from "wouter"; +import { AdminAccount } from "../lib/types/account"; + +interface UsernameProps { + account: AdminAccount; + linkTo?: string; + backLocation?: string; + classNames?: string[]; +} + +export default function Username({ account, linkTo, backLocation, classNames }: UsernameProps) { + const [ _location, setLocation ] = useLocation(); + + let className = "username-lozenge"; + let isLocal = account.domain == null; + + if (account.suspended) { + className += " suspended"; + } + + if (isLocal) { + className += " local"; + } + + if (classNames) { + className = [ className, classNames ].flat().join(" "); + } + + let icon = isLocal + ? { fa: "fa-home", info: "Local user" } + : { fa: "fa-external-link-square", info: "Remote user" }; + + const content = ( + <> + <i className={`fa fa-fw ${icon.fa}`} aria-hidden="true" title={icon.info} /> + <span className="sr-only">{icon.info}</span> + + <span className="acct">@{account.account.acct}</span> + </> + ); + + if (linkTo) { + className += " spanlink"; + return ( + <span + className={className} + onClick={() => { + // When clicking on an account, direct + // to the detail view for that account. + setLocation(linkTo, { + // Store the back location in history so + // the detail view can use it to return to + // this page (including query parameters). + state: { backLocation: backLocation } + }); + }} + role="link" + tabIndex={0} + > + {content} + </span> + ); + } else { + return ( + <div className={className}> + {content} + </div> + ); + } +} |