diff --git a/.env.example b/.env.example index b683e3e..ae0abbc 100644 --- a/.env.example +++ b/.env.example @@ -3,7 +3,7 @@ APP_ENV=local APP_KEY= APP_DEBUG=true APP_TIMEZONE=UTC -APP_URL=http://localhost +APP_URL=http://127.0.0.1:8000 APP_LOCALE=en APP_FALLBACK_LOCALE=en diff --git a/.github/workflows/test-on-pull-request-main.yml b/.github/workflows/test-on-pull-request-main.yml index f03d06e..57c9985 100644 --- a/.github/workflows/test-on-pull-request-main.yml +++ b/.github/workflows/test-on-pull-request-main.yml @@ -13,23 +13,8 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: '20.x' - - - name: Install and Build NodeJS dependencies - run: npm install && npm run build - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.3' - extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite - coverage: none - - - name: Install Composer dependencies - run: composer install --prefer-dist --no-interaction --no-progress + - name: Install Dependencies + run: npm install && npm run build && composer install --prefer-dist --no-interaction --no-progress && php artisan dusk:chrome-driver --detect - name: Copy environment file run: cp .env.example .env @@ -46,6 +31,8 @@ jobs: - name: Initial Database Migration run: php artisan migrate - - name: Execute tests + - name: Execute Unit Tests run: php artisan test - + + - name: Execute Browser Tests + run: php artisan dusk diff --git a/.github/workflows/test-on-push-main.yml b/.github/workflows/test-on-push-main.yml index 7d532a0..11d1fe2 100644 --- a/.github/workflows/test-on-push-main.yml +++ b/.github/workflows/test-on-push-main.yml @@ -13,23 +13,8 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: '20.x' - - - name: Install and Build NodeJS dependencies - run: npm install && npm run build - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.3' - extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite - coverage: none - - - name: Install Composer dependencies - run: composer install --prefer-dist --no-interaction --no-progress + - name: Install Dependencies + run: npm install && npm run build && composer install --prefer-dist --no-interaction --no-progress && php artisan dusk:chrome-driver --detect - name: Copy environment file run: cp .env.example .env @@ -46,6 +31,8 @@ jobs: - name: Initial Database Migration run: php artisan migrate - - name: Execute tests + - name: Execute Unit Tests run: php artisan test - + + - name: Execute Browser Tests + run: php artisan dusk diff --git a/composer.json b/composer.json index 69d4388..23db944 100644 --- a/composer.json +++ b/composer.json @@ -13,6 +13,7 @@ }, "require-dev": { "fakerphp/faker": "^1.23", + "laravel/dusk": "^8.2", "laravel/pint": "^1.13", "laravel/sail": "^1.26", "mockery/mockery": "^1.6", diff --git a/composer.lock b/composer.lock index 6427963..6ea7f9a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "398060acfd420c8e7e5eaeafe9cd15eb", + "content-hash": "98754fa2c3a24cebb2869e901a5b34b7", "packages": [ { "name": "brick/math", @@ -5935,6 +5935,78 @@ }, "time": "2020-07-09T08:09:16+00:00" }, + { + "name": "laravel/dusk", + "version": "v8.2.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/dusk.git", + "reference": "773a12dfbd3f84174b0f26fbc2807a414a379a66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/dusk/zipball/773a12dfbd3f84174b0f26fbc2807a414a379a66", + "reference": "773a12dfbd3f84174b0f26fbc2807a414a379a66", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-zip": "*", + "guzzlehttp/guzzle": "^7.5", + "illuminate/console": "^10.0|^11.0", + "illuminate/support": "^10.0|^11.0", + "php": "^8.1", + "php-webdriver/webdriver": "^1.9.0", + "symfony/console": "^6.2|^7.0", + "symfony/finder": "^6.2|^7.0", + "symfony/process": "^6.2|^7.0", + "vlucas/phpdotenv": "^5.2" + }, + "require-dev": { + "mockery/mockery": "^1.6", + "orchestra/testbench": "^8.19|^9.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.1|^11.0", + "psy/psysh": "^0.11.12|^0.12" + }, + "suggest": { + "ext-pcntl": "Used to gracefully terminate Dusk when tests are running." + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Dusk\\DuskServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Dusk\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel Dusk provides simple end-to-end testing and browser automation.", + "keywords": [ + "laravel", + "testing", + "webdriver" + ], + "support": { + "issues": "https://github.com/laravel/dusk/issues", + "source": "https://github.com/laravel/dusk/tree/v8.2.0" + }, + "time": "2024-04-16T15:51:19+00:00" + }, { "name": "laravel/pint", "version": "v1.15.1", @@ -6421,6 +6493,72 @@ }, "time": "2022-02-21T01:04:05+00:00" }, + { + "name": "php-webdriver/webdriver", + "version": "1.15.1", + "source": { + "type": "git", + "url": "https://github.com/php-webdriver/php-webdriver.git", + "reference": "cd52d9342c5aa738c2e75a67e47a1b6df97154e8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/cd52d9342c5aa738c2e75a67e47a1b6df97154e8", + "reference": "cd52d9342c5aa738c2e75a67e47a1b6df97154e8", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-zip": "*", + "php": "^7.3 || ^8.0", + "symfony/polyfill-mbstring": "^1.12", + "symfony/process": "^5.0 || ^6.0 || ^7.0" + }, + "replace": { + "facebook/webdriver": "*" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.20.0", + "ondram/ci-detector": "^4.0", + "php-coveralls/php-coveralls": "^2.4", + "php-mock/php-mock-phpunit": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpunit/phpunit": "^9.3", + "squizlabs/php_codesniffer": "^3.5", + "symfony/var-dumper": "^5.0 || ^6.0" + }, + "suggest": { + "ext-SimpleXML": "For Firefox profile creation" + }, + "type": "library", + "autoload": { + "files": [ + "lib/Exception/TimeoutException.php" + ], + "psr-4": { + "Facebook\\WebDriver\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP client for Selenium WebDriver. Previously facebook/webdriver.", + "homepage": "https://github.com/php-webdriver/php-webdriver", + "keywords": [ + "Chromedriver", + "geckodriver", + "php", + "selenium", + "webdriver" + ], + "support": { + "issues": "https://github.com/php-webdriver/php-webdriver/issues", + "source": "https://github.com/php-webdriver/php-webdriver/tree/1.15.1" + }, + "time": "2023-10-20T12:21:20+00:00" + }, { "name": "phpunit/php-code-coverage", "version": "11.0.3", diff --git a/resources/js/app.js b/resources/js/app.js index 00b94c9..7b8a98d 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -3,263 +3,42 @@ import './lib/goldenLayout'; import './lib/monaco'; import './lib/lucide'; import version from "./lib/version"; +import agreeDialog from './lib/agreeDialog'; +import shareDialog from './lib/shareDialog'; +import defaultLayout from './lib/defaultLayout'; -let sharedFlag = (window.location.pathname.indexOf("/s/") === 0); - -let lastPlayerHtml = ""; - -let layout = null; -let layoutInitialized = false; -let compiling = false; - -let layoutDefaultConfig = { - settings: { - showPopoutIcon: false, - }, - content: [{ - type: 'row', - content:[{ - type: 'component', - componentName: 'editorComponent', - componentState: {}, - isClosable: false, - title: 'C++ Editor', - },{ - type: 'component', - componentName: 'playerComponent', - componentState: {}, - isClosable: false, - title: 'Emscripten Player', - }], - }], -}; - -let layoutConfig = window.localStorage.getItem("pgetinkerLayout"); -layoutConfig = (layoutConfig !== null) ? JSON.parse(layoutConfig) : layoutDefaultConfig; - -let maxFileSize = 50000; - -let theme = window.localStorage.getItem("pgetinkerTheme"); -if(theme !== "dark" && theme !== "light") - theme = "dark"; - -let consoleShown = window.localStorage.getItem("pgetinkerConsoleShown"); -consoleShown = (consoleShown === "true") ? true : false; - -let monacoEditor = null; -let monacoModel = null; -let monacoModelIntellisense = null; - -function preCompile() +class PGEtinker { - if(monacoEditor.getValue().length > maxFileSize) - { - alert("Maximum size exceeded!"); - return false; - } - - compiling = true; - + sharedFlag = false; lastPlayerHtml = ""; - let playerFrame = document.querySelector("#player-panel iframe"); - - if(playerFrame != null) - playerFrame.remove(); - - 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"); - monacoEditor.trigger("", "closeMarkersNavigation"); - - return true; -} -function compileSuccessHandler(data) -{ - lastPlayerHtml = data.html; - - let playerFrame = document.createElement('iframe'); - playerFrame.setAttribute("srcdoc", lastPlayerHtml); - 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); - + layoutInitialized = false; compiling = false; -} -function compileFailHandler(stderr) -{ - const compilerRegex = /:(\d+):(\d+): (fatal error|error|warning): (.*)/gm; - const linkerRegex = /wasm-ld: error: pgetinker.o: (.*): (.*)/gm; + layoutConfig = null; - let markers = []; + maxFileSize = 50000; - let matches; + theme = "dark"; + consoleShown = false; - while((matches = compilerRegex.exec(stderr)) !== null) - { - markers.push({ - message: matches[4], - severity: (matches[3] === "warning") ? monaco.MarkerSeverity.Warning : monaco.MarkerSeverity.Error, - startLineNumber: parseInt(matches[1]), - startColumn: parseInt(matches[2]), - endLineNumber: parseInt(matches[1]), - endColumn: 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: monacoModel.getLineLength(1), - source: "Emscripten Linker", - }); - } + monacoEditor = null; + monacoModel = null; + monacoModelIntellisense = null; - // show errors in the editor, if they exist - if(markers.length > 0) + constructor() { - monaco.editor.setModelMarkers(monacoModel, "owner", markers); - monacoEditor.setPosition({lineNumber: markers[0].startLineNumber, column: markers[0].startColumn }); - setTimeout(() => { 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); - compiling = false; -} - -function SetupLayout() -{ - layout = new GoldenLayout(layoutConfig, document.querySelector("#content")) - - layout.registerComponent('playerComponent', function(container) - { - container.getElement().html(` -
-
- -
-
-
-
-
-
-
-
-

- Compiling -

-
-
- -

- Compile Failed. -

-
-
- `); - }); - - layout.registerComponent('editorComponent', function(container) - { - container.getElement().html(` -
-
-
Loading
-
- `); - }); - - layout.on("stateChanged", () => - { - if(layoutInitialized) - window.localStorage.setItem("pgetinkerLayout", JSON.stringify(layout.toConfig())); - }); - - layout.on("initialised", () => - { - layoutInitialized = true; - window.addEventListener("resize", (event) => layout.updateSize()); - - if(monacoModel === null) - { - monacoModel = monaco.editor.createModel("", "cpp", monaco.Uri.parse("inmemory://pgetinker.cpp")); - - let codeBox = document.querySelector("#code"); - if(codeBox.value !== "") - { - 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) => - { - monacoModel.setValue(response.data.code); - }).catch((reason) => console.log(reason)); - } - else - { - monacoModel.setValue(code); - } - } - } - - if(monacoModelIntellisense === null) - { - monacoModelIntellisense = monaco.editor.createModel("", "cpp", monaco.Uri.parse("inmemory://pgetinker.h")); - axios.get("/api/model/v0.01").then((response) => - { - monacoModelIntellisense.setValue(response.data); - }); - } - - monacoEditor = monaco.editor.create(document.querySelector('#editor-panel .code-editor'), { - automaticLayout: true, - model: monacoModel, - theme: `vs-${theme}`, - }); - - 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")); - } - }); - - monacoEditor.onDidChangeCursorPosition(() => UpdateStatusBar()); - - monacoEditor.onDidChangeModelContent(() => - { - window.localStorage.setItem("pgetinkerCode", JSON.stringify(monacoEditor.getValue())); - - if(sharedFlag) - { - window.history.replaceState({}, "", "/"); - } - }); + this.sharedFlag = (window.location.pathname.indexOf("/s/") === 0); - UpdateStatusBar(); + this.layoutConfig = window.localStorage.getItem("pgetinkerLayout"); + this.layoutConfig = (this.layoutConfig !== null) ? JSON.parse(this.layoutConfig) : defaultLayout; - document.querySelector("#player-panel iframe").setAttribute("srcdoc", lastPlayerHtml); + this.theme = window.localStorage.getItem("pgetinkerTheme"); + 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) => @@ -268,21 +47,21 @@ function SetupLayout() axios.get("/api/default-code").then((response) => { - monacoModel.setValue(response.data.code); + this.monacoModel.setValue(response.data.code); }).catch((reason) => console.log(reason)); }); - + // Toggle Theme Button document.querySelector("#toggle-theme").addEventListener("click", (event) => { event.preventDefault(); - if(theme === "dark") - theme = "light"; + if(this.theme === "dark") + this.theme = "light"; else - theme = "dark"; + this.theme = "dark"; - UpdateTheme(); + this.UpdateTheme(); }); // Default Layout @@ -290,19 +69,19 @@ function SetupLayout() { event.preventDefault(); - layout.destroy(); - layoutConfig = layoutDefaultConfig; + this.layout.destroy(); + this.layoutConfig = defaultLayout; - SetupLayout(); + this.SetupLayout(); }); - + // Toggle Console Button document.querySelector("#toggle-console").addEventListener("click", (event) => { event.preventDefault(); - consoleShown = !consoleShown; - window.localStorage.setItem("pgetinkerConsoleShown", consoleShown); + this.consoleShown = !this.consoleShown; + window.localStorage.setItem("pgetinkerConsoleShown", this.consoleShown); document.querySelector("#player-panel iframe").contentWindow.postMessage({ message: "toggle-console", @@ -314,7 +93,7 @@ function SetupLayout() { event.preventDefault(); - if(!lastPlayerHtml.includes("Emscripten-Generated Code")) + if(!this.lastPlayerHtml.includes("Emscripten-Generated Code")) { alert("You have to build the code before you can download!") return; @@ -323,7 +102,7 @@ function SetupLayout() const a = document.createElement('a'); // create the data url - a.href = `data:text/html;base64,${btoa(lastPlayerHtml)}`; + a.href = `data:text/html;base64,${btoa(this.lastPlayerHtml)}`; a.download = "pgetinker.html"; document.body.appendChild(a); @@ -336,42 +115,22 @@ function SetupLayout() { event.preventDefault(); - if(compiling) + if(this.compiling) return; - if(!preCompile()) + if(!this.preCompile()) return; axios.post("/api/share", { - code: monacoEditor.getValue() + code: this.monacoEditor.getValue() }).then((response) => { - let shareDialog = document.createElement('div'); - - shareDialog.classList.toggle("dialog", "true"); - shareDialog.classList.toggle("share-dialog", "true"); - shareDialog.innerHTML = ` -
-
Share Your Masterpiece!
-
-
- - - -
-
-
`; - - shareDialog.querySelector("button").addEventListener("click", (event) => - { - navigator.clipboard.writeText(response.data.shareURL).catch((reason) => console.log(reason)); - shareDialog.remove(); - }); - - document.body.appendChild(shareDialog); - - compileSuccessHandler(response.data); - + shareDialog(response.data.shareURL) + .finally(() => + { + this.compileSuccessHandler(response.data); + }); + }).catch((error) => { if(error.response) @@ -380,18 +139,18 @@ function SetupLayout() { if(error.response.status == 503) { - compileFailHandler("pgetinker.cpp:1:1: error: PGEtinker service has gone offline. try again later.\n"); + this.compileFailHandler("pgetinker.cpp:1:1: error: PGEtinker service has gone offline. try again later.\n"); return; } } if(error.response.data.stderr) { - compileFailHandler(error.response.data.stderr); + this.compileFailHandler(error.response.data.stderr); return; } } - compileFailHandler("pgetinker.cpp:1:1: error: compilation failed in a way that's not being handled. please make a bug report.\n"); + this.compileFailHandler("pgetinker.cpp:1:1: error: compilation failed in a way that's not being handled. please make a bug report.\n"); }); }); @@ -400,17 +159,17 @@ function SetupLayout() { event.preventDefault(); - if(compiling) + if(this.compiling) return; - if(!preCompile()) + if(!this.preCompile()) return; axios.post("/api/compile", { - code: monacoEditor.getValue() + code: this.monacoEditor.getValue() }).then((response) => { - compileSuccessHandler(response.data); + this.compileSuccessHandler(response.data); }).catch((error) => { @@ -420,201 +179,366 @@ function SetupLayout() { if(error.response.status == 503) { - compileFailHandler("pgetinker.cpp:1:1: error: PGEtinker service has gone offline. try again later.\n"); + this.compileFailHandler("pgetinker.cpp:1:1: error: PGEtinker service has gone offline. try again later.\n"); return; } } if(error.response.data.stderr) { - compileFailHandler(error.response.data.stderr); + this.compileFailHandler(error.response.data.stderr); return; } } - compileFailHandler("pgetinker.cpp:1:1: error: compilation failed in a way that's not being handled. please make a bug report.\n"); + this.compileFailHandler("pgetinker.cpp:1:1: error: compilation failed in a way that's not being handled. please make a bug report.\n"); }); }); - + document.querySelector("#supporters").addEventListener("click", (event) => { event.preventDefault(); alert("Not Implemented"); }); - - UpdateTheme(); - }); - - layout.init(); -} -function UpdateTheme() -{ - // update overall theme - document.body.className = theme; + 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 + }, "*"); + } + }); - // update golden layout theme - let goldenLayoutDarkThemeStyle = document.querySelector("#goldenlayout-dark-theme"); - let goldenLayoutLightThemeStyle = document.querySelector("#goldenlayout-light-theme"); + let agreedToTerms = window.localStorage.getItem("pgetinkerAgreedToTerms"); + agreedToTerms = (agreedToTerms == null) ? false : JSON.parse(agreedToTerms); + + if(!agreedToTerms) + { + agreeDialog() + .then(() => + { + window.localStorage.setItem("pgetinkerAgreedToTerms", JSON.stringify(true)); + this.SetupLayout(); + }) + .catch(() => + { + window.localStorage.removeItem("pgetinkerCode"); + window.localStorage.removeItem("pgetinkerTheme"); + window.localStorage.removeItem("pgetinkerLayout"); + window.localStorage.removeItem("pgetinkerVersion"); + window.location.pathname = "/disagree"; + }); + } + else + { + this.SetupLayout(); + } + } - if(theme === "dark") + preCompile() { - goldenLayoutDarkThemeStyle.disabled = false; - goldenLayoutLightThemeStyle.disabled = true; + if(this.monacoEditor.getValue().length > this.maxFileSize) + { + alert("Maximum size exceeded!"); + return false; + } + + this.compiling = true; + + this.lastPlayerHtml = ""; + let playerFrame = document.querySelector("#player-panel iframe"); + + if(playerFrame != null) + playerFrame.remove(); + + 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; } - - if(theme === "light") + + compileSuccessHandler(data) { - goldenLayoutDarkThemeStyle.disabled = true; - goldenLayoutLightThemeStyle.disabled = false; + this.lastPlayerHtml = data.html; + + let playerFrame = document.createElement('iframe'); + playerFrame.setAttribute("srcdoc", this.lastPlayerHtml); + 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.compiling = false; } - - // update editor theme - if(monacoEditor !== null) - monacoEditor.updateOptions({ theme: `vs-${theme}`}); - - // update player theme - document.querySelector("#player-panel iframe").contentWindow.postMessage({ - message: "set-theme", - theme: theme - }, "*"); - - // save theme into localStorage - window.localStorage.setItem("pgetinkerTheme", theme); -} - -function UpdateStatusBar() -{ - let statusBar = document.querySelector("#editor-panel .status"); - - let cursor = `Ln ${monacoEditor.getPosition().lineNumber}, Col ${monacoEditor.getPosition().column}`; - let fileSize = `${new Intl.NumberFormat().format(monacoEditor.getValue().length)} / ${new Intl.NumberFormat().format(maxFileSize)}`; - statusBar.classList.toggle('too-fucking-big', false); - if(monacoModel.getValueLength() > maxFileSize) + compileFailHandler(stderr) { - statusBar.classList.toggle('too-fucking-big', true); - fileSize += " EXCEEDING MAXIMUM!"; + const compilerRegex = /:(\d+):(\d+): (fatal error|error|warning): (.*)/gm; + const linkerRegex = /wasm-ld: error: pgetinker.o: (.*): (.*)/gm; + + let markers = []; + + let matches; + + while((matches = compilerRegex.exec(stderr)) !== null) + { + markers.push({ + message: matches[4], + severity: (matches[3] === "warning") ? monaco.MarkerSeverity.Warning : monaco.MarkerSeverity.Error, + 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.compiling = false; } + + SetupLayout() + { + this.layout = new GoldenLayout(this.layoutConfig, document.querySelector("#content")) + + this.layout.registerComponent('playerComponent', function(container) + { + container.getElement().html(` +
+
+
+
+
+
+
+
+
+
+

+ Compiling +

+
+
+ +

+ Compile Failed. +

+
+
+ `); + }); - statusBar.innerHTML = ` -
- Bytes: ${fileSize} -
-
- ${cursor} -
- `; -} - -// you're welcome dandistine -window.addEventListener("click", (event) => -{ - let shareDialog = document.querySelector(".share-dialog"); - if(shareDialog == null) - return; + this.layout.registerComponent('editorComponent', function(container) + { + container.getElement().html(` +
+
+
Loading
+
+ `); + }); + + this.layout.on("stateChanged", () => + { + if(this.layoutInitialized) + window.localStorage.setItem("pgetinkerLayout", JSON.stringify(this.layout.toConfig())); + }); + + this.layout.on("initialised", () => + { + 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")); - shareDialog.querySelector("button").dispatchEvent(new Event("click")); -}); - -window.addEventListener("message", (event) => -{ - if(typeof event.data !== "object") - return; + 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(typeof event.data.message !== "string") - return; - - if(event.data.message === "player-ready") + 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.01").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.monacoEditor.onDidChangeModelContent(() => + { + window.localStorage.setItem("pgetinkerCode", JSON.stringify(this.monacoEditor.getValue())); + + if(this.sharedFlag) + { + window.history.replaceState({}, "", "/"); + } + }); + + if(this.lastPlayerHtml != "") + { + document.querySelector("#player-panel .iframe-container iframe").srcdoc = this.lastPlayerHtml; + document.querySelector("#player-panel .iframe-container iframe").classList.toggle("display-block", true); + } + + this.UpdateStatusBar(); + this.UpdateTheme(); + }); + + this.layout.init(); + } + + UpdateStatusBar() { - // update player theme - document.querySelector("#player-panel iframe").contentWindow.postMessage({ - message: "set-theme", - theme: theme - }, "*"); + 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 + document.body.className = this.theme; + + // update golden layout theme + let goldenLayoutDarkThemeStyle = document.querySelector("#goldenlayout-dark-theme"); + let goldenLayoutLightThemeStyle = document.querySelector("#goldenlayout-light-theme"); + + if(this.theme === "dark") + { + goldenLayoutDarkThemeStyle.disabled = false; + goldenLayoutLightThemeStyle.disabled = true; + } + + if(this.theme === "light") + { + goldenLayoutDarkThemeStyle.disabled = true; + goldenLayoutLightThemeStyle.disabled = false; + } + + // update editor theme + if(this.monacoEditor !== null) + this.monacoEditor.updateOptions({ theme: `vs-${this.theme}`}); + // update player theme - document.querySelector("#player-panel iframe").contentWindow.postMessage({ - message: "show-console", - value: consoleShown - }, "*"); + let playerFrame = document.querySelector("#player-panel iframe"); + if(playerFrame != null) + { + document.querySelector("#player-panel iframe").contentWindow.postMessage({ + message: "set-theme", + theme: this.theme + }, "*"); + } + + // save theme into localStorage + window.localStorage.setItem("pgetinkerTheme", this.theme); } -}); + +} -let pgetinkerVersion = window.localStorage.getItem("pgetinkerVersion"); -pgetinkerVersion = (pgetinkerVersion != "string") ? pgetinkerVersion : ""; +new PGEtinker(); -console.log((version != pgetinkerVersion)); +// let pgetinkerVersion = window.localStorage.getItem("pgetinkerVersion"); +// pgetinkerVersion = (pgetinkerVersion != "string") ? pgetinkerVersion : ""; -let agreedToTerms = window.localStorage.getItem("pgetinkerAgreedToTerms"); -agreedToTerms = (agreedToTerms == null) ? false : JSON.parse(agreedToTerms); +// console.log((version != pgetinkerVersion)); -if(agreedToTerms) -{ - SetupLayout(); -} -else -{ - let agreeDialog = document.createElement('div'); - agreeDialog.setAttribute("class", "dialog first-time"); - agreeDialog.innerHTML = ` -
-
Welome to PGEtinker!
-
-

Hello and Welcome

-

- It would appear to be the first time you've - visited this site, at least from this browser. - In order for PGEtinker to function we require - your permission to do some things. -

-

Terms, in Plain English

-

- You agree that PGEtinker has permission to: -

- -
- -
`; - agreeDialog.querySelector("button.cancel").addEventListener("click", (event) => - { - window.localStorage.removeItem("pgetinkerCode"); - window.localStorage.removeItem("pgetinkerTheme"); - window.localStorage.removeItem("pgetinkerLayout"); - window.location.pathname = "/disagree"; - }); - - agreeDialog.querySelector("button.ok").addEventListener("click", (event) => - { - SetupLayout(); - - window.localStorage.setItem("pgetinkerAgreedToTerms", JSON.stringify(true)); - agreeDialog.remove(); - }); - - document.body.appendChild(agreeDialog); -} - diff --git a/resources/js/lib/agreeDialog.js b/resources/js/lib/agreeDialog.js new file mode 100644 index 0000000..2007866 --- /dev/null +++ b/resources/js/lib/agreeDialog.js @@ -0,0 +1,68 @@ +export default function agreeDialog() +{ + return new Promise((resolve, reject) => + { + let agreeDialog = document.createElement("div"); + + agreeDialog.classList.toggle("dialog", true); + agreeDialog.classList.toggle("first-time", true); + + agreeDialog.innerHTML = ` +
+
Welome to PGEtinker!
+
+

Hello and Welcome

+

+ It would appear to be the first time you've + visited this site, at least from this browser. + In order for PGEtinker to function we require + your permission to do some things. +

+

Terms, in Plain English

+

+ You agree that PGEtinker has permission to: +

+ +
+ +
`; + + agreeDialog.querySelector("#i-disagree").addEventListener("click", (event) => + { + reject(); + agreeDialog.remove(); + }); + + agreeDialog.querySelector("#i-agree").addEventListener("click", (event) => + { + resolve(); + agreeDialog.remove(); + }); + + document.body.appendChild(agreeDialog); + }); + +} \ No newline at end of file diff --git a/resources/js/lib/defaultLayout.js b/resources/js/lib/defaultLayout.js new file mode 100644 index 0000000..2f8d21a --- /dev/null +++ b/resources/js/lib/defaultLayout.js @@ -0,0 +1,23 @@ +const defaultLayout = { + settings: { + showPopoutIcon: false, + }, + content: [{ + type: 'row', + content:[{ + type: 'component', + componentName: 'editorComponent', + componentState: {}, + isClosable: false, + title: 'C++ Editor', + },{ + type: 'component', + componentName: 'playerComponent', + componentState: {}, + isClosable: false, + title: 'Emscripten Player', + }], + }], +}; + +export default defaultLayout; \ No newline at end of file diff --git a/resources/js/lib/shareDialog.js b/resources/js/lib/shareDialog.js new file mode 100644 index 0000000..34deab3 --- /dev/null +++ b/resources/js/lib/shareDialog.js @@ -0,0 +1,43 @@ +export default function shareDialog(shareUrl) +{ + function shareClickAnywhereHandler(event) + { + let shareDialog = document.querySelector(".share-dialog"); + if(shareDialog == null) + return; + + shareDialog.querySelector("button").dispatchEvent(new Event("click")); + } + + return new Promise((resolve) => + { + let shareDialog = document.createElement('div'); + + shareDialog.classList.toggle("dialog", "true"); + shareDialog.classList.toggle("share-dialog", "true"); + shareDialog.innerHTML = ` +
+
Share Your Masterpiece!
+
+
+ + + +
+
+
`; + + shareDialog.querySelector("button").addEventListener("click", (event) => + { + navigator.clipboard.writeText(shareUrl).catch((reason) => console.log(reason)); + shareDialog.remove(); + window.removeEventListener("click", shareClickAnywhereHandler); + resolve(); + }); + + document.body.appendChild(shareDialog); + + // you're welcome dandistine + window.addEventListener("click", shareClickAnywhereHandler); + }); +} diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index 2b9f7e8..98a4e96 100644 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -21,7 +21,7 @@