|
| 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 | + |
0 commit comments