diff --git a/backend/app/urls.py b/backend/app/urls.py index d4ffe8c3..78eadae5 100644 --- a/backend/app/urls.py +++ b/backend/app/urls.py @@ -4,4 +4,6 @@ urlpatterns = [ path('build', views.build, name='build'), path('download', views.download, name='download'), + path('install_block', views.install_block, name='install_block'), + path('installed_blocks', views.installed_blocks, name='installed_blocks'), ] diff --git a/backend/app/views.py b/backend/app/views.py index d6cf79ec..9e6ac6d7 100644 --- a/backend/app/views.py +++ b/backend/app/views.py @@ -6,6 +6,8 @@ from django.views.decorators.csrf import csrf_exempt import json +import os +from django.conf import settings @csrf_exempt def build(request): @@ -20,3 +22,45 @@ def build(request): def download(request): return JsonResponse({'status': 'request received'}) + + +@csrf_exempt +def install_block(request): + try: + data = json.loads(request.body) + block_name = data.get('package', {}).get('name') or data.get('name', 'Untitled') + + custom_blocks_dir = os.path.join(settings.BASE_DIR, 'custom_blocks') + if not os.path.exists(custom_blocks_dir): + os.makedirs(custom_blocks_dir) + + safe_filename = "".join([c for c in block_name if c.isalpha() or c.isdigit() or c in (' ')]).rstrip() + filepath = os.path.join(custom_blocks_dir, f"{safe_filename}.vc3") + + with open(filepath, 'w') as f: + json.dump(data, f, indent=4) + + return JsonResponse({'status': 'success', 'saved_as': os.path.basename(filepath)}) + except Exception as e: + return HttpResponseBadRequest(str(e)) + +def installed_blocks(request): + try: + custom_blocks_dir = os.path.join(settings.BASE_DIR, 'custom_blocks') + if not os.path.exists(custom_blocks_dir): + return JsonResponse({'blocks': []}) + + blocks = [] + for filename in os.listdir(custom_blocks_dir): + if filename.endswith('.vc3'): + filepath = os.path.join(custom_blocks_dir, filename) + try: + with open(filepath, 'r') as f: + block_data = json.load(f) + blocks.append(block_data) + except Exception as e: + print(f"Failed to load block {filename}: {e}") + + return JsonResponse({'blocks': blocks}) + except Exception as e: + return HttpResponseBadRequest(str(e)) diff --git a/frontend/src/components/marketplace/MarketplacePanel.tsx b/frontend/src/components/marketplace/MarketplacePanel.tsx index 4dbd5969..93d2c3d9 100644 --- a/frontend/src/components/marketplace/MarketplacePanel.tsx +++ b/frontend/src/components/marketplace/MarketplacePanel.tsx @@ -96,29 +96,27 @@ const MarketplacePanel: React.FC = ({ open, onClose, edit const isValid = validateMarketplaceBlock(blockData); if (isValid) { - // Save to Local Storage - let installedBlocks = []; try { - const stored = localStorage.getItem('vc_marketplace_blocks'); - if (stored) { - installedBlocks = JSON.parse(stored); - } - } catch (e) { - console.warn("Could not read local storage", e); - } - - // Avoid duplicates by checking package name - const exists = installedBlocks.find((b: any) => b.package && b.package.name === blockData.package.name); - if (!exists) { - installedBlocks.push(blockData); - localStorage.setItem('vc_marketplace_blocks', JSON.stringify(installedBlocks)); + const backendHost = process.env.REACT_APP_BACKEND_HOST || 'http://localhost:8000/api/'; + const endpoint = backendHost.endsWith('/') ? `${backendHost}install_block` : `${backendHost}/install_block`; - // Dispatch event so the MenuBar updates + const installResponse = await fetch(endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(blockData), + }); + + if (!installResponse.ok) { + throw new Error(`Backend error: ${installResponse.status}`); + } window.dispatchEvent(new Event('vc_marketplace_updated')); - alert(`Successfully added ${block.name} to your Downloads Menu!`); - } else { - alert(`${block.name} is already in your Downloads!`); + alert(`Successfully added ${block.name} to your Custom Blocks!`); + } catch (e) { + console.error("Failed to save to backend", e); + alert(`Failed to save ${block.name} to the backend.`); } } else { alert(`Failed to validate block ${block.name}.`); diff --git a/frontend/src/components/menu/index.tsx b/frontend/src/components/menu/index.tsx index 9a0e55d5..a932edfc 100644 --- a/frontend/src/components/menu/index.tsx +++ b/frontend/src/components/menu/index.tsx @@ -38,14 +38,18 @@ function MenuBar(props: MenuBarProps) { const [marketplaceOpen, setMarketplaceOpen] = useState(false); const [downloads, setDownloads] = useState([]); - const loadDownloads = () => { + const loadDownloads = async () => { try { - const stored = localStorage.getItem('vc_marketplace_blocks'); - if (stored) { - setDownloads(JSON.parse(stored)); + const backendHost = process.env.REACT_APP_BACKEND_HOST || 'http://localhost:8000/api/'; + const endpoint = backendHost.endsWith('/') ? `${backendHost}installed_blocks` : `${backendHost}/installed_blocks`; + + const response = await fetch(endpoint); + if (response.ok) { + const data = await response.json(); + setDownloads(data.blocks || []); } } catch (e) { - console.error("Failed to load downloads", e); + console.error("Failed to load downloads from backend", e); } };