added two apps, and better implementation

This commit is contained in:
David Meincke
2022-03-22 18:20:26 +01:00
parent bb44e0e92d
commit 33b3f2e5cc
47 changed files with 486 additions and 151 deletions
+103
View File
@@ -0,0 +1,103 @@
export class BaseElement extends HTMLElement {
private _hasConnectedCallback: boolean = false;
connectedCallback() {
if (!this._hasConnectedCallback) {
this.onInit();
}
else {
this.onUpdate();
}
this._hasConnectedCallback = true;
}
onInit() { }
onUpdate() { }
find(tagName: string): HTMLElement | null {
var found = document.getElementsByTagName(tagName);
if (found[0]) {
return <HTMLElement>found[0];
}
else {
return null;
}
}
findAncestor(tagName: string, startsWidth: boolean = false): Element | null {
var element = <any>this;
while (element.parentElement !== null) {
var parentEle = element.parentElement;
if (tagName.toUpperCase() === parentEle.tagName.toUpperCase()) {
return parentEle;
}
else if (startsWidth && parentEle.tagName.toUpperCase().indexOf(tagName.toUpperCase()) === 0) {
return parentEle;
}
element = parentEle;
}
return null;
}
findSibling(tagName: string, startsWidth: boolean = false): Element | null {
var parentEle = this.parentElement;
if (parentEle != null) {
for (let i = 0; i < parentEle.children.length; i++) {
const child = parentEle.children[i];
if (child != this) {
if (tagName.toUpperCase() === child.tagName.toUpperCase()) {
return child;
}
else if (startsWidth && child.tagName.toUpperCase().indexOf(tagName.toUpperCase()) === 0) {
return child;
}
}
}
}
return null;
}
hasChild(element: Element, startsWidth: boolean = false): boolean {
var ele = this;
if (ele != null) {
for (let i = 0; i < ele.children.length; i++) {
const child = ele.children[i];
if (child == element) {
return true;
}
}
}
return false;
}
map(tagName: string): any {
return this.getElementsByTagName(tagName)[0];
}
mapGlobal(tagName: string): any {
return document.getElementsByTagName(tagName)[0];
}
waitFor(value: any, fn: Function) {
var interv = setInterval(() => {
if (value) {
clearInterval(interv);
fn();
}
}, 100);
}
}
@@ -0,0 +1,5 @@
app-root {
width: 100%;
height: 100%;
display: block;
}
@@ -0,0 +1,12 @@
import { BaseElement } from "../_base";
import './app-root.less';
export class AppRoot extends BaseElement {
onInit(): void {
console.log("APP ROOT INIT");
}
onUpdate(): void {
console.log("APP ROOT UPDATE");
}
}
@@ -0,0 +1,5 @@
monaco-editor {
width: 100%;
height: 100%;
display: block;
}
@@ -0,0 +1,92 @@
import { BaseElement } from "../_base";
import './code-editor.less';
import * as monaco from 'monaco-editor';
import { OutputFrame } from "../output-frame/output-frame";
import initScript from './injects/editor-init.txt';
export class CodeEditor extends BaseElement {
public input: string = initScript;
onInit(): void {
this.initWorkers();
var editor = monaco.editor.create(this, {
value: this.input,
language: 'javascript',
automaticLayout: true,
contextmenu: false,
minimap: {
enabled: false
},
autoIndent: "full"
});
monaco.languages.registerCompletionItemProvider("javascript", {
triggerCharacters: ["."],
provideCompletionItems: (model, position, context, token) => (
{
suggestions: [
{
label: 'for',
kind: monaco.languages.CompletionItemKind.Function,
insertText: 'for (let i = 0; i < array.length; i++) {\n\n}',
range: <any>null,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet
},
{
label: 'forr',
kind: monaco.languages.CompletionItemKind.Function,
insertText: 'for (var i = array.length - 1; i >= 0; i--) {\n\n}',
range: <any>null,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet
}
]
}
)
});
editor.onDidChangeModelContent((e) => {
this.input = editor.getValue();
this.outputFrame.setScript(this.input);
});
this.waitFor(this.outputFrame, () => {
this.outputFrame.setScript(this.input);
});
}
initWorkers() {
// @ts-ignore
self.MonacoEnvironment = {
getWorkerUrl: function (moduleId: any, label: string) {
if (label === 'json') {
return './json.worker.bundle.js';
}
if (label === 'css' || label === 'scss' || label === 'less') {
return './css.worker.bundle.js';
}
if (label === 'html' || label === 'handlebars' || label === 'razor') {
return './html.worker.bundle.js';
}
if (label === 'typescript' || label === 'javascript') {
return './ts.worker.bundle.js';
}
return './editor.worker.bundle.js';
}
};
}
onUpdate(): void {
console.log("APP ROOT UPDATE");
}
get outputFrame(): OutputFrame {
return <OutputFrame>this.findSibling("output-frame");
}
}
@@ -0,0 +1,16 @@
var myDiv = document.createElement("div");
myDiv.innerText = "hello moto";
document.body.appendChild(myDiv);
var i = 0;
while (i < 10) {
console.log("hello " + i);
i++;
}
var obj = { hello: true, moto: "enabled" };
console.log(obj);
@@ -0,0 +1,38 @@
notification-bubble {
margin: 10px;
padding: 10px;
color: black;
background-color: #a0ffa7;
border: 1px solid #8ddf92;
display: block;
position: relative;
box-shadow: 1px 1px 1px 1px rgba(0,0,0,0.19);
border-radius: 4px;
}
notification-bubble.log {
background-color: #ffffff;
border-color: #dfdfdf;
}
notification-bubble.info {
background-color: #e7e7b3;
border-color: #d4d4a9;
}
notification-bubble.warn {
background-color: #fff9a0;
border-color: #e6e094;
}
notification-bubble.error {
background-color: #ffa0a0;
border-color: #df8e8e;
}
notification-bubble.syntax-error {
background-color: #ff6262;
background-color: #d15353;
font-size: 22px;
padding: 30px;
}
@@ -0,0 +1,13 @@
import { BaseElement } from "../_base";
import './notification-bubble.less';
export class NotificationBubble extends BaseElement {
onInit(): void {
if (!this.classList.contains("syntax-error")) {
setTimeout(() => {
this.parentElement?.removeChild(this);
}, 4000);
}
}
}
@@ -0,0 +1,8 @@
notification-bubbles {
display: block;
position: fixed;
bottom: 0;
right: 0;
z-index: 9999;
width: 380px;
}
@@ -0,0 +1,31 @@
import { messageListener } from "../../../shared/message-listener";
import { BaseElement } from "../_base";
import { NotificationBubble } from "./notification-bubble";
import './notification-bubbles.less';
export class NotificationBubbles extends BaseElement {
add(text: string, type: 'log' | 'info' | 'warn' | 'error' | 'syntax-error' | 'script' | 'time' | 'response') {
if (text && type) {
var bubble = <NotificationBubble>document.createElement("notification-bubble");
bubble.innerText = text;
bubble.classList.add(type);
this.appendChild(bubble);
if (this.children.length > 50) {
this.removeChild(this.children[0]);
}
}
}
reset() {
this.innerHTML = "";
}
onInit(): void {
messageListener((type, value) => {
if (type != 'script' && type != 'response')
this.add(value, type);
});
}
}
@@ -0,0 +1,12 @@
output-frame {
width: 100%;
height: 100%;
display: block;
}
output-frame > iframe {
position: relative;
width: 100%;
height: 100%;
border: none;
}
@@ -0,0 +1,84 @@
import { BaseElement } from "../_base";
import './output-frame.less';
import { NotificationBubbles } from "../notification-bubbles/notification-bubbles";
import { GetOutputFrameUrl } from "../../../shared/url-helpers";
import { GetNotificationBubbles } from "../../../shared/getdom";
import { postMessage } from "../../../shared/post-message";
export class OutputFrame extends BaseElement {
public Iframe: HTMLIFrameElement | null = null;
public CurrentIframeScriptRemoveResponseListener: Function | null = null;
constructor() {
super();
}
onInit(): void {
this.reset();
}
onUpdate(): void {
}
reset(resetNotificationBubbles: boolean = true) {
if (this.CurrentIframeScriptRemoveResponseListener != null) {
this.CurrentIframeScriptRemoveResponseListener();
this.CurrentIframeScriptRemoveResponseListener = null;
}
if (this.Iframe && this.hasChild(this.Iframe)) {
this.removeChild(this.Iframe);
}
this.Iframe = document.createElement("iframe");
this.Iframe.setAttribute("sandbox", "allow-pointer-lock allow-same-origin allow-scripts");
this.Iframe.src = GetOutputFrameUrl();
this.appendChild(this.Iframe);
if (resetNotificationBubbles) {
var bubbles = GetNotificationBubbles();
if (bubbles && bubbles.reset) {
bubbles.reset();
}
}
}
setError() {
this.reset();
if (this.Iframe) {
this.Iframe.src = GetOutputFrameUrl() + "execution-time-error.html";
}
}
preventInfiniteLoop(value: string) {
return value.replace("while (true)", "while (false)");
}
setScript(value: string) {
this.reset();
this.onIframeLoaded(() => {
this.CurrentIframeScriptRemoveResponseListener = postMessage(this.Iframe?.contentWindow, "script", value, (executeTimeInMs, exceedTimeInMs) => {
if (executeTimeInMs === -1) {
this.setError();
}
else {
var bubbles = GetNotificationBubbles();
bubbles.add("code executed in: " + executeTimeInMs + "ms", "info");
}
});
});
}
onIframeLoaded(fn: Function) {
if (this.Iframe) {
this.Iframe.onload = () => {
fn();
}
}
else {
this.waitFor(this.Iframe, () => {
this.onIframeLoaded(fn);
});
}
}
}