Improved figwheel layout

This commit is contained in:
Julien Eluard 2018-06-05 09:44:29 +02:00
parent 1e6ede7ed6
commit 04df08dd6f
No known key found for this signature in database
GPG Key ID: 6FD7DB5437FCBEF6
9 changed files with 272 additions and 54 deletions

View File

@ -4,11 +4,11 @@
reagent {:mvn/version "0.8.0"}
re-frame {:mvn/version "0.10.5"}}
:paths ["src" "resources"]
:aliases {:figwheel {:extra-paths ["target" "figwheel-resources" "examples/src" "examples/resources"]
:aliases {:figwheel {:extra-paths ["target" "figwheel/resources" "figwheel/src"]
:extra-deps {com.bhauman/figwheel-main {:mvn/version "0.1.0-SNAPSHOT"}
com.bhauman/rebel-readline-cljs {:mvn/version "0.1.1"}
binaryage/devtools {:mvn/version "0.9.10"}}
:main-opts ["-m" "figwheel.main" "-c" "pluto.demo" "-r"]}
:main-opts ["-m" "figwheel.main" "-c" "pluto.figwheel.index" "-r"]}
:test-clj {:extra-paths ["test"]
:extra-deps {com.cognitect/test-runner {:git/url "https://github.com/cognitect-labs/test-runner.git"
:sha "5fb4fc46ad0bf2e0ce45eba5b9117a2e89166479"}}

View File

@ -0,0 +1,52 @@
{meta
{:name "Piplette"
:description "A light client for Peepeth"
:documentation "..."}
lifecycle
{:on-activated [@fetch-all-posts]}
events/fetch-all-posts
[@ethereum.logs {:address "0xfa28ec7198028438514b49a3cf353bca5541ce1d"
:topics ["PeepEth()"]
:inputs [{:name :hash :type :string}] ;; Allows to decode transaction data
:on-log [@fetch-ipfs]}] ;; A map of decoded data will be injected
events/fetch-ipfs
(let [{:keys [hash]} properties]
[@ipfs.get {:hash hash
:on-success [@db.append {:path [:all-posts]}]}])
queries/all-posts
[@db.get {:path [:all-posts]
:limit 20}]
views/post
;; TODO get account details
;; handle threads
(let [{:keys [content untrustedAddress untrustedTimestamp parentID]} properties]
[view {}
[view {}
[text {}
untrustedAddress]
[text {}
untrustedTimestamp]]
[text {}
content]])
styles/screen
{:background-color #?(:android :green
:ios :red
:web :white)}
i18n/title
{:en "Peepeth !!"}
hooks/main
[screen {:style @style/screen}
[toolbar {}
[text {}
@title]]
(let [posts @all-posts]
[list {:data posts
:template [@post]}])]}

1
figwheel/README.md Normal file
View File

@ -0,0 +1 @@
This folder contains file needed during figwheel development sessions.

View File

@ -0,0 +1,19 @@
{:meta
{:name "Simple Demo"
:description "A simple demo of extension"
:documentation "Nothing. Just see a text with dynamic random color."}
:hooks/main
{:name ""
:description ""
:view #status/view [:views/main]}
:views/main
[view {}
[text {} "Hello"]
(let [cond? #status/query [:random-boolean]]
(if cond?
[text {:style {:color "green"}}
"World?"]
[text {:style {:color "red"}}
"World?"]))]}

View File

@ -0,0 +1,53 @@
<html>
<head>
<style>
main {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%
}
#selection {
padding: 10px;
}
#extension {
border: 40px solid #ddd;
border-width: 55px 7px;
border-radius: 40px;
margin: 50px auto;
width: 30vh;
height: 50vh;
overflow: hidden;
}
#extension iframe {
border: 0;
height: 100%;
width: 100%
}
</style>
</head>
<body>
<script>
function load(urn) {
pluto.figwheel.index.load_and_render(urn, document.getElementById("frame").contentWindow.document.body.firstChild, document.getElementById("errors"));
}
</script>
<main>
<div id="selection">
Load demo extension from
<button disabled onclick="load('ipfs:QmSKP6f2uUsFq4mk1Afe4ZktxwQifrLb4xRQYNE1LxidKz')">IPFS</button>
<button onclick="load('url:assets/extensions/demo')">HTTP</button>
</div>
<div id="extension">
<iframe id="frame" srcdoc="<body><main></main></body>"></iframe>
</div>
<div id="errors"></div>
</main>
</body>
</html>
<script src="cljs-out/main.js"></script>

View File

@ -0,0 +1,94 @@
(ns pluto.figwheel.index
(:require [clojure.string :as string]
[pluto.components.html :as html]
[pluto.reader :as reader]
[pluto.storage :as storage]
[pluto.storage.http :as http]
[pluto.storage.ipfs :as ipfs]
[reagent.core :as reagent]
[reagent.dom :as dom]
[re-frame.core :as re-frame]
[re-frame.loggers :as re-frame.loggers]
[devtools.core :as devtools]))
(enable-console-print!)
(devtools/install!)
(def warn (js/console.warn.bind js/console))
(re-frame.loggers/set-loggers!
{:warn (fn [& args]
(cond
(= "re-frame: overwriting" (first args)) nil
:else (apply warn args)))})
(defonce do-timer (js/setInterval #(re-frame/dispatch [:random (zero? (rand-int 2))]) 1000))
(re-frame/reg-event-db
:random
(fn [db [_ b]]
(assoc db :random b)))
(re-frame/reg-sub
:random-boolean
:random)
(defn render [h el]
(reagent/render h el))
(defn main-hook
"A simple hook for :hooks/main"
[m]
[:div
(let [{:views/keys [main]} m]
main)])
(defn errors-list [v]
[:div
[:div "Errors"]
(into [:ul]
(for [{:keys [type] :as m} v]
[:li
[:span [:b (str type)] (pr-str (dissoc m :type))]]))])
(defn storage-for [type]
(condp = type
"url" (http/HTTPStorage.)
"ipfs" (ipfs/IPFSStorage. "https://cors.io/?https://gateway.ipfs.io")))
(defn fetch [uri cb]
(let [[type id] (string/split uri ":")]
(storage/fetch
(storage-for type)
{:value id} cb)))
(defn parse [m]
(reader/parse {:components html/components
:capacities
{:queries #{:random-boolean}
:events #{}}
:valid-hooks {:hooks/main
{:extra-properties #{}
:type :view}}}
m))
(defn render-extension [m el el-errors]
(let [{:keys [data errors]} (parse m)]
(when errors
(render (errors-list errors) el-errors))
(render (main-hook data) el)))
(defn read-extension [o el el-errors]
(let [{:keys [data errors]} (reader/read (:content (first o)))]
(render-extension data el el-errors)))
(defn render-result [{:keys [type value]} el el-errors]
(case type
:error (set! (.-innerHTML el-errors) value)
(read-extension value el el-errors)))
(defn ^:export load-and-render
[s el el-errors]
(dom/unmount-component-at-node el)
(dom/unmount-component-at-node el-errors)
(re-frame/clear-subscription-cache!)
(fetch s #(render-result % el el-errors)))

View File

@ -56,34 +56,39 @@
(when-let [v @errors]
{:errors v}))))
(def valid-namespaces #{"extension" "hooks" "views" "events" "queries" "styles" "i18n"})
(def valid-extension-keys #{:meta})
(def valid-namespaces #{"hooks" "views" "events" "queries" "styles" "i18n"})
(defn validate-keys [{:keys [valid-hooks valid-extensions]} s]
(let [keys (set (filter (comp empty? namespace) s))
keys-with-ns (set/difference (set s) keys)
namespaces (set (map namespace keys-with-ns))
extra-namespaces (set/difference namespaces valid-namespaces)
hooks-keys (set/difference (set (filter #(= "hooks" (namespace %)) keys-with-ns)) valid-hooks)
extension-keys (set/difference (set (filter #(= "extension" (namespace %)) keys-with-ns)) valid-extensions)]
(defn validate-keys [{:keys [valid-hooks]} s]
(let [keys (set (filter (comp empty? namespace) s))
extra-extension-keys (set/difference keys valid-extension-keys)
keys-with-ns (set/difference (set s) keys)
namespaces (set (map namespace keys-with-ns))
extra-namespaces (set/difference namespaces valid-namespaces)
hooks-keys (set/difference (set (filter #(= "hooks" (namespace %)) keys-with-ns)) (map key valid-hooks))]
(cond-> nil
(seq keys) (assoc :no-namespace keys)
(seq extra-namespaces) (assoc :invalid-namespaces extra-namespaces)
(seq hooks-keys) (assoc :invalid-hooks hooks-keys)
(seq extension-keys) (assoc :invalid-extensions extension-keys))))
(seq extra-extension-keys) (assoc :type :invalid-extension-keys :value extra-extension-keys)
(seq extra-namespaces) (assoc :type :invalid-namespaces :value extra-namespaces)
(seq hooks-keys) (assoc :type :invalid-hooks :value hooks-keys))))
(defn accumulate-errors [m s]
(update m :errors concat s))
(defn merge-errors [m errors]
(cond-> m
(seq errors) (accumulate-errors errors)))
(declare parse-view)
(defn parse-hiccup-children [opts children]
(reduce #(let [{:keys [data errors]} (parse-view opts %2)]
(cond-> (update %1 :data conj data)
(seq errors) (accumulate-errors errors)))
(merge-errors (update %1 :data conj data)
errors))
{:data []} children))
(defn parse-hiccup-element [{:keys [components] :as opts} o]
;; TODO permissions
;; TODO handle errors
(cond
(or (symbol? o) (utils/primitive? o)) {:data o}
(vector? o)
@ -93,8 +98,7 @@
;; Reduce parsed children to a single map and wrap them in a hiccup element
;; whose component has been translated to the local platform
(update m :data #(apply conj [(or component element) properties] %)))
(nil? component) (accumulate-errors [{:type :unknown-component :element element}])))
:else 3))
(nil? component) (accumulate-errors [{:type :unknown-component :value element}])))))
(defn parse-view [opts o]
(cond
@ -113,35 +117,38 @@
* :errors a collection of errors"
(fn [_ k _] (namespace k)))
(defmethod parse-value "extension" [_ _ v] v)
(defmethod parse-value "hooks" [opts _ v] v)
(defmethod parse-value "views" [opts _ v] (parse-view opts v))
;; TODO extension, events, queries, i18n, styles
(defmethod parse-value :default [_ k _] {:errors [{:type :unknown-element-type :value k}]})
;; TODO events, queries, i18n, styles
(defmethod parse-value :default [_ _ _] {:errors [{:type :unknown-key}]})
(defn merge-parsed-value
"Merge result of parse-value into a map.
:data is updated to its parsed value
:errors are accumulated"
[opts m k v]
(let [{:keys [data errors]} (parse-value opts k v)]
(cond-> (assoc-in m [:data k] data)
(seq errors) (accumulate-errors (map #(assoc % :key k) errors)))))
(if (namespace k)
(let [{:keys [data errors]} (parse-value opts k v)] ;; TODO skip extension, hooks
(merge-errors (assoc-in m [:data k] data)
(map #(assoc % :key k) errors)))
m))
;; TODO use specs for validation? Depends on opts, need to perform replacement during walk
;; specs would allow to generate UIs and help with testing
;; specs would allow to generate UIs and help with testing
(defn parse
"Parse an extension definition map as encapsulated in :data key of the map returned by read.
`opts` is a map defining:
* `valid-hooks` a set of valid hooks
* `valid-hooks` a map of valid hook definitions
Returns a map defining:
* :data a map
* :permissions a vector of required permissions
* :errors a vector of errors map triggered during parse"
* :errors a vector of errors maps triggered during parse"
[opts m]
(let [errors (validate-keys opts (keys m))]
(cond-> (reduce-kv #(merge-parsed-value opts %1 %2 %3) {} m)
(seq errors) (accumulate-errors (map (fn [error] {:type (key error) :value (val error)}) errors)))))
(merge-errors (reduce-kv #(merge-parsed-value opts %1 %2 %3) {} m)
errors)))
;; TODO replace all refs to query, event and view here

View File

@ -3,7 +3,7 @@
;; Record used to track references identified by a set of predefined tag literal
(defrecord Reference [tag value])
(defrecord Reference [type value])
(defn create [type value] (Reference. type value))
@ -11,11 +11,14 @@
(defmulti resolve
""
(fn [tag _] tag))
(fn [m {:keys [type]}] type))
(defmethod resolve :view [_ value] {:data value}) ;; TODO properly handle local refs and globally whitelisted refs
(defmethod resolve :view [m {:keys [value]}]
(if-let [view (get m value)]
{:data view}
{:errors [{:type :unknown-view :value value}]})) ;; TODO properly handle local refs and globally whitelisted refs
;; TODO prevent infinite loops due to self refs ?
;; TODO other reference types
(defmethod resolve :default [tag value] {:errors [{:type :unknown-reference-tag :tag tag :value value}]})
(defmethod resolve :default [m {:keys [type value]}] {:errors [{:type :unknown-reference-type :value value}]})

View File

@ -31,13 +31,10 @@
\"World\"]))]}"))))
(deftest validate-keys
(is (= {:no-namespace #{:key}} (reader/validate-keys {} #{:views/id :key})))
(is (= {:invalid-namespaces #{"typo"}} (reader/validate-keys {} #{:typo/id})))
(is (= {:invalid-extensions #{:extension/unknown}}
(reader/validate-keys {:valid-extensions #{:extension/meta :extension/lifecycle}}
#{:extension/meta :extension/unknown})))
(is (= {:invalid-hooks #{:hooks/unknown}}
(reader/validate-keys {:valid-hooks #{:hooks/main}} #{:hooks/main :hooks/unknown}))))
(is (= {:type :invalid-extension-keys, :value #{:key}} (reader/validate-keys {} #{:views/id :key})))
(is (= {:type :invalid-namespaces :value #{"typo"}} (reader/validate-keys {} #{:typo/id})))
(is (= {:type :invalid-hooks :value #{:hooks/unknown}}
(reader/validate-keys {:valid-hooks {:hooks/main {}}} #{:hooks/main :hooks/unknown}))))
(deftest parse-hiccup-children
(is (= {:data (list [:text {} ""])} (reader/parse-hiccup-children {:components {'text :text}} (list ['text {} ""])))))
@ -45,28 +42,20 @@
(deftest parse
(is (= {} (reader/parse {} {})))
(is (= {:data {:views/main ['text {} "Hello"]}
:errors (list {:type :unknown-component :element 'text :key :views/main})}
:errors (list {:type :unknown-component :value 'text :key :views/main})}
(reader/parse {} {:views/main ['text {} "Hello"]})))
(is (= {:data {:views/main [:text {} "Hello"]}}
(reader/parse {:components {'text :text}} {:views/main ['text {} "Hello"]})))
(is (= {:data {:views/main [:text {} "Hello"]}}
(reader/parse {:components {'text :text}} {:views/main ['text {} "Hello"]}))))
#_
(deftest parse-references
(is (= {:data {:views/main [:pluto.reader-test/main]}} (reader/parse {} {:views/main (Reference. :view [::main])}))))
(not (= {:data {:views/main nil}} :errors ({:type :unsupported-type, :value "string", :key :views/main}) {:data {:views/main nil}, :errors ({:type :unsupported-type, :value "string", :key :views/main})}))
(deftest parse-blocks
(is (= {:data {:views/main [blocks/let-block {:env {'s "Hello"}} [:text {} 's]]}}
(reader/parse {:components {'text :text}} {:views/main (list 'let ['s "Hello"] ['text {} 's])})))
(is (= {:data {:views/main [blocks/when-block {:test 'cond} [:text {} ""]]}}
(reader/parse {:components {'text :text}} {:views/main (list 'when 'cond ['text {} ""])})))
(is (= {:data {:views/main [blocks/when-block {:test 'cond} [:text {} ""]]}}
(reader/parse {:components {'text :text}} {:views/main (list 'when "string" ['text {} ""])})))
#_
(is (= nil (reader/parse {:components {'text :text}} {:views/main (list 'let ['cond? (Reference. :query [::query])] ['text])})))
#_
(is (= nil (reader/parse {:components {'text :text}} {:views/main (list 'let ['cond? (Reference. :query [::query])]
(list 'when 'cond?
['text {} "World"]))}))))
(is (= {:data {:views/main nil}
:errors '({:type :unsupported-type :value "string" :key :views/main})}
(reader/parse {:components {'text :text}} {:views/main (list 'when "string" ['text {} ""])}))))