Merge branch 'master' into bug3785

Conflicts:
	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/Home.gwt.xml
	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java

Change-Id: I73298972a7761dc6580154ecc83d5eda1b39fadc
This commit is contained in:
Axel Uhl committed 2016-07-18 13:06:54 +02:00
commit 133fcdaa58
60 files changed
+1665 -172

No files matched your search

@@ -111,7 +111,11 @@ public class WebSocketConnectionManager implements LiveDataConnection {
if (session != null) {
session.disconnect();
}
client.stop();
synchronized (this) {
if (client != null) {
client.stop();
}
}
}
private class WebSocket extends WebSocketAdapter {
@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24.85 20.35"><path fill=
"#FFF" d="M6.58,7.58H16v2H6.61A5.3,5.3,0,0,1,6.58,7.58Zm3.42-3H8.15a5.51,5.51,
0,0,0-1.3,2H10v-2Zm-1.68,8H13v-2H6.91A5.52,5.52,0,0,0,8.32,12.58ZM2.5,
8.5c0-.31,0-0.62,0-0.92H0v2H2.57A9.52,9.52,0,0,1,2.5,8.5Zm0.24,
2.08H0v2H3.43A9.41,9.41,0,0,1,2.74,10.58Zm0.62-6H0v2H2.7A9.4,9.4,0,0,1,3.36,
4.58ZM23.44,20.35l-5.65-5.65a8.45,8.45,0,1,1,1.3-1.53l5.76,5.76ZM18.5,
8.5A6.5,6.5,0,1,0,12,15,6.51,6.51,0,0,0,18.5,8.5Z"/></svg>

After

Width:  |  Height:  |  Size: 506 B

@@ -43,7 +43,8 @@
<inherits name="com.sap.sailing.gwt.ui.SourceAdminConsole"/>
<inherits name="com.sap.sailing.gwt.regattaoverview.SourceRegattaOverview"/>
<inherits name="com.sap.sailing.gwt.ui.SourceLeaderboard"/>
<inherits name='com.sap.sse.gwt.CommonControlCSS'/>
<inherits name="com.sap.sailing.gwt.ui.SourceRaceboard"/>
<inherits name='com.sap.sse.gwt.CommonControlCSS'/>
<inherits name='com.sap.sailing.gwt.settings.Settings'/>
<replace-with class="com.sap.sailing.gwt.home.shared.utils.dateformatter.CustomEventDatesFormatter">
@@ -135,4 +135,36 @@ public class SimpleRaceMetadataDTO implements DTO, Comparable<SimpleRaceMetadata
final int compareByRaceName = new NaturalComparator().compare(getRaceName(), o.getRaceName());
return compareByRaceName != 0 ? compareByRaceName : Util.compareToWithNull(getStart(), o.getStart(), true);
}
/**
* Convenience method to check if this {@link SimpleRaceMetadataDTO race} has valid tracking data.
*
* @return <code>true</code> if the {@link #getTrackingState() tracking state} is
* {@link RaceTrackingState#TRACKED_VALID_DATA}, <code>false</code> otherwise
*/
public boolean hasValidTrackingData() {
return getTrackingState() == RaceTrackingState.TRACKED_VALID_DATA;
}
/**
* Convenience method to check if this {@link SimpleRaceMetadataDTO race} is already finished.
*
* @return <code>true</code> if the {@link #getViewState() view state} is {@link RaceViewState#FINISHED},
* <code>false</code> otherwise
*/
public boolean isFinished() {
return getViewState() == RaceViewState.FINISHED;
}
/**
* Convenience method to check if this {@link SimpleRaceMetadataDTO race} is currently running.
*
* @return <code>true</code> if the {@link #getViewState() view state} is {@link RaceViewState#RUNNING},
* <code>false</code> otherwise
*/
public boolean isRunning() {
return getViewState() == RaceViewState.RUNNING;
}
}
@@ -9,7 +9,6 @@ import com.google.gwt.cell.client.AbstractCell;
import com.google.gwt.cell.client.Cell;
import com.google.gwt.cell.client.TextCell;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style.TextTransform;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat;
@@ -25,9 +24,6 @@ import com.google.gwt.user.cellview.client.SafeHtmlHeader;
import com.sap.sailing.domain.common.InvertibleComparator;
import com.sap.sailing.domain.common.SortingOrder;
import com.sap.sailing.domain.common.impl.InvertibleComparatorAdapter;
import com.sap.sailing.gwt.common.client.SharedResources;
import com.sap.sailing.gwt.common.client.SharedResources.MainCss;
import com.sap.sailing.gwt.common.client.i18n.TextMessages;
import com.sap.sailing.gwt.home.communication.event.LiveRaceDTO;
import com.sap.sailing.gwt.home.communication.event.RaceListRaceDTO;
import com.sap.sailing.gwt.home.communication.event.SimpleCompetitorDTO;
@@ -35,11 +31,11 @@ import com.sap.sailing.gwt.home.communication.race.FlagStateDTO;
import com.sap.sailing.gwt.home.communication.race.FleetMetadataDTO;
import com.sap.sailing.gwt.home.communication.race.RaceMetadataDTO;
import com.sap.sailing.gwt.home.communication.race.RaceProgressDTO;
import com.sap.sailing.gwt.home.communication.race.SimpleRaceMetadataDTO.RaceTrackingState;
import com.sap.sailing.gwt.home.communication.race.SimpleRaceMetadataDTO.RaceViewState;
import com.sap.sailing.gwt.home.communication.race.wind.AbstractWindDTO;
import com.sap.sailing.gwt.home.communication.race.wind.WindStatisticsDTO;
import com.sap.sailing.gwt.home.desktop.partials.racelist.RaceListResources.LocalCss;
import com.sap.sailing.gwt.home.desktop.partials.raceviewerlaunchpad.RaceviewerLaunchPadCell;
import com.sap.sailing.gwt.home.desktop.places.event.EventView;
import com.sap.sailing.gwt.home.shared.utils.HomeSailingFlagsBuilder;
import com.sap.sailing.gwt.regattaoverview.client.FlagsMeaningExplanator;
@@ -49,16 +45,13 @@ import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil;
import com.sap.sailing.gwt.ui.shared.util.NullSafeComparableComparator;
import com.sap.sailing.gwt.ui.shared.util.NullSafeComparatorWrapper;
import com.sap.sse.common.Duration;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.common.util.NaturalComparator;
public class RaceListColumnFactory {
private static final LocalCss CSS = RaceListResources.INSTANCE.css();
private static final MainCss MAIN_CSS = SharedResources.INSTANCE.mainCss();
private static final StringMessages I18N = StringMessages.INSTANCE;
private static final TextMessages I18N_UBI = TextMessages.INSTANCE;
private static final CellTemplates TEMPLATE = GWT.create(CellTemplates.class);
interface CellTemplates extends SafeHtmlTemplates {
@@ -71,12 +64,6 @@ public class RaceListColumnFactory {
@Template("<img style=\"{0}\" src=\"images/home/windkompass_nord.svg\"/>")
SafeHtml windDirection(SafeStyles rotation);
@Template("<div style=\"{0}\">{1}</div>")
SafeHtml raceNotTracked(SafeStyles styles, String text);
@Template("<a href=\"{2}\" class=\"{0}\" target=\"_blank\">{1}</a>")
SafeHtml raceViewerLinkButton(String styleNames, String text, String link);
@Template("<img src=\"{3}\" class=\"{0}\" /><span class=\"{1}\">{4}</span><div class=\"{2}\" title=\"{5}\">{5}</div>")
SafeHtml winner(String styleNamesFlag, String styleNamesSailId, String styleNamesText, SafeUri flagImageURL, String sailId, String name);
@@ -502,28 +489,6 @@ public class RaceListColumnFactory {
}
public static <T extends RaceMetadataDTO<?>> SortableRaceListColumn<T, T> getRaceViewerButtonColumn(final EventView.Presenter presenter) {
Cell<T> cell = new AbstractCell<T>() {
private final String watchNowStyle = getButtonStyleNames(MAIN_CSS.buttonred());
private final String analyseRaceStyle = getButtonStyleNames(MAIN_CSS.buttonprimary());
@Override
public void render(Context context, T data, SafeHtmlBuilder sb) {
if (data.getTrackingState() != RaceTrackingState.TRACKED_VALID_DATA) {
SafeStylesBuilder styles = new SafeStylesBuilder().textTransform(TextTransform.UPPERCASE);
styles.trustedColor("#666").trustedNameAndValue("font-size", "0.933333333333333rem").toSafeStyles();
styles.trustedNameAndValue("padding", "0.714285714285714em 1.071428571428571em");
sb.append(TEMPLATE.raceNotTracked(styles.toSafeStyles(), I18N_UBI.eventRegattaRaceNotTracked()));
} else {
String styleNames = data.getViewState() == RaceViewState.FINISHED ? analyseRaceStyle : watchNowStyle;
String text = data.getViewState() == RaceViewState.FINISHED ? I18N.analyseRace() : I18N_UBI.watchNow();
sb.append(TEMPLATE.raceViewerLinkButton(styleNames, text, presenter.getRaceViewerURL(data)));
}
}
private final String getButtonStyleNames(String buttonColor) {
return Util.join(" ", MAIN_CSS.button(), MAIN_CSS.buttonstrong(), buttonColor, MAIN_CSS.buttonarrowrightwhite());
}
};
InvertibleComparator<T> comparator = new InvertibleComparatorAdapter<T>() {
@Override
public int compare(T o1, T o2) {
@@ -538,7 +503,7 @@ public class RaceListColumnFactory {
return -o1.getTrackingState().compareTo(o2.getTrackingState());
}
};
return new SortableRaceListColumn<T, T>("", cell, comparator) {
return new SortableRaceListColumn<T, T>("", new RaceviewerLaunchPadCell<T>(presenter), comparator) {
@Override
public String getHeaderStyle() {
return getStyleNamesString(CSS.raceslist_head_item(), CSS.raceslist_head_itembutton());
@@ -0,0 +1,140 @@
.raceviewerlaunchpad {
font-size: 1rem;
color: #fff;
font-weight: 700;
background: #008fcc;
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: -moz-inline-box;
display: -ms-inline-flexbox;
display: inline-flex;
-webkit-box-flex: 0;
-webkit-flex: 0;
-moz-box-flex: 0;
-ms-flex: 0;
flex: 0;
text-align: center;
-webkit-box-align: center;
-webkit-align-items: center;
-moz-box-align: center;
-ms-flex-align: center;
align-items: center;
height: 2.666666666666667em;
padding: 0 0.333333333333333em 0 1em;
border-radius: 0.2em;
position: relative;
}
.raceviewerlaunchpadwithname {
padding: 0.333333333333333em 0.333333333333333em 0.666666666666667em 1em;
height: 5.333333333333333em;
margin: 0.666666666666667em 0 0 0.666666666666667em;
vertical-align: top;
}
.raceviewerlaunchpad_wrapper {
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-webkit-flex-direction: column;
-moz-box-orient: vertical;
-moz-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-align: start;
-webkit-align-items: flex-start;
-moz-box-align: start;
-ms-flex-align: start;
align-items: flex-start;
}
.raceviewerlaunchpad_name {
font-size: 1.466666666666667em;
margin-bottom: 0.333333333333333em;
}
.raceviewerlaunchpad_date {
font-size: 0.666666666666667em;
}
.raceviewerlaunchpad_cta {
font-size: 0.666666666666667em;
text-transform: uppercase;
}
.raceviewerlaunchpad:hover {
cursor: pointer;
background: #0081b8;
}
.raceviewerlaunchpadlive {
background: #e94a1b;
}
.raceviewerlaunchpadlive:hover {
background: #d54115;
}
.raceviewerlaunchpadlive .raceviewerlaunchpad_content_item {
color: #e94a1b;
}
.raceviewerlaunchpadlive .raceviewerlaunchpad_content_item:hover {
background: #e94a1b;
}
.raceviewerlaunchpadlive .raceviewerlaunchpad_content_item_icon svg {
fill: #e94a1b;
}
.raceviewerlaunchpad_icon {
margin-left: 0.666666666666667em;
width: 1.666666666666667em;
height: 1.333333333333333em;
}
.raceviewerlaunchpad_icon svg {
width: 100%;
height: 100%;
fill: #fff;
}
.raceviewerlaunchpad_content {
display: block;
bottom: 0;
right: 0;
box-shadow: 0px 0px 0.666666666666667em 0px rgba(0,0,0,0.2);
}
.raceviewerlaunchpad_content_item {
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
-moz-box-align: center;
-ms-flex-align: center;
align-items: center;
height: 3em;
padding: 0 1.333333333333333em 0 0.666666666666667em;
background: #fff;
color: #008fcc;
font-weight: normal;
}
.raceviewerlaunchpad_content_item:hover {
background: #008fcc;
color: #fff;
text-decoration: underline;
}
.raceviewerlaunchpad_content_item:hover .raceviewerlaunchpad_content_item_icon svg {
fill: #fff;
}
.raceviewerlaunchpad_content_item_icon {
margin-right: 1em;
}
.raceviewerlaunchpad_content_item_icon svg {
width: 1.333333333333333em;
height: 1.666666666666667em;
fill: #008fcc;
stroke: none;
}
.raceviewerlaunchpad_content_item_title {
white-space: nowrap;
}
.raceviewerlaunchpad_not_tracked {
text-transform: uppercase;
font-size: 0.933333333333333rem;
padding: 0.714285714285714em 1.071428571428571em;
color: #666;
}
@@ -0,0 +1,75 @@
package com.sap.sailing.gwt.home.desktop.partials.raceviewerlaunchpad;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.home.communication.race.SimpleRaceMetadataDTO;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.raceboard.RaceBoardModes;
public abstract class RaceviewerLaunchPad extends Composite {
private static RaceviewerLaunchPadUiBinder uiBinder = GWT.create(RaceviewerLaunchPadUiBinder.class);
interface RaceviewerLaunchPadUiBinder extends UiBinder<Widget, RaceviewerLaunchPad> {
}
@UiField RaceviewerLaunchPadResources local_res;
@UiField DivElement itemContainerUi;
private final PopupPanel parent;
public RaceviewerLaunchPad(SimpleRaceMetadataDTO data, final PopupPanel parent) {
this.parent = parent;
initWidget(uiBinder.createAndBindUi(this));
local_res.css().ensureInjected();
if (!data.isFinished()) {
addStyleName(local_res.css().raceviewerlaunchpadlive());
addItem(data, RaceviewerLaunchPadMenuItem.WATCH_LIVE);
} else {
addItem(data, RaceviewerLaunchPadMenuItem.REPLAY);
addItem(data, RaceviewerLaunchPadMenuItem.RACE_ANALYSIS);
}
if (data.isRunning() || data.isFinished()) {
addItem(data, RaceviewerLaunchPadMenuItem.START_ANALYSIS);
}
addItem(data, RaceviewerLaunchPadMenuItem.WINNING_LANES);
sinkEvents(Event.ONCLICK);
}
@Override
public void onBrowserEvent(final Event event) {
if (event.getTypeInt() == Event.ONCLICK) {
parent.hide();
}
super.onBrowserEvent(event);
}
private void addItem(SimpleRaceMetadataDTO data, RaceviewerLaunchPadMenuItem item) {
String raceViewerUrl = getRaceViewerURL(data, item.raceBoardMode);
itemContainerUi.appendChild(new RaceviewerLaunchPadItem(item.label, item.icon, raceViewerUrl).getElement());
}
private enum RaceviewerLaunchPadMenuItem {
REPLAY(StringMessages.INSTANCE.replay(), "launch-replay", RaceBoardModes.PLAYER.name()),
WATCH_LIVE(StringMessages.INSTANCE.watchLive(), "launch-play", RaceBoardModes.PLAYER.name()),
RACE_ANALYSIS(StringMessages.INSTANCE.raceAnalysis(), "launch-loupe", RaceBoardModes.FULL_ANALYSIS.name()),
START_ANALYSIS(StringMessages.INSTANCE.startAnalysis(), "launch-start", RaceBoardModes.START_ANALYSIS.name()),
WINNING_LANES(StringMessages.INSTANCE.winningLanes(), "launch-winning-lanes", RaceBoardModes.WINNING_LANES.name());
private String label, icon, raceBoardMode;
private RaceviewerLaunchPadMenuItem(String label, String iconKey, String raceBoardMode) {
this.label = label;
this.icon = "<svg><use xlink:href=\"#" + iconKey + "\"></use></svg>";
this.raceBoardMode = raceBoardMode;
}
}
protected abstract String getRaceViewerURL(SimpleRaceMetadataDTO data, String raceBoardMode);
}
@@ -0,0 +1,33 @@
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder" xmlns:g="urn:import:com.google.gwt.user.client.ui"
xmlns:svg="http://www.w3.org/2000/svg">
<ui:with field="i18n" type="com.sap.sailing.gwt.ui.client.StringMessages" />
<ui:with field="res" type="com.sap.sailing.gwt.common.client.SharedResources" />
<ui:with field="local_res" type="com.sap.sailing.gwt.home.desktop.partials.raceviewerlaunchpad.RaceviewerLaunchPadResources" />
<g:HTMLPanel addStyleNames="{local_res.css.raceviewerlaunchpad_content}">
<svg style="display: none;">
<symbol id="launch-winning-lanes" viewBox="0 0 16.15 21.95">
<title>Winning Lanes icon</title>
<path d="M4.59,21.94L0,11.12,8.23,0l7.54,8.65ZM1.14,11.25L4.88,20,14.46,8.66,8.3,1.59ZM8.4,21.95L0.62,13l10.7-12.5,4.83,11.1ZM1.94,13l6.4,7.35L15,11.48l-4-9.1Z"></path>
</symbol>
<symbol id="launch-play" viewBox="0 0 20 20">
<title>Play icon</title>
<path d="M10,2a8,8,0,1,1-8,8,8,8,0,0,1,8-8m0-2A10,10,0,1,0,20,10,10,10,0,0,0,10,0h0ZM9.71,10l0.08,0L9.71,10V10m-2-3.43v7l3.07-1.74L13.86,10,10.78,8.26,7.71,6.52h0Z"></path>
</symbol>
<symbol id="launch-replay" viewBox="0 0 20.35 25.44">
<title>Replay icon</title>
<path d="M18.18,14.72a8,8,0,1,1-16,0h2a6,6,0,1,0,6-6v4l-5-5,5-5v4A8,8,0,0,1,18.18,14.72Z"></path>
</symbol>
<symbol id="launch-loupe" viewBox="0 0 24.85 20.35">
<title>Loupe icon</title>
<path d="M6.58,7.58H16v2H6.61A5.3,5.3,0,0,1,6.58,7.58Zm3.42-3H8.15a5.51,5.51,0,0,0-1.3,2H10v-2Zm-1.68,8H13v-2H6.91A5.52,5.52,0,0,0,8.32,12.58ZM2.5,8.5c0-.31,0-0.62,0-0.92H0v2H2.57A9.52,9.52,0,0,1,2.5,8.5Zm0.24,2.08H0v2H3.43A9.41,9.41,0,0,1,2.74,10.58Zm0.62-6H0v2H2.7A9.4,9.4,0,0,1,3.36,4.58ZM23.44,20.35l-5.65-5.65a8.45,8.45,0,1,1,1.3-1.53l5.76,5.76ZM18.5,8.5A6.5,6.5,0,1,0,12,15,6.51,6.51,0,0,0,18.5,8.5Z"></path>
</symbol>
<symbol id="launch-start" viewBox="0 0 21 19">
<title>Start icon</title>
<path d="M21,0V19H20V9.32c-2,.87-4.74,2.14-10.31.43C4.37,8.12,2,8.71,1,9.25V19H0V0H1V1.14C2,0.6,4.37,0,9.69,1.64,15.26,3.35,18,2.07,20,1.21V0h1ZM3.89,5.69a0.83,0.83,0,0,0,.39.41A1.66,1.66,0,0,0,5,6.28,1.32,1.32,0,0,0,5.9,6.06a0.92,0.92,0,0,0,.38-0.68A0.73,0.73,0,0,0,6.1,4.82a2.59,2.59,0,0,0-.66-0.43A2.13,2.13,0,0,1,5,4.11a0.28,0.28,0,0,1-.09-0.23A0.29,0.29,0,0,1,5,3.65a0.6,0.6,0,0,1,.39-0.06,0.61,0.61,0,0,1,.4.15A0.5,0.5,0,0,1,6,4.09l0.62,0A1,1,0,0,0,6.31,3.4a1.27,1.27,0,0,0-.83-0.32,1.19,1.19,0,0,0-.84.2,0.85,0.85,0,0,0-.34.63,0.78,0.78,0,0,0,.07.4,0.77,0.77,0,0,0,.24.29,4.31,4.31,0,0,0,.51.3,2.25,2.25,0,0,1,.43.26,0.3,0.3,0,0,1,.1.24,0.34,0.34,0,0,1-.16.26A0.73,0.73,0,0,1,5,5.75a0.66,0.66,0,0,1-.51-0.21,0.68,0.68,0,0,1-.07-0.4l-0.61,0A1.15,1.15,0,0,0,3.89,5.69ZM8.75,4.26l0.88,0.2L9.86,4l-2.4-.55-0.22.49,0.89,0.2L7,6.53,7.65,6.67ZM10.58,7.5l0.59,0.17,0.39-3.14-0.69-.2L8.3,6.83,8.93,7l0.55-.56,1.19,0.35ZM9.85,6l1.08-1L10.75,6.29Zm3,0.63,0.26,0a0.61,0.61,0,0,1,.31.1,1,1,0,0,1,.23.44q0.17,0.59.23,0.86L14.6,8.16a7.82,7.82,0,0,0-.35-1.09A1.27,1.27,0,0,0,14,6.76a1.14,1.14,0,0,0,.76-0.23,0.93,0.93,0,0,0,.31-0.62,0.88,0.88,0,0,0-.07-0.49,0.64,0.64,0,0,0-.29-0.31A1.66,1.66,0,0,0,14.22,5L12.84,4.8l-1,3L12.46,7.9Zm0.43-1.27L14,5.47a0.84,0.84,0,0,1,.36.1,0.28,0.28,0,0,1,.11.27,0.46,0.46,0,0,1-.13.26,0.55,0.55,0,0,1-.29.16,2.39,2.39,0,0,1-.69,0L13,6.2Zm3.5,0,0.9-.11,0-.53L15.31,5l0,0.53,0.91-.11L16,8.07,16.59,8ZM20,18H19v1h1V18Zm-2,0H17v1h1V18Zm-2,0H15v1h1V18Zm-2,0H13v1h1V18Zm-2,0H11v1h1V18Zm-2,0H9v1h1V18ZM8,18H7v1H8V18ZM6,18H5v1H6V18ZM4,18H3v1H4V18ZM2,18H1v1H2V18Z"></path>
</symbol>
</svg>
<div ui:field="itemContainerUi">
</div>
</g:HTMLPanel>
</ui:UiBinder>
@@ -0,0 +1,87 @@
package com.sap.sailing.gwt.home.desktop.partials.raceviewerlaunchpad;
import com.google.gwt.cell.client.AbstractCell;
import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.BrowserEvents;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.safehtml.client.SafeHtmlTemplates;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
import com.sap.sailing.gwt.common.client.i18n.TextMessages;
import com.sap.sailing.gwt.home.communication.race.RaceMetadataDTO;
import com.sap.sailing.gwt.home.communication.race.SimpleRaceMetadataDTO;
import com.sap.sailing.gwt.home.desktop.places.event.EventView;
import com.sap.sailing.gwt.home.desktop.places.event.EventView.Presenter;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.common.Util;
public class RaceviewerLaunchPadCell<T extends RaceMetadataDTO<?>> extends AbstractCell<T> {
interface CellTemplates extends SafeHtmlTemplates {
@Template("<div class=\"{0}\">{1}</div>")
SafeHtml raceNotTracked(String styleNames, String text);
@Template("<div class=\"{0}\"><div>{2}</div><div class=\"{1}\"><img src=\"images/home/launch-loupe.svg\"/></div>")
SafeHtml raceviewerLaunchPad(String styleNames, String iconStyleNames, String text);
}
private static final CellTemplates TEMPLATE = GWT.create(CellTemplates.class);
private static final TextMessages I18N_UBI = TextMessages.INSTANCE;
private static final StringMessages I18N = StringMessages.INSTANCE;
private final RaceviewerLaunchPadResources local_res = RaceviewerLaunchPadResources.INSTANCE;
private final String notTrackedStyleNames = local_res.css().raceviewerlaunchpad_not_tracked();
private final String analyzeStyleNames = local_res.css().raceviewerlaunchpad();
private final String liveStyleNames = Util.join(" ", analyzeStyleNames, local_res.css().raceviewerlaunchpadlive());
private final String iconStyleNames = local_res.css().raceviewerlaunchpad_icon();
private final EventView.Presenter presenter;
private final PopupPanel panel = new PopupPanel(true, false);
public RaceviewerLaunchPadCell(Presenter presenter) {
super(BrowserEvents.CLICK);
local_res.css().ensureInjected();
this.presenter = presenter;
}
@Override
public void onBrowserEvent(Context context, final Element parent, T data, NativeEvent event,
ValueUpdater<T> valueUpdater) {
if (data.hasValidTrackingData() && BrowserEvents.CLICK.equals(event.getType())
&& parent.getFirstChildElement().isOrHasChild(Element.as(event.getEventTarget()))) {
panel.setWidget(new RaceviewerLaunchPad(data, panel) {
@Override
protected String getRaceViewerURL(SimpleRaceMetadataDTO data, String mode) {
return presenter.getRaceViewerURL(data, mode);
}
});
panel.setPopupPositionAndShow(new PositionCallback() {
@Override
public void setPosition(int offsetWidth, int offsetHeight) {
int alignBottom = parent.getAbsoluteTop() + parent.getOffsetHeight() - offsetHeight;
int top = (alignBottom - Window.getScrollTop() < 0 ? parent.getAbsoluteTop() - 1 : alignBottom + 1);
panel.setPopupPosition(parent.getAbsoluteRight() + 1 - offsetWidth, top);
panel.getElement().scrollIntoView();
}
});
return;
}
super.onBrowserEvent(context, parent, data, event, valueUpdater);
}
@Override
public void render(Context context, T data, SafeHtmlBuilder sb) {
if (data.hasValidTrackingData()) {
String styleNames = data.isFinished() ? analyzeStyleNames : liveStyleNames;
sb.append(TEMPLATE.raceviewerLaunchPad(styleNames, iconStyleNames, I18N.raceDetailsToShow()));
} else {
sb.append(TEMPLATE.raceNotTracked(notTrackedStyleNames, I18N_UBI.eventRegattaRaceNotTracked()));
}
}
}
@@ -0,0 +1,28 @@
package com.sap.sailing.gwt.home.desktop.partials.raceviewerlaunchpad;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.AnchorElement;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.UIObject;
class RaceviewerLaunchPadItem extends UIObject {
private static RaceviewerLaunchPadItemUiBinder uiBinder = GWT.create(RaceviewerLaunchPadItemUiBinder.class);
interface RaceviewerLaunchPadItemUiBinder extends UiBinder<AnchorElement, RaceviewerLaunchPadItem> {
}
@UiField DivElement itemLabelUi;
@UiField DivElement itemIconContainerUi;
private final AnchorElement anchorUi;
RaceviewerLaunchPadItem(String label, String icon, String raceViewerUrl) {
setElement(anchorUi = uiBinder.createAndBindUi(this));
anchorUi.setHref(raceViewerUrl);
itemLabelUi.setInnerText(label);
itemIconContainerUi.setInnerHTML(icon);
}
}
@@ -0,0 +1,12 @@
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder" xmlns:g="urn:import:com.google.gwt.user.client.ui">
<ui:with field="i18n" type="com.sap.sailing.gwt.ui.client.StringMessages" />
<ui:with field="res" type="com.sap.sailing.gwt.common.client.SharedResources" />
<ui:with field="local_res" type="com.sap.sailing.gwt.home.desktop.partials.raceviewerlaunchpad.RaceviewerLaunchPadResources" />
<a target="_blank" class="{local_res.css.raceviewerlaunchpad_content_item}">
<div class="{local_res.css.raceviewerlaunchpad_content_item_icon}" ui:field="itemIconContainerUi">
</div>
<div class="{local_res.css.raceviewerlaunchpad_content_item_title}" ui:field="itemLabelUi">
</div>
</a>
</ui:UiBinder>
@@ -0,0 +1,28 @@
package com.sap.sailing.gwt.home.desktop.partials.raceviewerlaunchpad;
import com.google.gwt.core.client.GWT;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
public interface RaceviewerLaunchPadResources extends ClientBundle {
public static final RaceviewerLaunchPadResources INSTANCE = GWT.create(RaceviewerLaunchPadResources.class);
@Source("RaceviewerLaunchPad.gss")
LocalCss css();
public interface LocalCss extends CssResource {
String raceviewerlaunchpad();
String raceviewerlaunchpadwithname();
String raceviewerlaunchpad_wrapper();
String raceviewerlaunchpad_name();
String raceviewerlaunchpad_date();
String raceviewerlaunchpad_cta();
String raceviewerlaunchpadlive();
String raceviewerlaunchpad_content_item();
String raceviewerlaunchpad_content_item_icon();
String raceviewerlaunchpad_icon();
String raceviewerlaunchpad_content();
String raceviewerlaunchpad_content_item_title();
String raceviewerlaunchpad_not_tracked();
}
}
@@ -106,10 +106,11 @@
-moz-transition: background 0.2s;
transition: background 0.2s;
text-decoration: none;
text-align: center;
/* text-align: center; */
color: #fff;
padding: 0.233333333333333em 0.233333333333333em;
vertical-align: top;
cursor: pointer;
}
@media (min-width: 45em) {
.fleet_races_race {
@@ -136,15 +137,17 @@
.fleet_races_race_name {
font-size: 1.166666666666667em;
font-weight: 700;
position: relative;
}
.fleet_races_race_name:after {
content: '';
display: inline-block;
width: 0.466666666666667em;
height: 0.466666666666667em;
background: url("images/home/skip-forward.png") no-repeat center center;
margin: 0 0 0.033333333333333em 0.133333333333333em;
width: 1em;
height: 0.866666666666667em;
background: url("images/home/launch-loupe.svg") no-repeat center center;
font-size: 1.466666666666667em;
position: absolute;
right: 0;
}
.fleet_races_race_info {
width: 100%;
@@ -6,9 +6,12 @@ import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.home.communication.event.RaceCompetitionFormatFleetDTO;
import com.sap.sailing.gwt.home.communication.race.SimpleRaceMetadataDTO;
import com.sap.sailing.gwt.home.shared.partials.regattacompetition.AbstractRegattaCompetitionFleet;
import com.sap.sailing.gwt.home.shared.partials.regattacompetition.RegattaCompetitionPresenter;
import com.sap.sailing.gwt.home.shared.partials.regattacompetition.RegattaCompetitionView.RegattaCompetitionRaceView;
import com.sap.sailing.gwt.ui.client.StringMessages;
@@ -16,14 +19,14 @@ public class RegattaCompetitionFleet extends AbstractRegattaCompetitionFleet {
private static RegattaCompetitionFleetUiBinder uiBinder = GWT.create(RegattaCompetitionFleetUiBinder.class);
interface RegattaCompetitionFleetUiBinder extends UiBinder<Element, RegattaCompetitionFleet> {
interface RegattaCompetitionFleetUiBinder extends UiBinder<Widget, RegattaCompetitionFleet> {
}
@UiField RegattaCompetitionResources local_res;
@UiField DivElement fleetCornerUi;
@UiField DivElement fleetNameUi;
@UiField DivElement competitorCountUi;
@UiField DivElement racesContainerUi;
@UiField FlowPanel racesContainerUi;
public RegattaCompetitionFleet(RaceCompetitionFormatFleetDTO fleet) {
super(fleet);
@@ -32,9 +35,9 @@ public class RegattaCompetitionFleet extends AbstractRegattaCompetitionFleet {
}
@Override
public RegattaCompetitionRaceView addRaceView(SimpleRaceMetadataDTO race, String raceViewerUrl) {
RegattaCompetitionFleetRace raceView = new RegattaCompetitionFleetRace(race, raceViewerUrl);
racesContainerUi.appendChild(raceView.getElement());
public RegattaCompetitionRaceView addRaceView(SimpleRaceMetadataDTO race, RegattaCompetitionPresenter presenter) {
RegattaCompetitionFleetRace raceView = new RegattaCompetitionFleetRace(race, presenter);
racesContainerUi.add(raceView);
return raceView;
}
@@ -48,7 +51,7 @@ public class RegattaCompetitionFleet extends AbstractRegattaCompetitionFleet {
}
@Override
protected Element getMainUiElement() {
protected Widget getMainUiElement() {
return uiBinder.createAndBindUi(this);
}
@@ -4,12 +4,12 @@
xmlns:dp="urn:import:com.sap.sailing.gwt.home.desktop.partials">
<ui:with field="res" type="com.sap.sailing.gwt.common.client.SharedResources" />
<ui:with field="local_res" type="com.sap.sailing.gwt.home.desktop.partials.regattacompetition.RegattaCompetitionResources" />
<div class="{local_res.css.fleet}">
<g:HTMLPanel addStyleNames="{local_res.css.fleet}">
<div class="{local_res.css.fleet_corner}" ui:field="fleetCornerUi"></div>
<div class="{local_res.css.fleet_details}">
<div class="{local_res.css.fleet_details_name}" ui:field="fleetNameUi"></div>
<div class="{local_res.css.fleet_details_competitors}" ui:field="competitorCountUi"></div>
</div>
<div class="{local_res.css.fleet_races}" ui:field="racesContainerUi"></div>
</div>
<g:FlowPanel addStyleNames="{local_res.css.fleet_races}" ui:field="racesContainerUi"/>
</g:HTMLPanel>
</ui:UiBinder>
@@ -1,19 +1,19 @@
package com.sap.sailing.gwt.home.desktop.partials.regattacompetition;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.AnchorElement;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Element;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.sap.sailing.gwt.home.communication.race.SimpleRaceMetadataDTO;
import com.sap.sailing.gwt.home.shared.partials.regattacompetition.AbstractRegattaCompetitionFleetRace;
import com.sap.sailing.gwt.home.shared.partials.regattacompetition.RegattaCompetitionPresenter;
public class RegattaCompetitionFleetRace extends AbstractRegattaCompetitionFleetRace {
private static RegattaCompetitionFleetRaceUiBinder uiBinder = GWT.create(RegattaCompetitionFleetRaceUiBinder.class);
interface RegattaCompetitionFleetRaceUiBinder extends UiBinder<AnchorElement, RegattaCompetitionFleetRace> {
interface RegattaCompetitionFleetRaceUiBinder extends UiBinder<Element, RegattaCompetitionFleetRace> {
}
@UiField RegattaCompetitionResources local_res;
@@ -21,8 +21,8 @@ public class RegattaCompetitionFleetRace extends AbstractRegattaCompetitionFleet
@UiField DivElement raceStateUi;
@UiField DivElement raceDateUi;
public RegattaCompetitionFleetRace(SimpleRaceMetadataDTO race, String raceViewerUrl) {
super(race, raceViewerUrl);
public RegattaCompetitionFleetRace(SimpleRaceMetadataDTO race, RegattaCompetitionPresenter presenter) {
super(race, presenter);
}
@Override
@@ -31,7 +31,7 @@ public class RegattaCompetitionFleetRace extends AbstractRegattaCompetitionFleet
}
@Override
protected AnchorElement getMainUiElement() {
protected Element getMainUiElement() {
return uiBinder.createAndBindUi(this);
}
@@ -4,11 +4,11 @@
<ui:with field="i18n" type="com.sap.sailing.gwt.ui.client.StringMessages" />
<ui:with field="res" type="com.sap.sailing.gwt.common.client.SharedResources" />
<ui:with field="local_res" type="com.sap.sailing.gwt.home.desktop.partials.regattacompetition.RegattaCompetitionResources" />
<a class="{local_res.css.fleet_races_race}" target="_blank">
<div class="{local_res.css.fleet_races_race}">
<div class="{local_res.css.fleet_races_race_name}" ui:field="raceNameUi"></div>
<div class="{local_res.css.fleet_races_race_info} {res.mediaCss.hideonsmall}">
<div class="{local_res.css.fleet_races_race_state}" ui:field="raceStateUi"></div>
<div class="{local_res.css.fleet_races_race_date}" ui:field="raceDateUi"></div>
</div>
</a>
</div>
</ui:UiBinder>
@@ -10,6 +10,7 @@ import com.google.gwt.place.shared.Place;
import com.google.gwt.safehtml.shared.SafeUri;
import com.google.gwt.safehtml.shared.UriUtils;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.gwt.common.client.controls.tabbar.TabView;
import com.sap.sailing.gwt.home.communication.SailingDispatchSystem;
@@ -150,6 +151,15 @@ public abstract class AbstractEventActivity<PLACE extends AbstractEventPlace> ex
raceMetadata.getRegattaAndRaceIdentifier());
}
@Override
public String getRaceViewerURL(SimpleRaceMetadataDTO raceMetadata, String mode) {
RaceIdentifier raceIdentifier = raceMetadata.getRegattaAndRaceIdentifier();
Map<String, String> params = createRaceBoardLinkParameters(raceMetadata.getLeaderboardName(),
raceMetadata.getLeaderboardGroupName(), raceIdentifier.getRegattaName(), raceIdentifier.getRaceName());
params.put("mode", mode);
return EntryPointLinkFactory.createRaceBoardLink(params);
}
@Override
public String getRaceViewerURL(String leaderboardName, String leaderboardGroupName, RegattaAndRaceIdentifier raceIdentifier) {
return EntryPointLinkFactory.createRaceBoardLink(createRaceBoardLinkParameters(leaderboardName,
@@ -70,6 +70,8 @@ public interface EventView<PLACE extends AbstractEventPlace, PRES extends EventV
EventViewDTO getEventDTO();
String getRaceViewerURL(SimpleRaceMetadataDTO raceMetadata);
String getRaceViewerURL(SimpleRaceMetadataDTO raceMetadata, String mode);
String getRaceViewerURL(String leaderboardName, String leaderboardGroupName,
RegattaAndRaceIdentifier raceIdentifier);
@@ -248,8 +248,8 @@ public class RegattaRacesTabView extends Composite implements RegattaTabView<Reg
}
@Override
protected String getRaceViewerURL(SimpleRaceMetadataDTO raceMetadata) {
return currentPresenter.getRaceViewerURL(raceMetadata);
protected String getRaceViewerURL(SimpleRaceMetadataDTO raceMetadata, String mode) {
return currentPresenter.getRaceViewerURL(raceMetadata, mode);
}
}
@@ -47,6 +47,7 @@
}
.regattacompetition_phase_fleetfullwidth .regattacompetition_phase_fleet_race {
height: 3.466666666666666666em;
text-align: center;
}
.regattacompetition_phase_fleetfullwidth .regattacompetition_phase_fleet_race_title {
position: absolute;
@@ -67,6 +68,7 @@
/* } */
.regattacompetition_phase_fleetfullwidth .regattacompetition_phase_fleet_race_arrow {
display: block;
width: 1.6666666667em;
}
.regattacompetition_phase_fleet_title {
font-size: 1.066666666666667em;
@@ -79,8 +81,8 @@
position: relative;
background: #008fcc;
margin-bottom: 0.666666666666667em;
text-align: center;
padding: .8em 0;
/* text-align: center; */
padding: .8em .4em;
border-radius: 0.333333333333333em;
color: #fff;
/* color: #f6f9fc; alternative white*/
@@ -93,7 +95,7 @@
color: #008fcc;
background-color: #fff;
border: 0.133333333333333em solid #008fcc;
padding: .666666666666667em 0;
padding: .666666666666667em 0.266666666666667em;
cursor: default;
}
.regattacompetition_phase_fleet_raceuntracked .regattacompetition_phase_fleet_race_title:after {
@@ -125,13 +127,17 @@
.regattacompetition_phase_fleet_race_title {
font-size: 1.222222222222222em;
font-weight: 700;
position: relative;
}
.regattacompetition_phase_fleet_race_title:after {
content: '';
display: inline-block;
width: 0.75em;
height: 0.75em;
background: url("images/home/skip-forward.png") no-repeat center center;
width: 1.666666666666667em;
height: 1.1em;
background: url("images/home/launch-loupe.svg") no-repeat center center;
position: absolute;
right: 0;
top: 0.133333333333333em;
}
.regattacompetition_phase_fleet_race_subtitle {
font-size: 0.7em;
@@ -161,4 +167,3 @@
white-space: nowrap;
text-overflow: ellipsis;
}
@@ -6,38 +6,41 @@ import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.home.communication.event.RaceCompetitionFormatFleetDTO;
import com.sap.sailing.gwt.home.communication.race.SimpleRaceMetadataDTO;
import com.sap.sailing.gwt.home.shared.partials.regattacompetition.AbstractRegattaCompetitionFleet;
import com.sap.sailing.gwt.home.shared.partials.regattacompetition.RegattaCompetitionPresenter;
import com.sap.sailing.gwt.home.shared.partials.regattacompetition.RegattaCompetitionView.RegattaCompetitionRaceView;
public class RegattaCompetitionFleet extends AbstractRegattaCompetitionFleet {
private static RegattaCompetitionFleetUiBinder uiBinder = GWT.create(RegattaCompetitionFleetUiBinder.class);
interface RegattaCompetitionFleetUiBinder extends UiBinder<Element, RegattaCompetitionFleet> {
interface RegattaCompetitionFleetUiBinder extends UiBinder<Widget, RegattaCompetitionFleet> {
}
@UiField RegattaCompetitionResources local_res;
@UiField DivElement fleetCornerUi;
@UiField DivElement fleetNameUi;
@UiField DivElement raceContainerUi;
@UiField FlowPanel raceContainerUi;
public RegattaCompetitionFleet(RaceCompetitionFormatFleetDTO fleet) {
super(fleet);
}
@Override
public RegattaCompetitionRaceView addRaceView(SimpleRaceMetadataDTO race, String raceViewerUrl) {
RegattaCompetitionFleetRace raceView = new RegattaCompetitionFleetRace(race, raceViewerUrl);
raceContainerUi.appendChild(raceView.getElement());
public RegattaCompetitionRaceView addRaceView(SimpleRaceMetadataDTO race, RegattaCompetitionPresenter presenter) {
RegattaCompetitionFleetRace raceView = new RegattaCompetitionFleetRace(race, presenter);
raceContainerUi.add(raceView);
return raceView;
}
@Override
public void setNumberOfFleetsInSeries(int fleetCount) {
raceContainerUi.getStyle().setWidth(100.0 / fleetCount, Unit.PCT);
setStyleName(raceContainerUi, local_res.css().regattacompetition_phase_fleetfullwidth(), fleetCount < 2);
getElement().getStyle().setWidth(100.0 / fleetCount, Unit.PCT);
setStyleName(local_res.css().regattacompetition_phase_fleetfullwidth(), fleetCount < 2);
}
@Override
@@ -47,7 +50,7 @@ public class RegattaCompetitionFleet extends AbstractRegattaCompetitionFleet {
}
@Override
protected Element getMainUiElement() {
protected Widget getMainUiElement() {
return uiBinder.createAndBindUi(this);
}
@@ -4,8 +4,9 @@
<ui:with field="i18n" type="com.sap.sailing.gwt.ui.client.StringMessages" />
<ui:with field="res" type="com.sap.sailing.gwt.common.client.SharedResources" />
<ui:with field="local_res" type="com.sap.sailing.gwt.home.mobile.partials.regattacompetition.RegattaCompetitionResources" />
<div class="{local_res.css.regattacompetition_phase_fleet}" ui:field="raceContainerUi">
<g:HTMLPanel addStyleNames="{local_res.css.regattacompetition_phase_fleet}">
<div class="{res.mainCss.fleetcorner}" ui:field="fleetCornerUi"></div>
<div class="{local_res.css.regattacompetition_phase_fleet_title}" ui:field="fleetNameUi"></div>
</div>
<g:FlowPanel ui:field="raceContainerUi"/>
</g:HTMLPanel>
</ui:UiBinder>
@@ -1,7 +1,6 @@
package com.sap.sailing.gwt.home.mobile.partials.regattacompetition;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.AnchorElement;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style.Display;
@@ -11,12 +10,13 @@ import com.sap.sailing.gwt.home.communication.race.SimpleRaceMetadataDTO;
import com.sap.sailing.gwt.home.communication.race.SimpleRaceMetadataDTO.RaceTrackingState;
import com.sap.sailing.gwt.home.shared.ExperimentalFeatures;
import com.sap.sailing.gwt.home.shared.partials.regattacompetition.AbstractRegattaCompetitionFleetRace;
import com.sap.sailing.gwt.home.shared.partials.regattacompetition.RegattaCompetitionPresenter;
public class RegattaCompetitionFleetRace extends AbstractRegattaCompetitionFleetRace {
private static RegattaCompetitionFleetRaceUiBinder uiBinder = GWT.create(RegattaCompetitionFleetRaceUiBinder.class);
interface RegattaCompetitionFleetRaceUiBinder extends UiBinder<AnchorElement, RegattaCompetitionFleetRace> {
interface RegattaCompetitionFleetRaceUiBinder extends UiBinder<Element, RegattaCompetitionFleetRace> {
}
@UiField RegattaCompetitionResources local_res;
@@ -24,8 +24,8 @@ public class RegattaCompetitionFleetRace extends AbstractRegattaCompetitionFleet
@UiField DivElement raceStateUi;
@UiField DivElement raceDateUi;
public RegattaCompetitionFleetRace(SimpleRaceMetadataDTO race, String raceViewerUrl) {
super(race, raceViewerUrl);
public RegattaCompetitionFleetRace(SimpleRaceMetadataDTO race, RegattaCompetitionPresenter presenter) {
super(race, presenter);
}
@Override
@@ -34,7 +34,7 @@ public class RegattaCompetitionFleetRace extends AbstractRegattaCompetitionFleet
}
@Override
protected AnchorElement getMainUiElement() {
protected Element getMainUiElement() {
return uiBinder.createAndBindUi(this);
}
@@ -4,12 +4,12 @@
<ui:with field="i18n" type="com.sap.sailing.gwt.ui.client.StringMessages" />
<ui:with field="res" type="com.sap.sailing.gwt.common.client.SharedResources" />
<ui:with field="local_res" type="com.sap.sailing.gwt.home.mobile.partials.regattacompetition.RegattaCompetitionResources" />
<a class="{local_res.css.regattacompetition_phase_fleet_race}" style="display:block; text-decoration:none;">
<div class="{local_res.css.regattacompetition_phase_fleet_race}" style="display:block; text-decoration:none;">
<div class="{local_res.css.regattacompetition_phase_fleet_race_title}" ui:field="raceNameUi"></div>
<div class="{local_res.css.regattacompetition_phase_fleet_race_subtitle}">
<div class="{local_res.css.regattacompetition_phase_fleet_race_state}" ui:field="raceStateUi"></div>
<div class="{local_res.css.regattacompetition_phase_fleet_race_date}" ui:field="raceDateUi"></div>
</div>
<img src="images/home/skip-forward.png" class="{local_res.css.regattacompetition_phase_fleet_race_arrow}"></img>
</a>
<img src="images/home/launch-loupe.svg" class="{local_res.css.regattacompetition_phase_fleet_race_arrow}"></img>
</div>
</ui:UiBinder>
@@ -12,6 +12,7 @@ import java.util.UUID;
import com.google.gwt.activity.shared.AbstractActivity;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.user.client.ui.AcceptsOneWidget;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.gwt.home.communication.SailingDispatchSystem;
import com.sap.sailing.gwt.home.communication.event.news.LeaderboardNewsEntryDTO;
@@ -241,6 +242,15 @@ public abstract class AbstractEventActivity<PLACE extends AbstractEventPlace> ex
raceMetadata.getRegattaAndRaceIdentifier());
}
@Override
public String getRaceViewerURL(SimpleRaceMetadataDTO raceMetadata, String mode) {
RaceIdentifier raceIdentifier = raceMetadata.getRegattaAndRaceIdentifier();
Map<String, String> params = createRaceBoardLinkParameters(raceMetadata.getLeaderboardName(),
raceMetadata.getLeaderboardGroupName(), raceIdentifier.getRegattaName(), raceIdentifier.getRaceName());
params.put("mode", mode);
return EntryPointLinkFactory.createRaceBoardLink(params);
}
private String getRaceViewerURL(String leaderboardName, String leaderboardGroupName, RegattaAndRaceIdentifier raceIdentifier) {
return EntryPointLinkFactory.createRaceBoardLink(createRaceBoardLinkParameters(leaderboardName, leaderboardGroupName,
raceIdentifier.getRegattaName(), raceIdentifier.getRaceName()));
@@ -48,6 +48,8 @@ public interface EventViewBase extends IsWidget {
PlaceNavigation<?> getMediaPageNavigation();
String getRaceViewerURL(SimpleRaceMetadataDTO raceMetadata);
String getRaceViewerURL(SimpleRaceMetadataDTO raceMetadata, String mode);
String getRegattaId();
@@ -61,8 +61,9 @@ public class RacesViewImpl extends AbstractEventView<RacesView.Presenter> implem
}
@Override
protected String getRaceViewerURL(SimpleRaceMetadataDTO raceMetadata) {
return ExperimentalFeatures.ENABLE_RACE_VIEWER_LINK_ON_MOBILE ? currentPresenter.getRaceViewerURL(raceMetadata) : null;
protected String getRaceViewerURL(SimpleRaceMetadataDTO raceMetadata, String mode) {
return ExperimentalFeatures.ENABLE_RACE_VIEWER_LINK_ON_MOBILE
? currentPresenter.getRaceViewerURL(raceMetadata, mode) : null;
}
}
@@ -1,6 +1,7 @@
package com.sap.sailing.gwt.home.shared.partials.regattacompetition;
import com.google.gwt.dom.client.Element;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.home.communication.event.RaceCompetitionFormatFleetDTO;
import com.sap.sailing.gwt.home.communication.race.FleetMetadataDTO;
@@ -8,10 +9,10 @@ import com.sap.sailing.gwt.home.shared.partials.regattacompetition.RegattaCompet
import com.sap.sse.common.Util.Triple;
import com.sap.sse.common.impl.RGBColor;
public abstract class AbstractRegattaCompetitionFleet extends Widget implements RegattaCompetitionFleetView {
public abstract class AbstractRegattaCompetitionFleet extends Composite implements RegattaCompetitionFleetView {
protected AbstractRegattaCompetitionFleet(RaceCompetitionFormatFleetDTO fleet) {
setElement(getMainUiElement());
initWidget(getMainUiElement());
getFleetNameUiElement().setInnerText(fleet.getFleet().getFleetName());
getFleetCornerUiElement().getStyle().setProperty("borderTopColor", fleet.getFleet().getFleetColor());
if (fleet.getFleet().isDefaultFleet()) {
@@ -33,7 +34,7 @@ public abstract class AbstractRegattaCompetitionFleet extends Widget implements
protected abstract void onDefaultFleetName();
protected abstract Element getMainUiElement();
protected abstract Widget getMainUiElement();
protected abstract Element getFleetNameUiElement();
@@ -6,48 +6,85 @@ import static com.sap.sse.common.impl.MillisecondsTimePoint.now;
import java.util.Date;
import com.google.gwt.dom.client.AnchorElement;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Element;
import com.google.gwt.text.client.DateTimeFormatRenderer;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.common.client.DateUtil;
import com.sap.sailing.gwt.home.communication.race.SimpleRaceMetadataDTO;
import com.sap.sailing.gwt.home.communication.race.SimpleRaceMetadataDTO.RaceTrackingState;
import com.sap.sailing.gwt.home.communication.race.SimpleRaceMetadataDTO.RaceViewState;
import com.sap.sailing.gwt.home.desktop.partials.raceviewerlaunchpad.RaceviewerLaunchPad;
import com.sap.sailing.gwt.home.shared.partials.regattacompetition.RegattaCompetitionView.RegattaCompetitionRaceView;
import com.sap.sailing.gwt.ui.client.StringMessages;
public abstract class AbstractRegattaCompetitionFleetRace extends UIObject implements RegattaCompetitionRaceView {
public abstract class AbstractRegattaCompetitionFleetRace extends Widget implements RegattaCompetitionRaceView {
private static final StringMessages I18N = StringMessages.INSTANCE;
protected final AnchorElement anchorUiElement;
protected final Element mainElement;
private final SimpleRaceMetadataDTO race;
private final RegattaCompetitionPresenter presenter;
private final PopupPanel panel = new PopupPanel(true, false);
protected AbstractRegattaCompetitionFleetRace(SimpleRaceMetadataDTO race, String raceViewerUrl) {
this.anchorUiElement = getMainUiElement();
if (raceViewerUrl != null) {
anchorUiElement.setTarget("_blank");
anchorUiElement.setHref(raceViewerUrl);
}
protected AbstractRegattaCompetitionFleetRace(final SimpleRaceMetadataDTO race,
RegattaCompetitionPresenter presenter) {
this.race = race;
this.presenter = presenter;
this.mainElement = getMainUiElement();
setupRaceState(race.getTrackingState(), race.getViewState());
getRaceNameUiElement().setInnerText(race.getRaceName());
setupRaceStart(race.getStart());
setElement(anchorUiElement);
setElement(mainElement);
if (race.hasValidTrackingData()) {
sinkEvents(Event.ONCLICK);
}
}
@Override
public void onBrowserEvent(Event event) {
if (race.hasValidTrackingData() && event.getTypeInt() == Event.ONCLICK) {
this.getElement().scrollIntoView();
panel.setWidget(new RaceviewerLaunchPad(race, panel) {
@Override
protected String getRaceViewerURL(SimpleRaceMetadataDTO data, String mode) {
return presenter.getRaceViewerURL(data, mode);
}
});
panel.setVisible(false);
panel.show();
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
Widget button = AbstractRegattaCompetitionFleetRace.this, panelContent = panel.getWidget();
int alignRight = button.getAbsoluteLeft() + button.getOffsetWidth() - panelContent.getOffsetWidth();
int left = (alignRight - Window.getScrollLeft() < 0 ? button.getAbsoluteLeft() - 1 : alignRight + 1);
int alignBottom = button.getAbsoluteTop() + button.getOffsetHeight() - panelContent.getOffsetHeight();
int top = (alignBottom - Window.getScrollTop() < 0 ? button.getAbsoluteTop() - 1 : alignBottom + 1);
panel.setPopupPosition(left, top);
panel.setVisible(true);
}
});
}
}
private void setupRaceState(RaceTrackingState trackingState, RaceViewState viewState) {
// boolean isUntrackedRace = trackingState != RaceTrackingState.TRACKED_VALID_DATA;
boolean isUntrackedRace = isUntrackedRace(trackingState);
if (viewState == RaceViewState.RUNNING) {
anchorUiElement.addClassName(getRaceLiveStyleName());
mainElement.addClassName(getRaceLiveStyleName());
getRaceStateUiElement().setInnerText(isUntrackedRace ? I18N.live() : I18N.actionWatch());
} else if (viewState == RaceViewState.FINISHED) {
getRaceStateUiElement().setInnerText(isUntrackedRace ? I18N.raceIsFinished() : I18N.actionAnalyze());
} else {
anchorUiElement.addClassName(getRacePlannedStyleName());
mainElement.addClassName(getRacePlannedStyleName());
if (viewState == RaceViewState.SCHEDULED) getRaceStateUiElement().setInnerText(I18N.raceIsPlanned());
else getRaceStateUiElement().setInnerText(viewState.getLabel());
}
setStyleName(anchorUiElement, getRaceUntrackedStyleName(), isUntrackedRace);
setStyleName(mainElement, getRaceUntrackedStyleName(), isUntrackedRace);
}
private void setupRaceStart(Date startDate) {
@@ -58,7 +95,7 @@ public abstract class AbstractRegattaCompetitionFleetRace extends UIObject imple
}
}
protected abstract AnchorElement getMainUiElement();
protected abstract Element getMainUiElement();
protected abstract Element getRaceNameUiElement();
@@ -11,7 +11,6 @@ import com.sap.sailing.gwt.home.communication.event.RaceCompetitionFormatFleetDT
import com.sap.sailing.gwt.home.communication.event.RaceCompetitionFormatSeriesDTO;
import com.sap.sailing.gwt.home.communication.event.SimpleCompetitorDTO;
import com.sap.sailing.gwt.home.communication.race.SimpleRaceMetadataDTO;
import com.sap.sailing.gwt.home.communication.race.SimpleRaceMetadataDTO.RaceTrackingState;
import com.sap.sailing.gwt.home.shared.partials.filter.FilterValueChangeHandler;
import com.sap.sailing.gwt.home.shared.partials.regattacompetition.RegattaCompetitionView.RegattaCompetitionFleetView;
import com.sap.sailing.gwt.home.shared.partials.regattacompetition.RegattaCompetitionView.RegattaCompetitionRaceView;
@@ -48,9 +47,7 @@ public abstract class RegattaCompetitionPresenter implements
Map<RegattaCompetitionRaceView, SimpleRaceMetadataDTO> raceMap = new HashMap<>();
fleetMap.put(fleetView, raceMap);
for (SimpleRaceMetadataDTO race : fleet.getRaces()) {
boolean tracked = race.getTrackingState() == RaceTrackingState.TRACKED_VALID_DATA;
String raceViewerUrl = tracked ? getRaceViewerURL(race) : null;
RegattaCompetitionRaceView raceView = fleetView.addRaceView(race, raceViewerUrl);
RegattaCompetitionRaceView raceView = fleetView.addRaceView(race, this);
raceMap.put(raceView, race);
}
}
@@ -100,6 +97,6 @@ public abstract class RegattaCompetitionPresenter implements
seriesView.doFilter(unfilteredFleetCount == 0);
}
protected abstract String getRaceViewerURL(SimpleRaceMetadataDTO raceMetadata);
protected abstract String getRaceViewerURL(SimpleRaceMetadataDTO raceMetadata, String mode);
}
@@ -16,7 +16,7 @@ public interface RegattaCompetitionView {
}
public interface RegattaCompetitionFleetView {
RegattaCompetitionRaceView addRaceView(SimpleRaceMetadataDTO race, String raceViewerUrl);
RegattaCompetitionRaceView addRaceView(SimpleRaceMetadataDTO race, RegattaCompetitionPresenter presenter);
void setNumberOfFleetsInSeries(int fleetCount);
void doFilter(boolean filter);
}
@@ -31,7 +31,7 @@ public class GetLeaderboardByNameAction implements AsyncAction<LeaderboardDTO> {
private final SailingServiceAsync sailingService;
private final String leaderboardName;
private final Date date;
private final Collection<String> namesOfRacesForWhichToLoadLegDetails;
private final Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails;
private final boolean addOverallDetails;
private final LeaderboardDTO previousLeaderboard;
private final StringMessages stringMessages;
@@ -40,7 +40,7 @@ public class GetLeaderboardByNameAction implements AsyncAction<LeaderboardDTO> {
private final boolean fillTotalPointsUncorrected;
public GetLeaderboardByNameAction(SailingServiceAsync sailingService, String leaderboardName, Date date,
final Collection<String> namesOfRacesForWhichToLoadLegDetails, boolean addOverallDetails,
final Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails,
LeaderboardDTO previousLeaderboard, boolean fillTotalPointsUncorrected, Timer timerToAdjustOffsetIn,
ErrorReporter errorReporter, StringMessages stringMessages) {
this.sailingService = sailingService;
@@ -48,7 +48,7 @@ public class GetLeaderboardByNameAction implements AsyncAction<LeaderboardDTO> {
this.stringMessages = stringMessages;
this.leaderboardName = leaderboardName;
this.date = date;
this.namesOfRacesForWhichToLoadLegDetails = namesOfRacesForWhichToLoadLegDetails;
this.namesOfRaceColumnsForWhichToLoadLegDetails = namesOfRaceColumnsForWhichToLoadLegDetails;
this.addOverallDetails = addOverallDetails;
this.previousLeaderboard = previousLeaderboard;
this.timerToAdjustOffetIn = timerToAdjustOffsetIn;
@@ -58,7 +58,7 @@ public class GetLeaderboardByNameAction implements AsyncAction<LeaderboardDTO> {
@Override
public void execute(final AsyncCallback<LeaderboardDTO> callback) {
final long clientTimeWhenRequestWasSent = System.currentTimeMillis();
sailingService.getLeaderboardByName(leaderboardName, date, namesOfRacesForWhichToLoadLegDetails, addOverallDetails,
sailingService.getLeaderboardByName(leaderboardName, date, namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails,
previousLeaderboard==null?null:previousLeaderboard.getId(), fillTotalPointsUncorrected,
new AsyncCallback<IncrementalOrFullLeaderboardDTO>() {
@Override
@@ -1,8 +1,10 @@
package com.sap.sailing.gwt.ui.client;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.security.Permission;
@@ -10,7 +12,7 @@ import com.sap.sailing.domain.common.security.SailingPermissionsForRoleProvider;
import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil;
import com.sap.sailing.gwt.ui.shared.MarkPassingTimesDTO;
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
import com.sap.sse.common.Duration;
import com.sap.sse.common.Duration;
import com.sap.sse.common.Util;
import com.sap.sse.gwt.client.player.TimeRangeWithZoomProvider;
import com.sap.sse.gwt.client.player.Timer;
@@ -21,6 +23,19 @@ import com.sap.sse.security.ui.client.UserService;
import com.sap.sse.security.ui.client.UserStatusEventHandler;
import com.sap.sse.security.ui.shared.UserDTO;
/**
* A specific {@link TimePanel} that shows and manages a time slider for a race. Receives {@link RaceTimesInfoDTO}
* objects when registered as a {@link RaceTimesInfoProviderListener} on a {@link RaceTimesInfoProvider} and adjusts the
* time panel including its time slider and the marks displayed and the zoom model accordingly.
* <p>
*
* It is possible to register as a "transitive" {@link RaceTimesInfoProviderListener} on this object in case a component
* is interested in being notified when this object is done with its adjustments based on a
* {@link RaceTimesInfoProviderListener} notification.
*
* @author Axel Uhl (d043530)
*
*/
public class RaceTimePanel extends TimePanel<RaceTimePanelSettings> implements RaceTimesInfoProviderListener {
private final RaceTimesInfoProvider raceTimesInfoProvider;
@@ -31,6 +46,15 @@ public class RaceTimePanel extends TimePanel<RaceTimePanelSettings> implements R
private final UserService userService;
private final RaceTimePanelLifecycle componentLifecycle;
/**
* These listeners are notified transitively after this object has been
* {@link RaceTimesInfoProviderListener#raceTimesInfosReceived(Map, long, Date, long) notified} about race timing
* changes and has finished reacting accordingly. This way, e.g., a race board mode implementation can do its
* thing while assuming that the time slider has already been adjusted accordingly, therefore allowing for the
* timer to be set to a reasonable value.
*/
private final Set<RaceTimesInfoProviderListener> listeners;
private final UserStatusEventHandler userStatusEventHandler = new UserStatusEventHandler() {
@Override
public void onUserStatusChange(UserDTO user) {
@@ -55,6 +79,7 @@ public class RaceTimePanel extends TimePanel<RaceTimePanelSettings> implements R
this.componentLifecycle = componentLifecycle;
this.userService = userService;
this.raceTimesInfoProvider = raceTimesInfoProvider;
this.listeners = new HashSet<>();
selectedRace = null;
autoAdjustPlayMode = true;
selectedRace = selectedRaceIdentifier;
@@ -77,6 +102,14 @@ public class RaceTimePanel extends TimePanel<RaceTimePanelSettings> implements R
userService.removeUserStatusEventHandler(userStatusEventHandler);
}
public void addRaceTimesInfoProviderListener(RaceTimesInfoProviderListener listener) {
this.listeners.add(listener);
}
public void removeRaceTimesInfoProviderListener(RaceTimesInfoProviderListener listener) {
this.listeners.remove(listener);
}
@Override
protected boolean canReplayWhileLiveIsPossible() {
return this.hasCanReplayDuringLiveRacesPermission;
@@ -363,5 +396,8 @@ public class RaceTimePanel extends TimePanel<RaceTimePanelSettings> implements R
public void raceTimesInfosReceived(Map<RegattaAndRaceIdentifier, RaceTimesInfoDTO> raceTimesInfos, long clientTimeWhenRequestWasSent, Date serverTimeDuringRequest, long clientTimeWhenResponseWasReceived) {
timer.adjustClientServerOffset(clientTimeWhenRequestWasSent, serverTimeDuringRequest, clientTimeWhenResponseWasReceived);
updateTimeInfo(raceTimesInfos.get(selectedRace));
for (final RaceTimesInfoProviderListener listener : listeners) {
listener.raceTimesInfosReceived(raceTimesInfos, clientTimeWhenRequestWasSent, serverTimeDuringRequest, clientTimeWhenResponseWasReceived);
}
}
}
@@ -1618,6 +1618,10 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
String finishTime(String timeAsString);
String showCompetitorCharts();
String showWindChart();
String watchLive();
String raceAnalysis();
String startAnalysis();
String winningLanes();
String userNotificationsOnlyIfMailAddressIsVerified();
String enabled();
String disabled();
@@ -1599,3 +1599,7 @@ currentOrAverageRideHeightInMeters=Ride Height (\u2205 at end)
currentOrAverageRideHeightInMetersTooltip=Current Foiling ride height in meters while leg is still running; average ride height when leg is completed.
pleaseCreateAtLeastOneMappingForCompetitor=Please create at least one mapping by first selecting a track on the left hand side and then selecting a competitor on the right hand side
importAdditionalSensorData=Import additional sensor data
watchLive=Watch live
raceAnalysis=Race analysis
startAnalysis=Start analysis
winningLanes=Winning lanes
@@ -1584,3 +1584,7 @@ currentOrAverageRideHeightInMeters=Fahrhöhe (\u2205 nach Abschluss)
currentOrAverageRideHeightInMetersTooltip=Momentane Fahrhöhe (Foiling) während der Teilnehmer sich im Bahnabschnitt befindet; Durchschnitt, nachdem der Teilnehmer den Abschnitt beendet hat.
pleaseCreateAtLeastOneMappingForCompetitor=Bitte erstellen Sie mindestens eine Zuordnung, indem Sie zunächst einen Track auf der linken Seite und anschließend einen Teilnehmer auf der rechten Seite auswählen
importAdditionalSensorData=Zusätzliche Sensordaten importieren
watchLive=Live mitverfolgen
raceAnalysis=Renn-Analyse
startAnalysis=Start-Analyse
winningLanes=Sieger-Kurse
@@ -302,6 +302,10 @@ public class TimePanel<T extends TimePanelSettings> extends SimplePanel implemen
hideControlsPanel();
}
public TimeRangeWithZoomProvider getTimeRangeProvider() {
return timeRangeProvider;
}
private Button createSettingsButton() {
Button settingsButton = SettingsDialog.<T>createSettingsButton(this, stringMessages);
settingsButton.setStyleName(timePanelCss.settingsButtonStyle());
@@ -302,11 +302,6 @@ public abstract class AbstractCompetitorRaceChart<SettingsType extends ChartSett
Date fromDate = timeOfEarliestRequestInMillis == null ? null : new Date(timeOfEarliestRequestInMillis);
Date toDate = timeOfLatestRequestInMillis == null ? null : new Date(timeOfLatestRequestInMillis);
loadData(fromDate, toDate, competitorsToLoad, false);
} else {
// chart is not visible; adding a competitor will require re-loading all data when the
// chart becomes visible again
timeOfEarliestRequestInMillis = null;
timeOfLatestRequestInMillis = null;
}
}
@@ -225,8 +225,7 @@ public abstract class AbstractRaceChart<SettingsType extends Settings> extends A
@Override
public void onTimeZoomChanged(Date zoomStartTimepoint, Date zoomEndTimepoint) {
changeMinMaxAndExtremesInterval(zoomStartTimepoint, zoomEndTimepoint, true);
// Probably there is a function for this in a newer version of highcharts: http://jsfiddle.net/mqz3N/1071/
// chart.showResetZoom();
chart.showResetZoom(); // Patched method
}
@Override
@@ -6,6 +6,10 @@ public class MultiCompetitorRaceChartSettings extends ChartSettings {
private final DetailType firstDetailType;
private final DetailType secondDetailType;
/**
* @param firstDetailType must not be {@code null}
* @param secondDetailType may be {@code null} if only one detail is to be displayed per competitor in the chart
*/
public MultiCompetitorRaceChartSettings(ChartSettings settings, DetailType firstDetailType,
DetailType secondDetailType) {
super(settings);
@@ -43,6 +43,15 @@ public class RaceCompetitorSet extends RaceCompetitorIdsAsStringWithMD5Hash {
* When {@link #idsAsStringOfCompetitorsParticipatingInRace} is <code>null</code> then so is this field, and vice versa.
*/
private Iterable<CompetitorDTO> competitorsParticipatingInRace;
private Set<CompetitorsForRaceDefinedListener> competitorsForRaceDefinedListeners;
/**
* Such listeners are notified whenever the response to {@link #getCompetitorsParticipatingInRace()} changes.
*/
public static interface CompetitorsForRaceDefinedListener {
void competitorsForRaceDefined(Iterable<CompetitorDTO> competitors);
}
RaceCompetitorSet() {} // for GWT serialization only
@@ -54,6 +63,7 @@ public class RaceCompetitorSet extends RaceCompetitorIdsAsStringWithMD5Hash {
*/
public RaceCompetitorSet(CompetitorSelectionProvider competitorSelection) {
super();
this.competitorsForRaceDefinedListeners = new HashSet<>();
this.competitorSelection = competitorSelection;
this.competitorsParticipatingInRace = competitorSelection.getAllCompetitors();
competitorSelection.addCompetitorSelectionChangeListener(new CompetitorSelectionChangeListener() {
@@ -76,6 +86,7 @@ public class RaceCompetitorSet extends RaceCompetitorIdsAsStringWithMD5Hash {
@Override
public void competitorsListChanged(final Iterable<CompetitorDTO> competitors) {
competitorsParticipatingInRace = computeCompetitorsFromIDs(competitors);
notifyListeners();
}
@Override
@@ -85,11 +96,27 @@ public class RaceCompetitorSet extends RaceCompetitorIdsAsStringWithMD5Hash {
});
}
public void addCompetitorsForRaceDefinedListener(CompetitorsForRaceDefinedListener listener) {
competitorsForRaceDefinedListeners.add(listener);
}
public void removeCompetitorsForRaceDefinedListener(CompetitorsForRaceDefinedListener listener) {
competitorsForRaceDefinedListeners.remove(listener);
}
public void setIdsAsStringsOfCompetitorsInRace(Set<String> idsAsStringsOfCompetitorsInRace) throws UnsupportedEncodingException, NoSuchAlgorithmException {
super.setIdsAsStringsOfCompetitorsInRace(idsAsStringsOfCompetitorsInRace);
competitorsParticipatingInRace = computeCompetitorsFromIDs(competitorSelection.getAllCompetitors());
notifyListeners();
}
private void notifyListeners() {
for (final CompetitorsForRaceDefinedListener listener : competitorsForRaceDefinedListeners) {
listener.competitorsForRaceDefined(competitorsParticipatingInRace);
}
}
public Iterable<CompetitorDTO> getCompetitorsParticipatingInRace() {
return competitorsParticipatingInRace;
}
@@ -99,6 +99,7 @@ import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.WindSourceTypeFormatter;
import com.sap.sailing.gwt.ui.client.shared.filter.QuickRankProvider;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceCompetitorSet.CompetitorsForRaceDefinedListener;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapHelpLinesSettings.HelpLineTypes;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapZoomSettings.ZoomTypes;
import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil;
@@ -473,7 +474,11 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
return null;
}
private void updateCoordinateSystemFromSettings() {
/**
* @return {@code true} if the map was redrawn by the call to this method
*/
private boolean updateCoordinateSystemFromSettings() {
boolean redrawn = false;
final MapOptions mapOptions;
orientationChangeInProgress = true;
if (getSettings().isWindUp()) {
@@ -511,6 +516,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
if (mapOptions != null) { // if no coordinate system change happened that affects an existing map, don't redraw
fixesAndTails.clearTails();
redraw();
redrawn = true;
// zooming and setting options while the event loop is still working doesn't work reliably; defer until event loop returns
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
@@ -525,6 +531,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
}
});
}
return redrawn;
}
private void loadMapsAPIV3(final boolean showMapControls, final boolean showHeaderPanel) {
@@ -745,6 +752,10 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
return map;
}
public RaceSimulationOverlay getSimulationOverlay() {
return simulationOverlay;
}
/**
* @return the Panel where labels or other controls for the header can be positioned
*/
@@ -1025,7 +1036,9 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
final Iterable<CompetitorDTO> competitorsToShow, Map<CompetitorDTO, List<GPSFixDTOWithSpeedWindTackAndLegType>> boatData, boolean updateTailsOnly) {
final Map<CompetitorDTO, Runnable> tailPreparersPerCompetitor =
fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, transitionTimeInMillis);
showBoatsOnMap(newTime, transitionTimeInMillis, competitorsToShow, updateTailsOnly, tailPreparersPerCompetitor);
showBoatsOnMap(newTime, transitionTimeInMillis,
/* re-calculate; it could have changed since the asynchronous request was made: */ getCompetitorsToShow(),
updateTailsOnly, tailPreparersPerCompetitor);
if (!updateTailsOnly) {
showCompetitorInfoOnMap(newTime, transitionTimeInMillis, competitorSelection.getSelectedFilteredCompetitors());
// even though the wind data is retrieved by a separate call, re-draw the advantage line because it needs to
@@ -2887,4 +2900,12 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
return getLocalizedShortName();
}
public void addCompetitorsForRaceDefinedListener(CompetitorsForRaceDefinedListener listener) {
raceCompetitorSet.addCompetitorsForRaceDefinedListener(listener);
}
public void removeCompetitorsForRaceDefinedListener(CompetitorsForRaceDefinedListener listener) {
raceCompetitorSet.removeCompetitorsForRaceDefinedListener(listener);
}
}
@@ -526,7 +526,7 @@ public class LeaderboardPanel extends SimplePanel implements Component<Leaderboa
final LeaderboardDTO previousLeaderboard = getLeaderboard();
getSailingService().getLeaderboardByName(getLeaderboardName(),
timer.getPlayMode() == PlayModes.Live ? null : getLeaderboardDisplayDate(),
/* namesOfRacesForWhichToLoadLegDetails */getNamesOfExpandedRaces(),
/* namesOfRacesForWhichToLoadLegDetails */getNamesOfExpandedRaceColumns(),
shallAddOverallDetails(), previousLeaderboard.getId(), /* fillTotalPointsUncorrected */ false,
new MarkedAsyncCallback<IncrementalOrFullLeaderboardDTO>(
new AsyncCallback<IncrementalOrFullLeaderboardDTO>() {
@@ -586,7 +586,11 @@ public class LeaderboardPanel extends SimplePanel implements Component<Leaderboa
}
return raceTimesInfoProvider;
}
public AsyncActionsExecutor getExecutor() {
return asyncActionsExecutor;
}
protected class CompetitorColumn extends LeaderboardSortableColumnWithMinMax<LeaderboardRowDTO, LeaderboardRowDTO> {
private final CompetitorColumnBase<LeaderboardRowDTO> base;
@@ -1661,7 +1665,10 @@ public class LeaderboardPanel extends SimplePanel implements Component<Leaderboa
*/
@Override
public void addedToSelection(CompetitorDTO competitor) {
getSelectionModel().setSelected(getRow(competitor.getIdAsString()), true);
final LeaderboardRowDTO row = getRow(competitor.getIdAsString());
if (row != null) {
getSelectionModel().setSelected(row, true);
}
}
/**
@@ -1669,7 +1676,10 @@ public class LeaderboardPanel extends SimplePanel implements Component<Leaderboa
*/
@Override
public void removedFromSelection(CompetitorDTO competitor) {
getSelectionModel().setSelected(getRow(competitor.getIdAsString()), false);
final LeaderboardRowDTO row = getRow(competitor.getIdAsString());
if (row != null) {
getSelectionModel().setSelected(row, false);
}
}
@Override
@@ -2265,7 +2275,7 @@ public class LeaderboardPanel extends SimplePanel implements Component<Leaderboa
if (needsDataLoading()) {
GetLeaderboardByNameAction getLeaderboardByNameAction = new GetLeaderboardByNameAction(sailingService,
getLeaderboardName(), useNullAsTimePoint() ? null : date,
/* namesOfRacesForWhichToLoadLegDetails */getNamesOfExpandedRaces(), shallAddOverallDetails(), /* previousLeaderboard */
/* namesOfRaceColumnsForWhichToLoadLegDetails */getNamesOfExpandedRaceColumns(), shallAddOverallDetails(), /* previousLeaderboard */
getLeaderboard(), isFillTotalPointsUncorrected(), timer, errorReporter, stringMessages);
this.asyncActionsExecutor.execute(getLeaderboardByNameAction, LOAD_LEADERBOARD_DATA_CATEGORY,
new AsyncCallback<LeaderboardDTO>() {
@@ -2309,18 +2319,18 @@ public class LeaderboardPanel extends SimplePanel implements Component<Leaderboa
/**
* Determine from column expansion state which races need their leg details
*/
private Collection<String> getNamesOfExpandedRaces() {
Collection<String> namesOfExpandedRaces = new ArrayList<String>();
private Collection<String> getNamesOfExpandedRaceColumns() {
Collection<String> namesOfExpandedRaceColumns = new ArrayList<String>();
for (int i = 0; i < getLeaderboardTable().getColumnCount(); i++) {
Column<LeaderboardRowDTO, ?> column = getLeaderboardTable().getColumn(i);
if (column instanceof RaceColumn<?>) {
RaceColumn<?> raceColumn = (RaceColumn<?>) column;
if (raceColumn.isExpanded()) {
namesOfExpandedRaces.add(raceColumn.getRaceColumnName());
namesOfExpandedRaceColumns.add(raceColumn.getRaceColumnName());
}
}
}
return namesOfExpandedRaces;
return namesOfExpandedRaceColumns;
}
private boolean shallAddOverallDetails() {
@@ -2347,7 +2357,7 @@ public class LeaderboardPanel extends SimplePanel implements Component<Leaderboa
* Assigns <code>leaderboard</code> to {@link #leaderboard} and updates the UI accordingly. Also updates the min/max
* values on the columns.
*/
protected void updateLeaderboard(LeaderboardDTO leaderboard) {
public void updateLeaderboard(LeaderboardDTO leaderboard) {
if (leaderboard != null) {
Collection<RaceColumn<?>> columnsToCollapseAndExpandAgain = getExpandedRaceColumnsWhoseDisplayedLegCountChanged(leaderboard);
for (RaceColumn<?> columnToCollapseAndExpandAgain : columnsToCollapseAndExpandAgain) {
@@ -2564,14 +2574,16 @@ public class LeaderboardPanel extends SimplePanel implements Component<Leaderboa
* @return all competitors for which the {@link #getLeaderboard() leaderboard} has an entry whose
* {@link LeaderboardEntryDTO#race} equals <code>race</code>
*/
private Iterable<CompetitorDTO> getCompetitors(RaceIdentifier race) {
public Iterable<CompetitorDTO> getCompetitors(RaceIdentifier race) {
Set<CompetitorDTO> result = new HashSet<CompetitorDTO>();
for (RaceColumnDTO raceColumn : getLeaderboard().getRaceList()) {
if (raceColumn.hasTrackedRace(race)) {
for (Map.Entry<CompetitorDTO, LeaderboardRowDTO> e : getLeaderboard().rows.entrySet()) {
LeaderboardEntryDTO entry = e.getValue().fieldsByRaceColumnName.get(raceColumn.getRaceColumnName());
if (entry != null && entry.race != null && entry.race.equals(race)) {
result.add(e.getKey());
if (getLeaderboard() != null) {
for (RaceColumnDTO raceColumn : getLeaderboard().getRaceList()) {
if (raceColumn.hasTrackedRace(race)) {
for (Map.Entry<CompetitorDTO, LeaderboardRowDTO> e : getLeaderboard().rows.entrySet()) {
LeaderboardEntryDTO entry = e.getValue().fieldsByRaceColumnName.get(raceColumn.getRaceColumnName());
if (entry != null && entry.race != null && entry.race.equals(race)) {
result.add(e.getKey());
}
}
}
}
@@ -3040,7 +3052,7 @@ public class LeaderboardPanel extends SimplePanel implements Component<Leaderboa
return leaderboardTable;
}
protected SailingServiceAsync getSailingService() {
public SailingServiceAsync getSailingService() {
return sailingService;
}
@@ -1012,7 +1012,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
}
@Override
protected void updateLeaderboard(LeaderboardDTO leaderboard) {
public void updateLeaderboard(LeaderboardDTO leaderboard) {
super.updateLeaderboard(leaderboard);
if (leaderboard != null) {
String lastScoreCorrectionComment = leaderboard.getComment();
@@ -0,0 +1,191 @@
package com.sap.sailing.gwt.ui.raceboard;
import java.util.ArrayList;
import java.util.Date;
import java.util.Map;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.gwt.ui.actions.GetLeaderboardByNameAction;
import com.sap.sailing.gwt.ui.client.LeaderboardUpdateListener;
import com.sap.sailing.gwt.ui.client.RaceTimePanel;
import com.sap.sailing.gwt.ui.client.RaceTimesInfoProviderListener;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceCompetitorSet.CompetitorsForRaceDefinedListener;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel;
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
import com.sap.sse.gwt.client.player.Timer;
/**
* Abstract base class for implementing a {@link RaceBoardMode}. The {@link #applyTo(RaceBoardPanel)} method registers
* this object as a leaderboard update listener and as a race times info provider listener, also as a
* {@link CompetitorsForRaceDefinedListener} in order to learn when the set of competitors for the race displayed by the
* player is known. The default implementations of the corresponding callback methods will simply unregister this object
* as a listener for the respective callback, enters the knowledge gained in the object's fields and calls the
* {@link #trigger} method each time (abstract in this class). Subclasses can and should do the following:
*
* <ul>
* <li>Redefine {@link #applyTo(RaceBoardPanel)} and call the {@code stopReceiving...} methods for those types of
* events they don't need</li>
* <li>Redefine specific callback methods in case more processing is required or canceling the listener is desired only
* when certain preconditions are met</li>
* <li>Implement the {@link #trigger} method to check if all information required has been received by now and then use
* it to configure the {@link RaceBoard} and its components accordingly</li>
* </ul>
*
* Note that the {@link #updatedLeaderboard(LeaderboardDTO)} method will typically be called several times in more or
* less unpredictable order, asynchronously. Calling {@link Timer#setTime(long)} will trigger
* {@link #updatedLeaderboard(LeaderboardDTO)} eventually, but in between, other callbacks to the same method
* may occur, triggered by other, automatic adjustments or initialization of the {@link Timer}. To solve this
* problem, subclasses may decide to use a separate {@link GetLeaderboardByNameAction} action and execute
* it using the {@link #getLeaderboardPanel()}.{@link LeaderboardPanel#getExecutor() getExecutor()}. In the
* dedicated callback the subclass can then be sure that the {@link LeaderboardDTO} received matches the
* time previously set on the {@link Timer}.
*
* @author Axel Uhl (d043530)
*
*/
public abstract class AbstractRaceBoardMode implements RaceBoardMode, RaceTimesInfoProviderListener, LeaderboardUpdateListener {
private Timer timer;
private RegattaAndRaceIdentifier raceIdentifier;
private RaceTimePanel raceTimePanel;
private LeaderboardPanel leaderboardPanel;
private RaceBoardPanel raceBoardPanel;
private Map<RegattaAndRaceIdentifier, RaceTimesInfoDTO> raceTimesInfo;
private RaceTimesInfoDTO raceTimesInfoForRace;
private LeaderboardDTO leaderboard;
private LeaderboardDTO leaderboardForSpecificTimePoint;
private RaceColumnDTO raceColumn;
@Override
public void applyTo(RaceBoardPanel raceBoardPanel) {
this.raceBoardPanel = raceBoardPanel;
this.raceTimePanel = raceBoardPanel.getRaceTimePanel();
this.raceTimePanel.addRaceTimesInfoProviderListener(this);
this.leaderboardPanel = raceBoardPanel.getLeaderboardPanel();
this.leaderboardPanel.addLeaderboardUpdateListener(this);
this.timer = raceBoardPanel.getTimer();
this.raceIdentifier = raceBoardPanel.getSelectedRaceIdentifier();
}
/**
* Called whenever new information of any sort has become available. Subclasses can use this
* to decide whether everything they need has been received in order to carry out their actions.
*/
protected abstract void trigger();
protected Timer getTimer() {
return timer;
}
protected RegattaAndRaceIdentifier getRaceIdentifier() {
return raceIdentifier;
}
protected RaceTimePanel getRaceTimePanel() {
return raceTimePanel;
}
protected LeaderboardPanel getLeaderboardPanel() {
return leaderboardPanel;
}
protected RaceBoardPanel getRaceBoardPanel() {
return raceBoardPanel;
}
/**
* Called after the {@link RaceTimePanel} has reacted to this update. We assume that now the timing for the race has
* been received, and it should be clear by now whether we're talking about a live or a replay race. The
* {@link #raceTimesInfo} field is set to the {@code raceTimesInfo} object received as parameter, the
* {@link #raceTimesInfoForRace} field is set to the race times info specific to the race identified by
* {@link #getRaceIdentifier()}.
*/
@Override
public void raceTimesInfosReceived(Map<RegattaAndRaceIdentifier, RaceTimesInfoDTO> raceTimesInfo,
long clientTimeWhenRequestWasSent, Date serverTimeDuringRequest, long clientTimeWhenResponseWasReceived) {
this.raceTimesInfo = raceTimesInfo;
this.raceTimesInfoForRace = raceTimesInfo.get(getRaceIdentifier());
trigger();
}
protected void stopReceivingRaceTimesInfos() {
raceTimePanel.removeRaceTimesInfoProviderListener(this);
}
/**
* Stops listening for leaderboard updates
*/
@Override
public void updatedLeaderboard(LeaderboardDTO leaderboard) {
this.leaderboard = leaderboard;
trigger();
}
protected void stopReceivingLeaderboard() {
leaderboardPanel.removeLeaderboardUpdateListener(this);
}
/**
* Fetches the leaderboard named {@code leaderboardName} for {@code timePoint} with details for race column
* {@code raceColumnName} and stores it in {@link #leaderboardForSpecificTimePoint}, then invokes {@link #trigger()}.
* If a {@link #getLeaderboard()} has been received already, it is passed on in the call in order to reduce the
* bandwidth required, based on a differential approach.
*/
protected void loadLeaderboardForSpecificTimePoint(String leaderboardName,
String raceColumnName, Date timePoint) {
final AsyncCallback<LeaderboardDTO> callback = new AsyncCallback<LeaderboardDTO>() {
@Override
public void onFailure(Throwable caught) {
GWT.log("Error trying to load leaderboard", caught);
}
@Override
public void onSuccess(LeaderboardDTO result) {
leaderboardForSpecificTimePoint = result;
trigger();
}
};
final ArrayList<String> raceColumnNameAsList = new ArrayList<>();
if (getRaceColumn() != null) {
raceColumnNameAsList.add(getRaceColumn().getName());
}
final GetLeaderboardByNameAction getLeaderboardByNameAction = new GetLeaderboardByNameAction(
getLeaderboardPanel().getSailingService(), getLeaderboardPanel().getLeaderboard().name,
getTimer().getTime(), raceColumnNameAsList, /* addOverallDetails */ false,
getLeaderboard(), /* fillTotalPointsUncorrected */ false,
/* timerToAdjustOffsetIn */ getTimer(), /* errorReporter */ null, StringMessages.INSTANCE);
getLeaderboardPanel().getExecutor().execute(getLeaderboardByNameAction, LeaderboardPanel.LOAD_LEADERBOARD_DATA_CATEGORY, callback);
}
@Override
public void currentRaceSelected(RaceIdentifier raceIdentifier, RaceColumnDTO raceColumn) {
this.raceColumn = raceColumn;
trigger();
}
protected Map<RegattaAndRaceIdentifier, RaceTimesInfoDTO> getRaceTimesInfo() {
return raceTimesInfo;
}
protected RaceTimesInfoDTO getRaceTimesInfoForRace() {
return raceTimesInfoForRace;
}
protected LeaderboardDTO getLeaderboard() {
return leaderboard;
}
protected LeaderboardDTO getLeaderboardForSpecificTimePoint() {
return leaderboardForSpecificTimePoint;
}
protected RaceColumnDTO getRaceColumn() {
return raceColumn;
}
}
@@ -0,0 +1,66 @@
package com.sap.sailing.gwt.ui.raceboard;
import java.util.ArrayList;
import java.util.List;
import com.sap.sailing.domain.common.DetailType;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings;
import com.sap.sse.gwt.client.player.Timer.PlayModes;
import com.sap.sse.gwt.client.player.Timer.PlayStates;
/**
* This mode best applies to non-live "replay" races. It sets the time to the "end of race" time point and makes sure
* the race column in the leaderboard is expanded. The timer is put into {@link PlayStates#Paused} mode.
*
* @author Axel Uhl (d043530)
*
*/
public class FullAnalysisMode extends AbstractRaceBoardMode {
private boolean leaderboardSettingsAdjusted;
private boolean timerAdjusted;
/**
* Listening for race times info continues until information about the end of the race has been received. The leaderboard
* settings are adjusted as soon as a valid leaderboard has been received.
*/
@Override
protected void trigger() {
if (!leaderboardSettingsAdjusted && getLeaderboard() != null) {
leaderboardSettingsAdjusted = true;
// it's important to first unregister the listener before updateSettings is called because
// updateSettings will trigger another leaderboard load, leading to an endless recursion otherwise
stopReceivingLeaderboard();
adjustLeaderboardSettings();
}
if (!timerAdjusted && getRaceTimesInfoForRace() != null && getRaceTimesInfoForRace().endOfRace != null) {
timerAdjusted = true;
stopReceivingRaceTimesInfos();
if (getTimer().getPlayMode() == PlayModes.Live) {
getTimer().setPlayMode(PlayModes.Replay);
}
getTimer().setTime(getRaceTimesInfoForRace().endOfRace.getTime());
}
}
private void adjustLeaderboardSettings() {
final LeaderboardSettings existingSettings = getLeaderboardPanel().getSettings();
final List<DetailType> raceDetailsToShow = new ArrayList<>(existingSettings.getRaceDetailsToShow());
raceDetailsToShow.add(DetailType.RACE_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS);
raceDetailsToShow.add(DetailType.RACE_DISTANCE_TRAVELED);
raceDetailsToShow.add(DetailType.RACE_GAP_TO_LEADER_IN_SECONDS);
final LeaderboardSettings newSettings = new LeaderboardSettings(existingSettings.getManeuverDetailsToShow(),
existingSettings.getLegDetailsToShow(),
raceDetailsToShow, existingSettings.getOverallDetailsToShow(), existingSettings.getNamesOfRaceColumnsToShow(),
existingSettings.getNamesOfRacesToShow(),
existingSettings.getNumberOfLastRacesToShow(), /* auto-expand pre-selected race */ true,
existingSettings.getDelayBetweenAutoAdvancesInMilliseconds(),
existingSettings.getNameOfRaceToSort(), existingSettings.isSortAscending(),
existingSettings.isUpdateUponPlayStateChange(),
existingSettings.getActiveRaceColumnSelectionStrategy(),
existingSettings.isShowAddedScores(),
existingSettings.isShowOverallColumnWithNumberOfRacesCompletedPerCompetitor(),
existingSettings.isShowCompetitorSailIdColumn(),
existingSettings.isShowCompetitorFullNameColumn());
getLeaderboardPanel().updateSettings(newSettings);
}
}
@@ -0,0 +1,68 @@
package com.sap.sailing.gwt.ui.raceboard;
import java.util.ArrayList;
import java.util.List;
import com.sap.sailing.domain.common.DetailType;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings;
import com.sap.sse.common.Duration;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.gwt.client.player.Timer.PlayModes;
import com.sap.sse.gwt.client.player.Timer.PlayStates;
/**
* Puts the race viewer into player mode, setting the timer to 10s before start and into {@link PlayStates#Playing} state if it's
* not a live race where auto-play is started anyway.
*
* @author Axel Uhl (d043530)
*
*/
public class PlayerMode extends AbstractRaceBoardMode {
private final Duration DURATION_BEFORE_START_TO_SET_TIMER_TO_FOR_REPLAY_RACES = Duration.ONE_SECOND.times(10);
private boolean adjustedLeaderboardSettings;
private boolean timerAdjusted;
private void adjustLeaderboardSettings() {
final LeaderboardSettings existingSettings = getLeaderboardPanel().getSettings();
final List<DetailType> raceDetailsToShow = new ArrayList<>(existingSettings.getRaceDetailsToShow());
raceDetailsToShow.add(DetailType.RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS);
raceDetailsToShow.add(DetailType.RACE_GAP_TO_LEADER_IN_SECONDS);
final LeaderboardSettings newSettings = new LeaderboardSettings(existingSettings.getManeuverDetailsToShow(),
existingSettings.getLegDetailsToShow(),
raceDetailsToShow, existingSettings.getOverallDetailsToShow(), existingSettings.getNamesOfRaceColumnsToShow(),
existingSettings.getNamesOfRacesToShow(),
existingSettings.getNumberOfLastRacesToShow(), /* auto-expand pre-selected race */ true,
existingSettings.getDelayBetweenAutoAdvancesInMilliseconds(),
existingSettings.getNameOfRaceToSort(), existingSettings.isSortAscending(),
existingSettings.isUpdateUponPlayStateChange(),
existingSettings.getActiveRaceColumnSelectionStrategy(),
existingSettings.isShowAddedScores(),
existingSettings.isShowOverallColumnWithNumberOfRacesCompletedPerCompetitor(),
existingSettings.isShowCompetitorSailIdColumn(),
existingSettings.isShowCompetitorFullNameColumn());
getLeaderboardPanel().updateSettings(newSettings);
}
@Override
protected void trigger() {
if (getTimer().getPlayMode() == PlayModes.Live) {
stopReceivingRaceTimesInfos(); // this trigger wouldn't be stopped otherwise
}
if (!adjustedLeaderboardSettings && getLeaderboard() != null) {
adjustedLeaderboardSettings = true;
// it's important to first unregister the listener before updateSettings is called because
// updateSettings will trigger another leaderboard load, leading to an endless recursion otherwise
stopReceivingLeaderboard();
adjustLeaderboardSettings();
}
if (!timerAdjusted && getTimer().getPlayMode() != PlayModes.Live && getRaceTimesInfoForRace() != null && getRaceTimesInfoForRace().startOfRace != null) {
timerAdjusted = true;
getTimer().setTime(new MillisecondsTimePoint(getRaceTimesInfoForRace().startOfRace).minus(DURATION_BEFORE_START_TO_SET_TIMER_TO_FOR_REPLAY_RACES).asMillis());
getTimer().play();
// we've done our adjustments; remove listener and let go
stopReceivingRaceTimesInfos();
}
}
}
@@ -38,6 +38,11 @@ public class RaceBoardEntryPoint extends AbstractSailingEntryPoint {
private static final String PARAM_LEADERBOARD_GROUP_NAME = "leaderboardGroupName";
private static final String PARAM_EVENT_ID = "eventId";
/**
* Controls the predefined mode into which to switch or configure the race viewer.
*/
private static final String PARAM_MODE = "mode";
private String regattaName;
private String raceName;
private String leaderboardName;
@@ -53,6 +58,19 @@ public class RaceBoardEntryPoint extends AbstractSailingEntryPoint {
// read mandatory parameters
regattaName = Window.Location.getParameter(PARAM_REGATTA_NAME);
raceName = Window.Location.getParameter(PARAM_RACE_NAME);
final String modeName = Window.Location.getParameter(PARAM_MODE);
RaceBoardMode mode;
if (modeName == null) {
mode = null;
} else {
try {
mode = RaceBoardModes.valueOf(modeName).getMode();
} catch (IllegalArgumentException e) {
GWT.log("Couldn't resolve RaceBoard mode " + modeName);
mode = null;
}
}
final RaceBoardMode finalMode = mode;
String leaderboardNameParamValue = Window.Location.getParameter(PARAM_LEADERBOARD_NAME);
String leaderboardGroupNameParamValue = Window.Location.getParameter(PARAM_LEADERBOARD_GROUP_NAME);
if (leaderboardNameParamValue != null && !leaderboardNameParamValue.isEmpty()) {
@@ -109,8 +127,10 @@ public class RaceBoardEntryPoint extends AbstractSailingEntryPoint {
createErrorPage("Could not obtain a race with name " + raceName + " for a regatta with name " + regattaName);
return;
}
createPerspectivePage(raceboardData, perspectiveSettings, showChartMarkEditMediaButtonsAndVideo);
final RaceBoardPanel raceBoardPanel = createPerspectivePage(raceboardData, perspectiveSettings, showChartMarkEditMediaButtonsAndVideo);
if (finalMode != null) {
finalMode.applyTo(raceBoardPanel);
}
}
@Override
@@ -129,7 +149,7 @@ public class RaceBoardEntryPoint extends AbstractSailingEntryPoint {
vp.add(new Label(message));
}
private void createPerspectivePage(RaceboardDataDTO raceboardData, RaceBoardPerspectiveSettings perspectiveSettings,
private RaceBoardPanel createPerspectivePage(RaceboardDataDTO raceboardData, RaceBoardPerspectiveSettings perspectiveSettings,
boolean showChartMarkEditMediaButtonsAndVideo) {
selectedRace = raceboardData.getRace();
Window.setTitle(selectedRace.getName());
@@ -151,7 +171,7 @@ public class RaceBoardEntryPoint extends AbstractSailingEntryPoint {
raceboardData.getCompetitorAndTheirBoats(), timer, selectedRace.getRaceIdentifier(), leaderboardName,
leaderboardGroupName, eventId, RaceBoardEntryPoint.this, getStringMessages(), userAgent,
raceTimesInfoProvider, showChartMarkEditMediaButtonsAndVideo);
RootLayoutPanel.get().add(raceBoardPerspective.getEntryWidget());
return raceBoardPerspective;
}
}
@@ -0,0 +1,30 @@
package com.sap.sailing.gwt.ui.raceboard;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings;
/**
* The {@link RaceBoardEntryPoint} implements a versatile race viewer perspective that comes with a leaderboard, charts
* and a central map component. Through various options, this viewer and its components can be configured in various
* ways, making it a powerful and flexible tool. In order to simplify usage for specific anticipated scenarios,
* a {@link RaceBoardMode} implements a strategy for pre-configuring the various settings and component states
* in a way useful for that particular scenario. For example, a "start analysis" mode would adjust the
* {@link LeaderboardSettings} such that the columns making relevant statements about the start are shown
* whereas others are suppressed; the timer would be set to the time of the race start, and the competitor chart
* would be shown, with speed over ground as the metric selected and the top three boats selected.<p>
*
* @author Axel Uhl (d043530)
*
*/
public interface RaceBoardMode {
/**
* Applies this mode to the {@code raceBoardPanel}, making all adjustments necessary to the perspective, its
* components and their settings to put the panel into this mode.
* <p>
*
* Implementations in this same package can use the package-protected (default-scoped) methods of
* {@link RaceBoardPanel} to access the timer and the components that constitute the panel, such as
* {@link RaceBoardPanel#getLeaderboardPanel()}, {@link RaceBoardPanel#getMap()} or
* {@link RaceBoardPanel#getTimer()}.
*/
void applyTo(RaceBoardPanel raceBoardPanel);
}
@@ -0,0 +1,52 @@
package com.sap.sailing.gwt.ui.raceboard;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceCompetitorSet.CompetitorsForRaceDefinedListener;
import com.sap.sse.common.Util;
public abstract class RaceBoardModeWithPerRaceCompetitors extends AbstractRaceBoardMode implements CompetitorsForRaceDefinedListener {
private Iterable<CompetitorDTO> competitorsInRace;
abstract protected void updateCompetitorSelection();
@Override
public void applyTo(RaceBoardPanel raceBoardPanel) {
super.applyTo(raceBoardPanel);
raceBoardPanel.getMap().addCompetitorsForRaceDefinedListener(this);
}
protected void stopReceivingCompetitorsInRace() {
getRaceBoardPanel().getMap().removeCompetitorsForRaceDefinedListener(this);
}
protected void updateCompetitorSelection(final int howManyTopCompetitorsInRaceToSelect, LeaderboardDTO leaderboard) {
if (leaderboard != null) {
final List<CompetitorDTO> competitorsFromBestToWorstInColumn = leaderboard.getCompetitorsFromBestToWorst(getRaceColumn());
final Set<CompetitorDTO> competitorsToSelect = new HashSet<>();
int numberOfSelectedCompetitors = 0;
for (int i=0; numberOfSelectedCompetitors<howManyTopCompetitorsInRaceToSelect && i<competitorsFromBestToWorstInColumn.size(); i++) {
if (getCompetitorsInRace() == null || Util.contains(getCompetitorsInRace(), competitorsFromBestToWorstInColumn.get(i))) {
competitorsToSelect.add(competitorsFromBestToWorstInColumn.get(i));
numberOfSelectedCompetitors++;
}
}
getRaceBoardPanel().getCompetitorSelectionProvider().setSelection(competitorsToSelect);
}
}
@Override
public void competitorsForRaceDefined(Iterable<CompetitorDTO> competitorsInRace) {
this.competitorsInRace = competitorsInRace;
trigger();
}
protected Iterable<CompetitorDTO> getCompetitorsInRace() {
return competitorsInRace;
}
}
@@ -0,0 +1,15 @@
package com.sap.sailing.gwt.ui.raceboard;
public enum RaceBoardModes {
PLAYER(new PlayerMode()), FULL_ANALYSIS(new FullAnalysisMode()), START_ANALYSIS(new StartAnalysisMode()), WINNING_LANES(new WinningLanesMode());
private RaceBoardModes(RaceBoardMode mode) {
this.mode = mode;
}
public RaceBoardMode getMode() {
return mode;
}
private final RaceBoardMode mode;
}
@@ -415,6 +415,38 @@ public class RaceBoardPanel extends AbstractPerspectiveComposite<RaceBoardPerspe
componentViewer.forceLayout();
}
LeaderboardPanel getLeaderboardPanel() {
return leaderboardPanel;
}
MultiCompetitorRaceChart getCompetitorChart() {
return competitorChart;
}
WindChart getWindChart() {
return windChart;
}
RaceTimePanel getRaceTimePanel() {
return racetimePanel;
}
Timer getTimer() {
return timer;
}
RaceMap getMap() {
return raceMap;
}
RegattaAndRaceIdentifier getSelectedRaceIdentifier() {
return selectedRaceIdentifier;
}
CompetitorSelectionProvider getCompetitorSelectionProvider() {
return competitorSelectionProvider;
}
/**
* Sets the collapsable panel for the leaderboard open or close, if in <code>CASCADE</code> view mode.<br />
* Displays or hides the leaderboard, if in <code>ONESCREEN</code> view mode.<br /><br />
@@ -451,10 +483,6 @@ public class RaceBoardPanel extends AbstractPerspectiveComposite<RaceBoardPerspe
setComponentVisible(leaderboardAndMapViewer, competitorChart, visible);
}
public RaceTimePanel getTimePanel() {
return racetimePanel;
}
protected SailingServiceAsync getSailingService() {
return sailingService;
}
@@ -475,11 +503,11 @@ public class RaceBoardPanel extends AbstractPerspectiveComposite<RaceBoardPerspe
public void updatedLeaderboard(LeaderboardDTO leaderboard) {
leaderboardAndMapViewer.setLeftComponentWidth(leaderboardPanel.getContentPanel().getOffsetWidth());
if (editMarkPassingPanel != null) {
editMarkPassingPanel.setLeaderboard(leaderboard);
editMarkPassingPanel.setLeaderboard(leaderboard);
}
if (editMarkPositionPanel != null) {
editMarkPositionPanel.setLeaderboard(leaderboard);
}
}
}
@Override
@@ -560,12 +588,12 @@ public class RaceBoardPanel extends AbstractPerspectiveComposite<RaceBoardPerspe
}
private void manageTimePanelToggleButton(boolean advanceTimePanelEnabled) {
final Button toggleButton = getTimePanel().getAdvancedToggleButton();
final Button toggleButton = getRaceTimePanel().getAdvancedToggleButton();
if (advanceTimePanelEnabled) {
toggleButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
boolean advancedModeShown = getTimePanel().toggleAdvancedMode();
boolean advancedModeShown = getRaceTimePanel().toggleAdvancedMode();
if (advancedModeShown) {
dockPanel.setWidgetSize(timePanelWrapper, TIMEPANEL_EXPANDED_HEIGHT);
toggleButton.removeStyleDependentName("Closed");
@@ -585,7 +613,7 @@ public class RaceBoardPanel extends AbstractPerspectiveComposite<RaceBoardPerspe
private ResizableFlowPanel createTimePanelLayoutWrapper() {
ResizableFlowPanel timeLineInnerBgPanel = new ResizableFlowPanel();
timeLineInnerBgPanel.addStyleName("timeLineInnerBgPanel");
timeLineInnerBgPanel.add(getTimePanel());
timeLineInnerBgPanel.add(getRaceTimePanel());
ResizableFlowPanel timeLineInnerPanel = new ResizableFlowPanel();
timeLineInnerPanel.add(timeLineInnerBgPanel);
@@ -0,0 +1,189 @@
package com.sap.sailing.gwt.ui.raceboard;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.maps.client.events.idle.IdleMapEvent;
import com.google.gwt.maps.client.events.idle.IdleMapHandler;
import com.sap.sailing.domain.common.DetailType;
import com.sap.sailing.gwt.ui.client.shared.charts.ChartSettings;
import com.sap.sailing.gwt.ui.client.shared.charts.MultiCompetitorRaceChart;
import com.sap.sailing.gwt.ui.client.shared.charts.MultiCompetitorRaceChartSettings;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapSettings;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapZoomSettings;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapZoomSettings.ZoomTypes;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings;
import com.sap.sse.common.Duration;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.gwt.client.player.Timer.PlayModes;
import com.sap.sse.gwt.client.player.Timer.PlayStates;
/**
* The start analysis mode makes the competitor chart visible and sets it to speed over ground; the
* {@link LeaderboardSettings} are adjusted such that no leg columns but only start parameters are
* shown. The top three starters are selected when the leaderboard has been updated after setting
* the timer to a few seconds after the start. The {@link PlayStates#Paused} is used for the timer.
*
* @author Axel Uhl (d043530)
*
*/
public class StartAnalysisMode extends RaceBoardModeWithPerRaceCompetitors {
private static final Duration DURATION_AFTER_START_TO_SET_TIMER_TO_FOR_START_ANALYSIS = Duration.ONE_SECOND.times(10);
private static final Duration DURATION_BEFORE_START_TO_INCLUDE_IN_CHART_TIME_RANGE = Duration.ONE_SECOND.times(30);
private static final Duration DURATION_AFTER_START_TO_INCLUDE_IN_CHART_TIME_RANGE = Duration.ONE_SECOND.times(30);
private MultiCompetitorRaceChart competitorChart;
private boolean leaderboardSettingsAdjusted;
private boolean competitorSelectionUpdated;
private boolean timerAdjusted;
private boolean mapSettingsAndTimeZoomAdjusted;
@Override
public void applyTo(RaceBoardPanel raceBoardPanel) {
super.applyTo(raceBoardPanel);
this.competitorChart = raceBoardPanel.getCompetitorChart();
}
@Override
protected void updateCompetitorSelection() {
updateCompetitorSelection(/* howManyTopCompetitorsInRaceToSelect */ 3, getLeaderboardForSpecificTimePoint());
}
private void adjustMapSettings() {
final RaceMapSettings existingMapSettings = getRaceBoardPanel().getMap().getSettings();
final RaceMapSettings newMapSettings = new RaceMapSettings(
new RaceMapZoomSettings(Collections.singleton(ZoomTypes.BOATS), /* zoomToSelected */ false),
existingMapSettings.getHelpLinesSettings(),
existingMapSettings.getTransparentHoverlines(),
existingMapSettings.getHoverlineStrokeWeight(),
existingMapSettings.getTailLengthInMilliseconds(),
/* existingMapSettings.isWindUp() */ true,
existingMapSettings.getBuoyZoneRadiusInMeters(),
existingMapSettings.isShowOnlySelectedCompetitors(),
existingMapSettings.isShowSelectedCompetitorsInfo(),
existingMapSettings.isShowWindStreamletColors(),
existingMapSettings.isShowWindStreamletOverlay(),
existingMapSettings.isShowSimulationOverlay(),
existingMapSettings.isShowMapControls(),
existingMapSettings.getManeuverTypesToShow(),
existingMapSettings.isShowDouglasPeuckerPoints());
// try to update the settings once; the problem is the "wind up" display; it changes pan/zoom
// which is a major source of instability for the map. Going twice in the map idle event handler
// to ensure the settings are really applied.
final Object[] registration = new Object[1];
registration[0] = getRaceBoardPanel().getMap().getMap().addIdleHandler(new IdleMapHandler() {
@Override
public void onEvent(IdleMapEvent event) {
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
getRaceBoardPanel().getMap().updateSettings(newMapSettings);
}
});
((HandlerRegistration) registration[0]).removeHandler();
}
});
getRaceBoardPanel().getMap().updateSettings(newMapSettings);
}
@Override
protected void trigger() {
if (!leaderboardSettingsAdjusted && getLeaderboard() != null) {
leaderboardSettingsAdjusted = true;
stopReceivingLeaderboard();
adjustLeaderboardSettings();
}
if (!timerAdjusted && getRaceTimesInfoForRace() != null && getRaceTimesInfoForRace().startOfRace != null) {
timerAdjusted = true;
// we've done our adjustments; remove listener and let go
stopReceivingRaceTimesInfos();
// make sure we're no longer in live/playing mode/state; the start analysis is supposed to be a "frozen" display at first
if (getTimer().getPlayMode() == PlayModes.Live) {
getTimer().setPlayMode(PlayModes.Replay);
}
getTimer().pause();
// the following call will always trigger a leaderboard load and therefore a callback
// to updatedLeaderboard; therefore, updatedLeaderboard can decide when it's time to
// trigger the chart display and the map settings application
getTimer().setTime(new MillisecondsTimePoint(getRaceTimesInfoForRace().startOfRace).plus(DURATION_AFTER_START_TO_SET_TIMER_TO_FOR_START_ANALYSIS).asMillis());
// no need to do anything if we don't have the timing info yet; after obtaining the timing
// info we can start waiting for the simulation overlay and then adjust the map settings and,
// if available, show the competitor chart
if (!mapSettingsAndTimeZoomAdjusted) {
mapSettingsAndTimeZoomAdjusted = true;
Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
@Override
public boolean execute() {
// keep trying in 500ms steps until the simulation overlay is found; this way, changing the competitor chart
// settings is largely more successful
final boolean simulationOverlayFound = getRaceBoardPanel().getMap().getSimulationOverlay() != null;
if (simulationOverlayFound) {
if (isCompetitorChartEnabled()) {
getRaceBoardPanel().setCompetitorChartVisible(true);
final MultiCompetitorRaceChartSettings newCompetitorChartSettings = new MultiCompetitorRaceChartSettings(
new ChartSettings(/* stepSizeInMillis */ 1000), DetailType.RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS,
/* no second series */ null);
competitorChart.updateSettings(newCompetitorChartSettings);
getRaceTimePanel().getTimeRangeProvider().setTimeZoom(
new MillisecondsTimePoint(getRaceTimesInfoForRace().startOfRace).minus(DURATION_BEFORE_START_TO_INCLUDE_IN_CHART_TIME_RANGE).asDate(),
new MillisecondsTimePoint(getRaceTimesInfoForRace().startOfRace).plus(DURATION_AFTER_START_TO_INCLUDE_IN_CHART_TIME_RANGE).asDate());
}
adjustMapSettings();
}
return !simulationOverlayFound;
}
}, /* delay in milliseconds */ 500);
}
}
if (getLeaderboardForSpecificTimePoint() == null &&
getRaceColumn() != null && getLeaderboard() != null
&& timerAdjusted) {
loadLeaderboardForSpecificTimePoint(getLeaderboard().name, getRaceColumn().getName(), getTimer().getTime());
}
if (!competitorSelectionUpdated && getLeaderboardForSpecificTimePoint() != null && getCompetitorsInRace() != null) {
competitorSelectionUpdated = true;
stopReceivingCompetitorsInRace();
updateCompetitorSelection();
}
}
private void adjustLeaderboardSettings() {
final LeaderboardSettings existingSettings = getLeaderboardPanel().getSettings();
final List<DetailType> raceDetailsToShow = new ArrayList<>(existingSettings.getRaceDetailsToShow());
raceDetailsToShow.add(DetailType.RACE_SPEED_OVER_GROUND_FIVE_SECONDS_BEFORE_START);
raceDetailsToShow.add(DetailType.RACE_DISTANCE_TO_START_FIVE_SECONDS_BEFORE_RACE_START);
raceDetailsToShow.add(DetailType.DISTANCE_TO_START_AT_RACE_START);
raceDetailsToShow.add(DetailType.DISTANCE_TO_START_LINE);
raceDetailsToShow.add(DetailType.DISTANCE_TO_STARBOARD_END_OF_STARTLINE_WHEN_PASSING_START_IN_METERS);
raceDetailsToShow.add(DetailType.SPEED_OVER_GROUND_AT_RACE_START);
raceDetailsToShow.add(DetailType.SPEED_OVER_GROUND_WHEN_PASSING_START);
raceDetailsToShow.add(DetailType.START_TACK);
raceDetailsToShow.add(DetailType.RACE_GAP_TO_LEADER_IN_SECONDS);
raceDetailsToShow.remove(DetailType.DISPLAY_LEGS);
final LeaderboardSettings newSettings = new LeaderboardSettings(existingSettings.getManeuverDetailsToShow(),
existingSettings.getLegDetailsToShow(),
raceDetailsToShow, existingSettings.getOverallDetailsToShow(), existingSettings.getNamesOfRaceColumnsToShow(),
existingSettings.getNamesOfRacesToShow(),
existingSettings.getNumberOfLastRacesToShow(), /* auto-expand pre-selected race */ true,
existingSettings.getDelayBetweenAutoAdvancesInMilliseconds(),
existingSettings.getNameOfRaceToSort(), existingSettings.isSortAscending(),
existingSettings.isUpdateUponPlayStateChange(),
existingSettings.getActiveRaceColumnSelectionStrategy(),
existingSettings.isShowAddedScores(),
existingSettings.isShowOverallColumnWithNumberOfRacesCompletedPerCompetitor(),
existingSettings.isShowCompetitorSailIdColumn(),
existingSettings.isShowCompetitorFullNameColumn());
getLeaderboardPanel().updateSettings(newSettings);
}
private boolean isCompetitorChartEnabled() {
return competitorChart != null;
}
}
@@ -0,0 +1,135 @@
package com.sap.sailing.gwt.ui.raceboard;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import com.sap.sailing.domain.common.DetailType;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapSettings;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings;
import com.sap.sailing.gwt.ui.shared.MarkPassingTimesDTO;
import com.sap.sse.common.Duration;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.gwt.client.player.Timer.PlayModes;
/**
* Puts the race viewer in a mode where the user can see what may be called the "Winning Lanes." For this,
* the timer is set to the point in time when the first competitor finishes the race, or, for live races,
* to the current point in time. The tail length is chosen such that it covers the full track of the
* competitor farthest ahead.
*
* @author Axel Uhl (d043530)
*
*/
public class WinningLanesMode extends RaceBoardModeWithPerRaceCompetitors {
private Duration tailLength;
private boolean adjustedLeaderboardSettings;
private boolean adjustedCompetitorSelection;
@Override
protected void updateCompetitorSelection() {
final int howManyCompetitorsToSelect = getHowManyCompetitorsToSelect(getLeaderboardPanel().getCompetitors(getRaceIdentifier()));
updateCompetitorSelection(howManyCompetitorsToSelect, getLeaderboardForSpecificTimePoint());
}
private void adjustLeaderboardSettings() {
final LeaderboardSettings existingSettings = getLeaderboardPanel().getSettings();
final List<DetailType> raceDetailsToShow = new ArrayList<>(existingSettings.getRaceDetailsToShow());
raceDetailsToShow.add(DetailType.RACE_AVERAGE_ABSOLUTE_CROSS_TRACK_ERROR_IN_METERS);
raceDetailsToShow.add(DetailType.RACE_AVERAGE_SIGNED_CROSS_TRACK_ERROR_IN_METERS);
raceDetailsToShow.add(DetailType.RACE_DISTANCE_TRAVELED);
raceDetailsToShow.add(DetailType.RACE_TIME_TRAVELED);
raceDetailsToShow.remove(DetailType.DISPLAY_LEGS);
final LeaderboardSettings newSettings = new LeaderboardSettings(existingSettings.getManeuverDetailsToShow(),
existingSettings.getLegDetailsToShow(),
raceDetailsToShow, existingSettings.getOverallDetailsToShow(), existingSettings.getNamesOfRaceColumnsToShow(),
existingSettings.getNamesOfRacesToShow(),
existingSettings.getNumberOfLastRacesToShow(), /* auto-expand pre-selected race */ true,
existingSettings.getDelayBetweenAutoAdvancesInMilliseconds(),
existingSettings.getNameOfRaceToSort(), existingSettings.isSortAscending(),
existingSettings.isUpdateUponPlayStateChange(),
existingSettings.getActiveRaceColumnSelectionStrategy(),
existingSettings.isShowAddedScores(),
existingSettings.isShowOverallColumnWithNumberOfRacesCompletedPerCompetitor(),
existingSettings.isShowCompetitorSailIdColumn(),
existingSettings.isShowCompetitorFullNameColumn());
getLeaderboardPanel().updateSettings(newSettings);
}
private void adjustMapSettings() {
final RaceMapSettings existingMapSettings = getRaceBoardPanel().getMap().getSettings();
final RaceMapSettings newMapSettings = new RaceMapSettings(existingMapSettings.getZoomSettings(),
existingMapSettings.getHelpLinesSettings(),
existingMapSettings.getTransparentHoverlines(),
existingMapSettings.getHoverlineStrokeWeight(),
tailLength.asMillis(),
/* existingMapSettings.isWindUp() */ true,
existingMapSettings.getBuoyZoneRadiusInMeters(),
/* existingMapSettings.isShowOnlySelectedCompetitors() */ true, // show the top n competitors and their tails quickly
existingMapSettings.isShowSelectedCompetitorsInfo(),
existingMapSettings.isShowWindStreamletColors(),
existingMapSettings.isShowWindStreamletOverlay(),
existingMapSettings.isShowSimulationOverlay(),
existingMapSettings.isShowMapControls(),
existingMapSettings.getManeuverTypesToShow(),
existingMapSettings.isShowDouglasPeuckerPoints());
getRaceBoardPanel().getMap().updateSettings(newMapSettings);
}
/**
* Based on the set of competitors in the leaderboard, determines how many competitors to select for the "winning lanes"
* view. The number is determined to be at least one tenth of the number of competitors, but at least one if there are
* one or more competitors.
*/
private int getHowManyCompetitorsToSelect(Iterable<CompetitorDTO> competitors) {
final int numberOfCompetitors = Util.size(competitors);
return numberOfCompetitors==0 ? 0 : numberOfCompetitors<=4 ? 1 : numberOfCompetitors <=9 ? 2 : 3;
}
@Override
protected void trigger() {
final Date startOfRace;
if (getRaceTimesInfoForRace() != null && (startOfRace=getRaceTimesInfoForRace().startOfRace) != null) {
if (getTimer().getPlayMode() == PlayModes.Live) {
// adjust tail length such that for the leading boat the tail is shown since the start time point
tailLength = new MillisecondsTimePoint(startOfRace).until(getTimer().getLiveTimePoint());
} else {
final List<MarkPassingTimesDTO> markPassingTimes = getRaceTimesInfoForRace().getMarkPassingTimes();
final Date firstPassingOfLastWaypointPassed = markPassingTimes == null || markPassingTimes.isEmpty() ? null :
markPassingTimes.get(markPassingTimes.size()-1).firstPassingDate;
final Date endOfRace = getRaceTimesInfoForRace().endOfRace;
final Date end = firstPassingOfLastWaypointPassed != null ? firstPassingOfLastWaypointPassed :
endOfRace != null ? endOfRace : getRaceTimesInfoForRace().endOfTracking;
if (end != null) {
tailLength = new MillisecondsTimePoint(startOfRace).until(new MillisecondsTimePoint(end));
getTimer().setTime(end.getTime());
}
}
// we've done our adjustments; remove listener and let go
stopReceivingRaceTimesInfos();
}
if (!adjustedLeaderboardSettings && getLeaderboard() != null) {
adjustedLeaderboardSettings = true;
// it's important to first unregister the listener before updateSettings is called because
// updateSettings will trigger another leaderboard load, leading to an endless recursion otherwise
stopReceivingLeaderboard();
adjustLeaderboardSettings();
}
if (adjustedLeaderboardSettings && tailLength != null) {
adjustMapSettings();
}
if (getLeaderboardForSpecificTimePoint() == null && tailLength != null && getLeaderboard() != null && getRaceColumn() != null) {
loadLeaderboardForSpecificTimePoint(getLeaderboard().name, getRaceColumn().getName(), getTimer().getTime());
}
if (!adjustedCompetitorSelection && getLeaderboardForSpecificTimePoint() != null && getCompetitorsInRace() != null) {
stopReceivingCompetitorsInRace();
adjustedCompetitorSelection = true;
updateCompetitorSelection();
}
}
}
@@ -459,6 +459,7 @@ import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.Util.Triple;
import com.sap.sse.common.WithID;
import com.sap.sse.common.impl.MillisecondsDurationImpl;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.common.impl.TimeRangeImpl;
import com.sap.sse.common.mail.MailException;
@@ -2948,7 +2949,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
}
break;
default:
throw new UnsupportedOperationException("Theres currently no support for the enum value '" + dataType
throw new UnsupportedOperationException("There is currently no support for the enum value '" + dataType
+ "' in this method.");
}
return result;
@@ -2966,8 +2967,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
TimePoint newestEvent = trackedRace.getTimePointOfNewestEvent();
final TimePoint startTime = from == null ? trackedRace.getStartOfTracking() : new MillisecondsTimePoint(from);
final TimePoint endTime = (to == null || to.after(newestEvent.asDate())) ? newestEvent : new MillisecondsTimePoint(to);
final long adjustedStepSizeInMillis = startTime==null || endTime == null ? Long.MAX_VALUE :
Math.max(stepSizeInMillis, Math.abs(startTime.until(endTime).asMillis())/SailingServiceConstants.MAX_NUMBER_OF_FIXES_TO_QUERY);
final long adjustedStepSizeInMillis = (long) Math.max((double) stepSizeInMillis, startTime.until(endTime).divide(new MillisecondsDurationImpl(stepSizeInMillis)));
result = new CompetitorsRaceDataDTO(detailType, startTime==null?null:startTime.asDate(), endTime==null?null:endTime.asDate());
final int MAX_CACHE_SIZE = SailingServiceConstants.MAX_NUMBER_OF_FIXES_TO_QUERY;
final ConcurrentHashMap<TimePoint, WindLegTypeAndLegBearingCache> cachesByTimePoint = new ConcurrentHashMap<>();
@@ -6,6 +6,25 @@
<div class="innerContent">
<h5 class="articleSubheadline">July 2016</h5>
<ul class="bulletList">
<li>If you want to analyze a race, the navigation slightly changed. The button, which is shown for tracked
races won't lead you to the race board directly, but provides a small menu. There you can choose from
several pre-defined analysis, which will launch the race board with respective configurations:
<ul>
<li>Replay/Watch live: Launches race board in play mode starting at race start, if race is already
finished (Replay) or at the actual time, if the race is currently live (Watch live)
</li>
<li>Race analysis: Shows the race results by setting the timer to the end of the race.
<i>This option is only available, if the race is already finished.</i>
</li>
<li>Start analysis: Shows the race start 10 sec after start and shows the speed over ground (SOG) of
the best starters within the competitor chart (zoomed to start).
<i>This option is only available, if the race is already started.</i>
</li>
<li>Winning lanes: Shows the full lanes of the best competitor, if race is already finished or the of
leading competitors if the race is currently live.
</li>
</ul>
</li>
<li>The leaderboard now has a new measure value called "Ride height", which shows the ride height on
foils of a competitor, if respective data is available (currently ESS only). This information can
also be shown in the competitor chart on the RaceBaord page.
@@ -6,6 +6,8 @@ import java.util.Set;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
/**
* Manages a timer and can auto-advance it at a given acceleration/deceleration rate, with a given delay compared to
@@ -392,5 +394,9 @@ public class Timer {
public Date getLiveTimePointAsDate() {
return new Date(getLiveTimePointInMillis());
}
public TimePoint getLiveTimePoint() {
return new MillisecondsTimePoint(getLiveTimePointInMillis());
}
}
@@ -0,0 +1,18 @@
# Patches to GWT Highcharts Wrapper API
## Show reset zoom button
Added method to `org.moxieapps.gwt.highcharts.client.BaseChart` to provide highcharts functionality in Java API:
```
/**
* Patch, to provide highcharts functionality in Java API.
*/
public native void showResetZoom() /*-{
var chart = this.@org.moxieapps.gwt.highcharts.client.BaseChart::chart;
if (chart) chart.showResetZoom();
}-*/;
```
## Other changes
Some other change in the following classes are not documented yet:
- `org.moxieapps.gwt.highcharts.client.BaseChart`
- `org.moxieapps.gwt.highcharts.client.Series`
@@ -16,22 +16,73 @@
package org.moxieapps.gwt.highcharts.client;
import java.util.ArrayList;
import java.util.Iterator;
import org.moxieapps.gwt.highcharts.client.events.AxisSetExtremesEvent;
import org.moxieapps.gwt.highcharts.client.events.ChartClickEvent;
import org.moxieapps.gwt.highcharts.client.events.ChartClickEventHandler;
import org.moxieapps.gwt.highcharts.client.events.ChartLoadEvent;
import org.moxieapps.gwt.highcharts.client.events.ChartLoadEventHandler;
import org.moxieapps.gwt.highcharts.client.events.ChartRedrawEvent;
import org.moxieapps.gwt.highcharts.client.events.ChartRedrawEventHandler;
import org.moxieapps.gwt.highcharts.client.events.ChartSelectionEvent;
import org.moxieapps.gwt.highcharts.client.events.ChartSelectionEventHandler;
import org.moxieapps.gwt.highcharts.client.events.PointClickEvent;
import org.moxieapps.gwt.highcharts.client.events.PointLegendItemClickEvent;
import org.moxieapps.gwt.highcharts.client.events.PointMouseOutEvent;
import org.moxieapps.gwt.highcharts.client.events.PointMouseOverEvent;
import org.moxieapps.gwt.highcharts.client.events.PointRemoveEvent;
import org.moxieapps.gwt.highcharts.client.events.PointSelectEvent;
import org.moxieapps.gwt.highcharts.client.events.PointUnselectEvent;
import org.moxieapps.gwt.highcharts.client.events.PointUpdateEvent;
import org.moxieapps.gwt.highcharts.client.events.SeriesCheckboxClickEvent;
import org.moxieapps.gwt.highcharts.client.events.SeriesClickEvent;
import org.moxieapps.gwt.highcharts.client.events.SeriesHideEvent;
import org.moxieapps.gwt.highcharts.client.events.SeriesLegendItemClickEvent;
import org.moxieapps.gwt.highcharts.client.events.SeriesMouseOutEvent;
import org.moxieapps.gwt.highcharts.client.events.SeriesMouseOverEvent;
import org.moxieapps.gwt.highcharts.client.events.SeriesShowEvent;
import org.moxieapps.gwt.highcharts.client.labels.AxisLabelsData;
import org.moxieapps.gwt.highcharts.client.labels.DataLabelsData;
import org.moxieapps.gwt.highcharts.client.labels.LegendLabelsData;
import org.moxieapps.gwt.highcharts.client.labels.StackLabelsData;
import org.moxieapps.gwt.highcharts.client.plotOptions.AreaPlotOptions;
import org.moxieapps.gwt.highcharts.client.plotOptions.AreaRangePlotOptions;
import org.moxieapps.gwt.highcharts.client.plotOptions.AreaSplinePlotOptions;
import org.moxieapps.gwt.highcharts.client.plotOptions.AreaSplineRangePlotOptions;
import org.moxieapps.gwt.highcharts.client.plotOptions.BarPlotOptions;
import org.moxieapps.gwt.highcharts.client.plotOptions.BoxPlotOptions;
import org.moxieapps.gwt.highcharts.client.plotOptions.BubblePlotOptions;
import org.moxieapps.gwt.highcharts.client.plotOptions.CandlestickPlotOptions;
import org.moxieapps.gwt.highcharts.client.plotOptions.ColumnPlotOptions;
import org.moxieapps.gwt.highcharts.client.plotOptions.ColumnRangePlotOptions;
import org.moxieapps.gwt.highcharts.client.plotOptions.ErrorBarPlotOptions;
import org.moxieapps.gwt.highcharts.client.plotOptions.FunnelPlotOptions;
import org.moxieapps.gwt.highcharts.client.plotOptions.GaugePlotOptions;
import org.moxieapps.gwt.highcharts.client.plotOptions.LinePlotOptions;
import org.moxieapps.gwt.highcharts.client.plotOptions.Marker;
import org.moxieapps.gwt.highcharts.client.plotOptions.OHLCPlotOptions;
import org.moxieapps.gwt.highcharts.client.plotOptions.PiePlotOptions;
import org.moxieapps.gwt.highcharts.client.plotOptions.PlotOptions;
import org.moxieapps.gwt.highcharts.client.plotOptions.ScatterPlotOptions;
import org.moxieapps.gwt.highcharts.client.plotOptions.SeriesPlotOptions;
import org.moxieapps.gwt.highcharts.client.plotOptions.SplinePlotOptions;
import org.moxieapps.gwt.highcharts.client.plotOptions.WaterfallPlotOptions;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.json.client.*;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONBoolean;
import com.google.gwt.json.client.JSONNull;
import com.google.gwt.json.client.JSONNumber;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONString;
import com.google.gwt.json.client.JSONValue;
import com.google.gwt.user.client.ui.Widget;
import org.moxieapps.gwt.highcharts.client.events.*;
import org.moxieapps.gwt.highcharts.client.labels.AxisLabelsData;
import org.moxieapps.gwt.highcharts.client.labels.DataLabelsData;
import org.moxieapps.gwt.highcharts.client.labels.LegendLabelsData;
import org.moxieapps.gwt.highcharts.client.labels.StackLabelsData;
import org.moxieapps.gwt.highcharts.client.plotOptions.*;
import java.util.ArrayList;
import java.util.Iterator;
/**
* The common base class for both {@link Chart} types as well as {@link StockChart} types.
@@ -2986,5 +3037,13 @@ public abstract class BaseChart<T> extends Widget {
private static native void nativeSetTitle(JavaScriptObject chart, JavaScriptObject title, JavaScriptObject subTitle) /*-{
chart.setTitle(title, subTitle);
}-*/;
/**
* Patch, to provide highcharts functionality in Java API.
*/
public native void showResetZoom() /*-{
var chart = this.@org.moxieapps.gwt.highcharts.client.BaseChart::chart;
if (chart) chart.showResetZoom();
}-*/;
}