Run playground examples from entrypoint

This commit is contained in:
2026-07-09 19:12:18 +09:00
parent a653f0b7c6
commit 5b26d071ca
2 changed files with 38 additions and 7 deletions
@@ -3,6 +3,7 @@ export const playgroundExamples = [
id: 'product-api-production',
title: 'Production API deployment',
activePath: 'main.dcdl',
entryPath: 'main.dcdl',
files: {
'main.dcdl': `let
schema = import "./schemas/service.dcdl";
@@ -105,6 +106,7 @@ image = "registry.example.com/checkout-api:2026.06.15";
id: 'tenant-enterprise-plan',
title: 'Enterprise tenant contract',
activePath: 'main.dcdl',
entryPath: 'main.dcdl',
files: {
'main.dcdl': `let
schema = import "./schemas/tenant.dcdl";
@@ -192,6 +194,7 @@ Acme = {
id: 'event-pipeline-prod',
title: 'Event ingestion pipeline',
activePath: 'main.dcdl',
entryPath: 'main.dcdl',
files: {
'main.dcdl': `let
schema = import "./schemas/pipeline.dcdl";
@@ -276,6 +279,7 @@ privacy = ["redact_ip", "hash_user_id"];
id: 'edge-cdn-policy',
title: 'Edge CDN routing policy',
activePath: 'main.dcdl',
entryPath: 'main.dcdl',
files: {
'main.dcdl': `let
schema = import "./schemas/edge.dcdl";
@@ -340,6 +344,7 @@ StrictHeaders = {
id: 'incident-escalation',
title: 'Incident escalation policy',
activePath: 'main.dcdl',
entryPath: 'main.dcdl',
files: {
'main.dcdl': `let
schema = import "./schemas/oncall.dcdl";
+33 -7
View File
@@ -7,7 +7,7 @@ import { keymap } from '@codemirror/view';
import { decodal } from 'decodal-codemirror';
import { playgroundExamples } from './playground-examples.js';
const STORAGE_KEY = 'decodal-playground-project-v1';
const STORAGE_KEY = 'decodal-playground-project-v2';
const starterProject = playgroundExamples[0];
const editorHost = document.getElementById('editor');
@@ -90,6 +90,7 @@ for (const example of playgroundExamples) {
exampleSelect.value = starterProject.id;
setActiveFile(project.activePath);
updateRunLabel();
renderFileTree();
function loadProject() {
@@ -98,12 +99,22 @@ function loadProject() {
if (stored && stored.files && typeof stored.activePath === 'string') {
const files = normalizeFiles(stored.files);
const activePath = files[stored.activePath] === undefined ? Object.keys(files)[0] : stored.activePath;
if (activePath) return { files, activePath };
if (activePath) {
const storedEntryPath = typeof stored.entryPath === 'string' ? normalizePath(stored.entryPath) : '';
const entryPath = files[storedEntryPath] === undefined
? files['main.dcdl'] === undefined ? activePath : 'main.dcdl'
: storedEntryPath;
return { files, activePath, entryPath };
}
}
} catch (_error) {
// Fall back to the starter project.
}
return { files: cloneFiles(starterProject.files), activePath: starterProject.activePath };
return {
files: cloneFiles(starterProject.files),
activePath: starterProject.activePath,
entryPath: starterProject.entryPath ?? starterProject.activePath,
};
}
function cloneFiles(files) {
@@ -127,6 +138,7 @@ function loadExampleProject(exampleId) {
const example = playgroundExamples.find((item) => item.id === exampleId) ?? starterProject;
project.files = cloneFiles(example.files);
project.activePath = example.activePath;
project.entryPath = example.entryPath ?? example.activePath;
exampleSelect.value = example.id;
setActiveFile(project.activePath);
output.textContent = '';
@@ -166,16 +178,27 @@ function setActiveFile(path) {
}
project.activePath = normalized;
setEditorText(project.files[normalized]);
activeFile.textContent = normalized;
activeFile.textContent = normalized === project.entryPath ? `${normalized} (entry)` : normalized;
deleteFile.disabled = Object.keys(project.files).length <= 1;
renderFileTree();
updateRunLabel();
saveProject();
}
function updateRunLabel() {
const entryPath = project.files[project.entryPath] === undefined ? project.activePath : project.entryPath;
project.entryPath = entryPath;
run.textContent = `Run ${entryPath}`;
run.title = `Materialize ${entryPath}`;
}
function execute() {
project.files[project.activePath] = getEditorText();
const entryPath = project.files[project.entryPath] === undefined ? project.activePath : project.entryPath;
project.entryPath = entryPath;
updateRunLabel();
saveProject();
const result = JSON.parse(evaluateProject(project.activePath, JSON.stringify(project.files)));
const result = JSON.parse(evaluateProject(entryPath, JSON.stringify(project.files)));
output.textContent = result.ok ? result.output : result.error;
output.classList.toggle('error', !result.ok);
}
@@ -224,8 +247,8 @@ function renderTreeList(children) {
const button = document.createElement('button');
button.type = 'button';
button.className = child.path === project.activePath ? 'file active' : 'file';
button.textContent = child.name;
button.title = child.path;
button.textContent = child.path === project.entryPath ? `${child.name} *` : child.name;
button.title = child.path === project.entryPath ? `${child.path} (entry point)` : child.path;
button.addEventListener('click', () => setActiveFile(child.path));
item.append(button);
} else {
@@ -276,5 +299,8 @@ deleteFile.addEventListener('click', () => {
if (Object.keys(project.files).length <= 1) return;
if (!confirm(`Delete ${project.activePath}?`)) return;
delete project.files[project.activePath];
if (project.files[project.entryPath] === undefined) {
project.entryPath = Object.keys(project.files).sort()[0];
}
setActiveFile(Object.keys(project.files).sort()[0]);
});