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