mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-04-11 13:20:37 +02:00
add task manager modal
This commit is contained in:
parent
af1b65f77d
commit
8faf3e42fd
5
.changeset/tasty-scissors-taste.md
Normal file
5
.changeset/tasty-scissors-taste.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": minor
|
||||
---
|
||||
|
||||
Add task manager modal
|
15
src/app.tsx
15
src/app.tsx
@ -89,6 +89,7 @@ import BookmarksView from "./views/bookmarks";
|
||||
import LoginNostrAddressView from "./views/signin/address";
|
||||
import LoginNostrAddressCreate from "./views/signin/address/create";
|
||||
import DatabaseView from "./views/relays/cache/database";
|
||||
import TaskManagerProvider from "./views/task-manager/provider";
|
||||
const TracksView = lazy(() => import("./views/tracks"));
|
||||
const UserTracksTab = lazy(() => import("./views/user/tracks"));
|
||||
const UserVideosTab = lazy(() => import("./views/user/videos"));
|
||||
@ -451,11 +452,13 @@ const router = createHashRouter([
|
||||
|
||||
export const App = () => (
|
||||
<ErrorBoundary>
|
||||
<DrawerSubViewProvider parentRouter={router}>
|
||||
<Global styles={overrideReactTextareaAutocompleteStyles} />
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<RouterProvider router={router} />
|
||||
</Suspense>
|
||||
</DrawerSubViewProvider>
|
||||
<TaskManagerProvider parentRouter={router}>
|
||||
<DrawerSubViewProvider parentRouter={router}>
|
||||
<Global styles={overrideReactTextareaAutocompleteStyles} />
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<RouterProvider router={router} />
|
||||
</Suspense>
|
||||
</DrawerSubViewProvider>
|
||||
</TaskManagerProvider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
@ -5,10 +5,11 @@ import relayPoolService from "../services/relay-pool";
|
||||
import createDefer from "./deferred";
|
||||
import { PersistentSubject } from "./subject";
|
||||
import ControlledObservable from "./controlled-observable";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
export type PublishResult = { relay: AbstractRelay; success: boolean; message: string };
|
||||
|
||||
export default class NostrPublishAction {
|
||||
export default class PublishAction {
|
||||
id = nanoid();
|
||||
label: string;
|
||||
relays: string[];
|
||||
|
@ -5,13 +5,14 @@ import { css } from "@emotion/react";
|
||||
|
||||
import useCurrentAccount from "../../hooks/use-current-account";
|
||||
import AccountSwitcher from "./account-switcher";
|
||||
import PublishLog from "../publish-log";
|
||||
import NavItems from "./nav-items";
|
||||
import { PostModalContext } from "../../providers/route/post-modal-provider";
|
||||
import { WritingIcon } from "../icons";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { offlineMode } from "../../services/offline-mode";
|
||||
import WifiOff from "../icons/wifi-off";
|
||||
import { useTaskManagerContext } from "../../views/task-manager/provider";
|
||||
import TaskManagerButton from "./task-manager-button";
|
||||
|
||||
const hideScrollbar = css`
|
||||
-ms-overflow-style: none;
|
||||
@ -88,7 +89,7 @@ export default function DesktopSideNav(props: Omit<FlexProps, "children">) {
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
<PublishLog overflowY="auto" minH="15rem" my="4" />
|
||||
<TaskManagerButton mt="auto" flexShrink={0} py="2" />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import { Link as RouterLink } from "react-router-dom";
|
||||
import AccountSwitcher from "./account-switcher";
|
||||
import useCurrentAccount from "../../hooks/use-current-account";
|
||||
import NavItems from "./nav-items";
|
||||
import TaskManagerButton from "./task-manager-button";
|
||||
|
||||
export default function MobileSideDrawer({ ...props }: Omit<DrawerProps, "children">) {
|
||||
const account = useCurrentAccount();
|
||||
@ -23,16 +24,7 @@ export default function MobileSideDrawer({ ...props }: Omit<DrawerProps, "childr
|
||||
<Drawer placement="left" {...props}>
|
||||
<DrawerOverlay />
|
||||
<DrawerContent>
|
||||
<DrawerBody
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
px="4"
|
||||
pt="4"
|
||||
pb="8"
|
||||
overflowY="auto"
|
||||
overflowX="hidden"
|
||||
gap="2"
|
||||
>
|
||||
<DrawerBody display="flex" flexDirection="column" px="4" pt="4" overflowY="auto" overflowX="hidden" gap="2">
|
||||
{account ? (
|
||||
<AccountSwitcher />
|
||||
) : (
|
||||
@ -48,6 +40,7 @@ export default function MobileSideDrawer({ ...props }: Omit<DrawerProps, "childr
|
||||
Sign in
|
||||
</Button>
|
||||
)}
|
||||
<TaskManagerButton mt="auto" flexShrink={0} py="2" />
|
||||
</DrawerBody>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
18
src/components/layout/task-manager-button.tsx
Normal file
18
src/components/layout/task-manager-button.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { useContext } from "react";
|
||||
import { Button, ButtonProps } from "@chakra-ui/react";
|
||||
|
||||
import { PublishContext } from "../../providers/global/publish-provider";
|
||||
import { useTaskManagerContext } from "../../views/task-manager/provider";
|
||||
import PublishActionStatusTag from "../../views/task-manager/publish-log/action-status-tag";
|
||||
|
||||
export default function TaskManagerButton({ ...props }: Omit<ButtonProps, "children">) {
|
||||
const { log } = useContext(PublishContext);
|
||||
const { openTaskManager } = useTaskManagerContext();
|
||||
|
||||
return (
|
||||
<Button variant="link" justifyContent="space-between" onClick={() => openTaskManager("/network")} {...props}>
|
||||
Task Manager
|
||||
{log.length > 0 && <PublishActionStatusTag action={log[log.length - 1]} />}
|
||||
</Button>
|
||||
);
|
||||
}
|
@ -29,8 +29,8 @@ import { useForm } from "react-hook-form";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import { ChevronDownIcon, ChevronUpIcon, UploadImageIcon } from "../icons";
|
||||
import NostrPublishAction from "../../classes/nostr-publish-action";
|
||||
import { PublishDetails } from "../publish-details";
|
||||
import PublishAction from "../../classes/nostr-publish-action";
|
||||
import { PublishDetails } from "../../views/task-manager/publish-log/publish-details";
|
||||
import { TrustProvider } from "../../providers/local/trust";
|
||||
import {
|
||||
correctContentMentions,
|
||||
@ -85,7 +85,7 @@ export default function PostModal({
|
||||
const account = useCurrentAccount()!;
|
||||
const { noteDifficulty } = useAppSettings();
|
||||
const [miningTarget, setMiningTarget] = useState(0);
|
||||
const [publishAction, setPublishAction] = useState<NostrPublishAction>();
|
||||
const [publishAction, setPublishAction] = useState<PublishAction>();
|
||||
const emojis = useContextEmojis();
|
||||
const moreOptions = useDisclosure();
|
||||
|
||||
|
@ -1,96 +0,0 @@
|
||||
import { useContext } from "react";
|
||||
import {
|
||||
Flex,
|
||||
FlexProps,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
Spinner,
|
||||
Tag,
|
||||
TagLabel,
|
||||
TagProps,
|
||||
Text,
|
||||
useDisclosure,
|
||||
} from "@chakra-ui/react";
|
||||
|
||||
import NostrPublishAction from "../classes/nostr-publish-action";
|
||||
import useSubject from "../hooks/use-subject";
|
||||
import { CheckIcon, ErrorIcon } from "./icons";
|
||||
import { PublishDetails } from "./publish-details";
|
||||
import { PublishContext } from "../providers/global/publish-provider";
|
||||
|
||||
export function PublishActionStatusTag({ pub, ...props }: { pub: NostrPublishAction } & Omit<TagProps, "children">) {
|
||||
const results = useSubject(pub.results);
|
||||
|
||||
const successful = results.filter(({ success }) => success);
|
||||
const failedWithMessage = results.filter(({ success, message }) => !success && !!message);
|
||||
|
||||
let statusIcon = <Spinner size="xs" />;
|
||||
let statusColor: TagProps["colorScheme"] = "blue";
|
||||
if (results.length !== pub.relays.length) {
|
||||
statusColor = "blue";
|
||||
statusIcon = <Spinner size="xs" />;
|
||||
} else if (successful.length === 0) {
|
||||
statusColor = "red";
|
||||
statusIcon = <ErrorIcon />;
|
||||
} else if (failedWithMessage.length > 0) {
|
||||
statusColor = "orange";
|
||||
statusIcon = <CheckIcon />;
|
||||
} else {
|
||||
statusColor = "green";
|
||||
statusIcon = <CheckIcon />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tag colorScheme={statusColor} {...props}>
|
||||
<TagLabel mr="1">
|
||||
{successful.length}/{pub.relays.length}
|
||||
</TagLabel>
|
||||
{statusIcon}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
|
||||
function PublishAction({ pub }: { pub: NostrPublishAction }) {
|
||||
const details = useDisclosure();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex gap="2" alignItems="center" cursor="pointer" onClick={details.onOpen}>
|
||||
<Text isTruncated>{pub.label}</Text>
|
||||
<PublishActionStatusTag ml="auto" pub={pub} flexShrink={0} />
|
||||
</Flex>
|
||||
{details.isOpen && (
|
||||
<Modal isOpen onClose={details.onClose} size="2xl">
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader pt="4" px="4" pb="0">
|
||||
{pub.label}
|
||||
</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody p="2">
|
||||
<PublishDetails pub={pub} />
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PublishLog({ ...props }: Omit<FlexProps, "children">) {
|
||||
const { log } = useContext(PublishContext);
|
||||
const reverseLog = Array.from(log).reverse();
|
||||
|
||||
return (
|
||||
<Flex overflow="hidden" direction="column" gap="1" {...props}>
|
||||
{reverseLog.length > 0 && <Text>Activity log:</Text>}
|
||||
{reverseLog.map((pub) => (
|
||||
<PublishAction key={pub.id} pub={pub} />
|
||||
))}
|
||||
</Flex>
|
||||
);
|
||||
}
|
@ -4,7 +4,7 @@ import { EventTemplate, NostrEvent, kinds } from "nostr-tools";
|
||||
|
||||
import { useSigningContext } from "./signing-provider";
|
||||
import { DraftNostrEvent } from "../../types/nostr-event";
|
||||
import NostrPublishAction from "../../classes/nostr-publish-action";
|
||||
import PublishAction from "../../classes/nostr-publish-action";
|
||||
import clientRelaysService from "../../services/client-relays";
|
||||
import RelaySet from "../../classes/relay-set";
|
||||
import { getAllRelayHints, isReplaceable } from "../../helpers/nostr/event";
|
||||
@ -17,28 +17,28 @@ import deleteEventService from "../../services/delete-events";
|
||||
import userMailboxesService from "../../services/user-mailboxes";
|
||||
|
||||
type PublishContextType = {
|
||||
log: NostrPublishAction[];
|
||||
log: PublishAction[];
|
||||
publishEvent(
|
||||
label: string,
|
||||
event: EventTemplate | NostrEvent,
|
||||
additionalRelays: Iterable<string> | undefined,
|
||||
quite: false,
|
||||
onlyAdditionalRelays: false,
|
||||
): Promise<NostrPublishAction>;
|
||||
): Promise<PublishAction>;
|
||||
publishEvent(
|
||||
label: string,
|
||||
event: EventTemplate | NostrEvent,
|
||||
additionalRelays: Iterable<string> | undefined,
|
||||
quite: false,
|
||||
onlyAdditionalRelays?: boolean,
|
||||
): Promise<NostrPublishAction>;
|
||||
): Promise<PublishAction>;
|
||||
publishEvent(
|
||||
label: string,
|
||||
event: EventTemplate | NostrEvent,
|
||||
additionalRelays?: Iterable<string> | undefined,
|
||||
quite?: boolean,
|
||||
onlyAdditionalRelays?: boolean,
|
||||
): Promise<NostrPublishAction | undefined>;
|
||||
): Promise<PublishAction | undefined>;
|
||||
};
|
||||
export const PublishContext = createContext<PublishContextType>({
|
||||
log: [],
|
||||
@ -53,7 +53,7 @@ export function usePublishEvent() {
|
||||
|
||||
export default function PublishProvider({ children }: PropsWithChildren) {
|
||||
const toast = useToast();
|
||||
const [log, setLog] = useState<NostrPublishAction[]>([]);
|
||||
const [log, setLog] = useState<PublishAction[]>([]);
|
||||
const { requestSignature } = useSigningContext();
|
||||
|
||||
const publishEvent = useCallback(
|
||||
@ -84,7 +84,7 @@ export default function PublishProvider({ children }: PropsWithChildren) {
|
||||
signed = await requestSignature(draft);
|
||||
} else signed = event as NostrEvent;
|
||||
|
||||
const pub = new NostrPublishAction(label, relays, signed);
|
||||
const pub = new PublishAction(label, relays, signed);
|
||||
setLog((arr) => arr.concat(pub));
|
||||
|
||||
pub.onResult.subscribe(({ relay, success }) => {
|
||||
|
5
src/views/task-manager/database/index.tsx
Normal file
5
src/views/task-manager/database/index.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import DatabaseView from "../../relays/cache/database";
|
||||
|
||||
export default function TaskManagerDatabase() {
|
||||
return <DatabaseView />;
|
||||
}
|
44
src/views/task-manager/layout.tsx
Normal file
44
src/views/task-manager/layout.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
import { Tab, TabIndicator, TabList, TabPanel, TabPanels, Tabs } from "@chakra-ui/react";
|
||||
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
||||
|
||||
const tabs = ["network", "publish-log", "database"];
|
||||
|
||||
export default function TaskManagerLayout() {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const index = tabs.indexOf(location.pathname.split("/")[1] || "network");
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
flexGrow="1"
|
||||
isLazy
|
||||
colorScheme="primary"
|
||||
position="relative"
|
||||
variant="unstyled"
|
||||
index={index}
|
||||
onChange={(i) => navigate("/" + tabs[i], { replace: true })}
|
||||
>
|
||||
<TabList overflowX="auto" overflowY="hidden" flexShrink={0} mr="10">
|
||||
<Tab>Network</Tab>
|
||||
<Tab>Publish Log</Tab>
|
||||
<Tab>Database</Tab>
|
||||
</TabList>
|
||||
<TabIndicator height="2px" bg="primary.500" borderRadius="1px" />
|
||||
|
||||
<TabPanels>
|
||||
<TabPanel p={0} minH="50vh">
|
||||
<Outlet />
|
||||
</TabPanel>
|
||||
<TabPanel p={0} minH="50vh">
|
||||
<Outlet />
|
||||
</TabPanel>
|
||||
<TabPanel minH="50vh">
|
||||
<Outlet />
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
75
src/views/task-manager/modal.tsx
Normal file
75
src/views/task-manager/modal.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
import {
|
||||
Heading,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalOverlay,
|
||||
ModalProps,
|
||||
Spinner,
|
||||
Tab,
|
||||
TabIndicator,
|
||||
TabList,
|
||||
TabPanel,
|
||||
TabPanels,
|
||||
Tabs,
|
||||
} from "@chakra-ui/react";
|
||||
import { RouterProvider, createMemoryRouter } from "react-router-dom";
|
||||
|
||||
import { PersistentSubject } from "../../classes/subject";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import DatabaseView from "../relays/cache/database";
|
||||
import TaskManagerNetwork from "./network";
|
||||
import { Suspense } from "react";
|
||||
|
||||
type Router = ReturnType<typeof createMemoryRouter>;
|
||||
|
||||
export default function TaskManagerModal({
|
||||
router,
|
||||
isOpen,
|
||||
onClose,
|
||||
}: { router: Router } & Omit<ModalProps, "children">) {
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} size="6xl" scrollBehavior="inside">
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalBody display="flex" flexDirection="column" gap="2" p="0">
|
||||
<Suspense
|
||||
fallback={
|
||||
<Heading size="md" mx="auto" my="4">
|
||||
<Spinner /> Loading page
|
||||
</Heading>
|
||||
}
|
||||
>
|
||||
<RouterProvider router={router} />
|
||||
</Suspense>
|
||||
{/* <Tabs
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
flexGrow="1"
|
||||
isLazy
|
||||
colorScheme="primary"
|
||||
position="relative"
|
||||
variant="unstyled"
|
||||
>
|
||||
<TabList overflowX="auto" overflowY="hidden" flexShrink={0} mr="10">
|
||||
<Tab>Network</Tab>
|
||||
<Tab>Database</Tab>
|
||||
</TabList>
|
||||
<TabIndicator height="2px" bg="primary.500" borderRadius="1px" />
|
||||
|
||||
<TabPanels minH="50vh">
|
||||
<TabPanel p={0}>
|
||||
<TaskManagerNetwork />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<DatabaseView />
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs> */}
|
||||
</ModalBody>
|
||||
<ModalCloseButton />
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
}
|
38
src/views/task-manager/network/index.tsx
Normal file
38
src/views/task-manager/network/index.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import {
|
||||
Accordion,
|
||||
AccordionButton,
|
||||
AccordionIcon,
|
||||
AccordionItem,
|
||||
AccordionPanel,
|
||||
Box,
|
||||
Spacer,
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
import relayPoolService from "../../../services/relay-pool";
|
||||
import { RelayFavicon } from "../../../components/relay-favicon";
|
||||
import { RelayStatus } from "../../../components/relay-status";
|
||||
|
||||
export default function TaskManagerNetwork() {
|
||||
return (
|
||||
<Accordion>
|
||||
{Array.from(relayPoolService.relays.values()).map((relay) => (
|
||||
<AccordionItem key={relay.url}>
|
||||
<h2>
|
||||
<AccordionButton>
|
||||
<RelayFavicon relay={relay.url} size="sm" mr="2" />
|
||||
<Text isTruncated>{relay.url}</Text>
|
||||
<Spacer />
|
||||
<RelayStatus url={relay.url} />
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
</h2>
|
||||
<AccordionPanel pb={4}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
|
||||
dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex
|
||||
ea commodo consequat.
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
);
|
||||
}
|
5
src/views/task-manager/network/inspect-relay.tsx
Normal file
5
src/views/task-manager/network/inspect-relay.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import VerticalPageLayout from "../../../components/vertical-page-layout";
|
||||
|
||||
export default function InspectRelayView() {
|
||||
return <VerticalPageLayout></VerticalPageLayout>;
|
||||
}
|
152
src/views/task-manager/provider.tsx
Normal file
152
src/views/task-manager/provider.tsx
Normal file
@ -0,0 +1,152 @@
|
||||
import { PropsWithChildren, createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Router, Location, To, createMemoryRouter, RouteObject } from "react-router-dom";
|
||||
import { useRouterMarker } from "../../providers/drawer-sub-view-provider";
|
||||
import { logger } from "../../helpers/debug";
|
||||
import { RouteProviders } from "../../providers/route";
|
||||
import InspectRelayView from "./network/inspect-relay";
|
||||
|
||||
import TaskManagerModal from "./modal";
|
||||
import TaskManagerLayout from "./layout";
|
||||
import TaskManagerNetwork from "./network";
|
||||
import TaskManagerDatabase from "./database";
|
||||
import PublishLogView from "./publish-log";
|
||||
|
||||
type Router = ReturnType<typeof createMemoryRouter>;
|
||||
|
||||
const log = logger.extend("TaskManagerProvider");
|
||||
|
||||
const TaskManagerContext = createContext<{ openTaskManager: (route: To) => void; closeTaskManager: () => void }>({
|
||||
openTaskManager() {},
|
||||
closeTaskManager() {},
|
||||
});
|
||||
|
||||
export function useTaskManagerContext() {
|
||||
return useContext(TaskManagerContext);
|
||||
}
|
||||
|
||||
const routes: RouteObject[] = [
|
||||
{
|
||||
path: "",
|
||||
element: <TaskManagerLayout />,
|
||||
children: [
|
||||
{
|
||||
path: "network",
|
||||
element: <TaskManagerNetwork />,
|
||||
children: [
|
||||
{
|
||||
path: ":url",
|
||||
element: (
|
||||
<RouteProviders>
|
||||
<InspectRelayView />
|
||||
</RouteProviders>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{ path: "publish-log", element: <PublishLogView /> },
|
||||
{
|
||||
path: "database",
|
||||
element: <TaskManagerDatabase />,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default function TaskManagerProvider({ children, parentRouter }: PropsWithChildren & { parentRouter: Router }) {
|
||||
const [router, setRouter] = useState<Router | null>(null);
|
||||
|
||||
const openInParent = useCallback((to: To) => parentRouter.navigate(to), [parentRouter]);
|
||||
|
||||
const direction = useRef<"up" | "down">();
|
||||
const marker = useRouterMarker(parentRouter);
|
||||
|
||||
useEffect(() => {
|
||||
return parentRouter.subscribe((event) => {
|
||||
const location = event.location as Location<{ taskManagerRoute?: To | null } | null>;
|
||||
const subRoute = location.state?.taskManagerRoute;
|
||||
|
||||
if (subRoute) {
|
||||
if (router) {
|
||||
if (router.state.location.pathname !== subRoute && direction.current !== "up") {
|
||||
log("Updating router from parent state");
|
||||
direction.current = "down";
|
||||
router.navigate(subRoute);
|
||||
direction.current = undefined;
|
||||
}
|
||||
} else {
|
||||
log("Create Router");
|
||||
|
||||
const newRouter = createMemoryRouter(routes, { initialEntries: [subRoute] });
|
||||
newRouter.subscribe((e) => {
|
||||
if (
|
||||
e.errors &&
|
||||
e.errors["__shim-error-route__"].status === 404 &&
|
||||
e.errors["__shim-error-route__"].internal
|
||||
) {
|
||||
openInParent(e.location);
|
||||
} else if (direction.current !== "down") {
|
||||
log("Updating parent state from Router");
|
||||
direction.current = "up";
|
||||
parentRouter.navigate(parentRouter.state.location, {
|
||||
preventScrollReset: true,
|
||||
state: { ...parentRouter.state.location.state, taskManagerRoute: e.location.pathname },
|
||||
});
|
||||
}
|
||||
direction.current = undefined;
|
||||
});
|
||||
|
||||
// use the parent routers createHref method so that users can open links in new tabs
|
||||
newRouter.createHref = parentRouter.createHref;
|
||||
|
||||
setRouter(newRouter);
|
||||
}
|
||||
} else if (router) {
|
||||
log("Destroy Router");
|
||||
setRouter(null);
|
||||
}
|
||||
});
|
||||
}, [parentRouter, router, setRouter]);
|
||||
|
||||
const openTaskManager = useCallback(
|
||||
(to: To) => {
|
||||
marker.set();
|
||||
parentRouter.navigate(parentRouter.state.location, {
|
||||
preventScrollReset: true,
|
||||
state: { ...parentRouter.state.location.state, taskManagerRoute: to },
|
||||
});
|
||||
},
|
||||
[parentRouter],
|
||||
);
|
||||
|
||||
const closeTaskManager = useCallback(() => {
|
||||
const i = marker.index.current;
|
||||
if (i !== null && i > 0) {
|
||||
log(`Navigating back ${i} entries to the point the task manager was opened`);
|
||||
parentRouter.navigate(-i);
|
||||
} else {
|
||||
log(`Failed to navigate back, clearing state`);
|
||||
parentRouter.navigate(parentRouter.state.location, {
|
||||
preventScrollReset: true,
|
||||
state: { ...parentRouter.state.location.state, taskManagerRoute: undefined },
|
||||
});
|
||||
}
|
||||
|
||||
// reset marker
|
||||
marker.reset();
|
||||
}, [parentRouter]);
|
||||
|
||||
const context = useMemo(
|
||||
() => ({
|
||||
openTaskManager,
|
||||
closeTaskManager,
|
||||
}),
|
||||
[openTaskManager, closeTaskManager],
|
||||
);
|
||||
|
||||
return (
|
||||
<TaskManagerContext.Provider value={context}>
|
||||
{children}
|
||||
{router && <TaskManagerModal router={router} isOpen onClose={closeTaskManager} />}
|
||||
</TaskManagerContext.Provider>
|
||||
);
|
||||
}
|
40
src/views/task-manager/publish-log/action-status-tag.tsx
Normal file
40
src/views/task-manager/publish-log/action-status-tag.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import { Spinner, Tag, TagLabel, TagProps } from "@chakra-ui/react";
|
||||
|
||||
import PublishAction from "../../../classes/nostr-publish-action";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import { CheckIcon, ErrorIcon } from "../../../components/icons";
|
||||
|
||||
export default function PublishActionStatusTag({
|
||||
action,
|
||||
...props
|
||||
}: { action: PublishAction } & Omit<TagProps, "children">) {
|
||||
const results = useSubject(action.results);
|
||||
|
||||
const successful = results.filter(({ success }) => success);
|
||||
const failedWithMessage = results.filter(({ success, message }) => !success && !!message);
|
||||
|
||||
let statusIcon = <Spinner size="xs" />;
|
||||
let statusColor: TagProps["colorScheme"] = "blue";
|
||||
if (results.length !== action.relays.length) {
|
||||
statusColor = "blue";
|
||||
statusIcon = <Spinner size="xs" />;
|
||||
} else if (successful.length === 0) {
|
||||
statusColor = "red";
|
||||
statusIcon = <ErrorIcon />;
|
||||
} else if (failedWithMessage.length > 0) {
|
||||
statusColor = "orange";
|
||||
statusIcon = <CheckIcon />;
|
||||
} else {
|
||||
statusColor = "green";
|
||||
statusIcon = <CheckIcon />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tag colorScheme={statusColor} {...props}>
|
||||
<TagLabel mr="1">
|
||||
{successful.length}/{action.relays.length}
|
||||
</TagLabel>
|
||||
{statusIcon}
|
||||
</Tag>
|
||||
);
|
||||
}
|
52
src/views/task-manager/publish-log/index.tsx
Normal file
52
src/views/task-manager/publish-log/index.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
import { useContext } from "react";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionButton,
|
||||
AccordionIcon,
|
||||
AccordionItem,
|
||||
AccordionPanel,
|
||||
Heading,
|
||||
Spacer,
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
|
||||
import PublishAction from "../../../classes/nostr-publish-action";
|
||||
import PublishActionStatusTag from "./action-status-tag";
|
||||
import { PublishContext } from "../../../providers/global/publish-provider";
|
||||
import { PublishDetails } from "./publish-details";
|
||||
|
||||
function PublishLogAction({ action }: { action: PublishAction }) {
|
||||
return (
|
||||
<AccordionItem>
|
||||
<h2>
|
||||
<AccordionButton>
|
||||
<Text isTruncated>{action.label}</Text>
|
||||
<Spacer />
|
||||
<PublishActionStatusTag ml="auto" action={action} flexShrink={0} />
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
</h2>
|
||||
<AccordionPanel pb={4}>
|
||||
<PublishDetails pub={action} />
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PublishLogView() {
|
||||
const { log } = useContext(PublishContext);
|
||||
const reverseLog = Array.from(log).reverse();
|
||||
|
||||
return (
|
||||
<Accordion>
|
||||
{reverseLog.length === 0 && (
|
||||
<Heading mx="auto" mt="10" size="md" textAlign="center">
|
||||
No events published yet
|
||||
</Heading>
|
||||
)}
|
||||
{reverseLog.map((action) => (
|
||||
<PublishLogAction key={action.id} action={action} />
|
||||
))}
|
||||
</Accordion>
|
||||
);
|
||||
}
|
@ -12,13 +12,13 @@ import {
|
||||
} from "@chakra-ui/react";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
|
||||
import NostrPublishAction, { PublishResult } from "../classes/nostr-publish-action";
|
||||
import useSubject from "../hooks/use-subject";
|
||||
import { RelayPaidTag } from "../views/relays/components/relay-card";
|
||||
import { EmbedEvent } from "./embed-event";
|
||||
import PublishAction, { PublishResult } from "../../../classes/nostr-publish-action";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import { RelayPaidTag } from "../../relays/components/relay-card";
|
||||
import { EmbedEvent } from "../../../components/embed-event";
|
||||
|
||||
export type PostResultsProps = {
|
||||
pub: NostrPublishAction;
|
||||
pub: PublishAction;
|
||||
};
|
||||
|
||||
function PublishResultRow({ result }: { result: PublishResult }) {
|
Loading…
x
Reference in New Issue
Block a user