mirror of https://github.com/sveltejs/svelte
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
302 lines
6.7 KiB
302 lines
6.7 KiB
<script>
|
|
import { onMount } from 'svelte';
|
|
import { writable } from 'svelte/store';
|
|
import * as fleece from 'golden-fleece';
|
|
import SplitPane from './SplitPane.html';
|
|
import CodeMirror from './CodeMirror.html';
|
|
import Input from './Input/index.html';
|
|
import Output from './Output/index.html';
|
|
|
|
export let version = 'alpha'; // TODO change this to latest when the time comes
|
|
export let app;
|
|
|
|
export function toJSON() {
|
|
// TODO there's a bug here — Svelte hoists this function because
|
|
// it wrongly things that $component_store is global. Needs to
|
|
// factor in $ variables when determining hoistability
|
|
|
|
version; // workaround
|
|
|
|
return {
|
|
components: $component_store,
|
|
values: $values_store
|
|
};
|
|
}
|
|
|
|
let component_store = writable([]);
|
|
let values_store = writable({});
|
|
let selected_store = writable(null);
|
|
|
|
$: {
|
|
component_store.set(app.components);
|
|
values_store.set(app.values);
|
|
selected_store.set(app.components[0]);
|
|
}
|
|
|
|
$: {
|
|
// necessary pending https://github.com/sveltejs/svelte/issues/1889
|
|
$component_store;
|
|
$values_store;
|
|
}
|
|
|
|
let workers;
|
|
|
|
let bundle = null;
|
|
let dom;
|
|
let ssr;
|
|
let sourceError = null;
|
|
let runtimeError = null;
|
|
let warningCount = 0;
|
|
let js = '';
|
|
let css = '';
|
|
let uid = 0;
|
|
let props = null;
|
|
let sourceErrorLoc;
|
|
let runtimeErrorLoc;
|
|
|
|
let width = typeof window !== 'undefined' ? window.innerWidth : 300;
|
|
let show_output = false;
|
|
|
|
onMount(async () => {
|
|
workers = {
|
|
compiler: new Worker('/workers/compiler.js'),
|
|
bundler: new Worker('/workers/bundler.js')
|
|
};
|
|
|
|
workers.compiler.postMessage({ type: 'init', version });
|
|
workers.compiler.onmessage = event => {
|
|
js = event.data.js;
|
|
css = event.data.css;
|
|
if (event.data.props) props = event.data.props;
|
|
};
|
|
|
|
workers.bundler.postMessage({ type: 'init', version });
|
|
workers.bundler.onmessage = event => {
|
|
({ bundle, dom, ssr, warningCount, error: sourceError } = event.data);
|
|
if (sourceError) console.error(sourceError);
|
|
runtimeError = null;
|
|
};
|
|
|
|
return () => {
|
|
workers.compiler.terminate();
|
|
workers.bundler.terminate();
|
|
};
|
|
});
|
|
|
|
function removeComponent() {
|
|
const selected = $selected_store;
|
|
|
|
if (selected.name === 'App') {
|
|
// App.html can't be removed
|
|
selected.source = '';
|
|
// $selected_store.set(selected);
|
|
} else {
|
|
const components = $component_store;
|
|
const index = components.indexOf(selected);
|
|
|
|
if (~index) {
|
|
component_store.set(components.slice(0, index).concat(components.slice(index + 1)));
|
|
} else {
|
|
console.error(`Could not find component! That's... odd`);
|
|
}
|
|
|
|
selected_store.set(components[index] || components[components.length - 1]);
|
|
}
|
|
}
|
|
|
|
function compile(component) {
|
|
if (component.type === 'html') {
|
|
workers.compiler.postMessage({
|
|
type: 'compile',
|
|
source: component.source,
|
|
options: {
|
|
name: component.name,
|
|
filename: `${component.name}.html`
|
|
},
|
|
entry: component === $component_store[0]
|
|
});
|
|
} else {
|
|
js = css = `/* Select a component to see its compiled code */`;
|
|
}
|
|
}
|
|
|
|
function handleChange(event) {
|
|
selected_store.update(component => {
|
|
// TODO this is a bit hacky — we're relying on mutability
|
|
// so that updating component_store works... might be better
|
|
// if a) components had unique IDs, b) we tracked selected
|
|
// *index* rather than component, and c) `selected` was
|
|
// derived from `components` and `index`
|
|
component.source = event.detail.value;
|
|
return component;
|
|
});
|
|
|
|
component_store.update(c => c);
|
|
|
|
// recompile selected component
|
|
compile($selected_store);
|
|
|
|
// regenerate bundle (TODO do this in a separate worker?)
|
|
workers.bundler.postMessage({ type: 'bundle', components: $component_store });
|
|
}
|
|
|
|
function navigate(filename) {
|
|
const name = filename.replace(/\.html$/, '');
|
|
|
|
console.error(`TODO navigate`);
|
|
|
|
// if (selected.name === name) return;
|
|
// selected = components.find(c => c.name === name);
|
|
}
|
|
|
|
$: if (sourceError && $selected_store) {
|
|
sourceErrorLoc = sourceError.filename === `${$selected_store.name}.${$selected_store.type}`
|
|
? sourceError.start
|
|
: null;
|
|
}
|
|
|
|
$: if (runtimeError && $selected_store) {
|
|
runtimeErrorLoc = runtimeError.filename === `${$selected_store.name}.${$selected_store.type}`
|
|
? runtimeError.start
|
|
: null;
|
|
}
|
|
|
|
$: if (workers && app.components) {
|
|
workers.bundler.postMessage({ type: 'bundle', components: app.components });
|
|
}
|
|
|
|
$: if (workers && $selected_store) {
|
|
compile($selected_store);
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
.repl-inner {
|
|
width: 200%;
|
|
height: calc(100% - 4.2rem);
|
|
transition: transform 0.3s;
|
|
}
|
|
|
|
.repl-inner :global(section) {
|
|
position: relative;
|
|
padding: 4.2rem 0 0 0;
|
|
height: 100%;
|
|
}
|
|
|
|
.repl-inner :global(section) > :global(*):first-child {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 4.2rem;
|
|
}
|
|
|
|
.repl-inner :global(section) > :global(*):last-child {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
.repl-inner :global(.message) {
|
|
position: relative;
|
|
border-radius: var(--border-r);
|
|
margin: 0;
|
|
padding: 1.2rem 1.6rem 1.2rem 4.4rem;
|
|
vertical-align: middle;
|
|
font: 300 1.2rem/1.7 var(--font-ui);
|
|
color: white;
|
|
}
|
|
|
|
.repl-inner :global(.message::before) {
|
|
content: '!';
|
|
position: absolute;
|
|
left: 1.2rem;
|
|
top: 1.1rem;
|
|
width: 1rem;
|
|
height: 1rem;
|
|
text-align: center;
|
|
line-height: 1;
|
|
padding: .4rem;
|
|
border-radius: 50%;
|
|
color: white;
|
|
border: .2rem solid white;
|
|
}
|
|
|
|
.repl-inner :global(.error.message) {
|
|
background-color: #da106e;
|
|
}
|
|
|
|
.repl-inner :global(.warning.message) {
|
|
background-color: #e47e0a;
|
|
}
|
|
|
|
.repl-inner :global(.info.message) {
|
|
background-color: var(--second);
|
|
animation: fade-in .4s .2s both;
|
|
}
|
|
|
|
.repl-inner :global(.error) :global(.filename) {
|
|
cursor: pointer;
|
|
}
|
|
|
|
.input-output-toggle {
|
|
position: absolute;
|
|
}
|
|
|
|
.offset {
|
|
transform: translate(-50%,0);
|
|
}
|
|
|
|
@media (min-width: 768px) {
|
|
.repl-inner {
|
|
height: 100%;
|
|
}
|
|
|
|
.input-output-toggle {
|
|
display: none;
|
|
}
|
|
|
|
.offset {
|
|
transition: none;
|
|
transform: none;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<div class="repl-inner" class:offset="{show_output}" bind:offsetWidth={width}>
|
|
<SplitPane type="horizontal" fixed="{768 > width}" pos={60} fixed_pos={60}>
|
|
<section slot=a>
|
|
<Input
|
|
{component_store}
|
|
{selected_store}
|
|
{values_store}
|
|
error={sourceError}
|
|
errorLoc="{sourceErrorLoc || runtimeErrorLoc}"
|
|
{warningCount}
|
|
on:remove={removeComponent}
|
|
on:change="{handleChange}"
|
|
/>
|
|
</section>
|
|
|
|
<section slot=b style='height: 100%;'>
|
|
<Output
|
|
{version}
|
|
{selected_store}
|
|
{js}
|
|
{css}
|
|
{bundle}
|
|
{ssr}
|
|
{dom}
|
|
{props}
|
|
{values_store}
|
|
{sourceError}
|
|
{runtimeError}
|
|
/>
|
|
</section>
|
|
</SplitPane>
|
|
</div>
|
|
|
|
<label class="input-output-toggle">
|
|
<span>input</span>
|
|
<input type="checkbox" bind:checked={show_output}>
|
|
<span>output</span>
|
|
</label> |