mirror of
https://github.com/9x/sheetmusictrainer.git
synced 2026-09-02 09:34:33 +02:00
Readme and technical documentation
This commit is contained in:
22
src/App.css
22
src/App.css
@@ -511,6 +511,28 @@ button {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.link-button {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
color: var(--color-primary);
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
text-decoration: none;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.link-button:hover {
|
||||
color: var(--color-primary-dark);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.separator {
|
||||
margin: 0 8px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.app-subtitle a {
|
||||
color: var(--color-primary);
|
||||
text-decoration: none;
|
||||
|
||||
11
src/App.tsx
11
src/App.tsx
@@ -2,6 +2,7 @@ import { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import { SheetMusic } from './components/SheetMusic';
|
||||
import { Controls, type AppSettings } from './components/Controls';
|
||||
import { SettingsModal } from './components/SettingsModal';
|
||||
import { OpenSourceModal } from './components/OpenSourceModal';
|
||||
import { usePitchDetector } from './hooks/usePitchDetector';
|
||||
import { useMetronome } from './hooks/useMetronome';
|
||||
import { useAudioPlayer } from './hooks/useAudioPlayer';
|
||||
@@ -66,6 +67,7 @@ function App() {
|
||||
const [revealed, setRevealed] = useState(false);
|
||||
const [virtualNote, setVirtualNote] = useState<number | null>(null);
|
||||
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
||||
const [isOpenSourceModalOpen, setIsOpenSourceModalOpen] = useState(false);
|
||||
|
||||
const currentTuning = TUNINGS[settings.tuningId];
|
||||
const currentInstrumentDef = INSTRUMENT_DEFINITIONS[settings.instrument];
|
||||
@@ -575,6 +577,10 @@ function App() {
|
||||
currentPitch={pitchData ? { note: pitchData.note, cents: pitchData.cents } : null}
|
||||
/>
|
||||
<div className="app-subtitle">
|
||||
<button className="link-button" onClick={() => setIsOpenSourceModalOpen(true)}>Open Source Libraries</button>
|
||||
<span className="separator">•</span>
|
||||
<a href="https://github.com/9x/sheetmusictrainer" target="_blank" rel="noopener noreferrer">GitHub</a>
|
||||
<span className="separator">•</span>
|
||||
<a href="http://jensmohrmann.de" target="_blank" rel="noopener noreferrer">jensmohrmann.de</a>
|
||||
</div>
|
||||
</footer>
|
||||
@@ -633,6 +639,11 @@ function App() {
|
||||
settings={settings}
|
||||
onUpdateSettings={setSettings}
|
||||
/>
|
||||
|
||||
<OpenSourceModal
|
||||
isOpen={isOpenSourceModalOpen}
|
||||
onClose={() => setIsOpenSourceModalOpen(false)}
|
||||
/>
|
||||
</div >
|
||||
);
|
||||
}
|
||||
|
||||
48
src/components/OpenSourceModal.tsx
Normal file
48
src/components/OpenSourceModal.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import { X } from 'lucide-react';
|
||||
import '../styles/OpenSourceModal.css';
|
||||
|
||||
interface OpenSourceModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const LIBRARIES = [
|
||||
{ name: 'React', url: 'https://react.dev/', description: 'The library for web and native user interfaces' },
|
||||
{ name: 'TypeScript', url: 'https://www.typescriptlang.org/', description: 'JavaScript with syntax for types' },
|
||||
{ name: 'Vite', url: 'https://vitejs.dev/', description: 'Next Generation Frontend Tooling' },
|
||||
{ name: 'VexFlow', url: 'https://www.vexflow.com/', description: 'Music notation rendering for the web' },
|
||||
{ name: 'Pitchfinder', url: 'https://github.com/peterkhayes/pitchfinder', description: 'Pitch detection algorithms' },
|
||||
{ name: 'Lucide React', url: 'https://lucide.dev/', description: 'Beautiful & consistent icon toolkit' },
|
||||
];
|
||||
|
||||
export const OpenSourceModal: React.FC<OpenSourceModalProps> = ({ isOpen, onClose }) => {
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="modal-overlay" onClick={onClose}>
|
||||
<div className="modal-content open-source-modal" onClick={e => e.stopPropagation()}>
|
||||
<div className="modal-header">
|
||||
<h2>Open Source Libraries</h2>
|
||||
<button className="close-button" onClick={onClose} aria-label="Close">
|
||||
<X size={24} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="modal-body">
|
||||
<p>This project relies on these amazing open source libraries:</p>
|
||||
<ul className="library-list">
|
||||
{LIBRARIES.map((lib) => (
|
||||
<li key={lib.name} className="library-item">
|
||||
<a href={lib.url} target="_blank" rel="noopener noreferrer" className="library-name">
|
||||
{lib.name}
|
||||
</a>
|
||||
<span className="library-desc">{lib.description}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
48
src/styles/OpenSourceModal.css
Normal file
48
src/styles/OpenSourceModal.css
Normal file
@@ -0,0 +1,48 @@
|
||||
.open-source-modal {
|
||||
max-width: 500px;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.library-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.library-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.library-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.library-name {
|
||||
font-weight: 600;
|
||||
color: #646cff;
|
||||
/* Vite purple or accent color */
|
||||
text-decoration: none;
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.library-name:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.library-desc {
|
||||
color: #ccc;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
.library-item {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user