From cb19378766908bf2ac859ed62558053176188022 Mon Sep 17 00:00:00 2001 From: dusk Date: Wed, 19 Nov 2025 14:00:59 +0000 Subject: [PATCH] feat: nah make it toggle to record on tap, hold otherwise --- src/App.tsx | 19 +++---------------- src/components/MicRecorder.tsx | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------- src/components/Settings.tsx | 18 ------------------ src/lib/settings.ts | 14 -------------- 4 file(s) changed, 56 insertion(s)(+), 74 deletion(s)(-) diff --git a/src/App.tsx b/src/App.tsx --- a/src/App.tsx +++ b/src/App.tsx @@ -1,18 +1,10 @@ -import { createSignal, For } from "solid-js"; +import { For } from "solid-js"; -import { - CheckIcon, - ChevronsUpDownIcon, - ClipboardIcon, - HeartIcon, - MicIcon, - Trash2Icon, -} from "lucide-solid"; +import { CheckIcon, ChevronsUpDownIcon } from "lucide-solid"; import { Button } from "./components/ui/button"; import { Card } from "./components/ui/card"; import { Stack, Box, StackProps, HStack, VStack } from "styled-system/jsx"; import { FileUpload } from "./components/ui/file-upload"; -import { IconButton } from "./components/ui/icon-button"; import { Text } from "./components/ui/text"; import { AtprotoDid } from "@atcute/lexicons/syntax"; @@ -30,8 +22,6 @@ import Settings from "./components/Settings"; import MicRecorder from "./components/MicRecorder"; import { Link } from "./components/ui/link"; -import { css } from "styled-system/css"; -import { toggleToRecord } from "./lib/settings"; const App = () => { const collection = () => @@ -274,10 +264,7 @@ )} /> - + {/* diff --git a/src/components/MicRecorder.tsx b/src/components/MicRecorder.tsx --- a/src/components/MicRecorder.tsx +++ b/src/components/MicRecorder.tsx @@ -9,7 +9,6 @@ type MicRecorderProps = { selectedAccount: () => AtprotoDid | undefined; - holdToRecord?: boolean; }; const MicRecorder = (props: MicRecorderProps) => { @@ -23,6 +22,9 @@ let mediaStream: MediaStream | null = null; let audioChunks: Blob[] = []; + // Flag to handle case where user releases hold before recording actually starts + let stopRequestPending = false; + const isSafari = typeof navigator !== "undefined" && navigator.vendor && @@ -35,6 +37,7 @@ const startRecording = async () => { if (isRecording()) return; + stopRequestPending = false; try { audioChunks = []; @@ -55,6 +58,14 @@ echoCancellation: { ideal: true }, }, }); + + // check if holding stopped while waiting for permission/stream + if (stopRequestPending) { + mediaStream.getTracks().forEach((track) => track.stop()); + mediaStream = null; + return; + } + const audioTrack = mediaStream.getAudioTracks()[0] ?? null; if (!audioTrack) throw "no audio track found"; @@ -129,6 +140,9 @@ setIsRecording(true); setRecordingStart(Date.now()); + + // delayed hold release + if (stopRequestPending) stopRecording(); } catch (error) { console.error("error accessing microphone:", error); toaster.create({ @@ -145,7 +159,10 @@ }; const stopRecording = () => { - if (!isRecording() || !mediaRecorder) return; + if (!isRecording() || !mediaRecorder) { + stopRequestPending = true; + return; + } if (mediaRecorder.state !== "inactive") mediaRecorder.stop(); setIsRecording(false); }; @@ -162,6 +179,36 @@ return `${mins}:${secs.toString().padStart(2, "0")}`; }; + let pressStartTime = 0; + let startedSession = false; + + const handlePointerDown = (e: PointerEvent) => { + if (isRecording()) { + stopRecording(); + startedSession = false; + } else { + startRecording(); + pressStartTime = Date.now(); + startedSession = true; + } + }; + + const handlePointerUp = (e: PointerEvent) => { + if (startedSession) { + const duration = Date.now() - pressStartTime; + if (duration >= 500) stopRecording(); + + startedSession = false; + } + }; + + const handlePointerLeave = (e: PointerEvent) => { + if (startedSession && isRecording()) { + stopRecording(); + startedSession = false; + } + }; + return ( (isRecording() ? stopRecording() : startRecording()) - : undefined - } - onMouseDown={props.holdToRecord ? startRecording : undefined} - onMouseUp={props.holdToRecord ? stopRecording : undefined} - onMouseLeave={props.holdToRecord ? stopRecording : undefined} - onTouchStart={ - props.holdToRecord - ? (e) => { - e.preventDefault(); // Prevent mouse emulation - startRecording(); - } - : undefined - } - onTouchEnd={ - props.holdToRecord - ? (e) => { - e.preventDefault(); - stopRecording(); - } - : undefined - } + onPointerDown={handlePointerDown} + onPointerUp={handlePointerUp} + onPointerLeave={handlePointerLeave} + onContextMenu={(e) => e.preventDefault()} > {isRecording() ? : } diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx --- a/src/components/Settings.tsx +++ b/src/components/Settings.tsx @@ -34,10 +34,7 @@ backgroundColor as backgroundColorSetting, frameRate as frameRateSetting, useDominantColorAsBg as useDominantColorAsBgSetting, - toggleToRecordSetting, Setting, - toggleToRecord, - setToggleToRecord, } from "~/lib/settings"; import { handleResolver } from "~/lib/at"; import { toaster } from "~/components/Toaster"; @@ -354,21 +351,6 @@ - - user interface - - - - processing (key: string) => { return { get: () => { @@ -19,15 +17,3 @@ export const useDominantColorAsBg = setting("useDominantColorAsBg"); export const backgroundColor = setting("backgroundColor"); export const frameRate = setting("frameRate"); - -export const toggleToRecordSetting = setting("toggleToRecord"); -const [_toggleToRecord, _setToggleToRecord] = createSignal( - toggleToRecordSetting.get() ?? false, -); -export const toggleToRecord = _toggleToRecord; -export const setToggleToRecord = ( - value: boolean | ((prev: boolean) => boolean), -) => { - const newAccounts = _setToggleToRecord(value); - toggleToRecordSetting.set(newAccounts); -}; -- tangled.sh