React.js based plugin for LightTable

LightTable made quite a big fuzz some time ago. Now original authors are working fulltime on Eve, while LightTable is now more driven  by community members. And they're doing some pretty interesting work like switching to Atom Shell instead of node-webkit.

I like the idea that text editor is a browser. It gives you ability to do literally anything you want. So I decided to integrate React.js into LightTable plugins ecosystem. One of the most interesting things in latest release is User plugin. It is a place to configure your editor not just by tweaking settings and changing keybinding. You can add new features right there without need to create separate plugin. It is much like .vimrc or .emacs/init.el files. This turns process of configuring your editor into development task.

First let's add User plugin to workspace. Start LightTable and run command Settings: Add User plugin to workspace, open workspace panel (Workspace: toggle workspace tree command). Now you can see User dir with a some files added already.

Let's open user.behaviour file. It is a place to actually change settings and add or remove hooks on different events LightTable and plugins provide. We need to add custom javascript file with React.js and make sure User plugin is loaded too.

  [:app :lt.objs.plugins/load-js "/Users/brabadu/react/react.min.js"]
  [:app :lt.objs.plugins/load-js "user_compiled.js"]

Let's open User/src/lt/plugins/user.cljs. It already has an example that shows how to make objects and open new tabs. We'll tweak this example to use React.js.

First we need to define a namespace and dependencies
(ns lt.plugins.user
  (:require [clojure.string :as string]
            [lt.object :as object]
            [lt.objs.tabs :as tabs]
            [lt.objs.command :as cmd])
  (:require-macros [lt.macros :refer [defui behavior]]))

All LightTable plugins are in lt.plugins.* namespace. As you can see, we'll be working with LightTable object, commands and tabs. Next, we'll define couple of helper functions

(defn format-time [d]
  (first (string/split (.toTimeString d) #" ")))

(def el React/createElement)

format-time is for pretty-printing time from javascript Date object. And el is simply an alias to React.createElement.

(def label
     #js {:displayName "TimerLabel"
          :render (fn [] 
             (this-as this
                      (el "h1" nil (str "Timer: "
                                        (-> this .-props .-time)))))}))

Our component, that is going to show whole React.js-LightTable integration. I guess nothing stops us from using om/reagent/quiescent/etc. Add it's dependency in project.clj and you're good to go. Our example is simple enough not to bring anything more than bare React.

Interesting thing is how javascript this works. Calling macro this-as with symbol to which javascript this bounds is idiomatic way to access it. Most of ClojureScript libs are hiding this pattern in their innards, so you'll need it mostly when doing interop with javascript.

(defui react-panel [this]
  [:h1 {:id "app-root"} "React + LightTable!"])

(object/object* ::user.react-timer
                :tags [:user.hello]
                :behaviors [::on-close-timer-destroy]
                :init (fn [this]
                        (react-panel this)))

(def react-timer (object/create ::user.react-timer))

Here we define LightTable (not React.js) ui component react-panel and object prototype ::user.react-timer. LightTable has it's own notion of object, that it uses to implement BOT principle. When the object react-timer is created function that is passed with :init is executed.

(cmd/command {
  :desc "User: Show time"
  :exec (fn []
    (tabs/add-or-focus! react-timer)
    (let [app-root (.getElementById js/document "app-root")
          label-inst (React/render 
                        (el label #js {:time (format-time (js/Date.))} [])
          refresh-timer (fn []
                   (prn (format-time (js/Date.)))
                   (.setProps label-inst #js {:time (format-time (js/Date.))}))
          interval (js/window.setInterval refresh-timer 1000)]
       (object/merge! react-timer {:interval interval})))})

This code adds new command to LightTable. It creates new tab, then instantiates React.js component and mounts it on rendered by react-panel node.

After that defines refresh-timer function, that updates props in React component. It is called every second as a callback of js/window.setInterval. We could save timer ID in React component's state, but I decided to put it in object, so our component could be simple and it's only purpose would be view layer, while all the business-logic lies separately.

(behavior ::on-close-timer-destroy
          :triggers #{:close}
          :reaction (fn [this]
                      (js/window.clearTimeout (:interval @this))
                      (object/raise this :destroy)))

Now we're taking care cleaning on our tab close event. This is why I added prn to refresh-timer function - to see if it stopped being called by setInterval.

That's it. Code is a bit messy, but hope everything is clear. All code in one place is here

Another useful Ubuntu PPA list

It's been a long time since I posted my useful ppa list. Looking at that list and my current PPAs shows that almost everything changed and I could share my new favourites.
  • ppa:ubuntu-mozilla-daily/ppa Unofficial Firefox nightly builds. This is my main browser for about a year now. Even though sometimes it may be unstable it is great browser. It loosed it's speed in development and innovation for some time, but now it becomes good product again
  • ppa:conscioususer/polly-daily is for Polly Twitter Client which is the only usable desktop twitter client under linux for me.
  • ppa:tuxpoldo/btsync BTSync
  • ppa:webupd8team/sublime-text-3 Latest builds of the best text editor in existence - Sublime Text 3
  • ppa:ubuntu-desktop/ppa Pre-release builds of Unity and other Ubuntu goodness
  • ppa:webupd8team/java Java release by webupd8 team. I need it to run PyCharm, my main IDE for now
  • PyCharm has nonofficial PPA on UbuntuUpdates. But I prefer to go to it's EAP download page  to get fresh build.
To install any of those PPAs type in terminal:
sudo apt-add-repository ppa:ubuntu-wine/ppa
sudo apt-get update

After that you can install software from PPA with
sudo apt-get install ...

Those things are PPAs, but you have to either add them manually to /etc/apt/sources.list.d/ or with help of this tutorial
  • you can't run away from Google Chrome if you're doing web development these days. Here I also use untested version. And also it is a browser I use when some builds of Firefox Nightly has critical issues. 
  • Docker