├── .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 | --------------------------------------------------------------------------------