conf-cal
Version:
Convenient calendar for conferences
271 lines (256 loc) • 7.17 kB
JavaScript
module.exports = function slotsForRooms (timeZone, rooms) {
let slots = {}
const addSlot = (slotTime, entry, room) => {
let entries = slots[slotTime]
if (!entries) {
entries = {}
slots[slotTime] = entries
}
if (entry) {
const newEntry = {
start: entry.start,
end: entry.end,
summary: entry.summary,
person: entry.person,
lang: entry.lang || null,
room: entry.room,
rowSpan: 1
}
if (entry.id) {
newEntry.id = entry.id
}
if (entry.hasCustomId) {
newEntry.hasCustomId = true
}
if (entry.description) {
newEntry.description = entry.description
}
if (entry.entries) {
newEntry.entries = entry.entries
}
entries[room] = newEntry
}
}
Object.keys(rooms).forEach((room) => {
const roomData = rooms[room]
roomData.forEach((entry) => {
addSlot(entry.start, entry, room)
addSlot(entry.end, null)
})
})
let start = null
let slotList = Object.keys(slots).sort().reduce((slotList, slot) => {
if (start) {
slotList.push({
start: start, end: slot, entries: slots[start]
})
}
start = slot
return slotList
}, [])
let roomNames = Object.keys(rooms)
// We have all the slots identified
// now we distribute slots that cover more than
// one slot over all slots covered
distributeToAllSlots(roomNames, slotList)
// We go through the beginning and end entries and reduce the only entries
// to starter entries
let groupsStart = mergeBeginningEntries(slotList)
let groupsEnd = mergeClosingEntries(slotList, groupsStart)
// We fill the rooms in other slots
// with breaks (summary = null)
insertBreaks(roomNames, slotList, groupsStart, groupsEnd)
// We apply horizontal full breaks
mergeFullBreaks(slotList, groupsStart, groupsEnd)
// We calculate the rowspan
applyRowSpan(roomNames, slotList)
return {
tz: timeZone,
rooms: roomNames,
slots: slotList
}
}
function distributeToAllSlots (roomNames, slotList) {
roomNames.forEach(room => {
var lastEntry = null
slotList.forEach(slotEntry => {
if (lastEntry && lastEntry.end <= slotEntry.start) {
lastEntry = null
}
lastEntry = slotEntry.entries[room] || lastEntry
if (lastEntry) {
slotEntry.entries[room] = lastEntry
}
})
})
}
function insertBreaks (roomNames, slotList, indexMin, indexMax) {
roomNames.forEach(room => {
let formerEntry
for (let slotIndex = 0; slotIndex < indexMax; slotIndex++) {
let slotEntry = slotList[slotIndex]
let roomEntry
if (slotEntry.entries) {
roomEntry = slotEntry.entries[room]
} else if (slotEntry.room === room) {
roomEntry = slotEntry.entry
}
if (formerEntry && formerEntry.summary !== null && formerEntry.end <= slotEntry.start) {
formerEntry = null
}
if (!roomEntry && slotIndex >= indexMin) {
roomEntry = formerEntry || {
start: slotEntry.start,
summary: null,
person: null,
lang: null,
room
}
if (roomEntry.summary === null) {
roomEntry.end = slotEntry.end
}
slotEntry.entries[room] = roomEntry
}
formerEntry = roomEntry
}
})
}
function mergeEntries (slotList, index) {
let slotEntry = slotList[index]
if (!slotEntry) {
console.warn(new Error('warn: the slotlist contains a missing entry at ' + index).stack)
return true // be fault tolerant in case of a missing entry
}
if (slotEntry.entry) {
return false
}
let rooms = Object.keys(slotEntry.entries)
if (rooms.length === 1) {
let room = rooms[0]
let roomEntry = slotEntry.entries[room]
if (roomEntry.end > slotEntry.end || roomEntry.start < slotEntry.start) {
// If a roomEntry needs to span over two slots, its better to
// not merge it and show empty slots in the other slots.
return false
}
roomEntry.rowSpan = 1
slotEntry.entry = roomEntry
slotEntry.room = room
delete slotEntry.entries
return true
}
if (rooms.length === 0) {
delete slotEntry.entries
slotEntry.entry = {
start: slotEntry.start,
end: slotEntry.end,
summary: null,
person: null,
lang: null,
room: null,
rowSpan: 1
}
slotEntry.room = null
// will be turned into a full break, next iteration
return true
}
return false
}
function mergeBeginningEntries (slotList) {
let i
let max = slotList.length
for (i = 0; i < max; i++) {
if (!mergeEntries(slotList, i)) {
return i
}
}
return i
}
function mergeClosingEntries (slotList, iMin) {
let iMax = slotList.length
for (let i = iMax - 1; i >= iMin; i--) {
if (mergeEntries(slotList, i)) {
iMax = i
} else {
return iMax
}
}
return iMax
}
function mergeFullBreaks (slotList, iMin, iMax) {
while (iMin < iMax) {
let slotEntry = slotList[iMin]
let roomNames = Object.keys(slotEntry.entries)
let nonEmptyRooms = roomNames.filter(room => slotEntry.entries[room].summary !== null)
if (nonEmptyRooms.length === 0) {
roomNames.forEach(room => {
let roomEntry = slotEntry.entries[room]
if (roomEntry.end > slotEntry.start) {
let end = roomEntry.end
roomEntry.end = slotEntry.start
if (end > slotEntry.end) {
let followEntry = {
start: slotEntry.end,
end: end,
summary: null,
person: null,
room,
lang: null
}
let i = iMin + 1
while (i < iMax) {
let followSlotEntry = slotList[i].entries[room]
if (followSlotEntry !== roomEntry) {
return
}
if (followSlotEntry.end > followEntry.end) {
throw new Error('that shouldnt be the case')
}
slotList[i].entries[room] = followEntry
i++
}
}
}
})
delete slotEntry.entries
slotEntry.entry = {
start: slotEntry.start,
end: slotEntry.end,
summary: null,
person: null,
lang: null,
room: null,
rowSpan: 1
}
slotEntry.room = null
}
iMin++
}
}
function applySingleRowSpan (entry, room, formerRooms) {
if (formerRooms[room] === entry) {
entry.rowSpan += 1
return true
}
entry.rowSpan = 1
formerRooms[room] = entry
return false
}
function applyRowSpan (roomNames, slotList) {
let formerRooms = {}
slotList.forEach(slotEntry => {
if (slotEntry.entry) {
if (applySingleRowSpan(slotEntry.entry, slotEntry.room, formerRooms)) {
delete slotEntry.entry
delete slotEntry.room
}
} else {
Object.keys(slotEntry.entries).forEach(room => {
let roomEntry = slotEntry.entries[room]
if (applySingleRowSpan(roomEntry, room, formerRooms)) {
delete slotEntry.entries[room]
}
})
}
})
}