123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- 'use strict'
- function splitArray (arr, predicate) {
- const result = arr.reduce((multi, item) => {
- const current = multi[multi.length - 1]
- if (predicate(item)) {
- if (current.length > 0) multi.push([])
- } else {
- current.push(item)
- }
- return multi
- }, [[]])
- if (result[result.length - 1].length === 0) {
- return result.slice(0, result.length - 1)
- }
- return result
- }
- function joinArrays (arrays, joinIDs) {
- return arrays.reduce((joined, arr, i) => {
- if (i > 0 && arr.length) {
- if (joinIDs.length > 0) {
- joined.push(joinIDs[0])
- joinIDs.splice(0, 1)
- } else {
- joined.push({ type: 'separator' })
- }
- }
- return joined.concat(arr)
- }, [])
- }
- function pushOntoMultiMap (map, key, value) {
- if (!map.has(key)) {
- map.set(key, [])
- }
- map.get(key).push(value)
- }
- function indexOfGroupContainingID (groups, id, ignoreGroup) {
- return groups.findIndex(
- candidateGroup =>
- candidateGroup !== ignoreGroup &&
- candidateGroup.some(
- candidateItem => candidateItem.id === id
- )
- )
- }
- // Sort nodes topologically using a depth-first approach. Encountered cycles
- // are broken.
- function sortTopologically (originalOrder, edgesById) {
- const sorted = []
- const marked = new Set()
- const visit = (mark) => {
- if (marked.has(mark)) return
- marked.add(mark)
- const edges = edgesById.get(mark)
- if (edges != null) {
- edges.forEach(visit)
- }
- sorted.push(mark)
- }
- originalOrder.forEach(visit)
- return sorted
- }
- function attemptToMergeAGroup (groups) {
- for (let i = 0; i < groups.length; i++) {
- const group = groups[i]
- for (const item of group) {
- const toIDs = [...(item.before || []), ...(item.after || [])]
- for (const id of toIDs) {
- const index = indexOfGroupContainingID(groups, id, group)
- if (index === -1) continue
- const mergeTarget = groups[index]
- mergeTarget.push(...group)
- groups.splice(i, 1)
- return true
- }
- }
- }
- return false
- }
- function mergeGroups (groups) {
- let merged = true
- while (merged) {
- merged = attemptToMergeAGroup(groups)
- }
- return groups
- }
- function sortItemsInGroup (group) {
- const originalOrder = group.map((node, i) => i)
- const edges = new Map()
- const idToIndex = new Map(group.map((item, i) => [item.id, i]))
- group.forEach((item, i) => {
- if (item.before) {
- item.before.forEach(toID => {
- const to = idToIndex.get(toID)
- if (to != null) {
- pushOntoMultiMap(edges, to, i)
- }
- })
- }
- if (item.after) {
- item.after.forEach(toID => {
- const to = idToIndex.get(toID)
- if (to != null) {
- pushOntoMultiMap(edges, i, to)
- }
- })
- }
- })
- const sortedNodes = sortTopologically(originalOrder, edges)
- return sortedNodes.map(i => group[i])
- }
- function findEdgesInGroup (groups, i, edges) {
- const group = groups[i]
- for (const item of group) {
- if (item.beforeGroupContaining) {
- for (const id of item.beforeGroupContaining) {
- const to = indexOfGroupContainingID(groups, id, group)
- if (to !== -1) {
- pushOntoMultiMap(edges, to, i)
- return
- }
- }
- }
- if (item.afterGroupContaining) {
- for (const id of item.afterGroupContaining) {
- const to = indexOfGroupContainingID(groups, id, group)
- if (to !== -1) {
- pushOntoMultiMap(edges, i, to)
- return
- }
- }
- }
- }
- }
- function sortGroups (groups) {
- const originalOrder = groups.map((item, i) => i)
- const edges = new Map()
- for (let i = 0; i < groups.length; i++) {
- findEdgesInGroup(groups, i, edges)
- }
- const sortedGroupIndexes = sortTopologically(originalOrder, edges)
- return sortedGroupIndexes.map(i => groups[i])
- }
- function sortMenuItems (menuItems) {
- const isSeparator = (item) => item.type === 'separator'
- const separators = menuItems.filter(i => i.type === 'separator')
- // Split the items into their implicit groups based upon separators.
- const groups = splitArray(menuItems, isSeparator)
- const mergedGroups = mergeGroups(groups)
- const mergedGroupsWithSortedItems = mergedGroups.map(sortItemsInGroup)
- const sortedGroups = sortGroups(mergedGroupsWithSortedItems)
- const joined = joinArrays(sortedGroups, separators)
- return joined
- }
- module.exports = { sortMenuItems }
|