feat(curation): Scene 추천 자동 적용과 override 존중 규칙 도입
맥락: - 목표 입력 후 바로 시작할 수 있도록 Scene 기반 추천값을 자동으로 채우되, 사용자가 직접 바꾼 값은 유지해야 했습니다. 변경사항: - RoomTheme에 recommendedSoundPresetId, recommendedTimerPresetId 필드를 추가하고 각 Scene 더미 데이터에 추천 preset id를 매핑했습니다. - /space 초기 진입 시 선택된 Scene 추천값으로 타이머/사운드 기본값이 설정되도록 초기화 로직을 정리했습니다. - /space 상태에 override.sound/override.timer 플래그를 추가하고, Scene 변경 시 override가 false인 항목만 자동 동기화하도록 반영했습니다. - 추천 복원 액션(추천으로 되돌림)을 위한 핸들러/props 경로를 workspace -> tools-dock -> control-center까지 연결했습니다. 검증: - npx tsc --noEmit 세션-상태: Scene 추천 자동 적용과 override 기반 자동 동기화가 동작합니다. 세션-다음: Control Center를 Scene/Time 중심 구조로 단순화하고 추천 정보/되돌리기 UI를 적용합니다. 세션-리스크: Control Center UI가 아직 기존 구조(Sound/Pack 포함)라 다음 커밋에서 정리가 필요합니다.
This commit is contained in:
@@ -18,6 +18,8 @@ export const ROOM_THEMES: RoomTheme[] = [
|
|||||||
description: '빗소리 위로 스탠드 조명이 부드럽게 번집니다.',
|
description: '빗소리 위로 스탠드 조명이 부드럽게 번집니다.',
|
||||||
tags: ['저자극', '감성'],
|
tags: ['저자극', '감성'],
|
||||||
recommendedSound: 'Rain Focus',
|
recommendedSound: 'Rain Focus',
|
||||||
|
recommendedSoundPresetId: 'rain-focus',
|
||||||
|
recommendedTimerPresetId: '25-5',
|
||||||
recommendedTime: '밤',
|
recommendedTime: '밤',
|
||||||
vibeLabel: '잔잔함',
|
vibeLabel: '잔잔함',
|
||||||
hubColor: '#D6E6F7',
|
hubColor: '#D6E6F7',
|
||||||
@@ -38,6 +40,8 @@ export const ROOM_THEMES: RoomTheme[] = [
|
|||||||
description: '첫 커피 향처럼 잔잔하고 따뜻한 좌석.',
|
description: '첫 커피 향처럼 잔잔하고 따뜻한 좌석.',
|
||||||
tags: ['감성', '딥워크'],
|
tags: ['감성', '딥워크'],
|
||||||
recommendedSound: 'Cafe Murmur',
|
recommendedSound: 'Cafe Murmur',
|
||||||
|
recommendedSoundPresetId: 'cafe-work',
|
||||||
|
recommendedTimerPresetId: '25-5',
|
||||||
recommendedTime: '새벽',
|
recommendedTime: '새벽',
|
||||||
vibeLabel: '포근함',
|
vibeLabel: '포근함',
|
||||||
hubColor: '#F5DDCB',
|
hubColor: '#F5DDCB',
|
||||||
@@ -58,6 +62,8 @@ export const ROOM_THEMES: RoomTheme[] = [
|
|||||||
description: '넘기는 종이 소리만 들리는 정돈된 책상.',
|
description: '넘기는 종이 소리만 들리는 정돈된 책상.',
|
||||||
tags: ['저자극', '딥워크'],
|
tags: ['저자극', '딥워크'],
|
||||||
recommendedSound: 'Deep White',
|
recommendedSound: 'Deep White',
|
||||||
|
recommendedSoundPresetId: 'deep-white',
|
||||||
|
recommendedTimerPresetId: '50-10',
|
||||||
recommendedTime: '오후',
|
recommendedTime: '오후',
|
||||||
vibeLabel: '몰입',
|
vibeLabel: '몰입',
|
||||||
hubColor: '#DCE4D1',
|
hubColor: '#DCE4D1',
|
||||||
@@ -78,6 +84,8 @@ export const ROOM_THEMES: RoomTheme[] = [
|
|||||||
description: '잔잔한 해변 위로 호흡을 고르는 공간.',
|
description: '잔잔한 해변 위로 호흡을 고르는 공간.',
|
||||||
tags: ['움직임 적음', '감성'],
|
tags: ['움직임 적음', '감성'],
|
||||||
recommendedSound: 'Ocean Breath',
|
recommendedSound: 'Ocean Breath',
|
||||||
|
recommendedSoundPresetId: 'ocean-calm',
|
||||||
|
recommendedTimerPresetId: '25-5',
|
||||||
recommendedTime: '밤',
|
recommendedTime: '밤',
|
||||||
vibeLabel: '차분함',
|
vibeLabel: '차분함',
|
||||||
hubColor: '#CFE9EA',
|
hubColor: '#CFE9EA',
|
||||||
@@ -98,6 +106,8 @@ export const ROOM_THEMES: RoomTheme[] = [
|
|||||||
description: '바람이 나뭇잎을 스치는 소리로 마음을 낮춥니다.',
|
description: '바람이 나뭇잎을 스치는 소리로 마음을 낮춥니다.',
|
||||||
tags: ['저자극', '움직임 적음'],
|
tags: ['저자극', '움직임 적음'],
|
||||||
recommendedSound: 'Forest Hush',
|
recommendedSound: 'Forest Hush',
|
||||||
|
recommendedSoundPresetId: 'rain-focus',
|
||||||
|
recommendedTimerPresetId: '50-10',
|
||||||
recommendedTime: '오전',
|
recommendedTime: '오전',
|
||||||
vibeLabel: '맑음',
|
vibeLabel: '맑음',
|
||||||
hubColor: '#D1E7C9',
|
hubColor: '#D1E7C9',
|
||||||
@@ -118,6 +128,8 @@ export const ROOM_THEMES: RoomTheme[] = [
|
|||||||
description: '작은 불꽃이 주는 리듬으로 집중을 붙잡습니다.',
|
description: '작은 불꽃이 주는 리듬으로 집중을 붙잡습니다.',
|
||||||
tags: ['감성', '저자극'],
|
tags: ['감성', '저자극'],
|
||||||
recommendedSound: 'Fireplace',
|
recommendedSound: 'Fireplace',
|
||||||
|
recommendedSoundPresetId: 'fireplace',
|
||||||
|
recommendedTimerPresetId: '25-5',
|
||||||
recommendedTime: '밤',
|
recommendedTime: '밤',
|
||||||
vibeLabel: '온기',
|
vibeLabel: '온기',
|
||||||
hubColor: '#F2D4C0',
|
hubColor: '#F2D4C0',
|
||||||
@@ -138,6 +150,8 @@ export const ROOM_THEMES: RoomTheme[] = [
|
|||||||
description: '유리창 너머 야경이 멀리 흐르는 고요한 밤.',
|
description: '유리창 너머 야경이 멀리 흐르는 고요한 밤.',
|
||||||
tags: ['딥워크', '감성'],
|
tags: ['딥워크', '감성'],
|
||||||
recommendedSound: 'Night Lo-fi',
|
recommendedSound: 'Night Lo-fi',
|
||||||
|
recommendedSoundPresetId: 'deep-white',
|
||||||
|
recommendedTimerPresetId: '50-10',
|
||||||
recommendedTime: '심야',
|
recommendedTime: '심야',
|
||||||
vibeLabel: '고요함',
|
vibeLabel: '고요함',
|
||||||
hubColor: '#D9D3ED',
|
hubColor: '#D9D3ED',
|
||||||
@@ -158,6 +172,8 @@ export const ROOM_THEMES: RoomTheme[] = [
|
|||||||
description: '차분한 공기와 선명한 수평선이 머리를 맑게 합니다.',
|
description: '차분한 공기와 선명한 수평선이 머리를 맑게 합니다.',
|
||||||
tags: ['움직임 적음', '딥워크'],
|
tags: ['움직임 적음', '딥워크'],
|
||||||
recommendedSound: 'Cold Wind',
|
recommendedSound: 'Cold Wind',
|
||||||
|
recommendedSoundPresetId: 'deep-white',
|
||||||
|
recommendedTimerPresetId: '50-10',
|
||||||
recommendedTime: '새벽',
|
recommendedTime: '새벽',
|
||||||
vibeLabel: '선명함',
|
vibeLabel: '선명함',
|
||||||
hubColor: '#D8E7F3',
|
hubColor: '#D8E7F3',
|
||||||
@@ -178,6 +194,8 @@ export const ROOM_THEMES: RoomTheme[] = [
|
|||||||
description: '햇살이 들어오는 간결한 책상, 부담 없는 시작.',
|
description: '햇살이 들어오는 간결한 책상, 부담 없는 시작.',
|
||||||
tags: ['저자극', '딥워크'],
|
tags: ['저자극', '딥워크'],
|
||||||
recommendedSound: 'Soft Daylight',
|
recommendedSound: 'Soft Daylight',
|
||||||
|
recommendedSoundPresetId: 'silent',
|
||||||
|
recommendedTimerPresetId: '25-5',
|
||||||
recommendedTime: '오후',
|
recommendedTime: '오후',
|
||||||
vibeLabel: '가벼움',
|
vibeLabel: '가벼움',
|
||||||
hubColor: '#F6EDC7',
|
hubColor: '#F6EDC7',
|
||||||
@@ -198,6 +216,8 @@ export const ROOM_THEMES: RoomTheme[] = [
|
|||||||
description: '별빛만 남긴 어둠 속에서 깊게 잠수합니다.',
|
description: '별빛만 남긴 어둠 속에서 깊게 잠수합니다.',
|
||||||
tags: ['딥워크', '감성'],
|
tags: ['딥워크', '감성'],
|
||||||
recommendedSound: 'Deep Drone',
|
recommendedSound: 'Deep Drone',
|
||||||
|
recommendedSoundPresetId: 'deep-white',
|
||||||
|
recommendedTimerPresetId: '90-20',
|
||||||
recommendedTime: '심야',
|
recommendedTime: '심야',
|
||||||
vibeLabel: '깊음',
|
vibeLabel: '깊음',
|
||||||
hubColor: '#D4DCF4',
|
hubColor: '#D4DCF4',
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ export interface RoomTheme {
|
|||||||
description: string;
|
description: string;
|
||||||
tags: RoomTag[];
|
tags: RoomTag[];
|
||||||
recommendedSound: string;
|
recommendedSound: string;
|
||||||
|
recommendedSoundPresetId: string;
|
||||||
|
recommendedTimerPresetId: string;
|
||||||
recommendedTime: string;
|
recommendedTime: string;
|
||||||
vibeLabel: string;
|
vibeLabel: string;
|
||||||
hubColor: string;
|
hubColor: string;
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ interface ControlCenterSheetWidgetProps {
|
|||||||
selectedRoomId: string;
|
selectedRoomId: string;
|
||||||
selectedTimerLabel: string;
|
selectedTimerLabel: string;
|
||||||
selectedSoundPresetId: string;
|
selectedSoundPresetId: string;
|
||||||
|
sceneRecommendedSoundLabel: string;
|
||||||
|
sceneRecommendedTimerLabel: string;
|
||||||
timerPresets: TimerPreset[];
|
timerPresets: TimerPreset[];
|
||||||
soundPresets: SoundPreset[];
|
soundPresets: SoundPreset[];
|
||||||
onSelectRoom: (roomId: string) => void;
|
onSelectRoom: (roomId: string) => void;
|
||||||
@@ -25,6 +27,7 @@ interface ControlCenterSheetWidgetProps {
|
|||||||
onSelectSound: (soundPresetId: string) => void;
|
onSelectSound: (soundPresetId: string) => void;
|
||||||
onApplyPack: (packId: QuickPackId) => void;
|
onApplyPack: (packId: QuickPackId) => void;
|
||||||
onLockedClick: (source: string) => void;
|
onLockedClick: (source: string) => void;
|
||||||
|
onResetToRecommended: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type QuickPackId = 'balanced' | 'deep-work' | 'gentle';
|
type QuickPackId = 'balanced' | 'deep-work' | 'gentle';
|
||||||
@@ -80,6 +83,8 @@ export const ControlCenterSheetWidget = ({
|
|||||||
selectedRoomId,
|
selectedRoomId,
|
||||||
selectedTimerLabel,
|
selectedTimerLabel,
|
||||||
selectedSoundPresetId,
|
selectedSoundPresetId,
|
||||||
|
sceneRecommendedSoundLabel: _sceneRecommendedSoundLabel,
|
||||||
|
sceneRecommendedTimerLabel: _sceneRecommendedTimerLabel,
|
||||||
timerPresets,
|
timerPresets,
|
||||||
soundPresets,
|
soundPresets,
|
||||||
onSelectRoom,
|
onSelectRoom,
|
||||||
@@ -87,6 +92,7 @@ export const ControlCenterSheetWidget = ({
|
|||||||
onSelectSound,
|
onSelectSound,
|
||||||
onApplyPack,
|
onApplyPack,
|
||||||
onLockedClick,
|
onLockedClick,
|
||||||
|
onResetToRecommended: _onResetToRecommended,
|
||||||
}: ControlCenterSheetWidgetProps) => {
|
}: ControlCenterSheetWidgetProps) => {
|
||||||
const reducedMotion = useReducedMotion();
|
const reducedMotion = useReducedMotion();
|
||||||
const isPro = plan === 'pro';
|
const isPro = plan === 'pro';
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ interface SpaceToolsDockWidgetProps {
|
|||||||
onSetSoundMuted: (nextMuted: boolean) => void;
|
onSetSoundMuted: (nextMuted: boolean) => void;
|
||||||
thoughts: RecentThought[];
|
thoughts: RecentThought[];
|
||||||
thoughtCount: number;
|
thoughtCount: number;
|
||||||
|
sceneRecommendedSoundLabel: string;
|
||||||
|
sceneRecommendedTimerLabel: string;
|
||||||
onRoomSelect: (roomId: string) => void;
|
onRoomSelect: (roomId: string) => void;
|
||||||
onTimerSelect: (timerLabel: string) => void;
|
onTimerSelect: (timerLabel: string) => void;
|
||||||
onSelectPreset: (presetId: string) => void;
|
onSelectPreset: (presetId: string) => void;
|
||||||
@@ -40,6 +42,7 @@ interface SpaceToolsDockWidgetProps {
|
|||||||
onSetThoughtCompleted: (thoughtId: string, isCompleted: boolean) => RecentThought | null;
|
onSetThoughtCompleted: (thoughtId: string, isCompleted: boolean) => RecentThought | null;
|
||||||
onRestoreThought: (thought: RecentThought) => void;
|
onRestoreThought: (thought: RecentThought) => void;
|
||||||
onClearInbox: () => RecentThought[];
|
onClearInbox: () => RecentThought[];
|
||||||
|
onResetToSceneRecommended: () => void;
|
||||||
onStatusMessage: (payload: HudStatusLinePayload) => void;
|
onStatusMessage: (payload: HudStatusLinePayload) => void;
|
||||||
onExitRequested: () => void;
|
onExitRequested: () => void;
|
||||||
}
|
}
|
||||||
@@ -57,6 +60,8 @@ export const SpaceToolsDockWidget = ({
|
|||||||
onSetSoundMuted,
|
onSetSoundMuted,
|
||||||
thoughts,
|
thoughts,
|
||||||
thoughtCount,
|
thoughtCount,
|
||||||
|
sceneRecommendedSoundLabel,
|
||||||
|
sceneRecommendedTimerLabel,
|
||||||
onRoomSelect,
|
onRoomSelect,
|
||||||
onTimerSelect,
|
onTimerSelect,
|
||||||
onSelectPreset,
|
onSelectPreset,
|
||||||
@@ -65,6 +70,7 @@ export const SpaceToolsDockWidget = ({
|
|||||||
onSetThoughtCompleted,
|
onSetThoughtCompleted,
|
||||||
onRestoreThought,
|
onRestoreThought,
|
||||||
onClearInbox,
|
onClearInbox,
|
||||||
|
onResetToSceneRecommended,
|
||||||
onStatusMessage,
|
onStatusMessage,
|
||||||
onExitRequested,
|
onExitRequested,
|
||||||
}: SpaceToolsDockWidgetProps) => {
|
}: SpaceToolsDockWidgetProps) => {
|
||||||
@@ -414,6 +420,8 @@ export const SpaceToolsDockWidget = ({
|
|||||||
selectedRoomId={selectedRoomId}
|
selectedRoomId={selectedRoomId}
|
||||||
selectedTimerLabel={selectedTimerLabel}
|
selectedTimerLabel={selectedTimerLabel}
|
||||||
selectedSoundPresetId={selectedPresetId}
|
selectedSoundPresetId={selectedPresetId}
|
||||||
|
sceneRecommendedSoundLabel={sceneRecommendedSoundLabel}
|
||||||
|
sceneRecommendedTimerLabel={sceneRecommendedTimerLabel}
|
||||||
timerPresets={timerPresets}
|
timerPresets={timerPresets}
|
||||||
soundPresets={SOUND_PRESETS}
|
soundPresets={SOUND_PRESETS}
|
||||||
onSelectRoom={(roomId) => {
|
onSelectRoom={(roomId) => {
|
||||||
@@ -427,6 +435,7 @@ export const SpaceToolsDockWidget = ({
|
|||||||
}}
|
}}
|
||||||
onApplyPack={handleApplyPack}
|
onApplyPack={handleApplyPack}
|
||||||
onLockedClick={handleLockedClick}
|
onLockedClick={handleLockedClick}
|
||||||
|
onResetToRecommended={onResetToSceneRecommended}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useSearchParams } from 'next/navigation';
|
import { useSearchParams } from 'next/navigation';
|
||||||
import {
|
import {
|
||||||
getRoomBackgroundStyle,
|
getRoomBackgroundStyle,
|
||||||
@@ -22,6 +22,10 @@ import { SpaceSetupDrawerWidget } from '@/widgets/space-setup-drawer';
|
|||||||
import { SpaceToolsDockWidget } from '@/widgets/space-tools-dock';
|
import { SpaceToolsDockWidget } from '@/widgets/space-tools-dock';
|
||||||
|
|
||||||
type WorkspaceMode = 'setup' | 'focus';
|
type WorkspaceMode = 'setup' | 'focus';
|
||||||
|
type SelectionOverride = {
|
||||||
|
sound: boolean;
|
||||||
|
timer: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
const resolveInitialRoomId = (roomIdFromQuery: string | null) => {
|
const resolveInitialRoomId = (roomIdFromQuery: string | null) => {
|
||||||
if (roomIdFromQuery && getRoomById(roomIdFromQuery)) {
|
if (roomIdFromQuery && getRoomById(roomIdFromQuery)) {
|
||||||
@@ -31,11 +35,15 @@ const resolveInitialRoomId = (roomIdFromQuery: string | null) => {
|
|||||||
return ROOM_THEMES[0].id;
|
return ROOM_THEMES[0].id;
|
||||||
};
|
};
|
||||||
|
|
||||||
const resolveInitialSoundPreset = (presetIdFromQuery: string | null) => {
|
const resolveInitialSoundPreset = (presetIdFromQuery: string | null, recommendedPresetId?: string) => {
|
||||||
if (presetIdFromQuery && SOUND_PRESETS.some((preset) => preset.id === presetIdFromQuery)) {
|
if (presetIdFromQuery && SOUND_PRESETS.some((preset) => preset.id === presetIdFromQuery)) {
|
||||||
return presetIdFromQuery;
|
return presetIdFromQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (recommendedPresetId && SOUND_PRESETS.some((preset) => preset.id === recommendedPresetId)) {
|
||||||
|
return recommendedPresetId;
|
||||||
|
}
|
||||||
|
|
||||||
return SOUND_PRESETS[0].id;
|
return SOUND_PRESETS[0].id;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -44,11 +52,31 @@ const TIMER_SELECTION_PRESETS = TIMER_PRESETS.filter(
|
|||||||
typeof preset.focusMinutes === 'number' && typeof preset.breakMinutes === 'number',
|
typeof preset.focusMinutes === 'number' && typeof preset.breakMinutes === 'number',
|
||||||
).slice(0, 3);
|
).slice(0, 3);
|
||||||
|
|
||||||
const resolveInitialTimerLabel = (timerLabelFromQuery: string | null) => {
|
const resolveTimerLabelFromPresetId = (presetId?: string) => {
|
||||||
|
if (!presetId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const preset = TIMER_SELECTION_PRESETS.find((candidate) => candidate.id === presetId);
|
||||||
|
|
||||||
|
if (!preset) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return preset.label;
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolveInitialTimerLabel = (timerLabelFromQuery: string | null, recommendedPresetId?: string) => {
|
||||||
if (timerLabelFromQuery && TIMER_SELECTION_PRESETS.some((preset) => preset.label === timerLabelFromQuery)) {
|
if (timerLabelFromQuery && TIMER_SELECTION_PRESETS.some((preset) => preset.label === timerLabelFromQuery)) {
|
||||||
return timerLabelFromQuery;
|
return timerLabelFromQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const recommendedLabel = resolveTimerLabelFromPresetId(recommendedPresetId);
|
||||||
|
|
||||||
|
if (recommendedLabel) {
|
||||||
|
return recommendedLabel;
|
||||||
|
}
|
||||||
|
|
||||||
return TIMER_SELECTION_PRESETS[0]?.label ?? '25/5';
|
return TIMER_SELECTION_PRESETS[0]?.label ?? '25/5';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -65,15 +93,23 @@ export const SpaceWorkspaceWidget = () => {
|
|||||||
} = useThoughtInbox();
|
} = useThoughtInbox();
|
||||||
|
|
||||||
const initialRoomId = resolveInitialRoomId(searchParams.get('room'));
|
const initialRoomId = resolveInitialRoomId(searchParams.get('room'));
|
||||||
|
const initialRoom = getRoomById(initialRoomId) ?? ROOM_THEMES[0];
|
||||||
const initialGoal = searchParams.get('goal')?.trim() ?? '';
|
const initialGoal = searchParams.get('goal')?.trim() ?? '';
|
||||||
const initialSoundPresetId = resolveInitialSoundPreset(searchParams.get('sound'));
|
const initialSoundPresetId = resolveInitialSoundPreset(
|
||||||
const initialTimerLabel = resolveInitialTimerLabel(searchParams.get('timer'));
|
searchParams.get('sound'),
|
||||||
|
initialRoom.recommendedSoundPresetId,
|
||||||
|
);
|
||||||
|
const initialTimerLabel = resolveInitialTimerLabel(
|
||||||
|
searchParams.get('timer'),
|
||||||
|
initialRoom.recommendedTimerPresetId,
|
||||||
|
);
|
||||||
|
|
||||||
const [workspaceMode, setWorkspaceMode] = useState<WorkspaceMode>('setup');
|
const [workspaceMode, setWorkspaceMode] = useState<WorkspaceMode>('setup');
|
||||||
const [selectedRoomId, setSelectedRoomId] = useState(initialRoomId);
|
const [selectedRoomId, setSelectedRoomId] = useState(initialRoomId);
|
||||||
const [selectedTimerLabel, setSelectedTimerLabel] = useState(initialTimerLabel);
|
const [selectedTimerLabel, setSelectedTimerLabel] = useState(initialTimerLabel);
|
||||||
const [goalInput, setGoalInput] = useState(initialGoal);
|
const [goalInput, setGoalInput] = useState(initialGoal);
|
||||||
const [selectedGoalId, setSelectedGoalId] = useState<string | null>(null);
|
const [selectedGoalId, setSelectedGoalId] = useState<string | null>(null);
|
||||||
|
const [selectionOverride, setSelectionOverride] = useState<SelectionOverride>({ sound: false, timer: false });
|
||||||
|
|
||||||
const {
|
const {
|
||||||
selectedPresetId,
|
selectedPresetId,
|
||||||
@@ -102,6 +138,88 @@ export const SpaceWorkspaceWidget = () => {
|
|||||||
const isFocusMode = workspaceMode === 'focus';
|
const isFocusMode = workspaceMode === 'focus';
|
||||||
const { activeStatus, pushStatusLine, runActiveAction } = useHudStatusLine(isFocusMode);
|
const { activeStatus, pushStatusLine, runActiveAction } = useHudStatusLine(isFocusMode);
|
||||||
|
|
||||||
|
const applyRecommendedSelections = useCallback((roomId: string) => {
|
||||||
|
const room = getRoomById(roomId);
|
||||||
|
|
||||||
|
if (!room) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selectionOverride.timer) {
|
||||||
|
const recommendedTimerLabel = resolveTimerLabelFromPresetId(room.recommendedTimerPresetId);
|
||||||
|
|
||||||
|
if (recommendedTimerLabel) {
|
||||||
|
setSelectedTimerLabel(recommendedTimerLabel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selectionOverride.sound && SOUND_PRESETS.some((preset) => preset.id === room.recommendedSoundPresetId)) {
|
||||||
|
setSelectedPresetId(room.recommendedSoundPresetId);
|
||||||
|
}
|
||||||
|
}, [selectionOverride.sound, selectionOverride.timer, setSelectedPresetId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
applyRecommendedSelections(selectedRoomId);
|
||||||
|
}, [applyRecommendedSelections, selectedRoomId]);
|
||||||
|
|
||||||
|
const handleSelectRoom = (roomId: string) => {
|
||||||
|
setSelectedRoomId(roomId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectTimer = (timerLabel: string, markOverride = false) => {
|
||||||
|
setSelectedTimerLabel(timerLabel);
|
||||||
|
|
||||||
|
if (!markOverride) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectionOverride((current) => {
|
||||||
|
if (current.timer) {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...current, timer: true };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectSound = (presetId: string, markOverride = false) => {
|
||||||
|
setSelectedPresetId(presetId);
|
||||||
|
|
||||||
|
if (!markOverride) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectionOverride((current) => {
|
||||||
|
if (current.sound) {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...current, sound: true };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResetToSceneRecommended = () => {
|
||||||
|
const room = getRoomById(selectedRoomId);
|
||||||
|
|
||||||
|
if (!room) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectionOverride({ sound: false, timer: false });
|
||||||
|
|
||||||
|
const recommendedTimerLabel = resolveTimerLabelFromPresetId(room.recommendedTimerPresetId);
|
||||||
|
|
||||||
|
if (recommendedTimerLabel) {
|
||||||
|
setSelectedTimerLabel(recommendedTimerLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SOUND_PRESETS.some((preset) => preset.id === room.recommendedSoundPresetId)) {
|
||||||
|
setSelectedPresetId(room.recommendedSoundPresetId);
|
||||||
|
}
|
||||||
|
|
||||||
|
pushStatusLine({ message: '추천으로 되돌림(더미)' });
|
||||||
|
};
|
||||||
|
|
||||||
const handleGoalChipSelect = (chip: GoalChip) => {
|
const handleGoalChipSelect = (chip: GoalChip) => {
|
||||||
setSelectedGoalId(chip.id);
|
setSelectedGoalId(chip.id);
|
||||||
setGoalInput(chip.label);
|
setGoalInput(chip.label);
|
||||||
@@ -164,9 +282,9 @@ export const SpaceWorkspaceWidget = () => {
|
|||||||
soundPresets={SOUND_PRESETS}
|
soundPresets={SOUND_PRESETS}
|
||||||
timerPresets={TIMER_SELECTION_PRESETS}
|
timerPresets={TIMER_SELECTION_PRESETS}
|
||||||
canStart={canStart}
|
canStart={canStart}
|
||||||
onRoomSelect={setSelectedRoomId}
|
onRoomSelect={handleSelectRoom}
|
||||||
onTimerSelect={setSelectedTimerLabel}
|
onTimerSelect={(timerLabel) => handleSelectTimer(timerLabel, true)}
|
||||||
onSoundSelect={setSelectedPresetId}
|
onSoundSelect={(presetId) => handleSelectSound(presetId, true)}
|
||||||
onGoalChange={handleGoalChange}
|
onGoalChange={handleGoalChange}
|
||||||
onGoalChipSelect={handleGoalChipSelect}
|
onGoalChipSelect={handleGoalChipSelect}
|
||||||
onStart={handleStart}
|
onStart={handleStart}
|
||||||
@@ -194,9 +312,12 @@ export const SpaceWorkspaceWidget = () => {
|
|||||||
thoughts={thoughts}
|
thoughts={thoughts}
|
||||||
thoughtCount={thoughtCount}
|
thoughtCount={thoughtCount}
|
||||||
selectedPresetId={selectedPresetId}
|
selectedPresetId={selectedPresetId}
|
||||||
onRoomSelect={setSelectedRoomId}
|
onRoomSelect={handleSelectRoom}
|
||||||
onTimerSelect={setSelectedTimerLabel}
|
onTimerSelect={(timerLabel) => handleSelectTimer(timerLabel, true)}
|
||||||
onSelectPreset={setSelectedPresetId}
|
onSelectPreset={(presetId) => handleSelectSound(presetId, true)}
|
||||||
|
sceneRecommendedSoundLabel={selectedRoom.recommendedSound}
|
||||||
|
sceneRecommendedTimerLabel={resolveTimerLabelFromPresetId(selectedRoom.recommendedTimerPresetId) ?? selectedTimerLabel}
|
||||||
|
onResetToSceneRecommended={handleResetToSceneRecommended}
|
||||||
soundVolume={masterVolume}
|
soundVolume={masterVolume}
|
||||||
onSetSoundVolume={setMasterVolume}
|
onSetSoundVolume={setMasterVolume}
|
||||||
isSoundMuted={isMuted}
|
isSoundMuted={isMuted}
|
||||||
|
|||||||
Reference in New Issue
Block a user