/* create.js UI to craete new objects in the solid-app-set world ** */ import { DataBrowserContext, NewPaneOptions, PaneDefinition } from 'pane-registry' import { solidLogicSingleton } from 'solid-logic-jss' import * as debug from '../debug' import { icons } from '../iconBase' import { ensureLoadedProfile, selectWorkspace } from '../login/login' import ns from '../ns' import * as utils from '../utils' import * as widgets from '../widgets' import { CreateContext, NewAppInstanceOptions } from './types' const kb = solidLogicSingleton.store /* newThingUI -- return UI for user to select a new object, folder, etc ** ** context must include: dom, div, ** optional: folder: NamedNode -- the folder where the thing is bring put ** (suppresses asking for a full URI or workspace) ** */ export function newThingUI ( createContext: CreateContext, dataBrowserContext: DataBrowserContext, thePanes: Array ): void { const dom = createContext.dom const div = createContext.div if (createContext.me && !createContext.me.uri) { throw new Error('newThingUI: Invalid userid: ' + createContext.me) } const iconStyle = 'padding: 0.7em; width: 2em; height: 2em;' // was: 'padding: 1em; width: 3em; height: 3em;' const star = div.appendChild(dom.createElement('img')) let visible = false // the inividual tools tools // noun_272948.svg = black star // noun_34653_green.svg = green plus star.setAttribute('src', icons.iconBase + 'noun_34653_green.svg') star.setAttribute('style', iconStyle) star.setAttribute('title', 'Add another tool') const complain = function complain (message) { const pre = div.appendChild(dom.createElement('pre')) pre.setAttribute('style', 'background-color: pink') pre.appendChild(dom.createTextNode(message)) } function styleTheIcons (style) { for (let i = 0; i < iconArray.length; i++) { let st = iconStyle + style if (iconArray[i].disabled) { // @@ unused st += 'opacity: 0.3;' } iconArray[i].setAttribute('style', st) // eg 'background-color: #ccc;' } } function selectTool (icon) { styleTheIcons('display: none;') // 'background-color: #ccc;' icon.setAttribute('style', iconStyle + 'background-color: yellow;') } function selectNewTool (_event?) { visible = !visible star.setAttribute( 'style', iconStyle + (visible ? 'background-color: yellow;' : '') ) styleTheIcons(visible ? '' : 'display: none;') } star.addEventListener('click', selectNewTool) function makeNewAppInstance (options: NewAppInstanceOptions) { return new Promise(function (resolve, reject) { let selectUI // , selectUIParent function callbackWS (ws, newBase) { ensureLoadedProfile(createContext).then( _context => { const newPaneOptions: NewPaneOptions = Object.assign({ newBase, folder: options.folder || undefined, workspace: ws }, options) for (const opt in options) { // get div, dom, me, folder, pane, refreshTable newPaneOptions[opt] = options[opt] } debug.log(`newThingUI: Minting new ${newPaneOptions.pane.name} at ${newPaneOptions.newBase}`) options.pane .mintNew!(dataBrowserContext, newPaneOptions) .then(function (newPaneOptions) { if (!newPaneOptions || !newPaneOptions.newInstance) { throw new Error('Cannot mint new - missing newInstance') } if (newPaneOptions.folder) { const tail = newPaneOptions.newInstance.uri.slice( newPaneOptions.folder.uri.length ) const isPackage = tail.includes('/') debug.log(' new thing is packge? ' + isPackage) if (isPackage) { kb.add( newPaneOptions.folder, ns.ldp('contains'), kb.sym(newPaneOptions.newBase), newPaneOptions.folder.doc() ) } else { // single file kb.add( newPaneOptions.folder, ns.ldp('contains'), newPaneOptions.newInstance, newPaneOptions.folder.doc() ) // Ping the patch system? } // @ts-ignore @@ TODO check whether refresh can exist here. Either fix type or remove unreachable code if (newPaneOptions.refreshTarget && newPaneOptions.refreshTarget.refresh) { // @@ TODO Remove the need to cast as any ;(newPaneOptions.refreshTarget as any).refresh() // Refresh the containing display } // selectUI.parentNode.removeChild(selectUI) It removes itself } else { const p = options.div.appendChild(dom.createElement('p')) p.setAttribute('style', 'font-size: 120%;') // Make link to new thing p.innerHTML = 'Your new ' + options.noun + ' is ready to be set up. ' + '

Go to your new ' + options.noun + '.' // selectUI.parentNode.removeChild(selectUI) // Clean up // selectUIParent.removeChild(selectUI) // Clean up } selectNewTool() // toggle star to plain and menu vanish again }) .catch(function (err) { complain(err) reject(err) }) }, err => { // login fails complain('Error logging on: ' + err) } ) } // callbackWS const pa = options.pane // options.appPathSegment = pa.name // was 'edu.mit.solid.pane.' options.noun = pa.mintClass ? utils.label(pa.mintClass) : pa.name options.appPathSegment = options.noun.slice(0, 1).toUpperCase() + options.noun.slice(1) if (!options.folder) { // No folder given? Ask user for full URI selectUI = selectWorkspace(dom, { noun: options.noun, appPathSegment: options.appPathSegment }, callbackWS) options.div.appendChild(selectUI) // selectUIParent = options.div } else { const gotName = function (name) { if (!name) { // selectUIParent.removeChild(selectUI) itremves itself if cancelled selectNewTool() // toggle star to plain and menu vanish again } else { let uri = options.folder!.uri if (!uri.endsWith('/')) { uri = uri + '/' } uri = uri + encodeURIComponent(name) + '/' callbackWS(null, uri) } } widgets .askName( dom, kb, options.div, ns.foaf('name'), null, options.noun ) .then(gotName) // selectUI = getNameForm(dom, kb, options.noun, gotName) // options.div.appendChild(selectUI) // selectUIParent = options.div } }) } // makeNewAppInstance const iconArray: Array = [] const mintingPanes = Object.values(thePanes).filter(pane => pane.mintNew) const mintingClassMap = mintingPanes.reduce((classMap, pane) => { if (pane.mintClass) { classMap[pane.mintClass.uri] = (classMap[pane.mintClass.uri] || 0) + 1 } return classMap }, {}) mintingPanes.forEach(pane => { // @@ TODO Remove the need to cast to any const icon: any = createContext.div.appendChild(dom.createElement('img')) icon.setAttribute('src', pane.icon) const noun = pane.mintClass ? mintingClassMap[pane.mintClass.uri] > 1 ? `${utils.label(pane.mintClass)} (using ${pane.name} pane)` : utils.label(pane.mintClass) : pane.name + ' @@' icon.setAttribute('title', 'Make new ' + noun) icon.setAttribute('style', iconStyle + 'display: none;') iconArray.push(icon) if (!icon.disabled) { icon.addEventListener('click', function (e) { selectTool(icon) makeNewAppInstance({ event: e, folder: createContext.folder || null, iconEle: icon, pane, noun, noIndexHTML: true, // do NOT @@ for now write a HTML file div: createContext.div, me: createContext.me, dom: createContext.dom, refreshTarget: createContext.refreshTarget }) }) } }) } // Form to get the name of a new thing before we create it // // Used in contacts for new groups, individuals. // /* function getNameForm (dom, kb, classLabel, gotNameCallback) { const form = dom.createElement('div') // form is broken as HTML behaviour can resurface on js error form.innerHTML = '

Name of new ' + classLabel + ':

' const namefield = dom.createElement('input') namefield.setAttribute('type', 'text') namefield.setAttribute('size', '30') namefield.setAttribute('style', style.textInputStyle) namefield.setAttribute('maxLength', '2048') // No arbitrary limits namefield.select() // focus next user input const gotName = function () { namefield.setAttribute('class', 'pendingedit') namefield.disabled = true continueButton.disabled = true cancel.disabled = true gotNameCallback(true, namefield.value) } namefield.addEventListener('keyup', function (e) { if (e.keyCode === 13) { gotName() } }, false) form.appendChild(namefield) form.appendChild(dom.createElement('br')) const cancel = form.appendChild(widgets.cancelButton(dom)) cancel.addEventListener('click', function (e) { form.parentNode.removeChild(form) gotNameCallback(false) }, false) const continueButton = form.appendChild(widgets.continueButton(dom)) continueButton.addEventListener('click', function (e) { gotName() }, false) return form } */