fix(space): HUD 시작 흐름과 컨트롤 상태를 정리
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import {
|
||||
getSceneBackgroundStyle,
|
||||
@@ -24,6 +24,7 @@ import { SpaceToolsDockWidget } from '@/widgets/space-tools-dock';
|
||||
import { FocusTopToast } from './FocusTopToast';
|
||||
|
||||
type WorkspaceMode = 'setup' | 'focus';
|
||||
type SessionEntryPoint = 'space-setup' | 'goal-complete' | 'resume-restore';
|
||||
type SelectionOverride = {
|
||||
sound: boolean;
|
||||
timer: boolean;
|
||||
@@ -188,11 +189,14 @@ export const SpaceWorkspaceWidget = () => {
|
||||
const [resumeGoal, setResumeGoal] = useState('');
|
||||
const [showResumePrompt, setShowResumePrompt] = useState(false);
|
||||
const [hasHydratedSelection, setHasHydratedSelection] = useState(false);
|
||||
const [previewPlaybackState, setPreviewPlaybackState] = useState<'running' | 'paused'>('running');
|
||||
const [previewPlaybackState, setPreviewPlaybackState] = useState<'running' | 'paused'>('paused');
|
||||
const [pendingSessionEntryPoint, setPendingSessionEntryPoint] =
|
||||
useState<SessionEntryPoint>('space-setup');
|
||||
const [selectionOverride, setSelectionOverride] = useState<SelectionOverride>({
|
||||
sound: false,
|
||||
timer: false,
|
||||
});
|
||||
const queuedFocusStatusMessageRef = useRef<string | null>(null);
|
||||
|
||||
const {
|
||||
selectedPresetId,
|
||||
@@ -235,6 +239,9 @@ export const SpaceWorkspaceWidget = () => {
|
||||
const { activeStatus, pushStatusLine, runActiveAction } = useHudStatusLine(isFocusMode);
|
||||
const resolvedPlaybackState = playbackState ?? previewPlaybackState;
|
||||
const resolvedTimeDisplay = timeDisplay ?? resolveFocusTimeDisplayFromTimerLabel(selectedTimerLabel);
|
||||
const canStartSession = canStart && (!currentSession || resolvedPlaybackState !== 'running');
|
||||
const canPauseSession = Boolean(currentSession && resolvedPlaybackState === 'running');
|
||||
const canRestartSession = Boolean(currentSession);
|
||||
|
||||
const applyRecommendedSelections = useCallback((
|
||||
sceneId: string,
|
||||
@@ -391,42 +398,75 @@ export const SpaceWorkspaceWidget = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const startFocusFlow = async (
|
||||
const openFocusMode = (
|
||||
nextGoal: string,
|
||||
entryPoint: 'space-setup' | 'goal-complete' | 'resume-restore' = 'space-setup',
|
||||
entryPoint: SessionEntryPoint = 'space-setup',
|
||||
) => {
|
||||
const trimmedGoal = nextGoal.trim();
|
||||
|
||||
if (!trimmedGoal) {
|
||||
return;
|
||||
}
|
||||
|
||||
setShowResumePrompt(false);
|
||||
setPendingSessionEntryPoint(entryPoint);
|
||||
setPreviewPlaybackState('paused');
|
||||
setWorkspaceMode('focus');
|
||||
queuedFocusStatusMessageRef.current = '준비 완료 · 시작 버튼을 눌러 집중을 시작해요.';
|
||||
};
|
||||
|
||||
const startFocusFlow = async () => {
|
||||
const trimmedGoal = goalInput.trim();
|
||||
const timerPresetId = resolveTimerPresetIdFromLabel(selectedTimerLabel);
|
||||
|
||||
if (!trimmedGoal || !timerPresetId) {
|
||||
return;
|
||||
}
|
||||
|
||||
setShowResumePrompt(false);
|
||||
setPreviewPlaybackState('running');
|
||||
setWorkspaceMode('focus');
|
||||
|
||||
const startedSession = await startSession({
|
||||
sceneId: selectedSceneId,
|
||||
goal: trimmedGoal,
|
||||
timerPresetId,
|
||||
soundPresetId: selectedPresetId,
|
||||
entryPoint,
|
||||
entryPoint: pendingSessionEntryPoint,
|
||||
});
|
||||
|
||||
if (!startedSession) {
|
||||
pushStatusLine({
|
||||
message: '세션 API 연결 실패 · 로컬 미리보기 모드로 계속해요.',
|
||||
});
|
||||
if (startedSession) {
|
||||
setPreviewPlaybackState('running');
|
||||
return;
|
||||
}
|
||||
|
||||
setPreviewPlaybackState('paused');
|
||||
pushStatusLine({
|
||||
message: '세션을 시작하지 못했어요. 잠시 후 다시 시도해 주세요.',
|
||||
});
|
||||
};
|
||||
|
||||
const handleStart = () => {
|
||||
const handleSetupFocusOpen = () => {
|
||||
if (!canStart) {
|
||||
return;
|
||||
}
|
||||
|
||||
void startFocusFlow(goalInput, 'space-setup');
|
||||
openFocusMode(goalInput, 'space-setup');
|
||||
};
|
||||
|
||||
const handleStartRequested = async () => {
|
||||
if (!canStartSession) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentSession) {
|
||||
await startFocusFlow();
|
||||
return;
|
||||
}
|
||||
|
||||
const resumedSession = await resumeSession();
|
||||
|
||||
if (!resumedSession) {
|
||||
pushStatusLine({
|
||||
message: '세션을 다시 시작하지 못했어요.',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleExitRequested = async () => {
|
||||
@@ -439,7 +479,8 @@ export const SpaceWorkspaceWidget = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
setPreviewPlaybackState('running');
|
||||
setPreviewPlaybackState('paused');
|
||||
setPendingSessionEntryPoint('space-setup');
|
||||
setWorkspaceMode('setup');
|
||||
};
|
||||
|
||||
@@ -458,26 +499,8 @@ export const SpaceWorkspaceWidget = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleResumeRequested = async () => {
|
||||
if (!currentSession) {
|
||||
setPreviewPlaybackState('running');
|
||||
return;
|
||||
}
|
||||
|
||||
const resumedSession = await resumeSession();
|
||||
|
||||
if (!resumedSession) {
|
||||
pushStatusLine({
|
||||
message: '세션을 다시 시작하지 못했어요.',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleRestartRequested = async () => {
|
||||
if (!currentSession) {
|
||||
pushStatusLine({
|
||||
message: '실제 세션이 시작된 뒤에만 다시 시작할 수 있어요.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -512,12 +535,17 @@ export const SpaceWorkspaceWidget = () => {
|
||||
pushStatusLine({
|
||||
message: '현재 세션 완료를 서버에 반영하지 못했어요.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setGoalInput(trimmedNextGoal);
|
||||
setSelectedGoalId(null);
|
||||
void startFocusFlow(trimmedNextGoal, 'goal-complete');
|
||||
setPendingSessionEntryPoint('goal-complete');
|
||||
setPreviewPlaybackState('paused');
|
||||
pushStatusLine({
|
||||
message: '다음 한 조각 준비 완료 · 시작 버튼을 눌러 이어가요.',
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -561,6 +589,18 @@ export const SpaceWorkspaceWidget = () => {
|
||||
);
|
||||
}, [goalInput, hasHydratedSelection, resumeGoal, selectedSceneId, selectedTimerLabel, selectedPresetId, selectionOverride, showResumePrompt]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isFocusMode || !queuedFocusStatusMessageRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const message = queuedFocusStatusMessageRef.current;
|
||||
queuedFocusStatusMessageRef.current = null;
|
||||
pushStatusLine({
|
||||
message,
|
||||
});
|
||||
}, [isFocusMode, pushStatusLine]);
|
||||
|
||||
return (
|
||||
<div className="relative h-dvh overflow-hidden text-white">
|
||||
<div
|
||||
@@ -590,7 +630,7 @@ export const SpaceWorkspaceWidget = () => {
|
||||
onSoundSelect={(presetId) => handleSelectSound(presetId, true)}
|
||||
onGoalChange={handleGoalChange}
|
||||
onGoalChipSelect={handleGoalChipSelect}
|
||||
onStart={handleStart}
|
||||
onStart={handleSetupFocusOpen}
|
||||
resumeHint={
|
||||
showResumePrompt && resumeGoal
|
||||
? {
|
||||
@@ -599,7 +639,7 @@ export const SpaceWorkspaceWidget = () => {
|
||||
setGoalInput(resumeGoal);
|
||||
setSelectedGoalId(null);
|
||||
setShowResumePrompt(false);
|
||||
void startFocusFlow(resumeGoal, 'resume-restore');
|
||||
openFocusMode(resumeGoal, 'resume-restore');
|
||||
},
|
||||
onStartFresh: () => {
|
||||
setGoalInput('');
|
||||
@@ -616,15 +656,19 @@ export const SpaceWorkspaceWidget = () => {
|
||||
timerLabel={selectedTimerLabel}
|
||||
timeDisplay={resolvedTimeDisplay}
|
||||
visible={isFocusMode}
|
||||
hasActiveSession={Boolean(currentSession)}
|
||||
playbackState={resolvedPlaybackState}
|
||||
sessionPhase={phase ?? 'focus'}
|
||||
isSessionActionPending={isSessionMutating}
|
||||
canStartSession={canStartSession}
|
||||
canPauseSession={canPauseSession}
|
||||
canRestartSession={canRestartSession}
|
||||
onStartRequested={() => {
|
||||
void handleStartRequested();
|
||||
}}
|
||||
onPauseRequested={() => {
|
||||
void handlePauseRequested();
|
||||
}}
|
||||
onResumeRequested={() => {
|
||||
void handleResumeRequested();
|
||||
}}
|
||||
onRestartRequested={() => {
|
||||
void handleRestartRequested();
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user