Merge branch 'permission-vertical' into bug4709_pv

This commit is contained in:
Thomas Wiese
2018-11-05 09:46:06 +01:00
266 changed files with 9423 additions and 913 deletions
@@ -79,8 +79,8 @@ public class SecuredDomainType extends HasPermissionsImpl {
public static final HasPermissions RESULT_IMPORT_URL = new SecuredDomainType("RESULT_IMPORT_URL");
/**
* Describes access permissions to {@code ExpeditionDeviceConfiguration} objects. Type-relative
* object identifier is the {@code ExpeditionDeviceConfiguration.deviceUuid} property as string.
* Describes access permissions to {@code ExpeditionDeviceConfiguration} objects. Type-relative object identifier is
* the WildcardPermissionEncoder.encode(getServerInfo().getServerName(), deviceConfiguration.getName());
*/
public static final HasPermissions EXPEDITION_DEVICE_CONFIGURATION = new SecuredDomainType("EXPEDITION_DEVICE_CONFIGURATION");
@@ -8,13 +8,14 @@ import com.sap.sse.common.NamedWithID;
import com.sap.sse.common.Renamable;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.media.ImageSize;
import com.sap.sse.security.shared.WithQualifiedObjectIdentifier;
import com.sap.sse.shared.media.WithMedia;
/**
* Base interface for an Event consisting of all static information, which might be shared
* by the server and an Android application.
*/
public interface EventBase extends NamedWithID, WithDescription, Renamable, WithMedia {
public interface EventBase extends NamedWithID, WithDescription, Renamable, WithMedia, WithQualifiedObjectIdentifier {
void setDescription(String description);
@@ -13,6 +13,7 @@ import java.util.concurrent.ConcurrentLinkedQueue;
import com.sap.sailing.domain.base.EventBase;
import com.sap.sailing.domain.base.Venue;
import com.sap.sailing.domain.common.security.SecuredDomainType;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
@@ -20,6 +21,8 @@ import com.sap.sse.common.media.ImageSize;
import com.sap.sse.common.media.MediaTagConstants;
import com.sap.sse.common.media.MimeType;
import com.sap.sse.concurrent.CopyOnWriteHashMap;
import com.sap.sse.security.shared.HasPermissions;
import com.sap.sse.security.shared.QualifiedObjectIdentifier;
import com.sap.sse.shared.media.ImageDescriptor;
import com.sap.sse.shared.media.MediaDescriptor;
import com.sap.sse.shared.media.MediaUtils;
@@ -368,4 +371,14 @@ public abstract class EventBaseImpl implements EventBase {
}
return false;
}
@Override
public QualifiedObjectIdentifier getIdentifier() {
return getType().getQualifiedObjectIdentifier(getId().toString());
}
@Override
public HasPermissions getType() {
return SecuredDomainType.EVENT;
}
}
@@ -4,11 +4,7 @@ import java.util.UUID;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.domain.common.security.SecuredDomainType;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sse.security.shared.HasPermissions;
import com.sap.sse.security.shared.QualifiedObjectIdentifier;
import com.sap.sse.security.shared.WithQualifiedObjectIdentifier;
/**
* An event is a group of {@link Regatta regattas} carried out at a common venue within a common time frame. For
@@ -18,7 +14,7 @@ import com.sap.sse.security.shared.WithQualifiedObjectIdentifier;
* @author Axel Uhl (D043530)
*
*/
public interface Event extends EventBase, WithQualifiedObjectIdentifier {
public interface Event extends EventBase {
/**
* For events, the ID is always a UUID.
@@ -74,14 +70,4 @@ public interface Event extends EventBase, WithQualifiedObjectIdentifier {
* logo / link on the event's UI representation.
*/
void setWindFinderReviewedSpotsCollection(Iterable<String> reviewedSpotsCollectionIds);
@Override
default QualifiedObjectIdentifier getIdentifier() {
return getType().getQualifiedObjectIdentifier(getId().toString());
}
@Override
default HasPermissions getType() {
return SecuredDomainType.EVENT;
}
}
@@ -554,16 +554,20 @@ public class CandidateFinderImpl implements CandidateFinder {
new ArrayList<Candidate>(), new ArrayList<Candidate>());
TreeSet<GPSFix> affectedFixes = new TreeSet<GPSFix>(comp);
GPSFixTrack<Competitor, GPSFixMoving> track = race.getTrack(c);
for (GPSFix fix : fixes) {
for (GPSFixMoving fix : fixes) {
if (timeRangeForValidCandidates.getTimeRangeOrNull() != null && timeRangeForValidCandidates.getTimeRangeOrNull().includes(fix.getTimePoint())) {
affectedFixes.add(fix);
GPSFix fixBefore;
GPSFix fixAfter;
GPSFix fixBefore = null;
GPSFix fixAfter = null;
final boolean fixIsValid;
track.lockForRead();
try {
TimePoint timePoint = fix.getTimePoint();
fixBefore = track.getLastFixBefore(timePoint);
fixAfter = track.getFirstFixAfter(timePoint);
fixIsValid = track.isValid(fix);
if (fixIsValid) {
TimePoint t = fix.getTimePoint();
fixBefore = track.getLastFixBefore(t);
fixAfter = track.getFirstFixAfter(t);
}
} finally {
track.unlockAfterRead();
}
@@ -5,10 +5,12 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import com.google.gwt.place.shared.Place;
import com.google.gwt.place.shared.PlaceTokenizer;
import com.sap.sse.common.Util;
public abstract class AbstractMapTokenizer<P extends Place> implements PlaceTokenizer<P> {
@@ -18,48 +20,50 @@ public abstract class AbstractMapTokenizer<P extends Place> implements PlaceToke
public P getPlace(String token) {
return getPlaceFromParameters(toParameterMap(token));
}
protected abstract P getPlaceFromParameters(Map<String, String> parameters);
protected abstract Map<String, String> getParameters(P place);
protected abstract P getPlaceFromParameters(Map<String, Set<String>> parameters);
protected abstract Map<String, Set<String>> getParameters(P place);
@Override
public String getToken(P place) {
return toToken(getParameters(place));
}
private String toToken(Map<String, String> parameters) {
private String toToken(Map<String, Set<String>> parameters) {
StringBuilder stringBuilder = new StringBuilder();
for (Map.Entry<String, String> entry : parameters.entrySet()) {
if(entry.getValue() == null || entry.getValue().isEmpty()) {
for (Map.Entry<String, Set<String>> entry : parameters.entrySet()) {
if (entry.getValue() == null || entry.getValue().isEmpty()) {
continue;
}
if(stringBuilder.length() > 0) {
stringBuilder.append("&");
for (String val : entry.getValue()) {
if (stringBuilder.length() > 0) {
stringBuilder.append("&");
}
stringBuilder.append(entry.getKey() + "=" + val);
}
stringBuilder.append(entry.getKey() + "=" + entry.getValue());
}
return stringBuilder.toString();
}
private Map<String, String> toParameterMap(String placeParametersAsToken) {
private Map<String, Set<String>> toParameterMap(String placeParametersAsToken) {
List<String> list = Arrays.asList(placeParametersAsToken.split("&"));
if (list == null || list.size() < 1) {
logger.warning("Token empty, no-op");
Collections.emptyMap();
}
Map<String, String> result = new HashMap<>();
Map<String, Set<String>> result = new HashMap<>();
for (String listItem : list) {
String[] nvPair = listItem.split("=");
if (nvPair == null || nvPair.length != 2) {
logger.warning("Invalid parameters");
continue;
}
result.put(nvPair[0], nvPair[1]);
Util.addToValueSet(result, nvPair[0], nvPair[1]);
}
return result;
}
@@ -31,6 +31,10 @@
padding: 1.4em .666666666666667em .733333333333333em 1.333333333333333em;
}
.textCellWordWrap {
white-space: normal;
}
.buttonCell {
text-align: right;
vertical-align: middle;
@@ -13,6 +13,8 @@ public interface DesignedCellTableResources extends CellTable.Resources {
public interface DesignedCellTableStyle extends CellTable.Style {
String buttonCell();
String textCellWordWrap();
}
@Override
@@ -9,6 +9,7 @@ import com.sap.sailing.news.EventNewsService;
import com.sap.sailing.server.RacingEventService;
import com.sap.sailing.server.statistics.TrackedRaceStatisticsCache;
import com.sap.sse.gwt.dispatch.client.system.DispatchContext;
import com.sap.sse.security.SecurityService;
/**
* {@link DispatchContext} implementation, which is passed to backend-side {@link SailingAction} executions to provide
@@ -36,6 +37,9 @@ public interface SailingDispatchContext extends DispatchContext {
@GwtIncompatible
Date getCurrentClientTime();
@GwtIncompatible
SecurityService getSecurityService();
<T> T getPreferenceForCurrentUser(String preferenceKey);
void setPreferenceForCurrentUser(String preferenceKey, Object preference);
@@ -51,7 +51,8 @@ public class GetAnniversariesAction implements SailingAction<ResultWithTTL<Anniv
});
TimeToLiveCalculator timeToLiveCalculator = new TimeToLiveCalculator();
HomeServiceUtil.forAllPublicEvents(service, context.getRequest(), timeToLiveCalculator);
HomeServiceUtil.forAllPublicEventsWithReadPermission(service, context.getRequest(), context.getSecurityService(),
timeToLiveCalculator);
return new ResultWithTTL<AnniversariesDTO>(timeToLiveCalculator.getTimeToLive(), anniversaries);
}
@@ -49,8 +49,12 @@ public class GetCompetitionFormatRacesAction implements
@Override
@GwtIncompatible
public ResultWithTTL<ListResult<RaceCompetitionFormatSeriesDTO>> execute(SailingDispatchContext context) throws DispatchException {
context.getSecurityService()
.checkCurrentUserReadPermission(context.getRacingEventService().getLeaderboardByName(regattaId));
context.getSecurityService()
.checkCurrentUserReadPermission(context.getRacingEventService().getRegattaByName(regattaId));
RaceCompetitionFormatDataCalculator competitionFormatDataCalculator = new RaceCompetitionFormatDataCalculator();
EventActionUtil.forRacesOfRegatta(context, eventId, regattaId, competitionFormatDataCalculator);
EventActionUtil.forRacesOfRegattaWithReadPermissions(context, eventId, regattaId, competitionFormatDataCalculator);
Duration timeToLive = EventActionUtil.getEventStateDependentTTL(context, eventId, Duration.ONE_MINUTE);
return new ResultWithTTL<>(timeToLive, new ListResult<>(competitionFormatDataCalculator.getResult()));
}
@@ -19,7 +19,8 @@ public class GetEventListViewAction implements SailingAction<EventListViewDTO>,
@GwtIncompatible
public EventListViewDTO execute(SailingDispatchContext context) {
EventListDataCalculator eventListDataCalculator = new EventListDataCalculator(context.getRacingEventService());
HomeServiceUtil.forAllPublicEvents(context.getRacingEventService(), context.getRequest(),
HomeServiceUtil.forAllPublicEventsWithReadPermission(context.getRacingEventService(), context.getRequest(),
context.getSecurityService(),
eventListDataCalculator);
return eventListDataCalculator.getResult();
}
@@ -61,6 +61,7 @@ public class GetEventViewAction implements SailingAction<EventViewDTO>, IsClient
if (event == null) {
throw new RuntimeException("Event not found");
}
context.getSecurityService().checkCurrentUserReadPermission(event);
final EventViewDTO dto = new EventViewDTO();
HomeServiceUtil.mapToMetadataDTO(event, dto, context.getRacingEventService());
@@ -89,16 +90,16 @@ public class GetEventViewAction implements SailingAction<EventViewDTO>, IsClient
final Set<RegattaMetadataDTO> regattasOfEvent = new HashSet<>();
final Set<LeaderboardGroup> relevantLeaderboardGroupsOfEvent = new HashSet<>();
EventActionUtil.forLeaderboardsOfEvent(context, event, new LeaderboardCallback() {
EventActionUtil.forLeaderboardsOfEventWithReadPermissions(context, event, new LeaderboardCallback() {
@Override
public void doForLeaderboard(LeaderboardContext context) {
public void doForLeaderboard(LeaderboardContext lcontext) {
try {
Util.addAll(context.getLeaderboardGroups(), relevantLeaderboardGroupsOfEvent);
RegattaMetadataDTO regattaDTO = context.asRegattaMetadataDTO();
Util.addAll(lcontext.getLeaderboardGroups(), relevantLeaderboardGroupsOfEvent);
RegattaMetadataDTO regattaDTO = lcontext.asRegattaMetadataDTO();
regattasOfEvent.add(regattaDTO);
dto.getRegattas().add(regattaDTO);
} catch (Exception e) {
logger.log(Level.SEVERE, "Catched exception while reading data for leaderboard " + context.getLeaderboardName(), e);
logger.log(Level.SEVERE, "Catched exception while reading data for leaderboard " + lcontext.getLeaderboardName(), e);
}
}
});
@@ -50,7 +50,7 @@ public class GetFinishedRacesAction implements SailingAction<ResultWithTTL<ListR
@GwtIncompatible
public ResultWithTTL<ListResult<RaceListRaceDTO>> execute(SailingDispatchContext context) {
RaceListDataCalculator raceListDataCalculator = new RaceListDataCalculator();
EventActionUtil.forRacesOfRegatta(context, eventId, regattaId, raceListDataCalculator);
EventActionUtil.forRacesOfRegattaWithReadPermissions(context, eventId, regattaId, raceListDataCalculator);
return new ResultWithTTL<>(EventActionUtil.getEventStateDependentTTL(context, eventId,
Duration.ONE_MINUTE.times(5)), new ListResult<>(raceListDataCalculator.getResult()));
@@ -50,11 +50,11 @@ public class GetLiveRacesForEventAction implements SailingAction<ResultWithTTL<S
@Override
@GwtIncompatible
public ResultWithTTL<SortedSetResult<LiveRaceDTO>> execute(final SailingDispatchContext context) {
return EventActionUtil.withLiveRaceOrDefaultSchedule(context, eventId, new CalculationWithEvent<SortedSetResult<LiveRaceDTO>>() {
return EventActionUtil.withLiveRaceOrDefaultScheduleWithReadPermissions(context, eventId, new CalculationWithEvent<SortedSetResult<LiveRaceDTO>>() {
@Override
public ResultWithTTL<SortedSetResult<LiveRaceDTO>> calculateWithEvent(Event event) {
LiveRaceCalculator liveRaceCalculator = new LiveRaceCalculator();
EventActionUtil.forRacesOfEvent(context, eventId, liveRaceCalculator);
EventActionUtil.forRacesOfEventWithReadPermissions(context, eventId, liveRaceCalculator);
return liveRaceCalculator.getResult();
}
});
@@ -53,11 +53,11 @@ public class GetLiveRacesForRegattaAction implements SailingAction<ResultWithTTL
@Override
@GwtIncompatible
public ResultWithTTL<SortedSetResult<LiveRaceDTO>> execute(final SailingDispatchContext context) {
return EventActionUtil.withLiveRaceOrDefaultSchedule(context, eventId, new CalculationWithEvent<SortedSetResult<LiveRaceDTO>>() {
return EventActionUtil.withLiveRaceOrDefaultScheduleWithReadPermissions(context, eventId, new CalculationWithEvent<SortedSetResult<LiveRaceDTO>>() {
@Override
public ResultWithTTL<SortedSetResult<LiveRaceDTO>> calculateWithEvent(Event event) {
LiveRaceCalculator liveRaceCalculator = new LiveRaceCalculator();
EventActionUtil.forRacesOfRegatta(context, eventId, regattaName, liveRaceCalculator);
EventActionUtil.forRacesOfRegattaWithReadPermissions(context, eventId, regattaName, liveRaceCalculator);
return liveRaceCalculator.getResult();
}
});
@@ -48,8 +48,9 @@ public class GetRegattaListViewAction implements SailingAction<ResultWithTTL<Sor
@Override
@GwtIncompatible
public ResultWithTTL<SortedSetResult<RegattaWithProgressDTO>> execute(final SailingDispatchContext context) {
context.getSecurityService().checkCurrentUserReadPermission(context.getRacingEventService().getEvent(eventId));
final List<RegattaWithProgressDTO> result = new LinkedList<>();
EventActionUtil.forLeaderboardsOfEvent(context, eventId, new LeaderboardCallback() {
EventActionUtil.forLeaderboardsOfEventWithReadPermissions(context, eventId, new LeaderboardCallback() {
@Override
public void doForLeaderboard(LeaderboardContext leaderboardContext) {
result.add(leaderboardContext.getRegattaWithProgress());
@@ -46,7 +46,7 @@ public class GetRegattaStatisticsAction implements SailingAction<ResultWithTTL<E
@GwtIncompatible
public ResultWithTTL<EventStatisticsDTO> execute(SailingDispatchContext context) throws DispatchException {
StatisticsCalculator statisticsCalculator = new StatisticsCalculator(context.getTrackedRaceStatisticsCache());
statisticsCalculator.doForLeaderboard(EventActionUtil.getLeaderboardContext(context, eventId, regattaId));
statisticsCalculator.doForLeaderboard(EventActionUtil.getLeaderboardContextWithReadPermissions(context, eventId, regattaId));
return statisticsCalculator.getResult();
}
@@ -48,8 +48,11 @@ public class GetRegattaWithProgressAction implements SailingAction<ResultWithTTL
@Override
@GwtIncompatible
public ResultWithTTL<RegattaWithProgressDTO> execute(SailingDispatchContext context) {
context.getSecurityService().checkCurrentUserReadPermission(context.getRacingEventService().getEvent(eventId));
context.getSecurityService()
.checkCurrentUserReadPermission(context.getRacingEventService().getRegattaByName(regattaId));
return new ResultWithTTL<>(EventActionUtil.getEventStateDependentTTL(context, eventId, Duration.ONE_MINUTE.times(5)),
EventActionUtil.getLeaderboardContext(context, eventId, regattaId).getRegattaWithProgress());
EventActionUtil.getLeaderboardContextWithReadPermissions(context, eventId, regattaId).getRegattaWithProgress());
}
@Override
@@ -52,12 +52,13 @@ public class GetRegattasAndLiveRacesForEventAction implements SailingAction<Resu
@Override
@GwtIncompatible
public ResultWithTTL<RegattasAndLiveRacesDTO> execute(SailingDispatchContext context) {
context.getSecurityService().checkCurrentUserReadPermission(context.getRacingEventService().getEvent(eventId));
final Map<String, RegattaMetadataDTO> regattas = mapRegattas(context);
final TreeMap<RegattaMetadataDTO, TreeSet<LiveRaceDTO>> regattasWithRaces = new TreeMap<>();
final TreeSet<RegattaMetadataDTO> regattasWithoutRaces = new TreeSet<>();
final RaceRefreshCalculator refreshCalculator = new RaceRefreshCalculator();
EventActionUtil.forRacesOfEvent(context, eventId, raceContent -> {
EventActionUtil.forRacesOfEventWithReadPermissions(context, eventId, raceContent -> {
Optional<LiveRaceDTO> liveRace = Optional.ofNullable(raceContent.getLiveRaceOrNull());
liveRace.ifPresent(r -> ensureRegatta(raceContent, regattas, regattasWithRaces).add(r));
refreshCalculator.doForRace(raceContent);
@@ -66,13 +67,13 @@ public class GetRegattasAndLiveRacesForEventAction implements SailingAction<Resu
.forEach(regattasWithoutRaces::add);
RegattasAndLiveRacesDTO dto = new RegattasAndLiveRacesDTO(regattasWithRaces, regattasWithoutRaces);
ResultWithTTL<RegattasAndLiveRacesDTO> result = new ResultWithTTL<>(refreshCalculator.getTTL(), dto);
return EventActionUtil.withLiveRaceOrDefaultSchedule(context, eventId, event -> result, dto);
return EventActionUtil.withLiveRaceOrDefaultScheduleWithReadPermissions(context, eventId, event -> result, dto);
}
@GwtIncompatible
private Map<String, RegattaMetadataDTO> mapRegattas(SailingDispatchContext context) {
final Map<String, RegattaMetadataDTO> result = new HashMap<>();
EventActionUtil.forLeaderboardsOfEvent(context, eventId, new LeaderboardCallback() {
EventActionUtil.forLeaderboardsOfEventWithReadPermissions(context, eventId, new LeaderboardCallback() {
@Override
public void doForLeaderboard(LeaderboardContext context) {
result.put(context.getLeaderboardName(), context.asRegattaMetadataDTO());
@@ -45,7 +45,12 @@ public class GetSeriesStatisticsAction implements SailingAction<ResultWithTTL<Ev
public ResultWithTTL<EventStatisticsDTO> execute(SailingDispatchContext context) throws DispatchException {
StatisticsCalculator statisticsCalculator = new StatisticsCalculator(context.getTrackedRaceStatisticsCache());
final LeaderboardGroup leaderboardGroupByID = context.getRacingEventService().getLeaderboardGroupByID(seriesLeaderboardGroupId);
leaderboardGroupByID.getLeaderboards().forEach(statisticsCalculator::addLeaderboard);
context.getSecurityService().checkCurrentUserReadPermission(leaderboardGroupByID);
leaderboardGroupByID.getLeaderboards().forEach(t -> {
if (context.getSecurityService().hasCurrentUserReadPermission(t)) {
statisticsCalculator.addLeaderboard(t);
}
});
return statisticsCalculator.getResult();
}
@@ -43,4 +43,29 @@ public class SimpleCompetitorWithIdDTO extends SimpleCompetitorDTO implements Re
? this.idAsString.compareTo(((SimpleCompetitorWithIdDTO) obj).idAsString) : compareTo;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((idAsString == null) ? 0 : idAsString.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
SimpleCompetitorWithIdDTO other = (SimpleCompetitorWithIdDTO) obj;
if (idAsString == null) {
if (other.idAsString != null)
return false;
} else if (!idAsString.equals(other.idAsString))
return false;
return true;
}
}
@@ -64,7 +64,7 @@ public class GetMiniLeaderbordAction implements SailingAction<ResultWithTTL<GetM
@Override
@GwtIncompatible
public ResultWithTTL<GetMiniLeaderboardDTO> execute(SailingDispatchContext context) {
return EventActionUtil.getLeaderboardContext(context, eventId, leaderboardName).calculateMiniLeaderboard(context.getRacingEventService(), limit);
return EventActionUtil.getLeaderboardContextWithReadPermissions(context, eventId, leaderboardName).calculateMiniLeaderboard(context.getRacingEventService(), limit);
}
@Override
@@ -4,6 +4,7 @@ import java.util.UUID;
import com.google.gwt.core.shared.GwtIncompatible;
import com.sap.sailing.domain.base.Event;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.gwt.common.communication.routing.ProvidesLeaderboardRouting;
import com.sap.sailing.gwt.home.communication.SailingAction;
@@ -63,11 +64,16 @@ public class GetMiniOverallLeaderbordAction implements SailingAction<ResultWithT
@GwtIncompatible
public ResultWithTTL<GetMiniLeaderboardDTO> execute(SailingDispatchContext context) {
LeaderboardGroup leaderboardGroup = context.getRacingEventService().getLeaderboardGroupByID(leaderboardGroupUUID);
context.getSecurityService().checkCurrentUserReadPermission(leaderboardGroup);
if(leaderboardGroup == null) {
throw new RuntimeException("Invalid leaderboardGroupID");
}
Event event = HomeServiceUtil.determineBestMatchingEvent(context.getRacingEventService(), leaderboardGroup);
LeaderboardContext lctx = new LeaderboardContext(context, event, event.getLeaderboardGroups(), leaderboardGroup.getOverallLeaderboard());
context.getSecurityService().checkCurrentUserReadPermission(event);
Leaderboard overallLeaderBoard = leaderboardGroup.getOverallLeaderboard();
context.getSecurityService().checkCurrentUserReadPermission(overallLeaderBoard);
LeaderboardContext lctx = new LeaderboardContext(context, event, event.getLeaderboardGroups(),
overallLeaderBoard);
return lctx.calculateMiniLeaderboard(context.getRacingEventService(), limit);
}
@@ -73,7 +73,7 @@ public class GetEventOverviewNewsAction implements SailingAction<ResultWithTTL<L
@Override
@GwtIncompatible
public ResultWithTTL<ListResult<NewsEntryDTO>> execute(final SailingDispatchContext context) {
return EventActionUtil.withLiveRaceOrDefaultSchedule(context, eventId, new CalculationWithEvent<ListResult<NewsEntryDTO>>() {
return EventActionUtil.withLiveRaceOrDefaultScheduleWithReadPermissions(context, eventId, new CalculationWithEvent<ListResult<NewsEntryDTO>>() {
@Override
public ResultWithTTL<ListResult<NewsEntryDTO>> calculateWithEvent(Event event) {
return new ResultWithTTL<>(Duration.ONE_MINUTE.times(2), new ListResult<NewsEntryDTO>(getNews(context, event)));
@@ -42,7 +42,7 @@ public class GetEventStatisticsAction implements SailingAction<ResultWithTTL<Eve
@GwtIncompatible
public ResultWithTTL<EventStatisticsDTO> execute(SailingDispatchContext context) throws DispatchException {
StatisticsCalculator statisticsCalculator = new StatisticsCalculator(context.getTrackedRaceStatisticsCache());
EventActionUtil.forLeaderboardsOfEvent(context, eventId, statisticsCalculator);
EventActionUtil.forLeaderboardsOfEventWithReadPermissions(context, eventId, statisticsCalculator);
return statisticsCalculator.getResult();
}
@@ -41,7 +41,8 @@ public class GetRecentEventsAction implements SailingAction<ListResult<EventQuic
@GwtIncompatible
public ListResult<EventQuickfinderDTO> execute(final SailingDispatchContext context) throws DispatchException {
final SortedSet<EventQuickfinderDTO> events = new TreeSet<>();
HomeServiceUtil.forAllPublicEvents(context.getRacingEventService(), context.getRequest(), new EventVisitor() {
HomeServiceUtil.forAllPublicEventsWithReadPermission(context.getRacingEventService(), context.getRequest(),
context.getSecurityService(), new EventVisitor() {
@Override
public void visit(EventBase event, boolean onRemoteServer, URL baseURL) {
EventQuickfinderDTO dto = new EventQuickfinderDTO();
@@ -36,7 +36,8 @@ public class GetStagedEventsAction implements SailingAction<ListResult<EventStag
@GwtIncompatible
public ListResult<EventStageDTO> execute(final SailingDispatchContext context) throws DispatchException {
EventStageCandidateCalculator stageCandidateCalculator = new EventStageCandidateCalculator();
HomeServiceUtil.forAllPublicEvents(context.getRacingEventService(), context.getRequest(),
HomeServiceUtil.forAllPublicEventsWithReadPermission(context.getRacingEventService(), context.getRequest(),
context.getSecurityService(),
stageCandidateCalculator);
ListResult<EventStageDTO> result = new ListResult<>();
int count = 0;
@@ -47,7 +47,8 @@ public class GetStartViewAction implements SailingAction<StartViewDTO>, IsClient
public StartViewDTO execute(SailingDispatchContext context) {
EventStageCandidateCalculator stageCandidateCalculator = new EventStageCandidateCalculator();
RecentEventsCalculator recentEventsCalculator = new RecentEventsCalculator();
HomeServiceUtil.forAllPublicEvents(context.getRacingEventService(), context.getRequest(), stageCandidateCalculator, recentEventsCalculator);
HomeServiceUtil.forAllPublicEventsWithReadPermission(context.getRacingEventService(), context.getRequest(),
context.getSecurityService(), stageCandidateCalculator, recentEventsCalculator);
StartViewDTO result = new StartViewDTO();
int count = 0;
for (Pair<StageEventType, EventHolder> pair : stageCandidateCalculator.getFeaturedEvents()) {
@@ -0,0 +1,56 @@
package com.sap.sailing.gwt.home.communication.user.profile.domain;
import java.io.Serializable;
import java.util.UUID;
/** This object represents a badge in a sailor profile {@link SailorProfileDTO} */
public class BadgeDTO implements Serializable {
private static final long serialVersionUID = 2374312724838738559L;
private UUID key;
private String name;
protected BadgeDTO() {
}
public BadgeDTO(UUID key, String name) {
super();
this.key = key;
this.name = name;
}
public UUID getKey() {
return key;
}
public String getName() {
return name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((key == null) ? 0 : key.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
BadgeDTO other = (BadgeDTO) obj;
if (key == null) {
if (other.key != null)
return false;
} else if (!key.equals(other.key))
return false;
return true;
}
}
@@ -0,0 +1,42 @@
package com.sap.sailing.gwt.home.communication.user.profile.domain;
import java.io.Serializable;
import java.util.ArrayList;
import com.sap.sse.common.Util;
/**
* This object contains the name and id with a fixed set of Participated regattas ({@link ParticipatedRegattaDTO} in a
* {@link SailorProfileDTO} which is loaded asynchronously.
*/
public class ParticipatedEventDTO implements Serializable {
private static final long serialVersionUID = -716458570343167392L;
private String eventName;
private String eventId;
private ArrayList<ParticipatedRegattaDTO> participatedRegattas = new ArrayList<>();
protected ParticipatedEventDTO() {
}
public ParticipatedEventDTO(String eventName, String eventId,
Iterable<ParticipatedRegattaDTO> participatedRegattas) {
super();
this.eventName = eventName;
this.eventId = eventId;
Util.addAll(participatedRegattas, this.participatedRegattas);
}
public String getEventName() {
return eventName;
}
public String getEventId() {
return eventId;
}
public Iterable<ParticipatedRegattaDTO> getParticipatedRegattas() {
return participatedRegattas;
}
}
@@ -0,0 +1,60 @@
package com.sap.sailing.gwt.home.communication.user.profile.domain;
import java.io.Serializable;
import com.sap.sailing.gwt.home.communication.event.SimpleCompetitorWithIdDTO;
/**
* This object contains all relevant data of a regatta, a competitor ({@link #competitorDto}) participated in.
* Additionally to the name of the regatta and the unique IDs of the regatta and the containing event, this objects
* contains information about the rank and points of the participating competitor.
*/
public class ParticipatedRegattaDTO implements Serializable {
private static final long serialVersionUID = -126348629933556831L;
private String regattaName;
private int regattaRank;
private SimpleCompetitorWithIdDTO competitorDto;
private String regattaId;
private String eventId;
private double sumPoints;
protected ParticipatedRegattaDTO() {
}
public ParticipatedRegattaDTO(String regattaName, int regattaRank, SimpleCompetitorWithIdDTO competitorDto,
String regattaId, String eventId, double sumPoints) {
super();
this.regattaName = regattaName;
this.regattaRank = regattaRank;
this.competitorDto = competitorDto;
this.regattaId = regattaId;
this.eventId = eventId;
this.sumPoints = sumPoints;
}
public String getRegattaName() {
return regattaName;
}
public int getRegattaRank() {
return regattaRank;
}
public SimpleCompetitorWithIdDTO getCompetitorDto() {
return competitorDto;
}
public String getRegattaId() {
return regattaId;
}
public String getEventId() {
return eventId;
}
public double getSumPoints() {
return sumPoints;
}
}
@@ -0,0 +1,109 @@
package com.sap.sailing.gwt.home.communication.user.profile.domain;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.UUID;
import com.sap.sailing.domain.common.dto.BoatClassDTO;
import com.sap.sailing.gwt.home.communication.event.SimpleCompetitorWithIdDTO;
import com.sap.sse.common.Util;
import com.sap.sse.gwt.dispatch.shared.commands.Result;
/**
* This objects contains all data related to displaying a SailorProfile in the frontend UI. Since this object is also
* used when querying a sailor profile with a key from the backend, there is a field {@link #notFoundOnServer} to
* intercept the case that there is no corresponding sailor profile.<br/>
* <br/>
* Events and statistics are loaded asynchronously with {@link SailorProfileEventsDTO} and
* {@link SailorProfileStatisticDTO}.
*/
public class SailorProfileDTO implements Result, Serializable {
private static final long serialVersionUID = -5957161570595861618L;
/** {@link #notFoundOnServer} is true, if the {@link #key} does has no corresponding sailor profile. */
private boolean notFoundOnServer;
private UUID key;
private String name;
private ArrayList<SimpleCompetitorWithIdDTO> competitors = new ArrayList<>();
private ArrayList<BadgeDTO> badges = new ArrayList<>();
private ArrayList<BoatClassDTO> boatclasses = new ArrayList<>();
protected SailorProfileDTO() {
}
public SailorProfileDTO(boolean notFound) {
this.notFoundOnServer = notFound;
}
public SailorProfileDTO(UUID key, String name) {
this.key = key;
this.name = name;
}
public SailorProfileDTO(UUID key, String name, Iterable<SimpleCompetitorWithIdDTO> competitors,
Iterable<BadgeDTO> badges, Iterable<BoatClassDTO> boatclasses) {
super();
this.key = key;
this.name = name;
Util.addAll(competitors, this.competitors);
Util.addAll(badges, this.badges);
Util.addAll(boatclasses, this.boatclasses);
}
public boolean isNotFoundOnServer() {
return notFoundOnServer;
}
public UUID getKey() {
return key;
}
public Collection<SimpleCompetitorWithIdDTO> getCompetitors() {
return competitors;
}
public Collection<BadgeDTO> getBadges() {
return badges;
}
public Iterable<BoatClassDTO> getBoatclasses() {
return boatclasses;
}
public String getName() {
return name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((key == null) ? 0 : key.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
SailorProfileDTO other = (SailorProfileDTO) obj;
if (key == null) {
if (other.key != null)
return false;
} else if (!key.equals(other.key))
return false;
return true;
}
public void setName(String newName) {
this.name = newName;
}
}
@@ -0,0 +1,29 @@
package com.sap.sailing.gwt.home.communication.user.profile.domain;
import java.io.Serializable;
import java.util.ArrayList;
import com.sap.sse.common.Util;
import com.sap.sse.gwt.dispatch.shared.commands.Result;
/**
* Since the Events are loaded asynchronously after loading the {@link SailorProfileDTO}, this object contains a list of
* {@link ParticipatedEventDTO}.
*/
public class SailorProfileEventsDTO implements Serializable, Result {
private static final long serialVersionUID = 4658596949068331464L;
private ArrayList<ParticipatedEventDTO> participatedEvents = new ArrayList<>();
protected SailorProfileEventsDTO() {
}
public SailorProfileEventsDTO(Iterable<ParticipatedEventDTO> participatedEvents) {
Util.addAll(participatedEvents, this.participatedEvents);
}
public Iterable<ParticipatedEventDTO> getParticipatedEvents() {
return participatedEvents;
}
}
@@ -0,0 +1,32 @@
package com.sap.sailing.gwt.home.communication.user.profile.domain;
import com.sap.sailing.gwt.ui.raceboard.RaceBoardModes;
/** current types of statistics currently supported in the SailorProfiles and the corresponding backend service */
public enum SailorProfileNumericStatisticType {
MAX_SPEED(StatisticType.HIGHEST_IS_BEST, RaceBoardModes.PLAYER), BEST_DISTANCE_TO_START(
StatisticType.LOWEST_IS_BEST,
RaceBoardModes.START_ANALYSIS), BEST_STARTLINE_SPEED(StatisticType.HIGHEST_IS_BEST,
RaceBoardModes.START_ANALYSIS), AVERAGE_STARTLINE_DISTANCE(StatisticType.AVERAGE, null);
private StatisticType type;
private RaceBoardModes mode;
SailorProfileNumericStatisticType(StatisticType type, RaceBoardModes mode) {
this.type = type;
this.mode = mode;
}
public StatisticType getAggregationType() {
return type;
}
public RaceBoardModes getPlayerMode() {
return this.mode;
}
public static enum StatisticType {
LOWEST_IS_BEST, HIGHEST_IS_BEST, AVERAGE
}
}
@@ -0,0 +1,136 @@
package com.sap.sailing.gwt.home.communication.user.profile.domain;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import com.google.gwt.core.shared.GwtIncompatible;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.gwt.home.communication.event.SimpleCompetitorWithIdDTO;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.gwt.dispatch.shared.commands.Result;
/**
* Contains the result of a single statistic, to allow asynchronous loading of statistics without blocking loading the
* sailor profile or the events
**/
public class SailorProfileStatisticDTO implements Result, Serializable {
private static final long serialVersionUID = 2924378586764418626L;
// keep as specified as possible to save gwt compiler time
private HashMap<SimpleCompetitorWithIdDTO, ArrayList<SingleEntry>> result = new HashMap<>();
private String dataMiningQuery;
// GWTSerialisation only
protected SailorProfileStatisticDTO() {
super();
}
public SailorProfileStatisticDTO(Map<SimpleCompetitorWithIdDTO, ArrayList<SingleEntry>> result,
String dataMiningQuery) {
this.result.putAll(result);
this.dataMiningQuery = dataMiningQuery;
}
public HashMap<SimpleCompetitorWithIdDTO, ArrayList<SingleEntry>> getResult() {
return result;
}
public String getDataMiningQuery() {
return dataMiningQuery;
}
public static class SingleEntry implements Serializable {
private static final long serialVersionUID = -7722750678632551505L;
@GwtIncompatible
public SingleEntry(Double value, RegattaAndRaceIdentifier relatedRaceOrNull, TimePoint relatedTimePointOrNull,
TimePoint relatedRaceStartTimePointOrNull, String leaderboardNameOrNull,
String leaderboardGroupNameOrNull, UUID eventIdOrNull, String raceNameOrNull) {
super();
this.value = value;
this.relatedRaceOrNull = relatedRaceOrNull;
this.leaderboardNameOrNull = leaderboardNameOrNull;
this.leaderboardGroupNameOrNull = leaderboardGroupNameOrNull;
this.eventIdOrNull = eventIdOrNull;
this.raceNameOrNull = raceNameOrNull;
if (relatedTimePointOrNull != null) {
// not all TimePoints are GWT compatible, ensure we have a compatible one!
if (relatedTimePointOrNull instanceof MillisecondsTimePoint) {
this.relatedTimePointOrNull = (MillisecondsTimePoint) relatedTimePointOrNull;
} else {
this.relatedTimePointOrNull = new MillisecondsTimePoint(relatedTimePointOrNull.asMillis());
}
}
if (relatedRaceStartTimePointOrNull != null) {
if (relatedRaceStartTimePointOrNull instanceof MillisecondsTimePoint) {
this.relatedRaceStartTimePointOrNull = (MillisecondsTimePoint) relatedRaceStartTimePointOrNull;
} else {
this.relatedRaceStartTimePointOrNull = new MillisecondsTimePoint(
relatedRaceStartTimePointOrNull.asMillis());
}
}
}
// GWTSerialisation only
protected SingleEntry() {
}
private Double value;
private RegattaAndRaceIdentifier relatedRaceOrNull;
// not generic, to reduce possible permutations for gwt compiler
private MillisecondsTimePoint relatedTimePointOrNull;
private MillisecondsTimePoint relatedRaceStartTimePointOrNull;
private String leaderboardNameOrNull;
private String leaderboardGroupNameOrNull;
private String raceNameOrNull;
private UUID eventIdOrNull;
/**
* All values will be in SI Units if not otherwise stated in the Type documentation
*/
public Double getValue() {
return value;
}
public RegattaAndRaceIdentifier getRelatedRaceOrNull() {
return relatedRaceOrNull;
}
public MillisecondsTimePoint getRelatedTimePointOrNull() {
return relatedTimePointOrNull;
}
@Override
public String toString() {
return "SingleEntry [value=" + value + ", relatedRaceOrNull=" + relatedRaceOrNull
+ ", relatedTimePointOrNull=" + relatedTimePointOrNull + ", relatedRaceStartTimePointOrNull="
+ relatedRaceStartTimePointOrNull + ", leaderboardNameOrNull=" + leaderboardNameOrNull
+ ", leaderboardGroupNameOrNull=" + leaderboardGroupNameOrNull + ", raceNameOrNull="
+ raceNameOrNull + ", eventIdOrNull=" + eventIdOrNull + "]";
}
public String getLeaderboardNameOrNull() {
return leaderboardNameOrNull;
}
public UUID getEventIdOrNull() {
return eventIdOrNull;
}
public String getLeaderboardGroupNameOrNull() {
return leaderboardGroupNameOrNull;
}
public MillisecondsTimePoint getRelatedRaceStartTimePointOrNull() {
return relatedRaceStartTimePointOrNull;
}
public String getRaceNameOrNull() {
return raceNameOrNull;
}
}
}
@@ -0,0 +1,31 @@
package com.sap.sailing.gwt.home.communication.user.profile.domain;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import com.sap.sse.common.Util;
import com.sap.sse.gwt.dispatch.shared.commands.Result;
/**
* This domain object contains a list of {@link SailorProfileDTO} objects which is loaded to display the sailor profile
* overview.
*/
public class SailorProfilesDTO implements Result, Serializable {
private static final long serialVersionUID = -7062326258445057995L;
private ArrayList<SailorProfileDTO> entries = new ArrayList<>();
protected SailorProfilesDTO() {
}
public SailorProfilesDTO(Iterable<SailorProfileDTO> entries) {
super();
Util.addAll(entries, this.entries);
}
public List<SailorProfileDTO> getEntries() {
return entries;
}
}
@@ -0,0 +1,31 @@
package com.sap.sailing.gwt.home.communication.user.profile.sailorprofile;
import java.util.UUID;
import com.google.gwt.core.shared.GwtIncompatible;
import com.sap.sailing.domain.base.CompetitorAndBoatStore;
import com.sap.sailing.server.impl.preferences.model.SailorProfilePreference;
/**
* {@link UpdateSailorProfileAction} implementation to create a new sailor profile for the currently logged in user with
* a UUID and a name.
*/
public class CreateSailorProfileAction extends UpdateSailorProfileAction {
private String name;
public CreateSailorProfileAction(UUID uuid, String name) {
super(uuid);
this.name = name;
}
protected CreateSailorProfileAction() {
}
@GwtIncompatible
@Override
protected SailorProfilePreference updatePreference(CompetitorAndBoatStore store, SailorProfilePreference p) {
return new SailorProfilePreference(store, uuid, name);
}
}
@@ -0,0 +1,85 @@
package com.sap.sailing.gwt.home.communication.user.profile.sailorprofile;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import com.sap.sailing.datamining.data.HasLeaderboardContext;
import com.sap.sailing.datamining.data.HasLeaderboardGroupContext;
import com.sap.sailing.datamining.data.HasRaceOfCompetitorContext;
import com.sap.sailing.datamining.data.HasTrackedRaceContext;
import com.sap.sailing.gwt.home.communication.user.profile.domain.SailorProfileNumericStatisticType;
import com.sap.sailing.server.RacingEventService;
import com.sap.sse.common.Distance;
import com.sap.sse.datamining.shared.DataMiningQuerySerializer;
import com.sap.sse.datamining.shared.dto.StatisticQueryDefinitionDTO;
import com.sap.sse.datamining.shared.impl.dto.AggregationProcessorDefinitionDTO;
import com.sap.sse.datamining.shared.impl.dto.DataRetrieverChainDefinitionDTO;
import com.sap.sse.datamining.shared.impl.dto.DataRetrieverLevelDTO;
import com.sap.sse.datamining.shared.impl.dto.FunctionDTO;
import com.sap.sse.datamining.shared.impl.dto.LocalizedTypeDTO;
import com.sap.sse.datamining.shared.impl.dto.ModifiableStatisticQueryDefinitionDTO;
public final class DataMiningQueryCreatorForSailorProfiles {
private DataMiningQueryCreatorForSailorProfiles() {
}
private static final String leaderboardGroupRetrievalProcessorName = "com.sap.sailing.datamining.impl.components.LeaderboardGroupRetrievalProcessor";
private static final String leaderboardRetrievalProcessorName = "com.sap.sailing.datamining.impl.components.LeaderboardRetrievalProcessor";
private static final String trackedRaceRetrievalProcessorName = "com.sap.sailing.datamining.impl.components.TrackedRaceRetrievalProcessor";
private static final String raceCompetitorRetrievalProcessorName = "com.sap.sailing.datamining.impl.components.RaceOfCompetitorRetrievalProcessor";
public static final String getSerializedDataMiningQuery(SailorProfileNumericStatisticType type,
List<String> competitorNames) {
switch (type) {
case AVERAGE_STARTLINE_DISTANCE:
return DataMiningQuerySerializer.toBase64String(createQueryForAverageStartlineDistance(competitorNames));
default:
return null;
}
}
private static StatisticQueryDefinitionDTO createQueryForAverageStartlineDistance(
final List<String> competitorNames) {
FunctionDTO statistic = new FunctionDTO(false, "getDistanceToStartLineAtStart()",
HasRaceOfCompetitorContext.class.getName(), Distance.class.getName(), new ArrayList<String>(), "", 0);
AggregationProcessorDefinitionDTO aggregator = new AggregationProcessorDefinitionDTO("Minimum",
Distance.class.getName(), Distance.class.getName(), "");
ArrayList<DataRetrieverLevelDTO> retrieverLevels = new ArrayList<>();
retrieverLevels.add(new DataRetrieverLevelDTO(0, leaderboardGroupRetrievalProcessorName,
new LocalizedTypeDTO(HasLeaderboardGroupContext.class.getName(), ""), null));
retrieverLevels.add(new DataRetrieverLevelDTO(1, leaderboardRetrievalProcessorName,
new LocalizedTypeDTO(HasLeaderboardContext.class.getName(), ""), null));
retrieverLevels.add(new DataRetrieverLevelDTO(2, trackedRaceRetrievalProcessorName,
new LocalizedTypeDTO(HasTrackedRaceContext.class.getName(), ""), null));
retrieverLevels.add(new DataRetrieverLevelDTO(3, raceCompetitorRetrievalProcessorName,
new LocalizedTypeDTO(HasRaceOfCompetitorContext.class.getName(), ""), null));
DataRetrieverChainDefinitionDTO retrieverChain = new DataRetrieverChainDefinitionDTO("",
RacingEventService.class.getName(), retrieverLevels);
ModifiableStatisticQueryDefinitionDTO queryDefinition = new ModifiableStatisticQueryDefinitionDTO("en",
statistic, aggregator, retrieverChain);
HashMap<FunctionDTO, HashSet<? extends Serializable>> retrieverlevel3_FilterSelection = new HashMap<>();
FunctionDTO filterDimension0 = new FunctionDTO(true, "getCompetitor().getName()",
HasRaceOfCompetitorContext.class.getName(), String.class.getName(), new ArrayList<String>(), "", 0);
HashSet<Serializable> filterDimension0_Selection = new HashSet<>();
competitorNames.forEach(filterDimension0_Selection::add);
retrieverlevel3_FilterSelection.put(filterDimension0, filterDimension0_Selection);
queryDefinition.setFilterSelectionFor(retrieverChain.getRetrieverLevel(3), retrieverlevel3_FilterSelection);
FunctionDTO dimensionToGroupBy0 = new FunctionDTO(true, "getTrackedRaceContext().getRace().getName()",
HasRaceOfCompetitorContext.class.getName(), String.class.getName(), new ArrayList<String>(), "", 0);
queryDefinition.appendDimensionToGroupBy(dimensionToGroupBy0);
FunctionDTO dimensionToGroupBy1 = new FunctionDTO(true, "getCompetitor().getName()",
HasRaceOfCompetitorContext.class.getName(), String.class.getName(), new ArrayList<String>(), "", 0);
queryDefinition.appendDimensionToGroupBy(dimensionToGroupBy1);
return queryDefinition;
}
}
@@ -0,0 +1,41 @@
package com.sap.sailing.gwt.home.communication.user.profile.sailorprofile;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.StreamSupport;
import com.google.gwt.core.shared.GwtIncompatible;
import com.sap.sailing.gwt.home.communication.SailingAction;
import com.sap.sailing.gwt.home.communication.SailingDispatchContext;
import com.sap.sailing.gwt.home.communication.user.profile.domain.SailorProfileDTO;
import com.sap.sailing.gwt.home.communication.user.profile.domain.SailorProfilesDTO;
import com.sap.sailing.server.impl.preferences.model.SailorProfilePreferences;
import com.sap.sse.gwt.dispatch.shared.exceptions.DispatchException;
/**
* {@link SailingAction} implementation to load sailor profiles for the currently logged in user to bee shown on the
* sailor profiles overview page, preparing the appropriate data structure.
*/
public class GetAllSailorProfilesAction implements SailingAction<SailorProfilesDTO>, SailorProfileConverter {
public GetAllSailorProfilesAction() {
}
@Override
@GwtIncompatible
public SailorProfilesDTO execute(SailingDispatchContext ctx) throws DispatchException {
SailorProfilePreferences preferences = ctx.getPreferenceForCurrentUser(SailorProfilePreferences.PREF_NAME);
SailorProfilesDTO result;
List<SailorProfileDTO> list = new ArrayList<>();
// load sailor profile from preferences if available
if (preferences != null) {
StreamSupport.stream(preferences.getSailorProfiles().spliterator(), false)
.forEach(s -> list.add(convertSailorProfilePreferenceToDto(s,
ctx.getRacingEventService().getCompetitorAndBoatStore(), ctx.getRacingEventService())));
}
result = new SailorProfilesDTO(list);
return result;
}
}
@@ -0,0 +1,141 @@
package com.sap.sailing.gwt.home.communication.user.profile.sailorprofile;
import java.util.ArrayList;
import java.util.Collection;
import java.util.UUID;
import com.google.gwt.core.shared.GwtIncompatible;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.CompetitorAndBoatStore;
import com.sap.sailing.domain.base.Event;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.gwt.home.communication.SailingAction;
import com.sap.sailing.gwt.home.communication.SailingDispatchContext;
import com.sap.sailing.gwt.home.communication.event.SimpleCompetitorWithIdDTO;
import com.sap.sailing.gwt.home.communication.user.profile.domain.ParticipatedEventDTO;
import com.sap.sailing.gwt.home.communication.user.profile.domain.ParticipatedRegattaDTO;
import com.sap.sailing.gwt.home.communication.user.profile.domain.SailorProfileEventsDTO;
import com.sap.sailing.gwt.server.HomeServiceUtil;
import com.sap.sailing.server.impl.preferences.model.SailorProfilePreference;
import com.sap.sailing.server.impl.preferences.model.SailorProfilePreferences;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.gwt.dispatch.shared.exceptions.DispatchException;
/**
* {@link SailingAction} implementation to load all events, the competitors in a sailor profile with a specific uuid for
* the currently logged in user have participated in to be shown on the sailor profile details page in the events
* container.
*/
public class GetEventsForSailorProfileAction implements SailingAction<SailorProfileEventsDTO>, SailorProfileConverter {
private UUID uuid;
public GetEventsForSailorProfileAction(UUID uuid) {
this.uuid = uuid;
}
public GetEventsForSailorProfileAction() {
}
@Override
@GwtIncompatible
public SailorProfileEventsDTO execute(SailingDispatchContext ctx) throws DispatchException {
CompetitorAndBoatStore store = ctx.getRacingEventService().getCompetitorAndBoatStore();
SailorProfilePreferences prefs = ctx.getPreferenceForCurrentUser(SailorProfilePreferences.PREF_NAME);
SailorProfilePreference pref = findSailorProfile(store, prefs);
Collection<ParticipatedEventDTO> participatedEvents = new ArrayList<>();
// iterate over all existing events
for (Event event : ctx.getRacingEventService().getAllEvents()) {
Collection<ParticipatedRegattaDTO> participatedRegattas = new ArrayList<>();
// iterate over the leaderboard groups
for (LeaderboardGroup leaderboardGroup : event.getLeaderboardGroups()) {
if (leaderboardGroup.hasOverallLeaderboard()) {
leaderboardGroup.getOverallLeaderboard();
}
// iterate over the leaderboards in each leaderboard group
for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) {
// check if this leaderboard contains at least one of the selected competitors
Collection<Competitor> containedCompetitors = new ArrayList<>();
for (Competitor competitor : pref.getCompetitors()) {
if (leaderboard.getCompetitors() != null
&& Util.contains(leaderboard.getCompetitors(), competitor)) {
containedCompetitors.add(competitor);
}
}
// skip if none of the selected competitors is in this leaderboard
if (containedCompetitors.size() == 0) {
continue;
}
// create and add ParticipatedRegattaDTO for each of the selected competitors who was in this
// leaderboard
for (Competitor competitor : containedCompetitors) {
int rank = 0;
try {
rank = leaderboard.getTotalRankOfCompetitor(competitor, MillisecondsTimePoint.now());
} catch (NoWindException e1) {
// ignore
}
// final String leaderboardName = leaderboard.getDisplayName();
final double points = leaderboard.getNetPoints(competitor, MillisecondsTimePoint.now());
// regatta name is equal to the leaderboard name
String regattaName = leaderboard.getName();
Regatta regatta = ctx.getRacingEventService().getRegattaByName(regattaName);
if (regatta == null) {
continue;
}
// skip, if the regatta is not part of this event (e.g. shared leaderboard group)
if (!HomeServiceUtil.isPartOfEvent(event, leaderboard)) {
// skip, if overall leaderboard is not part of this event, don't skip if this is a regatta
// during an event which is not part of an overall leaderboard
if (leaderboardGroup.hasOverallLeaderboard()
&& !HomeServiceUtil.isPartOfEvent(event, leaderboardGroup.getOverallLeaderboard()))
continue;
}
participatedRegattas.add(
new ParticipatedRegattaDTO(regattaName, rank, new SimpleCompetitorWithIdDTO(competitor),
"" + regatta.getId(), "" + event.getId(), points));
}
}
}
if (participatedRegattas.size() > 0) {
participatedEvents
.add(new ParticipatedEventDTO(event.getName(), event.getId().toString(), participatedRegattas));
}
}
return new SailorProfileEventsDTO(participatedEvents);
}
/** @return find SailorProfilePreference in SailorProfilePreferences by using {@link #uuid} */
@GwtIncompatible
private SailorProfilePreference findSailorProfile(CompetitorAndBoatStore store, SailorProfilePreferences prefs) {
if (prefs == null) {
throw new NullPointerException("no sailor profile present");
} else {
for (SailorProfilePreference p : prefs.getSailorProfiles()) {
if (p.getUuid().equals(uuid)) {
return p;
}
}
return null;
}
}
}
@@ -0,0 +1,348 @@
package com.sap.sailing.gwt.home.communication.user.profile.sailorprofile;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NavigableSet;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.CompetitorAndBoatStore;
import com.sap.sailing.domain.base.Event;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.gwt.home.communication.SailingAction;
import com.sap.sailing.gwt.home.communication.SailingDispatchContext;
import com.sap.sailing.gwt.home.communication.event.SimpleCompetitorWithIdDTO;
import com.sap.sailing.gwt.home.communication.user.profile.domain.SailorProfileNumericStatisticType;
import com.sap.sailing.gwt.home.communication.user.profile.domain.SailorProfileNumericStatisticType.StatisticType;
import com.sap.sailing.gwt.home.communication.user.profile.domain.SailorProfileStatisticDTO;
import com.sap.sailing.gwt.home.communication.user.profile.domain.SailorProfileStatisticDTO.SingleEntry;
import com.sap.sailing.gwt.server.HomeServiceUtil;
import com.sap.sailing.server.impl.preferences.model.SailorProfilePreference;
import com.sap.sailing.server.impl.preferences.model.SailorProfilePreferences;
import com.sap.sse.common.Distance;
import com.sap.sse.common.Speed;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.common.settings.GwtIncompatible;
import com.sap.sse.gwt.dispatch.shared.exceptions.DispatchException;
/**
* {@link SailingAction} implementation to load all events, the competitors in a sailor profile with a specific uuid for
* the currently logged in user have participated in to be shown on the sailor profile details page in the events
* container.
*/
public class GetNumericStatisticForSailorProfileAction
implements SailingAction<SailorProfileStatisticDTO>, SailorProfileConverter {
private SailorProfileNumericStatisticType type;
private UUID uuid;
public GetNumericStatisticForSailorProfileAction(UUID uuid, SailorProfileNumericStatisticType type) {
this.uuid = uuid;
this.type = type;
}
/**
* GWT serialization only
*/
@SuppressWarnings("unused")
private GetNumericStatisticForSailorProfileAction() {
}
@Override
@GwtIncompatible
public SailorProfileStatisticDTO execute(SailingDispatchContext ctx) throws DispatchException {
final Map<SimpleCompetitorWithIdDTO, ArrayList<SingleEntry>> result = new HashMap<>();
CompetitorAndBoatStore store = ctx.getRacingEventService().getCompetitorAndBoatStore();
SailorProfilePreferences prefs = ctx.getPreferenceForCurrentUser(SailorProfilePreferences.PREF_NAME);
SailorProfilePreference pref = findSailorProfile(store, prefs);
for (Competitor competitor : pref.getCompetitors()) {
final Aggregator aggregator = determineAggregator();
if (aggregator == null) {
continue;
}
for (Event event : ctx.getRacingEventService().getAllEvents()) {
// determine end of event, or now in live case
TimePoint end = event.getEndDate();
if (end == null) {
end = MillisecondsTimePoint.now();
}
for (LeaderboardGroup leaderboardGroup : event.getLeaderboardGroups()) {
for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) {
// check if this leaderboard contains at least one of the selected competitors
if (!Util.contains(leaderboard.getCompetitors(), competitor)) {
continue;
}
String regattaName = leaderboard.getName();
Regatta regatta = ctx.getRacingEventService().getRegattaByName(regattaName);
if (regatta == null) {
continue;
}
// skip, if the leaderboard is not part of this event (e.g. shared leaderboard group)
if (!HomeServiceUtil.isPartOfEvent(event, leaderboard)) {
continue;
}
for (TrackedRace tr : leaderboard.getTrackedRaces()) {
if (Util.contains(tr.getRace().getCompetitors(), competitor)) {
extractValue(competitor, aggregator, end, leaderboard, tr, leaderboardGroup, event);
}
}
}
}
}
result.put(new SimpleCompetitorWithIdDTO(competitor), aggregator.getResult());
}
List<String> competitorNames = StreamSupport.stream(pref.getCompetitors().spliterator(), false)
.map(Competitor::getName).collect(Collectors.toList());
String serializedQuery = DataMiningQueryCreatorForSailorProfiles.getSerializedDataMiningQuery(type,
competitorNames);
keepOnlyBestIfNecessary(result, type.getAggregationType());
return new SailorProfileStatisticDTO(result, serializedQuery);
}
/**
* This method is responsible for getting a Value and adding it to the aggregator based on the
* SailorProfileNumericStatisticType
*/
@GwtIncompatible
private void extractValue(Competitor competitor, final Aggregator aggregator, TimePoint end,
Leaderboard leaderboard, TrackedRace tr, LeaderboardGroup leaderboardGroup, Event event) {
switch (type) {
case MAX_SPEED:
getMaxSpeedInRaces(leaderboard, competitor, aggregator, tr, end, leaderboardGroup.getName(), event.getId());
break;
case BEST_DISTANCE_TO_START:
aggregator.add(tr.getDistanceToStartLine(competitor, 0), tr.getStartOfRace(), tr.getStartOfRace(),
tr.getRaceIdentifier(), leaderboard.getName(), leaderboardGroup.getName(), event.getId(),
tr.getRace().getName());
break;
case BEST_STARTLINE_SPEED:
Speed speed = tr.getSpeedWhenCrossingStartLine(competitor);
aggregator.add(speed, tr.getStartOfRace(), tr.getStartOfRace(), tr.getRaceIdentifier(),
leaderboard.getName(), leaderboardGroup.getName(), event.getId(), tr.getRace().getName());
break;
case AVERAGE_STARTLINE_DISTANCE:
Distance distance = tr.getDistanceToStartLine(competitor, 0);
aggregator.add(distance, null, null, null, null, null, null, null);
default:
break;
}
}
/** reduces result map to only the best competitor if statistic is not average */
@GwtIncompatible
private void keepOnlyBestIfNecessary(Map<SimpleCompetitorWithIdDTO, ArrayList<SingleEntry>> results,
StatisticType type) {
final Set<SimpleCompetitorWithIdDTO> competitorsToRemove = new HashSet<>();
if (type == StatisticType.HIGHEST_IS_BEST || type == StatisticType.LOWEST_IS_BEST) {
// set best value to min/max depending whether highest/lowest is best
double best = (type == StatisticType.HIGHEST_IS_BEST) ? Double.MIN_VALUE : Double.MAX_VALUE;
SimpleCompetitorWithIdDTO bestCompetitor = null;
for (Entry<SimpleCompetitorWithIdDTO, ArrayList<SingleEntry>> mapEntry : results.entrySet()) {
for (SingleEntry statisticEntry : mapEntry.getValue()) {
// lower is better -> best must be bigger then the value of this entry to update the best
// higher is better -> best must be smaller then the value of this entry to update the best
if ((type == StatisticType.LOWEST_IS_BEST && best > statisticEntry.getValue())
|| type == StatisticType.HIGHEST_IS_BEST && best < statisticEntry.getValue()) {
best = statisticEntry.getValue();
competitorsToRemove.add(bestCompetitor);
bestCompetitor = mapEntry.getKey();
} else {
competitorsToRemove.add(mapEntry.getKey());
continue;
}
}
}
}
competitorsToRemove.forEach(competitor -> results.remove(competitor));
}
/**
* Determines the required aggregator based on SailorProfileNumericStatisticType
*/
@GwtIncompatible
private Aggregator determineAggregator() {
final Aggregator aggregator;
switch (type.getAggregationType()) {
case AVERAGE:
aggregator = new AverageAggregator();
break;
case HIGHEST_IS_BEST:
aggregator = new MinMaxAggregator(true);
break;
case LOWEST_IS_BEST:
aggregator = new MinMaxAggregator(false);
break;
default:
aggregator = null;
break;
}
return aggregator;
}
@GwtIncompatible
private SingleEntry getMaxSpeedInRaces(Leaderboard leaderboard, Competitor competitor, Aggregator aggregator,
TrackedRace tr, TimePoint endOfEvent, String bestLeaderboardGroupName, UUID eventId) {
Pair<GPSFixMoving, Speed> bestFix = leaderboard.getMaximumSpeedOverGround(competitor, endOfEvent);
SingleEntry newBetterResult = null;
if (bestFix != null) {
NavigableSet<MarkPassing> markPassings = tr.getMarkPassings(competitor);
if (!markPassings.isEmpty()) {
TimePoint from = markPassings.first().getTimePoint();
// only count to last known markpassing (and finish as race end this way)
TimePoint to = markPassings.last().getTimePoint();
com.sap.sse.common.Util.Pair<GPSFixMoving, Speed> maxSpeed = tr.getTrack(competitor)
.getMaximumSpeedOverGround(from, to);
if (maxSpeed != null && maxSpeed.getA() != null && maxSpeed.getB() != null) {
aggregator.add(maxSpeed.getB(), bestFix.getA().getTimePoint(), tr.getStartOfRace(),
tr.getRaceIdentifier(), leaderboard.getName(), bestLeaderboardGroupName, eventId,
tr.getRace().getName());
}
}
}
return newBetterResult;
}
@GwtIncompatible
private SailorProfilePreference findSailorProfile(CompetitorAndBoatStore store, SailorProfilePreferences prefs) {
if (prefs == null) {
throw new NullPointerException("no sailor profile present");
} else {
for (SailorProfilePreference p : prefs.getSailorProfiles()) {
if (p.getUuid().equals(uuid)) {
return p;
}
}
return null;
}
}
/**
* A helper to reduce redundant code, that offers a simple interface to add new values. This class should be null
* safe so that the caller does not have to ensure this
*/
@GwtIncompatible
interface Aggregator {
default void add(Distance distance, TimePoint bestTimePointOrNull, TimePoint startTimePointOrNull,
RegattaAndRaceIdentifier regattaAndRaceIdentifierOrNull, String bestLeaderboardName,
String bestLeaderboardGroupName, UUID eventId, String bestRaceName) {
if (distance != null) {
add(distance.getMeters(), bestTimePointOrNull, startTimePointOrNull, regattaAndRaceIdentifierOrNull,
bestLeaderboardName, bestLeaderboardGroupName, eventId, bestRaceName);
}
}
void add(Double value, TimePoint bestTime, TimePoint startTime, RegattaAndRaceIdentifier race,
String bestLeaderboardName, String bestLeaderboardGroupName, UUID bestEventId, String bestRaceName);
default void add(Speed speed, TimePoint bestTimePointOrNull, TimePoint startTimePointOrNull,
RegattaAndRaceIdentifier regattaAndRaceIdentifierOrNull, String bestLeaderboardName,
String bestLeaderboardGroupName, UUID eventId, String bestRaceName) {
if (speed != null) {
add(speed.getKnots(), bestTimePointOrNull, startTimePointOrNull, regattaAndRaceIdentifierOrNull,
bestLeaderboardName, bestLeaderboardGroupName, eventId, bestRaceName);
}
}
ArrayList<SingleEntry> getResult();
}
@GwtIncompatible
private static class MinMaxAggregator implements Aggregator {
private boolean max;
private Double bestValue;
private String bestLeaderboardName;
private String bestLeaderboardGroupName;
private String bestRaceName;
private UUID bestEventId;
private TimePoint bestTimePoint;
private TimePoint startTimePoint;
private RegattaAndRaceIdentifier bestRace;
public MinMaxAggregator(boolean max) {
this.max = max;
}
@Override
public void add(Double value, TimePoint bestTime, TimePoint startTime, RegattaAndRaceIdentifier race,
String bestLeaderboardName, String bestLeaderboardGroupName, UUID bestEventId, String bestRaceName) {
if (value != null) {
if (this.bestValue == null || ((max && value > bestValue) || (!max && value < bestValue))) {
this.bestValue = value;
this.bestTimePoint = bestTime;
this.startTimePoint = startTime;
this.bestRace = race;
this.bestLeaderboardName = bestLeaderboardName;
this.bestLeaderboardGroupName = bestLeaderboardGroupName;
this.bestEventId = bestEventId;
this.bestRaceName = bestRaceName;
}
}
}
@Override
public ArrayList<SingleEntry> getResult() {
ArrayList<SingleEntry> result = new ArrayList<>();
if (bestValue != null) {
// not all timepoints are serializable, ensure we use a compatible one
result.add(new SingleEntry(bestValue, bestRace, new MillisecondsTimePoint(bestTimePoint.asMillis()),
new MillisecondsTimePoint(startTimePoint.asMillis()), bestLeaderboardName,
bestLeaderboardGroupName, bestEventId, bestRaceName));
}
return result;
}
}
@GwtIncompatible
private static class AverageAggregator implements Aggregator {
double averageCount = 0;
Double average = null;
@Override
public void add(Double value, TimePoint bestTime, TimePoint startTime, RegattaAndRaceIdentifier race,
String bestLeaderboardName, String bestLeaderboardGroupName, UUID eventId, String bestRaceName) {
if (value != null) {
averageCount++;
if (average == null) {
average = value;
} else {
average = average * ((averageCount - 1) / averageCount) + value * (1 / averageCount);
}
}
}
@Override
public ArrayList<SingleEntry> getResult() {
ArrayList<SingleEntry> result = new ArrayList<>();
if (averageCount > 0) {
result.add(new SingleEntry(average, null, null, null, null, null, null, null));
}
return result;
}
}
}
@@ -0,0 +1,56 @@
package com.sap.sailing.gwt.home.communication.user.profile.sailorprofile;
import java.util.UUID;
import com.google.gwt.core.shared.GwtIncompatible;
import com.sap.sailing.domain.base.CompetitorAndBoatStore;
import com.sap.sailing.gwt.home.communication.SailingAction;
import com.sap.sailing.gwt.home.communication.SailingDispatchContext;
import com.sap.sailing.gwt.home.communication.user.profile.domain.SailorProfileDTO;
import com.sap.sailing.server.impl.preferences.model.SailorProfilePreference;
import com.sap.sailing.server.impl.preferences.model.SailorProfilePreferences;
import com.sap.sse.gwt.dispatch.shared.exceptions.DispatchException;
/**
* {@link SailingAction} implementation to load a sailor profile with a specific uuid for the currently logged in user
* to bee shown on the sailor profile details page, preparing the appropriate data structure.
*/
public class GetSailorProfileAction implements SailingAction<SailorProfileDTO>, SailorProfileConverter {
private UUID uuid;
public GetSailorProfileAction(UUID uuid) {
this.uuid = uuid;
}
protected GetSailorProfileAction() {
}
@Override
@GwtIncompatible
public SailorProfileDTO execute(SailingDispatchContext ctx) throws DispatchException {
CompetitorAndBoatStore store = ctx.getRacingEventService().getCompetitorAndBoatStore();
SailorProfilePreferences prefs = ctx.getPreferenceForCurrentUser(SailorProfilePreferences.PREF_NAME);
SailorProfilePreference pref = findSailorProfile(store, prefs);
return convertSailorProfilePreferenceToDto(pref, store, ctx.getRacingEventService());
}
/** find the SailorProfilePreference in SailorProfilePreferences from the {@link #uuid} */
@GwtIncompatible
private SailorProfilePreference findSailorProfile(CompetitorAndBoatStore store, SailorProfilePreferences prefs) {
SailorProfilePreference result = null;
if (prefs != null) {
for (SailorProfilePreference p : prefs.getSailorProfiles()) {
if (p.getUuid().equals(uuid)) {
result = p;
break;
}
}
}
return result;
}
}
@@ -0,0 +1,17 @@
package com.sap.sailing.gwt.home.communication.user.profile.sailorprofile;
import java.util.UUID;
/**
* {@link UpdateSailorProfileAction} implementation to remove a sailor profile identified by the UUID for the currently
* logged in user.
*/
public class RemoveSailorProfileAction extends UpdateSailorProfileAction {
public RemoveSailorProfileAction(UUID uuid) {
super(uuid);
}
protected RemoveSailorProfileAction() {
}
}
@@ -0,0 +1,107 @@
package com.sap.sailing.gwt.home.communication.user.profile.sailorprofile;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import com.google.gwt.core.shared.GwtIncompatible;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.CompetitorAndBoatStore;
import com.sap.sailing.domain.base.CompetitorWithBoat;
import com.sap.sailing.domain.base.Event;
import com.sap.sailing.domain.common.dto.BoatClassDTO;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.gwt.home.communication.event.SimpleCompetitorWithIdDTO;
import com.sap.sailing.gwt.home.communication.user.profile.domain.BadgeDTO;
import com.sap.sailing.gwt.home.communication.user.profile.domain.SailorProfileDTO;
import com.sap.sailing.server.RacingEventService;
import com.sap.sailing.server.impl.preferences.model.SailorProfilePreference;
public interface SailorProfileConverter {
/**
* converts the {@link SailorProfilePreference} to a {@link SailorProfileDTO} by converting the competitors and
* retrieve the corresponding boatclasses.
*
* @param notFoundOnServer
* @return converted {@link SailorProfileDTO} from {@link SailorProfilePreference}
*/
@GwtIncompatible
default SailorProfileDTO convertSailorProfilePreferenceToDto(final SailorProfilePreference pref,
final CompetitorAndBoatStore store, RacingEventService racingEventService) {
SailorProfileDTO result;
if (pref == null) {
result = new SailorProfileDTO(true);
} else {
result = new SailorProfileDTO(pref.getUuid(), pref.getName(),
convertCompetitorsToDTOs(pref.getCompetitors()), new ArrayList<BadgeDTO>(),
getCorrespondingBoatClassesDTOs(pref.getCompetitors(), store, racingEventService));
}
return result;
}
/** @return converted list of SimpleCompetitorWithIdDTOs from Competitors without null values */
@GwtIncompatible
default List<SimpleCompetitorWithIdDTO> convertCompetitorsToDTOs(final Iterable<Competitor> comps) {
return StreamSupport.stream(comps.spliterator(), false).filter(c -> c != null)
.map(c -> new SimpleCompetitorWithIdDTO(c)).collect(Collectors.toList());
}
/**
* get the corresponding boat class for each competitor in the list of competitors from the
* {@link CompetitorAndBoatStore} and convert it to {@link BoatClassDTO}
*/
@GwtIncompatible
default Set<BoatClassDTO> getCorrespondingBoatClassesDTOs(final Iterable<Competitor> comps,
final CompetitorAndBoatStore store, RacingEventService racingEventService) {
final Set<BoatClassDTO> boatclasses = new HashSet<>();
for (Competitor c : comps) {
if (c != null) {
CompetitorWithBoat cwd = store.getExistingCompetitorWithBoatById(c.getId());
if (cwd != null && cwd.hasBoat() && cwd.getBoat().getBoatClass() != null) {
BoatClassDTO dto = convertBoatClassToDTO(cwd.getBoat().getBoatClass());
boatclasses.add(dto);
} else {
BoatClass boatclass = getBoatClassForCompetitorWithoutBoatClass(racingEventService, c);
if (boatclass != null) {
boatclasses.add(convertBoatClassToDTO(boatclass));
}
}
}
}
return boatclasses;
}
/**
* get the corresponding boat class for each competitor in the list of competitors from the leaderboard via the
* {@link RacingEventService}
*/
@GwtIncompatible
default BoatClass getBoatClassForCompetitorWithoutBoatClass(RacingEventService racingEventService, Competitor c) {
for (Event event : racingEventService.getAllEvents()) {
for (LeaderboardGroup leaderboardGroup : event.getLeaderboardGroups()) {
for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) {
for (Competitor competitor : leaderboard.getCompetitors()) {
if (competitor.getId().equals(c.getId())) {
if (leaderboard.getBoatClass() != null) {
return leaderboard.getBoatClass();
}
}
}
}
}
}
return null;
}
/** @return converted BoatClassDTO from BoatClass */
@GwtIncompatible
default BoatClassDTO convertBoatClassToDTO(BoatClass bc) {
return new BoatClassDTO(bc.getName(), bc.getDisplayName(), bc.getHullLength(), bc.getHullBeam());
}
}
@@ -0,0 +1,96 @@
package com.sap.sailing.gwt.home.communication.user.profile.sailorprofile;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import com.google.gwt.core.shared.GwtIncompatible;
import com.sap.sailing.domain.base.CompetitorAndBoatStore;
import com.sap.sailing.gwt.home.communication.SailingAction;
import com.sap.sailing.gwt.home.communication.SailingDispatchContext;
import com.sap.sailing.gwt.home.communication.user.profile.domain.SailorProfileDTO;
import com.sap.sailing.server.impl.preferences.model.SailorProfilePreference;
import com.sap.sailing.server.impl.preferences.model.SailorProfilePreferences;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.gwt.dispatch.shared.exceptions.DispatchException;
/**
* {@link SailingAction} implementation to update attributes of a sailor profile identified by its UUID for the
* currently logged in user. Returns an updated SailorProfileDTO to bee shown on the sailor profile details page,
* preparing the appropriate data structure.
*/
public abstract class UpdateSailorProfileAction implements SailingAction<SailorProfileDTO>, SailorProfileConverter {
protected UUID uuid;
public UpdateSailorProfileAction(UUID uuid) {
this.uuid = uuid;
}
protected UpdateSailorProfileAction() {
}
@Override
@GwtIncompatible
public SailorProfileDTO execute(SailingDispatchContext ctx) throws DispatchException {
CompetitorAndBoatStore store = ctx.getRacingEventService().getCompetitorAndBoatStore();
SailorProfilePreferences prefs = ctx.getPreferenceForCurrentUser(SailorProfilePreferences.PREF_NAME);
if (prefs == null) {
prefs = new SailorProfilePreferences(store);
}
Pair<SailorProfilePreferences, SailorProfilePreference> pair = findAndUpdateCorrectPreference(store, prefs);
prefs = pair.getA();
ctx.setPreferenceForCurrentUser(SailorProfilePreferences.PREF_NAME, prefs);
return pair.getB() != null
? convertSailorProfilePreferenceToDto(pair.getB(), store, ctx.getRacingEventService())
: null;
}
@GwtIncompatible
private Pair<SailorProfilePreferences, SailorProfilePreference> findAndUpdateCorrectPreference(
CompetitorAndBoatStore store, SailorProfilePreferences prefs) {
List<SailorProfilePreference> sailorProfilePreferences = new ArrayList<>();
SailorProfilePreference sp = null;
if (prefs == null) {
throw new NullPointerException("no sailor profile present");
} else {
// 1) copy existing SailorProfilePreference objects from SailorProfilePreferences into list except the
// changed
// SailorProfilePreference
// 2) add the new version of the SailorProfilePreference to the list
// 3) add the list to a new SailorProfilePreferences
for (SailorProfilePreference p : prefs.getSailorProfiles()) {
if (!p.getUuid().equals(uuid)) {
sailorProfilePreferences.add(p);
} else {
sp = updatePreference(store, p);
if (sp != null) {
sailorProfilePreferences.add(sp);
}
}
}
// if might be a new SailorProfile (unknown UUID), so create a new preference for it
if (sp == null) {
sp = updatePreference(store, null);
if (sp != null) {
sailorProfilePreferences.add(sp);
}
}
prefs = new SailorProfilePreferences(store);
prefs.setSailorProfiles(sailorProfilePreferences);
}
return new Pair<SailorProfilePreferences, SailorProfilePreference>(prefs, sp);
}
/**
* @return the updated SailorProfilePreference from the original {@link p}. If this returns null, p will be removed
*/
@GwtIncompatible
protected SailorProfilePreference updatePreference(CompetitorAndBoatStore store, SailorProfilePreference p) {
return null;
}
}
@@ -0,0 +1,44 @@
package com.sap.sailing.gwt.home.communication.user.profile.sailorprofile;
import java.util.ArrayList;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import com.google.gwt.core.shared.GwtIncompatible;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.CompetitorAndBoatStore;
import com.sap.sailing.gwt.home.communication.event.SimpleCompetitorWithIdDTO;
import com.sap.sailing.server.impl.preferences.model.SailorProfilePreference;
import com.sap.sse.common.Util;
/**
* {@link UpdateSailorProfileAction} implementation to update the competitors of a sailor profile identified by the UUID
* for the currently logged in user.
*/
public class UpdateSailorProfileCompetitorsAction extends UpdateSailorProfileAction {
private ArrayList<SimpleCompetitorWithIdDTO> competitors = new ArrayList<>();
public UpdateSailorProfileCompetitorsAction(UUID uuid, Iterable<SimpleCompetitorWithIdDTO> competitors) {
super(uuid);
Util.addAll(competitors, this.competitors);
}
protected UpdateSailorProfileCompetitorsAction() {
}
/** convert SimpleCompetitorIdWithDTOs to Competitors */
@GwtIncompatible
private Iterable<Competitor> convertCompetitorDTOs(final Iterable<SimpleCompetitorWithIdDTO> comps,
CompetitorAndBoatStore store) {
return StreamSupport.stream(comps.spliterator(), false)
.map(c -> store.getExistingCompetitorByIdAsString(c.getIdAsString())).collect(Collectors.toList());
}
@GwtIncompatible
@Override
protected SailorProfilePreference updatePreference(CompetitorAndBoatStore store, SailorProfilePreference p) {
return new SailorProfilePreference(store, p, convertCompetitorDTOs(this.competitors, store));
}
}
@@ -0,0 +1,31 @@
package com.sap.sailing.gwt.home.communication.user.profile.sailorprofile;
import java.util.UUID;
import com.google.gwt.core.shared.GwtIncompatible;
import com.sap.sailing.domain.base.CompetitorAndBoatStore;
import com.sap.sailing.server.impl.preferences.model.SailorProfilePreference;
/**
* {@link UpdateSailorProfileAction} implementation to load update the title of a sailor profile with the UUID for the
* currently logged in user.
*/
public class UpdateSailorProfileTitleAction extends UpdateSailorProfileAction {
private String title;
public UpdateSailorProfileTitleAction(UUID uuid, String title) {
super(uuid);
this.title = title;
}
protected UpdateSailorProfileTitleAction() {
}
@GwtIncompatible
@Override
protected SailorProfilePreference updatePreference(CompetitorAndBoatStore store, SailorProfilePreference p) {
return new SailorProfilePreference(store, p, this.title);
}
}
@@ -0,0 +1,124 @@
package com.sap.sailing.gwt.home.desktop.partials.desktopaccordion;
import java.util.ArrayList;
import java.util.List;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.SpanElement;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.home.mobile.partials.section.MobileSection;
import com.sap.sailing.gwt.home.shared.utils.CollapseAnimation;
import com.sap.sailing.gwt.ui.client.StringMessages;
/**
* This object is similar to the accordion ({@link MobileSection})on mobile. It has a header with a title and a
* collapsable content panel.
*/
public class DesktopAccordion extends Composite {
interface DesktopAccordionUiBinder extends UiBinder<Widget, DesktopAccordion> {
}
private static DesktopAccordionUiBinder uiBinder = GWT.create(DesktopAccordionUiBinder.class);
@UiField
SpanElement titleUi;
@UiField
FlowPanel contentPanelUi;
@UiField
HTMLPanel headerDivUi;
@UiField
StringMessages i18n;
private boolean isContentVisible;
private final CollapseAnimation animation;
/** true, as soon as the accordion was expanded once. */
private boolean wasOpenend;
private List<AccordionExpansionListener> accordionListeners;
private boolean expanded;
public interface AccordionExpansionListener {
void onExpansion(boolean expanded);
}
public DesktopAccordion() {
this(false);
}
public DesktopAccordion(boolean showInitial) {
accordionListeners = new ArrayList<>();
DesktopAccordionResources.INSTANCE.css().ensureInjected();
initWidget(uiBinder.createAndBindUi(this));
this.titleUi.setInnerText("Title");
headerDivUi.addDomHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
onHeaderCicked();
}
}, ClickEvent.getType());
isContentVisible = showInitial;
animation = new CollapseAnimation(contentPanelUi.getElement(), showInitial);
updateAccordionState();
}
public void setTitle(String title) {
titleUi.setInnerText(title);
}
private void onHeaderCicked() {
isContentVisible = !isContentVisible;
updateContentVisibility();
}
private void updateContentVisibility() {
animation.animate(isContentVisible);
updateAccordionState();
}
public void addWidget(Widget w) {
contentPanelUi.add(w);
}
private void updateAccordionState() {
if (isContentVisible) {
getElement().removeClassName(DesktopAccordionResources.INSTANCE.css().accordionCollapsed());
if (!wasOpenend) {
wasOpenend = true;
expanded = true;
for (AccordionExpansionListener accordionListener : accordionListeners) {
accordionListener.onExpansion(expanded);
}
accordionListeners.clear();
}
} else {
expanded = false;
getElement().addClassName(DesktopAccordionResources.INSTANCE.css().accordionCollapsed());
}
}
public void clear() {
contentPanelUi.clear();
accordionListeners.clear();
}
public void addAccordionListener(AccordionExpansionListener listener) {
accordionListeners.add(listener);
}
public boolean isExpanded() {
return expanded;
}
}
@@ -0,0 +1,28 @@
<!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="shr" type="com.sap.sailing.gwt.home.shared.resources.SharedHomeResources" />
<ui:with field="ares"
type="com.sap.sailing.gwt.home.desktop.partials.desktopaccordion.DesktopAccordionResources" />
<g:HTMLPanel>
<div class="{ares.css.accordion}">
<g:HTMLPanel ui:field="headerDivUi" addStyleNames="{ares.css.accordionHeader}">
<div class="{res.mediaCss.grid}">
<div class="{res.mediaCss.small2} {res.mediaCss.columns}">
<div class="{ares.css.accordionTitle}">
<span ui:field="titleUi" />
</div>
</div>
<div class="{res.mediaCss.small10} {res.mediaCss.columns} {ares.css.accordionHeaderRight}">
<div class="{ares.css.accordionHeaderRightArrow}">
<img src="{shr.arrowDownGrey.getSafeUri}" class="{ares.css.accordionArrowImage}"></img>
</div>
</div>
</div>
</g:HTMLPanel>
<g:FlowPanel addStyleNames="{ares.css.accordionContentPanel}" ui:field="contentPanelUi" />
</div>
</g:HTMLPanel>
</ui:UiBinder>
@@ -0,0 +1,66 @@
.accordionTitle {
padding: 1.11111111111111em;
font-size: 1.2em;
font-weight: 700;
line-height: 1.333333333em;
}
.accordionHeader {
background-color: #f2f2f2;
cursor: pointer;
border-bottom: 1px solid #dedede;
}
.accordionHeaderRight {
top: 0;
bottom: 0;
}
.accordionHeaderRightArrow {
padding: 1.11111111em;
font-size: 1.2em;
font-weight: 700;
line-height: 1.333333333em;
right: 1.111111111111em;
position: absolute;
}
.accordionCollapsed .accordionArrowImage {
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-ms-transform: rotate(0deg);
transform: rotate(0deg);
}
.accordionArrowImage {
width: 1.3333333333em;
vertical-align: middle;
-webkit-transform: rotate(180deg);
-moz-transform: rotate(180deg);
-ms-transform: rotate(180deg);
transform: rotate(180deg);
-webkit-transition: -webkit-transform 0.4s;
-moz-transition: -moz-transform 0.4s;
transition: transform 0.4s;
}
.accordion {
border: 1px solid #dedede;
border-radius: .333333333333333em;
-webkit-transition: opacity 0.1s;
-moz-transition: opacity 0.1s;
transition: opacity 0.1s;
}
.accordionContentPanel {
float: none;
margin-left: 1em;
margin-right: 1em;
}
.accordionEmptyMessage {
margin: 1.033333333333333em;
font-style: italic;
line-height: 2em;
text-align: center;
}
@@ -0,0 +1,34 @@
package com.sap.sailing.gwt.home.desktop.partials.desktopaccordion;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
public interface DesktopAccordionResources extends ClientBundle {
public static final DesktopAccordionResources INSTANCE = GWT.create(DesktopAccordionResources.class);
@Source("DesktopAccordionResources.gss")
SailorProfilesCss css();
public interface SailorProfilesCss extends CssResource {
String accordionTitle();
String accordionHeader();
String accordionHeaderRight();
String accordionHeaderRightArrow();
String accordionArrowImage();
String accordionCollapsed();
String accordion();
String accordionContentPanel();
String accordionEmptyMessage();
}
}
@@ -16,6 +16,9 @@
.accordion_content {
font-size: inherit;
}
.accordion_panel {
float: none;
}
.eventsoverviewrecent {
font-size: 1rem;
padding-top: 2em;
@@ -16,6 +16,7 @@ public interface EventsOverviewRecentResources extends ClientBundle {
String accordion();
String accordion_trigger();
String accordion_content();
String accordion_panel();
String eventsoverviewrecent();
String eventsoverviewrecent_year();
String accordioncollapsed();
@@ -1,30 +1,73 @@
package com.sap.sailing.gwt.home.desktop.places.event.regatta.leaderboardtab;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.google.gwt.place.shared.Prefix;
import com.sap.sailing.gwt.home.desktop.places.event.regatta.AbstractEventRegattaPlace;
import com.sap.sailing.gwt.home.shared.places.PlaceTokenPrefixes;
import com.sap.sailing.gwt.home.shared.places.event.AbstractEventPlace;
import com.sap.sailing.gwt.home.shared.places.event.EventContext;
import com.sap.sse.common.Util;
public class RegattaLeaderboardPlace extends AbstractEventRegattaPlace {
public RegattaLeaderboardPlace(String id, String regattaId) {
private final Set<String> selectedCompetitors = new HashSet<>();
public RegattaLeaderboardPlace(String id, String regattaId, Set<String> selectedCompetitors) {
super(id, regattaId);
if (selectedCompetitors != null) {
this.selectedCompetitors.addAll(selectedCompetitors);
}
}
public RegattaLeaderboardPlace(EventContext context) {
public RegattaLeaderboardPlace(String id, String regattaId) {
this(id, regattaId, null);
}
public RegattaLeaderboardPlace(EventContext context, Set<String> selectedCompetitors) {
super(context);
if (selectedCompetitors != null) {
this.selectedCompetitors.addAll(selectedCompetitors);
}
}
public RegattaLeaderboardPlace(EventContext context) {
this(context, null);
}
@Override
public AbstractEventRegattaPlace newInstanceWithContext(EventContext ctx) {
return new RegattaLeaderboardPlace(ctx);
return new RegattaLeaderboardPlace(ctx, null);
}
public Set<String> getSelectedCompetitors() {
return selectedCompetitors;
}
@Prefix(PlaceTokenPrefixes.RegattaLeaderboard)
public static class Tokenizer extends AbstractEventPlace.Tokenizer<RegattaLeaderboardPlace> {
/** short parameter name to decrease total URL length */
private static final String PARAM_SELECTED_COMPETITORS = "sc";
@Override
protected Map<String, Set<String>> getParameters(RegattaLeaderboardPlace place) {
Map<String, Set<String>> map = super.getParameters(place);
place.getSelectedCompetitors().forEach(value -> Util.addToValueSet(map, PARAM_SELECTED_COMPETITORS, value));
return map;
}
@Override
protected RegattaLeaderboardPlace getRealPlace(EventContext context, Map<String, Set<String>> parameters) {
return new RegattaLeaderboardPlace(context, parameters.get(PARAM_SELECTED_COMPETITORS));
}
@Override
protected RegattaLeaderboardPlace getRealPlace(EventContext context) {
return new RegattaLeaderboardPlace(context);
// Is not called since the calling method is overridden above
return null;
}
}
}
@@ -1,5 +1,9 @@
package com.sap.sailing.gwt.home.desktop.places.event.regatta.leaderboardtab;
import java.util.ArrayList;
import java.util.Collection;
import java.util.UUID;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
@@ -14,6 +18,7 @@ import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Label;
import com.sap.sailing.domain.common.DetailType;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.gwt.common.client.controls.tabbar.TabView;
@@ -33,15 +38,13 @@ import com.sap.sailing.gwt.home.shared.refresh.RefreshManager;
import com.sap.sailing.gwt.home.shared.refresh.RefreshManagerWithErrorAndBusy;
import com.sap.sailing.gwt.home.shared.refresh.RefreshableWidget;
import com.sap.sailing.gwt.settings.client.leaderboard.MultiRaceLeaderboardSettings;
import com.sap.sailing.gwt.ui.client.LeaderboardUpdateListener;
import com.sap.sailing.gwt.ui.client.LeaderboardUpdateProvider;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.leaderboard.MultiRaceLeaderboardPanel;
import com.sap.sse.gwt.client.shared.settings.DefaultOnSettingsLoadedCallback;
import com.sap.sse.gwt.dispatch.shared.commands.CollectionResult;
/**
* Created by pgtaboada on 25.11.14.
*/
public class RegattaLeaderboardTabView extends SharedLeaderboardRegattaTabView<RegattaLeaderboardPlace> {
interface MyBinder extends UiBinder<FlowPanel, RegattaLeaderboardTabView> {
}
@@ -119,6 +122,43 @@ public class RegattaLeaderboardTabView extends SharedLeaderboardRegattaTabView<R
if (leaderboardPanel.getLeaderboard() != null) {
leaderboard.updatedLeaderboard(leaderboardPanel.getLeaderboard());
}
selectCompetitorsFromURLArgumentsWhenReady(myPlace, leaderboardPanel);
}
private void selectCompetitorsFromURLArgumentsWhenReady(final RegattaLeaderboardPlace myPlace,
MultiRaceLeaderboardPanel leaderboardPanel) {
// select competitors from URL arguments if present
leaderboardPanel.addLeaderboardUpdateListener(new LeaderboardUpdateListener() {
@Override
public void updatedLeaderboard(LeaderboardDTO leaderboard) {
if (!myPlace.getSelectedCompetitors().isEmpty()) {
Collection<CompetitorDTO> newSelection = new ArrayList<>();
for (String selectedCompetitorId : myPlace.getSelectedCompetitors()) {
try {
UUID uuid = UUID.fromString(selectedCompetitorId);
for (CompetitorDTO competitor : leaderboard.competitors) {
if (selectedCompetitorId.equals(competitor.getIdAsString())) {
newSelection.add(competitor);
break;
}
}
//
} catch (IllegalArgumentException e) {
GWT.log(e.getMessage());
}
}
leaderboardPanel.setSelection(newSelection);
if(!newSelection.isEmpty()) {
leaderboardPanel.removeLeaderboardUpdateListener(this);
}
}
}
@Override
public void currentRaceSelected(RaceIdentifier raceIdentifier, RaceColumnDTO raceColumn) {
}
});
}
};
if (leaderboardPanel == null) {
@@ -19,6 +19,7 @@
<g:HTMLPanel addStyleNames="{res.mediaCss.grid}">
<h1 class="{res.mediaCss.small12} {res.mediaCss.columns}"><ui:text from="{i18n.moreLoginInformationHeadline}" /></h1>
<p class="{res.mediaCss.small12} {res.mediaCss.columns}"><ui:text from="{i18n.moreLoginInformationIntroduction}" /></p>
<login:MoreLoginInformationContent addStyleNames="{style.clear}" title="{i18n.moreLoginInformationSectionSailorProfilesHeading}" image="{local_res.sailorprofiles}" imageOnLeft="false" content="{i18n.moreLoginInformationSectionSailorProfilesDescription}" />
<login:MoreLoginInformationContent addStyleNames="{style.clear}" title="{i18n.moreLoginInformationSectionUserSettingsHeading}" image="{local_res.settings}" imageOnLeft="true" content="{i18n.moreLoginInformationSectionUserSettingsDescription}" />
<login:MoreLoginInformationContent title="{i18n.strategySimulator}" image="{local_res.simulator}" imageOnLeft="false" content="{i18n.moreLoginInformationSectionStrategySimulatorDescription}" />
<login:MoreLoginInformationContent title="{i18n.moreLoginInformationSectionUserNotificationsHeading}" image="{local_res.notifications}" imageOnLeft="true" content="{i18n.moreLoginInformationSectionUserNotificationsDescription}" />
@@ -13,6 +13,7 @@ import com.sap.sailing.gwt.common.client.controls.tabbar.TabPanel;
import com.sap.sailing.gwt.common.client.controls.tabbar.TabPanelPlaceSelectionEvent;
import com.sap.sailing.gwt.common.client.controls.tabbar.TabView;
import com.sap.sailing.gwt.home.desktop.places.user.profile.preferencestab.UserProfilePreferencesTabView;
import com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab.SailorProfileTabView;
import com.sap.sailing.gwt.home.shared.app.ApplicationHistoryMapper;
import com.sap.sailing.gwt.home.shared.places.user.profile.AbstractUserProfilePlace;
import com.sap.sailing.gwt.ui.client.FlagImageResolver;
@@ -41,6 +42,9 @@ public class TabletAndDesktopUserProfileView extends Composite implements UserPr
@UiField(provided = true)
UserProfilePreferencesTabView preferencesTabUi;
@UiField(provided = true)
SailorProfileTabView sailorProfileTabUi;
private final FlagImageResolver flagImageResolver;
public TabletAndDesktopUserProfileView(FlagImageResolver flagImageResolver) {
@@ -56,6 +60,8 @@ public class TabletAndDesktopUserProfileView extends Composite implements UserPr
preferencesTabUi = new UserProfilePreferencesTabView(flagImageResolver);
sailorProfileTabUi = new SailorProfileTabView(flagImageResolver);
initWidget(uiBinder.createAndBindUi(this));
}
@@ -15,6 +15,9 @@
<c:tabcontent title="{i18n.favoritesAndNotifications}">
<s:preferencestab.UserProfilePreferencesTabView ui:field="preferencesTabUi" />
</c:tabcontent>
<c:tabcontent title="{i18n.sailorProfiles}">
<s:sailorprofiletab.SailorProfileTabView ui:field="sailorProfileTabUi" />
</c:tabcontent>
<c:tabcontent title="{i18n.settings}">
<s:settingstab.UserProfileSettingsTabView ui:field="settingsTabUi" />
</c:tabcontent>
@@ -1,10 +1,9 @@
package com.sap.sailing.gwt.home.desktop.places.user.profile;
import com.sap.sailing.gwt.home.shared.app.ClientFactoryWithDispatch;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.ClientFactoryWithDispatchAndErrorAndUserService;
import com.sap.sailing.gwt.ui.client.SailingClientFactory;
import com.sap.sailing.gwt.ui.client.refresh.ErrorAndBusyClientFactory;
import com.sap.sse.security.ui.authentication.WithAuthenticationManager;
import com.sap.sse.security.ui.authentication.WithUserService;
public interface UserProfileClientFactory extends ClientFactoryWithDispatch, SailingClientFactory, ErrorAndBusyClientFactory, WithAuthenticationManager, WithUserService {
public interface UserProfileClientFactory
extends SailingClientFactory, WithAuthenticationManager, ClientFactoryWithDispatchAndErrorAndUserService {
}
@@ -0,0 +1,28 @@
package com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab;
import com.sap.sailing.gwt.home.desktop.places.user.profile.UserProfileView;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.ClientFactoryWithDispatchAndErrorAndUserService;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.HasLoginFormAndFactory;
/**
* simple adapter to use the client factory from a {@link UserProfileView.Presenter} as a client factory in
* {@link HasLoginFormAndFactory}
*/
public class ClientFactoryAdapter implements HasLoginFormAndFactory {
private final UserProfileView.Presenter presenter;
public ClientFactoryAdapter(UserProfileView.Presenter presenter) {
this.presenter = presenter;
}
@Override
public void doTriggerLoginForm() {
presenter.doTriggerLoginForm();
}
@Override
public ClientFactoryWithDispatchAndErrorAndUserService getClientFactory() {
return presenter.getClientFactory();
}
}
@@ -0,0 +1,19 @@
package com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab;
import java.util.UUID;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.ClientFactoryWithDispatchAndErrorAndUserService;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.EditSailorProfileDetailsView;
import com.sap.sailing.gwt.ui.client.FlagImageResolver;
import com.sap.sse.security.ui.authentication.app.NeedsAuthenticationContext;
import com.sap.sse.security.ui.authentication.decorator.NotLoggedInPresenter;
public interface SailingProfileOverviewPresenter extends NotLoggedInPresenter, NeedsAuthenticationContext {
EditSailorProfileDetailsView.Presenter getSharedSailorProfilePresenter();
ClientFactoryWithDispatchAndErrorAndUserService getClientFactory();
FlagImageResolver getFlagImageResolver();
void removeSailorProfile(UUID uuid);
}
@@ -0,0 +1,57 @@
package com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.safehtml.client.SafeHtmlTemplates;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeUri;
public interface SailorProfileDesktopResources extends ClientBundle {
public static final SailorProfileDesktopResources INSTANCE = GWT.create(SailorProfileDesktopResources.class);
public static final SailorProfileDesktopTemplates TEMPLATE = GWT.create(SailorProfileDesktopTemplates.class);
@Source("SailorProfiles.gss")
SailorProfilesCss css();
public interface SailorProfileDesktopTemplates extends SafeHtmlTemplates {
@Template("<a href='{0}'><img src='{1}' style='-webkit-transform: rotate(270deg);-moz-transform: rotate(270deg);-ms-transform: rotate(270deg); transform: rotate(270deg);width:1.33333333333em; cursor:pointer; min-width:1.33333333em'/></a>")
SafeHtml navigator(SafeUri url, SafeUri imageUrl);
@Template("<button type='button' tabindex='-1' style='background-color: #e94a1b'>{0}</button>")
SafeHtml removeButtonCell(SafeHtml title);
}
public interface SailorProfilesCss extends CssResource {
String showAndEditHeader();
String eventsTable();
String eventsTableTitle();
String eventsTableEmpty();
String eventsTablePanel();
String overviewTable();
String overviewTableFooter();
String overviewTableEmpty();
String clickableColumn();
String statisticsTableHeaderIcon();
String statisticsTableHeader();
String statisticsTableHeaderRight();
String addButton();
String marginLastTable();
String boatClassColumn();
}
}
@@ -0,0 +1,218 @@
package com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab;
import java.util.Collection;
import java.util.stream.Collectors;
import com.google.gwt.cell.client.AbstractCell;
import com.google.gwt.cell.client.ButtonCell;
import com.google.gwt.cell.client.TextCell;
import com.google.gwt.core.client.GWT;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.dto.BoatClassDTO;
import com.sap.sailing.gwt.common.client.BoatClassImageResolver;
import com.sap.sailing.gwt.common.client.SharedResources;
import com.sap.sailing.gwt.common.theme.component.celltable.DesignedCellTableResources;
import com.sap.sailing.gwt.home.communication.user.profile.domain.BadgeDTO;
import com.sap.sailing.gwt.home.communication.user.profile.domain.SailorProfileDTO;
import com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab.details.events.NavigatorColumn;
import com.sap.sailing.gwt.home.shared.app.ApplicationHistoryMapper;
import com.sap.sailing.gwt.home.shared.partials.dialog.ConfirmDialogFactory;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.SailorProfilePlace;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.SharedSailorProfileResources;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.view.SailorProfileOverview;
import com.sap.sailing.gwt.home.shared.usermanagement.decorator.AuthorizedContentDecoratorDesktop;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.gwt.client.celltable.SortedCellTable;
import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback;
import com.sap.sse.security.ui.authentication.app.NeedsAuthenticationContext;
/** Sailor Profile Overview where all the sailor profiles are listed */
public class SailorProfileOverviewImpl extends Composite implements SailorProfileOverview {
private static ApplicationHistoryMapper historyMapper = GWT.create(ApplicationHistoryMapper.class);
interface MyBinder extends UiBinder<Widget, SailorProfileOverviewImpl> {
}
private static MyBinder uiBinder = GWT.create(MyBinder.class);
private SailingProfileOverviewPresenter presenter;
@UiField
StringMessages i18n;
@UiField(provided = true)
final SortedCellTable<SailorProfileDTO> sailorProfilesTable = new SortedCellTable<>(0,
DesignedCellTableResources.INSTANCE);
@UiField
FlowPanel footerUi;
@UiField(provided = true)
AuthorizedContentDecoratorDesktop decoratorUi;
@Override
public void setPresenter(SailingProfileOverviewPresenter presenter) {
this.presenter = presenter;
SharedSailorProfileResources.INSTANCE.css().ensureInjected();
SailorProfileDesktopResources.INSTANCE.css().ensureInjected();
SharedResources.INSTANCE.mainCss().ensureInjected();
decoratorUi = new AuthorizedContentDecoratorDesktop(presenter);
initWidget(uiBinder.createAndBindUi(this));
setupTable();
createFooter();
}
@Override
public void setProfileList(Collection<SailorProfileDTO> entries) {
sailorProfilesTable.setPageSize(entries.size());
sailorProfilesTable.setList(entries);
}
private void setupTable() {
sailorProfilesTable.addColumn(profileNameColumn, i18n.profileName());
// sailorProfilesTable.addColumn(badgeColumn, i18n.badges());
sailorProfilesTable.addColumn(competitorColumn, i18n.competitors());
sailorProfilesTable.addColumn(boatClassColumn, i18n.boatClasses());
sailorProfilesTable.addColumn(navigatorColumn);
sailorProfilesTable.addColumn(removeColumn);
// sailorProfilesTable.setColumnWidth(boatClassColumn, "6em");
addWordwrapStyle(profileNameColumn);
addWordwrapStyle(badgeColumn);
addWordwrapStyle(competitorColumn);
addBoatclassStyle(boatClassColumn);
addButtonStyle(navigatorColumn);
navigatorColumn.setFieldUpdater((int index, SailorProfileDTO entry, String value) -> presenter
.getClientFactory().getPlaceController().goTo(getTargetPlace(entry)));
removeColumn.setCellStyleNames(DesignedCellTableResources.INSTANCE.cellTableStyle().buttonCell());
removeColumn.setFieldUpdater((int index, SailorProfileDTO dto, String value) -> ConfirmDialogFactory
.showConfirmDialog(StringMessages.INSTANCE.sailorProfileRemoveMessage(), new DialogCallback<Void>() {
@Override
public void ok(Void editedObject) {
presenter.removeSailorProfile(dto.getKey());
}
@Override
public void cancel() {
}
}));
sailorProfilesTable.addCellPreviewHandler(e -> {
/* no navigation for remove column */
if ("click".equals(e.getNativeEvent().getType()) && e.getColumn() != 5) {
presenter.getClientFactory().getPlaceController().goTo(new SailorProfilePlace(e.getValue().getKey()));
}
});
}
private void addWordwrapStyle(Column<SailorProfileDTO, ?> col) {
col.setCellStyleNames(DesignedCellTableResources.INSTANCE.cellTableStyle().textCellWordWrap() + " "
+ SailorProfileDesktopResources.INSTANCE.css().clickableColumn());
}
private void addBoatclassStyle(Column<SailorProfileDTO, ?> col) {
col.setCellStyleNames(DesignedCellTableResources.INSTANCE.cellTableStyle().textCellWordWrap() + " "
+ SailorProfileDesktopResources.INSTANCE.css().clickableColumn() + " "
+ SailorProfileDesktopResources.INSTANCE.css().boatClassColumn());
}
private void addButtonStyle(Column<?, ?> col) {
col.setCellStyleNames(DesignedCellTableResources.INSTANCE.cellTableStyle().buttonCell() + " "
+ SailorProfileDesktopResources.INSTANCE.css().clickableColumn());
}
private void createFooter() {
Button addButton = new Button(i18n.addSailorProfileMessage());
addButton.addStyleName(SharedResources.INSTANCE.mainCss().buttonprimary());
addButton.addStyleName(SharedResources.INSTANCE.mainCss().button());
addButton.addStyleName(SailorProfileDesktopResources.INSTANCE.css().addButton());
footerUi.add(addButton);
addButton.addClickHandler(
event -> presenter.getClientFactory().getPlaceController().goTo(new SailorProfilePlace(true)));
}
private final Column<SailorProfileDTO, String> profileNameColumn = new Column<SailorProfileDTO, String>(
new TextCell()) {
@Override
public String getValue(SailorProfileDTO entry) {
return entry.getName();
}
};
private final Column<SailorProfileDTO, String> badgeColumn = new Column<SailorProfileDTO, String>(new TextCell()) {
@Override
public String getValue(SailorProfileDTO entry) {
if (entry != null && entry.getBadges() != null) {
return entry.getBadges().stream().map(BadgeDTO::getName).collect(Collectors.joining(", "));
}
return "-";
}
};
private final Column<SailorProfileDTO, SailorProfileDTO> boatClassColumn = new Column<SailorProfileDTO, SailorProfileDTO>(
new AbstractCell<SailorProfileDTO>() {
@Override
public void render(Context context, SailorProfileDTO value, SafeHtmlBuilder sb) {
for (BoatClassDTO boatclass : value.getBoatclasses()) {
sb.append(SharedSailorProfileResources.TEMPLATES.buildBoatclassIcon(BoatClassImageResolver
.getBoatClassIconResource(boatclass.getName()).getSafeUri().asString()));
}
}
}) {
@Override
public SailorProfileDTO getValue(SailorProfileDTO object) {
return object;
}
};
private final Column<SailorProfileDTO, String> competitorColumn = new Column<SailorProfileDTO, String>(
new TextCell()) {
@Override
public String getValue(SailorProfileDTO entry) {
if (entry != null && entry.getCompetitors() != null) {
return entry.getCompetitors().stream().map(c -> c.getName()).collect(Collectors.joining(", "));
}
return "-";
}
};
private final Column<SailorProfileDTO, String> navigatorColumn = new NavigatorColumn<SailorProfileDTO>() {
@Override
public String getValue(SailorProfileDTO object) {
return "#" + historyMapper.getToken(getTargetPlace(object));
}
};
private final Column<SailorProfileDTO, String> removeColumn = new Column<SailorProfileDTO, String>(
new RemoveButtonCell()) {
@Override
public String getValue(SailorProfileDTO entry) {
return "X";
}
};
@Override
public NeedsAuthenticationContext authentificationContextConsumer() {
return decoratorUi;
}
private SailorProfilePlace getTargetPlace(SailorProfileDTO entry) {
return new SailorProfilePlace(entry.getKey());
}
private class RemoveButtonCell extends ButtonCell {
@Override
public void render(Context context, SafeHtml data, SafeHtmlBuilder sb) {
sb.append(SailorProfileDesktopResources.TEMPLATE.removeButtonCell(data));
}
}
}
@@ -0,0 +1,22 @@
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder' xmlns:g='urn:import:com.google.gwt.user.client.ui'
xmlns:um="urn:import:com.sap.sailing.gwt.home.shared.usermanagement.decorator"
xmlns:c="urn:import:com.google.gwt.user.cellview.client">
<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.places.user.profile.sailorprofiletab.SailorProfileDesktopResources" />
<um:AuthorizedContentDecoratorDesktop ui:field="decoratorUi">
<um:content>
<g:HTMLPanel addStyleNames="{res.mediaCss.grid} {res.mainCss.spacermarginbottommediumsmall}">
<div class="{res.mediaCss.column} {res.mediaCss.small12} {res.mainCss.spacermargintopmedium}">
<c:CellTable ui:field="sailorProfilesTable" addStyleNames="{local_res.css.overviewTable}">
<c:emptyTableWidget>
<g:Label text="{i18n.noDataFound}" addStyleNames="{local_res.css.overviewTableEmpty}" />
</c:emptyTableWidget>
</c:CellTable>
<g:FlowPanel addStyleNames="{local_res.css.overviewTableFooter}" ui:field="footerUi" />
</div>
</g:HTMLPanel>
</um:content>
</um:AuthorizedContentDecoratorDesktop>
</ui:UiBinder>
@@ -0,0 +1,113 @@
package com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab;
import java.util.UUID;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sailing.gwt.home.communication.user.profile.domain.SailorProfileDTO;
import com.sap.sailing.gwt.home.communication.user.profile.domain.SailorProfilesDTO;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.ClientFactoryWithDispatchAndErrorAndUserService;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.EditSailorProfileDetailsView;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.EditSailorProfileDetailsView.Presenter;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.EditSailorProfilePresenter;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.HasLoginFormAndFactory;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.SailorProfilePlace;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.view.SailorProfileOverview;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.view.SailorProfileView;
import com.sap.sailing.gwt.ui.client.FlagImageResolver;
import com.sap.sse.security.ui.authentication.app.AuthenticationContext;
public class SailorProfileOverviewImplPresenter implements SailingProfileOverviewPresenter {
private final SailorProfileView view;
private final HasLoginFormAndFactory loginAndFactory;
private final EditSailorProfileDetailsView.Presenter sharedSailorProfilePresenter;
private final FlagImageResolver flagImageResolver;
public SailorProfileOverviewImplPresenter(final SailorProfileView view,
final HasLoginFormAndFactory loginAndFactory, final FlagImageResolver flagImageResolver) {
this(view, loginAndFactory, flagImageResolver,
new EditSailorProfilePresenter(loginAndFactory.getClientFactory()));
}
public SailorProfileOverviewImplPresenter(final SailorProfileView view,
final HasLoginFormAndFactory loginAndFactory, final FlagImageResolver flagImageResolver, final EditSailorProfilePresenter presenter) {
this.view = view;
this.loginAndFactory = loginAndFactory;
this.sharedSailorProfilePresenter = presenter;
this.flagImageResolver = flagImageResolver;
view.setPresenter(this);
}
@Override
public ClientFactoryWithDispatchAndErrorAndUserService getClientFactory() {
return loginAndFactory.getClientFactory();
}
@Override
public Presenter getSharedSailorProfilePresenter() {
return sharedSailorProfilePresenter;
}
@Override
public void setAuthenticationContext(AuthenticationContext authenticationContext) {
view.authentificationContextConsumer().setAuthenticationContext(authenticationContext);
if (authenticationContext.isLoggedIn() && view instanceof SailorProfileOverview) {
sharedSailorProfilePresenter.getDataProvider().loadSailorProfiles(new AsyncCallback<SailorProfilesDTO>() {
@Override
public void onSuccess(SailorProfilesDTO result) {
((SailorProfileOverview) view).setProfileList(result.getEntries());
}
@Override
public void onFailure(Throwable caught) {
GWT.log(caught.getMessage(), caught);
}
});
}
}
@Override
public void removeSailorProfile(UUID uuid) {
sharedSailorProfilePresenter.getDataProvider().removeSailorProfile(uuid, new AsyncCallback<SailorProfileDTO>() {
@Override
public void onFailure(Throwable caught) {
GWT.log(caught.getMessage(), caught);
}
@Override
public void onSuccess(SailorProfileDTO result) {
sharedSailorProfilePresenter.getDataProvider()
.loadSailorProfiles(new AsyncCallback<SailorProfilesDTO>() {
@Override
public void onSuccess(SailorProfilesDTO result) {
if (view instanceof SailorProfileOverview) {
((SailorProfileOverview) view).setProfileList(result.getEntries());
} else {
getClientFactory().getPlaceController().goTo(new SailorProfilePlace(null));
}
}
@Override
public void onFailure(Throwable caught) {
GWT.log(caught.getMessage(), caught);
}
});
}
});
}
@Override
public void doTriggerLoginForm() {
loginAndFactory.doTriggerLoginForm();
}
@Override
public FlagImageResolver getFlagImageResolver() {
return this.flagImageResolver;
}
}
@@ -0,0 +1,88 @@
package com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab;
import java.util.UUID;
import com.google.gwt.user.client.ui.AcceptsOneWidget;
import com.google.gwt.user.client.ui.Composite;
import com.sap.sailing.gwt.common.client.controls.tabbar.TabView;
import com.sap.sailing.gwt.home.desktop.places.user.profile.UserProfileTabView;
import com.sap.sailing.gwt.home.desktop.places.user.profile.UserProfileView;
import com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab.details.SailorProfileDetails;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.SailorProfilePlace;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.view.SailorProfileView;
import com.sap.sailing.gwt.ui.client.FlagImageResolver;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.security.ui.authentication.app.AuthenticationContext;
/** main entry point for sailor profiles overview on desktop */
public class SailorProfileTabView extends Composite implements UserProfileTabView<SailorProfilePlace> {
private SailingProfileOverviewPresenter currentPresenter;
private final FlagImageResolver flagImageResolver;
private UserProfileView.Presenter ownPresenter;
private AcceptsOneWidget contentArea;
private SailorProfilePlace place;
public SailorProfileTabView(FlagImageResolver flagImageResolver) {
this.flagImageResolver = flagImageResolver;
}
@Override
public Class<SailorProfilePlace> getPlaceClassForActivation() {
return SailorProfilePlace.class;
}
@Override
public TabView.State getState() {
return TabView.State.VISIBLE;
}
@Override
public void start(SailorProfilePlace myPlace, AcceptsOneWidget contentArea) {
this.place = myPlace;
this.contentArea = contentArea;
}
@Override
public void setAuthenticationContext(AuthenticationContext authenticationContext) {
UUID uuid = place.getSailorProfileUuid();
SailorProfileView view;
if (uuid != null || place.isCreateNew()) {
view = new SailorProfileDetails();
} else {
view = new SailorProfileOverviewImpl();
}
this.currentPresenter = new SailorProfileOverviewImplPresenter(view,
new ClientFactoryAdapter(this.ownPresenter), flagImageResolver);
currentPresenter.setAuthenticationContext(authenticationContext);
contentArea.setWidget(view);
if (authenticationContext.isLoggedIn()) {
if (uuid != null || place.isCreateNew()) {
if (place.isCreateNew()) {
currentPresenter.getSharedSailorProfilePresenter().getDataProvider()
.createNewEntry(UUID.randomUUID(), StringMessages.INSTANCE.newSailorProfileName());
} else {
currentPresenter.getSharedSailorProfilePresenter().getDataProvider()
.loadSailorProfile(place.getSailorProfileUuid());
}
}
}
}
@Override
public void stop() {
}
@Override
public SailorProfilePlace placeToFire() {
return new SailorProfilePlace(null);
}
@Override
public void setPresenter(UserProfileView.Presenter ownPresenter) {
this.ownPresenter = ownPresenter;
}
}
@@ -0,0 +1,102 @@
.showAndEditHeader {
font-size: 1.866666666666667em;
font-weight: bold;
color: #333;
margin-left: 1em;
margin-top: 0.7143em;
}
.eventsTable {
width: 100%;
border-radius: 0px;
border: none;
}
.eventsTable .eventsTableEmpty {
margin: 1.033333333333333em;
font-style: italic;
line-height: 2em;
}
.eventsTable td {
vertical-align: middle;
padding-top: 0.73333335em;
}
.eventsTableTitle {
background-color: #008FCC;
vertical-align: middle;
padding-top: 1.333em;
padding-bottom: 1.3333em;
padding-left: 1.333em;
color: white;
font-weight: bold;
}
.eventsTablePanel {
border: 1px solid #dedede;
border-radius: .333333333333333em;
overflow: hidden;
}
.marginLastTable:last-child {
margin-bottom: 1.333333333333333em;
}
.overviewTable {
width: 100%;
}
.overviewTable .overviewTableEmpty {
margin: 1.033333333333333em;
font-style: italic;
line-height: 2em;
}
.overviewTable td {
vertical-align: middle;
padding-top: 0.73333335em;
}
.overviewTableFooter {
padding-top: 1.333em;
}
.clickableColumn {
cursor: pointer;
}
.statisticsTableHeaderIcon {
width: 2em;
height: auto;
margin-right: 1em;
vertical-align: middle;
}
.statisticsTableHeader {
padding-left: 1em;
padding-bottom: 1em;
padding-top: 1em;
}
.statisticsTableHeaderRight {
display: inline-block;
right: 1em;
position: absolute;
cursor: pointer;
width: 20px;
height: 20px;
-webkit-transform: rotate(270deg);
-moz-transform: rotate(270deg);
-o-transform: rotate(270deg);
-ms-transform: rotate(270deg);
transform: rotate(270deg);
}
.addButton {
text-transform: uppercase;
}
.boatClassColumn {
min-width: 120px;
}
@@ -0,0 +1,46 @@
package com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab.details;
import com.google.gwt.core.client.GWT;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab.SailingProfileOverviewPresenter;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.EditSailorProfileDetailsView;
import com.sap.sailing.gwt.home.shared.usermanagement.decorator.AuthorizedContentDecoratorDesktop;
import com.sap.sse.security.ui.authentication.app.NeedsAuthenticationContext;
/**
* View element which wraps around {@link ShowAndEditSailorProfile} and provides an
* {@link AuthorizedContentDecoratorDesktop}
*/
public class SailorProfileDetails extends Composite implements SailorProfileDetailsView {
interface MyBinder extends UiBinder<Widget, SailorProfileDetails> {
}
private static MyBinder uiBinder = GWT.create(MyBinder.class);
@UiField(provided = true)
AuthorizedContentDecoratorDesktop decoratorUi;
@UiField(provided = true)
ShowAndEditSailorProfile editSailorProfileUi;
@Override
public void setPresenter(SailingProfileOverviewPresenter presenter) {
decoratorUi = new AuthorizedContentDecoratorDesktop(presenter);
editSailorProfileUi = new ShowAndEditSailorProfile(presenter.getSharedSailorProfilePresenter(),
presenter.getFlagImageResolver(), this, presenter.getClientFactory().getUserService());
initWidget(uiBinder.createAndBindUi(this));
presenter.getSharedSailorProfilePresenter().getDataProvider().setView(editSailorProfileUi);
}
@Override
public NeedsAuthenticationContext authentificationContextConsumer() {
return decoratorUi;
}
public EditSailorProfileDetailsView getEditView() {
return editSailorProfileUi;
}
}
@@ -0,0 +1,24 @@
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder' xmlns:g='urn:import:com.google.gwt.user.client.ui'
xmlns:um="urn:import:com.sap.sailing.gwt.home.shared.usermanagement.decorator"
xmlns:up="urn:import:com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab.details">
<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:style>
.table {
width: 100%;
}
.table .empty {
margin: 1.033333333333333em;
font-style: italic;
line-height: 2em;
}
</ui:style>
<g:HTMLPanel addStyleNames="{res.mediaCss.grid} {res.mainCss.spacermarginbottommediumsmall}">
<um:AuthorizedContentDecoratorDesktop ui:field="decoratorUi">
<um:content>
<up:ShowAndEditSailorProfile ui:field="editSailorProfileUi" />
</um:content>
</um:AuthorizedContentDecoratorDesktop>
</g:HTMLPanel>
</ui:UiBinder>
@@ -0,0 +1,8 @@
package com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab.details;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.view.SailorProfileView;
public interface SailorProfileDetailsView extends SailorProfileView {
}
@@ -0,0 +1,215 @@
package com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab.details;
import java.util.ArrayList;
import java.util.Map.Entry;
import java.util.UUID;
import com.google.gwt.core.client.GWT;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.home.communication.event.SimpleCompetitorWithIdDTO;
import com.sap.sailing.gwt.home.communication.user.profile.domain.ParticipatedEventDTO;
import com.sap.sailing.gwt.home.communication.user.profile.domain.SailorProfileDTO;
import com.sap.sailing.gwt.home.communication.user.profile.domain.SailorProfileEventsDTO;
import com.sap.sailing.gwt.home.communication.user.profile.domain.SailorProfileNumericStatisticType;
import com.sap.sailing.gwt.home.communication.user.profile.domain.SailorProfileStatisticDTO;
import com.sap.sailing.gwt.home.communication.user.profile.domain.SailorProfileStatisticDTO.SingleEntry;
import com.sap.sailing.gwt.home.desktop.partials.desktopaccordion.DesktopAccordion;
import com.sap.sailing.gwt.home.desktop.partials.desktopaccordion.DesktopAccordion.AccordionExpansionListener;
import com.sap.sailing.gwt.home.desktop.partials.desktopaccordion.DesktopAccordionResources;
import com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab.SailorProfileDesktopResources;
import com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab.details.events.SailorProfileEventsTable;
import com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab.details.statistic.SailorProfileStatisticTable;
import com.sap.sailing.gwt.home.shared.partials.editable.EditableSuggestedMultiSelectionCompetitor;
import com.sap.sailing.gwt.home.shared.partials.editable.InlineEditLabel;
import com.sap.sailing.gwt.home.shared.partials.listview.BoatClassListView;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.DataMiningQueryForSailorProfilesPersistor;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.EditSailorProfileDetailsView;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.SharedSailorProfileResources;
import com.sap.sailing.gwt.ui.client.FlagImageResolver;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.settings.SailingSettingsConstants;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.gwt.client.Notification;
import com.sap.sse.gwt.client.Notification.NotificationType;
import com.sap.sse.security.ui.client.UserService;
/**
* Implementation of {@link EditSailorProfileDetailsView} where users can view the details of a SailorProfile and edit
* them. The data is loaded when the accordion is opened for the first time.
*/
public class ShowAndEditSailorProfile extends Composite implements EditSailorProfileDetailsView {
private static SharedSailorProfileUiBinder uiBinder = GWT.create(SharedSailorProfileUiBinder.class);
interface SharedSailorProfileUiBinder extends UiBinder<Widget, ShowAndEditSailorProfile> {
}
@UiField
StringMessages i18n;
@UiField(provided = true)
EditableSuggestedMultiSelectionCompetitor competitorSelectionUi;
@UiField
InlineEditLabel titleUi;
@UiField
BoatClassListView boatClassesUi;
@UiField
DesktopAccordion accordionEventsUi;
@UiField
DesktopAccordion accordionStatisticsUi;
@UiField
DesktopAccordion accordionPolarDiagramUi;
Label eventsEmptyUi;
private final EditSailorProfileDetailsView.Presenter presenter;
private FlagImageResolver flagImageResolver;
private UserService userService;
public ShowAndEditSailorProfile(EditSailorProfileDetailsView.Presenter presenter,
FlagImageResolver flagImageResolver, SailorProfileDetailsView parent, UserService userService) {
this.presenter = presenter;
this.flagImageResolver = flagImageResolver;
this.userService = userService;
presenter.getDataProvider().setView(this);
competitorSelectionUi = new EditableSuggestedMultiSelectionCompetitor(presenter.getCompetitorPresenter(),
flagImageResolver);
initWidget(uiBinder.createAndBindUi(this));
SharedSailorProfileResources.INSTANCE.css().ensureInjected();
SailorProfileDesktopResources.INSTANCE.css().ensureInjected();
boatClassesUi.setText(i18n.boatClasses());
accordionPolarDiagramUi.setVisible(false);
}
private void setupTitleChangeListener(final UUID uuid) {
// setup title change handler
titleUi.clearTextChangeHandlers();
titleUi.addTextChangeHandler((text) -> {
presenter.getDataProvider().updateTitle(uuid, text);
});
}
@Override
public void setEntry(SailorProfileDTO entry) {
competitorSelectionUi.setSelectedItems(entry.getCompetitors());
titleUi.setText(entry.getName());
boatClassesUi.setEmptyMessage(StringMessages.INSTANCE.pleaseSelectCompetitorFirst());
boatClassesUi.setItems(entry.getBoatclasses());
accordionEventsUi.clear();
// Get events asynchronously
presenter.getDataProvider().getEvents(entry.getKey(), new AsyncCallback<SailorProfileEventsDTO>() {
@Override
public void onFailure(Throwable caught) {
GWT.log(caught.getMessage(), caught);
}
@Override
public void onSuccess(SailorProfileEventsDTO result) {
appendEmptyMessageIfNecessary(result);
for (ParticipatedEventDTO dto : result.getParticipatedEvents()) {
SailorProfileEventsTable table = new SailorProfileEventsTable(flagImageResolver,
presenter.getPlaceController(), dto);
accordionEventsUi.addWidget(table);
}
}
/** adds empty widget */
private void appendEmptyMessageIfNecessary(SailorProfileEventsDTO result) {
if (Util.isEmpty(result.getParticipatedEvents())) {
createEventsEmptyUiIfNecessary();
accordionEventsUi.addWidget(eventsEmptyUi);
} else if (eventsEmptyUi != null && eventsEmptyUi.isAttached()) {
eventsEmptyUi.removeFromParent();
}
}
/** creates empty widget */
private void createEventsEmptyUiIfNecessary() {
if (eventsEmptyUi == null) {
eventsEmptyUi = new Label(StringMessages.INSTANCE.noEventsFoundForCompetitors());
DesktopAccordionResources.INSTANCE.css().ensureInjected();
eventsEmptyUi.addStyleName(DesktopAccordionResources.INSTANCE.css().accordionEmptyMessage());
}
}
});
accordionStatisticsUi.clear();
setupTables(entry);
setupTitleChangeListener(entry.getKey());
}
/** create tables for statistic types */
private void setupTables(SailorProfileDTO entry) {
DataMiningQueryForSailorProfilesPersistor.removeDMQueriesFromLocalStorage();
for (SailorProfileNumericStatisticType type : SailorProfileNumericStatisticType.values()) {
SailorProfileStatisticTable table = new SailorProfileStatisticTable(flagImageResolver, type, i18n,
userService);
accordionStatisticsUi.addWidget(table);
if (accordionStatisticsUi.isExpanded()) {
updateStatistic(entry, type, table);
} else {
// load the statistic data when the accordion is expanded for the first time
accordionStatisticsUi.addAccordionListener(new AccordionExpansionListener() {
@Override
public void onExpansion(boolean expanded) {
if (expanded) {
updateStatistic(entry, type, table);
}
}
});
}
}
}
/** retrieve and update the statistic for a given type of a sailor profile entry */
private void updateStatistic(SailorProfileDTO entry, SailorProfileNumericStatisticType type,
SailorProfileStatisticTable table) {
presenter.getDataProvider().getStatisticFor(entry.getKey(), type,
new AsyncCallback<SailorProfileStatisticDTO>() {
private String navigationUrl;
@Override
public void onFailure(Throwable caught) {
Notification.notify(i18n.couldNotDetermineStatistic(), NotificationType.WARNING);
}
@Override
public void onSuccess(SailorProfileStatisticDTO answer) {
ArrayList<Pair<SimpleCompetitorWithIdDTO, SingleEntry>> data = new ArrayList<>();
for (Entry<SimpleCompetitorWithIdDTO, ArrayList<SingleEntry>> entry : answer.getResult()
.entrySet()) {
for (SingleEntry value : entry.getValue()) {
data.add(new Pair<SimpleCompetitorWithIdDTO, SingleEntry>(entry.getKey(), value));
}
}
if (answer.getDataMiningQuery() != null) {
final String identifier = SailingSettingsConstants.DATAMINING_QUERY_PREFIX
+ UUID.randomUUID().toString();
navigationUrl = "DataMining.html?q=" + identifier;
table.setNavigationTarget(this::func);
DataMiningQueryForSailorProfilesPersistor.writeDMQueriesToLocalStorageIfPossible(answer,
identifier);
}
table.setData(data);
}
String func(Object o) {
return navigationUrl;
}
});
}
}
@@ -0,0 +1,31 @@
<!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:lv="urn:import:com.sap.sailing.gwt.home.shared.partials.listview"
xmlns:dac="urn:import:com.sap.sailing.gwt.home.desktop.partials.desktopaccordion"
xmlns:ms="urn:import:com.sap.sailing.gwt.home.shared.partials.editable">
<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="sprd"
type="com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab.SailorProfileDesktopResources" />
<g:HTMLPanel addStyleNames="{res.mediaCss.grid} {res.mainCss.spacermarginbottommediumsmall}">
<ms:InlineEditLabel ui:field="titleUi" addStyleNames="{sprd.css.showAndEditHeader}" />
<div class="{res.mediaCss.column}">
<ms:EditableSuggestedMultiSelectionCompetitor
ui:field="competitorSelectionUi"
addStyleNames="{res.mediaCss.column} {res.mainCss.spacermargintopmediumsmall} {res.mediaCss.small12} {res.mediaCss.medium6}" />
<lv:BoatClassListView ui:field="boatClassesUi"
addStyleNames="{res.mediaCss.column} {res.mainCss.spacermargintopmediumsmall} {res.mediaCss.small12} {res.mediaCss.medium6}" />
<div class="{res.mediaCss.column} {res.mainCss.spacermargintopmediumsmall} {res.mediaCss.small12}">
<dac:DesktopAccordion ui:field="accordionEventsUi" title="{i18n.events}" />
</div>
<div class="{res.mediaCss.column} {res.mainCss.spacermargintopmediumsmall} {res.mediaCss.small12}">
<dac:DesktopAccordion ui:field="accordionStatisticsUi" title="{i18n.statistics}" />
</div>
<div class="{res.mediaCss.column} {res.mainCss.spacermargintopmediumsmall} {res.mediaCss.small12}">
<dac:DesktopAccordion ui:field="accordionPolarDiagramUi"
title="{i18n.polarSheetChart}" />
</div>
</div>
</g:HTMLPanel>
</ui:UiBinder>
@@ -0,0 +1,38 @@
package com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab.details.events;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.SpanElement;
import com.google.gwt.dom.client.Style.Position;
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.Widget;
import com.sap.sailing.gwt.home.communication.event.SimpleCompetitorDTO;
import com.sap.sailing.gwt.ui.client.FlagImageResolver;
public class CompetitorWithoutClubnameItemDescription extends Widget {
private static LocalUiBinder uiBinder = GWT.create(LocalUiBinder.class);
interface LocalUiBinder extends UiBinder<Element, CompetitorWithoutClubnameItemDescription> {
}
@UiField DivElement flagImageUi;
@UiField SpanElement sailIdUi;
public CompetitorWithoutClubnameItemDescription(SimpleCompetitorDTO competitor,
FlagImageResolver flagImageResolver) {
setElement(uiBinder.createAndBindUi(this));
flagImageUi.getStyle().setBackgroundImage("url('" + flagImageResolver.getFlagImageUri(
competitor.getFlagImageURL(), competitor.getTwoLetterIsoCountryCode()).asString() + "')");
sailIdUi.setInnerText(competitor.getShortInfo());
}
public void fixFlagPosition(int offsetTop) {
flagImageUi.getStyle().setPosition(Position.RELATIVE);
flagImageUi.getStyle().setTop(offsetTop, Unit.PX);
}
}
@@ -0,0 +1,18 @@
<!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.shared.partials.multiselection.SuggestedMultiSelectionResources" />
<ui:with field="spr" type="com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.SharedSailorProfileResources" />
<ui:style>
.middle-aligned {
vertical-align:middle;
}
</ui:style>
<div>
<div class="{local_res.css.suggestionsItemDescriptionImage} {spr.css.competitorWithoutClubnameItemDescription} {style.middle-aligned}"
ui:field="flagImageUi"></div>
<span class="{local_res.css.suggestionsItemDescriptionId} {style.middle-aligned}" ui:field="sailIdUi"></span>
</div>
</ui:UiBinder>
@@ -0,0 +1,67 @@
package com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab.details.events;
import static com.google.gwt.dom.client.BrowserEvents.CLICK;
import static com.google.gwt.dom.client.BrowserEvents.KEYDOWN;
import static com.google.gwt.dom.client.BrowserEvents.TOUCHEND;
import com.google.gwt.cell.client.AbstractSafeHtmlCell;
import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.safehtml.shared.SafeUri;
import com.google.gwt.safehtml.shared.UriUtils;
import com.google.gwt.text.shared.SafeHtmlRenderer;
import com.google.gwt.user.cellview.client.Column;
import com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab.SailorProfileDesktopResources;
import com.sap.sailing.gwt.home.shared.resources.SharedHomeResources;
/**
* NavigatorColumn with a navigator arrow. After a click on the column, it will redirect to the associated URL,
* specified in the overwritten {@link #getValue(Object)}-method.
*/
public abstract class NavigatorColumn<T> extends Column<T, String> {
public NavigatorColumn() {
super(new NavigatorCell());
}
private static class NavigatorCell extends AbstractSafeHtmlCell<String> {
public NavigatorCell() {
super(new SafeHtmlRenderer<String>() {
@Override
public SafeHtml render(String url) {
if (url != null) {
SafeUri src = SharedHomeResources.INSTANCE.arrowDownGrey().getSafeUri();
return SailorProfileDesktopResources.TEMPLATE.navigator(UriUtils.fromSafeConstant(url), src);
}
return new SafeHtmlBuilder().toSafeHtml();
}
@Override
public void render(String object, SafeHtmlBuilder builder) {
builder.append(render(object));
}
}, CLICK, KEYDOWN, TOUCHEND);
}
@Override
public void onBrowserEvent(Context context, Element parent, String value, NativeEvent event,
ValueUpdater<String> valueUpdater) {
super.onBrowserEvent(context, parent, value, event, valueUpdater);
if (valueUpdater != null) {
valueUpdater.update(value);
}
}
@Override
protected void render(Context context, SafeHtml data, SafeHtmlBuilder sb) {
if (data != null) {
sb.append(data);
}
}
}
}
@@ -0,0 +1,156 @@
package com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab.details.events;
import java.util.Arrays;
import java.util.HashSet;
import com.google.gwt.cell.client.AbstractCell;
import com.google.gwt.cell.client.FieldUpdater;
import com.google.gwt.cell.client.NumberCell;
import com.google.gwt.cell.client.TextCell;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.SpanElement;
import com.google.gwt.place.shared.PlaceController;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.common.theme.component.celltable.DesignedCellTableResources;
import com.sap.sailing.gwt.home.communication.user.profile.domain.ParticipatedEventDTO;
import com.sap.sailing.gwt.home.communication.user.profile.domain.ParticipatedRegattaDTO;
import com.sap.sailing.gwt.home.desktop.places.event.regatta.leaderboardtab.RegattaLeaderboardPlace;
import com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab.SailorProfileDesktopResources;
import com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab.details.ShowAndEditSailorProfile;
import com.sap.sailing.gwt.home.shared.app.ApplicationHistoryMapper;
import com.sap.sailing.gwt.ui.client.FlagImageResolver;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.common.Util;
import com.sap.sse.gwt.client.celltable.SortedCellTable;
/** This element displays the events a sailor has participated in in the {@link ShowAndEditSailorProfile} view. */
public class SailorProfileEventsTable extends Composite {
interface MyBinder extends UiBinder<Widget, SailorProfileEventsTable> {
}
private static MyBinder uiBinder = GWT.create(MyBinder.class);
private ApplicationHistoryMapper historyMapper = GWT.create(ApplicationHistoryMapper.class);
private PlaceController placeController;
@UiField(provided = true)
final SortedCellTable<ParticipatedRegattaDTO> sailorProfilesTable = new SortedCellTable<>(0,
DesignedCellTableResources.INSTANCE);
@UiField
SpanElement titleUi;
private FlagImageResolver flagImageResolver;
public SailorProfileEventsTable(FlagImageResolver flagImageResolver, PlaceController placeController,
ParticipatedEventDTO event) {
this.flagImageResolver = flagImageResolver;
this.placeController = placeController;
initWidget(uiBinder.createAndBindUi(this));
setupTable(event.getParticipatedRegattas());
titleUi.setInnerText(event.getEventName());
}
private void setupTable(Iterable<ParticipatedRegattaDTO> regattas) {
sailorProfilesTable.addColumn(regattaNameColumn, StringMessages.INSTANCE.regattaName());
sailorProfilesTable.addColumn(regattaRank, StringMessages.INSTANCE.regattaRank());
sailorProfilesTable.addColumn(competitorColumn, StringMessages.INSTANCE.competitor());
sailorProfilesTable.addColumn(clubNameColumn, StringMessages.INSTANCE.name());
sailorProfilesTable.addColumn(sumPointsColumn, "\u2211");
sailorProfilesTable.addColumn(navigatorColumn);
navigatorColumn.setCellStyleNames(DesignedCellTableResources.INSTANCE.cellTableStyle().buttonCell());
navigatorColumn.setFieldUpdater(new FieldUpdater<ParticipatedRegattaDTO, String>() {
@Override
public void update(int index, ParticipatedRegattaDTO entry, String value) {
placeController.goTo(getTargetPlace(entry));
}
});
sailorProfilesTable.setPageSize(Util.size(regattas));
sailorProfilesTable.setList(regattas);
addButtonStyle(regattaNameColumn);
addButtonStyle(regattaRank);
addButtonStyle(competitorColumn);
addButtonStyle(clubNameColumn);
addButtonStyle(sumPointsColumn);
addButtonStyle(navigatorColumn);
sailorProfilesTable.addCellPreviewHandler(e -> {
if ("click".equals(e.getNativeEvent().getType())) {
placeController.goTo(getTargetPlace(e.getValue()));
}
});
}
private void addButtonStyle(Column<?, ?> col) {
col.setCellStyleNames(col.getCellStyleNames(null, null) + " "
+ SailorProfileDesktopResources.INSTANCE.css().clickableColumn());
}
private RegattaLeaderboardPlace getTargetPlace(ParticipatedRegattaDTO entry) {
return new RegattaLeaderboardPlace(entry.getEventId(), entry.getRegattaId(),
new HashSet<>(Arrays.asList(entry.getCompetitorDto().getIdAsString())));
}
private final Column<ParticipatedRegattaDTO, String> regattaNameColumn = new Column<ParticipatedRegattaDTO, String>(
new TextCell()) {
@Override
public String getValue(ParticipatedRegattaDTO entry) {
return entry.getRegattaName();
}
};
private final Column<ParticipatedRegattaDTO, Number> regattaRank = new Column<ParticipatedRegattaDTO, Number>(
new NumberCell()) {
@Override
public Number getValue(ParticipatedRegattaDTO entry) {
return entry.getRegattaRank();
}
};
private final Column<ParticipatedRegattaDTO, ParticipatedRegattaDTO> competitorColumn = new Column<ParticipatedRegattaDTO, ParticipatedRegattaDTO>(
new AbstractCell<ParticipatedRegattaDTO>() {
@Override
public void render(Context context, ParticipatedRegattaDTO value, SafeHtmlBuilder sb) {
sb.appendHtmlConstant(
new CompetitorWithoutClubnameItemDescription(value.getCompetitorDto(), flagImageResolver)
.getElement().getInnerHTML());
}
}) {
@Override
public ParticipatedRegattaDTO getValue(ParticipatedRegattaDTO object) {
return object;
}
};
private final Column<ParticipatedRegattaDTO, String> clubNameColumn = new Column<ParticipatedRegattaDTO, String>(
new TextCell()) {
@Override
public String getValue(ParticipatedRegattaDTO entry) {
return entry.getCompetitorDto().getName();
}
};
private final Column<ParticipatedRegattaDTO, Number> sumPointsColumn = new Column<ParticipatedRegattaDTO, Number>(
new NumberCell()) {
@Override
public Number getValue(ParticipatedRegattaDTO entry) {
return entry.getSumPoints();
}
};
private final Column<ParticipatedRegattaDTO, String> navigatorColumn = new NavigatorColumn<ParticipatedRegattaDTO>() {
@Override
public String getValue(ParticipatedRegattaDTO object) {
return "#" + historyMapper.getToken(getTargetPlace(object));
}
};
}
@@ -0,0 +1,24 @@
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder' xmlns:g='urn:import:com.google.gwt.user.client.ui'
xmlns:um="urn:import:com.sap.sailing.gwt.home.shared.usermanagement.decorator"
xmlns:up="urn:import:com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile"
xmlns:c="urn:import:com.google.gwt.user.cellview.client">
<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="spr"
type="com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab.SailorProfileDesktopResources" />
<ui:style>
</ui:style>
<g:HTMLPanel
addStyleNames="{res.mediaCss.column} {res.mediaCss.small12} {res.mainCss.spacermargintopmediumsmall} {spr.css.marginLastTable}">
<div class="{res.mediaCss.grid} {spr.css.eventsTablePanel}">
<div class="{spr.css.eventsTableTitle}">
<span ui:field="titleUi" />
</div>
<c:CellTable ui:field="sailorProfilesTable" addStyleNames="{spr.css.eventsTable}">
<c:emptyTableWidget>
<g:Label text="{i18n.noEventsFoundForCompetitors}" addStyleNames="{spr.css.eventsTableEmpty}" />
</c:emptyTableWidget>
</c:CellTable>
</div>
</g:HTMLPanel>
</ui:UiBinder>
@@ -0,0 +1,84 @@
package com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab.details.statistic;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.safehtml.shared.SafeUri;
import com.google.gwt.safehtml.shared.UriUtils;
import com.sap.sailing.gwt.home.communication.user.profile.domain.SailorProfileNumericStatisticType;
import com.sap.sailing.gwt.home.shared.partials.statistics.StatisticsBoxResources;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.common.TimePoint;
/** contains methods for formatting numeric statistics in sailor profiles */
public final class SailorProfileNumericStatisticTypeFormatter {
private SailorProfileNumericStatisticTypeFormatter() {
}
private static final DateTimeFormat dateToHoursAndMinutesFormat = DateTimeFormat.getFormat("HH:mm");
public static String getDisplayName(SailorProfileNumericStatisticType type, StringMessages stringMessages) {
switch (type) {
case AVERAGE_STARTLINE_DISTANCE:
return stringMessages.averageDistanceToStartLine();
case BEST_DISTANCE_TO_START:
return stringMessages.shortestDistanceToStartline();
case BEST_STARTLINE_SPEED:
return stringMessages.maxStartLineSpeed();
case MAX_SPEED:
return stringMessages.maxSpeedTitle();
default:
return type.name();
}
}
/** @return an icon safe uri corresponding to the given statistic type */
public static SafeUri getIcon(SailorProfileNumericStatisticType type) {
switch (type) {
case AVERAGE_STARTLINE_DISTANCE:
return StatisticsBoxResources.INSTANCE.fastestSailorWhite().getSafeUri();
case BEST_DISTANCE_TO_START:
return StatisticsBoxResources.INSTANCE.fastestSailorWhite().getSafeUri();
case BEST_STARTLINE_SPEED:
return StatisticsBoxResources.INSTANCE.maxSpeedWhite().getSafeUri();
case MAX_SPEED:
return StatisticsBoxResources.INSTANCE.maxSpeedWhite().getSafeUri();
default:
return UriUtils.fromSafeConstant("");
}
}
/** @return the {@link #timePoint} formatted as hours and minutes */
public static String format(TimePoint timePoint) {
return dateToHoursAndMinutesFormat.format(timePoint.asDate());
}
/** @return the formatted value with unit corresponding to the given statistic type */
public static String format(SailorProfileNumericStatisticType type, Double value, StringMessages stringMessages) {
switch (type) {
case AVERAGE_STARTLINE_DISTANCE:
return "\u00D8 " + stringMessages.metersValue(value);
case BEST_DISTANCE_TO_START:
return stringMessages.metersValue(value);
case BEST_STARTLINE_SPEED:
case MAX_SPEED:
return stringMessages.knotsValue(value);
default:
return "" + value;
}
}
/** @return the column heading name corresponding to the given statistic type */
public static String getColumnHeadingName(SailorProfileNumericStatisticType type, StringMessages stringMessages) {
switch (type) {
case AVERAGE_STARTLINE_DISTANCE:
case BEST_DISTANCE_TO_START:
return stringMessages.distance();
case BEST_STARTLINE_SPEED:
case MAX_SPEED:
return stringMessages.speed();
default:
return stringMessages.value();
}
}
}
@@ -0,0 +1,274 @@
package com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab.details.statistic;
import java.util.HashMap;
import java.util.List;
import java.util.function.Function;
import com.google.gwt.cell.client.AbstractCell;
import com.google.gwt.cell.client.FieldUpdater;
import com.google.gwt.cell.client.TextCell;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.SpanElement;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.security.SecuredDomainType;
import com.sap.sailing.gwt.common.theme.component.celltable.DesignedCellTableResources;
import com.sap.sailing.gwt.home.communication.event.SimpleCompetitorWithIdDTO;
import com.sap.sailing.gwt.home.communication.user.profile.domain.SailorProfileNumericStatisticType;
import com.sap.sailing.gwt.home.communication.user.profile.domain.SailorProfileNumericStatisticType.StatisticType;
import com.sap.sailing.gwt.home.communication.user.profile.domain.SailorProfileStatisticDTO.SingleEntry;
import com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab.SailorProfileDesktopResources;
import com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab.details.ShowAndEditSailorProfile;
import com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab.details.events.CompetitorWithoutClubnameItemDescription;
import com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab.details.events.NavigatorColumn;
import com.sap.sailing.gwt.home.shared.resources.SharedHomeResources;
import com.sap.sailing.gwt.settings.client.EntryPointWithSettingsLinkFactory;
import com.sap.sailing.gwt.settings.client.raceboard.RaceBoardPerspectiveOwnSettings;
import com.sap.sailing.gwt.settings.client.raceboard.RaceboardContextDefinition;
import com.sap.sailing.gwt.ui.client.FlagImageResolver;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapLifecycle;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapSettings;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.impl.InvertibleComparatorAdapter;
import com.sap.sse.common.impl.MillisecondsDurationImpl;
import com.sap.sse.common.settings.Settings;
import com.sap.sse.gwt.client.celltable.SortedCellTable;
import com.sap.sse.gwt.client.shared.perspective.PerspectiveCompositeSettings;
import com.sap.sse.security.ui.client.UserService;
/**
* This element displays a given statistic for each sailor in a sailor profile in the {@link ShowAndEditSailorProfile}
* view.
*/
public class SailorProfileStatisticTable extends Composite {
interface MyBinder extends UiBinder<Widget, SailorProfileStatisticTable> {
}
private static MyBinder uiBinder = GWT.create(MyBinder.class);
@UiField(provided = true)
final SortedCellTable<Pair<SimpleCompetitorWithIdDTO, SingleEntry>> sailorProfilesTable = new SortedCellTable<>(0,
DesignedCellTableResources.INSTANCE);
@UiField
SpanElement titleUi;
@UiField
Image titleIconUi;
@UiField
Anchor anchor;
private final Image titleHeaderRight;
private final FlagImageResolver flagImageResolver;
private final SailorProfileNumericStatisticType type;
private final StringMessages stringMessages;
private Function<Pair<SimpleCompetitorWithIdDTO, SingleEntry>, String> navigationTarget;
private String dataMiningUrl;
private UserService userService;
public SailorProfileStatisticTable(FlagImageResolver flagImageResolver, SailorProfileNumericStatisticType type,
StringMessages stringMessages, UserService userService) {
this.userService = userService;
this.flagImageResolver = flagImageResolver;
this.type = type;
this.stringMessages = stringMessages;
SailorProfileDesktopResources.INSTANCE.css().ensureInjected();
initWidget(uiBinder.createAndBindUi(this));
titleHeaderRight = new Image();
titleHeaderRight.addStyleName(SailorProfileDesktopResources.INSTANCE.css().statisticsTableHeaderRight());
setupTable();
titleUi.setInnerText(SailorProfileNumericStatisticTypeFormatter.getDisplayName(type, stringMessages));
titleIconUi.setUrl(SailorProfileNumericStatisticTypeFormatter.getIcon(type));
}
/**
* @param isAverage
* false -> {@link #navigationTarget} is called when the arrow in each row is clicked <br/>
* true -> {@link #navigationTarget} is called when the arrow in the table header is clicked
* @param navigationTarget
*/
private void setNavigationTarget(Function<Pair<SimpleCompetitorWithIdDTO, SingleEntry>, String> navigationTarget,
boolean isAverage) {
if (isAverage) {
setNavigationTarget(navigationTarget);
titleHeaderRight.setVisible(true);
sailorProfilesTable.removeColumn(navigatorColumn);
} else {
this.navigationTarget = navigationTarget;
titleHeaderRight.setVisible(false);
sailorProfilesTable.addColumn(navigatorColumn);
}
}
/** sets {@link #navigationTarget} which is called when the arrow in the table header is clicked */
public void setNavigationTarget(Function<Pair<SimpleCompetitorWithIdDTO, SingleEntry>, String> navigationTarget) {
titleHeaderRight.setUrl(SharedHomeResources.INSTANCE.arrowDownWhite().getSafeUri());
anchor.getElement().appendChild(titleHeaderRight.getElement());
this.navigationTarget = navigationTarget;
}
public void setData(final List<Pair<SimpleCompetitorWithIdDTO, SingleEntry>> data) {
sailorProfilesTable.setPageSize(data.size());
if (data.size() > 0) {
dataMiningUrl = navigationTarget.apply(data.get(0));
anchor.setHref(dataMiningUrl);
}
if (!userService.hasPermission(SecuredDomainType.DATA_MINING.getStringPermission())) {
anchor.setVisible(false);
}
sailorProfilesTable.setList(data);
}
private void setupTable() {
boolean isAverage = type.getAggregationType() == StatisticType.AVERAGE;
if (!isAverage) {
sailorProfilesTable.addColumn(eventNameColumn, StringMessages.INSTANCE.eventName());
sailorProfilesTable.addColumn(timeColumn, StringMessages.INSTANCE.time());
}
actualValueColumn.setSortable(true);
sailorProfilesTable.getColumnSortList().push(actualValueColumn);
sailorProfilesTable.setInitialSortColumn(actualValueColumn);
sailorProfilesTable.setDefaultSortOrder(actualValueColumn, true);
sailorProfilesTable.addColumn(actualValueColumn,
SailorProfileNumericStatisticTypeFormatter.getColumnHeadingName(type, stringMessages),
new InvertibleComparatorAdapter<Pair<SimpleCompetitorWithIdDTO, SingleEntry>>(true) {
@Override
public int compare(Pair<SimpleCompetitorWithIdDTO, SingleEntry> o1,
Pair<SimpleCompetitorWithIdDTO, SingleEntry> o2) {
return o1.getB().getValue().compareTo(o2.getB().getValue());
}
}, true);
sailorProfilesTable.addColumn(competitorColumn, StringMessages.INSTANCE.competitor());
sailorProfilesTable.addColumn(clubNameColumn, StringMessages.INSTANCE.name());
navigatorColumn.setCellStyleNames(DesignedCellTableResources.INSTANCE.cellTableStyle().buttonCell());
navigatorColumn.setFieldUpdater(new FieldUpdater<Pair<SimpleCompetitorWithIdDTO, SingleEntry>, String>() {
@Override
public void update(int index, Pair<SimpleCompetitorWithIdDTO, SingleEntry> entry, String value) {
Window.Location.assign(value);
}
});
if (!isAverage) {
setNavigationTarget(this::createRaceboardURL, false);
} else {
// navigation target is set from outside
}
if (userService.hasPermission(SecuredDomainType.DATA_MINING.getStringPermission())
|| !isAverage) {
addButtonStyle(actualValueColumn);
addButtonStyle(clubNameColumn);
addButtonStyle(competitorColumn);
addButtonStyle(eventNameColumn);
addButtonStyle(navigatorColumn);
addButtonStyle(timeColumn);
sailorProfilesTable.addCellPreviewHandler(e -> {
if ("click".equals(e.getNativeEvent().getType())) {
Window.Location.assign(navigationTarget.apply(e.getValue()));
}
});
}
}
private void addButtonStyle(Column<?, ?> col) {
col.setCellStyleNames(col.getCellStyleNames(null, null) + " "
+ SailorProfileDesktopResources.INSTANCE.css().clickableColumn());
}
private String createRaceboardURL(Pair<SimpleCompetitorWithIdDTO, SingleEntry> entry) {
String result = null;
if (type.getAggregationType() != StatisticType.AVERAGE) {
final RegattaAndRaceIdentifier raceIdentifier = entry.getB().getRelatedRaceOrNull();
// create raceboard context
RaceboardContextDefinition raceboardContext = new RaceboardContextDefinition(
raceIdentifier.getRegattaName(), raceIdentifier.getRaceName(),
entry.getB().getLeaderboardNameOrNull(), entry.getB().getLeaderboardGroupNameOrNull(),
entry.getB().getEventIdOrNull(), type.getPlayerMode().name(), entry.getA().getIdAsString());
RaceBoardPerspectiveOwnSettings perspectiveOwnSettings = new RaceBoardPerspectiveOwnSettings(
new MillisecondsDurationImpl(entry.getB().getRelatedTimePointOrNull().asMillis()
- entry.getB().getRelatedRaceStartTimePointOrNull().asMillis()));
// create raceboard settings
HashMap<String, Settings> innerSettings = new HashMap<>();
innerSettings.put(RaceMapLifecycle.ID, RaceMapSettings.getDefaultWithShowMapControls(true));
PerspectiveCompositeSettings<RaceBoardPerspectiveOwnSettings> settings = new PerspectiveCompositeSettings<>(
perspectiveOwnSettings, innerSettings);
result = EntryPointWithSettingsLinkFactory.createRaceBoardLink(raceboardContext, settings);
}
return result;
}
private final Column<Pair<SimpleCompetitorWithIdDTO, SingleEntry>, String> navigatorColumn = new NavigatorColumn<Pair<SimpleCompetitorWithIdDTO, SingleEntry>>() {
@Override
public String getValue(Pair<SimpleCompetitorWithIdDTO, SingleEntry> entry) {
return navigationTarget.apply(entry);
}
};
private final Column<Pair<SimpleCompetitorWithIdDTO, SingleEntry>, String> actualValueColumn = new Column<Pair<SimpleCompetitorWithIdDTO, SingleEntry>, String>(
new TextCell()) {
@Override
public String getValue(Pair<SimpleCompetitorWithIdDTO, SingleEntry> entry) {
return SailorProfileNumericStatisticTypeFormatter.format(type, entry.getB().getValue(), stringMessages);
}
};
private final Column<Pair<SimpleCompetitorWithIdDTO, SingleEntry>, SimpleCompetitorWithIdDTO> competitorColumn = new Column<Pair<SimpleCompetitorWithIdDTO, SingleEntry>, SimpleCompetitorWithIdDTO>(
new AbstractCell<SimpleCompetitorWithIdDTO>() {
@Override
public void render(Context context, SimpleCompetitorWithIdDTO value, SafeHtmlBuilder sb) {
sb.appendHtmlConstant(new CompetitorWithoutClubnameItemDescription(value, flagImageResolver)
.getElement().getInnerHTML());
}
}) {
@Override
public SimpleCompetitorWithIdDTO getValue(Pair<SimpleCompetitorWithIdDTO, SingleEntry> object) {
return object.getA();
}
};
private final Column<Pair<SimpleCompetitorWithIdDTO, SingleEntry>, String> clubNameColumn = new Column<Pair<SimpleCompetitorWithIdDTO, SingleEntry>, String>(
new TextCell()) {
@Override
public String getValue(Pair<SimpleCompetitorWithIdDTO, SingleEntry> entry) {
return entry.getA().getName();
}
};
private final Column<Pair<SimpleCompetitorWithIdDTO, SingleEntry>, String> eventNameColumn = new Column<Pair<SimpleCompetitorWithIdDTO, SingleEntry>, String>(
new TextCell()) {
@Override
public String getValue(Pair<SimpleCompetitorWithIdDTO, SingleEntry> entry) {
return entry.getB().getLeaderboardNameOrNull() + " " + entry.getB().getRaceNameOrNull();
}
};
private final Column<Pair<SimpleCompetitorWithIdDTO, SingleEntry>, String> timeColumn = new Column<Pair<SimpleCompetitorWithIdDTO, SingleEntry>, String>(
new TextCell()) {
@Override
public String getValue(Pair<SimpleCompetitorWithIdDTO, SingleEntry> entry) {
return SailorProfileNumericStatisticTypeFormatter.format(entry.getB().getRelatedTimePointOrNull());
}
};
}
@@ -0,0 +1,22 @@
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder' xmlns:g='urn:import:com.google.gwt.user.client.ui'
xmlns:c="urn:import:com.google.gwt.user.cellview.client">
<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="spr"
type="com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab.SailorProfileDesktopResources" />
<g:HTMLPanel
addStyleNames="{res.mediaCss.column} {res.mediaCss.small12} {res.mainCss.spacermargintopmediumsmall} {spr.css.marginLastTable}">
<div class="{res.mediaCss.grid} {spr.css.eventsTablePanel}">
<div class="{spr.css.eventsTableTitle} {spr.css.statisticsTableHeader}">
<g:Image addStyleNames="{spr.css.statisticsTableHeaderIcon}" ui:field="titleIconUi" />
<span ui:field="titleUi" />
<g:Anchor ui:field="anchor" />
</div>
<c:CellTable ui:field="sailorProfilesTable" addStyleNames="{spr.css.eventsTable}">
<c:emptyTableWidget>
<g:Label text="{i18n.noDataFound}" addStyleNames="{spr.css.eventsTableEmpty}" />
</c:emptyTableWidget>
</c:CellTable>
</div>
</g:HTMLPanel>
</ui:UiBinder>
@@ -6,24 +6,41 @@
<div class="innerContent">
<h5 class="articleSubheadline">October 2018</h5>
<ul class="bulletList">
<li>Introduction of the Sailor Profiles</li>
<ul class="bulletList">
<li>Authenticated users can now create sailor profiles in their profile view.</li>
<li>Sailor Profiles show information about the selected competitors and help to compare their strengths and weaknesses, like:</li>
<ul class="bulletList">
<li>The events the sailor has participated in with the rank and points of the sailor.</li>
<li>Several statistics for each competitor:
<ul class="bulletList">
<li>the maximum speed</li>
<li>the minimum distance to the start line at race start</li>
<li>the average distance to the start line at race start</li>
<li>the maximum speed while crossing the start line</li>
</ul>
<li>It is possible to go from the sailor profile directly to the dynamic place of interest. For example, after clicking on the maximum speed statistic value, the race board will open and the exact point in time where the maximum speed was reached is displayed.</li>
</ul>
</ul>
<li>Searching for phrases containing whitespace characters with double quotes such as "Tobias Schadewaldt" will now work
across a distributed server and event landscape.</li>
<li>A column showing the used boat can now be shown for each extended race column in leaderboards with multiple races.
This new column is activated per default, if the boats change between races. </li>
<li>A column showing the used boat can now be shown for each extended race column in leaderboards with multiple races.
This new column is activated per default, if the boats change between races. </li>
<li>It is now possible to store and load queries in <tt>DataMining.html</tt>. Such queries are saved per user.</li>
</ul>
<h5 class="articleSubheadline">September 2018</h5>
<ul class="bulletList">
<li>In <tt>RaceBoard.html</tt> is a new feature available which allows users to create tags consisting of a title
and optional comment and/or image per race. These tags can be saved visible only for the creating user or for everbody
(requiring special permissions) and can be used to mark interesting situations during a race. Tag buttons allow users to
save frequently used tags as a preset which can be used quckily and do not require repeating input of the same tag.
and optional comment and/or image per race. These tags can be saved visible only for the creating user or for everybody
(requiring special permissions) and can be used to mark interesting situations during a race. Tag buttons allow users to
save frequently used tags as a preset which can be used quickly and do not require repeating input of the same tag.
Those tags and their comments are also used in order to find the containing leaderboard via the global search.</li>
</ul>
<h5 class="articleSubheadline">August 2018</h5>
<ul class="bulletList">
<li>The windstreamlets are now colored dynamically according to the wind fields maximum and minimum wind speed.</li>
<li>The windstreamlets are now colored dynamically according to the wind fields maximum and minimum wind speed.</li>
<li>Changes of <tt>DataMining.html</tt>
<ul class="bulletList">
<li>The currently selected result tab will be remembered when a query is run. It is now possible
@@ -24,13 +24,12 @@ import com.sap.sailing.gwt.home.shared.places.user.confirmation.ConfirmationView
import com.sap.sailing.gwt.home.shared.places.user.passwordreset.PasswordResetClientFactory;
import com.sap.sailing.gwt.home.shared.places.user.passwordreset.PasswordResetView;
import com.sap.sailing.gwt.home.shared.places.user.passwordreset.PasswordResetViewImpl;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.ClientFactoryWithDispatchAndErrorAndUserService;
import com.sap.sailing.gwt.ui.client.refresh.BusyView;
import com.sap.sailing.gwt.ui.client.refresh.ErrorAndBusyClientFactory;
import com.sap.sse.gwt.client.mvp.ErrorView;
import com.sap.sse.security.ui.authentication.AuthenticationManager;
import com.sap.sse.security.ui.authentication.AuthenticationManagerImpl;
import com.sap.sse.security.ui.authentication.WithAuthenticationManager;
import com.sap.sse.security.ui.authentication.WithUserService;
import com.sap.sse.security.ui.authentication.login.LoginHintContent;
import com.sap.sse.security.ui.client.SecureClientFactoryImpl;
import com.sap.sse.security.ui.client.i18n.StringMessages;
@@ -42,8 +41,8 @@ import com.sap.sse.security.ui.client.i18n.StringMessages;
*/
public class MobileApplicationClientFactory extends
SecureClientFactoryImpl<ApplicationTopLevelView<ResettableNavigationPathDisplay>> implements
ErrorAndBusyClientFactory, SearchResultClientFactory, ConfirmationClientFactory, PasswordResetClientFactory,
WithAuthenticationManager, WithUserService {
SearchResultClientFactory, ConfirmationClientFactory, PasswordResetClientFactory, WithAuthenticationManager,
ClientFactoryWithDispatchAndErrorAndUserService {
private final MobilePlacesNavigator navigator;
private final SailingDispatchSystem dispatch = new SailingDispatchSystemImpl();
private final AuthenticationManager authenticationManager;
@@ -139,3 +139,6 @@
.sectionheader_item_adjust_title_right {
padding-right: 2.5em;
}
.sectionheader_element{
top: 0.666666666666em;
}
@@ -1,16 +1,21 @@
package com.sap.sailing.gwt.home.mobile.partials.sectionHeader;
import java.util.ArrayList;
import java.util.Collection;
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.HeadingElement;
import com.google.gwt.dom.client.Style.Display;
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.DOM;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.home.communication.event.LabelType;
@@ -24,42 +29,65 @@ import com.sap.sailing.gwt.home.shared.utils.LabelTypeUtil;
import com.sap.sse.gwt.client.LinkUtil;
public class SectionHeaderContent extends Composite {
protected static final String ACCORDION_COLLAPSED_STYLE = SectionHeaderResources.INSTANCE.css().collapsed();
private static MyBinder uiBinder = GWT.create(MyBinder.class);
interface MyBinder extends UiBinder<Widget, SectionHeaderContent> {
}
@UiField SectionHeaderResources local_res;
@UiField AnchorElement headerMainUi;
@UiField DivElement headerLeftUi;
@UiField DivElement titleAndLabelContainerUi;
@UiField HeadingElement titleUi;
@UiField DivElement labelUi;
@UiField DivElement imageUi;
@UiField DivElement subtitleUi;
@UiField DivElement headerRightUi;
@UiField DivElement infoTextUi;
@UiField DivElement actionArrowUi;
@UiField SimplePanel filterSelectContainerUi;
@UiField
SectionHeaderResources local_res;
@UiField
AnchorElement headerMainUi;
@UiField
DivElement headerLeftUi;
@UiField
DivElement titleAndLabelContainerUi;
@UiField
HeadingElement titleUi;
@UiField
DivElement labelUi;
@UiField
DivElement imageUi;
@UiField
DivElement subtitleUi;
@UiField
HTMLPanel headerRightUi;
@UiField
DivElement infoTextUi;
@UiField
DivElement actionArrowUi;
@UiField
SimplePanel filterSelectContainerUi;
@UiField
HTMLPanel headerContentUi;
private boolean expanded = false;;
private final Collection<AccordionExpansionListener> accordionListeners = new ArrayList<>();
public interface AccordionExpansionListener {
public void onExpansionChange(boolean collapsed);
}
public SectionHeaderContent() {
SectionHeaderResources.INSTANCE.css().ensureInjected();
initWidget(uiBinder.createAndBindUi(this));
headerRightUi.setVisible(false);
filterSelectContainerUi.setVisible(false);
}
public void setSectionTitle(String sectionHeaderTitle) {
titleUi.setInnerText(sectionHeaderTitle);
}
public void setSubtitle(String subtitle) {
subtitleUi.getStyle().clearDisplay();
subtitleUi.setInnerText(subtitle);
headerLeftUi.addClassName(SectionHeaderResources.INSTANCE.css().sectionheader_itemdoublerow());
}
public void setLabelType(LabelType labelType) {
if (labelType == null || !labelType.isRendered()) {
labelUi.getStyle().setDisplay(Display.NONE);
@@ -68,20 +96,20 @@ public class SectionHeaderContent extends Composite {
LabelTypeUtil.renderLabelType(labelUi, labelType);
}
}
public void setImageUrl(String imageUrl) {
imageUi.getStyle().clearDisplay();
imageUi.getStyle().setBackgroundImage("url(" + imageUrl + ")");
titleAndLabelContainerUi
.addClassName(SectionHeaderResources.INSTANCE.css().sectionheader_item_adjust_title_left());
}
public void setInfoText(String infoText) {
headerRightUi.getStyle().clearDisplay();
headerRightUi.setVisible(true);
infoTextUi.getStyle().clearDisplay();
infoTextUi.setInnerText(infoText);
}
public void initCollapsibility(Element content, boolean showInitial) {
final CollapseAnimation animation = new CollapseAnimation(content, showInitial);
setClassName(actionArrowUi, ACCORDION_COLLAPSED_STYLE, !showInitial);
@@ -89,26 +117,28 @@ public class SectionHeaderContent extends Composite {
@Override
public void run() {
boolean collapsed = actionArrowUi.hasClassName(ACCORDION_COLLAPSED_STYLE);
expanded = collapsed;
setClassName(actionArrowUi, ACCORDION_COLLAPSED_STYLE, !collapsed);
animation.animate(collapsed);
accordionListeners.forEach(l -> l.onExpansionChange(!collapsed));
}
});
actionArrowUi.addClassName(SectionHeaderResources.INSTANCE.css().accordion());
titleAndLabelContainerUi
.addClassName(SectionHeaderResources.INSTANCE.css().sectionheader_item_adjust_title_right());
actionArrowUi.getStyle().clearDisplay();
headerRightUi.getStyle().clearDisplay();
headerRightUi.setVisible(true);
}
public void setClickAction(final PlaceNavigation<?> placeNavigation) {
placeNavigation.configureAnchorElement(headerMainUi);
this.adjustedActionStyles();
titleAndLabelContainerUi
.addClassName(SectionHeaderResources.INSTANCE.css().sectionheader_item_adjust_title_right());
headerRightUi.getStyle().clearDisplay();
headerRightUi.setVisible(true);
actionArrowUi.getStyle().clearDisplay();
}
public void setClickAction(final String url) {
headerMainUi.setHref(url);
DOM.setEventListener(headerMainUi, event -> {
@@ -119,30 +149,46 @@ public class SectionHeaderContent extends Composite {
private void adjustedActionStyles() {
titleAndLabelContainerUi
.addClassName(SectionHeaderResources.INSTANCE.css().sectionheader_item_adjust_title_right());
headerRightUi.getStyle().clearDisplay();
headerRightUi.setVisible(true);
actionArrowUi.getStyle().clearDisplay();
}
public void setClickAction(final Runnable commandToExecute) {
headerMainUi.setHref(Window.Location.getHref());
headerRightUi.getStyle().clearDisplay();
headerRightUi.setVisible(true);
actionArrowUi.getStyle().clearDisplay();
LinkUtil.configureForAction(headerMainUi, commandToExecute);
}
private void setClassName(Element element, String className, boolean set) {
if (set) element.addClassName(className);
else element.removeClassName(className);
if (set) {
element.addClassName(className);
} else {
element.removeClassName(className);
}
}
public void appendHeaderElement(Widget widget) {
headerRightUi.add(widget);
actionArrowUi.getStyle().setMarginRight(.5, Unit.EM);
titleAndLabelContainerUi.getStyle().setPaddingRight(4.1, Unit.EM);
}
public void setFirstHeaderElement(Widget widget) {
actionArrowUi.removeFromParent();
headerRightUi.add(widget);
headerRightUi.getElement().appendChild(actionArrowUi);
titleAndLabelContainerUi.getStyle().setPaddingRight(4.1, Unit.EM);
}
public void initFilterSelectUi(AbstractSelectionFilter<?, ?> selectionFilter) {
headerRightUi.getStyle().clearDisplay();
headerRightUi.setVisible(true);
filterSelectContainerUi.setVisible(true);
filterSelectContainerUi.setWidget(selectionFilter);
selectionFilter.addStyleName(local_res.css().sectionheader_item_select());
selectionFilter.getElement().setAttribute("dir", "rtl");
}
public void initBoatClassPopup(String boatClassName) {
BubbleContentBoatClass content = new BubbleContentBoatClass(boatClassName);
Bubble.DefaultPresenter presenter = new Bubble.DefaultPresenter(content, getElement(), imageUi,
@@ -150,4 +196,12 @@ public class SectionHeaderContent extends Composite {
presenter.registerTarget(imageUi);
}
public void addAccordionListener(AccordionExpansionListener listener) {
accordionListeners.add((AccordionExpansionListener) listener);
}
public boolean isExpanded() {
return expanded;
}
}
@@ -1,26 +1,40 @@
<!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.mobile.partials.sectionHeader.SectionHeaderResources" />
<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.sectionHeader.SectionHeaderResources" />
<ui:with field="shr" type="com.sap.sailing.gwt.home.shared.resources.SharedHomeResources" />
<g:HTMLPanel>
<a style="display:block;" ui:field="headerMainUi">
<div class="{local_res.css.sectionheader_item}" ui:field="headerLeftUi">
<div class="{local_res.css.sectionheader_item_image}" style="display:none;" ui:field="imageUi">
<div class="{local_res.css.sectionheader_item_image_seperator}"></div>
</div>
<div ui:field="titleAndLabelContainerUi">
<h2 class="{local_res.css.sectionheader_item_title}" ui:field="titleUi"></h2>
<div class="{local_res.css.sectionheader_item_label} {res.mainCss.label}" style="display:none;" ui:field="labelUi"></div>
</div>
<div class="{local_res.css.sectionheader_item_subtitle}" style="display:none;" ui:field="subtitleUi"></div>
</div>
<div class="{local_res.css.sectionheader_item} {local_res.css.sectionheader_itemright}" style="display:none;" ui:field="headerRightUi">
<div class="{local_res.css.sectionheader_item_infotext}" style="display:none;" ui:field="infoTextUi"></div>
<div class="{local_res.css.sectionheader_item_arrow}" style="display:none;" ui:field="actionArrowUi"></div>
<g:SimplePanel ui:field="filterSelectContainerUi"></g:SimplePanel>
</div>
</a>
</g:HTMLPanel>
<ui:style>
.headerContentPanel {
display:none;
position:absolue;
right:49px;
}
</ui:style>
<g:HTMLPanel>
<a style="display:block;" ui:field="headerMainUi">
<div class="{local_res.css.sectionheader_item}" ui:field="headerLeftUi">
<div class="{local_res.css.sectionheader_item_image}" style="display:none;" ui:field="imageUi">
<div class="{local_res.css.sectionheader_item_image_seperator}"></div>
</div>
<div ui:field="titleAndLabelContainerUi">
<h2 class="{local_res.css.sectionheader_item_title}" ui:field="titleUi"></h2>
<div class="{local_res.css.sectionheader_item_label} {res.mainCss.label}" style="display:none;"
ui:field="labelUi"></div>
</div>
<div class="{local_res.css.sectionheader_item_subtitle}" style="display:none;" ui:field="subtitleUi"></div>
</div>
<g:HTMLPanel addStyleNames="{local_res.css.sectionheader_item} {local_res.css.sectionheader_itemright}"
ui:field="headerRightUi">
<div class="{local_res.css.sectionheader_item_infotext}" style="display:none;" ui:field="infoTextUi"></div>
<div class="{local_res.css.sectionheader_item_arrow}" style="display:none;" ui:field="actionArrowUi"></div>
<g:SimplePanel ui:field="filterSelectContainerUi"></g:SimplePanel>
</g:HTMLPanel>
</a>
<g:HTMLPanel
addStyleNames="{local_res.css.sectionheader_itemright} {local_res.css.sectionheader_element}"
ui:field="headerContentUi" />
</g:HTMLPanel>
</ui:UiBinder>
@@ -34,5 +34,6 @@ public interface SectionHeaderResources extends SharedHomeResources {
String sectionheader_item_select();
String accordion();
String collapsed();
String sectionheader_element();
}
}
@@ -31,6 +31,11 @@
</p>
</g:HTMLPanel>
</a:header>
<a:item>
<login:MoreLoginInformationContentMobile
title="{i18n.moreLoginInformationSectionSailorProfilesHeading}" image="{local_res.sailorprofiles}"
content="{i18n.moreLoginInformationSectionSailorProfilesDescription}" />
</a:item>
<a:item>
<login:MoreLoginInformationContentMobile
title="{i18n.moreLoginInformationSectionUserSettingsHeading}" image="{local_res.settings}"
@@ -33,4 +33,9 @@ public abstract class AbstractUserProfileActivity extends AbstractActivity imple
return clientFactory.getNavigator().getUserSettingsNavigation();
}
@Override
public PlaceNavigation<? extends AbstractUserProfilePlace> getSailorProfilesNavigation() {
return clientFactory.getNavigator().getSailorProfilesNavigation();
}
}
@@ -43,6 +43,7 @@ public class AbstractUserProfileView extends Composite implements UserProfileVie
this.layout.quickfinderUi.addPlaceholderItem(i18n.profileQuickfinder());
this.layout.quickfinderUi.addItem(i18n.details(), presenter.getUserProfileNavigation());
this.layout.quickfinderUi.addItem(i18n.favoritesAndNotifications(), presenter.getUserPreferencesNavigation());
this.layout.quickfinderUi.addItem(i18n.sailorProfiles(), presenter.getSailorProfilesNavigation());
if(ExperimentalFeatures.SHOW_MY_SETTINGS_IN_USER_PROFILE) {
this.layout.quickfinderUi.addItem(i18n.settings(), presenter.getUserSettingsNavigation());
}
@@ -9,9 +9,11 @@ import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sailing.gwt.home.mobile.app.MobileApplicationClientFactory;
import com.sap.sailing.gwt.home.mobile.places.user.profile.details.UserProfileDetailsActivity;
import com.sap.sailing.gwt.home.mobile.places.user.profile.preferences.UserProfilePreferencesActivity;
import com.sap.sailing.gwt.home.mobile.places.user.profile.sailorprofiles.SailorProfilesActivity;
import com.sap.sailing.gwt.home.mobile.places.user.profile.settings.UserProfileSettingsActivity;
import com.sap.sailing.gwt.home.shared.places.user.profile.AbstractUserProfilePlace;
import com.sap.sailing.gwt.home.shared.places.user.profile.preferences.UserProfilePreferencesPlace;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.SailorProfilePlace;
import com.sap.sailing.gwt.home.shared.places.user.profile.settings.UserProfileSettingsPlace;
import com.sap.sailing.gwt.ui.client.FlagImageResolver;
import com.sap.sse.gwt.client.mvp.AbstractActivityProxy;
@@ -36,6 +38,8 @@ public class UserProfileActivityProxy extends AbstractActivityProxy {
withFlagImageResolver(flagImageResolver -> new UserProfilePreferencesActivity(userProfilePrefsPlace, clientFactory, flagImageResolver));
} else if(currentPlace instanceof UserProfileSettingsPlace) {
super.onSuccess(new UserProfileSettingsActivity((UserProfileSettingsPlace) currentPlace, clientFactory));
} else if (currentPlace instanceof SailorProfilePlace) {
super.onSuccess(new SailorProfilesActivity((SailorProfilePlace) currentPlace, clientFactory));
} else {
super.onSuccess(new UserProfileDetailsActivity(currentPlace, clientFactory));
}
@@ -7,10 +7,11 @@ import com.sap.sse.security.ui.authentication.app.NeedsAuthenticationContext;
import com.sap.sse.security.ui.authentication.decorator.NotLoggedInPresenter;
public interface UserProfileViewBase extends IsWidget, NeedsAuthenticationContext {
public interface Presenter extends NotLoggedInPresenter {
PlaceNavigation<? extends AbstractUserProfilePlace> getUserProfileNavigation();
PlaceNavigation<? extends AbstractUserProfilePlace> getUserPreferencesNavigation();
PlaceNavigation<? extends AbstractUserProfilePlace> getUserSettingsNavigation();
PlaceNavigation<? extends AbstractUserProfilePlace> getSailorProfilesNavigation();
}
}
@@ -0,0 +1,24 @@
package com.sap.sailing.gwt.home.mobile.places.user.profile.sailorprofiles;
import com.google.gwt.dom.client.Element;
import com.google.gwt.user.client.DOM;
import com.sap.sailing.domain.common.dto.BoatClassDTO;
import com.sap.sailing.gwt.common.client.BoatClassImageResolver;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.SharedSailorProfileResources;
public final class BoatclassElementBuilder {
private BoatclassElementBuilder() {
}
/** @return a boatclass element for mobile display with the boatclass icon and the corresponding name */
public static Element generateBoatclassElementForMobile(final BoatClassDTO boatclass) {
SailorProfileMobileResources.INSTANCE.css().ensureInjected();
Element elem = DOM.createDiv();
elem.setInnerSafeHtml(SharedSailorProfileResources.TEMPLATES.buildBoatclassIconWithName(
BoatClassImageResolver.getBoatClassIconResource(boatclass.getName()).getSafeUri().asString(),
boatclass.getName()));
elem.addClassName(SailorProfileMobileResources.INSTANCE.css().boatclassWithNameEntry());
return elem;
}
}
@@ -0,0 +1,220 @@
.eventEntryPanel {
font-size: 0.8em;
font-weight: normal;
background-color: white;
float: none;
padding-left: 2em;
padding-right: 2em;
}
.regattaEntryPanel {
font-size: 0.8em;
font-weight: normal;
}
.sumPoints {
font-weight: bold;
background-color: #f2f2f2;
border: 1px solid #cccccc;
border-radius: 2px;
padding: 0.5em;
vertical-align: middle;
display: inline-block;
}
.regattaName {
margin-top: 1em;
font-size: 1.333333em;
margin-bottom: 1em;
}
.regattaRank {
font-weight: bold;
display: inline-block;
}
.competitor {
font-weight: bold;
display: inline-block;
margin-bottom: 0.1em;
margin-left: 1em;
}
.regattaInfoLeft {
display: inline-block;
}
.regattaInfoRight {
display: inline-block;
right: 3em;
position: absolute;
vertical-align: middle;
}
.showLeaderboardButton {
display: block;
width: 100%;
margin-top: 1em;
}
.regattaSplitter1px {
margin-left: -1.66666666667em;
margin-right: -1.66666666667em;
border-bottom: 1px solid #ccc;
margin-top: 1em;
}
.detailsSectionPanel {
font-size: 1.3333em;
font-weight: bold;
margin-bottom: 3px;
margin-top: 10px;
}
.competitorWithClubnameItemDescriptionId {
display: inline-block;
font-weight: 600;
font-size: 0.8em;
min-width: 5.5em;
margin-left: 3px;
}
.competitorWithClubnameItemDescriptionClubname {
display: inline-block;
font-weight: normal;
font-size: 0.8em;
margin-left: 5px;
}
.eventTableHeader {
font-weight: normal;
font-size: 0.8em;
padding-left: 0;
text-align: center;
color: white;
background-color: #008fcc;
padding-top: 1em;
padding-bottom: 1em;
width: 100%;
position: relative;
}
.overviewPanel h3 {
font-size: 1.3333em;
font-weight: bold;
margin-bottom: 3px;
margin-top: 10px;
}
.gotoEventButton {
position: absolute;
right: 0;
margin-right: 1em;
-webkit-transform: rotate(270deg);
-moz-transform: rotate(270deg);
-ms-transform: rotate(270deg);
transform: rotate(270deg);
width: 1.33333333333em;
cursor: pointer;
display: inline-block;
}
.eventTableHeaderText {
display: inline-block;
}
.detailsEventsContainer {
margin-left: -1rem;
margin-right: -1rem;
}
.detailsCompetitorsContainer {
margin-left: -1em;
margin-right: -1em;
}
.overviewTableFooterMobile {
cursor: pointer;
border-top: 2px solid #ccc;
font-size: 1rem;
padding: 1em 1em 0;
vertical-align: middle;
width: auto;
display: block;
position: relative;
margin-left: -1em;
margin-right: -1em
}
.inlineTitleUi {
padding-left: 1em;
font-weight: bold;
background-color: #f2f2f2;
}
.editButton {
position: initial !important;
display: inline-block;
}
.footerAddButton {
width: 100%;
height: 100%;
}
.statisticsHeaderLeft {
width: 2em;
display: inline-block;
position: absolute;
left: 2em;
top: 0.666666667em;
}
.statisticsHeaderRight {
margin-left: 2em;
display: inline-block;
}
.boatclassWithNameEntry {
margin-left: -1rem;
margin-right: -1rem;
border-bottom: 1px solid #ddd;
padding-left: .5em;
padding-right: 1em;
padding-top: .25em;
padding-bottom: .25em;
}
.boatclassWithNameEntry:last-child {
border: none;
}
.detailsBoatclassesSelectionPanel {
font-size: 1.3333em;
font-weight: bold;
margin-bottom: 0;
margin-top: 0;
}
.statisticTime {
display: inline-block;
}
.statisticValue {
display: inline-block;
}
.statisticCompetitor {
margin-top: 1em;
font-weight: bold;
}
.statisticLabel {
display: inline-block;
font-weight: bold;
}
.detailsBoatclassesEmptyLabel {
font-size: 1em;
font-weight: initial;
}
@@ -0,0 +1,80 @@
package com.sap.sailing.gwt.home.mobile.places.user.profile.sailorprofiles;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
public interface SailorProfileMobileResources extends ClientBundle {
public static final SailorProfileMobileResources INSTANCE = GWT.create(SailorProfileMobileResources.class);
@Source("SailorProfileMobile.gss")
SailorProfileMobileCss css();
public interface SailorProfileMobileCss extends CssResource {
String eventEntryPanel();
String regattaEntryPanel();
String sumPoints();
String regattaName();
String regattaRank();
String competitor();
String regattaInfoLeft();
String regattaInfoRight();
String showLeaderboardButton();
String regattaSplitter1px();
String detailsSectionPanel();
String competitorWithClubnameItemDescriptionId();
String competitorWithClubnameItemDescriptionClubname();
String eventTableHeader();
String overviewPanel();
String gotoEventButton();
String eventTableHeaderText();
String detailsEventsContainer();
String overviewTableFooterMobile();
String detailsCompetitorsContainer();
String inlineTitleUi();
String editButton();
String footerAddButton();
String statisticsHeaderLeft();
String statisticsHeaderRight();
String boatclassWithNameEntry();
String detailsBoatclassesSelectionPanel();
String detailsBoatclassesEmptyLabel();
String statisticTime();
String statisticValue();
String statisticCompetitor();
String statisticLabel();
}
}
@@ -0,0 +1,122 @@
package com.sap.sailing.gwt.home.mobile.places.user.profile.sailorprofiles;
import java.util.UUID;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.dto.BoatClassDTO;
import com.sap.sailing.gwt.common.client.BoatClassImageResolver;
import com.sap.sailing.gwt.common.client.SharedResources;
import com.sap.sailing.gwt.home.communication.event.SimpleCompetitorWithIdDTO;
import com.sap.sailing.gwt.home.communication.user.profile.domain.BadgeDTO;
import com.sap.sailing.gwt.home.communication.user.profile.domain.SailorProfileDTO;
import com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab.SailingProfileOverviewPresenter;
import com.sap.sailing.gwt.home.mobile.partials.sectionHeader.SectionHeaderContent;
import com.sap.sailing.gwt.home.shared.partials.dialog.ConfirmDialogFactory;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.SailorProfilePlace;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.SharedSailorProfileResources;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback;
/** sailor profile overview entry with one sailor profile on mobile */
public class SailorProfileOverviewEntry extends Composite {
private static SailorProfileOverviewEntryUiBinder uiBinder = GWT.create(SailorProfileOverviewEntryUiBinder.class);
interface SailorProfileOverviewEntryUiBinder extends UiBinder<Widget, SailorProfileOverviewEntry> {
}
@UiField
Button detailsButtonUi;
@UiField
DivElement badgesDivUi;
@UiField
DivElement competitorsDivUi;
@UiField
DivElement boatclassesDivUi;
@UiField
SectionHeaderContent sectionTitleUi;
@UiField
HTMLPanel contentContainerCompetitorsUi;
@UiField
DivElement badgesArea;
private final UUID uuidRef;
private final SailingProfileOverviewPresenter presenter;
public SailorProfileOverviewEntry(SailorProfileDTO entry, SailingProfileOverviewPresenter presenter) {
this.presenter = presenter;
initWidget(uiBinder.createAndBindUi(this));
SailorProfileMobileResources.INSTANCE.css().ensureInjected();
this.sectionTitleUi.setSectionTitle(entry.getName());
badgesArea.getStyle().setDisplay(Display.NONE);
// add badges
for (BadgeDTO badge : entry.getBadges()) {
Element elem = DOM.createDiv();
elem.setInnerText(badge.getName());
badgesDivUi.appendChild(elem);
}
// add competitors
for (SimpleCompetitorWithIdDTO competitor : entry.getCompetitors()) {
Element elem = DOM.createDiv();
elem.setInnerText(competitor.getName());
competitorsDivUi.appendChild(elem);
}
// add boatclasses
for (BoatClassDTO boatclass : entry.getBoatclasses()) {
Element elem = DOM.createDiv();
elem.setInnerSafeHtml(SharedSailorProfileResources.TEMPLATES.buildBoatclassIcon(
BoatClassImageResolver.getBoatClassIconResource(boatclass.getName()).getSafeUri().asString()));
elem.getStyle().setDisplay(Display.INLINE_BLOCK);
boatclassesDivUi.appendChild(elem);
}
this.uuidRef = entry.getKey();
sectionTitleUi.initCollapsibility(contentContainerCompetitorsUi.getElement(), false);
final Button removeButton = new Button("X");
removeButton.addClickHandler(e -> {
e.preventDefault();
e.getNativeEvent().preventDefault();
e.getNativeEvent().stopPropagation();
ConfirmDialogFactory.showConfirmDialog(StringMessages.INSTANCE.sailorProfileRemoveMessage(),
new DialogCallback<Void>() {
@Override
public void ok(Void v) {
presenter.removeSailorProfile(uuidRef);
}
@Override
public void cancel() {
}
});
});
removeButton.addStyleName(SharedResources.INSTANCE.mainCss().buttonred());
sectionTitleUi.appendHeaderElement(removeButton);
}
@UiHandler("detailsButtonUi")
void onClick(ClickEvent e) {
presenter.getClientFactory().getPlaceController().goTo(new SailorProfilePlace(uuidRef));
}
}
@@ -0,0 +1,40 @@
<!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:p="urn:import:com.sap.sailing.gwt.home.mobile.partials"
xmlns:ms="urn:import:com.sap.sailing.gwt.home.mobile.partials.section">
<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="spmr"
type="com.sap.sailing.gwt.home.mobile.places.user.profile.sailorprofiles.SailorProfileMobileResources" />
<ui:with field="spr"
type="com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.SharedSailorProfileResources" />
<g:HTMLPanel>
<ms:MobileSection>
<ms:header>
<p:sectionHeader.SectionHeaderContent ui:field="sectionTitleUi" />
</ms:header>
<ms:content>
<g:HTMLPanel ui:field="contentContainerCompetitorsUi"
addStyleNames="{spmr.css.overviewPanel} {res.mainCss.spacermarginbottomsmall}">
<div ui:field="badgesArea">
<h3>
<ui:text from="{i18n.badges}" />
</h3>
<div ui:field="badgesDivUi" />
</div>
<h3>
<ui:text from="{i18n.competitors}" />
</h3>
<div ui:field="competitorsDivUi" />
<h3>
<ui:text from="{i18n.boatClasses}" />
</h3>
<div ui:field="boatclassesDivUi" />
<g:Button styleName="{spr.css.inverseButton}" ui:field="detailsButtonUi"
text="{i18n.showDetails}" />
</g:HTMLPanel>
</ms:content>
</ms:MobileSection>
</g:HTMLPanel>
</ui:UiBinder>
@@ -0,0 +1,120 @@
package com.sap.sailing.gwt.home.mobile.places.user.profile.sailorprofiles;
import java.util.UUID;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.AcceptsOneWidget;
import com.sap.sailing.gwt.home.communication.user.profile.domain.SailorProfilesDTO;
import com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab.SailingProfileOverviewPresenter;
import com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab.SailorProfileOverviewImplPresenter;
import com.sap.sailing.gwt.home.mobile.app.MobileApplicationClientFactory;
import com.sap.sailing.gwt.home.mobile.places.user.profile.AbstractUserProfileActivity;
import com.sap.sailing.gwt.home.mobile.places.user.profile.sailorprofiles.details.SailorProfilesDetailsViewImpl;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.ClientFactoryWithDispatchAndErrorAndUserService;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.EditSailorProfilePresenter;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.EditSailorProfileDetailsView;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.HasLoginFormAndFactory;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.SailorProfilePlace;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.view.SailorProfileView;
import com.sap.sailing.gwt.ui.client.FlagImageResolverImpl;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.security.ui.authentication.AuthenticationContextEvent;
/** main entry point for Sailor Profiles on mobile */
public class SailorProfilesActivity extends AbstractUserProfileActivity
implements SailorProfilesOverviewView.Presenter, HasLoginFormAndFactory {
private final MobileApplicationClientFactory clientFactory;
private final SailorProfileView currentView;
private final SailingProfileOverviewPresenter sailorProfileOverviewPresenter;
// redundant with sailorProfileOverviewPresenter.getSharedPresenter() --> TODO: remove
private final EditSailorProfilePresenter editSailorProfilePresenter;
private final SailorProfilePlace place;
public SailorProfilesActivity(SailorProfilePlace place, MobileApplicationClientFactory clientFactory) {
super(clientFactory);
this.place = place;
this.clientFactory = clientFactory;
editSailorProfilePresenter = new EditSailorProfilePresenter(clientFactory);
if (place.getSailorProfileUuid() == null) {
if (place.isCreateNew()) {
// no uuid & createNew -> go to details view to create new sailor profile
this.currentView = new SailorProfilesDetailsViewImpl(this);
} else {
// no uuid & !createNew -> go to overview
this.currentView = new SailorProfilesOverviewViewImpl(this);
}
} else {
// existing uuid -> go to details
this.currentView = new SailorProfilesDetailsViewImpl(this);
}
sailorProfileOverviewPresenter = new SailorProfileOverviewImplPresenter(this.currentView, this,
FlagImageResolverImpl.get(), editSailorProfilePresenter);
}
@Override
public void start(final AcceptsOneWidget panel, final EventBus eventBus) {
panel.setWidget(currentView);
if (currentView instanceof SailorProfilesViewWithAuthenticationContext) {
SailorProfilesViewWithAuthenticationContext cView = (SailorProfilesViewWithAuthenticationContext) currentView;
cView.setAuthenticationContext(clientFactory.getAuthenticationManager().getAuthenticationContext());
eventBus.addHandler(AuthenticationContextEvent.TYPE, new AuthenticationContextEvent.Handler() {
@Override
public void onUserChangeEvent(AuthenticationContextEvent event) {
cView.setAuthenticationContext(event.getCtx());
}
});
if (cView instanceof SailorProfilesOverviewView) {
SailorProfilesOverviewView overview = (SailorProfilesOverviewView) cView;
if (clientFactory.getAuthenticationManager().getAuthenticationContext().isLoggedIn()) {
sailorProfileOverviewPresenter.getSharedSailorProfilePresenter().getDataProvider()
.loadSailorProfiles(new AsyncCallback<SailorProfilesDTO>() {
@Override
public void onSuccess(SailorProfilesDTO result) {
overview.setProfileList(result.getEntries());
}
@Override
public void onFailure(Throwable caught) {
GWT.log(caught.getMessage(), caught);
}
});
}
} else if (cView instanceof EditSailorProfileDetailsView) {
EditSailorProfileDetailsView detailsView = (EditSailorProfileDetailsView) cView;
editSailorProfilePresenter.getDataProvider().setView(detailsView);
if (place.isCreateNew()) {
editSailorProfilePresenter.getDataProvider().createNewEntry(UUID.randomUUID(),
StringMessages.INSTANCE.newSailorProfileName());
} else {
editSailorProfilePresenter.getDataProvider().loadSailorProfile(place.getSailorProfileUuid());
}
}
}
}
@Override
public EditSailorProfilePresenter getSailorProfilePresenter() {
return editSailorProfilePresenter;
}
@Override
public SailingProfileOverviewPresenter getSailorProfileOverviewPresenter() {
return sailorProfileOverviewPresenter;
}
@Override
public ClientFactoryWithDispatchAndErrorAndUserService getClientFactory() {
return editSailorProfilePresenter.getClientFactory();
}
}
@@ -0,0 +1,76 @@
package com.sap.sailing.gwt.home.mobile.places.user.profile.sailorprofiles;
import java.util.Collection;
import com.google.gwt.core.client.GWT;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.common.client.SharedResources;
import com.sap.sailing.gwt.home.communication.user.profile.domain.SailorProfileDTO;
import com.sap.sailing.gwt.home.desktop.places.user.profile.sailorprofiletab.SailingProfileOverviewPresenter;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.SailorProfilePlace;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.SharedSailorProfileResources;
import com.sap.sailing.gwt.home.shared.places.user.profile.sailorprofile.view.SailorProfileOverview;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.security.ui.authentication.app.NeedsAuthenticationContext;
/**
* Implementation of {@link SailorProfileOverview} where a user can see an overview over all his sailor profiles.
*/
public class SailorProfilesOverviewImpl extends Composite implements SailorProfileOverview {
private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class);
interface MyUiBinder extends UiBinder<Widget, SailorProfilesOverviewImpl> {
}
@UiField
FlowPanel contentUi;
private SailingProfileOverviewPresenter presenter;
public SailorProfilesOverviewImpl() {
initWidget(uiBinder.createAndBindUi(this));
SailorProfileMobileResources.INSTANCE.css().ensureInjected();
SharedSailorProfileResources.INSTANCE.css().ensureInjected();
}
@Override
public void setPresenter(SailingProfileOverviewPresenter presenter) {
this.presenter = presenter;
}
@Override
public NeedsAuthenticationContext authentificationContextConsumer() {
return null;
}
@Override
public void setProfileList(Collection<SailorProfileDTO> entries) {
for (SailorProfileDTO entry : entries) {
SailorProfileOverviewEntry entryOverview = new SailorProfileOverviewEntry(entry, presenter);
contentUi.add(entryOverview);
}
createFooter();
}
private void createFooter() {
Button addButton = new Button("+ " + StringMessages.INSTANCE.addSailorProfileMessage());
SimplePanel panel = new SimplePanel(addButton);
panel.addStyleName(SailorProfileMobileResources.INSTANCE.css().overviewTableFooterMobile());
addButton.addStyleName(SharedResources.INSTANCE.mainCss().buttonprimary());
addButton.addStyleName(SharedResources.INSTANCE.mainCss().button());
addButton.addStyleName(SailorProfileMobileResources.INSTANCE.css().footerAddButton());
contentUi.add(panel);
addButton.addClickHandler((event) -> {
presenter.getClientFactory().getPlaceController().goTo(new SailorProfilePlace(true));
});
}
}
@@ -0,0 +1,7 @@
<!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="res" type="com.sap.sailing.gwt.common.client.SharedResources" />
<g:HTMLPanel addStyleNames="{res.mediaCss.grid}">
<g:FlowPanel addStyleNames="{res.mediaCss.column} {res.mediaCss.small12}" ui:field="contentUi" />
</g:HTMLPanel>
</ui:UiBinder>

Some files were not shown because too many files have changed in this diff Show More