Web based ASCII diagrams editor, written in ClojureScript.
Basically, the main goal of this project initially was to try out ClojureScript, React, and the approach of having one global state for the whole relatively complex app and having fully synchronous flow instead of reacting to changes via observers.
As a side effect, it appears to be nice and hopefully useful tool. :)
It is written in ClojureScript, using ReactJS as a view layer, Quiescent as a thin functional wrapper around React, CodeMirror as a text editor, and a bunch of other dependencies you can find in project.clj
All starts from tixi.core
, where we do initial assigning of auxiliary event listeners (like, keypress
events or resize events), create a core.async channel listener, where forward all the messages from the channel
to tixi.dispatcher
. And then - it does initial rendering of the view via tixi.view/render
, passing our
global state into it, which is just one large atom value.
In tixi.view
, we assign different event handlers via React's event system, and when these events
happen, we create a appropriate payload and send it to core.async
channel, which then will be dispatched
to tixi.dispatcher
.
tixi.dispatcher
tries to figure out what's really going on (e.g., if this is just mouse move or mouse drag),
and calls an appropriate function in tixi.controller
. After that, tixi.controller
depending on the
received payload calls various tixi.mutators
, which change the global state (our atom), and then
tixi.controller
rerenders the app via tixi.view/render
again, passing the new value of the atom to it
(from tixi.data
)
Like this:
+----------------+ +-------------------+ +-------------------+
| | | | | |
| tixi.core +----->+ tixi.view +----->+ tixi.dispatcher |
| | | | | |
+----------------+ +---------+---------+ +---------+---------+
^ |
| v
| +---------+---------+
+----------------+ |
| tixi.controller |
+--------------->+ |
| +---------+---------+
| |
| v
+---------+---------+ +---------+---------+
| | | |
| tixi.data +<-----+ tixi.mutators |
| | | |
+-------------------+ +-------------------+
And that's it. Very simple, no observers at all, very clear flow.
Pros:
tixi.data/data
.tixi.mutators
, which do something new with
the data.tixi.mutators
. All other
functions are pure.Cons:
The application data is kind of split to 3 parts - canvas state (with undo/redo stack), cache and all other stuff. The canvas state contains only the data, which describe the data on the canvas in the shortest way.
For example, if we have a line and a rectangle on the canvas, we will describe them as:
{:completed {0 {:input R[1 1][5 5], :type :rect, :z 0}
1 {:input R[10 10][20 20], :type :line, :z 0}}}
Which means the rectangle will be with the coordinates - left-top corner is (1,1), and right-bottom corner is (5,5), and the line will be with coordinates (10, 10) and (20, 20).
We use that information to build :cache - a set of points and text, which we are going to show on the screen.
The code for that is in tixi.items
and resources/tixi/js/drawer.js
. I had to write generating points and ASCII text
in JavaScript, because it shows significantly better performance, and this is the only part of the app where this
performance is crucial for smooth UX.
After that, we end up with the list of points, which we could use to track "locks", "hits" (to track when mouse is over some character), and also with the ASCII text we are going to render on the screen.
Undo/Redo stack is a tree. So, it actually could support "Undo Tree", like in Vim or Emacs, when you don't lose your history even if you accidentally Undoed something, and then added a new change.
So far, it is not implemented in UI though, in UI it is "flat" for now, so I always select the rightmost branch of the tree when do "Redo".
The most convenient way of doing that is just to run:
$ lein figwheel dev
It will compile a project, and start the figwheel server, which will reload the code in browser every single time you change any cljs or CSS file
Now, open http://localhost:3449/index.html in your browser, and you should be able to see it working.
There is also brepl running on 9000 port in dev build, so you could connect to it from REPL.
If you want to help to the project, and fix some bug or add some feature - feel free to create a pull request, that's the whole point of the open source software, right? :)
To run tests use lein cljsbuild test unit
command.
If you found a bug, and want to create a ticket - please do that in Github Issues.
If you just want to help and add some code, but have no idea what to work on, there is TODO, you can get something from there.
Right now the UI of the app looks awful, and some designer's help would be very appreciated. If you want to help with the design, that would be very appreciated.