mirror of
https://github.com/leaningtech/javafiddle.git
synced 2025-09-06 22:15:28 +00:00
dropped iframe
This commit is contained in:

committed by
Alessandro Pignotti

parent
a07d52aeed
commit
45e932dc83
@@ -1,40 +1,30 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { browser } from '$app/environment';
|
||||
import { tryPlausible } from './plausible';
|
||||
import { files, loading, type File } from './repl/state';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { autoRun, compileLog, files, isRunning, isSaved, runCode, type File } from './repl/state';
|
||||
import { debounceFunction } from './utilities';
|
||||
|
||||
const dispatch = createEventDispatcher<{ ready: undefined }>();
|
||||
|
||||
export let display: HTMLElement;
|
||||
export let consoleEl: HTMLPreElement;
|
||||
|
||||
async function onLoad() {
|
||||
let cjConsole: HTMLElement;
|
||||
let cjOutput: HTMLElement;
|
||||
let cjOutputObserver: MutationObserver;
|
||||
|
||||
async function startCheerpj() {
|
||||
await cheerpjInit({
|
||||
status: 'none',
|
||||
javaProperties: ['java.library.path=/app/cheerpj-natives/natives']
|
||||
});
|
||||
const display = document.getElementById("output");
|
||||
cheerpjCreateDisplay(-1, -1, display);
|
||||
dispatch('ready');
|
||||
}
|
||||
|
||||
if (browser) { // so it doesn't run server-side
|
||||
onLoad();
|
||||
}
|
||||
|
||||
export async function compileAndRun() {
|
||||
if (!browser) return;
|
||||
async function runCheerpj() {
|
||||
if ($isRunning) return;
|
||||
|
||||
console.info('compileAndRun');
|
||||
|
||||
// custom event tracking for analytics
|
||||
tryPlausible('Compile');
|
||||
|
||||
consoleEl.innerHTML = '';
|
||||
$isRunning = true;
|
||||
cjConsole.innerHTML = '';
|
||||
cjOutput.innerHTML = '';
|
||||
|
||||
const classPath = '/app/tools.jar:/app/lwjgl-2.9.0.jar:/app/lwjgl_util-2.9.0.jar:/files/';
|
||||
|
||||
const sourceFiles = $files.map((file) => '/str/' + file.path);
|
||||
const code = await cheerpjRunMain(
|
||||
'com.sun.tools.javac.Main',
|
||||
@@ -44,17 +34,12 @@
|
||||
'/files/',
|
||||
'-Xlint'
|
||||
);
|
||||
const compileLog = consoleEl.innerText;
|
||||
if (code != 0) {
|
||||
$loading = false;
|
||||
window.top?.postMessage({ action: 'compile_error', compileLog }, window.location.origin);
|
||||
throw new Error('Compilation failed');
|
||||
}
|
||||
if (code === 0) await cheerpjRunMain(deriveMainClass($files[0]), classPath);
|
||||
|
||||
consoleEl.innerHTML = '';
|
||||
cheerpjRunMain(deriveMainClass($files[0]), classPath);
|
||||
$loading = false;
|
||||
window.top?.postMessage({ action: 'running', compileLog }, window.location.origin);
|
||||
// in case nothing is written on cjConsole and cjOutput
|
||||
// manually unflag $isRunning
|
||||
if ($isRunning) $isRunning = false;
|
||||
$compileLog = cjConsole.innerText;
|
||||
}
|
||||
|
||||
function deriveMainClass(file: File) {
|
||||
@@ -68,18 +53,65 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Persist files to CheerpJ filesystem
|
||||
files.subscribe(($files) => {
|
||||
if ('cheerpjAddStringFile' in globalThis) {
|
||||
const debounceRunCheerpj = debounceFunction(runCheerpj, 500);
|
||||
|
||||
let unsubSaveFiles: () => void;
|
||||
let unsubRunCode: () => void;
|
||||
|
||||
onMount(async () => {
|
||||
await startCheerpj();
|
||||
|
||||
cjConsole = document.getElementById("console");
|
||||
cjOutput = document.getElementById("cheerpjDisplay");
|
||||
// remove useless loading screen
|
||||
cjOutput.classList.remove("cheerpjLoading");
|
||||
|
||||
unsubSaveFiles = files.subscribe(() => {
|
||||
if ($isRunning) {
|
||||
$isSaved = false;
|
||||
} else {
|
||||
try {
|
||||
const encoder = new TextEncoder();
|
||||
for (const file of $files) {
|
||||
cheerpjAddStringFile('/str/' + file.path, encoder.encode(file.content));
|
||||
}
|
||||
console.info('wrote files');
|
||||
$isSaved = true;
|
||||
if ($autoRun) $runCode = true;
|
||||
} catch (error) {
|
||||
console.error('Error writing files to CheerpJ', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
unsubRunCode = runCode.subscribe(() => {
|
||||
if ($runCode) {
|
||||
$runCode = false;
|
||||
($autoRun) ? debounceRunCheerpj() : runCheerpj();
|
||||
}
|
||||
});
|
||||
|
||||
// code execution (flagged by isRunning) is considered over
|
||||
// when cjConsole or cjOutput are updated
|
||||
cjOutputObserver = new MutationObserver(() => {
|
||||
if ($isRunning && (cjConsole.innerHTML || cjOutput.innerHTML)) {
|
||||
$isRunning = false;
|
||||
if (!$isSaved) files.update((files) => files);
|
||||
}
|
||||
});
|
||||
cjOutputObserver.observe((cjConsole), {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
cjOutputObserver.observe((cjOutput), {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
|
||||
await runCheerpj();
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
if (unsubSaveFiles) unsubSaveFiles();
|
||||
if (unsubRunCode) unsubRunCode();
|
||||
if (cjOutputObserver) cjOutputObserver.disconnect();
|
||||
});
|
||||
</script>
|
||||
|
@@ -2,86 +2,24 @@
|
||||
import Menu from './repl/Menu.svelte';
|
||||
import Sidebar from './repl/Sidebar.svelte';
|
||||
import Editor from './repl/Editor.svelte';
|
||||
import { files, autoRun, loading } from './repl/state';
|
||||
import { isSaved, runCode } from './repl/state';
|
||||
import FileTabs from './repl/FileTabs.svelte';
|
||||
import Loading from './Loading.svelte';
|
||||
import { SplitPane } from '@rich_harris/svelte-split-pane';
|
||||
import { theme } from './settings/store';
|
||||
import { onMount } from 'svelte';
|
||||
import { tryPlausible } from './plausible';
|
||||
import { tryPlausible } from './utilities';
|
||||
import Output from './repl/Output.svelte';
|
||||
|
||||
export let outputUrl: string;
|
||||
export let enableSidebar: boolean = true;
|
||||
export let enableMenu: boolean = true;
|
||||
|
||||
let isSaved = true;
|
||||
|
||||
let iframe: HTMLIFrameElement;
|
||||
let compileLog = '';
|
||||
|
||||
files.subscribe(() => {
|
||||
isSaved = false;
|
||||
if ($autoRun) run();
|
||||
});
|
||||
|
||||
// files is set by +layout.svelte on load, but we want to keep isSaved true on load
|
||||
// i.e. undo above subscription
|
||||
onMount(() => {
|
||||
isSaved = true;
|
||||
});
|
||||
|
||||
function run() {
|
||||
if (!$loading) {
|
||||
$loading = true;
|
||||
iframe?.contentWindow?.postMessage(
|
||||
{
|
||||
action: 'reload'
|
||||
},
|
||||
window.location.origin
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function onMessage(event: MessageEvent) {
|
||||
if (event.origin !== window.location.origin) return;
|
||||
|
||||
const { action } = event.data;
|
||||
console.log('recv from iframe', event.data);
|
||||
|
||||
if (action === 'ready') {
|
||||
iframe?.contentWindow?.postMessage(
|
||||
{
|
||||
action: 'run',
|
||||
files: $files
|
||||
},
|
||||
window.location.origin
|
||||
);
|
||||
$loading = false; // once files are sent, any changes to files will trigger a reload
|
||||
} else if (action === 'running') {
|
||||
compileLog = event.data.compileLog;
|
||||
} else if (action === 'compile_error') {
|
||||
compileLog = event.data.compileLog;
|
||||
}
|
||||
}
|
||||
|
||||
async function share() {
|
||||
|
||||
// custom event tracking for analytics
|
||||
tryPlausible('Share');
|
||||
|
||||
isSaved = true;
|
||||
await navigator.clipboard.writeText(window.location.toString());
|
||||
}
|
||||
|
||||
// Notify iframe of theme changes so it can reload its theme from localStorage
|
||||
$: {
|
||||
$theme;
|
||||
iframe?.contentWindow?.postMessage(
|
||||
{
|
||||
action: 'theme_change'
|
||||
},
|
||||
window.location.origin
|
||||
);
|
||||
// only used when the users presses the button RUN
|
||||
async function run() {
|
||||
tryPlausible('Compile');
|
||||
$runCode = true;
|
||||
}
|
||||
|
||||
function onBeforeUnload(evt: BeforeUnloadEvent) {
|
||||
@@ -92,7 +30,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:message={onMessage} on:beforeunload={isSaved ? undefined : onBeforeUnload} />
|
||||
<svelte:window on:beforeunload={$isSaved ? undefined : onBeforeUnload} />
|
||||
|
||||
<div class="w-full h-screen font-sans flex flex-col overflow-hidden">
|
||||
{#if enableMenu}
|
||||
@@ -109,21 +47,10 @@
|
||||
<FileTabs />
|
||||
</div>
|
||||
|
||||
<Editor {compileLog} />
|
||||
<Editor />
|
||||
</section>
|
||||
<section slot="b" class="border-t border-stone-200 dark:border-stone-700 overflow-hidden">
|
||||
<div class="w-full h-full" class:hidden={!$loading}>
|
||||
<Loading />
|
||||
</div>
|
||||
<iframe
|
||||
bind:this={iframe}
|
||||
src={outputUrl}
|
||||
class="w-full h-full"
|
||||
class:hidden={$loading}
|
||||
title="Output"
|
||||
allowtransparency={true}
|
||||
frameborder={0}
|
||||
/>
|
||||
<Output />
|
||||
</section>
|
||||
</SplitPane>
|
||||
</div>
|
||||
|
@@ -1,5 +0,0 @@
|
||||
// for adblockers protection
|
||||
export function tryPlausible(msg: string) {
|
||||
if (self.plausible)
|
||||
plausible(msg)
|
||||
}
|
@@ -7,7 +7,7 @@
|
||||
import { indentUnit } from '@codemirror/language';
|
||||
import { lintGutter } from '@codemirror/lint';
|
||||
import { java } from '@codemirror/lang-java';
|
||||
import { files, fiddleTitle, fiddleUpdated, selectedFilePath, type File } from './state';
|
||||
import { files, fiddleTitle, fiddleUpdated, selectedFilePath, type File, compileLog } from './state';
|
||||
import './codemirror.css';
|
||||
import { compartment, diagnostic, parseCompileLog } from './linter';
|
||||
import { effectiveTheme } from '$lib/settings/store';
|
||||
@@ -111,15 +111,6 @@
|
||||
editorView?.destroy();
|
||||
};
|
||||
});
|
||||
/*
|
||||
beforeNavigate(() => {
|
||||
skipReset = true;
|
||||
});
|
||||
afterNavigate(() => {
|
||||
skipReset = false;
|
||||
editorStates.clear();
|
||||
reset(files);
|
||||
});*/
|
||||
|
||||
// Look at the selected file
|
||||
$: {
|
||||
@@ -130,9 +121,8 @@
|
||||
}
|
||||
|
||||
// Linter
|
||||
export let compileLog: string;
|
||||
$: {
|
||||
const diagnostics = parseCompileLog(compileLog, $files);
|
||||
const diagnostics = parseCompileLog($compileLog, $files);
|
||||
for (let fileIndex = 0; fileIndex < diagnostics.length; fileIndex++) {
|
||||
const diagnosticsForFile = diagnostics[fileIndex];
|
||||
const path = $files[fileIndex].path;
|
||||
|
@@ -1,54 +1,20 @@
|
||||
<script lang="ts">
|
||||
import CheerpJ from '$lib/CheerpJ.svelte';
|
||||
import Icon from '@iconify/svelte';
|
||||
import { files, loading, type File } from './state';
|
||||
import { theme } from '$lib/settings/store';
|
||||
import { isRunning } from './state';
|
||||
import Loading from '$lib/Loading.svelte';
|
||||
|
||||
export let showLink: boolean;
|
||||
let consoleEl: HTMLPreElement;
|
||||
let display: HTMLElement;
|
||||
let cjConsole: HTMLPreElement;
|
||||
let lwjglCanvas: HTMLCanvasElement;
|
||||
|
||||
$: if (lwjglCanvas) window.lwjglCanvasElement = lwjglCanvas;
|
||||
|
||||
let cheerpj: CheerpJ;
|
||||
|
||||
async function ready() {
|
||||
if (window.parent !== window && window.parent) {
|
||||
// Tell parent frame we are ready to recieve files
|
||||
window.parent.postMessage({ action: 'ready' }, window.location.origin);
|
||||
} else {
|
||||
// Load files from load function
|
||||
files.set($files); // Force file write
|
||||
cheerpj?.compileAndRun();
|
||||
}
|
||||
}
|
||||
|
||||
function onMessage(event: MessageEvent) {
|
||||
if (event.origin !== window.location.origin) return;
|
||||
|
||||
const { action } = event.data;
|
||||
console.log('recv from top', event.data);
|
||||
|
||||
if (action === 'reload') {
|
||||
window.location.reload();
|
||||
} else if (action === 'run') {
|
||||
files.set(event.data.files);
|
||||
cheerpj?.compileAndRun();
|
||||
} else if (action === 'theme_change') {
|
||||
$theme = JSON.parse(localStorage['theme']);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:message={onMessage} />
|
||||
|
||||
<div class="w-screen h-screen" class:hidden={!$loading}>
|
||||
<div class="w-full h-full" class:hidden={!$isRunning}>
|
||||
<Loading />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 grow">
|
||||
<div class="w-full h-full grid grid-cols-2 grow">
|
||||
<section class="border-r border-stone-200 dark:border-stone-700">
|
||||
<div class="p-3 h-full overflow-scroll text-stone-800 dark:text-stone-100">
|
||||
<div class="flex text-stone-500 text-sm select-none pb-3">
|
||||
@@ -56,19 +22,19 @@
|
||||
|
||||
<button
|
||||
class="ml-auto text-xs hover:underline text-stone-400 dark:text-stone-600"
|
||||
on:click={() => (consoleEl.innerText = '')}
|
||||
on:click={() => (cjConsole.innerText = '')}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- CheerpJ implicitly looks for a #console to write to -->
|
||||
<pre class="font-mono text-sm h-0" bind:this={consoleEl} id="console" />
|
||||
<pre class="font-mono text-sm h-0" bind:this={cjConsole} id="console" />
|
||||
</div>
|
||||
</section>
|
||||
<section class="flex flex-col">
|
||||
<div class="p-3 text-stone-500 text-sm select-none">Result</div>
|
||||
<div class="grow relative" bind:this={display}>
|
||||
<div class="grow relative" id="output">
|
||||
<canvas bind:this={lwjglCanvas} class="absolute inset-0 w-full h-full" />
|
||||
<!-- #cheerpjDisplay will be inserted here -->
|
||||
</div>
|
||||
@@ -76,12 +42,10 @@
|
||||
</div>
|
||||
|
||||
<div class="absolute top-0 right-0 text-stone-500 text-sm flex items-center select-none">
|
||||
{#if showLink}
|
||||
<!-- svelte-ignore a11y-invalid-attribute -->
|
||||
<a href="" target="_blank" rel="noreferrer" class="px-2 py-2" title="Open in new tab">
|
||||
<Icon icon="mi:external-link" class="w-5 h-5" />
|
||||
</a>
|
||||
{/if}
|
||||
<!-- svelte-ignore a11y-invalid-attribute -->
|
||||
<a href="" target="_blank" rel="noreferrer" class="px-2 py-2" title="Open in new tab">
|
||||
<Icon icon="mi:external-link" class="w-5 h-5" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@@ -90,4 +54,4 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<CheerpJ bind:this={cheerpj} on:ready={ready} {display} {consoleEl} />
|
||||
<CheerpJ/>
|
||||
|
@@ -29,4 +29,10 @@ export const description = writable<string>('JavaFiddle is an online, browser-ba
|
||||
|
||||
export const autoRun = persist(writable<boolean>(false), createLocalStorage(), 'autoRun');
|
||||
|
||||
export const loading = writable<boolean>(false);
|
||||
export const isRunning = writable<boolean>(false);
|
||||
|
||||
export const isSaved = writable<boolean>(true);
|
||||
|
||||
export const runCode = writable<boolean>(false);
|
||||
|
||||
export const compileLog = writable<string>('');
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import ThemeSwitcher from './ThemeSwitcher.svelte';
|
||||
import { autoRun } from '$lib/repl/state';
|
||||
import { autoRun, isRunning, runCode } from '$lib/repl/state';
|
||||
</script>
|
||||
|
||||
<!-- triangle pointing above -->
|
||||
@@ -27,7 +27,10 @@
|
||||
|
||||
<h3 class="font-semibold">Behaviour</h3>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<input type="checkbox" bind:checked={$autoRun} id="auto-run" />
|
||||
<input type="checkbox" bind:checked={$autoRun} on:change={() => {
|
||||
// if autorun is set force re-run by updating files
|
||||
$runCode = $autoRun && !$isRunning;
|
||||
}} id="auto-run" />
|
||||
<label for="auto-run" class="grow">Run code automatically</label>
|
||||
</div>
|
||||
</div>
|
||||
|
13
src/lib/utilities.ts
Normal file
13
src/lib/utilities.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
// for adblockers protection
|
||||
export function tryPlausible(msg: string) {
|
||||
if (self.plausible)
|
||||
plausible(msg)
|
||||
}
|
||||
|
||||
export function debounceFunction(fn: () => void, delay: number) {
|
||||
let timeoutId: number;
|
||||
return(() => {
|
||||
clearInterval(timeoutId);
|
||||
timeoutId = setTimeout(() => fn(), delay);
|
||||
});
|
||||
}
|
@@ -1,7 +1,5 @@
|
||||
<script lang="ts">
|
||||
import Repl from '$lib/Repl.svelte';
|
||||
|
||||
export let data;
|
||||
</script>
|
||||
|
||||
<Repl outputUrl={data.outputUrl} />
|
||||
<Repl/>
|
||||
|
@@ -1,8 +0,0 @@
|
||||
export function load({ url }) {
|
||||
let outputUrl = url.pathname;
|
||||
if (!outputUrl.endsWith('/')) outputUrl += '/';
|
||||
outputUrl += 'output';
|
||||
return {
|
||||
outputUrl
|
||||
};
|
||||
}
|
@@ -1,7 +1,5 @@
|
||||
<script lang="ts">
|
||||
import Repl from '$lib/Repl.svelte';
|
||||
|
||||
export let data;
|
||||
</script>
|
||||
|
||||
<Repl outputUrl={data.outputUrl} enableMenu={false} enableSidebar={false} />
|
||||
<Repl enableMenu={false} enableSidebar={false} />
|
||||
|
@@ -1,8 +0,0 @@
|
||||
export function load({ url }) {
|
||||
let outputUrl = url.pathname.replace('/embed', '');
|
||||
if (!outputUrl.endsWith('/')) outputUrl += '/';
|
||||
outputUrl += 'output';
|
||||
return {
|
||||
outputUrl
|
||||
};
|
||||
}
|
@@ -1,16 +1,5 @@
|
||||
<script lang="ts">
|
||||
import Output from '$lib/repl/Output.svelte';
|
||||
import { loading } from '$lib/repl/state';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let isTop = false;
|
||||
let isShared = false;
|
||||
onMount(() => {
|
||||
isTop = window.top === window;
|
||||
isShared = window.location.pathname !== '/output';
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col w-screen h-screen overflow-hidden" class:hidden={$loading}>
|
||||
<Output showLink={!isTop && isShared} />
|
||||
</div>
|
||||
<Output />
|
||||
|
Reference in New Issue
Block a user