on master help sign in
Brought in landscape-tile doc post docs repo reorg
0804955David Kerschner
tutorials/hoon/landscape-tile.mdnew file+276
@@ -0,0 +1,276 @@
1++++
2+title = "2.9.1 Walkthrough: Landscape Tile"
3+weight = 40
4+template = "doc.html"
5++++
6+
7+Tiles are one way to build apps in Landscape, the Urbit web interface. This document will guide you through the process of creating a tile using Hoon and some pre-built tools.
8+
9+> Note that Tiles use the [React](https://reactjs.org/) framework, which will not be covered in this walkthrough; instead of writing the React ourselves, we will use the tools from the repo mentioned above. We encourage you to use react to build a Tile yourself, though.
10+
11+![](https://media.urbit.org/docs/landscape-tile/default-tiles.png)
12+
13+## Getting Started
14+
15+It's recommended to always use this process in this section when creating a tile, even when your knowledge becomes more advanced. It makes development much easier.
16+
17+First and foremost, you'll need to have a [mounted fakezod](/docs/using/creating-development-ship) of Urbit version 0.8.0 or higher.
18+
19+Then you'll need to get the pre-built tools that were mentioned. Navigate to [urbit/create-landscape-app](https://github.com/urbit/create-landscape-app) and click the green `Use this template` button on to copy the repo to your own account. After that, you will need to clone that new repository locally, using either the `git clone <repo-url>` terminal command, or by clicking the `Clone or download` button on the GitHub.
20+Once you've cloned the repo, `cd` into the newly downloaded folder and run `npm install` to install dependencies. After that, run `npm start` to start the setup wizard.
21+
22+![](https://media.urbit.org/docs/landscape-tile/npm-setup.png)
23+
24+The wizard prompts you with three questions.
25+- For the purposes of this walkthrough, choose `testing` as the name of your file; your answer determines the name of a Hoon file and the name of directory containing the `tile.js` file that the Hoon file references.
26+- When prompted to choose between a title and a full application, answer `tile`.
27+- For the final question, simply give the full path to your ship's pier and the desired desk (such as `/home`). This links your ship to this repo.
28+
29+### Testing the Default App
30+
31+With the questions answered, you now have two halves a default app that comes with the repo: `testing.hoon` and `tiles/tile.js`. Let's test of that the default app is set up correctly, so that we make an app of our own.
32+
33+Run `npm run build`. This copies files into the pier that you linked into the `/app` directory of your desk. Your Hoon file will be `/app/testing.hoon`, and your js file will be `/app/testing/js/tile.js`.
34+
35+![](https://media.urbit.org/docs/landscape-tile/npm-run-build.png)
36+
37+Starting with Urbit version 0.8.0, files are no longer automatically synced from your Unix pier to your ship, so after running the build command, you'll need to run the `|commit %home` command in your ship's Dojo.
38+
39+![](https://media.urbit.org/docs/landscape-tile/commithome.png)
40+
41+Once this is done, you can start the application by running `|start %testing` in your ship's Dojo. Then navigate to `http://localhost:80` (or the appropriate port), and log in with the password you get from running `+code` in the Dojo. Then you should see where you should see your new tile.
42+
43+Any time you make changes to the Hoon file or the js file, you will need to run the `npm run build` command in the repo as well as `|commit %home` in the Dojo. Ford will then automatically rebuild your application with the new changes.
44+
45+## Our Own Tile
46+
47+Let's examine a sample tile that has different Hoon code from the default, to explore how the tile system works. This program, composed of the `testing.hoon` and `tile.js` files, creates a tile which can be used to send `hi` messages to ships, just the way you can with `|hi ~zod` from the dojo. Keep in mind that unless you boot other fakeships you won't get responses from ships other than `~zod`.
48+
49+`tile.js` does the actual front-end rendering using React. The details of React are left for the reader to learn on their own, but you can see here how to store data in the state. That tile will be rendered every time the state gets updated. Calling `api.action` will send a JSON poke to our gall app. We have attached the function where we use that to the button on the tile, so it will get run when the button is clicked.
50+
51+In your pier, replace the code in `/app/testing.hoon` with the Hoon code below.
52+
53+```
54+/+ *server
55+/= tile-js
56+ /^ octs
57+ /; as-octs:mimes:html
58+ /: /===/app/testing/js/tile
59+ /| /js/
60+ /~ ~
61+ ==
62+=, format
63+|%
64++$ move (pair bone card)
65++$ poke
66+ $% [%launch-action [@tas path @t]]
67+ [%helm-hi @t]
68+ ==
69++$ card
70+ $% [%poke wire dock poke]
71+ [%http-response =http-event:http]
72+ [%connect wire binding:eyre term]
73+ [%diff %json json]
74+ ==
75+--
76+|_ [bol=bowl:gall ~]
77+++ bound
78+ |= [wir=wire success=? binding=binding:eyre]
79+ ^- (quip move _+>.$)
80+ [~ +>.$]
81+++ prep
82+ |= old=(unit ~)
83+ ~& 'it built'
84+ ^- (quip move _+>.$)
85+ =/ launcha
86+ [%launch-action [%testing /testingtile '/~testing/js/tile.js']]
87+ :_ +>.$
88+ :~
89+ [ost.bol %connect / [~ /'~testing'] %testing]
90+ [ost.bol %poke /testing [our.bol %launch] launcha]
91+ ==
92+++ peer-testingtile
93+ |= pax=path
94+ ^- (quip move _+>.$)
95+ =/ jon=json %- pairs:enjs:format :~
96+ [%status `json`s+'First starting']
97+ ==
98+ [[ost.bol %diff %json jon]~ +>.$]
99+++ send-tile-diff
100+ |= jon=json
101+ ^- (list move)
102+ %+ turn (prey:pubsub:userlib /testingtile bol)
103+ |= [=bone ^]
104+ [bone %diff %json jon]
105+++ send-status-diff
106+ |= msg=tape
107+ %- send-tile-diff
108+ %- pairs:enjs:format :~
109+ [%status `json`s+(crip msg)]
110+ ==
111+++ poke-json
112+ |= jon=json
113+ ^- (quip move _+>.$)
114+ ~& 'poke-json in testing called'
115+ ~& jon
116+ =/ json-map ((om:dejs format same) jon)
117+ =/ ship-to-hi (so:dejs:format (~(got by json-map) %ship))
118+ ~& ship-to-hi
119+ =/ sthu (need (slaw %p ship-to-hi))
120+ :_ +>.$
121+ %+ weld
122+ (send-status-diff "looking")
123+ ^- (list move)
124+ :~
125+ :- ost.bol
126+ :^ %poke
127+ /helm/hi/[ship-to-hi]
128+ [sthu %hood]
129+ [%helm-hi '']
130+ ==
131+++ coup-helm-hi
132+ |= [pax=path cop=(unit tang)]
133+ ~& ["Coup recieved" pax]
134+ :_ +>.$
135+ ?~ cop
136+ (send-status-diff "successfully found {<pax>}")
137+ (send-status-diff "failure")
138+++ poke-handle-http-request
139+ %- (require-authorization:app ost.bol move .)
140+ |= =inbound-request:eyre
141+ ^- (quip move _+>.$)
142+ =/ request-line (parse-request-line url.request.inbound-request)
143+ =/ back-path (flop site.request-line)
144+ =/ name=@t
145+ =/ back-path (flop site.request-line)
146+ ?~ back-path
147+ ''
148+ i.back-path
149+ ?~ back-path
150+ [[ost.bol %http-response not-found:app]~ +>.$]
151+ ?: =(name 'tile')
152+ [[ost.bol %http-response (js-response:app tile-js)]~ +>.$]
153+ [[ost.bol %http-response not-found:app]~ +>.$]
154+::
155+--
156+```
157+
158+Now, replace the code below is the contents of `/app/testing/js/tile.js` with the code below. Once you've replaced the code in both files, run `|commit %home` in the Dojo.
159+
160+```
161+import React, { Component } from 'react';
162+import classnames from 'classnames';
163+import _ from 'lodash';
164+
165+
166+export default class testingTile extends Component {
167+
168+ constructor(props) {
169+ super(props);
170+ console.log("og props");
171+ console.log(this.props);
172+ this.state = { ship: "~zod" };
173+ }
174+
175+ sub() {
176+ api.action('testing', 'json', {ship: this.state.ship});
177+ }
178+
179+ handleChange(event) {
180+ this.setState({ship: event.target.value});
181+ }
182+
183+ render() {
184+ return (
185+ <div className="w-100 h-100 relative" style={{ background: '#1a1a1a' }}>
186+ <p className="gray label-regular b absolute" style={{ left: 8, top: 4 }}>Testing</p>
187+ <p className="white absolute" style={{ top: 25, left: 8 }}>Hi a ship {this.props.data.status}</p>
188+ <p className="white absolute" style={{ top: 100, left: 8 }}>
189+ <button onClick={this.sub.bind(this)}>stuff</button></p>
190+ <p className="white absolute" style={{ top: 150, left: 15 }}>
191+ <input type="text" value={this.state.ship}
192+ onChange={this.handleChange.bind(this)}/></p>
193+ </div>
194+ );
195+ }
196+
197+}
198+
199+window.testingTile = testingTile;
200+```
201+
202+### Default Code Components
203+
204+Parts of the `testing.hoon` code shown above is the "scaffolding" that was automatically built for us by the default Hoon file from the `create-landscape-app` repo. We'll give a general overview of those arms here.
205+
206+As with other gall apps, `card` and `move` define the types that can be produced by the app, `card` specifically defines which type of requests can be made to other parts of the system.
207+
208+`++prep` is called when the application first starts, and when its code gets updated. It will get a `unit` of the old state and `++prep` needs to update the state. You might notice the use of `~&`. This is useful to give us feedback to make sure our changes have been loaded.
209+
210+The `++peer-testingtile` arm is called when the tile first subscribes to the app. The subscription logic is mostly handled for us by the `launch` app here it simply produces some data to send back to the tile, indicating its initial state.
211+
212+The two arms that are the most important for our application are `++poke-json` which receives any JSON sent by the tile and `++coup-helm-hi`, which we'll cover in a bit.
213+
214+### Interesting Code
215+
216+Now that we've glanced at the default components of the program, lets take a close look at the interesting parts of the code.
217+
218+```
219+++ poke-json
220+ |= jon=json
221+ ^- (quip move _+>.$)
222+ ~& 'poke-json in testing called'
223+ ~& jon
224+ =/ json-map ((om:dejs format same) jon)
225+ =/ ship-to-hi (so:dejs:format (~(got by json-map) %ship))
226+ ~& ship-to-hi
227+ =/ sthu (need (slaw %p ship-to-hi))
228+ :_ +>.$
229+ %+ weld
230+ (send-status-diff "looking")
231+ ^- (list move)
232+ :~
233+ :- ost.bol
234+ :^ %poke
235+ /helm/hi/[ship-to-hi]
236+ [sthu %hood]
237+ [%helm-hi '']
238+ ==
239+```
240+
241+`++poke-json` is going to accept a `json` and produce a `quip`. A `quip` is a pair of: a list of `moves` (in this case), and some state which is the same type as the core we are building. To be clear, a `json` in Hoon is not the same thing as something in the more general JSON format. It is rather a parsed data structure.
242+
243+There are several uses of the `~&` rune. These are simply debugging printfs that we can skip over.
244+
245+`(om:dejs:format same)` builds a gate that converts a `json` into a `map`.
246+
247+`++so:dejs:format` formats some piece of data out of the `json` map, in this case the value of `%ship`. `sthu` is transformed by [`slaw`](https://urbit.org/docs/reference/library/4m/#slaw) into an actual `@p` to verify that we didn't get sent nonsense. If we did, `need` will cause the gate to crash.
248+
249+Finally, we produce the list of `move`s and the new state that is the data. This starts with the `:_` rune, which is the inverted form of `:-`, the [cons](https://en.wikipedia.org/wiki/Cons) rune. The state of our application will not actually change, so we can just use `+>.$` the existing core.
250+
251+```
252+++ send-status-diff
253+ |= msg=tape
254+ %- send-tile-diff
255+ %- pairs:enjs:format :~
256+ [%status `json`s+(crip msg)]
257+ ==
258+```
259+
260+`++send-status-diff` takes a `tape` and produces a `list` of `moves`, one to each subscriber to our application, to update them with a JSON structure made from the `tape`.
261+
262+We weld the result of this gate with one more `move` we've built here: the list starting with `:- ost.bol`. This `move` is a `%poke` to `%hood`. Specifically, it's a `%helm-hi` poke, the poke used by the `hi.hoon` generator to send `|hi` messages to other ships. In this case, we're sending it to the ship whose name was given to us in the JSON.
263+
264+When we get a response from `%helm` to our `poke`, we'll receive a `coup`, which is the `move` always sent in response to a `poke`. Specifically, we'll get a `coup` for `helm-hi`, which is why we want an arm named `coup-helm-hi` to handle that.
265+
266+```
267+++ coup-helm-hi
268+ |= [pax=path cop=(unit tang)]
269+ ~& ["Coup recieved" pax]
270+ :_ +>.$
271+ ?~ cop
272+ (send-status-diff "successfully found {<pax>}")
273+ (send-status-diff "failure")
274+```
275+
276+The code above should be easy to understand. `cop` is `unit` that will have an error in it if the request failed and be `~` otherwise. If it's `~`, we use `send-status-diff` to send the tile a success message; otherwise we send a failure message.