├── .gitignore
├── .nrepl-port
├── Procfile
├── README.md
├── clojurescript-ethereum-example.iml
├── compile-solidity.sh
├── env
└── dev
│ └── user.clj
├── project.clj
├── resources
└── public
│ ├── contracts
│ ├── build
│ │ ├── SimpleTwitter.abi
│ │ ├── SimpleTwitter.bin
│ │ ├── SimpleTwitter.json
│ │ ├── strings.abi
│ │ └── strings.bin
│ └── src
│ │ ├── SimpleTwitter.sol
│ │ └── strings.sol
│ ├── css
│ ├── styles.css
│ └── styles.main.css.map
│ ├── index.html
│ └── less
│ └── styles.main.less
└── src
├── clj
└── clojurescript_ethereum_example
│ └── core.clj
└── cljs
└── clojurescript_ethereum_example
├── address_select_field.cljs
├── core.cljs
├── db.cljs
├── handlers.cljs
├── subs.cljs
├── utils.cljs
└── views.cljs
/.gitignore:
--------------------------------------------------------------------------------
1 | /*.log
2 | /target
3 | /*-init.clj
4 | /resources/public/js/compiled
5 | out
6 | /figwheel_server.log
7 | /.idea/
8 | /.nrepl-port
9 | /clojurescript-ethereum-example.iml
10 |
--------------------------------------------------------------------------------
/.nrepl-port:
--------------------------------------------------------------------------------
1 | 62049
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: java $JVM_OPTS -cp target/clojurescript-ethereum-example.jar clojure.main -m clojurescript-ethereum-example.core
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Clojure Ethereum Example
2 |
3 | This is source code for tutorials:
4 | * [How to create decentralised apps with Clojurescript re-frame and Ethereum](https://medium.com/@matus.lestan/how-to-create-decentralised-apps-with-clojurescript-re-frame-and-ethereum-81de24d72ff5#.nvfyq27lb)
5 | * [How to deploy Clojurescript app into distributed storage IPFS](https://medium.com/@matus.lestan/how-to-deploy-clojurescript-app-into-distributed-storage-ipfs-e9d02cdfbc20#.ax3ra84bz)
6 |
7 | Deployed at:
8 | * https://clojurescript-ethereum-example.herokuapp.com/
9 | * https://ipfs.io/ipns/QmXj5vKEKSU4kkjGnWq9ZJeG4xrkacDugme4iXz6qtvPgN/
10 |
11 | ## Start App
12 | Start Solidity auto compiling
13 | ```
14 | lein auto compile-solidity
15 | ```
16 | Start less compiling
17 | ```
18 | lein less4j auto
19 | ```
20 | Start App
21 | ```
22 | lein repl
23 | ```
24 | ```clojure
25 | (clojurescript-ethereum-example.core/-main)
26 | (figwheel-sidecar.repl-api/start-figwheel! (figwheel-sidecar.config/fetch-config))
27 | (figwheel-sidecar.repl-api/cljs-repl)
28 | ```
29 | Open at http://localhost:6655/
30 |
31 |
--------------------------------------------------------------------------------
/clojurescript-ethereum-example.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
--------------------------------------------------------------------------------
/compile-solidity.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | cd resources/public/contracts/src
3 | solc --overwrite --optimize --bin --abi --combined-json bin,abi SimpleTwitter.sol -o ../build/ > ../build/SimpleTwitter.json
4 |
--------------------------------------------------------------------------------
/env/dev/user.clj:
--------------------------------------------------------------------------------
1 | (ns user
2 | (:require [figwheel-sidecar.repl-api]
3 | [clojurescript-ethereum-example.core]
4 | [ring.middleware.reload :refer [wrap-reload]]))
5 |
6 | (set! *warn-on-reflection* true)
7 | (set! *unchecked-math* :warn-on-boxed)
8 |
9 | (def http-handler
10 | (wrap-reload #'clojurescript-ethereum-example.core/http-handler))
--------------------------------------------------------------------------------
/project.clj:
--------------------------------------------------------------------------------
1 | (defproject clojurescript-ethereum-example "0.1.0-SNAPSHOT"
2 | :dependencies [[bk/ring-gzip "0.1.1"]
3 | [cljs-ajax "0.5.8"]
4 | [cljs-react-material-ui "0.2.22"]
5 | [cljs-web3 "0.16.0-0"]
6 | [cljsjs/bignumber "2.1.4-1"]
7 | [cljsjs/react-flexbox-grid "0.10.2-1" :exclusions [cljsjs/react]]
8 | [com.andrewmcveigh/cljs-time "0.4.0"]
9 | [compojure "1.6.0-beta1"]
10 | [day8.re-frame/http-fx "0.0.4"]
11 | [environ "1.0.3"]
12 | [http-kit "2.2.0"]
13 | [madvas.re-frame/web3-fx "0.1.0"]
14 | [medley "0.8.3"]
15 | [org.clojure/clojure "1.8.0"]
16 | [org.clojure/clojurescript "1.9.229"]
17 | [print-foo-cljs "2.0.3"]
18 | [re-frame "0.8.0"]
19 | [reagent "0.6.0" :exclusions [cljsjs/react]]
20 | [ring.middleware.logger "0.5.0"]
21 | [ring/ring-core "1.6.0-beta5"]
22 | [ring/ring-defaults "0.3.0-beta1"]
23 | [ring/ring-devel "1.6.0-beta5"]]
24 |
25 | :plugins [[lein-auto "0.1.2"]
26 | [lein-cljsbuild "1.1.4"]
27 | [lein-shell "0.5.0"]
28 | [deraen/lein-less4j "0.5.0"]]
29 |
30 | :min-lein-version "2.5.3"
31 | :main clojurescript-ethereum-example.core
32 |
33 | :source-paths ["src/clj"]
34 |
35 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target"]
36 |
37 | :figwheel {:css-dirs ["resources/public/css"]
38 | :server-port 6777
39 | :ring-handler user/http-handler}
40 |
41 | :auto {"compile-solidity" {:file-pattern #"\.(sol)$"
42 | :paths ["resources/public/contracts/src"]}}
43 |
44 | :aliases {"compile-solidity" ["shell" "./compile-solidity.sh"]}
45 |
46 | :repl-options {:nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]}
47 |
48 | :less {:source-paths ["resources/public/less"]
49 | :target-path "resources/public/css"
50 | :target-dir "resources/public/css"
51 | :source-map true
52 | :compression true}
53 |
54 | :uberjar-name "clojurescript-ethereum-example.jar"
55 |
56 | :profiles
57 | {:dev
58 | {:dependencies [[binaryage/devtools "0.8.2"]
59 | [com.cemerick/piggieback "0.2.1"]
60 | [figwheel-sidecar "0.5.8"]
61 | [org.clojure/tools.nrepl "0.2.11"]]
62 | :plugins [[lein-figwheel "0.5.8"]]
63 | :source-paths ["env/dev"]
64 | :cljsbuild {:builds [{:id "dev"
65 | :source-paths ["src/cljs"]
66 | :figwheel {:on-jsload "clojurescript-ethereum-example.core/mount-root"}
67 | :compiler {:main clojurescript-ethereum-example.core
68 | :output-to "resources/public/js/compiled/app.js"
69 | :output-dir "resources/public/js/compiled/out"
70 | :asset-path "./js/compiled/out"
71 | :source-map-timestamp true
72 | :optimizations :none
73 | :closure-defines {goog.DEBUG true}
74 | :preloads [print.foo.preloads.devtools]}}]}}
75 |
76 | :uberjar {:hooks [leiningen.cljsbuild]
77 | :omit-source true
78 | :aot :all
79 | :main emojillionaire.core
80 | :cljsbuild {:builds {:app {:id "uberjar"
81 | :source-paths ["src/cljs"]
82 | :compiler {:main clojurescript-ethereum-example.core
83 | :output-to "resources/public/js/compiled/app.js"
84 | :optimizations :advanced
85 | :closure-defines {goog.DEBUG false}
86 | :pretty-print true
87 | :pseudo-names true}}}}}})
88 |
--------------------------------------------------------------------------------
/resources/public/contracts/build/SimpleTwitter.abi:
--------------------------------------------------------------------------------
1 | [{"constant":true,"inputs":[],"name":"maxTweetLength","outputs":[{"name":"","type":"uint16"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_maxNameLength","type":"uint16"},{"name":"_maxTweetLength","type":"uint16"}],"name":"setSettings","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"maxNameLength","outputs":[{"name":"","type":"uint16"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"name","type":"string"},{"name":"text","type":"string"}],"name":"addTweet","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"getSettings","outputs":[{"name":"","type":"uint16"},{"name":"","type":"uint16"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"developer","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"tweets","outputs":[{"name":"authorAddress","type":"address"},{"name":"name","type":"string"},{"name":"text","type":"string"},{"name":"date","type":"uint256"}],"payable":false,"type":"function"},{"inputs":[],"payable":false,"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"authorAddress","type":"address"},{"indexed":false,"name":"name","type":"string"},{"indexed":false,"name":"text","type":"string"},{"indexed":false,"name":"date","type":"uint256"},{"indexed":false,"name":"tweetKey","type":"uint256"}],"name":"onTweetAdded","type":"event"}]
--------------------------------------------------------------------------------
/resources/public/contracts/build/SimpleTwitter.bin:
--------------------------------------------------------------------------------
1 | 6060604052341561000c57fe5b5b6000805460a060020a61ffff021916748c00000000000000000000000000000000000000001760b060020a61ffff02191676140000000000000000000000000000000000000000000017600160a060020a03191633600160a060020a03161790555b5b6109018061007f6000396000f300606060405236156100805763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166313ce93588114610082578063398b91af146100a95780633b992f2b146100c85780637e75b408146100ef57806385b4bb5314610184578063ca4b208b146101b5578063e8d857b0146101e1575bfe5b341561008a57fe5b61009261030f565b6040805161ffff9092168252519081900360200190f35b34156100b157fe5b6100c661ffff60043581169060243516610320565b005b34156100d057fe5b610092610398565b6040805161ffff9092168252519081900360200190f35b34156100f757fe5b6100c6600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284375050604080516020601f89358b018035918201839004830284018301909452808352979998810197919650918201945092508291508401838280828437509496506103a995505050505050565b005b341561018c57fe5b6101946105f8565b6040805161ffff938416815291909216602082015281519081900390910190f35b34156101bd57fe5b6101c5610615565b60408051600160a060020a039092168252519081900360200190f35b34156101e957fe5b6101f4600435610624565b60408051600160a060020a038616815260608101839052608060208201818152865460026000196101006001841615020190911604918301829052919283019060a0840190879080156102885780601f1061025d57610100808354040283529160200191610288565b820191906000526020600020905b81548152906001019060200180831161026b57829003601f168201915b50508381038252855460026000196101006001841615020190911604808252602090910190869080156102fc5780601f106102d1576101008083540402835291602001916102fc565b820191906000526020600020905b8154815290600101906020018083116102df57829003601f168201915b5050965050505050505060405180910390f35b60005460a060020a900461ffff1681565b60005433600160a060020a0390811691161461033c5760006000fd5b6000805461ffff83811660a060020a0275ffff00000000000000000000000000000000000000001991861660b060020a0277ffff000000000000000000000000000000000000000000001990931692909217161790555b5b5050565b60005460b060020a900461ffff1681565b60005460b060020a900461ffff166103c86103c384610664565b61068d565b11156103d45760006000fd5b60005460a060020a900461ffff166103f36103c383610664565b61068d565b11156103ff5760006000fd5b600180548082016104108382610747565b916000526020600020906004020160005b5060408051608081018252600160a060020a0333168082526020808301889052928201869052426060830152835473ffffffffffffffffffffffffffffffffffffffff1916178355855190929161047f916001840191880190610779565b506040820151805161049b916002840191602090910190610779565b50606082015181600301555050507f9a09b2b614ff0b166c21753b02abd530af36fc391ffaa6c43f7f7a3884f7b147338383426001600180549050036040518086600160a060020a0316600160a060020a03168152602001806020018060200185815260200184815260200183810383528781815181526020019150805190602001908083836000831461054a575b80518252602083111561054a57601f19909201916020918201910161052a565b505050905090810190601f1680156105765780820380516001836020036101000a031916815260200191505b50838103825286518152865160209182019188019080838382156105b5575b8051825260208311156105b557601f199092019160209182019101610595565b505050905090810190601f1680156105e15780820380516001836020036101000a031916815260200191505b5097505050505050505060405180910390a15b5050565b60005461ffff60b060020a820481169160a060020a9004165b9091565b600054600160a060020a031681565b600180548290811061063257fe5b906000526020600020906004020160005b5080546003820154600160a060020a03909116925060018201916002019084565b61066c6107f8565b50604080518082019091528151815260208281019082018190525b50919050565b60208101518151600091601e19808201929091010182805b8284101561073a5750825160ff1660808110156106c75760018401935061072a565b60e08160ff1610156106de5760028401935061072a565b60f08160ff1610156106f55760038401935061072a565b60f88160ff16101561070c5760048401935061072a565b60fc8160ff1610156107235760058401935061072a565b6006840193505b5b5b5b5b5b6001909101906106a5565b8194505b50505050919050565b81548183558181151161077357600402816004028360005260206000209182019101610773919061080f565b5b505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106107ba57805160ff19168380011785556107e7565b828001600101855582156107e7579182015b828111156107e75782518255916020019190600101906107cc565b5b506107f492915061086c565b5090565b604080518082019091526000808252602082015290565b61086991905b808211156107f457805473ffffffffffffffffffffffffffffffffffffffff191681556000610847600183018261088d565b61085560028301600061088d565b5060006003820155600401610815565b5090565b90565b61086991905b808211156107f45760008155600101610872565b5090565b90565b50805460018160011615610100020316600290046000825580601f106108b357506108d1565b601f0160209004906000526020600020908101906108d1919061086c565b5b505600a165627a7a723058203360392f78910e972f522f7f2c06fcc2f4095a4607e84e4d0388654696cf80280029
--------------------------------------------------------------------------------
/resources/public/contracts/build/SimpleTwitter.json:
--------------------------------------------------------------------------------
1 | {"contracts":{"SimpleTwitter.sol:SimpleTwitter":{"abi":"[{\"constant\":true,\"inputs\":[],\"name\":\"maxTweetLength\",\"outputs\":[{\"name\":\"\",\"type\":\"uint16\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_maxNameLength\",\"type\":\"uint16\"},{\"name\":\"_maxTweetLength\",\"type\":\"uint16\"}],\"name\":\"setSettings\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"maxNameLength\",\"outputs\":[{\"name\":\"\",\"type\":\"uint16\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"text\",\"type\":\"string\"}],\"name\":\"addTweet\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getSettings\",\"outputs\":[{\"name\":\"\",\"type\":\"uint16\"},{\"name\":\"\",\"type\":\"uint16\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"developer\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"tweets\",\"outputs\":[{\"name\":\"authorAddress\",\"type\":\"address\"},{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"text\",\"type\":\"string\"},{\"name\":\"date\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"inputs\":[],\"payable\":false,\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"authorAddress\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"name\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"text\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"date\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"tweetKey\",\"type\":\"uint256\"}],\"name\":\"onTweetAdded\",\"type\":\"event\"}]","bin":"6060604052341561000c57fe5b5b6000805460a060020a61ffff021916748c00000000000000000000000000000000000000001760b060020a61ffff02191676140000000000000000000000000000000000000000000017600160a060020a03191633600160a060020a03161790555b5b6109018061007f6000396000f300606060405236156100805763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166313ce93588114610082578063398b91af146100a95780633b992f2b146100c85780637e75b408146100ef57806385b4bb5314610184578063ca4b208b146101b5578063e8d857b0146101e1575bfe5b341561008a57fe5b61009261030f565b6040805161ffff9092168252519081900360200190f35b34156100b157fe5b6100c661ffff60043581169060243516610320565b005b34156100d057fe5b610092610398565b6040805161ffff9092168252519081900360200190f35b34156100f757fe5b6100c6600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284375050604080516020601f89358b018035918201839004830284018301909452808352979998810197919650918201945092508291508401838280828437509496506103a995505050505050565b005b341561018c57fe5b6101946105f8565b6040805161ffff938416815291909216602082015281519081900390910190f35b34156101bd57fe5b6101c5610615565b60408051600160a060020a039092168252519081900360200190f35b34156101e957fe5b6101f4600435610624565b60408051600160a060020a038616815260608101839052608060208201818152865460026000196101006001841615020190911604918301829052919283019060a0840190879080156102885780601f1061025d57610100808354040283529160200191610288565b820191906000526020600020905b81548152906001019060200180831161026b57829003601f168201915b50508381038252855460026000196101006001841615020190911604808252602090910190869080156102fc5780601f106102d1576101008083540402835291602001916102fc565b820191906000526020600020905b8154815290600101906020018083116102df57829003601f168201915b5050965050505050505060405180910390f35b60005460a060020a900461ffff1681565b60005433600160a060020a0390811691161461033c5760006000fd5b6000805461ffff83811660a060020a0275ffff00000000000000000000000000000000000000001991861660b060020a0277ffff000000000000000000000000000000000000000000001990931692909217161790555b5b5050565b60005460b060020a900461ffff1681565b60005460b060020a900461ffff166103c86103c384610664565b61068d565b11156103d45760006000fd5b60005460a060020a900461ffff166103f36103c383610664565b61068d565b11156103ff5760006000fd5b600180548082016104108382610747565b916000526020600020906004020160005b5060408051608081018252600160a060020a0333168082526020808301889052928201869052426060830152835473ffffffffffffffffffffffffffffffffffffffff1916178355855190929161047f916001840191880190610779565b506040820151805161049b916002840191602090910190610779565b50606082015181600301555050507f9a09b2b614ff0b166c21753b02abd530af36fc391ffaa6c43f7f7a3884f7b147338383426001600180549050036040518086600160a060020a0316600160a060020a03168152602001806020018060200185815260200184815260200183810383528781815181526020019150805190602001908083836000831461054a575b80518252602083111561054a57601f19909201916020918201910161052a565b505050905090810190601f1680156105765780820380516001836020036101000a031916815260200191505b50838103825286518152865160209182019188019080838382156105b5575b8051825260208311156105b557601f199092019160209182019101610595565b505050905090810190601f1680156105e15780820380516001836020036101000a031916815260200191505b5097505050505050505060405180910390a15b5050565b60005461ffff60b060020a820481169160a060020a9004165b9091565b600054600160a060020a031681565b600180548290811061063257fe5b906000526020600020906004020160005b5080546003820154600160a060020a03909116925060018201916002019084565b61066c6107f8565b50604080518082019091528151815260208281019082018190525b50919050565b60208101518151600091601e19808201929091010182805b8284101561073a5750825160ff1660808110156106c75760018401935061072a565b60e08160ff1610156106de5760028401935061072a565b60f08160ff1610156106f55760038401935061072a565b60f88160ff16101561070c5760048401935061072a565b60fc8160ff1610156107235760058401935061072a565b6006840193505b5b5b5b5b5b6001909101906106a5565b8194505b50505050919050565b81548183558181151161077357600402816004028360005260206000209182019101610773919061080f565b5b505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106107ba57805160ff19168380011785556107e7565b828001600101855582156107e7579182015b828111156107e75782518255916020019190600101906107cc565b5b506107f492915061086c565b5090565b604080518082019091526000808252602082015290565b61086991905b808211156107f457805473ffffffffffffffffffffffffffffffffffffffff191681556000610847600183018261088d565b61085560028301600061088d565b5060006003820155600401610815565b5090565b90565b61086991905b808211156107f45760008155600101610872565b5090565b90565b50805460018160011615610100020316600290046000825580601f106108b357506108d1565b601f0160209004906000526020600020908101906108d1919061086c565b5b505600a165627a7a723058203360392f78910e972f522f7f2c06fcc2f4095a4607e84e4d0388654696cf80280029"},"strings.sol:strings":{"abi":"[]","bin":"60606040523415600b57fe5b5b60338060196000396000f30060606040525bfe00a165627a7a72305820062c7f243b9e78c027276fbfed3e8d740ecfff565b17101d5dcf06a4caf4f6380029"}},"version":"0.4.11+commit.68ef5810.Linux.g++"}
2 |
--------------------------------------------------------------------------------
/resources/public/contracts/build/strings.abi:
--------------------------------------------------------------------------------
1 | []
--------------------------------------------------------------------------------
/resources/public/contracts/build/strings.bin:
--------------------------------------------------------------------------------
1 | 60606040523415600b57fe5b5b60338060196000396000f30060606040525bfe00a165627a7a72305820062c7f243b9e78c027276fbfed3e8d740ecfff565b17101d5dcf06a4caf4f6380029
--------------------------------------------------------------------------------
/resources/public/contracts/src/SimpleTwitter.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.1;
2 |
3 | import "strings.sol";
4 |
5 | contract SimpleTwitter {
6 | using strings for *;
7 |
8 | address public developer;
9 | uint16 public maxTweetLength;
10 | uint16 public maxNameLength;
11 |
12 | struct Tweet {
13 | address authorAddress;
14 | string name;
15 | string text;
16 | uint date;
17 | }
18 |
19 | Tweet[] public tweets;
20 |
21 | event onTweetAdded(address authorAddress, string name, string text, uint date, uint tweetKey);
22 |
23 | modifier onlyDeveloper() {
24 | if (msg.sender != developer) throw;
25 | _;
26 | }
27 |
28 | function SimpleTwitter() {
29 | maxTweetLength = 140;
30 | maxNameLength = 20;
31 | developer = msg.sender;
32 | }
33 |
34 | function addTweet(string name, string text) {
35 | if (name.toSlice().len() > maxNameLength) throw;
36 | if (text.toSlice().len() > maxTweetLength) throw;
37 |
38 | tweets.push(Tweet(msg.sender, name, text, now));
39 | onTweetAdded(msg.sender, name, text, now, tweets.length - 1);
40 | }
41 |
42 | function getSettings() constant returns(uint16, uint16) {
43 | return (maxNameLength, maxTweetLength);
44 | }
45 |
46 | function setSettings(uint16 _maxNameLength, uint16 _maxTweetLength) onlyDeveloper {
47 | maxNameLength = _maxNameLength;
48 | maxTweetLength = _maxTweetLength;
49 | }
50 | }
--------------------------------------------------------------------------------
/resources/public/contracts/src/strings.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.1;
2 |
3 | /*
4 | * @title String & slice utility library for Solidity contracts.
5 | * @author Nick Johnson
6 | *
7 | * @dev Functionality in this library is largely implemented using an
8 | * abstraction called a 'slice'. A slice represents a part of a string -
9 | * anything from the entire string to a single character, or even no
10 | * characters at all (a 0-length slice). Since a slice only has to specify
11 | * an offset and a length, copying and manipulating slices is a lot less
12 | * expensive than copying and manipulating the strings they reference.
13 | *
14 | * To further reduce gas costs, most functions on slice that need to return
15 | * a slice modify the original one instead of allocating a new one; for
16 | * instance, `s.split(".")` will return the text up to the first '.',
17 | * modifying s to only contain the remainder of the string after the '.'.
18 | * In situations where you do not want to modify the original slice, you
19 | * can make a copy first with `.copy()`, for example:
20 | * `s.copy().split(".")`. Try and avoid using this idiom in loops; since
21 | * Solidity has no memory management, it will result in allocating many
22 | * short-lived slices that are later discarded.
23 | *
24 | * Functions that return two slices come in two versions: a non-allocating
25 | * version that takes the second slice as an argument, modifying it in
26 | * place, and an allocating version that allocates and returns the second
27 | * slice; see `nextRune` for example.
28 | *
29 | * Functions that have to copy string data will return strings rather than
30 | * slices; these can be cast back to slices for further processing if
31 | * required.
32 | *
33 | * For convenience, some functions are provided with non-modifying
34 | * variants that create a new slice and return both; for instance,
35 | * `s.splitNew('.')` leaves s unmodified, and returns two values
36 | * corresponding to the left and right parts of the string.
37 | */
38 |
39 |
40 | library strings {
41 | struct slice {
42 | uint _len;
43 | uint _ptr;
44 | }
45 |
46 | function memcpy(uint dest, uint src, uint len) private {
47 | // Copy word-length chunks while possible
48 | for(; len >= 32; len -= 32) {
49 | assembly {
50 | mstore(dest, mload(src))
51 | }
52 | dest += 32;
53 | src += 32;
54 | }
55 |
56 | // Copy remaining bytes
57 | uint mask = 256 ** (32 - len) - 1;
58 | assembly {
59 | let srcpart := and(mload(src), not(mask))
60 | let destpart := and(mload(dest), mask)
61 | mstore(dest, or(destpart, srcpart))
62 | }
63 | }
64 |
65 | /*
66 | * @dev Returns a slice containing the entire string.
67 | * @param self The string to make a slice from.
68 | * @return A newly allocated slice containing the entire string.
69 | */
70 | function toSlice(string self) internal returns (slice) {
71 | uint ptr;
72 | assembly {
73 | ptr := add(self, 0x20)
74 | }
75 | return slice(bytes(self).length, ptr);
76 | }
77 |
78 | /*
79 | * @dev Returns the length of a null-terminated bytes32 string.
80 | * @param self The value to find the length of.
81 | * @return The length of the string, from 0 to 32.
82 | */
83 | function len(bytes32 self) internal returns (uint) {
84 | uint ret;
85 | if (self == 0)
86 | return 0;
87 | if (self & 0xffffffffffffffffffffffffffffffff == 0) {
88 | ret += 16;
89 | self = bytes32(uint(self) / 0x100000000000000000000000000000000);
90 | }
91 | if (self & 0xffffffffffffffff == 0) {
92 | ret += 8;
93 | self = bytes32(uint(self) / 0x10000000000000000);
94 | }
95 | if (self & 0xffffffff == 0) {
96 | ret += 4;
97 | self = bytes32(uint(self) / 0x100000000);
98 | }
99 | if (self & 0xffff == 0) {
100 | ret += 2;
101 | self = bytes32(uint(self) / 0x10000);
102 | }
103 | if (self & 0xff == 0) {
104 | ret += 1;
105 | }
106 | return 32 - ret;
107 | }
108 |
109 | /*
110 | * @dev Returns a slice containing the entire bytes32, interpreted as a
111 | * null-termintaed utf-8 string.
112 | * @param self The bytes32 value to convert to a slice.
113 | * @return A new slice containing the value of the input argument up to the
114 | * first null.
115 | */
116 | function toSliceB32(bytes32 self) internal returns (slice ret) {
117 | // Allocate space for `self` in memory, copy it there, and point ret at it
118 | assembly {
119 | let ptr := mload(0x40)
120 | mstore(0x40, add(ptr, 0x20))
121 | mstore(ptr, self)
122 | mstore(add(ret, 0x20), ptr)
123 | }
124 | ret._len = len(self);
125 | }
126 |
127 | /*
128 | * @dev Returns a new slice containing the same data as the current slice.
129 | * @param self The slice to copy.
130 | * @return A new slice containing the same data as `self`.
131 | */
132 | function copy(slice self) internal returns (slice) {
133 | return slice(self._len, self._ptr);
134 | }
135 |
136 | /*
137 | * @dev Copies a slice to a new string.
138 | * @param self The slice to copy.
139 | * @return A newly allocated string containing the slice's text.
140 | */
141 | function toString(slice self) internal returns (string) {
142 | var ret = new string(self._len);
143 | uint retptr;
144 | assembly { retptr := add(ret, 32) }
145 |
146 | memcpy(retptr, self._ptr, self._len);
147 | return ret;
148 | }
149 |
150 | /*
151 | * @dev Returns the length in runes of the slice. Note that this operation
152 | * takes time proportional to the length of the slice; avoid using it
153 | * in loops, and call `slice.empty()` if you only need to know whether
154 | * the slice is empty or not.
155 | * @param self The slice to operate on.
156 | * @return The length of the slice in runes.
157 | */
158 | function len(slice self) internal returns (uint) {
159 | // Starting at ptr-31 means the LSB will be the byte we care about
160 | var ptr = self._ptr - 31;
161 | var end = ptr + self._len;
162 | for (uint len = 0; ptr < end; len++) {
163 | uint8 b;
164 | assembly { b := and(mload(ptr), 0xFF) }
165 | if (b < 0x80) {
166 | ptr += 1;
167 | } else if(b < 0xE0) {
168 | ptr += 2;
169 | } else if(b < 0xF0) {
170 | ptr += 3;
171 | } else if(b < 0xF8) {
172 | ptr += 4;
173 | } else if(b < 0xFC) {
174 | ptr += 5;
175 | } else {
176 | ptr += 6;
177 | }
178 | }
179 | return len;
180 | }
181 |
182 | /*
183 | * @dev Returns true if the slice is empty (has a length of 0).
184 | * @param self The slice to operate on.
185 | * @return True if the slice is empty, False otherwise.
186 | */
187 | function empty(slice self) internal returns (bool) {
188 | return self._len == 0;
189 | }
190 |
191 | /*
192 | * @dev Returns a positive number if `other` comes lexicographically after
193 | * `self`, a negative number if it comes before, or zero if the
194 | * contents of the two slices are equal. Comparison is done per-rune,
195 | * on unicode codepoints.
196 | * @param self The first slice to compare.
197 | * @param other The second slice to compare.
198 | * @return The result of the comparison.
199 | */
200 | function compare(slice self, slice other) internal returns (int) {
201 | uint shortest = self._len;
202 | if (other._len < self._len)
203 | shortest = other._len;
204 |
205 | var selfptr = self._ptr;
206 | var otherptr = other._ptr;
207 | for (uint idx = 0; idx < shortest; idx += 32) {
208 | uint a;
209 | uint b;
210 | assembly {
211 | a := mload(selfptr)
212 | b := mload(otherptr)
213 | }
214 | if (a != b) {
215 | // Mask out irrelevant bytes and check again
216 | uint mask = ~(2 ** (8 * (32 - shortest + idx)) - 1);
217 | var diff = (a & mask) - (b & mask);
218 | if (diff != 0)
219 | return int(diff);
220 | }
221 | selfptr += 32;
222 | otherptr += 32;
223 | }
224 | return int(self._len) - int(other._len);
225 | }
226 |
227 | /*
228 | * @dev Returns true if the two slices contain the same text.
229 | * @param self The first slice to compare.
230 | * @param self The second slice to compare.
231 | * @return True if the slices are equal, false otherwise.
232 | */
233 | function equals(slice self, slice other) internal returns (bool) {
234 | return compare(self, other) == 0;
235 | }
236 |
237 | /*
238 | * @dev Extracts the first rune in the slice into `rune`, advancing the
239 | * slice to point to the next rune and returning `self`.
240 | * @param self The slice to operate on.
241 | * @param rune The slice that will contain the first rune.
242 | * @return `rune`.
243 | */
244 | function nextRune(slice self, slice rune) internal returns (slice) {
245 | rune._ptr = self._ptr;
246 |
247 | if (self._len == 0) {
248 | rune._len = 0;
249 | return rune;
250 | }
251 |
252 | uint len;
253 | uint b;
254 | // Load the first byte of the rune into the LSBs of b
255 | assembly { b := and(mload(sub(mload(add(self, 32)), 31)), 0xFF) }
256 | if (b < 0x80) {
257 | len = 1;
258 | } else if(b < 0xE0) {
259 | len = 2;
260 | } else if(b < 0xF0) {
261 | len = 3;
262 | } else {
263 | len = 4;
264 | }
265 |
266 | // Check for truncated codepoints
267 | if (len > self._len) {
268 | rune._len = self._len;
269 | self._ptr += self._len;
270 | self._len = 0;
271 | return rune;
272 | }
273 |
274 | self._ptr += len;
275 | self._len -= len;
276 | rune._len = len;
277 | return rune;
278 | }
279 |
280 | /*
281 | * @dev Returns the first rune in the slice, advancing the slice to point
282 | * to the next rune.
283 | * @param self The slice to operate on.
284 | * @return A slice containing only the first rune from `self`.
285 | */
286 | function nextRune(slice self) internal returns (slice ret) {
287 | nextRune(self, ret);
288 | }
289 |
290 | /*
291 | * @dev Returns the number of the first codepoint in the slice.
292 | * @param self The slice to operate on.
293 | * @return The number of the first codepoint in the slice.
294 | */
295 | function ord(slice self) internal returns (uint ret) {
296 | if (self._len == 0) {
297 | return 0;
298 | }
299 |
300 | uint word;
301 | uint len;
302 | uint div = 2 ** 248;
303 |
304 | // Load the rune into the MSBs of b
305 | assembly { word:= mload(mload(add(self, 32))) }
306 | var b = word / div;
307 | if (b < 0x80) {
308 | ret = b;
309 | len = 1;
310 | } else if(b < 0xE0) {
311 | ret = b & 0x1F;
312 | len = 2;
313 | } else if(b < 0xF0) {
314 | ret = b & 0x0F;
315 | len = 3;
316 | } else {
317 | ret = b & 0x07;
318 | len = 4;
319 | }
320 |
321 | // Check for truncated codepoints
322 | if (len > self._len) {
323 | return 0;
324 | }
325 |
326 | for (uint i = 1; i < len; i++) {
327 | div = div / 256;
328 | b = (word / div) & 0xFF;
329 | if (b & 0xC0 != 0x80) {
330 | // Invalid UTF-8 sequence
331 | return 0;
332 | }
333 | ret = (ret * 64) | (b & 0x3F);
334 | }
335 |
336 | return ret;
337 | }
338 |
339 | /*
340 | * @dev Returns the keccak-256 hash of the slice.
341 | * @param self The slice to hash.
342 | * @return The hash of the slice.
343 | */
344 | function keccak(slice self) internal returns (bytes32 ret) {
345 | assembly {
346 | ret := sha3(mload(add(self, 32)), mload(self))
347 | }
348 | }
349 |
350 | /*
351 | * @dev Returns true if `self` starts with `needle`.
352 | * @param self The slice to operate on.
353 | * @param needle The slice to search for.
354 | * @return True if the slice starts with the provided text, false otherwise.
355 | */
356 | function startsWith(slice self, slice needle) internal returns (bool) {
357 | if (self._len < needle._len) {
358 | return false;
359 | }
360 |
361 | if (self._ptr == needle._ptr) {
362 | return true;
363 | }
364 |
365 | bool equal;
366 | assembly {
367 | let len := mload(needle)
368 | let selfptr := mload(add(self, 0x20))
369 | let needleptr := mload(add(needle, 0x20))
370 | equal := eq(sha3(selfptr, len), sha3(needleptr, len))
371 | }
372 | return equal;
373 | }
374 |
375 | /*
376 | * @dev If `self` starts with `needle`, `needle` is removed from the
377 | * beginning of `self`. Otherwise, `self` is unmodified.
378 | * @param self The slice to operate on.
379 | * @param needle The slice to search for.
380 | * @return `self`
381 | */
382 | function beyond(slice self, slice needle) internal returns (slice) {
383 | if (self._len < needle._len) {
384 | return self;
385 | }
386 |
387 | bool equal = true;
388 | if (self._ptr != needle._ptr) {
389 | assembly {
390 | let len := mload(needle)
391 | let selfptr := mload(add(self, 0x20))
392 | let needleptr := mload(add(needle, 0x20))
393 | equal := eq(sha3(selfptr, len), sha3(needleptr, len))
394 | }
395 | }
396 |
397 | if (equal) {
398 | self._len -= needle._len;
399 | self._ptr += needle._len;
400 | }
401 |
402 | return self;
403 | }
404 |
405 | /*
406 | * @dev Returns true if the slice ends with `needle`.
407 | * @param self The slice to operate on.
408 | * @param needle The slice to search for.
409 | * @return True if the slice starts with the provided text, false otherwise.
410 | */
411 | function endsWith(slice self, slice needle) internal returns (bool) {
412 | if (self._len < needle._len) {
413 | return false;
414 | }
415 |
416 | var selfptr = self._ptr + self._len - needle._len;
417 |
418 | if (selfptr == needle._ptr) {
419 | return true;
420 | }
421 |
422 | bool equal;
423 | assembly {
424 | let len := mload(needle)
425 | let needleptr := mload(add(needle, 0x20))
426 | equal := eq(sha3(selfptr, len), sha3(needleptr, len))
427 | }
428 |
429 | return equal;
430 | }
431 |
432 | /*
433 | * @dev If `self` ends with `needle`, `needle` is removed from the
434 | * end of `self`. Otherwise, `self` is unmodified.
435 | * @param self The slice to operate on.
436 | * @param needle The slice to search for.
437 | * @return `self`
438 | */
439 | function until(slice self, slice needle) internal returns (slice) {
440 | if (self._len < needle._len) {
441 | return self;
442 | }
443 |
444 | var selfptr = self._ptr + self._len - needle._len;
445 | bool equal = true;
446 | if (selfptr != needle._ptr) {
447 | assembly {
448 | let len := mload(needle)
449 | let needleptr := mload(add(needle, 0x20))
450 | equal := eq(sha3(selfptr, len), sha3(needleptr, len))
451 | }
452 | }
453 |
454 | if (equal) {
455 | self._len -= needle._len;
456 | }
457 |
458 | return self;
459 | }
460 |
461 | // Returns the memory address of the first byte of the first occurrence of
462 | // `needle` in `self`, or the first byte after `self` if not found.
463 | function findPtr(uint selflen, uint selfptr, uint needlelen, uint needleptr) private returns (uint) {
464 | uint ptr;
465 | uint idx;
466 |
467 | if (needlelen <= selflen) {
468 | if (needlelen <= 32) {
469 | // Optimized assembly for 68 gas per byte on short strings
470 | assembly {
471 | let mask := not(sub(exp(2, mul(8, sub(32, needlelen))), 1))
472 | let needledata := and(mload(needleptr), mask)
473 | let end := add(selfptr, sub(selflen, needlelen))
474 | ptr := selfptr
475 | loop:
476 | jumpi(exit, eq(and(mload(ptr), mask), needledata))
477 | ptr := add(ptr, 1)
478 | jumpi(loop, lt(sub(ptr, 1), end))
479 | ptr := add(selfptr, selflen)
480 | exit:
481 | }
482 | return ptr;
483 | } else {
484 | // For long needles, use hashing
485 | bytes32 hash;
486 | assembly { hash := sha3(needleptr, needlelen) }
487 | ptr = selfptr;
488 | for (idx = 0; idx <= selflen - needlelen; idx++) {
489 | bytes32 testHash;
490 | assembly { testHash := sha3(ptr, needlelen) }
491 | if (hash == testHash)
492 | return ptr;
493 | ptr += 1;
494 | }
495 | }
496 | }
497 | return selfptr + selflen;
498 | }
499 |
500 | // Returns the memory address of the first byte after the last occurrence of
501 | // `needle` in `self`, or the address of `self` if not found.
502 | function rfindPtr(uint selflen, uint selfptr, uint needlelen, uint needleptr) private returns (uint) {
503 | uint ptr;
504 |
505 | if (needlelen <= selflen) {
506 | if (needlelen <= 32) {
507 | // Optimized assembly for 69 gas per byte on short strings
508 | assembly {
509 | let mask := not(sub(exp(2, mul(8, sub(32, needlelen))), 1))
510 | let needledata := and(mload(needleptr), mask)
511 | ptr := add(selfptr, sub(selflen, needlelen))
512 | loop:
513 | jumpi(ret, eq(and(mload(ptr), mask), needledata))
514 | ptr := sub(ptr, 1)
515 | jumpi(loop, gt(add(ptr, 1), selfptr))
516 | ptr := selfptr
517 | jump(exit)
518 | ret:
519 | ptr := add(ptr, needlelen)
520 | exit:
521 | }
522 | return ptr;
523 | } else {
524 | // For long needles, use hashing
525 | bytes32 hash;
526 | assembly { hash := sha3(needleptr, needlelen) }
527 | ptr = selfptr + (selflen - needlelen);
528 | while (ptr >= selfptr) {
529 | bytes32 testHash;
530 | assembly { testHash := sha3(ptr, needlelen) }
531 | if (hash == testHash)
532 | return ptr + needlelen;
533 | ptr -= 1;
534 | }
535 | }
536 | }
537 | return selfptr;
538 | }
539 |
540 | /*
541 | * @dev Modifies `self` to contain everything from the first occurrence of
542 | * `needle` to the end of the slice. `self` is set to the empty slice
543 | * if `needle` is not found.
544 | * @param self The slice to search and modify.
545 | * @param needle The text to search for.
546 | * @return `self`.
547 | */
548 | function find(slice self, slice needle) internal returns (slice) {
549 | uint ptr = findPtr(self._len, self._ptr, needle._len, needle._ptr);
550 | self._len -= ptr - self._ptr;
551 | self._ptr = ptr;
552 | return self;
553 | }
554 |
555 | /*
556 | * @dev Modifies `self` to contain the part of the string from the start of
557 | * `self` to the end of the first occurrence of `needle`. If `needle`
558 | * is not found, `self` is set to the empty slice.
559 | * @param self The slice to search and modify.
560 | * @param needle The text to search for.
561 | * @return `self`.
562 | */
563 | function rfind(slice self, slice needle) internal returns (slice) {
564 | uint ptr = rfindPtr(self._len, self._ptr, needle._len, needle._ptr);
565 | self._len = ptr - self._ptr;
566 | return self;
567 | }
568 |
569 | /*
570 | * @dev Splits the slice, setting `self` to everything after the first
571 | * occurrence of `needle`, and `token` to everything before it. If
572 | * `needle` does not occur in `self`, `self` is set to the empty slice,
573 | * and `token` is set to the entirety of `self`.
574 | * @param self The slice to split.
575 | * @param needle The text to search for in `self`.
576 | * @param token An output parameter to which the first token is written.
577 | * @return `token`.
578 | */
579 | function split(slice self, slice needle, slice token) internal returns (slice) {
580 | uint ptr = findPtr(self._len, self._ptr, needle._len, needle._ptr);
581 | token._ptr = self._ptr;
582 | token._len = ptr - self._ptr;
583 | if (ptr == self._ptr + self._len) {
584 | // Not found
585 | self._len = 0;
586 | } else {
587 | self._len -= token._len + needle._len;
588 | self._ptr = ptr + needle._len;
589 | }
590 | return token;
591 | }
592 |
593 | /*
594 | * @dev Splits the slice, setting `self` to everything after the first
595 | * occurrence of `needle`, and returning everything before it. If
596 | * `needle` does not occur in `self`, `self` is set to the empty slice,
597 | * and the entirety of `self` is returned.
598 | * @param self The slice to split.
599 | * @param needle The text to search for in `self`.
600 | * @return The part of `self` up to the first occurrence of `delim`.
601 | */
602 | function split(slice self, slice needle) internal returns (slice token) {
603 | split(self, needle, token);
604 | }
605 |
606 | /*
607 | * @dev Splits the slice, setting `self` to everything before the last
608 | * occurrence of `needle`, and `token` to everything after it. If
609 | * `needle` does not occur in `self`, `self` is set to the empty slice,
610 | * and `token` is set to the entirety of `self`.
611 | * @param self The slice to split.
612 | * @param needle The text to search for in `self`.
613 | * @param token An output parameter to which the first token is written.
614 | * @return `token`.
615 | */
616 | function rsplit(slice self, slice needle, slice token) internal returns (slice) {
617 | uint ptr = rfindPtr(self._len, self._ptr, needle._len, needle._ptr);
618 | token._ptr = ptr;
619 | token._len = self._len - (ptr - self._ptr);
620 | if (ptr == self._ptr) {
621 | // Not found
622 | self._len = 0;
623 | } else {
624 | self._len -= token._len + needle._len;
625 | }
626 | return token;
627 | }
628 |
629 | /*
630 | * @dev Splits the slice, setting `self` to everything before the last
631 | * occurrence of `needle`, and returning everything after it. If
632 | * `needle` does not occur in `self`, `self` is set to the empty slice,
633 | * and the entirety of `self` is returned.
634 | * @param self The slice to split.
635 | * @param needle The text to search for in `self`.
636 | * @return The part of `self` after the last occurrence of `delim`.
637 | */
638 | function rsplit(slice self, slice needle) internal returns (slice token) {
639 | rsplit(self, needle, token);
640 | }
641 |
642 | /*
643 | * @dev Counts the number of nonoverlapping occurrences of `needle` in `self`.
644 | * @param self The slice to search.
645 | * @param needle The text to search for in `self`.
646 | * @return The number of occurrences of `needle` found in `self`.
647 | */
648 | function count(slice self, slice needle) internal returns (uint count) {
649 | uint ptr = findPtr(self._len, self._ptr, needle._len, needle._ptr) + needle._len;
650 | while (ptr <= self._ptr + self._len) {
651 | count++;
652 | ptr = findPtr(self._len - (ptr - self._ptr), ptr, needle._len, needle._ptr) + needle._len;
653 | }
654 | }
655 |
656 | /*
657 | * @dev Returns True if `self` contains `needle`.
658 | * @param self The slice to search.
659 | * @param needle The text to search for in `self`.
660 | * @return True if `needle` is found in `self`, false otherwise.
661 | */
662 | function contains(slice self, slice needle) internal returns (bool) {
663 | return rfindPtr(self._len, self._ptr, needle._len, needle._ptr) != self._ptr;
664 | }
665 |
666 | /*
667 | * @dev Returns a newly allocated string containing the concatenation of
668 | * `self` and `other`.
669 | * @param self The first slice to concatenate.
670 | * @param other The second slice to concatenate.
671 | * @return The concatenation of the two strings.
672 | */
673 | function concat(slice self, slice other) internal returns (string) {
674 | var ret = new string(self._len + other._len);
675 | uint retptr;
676 | assembly { retptr := add(ret, 32) }
677 | memcpy(retptr, self._ptr, self._len);
678 | memcpy(retptr + self._len, other._ptr, other._len);
679 | return ret;
680 | }
681 |
682 | /*
683 | * @dev Joins an array of slices, using `self` as a delimiter, returning a
684 | * newly allocated string.
685 | * @param self The delimiter to use.
686 | * @param parts A list of slices to join.
687 | * @return A newly allocated string containing all the slices in `parts`,
688 | * joined with `self`.
689 | */
690 | function join(slice self, slice[] parts) internal returns (string) {
691 | if (parts.length == 0)
692 | return "";
693 |
694 | uint len = self._len * (parts.length - 1);
695 | for(uint i = 0; i < parts.length; i++)
696 | len += parts[i]._len;
697 |
698 | var ret = new string(len);
699 | uint retptr;
700 | assembly { retptr := add(ret, 32) }
701 |
702 | for(i = 0; i < parts.length; i++) {
703 | memcpy(retptr, parts[i]._ptr, parts[i]._len);
704 | retptr += parts[i]._len;
705 | if (i < parts.length - 1) {
706 | memcpy(retptr, self._ptr, self._len);
707 | retptr += self._len;
708 | }
709 | }
710 |
711 | return ret;
712 | }
713 |
714 | function bytes32ToString (bytes32 x) internal returns (string) {
715 | bytes memory bytesString = new bytes(32);
716 | uint charCount = 0;
717 | for (uint j = 0; j < 32; j++) {
718 | byte char = byte(bytes32(uint(x) * 2 ** (8 * j)));
719 | if (char != 0) {
720 | bytesString[charCount] = char;
721 | charCount++;
722 | }
723 | }
724 | bytes memory resultBytes = new bytes(charCount);
725 | for (j = 0; j < charCount; j++) {
726 | resultBytes[j] = bytesString[j];
727 | }
728 |
729 | return string(resultBytes);
730 | }
731 |
732 | function uintToBytes(uint v) internal returns (bytes32 ret) {
733 | if (v == 0) {
734 | ret = '0';
735 | }
736 | else {
737 | while (v > 0) {
738 | ret = bytes32(uint(ret) / (2 ** 8));
739 | ret |= bytes32(((v % 10) + 48) * 2 ** (8 * 31));
740 | v /= 10;
741 | }
742 | }
743 | return ret;
744 | }
745 |
746 | function uintToString(uint v) internal returns (string ret) {
747 | return bytes32ToString(uintToBytes(v));
748 | }
749 |
750 | }
751 |
--------------------------------------------------------------------------------
/resources/public/css/styles.css:
--------------------------------------------------------------------------------
1 | .flexboxgrid__container-fluid___2lUES,.flexboxgrid__container___R2zU9{margin-right:auto;margin-left:auto}.flexboxgrid__container-fluid___2lUES{padding-right:2rem;padding-left:2rem}.flexboxgrid__row___1y_mg{box-sizing:border-box;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:0;-ms-flex:0 1 auto;flex:0 1 auto;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-0.5rem;margin-left:-0.5rem}.flexboxgrid__row___1y_mg.flexboxgrid__reverse___1X682{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}.flexboxgrid__col___3RqPP.flexboxgrid__reverse___1X682{-webkit-box-orient:vertical;-webkit-box-direction:reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}.flexboxgrid__col-xs___1ROHR,.flexboxgrid__col-xs-1___VtNIK,.flexboxgrid__col-xs-2___36nDa,.flexboxgrid__col-xs-3___2f2Ql,.flexboxgrid__col-xs-4___TxBJg,.flexboxgrid__col-xs-5___1HkK5,.flexboxgrid__col-xs-6___1DhV6,.flexboxgrid__col-xs-7___3o2m-,.flexboxgrid__col-xs-8___3ARGc,.flexboxgrid__col-xs-9___15qfl,.flexboxgrid__col-xs-10___2AWNv,.flexboxgrid__col-xs-11___3H-6F,.flexboxgrid__col-xs-12___phbtE,.flexboxgrid__col-xs-offset-0___10C7E,.flexboxgrid__col-xs-offset-1___12o_R,.flexboxgrid__col-xs-offset-2___2Hh-B,.flexboxgrid__col-xs-offset-3___8NCys,.flexboxgrid__col-xs-offset-4___dA0P1,.flexboxgrid__col-xs-offset-5___2MbdF,.flexboxgrid__col-xs-offset-6___3N3bt,.flexboxgrid__col-xs-offset-7___1yQDG,.flexboxgrid__col-xs-offset-8___2aEcW,.flexboxgrid__col-xs-offset-9___2haBv,.flexboxgrid__col-xs-offset-10___1QsVg,.flexboxgrid__col-xs-offset-11___29xQn,.flexboxgrid__col-xs-offset-12___1XWFb{box-sizing:border-box;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;padding-right:.5rem;padding-left:.5rem}.flexboxgrid__col-xs___1ROHR{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-preferred-size:0;flex-basis:0;max-width:100%}.flexboxgrid__col-xs-1___VtNIK{-ms-flex-preferred-size:8.33333333%;flex-basis:8.33333333%;max-width:8.33333333%}.flexboxgrid__col-xs-2___36nDa{-ms-flex-preferred-size:16.66666667%;flex-basis:16.66666667%;max-width:16.66666667%}.flexboxgrid__col-xs-3___2f2Ql{-ms-flex-preferred-size:25%;flex-basis:25%;max-width:25%}.flexboxgrid__col-xs-4___TxBJg{-ms-flex-preferred-size:33.33333333%;flex-basis:33.33333333%;max-width:33.33333333%}.flexboxgrid__col-xs-5___1HkK5{-ms-flex-preferred-size:41.66666667%;flex-basis:41.66666667%;max-width:41.66666667%}.flexboxgrid__col-xs-6___1DhV6{-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}.flexboxgrid__col-xs-7___3o2m-{-ms-flex-preferred-size:58.33333333%;flex-basis:58.33333333%;max-width:58.33333333%}.flexboxgrid__col-xs-8___3ARGc{-ms-flex-preferred-size:66.66666667%;flex-basis:66.66666667%;max-width:66.66666667%}.flexboxgrid__col-xs-9___15qfl{-ms-flex-preferred-size:75%;flex-basis:75%;max-width:75%}.flexboxgrid__col-xs-10___2AWNv{-ms-flex-preferred-size:83.33333333%;flex-basis:83.33333333%;max-width:83.33333333%}.flexboxgrid__col-xs-11___3H-6F{-ms-flex-preferred-size:91.66666667%;flex-basis:91.66666667%;max-width:91.66666667%}.flexboxgrid__col-xs-12___phbtE{-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}.flexboxgrid__col-xs-offset-0___10C7E{margin-left:0}.flexboxgrid__col-xs-offset-1___12o_R{margin-left:8.33333333%}.flexboxgrid__col-xs-offset-2___2Hh-B{margin-left:16.66666667%}.flexboxgrid__col-xs-offset-3___8NCys{margin-left:25%}.flexboxgrid__col-xs-offset-4___dA0P1{margin-left:33.33333333%}.flexboxgrid__col-xs-offset-5___2MbdF{margin-left:41.66666667%}.flexboxgrid__col-xs-offset-6___3N3bt{margin-left:50%}.flexboxgrid__col-xs-offset-7___1yQDG{margin-left:58.33333333%}.flexboxgrid__col-xs-offset-8___2aEcW{margin-left:66.66666667%}.flexboxgrid__col-xs-offset-9___2haBv{margin-left:75%}.flexboxgrid__col-xs-offset-10___1QsVg{margin-left:83.33333333%}.flexboxgrid__col-xs-offset-11___29xQn{margin-left:91.66666667%}.flexboxgrid__start-xs___h8qdA{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;text-align:start}.flexboxgrid__center-xs___1JWon{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;text-align:center}.flexboxgrid__end-xs___33Mku{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;text-align:end}.flexboxgrid__top-xs___UhA-V{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.flexboxgrid__middle-xs___1h5t3{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.flexboxgrid__bottom-xs___2tRUa{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.flexboxgrid__around-xs___1okkK{-ms-flex-pack:distribute;justify-content:space-around}.flexboxgrid__between-xs___WFP84{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.flexboxgrid__first-xs___XoosK{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.flexboxgrid__last-xs___HnlRw{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}@media only screen and (min-width:48em){.flexboxgrid__container___R2zU9{width:49rem}.flexboxgrid__col-sm___3tZ-z,.flexboxgrid__col-sm-1___2Gca6,.flexboxgrid__col-sm-2___YETza,.flexboxgrid__col-sm-3___2irZQ,.flexboxgrid__col-sm-4___3kj7S,.flexboxgrid__col-sm-5___gAxuQ,.flexboxgrid__col-sm-6___vUdKH,.flexboxgrid__col-sm-7___22IcQ,.flexboxgrid__col-sm-8___2_YhB,.flexboxgrid__col-sm-9___2ubpx,.flexboxgrid__col-sm-10___262G9,.flexboxgrid__col-sm-11___39s7J,.flexboxgrid__col-sm-12___1e5Uk,.flexboxgrid__col-sm-offset-0___llQ6-,.flexboxgrid__col-sm-offset-1___1PFWu,.flexboxgrid__col-sm-offset-2___1DgbO,.flexboxgrid__col-sm-offset-3___3W5Iv,.flexboxgrid__col-sm-offset-4___3YToG,.flexboxgrid__col-sm-offset-5___609Vo,.flexboxgrid__col-sm-offset-6___TCeVQ,.flexboxgrid__col-sm-offset-7___csvBu,.flexboxgrid__col-sm-offset-8___11PYH,.flexboxgrid__col-sm-offset-9___24Evy,.flexboxgrid__col-sm-offset-10___1-lcE,.flexboxgrid__col-sm-offset-11___2ynFq,.flexboxgrid__col-sm-offset-12___3MBMi{box-sizing:border-box;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;padding-right:.5rem;padding-left:.5rem}.flexboxgrid__col-sm___3tZ-z{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-preferred-size:0;flex-basis:0;max-width:100%}.flexboxgrid__col-sm-1___2Gca6{-ms-flex-preferred-size:8.33333333%;flex-basis:8.33333333%;max-width:8.33333333%}.flexboxgrid__col-sm-2___YETza{-ms-flex-preferred-size:16.66666667%;flex-basis:16.66666667%;max-width:16.66666667%}.flexboxgrid__col-sm-3___2irZQ{-ms-flex-preferred-size:25%;flex-basis:25%;max-width:25%}.flexboxgrid__col-sm-4___3kj7S{-ms-flex-preferred-size:33.33333333%;flex-basis:33.33333333%;max-width:33.33333333%}.flexboxgrid__col-sm-5___gAxuQ{-ms-flex-preferred-size:41.66666667%;flex-basis:41.66666667%;max-width:41.66666667%}.flexboxgrid__col-sm-6___vUdKH{-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}.flexboxgrid__col-sm-7___22IcQ{-ms-flex-preferred-size:58.33333333%;flex-basis:58.33333333%;max-width:58.33333333%}.flexboxgrid__col-sm-8___2_YhB{-ms-flex-preferred-size:66.66666667%;flex-basis:66.66666667%;max-width:66.66666667%}.flexboxgrid__col-sm-9___2ubpx{-ms-flex-preferred-size:75%;flex-basis:75%;max-width:75%}.flexboxgrid__col-sm-10___262G9{-ms-flex-preferred-size:83.33333333%;flex-basis:83.33333333%;max-width:83.33333333%}.flexboxgrid__col-sm-11___39s7J{-ms-flex-preferred-size:91.66666667%;flex-basis:91.66666667%;max-width:91.66666667%}.flexboxgrid__col-sm-12___1e5Uk{-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}.flexboxgrid__col-sm-offset-0___llQ6-{margin-left:0}.flexboxgrid__col-sm-offset-1___1PFWu{margin-left:8.33333333%}.flexboxgrid__col-sm-offset-2___1DgbO{margin-left:16.66666667%}.flexboxgrid__col-sm-offset-3___3W5Iv{margin-left:25%}.flexboxgrid__col-sm-offset-4___3YToG{margin-left:33.33333333%}.flexboxgrid__col-sm-offset-5___609Vo{margin-left:41.66666667%}.flexboxgrid__col-sm-offset-6___TCeVQ{margin-left:50%}.flexboxgrid__col-sm-offset-7___csvBu{margin-left:58.33333333%}.flexboxgrid__col-sm-offset-8___11PYH{margin-left:66.66666667%}.flexboxgrid__col-sm-offset-9___24Evy{margin-left:75%}.flexboxgrid__col-sm-offset-10___1-lcE{margin-left:83.33333333%}.flexboxgrid__col-sm-offset-11___2ynFq{margin-left:91.66666667%}.flexboxgrid__start-sm___3Dilu{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;text-align:start}.flexboxgrid__center-sm___39HWq{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;text-align:center}.flexboxgrid__end-sm___3B07f{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;text-align:end}.flexboxgrid__top-sm___1begS{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.flexboxgrid__middle-sm___Oh4K7{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.flexboxgrid__bottom-sm___1jPnc{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.flexboxgrid__around-sm___3ffbb{-ms-flex-pack:distribute;justify-content:space-around}.flexboxgrid__between-sm___1Rcaf{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.flexboxgrid__first-sm___2Gzhb{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.flexboxgrid__last-sm___1pF8w{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}}@media only screen and (min-width:64em){.flexboxgrid__container___R2zU9{width:65rem}.flexboxgrid__col-md___2lbzm,.flexboxgrid__col-md-1___1Lapj,.flexboxgrid__col-md-2___1c_4t,.flexboxgrid__col-md-3___3ANRS,.flexboxgrid__col-md-4___a_FyK,.flexboxgrid__col-md-5___YXlMq,.flexboxgrid__col-md-6___5OSyJ,.flexboxgrid__col-md-7___1Zp-r,.flexboxgrid__col-md-8___3979J,.flexboxgrid__col-md-9___2fXuC,.flexboxgrid__col-md-10___2Jbee,.flexboxgrid__col-md-11___3drbK,.flexboxgrid__col-md-12___zR2lK,.flexboxgrid__col-md-offset-0___2O3vR,.flexboxgrid__col-md-offset-1___2XNCz,.flexboxgrid__col-md-offset-2___2t-NV,.flexboxgrid__col-md-offset-3___1zlTP,.flexboxgrid__col-md-offset-4___3aHxz,.flexboxgrid__col-md-offset-5___3S2Gw,.flexboxgrid__col-md-offset-6___3KV0V,.flexboxgrid__col-md-offset-7___1OdCD,.flexboxgrid__col-md-offset-8___2vFbQ,.flexboxgrid__col-md-offset-9___1q95x,.flexboxgrid__col-md-offset-10___2CeMK,.flexboxgrid__col-md-offset-11___3u6XW,.flexboxgrid__col-md-offset-12___eKUlL{box-sizing:border-box;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;padding-right:.5rem;padding-left:.5rem}.flexboxgrid__col-md___2lbzm{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-preferred-size:0;flex-basis:0;max-width:100%}.flexboxgrid__col-md-1___1Lapj{-ms-flex-preferred-size:8.33333333%;flex-basis:8.33333333%;max-width:8.33333333%}.flexboxgrid__col-md-2___1c_4t{-ms-flex-preferred-size:16.66666667%;flex-basis:16.66666667%;max-width:16.66666667%}.flexboxgrid__col-md-3___3ANRS{-ms-flex-preferred-size:25%;flex-basis:25%;max-width:25%}.flexboxgrid__col-md-4___a_FyK{-ms-flex-preferred-size:33.33333333%;flex-basis:33.33333333%;max-width:33.33333333%}.flexboxgrid__col-md-5___YXlMq{-ms-flex-preferred-size:41.66666667%;flex-basis:41.66666667%;max-width:41.66666667%}.flexboxgrid__col-md-6___5OSyJ{-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}.flexboxgrid__col-md-7___1Zp-r{-ms-flex-preferred-size:58.33333333%;flex-basis:58.33333333%;max-width:58.33333333%}.flexboxgrid__col-md-8___3979J{-ms-flex-preferred-size:66.66666667%;flex-basis:66.66666667%;max-width:66.66666667%}.flexboxgrid__col-md-9___2fXuC{-ms-flex-preferred-size:75%;flex-basis:75%;max-width:75%}.flexboxgrid__col-md-10___2Jbee{-ms-flex-preferred-size:83.33333333%;flex-basis:83.33333333%;max-width:83.33333333%}.flexboxgrid__col-md-11___3drbK{-ms-flex-preferred-size:91.66666667%;flex-basis:91.66666667%;max-width:91.66666667%}.flexboxgrid__col-md-12___zR2lK{-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}.flexboxgrid__col-md-offset-0___2O3vR{margin-left:0}.flexboxgrid__col-md-offset-1___2XNCz{margin-left:8.33333333%}.flexboxgrid__col-md-offset-2___2t-NV{margin-left:16.66666667%}.flexboxgrid__col-md-offset-3___1zlTP{margin-left:25%}.flexboxgrid__col-md-offset-4___3aHxz{margin-left:33.33333333%}.flexboxgrid__col-md-offset-5___3S2Gw{margin-left:41.66666667%}.flexboxgrid__col-md-offset-6___3KV0V{margin-left:50%}.flexboxgrid__col-md-offset-7___1OdCD{margin-left:58.33333333%}.flexboxgrid__col-md-offset-8___2vFbQ{margin-left:66.66666667%}.flexboxgrid__col-md-offset-9___1q95x{margin-left:75%}.flexboxgrid__col-md-offset-10___2CeMK{margin-left:83.33333333%}.flexboxgrid__col-md-offset-11___3u6XW{margin-left:91.66666667%}.flexboxgrid__start-md___2B-sg{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;text-align:start}.flexboxgrid__center-md___3VDfS{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;text-align:center}.flexboxgrid__end-md___2fJWy{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;text-align:end}.flexboxgrid__top-md___12FDg{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.flexboxgrid__middle-md___3wIJR{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.flexboxgrid__bottom-md___2v1cd{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.flexboxgrid__around-md___1x54_{-ms-flex-pack:distribute;justify-content:space-around}.flexboxgrid__between-md___Xn-9x{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.flexboxgrid__first-md___3j4t5{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.flexboxgrid__last-md___3y72e{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}}@media only screen and (min-width:75em){.flexboxgrid__container___R2zU9{width:76rem}.flexboxgrid__col-lg___3SaXd,.flexboxgrid__col-lg-1___2VMiv,.flexboxgrid__col-lg-2___21dKK,.flexboxgrid__col-lg-3___vbACp,.flexboxgrid__col-lg-4___2hzy8,.flexboxgrid__col-lg-5___1-g7-,.flexboxgrid__col-lg-6___21lf8,.flexboxgrid__col-lg-7___3kBG1,.flexboxgrid__col-lg-8___afECx,.flexboxgrid__col-lg-9___10mdl,.flexboxgrid__col-lg-10___1yTfj,.flexboxgrid__col-lg-11___3hMRu,.flexboxgrid__col-lg-12___1rlAA,.flexboxgrid__col-lg-offset-0___3KM3x,.flexboxgrid__col-lg-offset-1___KhvqR,.flexboxgrid__col-lg-offset-2___1ZD_z,.flexboxgrid__col-lg-offset-3___2GQVa,.flexboxgrid__col-lg-offset-4___1zPZj,.flexboxgrid__col-lg-offset-5___Kj8Iq,.flexboxgrid__col-lg-offset-6___3nun3,.flexboxgrid__col-lg-offset-7___YTmn9,.flexboxgrid__col-lg-offset-8___1qG2t,.flexboxgrid__col-lg-offset-9___qd27B,.flexboxgrid__col-lg-offset-10___2YScP,.flexboxgrid__col-lg-offset-11___3pPvj,.flexboxgrid__col-lg-offset-12___2rHEg{box-sizing:border-box;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;padding-right:.5rem;padding-left:.5rem}.flexboxgrid__col-lg___3SaXd{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-preferred-size:0;flex-basis:0;max-width:100%}.flexboxgrid__col-lg-1___2VMiv{-ms-flex-preferred-size:8.33333333%;flex-basis:8.33333333%;max-width:8.33333333%}.flexboxgrid__col-lg-2___21dKK{-ms-flex-preferred-size:16.66666667%;flex-basis:16.66666667%;max-width:16.66666667%}.flexboxgrid__col-lg-3___vbACp{-ms-flex-preferred-size:25%;flex-basis:25%;max-width:25%}.flexboxgrid__col-lg-4___2hzy8{-ms-flex-preferred-size:33.33333333%;flex-basis:33.33333333%;max-width:33.33333333%}.flexboxgrid__col-lg-5___1-g7-{-ms-flex-preferred-size:41.66666667%;flex-basis:41.66666667%;max-width:41.66666667%}.flexboxgrid__col-lg-6___21lf8{-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}.flexboxgrid__col-lg-7___3kBG1{-ms-flex-preferred-size:58.33333333%;flex-basis:58.33333333%;max-width:58.33333333%}.flexboxgrid__col-lg-8___afECx{-ms-flex-preferred-size:66.66666667%;flex-basis:66.66666667%;max-width:66.66666667%}.flexboxgrid__col-lg-9___10mdl{-ms-flex-preferred-size:75%;flex-basis:75%;max-width:75%}.flexboxgrid__col-lg-10___1yTfj{-ms-flex-preferred-size:83.33333333%;flex-basis:83.33333333%;max-width:83.33333333%}.flexboxgrid__col-lg-11___3hMRu{-ms-flex-preferred-size:91.66666667%;flex-basis:91.66666667%;max-width:91.66666667%}.flexboxgrid__col-lg-12___1rlAA{-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}.flexboxgrid__col-lg-offset-0___3KM3x{margin-left:0}.flexboxgrid__col-lg-offset-1___KhvqR{margin-left:8.33333333%}.flexboxgrid__col-lg-offset-2___1ZD_z{margin-left:16.66666667%}.flexboxgrid__col-lg-offset-3___2GQVa{margin-left:25%}.flexboxgrid__col-lg-offset-4___1zPZj{margin-left:33.33333333%}.flexboxgrid__col-lg-offset-5___Kj8Iq{margin-left:41.66666667%}.flexboxgrid__col-lg-offset-6___3nun3{margin-left:50%}.flexboxgrid__col-lg-offset-7___YTmn9{margin-left:58.33333333%}.flexboxgrid__col-lg-offset-8___1qG2t{margin-left:66.66666667%}.flexboxgrid__col-lg-offset-9___qd27B{margin-left:75%}.flexboxgrid__col-lg-offset-10___2YScP{margin-left:83.33333333%}.flexboxgrid__col-lg-offset-11___3pPvj{margin-left:91.66666667%}.flexboxgrid__start-lg___ageu9{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;text-align:start}.flexboxgrid__center-lg___3H3SI{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;text-align:center}.flexboxgrid__end-lg___27_fM{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;text-align:end}.flexboxgrid__top-lg___1tWWw{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.flexboxgrid__middle-lg___nocGI{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.flexboxgrid__bottom-lg___IYGks{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.flexboxgrid__around-lg___zZC2C{-ms-flex-pack:distribute;justify-content:space-around}.flexboxgrid__between-lg___2njzk{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.flexboxgrid__first-lg___6dksO{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.flexboxgrid__last-lg___xGBvS{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}}a{color:#ff4081;text-decoration:none;} a.no-decor{color:inherit !important;} a:hover{text-decoration:underline;} a.no-decor:hover{text-decoration:none;} html{font-family:'Roboto',sans-serif;-webkit-font-smoothing:antialiased;} body{background-color:#efefef;} body,
2 | h1,
3 | h2,
4 | h3,
5 | h4,
6 | h5,
7 | h6{margin:0;color:rgba(0, 0, 0, 0.87);} h1,
8 | h2,
9 | h3,
10 | h4,
11 | h5,
12 | h6{font-weight:300;} body{font-size:15px;line-height:24px;} strong,
13 | b{font-weight:500;}
14 | /*# sourceMappingURL=styles.main.css.map */
15 |
--------------------------------------------------------------------------------
/resources/public/css/styles.main.css.map:
--------------------------------------------------------------------------------
1 | {
2 | "version":3,
3 | "file":"styles.main.css",
4 | "lineCount":13,
5 | "mappings":"A,qwiBCGAA,C,sCAKAA,CAACC,S,4BAIDD,CAACE,M,6BAIDF,CAACC,SAASC,M,wBAIVC,I,sEAKAC,I,4BAIAA,I;AAAMC,E;AAAIC,E;AAAIC,E;AAAIC,E;AAAIC,E;AAAIC,E,sCAK1BL,E;AAAIC,E;AAAIC,E;AAAIC,E;AAAIC,E;AAAIC,E,mBAIpBN,I,mCAKAO,M;AAAQC;",
6 | "sources":["jar:file:/Users/matus/.m2/repository/cljsjs/react-flexbox-grid/0.10.2-1/react-flexbox-grid-0.10.2-1.jar!/cljsjs/react-flexbox-grid/production/react-flexbox-grid.min.inc.css","styles.main.less"],
7 | "sourcesContent":[".flexboxgrid__container-fluid___2lUES,.flexboxgrid__container___R2zU9{margin-right:auto;margin-left:auto}.flexboxgrid__container-fluid___2lUES{padding-right:2rem;padding-left:2rem}.flexboxgrid__row___1y_mg{box-sizing:border-box;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:0;-ms-flex:0 1 auto;flex:0 1 auto;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-0.5rem;margin-left:-0.5rem}.flexboxgrid__row___1y_mg.flexboxgrid__reverse___1X682{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}.flexboxgrid__col___3RqPP.flexboxgrid__reverse___1X682{-webkit-box-orient:vertical;-webkit-box-direction:reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}.flexboxgrid__col-xs___1ROHR,.flexboxgrid__col-xs-1___VtNIK,.flexboxgrid__col-xs-2___36nDa,.flexboxgrid__col-xs-3___2f2Ql,.flexboxgrid__col-xs-4___TxBJg,.flexboxgrid__col-xs-5___1HkK5,.flexboxgrid__col-xs-6___1DhV6,.flexboxgrid__col-xs-7___3o2m-,.flexboxgrid__col-xs-8___3ARGc,.flexboxgrid__col-xs-9___15qfl,.flexboxgrid__col-xs-10___2AWNv,.flexboxgrid__col-xs-11___3H-6F,.flexboxgrid__col-xs-12___phbtE,.flexboxgrid__col-xs-offset-0___10C7E,.flexboxgrid__col-xs-offset-1___12o_R,.flexboxgrid__col-xs-offset-2___2Hh-B,.flexboxgrid__col-xs-offset-3___8NCys,.flexboxgrid__col-xs-offset-4___dA0P1,.flexboxgrid__col-xs-offset-5___2MbdF,.flexboxgrid__col-xs-offset-6___3N3bt,.flexboxgrid__col-xs-offset-7___1yQDG,.flexboxgrid__col-xs-offset-8___2aEcW,.flexboxgrid__col-xs-offset-9___2haBv,.flexboxgrid__col-xs-offset-10___1QsVg,.flexboxgrid__col-xs-offset-11___29xQn,.flexboxgrid__col-xs-offset-12___1XWFb{box-sizing:border-box;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;padding-right:.5rem;padding-left:.5rem}.flexboxgrid__col-xs___1ROHR{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-preferred-size:0;flex-basis:0;max-width:100%}.flexboxgrid__col-xs-1___VtNIK{-ms-flex-preferred-size:8.33333333%;flex-basis:8.33333333%;max-width:8.33333333%}.flexboxgrid__col-xs-2___36nDa{-ms-flex-preferred-size:16.66666667%;flex-basis:16.66666667%;max-width:16.66666667%}.flexboxgrid__col-xs-3___2f2Ql{-ms-flex-preferred-size:25%;flex-basis:25%;max-width:25%}.flexboxgrid__col-xs-4___TxBJg{-ms-flex-preferred-size:33.33333333%;flex-basis:33.33333333%;max-width:33.33333333%}.flexboxgrid__col-xs-5___1HkK5{-ms-flex-preferred-size:41.66666667%;flex-basis:41.66666667%;max-width:41.66666667%}.flexboxgrid__col-xs-6___1DhV6{-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}.flexboxgrid__col-xs-7___3o2m-{-ms-flex-preferred-size:58.33333333%;flex-basis:58.33333333%;max-width:58.33333333%}.flexboxgrid__col-xs-8___3ARGc{-ms-flex-preferred-size:66.66666667%;flex-basis:66.66666667%;max-width:66.66666667%}.flexboxgrid__col-xs-9___15qfl{-ms-flex-preferred-size:75%;flex-basis:75%;max-width:75%}.flexboxgrid__col-xs-10___2AWNv{-ms-flex-preferred-size:83.33333333%;flex-basis:83.33333333%;max-width:83.33333333%}.flexboxgrid__col-xs-11___3H-6F{-ms-flex-preferred-size:91.66666667%;flex-basis:91.66666667%;max-width:91.66666667%}.flexboxgrid__col-xs-12___phbtE{-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}.flexboxgrid__col-xs-offset-0___10C7E{margin-left:0}.flexboxgrid__col-xs-offset-1___12o_R{margin-left:8.33333333%}.flexboxgrid__col-xs-offset-2___2Hh-B{margin-left:16.66666667%}.flexboxgrid__col-xs-offset-3___8NCys{margin-left:25%}.flexboxgrid__col-xs-offset-4___dA0P1{margin-left:33.33333333%}.flexboxgrid__col-xs-offset-5___2MbdF{margin-left:41.66666667%}.flexboxgrid__col-xs-offset-6___3N3bt{margin-left:50%}.flexboxgrid__col-xs-offset-7___1yQDG{margin-left:58.33333333%}.flexboxgrid__col-xs-offset-8___2aEcW{margin-left:66.66666667%}.flexboxgrid__col-xs-offset-9___2haBv{margin-left:75%}.flexboxgrid__col-xs-offset-10___1QsVg{margin-left:83.33333333%}.flexboxgrid__col-xs-offset-11___29xQn{margin-left:91.66666667%}.flexboxgrid__start-xs___h8qdA{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;text-align:start}.flexboxgrid__center-xs___1JWon{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;text-align:center}.flexboxgrid__end-xs___33Mku{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;text-align:end}.flexboxgrid__top-xs___UhA-V{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.flexboxgrid__middle-xs___1h5t3{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.flexboxgrid__bottom-xs___2tRUa{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.flexboxgrid__around-xs___1okkK{-ms-flex-pack:distribute;justify-content:space-around}.flexboxgrid__between-xs___WFP84{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.flexboxgrid__first-xs___XoosK{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.flexboxgrid__last-xs___HnlRw{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}@media only screen and (min-width:48em){.flexboxgrid__container___R2zU9{width:49rem}.flexboxgrid__col-sm___3tZ-z,.flexboxgrid__col-sm-1___2Gca6,.flexboxgrid__col-sm-2___YETza,.flexboxgrid__col-sm-3___2irZQ,.flexboxgrid__col-sm-4___3kj7S,.flexboxgrid__col-sm-5___gAxuQ,.flexboxgrid__col-sm-6___vUdKH,.flexboxgrid__col-sm-7___22IcQ,.flexboxgrid__col-sm-8___2_YhB,.flexboxgrid__col-sm-9___2ubpx,.flexboxgrid__col-sm-10___262G9,.flexboxgrid__col-sm-11___39s7J,.flexboxgrid__col-sm-12___1e5Uk,.flexboxgrid__col-sm-offset-0___llQ6-,.flexboxgrid__col-sm-offset-1___1PFWu,.flexboxgrid__col-sm-offset-2___1DgbO,.flexboxgrid__col-sm-offset-3___3W5Iv,.flexboxgrid__col-sm-offset-4___3YToG,.flexboxgrid__col-sm-offset-5___609Vo,.flexboxgrid__col-sm-offset-6___TCeVQ,.flexboxgrid__col-sm-offset-7___csvBu,.flexboxgrid__col-sm-offset-8___11PYH,.flexboxgrid__col-sm-offset-9___24Evy,.flexboxgrid__col-sm-offset-10___1-lcE,.flexboxgrid__col-sm-offset-11___2ynFq,.flexboxgrid__col-sm-offset-12___3MBMi{box-sizing:border-box;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;padding-right:.5rem;padding-left:.5rem}.flexboxgrid__col-sm___3tZ-z{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-preferred-size:0;flex-basis:0;max-width:100%}.flexboxgrid__col-sm-1___2Gca6{-ms-flex-preferred-size:8.33333333%;flex-basis:8.33333333%;max-width:8.33333333%}.flexboxgrid__col-sm-2___YETza{-ms-flex-preferred-size:16.66666667%;flex-basis:16.66666667%;max-width:16.66666667%}.flexboxgrid__col-sm-3___2irZQ{-ms-flex-preferred-size:25%;flex-basis:25%;max-width:25%}.flexboxgrid__col-sm-4___3kj7S{-ms-flex-preferred-size:33.33333333%;flex-basis:33.33333333%;max-width:33.33333333%}.flexboxgrid__col-sm-5___gAxuQ{-ms-flex-preferred-size:41.66666667%;flex-basis:41.66666667%;max-width:41.66666667%}.flexboxgrid__col-sm-6___vUdKH{-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}.flexboxgrid__col-sm-7___22IcQ{-ms-flex-preferred-size:58.33333333%;flex-basis:58.33333333%;max-width:58.33333333%}.flexboxgrid__col-sm-8___2_YhB{-ms-flex-preferred-size:66.66666667%;flex-basis:66.66666667%;max-width:66.66666667%}.flexboxgrid__col-sm-9___2ubpx{-ms-flex-preferred-size:75%;flex-basis:75%;max-width:75%}.flexboxgrid__col-sm-10___262G9{-ms-flex-preferred-size:83.33333333%;flex-basis:83.33333333%;max-width:83.33333333%}.flexboxgrid__col-sm-11___39s7J{-ms-flex-preferred-size:91.66666667%;flex-basis:91.66666667%;max-width:91.66666667%}.flexboxgrid__col-sm-12___1e5Uk{-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}.flexboxgrid__col-sm-offset-0___llQ6-{margin-left:0}.flexboxgrid__col-sm-offset-1___1PFWu{margin-left:8.33333333%}.flexboxgrid__col-sm-offset-2___1DgbO{margin-left:16.66666667%}.flexboxgrid__col-sm-offset-3___3W5Iv{margin-left:25%}.flexboxgrid__col-sm-offset-4___3YToG{margin-left:33.33333333%}.flexboxgrid__col-sm-offset-5___609Vo{margin-left:41.66666667%}.flexboxgrid__col-sm-offset-6___TCeVQ{margin-left:50%}.flexboxgrid__col-sm-offset-7___csvBu{margin-left:58.33333333%}.flexboxgrid__col-sm-offset-8___11PYH{margin-left:66.66666667%}.flexboxgrid__col-sm-offset-9___24Evy{margin-left:75%}.flexboxgrid__col-sm-offset-10___1-lcE{margin-left:83.33333333%}.flexboxgrid__col-sm-offset-11___2ynFq{margin-left:91.66666667%}.flexboxgrid__start-sm___3Dilu{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;text-align:start}.flexboxgrid__center-sm___39HWq{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;text-align:center}.flexboxgrid__end-sm___3B07f{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;text-align:end}.flexboxgrid__top-sm___1begS{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.flexboxgrid__middle-sm___Oh4K7{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.flexboxgrid__bottom-sm___1jPnc{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.flexboxgrid__around-sm___3ffbb{-ms-flex-pack:distribute;justify-content:space-around}.flexboxgrid__between-sm___1Rcaf{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.flexboxgrid__first-sm___2Gzhb{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.flexboxgrid__last-sm___1pF8w{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}}@media only screen and (min-width:64em){.flexboxgrid__container___R2zU9{width:65rem}.flexboxgrid__col-md___2lbzm,.flexboxgrid__col-md-1___1Lapj,.flexboxgrid__col-md-2___1c_4t,.flexboxgrid__col-md-3___3ANRS,.flexboxgrid__col-md-4___a_FyK,.flexboxgrid__col-md-5___YXlMq,.flexboxgrid__col-md-6___5OSyJ,.flexboxgrid__col-md-7___1Zp-r,.flexboxgrid__col-md-8___3979J,.flexboxgrid__col-md-9___2fXuC,.flexboxgrid__col-md-10___2Jbee,.flexboxgrid__col-md-11___3drbK,.flexboxgrid__col-md-12___zR2lK,.flexboxgrid__col-md-offset-0___2O3vR,.flexboxgrid__col-md-offset-1___2XNCz,.flexboxgrid__col-md-offset-2___2t-NV,.flexboxgrid__col-md-offset-3___1zlTP,.flexboxgrid__col-md-offset-4___3aHxz,.flexboxgrid__col-md-offset-5___3S2Gw,.flexboxgrid__col-md-offset-6___3KV0V,.flexboxgrid__col-md-offset-7___1OdCD,.flexboxgrid__col-md-offset-8___2vFbQ,.flexboxgrid__col-md-offset-9___1q95x,.flexboxgrid__col-md-offset-10___2CeMK,.flexboxgrid__col-md-offset-11___3u6XW,.flexboxgrid__col-md-offset-12___eKUlL{box-sizing:border-box;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;padding-right:.5rem;padding-left:.5rem}.flexboxgrid__col-md___2lbzm{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-preferred-size:0;flex-basis:0;max-width:100%}.flexboxgrid__col-md-1___1Lapj{-ms-flex-preferred-size:8.33333333%;flex-basis:8.33333333%;max-width:8.33333333%}.flexboxgrid__col-md-2___1c_4t{-ms-flex-preferred-size:16.66666667%;flex-basis:16.66666667%;max-width:16.66666667%}.flexboxgrid__col-md-3___3ANRS{-ms-flex-preferred-size:25%;flex-basis:25%;max-width:25%}.flexboxgrid__col-md-4___a_FyK{-ms-flex-preferred-size:33.33333333%;flex-basis:33.33333333%;max-width:33.33333333%}.flexboxgrid__col-md-5___YXlMq{-ms-flex-preferred-size:41.66666667%;flex-basis:41.66666667%;max-width:41.66666667%}.flexboxgrid__col-md-6___5OSyJ{-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}.flexboxgrid__col-md-7___1Zp-r{-ms-flex-preferred-size:58.33333333%;flex-basis:58.33333333%;max-width:58.33333333%}.flexboxgrid__col-md-8___3979J{-ms-flex-preferred-size:66.66666667%;flex-basis:66.66666667%;max-width:66.66666667%}.flexboxgrid__col-md-9___2fXuC{-ms-flex-preferred-size:75%;flex-basis:75%;max-width:75%}.flexboxgrid__col-md-10___2Jbee{-ms-flex-preferred-size:83.33333333%;flex-basis:83.33333333%;max-width:83.33333333%}.flexboxgrid__col-md-11___3drbK{-ms-flex-preferred-size:91.66666667%;flex-basis:91.66666667%;max-width:91.66666667%}.flexboxgrid__col-md-12___zR2lK{-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}.flexboxgrid__col-md-offset-0___2O3vR{margin-left:0}.flexboxgrid__col-md-offset-1___2XNCz{margin-left:8.33333333%}.flexboxgrid__col-md-offset-2___2t-NV{margin-left:16.66666667%}.flexboxgrid__col-md-offset-3___1zlTP{margin-left:25%}.flexboxgrid__col-md-offset-4___3aHxz{margin-left:33.33333333%}.flexboxgrid__col-md-offset-5___3S2Gw{margin-left:41.66666667%}.flexboxgrid__col-md-offset-6___3KV0V{margin-left:50%}.flexboxgrid__col-md-offset-7___1OdCD{margin-left:58.33333333%}.flexboxgrid__col-md-offset-8___2vFbQ{margin-left:66.66666667%}.flexboxgrid__col-md-offset-9___1q95x{margin-left:75%}.flexboxgrid__col-md-offset-10___2CeMK{margin-left:83.33333333%}.flexboxgrid__col-md-offset-11___3u6XW{margin-left:91.66666667%}.flexboxgrid__start-md___2B-sg{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;text-align:start}.flexboxgrid__center-md___3VDfS{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;text-align:center}.flexboxgrid__end-md___2fJWy{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;text-align:end}.flexboxgrid__top-md___12FDg{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.flexboxgrid__middle-md___3wIJR{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.flexboxgrid__bottom-md___2v1cd{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.flexboxgrid__around-md___1x54_{-ms-flex-pack:distribute;justify-content:space-around}.flexboxgrid__between-md___Xn-9x{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.flexboxgrid__first-md___3j4t5{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.flexboxgrid__last-md___3y72e{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}}@media only screen and (min-width:75em){.flexboxgrid__container___R2zU9{width:76rem}.flexboxgrid__col-lg___3SaXd,.flexboxgrid__col-lg-1___2VMiv,.flexboxgrid__col-lg-2___21dKK,.flexboxgrid__col-lg-3___vbACp,.flexboxgrid__col-lg-4___2hzy8,.flexboxgrid__col-lg-5___1-g7-,.flexboxgrid__col-lg-6___21lf8,.flexboxgrid__col-lg-7___3kBG1,.flexboxgrid__col-lg-8___afECx,.flexboxgrid__col-lg-9___10mdl,.flexboxgrid__col-lg-10___1yTfj,.flexboxgrid__col-lg-11___3hMRu,.flexboxgrid__col-lg-12___1rlAA,.flexboxgrid__col-lg-offset-0___3KM3x,.flexboxgrid__col-lg-offset-1___KhvqR,.flexboxgrid__col-lg-offset-2___1ZD_z,.flexboxgrid__col-lg-offset-3___2GQVa,.flexboxgrid__col-lg-offset-4___1zPZj,.flexboxgrid__col-lg-offset-5___Kj8Iq,.flexboxgrid__col-lg-offset-6___3nun3,.flexboxgrid__col-lg-offset-7___YTmn9,.flexboxgrid__col-lg-offset-8___1qG2t,.flexboxgrid__col-lg-offset-9___qd27B,.flexboxgrid__col-lg-offset-10___2YScP,.flexboxgrid__col-lg-offset-11___3pPvj,.flexboxgrid__col-lg-offset-12___2rHEg{box-sizing:border-box;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;padding-right:.5rem;padding-left:.5rem}.flexboxgrid__col-lg___3SaXd{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-preferred-size:0;flex-basis:0;max-width:100%}.flexboxgrid__col-lg-1___2VMiv{-ms-flex-preferred-size:8.33333333%;flex-basis:8.33333333%;max-width:8.33333333%}.flexboxgrid__col-lg-2___21dKK{-ms-flex-preferred-size:16.66666667%;flex-basis:16.66666667%;max-width:16.66666667%}.flexboxgrid__col-lg-3___vbACp{-ms-flex-preferred-size:25%;flex-basis:25%;max-width:25%}.flexboxgrid__col-lg-4___2hzy8{-ms-flex-preferred-size:33.33333333%;flex-basis:33.33333333%;max-width:33.33333333%}.flexboxgrid__col-lg-5___1-g7-{-ms-flex-preferred-size:41.66666667%;flex-basis:41.66666667%;max-width:41.66666667%}.flexboxgrid__col-lg-6___21lf8{-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}.flexboxgrid__col-lg-7___3kBG1{-ms-flex-preferred-size:58.33333333%;flex-basis:58.33333333%;max-width:58.33333333%}.flexboxgrid__col-lg-8___afECx{-ms-flex-preferred-size:66.66666667%;flex-basis:66.66666667%;max-width:66.66666667%}.flexboxgrid__col-lg-9___10mdl{-ms-flex-preferred-size:75%;flex-basis:75%;max-width:75%}.flexboxgrid__col-lg-10___1yTfj{-ms-flex-preferred-size:83.33333333%;flex-basis:83.33333333%;max-width:83.33333333%}.flexboxgrid__col-lg-11___3hMRu{-ms-flex-preferred-size:91.66666667%;flex-basis:91.66666667%;max-width:91.66666667%}.flexboxgrid__col-lg-12___1rlAA{-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}.flexboxgrid__col-lg-offset-0___3KM3x{margin-left:0}.flexboxgrid__col-lg-offset-1___KhvqR{margin-left:8.33333333%}.flexboxgrid__col-lg-offset-2___1ZD_z{margin-left:16.66666667%}.flexboxgrid__col-lg-offset-3___2GQVa{margin-left:25%}.flexboxgrid__col-lg-offset-4___1zPZj{margin-left:33.33333333%}.flexboxgrid__col-lg-offset-5___Kj8Iq{margin-left:41.66666667%}.flexboxgrid__col-lg-offset-6___3nun3{margin-left:50%}.flexboxgrid__col-lg-offset-7___YTmn9{margin-left:58.33333333%}.flexboxgrid__col-lg-offset-8___1qG2t{margin-left:66.66666667%}.flexboxgrid__col-lg-offset-9___qd27B{margin-left:75%}.flexboxgrid__col-lg-offset-10___2YScP{margin-left:83.33333333%}.flexboxgrid__col-lg-offset-11___3pPvj{margin-left:91.66666667%}.flexboxgrid__start-lg___ageu9{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;text-align:start}.flexboxgrid__center-lg___3H3SI{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;text-align:center}.flexboxgrid__end-lg___27_fM{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;text-align:end}.flexboxgrid__top-lg___1tWWw{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.flexboxgrid__middle-lg___nocGI{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.flexboxgrid__bottom-lg___IYGks{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.flexboxgrid__around-lg___zZC2C{-ms-flex-pack:distribute;justify-content:space-around}.flexboxgrid__between-lg___2njzk{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.flexboxgrid__first-lg___6dksO{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.flexboxgrid__last-lg___xGBvS{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}}","@import (inline) \"cljsjs/react-flexbox-grid/production/react-flexbox-grid.min.inc.css\";\n\n\na {\n color: #ff4081;\n text-decoration: none;\n}\n\na.no-decor {\n color: inherit !important;\n}\n\na:hover {\n text-decoration: underline;\n}\n\na.no-decor:hover {\n text-decoration: none;\n}\n\nhtml {\n font-family: 'Roboto', sans-serif;\n -webkit-font-smoothing: antialiased;\n}\n\nbody {\n background-color: #efefef;\n}\n\nbody, h1, h2, h3, h4, h5, h6 {\n margin: 0;\n color: rgba(0,0,0,.87);\n}\n\nh1, h2, h3, h4, h5, h6 {\n font-weight: 300;\n}\n\nbody {\n font-size: 15px;\n line-height: 24px;\n}\n\nstrong, b {\n font-weight: 500;\n}\n"],
8 | "names":["a",".no-decor",":hover","html","body","h1","h2","h3","h4","h5","h6","strong","b"]
9 | }
10 |
--------------------------------------------------------------------------------
/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/resources/public/less/styles.main.less:
--------------------------------------------------------------------------------
1 | @import (inline) "cljsjs/react-flexbox-grid/production/react-flexbox-grid.min.inc.css";
2 |
3 |
4 | a {
5 | color: #ff4081;
6 | text-decoration: none;
7 | }
8 |
9 | a.no-decor {
10 | color: inherit !important;
11 | }
12 |
13 | a:hover {
14 | text-decoration: underline;
15 | }
16 |
17 | a.no-decor:hover {
18 | text-decoration: none;
19 | }
20 |
21 | html {
22 | font-family: 'Roboto', sans-serif;
23 | -webkit-font-smoothing: antialiased;
24 | }
25 |
26 | body {
27 | background-color: #efefef;
28 | }
29 |
30 | body, h1, h2, h3, h4, h5, h6 {
31 | margin: 0;
32 | color: rgba(0,0,0,.87);
33 | }
34 |
35 | h1, h2, h3, h4, h5, h6 {
36 | font-weight: 300;
37 | }
38 |
39 | body {
40 | font-size: 15px;
41 | line-height: 24px;
42 | }
43 |
44 | strong, b {
45 | font-weight: 500;
46 | }
47 |
--------------------------------------------------------------------------------
/src/clj/clojurescript_ethereum_example/core.clj:
--------------------------------------------------------------------------------
1 | (ns clojurescript-ethereum-example.core
2 | (:require [clojure.java.io :as io]
3 | [compojure.core :refer [ANY GET PUT POST DELETE defroutes]]
4 | [compojure.route :refer [resources]]
5 | [ring.middleware.defaults :refer [wrap-defaults site-defaults]]
6 | [ring.middleware.gzip :refer [wrap-gzip]]
7 | [ring.middleware.logger :refer [wrap-with-logger]]
8 | [environ.core :refer [env]]
9 | [org.httpkit.server :refer [run-server]])
10 | (:gen-class))
11 |
12 | (def ^:dynamic *server*)
13 |
14 | (defroutes routes
15 | (GET "/js/*" _
16 | {:status 404})
17 | (GET "/*" _
18 | {:status 200
19 | :headers {"Content-Type" "text/html; charset=utf-8"}
20 | :body (io/input-stream (io/resource "public/index.html"))}))
21 |
22 | (def http-handler
23 | (-> routes
24 | (wrap-defaults site-defaults)
25 | wrap-with-logger
26 | wrap-gzip))
27 |
28 | (defn -main [& [port]]
29 | (let [port (Integer. (or port (env :port) 6655))]
30 | (alter-var-root (var *server*)
31 | (constantly (run-server http-handler {:port port :join? false})))))
32 |
33 | (defn stop-server []
34 | (*server*)
35 | (alter-var-root (var *server*) (constantly nil)))
36 |
37 | (defn restart-server []
38 | (stop-server)
39 | (-main))
40 |
41 | (comment
42 | (restart-server)
43 | (-main)
44 | (stop-server))
--------------------------------------------------------------------------------
/src/cljs/clojurescript_ethereum_example/address_select_field.cljs:
--------------------------------------------------------------------------------
1 | (ns clojurescript-ethereum-example.address-select-field
2 | (:require
3 | [cljs-react-material-ui.reagent :as ui]
4 | [clojurescript-ethereum-example.utils :as u]
5 | [re-frame.core :refer [dispatch]]
6 | [reagent.core :as r]))
7 |
8 | (defn address-select-field [addresses selected-address dispatch-vec & [props]]
9 | (let [no-addresses? (empty? addresses)]
10 | [ui/select-field
11 | (r/merge-props
12 | {:value selected-address
13 | :on-change #(dispatch (conj dispatch-vec %3))
14 | :disabled no-addresses?
15 | :style {:text-align :left}
16 | :floating-label-text (if no-addresses? "No Accounts Connected" "Choose your account")}
17 | props)
18 | (for [address addresses]
19 | [ui/menu-item
20 | {:value address
21 | :key address
22 | :primary-text (u/truncate address 25)}])]))
23 |
--------------------------------------------------------------------------------
/src/cljs/clojurescript_ethereum_example/core.cljs:
--------------------------------------------------------------------------------
1 | (ns clojurescript-ethereum-example.core
2 | (:require
3 | [cljs-time.extend]
4 | [cljsjs.material-ui]
5 | [cljsjs.react-flexbox-grid]
6 | [cljsjs.web3]
7 | [clojurescript-ethereum-example.handlers]
8 | [clojurescript-ethereum-example.subs]
9 | [clojurescript-ethereum-example.views :as views]
10 | [print.foo.preloads.devtools]
11 | [re-frame.core :as re-frame]
12 | [reagent.core :as reagent]
13 | ))
14 |
15 |
16 | (enable-console-print!)
17 |
18 | (defn mount-root []
19 | (reagent/render [views/main-panel]
20 | (.getElementById js/document "app")))
21 |
22 | (defn ^:export init []
23 | (re-frame/dispatch-sync [:initialize])
24 | (mount-root))
25 |
--------------------------------------------------------------------------------
/src/cljs/clojurescript_ethereum_example/db.cljs:
--------------------------------------------------------------------------------
1 | (ns clojurescript-ethereum-example.db
2 | (:require [cljs-web3.core :as web3]))
3 |
4 | (def default-db
5 | {:tweets []
6 | :settings {}
7 | :my-addresses []
8 | :accounts {}
9 | :new-tweet {:text ""
10 | :name ""
11 | :address nil
12 | :sending? false}
13 | :web3 (or (aget js/window "web3")
14 | (if goog.DEBUG
15 | (web3/create-web3 "http://localhost:8545/")
16 | (web3/create-web3 "https://morden.infura.io/metamask")))
17 | :provides-web3? (or (aget js/window "web3") goog.DEBUG)
18 | :contract {:name "SimpleTwitter"
19 | :abi nil
20 | :bin nil
21 | :instance nil
22 | :address "0xe70a510651c3394f546f187c441303fd6520a1cd"}})
23 |
--------------------------------------------------------------------------------
/src/cljs/clojurescript_ethereum_example/handlers.cljs:
--------------------------------------------------------------------------------
1 | (ns clojurescript-ethereum-example.handlers
2 | (:require
3 | [ajax.core :as ajax]
4 | [cljs-web3.core :as web3]
5 | [cljs-web3.eth :as web3-eth]
6 | [cljs-web3.personal :as web3-personal]
7 | [cljsjs.web3]
8 | [clojurescript-ethereum-example.db :as db]
9 | [day8.re-frame.http-fx]
10 | [goog.string :as gstring]
11 | [goog.string.format]
12 | [madvas.re-frame.web3-fx]
13 | [re-frame.core :refer [reg-event-db reg-event-fx path trim-v after debug reg-fx console dispatch]]
14 | [clojurescript-ethereum-example.utils :as u]))
15 |
16 | (def interceptors [#_(when ^boolean js/goog.DEBUG debug)
17 | trim-v])
18 |
19 | (def tweet-gas-limit 1000000)
20 |
21 | (comment
22 | (dispatch [:contract/fetch-compiled-code [:contract/deploy-compiled-code]])
23 | (dispatch [:blockchain/unlock-account "0x6fce64667819c82a8bcbb78e294d7b444d2e1a29" "m"])
24 | (dispatch [:blockchain/unlock-account "0xc5aa141d3822c3368df69bfd93ef2b13d1c59aec" "m"])
25 | (dispatch [:blockchain/unlock-account "0xe206f52728e2c1e23de7d42d233f39ac2e748977" "m"])
26 | (dispatch [:blockchain/unlock-account "0x522f9c6b122f4ca8067eb5459c10d03a35798ed9" "m"])
27 | (dispatch [:blockchain/unlock-account "0x43100e355296c4fe3d2c0a356aa4151f1257393b" "m"])
28 | )
29 |
30 | (reg-event-fx
31 | :initialize
32 | (fn [_ _]
33 | (merge
34 | {:db db/default-db
35 | :http-xhrio {:method :get
36 | :uri (gstring/format "./contracts/build/%s.abi"
37 | (get-in db/default-db [:contract :name]))
38 | :timeout 6000
39 | :response-format (ajax/json-response-format {:keywords? true})
40 | :on-success [:contract/abi-loaded]
41 | :on-failure [:log-error]}}
42 | (when (:provides-web3? db/default-db)
43 | {:web3-fx.blockchain/fns
44 | {:web3 (:web3 db/default-db)
45 | :fns [[web3-eth/accounts :blockchain/my-addresses-loaded :log-error]]}}))))
46 |
47 | (reg-event-fx
48 | :blockchain/my-addresses-loaded
49 | interceptors
50 | (fn [{:keys [db]} [addresses]]
51 | {:db (-> db
52 | (assoc :my-addresses addresses)
53 | (assoc-in [:new-tweet :address] (first addresses)))
54 | :web3-fx.blockchain/balances
55 | {:web3 (:web3 db/default-db)
56 | :addresses addresses
57 | :watch? true
58 | :blockchain-filter-opts "latest"
59 | :dispatches [:blockchain/balance-loaded :log-error]}}))
60 |
61 | (reg-event-fx
62 | :contract/abi-loaded
63 | interceptors
64 | (fn [{:keys [db]} [abi]]
65 | (let [web3 (:web3 db)
66 | contract-instance (web3-eth/contract-at web3 abi (:address (:contract db)))]
67 |
68 | {:db (assoc-in db [:contract :instance] contract-instance)
69 |
70 | :web3-fx.contract/events
71 | {:instance contract-instance
72 | :db db
73 | :db-path [:contract :events]
74 | :events [[:on-tweet-added {} {:from-block 0} :contract/on-tweet-loaded :log-error]]}
75 |
76 | :web3-fx.contract/constant-fns
77 | {:instance contract-instance
78 | :fns [[:get-settings :contract/settings-loaded :log-error]]}})))
79 |
80 | (reg-event-db
81 | :contract/on-tweet-loaded
82 | interceptors
83 | (fn [db [tweet]]
84 | (update db :tweets conj (merge (select-keys tweet [:author-address :text :name])
85 | {:date (u/big-number->date-time (:date tweet))
86 | :tweet-key (.toNumber (:tweet-key tweet))}))))
87 |
88 | (reg-event-db
89 | :contract/settings-loaded
90 | interceptors
91 | (fn [db [[max-name-length max-tweet-length]]]
92 | (assoc db :settings {:max-name-length (.toNumber max-name-length)
93 | :max-tweet-length (.toNumber max-tweet-length)})))
94 |
95 | (reg-event-db
96 | :blockchain/balance-loaded
97 | interceptors
98 | (fn [db [balance address]]
99 | (assoc-in db [:accounts address :balance] balance)))
100 |
101 | (reg-event-db
102 | :new-tweet/update
103 | interceptors
104 | (fn [db [key value]]
105 | (assoc-in db [:new-tweet key] value)))
106 |
107 | (reg-event-fx
108 | :new-tweet/send
109 | interceptors
110 | (fn [{:keys [db]} []]
111 | (let [{:keys [name text address]} (:new-tweet db)]
112 | {:web3-fx.contract/state-fn
113 | {:instance (:instance (:contract db))
114 | :web3 (:web3 db)
115 | :db-path [:contract :send-tweet]
116 | :fn [:add-tweet name text
117 | {:from address
118 | :gas tweet-gas-limit}
119 | :new-tweet/confirmed
120 | :log-error
121 | :new-tweet/transaction-receipt-loaded]}})))
122 |
123 | (reg-event-db
124 | :new-tweet/confirmed
125 | interceptors
126 | (fn [db [transaction-hash]]
127 | (assoc-in db [:new-tweet :sending?] true)))
128 |
129 | (reg-event-db
130 | :new-tweet/transaction-receipt-loaded
131 | interceptors
132 | (fn [db [{:keys [gas-used] :as transaction-receipt}]]
133 | (console :log transaction-receipt)
134 | (when (= gas-used tweet-gas-limit)
135 | (console :error "All gas used"))
136 | (assoc-in db [:new-tweet :sending?] false)))
137 |
138 | (reg-event-fx
139 | :contract/fetch-compiled-code
140 | interceptors
141 | (fn [{:keys [db]} [on-success]]
142 | {:http-xhrio {:method :get
143 | :uri (gstring/format "/contracts/build/%s.json"
144 | (get-in db [:contract :name]))
145 | :timeout 6000
146 | :response-format (ajax/json-response-format {:keywords? true})
147 | :on-success on-success
148 | :on-failure [:log-error]}}))
149 |
150 | (reg-event-fx
151 | :contract/deploy-compiled-code
152 | interceptors
153 | (fn [{:keys [db]} [contracts]]
154 | (let [{:keys [abi bin]} (get-in contracts [:contracts (keyword (:name (:contract db)))])]
155 | {:web3-fx.blockchain/fns
156 | {:web3 (:web3 db)
157 | :fns [[web3-eth/contract-new
158 | (js/JSON.parse abi)
159 | {:gas 4500000
160 | :data bin
161 | :from (first (:my-addresses db))}
162 | :contract/deployed
163 | :log-error]]}})))
164 |
165 | (reg-event-fx
166 | :blockchain/unlock-account
167 | interceptors
168 | (fn [{:keys [db]} [address password]]
169 | {:web3-fx.blockchain/fns
170 | {:web3 (:web3 db)
171 | :fns [[web3-personal/unlock-account address password 999999
172 | :blockchain/account-unlocked
173 | :log-error]]}}))
174 |
175 | (reg-event-fx
176 | :blockchain/account-unlocked
177 | interceptors
178 | (fn [{:keys [db]}]
179 | (console :log "Account was unlocked.")
180 | {}))
181 |
182 | (reg-event-fx
183 | :contract/deployed
184 | interceptors
185 | (fn [_ [contract-instance]]
186 | (when-let [address (aget contract-instance "address")]
187 | (console :log "Contract deployed at" address))))
188 |
189 | (reg-event-fx
190 | :log-error
191 | interceptors
192 | (fn [_ [err]]
193 | (console :error err)
194 | {}))
195 |
--------------------------------------------------------------------------------
/src/cljs/clojurescript_ethereum_example/subs.cljs:
--------------------------------------------------------------------------------
1 | (ns clojurescript-ethereum-example.subs
2 | (:require [re-frame.core :refer [reg-sub]]))
3 |
4 | (reg-sub
5 | :db/my-addresses
6 | (fn [db]
7 | (:my-addresses db)))
8 |
9 | (reg-sub
10 | :db/tweets
11 | (fn [db]
12 | (sort-by :date #(compare %2 %1) (:tweets db))))
13 |
14 | (reg-sub
15 | :db/new-tweet
16 | (fn [db]
17 | (:new-tweet db)))
18 |
19 | (reg-sub
20 | :db/settings
21 | (fn [db]
22 | (:settings db)))
23 |
24 | (reg-sub
25 | :new-tweet/selected-address-balance
26 | (fn [db]
27 | (get-in db [:accounts (:address (:new-tweet db)) :balance])))
28 |
--------------------------------------------------------------------------------
/src/cljs/clojurescript_ethereum_example/utils.cljs:
--------------------------------------------------------------------------------
1 | (ns clojurescript-ethereum-example.utils
2 | (:require [cljs-time.coerce :refer [to-date-time to-long to-local-date-time]]
3 | [cljs-time.core :refer [date-time to-default-time-zone]]
4 | [cljs-time.format :as time-format]
5 | [cljs-web3.core :as web3]))
6 |
7 | (defn truncate
8 | "Truncate a string with suffix (ellipsis by default) if it is
9 | longer than specified length."
10 | ([string length]
11 | (truncate string length "..."))
12 | ([string length suffix]
13 | (let [string-len (count string)
14 | suffix-len (count suffix)]
15 | (if (<= string-len length)
16 | string
17 | (str (subs string 0 (- length suffix-len)) suffix)))))
18 |
19 | (defn evt-val [e]
20 | (aget e "target" "value"))
21 |
22 | (defn big-number->date-time [big-num]
23 | (to-date-time (* (.toNumber big-num) 1000)))
24 |
25 | (defn eth [big-num]
26 | (str (web3/from-wei big-num :ether) " ETH"))
27 |
28 | (defn format-date [date]
29 | (time-format/unparse-local (time-format/formatters :rfc822) (to-default-time-zone (to-date-time date))))
30 |
--------------------------------------------------------------------------------
/src/cljs/clojurescript_ethereum_example/views.cljs:
--------------------------------------------------------------------------------
1 | (ns clojurescript-ethereum-example.views
2 | (:require
3 | [re-frame.core :refer [dispatch subscribe]]
4 | [reagent.core :as r]
5 | [clojurescript-ethereum-example.address-select-field :refer [address-select-field]]
6 | [cljs-react-material-ui.reagent :as ui]
7 | [cljs-react-material-ui.core :refer [get-mui-theme color]]
8 | [clojurescript-ethereum-example.utils :as u]))
9 |
10 | (def col (r/adapt-react-class js/ReactFlexboxGrid.Col))
11 | (def row (r/adapt-react-class js/ReactFlexboxGrid.Row))
12 |
13 | (defn- new-tweet-component []
14 | (let [settings (subscribe [:db/settings])
15 | new-tweet (subscribe [:db/new-tweet])
16 | my-addresses (subscribe [:db/my-addresses])
17 | balance (subscribe [:new-tweet/selected-address-balance])]
18 | (fn []
19 | [row
20 | [col {:xs 12 :sm 12 :md 10 :lg 6 :md-offset 1 :lg-offset 3}
21 | [ui/paper {:style {:padding "0 20px 20px"}}
22 | [ui/text-field {:default-value (:name @new-tweet)
23 | :on-change #(dispatch [:new-tweet/update :name (u/evt-val %)])
24 | :name "name"
25 | :max-length (:max-name-length @settings)
26 | :floating-label-text "Your Name"
27 | :style {:width "100%"}}]
28 | [:br]
29 | [ui/text-field {:default-value (:text @new-tweet)
30 | :on-change #(dispatch [:new-tweet/update :text (u/evt-val %)])
31 | :name "tweet"
32 | :max-length (:max-tweet-length @settings)
33 | :floating-label-text "What's happening?"
34 | :style {:width "100%"}}]
35 | [:br]
36 | [address-select-field
37 | @my-addresses
38 | (:address @new-tweet)
39 | [:new-tweet/update :address]]
40 | [:br]
41 | [:h3 "Balance: " (u/eth @balance)]
42 | [:br]
43 | [ui/raised-button
44 | {:secondary true
45 | :disabled (or (empty? (:text @new-tweet))
46 | (empty? (:name @new-tweet))
47 | (empty? (:address @new-tweet))
48 | (:sending? @new-tweet))
49 | :label "Tweet"
50 | :style {:margin-top 15}
51 | :on-touch-tap #(dispatch [:new-tweet/send])}]]]])))
52 |
53 | (defn- tweets-component []
54 | (let [tweets (subscribe [:db/tweets])]
55 | (fn []
56 | [row
57 | [col {:xs 12 :sm 12 :md 10 :lg 6 :md-offset 1 :lg-offset 3}
58 | [ui/paper {:style {:padding 20 :margin-top 20}}
59 | [:h1 "Tweets"]
60 | (for [{:keys [tweet-key name text date author-address]} @tweets]
61 | [:div {:style {:margin-top 20}
62 | :key tweet-key}
63 | [:h3 name]
64 | [:h5 (u/format-date date)]
65 | [:div {:style {:margin-top 5}}
66 | text]
67 | [:h3 {:style {:margin "5px 0 10px"}}
68 | author-address]
69 | [ui/divider]])]]])))
70 |
71 | (defn main-panel []
72 | (let []
73 | (fn []
74 | [ui/mui-theme-provider
75 | {:mui-theme (get-mui-theme {:palette {:primary1-color (color :light-blue500)
76 | :accent1-color (color :amber700)}})}
77 | [:div
78 | [ui/app-bar {:title "Simple Decentralized Twitter"}]
79 | [new-tweet-component]
80 | [tweets-component]]])))
81 |
--------------------------------------------------------------------------------