From e898e47efe33f2e11680abd964efdc5f8f382439 Mon Sep 17 00:00:00 2001 From: crimson-knight Date: Sun, 12 Nov 2023 14:14:50 -0500 Subject: [PATCH 1/2] Improve the logger output so exception messages are now output, making 500 server errors easier to diagnose --- src/amber/cli/templates/app/config/logger.cr.ecr | 1 + 1 file changed, 1 insertion(+) diff --git a/src/amber/cli/templates/app/config/logger.cr.ecr b/src/amber/cli/templates/app/config/logger.cr.ecr index d31de0f15..c1dc58feb 100644 --- a/src/amber/cli/templates/app/config/logger.cr.ecr +++ b/src/amber/cli/templates/app/config/logger.cr.ecr @@ -26,6 +26,7 @@ backend.formatter = Log::Formatter.new do |entry, io| io << " (#{entry.severity})" if entry.severity > Log::Severity::Debug io << " " io << entry.message + io << " " + entry.exception.message if entry.exception end Log.builder.clear From 049b6b6a495c843c098848ea2247362340512a0c Mon Sep 17 00:00:00 2001 From: crimson-knight Date: Tue, 14 Nov 2023 08:50:59 -0500 Subject: [PATCH 2/2] Updates the new amber app template to include import maps and modernizes the basic JS to allow for autoreloading & the minimal JS for CRUD --- src/amber/cli/templates/app/.amber.yml.ecr | 1 + .../app/config/initializers/import_map.cr.ecr | 15 ++++++ .../cli/templates/app/config/logger.cr.ecr | 2 +- .../templates/app/public/js/client_reload.js | 52 ------------------- src/amber/cli/templates/app/shard.yml.ecr | 4 ++ .../{public/js => src/javascript}/amber.js | 46 ++++++++-------- .../app/src/javascript/client_reload.js | 35 +++++++++++++ .../app/src/views/layouts/application.ecr.ecr | 14 +++-- .../src/views/layouts/application.slang.ecr | 9 ++-- 9 files changed, 95 insertions(+), 83 deletions(-) create mode 100644 src/amber/cli/templates/app/config/initializers/import_map.cr.ecr delete mode 100644 src/amber/cli/templates/app/public/js/client_reload.js rename src/amber/cli/templates/app/{public/js => src/javascript}/amber.js (87%) create mode 100644 src/amber/cli/templates/app/src/javascript/client_reload.js diff --git a/src/amber/cli/templates/app/.amber.yml.ecr b/src/amber/cli/templates/app/.amber.yml.ecr index 100b05076..6bf401dbb 100644 --- a/src/amber/cli/templates/app/.amber.yml.ecr +++ b/src/amber/cli/templates/app/.amber.yml.ecr @@ -22,6 +22,7 @@ watch: - ./src/views/**/*.slang <%- end -%> - ./src/locales/*.yml + - ./src/javascript/**/*.js # exclude: # NOTE simplistic implementation: (1) enumerate all includes and excludes; (2) return (includes - excludes) # - ./src/some_irrelevant_file.cr spec: diff --git a/src/amber/cli/templates/app/config/initializers/import_map.cr.ecr b/src/amber/cli/templates/app/config/initializers/import_map.cr.ecr new file mode 100644 index 000000000..e60ca28f1 --- /dev/null +++ b/src/amber/cli/templates/app/config/initializers/import_map.cr.ecr @@ -0,0 +1,15 @@ +require "asset_pipeline" + +FRONT_LOADER = AssetPipeline::FrontLoader.new(js_source_path: Path["src/javascript"], js_output_path: Path["public"]) do |import_maps| + import_map = AssetPipeline::ImportMap.new + + if Amber.settings.auto_reload + import_map.add_import("client_reload", "/client_reload.js") + end + + import_map.add_import("@popperjs/core", "https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js") + import_map.add_import("bootstrap", "https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.esm.min.js") + import_map.add_import("amber", "/amber.js") + + import_maps << import_map +end \ No newline at end of file diff --git a/src/amber/cli/templates/app/config/logger.cr.ecr b/src/amber/cli/templates/app/config/logger.cr.ecr index c1dc58feb..1a198328c 100644 --- a/src/amber/cli/templates/app/config/logger.cr.ecr +++ b/src/amber/cli/templates/app/config/logger.cr.ecr @@ -26,7 +26,7 @@ backend.formatter = Log::Formatter.new do |entry, io| io << " (#{entry.severity})" if entry.severity > Log::Severity::Debug io << " " io << entry.message - io << " " + entry.exception.message if entry.exception + io << " #{entry.exception}" if entry.exception end Log.builder.clear diff --git a/src/amber/cli/templates/app/public/js/client_reload.js b/src/amber/cli/templates/app/public/js/client_reload.js deleted file mode 100644 index cf0e04dab..000000000 --- a/src/amber/cli/templates/app/public/js/client_reload.js +++ /dev/null @@ -1,52 +0,0 @@ -if ('WebSocket' in window) { - (function () { - /** - * Allows to reload the browser when the server connection is lost - */ - function tryReload() { - var request = new XMLHttpRequest(); - request.open('GET', window.location.href, true); - request.onreadystatechange = function () { - if (request.readyState == 4) { - if (request.status == 0) { - setTimeout(function () { - tryReload(); - }, 1000) - } else { - window.location.reload(); - } - } - }; - request.send(); - } - - /** - * Listen server file reload - */ - function refreshCSS() { - var sheets = [].slice.call(document.getElementsByTagName('link')); - var head = document.getElementsByTagName('head')[0]; - for (var i = 0; i < sheets.length; ++i) { - var elem = sheets[i]; - var rel = elem.rel; - if (elem.href && typeof rel != 'string' || rel.length == 0 || rel.toLowerCase() == 'stylesheet') { - head.removeChild(elem); - var url = elem.href.replace(/(&|\\?)_cacheOverride=\\d+/, ''); - elem.href = url + (url.indexOf('?') >= 0 ? '&' : '?') + '_cacheOverride=' + (new Date().valueOf()); - head.appendChild(elem); - } - } - } - - var protocol = window.location.protocol === 'http:' ? 'ws://' : 'wss://'; - var address = protocol + window.location.host + '/client-reload'; - var socket = new WebSocket(address); - socket.onmessage = function (msg) { - if (msg.data == 'reload') { - tryReload(); - } else if (msg.data == 'refreshcss') { - refreshCSS(); - } - }; - })(); -} diff --git a/src/amber/cli/templates/app/shard.yml.ecr b/src/amber/cli/templates/app/shard.yml.ecr index baf76e2fe..5c2e03fb9 100644 --- a/src/amber/cli/templates/app/shard.yml.ecr +++ b/src/amber/cli/templates/app/shard.yml.ecr @@ -51,6 +51,10 @@ dependencies: github: dare892/citrine-i18n version: ~> 1.0.0 + asset_pipeline: + github: amberframework/asset_pipeline + version: ~> 0.34.0 + development_dependencies: ameba: github: crystal-ameba/ameba diff --git a/src/amber/cli/templates/app/public/js/amber.js b/src/amber/cli/templates/app/src/javascript/amber.js similarity index 87% rename from src/amber/cli/templates/app/public/js/amber.js rename to src/amber/cli/templates/app/src/javascript/amber.js index 1abbd755f..c4ffcae4a 100644 --- a/src/amber/cli/templates/app/public/js/amber.js +++ b/src/amber/cli/templates/app/src/javascript/amber.js @@ -231,28 +231,30 @@ export default { /** * Allows delete links to post for security and ease of use similar to Rails jquery_ujs */ -document.addEventListener("DOMContentLoaded", () => { - let elements = document.querySelectorAll("a[data-method='delete']"); - for (let i = 0; i < elements.length; i++) { - elements[i].addEventListener("click", (e) => { - e.preventDefault(); - let message = elements[i].getAttribute("data-confirm") || "Are you sure?"; - if (confirm(message)) { - let form = document.createElement("form"); - let input = document.createElement("input"); - form.setAttribute("action", elements[i].getAttribute("href")); - form.setAttribute("method", "POST"); - input.setAttribute("type", "hidden"); - input.setAttribute("name", "_method"); - input.setAttribute("value", "DELETE"); - form.appendChild(input); - document.body.appendChild(form); - form.submit(); - } - return false; - }) - } -}); +export function initialize() { + document.addEventListener("DOMContentLoaded", () => { + let elements = document.querySelectorAll("a[data-method='delete']"); + for (let i = 0; i < elements.length; i++) { + elements[i].addEventListener("click", (e) => { + e.preventDefault(); + let message = elements[i].getAttribute("data-confirm") || "Are you sure?"; + if (confirm(message)) { + let form = document.createElement("form"); + let input = document.createElement("input"); + form.setAttribute("action", elements[i].getAttribute("href")); + form.setAttribute("method", "POST"); + input.setAttribute("type", "hidden"); + input.setAttribute("name", "_method"); + input.setAttribute("value", "DELETE"); + form.appendChild(input); + document.body.appendChild(form); + form.submit(); + } + return false; + }) + } + }); +} if (!Date.prototype.toGranite) { (function() { diff --git a/src/amber/cli/templates/app/src/javascript/client_reload.js b/src/amber/cli/templates/app/src/javascript/client_reload.js new file mode 100644 index 000000000..e86d225ba --- /dev/null +++ b/src/amber/cli/templates/app/src/javascript/client_reload.js @@ -0,0 +1,35 @@ +let tryReload + +export function initializeWebSockets() { + console.log("initializeWebSockets has run...") + + tryReload = async function () { + try { + const response = await fetch(window.location.href); + if (!response.ok) { + setTimeout(tryReload, 1000); + } else { + window.location.reload(); + } + } catch (error) { + setTimeout(tryReload, 1000); + } + } + + if ('WebSocket' in window) { + const protocol = window.location.protocol === 'http:' ? 'ws://' : 'wss://' + const address = protocol + window.location.host + '/client-reload' + const socket = new WebSocket(address) + + socket.onmessage = function (msg) { + console.log(msg) + if (msg.data == 'reload') { + tryReload() + } + } + + socket.onclose = function () { + setTimeout(tryReload, 1000) + } + } +} diff --git a/src/amber/cli/templates/app/src/views/layouts/application.ecr.ecr b/src/amber/cli/templates/app/src/views/layouts/application.ecr.ecr index fe092e60c..10c337d65 100644 --- a/src/amber/cli/templates/app/src/views/layouts/application.ecr.ecr +++ b/src/amber/cli/templates/app/src/views/layouts/application.ecr.ecr @@ -38,9 +38,15 @@ - - - - <%="<"%>%- if Amber.settings.auto_reload? -%><%="<"%>%- end -%> + <%= "<"%>%= FRONT_LOADER.render_import_map_tag %> + diff --git a/src/amber/cli/templates/app/src/views/layouts/application.slang.ecr b/src/amber/cli/templates/app/src/views/layouts/application.slang.ecr index 4e2c2115d..9125f751d 100644 --- a/src/amber/cli/templates/app/src/views/layouts/application.slang.ecr +++ b/src/amber/cli/templates/app/src/views/layouts/application.slang.ecr @@ -28,8 +28,9 @@ html .main== content - script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" + == FRONT_LOADER.render_import_map_tag - script type="module" src="/js/amber.js" - - if Amber.settings.auto_reload? - script src="/js/client_reload.js" + script type="module" + = "import { initialize } from 'amber'"; + - if Amber.settings.auto_reload? + = "import { initializeWebSockets } from 'client_reload'";