diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bc8a670 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea/* \ No newline at end of file diff --git a/index.html b/index.html index f201821..ecba9fc 100644 --- a/index.html +++ b/index.html @@ -16,6 +16,9 @@ + + +
@@ -53,13 +56,16 @@

Editeur de diagrammes d'actions (pseudo-code)

Source

-
@@ -85,9 +91,5 @@

Résultat

- - - - \ No newline at end of file diff --git a/scripts/PseudoCodeParser.js b/scripts/PseudoCodeParser.js index 461b692..b68275a 100644 --- a/scripts/PseudoCodeParser.js +++ b/scripts/PseudoCodeParser.js @@ -1,42 +1,47 @@ -var PseudoCodeParser = function(ownValues) { +const PseudoCodeParser = function(ownValues) { this.borders = ""; this.delimiters = [ - { pattern: /---\*/i, replacement: "┌─── *", border: "│" }, - { pattern: /---[-]+/i, replacement: "└──────────", border: false }, - { pattern: /\bif\b/i, replacement: "┌── if", border: "│" }, - { pattern: /\belseif\b/i, replacement: "├── if", border: true }, - { pattern: /\belse\b/i, replacement: "├── else", border: true }, - { pattern: /\bendif\b/i, replacement: "└──", border: false }, - { pattern: /\b___\b/i, replacement: "└──", border: false }, - { pattern: /\bdo\b/i, replacement: "╔══ do", border: "║" }, - { pattern: /\bwhile\b/i, replacement: "╔══ while", border: "║" }, - { pattern: /\benddo\b/i, replacement: "╙──", border: false }, - { pattern: /\bendwhile\b/i, replacement: "╙──", border: false }, - { pattern: /===/i, replacement: "╙──", border: false } + { pattern: /---\*/i, replacement: "┌─── *", border: "│" }, + { pattern: /---+/i, replacement: "└──────────", border: false }, + { pattern: /\bif\b/i, replacement: "┌── if", border: "│" }, + { pattern: /\belseif\b/i, replacement: "├── if", border: true }, + { pattern: /\belse\b/i, replacement: "├── else", border: true }, + { pattern: /\bendif\b/i, replacement: "└──", border: false }, + { pattern: /\b___\b/i, replacement: "└──", border: false }, + { pattern: /\bdo\b/i, replacement: "╔══ do", border: "║" }, + { pattern: /\bwhile\b/i, replacement: "╔══ while", border: "║" }, + { pattern: /\benddo\b/i, replacement: "╙──", border: false }, + { pattern: /\bendwhile\b/i, replacement: "╙──", border: false }, + { pattern: /===+/i, replacement: "╙──", border: false }, + { pattern: /\bmatch\b/i, replacement: "┌── match", border: "│" }, + { pattern: /\bcase\b/i, replacement: "├── case", border: true }, + { pattern: /\bdefault\b/i, replacement: "├── default", border: true }, + { pattern: /\bendmatch\b/i, replacement: "└──", border: false }, ]; this.symbols = [ - { pattern: /<=/g, replacement: "≤" }, - { pattern: />=/g, replacement: "≥" }, - { pattern: /!=/g, replacement: "≠" }, - { pattern: /<->/g, replacement: "←→" }, - { pattern: /->/g, replacement: "→" }, - { pattern: /=>/g, replacement: "⇒" }, - { pattern: /sqrt\^/g, replacement: "√" }, - { pattern: /&&/g, replacement: "AND" } + { pattern: /<=/g, replacement: "≤" }, + { pattern: />=/g, replacement: "≥" }, + { pattern: /!=/g, replacement: "≠" }, + { pattern: /<->/g, replacement: "←→" }, + { pattern: /->/g, replacement: "→" }, + { pattern: /=>/g, replacement: "⇒" }, + { pattern: /sqrt\^/g, replacement: "√" }, + { pattern: /&&/g, replacement: "AND" }, + { pattern: /\|\|/g, replacement: "OR" }, ]; this.extendedExpressions = [ - { pattern: /"([^"\n]*)"/ig, replacement: '"$1"' }, - { pattern: /'([^'\n]*)'/ig, replacement: '\'$1\'' }, - { pattern: /\/\/ (.*)/ig, replacement: '// $1' }, - { pattern: /\/\*([^*/]*)\*\//ig, replacement: '/*$1*/' }, - { pattern: /\b(if|else|do|while|until|times|and|or|is|not|than|est|non)\b/ig, replacement: '$1' }, - { pattern: /\b(true|false|break|stop|vrai|faux|hv|lv|null|nil|equal)\b/ig, replacement: '$1' }, - { pattern: /\b(obtenir|sortir|libérer|liberer|traiter|get|print|return|free|process)\b/ig, replacement: '$1' }, - { pattern: /┌─── \* (.*)/ig, replacement: '┌─── * $1' }, - { pattern: /\[([^\]]+)\]ent/ig, replacement: '[$1]ENT' } + { pattern: /"([^"\n]*)"/ig, replacement: '"$1"' }, + { pattern: /'([^'\n]*)'/ig, replacement: '\'$1\'' }, + { pattern: /\/\/ (.*)/ig, replacement: '// $1' }, + { pattern: /\/\*([^*/]*)\*\//ig, replacement: '/*$1*/' }, + { pattern: /\b(if|else|do|while|until|times|and|or|is|not|than|est|non|match|case|default)\b/ig, replacement: '$1' }, + { pattern: /\b(true|false|break|stop|vrai|faux|hv|lv|null|nil|equal)\b/ig, replacement: '$1' }, + { pattern: /\b(obtenir|sortir|libérer|liberer|traiter|get|print|return|free|process)\b/ig, replacement: '$1' }, + { pattern: /┌─── \* (.*)/ig, replacement: '┌─── * $1' }, + { pattern: /\[([^\]]+)]ent/ig, replacement: '[$1]ENT' }, ]; if (ownValues) { @@ -55,14 +60,14 @@ var PseudoCodeParser = function(ownValues) { PseudoCodeParser.prototype.getFormattedDiagram = function(string, useExtendedFormatting) { this.borders = ""; - var diagram = this.normalize(string); + let diagram = this.normalize(string); diagram = this.replaceSymbols(diagram); diagram = this.escapeHtml(diagram); diagram = this.parseModules(diagram); - var lines = diagram.split("\n"); + const lines = diagram.split("\n"); - for (var i in lines) { + for (const i in lines) { lines[i] = this.parseBlock(lines[i].trim()); } @@ -103,10 +108,11 @@ PseudoCodeParser.prototype.replaceExtendedExpressions = function(string) { }; PseudoCodeParser.prototype.escapeHtml = function(string) { - var entityMap = { + const entityMap = { "&": "&", "<": "<", - ">": ">" + ">": ">", + "|": "|", }; return string.replace(/[&<>]/g, function(symbol) { @@ -136,10 +142,10 @@ PseudoCodeParser.prototype.parseModules = function(string) { }; PseudoCodeParser.prototype.parseBlock = function(string) { - var matchesFirstWord = string.match(/^[a-z-_\*=]+/i); - var firstWord = (matchesFirstWord) ? matchesFirstWord[0] : ""; + const matchesFirstWord = string.match(/^[a-z-_*=]+/i); + const firstWord = (matchesFirstWord) ? matchesFirstWord[0] : ""; - var delimiter = this.getDelimiterObject(firstWord); + const delimiter = this.getDelimiterObject(firstWord); if (!delimiter) { return this.addBorders(string, "", true); @@ -148,17 +154,17 @@ PseudoCodeParser.prototype.parseBlock = function(string) { string = string.replace(delimiter.pattern, delimiter.replacement); string = this.addBorders(string, delimiter.border, false); - return string; + return string; }; PseudoCodeParser.prototype.createModule = function(title, inputs, outputs, corners) { - var border = ""; + let border = ""; - for (var i = 0; i < title.length; i++) { + for (let i = 0; i < title.length; i++) { border += "─"; } - var moduleBlock = corners.topLeft + "─" + border + "─" + corners.topRight + inputs + "\n"; + let moduleBlock = corners.topLeft + "─" + border + "─" + corners.topRight + inputs + "\n"; moduleBlock += "│ " + title + " │\n"; moduleBlock += corners.bottomLeft + "─" + border + "─" + corners.bottomRight + outputs; @@ -166,7 +172,7 @@ PseudoCodeParser.prototype.createModule = function(title, inputs, outputs, corne }; PseudoCodeParser.prototype.addBorders = function(string, border, whitespace) { - var borders = this.borders; + let borders = this.borders; if (typeof border === "string" && border.length > 0) { this.borders += border; @@ -182,7 +188,7 @@ PseudoCodeParser.prototype.addBorders = function(string, border, whitespace) { }; PseudoCodeParser.prototype.getDelimiterObject = function(string) { - var i = 0; + let i = 0; while (i < this.delimiters.length && !string.match(this.delimiters[i].pattern)) { i++; diff --git a/scripts/app.js b/scripts/app.js index bace7ab..5077a01 100644 --- a/scripts/app.js +++ b/scripts/app.js @@ -3,42 +3,46 @@ // ----------------------------------------------------------------------------- // Welcome Overlay -var welcomePanel = document.getElementById("welcomePanel"); -var buttonOpen = document.getElementById("buttonOpen"); -var buttonOpenWithExample = document.getElementById("buttonOpenWithExample"); +const welcomePanel = document.getElementById("welcomePanel"); +const buttonOpen = document.getElementById("buttonOpen"); +const buttonOpenWithExample = document.getElementById("buttonOpenWithExample"); // Editor -var editorInput = document.getElementById("editorInput"); -var input = document.getElementById("input"); -var output = document.getElementById("output"); +const editorInput = document.getElementById("editorInput"); +const input = document.getElementById("input"); +const output = document.getElementById("output"); // General Buttons -var buttonNew = document.getElementById("buttonNew"); -var buttonPrint = document.getElementById("buttonPrint"); -var inputFileNameToSaveAs = document.getElementById("inputFileNameToSaveAs"); -var buttonSaveAsFile = document.getElementById("buttonSaveAsFile"); -var buttonSaveInCache = document.getElementById("buttonSaveInCache"); +const buttonNew = document.getElementById("buttonNew"); +const buttonPrint = document.getElementById("buttonPrint"); +const inputFileNameToSaveAs = document.getElementById("inputFileNameToSaveAs"); +const buttonSaveAsFile = document.getElementById("buttonSaveAsFile"); +const buttonSaveInCache = document.getElementById("buttonSaveInCache"); // Input Buttons -var buttonInsertMain = document.getElementById("buttonInsert__Main"); -var buttonInsertCondition = document.getElementById("buttonInsert__Condition"); -var buttonInsertSwitch = document.getElementById("buttonInsert__Switch"); -var buttonInsertLoop = document.getElementById("buttonInsert__Loop"); -var buttonInsertParagraph = document.getElementById("buttonInsert__Paragraph"); -var buttonInsertModule = document.getElementById("buttonInsert__Module"); -var buttonExtendedFormatting = document.getElementById("buttonExtendedFormatting"); -var buttonDarkTheme = document.getElementById("buttonDarkTheme");// Output Tool Buttons +const buttonInsertMain = document.getElementById("buttonInsert__Main"); +const buttonInsertCondition = document.getElementById("buttonInsert__Condition"); +const buttonInsertSwitch = document.getElementById("buttonInsert__Switch"); +const buttonInsertLoop = document.getElementById("buttonInsert__Loop"); +const buttonInsertParagraph = document.getElementById("buttonInsert__Paragraph"); +const buttonInsertModule = document.getElementById("buttonInsert__Module"); +const buttonDarkTheme = document.getElementById("buttonDarkTheme");// Output Tool Buttons // Output Buttons -var buttonReload = document.getElementById("buttonReload"); -var buttonLiveReload = document.getElementById("buttonLiveReload"); -var buttonExtendedFormatting = document.getElementById("buttonExtendedFormatting"); +const buttonReload = document.getElementById("buttonReload"); +const buttonLiveReload = document.getElementById("buttonLiveReload"); +const buttonExtendedFormatting = document.getElementById("buttonExtendedFormatting"); + +// Zoom Buttons +const buttonZoomPlus = document.getElementById("buttonZoomPlus"); +const buttonZoomMinus = document.getElementById("buttonZoomMinus"); +const buttonZoomReset = document.getElementById("buttonZoomReset"); // ----------------------------------------------------------------------------- // Global Variables // ----------------------------------------------------------------------------- -var parser = new PseudoCodeParser({ +const parser = new PseudoCodeParser({ delimiters: [ { pattern: /\bperform\b/i, replacement: "╔══ perform", border: "║" }, { pattern: /\bendperform\b/i, replacement: "╙──", border: false }, @@ -48,10 +52,10 @@ var parser = new PseudoCodeParser({ { pattern: /\b__print-pb__\b/ig, replacement: '' } ] }); -var useExtendedFormatting = true; -var liveReload = true; -var inputValue = input.value; -var darkTheme = true; +let useExtendedFormatting = true; +let liveReload = true; +let inputValue = input.value; +let darkTheme = true; // ----------------------------------------------------------------------------- // Definition of Listeners @@ -89,8 +93,68 @@ document.addEventListener("keydown", function(event) { saveContentToCache(); } + + if (!event.ctrlKey) { + return; + } + + if (event.code === "Slash") { + event.preventDefault(); + zoom(0.1); + } else if (event.code === "Equal") { + event.preventDefault(); + zoom(-0.1); + } else if (event.code === "Digit0") { + event.preventDefault(); + resetZoom(); + } +}); + +// Zoom text +buttonZoomPlus.addEventListener("click", function(event) { + event.preventDefault(); + zoom(0.1); +}); + +buttonZoomMinus.addEventListener("click", function(event) { + event.preventDefault(); + zoom(-0.1); +}); + +buttonZoomReset.addEventListener("click", function(event) { + event.preventDefault(); + resetZoom(); }); +function zoom(zoomValue) { + const documentFontSize = parseFloat(window.getComputedStyle(document.documentElement).fontSize); + + const fontSizeRem = parseFloat(window.getComputedStyle(input).fontSize) / documentFontSize; + const lineHeightRem = parseFloat(window.getComputedStyle(input).lineHeight) / documentFontSize; + const outputLineHeightRem = parseFloat(window.getComputedStyle(output).lineHeight) / documentFontSize; + + const newFontSizeRem = fontSizeRem + zoomValue; + + if (newFontSizeRem < 0.5 || newFontSizeRem > 2) { + return; + } + + const newLineHeightRem = lineHeightRem + zoomValue; + const newOutputLineHeightRem = outputLineHeightRem + zoomValue; + + input.style.fontSize = `${newFontSizeRem}rem`; + output.style.fontSize = `${newFontSizeRem}rem`; + input.style.lineHeight = `${newLineHeightRem}rem`; + output.style.lineHeight = `${newOutputLineHeightRem}rem`; +} + +function resetZoom() { + input.style.fontSize = ""; + output.style.fontSize = ""; + input.style.lineHeight = ""; + output.style.lineHeight = ""; +} + // Welcome Overlay buttonOpen.addEventListener("click", function(event) { event.preventDefault(); @@ -115,16 +179,87 @@ input.addEventListener("keyup", function() { }); input.addEventListener("keydown", function (event) { + if (event.shiftKey && event.code === "Tab") { + event.preventDefault(); + + manageTabulation(-2); + return; + } + // Tabulation - if (event.keyCode === 9) { + if (event.code === "Tab") { event.preventDefault(); - var cursor = input.selectionStart; - insert(input, " "); - input.selectionStart = cursor + 2; - input.selectionEnd = input.selectionStart; + + manageTabulation(2); + return; + } + + const closingBlocks = { + "(": ")", + "[": "]", + "{": "}", + "\"": "\"", + "'": "'" + }; + + // Parenthesis + if (event.key in closingBlocks) { + const start = this.selectionStart; + const end = this.selectionEnd; + + if (start === end) { + return; + } + event.preventDefault(); + + const selectedText = this.value.substring(start, end); + + const open = event.key; + const close = closingBlocks[event.key]; + + this.value = this.value.substring(0, start) + open + selectedText + close + this.value.substring(end); + + // Put caret at right position again + this.selectionStart = start + 1; + this.selectionEnd = end + 1; } }); +function manageTabulation(spaceNumber) { + const start = input.selectionStart; + const end = input.selectionEnd; + + // Expand selection to the start and end of the lines + const startLine = input.value.lastIndexOf("\n", start - 1) + 1; + const endLine = input.value.indexOf("\n", end); + const adjustedEnd = endLine === -1 ? input.value.length : endLine; + + const selectedText = input.value.substring(startLine, adjustedEnd); + const lines = selectedText.split("\n"); + + for (let i = 0; i < lines.length; i++) { + if (spaceNumber > 0) { + lines[i] = " ".repeat(spaceNumber) + lines[i]; + } else { + const regex = new RegExp(`^ {0,${-spaceNumber}}`); + lines[i] = lines[i].replace(regex, ""); + } + } + + const newText = lines.join("\n"); + + input.value = input.value.substring(0, startLine) + newText + input.value.substring(adjustedEnd); + + input.selectionStart = start; + input.selectionEnd = start + newText.length; +} + +input.addEventListener("scroll", function () { + const ratio = input.scrollTop / (input.scrollHeight - input.clientHeight); + output.scrollTop = ratio * (output.scrollHeight - output.clientHeight); + output.scrollLeft = input.scrollLeft; +}); + // General Buttons buttonNew.addEventListener("click", function(event){ event.preventDefault(); @@ -146,7 +281,7 @@ buttonPrint.addEventListener("click", function(event) { buttonSaveAsFile.addEventListener("click", function(event){ event.preventDefault(); - var fileName = inputFileNameToSaveAs.value; + const fileName = inputFileNameToSaveAs.value; if (fileName) { saveTextAsFile(fileName); @@ -171,7 +306,7 @@ buttonInsertCondition.addEventListener("click", function(event) { buttonInsertSwitch.addEventListener("click", function(event) { event.preventDefault(); - insert(input, "if ()\n\nelseif ()\n\nelseif ()\n\nelse\n\nendif"); + insert(input, "match ()\n\ncase ()\n\ndefault\n\nendmatch"); drawDiagram(); }); @@ -246,20 +381,20 @@ function drawDiagram() { } function insert(input, string) { - var position = input.selectionStart; - var before = input.value.substring(0, position); - var after = input.value.substring(position, input.value.length); + const position = input.selectionStart; + const before = input.value.substring(0, position); + const after = input.value.substring(position, input.value.length); input.value = before + string + after; input.selectionStart = position + string.length; input.selectionEnd = input.selectionStart; input.focus(); -}; +} function saveTextAsFile(fileName) { - var source = input.value.replace(/\n/g, "\r\n"); - var fileUrl = window.URL.createObjectURL(new Blob([source], {type:"text/plain"})); - var downloadLink = createDownloadLink(fileUrl, fileName); + const source = input.value.replace(/\n/g, "\r\n"); + const fileUrl = window.URL.createObjectURL(new Blob([source], {type:"text/plain"})); + const downloadLink = createDownloadLink(fileUrl, fileName); document.body.appendChild(downloadLink); downloadLink.click(); @@ -267,7 +402,7 @@ function saveTextAsFile(fileName) { } function createDownloadLink(href, name) { - var downloadLink = document.createElement("a"); + const downloadLink = document.createElement("a"); downloadLink.download = name; downloadLink.innerHTML = "Download File"; downloadLink.href = href; @@ -281,10 +416,20 @@ function saveContentToCache(){ } function putCacheContentToInput(){ - var codeDA = localStorage['codeDA'] || ''; - document.getElementById("input").value = codeDA; + input.value = localStorage['codeDA'] || ''; } function isSaveKeyboardShortcut(event) { - return (window.navigator.platform.match("Mac") ? event.metaKey : event.ctrlKey) && event.keyCode == 83; + const userAgent = window.navigator.userAgent; + + const osMapping = { + "Linux": "Linux", + "Mac": "Mac", + "Win": "Windows", + "X11": "Android" + }; + + const os = Object.keys(osMapping).find(key => userAgent.includes(key)) || "unknown"; + + return (os === "Mac" ? event.metaKey : event.ctrlKey) && event.keyCode === 83; } diff --git a/serviceWorkerOffline.js b/serviceWorkerOffline.js index d22e539..f181f2f 100644 --- a/serviceWorkerOffline.js +++ b/serviceWorkerOffline.js @@ -27,7 +27,7 @@ self.addEventListener('install', function(event) { event.waitUntil(addAllToCache()); }); -self.addEventListener('activate', function(event) { +self.addEventListener('activate', function(_) { console.debug('Service worker is activated!'); }); @@ -40,8 +40,7 @@ self.addEventListener('fetch', function(event) { async function cacheNetworkRace(event) { try { const cache = await caches.open(CACHE_NAME); - const response = await Promise.any([cacheFetch(cache, event.request), networkFetch(cache, event.request)]); - return response; + return await Promise.any([cacheFetch(cache, event.request), networkFetch(cache, event.request)]); } catch (error) { console.error("Not found in cache or network", error); } diff --git a/styles/app.css b/styles/app.css index 0c48d9e..e12380e 100644 --- a/styles/app.css +++ b/styles/app.css @@ -285,6 +285,25 @@ input, textarea { line-height: 11pt; } +.source-input { + resize: none; + outline: none; +} + +.hide-scrollbar::-webkit-scrollbar { + display: none; +} + +.hide-scrollbar { + -ms-overflow-style: none; + scrollbar-width: none; +} + +.horizontal-scroll { + overflow-x: auto; + white-space: nowrap; +} + @media (min-width: 80rem) { .editor-container::after { display: block; diff --git a/styles/print.css b/styles/print.css index 97949df..ccc55a5 100644 --- a/styles/print.css +++ b/styles/print.css @@ -1,5 +1,5 @@ .print-pb { - height: 0px !important; + height: 0 !important; page-break-before: always; } diff --git a/styles/reset.css b/styles/reset.css index af94440..f520efa 100644 --- a/styles/reset.css +++ b/styles/reset.css @@ -19,7 +19,6 @@ time, mark, audio, video { margin: 0; padding: 0; border: 0; - font-size: 100%; font: inherit; vertical-align: baseline; } @@ -39,7 +38,6 @@ blockquote, q { } blockquote:before, blockquote:after, q:before, q:after { - content: ''; content: none; } table {