New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add menu item order control #12362
Add menu item order control #12362
Changes from all commits
74c7695
9a0b940
6b4e5c9
479cdf0
cfb1af3
39c8bcb
70157c6
fb09e20
165916e
6c300d7
6487db1
d5b74dd
4c9cb87
bc7de6f
a99625f
c60a928
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
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, joiner) { | ||
return arrays.reduce((joined, arr, i) => { | ||
if (i > 0 && arr.length) { | ||
joined.push(joiner) | ||
} | ||
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++) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. won't that not let me increment There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. derp sorry |
||
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' | ||
|
||
// 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, { type: 'separator' }) | ||
return joined | ||
} | ||
|
||
module.exports = {sortMenuItems} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really want this to be a
reduce
(not a fan of aforEach
creating a new array)Not sure if that's ^^ the best way to do it but would love to see the
forEach
go away 😄