issue-pane 2.4.10 → 2.4.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintrc CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
+ "root": true,
2
3
  "env": {
3
4
  "browser": true,
4
5
  "es6": true,
5
6
  "node": true
6
7
  },
7
- "extends": "standard",
8
8
  "globals": {
9
9
  "Atomics": "readonly",
10
10
  "SharedArrayBuffer": "readonly"
@@ -14,5 +14,6 @@
14
14
  "argsIgnorePattern": "^_",
15
15
  "varsIgnorePattern": "^_"
16
16
  }]
17
- }
17
+ },
18
+ "extends": ["eslint:recommended", "plugin:import/recommended"]
18
19
  }
@@ -15,7 +15,6 @@ jobs:
15
15
  strategy:
16
16
  matrix:
17
17
  node-version:
18
- - 12.x
19
18
  - 14.x
20
19
  - 16.x
21
20
  steps:
package/.nvmrc CHANGED
@@ -1 +1 @@
1
- v12.7.0
1
+ v16.14.0
package/board.js CHANGED
@@ -10,10 +10,9 @@
10
10
  * @returns dom:Element
11
11
  */
12
12
 
13
- import * as UI from 'solid-ui'
13
+ import { ns, rdf, utils, widgets } from 'solid-ui'
14
14
  import { store } from 'solid-logic'
15
- const ns = UI.ns
16
- const $rdf = UI.rdf
15
+ const $rdf = rdf
17
16
 
18
17
  export function board (dom, columnValues, renderItem, options) {
19
18
  const board = dom.createElement('div')
@@ -27,7 +26,7 @@ export function board (dom, columnValues, renderItem, options) {
27
26
  const mainRow = table.appendChild(dom.createElement('tr'))
28
27
  columnValues.forEach(x => {
29
28
  const cell = headerRow.appendChild(dom.createElement('th'))
30
- cell.textContent = UI.utils.label(x, true) // Initial capital
29
+ cell.textContent = utils.label(x, true) // Initial capital
31
30
  cell.subject = x
32
31
  cell.style = 'margin: 0.3em; padding: 0.5em 1em; font-treatment: bold; font-size: 120%;'
33
32
 
@@ -44,7 +43,7 @@ export function board (dom, columnValues, renderItem, options) {
44
43
  }
45
44
 
46
45
  if (options.columnDropHandler) {
47
- UI.widgets.makeDropTarget(column, droppedURIHandler)
46
+ widgets.makeDropTarget(column, droppedURIHandler)
48
47
  }
49
48
  })
50
49
 
@@ -57,7 +56,7 @@ export function board (dom, columnValues, renderItem, options) {
57
56
  const classes = store.each(item, ns.rdf('type'))
58
57
  const catColors = classes.map(cat => store.any(cat, ns.ui('backgroundColor'))).filter(c => c)
59
58
 
60
- table.appendChild(UI.widgets.personTR(dom, null, item))
59
+ table.appendChild(widgets.personTR(dom, null, item))
61
60
  table.subject = item
62
61
  table.style = 'margin: 1em;' // @@ use style.js
63
62
  const backgroundColor = catColors[0] || store.any(category, ns.ui('backgroundColor'))
@@ -76,7 +75,7 @@ export function board (dom, columnValues, renderItem, options) {
76
75
  const actualRenderItem = renderItem || options.renderItem || defaultRenderItem
77
76
  function localRenderItem (subject) {
78
77
  const ele = actualRenderItem(subject)
79
- UI.widgets.makeDraggable(ele, subject)
78
+ widgets.makeDraggable(ele, subject)
80
79
  ele.subject = subject
81
80
  return ele
82
81
  }
@@ -88,7 +87,7 @@ export function board (dom, columnValues, renderItem, options) {
88
87
  items = items.filter(options.filter)
89
88
  }
90
89
  const sortedItems = sortedBy(items, sortBy, now, true)
91
- UI.utils.syncTableToArrayReOrdered(col, sortedItems, localRenderItem)
90
+ utils.syncTableToArrayReOrdered(col, sortedItems, localRenderItem)
92
91
  }
93
92
  }
94
93
 
package/issue.js CHANGED
@@ -5,20 +5,33 @@ import { authn, store } from 'solid-logic'
5
5
  import { newIssueForm } from './newIssue'
6
6
 
7
7
  const $rdf = rdf
8
+ const kb = store
8
9
 
9
10
  const SET_MODIFIED_DATES = false
10
11
 
12
+ export const TASK_ICON = icons.iconBase + 'noun_17020_gray-tick.svg'
13
+ export const OPEN_TASK_ICON = icons.iconBase + 'noun_17020_sans-tick.svg'
14
+ export const CLOSED_TASK_ICON = icons.iconBase + 'noun_17020.svg'
15
+
11
16
  function complain (message, context) {
12
17
  console.warn(message)
13
18
  context.paneDiv.appendChild(widgets.errorMessageBlock(context.dom, message))
14
19
  }
15
20
 
21
+ export function isOpen (issue) {
22
+ const types = kb.findTypeURIs(issue)
23
+ return !!types[ns.wf('Open').uri]
24
+ }
25
+
26
+ export function iconForIssue (issue) {
27
+ return isOpen(issue) ? TASK_ICON : CLOSED_TASK_ICON
28
+ }
16
29
  export function getState (issue, classification) {
17
- const tracker = store.the(issue, ns.wf('tracker'), null, issue.doc())
18
- const states = store.any(tracker, ns.wf('issueClass'))
30
+ const tracker = kb.the(issue, ns.wf('tracker'), null, issue.doc())
31
+ const states = kb.any(tracker, ns.wf('issueClass'))
19
32
  classification = classification || states
20
- const types = store.each(issue, ns.rdf('type'))
21
- .filter(ty => store.holds(ty, ns.rdfs('subClassOf'), classification))
33
+ const types = kb.each(issue, ns.rdf('type'))
34
+ .filter(ty => kb.holds(ty, ns.rdfs('subClassOf'), classification))
22
35
  if (types.length !== 1) {
23
36
  // const initialState = kb.any(tracker, ns.wf('initialState')) No do NOT default
24
37
  // if (initialState) return initialState
@@ -27,26 +40,27 @@ export function getState (issue, classification) {
27
40
  return types[0]
28
41
  }
29
42
 
30
- export function renderIssueCard (issue, context) {
31
- function getBackgroundColor () {
32
- const classes = store.each(issue, ns.rdf('type')) // @@ pick cats in order then state
33
- const catColors = classes.map(cat => store.any(cat, ns.ui('backgroundColor'))).filter(c => !!c)
43
+ export function getBackgroundColorFromTypes (issue) {
44
+ const classes = kb.each(issue, ns.rdf('type')) // @@ pick cats in order then state
45
+ const catColors = classes.map(cat => kb.any(cat, ns.ui('backgroundColor'))).filter(c => !!c)
34
46
 
35
- if (catColors.length) return catColors[0].value // pick first one
36
- return null
37
- }
47
+ if (catColors.length) return catColors[0].value // pick first one
48
+ return null
49
+ }
50
+
51
+ export function renderIssueCard (issue, context) {
38
52
  function refresh () {
39
- const backgroundColor = getBackgroundColor() || 'white'
53
+ const backgroundColor = getBackgroundColorFromTypes(issue) || 'white'
40
54
  card.style.backgroundColor = backgroundColor
41
55
  editButton.style.backgroundColor = backgroundColor // Override white from style sheet
42
56
  }
43
57
  const dom = context.dom
44
- const uncategorized = !getBackgroundColor() // This is a suspect issue. Prompt to delete it
58
+ const uncategorized = !getBackgroundColorFromTypes(issue) // This is a suspect issue. Prompt to delete it
45
59
 
46
60
  const card = dom.createElement('div')
47
61
  const table = card.appendChild(dom.createElement('table'))
48
62
  table.style.width = '100%'
49
- const options = { draggable: false } // Let the baord make th ewhole card draggable
63
+ const options = { draggable: false } // Let the board make the whole card draggable
50
64
  table.appendChild(widgets.personTR(dom, null, issue, options))
51
65
  table.subject = issue
52
66
  card.style = 'border-radius: 0.4em; border: 0.05em solid grey; margin: 0.3em;'
@@ -66,7 +80,7 @@ export function renderIssueCard (issue, context) {
66
80
  if (uncategorized) {
67
81
  const deleteButton = widgets.deleteButtonWithCheck(dom, buttonsCell, 'issue', async function () { // noun?
68
82
  try {
69
- await store.updater.update(store.connectedStatements(issue))
83
+ await kb.updater.update(kb.connectedStatements(issue))
70
84
  } catch (err) {
71
85
  complain(`Unable to delete issue: ${err}`, context)
72
86
  }
@@ -100,18 +114,24 @@ export function exposeOverlay (subject, context) {
100
114
  overlay.firstChild.style.overflow = 'auto' // was scroll
101
115
  }
102
116
 
117
+ function renderSpacer (dom, backgroundColor) {
118
+ const spacer = dom.createElement('div')
119
+ spacer.setAttribute('style', 'height: 1em; margin: 0.5em;') // spacer and placeHolder
120
+ spacer.style.backgroundColor = backgroundColor // try that
121
+ return spacer
122
+ }
123
+
103
124
  export function renderIssue (issue, context) {
104
125
  // Don't bother changing the last modified dates of things: save time
105
- function setModifiedDate (subj, store, doc) {
126
+ function setModifiedDate (subj, kb, doc) {
106
127
  if (SET_MODIFIED_DATES) {
107
128
  if (!getOption(tracker, 'trackLastModified')) return
108
- let deletions = store.statementsMatching(issue, ns.dct('modified'))
109
- deletions = deletions.concat(
110
- store.statementsMatching(issue, ns.wf('modifiedBy'))
111
- )
129
+ const deletions = kb.statementsMatching(issue, ns.dct('modified'))
130
+ .concat(kb.statementsMatching(issue, ns.wf('modifiedBy'))
131
+ )
112
132
  const insertions = [$rdf.st(issue, ns.dct('modified'), new Date(), doc)]
113
133
  if (me) insertions.push($rdf.st(issue, ns.wf('modifiedBy'), me, doc))
114
- store.updater.update(deletions, insertions, function (_uri, _ok, _body) {})
134
+ kb.updater.update(deletions, insertions, function (_uri, _ok, _body) {})
115
135
  }
116
136
  }
117
137
 
@@ -123,7 +143,7 @@ export function renderIssue (issue, context) {
123
143
  return pre
124
144
  }
125
145
 
126
- const timestring = function () {
146
+ function timestring () {
127
147
  const now = new Date()
128
148
  return '' + now.getTime()
129
149
  // http://www.w3schools.com/jsref/jsref_obj_date.asp
@@ -144,52 +164,50 @@ export function renderIssue (issue, context) {
144
164
  }
145
165
  function getOption (tracker, option) {
146
166
  // eg 'allowSubIssues'
147
- const opt = store.any(tracker, ns.ui(option))
167
+ const opt = kb.any(tracker, ns.ui(option))
148
168
  return !!(opt && opt.value)
149
169
  }
150
170
 
151
171
  function setPaneStyle () {
152
- const types = store.findTypeURIs(issue)
153
- let mystyle = 'padding: 0.5em 1.5em 1em 1.5em; '
154
- let backgroundColor = null
155
- for (const uri in types) {
156
- backgroundColor = store.any(
157
- store.sym(uri),
158
- store.sym('http://www.w3.org/ns/ui#backgroundColor')
159
- )
160
- if (backgroundColor) break
161
- }
162
- backgroundColor = backgroundColor ? backgroundColor.value : '#eee' // default grey
163
- mystyle += 'background-color: ' + backgroundColor + '; '
172
+ const backgroundColor = getBackgroundColorFromTypes(issue) || '#eee' // default grey
173
+ const mystyle0 = 'padding: 0.5em 1.5em 1em 1.5em; border: 0.7em;'
174
+ const mystyle = mystyle0 + 'border-color: ' + backgroundColor + '; '
164
175
  issueDiv.setAttribute('style', mystyle)
176
+ issueDiv.style.backgroundColor = 'white'
165
177
  }
166
178
 
179
+ /// ////////////// Body of renderIssue
180
+
167
181
  const dom = context.dom
168
182
  // eslint-disable-next-line no-use-before-define
169
- const tracker = store.the(issue, ns.wf('tracker'), null, issue.doc())
183
+ const tracker = kb.the(issue, ns.wf('tracker'), null, issue.doc())
170
184
  if (!tracker) throw new Error('No tracker')
171
185
  // eslint-disable-next-line no-use-before-define
172
- const stateStore = store.any(tracker, ns.wf('stateStore'))
186
+ const stateStore = kb.any(tracker, ns.wf('stateStore'))
173
187
  const store = issue.doc()
174
188
 
175
189
  const issueDiv = dom.createElement('div')
176
190
  const me = authn.currentUser()
191
+ const backgroundColor = getBackgroundColorFromTypes(issue) || 'white'
177
192
 
178
193
  setPaneStyle()
179
194
 
180
195
  authn.checkUser() // kick off async operation
181
196
 
182
- const states = store.any(tracker, ns.wf('issueClass'))
197
+ const iconButton = issueDiv.appendChild(widgets.button(dom, iconForIssue(issue)))
198
+ widgets.makeDraggable(iconButton, issue) // Drag me wherever you need to do stuff with this issue
199
+
200
+ const states = kb.any(tracker, ns.wf('issueClass'))
183
201
  if (!states) { throw new Error('This tracker ' + tracker + ' has no issueClass') }
184
202
  const select = widgets.makeSelectForCategory(
185
203
  dom,
186
- store,
204
+ kb,
187
205
  issue,
188
206
  states,
189
207
  stateStore,
190
208
  function (ok, body) {
191
209
  if (ok) {
192
- setModifiedDate(store, store, store)
210
+ setModifiedDate(store, kb, store)
193
211
  widgets.refreshTree(issueDiv)
194
212
  } else {
195
213
  console.log('Failed to change state:\n' + body)
@@ -198,18 +216,18 @@ export function renderIssue (issue, context) {
198
216
  )
199
217
  issueDiv.appendChild(select)
200
218
 
201
- const cats = store.each(tracker, ns.wf('issueCategory')) // zero or more
202
- for (let i = 0; i < cats.length; i++) {
219
+ const cats = kb.each(tracker, ns.wf('issueCategory')) // zero or more
220
+ for (const cat of cats) {
203
221
  issueDiv.appendChild(
204
222
  widgets.makeSelectForCategory(
205
223
  dom,
206
- store,
224
+ kb,
207
225
  issue,
208
- cats[i],
226
+ cat,
209
227
  stateStore,
210
228
  function (ok, body) {
211
229
  if (ok) {
212
- setModifiedDate(store, store, store)
230
+ setModifiedDate(store, kb, store)
213
231
  widgets.refreshTree(issueDiv)
214
232
  } else {
215
233
  console.log('Failed to change category:\n' + body)
@@ -253,40 +271,26 @@ export function renderIssue (issue, context) {
253
271
  wf:Task :creationForm core:coreIsueForm .
254
272
  `
255
273
  const CORE_ISSUE_FORM = ns.wf('coreIsueForm')
256
- $rdf.parse(coreIssueFormText, store, CORE_ISSUE_FORM.doc().uri, 'text/turtle')
257
- widgets.appendForm(
274
+ $rdf.parse(coreIssueFormText, kb, CORE_ISSUE_FORM.doc().uri, 'text/turtle')
275
+ const form = widgets.appendForm(
258
276
  dom,
259
- issueDiv,
277
+ null, // was: container
260
278
  {},
261
279
  issue,
262
280
  CORE_ISSUE_FORM,
263
281
  stateStore,
264
282
  complainIfBad
265
283
  )
266
-
267
- // Descriptions can be long and are stored local to the issue
268
- /*
269
- issueDiv.appendChild(
270
- widgets.makeDescription(
271
- dom,
272
- kb,
273
- issue,
274
- ns.wf('description'),
275
- store,
276
- function (ok, body) {
277
- if (ok) setModifiedDate(store, kb, store)
278
- else console.log('Failed to change description:\n' + body)
279
- }
280
- )
281
- ) */
284
+ issueDiv.appendChild(form)
285
+ form.style.backgroundColor = backgroundColor
282
286
 
283
287
  // Assigned to whom?
284
288
 
285
- const assignments = store.statementsMatching(issue, ns.wf('assignee'))
289
+ const assignments = kb.statementsMatching(issue, ns.wf('assignee'))
286
290
  if (assignments.length > 1) {
287
291
  say('Weird, was assigned to more than one person. Fixing ..')
288
292
  const deletions = assignments.slice(1)
289
- store.updater.update(deletions, [], function (uri, ok, body) {
293
+ kb.updater.update(deletions, [], function (uri, ok, body) {
290
294
  if (ok) {
291
295
  say('Now fixed.')
292
296
  } else {
@@ -299,20 +303,16 @@ export function renderIssue (issue, context) {
299
303
  // Anyone assigned to any issue we know about
300
304
 
301
305
  async function getPossibleAssignees () {
302
- let devs = []
303
- const devGroups = store.each(issue, ns.wf('assigneeGroup'))
304
- for (let i = 0; i < devGroups.length; i++) {
305
- const group = devGroups[i]
306
- await store.fetcher.load()
307
- devs = devs.concat(store.each(group, ns.vcard('member')))
308
- }
306
+ const devGroups = kb.each(issue, ns.wf('assigneeGroup'))
307
+ await kb.fetcher.load(devGroups) // Load them all
308
+ const groupDevs = devGroups.map(group => kb.each(group, ns.vcard('member'), null, group.doc())).flat()
309
309
  // Anyone who is a developer of any project which uses this tracker
310
- const proj = store.any(null, ns.doap('bug-database'), tracker) // What project?
310
+ const proj = kb.any(null, ns.doap('bug-database'), tracker) // What project?
311
311
  if (proj) {
312
- await store.fetcher.load(proj)
313
- devs = devs.concat(store.each(proj, ns.doap('developer')))
312
+ await kb.fetcher.load(proj)
314
313
  }
315
- return devs
314
+ const projectDevs = proj ? kb.each(proj, ns.doap('developer')) : []
315
+ return groupDevs.concat(projectDevs)
316
316
  }
317
317
 
318
318
  // Super issues first - like parent directories .. maybe use breadcrums from?? @@
@@ -324,7 +324,7 @@ export function renderIssue (issue, context) {
324
324
  getPossibleAssignees().then(devs => {
325
325
  if (devs.length) {
326
326
  devs.forEach(function (person) {
327
- store.fetcher.lookUpThing(person)
327
+ kb.fetcher.lookUpThing(person)
328
328
  }) // best effort async for names etc
329
329
  const opts = {
330
330
  // 'mint': '** Add new person **',
@@ -339,14 +339,14 @@ export function renderIssue (issue, context) {
339
339
  issueDiv.appendChild(
340
340
  widgets.makeSelectForOptions(
341
341
  dom,
342
- store,
342
+ kb,
343
343
  issue,
344
344
  ns.wf('assignee'),
345
345
  devs,
346
346
  opts,
347
347
  store,
348
348
  function (ok, body) {
349
- if (ok) setModifiedDate(store, store, store)
349
+ if (ok) setModifiedDate(store, kb, store)
350
350
  else console.log('Failed to change assignee:\n' + body)
351
351
  }
352
352
  )
@@ -354,27 +354,36 @@ export function renderIssue (issue, context) {
354
354
  }
355
355
  })
356
356
 
357
- /* The trees of super issues and subissues
357
+ /* The trees of super-issues and sub-issues
358
358
  */
359
- let subIssuePanel
359
+ function supersOver (issue, stack) {
360
+ stack = stack || []
361
+ const sup = kb.any(null, ns.wf('dependent'), issue, issue.doc())
362
+ if (sup) return supersOver(sup, [sup].concat(stack))
363
+ return stack
364
+ }
360
365
  if (getOption(tracker, 'allowSubIssues')) {
361
- if (!subIssuePanel) {
362
- subIssuePanel = issueDiv.appendChild(dom.createElement('div'))
363
- subIssuePanel.style = 'margin: 1em; padding: 1em;'
364
- }
366
+ const subIssuePanel = issueDiv.appendChild(dom.createElement('div'))
367
+ subIssuePanel.style = 'margin: 1em; padding: 1em;'
365
368
 
366
369
  subIssuePanel.appendChild(dom.createElement('h4')).textContent = 'Super Issues'
367
370
  const listOfSupers = subIssuePanel.appendChild(dom.createElement('div'))
371
+ listOfSupers.style.display = 'flex'
368
372
  listOfSupers.refresh = function () {
369
- utils.syncTableToArrayReOrdered(listOfSupers, store.each(null, ns.wf('dependent'), issue), renderSubIssue)
373
+ // const supers = kb.each(null, ns.wf('dependent'), issue, issue.doc())
374
+ const supers = supersOver(issue)
375
+ utils.syncTableToArrayReOrdered(listOfSupers, supers, renderSubIssue)
370
376
  }
371
377
  listOfSupers.refresh()
372
378
 
373
379
  // Sub issues
374
380
  subIssuePanel.appendChild(dom.createElement('h4')).textContent = 'Sub Issues'
375
381
  const listOfSubs = subIssuePanel.appendChild(dom.createElement('div'))
382
+ listOfSubs.style.display = 'flex'
383
+ listOfSubs.style.flexDirection = 'reverse' // Or center
376
384
  listOfSubs.refresh = function () {
377
- utils.syncTableToArrayReOrdered(listOfSubs, store.each(issue, ns.wf('dependent')), renderSubIssue)
385
+ const subs = kb.each(issue, ns.wf('dependent'), null, issue.doc())
386
+ utils.syncTableToArrayReOrdered(listOfSubs, subs, renderSubIssue)
378
387
  }
379
388
  listOfSubs.refresh()
380
389
 
@@ -387,7 +396,7 @@ export function renderIssue (issue, context) {
387
396
  b.addEventListener(
388
397
  'click',
389
398
  function (_event) {
390
- subIssuePanel.insertBefore(newIssueForm(dom, store, tracker, issue, listOfSubs.refresh), b.nextSibling) // Pop form just after button
399
+ subIssuePanel.insertBefore(newIssueForm(dom, kb, tracker, issue, listOfSubs.refresh), b.nextSibling) // Pop form just after button
391
400
  },
392
401
  false
393
402
  )
@@ -396,7 +405,7 @@ export function renderIssue (issue, context) {
396
405
  issueDiv.appendChild(dom.createElement('br'))
397
406
 
398
407
  // Extras are stored centrally to the tracker
399
- const extrasForm = store.any(tracker, ns.wf('extrasEntryForm'))
408
+ const extrasForm = kb.any(tracker, ns.wf('extrasEntryForm'))
400
409
  if (extrasForm) {
401
410
  widgets.appendForm(
402
411
  dom,
@@ -407,14 +416,14 @@ export function renderIssue (issue, context) {
407
416
  stateStore,
408
417
  complainIfBad
409
418
  )
419
+ // issueDiv.appendChild(renderSpacer(backgroundColor))
410
420
  }
411
421
 
412
422
  // Comment/discussion area
413
423
 
414
- const spacer = issueDiv.appendChild(dom.createElement('tr'))
415
- spacer.setAttribute('style', 'height: 1em') // spacer and placeHolder
424
+ const spacer = issueDiv.appendChild(renderSpacer(dom, backgroundColor))
416
425
 
417
- const template = store.anyValue(tracker, ns.wf('issueURITemplate'))
426
+ const template = kb.anyValue(tracker, ns.wf('issueURITemplate'))
418
427
  /*
419
428
  var chatDocURITemplate = kb.anyValue(tracker, ns.wf('chatDocURITemplate')) // relaive to issue
420
429
  var chat
@@ -427,20 +436,21 @@ export function renderIssue (issue, context) {
427
436
  if (template) {
428
437
  messageStore = issue.doc() // for now. Could go deeper
429
438
  } else {
430
- messageStore = store.any(tracker, ns.wf('messageStore'))
431
- if (!messageStore) messageStore = store.any(tracker, ns.wf('stateStore'))
432
- store.sym(messageStore.uri + '#' + 'Chat' + timestring()) // var chat =
439
+ messageStore = kb.any(tracker, ns.wf('messageStore'))
440
+ if (!messageStore) messageStore = kb.any(tracker, ns.wf('stateStore'))
441
+ kb.sym(messageStore.uri + '#' + 'Chat' + timestring()) // var chat =
433
442
  }
434
443
 
435
- store.fetcher.nowOrWhenFetched(messageStore, function (ok, body, _xhr) {
444
+ kb.fetcher.nowOrWhenFetched(messageStore, function (ok, body, _xhr) {
436
445
  if (!ok) {
437
446
  const er = dom.createElement('p')
438
447
  er.textContent = body // @@ use nice error message
439
448
  issueDiv.insertBefore(er, spacer)
440
449
  } else {
441
- const discussion = messageArea(dom, store, issue, messageStore)
450
+ const discussion = messageArea(dom, kb, issue, messageStore)
442
451
  issueDiv.insertBefore(discussion, spacer)
443
- }
452
+ issueDiv.insertBefore(renderSpacer(dom, backgroundColor), discussion)
453
+ } // Not sure why e stuck this in upwards rather than downwards
444
454
  })
445
455
 
446
456
  // Draggable attachment list
@@ -448,23 +458,22 @@ export function renderIssue (issue, context) {
448
458
  attachmentHint.innerHTML = `<h4>Attachments</h4>
449
459
  <p>Drag files, emails,
450
460
  web pages onto the paper clip, or click the file upload button.</p>`
451
- let uploadFolderURI
452
- if (issue.uri.endsWith('/index.ttl#this')) { // This has a whole folder to itself
453
- uploadFolderURI = issue.uri.slice(0, 14) + 'Files/' // back to slash
454
- } else { // like state.ttl#Iss1587852322438
455
- uploadFolderURI = issue.dir().uri + 'Files/' + issue.uri.split('#')[1] + '/' // New folder for issue in file with others
456
- }
461
+ const uploadFolderURI =
462
+ issue.uri.endsWith('/index.ttl#this') // This has a whole folder to itself
463
+ ? issue.uri.slice(0, 14) + 'Files/' // back to slash
464
+ : issue.dir().uri + 'Files/' + issue.uri.split('#')[1] + '/' // New folder for issue in file with others
465
+
457
466
  widgets.attachmentList(dom, issue, issueDiv, {
458
467
  doc: stateStore,
459
468
  promptIcon: icons.iconBase + 'noun_25830.svg',
460
- uploadFolder: store.sym(uploadFolderURI), // Allow local files to be uploaded
469
+ uploadFolder: kb.sym(uploadFolderURI), // Allow local files to be uploaded
461
470
  predicate: ns.wf('attachment')
462
471
  })
463
472
 
464
473
  // Delete button to delete the issue
465
474
  const deleteButton = widgets.deleteButtonWithCheck(dom, issueDiv, 'issue', async function () {
466
475
  try {
467
- await store.updater.update(store.connectedStatements(issue))
476
+ await kb.updater.update(kb.connectedStatements(issue))
468
477
  } catch (err) {
469
478
  complain(`Unable to delete issue: ${err}`, context)
470
479
  }
@@ -482,7 +491,7 @@ export function renderIssue (issue, context) {
482
491
  'click',
483
492
  async function (_event) {
484
493
  try {
485
- await store.fetcher.load(messageStore, { force: true, clearPreviousData: true })
494
+ await kb.fetcher.load(messageStore, { force: true, clearPreviousData: true })
486
495
  } catch (err) {
487
496
  alert(err)
488
497
  return
package/issuePane.js CHANGED
@@ -5,7 +5,7 @@
5
5
  **
6
6
  */
7
7
 
8
- import * as UI from 'solid-ui'
8
+ import { create, login, ns, icons, rdf, tabs, table, utils, widgets } from 'solid-ui'
9
9
  import { store, authn } from 'solid-logic'
10
10
  import { board } from './board' // @@ will later be in solid-UI
11
11
  import { renderIssue, renderIssueCard, getState, exposeOverlay } from './issue'
@@ -14,9 +14,8 @@ import { newIssueForm } from './newIssue'
14
14
  import { trackerSettingsFormText } from './trackerSettingsForm.js'
15
15
  // import { trackerInstancesFormText } from './trackerInstancesForm.js'
16
16
 
17
- const $rdf = UI.rdf
18
- const ns = UI.ns
19
- const widgets = UI.widgets
17
+ const $rdf = rdf
18
+ const kb = store
20
19
 
21
20
  // const MY_TRACKERS_ICON = UI.icons.iconBase + 'noun_Document_998605.svg'
22
21
  // const TRACKER_ICON = UI.icons.iconBase + 'noun_list_638112'
@@ -24,7 +23,7 @@ const widgets = UI.widgets
24
23
 
25
24
  const OVERFLOW_STYLE = 'position: fixed; z-index: 100; top: 1.51em; right: 2em; left: 2em; bottom:1.5em; border: 0.1em grey; overflow: scroll;'
26
25
  export default {
27
- icon: UI.icons.iconBase + 'noun_122196.svg', // was: js/panes/issue/tbl-bug-22.png
26
+ icon: icons.iconBase + 'noun_122196.svg', // was: js/panes/issue/tbl-bug-22.png
28
27
  // noun_list_638112 is a checklist document
29
28
  // noun_Document_998605.svg is a stack of twpo checklists
30
29
  // noun_97839.svg is a ladybug
@@ -36,10 +35,10 @@ export default {
36
35
 
37
36
  // Does the subject deserve an issue pane?
38
37
  label: function (subject, _context) {
39
- const t = store.findTypeURIs(subject)
38
+ const t = kb.findTypeURIs(subject)
40
39
  if (
41
40
  t['http://www.w3.org/2005/01/wf/flow#Task'] ||
42
- store.holds(subject, UI.ns.wf('tracker'))
41
+ kb.holds(subject, ns.wf('tracker'))
43
42
  ) { return 'issue' } // in case ontology not available
44
43
  if (t['http://www.w3.org/2005/01/wf/flow#Tracker']) return 'tracker'
45
44
  // Later: Person. For a list of things assigned to them,
@@ -61,8 +60,7 @@ export default {
61
60
  return Promise.all(updates)
62
61
  }
63
62
 
64
- var kb = context.session.store
65
- const ns = UI.ns
63
+ const kb = context.session.store
66
64
  let stateStore
67
65
  if (options.newInstance) {
68
66
  stateStore = kb.sym(options.newInstance.doc().uri + '_state.ttl')
@@ -94,13 +92,13 @@ export default {
94
92
  try {
95
93
  await updateMany([], ins)
96
94
  } catch (err) {
97
- return UI.widgets.complain(context, 'Error writing tracker configuration: ' + err)
95
+ return widgets.complain(context, 'Error writing tracker configuration: ' + err)
98
96
  }
99
97
  /*
100
98
  try {
101
99
  await kb.updater.updateMany([], kb.statementsMatching(undefined, undefined, undefined, stateStore))
102
100
  } catch (err) {
103
- return UI.widgets.complain(context, 'Error writing tracker state file: ' + err)
101
+ return widgets.complain(context, 'Error writing tracker state file: ' + err)
104
102
  }
105
103
  */
106
104
  const dom = context.dom
@@ -126,7 +124,7 @@ export default {
126
124
 
127
125
  function complain (message) {
128
126
  console.warn(message)
129
- paneDiv.appendChild(UI.widgets.errorMessageBlock(dom, message))
127
+ paneDiv.appendChild(widgets.errorMessageBlock(dom, message))
130
128
  }
131
129
 
132
130
  function complainIfBad (ok, message) {
@@ -140,83 +138,78 @@ export default {
140
138
  ** This is would not be needed if our quey language
141
139
  ** allowed is to query ardf Collection membership.
142
140
  */
143
- async function fixSubClasses (kb, tracker) {
141
+ async function fixSubClasses (kb, tracker) { // 20220228
144
142
  async function checkOneSuperclass (klass) {
145
143
  const collection = kb.any(klass, ns.owl('disjointUnionOf'), null, doc)
146
144
  if (!collection) throw new Error(`Classification ${klass} has no disjointUnionOf`)
147
145
  if (!collection.elements) throw new Error(`Classification ${klass} has no array`)
148
146
  const needed = new Set(collection.elements.map(x => x.uri))
149
- const existing = new Set(kb.each(null, ns.rdfs('subClassOf'), klass, doc)
150
- .map(x => x.uri))
151
- for (const sub of existing) {
152
- if (!needed.has(sub)) {
153
- deletables.push($rdf.st(kb.sym(sub), ns.rdfs('subClassOf'), klass, doc))
154
- }
155
- }
156
- for (const sub of needed) {
157
- if (!existing.has(sub)) {
158
- insertables.push($rdf.st(kb.sym(sub), ns.rdfs('subClassOf'), klass, doc))
159
- }
160
- }
147
+ const existing = new Set(kb.each(null, ns.rdfs('subClassOf'), klass, doc).map(x => x.uri))
148
+
149
+ const superfluous = [...existing].filter(sub => !needed.has(sub))
150
+ const deleteActions = superfluous.map(sub => { return { action: 'delete', st: $rdf.st(kb.sym(sub), ns.rdfs('subClassOf'), klass, doc) } })
151
+
152
+ const missing = [...needed].filter(sub => !existing.has(sub))
153
+ const insertActions = missing.map(sub => { return { action: 'insert', st: $rdf.st(kb.sym(sub), ns.rdfs('subClassOf'), klass, doc) } })
154
+ return deleteActions.concat(insertActions)
161
155
  }
162
156
  const doc = tracker.doc()
163
157
  const states = kb.any(tracker, ns.wf('issueClass'))
164
- const cats = kb.each(tracker, ns.wf('issueCategory'))
165
- var insertables = []
166
- var deletables = []
167
- cats.push(states)
158
+ const cats = kb.each(tracker, ns.wf('issueCategory')).concat([states])
159
+ let damage = [] // to make totally functionaly need to deal with map over async.
168
160
  for (const klass of cats) {
169
- await checkOneSuperclass(klass)
161
+ damage = damage.concat(await checkOneSuperclass(klass))
170
162
  }
171
- const damage = insertables.length + deletables.length
172
- if (damage) {
173
- alert(`Internal error: s${damage} subclasses inconsistences!`)
174
- /*
163
+ if (damage.length) {
164
+ const insertables = damage.filter(fix => fix.action === 'insert').map(fix => fix.st)
165
+ const deletables = damage.filter(fix => fix.action === 'delete').map(fix => fix.st)
166
+ // alert(`Internal error: s${damage} subclasses inconsistences!`)
167
+ console.log('Damage:', damage)
175
168
  if (confirm(`Fix ${damage} inconsistent subclasses in tracker config?`)) {
176
169
  await kb.updater.update(deletables, insertables)
177
- */
170
+ }
178
171
  }
179
172
  }
180
173
 
181
174
  /** /////////////////////////// Board
182
175
  */
183
176
  function renderBoard (tracker, klass) {
184
- const states = store.any(tracker, ns.wf('issueClass'))
177
+ const states = kb.any(tracker, ns.wf('issueClass'))
185
178
  klass = klass || states // default to states
186
179
  const doingStates = klass.sameTerm(states)
187
180
 
188
181
  // These are states we will show by default: the open issues.
189
- const stateArray = store.any(klass, ns.owl('disjointUnionOf'))
182
+ const stateArray = kb.any(klass, ns.owl('disjointUnionOf'))
190
183
  if (!stateArray) {
191
184
  return complain(`Configuration error: state ${states} does not have substates`)
192
185
  }
193
186
  let columnValues = stateArray.elements
194
187
  if (doingStates && columnValues.length > 2 // and there are more than two
195
188
  ) { // strip out closed states
196
- columnValues = columnValues.filter(state => store.holds(state, ns.rdfs('subClassOf'), ns.wf('Open')) || state.sameTerm(ns.wf('Open')))
189
+ columnValues = columnValues.filter(state => kb.holds(state, ns.rdfs('subClassOf'), ns.wf('Open')) || state.sameTerm(ns.wf('Open')))
197
190
  }
198
191
 
199
192
  async function columnDropHandler (issue, newState) {
200
193
  const currentState = getState(issue, klass)
201
- const tracker = store.the(issue, ns.wf('tracker'), null, issue.doc())
202
- const stateStore = store.any(tracker, ns.wf('stateStore'))
194
+ const tracker = kb.the(issue, ns.wf('tracker'), null, issue.doc())
195
+ const stateStore = kb.any(tracker, ns.wf('stateStore'))
203
196
 
204
197
  if (newState.sameTerm(currentState)) {
205
- // alert('Same state ' + UI.utils.label(currentState)) // @@ remove
198
+ // alert('Same state ' + utils.label(currentState)) // @@ remove
206
199
  return
207
200
  }
208
201
  try {
209
- await store.updater.update(
202
+ await kb.updater.update(
210
203
  [$rdf.st(issue, ns.rdf('type'), currentState, stateStore)],
211
204
  [$rdf.st(issue, ns.rdf('type'), newState, stateStore)])
212
205
  } catch (err) {
213
- UI.widgets.complain(context, 'Unable to change issue state: ' + err)
206
+ widgets.complain(context, 'Unable to change issue state: ' + err)
214
207
  }
215
208
  boardDiv.refresh() // reorganize board to match the new reality
216
209
  }
217
210
 
218
211
  function isOpen (issue) {
219
- const types = store.findTypeURIs(issue)
212
+ const types = kb.findTypeURIs(issue)
220
213
  return !!types[ns.wf('Open').uri]
221
214
  }
222
215
 
@@ -234,15 +227,15 @@ export default {
234
227
  /** ////////////// Table
235
228
  */
236
229
  function tableRefreshButton (stateStore, tableDiv) {
237
- const refreshButton = widgets.button(dom, UI.icons.iconBase + 'noun_479395.svg',
230
+ const refreshButton = widgets.button(dom, icons.iconBase + 'noun_479395.svg',
238
231
  'refresh table', async _event => {
239
232
  try {
240
- await store.fetcher.load(stateStore, { force: true, clearPreviousData: true })
233
+ await kb.fetcher.load(stateStore, { force: true, clearPreviousData: true })
241
234
  } catch (err) {
242
235
  alert(err)
243
236
  return
244
237
  }
245
- UI.widgets.refreshTree(tableDiv)
238
+ widgets.refreshTree(tableDiv)
246
239
  })
247
240
  return refreshButton
248
241
  }
@@ -253,10 +246,10 @@ export default {
253
246
  query.pat.optional.push(clause)
254
247
  return clause
255
248
  }
256
- const states = store.any(subject, ns.wf('issueClass'))
257
- const cats = store.each(tracker, ns.wf('issueCategory')) // zero or more
249
+ const states = kb.any(subject, ns.wf('issueClass'))
250
+ const cats = kb.each(tracker, ns.wf('issueCategory')) // zero or more
258
251
  const vars = ['issue', 'state', 'created']
259
- var query = new $rdf.Query(UI.utils.label(subject))
252
+ const query = new $rdf.Query(utils.label(subject))
260
253
  for (let i = 0; i < cats.length; i++) {
261
254
  vars.push('_cat_' + i)
262
255
  }
@@ -278,7 +271,7 @@ export default {
278
271
  clause.add(v['_cat_' + i], ns.rdfs('subClassOf'), cats[i])
279
272
  }
280
273
 
281
- const propertyList = store.any(tracker, ns.wf('propertyList')) // List of extra properties
274
+ const propertyList = kb.any(tracker, ns.wf('propertyList')) // List of extra properties
282
275
  if (propertyList) {
283
276
  const properties = propertyList.elements
284
277
  for (let p = 0; p < properties.length; p++) {
@@ -294,10 +287,10 @@ export default {
294
287
  }
295
288
 
296
289
  const selectedStates = {}
297
- const possible = store.each(undefined, ns.rdfs('subClassOf'), states)
290
+ const possible = kb.each(undefined, ns.rdfs('subClassOf'), states)
298
291
  possible.forEach(function (s) {
299
292
  if (
300
- store.holds(s, ns.rdfs('subClassOf'), ns.wf('Open')) ||
293
+ kb.holds(s, ns.rdfs('subClassOf'), ns.wf('Open')) ||
301
294
  s.sameTerm(ns.wf('Open'))
302
295
  ) {
303
296
  selectedStates[s.uri] = true
@@ -310,7 +303,7 @@ export default {
310
303
  exposeOverlay(subject, context)
311
304
  }
312
305
 
313
- const tableDiv = UI.table(dom, {
306
+ const tableDiv = table(dom, {
314
307
  query: query,
315
308
  keyVariable: '?issue', // Charactersic of row
316
309
  sortBy: '?created', // By default, sort by date
@@ -321,7 +314,7 @@ export default {
321
314
  '?state': { initialSelection: selectedStates, label: 'Status' }
322
315
  }
323
316
  })
324
- const stateStore = store.any(subject, ns.wf('stateStore'))
317
+ const stateStore = kb.any(subject, ns.wf('stateStore'))
325
318
  tableDiv.appendChild(tableRefreshButton(stateStore, tableDiv))
326
319
  return tableDiv
327
320
  }
@@ -341,14 +334,14 @@ export default {
341
334
  }
342
335
  const issuePane = context.session.paneRegistry.byName('issue')
343
336
  const relevantPanes = [issuePane]
344
- UI.create.newThingUI(creationContext, context, relevantPanes) // Have to pass panes down newUI
337
+ create.newThingUI(creationContext, context, relevantPanes) // Have to pass panes down newUI
345
338
  return creationDiv
346
339
  }
347
340
 
348
341
  function renderInstances (theClass) {
349
342
  const instancesDiv = dom.createElement('div')
350
343
  const context = { dom, div: instancesDiv, noun: 'tracker' }
351
- UI.login.registrationList(context, { public: true, private: true, type: theClass }).then(_context2 => {
344
+ login.registrationList(context, { public: true, private: true, type: theClass }).then(_context2 => {
352
345
  instancesDiv.appendChild(renderCreationControl(instancesDiv))
353
346
  /* // keep this code in case we need a form
354
347
  const InstancesForm = ns.wf('TrackerInstancesForm')
@@ -362,12 +355,21 @@ export default {
362
355
  }
363
356
  function renderSettings (tracker) {
364
357
  const settingsDiv = dom.createElement('div')
358
+ const states = kb.any(tracker, ns.wf('issueClass'))
359
+ const views = [tableView, states] // Possible default views
360
+ .concat(kb.each(tracker, ns.wf('issueCategory')))
361
+ const box = settingsDiv.appendChild(dom.createElement('div'))
362
+ const lhs = widgets.renderNameValuePair(dom, kb, box, null, 'Default view') // @@ use a predicate?
363
+ lhs.appendChild(widgets.makeSelectForOptions(dom, kb, tracker,
364
+ ns.wf('defaultView'),
365
+ views, {}, tracker.doc()))
366
+
365
367
  // A registration control allows the to record this tracker in their type index
366
368
  const context = { dom, div: settingsDiv, noun: 'tracker' }
367
- UI.login.registrationControl(context, tracker, ns.wf('Tracker')).then(_context2 => {
369
+ login.registrationControl(context, tracker, ns.wf('Tracker')).then(_context2 => {
368
370
  const settingsForm = ns.wf('TrackerSettingsForm')
369
371
  const text = trackerSettingsFormText
370
- $rdf.parse(text, store, settingsForm.doc().uri, 'text/turtle')
372
+ $rdf.parse(text, kb, settingsForm.doc().uri, 'text/turtle')
371
373
  widgets.appendForm(dom, settingsDiv, {}, tracker, settingsForm,
372
374
  tracker.doc(), complainIfBad)
373
375
  })
@@ -385,76 +387,103 @@ export default {
385
387
  ele.appendChild(renderSettings(tracker))
386
388
  } else if (object.sameTerm(instancesView)) {
387
389
  ele.appendChild(renderInstances(ns.wf('Tracker')))
388
- } else if ((store.holds(tracker, ns.wf('issueCategory'), object)) ||
389
- (store.holds(tracker, ns.wf('issueClass'), object))) {
390
+ } else if ((kb.holds(tracker, ns.wf('issueCategory'), object)) ||
391
+ (kb.holds(tracker, ns.wf('issueClass'), object))) {
390
392
  ele.appendChild(renderBoard(tracker, object))
391
393
  } else {
392
394
  throw new Error('Unexpected tab type: ' + object)
393
395
  }
394
396
  }
395
- const states = store.any(tracker, ns.wf('issueClass'))
397
+ const states = kb.any(tracker, ns.wf('issueClass'))
396
398
  const items = [instancesView, tableView, states]
397
- .concat(store.each(tracker, ns.wf('issueCategory')))
399
+ .concat(kb.each(tracker, ns.wf('issueCategory')))
398
400
  items.push(settingsView)
399
- const selectedTab = tableView
401
+ const selectedTab = kb.any(tracker, ns.wf('defaultView'), null, tracker.doc()) || tableView
400
402
  const options = { renderMain, items, selectedTab }
401
403
 
402
404
  // Add stuff to the ontologies which we believe but they don't say
403
405
  const doc = instancesView.doc()
404
- store.add(instancesView, ns.rdfs('label'), 'My Trackers', doc) // @@ squatting on wf ns
405
- store.add(settingsView, ns.rdfs('label'), 'Settings', doc) // @@ squatting on wf ns
406
- store.add(states, ns.rdfs('label'), 'By State', doc) // @@ squatting on wf ns
406
+ kb.add(instancesView, ns.rdfs('label'), 'My Trackers', doc) // @@ squatting on wf ns
407
+ kb.add(settingsView, ns.rdfs('label'), 'Settings', doc) // @@ squatting on wf ns
408
+ kb.add(states, ns.rdfs('label'), 'By State', doc) // @@ squatting on wf ns
407
409
 
408
- const tabs = UI.tabs.tabWidget(options)
409
- return tabs
410
+ const myTabs = tabs.tabWidget(options)
411
+ return myTabs
412
+ }
413
+
414
+ async function renderSingleIssue () {
415
+ tracker = kb.any(subject, ns.wf('tracker'))
416
+ if (!tracker) throw new Error('This issue ' + subject + 'has no tracker')
417
+
418
+ // Much data is in the tracker instance, so wait for the data from it
419
+ try {
420
+ const _xhrs = await context.session.store.fetcher.load(tracker.doc())
421
+ } catch (err) {
422
+ const msg = 'Failed to load tracker config ' + tracker.doc() + ': ' + err
423
+ return complain(msg)
424
+ }
425
+
426
+ const stateStore = kb.any(tracker, ns.wf('stateStore'))
427
+ if (!stateStore) {
428
+ return complain('Tracker has no state store: ' + tracker)
429
+ }
430
+ try {
431
+ await context.session.store.fetcher.load(subject)
432
+ } catch (err) {
433
+ return complain('Failed to load issue state ' + stateStore + ': ' + err)
434
+ }
435
+ paneDiv.appendChild(renderIssue(subject, context))
436
+ updater.addDownstreamChangeListener(stateStore, function () {
437
+ widgets.refreshTree(paneDiv)
438
+ }) // Live update
410
439
  }
411
440
 
412
441
  async function renderTracker () {
413
442
  function showNewIssue (issue) {
414
- UI.widgets.refreshTree(paneDiv)
443
+ widgets.refreshTree(paneDiv)
415
444
  exposeOverlay(issue, context)
416
- b.disabled = false // https://stackoverflow.com/questions/41176582/enable-disable-a-button-in-pure-javascript
445
+ newIssueButton.disabled = false // https://stackoverflow.com/questions/41176582/enable-disable-a-button-in-pure-javascript
417
446
  }
418
447
  tracker = subject
419
448
 
420
449
  try {
421
- await fixSubClasses(store, tracker)
450
+ await fixSubClasses(kb, tracker)
422
451
  } catch (err) {
423
452
  console.log('@@@ Error fixing subclasses in config: ' + err)
424
453
  }
425
454
 
426
- const states = store.any(subject, ns.wf('issueClass'))
455
+ const states = kb.any(subject, ns.wf('issueClass'))
427
456
  if (!states) throw new Error('This tracker has no issueClass')
428
- const stateStore = store.any(subject, ns.wf('stateStore'))
457
+ const stateStore = kb.any(subject, ns.wf('stateStore'))
429
458
  if (!stateStore) throw new Error('This tracker has no stateStore')
430
459
 
431
- authn.checkUser() // kick off async operation
460
+ // const me = await authn.currentUser()
432
461
 
433
462
  const h = dom.createElement('h2')
434
463
  h.setAttribute('style', 'font-size: 150%')
435
464
  paneDiv.appendChild(h)
436
- const classLabel = UI.utils.label(states)
465
+ const classLabel = utils.label(states)
437
466
  h.appendChild(dom.createTextNode(classLabel + ' list')) // Use class label @@I18n
438
467
 
439
468
  // New Issue button
440
- var b = dom.createElement('button')
469
+ const newIssueButton = dom.createElement('button')
441
470
  const container = dom.createElement('div')
442
- b.setAttribute('type', 'button')
443
- b.setAttribute('style', 'padding: 0.3em; font-size: 100%; margin: 0.5em;')
444
- container.appendChild(b)
471
+ newIssueButton.setAttribute('type', 'button')
472
+ newIssueButton.setAttribute('style', 'padding: 0.3em; font-size: 100%; margin: 0.5em;')
473
+ container.appendChild(newIssueButton)
445
474
  paneDiv.appendChild(container)
446
475
  const img = dom.createElement('img')
447
- img.setAttribute('src', UI.icons.iconBase + 'noun_19460_green.svg')
476
+ img.setAttribute('src', icons.iconBase + 'noun_19460_green.svg')
448
477
  img.setAttribute('style', 'width: 1em; height: 1em; margin: 0.2em;')
449
- b.appendChild(img)
478
+ newIssueButton.appendChild(img)
450
479
  const span = dom.createElement('span')
451
480
  span.innerHTML = 'New ' + classLabel
452
- b.appendChild(span)
453
- b.addEventListener(
481
+ newIssueButton.appendChild(span)
482
+ newIssueButton.addEventListener(
454
483
  'click',
455
484
  function (_event) {
456
- b.disabled = true
457
- container.appendChild(newIssueForm(dom, store, tracker, null, showNewIssue))
485
+ newIssueButton.disabled = true
486
+ container.appendChild(newIssueForm(dom, kb, tracker, null, showNewIssue))
458
487
  },
459
488
  false
460
489
  )
@@ -474,7 +503,7 @@ export default {
474
503
  } else {
475
504
  console.log('No table refresh function?!')
476
505
  }
477
- paneDiv.appendChild(newTrackerButton(subject))
506
+ paneDiv.appendChild(newTrackerButton(subject, context))
478
507
  updater.addDownstreamChangeListener(stateStore, tableDiv.refresh) // Live update
479
508
  })
480
509
  .catch(function (err) {
@@ -490,73 +519,30 @@ export default {
490
519
  const settingsView = ns.wf('SettingsView')
491
520
  const instancesView = ns.wf('InstancesView')
492
521
 
493
- const updater = store.updater
494
- const t = store.findTypeURIs(subject)
522
+ const updater = kb.updater
523
+ const t = kb.findTypeURIs(subject)
495
524
  let tracker
496
525
 
497
526
  // Whatever we are rendering, lets load the ontology
498
- const flowOntology = UI.ns.wf('').doc()
499
- if (!store.holds(undefined, undefined, undefined, flowOntology)) {
527
+ const flowOntology = ns.wf('').doc()
528
+ if (!kb.holds(undefined, undefined, undefined, flowOntology)) {
500
529
  // If not loaded already
501
- $rdf.parse(require('./wf.js'), store, flowOntology.uri, 'text/turtle') // Load ontology directly
530
+ $rdf.parse(require('./wf.js'), kb, flowOntology.uri, 'text/turtle') // Load ontology directly
502
531
  }
503
- const userInterfaceOntology = UI.ns.ui('').doc()
504
- if (!store.holds(undefined, undefined, undefined, userInterfaceOntology)) {
532
+ const userInterfaceOntology = ns.ui('').doc()
533
+ if (!kb.holds(undefined, undefined, undefined, userInterfaceOntology)) {
505
534
  // If not loaded already
506
- $rdf.parse(require('./ui.js'), store, userInterfaceOntology.uri, 'text/turtle') // Load ontology directly
535
+ $rdf.parse(require('./ui.js'), kb, userInterfaceOntology.uri, 'text/turtle') // Load ontology directly
507
536
  }
508
537
 
509
538
  // Render a single issue
510
539
  if (
511
540
  t['http://www.w3.org/2005/01/wf/flow#Task'] ||
512
- store.holds(subject, UI.ns.wf('tracker'))
541
+ kb.holds(subject, ns.wf('tracker'))
513
542
  ) {
514
- tracker = store.any(subject, ns.wf('tracker'))
515
- if (!tracker) throw new Error('This issue ' + subject + 'has no tracker')
516
-
517
- // Much data is in the tracker instance, so wait for the data from it
518
-
519
- context.session.store.fetcher
520
- .load(tracker.doc())
521
- .then(function (_xhrs) {
522
- const stateStore = store.any(tracker, ns.wf('stateStore'))
523
- context.session.store.fetcher.nowOrWhenFetched(
524
- stateStore,
525
- subject,
526
- function drawIssuePane2 (ok, body) {
527
- if (!ok) {
528
- return console.log(
529
- 'Failed to load state ' + stateStore + ' ' + body
530
- )
531
- }
532
- paneDiv.appendChild(renderIssue(subject, context))
533
- updater.addDownstreamChangeListener(stateStore, function () {
534
- UI.widgets.refreshTree(paneDiv)
535
- }) // Live update
536
- }
537
- )
538
- })
539
- .catch(err => {
540
- const msg = 'Failed to load config ' + tracker.doc() + ' ' + err
541
- return complain(msg)
542
- })
543
- context.session.store.fetcher.nowOrWhenFetched(
544
- tracker.doc(),
545
- subject,
546
- function drawIssuePane1 (ok, body) {
547
- if (!ok) {
548
- return console.log(
549
- 'Failed to load config ' + tracker.doc() + ' ' + body
550
- )
551
- }
552
- }
553
- ) // End nowOrWhenFetched tracker
554
-
555
- // /////////////////////////////////////////////////////////
556
- //
557
- // Render a Tracker instance
558
- //
543
+ renderSingleIssue().then(() => console.log('Single issue rendered'))
559
544
  } else if (t['http://www.w3.org/2005/01/wf/flow#Tracker']) {
545
+ // Render a Tracker instance
560
546
  renderTracker().then(() => console.log('Tracker rendered'))
561
547
  } else {
562
548
  console.log(
@@ -572,8 +558,6 @@ export default {
572
558
  overlay.style = OVERFLOW_STYLE
573
559
  overlay.style.visibility = 'hidden'
574
560
 
575
- // var overlayPane = null // overlay.appendChild(dom.createElement('div')) // avoid stomping on style by pane
576
-
577
561
  authn.checkUser().then(webId => {
578
562
  if (webId) {
579
563
  console.log('Web ID set already: ' + webId)
@@ -582,9 +566,9 @@ export default {
582
566
  return
583
567
  }
584
568
 
585
- loginOutButton = UI.login.loginStatusBox(dom, webIdUri => {
569
+ loginOutButton = authn.loginStatusBox(dom, webIdUri => {
586
570
  if (webIdUri) {
587
- context.me = store.sym(webIdUri)
571
+ context.me = kb.sym(webIdUri)
588
572
  console.log('Web ID set from login button: ' + webIdUri)
589
573
  paneDiv.removeChild(loginOutButton)
590
574
  // enable things
package/newIssue.js CHANGED
@@ -1,9 +1,8 @@
1
1
  // Form to collect data about a New Issue
2
2
  //
3
- import * as UI from 'solid-ui'
3
+ import { ns, rdf, utils } from 'solid-ui'
4
4
 
5
- const $rdf = UI.rdf
6
- const ns = UI.ns
5
+ const $rdf = rdf
7
6
 
8
7
  export function newIssueForm (dom, kb, tracker, superIssue, showNewIssue) {
9
8
  const form = dom.createElement('div') // form is broken as HTML behaviour can resurface on js error
@@ -19,7 +18,6 @@ export function newIssueForm (dom, kb, tracker, superIssue, showNewIssue) {
19
18
  titlefield.setAttribute('class', 'pendingedit')
20
19
  titlefield.disabled = true
21
20
  const sts = []
22
- let issue
23
21
 
24
22
  const expandTemplate = function (template) {
25
23
  const now = new $rdf.Literal(new Date())
@@ -34,34 +32,30 @@ export function newIssueForm (dom, kb, tracker, superIssue, showNewIssue) {
34
32
  .replace('{DD}', DD)
35
33
  }
36
34
  // Where to store the new issue?
37
- let template = kb.anyValue(tracker, ns.wf('issueURITemplate'))
38
- let issueDoc
39
- if (template) {
35
+ const template = kb.anyValue(tracker, ns.wf('issueURITemplate'))
36
+ const issue = template
40
37
  // Does each issue do in its own file?
41
- template = $rdf.uri.join(template, stateStore.uri) // Template is relative
42
- issue = kb.sym(expandTemplate(template))
43
- } else {
44
- issue = kb.sym(stateStore.uri + '#' + 'Iss' + timestring())
45
- }
46
- // eslint-disable-next-line prefer-const
47
- issueDoc = issue.doc()
38
+ ? kb.sym(expandTemplate($rdf.uri.join(template, stateStore.uri)))
39
+ : kb.sym(stateStore.uri + '#' + 'Iss' + timestring())
40
+
41
+ const issueDoc = issue.doc()
48
42
 
49
43
  // Basic 9 core predicates are stored in the main stateStore
50
44
 
51
45
  const title = kb.literal(titlefield.value)
52
46
  sts.push(new $rdf.Statement(issue, ns.wf('tracker'), tracker, stateStore))
53
47
  sts.push(new $rdf.Statement(issue, ns.dc('title'), title, stateStore))
54
- sts.push(
55
- new $rdf.Statement(issue, ns.dct('created'), new Date(), stateStore)
56
- )
57
- const initialStates = kb.each(tracker, ns.wf('initialState'))
58
- if (initialStates.length === 0) { console.log('This tracker has no initialState') }
59
- for (let i = 0; i < initialStates.length; i++) {
48
+ sts.push(new $rdf.Statement(issue, ns.dct('created'), new Date(), stateStore))
49
+ // Copy states from super issue as after all they are subtasks so initially same state same category
50
+ const initialStates = superIssue
51
+ ? kb.each(superIssue, ns.rdf('type'), null, superIssue.doc())
52
+ : kb.each(tracker, ns.wf('initialState'))
53
+ for (const state of initialStates) {
60
54
  sts.push(
61
55
  new $rdf.Statement(
62
56
  issue,
63
57
  ns.rdf('type'),
64
- initialStates[i],
58
+ state,
65
59
  stateStore
66
60
  )
67
61
  )
@@ -92,7 +86,7 @@ export function newIssueForm (dom, kb, tracker, superIssue, showNewIssue) {
92
86
  }
93
87
 
94
88
  const states = kb.any(tracker, ns.wf('issueClass'))
95
- const classLabel = UI.utils.label(states)
89
+ const classLabel = utils.label(states)
96
90
  form.innerHTML =
97
91
  '<h2>Add new ' +
98
92
  (superIssue ? 'sub ' : '') +
@@ -100,7 +94,7 @@ export function newIssueForm (dom, kb, tracker, superIssue, showNewIssue) {
100
94
  '</h2><p>Title of new ' +
101
95
  classLabel +
102
96
  ':</p>'
103
- var titlefield = dom.createElement('input')
97
+ const titlefield = dom.createElement('input')
104
98
  titlefield.setAttribute('type', 'text')
105
99
  titlefield.setAttribute(
106
100
  'style',
@@ -119,5 +113,6 @@ export function newIssueForm (dom, kb, tracker, superIssue, showNewIssue) {
119
113
  false
120
114
  )
121
115
  form.appendChild(titlefield)
116
+ titlefield.focus() // we want user cursor here
122
117
  return form
123
118
  }
package/newTracker.js CHANGED
@@ -3,6 +3,7 @@ import { store } from 'solid-logic'
3
3
 
4
4
  const $rdf = UI.rdf
5
5
  const ns = UI.ns
6
+ const kb = store
6
7
  const updater = store.updater
7
8
 
8
9
  /* Button for making a whole new tracker
@@ -20,13 +21,25 @@ export function newTrackerButton (thisTracker, context) {
20
21
  ws,
21
22
  base
22
23
  ) {
24
+ function morph (x) {
25
+ // Move any URIs in this space into that space
26
+ if (x.elements !== undefined) return x.elements.map(morph) // Morph within lists
27
+ if (x.uri === undefined) return x
28
+ let u = x.uri
29
+ if (u === stateStore.uri) return newStore // special case
30
+ if (u.slice(0, oldBase.length) === oldBase) {
31
+ u = base + u.slice(oldBase.length)
32
+ }
33
+ return kb.sym(u)
34
+ }
35
+
23
36
  const appPathSegment = 'issuetracker.w3.org' // how to allocate this string and connect to
24
37
  // console.log("Ready to make new instance at "+ws)
25
38
  const sp = UI.ns.space
26
- const store = context.session.store
39
+ const kb = context.session.store
27
40
 
28
41
  if (!base) {
29
- base = store.any(ws, sp('uriPrefix')).value
42
+ base = kb.any(ws, sp('uriPrefix')).value
30
43
  if (base.slice(-1) !== '/') {
31
44
  $rdf.log.error(
32
45
  appPathSegment + ': No / at end of uriPrefix ' + base
@@ -39,29 +52,17 @@ export function newTrackerButton (thisTracker, context) {
39
52
  }
40
53
  }
41
54
 
42
- const stateStore = store.any(thisTracker, ns.wf('stateStore'))
43
- const newStore = store.sym(base + 'store.ttl')
55
+ const stateStore = kb.any(thisTracker, ns.wf('stateStore'))
56
+ const newStore = kb.sym(base + 'store.ttl')
44
57
 
45
58
  const here = thisTracker.doc()
46
59
 
47
60
  const oldBase = here.uri.slice(0, here.uri.lastIndexOf('/') + 1)
48
61
 
49
- const morph = function (x) {
50
- // Move any URIs in this space into that space
51
- if (x.elements !== undefined) return x.elements.map(morph) // Morph within lists
52
- if (x.uri === undefined) return x
53
- let u = x.uri
54
- if (u === stateStore.uri) return newStore // special case
55
- if (u.slice(0, oldBase.length) === oldBase) {
56
- u = base + u.slice(oldBase.length)
57
- $rdf.log.debug(' Map ' + x.uri + ' to ' + u)
58
- }
59
- return store.sym(u)
60
- }
61
62
  const there = morph(here)
62
63
  const newTracker = morph(thisTracker)
63
64
 
64
- const myConfig = store.statementsMatching(
65
+ const myConfig = kb.statementsMatching(
65
66
  undefined,
66
67
  undefined,
67
68
  undefined,
@@ -69,7 +70,7 @@ export function newTrackerButton (thisTracker, context) {
69
70
  )
70
71
  for (let i = 0; i < myConfig.length; i++) {
71
72
  const st = myConfig[i]
72
- store.add(
73
+ kb.add(
73
74
  morph(st.subject),
74
75
  morph(st.predicate),
75
76
  morph(st.object),
@@ -79,15 +80,15 @@ export function newTrackerButton (thisTracker, context) {
79
80
 
80
81
  // Keep a paper trail @@ Revisit when we have non-public ones @@ Privacy
81
82
  //
82
- store.add(newTracker, UI.ns.space('inspiration'), thisTracker, stateStore)
83
+ kb.add(newTracker, UI.ns.space('inspiration'), thisTracker, stateStore)
83
84
 
84
- store.add(newTracker, UI.ns.space('inspiration'), thisTracker, there)
85
+ kb.add(newTracker, UI.ns.space('inspiration'), thisTracker, there)
85
86
 
86
87
  // $rdf.log.debug("\n Ready to put " + kb.statementsMatching(undefined, undefined, undefined, there)); //@@
87
88
 
88
89
  updater.put(
89
90
  there,
90
- store.statementsMatching(undefined, undefined, undefined, there),
91
+ kb.statementsMatching(undefined, undefined, undefined, there),
91
92
  'text/turtle',
92
93
  function (uri2, ok, message) {
93
94
  if (ok) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "issue-pane",
3
- "version": "2.4.10",
3
+ "version": "2.4.11",
4
4
  "description": "Solid-compatible Panes: issue editor",
5
5
  "main": "./issuePane.js",
6
6
  "scripts": {
@@ -34,16 +34,16 @@
34
34
  },
35
35
  "homepage": "https://github.com/solid/issue-pane",
36
36
  "dependencies": {
37
- "pane-registry": "^2.4.7",
38
- "rdflib": "^2.2.17",
39
- "solid-logic": "^1.3.14",
40
- "solid-ui": "^2.4.19"
37
+ "pane-registry": "^2.4.9",
38
+ "rdflib": "^2.2.19",
39
+ "solid-logic": "^1.3.16",
40
+ "solid-ui": "^2.4.20"
41
41
  },
42
42
  "devDependencies": {
43
- "eslint": "^7.32.0",
43
+ "eslint": "^8.12.0",
44
+ "eslint-plugin-import": "^2.25.4",
44
45
  "husky": "^7.0.4",
45
- "lint-staged": "^11.2.6",
46
- "standard": "^16.0.4"
46
+ "lint-staged": "^12.3.7"
47
47
  },
48
48
  "husky": {
49
49
  "hooks": {