Skip to content

Commit 0eb8442

Browse files
authored
Merge pull request #67 from SolidOS/csv
Add a button to copy a Comma Separated Variables version of the tracker
2 parents 787d504 + 54ee8a5 commit 0eb8442

14 files changed

+13213
-3569
lines changed

Makefile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Wrap TTL files into JS files for bundling with library
22

3-
,all : wf.js trackerSettingsForm.js trackerInstancesForm.js ui.js
3+
# unused: trackerInstancesForm.js
4+
5+
,all : wf.js trackerSettingsForm.js ui.js
46

57
#individualForm.js : individualForm.ttl
68
# (echo 'module.exports = `' ; cat individualForm.ttl; echo '`') > individualForm.js

csvButton.js

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// A Button to copy the state of the tracker in CSV format
2+
// Comma-separated Values
3+
//
4+
// Yes this mixes the layers but that is not all bad if it gets it in one file
5+
// one can look at
6+
7+
import { icons, ns, utils, widgets } from 'solid-ui'
8+
import { store } from 'solid-logic'
9+
10+
export function quoteString(value) {
11+
// https://www.rfc-editor.org/rfc/rfc4180
12+
const stripped = value.replaceAll('\n', ' ')
13+
if (!stripped.includes(',')) {
14+
return stripped
15+
} // If contains comma then put in quotes and double up internal quotes
16+
const quoted = '"' + stripped.replaceAll('"', '""') + '"'
17+
console.log('Quoted: >>>' + quoted + '<<<')
18+
const check = quoted.slice(1,-1).replaceAll('""', '')
19+
if (check.includes('"')) throw new Error('CSV inconsistecy')
20+
return quoted
21+
}
22+
23+
export function csvText(store, tracker) {
24+
25+
function columnText(task, column) {
26+
let thing
27+
if (column.predicate) {
28+
thing = store.any(task, column.predicate)
29+
return thing? thing.value : '--'
30+
}
31+
else if (column.category) {
32+
const types = store.each(task, ns.rdf('type'))
33+
for (const t of types) {
34+
// console.log('@@ checking subclass type: ', t, ' category: ', column.category )
35+
if (store.holds(t, ns.rdfs('subClassOf'), column.category)){
36+
thing = t
37+
}
38+
}
39+
if (!thing) return '?' + utils.label(column.category) // Missing cat OK
40+
// if (!thing) throw new Error('wot no class of category ', column.category)
41+
} else {
42+
throw new Error('wot no pred or cat', column)
43+
}
44+
return utils.label(thing)
45+
}
46+
47+
function taskLine(task) {
48+
return columns.map(column => columnText(task, column))
49+
.map(quoteString)
50+
.join(',')
51+
+ '\n'
52+
}
53+
const stateStore = store.any(tracker, ns.wf('stateStore'))
54+
const tasks = store.each(null, ns.wf('tracker'), tracker, stateStore)
55+
console.log(' CSV: Tasks:', tasks.length)
56+
57+
const columns = [
58+
59+
{ label: 'Name', predicate: ns.dc('title') },
60+
/* { label: 'Description', predicate: ns.wf('description') }, */
61+
62+
/* { label: 'State', category: ns.wf('Task') }
63+
*/
64+
]
65+
const states = store.any(tracker, ns.wf('issueClass')) // Main states are subclasses of this class
66+
console.log(' CSV: States - main superclass:', states)
67+
const stateColumn = { label: 'State', category: states} // better than 'task'
68+
console.log(' CSV: found column from state', stateColumn)
69+
columns.push(stateColumn)
70+
71+
const categories = store.each(tracker, ns.wf('issueCategory'))
72+
console.log(' CSV: Categories : ', categories )
73+
console.log(' CSV: Categories : length: ', categories.length)
74+
console.log(' CSV: Categories : first: ', categories[0])
75+
76+
const classifications = categories
77+
for (const c of classifications){
78+
const column = { label: utils.label(c), category: c}
79+
console.log(' CSV: found column from classifications', column)
80+
columns.push(column) // Classes are different
81+
}
82+
83+
// const propertyList = ns.wf('propertyList')
84+
const form = store.any(tracker, ns.wf('extrasEntryForm'), null, null)
85+
console.log(' CSV: Form : ', form )
86+
87+
if (form) {
88+
const parts = store.any(form, ns.ui('parts'), null, form.doc())
89+
console.log(' CSV: parts : ', parts )
90+
91+
const fields = parts.elements
92+
console.log(' CSV: fields : ', fields )
93+
94+
for (const field of fields) {
95+
const prop = store.any(field,ns.ui('property'))
96+
if (prop) {
97+
const lab = utils.label(prop)
98+
const column = {label: lab, predicate: prop}
99+
console.log(' CSV: found column from form', column)
100+
columns.push(column)
101+
}
102+
}
103+
}
104+
// Put description on the end as it can be long
105+
columns.push({ label: 'Description', predicate: ns.wf('description') })
106+
console.log('Columns: ', columns.length)
107+
const header = columns.map(col => col.label).join(',') + '\n'
108+
console.log('CSV: Header= ', header)
109+
// Order tasks?? By Creation date? By Status?
110+
const body = tasks.map(taskLine).join('')
111+
return header + body
112+
}
113+
114+
export function csvButton (dom, tracker) {
115+
const wrapper = dom.createElement('div')
116+
// Add a button
117+
const button = widgets.button(dom, icons.iconBase + 'noun_Document_998605.svg',
118+
'Copy as CSV', async _event => {
119+
120+
const div = button.parentNode.parentNode
121+
console.log('button gparent div', div)
122+
div.addEventListener('copy', event => {
123+
// alert ('Copy caught');
124+
const csv = csvText(store, tracker);
125+
event.clipboardData.setData("text/plain", csv);
126+
event.clipboardData.setData("text/csv", csv);
127+
alert ('Copy data: ' + csv)
128+
event.preventDefault();
129+
})
130+
})
131+
132+
wrapper.appendChild(button)
133+
return wrapper
134+
}
135+

dev/context.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { DataBrowserContext, PaneRegistry } from "pane-registry";
2+
import { LiveStore, solidLogicSingleton, store } from "solid-logic";
3+
4+
export const context = {
5+
session: {
6+
store: store,
7+
paneRegistry: {
8+
byName: (name) => {
9+
return // longChatPane
10+
}
11+
},
12+
logic : solidLogicSingleton
13+
},
14+
dom: document,
15+
getOutliner: () => null,
16+
};
17+
18+
export const fetcher = store.fetcher;

dev/index.html

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8"/>
5+
<title>issue-pane dev server</title>
6+
<style>
7+
body {
8+
margin: 0;
9+
font-family: sans-serif;
10+
}
11+
h1 {
12+
padding: 0.5rem;
13+
margin: 0;
14+
color: #666;
15+
}
16+
#loginBanner {
17+
display: grid;
18+
padding: 0.5rem;
19+
grid-gap: 1rem;
20+
grid-auto-flow: column;
21+
justify-content: flex-start;
22+
align-items: center;
23+
}
24+
.banner {
25+
margin: 0;
26+
height: 1rem;
27+
width: 100%;
28+
background: repeating-linear-gradient(
29+
-45deg,
30+
#ffffff,
31+
#d8d9d5 20px,
32+
#d41717 20px,
33+
#984646 40px
34+
);
35+
}
36+
</style>
37+
</head>
38+
<body>
39+
<h1>pane under development</h1>
40+
<div>
41+
<div id="webId"></div>
42+
</div>
43+
<div id="loginBanner"></div>
44+
<div class="banner"></div>
45+
<div id="app">Rendering...</div>
46+
</body>
47+
</html>

dev/index.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { sym } from "rdflib";
2+
import { default as pane } from "..";
3+
import { context, fetcher } from "./context";
4+
import { authn, authSession } from "solid-logic";
5+
import * as UI from "solid-ui";
6+
7+
const loginBanner = document.getElementById("loginBanner");
8+
const webId = document.getElementById("webId");
9+
10+
loginBanner.appendChild(UI.login.loginStatusBox(document, null, {}));
11+
12+
async function finishLogin() {
13+
await authSession.handleIncomingRedirect();
14+
const session = authSession;
15+
if (session.info.isLoggedIn) {
16+
// Update the page with the status.
17+
webId.innerHTML = "Logged in as: " + authn.currentUser().uri;
18+
} else {
19+
webId.innerHTML = "";
20+
}
21+
}
22+
23+
finishLogin();
24+
25+
26+
// https://testingsolidos.solidcommunity.net/profile/card#me
27+
// https://timbl.solidcommunity.net/profile/card#me
28+
//
29+
// const targetURIToShow = "https://angelo.veltens.org/profile/card#me";
30+
// const targetURIToShow = "https://testingsolidos.solidcommunity.net/profile/card#me";
31+
// const targetURIToShow = "https://timbl.solidcommunity.net/profile/card#me";
32+
33+
// const targetURIToShow = "https://solidproject.solidcommunity.net/Roadmap/index.ttl#this";
34+
35+
// const targetURIToShow = "https://timbl.com/timbl/Automation/mother/tracker.n3#mother"
36+
37+
const targetURIToShow = "http://localhost:8080/big-tracker.ttl#this"
38+
39+
fetcher.load(targetURIToShow).then(() => {
40+
const app = pane.render(sym(targetURIToShow), context);
41+
document.getElementById("app").replaceWith(app);
42+
});

0 commit comments

Comments
 (0)