Let's fucking go
This commit is contained in:
331
src/App.svelte
331
src/App.svelte
@ -6,6 +6,9 @@
|
||||
import FileBrowser from './lib/FileBrowser.svelte';
|
||||
import SidePanel from './lib/SidePanel.svelte';
|
||||
import Popup from './lib/Popup.svelte';
|
||||
import ResizablePopup from './lib/ResizablePopup.svelte';
|
||||
import AudioScope from './lib/AudioScope.svelte';
|
||||
import Spectrogram from './lib/Spectrogram.svelte';
|
||||
import { csound, csoundLogs, type LogEntry } from './lib/csound';
|
||||
import { projectManager, type CsoundProject } from './lib/project-system';
|
||||
import ConfirmDialog from './lib/ConfirmDialog.svelte';
|
||||
@ -18,13 +21,54 @@
|
||||
PanelBottomClose,
|
||||
PanelBottomOpen,
|
||||
LayoutGrid,
|
||||
Save
|
||||
Save,
|
||||
Share2,
|
||||
Activity
|
||||
} from 'lucide-svelte';
|
||||
|
||||
let sidePanelVisible = $state(true);
|
||||
let sidePanelPosition = $state<'left' | 'right' | 'bottom'>('right');
|
||||
let popupVisible = $state(false);
|
||||
let editorValue = $state('<CsoundSynthesizer>\n<CsOptions>\n-odac\n</CsOptions>\n<CsInstruments>\n\nsr = 44100\nksmps = 32\nnchnls = 2\n0dbfs = 1\n\ninstr 1\n aOut oscili 0.5, 440\n outs aOut, aOut\nendin\n\n</CsInstruments>\n<CsScore>\ni 1 0 2\n</CsScore>\n</CsoundSynthesizer>\n');
|
||||
let sharePopupVisible = $state(false);
|
||||
let shareUrl = $state('');
|
||||
let audioPermissionPopupVisible = $state(true);
|
||||
let scopePopupVisible = $state(false);
|
||||
let spectrogramPopupVisible = $state(false);
|
||||
let analyserNode = $state<AnalyserNode | null>(null);
|
||||
let editorValue = $state(`<CsoundSynthesizer>
|
||||
<CsOptions>
|
||||
-odac
|
||||
</CsOptions>
|
||||
<CsInstruments>
|
||||
|
||||
sr = 44100
|
||||
ksmps = 32
|
||||
nchnls = 2
|
||||
0dbfs = 1
|
||||
|
||||
instr 1
|
||||
iFreq = p4
|
||||
iAmp = p5
|
||||
|
||||
; ADSR envelope
|
||||
kEnv madsr 0.01, 0.1, 0.6, 0.2
|
||||
|
||||
; Sine wave oscillator
|
||||
aOsc oscili iAmp * kEnv, iFreq
|
||||
|
||||
outs aOsc, aOsc
|
||||
endin
|
||||
|
||||
</CsInstruments>
|
||||
<CsScore>
|
||||
; Arpeggio: C4 E4 G4 C5
|
||||
i 1 0.0 0.5 261.63 0.3
|
||||
i 1 0.5 0.5 329.63 0.3
|
||||
i 1 1.0 0.5 392.00 0.3
|
||||
i 1 1.5 0.5 523.25 0.3
|
||||
</CsScore>
|
||||
</CsoundSynthesizer>
|
||||
`);
|
||||
let interpreterLogs = $state<LogEntry[]>([]);
|
||||
|
||||
let sidePanelRef: SidePanel;
|
||||
@ -37,10 +81,42 @@
|
||||
let showUnsavedDialog = $state(false);
|
||||
let showSaveAsDialog = $state(false);
|
||||
|
||||
const TEMPLATE_CONTENT = '<CsoundSynthesizer>\n<CsOptions>\n-odac\n</CsOptions>\n<CsInstruments>\n\nsr = 44100\nksmps = 32\nnchnls = 2\n0dbfs = 1\n\ninstr 1\n aOut oscili 0.5, 440\n outs aOut, aOut\nendin\n\n</CsInstruments>\n<CsScore>\ni 1 0 2\n</CsScore>\n</CsoundSynthesizer>\n';
|
||||
const TEMPLATE_CONTENT = `<CsoundSynthesizer>
|
||||
<CsOptions>
|
||||
-odac
|
||||
</CsOptions>
|
||||
<CsInstruments>
|
||||
|
||||
sr = 44100
|
||||
ksmps = 32
|
||||
nchnls = 2
|
||||
0dbfs = 1
|
||||
|
||||
instr 1
|
||||
iFreq = p4
|
||||
iAmp = p5
|
||||
|
||||
; ADSR envelope
|
||||
kEnv madsr 0.01, 0.1, 0.6, 0.2
|
||||
|
||||
; Sine wave oscillator
|
||||
aOsc oscili iAmp * kEnv, iFreq
|
||||
|
||||
outs aOsc, aOsc
|
||||
endin
|
||||
|
||||
</CsInstruments>
|
||||
<CsScore>
|
||||
; Arpeggio: C4 E4 G4 C5
|
||||
i 1 0.0 0.5 261.63 0.3
|
||||
i 1 0.5 0.5 329.63 0.3
|
||||
i 1 1.0 0.5 392.00 0.3
|
||||
i 1 1.5 0.5 523.25 0.3
|
||||
</CsScore>
|
||||
</CsoundSynthesizer>
|
||||
`;
|
||||
|
||||
onMount(async () => {
|
||||
await csound.init();
|
||||
await projectManager.init();
|
||||
|
||||
const result = await projectManager.getAllProjects();
|
||||
@ -62,6 +138,15 @@
|
||||
};
|
||||
});
|
||||
|
||||
async function handleEnableAudio() {
|
||||
try {
|
||||
await csound.init();
|
||||
audioPermissionPopupVisible = false;
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize audio:', error);
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(async () => {
|
||||
await csound.destroy();
|
||||
});
|
||||
@ -203,6 +288,41 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function handleShare() {
|
||||
if (!currentProjectId) return;
|
||||
|
||||
const result = await projectManager.exportProjectToUrl(currentProjectId);
|
||||
if (result.success) {
|
||||
shareUrl = result.data;
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(shareUrl);
|
||||
} catch (err) {
|
||||
console.error('Failed to copy to clipboard:', err);
|
||||
}
|
||||
|
||||
sharePopupVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
function handleOpenScope() {
|
||||
scopePopupVisible = true;
|
||||
}
|
||||
|
||||
function handleOpenSpectrogram() {
|
||||
spectrogramPopupVisible = true;
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (scopePopupVisible || spectrogramPopupVisible) {
|
||||
const interval = setInterval(() => {
|
||||
analyserNode = csound.getAnalyserNode();
|
||||
}, 100);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}
|
||||
});
|
||||
|
||||
const panelTabs = [
|
||||
{
|
||||
id: 'editor',
|
||||
@ -232,16 +352,18 @@
|
||||
{/snippet}
|
||||
|
||||
<div class="app-container">
|
||||
<TopBar title="oldboy">
|
||||
<button
|
||||
class="icon-button"
|
||||
onclick={saveCurrentProject}
|
||||
disabled={!hasUnsavedChanges}
|
||||
title="Save {hasUnsavedChanges ? '(unsaved changes)' : ''}"
|
||||
class:has-changes={hasUnsavedChanges}
|
||||
>
|
||||
<Save size={18} />
|
||||
</button>
|
||||
<TopBar title="OldBoy">
|
||||
{#snippet leftActions()}
|
||||
<button
|
||||
class="icon-button"
|
||||
onclick={saveCurrentProject}
|
||||
disabled={!hasUnsavedChanges}
|
||||
title="Save {hasUnsavedChanges ? '(unsaved changes)' : ''}"
|
||||
class:has-changes={hasUnsavedChanges}
|
||||
>
|
||||
<Save size={18} />
|
||||
</button>
|
||||
{/snippet}
|
||||
<button onclick={toggleSidePanel} class="icon-button">
|
||||
{#if sidePanelVisible}
|
||||
{#if sidePanelPosition === 'left'}
|
||||
@ -264,6 +386,34 @@
|
||||
<button onclick={cyclePanelPosition} class="icon-button" title="Change panel position">
|
||||
<LayoutGrid size={18} />
|
||||
</button>
|
||||
<button
|
||||
onclick={handleShare}
|
||||
class="icon-button"
|
||||
disabled={!currentProjectId}
|
||||
title="Share project"
|
||||
>
|
||||
<Share2 size={18} />
|
||||
</button>
|
||||
<button
|
||||
onclick={handleOpenScope}
|
||||
class="icon-button"
|
||||
title="Open audio scope"
|
||||
>
|
||||
<Activity size={18} />
|
||||
</button>
|
||||
<button
|
||||
onclick={handleOpenSpectrogram}
|
||||
class="icon-button"
|
||||
title="Open spectrogram"
|
||||
>
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2"/>
|
||||
<path d="M3 9h18"/>
|
||||
<path d="M3 15h18"/>
|
||||
<path d="M9 3v18"/>
|
||||
<path d="M15 3v18"/>
|
||||
</svg>
|
||||
</button>
|
||||
</TopBar>
|
||||
|
||||
<div class="main-content" class:panel-bottom={sidePanelPosition === 'bottom'}>
|
||||
@ -321,6 +471,88 @@
|
||||
<p>It stays on top of everything else.</p>
|
||||
</Popup>
|
||||
|
||||
<ResizablePopup
|
||||
bind:visible={sharePopupVisible}
|
||||
title="Share Project"
|
||||
x={(typeof window !== 'undefined' ? window.innerWidth / 2 - 300 : 300)}
|
||||
y={(typeof window !== 'undefined' ? window.innerHeight / 2 - 100 : 200)}
|
||||
width={600}
|
||||
height={200}
|
||||
minWidth={400}
|
||||
minHeight={150}
|
||||
>
|
||||
{#snippet children()}
|
||||
<div class="share-content">
|
||||
<p>Link copied to clipboard!</p>
|
||||
<div class="share-url-container">
|
||||
<input
|
||||
type="text"
|
||||
readonly
|
||||
value={shareUrl}
|
||||
class="share-url-input"
|
||||
onclick={(e) => e.currentTarget.select()}
|
||||
/>
|
||||
</div>
|
||||
<p class="share-instructions">Anyone with this link can import the project.</p>
|
||||
</div>
|
||||
{/snippet}
|
||||
</ResizablePopup>
|
||||
|
||||
<ResizablePopup
|
||||
visible={audioPermissionPopupVisible}
|
||||
title="Audio Permission Required"
|
||||
x={(typeof window !== 'undefined' ? window.innerWidth / 2 - 250 : 250)}
|
||||
y={(typeof window !== 'undefined' ? window.innerHeight / 2 - 125 : 200)}
|
||||
width={500}
|
||||
height={250}
|
||||
minWidth={400}
|
||||
minHeight={200}
|
||||
closable={false}
|
||||
>
|
||||
{#snippet children()}
|
||||
<div class="audio-permission-content">
|
||||
<h3>Enable Audio Context</h3>
|
||||
<p>OldBoy needs permission to use audio playback.</p>
|
||||
<p>Click the button below to enable audio and start using the application.</p>
|
||||
<button class="enable-audio-button" onclick={handleEnableAudio}>
|
||||
Enable Audio
|
||||
</button>
|
||||
</div>
|
||||
{/snippet}
|
||||
</ResizablePopup>
|
||||
|
||||
<ResizablePopup
|
||||
bind:visible={scopePopupVisible}
|
||||
title="Audio Scope"
|
||||
x={(typeof window !== 'undefined' ? window.innerWidth / 2 - 400 : 100)}
|
||||
y={(typeof window !== 'undefined' ? window.innerHeight / 2 - 300 : 100)}
|
||||
width={800}
|
||||
height={600}
|
||||
minWidth={400}
|
||||
minHeight={300}
|
||||
noPadding={true}
|
||||
>
|
||||
{#snippet children()}
|
||||
<AudioScope analyserNode={analyserNode} />
|
||||
{/snippet}
|
||||
</ResizablePopup>
|
||||
|
||||
<ResizablePopup
|
||||
bind:visible={spectrogramPopupVisible}
|
||||
title="Spectrogram"
|
||||
x={(typeof window !== 'undefined' ? window.innerWidth / 2 - 400 : 150)}
|
||||
y={(typeof window !== 'undefined' ? window.innerHeight / 2 - 300 : 150)}
|
||||
width={800}
|
||||
height={600}
|
||||
minWidth={400}
|
||||
minHeight={300}
|
||||
noPadding={true}
|
||||
>
|
||||
{#snippet children()}
|
||||
<Spectrogram analyserNode={analyserNode} />
|
||||
{/snippet}
|
||||
</ResizablePopup>
|
||||
|
||||
<ConfirmDialog
|
||||
bind:visible={showUnsavedDialog}
|
||||
title="Unsaved Changes"
|
||||
@ -399,4 +631,75 @@
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.share-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.share-url-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.share-url-input {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
background-color: #2a2a2a;
|
||||
border: 1px solid #3a3a3a;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
font-size: 0.875rem;
|
||||
font-family: monospace;
|
||||
outline: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.share-url-input:focus {
|
||||
border-color: #646cff;
|
||||
}
|
||||
|
||||
.share-instructions {
|
||||
font-size: 0.875rem;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.audio-permission-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.audio-permission-content h3 {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.audio-permission-content p {
|
||||
margin: 0;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.enable-audio-button {
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem 2rem;
|
||||
background-color: #646cff;
|
||||
color: white;
|
||||
border: none;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.enable-audio-button:hover {
|
||||
background-color: #535bdb;
|
||||
}
|
||||
|
||||
.enable-audio-button:active {
|
||||
background-color: #424ab8;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user