UNPKG

tapspace

Version:

A zoomable user interface lib for web apps

135 lines (122 loc) 4.04 kB
module.exports = function (id, content) { // @TreeLoader:addSpace(id, content) // // Add a space, given that it has existing neighbors and is expected. // A space is expected after the loader emits 'open' and before it is closed. // If the space exists already, the old space is replaced with the content. // // Parameters: // id // a string // content // a Component, the space // // Return // a boolean. True if it was possible to add or replace the space // .. and false otherwise. // // Ensure valid id if (!id) { console.warn('Nullish space ID detected: ' + id) return false } // Space may already exist. if (this.spaces[id]) { // Just replace. const oldSpace = this.spaces[id] const basis = oldSpace.getBasis().changeBasis(this.viewport) this.spaces[id] = content this.viewport.hyperspace.replaceChild(oldSpace, content) content.setBasis(basis) delete this.loading[id] // Not necessary but just for safety. return true } // If the space is not loading and does not exist, the addition is unexpected // and probably due to a delayed server response. Do not add these spaces. if (!this.loading[id]) { // DEBUG console.warn('Unexpected space addition detected: ' + id) return false } // Try find basis for the space. Three ways: // - cached basis // - mapped basis via the parent // - mapped basis via a child let basis = null // Record the direction to control the rendering order. // - render parents first // - add parent spaces by prepending // - add child spaces by appending let isChild = true // Try cached basis if (this.bases[id]) { // Consume so it does not stay haunting. basis = this.bases[id] delete this.bases[id] } // Try via parent. if (!basis) { isChild = true const parentId = this.backtracker(id, content) if (parentId && this.spaces[parentId]) { // Has existing parent. const parentSpace = this.spaces[parentId] // Position about the parent. basis = this.mapper(parentId, parentSpace, id) // If mapper fails, try reverse backmapper. if (!basis) { const insertionBasis = this.backmapper(id, content, parentId) if (insertionBasis) { // Insertion basis is the position of the parent w.r.t. the child. // However, the child does not yet exist in DOM. const parentBasis = parentSpace.getBasis() // Therefore compute where would the child basis be. basis = insertionBasis.getMatchedOuter(parentBasis) } } } } // Try via children. if (!basis) { isChild = false const childIds = this.tracker(id, content) // Try to find existing child. const childId = childIds.find((cid) => { return this.spaces[cid] }) if (childId) { // Has existing child. const childSpace = this.spaces[childId] // Position about the child basis = this.backmapper(childId, childSpace, id) // If backmapper fails, try reverse mapper. if (!basis) { const insertionBasis = this.mapper(id, content, childId) if (insertionBasis) { // Insertion basis is the position of the child w.r.t. the parent. // However, the parent does not yet exist in DOM. const childBasis = childSpace.getBasis() // Therefore compute where would the parent basis be. basis = insertionBasis.getMatchedOuter(childBasis) } } } } if (!basis) { // Cannot find basis. // DEBUG console.warn('Cannot find position for space ' + id) return false } // Basis found. Add the content. this.spaces[id] = content if (isChild) { this.viewport.addChild(content) } else { this.viewport.prependChild(content) } content.setBasis(basis) // Register loading state. delete this.loading[id] // Signal that the node is now open and available. this.emit('opened', { id, space: content }) return true }