more REPL stuff

pull/1890/head
Rich Harris 7 years ago
parent 15d25b7374
commit e03ba9051c

@ -1,6 +1,6 @@
<script> <script>
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import { enter } from './events.js'; import { enter } from '../events.js';
export let examples; export let examples;
export let name; export let name;

@ -1,5 +1,5 @@
<script> <script>
import { user, logout } from '../../../user.js'; import { user, logout } from '../../../../user.js';
let showMenu = false; let showMenu = false;
let name; let name;

@ -2,10 +2,10 @@
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import ExampleSelector from './ExampleSelector.html'; import ExampleSelector from './ExampleSelector.html';
import UserMenu from './UserMenu.html'; import UserMenu from './UserMenu.html';
import Icon from '../../../components/icon.html'; import Icon from '../../../../components/icon.html';
import * as doNotZip from 'do-not-zip'; import * as doNotZip from 'do-not-zip';
import downloadBlob from '../_utils/downloadBlob.js'; import downloadBlob from '../../_utils/downloadBlob.js';
import { user } from '../../../user.js'; import { user } from '../../../../user.js';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();

@ -1,3 +1,16 @@
<script context="module">
let codemirror_promise;
let _CodeMirror;
if (process.browser) {
codemirror_promise = import(/* webpackChunkName: "codemirror" */ './_codemirror.js');
codemirror_promise.then(mod => {
_CodeMirror = mod.default;
});
}
</script>
<script> <script>
import { onMount, beforeUpdate, createEventDispatcher } from 'svelte'; import { onMount, beforeUpdate, createEventDispatcher } from 'svelte';
@ -9,6 +22,8 @@
export let error; export let error;
export let errorLoc; export let errorLoc;
export let warningCount = 0; export let warningCount = 0;
export let flex = false;
export let lineNumbers = true;
let w; let w;
let h; let h;
@ -29,12 +44,12 @@
}; };
const refs = {}; const refs = {};
let CodeMirror;
let editor; let editor;
let updating = false; let updating = false;
let marker; let marker;
let error_line; let error_line;
let destroyed = false; let destroyed = false;
let CodeMirror;
$: if (CodeMirror) { $: if (CodeMirror) {
createEditor(mode); createEditor(mode);
@ -79,9 +94,13 @@
} }
onMount(() => { onMount(() => {
import(/* webpackChunkName: "codemirror" */ './_codemirror.js').then(mod => { if (_CodeMirror) {
CodeMirror = _CodeMirror;
} else {
codemirror_promise.then(mod => {
CodeMirror = mod.default; CodeMirror = mod.default;
}); });
}
return () => { return () => {
destroyed = true; destroyed = true;
@ -101,7 +120,7 @@
} }
editor = CodeMirror.fromTextArea(refs.editor, { editor = CodeMirror.fromTextArea(refs.editor, {
lineNumbers: true, lineNumbers,
lineWrapping: true, lineWrapping: true,
indentWithTabs: true, indentWithTabs: true,
indentUnit: 2, indentUnit: 2,
@ -117,6 +136,7 @@
if (!updating) { if (!updating) {
updating = true; updating = true;
code = instance.getValue(); code = instance.getValue();
dispatch('change', { value: code });
} }
}); });
@ -131,11 +151,20 @@
.codemirror-container :global(.CodeMirror) { .codemirror-container :global(.CodeMirror) {
height: 100%; height: 100%;
background: var(--background); /* background: var(--background); */
background: transparent;
font: 300 var(--code-fs)/1.7 var(--font-mono); font: 300 var(--code-fs)/1.7 var(--font-mono);
color: var(--base); color: var(--base);
} }
.codemirror-container.flex :global(.CodeMirror) {
height: auto;
}
.codemirror-container.flex :global(.CodeMirror-lines) {
padding: 0;
}
@media (min-width: 768px) { @media (min-width: 768px) {
.codemirror-container { .codemirror-container {
height: 100%; height: 100%;
@ -186,7 +215,7 @@
- just started to transfer colors from prism to codemirror - just started to transfer colors from prism to codemirror
----------------------------------------------- -----------------------------------------------
--> -->
<div class='codemirror-container' bind:offsetWidth={w} bind:offsetHeight={h}> <div class='codemirror-container' class:flex bind:offsetWidth={w} bind:offsetHeight={h}>
<textarea tabindex='2' bind:this={refs.editor}></textarea> <textarea tabindex='2' bind:this={refs.editor}></textarea>
{#if error} {#if error}

@ -5,9 +5,11 @@
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
export let components; export let components = [];
export let selectedComponent; export let selectedComponent;
let editing = null;
// let previous_components; // let previous_components;
// $: { // $: {
@ -20,7 +22,7 @@
function selectComponent(component, selectedComponent) { function selectComponent(component, selectedComponent) {
if (selectedComponent != component) { if (selectedComponent != component) {
selectedComponent.edit = false; editing = null;
} }
dispatch('select', { component }); dispatch('select', { component });
@ -28,7 +30,7 @@
function editTab(component, selectedComponent) { function editTab(component, selectedComponent) {
if (selectedComponent === component) { if (selectedComponent === component) {
selectedComponent.edit = true; // TODO can we make this local state? editing = selectedComponent;
} }
} }
@ -36,7 +38,7 @@
const match = /(.+)\.(html|js)$/.exec(selectedComponent.name); const match = /(.+)\.(html|js)$/.exec(selectedComponent.name);
selectedComponent.name = match ? match[1] : selectedComponent.name; selectedComponent.name = match ? match[1] : selectedComponent.name;
if (match && match[2]) selectedComponent.type = match[2]; if (match && match[2]) selectedComponent.type = match[2];
selectedComponent.edit = false; editing = null;
components = components; // TODO necessary? components = components; // TODO necessary?
} }
@ -51,12 +53,34 @@
event.target.select(); event.target.select();
}); });
} }
let uid = 1;
function addNew() {
const component = {
name: uid++ ? `Component${uid}` : 'Component1',
type: 'html',
source: ''
};
editing = component;
setTimeout(() => {
// TODO we can do this without IDs
document.getElementById(component.name).scrollIntoView(false);
// selectedComponent = component;
});
dispatch('create', { component });
dispatch('select', { component });
}
</script> </script>
<style> <style>
.component-selector { .component-selector {
position: relative; position: relative;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
overflow: hidden;
} }
.file-tabs { .file-tabs {
@ -66,7 +90,7 @@
white-space: nowrap; white-space: nowrap;
overflow-x: auto; overflow-x: auto;
overflow-y: hidden; overflow-y: hidden;
height: 100%; height: 10em;
} }
.file-tabs button { .file-tabs button {
@ -146,11 +170,11 @@
width: 5rem; width: 5rem;
height: 100%; height: 100%;
text-align: center; text-align: center;
background-color: white;
} }
.add-new:hover { .add-new:hover {
color: var(--flash); color: var(--flash);
background-color: white;
} }
</style> </style>
@ -169,8 +193,8 @@
App.html App.html
</div> </div>
{:else} {:else}
{#if component.edit} {#if component === editing}
<span class="input-sizer">{component.name + (/\./.test(component.name) ? '' : '.html')}</span> <span class="input-sizer">{component.name + (/\./.test(component.name) ? '' : `.${component.type}`)}</span>
<input <input
autofocus autofocus
@ -198,7 +222,7 @@
{/each} {/each}
</div> </div>
<button class="add-new" on:click="{() => dispatch('create')}" title="add new component"> <button class="add-new" on:click="{addNew}" title="add new component">
<Icon name="plus" /> <Icon name="plus" />
</button> </button>
</div> </div>

@ -1,7 +1,7 @@
<script> <script>
import CodeMirror from '../CodeMirror.html'; import CodeMirror from '../CodeMirror.html';
export let selectedComponent; export let component;
export let error; export let error;
export let errorLoc; export let errorLoc;
export let warningCount = 0; export let warningCount = 0;
@ -24,13 +24,14 @@
</style> </style>
<div class="editor-wrapper"> <div class="editor-wrapper">
{#if selectedComponent} {#if component}
<CodeMirror <CodeMirror
mode="{selectedComponent.type === 'js' ? 'javascript' : 'handlebars'}" mode="{component.type === 'js' ? 'javascript' : 'handlebars'}"
bind:code={selectedComponent.source} bind:code={component.source}
{error} {error}
{errorLoc} {errorLoc}
{warningCount} {warningCount}
on:change
on:navigate on:navigate
/> />
{/if} {/if}

@ -10,6 +10,7 @@
export let warningCount; export let warningCount;
</script> </script>
<!-- TODO would be nice if events bubbled -->
<ComponentSelector <ComponentSelector
{components} {components}
bind:selectedComponent bind:selectedComponent
@ -19,9 +20,10 @@
/> />
<ModuleEditor <ModuleEditor
bind:selectedComponent component={selectedComponent}
error={sourceError} error={sourceError}
errorLoc="{sourceErrorLoc || runtimeErrorLoc}" errorLoc="{sourceErrorLoc || runtimeErrorLoc}"
{warningCount} {warningCount}
on:change
on:navigate on:navigate
/> />

@ -0,0 +1,55 @@
<script>
import { createEventDispatcher } from 'svelte';
import * as fleece from 'golden-fleece';
import CodeMirror from '../CodeMirror.html';
const dispatch = createEventDispatcher();
export let value;
let json5;
if (value === undefined) {
json5 = 'undefined';
} else {
json5 = fleece.stringify(value);
}
$: try {
value = fleece.evaluate(json5);
} catch (err) {
console.error(Object.keys(err));
// TODO show in UI
}
function handleChange(event) {
try {
const value = fleece.evaluate(event.detail.value);
dispatch('change', { value });
} catch (err) {
// TODO indicate error
}
}
</script>
<style>
.prop-editor {
border: 1px solid #eee;
}
</style>
<div class="prop-editor">
<CodeMirror
mode="json"
bind:code={json5}
lineNumbers={false}
on:change={handleChange}
flex
/>
</div>
<!-- <CodeMirror
mode="json"
bind:code={json5}
error={dataError}
errorLoc={dataErrorLoc}
/> -->

@ -8,10 +8,19 @@
export let bundle; export let bundle;
export let dom; export let dom;
export let ssr; export let ssr;
export let data; export let values;
export let props;
export let sourceError; export let sourceError;
export let error; export let error;
export function setProp(prop, value) {
if (component) {
component[prop] = value;
}
}
let component;
const refs = {}; const refs = {};
const importCache = {}; const importCache = {};
let pendingImports = 0; let pendingImports = 0;
@ -70,8 +79,6 @@
let init; let init;
onMount(() => { onMount(() => {
let component;
refs.child.addEventListener('load', () => { refs.child.addEventListener('load', () => {
const iframe = refs.child; const iframe = refs.child;
const body = iframe.contentDocument.body; const body = iframe.contentDocument.body;
@ -150,7 +157,7 @@
const createHtml = () => { const createHtml = () => {
try { try {
evalInIframe(`${ssr.code} evalInIframe(`${ssr.code}
var rendered = SvelteComponent.render(${JSON.stringify(data)}); var rendered = SvelteComponent.render(${JSON.stringify(values)});
if (rendered.css.code) { if (rendered.css.code) {
var style = document.createElement('style'); var style = document.createElement('style');
@ -183,7 +190,7 @@
var component = new SvelteComponent({ var component = new SvelteComponent({
target: document.body, target: document.body,
data: ${JSON.stringify(data)} props: ${JSON.stringify(values)}
});`); });`);
component = window.app = window.component = iframe.contentWindow.component; component = window.app = window.component = iframe.contentWindow.component;
@ -249,17 +256,18 @@
toDestroy = component; toDestroy = component;
component = null; component = null;
if (data !== undefined) init(); if (values !== undefined) init();
}; };
data_handler = data => { values_handler = values => {
console.log({ values });
if (updating) return; if (updating) return;
try { try {
if (component) { if (component) {
error = null; error = null;
updating = true; updating = true;
component.$set(data); component.$set(values);
updating = false; updating = false;
} else { } else {
init(); init();
@ -274,16 +282,35 @@
error = e; error = e;
} }
}; };
props_handler = props => {
if (!component) {
// TODO can this happen?
console.error(`no component to bind to`);
return;
}
props.forEach(prop => {
// TODO should there be a public API for binding?
// e.g. `component.$watch(prop, handler)`?
// (answer: probably)
component.$$.bound[prop] = value => {
dispatch('binding', { prop, value });
};
});
};
}); });
}); });
function noop(){} function noop(){}
let run = noop; let run = noop;
let bundle_handler = noop; let bundle_handler = noop;
let data_handler = noop; let values_handler = noop;
let props_handler = noop;
$: bundle_handler(bundle); $: bundle_handler(bundle);
$: data_handler(data); $: values_handler(values);
$: props_handler(props);
</script> </script>
<style> <style>

@ -1,20 +1,36 @@
<script> <script>
import SplitPane from '../SplitPane.html'; import SplitPane from '../SplitPane.html';
import Viewer from './Viewer.html'; import Viewer from './Viewer.html';
import PropEditor from './PropEditor.html';
import CodeMirror from '../CodeMirror.html'; import CodeMirror from '../CodeMirror.html';
export let bundle export let bundle
export let compiled; export let compiled;
export let dom; export let dom;
export let ssr; export let ssr;
export let data; export let props;
export let values;
export let json5; export let json5;
export let sourceError; export let sourceError;
export let runtimeError; export let runtimeError;
export let dataError; export let dataError;
export let dataErrorLoc; export let dataErrorLoc;
// refs
let viewer;
const propEditors = {};
let view = 'result'; let view = 'result';
function setPropFromViewer(prop, value) {
// console.log(`setting prop from component`, prop, value);
// propEditors[prop].setValue(value);
}
function setPropFromEditor(prop, value) {
console.log(`setting prop from editor`, prop, value);
viewer.setProp(prop, value);
}
</script> </script>
<style> <style>
@ -46,6 +62,27 @@
font: 300 1.2rem/1.5 var(--font-ui); font: 300 1.2rem/1.5 var(--font-ui);
padding: 1.2rem 0 0.8rem 1rem; padding: 1.2rem 0 0.8rem 1rem;
} }
.loading.message {
position: absolute !important;
background-color: #666;
top: 1em;
left: 50%;
transform: translate(-50%,0);
}
.props {
display: grid;
padding: 0.5em;
grid-template-columns: auto 1fr;
grid-template-rows: min-content;
grid-column-gap: 0.5em;
overflow-y: auto;
}
.options {
padding: 0.5em;
}
</style> </style>
<div class="view-toggle"> <div class="view-toggle">
@ -66,31 +103,39 @@
<div slot="a"> <div slot="a">
{#if bundle} {#if bundle}
<Viewer <Viewer
bind:this={viewer}
{bundle} {bundle}
{dom} {dom}
{ssr} {ssr}
{data} {data}
bind:values
{props}
{sourceError} {sourceError}
bind:error={runtimeError} bind:error={runtimeError}
on:data="{e => updateData(e.detail.current)}" on:data="{e => updateData(e.detail.current)}"
on:navigate={navigate} on:navigate={navigate}
on:binding="{e => setPropFromViewer(e.detail.prop, e.detail.value)}"
/> />
{:else} {:else}
<p class="loading">loading Svelte compiler...</p> <!-- TODO componentise this -->
<p class="loading message">loading Svelte compiler...</p>
{/if} {/if}
</div> </div>
<section slot="b"> <section slot="b">
<h3> <h3>Props editor</h3>
Props editor
</h3>
<CodeMirror <div class="props">
mode="json" {#each props.sort() as prop (prop)}
bind:code={json5} <code style="display: block; whitespace: pre;">{prop}</code>
error={dataError}
errorLoc={dataErrorLoc} <!-- TODO `bind:this={propEditors[prop]}` — currently fails -->
<PropEditor
value={values[prop]}
on:change="{e => setPropFromEditor(prop, e.detail.value)}"
/> />
{/each}
</div>
</section> </section>
</SplitPane> </SplitPane>
{:else} {:else}
@ -106,11 +151,9 @@
</div> </div>
<section slot="b"> <section slot="b">
<h3> <h3>Compiler options</h3>
Compiler options
</h3>
<div>TODO</div> <div class="options">TODO</div>
</section> </section>
</SplitPane> </SplitPane>
{/if} {/if}

@ -9,8 +9,7 @@
export let version; export let version;
export let components; export let components;
export let selectedComponent; export let selectedComponent;
export let data; export let values;
export let json5;
let bundle = null; let bundle = null;
let dom; let dom;
@ -20,10 +19,11 @@
let dataError = null; let dataError = null;
let dataErrorLoc = null; let dataErrorLoc = null;
let warningCount = 0; let warningCount = 0;
let compiled = ''; let code = '';
let uid = 0; let uid = 0;
let props = [];
let sourceErrorLoc, runtimeErrorLoc; let sourceErrorLoc;
let runtimeErrorLoc;
let worker; let worker;
@ -38,11 +38,13 @@
case 'bundled': case 'bundled':
({ bundle, dom, ssr, warningCount, error: sourceError } = event.data.result); ({ bundle, dom, ssr, warningCount, error: sourceError } = event.data.result);
console.log(dom, sourceError);
runtimeError = null; runtimeError = null;
break; break;
case 'compiled': case 'compiled':
compiled = event.data.result; code = event.data.result.code;
if (event.data.result.props) props = event.data.result.props;
break; break;
} }
} }
@ -57,22 +59,6 @@
}; };
}); });
function createComponent() {
const newComponent = {
name: uid++ ? `Component${uid}` : 'Component1',
type: 'html',
source: '',
edit: true
};
components = components.concat(newComponent);
setTimeout(() => {
document.getElementById(newComponent.name).scrollIntoView(false);
selectedComponent = newComponent;
});
}
function removeComponent() { function removeComponent() {
const component = selectedComponent; const component = selectedComponent;
@ -92,6 +78,35 @@
} }
} }
function compile(component) {
if (component.type === 'html') {
worker.postMessage({
type: 'compile',
component,
entry: component === components[0]
});
} else {
code = component.source;
}
}
function handleSelect(event) {
console.log(`handleSelect`);
selectedComponent = event.detail.component;
// compile(selectedComponent);
}
function handleChange() {
console.log(`handleChange`);
// recompile selected component
compile(selectedComponent);
// regenerate bundle (TODO do this in a separate worker?)
worker.postMessage({ type: 'bundle', components });
}
function updateData(current) { function updateData(current) {
const data = fleece.evaluate(json5); const data = fleece.evaluate(json5);
@ -121,6 +136,15 @@
: null; : null;
} }
$: if (worker && components) {
console.log(`posting`, worker, components);
worker.postMessage({ type: 'bundle', components });
}
$: if (worker && selectedComponent) {
compile(selectedComponent);
}
$: try { $: try {
data= fleece.evaluate(json5); data= fleece.evaluate(json5);
dataError = null; dataError = null;
@ -129,34 +153,6 @@
dataError = err; dataError = err;
dataErrorLoc = err && err.loc; dataErrorLoc = err && err.loc;
} }
$: if (worker && components.length > 0) {
worker.postMessage({ type: 'bundle', components });
}
let last_selected_component;
$: {
// slightly counterintuitively, we only want to rebundle if
// this is the *same* component — not if we've just selected
// a different one
// if (selectedComponent === last_selected_component) {
// if (worker && components.length > 0) {
// console.log(`2`, components);
// worker.postMessage({ type: 'bundle', components });
// }
// }
// recompile the currently selected component
if (selectedComponent) {
if (selectedComponent.type === 'html') {
worker.postMessage({ type: 'compile', component: selectedComponent });
} else {
compiled = selectedComponent.source;
}
}
last_selected_component = selectedComponent;
}
</script> </script>
<style> <style>
@ -243,25 +239,26 @@
<SplitPane type="horizontal"> <SplitPane type="horizontal">
<section slot=a> <section slot=a>
<Input <Input
{components} bind:components
bind:selectedComponent {selectedComponent}
error={sourceError} error={sourceError}
errorLoc="{sourceErrorLoc || runtimeErrorLoc}" errorLoc="{sourceErrorLoc || runtimeErrorLoc}"
{warningCount} {warningCount}
on:create={createComponent} on:create="{e => components = components.concat(e.detail.component)}"
on:remove={removeComponent}
on:select="{e => selectedComponent = e.detail.component}" on:select="{e => selectedComponent = e.detail.component}"
on:remove={removeComponent}
on:change="{handleChange}"
/> />
</section> </section>
<section slot=b style='height: 100%;'> <section slot=b style='height: 100%;'>
<Output <Output
{compiled} {code}
{bundle} {bundle}
{ssr} {ssr}
{dom} {dom}
{data} {props}
{json5} {values}
{sourceError} {sourceError}
{runtimeError} {runtimeError}
{dataError} {dataError}

@ -1,62 +0,0 @@
.module-name {
position: relative;
display: block;
background-color: #ff00d4;
}
.panel-header {
/* padding: 0 40px .5em 0; */
background-color: #ff00d4;
}
.dropdown {
position: relative;
display: block;
float: left;
padding: 0 2em 0 0;
background-color: #ff00d4;
}
.dropdown::after {
content: '▼';
position: absolute;
right: 1rem;
top: .55rem;
font-size: .8em;
color: #999;
pointer-events: none;
background-color: #ff00d4;
}
.input-wrapper {
position: relative;
display: block;
float: left;
line-height: 1;
/* margin: 0 .3em 0 0; */
background-color: #ff00d4;
}
.file-tabs li.active {
/* background-color: var(--back-light); */
background-color: #ff00d4;
}
.widther {
display: block;
font-family: inherit;
font-size: inherit;
border: 1px solid #eee;
padding: calc(.5em - 1px) .25em;
line-height: 1;
background-color: #ff00d4;
}
.file-extension {
display: inline-block;
padding: calc(.5em - 1px) 0;
color: var(--prime);
left: -.2em;
pointer-events: none;
background-color: #ff00d4;
}

@ -1,45 +1,25 @@
<script context="module">
// export async function preload({ query }) {
// return {
// examples,
// version: query.version || 'latest',
// components: [],
// data: {},
// name: 'loading...',
// json5: '{}',
// gist: null
// };
// }
</script>
<script> <script>
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { locate } from 'locate-character'; import { locate } from 'locate-character';
import * as fleece from 'golden-fleece'; import * as fleece from 'golden-fleece';
import AppControls from './_components/AppControls.html'; import AppControls from './_components/AppControls/index.html';
import Repl from './_components/Repl.html'; import Repl from './_components/Repl.html';
import examples from '../../../content/examples/manifest.json'; import examples from '../../../content/examples/manifest.json';
export let query; export let query;
const refs = {};
let version = query.version || 'alpha'; let version = query.version || 'alpha';
let components = []; let components;
let selectedComponent; let selectedComponent;
let data = {}; let values = {};
let name = 'loading...'; let name = 'loading...';
let json5 = '{}';
let gist = null;
let zen_mode = false; let zen_mode = false;
let bundle;
$: if (typeof history !== 'undefined') { $: if (typeof history !== 'undefined') {
const params = []; const params = [];
if (version !== 'latest') params.push(`version=${version}`);
if (query.gist) params.push(`gist=${gist.id}`); if (version !== 'latest') params.push(`version=${version}`);
else if (query.data) params.push(`data=${query.data}`); if (query.gist) params.push(`gist=${query.gist}`);
else if (query.demo) params.push(`demo=${query.demo}`); else if (query.demo) params.push(`demo=${query.demo}`);
const url = params.length > 0 const url = params.length > 0
@ -56,33 +36,11 @@
}); });
}); });
function tryParseData(encoded) {
try {
const base64 = decodeURIComponent(encoded);
const json = atob(base64);
return JSON.parse(json);
} catch (err) {
console.error(err.message);
return {};
}
}
function stringifyComponents(components) {
return JSON.stringify(
components.map(component => {
return {
name: component.name,
source: component.source
};
})
);
}
onMount(() => { onMount(() => {
if (query.gist) { if (query.gist) {
fetch(`gist/${query.gist}`).then(r => r.json()).then(gist => { fetch(`gist/${query.gist}`).then(r => r.json()).then(gist => {
name = gist.description; name = gist.description;
json5 = '{}'; values = {};
components = Object.keys(gist.files) components = Object.keys(gist.files)
.map(file => { .map(file => {
@ -92,7 +50,9 @@
const source = gist.files[file].content; const source = gist.files[file].content;
// while we're here... // while we're here...
if (file === 'data.json' || file === 'data.json5') json5 = source; if (file === 'data.json' || file === 'data.json5') {
values = tryParseData(source) || {};
}
return { return {
name: file.slice(0, dot), name: file.slice(0, dot),
@ -118,18 +78,31 @@
}); });
function load_demo(slug) { function load_demo(slug) {
fetch(`api/${slugs.has(query.demo) ? 'examples' : 'guide/demo'}/${query.demo}`).then(async response => { const url = slugs.has(query.demo)
? `api/examples/${query.demo}`
: `guide/demo/${query.demo}.json`;
fetch(url).then(async response => {
if (response.ok) { if (response.ok) {
const demo = await response.json(); const demo = await response.json();
name = demo.title; name = demo.title;
json5 = demo.json5 || '{}'; values = tryParseData(demo.json5) || {}; // TODO make this more error-resistant
components = demo.components; components = demo.components;
selectedComponent = components[0]; selectedComponent = components[0];
} }
}); });
} }
function tryParseData(json5) {
try {
return fleece.evaluate(json5);
} catch (err) {
return null;
}
}
$: if (process.browser && query.demo) { $: if (process.browser && query.demo) {
load_demo(query.demo); load_demo(query.demo);
} }
@ -198,22 +171,19 @@
<div class="repl-outer {zen_mode ? 'zen-mode' : ''}"> <div class="repl-outer {zen_mode ? 'zen-mode' : ''}">
<AppControls <AppControls
{examples} {examples}
{bundle}
{components} {components}
{data} {values}
{json5} {name}
{gist} gist_id={query.gist}
bind:name
bind:zen_mode bind:zen_mode
on:select="{e => (query.demo = e.detail.slug, gist = null)}" on:select="{e => query = Object.assign({}, query, { demo: e.detail.slug, gist: null })}"
on:forked="{e => gist = e.detail.gist}" on:forked="{e => query = Object.assign({}, query, { demo: null, gist: e.detail.gist })}"
/> />
<Repl <Repl
bind:version bind:version
bind:selectedComponent
{components} {components}
{data} {selectedComponent}
{json5} {values}
/> />
</div> </div>

@ -0,0 +1,15 @@
export default function process_markdown(markdown) {
const match = /---\n([\s\S]+?)\n---/.exec(markdown);
const frontMatter = match[1];
const content = markdown.slice(match[0].length);
const metadata = {};
frontMatter.split('\n').forEach(pair => {
const colonIndex = pair.indexOf(':');
metadata[pair.slice(0, colonIndex).trim()] = pair
.slice(colonIndex + 1)
.trim();
});
return { metadata, content };
}

@ -339,7 +339,6 @@ a:hover > .icon { stroke: var(--flash) }
/* tables --------------------------------- */ /* tables --------------------------------- */
table { table {
margin: 4rem 0;
width: 100%; width: 100%;
font-size: var(--h5); font-size: var(--h5);
} }

@ -1,10 +1,10 @@
self.window = self; // egregious hack to get magic-string to work in a worker self.window = self; // egregious hack to get magic-string to work in a worker
let ready = false;
let pending_components;
let pending_component;
let version; let version;
let fulfil_ready;
const ready = new Promise(f => {
fulfil_ready = f;
});
self.addEventListener('message', async event => { self.addEventListener('message', async event => {
switch (event.data.type) { switch (event.data.type) {
@ -17,30 +17,21 @@ self.addEventListener('message', async event => {
break; break;
case 'bundle': case 'bundle':
if (ready) { await ready;
const result = await bundle(event.data.components);
if (result) {
postMessage({ postMessage({
type: 'bundled', type: 'bundled',
result result: await bundle(event.data.components)
}); });
}
} else {
pending_components = event.data.components;
}
break; break;
case 'compile': case 'compile':
if (ready) { await ready;
const result = await compile(event.data.component);
postMessage({ postMessage({
type: 'compiled', type: 'compiled',
result result: compile(event.data.component, event.data.entry)
}); });
} else {
pending_component = event.data.component;
}
break; break;
} }
}); });
@ -59,29 +50,7 @@ async function init(version) {
`https://unpkg.com/rollup/dist/rollup.browser.js` `https://unpkg.com/rollup/dist/rollup.browser.js`
); );
if (pending_components) { fulfil_ready();
const result = await bundle(pending_components);
if (result) {
postMessage({
type: 'bundled',
result
});
}
pending_components = null;
}
if (pending_component) {
const result = await compile(pending_component);
postMessage({
type: 'compiled',
result
});
pending_component = null;
}
ready = true;
return version === 'local' ? version : svelte.VERSION; return version === 'local' ? version : svelte.VERSION;
} }
@ -264,19 +233,19 @@ async function bundle(components) {
} }
} }
function compile(component) { function compile(component, entry) {
try { try {
const { js } = svelte.compile(component.source, Object.assign({ const { js, stats } = svelte.compile(component.source, Object.assign({
// TODO make options configurable // TODO make options configurable
name: component.name, name: component.name,
filename: component.name + '.html', filename: component.name + '.html',
}, commonCompilerOptions)); }, commonCompilerOptions));
return js.code; return { code: js.code, props: entry ? stats.props : null };
} catch (err) { } catch (err) {
let result = `/* Error compiling component\n\n${err.message}`; let result = `/* Error compiling component\n\n${err.message}`;
if (err.frame) result += `\n${err.frame}`; if (err.frame) result += `\n${err.frame}`;
result += `\n\n*/`; result += `\n\n*/`;
return result; return { code: result, props: null };
} }
} }

Loading…
Cancel
Save