mirror of
https://github.com/status-im/pluto.git
synced 2025-02-24 00:18:16 +00:00
Improved figwheel layout
This commit is contained in:
parent
1e6ede7ed6
commit
04df08dd6f
4
deps.edn
4
deps.edn
@ -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"}}
|
||||
|
52
examples/resources/extensions/piplette/extension.edn
Normal file
52
examples/resources/extensions/piplette/extension.edn
Normal 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
1
figwheel/README.md
Normal file
@ -0,0 +1 @@
|
||||
This folder contains file needed during figwheel development sessions.
|
@ -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?"]))]}
|
53
figwheel/resources/public/index.html
Normal file
53
figwheel/resources/public/index.html
Normal 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>
|
94
figwheel/src/pluto/figwheel/index.cljs
Normal file
94
figwheel/src/pluto/figwheel/index.cljs
Normal 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)))
|
@ -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
|
@ -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}]})
|
||||
|
@ -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 {} ""])}))))
|
||||
|
Loading…
x
Reference in New Issue
Block a user