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 { rebuildGraph } from '../engine/audioEngine.js';
import { getPresets } from '../engine/presets.js';
const TAGS = ['ambient', 'bass', 'drums', 'pad', 'lead', 'fx', 'chiptune', 'experimental'];
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 (!selectedPreset) { setError('Selecciona un preset para compartir'); return; }
setLoading(true);
setError('');
try {
// 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(),
tags: selectedTags,
data: patchData,
});
onShared?.();
onClose();
} catch (err) {
setError(err.message);
}
setLoading(false);
};
return (
e.stopPropagation()} style={{ gap: 14, maxHeight: '80vh', overflow: 'auto' }}>
Compartir Patch
{presets.length === 0 ? (
No tienes presets guardados. Ve al Sandbox, crea algo y guardalo con "Save" primero.
) : (
{presets.map((p, i) => (
))}
)}
setTitle(e.target.value)} />
);
}
function PatchCard({ patch, onLoad, onLike }) {
const moduleCount = patch.data?.modules?.length || 0;
const wireCount = patch.data?.connections?.length || 0;
return (
{moduleCount > 6 ? '~ ~ ~ ~' : '~ ~'}
{patch.title}
por {patch.author?.username || 'Anonimo'}
{patch.tags?.length > 0 && (
{patch.tags.map(t => {t})}
)}
{moduleCount} modules · {wireCount} wires
);
}
export default function Workshop({ onSwitchToSandbox, onSwitchToGame, onSwitchToAdmin }) {
const { isLoggedIn, isAdmin, openAuth, logout, user } = useAuth();
const [patches, setPatches] = useState([]);
const [search, setSearch] = useState('');
const [activeTag, setActiveTag] = useState('');
const [sort, setSort] = useState('recent');
const [loading, setLoading] = useState(true);
const [showShare, setShowShare] = useState(false);
const loadPatches = useCallback(async () => {
setLoading(true);
try {
const params = new URLSearchParams();
if (search) params.set('q', search);
if (activeTag) params.set('tags', activeTag);
params.set('sort', sort);
const data = await workshopApi.browse(params.toString());
setPatches(data.patches || []);
} catch (err) {
console.warn('Workshop load failed:', err);
}
setLoading(false);
}, [search, activeTag, sort]);
useEffect(() => { loadPatches(); }, [loadPatches]);
const handleLoad = (patch) => {
if (!patch.data) return;
// Deep clone and load — same pattern as loadPreset()
// Don't stop audio first: rebuildGraph destroys and recreates all nodes
const cleanData = JSON.parse(JSON.stringify(patch.data));
deserialize(cleanData);
if (state.isRunning) rebuildGraph();
onSwitchToSandbox?.();
};
const handleLike = async (patchId) => {
if (!isLoggedIn) { openAuth(); return; }
try {
await workshopApi.like(patchId);
loadPatches();
} catch {}
};
const handleShare = () => {
if (!isLoggedIn) { openAuth(); return; }
setShowShare(true);
};
return (
Workshop
Explora, comparte y descubre sonidos de la comunidad
{loading ? (
Cargando...
) : patches.length === 0 ? (
No hay patches aun. Se el primero en compartir!
) : (
patches.map(p => (
))
)}
{showShare &&
setShowShare(false)} onShared={loadPatches} />}
);
}