UNPKG

@dugongjs/cli

Version:

194 lines (193 loc) 12.6 kB
import { Box, Text, useInput } from "ink"; import React from "react"; import { getAggregateQueryAdapter } from "../adapters/resolver.js"; import { getCurrentContext } from "../config/context.js"; import { HotkeyIndicator } from "./components/hotkey-indicator.js"; import { ValueDisplay } from "./components/value-display.js"; import { useAggregateIds } from "./hooks/use-aggregate-ids.js"; import { useAggregateTypes } from "./hooks/use-aggregate-types.js"; import { useAggregate } from "./hooks/use-aggregate.js"; import { useDomainEvents } from "./hooks/use-domain-events.js"; import { useStdoutDimensions } from "./hooks/use-stdout-dimensions.js"; import { AggregateDiffPane } from "./panes/aggregate-diff-pane.js"; import { AggregateIdSelectPane } from "./panes/aggregate-id-select-pane.js"; import { AggregateViewPane } from "./panes/aggregate-view-pane.js"; import { DomainEventSelectPane } from "./panes/domain-event-select-pane.js"; import { DomainEventViewPane } from "./panes/domain-event-view-pane.js"; import { ProgressBar } from "./panes/progress-bar.js"; import { Sidebar } from "./panes/sidebar.js"; const FocusIndex = { SIDEBAR: 0, AGGREGATE_ID_SELECT: 1, DOMAIN_EVENT_SELECT: 2, DOMAIN_EVENT_VIEW: 3, AGGREGATE_VIEW: 4 }; function focusOrder(sidebarOpen) { return sidebarOpen ? [ FocusIndex.SIDEBAR, FocusIndex.AGGREGATE_ID_SELECT, FocusIndex.DOMAIN_EVENT_SELECT, FocusIndex.DOMAIN_EVENT_VIEW, FocusIndex.AGGREGATE_VIEW ] : [ FocusIndex.AGGREGATE_ID_SELECT, FocusIndex.DOMAIN_EVENT_SELECT, FocusIndex.DOMAIN_EVENT_VIEW, FocusIndex.AGGREGATE_VIEW ]; } function maxReachablePos(hasSelectedId, order) { const maxTarget = hasSelectedId ? FocusIndex.AGGREGATE_VIEW : FocusIndex.AGGREGATE_ID_SELECT; return order.indexOf(maxTarget); } export function MainView({ preselectedType, preselectedId }) { const [cols, rows] = useStdoutDimensions(); const [focusIndex, setFocusIndex] = React.useState(FocusIndex.SIDEBAR); const [sidebarOpen, setSidebarOpen] = React.useState(true); const [showDiff, setShowDiff] = React.useState(false); const [selectedType, setSelectedType] = React.useState(preselectedType ?? null); const [selectedId, setSelectedId] = React.useState(preselectedId ?? null); const [selectedDomainEventIndex, setSelectedDomainEventIndex] = React.useState(null); const [context, setContext] = React.useState(null); const [adapter, setAdapter] = React.useState(null); useInput((_, key) => { if (!key.tab) return; const order = focusOrder(sidebarOpen); const maxPos = maxReachablePos(!!selectedId, order); const reachable = order.slice(0, Math.max(0, maxPos) + 1); const currPos = Math.max(0, reachable.indexOf(focusIndex)); const dir = key.shift ? -1 : 1; const nextPos = (currPos + dir + reachable.length) % reachable.length; setFocusIndex(reachable[nextPos]); }); useInput((input, key) => { if (key.ctrl && input.toLowerCase() === "b") { setSidebarOpen((o) => { const nextOpen = !o; if (!nextOpen && focusIndex === FocusIndex.SIDEBAR) { setFocusIndex(FocusIndex.AGGREGATE_ID_SELECT); } return nextOpen; }); } }); useInput((_, key) => { if (key.delete || key.backspace) { if (selectedId) { setSelectedId(null); setFocusIndex(FocusIndex.AGGREGATE_ID_SELECT); } else if (selectedType) { setSelectedType(null); setFocusIndex(FocusIndex.SIDEBAR); } } }); useInput((input, key) => { if (key.ctrl && input.toLowerCase() === "d") { setShowDiff((d) => !d); } }); React.useEffect(() => { const { adapter, close } = getAggregateQueryAdapter(); const context = getCurrentContext(); setAdapter(adapter); setContext(context); return () => void close?.(); }, []); React.useEffect(() => { if (selectedType) { setSelectedId(null); setSelectedDomainEventIndex(null); setFocusIndex(FocusIndex.AGGREGATE_ID_SELECT); } }, [selectedType]); React.useEffect(() => { if (selectedId) setFocusIndex(FocusIndex.DOMAIN_EVENT_SELECT); }, [selectedId]); const { data: types, isLoading: typesLoading, isError: typesError, error: typesErrObj } = useAggregateTypes(adapter); const { data: ids, isLoading: idsLoading, isError: idsError, error: idsErrObj } = useAggregateIds(adapter, selectedType); const { data: domainEvents, isLoading: eventsLoading, isError: eventsError, error: eventsErrObj } = useDomainEvents(adapter, selectedType, selectedId); React.useEffect(() => { if (!domainEvents || domainEvents.length === 0) { setSelectedDomainEventIndex(null); } else { setSelectedDomainEventIndex((prev) => prev == null ? domainEvents.length - 1 : Math.min(prev, domainEvents.length - 1)); } }, [domainEvents]); const currentSeq = selectedDomainEventIndex != null && domainEvents ? domainEvents[selectedDomainEventIndex]?.sequenceNumber : null; const prevSeq = currentSeq != null && currentSeq > 0 ? currentSeq - 1 : currentSeq; const { data: aggregate, isLoading: aggregateLoading, isError: aggregateError, error: aggregateErrObj } = useAggregate(adapter, selectedType, selectedId, currentSeq); const { data: previousAggregate, isLoading: prevAggLoading, isError: prevAggError } = useAggregate(adapter, selectedType, selectedId, prevSeq); return (React.createElement(Box, { width: cols, height: rows - 1, marginTop: 1, flexDirection: "row" }, sidebarOpen && (React.createElement(Sidebar, { types: types ?? [], selectedType: selectedType, setSelectedType: setSelectedType, isFocused: focusIndex === FocusIndex.SIDEBAR, isLoading: typesLoading, error: typesError ? (typesErrObj?.message ?? "Failed to load types") : null })), React.createElement(Box, { flexGrow: 1, height: "100%", flexDirection: "column" }, React.createElement(Box, { flexBasis: 6, padding: 1, flexDirection: "row", justifyContent: "space-between" }, React.createElement(Box, { flexDirection: "row", gap: 4 }, React.createElement(Box, { flexDirection: "column" }, React.createElement(ValueDisplay, { label: "Context", value: context?.name }), React.createElement(ValueDisplay, { label: "Host", value: context?.host }), React.createElement(ValueDisplay, { label: "Port", value: context?.port?.toString() }), React.createElement(ValueDisplay, { label: "Adapter", value: context?.adapter }), React.createElement(ValueDisplay, { label: "Transport", value: context?.transport })), React.createElement(Box, { flexDirection: "column" }, React.createElement(ValueDisplay, { label: "Aggregate type", value: selectedType }), React.createElement(ValueDisplay, { label: "Aggregate ID", value: selectedId }), React.createElement(ValueDisplay, { label: "Domain event ID", value: selectedDomainEventIndex != null && domainEvents ? domainEvents[selectedDomainEventIndex]?.id : null }), React.createElement(Box, null, React.createElement(Text, { color: "blueBright" }, "Aggregate progression: "), React.createElement(ProgressBar, { value: domainEvents && selectedDomainEventIndex != null && domainEvents.length > 0 ? Math.round(((selectedDomainEventIndex + 1) / domainEvents.length) * 100) : 0 }), React.createElement(Text, null, domainEvents && selectedDomainEventIndex != null && domainEvents.length > 0 ? ` ${selectedDomainEventIndex + 1}/${domainEvents.length}` : " N/A")))), React.createElement(Box, null, React.createElement(Text, { color: "cyanBright" }, "dugong studio"))), React.createElement(Box, { flexGrow: 1, borderStyle: "doubleSingle", borderColor: focusIndex === FocusIndex.SIDEBAR ? "gray" : focusIndex === FocusIndex.AGGREGATE_ID_SELECT ? "cyan" : "magentaBright" }, selectedType ? (!selectedId ? (React.createElement(AggregateIdSelectPane, { ids: ids ?? [], selectedId: selectedId, setSelectedId: setSelectedId, selectedType: selectedType, isFocused: focusIndex === FocusIndex.AGGREGATE_ID_SELECT, isLoading: idsLoading, error: idsError ? (idsErrObj?.message ?? "Failed to load IDs") : null })) : (React.createElement(Box, { flexDirection: "row", flexGrow: 1 }, React.createElement(Box, { width: "20%" }, React.createElement(DomainEventSelectPane, { domainEvents: domainEvents ?? [], selectedDomainEventIndex: selectedDomainEventIndex, setSelectedDomainEventIndex: setSelectedDomainEventIndex, isFocused: focusIndex === FocusIndex.DOMAIN_EVENT_SELECT, isLoading: eventsLoading, error: eventsError ? (eventsErrObj?.message ?? "Failed to load events") : null })), React.createElement(Box, { width: "40%" }, React.createElement(DomainEventViewPane, { domainEvent: selectedDomainEventIndex != null && domainEvents ? domainEvents[selectedDomainEventIndex] : null, maximumSequenceNumber: domainEvents && domainEvents.length > 0 ? domainEvents[domainEvents.length - 1].sequenceNumber : null, isFocused: focusIndex === FocusIndex.DOMAIN_EVENT_VIEW, isLoading: eventsLoading, error: eventsError ? (eventsErrObj?.message ?? "Failed to load event") : null })), React.createElement(Box, { width: "40%" }, showDiff ? (React.createElement(AggregateDiffPane, { current: aggregate ?? null, previous: previousAggregate ?? null, isFocused: focusIndex === FocusIndex.AGGREGATE_VIEW, isLoading: aggregateLoading || prevAggLoading, error: aggregateError ? (aggregateErrObj?.message ?? "Failed to load aggregate") : prevAggError ? "Failed to load previous aggregate" : null })) : (React.createElement(AggregateViewPane, { aggregate: aggregate ?? null, isDeleted: aggregate?.isDeletedInternal ?? false, isFocused: focusIndex === FocusIndex.AGGREGATE_VIEW, isLoading: aggregateLoading || prevAggLoading, error: aggregateError ? (aggregateErrObj?.message ?? "Failed to load aggregate") : prevAggError ? "Failed to load previous aggregate" : null })))))) : (React.createElement(Text, { color: "grey" }, "No aggregate type selected"))), React.createElement(Box, { borderStyle: "single", borderColor: "gray", gap: 2, flexBasis: 3, flexShrink: 0 }, React.createElement(HotkeyIndicator, { hotkey: "Tab", label: "next pane" }), React.createElement(HotkeyIndicator, { hotkey: "Shift+Tab", label: "previous pane", hotkeyWidth: 9 }), React.createElement(HotkeyIndicator, { hotkey: "Ctrl+B", label: `${sidebarOpen ? "hide" : "show"} sidebar`, hotkeyWidth: 6 }), React.createElement(HotkeyIndicator, { hotkey: "\u2191/\u2193", label: "navigate" }), React.createElement(HotkeyIndicator, { hotkey: "\u23CE", label: "select", hotkeyWidth: 1 }), React.createElement(HotkeyIndicator, { hotkey: "\u232B", label: "deselect", hotkeyWidth: 1 }), React.createElement(HotkeyIndicator, { hotkey: "Ctrl+D", label: `show ${showDiff ? "aggregate" : "diff"}`, hotkeyWidth: 6 }))))); }