refactor: FSD 구조 강화 및 파일 500줄 제한에 따른 대규모 리팩토링
- SpaceWorkspaceWidget 로직을 전용 훅 및 유틸리티로 분리 (900줄 -> 300줄) - useSpaceWorkspaceSelection 훅을 기능별(영속성, 진단 등) 소형 훅으로 분리 - SpaceToolsDockWidget의 상태 및 핸들러 로직 추출 - 거대 i18n 번역 파일(ko.ts)을 도메인별 메시지 파일로 구조화 - AdminConsoleWidget 누락분 추가 및 미디어 엔티티 타입 오류 수정
This commit is contained in:
206
src/widgets/space-tools-dock/model/useSpaceToolsDockHandlers.ts
Normal file
206
src/widgets/space-tools-dock/model/useSpaceToolsDockHandlers.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, useState, type KeyboardEvent as ReactKeyboardEvent } from 'react';
|
||||
import type { RecentThought } from '@/entities/session';
|
||||
import { copy } from '@/shared/i18n';
|
||||
import type { HudStatusLinePayload } from '@/shared/lib/useHudStatusLine';
|
||||
import type { SpaceAnchorPopoverId, SpaceUtilityPanelId } from './types';
|
||||
import type { PlanTier } from '@/entities/plan';
|
||||
|
||||
interface UseSpaceToolsDockHandlersParams {
|
||||
setIdle: (idle: boolean) => void;
|
||||
setOpenPopover: (popover: SpaceAnchorPopoverId | null) => void;
|
||||
setUtilityPanel: (panel: SpaceUtilityPanelId | null) => void;
|
||||
onCaptureThought: (note: string) => RecentThought | null;
|
||||
onDeleteThought: (thoughtId: string) => RecentThought | null;
|
||||
onSetThoughtCompleted: (thoughtId: string, isCompleted: boolean) => RecentThought | null;
|
||||
onRestoreThought: (thought: RecentThought) => void;
|
||||
onRestoreThoughts: (thoughts: RecentThought[]) => void;
|
||||
onClearInbox: () => RecentThought[];
|
||||
onStatusMessage: (payload: HudStatusLinePayload) => void;
|
||||
onSetSoundVolume: (volume: number) => void;
|
||||
onSetSoundMuted: (muted: boolean) => void;
|
||||
soundVolume: number;
|
||||
isSoundMuted: boolean;
|
||||
showVolumeFeedback: (volume: number) => void;
|
||||
}
|
||||
|
||||
export const useSpaceToolsDockHandlers = ({
|
||||
setIdle,
|
||||
setOpenPopover,
|
||||
setUtilityPanel,
|
||||
onCaptureThought,
|
||||
onDeleteThought,
|
||||
onSetThoughtCompleted,
|
||||
onRestoreThought,
|
||||
onRestoreThoughts,
|
||||
onClearInbox,
|
||||
onStatusMessage,
|
||||
onSetSoundVolume,
|
||||
onSetSoundMuted,
|
||||
soundVolume,
|
||||
isSoundMuted,
|
||||
showVolumeFeedback,
|
||||
}: UseSpaceToolsDockHandlersParams) => {
|
||||
const { toolsDock } = copy.space;
|
||||
const [noteDraft, setNoteDraft] = useState('');
|
||||
const [plan, setPlan] = useState<PlanTier>('normal');
|
||||
|
||||
const openUtilityPanel = useCallback((panel: SpaceUtilityPanelId) => {
|
||||
setIdle(false);
|
||||
setOpenPopover(null);
|
||||
setUtilityPanel(panel);
|
||||
}, [setIdle, setOpenPopover, setUtilityPanel]);
|
||||
|
||||
const handleNoteSubmit = useCallback(() => {
|
||||
const trimmedNote = noteDraft.trim();
|
||||
|
||||
if (!trimmedNote) {
|
||||
return;
|
||||
}
|
||||
|
||||
const addedThought = onCaptureThought(trimmedNote);
|
||||
|
||||
if (!addedThought) {
|
||||
return;
|
||||
}
|
||||
|
||||
setNoteDraft('');
|
||||
onStatusMessage({
|
||||
message: toolsDock.inboxSaved,
|
||||
durationMs: 4200,
|
||||
priority: 'undo',
|
||||
action: {
|
||||
label: toolsDock.undo,
|
||||
onClick: () => {
|
||||
const removed = onDeleteThought(addedThought.id);
|
||||
|
||||
if (!removed) {
|
||||
return;
|
||||
}
|
||||
|
||||
onStatusMessage({ message: toolsDock.inboxSaveUndone });
|
||||
},
|
||||
},
|
||||
});
|
||||
}, [noteDraft, onCaptureThought, onDeleteThought, onStatusMessage, toolsDock.inboxSaved, toolsDock.inboxSaveUndone, toolsDock.undo]);
|
||||
|
||||
const handleInboxComplete = useCallback((thought: RecentThought) => {
|
||||
onSetThoughtCompleted(thought.id, !thought.isCompleted);
|
||||
}, [onSetThoughtCompleted]);
|
||||
|
||||
const handleInboxDelete = useCallback((thought: RecentThought) => {
|
||||
const removedThought = onDeleteThought(thought.id);
|
||||
|
||||
if (!removedThought) {
|
||||
return;
|
||||
}
|
||||
|
||||
onStatusMessage({
|
||||
message: toolsDock.deleted,
|
||||
durationMs: 4200,
|
||||
priority: 'undo',
|
||||
action: {
|
||||
label: toolsDock.undo,
|
||||
onClick: () => {
|
||||
onRestoreThought(removedThought);
|
||||
onStatusMessage({ message: toolsDock.deleteUndone });
|
||||
},
|
||||
},
|
||||
});
|
||||
}, [onDeleteThought, onRestoreThought, onStatusMessage, toolsDock.deleted, toolsDock.deleteUndone, toolsDock.undo]);
|
||||
|
||||
const handleInboxClear = useCallback(() => {
|
||||
const snapshot = onClearInbox();
|
||||
|
||||
if (snapshot.length === 0) {
|
||||
onStatusMessage({ message: toolsDock.emptyToClear });
|
||||
return;
|
||||
}
|
||||
|
||||
onStatusMessage({
|
||||
message: toolsDock.clearedAll,
|
||||
durationMs: 4200,
|
||||
priority: 'undo',
|
||||
action: {
|
||||
label: toolsDock.undo,
|
||||
onClick: () => {
|
||||
onRestoreThoughts(snapshot);
|
||||
onStatusMessage({ message: toolsDock.restored });
|
||||
},
|
||||
},
|
||||
});
|
||||
}, [onClearInbox, onRestoreThoughts, onStatusMessage, toolsDock.clearedAll, toolsDock.emptyToClear, toolsDock.restored, toolsDock.undo]);
|
||||
|
||||
const handlePlanPillClick = useCallback(() => {
|
||||
if (plan === 'pro') {
|
||||
openUtilityPanel('manage-plan');
|
||||
return;
|
||||
}
|
||||
|
||||
onStatusMessage({ message: toolsDock.normalPlanInfo });
|
||||
}, [openUtilityPanel, onStatusMessage, plan, toolsDock.normalPlanInfo]);
|
||||
|
||||
const handleLockedClick = useCallback((source: string) => {
|
||||
onStatusMessage({ message: toolsDock.proFeatureLocked(source) });
|
||||
openUtilityPanel('paywall');
|
||||
}, [onStatusMessage, openUtilityPanel, toolsDock]);
|
||||
|
||||
const handleSelectProFeature = useCallback((featureId: string) => {
|
||||
const label =
|
||||
featureId === 'scene-packs'
|
||||
? toolsDock.featureLabels.scenePacks
|
||||
: featureId === 'sound-packs'
|
||||
? toolsDock.featureLabels.soundPacks
|
||||
: toolsDock.featureLabels.profiles;
|
||||
|
||||
onStatusMessage({ message: toolsDock.proFeaturePending(label) });
|
||||
}, [onStatusMessage, toolsDock.featureLabels]);
|
||||
|
||||
const handleStartPro = useCallback(() => {
|
||||
setPlan('pro');
|
||||
onStatusMessage({ message: toolsDock.purchaseMock });
|
||||
openUtilityPanel('control-center');
|
||||
}, [onStatusMessage, openUtilityPanel, toolsDock.purchaseMock]);
|
||||
|
||||
const handleVolumeChange = useCallback((nextVolume: number) => {
|
||||
const clamped = Math.min(100, Math.max(0, nextVolume));
|
||||
onSetSoundVolume(clamped);
|
||||
|
||||
if (isSoundMuted && clamped > 0) {
|
||||
onSetSoundMuted(false);
|
||||
}
|
||||
|
||||
showVolumeFeedback(clamped);
|
||||
}, [isSoundMuted, onSetSoundMuted, onSetSoundVolume, showVolumeFeedback]);
|
||||
|
||||
const handleVolumeKeyDown = useCallback((event: ReactKeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
const step = event.shiftKey ? 10 : 5;
|
||||
const delta = event.key === 'ArrowRight' ? step : -step;
|
||||
handleVolumeChange(soundVolume + delta);
|
||||
}, [handleVolumeChange, soundVolume]);
|
||||
|
||||
return {
|
||||
noteDraft,
|
||||
setNoteDraft,
|
||||
plan,
|
||||
setPlan,
|
||||
openUtilityPanel,
|
||||
handleNoteSubmit,
|
||||
handleInboxComplete,
|
||||
handleInboxDelete,
|
||||
handleInboxClear,
|
||||
handlePlanPillClick,
|
||||
handleLockedClick,
|
||||
handleSelectProFeature,
|
||||
handleStartPro,
|
||||
handleVolumeChange,
|
||||
handleVolumeKeyDown,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user