mirror of
https://github.com/voc/streaming-website
synced 2024-11-10 06:34:17 +00:00
preserve schedule timezones and show current time + event timezone on the website (#117)
This commit is contained in:
parent
84b8eb8048
commit
c31a16cb6d
6 changed files with 160 additions and 125 deletions
|
@ -16,19 +16,21 @@ body .schedule {
|
|||
|
||||
.now {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 150px;
|
||||
height: 100%;
|
||||
background-color: @schedule-now-bg;
|
||||
font-size: 14px;
|
||||
pointer-events: none;
|
||||
|
||||
height: 100%;
|
||||
display: flex;
|
||||
left: 0;
|
||||
z-index: 5;
|
||||
|
||||
span {
|
||||
display: block;
|
||||
position: absolute;
|
||||
right: -28px;
|
||||
.overlay {
|
||||
width: 150px;
|
||||
height: 100%;
|
||||
background-color: @schedule-now-bg;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 14px;
|
||||
padding-left: 5px;
|
||||
color: @schedule-now;
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +52,7 @@ body .schedule {
|
|||
|
||||
.inner {
|
||||
display: block;
|
||||
padding: 10px;
|
||||
padding: 15px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
|
|
@ -55,6 +55,11 @@ nav {
|
|||
}
|
||||
}
|
||||
|
||||
.navbar-time {
|
||||
line-height: 27px;
|
||||
padding: 10px 10px;
|
||||
}
|
||||
|
||||
.button-wrapper > .btn {
|
||||
width: 40px;
|
||||
}
|
||||
|
|
|
@ -127,6 +127,7 @@ $(function() {
|
|||
$(function() {
|
||||
var
|
||||
$schedule = $('body .schedule'),
|
||||
$time = $('.navbar-time'),
|
||||
$now = $schedule.find('.now'),
|
||||
scrollLock = false,
|
||||
rewindTimeout,
|
||||
|
@ -154,6 +155,23 @@ $(function() {
|
|||
}
|
||||
});
|
||||
|
||||
function formatLocalTime(timestamp, offset) {
|
||||
const d = new Date(timestamp * 1000);
|
||||
|
||||
// js timezone offset is negative
|
||||
const diff = -d.getTimezoneOffset() - offset;
|
||||
d.setUTCMinutes(d.getUTCMinutes() - diff);
|
||||
|
||||
return String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0');
|
||||
}
|
||||
|
||||
function formatOffset(offset) {
|
||||
const sign = offset < 0 ? "-" : "+";
|
||||
const hours = String(Math.floor(Math.abs(offset)/60)).padStart(2, '0');
|
||||
const minutes = String(Math.abs(offset)%60).padStart(2, '0');
|
||||
return sign + hours + ":" + minutes;
|
||||
}
|
||||
|
||||
// schedule now-marker & scrolling
|
||||
function updateProgramView(initial) {
|
||||
var
|
||||
|
@ -167,12 +185,22 @@ $(function() {
|
|||
.find('.room')
|
||||
.first()
|
||||
.find('.block')
|
||||
.filter(function(i, el) {
|
||||
.filter(function(i, el) {
|
||||
return $(this).data('start') < now;
|
||||
}).last();
|
||||
|
||||
if($block.length == 0)
|
||||
return $now.css('width', 0);
|
||||
// if we are yet to start find first block as reference
|
||||
if (!$block.length)
|
||||
$block = $schedule
|
||||
.find('.room').first()
|
||||
.find('.block').first();
|
||||
|
||||
// still no luck
|
||||
if(!$block.length) {
|
||||
$now.find('.overlay').css('width', 0);
|
||||
$now.find('.label').text('now');
|
||||
return;
|
||||
}
|
||||
|
||||
var
|
||||
// start & end-timestamp
|
||||
|
@ -195,15 +223,19 @@ $(function() {
|
|||
px_in_display = px - scrollx;
|
||||
|
||||
//console.log($block.get(0), new Date(start*1000), new Date(now*1000), new Date(end*1000), normalized, px);
|
||||
$now.css('width', px);
|
||||
const eventOffset = parseFloat($block.data('offset')) || 0;
|
||||
const eventTime = formatLocalTime(now, eventOffset);
|
||||
$now.find('.overlay').css('width', px);
|
||||
$now.find('.label').text("now (" + eventTime + ")");
|
||||
$time.text(eventTime + " (" + formatOffset(eventOffset) + ")");
|
||||
|
||||
// scrolling is locked by manual interaction
|
||||
if(scrollLock)
|
||||
if (scrollLock)
|
||||
return;
|
||||
|
||||
if(
|
||||
// now marker is > 2/3 of the schedule-display-width
|
||||
px_in_display > (displayw * 2/3) ||
|
||||
px_in_display > (displayw * 2/3) ||
|
||||
|
||||
// now marker is <1/7 of the schedule-display-width
|
||||
px_in_display < (displayw/7)
|
||||
|
|
|
@ -89,31 +89,32 @@ class Schedule
|
|||
// so to be on the safer side we calculate our own daystart/end here
|
||||
foreach($schedule->day as $day)
|
||||
{
|
||||
$daystart = PHP_INT_MAX;
|
||||
$dayend = 0;
|
||||
$daystart = new DateTimeImmutable('+100 year');
|
||||
$dayend = new DateTimeImmutable('-100 year');
|
||||
|
||||
foreach($day->room as $room)
|
||||
{
|
||||
$roomName = (string)$room['name'];
|
||||
if($this->isRoomFiltered($roomName))
|
||||
continue;
|
||||
|
||||
|
||||
if(!in_array($roomName, $rooms))
|
||||
$rooms[] = $roomName;
|
||||
|
||||
foreach($room->event as $event)
|
||||
{
|
||||
$start = strtotime((string)$event->date);
|
||||
$duration = $this->strToDuration((string)$event->duration);
|
||||
$end = $start + $duration;
|
||||
$start = new DateTimeImmutable((string)$event->date);
|
||||
$interval = $this->strToInterval((string)$event->duration);
|
||||
$end = $start->add($interval);
|
||||
|
||||
$daystart = min($daystart, $start);
|
||||
$dayend = max($dayend, $end);
|
||||
$daystart = $start < $daystart ? $start : $daystart;
|
||||
$dayend = $end > $dayend ? $end : $dayend;
|
||||
}
|
||||
}
|
||||
|
||||
$day['start'] = $daystart;
|
||||
$day['end'] = $dayend;
|
||||
// stringify again to store in simplexml
|
||||
$day['start'] = $daystart->format('c');
|
||||
$day['end'] = $dayend->format('c');
|
||||
}
|
||||
|
||||
|
||||
|
@ -123,23 +124,23 @@ class Schedule
|
|||
$daysSorted[] = $day;
|
||||
}
|
||||
|
||||
usort($daysSorted, function($a, $b) {
|
||||
return (int)$a['start'] - (int)$b['start'];
|
||||
usort($daysSorted, function($a, $b): int {
|
||||
return strcmp($a['start'], $b['start']);
|
||||
});
|
||||
|
||||
$dayidx = 0;
|
||||
foreach($daysSorted as $day)
|
||||
{
|
||||
$dayidx++;
|
||||
$daystart = (int)$day['start'];
|
||||
$dayend = (int)$day['end'];
|
||||
$daystart = new DateTimeImmutable($day['start']);
|
||||
$dayend = new DateTimeImmutable($day['end']);
|
||||
|
||||
$roomidx = 0;
|
||||
foreach($rooms as $roomName)
|
||||
{
|
||||
$roomidx++;
|
||||
$laststart = false;
|
||||
$lastend = false;
|
||||
$laststart = NULL;
|
||||
$lastend = NULL;
|
||||
|
||||
if($this->isRoomFiltered($roomName))
|
||||
continue;
|
||||
|
@ -147,24 +148,16 @@ class Schedule
|
|||
$result = $day->xpath("room[@name='".$roomName."']");
|
||||
if(!$result) {
|
||||
// this room has no events on this day -> add long gap
|
||||
$program[$roomName][] = array(
|
||||
'special' => 'gap',
|
||||
$gap = $this->makeEvent($daystart, $dayend);
|
||||
$gap['special'] = 'gap';
|
||||
$program[$roomName][] = $gap;
|
||||
|
||||
'fstart' => date('c', $daystart),
|
||||
'fend' => date('c', $dayend),
|
||||
|
||||
'start' => $daystart,
|
||||
'end' => $dayend,
|
||||
'duration' => $dayend - $daystart,
|
||||
);
|
||||
$program[$roomName][] = array(
|
||||
'special' => 'daychange',
|
||||
'title' => 'Daychange from Day '.$dayidx.' to '.($dayidx+1),
|
||||
|
||||
'start' => $dayend,
|
||||
'end' => (int)$schedule->day[$dayidx]['start'],
|
||||
'duration' => 60*60,
|
||||
);
|
||||
$end = new DateTimeImmutable($schedule->day[$dayidx]['start']);
|
||||
$daychange = $this->makeEvent($dayend, $end);
|
||||
$daychange['special'] = 'daychange';
|
||||
$daychange['title'] = 'Daychange from Day '.$dayidx.' to '.($dayidx+1);
|
||||
$daychange['duration'] = 3600;
|
||||
$program[$roomName][] = $daychange;
|
||||
continue;
|
||||
}
|
||||
$room = $result[0];
|
||||
|
@ -184,103 +177,74 @@ class Schedule
|
|||
|
||||
foreach($eventsSorted as $event)
|
||||
{
|
||||
$start = strtotime((string)$event->date);
|
||||
$duration = $this->strToDuration((string)$event->duration);
|
||||
$end = $start + $duration;
|
||||
$start = new DateTimeImmutable((string)$event->date);
|
||||
$interval = $this->strToInterval((string)$event->duration);
|
||||
$end = $start->add($interval);
|
||||
|
||||
// skip duplicate events in fahrplan source
|
||||
if ( $laststart == $start )
|
||||
continue;
|
||||
continue;
|
||||
|
||||
if($lastend && $lastend < $start)
|
||||
{
|
||||
// synthesize pause event
|
||||
$pauseduration = $start - $lastend;
|
||||
$program[$roomName][] = array(
|
||||
'special' => 'pause',
|
||||
'title' => round($pauseduration / 60).' minutes pause',
|
||||
|
||||
'fstart' => date('c', $lastend),
|
||||
'fend' => date('c', $start),
|
||||
|
||||
'start' => $lastend,
|
||||
'end' => $start,
|
||||
'duration' => $pauseduration,
|
||||
'room_known' => $this->isRoomMapped($roomName),
|
||||
);
|
||||
// pause between talks
|
||||
$pause = $this->makeEvent($lastend, $start);
|
||||
$pause['special'] = 'pause';
|
||||
$pause['title'] = round($pause['duration'] / 60).' minutes pause';
|
||||
$pause['room_known'] = $this->isRoomMapped($roomName);
|
||||
$program[$roomName][] = $pause;
|
||||
}
|
||||
else if(!$lastend && $daystart < $start)
|
||||
{
|
||||
$program[$roomName][] = array(
|
||||
'special' => 'gap',
|
||||
|
||||
'fstart' => date('c', $daystart),
|
||||
'fend' => date('c', $start),
|
||||
|
||||
'start' => $daystart,
|
||||
'end' => $start,
|
||||
'duration' => $start - $daystart,
|
||||
'room_known' => $this->isRoomMapped($roomName),
|
||||
);
|
||||
// gap before first talk
|
||||
$gap = $this->makeEvent($daystart, $start);
|
||||
$gap['special'] = 'gap';
|
||||
$gap['room_known'] = $this->isRoomMapped($roomName);
|
||||
$program[$roomName][] = $gap;
|
||||
}
|
||||
|
||||
$personnames = array();
|
||||
if(isset($event->persons)) foreach($event->persons->person as $person)
|
||||
$personnames[] = (string)$person;
|
||||
|
||||
$program[$roomName][] = array(
|
||||
'title' => (string)$event->title,
|
||||
'speaker' => implode(', ', $personnames),
|
||||
|
||||
'fstart' => date('c', $start),
|
||||
'fend' => date('c', $end),
|
||||
|
||||
'start' => $start,
|
||||
'end' => $end,
|
||||
'duration' => $duration,
|
||||
'room_known' => $this->isRoomMapped($roomName),
|
||||
'optout' => $this->isOptout($event),
|
||||
);
|
||||
// normal talk
|
||||
$talk = $this->makeEvent($start, $end);
|
||||
$talk['title'] = (string)$event->title;
|
||||
$talk['speaker'] = implode(', ', $personnames);
|
||||
$talk['room_known'] = $this->isRoomMapped($roomName);
|
||||
$talk['optout'] = $this->isOptout($event);
|
||||
$program[$roomName][] = $talk;
|
||||
|
||||
$laststart = $start;
|
||||
$lastend = $end;
|
||||
}
|
||||
|
||||
// synthesize daychange event
|
||||
if(!$lastend) $lastend = $daystart;
|
||||
if($lastend < $dayend)
|
||||
{
|
||||
$program[$roomName][] = array(
|
||||
'special' => 'gap',
|
||||
|
||||
'fstart' => date('c', $lastend),
|
||||
'fend' => date('c', $dayend),
|
||||
|
||||
'start' => $lastend,
|
||||
'end' => $dayend,
|
||||
'duration' => $dayend - $lastend,
|
||||
);
|
||||
// gap after last talk
|
||||
$gap = $this->makeEvent($lastend, $dayend);
|
||||
$gap['special'] = 'gap';
|
||||
$program[$roomName][] = $gap;
|
||||
}
|
||||
|
||||
if($dayidx < count($schedule->day))
|
||||
{
|
||||
$program[$roomName][] = array(
|
||||
'special' => 'daychange',
|
||||
'title' => 'Daychange from Day '.$dayidx.' to '.($dayidx+1),
|
||||
|
||||
'start' => $dayend,
|
||||
'end' => (int)$schedule->day[$dayidx]['start'],
|
||||
'duration' => 60*60,
|
||||
);
|
||||
// daychange
|
||||
$end = new DateTimeImmutable($schedule->day[$dayidx]['start']);
|
||||
$daychange = $this->makeEvent($dayend, $end);
|
||||
$daychange['special'] = 'daychange';
|
||||
$daychange['title'] = 'Daychange from Day '.$dayidx.' to '.($dayidx+1);
|
||||
$daychange['duration'] = 3600;
|
||||
$program[$roomName][] = $daychange;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$mapping = $this->getScheduleToRoomSlugMapping();
|
||||
if($this->getConference()->has('SCHEDULE.ROOMFILTER'))
|
||||
{
|
||||
// sort by roomfilter
|
||||
// determine roomfilter
|
||||
$roomfilter = $this->getConference()->get('SCHEDULE.ROOMFILTER');
|
||||
|
||||
// map roomfilter-rooms to room-slugs
|
||||
|
@ -291,7 +255,7 @@ class Schedule
|
|||
return $e;
|
||||
}, $roomfilter);
|
||||
|
||||
// sort according to roomtilter ordering
|
||||
// sort according to roomfilter ordering
|
||||
uksort($program, function($a, $b) use ($roomfilter) {
|
||||
return array_search($a, $roomfilter) - array_search($b, $roomfilter);
|
||||
});
|
||||
|
@ -300,6 +264,32 @@ class Schedule
|
|||
return $program;
|
||||
}
|
||||
|
||||
private function makeEvent(DateTimeImmutable $from, DateTimeImmutable $to): array {
|
||||
return array(
|
||||
'fstart' => $from->format('c'),
|
||||
'fend' => $to->format('c'),
|
||||
'tstart' => $from->format('H:i'),
|
||||
'tend' => $to->format('H:i'),
|
||||
|
||||
'start' => $from->getTimestamp(),
|
||||
'end' => $to->getTimestamp(),
|
||||
'offset' => $from->getOffset(),
|
||||
'duration' => $to->getTimestamp() - $from->getTimestamp(),
|
||||
);
|
||||
}
|
||||
|
||||
private function intervalToDuration(DateInterval $interval): int {
|
||||
$one = new DateTimeImmutable();
|
||||
$two = $one->add($interval);
|
||||
return $two->getTimestamp() - $one->getTimestamp();
|
||||
}
|
||||
|
||||
private function strToInterval(string $str): DateInterval
|
||||
{
|
||||
$parts = explode(':', $str);
|
||||
return new DateInterval('PT'.$parts[0].'H'.$parts[1].'M');
|
||||
}
|
||||
|
||||
|
||||
public function getDurationSum()
|
||||
{
|
||||
|
@ -312,23 +302,18 @@ class Schedule
|
|||
}
|
||||
|
||||
|
||||
|
||||
private function strToDuration($str)
|
||||
{
|
||||
$parts = explode(':', $str);
|
||||
return ((int)$parts[0] * 60 + (int)$parts[1]) * 60;
|
||||
}
|
||||
|
||||
public function getScheduleUrl()
|
||||
{
|
||||
return $this->getConference()->get('SCHEDULE.URL');
|
||||
}
|
||||
|
||||
|
||||
public function getScheduleCache()
|
||||
{
|
||||
return sprintf('/tmp/schedule-cache-%s.xml', $this->getConference()->getSlug());
|
||||
}
|
||||
|
||||
|
||||
public function getScheduleToRoomSlugMapping()
|
||||
{
|
||||
$mapping = array();
|
||||
|
|
|
@ -22,6 +22,12 @@
|
|||
<span class="fa fa-info"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<? if(isset($room) && $room->hasSchedule()): ?>
|
||||
<div class="navbar-right navbar-time">
|
||||
Current Time
|
||||
</div>
|
||||
<? endif ?>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
<div class="schedule scroll-container">
|
||||
<div class="scroll-element">
|
||||
<div class="now"><span>now</span></div>
|
||||
<? $totalWidth = round($schedule->getDurationSum() / $schedule->getScale()) ?>
|
||||
<div class="now" style="width: <?= h($totalWidth) ?>px">
|
||||
<div class="overlay"></div>
|
||||
<div class="label">now</div>
|
||||
</div>
|
||||
<? $rooms = $schedule->getSchedule() ?>
|
||||
<? foreach($rooms as $roomname => $events): ?>
|
||||
<? $scheduleRoom = $schedule->getMappedRoom($roomname) ?>
|
||||
<div class="room <? if(isset($room) && $roomname == $room->getScheduleName()): ?>highlight<? endif ?>" style="width: <?=round($schedule->getDurationSum() / $schedule->getScale())?>px">
|
||||
<div class="room <? if(isset($room) && $roomname == $room->getScheduleName()): ?>highlight<? endif ?>" style="width: <?= h($totalWidth) ?>px">
|
||||
<? $fromstart = 0; ?>
|
||||
<? foreach($events as $event): ?>
|
||||
<div
|
||||
|
@ -12,6 +16,7 @@
|
|||
style="width: <?=h(round($event['duration'] / $schedule->getScale()))?>px; left: <?=h(round($fromstart / $schedule->getScale()))?>px"
|
||||
data-start="<?=intval($event['start'])?>"
|
||||
data-end="<?=intval($event['end'])?>"
|
||||
data-offset="<?=intval($event['offset']/60)?>"
|
||||
>
|
||||
<? $fromstart += $event['duration'] ?>
|
||||
<? if($scheduleRoom): ?>
|
||||
|
@ -42,9 +47,9 @@
|
|||
|
||||
<? else: ?>
|
||||
<? if($event['duration'] > 10*60): /* only display when event is longer as 10 minutes */ ?>
|
||||
<h4><?=h(strftime('%H:%M', $event['start']))?>
|
||||
<h4><?=h($event['tstart'])?>
|
||||
–
|
||||
<?=h(strftime('%H:%M', $event['end']))?>
|
||||
<?=h($event['tend'])?>
|
||||
in
|
||||
<?=h($scheduleRoom ? $scheduleRoom->getDisplayShort() : $roomname) ?>
|
||||
</h4>
|
||||
|
|
Loading…
Reference in a new issue