mirror of https://github.com/sveltejs/svelte
parent
c7f9f514be
commit
c3b4e6c792
@ -1,44 +0,0 @@
|
|||||||
const workers = new Map();
|
|
||||||
|
|
||||||
let uid = 1;
|
|
||||||
|
|
||||||
export default class Bundler {
|
|
||||||
constructor(version) {
|
|
||||||
if (!workers.has(version)) {
|
|
||||||
const worker = new Worker('/workers/bundler.js');
|
|
||||||
worker.postMessage({ type: 'init', version });
|
|
||||||
workers.set(version, worker);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.worker = workers.get(version);
|
|
||||||
|
|
||||||
this.handlers = new Map();
|
|
||||||
|
|
||||||
this.worker.addEventListener('message', event => {
|
|
||||||
const handler = this.handlers.get(event.data.id);
|
|
||||||
|
|
||||||
if (handler) { // if no handler, was meant for a different REPL
|
|
||||||
handler(event.data);
|
|
||||||
this.handlers.delete(event.data.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bundle(components) {
|
|
||||||
return new Promise(fulfil => {
|
|
||||||
const id = uid++;
|
|
||||||
|
|
||||||
this.handlers.set(id, fulfil);
|
|
||||||
|
|
||||||
this.worker.postMessage({
|
|
||||||
id,
|
|
||||||
type: 'bundle',
|
|
||||||
components
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
this.worker.terminate();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,273 +0,0 @@
|
|||||||
<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>
|
|
||||||
import { onMount, beforeUpdate, createEventDispatcher, getContext } from 'svelte';
|
|
||||||
import Message from './Message.svelte';
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
const { navigate } = getContext('REPL');
|
|
||||||
|
|
||||||
export let readonly = false;
|
|
||||||
export let errorLoc = null;
|
|
||||||
export let flex = false;
|
|
||||||
export let lineNumbers = true;
|
|
||||||
export let tab = true;
|
|
||||||
|
|
||||||
let w;
|
|
||||||
let h;
|
|
||||||
let code = '';
|
|
||||||
let mode;
|
|
||||||
|
|
||||||
// We have to expose set and update methods, rather
|
|
||||||
// than making this state-driven through props,
|
|
||||||
// because it's difficult to update an editor
|
|
||||||
// without resetting scroll otherwise
|
|
||||||
export async function set(new_code, new_mode) {
|
|
||||||
if (new_mode !== mode) {
|
|
||||||
await createEditor(mode = new_mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
code = new_code;
|
|
||||||
updating_externally = true;
|
|
||||||
if (editor) editor.setValue(code);
|
|
||||||
updating_externally = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function update(new_code) {
|
|
||||||
code = new_code;
|
|
||||||
|
|
||||||
if (editor) {
|
|
||||||
const { left, top } = editor.getScrollInfo();
|
|
||||||
editor.setValue(code = new_code);
|
|
||||||
editor.scrollTo(left, top);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resize() {
|
|
||||||
editor.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function focus() {
|
|
||||||
editor.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
const modes = {
|
|
||||||
js: {
|
|
||||||
name: 'javascript',
|
|
||||||
json: false
|
|
||||||
},
|
|
||||||
json: {
|
|
||||||
name: 'javascript',
|
|
||||||
json: true
|
|
||||||
},
|
|
||||||
svelte: {
|
|
||||||
name: 'handlebars',
|
|
||||||
base: 'text/html'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const refs = {};
|
|
||||||
let editor;
|
|
||||||
let updating_externally = false;
|
|
||||||
let marker;
|
|
||||||
let error_line;
|
|
||||||
let destroyed = false;
|
|
||||||
let CodeMirror;
|
|
||||||
|
|
||||||
$: if (editor && w && h) {
|
|
||||||
editor.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
$: {
|
|
||||||
if (marker) marker.clear();
|
|
||||||
|
|
||||||
if (errorLoc) {
|
|
||||||
const line = errorLoc.line - 1;
|
|
||||||
const ch = errorLoc.column;
|
|
||||||
|
|
||||||
marker = editor.markText({ line, ch }, { line, ch: ch + 1 }, {
|
|
||||||
className: 'error-loc'
|
|
||||||
});
|
|
||||||
|
|
||||||
error_line = line;
|
|
||||||
} else {
|
|
||||||
error_line = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let previous_error_line;
|
|
||||||
$: if (editor) {
|
|
||||||
if (previous_error_line != null) {
|
|
||||||
editor.removeLineClass(previous_error_line, 'wrap', 'error-line')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error_line && (error_line !== previous_error_line)) {
|
|
||||||
editor.addLineClass(error_line, 'wrap', 'error-line');
|
|
||||||
previous_error_line = error_line;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
if (_CodeMirror) {
|
|
||||||
CodeMirror = _CodeMirror;
|
|
||||||
createEditor(mode || 'svelte').then(() => {
|
|
||||||
editor.setValue(code || '');
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
codemirror_promise.then(async mod => {
|
|
||||||
CodeMirror = mod.default;
|
|
||||||
await createEditor(mode || 'svelte');
|
|
||||||
editor.setValue(code || '');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
destroyed = true;
|
|
||||||
if (editor) editor.toTextArea();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function createEditor(mode) {
|
|
||||||
if (destroyed || !CodeMirror) return;
|
|
||||||
|
|
||||||
if (editor) editor.toTextArea();
|
|
||||||
|
|
||||||
const opts = {
|
|
||||||
lineNumbers,
|
|
||||||
lineWrapping: true,
|
|
||||||
indentWithTabs: true,
|
|
||||||
indentUnit: 2,
|
|
||||||
tabSize: 2,
|
|
||||||
value: '',
|
|
||||||
mode: modes[mode] || {
|
|
||||||
name: mode
|
|
||||||
},
|
|
||||||
readOnly: readonly
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!tab) opts.extraKeys = {
|
|
||||||
Tab: tab,
|
|
||||||
'Shift-Tab': tab
|
|
||||||
};
|
|
||||||
|
|
||||||
// Creating a text editor is a lot of work, so we yield
|
|
||||||
// the main thread for a moment. This helps reduce jank
|
|
||||||
await sleep(50);
|
|
||||||
|
|
||||||
if (destroyed) return;
|
|
||||||
|
|
||||||
editor = CodeMirror.fromTextArea(refs.editor, opts);
|
|
||||||
|
|
||||||
editor.on('change', instance => {
|
|
||||||
if (!updating_externally) {
|
|
||||||
const value = instance.getValue();
|
|
||||||
dispatch('change', { value });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await sleep(50);
|
|
||||||
editor.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
function sleep(ms) {
|
|
||||||
return new Promise(fulfil => setTimeout(fulfil, ms));
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.codemirror-container {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border: none;
|
|
||||||
line-height: 1.5;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.codemirror-container :global(.CodeMirror) {
|
|
||||||
height: 100%;
|
|
||||||
/* background: var(--background); */
|
|
||||||
background: transparent;
|
|
||||||
font: 400 var(--code-fs)/1.7 var(--font-mono);
|
|
||||||
color: var(--base);
|
|
||||||
}
|
|
||||||
|
|
||||||
.codemirror-container.flex :global(.CodeMirror) {
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.codemirror-container.flex :global(.CodeMirror-lines) {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.codemirror-container :global(.CodeMirror-gutters) {
|
|
||||||
padding: 0 1.6rem 0 .8rem;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.codemirror-container :global(.error-loc) {
|
|
||||||
position: relative;
|
|
||||||
border-bottom: 2px solid #da106e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.codemirror-container :global(.error-line) {
|
|
||||||
background-color: rgba(200, 0, 0, .05);
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
border: none;
|
|
||||||
padding: 4px 4px 4px 60px;
|
|
||||||
resize: none;
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
font-size: 1.3rem;
|
|
||||||
line-height: 1.7;
|
|
||||||
user-select: none;
|
|
||||||
pointer-events: none;
|
|
||||||
color: #ccc;
|
|
||||||
tab-size: 2;
|
|
||||||
-moz-tab-size: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex pre {
|
|
||||||
padding: 0 0 0 4px;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class='codemirror-container' class:flex bind:offsetWidth={w} bind:offsetHeight={h}>
|
|
||||||
<textarea
|
|
||||||
tabindex='2'
|
|
||||||
bind:this={refs.editor}
|
|
||||||
readonly
|
|
||||||
value={code}
|
|
||||||
></textarea>
|
|
||||||
|
|
||||||
{#if !CodeMirror}
|
|
||||||
<pre style="position: absolute; left: 0; top: 0"
|
|
||||||
>{code}</pre>
|
|
||||||
|
|
||||||
<div style="position: absolute; width: 100%; bottom: 0">
|
|
||||||
<Message kind='info'>loading editor...</Message>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
@ -1,233 +0,0 @@
|
|||||||
<script>
|
|
||||||
import { getContext, createEventDispatcher } from 'svelte';
|
|
||||||
import Icon from '../../Icon.svelte';
|
|
||||||
import { enter } from '../../../utils/events.js';
|
|
||||||
|
|
||||||
export let handle_select;
|
|
||||||
|
|
||||||
const { components, selected, request_focus, rebundle } = getContext('REPL');
|
|
||||||
|
|
||||||
let editing = null;
|
|
||||||
|
|
||||||
function selectComponent(component) {
|
|
||||||
if ($selected !== component) {
|
|
||||||
editing = null;
|
|
||||||
handle_select(component);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function editTab(component) {
|
|
||||||
if ($selected === component) {
|
|
||||||
editing = $selected;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeEdit() {
|
|
||||||
const match = /(.+)\.(svelte|js)$/.exec($selected.name);
|
|
||||||
$selected.name = match ? match[1] : $selected.name;
|
|
||||||
if (match && match[2]) $selected.type = match[2];
|
|
||||||
editing = null;
|
|
||||||
|
|
||||||
// re-select, in case the type changed
|
|
||||||
handle_select($selected);
|
|
||||||
|
|
||||||
components = components; // TODO necessary?
|
|
||||||
|
|
||||||
// focus the editor, but wait a beat (so key events aren't misdirected)
|
|
||||||
setTimeout(request_focus);
|
|
||||||
|
|
||||||
rebundle();
|
|
||||||
}
|
|
||||||
|
|
||||||
function remove(component) {
|
|
||||||
let result = confirm(`Are you sure you want to delete ${component.name}.${component.type}?`);
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
const index = $components.indexOf(component);
|
|
||||||
|
|
||||||
if (~index) {
|
|
||||||
components.set($components.slice(0, index).concat($components.slice(index + 1)));
|
|
||||||
} else {
|
|
||||||
console.error(`Could not find component! That's... odd`);
|
|
||||||
}
|
|
||||||
|
|
||||||
handle_select($components[index] || $components[$components.length - 1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectInput(event) {
|
|
||||||
setTimeout(() => {
|
|
||||||
event.target.select();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let uid = 1;
|
|
||||||
|
|
||||||
function addNew() {
|
|
||||||
const component = {
|
|
||||||
name: uid++ ? `Component${uid}` : 'Component1',
|
|
||||||
type: 'svelte',
|
|
||||||
source: ''
|
|
||||||
};
|
|
||||||
|
|
||||||
editing = component;
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
// TODO we can do this without IDs
|
|
||||||
document.getElementById(component.name).scrollIntoView(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
components.update(components => components.concat(component));
|
|
||||||
handle_select(component);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.component-selector {
|
|
||||||
position: relative;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-tabs {
|
|
||||||
border: none;
|
|
||||||
margin: 0;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow-x: auto;
|
|
||||||
overflow-y: hidden;
|
|
||||||
height: 10em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-tabs .button, .file-tabs button {
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
font: 400 1.2rem/1.5 var(--font);
|
|
||||||
border-bottom: var(--border-w) solid transparent;
|
|
||||||
padding: 1.2rem 1.4rem 0.8rem 0.8rem;
|
|
||||||
margin: 0;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-tabs .button:first-child {
|
|
||||||
padding-left: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-tabs .button.active {
|
|
||||||
/* color: var(--second); */
|
|
||||||
color: #333;
|
|
||||||
border-bottom: var(--border-w) solid var(--prime);
|
|
||||||
}
|
|
||||||
|
|
||||||
.editable, .uneditable, .input-sizer, input {
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-sizer {
|
|
||||||
color: #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
left: 0.8rem;
|
|
||||||
top: 1.2rem;
|
|
||||||
font: 400 1.2rem/1.5 var(--font);
|
|
||||||
border: none;
|
|
||||||
color: var(--flash);
|
|
||||||
outline: none;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remove {
|
|
||||||
position: absolute;
|
|
||||||
display: none;
|
|
||||||
right: .1rem;
|
|
||||||
top: .4rem;
|
|
||||||
width: 1.6rem;
|
|
||||||
text-align: right;
|
|
||||||
padding: 1.2em 0 1.2em .5em;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remove:hover {
|
|
||||||
color: var(--flash);
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-tabs .button.active .editable {
|
|
||||||
cursor: text;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-tabs .button.active .remove {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-new {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
padding: 1.2rem 1rem 0.8rem 0 !important;
|
|
||||||
height: 4.2rem;
|
|
||||||
text-align: center;
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-new:hover {
|
|
||||||
color: var(--flash) !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="component-selector">
|
|
||||||
{#if $components.length}
|
|
||||||
<div class="file-tabs" on:dblclick="{addNew}">
|
|
||||||
{#each $components as component}
|
|
||||||
<div
|
|
||||||
id={component.name}
|
|
||||||
class="button"
|
|
||||||
role="button"
|
|
||||||
class:active="{component === $selected}"
|
|
||||||
on:click="{() => selectComponent(component)}"
|
|
||||||
on:dblclick="{e => e.stopPropagation()}"
|
|
||||||
>
|
|
||||||
{#if component.name == 'App'}
|
|
||||||
<div class="uneditable">
|
|
||||||
App.svelte
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
{#if component === editing}
|
|
||||||
<span class="input-sizer">{editing.name + (/\./.test(editing.name) ? '' : `.${editing.type}`)}</span>
|
|
||||||
|
|
||||||
<input
|
|
||||||
autofocus
|
|
||||||
spellcheck={false}
|
|
||||||
bind:value={editing.name}
|
|
||||||
on:focus={selectInput}
|
|
||||||
on:blur={closeEdit}
|
|
||||||
use:enter="{e => e.target.blur()}"
|
|
||||||
>
|
|
||||||
{:else}
|
|
||||||
<div
|
|
||||||
class="editable"
|
|
||||||
title="edit component name"
|
|
||||||
on:click="{() => editTab(component)}"
|
|
||||||
>
|
|
||||||
{component.name}.{component.type}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span class="remove" on:click="{() => remove(component)}">
|
|
||||||
<Icon name="close" size={12}/>
|
|
||||||
<!-- × -->
|
|
||||||
</span>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
|
|
||||||
<button class="add-new" on:click={addNew} title="add new component">
|
|
||||||
<Icon name="plus" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
@ -1,63 +0,0 @@
|
|||||||
<script>
|
|
||||||
import { getContext, onMount } from 'svelte';
|
|
||||||
import CodeMirror from '../CodeMirror.svelte';
|
|
||||||
import Message from '../Message.svelte';
|
|
||||||
|
|
||||||
const { bundle, selected, handle_change, navigate, register_module_editor } = getContext('REPL');
|
|
||||||
|
|
||||||
export let errorLoc;
|
|
||||||
|
|
||||||
let editor;
|
|
||||||
onMount(() => {
|
|
||||||
register_module_editor(editor);
|
|
||||||
});
|
|
||||||
|
|
||||||
export function focus() {
|
|
||||||
editor.focus();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.editor-wrapper {
|
|
||||||
z-index: 5;
|
|
||||||
background: var(--back-light);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor {
|
|
||||||
height: 0;
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 600px) {
|
|
||||||
:global(.columns) .editor-wrapper {
|
|
||||||
/* make it easier to interact with scrollbar */
|
|
||||||
padding-right: 8px;
|
|
||||||
height: auto;
|
|
||||||
/* height: 100%; */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="editor-wrapper">
|
|
||||||
<div class="editor">
|
|
||||||
<CodeMirror
|
|
||||||
bind:this={editor}
|
|
||||||
{errorLoc}
|
|
||||||
on:change={handle_change}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="info">
|
|
||||||
{#if $bundle}
|
|
||||||
{#if $bundle.error}
|
|
||||||
<Message kind="error" details={$bundle.error} filename="{$selected.name}.{$selected.type}"/>
|
|
||||||
{:else if $bundle.warnings.length > 0}
|
|
||||||
{#each $bundle.warnings as warning}
|
|
||||||
<Message kind="warning" details={warning} filename="{$selected.name}.{$selected.type}"/>
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,35 +0,0 @@
|
|||||||
<script>
|
|
||||||
export let checked;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.input-output-toggle {
|
|
||||||
display: grid;
|
|
||||||
position: absolute;
|
|
||||||
user-select: none;
|
|
||||||
grid-template-columns: 1fr 40px 1fr;
|
|
||||||
grid-gap: 0.5em;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
height: 4.2rem;
|
|
||||||
border-top: 1px solid var(--second);
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
color: #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.active {
|
|
||||||
color: #555;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<label class="input-output-toggle">
|
|
||||||
<span class:active={!checked} style="text-align: right">input</span>
|
|
||||||
<input type="checkbox" bind:checked>
|
|
||||||
<span class:active={checked}>output</span>
|
|
||||||
</label>
|
|
@ -1,80 +0,0 @@
|
|||||||
<script>
|
|
||||||
import { getContext } from 'svelte';
|
|
||||||
|
|
||||||
const { navigate } = getContext('REPL');
|
|
||||||
|
|
||||||
export let kind;
|
|
||||||
export let details = null;
|
|
||||||
export let filename = null;
|
|
||||||
|
|
||||||
function message(details) {
|
|
||||||
let str = details.message || '[missing message]';
|
|
||||||
|
|
||||||
let loc = [];
|
|
||||||
|
|
||||||
if (details.filename && details.filename !== filename) {
|
|
||||||
loc.push(details.filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (details.start) loc.push(details.start.line, details.start.column);
|
|
||||||
|
|
||||||
return str + (loc.length ? ` (${loc.join(':')})` : ``);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.message {
|
|
||||||
position: relative;
|
|
||||||
color: white;
|
|
||||||
padding: 1.2rem 1.6rem 1.2rem 4.4rem;
|
|
||||||
font: 400 1.2rem/1.7 var(--font);
|
|
||||||
margin: 0;
|
|
||||||
border-top: 1px solid white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navigable {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info {
|
|
||||||
background-color: var(--second);
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
|
||||||
background-color: #da106e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.warning {
|
|
||||||
background-color: #e47e0a;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="message {kind}">
|
|
||||||
{#if details}
|
|
||||||
<p
|
|
||||||
class:navigable={details.filename}
|
|
||||||
on:click="{() => navigate(details)}"
|
|
||||||
>{message(details)}</p>
|
|
||||||
{:else}
|
|
||||||
<slot></slot>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
@ -1,49 +0,0 @@
|
|||||||
const workers = new Map();
|
|
||||||
|
|
||||||
let uid = 1;
|
|
||||||
|
|
||||||
export default class Compiler {
|
|
||||||
constructor(version) {
|
|
||||||
if (!workers.has(version)) {
|
|
||||||
const worker = new Worker('/workers/compiler.js');
|
|
||||||
worker.postMessage({ type: 'init', version });
|
|
||||||
workers.set(version, worker);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.worker = workers.get(version);
|
|
||||||
|
|
||||||
this.handlers = new Map();
|
|
||||||
|
|
||||||
this.worker.addEventListener('message', event => {
|
|
||||||
const handler = this.handlers.get(event.data.id);
|
|
||||||
|
|
||||||
if (handler) { // if no handler, was meant for a different REPL
|
|
||||||
handler(event.data.result);
|
|
||||||
this.handlers.delete(event.data.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
compile(component, options) {
|
|
||||||
return new Promise(fulfil => {
|
|
||||||
const id = uid++;
|
|
||||||
|
|
||||||
this.handlers.set(id, fulfil);
|
|
||||||
|
|
||||||
this.worker.postMessage({
|
|
||||||
id,
|
|
||||||
type: 'compile',
|
|
||||||
source: component.source,
|
|
||||||
options: Object.assign({
|
|
||||||
name: component.name,
|
|
||||||
filename: `${component.name}.svelte`
|
|
||||||
}, options),
|
|
||||||
entry: component.name === 'App'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
this.worker.terminate();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,146 +0,0 @@
|
|||||||
<script>
|
|
||||||
import { getContext } from 'svelte';
|
|
||||||
|
|
||||||
const { compile_options } = getContext('REPL');
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.options {
|
|
||||||
padding: 0 1rem;
|
|
||||||
font-family: var(--font-ui);
|
|
||||||
font-size: 1.3rem;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option {
|
|
||||||
display: block;
|
|
||||||
padding: 0 0 0 1.25em;
|
|
||||||
white-space: nowrap;
|
|
||||||
color: var(--text);
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.key {
|
|
||||||
display: inline-block;
|
|
||||||
width: 9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.string {
|
|
||||||
color: hsl(41, 37%, 45%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.boolean {
|
|
||||||
color: hsl(45, 7%, 45%);
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
label[for] {
|
|
||||||
color: var(--string);
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=checkbox] {
|
|
||||||
top: -1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=radio] {
|
|
||||||
position: absolute;
|
|
||||||
top: auto;
|
|
||||||
overflow: hidden;
|
|
||||||
clip: rect(1px, 1px, 1px, 1px);
|
|
||||||
width: 1px;
|
|
||||||
height: 1px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=radio] + label {
|
|
||||||
padding: 0 0 0 1.6em;
|
|
||||||
margin: 0 0.6em 0 0;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=radio]:checked + label {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* input[type=radio]:focus + label {
|
|
||||||
color: #00f;
|
|
||||||
outline: 1px dotted #00f;
|
|
||||||
} */
|
|
||||||
|
|
||||||
input[type=radio] + label:before {
|
|
||||||
content: '';
|
|
||||||
background: #eee;
|
|
||||||
display: block;
|
|
||||||
box-sizing: border-box;
|
|
||||||
float: left;
|
|
||||||
width: 1.5rem;
|
|
||||||
height: 1.5rem;
|
|
||||||
margin-left: -2.1rem;
|
|
||||||
margin-top: 0.4rem;
|
|
||||||
vertical-align: top;
|
|
||||||
cursor: pointer;
|
|
||||||
text-align: center;
|
|
||||||
transition: box-shadow 0.1s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=radio] + label:before {
|
|
||||||
background-color: var(--second);
|
|
||||||
border-radius: 100%;
|
|
||||||
box-shadow: inset 0 0 0 0.5em rgba(255, 255, 255, .95);
|
|
||||||
border: 1px solid var(--second);
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=radio]:checked + label:before {
|
|
||||||
background-color: var(--prime);
|
|
||||||
box-shadow: inset 0 0 0 .15em rgba(255, 255, 255, .95);
|
|
||||||
border: 1px solid var(--second);
|
|
||||||
transition: box-shadow 0.2s ease-out;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="options">
|
|
||||||
result = svelte.compile(source, {
|
|
||||||
<div class="option">
|
|
||||||
<span class="key">generate:</span>
|
|
||||||
|
|
||||||
<input id="dom-input" type="radio" bind:group={$compile_options.generate} value="dom">
|
|
||||||
<label for="dom-input"><span class="string">"dom"</span></label>
|
|
||||||
|
|
||||||
<input id="ssr-input" type="radio" bind:group={$compile_options.generate} value="ssr">
|
|
||||||
<label for="ssr-input"><span class="string">"ssr"</span>,</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<label class="option">
|
|
||||||
<span class="key">dev:</span>
|
|
||||||
<input type="checkbox" bind:checked={$compile_options.dev}> <span class="boolean">{$compile_options.dev}</span>,
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label class="option">
|
|
||||||
<span class="key">css:</span>
|
|
||||||
<input type="checkbox" bind:checked={$compile_options.css}> <span class="boolean">{$compile_options.css}</span>,
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label class="option">
|
|
||||||
<span class="key">hydratable:</span>
|
|
||||||
<input type="checkbox" bind:checked={$compile_options.hydratable}> <span class="boolean">{$compile_options.hydratable}</span>,
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label class="option">
|
|
||||||
<span class="key">customElement:</span>
|
|
||||||
<input type="checkbox" bind:checked={$compile_options.customElement}> <span class="boolean">{$compile_options.customElement}</span>,
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label class="option">
|
|
||||||
<span class="key">immutable:</span>
|
|
||||||
<input type="checkbox" bind:checked={$compile_options.immutable}> <span class="boolean">{$compile_options.immutable}</span>,
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label class="option">
|
|
||||||
<span class="key">legacy:</span>
|
|
||||||
<input type="checkbox" bind:checked={$compile_options.legacy}> <span class="boolean">{$compile_options.legacy}</span>
|
|
||||||
</label>
|
|
||||||
});
|
|
||||||
</div>
|
|
@ -1,75 +0,0 @@
|
|||||||
let uid = 1;
|
|
||||||
|
|
||||||
export default class ReplProxy {
|
|
||||||
constructor(iframe, handlers) {
|
|
||||||
this.iframe = iframe;
|
|
||||||
this.handlers = handlers;
|
|
||||||
|
|
||||||
this.pending_cmds = new Map();
|
|
||||||
|
|
||||||
this.handle_event = e => this.handle_repl_message(e);
|
|
||||||
window.addEventListener('message', this.handle_event, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
window.removeEventListener('message', this.handle_event);
|
|
||||||
}
|
|
||||||
|
|
||||||
iframe_command(action, args) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const cmd_id = uid++;
|
|
||||||
|
|
||||||
this.pending_cmds.set(cmd_id, { resolve, reject });
|
|
||||||
|
|
||||||
this.iframe.contentWindow.postMessage({ action, cmd_id, args }, '*');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handle_command_message(cmd_data) {
|
|
||||||
let action = cmd_data.action;
|
|
||||||
let id = cmd_data.cmd_id;
|
|
||||||
let handler = this.pending_cmds.get(id);
|
|
||||||
|
|
||||||
if (handler) {
|
|
||||||
this.pending_cmds.delete(id);
|
|
||||||
if (action === 'cmd_error') {
|
|
||||||
let { message, stack } = cmd_data;
|
|
||||||
let e = new Error(message);
|
|
||||||
e.stack = stack;
|
|
||||||
handler.reject(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action === 'cmd_ok') {
|
|
||||||
handler.resolve(cmd_data.args)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error('command not found', id, cmd_data, [...this.pending_cmds.keys()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handle_repl_message(event) {
|
|
||||||
if (event.source !== this.iframe.contentWindow) return;
|
|
||||||
|
|
||||||
const { action, args } = event.data;
|
|
||||||
|
|
||||||
if (action === 'cmd_error' || action === 'cmd_ok') {
|
|
||||||
this.handle_command_message(event.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action === 'fetch_progress') {
|
|
||||||
this.handlers.on_fetch_progress(args.remaining)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eval(script) {
|
|
||||||
return this.iframe_command('eval', { script });
|
|
||||||
}
|
|
||||||
|
|
||||||
handle_links() {
|
|
||||||
return this.iframe_command('catch_clicks', {});
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch_imports(imports, import_map) {
|
|
||||||
return this.iframe_command('fetch_imports', { imports, import_map })
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,156 +0,0 @@
|
|||||||
<script>
|
|
||||||
import { onMount, createEventDispatcher, getContext } from 'svelte';
|
|
||||||
import getLocationFromStack from './getLocationFromStack.js';
|
|
||||||
import ReplProxy from './ReplProxy.js';
|
|
||||||
import Message from '../Message.svelte';
|
|
||||||
import { decode } from 'sourcemap-codec';
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
const { bundle, navigate } = getContext('REPL');
|
|
||||||
|
|
||||||
export let error; // TODO should this be exposed as a prop?
|
|
||||||
|
|
||||||
export function setProp(prop, value) {
|
|
||||||
if (!proxy) return;
|
|
||||||
proxy.setProp(prop, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
export let relaxed = false;
|
|
||||||
|
|
||||||
let iframe;
|
|
||||||
let pending_imports = 0;
|
|
||||||
let pending = false;
|
|
||||||
|
|
||||||
let proxy = null;
|
|
||||||
|
|
||||||
let ready = false;
|
|
||||||
let inited = false;
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
proxy = new ReplProxy(iframe, {
|
|
||||||
on_fetch_progress: progress => {
|
|
||||||
pending_imports = progress;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
iframe.addEventListener('load', () => {
|
|
||||||
proxy.handle_links();
|
|
||||||
ready = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
proxy.destroy();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let current_token;
|
|
||||||
|
|
||||||
async function apply_bundle($bundle) {
|
|
||||||
if (!$bundle || $bundle.error) return;
|
|
||||||
|
|
||||||
const token = current_token = {};
|
|
||||||
|
|
||||||
try {
|
|
||||||
await proxy.fetch_imports($bundle.imports, $bundle.import_map);
|
|
||||||
if (token !== current_token) return;
|
|
||||||
|
|
||||||
await proxy.eval(`
|
|
||||||
// needed for context API tutorial
|
|
||||||
window.MAPBOX_ACCESS_TOKEN = process.env.MAPBOX_ACCESS_TOKEN;
|
|
||||||
|
|
||||||
const styles = document.querySelectorAll('style[id^=svelte-]');
|
|
||||||
|
|
||||||
${$bundle.dom.code}
|
|
||||||
|
|
||||||
let i = styles.length;
|
|
||||||
while (i--) styles[i].parentNode.removeChild(styles[i]);
|
|
||||||
|
|
||||||
if (window.component) {
|
|
||||||
try {
|
|
||||||
window.component.$destroy();
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.body.innerHTML = '';
|
|
||||||
window.location.hash = '';
|
|
||||||
window._svelteTransitionManager = null;
|
|
||||||
|
|
||||||
window.component = new SvelteComponent.default({
|
|
||||||
target: document.body
|
|
||||||
});
|
|
||||||
`);
|
|
||||||
|
|
||||||
error = null;
|
|
||||||
} catch (e) {
|
|
||||||
const loc = getLocationFromStack(e.stack, $bundle.dom.map);
|
|
||||||
if (loc) {
|
|
||||||
e.filename = loc.source;
|
|
||||||
e.loc = { line: loc.line, column: loc.column };
|
|
||||||
}
|
|
||||||
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
inited = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$: if (ready) apply_bundle($bundle);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.iframe-container {
|
|
||||||
position: absolute;
|
|
||||||
background-color: white;
|
|
||||||
border: none;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
iframe {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
/* height: calc(100vh - var(--nav-h)); */
|
|
||||||
border: none;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.greyed-out {
|
|
||||||
filter: grayscale(50%) blur(1px);
|
|
||||||
opacity: .25;
|
|
||||||
}
|
|
||||||
|
|
||||||
.overlay {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="iframe-container">
|
|
||||||
<iframe title="Result" class:inited bind:this={iframe} sandbox="allow-popups-to-escape-sandbox allow-scripts allow-popups allow-forms allow-pointer-lock allow-top-navigation allow-modals {relaxed ? 'allow-scripts allow-same-origin' : ''}" class="{error || pending || pending_imports ? 'greyed-out' : ''}" srcdoc='
|
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<link rel="stylesheet" href="/repl-viewer.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<script src="/curl.js"></script>
|
|
||||||
<script>curl.config({ dontAddFileExt: /./ });</script>
|
|
||||||
<script src="/repl-runner.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
'></iframe>
|
|
||||||
|
|
||||||
<div class="overlay">
|
|
||||||
{#if error}
|
|
||||||
<Message kind="error" details={error}/>
|
|
||||||
{:else if !$bundle}
|
|
||||||
<Message kind="info">loading Svelte compiler...</Message>
|
|
||||||
{:else if pending_imports}
|
|
||||||
<Message kind="info">loading {pending_imports} {pending_imports === 1 ? 'dependency' : 'dependencies'} from
|
|
||||||
https://bundle.run</Message>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,31 +0,0 @@
|
|||||||
import { decode } from 'sourcemap-codec';
|
|
||||||
|
|
||||||
export default function getLocationFromStack(stack, map) {
|
|
||||||
if (!stack) return;
|
|
||||||
const last = stack.split('\n')[1];
|
|
||||||
const match = /<anonymous>:(\d+):(\d+)\)$/.exec(last);
|
|
||||||
|
|
||||||
if (!match) return null;
|
|
||||||
|
|
||||||
const line = +match[1];
|
|
||||||
const column = +match[2];
|
|
||||||
|
|
||||||
return trace({ line, column }, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
function trace(loc, map) {
|
|
||||||
const mappings = decode(map.mappings);
|
|
||||||
const segments = mappings[loc.line - 1];
|
|
||||||
|
|
||||||
for (let i = 0; i < segments.length; i += 1) {
|
|
||||||
const segment = segments[i];
|
|
||||||
if (segment[0] === loc.column) {
|
|
||||||
const [, sourceIndex, line, column] = segment;
|
|
||||||
const source = map.sources[sourceIndex].slice(2);
|
|
||||||
|
|
||||||
return { source, line: line + 1, column };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
@ -1,166 +0,0 @@
|
|||||||
<script>
|
|
||||||
import { getContext, onMount } from 'svelte';
|
|
||||||
import SplitPane from '../SplitPane.svelte';
|
|
||||||
import Viewer from './Viewer.svelte';
|
|
||||||
import CompilerOptions from './CompilerOptions.svelte';
|
|
||||||
import Compiler from './Compiler.js';
|
|
||||||
import CodeMirror from '../CodeMirror.svelte';
|
|
||||||
|
|
||||||
const { register_output } = getContext('REPL');
|
|
||||||
|
|
||||||
export let version;
|
|
||||||
export let sourceErrorLoc = null;
|
|
||||||
export let runtimeError = null;
|
|
||||||
export let embedded = false;
|
|
||||||
export let relaxed = false;
|
|
||||||
|
|
||||||
let foo; // TODO workaround for https://github.com/sveltejs/svelte/issues/2122
|
|
||||||
|
|
||||||
register_output({
|
|
||||||
set: async (selected, options) => {
|
|
||||||
if (selected.type === 'js') {
|
|
||||||
js_editor.set(`/* Select a component to see its compiled code */`);
|
|
||||||
css_editor.set(`/* Select a component to see its compiled code */`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const compiled = await compiler.compile(selected, options);
|
|
||||||
|
|
||||||
js_editor.set(compiled.js, 'js');
|
|
||||||
css_editor.set(compiled.css, 'css');
|
|
||||||
},
|
|
||||||
|
|
||||||
update: async (selected, options) => {
|
|
||||||
if (selected.type === 'js') return;
|
|
||||||
|
|
||||||
const compiled = await compiler.compile(selected, options);
|
|
||||||
|
|
||||||
js_editor.update(compiled.js);
|
|
||||||
css_editor.update(compiled.css);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const compiler = process.browser && new Compiler(version);
|
|
||||||
|
|
||||||
// refs
|
|
||||||
let viewer;
|
|
||||||
let js_editor;
|
|
||||||
let css_editor;
|
|
||||||
const setters = {};
|
|
||||||
|
|
||||||
let view = 'result';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.view-toggle {
|
|
||||||
height: var(--pane-controls-h);
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
/* width: 50%;
|
|
||||||
height: 100%; */
|
|
||||||
text-align: left;
|
|
||||||
position: relative;
|
|
||||||
font: 400 1.2rem/1.5 var(--font);
|
|
||||||
border-bottom: var(--border-w) solid transparent;
|
|
||||||
padding: 1.2rem 1.2rem 0.8rem 1.2rem;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.active {
|
|
||||||
border-bottom: var(--border-w) solid var(--prime);
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
div[slot] {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font: 700 1.2rem/1.5 var(--font);
|
|
||||||
padding: 1.2rem 0 0.8rem 1rem;
|
|
||||||
color: var(--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-content {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: calc(100% - 4.2rem);
|
|
||||||
opacity: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-content.visible {
|
|
||||||
/* can't use visibility due to a weird painting bug in Chrome */
|
|
||||||
opacity: 1;
|
|
||||||
pointer-events: all;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="view-toggle">
|
|
||||||
<button
|
|
||||||
class:active="{view === 'result'}"
|
|
||||||
on:click="{() => view = 'result'}"
|
|
||||||
>Result</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class:active="{view === 'js'}"
|
|
||||||
on:click="{() => view = 'js'}"
|
|
||||||
>JS output</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class:active="{view === 'css'}"
|
|
||||||
on:click="{() => view = 'css'}"
|
|
||||||
>CSS output</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- component viewer -->
|
|
||||||
<div class="tab-content" class:visible="{view === 'result'}">
|
|
||||||
<Viewer
|
|
||||||
bind:this={viewer}
|
|
||||||
bind:error={runtimeError}
|
|
||||||
{relaxed}
|
|
||||||
on:binding="{e => setPropFromViewer(e.detail.prop, e.detail.value)}"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- js output -->
|
|
||||||
<div class="tab-content" class:visible="{view === 'js'}">
|
|
||||||
{#if embedded}
|
|
||||||
<CodeMirror
|
|
||||||
bind:this={js_editor}
|
|
||||||
mode="js"
|
|
||||||
errorLoc={sourceErrorLoc}
|
|
||||||
readonly
|
|
||||||
/>
|
|
||||||
{:else}
|
|
||||||
<SplitPane type="vertical" pos={67}>
|
|
||||||
<div slot="a">
|
|
||||||
<CodeMirror
|
|
||||||
bind:this={js_editor}
|
|
||||||
mode="js"
|
|
||||||
errorLoc={sourceErrorLoc}
|
|
||||||
readonly
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<section slot="b">
|
|
||||||
<h3>Compiler options</h3>
|
|
||||||
|
|
||||||
<CompilerOptions bind:foo={foo}/>
|
|
||||||
</section>
|
|
||||||
</SplitPane>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- css output -->
|
|
||||||
<div class="tab-content" class:visible="{view === 'css'}">
|
|
||||||
<CodeMirror
|
|
||||||
bind:this={css_editor}
|
|
||||||
mode="css"
|
|
||||||
errorLoc={sourceErrorLoc}
|
|
||||||
readonly
|
|
||||||
/>
|
|
||||||
</div>
|
|
@ -1,164 +0,0 @@
|
|||||||
<script>
|
|
||||||
import * as yootils from 'yootils';
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
|
|
||||||
export let type;
|
|
||||||
export let pos = 50;
|
|
||||||
export let fixed = false;
|
|
||||||
export let fixed_pos = pos;
|
|
||||||
export let min = 50;
|
|
||||||
// export let min1 = min;
|
|
||||||
// export let min2 = min;
|
|
||||||
|
|
||||||
const refs = {};
|
|
||||||
const side = type === 'horizontal' ? 'left' : 'top';
|
|
||||||
const dimension = type === 'horizontal' ? 'width' : 'height';
|
|
||||||
|
|
||||||
let dragging = false;
|
|
||||||
|
|
||||||
function setPos(event) {
|
|
||||||
const { top, bottom, left, right } = refs.container.getBoundingClientRect();
|
|
||||||
|
|
||||||
const extents = type === 'vertical' ? [top, bottom] : [left, right];
|
|
||||||
|
|
||||||
const px = yootils.clamp(
|
|
||||||
type === 'vertical' ? event.clientY : event.clientX,
|
|
||||||
extents[0] + min,
|
|
||||||
extents[1] - min
|
|
||||||
);
|
|
||||||
|
|
||||||
pos = 100 * (px - extents[0]) / (extents[1] - extents[0]);
|
|
||||||
|
|
||||||
dispatch('change');
|
|
||||||
}
|
|
||||||
|
|
||||||
function drag(node, callback) {
|
|
||||||
const mousedown = event => {
|
|
||||||
if (event.which !== 1) return;
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
dragging = true;
|
|
||||||
|
|
||||||
const onmouseup = () => {
|
|
||||||
dragging = false;
|
|
||||||
|
|
||||||
window.removeEventListener('mousemove', callback, false);
|
|
||||||
window.removeEventListener('mouseup', onmouseup, false);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener('mousemove', callback, false);
|
|
||||||
window.addEventListener('mouseup', onmouseup, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
node.addEventListener('mousedown', mousedown, false);
|
|
||||||
|
|
||||||
return {
|
|
||||||
destroy() {
|
|
||||||
node.removeEventListener('mousedown', onmousedown, false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.container {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pane {
|
|
||||||
position: relative;
|
|
||||||
float: left;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mousecatcher {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: rgba(255,255,255,.01);
|
|
||||||
}
|
|
||||||
|
|
||||||
.divider {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 10;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.divider::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
/* background-color: #eee; */
|
|
||||||
background-color: var(--second);
|
|
||||||
}
|
|
||||||
|
|
||||||
.horizontal {
|
|
||||||
padding: 0 8px;
|
|
||||||
width: 0;
|
|
||||||
height: 100%;
|
|
||||||
cursor: ew-resize;
|
|
||||||
}
|
|
||||||
|
|
||||||
.horizontal::after {
|
|
||||||
left: 8px;
|
|
||||||
top: 0;
|
|
||||||
width: 1px;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vertical {
|
|
||||||
padding: 8px 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 0;
|
|
||||||
cursor: ns-resize;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vertical::after {
|
|
||||||
top: 8px;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left, .right, .divider {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left, .right {
|
|
||||||
height: 100%;
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.top, .bottom {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.top { top: 0; }
|
|
||||||
.bottom { bottom: 0; }
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="container" bind:this={refs.container}>
|
|
||||||
<div class="pane" style="{dimension}: {fixed ? fixed_pos : pos}%;">
|
|
||||||
<slot name="a"></slot>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pane" style="{dimension}: {100 - (fixed ? fixed_pos : pos)}%;">
|
|
||||||
<slot name="b"></slot>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if !fixed}
|
|
||||||
<div class="{type} divider" style="{side}: calc({pos}% - 8px)" use:drag={setPos}></div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if dragging}
|
|
||||||
<div class="mousecatcher"></div>
|
|
||||||
{/if}
|
|
@ -1,10 +0,0 @@
|
|||||||
const CodeMirror = require('codemirror');
|
|
||||||
require('./codemirror.css');
|
|
||||||
require('codemirror/mode/javascript/javascript.js');
|
|
||||||
require('codemirror/mode/shell/shell.js');
|
|
||||||
require('codemirror/mode/handlebars/handlebars.js');
|
|
||||||
require('codemirror/mode/htmlmixed/htmlmixed.js');
|
|
||||||
require('codemirror/mode/xml/xml.js');
|
|
||||||
require('codemirror/mode/css/css.js');
|
|
||||||
|
|
||||||
module.exports = CodeMirror;
|
|
@ -1,350 +0,0 @@
|
|||||||
/* BASICS */
|
|
||||||
|
|
||||||
.CodeMirror {
|
|
||||||
/* copied colors over from prism */
|
|
||||||
--background: var(--back-light);
|
|
||||||
--base: hsl(45, 7%, 45%);
|
|
||||||
--comment: hsl(210, 25%, 60%);
|
|
||||||
--keyword: hsl(204, 58%, 45%);
|
|
||||||
--function: hsl(19, 67%, 45%);
|
|
||||||
--string: hsl(41, 37%, 45%);
|
|
||||||
--number: hsl(102, 27%, 50%);
|
|
||||||
--tags: var(--function);
|
|
||||||
--important: var(--string);
|
|
||||||
|
|
||||||
/* Set height, width, borders, and global font properties here */
|
|
||||||
/* see prism.css */
|
|
||||||
height: 300px;
|
|
||||||
direction: ltr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* PADDING */
|
|
||||||
|
|
||||||
.CodeMirror-lines {
|
|
||||||
padding: 4px 0; /* Vertical padding around content */
|
|
||||||
}
|
|
||||||
.CodeMirror pre {
|
|
||||||
padding: 0 4px; /* Horizontal padding of content */
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
|
||||||
background-color: white; /* The little square between H and V scrollbars */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* GUTTER */
|
|
||||||
|
|
||||||
.CodeMirror-gutters {
|
|
||||||
border-right: 1px solid #ddd;
|
|
||||||
background-color: var(--back-light);
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
.CodeMirror-linenumber {
|
|
||||||
padding: 0 3px 0 5px;
|
|
||||||
min-width: 20px;
|
|
||||||
text-align: right;
|
|
||||||
color: var(--comment);
|
|
||||||
white-space: nowrap;
|
|
||||||
opacity: .6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-guttermarker { color: black; }
|
|
||||||
.CodeMirror-guttermarker-subtle { color: #999; }
|
|
||||||
|
|
||||||
/* CURSOR */
|
|
||||||
|
|
||||||
.CodeMirror-cursor {
|
|
||||||
border-left: 1px solid black;
|
|
||||||
border-right: none;
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
/* Shown when moving in bi-directional text */
|
|
||||||
.CodeMirror div.CodeMirror-secondarycursor {
|
|
||||||
border-left: 1px solid silver;
|
|
||||||
}
|
|
||||||
.cm-fat-cursor .CodeMirror-cursor {
|
|
||||||
width: auto;
|
|
||||||
border: 0 !important;
|
|
||||||
background: #7e7;
|
|
||||||
}
|
|
||||||
.cm-fat-cursor div.CodeMirror-cursors {
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
.cm-fat-cursor-mark {
|
|
||||||
background-color: rgba(20, 255, 20, .5);
|
|
||||||
-webkit-animation: blink 1.06s steps(1) infinite;
|
|
||||||
-moz-animation: blink 1.06s steps(1) infinite;
|
|
||||||
animation: blink 1.06s steps(1) infinite;
|
|
||||||
}
|
|
||||||
.cm-animate-fat-cursor {
|
|
||||||
width: auto;
|
|
||||||
border: 0;
|
|
||||||
-webkit-animation: blink 1.06s steps(1) infinite;
|
|
||||||
-moz-animation: blink 1.06s steps(1) infinite;
|
|
||||||
animation: blink 1.06s steps(1) infinite;
|
|
||||||
background-color: #7e7;
|
|
||||||
}
|
|
||||||
@-moz-keyframes blink {
|
|
||||||
0% {}
|
|
||||||
50% { background-color: transparent; }
|
|
||||||
100% {}
|
|
||||||
}
|
|
||||||
@-webkit-keyframes blink {
|
|
||||||
0% {}
|
|
||||||
50% { background-color: transparent; }
|
|
||||||
100% {}
|
|
||||||
}
|
|
||||||
@keyframes blink {
|
|
||||||
0% {}
|
|
||||||
50% { background-color: transparent; }
|
|
||||||
100% {}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cm-tab { display: inline-block; text-decoration: inherit; }
|
|
||||||
|
|
||||||
.CodeMirror-rulers {
|
|
||||||
position: absolute;
|
|
||||||
left: 0; right: 0; top: -50px; bottom: -20px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.CodeMirror-ruler {
|
|
||||||
border-left: 1px solid #ccc;
|
|
||||||
top: 0; bottom: 0;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* DEFAULT THEME */
|
|
||||||
.cm-s-default .cm-header {color: blue}
|
|
||||||
.cm-s-default .cm-quote {color: #090}
|
|
||||||
.cm-negative {color: #d44}
|
|
||||||
.cm-positive {color: #292}
|
|
||||||
.cm-header, .cm-strong {font-weight: bold}
|
|
||||||
.cm-em {font-style: italic}
|
|
||||||
.cm-link {text-decoration: underline}
|
|
||||||
.cm-strikethrough {text-decoration: line-through}
|
|
||||||
|
|
||||||
.cm-s-default .cm-atom,
|
|
||||||
.cm-s-default .cm-def,
|
|
||||||
.cm-s-default .cm-property,
|
|
||||||
.cm-s-default .cm-variable-2,
|
|
||||||
.cm-s-default .cm-variable-3,
|
|
||||||
.cm-s-default .cm-punctuation {color: var(--base)}
|
|
||||||
.cm-s-default .cm-hr,
|
|
||||||
.cm-s-default .cm-comment {color: var(--comment)}
|
|
||||||
.cm-s-default .cm-attribute,
|
|
||||||
.cm-s-default .cm-keyword {color: var(--keyword)}
|
|
||||||
.cm-s-default .cm-variable,
|
|
||||||
.cm-s-default .cm-bracket,
|
|
||||||
.cm-s-default .cm-tag {color: var(--tags)}
|
|
||||||
.cm-s-default .cm-number {color: var(--number)}
|
|
||||||
.cm-s-default .cm-string {color: var(--string)}
|
|
||||||
|
|
||||||
.cm-s-default .cm-string-2 {color: #f50}
|
|
||||||
.cm-s-default .cm-type {color: #085}
|
|
||||||
.cm-s-default .cm-meta {color: #555}
|
|
||||||
.cm-s-default .cm-qualifier {color: #555}
|
|
||||||
.cm-s-default .cm-builtin {color: #30a}
|
|
||||||
.cm-s-default .cm-link {color: var(--flash)}
|
|
||||||
.cm-s-default .cm-error {color: #ff008c}
|
|
||||||
.cm-invalidchar {color: #ff008c}
|
|
||||||
|
|
||||||
.CodeMirror-composing { border-bottom: 2px solid; }
|
|
||||||
|
|
||||||
/* Default styles for common addons */
|
|
||||||
|
|
||||||
div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
|
|
||||||
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
|
|
||||||
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
|
|
||||||
.CodeMirror-activeline-background {background: #e8f2ff;}
|
|
||||||
|
|
||||||
/* STOP */
|
|
||||||
|
|
||||||
/* The rest of this file contains styles related to the mechanics of
|
|
||||||
the editor. You probably shouldn't touch them. */
|
|
||||||
|
|
||||||
.CodeMirror {
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-scroll {
|
|
||||||
overflow: scroll !important; /* Things will break if this is overridden */
|
|
||||||
/* 30px is the magic margin used to hide the element's real scrollbars */
|
|
||||||
/* See overflow: hidden in .CodeMirror */
|
|
||||||
margin-bottom: -30px; margin-right: -30px;
|
|
||||||
padding-bottom: 30px;
|
|
||||||
height: 100%;
|
|
||||||
outline: none; /* Prevent dragging from highlighting the element */
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.CodeMirror-sizer {
|
|
||||||
position: relative;
|
|
||||||
border-right: 30px solid transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The fake, visible scrollbars. Used to force redraw during scrolling
|
|
||||||
before actual scrolling happens, thus preventing shaking and
|
|
||||||
flickering artifacts. */
|
|
||||||
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 6;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.CodeMirror-vscrollbar {
|
|
||||||
right: 0; top: 0;
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
.CodeMirror-hscrollbar {
|
|
||||||
bottom: 0; left: 0;
|
|
||||||
overflow-y: hidden;
|
|
||||||
overflow-x: scroll;
|
|
||||||
}
|
|
||||||
.CodeMirror-scrollbar-filler {
|
|
||||||
right: 0; bottom: 0;
|
|
||||||
}
|
|
||||||
.CodeMirror-gutter-filler {
|
|
||||||
left: 0; bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-gutters {
|
|
||||||
position: absolute; left: 0; top: 0;
|
|
||||||
min-height: 100%;
|
|
||||||
z-index: 3;
|
|
||||||
}
|
|
||||||
.CodeMirror-gutter {
|
|
||||||
white-space: normal;
|
|
||||||
height: 100%;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: top;
|
|
||||||
margin-bottom: -30px;
|
|
||||||
}
|
|
||||||
.CodeMirror-gutter-wrapper {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 4;
|
|
||||||
background: none !important;
|
|
||||||
border: none !important;
|
|
||||||
}
|
|
||||||
.CodeMirror-gutter-background {
|
|
||||||
position: absolute;
|
|
||||||
top: 0; bottom: 0;
|
|
||||||
z-index: 4;
|
|
||||||
}
|
|
||||||
.CodeMirror-gutter-elt {
|
|
||||||
position: absolute;
|
|
||||||
cursor: default;
|
|
||||||
z-index: 4;
|
|
||||||
}
|
|
||||||
.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
|
|
||||||
.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
|
|
||||||
|
|
||||||
.CodeMirror-lines {
|
|
||||||
cursor: text;
|
|
||||||
min-height: 1px; /* prevents collapsing before first draw */
|
|
||||||
}
|
|
||||||
.CodeMirror pre {
|
|
||||||
/* Reset some styles that the rest of the page might have set */
|
|
||||||
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
|
|
||||||
border-width: 0;
|
|
||||||
background: transparent;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: inherit;
|
|
||||||
margin: 0;
|
|
||||||
white-space: pre;
|
|
||||||
word-wrap: normal;
|
|
||||||
line-height: inherit;
|
|
||||||
color: inherit;
|
|
||||||
z-index: 2;
|
|
||||||
position: relative;
|
|
||||||
overflow: visible;
|
|
||||||
-webkit-tap-highlight-color: transparent;
|
|
||||||
-webkit-font-variant-ligatures: contextual;
|
|
||||||
font-variant-ligatures: contextual;
|
|
||||||
}
|
|
||||||
.CodeMirror-wrap pre {
|
|
||||||
word-wrap: break-word;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-linebackground {
|
|
||||||
position: absolute;
|
|
||||||
left: 0; right: 0; top: 0; bottom: 0;
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-linewidget {
|
|
||||||
position: relative;
|
|
||||||
z-index: 2;
|
|
||||||
padding: .1px; /* Force widget margins to stay inside of the container */
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-rtl pre { direction: rtl; }
|
|
||||||
|
|
||||||
.CodeMirror-code {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Force content-box sizing for the elements where we expect it */
|
|
||||||
.CodeMirror-scroll,
|
|
||||||
.CodeMirror-sizer,
|
|
||||||
.CodeMirror-gutter,
|
|
||||||
.CodeMirror-gutters,
|
|
||||||
.CodeMirror-linenumber {
|
|
||||||
-moz-box-sizing: content-box;
|
|
||||||
box-sizing: content-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-measure {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-cursor {
|
|
||||||
position: absolute;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
.CodeMirror-measure pre { position: static; }
|
|
||||||
|
|
||||||
div.CodeMirror-cursors {
|
|
||||||
visibility: hidden;
|
|
||||||
position: relative;
|
|
||||||
z-index: 3;
|
|
||||||
}
|
|
||||||
div.CodeMirror-dragcursors {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-focused div.CodeMirror-cursors {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-selected { background: #d9d9d9; }
|
|
||||||
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
|
|
||||||
.CodeMirror-crosshair { cursor: crosshair; }
|
|
||||||
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
|
|
||||||
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
|
|
||||||
|
|
||||||
.cm-searching {
|
|
||||||
background-color: #ffa;
|
|
||||||
background-color: rgba(255, 255, 0, .4);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Used to force a border model for a node */
|
|
||||||
.cm-force-border { padding-right: .1px; }
|
|
||||||
|
|
||||||
@media print {
|
|
||||||
/* Hide the cursor when printing */
|
|
||||||
.CodeMirror div.CodeMirror-cursors {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* See issue #2901 */
|
|
||||||
.cm-tab-wrap-hack:after { content: ''; }
|
|
||||||
|
|
||||||
/* Help users use markselection to safely style text background */
|
|
||||||
span.CodeMirror-selectedtext { background: none; }
|
|
@ -1,232 +0,0 @@
|
|||||||
<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 let relaxed = false;
|
|
||||||
|
|
||||||
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);
|
|
||||||
handle_select(component);
|
|
||||||
|
|
||||||
// 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} {relaxed}/>
|
|
||||||
</section>
|
|
||||||
</SplitPane>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<InputOutputToggle bind:checked={show_output}/>
|
|
Loading…
Reference in new issue