introduce stream compose flow
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
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 }),
|
||||
}));
|
||||
Reference in New Issue
Block a user