mirror of
https://gitlab.com/bramw/baserow.git
synced 2024-11-21 23:37:55 +00:00
160 lines
4.8 KiB
JavaScript
160 lines
4.8 KiB
JavaScript
/**
|
|
* This helper method will create an array of slots, where every slot contains one of
|
|
* the provided `items`. Every slot has a unique `id` and when the items change, the
|
|
* slots are recycled to avoid re-render. Every item that persists will it's own
|
|
* slot id.
|
|
*
|
|
* This is useful so virtual scroll. The `id` of the slot can be used to as
|
|
* `:key="slot.id"` value in the template. Because the id will always match the item
|
|
* id, it will never update or re-render the component. New items will get a
|
|
* recycled slot id so that they don't have to be re-rendered.
|
|
*
|
|
* It is a requirement that every item has an `id` property to make sure the id of
|
|
* the slot is properly recycled.
|
|
*
|
|
* Optionally a `getPosition` can be provided to set the `position` property in each
|
|
* slot. This is typically used by virtual scrolling change the absolute position of
|
|
* the dom element. This could be needed because the elements before are not rendered
|
|
* and are not pushing it into the right position.
|
|
*
|
|
* Example:
|
|
*
|
|
* const slots = recycleSlots(
|
|
* [],
|
|
* [
|
|
* { id: 1, name: "Item 1" },
|
|
* { id: 2, name: "Item 2" }
|
|
* ]
|
|
* ) == [
|
|
* { id: 0, position: {}, item: { id: 1, name: "Item 1" } },
|
|
* { id: 1, position: {}, item: { id: 2, name: "Item 2" } }
|
|
* ]
|
|
*
|
|
* recycleSlots(
|
|
* slots,
|
|
* [
|
|
* { id: 2, name: "Item 2" },
|
|
* { id: 3, name: "Item 3" }
|
|
* ]
|
|
* ) == [
|
|
* { id: 0, position: {}, item: { id: 3, name: "Item 3" } },
|
|
* { id: 1, position: {}, item: { id: 2, name: "Item 2" } }
|
|
* ]
|
|
*/
|
|
export const recycleSlots = (slots, items, getPosition, min = items.length) => {
|
|
// If there are more items than the minimum that must be rendered, we want to
|
|
// increase the minimum to ensure all items are visible.
|
|
if (min < items.length) {
|
|
min = items.length
|
|
}
|
|
|
|
for (let i = slots.length; i < min; i++) {
|
|
slots.push({
|
|
id: i,
|
|
position: {},
|
|
item: undefined,
|
|
})
|
|
}
|
|
|
|
// Remove slots that aren't needed anymore.
|
|
if (slots.length > min) {
|
|
slots.splice(items.length, min)
|
|
}
|
|
|
|
const emptySlots = []
|
|
const itemSlotIndexMap = {}
|
|
|
|
// Loop over the slots and clear the items that must not be rendered anymore.
|
|
slots.forEach((slot, index) => {
|
|
if (slot.item === undefined || slot.item === null) {
|
|
emptySlots.push(index)
|
|
return
|
|
}
|
|
|
|
const itemIndex = items.findIndex(
|
|
(item) => item !== null && item.id === slot.item.id
|
|
)
|
|
|
|
if (itemIndex < 0) {
|
|
// Slot item is not visible anymore.
|
|
slot.item = undefined
|
|
slot.position = {}
|
|
emptySlots.push(index)
|
|
} else {
|
|
// Item is found in slot array.
|
|
itemSlotIndexMap[items[itemIndex].id] = index
|
|
}
|
|
})
|
|
|
|
// Loop over the items and assign them to a slot if they don't yet exist.
|
|
items.forEach((item, position) => {
|
|
let index = itemSlotIndexMap[item?.id]
|
|
|
|
// If item isn't in a slot yet we use the first empty slot index.
|
|
if (index === undefined) {
|
|
index = emptySlots.shift()
|
|
}
|
|
|
|
// Only update the item and position if it has changed in the slot to avoid
|
|
// re-renders.
|
|
if (slots[index].item !== item) {
|
|
slots[index].item = item
|
|
}
|
|
|
|
const slotPosition = getPosition(item, position)
|
|
if (
|
|
JSON.stringify(slotPosition) !== JSON.stringify(slots[index].position)
|
|
) {
|
|
slots[index].position = slotPosition
|
|
}
|
|
})
|
|
|
|
// The remaining empty slots must be cleared because they could contain old items.
|
|
emptySlots.forEach((slotIndex) => {
|
|
slots[slotIndex].item = undefined
|
|
slots[slotIndex].position = {}
|
|
})
|
|
}
|
|
|
|
/**
|
|
* This function will order the slots based on the item position in items array. The
|
|
* slots will be moved without recreating the array to prevent re-rendering.
|
|
*/
|
|
export const orderSlots = (slots, items) => {
|
|
let i = 0
|
|
while (i < items.length) {
|
|
const item = items[i]
|
|
|
|
// Items can be null until they're fetched from server.
|
|
if (item === null) {
|
|
i++
|
|
continue
|
|
}
|
|
|
|
const existingIndex = slots.findIndex(
|
|
(slot) =>
|
|
slot.item !== null &&
|
|
slot.item !== undefined &&
|
|
slot.item.id === item.id
|
|
)
|
|
|
|
if (existingIndex > -1 && existingIndex !== i) {
|
|
// If the item already exists in the slots array, but the position match yet,
|
|
// we need to move it.
|
|
if (existingIndex < i) {
|
|
// In this case, the existing index is lower than the new index, so in order
|
|
// avoid conflicts with already moved items we just swap them.
|
|
slots.splice(i, 0, slots.splice(existingIndex, 1)[0])
|
|
slots.splice(existingIndex, 0, slots.splice(i - 1, 1)[0])
|
|
i++
|
|
} else if (existingIndex > i) {
|
|
// If the existing index is higher than the expected index, we need to move
|
|
// it one by one to avoid conflicts with already moved items.
|
|
slots.splice(existingIndex - 1, 0, slots.splice(existingIndex, 1)[0])
|
|
}
|
|
} else {
|
|
i++
|
|
}
|
|
}
|
|
}
|