JSFiddle - React, Tailwind, and code Playground
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Compiler</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" />
<link rel="stylesheet" data-name="vs/editor/editor.main"
href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.52.2/min/vs/editor/editor.main.min.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<header id="head"><span id="name">Compiler</span>
<div id="editorSettings">
<button id="settings"><i class="fa-solid fa-gear"></i></button>
</div>
</header>
<nav id="nav">
<div class="tab-container" id="tabs">
</div>
<div id="newFile" popover>
<button class="option" popovertarget="newFile" onclick="createNewFile('html')">New HTML file</button>
<button class="option" popovertarget="newFile" onclick="createNewFile('css')">New CSS file</button>
<button class="option" popovertarget="newFile" onclick="createNewFile('js')">New JS file</button>
</div>
<div id="stuff">
<button popovertarget="newFile" id="fileCreator">+</button>
<button id="run" onclick="previewFirstHtmlFile()">RUN</button>
</div>
</nav>
<div id="main">
<div id="input"></div>
<div id="helper"></div>
<div id="divider"></div>
<div id="browser">
<div id="output">
<iframe id="outputFrame"></iframe>
</div>
<div id="console">
<div id="verticalDivider"></div>
<div id="verticalHelper"></div>
</div>
</div>
</div>
<script src="resize.js"></script>
<script src="core.js"></script>
<script src="editor.js"></script>
<script src="file-actions.js"></script>
<script src="preview.js"></script>
<script src="console.js"></script>
</body>
</html>
CSS
:root {
--inWidth: 50%;
--outWidth: calc(100% - var(--inWidth));
--height: calc(100% - 12px);
--consoleHeight: calc(100% - var(--height));
--console-bg: #1e1e1e;
--console-text: #eaeaea;
--log-color-log: #ccc;
--log-color-info: #00f;
--log-color-warn: #ff0;
--log-color-error: #f00;
}
.custom-console {
display: flex;
flex-direction: column;
border: 1px solid #555;
overflow: hidden;
}
.console-controls {
display: flex;
gap: 5px;
padding: 5px;
background: #2b2b2b;
border-bottom: 1px solid #444;
}
.console-controls button {
background: #444;
color: white;
border: none;
padding: 5px 10px;
cursor: pointer;
font-family: inherit;
}
.console-output {
flex: 1;
overflow-y: auto;
padding: 10px;
background: var(--console-bg);
}
.log-entry {
padding: 5px 0;
border-bottom: 1px solid #333;
word-wrap: break-word;
}
.log-entry.log { color: var(--log-color-log); }
.log-entry.info { color: var(--log-color-info); }
.log-entry.warn { color: var(--log-color-warn); }
.log-entry.error { color: var(--log-color-error); }
.timestamp {
color: #888;
margin-right: 5px;
}
body {
overflow: hidden;
margin: 0;
}
#helper,
#verticalHelper {
transform: translate(-50%, -50%);
position: fixed;
height: 400px;
width: 400px;
top: -1000px;
z-index: 2147483647;
/* backdrop-filter: invert(); */
}
#helper {
cursor: ew-resize;
}
#verticalHelper {
cursor: ns-resize;
}
#head {
border-bottom: 1px #131419 solid;
background-color: #1E2028;
width: 100vw;
height: 40px;
display: flex;
place-content: center;
}
#name {
font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
position: absolute;
left: 0;
font-size: 35px;
color: #ffffff;
}
#nav {
background-color: #161A22;
/* Vertically align items in the nav */
align-items: center;
/* Ensure tabs are in a row */
display: flex;
width: 100vw;
height: 40px;
}
#nav #stuff {
justify-content: center;
position:...
JavaScript
const helper = document.getElementById('helper');
let horizontalMouseDown = false;
let verticalMouseDown = false
const root = document.documentElement;
const resize = document.getElementById('divider');
const verticalDivider = document.getElementById('verticalDivider')
const verticalHelper = document.getElementById('verticalHelper')
const browser = document.getElementById('browser')
verticalDivider.addEventListener('mousedown', (e) => {
verticalMouseDown = true;
verticalHelper.style.left = `${e.clientX}px`;
verticalHelper.style.top = `${e.clientY}px`;
})
resize.addEventListener('mousedown', (e) => {
horizontalMouseDown = true;
helper.style.left = `${e.clientX}px`;
helper.style.top = `${e.clientY}px`;
});
document.addEventListener('mousemove', (e) => {
if (horizontalMouseDown) {
e.preventDefault();
helper.style.left = `${e.clientX}px`;
helper.style.top = `${e.clientY}px`;
const viewportWidth = window.innerWidth;
root.style.setProperty('--inWidth', `${e.clientX / viewportWidth * 100}%`);
}
if (verticalMouseDown) {
e.preventDefault();
verticalHelper.style.left = `${e.clientX}px`;
verticalHelper.style.top = `${e.clientY}px`;
const viewportHeight = browser.clientHeight;
root.style.setProperty('--height', `clamp(0px, calc(${e.clientY} / ${viewportHeight} * 100% - 87px), calc(100% - 12px)`);
}
});
document.addEventListener('mouseup', () => {
horizontalMouseDown = false;
verticalMouseDown = false;
helper.style.left = `-2000px`;
helper.style.top = `-2000px`;
verticalHelper.style.left = `-2000px`;
verticalHelper.style.top = `-2000px`;
});
const tabsDiv = document.getElementById('tabs');
const outputDiv = document.getElementById('output');
const inputDiv = document.getElementById('input');
let files = {};
let fileCounter = 0;
let activeFile = null;
let firstHtmlFileId = null;
class File {
constructor(type, name, uniqueId) {
this.uniqueId = uniqueId;
this.name = name;
this.type =...