├── .clj-kondo ├── babashka │ └── sci │ │ ├── config.edn │ │ └── sci │ │ └── core.clj ├── cjohansen │ └── dumdom │ │ ├── config.edn │ │ └── dumdom │ │ └── defcomponent.clj ├── config.edn ├── funcool │ └── promesa │ │ └── config.edn ├── hooks.clj ├── metosin │ ├── malli-types-clj │ │ └── config.edn │ └── malli │ │ └── config.edn ├── no.cjohansen │ └── portfolio │ │ ├── config.edn │ │ └── portfolio │ │ └── defscene.clj ├── reagent │ └── reagent │ │ └── config.edn ├── rewrite-clj │ └── rewrite-clj │ │ └── config.edn └── taoensso │ └── encore │ ├── config.edn │ └── taoensso │ └── encore.clj ├── .cljfmt.edn ├── .editorconfig ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── pull_request_template.md └── workflows │ ├── cla.yml │ ├── clj-holmes.yml │ ├── demo.yml │ ├── dependencies.yml │ ├── sponsors.yml │ └── studio.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── cla.json ├── deps.edn ├── karma.conf.js ├── package-lock.json ├── package.json ├── portfolio ├── resources │ └── public │ │ ├── .gitignore │ │ └── index.html └── src │ ├── pages │ ├── components.cljs │ ├── icons.cljs │ └── sections.cljs │ └── portfolio.cljs ├── resources └── public │ ├── icons │ ├── LICENSE │ ├── README.md │ ├── a11y.svg │ ├── android_head.svg │ ├── animation.svg │ ├── arc.svg │ ├── arrow-minimize.svg │ ├── bezier-curve.svg │ ├── blob.svg │ ├── bring-forward.svg │ ├── bring-front.svg │ ├── brush.svg │ ├── bug.svg │ ├── checkmark.svg │ ├── chemical-element.svg │ ├── chevron-down.svg │ ├── chevron-left.svg │ ├── chevron-right.svg │ ├── chevron-up.svg │ ├── chrome.svg │ ├── circle-tool.svg │ ├── circle.svg │ ├── code.svg │ ├── command.svg │ ├── commit.svg │ ├── copy.svg │ ├── cut.svg │ ├── dark.svg │ ├── degrees.svg │ ├── delete.svg │ ├── deselect-all.svg │ ├── distribute-spacing-horizontal.svg │ ├── distribute-spacing-vertical.svg │ ├── divide.svg │ ├── dot.svg │ ├── download.svg │ ├── earth.svg │ ├── edge.svg │ ├── edit.svg │ ├── ellipse-tool.svg │ ├── ellipse.svg │ ├── ellipsis-h.svg │ ├── ellipsis-v.svg │ ├── exclude.svg │ ├── exit.svg │ ├── export.svg │ ├── eye-closed.svg │ ├── eye-dropper.svg │ ├── eye.svg │ ├── file.svg │ ├── fill.svg │ ├── firefox.svg │ ├── flip-horizontal.svg │ ├── flip-vertical.svg │ ├── focus.svg │ ├── folder-plus.svg │ ├── folder.svg │ ├── go-to-end.svg │ ├── go-to-start.svg │ ├── grid.svg │ ├── group.svg │ ├── hand.svg │ ├── history.svg │ ├── icon-template.svg │ ├── ie.svg │ ├── image.svg │ ├── import.svg │ ├── info.svg │ ├── intersect.svg │ ├── invert-selection.svg │ ├── lgpl.svg │ ├── light.svg │ ├── line-tool.svg │ ├── line.svg │ ├── linecap-butt.svg │ ├── linecap-round.svg │ ├── linecap-square.svg │ ├── list.svg │ ├── lock.svg │ ├── magnet.svg │ ├── magnifier.svg │ ├── minus.svg │ ├── objects-align-bottom.svg │ ├── objects-align-center-horizontal.svg │ ├── objects-align-center-vertical.svg │ ├── objects-align-left.svg │ ├── objects-align-right.svg │ ├── objects-align-top.svg │ ├── oculus.svg │ ├── opera.svg │ ├── page-plus.svg │ ├── page.svg │ ├── paste.svg │ ├── pause.svg │ ├── pencil.svg │ ├── play.svg │ ├── plus.svg │ ├── pointer.svg │ ├── polygon-tool.svg │ ├── polygon.svg │ ├── polyline.svg │ ├── printer.svg │ ├── properties.svg │ ├── rectangle-tool.svg │ ├── rectangle.svg │ ├── redo.svg │ ├── refresh.svg │ ├── rotate-clockwise.svg │ ├── rotate-counterclockwise.svg │ ├── ruler-combined.svg │ ├── ruler-triangle.svg │ ├── safari.svg │ ├── samsunginternet_android.svg │ ├── save-as.svg │ ├── save.svg │ ├── select-all.svg │ ├── select-same.svg │ ├── send-back.svg │ ├── send-backward.svg │ ├── shell.svg │ ├── spinner.svg │ ├── square+plus.svg │ ├── square-minus.svg │ ├── stop.svg │ ├── subtract.svg │ ├── svg.svg │ ├── swap-horizontal.svg │ ├── swap-vertical.svg │ ├── system.svg │ ├── text.svg │ ├── timeline.svg │ ├── times.svg │ ├── tree.svg │ ├── triangle.svg │ ├── undo.svg │ ├── ungroup.svg │ ├── unite.svg │ ├── unlock.svg │ ├── warning.svg │ ├── webview_android.svg │ ├── window-close.svg │ ├── window-maximize.svg │ ├── window-minimize.svg │ ├── window-restore.svg │ ├── zoom-in.svg │ └── zoom-out.svg │ ├── img │ ├── banner.svg │ ├── favicon.png │ ├── icon-no-bg.svg │ ├── icon.png │ └── icon.svg │ ├── index.html │ └── loading.html ├── shadow-cljs.edn ├── src ├── config.cljs ├── dev.cljs ├── electron │ ├── file.cljs │ ├── main.cljs │ └── preload.cljs ├── lang │ ├── el-GR.edn │ └── en-US.edn ├── renderer │ ├── animations.css │ ├── app │ │ ├── db.cljs │ │ ├── effects.cljs │ │ ├── events.cljs │ │ ├── subs.cljs │ │ └── views.cljs │ ├── attribute │ │ ├── hierarchy.cljs │ │ ├── impl │ │ │ ├── angle.cljs │ │ │ ├── clock.cljs │ │ │ ├── color.cljs │ │ │ ├── core.cljs │ │ │ ├── d.cljs │ │ │ ├── font_family.cljs │ │ │ ├── font_size.cljs │ │ │ ├── font_weight.cljs │ │ │ ├── href.cljs │ │ │ ├── length.cljs │ │ │ ├── overflow.cljs │ │ │ ├── points.cljs │ │ │ ├── range.cljs │ │ │ ├── stroke_linecap.cljs │ │ │ ├── stroke_linejoin.cljs │ │ │ ├── style.cljs │ │ │ └── transform.cljs │ │ └── views.cljs │ ├── components.css │ ├── core.cljs │ ├── dialog │ │ ├── db.cljs │ │ ├── events.cljs │ │ ├── handlers.cljs │ │ ├── subs.cljs │ │ └── views.cljs │ ├── document │ │ ├── db.cljs │ │ ├── events.cljs │ │ ├── handlers.cljs │ │ ├── subs.cljs │ │ └── views.cljs │ ├── effects.cljs │ ├── element │ │ ├── db.cljs │ │ ├── effects.cljs │ │ ├── events.cljs │ │ ├── handlers.cljs │ │ ├── hierarchy.cljs │ │ ├── impl │ │ │ ├── animation │ │ │ │ ├── animate.cljs │ │ │ │ ├── animate_motion.cljs │ │ │ │ ├── animate_transform.cljs │ │ │ │ └── core.cljs │ │ │ ├── box.cljs │ │ │ ├── container │ │ │ │ ├── canvas.cljs │ │ │ │ ├── core.cljs │ │ │ │ ├── group.cljs │ │ │ │ └── svg.cljs │ │ │ ├── core.cljs │ │ │ ├── custom │ │ │ │ ├── blob.cljs │ │ │ │ ├── brush.cljs │ │ │ │ ├── core.cljs │ │ │ │ └── measure.cljs │ │ │ ├── renderable.cljs │ │ │ ├── shape │ │ │ │ ├── circle.cljs │ │ │ │ ├── core.cljs │ │ │ │ ├── ellipse.cljs │ │ │ │ ├── image.cljs │ │ │ │ ├── line.cljs │ │ │ │ ├── path.cljs │ │ │ │ ├── polygon.cljs │ │ │ │ ├── polyline.cljs │ │ │ │ ├── polyshape.cljs │ │ │ │ └── rect.cljs │ │ │ └── text.cljs │ │ ├── subs.cljs │ │ └── views.cljs │ ├── event │ │ ├── db.cljs │ │ ├── effects.cljs │ │ ├── events.cljs │ │ ├── handlers.cljs │ │ └── impl │ │ │ ├── drag.cljs │ │ │ ├── keyboard.cljs │ │ │ ├── pointer.cljs │ │ │ └── wheel.cljs │ ├── events.cljs │ ├── frame │ │ ├── README.md │ │ ├── db.cljs │ │ ├── events.cljs │ │ ├── handlers.cljs │ │ ├── subs.cljs │ │ └── views.cljs │ ├── history │ │ ├── README.md │ │ ├── db.cljs │ │ ├── events.cljs │ │ ├── handlers.cljs │ │ ├── subs.cljs │ │ └── views.cljs │ ├── main.css │ ├── menubar │ │ ├── events.cljs │ │ ├── filters.cljs │ │ ├── styles.css │ │ └── views.cljs │ ├── notification │ │ ├── db.cljs │ │ ├── events.cljs │ │ ├── handlers.cljs │ │ ├── subs.cljs │ │ └── views.cljs │ ├── overrides.css │ ├── reepl │ │ ├── LICENSE │ │ ├── README.md │ │ ├── codemirror.cljs │ │ ├── db.cljs │ │ ├── handlers.cljs │ │ ├── replumb.cljs │ │ ├── show_devtools.cljs │ │ ├── show_function.cljs │ │ ├── show_value.cljs │ │ ├── subs.cljs │ │ └── views.cljs │ ├── ruler │ │ ├── db.cljs │ │ ├── events.cljs │ │ ├── handlers.cljs │ │ ├── subs.cljs │ │ └── views.cljs │ ├── snap │ │ ├── db.cljs │ │ ├── events.cljs │ │ ├── handlers.cljs │ │ ├── subs.cljs │ │ └── views.cljs │ ├── theme │ │ ├── db.cljs │ │ ├── effects.cljs │ │ ├── events.cljs │ │ ├── handlers.cljs │ │ ├── styles.css │ │ └── subs.cljs │ ├── timeline │ │ ├── db.cljs │ │ ├── effects.cljs │ │ ├── events.cljs │ │ ├── subs.cljs │ │ └── views.cljs │ ├── tool │ │ ├── db.cljs │ │ ├── events.cljs │ │ ├── handlers.cljs │ │ ├── hierarchy.cljs │ │ ├── impl │ │ │ ├── base │ │ │ │ ├── core.cljs │ │ │ │ ├── edit.cljs │ │ │ │ ├── pan.cljs │ │ │ │ ├── transform.cljs │ │ │ │ └── zoom.cljs │ │ │ ├── core.cljs │ │ │ ├── draw │ │ │ │ ├── brush.cljs │ │ │ │ ├── core.cljs │ │ │ │ └── pen.cljs │ │ │ ├── element │ │ │ │ ├── circle.cljs │ │ │ │ ├── core.cljs │ │ │ │ ├── ellipse.cljs │ │ │ │ ├── image.cljs │ │ │ │ ├── line.cljs │ │ │ │ ├── polygon.cljs │ │ │ │ ├── polyline.cljs │ │ │ │ ├── polyshape.cljs │ │ │ │ ├── rect.cljs │ │ │ │ ├── svg.cljs │ │ │ │ └── text.cljs │ │ │ ├── extension │ │ │ │ ├── blob.cljs │ │ │ │ └── core.cljs │ │ │ └── misc │ │ │ │ ├── core.cljs │ │ │ │ ├── dropper.cljs │ │ │ │ ├── fill.cljs │ │ │ │ └── measure.cljs │ │ ├── subs.cljs │ │ └── views.cljs │ ├── toolbar │ │ ├── object.cljs │ │ ├── status.cljs │ │ ├── tools.cljs │ │ └── views.cljs │ ├── tree │ │ ├── effects.cljs │ │ ├── events.cljs │ │ └── views.cljs │ ├── utilities.css │ ├── utils │ │ ├── attribute.cljs │ │ ├── bounds.cljs │ │ ├── compatibility.cljs │ │ ├── dom.cljs │ │ ├── element.cljs │ │ ├── error.cljs │ │ ├── extra.cljs │ │ ├── hiccup.cljs │ │ ├── i18n.cljs │ │ ├── length.cljs │ │ ├── map.cljs │ │ ├── math.cljs │ │ ├── migration.cljs │ │ ├── path.cljs │ │ ├── svg.cljs │ │ ├── unit.cljs │ │ └── vec.cljs │ ├── views.cljs │ ├── window │ │ ├── db.cljs │ │ ├── effects.cljs │ │ ├── events.cljs │ │ ├── subs.cljs │ │ └── views.cljs │ └── worker │ │ ├── effects.cljs │ │ ├── events.cljs │ │ └── subs.cljs ├── user.cljs └── worker │ └── core.cljs ├── test ├── app_test.cljs ├── benchmark.cljs ├── core_test.cljs ├── document_test.cljs ├── element_impl_test.cljs ├── element_test.cljs ├── frame_test.cljs ├── history_test.cljs ├── notification_test.cljs ├── theme_test.cljs ├── tool_test.cljs ├── utils │ ├── attribute_test.cljs │ ├── bounds_test.cljs │ ├── compatibility_test.cljs │ ├── element_test.cljs │ ├── extra_test.cljs │ ├── length_test.cljs │ ├── map_test.cljs │ ├── unit_test.cljs │ └── vec_test.cljs └── window_test.cljs └── typos.toml /.clj-kondo/babashka/sci/config.edn: -------------------------------------------------------------------------------- 1 | {:hooks {:macroexpand {sci.core/copy-ns sci.core/copy-ns}}} 2 | -------------------------------------------------------------------------------- /.clj-kondo/babashka/sci/sci/core.clj: -------------------------------------------------------------------------------- 1 | (ns sci.core) 2 | 3 | (defmacro copy-ns 4 | ([ns-sym sci-ns] 5 | `(copy-ns ~ns-sym ~sci-ns nil)) 6 | ([ns-sym sci-ns opts] 7 | `[(quote ~ns-sym) 8 | ~sci-ns 9 | (quote ~opts)])) 10 | -------------------------------------------------------------------------------- /.clj-kondo/cjohansen/dumdom/config.edn: -------------------------------------------------------------------------------- 1 | {:linters {:dumdom/component-options {:level :warning}} 2 | :hooks {:analyze-call {dumdom.core/defcomponent dumdom.defcomponent/defcomponent}}} 3 | -------------------------------------------------------------------------------- /.clj-kondo/funcool/promesa/config.edn: -------------------------------------------------------------------------------- 1 | {:lint-as {promesa.core/-> clojure.core/-> 2 | promesa.core/->> clojure.core/->> 3 | promesa.core/as-> clojure.core/as-> 4 | promesa.core/let clojure.core/let 5 | promesa.core/plet clojure.core/let 6 | promesa.core/loop clojure.core/loop 7 | promesa.core/recur clojure.core/recur 8 | promesa.core/with-redefs clojure.core/with-redefs 9 | promesa.core/doseq clojure.core/doseq}} 10 | -------------------------------------------------------------------------------- /.clj-kondo/hooks.clj: -------------------------------------------------------------------------------- 1 | (ns hooks 2 | (:require [clj-kondo.hooks-api :as api])) 3 | 4 | (defn ^:export => [{:keys [node]}] 5 | (let [[macro-sym name-node & schema-nodes] (:children node) 6 | name-with-meta (with-meta name-node {:clj-kondo/ignore-reference true})] 7 | {:node (api/list-node (list* macro-sym name-with-meta schema-nodes))})) 8 | -------------------------------------------------------------------------------- /.clj-kondo/metosin/malli-types-clj/config.edn: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.clj-kondo/metosin/malli/config.edn: -------------------------------------------------------------------------------- 1 | {:lint-as {malli.experimental/defn schema.core/defn} 2 | :linters {:unresolved-symbol {:exclude [(malli.core/=>)]}}} 3 | -------------------------------------------------------------------------------- /.clj-kondo/no.cjohansen/portfolio/config.edn: -------------------------------------------------------------------------------- 1 | {:linters {:portfolio/component-options {:level :warning}} 2 | :hooks {:analyze-call {portfolio.dom/defscene portfolio.defscene/defscene 3 | portfolio.dumdom/defscene portfolio.defscene/defscene 4 | portfolio.html/defscene portfolio.defscene/defscene 5 | portfolio.react/defscene portfolio.defscene/defscene 6 | portfolio.react-18/defscene portfolio.defscene/defscene 7 | portfolio.reagent/defscene portfolio.defscene/defscene 8 | portfolio.reagent-18/defscene portfolio.defscene/defscene 9 | portfolio.replicant/defscene portfolio.defscene/defscene 10 | portfolio.rum/defscene portfolio.defscene/defscene}}} 11 | -------------------------------------------------------------------------------- /.clj-kondo/reagent/reagent/config.edn: -------------------------------------------------------------------------------- 1 | {:lint-as {reagent.core/with-let clojure.core/let}} 2 | -------------------------------------------------------------------------------- /.clj-kondo/rewrite-clj/rewrite-clj/config.edn: -------------------------------------------------------------------------------- 1 | {:lint-as 2 | {rewrite-clj.zip/subedit-> clojure.core/-> 3 | rewrite-clj.zip/subedit->> clojure.core/->> 4 | rewrite-clj.zip/edit-> clojure.core/-> 5 | rewrite-clj.zip/edit->> clojure.core/->>}} 6 | -------------------------------------------------------------------------------- /.clj-kondo/taoensso/encore/config.edn: -------------------------------------------------------------------------------- 1 | {:hooks 2 | {:analyze-call 3 | {taoensso.encore/defalias taoensso.encore/defalias 4 | taoensso.encore/defn-cached taoensso.encore/defn-cached}}} 5 | -------------------------------------------------------------------------------- /.clj-kondo/taoensso/encore/taoensso/encore.clj: -------------------------------------------------------------------------------- 1 | (ns taoensso.encore 2 | "I don't personally use clj-kondo, so these hooks are 3 | kindly authored and maintained by contributors. 4 | PRs very welcome! - Peter Taoussanis" 5 | (:require 6 | [clj-kondo.hooks-api :as hooks])) 7 | 8 | (defn defalias 9 | [{:keys [node]}] 10 | (let [[sym-raw src-raw] (rest (:children node)) 11 | src (if src-raw src-raw sym-raw) 12 | sym 13 | (if src-raw 14 | sym-raw 15 | (symbol (name (hooks/sexpr src))))] 16 | 17 | {:node 18 | (with-meta 19 | (hooks/list-node 20 | [(hooks/token-node 'def) 21 | (hooks/token-node (hooks/sexpr sym)) 22 | (hooks/token-node (hooks/sexpr src))]) 23 | (meta src))})) 24 | 25 | (defn defn-cached 26 | [{:keys [node] :as x}] 27 | (let [[sym _opts binding-vec & body] (rest (:children node))] 28 | {:node 29 | (hooks/list-node 30 | (list 31 | (hooks/token-node 'def) 32 | sym 33 | (hooks/list-node 34 | (list* 35 | (hooks/token-node 'fn) 36 | binding-vec 37 | body))))})) 38 | -------------------------------------------------------------------------------- /.cljfmt.edn: -------------------------------------------------------------------------------- 1 | {:sort-ns-references? true} 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | 9 | [*{clj,cljc,cljs,js,edn,css}] 10 | indent_style = space 11 | indent_size = 2 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [repath-project] 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **System (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Version [e.g. 22] 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | Please include a summary of the changes and the related issue. Please also include relevant motivation and context. 4 | 5 | Fixes # (issue) 6 | 7 | ## Type of change 8 | 9 | Please delete options that are not relevant. 10 | 11 | - [ ] Bug fix (non-breaking change which fixes an issue) 12 | - [ ] New feature (non-breaking change which adds functionality) 13 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 14 | - [ ] This change requires a documentation update 15 | 16 | # How Has This Been Tested? 17 | 18 | Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration 19 | 20 | - [ ] Test A 21 | - [ ] Test B 22 | 23 | **Test Configuration**: 24 | * Firmware version: 25 | * Hardware: 26 | * Toolchain: 27 | * SDK: 28 | 29 | # Checklist: 30 | 31 | - [ ] My code follows the style guidelines of this project 32 | - [ ] I have performed a self-review of my code 33 | - [ ] I have commented my code, particularly in hard-to-understand areas 34 | - [ ] I have made corresponding changes to the documentation 35 | - [ ] My changes generate no new warnings 36 | - [ ] I have added tests that prove my fix is effective or that my feature works 37 | - [ ] New and existing unit tests pass locally with my changes 38 | - [ ] Any dependent changes have been merged and published in downstream modules 39 | -------------------------------------------------------------------------------- /.github/workflows/clj-holmes.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | name: Security testing 7 | 8 | on: 9 | push: 10 | branches: [ main ] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [ main ] 14 | schedule: 15 | - cron: '40 14 * * 6' 16 | 17 | permissions: 18 | contents: read 19 | 20 | jobs: 21 | clj-holmes: 22 | name: Run clj-holmes scanning 23 | runs-on: ubuntu-latest 24 | permissions: 25 | contents: read 26 | security-events: write 27 | steps: 28 | - name: Checkout code 29 | uses: actions/checkout@v4 30 | with: 31 | persist-credentials: false 32 | 33 | - name: Scan code 34 | uses: clj-holmes/clj-holmes-action@53daa4da4ff495cccf791e4ba4222a8317ddae9e 35 | with: 36 | rules-repository: 'git://clj-holmes/clj-holmes-rules#main' 37 | output-type: 'sarif' 38 | output-file: 'clj-holmes-results.sarif' 39 | fail-on-result: 'false' 40 | 41 | - name: Upload analysis results to GitHub Security tab 42 | uses: github/codeql-action/upload-sarif@v3 43 | with: 44 | sarif_file: ${{github.workspace}}/clj-holmes-results.sarif 45 | wait-for-processing: true 46 | -------------------------------------------------------------------------------- /.github/workflows/dependencies.yml: -------------------------------------------------------------------------------- 1 | name: Outdated dependencies 2 | 3 | on: 4 | push: 5 | schedule: 6 | - cron: '0 21 * * *' 7 | 8 | jobs: 9 | antq: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | persist-credentials: false 16 | - uses: liquidz/antq-action@main 17 | with: 18 | excludes: 'org.clojure/tools.deps.alpha lambdaisland/deep-diff2' 19 | -------------------------------------------------------------------------------- /.github/workflows/sponsors.yml: -------------------------------------------------------------------------------- 1 | name: Add GitHub Sponsors to Readme 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: 30 15 * * 0-6 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 🛎️ 13 | uses: actions/checkout@v4 14 | with: 15 | persist-credentials: false 16 | 17 | - name: Generate Sponsors 💖 18 | uses: JamesIves/github-sponsors-readme-action@v1 19 | with: 20 | token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} 21 | file: 'README.md' 22 | 23 | - name: Deploy to GitHub Pages 🚀 24 | uses: JamesIves/github-pages-deploy-action@v4 25 | with: 26 | branch: main 27 | folder: '.' 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /.calva/ 3 | /.cpcache/ 4 | /.lsp/ 5 | /.vscode/ 6 | /resources/public/js/ 7 | /resources/public/css/ 8 | /resources/public/src/ 9 | /resources/*.js 10 | /target/ 11 | /*-init.clj 12 | /*.log 13 | 14 | # Node.js dependencies 15 | /node_modules/ 16 | 17 | # shadow-cljs cache, port files 18 | /.shadow-cljs/ 19 | 20 | # clj-kondo cache 21 | /.clj-kondo/.cache 22 | 23 | # Build 24 | /dist/ 25 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | Please [open a security advisory](https://github.com/repath-project/repath-studio/security/advisories/new) or send a detailed mail to contact@repath.studio to report security vulnerabilities. 6 | Even when unsure whether the bug in question is an exploitable vulnerability, it is recommended to use the above methods and not discuss the issue anywhere else. 7 | -------------------------------------------------------------------------------- /cla.json: -------------------------------------------------------------------------------- 1 | { 2 | "signedContributors": [ 3 | { 4 | "name": "WonderlustKing", 5 | "id": 6639267, 6 | "comment_id": 1060054205, 7 | "created_at": "2022-03-06T22:46:20Z", 8 | "repoId": 374744219, 9 | "pullRequestNo": 32 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /portfolio/resources/public/.gitignore: -------------------------------------------------------------------------------- 1 | js 2 | -------------------------------------------------------------------------------- /portfolio/resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Hello, Portfolio! 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /portfolio/src/pages/sections.cljs: -------------------------------------------------------------------------------- 1 | (ns pages.sections 2 | (:require 3 | [portfolio.reagent-18 :refer-macros [defscene]] 4 | [renderer.app.subs] 5 | [renderer.app.views :as app.views])) 6 | 7 | (defscene home 8 | :title "Home" 9 | :params (atom ["path/to/file/name.rps"]) 10 | [store] 11 | [:div.flex.flex-col.h-dvh.overflow-hidden 12 | [app.views/home @store]]) 13 | -------------------------------------------------------------------------------- /portfolio/src/portfolio.cljs: -------------------------------------------------------------------------------- 1 | (ns portfolio 2 | (:require [pages.components] 3 | [pages.sections] 4 | [portfolio.ui :as ui])) 5 | 6 | (ui/start! 7 | {:config 8 | {:css-paths ["/main.css"] 9 | :background/options [{:id :light-mode 10 | :title "Light mode" 11 | :value {:background/background-color "#fff" 12 | :background/document-data {:theme "light"}}} 13 | {:id :dark-mode 14 | :title "Dark mode" 15 | :value {:background/background-color "#111" 16 | :background/document-data {:theme "dark"}}}] 17 | :background/default-option-id :dark-mode 18 | :viewport/defaults {:viewport/padding [0] 19 | :viewport/width "100%" 20 | :viewport/height "500px"} 21 | :canvas/gallery-defaults {:viewport/padding [0] 22 | :viewport/width "100%" 23 | :viewport/height 41}}}) 24 | 25 | (defn ^:export init! []) 26 | -------------------------------------------------------------------------------- /resources/public/icons/LICENSE: -------------------------------------------------------------------------------- 1 | # Brand Icons 2 | 3 | - android_head 4 | - chrome 5 | - chrome_android 6 | - edge 7 | - firefox 8 | - firefox_android 9 | - ie 10 | - oculus 11 | - opera 12 | - opera_android 13 | - safari 14 | - safari_ios 15 | - samsunginternet_android 16 | - webview_android 17 | 18 | All brand icons are trademarks of their respective owners. 19 | Brand icons should only be used to represent the company or product to which they refer. 20 | Please do not use brand logos for any purpose except to represent that particular brand or service. 21 | -------------------------------------------------------------------------------- /resources/public/icons/a11y.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/android_head.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/animation.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/arc.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/bezier-curve.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/bring-forward.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/bring-front.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/checkmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/chemical-element.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/chevron-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/chevron-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/chevron-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/chevron-up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/chrome.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/circle-tool.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/code.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/commit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/cut.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/degrees.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/delete.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/deselect-all.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/distribute-spacing-horizontal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/distribute-spacing-vertical.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/dot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/download.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/public/icons/edit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/ellipse-tool.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/ellipse.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/ellipsis-h.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/ellipsis-v.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/exit.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/public/icons/export.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/eye-closed.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/eye-dropper.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/eye.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/file.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/flip-horizontal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/flip-vertical.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/focus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/folder-plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/folder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/go-to-end.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/go-to-start.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/grid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/group.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/hand.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/history.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/ie.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/image.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/import.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/info.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/intersect.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/lgpl.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/line-tool.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/line.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/linecap-butt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/linecap-round.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/linecap-square.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/list.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/public/icons/lock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/magnet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/magnifier.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/minus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/objects-align-bottom.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/objects-align-center-horizontal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/objects-align-center-vertical.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/objects-align-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/objects-align-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/objects-align-top.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/oculus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/opera.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/page-plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/page.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/paste.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/pause.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/pencil.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/play.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/pointer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/polygon-tool.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/polygon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/polyline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/printer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/properties.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/rectangle-tool.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/rectangle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/redo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/refresh.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/rotate-clockwise.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/rotate-counterclockwise.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/ruler-combined.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/ruler-triangle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/save-as.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/public/icons/save.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/send-back.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/send-backward.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/shell.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/public/icons/spinner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/square+plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/square-minus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/stop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/subtract.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/svg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/swap-horizontal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/swap-vertical.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/system.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/text.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/timeline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/times.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/tree.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/triangle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/undo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/ungroup.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/unlock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/webview_android.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/window-close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/window-maximize.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/window-minimize.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/window-restore.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/zoom-in.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/icons/zoom-out.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/public/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/repath-project/repath-studio/1c9c08aebd0915538a169898470aea55c3f67af0/resources/public/img/favicon.png -------------------------------------------------------------------------------- /resources/public/img/icon-no-bg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /resources/public/img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/repath-project/repath-studio/1c9c08aebd0915538a169898470aea55c3f67af0/resources/public/img/icon.png -------------------------------------------------------------------------------- /resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Repath Studio 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /resources/public/loading.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Loading 7 | 8 | 9 | 10 |
11 |
12 | 13 |
14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/config.cljs: -------------------------------------------------------------------------------- 1 | (ns config) 2 | 3 | (goog-define ^js/String version "unknown") 4 | 5 | (def debug? ^boolean goog.DEBUG) 6 | 7 | (def ext "rps") 8 | 9 | (def app-key :repath-studio) 10 | 11 | (def app-name "Repath Studio") 12 | 13 | (def mime-type "application/x-repath-studio") 14 | 15 | (def default-path "documents") 16 | -------------------------------------------------------------------------------- /src/dev.cljs: -------------------------------------------------------------------------------- 1 | (ns dev 2 | {:dev/always true} 3 | (:require 4 | [clojure.pprint :refer (pprint)] 5 | [clojure.string :as string] 6 | [malli.dev.cljs :as dev] 7 | [re-frame.core :as rf] 8 | [renderer.app.events :as app.events])) 9 | 10 | (comment 11 | ;; Enable full db validation 12 | (rf/reg-global-interceptor app.events/schema-validator) 13 | (rf/clear-global-interceptor ::app.events/schema-validator) 14 | 15 | ;; Enable function instrumentation 16 | ;; https://github.com/metosin/malli/blob/master/docs/clojurescript-function-instrumentation.md 17 | (dev/start!) 18 | (dev/stop!) 19 | 20 | (pprint (string/trim "This line suppresses some clj-kondo warnings."))) 21 | -------------------------------------------------------------------------------- /src/electron/preload.cljs: -------------------------------------------------------------------------------- 1 | (ns electron.preload 2 | "https://www.electronjs.org/docs/latest/tutorial/tutorial-preload" 3 | (:require 4 | ["electron" :refer [contextBridge ipcRenderer]] 5 | ["font-scanner" :as fontManager] 6 | ["opentype.js" :as opentype] 7 | ["os" :as os])) 8 | 9 | (defn text->path 10 | "https://github.com/opentypejs/opentype.js#loading-a-font-synchronously-nodejs" 11 | [text {:keys [font-url x y font-size]}] 12 | (let [font (.loadSync opentype font-url) 13 | path (.getPath font text x y font-size)] 14 | (.toPathData path))) 15 | 16 | (defonce api 17 | {;; Strip event as it includes `sender` 18 | ;; https://www.electronjs.org/docs/latest/api/ipc-renderer#ipcrendereronchannel-listener 19 | :on (fn [channel f] (.on ipcRenderer channel (fn [_e args] (f args)))) 20 | :send (fn [channel args] (.send ipcRenderer channel args)) 21 | :invoke (fn [channel args] (.invoke ipcRenderer channel args)) 22 | :platform (.platform os) 23 | :findFont (fn [descriptor] (.findFontSync fontManager descriptor)) 24 | :textToPath (fn [s options] (text->path s (js->clj options :keywordize-keys true)))}) 25 | 26 | (defn ^:export init! [] 27 | ;; Expose protected methods that allow the renderer process to use the 28 | ;; ipcRenderer without exposing the entire object 29 | ;; https://www.electronjs.org/docs/api/context-bridge 30 | (.exposeInMainWorld contextBridge "api" (clj->js api))) 31 | -------------------------------------------------------------------------------- /src/lang/el-GR.edn: -------------------------------------------------------------------------------- 1 | {:missing "Λείπει η μετάφραση για :el-GR" 2 | :cmdk 3 | {:search-command "Αναζήτηση για εντολή" 4 | :no-results "Δεν βρέθηκαν αποτελέσματα."} 5 | 6 | :color 7 | {:swap "Ανταλλάξτε το γέμισμα με τη γραμμή"}} 8 | -------------------------------------------------------------------------------- /src/lang/en-US.edn: -------------------------------------------------------------------------------- 1 | {:missing "Missing translation for :en-US" 2 | :cmdk 3 | {:search-command "Search for a command" 4 | :no-results "No results found."} 5 | 6 | :color 7 | {:swap "Swap fill with stroke"}} 8 | -------------------------------------------------------------------------------- /src/renderer/attribute/hierarchy.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.attribute.hierarchy) 2 | 3 | (defmulti update-attr (fn [_ k & _more] k)) 4 | (defmulti description (fn [tag k] [tag k])) 5 | (defmulti form-element (fn [tag k _v _attrs] [tag k])) 6 | 7 | (defmethod update-attr :default 8 | [el k f & more] 9 | (apply update-in el [:attrs k] f more)) 10 | -------------------------------------------------------------------------------- /src/renderer/attribute/impl/angle.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.attribute.impl.angle 2 | "https://developer.mozilla.org/en-US/docs/Web/SVG/Content_type#angle" 3 | (:require 4 | ["@radix-ui/react-popover" :as Popover] 5 | [renderer.attribute.hierarchy :as attribute.hierarchy] 6 | [renderer.attribute.views :as attribute.views] 7 | [renderer.views :as views])) 8 | 9 | (defmethod attribute.hierarchy/form-element [:default ::angle] 10 | [_ k v attrs] 11 | [:div.flex.gap-px.w-full 12 | [attribute.views/form-input k v attrs] 13 | [:> Popover/Root {:modal true} 14 | [:> Popover/Trigger 15 | {:title "Pick angle" 16 | :class "form-control-button"} 17 | [views/icon "degrees"]] 18 | [:> Popover/Portal 19 | [:> Popover/Content 20 | {:sideOffset 5 21 | :className "popover-content" 22 | :align "end"} 23 | [:div.circular-slider] 24 | [:> Popover/Arrow {:class "popover-arrow"}]]]]]) 25 | -------------------------------------------------------------------------------- /src/renderer/attribute/impl/clock.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.attribute.impl.clock 2 | "https://developer.mozilla.org/en-US/docs/Web/SVG/Content_type#clock-value") 3 | 4 | #_(def units 5 | {:ms 0.001 6 | :s 1 7 | :m 60 8 | :h 3600}) 9 | -------------------------------------------------------------------------------- /src/renderer/attribute/impl/color.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.attribute.impl.color 2 | "https://developer.mozilla.org/en-US/docs/Web/SVG/Content_type#color" 3 | (:require 4 | ["@radix-ui/react-popover" :as Popover] 5 | ["@repath-project/react-color" :refer [ChromePicker]] 6 | [re-frame.core :as rf] 7 | [renderer.attribute.hierarchy :as attribute.hierarchy] 8 | [renderer.attribute.views :as attribute.views] 9 | [renderer.element.events :as-alias element.events])) 10 | 11 | (derive :stroke ::color) 12 | (derive :fill ::color) 13 | (derive :color ::color) 14 | 15 | (defmethod attribute.hierarchy/form-element [:default ::color] 16 | [_ k v {:keys [disabled] :as attrs}] 17 | [:div.flex.gap-px.w-full 18 | [attribute.views/form-input k v attrs] 19 | [:> Popover/Root {:modal true} 20 | [:> Popover/Trigger 21 | {:as-child true 22 | :disabled disabled} 23 | [:button.button.color-drip.inline-block 24 | {:title "Pick color" 25 | :style {:border "5px solid var(--bg-primary)" 26 | :background v}}]] 27 | [:> Popover/Portal 28 | [:> Popover/Content 29 | {:sideOffset 5 30 | :className "popover-content" 31 | :align "end"} 32 | [:> ChromePicker 33 | {:color (or v "") 34 | :on-change-complete #(rf/dispatch [::element.events/set-attr k (.-hex %)]) 35 | :on-change #(rf/dispatch [::element.events/preview-attr k (.-hex %)])}]]]]]) 36 | -------------------------------------------------------------------------------- /src/renderer/attribute/impl/font_size.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.attribute.impl.font-size 2 | "https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-size" 3 | (:require 4 | [renderer.attribute.hierarchy :as attribute.hierarchy])) 5 | 6 | (defmethod attribute.hierarchy/description [:default :font-size] 7 | [] 8 | "The font-size attribute refers to the size of the font from baseline to 9 | baseline when multiple lines of text are set solid in a multiline layout environment.") 10 | 11 | (defmethod attribute.hierarchy/update-attr :font-size 12 | [el attribute f & more] 13 | (let [font-size (js/parseFloat (or (-> el :attrs attribute) 16))] 14 | (assoc-in el [:attrs attribute] (str (apply f font-size more))))) 15 | -------------------------------------------------------------------------------- /src/renderer/attribute/impl/font_weight.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.attribute.impl.font-weight 2 | "https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-weight" 3 | (:require 4 | [re-frame.core :as rf] 5 | [renderer.attribute.hierarchy :as attribute.hierarchy] 6 | [renderer.attribute.views :as attribute.views] 7 | [renderer.element.subs :as-alias element.subs])) 8 | 9 | (defmethod attribute.hierarchy/description [:default :font-weight] 10 | [] 11 | "The font-weight attribute refers to the boldness or lightness of the glyphs 12 | used to render the text, relative to other fonts in the same font family.") 13 | 14 | (def name-mapping 15 | "https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-weight#common_weight_name_mapping" 16 | {100 "Thin (Hairline)" 17 | 200 "Extra Light (Ultra Light)" 18 | 300 "Light" 19 | 400 "Normal" 20 | 500 "Medium" 21 | 600 "Semi Bold (Demi Bold)" 22 | 700 "Bold" 23 | 800 "Extra Bold (Ultra Bold)" 24 | 900 "Black (Heavy)"}) 25 | 26 | (defmethod attribute.hierarchy/form-element [:default :font-weight] 27 | [_ k v attrs] 28 | (let [weights @(rf/subscribe [::element.subs/font-weights]) 29 | weights (if (seq weights) weights (sort (keys name-mapping)))] 30 | [attribute.views/select-input k v 31 | (merge attrs 32 | {:default-value "400" 33 | :items (mapv #(do {:key % 34 | :label (str % " - " (-> % name-mapping)) 35 | :value (str %)}) weights)})])) 36 | -------------------------------------------------------------------------------- /src/renderer/attribute/impl/overflow.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.attribute.impl.overflow 2 | "https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/overflow" 3 | (:require 4 | [renderer.attribute.hierarchy :as attribute.hierarchy] 5 | [renderer.attribute.views :as attribute.views])) 6 | 7 | (defmethod attribute.hierarchy/description [:default :overflow] 8 | [] 9 | "The overflow attribute sets what to do when an element's content is too big 10 | to fit in its block formatting context. This feature is not widely 11 | implemented yet.") 12 | 13 | (defmethod attribute.hierarchy/form-element [:default :overflow] 14 | [_ k v {:keys [disabled]}] 15 | [attribute.views/select-input k v 16 | {:disabled disabled 17 | ;; Although the initial value for overflow is auto, it is overwritten 18 | ;; in the User Agent style sheet for the element when it is not 19 | ;; the root element of a stand-alone document, the element, 20 | ;; and the element to be hidden by default. 21 | :placeholder "hidden" 22 | :default-value "hidden" 23 | :items [{:key :visible 24 | :value "visible" 25 | :label "Visible"} 26 | {:key :hidden 27 | :value "hidden" 28 | :label "Hidden"} 29 | #_{:key :scroll 30 | :value "scroll" 31 | :label "Scroll"} 32 | #_{:key :auto 33 | :value "auto" 34 | :label "Auto"}]}]) 35 | -------------------------------------------------------------------------------- /src/renderer/attribute/impl/range.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.attribute.impl.range 2 | (:require 3 | [renderer.attribute.hierarchy :as attribute.hierarchy] 4 | [renderer.attribute.views :as attribute.views])) 5 | 6 | (derive :opacity ::range) 7 | 8 | (defmethod attribute.hierarchy/form-element [:default ::range] 9 | [_ k v attrs] 10 | [attribute.views/range-input k v (merge attrs {:min 0 11 | :max 1 12 | :step 0.01})]) 13 | -------------------------------------------------------------------------------- /src/renderer/attribute/impl/stroke_linecap.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.attribute.impl.stroke-linecap 2 | "https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/stroke-linecap" 3 | (:require 4 | [renderer.attribute.hierarchy :as attribute.hierarchy] 5 | [renderer.attribute.views :as attribute.views])) 6 | 7 | (defmethod attribute.hierarchy/description [:default :stroke-linecap] 8 | [] 9 | "The stroke-linecap attribute is a presentation attribute defining the shape 10 | to be used at the end of open subpaths when they are stroked.") 11 | 12 | (defmethod attribute.hierarchy/form-element [:default :stroke-linecap] 13 | [_ k v attrs] 14 | [attribute.views/select-input k v 15 | (merge attrs {:default-value "butt" 16 | :items [{:key :butt 17 | :value "butt" 18 | :label "Butt" 19 | :icon "linecap-butt"} 20 | {:key :round 21 | :value "round" 22 | :label "Round" 23 | :icon "linecap-round"} 24 | {:key :square 25 | :value "square" 26 | :label "Square" 27 | :icon "linecap-square"}]})]) 28 | -------------------------------------------------------------------------------- /src/renderer/attribute/impl/stroke_linejoin.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.attribute.impl.stroke-linejoin 2 | "https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/stroke-linejoin" 3 | (:require 4 | [renderer.attribute.hierarchy :as attribute.hierarchy] 5 | [renderer.attribute.views :as attribute.views])) 6 | 7 | (defmethod attribute.hierarchy/description [:default :stroke-linejoin] 8 | [] 9 | "The stroke-linejoin attribute is a presentation attribute defining the shape 10 | to be used at the corners of paths when they are stroked.") 11 | 12 | (defmethod attribute.hierarchy/form-element [:default :stroke-linejoin] 13 | [_ k v attrs] 14 | [attribute.views/select-input k v 15 | (merge attrs {:default-value "miter" 16 | :items [{:key :bevel 17 | :value "bevel" 18 | :label "Bevel"} 19 | {:key :miter 20 | :value "miter" 21 | :label "Miter"} 22 | {:key :round 23 | :value "round" 24 | :label "Round"}]})]) 25 | -------------------------------------------------------------------------------- /src/renderer/attribute/impl/style.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.attribute.impl.style 2 | "https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/style" 3 | (:require 4 | [re-frame.core :as rf] 5 | [renderer.attribute.hierarchy :as attribute.hierarchy] 6 | [renderer.element.events :as-alias element.events] 7 | [renderer.views :as views])) 8 | 9 | (defmethod attribute.hierarchy/form-element [:default :style] 10 | [_ k v {:keys [disabled]}] 11 | [:div.w-full.bg-primary.p-1 12 | [views/cm-editor v {:on-blur #(rf/dispatch [::element.events/set-attr k %]) 13 | :attrs {:id (name k)} 14 | :options {:mode "css" 15 | :readOnly disabled}}]]) 16 | -------------------------------------------------------------------------------- /src/renderer/attribute/impl/transform.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.attribute.impl.transform 2 | "https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform" 3 | (:require 4 | [renderer.attribute.hierarchy :as attribute.hierarchy])) 5 | 6 | (defmethod attribute.hierarchy/description [:default :transform] 7 | [] 8 | "The transform attribute defines a list of transform definitions that are 9 | applied to an element and the element's children.") 10 | -------------------------------------------------------------------------------- /src/renderer/dialog/db.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.dialog.db 2 | (:require 3 | [renderer.utils.hiccup :refer [Hiccup]])) 4 | 5 | (def Dialog 6 | [:map {:closed true} 7 | [:title {:optional true} Hiccup] 8 | [:content {:optional true} any?] 9 | [:close-button {:optional true} boolean?] 10 | [:attrs {:optional true} [:map [:as-child {:optional true :default false} boolean?] 11 | [:force-mount {:optional true} boolean?] 12 | [:on-open-auto-focus {:optional true} ifn?] 13 | [:on-close-auto-focus {:optional true} ifn?] 14 | [:on-escape-key-down {:optional true} ifn?] 15 | [:on-pointer-down-outside {:optional true} ifn?] 16 | [:on-interact-outside {:optional true} ifn?]]]]) 17 | -------------------------------------------------------------------------------- /src/renderer/dialog/events.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.dialog.events 2 | (:require 3 | [config :as config] 4 | [re-frame.core :as rf] 5 | [renderer.dialog.handlers :as dialog.handlers] 6 | [renderer.dialog.views :as dialog.views])) 7 | 8 | (rf/reg-event-db 9 | ::show-cmdk 10 | (fn [db [_]] 11 | (dialog.handlers/create db {:title [:div.sr-only "Command panel"] 12 | :content (dialog.views/cmdk) 13 | :attrs {:class "dialog-content" 14 | :style {:top "33px" 15 | :transform "translate(-50%, 0)"}}}))) 16 | 17 | (rf/reg-event-db 18 | ::show-about 19 | (fn [db [_]] 20 | (dialog.handlers/create db {:title config/app-name 21 | :close-button true 22 | :content (dialog.views/about)}))) 23 | 24 | (rf/reg-event-db 25 | ::show-confirmation 26 | (fn [db [_ data]] 27 | (dialog.handlers/create db {:title (:title data) 28 | :close-button true 29 | :content (dialog.views/confirmation data)}))) 30 | 31 | (rf/reg-event-fx 32 | ::close 33 | (fn [{:keys [db]} [_ on-close]] 34 | (cond-> {:db (update db :dialogs pop)} 35 | on-close 36 | (assoc :dispatch on-close)))) 37 | -------------------------------------------------------------------------------- /src/renderer/dialog/handlers.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.dialog.handlers 2 | (:require 3 | [malli.core :as m] 4 | [renderer.app.db :refer [App]] 5 | [renderer.dialog.db :refer [Dialog]])) 6 | 7 | (m/=> create [:-> App Dialog App]) 8 | (defn create 9 | [db dialog] 10 | (update db :dialogs conj dialog)) 11 | -------------------------------------------------------------------------------- /src/renderer/dialog/subs.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.dialog.subs 2 | (:require 3 | [re-frame.core :as rf])) 4 | 5 | (rf/reg-sub 6 | ::entities 7 | :-> :dialogs) 8 | -------------------------------------------------------------------------------- /src/renderer/element/db.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.element.db 2 | (:require 3 | [malli.core :as m] 4 | [malli.transform :as m.transform] 5 | [renderer.element.hierarchy :as element.hierarchy] 6 | [renderer.utils.bounds :refer [BBox]])) 7 | 8 | (defn tag? 9 | [k] 10 | (contains? (descendants ::element.hierarchy/element) k)) 11 | 12 | (def Tag 13 | [:fn {:error/fn (fn [{:keys [value]} _] (str value ", is not a supported tag"))} 14 | tag?]) 15 | 16 | (def AnimationTag 17 | [:enum :animate :animateTransform :animateMotion]) 18 | 19 | (def Attrs 20 | [:map-of keyword? string?]) 21 | 22 | (def Direction 23 | [:enum :top :center-vertical :bottom :left :center-horizontal :right]) 24 | 25 | (def Element 26 | [:map {:closed true} 27 | [:id {:optional true} uuid?] 28 | [:tag Tag] 29 | [:label {:optional true} string?] 30 | [:parent {:optional true} uuid?] 31 | [:type {:optional true} [:= :element]] 32 | [:visible {:optional true} boolean?] 33 | [:locked {:optional true} boolean?] 34 | [:selected {:optional true} boolean?] 35 | [:children {:default [] :optional true} [:vector uuid?]] 36 | [:bbox {:optional true} BBox] 37 | [:content {:optional true} string?] 38 | [:attrs {:optional true} Attrs]]) 39 | 40 | (def valid? (m/validator Element)) 41 | 42 | (def explain (m/explainer Element)) 43 | 44 | (def default (m/decode Element {:type :element 45 | :visible true 46 | :children []} m.transform/default-value-transformer)) 47 | -------------------------------------------------------------------------------- /src/renderer/element/hierarchy.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.element.hierarchy) 2 | 3 | ;; REVIEW: Is this type of complexity really needed? 4 | (derive ::graphics ::renderable) 5 | (derive ::gradient ::renderable) 6 | (derive ::descriptive ::renderable) 7 | (derive :foreignObject ::graphics) 8 | (derive :textPath ::graphics) 9 | (derive :tspan ::graphics) 10 | (derive :linearGradient ::gradient) 11 | (derive :radialGradient ::gradient) 12 | (derive :desc ::descriptive) 13 | (derive :metadata ::descriptive) 14 | (derive :title ::descriptive) 15 | 16 | (defmulti render :tag) 17 | (defmulti render-to-string :tag) 18 | (defmulti render-edit :tag) 19 | (defmulti path :tag) 20 | (defmulti area :tag) 21 | (defmulti centroid :tag) 22 | (defmulti snapping-points :tag) 23 | (defmulti bbox :tag) 24 | (defmulti translate (fn [el _offset] (:tag el))) 25 | (defmulti scale (fn [el _ratio _pivot-point] (:tag el))) 26 | (defmulti edit (fn [el _offset _handle] (:tag el))) 27 | (defmulti properties identity) 28 | 29 | (defmethod render :default []) 30 | (defmethod render-to-string :default [el] [render el]) 31 | (defmethod render-edit :default []) 32 | (defmethod bbox :default []) 33 | (defmethod area :default []) 34 | (defmethod centroid :default []) 35 | (defmethod snapping-points :default []) 36 | (defmethod translate :default [el _offset] el) 37 | (defmethod scale :default [el _ratio _pivot-point] el) 38 | (defmethod edit :default [el _offset _handle] el) 39 | (defmethod properties :default []) 40 | -------------------------------------------------------------------------------- /src/renderer/element/impl/animation/animate.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.element.impl.animation.animate 2 | "https://svgwg.org/specs/animations/#AnimateElement 3 | https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/animate" 4 | (:require 5 | [renderer.element.hierarchy :as element.hierarchy])) 6 | 7 | (derive :animate ::element.hierarchy/animation) 8 | 9 | (defmethod element.hierarchy/properties :animate 10 | [] 11 | {:icon "animation" 12 | :description "The SVG element provides a way to animate an 13 | attribute of an element over time." 14 | :attrs [:href 15 | :attributeName 16 | :begin 17 | :end 18 | :dur 19 | :min 20 | :max 21 | :restart 22 | :repeatCount 23 | :repeatDur 24 | :fill 25 | :calcMode 26 | :values 27 | :keyTimes 28 | :keySplines 29 | :from 30 | :to 31 | :by 32 | :autoReverse 33 | :accelerate 34 | :decelerate 35 | :additive 36 | :accumulate 37 | :id 38 | :class]}) 39 | -------------------------------------------------------------------------------- /src/renderer/element/impl/animation/animate_motion.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.element.impl.animation.animate-motion 2 | "https://svgwg.org/specs/animations/#AnimateMotionElement 3 | https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/animateMotion" 4 | (:require 5 | [renderer.element.hierarchy :as element.hierarchy])) 6 | 7 | (derive :animateMotion ::element.hierarchy/animation) 8 | 9 | (defmethod element.hierarchy/properties :animateMotion 10 | [] 11 | {:icon "animation" 12 | :description "The SVG element let define how an element 13 | moves along a motion path." 14 | :attrs [:keyPoints 15 | :path 16 | :rotate]}) 17 | -------------------------------------------------------------------------------- /src/renderer/element/impl/animation/animate_transform.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.element.impl.animation.animate-transform 2 | "https://svgwg.org/specs/animations/#AnimateTransformElement 3 | https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/animateTransform" 4 | (:require 5 | [renderer.element.hierarchy :as element.hierarchy])) 6 | 7 | (derive :animateTransform ::element.hierarchy/animation) 8 | 9 | (defmethod element.hierarchy/properties :animateTransform 10 | [] 11 | {:icon "animation" 12 | :description "The animateTransform element animates a transformation 13 | attribute on its target element, thereby allowing animations 14 | to control translation, scaling, rotation, and/or skewing." 15 | :attrs [:type 16 | :from 17 | :to 18 | :by]}) 19 | -------------------------------------------------------------------------------- /src/renderer/element/impl/animation/core.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.element.impl.animation.core 2 | "https://svgwg.org/specs/animations/#AnimationElements" 3 | (:require 4 | [re-frame.core :as rf] 5 | [renderer.element.hierarchy :as element.hierarchy] 6 | [renderer.element.impl.animation.animate] 7 | [renderer.element.impl.animation.animate-motion] 8 | [renderer.element.impl.animation.animate-transform] 9 | [renderer.element.subs :as-alias element.subs])) 10 | 11 | (derive ::element.hierarchy/animation ::element.hierarchy/descriptive) 12 | 13 | (defmethod element.hierarchy/render ::element.hierarchy/animation 14 | [el] 15 | (let [{:keys [children tag attrs id]} el 16 | child-elements @(rf/subscribe [::element.subs/filter-visible children])] 17 | [tag attrs (for [el child-elements] 18 | ^{:key id} [element.hierarchy/render el])])) 19 | -------------------------------------------------------------------------------- /src/renderer/element/impl/core.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.element.impl.core 2 | (:require 3 | [renderer.element.impl.animation.core] 4 | [renderer.element.impl.box] 5 | [renderer.element.impl.container.core] 6 | [renderer.element.impl.custom.core] 7 | [renderer.element.impl.renderable] 8 | [renderer.element.impl.shape.core] 9 | [renderer.element.impl.text])) 10 | -------------------------------------------------------------------------------- /src/renderer/element/impl/custom/core.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.element.impl.custom.core 2 | (:require 3 | [renderer.element.impl.custom.blob] 4 | [renderer.element.impl.custom.brush] 5 | [renderer.element.impl.custom.measure])) 6 | -------------------------------------------------------------------------------- /src/renderer/element/impl/custom/measure.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.element.impl.custom.measure 2 | (:require 3 | [re-frame.core :as rf] 4 | [renderer.document.subs :as-alias document.subs] 5 | [renderer.element.hierarchy :as element.hierarchy] 6 | [renderer.utils.length :as utils.length] 7 | [renderer.utils.math :as utils.math] 8 | [renderer.utils.svg :as utils.svg])) 9 | 10 | (derive :measure ::element.hierarchy/element) 11 | 12 | (defmethod element.hierarchy/render :measure 13 | [el] 14 | (let [{:keys [attrs id]} el 15 | {:keys [x1 x2 y1 y2 hypotenuse]} attrs 16 | [x1 y1 x2 y2] (map utils.length/unit->px [x1 y1 x2 y2]) 17 | angle (utils.math/angle [x1 y1] [x2 y2]) 18 | zoom @(rf/subscribe [::document.subs/zoom]) 19 | straight? (< angle 180) 20 | straight-angle (if straight? angle (- angle 360))] 21 | [:g {:key id} 22 | [utils.svg/cross [x1 y1]] 23 | [utils.svg/cross [x2 y2]] 24 | 25 | [utils.svg/arc [x1 y1] 20 (if straight? 0 angle) (abs straight-angle)] 26 | 27 | [utils.svg/line [x1 y1] [x2 y2] false] 28 | [utils.svg/line [x1 y1] [(+ x1 (/ 30 zoom)) y1]] 29 | 30 | [utils.svg/label 31 | (str (.toFixed straight-angle 2) "°") 32 | [(+ x1 (/ 40 zoom)) y1] 33 | "start"] 34 | 35 | [utils.svg/label 36 | (-> hypotenuse js/parseFloat (.toFixed 2) str) 37 | [(/ (+ x1 x2) 2) (/ (+ y1 y2) 2)]]])) 38 | -------------------------------------------------------------------------------- /src/renderer/element/impl/shape/core.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.element.impl.shape.core 2 | "https://www.w3.org/TR/SVG/shapes.html#TermShapeElement" 3 | (:require 4 | [re-frame.core :as rf] 5 | [renderer.element.hierarchy :as element.hierarchy] 6 | [renderer.element.impl.shape.circle] 7 | [renderer.element.impl.shape.ellipse] 8 | [renderer.element.impl.shape.image] 9 | [renderer.element.impl.shape.line] 10 | [renderer.element.impl.shape.path] 11 | [renderer.element.impl.shape.polygon] 12 | [renderer.element.impl.shape.polyline] 13 | [renderer.element.impl.shape.polyshape] 14 | [renderer.element.impl.shape.rect] 15 | [renderer.element.subs :as-alias element.subs] 16 | [renderer.utils.element :as utils.element])) 17 | 18 | (derive ::element.hierarchy/shape ::element.hierarchy/graphics) 19 | 20 | (defmethod element.hierarchy/render-to-string ::element.hierarchy/shape 21 | [el] 22 | (let [{:keys [tag attrs title children content]} el 23 | child-elements @(rf/subscribe [::element.subs/filter-visible children]) 24 | attrs (->> (utils.element/style->map attrs) 25 | (remove #(empty? (str (second %)))) 26 | (into {}))] 27 | (into [tag attrs (when title [:title title]) content] 28 | (map element.hierarchy/render-to-string child-elements)))) 29 | -------------------------------------------------------------------------------- /src/renderer/element/impl/shape/image.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.element.impl.shape.image 2 | "https://www.w3.org/TR/SVG/embedded.html#ImageElement 3 | https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/image" 4 | (:require 5 | [renderer.element.hierarchy :as element.hierarchy])) 6 | 7 | (derive :image ::element.hierarchy/graphics) 8 | (derive :image ::element.hierarchy/box) 9 | 10 | (defmethod element.hierarchy/properties :image 11 | [] 12 | {:icon "image" 13 | :description "The SVG element includes images inside SVG documents. 14 | It can display raster image files or other SVG files." 15 | :attrs [:href]}) 16 | -------------------------------------------------------------------------------- /src/renderer/element/impl/shape/polygon.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.element.impl.shape.polygon 2 | "https://www.w3.org/TR/SVG/shapes.html#PolygonElement 3 | https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/polygon" 4 | (:require [renderer.element.hierarchy :as element.hierarchy])) 5 | 6 | (derive :polygon ::element.hierarchy/polyshape) 7 | 8 | (defmethod element.hierarchy/properties :polygon 9 | [] 10 | {:icon "polygon" 11 | :description "The SVG element defines a closed shape consisting of 12 | a set of connected straight line segments. The last point is 13 | connected to the first point." 14 | :attrs [:stroke-width 15 | :fill 16 | :stroke 17 | :stroke-linejoin 18 | :stroke-linecap 19 | :stroke-dasharray 20 | :opacity]}) 21 | 22 | (defmethod element.hierarchy/path :polygon 23 | [el] 24 | (str "M" (-> el :attrs :points) "z")) 25 | -------------------------------------------------------------------------------- /src/renderer/element/impl/shape/polyline.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.element.impl.shape.polyline 2 | "https://www.w3.org/TR/SVG/shapes.html#PolylineElement 3 | https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/polyline" 4 | (:require [renderer.element.hierarchy :as element.hierarchy])) 5 | 6 | (derive :polyline ::element.hierarchy/polyshape) 7 | 8 | (defmethod element.hierarchy/properties :polyline 9 | [] 10 | {:icon "polyline" 11 | :description "The SVG element is an SVG basic shape that creates 12 | straight lines connecting several points. Typically a polyline 13 | is used to create open shapes as the last point doesn't have to 14 | be connected to the first point." 15 | :attrs [:stroke-width 16 | :fill 17 | :stroke 18 | :stroke-linejoin 19 | :stroke-linecap 20 | :stroke-dasharray 21 | :opacity]}) 22 | 23 | (defmethod element.hierarchy/path :polyline 24 | [el] 25 | (str "M" (-> el :attrs :points))) 26 | -------------------------------------------------------------------------------- /src/renderer/event/effects.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.event.effects 2 | (:require 3 | [re-frame.core :as rf] 4 | [renderer.element.events :as-alias element.events] 5 | [renderer.utils.dom :as utils.dom])) 6 | 7 | (rf/reg-fx 8 | ::set-pointer-capture 9 | (fn [pointer-id] 10 | (.setPointerCapture (utils.dom/canvas-element!) pointer-id))) 11 | 12 | (rf/reg-fx 13 | ::release-pointer-capture 14 | (fn [pointer-id] 15 | (.releasePointerCapture (utils.dom/canvas-element!) pointer-id))) 16 | 17 | (rf/reg-fx 18 | ::drop 19 | (fn [[position data-transfer]] 20 | (doseq [item (.-items data-transfer)] 21 | (when (= (.-kind item) "string") 22 | (let [[x y] position] 23 | (.getAsString item #(rf/dispatch [::element.events/add 24 | {:type :element 25 | :tag :text 26 | :content % 27 | :attrs {:x x 28 | :y y}}]))))) 29 | 30 | (doseq [file (.-files data-transfer)] 31 | (rf/dispatch [::element.events/import-file file position])))) 32 | -------------------------------------------------------------------------------- /src/renderer/event/events.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.event.events 2 | (:require 3 | [re-frame.core :as rf] 4 | [renderer.event.handlers :as event.handlers])) 5 | 6 | (rf/reg-event-db 7 | ::pointer 8 | (fn [db [_ e]] 9 | (event.handlers/pointer db e))) 10 | 11 | (rf/reg-event-db 12 | ::wheel 13 | (fn [db [_ e]] 14 | (event.handlers/wheel db e))) 15 | 16 | (rf/reg-event-db 17 | ::drag 18 | (fn [db [_ e]] 19 | (event.handlers/drag db e))) 20 | 21 | (rf/reg-event-db 22 | ::keyboard 23 | (fn [db [_ e]] 24 | (event.handlers/keyboard db e))) 25 | -------------------------------------------------------------------------------- /src/renderer/event/impl/drag.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.event.impl.drag 2 | (:require 3 | [malli.core :as m] 4 | [re-frame.core :as rf] 5 | [renderer.event.db :refer [DragEvent]] 6 | [renderer.event.events :as-alias event.events])) 7 | 8 | (m/=> ->clj [:-> any? DragEvent]) 9 | (defn ->clj 10 | "https://developer.mozilla.org/en-US/docs/Web/API/DragEvent" 11 | [^js/DragEvent e] 12 | {:type (.-type e) 13 | :pointer-pos [(.-pageX e) (.-pageY e)] 14 | :data-transfer (.-dataTransfer e)}) 15 | 16 | (defn handler! 17 | [^js/DragEvent e] 18 | (.stopPropagation e) 19 | (.preventDefault e) 20 | 21 | (rf/dispatch-sync [::event.events/drag (->clj e)])) 22 | -------------------------------------------------------------------------------- /src/renderer/event/impl/wheel.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.event.impl.wheel 2 | (:require 3 | [malli.core :as m] 4 | [re-frame.core :as rf] 5 | [renderer.event.db :refer [WheelEvent]] 6 | [renderer.event.events :as event.events])) 7 | 8 | (m/=> ->clj [:-> any? WheelEvent]) 9 | (defn ->clj 10 | "https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent" 11 | [^js/WheelEvent e] 12 | {:target (.-target e) 13 | :type (.-type e) 14 | :pointer-pos [(.-pageX e) (.-pageY e)] 15 | :delta-x (.-deltaX e) 16 | :delta-y (.-deltaY e) 17 | :delta-z (.-deltaZ e) 18 | :alt-key (.-altKey e) 19 | :ctrl-key (.-ctrlKey e) 20 | :meta-key (.-metaKey e) 21 | :shift-key (.-shiftKey e)}) 22 | 23 | (defn handler! 24 | [^js/WheelEvent e] 25 | (.stopPropagation e) 26 | (.preventDefault e) 27 | 28 | (rf/dispatch-sync [::event.events/wheel (->clj e)])) 29 | -------------------------------------------------------------------------------- /src/renderer/events.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.events 2 | (:require 3 | [re-frame.core :as rf] 4 | [renderer.effects :as-alias effects])) 5 | 6 | (rf/reg-event-fx 7 | ::focus 8 | (fn [_ [_ id]] 9 | {::effects/focus id})) 10 | 11 | (rf/reg-event-fx 12 | ::scroll-into-view 13 | (fn [_ [_ el]] 14 | {::effects/scroll-into-view el})) 15 | 16 | (rf/reg-event-fx 17 | ::scroll-to-bottom 18 | (fn [_ [_ el]] 19 | {::effects/scroll-to-bottom el})) 20 | 21 | (rf/reg-event-fx 22 | ::file-open 23 | (fn [_ [_ options]] 24 | {::effects/file-open options})) 25 | 26 | (rf/reg-event-fx 27 | ::open-remote-url 28 | (fn [{:keys [db]} [_ url]] 29 | (if (= (:platform db) "web") 30 | {::effects/open-remote-url url} 31 | {::effects/ipc-send ["open-remote-url" url]}))) 32 | -------------------------------------------------------------------------------- /src/renderer/frame/README.md: -------------------------------------------------------------------------------- 1 | # Frame 2 | 3 | This is an iFrame element that hosts anything that needs to be rendered. 4 | The main SVG canvas is rendered inside the iFrame using 5 | [react-frame-component](https://github.com/ryanseddon/react-frame-component). 6 | 7 | ## Why would you render to an iFrame? 8 | ### Style encapsulation 9 | We won't have to worry about style inheritance. 10 | ### Layout boundaries 11 | Canvas elements will not affect the parent window. 12 | 13 | ## Caveats 14 | The iFrame introduces a different browsing context so we need to simulate 15 | some of the events to the parent window. We also have to redeclare some styles, 16 | because we can't use the css variables of the parent window. 17 | -------------------------------------------------------------------------------- /src/renderer/frame/db.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.frame.db) 2 | 3 | (def DomRect 4 | [:map {:closed true} 5 | [:x number?] 6 | [:y number?] 7 | [:width number?] 8 | [:height number?] 9 | [:top number?] 10 | [:right number?] 11 | [:bottom number?] 12 | [:left number?]]) 13 | 14 | (def FocusType [:enum :original :fit :fill]) 15 | 16 | (def Viewbox 17 | [:tuple 18 | [number? {:title "x"}] 19 | [number? {:title "y"}] 20 | [number? {:title "width"}] 21 | [number? {:title "height"}]]) 22 | -------------------------------------------------------------------------------- /src/renderer/frame/subs.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.frame.subs 2 | (:require 3 | [clojure.string :as string] 4 | [re-frame.core :as rf] 5 | [renderer.app.subs :as-alias app.subs] 6 | [renderer.document.subs :as-alias document.subs] 7 | [renderer.frame.handlers :as frame.handlers])) 8 | 9 | (rf/reg-sub 10 | ::viewbox 11 | :<- [::document.subs/zoom] 12 | :<- [::document.subs/pan] 13 | :<- [::app.subs/dom-rect] 14 | (fn [[zoom pan dom-rect] _] 15 | (frame.handlers/viewbox zoom pan dom-rect))) 16 | 17 | (rf/reg-sub 18 | ::viewbox-attr 19 | :<- [::viewbox] 20 | (fn [viewbox _] 21 | (string/join " " viewbox))) 22 | -------------------------------------------------------------------------------- /src/renderer/history/db.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.history.db 2 | (:require 3 | [renderer.element.db :refer [Element]] 4 | [renderer.utils.math :refer [Vec2]])) 5 | 6 | (def HistoryState 7 | [:map {:closed true} 8 | [:explanation string?] 9 | [:timestamp number?] 10 | [:index [:or pos-int? zero?]] 11 | [:id uuid?] 12 | [:elements {:optional true} [:map-of uuid? Element]] 13 | [:parent {:optional true} uuid?] 14 | [:children [:vector uuid?]]]) 15 | 16 | (def History 17 | [:map {:closed true} 18 | [:zoom {:optional true :default 0.5} number?] 19 | [:translate {:optional true} Vec2] 20 | [:position {:optional true} uuid?] 21 | [:states {:default {}} [:map-of uuid? HistoryState]]]) 22 | -------------------------------------------------------------------------------- /src/renderer/main.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss'; 2 | 3 | @import 'codemirror/lib/codemirror.css'; 4 | @import 'codemirror/addon/hint/show-hint.css'; 5 | @import 'codemirror/theme/tomorrow-night-eighties.css'; 6 | 7 | @import './utilities.css'; 8 | @import './components.css'; 9 | @import './animations.css'; 10 | @import './overrides.css'; 11 | 12 | @import './theme/styles.css'; 13 | @import './menubar/styles.css'; 14 | 15 | body { 16 | font-size: var(--font-size); 17 | color: var(--font-color); 18 | fill: var(--font-color); 19 | } 20 | 21 | p { 22 | margin-bottom: 1rem; 23 | } 24 | 25 | input { 26 | @apply font-mono appearance-none; 27 | 28 | &:focus { 29 | @apply outline-hidden; 30 | } 31 | 32 | &::-webkit-outer-spin-button, 33 | &::-webkit-inner-spin-button { 34 | @apply appearance-none; 35 | } 36 | } 37 | 38 | label { 39 | @apply block text-right h-full text-muted overflow-hidden text-ellipsis w-auto; 40 | 41 | &:hover { 42 | color: var(--font-color-hovered); 43 | cursor: pointer; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/renderer/menubar/events.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.menubar.events 2 | (:require 3 | [re-frame.core :as rf] 4 | [renderer.events :as-alias events])) 5 | 6 | (rf/reg-event-fx 7 | ::select-item 8 | (fn [_ [_ dispatch]] 9 | {:dispatch-n [dispatch 10 | [::events/focus nil]]})) 11 | -------------------------------------------------------------------------------- /src/renderer/notification/db.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.notification.db 2 | (:require 3 | [renderer.utils.hiccup :refer [Hiccup]])) 4 | 5 | (def Notification 6 | [:map {:closed true} 7 | [:count pos-int?] 8 | [:content Hiccup]]) 9 | -------------------------------------------------------------------------------- /src/renderer/notification/events.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.notification.events 2 | (:require 3 | [re-frame.core :as rf] 4 | [renderer.notification.handlers :as notification.handlers] 5 | [renderer.notification.views :as notification.views] 6 | [renderer.utils.vec :as utils.vec])) 7 | 8 | (rf/reg-event-db 9 | ::add 10 | (fn [db [_ notification]] 11 | (cond-> db notification (notification.handlers/add notification)))) 12 | 13 | (rf/reg-event-db 14 | ::show-unavailable-feature 15 | (fn [db [_ feature compatibility-url]] 16 | (notification.handlers/add db (notification.views/unavailable-feature 17 | feature 18 | compatibility-url)))) 19 | 20 | (rf/reg-event-db 21 | ::show-exception 22 | (fn [db [_ ^js/Error error]] 23 | (notification.handlers/add db (notification.views/exception error)))) 24 | 25 | (rf/reg-event-db 26 | ::remove-nth 27 | (fn [db [_ i]] 28 | (update db :notifications utils.vec/remove-nth i))) 29 | 30 | (rf/reg-event-db 31 | ::clear-all 32 | (fn [db [_]] 33 | (assoc db :notifications []))) 34 | -------------------------------------------------------------------------------- /src/renderer/notification/handlers.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.notification.handlers 2 | (:require 3 | [malli.core :as m] 4 | [renderer.app.db :refer [App]] 5 | [renderer.utils.hiccup :refer [Hiccup]])) 6 | 7 | (m/=> add [:-> App Hiccup App]) 8 | (defn add 9 | [db notification] 10 | (let [notifications (:notifications db)] 11 | (if (= notification (-> notifications peek :content)) 12 | (assoc db :notifications 13 | (conj (pop notifications) (update (peek notifications) :count inc))) 14 | (update db :notifications conj {:content notification 15 | :count 1})))) 16 | -------------------------------------------------------------------------------- /src/renderer/notification/subs.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.notification.subs 2 | (:require 3 | [re-frame.core :as rf])) 4 | 5 | (rf/reg-sub 6 | ::entities 7 | :-> :notifications) 8 | -------------------------------------------------------------------------------- /src/renderer/reepl/LICENSE: -------------------------------------------------------------------------------- 1 | ISC License (ISC) 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 9 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 11 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 12 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 13 | PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /src/renderer/reepl/README.md: -------------------------------------------------------------------------------- 1 | # Repl module 2 | 3 | Based on reepl https://github.com/jaredly/reepl by Jared Forsyth under "ISC License" https://opensource.org/licenses/ISC 4 | 5 | ## Changes 6 | 7 | - Shadow-cljs build 8 | - Conform with replumb breaking changes https://github.com/arichiardi/replumb#usage 9 | - Parinfer removed 10 | - Multiple view and style changes 11 | - Separate styles 12 | 13 | ## Todo 14 | 15 | - Use re-frame to store and retrieve state to global app-db 16 | 17 | ## Considerations 18 | 19 | Maybe switch to SCI https://github.com/borkdude/sci which SCI is simpler, safer and it works when compiled with :advanced. 20 | -------------------------------------------------------------------------------- /src/renderer/reepl/db.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.reepl.db) 2 | 3 | (def initial-state 4 | {:items [] 5 | :hist-pos 0 6 | :history [""]}) 7 | -------------------------------------------------------------------------------- /src/renderer/reepl/show_value.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.reepl.show-value 2 | (:require 3 | [cljs.pprint :as pprint] 4 | [renderer.utils.extra :refer [partial-right]])) 5 | 6 | (defn pprint-str 7 | [v] 8 | (pprint/write v :stream nil)) 9 | 10 | (defn show-str 11 | [v] 12 | (if (string? v) 13 | v 14 | (pprint-str v))) 15 | 16 | (defn show-value- 17 | [v config showers] 18 | (loop [shower-list showers] 19 | (if (empty? shower-list) 20 | (throw (js/Error. (str "No shower for value " v))) 21 | (let [res ((first shower-list) v config (partial-right show-value- showers))] 22 | (if res 23 | [:div.inline-flex res] 24 | (recur (rest shower-list))))))) 25 | 26 | (defn show-value [v opts show-opts] 27 | (show-value- v opts (conj (vec (:showers show-opts)) show-str))) 28 | -------------------------------------------------------------------------------- /src/renderer/reepl/subs.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.reepl.subs 2 | (:require-macros 3 | [reagent.ratom :refer [reaction]])) 4 | 5 | (defn items 6 | [db] 7 | (reaction (:items @db))) 8 | 9 | (defn current-text 10 | [db] 11 | (let [idx (reaction (:hist-pos @db)) 12 | history (reaction (:history @db))] 13 | (reaction (let [history @history 14 | pos (- (count history) @idx 1)] 15 | {:pos pos 16 | :count (count history) 17 | :text (get history pos)})))) 18 | -------------------------------------------------------------------------------- /src/renderer/ruler/db.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.ruler.db) 2 | 3 | (def Ruler 4 | [:map {:closed true} 5 | [:visible {:default true} boolean?] 6 | [:locked {:default false} boolean?]]) 7 | 8 | (def Orientation 9 | [:enum :vertical :horizontal]) 10 | -------------------------------------------------------------------------------- /src/renderer/ruler/events.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.ruler.events 2 | (:require 3 | [re-frame.core :as rf] 4 | [renderer.app.events :refer [persist]])) 5 | 6 | (rf/reg-event-db 7 | ::toggle-visible 8 | [persist] 9 | (fn [db [_]] 10 | (update-in db [:ruler :visible] not))) 11 | 12 | (rf/reg-event-db 13 | ::toggle-locked 14 | [persist] 15 | (fn [db [_]] 16 | (update-in db [:ruler :locked] not))) 17 | -------------------------------------------------------------------------------- /src/renderer/ruler/subs.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.ruler.subs 2 | (:require 3 | [re-frame.core :as rf] 4 | [renderer.document.subs :as-alias document.subs] 5 | [renderer.element.subs :as-alias element.subs] 6 | [renderer.frame.subs :as-alias frame.subs] 7 | [renderer.ruler.handlers :as ruler.handlers])) 8 | 9 | (rf/reg-sub 10 | ::ruler 11 | :-> :ruler) 12 | 13 | (rf/reg-sub 14 | ::locked? 15 | :<- [::ruler] 16 | :-> :locked) 17 | 18 | (rf/reg-sub 19 | ::visible? 20 | :<- [::ruler] 21 | :-> :visible) 22 | 23 | (rf/reg-sub 24 | ::step 25 | :<- [::document.subs/zoom] 26 | ruler.handlers/step) 27 | 28 | (rf/reg-sub 29 | ::steps-coll 30 | :<- [::step] 31 | :<- [::frame.subs/viewbox] 32 | (fn [[step viewbox] [_ orientation]] 33 | (ruler.handlers/steps-coll step viewbox orientation))) 34 | 35 | (rf/reg-sub 36 | ::bbox-rect-attrs 37 | :<- [::document.subs/zoom] 38 | :<- [::document.subs/pan] 39 | :<- [::element.subs/bbox] 40 | (fn [[zoom pan bbox] [_ orientation size]] 41 | (let [[min-x min-y max-x max-y] (map #(* % zoom) bbox)] 42 | (if (= orientation :vertical) 43 | {:x 0 44 | :y (- min-y (* (second pan) zoom)) 45 | :width size 46 | :height (- max-y min-y)} 47 | {:x (- min-x (* (first pan) zoom)) 48 | :y 0 49 | :width (- max-x min-x) 50 | :height size})))) 51 | -------------------------------------------------------------------------------- /src/renderer/snap/db.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.snap.db 2 | (:require [renderer.utils.math :refer [Vec2]])) 3 | 4 | (def snap-options 5 | [:centers :midpoints :corners :nodes #_:grid]) 6 | 7 | (def SnapOption 8 | (into [:enum] snap-options)) 9 | 10 | (def SnapOptions 11 | [:set SnapOption]) 12 | 13 | (def NearestNeighbor 14 | [:map {:closed true} 15 | [:point Vec2] 16 | [:base-point Vec2] 17 | [:dist-squared number?]]) 18 | 19 | (def Snap 20 | [:map {:closed true} 21 | [:active {:default true} boolean?] 22 | [:threshold {:default 15} number?] 23 | [:options {:default (set snap-options)} SnapOptions]]) 24 | -------------------------------------------------------------------------------- /src/renderer/snap/subs.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.snap.subs 2 | (:require 3 | [re-frame.core :as rf])) 4 | 5 | (rf/reg-sub 6 | ::snap 7 | :-> :snap) 8 | 9 | (rf/reg-sub 10 | ::active? 11 | :<- [::snap] 12 | :-> :active) 13 | 14 | (rf/reg-sub 15 | ::options 16 | :<- [::snap] 17 | :-> :options) 18 | 19 | (rf/reg-sub 20 | ::nearest-neighbor 21 | :-> :nearest-neighbor) 22 | -------------------------------------------------------------------------------- /src/renderer/theme/db.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.theme.db) 2 | 3 | ;; The iframe is isolated so we don't have access to the css vars of the parent. 4 | ;; We are currently using hardcoded values, but we should be able to set those 5 | ;; vars in the nested document if we have to. 6 | (def accent-inverted "#fff") 7 | (def accent "#d12b65") 8 | (def font-mono "'Consolas (Custom)', 'Bitstream Vera Sans Mono', monospace, 9 | 'Apple Color Emoji', 'Segoe UI Emoji'") 10 | (def handle-size 13) 11 | (def dash-size 5) 12 | 13 | (def modes 14 | [:dark :light :system]) 15 | 16 | (def ThemeMode 17 | (into [:enum] modes)) 18 | 19 | (def Theme 20 | [:map {:closed true} 21 | [:mode {:default :dark} ThemeMode]]) 22 | -------------------------------------------------------------------------------- /src/renderer/theme/effects.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.theme.effects 2 | (:require 3 | [re-frame.core :as rf])) 4 | 5 | (def native-query! (.matchMedia js/window "(prefers-color-scheme: dark)")) 6 | 7 | (rf/reg-cofx 8 | ::native-mode 9 | (fn [coeffects _] 10 | (assoc coeffects :native-mode (if (.-matches native-query!) 11 | :dark 12 | :light)))) 13 | 14 | (rf/reg-fx 15 | ::add-native-listener 16 | (fn [e] 17 | (.addListener native-query! #(rf/dispatch e)))) 18 | -------------------------------------------------------------------------------- /src/renderer/theme/events.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.theme.events 2 | (:require 3 | [re-frame.core :as rf] 4 | [renderer.app.events :refer [persist]] 5 | [renderer.effects :as-alias effects] 6 | [renderer.theme.db :as theme.db] 7 | [renderer.theme.effects :as-alias theme.effects] 8 | [renderer.theme.handlers :as theme.handlers])) 9 | 10 | (rf/reg-event-fx 11 | ::add-native-listener 12 | (fn [_ _] 13 | {::theme.effects/add-native-listener ::set-document-attr})) 14 | 15 | (rf/reg-event-fx 16 | ::set-document-attr 17 | [(rf/inject-cofx ::theme.effects/native-mode)] 18 | (fn [{:keys [db native-mode]} _] 19 | (let [mode (-> db :theme :mode) 20 | mode (if (= mode :system) native-mode mode)] 21 | {::effects/set-document-attr ["data-theme" (name mode)]}))) 22 | 23 | (rf/reg-event-fx 24 | ::cycle-mode 25 | [persist] 26 | (fn [{:keys [db]} [_]] 27 | (let [index (.indexOf theme.db/modes (-> db :theme :mode)) 28 | mode (or (get theme.db/modes (inc index)) 29 | (first theme.db/modes))] 30 | {:db (theme.handlers/set-mode db mode) 31 | :dispatch [::set-document-attr]}))) 32 | -------------------------------------------------------------------------------- /src/renderer/theme/handlers.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.theme.handlers 2 | (:require 3 | [malli.core :as m] 4 | [renderer.app.db :refer [App]] 5 | [renderer.theme.db :refer [ThemeMode]])) 6 | 7 | (m/=> set-mode [:-> App ThemeMode App]) 8 | (defn set-mode 9 | [db mode] 10 | (assoc-in db [:theme :mode] mode)) 11 | -------------------------------------------------------------------------------- /src/renderer/theme/subs.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.theme.subs 2 | (:require 3 | [re-frame.core :as rf])) 4 | 5 | (rf/reg-sub 6 | ::mode 7 | (fn [db _] 8 | (-> db :theme :mode))) 9 | -------------------------------------------------------------------------------- /src/renderer/timeline/db.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.timeline.db) 2 | 3 | (def Timeline 4 | [:map {:closed true} 5 | [:time {:default 0} number?] 6 | [:speed {:default 1} number?] 7 | [:replay? {:default false} boolean?] 8 | [:grid-snap? {:default false} boolean?] 9 | [:guide-snap? {:default true} boolean?] 10 | [:paused? {:default false} boolean?]]) 11 | -------------------------------------------------------------------------------- /src/renderer/timeline/effects.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.timeline.effects 2 | (:require 3 | [re-frame.core :as rf] 4 | [renderer.utils.dom :as utils.dom])) 5 | 6 | (defn svg-elements! 7 | [] 8 | (when-let [document (utils.dom/frame-document!)] 9 | (.querySelectorAll document "svg"))) 10 | 11 | (rf/reg-fx 12 | ::set-current-time 13 | (fn [t] 14 | (doall (map #(.setCurrentTime % t) (svg-elements!))))) 15 | 16 | (rf/reg-fx 17 | ::pause-animations 18 | (fn [] 19 | (doall (map #(.pauseAnimations %) (svg-elements!))))) 20 | -------------------------------------------------------------------------------- /src/renderer/timeline/events.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.timeline.events 2 | (:require 3 | [re-frame.core :as rf] 4 | [renderer.timeline.effects :as timeline.effects])) 5 | 6 | (rf/reg-event-db 7 | ::pause 8 | (fn [db _] 9 | (assoc-in db [:timeline :paused?] true))) 10 | 11 | (rf/reg-event-db 12 | ::play 13 | (fn [db _] 14 | (assoc-in db [:timeline :paused?] false))) 15 | 16 | (rf/reg-event-db 17 | ::set-grid-snap 18 | (fn [db [_ state]] 19 | (assoc-in db [:timeline :grid-snap?] state))) 20 | 21 | (rf/reg-event-db 22 | ::set-guide-snap 23 | (fn [db [_ state]] 24 | (assoc-in db [:timeline :guide-snap?] state))) 25 | 26 | (rf/reg-event-db 27 | ::toggle-replay 28 | (fn [db _] 29 | (update-in db [:timeline :replay?] not))) 30 | 31 | (rf/reg-event-db 32 | ::set-speed 33 | (fn [db [_ speed]] 34 | (assoc-in db [:timeline :speed] speed))) 35 | 36 | (rf/reg-event-fx 37 | ::set-time 38 | (fn [{:keys [db]} [_ t]] 39 | {:db (assoc-in db [:timeline :time] t) 40 | ::timeline.effects/set-current-time t 41 | ::timeline.effects/pause-animations nil})) 42 | -------------------------------------------------------------------------------- /src/renderer/tool/events.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.tool.events 2 | (:require 3 | [re-frame.core :as rf] 4 | [renderer.effects :as-alias effects] 5 | [renderer.element.events :as element.events] 6 | [renderer.tool.handlers :as tool.handlers])) 7 | 8 | (rf/reg-event-fx 9 | ::activate 10 | (fn [{:keys [db]} [_ tool]] 11 | {:db (tool.handlers/activate db tool) 12 | ::effects/focus nil})) 13 | 14 | (rf/reg-event-db 15 | ::set-state 16 | (fn [db [_ state]] 17 | (tool.handlers/set-state db state))) 18 | 19 | (rf/reg-event-fx 20 | ::cancel 21 | (fn [{:keys [db]} _] 22 | (if (and (= (:tool db) :transform) 23 | (= (:state db) :idle)) 24 | {:dispatch [::element.events/deselect-all]} 25 | {:db (tool.handlers/cancel db)}))) 26 | 27 | (rf/reg-global-interceptor 28 | (rf/->interceptor 29 | :id ::custom-fx 30 | :after (fn [context] 31 | (let [db (rf/get-effect context :db) 32 | fx (rf/get-effect context :fx)] 33 | (cond-> context 34 | db 35 | (-> (rf/assoc-effect :fx (apply conj (or fx []) (:fx db))) 36 | (rf/assoc-effect :db (assoc db :fx [])))))))) 37 | -------------------------------------------------------------------------------- /src/renderer/tool/impl/base/core.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.tool.impl.base.core 2 | (:require 3 | [renderer.tool.impl.base.edit] 4 | [renderer.tool.impl.base.pan] 5 | [renderer.tool.impl.base.transform] 6 | [renderer.tool.impl.base.zoom])) 7 | -------------------------------------------------------------------------------- /src/renderer/tool/impl/base/pan.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.tool.impl.base.pan 2 | (:require 3 | [clojure.core.matrix :as matrix] 4 | [renderer.app.effects :as-alias app.effects] 5 | [renderer.frame.handlers :as frame.handlers] 6 | [renderer.snap.handlers :as snap.handlers] 7 | [renderer.tool.handlers :as tool.handlers] 8 | [renderer.tool.hierarchy :as tool.hierarchy])) 9 | 10 | (derive :pan ::tool.hierarchy/tool) 11 | 12 | (defmethod tool.hierarchy/properties :pan 13 | [] 14 | {:icon "hand"}) 15 | 16 | (defmethod tool.hierarchy/on-activate :pan 17 | [db] 18 | (tool.handlers/set-cursor db "grab")) 19 | 20 | (defmethod tool.hierarchy/help [:pan :idle] 21 | [] 22 | "Click and drag to pan.") 23 | 24 | (defmethod tool.hierarchy/help [:pan :pan] 25 | [] 26 | "Drag to pan.") 27 | 28 | (defmethod tool.hierarchy/on-pointer-up :pan 29 | [db _e] 30 | (-> (tool.handlers/set-cursor db "grab") 31 | (tool.handlers/set-state :idle))) 32 | 33 | (defmethod tool.hierarchy/on-pointer-down :pan 34 | [db _e] 35 | (-> (tool.handlers/set-cursor db "grabbing") 36 | (tool.handlers/set-state :pan))) 37 | 38 | (defmethod tool.hierarchy/on-drag :pan 39 | [db e] 40 | (frame.handlers/pan-by db (matrix/sub (:pointer-pos db) (:pointer-pos e)))) 41 | 42 | (defmethod tool.hierarchy/on-drag-end :pan 43 | [db _e] 44 | (-> (tool.handlers/set-cursor db "grab") 45 | (tool.handlers/set-state :idle) 46 | (snap.handlers/update-viewport-tree) 47 | (tool.handlers/add-fx [::app.effects/persist]))) 48 | -------------------------------------------------------------------------------- /src/renderer/tool/impl/core.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.tool.impl.core 2 | (:require 3 | [renderer.tool.impl.base.core] 4 | [renderer.tool.impl.draw.core] 5 | [renderer.tool.impl.element.core] 6 | [renderer.tool.impl.extension.core] 7 | [renderer.tool.impl.misc.core])) 8 | -------------------------------------------------------------------------------- /src/renderer/tool/impl/draw/core.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.tool.impl.draw.core 2 | (:require 3 | [renderer.tool.handlers :as tool.handlers] 4 | [renderer.tool.hierarchy :as tool.hierarchy] 5 | [renderer.tool.impl.draw.brush] 6 | [renderer.tool.impl.draw.pen])) 7 | 8 | (derive ::tool.hierarchy/draw ::tool.hierarchy/tool) 9 | 10 | (defmethod tool.hierarchy/help [::tool.hierarchy/draw :idle] 11 | [] 12 | "Click and drag to draw.") 13 | 14 | (defmethod tool.hierarchy/on-activate ::tool.hierarchy/draw 15 | [db] 16 | (tool.handlers/set-cursor db "crosshair")) 17 | -------------------------------------------------------------------------------- /src/renderer/tool/impl/element/circle.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.tool.impl.element.circle 2 | "https://www.w3.org/TR/SVG/shapes.html#CircleElement" 3 | (:require 4 | [clojure.core.matrix :as matrix] 5 | [renderer.document.handlers :as document.handlers] 6 | [renderer.tool.handlers :as tool.handlers] 7 | [renderer.tool.hierarchy :as tool.hierarchy])) 8 | 9 | (derive :circle ::tool.hierarchy/element) 10 | 11 | (defmethod tool.hierarchy/properties :circle 12 | [] 13 | {:icon "circle-tool"}) 14 | 15 | (defmethod tool.hierarchy/on-drag :circle 16 | [db _e] 17 | (let [offset (or (:nearest-neighbor-offset db) (:adjusted-pointer-offset db)) 18 | position (or (:point (:nearest-neighbor db)) (:adjusted-pointer-pos db)) 19 | [x y] offset 20 | radius (matrix/distance position offset) 21 | attrs {:cx x 22 | :cy y 23 | :fill (document.handlers/attr db :fill) 24 | :stroke (document.handlers/attr db :stroke) 25 | :r radius}] 26 | (tool.handlers/set-temp db {:type :element :tag :circle :attrs attrs}))) 27 | 28 | (defmethod tool.hierarchy/snapping-points :circle 29 | [db] 30 | [(with-meta 31 | (:adjusted-pointer-pos db) 32 | {:label (str (name (:tool db)) " " (if (tool.handlers/temp db) "radius" "center"))})]) 33 | -------------------------------------------------------------------------------- /src/renderer/tool/impl/element/ellipse.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.tool.impl.element.ellipse 2 | "https://www.w3.org/TR/SVG/shapes.html#EllipseElement" 3 | (:require 4 | [renderer.document.handlers :as document.handlers] 5 | [renderer.tool.handlers :as tool.handlers] 6 | [renderer.tool.hierarchy :as tool.hierarchy])) 7 | 8 | (derive :ellipse ::tool.hierarchy/element) 9 | 10 | (defmethod tool.hierarchy/properties :ellipse 11 | [] 12 | {:icon "ellipse-tool"}) 13 | 14 | (defmethod tool.hierarchy/help [:ellipse :create] 15 | [] 16 | [:div "Hold " [:span.shortcut-key "Ctrl"] " to lock proportions."]) 17 | 18 | (defmethod tool.hierarchy/on-drag :ellipse 19 | [db e] 20 | (let [[offset-x offset-y] (or (:nearest-neighbor-offset db) (:adjusted-pointer-offset db)) 21 | [x y] (or (:point (:nearest-neighbor db)) (:adjusted-pointer-pos db)) 22 | lock-ratio (:ctrl-key e) 23 | rx (abs (- x offset-x)) 24 | ry (abs (- y offset-y)) 25 | attrs {:cx offset-x 26 | :cy offset-y 27 | :fill (document.handlers/attr db :fill) 28 | :stroke (document.handlers/attr db :stroke) 29 | :rx (if lock-ratio (min rx ry) rx) 30 | :ry (if lock-ratio (min rx ry) ry)}] 31 | (tool.handlers/set-temp db {:type :element 32 | :tag :ellipse 33 | :attrs attrs}))) 34 | -------------------------------------------------------------------------------- /src/renderer/tool/impl/element/polygon.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.tool.impl.element.polygon 2 | "https://www.w3.org/TR/SVG/shapes.html#PolygonElement" 3 | (:require [renderer.tool.hierarchy :as tool.hierarchy])) 4 | 5 | (derive :polygon ::tool.hierarchy/polyshape) 6 | 7 | (defmethod tool.hierarchy/properties :polygon 8 | [] 9 | {:icon "polygon-tool"}) 10 | -------------------------------------------------------------------------------- /src/renderer/tool/impl/element/polyline.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.tool.impl.element.polyline 2 | "https://www.w3.org/TR/SVG/shapes.html#PolylineElement" 3 | (:require [renderer.tool.hierarchy :as tool.hierarchy])) 4 | 5 | (derive :polyline ::tool.hierarchy/polyshape) 6 | 7 | (defmethod tool.hierarchy/properties :polyline 8 | [] 9 | {:icon "polyline"}) 10 | -------------------------------------------------------------------------------- /src/renderer/tool/impl/element/rect.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.tool.impl.element.rect 2 | "https://www.w3.org/TR/SVG/shapes.html#RectElement" 3 | (:require 4 | [renderer.document.handlers :as document.handlers] 5 | [renderer.tool.handlers :as tool.handlers] 6 | [renderer.tool.hierarchy :as tool.hierarchy])) 7 | 8 | (derive :rect ::tool.hierarchy/element) 9 | 10 | (defmethod tool.hierarchy/properties :rect 11 | [] 12 | {:icon "rectangle-tool" 13 | :label "Rectangle"}) 14 | 15 | (defmethod tool.hierarchy/help [:rect :create] 16 | [] 17 | [:div "Hold " [:span.shortcut-key "Ctrl"] " to lock proportions."]) 18 | 19 | (defmethod tool.hierarchy/on-drag :rect 20 | [db e] 21 | (let [[offset-x offset-y] (or (:nearest-neighbor-offset db) (:adjusted-pointer-offset db)) 22 | [x y] (or (:point (:nearest-neighbor db)) (:adjusted-pointer-pos db)) 23 | width (abs (- x offset-x)) 24 | height (abs (- y offset-y))] 25 | (tool.handlers/set-temp db {:type :element 26 | :tag :rect 27 | :attrs {:x (min x offset-x) 28 | :y (min y offset-y) 29 | :width (if (:ctrl-key e) (min width height) width) 30 | :height (if (:ctrl-key e) (min width height) height) 31 | :fill (document.handlers/attr db :fill) 32 | :stroke (document.handlers/attr db :stroke)}}))) 33 | -------------------------------------------------------------------------------- /src/renderer/tool/impl/element/svg.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.tool.impl.element.svg 2 | "https://www.w3.org/TR/SVG/struct.html#SVGElement" 3 | (:require 4 | [renderer.tool.handlers :as tool.handlers] 5 | [renderer.tool.hierarchy :as tool.hierarchy])) 6 | 7 | (derive :svg ::tool.hierarchy/element) 8 | 9 | (defmethod tool.hierarchy/properties :svg 10 | [] 11 | {:icon "svg"}) 12 | 13 | (defmethod tool.hierarchy/help [:svg :create] 14 | [] 15 | [:div "Hold " [:span.shortcut-key "Ctrl"] " to lock proportions."]) 16 | 17 | (defmethod tool.hierarchy/on-drag :svg 18 | [db e] 19 | (let [[offset-x offset-y] (or (:nearest-neighbor-offset db) (:adjusted-pointer-offset db)) 20 | [x y] (or (:point (:nearest-neighbor db)) (:adjusted-pointer-pos db)) 21 | lock-ratio (:ctrl-key e) 22 | width (abs (- x offset-x)) 23 | height (abs (- y offset-y)) 24 | attrs {:x (min x offset-x) 25 | :y (min y offset-y) 26 | :width (if lock-ratio (min width height) width) 27 | :height (if lock-ratio (min width height) height)}] 28 | (tool.handlers/set-temp db {:tag :svg 29 | :type :element 30 | :attrs attrs}))) 31 | -------------------------------------------------------------------------------- /src/renderer/tool/impl/element/text.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.tool.impl.element.text 2 | (:require 3 | [renderer.element.handlers :as element.handlers] 4 | [renderer.tool.handlers :as tool.handlers] 5 | [renderer.tool.hierarchy :as tool.hierarchy])) 6 | 7 | (derive :text ::tool.hierarchy/element) 8 | 9 | (defmethod tool.hierarchy/properties :text 10 | [] 11 | {:icon "text"}) 12 | 13 | (defmethod tool.hierarchy/help [:text :idle] 14 | [] 15 | "Click to start typing.") 16 | 17 | (defmethod tool.hierarchy/on-activate :text 18 | [db] 19 | (tool.handlers/set-cursor db "text")) 20 | 21 | (defmethod tool.hierarchy/on-pointer-up :text 22 | [db _e] 23 | (let [[offset-x offset-y] (or (:nearest-neighbor-offset db) (:adjusted-pointer-offset db)) 24 | el {:type :element 25 | :tag :text 26 | :attrs {:x offset-x 27 | :y offset-y}}] 28 | (-> (element.handlers/deselect-all db) 29 | (element.handlers/add el) 30 | (tool.handlers/activate :edit) 31 | (tool.handlers/set-state :type)))) 32 | 33 | (defmethod tool.hierarchy/on-drag-end :text 34 | [db e] 35 | (tool.hierarchy/on-pointer-up db e)) 36 | -------------------------------------------------------------------------------- /src/renderer/tool/impl/extension/core.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.tool.impl.extension.core 2 | (:require [renderer.tool.impl.extension.blob])) 3 | -------------------------------------------------------------------------------- /src/renderer/tool/impl/misc/core.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.tool.impl.misc.core 2 | (:require 3 | [renderer.tool.impl.misc.dropper] 4 | [renderer.tool.impl.misc.fill] 5 | [renderer.tool.impl.misc.measure])) 6 | -------------------------------------------------------------------------------- /src/renderer/tool/impl/misc/fill.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.tool.impl.misc.fill 2 | (:require 3 | [renderer.document.handlers :as document.handlers] 4 | [renderer.element.handlers :as element.handlers] 5 | [renderer.history.handlers :as history.handlers] 6 | [renderer.tool.handlers :as tool.handlers] 7 | [renderer.tool.hierarchy :as tool.hierarchy])) 8 | 9 | (derive :fill ::tool.hierarchy/tool) 10 | 11 | (defmethod tool.hierarchy/properties :fill 12 | [] 13 | {:icon "fill"}) 14 | 15 | (defmethod tool.hierarchy/help [:fill :idle] 16 | [] 17 | "Click on an element to fill.") 18 | 19 | (defmethod tool.hierarchy/on-activate :fill 20 | [db] 21 | (tool.handlers/set-cursor db "crosshair")) 22 | 23 | (defmethod tool.hierarchy/on-pointer-up :fill 24 | [db e] 25 | (let [color (document.handlers/attr db :fill) 26 | el-id (-> e :element :id)] 27 | (-> (element.handlers/set-attr db el-id :fill color) 28 | (history.handlers/finalize "Fill")))) 29 | -------------------------------------------------------------------------------- /src/renderer/tool/subs.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.tool.subs 2 | (:require 3 | [re-frame.core :as rf] 4 | [renderer.tool.hierarchy :as tool.hierarchy])) 5 | 6 | (rf/reg-sub 7 | ::active 8 | :-> :tool) 9 | 10 | (rf/reg-sub 11 | ::primary 12 | :-> :primary-tool) 13 | 14 | (rf/reg-sub 15 | ::pivot-point 16 | :-> :pivot-point) 17 | 18 | (rf/reg-sub 19 | ::drag? 20 | :-> :drag) 21 | 22 | (rf/reg-sub 23 | ::cursor 24 | :-> :cursor) 25 | 26 | (rf/reg-sub 27 | ::state 28 | :-> :state) 29 | 30 | (rf/reg-sub 31 | ::help 32 | :<- [::active] 33 | :<- [::state] 34 | (fn [[tool state] _] 35 | (let [dispatch-state (if (contains? (methods tool.hierarchy/help) [tool state]) 36 | state 37 | :idle)] 38 | (tool.hierarchy/help tool dispatch-state)))) 39 | -------------------------------------------------------------------------------- /src/renderer/toolbar/views.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.toolbar.views 2 | (:require 3 | ["@radix-ui/react-tooltip" :as Tooltip] 4 | [re-frame.core :as rf] 5 | [renderer.views :as views])) 6 | 7 | (defn button 8 | [{:keys [title icon disabled action] :as attrs}] 9 | (if (= (:type attrs) :divider) 10 | [:span.h-divider] 11 | [:> Tooltip/Root 12 | [:> Tooltip/Trigger 13 | {:as-child true} 14 | [:span.shadow-4 15 | [views/icon-button icon {:disabled disabled 16 | :aria-label title 17 | :on-click #(rf/dispatch action)}]]] 18 | [:> Tooltip/Portal 19 | [:> Tooltip/Content 20 | {:class "tooltip-content" 21 | :side "left" 22 | :sideOffset 5} 23 | [:div.flex.gap-2.items-center 24 | title]]]])) 25 | -------------------------------------------------------------------------------- /src/renderer/tree/events.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.tree.events 2 | (:require 3 | [re-frame.core :as rf] 4 | [renderer.tree.effects :as tree.effects])) 5 | 6 | (rf/reg-event-fx 7 | ::focus-up 8 | (fn [_ [_ id]] 9 | {::tree.effects/focus-next [id :up]})) 10 | 11 | (rf/reg-event-fx 12 | ::focus-down 13 | (fn [_ [_ id]] 14 | {::tree.effects/focus-next [id :down]})) 15 | 16 | (rf/reg-event-fx 17 | ::select-range 18 | (fn [_ [_ last-focused-id id]] 19 | {::tree.effects/select-range [last-focused-id id]})) 20 | -------------------------------------------------------------------------------- /src/renderer/utils/dom.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.utils.dom) 2 | 3 | (defn frame-document! 4 | [] 5 | (when-let [frame (.getElementById js/document "frame")] 6 | (when-let [window (.-contentWindow frame)] 7 | (.-document window)))) 8 | 9 | (defn canvas-element! 10 | [] 11 | (when-let [document (frame-document!)] 12 | (.getElementById document "canvas"))) 13 | -------------------------------------------------------------------------------- /src/renderer/utils/extra.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.utils.extra) 2 | 3 | (defn partial-right 4 | "Like partial, takes a function f and fewer than the normal arguments to f, 5 | and returns a fn that takes a variable number of additional args. 6 | When called, the returned function calls f with the additional args prepended." 7 | ([f] f) 8 | ([f arg1] 9 | (fn 10 | ([] (f arg1)) 11 | ([x] (f x arg1)) 12 | ([x y] (f x y arg1)) 13 | ([x y z] (f x y z arg1)) 14 | ([x y z & args] (apply f x y z (concat args arg1))))) 15 | ([f arg1 arg2] 16 | (fn 17 | ([] (f arg1 arg2)) 18 | ([x] (f x arg1 arg2)) 19 | ([x y] (f x y arg1 arg2)) 20 | ([x y z] (f x y z arg1 arg2)) 21 | ([x y z & args] (apply f x y z (concat args arg1 arg2))))) 22 | ([f arg1 arg2 arg3] 23 | (fn 24 | ([] (f arg1 arg2 arg3)) 25 | ([x] (f x arg1 arg2 arg3)) 26 | ([x y] (f x y arg1 arg2 arg3)) 27 | ([x y z] (f x y z arg1 arg2 arg3)) 28 | ([x y z & args] (apply f x y z (concat args arg1 arg2 arg3))))) 29 | ([f arg1 arg2 arg3 & more] 30 | (fn [& args] (apply f (concat args arg1 arg2 arg3 more))))) 31 | -------------------------------------------------------------------------------- /src/renderer/utils/hiccup.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.utils.hiccup 2 | (:require 3 | [clojure.zip :as zip])) 4 | 5 | (def Props [:? [:map-of keyword? any?]]) 6 | 7 | (def Hiccup 8 | [:schema {:registry {"hiccup" [:orn 9 | [:node [:catn 10 | [:name keyword?] 11 | [:props Props] 12 | [:children [:* [:schema [:ref "hiccup"]]]]]] 13 | [:primitive [:orn 14 | [:nil nil?] 15 | [:boolean boolean?] 16 | [:number number?] 17 | [:text string?]]]]}} 18 | "hiccup"]) 19 | 20 | (defn find-svg 21 | [zipper] 22 | (loop [loc zipper] 23 | (if (zip/end? loc) 24 | (zip/root loc) 25 | (if (= (:tag (zip/node loc)) :svg) 26 | (zip/node loc) 27 | (recur (zip/next loc)))))) 28 | -------------------------------------------------------------------------------- /src/renderer/utils/i18n.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.utils.i18n 2 | "Internationalization namespace 3 | https://github.com/taoensso/tempura" 4 | (:require 5 | [malli.core :as m] 6 | [re-frame.core :as rf] 7 | [renderer.app.subs :as-alias app.subs] 8 | [taoensso.tempura :as tempura :refer-macros [load-resource-at-compile-time]])) 9 | 10 | ;; We need to load resources at compile time in clojurescript 11 | ;; https://github.com/taoensso/tempura/issues/25#issuecomment-451742526 12 | (def dictionary 13 | {"en-US" (load-resource-at-compile-time "lang/en-US.edn") 14 | "el-GR" (load-resource-at-compile-time "lang/el-GR.edn")}) 15 | 16 | (m/=> supported-lang? [:-> string? boolean?]) 17 | (defn supported-lang? 18 | [lang] 19 | (contains? dictionary lang)) 20 | 21 | (def opts {:dict dictionary}) 22 | 23 | (defn t 24 | "Translation function that should be called in a reactive context." 25 | [& more] 26 | (let [lang @(rf/subscribe [::app.subs/lang])] 27 | (apply tempura/tr opts [lang] more))) 28 | -------------------------------------------------------------------------------- /src/renderer/utils/map.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.utils.map 2 | (:require 3 | [clojure.set :as set] 4 | [malli.core :as m])) 5 | 6 | (m/=> merge-common-with [:-> ifn? [:* map?] map?]) 7 | (defn merge-common-with 8 | "Equivalent to merge-with for common keys across all maps." 9 | [f & maps] 10 | (let [common-keys (apply set/intersection (map (comp set keys) maps))] 11 | (->> (apply merge-with f (map #(select-keys % common-keys) maps)) 12 | (into {})))) 13 | 14 | (m/=> remove-nils [:-> map? map?]) 15 | (defn remove-nils 16 | "Removes nil values from maps (should be used sparingly)." 17 | [m] 18 | (->> m 19 | (remove (comp nil? val)) 20 | (into {}))) 21 | -------------------------------------------------------------------------------- /src/renderer/utils/math.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.utils.math 2 | (:require 3 | [clojure.math :as math] 4 | [malli.core :as m])) 5 | 6 | (def Vec2 7 | [:tuple number? number?]) 8 | 9 | (m/=> clamp [:-> number? number? number? number?]) 10 | (defn clamp 11 | "Clamps a number within the provided bounds." 12 | [x minimum maximum] 13 | (min (max x minimum) maximum)) 14 | 15 | (m/=> angle-dx [:-> number? number? number?]) 16 | (defn angle-dx 17 | [degrees radius] 18 | (* radius (Math/cos (math/to-radians degrees)))) 19 | 20 | (m/=> angle-dy [:-> number? number? number?]) 21 | (defn angle-dy 22 | [degrees radius] 23 | (* radius (Math/sin (math/to-radians degrees)))) 24 | 25 | (m/=> normalize-angle [:-> number? number?]) 26 | (defn normalize-angle 27 | "Normalizes an angle to be in range [0-360). Angles outside this range will 28 | be normalized to be the equivalent angle with that range." 29 | [angle] 30 | (mod angle (* 2 Math/PI))) 31 | 32 | (m/=> angle [:-> Vec2 Vec2 number?]) 33 | (defn angle 34 | "Calculates the angle between two points." 35 | [[x1 y1] [x2 y2]] 36 | (-> (Math/atan2 (- y2 y1) (- x2 x1)) 37 | (normalize-angle) 38 | (math/to-degrees))) 39 | -------------------------------------------------------------------------------- /src/renderer/utils/path.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.utils.path 2 | (:require 3 | ["paper" :refer [Path]] 4 | [malli.core :as m])) 5 | 6 | (def PathBooleanOperation 7 | [:enum :unite :intersect :subtract :exclude :divide]) 8 | 9 | (def PathManipulation 10 | [:enum :simplify :smooth :flatten :reverse]) 11 | 12 | (m/=> get-d [:-> any? string?]) 13 | (defn get-d 14 | [paper-path] 15 | (-> paper-path 16 | (.exportSVG) 17 | (.getAttribute "d"))) 18 | 19 | (m/=> manipulate [:-> string? PathManipulation string?]) 20 | (defn manipulate 21 | [path manipulation] 22 | (let [path (Path. path)] 23 | (case manipulation 24 | :simplify (.simplify path) 25 | :smooth (.smooth path) 26 | :flatten (.flatten path) 27 | :reverse (.reverse path)) 28 | (get-d path))) 29 | 30 | (m/=> boolean-operation [:-> string? string? PathBooleanOperation string?]) 31 | (defn boolean-operation 32 | [path-a path-b operation] 33 | (let [path-a (Path. path-a) 34 | path-b (Path. path-b)] 35 | (get-d (case operation 36 | :unite (.unite path-a path-b) 37 | :intersect (.intersect path-a path-b) 38 | :subtract (.subtract path-a path-b) 39 | :exclude (.exclude path-a path-b) 40 | :divide (.divide path-a path-b))))) 41 | -------------------------------------------------------------------------------- /src/renderer/utils/unit.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.utils.unit 2 | (:require 3 | [clojure.string :as string] 4 | [malli.core :as m])) 5 | 6 | (m/=> ->key [:-> string? keyword?]) 7 | (defn ->key 8 | "Converts the string unit to a lower-cased keyword." 9 | [s] 10 | (keyword (string/lower-case s))) 11 | 12 | (m/=> match [:-> string? string?]) 13 | (defn match 14 | [s] 15 | (second (re-matches #"[\d.\-\+]*\s*(.*)" s))) 16 | 17 | (m/=> parse [:-> [:or string? number? nil?] [:tuple number? string?]]) 18 | (defn parse 19 | [v] 20 | (let [s (string/trim (str v)) 21 | n (js/parseFloat s 10) 22 | unit (match s)] 23 | [(if (js/isNaN n) 0 n) unit])) 24 | -------------------------------------------------------------------------------- /src/renderer/utils/vec.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.utils.vec 2 | (:require 3 | [malli.core :as m])) 4 | 5 | (m/=> remove-nth [:-> vector? number? vector?]) 6 | (defn remove-nth 7 | "Removes element at index." 8 | [v i] 9 | (into (subvec v 0 i) 10 | (subvec v (inc i)))) 11 | 12 | (m/=> add [:-> vector? number? any? vector?]) 13 | (defn add 14 | "Adds element to index." 15 | [v index el] 16 | (vec (concat 17 | (subvec v 0 index) 18 | [el] 19 | (subvec v index)))) 20 | 21 | (m/=> move [:-> vector? number? number? vector?]) 22 | (defn move 23 | "Moves element by index." 24 | [v i-1 i-2] 25 | (let [el (nth v i-1)] 26 | (if (= i-1 i-2) 27 | v 28 | (-> (remove-nth v i-1) 29 | (add i-2 el))))) 30 | 31 | (m/=> swap [:-> vector? number? number? vector?]) 32 | (defn swap 33 | "Swaps the position of two elements by index." 34 | [v i-1 i-2] 35 | (assoc v 36 | i-2 (v i-1) 37 | i-1 (v i-2))) 38 | -------------------------------------------------------------------------------- /src/renderer/window/db.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.window.db) 2 | 3 | (def Window 4 | [:map {:closed true} 5 | [:maximized {:default true} boolean?] 6 | [:minimized {:default false} boolean?] 7 | [:fullscreen {:default false} boolean?] 8 | [:focused {:default false} boolean?]]) 9 | -------------------------------------------------------------------------------- /src/renderer/window/effects.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.window.effects 2 | (:require 3 | [re-frame.core :as rf] 4 | [renderer.utils.dom :as utils.dom])) 5 | 6 | (rf/reg-cofx 7 | ::focused 8 | (fn [coeffects _] 9 | (assoc coeffects :focused (or (.hasFocus js/document) 10 | (and (utils.dom/frame-document!) 11 | (.hasFocus (utils.dom/frame-document!))))))) 12 | 13 | (rf/reg-cofx 14 | ::fullscreen 15 | (fn [coeffects _] 16 | (assoc coeffects :fullscreen (boolean (.-fullscreenElement js/document))))) 17 | 18 | (rf/reg-fx 19 | ::close 20 | (fn [_] 21 | (.close js/window))) 22 | 23 | (rf/reg-fx 24 | ::reload 25 | (fn [_] 26 | (.reload js/window.location))) 27 | 28 | (rf/reg-fx 29 | ::toggle-fullscreen 30 | (fn [_] 31 | (if (.-fullscreenElement js/document) 32 | (.exitFullscreen js/document) 33 | (.. js/document -documentElement requestFullscreen)))) 34 | -------------------------------------------------------------------------------- /src/renderer/window/subs.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.window.subs 2 | (:require 3 | [re-frame.core :as rf])) 4 | 5 | (rf/reg-sub 6 | ::window 7 | :-> :window) 8 | 9 | (rf/reg-sub 10 | ::maximized? 11 | :<- [::window] 12 | :-> :maximized) 13 | 14 | (rf/reg-sub 15 | ::fullscreen? 16 | :<- [::window] 17 | :-> :fullscreen) 18 | 19 | (rf/reg-sub 20 | ::focused? 21 | :<- [::window] 22 | :-> :focused) 23 | -------------------------------------------------------------------------------- /src/renderer/worker/effects.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.worker.effects 2 | (:require 3 | [re-frame.core :as rf] 4 | [renderer.worker.events :as-alias worker.events])) 5 | 6 | (rf/reg-fx 7 | ::post 8 | (fn [{:keys [data on-success on-error]}] 9 | (let [worker (js/Worker. "js/worker.js") 10 | id (uuid (:id data))] 11 | (.addEventListener 12 | worker 13 | "message" 14 | #(let [response-data (js->clj (.. % -data) :keywordize-keys true)] 15 | (rf/dispatch [::worker.events/message id on-success response-data]))) 16 | 17 | (.addEventListener worker "error" #(rf/dispatch [::worker.events/message id on-error %])) 18 | 19 | (.postMessage worker (clj->js data))))) 20 | -------------------------------------------------------------------------------- /src/renderer/worker/events.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.worker.events 2 | (:require 3 | [re-frame.core :as rf] 4 | [renderer.effects :as-alias effects] 5 | [renderer.worker.effects :as worker.effects])) 6 | 7 | (rf/reg-event-fx 8 | ::create 9 | [(rf/inject-cofx ::effects/guid)] 10 | (fn [{:keys [db guid]} [_ options]] 11 | {:db (assoc-in db [:worker :tasks guid] (:action options)) 12 | ::worker.effects/post (-> options 13 | (assoc-in [:data :id] (str guid)) 14 | (assoc-in [:data :action] (:action options)))})) 15 | 16 | (rf/reg-event-fx 17 | ::message 18 | (fn [{:keys [db]} [_ id event response-data]] 19 | (cond-> {:db (update-in db [:worker :tasks] dissoc id)} 20 | event 21 | (assoc :dispatch (conj event response-data))))) 22 | -------------------------------------------------------------------------------- /src/renderer/worker/subs.cljs: -------------------------------------------------------------------------------- 1 | (ns renderer.worker.subs 2 | (:require 3 | [re-frame.core :as rf])) 4 | 5 | (rf/reg-sub 6 | :worker 7 | :-> :worker) 8 | 9 | (rf/reg-sub 10 | ::tasks 11 | :<- [:worker] 12 | :-> :tasks) 13 | 14 | (rf/reg-sub 15 | ::loading? 16 | :<- [::tasks] 17 | seq) 18 | -------------------------------------------------------------------------------- /src/worker/core.cljs: -------------------------------------------------------------------------------- 1 | (ns worker.core 2 | (:require 3 | ["imagetracerjs" :as ImageTracer])) 4 | 5 | (defn ^:export init! [] 6 | (js/self.addEventListener 7 | "message" 8 | (fn [^js e] 9 | (let [data (.-data e)] 10 | (case (.-action data) 11 | "trace" 12 | (let [svg (.imagedataToSVG ImageTracer (.-image data))] 13 | (js/postMessage #js {:svg svg 14 | :label (str "Traced " (or (.-label data) "image")) 15 | :position (.-position data) 16 | :id (.-id data)})) 17 | 18 | (js/postMessage #js {:id (.-id data)})))))) 19 | -------------------------------------------------------------------------------- /test/core_test.cljs: -------------------------------------------------------------------------------- 1 | (ns core-test 2 | (:require 3 | [malli.instrument :as m.instrument] 4 | [re-frame.core :as rf] 5 | [re-frame.subs :as rf.subs] 6 | [renderer.app.effects] 7 | [renderer.app.events :as app.events] 8 | [renderer.app.subs] 9 | [renderer.dialog.events] 10 | [renderer.document.events] 11 | [renderer.document.subs] 12 | [renderer.element.effects] 13 | [renderer.element.events] 14 | [renderer.element.impl.core] 15 | [renderer.element.subs] 16 | [renderer.frame.events] 17 | [renderer.frame.subs] 18 | [renderer.history.events] 19 | [renderer.history.subs] 20 | [renderer.notification.events] 21 | [renderer.notification.subs] 22 | [renderer.ruler.events] 23 | [renderer.snap.events] 24 | [renderer.theme.events] 25 | [renderer.theme.subs] 26 | [renderer.timeline.events] 27 | [renderer.tool.events] 28 | [renderer.tool.impl.core] 29 | [renderer.tool.subs] 30 | [renderer.tree.events] 31 | [renderer.window.events] 32 | [renderer.window.subs] 33 | [renderer.worker.events])) 34 | 35 | (set! rf.subs/warn-when-not-reactive (constantly nil)) 36 | 37 | (defn ^:dev/after-load instrument! [] 38 | (m.instrument/instrument!) 39 | (rf/reg-global-interceptor app.events/schema-validator)) 40 | 41 | (instrument!) 42 | -------------------------------------------------------------------------------- /test/theme_test.cljs: -------------------------------------------------------------------------------- 1 | (ns theme-test 2 | (:require 3 | [cljs.test :refer-macros [deftest is testing]] 4 | [day8.re-frame.test :as rf.test] 5 | [re-frame.core :as rf] 6 | [renderer.app.events :as-alias app.events] 7 | [renderer.theme.effects :as-alias theme.effects] 8 | [renderer.theme.events :as-alias theme.events] 9 | [renderer.theme.subs :as-alias theme.subs])) 10 | 11 | (defn test-fixtures 12 | [] 13 | (rf/reg-cofx 14 | ::theme.effects/native-mode 15 | (fn [cofx _] 16 | (assoc cofx :native-mode :light)))) 17 | 18 | (deftest mode 19 | (rf.test/run-test-sync 20 | (test-fixtures) 21 | (rf/dispatch [::app.events/initialize-db]) 22 | (rf/dispatch [::theme.events/set-document-attr]) 23 | 24 | (let [theme-mode (rf/subscribe [::theme.subs/mode])] 25 | 26 | (testing "default theme" 27 | (is (= :dark @theme-mode))) 28 | 29 | (testing "theme cycling" 30 | (rf/dispatch [::theme.events/cycle-mode]) 31 | (is (= :light @theme-mode)) 32 | 33 | (rf/dispatch [::theme.events/cycle-mode]) 34 | (is (= :system @theme-mode)) 35 | 36 | (rf/dispatch [::theme.events/cycle-mode]) 37 | (is (= :dark @theme-mode)))))) 38 | -------------------------------------------------------------------------------- /test/tool_test.cljs: -------------------------------------------------------------------------------- 1 | (ns tool-test 2 | (:require 3 | [cljs.test :refer-macros [deftest is testing]] 4 | [day8.re-frame.test :as rf.test] 5 | [re-frame.core :as rf] 6 | [renderer.app.events :as-alias app.events] 7 | [renderer.document.events :as-alias document.events] 8 | [renderer.element.events :as-alias element.events] 9 | [renderer.tool.events :as-alias tool.events] 10 | [renderer.tool.hierarchy :as tool.hierarchy] 11 | [renderer.tool.subs :as-alias tool.subs])) 12 | 13 | (deftest tool 14 | (rf.test/run-test-sync 15 | (rf/dispatch [::app.events/initialize-db]) 16 | (rf/dispatch [::document.events/init]) 17 | 18 | (let [active-tool (rf/subscribe [::tool.subs/active])] 19 | 20 | (testing "initial" 21 | (is (= @active-tool :transform))) 22 | 23 | (testing "edit tool" 24 | (rf/dispatch [::tool.events/activate :edit]) 25 | (is (= @active-tool :edit)) 26 | (is (= (tool.hierarchy/render :edit) [:g ()])) 27 | 28 | (rf/dispatch [::element.events/add {:tag :rect 29 | :attrs {:width 100 30 | :height 100}}]) 31 | 32 | (rf/dispatch [::tool.events/activate :edit]) 33 | (is (not= (tool.hierarchy/render :edit) [:g ()]))) 34 | 35 | (testing "cancel" 36 | (rf/dispatch [::tool.events/cancel]) 37 | (is (= @active-tool :transform)))))) 38 | -------------------------------------------------------------------------------- /test/utils/compatibility_test.cljs: -------------------------------------------------------------------------------- 1 | (ns utils.compatibility-test 2 | (:require 3 | [cljs.test :refer-macros [deftest testing are]] 4 | [renderer.utils.compatibility :as utils.compatibility])) 5 | 6 | (deftest test-version->vec 7 | (testing "version to vector" 8 | (are [x y] (= x y) 9 | [0 3 3] (utils.compatibility/version->vec "0.3.3-2-4cd3bf6-SNAPSHOT") 10 | [123 0 3] (utils.compatibility/version->vec "123.0.3-32423423")))) 11 | 12 | (deftest test-requires-migration? 13 | (testing "migration requirement" 14 | (are [x y] (= x y) 15 | true (utils.compatibility/requires-migration? [0 3 3] [0 4 0]) 16 | true (utils.compatibility/requires-migration? [0 3 3] [0 3 4]) 17 | false (utils.compatibility/requires-migration? [1 3 3] [0 3 4]) 18 | false (utils.compatibility/requires-migration? [1 3 3] [1 3 3])))) 19 | -------------------------------------------------------------------------------- /test/utils/element_test.cljs: -------------------------------------------------------------------------------- 1 | (ns utils.element-test 2 | (:require 3 | [cljs.test :refer-macros [deftest testing are]] 4 | [renderer.element.impl.core] 5 | [renderer.utils.element :as utils.element])) 6 | 7 | (deftest test-root? 8 | (testing "is root element" 9 | (are [x y] (= x y) 10 | true (utils.element/root? {:type :element :tag :canvas}) 11 | false (utils.element/root? {:type :element :tag :rect})))) 12 | -------------------------------------------------------------------------------- /test/utils/extra_test.cljs: -------------------------------------------------------------------------------- 1 | (ns utils.extra-test 2 | (:require 3 | [cljs.test :refer-macros [deftest testing are]] 4 | [renderer.utils.extra :refer [partial-right]])) 5 | 6 | (deftest test-partial-right 7 | (testing "partial right" 8 | (are [x y] (= x y) 9 | [1 0 2 0 3 0] (reduce (partial-right conj 0) [] [1 2 3]) 10 | "1a2a3a" (reduce (partial-right str "a") "" [1 2 3])))) 11 | -------------------------------------------------------------------------------- /test/utils/length_test.cljs: -------------------------------------------------------------------------------- 1 | (ns utils.length-test 2 | (:require 3 | [cljs.test :refer-macros [deftest testing are]] 4 | [renderer.utils.length :as utils.length])) 5 | 6 | (deftest test-valid-unit? 7 | (testing "check if unit is valid" 8 | (are [x y] (= x y) 9 | true (utils.length/valid-unit? "px") 10 | true (utils.length/valid-unit? "em") 11 | true (utils.length/valid-unit? "rem") 12 | false (utils.length/valid-unit? "foo") 13 | false (utils.length/valid-unit? "")))) 14 | -------------------------------------------------------------------------------- /test/utils/map_test.cljs: -------------------------------------------------------------------------------- 1 | (ns utils.map-test 2 | (:require 3 | [cljs.test :refer-macros [deftest testing are]] 4 | [renderer.utils.map :as utils.map])) 5 | 6 | (deftest test-merge-common-with 7 | (testing "merging common keys with" 8 | (are [x y] (= x y) 9 | {:foo "13"} (utils.map/merge-common-with str {:foo 1 :test 2} {:foo 3}) 10 | {} (utils.map/merge-common-with str {:test 2} {:foo 3})))) 11 | 12 | (deftest test-remove-nils 13 | (testing "removing nils from maps" 14 | (are [x y] (= x y) 15 | {:foo "bar"} (utils.map/remove-nils {:foo "bar" :test nil}) 16 | {:foo "bar" :test false} (utils.map/remove-nils {:foo "bar" :test false})))) 17 | -------------------------------------------------------------------------------- /test/utils/unit_test.cljs: -------------------------------------------------------------------------------- 1 | (ns utils.unit-test 2 | (:require 3 | [cljs.test :refer-macros [deftest testing are]] 4 | [renderer.utils.unit :as utils.unit])) 5 | 6 | (deftest test-->key 7 | (testing "convert unit string to keyword" 8 | (are [x y] (= x y) 9 | :px (utils.unit/->key "px") 10 | :px (utils.unit/->key "Px")))) 11 | 12 | (deftest test-unit 13 | (testing "match unit" 14 | (are [x y] (= x y) 15 | "px" (utils.unit/match "5px") 16 | ;; TODO: The following case should not work. We need to adjust the regex. 17 | "px" (utils.unit/match "5 px") 18 | "px" (utils.unit/match "5454px") 19 | "px" (utils.unit/match "px") 20 | "" (utils.unit/match "0")))) 21 | 22 | (deftest test-parse-unit 23 | (testing "parse unit" 24 | (are [x y] (= x y) 25 | [5 "px"] (utils.unit/parse "5px") 26 | [5 "px"] (utils.unit/parse " 5px ") 27 | [5 "px"] (utils.unit/parse " 5 px ") 28 | [5454 "px"] (utils.unit/parse "5454px") 29 | [0 "px"] (utils.unit/parse "px") 30 | [0 ""] (utils.unit/parse "0")))) 31 | -------------------------------------------------------------------------------- /test/utils/vec_test.cljs: -------------------------------------------------------------------------------- 1 | (ns utils.vec-test 2 | (:require 3 | [cljs.test :refer-macros [deftest testing are]] 4 | [renderer.utils.vec :as utils.vec])) 5 | 6 | (deftest test-remove-nth 7 | (testing "removing bth element of vector" 8 | (are [x y] (= x y) 9 | [:a :b :d] (utils.vec/remove-nth [:a :b :c :d] 2) 10 | [:a :b :c] (utils.vec/remove-nth [:a :b :c :d] 3)))) 11 | 12 | (deftest test-add 13 | (testing "adding an element to index" 14 | (are [x y] (= x y) 15 | [:a :b :c :d] (utils.vec/add [:a :b :c] 3 :d) 16 | [:a :x :b :c :d] (utils.vec/add [:a :b :c :d] 1 :x)))) 17 | 18 | (deftest test-move 19 | (testing "moving element by index" 20 | (are [x y] (= x y) 21 | [:a :b :d :c] (utils.vec/move [:a :b :c :d] 2 3) 22 | [:a :b :c :d] (utils.vec/move [:a :b :c :d] 1 1)))) 23 | 24 | (deftest test-swap 25 | (testing "swapping elements by index" 26 | (are [x y] (= x y) 27 | [:d :b :c :a] (utils.vec/swap [:a :b :c :d] 0 3) 28 | [:a :b :c :d] (utils.vec/swap [:a :b :c :d] 1 1)))) 29 | -------------------------------------------------------------------------------- /typos.toml: -------------------------------------------------------------------------------- 1 | [default.extend-words] 2 | edn = "edn" 3 | --------------------------------------------------------------------------------