diff --git a/.env.example b/.env.example index 222d580..5087031 100644 --- a/.env.example +++ b/.env.example @@ -19,6 +19,8 @@ COMPILER_CODE_PROCESSING_TIMEOUT=5 COMPILER_REMOTE_INCLUDE_CACHING=true COMPILER_NSJAIL_CFG=/third_party/nsjail-emscripten.cfg +DUSK_START_MAXIMIZED=true + BCRYPT_ROUNDS=12 LOG_CHANNEL=stack diff --git a/.env.test b/.env.test index 3a4a1c4..f84a231 100644 --- a/.env.test +++ b/.env.test @@ -19,6 +19,7 @@ COMPILER_CODE_PROCESSING_TIMEOUT=5 COMPILER_REMOTE_INCLUDE_CACHING=false COMPILER_NSJAIL_CFG=/third_party/nsjail-emscripten-ci.cfg +DUSK_START_MAXIMIZED=true BCRYPT_ROUNDS=12 diff --git a/CHANGELOG.md b/CHANGELOG.md index 2669e99..026553c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file. Each batch It is a summary of changes that would be pertinent to the end user of the PGEtinker website. For a comprehensive history of changes made to the project, please refer to the repository's commit history. +## 2024-05-27 + +- Changed complete revamp of the frontend code, much more organized +- Fixed UI annoyances +- Added Control+S to the Build and Run command +- Added Default editor font size +- Added Control+Mouse Wheel zooming in the editor +- Added Control+0 to reset editor zoom +- Changed Build & Run to Run, that turns into a stop button when the player is running + ## 2024-05-25 - Fixed screenshot failure handling diff --git a/resources/js/app.js b/resources/js/app.js index 025f05b..b35313a 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -1,6 +1,6 @@ import './lib/bootstrap'; import './lib/goldenLayout'; -import './lib/monaco'; +// import './lib/monaco'; import './lib/lucide'; import version from "./lib/version"; import agreeDialog from './lib/agreeDialog'; @@ -9,33 +9,32 @@ import newsDialog from './lib/newsDialog'; import defaultLayout from './lib/defaultLayout'; import supportersDialog from './lib/supportersDialog'; +import ConsolePanel from './components/ConsolePanel'; +import EditorPanel from './components/EditorPanel'; +import InfoPanel from './components/InfoPanel'; +import PlayerPanel from './components/PlayerPanel'; + class PGEtinker { - sharedFlag = false; - lastPlayerHtml = ""; + consolePanel; + editorPanel; + infoPanel; + playerPanel; layoutInitialized = false; compiling = false; layoutConfig = null; - maxFileSize = 50000; - theme = "dark"; - consoleShown = false; - - monacoEditor = null; - monacoModel = null; - monacoModelIntellisense = null; - - consolePanelExist = false; - informationPanelExist = false; - consoleAutoScrollEnabled = true; constructor() { - this.sharedFlag = (window.location.pathname.indexOf("/s/") === 0); - + this.consolePanel = new ConsolePanel(this); + this.editorPanel = new EditorPanel(this); + this.infoPanel = new InfoPanel(this); + this.playerPanel = new PlayerPanel(this); + this.layoutConfig = window.localStorage.getItem("pgetinkerLayout"); this.layoutConfig = (this.layoutConfig !== null) ? JSON.parse(this.layoutConfig) : defaultLayout; @@ -43,9 +42,6 @@ class PGEtinker if(this.theme !== "dark" && this.theme !== "light") this.theme = "dark"; - this.consoleShown = window.localStorage.getItem("pgetinkerConsoleShown"); - this.consoleShown = (this.consoleShown === "true") ? true : false; - // Default Code Button document.querySelector("#default-code").addEventListener("click", (event) => { @@ -53,8 +49,8 @@ class PGEtinker axios.get("/api/default-code").then((response) => { - this.monacoModel.setValue(response.data.code); - this.monacoEditor.revealPositionInCenter({ + this.editorPanel.setValue(response.data.code); + this.editorPanel.reveal({ column: 1, lineNumber: 1, }); @@ -90,7 +86,7 @@ class PGEtinker { event.preventDefault(); - if(!this.lastPlayerHtml.includes("Emscripten-Generated Code")) + if(!this.playerPanel.getHtml().includes("Emscripten-Generated Code")) { alert("You have to build the code before you can download!") return; @@ -99,7 +95,7 @@ class PGEtinker const a = document.createElement('a'); // create the data url - a.href = `data:text/html;base64,${btoa(this.lastPlayerHtml)}`; + a.href = `data:text/html;base64,${btoa(this.playerPanel.getHtml())}`; a.download = "pgetinker.html"; document.body.appendChild(a); @@ -119,7 +115,7 @@ class PGEtinker return; axios.post("/api/share", { - code: this.monacoEditor.getValue() + code: this.editorPanel.getValue() }).then((response) => { shareDialog(response.data.shareURL, response.data.shareThumbURL) @@ -152,43 +148,35 @@ class PGEtinker }); // Compile Button - document.querySelector("#compile").addEventListener("click", (event) => + document.querySelector("#start-stop").addEventListener("click", (event) => { event.preventDefault(); + let startStopElem = document.querySelector("#start-stop"); + let playIconElem = startStopElem.querySelector(".lucide-circle-play"); + let stopIconElem = startStopElem.querySelector(".lucide-circle-stop"); + let spanElem = startStopElem.querySelector("span"); - if(this.compiling) + if(spanElem.innerHTML == "Run") + { + playIconElem.classList.toggle("hidden", true); + stopIconElem.classList.toggle("hidden", false); + spanElem.innerHTML = "Stop"; + this.compile().catch(() => + { + playIconElem.classList.toggle("hidden", false); + stopIconElem.classList.toggle("hidden", true); + spanElem.innerHTML = "Run"; + }); return; + } - if(!this.preCompile()) - return; - - axios.post("/api/compile", { - code: this.monacoEditor.getValue() - }).then((response) => - { - this.compileSuccessHandler(response.data); - }).catch((error) => + if(spanElem.innerHTML == "Stop") { - - if(error.response) - { - if(error.response.status) - { - if(error.response.status == 503) - { - this.compileFailHandler("pgetinker.cpp:1:1: error: PGEtinker service has gone offline. try again later.\n"); - return; - } - } - - if(error.response.data.stderr) - { - this.compileFailHandler(error.response.data.stderr); - return; - } - } - this.compileFailHandler("pgetinker.cpp:1:1: error: compilation failed in a way that's not being handled. please make a bug report.\n"); - }); + this.playerPanel.stop(); + playIconElem.classList.toggle("hidden", false); + stopIconElem.classList.toggle("hidden", true); + spanElem.innerHTML = "Run"; + } }); document.querySelector("#supporters").addEventListener("click", (event) => @@ -203,51 +191,6 @@ class PGEtinker newsDialog(); }); - window.addEventListener("message", (event) => - { - if(typeof event.data !== "object") - return; - - if(typeof event.data.message !== "string") - return; - - if(event.data.message === "player-ready") - { - // update player theme - document.querySelector("#player-panel iframe").contentWindow.postMessage({ - message: "set-theme", - theme: this.theme - }, "*"); - - // update player theme - document.querySelector("#player-panel iframe").contentWindow.postMessage({ - message: "show-console", - value: this.consoleShown - }, "*"); - } - - if(event.data.message === "console-output") - { - if(!this.informationPanelExist) - return; - - - let consoleContainer = document.querySelector("#console-panel"); - consoleContainer.innerHTML += `
${event.data.data}
`; - - // auto scroll - if(this.consoleAutoScrollEnabled) - consoleContainer.scrollTop = consoleContainer.scrollHeight; - - let consolePanel = this.layout.root.getItemsById('console')[0]; - if(consolePanel.parent.isStack) - { - consolePanel.parent.setActiveContentItem(consolePanel); - } - } - - }); - let agreedToTerms = window.localStorage.getItem("pgetinkerAgreedToTerms"); agreedToTerms = (agreedToTerms == null) ? false : JSON.parse(agreedToTerms); @@ -276,174 +219,92 @@ class PGEtinker preCompile() { - if(this.monacoEditor.getValue().length > this.maxFileSize) + if(this.editorPanel.exceedsMaxSize()) { alert("Maximum size exceeded!"); return false; } - if(this.informationPanelExist) - { - let infoPanel = this.layout.root.getItemsById('info')[0]; - if(infoPanel.parent.isStack) - { - infoPanel.parent.setActiveContentItem(infoPanel); - } - - document.querySelector("#info-panel").innerHTML = ""; - document.querySelector("#console-panel").innerHTML = ""; - } + this.infoPanel.focus(); + this.infoPanel.clear(); + this.consolePanel.clear(); + this.editorPanel.clearMarkers(); + this.playerPanel.setCompiling(); + this.compiling = true; + return true; + } - this.lastPlayerHtml = ""; - let playerFrame = document.querySelector("#player-panel iframe"); + compile() + { + if(this.compiling) + return new Promise((_, reject) => reject()); + + if(!this.preCompile()) + return new Promise((_, reject) => reject()); - if(playerFrame != null) - playerFrame.remove(); + return new Promise((resolve, reject) => + { + axios.post("/api/compile", { + code: this.editorPanel.getValue() + }).then((response) => + { + this.compileSuccessHandler(response.data); + resolve(); + }).catch((error) => + { + + if(error.response) + { + if(error.response.status) + { + if(error.response.status == 503) + { + this.compileFailHandler("pgetinker.cpp:1:1: error: PGEtinker service has gone offline. try again later.\n"); + reject(); + return; + } + } + + if(error.response.data.stderr) + { + this.compileFailHandler(error.response.data.stderr); + reject(); + return; + } + } + this.compileFailHandler("pgetinker.cpp:1:1: error: compilation failed in a way that's not being handled. please make a bug report.\n"); + reject(); + }); + }); - document.querySelector("#player-panel .compiling").classList.toggle("display-flex", true); - document.querySelector("#player-panel .compiling-failed").classList.toggle("display-flex", false); - - monaco.editor.removeAllMarkers("owner"); - this.monacoEditor.trigger("", "closeMarkersNavigation"); - - return true; } compileSuccessHandler(data) { - this.lastPlayerHtml = data.html; - - let playerFrame = document.createElement('iframe'); - playerFrame.setAttribute("srcdoc", this.lastPlayerHtml); - playerFrame.setAttribute("sandbox", "allow-scripts"); - document.querySelector("#player-panel .iframe-container").append(playerFrame); - - playerFrame.classList.toggle("display-block", true); - document.querySelector("#player-panel .compiling").classList.toggle("display-flex", false); - document.querySelector("#player-panel .compiling-failed").classList.toggle("display-flex", false); - + this.playerPanel.setHtml(data.html); this.compiling = false; } compileFailHandler(stderr) { - let infoPanel = document.querySelector("#info-panel"); - infoPanel.innerHTML = `
${stderr}
`; - infoPanel.scrollTop = infoPanel.scrollHeight; + this.infoPanel.setContent(stderr); + this.editorPanel.extractAndSetMarkers(stderr); - const compilerRegex = /pgetinker.cpp:(\d+):(\d+): (fatal error|error|warning|note): (.*)/gm; - const linkerRegex = /wasm-ld: error: pgetinker.o: (.*): (.*)/gm; - - let markers = []; - - let matches; - - while((matches = compilerRegex.exec(stderr)) !== null) - { - let severity = monaco.MarkerSeverity.Error; - - if(matches[3] == "warning") - severity = monaco.MarkerSeverity.Warning; - - if(matches[3] == "note") - severity = monaco.MarkerSeverity.Info; - - markers.push({ - message: matches[4], - severity: severity, - startLineNumber: parseInt(matches[1]), - startColumn: parseInt(matches[2]), - endLineNumber: parseInt(matches[1]), - endColumn: this.monacoModel.getLineLength(parseInt(matches[1])), - source: "Emscripten Compiler", - }); - } - - while((matches = linkerRegex.exec(stderr)) !== null) - { - markers.push({ - message: `${matches[1]} ${matches[2]}`, - severity: monaco.MarkerSeverity.Error, - startLineNumber: 1, - startColumn: 1, - endLineNumber: 1, - endColumn: this.monacoModel.getLineLength(1), - source: "Emscripten Linker", - }); - } - - // show errors in the editor, if they exist - if(markers.length > 0) - { - monaco.editor.setModelMarkers(this.monacoModel, "owner", markers); - this.monacoEditor.setPosition({lineNumber: markers[0].startLineNumber, column: markers[0].startColumn }); - setTimeout(() => { this.monacoEditor.trigger("", "editor.action.marker.next"); }, 50); - } - - document.querySelector("#player-panel .compiling").classList.toggle("display-flex", false); - document.querySelector("#player-panel .compiling-failed").classList.toggle("display-flex", true); + this.playerPanel.setCompilingFailed(); this.compiling = false; } SetupLayout() { this.layout = new GoldenLayout(this.layoutConfig, document.querySelector("#content")) - - this.layout.registerComponent('consoleComponent', function(container) - { - container.getElement().html(` -
- - `); - }); - - this.layout.registerComponent('infoComponent', function(container) - { - container.getElement().html(` -
-
- `); - }); - - this.layout.registerComponent('playerComponent', function(container) - { - container.getElement().html(` -
-
-
-
-
-
-
-
-
-
-

- Compiling -

-
-
- -

- Compile Failed. -

-
-
- `); - }); - - this.layout.registerComponent('editorComponent', function(container) - { - container.getElement().html(` -
-
-
Loading
-
- `); - }); + this.consolePanel.register(); + this.editorPanel.register(); + this.infoPanel.register(); + this.playerPanel.register(); + this.layout.on("stateChanged", () => { if(this.layoutInitialized) @@ -452,115 +313,14 @@ class PGEtinker this.layout.on("initialised", () => { - this.informationPanelExist = (this.layout.root.getItemsById('info').length > 0); - this.consolePanelExist = (this.layout.root.getItemsById('console').length > 0); - this.layoutInitialized = true; window.addEventListener("resize", (event) => this.layout.updateSize()); - if(this.monacoModel === null) - { - this.monacoModel = monaco.editor.createModel("", "cpp", monaco.Uri.parse("inmemory://pgetinker.cpp")); - - let codeBox = document.querySelector("#code"); - if(codeBox.value !== "") - { - this.monacoModel.setValue(document.querySelector("#code").value); - window.localStorage.setItem("pgetinkerCode", JSON.stringify(document.querySelector("#code").value)); - } - else - { - let code = window.localStorage.getItem("pgetinkerCode"); - code = (code !== null) ? JSON.parse(code) : ""; - - if(code === "") - { - axios.get("/api/default-code").then((response) => - { - this.monacoModel.setValue(response.data.code); - }).catch((reason) => console.log(reason)); - } - else - { - this.monacoModel.setValue(code); - } - } - } - - if(this.monacoModelIntellisense === null) - { - this.monacoModelIntellisense = monaco.editor.createModel("", "cpp", monaco.Uri.parse("inmemory://pgetinker.h")); - axios.get("/api/model/v0.02").then((response) => - { - this.monacoModelIntellisense.setValue(response.data); - }); - } - - this.monacoEditor = monaco.editor.create(document.querySelector('#editor-panel .code-editor'), { - automaticLayout: true, - model: this.monacoModel, - theme: `vs-${this.theme}`, - }); - - this.monacoEditor.addAction({ - id: 'build-and-run', - label: 'Build and Run', - keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter], - run: () => - { - document.querySelector("#compile").dispatchEvent(new Event("click")); - } - }); - - this.monacoEditor.onDidChangeCursorPosition(() => this.UpdateStatusBar()); + this.consolePanel.onInit(); + this.editorPanel.onInit(); + this.infoPanel.onInit(); + this.playerPanel.onInit(); - this.monacoEditor.onDidChangeModelContent(() => - { - window.localStorage.setItem("pgetinkerCode", JSON.stringify(this.monacoEditor.getValue())); - - if(this.sharedFlag) - { - window.history.replaceState({}, "", "/"); - } - }); - - if(this.lastPlayerHtml != "") - { - let playerFrame = document.createElement('iframe'); - playerFrame.setAttribute("srcdoc", this.lastPlayerHtml); - playerFrame.setAttribute("sandbox", "allow-scripts"); - document.querySelector("#player-panel .iframe-container").append(playerFrame); - - playerFrame.classList.toggle("display-block", true); - document.querySelector("#player-panel .compiling").classList.toggle("display-flex", false); - document.querySelector("#player-panel .compiling-failed").classList.toggle("display-flex", false); - } - - let consoleContainer = document.querySelector("#console-panel"); - - document.querySelector("#console-auto-scroll").addEventListener("click", () => - { - this.consoleAutoScrollEnabled = true; - document.querySelector("#console-auto-scroll").classList.toggle("hidden", this.consoleAutoScrollEnabled); - }); - - consoleContainer.addEventListener("wheel", (event) => - { - let nearBottom = ((consoleContainer.scrollHeight - consoleContainer.clientHeight) <= (consoleContainer.scrollTop + 1)); - - if(nearBottom) - { - // up - if(event.deltaY < 0) - { - this.consoleAutoScrollEnabled = false; - consoleContainer.scrollTop = consoleContainer.scrollHeight - 20; - document.querySelector("#console-auto-scroll").classList.toggle("hidden", this.consoleAutoScrollEnabled); - } - } - }); - - this.UpdateStatusBar(); this.UpdateTheme(); }); @@ -580,30 +340,6 @@ class PGEtinker } - UpdateStatusBar() - { - let statusBar = document.querySelector("#editor-panel .status"); - - let cursor = `Ln ${this.monacoEditor.getPosition().lineNumber}, Col ${this.monacoEditor.getPosition().column}`; - let fileSize = `${new Intl.NumberFormat().format(this.monacoEditor.getValue().length)} / ${new Intl.NumberFormat().format(this.maxFileSize)}`; - - statusBar.classList.toggle('too-fucking-big', false); - if(this.monacoModel.getValueLength() > this.maxFileSize) - { - statusBar.classList.toggle('too-fucking-big', true); - fileSize += " EXCEEDING MAXIMUM!"; - } - - statusBar.innerHTML = ` -
- Bytes: ${fileSize} -
-
- ${cursor} -
- `; - } - UpdateTheme() { // update overall theme @@ -626,22 +362,18 @@ class PGEtinker } // update editor theme - if(this.monacoEditor !== null) - this.monacoEditor.updateOptions({ theme: `vs-${this.theme}`}); + this.editorPanel.setTheme(this.theme); // update player theme - let playerFrame = document.querySelector("#player-panel iframe"); - if(playerFrame != null) - { - document.querySelector("#player-panel iframe").contentWindow.postMessage({ - message: "set-theme", - theme: this.theme - }, "*"); - } - + this.playerPanel.setTheme(this.theme); + // save theme into localStorage window.localStorage.setItem("pgetinkerTheme", this.theme); } } new PGEtinker(); + + + + diff --git a/resources/js/components/ConsolePanel.js b/resources/js/components/ConsolePanel.js new file mode 100644 index 0000000..0e13985 --- /dev/null +++ b/resources/js/components/ConsolePanel.js @@ -0,0 +1,103 @@ + +export default class ConsolePanel +{ + consoleShown = false; + consolePanelExist = false; + consoleAutoScrollEnabled = true; + + state; + + constructor(state) + { + this.state = state; + console.log("Console panel", "constructor"); + + this.consoleShown = window.localStorage.getItem("pgetinkerConsoleShown"); + this.consoleShown = (this.consoleShown === "true") ? true : false; + + window.addEventListener("message", (event) => + { + if(typeof event.data !== "object") + return; + + if(typeof event.data.message !== "string") + return; + + if(event.data.message === "console-output") + { + if(!this.state.infoPanel.exists()) + return; + + + let consoleContainer = document.querySelector("#console-panel"); + consoleContainer.innerHTML += `
${event.data.data}
`; + + // auto scroll + if(this.consoleAutoScrollEnabled) + consoleContainer.scrollTop = consoleContainer.scrollHeight; + + let consolePanel = this.state.layout.root.getItemsById('console')[0]; + if(consolePanel.parent.isStack) + { + consolePanel.parent.setActiveContentItem(consolePanel); + } + } + }); + } + + clear() + { + document.querySelector("#console-panel").innerHTML = ""; + } + + exists() + { + return this.consolePanelExist; + } + + onInit() + { + this.consolePanelExist = (this.state.layout.root.getItemsById('console').length > 0); + + let consoleContainer = document.querySelector("#console-panel"); + + document.querySelector("#console-auto-scroll").addEventListener("click", () => + { + this.consoleAutoScrollEnabled = true; + document.querySelector("#console-auto-scroll").classList.toggle("hidden", this.consoleAutoScrollEnabled); + }); + + consoleContainer.addEventListener("wheel", (event) => + { + let nearBottom = ((consoleContainer.scrollHeight - consoleContainer.clientHeight) <= (consoleContainer.scrollTop + 1)); + + if(nearBottom) + { + // up + if(event.deltaY < 0) + { + this.consoleAutoScrollEnabled = false; + consoleContainer.scrollTop = consoleContainer.scrollHeight - 20; + document.querySelector("#console-auto-scroll").classList.toggle("hidden", this.consoleAutoScrollEnabled); + } + } + }); + } + + register() + { + this.state.layout.registerComponent('consoleComponent', function(container) + { + container.getElement().html(` +
+ + `); + }); + } + + shown() + { + return this.consoleShown; + } + +} \ No newline at end of file diff --git a/resources/js/components/EditorPanel.js b/resources/js/components/EditorPanel.js new file mode 100644 index 0000000..f09189d --- /dev/null +++ b/resources/js/components/EditorPanel.js @@ -0,0 +1,251 @@ +import * as monaco from 'monaco-editor'; +import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'; + +self.MonacoEnvironment = { + getWorker: function (workerId, label) + { + return editorWorker(); + } +}; +monaco.languages.typescript.javascriptDefaults.setEagerModelSync(true); +monaco.languages.typescript.javascriptDefaults.setCompilerOptions({ + noLib: true, + allowNonTsExtensions: true +}); + +export default class EditorPanel +{ + state; + + monacoEditor = null; + monacoModel = null; + monacoModelIntellisense = null; + + maxFileSize = 50000; + + sharedFlag = false; + + constructor(state) + { + this.state = state; + this.sharedFlag = (window.location.pathname.indexOf("/s/") === 0); + console.log("Editor panel", "constructor"); + } + + clearMarkers() + { + monaco.editor.removeAllMarkers("owner"); + this.monacoEditor.trigger("", "closeMarkersNavigation"); + } + + exceedsMaxSize() + { + return this.monacoEditor.getValue().length > this.maxFileSize; + } + + extractAndSetMarkers(data) + { + const compilerRegex = /pgetinker.cpp:(\d+):(\d+): (fatal error|error|warning|note): (.*)/gm; + const linkerRegex = /wasm-ld: error: pgetinker.o: (.*): (.*)/gm; + + let markers = []; + + let matches; + + while((matches = compilerRegex.exec(data)) !== null) + { + let severity = monaco.MarkerSeverity.Error; + + if(matches[3] == "warning") + severity = monaco.MarkerSeverity.Warning; + + if(matches[3] == "note") + severity = monaco.MarkerSeverity.Info; + + markers.push({ + message: matches[4], + severity: severity, + startLineNumber: parseInt(matches[1]), + startColumn: parseInt(matches[2]), + endLineNumber: parseInt(matches[1]), + endColumn: this.monacoModel.getLineLength(parseInt(matches[1])), + source: "Emscripten Compiler", + }); + } + + while((matches = linkerRegex.exec(data)) !== null) + { + markers.push({ + message: `${matches[1]} ${matches[2]}`, + severity: monaco.MarkerSeverity.Error, + startLineNumber: 1, + startColumn: 1, + endLineNumber: 1, + endColumn: this.monacoModel.getLineLength(1), + source: "Emscripten Linker", + }); + } + + // show errors in the editor, if they exist + if(markers.length > 0) + { + this.setMarkers(markers); + } + } + + getValue() + { + return this.monacoEditor.getValue(); + } + setValue(value) + { + this.monacoModel.setValue(value); + + } + onInit() + { + if(this.monacoModel === null) + { + this.monacoModel = monaco.editor.createModel("", "cpp", monaco.Uri.parse("inmemory://pgetinker.cpp")); + + let codeBox = document.querySelector("#code"); + if(codeBox.value !== "") + { + this.monacoModel.setValue(document.querySelector("#code").value); + window.localStorage.setItem("pgetinkerCode", JSON.stringify(document.querySelector("#code").value)); + } + else + { + let code = window.localStorage.getItem("pgetinkerCode"); + code = (code !== null) ? JSON.parse(code) : ""; + + if(code === "") + { + axios.get("/api/default-code").then((response) => + { + this.monacoModel.setValue(response.data.code); + }).catch((reason) => console.log(reason)); + } + else + { + this.monacoModel.setValue(code); + } + } + } + + if(this.monacoModelIntellisense === null) + { + this.monacoModelIntellisense = monaco.editor.createModel("", "cpp", monaco.Uri.parse("inmemory://pgetinker.h")); + axios.get("/api/model/v0.02").then((response) => + { + this.monacoModelIntellisense.setValue(response.data); + }); + } + + this.monacoEditor = monaco.editor.create(document.querySelector('#editor-panel .code-editor'), { + automaticLayout: true, + model: this.monacoModel, + fontSize: 14, + mouseWheelZoom: true, + theme: `vs-${this.state.theme}`, + }); + + this.monacoEditor.addAction({ + id: 'build-and-run', + label: 'Build and Run', + keybindings: [ + monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, + monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS + ], + run: () => + { + document.querySelector("#start-stop").dispatchEvent(new Event("click")); + } + }); + + this.monacoEditor.addAction({ + id: 'reset-editor-zoom', + label: 'Reset Editor Zoom', + keybindings: [ + monaco.KeyMod.CtrlCmd | monaco.KeyCode.Digit0, + ], + run: () => + { + this.monacoEditor.trigger("", "editor.action.fontZoomReset"); + } + }) + + this.monacoEditor.onDidChangeCursorPosition(() => this.updateStatusBar()); + + this.monacoEditor.onDidChangeModelContent(() => + { + window.localStorage.setItem("pgetinkerCode", JSON.stringify(this.monacoEditor.getValue())); + + if(this.sharedFlag) + { + window.history.replaceState({}, "", "/"); + } + }); + + this.updateStatusBar(); + } + + register() + { + this.state.layout.registerComponent('editorComponent', function(container) + { + container.getElement().html(` +
+
+
Loading
+
+ `); + }); + } + + reveal(position) + { + this.monacoEditor.revealPositionInCenter(position); + } + + setMarkers(markers) + { + // set model markers + monaco.editor.setModelMarkers(this.monacoModel, "owner", markers); + // move to first marker + this.monacoEditor.setPosition({lineNumber: markers[0].startLineNumber, column: markers[0].startColumn }); + // trigger activate nearest marker + setTimeout(() => { this.monacoEditor.trigger("", "editor.action.marker.next"); }, 50); + } + + setTheme(theme) + { + if(this.monacoEditor !== null) + this.monacoEditor.updateOptions({ theme: `vs-${theme}`}); + } + + updateStatusBar() + { + let statusBar = document.querySelector("#editor-panel .status"); + + let cursor = `Ln ${this.monacoEditor.getPosition().lineNumber}, Col ${this.monacoEditor.getPosition().column}`; + let fileSize = `${new Intl.NumberFormat().format(this.monacoEditor.getValue().length)} / ${new Intl.NumberFormat().format(this.maxFileSize)}`; + + statusBar.classList.toggle('too-fucking-big', false); + if(this.monacoModel.getValueLength() > this.maxFileSize) + { + statusBar.classList.toggle('too-fucking-big', true); + fileSize += " EXCEEDING MAXIMUM!"; + } + + statusBar.innerHTML = ` +
+ Bytes: ${fileSize} +
+
+ ${cursor} +
+ `; + } + +} \ No newline at end of file diff --git a/resources/js/components/InfoPanel.js b/resources/js/components/InfoPanel.js new file mode 100644 index 0000000..599a613 --- /dev/null +++ b/resources/js/components/InfoPanel.js @@ -0,0 +1,61 @@ + +export default class InfoPanel +{ + state; + + informationPanelExist = false; + + constructor(state) + { + this.state = state; + console.log("Info panel", "constructor"); + } + + + + clear() + { + document.querySelector("#info-panel").innerHTML = ""; + } + + exists() + { + return this.informationPanelExist; + } + + focus() + { + if(this.exists()) + { + let infoPanel = this.state.layout.root.getItemsById('info')[0]; + if(infoPanel.parent.isStack) + { + infoPanel.parent.setActiveContentItem(infoPanel); + } + } + } + + onInit() + { + this.informationPanelExist = (this.state.layout.root.getItemsById('info').length > 0); + } + + register() + { + this.state.layout.registerComponent('infoComponent', function(container) + { + container.getElement().html(` +
+
+ `); + }); + } + + setContent(content) + { + let infoPanel = document.querySelector("#info-panel"); + infoPanel.innerHTML = `
${content}
`; + infoPanel.scrollTop = infoPanel.scrollHeight; + } +} + diff --git a/resources/js/components/PlayerPanel.js b/resources/js/components/PlayerPanel.js new file mode 100644 index 0000000..b8a97fd --- /dev/null +++ b/resources/js/components/PlayerPanel.js @@ -0,0 +1,149 @@ + +export default class PlayerPanel +{ + state; + lastPlayerHtml = ""; + running = false; + + constructor(state) + { + this.state = state; + console.log("Player panel", "constructor"); + window.addEventListener("message", (event) => + { + if(typeof event.data !== "object") + return; + + if(typeof event.data.message !== "string") + return; + + if(event.data.message === "player-ready") + { + // update player theme + document.querySelector("#player-panel iframe").contentWindow.postMessage({ + message: "set-theme", + theme: this.state.theme + }, "*"); + + // update player theme + document.querySelector("#player-panel iframe").contentWindow.postMessage({ + message: "show-console", + value: this.state.consolePanel.shown() + }, "*"); + } + + }); + } + + getHtml() + { + return this.lastPlayerHtml; + } + + isRunning() + { + return this.running; + } + + onInit() + { + if(this.lastPlayerHtml != "") + { + let playerFrame = document.createElement('iframe'); + playerFrame.setAttribute("srcdoc", this.lastPlayerHtml); + playerFrame.setAttribute("sandbox", "allow-scripts"); + document.querySelector("#player-panel .iframe-container").append(playerFrame); + + playerFrame.classList.toggle("display-block", true); + document.querySelector("#player-panel .compiling").classList.toggle("display-flex", false); + document.querySelector("#player-panel .compiling-failed").classList.toggle("display-flex", false); + } + } + + register() + { + this.state.layout.registerComponent('playerComponent', function(container) + { + container.getElement().html(` +
+
+
+
+
+
+
+
+
+
+

+ Compiling +

+
+
+ +

+ Compile Failed. +

+
+
+ `); + }); + } + + setCompiling() + { + this.lastPlayerHtml = ""; + this.stop(); + + document.querySelector("#player-panel .compiling").classList.toggle("display-flex", true); + document.querySelector("#player-panel .compiling-failed").classList.toggle("display-flex", false); + } + + setCompilingFailed() + { + document.querySelector("#player-panel .compiling").classList.toggle("display-flex", false); + document.querySelector("#player-panel .compiling-failed").classList.toggle("display-flex", true); + } + + setHtml(html) + { + this.lastPlayerHtml = html; + this.start(); + } + + setTheme(theme) + { + let iframe = document.querySelector("#player-panel iframe"); + if(iframe != null) + { + iframe.contentWindow.postMessage({ + message: "set-theme", + theme: theme + }, "*"); + } + } + + start() + { + let playerFrame = document.createElement('iframe'); + playerFrame.setAttribute("srcdoc", this.lastPlayerHtml); + playerFrame.setAttribute("sandbox", "allow-scripts"); + document.querySelector("#player-panel .iframe-container").append(playerFrame); + + playerFrame.classList.toggle("display-block", true); + document.querySelector("#player-panel .compiling").classList.toggle("display-flex", false); + document.querySelector("#player-panel .compiling-failed").classList.toggle("display-flex", false); + + this.running = true; + } + + stop() + { + let playerFrame = document.querySelector("#player-panel iframe"); + + if(playerFrame != null) + playerFrame.remove(); + + this.running = false; + } +} diff --git a/resources/js/lib/bootstrap.js b/resources/js/lib/bootstrap.js index 6c064d7..cb7fff8 100644 --- a/resources/js/lib/bootstrap.js +++ b/resources/js/lib/bootstrap.js @@ -6,11 +6,6 @@ window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; window.axios.defaults.withCredentials = true; window.axios.defaults.withXSRFToken = true; -if(window.location.pathname.indexOf("/beta/") == 0) -{ - window.axios.defaults.baseURL = `${window.location.protocol}//${window.location.host}/staging/`; -} - if(window.location.pathname.indexOf("/staging/") == 0) { window.axios.defaults.baseURL = `${window.location.protocol}//${window.location.host}/staging/`; diff --git a/resources/js/lib/lucide.js b/resources/js/lib/lucide.js index 37970de..a0a6820 100644 --- a/resources/js/lib/lucide.js +++ b/resources/js/lib/lucide.js @@ -4,6 +4,7 @@ import { Bug, CircleDollarSign, CirclePlay, + CircleStop, Download, ExternalLink, Github, @@ -20,6 +21,7 @@ createIcons({ BadgePlus, Bug, CirclePlay, + CircleStop, CircleDollarSign, Download, ExternalLink, diff --git a/resources/js/lib/monaco.js b/resources/js/lib/monaco.js deleted file mode 100644 index 110561c..0000000 --- a/resources/js/lib/monaco.js +++ /dev/null @@ -1,37 +0,0 @@ -import * as monaco from 'monaco-editor'; - -import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'; -import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker'; -import htmlWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'; -import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'; -import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'; - -self.MonacoEnvironment = { - getWorker: function (workerId, label) - { - switch (label) { - case 'json': - return new jsonWorker(); - case 'css': - case 'scss': - case 'less': - return cssWorker(); - case 'html': - case 'handlebars': - case 'razor': - return htmlWorker(); - case 'typescript': - case 'javascript': - return tsWorker(); - default: - return editorWorker(); - } - } -}; -monaco.languages.typescript.javascriptDefaults.setEagerModelSync(true); -monaco.languages.typescript.javascriptDefaults.setCompilerOptions({ - noLib: true, - allowNonTsExtensions: true -}); - -window.monaco = monaco; diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index 5ab379d..d4e7903 100644 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -63,9 +63,10 @@ - + - Build & Run + + Run