|
1 | | -import { useQuery } from "@tanstack/react-query"; |
| 1 | +import { TasksApi, type InitResponse } from "@repo/shared"; |
| 2 | +import { getState, setState } from "@repo/webview-shared"; |
| 3 | +import { useIpc } from "@repo/webview-shared/react"; |
2 | 4 | import { |
3 | | - VscodeButton, |
4 | | - VscodeIcon, |
| 5 | + VscodeCollapsible, |
5 | 6 | VscodeProgressRing, |
| 7 | + VscodeScrollable, |
6 | 8 | } from "@vscode-elements/react-elements"; |
| 9 | +import { useEffect, useRef, useState } from "react"; |
7 | 10 |
|
8 | | -import { useTasksApi } from "./hooks/useTasksApi"; |
| 11 | +import { CreateTaskSection } from "./components/CreateTaskSection"; |
| 12 | +import { ErrorState } from "./components/ErrorState"; |
| 13 | +import { NoTemplateState } from "./components/NoTemplateState"; |
| 14 | +import { NotSupportedState } from "./components/NotSupportedState"; |
| 15 | +import { TaskList } from "./components/TaskList"; |
| 16 | +import { useCollapsibleToggle } from "./hooks/useCollapsibleToggle"; |
| 17 | +import { useScrollableHeight } from "./hooks/useScrollableHeight"; |
| 18 | +import { useTasksQuery } from "./hooks/useTasksQuery"; |
| 19 | + |
| 20 | +interface PersistedState extends InitResponse { |
| 21 | + createExpanded: boolean; |
| 22 | + historyExpanded: boolean; |
| 23 | +} |
| 24 | + |
| 25 | +type CollapsibleElement = React.ComponentRef<typeof VscodeCollapsible>; |
| 26 | +type ScrollableElement = React.ComponentRef<typeof VscodeScrollable>; |
9 | 27 |
|
10 | 28 | export default function App() { |
11 | | - const api = useTasksApi(); |
| 29 | + const [restored] = useState(() => getState<PersistedState>()); |
| 30 | + const { tasks, templates, tasksSupported, data, isLoading, error, refetch } = |
| 31 | + useTasksQuery(restored); |
| 32 | + |
| 33 | + const [createRef, createOpen, setCreateOpen] = |
| 34 | + useCollapsibleToggle<CollapsibleElement>(restored?.createExpanded ?? true); |
| 35 | + const [historyRef, historyOpen] = useCollapsibleToggle<CollapsibleElement>( |
| 36 | + restored?.historyExpanded ?? true, |
| 37 | + ); |
12 | 38 |
|
13 | | - const { data, isLoading, error, refetch } = useQuery({ |
14 | | - queryKey: ["tasks-init"], |
15 | | - queryFn: () => api.init(), |
16 | | - }); |
| 39 | + const createScrollRef = useRef<ScrollableElement>(null); |
| 40 | + const historyScrollRef = useRef<ScrollableElement>(null); |
| 41 | + useScrollableHeight(createRef, createScrollRef); |
| 42 | + useScrollableHeight(historyRef, historyScrollRef); |
17 | 43 |
|
18 | | - if (isLoading) { |
19 | | - return <VscodeProgressRing />; |
20 | | - } |
| 44 | + const { onNotification } = useIpc(); |
| 45 | + useEffect(() => { |
| 46 | + return onNotification(TasksApi.showCreateForm, () => setCreateOpen(true)); |
| 47 | + }, [onNotification, setCreateOpen]); |
| 48 | + |
| 49 | + useEffect(() => { |
| 50 | + if (data) { |
| 51 | + setState<PersistedState>({ |
| 52 | + ...data, |
| 53 | + createExpanded: createOpen, |
| 54 | + historyExpanded: historyOpen, |
| 55 | + }); |
| 56 | + } |
| 57 | + }, [data, createOpen, historyOpen]); |
21 | 58 |
|
22 | | - if (error) { |
23 | | - return <p>Error: {error.message}</p>; |
| 59 | + if (isLoading) { |
| 60 | + return ( |
| 61 | + <div className="loading-container"> |
| 62 | + <VscodeProgressRing /> |
| 63 | + </div> |
| 64 | + ); |
24 | 65 | } |
25 | 66 |
|
26 | | - if (!data?.tasksSupported) { |
| 67 | + if (error && tasks.length === 0) { |
27 | 68 | return ( |
28 | | - <p> |
29 | | - <VscodeIcon name="warning" /> Tasks not supported |
30 | | - </p> |
| 69 | + <ErrorState message={error.message} onRetry={() => void refetch()} /> |
31 | 70 | ); |
32 | 71 | } |
33 | 72 |
|
| 73 | + if (!tasksSupported) { |
| 74 | + return <NotSupportedState />; |
| 75 | + } |
| 76 | + |
| 77 | + if (templates.length === 0) { |
| 78 | + return <NoTemplateState />; |
| 79 | + } |
| 80 | + |
34 | 81 | return ( |
35 | | - <div> |
36 | | - <p> |
37 | | - <VscodeIcon name="check" /> Connected to {data.baseUrl} |
38 | | - </p> |
39 | | - <p>Templates: {data.templates.length}</p> |
40 | | - <p>Tasks: {data.tasks.length}</p> |
41 | | - <VscodeButton icon="refresh" onClick={() => void refetch()}> |
42 | | - Refresh |
43 | | - </VscodeButton> |
| 82 | + <div className="tasks-panel"> |
| 83 | + <VscodeCollapsible |
| 84 | + ref={createRef} |
| 85 | + heading="Create new task" |
| 86 | + open={createOpen} |
| 87 | + > |
| 88 | + <VscodeScrollable ref={createScrollRef}> |
| 89 | + <CreateTaskSection templates={templates} /> |
| 90 | + </VscodeScrollable> |
| 91 | + </VscodeCollapsible> |
| 92 | + |
| 93 | + <VscodeCollapsible |
| 94 | + ref={historyRef} |
| 95 | + heading="Task History" |
| 96 | + open={historyOpen} |
| 97 | + > |
| 98 | + <VscodeScrollable ref={historyScrollRef}> |
| 99 | + <TaskList |
| 100 | + tasks={tasks} |
| 101 | + onSelectTask={(_taskId: string) => { |
| 102 | + // Task detail view will be added in next PR |
| 103 | + }} |
| 104 | + /> |
| 105 | + </VscodeScrollable> |
| 106 | + </VscodeCollapsible> |
44 | 107 | </div> |
45 | 108 | ); |
46 | 109 | } |
0 commit comments