diff --git a/src/App.tsx b/src/App.tsx index 381b81b..aea7230 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -18,7 +18,7 @@ import type { RenderingEvent } from './core/audio-interface/KGOfflineRenderer'; import { KGCore } from './core/KGCore'; import { ConfigManager } from './core/config/ConfigManager'; import { validateFunctionalChordsJSON } from './util/scaleUtil'; -import { showAlert } from './components/common/DialogProvider'; +import { showAlert } from './util/dialogUtil'; import { KGProjectStorage } from './core/io/KGProjectStorage'; import { RESERVED_PROJECT_NAME } from './util/projectNameUtil'; diff --git a/src/components/KGOnePanel.tsx b/src/components/KGOnePanel.tsx index ffecc44..d53dbd2 100644 --- a/src/components/KGOnePanel.tsx +++ b/src/components/KGOnePanel.tsx @@ -14,7 +14,7 @@ import { sliceAudioToWav } from '../util/audioUtil'; import type { KeySignature } from '../core/KGProject'; import { ImportStemsCommand } from '../core/commands'; import type { StemImportEntry } from '../core/commands'; -import { showAlert } from './common/DialogProvider'; +import { showAlert } from '../util/dialogUtil'; // ─── Types ──────────────────────────────────────────────────────────────────── diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index b4b0a44..ca5d52f 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -33,7 +33,7 @@ import { clearChatHistoryAndUI } from '../util/chatUtil'; import PianoIcon from './common/icons/PianoIcon'; import MetronomeIcon from './common/icons/MetronomeIcon'; import { ConfigManager } from '../core/config/ConfigManager'; -import { showAlert, showConfirm, showPrompt, showTimeSigPrompt } from './common/DialogProvider'; +import { showAlert, showConfirm, showPrompt, showTimeSigPrompt } from '../util/dialogUtil'; const Toolbar: React.FC = () => { const { diff --git a/src/components/common/DialogProvider.tsx b/src/components/common/DialogProvider.tsx index 5e18460..eadeffb 100644 --- a/src/components/common/DialogProvider.tsx +++ b/src/components/common/DialogProvider.tsx @@ -1,60 +1,8 @@ import React, { useState, useCallback, useRef } from 'react'; import './DialogProvider.css'; import { FaTimes } from 'react-icons/fa'; - -export interface ConfirmOptions { - confirmLabel?: string; - cancelLabel?: string; -} - -export interface PromptOptions { - confirmLabel?: string; - cancelLabel?: string; - placeholder?: string; -} - -export interface TimeSigResult { - numerator: number; - denominator: number; -} - -let _showAlertFn: ((message: string) => Promise) | null = null; -let _showConfirmFn: ((message: string, options?: ConfirmOptions) => Promise) | null = null; -let _showPromptFn: ((message: string, defaultValue?: string, options?: PromptOptions) => Promise) | null = null; -let _showTimeSigFn: ((message: string, defaultValue?: TimeSigResult) => Promise) | null = null; - -export function showAlert(message: string): Promise { - if (!_showAlertFn) { - window.alert(message); - return Promise.resolve(); - } - return _showAlertFn(message); -} - -export function showConfirm(message: string, options?: ConfirmOptions): Promise { - if (!_showConfirmFn) { - return Promise.resolve(window.confirm(message)); - } - return _showConfirmFn(message, options); -} - -export function showPrompt(message: string, defaultValue?: string, options?: PromptOptions): Promise { - if (!_showPromptFn) { - return Promise.resolve(window.prompt(message, defaultValue)); - } - return _showPromptFn(message, defaultValue, options); -} - -export function showTimeSigPrompt(message: string, defaultValue?: TimeSigResult): Promise { - if (!_showTimeSigFn) { - const raw = window.prompt(message, defaultValue ? `${defaultValue.numerator}/${defaultValue.denominator}` : '4/4'); - if (!raw) return Promise.resolve(null); - const [n, d] = raw.split('/').map(Number); - if (!n || !d) return Promise.resolve(null); - return Promise.resolve({ numerator: n, denominator: d }); - } - return _showTimeSigFn(message, defaultValue); -} +import { registerDialogFns } from '../../util/dialogUtil'; +import type { ConfirmOptions, PromptOptions, TimeSigResult } from '../../util/dialogUtil'; interface DialogInfo { type: 'alert' | 'confirm' | 'prompt' | 'timesig'; @@ -129,10 +77,7 @@ const DialogProvider: React.FC<{ children: React.ReactNode }> = ({ children }) = const registered = useRef(false); if (!registered.current) { registered.current = true; - _showAlertFn = openAlert; - _showConfirmFn = openConfirm; - _showPromptFn = openPrompt; - _showTimeSigFn = openTimeSig; + registerDialogFns(openAlert, openConfirm, openPrompt, openTimeSig); } if (!dialog) { diff --git a/src/components/common/FileImportModal.tsx b/src/components/common/FileImportModal.tsx index c5fddf3..bffecf2 100644 --- a/src/components/common/FileImportModal.tsx +++ b/src/components/common/FileImportModal.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useRef, useState } from 'react'; import './FileImportModal.css'; import { FaTimes } from 'react-icons/fa'; -import { showAlert } from './DialogProvider'; +import { showAlert } from '../../util/dialogUtil'; interface FileImportModalProps { isVisible: boolean; diff --git a/src/components/common/OpenProjectModal.tsx b/src/components/common/OpenProjectModal.tsx index 099103a..0308a39 100644 --- a/src/components/common/OpenProjectModal.tsx +++ b/src/components/common/OpenProjectModal.tsx @@ -3,7 +3,7 @@ import { FaTimes, FaSortUp, FaSortDown, FaCopy, FaTrash, FaUndo } from 'react-ic import { KGProjectStorage, type ProjectMeta } from '../../core/io/KGProjectStorage'; import { isValidProjectName } from '../../util/projectNameUtil'; import './OpenProjectModal.css'; -import { showAlert, showConfirm, showPrompt } from './DialogProvider'; +import { showAlert, showConfirm, showPrompt } from '../../util/dialogUtil'; interface OpenProjectModalProps { onClose: () => void; diff --git a/src/components/common/index.ts b/src/components/common/index.ts index 4dd4446..9fafde9 100644 --- a/src/components/common/index.ts +++ b/src/components/common/index.ts @@ -3,5 +3,6 @@ export { default as Playhead } from './Playhead'; export { default as FileImportModal } from './FileImportModal'; export { default as LoadingOverlay } from './LoadingOverlay'; export { default as OpenProjectModal } from './OpenProjectModal'; -export { default as DialogProvider, showAlert, showConfirm, showPrompt, showTimeSigPrompt } from './DialogProvider'; -export type { ConfirmOptions, PromptOptions, TimeSigResult } from './DialogProvider'; \ No newline at end of file +export { default as DialogProvider } from './DialogProvider'; +export { showAlert, showConfirm, showPrompt, showTimeSigPrompt } from '../../util/dialogUtil'; +export type { ConfirmOptions, PromptOptions, TimeSigResult } from '../../util/dialogUtil'; \ No newline at end of file diff --git a/src/components/piano-roll/PianoRoll.tsx b/src/components/piano-roll/PianoRoll.tsx index 524afc8..2fe27a5 100644 --- a/src/components/piano-roll/PianoRoll.tsx +++ b/src/components/piano-roll/PianoRoll.tsx @@ -15,7 +15,7 @@ import { ConfigManager } from '../../core/config/ConfigManager'; import { beatsToBar } from '../../util/midiUtil'; import { UpdateRegionCommand } from '../../core/commands'; import { getSuitableChords, noteNameToPitchClass } from '../../util/scaleUtil'; -import { showAlert, showPrompt } from '../common/DialogProvider'; +import { showAlert, showPrompt } from '../../util/dialogUtil'; interface PianoRollProps { onClose: () => void; diff --git a/src/components/settings/sections/ChordGuideSettings.tsx b/src/components/settings/sections/ChordGuideSettings.tsx index 2f8c2c5..69ca7b5 100644 --- a/src/components/settings/sections/ChordGuideSettings.tsx +++ b/src/components/settings/sections/ChordGuideSettings.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect, useCallback } from 'react'; import { ConfigManager } from '../../../core/config/ConfigManager'; import { validateFunctionalChordsJSON } from '../../../util/scaleUtil'; import { KGCore } from '../../../core/KGCore'; -import { showAlert } from '../../common/DialogProvider'; +import { showAlert } from '../../../util/dialogUtil'; const ChordGuideSettings: React.FC = () => { const [chordDefinition, setChordDefinition] = useState(''); diff --git a/src/components/track/TrackGridPanel.tsx b/src/components/track/TrackGridPanel.tsx index 560ed16..da64b29 100644 --- a/src/components/track/TrackGridPanel.tsx +++ b/src/components/track/TrackGridPanel.tsx @@ -13,7 +13,7 @@ import { KGAudioInterface } from '../../core/audio-interface/KGAudioInterface'; import { KGAudioRegion } from '../../core/region/KGAudioRegion'; import { generateNewRegionName } from '../../util/miscUtil'; import { KGAudioFileStorage } from '../../core/io/KGAudioFileStorage'; -import { showAlert } from '../common/DialogProvider'; +import { showAlert } from '../../util/dialogUtil'; import { parseMidiFirstTrackNotes } from '../../util/midiUtil'; import * as Tone from 'tone'; diff --git a/src/components/track/TrackInfoItem.tsx b/src/components/track/TrackInfoItem.tsx index f03b95f..7cf9cc3 100644 --- a/src/components/track/TrackInfoItem.tsx +++ b/src/components/track/TrackInfoItem.tsx @@ -12,7 +12,7 @@ import { FLUIDR3_INSTRUMENT_MAP } from '../../constants/generalMidiConstants'; import { DEBUG_MODE } from '../../constants/uiConstants'; import { KGAudioInterface } from '../../core/audio-interface/KGAudioInterface'; import { AUDIO_INTERFACE_CONSTANTS } from '../../constants/coreConstants'; -import { showAlert, showConfirm, showPrompt } from '../common/DialogProvider'; +import { showAlert, showConfirm, showPrompt } from '../../util/dialogUtil'; const UNITY_POS = 750; const SLIDER_MAX = 1000; diff --git a/src/core/KGDebugger.ts b/src/core/KGDebugger.ts index a80ee13..e32d302 100644 --- a/src/core/KGDebugger.ts +++ b/src/core/KGDebugger.ts @@ -513,8 +513,7 @@ export class KGDebugger { try { textarea.selectionStart = textarea.selectionEnd = typed.length; - } catch { - } + } catch { /* setting cursor position can fail in read-only or special inputs */ } textarea.scrollTop = textarea.scrollHeight; @@ -583,8 +582,7 @@ export class KGDebugger { try { textarea.selectionStart = textarea.selectionEnd = typed.length; - } catch { - } + } catch { /* setting cursor position can fail in read-only or special inputs */ } textarea.scrollTop = textarea.scrollHeight; diff --git a/src/core/config-upgrader/upgradeConfigToV1.test.ts b/src/core/config-upgrader/upgradeConfigToV1.test.ts index cc99d2f..190addd 100644 --- a/src/core/config-upgrader/upgradeConfigToV1.test.ts +++ b/src/core/config-upgrader/upgradeConfigToV1.test.ts @@ -47,9 +47,8 @@ class MockFileHandle { async getFile() { return { text: () => Promise.resolve(this._content) }; } async createWritable() { const w = new MockWritable(); - const self = this; const origClose = w.close.bind(w); - w.close = async () => { self._content = w.data; await origClose(); }; + w.close = async () => { this._content = w.data; await origClose(); }; return w; } } diff --git a/src/core/io/KGProjectStorage.test.ts b/src/core/io/KGProjectStorage.test.ts index 3ded8e9..85b382f 100644 --- a/src/core/io/KGProjectStorage.test.ts +++ b/src/core/io/KGProjectStorage.test.ts @@ -19,10 +19,9 @@ class MockFileSystemFileHandle { async createWritable() { const stream = new MockFileSystemWritableFileStream(); // When stream closes, update our content - const self = this; const origClose = stream.close.bind(stream); stream.close = async () => { - self._content = stream.data; + this._content = stream.data; await origClose(); }; return stream; diff --git a/src/test/mocks/indexed-db.ts b/src/test/mocks/indexed-db.ts index 606a102..3e631a0 100644 --- a/src/test/mocks/indexed-db.ts +++ b/src/test/mocks/indexed-db.ts @@ -5,11 +5,13 @@ import { vi } from 'vitest'; // In-memory storage for tests +// eslint-disable-next-line @typescript-eslint/no-explicit-any const mockStorage = new Map(); export const mockIndexedDB = { openDB: vi.fn().mockImplementation(() => { return Promise.resolve({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any put: vi.fn().mockImplementation((storeName: string, data: any, key?: string) => { const actualKey = key || data.id || 'default'; mockStorage.set(`${storeName}:${actualKey}`, data); @@ -21,6 +23,7 @@ export const mockIndexedDB = { }), getAll: vi.fn().mockImplementation((storeName: string) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any const results: any[] = []; for (const [key, value] of mockStorage.entries()) { if (key.startsWith(`${storeName}:`)) { diff --git a/src/util/dialogUtil.ts b/src/util/dialogUtil.ts new file mode 100644 index 0000000..c9b84a8 --- /dev/null +++ b/src/util/dialogUtil.ts @@ -0,0 +1,65 @@ +export interface ConfirmOptions { + confirmLabel?: string; + cancelLabel?: string; +} + +export interface PromptOptions { + confirmLabel?: string; + cancelLabel?: string; + placeholder?: string; +} + +export interface TimeSigResult { + numerator: number; + denominator: number; +} + +let _showAlertFn: ((message: string) => Promise) | null = null; +let _showConfirmFn: ((message: string, options?: ConfirmOptions) => Promise) | null = null; +let _showPromptFn: ((message: string, defaultValue?: string, options?: PromptOptions) => Promise) | null = null; +let _showTimeSigFn: ((message: string, defaultValue?: TimeSigResult) => Promise) | null = null; + +export function registerDialogFns( + alertFn: (message: string) => Promise, + confirmFn: (message: string, options?: ConfirmOptions) => Promise, + promptFn: (message: string, defaultValue?: string, options?: PromptOptions) => Promise, + timeSigFn: (message: string, defaultValue?: TimeSigResult) => Promise, +) { + _showAlertFn = alertFn; + _showConfirmFn = confirmFn; + _showPromptFn = promptFn; + _showTimeSigFn = timeSigFn; +} + +export function showAlert(message: string): Promise { + if (!_showAlertFn) { + window.alert(message); + return Promise.resolve(); + } + return _showAlertFn(message); +} + +export function showConfirm(message: string, options?: ConfirmOptions): Promise { + if (!_showConfirmFn) { + return Promise.resolve(window.confirm(message)); + } + return _showConfirmFn(message, options); +} + +export function showPrompt(message: string, defaultValue?: string, options?: PromptOptions): Promise { + if (!_showPromptFn) { + return Promise.resolve(window.prompt(message, defaultValue)); + } + return _showPromptFn(message, defaultValue, options); +} + +export function showTimeSigPrompt(message: string, defaultValue?: TimeSigResult): Promise { + if (!_showTimeSigFn) { + const raw = window.prompt(message, defaultValue ? `${defaultValue.numerator}/${defaultValue.denominator}` : '4/4'); + if (!raw) return Promise.resolve(null); + const [n, d] = raw.split('/').map(Number); + if (!n || !d) return Promise.resolve(null); + return Promise.resolve({ numerator: n, denominator: d }); + } + return _showTimeSigFn(message, defaultValue); +} diff --git a/src/util/saveUtil.ts b/src/util/saveUtil.ts index 690db4c..9114669 100644 --- a/src/util/saveUtil.ts +++ b/src/util/saveUtil.ts @@ -1,7 +1,7 @@ import { KGProjectStorage } from '../core/io/KGProjectStorage'; import { KGCore } from '../core/KGCore'; import { RESERVED_PROJECT_NAME } from './projectNameUtil'; -import { showAlert } from '../components/common/DialogProvider'; +import { showAlert } from './dialogUtil'; /** * Save project utility function. diff --git a/src/util/xmlUtil.test.ts b/src/util/xmlUtil.test.ts index 03f5d31..1b909a4 100644 --- a/src/util/xmlUtil.test.ts +++ b/src/util/xmlUtil.test.ts @@ -214,8 +214,8 @@ describe('xmlUtil', () => { it('should handle empty input', () => { expect(wrapXmlBlocksInContent('')).toBe(''); - expect(wrapXmlBlocksInContent(null as any)).toBeNull(); - expect(wrapXmlBlocksInContent(undefined as any)).toBeUndefined(); + expect(wrapXmlBlocksInContent(null as unknown as string)).toBeNull(); + expect(wrapXmlBlocksInContent(undefined as unknown as string)).toBeUndefined(); }); it('should wrap XML blocks from multipleXmlBlocks fixture', () => {