diff --git a/platform/commonUI/edit/src/representers/EditToolbar.js b/platform/commonUI/edit/src/representers/EditToolbar.js index 91133e98a6..591a312aa3 100644 --- a/platform/commonUI/edit/src/representers/EditToolbar.js +++ b/platform/commonUI/edit/src/representers/EditToolbar.js @@ -4,17 +4,162 @@ define( function () { "use strict"; + // Utility functions for reducing truth arrays + function and(a, b) { return a && b; } + function or(a, b) { return a || b; } + + + /** + * Provides initial structure and state (as suitable for provision + * to the `mct-toolbar` directive) for a view's tool bar, based on + * that view's declaration of what belongs in its tool bar and on + * the current selection. + * + * @param structure toolbar structure, as provided by view definition + * @param {Array} selection the current selection state + * @constructor + */ function EditToolbar(structure, selection) { + var toolbarStructure = Object.create(structure || {}), + toolbarState, + properties = []; + + // Generate a new key for an item's property + function addKey(property) { + properties.push(property); + return properties.length - 1; // Return index of property + } + + // Update value for this property in all elements of the + // selection which have this property. + function updateProperties(property, value) { + // Update property in a selected element + function updateProperty(selected) { + // Ignore selected elements which don't have this property + if (selected[property] !== undefined) { + // Check if this is a setter, or just assignable + if (typeof selected[property] === 'function') { + selected[property](value); + } else { + selected[property] = value; + } + } + } + + // Update property in all selected elements + selection.forEach(updateProperty); + } + + // Look up the current value associated with a property + // in selection i + function lookupState(property, selected) { + var value = selected[property]; + return (typeof value === 'function') ? value() : value; + } + + // Get initial value for a given property + function initializeState(property) { + var result; + // Look through all selections for this property; + // values should all match by the time we perform + // this lookup anyway. + selection.forEach(function (selected) { + result = (selected[property] !== undefined) ? + lookupState(property, selected) : + result; + }); + return result; + } + + // Check if all elements of the selection which have this + // property have the same value for this property. + function isConsistent(property) { + var consistent = true, + observed = false, + state; + + // Check if a given element of the selection is consistent + // with previously-observed elements for this property. + function checkConsistency(selected) { + var next; + // Ignore selections which don't have this property + if (selected[property] !== undefined) { + // Look up state of this element in the selection + next = lookupState(property, selected); + // Detect inconsistency + if (observed) { + consistent = consistent && (next === state); + } + // Track state for next iteration + state = next; + observed = true; + } + } + + // Iterate through selections + selection.forEach(checkConsistency); + + return consistent; + } + + // Used to filter out items which are applicable (or not) + // to the current selection. + function isApplicable(item) { + var property = (item || {}).property, + exclusive = !(item || {}).inclusive; + + // Check if a selected item defines this property + function hasProperty(selected) { + return selected[property] !== undefined; + } + + return property && selection.map(hasProperty).reduce( + exclusive ? and : or, + exclusive + ) && isConsistent(property); + } + + // Prepare a toolbar item based on current selection + function convertItem(item) { + var converted = Object.create(item || {}); + converted.key = addKey(item.property); + return converted; + } + + // Used to filter out sections that have become empty + function nonEmpty(section) { + return section && section.items && section.items.length > 0; + } + + // Prepare a toolbar section based on current selection + function convertSection(section) { + var converted = Object.create(section || {}); + converted.items = + ((section || {}).items || []) + .map(convertItem) + .filter(isApplicable); + return converted; + } + + toolbarStructure.sections = + ((structure || {}).sections || []) + .map(convertSection) + .filter(nonEmpty); + + toolbarState = properties.map(initializeState); return { + /** + * + */ getStructure: function () { - + return toolbarStructure; }, getState: function () { - + return toolbarState; }, - updateState: function (key) { - + updateState: function (key, value) { + updateProperties(properties[key], value); } }; } diff --git a/platform/commonUI/edit/test/representers/EditToolbarSpec.js b/platform/commonUI/edit/test/representers/EditToolbarSpec.js index 3348ae04f3..de22fe03e4 100644 --- a/platform/commonUI/edit/test/representers/EditToolbarSpec.js +++ b/platform/commonUI/edit/test/representers/EditToolbarSpec.js @@ -35,8 +35,8 @@ define( testAB = { a: 0, b: 1 }; testABC = { a: 0, b: 1, c: 2 }; testABC2 = { a: 4, b: 1, c: 2 }; // For inconsistent-state checking - testABCXYZ = { a: 'A!', b: 'B!', c: 'C!', x: 'X!', y: 'Y!', z: 'Z!' }; - testABCYZ = { a: 'A!', b: 'B!', c: 'C!', y: 'Y!', z: 'Z!' }; + testABCXYZ = { a: 0, b: 1, c: 2, x: 'X!', y: 'Y!', z: 'Z!' }; + testABCYZ = { a: 0, b: 1, c: 2, y: 'Y!', z: 'Z!' }; }); it("provides properties from the original structure", function () {