From 503b83e61ef558191ed121c9b0062c7434030681 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Hol=C3=BD?= Date: Mon, 2 Oct 2023 14:47:52 +0200 Subject: [PATCH] Build static SCI playground page in GitHub pages (#31) Build a GitHub Pages site with an editor with pre-loaded all libs this sci.configs support, so that people may play with them. --- .github/workflows/deloy-site.yml | 48 +++++++++ playground/.gitignore | 4 + playground/bb.edn | 6 ++ playground/deps.edn | 18 ++++ playground/package.json | 25 +++++ playground/shadow-cljs.edn | 11 ++ playground/src/playground.cljs | 171 +++++++++++++++++++++++++++++++ playground/www/index.html | 43 ++++++++ 8 files changed, 326 insertions(+) create mode 100644 .github/workflows/deloy-site.yml create mode 100644 playground/.gitignore create mode 100644 playground/bb.edn create mode 100644 playground/deps.edn create mode 100644 playground/package.json create mode 100644 playground/shadow-cljs.edn create mode 100644 playground/src/playground.cljs create mode 100644 playground/www/index.html diff --git a/.github/workflows/deloy-site.yml b/.github/workflows/deloy-site.yml new file mode 100644 index 0000000..2314eb2 --- /dev/null +++ b/.github/workflows/deloy-site.yml @@ -0,0 +1,48 @@ +name: Build and Deploy +on: + push: + branches: + - main +permissions: + contents: read + pages: write + id-token: write +jobs: + build-and-deploy: + concurrency: ci-${{ github.ref }} # Recommended if you intend to make multiple deployments in quick succession. + runs-on: ubuntu-latest + steps: + + - name: Checkout 🛎️ + uses: actions/checkout@v3 + + - name: Prepare java + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '11' + + - name: Install clojure tools + uses: DeLaGuardo/setup-clojure@12.1 + with: + # Install just one or all simultaneously + # The value must indicate a particular version of the tool, or use 'latest' + # to always provision the latest version + cli: latest + bb: latest + + - name: Install and Build 🔧 + run: | + cd playground && bb build + + - name: Setup Pages + uses: actions/configure-pages@v3 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v2 + with: + path: 'playground/www' + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 diff --git a/playground/.gitignore b/playground/.gitignore new file mode 100644 index 0000000..5212408 --- /dev/null +++ b/playground/.gitignore @@ -0,0 +1,4 @@ +www/js/ +node_modules/ +package-lock.json +yarn.lock diff --git a/playground/bb.edn b/playground/bb.edn new file mode 100644 index 0000000..5a1326b --- /dev/null +++ b/playground/bb.edn @@ -0,0 +1,6 @@ +{:tasks + {build {:doc "Build the static 'SCI Playground' site with sci and an editor" + :task (do (shell "yarn install") + (clojure "-M:dev:shadow-cli release playground") + (println "Built www"))} + watch (clojure "-M:dev:shadow-cli watch playground")}} diff --git a/playground/deps.edn b/playground/deps.edn new file mode 100644 index 0000000..b90244d --- /dev/null +++ b/playground/deps.edn @@ -0,0 +1,18 @@ +{:deps {; SCI + org.babashka/sci.configs {:local/root ".."} + org.babashka/sci {:git/url "https://github.com/babashka/sci" + :git/sha "987910fb38fdd166865458c3fd4b468a22fb9992"} + ;; Editor + io.github.nextjournal/clojure-mode {:git/sha "7b911bf6feab0f67b60236036d124997627cbe5e"} + ;; Included libs + org.clojure/clojurescript {:mvn/version "1.11.51"} + applied-science/js-interop {:mvn/version "0.3.3"} + cljs-bean/cljs-bean {:mvn/version "1.9.0"} + com.fulcrologic/fulcro {:mvn/version "3.6.10"} + datascript/datascript {:mvn/version "1.5.3"} + funcool/promesa {:git/url "https://github.com/funcool/promesa" + :git/sha "e503874b154224ce85b223144e80b697df91d18e"} + reagent/reagent {:mvn/version "1.1.0"} + re-frame/re-frame {:mvn/version "1.3.0"}} + :aliases {:dev {:extra-deps {thheller/shadow-cljs {:mvn/version "2.25.7"}}} + :shadow-cli {:main-opts ["-m" "shadow.cljs.devtools.cli"]}}} diff --git a/playground/package.json b/playground/package.json new file mode 100644 index 0000000..82d1166 --- /dev/null +++ b/playground/package.json @@ -0,0 +1,25 @@ +{ + "name": "sci_playground", + "version": "1.0.0", + "devDependencies": { + "@codemirror/autocomplete": "^6.0.2", + "@codemirror/commands": "^6.0.0", + "@codemirror/lang-markdown": "6.0.0", + "@codemirror/language": "^6.1.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.1", + "@codemirror/view": "^6.0.2", + "@lezer/common": "^1.0.0", + "@lezer/generator": "^1.0.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "@nextjournal/lezer-clojure": "1.0.0", + "react": "18.2.0", + "react-dom": "18.2.0", + "shadow-cljs": "^2.25.7" + }, + "dependencies": { + "w3c-keyname": "^2.2.4" + } +} diff --git a/playground/shadow-cljs.edn b/playground/shadow-cljs.edn new file mode 100644 index 0000000..bf8b00e --- /dev/null +++ b/playground/shadow-cljs.edn @@ -0,0 +1,11 @@ +{:deps {:aliases [:dev]} + :nrepl {:port 9000} + :dev-http {8081 "www"} + :builds {:playground {:compiler-options {:output-feature-set :es8 + :optimizations :advanced + :source-map true + :output-wrapper false} + :target :browser + :output-dir "www/js" + :modules {:playground {:init-fn playground/init}} + :devtools {:after-load playground/reload}}}} diff --git a/playground/src/playground.cljs b/playground/src/playground.cljs new file mode 100644 index 0000000..25ae49c --- /dev/null +++ b/playground/src/playground.cljs @@ -0,0 +1,171 @@ +(ns playground + "Build CodeMirror editor with SCI evaluation for the SCI Playground." + (:require + [sci.core :as sci] + [clojure.string :as str] + + ;; All the configs + sci.configs.applied-science.js-interop + sci.configs.cljs.pprint + sci.configs.cljs.test + ;sci.configs.clojure.test + sci.configs.fulcro.fulcro + sci.configs.funcool.promesa + sci.configs.mfikes.cljs-bean + sci.configs.re-frame.re-frame + sci.configs.reagent.reagent + sci.configs.reagent.reagent-dom-server + sci.configs.tonsky.datascript + ; sci.configs.clojure-1-11 + + ;; Code editor + ;; Inspiration: https://github.com/nextjournal/clojure-mode/blob/main/demo/src/nextjournal/clojure_mode/demo.cljs + ["@codemirror/commands" :refer [history historyKeymap]] + ["@codemirror/language" :refer [#_foldGutter + syntaxHighlighting + defaultHighlightStyle]] + ["@codemirror/state" :refer [EditorState]] + ["@codemirror/view" :as view :refer [EditorView lineNumbers showPanel]] + + + ;; JS deps for re-export to sci + ["react" :as react] + ["react-dom" :as react-dom] + + [nextjournal.clojure-mode :as cm-clj] + )) + +;; Necessary to avoid the error 'Attempting to call unbound fn: #'clojure.core/*print-fn*' +;; when calling `println` inside the evaluated code +(enable-console-print!) +(sci/alter-var-root sci/print-fn (constantly *print-fn*)) +(sci/alter-var-root sci/print-err-fn (constantly *print-err-fn*)) + +;; ------------------------------------------------------------ SCI eval + +(def all-configs ; vars so that we can extract ns info + [#'sci.configs.applied-science.js-interop/config + #'sci.configs.cljs.pprint/config + #'sci.configs.cljs.test/config + ;#'sci.configs.clojure.test/config + #'sci.configs.fulcro.fulcro/config + #'sci.configs.funcool.promesa/config + #'sci.configs.mfikes.cljs-bean/config + #'sci.configs.re-frame.re-frame/config + #'sci.configs.reagent.reagent/config + #'sci.configs.reagent.reagent-dom-server/config + #'sci.configs.tonsky.datascript/config]) + +(def sci-ctx + (->> all-configs + (map deref) + (reduce + sci/merge-opts + (sci/init {:classes {'js js/globalThis :allow :all} + :js-libs {"react" react + "react-dom" react-dom}})))) + +(defn eval-code [code] + (try (sci/eval-string* sci-ctx code) + (catch :default e + (try (js/console.log "Evaluation failed:" (ex-message e) + (some-> e ex-data clj->js)) + (catch :default _)) + {::error (str (.-message e)) :data (ex-data e)}))) + +(defn eval-all [on-result x] + (on-result (some->> (.-doc (.-state x)) str eval-code)) + true) + +(defn sci-extension [on-result] + (.of view/keymap + #js [#js {:key "Mod-Enter" ; Cmd or Ctrl + :run (partial eval-all on-result)}])) + +;; ------------------------------------------------------------ Code editor + +(defn mac? [] + (some? (re-find #"(Mac)|(iPhone)|(iPad)|(iPod)" js/navigator.platform))) + +(defn output-panel-extension + "Display a panel below the editor with the output of the + last evaluation (read from the passed-in `result-atom`)" + [result-atom] + (let [dom (js/document.createElement "div")] + (add-watch result-atom :output-panel + (fn [_ _ _ new] + (if (::error new) + (do + (.add (.-classList dom) "error") + (set! (.-textContent dom) (str "ERROR: " (::error new) + (some->> new :data pr-str (str " "))))) + (do + (.remove (.-classList dom) "error") + (set! (.-textContent dom) (str ";; => " (pr-str new))))))) + (set! (.-className dom) "cm-output-panel") + (set! (.-textContent dom) + (str "Press " + (if (mac?) "Cmd" "Ctrl") + "-Enter in the editor to evaluate it. Return value will show up here.")) + (.of showPanel + (fn [_view] #js {:dom dom})))) + +(def theme + (.theme + EditorView + #js {".cm-output-panel.error" #js {:color "red"}})) + +(defonce extensions + #js[theme + (history) + (syntaxHighlighting defaultHighlightStyle) + (view/drawSelection) + (lineNumbers) + (.. EditorState -allowMultipleSelections (of true)) + cm-clj/default-extensions + (.of view/keymap cm-clj/complete-keymap) + (.of view/keymap historyKeymap)]) + +(defn bind-editor! [el code] + {:pre [el code]} + (let [target-el (js/document.createElement "div") + last-result (atom nil) + exts (.concat extensions + #js [(sci-extension (partial reset! last-result)) + (output-panel-extension last-result)])] + (.replaceWith el target-el) + (new EditorView + #js {:parent target-el + :state (.create EditorState #js {:doc code + :extensions exts})}))) + +(defn list-libraries [all-config-vars] + (->> all-config-vars + (map (comp name :ns meta)) + (map #(clojure.string/replace % #"^sci\.configs\.[\w-]+\." "")) + (remove #{"pprint" "test"}) + sort + (str/join ", "))) + +(defn ^:export init [] + (let [code-el (js/document.getElementById "code") + code (.-textContent code-el) + libs-el (js/document.getElementById "libs")] + (set! (.-textContent libs-el) (list-libraries all-configs)) + (bind-editor! code-el code)) + (println "Init run")) + +(defn ^:export reload [] + (println "Reload run (noop)")) + +(comment + + (def X #'sci.configs.cljs.pprint/config) + + + (-> X meta :ns name + (clojure.string/replace-first "sci.configs." "")) + + + + ) \ No newline at end of file diff --git a/playground/www/index.html b/playground/www/index.html new file mode 100644 index 0000000..f10df21 --- /dev/null +++ b/playground/www/index.html @@ -0,0 +1,43 @@ + + + + + + SCI Playground + + + + +

SCI Playground

+

Write and evaluate SCI Clojure using any of the libraries in sci.configs + in the editor below. Use Cmd / Ctr - Enter to evaluate.

+

Supported libraries: todo.

+ +
(ns test1
+      (:require
+        [applied-science.js-interop :as j]
+        [promesa.core :as p]
+        [cljs-bean.core :refer [bean ->clj ->js]]
+        [re-frame.core :as rf]
+        [re-frame.db :as r.db]
+        [reagent.core :as r]
+        [reagent.dom.server :as rds]
+        [reagent.debug]
+        [reagent.ratom :as ratom]
+        [datascript.core :as d]
+        [datascript.db :as d.db]   
+        [com.fulcrologic.fulcro.application :as app]
+        [com.fulcrologic.fulcro.components :as comp :refer [defsc]]
+        [com.fulcrologic.fulcro.dom :as dom]))
+    (defsc Root [this props]
+     (dom/div (dom/h3 "Hello from SCI!")
+       (dom/p "Here you can play with Fulcro and Reagent apps and much more!")))
+    (app/mount! (app/fulcro-app) Root "app")
+    
+
+ You can use this <div id="app"> to render DOM. +
+ + + + \ No newline at end of file