Browse Source

initial commit - a messy but working app

mrkvon 2 years ago
commit
c7cfe9d78c
10 changed files with 352 additions and 0 deletions
  1. 21
    0
      LICENSE
  2. 5
    0
      README.md
  3. 45
    0
      index.html
  4. 25
    0
      popup.html
  5. 87
    0
      scripts/graph.js
  6. 2
    0
      scripts/jquery.js
  7. 71
    0
      scripts/main.js
  8. 64
    0
      scripts/rdflib.min.js
  9. 16
    0
      scripts/solid-auth-client.bundle.js
  10. 16
    0
      styles/main.css

+ 21
- 0
LICENSE View File

@@ -0,0 +1,21 @@
1
+MIT License
2
+
3
+Copyright (c) 2018 mrkvon
4
+
5
+Permission is hereby granted, free of charge, to any person obtaining a copy
6
+of this software and associated documentation files (the "Software"), to deal
7
+in the Software without restriction, including without limitation the rights
8
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+copies of the Software, and to permit persons to whom the Software is
10
+furnished to do so, subject to the following conditions:
11
+
12
+The above copyright notice and this permission notice shall be included in all
13
+copies or substantial portions of the Software.
14
+
15
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+SOFTWARE.

+ 5
- 0
README.md View File

@@ -0,0 +1,5 @@
1
+# Solid friends crawler
2
+
3
+Based on https://solid.inrupt.com/docs/app-on-your-lunch-break.
4
+
5
+Crawls `foaf:knows` relationships with breadth-first graph search.

+ 45
- 0
index.html View File

@@ -0,0 +1,45 @@
1
+<!doctype html>
2
+<html lang="en">
3
+  <head>
4
+    <meta charset="utf-8">
5
+    <title>Solid Friends Crawler</title>
6
+    <link rel="stylesheet" href="styles/main.css">
7
+  </head>
8
+  <body>
9
+    <header>
10
+      <h1>Solid Friends Crawler</h1>
11
+    </header>
12
+
13
+    <section>
14
+      <p id="login">
15
+        You are not logged in.
16
+        <button>Log in.</button>
17
+      </p>
18
+      <p id="logout">
19
+        You are logged in as <span id="user"></span>.
20
+        <button>Log out.</button>
21
+      </p>
22
+    </section>
23
+
24
+    <section>
25
+      <p>
26
+        <label for="profile">Profile:</label>
27
+        <input id="profile" value="https://ruben.verborgh.org/profile/#me">
28
+        <button id="view">View</button>
29
+      </p>
30
+
31
+      <canvas width="500" height="500"></canvas>
32
+    </section>
33
+
34
+    <footer>
35
+      based on <a href="https://solid.inrupt.com/docs/app-on-your-lunch-break">Solid lunchbreak tutorial</a>
36
+    </footer>
37
+
38
+    <script src="scripts/jquery.js"></script>
39
+    <script src="scripts/solid-auth-client.bundle.js"></script>
40
+    <script src="scripts/rdflib.min.js"></script>
41
+    <script src="https://d3js.org/d3.v5.min.js"></script>
42
+    <script src="scripts/graph.js"></script>
43
+    <script src="scripts/main.js"></script>
44
+  </body>
45
+</html>

+ 25
- 0
popup.html
File diff suppressed because it is too large
View File


+ 87
- 0
scripts/graph.js View File

@@ -0,0 +1,87 @@
1
+graph = (function (d3) {
2
+
3
+  var canvas = document.querySelector("canvas"),
4
+      context = canvas.getContext("2d"),
5
+      width = canvas.width,
6
+      height = canvas.height;
7
+
8
+  var simulation = d3.forceSimulation()
9
+      .force("link", d3.forceLink()
10
+        .id(function(d) { return d.id; })
11
+        .strength(1.1))
12
+      .force("charge", d3.forceManyBody())
13
+      .force("center", d3.forceCenter(width / 2, height / 2));
14
+
15
+  const graph = {
16
+    nodes: [],
17
+    links: [],
18
+    add({ nodes=[], links=[] }) {
19
+      nodes.forEach(node => this.nodes.push(node));
20
+      links.forEach(link => this.links.push(link));
21
+      simulation.nodes(this.nodes)
22
+        .alphaTarget(1)
23
+        .restart()
24
+    },
25
+    /*
26
+    removeNode(id) {
27
+      console.log(this.links)
28
+      this.links = this.links.filter(link => {
29
+        return !(link.source.id === id || link.target.id === id)
30
+      })
31
+      console.log(this.links.length)
32
+      this.nodes = this.nodes.filter(node => !(node.id === id))
33
+
34
+      simulation.nodes(this.nodes)
35
+        .alphaTarget(1)
36
+        .restart()
37
+    }
38
+    */
39
+  }
40
+
41
+  simulation
42
+        .nodes(graph.nodes)
43
+        .on("tick", ticked);
44
+
45
+  simulation.force("link")
46
+      .links(graph.links);
47
+
48
+  function ticked() {
49
+    context.clearRect(0, 0, width, height);
50
+
51
+    context.beginPath();
52
+    graph.links.forEach(drawLink);
53
+    context.strokeStyle = '#aaa2';
54
+    context.stroke();
55
+
56
+    // context.beginPath();
57
+    graph.nodes.forEach(node => {
58
+      drawNode({ ...node, ...graph.detailNodes[node.id] })
59
+    });
60
+  }
61
+
62
+  function drawLink(d) {
63
+    context.moveTo(d.source.x, d.source.y);
64
+    context.lineTo(d.target.x, d.target.y);
65
+  }
66
+
67
+  function drawNode(d) {
68
+    context.beginPath();
69
+    context.moveTo(d.x + 3, d.y);
70
+    context.arc(d.x, d.y, 3, 0, 2 * Math.PI);
71
+    context.fillStyle = (!d.processed)
72
+      ? 'lightblue'
73
+      : (d.error) ? 'red' : 'blue';
74
+    context.fill();
75
+    context.strokeStyle = context.fillStyle;
76
+    context.stroke();
77
+  }
78
+
79
+  $('#add-node').click(() => {
80
+    graph.add({
81
+      nodes: [{ id: graph.nodes.length }],
82
+      links: [{ source: graph.nodes.length, target: graph.nodes.length - 1 }]
83
+    })
84
+  });
85
+
86
+  return graph
87
+}(d3))

+ 2
- 0
scripts/jquery.js
File diff suppressed because it is too large
View File


+ 71
- 0
scripts/main.js View File

@@ -0,0 +1,71 @@
1
+(function ($, solid, $rdf, graph) {
2
+  const FOAF = $rdf.Namespace('http://xmlns.com/foaf/0.1/');
3
+
4
+  const popupUri = 'popup.html';
5
+
6
+  $('#login button').click(() => solid.auth.popupLogin({ popupUri }));
7
+  $('#logout button').click(() => solid.auth.logout());
8
+
9
+  solid.auth.trackSession(session => {
10
+    const loggedIn = !!session;
11
+    $('#login').toggle(!loggedIn);
12
+    $('#logout').toggle(loggedIn);
13
+    if (session) {
14
+      $('#user').text(session.webId);
15
+      if (!$('#profile').val())
16
+        $('#profile').val(session.webId);
17
+    }
18
+  })
19
+
20
+  $('#view').click(() => {
21
+    const initialId = $('#profile').val();
22
+    bfs(initialId, graph, FOAF);
23
+  })
24
+
25
+}($, solid, $rdf, graph))
26
+
27
+/**
28
+ * perform a breadth-first search
29
+ * @param {string} initialId - webId of the person to start crawling
30
+ * @param {object} graph - object which will be updated with the found nodes
31
+ */
32
+async function bfs(initialId, graph, FOAF) {
33
+  const store = $rdf.graph();
34
+  const fetcher = new $rdf.Fetcher(store);
35
+
36
+  const queue = [initialId];
37
+  const processing = {};
38
+  graph.detailNodes = processing;
39
+
40
+  processing[initialId] = { processed: false, error: false };
41
+  graph.add({ nodes: [{ id: initialId }] })
42
+
43
+  while (queue.length > 0) {
44
+    const idToProcess = queue.shift();
45
+    try {
46
+      await fetcher.load(idToProcess);
47
+      // find friends of the current id
48
+      const friends = store.each($rdf.sym(idToProcess), FOAF('knows'))
49
+      // add found nodes to graph if they're not existent already
50
+      const nodesToAdd = friends
51
+        .map(friend => friend.value)
52
+        .filter(id => !processing.hasOwnProperty(id))
53
+        .map(id => ({ id }))
54
+      const linksToAdd = friends.map(friend => ({ source: idToProcess, target: friend.value }))
55
+      // add found links to graph
56
+      graph.add({ nodes: nodesToAdd, links: linksToAdd })
57
+
58
+      // add found nodes to queue if they're not in processing already
59
+      nodesToAdd.forEach(node => queue.push(node.id))
60
+      // change processed node to processed: true
61
+      processing[idToProcess].processed = true
62
+
63
+      // add found nodes to processing with processed: false, error: false
64
+      nodesToAdd.forEach(node => { processing[node.id] = { processed: false, error: false } })
65
+    } catch (e) {
66
+      processing[idToProcess].processed = true
67
+      processing[idToProcess].error = true
68
+      // graph.removeNode(idToProcess)
69
+    }
70
+  }
71
+}

+ 64
- 0
scripts/rdflib.min.js
File diff suppressed because it is too large
View File


+ 16
- 0
scripts/solid-auth-client.bundle.js
File diff suppressed because it is too large
View File


+ 16
- 0
styles/main.css View File

@@ -0,0 +1,16 @@
1
+body {
2
+  max-width: 800px;
3
+  margin: 0 auto;
4
+}
5
+
6
+body, input, button {
7
+  font: 11pt/1.3 "Helvetica Neue" Arial, sans-serif;
8
+}
9
+
10
+button, label {
11
+  font-weight: bold;
12
+}
13
+
14
+#profile {
15
+  width: 400px;
16
+}