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;