Compare commits

...
10 Commits
Author SHA1 Message Date
David Meincke 74f812c5aa improved sanity check 2023-06-02 11:10:10 +02:00
David Meincke 221183962c added sanity check 2023-06-02 10:43:19 +02:00
david 65a6312b8e some tests 2023-06-01 22:02:03 +02:00
David Meincke e3515901e4 added slider and some ui fixes 2023-05-30 17:10:35 +02:00
David Meincke 28631e8251 fixed some bugs, dark/white mode fix and sessionid in url 2023-05-30 11:39:21 +02:00
David Meincke e0c998cfac added sessions support 2023-05-12 09:44:36 +02:00
David Meincke d3a0bd984e fix resize-handle (again) 2022-03-27 17:49:21 +02:00
David Meincke eae4476ed8 fix resize-handle display 2022-03-27 17:44:54 +02:00
David Meincke 1d90760c33 set init settings for languages + fixes 2022-03-27 17:21:16 +02:00
David Meincke 7fce9b48c3 frame css fix 2022-03-27 17:05:00 +02:00
34 changed files with 6300 additions and 3901 deletions
+6 -1
View File
@@ -2,12 +2,17 @@
<div class="flex-shrink section top">
<h1>code editor</h1>
<window-control distribute=".code-editor-containers">
<window-control-connector style="display: none;" target=".menu-container" class="active">Sessions</window-control-connector>
<window-control-connector target="code-editor-container[language='Javascript']" class="active">Javascript</window-control-connector>
<window-control-connector target="code-editor-container[language='HTML']">HTML</window-control-connector>
<window-control-connector target="code-editor-container[language='CSS']">CSS</window-control-connector>
</window-control>
</div>
<div class="flex-dynamic">
<div class="menu menu-container" id="session-menu" style="display: none">
<resize-handle target=".menu-container" place="right" dir="horizontal"></resize-handle>
<session-list></session-list>
</div>
<div class="code-editor-containers flex-vert">
<code-editor-container language="Javascript" dir="vertical" class="first-visible last-visible">
</code-editor-container>
@@ -24,6 +29,6 @@
<output-frame></output-frame>
</div>
</div>
<div class="flex-shrink section bottom">methods: rect, box, center, width, height, rand</div>
<div class="flex-shrink section bottom">methods: rect, box, center, width, height, rand, randomColor</div>
</div>
<notification-bubbles></notification-bubbles>
+3 -41
View File
@@ -1,4 +1,5 @@
@import url('../shared/theme.less');
@import url('../shared/flex.less');
html, body {
margin: 0;
@@ -9,18 +10,6 @@ html, body {
}
.flex-horiz {
display: flex;
flex-direction: row;
height: 100%;
overflow: hidden;
}
.flex-vert {
display: flex;
flex-direction: column;
height: 100%;
}
.flex-horiz > * {
min-height: auto !important;
@@ -33,37 +22,10 @@ html, body {
}
.flex-horiz > *, .flex-vert > * {
flex-grow: 1;
flex-shrink: 1;
flex-basis: 1px;
position: relative;
}
.animate .flex-horiz > *, .animate .flex-vert > * {
transition: all 280ms ease-in-out;
}
.flex-horiz .flex-shrink, .flex-vert .flex-shrink {
flex-grow: 0;
flex-shrink: 1;
}
.flex-horiz .flex-grow, .flex-vert .flex-grow {
flex-grow: 1;
flex-shrink: 0;
}
.flex-reset {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 100%;
height: 100%;
}
body {
font-family: @primary-font-family;
}
@@ -119,11 +81,11 @@ body {
max-height: 0 !important;
}
.flex-dynamic.flex-horiz .code-editor-containers resize-handle[dir='vertical'] {
.flex-dynamic.flex-horiz .output-frame-container resize-handle[dir='vertical'] {
display: none;
}
.flex-dynamic.flex-vert .code-editor-containers resize-handle[dir='horizontal'] {
.flex-dynamic.flex-vert .output-frame-container resize-handle[dir='horizontal'] {
display: none;
}
+20 -1
View File
@@ -12,6 +12,10 @@ import { JavascriptCodeEditor } from './elements/code-editor/javascript-code-edi
import { HtmlCodeEditor } from './elements/code-editor/html-code-editor';
import { CssCodeEditor } from './elements/code-editor/css-code-editor';
import { setDynamicFlexListener } from '../shared/set-dynamic-flex-listener';
import { SessionList } from './elements/session-list/session-list';
import { SessionListItem } from './elements/session-list-item/session-list-item';
import { LoadingSpinner } from './elements/loading-spinner/loading-spinner';
import { isSessionPage } from '../shared/page';
window.customElements.define('app-root', AppRoot);
window.customElements.define('code-editor-container', CodeEditorContainer);
@@ -24,8 +28,23 @@ window.customElements.define('notification-bubble', NotificationBubble);
window.customElements.define('resize-handle', ResizeHandle);
window.customElements.define('window-control', WindowControl);
window.customElements.define('window-control-connector', WindowControlConnector);
window.customElements.define('session-list', SessionList);
window.customElements.define('session-list-item', SessionListItem);
window.customElements.define('loading-spinner', LoadingSpinner);
document.body.innerHTML += bootHtml;
if (isSessionPage()) {
var menuEle = document.getElementById("session-menu");
var containers = <HTMLElement>document.getElementsByClassName("code-editor-containers")[0];
var outputframe = <HTMLElement>document.getElementsByClassName("output-frame-container")[0];
var sessionMenuItem = <HTMLElement>document.querySelector("window-control-connector[target='.menu-container']");
if (menuEle) {
menuEle.style.display = "block";
sessionMenuItem.style.display = "inline-block";
containers.style.display = "none";
outputframe.style.display = "none";
}
}
setDynamicFlexListener();
@@ -4,6 +4,7 @@ import { ResizeHandle } from "../resize-handle/resize-handle";
import './code-editor-container.less';
export class CodeEditorContainer extends BaseElement {
private codeEditor: CodeEditor | null = null;
onInit(): void {
var language = this.getAttribute("language");
this.classList.add("flex-vert");
@@ -22,11 +23,15 @@ export class CodeEditorContainer extends BaseElement {
this.appendChild(div);
}
setInput(input: any) {
this.codeEditor?.setInput(input);
}
createCodeEditor(language: string, expandDir: 'vertical' | 'horizontal') {
var codeEditorContent = document.createElement("div");
codeEditorContent.classList.add("code-editor-content");
var codeEditor = <CodeEditor>document.createElement(language + "-code-editor");
codeEditorContent.appendChild(codeEditor);
this.codeEditor = <CodeEditor>document.createElement(language + "-code-editor");
codeEditorContent.appendChild(this.codeEditor);
var resizeHandle = <ResizeHandle>document.createElement("resize-handle");
resizeHandle.setAttribute("dir", expandDir);
@@ -3,27 +3,34 @@ import './code-editor.less';
import * as monaco from 'monaco-editor';
import { OutputFrame } from "../output-frame/output-frame";
import { debounceManager } from "../../../shared/ensure-debounce";
import * as sessionApi from '../../../shared/session-api';
import { isSessionPage } from "../../../shared/page";
import { sanityConvert } from "../../../shared/sanity-code-check";
export class CodeEditor extends BaseElement {
public input: string = "";
public language: string = "";
public editor!: monaco.editor.IStandaloneCodeEditor;
public lastInputCache: any;
onInit(): void {
this.initWorkers();
this.input = this.setInitInput();
this.language = this.tagName.substring(0, this.tagName.indexOf("-")).toLocaleLowerCase();
this.lastInputCache = localStorage.getItem(this.language + "LastInputCache");
this.input = this.setInitInput();
console.log(this.language);
this.setInitSettings();
const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)");
var editor = monaco.editor.create(this, {
value: this.input,
this.editor = monaco.editor.create(this, {
value: isSessionPage() ? "" : this.input,
language: this.language,
automaticLayout: true,
contextmenu: false,
readOnly: isSessionPage(),
minimap: {
enabled: false
},
@@ -32,23 +39,44 @@ export class CodeEditor extends BaseElement {
});
var debounce = debounceManager(1000);
editor.onDidChangeModelContent((e) => {
this.input = editor.getValue();
this.editor.onDidChangeModelContent((e) => {
if (!isSessionPage()) {
this.input = this.editor.getValue();
this.lastInputCache = this.input;
localStorage.setItem(this.language + "LastInputCache", this.input);
debounce.ensureDebounce(() => {
this.outputFrame.setContent(this.language, this.input);
sessionApi.setSessionData(this.language, this.input);
this.outputFrame.setContent(this.language, this.language.toLowerCase() === "javascript" ? sanityConvert(this.input) : this.input);
});
}
});
this.waitFor(this.outputFrame, () => {
this.outputFrame.setContent(this.language, this.input);
this.outputFrame.setContent(this.language, isSessionPage() ? "" : (this.language.toLowerCase() === "javascript" ? sanityConvert(this.input) : this.input));
});
}
setInput(input: any) {
if (this.input !== input) {
this.input = input;
this.lastInputCache = this.input;
localStorage.setItem(this.language + "LastInputCache", this.input);
this.editor.setValue(input);
this.outputFrame.setContent(this.language, this.language.toLowerCase() === "javascript" ? sanityConvert(this.input) : this.input);
}
}
setInitInput(): string {
if (!isSessionPage() && sessionApi.hasSession()) {
return this.lastInputCache;
}
else {
return "";
}
}
setInitSettings(): void {
}
@@ -1,12 +1,19 @@
import { CodeEditor } from "./code-editor";
import * as monaco from 'monaco-editor';
import initCss from '!!raw-loader!./injects/editor-init.css';
import { createScript } from "../../../shared/create-script";
import flexCss from '!!raw-loader!../../../shared/flex.less';
export class CssCodeEditor extends CodeEditor {
setInitInput(): string {
var base = super.setInitInput();
if (!base) {
return initCss;
}
else {
return base;
}
}
setInitSettings(): void {
monaco.languages.registerCompletionItemProvider("css", {
@@ -16,7 +23,7 @@ export class CssCodeEditor extends CodeEditor {
{
label: 'insert-flex',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: createScript('https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js', false).toString(),
insertText: flexCss,
range: <any>null,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet
}
@@ -1,12 +1,17 @@
import { CodeEditor } from "./code-editor";
import * as monaco from 'monaco-editor';
import initHtml from '!!raw-loader!./injects/editor-init.html';
import { createScript } from "../../../shared/create-script";
export class HtmlCodeEditor extends CodeEditor {
setInitInput(): string {
var base = super.setInitInput();
if (!base) {
return initHtml;
}
else {
return base;
}
}
setInitSettings(): void {
monaco.languages.registerCompletionItemProvider("html", {
@@ -16,7 +21,7 @@ export class HtmlCodeEditor extends CodeEditor {
{
label: 'jqueryCDN',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: createScript('https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js', false).toString(),
insertText: "<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js'></script>",
range: <any>null,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet
}
@@ -6,8 +6,14 @@ import declarations from '!!raw-loader!./injects/declarations.d.ts';
export class JavascriptCodeEditor extends CodeEditor {
setInitInput(): string {
var base = super.setInitInput();
if (!base) {
return initScript;
}
else {
return base;
}
}
setInitSettings(): void {
monaco.languages.typescript.javascriptDefaults.addExtraLib(declarations, 'ts:filename/declarations.d.ts');
@@ -31,8 +37,8 @@ export class JavascriptCodeEditor extends CodeEditor {
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet
},
{
label: 'spiralBoxesSolutionSnippet',
detail: "inserts solution of spiral boxes",
label: 'spiralSolutionSnippet',
detail: "",
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: spiralBoxesSolutionScript,
range: <any>null,
@@ -0,0 +1,176 @@
loading-spinner {
display: block;
position: relative;
margin: 0 auto;
top: 50%;
transform: translateY(-50%);
border-radius: 50%;
opacity: 0.3;
}
.infinity-1 {
width:90px;
height:14px;
background:
radial-gradient(circle 7px at bottom, #fff 92%,#0000 ) 0 0,
radial-gradient(circle 7px at top , #fff 92%,#0000 ) 0 100%;
background-size:calc(100%/4) 50%;
background-repeat:repeat-x;
animation:i1 1s infinite;
}
@keyframes i1 {
80%,100% {background-position: calc(100%/3) 0,calc(100%/-3) 100%}
}
.infinity-2 {
width:90px;
height:14px;
background:
conic-gradient(from 135deg at top ,#fff 90deg,#0000 0) 0 0,
conic-gradient(from -45deg at bottom,#fff 90deg,#0000 0) 0 100%;
background-size:calc(100%/4) 50%;
background-repeat:repeat-x;
animation:i2 1s infinite;
}
@keyframes i2 {
80%,100% {background-position: calc(100%/3) 0,calc(100%/-3) 100%}
}
.infinity-3 {
width:90px;
height:14px;
background:
radial-gradient(circle 7px at bottom, #fff 92%,#0000 ) calc(100%/2) 0,
radial-gradient(circle 7px at top , #fff 92%,#0000 ) calc(100%/2) 100%,
conic-gradient(from 135deg at top ,#fff 90deg,#0000 0) 0 0,
conic-gradient(from -45deg at bottom,#fff 90deg,#0000 0) 0 100%;
background-size:calc(100%/2) 50%;
background-repeat:repeat-x;
animation:i3 3s infinite;
}
@keyframes i3 {
0% {background-position: calc(100%/2) 0,calc(100%/2) 100%,0 0, 0 100%}
20%,
30% {background-position: calc(3*100%/4) 0,calc(100%/4) 100%,calc(100%/4) 0, calc(100%/-4) 100%}
45%,
55% {background-position: 100% 0,0 100%,calc(100%/2) 0, calc(100%/-2) 100%}
70%,
80% {background-position: calc(5*100%/4) 0,calc(100%/-4) 100%,calc(3*100%/4) 0, calc(3*100%/-4) 100%}
100%{background-position: calc(3*100%/2) 0,calc(100%/-2) 100%,100% 0, -100% 100%}
}
.infinity-4 {
width:90px;
height:14px;
background:
radial-gradient(circle closest-side, #fff 92%,#0000 ) calc(100%/-4) 0,
radial-gradient(circle closest-side, #fff 92%,#0000 ) calc(100%/4) 0;
background-size:calc(100%/2) 100%;
animation:i4 1.5s infinite;
}
@keyframes i4 {
0% {background-position: calc(100%/-4) 0 ,calc(100%/4) 0}
50% {background-position: calc(100%/-4) -14px,calc(100%/4) 14px}
100% {background-position: calc(100%/4) -14px,calc(3*100%/4) 14px}
}
.infinity-5 {
width:60px;
aspect-ratio:1;
--g: conic-gradient(from -90deg at 10px 10px,#fff 90deg,#0000 0);
background:
var(--g),
var(--g) 10px 10px,
var(--g) 20px 20px;
background-size: 50% 50%;
animation:i5 1s infinite;
}
@keyframes i5 {
90%,100% {background-position: -30px 30px,-20px 40px,-10px 50px}
}
.infinity-6 {
width:60px;
aspect-ratio:1;
--g: conic-gradient(from -90deg at 10px 10px,#fff 90deg,#0000 0);
background: var(--g), var(--g), var(--g);
background-size: 50% 50%;
animation:i6 1s infinite;
}
@keyframes i6 {
0% {background-position:0 0 ,10px 10px,20px 20px}
50% {background-position:0 20px,10px 10px,20px 0 }
100% {background-position:20px 20px,10px 10px,0 0 }
}
.infinity-7 {
width:60px;
aspect-ratio:1;
--g: conic-gradient(from -90deg at 10px 10px,#fff 90deg,#0000 0);
background: var(--g), var(--g), var(--g);
background-size: 50% 50%;
animation:i7 1s infinite;
}
@keyframes i7 {
0% {background-position:0 0, 10px 10px, 20px 20px}
33% {background-position:-30px 0, 10px 10px, 20px 20px}
66% {background-position:-30px 0,-20px 10px, 20px 20px}
100% {background-position:-30px 0,-20px 10px,-10px 20px}
}
.infinity-8 {
width:60px;
aspect-ratio:1;
--g: conic-gradient(from -90deg at 10px 10px,#fff 90deg,#0000 0);
background: var(--g), var(--g), var(--g);
background-size: 50% 50%;
animation:i8 1s infinite;
}
@keyframes i8 {
0% {background-position:0 0, 10px 10px, 20px 20px}
33% {background-position:-30px 0, 10px 10px, 20px 20px}
66% {background-position:-30px 0, 10px 40px, 20px 20px}
100% {background-position:-30px 0, 10px 40px, 50px 20px}
}
.infinity-9 {
width:60px;
aspect-ratio:1;
--g: conic-gradient(from -90deg at 10px 10px,#fff 90deg,#0000 0);
background: var(--g), var(--g), var(--g);
background-size: 50% 50%;
animation:i9 1s infinite;
}
@keyframes i9 {
0% {background-position:0 0, 10px 10px, 20px 20px}
33% {background-position:10px 10px}
66% {background-position:0 20px,10px 10px,20px 0 }
100% {background-position:0 0, 10px 10px, 20px 20px}
}
.infinity-10 {
width:60px;
display:flex;
align-items:flex-start;
aspect-ratio:1;
}
.infinity-10:before,
.infinity-10:after {
content:"";
flex:1;
aspect-ratio:1;
--g: conic-gradient(from -90deg at 10px 10px,#fff 90deg,#0000 0);
background: var(--g), var(--g), var(--g);
filter:drop-shadow(30px 30px 0 #fff);
animation:i10 1s infinite;
}
.infinity-10:after {
transform:scaleX(-1);
}
@keyframes i10 {
0% {background-position:0 0, 10px 10px, 20px 20px}
33% {background-position:10px 10px}
66% {background-position:0 20px,10px 10px,20px 0 }
100% {background-position:0 0, 10px 10px, 20px 20px}
}
@@ -0,0 +1,8 @@
import { BaseElement } from '../../../shared/_base';
import './loading-spinner.less';
export class LoadingSpinner extends BaseElement {
onInit(): void {
this.classList.add("infinity-5");
}
}
@@ -7,7 +7,7 @@ output-frame {
}
output-frame > iframe {
position: relative;
position: absolute;
width: 100%;
height: 100%;
border: none;
@@ -21,27 +21,37 @@ export class OutputFrame extends BaseElement {
onUpdate(): void {
}
reset(onload: ((ev: Event) => void) | null = null, resetNotificationBubbles: boolean = true) {
reset(onload: ((ev: Event) => void) | null = null, resetNotificationBubbles: boolean = true, source: string | null = null) {
if (this.CurrentIframeScriptRemoveResponseListener != null) {
this.CurrentIframeScriptRemoveResponseListener();
this.CurrentIframeScriptRemoveResponseListener = null;
}
if (this.Iframe && this.hasChild(this.Iframe)) {
this.Iframe.setAttribute("sandbox", "allow-pointer-lock allow-same-origin");
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();
if (onload) {
this.Iframe.onload = ((ev) => {
onload(ev);
if (this.Iframe)
this.Iframe.onload = () => { };
});
}
if (source != null) {
this.Iframe.src = source;
}
else {
this.Iframe.src = GetOutputFrameUrl();
}
if (source !== "") {
this.appendChild(this.Iframe);
}
if (resetNotificationBubbles) {
var bubbles = GetNotificationBubbles();
@@ -53,9 +63,12 @@ export class OutputFrame extends BaseElement {
setError() {
this.reset(() => {
if (this.Iframe)
this.Iframe.src = GetOutputFrameUrl() + "execution-time-error.html";
});
console.log("ERROR RESET");
if (this.Iframe) {
// this.Iframe.contentWindow?.location.reload();
}
}, true, "");
}
setContent(language: string, value: string) {
@@ -72,7 +85,8 @@ export class OutputFrame extends BaseElement {
}
doPostMessage(language: string, value: string) {
this.CurrentIframeScriptRemoveResponseListener = postMessage(this.Iframe?.contentWindow, language, this.contents[language], language == 'javascript' ? (executeTimeInMs, exceedTimeInMs) => {
this.CurrentIframeScriptRemoveResponseListener = postMessage(this.Iframe?.contentWindow, language, this.contents[language], language == 'javascript' ?
(executeTimeInMs, exceedTimeInMs) => {
if (executeTimeInMs === -1) {
this.setError();
}
@@ -34,6 +34,13 @@ resize-handle[dir="vertical"][place='top'] {
bottom: auto;
}
resize-handle[dir="horizontal"][place='right'] {
top: 0;
bottom: 0;
right: 0;
left: auto;
}
#fixed-resize-overlay {
position: fixed;
left: 0;
@@ -0,0 +1,131 @@
@import url('../../../shared/theme.less');
session-list-item {
color: @primary-text-color-white;
padding: 10px;
display: block;
position: relative;
border-bottom: 1px solid;
border-color: @primary-bg-color-white;
cursor: pointer;
}
session-list-item.selected {
background-color: lighten(@primary-interaction-highlight-white, 30%);
}
session-list-item.selected .slider-wrap {
display: block;
}
.session-list-item-session-id::before {
content: "";
}
.session-list-item-timestamp::before {
content: "Created: ";
}
.session-list-item-timestamp {
font-size: 12px;
opacity: 0.9;
padding-left: 3px;
}
.session-list-item-session-id {
display: block;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
margin-bottom: 3px;
}
.session-list-item-session-id > span {
display: inline-block;
font-size: 9px;
padding: 2px 4px;
background-color: @editor-bg-white;
border: 1px solid;
border-color: darken(@editor-bg-white, 10%);
border-radius: 3px;
opacity: 0.7;
}
.session-list-item-number-of-changes {
font-size: 12px;
opacity: 0.6;
padding-left: 3px;
}
.session-list-item-number-of-changes::before {
content: "Number of changes: ";
position: relative;
z-index: 0;
}
.slider-wrap {
display: none;
margin-top: 3px;
}
.slider-wrap input {
width: 100%; /* Width of the outside container */
}
/* The slider itself */
.slider-wrap input {
-webkit-appearance: none; /* Override default CSS styles */
appearance: none;
width: 100%; /* Full-width */
height: 25px; /* Specified height */
background: @editor-bg-white; /* Grey background */
outline: none; /* Remove outline */
opacity: 0.7; /* Set transparency (for mouse-over effects on hover) */
-webkit-transition: .2s; /* 0.2 seconds transition on hover */
transition: opacity .2s;
border-radius: 3px;
}
/* Mouse-over effects */
.slider-wrap input:hover {
opacity: 1; /* Fully shown on mouse-over */
}
/* The slider handle (use -webkit- (Chrome, Opera, Safari, Edge) and -moz- (Firefox) to override default look) */
.slider-wrap input::-webkit-slider-thumb {
-webkit-appearance: none; /* Override default look */
appearance: none;
width: 25px; /* Set a specific slider handle width */
height: 25px; /* Slider handle height */
background: @primary-interaction-highlight-white; /* Green background */
cursor: pointer; /* Cursor on hover */
border-radius: 3px;
}
.slider-wrap input::-moz-range-thumb {
width: 25px; /* Set a specific slider handle width */
height: 25px; /* Slider handle height */
background: @primary-interaction-highlight-white; /* Green background */
cursor: pointer; /* Cursor on hover */
border-radius: 3px;
}
@media (prefers-color-scheme: dark) {
session-list-item {
border-color: @primary-bg-color-dark;
color: @primary-text-color-dark;
}
session-list-item.selected {
background-color: @primary-interaction-highlight-dark;
}
.slider-wrap input {
background: @editor-bg-dark; /* Grey background */
}
.session-list-item-session-id > span {
background-color: @editor-bg-dark;
border-color: lighten(@editor-bg-dark, 10%);
}
}
@@ -0,0 +1,216 @@
import { BaseElement } from '../../../shared/_base';
import { CodeEditorContainer } from '../code-editor-container/code-editor-container';
import './session-list-item.less';
import * as sessionApi from '../../../shared/session-api';
import { formatDate } from '../../../shared/date-helper';
import { navigateTo } from '../../../shared/navigation';
import { getSessionDetailsId } from '../../../shared/page';
export class SessionListItem extends BaseElement {
historyIndex: number | null = null;
onInit(): void {
if (this.hasAttribute("sessionId")) {
var sessionIdEle = document.createElement("div");
sessionIdEle.classList.add("session-list-item-session-id");
sessionIdEle.innerHTML = "<span>" + (this.getAttribute("sessionId")?.toString() ?? "") + "</span>";
this.appendChild(sessionIdEle);
}
if (this.hasAttribute("timestamp")) {
var timestampEle = document.createElement("div");
timestampEle.classList.add("session-list-item-timestamp");
timestampEle.innerHTML = formatDate(this.getAttribute("timestamp")?.toString() ?? "");
this.appendChild(timestampEle);
}
if (this.hasAttribute("numberOfChanges")) {
var numberOfChangesEle = document.createElement("div");
numberOfChangesEle.classList.add("session-list-item-number-of-changes");
numberOfChangesEle.innerHTML = this.getNumberOfChanges().toString();
this.appendChild(numberOfChangesEle);
}
this.addSlider(this.getNumberOfChanges());
this.addEventListener("click", (evt) => {
this.itemClicked();
});
if (this.isUrlSelected()) {
this.itemClicked();
}
}
getNumberOfChanges() {
return parseInt(this.getAttribute("numberOfChanges")?.toString() ?? "0");
}
updateNumberOfChanges(numberOfChanges: number) {
this.setAttribute("numberOfChanges", numberOfChanges.toString());
this.getElementsByClassName("session-list-item-number-of-changes")[0].innerHTML = (this.historyIndex !== null ? (this.historyIndex + 1) + " / " : "") + numberOfChanges.toString();
this.updateSlider(numberOfChanges);
}
private hasSliderChanges: boolean = false;
private addSlider(numberOfChanges: number) {
var sliderEle = document.createElement("div");
sliderEle.classList.add("slider-wrap");
var sliderInput = document.createElement("input");
sliderInput.setAttribute("min", "0");
sliderInput.setAttribute("max", (numberOfChanges - 1).toString());
sliderInput.setAttribute("type", "range");
sliderInput.setAttribute("value", (numberOfChanges - 1).toString());
sliderInput.value = (numberOfChanges - 1).toString();
sliderInput.addEventListener("input", (evt) => {
this.hasSliderChanges = true;
evt.preventDefault();
evt.stopPropagation();
evt.stopImmediatePropagation();
this.historyIndex = parseInt((<any>evt).target.value);
if (this.historyIndex != null && this.historyIndex >= this.getNumberOfChanges() - 1) {
this.historyIndex = null;
}
this.updateNumberOfChanges(this.getNumberOfChanges());
this.updateInputs(true);
});
sliderEle.appendChild(sliderInput);
this.appendChild(sliderEle);
}
private updateSlider(numberOfChanges: number) {
var sliderInput = <HTMLInputElement>this.querySelector(".slider-wrap input");
if (sliderInput) {
sliderInput.setAttribute("max", (numberOfChanges - 1).toString());
if (this.historyIndex == null) {
sliderInput.setAttribute("value", (numberOfChanges - 1).toString());
sliderInput.value = (numberOfChanges - 1).toString();
}
}
}
private intervalHandle: number | null = null;
stopUpdate() {
this.classList.remove("selected");
this.historyIndex = null;
if (this.intervalHandle) {
this.updateNumberOfChanges(this.getNumberOfChanges());
if (this.isUrlSelected()) {
navigateTo("/sessions");
}
window.clearInterval(this.intervalHandle);
this.intervalHandle = null;
}
}
isUrlSelected(): boolean {
var selectedId = getSessionDetailsId();
return selectedId === this.getAttribute("sessionId")?.toString()?? "";
}
startUpdate() {
this.stopUpdate();
this.classList.add("selected");
this.updateInputs();
this.intervalHandle = window.setInterval(this.updateInputs.bind(this), 1000);
}
updateInputs(fromCache: boolean = false) {
var codeEditorHTMLContainer = <CodeEditorContainer>document.querySelector("code-editor-container[language='HTML']");
var codeEditorCSSContainer = <CodeEditorContainer>document.querySelector("code-editor-container[language='CSS']");
var codeEditorJavascriptContainer = <CodeEditorContainer>document.querySelector("code-editor-container[language='Javascript']");
this.getSessionData((js, html, css) => {
codeEditorJavascriptContainer.setInput(js?.data ?? "");
codeEditorHTMLContainer.setInput(html?.data ?? "");
codeEditorCSSContainer.setInput(css?.data ?? "");
}, fromCache);
}
private sessionDataCache: any[] = [];
getSessionData(onGet: (javascript: any, html: any, css: any) => void, fromCache: boolean = false) {
if (fromCache) {
const res = this.findLastContent(this.sessionDataCache);
onGet(res.lastJavascript, res.lastHtml, res.lastCSS);
this.updateNumberOfChanges(this.sessionDataCache.length);
}
else {
sessionApi.getSessionData(this.getAttribute("sessionId")?.toString()?? "", (data) => {
this.sessionDataCache = data;
const res = this.findLastContent(data);
onGet(res.lastJavascript, res.lastHtml, res.lastCSS);
this.updateNumberOfChanges(data.length);
});
}
}
private findLastContent(data: any[]) {
var lastJavascript: any | null = null;
var lastHtml: any | null = null;
var lastCSS: any | null = null;
for (var i = (this.historyIndex != null ? (this.historyIndex + 1) : data.length) - 1; i >= 0; i--) {
var dataPoint = data[i];
if (!lastJavascript && dataPoint.type == 0) {
lastJavascript = dataPoint;
}
else if (!lastHtml && dataPoint.type == 1) {
lastHtml = dataPoint;
}
else if (!lastCSS && dataPoint.type == 2) {
lastCSS = dataPoint;
}
}
return { lastJavascript, lastHtml, lastCSS };
}
itemClicked() {
if (!this.hasSliderChanges) {
if (!this.intervalHandle) {
var containers = <HTMLElement>document.getElementsByClassName("code-editor-containers")[0];
var outputframe = <HTMLElement>document.getElementsByClassName("output-frame-container")[0];
containers.style.display = "block";
outputframe.style.display = "block";
navigateTo("/sessions/" + this.getAttribute("sessionId")?.toString()?? "");
var children = this.parentElement?.children;
if (children) {
for (let i = 0; i < children.length; i++) {
const child = <SessionListItem>children[i];
if (child != this) {
child.stopUpdate();
}
else {
child.startUpdate();
}
}
}
}
else {
this.stopUpdate();
}
}
else {
this.hasSliderChanges = false;
}
}
}
@@ -0,0 +1,15 @@
@import url('../../../shared/theme.less');
session-list:empty::before {
display: block;
position: relative;
padding: 10px;
color: @primary-text-color-dark;
content: "There is no sessions within the last 30 days";
}
@media (prefers-color-scheme: dark) {
session-list {
color: @primary-text-color-white;
}
}
@@ -0,0 +1,48 @@
import { BaseElement } from '../../../shared/_base';
import './session-list.less';
import * as sessionApi from '../../../shared/session-api';
import { isSessionPage } from '../../../shared/page';
export class SessionList extends BaseElement {
onInit(): void {
if (isSessionPage()) {
var loadingSpinner = document.createElement("loading-spinner");
this.appendChild(loadingSpinner);
this.setUpdate();
setInterval(() => {
this.setUpdate();
}, 5000);
}
}
setUpdate() {
sessionApi.getSessions((sessions) => {
var loadingSpinner = this.getElementsByTagName("loading-spinner")[0];
if (loadingSpinner) {
this.removeChild(loadingSpinner);
}
for (let session of sessions) {
var exists = document.querySelector("session-list-item[sessionId='" + session.id + "']");
if (!exists) {
var ele = document.createElement("session-list-item");
ele.setAttribute("sessionId", session.id);
ele.setAttribute("numberOfChanges", session.numberOfChanges);
ele.setAttribute("timestamp", session.timestamp);
this.appendChild(ele);
}
//do this in item instead:
else {
(<any>exists).updateNumberOfChanges(session.numberOfChanges);
}
}
});
}
}
@@ -51,7 +51,6 @@ export class WindowControlConnector extends BaseElement {
if (distTarget) {
var visibleTargetChildren = this.visibleChildren(distTarget);
console.log(visibleTargetChildren);
if (visibleTargetChildren.length === 0) {
distTarget.classList.add("force-close");
+1
View File
@@ -1 +1,2 @@
<render-canvas></render-canvas>
<loading-spinner></loading-spinner>
+16 -2
View File
@@ -7,14 +7,26 @@ import { messageListener } from '../shared/message-listener';
import { postMessage, postResponseMessage } from '../shared/post-message';
import { RenderCanvas } from './elements/render-canvas/render-canvas';
import { getCenter, getHeight, getRandom, getWidth, rect, box, getRandomColor } from './injects/dom-helpers';
import { setWindowStop } from './injects/window-stop';
import { LoadingSpinner } from './elements/loading-spinner/loading-spinner';
window.customElements.define('loading-spinner', LoadingSpinner);
window.customElements.define('render-canvas', RenderCanvas);
document.body.innerHTML += bootHtml;
console.log("BOOTING");
document.body.innerHTML += bootHtml;
let consoleLimit = 10;
watchConsole((type, value) => {
postMessage(parent, type, value);
if (consoleLimit > 0) {
--consoleLimit;
postMessage(parent, type, value.toString());
}
else if (consoleLimit === 0) {
--consoleLimit;
postMessage(parent, type, "+ more");
}
});
messageListener((type, value, id) => {
if (type == "javascript") {
@@ -46,3 +58,5 @@ window.center = getCenter;
window.rand = getRandom;
//@ts-ignore
window.randomColor = getRandomColor;
setWindowStop();
@@ -0,0 +1,32 @@
loading-spinner {
display: block;
position: absolute;
bottom: 0;
left: 0;
right: 0;
z-index: 1;
opacity: 0.3;
height: 10px;
}
loading-spinner::before {
position: absolute;
content: "";
width: 10px;
height: 100%;
top: 0;
left: 0;
animation-name: loadingAnimation;
animation-duration: 1s;
animation-iteration-count: infinite;
background-color: blue;
}
@keyframes loadingAnimation {
0% {
left: 0;
}
100% {
left: calc(100% - 10px);
}
}
@@ -0,0 +1,7 @@
import { BaseElement } from '../../../shared/_base';
import './loading-spinner.less';
export class LoadingSpinner extends BaseElement {
onInit(): void {
}
}
+13 -13
View File
@@ -1,24 +1,24 @@
import { NotificationBubbles } from "../../codeeditor-app/elements/notification-bubbles/notification-bubbles";
export function watchConsole(fn: (type: 'log' | 'info' | 'warn' | 'error', value: any) => void) {
export function watchConsole(fn: (type: 'log' | 'info' | 'warn' | 'error', ...value: any[]) => void) {
// define a new console
var console: any = (function (oldCons) {
return {
log: function (text: any) {
oldCons.log(text);
fn('log', text);
log: function (...data: any[]) {
oldCons.log(...data);
fn('log', data);
},
info: function (text: any) {
oldCons.info(text);
fn('info', text);
info: function (...data: any[]) {
oldCons.info(...data);
fn('info', data);
},
warn: function (text: any) {
oldCons.warn(text);
fn('warn', text);
warn: function (...data: any[]) {
oldCons.warn(...data);
fn('warn', data);
},
error: function (text: any) {
oldCons.error(text);
fn('error', text);
error: function (...data: any[]) {
oldCons.error(...data);
fn('error', data);
}
};
}(window.console));
+3 -1
View File
@@ -1,4 +1,6 @@
export function setWindowStop() {
setTimeout(() => {
window.stop();
console.log("window stopped");
//throw new Error("window stopped");
}, 4 * 1000);
}
+30
View File
@@ -6,9 +6,39 @@
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>output</title>
<base href="/" />
<style>
body {
font-family: "Segoe UI", Frutiger, "Frutiger Linotype", "Dejavu Sans", "Helvetica Neue", Arial, sans-serif;
}
.center {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 32px;
text-align: center;
opacity: 0.04;
font-weight: 100;
}
small {
font-size: 0.6em;
}
@media (prefers-color-scheme: dark) {
.center {
color: white;
}
}
</style>
</head>
<body>
<div class="center">
codeeditor
</div>
<script type="text/javascript" src="/output.frame.bundle.js?v=1"></script>
</body>
+5281 -3810
View File
File diff suppressed because it is too large Load Diff
+20 -21
View File
@@ -13,33 +13,32 @@
"dependencies": {
},
"devDependencies": {
"monaco-editor-core": "0.33.0",
"monaco-editor": "^0.33.0",
"@types/node": "17.0.21",
"@babel/core": "7.17.8",
"@webcomponents/custom-elements": "1.5.0",
"babel-loader": "8.2.3",
"monaco-editor-core": "0.38.0",
"monaco-editor": "0.38.0",
"@types/node": "20.2.5",
"@babel/core": "7.22.1",
"@webcomponents/custom-elements": "1.6.0",
"babel-loader": "9.1.2",
"babel-plugin-transform-custom-element-classes": "0.1.0",
"babel-preset-env": "1.7.0",
"copy-webpack-plugin": "^10.2.4",
"css-loader": "6.7.1",
"extract-text-webpack-plugin": "3.0.2",
"copy-webpack-plugin": "11.0.0",
"css-loader": "6.8.1",
"file-loader": "6.2.0",
"gulp": "4.0.2",
"html-loader": "3.1.0",
"less": "4.1.2",
"less-loader": "10.2.0",
"npm": "8.5.5",
"open": "8.4.0",
"html-loader": "4.2.0",
"less": "4.1.3",
"less-loader": "11.1.1",
"npm": "9.6.7",
"open": "9.1.0",
"path": "0.12.7",
"replace-in-file-webpack-plugin": "1.0.6",
"style-loader": "3.3.1",
"ts-loader": "9.2.8",
"typescript": "4.6.2",
"webpack": "5.70.0",
"webpack-cli": "4.9.2",
"webpack-dev-server": "4.7.4",
"webpack-merge": "5.8.0",
"style-loader": "3.3.3",
"ts-loader": "9.4.3",
"typescript": "5.0.4",
"webpack": "5.84.1",
"webpack-cli": "5.1.1",
"webpack-dev-server": "4.15.0",
"webpack-merge": "5.9.0",
"raw-loader": "4.0.2",
"monaco-editor-webpack-plugin": "7.0.1"
}
+13
View File
@@ -0,0 +1,13 @@
export function formatDate(date: string): string {
if (date == "") {
return "No date";
}
var d = new Date(date);
return pad(d.getDate()) + "-" + pad(d.getMonth() + 1) + "-" + d.getFullYear() + " " + pad(d.getHours()) + ":" + pad(d.getMinutes());
}
function pad(value: number): string {
var strVal = value.toString();
return strVal.length > 1 ? strVal : "0" + strVal;
}
+29
View File
@@ -0,0 +1,29 @@
.flex-horiz {
display: flex;
flex-direction: row;
height: 100%;
overflow: hidden;
}
.flex-vert {
display: flex;
flex-direction: column;
height: 100%;
}
.flex-horiz > *, .flex-vert > * {
flex-grow: 1;
flex-shrink: 1;
flex-basis: 1px;
position: relative;
}
.flex-horiz .flex-shrink, .flex-vert .flex-shrink {
flex-grow: 0;
flex-shrink: 1;
}
.flex-horiz .flex-grow, .flex-vert .flex-grow {
flex-grow: 1;
flex-shrink: 0;
}
+3
View File
@@ -0,0 +1,3 @@
export function navigateTo(url: string) {
window.history.pushState("", "", url);
}
+15
View File
@@ -0,0 +1,15 @@
export function isSessionPage() {
return document.URL.indexOf("/sessions") != -1;
}
export function isRootPage() {
return document.URL.indexOf("/sessions") == -1;
}
export function isSessionDetailPage() {
return document.URL.indexOf("/sessions") != -1 && document.URL.length > 40;
}
export function getSessionDetailsId() {
return document.URL.length > 40 ? document.URL.substring(document.URL.indexOf("/sessions/") + 10) : "";
}
+17
View File
@@ -0,0 +1,17 @@
export function sanityConvert(code: string, maxLoops: number = 2000) {
var beforeCondition = "var conditionCount = " + maxLoops + "; \n";
var insideCondition = "\n --conditionCount; \n if (conditionCount <= 0) { break; } \n";
const regex = /(?<!function.*)(while|for|if)(\s*\((?:[^()]|\([^()]*\))*\)\s*)(.*?{[^}]*}|.*?;)/gs;
const output = code.replace(regex, (match, p1, p2, p3) => {
let statement = p3.trim();
if (statement.startsWith('{') && statement.endsWith('}')) {
statement = statement.replace('{', '{' + insideCondition);
} else {
statement = statement.endsWith(';') ? statement.slice(0, -1) : statement;
statement = `{${insideCondition}${statement};}`;
}
return `${beforeCondition}${p1}${p2}${statement}`;
});
return output;
}
+113
View File
@@ -0,0 +1,113 @@
var apiUrl = "https://codeeditor-api.davidssoft.com/";
//var apiUrl = "https://codeeditor-api.azurewebsites.net/";
export function hasSession() {
var localstorageSessionId = localStorage.getItem("sessionId");
if (!localstorageSessionId) {
return false;
}
else {
var expiration = localStorage.getItem("sessionIdExpiration");
if (expiration) {
return !sessionIsExpired(expiration);
}
else {
return false;
}
}
}
function getSessionId() {
var localstorageSessionId = localStorage.getItem("sessionId");
if (!localstorageSessionId) {
return setNewSession();
}
else {
var expiration = localStorage.getItem("sessionIdExpiration");
if (expiration) {
if (sessionIsExpired(expiration)) {
return setNewSession();
}
else {
localStorage.setItem("sessionIdExpiration", (new Date()).toString());
return localstorageSessionId;
}
}
else {
return setNewSession();
}
}
}
function sessionIsExpired(expiration: string) {
return dateDiff(new Date(expiration), new Date()) > 120;
}
function setNewSession() {
var newSessionId = uuidv4();
localStorage.setItem("sessionIdExpiration", (new Date()).toString());
localStorage.setItem("sessionId", newSessionId);
return newSessionId;
}
function dateDiff(startTime: Date, endTime: Date) {
var difference = endTime.getTime() - startTime.getTime(); // This will give difference in milliseconds
var resultInMinutes = Math.round(difference / 60000);
return resultInMinutes;
}
function uuidv4() {
//@ts-ignore
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
}
export function getSessions(onGet: (sessions: any[]) => void) {
fetch(apiUrl + "session/all").then((res) => {
res.json().then((jsonRes) => {
onGet(jsonRes as any);
});
});
}
export function getSessionData(sessionId: string, onGet: (sessions: any[]) => void) {
fetch(apiUrl + "session/data/" + sessionId, { method: "GET" }).then((res) => {
res.json().then((jsonRes) => {
onGet(jsonRes as any);
});
});
}
export function setSessionData(language: string, input: any, onSet?: () => void) {
var type = 0;
if (language.toLowerCase() == "javascript") {
type = 0;
}
else if (language.toLowerCase() == "html") {
type = 1;
}
else if (language.toLowerCase() == "css") {
type = 2;
}
else {
type = 3;
}
fetch(apiUrl + "session/set", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ sessionId: getSessionId(), timestamp: new Date(), type: type, data: input }) }).then((res) => {
if (onSet) {
onSet();
}
});
}
+5 -3
View File
@@ -6,11 +6,13 @@ export function IsLocalhost(): boolean {
return location.href.indexOf("://localhost") != -1;
}
export function GetOutputFrameUrl(): string {
export function GetOutputFrameUrl(path: string = ""): string {
var v = "?v=" + (+new Date());
if (IsLocalhost()) {
return "http://127.0.0.1:8021/";
return "http://localhost:8021/" + path + v;
}
else {
return "https://outputframe.davidmeincke.dk/";
return "https://outputframe.davidmeincke.dk/" + path + v;
//return "https://codeeditoroutput.z16.web.core.windows.net/";
}
}