• Stars
    star
    427
  • Rank 101,680 (Top 3 %)
  • Language
    JavaScript
  • License
    Eclipse Public Li...
  • Created over 10 years ago
  • Updated about 3 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Ascii charts editor

Textik Travis CI status

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. :)

Features

  • You can draw rectangles and lines (items) on the canvas, and write text
  • You can write text inside the items
  • You can select items, move them around, resize
  • You can "lock" lines to rectangles, so if you move/resize rectangle, the line will be moved too
  • You can add arrows to the lines
  • There is undo and redo.
  • There is copy and paste (with regular Cmd+C/Ctrl+C and Cmd+V/Ctrl+V)
  • You can copy the final result to the clipboard, and paste it somewhere in email/docs/comments/etc.

Libraries and Frameworks

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

Architecture

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:

  • Simple architecture - less bugs, easier to track them, we can easily restore the whole state of the app just by assigning one value to tixi.data/data.
  • Easy to add new features - you almost always just add new tixi.mutators, which do something new with the data.
  • Clear separate of responsibilities of the namespaces
  • It's easy to test business logic
  • There is basically only one place where the data is being changed - in tixi.mutators. All other functions are pure.
  • Purely React thing, but so cool - React tracks and cleans up all the event handlers you create there. It is a huge deal, from my experience there is always a lot of hard-tracking errors and memory leaks when you somehow forget to remove event handlers.

Cons:

  • It's a bit harder to make REALLY reusable widgets - they should know something about the global data structure to keep their data there. Not a big deal for this app though.

Data and rendering

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

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".

Running on your machine

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.

Contributing

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.

More Repositories

1

vim-ruby-debugger

Vim plugin for debugging Ruby applications (using ruby-debug-ide gem)
Vim Script
435
star
2

liftosaur

Weightlifting tracker app for coders
TypeScript
186
star
3

persistent.objc

Persistent data structures in Objective-C
Objective-C
114
star
4

aws-cdk-lambda-typescript-starter

AWS CDK TypeScript skeleton starter for SSR app on Lambda and DynamoDB
TypeScript
27
star
5

page_versioning

Extension for the Radiant CMS. Allows you to save and review all changes of the pages, snippets and layouts.
Ruby
26
star
6

jquery-ui-tree

Widget for jQuery UI. Adds nested expanded/collapsed tree with drag'n'drop support.
JavaScript
19
star
7

perfection

Skeleton of a nice dev environment for ClojureScript
Clojure
17
star
8

debugger-xml

XML interface for 'debugger' gem, compatible with ruby-debug-ide
Ruby
17
star
9

crossdart

Cross-reference of Dart packages
Dart
13
star
10

dartdocs.org

Generates the Dart documentation for all the packages in pub
Dart
12
star
11

crossdart-chrome-extension

Crossdart Chrome Extension, adds "Go to Declaration" and "Find Usages" to Dart projects on Github.
JavaScript
12
star
12

radiant-truncate-extension

Adds truncate tag to Radiant for truncating data within the tag.
Ruby
8
star
13

radiant-webservices-extension

Extension for the Radiant CMS. It adds webservices radiant tags that allows to make remote queries to your webservices and paste results on the pages.
Ruby
7
star
14

radiant-route-handler-extension

RadiantCMS extension. If static page with given URL doesn't exist, it will try to open some special page and send some special params to there.
Ruby
6
star
15

solutions_grid

Ruby On Rails plugin, implementing AJAXed grid with sorting, filtering and paginating.
Ruby
6
star
16

crosshub-chrome-extension

Adds 'Go to definition' and 'Find Usages' functionality to TypeScript projects on Github
JavaScript
6
star
17

squilder

Type-safe SQL builder in Dart
Dart
4
star
18

mintik

A minimal example of a Quiescent app
Clojure
4
star
19

radiant-date-converter-extension

RadiantCMS extension. Adds tag <r:date_converter> for converting date from one format to another
Ruby
4
star
20

radiant-file-content-extension

An extension for RadiantCMS. Adds tag <r:file_content> for inserting contents of external files.
Ruby
3
star
21

ormapper.dart

Lightweight ORM based on Squilder
Dart
3
star
22

qunit_test

Generator and rake task for testing of JavaScript by QUnit
JavaScript
3
star
23

lens-shmens

TypeScript introspectable lens library, with ability to create "recordings" - delayed lens execution.
TypeScript
2
star
24

radiant-cache-observer

Run script after every clear of cache
2
star
25

radiant-random-number-extension

RadiantCMS extension. Adds the tag <r:random_number> for generating random number in given range.
Ruby
2
star
26

lens.dart

Uber simple functional lenses builder in Dart
Dart
2
star
27

selenium-on-rails

Fork of stable version of Selenium on Rails for Rails 2.1
2
star
28

fudge_form

Ruby
2
star
29

rails_foreign_keys

Fork of Foreign Key Schema Dumper Plugin, but only with MySQL and fixed for working with Rails 2.1
2
star
30

12_hour_time

Fork of rails plugin that adds AM/PM time formatting to DateHelper
2
star
31

human_attribute_override

Fork of Human Attribute Override plugin for Rails. The plugin allows humanized versions of attributes to be overridden with custom strings.
Ruby
2
star
32

yatro

Yet Another Typesafe Router
TypeScript
1
star
33

vimfiles

My .vim files
JavaScript
1
star
34

crossdart-server

Crossdart server
Dart
1
star
35

ar_query_source

Puts the source line of the SQL query after the query in logs
Ruby
1
star