fix: minor UI adjustments
This commit is contained in:
@@ -296,24 +296,37 @@
|
|||||||
|
|
||||||
/* Generate / Separate button */
|
/* Generate / Separate button */
|
||||||
.kgone-btn-generate {
|
.kgone-btn-generate {
|
||||||
background-color: #4a5fa0;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
color: #e0e0e0;
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
cursor: pointer;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kgone-btn-generate-accent {
|
||||||
|
background-color: #4a5fa0;
|
||||||
|
border: none;
|
||||||
|
color: #e0e0e0;
|
||||||
|
cursor: pointer;
|
||||||
transition: background-color 0.15s;
|
transition: background-color 0.15s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.kgone-btn-generate:hover:not(:disabled) {
|
.kgone-btn-generate-accent:hover:not(:disabled) {
|
||||||
background-color: #5a70b8;
|
background-color: #5a70b8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.kgone-btn-generate:disabled {
|
.kgone-btn-generate-accent:disabled {
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
color: #666;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kgone-btn-generate.dialog-btn {
|
||||||
|
min-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kgone-btn-generate.dialog-btn:disabled {
|
||||||
background-color: #3a3a3a;
|
background-color: #3a3a3a;
|
||||||
color: #666;
|
color: #666;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
|
|||||||
@@ -524,7 +524,7 @@ const ClipTab: React.FC<ClipTabProps> = ({ bpm, keySignature }) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="kgone-btn-generate"
|
className="kgone-btn-generate kgone-btn-generate-accent"
|
||||||
disabled={isGenerating || !prompt.trim()}
|
disabled={isGenerating || !prompt.trim()}
|
||||||
onClick={handleGenerate}
|
onClick={handleGenerate}
|
||||||
>
|
>
|
||||||
@@ -818,7 +818,7 @@ const FullSongTab: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="kgone-btn-generate"
|
className="dialog-btn dialog-btn-primary kgone-btn-generate"
|
||||||
disabled={isGenerating || !caption.trim()}
|
disabled={isGenerating || !caption.trim()}
|
||||||
onClick={handleGenerate}
|
onClick={handleGenerate}
|
||||||
>
|
>
|
||||||
@@ -1511,7 +1511,7 @@ const SeparatorTab: React.FC<{ mode: KGOneMode }> = ({ mode }) => {
|
|||||||
{genStatus === 'done' && stemAudioUrls.length > 0 && (
|
{genStatus === 'done' && stemAudioUrls.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
className="kgone-btn-generate"
|
className="dialog-btn dialog-btn-primary kgone-btn-generate"
|
||||||
disabled={isImporting}
|
disabled={isImporting}
|
||||||
onClick={handleImportAll}
|
onClick={handleImportAll}
|
||||||
style={{ marginTop: 0 }}
|
style={{ marginTop: 0 }}
|
||||||
@@ -1529,7 +1529,7 @@ const SeparatorTab: React.FC<{ mode: KGOneMode }> = ({ mode }) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="kgone-btn-generate"
|
className="dialog-btn dialog-btn-primary kgone-btn-generate"
|
||||||
disabled={isGenerating || (localOnlyMode && !isLocalModelCached)}
|
disabled={isGenerating || (localOnlyMode && !isLocalModelCached)}
|
||||||
onClick={handleSeparate}
|
onClick={handleSeparate}
|
||||||
>
|
>
|
||||||
@@ -1990,7 +1990,7 @@ const RemixTab: React.FC = () => {
|
|||||||
{genStatus === 'done' && audioUrl && (
|
{genStatus === 'done' && audioUrl && (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
className="kgone-btn-generate"
|
className="kgone-btn-generate kgone-btn-generate-accent"
|
||||||
disabled={isImporting}
|
disabled={isImporting}
|
||||||
onClick={handleImportAligned}
|
onClick={handleImportAligned}
|
||||||
style={{ marginTop: 0 }}
|
style={{ marginTop: 0 }}
|
||||||
@@ -2007,7 +2007,7 @@ const RemixTab: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="kgone-btn-generate"
|
className="dialog-btn dialog-btn-primary kgone-btn-generate"
|
||||||
disabled={isGenerating || !caption.trim()}
|
disabled={isGenerating || !caption.trim()}
|
||||||
onClick={handleRemix}
|
onClick={handleRemix}
|
||||||
>
|
>
|
||||||
@@ -2520,7 +2520,7 @@ const RepaintTab: React.FC = () => {
|
|||||||
{genStatus === 'done' && audioUrl && (
|
{genStatus === 'done' && audioUrl && (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
className="kgone-btn-generate"
|
className="kgone-btn-generate kgone-btn-generate-accent"
|
||||||
disabled={isImporting}
|
disabled={isImporting}
|
||||||
onClick={handleImportAligned}
|
onClick={handleImportAligned}
|
||||||
style={{ marginTop: 0 }}
|
style={{ marginTop: 0 }}
|
||||||
@@ -2537,7 +2537,7 @@ const RepaintTab: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="kgone-btn-generate"
|
className="dialog-btn dialog-btn-primary kgone-btn-generate"
|
||||||
disabled={isGenerating || !caption.trim()}
|
disabled={isGenerating || !caption.trim()}
|
||||||
onClick={handleRepaint}
|
onClick={handleRepaint}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -0,0 +1,165 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { fireEvent, render, screen } from '@testing-library/react';
|
||||||
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
import Toolbar from './Toolbar';
|
||||||
|
|
||||||
|
const storeState = {
|
||||||
|
projectName: 'Test Project',
|
||||||
|
setProjectName: vi.fn(),
|
||||||
|
savedProjectName: 'Test Project',
|
||||||
|
setSavedProjectName: vi.fn(),
|
||||||
|
bpm: 120,
|
||||||
|
timeSignature: { numerator: 4, denominator: 4 },
|
||||||
|
keySignature: 'C major',
|
||||||
|
setStatus: vi.fn(),
|
||||||
|
isPlaying: false,
|
||||||
|
isPreparingPlayback: false,
|
||||||
|
startPlaying: vi.fn(),
|
||||||
|
stopTransport: vi.fn(),
|
||||||
|
setPlayheadPosition: vi.fn(),
|
||||||
|
currentTime: '0:00',
|
||||||
|
setBpm: vi.fn(),
|
||||||
|
setTimeSignature: vi.fn(),
|
||||||
|
setKeySignature: vi.fn(),
|
||||||
|
maxBars: 32,
|
||||||
|
setMaxBars: vi.fn(),
|
||||||
|
barWidthMultiplier: 1,
|
||||||
|
setBarWidthMultiplier: vi.fn(),
|
||||||
|
isLooping: false,
|
||||||
|
toggleLoop: vi.fn(),
|
||||||
|
canUndo: false,
|
||||||
|
canRedo: false,
|
||||||
|
undoDescription: null,
|
||||||
|
redoDescription: null,
|
||||||
|
undo: vi.fn(),
|
||||||
|
redo: vi.fn(),
|
||||||
|
toggleChatBox: vi.fn(),
|
||||||
|
toggleSettings: vi.fn(),
|
||||||
|
toggleKGOnePanel: vi.fn(),
|
||||||
|
toggleEventListPanel: vi.fn(),
|
||||||
|
activateSidePanel: vi.fn(),
|
||||||
|
showKGOnePanel: true,
|
||||||
|
showEventListPanel: false,
|
||||||
|
showChatBox: true,
|
||||||
|
showSettings: true,
|
||||||
|
cleanupProjectState: vi.fn(),
|
||||||
|
toggleMetronome: vi.fn(),
|
||||||
|
isMetronomeEnabled: false,
|
||||||
|
isRecording: false,
|
||||||
|
startRecording: vi.fn(),
|
||||||
|
stopRecording: vi.fn(),
|
||||||
|
showPianoRoll: false,
|
||||||
|
setShowPianoRoll: vi.fn(),
|
||||||
|
activeRegionId: null,
|
||||||
|
setActiveRegionId: vi.fn(),
|
||||||
|
selectedRegionIds: [],
|
||||||
|
selectedTrackId: null,
|
||||||
|
playheadPosition: 0,
|
||||||
|
refreshProjectState: vi.fn(),
|
||||||
|
requestMainContentScroll: vi.fn(),
|
||||||
|
requestPianoRollScroll: vi.fn(),
|
||||||
|
tracks: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
type StoreState = typeof storeState;
|
||||||
|
type StoreSelector = (state: StoreState) => unknown;
|
||||||
|
|
||||||
|
vi.mock('../stores/projectStore', () => ({
|
||||||
|
useProjectStore: Object.assign(
|
||||||
|
(selector?: StoreSelector) => (selector ? selector(storeState) : storeState),
|
||||||
|
{ getState: () => storeState }
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../constants/uiConstants', async (importOriginal) => {
|
||||||
|
const actual = await importOriginal<typeof import('../constants/uiConstants')>();
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
DEBUG_MODE: { ...actual.DEBUG_MODE, TOOLBAR: false },
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mock('../constants/coreConstants', async (importOriginal) => {
|
||||||
|
const actual = await importOriginal<typeof import('../constants/coreConstants')>();
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
KEY_SIGNATURE_MAP: { ...actual.KEY_SIGNATURE_MAP, 'C major': [] },
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mock('../util/saveUtil', () => ({ saveProject: vi.fn() }));
|
||||||
|
vi.mock('../core/io/KGProjectStorage', () => ({ KGProjectStorage: { getInstance: vi.fn() } }));
|
||||||
|
vi.mock('../util/projectNameUtil', () => ({
|
||||||
|
isValidProjectName: vi.fn(() => true),
|
||||||
|
isReservedProjectName: vi.fn(() => false),
|
||||||
|
RESERVED_PROJECT_NAME: 'Untitled Project',
|
||||||
|
}));
|
||||||
|
vi.mock('../core/KGCore', () => ({ KGCore: { instance: vi.fn(() => ({ getCurrentProject: vi.fn(() => ({ getTracks: () => [] })) })) } }));
|
||||||
|
vi.mock('../core/midi-input/KGMidiInput', () => ({ KGMidiInput: { instance: vi.fn(() => ({ getConnectedInputCount: () => 0 })) } }));
|
||||||
|
vi.mock('../core/region/KGMidiRegion', () => ({ KGMidiRegion: class {} }));
|
||||||
|
vi.mock('../core/track/KGAudioTrack', () => ({ KGAudioTrack: class {} }));
|
||||||
|
vi.mock('class-transformer', () => ({
|
||||||
|
plainToInstance: vi.fn(),
|
||||||
|
Expose: () => () => undefined,
|
||||||
|
Type: () => () => undefined,
|
||||||
|
Transform: () => () => undefined,
|
||||||
|
}));
|
||||||
|
vi.mock('../core/state/KGMainContentState', () => ({ KGMainContentState: {} }));
|
||||||
|
vi.mock('../util/regionDeleteUtil', () => ({ regionDeleteManager: { deleteSelectedRegions: vi.fn(() => false) } }));
|
||||||
|
vi.mock('../core/commands/region/SplitRegionCommand', () => ({ SplitRegionCommand: class {} }));
|
||||||
|
vi.mock('../core/commands/region/MergeMidiRegionsCommand', () => ({ MergeMidiRegionsCommand: class {} }));
|
||||||
|
vi.mock('../util/copyPasteUtil', () => ({
|
||||||
|
handleCopyOperation: vi.fn(() => false),
|
||||||
|
handlePasteOperation: vi.fn(() => false),
|
||||||
|
}));
|
||||||
|
vi.mock('../util/midiUtil', () => ({
|
||||||
|
convertProjectToMidi: vi.fn(),
|
||||||
|
convertMidiToProject: vi.fn(),
|
||||||
|
}));
|
||||||
|
vi.mock('../core/audio-interface/KGOfflineRenderer', () => ({ KGOfflineRenderer: { instance: vi.fn(() => ({})) } }));
|
||||||
|
vi.mock('./common/KGDropdown', () => ({ default: () => null }));
|
||||||
|
vi.mock('./common/FileImportModal', () => ({ default: () => null }));
|
||||||
|
vi.mock('./common/LoadingOverlay', () => ({ default: () => null }));
|
||||||
|
vi.mock('./common/OpenProjectModal', () => ({ default: () => null }));
|
||||||
|
vi.mock('../util/chatUtil', () => ({ clearChatHistoryAndUI: vi.fn() }));
|
||||||
|
vi.mock('./common/icons/PianoIcon', () => ({ default: () => <span>piano</span> }));
|
||||||
|
vi.mock('./common/icons/MetronomeIcon', () => ({ default: () => <span>metro</span> }));
|
||||||
|
vi.mock('../core/config/ConfigManager', () => ({ ConfigManager: { instance: vi.fn(() => ({})) } }));
|
||||||
|
vi.mock('../util/dialogUtil', () => ({
|
||||||
|
showAlert: vi.fn(),
|
||||||
|
showChoice: vi.fn(),
|
||||||
|
showConfirm: vi.fn(),
|
||||||
|
showPrompt: vi.fn(),
|
||||||
|
showTimeSigPrompt: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('Toolbar settings side-panel behavior', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
storeState.toggleChatBox.mockClear();
|
||||||
|
storeState.toggleKGOnePanel.mockClear();
|
||||||
|
storeState.toggleEventListPanel.mockClear();
|
||||||
|
storeState.activateSidePanel.mockClear();
|
||||||
|
storeState.setStatus.mockClear();
|
||||||
|
storeState.showSettings = true;
|
||||||
|
storeState.showChatBox = true;
|
||||||
|
storeState.showKGOnePanel = true;
|
||||||
|
storeState.showEventListPanel = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('suppresses active styling for side-panel buttons while Settings is visible', () => {
|
||||||
|
render(<Toolbar />);
|
||||||
|
|
||||||
|
expect(screen.getByTitle('K.G.One Music Generator')).not.toHaveClass('active');
|
||||||
|
expect(screen.getByTitle('Chat')).not.toHaveClass('active');
|
||||||
|
expect(screen.getByTitle('Event List Editor')).not.toHaveClass('active');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('activates Event List directly instead of toggling when clicked during Settings', () => {
|
||||||
|
render(<Toolbar />);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTitle('Event List Editor'));
|
||||||
|
|
||||||
|
expect(storeState.activateSidePanel).toHaveBeenCalledWith('eventList');
|
||||||
|
expect(storeState.toggleEventListPanel).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -49,7 +49,7 @@ const Toolbar: React.FC = () => {
|
|||||||
barWidthMultiplier, setBarWidthMultiplier,
|
barWidthMultiplier, setBarWidthMultiplier,
|
||||||
isLooping, toggleLoop,
|
isLooping, toggleLoop,
|
||||||
canUndo, canRedo, undoDescription, redoDescription, undo, redo,
|
canUndo, canRedo, undoDescription, redoDescription, undo, redo,
|
||||||
toggleChatBox, toggleSettings, toggleKGOnePanel, toggleEventListPanel, showKGOnePanel, showEventListPanel, showChatBox, showSettings, cleanupProjectState, toggleMetronome, isMetronomeEnabled,
|
toggleChatBox, toggleSettings, toggleKGOnePanel, toggleEventListPanel, activateSidePanel, showKGOnePanel, showEventListPanel, showChatBox, showSettings, cleanupProjectState, toggleMetronome, isMetronomeEnabled,
|
||||||
isRecording, startRecording, stopRecording,
|
isRecording, startRecording, stopRecording,
|
||||||
// Piano roll state/actions
|
// Piano roll state/actions
|
||||||
showPianoRoll, setShowPianoRoll, activeRegionId, setActiveRegionId,
|
showPianoRoll, setShowPianoRoll, activeRegionId, setActiveRegionId,
|
||||||
@@ -970,7 +970,11 @@ const Toolbar: React.FC = () => {
|
|||||||
console.log("Chat button clicked");
|
console.log("Chat button clicked");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showSettings) {
|
||||||
|
activateSidePanel('chat');
|
||||||
|
} else {
|
||||||
toggleChatBox();
|
toggleChatBox();
|
||||||
|
}
|
||||||
setStatus("Chat toggled");
|
setStatus("Chat toggled");
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -989,6 +993,12 @@ const Toolbar: React.FC = () => {
|
|||||||
if (DEBUG_MODE.TOOLBAR) {
|
if (DEBUG_MODE.TOOLBAR) {
|
||||||
console.log("K.G.One button clicked");
|
console.log("K.G.One button clicked");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showSettings) {
|
||||||
|
activateSidePanel('kgone');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
toggleKGOnePanel();
|
toggleKGOnePanel();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -996,6 +1006,12 @@ const Toolbar: React.FC = () => {
|
|||||||
if (DEBUG_MODE.TOOLBAR) {
|
if (DEBUG_MODE.TOOLBAR) {
|
||||||
console.log("Event List button clicked");
|
console.log("Event List button clicked");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showSettings) {
|
||||||
|
activateSidePanel('eventList');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
toggleEventListPanel();
|
toggleEventListPanel();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1178,7 +1194,7 @@ const Toolbar: React.FC = () => {
|
|||||||
{!isPlaying ? (
|
{!isPlaying ? (
|
||||||
<button title="Play" className="button-play" onClick={handlePlayClick} disabled={isPreparingPlayback}><FaPlay /></button>
|
<button title="Play" className="button-play" onClick={handlePlayClick} disabled={isPreparingPlayback}><FaPlay /></button>
|
||||||
) : (
|
) : (
|
||||||
<button title="Pause" className="button-pause" onClick={handlePauseClick}><FaPause /></button>
|
<button title="Pause" className="tool-button button-pause active" onClick={handlePauseClick}><FaPause /></button>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
title={isRecording ? "Stop Recording" : "Record"}
|
title={isRecording ? "Stop Recording" : "Record"}
|
||||||
@@ -1265,21 +1281,21 @@ const Toolbar: React.FC = () => {
|
|||||||
<button
|
<button
|
||||||
title="K.G.One Music Generator"
|
title="K.G.One Music Generator"
|
||||||
onClick={handleKGOneClick}
|
onClick={handleKGOneClick}
|
||||||
className={showKGOnePanel ? 'active' : ''}
|
className={!showSettings && showKGOnePanel ? 'active' : ''}
|
||||||
>
|
>
|
||||||
<FaWandMagicSparkles />
|
<FaWandMagicSparkles />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
title="Chat"
|
title="Chat"
|
||||||
onClick={handleChatClick}
|
onClick={handleChatClick}
|
||||||
className={showChatBox ? 'active' : ''}
|
className={!showSettings && showChatBox ? 'active' : ''}
|
||||||
>
|
>
|
||||||
<FaComments />
|
<FaComments />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
title="Event List Editor"
|
title="Event List Editor"
|
||||||
onClick={handleEventListClick}
|
onClick={handleEventListClick}
|
||||||
className={showEventListPanel ? 'active' : ''}
|
className={!showSettings && showEventListPanel ? 'active' : ''}
|
||||||
>
|
>
|
||||||
<FaListUl />
|
<FaListUl />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ body {
|
|||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
a:visited {
|
||||||
|
color: rgb(90, 159, 212);
|
||||||
|
}
|
||||||
|
|
||||||
#root {
|
#root {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -250,4 +250,80 @@ describe('projectStore piano roll state', () => {
|
|||||||
|
|
||||||
expect(useProjectStore.getState().trackAutomationRedrawVersion).toBe(initialVersion + 2);
|
expect(useProjectStore.getState().trackAutomationRedrawVersion).toBe(initialVersion + 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('restores Chat after closing Settings when Chat was active on entry', async () => {
|
||||||
|
const { useProjectStore } = await import('./projectStore');
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
useProjectStore.getState().toggleChatBox();
|
||||||
|
useProjectStore.getState().setShowSettings(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
let state = useProjectStore.getState();
|
||||||
|
expect(state.showSettings).toBe(true);
|
||||||
|
expect(state.settingsReturnSidePanel).toBe('chat');
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
useProjectStore.getState().setShowSettings(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
state = useProjectStore.getState();
|
||||||
|
expect(state.showSettings).toBe(false);
|
||||||
|
expect(state.showChatBox).toBe(true);
|
||||||
|
expect(state.showKGOnePanel).toBe(false);
|
||||||
|
expect(state.showEventListPanel).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('restores K.G.One after closing Settings when K.G.One was active on entry', async () => {
|
||||||
|
const { useProjectStore } = await import('./projectStore');
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
useProjectStore.getState().toggleKGOnePanel();
|
||||||
|
useProjectStore.getState().setShowSettings(true);
|
||||||
|
useProjectStore.getState().setShowSettings(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = useProjectStore.getState();
|
||||||
|
expect(state.showSettings).toBe(false);
|
||||||
|
expect(state.showKGOnePanel).toBe(true);
|
||||||
|
expect(state.showChatBox).toBe(false);
|
||||||
|
expect(state.showEventListPanel).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('restores no side panel after closing Settings when none was active on entry', async () => {
|
||||||
|
const { useProjectStore } = await import('./projectStore');
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
useProjectStore.getState().toggleChatBox();
|
||||||
|
useProjectStore.getState().toggleChatBox();
|
||||||
|
useProjectStore.getState().setShowSettings(true);
|
||||||
|
useProjectStore.getState().setShowSettings(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = useProjectStore.getState();
|
||||||
|
expect(state.showSettings).toBe(false);
|
||||||
|
expect(state.showChatBox).toBe(false);
|
||||||
|
expect(state.showKGOnePanel).toBe(false);
|
||||||
|
expect(state.showEventListPanel).toBe(false);
|
||||||
|
expect(state.settingsReturnSidePanel).toBeNull();
|
||||||
|
expect(state.lastActiveSidePanel).toBe('chat');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('opens Event List and exits Settings when Event List is activated from Settings', async () => {
|
||||||
|
const { useProjectStore } = await import('./projectStore');
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
useProjectStore.getState().toggleChatBox();
|
||||||
|
useProjectStore.getState().setShowSettings(true);
|
||||||
|
useProjectStore.getState().activateSidePanel('eventList');
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = useProjectStore.getState();
|
||||||
|
expect(state.showSettings).toBe(false);
|
||||||
|
expect(state.showEventListPanel).toBe(true);
|
||||||
|
expect(state.showChatBox).toBe(false);
|
||||||
|
expect(state.showKGOnePanel).toBe(false);
|
||||||
|
expect(state.settingsReturnSidePanel).toBeNull();
|
||||||
|
expect(state.lastActiveSidePanel).toBe('eventList');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+73
-11
@@ -53,6 +53,16 @@ function updateBarWidthMultiplierCSS(multiplier: number): void {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SidePanelType = 'kgone' | 'chat' | 'eventList';
|
||||||
|
|
||||||
|
function getSidePanelVisibilityState(activePanel: SidePanelType | null) {
|
||||||
|
return {
|
||||||
|
showKGOnePanel: activePanel === 'kgone',
|
||||||
|
showChatBox: activePanel === 'chat',
|
||||||
|
showEventListPanel: activePanel === 'eventList',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Define the store state interface
|
// Define the store state interface
|
||||||
interface ProjectState {
|
interface ProjectState {
|
||||||
// State
|
// State
|
||||||
@@ -101,6 +111,8 @@ interface ProjectState {
|
|||||||
|
|
||||||
// Event list panel state
|
// Event list panel state
|
||||||
showEventListPanel: boolean;
|
showEventListPanel: boolean;
|
||||||
|
lastActiveSidePanel: SidePanelType | null;
|
||||||
|
settingsReturnSidePanel: SidePanelType | null;
|
||||||
|
|
||||||
// Instrument selection panel state
|
// Instrument selection panel state
|
||||||
showInstrumentSelection: boolean;
|
showInstrumentSelection: boolean;
|
||||||
@@ -199,6 +211,7 @@ interface ProjectState {
|
|||||||
|
|
||||||
// Event List panel actions
|
// Event List panel actions
|
||||||
toggleEventListPanel: () => void;
|
toggleEventListPanel: () => void;
|
||||||
|
activateSidePanel: (panel: SidePanelType) => void;
|
||||||
|
|
||||||
// Instrument selection panel actions
|
// Instrument selection panel actions
|
||||||
openInstrumentSelectionForTrack: () => void;
|
openInstrumentSelectionForTrack: () => void;
|
||||||
@@ -430,6 +443,8 @@ export const useProjectStore = create<ProjectState>((set, get) => {
|
|||||||
|
|
||||||
// Initial Event List panel state
|
// Initial Event List panel state
|
||||||
showEventListPanel: false,
|
showEventListPanel: false,
|
||||||
|
lastActiveSidePanel: initialChatBoxState ? 'chat' : null,
|
||||||
|
settingsReturnSidePanel: null,
|
||||||
|
|
||||||
// Initial Instrument Selection panel state
|
// Initial Instrument Selection panel state
|
||||||
showInstrumentSelection: initialShowInstrumentSelection,
|
showInstrumentSelection: initialShowInstrumentSelection,
|
||||||
@@ -1579,27 +1594,52 @@ export const useProjectStore = create<ProjectState>((set, get) => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// ChatBox action implementations
|
// ChatBox action implementations
|
||||||
setShowChatBox: (show: boolean) => {
|
activateSidePanel: (panel: SidePanelType) => {
|
||||||
set({
|
set({
|
||||||
showChatBox: show,
|
...getSidePanelVisibilityState(panel),
|
||||||
showKGOnePanel: show ? false : get().showKGOnePanel,
|
lastActiveSidePanel: panel,
|
||||||
showEventListPanel: show ? false : get().showEventListPanel
|
showSettings: false,
|
||||||
|
settingsReturnSidePanel: null,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setShowChatBox: (show: boolean) => {
|
||||||
|
if (show) {
|
||||||
|
get().activateSidePanel('chat');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
set({ showChatBox: false });
|
||||||
|
},
|
||||||
|
|
||||||
toggleChatBox: () => {
|
toggleChatBox: () => {
|
||||||
const { showChatBox } = get();
|
const { showChatBox, showSettings } = get();
|
||||||
set({ showChatBox: !showChatBox, showKGOnePanel: false, showEventListPanel: false });
|
if (showSettings || !showChatBox) {
|
||||||
|
get().activateSidePanel('chat');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
set({ showChatBox: false });
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleKGOnePanel: () => {
|
toggleKGOnePanel: () => {
|
||||||
const { showKGOnePanel } = get();
|
const { showKGOnePanel, showSettings } = get();
|
||||||
set({ showKGOnePanel: !showKGOnePanel, showChatBox: false, showEventListPanel: false });
|
if (showSettings || !showKGOnePanel) {
|
||||||
|
get().activateSidePanel('kgone');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
set({ showKGOnePanel: false });
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleEventListPanel: () => {
|
toggleEventListPanel: () => {
|
||||||
const { showEventListPanel } = get();
|
const { showEventListPanel, showSettings } = get();
|
||||||
set({ showEventListPanel: !showEventListPanel, showChatBox: false, showKGOnePanel: false });
|
if (showSettings || !showEventListPanel) {
|
||||||
|
get().activateSidePanel('eventList');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
set({ showEventListPanel: false });
|
||||||
},
|
},
|
||||||
|
|
||||||
// Instrument selection panel actions
|
// Instrument selection panel actions
|
||||||
@@ -1615,7 +1655,29 @@ export const useProjectStore = create<ProjectState>((set, get) => {
|
|||||||
|
|
||||||
// Settings action implementations
|
// Settings action implementations
|
||||||
setShowSettings: (show: boolean) => {
|
setShowSettings: (show: boolean) => {
|
||||||
set({ showSettings: show });
|
if (show) {
|
||||||
|
const { showKGOnePanel, showChatBox, showEventListPanel } = get();
|
||||||
|
const activePanel = showKGOnePanel
|
||||||
|
? 'kgone'
|
||||||
|
: showChatBox
|
||||||
|
? 'chat'
|
||||||
|
: showEventListPanel
|
||||||
|
? 'eventList'
|
||||||
|
: null;
|
||||||
|
|
||||||
|
set({
|
||||||
|
showSettings: true,
|
||||||
|
settingsReturnSidePanel: activePanel,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { settingsReturnSidePanel } = get();
|
||||||
|
set({
|
||||||
|
showSettings: false,
|
||||||
|
settingsReturnSidePanel: null,
|
||||||
|
...getSidePanelVisibilityState(settingsReturnSidePanel),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleSettings: () => {
|
toggleSettings: () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user