+2
-1
src/App.tsx
+2
-1
src/App.tsx
···
162
const Tasks = (props: TasksProps) => (
163
<Stack
164
border="1px solid var(--colors-border-subtle)"
165
gap="1.5"
166
p="2"
167
rounded="sm"
···
210
const Upload = (props: FileUpload.RootProps) => {
211
return (
212
<FileUpload.Root maxFiles={100} {...props}>
213
-
<FileUpload.Dropzone>
214
<FileUpload.Label>drop your files here</FileUpload.Label>
215
<HStack alignItems="center">
216
<FileUpload.Trigger
···
162
const Tasks = (props: TasksProps) => (
163
<Stack
164
border="1px solid var(--colors-border-subtle)"
165
+
borderBottomWidth="3px"
166
gap="1.5"
167
p="2"
168
rounded="sm"
···
211
const Upload = (props: FileUpload.RootProps) => {
212
return (
213
<FileUpload.Root maxFiles={100} {...props}>
214
+
<FileUpload.Dropzone borderBottomWidth="3px">
215
<FileUpload.Label>drop your files here</FileUpload.Label>
216
<HStack alignItems="center">
217
<FileUpload.Trigger
+59
-27
src/components/FileTask.tsx
+59
-27
src/components/FileTask.tsx
···
1
-
import { CircleAlertIcon, DownloadIcon, SendIcon } from "lucide-solid";
2
import { Stack } from "styled-system/jsx";
3
import { IconButton } from "~/components/ui/icon-button";
4
import { Spinner } from "~/components/ui/spinner";
···
9
10
import { TaskState } from "~/lib/task";
11
import PostDialog from "./PostDialog";
12
13
const downloadFile = (blob: Blob, fileName: string) => {
14
const url = URL.createObjectURL(blob);
···
23
};
24
25
const Task = (process: TaskState, selectedAccount: Account | undefined) => {
26
const statusError = (error: string) => (
27
<Popover.Root>
28
<Popover.Trigger
···
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
};
···
104
<Stack
105
direction="row"
106
border="1px solid var(--colors-border-muted)"
107
gap="2"
108
align="center"
109
rounded="sm"
···
1
+
import {
2
+
CircleAlertIcon,
3
+
DownloadIcon,
4
+
EllipsisVerticalIcon,
5
+
SendIcon,
6
+
} from "lucide-solid";
7
import { Stack } from "styled-system/jsx";
8
import { IconButton } from "~/components/ui/icon-button";
9
import { Spinner } from "~/components/ui/spinner";
···
14
15
import { TaskState } from "~/lib/task";
16
import PostDialog from "./PostDialog";
17
+
import { Button } from "./ui/button";
18
+
import { Menu } from "./ui/menu";
19
+
import { createSignal } from "solid-js";
20
21
const downloadFile = (blob: Blob, fileName: string) => {
22
const url = URL.createObjectURL(blob);
···
31
};
32
33
const Task = (process: TaskState, selectedAccount: Account | undefined) => {
34
+
const [dialogOpen, setDialogOpen] = createSignal(false);
35
const statusError = (error: string) => (
36
<Popover.Root>
37
<Popover.Trigger
···
56
const statusSuccess = (result: Blob) => {
57
return (
58
<>
59
<PostDialog
60
+
openSignal={[dialogOpen, setDialogOpen]}
61
account={selectedAccount}
62
result={result}
63
/>
64
+
<Menu.Root
65
+
positioning={{ placement: "bottom-start", strategy: "fixed" }}
66
+
>
67
+
<Menu.Trigger
68
+
asChild={(triggerProps) => (
69
+
<IconButton {...triggerProps()} variant="ghost">
70
+
<EllipsisVerticalIcon />
71
+
</IconButton>
72
+
)}
73
+
/>
74
+
<Menu.Positioner>
75
+
<Menu.Content>
76
+
<Menu.ItemGroup>
77
+
<Button
78
+
color={{ _hover: "colorPalette.emphasized" }}
79
+
onClick={() =>
80
+
downloadFile(
81
+
result,
82
+
process.file.name
83
+
.split(".")
84
+
.slice(0, -1)
85
+
.join(".")
86
+
.concat(".mp4"),
87
+
)
88
+
}
89
+
variant="ghost"
90
+
display="flex"
91
+
justifyContent="space-between"
92
+
alignItems="center"
93
+
>
94
+
download <DownloadIcon />
95
+
</Button>
96
+
<Button
97
+
onClick={() => setDialogOpen(!dialogOpen())}
98
+
disabled={selectedAccount === undefined}
99
+
color={{ _hover: "colorPalette.emphasized" }}
100
+
variant="ghost"
101
+
display="flex"
102
+
justifyContent="space-between"
103
+
alignItems="center"
104
+
>
105
+
post to bsky <SendIcon />
106
+
</Button>
107
+
</Menu.ItemGroup>
108
+
</Menu.Content>
109
+
</Menu.Positioner>
110
+
</Menu.Root>
111
</>
112
);
113
};
···
135
<Stack
136
direction="row"
137
border="1px solid var(--colors-border-muted)"
138
+
borderBottomWidth="2px"
139
gap="2"
140
align="center"
141
rounded="sm"
+7
-5
src/components/MicRecorder.tsx
+7
-5
src/components/MicRecorder.tsx
···
99
mediaRecorder?.mimeType || mimeType || fallbackMimeType;
100
const fileExtension = usedMime.split("/")[1]?.split(";")[0] || "webm";
101
const blob = new Blob(audioChunks, { type: usedMime });
102
-
const file = new File(
103
-
[blob],
104
-
`rec-${new Date().toISOString().replace(/:/g, "-")}.${fileExtension}`,
105
-
{ type: usedMime },
106
-
);
107
108
addTask(props.selectedAccount(), file);
109
audioChunks = [];
···
99
mediaRecorder?.mimeType || mimeType || fallbackMimeType;
100
const fileExtension = usedMime.split("/")[1]?.split(";")[0] || "webm";
101
const blob = new Blob(audioChunks, { type: usedMime });
102
+
const fileDate = new Date()
103
+
.toLocaleTimeString()
104
+
.replace(/:/g, "-")
105
+
.replace(/\s+/g, "_");
106
+
const file = new File([blob], `rec-${fileDate}.${fileExtension}`, {
107
+
type: usedMime,
108
+
});
109
110
addTask(props.selectedAccount(), file);
111
audioChunks = [];
+3
-6
src/components/PostDialog.tsx
+3
-6
src/components/PostDialog.tsx
···
1
-
import { Component, createSignal } from "solid-js";
2
3
import { SendIcon, XIcon } from "lucide-solid";
4
import { Stack } from "styled-system/jsx";
···
16
import { Account } from "~/lib/accounts";
17
18
const PostDialog = (props: {
19
-
trigger: Component;
20
result: Blob;
21
account: Account | undefined;
22
}) => {
23
const [postContent, setPostContent] = createSignal<string>("");
24
const [posting, setPosting] = createSignal(false);
25
-
const [open, setOpen] = createSignal(false);
26
27
return (
28
<Dialog.Root open={open()} onOpenChange={(e) => setOpen(e.open)}>
29
-
<Dialog.Trigger
30
-
asChild={(triggerProps) => <props.trigger {...triggerProps()} />}
31
-
/>
32
<Dialog.Backdrop />
33
<Dialog.Positioner>
34
<Dialog.Content>
···
1
+
import { Component, createSignal, Signal } from "solid-js";
2
3
import { SendIcon, XIcon } from "lucide-solid";
4
import { Stack } from "styled-system/jsx";
···
16
import { Account } from "~/lib/accounts";
17
18
const PostDialog = (props: {
19
result: Blob;
20
account: Account | undefined;
21
+
openSignal: Signal<boolean>;
22
}) => {
23
const [postContent, setPostContent] = createSignal<string>("");
24
const [posting, setPosting] = createSignal(false);
25
+
const [open, setOpen] = props.openSignal;
26
27
return (
28
<Dialog.Root open={open()} onOpenChange={(e) => setOpen(e.open)}>
29
<Dialog.Backdrop />
30
<Dialog.Positioner>
31
<Dialog.Content>
+16
-11
src/components/Settings.tsx
+16
-11
src/components/Settings.tsx
···
224
};
225
226
const Accounts = () => {
227
-
const item = (account: Account) => (
228
<Stack
229
direction="row"
230
w="full"
231
px="2"
232
pb="2"
233
-
borderBottom="1px solid var(--colors-border-muted)"
234
align="center"
235
>
236
{account.handle ? `@${account.handle}` : account.did}
···
253
</Text>
254
}
255
>
256
-
{item}
257
</For>
258
);
259
return (
260
<Stack>
261
<FormLabel>accounts</FormLabel>
262
-
<Stack border="1px solid var(--colors-border-default)" rounded="xs">
263
<Stack
264
borderBottom="1px solid var(--colors-border-default)"
265
p="2"
···
350
<Stack
351
gap="0"
352
border="1px solid var(--colors-border-default)"
353
rounded="xs"
354
>
355
<Box borderBottom="1px solid var(--colors-border-subtle)">
···
376
signal={[backgroundColor, setBackgroundColor]}
377
/>
378
</Stack>
379
-
<Box borderBottom="1px solid var(--colors-border-muted)">
380
-
<SettingSelect
381
-
label="frame rate"
382
-
signal={[frameRate, setFrameRate]}
383
-
collection={frameRateCollection}
384
-
/>
385
-
</Box>
386
</Stack>
387
</Stack>
388
</Stack>
···
224
};
225
226
const Accounts = () => {
227
+
const item = (account: Account, isLatest: boolean) => (
228
<Stack
229
direction="row"
230
w="full"
231
px="2"
232
pb="2"
233
+
borderBottom={
234
+
!isLatest ? "1px solid var(--colors-border-muted)" : undefined
235
+
}
236
align="center"
237
>
238
{account.handle ? `@${account.handle}` : account.did}
···
255
</Text>
256
}
257
>
258
+
{(acc, idx) => item(acc, idx() === accounts.length - 1)}
259
</For>
260
);
261
return (
262
<Stack>
263
<FormLabel>accounts</FormLabel>
264
+
<Stack
265
+
border="1px solid var(--colors-border-default)"
266
+
borderBottomWidth="3px"
267
+
rounded="xs"
268
+
>
269
<Stack
270
borderBottom="1px solid var(--colors-border-default)"
271
p="2"
···
356
<Stack
357
gap="0"
358
border="1px solid var(--colors-border-default)"
359
+
borderBottomWidth="3px"
360
rounded="xs"
361
>
362
<Box borderBottom="1px solid var(--colors-border-subtle)">
···
383
signal={[backgroundColor, setBackgroundColor]}
384
/>
385
</Stack>
386
+
<SettingSelect
387
+
label="frame rate"
388
+
signal={[frameRate, setFrameRate]}
389
+
collection={frameRateCollection}
390
+
/>
391
</Stack>
392
</Stack>
393
</Stack>
+5
src/index.css
+5
src/index.css
+5
-4
src/lib/render.ts
+5
-4
src/lib/render.ts
···
336
drawBackground();
337
338
if (pfpImg) {
339
-
const pfpSize = Math.min(renderCanvas.width, renderCanvas.height) * 0.5;
340
-
const pfpX = (renderCanvas.width - pfpSize) / 2;
341
-
const pfpY = (renderCanvas.height - pfpSize) / 2;
342
343
-
drawPfp(ctx, pfpImg, pfpX, pfpY, pfpSize);
344
}
345
346
await videoSource.add(0, duration);
···
336
drawBackground();
337
338
if (pfpImg) {
339
+
const centerX = renderCanvas.width / 2;
340
+
const centerY = renderCanvas.height / 2;
341
+
const baseRadius =
342
+
Math.min(renderCanvas.width, renderCanvas.height) * 0.15;
343
344
+
drawPfp(ctx, pfpImg, centerX, centerY, baseRadius);
345
}
346
347
await videoSource.add(0, duration);