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