fix: Workshop share from saved presets + clean load

Share:
- Share modal now shows user's saved presets to pick from
- No longer grabs live canvas (which had serialization issues)
- Auto-fills title from preset name
- Shows module/wire count per preset

Load:
- Stops audio before loading (prevents ghost sounds)
- Deep clones patch data (prevents reference issues)
- Calls deserialize → rebuildGraph → emit in correct order
- Switches to Sandbox after loading

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jose Luis
2026-03-21 20:42:19 +01:00
parent 982654c3ef
commit 3b80070c9a

View File

@@ -1,9 +1,9 @@
import React, { useState, useEffect, useCallback } from 'react';
import { workshop as workshopApi } from '../services/api.js';
import { useAuth } from '../services/AuthContext.jsx';
import { state, deserialize } from '../engine/state.js';
import { serialize } from '../engine/state.js';
import { rebuildGraph } from '../engine/audioEngine.js';
import { state, deserialize, emit } from '../engine/state.js';
import { stopAudio, rebuildGraph } from '../engine/audioEngine.js';
import { getPresets } from '../engine/presets.js';
const TAGS = ['ambient', 'bass', 'drums', 'pad', 'lead', 'fx', 'chiptune', 'experimental'];
@@ -11,17 +11,27 @@ function ShareModal({ onClose, onShared }) {
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const [selectedTags, setSelectedTags] = useState([]);
const [selectedPreset, setSelectedPreset] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const presets = getPresets();
const handleShare = async () => {
if (!title.trim()) { setError('Titulo requerido'); return; }
if (state.modules.length === 0) { setError('No hay modulos en el canvas'); return; }
if (!selectedPreset) { setError('Selecciona un preset para compartir'); return; }
setLoading(true);
setError('');
try {
const patchData = serialize();
// Use the preset data directly (already serialized correctly)
const patchData = {
modules: selectedPreset.modules || [],
connections: selectedPreset.connections || [],
camera: selectedPreset.camera || { camX: 0, camY: 0, zoom: 1 },
masterVolume: selectedPreset.masterVolume ?? -6,
};
await workshopApi.share({
title: title.trim(),
description: description.trim(),
@@ -38,10 +48,36 @@ function ShareModal({ onClose, onShared }) {
return (
<div className="auth-overlay" onClick={onClose}>
<div className="auth-card" onClick={e => e.stopPropagation()} style={{ gap: 14 }}>
<div className="auth-card" onClick={e => e.stopPropagation()} style={{ gap: 14, maxHeight: '80vh', overflow: 'auto' }}>
<h2 style={{ fontSize: 18, fontWeight: 700, color: 'var(--text)', margin: 0 }}>Compartir Patch</h2>
<div className="auth-form" style={{ gap: 10 }}>
<label className="auth-label">SELECCIONA UN PRESET</label>
{presets.length === 0 ? (
<p style={{ fontSize: 12, color: 'var(--text2)' }}>
No tienes presets guardados. Ve al Sandbox, crea algo y guardalo con "Save" primero.
</p>
) : (
<div style={{ display: 'flex', flexDirection: 'column', gap: 4, maxHeight: 150, overflowY: 'auto' }}>
{presets.map((p, i) => (
<button key={i} type="button"
style={{
padding: '10px 12px', background: selectedPreset === p ? 'var(--surface2)' : 'var(--bg)',
border: `1px solid ${selectedPreset === p ? 'var(--accent)' : 'var(--border)'}`,
borderRadius: 6, cursor: 'pointer', textAlign: 'left',
color: 'var(--text)', fontSize: 13, fontFamily: 'inherit',
}}
onClick={() => { setSelectedPreset(p); if (!title) setTitle(p.name || ''); }}
>
<strong>{p.name}</strong>
<span style={{ color: 'var(--text2)', fontSize: 11, marginLeft: 8 }}>
{p.modules?.length || 0} modules · {p.connections?.length || 0} wires
</span>
</button>
))}
</div>
)}
<label className="auth-label">TITULO</label>
<input className="auth-input" placeholder="Nombre de tu patch"
value={title} onChange={e => setTitle(e.target.value)} />
@@ -65,7 +101,8 @@ function ShareModal({ onClose, onShared }) {
{error && <div className="auth-error">{error}</div>}
<button className="auth-submit" onClick={handleShare} disabled={loading}>
<button className="auth-submit" onClick={handleShare}
disabled={loading || presets.length === 0}>
{loading ? 'Compartiendo...' : 'Compartir'}
</button>
</div>
@@ -132,11 +169,18 @@ export default function Workshop({ onSwitchToSandbox, onSwitchToGame }) {
useEffect(() => { loadPatches(); }, [loadPatches]);
const handleLoad = (patch) => {
if (patch.data) {
deserialize(patch.data);
if (state.isRunning) rebuildGraph();
onSwitchToSandbox?.();
}
if (!patch.data) return;
// Stop audio, clean state, then load
if (state.isRunning) stopAudio();
// Deep clone to avoid reference issues
const cleanData = JSON.parse(JSON.stringify(patch.data));
deserialize(cleanData);
rebuildGraph();
emit();
onSwitchToSandbox?.();
};
const handleLike = async (patchId) => {