From 6ddaf76be7efd6d97a7d05513c2aaa7e917274d3 Mon Sep 17 00:00:00 2001
From: uhensler <urs.hensler@frentix.com>
Date: Mon, 3 Jun 2019 15:33:29 +0200
Subject: [PATCH] OO-4043: Peekview of live stream course node

---
 .../course/nodes/LiveStreamCourseNode.java    |   8 +
 .../ui/LiveStreamPeekviewController.java      | 179 ++++++++++++++++++
 .../livestream/ui/_content/peekview.html      |  38 ++++
 .../ui/_i18n/LocalStrings_de.properties       |   5 +
 .../ui/_i18n/LocalStrings_en.properties       |   5 +
 5 files changed, 235 insertions(+)
 create mode 100644 src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamPeekviewController.java
 create mode 100644 src/main/java/org/olat/course/nodes/livestream/ui/_content/peekview.html

diff --git a/src/main/java/org/olat/course/nodes/LiveStreamCourseNode.java b/src/main/java/org/olat/course/nodes/LiveStreamCourseNode.java
index 436e7673f2c..bb478e1d798 100644
--- a/src/main/java/org/olat/course/nodes/LiveStreamCourseNode.java
+++ b/src/main/java/org/olat/course/nodes/LiveStreamCourseNode.java
@@ -40,6 +40,7 @@ import org.olat.course.nodes.livestream.LiveStreamModule;
 import org.olat.course.nodes.livestream.LiveStreamSecurityCallback;
 import org.olat.course.nodes.livestream.LiveStreamSecurityCallbackFactory;
 import org.olat.course.nodes.livestream.ui.LiveStreamEditController;
+import org.olat.course.nodes.livestream.ui.LiveStreamPeekviewController;
 import org.olat.course.nodes.livestream.ui.LiveStreamRunController;
 import org.olat.course.run.navigation.NodeRunConstructionResult;
 import org.olat.course.run.userview.NodeEvaluation;
@@ -100,6 +101,13 @@ public class LiveStreamCourseNode extends AbstractAccessableCourseNode {
 		Controller ctrl = TitledWrapperHelper.getWrapper(ureq, wControl, runCtrl, this, "o_livestream_icon");
 		return new NodeRunConstructionResult(ctrl);
 	}
+	
+	@Override
+	public Controller createPeekViewRunController(UserRequest ureq, WindowControl wControl,
+			UserCourseEnvironment userCourseEnv, NodeEvaluation ne) {
+		CourseCalendars calendars = CourseCalendars.createCourseCalendarsWrapper(ureq, wControl, userCourseEnv, ne);
+		return new LiveStreamPeekviewController(ureq, wControl, getIdent(), this.getModuleConfiguration(), calendars);
+	}
 
 	@SuppressWarnings("deprecation")
 	@Override
diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamPeekviewController.java b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamPeekviewController.java
new file mode 100644
index 00000000000..a0c1a90ac5d
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamPeekviewController.java
@@ -0,0 +1,179 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.course.nodes.livestream.ui;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.commons.lang.time.DateUtils;
+import org.olat.commons.calendar.CalendarUtils;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.Component;
+import org.olat.core.gui.components.htmlsite.OlatCmdEvent;
+import org.olat.core.gui.components.link.Link;
+import org.olat.core.gui.components.link.LinkFactory;
+import org.olat.core.gui.components.velocity.VelocityContainer;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.Event;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.controller.BasicController;
+import org.olat.core.util.StringHelper;
+import org.olat.course.nodes.LiveStreamCourseNode;
+import org.olat.course.nodes.cal.CourseCalendars;
+import org.olat.course.nodes.livestream.LiveStreamEvent;
+import org.olat.course.nodes.livestream.LiveStreamService;
+import org.olat.modules.ModuleConfiguration;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 3 Jun 2019<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public class LiveStreamPeekviewController extends BasicController implements Controller {
+	
+	private final VelocityContainer mainVC;
+	private Link nodeLink;
+	
+	private final String nodeId;
+	
+	@Autowired
+	private LiveStreamService liveStreamService;
+
+	public LiveStreamPeekviewController(UserRequest ureq, WindowControl wControl, String nodeId,
+			ModuleConfiguration moduleConfiguration, CourseCalendars calendars) {
+		super(ureq, wControl);
+		this.nodeId = nodeId;
+		
+		int bufferBeforeMin = moduleConfiguration.getIntegerSafe(LiveStreamCourseNode.CONFIG_BUFFER_BEFORE_MIN, 0);
+		int bufferAfterMin = moduleConfiguration.getIntegerSafe(LiveStreamCourseNode.CONFIG_BUFFER_AFTER_MIN, 0);
+		
+		mainVC = createVelocityContainer("peekview");
+		List<? extends LiveStreamEvent> runningEvents = liveStreamService.getRunningEvents(calendars, bufferBeforeMin, bufferAfterMin);
+		List<EventWrapper> events;
+		if (!runningEvents.isEmpty()) {
+			events = wrapEvents(runningEvents, true);
+			nodeLink = LinkFactory.createLink("peekview.open.live", mainVC, this);
+			nodeLink.setIconRightCSS("o_icon o_icon_start");
+			nodeLink.setElementCssClass("pull-right");
+		} else {
+			List<? extends LiveStreamEvent> upcomingEvents = liveStreamService.getUpcomingEvents(calendars, bufferBeforeMin);
+			if (!upcomingEvents.isEmpty()) {
+				events = new ArrayList<>(1);
+				events.add(wrapEvent(upcomingEvents.get(0), false));
+				nodeLink = LinkFactory.createLink("peekview.open.upcoming", mainVC, this);
+				nodeLink.setIconRightCSS("o_icon o_icon_start");
+				nodeLink.setElementCssClass("pull-right");
+			} else {
+				events = new ArrayList<>(0);
+			}
+		}
+		mainVC.contextPut("events", events);
+		
+		putInitialPanel(mainVC);
+	}
+
+	private List<EventWrapper> wrapEvents(List<? extends LiveStreamEvent> events, boolean live) {
+		List<EventWrapper> wrappedEvents = new ArrayList<>(events.size());
+		for (LiveStreamEvent event : events) {
+			wrappedEvents.add(wrapEvent(event, live));
+		}
+		return wrappedEvents;
+	}
+
+	private EventWrapper wrapEvent(LiveStreamEvent event, boolean live) {
+		String titleI18n = live? "peekview.title.live": "peekview.title.upcoming";
+		String title = translate(titleI18n, new String[] {event.getSubject()});
+		
+		Locale locale = getLocale();
+		Calendar cal = CalendarUtils.createCalendarInstance(locale);
+		Date begin = event.getBegin();
+		Date end = event.getEnd();
+		cal.setTime(begin);
+		StringBuilder dateSb = new StringBuilder();
+		dateSb.append(StringHelper.formatLocaleDateFull(begin.getTime(), locale));
+		String date  = dateSb.toString();
+		
+		String time = null;
+		if (!event.isAllDayEvent()) {
+			StringBuilder timeSb = new StringBuilder();
+			timeSb.append(StringHelper.formatLocaleTime(begin.getTime(), locale));
+			timeSb.append(" - ");
+			if (!DateUtils.isSameDay(begin, end)) {
+				timeSb.append(StringHelper.formatLocaleDateFull(end.getTime(), locale)).append(", ");
+			} 
+			timeSb.append(StringHelper.formatLocaleTime(end.getTime(), locale));
+			time = timeSb.toString();
+		}
+
+		String location = event.getLocation();
+		
+		return new EventWrapper(title, date, time, location);
+	}
+
+	@Override
+	protected void event(UserRequest ureq, Component source, Event event) {
+		if (source == nodeLink) {
+			fireEvent(ureq, new OlatCmdEvent(OlatCmdEvent.GOTONODE_CMD, nodeId));
+		}
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+	
+	public final static class EventWrapper {
+		
+		private final String title;
+		private final String date;
+		private final String time;
+		private final String location;
+
+		private EventWrapper(String title, String date, String time, String location) {
+			this.title = title;
+			this.date = date;
+			this.time = time;
+			this.location = location;
+		}
+
+		public String getTitle() {
+			return title;
+		}
+
+		public String getDate() {
+			return date;
+		}
+
+		public String getTime() {
+			return time;
+		}
+
+		public String getLocation() {
+			return location;
+		}
+		
+	}
+
+}
diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/_content/peekview.html b/src/main/java/org/olat/course/nodes/livestream/ui/_content/peekview.html
new file mode 100644
index 00000000000..47ca8de4a90
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/livestream/ui/_content/peekview.html
@@ -0,0 +1,38 @@
+<div class="o_livestream_peekview clearfix">
+	#if($events.isEmpty())
+		<div class="o_instruction">
+			$r.translate("peekview.no.streams")
+		</div>
+	</div>
+	#else
+		#foreach($event in $events)
+			<div class="o_livestream_peekview_event">
+				#if($event.getTitle())
+					<h5>$event.getTitle()</h5>
+				#end
+				<div class="o_cal_date text-muted">
+					<i class="o_icon o_icon-fw o_icon_calendar">&nbsp;</i>
+					$event.getDate()
+				</div>
+				#if($event.getTime() && $event.getTime() != "")
+					<div class="o_cal_time text-muted">
+						<i class="o_icon o_icon-fw o_icon_time">&nbsp;</i>
+						$event.getTime()
+					</div>
+				#end
+				#if($event.getLocation() && $event.getLocation() != "")
+					<div class="o_cal_location text-muted">
+						<i class="o_icon o_icon-fw o_icon_home" title="$r.translateInAttribute("cal.form.location")">&nbsp;</i>
+						$r.escapeHtml($event.getLocation())
+					</div>
+				#end
+			</div>
+		#end
+		#if($r.available("peekview.open.live"))
+			$r.render("peekview.open.live")
+		#end
+		#if($r.available("peekview.open.upcoming"))
+			$r.render("peekview.open.upcoming")
+		#end
+	#end
+</div>
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_de.properties
index 5401196541f..412ca080337 100644
--- a/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_de.properties
@@ -7,6 +7,11 @@ link.text=Video Livestream
 list.title=Anstehende Livestreams
 pane.tab.accessibility=Zugang
 pane.tab.config=Konfiguration
+peekview.no.streams=Es sind keine Livestreams geplant.
+peekview.open.live=Livestream anzeigen
+peekview.open.upcoming=Alle anzeigen
+peekview.title.live=Jetzt live: {0}
+peekview.title.upcoming=Demn\u00E4chst: {0}
 run.edit.events=Termine bearbeiten
 run.streams=Live Streams
 table.empty=Es sind keine Livestreams anstehend.
diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_en.properties
index 8a75569a9dd..c40834169c0 100644
--- a/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_en.properties
@@ -7,6 +7,11 @@ link.text=Video live stream
 list.title=Upcoming live streams
 pane.tab.accessibility=Access
 pane.tab.config=Configuration
+peekview.no.streams=No live streams are planned.
+peekview.open.live=Show live stream
+peekview.open.upcoming=Show all
+peekview.title.live=Live: {0}
+peekview.title.upcoming=Upcoming: {0}
 run.edit.events=Edit events
 run.streams=Livestreams
 table.empty=No upcoming live streams are available.
-- 
GitLab