From c6845a764dea3f40075258ed7a7bbbec462d3afe Mon Sep 17 00:00:00 2001 From: Jon Winton Date: Thu, 29 Sep 2016 20:51:03 -0400 Subject: [PATCH 1/7] Adding the invisible logic to the add controller --- src/controllers/add-component-controller.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/controllers/add-component-controller.js b/src/controllers/add-component-controller.js index 723bd0a..85c4c29 100644 --- a/src/controllers/add-component-controller.js +++ b/src/controllers/add-component-controller.js @@ -14,12 +14,14 @@ var dom = require('@nymag/dom'), * @param {Object} spaceParent * @param {Function} callback */ -function AddComponent(spaceParent, callback) { +function AddComponent(spaceParent, callback, invisible) { this.parent = spaceParent; this.parentRef = spaceParent.getAttribute('data-uri'); - this.callback = callback; + this.callback = callback ? callback : _.noop; + + this.invisible = invisible; this.launchPane(); } @@ -93,9 +95,9 @@ proto.makeNewComponentActive = function (targetEl) { statusService.removeEditing(logic); }); - statusService.setEditing(targetEl); + this.invisible ? _.noop() : statusService.setEditing(targetEl); }; -module.exports = function (spaceParent, callback) { - return new AddComponent(spaceParent, callback); +module.exports = function (spaceParent, callback, invisible = false) { + return new AddComponent(spaceParent, callback, invisible); }; From 0d073a6b992656510d29515f5f3068d7e6780386 Mon Sep 17 00:00:00 2001 From: Jon Winton Date: Thu, 29 Sep 2016 20:52:01 -0400 Subject: [PATCH 2/7] Adding a better check for Spaces vs SpaceEdit --- src/index.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/index.js b/src/index.js index 9aecabe..38285b2 100644 --- a/src/index.js +++ b/src/index.js @@ -1,24 +1,34 @@ var selector = require('./services/selector'), SpaceController = require('./controllers/space-controller'), - utils = require('./services/utils'); + invisibleLists = require('./services/invisible-lists'), + utils = require('./services/utils'), + dom = require('@nymag/dom'); // Load styles require('./styleguide/styles.scss'); function updateSelector(el, options, parent) { - var isSpaceComponent = utils.checkIfSpace(options.ref), - availableSpaces = utils.spaceInComponentList(parent); + var isSpaceComponent, availableSpaces; + + if (utils.checkIfSpaceEdit(options.ref)) { + return; + } + + isSpaceComponent = utils.checkIfSpace(options.ref); + availableSpaces = utils.spaceInComponentList(parent); if (availableSpaces && availableSpaces.length > 0 && !isSpaceComponent) { selector.addAvailableSpaces(el, availableSpaces); selector.addCreateSpaceButton(el, options, parent); selector.stripSpaceFromComponentList(el); + } if (isSpaceComponent) { selector.addAvailableSpaces(el, availableSpaces); SpaceController(el, parent); selector.addToComponentList(el, options, parent); + } } @@ -26,4 +36,5 @@ window.kiln = window.kiln || {}; window.kiln.plugins = window.kiln.plugins || {}; window.kiln.plugins['spaces-edit'] = function initSpaces() { window.kiln.on('add-selector', updateSelector); + window.kiln.on('component-pane:create-invisible-tab', invisibleLists); }; From dc6d551bb497e87d10a5686294995f3ac5f14d9b Mon Sep 17 00:00:00 2001 From: Jon Winton Date: Thu, 29 Sep 2016 20:53:16 -0400 Subject: [PATCH 3/7] Adding logic for browsing invisible spaces --- src/controllers/space-settings-controller.js | 163 ++++++++++++++++--- 1 file changed, 138 insertions(+), 25 deletions(-) diff --git a/src/controllers/space-settings-controller.js b/src/controllers/space-settings-controller.js index 23905a3..7bea875 100644 --- a/src/controllers/space-settings-controller.js +++ b/src/controllers/space-settings-controller.js @@ -13,15 +13,26 @@ var dom = require('@nymag/dom'), * @param {Element} el * @param {Object} callbacks */ -function BrowseController(el, callbacks) { +function BrowseController(el, callbacks, invisible) { /** - * The Clay Space component data - * @type {Object} + * The Space element + * + * @type {Element} */ this.el = el; - this.spaceRef = el.getAttribute('data-uri'); + /** + * The ref to the Space element + * + * @type {String} + */ + this.spaceRef = el.getAttribute(references.referenceAttribute); + /** + * An object of callback functions + * + * @type {Object} + */ this.callbacks = callbacks; /** @@ -37,6 +48,13 @@ function BrowseController(el, callbacks) { */ this.componentList = []; + /** + * Boolean flag for whether or not you're launching + * a browse pane for an invisble Space + * + * @type {Boolean} + */ + this.invisible = invisible; this.findChildrenMakeList(this.el); // Launch the pane @@ -44,11 +62,11 @@ function BrowseController(el, callbacks) { } /** - * Open the settins pane + * Open the settings pane */ proto.launchPane = function () { var paneContent = this.markActiveInList(utils.createFilterableList(this.componentList, { - click: this.listItemClick.bind(this), + click: this.invisible ? _.noop : this.listItemClick.bind(this), reorder: this.reorder.bind(this), settings: this.settings.bind(this), remove: this.remove.bind(this), @@ -56,14 +74,77 @@ proto.launchPane = function () { addTitle: 'Add Component To Space' })); + if (this.invisible) { + this.addInTarget(paneContent); + } else { + this.swapInTargetIcon(paneContent); + } + references.pane.open([{ header: this.el.getAttribute(references.dataPaneTitle) || 'Browse Space', content: paneContent }]); }; +/** + * Add in a separate target button for invisible lists. + * This is important because the settings still need + * to point to the original component. + * + * @param {Element} content + */ +proto.addInTarget = function (content) { + var settingsIcons = dom.findAll(content, '.filtered-item-settings'), + targetSpaceBtn = references.tpl.get('.target-space'); + + // Whenever you append a document fragment like `targetSpaceBtn` into the DOM + // you are taking the content out of the document fragment. If you use `appendChild` + // then the HTML of the fragment is returned, but since we want to insert this + // icon before another icon we don't get that benefit. For this reason we have + // to clone the node (`cloneNode`) so that we can iterate through mutliple and + // items and apply the button. + _.forEach(settingsIcons, (icon) => { + var targetBtn, + targetSpaceBtnClone = targetSpaceBtn.cloneNode(true); + + // Add in button + dom.insertBefore(icon, targetSpaceBtnClone); + // Find the button + targetBtn = dom.find(icon.parentElement, '.space-target'); + // Attach event listeners + targetBtn.addEventListener('click', this.targetBtnClick.bind(this)); + }); +}; + +/** + * Event handler for the target button click event + * + * @param {Object} e + */ +proto.targetBtnClick = function (e) { + var componentUri = dom.closest(e.target, '[data-item-id]').getAttribute('data-item-id'), + component = dom.find(`[data-uri="${componentUri}"]`); + + this.settings(component.parentElement.getAttribute(references.referenceAttribute)); +} + +/** + * Replace the settings icon with the target icon because + * accurate iconography is important + * + * @param {Element} content + */ +proto.swapInTargetIcon = function (content) { + var settingsBtnSvgs = dom.findAll(content, '.filtered-item-settings svg'), + targetSvg = references.tpl.get('.target-space-svg'); + + _.forEach(settingsBtnSvgs, function (icon) { + dom.replaceElement(icon, targetSvg.cloneNode(true)); + }); +}; + /** * Launch the AddController */ proto.addComponent = function () { - AddController(this.el, this.callbacks.add); + AddController(this.el, this.callbacks.add, this.invisible); }; /** @@ -75,28 +156,49 @@ proto.findChildrenMakeList = function (el) { this.componentList = this.makeList(this.childComponents); }; +/** + * Determine if it's the Logic or the child that should be + * used based on whether or not the Space is invisible + * + * @param {Element} logic + * @return {Element} + */ +proto.logicOrEmbedded = function (logic) { + return this.invisible ? dom.find(logic, '[data-uri]') : logic; +}; + /** * Apply active styling to the proper item in the filterable list + * * @param {element} listHtml * @returns {element} */ proto.markActiveInList = function (listHtml) { - _.each(this.childComponents, function (logicComponent) { - if (statusService.isEditing(logicComponent)) { - dom.find(listHtml, '[data-item-id="' + logicComponent.getAttribute('data-uri') + '"]').classList.add('active'); - } - }); + if (!this.invisible) { // An 'active' item isn't a thing when you can't see it...right? + _.each(this.childComponents, (logicComponent) => { + var targetUri = this.logicOrEmbedded(logicComponent).getAttribute('data-uri'); + if (statusService.isEditing(logicComponent)) { + dom.find(listHtml, `[data-item-id="${targetUri}"]`).classList.add('active'); + } + }); + } return listHtml; }; +proto.logicFromChildRef = function (ref) { + return dom.closest(dom.find(`[data-uri="${ref}"]`), '[data-logic]'); +}; + /** * Delete a component from a space * * @param {string} id The `id` value of the item in the filterable list that was clicked */ proto.remove = function (id) { - removeService.removeLogic(id, this.el) + var logicUri = this.invisible ? this.logicFromChildRef(id).getAttribute('data-uri') : id; + + removeService.removeLogic(logicUri, this.el) .then(() => { // Make new component list from the returned HTML this.findChildrenMakeList(this.el); @@ -116,17 +218,19 @@ proto.remove = function (id) { * @return {Object} */ proto.makeList = function (components) { - return _.map(components, function (item) { - var childComponent = dom.find(item, '[data-uri]'), - componentType = references.getComponentNameFromReference(childComponent.getAttribute('data-uri')), - componentTitle = references.label(componentType), - readouts = logicReadoutService(item); + return _.map(components, (item) => { + var childComponentUri = dom.find(item, '[data-uri]').getAttribute('data-uri'), // Get the child of the logic + componentType = references.getComponentNameFromReference(childComponentUri), // Get it's name + componentTitle = references.label(componentType), // Make the title + readouts = logicReadoutService(item), // Get logic readouts + returnId = this.invisible ? childComponentUri : item.getAttribute('data-uri'); - componentTitle = readouts ? componentTitle + readouts : componentTitle; + componentTitle = readouts ? componentTitle + readouts : componentTitle; // Great the title + // Return an object representing the Logic and it's child return { title: componentTitle, - id: item.getAttribute('data-uri') + id: returnId }; }); }; @@ -138,9 +242,18 @@ proto.makeList = function (components) { * @param {number} oldIndex */ proto.reorder = function (id, newIndex, oldIndex) { + var data = { _ref: this.spaceRef }, - content = _.map(this.componentList, function (item) { - return { _ref: item.id }; + content = _.map(this.componentList, (item) => { + var id = item.id; + + // If invisible then `item.id` will be the direct component's + // uri and not its parent Logic's. Need to fix that. + if (this.invisible) { + id = this.logicFromChildRef(id).getAttribute('data-uri'); + } + + return { _ref: id }; }); content.splice(newIndex, 0, content.splice(oldIndex, 1)[0]); // reorder the array @@ -230,12 +343,12 @@ proto.settings = function (id) { /** * Make a new Space Settings instance * - * @param {Object} parent + * @param {Object} el * @param {Object} callbacks * @return {BrowseController} */ -function spaceSettings(parent, callbacks) { - return new BrowseController(parent, callbacks); +function spaceSettings(el, callbacks = {}, invisible = false) { + return new BrowseController(el, callbacks, invisible); }; module.exports = spaceSettings; From 855361227175668073f6af163100b8de3192ed9c Mon Sep 17 00:00:00 2001 From: Jon Winton Date: Thu, 29 Sep 2016 20:53:51 -0400 Subject: [PATCH 4/7] Adding a check for space-edit --- src/services/utils.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/services/utils.js b/src/services/utils.js index e9a50f8..3041f42 100644 --- a/src/services/utils.js +++ b/src/services/utils.js @@ -70,6 +70,16 @@ function checkIfSpace(ref) { return _.includes(ref, references.spacePrefix); } +/** + * Checks if a reference is the Space Edit component. + * + * @param {String} ref The uri of a component + * @return {Boolean} + */ +function checkIfSpaceEdit(ref) { + return _.includes(ref, references.spaceEdit); +} + /** * Return an array of all components with the * `data-logic` attribute @@ -86,4 +96,5 @@ module.exports.spaceInComponentList = spaceInComponentList; module.exports.availableSpaces = availableSpaces; module.exports.createFilterableList = createFilterableList; module.exports.checkIfSpace = checkIfSpace; +module.exports.checkIfSpaceEdit = checkIfSpaceEdit; module.exports.findAllLogic = findAllLogic; From 64e47ce798dc40ee8b6be3f580e9ff2d570612e3 Mon Sep 17 00:00:00 2001 From: Jon Winton Date: Thu, 29 Sep 2016 20:54:21 -0400 Subject: [PATCH 5/7] Removing a duplicate reference --- src/services/references.js | 1 - src/services/selector.js | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/services/references.js b/src/services/references.js index 9c318bf..5483953 100644 --- a/src/services/references.js +++ b/src/services/references.js @@ -9,7 +9,6 @@ kilnServices = window.kiln.services; referencesObj = _.assign({ spaceEdit: 'clay-space-edit', spacePrefix: 'clay-space', - spaceClass: 'clay-space', dataAvailableSpaces: 'data-spaces-available', dataPaneTitle: 'data-space-browse-title', render: kilnServices.render, diff --git a/src/services/selector.js b/src/services/selector.js index b0a97da..ca548d2 100644 --- a/src/services/selector.js +++ b/src/services/selector.js @@ -121,7 +121,7 @@ function addBrowseButton(logicComponent) { * @param {Object} callbacks [description] */ function launchBrowsePane(spaceElement, callbacks) { - SpaceSettings(spaceElement, callbacks); + SpaceSettings(spaceElement, callbacks, false); } function addRemoveButton(logic) { @@ -142,7 +142,7 @@ function addRemoveButton(logic) { function stripSpaceFromComponentList(el) { var addButton = dom.find(el, '.selected-add'), components = addButton.getAttribute('data-components').split(','), - componentsSansSpace = _.pull(components, references.spaceClass); + componentsSansSpace = _.pull(components, references.spacePrefix); addButton.setAttribute('data-components', componentsSansSpace); } From 80d7d4a17258c19a015c70403e474c2786e7bcfd Mon Sep 17 00:00:00 2001 From: Jon Winton Date: Thu, 29 Sep 2016 20:54:44 -0400 Subject: [PATCH 6/7] Adding invisibility service and the markup/styles for the feature --- media/target.svg | 1 + src/services/invisible-lists.js | 154 ++++++++++++++++++++++++++++++++ src/styleguide/styles.scss | 23 ++++- template.nunjucks | 27 ++++++ 4 files changed, 203 insertions(+), 2 deletions(-) create mode 100644 media/target.svg create mode 100644 src/services/invisible-lists.js diff --git a/media/target.svg b/media/target.svg new file mode 100644 index 0000000..5715fe4 --- /dev/null +++ b/media/target.svg @@ -0,0 +1 @@ + diff --git a/src/services/invisible-lists.js b/src/services/invisible-lists.js new file mode 100644 index 0000000..67428ce --- /dev/null +++ b/src/services/invisible-lists.js @@ -0,0 +1,154 @@ +var dom = require('@nymag/dom'), + _ = require('lodash'), + createService = require('./create-service'), + removeService = require('./remove-service'), + references = require('references'), + utils = require('./utils'), + SpaceSettings = require('../controllers/space-settings-controller'), + settingsCallbacks = { + remove: removeCallback + }; + +/** + * Modify the HTML of an invisible component list + * tab to include anything related to Spaces + * + * @param {Element} invisibleList + */ +function openInvisibleList(invisibleList) { + var spaceableElements = dom.findAll(invisibleList.list.el, `[${references.dataAvailableSpaces}]`), + spaceableRefs = _.compact(_.map(spaceableElements, function (element) { + var uri = element.getAttribute(references.referenceAttribute); + + return _.includes(uri, references.spacePrefix) ? null : uri; + })), + listItemElements = dom.findAll(invisibleList.content, '[data-item-id]'), + spaceComponents = _.filter(listItemElements, function (el) { + var itemId = el.getAttribute('data-item-id'); + + return _.includes(itemId, references.spacePrefix) && !_.includes(itemId, references.spaceEdit); + }); + + _.forEach(filterListItems(spaceableRefs, listItemElements), addCreateButton.bind(null, invisibleList)); + _.forEach(spaceComponents, addBrowseButton); +} + +/** + * Find all the list items in the filterable list + * that can be made into a Space + * + * @param {Array} targetRefs + * @param {Element} items + * @return {Array} + */ +function filterListItems(targetRefs, items) { + return _.filter(items, function (item) { + return _.includes(targetRefs, item.getAttribute('data-item-id')); + }); +} + +/** + * Add in the 'Create Space' button + * @param {Object} invisibleList + * @param {Element} item + */ +function addCreateButton(invisibleList, item) { + var settings = dom.find(item, '.filtered-item-settings'), + createSpaceButton = references.tpl.get('.create-space-list'), + createButton; + + dom.insertBefore(settings, createSpaceButton); + + createButton = dom.find(item, '.space-create'); + createButton.addEventListener('click', createSpaceFromInvisibleComponent.bind(null, item, invisibleList)); +} + +/** + * Create a Space out of the invisible component + * + * @param {Element} item + * @param {Object} invisibleList + */ +function createSpaceFromInvisibleComponent(item, invisibleList) { + var uri = item.getAttribute('data-item-id'), + element = dom.find(document, `[data-uri="${uri}"]`), + parent = dom.closest(element.parentElement, '[data-uri]'); + + createService.createSpace({ ref: uri, data: { _ref: uri } }, { + el: parent, + ref: invisibleList.layoutRef, + path: invisibleList.list.path + }); +} + +/** + * Add in the proper browse button + * + * @param {Element} item + */ +function addBrowseButton(item) { + var settings = dom.find(item, '.filtered-item-settings'), + remove = dom.find(item, '.filtered-item-remove'), + spaceUri = item.getAttribute('data-item-id'), + spaceElement = dom.find(document, `[data-uri="${spaceUri}"]`), + browseSpaceButton = references.tpl.get('.browse-space-list'), + browseButton; + + dom.insertBefore(settings, browseSpaceButton); + settings.classList.add('kiln-hide'); + remove.classList.add('kiln-hide'); + // Find the browse button + browseButton = dom.find(item, '.space-browse'); + // Add count of elements + getBrowseCount(spaceElement, browseButton); + // Create the browse pane + browseButton.addEventListener('click', function () { + launchBrowse(spaceElement); + }); +}; + +/** + * Open the browse pane for a Space + * + * @param {Element} spaceElement + */ +function launchBrowse(spaceElement) { + SpaceSettings(spaceElement, settingsCallbacks, true); +} + +/** + * The callback function when a component is removed. + * Either remove the Logic and re-open the pane or + * remove the whole Space + * + * @param {Element} space + */ +function removeCallback(space) { + var logics = utils.findAllLogic(space), + parentPath; + + if (logics.length) { + launchBrowse(space); + } else { + parentPath = dom.closest(space, '[data-editable]').getAttribute('data-editable'); + references.edit.getLayout() + .then(function (data) { + references.pane.close(); + return removeService.removeSpace(space, { ref: data, path: parentPath }) + }); + } +} + +/** + * Add the count to the browse button + * + * @param {Element} spaceElement + * @param {Element} button + */ +function getBrowseCount(spaceElement, button) { + var count = dom.find(button, '.logic-count'); + + count.innerHTML = dom.findAll(spaceElement, '[data-logic]').length; +} + +module.exports = openInvisibleList; diff --git a/src/styleguide/styles.scss b/src/styleguide/styles.scss index 3f12c2c..fcb267c 100644 --- a/src/styleguide/styles.scss +++ b/src/styleguide/styles.scss @@ -20,19 +20,38 @@ $UI_BUTTON: #694C79; border-left: 1px solid $UI_PRIMARY; } +.logic-count { + position: absolute; +} + .space-browse { border-left: 1px solid $UI_PRIMARY; position: relative; .logic-count { bottom: 13px; - position: absolute; right: 7px; } } // Readouts inside the logic browse pane +.filtered-list-button { + background: none; + border: 0; + cursor: pointer; + flex: 0 0 auto; + margin-left: 10px; + outline: none; + padding: 0; + position: relative; + + .logic-count { + bottom: -5px; + right: -8px; + } +} + .filtered-item-title-logic-readout { display: flex; flex-wrap: wrap; @@ -46,9 +65,9 @@ $UI_BUTTON: #694C79; margin-right: 8px; & svg { - width: 12px; height: 12px; vertical-align: text-top; + width: 12px; } & > .readout-icon { diff --git a/template.nunjucks b/template.nunjucks index 4b37569..b88e0d5 100644 --- a/template.nunjucks +++ b/template.nunjucks @@ -27,6 +27,33 @@ + {# Filter List Creat Space Button #} + + + {# Filter List Browse Button #} + + + {# Target Space Button #} + + + {# Target SVG #} + + {# Readout Icons #} {# Need more icons? Submit a PR to https://github.com/nymag/clay-space-edit with the icon #}