107 lines
2.5 KiB
TypeScript
107 lines
2.5 KiB
TypeScript
import { create } from "zustand";
|
|
|
|
export type ComposeStep =
|
|
| "idle"
|
|
| "recording"
|
|
| "reviewing"
|
|
| "typing"
|
|
| "configuring";
|
|
|
|
export type RecordingMode = "video" | "audio";
|
|
|
|
const RECORDING_MODE_KEY = "llink:recording-mode";
|
|
|
|
function loadRecordingMode(): RecordingMode {
|
|
const stored = localStorage.getItem(RECORDING_MODE_KEY);
|
|
return stored === "audio" ? "audio" : "video";
|
|
}
|
|
|
|
interface ComposeState {
|
|
// Flow
|
|
step: ComposeStep;
|
|
networkId: string | null;
|
|
error: string | null;
|
|
|
|
// Text compose
|
|
textContent: string;
|
|
|
|
// Recording
|
|
recordingMode: RecordingMode;
|
|
mediaStream: MediaStream | null;
|
|
reviewBlob: Blob | null;
|
|
reviewDurationMs: number;
|
|
|
|
// Transitions
|
|
startRecording: (networkId: string) => void;
|
|
finishRecording: (blob: Blob, durationMs: number) => void;
|
|
startTyping: (networkId: string) => void;
|
|
advanceToConfigure: () => void;
|
|
setTextContent: (text: string) => void;
|
|
cancel: () => void;
|
|
|
|
// Recording helpers (called by useRecorder)
|
|
setMediaStream: (stream: MediaStream | null) => void;
|
|
setRecordingMode: (mode: RecordingMode) => void;
|
|
setError: (error: string) => void;
|
|
}
|
|
|
|
export const useComposeStore = create<ComposeState>((set, get) => ({
|
|
step: "idle",
|
|
networkId: null,
|
|
error: null,
|
|
textContent: "",
|
|
recordingMode: loadRecordingMode(),
|
|
mediaStream: null,
|
|
reviewBlob: null,
|
|
reviewDurationMs: 0,
|
|
|
|
startRecording: (networkId) => {
|
|
if (get().step !== "idle") return;
|
|
set({
|
|
step: "recording",
|
|
networkId,
|
|
error: null,
|
|
reviewBlob: null,
|
|
reviewDurationMs: 0,
|
|
});
|
|
},
|
|
|
|
finishRecording: (blob, durationMs) => {
|
|
if (get().step !== "recording") return;
|
|
set({ step: "reviewing", reviewBlob: blob, reviewDurationMs: durationMs });
|
|
},
|
|
|
|
startTyping: (networkId) => {
|
|
if (get().step !== "idle") return;
|
|
set({ step: "typing", networkId, textContent: "", error: null });
|
|
},
|
|
|
|
advanceToConfigure: () => {
|
|
const { step } = get();
|
|
if (step !== "reviewing" && step !== "typing") return;
|
|
set({ step: "configuring" });
|
|
},
|
|
|
|
setTextContent: (textContent) => set({ textContent }),
|
|
|
|
cancel: () =>
|
|
set({
|
|
step: "idle",
|
|
networkId: null,
|
|
error: null,
|
|
textContent: "",
|
|
mediaStream: null,
|
|
reviewBlob: null,
|
|
reviewDurationMs: 0,
|
|
}),
|
|
|
|
setMediaStream: (mediaStream) => set({ mediaStream }),
|
|
|
|
setRecordingMode: (mode) => {
|
|
localStorage.setItem(RECORDING_MODE_KEY, mode);
|
|
set({ recordingMode: mode });
|
|
},
|
|
|
|
setError: (error) => set({ error }),
|
|
}));
|