creates video voice memos from audio clips; with bluesky integration.
trill.ptr.pet
1import { CircleAlertIcon, DownloadIcon, SendIcon } from "lucide-solid";
2import { Stack } from "styled-system/jsx";
3import { IconButton } from "~/components/ui/icon-button";
4import { Spinner } from "~/components/ui/spinner";
5import { Popover } from "~/components/ui/popover";
6
7import { css } from "styled-system/css";
8import { Account } from "~/lib/accounts";
9
10import { TaskState } from "~/lib/task";
11import PostDialog from "./PostDialog";
12
13const downloadFile = (blob: Blob, fileName: string) => {
14 const url = URL.createObjectURL(blob);
15 const a = document.createElement("a");
16 a.href = url;
17 // handle file names with periods in them
18 a.download = fileName;
19 document.body.appendChild(a);
20 a.click();
21 document.body.removeChild(a);
22 URL.revokeObjectURL(url);
23};
24
25const Task = (process: TaskState, selectedAccount: Account | undefined) => {
26 const statusError = (error: string) => (
27 <Popover.Root>
28 <Popover.Trigger
29 asChild={(triggerProps) => (
30 <IconButton
31 {...triggerProps()}
32 color={{
33 base: "red",
34 _hover: "red.emphasized",
35 }}
36 variant="ghost"
37 >
38 <CircleAlertIcon />
39 </IconButton>
40 )}
41 />
42 <Popover.Positioner>
43 <Popover.Content>error processing file: {error}</Popover.Content>
44 </Popover.Positioner>
45 </Popover.Root>
46 );
47 const statusSuccess = (result: Blob) => {
48 return (
49 <>
50 <IconButton
51 color={{ _hover: "colorPalette.emphasized" }}
52 onClick={() =>
53 downloadFile(
54 result,
55 process.file.name
56 .split(".")
57 .slice(0, -1)
58 .join(".")
59 .concat(".mp4"),
60 )
61 }
62 variant="ghost"
63 >
64 <DownloadIcon />
65 </IconButton>
66 <PostDialog
67 trigger={(props) => (
68 <IconButton
69 {...props}
70 disabled={selectedAccount === undefined}
71 color={{ _hover: "colorPalette.emphasized" }}
72 variant="ghost"
73 >
74 <SendIcon />
75 </IconButton>
76 )}
77 account={selectedAccount}
78 result={result}
79 />
80 </>
81 );
82 };
83 const statusProcessing = () => (
84 <Spinner
85 borderLeftColor="bg.emphasized"
86 borderBottomColor="bg.emphasized"
87 borderWidth="4px"
88 m="2"
89 />
90 );
91
92 const status = () => {
93 switch (process.status) {
94 case "success":
95 return statusSuccess(process.result);
96 case "processing":
97 return statusProcessing();
98 default:
99 return statusError(process.error);
100 }
101 };
102
103 return (
104 <Stack
105 direction="row"
106 border="1px solid var(--colors-border-muted)"
107 gap="2"
108 align="center"
109 rounded="sm"
110 >
111 <span
112 class={css({
113 overflow: "hidden",
114 textOverflow: "ellipsis",
115 whiteSpace: "nowrap",
116 pl: 2,
117 })}
118 >
119 {process.file.name}
120 </span>
121 <div class={css({ flexGrow: 1 })}></div>
122 <Stack direction="row" gap="0" flexShrink="0" align="center">
123 {status()}
124 </Stack>
125 </Stack>
126 );
127};
128
129export default Task;