├── .github
└── FUNDING.yml
├── .gitignore
├── LICENSE
├── README.md
├── deps.edn
├── dev
└── example
│ └── core.cljs
├── index.html
├── logo.svg
├── package-lock.json
├── package.json
├── pom.xml
├── release.edn
├── shadow-cljs.edn
├── src
└── cljs_react_devtools
│ └── core.cljs
└── yarn.lock
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: roman01la
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cpcache
3 | .idea
4 | *.iml
5 | .shadow-cljs
6 | out
7 | target
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Eclipse Public License - v 2.0
2 |
3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
4 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION
5 | OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
6 |
7 | 1. DEFINITIONS
8 |
9 | "Contribution" means:
10 |
11 | a) in the case of the initial Contributor, the initial content
12 | Distributed under this Agreement, and
13 |
14 | b) in the case of each subsequent Contributor:
15 | i) changes to the Program, and
16 | ii) additions to the Program;
17 | where such changes and/or additions to the Program originate from
18 | and are Distributed by that particular Contributor. A Contribution
19 | "originates" from a Contributor if it was added to the Program by
20 | such Contributor itself or anyone acting on such Contributor's behalf.
21 | Contributions do not include changes or additions to the Program that
22 | are not Modified Works.
23 |
24 | "Contributor" means any person or entity that Distributes the Program.
25 |
26 | "Licensed Patents" mean patent claims licensable by a Contributor which
27 | are necessarily infringed by the use or sale of its Contribution alone
28 | or when combined with the Program.
29 |
30 | "Program" means the Contributions Distributed in accordance with this
31 | Agreement.
32 |
33 | "Recipient" means anyone who receives the Program under this Agreement
34 | or any Secondary License (as applicable), including Contributors.
35 |
36 | "Derivative Works" shall mean any work, whether in Source Code or other
37 | form, that is based on (or derived from) the Program and for which the
38 | editorial revisions, annotations, elaborations, or other modifications
39 | represent, as a whole, an original work of authorship.
40 |
41 | "Modified Works" shall mean any work in Source Code or other form that
42 | results from an addition to, deletion from, or modification of the
43 | contents of the Program, including, for purposes of clarity any new file
44 | in Source Code form that contains any contents of the Program. Modified
45 | Works shall not include works that contain only declarations,
46 | interfaces, types, classes, structures, or files of the Program solely
47 | in each case in order to link to, bind by name, or subclass the Program
48 | or Modified Works thereof.
49 |
50 | "Distribute" means the acts of a) distributing or b) making available
51 | in any manner that enables the transfer of a copy.
52 |
53 | "Source Code" means the form of a Program preferred for making
54 | modifications, including but not limited to software source code,
55 | documentation source, and configuration files.
56 |
57 | "Secondary License" means either the GNU General Public License,
58 | Version 2.0, or any later versions of that license, including any
59 | exceptions or additional permissions as identified by the initial
60 | Contributor.
61 |
62 | 2. GRANT OF RIGHTS
63 |
64 | a) Subject to the terms of this Agreement, each Contributor hereby
65 | grants Recipient a non-exclusive, worldwide, royalty-free copyright
66 | license to reproduce, prepare Derivative Works of, publicly display,
67 | publicly perform, Distribute and sublicense the Contribution of such
68 | Contributor, if any, and such Derivative Works.
69 |
70 | b) Subject to the terms of this Agreement, each Contributor hereby
71 | grants Recipient a non-exclusive, worldwide, royalty-free patent
72 | license under Licensed Patents to make, use, sell, offer to sell,
73 | import and otherwise transfer the Contribution of such Contributor,
74 | if any, in Source Code or other form. This patent license shall
75 | apply to the combination of the Contribution and the Program if, at
76 | the time the Contribution is added by the Contributor, such addition
77 | of the Contribution causes such combination to be covered by the
78 | Licensed Patents. The patent license shall not apply to any other
79 | combinations which include the Contribution. No hardware per se is
80 | licensed hereunder.
81 |
82 | c) Recipient understands that although each Contributor grants the
83 | licenses to its Contributions set forth herein, no assurances are
84 | provided by any Contributor that the Program does not infringe the
85 | patent or other intellectual property rights of any other entity.
86 | Each Contributor disclaims any liability to Recipient for claims
87 | brought by any other entity based on infringement of intellectual
88 | property rights or otherwise. As a condition to exercising the
89 | rights and licenses granted hereunder, each Recipient hereby
90 | assumes sole responsibility to secure any other intellectual
91 | property rights needed, if any. For example, if a third party
92 | patent license is required to allow Recipient to Distribute the
93 | Program, it is Recipient's responsibility to acquire that license
94 | before distributing the Program.
95 |
96 | d) Each Contributor represents that to its knowledge it has
97 | sufficient copyright rights in its Contribution, if any, to grant
98 | the copyright license set forth in this Agreement.
99 |
100 | e) Notwithstanding the terms of any Secondary License, no
101 | Contributor makes additional grants to any Recipient (other than
102 | those set forth in this Agreement) as a result of such Recipient's
103 | receipt of the Program under the terms of a Secondary License
104 | (if permitted under the terms of Section 3).
105 |
106 | 3. REQUIREMENTS
107 |
108 | 3.1 If a Contributor Distributes the Program in any form, then:
109 |
110 | a) the Program must also be made available as Source Code, in
111 | accordance with section 3.2, and the Contributor must accompany
112 | the Program with a statement that the Source Code for the Program
113 | is available under this Agreement, and informs Recipients how to
114 | obtain it in a reasonable manner on or through a medium customarily
115 | used for software exchange; and
116 |
117 | b) the Contributor may Distribute the Program under a license
118 | different than this Agreement, provided that such license:
119 | i) effectively disclaims on behalf of all other Contributors all
120 | warranties and conditions, express and implied, including
121 | warranties or conditions of title and non-infringement, and
122 | implied warranties or conditions of merchantability and fitness
123 | for a particular purpose;
124 |
125 | ii) effectively excludes on behalf of all other Contributors all
126 | liability for damages, including direct, indirect, special,
127 | incidental and consequential damages, such as lost profits;
128 |
129 | iii) does not attempt to limit or alter the recipients' rights
130 | in the Source Code under section 3.2; and
131 |
132 | iv) requires any subsequent distribution of the Program by any
133 | party to be under a license that satisfies the requirements
134 | of this section 3.
135 |
136 | 3.2 When the Program is Distributed as Source Code:
137 |
138 | a) it must be made available under this Agreement, or if the
139 | Program (i) is combined with other material in a separate file or
140 | files made available under a Secondary License, and (ii) the initial
141 | Contributor attached to the Source Code the notice described in
142 | Exhibit A of this Agreement, then the Program may be made available
143 | under the terms of such Secondary Licenses, and
144 |
145 | b) a copy of this Agreement must be included with each copy of
146 | the Program.
147 |
148 | 3.3 Contributors may not remove or alter any copyright, patent,
149 | trademark, attribution notices, disclaimers of warranty, or limitations
150 | of liability ("notices") contained within the Program from any copy of
151 | the Program which they Distribute, provided that Contributors may add
152 | their own appropriate notices.
153 |
154 | 4. COMMERCIAL DISTRIBUTION
155 |
156 | Commercial distributors of software may accept certain responsibilities
157 | with respect to end users, business partners and the like. While this
158 | license is intended to facilitate the commercial use of the Program,
159 | the Contributor who includes the Program in a commercial product
160 | offering should do so in a manner which does not create potential
161 | liability for other Contributors. Therefore, if a Contributor includes
162 | the Program in a commercial product offering, such Contributor
163 | ("Commercial Contributor") hereby agrees to defend and indemnify every
164 | other Contributor ("Indemnified Contributor") against any losses,
165 | damages and costs (collectively "Losses") arising from claims, lawsuits
166 | and other legal actions brought by a third party against the Indemnified
167 | Contributor to the extent caused by the acts or omissions of such
168 | Commercial Contributor in connection with its distribution of the Program
169 | in a commercial product offering. The obligations in this section do not
170 | apply to any claims or Losses relating to any actual or alleged
171 | intellectual property infringement. In order to qualify, an Indemnified
172 | Contributor must: a) promptly notify the Commercial Contributor in
173 | writing of such claim, and b) allow the Commercial Contributor to control,
174 | and cooperate with the Commercial Contributor in, the defense and any
175 | related settlement negotiations. The Indemnified Contributor may
176 | participate in any such claim at its own expense.
177 |
178 | For example, a Contributor might include the Program in a commercial
179 | product offering, Product X. That Contributor is then a Commercial
180 | Contributor. If that Commercial Contributor then makes performance
181 | claims, or offers warranties related to Product X, those performance
182 | claims and warranties are such Commercial Contributor's responsibility
183 | alone. Under this section, the Commercial Contributor would have to
184 | defend claims against the other Contributors related to those performance
185 | claims and warranties, and if a court requires any other Contributor to
186 | pay any damages as a result, the Commercial Contributor must pay
187 | those damages.
188 |
189 | 5. NO WARRANTY
190 |
191 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT
192 | PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS"
193 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
194 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF
195 | TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR
196 | PURPOSE. Each Recipient is solely responsible for determining the
197 | appropriateness of using and distributing the Program and assumes all
198 | risks associated with its exercise of rights under this Agreement,
199 | including but not limited to the risks and costs of program errors,
200 | compliance with applicable laws, damage to or loss of data, programs
201 | or equipment, and unavailability or interruption of operations.
202 |
203 | 6. DISCLAIMER OF LIABILITY
204 |
205 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT
206 | PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS
207 | SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
208 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST
209 | PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
210 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
211 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
212 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE
213 | POSSIBILITY OF SUCH DAMAGES.
214 |
215 | 7. GENERAL
216 |
217 | If any provision of this Agreement is invalid or unenforceable under
218 | applicable law, it shall not affect the validity or enforceability of
219 | the remainder of the terms of this Agreement, and without further
220 | action by the parties hereto, such provision shall be reformed to the
221 | minimum extent necessary to make such provision valid and enforceable.
222 |
223 | If Recipient institutes patent litigation against any entity
224 | (including a cross-claim or counterclaim in a lawsuit) alleging that the
225 | Program itself (excluding combinations of the Program with other software
226 | or hardware) infringes such Recipient's patent(s), then such Recipient's
227 | rights granted under Section 2(b) shall terminate as of the date such
228 | litigation is filed.
229 |
230 | All Recipient's rights under this Agreement shall terminate if it
231 | fails to comply with any of the material terms or conditions of this
232 | Agreement and does not cure such failure in a reasonable period of
233 | time after becoming aware of such noncompliance. If all Recipient's
234 | rights under this Agreement terminate, Recipient agrees to cease use
235 | and distribution of the Program as soon as reasonably practicable.
236 | However, Recipient's obligations under this Agreement and any licenses
237 | granted by Recipient relating to the Program shall continue and survive.
238 |
239 | Everyone is permitted to copy and distribute copies of this Agreement,
240 | but in order to avoid inconsistency the Agreement is copyrighted and
241 | may only be modified in the following manner. The Agreement Steward
242 | reserves the right to publish new versions (including revisions) of
243 | this Agreement from time to time. No one other than the Agreement
244 | Steward has the right to modify this Agreement. The Eclipse Foundation
245 | is the initial Agreement Steward. The Eclipse Foundation may assign the
246 | responsibility to serve as the Agreement Steward to a suitable separate
247 | entity. Each new version of the Agreement will be given a distinguishing
248 | version number. The Program (including Contributions) may always be
249 | Distributed subject to the version of the Agreement under which it was
250 | received. In addition, after a new version of the Agreement is published,
251 | Contributor may elect to Distribute the Program (including its
252 | Contributions) under the new version.
253 |
254 | Except as expressly stated in Sections 2(a) and 2(b) above, Recipient
255 | receives no rights or licenses to the intellectual property of any
256 | Contributor under this Agreement, whether expressly, by implication,
257 | estoppel or otherwise. All rights in the Program not expressly granted
258 | under this Agreement are reserved. Nothing in this Agreement is intended
259 | to be enforceable by any entity that is not a Contributor or Recipient.
260 | No third-party beneficiary rights are created under this Agreement.
261 |
262 | Exhibit A - Form of Secondary Licenses Notice
263 |
264 | "This Source Code may also be made available under the following
265 | Secondary Licenses when the conditions for such availability set forth
266 | in the Eclipse Public License, v. 2.0 are satisfied: {name license(s),
267 | version(s), and exceptions or additional permissions here}."
268 |
269 | Simply including a copy of this Agreement, including this Exhibit A
270 | is not sufficient to license the Source Code under Secondary Licenses.
271 |
272 | If it is not possible or desirable to put the notice in a particular
273 | file, then You may include the notice in a location (such as a LICENSE
274 | file in a relevant directory) where a recipient would be likely to
275 | look for such a notice.
276 |
277 | You may add additional accurate notices of copyright ownership.
278 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | _React DevTools for ClojureScript wrappers_
4 |
5 | > ⚠️ _EXPERIMENTAL_
6 |
7 | https://github.com/roman01la/cljs-react-devtools/assets/1355501/c3bd8d6d-1127-4459-89ac-3b551d47da36
8 |
9 | [*Live demo*](https://roman01la.github.io/cljs-react-devtools/)
10 |
11 | [](https://clojars.org/com.github.roman01la/cljs-react-devtools)
12 |
13 | ## Features
14 |
15 | - React components & DOM nodes tree
16 | - Visual picking and highlighting
17 | - Props, hooks, Reagent reactions and re-frame subscriptions inspector
18 | - Update reactions and subscriptions from the inspector
19 | - Click on a value in the inspector to log it to the console
20 | - Press a shortcut to toggle DevTools visibility
21 | - Bottom, left, right docking and undocking into a separate window
22 |
23 | ### Supported React wrappers
24 |
25 | - UIx
26 | - Reagent
27 |
28 | ## Setup
29 |
30 | 1. Install the library via Git deps
31 |
32 | ```clojure
33 | {:deps {com.github.roman01la/cljs-react-devtools {:mvn/version "0.2.0"}}}
34 | ```
35 |
36 | 2. Create preload namespace and initialize DevTools
37 |
38 | ```clojure
39 | (cljs-react-devtools.core/init!
40 | {:root (js/document.getElementById "root") ;; React root
41 | :shortcut "Control-Shift-Meta-R"}) ;; toggles DevTools visibility
42 | ```
43 |
44 | ## Run example in this repo
45 |
46 | 1. Install NPM deps
47 | 2. Run `clojure -A:examples -M -m shadow.cljs.devtools.cli watch examples`
48 | 3. Open `http://localhost:3000/`
49 |
50 | [_Support development of the project via GitHub Sponsors program_](https://github.com/sponsors/roman01la)
51 |
--------------------------------------------------------------------------------
/deps.edn:
--------------------------------------------------------------------------------
1 | {:deps {com.pitch/uix.core {:mvn/version "1.2.0"}
2 | com.pitch/uix.dom {:mvn/version "1.2.0"}}
3 | :paths ["src"]
4 | :aliases {:examples {:extra-paths ["dev"]
5 | :extra-deps {org.clojure/clojure {:mvn/version "1.12.0"}
6 | org.clojure/clojurescript {:mvn/version "1.11.60"}
7 | thheller/shadow-cljs {:mvn/version "2.28.19"}
8 | reagent/reagent {:mvn/version "1.2.0"}
9 | re-frame/re-frame {:mvn/version "1.4.2"}}}
10 | :release {:extra-paths ["dev"]
11 | :extra-deps {appliedscience/deps-library {:mvn/version "0.3.4"}
12 | org.apache.maven/maven-model {:mvn/version "3.6.3"}}
13 | :main-opts ["-m" "release"]}}}
--------------------------------------------------------------------------------
/dev/example/core.cljs:
--------------------------------------------------------------------------------
1 | (ns example.core
2 | (:require [cljs-react-devtools.core]
3 | [uix.core :as uix :refer [$ defui]]
4 | [uix.dom]
5 | [uix.dev]
6 | [reagent.core :as r]
7 | [re-frame.core :as rf]))
8 |
9 |
10 | ;; dev setup
11 | (uix.dev/init-fast-refresh!)
12 |
13 | (defn ^:dev/after-load refresh []
14 | (uix.dev/refresh!))
15 |
16 | ;; app code
17 | (def tools [:rect :circle :text])
18 |
19 | (rf/reg-sub :hello/workd
20 | (fn []
21 | :nothing))
22 |
23 | (defn tool-button [{:keys [selected? label on-press]}]
24 | (r/with-let [a (r/atom 1)]
25 | @(rf/subscribe [:hello/workd])
26 | [:div {:on-click on-press
27 | :style {:padding "4px 8px"
28 | :cursor :pointer
29 | :border-radius 3
30 | :color (when selected? "#fff")
31 | :background-color (when selected? "#ff89da")}}
32 | label]))
33 |
34 | (defui toolbar [{:keys [state set-state on-add-shape]}]
35 | (let [{:keys [grid?]} state]
36 | ($ :div {:style {:padding "8px 16px"
37 | :height 46
38 | :display :flex
39 | :align-items :center
40 | :background-color "#fff"
41 | :position :relative
42 | :box-shadow "0 1px 1px rgba(0, 0, 10, 0.2)"}}
43 | ($ :img {:src "https://raw.githubusercontent.com/pitch-io/uix/master/logo.png"
44 | :style {:height "100%"
45 | :margin "0 16px 0 0"}})
46 | (for [t tools]
47 | (r/as-element [tool-button {:key t :label (name t) :on-press #(on-add-shape t)}]))
48 | ($ :div {:style {:width 1 :height "60%" :background-color "#c1cdd0" :margin "0 8px"}})
49 | (r/as-element
50 | [tool-button {:label "grid"
51 | :selected? grid?
52 | :on-press #(set-state (update state :grid? not))}]))))
53 |
54 | (defui ^:memo canvas-grid [{:keys [width height size color]}]
55 | (let [wn (Math/ceil (/ width size))
56 | hn (Math/ceil (/ height size))]
57 | ($ :<>
58 | (for [widx (range wn)]
59 | ($ :line {:key widx
60 | :x1 (* size widx)
61 | :x2 (* size widx)
62 | :y1 0
63 | :y2 height
64 | :stroke color}))
65 | (for [hidx (range hn)]
66 | ($ :line {:key hidx
67 | :y1 (* size hidx)
68 | :y2 (* size hidx)
69 | :x1 0
70 | :x2 width
71 | :stroke color})))))
72 |
73 | (defui cursor [{:keys [mx my r color]}]
74 | (let [mx (+ mx (/ r 2))
75 | my (+ my (/ r 2))]
76 | ($ :circle {:cx (- mx (/ r 2)) :cy (- my (/ r 2)) :r r :fill color})))
77 |
78 | (defui rect [{:keys [x y width height fill-color stroke-width stroke-color
79 | children on-mouse-down on-mouse-up]}]
80 | ($ :rect
81 | {:on-mouse-down on-mouse-down
82 | :on-mouse-up on-mouse-up
83 | :width width
84 | :height height
85 | :x x
86 | :y y
87 | :fill fill-color
88 | :stroke-width stroke-width
89 | :stroke stroke-color}
90 | children))
91 |
92 | (defui circle [{:keys [x y width height fill-color stroke-width stroke-color on-mouse-down]}]
93 | ($ :ellipse
94 | {:on-mouse-down on-mouse-down
95 | :cx (+ x (/ width 2))
96 | :cy (+ y (/ height 2))
97 | :rx (/ width 2)
98 | :ry (/ height 2)
99 | :fill fill-color
100 | :stroke-width stroke-width
101 | :stroke stroke-color}))
102 |
103 | (defui text [{:keys [x y width height fill-color stroke-width stroke-color
104 | value font-size font-family font-style
105 | on-mouse-down]}]
106 | ($ :text
107 | {:on-mouse-down on-mouse-down
108 | :x x
109 | :y y
110 | :font-family font-family
111 | :font-size font-size
112 | :font-style font-style}
113 | value))
114 |
115 | (defn map-object [object size]
116 | (-> object
117 | (update :x * size)
118 | (update :y * size)
119 | (update :width * size)
120 | (update :height * size)))
121 |
122 | (defui ^:memo objects-layer [{:keys [objects size on-select]}]
123 | (for [{:keys [id] :as object} objects]
124 | (let [idx (.indexOf objects object)
125 | object (-> (map-object object size)
126 | (assoc :key id :on-mouse-down #(on-select idx)))]
127 | (case (:type object)
128 | :rect ($ rect object)
129 | :circle ($ circle object)
130 | :text ($ text object)))))
131 |
132 | (defui ^:memo edit-layer [{:keys [mx my on-object-changed on-select idx selected size]}]
133 | (let [[active? set-active] (uix/use-state false)
134 | selected? (some? selected)
135 | on-move (uix/use-callback
136 | (fn [x y]
137 | (on-object-changed idx (assoc selected :x x :y y)))
138 | [idx selected on-object-changed])]
139 |
140 | (uix/use-effect
141 | #(when active?
142 | (on-move mx my))
143 | [selected? active? mx my on-move])
144 |
145 | (uix/use-effect
146 | #(when selected?
147 | (set-active true))
148 | [selected?])
149 |
150 | (when selected
151 | ($ rect
152 | (-> (map-object selected size)
153 | (assoc
154 | :on-mouse-down #(set-active true)
155 | :on-mouse-up #(set-active false)
156 | :stroke-width 1
157 | :stroke-color "#0000ff"
158 | :fill-color :transparent))))))
159 |
160 | (defui ^:memo background-layer [{:keys [width height on-mouse-down]}]
161 | ($ rect
162 | {:on-mouse-down #(on-mouse-down)
163 | :x 0
164 | :y 0
165 | :width width
166 | :height height
167 | :fill-color :transparent
168 | :stroke-color :none}))
169 |
170 | (defui canvas [{:keys [state on-object-changed on-object-select]}]
171 | (let [{:keys [grid? canvas]} state
172 | [[width height] set-size] (uix/use-state [0 0])
173 | [[ox oy] set-offset] (uix/use-state [0 0])
174 | [[mx my] set-mouse] (uix/use-state [0 0])
175 | ref (uix/use-ref)
176 | size 8
177 | mx (quot (- mx ox) size)
178 | my (quot (- my oy) size)]
179 | (uix/use-effect
180 | (fn []
181 | (set-offset [(.-offsetLeft @ref) (.-offsetTop @ref)])
182 | (set-size [(.-width js/screen) (.-height js/screen)]))
183 | [])
184 | ($ :div {:ref ref
185 | :on-mouse-move (fn [^js e]
186 | (set-mouse [(.-clientX e) (.-clientY e)]))
187 | :style {:flex 1
188 | :position :relative
189 | :background-color "#ebeff0"}}
190 | ($ :svg {:style {:width width
191 | :height height
192 | :position :absolute
193 | :left 0
194 | :top 0}
195 | :view-box (str "0 0 " width " " height)}
196 | (when grid?
197 | ($ :<>
198 | ($ canvas-grid {:width width :height height :size size :color "#c1cdd0"})
199 | ($ cursor {:r 2
200 | :color "#4f7f8b"
201 | :mx (* size mx)
202 | :my (* size my)})))
203 | ($ background-layer
204 | {:width width
205 | :height height
206 | :on-mouse-down on-object-select})
207 | ($ objects-layer
208 | {:objects (:objects canvas)
209 | :size size
210 | :on-select on-object-select})
211 | ($ edit-layer
212 | {:size size
213 | :on-select on-object-select
214 | :on-object-changed on-object-changed
215 | :mx mx
216 | :my my
217 | :idx (:selected canvas)
218 | :selected (when (:selected canvas)
219 | (nth (:objects canvas) (:selected canvas)))})))))
220 |
221 | (def default-styles
222 | {:x 32
223 | :y 32
224 | :width 12
225 | :height 12
226 | :stroke-width 2
227 | :stroke-color "#ff0000"
228 | :fill-color "#00ff00"})
229 |
230 | (defui app []
231 | (let [[state set-state] (uix/use-state {:grid? true
232 | :canvas {:selected nil
233 | :objects []}})
234 | on-add-shape (fn [shape]
235 | (let [id (random-uuid)]
236 | (set-state
237 | (->> (case shape
238 | :rect (merge default-styles {:type :rect :id id})
239 | :circle (merge default-styles {:type :circle :id id})
240 | :text (merge default-styles {:type :text :id id
241 | :value "text" :font-family "Inter"
242 | :font-size 32 :font-style :normal}))
243 | (update-in state [:canvas :objects] conj)))))
244 | on-object-select (fn
245 | ([]
246 | (set-state (assoc-in state [:canvas :selected] nil)))
247 | ([idx]
248 | (set-state (assoc-in state [:canvas :selected] idx))))
249 | on-object-changed (uix/use-callback
250 | (fn [idx object]
251 | (set-state
252 | #(assoc-in % [:canvas :objects idx] object)))
253 | [])]
254 | ($ :div {:style {:font-family "Inter"
255 | :font-size 14
256 | :display :flex
257 | :flex-direction :column
258 | :width "100vw"
259 | :height "100vh"}}
260 | ($ toolbar {:state state :set-state set-state :on-add-shape on-add-shape})
261 | ($ canvas {:state state
262 | :on-object-select on-object-select
263 | :on-object-changed on-object-changed}))))
264 |
265 | ;; init app
266 | (defonce -init
267 | (let [root (uix.dom/create-root (js/document.getElementById "root"))]
268 | (uix.dom/render-root ($ app) root)
269 | nil))
270 |
271 | (cljs-react-devtools.core/init!
272 | {:root (js/document.getElementById "root")
273 | :shortcut "Control-Shift-Meta-R"})
274 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |