Commits (4)
......@@ -55,9 +55,6 @@ body {
overflow: auto;
.ui-page main {
.ui-page .sidebar {
flex-basis: 15rem;
border-right: 0.2px solid lightgrey;
......@@ -76,6 +73,19 @@ body {
font-size: 1.2em;
.main-container {
display: flex;
flex-direction: column;
width: 100%;
.main-container .toolbar {
height: 20px;
text-align: right;
margin-top: 20px;
margin-right: 20px;
.ui-page main {
overflow: auto;
margin-top: 20px;
......@@ -46,24 +46,32 @@
;; ============== Start fetching data ============
(defn load-ontologies []
(geopub.state/add-triples! state
(geopub.state/add-rdf-graph! state
(get-rdf "activitystreams2.ttl"
{:content-type "text/turtle"}))
(geopub.state/add-triples! state
(geopub.state/add-rdf-graph! state
(get-rdf "schema.ttl" {:content-type "text/turtle"})))
(defn cpub-get-data! []
"Get data from CPub server"
;; get public timeline
(geopub.state/add-triples! state
(cpub/get-public-timeline server-url))
(geopub.state/add-rdf-graph! state
(cpub/get-public-timeline server-url))
;; get actor profile
(geopub.state/add-triples! state
(get-rdf actor-id {:auth auth}))
(geopub.state/add-rdf-graph! state
(get-rdf actor-id
{:auth auth
:with-credentials? false}))
;; get actor inbox TODO: figure out outbox from actor object
(geopub.state/add-triples! state (get-rdf (str actor-id "/inbox") {:basic-auth auth}))
(geopub.state/add-rdf-graph! state (get-rdf (str actor-id "/inbox")
{:basic-auth auth
:with-credentials? false
;; get actor outbox
(geopub.state/add-triples! state (get-rdf (str actor-id "/outbox") {:basic-auth auth})))
(geopub.state/add-rdf-graph! state (get-rdf (str actor-id "/outbox")
{:basic-auth auth
:with-credentials? false})))
;; ==================== UI =======================
......@@ -136,4 +144,11 @@
;; (load-ontologies)
;; (geopub.state/reset-graph! state)
;; (geopub.state/add-triples! state (get-rdf "https://www.rubensworks.net/"))
;; (geopub.state/add-rdf-graph! state (get-rdf "https://openengiadina.net/"
;; {:with-credentials? false}))
;; (geopub.state/add-triples! state (get-rdf "https://inqlab.net/"
;; {:with-credentials? false}))
;; (geopub.state/add-triples! state (get-rdf "https://ruben.verborgh.org"
;; {:with-credentials? false}))
......@@ -32,7 +32,7 @@
"Returns a channel holding the content of the public timeline"
(-> server-url
(get-rdf {:with-credentials? false})))
;; (defn post-rdf [data url auth]
......@@ -6,9 +6,11 @@
[rdf.ns :as rdf-ns]
[rdf.n3 :as n3]
[rdf.parse :as rdf-parse]
[reagent.core :as r]
[cljs.core.async :as async :refer [<!]]
[cljs-http.client :as http]
[geopub.ns :as ns :refer [as rdfs schema]]
[reitit.frontend.easy :as rfe])
......@@ -16,44 +18,84 @@
;; Data fetching
;; TODO Parser is capable of parsing streams, but cljs-http returns entire body in one go. Explore using the Streams API (https://developer.mozilla.org/en-US/docs/Web/API/Streams_API). This might also explain the amount of plumbing.
;; TODO Parser is capable of parsing streams, but cljs-http returns entire body in one go. Explore using the Streams API (https://developer.mozilla.org/en-US/docs/Web/API/Streams_API).
(defn- map-content-types
"Some projects and services do not use standard content-types for some reason or an other."
(condp = ct
;; https://www.w3.org/TR/activitystreams-core/#media-type
"application/activity+json" "application/ld+json"
(defn- get-content-type [response]
(get-in response [:headers "content-type"]) ";")))
(defn- parse-http-request [url request & [opts]]
;; TODO Error handling
;; TODO This includes a lot of plumbing. The reason is that the content-type (required by the parser) is in the response and needs to be taken out before creating the parser. The interface for rdf-parse/parse does not seem right. Figure out how to do this nicer.
(let [parser-input (async/chan)
parser-output (async/chan)]
;; take response from request
(async/take! request
(fn [response]
;; put body into parser-input channel
(async/put! parser-input
(:body response)
;; and close the parser-input channel.
#(async/close! parser-input))
;; parse body and pipe to parser-output
(rdf-parse/parse parser-input
(-> response
(get-in [:headers "content-type"])
(clojure.string/split ";")
(defn wrap-request
"Returns a batteries-included HTTP request function coresponding to the given
core client. See client/request"
(-> cljs-http.core/request
;; wrap-json-response
(defn get-rdf
"Do a HTTP Get request and attempt to parse respose as RDF. Returns a channel holding an RDF graph or an error."
[url & [opts]]
(let [request-opts (merge {:headers
{"Accept" (clojure.string/join
", " (rdf-parse/content-types))}
:method :get
:url url}
;; cljs-http does too much post-processing (such as parsing json)
request (wrap-request cljs-http.core/request)
response (<! (request request-opts))]
(if (:success response )
;; Parse RDF triples and add to a new graph
(<! (async/reduce
(fn [graph triple]
;; handle errors
(instance? js/Error triple) triple
(instance? js/Error graph) graph
:else (rdf/graph-add graph triple)))
;; initialize a fresh graph
;; receive parsed triples in channel
(rdf-parse/parse-string (:body response)
:content-type (or (:content-type opts)
(get-content-type response))
:base-iri (str url)
:path (str url))
;; return channel with parsed triples
(defn get-rdf [url & [opts]]
(let [request-opts (merge {:with-credentials? false
:headers {"Accept"
", " (rdf-parse/content-types))}} opts)
request (http/get url request-opts)]
(parse-http-request url request opts)))
:path (str url))))
;; HTTP request failed. Return an error.
(ex-info "HTTP request failed" response)))))
;; Reagent components
......@@ -10,6 +10,17 @@
{:graph (rdf.graph.map/graph)}))
(defn add-rdf-graph!
"Takes RDF graph from a channel and merge with stategraph."
[state chan]
(let [input-graph (<! chan)
;; merge graph in state with input-graph
new-graph (rdf/graph-merge (:graph @state) input-graph)]
;; swap in new graph
(swap! state (fn [state]
(assoc state :graph new-graph))))))
(defn add-triples!
"Takes triples from a channel and adds them to the graph. The input channel must be closed before changes are applied."
[state chan]
......@@ -3,8 +3,10 @@
[rdf.description :as rdf-description]
[reagent.core :as r]
[geopub.data.rdf :refer [description-header-component
[reitit.frontend.easy :as rfe]))
(defn sidebar []
......@@ -24,13 +26,40 @@
(defn go-to-url []
(let [input (r/atom "")]
(fn []
[:input {:type "text"
:on-change #(reset! input (-> % .-target .-value))}]
{:on-click #(rfe/push-state :geopub.core/browse
{:iri (goog.string.urlEncode @input)})}
(defn toolbar [state]
(geopub.data.rdf/get-rdf (rdf/iri-value (get-iri state))))}
"Fetch more data"]])
(defn description-view [state]
(let [iri (get-iri state)
description (r/track #(rdf-description/description iri (:graph @state)))]
[:main [description-component @description]]]))
[toolbar state]
[description-component @description]]]]))
(defn type-view [state]
......@@ -130,7 +130,9 @@
([t] (cond
(instance? Triple t) t
(satisfies? ITripleConvert t) (-as-triple t)))
(satisfies? ITripleConvert t) (-as-triple t)
:else (throw (ex-info "can not cast to triple" t))))
([s p o] (->Triple
;; attempt to cast subject
......@@ -5,6 +5,7 @@
[cljs.core.async :as async :refer [<! >!]]
["rdf-parse" :as rdf-parse]
["stream" :as stream]
;; The rdf-parse.js parser
......@@ -13,11 +14,12 @@
;; Helpers to get data between streams and core.async
(defn put-stream!
"Reads values from a readable stream and puts them on a channel. When the stream ends, the channel is closed."
"Reads values from a readable stream and puts them on a channel. When the stream ends, the channel is closed. Errors will also be put on the channel."
[ch stream]
(.on stream "data" (fn [data]
(.pause stream)
(async/put! ch data #(.resume stream))))
(.on stream "error" #(async/put! ch %))
(.on stream "end" #(async/close! ch))
......@@ -46,20 +48,21 @@
(defn parse
"Takes data from the input-channel and parses them to Triples. Returns a channel that contains triples. input-channel needs to be closed before all triples are emitted."
[input-channel & {:keys [content-type base-iri]}]
[input-stream (new (.-PassThrough stream))
;; cast to rdf/triple by transducing output channel
output-channel (async/chan 1 (map rdf/triple))
;; cast to rdf/triple by transducing output channel, pass errors trough
output-channel (async/chan 1 (map rdf/triple) identity)
opts (clj->js {:contentType (or content-type "text/turtle")
:baseIRI (or base-iri "")})]
......@@ -73,25 +76,12 @@
;; Return the output-channel
;; (defn print-channel
;; "Prints anything that is put in the channel" []
;; (let [c (async/chan)]
;; (go-loop []
;; (let [x (<! c)]
;; (println x)
;; (if x (recur))))
;; c))
;; (def turtle-string "<a> <b> <c>, <d>, <e>.")
;; (go
;; (let [printer (print-channel)
;; input-channel (async/chan)]
;; (async/pipe (parse input-channel) printer)
;; (async/put! input-channel turtle-string)
;; (async/close! input-channel)))
(defn parse-string
"Parse string to triples. Returns a channel containing triples."
[input & {:keys [content-type base-iri]}]
(let [input-chan (async/chan)]
(async/put! input-chan input #(async/close! input-chan))
(parse input-chan
:content-type content-type
:base-iri base-iri)))