diff options
-rw-r--r-- | assets/css/_schedule.less | 24 | ||||
-rw-r--r-- | assets/css/_structure.less | 5 | ||||
-rw-r--r-- | assets/js/lustiges-script.js | 44 | ||||
-rw-r--r-- | model/Schedule.php | 195 | ||||
-rw-r--r-- | template/assemblies/header.phtml | 6 | ||||
-rw-r--r-- | template/assemblies/schedule.phtml | 13 |
6 files changed, 161 insertions, 126 deletions
diff --git a/assets/css/_schedule.less b/assets/css/_schedule.less index fe512a9..852d5a4 100644 --- a/assets/css/_schedule.less +++ b/assets/css/_schedule.less @@ -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%; } diff --git a/assets/css/_structure.less b/assets/css/_structure.less index bf9e3b1..eeb1b23 100644 --- a/assets/css/_structure.less +++ b/assets/css/_structure.less @@ -55,6 +55,11 @@ nav { } } + .navbar-time { + line-height: 27px; + padding: 10px 10px; + } + .button-wrapper > .btn { width: 40px; } diff --git a/assets/js/lustiges-script.js b/assets/js/lustiges-script.js index 67ecd04..b1664f3 100644 --- a/assets/js/lustiges-script.js +++ b/assets/js/lustiges-script.js @@ -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) diff --git a/model/Schedule.php b/model/Schedule.php index d006d1f..739d2c8 100644 --- a/model/Schedule.php +++ b/model/Schedule.php @@ -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', - - '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, - ); + $gap = $this->makeEvent($daystart, $dayend); + $gap['special'] = 'gap'; + $program[$roomName][] = $gap; + + $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(); diff --git a/template/assemblies/header.phtml b/template/assemblies/header.phtml index 43c6494..f7051f1 100644 --- a/template/assemblies/header.phtml +++ b/template/assemblies/header.phtml @@ -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> diff --git a/template/assemblies/schedule.phtml b/template/assemblies/schedule.phtml index fe0ccbb..2680a44 100644 --- a/template/assemblies/schedule.phtml +++ b/template/assemblies/schedule.phtml @@ -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> |