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.
234 lines
5.3 KiB
234 lines
5.3 KiB
<script>
|
|
import { onMount, setContext, createEventDispatcher } from 'svelte';
|
|
import { writable } from 'svelte/store';
|
|
import SplitPane from './SplitPane.svelte';
|
|
import CodeMirror from './CodeMirror.svelte';
|
|
import ComponentSelector from './Input/ComponentSelector.svelte';
|
|
import ModuleEditor from './Input/ModuleEditor.svelte';
|
|
import Output from './Output/index.svelte';
|
|
import InputOutputToggle from './InputOutputToggle.svelte';
|
|
import Bundler from './Bundler.js';
|
|
|
|
export let version = 'beta'; // TODO change this to latest when the time comes
|
|
export let embedded = false;
|
|
export let orientation = 'columns';
|
|
|
|
export function toJSON() {
|
|
// TODO there's a bug here — Svelte hoists this function because
|
|
// it wrongly things that $components is global. Needs to
|
|
// factor in $ variables when determining hoistability
|
|
|
|
version; // workaround
|
|
|
|
return {
|
|
imports: $bundle.imports,
|
|
components: $components
|
|
};
|
|
}
|
|
|
|
export function set(data) {
|
|
components.set(data.components);
|
|
selected.set(data.components[0]);
|
|
|
|
module_editor.set($selected.source, $selected.type);
|
|
output.set($selected, $compile_options);
|
|
|
|
rebundle();
|
|
}
|
|
|
|
export function update(data) {
|
|
const { name, type } = $selected || {};
|
|
|
|
components.set(data.components);
|
|
|
|
const matched_component = data.components.find(file => file.name === name && file.type === type);
|
|
selected.set(matched_component || data.components[0]);
|
|
|
|
if (matched_component) {
|
|
module_editor.update(matched_component.source);
|
|
output.update(matched_component, $compile_options);
|
|
} else {
|
|
module_editor.set(matched_component.source, matched_component.type);
|
|
output.set(matched_component, $compile_options);
|
|
}
|
|
}
|
|
|
|
const dispatch = createEventDispatcher();
|
|
|
|
const components = writable([]);
|
|
const selected = writable(null);
|
|
const bundle = writable(null);
|
|
|
|
const compile_options = writable({
|
|
generate: 'dom',
|
|
dev: false,
|
|
css: false,
|
|
hydratable: false,
|
|
customElement: false,
|
|
immutable: false,
|
|
legacy: false
|
|
});
|
|
|
|
let module_editor;
|
|
let output;
|
|
|
|
let current_token;
|
|
async function rebundle() {
|
|
const token = current_token = {};
|
|
const result = await bundler.bundle($components);
|
|
if (result && token === current_token) bundle.set(result);
|
|
}
|
|
|
|
setContext('REPL', {
|
|
components,
|
|
selected,
|
|
bundle,
|
|
compile_options,
|
|
|
|
rebundle,
|
|
|
|
navigate: item => {
|
|
const match = /^(.+)\.(\w+)$/.exec(item.filename);
|
|
if (!match) return; // ???
|
|
|
|
const [, name, type] = match;
|
|
const component = $components.find(c => c.name === name && c.type === type);
|
|
selected.set(component);
|
|
|
|
output.set($selected, $compile_options);
|
|
|
|
// TODO select the line/column in question
|
|
},
|
|
|
|
handle_change: event => {
|
|
selected.update(component => {
|
|
// TODO this is a bit hacky — we're relying on mutability
|
|
// so that updating components 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;
|
|
});
|
|
|
|
components.update(c => c);
|
|
|
|
// recompile selected component
|
|
output.update($selected, $compile_options);
|
|
|
|
rebundle();
|
|
|
|
dispatch('change', {
|
|
components: $components
|
|
});
|
|
},
|
|
|
|
register_module_editor(editor) {
|
|
module_editor = editor;
|
|
},
|
|
|
|
register_output(handlers) {
|
|
output = handlers;
|
|
},
|
|
|
|
request_focus() {
|
|
module_editor.focus();
|
|
}
|
|
});
|
|
|
|
function handle_select(component) {
|
|
selected.set(component);
|
|
module_editor.set(component.source, component.type);
|
|
output.set($selected, $compile_options);
|
|
}
|
|
|
|
let workers;
|
|
|
|
let input;
|
|
let sourceErrorLoc;
|
|
let runtimeErrorLoc; // TODO refactor this stuff — runtimeErrorLoc is unused
|
|
|
|
let width = typeof window !== 'undefined' ? window.innerWidth : 300;
|
|
let show_output = false;
|
|
|
|
const bundler = process.browser && new Bundler(version);
|
|
|
|
$: if (output && $selected) {
|
|
output.update($selected, $compile_options);
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
.container {
|
|
position: relative;
|
|
width: 100%;
|
|
height: calc(100% - 4.2rem);
|
|
}
|
|
|
|
.repl-inner {
|
|
width: 200%;
|
|
height: 100%;
|
|
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%;
|
|
}
|
|
|
|
.offset {
|
|
transform: translate(-50%,0);
|
|
}
|
|
|
|
@media (min-width: 600px) {
|
|
.container {
|
|
height: 100%;
|
|
}
|
|
|
|
.repl-inner {
|
|
width: 100%;
|
|
}
|
|
|
|
.offset {
|
|
transition: none;
|
|
transform: none;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<div class="container" class:orientation bind:clientWidth={width}>
|
|
<div class="repl-inner" class:offset="{show_output}">
|
|
<SplitPane
|
|
type="{orientation === 'rows' ? 'vertical' : 'horizontal'}"
|
|
fixed="{600 > width}"
|
|
pos="{orientation === 'rows' ? 50 : 60}"
|
|
fixed_pos={50}
|
|
>
|
|
<section slot=a>
|
|
<ComponentSelector {handle_select}/>
|
|
<ModuleEditor bind:this={input} errorLoc="{sourceErrorLoc || runtimeErrorLoc}"/>
|
|
</section>
|
|
|
|
<section slot=b style='height: 100%;'>
|
|
<Output {version} {embedded}/>
|
|
</section>
|
|
</SplitPane>
|
|
</div>
|
|
</div>
|
|
|
|
<InputOutputToggle bind:checked={show_output}/>
|