diff --git a/home.md b/home.md index eac3ef3973a..4bbcf4a4368 100644 --- a/home.md +++ b/home.md @@ -6,6 +6,8 @@ This is the Wiki Like businesses, sailors need the latest information to make strategic decisions - but they need it even faster. One wrong tack, a false estimation of the current, or the slightest wind shift can cost the skipper the entire race. As premium sponsor of the Kieler Woche 2011, and co-sponsor of Sailing Team Germany (STG), SAP is showing how innovative IT solutions providing real time data analysis can give teams the competitive edge. +SAP is at the center of today’s technology revolution, developing innovations that not only help businesses run like never before, but also improve the lives of people everywhere. As market leader in enterprise application software, SAP SE (NYSE: SAP) helps companies of all sizes and industries run better. From back office to boardroom, warehouse to storefront, desktop to mobile device – SAP empowers people and organizations to work together more efficiently and use business insight more effectively to stay ahead of the competition. SAP applications and services enable more than 258,000 customers to operate profitably, adapt continuously, and grow sustainably. + ### Table of Contents * [[Information about this Wiki and HowTo|wiki/howto]] @@ -19,11 +21,12 @@ Like businesses, sailors need the latest information to make strategic decisions * [[Monitoring Apache and RabbitMQ|wiki/monitoring-apache-and-rabbitmq]] * [[JMX Support|wiki/jmx]] * Information for Developers - * [[Cook Book|wiki/cook-book]] * [[OnBoarding Information|wiki/onboarding]] + * [[Cook Book|wiki/cook-book]] * [[Building and Deploying|wiki/building-and-deploying]] * [[Continuous Integration with Hudson/Jenkins|wiki/ci]] * [[UI Tests with Selenium|wiki/selenium-ui-tests]] + * [[Profiling|wiki/profiling]] * [[Typical Development Scenarios|wiki/typical-development-scenarios]] * [[Load Tests|wiki/server-load-tests]] * [[Server Replication|wiki/server-replication]] @@ -37,6 +40,7 @@ Like businesses, sailors need the latest information to make strategic decisions * [[Layout repository|wiki/webdesign]] * [[Create boat graphics for the 2D race viewer|wiki/boatGraphicsSVG]] * [[Create clickable UI prototypes with Axure|wiki/ui-clickable-prototypes]] + * [[Uploading Media Content|wiki/uploading-media-content]] * General Information * [[Architecture and Infrastructure|wiki/architecture-and-infrastructure]] * [[Sailing Domain Algorithms|wiki/sailing-domain-algorithms]] @@ -51,6 +55,7 @@ Like businesses, sailors need the latest information to make strategic decisions * [[General Event Planning|wiki/general-event-planning]] * [[Information about Extreme Sailing Series|wiki/extreme-sailing-series]] * [[Travemünder Woche 2014 event page|wiki/tw2014]] + * [[505 worlds Kiel 2014 event page|wiki/505-worlds-kiel-2014]] ### Internal services (not related to wiki but useful) diff --git a/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/components/TestLeaderboardGroupFilteringRetrievalProcessor.java b/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/components/TestLeaderboardGroupFilteringRetrievalProcessor.java index b39b8004344..15ec5f0c560 100644 --- a/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/components/TestLeaderboardGroupFilteringRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/components/TestLeaderboardGroupFilteringRetrievalProcessor.java @@ -66,11 +66,11 @@ public class TestLeaderboardGroupFilteringRetrievalProcessor { private Map getGroupsInService() { Map groups = new HashMap<>(); - groups.put("LG1", new LeaderboardGroupImpl("LG1", "", false, new ArrayList())); - groups.put("LG2", new LeaderboardGroupImpl("LG2", "", false, new ArrayList())); - groups.put("LG3", new LeaderboardGroupImpl("LG3", "", false, new ArrayList())); - groups.put("LG4", new LeaderboardGroupImpl("LG4", "", false, new ArrayList())); - groups.put("LG5", new LeaderboardGroupImpl("LG5", "", false, new ArrayList())); + groups.put("LG1", new LeaderboardGroupImpl("LG1", "", /* displayName */ null, false, new ArrayList())); + groups.put("LG2", new LeaderboardGroupImpl("LG2", "", /* displayName */ null, false, new ArrayList())); + groups.put("LG3", new LeaderboardGroupImpl("LG3", "", /* displayName */ null, false, new ArrayList())); + groups.put("LG4", new LeaderboardGroupImpl("LG4", "", /* displayName */ null, false, new ArrayList())); + groups.put("LG5", new LeaderboardGroupImpl("LG5", "", /* displayName */ null, false, new ArrayList())); return groups; } @@ -86,11 +86,11 @@ public class TestLeaderboardGroupFilteringRetrievalProcessor { private Collection getExpectedRetrievedGroups() { Collection groups = new HashSet<>(); - groups.add(new LeaderboardGroupImpl("LG1", "", false, new ArrayList())); - groups.add(new LeaderboardGroupImpl("LG2", "", false, new ArrayList())); - groups.add(new LeaderboardGroupImpl("LG3", "", false, new ArrayList())); - groups.add(new LeaderboardGroupImpl("LG4", "", false, new ArrayList())); - groups.add(new LeaderboardGroupImpl("LG5", "", false, new ArrayList())); + groups.add(new LeaderboardGroupImpl("LG1", "", /* displayName */ null, false, new ArrayList())); + groups.add(new LeaderboardGroupImpl("LG2", "", /* displayName */ null, false, new ArrayList())); + groups.add(new LeaderboardGroupImpl("LG3", "", /* displayName */ null, false, new ArrayList())); + groups.add(new LeaderboardGroupImpl("LG4", "", /* displayName */ null, false, new ArrayList())); + groups.add(new LeaderboardGroupImpl("LG5", "", /* displayName */ null, false, new ArrayList())); return groups; } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/AbstractBearing.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/AbstractBearing.java index 9a89c3f3c14..24f4b293e23 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/AbstractBearing.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/AbstractBearing.java @@ -60,5 +60,10 @@ public abstract class AbstractBearing implements Bearing { return object != null && object instanceof Bearing && getDegrees() == ((Bearing) object).getDegrees(); } + @Override + public double getRadians() { + return getDegrees() / 180. * Math.PI; + } + } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java new file mode 100644 index 00000000000..4e99e41b06a --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java @@ -0,0 +1,133 @@ +package com.sap.sailing.domain.common; + +import com.sap.sailing.domain.common.impl.MeterDistance; + + +public enum BoatClassMasterdata { + _2_4M ("2.4 Meter", true, 4.11, 0.81, BoatHullType.MONOHULL, "2.4M", "2.4mR"), + _5O5 ("5O5", true, 5.03, 1.88, BoatHullType.MONOHULL, "505", "5o5", "505er", "5o5er"), + _12M ("12 Meter", true, 5.97, 1.43, BoatHullType.MONOHULL, "12M", "12mR", "12SQM"), + _29ER ("29er", true, 4.45, 1.70, BoatHullType.MONOHULL), + _29ERXX ("29erXX", true, 4.45, 1.77, BoatHullType.MONOHULL, "29XX"), + _49ER ("49er", true, 4.88, 1.93, BoatHullType.MONOHULL), + _49ERFX ("49erFX", true, 4.88, 1.93, BoatHullType.MONOHULL, "49FX"), + _420 ("420", true, 4.20, 1.65, BoatHullType.MONOHULL, "420er", "420M", "420W"), + _470 ("470", true, 4.70, 1.68, BoatHullType.MONOHULL, "470er", "470M", "470W"), + A_CAT ("A-Catamaran", true, 5.49, 2.30, BoatHullType.CATAMARAN, "A-Cat", "ACat", "A-Class Catamaran"), + ALBIN_EXPRESS ("Albin Express", true, 7.77, 2.50, BoatHullType.MONOHULL), + B_ONE ("B/ONE", true, 7.80, 2.49, BoatHullType.MONOHULL, "B-ONE"), + DRAGON_INT ("Dragon Int.", true, 8.89, 1.96, BoatHullType.MONOHULL, "Drachen", "Dragon"), + EXTREME_40 ("Extreme 40", false, 12.2, 6.60, BoatHullType.CATAMARAN, "Extreme-40", "Extreme40", "ESS40"), + EUROPE_INT ("Europe Int.", true, 3.35, 1.35, BoatHullType.MONOHULL, "Europe"), + F_18 ("Formula 18", true, 6.85, 2.25, BoatHullType.CATAMARAN, "F18", "F-18"), + FARR_30 ("Farr 30", true, 9.42, 3.08, BoatHullType.MONOHULL, "F30", "F-30", "Farr-30"), + FINN ("Finn", true, 4.50, 1.51, BoatHullType.MONOHULL), + FOLKBOAT ("Folkboat", true, 7.68, 2.20, BoatHullType.MONOHULL, "Folke", "Folkeboot"), + F_16 ("Formula 16", true, 5.00, 2.50, BoatHullType.CATAMARAN, "F16", "F-16"), + HOBIE_16 ("Hobie 16", true, 5.05, 2.41, BoatHullType.CATAMARAN, "H16"), + H_BOAT ("H-Boat", true, 8.28, 2.18, BoatHullType.MONOHULL, "HB"), + HOBIE_TIGER ("Hobie Tiger", true, 5.51, 2.60, BoatHullType.CATAMARAN), + HOBIE_WILD_CAT ("Hobie Wild Cat", true, 5.49, 2.59, BoatHullType.CATAMARAN, "Hobie Wild Cat F18"), + J70 ("J/70", true, 6.93, 2.25, BoatHullType.MONOHULL, "J70", "J-70"), + J80 ("J/80", true, 8.0, 2.51, BoatHullType.MONOHULL, "J80", "J-80"), + J24 ("J/24", true, 7.32, 2.67, BoatHullType.MONOHULL, "J24", "J-24"), + KITE ("Kite", true, 3.35, 1.52, BoatHullType.MONOHULL), // + LASER_4_7 ("Laser 4.7", true, 4.20, 1.39, BoatHullType.MONOHULL, "L4.7"), + LASER_RADIAL ("Laser Radial", true, 4.19, 1.41, BoatHullType.MONOHULL, "LAR", "Laser RAD", "RAD", "Radial"), + LASER_INT ("Laser Int.", true, 4.19, 1.39, BoatHullType.MONOHULL, "Laser", "LSR"), + LASER_SB3 ("Laser SB3", true, 6.15, 2.15, BoatHullType.MONOHULL, "LSB3", "SB20"), + MELGES_24 ("Melges 24", true, 7.32, 2.50, BoatHullType.MONOHULL, "Melges-24", "M24"), + MINI_TRANSAT ("Mini Transat 6.50", true, 6.50, 3.00, BoatHullType.MONOHULL, "Mini Transat"), + MUSTO_SKIFF ("Musto Skiff", true, 4.55, 1.35, BoatHullType.MONOHULL, "Musto Performance Skiff", "MPS", "Musto"), + NACRA_17 ("Nacra 17", true, 5.25, 2.59, BoatHullType.CATAMARAN, "N17", "Nacra-17"), + OK ("OK Dinghy", true, 5.25, 2.59, BoatHullType.MONOHULL, "OK-Dinghy", "OK-Jolle", "OK"), + OPTIMIST ("Optimist", true, 2.34, 1.07, BoatHullType.MONOHULL, "Opti", "Optimist Dinghy"), + PLATU_25 ("Platu 25", true, 7.53, 2.62, BoatHullType.MONOHULL, "Platu", "Platu-25", "PLA", "B25"), + RS_X ("RS:X", true, 2.86, 0.93, BoatHullType.SURFERBOARD, "RS-X", "RSX", "RS:X"), + SONAR ("Sonar", true, 7.01, 2.39, BoatHullType.MONOHULL), + STAR ("Star", true, 6.92, 1.74, BoatHullType.MONOHULL), + STREAMLINE ("Streamline", true, 7.15, 2.55, BoatHullType.MONOHULL), + SWAN_45 ("Swan 45", true, 13.83, 3.91, BoatHullType.MONOHULL, "Swan", "Swan-45"), + //TODO: which boatclasses of swan are important (there are several ones) + TORNADO ("Tornado Catamaran", true, 6.10, 3.02, BoatHullType.CATAMARAN, "Tornado", "Tornado Cat"), + X_99 ("X-99", true, 9.96, 2.95, BoatHullType.MONOHULL, "X99"), + CONTENDER ("Contender", true, 4.88, 1.42, BoatHullType.MONOHULL), + RC44 ("RC44", true, 13.35, 2.75, BoatHullType.MONOHULL), + FLYING_DUTCHMAN ("Flying Dutchman", true, 6.10, 1.80, BoatHullType.MONOHULL), + DYAS("Dyas", true, 7.15, 1.95, BoatHullType.MONOHULL), + INTERNATIONAL_14("International 14", true, 4.27, 1.83, BoatHullType.MONOHULL, "I14"), + OPEN_BIC("O'pen BIC", true, 2.75, 1.14, BoatHullType.MONOHULL, "OpenBIC"); + + private final String displayName; + private final String[] alternativeNames; + private final double hullLengthInMeter; + private final double hullBeamInMeter; + private final BoatHullType hullType; + private final boolean typicallyStartsUpwind; + + private BoatClassMasterdata(String displayName, boolean typicallyStartsUpwind, double hullLengthInMeter, + double hullBeamInMeter, BoatHullType hullType, String... alternativeNames) { + this.displayName = displayName; + this.typicallyStartsUpwind = typicallyStartsUpwind; + this.hullLengthInMeter = hullLengthInMeter; + this.hullBeamInMeter = hullBeamInMeter; + this.hullType = hullType; + this.alternativeNames = alternativeNames; + } + + private BoatClassMasterdata(String displayName, boolean typicallyStartsUpwind, double hullLengthInMeter, + double hullBeamInMeter, BoatHullType hullType) { + this.displayName = displayName; + this.typicallyStartsUpwind = typicallyStartsUpwind; + this.hullLengthInMeter = hullLengthInMeter; + this.hullBeamInMeter = hullBeamInMeter; + this.hullType = hullType; + this.alternativeNames = null; + } + + public static BoatClassMasterdata resolveBoatClass(String boatClassName) { + String boatClassNameToResolve = unifyBoatClassName(boatClassName); + + for (BoatClassMasterdata boatClass : values()) { + if (unifyBoatClassName(boatClass.displayName).equals(boatClassNameToResolve)) { + return boatClass; + } + if(boatClass.alternativeNames != null) { + for (String name : boatClass.alternativeNames) { + if (unifyBoatClassName(name).equals(boatClassNameToResolve)) { + return boatClass; + } + } + } + } + return null; + } + + private static String unifyBoatClassName(String boatClassName) { + return boatClassName.toUpperCase().replaceAll("\\s+",""); + } + + public Distance getHullLength() { + return new MeterDistance(hullLengthInMeter); + } + + public String getDisplayName() { + return displayName; + } + + public String[] getAlternativeNames() { + return alternativeNames == null ? new String[0] : alternativeNames; + } + + public Distance getHullBeam() { + return new MeterDistance(hullBeamInMeter); + } + + public BoatHullType getHullType() { + return hullType; + } + + public boolean isTypicallyStartsUpwind() { + return typicallyStartsUpwind; + } +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatHullType.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatHullType.java new file mode 100644 index 00000000000..e793bf5c32e --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatHullType.java @@ -0,0 +1,5 @@ +package com.sap.sailing.domain.common; + +public enum BoatHullType { + MONOHULL, CATAMARAN, TRIMARAN, SURFERBOARD +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Bounds.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Bounds.java index 60a876a4f77..19958951201 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Bounds.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Bounds.java @@ -2,17 +2,35 @@ package com.sap.sailing.domain.common; public interface Bounds { Position getNorthEast(); + Position getSouthWest(); + Position getNorthWest(); + Position getSouthEast(); - + Bounds intersect(Bounds other); + + /** + * A short-hand for {@link #union} + */ + Bounds extend(Bounds other); + + Bounds extend(Position p); + Bounds union(Bounds other); + boolean intersects(Bounds other); + boolean contains(Position other); + boolean contains(Bounds other); + boolean isCrossesDateLine(); + boolean containsLatDeg(double latDeg); + boolean containsLngDeg(double lngDeg); + boolean isEmpty(); } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ImageSize.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ImageSize.java new file mode 100755 index 00000000000..fb1182e2ec9 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ImageSize.java @@ -0,0 +1,8 @@ +package com.sap.sailing.domain.common; + +import java.io.Serializable; + +public interface ImageSize extends Serializable { + int getWidth(); + int getHeight(); +} \ No newline at end of file diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ScoringSchemeType.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ScoringSchemeType.java index ee5150bff9b..ef368469292 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ScoringSchemeType.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ScoringSchemeType.java @@ -4,5 +4,6 @@ public enum ScoringSchemeType { LOW_POINT, HIGH_POINT, HIGH_POINT_ESS_OVERALL, HIGH_POINT_LAST_BREAKS_TIE, HIGH_POINT_FIRST_GETS_TEN, HIGH_POINT_FIRST_GETS_ONE, LOW_POINT_WINNER_GETS_ZERO, HIGH_POINT_WINNER_GETS_SIX, HIGH_POINT_WINNER_GETS_FIVE, HIGH_POINT_FIRST_GETS_TEN_OR_EIGHT, - HIGH_POINT_WINNER_GETS_FIVE_IGNORING_RACE_COUNT + HIGH_POINT_WINNER_GETS_FIVE_IGNORING_RACE_COUNT, + HIGH_POINT_WINNER_GETS_SIX_IGNORING_RACE_COUNT } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/AbstractLeaderboardDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/AbstractLeaderboardDTO.java index 8786609a8a8..e9231cde87f 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/AbstractLeaderboardDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/AbstractLeaderboardDTO.java @@ -41,8 +41,10 @@ public abstract class AbstractLeaderboardDTO implements Serializable { public Set getBoatClasses() { Set result = new HashSet(); - for (CompetitorDTO competitor : rows.keySet()) { - result.add(competitor.getBoatClass()); + if(rows != null) { + for (CompetitorDTO competitor : rows.keySet()) { + result.add(competitor.getBoatClass()); + } } return result; } @@ -121,16 +123,6 @@ public abstract class AbstractLeaderboardDTO implements Serializable { return getRaceColumnByName(raceColumnName).isMedalRace(); } - private RaceColumnDTO getOrCreateRaceColumn(String raceColumnName) { - RaceColumnDTO result = getRaceColumnByName(raceColumnName); - if (result == null) { - result = new RaceColumnDTO(/* isValidInTotalScore */ true); - result.setName(raceColumnName); - races.add(result); - } - return result; - } - /** * If the {@link RaceColumnDTO} by the name raceColumnName doesn't exist yet within this leaderboard * DTO, it is created, setting is {@link RaceColumnDTO#isValidInTotalScore()} to true. This method @@ -144,15 +136,26 @@ public abstract class AbstractLeaderboardDTO implements Serializable { * @param effectiveFactor * is what you get when you call {@link RaceColumn#getFactor()} on the race column that the resulting * {@link RaceColumnDTO} represents + * @param regattaName + * must not be null if seriesName is not null; specified + * separately from trackedRaceIdentifier because a column may belong to regatta/series + * despite not having a tracked race associated + * @param seriesName + * if null, this method will produce a {@link RaceColumnDTO}, otherwise a + * {@link RaceColumnInSeriesDTO} * @param fleetDTO * must not be null */ public RaceColumnDTO addRace(String raceColumnName, Double explicitFactor, double effectiveFactor, - FleetDTO fleetDTO, boolean medalRace, RegattaAndRaceIdentifier trackedRaceIdentifier, RaceDTO race) { + String regattaName, String seriesName, FleetDTO fleetDTO, boolean medalRace, RegattaAndRaceIdentifier trackedRaceIdentifier, RaceDTO race) { assert fleetDTO != null; - RaceColumnDTO raceColumnDTO = getOrCreateRaceColumn(raceColumnName); + RaceColumnDTO raceColumnDTO = getRaceColumnByName(raceColumnName); + if (raceColumnDTO == null) { + raceColumnDTO = RaceColumnDTOFactory.INSTANCE.createRaceColumnDTO(raceColumnName, medalRace, + explicitFactor, regattaName, seriesName); + races.add(raceColumnDTO); + } raceColumnDTO.setEffectiveFactor(effectiveFactor); - raceColumnDTO.setExplicitFactor(explicitFactor); boolean contains = false; for (FleetDTO fleet : raceColumnDTO.getFleets()) { if (fleet.getName().equals(fleetDTO.getName())) { @@ -163,16 +166,14 @@ public abstract class AbstractLeaderboardDTO implements Serializable { if (!contains) { raceColumnDTO.addFleet(fleetDTO); } - raceColumnDTO.setMedalRace(medalRace); raceColumnDTO.setRaceIdentifier(fleetDTO, trackedRaceIdentifier); raceColumnDTO.setRace(fleetDTO, race); return raceColumnDTO; } - public RaceColumnDTO createEmptyRaceColumn(String raceColumnName, boolean medalRace, boolean isValidInTotalScore) { - RaceColumnDTO raceColumn = new RaceColumnDTO(isValidInTotalScore); - raceColumn.setName(raceColumnName); - raceColumn.setMedalRace(medalRace); + public RaceColumnDTO createEmptyRaceColumn(String raceColumnName, boolean medalRace, String regattaName, String seriesName) { + final RaceColumnDTO raceColumn = RaceColumnDTOFactory.INSTANCE.createRaceColumnDTO(raceColumnName, + medalRace, /* explicit factor */ null, regattaName, seriesName); races.add(raceColumn); return raceColumn; } @@ -186,6 +187,30 @@ public abstract class AbstractLeaderboardDTO implements Serializable { return null; } + public int getRaceColumnsCount() { + return races.size(); + } + + public int getRacesCount() { + int result = 0; + for (RaceColumnDTO race : getRaceList()) { + result += race.getFleets().size(); + } + return result; + } + + public int getTrackedRacesCount() { + int result = 0; + for (RaceColumnDTO race : getRaceList()) { + for (FleetDTO fleet : race.getFleets()) { + if (race.isTrackedRace(fleet)) { + result++; + } + } + } + return result; + } + public List getRaceList() { return races; } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/BoatClassDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/BoatClassDTO.java index 3577bd4ea37..9648c8cdd59 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/BoatClassDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/BoatClassDTO.java @@ -5,15 +5,26 @@ import java.io.Serializable; public class BoatClassDTO extends NamedDTO implements Serializable { private static final long serialVersionUID = 1981789833769906676L; private double hullLengthInMeters; + private String displayName; BoatClassDTO() {} + public BoatClassDTO(String name, String displayName, double hullLengthInMeters) { + super(name); + this.hullLengthInMeters = hullLengthInMeters; + } + public BoatClassDTO(String name, double hullLengthInMeters) { super(name); this.hullLengthInMeters = hullLengthInMeters; + this.displayName = null; } public double getHullLengthInMeters() { return hullLengthInMeters; } + + public String getDisplayName() { + return displayName; + } } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardRowDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardRowDTO.java index 3598bf5affa..b7e0ddbdcc4 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardRowDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardRowDTO.java @@ -116,4 +116,9 @@ public class LeaderboardRowDTO implements Serializable { return false; return true; } + + @Override + public String toString() { + return "LeaderboardRowDTO for competitor "+competitor; + } } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceColumnDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceColumnDTO.java index c399d6dbdf6..37262e14cfc 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceColumnDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceColumnDTO.java @@ -9,9 +9,11 @@ import java.util.List; import java.util.Map; import java.util.Set; +import com.sap.sailing.domain.common.LeaderboardNameConstants; import com.sap.sailing.domain.common.RaceIdentifier; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.TimingConstants; +import com.sap.sse.common.Util; public class RaceColumnDTO extends NamedDTO implements Serializable { private static final long serialVersionUID = -3228244237400937852L; @@ -19,25 +21,17 @@ public class RaceColumnDTO extends NamedDTO implements Serializable { private List fleets; private Map trackedRaceIdentifiersPerFleet; private Map racesPerFleet; - private Boolean isValidInTotalScore; private Double explicitFactor; private double effectiveFactor; private Map raceLogTrackingInfos = new HashMap(); - RaceColumnDTO() {} // for GWT serialization - - public RaceColumnDTO(Boolean isValidInTotalScore) { - this.isValidInTotalScore = isValidInTotalScore; + public RaceColumnDTO() { trackedRaceIdentifiersPerFleet = new HashMap(); racesPerFleet = new HashMap(); fleets = new ArrayList(); } - public boolean isValidInTotalScore() { - return isValidInTotalScore; - } - public String getRaceColumnName() { return getName(); } @@ -54,6 +48,17 @@ public class RaceColumnDTO extends NamedDTO implements Serializable { return trackedRaceIdentifiersPerFleet.values().contains(raceIdentifier); } + public FleetDTO getFleet(RaceIdentifier raceIdentifier) { + FleetDTO result = null; + for (FleetDTO identifier : trackedRaceIdentifiersPerFleet.keySet()) { + if (Util.equalsWithNull(trackedRaceIdentifiersPerFleet.get(identifier), raceIdentifier)) { + result = identifier; + break; + } + } + return result; + } + public boolean isMedalRace() { return medalRace; } @@ -93,7 +98,7 @@ public class RaceColumnDTO extends NamedDTO implements Serializable { this.racesPerFleet.put(fleet, race); } - public Iterable getFleets() { + public List getFleets() { return fleets; } @@ -178,20 +183,18 @@ public class RaceColumnDTO extends NamedDTO implements Serializable { return result; } - /** - * Returns live state for the current server time - * @see RaceColumnDTO#isLive(FleetDTO, long) - * @param fleet - * @return - */ - public boolean isLiveInServerTime(FleetDTO fleet) { - return isLive(fleet, 0); - } - public boolean containsRace(RaceIdentifier preSelectedRace) { return trackedRaceIdentifiersPerFleet.values().contains(preSelectedRace); } + public boolean hasLiveRaces(long serverTimePointAsMillis) { + boolean result = false; + for(FleetDTO fleet: fleets) { + result |= isLive(fleet, serverTimePointAsMillis); + } + return result; + } + public boolean hasTrackedRaces() { Set raceIdentifiers = new HashSet(trackedRaceIdentifiersPerFleet.values()); raceIdentifiers.remove(null); @@ -229,7 +232,6 @@ public class RaceColumnDTO extends NamedDTO implements Serializable { result = prime * result + (int) (temp ^ (temp >>> 32)); result = prime * result + ((explicitFactor == null) ? 0 : explicitFactor.hashCode()); result = prime * result + ((fleets == null) ? 0 : fleets.hashCode()); - result = prime * result + ((isValidInTotalScore == null) ? 0 : isValidInTotalScore.hashCode()); result = prime * result + (medalRace ? 1231 : 1237); result = prime * result + ((racesPerFleet == null) ? 0 : racesPerFleet.hashCode()); result = prime * result @@ -258,11 +260,6 @@ public class RaceColumnDTO extends NamedDTO implements Serializable { return false; } else if (!fleets.equals(other.fleets)) return false; - if (isValidInTotalScore == null) { - if (other.isValidInTotalScore != null) - return false; - } else if (!isValidInTotalScore.equals(other.isValidInTotalScore)) - return false; if (medalRace != other.medalRace) return false; if (racesPerFleet == null) { @@ -293,4 +290,8 @@ public class RaceColumnDTO extends NamedDTO implements Serializable { public void setRaceLogTrackingInfo(FleetDTO fleet, RaceLogTrackingInfoDTO info) { raceLogTrackingInfos.put(fleet, info); } + + public String getSeriesName() { + return LeaderboardNameConstants.DEFAULT_SERIES_NAME; + } } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceColumnDTOFactory.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceColumnDTOFactory.java new file mode 100755 index 00000000000..7d8a0efbb67 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceColumnDTOFactory.java @@ -0,0 +1,29 @@ +package com.sap.sailing.domain.common.dto; + +public class RaceColumnDTOFactory { + public static final RaceColumnDTOFactory INSTANCE = new RaceColumnDTOFactory(); + + /** + * @param regattaName + * must not be null if seriesName is not null + * @param seriesName + * when null, a regular {@link RaceColumnDTO} will be produced and initialized from the + * properties passed; otherwise, a {@link RaceColumnInSeriesDTO} will be created. + */ + public RaceColumnDTO createRaceColumnDTO(String columnName, boolean isMedal, Double explicitFactor, String regattaName, String seriesName) { + final RaceColumnDTO raceColumnDTO; + if (seriesName != null) { + raceColumnDTO = new RaceColumnInSeriesDTO(seriesName, regattaName); + } else { + raceColumnDTO = new RaceColumnDTO(); + } + fillRaceColumnDTO(raceColumnDTO, columnName, isMedal, explicitFactor); + return raceColumnDTO; + } + + private void fillRaceColumnDTO(RaceColumnDTO raceColumnDTO, String columnName, boolean isMedal, Double explicitFactor) { + raceColumnDTO.setName(columnName); + raceColumnDTO.setMedalRace(isMedal); + raceColumnDTO.setExplicitFactor(explicitFactor); + } +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceColumnInSeriesDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceColumnInSeriesDTO.java index dd9f5fd6056..9b4291b1d0f 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceColumnInSeriesDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceColumnInSeriesDTO.java @@ -45,6 +45,7 @@ public class RaceColumnInSeriesDTO extends RaceColumnDTO { return true; } + @Override public String getSeriesName() { return seriesName; } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceDTO.java index 794ea1f1e20..42798eb83c0 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceDTO.java @@ -28,6 +28,7 @@ public class RaceDTO extends NamedDTO { public PlacemarkOrderDTO places; public TrackedRaceDTO trackedRace; + public TrackedRaceStatisticsDTO trackedRaceStatistics; private String regattaName; public String boatClass; diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/TrackedRaceStatisticsDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/TrackedRaceStatisticsDTO.java new file mode 100644 index 00000000000..01903a448de --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/TrackedRaceStatisticsDTO.java @@ -0,0 +1,38 @@ +package com.sap.sailing.domain.common.dto; + +import java.io.Serializable; + +import com.sap.sailing.domain.common.Duration; + +public class TrackedRaceStatisticsDTO implements Serializable { + private static final long serialVersionUID = -2123085927945474642L; + + /** Indicators if statistical data is available for a date or not */ + public boolean hasGPSData; + public boolean hasAudioData; + public boolean hasVideoData; + public boolean hasMeasuredWindData; + public boolean hasLegProgressData; + public boolean hasLeaderOrWinnerData; + + public Duration averageGPSDataSampleInterval; + public Integer measuredWindSourcesCount; + public Duration averageWindDataSampleInterval; + public Integer audioTracksCount; + public Integer videoTracksCount; + + public Integer totalLegsCount; + public Integer currentLegNo; + public CompetitorDTO leaderOrWinner; + /** + * Default constructor for GWT-Serialization + */ + public TrackedRaceStatisticsDTO() { + hasGPSData = false; + hasAudioData= false; + hasVideoData= false; + hasMeasuredWindData = false; + hasLegProgressData = false; + hasLeaderOrWinnerData = false; + } +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/BoundsImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/BoundsImpl.java index 5ef09a1da53..86784eb8cd3 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/BoundsImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/BoundsImpl.java @@ -98,6 +98,16 @@ public class BoundsImpl implements Bounds { } return new BoundsImpl(new DegreePosition(minLatDeg, bestWest), new DegreePosition(maxLatDeg, bestEast)); } + + @Override + public Bounds extend(Bounds other) { + return union(other); + } + + @Override + public Bounds extend(Position p) { + return union(new BoundsImpl(p, p)); + } private boolean spansLngDeg(double westLngDeg, double eastLngDeg, double lngDeg) { return isCrossingDateLine(westLngDeg, eastLngDeg) diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/DegreeBearingImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/DegreeBearingImpl.java index 63061a72f50..60ff5fd53f6 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/DegreeBearingImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/DegreeBearingImpl.java @@ -20,10 +20,5 @@ public class DegreeBearingImpl extends AbstractBearing implements Bearing { public double getDegrees() { return bearingDeg; } - - @Override - public double getRadians() { - return getDegrees() / 180. * Math.PI; - } } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/ImageSizeImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/ImageSizeImpl.java new file mode 100755 index 00000000000..e033e665001 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/ImageSizeImpl.java @@ -0,0 +1,58 @@ +package com.sap.sailing.domain.common.impl; + +import com.sap.sailing.domain.common.ImageSize; + +public class ImageSizeImpl implements ImageSize { + private static final long serialVersionUID = 1170701774852068780L; + private int width; + private int height; + + ImageSizeImpl() { + } // for GWT serialization + + public ImageSizeImpl(int width, int height) { + super(); + this.width = width; + this.height = height; + } + + @Override + public int getWidth() { + return width; + } + + @Override + public int getHeight() { + return height; + } + + @Override + public String toString() { + return "(" + getWidth() + "x" + getHeight() + ")"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + height; + result = prime * result + width; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ImageSizeImpl other = (ImageSizeImpl) obj; + if (height != other.height) + return false; + if (width != other.width) + return false; + return true; + } +} diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/shared/IgtimiWindReceiver.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/shared/IgtimiWindReceiver.java index c0b114ed3ac..4cd68bc1f7f 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/shared/IgtimiWindReceiver.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/shared/IgtimiWindReceiver.java @@ -220,9 +220,13 @@ public class IgtimiWindReceiver implements BulkFixReceiver { Speed sog = getSOG(timePoint, sogPair); com.sap.sse.common.Util.Pair cogPair = getSurroundingFixes(getCogTrack(deviceSerialNumber), timePoint); Bearing cog = getCOG(timePoint, cogPair); - SpeedWithBearing sogCog = new KnotSpeedWithBearingImpl(sog.getKnots(), cog); - SpeedWithBearing trueWindSpeedAndDirection = apparentWindSpeedWithDirection.add(sogCog); - result = new WindImpl(pos, timePoint, trueWindSpeedAndDirection); + if (sog != null && cog != null) { + SpeedWithBearing sogCog = new KnotSpeedWithBearingImpl(sog.getKnots(), cog); + SpeedWithBearing trueWindSpeedAndDirection = apparentWindSpeedWithDirection.add(sogCog); + result = new WindImpl(pos, timePoint, trueWindSpeedAndDirection); + } else { + result = null; + } } else { result = null; } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java index e80ea00f53a..17e281c0d00 100755 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java @@ -713,6 +713,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { // migration: leaderboard groups that don't yet have a UUID receive a random one } String description = (String) o.get(FieldNames.LEADERBOARD_GROUP_DESCRIPTION.name()); + String displayName = (String) o.get(FieldNames.LEADERBOARD_GROUP_DISPLAY_NAME.name()); boolean displayGroupsInReverseOrder = false; // default value Object displayGroupsInReverseOrderObj = o.get(FieldNames.LEADERBOARD_GROUP_DISPLAY_IN_REVERSE_ORDER.name()); if (displayGroupsInReverseOrderObj != null) { @@ -734,7 +735,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { } } logger.info("loaded leaderboard group "+name); - LeaderboardGroupImpl result = new LeaderboardGroupImpl(uuid, name, description, displayGroupsInReverseOrder, leaderboards); + LeaderboardGroupImpl result = new LeaderboardGroupImpl(uuid, name, description, displayName, displayGroupsInReverseOrder, leaderboards); Object overallLeaderboardIdOrName = o.get(FieldNames.LEADERBOARD_GROUP_OVERALL_LEADERBOARD.name()); if (overallLeaderboardIdOrName != null) { final DBObject dbOverallLeaderboard; @@ -1425,7 +1426,13 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { private RaceLogEvent loadRaceLogAdditionalScoringInformationEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { - AdditionalScoringInformationType informationType = AdditionalScoringInformationType.valueOf(dbObject.get(FieldNames.RACE_LOG_ADDITIONAL_SCORING_INFORMATION_TYPE.name()).toString()); + Object additionalScoringInformationTypeInfo = dbObject.get(FieldNames.RACE_LOG_ADDITIONAL_SCORING_INFORMATION_TYPE.name()); + AdditionalScoringInformationType informationType = AdditionalScoringInformationType.UNKNOWN; + if (additionalScoringInformationTypeInfo != null) { + informationType = AdditionalScoringInformationType.valueOf(additionalScoringInformationTypeInfo.toString()); + } else { + logger.warning("Could not find additional scoring information attached to db log for " + dbObject.toString()); + } return raceLogEventFactory.createAdditionalScoringInformationEvent(createdAt, author, logicalTimePoint, id, competitors, passId, informationType); } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java index cd44dd0309a..189fefa5462 100755 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java @@ -71,7 +71,8 @@ public enum FieldNames { LEADERBOARD_SCORE_CORRECTION_COMMENT, LEADERBOARD_COLUMN_FACTORS, // leaderboard group - LEADERBOARD_GROUP_UUID, LEADERBOARD_GROUP_NAME, LEADERBOARD_GROUP_DESCRIPTION, LEADERBOARD_GROUP_LEADERBOARDS, LEADERBOARD_GROUP_OVERALL_LEADERBOARD, + LEADERBOARD_GROUP_UUID, LEADERBOARD_GROUP_NAME, LEADERBOARD_GROUP_DESCRIPTION, + LEADERBOARD_GROUP_DISPLAY_NAME, LEADERBOARD_GROUP_LEADERBOARDS, LEADERBOARD_GROUP_OVERALL_LEADERBOARD, LEADERBOARD_GROUP_DISPLAY_IN_REVERSE_ORDER, // race ID to regatta name diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java index 1c95ae3b0c7..fa9302e3dcb 100755 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java @@ -442,6 +442,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_UUID.name(), leaderboardGroup.getId()); dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_NAME.name(), leaderboardGroup.getName()); dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_DESCRIPTION.name(), leaderboardGroup.getDescription()); + dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_DISPLAY_NAME.name(), leaderboardGroup.getDisplayName()); dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_DISPLAY_IN_REVERSE_ORDER.name(), leaderboardGroup.isDisplayGroupsInReverseOrder()); final Leaderboard overallLeaderboard = leaderboardGroup.getOverallLeaderboard(); if (overallLeaderboard != null) { diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/BoatClass.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/BoatClass.java index 21ce155598e..c1bfbe481c0 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/BoatClass.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/BoatClass.java @@ -1,5 +1,6 @@ package com.sap.sailing.domain.base; +import com.sap.sailing.domain.common.BoatHullType; import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.TimePoint; import com.sap.sse.common.Named; @@ -37,9 +38,15 @@ public interface BoatClass extends Named, IsManagedBySharedDomainFactory { * start with a non-upwind leg even though this method returns true. */ boolean typicallyStartsUpwind(); - - Distance getHullLength(); + String getDisplayName(); + + Distance getHullLength(); + + Distance getHullBeam(); + + BoatHullType getHullType(); + /** * Downwind leg-based wind estimations are inherently less confident than upwind leg-based estimations because * jibing angles vary more greatly from boat to boat than tacking angles. diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/EventBase.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/EventBase.java index 5c9beb76386..db395fc2984 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/EventBase.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/EventBase.java @@ -1,7 +1,9 @@ package com.sap.sailing.domain.base; import java.net.URL; +import java.util.concurrent.ExecutionException; +import com.sap.sailing.domain.common.ImageSize; import com.sap.sailing.domain.common.Renamable; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.WithID; @@ -61,6 +63,14 @@ public interface EventBase extends Named, WithDescription, Renamable, WithID { void removeSponsorImageURL(URL sponsorImageURL); + /** + * Replaces the {@link #getSponsorImageURLs() current contents of the sponsorship image URL sequence} by the image URLs in + * sponsorImageURLs. + * + * @param sponsorImageURLs + * if null, the internal sequence of sponsorship image URLs is cleared but remains valid (non- + * null) + */ void setSponsorImageURLs(Iterable sponsorImageURLs); /** @@ -111,4 +121,10 @@ public interface EventBase extends Named, WithDescription, Renamable, WithID { void setOfficialWebsiteURL(URL officialWebsiteURL); Iterable getLeaderboardGroups(); + + /** + * For the images references by the image URLs in {@link #getImageURLs()}, {@link #getSponsorImageURLs()} and {@link #getLogoImageURL()} + * determines the image dimensions. + */ + ImageSize getImageSize(URL imageURL) throws InterruptedException, ExecutionException; } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/LeaderboardGroupBase.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/LeaderboardGroupBase.java index a73fe817219..0bf8ea7e4e1 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/LeaderboardGroupBase.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/LeaderboardGroupBase.java @@ -10,4 +10,6 @@ public interface LeaderboardGroupBase extends Renamable, WithID { String getDescription(); void setDescriptiom(String description); boolean hasOverallLeaderboard(); + String getDisplayName(); + void setDisplayName(String displayName); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/BoatClassImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/BoatClassImpl.java index 835b54c7510..1b213785fef 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/BoatClassImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/BoatClassImpl.java @@ -2,6 +2,8 @@ package com.sap.sailing.domain.base.impl; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.SharedDomainFactory; +import com.sap.sailing.domain.common.BoatClassMasterdata; +import com.sap.sailing.domain.common.BoatHullType; import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.impl.MeterDistance; import com.sap.sailing.domain.common.impl.NamedImpl; @@ -35,18 +37,35 @@ public class BoatClassImpl extends NamedImpl implements BoatClass { private final long approximateManeuverDurationInMilliseconds; private final boolean typicallyStartsUpwind; + + private final String displayName; private final Distance hullLength; + + private final Distance hullBeam; + + private final BoatHullType hullType; public BoatClassImpl(String name, boolean typicallyStartsUpwind) { - super(name); - this.typicallyStartsUpwind = typicallyStartsUpwind; - approximateManeuverDurationInMilliseconds = 8000; // as discussed with Dennis Gehrlein - // TODO see bug 911: these values need to come from a master data base - hullLength = name.toLowerCase().contains("extreme") && name.contains("40") - ? new MeterDistance(40*12*2.54/100) - : new MeterDistance(5); // a good average for the olympic classes... + this(name, typicallyStartsUpwind, /* displayName */ null, + /* hull length */ new MeterDistance(5), /* hullBeam */ null, /* hullType */ null); } + + public BoatClassImpl(String name, BoatClassMasterdata masterData) { + this(name, masterData.isTypicallyStartsUpwind(), masterData.getDisplayName(), masterData.getHullLength(), + masterData.getHullBeam(), masterData.getHullType()); + } + + public BoatClassImpl(String name, boolean typicallyStartsUpwind, String displayName, + Distance hullLength, Distance hullBeam, BoatHullType hullType) { + super(name); + this.typicallyStartsUpwind = typicallyStartsUpwind; + this.displayName = displayName; + this.hullLength = hullLength; + this.hullBeam = hullBeam; + this.hullType = hullType; + approximateManeuverDurationInMilliseconds = 8000; // as discussed with Dennis Gehrlein + } @Override public long getApproximateManeuverDurationInMilliseconds() { @@ -110,4 +129,16 @@ public class BoatClassImpl extends NamedImpl implements BoatClass { return domainFactory.getOrCreateBoatClass(getName(), typicallyStartsUpwind()); } + public String getDisplayName() { + return displayName; + } + + public Distance getHullBeam() { + return hullBeam; + } + + public BoatHullType getHullType() { + return hullType; + } + } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/EventBaseImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/EventBaseImpl.java index bf5a1af542d..acb5610715d 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/EventBaseImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/EventBaseImpl.java @@ -146,7 +146,9 @@ public abstract class EventBaseImpl implements EventBase { @Override public void setImageURLs(Iterable imageURLs) { this.imageURLs.clear(); - Util.addAll(imageURLs, this.imageURLs); + if (imageURLs != null) { + Util.addAll(imageURLs, this.imageURLs); + } } @Override @@ -169,7 +171,9 @@ public abstract class EventBaseImpl implements EventBase { @Override public void setVideoURLs(Iterable videoURLs) { this.videoURLs.clear(); - Util.addAll(videoURLs, this.videoURLs); + if (videoURLs != null) { + Util.addAll(videoURLs, this.videoURLs); + } } @Override @@ -192,7 +196,9 @@ public abstract class EventBaseImpl implements EventBase { @Override public void setSponsorImageURLs(Iterable sponsorImageURLs) { this.sponsorImageURLs.clear(); - Util.addAll(sponsorImageURLs, this.sponsorImageURLs); + if (sponsorImageURLs != null) { + Util.addAll(sponsorImageURLs, this.sponsorImageURLs); + } } @Override @@ -214,4 +220,5 @@ public abstract class EventBaseImpl implements EventBase { public void setOfficialWebsiteURL(URL officialWebsiteURL) { this.officialWebsiteURL = officialWebsiteURL; } + } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/LeaderboardGroupBaseImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/LeaderboardGroupBaseImpl.java index 59c1fd5e78e..347ba1d5e3a 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/LeaderboardGroupBaseImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/LeaderboardGroupBaseImpl.java @@ -11,12 +11,14 @@ public abstract class LeaderboardGroupBaseImpl implements LeaderboardGroupBase { private UUID id; private String name; + private String displayName; private String description; - public LeaderboardGroupBaseImpl(UUID id, String name, String description) { + public LeaderboardGroupBaseImpl(UUID id, String name, String description, String displayName) { this.id = id; this.name = name; this.description = description; + this.displayName = displayName; } private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { @@ -51,4 +53,14 @@ public abstract class LeaderboardGroupBaseImpl implements LeaderboardGroupBase { this.name = name; } + @Override + public String getDisplayName() { + return displayName; + } + + @Override + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/SharedDomainFactoryImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/SharedDomainFactoryImpl.java index 19e0bb03206..e548557cf08 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/SharedDomainFactoryImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/SharedDomainFactoryImpl.java @@ -26,6 +26,7 @@ import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher; import com.sap.sailing.domain.base.configuration.impl.DeviceConfigurationMatcherMulti; import com.sap.sailing.domain.base.configuration.impl.DeviceConfigurationMatcherSingle; +import com.sap.sailing.domain.common.BoatClassMasterdata; import com.sap.sailing.domain.common.Color; import com.sap.sailing.domain.common.MarkType; import com.sap.sailing.domain.common.PassingInstruction; @@ -268,6 +269,13 @@ public class SharedDomainFactoryImpl implements SharedDomainFactory { public BoatClass getOrCreateBoatClass(String name, boolean typicallyStartsUpwind) { synchronized (boatClassCache) { BoatClass result = boatClassCache.get(name); + if(result == null) { + BoatClassMasterdata boatClassMasterdata = BoatClassMasterdata.resolveBoatClass(name); + if(boatClassMasterdata != null) { + result = new BoatClassImpl(name, boatClassMasterdata); + boatClassCache.put(name, result); + } + } if (result == null) { result = new BoatClassImpl(name, typicallyStartsUpwind); boatClassCache.put(name, result); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/StrippedEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/StrippedEventImpl.java index 5874331e7e1..5f7d1b2a0da 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/StrippedEventImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/StrippedEventImpl.java @@ -1,15 +1,21 @@ package com.sap.sailing.domain.base.impl; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; import java.util.UUID; +import java.util.concurrent.ExecutionException; import com.sap.sailing.domain.base.EventBase; import com.sap.sailing.domain.base.LeaderboardGroupBase; import com.sap.sailing.domain.base.Venue; +import com.sap.sailing.domain.common.ImageSize; import com.sap.sailing.domain.common.TimePoint; /** * A simplified implementation of the {@link EventBase} interface which maintains an immutable collection of - * {@link LeaderboardGroupBase} objects to implement the {@link #getLeaderboardGroups()} method. + * {@link LeaderboardGroupBase} objects to implement the {@link #getLeaderboardGroups()} method. A local image + * size cache can be maintained using the {@link #setImageSize} method. * * @author Axel Uhl (D043530) * @@ -17,21 +23,31 @@ import com.sap.sailing.domain.common.TimePoint; public class StrippedEventImpl extends EventBaseImpl { private static final long serialVersionUID = 5608501747499933988L; private final Iterable leaderboardGroups; + private final Map imageSizes; public StrippedEventImpl(String name, TimePoint startDate, TimePoint endDate, String venueName, boolean isPublic, UUID id, Iterable leaderboardGroups) { - super(name, startDate, endDate, venueName, isPublic, id); - this.leaderboardGroups = leaderboardGroups; + this(name, startDate, endDate, new VenueImpl(venueName), isPublic, id, leaderboardGroups); } public StrippedEventImpl(String name, TimePoint startDate, TimePoint endDate, Venue venue, boolean isPublic, UUID id, Iterable leaderboardGroups) { super(name, startDate, endDate, venue, isPublic, id); this.leaderboardGroups = leaderboardGroups; + this.imageSizes = new HashMap(); } @Override public Iterable getLeaderboardGroups() { return leaderboardGroups; } + + public void setImageSize(URL imageURL, ImageSize imageSize) { + imageSizes.put(imageURL, imageSize); + } + + @Override + public ImageSize getImageSize(URL imageURL) throws InterruptedException, ExecutionException { + return imageSizes.get(imageURL); + } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/StrippedLeaderboardGroupImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/StrippedLeaderboardGroupImpl.java index 7df0a3d23a8..b97b42187ce 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/StrippedLeaderboardGroupImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/StrippedLeaderboardGroupImpl.java @@ -6,8 +6,8 @@ public class StrippedLeaderboardGroupImpl extends LeaderboardGroupBaseImpl { private static final long serialVersionUID = 7263952130620919924L; private final boolean hasOverallLeaderboard; - public StrippedLeaderboardGroupImpl(UUID id, String name, String description, boolean hasOverallLeaderboard) { - super(id, name, description); + public StrippedLeaderboardGroupImpl(UUID id, String name, String description, String displayName, boolean hasOverallLeaderboard) { + super(id, name, description, displayName); this.hasOverallLeaderboard = hasOverallLeaderboard; } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TransientCompetitorStoreImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TransientCompetitorStoreImpl.java index a27469b908f..f1b221ab707 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TransientCompetitorStoreImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TransientCompetitorStoreImpl.java @@ -219,7 +219,7 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab : countryCode.getTwoLetterISOCode(), countryCode == null ? "" : countryCode.getThreeLetterIOCCode(), countryCode == null ? "" : countryCode.getName(), c .getBoat().getSailID(), c.getId().toString(), new BoatClassDTO(c.getBoat().getBoatClass() - .getName(), c.getBoat().getBoatClass().getHullLength().getMeters())); + .getName(), c.getBoat().getBoatClass().getDisplayName(), c.getBoat().getBoatClass().getHullLength().getMeters())); weakCompetitorDTOCache.put(c, competitorDTO); } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/util/impl/ConcurrentHashBag.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/util/impl/ConcurrentHashBag.java new file mode 100755 index 00000000000..05035023ca5 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/util/impl/ConcurrentHashBag.java @@ -0,0 +1,92 @@ +package com.sap.sailing.util.impl; + +import java.util.AbstractCollection; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +/** + * An "almost" thread-safe bag implementation. The constraint: when for equal objects an add or remove is performed, + * there should be no concurrency regarding that key. The implementation uses a {@link ConcurrentHashMap} internally. + * + * @author Axel Uhl (D043530) + */ +public class ConcurrentHashBag extends AbstractCollection { + private ConcurrentHashMap map = new ConcurrentHashMap(); + private int size; + + @Override + public boolean contains(Object o) { + return map.containsKey(o); + } + + @Override + public boolean remove(Object o) { + @SuppressWarnings("unchecked") + T t = (T) o; + Integer oldCount = map.remove(t); + if (oldCount != null && oldCount != 1) { + map.put(t, oldCount - 1); + } + if (oldCount != null) { + size--; + } + return oldCount != null; + } + + @Override + public boolean add(T e) { + Integer oldCount = map.put(e, 1); + if (oldCount != null && oldCount != 0) { + map.put(e, oldCount + 1); + } + size++; + return true; + } + + @Override + public java.util.Iterator iterator() { + return new Iterator(); + } + + @Override + public int size() { + return size; + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + private class Iterator implements java.util.Iterator { + private final java.util.Iterator> iter = map.entrySet().iterator(); + private int howManyMore = 0; + private T lastElementOfWhichWeHaveMore; + + @Override + public boolean hasNext() { + return howManyMore > 0 || iter.hasNext(); + } + + @Override + public T next() { + final T result; + if (howManyMore > 0) { + result = lastElementOfWhichWeHaveMore; + howManyMore--; + } else { + Entry next = iter.next(); + howManyMore = next.getValue()-1; + lastElementOfWhichWeHaveMore = next.getKey(); + result = next.getKey(); + } + return result; + } + + @Override + public void remove() { + // TODO Auto-generated method stub + + } + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/util/impl/LockUtil.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/util/impl/LockUtil.java index 98deae3fc26..3169c27ec8b 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/util/impl/LockUtil.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/util/impl/LockUtil.java @@ -2,7 +2,6 @@ package com.sap.sailing.util.impl; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -381,7 +380,7 @@ public class LockUtil { // capture the stack traces as quickly as possible to try to reflect the situation as it was when the lock couuldn't be obtained StackTraceElement[] writerStackTrace = writer != null ? writer.getStackTrace() : null; Map readerStackTraces = new HashMap(); - final List readers = lockParent.getReaders(); + final Iterable readers = lockParent.getReaders(); for (Thread reader : readers) { readerStackTraces.put(reader, reader.getStackTrace()); } @@ -396,7 +395,7 @@ public class LockUtil { message.append("\nThe current writer is:\n"); appendThreadData(message, writer, writerStackTrace); } - if (readers != null && !readers.isEmpty()) { + if (readers != null && !Util.isEmpty(readers)) { message.append("\nThe current readers are:\n"); for (Thread reader : readers) { appendThreadData(message, reader, readerStackTraces.get(reader)); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/util/impl/NamedReentrantReadWriteLock.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/util/impl/NamedReentrantReadWriteLock.java index d465b4de2f3..d8525e270a2 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/util/impl/NamedReentrantReadWriteLock.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/util/impl/NamedReentrantReadWriteLock.java @@ -3,8 +3,6 @@ package com.sap.sailing.util.impl; import java.io.IOException; import java.io.ObjectInputStream; import java.util.ArrayList; -import java.util.Collections; -import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -35,7 +33,7 @@ public class NamedReentrantReadWriteLock extends ReentrantReadWriteLock implemen private final String writeLockName; private final WriteLockWrapper writeLockWrapper; private final ReadLockWrapper readLockWrapper; - private transient List readers; + private transient ConcurrentHashBag readers; private class WriteLockWrapper extends WriteLock { private static final long serialVersionUID = -4234819025137348944L; @@ -110,7 +108,8 @@ public class NamedReentrantReadWriteLock extends ReentrantReadWriteLock implemen public void lockInterruptibly() throws InterruptedException { try { readLock.lockInterruptibly(); - readers.add(Thread.currentThread()); + final Thread currentThread = Thread.currentThread(); + readers.add(currentThread); } catch (InterruptedException ie) { throw ie; } @@ -120,7 +119,8 @@ public class NamedReentrantReadWriteLock extends ReentrantReadWriteLock implemen public boolean tryLock() { boolean result = readLock.tryLock(); if (result) { - readers.add(Thread.currentThread()); + final Thread currentThread = Thread.currentThread(); + readers.add(currentThread); } return result; } @@ -129,7 +129,8 @@ public class NamedReentrantReadWriteLock extends ReentrantReadWriteLock implemen public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { boolean result = readLock.tryLock(timeout, unit); if (result) { - readers.add(Thread.currentThread()); + final Thread currentThread = Thread.currentThread(); + readers.add(currentThread); } return result; } @@ -159,12 +160,12 @@ public class NamedReentrantReadWriteLock extends ReentrantReadWriteLock implemen this.writeLockName = "writeLock "+name; this.writeLockWrapper = new WriteLockWrapper(super.writeLock()); this.readLockWrapper = new ReadLockWrapper(super.readLock()); - this.readers = Collections.synchronizedList(new ArrayList()); + this.readers = new ConcurrentHashBag(); } private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.defaultReadObject(); - this.readers = Collections.synchronizedList(new ArrayList()); + this.readers = new ConcurrentHashBag(); } @Override @@ -184,9 +185,9 @@ public class NamedReentrantReadWriteLock extends ReentrantReadWriteLock implemen /** * Contains the threads currently holding a read lock. Each thread is contained as many times as it - * successfully acquired the read lock re-entrantly. + * successfully acquired the read lock re-entrantly. The result is a snapshot that is not live. */ - public List getReaders() { + public Iterable getReaders() { return new ArrayList(readers); } diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterConnectorImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterConnectorImpl.java index bdb88d3b89b..413b31942dc 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterConnectorImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterConnectorImpl.java @@ -27,6 +27,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.common.AbstractBearing; import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.Speed; @@ -388,7 +389,7 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement final Speed velocityMadeGood = fixSections[vmgIndex].trim().length() == 0 ? null : new KnotSpeedImpl( Double.valueOf(fixSections[vmgIndex])); fixDetailIndex += 2; - final DegreeBearingImpl cog = new DegreeBearingImpl( + final AbstractBearing cog = new DegreeBearingImpl( Double.valueOf(fixSections[fixDetailIndex++])); final SpeedWithBearing speed = new KnotSpeedWithBearingImpl(speedOverGroundInKnots, cog); final Integer nextMarkIndex = fixSections.length <= fixDetailIndex diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/LeaderboardDTODiffingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/LeaderboardDTODiffingTest.java index b9b1a1c3e74..0f10c876b2c 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/LeaderboardDTODiffingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/LeaderboardDTODiffingTest.java @@ -337,7 +337,7 @@ public class LeaderboardDTODiffingTest { public void testPartialRaceColumnDTOCompaction() throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException { // create a modified R9 RaceDTO clone in newVersion to make sure that even changing a property in the RaceDTO will keep the RaceColumnDTO from being omitted RaceColumnDTO r9 = newVersion.getRaceColumnByName("R9"); - RaceColumnDTO clonedR9 = new RaceColumnDTO(r9.isValidInTotalScore()); + RaceColumnDTO clonedR9 = new RaceColumnDTO(); cloner.clone(r9, clonedR9); // also clone the racesPerFleet map, or else we'd be modifying the previous version's one too final Field racesPerFleetField = clonedR9.getClass().getDeclaredField("racesPerFleet"); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractTracTracLiveTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractTracTracLiveTest.java index 3f54d823faa..2d55faa233c 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractTracTracLiveTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractTracTracLiveTest.java @@ -55,7 +55,7 @@ public abstract class AbstractTracTracLiveTest extends StoredTrackBasedTest { private IRaceSubscriber raceSubscriber; private final Collection receivers; - @Rule public Timeout AbstractTracTracLiveTestTimeout = new Timeout(2 * 60 * 1000); + @Rule public Timeout AbstractTracTracLiveTestTimeout = new Timeout(3 * 60 * 1000); protected AbstractTracTracLiveTest() throws URISyntaxException, MalformedURLException { receivers = new HashSet(); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/BoundsTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/BoundsTest.java index 65bc5788241..90ec758979b 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/BoundsTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/BoundsTest.java @@ -88,6 +88,28 @@ public class BoundsTest { assertEquals(5.0, u.getNorthEast().getLngDeg(), 0.00000001); } + @Test + public void unionTestWithEmptyBounds() { + Bounds b1 = new BoundsImpl(new DegreePosition(1, 1), new DegreePosition(3, 3)); + Bounds b2 = new BoundsImpl(new DegreePosition(4, 4), new DegreePosition(4, 4)); + Bounds u = b1.union(b2); + assertEquals(1.0, u.getSouthWest().getLatDeg(), 0.00000001); + assertEquals(1.0, u.getSouthWest().getLngDeg(), 0.00000001); + assertEquals(4.0, u.getNorthEast().getLatDeg(), 0.00000001); + assertEquals(4.0, u.getNorthEast().getLngDeg(), 0.00000001); + } + + @Test + public void unionTestWithTwoEmptyBounds() { + Bounds b1 = new BoundsImpl(new DegreePosition(1, 1), new DegreePosition(1, 1)); + Bounds b2 = new BoundsImpl(new DegreePosition(4, 4), new DegreePosition(4, 4)); + Bounds u = b1.union(b2); + assertEquals(1.0, u.getSouthWest().getLatDeg(), 0.00000001); + assertEquals(1.0, u.getSouthWest().getLngDeg(), 0.00000001); + assertEquals(4.0, u.getNorthEast().getLatDeg(), 0.00000001); + assertEquals(4.0, u.getNorthEast().getLngDeg(), 0.00000001); + } + @Test public void simpleIntersectsTest() { Bounds b1 = new BoundsImpl(new DegreePosition(1, 1), new DegreePosition(3, 3)); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CalculateImageSizeFromUrlTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CalculateImageSizeFromUrlTest.java index 8a9be87e216..920d42c7573 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CalculateImageSizeFromUrlTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CalculateImageSizeFromUrlTest.java @@ -3,7 +3,6 @@ package com.sap.sailing.domain.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import java.awt.Dimension; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; @@ -20,6 +19,8 @@ import org.junit.Test; import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.impl.EventImpl; +import com.sap.sailing.domain.common.ImageSize; +import com.sap.sailing.domain.common.impl.ImageSizeImpl; public class CalculateImageSizeFromUrlTest { @@ -27,12 +28,12 @@ public class CalculateImageSizeFromUrlTest { public void calculateImageSizeFromUrl() throws IOException { int width = 350; int height = 150; - Dimension size = calculate("http://placehold.it/" + width + "x" + height); + ImageSize size = calculate("http://placehold.it/" + width + "x" + height); assertTrue(size.getWidth() == width); assertTrue(size.getHeight() == height); } - public Dimension calculate(String urlS) throws IOException { + public ImageSize calculate(String urlS) throws IOException { ImageInputStream in = null; try { URL url = new URL(urlS); @@ -43,7 +44,7 @@ public class CalculateImageSizeFromUrlTest { ImageReader reader = readers.next(); try { reader.setInput(in); - return new Dimension(reader.getWidth(0), reader.getHeight(0)); + return new ImageSizeImpl(reader.getWidth(0), reader.getHeight(0)); } finally { reader.dispose(); } @@ -63,7 +64,7 @@ public class CalculateImageSizeFromUrlTest { int height = Math.max(10, (int) (100. * Math.random())); URL imageURL = new URL("http://placehold.it/" + width + "x" + height); e.addImageURL(imageURL); - Dimension expectedSize = new Dimension(width, height); + ImageSize expectedSize = new ImageSizeImpl(width, height); assertEquals(expectedSize, e.getImageSize(imageURL)); } } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ConcurrentCollectionsPerformanceTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ConcurrentCollectionsPerformanceTest.java new file mode 100755 index 00000000000..d3bc8e3d580 --- /dev/null +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ConcurrentCollectionsPerformanceTest.java @@ -0,0 +1,62 @@ +package com.sap.sailing.domain.test; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; + +import org.junit.Test; + +import com.sap.sailing.util.impl.ConcurrentHashBag; +import com.sap.sse.common.Util; + +/** + * Measures the performance of different concurrent collections types and shows their add and remove behavior for + * different collection sizes and different numbers of concurrently accessing threads. The collections used must be able + * to hold the same element multiple times, keeping count, not necessarily order. + * + * @author Axel Uhl (D043530) + * + */ +public class ConcurrentCollectionsPerformanceTest { + private static final int COUNT = 10000; + + @Test + public void testConcurrentLinkedQueue() { + Collection c = new ConcurrentLinkedQueue(); + System.out.println("ConcurrentLinkedQueue" + runWith(c)); + } + + @Test + public void testConcurrentHashMap() { + Collection c = new ConcurrentHashBag(); + System.out.println("ConcurrentHashBag" + runWith(c)); + } + + private com.sap.sse.common.Util.Pair runWith(Collection c) { + List l = new ArrayList<>(COUNT); + for (int i=0; i(l)) { + l.add(o); + } + long startInsert = System.currentTimeMillis(); + for (Object o : l) { + c.add(o); + } + long endInsert = System.currentTimeMillis(); + assertEquals(l.size(), c.size()); + Collections.shuffle(l); + long startRemove = System.currentTimeMillis(); + for (Object o : l) { + c.remove(o); + } + long endRemove = System.currentTimeMillis(); + return new Util.Pair(endInsert-startInsert, endRemove-startRemove); + } +} diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseTest.java index b8c6fae8e02..9bd0c9387bc 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseTest.java @@ -24,6 +24,7 @@ import com.sap.sailing.domain.base.Sideline; import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.base.impl.BoatClassImpl; import com.sap.sailing.domain.base.impl.CompetitorImpl; +import com.sap.sailing.domain.base.impl.ControlPointWithTwoMarksImpl; import com.sap.sailing.domain.base.impl.CourseImpl; import com.sap.sailing.domain.base.impl.MarkImpl; import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; @@ -37,6 +38,7 @@ import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl; import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl; import com.sap.sailing.domain.tracking.impl.EmptyWindStore; import com.sap.sse.common.Util; +import com.sap.sse.common.Util.Pair; import difflib.PatchFailedException; @@ -186,6 +188,53 @@ public class CourseTest { course.update(courseToUpdate, DomainFactory.INSTANCE); assertWaypointIndexes(course); } + + /** + * This test tries to replicate the behavior described in bug 2223. The course was + * "RC-Black Conical -> Orange -> White Gate -> Red -> Yellow -> Finish Pole-Cylinder" and the delta was + * "[[DeleteDelta, position: 1, lines: [Orange, White Gate]], [InsertDelta, position: 4, lines: [White Gate, Red]]]" + * which was computed from the new sequence of control points + * "[Control, RC-Black Conical, Control, Red, Control, White Gate, Control, Red, Control, Yellow, Control, Finish Pole-Cylinder]". + */ + @Test + public void testWaypointDeleteWithSubsequentInsertInOnePatch() throws PatchFailedException { + List waypoints = new ArrayList(); + final WaypointImpl rcBlackConical = new WaypointImpl(new ControlPointWithTwoMarksImpl(new MarkImpl("RC"), new MarkImpl("Black Conical"), "RC-Black Conical")); + waypoints.add(rcBlackConical); + final WaypointImpl orange = new WaypointImpl(new MarkImpl("Orange")); + waypoints.add(orange); + final WaypointImpl whiteGate = new WaypointImpl(new ControlPointWithTwoMarksImpl(new MarkImpl("White L"), new MarkImpl("White R"), "White Gate")); + waypoints.add(whiteGate); + final WaypointImpl red = new WaypointImpl(new MarkImpl("Red")); + waypoints.add(red); + final WaypointImpl yellow = new WaypointImpl(new MarkImpl("Yellow")); + waypoints.add(yellow); + final WaypointImpl finishPoleCylinder = new WaypointImpl(new ControlPointWithTwoMarksImpl(new MarkImpl("Finish Pole"), new MarkImpl("Cylinder"), "Finish Pole-Cylinder")); + waypoints.add(finishPoleCylinder); + Course course = new CourseImpl("Race 24", waypoints); + assertWaypointIndexes(course); + List> courseToUpdate = new ArrayList>(); + courseToUpdate.add(new Pair<>(rcBlackConical.getControlPoint(), rcBlackConical.getPassingInstructions())); + courseToUpdate.add(new Pair<>(red.getControlPoint(), red.getPassingInstructions())); + courseToUpdate.add(new Pair<>(whiteGate.getControlPoint(), whiteGate.getPassingInstructions())); + courseToUpdate.add(new Pair<>(red.getControlPoint(), red.getPassingInstructions())); + courseToUpdate.add(new Pair<>(yellow.getControlPoint(), yellow.getPassingInstructions())); + courseToUpdate.add(new Pair<>(finishPoleCylinder.getControlPoint(), finishPoleCylinder.getPassingInstructions())); + course.update(courseToUpdate, DomainFactory.INSTANCE); + assertWaypointIndexes(course); + List newControlPoints = new ArrayList<>(); + for (Waypoint newWp : course.getWaypoints()) { + newControlPoints.add(newWp.getControlPoint()); + } + assertTrue(Util.equals(Arrays.asList( + rcBlackConical.getControlPoint(), + red.getControlPoint(), + whiteGate.getControlPoint(), + red.getControlPoint(), + yellow.getControlPoint(), + finishPoleCylinder.getControlPoint()), + newControlPoints)); + } @Test public void testRemoveWaypointFromCourseWithThreeWaypoints() { diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/EventLeaderboardGroupLinkTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/EventLeaderboardGroupLinkTest.java index 42754e124f9..27fa19b68b4 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/EventLeaderboardGroupLinkTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/EventLeaderboardGroupLinkTest.java @@ -21,10 +21,10 @@ public class EventLeaderboardGroupLinkTest { @Test public void testLivelinessAndIteratorStabilityOfUnmodifiableLeaderboardGroupCollection() { Event e = new EventImpl("E", null, null, "Venue", /* public */ true, UUID.randomUUID()); - LeaderboardGroupImpl lg1 = new LeaderboardGroupImpl("lg1", "LG1", /* displayGroupsInReverseOrder */ false, Collections.emptyList()); - LeaderboardGroupImpl lg2 = new LeaderboardGroupImpl("lg2", "LG2", /* displayGroupsInReverseOrder */ false, Collections.emptyList()); - LeaderboardGroupImpl lg3 = new LeaderboardGroupImpl("lg3", "LG3", /* displayGroupsInReverseOrder */ false, Collections.emptyList()); - LeaderboardGroupImpl lg4 = new LeaderboardGroupImpl("lg4", "LG4", /* displayGroupsInReverseOrder */ false, Collections.emptyList()); + LeaderboardGroupImpl lg1 = new LeaderboardGroupImpl("lg1", "LG1", /* displayName */ null, /* displayGroupsInReverseOrder */ false, Collections.emptyList()); + LeaderboardGroupImpl lg2 = new LeaderboardGroupImpl("lg2", "LG2", /* displayName */ null, /* displayGroupsInReverseOrder */ false, Collections.emptyList()); + LeaderboardGroupImpl lg3 = new LeaderboardGroupImpl("lg3", "LG3", /* displayName */ null, /* displayGroupsInReverseOrder */ false, Collections.emptyList()); + LeaderboardGroupImpl lg4 = new LeaderboardGroupImpl("lg4", "LG4", /* displayName */ null, /* displayGroupsInReverseOrder */ false, Collections.emptyList()); e.addLeaderboardGroup(lg1); Iterable lgs = e.getLeaderboardGroups(); Iterator iter = lgs.iterator(); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java index 443d46cb43a..1363731c006 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java @@ -909,8 +909,8 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { false); assertEquals(16, leaderboardHighPoint.getCompetitorsFromBestToWorst(later).size()); assertEquals(new Double(16), leaderboardHighPoint.getTotalPoints(competitors[0], later)); - LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl("ESS", "ESS", /* displayGroupsInReverseOrder */ false, - Arrays.asList(new Leaderboard[] { leaderboardHighPoint10LastBreaksTie })); + LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl("ESS", "ESS", /* displayName */ null, + /* displayGroupsInReverseOrder */ false, Arrays.asList(new Leaderboard[] { leaderboardHighPoint10LastBreaksTie })); LeaderboardGroupMetaLeaderboard leaderboardHighPointESSOverall = new LeaderboardGroupMetaLeaderboard( leaderboardGroup, new HighPointExtremeSailingSeriesOverall(), new ThresholdBasedResultDiscardingRuleImpl(/* discarding thresholds */ new int[0])); assertEquals(16, leaderboardHighPointESSOverall.getCompetitorsFromBestToWorst(later).size()); @@ -966,7 +966,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { false); assertTrue(leaderboard3.getCompetitorsFromBestToWorst(later).indexOf(c[3]) < leaderboard3.getCompetitorsFromBestToWorst(later).indexOf(c[0])); // c3 better than c0; won last race - LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl("Leaderboard Group", "Leaderboard Group", false, Arrays.asList(leaderboard1, + LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl("Leaderboard Group", "Leaderboard Group", /* displayName */ null, false, Arrays.asList(leaderboard1, leaderboard2, leaderboard3)); leaderboardGroup.setOverallLeaderboard(new LeaderboardGroupMetaLeaderboard(leaderboardGroup, new HighPointExtremeSailingSeriesOverall(), new ThresholdBasedResultDiscardingRuleImpl(new int[0]))); @@ -1019,8 +1019,8 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { assertEquals(4, leaderboard2.getTotalPoints(c[9], later), 0.000000001); assertEquals(3, leaderboard2.getTotalPoints(c[11], later), 0.000000001); - LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl("Leaderboard Group ESS Overall", "Leaderboard Group", false, - Arrays.asList(leaderboard1, leaderboard2)); + LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl("Leaderboard Group ESS Overall", "Leaderboard Group", /* displayName */ null, + false, Arrays.asList(leaderboard1, leaderboard2)); leaderboardGroup.setOverallLeaderboard(new LeaderboardGroupMetaLeaderboard(leaderboardGroup, new HighPointExtremeSailingSeriesOverall(), new ThresholdBasedResultDiscardingRuleImpl(new int[0]))); leaderboardGroup.getOverallLeaderboard().setSuppressed(c[c.length-1], true); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LockTraceTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LockTraceTest.java index daa6684533a..abb276740d0 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LockTraceTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LockTraceTest.java @@ -16,6 +16,7 @@ import com.sap.sailing.domain.test.measurements.MeasurementCase; import com.sap.sailing.domain.test.measurements.MeasurementXMLFile; import com.sap.sailing.util.impl.LockUtil; import com.sap.sailing.util.impl.NamedReentrantReadWriteLock; +import com.sap.sse.common.Util; public class LockTraceTest { private static class LockingThread extends Thread { @@ -47,18 +48,18 @@ public class LockTraceTest { public void testReentrantReadLocking() { NamedReentrantReadWriteLock lock = new NamedReentrantReadWriteLock("testReentrantReadLocking-Lock", /* fair */ true); LockUtil.lockForRead(lock); - assertTrue(lock.getReaders().contains(Thread.currentThread())); - assertEquals(1, lock.getReaders().size()); + assertTrue(Util.contains(lock.getReaders(), Thread.currentThread())); + assertEquals(1, Util.size(lock.getReaders())); LockUtil.lockForRead(lock); - assertTrue(lock.getReaders().contains(Thread.currentThread())); - assertEquals(2, lock.getReaders().size()); + assertTrue(Util.contains(lock.getReaders(), Thread.currentThread())); + assertEquals(2, Util.size(lock.getReaders())); LockUtil.unlockAfterRead(lock); - assertTrue(lock.getReaders().contains(Thread.currentThread())); - assertEquals(1, lock.getReaders().size()); + assertTrue(Util.contains(lock.getReaders(), Thread.currentThread())); + assertEquals(1, Util.size(lock.getReaders())); LockUtil.unlockAfterRead(lock); - assertFalse(lock.getReaders().contains(Thread.currentThread())); + assertFalse(Util.contains(lock.getReaders(), Thread.currentThread())); assertEquals(0, lock.getReadHoldCount()); - assertEquals(0, lock.getReaders().size()); + assertEquals(0, Util.size(lock.getReaders())); } @Test @@ -67,15 +68,15 @@ public class LockTraceTest { LockUtil.lockForWrite(lock); assertTrue(lock.getWriter() == Thread.currentThread()); LockUtil.lockForRead(lock); // reentrant read while holding write - assertTrue(lock.getReaders().contains(Thread.currentThread())); + assertTrue(Util.contains(lock.getReaders(), Thread.currentThread())); LockUtil.lockForWrite(lock); // reentrant write assertTrue(lock.getWriter() == Thread.currentThread()); - assertTrue(lock.getReaders().contains(Thread.currentThread())); + assertTrue(Util.contains(lock.getReaders(), Thread.currentThread())); LockUtil.unlockAfterRead(lock); - assertFalse(lock.getReaders().contains(Thread.currentThread())); + assertFalse(Util.contains(lock.getReaders(), Thread.currentThread())); assertEquals(0, lock.getReadHoldCount()); assertTrue(lock.getWriter() == Thread.currentThread()); - assertFalse(lock.getReaders().contains(Thread.currentThread())); + assertFalse(Util.contains(lock.getReaders(), Thread.currentThread())); assertEquals(0, lock.getReadHoldCount()); LockUtil.unlockAfterWrite(lock); assertTrue(lock.getWriter() == Thread.currentThread()); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OfflineSerializationTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OfflineSerializationTest.java index de167da9325..50ebffd0334 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OfflineSerializationTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OfflineSerializationTest.java @@ -92,9 +92,9 @@ public class OfflineSerializationTest extends AbstractSerializationTest { DomainFactory receiverDomainFactory = new DomainFactoryImpl(); Event e = new EventImpl("Event Name", MillisecondsTimePoint.now(), MillisecondsTimePoint.now().plus( Duration.ONE_DAY.times(10)), "At Home", /* is public */true, UUID.randomUUID()); - LeaderboardGroup lg1 = new LeaderboardGroupImpl("LG1", "LG1 Description", /* displayGroupsInReverseOrder */ false, Collections. emptyList()); + LeaderboardGroup lg1 = new LeaderboardGroupImpl("LG1", "LG1 Description", /* displayName */ null, /* displayGroupsInReverseOrder */ false, Collections. emptyList()); e.addLeaderboardGroup(lg1); - LeaderboardGroup lg2 = new LeaderboardGroupImpl("LG2", "LG2 Description", /* displayGroupsInReverseOrder */ false, Collections. emptyList()); + LeaderboardGroup lg2 = new LeaderboardGroupImpl("LG2", "LG2 Description", /* displayName */ null, /* displayGroupsInReverseOrder */ false, Collections. emptyList()); e.addLeaderboardGroup(lg2); Event deserialized = cloneBySerialization(e, receiverDomainFactory); assertEquals(Util.size(e.getLeaderboardGroups()), Util.size(deserialized.getLeaderboardGroups())); @@ -118,7 +118,7 @@ public class OfflineSerializationTest extends AbstractSerializationTest { @Test public void testSerializingOverallLeaderboardWithFactorOnColumn() throws ClassNotFoundException, IOException { Leaderboard leaderboard = new FlexibleLeaderboardImpl("Test Leaderboard", new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 5 }), new HighPoint(), new CourseAreaImpl("Alpha", UUID.randomUUID())); - LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl("LeaderboardGroup", "Test Leaderboard Group", /* displayGroupsInReverseOrder */ false, Arrays.asList(new Leaderboard[] { leaderboard })); + LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl("LeaderboardGroup", "Test Leaderboard Group", /* displayName */ null, /* displayGroupsInReverseOrder */ false, Arrays.asList(new Leaderboard[] { leaderboard })); final LeaderboardGroupMetaLeaderboard overallLeaderboard = new LeaderboardGroupMetaLeaderboard(leaderboardGroup, new HighPoint(), new ThresholdBasedResultDiscardingRuleImpl(new int[0])); leaderboardGroup.setOverallLeaderboard(overallLeaderboard); final double FACTOR = 2.0; diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/PolarSheetGenerationTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/PolarSheetGenerationTest.java index dfccf777536..17d8541af65 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/PolarSheetGenerationTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/PolarSheetGenerationTest.java @@ -278,7 +278,7 @@ public class PolarSheetGenerationTest { } @Override - public boolean isValid() { + public boolean isValidCached() { return true; } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/SmartFutureCacheDeadlockTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/SmartFutureCacheDeadlockTest.java index 92d1bda16f1..b03b00bd6b9 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/SmartFutureCacheDeadlockTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/SmartFutureCacheDeadlockTest.java @@ -19,6 +19,7 @@ import com.sap.sailing.util.SmartFutureCache; import com.sap.sailing.util.SmartFutureCache.EmptyUpdateInterval; import com.sap.sailing.util.impl.LockUtil; import com.sap.sailing.util.impl.NamedReentrantReadWriteLock; +import com.sap.sse.common.Util; /** * When the thread reading a value from the cache holds a fair read lock, and another thread is trying to obtain the @@ -88,15 +89,15 @@ public class SmartFutureCacheDeadlockTest { @Test public void testBasicLockingAndUnlockingWithScripts() throws InterruptedException { logger.info("starting testBasicLockingAndUnlockingWithScripts"); - assertFalse(lock.getReaders().contains(readerThread)); + assertFalse(Util.contains(lock.getReaders(), readerThread)); try { reader.performAndWait(Command.LOCK_FOR_READ); - assertTrue(lock.getReaders().contains(readerThread)); + assertTrue(Util.contains(lock.getReaders(), readerThread)); writer.perform(Command.LOCK_FOR_WRITE); assertNull(lock.getWriter()); } finally { reader.performAndWait(Command.UNLOCK_AFTER_READ); - assertFalse(lock.getReaders().contains(readerThread)); + assertFalse(Util.contains(lock.getReaders(), readerThread)); writer.performAndWait(Command.UNLOCK_AFTER_WRITE); } } @@ -195,7 +196,7 @@ public class SmartFutureCacheDeadlockTest { writerThread.join(); assertFalse(reader.isRunning()); assertFalse(writer.isRunning()); - assertTrue(lock.getReaders().isEmpty()); + assertTrue(Util.isEmpty(lock.getReaders())); assertNull(lock.getWriter()); } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackTest.java index c08c2a1d461..3b33a28e192 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackTest.java @@ -26,6 +26,7 @@ import com.sap.sailing.domain.base.Boat; import com.sap.sailing.domain.base.Timed; import com.sap.sailing.domain.base.impl.BoatClassImpl; import com.sap.sailing.domain.base.impl.BoatImpl; +import com.sap.sailing.domain.common.AbstractBearing; import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.Position; @@ -43,6 +44,7 @@ import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; import com.sap.sailing.domain.tracking.GPSFix; import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.GPSFixTrack; +import com.sap.sailing.domain.tracking.impl.CompactGPSFixMovingImpl; import com.sap.sailing.domain.tracking.impl.DistanceCache; import com.sap.sailing.domain.tracking.impl.DynamicGPSFixMovingTrackImpl; import com.sap.sailing.domain.tracking.impl.DynamicGPSFixTrackImpl; @@ -852,7 +854,7 @@ public class TrackTest { true), null), /* millisecondsOverWhichToAverage */5000, /* no smoothening */null); TimePoint now1 = MillisecondsTimePoint.now(); TimePoint now2 = addMillisToTimepoint(now1, 1000); // 1s - DegreeBearingImpl bearing = new DegreeBearingImpl(90); + AbstractBearing bearing = new DegreeBearingImpl(90); Position position1 = new DegreePosition(1, 2); Position position2 = position1.translateGreatCircle(bearing, new MeterDistance(1)); GPSFixMovingImpl myGpsFix1 = new GPSFixMovingImpl(position1, now1, new MeterPerSecondSpeedWithDegreeBearingImpl(1, bearing)); @@ -873,7 +875,7 @@ public class TrackTest { true), null), /* millisecondsOverWhichToAverage */5000, /* no smoothening */null); TimePoint now1 = MillisecondsTimePoint.now(); TimePoint now2 = addMillisToTimepoint(now1, 1000); // 1s - DegreeBearingImpl bearing = new DegreeBearingImpl(90); + AbstractBearing bearing = new DegreeBearingImpl(90); Position position1 = new DegreePosition(1, 2); Position position2 = position1.translateGreatCircle(bearing, new MeterDistance(1)); GPSFixMovingImpl myGpsFix1 = new GPSFixMovingImpl(position1, now1, new MeterPerSecondSpeedWithDegreeBearingImpl(10, bearing)); @@ -894,7 +896,7 @@ public class TrackTest { true), null), /* millisecondsOverWhichToAverage */5000, /* no smoothening */null); TimePoint now1 = MillisecondsTimePoint.now(); TimePoint now2 = addMillisToTimepoint(now1, 1000); // 1s - DegreeBearingImpl bearing = new DegreeBearingImpl(90); + AbstractBearing bearing = new DegreeBearingImpl(90); Position position1 = new DegreePosition(1, 2); Position position2 = position1.translateGreatCircle(bearing, new MeterDistance(1)); GPSFixMovingImpl myGpsFix1 = new GPSFixMovingImpl(position1, now1, new MeterPerSecondSpeedWithDegreeBearingImpl(0.1, bearing)); @@ -1053,4 +1055,33 @@ public class TrackTest { assertEquals(3, track.getAverageIntervalBetweenFixes().asMillis()); assertEquals(3, track.getAverageIntervalBetweenRawFixes().asMillis()); } + + @Test + public void testEstimatedSpeedCaching() { + GPSFixMoving originalFix = new GPSFixMovingImpl(new DegreePosition(12, 34), MillisecondsTimePoint.now(), + new KnotSpeedWithBearingImpl(9, new DegreeBearingImpl(123))); + GPSFixMoving fix = new CompactGPSFixMovingImpl(originalFix); + assertFalse(fix.isEstimatedSpeedCached()); + final KnotSpeedWithBearingImpl estimatedSpeed = new KnotSpeedWithBearingImpl(9.1, new DegreeBearingImpl(124)); + fix.cacheEstimatedSpeed(estimatedSpeed); + assertTrue(fix.isEstimatedSpeedCached()); + SpeedWithBearing cachedEstimatedSpeed = fix.getCachedEstimatedSpeed(); + assertEquals(estimatedSpeed, cachedEstimatedSpeed); + fix.invalidateEstimatedSpeedCache(); + assertFalse(fix.isEstimatedSpeedCached()); + } + + @Test + public void testEstimatedSpeedCachingOnTrack() { + GPSFixMoving compactFix3 = track.getFirstFixAtOrAfter(gpsFix3.getTimePoint()); + assertFalse(compactFix3.isEstimatedSpeedCached()); + SpeedWithBearing fix3EstimatedSpeed = track.getEstimatedSpeed(gpsFix3.getTimePoint()); + assertTrue(compactFix3.isEstimatedSpeedCached()); + assertEquals(fix3EstimatedSpeed, compactFix3.getCachedEstimatedSpeed()); + assertEquals(fix3EstimatedSpeed, track.getEstimatedSpeed(gpsFix3.getTimePoint())); // fetch again from the cache + // assuming that all test fixes are within a few milliseconds and the averaging interval is much larger than that, + // adding a single fix in the middle should invalidate the cache + track.add(new GPSFixMovingImpl(gpsFix3.getPosition(), gpsFix3.getTimePoint().plus(1), gpsFix3.getSpeed())); + assertFalse(compactFix3.isEstimatedSpeedCached()); + } } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java index 694bfcb97e0..4dbb96e1ee5 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java @@ -1,6 +1,7 @@ package com.sap.sailing.domain.test.mock; import java.io.Serializable; +import java.util.Collections; import java.util.List; import java.util.NavigableSet; import java.util.TreeSet; @@ -617,14 +618,12 @@ public class MockedTrackedRace implements DynamicTrackedRace { @Override public Iterable getWindSources(WindSourceType type) { - // TODO Auto-generated method stub - return null; + return Collections.emptyList(); } @Override public Iterable getWindSources() { - // TODO Auto-generated method stub - return null; + return Collections.emptyList(); } @Override diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java index da4f91a1656..801723bc7e0 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java @@ -233,14 +233,12 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace { @Override public Iterable getWindSources(WindSourceType type) { - // TODO Auto-generated method stub - return null; + return Collections.emptyList(); } @Override public Iterable getWindSources() { - // TODO Auto-generated method stub - return null; + return Collections.emptyList(); } @Override diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/DomainFactory.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/DomainFactory.java index 2cfd6d10a98..a9bad5f525b 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/DomainFactory.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/DomainFactory.java @@ -2,6 +2,7 @@ package com.sap.sailing.domain.base; import java.io.IOException; import java.io.InputStream; +import java.util.Collection; import java.util.List; import com.sap.sailing.domain.base.impl.DomainFactoryImpl; @@ -14,6 +15,9 @@ import com.sap.sailing.domain.common.dto.FleetDTO; import com.sap.sailing.domain.common.dto.PlacemarkDTO; import com.sap.sailing.domain.common.dto.RaceDTO; import com.sap.sailing.domain.common.dto.TrackedRaceDTO; +import com.sap.sailing.domain.common.dto.TrackedRaceStatisticsDTO; +import com.sap.sailing.domain.common.media.MediaTrack; +import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.ScoringScheme; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.TrackedRace; @@ -53,6 +57,9 @@ public interface DomainFactory extends SharedDomainFactory { TrackedRaceDTO createTrackedRaceDTO(TrackedRace trackedRace); + TrackedRaceStatisticsDTO createTrackedRaceStatisticsDTO(TrackedRace trackedRace, Leaderboard leaderboard, RaceColumn raceColumn, + Fleet fleet, Collection mediatracks); + /** * @param trackedRace must not be null */ diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Event.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Event.java index b3ec3ca9add..1abf52edd38 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Event.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Event.java @@ -1,10 +1,10 @@ package com.sap.sailing.domain.base; -import java.awt.Dimension; import java.net.URL; import java.util.UUID; import java.util.concurrent.ExecutionException; +import com.sap.sailing.domain.common.ImageSize; import com.sap.sailing.domain.leaderboard.LeaderboardGroup; /** @@ -59,6 +59,6 @@ public interface Event extends EventBase { * Note that exceptions may result should the image be unavailable. If the image URL leads to a document that is not an image, * null will result. */ - Dimension getImageSize(URL imageURL) throws InterruptedException, ExecutionException; + ImageSize getImageSize(URL imageURL) throws InterruptedException, ExecutionException; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/BoatClassMasterdata.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/BoatClassMasterdata.java deleted file mode 100644 index 6fd246e66b2..00000000000 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/BoatClassMasterdata.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.sap.sailing.domain.base.impl; - -public enum BoatClassMasterdata { - - LASER_SB3 ( "Laser SB3", 6.15 ), - EXTREME_40 ("Extreme 40", 12.2), - J80 ("J/80", 8.0, "J80"), - _49ER ("49er", 4.88), - _29ER ("29er", 4.45), - _505 ("505", 5.03), - _420 ("420", 4.2), - _470 ("470", 4.7); - - private final double hullLengthInMeter; - - private final String displayName; - - private final String[] alternativeNames; - - BoatClassMasterdata(String displayName, double hullLengthInMeter) { - this.displayName = displayName; - this.hullLengthInMeter = hullLengthInMeter; - this.alternativeNames = new String[0]; - } - - BoatClassMasterdata(String displayName, double hullLengthInMeter, String... alternativeNames) { - this.displayName = displayName; - this.hullLengthInMeter = hullLengthInMeter; - this.alternativeNames = alternativeNames; - } - - public static BoatClassMasterdata resolveBoatClass(String boatClassName) { - for(BoatClassMasterdata boatClass: values()) { - if(boatClass.displayName.toUpperCase().equals(boatClassName.toUpperCase())) { - return boatClass; - } - for(String name: boatClass.alternativeNames) { - if(name.toUpperCase().equals(boatClassName.toUpperCase())) { - return boatClass; - } - } - } - return null; - } - - public double getHullLengthInMeter() { - return hullLengthInMeter; - } - - public String getDisplayName() { - return displayName; - } -} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DomainFactoryImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DomainFactoryImpl.java index 6837a6af8fe..7ebe68a5235 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DomainFactoryImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DomainFactoryImpl.java @@ -3,6 +3,7 @@ package com.sap.sailing.domain.base.impl; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.logging.Logger; @@ -13,13 +14,17 @@ import com.sap.sailing.domain.base.DomainFactory; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.ObjectInputStreamResolvingAgainstDomainFactory; +import com.sap.sailing.domain.base.RaceColumn; +import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.Placemark; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.domain.common.dto.FleetDTO; import com.sap.sailing.domain.common.dto.PlacemarkDTO; @@ -28,6 +33,10 @@ import com.sap.sailing.domain.common.dto.PositionDTO; import com.sap.sailing.domain.common.dto.RaceDTO; import com.sap.sailing.domain.common.dto.RaceStatusDTO; import com.sap.sailing.domain.common.dto.TrackedRaceDTO; +import com.sap.sailing.domain.common.dto.TrackedRaceStatisticsDTO; +import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; +import com.sap.sailing.domain.common.media.MediaTrack; +import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.ScoringScheme; import com.sap.sailing.domain.leaderboard.impl.HighPoint; import com.sap.sailing.domain.leaderboard.impl.HighPointExtremeSailingSeriesOverall; @@ -38,10 +47,14 @@ import com.sap.sailing.domain.leaderboard.impl.HighPointLastBreaksTie; import com.sap.sailing.domain.leaderboard.impl.HighPointWinnerGetsFive; import com.sap.sailing.domain.leaderboard.impl.HighPointWinnerGetsFiveIgnoringRaceCount; import com.sap.sailing.domain.leaderboard.impl.HighPointWinnerGetsSix; +import com.sap.sailing.domain.leaderboard.impl.HighPointWinnerGetsSixIgnoringRaceCount; import com.sap.sailing.domain.leaderboard.impl.LowPoint; import com.sap.sailing.domain.leaderboard.impl.LowPointWinnerGetsZero; import com.sap.sailing.domain.tracking.GPSFix; +import com.sap.sailing.domain.tracking.GPSFixMoving; +import com.sap.sailing.domain.tracking.GPSFixTrack; import com.sap.sailing.domain.tracking.MarkPassing; +import com.sap.sailing.domain.tracking.TrackedLeg; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; import com.sap.sailing.domain.tracking.impl.MarkPassingImpl; @@ -96,6 +109,8 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain return new HighPointWinnerGetsFiveIgnoringRaceCount(); case HIGH_POINT_WINNER_GETS_SIX: return new HighPointWinnerGetsSix(); + case HIGH_POINT_WINNER_GETS_SIX_IGNORING_RACE_COUNT: + return new HighPointWinnerGetsSixIgnoringRaceCount(); case HIGH_POINT_FIRST_GETS_TEN_OR_EIGHT: return new HighPointFirstGets10Or8AndLastBreaksTie(); } @@ -147,6 +162,75 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain return trackedRaceDTO; } + @Override + public TrackedRaceStatisticsDTO createTrackedRaceStatisticsDTO(TrackedRace trackedRace, Leaderboard leaderboard, + RaceColumn raceColumn, Fleet fleet, Collection mediaTracks) { + TrackedRaceStatisticsDTO statisticsDTO = new TrackedRaceStatisticsDTO(); + + // GPS data + statisticsDTO.hasGPSData = trackedRace.hasGPSData(); + + Competitor leaderOrWinner = null; + TimePoint now = MillisecondsTimePoint.now(); + try { + if(trackedRace.isLive(now)) { + leaderOrWinner = trackedRace.getOverallLeader(now); + } else if (trackedRace.getEndOfRace() != null) { + for(Competitor competitor: leaderboard.getCompetitorsFromBestToWorst(raceColumn, now)) { + Fleet fleetOfCompetitor = raceColumn.getFleetOfCompetitor(competitor); + if(fleetOfCompetitor != null && fleetOfCompetitor.equals(fleet)) { + leaderOrWinner = competitor; + break; + } + } + } + if(leaderOrWinner != null) { + statisticsDTO.hasLeaderOrWinnerData = true; + statisticsDTO.leaderOrWinner = convertToCompetitorDTO(leaderOrWinner); + GPSFixTrack track = trackedRace.getTrack(leaderOrWinner); + if(track != null) { + statisticsDTO.averageGPSDataSampleInterval = track.getAverageIntervalBetweenFixes(); + } + } + } catch (NoWindException e) { + } + + // Measured wind sources data + statisticsDTO.measuredWindSourcesCount = Util.size(trackedRace.getWindSources(WindSourceType.EXPEDITION)); + statisticsDTO.hasMeasuredWindData = statisticsDTO.measuredWindSourcesCount > 0; + + // leg progress data + RaceDefinition race = trackedRace.getRace(); + if(race.getCourse() != null) { + statisticsDTO.hasLegProgressData = true; + statisticsDTO.totalLegsCount = race.getCourse().getLegs().size(); + TrackedLeg currentLeg = trackedRace.getCurrentLeg(MillisecondsTimePoint.now()); + if(currentLeg != null) { + statisticsDTO.currentLegNo = race.getCourse().getIndexOfWaypoint(currentLeg.getLeg().getFrom()); + } else { + statisticsDTO.currentLegNo = 0; + } + } + + // media data + if(mediaTracks != null) { + for(MediaTrack track: mediaTracks) { + switch(track.mimeType.mediaType) { + case audio: + statisticsDTO.hasAudioData = true; + statisticsDTO.audioTracksCount = statisticsDTO.audioTracksCount == null ? 1 : statisticsDTO.audioTracksCount++; + break; + case video: + statisticsDTO.hasVideoData = true; + statisticsDTO.videoTracksCount = statisticsDTO.videoTracksCount == null ? 1 : statisticsDTO.videoTracksCount++; + break; + } + } + } + + return statisticsDTO; + } + private PlacemarkOrderDTO getRacePlaces(TrackedRace trackedRace) { Util.Pair startAndFinish = getStartFinishPlacemarksForTrackedRace(trackedRace); PlacemarkOrderDTO racePlaces = new PlacemarkOrderDTO(); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/EventImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/EventImpl.java index b72fd948dfc..7664b9c0f54 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/EventImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/EventImpl.java @@ -1,6 +1,5 @@ package com.sap.sailing.domain.base.impl; -import java.awt.Dimension; import java.io.IOException; import java.io.ObjectInputStream; import java.net.URL; @@ -25,7 +24,9 @@ import javax.imageio.stream.ImageInputStream; import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.Venue; +import com.sap.sailing.domain.common.ImageSize; import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.ImageSizeImpl; import com.sap.sailing.domain.leaderboard.LeaderboardGroup; import com.sap.sse.common.Util; @@ -36,7 +37,7 @@ public class EventImpl extends EventBaseImpl implements Event { private ConcurrentLinkedQueue leaderboardGroups; - private transient ConcurrentHashMap> imageSizeFetchers; + private transient ConcurrentHashMap> imageSizeFetchers; private transient ExecutorService executor; public EventImpl(String name, TimePoint startDate, TimePoint endDate, String venueName, boolean isPublic, UUID id) { @@ -103,13 +104,13 @@ public class EventImpl extends EventBaseImpl implements Event { return leaderboardGroups.remove(leaderboardGroup); } - private Future getOrCreateImageSizeCalculator(final URL imageURL) { - Future imageSizeFetcher = imageSizeFetchers.get(imageURL); + private Future getOrCreateImageSizeCalculator(final URL imageURL) { + Future imageSizeFetcher = imageSizeFetchers.get(imageURL); if (imageSizeFetcher == null) { - imageSizeFetcher = executor.submit(new Callable() { + imageSizeFetcher = executor.submit(new Callable() { @Override - public Dimension call() throws IOException { - Dimension result = null; + public ImageSize call() throws IOException { + ImageSize result = null; ImageInputStream in = null; try { URLConnection conn = imageURL.openConnection(); @@ -119,7 +120,7 @@ public class EventImpl extends EventBaseImpl implements Event { ImageReader reader = readers.next(); try { reader.setInput(in); - result = new Dimension(reader.getWidth(0), reader.getHeight(0)); + result = new ImageSizeImpl(reader.getWidth(0), reader.getHeight(0)); } finally { reader.dispose(); } @@ -138,8 +139,8 @@ public class EventImpl extends EventBaseImpl implements Event { } @Override - public Dimension getImageSize(URL imageURL) throws InterruptedException, ExecutionException { - Future imageSizeCalculator = getOrCreateImageSizeCalculator(imageURL); + public ImageSize getImageSize(URL imageURL) throws InterruptedException, ExecutionException { + Future imageSizeCalculator = getOrCreateImageSizeCalculator(imageURL); return imageSizeCalculator.get(); } @@ -171,8 +172,10 @@ public class EventImpl extends EventBaseImpl implements Event { @Override public void setImageURLs(Iterable imageURLs) { super.setImageURLs(imageURLs); - for (URL imageURL : imageURLs) { - refreshImageSizeFetcher(imageURL); + if (imageURLs != null) { + for (URL imageURL : imageURLs) { + refreshImageSizeFetcher(imageURL); + } } } @@ -191,8 +194,10 @@ public class EventImpl extends EventBaseImpl implements Event { @Override public void setSponsorImageURLs(Iterable sponsorImageURLs) { super.setSponsorImageURLs(sponsorImageURLs); - for (URL imageURL : sponsorImageURLs) { - refreshImageSizeFetcher(imageURL); + if (sponsorImageURLs != null) { + for (URL imageURL : sponsorImageURLs) { + refreshImageSizeFetcher(imageURL); + } } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java index 2ab607fbb0e..5c61a242a33 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java @@ -36,6 +36,7 @@ import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.Leg; import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.RaceColumn; +import com.sap.sailing.domain.base.RaceColumnInSeries; import com.sap.sailing.domain.base.RaceColumnListener; import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.common.Distance; @@ -1248,7 +1249,8 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race Map>> competitorsFromBestToWorstTasks = new HashMap<>(); for (final RaceColumn raceColumn : this.getRaceColumns()) { result.createEmptyRaceColumn(raceColumn.getName(), raceColumn.isMedalRace(), - this.getScoringScheme().isValidInTotalScore(this, raceColumn, timePoint)); + raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null, + raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null); for (Fleet fleet : raceColumn.getFleets()) { RegattaAndRaceIdentifier raceIdentifier = null; RaceDTO race = null; @@ -1261,8 +1263,10 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race } // Note: the RaceColumnDTO won't be created by the following addRace call because it has been created // above by the result.createEmptyRaceColumn call - result.addRace(raceColumn.getName(), raceColumn.getExplicitFactor(), raceColumn.getFactor(), fleetDTO, - raceColumn.isMedalRace(), raceIdentifier, race); + result.addRace(raceColumn.getName(), raceColumn.getExplicitFactor(), raceColumn.getFactor(), + raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null, + raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null, + fleetDTO, raceColumn.isMedalRace(), raceIdentifier, race); } FutureTask> task = new FutureTask>(new Callable>() { @Override @@ -1655,7 +1659,8 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race boolean waitForLatestAnalyses, Map> legRanksCache, LeaderboardDTOCalculationReuseCache cache) throws NoWindException { LegEntryDTO result; - if (trackedLeg == null || trackedLeg.getTime(timePoint) == null) { + final Duration time = trackedLeg.getTime(timePoint); + if (trackedLeg == null || time == null) { result = null; } else { result = new LegEntryDTO(); @@ -1677,7 +1682,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race Distance distanceTraveled = trackedLeg.getDistanceTraveled(timePoint); result.distanceTraveledInMeters = distanceTraveled == null ? null : distanceTraveled.getMeters(); result.estimatedTimeToNextWaypointInSeconds = trackedLeg.getEstimatedTimeToNextMarkInSeconds(timePoint, WindPositionMode.EXACT, cache); - result.timeInMilliseconds = trackedLeg.getTime(timePoint).asMillis(); + result.timeInMilliseconds = time.asMillis(); result.finished = trackedLeg.hasFinishedLeg(timePoint); result.gapToLeaderInSeconds = trackedLeg.getGapToLeaderInSeconds(timePoint, legRanksCache.get(trackedLeg.getLeg()).entrySet().iterator().next().getKey(), WindPositionMode.LEG_MIDDLE); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointWinnerGetsSixIgnoringRaceCount.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointWinnerGetsSixIgnoringRaceCount.java new file mode 100644 index 00000000000..0d8b0a82b86 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointWinnerGetsSixIgnoringRaceCount.java @@ -0,0 +1,20 @@ +package com.sap.sailing.domain.leaderboard.impl; + +import com.sap.sailing.domain.common.ScoringSchemeType; + +public class HighPointWinnerGetsSixIgnoringRaceCount extends HighPointWinnerGetsSix { + private static final long serialVersionUID = -6974180846946052247L; + + /** + * Ignores the number of races scored, making the simple total points the first ranking criterion. + */ + @Override + public int compareByNumberOfRacesScored(int competitor1NumberOfRacesScored, int competitor2NumberOfRacesScored) { + return 0; + } + + @Override + public ScoringSchemeType getType() { + return ScoringSchemeType.HIGH_POINT_WINNER_GETS_SIX_IGNORING_RACE_COUNT; + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardGroupImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardGroupImpl.java index 2e654c8ead7..7d4a5736602 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardGroupImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardGroupImpl.java @@ -31,8 +31,9 @@ public class LeaderboardGroupImpl extends LeaderboardGroupBaseImpl implements Le /** * Creates a new leaderboard group with a new UUID as its ID. */ - public LeaderboardGroupImpl(String name, String description, boolean displayGroupsInReverseOrder, List leaderboards) { - this(UUID.randomUUID(), name, description, displayGroupsInReverseOrder, leaderboards); + public LeaderboardGroupImpl(String name, String description, String displayName, + boolean displayGroupsInReverseOrder, List leaderboards) { + this(UUID.randomUUID(), name, description, displayName, displayGroupsInReverseOrder, leaderboards); } private synchronized void writeObject(ObjectOutputStream oos) throws IOException { @@ -42,8 +43,9 @@ public class LeaderboardGroupImpl extends LeaderboardGroupBaseImpl implements Le /** * Use this constructor when loading or deserializing a leaderboard group and the ID is known and is provided to the constructor. */ - public LeaderboardGroupImpl(UUID id, String name, String description, boolean displayGroupsInReverseOrder, List leaderboards) { - super(id, name, description); + public LeaderboardGroupImpl(UUID id, String name, String description, String displayName, + boolean displayGroupsInReverseOrder, List leaderboards) { + super(id, name, description, displayName); this.displayGroupsInReverseOrder = displayGroupsInReverseOrder; this.leaderboards = new ArrayList(leaderboards); this.listeners = new HashSet(); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/masterdataimport/TopLevelMasterData.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/masterdataimport/TopLevelMasterData.java index 61080855d20..753278e985f 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/masterdataimport/TopLevelMasterData.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/masterdataimport/TopLevelMasterData.java @@ -73,14 +73,11 @@ public class TopLevelMasterData implements Serializable { HashSet eventSet = new HashSet(); eventsForLeaderboardGroup.put(leaderboardGroup, eventSet); for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) { - if (leaderboard instanceof RegattaLeaderboard) { - RegattaLeaderboard regattaLeaderboard = (RegattaLeaderboard) leaderboard; - CourseArea courseArea = regattaLeaderboard.getRegatta().getDefaultCourseArea(); - if (courseArea != null) { - Event event = eventForCourseArea.get(courseArea); - if (event != null) { - eventSet.add(event); - } + CourseArea courseArea = leaderboard.getDefaultCourseArea(); + if (courseArea != null) { + Event event = eventForCourseArea.get(courseArea); + if (event != null) { + eventSet.add(event); } } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSFix.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSFix.java index f1764bdd193..673b1870980 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSFix.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSFix.java @@ -3,6 +3,6 @@ package com.sap.sailing.domain.tracking; import com.sap.sailing.domain.base.Timed; import com.sap.sailing.domain.common.SpeedWithBearing; -public interface GPSFix extends Positioned, Timed, WithValidityCache { +public interface GPSFix extends Positioned, Timed, WithValidityCache, WithEstimatedSpeedCache { SpeedWithBearing getSpeedAndBearingRequiredToReach(GPSFix to); } \ No newline at end of file diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/MarkPassingListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/MarkPassingListener.java deleted file mode 100755 index 60294b7041a..00000000000 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/MarkPassingListener.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.sap.sailing.domain.tracking; - - -public interface MarkPassingListener { - void legCompleted(MarkPassing markPassing); -} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/WithEstimatedSpeedCache.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/WithEstimatedSpeedCache.java new file mode 100755 index 00000000000..d439add3db6 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/WithEstimatedSpeedCache.java @@ -0,0 +1,17 @@ +package com.sap.sailing.domain.tracking; + +import com.sap.sailing.domain.common.SpeedWithBearing; + +public interface WithEstimatedSpeedCache { + boolean isEstimatedSpeedCached(); + + /** + * Returns a valid result if {@link #isEstimatedSpeedCached()} returns true + */ + SpeedWithBearing getCachedEstimatedSpeed(); + + void invalidateEstimatedSpeedCache(); + + void cacheEstimatedSpeed(SpeedWithBearing estimatedSpeed); + +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/WithValidityCache.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/WithValidityCache.java index 66c1c639df4..7e73e89410d 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/WithValidityCache.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/WithValidityCache.java @@ -3,7 +3,10 @@ package com.sap.sailing.domain.tracking; public interface WithValidityCache { boolean isValidityCached(); - boolean isValid(); + /** + * Returns a valid result if {@link #isValidCached()} returns true + */ + boolean isValidCached(); void invalidateCache(); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/AbstractGPSFixImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/AbstractGPSFixImpl.java index 8471cbc5d53..ff982b430db 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/AbstractGPSFixImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/AbstractGPSFixImpl.java @@ -48,14 +48,21 @@ public abstract class AbstractGPSFixImpl implements GPSFix { Speed speed = distance.inTime(to.getTimePoint().asMillis()-getTimePoint().asMillis()); return new KnotSpeedWithBearingImpl(speed.getKnots(), bearing); } - + + /** + * Subclasses overriding this method also need to override {@link #isValid} with a useful implementation. + */ @Override public boolean isValidityCached() { return false; } + /** + * Only evaluated if {@link #isValidityCached()} returns true. Therefore, subclassess that + * override {@link #isValidityCached()} also need to override this method accordingly. + */ @Override - public boolean isValid() { + public boolean isValidCached() { return false; } @@ -66,4 +73,29 @@ public abstract class AbstractGPSFixImpl implements GPSFix { @Override public void cacheValidity(boolean isValid) { } + + /** + * Subclasses overriding this method also need to override {@link #getCachedEstimatedSpeed()} with a useful implementation. + */ + @Override + public boolean isEstimatedSpeedCached() { + return false; + } + + /** + * Only evaluated if {@link #isEstimatedSpeedCached()} returns true. Therefore, subclassess that + * override {@link #isEstimatedSpeedCached()} also need to override this method accordingly. + */ + @Override + public SpeedWithBearing getCachedEstimatedSpeed() { + return null; + } + + @Override + public void invalidateEstimatedSpeedCache() { + } + + @Override + public void cacheEstimatedSpeed(SpeedWithBearing estimatedSpeed) { + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CompactGPSFixImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CompactGPSFixImpl.java index 77fd16a6521..1071c4b254e 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CompactGPSFixImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CompactGPSFixImpl.java @@ -1,8 +1,12 @@ package com.sap.sailing.domain.tracking.impl; +import com.sap.sailing.domain.common.AbstractBearing; import com.sap.sailing.domain.common.AbstractPosition; +import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.SpeedWithBearing; import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.AbstractSpeedWithAbstractBearingImpl; import com.sap.sailing.domain.common.impl.AbstractTimePoint; import com.sap.sailing.domain.tracking.GPSFix; @@ -19,6 +23,23 @@ import com.sap.sailing.domain.tracking.GPSFix; */ public class CompactGPSFixImpl extends AbstractGPSFixImpl { private static final long serialVersionUID = 8167588584536992501L; + + /** + * Bit mask for {@link #whatIsCached}, telling whether validity is currently cached + */ + private static final byte IS_VALIDITY_CACHED = 1<<0; + + /** + * Bit mask for {@link #whatIsCached}, telling whether the estimated speed is currently cached + */ + private static final byte IS_ESTIMATED_SPEED_CACHED = 1<<1; + + /** + * Bit mask for {@link #whatIsCached}, telling the validity of the fix; only relevant if + * {@link #whatIsCached}&{@link #IS_VALIDITY_CACHED} != 0 + */ + private static final byte VALIDITY = 1<<2; + private final double latDeg; private final double lngDeg; private final long timePointAsMillis; @@ -28,7 +49,19 @@ public class CompactGPSFixImpl extends AbstractGPSFixImpl { * needs to be invalidated as soon as fixes are added to the containing track which may have an impact * on this fix's validity. -1 means "no value"; 0 means invalid, 1 means valid. */ - private byte validityCache = -1; + private byte whatIsCached = 0; + + /** + * When {@link #whatIsCached}&{@link #IS_ESTIMATED_SPEED_CACHED} != 0, this field tells the estimated speed's + * true "bearing" (true course over ground) in degrees. + */ + private double cachedEstimatedSpeedBearingInDegrees; + + /** + * When {@link #whatIsCached}&{@link #IS_ESTIMATED_SPEED_CACHED} != 0, this field tells the estimated speed + * in knots. + */ + private double cachedEstimatedSpeedInKnots; private class CompactPosition extends AbstractPosition { private static final long serialVersionUID = 5621506820766614178L; @@ -53,6 +86,29 @@ public class CompactGPSFixImpl extends AbstractGPSFixImpl { } } + private class CompactEstimatedSpeedBearing extends AbstractBearing { + private static final long serialVersionUID = 8549231429037883121L; + + @Override + public double getDegrees() { + return cachedEstimatedSpeedBearingInDegrees; + } + } + + private class CompactEstimatedSpeed extends AbstractSpeedWithAbstractBearingImpl { + private static final long serialVersionUID = -5871855443391817248L; + + @Override + public Bearing getBearing() { + return new CompactEstimatedSpeedBearing(); + } + + @Override + public double getKnots() { + return cachedEstimatedSpeedInKnots; + } + } + public CompactGPSFixImpl(Position position, TimePoint timePoint) { latDeg = position.getLatDeg(); lngDeg = position.getLngDeg(); @@ -80,22 +136,52 @@ public class CompactGPSFixImpl extends AbstractGPSFixImpl { @Override public boolean isValidityCached() { - return validityCache != -1; + return (whatIsCached & IS_VALIDITY_CACHED) != 0; } @Override - public boolean isValid() { - return validityCache == 1; + public boolean isValidCached() { + assert isValidityCached(); + return (whatIsCached & VALIDITY) != 0; } @Override public void invalidateCache() { - validityCache = -1; + whatIsCached &= ~IS_VALIDITY_CACHED; } @Override public void cacheValidity(boolean isValid) { - validityCache = (byte) (isValid ? 1 : 0); + if (isValid) { + whatIsCached |= IS_VALIDITY_CACHED | VALIDITY; + } else { + whatIsCached |= IS_VALIDITY_CACHED; + whatIsCached &= ~VALIDITY; + } + } + + @Override + public boolean isEstimatedSpeedCached() { + return (whatIsCached & IS_ESTIMATED_SPEED_CACHED) != 0; + } + + @Override + public SpeedWithBearing getCachedEstimatedSpeed() { + assert isEstimatedSpeedCached(); + return new CompactEstimatedSpeed(); + } + + @Override + public void invalidateEstimatedSpeedCache() { + whatIsCached &= ~IS_ESTIMATED_SPEED_CACHED; + } + + @Override + public void cacheEstimatedSpeed(SpeedWithBearing estimatedSpeed) { + cachedEstimatedSpeedBearingInDegrees = estimatedSpeed.getBearing().getDegrees(); + cachedEstimatedSpeedInKnots = estimatedSpeed.getKnots(); + whatIsCached |= IS_ESTIMATED_SPEED_CACHED; } + } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicGPSFixMovingTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicGPSFixMovingTrackImpl.java index 7e9472cd8ed..9fd5fb15d0e 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicGPSFixMovingTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicGPSFixMovingTrackImpl.java @@ -123,7 +123,7 @@ public class DynamicGPSFixMovingTrackImpl extends GPSFixTrackImpl extends TrackImpl private transient MaxSpeedCache maxSpeedCache; + private int estimatedSpeedCacheHits; + private int estimatedSpeedCacheMisses; + public GPSFixTrackImpl(ItemType trackedItem, long millisecondsOverWhichToAverage) { this(trackedItem, millisecondsOverWhichToAverage, DEFAULT_MAX_SPEED_FOR_SMOOTHING); } @@ -230,7 +233,7 @@ public class GPSFixTrackImpl extends TrackImpl } @Override - public boolean isValid() { + public boolean isValidCached() { return false; } @@ -241,6 +244,24 @@ public class GPSFixTrackImpl extends TrackImpl @Override public void cacheValidity(boolean isValid) { } + + @Override + public boolean isEstimatedSpeedCached() { + return false; + } + + @Override + public SpeedWithBearing getCachedEstimatedSpeed() { + return null; + } + + @Override + public void invalidateEstimatedSpeedCache() { + } + + @Override + public void cacheEstimatedSpeed(SpeedWithBearing estimatedSpeed) { + } } @@ -577,12 +598,29 @@ public class GPSFixTrackImpl extends TrackImpl @Override public SpeedWithBearing getEstimatedSpeed(TimePoint at) { lockForRead(); + FixType ceil = getInternalFixes().ceiling(createDummyGPSFix(at)); try { - SpeedWithBearingWithConfidence estimatedSpeed = getEstimatedSpeed(at, getInternalFixes(), + final SpeedWithBearing result; + if (ceil != null && ceil.getTimePoint().equals(at) && ceil.isEstimatedSpeedCached()) { + estimatedSpeedCacheHits++; + result = ceil.getCachedEstimatedSpeed(); + } else { + estimatedSpeedCacheMisses++; + SpeedWithBearingWithConfidence estimatedSpeed = getEstimatedSpeed(at, getInternalFixes(), ConfidenceFactory.INSTANCE.createExponentialTimeDifferenceWeigher( // use a minimum confidence to avoid the bearing to flip to 270deg in case all is zero getMillisecondsOverWhichToAverageSpeed()/2, /* minimumConfidence */ 0.00000001)); // half confidence if half averaging interval apart - return estimatedSpeed == null ? null : estimatedSpeed.getObject(); + result = estimatedSpeed == null ? null : estimatedSpeed.getObject(); + if (estimatedSpeed != null) { + if (ceil != null && ceil.getTimePoint().equals(at)) { + ceil.cacheEstimatedSpeed(result); + } + } + } + if (logger.isLoggable(Level.FINE) && (estimatedSpeedCacheHits + estimatedSpeedCacheMisses) % 1000 == 0) { + logger.fine("estimated speed cache hits/misses: "+estimatedSpeedCacheHits+"/"+estimatedSpeedCacheMisses); + } + return result; } finally { unlockAfterRead(); } @@ -769,6 +807,9 @@ public class GPSFixTrackImpl extends TrackImpl * * But even for a track with {@link GPSFixMoving} fixes this is a good algorithm because in case the speed changes * significantly between fixes, it is important to know the next fix to understand and consider the trend. + * + * @see #getMillisecondsOverWhichToAverage() + * @see #getMillisecondsOverWhichToAverageSpeed() */ protected List getFixesRelevantForSpeedEstimation(TimePoint at, NavigableSet fixesToUseForSpeedEstimation) { lockForRead(); @@ -858,7 +899,7 @@ public class GPSFixTrackImpl extends TrackImpl } /** - * When redefining this method, make sure to redefine {@link #invalidateValidityAndDistanceCaches(GPSFix)} + * When redefining this method, make sure to redefine {@link #invalidateValidityAndEstimatedSpeedAndDistanceCaches(GPSFix)} * accordingly. This implementation checks the immediate previous and next fix for e. Therefore, when * adding a fix, only immediately adjacent fix's validity caches need to be invalidated. *

@@ -875,7 +916,7 @@ public class GPSFixTrackImpl extends TrackImpl isValid = true; } else { if (e.isValidityCached()) { - isValid = e.isValid(); + isValid = e.isValidCached(); } else { FixType previous = rawFixes.lower(e); final boolean atLeastOnePreviousFixInRange = previous != null && e.getTimePoint().asMillis() - previous.getTimePoint().asMillis() <= getMillisecondsOverWhichToAverageSpeed(); @@ -918,7 +959,7 @@ public class GPSFixTrackImpl extends TrackImpl * of the gpsFix "upwards." However, if the adjacent earlier fixes have changed their validity by the addition * of gpsFix, the distance cache must be invalidated starting with the first fix whose validity changed. */ - protected void invalidateValidityAndDistanceCaches(FixType gpsFix) { + protected void invalidateValidityAndEstimatedSpeedAndDistanceCaches(FixType gpsFix) { assertWriteLock(); TimePoint distanceCacheInvalidationStart = gpsFix.getTimePoint(); // see also bug 968: cache entries for intervals ending after the last fix need to be removed because they are @@ -930,6 +971,10 @@ public class GPSFixTrackImpl extends TrackImpl distanceCacheInvalidationStart = last.getTimePoint().plus(1); // add one millisecond to invalidate *after* the last fix only } gpsFix.invalidateCache(); + for (FixType fixOnWhichToInvalidateEstimatedSpeed : getFixesRelevantForSpeedEstimation(gpsFix.getTimePoint(), + getInternalRawFixes())) { + fixOnWhichToInvalidateEstimatedSpeed.invalidateEstimatedSpeedCache(); + } Iterable lowers = getEarlierFixesWhoseValidityMayBeAffected(gpsFix); for (FixType lower : lowers) { boolean lowerWasValid = isValid(getRawFixes(), lower); @@ -1014,7 +1059,7 @@ public class GPSFixTrackImpl extends TrackImpl try { firstFixInTrack = getRawFixes().isEmpty(); result = addWithoutLocking(fix); - invalidateValidityAndDistanceCaches(fix); + invalidateValidityAndEstimatedSpeedAndDistanceCaches(fix); } finally { unlockAfterWrite(); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegImpl.java index f725b950405..f01da52e594 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegImpl.java @@ -257,7 +257,7 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener { @Override public void waypointAdded(int zeroBasedIndex, Waypoint waypointThatGotAdded) { clearCaches(); // necessary because the competitor tracks ordered by rank may change for legs adjacent to the waypoint added - } + } // and because the leg's from/to waypoints may have changed @Override public void waypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java index 1b2491871b4..60bb970f31f 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java @@ -220,7 +220,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl * them. */ private transient SmartFutureCache>, EmptyUpdateInterval> maneuverCache; - + private transient Map> directionFromStartToNextMarkCache; private final ConcurrentHashMap> markTracks; @@ -234,7 +234,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl private transient Timer cacheInvalidationTimer; private transient Object cacheInvalidationTimerLock; - protected transient HashMap attachedRaceLogs; + protected transient ConcurrentHashMap attachedRaceLogs; /** * The time delay to the current point in time in milliseconds. @@ -289,7 +289,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl super(race, trackedRegatta, windStore, millisecondsOverWhichToAverageWind); shortTimeWindCache = new ShortTimeWindCache(this, millisecondsOverWhichToAverageWind / 2); locksForMarkPassings = new IdentityHashMap<>(); - attachedRaceLogs = new HashMap<>(); + attachedRaceLogs = new ConcurrentHashMap<>(); this.status = new TrackedRaceStatusImpl(TrackedRaceStatusEnum.PREPARED, 0.0); this.statusNotifier = new Object[0]; this.loadingFromWindStoreLock = new NamedReentrantReadWriteLock("Loading from wind store lock for tracked race " @@ -467,7 +467,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl */ private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.defaultReadObject(); - attachedRaceLogs = new HashMap<>(); + attachedRaceLogs = new ConcurrentHashMap<>(); markPassingsTimes = new ArrayList>>(); cacheInvalidationTimerLock = new Object(); windStore = EmptyWindStore.INSTANCE; @@ -491,7 +491,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl @Override public synchronized void waitForLoadingFromGPSFixStoreToFinishRunning(RaceLog fromRaceLog) throws InterruptedException { - while (! attachedRaceLogs.containsKey(fromRaceLog.getId()) || loadingFromGPSFixStore) { + while (!attachedRaceLogs.containsKey(fromRaceLog.getId()) || loadingFromGPSFixStore) { wait(); } } @@ -506,7 +506,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl } }, /* nameForLocks */"Maneuver cache for race " + getRace().getName()); } - + /** * Precondition: race has already been set, e.g., in constructor before this method is called */ diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/util/SmartFutureCache.java b/java/com.sap.sailing.domain/src/com/sap/sailing/util/SmartFutureCache.java index b20bd3c8809..a1867c15b94 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/util/SmartFutureCache.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/util/SmartFutureCache.java @@ -507,7 +507,7 @@ public class SmartFutureCache> { * from the cache straight away (waitForLatest==false) or, if a re-calculation for the key is still * ongoing, the result of that ongoing re-calculation is returned. */ - public V get(K key, boolean waitForLatest) { + public V get(final K key, boolean waitForLatest) { V value = null; if (waitForLatest) { FutureTaskWithCancelBlocking future; @@ -545,11 +545,12 @@ public class SmartFutureCache> { } } // else no calculation currently going on; value has been fetched from latest cache entry } else { - LockUtil.lockForRead(getOrCreateLockForKey(key)); + final NamedReentrantReadWriteLock lock = getOrCreateLockForKey(key); + LockUtil.lockForRead(lock); try { value = cache.get(key); } finally { - LockUtil.unlockAfterRead(getOrCreateLockForKey(key)); + LockUtil.unlockAfterRead(lock); } } return value; @@ -576,4 +577,11 @@ public class SmartFutureCache> { return smartFutureCacheTaskReuseCounter; } + /** + * Removes the key from the cache. If any updates are still running, they may again insert the key into hte cache. + */ + public void remove(K key) { + cache(key, null); + } + } diff --git a/java/com.sap.sailing.gwt.ui.test/.classpath b/java/com.sap.sailing.gwt.ui.test/.classpath index 5cc60e2c543..d03a3fcb224 100755 --- a/java/com.sap.sailing.gwt.ui.test/.classpath +++ b/java/com.sap.sailing.gwt.ui.test/.classpath @@ -1,10 +1,10 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/GwtTestCaseColumnToggling.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/GwtTestCaseColumnToggling.java index f56b53061f8..18f7842172e 100644 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/GwtTestCaseColumnToggling.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/GwtTestCaseColumnToggling.java @@ -149,7 +149,7 @@ public class GwtTestCaseColumnToggling extends GWTTestCase { @Override public void onSuccess(Void result) { System.out.println("Added column to leaderboard."); - RaceColumnDTO race = new RaceColumnDTO(/* isValidInTotalScore */ null); + RaceColumnDTO race = new RaceColumnDTO(); race.setName(COLUMN1_NAME); race.setMedalRace(false); leaderboardPanel.addColumn(leaderboardPanel.createRaceColumn(race)); diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/LeaderboardPanelMock.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/LeaderboardPanelMock.java index 411b5379c72..6ee5c04a7c8 100644 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/LeaderboardPanelMock.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/LeaderboardPanelMock.java @@ -9,7 +9,7 @@ import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettingsFactory; -import com.sap.sailing.gwt.ui.leaderboard.SortableColumn; +import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSortableColumnWithMinMax; import com.sap.sailing.gwt.ui.leaderboard.SortedCellTable; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; import com.sap.sse.gwt.client.useragent.UserAgentDetails; @@ -20,12 +20,12 @@ public class LeaderboardPanelMock extends LeaderboardPanel { String leaderboardName, ErrorReporter errorReporter, StringMessages stringMessages) { super(sailingService, new AsyncActionsExecutor(), LeaderboardSettingsFactory.getInstance().createNewDefaultSettings( - /* racesToShow */null, /* namesOfRacesToShow */ null, null, /* autoExpandFirstRace */ false), new CompetitorSelectionModel( + /* racesToShow */null, /* namesOfRacesToShow */ null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true), new CompetitorSelectionModel( /* hasMultiSelection */true), leaderboardName, errorReporter, stringMessages, new UserAgentDetails("gecko1_8"), /* showRaceDetails */ true); } @Override - public void addColumn(SortableColumn column) { + public void addColumn(LeaderboardSortableColumnWithMinMax column) { // TODO Auto-generated method stub super.addColumn(column); } diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java index db4b3ba8ab5..ba99ba90492 100755 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java @@ -209,7 +209,7 @@ public class PolarSheetGenerationServiceTest { } @Override - public boolean isValid() { + public boolean isValidCached() { return true; } diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/TestColumnSwapping.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/TestColumnSwapping.java index 00113e74ea5..b828e4b4245 100755 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/TestColumnSwapping.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/TestColumnSwapping.java @@ -61,9 +61,9 @@ public class TestColumnSwapping { return UUID.randomUUID().toString(); } }, /* hasOverallDetails */ false); - leaderboardOriginalDTO.addRace("Race1", /* explicitFactor */ null, 2., DEFAULT_FLEET, true, null, null); - leaderboardOriginalDTO.addRace("Race3", /* explicitFactor */ null, 2., DEFAULT_FLEET, true, null, null); - leaderboardOriginalDTO.addRace("Race2", /* explicitFactor */ null, 2., DEFAULT_FLEET, true, null, null); + leaderboardOriginalDTO.addRace("Race1", /* explicitFactor */ null, 2., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null); + leaderboardOriginalDTO.addRace("Race3", /* explicitFactor */ null, 2., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null); + leaderboardOriginalDTO.addRace("Race2", /* explicitFactor */ null, 2., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null); } catch (Exception e) { e.printStackTrace(); } @@ -126,9 +126,9 @@ public class TestColumnSwapping { } }, /* hasOverallDetails */ false); assertNotNull("Leaderboard != NULL", lb); - lb.addRace("1", /* explicitFactor */ null, 1., DEFAULT_FLEET, false, null, null); - lb.addRace("2", /* explicitFactor */ null, 1., DEFAULT_FLEET, false, null, null); - lb.addRace("3", /* explicitFactor */ null, 1., DEFAULT_FLEET, true, null, null); + lb.addRace("1", /* explicitFactor */ null, 1., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, false, null, null); + lb.addRace("2", /* explicitFactor */ null, 1., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, false, null, null); + lb.addRace("3", /* explicitFactor */ null, 1., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null); lb.moveRaceDown("1"); String[] s = new String[] { "2", "1", "3" }; for (int i = 0; i < lb.getRaceList().size(); i++) { diff --git a/java/com.sap.sailing.gwt.ui/.settings/com.google.gwt.eclipse.core.prefs b/java/com.sap.sailing.gwt.ui/.settings/com.google.gwt.eclipse.core.prefs index 946db750b2f..b7c13760e13 100755 --- a/java/com.sap.sailing.gwt.ui/.settings/com.google.gwt.eclipse.core.prefs +++ b/java/com.sap.sailing.gwt.ui/.settings/com.google.gwt.eclipse.core.prefs @@ -1,4 +1,3 @@ eclipse.preferences.version=1 entryPointModules= filesCopiedToWebInfLib=gwt-servlet.jar -gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDMgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0UgLWNvbXBpbGVSZXBvcnQgLVhzb3ljRGV0YWlsZWRdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5SYWNlQm9hcmQ8L2VudHJ5LXBvaW50LW1vZHVsZT48L2d3dC1jb21waWxlLXNldHRpbmdzPg\=\= diff --git a/java/com.sap.sailing.gwt.ui/.settings/org.eclipse.core.resources.prefs b/java/com.sap.sailing.gwt.ui/.settings/org.eclipse.core.resources.prefs index 548f1163d5a..8aa4ae5d789 100755 --- a/java/com.sap.sailing.gwt.ui/.settings/org.eclipse.core.resources.prefs +++ b/java/com.sap.sailing.gwt.ui/.settings/org.eclipse.core.resources.prefs @@ -1,3 +1,4 @@ eclipse.preferences.version=1 +encoding//src/main/java/com/sap/sailing/gwt/home/client/i18n/TextMessages_de.properties=UTF-8 encoding//src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties=UTF-8 encoding//src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties=UTF-8 diff --git a/java/com.sap.sailing.gwt.ui/AdminConsole.css b/java/com.sap.sailing.gwt.ui/AdminConsole.css index 22a9b491cb8..d87c9892dd9 100644 --- a/java/com.sap.sailing.gwt.ui/AdminConsole.css +++ b/java/com.sap.sailing.gwt.ui/AdminConsole.css @@ -151,6 +151,7 @@ fieldset legend { .gwt-Button { margin: 3px 2px 3px 5px; + outline: 0; } .gwt-Button:hover { diff --git a/java/com.sap.sailing.gwt.ui/CommonControls.css b/java/com.sap.sailing.gwt.ui/CommonControls.css old mode 100644 new mode 100755 index 23b8c26a282..68dd8edf6e6 --- a/java/com.sap.sailing.gwt.ui/CommonControls.css +++ b/java/com.sap.sailing.gwt.ui/CommonControls.css @@ -30,6 +30,8 @@ body ::-webkit-scrollbar-track { background: #b6b6b6; -webkit-border-radius: 5px; -moz-border-radius: 5px; + -o-border-radius: 5px; + -ms-border-radius: 5px; border-radius: 5px; overflow: hidden; border: 1px solid #aaaaaa; @@ -41,6 +43,8 @@ body ::-webkit-scrollbar-track { margin: 12px; -webkit-border-radius: 5px; -moz-border-radius: 5px; + -o-border-radius: 5px; + -ms-border-radius: 5px; border-radius: 5px; width: auto !important; border: 1px solid #aaaaaa; @@ -54,9 +58,13 @@ body ::-webkit-scrollbar-track { position: relative; -webkit-border-top-left-radius: 4px; -moz-border-top-left-radius: 4px; + -o-border-top-left-radius: 4px; + -ms-border-top-left-radius: 4px; border-top-left-radius: 4px; -webkit-border-top-right-radius: 4px; -moz-border-top-right-radius: 4px; + -o-border-top-right-radius: 4px; + -ms-border-top-right-radius: 4px; border-top-right-radius: 4px; } @@ -136,10 +144,6 @@ body ::-webkit-scrollbar-track { color:#A1A1A1; } -.gwt-SplitLayoutPanel-HDragger { - width: 20px; -} - .gwt-ToggleButton { background: url(images/btn-cta-orangeleft.png) center top repeat-x; padding: 8px; @@ -152,18 +156,179 @@ body ::-webkit-scrollbar-track { color: #fff; } -.gwt-SplitLayoutPanel-HDragger { - width: 12px; +.gwt-SplitLayoutPanel-NorthSouthToggleButton-Panel { + position: absolute; + right: 10px; + top: 3px; +} + +.gwt-SplitLayoutPanel-EastToggleButton-Panel { + position: absolute; + bottom: 77px; + margin-left: -35px; +} + +.gwt-SplitLayoutPanel-EastToggleButton { + height: 40px; + background-color: white; + border: none; + -webkit-transform: rotate(-90deg); + -moz-transform: rotate(-90deg); + -o-transform: rotate(-90deg); + -ms-transform: rotate(-90deg); + padding-right: 25px; + padding-left: 30px; + margin-left: -15px; + border-bottom: 1px solid #cecece; + border-right: 1px solid #cecece; + border-left: 1px solid #cecece; + border-bottom-right-radius: 10px; + border-bottom-left-radius: 10px; +} + +.gwt-SplitLayoutPanel-EastToggleButton-Open { + background: white url(images/arrows_top_black_small.png) 96% 50% no-repeat; +} + +.gwt-SplitLayoutPanel-EastToggleButton-Closed { + background: white url(images/arrows_bottom_black_small.png) 96% 50% no-repeat; +} + +.gwt-SplitLayoutPanel-EastToggleButton:hover { + background-color: #ececec; +} + +.gwt-SplitLayoutPanel-EastToggleButton:focus { + outline:0 !important; +} + +.gwt-SplitLayoutPanel-NorthSouthToggleButton { + height: 33px; + background-color: #fff; + border: 1px solid #cecece; + margin-left: 5px; + padding: 2px 40px; + border-radius: 7px; + margin-top: 0px; +} + +.gwt-SplitLayoutPanel-NorthSouthToggleButton-Closed { + background: white url(images/arrows_top_black_small.png) 95% 50% no-repeat; + font-weight: normal; +} + +.gwt-SplitLayoutPanel-NorthSouthToggleButton-Open { + background: white url(images/arrows_bottom_black_small.png) 95% 50% no-repeat; + font-weight: bold; + border-width: 3px; +} + +.gwt-SplitLayoutPanel-NorthSouthToggleButton:focus { + outline:0 !important; +} + +.gwt-SplitLayoutPanel-NorthSouthToggleButton:hover { + background-color: #ececec; } .gwt-SplitLayoutPanel-HDragger { - background: #cbcaca url(images/thumb_vertical.png) center center no-repeat; - cursor: col-resize; + position: absolute; + height: 60px !important; + width: 25px !important; + margin-left: 3px; + top: 40% !important; + background: #ffffff url(images/thumb_vertical.png) 70% 50% no-repeat; + border: 2px solid #cecece; + border-left: 0px; + cursor: col-resize; + border-top-right-radius: 25px; + border-bottom-right-radius: 25px; + z-index: 1; +} + +.SplitLayoutPanel-Divider-Horizontal { + background: #ffffff; +} + +.SplitLayoutPanel-Divider-Vertical { + background: #ffffff; + border-top: 1px solid #cecece; } .gwt-SplitLayoutPanel-VDragger { - background: #cbcaca url(images/thumb_horz.png) center center no-repeat; - cursor: row-resize; + position: absolute; + width: 60px !important; + height: 21px !important; + left: 48% !important; + top: 2px !important; + background: #ffffff url(images/thumb_horz.png) 50% 30% no-repeat; + cursor: row-resize; + border: 2px solid #cecece; + border-bottom: 0px; + border-top-left-radius: 25px; + border-top-right-radius: 25px; + opacity: .7; + z-index: 1; +} + +.gwt-SplitLayoutPanel-CloseAndSettingsButton-Panel { + position: absolute; + right: 2px; + top: 2px; + z-index: 1; +} + +.gwt-SplitLayoutPanel-CloseButton { + margin-left: 10px; + border-top-left-radius: 25px; + border-top-right-radius: 25px; + border-bottom-left-radius: 25px; + border-bottom-right-radius: 25px; + background: white url(images/close_black.png) center no-repeat; + z-index: 1; + width: 28px; + height: 28px; + cursor: pointer; +} + +.gwt-SplitLayoutPanel-CloseButton:focus { + outline:0 !important; +} + +.gwt-SplitLayoutPanel-CloseButton:hover { + background-color: #ececec; +} + +.gwt-SettingsButton { + border: none; + background: grey url(images/SAP_RV_Settings.png) center no-repeat; + z-index: 1; + width: 28px; + height: 28px; + cursor: pointer; +} + +.gwt-SettingsButton:hover { + background-color: #cecece; +} + +.gwt-SettingsButton:focus { + outline: 0 !important; +} + +.gwt-MapSettingsButton { + margin-right: 30px; + margin-top: 5px; + border-top-left-radius: 25px; + border-top-right-radius: 25px; + border-bottom-left-radius: 25px; + border-bottom-right-radius: 25px; + border: none; + background: white url(images/SAP_RV_SettingsBlack.png) center no-repeat; + z-index: 1; + width: 28px; + height: 28px; + cursor: pointer; } .gwt-ToggleButton-up-hovering, .gwt-ToggleButton-down-hovering { @@ -310,15 +475,13 @@ body ::-webkit-scrollbar-track { } .timeLinePanel { - color: #fff; + color: black; width: 100%; position: fixed; bottom: 0; z-index: 9; - background: #7c7b7c; - box-shadow: 0 0 20px #858585; - -webkit-box-shadow: 0 0 20px #858585; - -moz-box-shadow: 0 0 20px #858585; + background: #f6f6f6; + border-top: 2px solid #cecece; } .timeLineInnerBgPanel { @@ -337,8 +500,6 @@ body ::-webkit-scrollbar-track { .timePanel-controls { margin: 0; padding: 7px 12px 0 12px; - -webkit-box-shadow: 0px -7px 8px 0px rgba(84, 84, 84, 1); - box-shadow: 0px -7px 8px 0px rgba(84, 84, 84, 1); height: 29px; } @@ -352,7 +513,7 @@ body ::-webkit-scrollbar-track { .timePanel-timeslider-play { float:left; - padding: 10px; + padding: 10px 4px; height: 24px; margin: 5px 14px 5px 0; } @@ -395,7 +556,7 @@ body ::-webkit-scrollbar-track { .timePanel-controls-timeDelay { float: right; - margin: 0 10px 0 0; + margin: 0 50px 0 0; } .timePanel-controls-playmode{ @@ -421,8 +582,6 @@ body ::-webkit-scrollbar-track { } .timePanelInnerWrapper { - -webkit-box-shadow: inset 0px -7px 13px 0px rgba(84, 84, 84, 1); - box-shadow: inset 0px -7px 13px 0px rgba(84, 84, 84, 1); } @@ -438,6 +597,8 @@ body ::-webkit-scrollbar-track { box-shadow: 0 0 20px #858585; -moz-box-shadow: 0 0 20px #858585; -webkit-box-shadow: 0 0 20px #858585; + -o-box-shadow: 0 0 20px #858585; + -ms-box-shadow: 0 0 20px #858585; z-index: 1; } @@ -456,7 +617,7 @@ body ::-webkit-scrollbar-track { right:0; } -.globalNavigationLink, .globalNavigationLink:visited { +.globalNavigation-Link, .globalNavigation-Link:visited { font-family: OpenSansSemiBold, Verdana, Arial, sans-serif; border: none; display: inline-block; @@ -476,13 +637,13 @@ body ::-webkit-scrollbar-track { white-space: nowrap; } -.globalNavigationLink:active { +.globalNavigation-Link:active { top: 1px; position: relative; border: none; } -.globalNavigationLink:hover { +.globalNavigation-Link:hover { background: url(images/btn-cta-orangeleft.png) center -30px repeat-x; cursor: pointer; } @@ -537,12 +698,17 @@ body ::-webkit-scrollbar-track { .gwt-DialogBox .Caption { font-size: 14px; - padding: 14px 10px; - background: url(images/dialogBox-bg.png) 0 0 repeat-x; + padding: 8px 10px; + background-color: #cecece; + border: 1px solid #cecece; -webkit-border-top-left-radius: 5px; -webkit-border-top-right-radius: 5px; -moz-border-radius-topleft: 5px; -moz-border-radius-topright: 5px; + -o-border-radius-topleft: 5px; + -o-border-radius-topright: 5px; + -ms-border-radius-topleft: 5px; + -ms-border-radius-topright: 5px; border-top-left-radius: 5px; border-top-right-radius: 5px; font-family: OpenSansSemiBold, Verdana, Arial, sans-serif; @@ -551,9 +717,13 @@ body ::-webkit-scrollbar-track { .gwt-DialogBox .dialogContent { background: #ffffff; padding: 10px; - border: 10px solid #a6a6a6; + border: 1px solid #cecece; -webkit-border-bottom-right-radius: 5px; -webkit-border-bottom-left-radius: 5px; + -o-border-bottom-right-radius: 5px; + -o-border-bottom-left-radius: 5px; + -ms-border-bottom-right-radius: 5px; + -ms-border-bottom-left-radius: 5px; -moz-border-radius-bottomright: 5px; -moz-border-radius-bottomleft: 5px; border-bottom-right-radius: 5px; @@ -563,23 +733,26 @@ body ::-webkit-scrollbar-track { .gwt-Button, .gwt-Button:visited { font-family: OpenSansSemiBold, Verdana, Arial, sans-serif; border: none; - - background: url(images/btn-cta-orangeleft.png) center top repeat-x; + background: url(images/btn-cta-orangeleft.png) center top repeat-x; display: inline-block; padding: 2px 10px 4px; color: #fff; text-decoration: none; -moz-border-radius: 5px; -webkit-border-radius: 5px; + -o-border-radius: 5px; + -ms-border-radius: 5px; border-radius: 5px; - -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); - -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); - text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.25); border-bottom: 1px solid rgba(0, 0, 0, 0.25); cursor: pointer; height: 24px; white-space: nowrap; + -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + -o-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + -ms-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.25); } .gwt-Button:active { @@ -600,6 +773,8 @@ body ::-webkit-scrollbar-track { border: 1px solid #919090; -moz-box-shadow: none; -webkit-box-shadow: none; + -o-box-shadow: none; + -ms-box-shadow: none; box-shadow: none; border: none; text-shadow: none; @@ -659,7 +834,6 @@ body ::-webkit-scrollbar-track { } .SettingsDialogComponent .dialogInnerHeadline { - background: url(images/settingsDialog_bg.png) repeat-x 0 0 #E5E5E5; border: 1px solid #CCCCCC; font-family: OpenSansSemiBold,Arial,Verdana,sans-serif; height: 21px; @@ -667,6 +841,8 @@ body ::-webkit-scrollbar-track { border-radius: 3px; -moz-border-radius: 3px; -webkit-border-radius: 3px; + -o-border-radius: 3px; + -ms-border-radius: 3px; margin: 0 0 10px 0; } @@ -743,4 +919,49 @@ video { .pointeredLink { cursor: pointer; cursor: hand; -} \ No newline at end of file +} + +.betterCheckboxCellSelected { + margin: 0px; + height: 18px; + width: 18px; + border: 1px; + background-color: #acacac; + vertical-align: middle; +} + +.betterCheckboxCellDeselected { + margin-left: 2px; /* account for the selection border that pushes a selected checkbox to the side */ + margin-top: 0px; + margin-bottom: 0px; + margin-right: 0px; + border: 1px; + height: 18px; + width: 18px; + vertical-align: middle; +} + +.LeaderboardPanel-FilterControl-Panel { + width: 380px; + border-top: 1px solid #ececec; + margin-top: 3px; +} + +.LeaderboardPanel-FilterClear-Button { + margin-top: 2px; + font-size: 10px; + outline: 0; + height: 23px; + background-color: #fff; + border: 1px solid #cecece; + border-radius: 7px; +} + +.LeaderboardPanel-FilterClear-Button:hover { + background-color: #ececec; +} + +.LeaderboardPanel-FilterControl-StatusLabel { + padding: 8px 2px; + font-size: 10px; +} diff --git a/java/com.sap.sailing.gwt.ui/CommonGwtControls.css b/java/com.sap.sailing.gwt.ui/CommonGwtControls.css deleted file mode 100644 index e3449e68e54..00000000000 --- a/java/com.sap.sailing.gwt.ui/CommonGwtControls.css +++ /dev/null @@ -1,209 +0,0 @@ -.gwt-SplitLayoutPanel-HDragger { - width: 20px; -} - -.gwt-ToggleButton { - background: url(images/btn-cta-orangeleft.png) center top repeat-x; - padding: 8px; - margin: 0 11px 0 0; - border: 0; -} - -.gwt-ToggleButton-down { - background: url(images/btn-cta-orangeleft.png) center -30px repeat-x; - color: #fff; -} - -.gwt-SplitLayoutPanel-HDragger { - width: 12px; -} - -.gwt-SplitLayoutPanel-HDragger { - background: #cbcaca url(images/thumb_vertical.png) center center no-repeat; - cursor: col-resize; -} - -.gwt-SplitLayoutPanel-VDragger { - background: #cbcaca url(images/thumb_horz.png) center center no-repeat; - cursor: row-resize; -} - -.gwt-ToggleButton-up-hovering, .gwt-ToggleButton-down-hovering { - background: url(images/btn-cta-orangeleft-down.png) center top repeat-x; - color: #fff; -} - -.gwt-MenuBarPopup .menuPopupTopLeft { - background: none !important; -} - -.gwt-MenuBarPopup .menuPopupTopCenter { - background: none !important; -} - -.gwt-MenuBarPopup .menuPopupTopCenter { - background: none !important; -} - -.gwt-MenuBarPopup .menuPopupTopRight { - background: none !important; -} - -.gwt-MenuBarPopup .menuPopupMiddleLeft { - background: none !important; -} - -.gwt-MenuBarPopup .menuPopupMiddleRight { - background: none !important; -} - -.gwt-MenuBarPopup .menuPopupMiddleRight { - background: none !important; -} - -.gwt-MenuBarPopup .menuPopupBottomCenter { - background: none !important; -} - -.gwt-MenuBarPopup .menuPopupBottomRight { - background: none !important; -} - -.gwt-MenuBarPopup .menuPopupBottomLeft { - background: none !important; -} - -.gwt-MenuBar-vertical .gwt-MenuItem { - padding: 10px; - padding-bottom: 5px; - padding-top: 5px; -} - -.gwt-MenuBar .gwt-MenuItem-selected { - background: none repeat scroll 0 0 #efaa00; -} - -.gwt-DialogBox { - -webkit-box-shadow: 7px 7px 5px rgba(50, 50, 50, 0.75); - -moz-box-shadow: 7px 7px 5px rgba(50, 50, 50, 0.75); - box-shadow: 7px 7px 5px rgba(50, 50, 50, 0.75); - line-height: 1.4; -} - -.gwt-DialogBox .Caption { - font-size: 14px; - padding: 14px 10px; - background: url(images/dialogBox-bg.png) 0 0 repeat-x; - -webkit-border-top-left-radius: 5px; - -webkit-border-top-right-radius: 5px; - -moz-border-radius-topleft: 5px; - -moz-border-radius-topright: 5px; - border-top-left-radius: 5px; - border-top-right-radius: 5px; - font-family: UbuntuBold, Verdana, Arial, sans-serif; -} - -.gwt-DialogBox .dialogContent { - background: #ffffff; - padding: 10px; - border: 10px solid #a6a6a6; - -webkit-border-bottom-right-radius: 5px; - -webkit-border-bottom-left-radius: 5px; - -moz-border-radius-bottomright: 5px; - -moz-border-radius-bottomleft: 5px; - border-bottom-right-radius: 5px; - border-bottom-left-radius: 5px; -} - -.gwt-Button, .gwt-Button:visited { - font-family: UbuntuBold, Verdana, Arial, sans-serif; - border: none; - - background: url(images/btn-cta-orangeleft.png) center top repeat-x; - display: inline-block; - padding: 2px 10px 4px; - color: #fff; - text-decoration: none; - -moz-border-radius: 5px; - -webkit-border-radius: 5px; - border-radius: 5px; - -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); - -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); - text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.25); - border-bottom: 1px solid rgba(0, 0, 0, 0.25); - cursor: pointer; - height: 24px; - white-space: nowrap; -} - -.gwt-Button:active { - top: 1px; - position: relative; - border: none; -} - -.gwt-Button:hover { - background: url(images/btn-cta-orangeleft.png) center -30px repeat-x; - cursor: pointer; -} - -.gwt-Button[disabled] { - cursor: default; - color: #888; - background: #F1F1F1; - border: 1px solid #919090; - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; - border: none; - text-shadow: none; -} - -.gwt-Button[disabled]:hover { - cursor: default; - color: #888; - background: #F1F1F1; -} - -.gwt-Button[disabled]:active { - cursor: default; - color: #888; - background: #F1F1F1; - top: 0; -} - -.gwt-ButtonLarge -.gwt-ButtonMedium -.gwt-ButtonSmall - -.gwt-DialogBox .boxPanel { - padding: 0 0 10px 0; -} - -.gwt-DialogBox .boxPanel:after { - display: inline-block; - content: " "; - display: block; - height: 0; - clear: both; - visibility: hidden; -} - -.gwt-DialogBox .settingsDialogLabel { - float: left; - padding: 3px 6px 0 0; -} - -.gwt-DialogBox .settingsDialogValue { - float: right; -} - -.gwt-DialogBox .additionalWidgets { - padding: 5px 0 0 0; - text-align: right; -} - -.gwt-DialogBox .gwt-Label { - line-height: 1.4; -} diff --git a/java/com.sap.sailing.gwt.ui/DataMining.css b/java/com.sap.sailing.gwt.ui/DataMining.css index 4fdabcb0f2e..a1e0d76ae36 100644 --- a/java/com.sap.sailing.gwt.ui/DataMining.css +++ b/java/com.sap.sailing.gwt.ui/DataMining.css @@ -80,6 +80,8 @@ table { box-shadow: 0 0 20px #858585; -moz-box-shadow: 0 0 20px #858585; -webkit-box-shadow: 0 0 20px #858585; + -o-box-shadow: 0 0 20px #858585; + -ms-box-shadow: 0 0 20px #858585; } .LogoAndTitlePanel:after { @@ -192,9 +194,13 @@ input.magnifier { text-decoration: none; -moz-border-radius: 5px 0 0 5px; -webkit-border-radius: 5px 0 0 5px; + -o-border-radius: 5px 0 0 5px; + -ms-border-radius: 5px 0 0 5px; border-radius: 5px 0 0 5px; -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + -o-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + -ms-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.25); border-bottom: 1px solid rgba(0, 0, 0, 0.25); @@ -208,9 +214,12 @@ input.magnifier { height: 23px; margin: 0 12px 0 1px; -webkit-border-radius: 0px 3px 3px 0px; + -moz-border-radius: 0px 3px 3px 0px; + -o-border-radius: 0px 3px 3px 0px; + -ms-border-radius: 0px 3px 3px 0px; border-radius: 0px 3px 3px 0px; } .dataTypeLabel { font-weight: bold; -} \ No newline at end of file +} diff --git a/java/com.sap.sailing.gwt.ui/Home.html b/java/com.sap.sailing.gwt.ui/Home.html index 319a40e0491..b834326a418 100644 --- a/java/com.sap.sailing.gwt.ui/Home.html +++ b/java/com.sap.sailing.gwt.ui/Home.html @@ -7,13 +7,14 @@ + - + diff --git a/java/com.sap.sailing.gwt.ui/Leaderboard.css b/java/com.sap.sailing.gwt.ui/Leaderboard.css index 270d817cdc7..6bdeedb1d78 100755 --- a/java/com.sap.sailing.gwt.ui/Leaderboard.css +++ b/java/com.sap.sailing.gwt.ui/Leaderboard.css @@ -134,15 +134,23 @@ body, table td, select, button { height: 8px; -moz-border-radius-topleft: 50%; -webkit-border-top-left-radius: 50%; + -o-border-top-left-radius: 50%; + -ms-border-top-left-radius: 50%; border-top-left-radius: 50%; -moz-border-radius-topright: 50%; -webkit-border-top-right-radius: 50%; + -o-border-top-right-radius: 50%; + -ms-border-top-right-radius: 50%; border-top-right-radius: 50%; -moz-border-radius-bottomleft: 50%; -webkit-border-bottom-left-radius: 50%; + -o-border-bottom-left-radius: 50%; + -ms-border-bottom-left-radius: 50%; border-bottom-left-radius: 50%; -moz-border-radius-bottomright: 50%; -webkit-border-bottom-right-radius: 50%; + -o-border-bottom-right-radius: 50%; + -ms-border-bottom-right-radius: 50%; border-bottom-right-radius: 50%; float: left; } diff --git a/java/com.sap.sailing.gwt.ui/LeaderboardNewHome.css b/java/com.sap.sailing.gwt.ui/LeaderboardNewHome.css new file mode 100644 index 00000000000..94f6c74fc11 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/LeaderboardNewHome.css @@ -0,0 +1,122 @@ +@import url(fontface.css); +@import url(CommonControls.css); +@import url(ViewerToolbar.css); + +table { + border-collapse: separate; } + +.leaderboardContent { +} + +.leaderboardContent td, .leaderboardContent th { + vertical-align: bottom; + line-height: 1.3; + padding-top: 2px; +} + +.leaderboardContent-toolbar { + +} + +.leaderboardLabel { + border-bottom: 2px solid #007dc0; + font-weight: bold; + margin: 5px; + } + +.refreshAndSettings { + background: #f8f8f8; + height: 30px; } + +.refreshAndSettings table td { + padding: 0 10px 0 0;} + +.refreshAndSettings table table td{ + padding: 0 0 0 0;} + +input.magnifier{ + display: block; + margin: 3px auto 0 auto !important; +} + +body, table td, select, button { + font-family: 'OpenSansLight', Arial, Verdana, sans-serif; + font-size: 14px; +} + +.errorLabel { + color: #FF0000; +} + +.abstractChartPanel-importantMessageOfChart { + font-family: 'OpenSansBold',Arial,Verdana,sans-serif; + font-size: 20px; + height: 70px; + margin: 0 auto; + padding: 49px 0 0 80px; + width: 300px; + color: #7c7c7c; + background: url(images/important-message.png) left center no-repeat; +} + +.LeaderboardHeader { + position: relative; + margin: 0 auto; + width: 400px; + text-align: center; + font-family: 'OpenSansRegular', Arial, Verdana, sans-serif; + font-size: 18px; + color: #fff; + margin-top: 22px; +} + +.leaderboardHeading { + font-size: 16px; + font-family: 'OpenSansRegular', Arial, Verdana, sans-serif; + font-weight: bold; +} + +.gwt-DialogBox { + z-index: 20; +} + +.bold { + font-family: "OpenSansCondensedBold", sans-serif; +} + +.trackingQuality-circle { + width: 8px; + height: 8px; + -moz-border-radius-topleft: 50%; + -webkit-border-top-left-radius: 50%; + border-top-left-radius: 50%; + -moz-border-radius-topright: 50%; + -webkit-border-top-right-radius: 50%; + border-top-right-radius: 50%; + -moz-border-radius-bottomleft: 50%; + -webkit-border-bottom-left-radius: 50%; + border-bottom-left-radius: 50%; + -moz-border-radius-bottomright: 50%; + -webkit-border-bottom-right-radius: 50%; + border-bottom-right-radius: 50%; + float: left; +} + +.circleYellow { + background: #fbba00; +} + +.circleGreen { + background: #8ab54e; +} + +.circleRed { + background: #f01010; +} + +.busyIndicator-Simple { + margin-top: 7px; +} + +.busyIndicator-Circle { +} diff --git a/java/com.sap.sailing.gwt.ui/PolarSheets.html b/java/com.sap.sailing.gwt.ui/PolarSheets.html index fdf8b1e9f87..19e100f2629 100644 --- a/java/com.sap.sailing.gwt.ui/PolarSheets.html +++ b/java/com.sap.sailing.gwt.ui/PolarSheets.html @@ -11,7 +11,7 @@ - + diff --git a/java/com.sap.sailing.gwt.ui/Profile SailingGWT.launch b/java/com.sap.sailing.gwt.ui/Profile SailingGWT.launch new file mode 100755 index 00000000000..32331edf760 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/Profile SailingGWT.launch @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/RaceBoard.css b/java/com.sap.sailing.gwt.ui/RaceBoard.css index f3daea67dce..86583207d0f 100644 --- a/java/com.sap.sailing.gwt.ui/RaceBoard.css +++ b/java/com.sap.sailing.gwt.ui/RaceBoard.css @@ -30,7 +30,7 @@ iframe {display: block; width: 100%; height: 100%; border: none; overflow-y: aut } body { - background: #ebebeb; + background: white; font-family: 'OpenSansRegular', Arial, Verdana, sans-serif; line-height: 1; font-size: 14px; @@ -68,13 +68,18 @@ table { .CombinedWindPanel { -moz-border-radius: 4px; -webkit-border-radius: 4px; + -o-border-radius: 4px; + -ms-border-radius: 4px; border-radius: 4px; background-color: rgba(255, 255, 255, 0.75); border: 1px solid #C9CACA; } -.RaceBoardHeaderPanel { - margin: 0 12px 14px 12px; +.RaceMap-HeaderPanel { + height: 60px; + width: 100%; + background-color: #333; + opacity: .5; } .leaderboardLabel { @@ -193,6 +198,9 @@ img.openColumn { height: 23px; margin: 0 12px 0 1px; -webkit-border-radius: 0px 3px 3px 0px; + -moz-border-radius: 0px 3px 3px 0px; + -o-border-radius: 0px 3px 3px 0px; + -ms-border-radius: 0px 3px 3px 0px; border-radius: 0px 3px 3px 0px; } @@ -203,17 +211,6 @@ img.openColumn { cursor: pointer; } -.raceBoardNavigation-filterButton { - background: url(images/filter_white.png), url(images/btn-cta-orangeleft.png); - background-position: center, center 0; - background-repeat: no-repeat, repeat-x; - width: 27px; - height: 23px; - margin: 0 12px 0 1px; - -webkit-border-radius: 0px 3px 3px 0px; - border-radius: 0px 3px 3px 0px; -} - .raceBoardNavigation-filterButton:hover { background-image: url(images/filter_white.png), url(images/btn-cta-orangeleft.png); background-position: center, center -30px; @@ -236,9 +233,13 @@ img.openColumn { text-decoration: none; -moz-border-radius: 5px 0 0 5px; -webkit-border-radius: 5px 0 0 5px; + -o-border-radius: 5px 0 0 5px; + -ms-border-radius: 5px 0 0 5px; border-radius: 5px 0 0 5px; -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + -o-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + -ms-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.25); border-bottom: 1px solid rgba(0, 0, 0, 0.25); @@ -271,12 +272,16 @@ img.openColumn { text-decoration: none; -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + -o-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + -ms-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.25); border-bottom: 1px solid rgba(0, 0, 0, 0.25); border-radius: 5px 5px 5px 5px; -moz-border-radius: 5px 0 0 5px; -webkit-border-radius: 5px 5px 5px 5px; + -o-border-radius: 5px 5px 5px 5px; + -ms-border-radius: 5px 5px 5px 5px; } .raceBoardNavigation-standaloneElement:active { @@ -298,41 +303,79 @@ img.openColumn { margin: 0 0 0 5px; } +.timePanelButton-SlowDown { + background-color: red !important; +} + +.timePanelButton-SpeedUp { + background-color: green !important; +} + .playPauseButton { padding: 0; float: left; - width: 28px; - height: 25px; - background: url(images/timeslider-play-active-icon.png) 0 0 no-repeat; + width: 32px; + height: 28px; + background: url(images/viewicons/@1x/img/SAP_RV_Play_INACTIVE.png) center no-repeat; cursor: pointer; + border: 1px solid #cecece; + border-radius: 4px; + outline: 0; } .playPauseButton:hover{ - background: url(images/playbutton-hover.png) 0 0 no-repeat !important; cursor: pointer; + background-color: #cecece; } .playPauseButtonPause { - background: url(images/timeslider-pause-icon.png) 0 0 no-repeat; + background: url(images/viewicons/@2x/img/SAP_RV_Pause_INACTIVE.png) 2px 0 no-repeat; + outline: 0; + background-size: contain; } .playPauseButtonPause:hover { - background: url(images/playbuttonPause-hover.png) 0 0 no-repeat !important; cursor: pointer; } .backToLivePlayButton { - background: url(images/btn-cta-greenleft.png) center top repeat-x; - margin: 0 0 0 5px; + position: absolute; + right: 15px; + top: 8px; + width: 42px; + height: 38px; + font-size: 8px; + white-space: normal; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + -o-border-radius: 5px; + -ms-border-radius: 5px; + border-radius: 5px; + border: none; + cursor: pointer; +} + +.backToLivePlayButton-Inactive { + color: #6dc021; + background-color: white; + font-weight: bold; +} + +.backToLivePlayButton-Active { + background-color: #6dc021; + color: white; + font-weight: normal; } .backToLivePlayButton:hover { - background: url(images/btn-cta-greenleft.png) center -30px repeat-x; - margin: 0 0 0 5px; + border: 2px solid gray; +} + +.backToLivePlayButton:focus { + outline: 0 !important; } .dockLayoutPanel div[style="position: absolute; overflow: hidden; left: 0px; right: 0px; bottom: 0px; height: 90px;"] { - box-shadow: 0 0 8px #424242; z-index: 10; } @@ -346,15 +389,23 @@ img.openColumn { height: 8px; -moz-border-radius-topleft: 50%; -webkit-border-top-left-radius: 50%; + -o-border-top-left-radius: 50%; + -ms-border-top-left-radius: 50%; border-top-left-radius: 50%; -moz-border-radius-topright: 50%; -webkit-border-top-right-radius: 50%; + -o-border-top-right-radius: 50%; + -ms-border-top-right-radius: 50%; border-top-right-radius: 50%; -moz-border-radius-bottomleft: 50%; -webkit-border-bottom-left-radius: 50%; + -o-border-bottom-left-radius: 50%; + -ms-border-bottom-left-radius: 50%; border-bottom-left-radius: 50%; -moz-border-radius-bottomright: 50%; -webkit-border-bottom-right-radius: 50%; + -o-border-bottom-right-radius: 50%; + -ms-border-bottom-right-radius: 50%; border-bottom-right-radius: 50%; float: left; } @@ -372,8 +423,222 @@ img.openColumn { } .busyIndicator-Simple { - margin-top: 7px; + position: absolute; + top: 0.7em; + right: 7.4em; } .busyIndicator-Circle { } + +.raceBoard-TitlePanel { + position: relative; + top: 10px; + z-index: 10; + color: #fff; + margin-left: auto; + margin-right: auto; +} + +.raceBoard-TitlePanel-RaceNameLabel { + font-size: 32px; + font-weight: bold; + padding: 7px; +} + +.raceBoard-Logo { + position: absolute; + top: 6px; + left: 10px; + z-index: 10; + cursor: pointer; +} + +.SideBySideComponentViewer-MainPanel { + top: 12px; +} + +.TimePanel-ShowExtended-Button { + width: 29px; + height: 28px; + float: left; + cursor: pointer; + border-top-left-radius: 5px; + border-top-right-radius: 5px; + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; + border: 1px solid #ececec; +} + +.TimePanel-ShowExtended-Button-Open { + background: white url(images/arrows_top_black.png) center no-repeat; +} + +.TimePanel-ShowExtended-Button-Closed { + background: white url(images/arrows_bottom_black.png) center no-repeat; +} + +.TimePanel-ShowExtended-Button:hover { + background-color: #cecece; +} + +.TimePanel-ShowExtended-Button:focus { + outline: 0 !important; +} + +.RaceBoard-globalNavigation-Link { + display: inline; + margin-right: 3px; +} + +.RaceBoard-globalNavigation-Link a { + color: #fff; + cursor: pointer; +} + +.RaceBoard-globalNavigation-Link+.RaceBoard-globalNavigation-Link:before { + content: ' \00bb\00a0'; +} + +.TimePanel-settingsButtonStyle { + position: absolute; + right: 0px; + z-index: 1; + border: none; +} + +.TimePanel-settingsButtonStyle:focus { + outline: 0 !important; +} + +.TimePanel-settingsButtonStyle:hover { + border: 1px solid gray; +} + +.gwt-SplitLayoutPanel-NorthSouthToggleButton-Closed-multiCompetitorRaceChart { + background-color: white; + background-image: url(images/viewicons/@1x/img/SAP_RV_CompetitorCharts_INACTIVE.png), url(images/arrows_top_black_small.png); + background-repeat: no-repeat, no-repeat; + background-position: 5% 50%, 95% 50% +} + +.gwt-SplitLayoutPanel-NorthSouthToggleButton-Open-multiCompetitorRaceChart { + background-color: white; + background-image: url(images/viewicons/@1x/img/SAP_RV_CompetitorCharts_GHOSTED.png), url(images/arrows_bottom_black_small.png); + background-repeat: no-repeat, no-repeat; + background-position: 5% 50%, 95% 50% +} + +.gwt-SplitLayoutPanel-NorthSouthToggleButton-Closed-windChart { + background-color: white; + background-image: url(images/viewicons/@1x/img/SAP_RV_CompetitorCharts_INACTIVE.png), url(images/arrows_top_black_small.png); + background-repeat: no-repeat, no-repeat; + background-position: 5% 50%, 95% 50% +} + +.gwt-SplitLayoutPanel-NorthSouthToggleButton-Open-windChart { + background-color: white; + background-image: url(images/viewicons/@1x/img/SAP_RV_CompetitorCharts_GHOSTED.png), url(images/arrows_bottom_black_small.png); + background-repeat: no-repeat, no-repeat; + background-position: 5% 50%, 95% 50% +} + +.gwt-SplitLayoutPanel-EastToggleButton-Open-leaderboard { + background-color: white; + background-image: url(images/viewicons/@1x/img/SAP_RV_Leaderboard_GHOSTED.png), url(images/arrows_top_black_small.png); + background-repeat: no-repeat, no-repeat; + background-position: 4% 50%, 96% 50% +} + +.gwt-SplitLayoutPanel-EastToggleButton-Closed-leaderboard { + background-color: white; + background-image: url(images/viewicons/@1x/img/SAP_RV_Leaderboard_INACTIVE.png), url(images/arrows_bottom_black_small.png); + background-repeat: no-repeat, no-repeat; + background-position: 4% 50%, 96% 50% +} + +.gwt-SplitLayoutPanel-NorthSouthToggleButton-Closed-media { + background-color: white; + background-image: url(images/viewicons/@1x/img/SAP_RV_AudioVideo_INACTIVE.png); + background-repeat: no-repeat, no-repeat; + background-position: 4% 50%, 96% 50% +} + +.gwt-SplitLayoutPanel-NorthSouthToggleButton-Open-media { + background-color: white; + background-image: url(images/viewicons/@1x/img/SAP_RV_AudioVideo_INACTIVE.png); + background-repeat: no-repeat, no-repeat; + background-position: 4% 50%, 96% 50% +} + +.gwt-SplitLayoutPanel-NorthSouthToggleButton-Small-media { + background-position: 50% 50%, 100% 50% !important; + margin-left: 0px !important; + padding: 2px 20px !important; +} + +.RegattaRaceInformation-Header { + position: relative; + left: 140px; + top: 11px; +} + +.RegattaRaceInformation-Header .RaceName-Label { + display: inline; + color: white; + margin-right: 10px; + font-size: 36px; +} + +.RegattaRaceInformation-Header .RaceSeriesAndFleet-Label { + display: inline; + color: white; + font-size: 15px; +} + +.RegattaAndRaceTime-Header { + position: absolute; + right: 10px; + top: 13px; + text-align: right; + color: white; +} + +.RegattaAndRaceTime-Header .RegattaName-Anchor { + font-size: 15px; + color: white; + text-decoration: none; + padding-left: 25px; + background-image: url(images/home.png); + background-repeat: no-repeat; + background-size: 17px; + background-position: 4px 1px; +} + +.RegattaAndRaceTime-Header .RegattaName-Anchor:hover { + text-decoration: underline; +} + +.RegattaAndRaceTime-Header .RaceTime-Label { + font-size: 15px; + margin-top: 5px; +} + +.VideoPopup-Close-Button { + border: none; +} + +.gwt-Button, .gwt-Button:visited { + background-color: #fcbb22; + background-image: none !important; + -moz-box-shadow: none; + -webkit-box-shadow: none; + -o-box-shadow: none; + -ms-box-shadow: none; + box-shadow: none; + text-shadow: none; +} + +.gwt-Button:hover { + background-color: #fcbb22 !important; +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/RegattaOverview.css b/java/com.sap.sailing.gwt.ui/RegattaOverview.css index fa4fb0cf139..87fc2aaaa64 100644 --- a/java/com.sap.sailing.gwt.ui/RegattaOverview.css +++ b/java/com.sap.sailing.gwt.ui/RegattaOverview.css @@ -80,6 +80,8 @@ table { box-shadow: 0 0 20px #858585; -moz-box-shadow: 0 0 20px #858585; -webkit-box-shadow: 0 0 20px #858585; + -o-box-shadow: 0 0 20px #858585; + -ms-box-shadow: 0 0 20px #858585; } .LogoAndTitlePanel:after { @@ -136,15 +138,23 @@ table { background: url(images/functionBar_bg.png) 0 0 repeat-x; -moz-border-radius-topleft: 5px; -webkit-border-top-left-radius: 5px; + -o-border-top-left-radius: 5px; + -ms-border-top-left-radius: 5px; border-top-left-radius: 5px; -moz-border-radius-topright: 5px; -webkit-border-top-right-radius: 5px; + -o-border-top-right-radius: 5px; + -ms-border-top-right-radius: 5px; border-top-right-radius: 5px; -moz-border-radius-bottomleft: 5px; -webkit-border-bottom-left-radius: 5px; + -o-border-bottom-left-radius: 5px; + -ms-border-bottom-left-radius: 5px; border-bottom-left-radius: 5px; -moz-border-radius-bottomright: 5px; -webkit-border-bottom-right-radius: 5px; + -o-border-bottom-right-radius: 5px; + -ms-border-bottom-right-radius: 5px; border-bottom-right-radius: 5px; margin: 0 0 15px 0; } @@ -162,15 +172,23 @@ table { height: 10px; -moz-border-radius-topleft: 50%; -webkit-border-top-left-radius: 50%; + -o-border-top-left-radius: 50%; + -ms-border-top-left-radius: 50%; border-top-left-radius: 50%; -moz-border-radius-topright: 50%; -webkit-border-top-right-radius: 50%; + -o-border-top-right-radius: 50%; + -ms-border-top-right-radius: 50%; border-top-right-radius: 50%; -moz-border-radius-bottomleft: 50%; -webkit-border-bottom-left-radius: 50%; + -o-border-bottom-left-radius: 50%; + -ms-border-bottom-left-radius: 50%; border-bottom-left-radius: 50%; -moz-border-radius-bottomright: 50%; -webkit-border-bottom-right-radius: 50%; + -o-border-bottom-right-radius: 50%; + -ms-border-bottom-right-radius: 50%; border-bottom-right-radius: 50%; float: left; margin: 2px 6px 0 0; @@ -226,9 +244,13 @@ table { text-decoration: none; -moz-border-radius: 5px 0 0 5px; -webkit-border-radius: 5px 0 0 5px; + -o-border-radius: 5px 0 0 5px; + -ms-border-radius: 5px 0 0 5px; border-radius: 5px 0 0 5px; -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + -o-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + -ms-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.25); border-bottom: 1px solid rgba(0, 0, 0, 0.25); diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling.launch b/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling (Run-Mode only).launch similarity index 74% rename from java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling.launch rename to java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling (Run-Mode only).launch index 1b84b74772a..0b8d83049f1 100755 --- a/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling.launch +++ b/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling (Run-Mode only).launch @@ -4,7 +4,8 @@ - + + @@ -18,7 +19,7 @@ - + @@ -40,13 +41,20 @@ + + + + + + + - + - + diff --git a/java/com.sap.sailing.gwt.ui/Simulator.css b/java/com.sap.sailing.gwt.ui/Simulator.css index 35389cf3dd5..71f9f9baee2 100644 --- a/java/com.sap.sailing.gwt.ui/Simulator.css +++ b/java/com.sap.sailing.gwt.ui/Simulator.css @@ -1,385 +1,392 @@ -@import url(fontface.css); -//@import url(CommonControls.css); - -/** Add css rules here for your application. */ -/* - body #calendar:after - content: " "; - display: block; - height: 0; - clear: both; - visibility: hidden; -*/ -.clear { - clear: both; - font-size: 1px; - height: 0px; - line-height: 0px; - margin: -1px 0 0; - width: 100%; -} - -.cover { - left: -1000px; - overflow: hidden; - position: absolute; - top: -1000px; -} - -.titleLabelWrapper { - position: relative; - margin-left: auto; - margin-right: auto; - width: 50%; -} - -.titleLabel { - text-align: center; - font-family: 'OpenSansSemiBold', Arial, Verdana, sans-serif; - font-size: 18px; - color: #fff; - margin: 23px 0 0 0; -} - -body { - background: #ebebeb; - font-family: 'OpenSansRegular', Arial, Verdana, sans-serif; - line-height: 1; - font-size: 14px; - font-weight: 400; - padding: 80px 0 160px 0; - margin: 0; - color: #7c7c7c; -} - -table td,select,button { - font-family: 'OpenSansLight', Arial, Verdana, sans-serif; - line-height: 1; - font-size: 14px; -} - -.contentOuterPanel { - background: #dddcdc; - margin: 0 12px 0 12px; - padding: 12px; -} - -/** Example rules used by the template application (remove for your app) */ -h1 { - font-size: 2em; - font-family: 'OpenSansSemiBold', Arial, Verdana, sans-serif; - color: #777777; - margin: 40px 0px 70px; - text-align: center; -} - -table { - border-collapse: separate; -} - -/*.RaceBoardHeaderPanel { - margin: 0 12px 14px 12px; -}*/ -.sapLogo { - float: left; - margin: 13px 0 0 12px; -} - -.pioLogo { - float: right; - margin: 11px 20px 0 12px; -} - -.sailingAnalyticsLabelPanel { - margin: 10px 0 0 0; -} - -.sailingAnalyticsLabel { - float: left; - color: #fff; - font-size: 14px; - font-weight: bold; - margin: 15px 0 0 0; -} - -.timePanelSettings { - display: none; -} - -/*.leaderboardLabel { - border-bottom: 2px solid #007dc0; - padding: 0 0 6px 0; - font-family: 'OpenSansSemiBold', Arial, Verdana, sans-serif; -}*/ -.refreshAndSettings { - background: #f2f2f2; - border-bottom: 2px solid #fff; - border-top: 2px solid #fff; - height: 30px; -} - -.refreshAndSettings table td { - padding: 0 10px 0 0; -} - -.refreshAndSettings table table td { - padding: 0 0 0 0; -} - -.race-name { - line-height: 1.0; -} - -input.magnifier { - display: block; - margin: 3px auto 0 auto !important; -} - -.breadcrumbPanel { - height: 30px; - margin: 0 12px 13px 12px; -} - -.mainPanel { - width: 100%; -} - -/*.raceBoardNavigation { - float: right; - margin: 26px 0 0 0; - font-family: 'OpenSansLight', Arial, Verdana, sans-serif; -} - -.raceBoardNavigation-navigationitem { - color: #dddcdc; - text-transform: uppercase; - font-weight: 500; - margin: 0 12px 0 0; - padding: 0 0 5px 0; -} - -.raceBoardNavigation-navigationitem:hover { - border-bottom: 4px solid #f0ab00; - color: #dddcdc; - text-decoration: none; -} - -.raceBoardNavigation-navigationitem:active { - border-bottom: 4px solid #f0ab00; - color: #dddcdc; - text-decoration: none; -}*/ -.abstractChartPanel-importantMessageOfChart { - font-family: 'OpenSansSemiBold', Arial, Verdana, sans-serif; - font-size: 20px; - height: 70px; - margin: 0 auto; - padding: 49px 0 0 80px; - width: 300px; - color: #7c7c7c; - background: url(images/important-message.png) left center no-repeat; -} - -/*.breadcrumbPanel-NextArrow { - display: none; -}*/ -.replayPanel { - position: relative; - z-index: 10; -} - -.innerHeadline { - font-family: OpenSansSemiBold, Arial, Verdana, sans-serif; - font-size: 16px; - color: #7c7c7c; - margin: 15px 0 -} - -.sectorHeadline { - font-family: OpenSansLight, Arial, Verdana, sans-serif; - font-size: 16px; -} - -.controlPanelInnerWrapper { - margin: 0 10px; -} - -.windPanel { - width: 100%; -} - -.choosePattern { - width: 100%; - margin: 0 0 15px 0; -} - -.choosePattern td:last-child { - text-align: right; -} - -.optionsPanel { - background: url(images/simulator/panel_bg.png) 0 0 repeat-x; - border-bottom: 4px solid #7c7b7c; - padding: 0 10px; -} - -.mapOptions { - background: url(images/simulator/panel_bg.png) 0 0 repeat-x; - border-bottom: 4px solid #7c7b7c; - padding: 0 10px; -} - -.timePanelButton { - float: left; - margin: 0 0 0 5px; -} - -.playPauseButton { - padding: 0; - width: 28px; - height: 24px; - background: url(images/timeslider-play-active-icon.png) 0 0 no-repeat; - cursor: pointer; -} - -.playPauseButton:hover{ - background: url(images/playbutton-hover.png) 0 0 no-repeat !important; - cursor: pointer; -} - -.playPauseButtonPause { - background: url(images/timeslider-pause-icon.png) 0 0 no-repeat; -} - -.playPauseButtonPause:hover { - background: url(images/playbuttonPause-hover.png) 0 0 no-repeat !important; - cursor: pointer; -} - - -.backToLivePlayButton { - float: left; - padding: 3px 10px 4px; -} - - -.sliderWrapper { - float: left; - width: 100%; - margin: 0 0 7px 0; -} - -.sliderLabel { - width: 200px; - padding: 12px 0 14px 12px; -} - -/* styles for the masterPanel */ -#masterPanelLeft .gwt-SliderBar-shell { - background: url(images/simulator/slider_bg.png) 0 0 repeat-x; - border-radius: 3px; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - border: 1px solid #ccc; - height: 36px; -} - -#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-line { - border: 1px solid #919191; - background-color: #818181; - height: 4px; - width: 90%; - top: 23px; - overflow: hidden; -} - -#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-line-disabled { - border: 1px solid gray; -} - -/*#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-knob { - top: 14px; - width: 11px; - height: 21px; - z-index: 1; - cursor: pointer; -}*/ - -#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-tick { - top: 18px; - width: 1px; - height: 6pt; - background: #919191; - overflow: hidden; -} - -#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-mark { - top: 21pt; - width: 6px; - height: 6pt; - background: #f0ab00; - overflow: hidden; - border-left: 4px solid #fff; - border-right: 4px solid #fff; -} - -#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-tick-disabled { - background: gray; -} - -#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-ticklabel { - font-size: 12px; - cursor: default; - font-family: 'OpenSansSemiBold',Arial,Verdana,sans-serif; - top: 5px; -} - -#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-markerlabel { - top: 30pt; - font-size: 12px; - color: #bc9434; - cursor: default; - font-family: 'OpenSansSemiBold',Arial,Verdana,sans-serif; -} - -#masterPanelLeft .gwt-SliderBar-shell-focused { - -} - -#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-line-sliding { - cursor: pointer; -} - -.boatClassLabel { - width: 235px; - margin: 0 0 15px 0; -} - -.sailingPanel { - width: 100%; -} - -.dockLayoutPanel div[style="position: absolute; overflow: hidden; left: 0px; right: 0px; bottom: 0px; height: 90px;"] { - box-shadow: 0 0 8px #424242; - z-index: 10; -} - -.MapDisplayOptions { - margin: 0 15px 0 0; -} - -.MapDisplayOptions label,.gwt-RadioButton label { - margin: 0 0 0 5px; -} -.MapDisplayOptions { - margin: 0 0 0 5px; -} - -.timeSliderWrapperPanel{ - -webkit-box-shadow: inset 0px 5px 10px 0px rgba(0, 0, 0, 0.15); /* WebKit */ - -moz-box-shadow: inset 0px 5px 10px 0px rgba(0, 0, 0, 0.15); /* Firefox */ - box-shadow: inset 0px 5px 10px 0px rgba(0, 0, 0, 0.15); /* Standard */ -} -.polarDiv -{background-color: white;width:100%;height:100px;border-radius: 5px; -moz-border-radius: 5px; +@import url(fontface.css); +//@import url(CommonControls.css); + +/** Add css rules here for your application. */ +/* + body #calendar:after + content: " "; + display: block; + height: 0; + clear: both; + visibility: hidden; +*/ +.clear { + clear: both; + font-size: 1px; + height: 0px; + line-height: 0px; + margin: -1px 0 0; + width: 100%; +} + +.cover { + left: -1000px; + overflow: hidden; + position: absolute; + top: -1000px; +} + +.titleLabelWrapper { + position: relative; + margin-left: auto; + margin-right: auto; + width: 50%; +} + +.titleLabel { + text-align: center; + font-family: 'OpenSansSemiBold', Arial, Verdana, sans-serif; + font-size: 18px; + color: #fff; + margin: 23px 0 0 0; +} + +body { + background: #ebebeb; + font-family: 'OpenSansRegular', Arial, Verdana, sans-serif; + line-height: 1; + font-size: 14px; + font-weight: 400; + padding: 80px 0 160px 0; + margin: 0; + color: #7c7c7c; +} + +table td,select,button { + font-family: 'OpenSansLight', Arial, Verdana, sans-serif; + line-height: 1; + font-size: 14px; +} + +.contentOuterPanel { + background: #dddcdc; + margin: 0 12px 0 12px; + padding: 12px; +} + +/** Example rules used by the template application (remove for your app) */ +h1 { + font-size: 2em; + font-family: 'OpenSansSemiBold', Arial, Verdana, sans-serif; + color: #777777; + margin: 40px 0px 70px; + text-align: center; +} + +table { + border-collapse: separate; +} + +/*.RaceBoardHeaderPanel { + margin: 0 12px 14px 12px; +}*/ +.sapLogo { + float: left; + margin: 13px 0 0 12px; +} + +.pioLogo { + float: right; + margin: 11px 20px 0 12px; +} + +.sailingAnalyticsLabelPanel { + margin: 10px 0 0 0; +} + +.sailingAnalyticsLabel { + float: left; + color: #fff; + font-size: 14px; + font-weight: bold; + margin: 15px 0 0 0; +} + +.timePanelSettings { + display: none; +} + +/*.leaderboardLabel { + border-bottom: 2px solid #007dc0; + padding: 0 0 6px 0; + font-family: 'OpenSansSemiBold', Arial, Verdana, sans-serif; +}*/ +.refreshAndSettings { + background: #f2f2f2; + border-bottom: 2px solid #fff; + border-top: 2px solid #fff; + height: 30px; +} + +.refreshAndSettings table td { + padding: 0 10px 0 0; +} + +.refreshAndSettings table table td { + padding: 0 0 0 0; +} + +.race-name { + line-height: 1.0; +} + +input.magnifier { + display: block; + margin: 3px auto 0 auto !important; +} + +.breadcrumbPanel { + height: 30px; + margin: 0 12px 13px 12px; +} + +.mainPanel { + width: 100%; +} + +/*.raceBoardNavigation { + float: right; + margin: 26px 0 0 0; + font-family: 'OpenSansLight', Arial, Verdana, sans-serif; +} + +.raceBoardNavigation-navigationitem { + color: #dddcdc; + text-transform: uppercase; + font-weight: 500; + margin: 0 12px 0 0; + padding: 0 0 5px 0; +} + +.raceBoardNavigation-navigationitem:hover { + border-bottom: 4px solid #f0ab00; + color: #dddcdc; + text-decoration: none; +} + +.raceBoardNavigation-navigationitem:active { + border-bottom: 4px solid #f0ab00; + color: #dddcdc; + text-decoration: none; +}*/ +.abstractChartPanel-importantMessageOfChart { + font-family: 'OpenSansSemiBold', Arial, Verdana, sans-serif; + font-size: 20px; + height: 70px; + margin: 0 auto; + padding: 49px 0 0 80px; + width: 300px; + color: #7c7c7c; + background: url(images/important-message.png) left center no-repeat; +} + +/*.breadcrumbPanel-NextArrow { + display: none; +}*/ +.replayPanel { + position: relative; + z-index: 10; +} + +.innerHeadline { + font-family: OpenSansSemiBold, Arial, Verdana, sans-serif; + font-size: 16px; + color: #7c7c7c; + margin: 15px 0 +} + +.sectorHeadline { + font-family: OpenSansLight, Arial, Verdana, sans-serif; + font-size: 16px; +} + +.controlPanelInnerWrapper { + margin: 0 10px; +} + +.windPanel { + width: 100%; +} + +.choosePattern { + width: 100%; + margin: 0 0 15px 0; +} + +.choosePattern td:last-child { + text-align: right; +} + +.optionsPanel { + background: url(images/simulator/panel_bg.png) 0 0 repeat-x; + border-bottom: 4px solid #7c7b7c; + padding: 0 10px; +} + +.mapOptions { + background: url(images/simulator/panel_bg.png) 0 0 repeat-x; + border-bottom: 4px solid #7c7b7c; + padding: 0 10px; +} + +.timePanelButton { + float: left; + margin: 0 0 0 5px; +} + +.playPauseButton { + padding: 0; + width: 28px; + height: 24px; + background: url(images/timeslider-play-active-icon.png) 0 0 no-repeat; + cursor: pointer; +} + +.playPauseButton:hover{ + background: url(images/playbutton-hover.png) 0 0 no-repeat !important; + cursor: pointer; +} + +.playPauseButtonPause { + background: url(images/timeslider-pause-icon.png) 0 0 no-repeat; +} + +.playPauseButtonPause:hover { + background: url(images/playbuttonPause-hover.png) 0 0 no-repeat !important; + cursor: pointer; +} + + +.backToLivePlayButton { + float: left; + padding: 3px 10px 4px; +} + + +.sliderWrapper { + float: left; + width: 100%; + margin: 0 0 7px 0; +} + +.sliderLabel { + width: 200px; + padding: 12px 0 14px 12px; +} + +/* styles for the masterPanel */ +#masterPanelLeft .gwt-SliderBar-shell { + background: url(images/simulator/slider_bg.png) 0 0 repeat-x; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + -o-border-radius: 3px; + -ms-border-radius: 3px; + border: 1px solid #ccc; + height: 36px; +} + +#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-line { + border: 1px solid #919191; + background-color: #818181; + height: 4px; + width: 90%; + top: 23px; + overflow: hidden; +} + +#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-line-disabled { + border: 1px solid gray; +} + +/*#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-knob { + top: 14px; + width: 11px; + height: 21px; + z-index: 1; + cursor: pointer; +}*/ + +#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-tick { + top: 18px; + width: 1px; + height: 6pt; + background: #919191; + overflow: hidden; +} + +#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-mark { + top: 21pt; + width: 6px; + height: 6pt; + background: #f0ab00; + overflow: hidden; + border-left: 4px solid #fff; + border-right: 4px solid #fff; +} + +#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-tick-disabled { + background: gray; +} + +#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-ticklabel { + font-size: 12px; + cursor: default; + font-family: 'OpenSansSemiBold',Arial,Verdana,sans-serif; + top: 5px; +} + +#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-markerlabel { + top: 30pt; + font-size: 12px; + color: #bc9434; + cursor: default; + font-family: 'OpenSansSemiBold',Arial,Verdana,sans-serif; +} + +#masterPanelLeft .gwt-SliderBar-shell-focused { + +} + +#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-line-sliding { + cursor: pointer; +} + +.boatClassLabel { + width: 235px; + margin: 0 0 15px 0; +} + +.sailingPanel { + width: 100%; +} + +.dockLayoutPanel div[style="position: absolute; overflow: hidden; left: 0px; right: 0px; bottom: 0px; height: 90px;"] { + box-shadow: 0 0 8px #424242; + z-index: 10; +} + +.MapDisplayOptions { + margin: 0 15px 0 0; +} + +.MapDisplayOptions label,.gwt-RadioButton label { + margin: 0 0 0 5px; +} +.MapDisplayOptions { + margin: 0 0 0 5px; +} + +.timeSliderWrapperPanel{ + -webkit-box-shadow: inset 0px 5px 10px 0px rgba(0, 0, 0, 0.15); /* WebKit */ + -moz-box-shadow: inset 0px 5px 10px 0px rgba(0, 0, 0, 0.15); /* Firefox */ + -o-box-shadow: inset 0px 5px 10px 0px rgba(0, 0, 0, 0.15); /* Firefox */ + -ms-box-shadow: inset 0px 5px 10px 0px rgba(0, 0, 0, 0.15); /* Firefox */ + box-shadow: inset 0px 5px 10px 0px rgba(0, 0, 0, 0.15); /* Standard */ +} +.polarDiv +{background-color: white;width:100%;height:100px;border-radius: 5px; -moz-border-radius: 5px; -webkit-border-radius: 5px; border: 1px solid #177980;} + -moz-border-radius: 5px; border: 1px solid #177980;} + -o-border-radius: 5px; border: 1px solid #177980;} + -ms-border-radius: 5px; border: 1px solid #177980;} diff --git a/java/com.sap.sailing.gwt.ui/Simulator.html b/java/com.sap.sailing.gwt.ui/Simulator.html index a90d4a20796..5e66810b126 100644 --- a/java/com.sap.sailing.gwt.ui/Simulator.html +++ b/java/com.sap.sailing.gwt.ui/Simulator.html @@ -14,7 +14,7 @@ - + diff --git a/java/com.sap.sailing.gwt.ui/Spectator.css b/java/com.sap.sailing.gwt.ui/Spectator.css index c45f733f108..1afd3de0213 100644 --- a/java/com.sap.sailing.gwt.ui/Spectator.css +++ b/java/com.sap.sailing.gwt.ui/Spectator.css @@ -80,6 +80,8 @@ table { box-shadow: 0 0 20px #858585; -moz-box-shadow: 0 0 20px #858585; -webkit-box-shadow: 0 0 20px #858585; + -o-box-shadow: 0 0 20px #858585; + -ms-box-shadow: 0 0 20px #858585; } .LogoAndTitlePanel:after { @@ -210,6 +212,8 @@ input.magnifier { border-radius: 4px; -moz-border-radius: 4px; -webkit-border-radius: 4px; + -o-border-radius: 4px; + -ms-border-radius: 4px; } .leaderboardGroupPanel-ActiveRace, .leaderboardGroupPanel-InactiveRace, .leaderboardGroupPanel-LegendTracked { @@ -220,6 +224,8 @@ input.magnifier { border-radius: 4px; -moz-border-radius: 4px; -webkit-border-radius: 4px; + -o-border-radius: 4px; + -ms-border-radius: 4px; background: url("images/btn-cta-blueleft.png") repeat-x -10px 0 #e7e7e7; text-decoration: none; color: #fff; @@ -235,6 +241,8 @@ input.magnifier { border-radius: 4px; -moz-border-radius: 4px; -webkit-border-radius: 4px; + -o-border-radius: 4px; + -ms-border-radius: 4px; background: url("images/btn-cta-greenleft.png") repeat-x -10px 0 #e7e7e7; text-decoration: none; color: #fff; @@ -250,6 +258,8 @@ input.magnifier { border-radius: 4px; -moz-border-radius: 4px; -webkit-border-radius: 4px; + -o-border-radius: 4px; + -ms-border-radius: 4px; background: url("images/btn-cta-orangeleft.png") repeat-x -10px 0 #e7e7e7; text-decoration: none; color: #fff; @@ -262,6 +272,8 @@ input.magnifier { border-radius: 4px; -moz-border-radius: 4px; -webkit-border-radius: 4px; + -o-border-radius: 4px; + -ms-border-radius: 4px; background: url("images/btn-cta-blueleft.png") repeat-x 0px 0 #e7e7e7; text-decoration: none; color: #fff; diff --git a/java/com.sap.sailing.gwt.ui/ViewerToolbar.css b/java/com.sap.sailing.gwt.ui/ViewerToolbar.css index dcea3a8a731..3fd42f875f5 100644 --- a/java/com.sap.sailing.gwt.ui/ViewerToolbar.css +++ b/java/com.sap.sailing.gwt.ui/ViewerToolbar.css @@ -41,6 +41,9 @@ height: 23px; margin: 0 12px 0 1px; -webkit-border-radius: 0px 3px 3px 0px; + -moz-border-radius: 0px 3px 3px 0px; + -o-border-radius: 0px 3px 3px 0px; + -ms-border-radius: 0px 3px 3px 0px; border-radius: 0px 3px 3px 0px; } @@ -78,9 +81,13 @@ text-decoration: none; -moz-border-radius: 5px 0 0 5px; -webkit-border-radius: 5px 0 0 5px; + -o-border-radius: 5px 0 0 5px; + -ms-border-radius: 5px 0 0 5px; border-radius: 5px 0 0 5px; -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + -o-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + -ms-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.25); border-bottom: 1px solid rgba(0, 0, 0, 0.25); @@ -113,12 +120,16 @@ text-decoration: none; -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + -o-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + -ms-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.25); border-bottom: 1px solid rgba(0, 0, 0, 0.25); border-radius: 5px 5px 5px 5px; -moz-border-radius: 5px 0 0 5px; -webkit-border-radius: 5px 5px 5px 5px; + -o-border-radius: 5px 5px 5px 5px; + -ms-border-radius: 5px 5px 5px 5px; } .viewerToolbar-standaloneElement:active { diff --git a/java/com.sap.sailing.gwt.ui/build.properties b/java/com.sap.sailing.gwt.ui/build.properties index 3e0c785617a..6e4fd18d28b 100755 --- a/java/com.sap.sailing.gwt.ui/build.properties +++ b/java/com.sap.sailing.gwt.ui/build.properties @@ -7,6 +7,7 @@ bin.includes = META-INF/,\ AdminConsole.css,\ AdminConsole.html,\ Leaderboard.css,\ + LeaderboardNewHome.css,\ Leaderboard.html,\ LeaderboardEditing.html,\ UserManagement.html,\ @@ -46,7 +47,6 @@ bin.includes = META-INF/,\ PolarSheets.css,\ PolarSheets.html,\ ViewerToolbar.css,\ - CommonGwtControls.css,\ com.sap.sailing.gwt.ui.DataMining/,\ DataMining.css,\ DataMining.html,\ diff --git a/java/com.sap.sailing.gwt.ui/images/SAP_RV_Settings.png b/java/com.sap.sailing.gwt.ui/images/SAP_RV_Settings.png new file mode 100644 index 00000000000..fbe4d7d99d8 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/SAP_RV_Settings.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/SAP_RV_SettingsBlack.png b/java/com.sap.sailing.gwt.ui/images/SAP_RV_SettingsBlack.png new file mode 100644 index 00000000000..9b63ce60c98 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/SAP_RV_SettingsBlack.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/arrows_bottom_black.png b/java/com.sap.sailing.gwt.ui/images/arrows_bottom_black.png new file mode 100644 index 00000000000..824b928db71 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/arrows_bottom_black.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/arrows_bottom_black_small.png b/java/com.sap.sailing.gwt.ui/images/arrows_bottom_black_small.png new file mode 100644 index 00000000000..5d06cf1589f Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/arrows_bottom_black_small.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/arrows_right_black.png b/java/com.sap.sailing.gwt.ui/images/arrows_right_black.png new file mode 100644 index 00000000000..274a9f0c8d1 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/arrows_right_black.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/arrows_top_black.png b/java/com.sap.sailing.gwt.ui/images/arrows_top_black.png new file mode 100644 index 00000000000..02d02e1cde6 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/arrows_top_black.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/arrows_top_black_small.png b/java/com.sap.sailing.gwt.ui/images/arrows_top_black_small.png new file mode 100644 index 00000000000..4eddabb57b9 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/arrows_top_black_small.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/close_black.png b/java/com.sap.sailing.gwt.ui/images/close_black.png new file mode 100644 index 00000000000..188969f926f Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/close_black.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/home.png b/java/com.sap.sailing.gwt.ui/images/home.png new file mode 100644 index 00000000000..5b13eae6506 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/home.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/home/busy_indicator.gif b/java/com.sap.sailing.gwt.ui/images/home/busy_indicator.gif new file mode 100644 index 00000000000..ed72b117dd9 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/home/busy_indicator.gif differ diff --git a/java/com.sap.sailing.gwt.ui/images/home/location-icon.png b/java/com.sap.sailing.gwt.ui/images/home/location-icon.png new file mode 100644 index 00000000000..737b6e4e2d0 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/home/location-icon.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/home/reload.png b/java/com.sap.sailing.gwt.ui/images/home/reload.png new file mode 100644 index 00000000000..a333086fa24 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/home/reload.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/home/settings.png b/java/com.sap.sailing.gwt.ui/images/home/settings.png new file mode 100644 index 00000000000..1f16ed08149 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/home/settings.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/home/social-icons.png b/java/com.sap.sailing.gwt.ui/images/home/social-icons.png new file mode 100644 index 00000000000..f9566384566 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/home/social-icons.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/sap_logo_header.png b/java/com.sap.sailing.gwt.ui/images/sap_logo_header.png new file mode 100644 index 00000000000..e5d2daf91f3 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/sap_logo_header.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/settings_black.png b/java/com.sap.sailing.gwt.ui/images/settings_black.png new file mode 100644 index 00000000000..a1969e23b01 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/settings_black.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/SAP_RV_TweekSprite_a00.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/SAP_RV_TweekSprite_a00.png new file mode 100755 index 00000000000..8ffc474113f Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/SAP_RV_TweekSprite_a00.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_AudioVideo_ACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_AudioVideo_ACTIVE.png new file mode 100755 index 00000000000..73f224bde91 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_AudioVideo_ACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_AudioVideo_GHOSTED.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_AudioVideo_GHOSTED.png new file mode 100755 index 00000000000..cdaa4261f8d Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_AudioVideo_GHOSTED.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_AudioVideo_INACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_AudioVideo_INACTIVE.png new file mode 100755 index 00000000000..e123081fc31 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_AudioVideo_INACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Checkbox_CHECKED.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Checkbox_CHECKED.png new file mode 100755 index 00000000000..fb678245b17 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Checkbox_CHECKED.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Checkbox_UNCHECKED.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Checkbox_UNCHECKED.png new file mode 100755 index 00000000000..66dbf06d92c Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Checkbox_UNCHECKED.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Clear.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Clear.png new file mode 100755 index 00000000000..c9a92b7a63a Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Clear.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_CompetitorCharts_ACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_CompetitorCharts_ACTIVE.png new file mode 100755 index 00000000000..8a38e267534 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_CompetitorCharts_ACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_CompetitorCharts_GHOSTED.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_CompetitorCharts_GHOSTED.png new file mode 100755 index 00000000000..188192c8e81 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_CompetitorCharts_GHOSTED.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_CompetitorCharts_INACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_CompetitorCharts_INACTIVE.png new file mode 100755 index 00000000000..c58f87ba96d Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_CompetitorCharts_INACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_CompetitorsFilter_ACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_CompetitorsFilter_ACTIVE.png new file mode 100755 index 00000000000..0d3d9c1ca44 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_CompetitorsFilter_ACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_CompetitorsFilter_GHOSTED.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_CompetitorsFilter_GHOSTED.png new file mode 100755 index 00000000000..194c13de49a Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_CompetitorsFilter_GHOSTED.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_CompetitorsFilter_INACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_CompetitorsFilter_INACTIVE.png new file mode 100755 index 00000000000..a689e2101bc Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_CompetitorsFilter_INACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Leaderboard_ACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Leaderboard_ACTIVE.png new file mode 100755 index 00000000000..fb0fc5e8d68 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Leaderboard_ACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Leaderboard_GHOSTED.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Leaderboard_GHOSTED.png new file mode 100755 index 00000000000..73bd5698fa8 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Leaderboard_GHOSTED.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Leaderboard_INACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Leaderboard_INACTIVE.png new file mode 100755 index 00000000000..a933b3dc3c4 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Leaderboard_INACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_LivePlay_ACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_LivePlay_ACTIVE.png new file mode 100755 index 00000000000..eaf9e9ccfe2 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_LivePlay_ACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_LivePlay_GHOSTED.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_LivePlay_GHOSTED.png new file mode 100755 index 00000000000..954a80eb32a Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_LivePlay_GHOSTED.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_LivePlay_INACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_LivePlay_INACTIVE.png new file mode 100755 index 00000000000..6795ac0f02e Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_LivePlay_INACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Map_ACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Map_ACTIVE.png new file mode 100755 index 00000000000..12811f5881c Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Map_ACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Map_GHOSTED.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Map_GHOSTED.png new file mode 100755 index 00000000000..f8a0fd719fd Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Map_GHOSTED.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Map_INACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Map_INACTIVE.png new file mode 100755 index 00000000000..d35eb30aa6b Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Map_INACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_PlaySpeed.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_PlaySpeed.png new file mode 100755 index 00000000000..128d31c2151 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_PlaySpeed.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Play_ACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Play_ACTIVE.png new file mode 100755 index 00000000000..e4edff8cbde Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Play_ACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Play_GHOSTED.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Play_GHOSTED.png new file mode 100755 index 00000000000..2c1285bc534 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Play_GHOSTED.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Play_INACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Play_INACTIVE.png new file mode 100755 index 00000000000..2144eacf6c8 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Play_INACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Search.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Search.png new file mode 100755 index 00000000000..22698de84ad Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Search.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Settings.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Settings.png new file mode 100755 index 00000000000..fbe4d7d99d8 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Settings.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_TweekSprite_a00_29.jpg b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_TweekSprite_a00_29.jpg new file mode 100755 index 00000000000..46323c6c6c3 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_TweekSprite_a00_29.jpg differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_TweekSprite_a00_32.jpg b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_TweekSprite_a00_32.jpg new file mode 100755 index 00000000000..3d258927a58 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_TweekSprite_a00_32.jpg differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Wind_ACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Wind_ACTIVE.png new file mode 100755 index 00000000000..110f48efada Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Wind_ACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Wind_GHOSTED.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Wind_GHOSTED.png new file mode 100755 index 00000000000..9d326fb2e37 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Wind_GHOSTED.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Wind_INACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Wind_INACTIVE.png new file mode 100755 index 00000000000..746b9b37f3f Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@1x/img/SAP_RV_Wind_INACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/SAP_RV_TweekSprite_a00.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/SAP_RV_TweekSprite_a00.png new file mode 100755 index 00000000000..9f2f9ff5ab2 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/SAP_RV_TweekSprite_a00.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_AudioVideo_ACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_AudioVideo_ACTIVE.png new file mode 100755 index 00000000000..44d1bbb626d Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_AudioVideo_ACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_AudioVideo_GHOSTED.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_AudioVideo_GHOSTED.png new file mode 100755 index 00000000000..c924f99480d Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_AudioVideo_GHOSTED.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_AudioVideo_INACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_AudioVideo_INACTIVE.png new file mode 100755 index 00000000000..e7c4026d648 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_AudioVideo_INACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Checkbox_CHECKED.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Checkbox_CHECKED.png new file mode 100755 index 00000000000..65b76a26fd3 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Checkbox_CHECKED.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Checkbox_UNCHECKED.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Checkbox_UNCHECKED.png new file mode 100755 index 00000000000..de9eedc724e Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Checkbox_UNCHECKED.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Clear.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Clear.png new file mode 100755 index 00000000000..2108ec5b9a1 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Clear.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_CompetitorCharts_ACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_CompetitorCharts_ACTIVE.png new file mode 100755 index 00000000000..38890afa4cc Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_CompetitorCharts_ACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_CompetitorCharts_GHOSTED.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_CompetitorCharts_GHOSTED.png new file mode 100755 index 00000000000..68d21fcc689 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_CompetitorCharts_GHOSTED.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_CompetitorCharts_INACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_CompetitorCharts_INACTIVE.png new file mode 100755 index 00000000000..37412eabfc9 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_CompetitorCharts_INACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_CompetitorsFilter_ACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_CompetitorsFilter_ACTIVE.png new file mode 100755 index 00000000000..7a015ebfca0 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_CompetitorsFilter_ACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_CompetitorsFilter_GHOSTED.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_CompetitorsFilter_GHOSTED.png new file mode 100755 index 00000000000..561a34f9c0c Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_CompetitorsFilter_GHOSTED.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_CompetitorsFilter_INACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_CompetitorsFilter_INACTIVE.png new file mode 100755 index 00000000000..0a050a30305 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_CompetitorsFilter_INACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Leaderboard_ACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Leaderboard_ACTIVE.png new file mode 100755 index 00000000000..1e24f619098 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Leaderboard_ACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Leaderboard_GHOSTED.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Leaderboard_GHOSTED.png new file mode 100755 index 00000000000..d8d0d7fc441 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Leaderboard_GHOSTED.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Leaderboard_INACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Leaderboard_INACTIVE.png new file mode 100755 index 00000000000..ff1f70ee98c Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Leaderboard_INACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_LivePlay_ACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_LivePlay_ACTIVE.png new file mode 100755 index 00000000000..2202da0998f Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_LivePlay_ACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_LivePlay_GHOSTED.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_LivePlay_GHOSTED.png new file mode 100755 index 00000000000..be3e1ffc961 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_LivePlay_GHOSTED.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_LivePlay_INACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_LivePlay_INACTIVE.png new file mode 100755 index 00000000000..4b305220449 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_LivePlay_INACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Map_ACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Map_ACTIVE.png new file mode 100755 index 00000000000..49b2f6a7b91 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Map_ACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Map_GHOSTED.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Map_GHOSTED.png new file mode 100755 index 00000000000..449e081eb2a Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Map_GHOSTED.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Map_INACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Map_INACTIVE.png new file mode 100755 index 00000000000..a736707b684 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Map_INACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Pause_INACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Pause_INACTIVE.png new file mode 100644 index 00000000000..f38f74c7ad1 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Pause_INACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_PlaySpeed.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_PlaySpeed.png new file mode 100755 index 00000000000..bc5b691963c Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_PlaySpeed.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Play_ACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Play_ACTIVE.png new file mode 100755 index 00000000000..8ca76b0b44d Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Play_ACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Play_GHOSTED.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Play_GHOSTED.png new file mode 100755 index 00000000000..e4f8256df5d Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Play_GHOSTED.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Play_INACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Play_INACTIVE.png new file mode 100755 index 00000000000..09d9f3da798 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Play_INACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Search.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Search.png new file mode 100755 index 00000000000..c294f7f2ead Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Search.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Settings.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Settings.png new file mode 100755 index 00000000000..3b35b62d820 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Settings.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_TweekSprite_a00_29.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_TweekSprite_a00_29.png new file mode 100755 index 00000000000..92ceb496a7e Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_TweekSprite_a00_29.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_TweekSprite_a00_32.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_TweekSprite_a00_32.png new file mode 100755 index 00000000000..e230cfb16db Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_TweekSprite_a00_32.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Wind_ACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Wind_ACTIVE.png new file mode 100755 index 00000000000..32816de58b5 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Wind_ACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Wind_GHOSTED.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Wind_GHOSTED.png new file mode 100755 index 00000000000..8e61221881f Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Wind_GHOSTED.png differ diff --git a/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Wind_INACTIVE.png b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Wind_INACTIVE.png new file mode 100755 index 00000000000..ddc63c21bd7 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/images/viewicons/@2x/img/SAP_RV_Wind_INACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/pom.xml b/java/com.sap.sailing.gwt.ui/pom.xml index 33ae1e4bfce..c9c3330cc12 100644 --- a/java/com.sap.sailing.gwt.ui/pom.xml +++ b/java/com.sap.sailing.gwt.ui/pom.xml @@ -84,7 +84,7 @@ - -Dgwt.persistentunitcache=false + -Dgwt.persistentunitcache=false -Xmx4096m @@ -109,8 +109,10 @@ 3 ${basedir} ${basedir}/src/main/resources - + compile diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/BoatClassImageResolver.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/BoatClassImageResolver.java new file mode 100644 index 00000000000..a8f1fd84cc1 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/BoatClassImageResolver.java @@ -0,0 +1,71 @@ +package com.sap.sailing.gwt.home.client; + +import java.util.HashMap; +import java.util.Map; + +import com.google.gwt.resources.client.ImageResource; +import com.sap.sailing.domain.common.BoatClassMasterdata; + +public class BoatClassImageResolver { + private static Map boatClassIconsMap; + private static BoatClassImageResources imageResources = BoatClassImageResources.INSTANCE; + + static { + boatClassIconsMap = new HashMap(); + + boatClassIconsMap.put(BoatClassMasterdata._12M.getDisplayName(), imageResources._12mRIcon()); + boatClassIconsMap.put(BoatClassMasterdata._2_4M.getDisplayName(), imageResources._2_4mIcon()); + boatClassIconsMap.put(BoatClassMasterdata._29ER.getDisplayName(), imageResources._29erIcon()); + boatClassIconsMap.put(BoatClassMasterdata._29ERXX.getDisplayName(), imageResources._29erIcon()); + boatClassIconsMap.put(BoatClassMasterdata._420.getDisplayName(), imageResources._420Icon()); + boatClassIconsMap.put(BoatClassMasterdata._470.getDisplayName(), imageResources._470Icon()); + boatClassIconsMap.put(BoatClassMasterdata._49ERFX.getDisplayName(), imageResources._49erFXIcon()); + boatClassIconsMap.put(BoatClassMasterdata._49ER.getDisplayName(), imageResources._49erIcon()); + boatClassIconsMap.put(BoatClassMasterdata._5O5.getDisplayName(), imageResources._505mIcon()); + boatClassIconsMap.put(BoatClassMasterdata.A_CAT.getDisplayName(), imageResources.A_CatIcon()); + boatClassIconsMap.put(BoatClassMasterdata.ALBIN_EXPRESS.getDisplayName(), imageResources.AlbinExpressIcon()); + boatClassIconsMap.put(BoatClassMasterdata.B_ONE.getDisplayName(), imageResources.BOne()); + boatClassIconsMap.put(BoatClassMasterdata.CONTENDER.getDisplayName(), imageResources.ContenderIcon()); + boatClassIconsMap.put(BoatClassMasterdata.DRAGON_INT.getDisplayName(), imageResources.DragonIcon()); + boatClassIconsMap.put(BoatClassMasterdata.EUROPE_INT.getDisplayName(), imageResources.EuropeIcon()); + boatClassIconsMap.put(BoatClassMasterdata.EXTREME_40.getDisplayName(), imageResources.Extreme40Icon()); + boatClassIconsMap.put(BoatClassMasterdata.F_16.getDisplayName(), imageResources.F16Icon()); + boatClassIconsMap.put(BoatClassMasterdata.F_18.getDisplayName(), imageResources.F18Icon()); + boatClassIconsMap.put(BoatClassMasterdata.FARR_30.getDisplayName(), imageResources.Farr30Icon()); + boatClassIconsMap.put(BoatClassMasterdata.FINN.getDisplayName(), imageResources.FinnIcon()); + boatClassIconsMap.put(BoatClassMasterdata.FLYING_DUTCHMAN.getDisplayName(), imageResources.FlyingDutchmanIcon()); + boatClassIconsMap.put(BoatClassMasterdata.FOLKBOAT.getDisplayName(), imageResources.FolkBoatIcon()); + boatClassIconsMap.put(BoatClassMasterdata.H_BOAT.getDisplayName(), imageResources.H_BoatIcon()); + boatClassIconsMap.put(BoatClassMasterdata.HOBIE_16.getDisplayName(), imageResources.HobieIcon()); + boatClassIconsMap.put(BoatClassMasterdata.HOBIE_TIGER.getDisplayName(), imageResources.HobieIcon()); + boatClassIconsMap.put(BoatClassMasterdata.HOBIE_WILD_CAT.getDisplayName(), imageResources.HobieIcon()); + boatClassIconsMap.put(BoatClassMasterdata.J24.getDisplayName(), imageResources.J24Icon()); + boatClassIconsMap.put(BoatClassMasterdata.J70.getDisplayName(), imageResources.J70Icon()); + boatClassIconsMap.put(BoatClassMasterdata.J80.getDisplayName(), imageResources.J80Icon()); + boatClassIconsMap.put(BoatClassMasterdata.LASER_SB3.getDisplayName(), imageResources.LaserSB3Icon()); + boatClassIconsMap.put(BoatClassMasterdata.LASER_INT.getDisplayName(), imageResources.LaserIcon()); + boatClassIconsMap.put(BoatClassMasterdata.LASER_4_7.getDisplayName(), imageResources.LaserIcon()); + boatClassIconsMap.put(BoatClassMasterdata.LASER_RADIAL.getDisplayName(), imageResources.LaserIcon()); + boatClassIconsMap.put(BoatClassMasterdata.MELGES_24.getDisplayName(), imageResources.Melges24Icon()); + boatClassIconsMap.put(BoatClassMasterdata.MINI_TRANSAT.getDisplayName(), imageResources.MiniTransatIcon()); + boatClassIconsMap.put(BoatClassMasterdata.MUSTO_SKIFF.getDisplayName(), imageResources.MustoSkiffIcon()); + boatClassIconsMap.put(BoatClassMasterdata.NACRA_17.getDisplayName(), imageResources.Nacra17Icon()); + boatClassIconsMap.put(BoatClassMasterdata.OK.getDisplayName(), imageResources.OKIcon()); + boatClassIconsMap.put(BoatClassMasterdata.OPTIMIST.getDisplayName(), imageResources.OptimistIcon()); + boatClassIconsMap.put(BoatClassMasterdata.PLATU_25.getDisplayName(), imageResources.Platu25Icon()); + boatClassIconsMap.put(BoatClassMasterdata.RS_X.getDisplayName(), imageResources.RSXIcon()); + boatClassIconsMap.put(BoatClassMasterdata.SONAR.getDisplayName(), imageResources.SonarIcon()); + boatClassIconsMap.put(BoatClassMasterdata.STAR.getDisplayName(), imageResources.StarIcon()); + boatClassIconsMap.put(BoatClassMasterdata.STREAMLINE.getDisplayName(), imageResources.StreamlineIcon()); + boatClassIconsMap.put(BoatClassMasterdata.SWAN_45.getDisplayName(), imageResources.Swan45Icon()); + boatClassIconsMap.put(BoatClassMasterdata.TORNADO.getDisplayName(), imageResources.TornadoIcon()); + boatClassIconsMap.put(BoatClassMasterdata.X_99.getDisplayName(), imageResources.X99Icon()); + boatClassIconsMap.put(BoatClassMasterdata.INTERNATIONAL_14.getDisplayName(), imageResources.International14Icon()); + boatClassIconsMap.put(BoatClassMasterdata.DYAS.getDisplayName(), imageResources.DyasIcon()); + boatClassIconsMap.put(BoatClassMasterdata.OPEN_BIC.getDisplayName(), imageResources.OpenBicIcon()); + } + + public static ImageResource getBoatClassIconResource(String displayName) { + return boatClassIconsMap.get(displayName); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/BoatClassImageResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/BoatClassImageResources.java new file mode 100644 index 00000000000..2a962a2ed5a --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/BoatClassImageResources.java @@ -0,0 +1,147 @@ +package com.sap.sailing.gwt.home.client; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.ImageResource; + +public interface BoatClassImageResources extends ClientBundle { + public static final BoatClassImageResources INSTANCE = GWT.create(BoatClassImageResources.class); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/GENERIC_BOATCLASS.png") + ImageResource genericBoatClass(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/12MR.png") + ImageResource _12mRIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/2_4M.png") + ImageResource _2_4mIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/29ER.png") + ImageResource _29erIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/420.png") + ImageResource _420Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/470.png") + ImageResource _470Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/49ER_FX.png") + ImageResource _49erFXIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/49ER.png") + ImageResource _49erIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/505.png") + ImageResource _505mIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/A_CAT.png") + ImageResource A_CatIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/ALBIN_EXPRESS.png") + ImageResource AlbinExpressIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/B_ONE.png") + ImageResource BOne(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/CONTENDER.png") + ImageResource ContenderIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/DRAGON.png") + ImageResource DragonIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/EUROPE.png") + ImageResource EuropeIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/EXTREME40.png") + ImageResource Extreme40Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/F16.png") + ImageResource F16Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/F18.png") + ImageResource F18Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/FARR_30.png") + ImageResource Farr30Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/FINN.png") + ImageResource FinnIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/FLYING_DUTCHMAN.png") + ImageResource FlyingDutchmanIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/FOLKBOAT.png") + ImageResource FolkBoatIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/H_BOAT.png") + ImageResource H_BoatIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/HOBIE.png") + ImageResource HobieIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/J24.png") + ImageResource J24Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/J70.png") + ImageResource J70Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/J80.png") + ImageResource J80Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/LASER_SB3.png") + ImageResource LaserSB3Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/LASER.png") + ImageResource LaserIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/MELGES_24.png") + ImageResource Melges24Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/MINI_TRANSAT.png") + ImageResource MiniTransatIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/MUSTO_SKIFF.png") + ImageResource MustoSkiffIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/NACRA_17.png") + ImageResource Nacra17Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/OK.png") + ImageResource OKIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/OPTIMIST.png") + ImageResource OptimistIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/PLATU_25.png") + ImageResource Platu25Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/RS_X.png") + ImageResource RSXIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/SONAR.png") + ImageResource SonarIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/STAR.png") + ImageResource StarIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/STREAMLINE.png") + ImageResource StreamlineIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/SWAN_45.png") + ImageResource Swan45Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/TORNADO.png") + ImageResource TornadoIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/X_99.png") + ImageResource X99Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/DYAS.png") + ImageResource DyasIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/INTERNATIONAL_14.png") + ImageResource International14Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/OPENBIC.png") + ImageResource OpenBicIcon(); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/ApplicationActivityMapper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/ApplicationActivityMapper.java index 9bf6fac4580..ef42d84a558 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/ApplicationActivityMapper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/ApplicationActivityMapper.java @@ -11,6 +11,8 @@ import com.sap.sailing.gwt.home.client.place.event.EventActivityProxy; import com.sap.sailing.gwt.home.client.place.event.EventPlace; import com.sap.sailing.gwt.home.client.place.events.EventsActivityProxy; import com.sap.sailing.gwt.home.client.place.events.EventsPlace; +import com.sap.sailing.gwt.home.client.place.leaderboard.LeaderboardActivityProxy; +import com.sap.sailing.gwt.home.client.place.leaderboard.LeaderboardPlace; import com.sap.sailing.gwt.home.client.place.searchresult.SearchResultActivityProxy; import com.sap.sailing.gwt.home.client.place.searchresult.SearchResultPlace; import com.sap.sailing.gwt.home.client.place.solutions.SolutionsActivityProxy; @@ -44,6 +46,8 @@ public class ApplicationActivityMapper implements ActivityMapper { return new SponsoringActivityProxy((SponsoringPlace) place, clientFactory); } else if (place instanceof SolutionsPlace) { return new SolutionsActivityProxy((SolutionsPlace) place, clientFactory); + } else if (place instanceof LeaderboardPlace) { + return new LeaderboardActivityProxy((LeaderboardPlace) place, clientFactory); } else if (place instanceof SearchResultPlace) { return new SearchResultActivityProxy((SearchResultPlace) place, clientFactory); } else { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/ApplicationClientFactory.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/ApplicationClientFactory.java index 5269aff0492..3cb19e535f6 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/ApplicationClientFactory.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/ApplicationClientFactory.java @@ -4,12 +4,14 @@ import com.sap.sailing.gwt.home.client.place.aboutus.AboutUsClientFactory; import com.sap.sailing.gwt.home.client.place.contact.ContactClientFactory; import com.sap.sailing.gwt.home.client.place.event.EventClientFactory; import com.sap.sailing.gwt.home.client.place.events.EventsClientFactory; +import com.sap.sailing.gwt.home.client.place.leaderboard.LeaderboardClientFactory; import com.sap.sailing.gwt.home.client.place.searchresult.SearchResultClientFactory; import com.sap.sailing.gwt.home.client.place.solutions.SolutionsClientFactory; import com.sap.sailing.gwt.home.client.place.sponsoring.SponsoringClientFactory; import com.sap.sailing.gwt.home.client.place.start.StartClientFactory; public interface ApplicationClientFactory extends AboutUsClientFactory, ContactClientFactory, EventClientFactory, - EventsClientFactory, StartClientFactory, SponsoringClientFactory, SolutionsClientFactory, SearchResultClientFactory { + EventsClientFactory, StartClientFactory, SponsoringClientFactory, SolutionsClientFactory, SearchResultClientFactory, + LeaderboardClientFactory { PlaceNavigator getPlaceNavigator(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/ApplicationHistoryMapper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/ApplicationHistoryMapper.java index d3fe642da21..72f4ab48c09 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/ApplicationHistoryMapper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/ApplicationHistoryMapper.java @@ -6,8 +6,14 @@ import com.sap.sailing.gwt.home.client.place.aboutus.AboutUsPlace; import com.sap.sailing.gwt.home.client.place.contact.ContactPlace; import com.sap.sailing.gwt.home.client.place.event.EventPlace; import com.sap.sailing.gwt.home.client.place.events.EventsPlace; +import com.sap.sailing.gwt.home.client.place.leaderboard.LeaderboardPlace; +import com.sap.sailing.gwt.home.client.place.searchresult.SearchResultPlace; +import com.sap.sailing.gwt.home.client.place.solutions.SolutionsPlace; +import com.sap.sailing.gwt.home.client.place.sponsoring.SponsoringPlace; import com.sap.sailing.gwt.home.client.place.start.StartPlace; -@WithTokenizers({ AboutUsPlace.Tokenizer.class, ContactPlace.Tokenizer.class, EventPlace.Tokenizer.class, EventsPlace.Tokenizer.class, StartPlace.Tokenizer.class }) +@WithTokenizers({ AboutUsPlace.Tokenizer.class, ContactPlace.Tokenizer.class, EventPlace.Tokenizer.class, EventsPlace.Tokenizer.class, + SolutionsPlace.Tokenizer.class, SponsoringPlace.Tokenizer.class, SearchResultPlace.Tokenizer.class, StartPlace.Tokenizer.class, + LeaderboardPlace.Tokenizer.class}) public interface ApplicationHistoryMapper extends PlaceHistoryMapper { } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/PlaceNavigator.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/PlaceNavigator.java index e0cff6053bb..ca3f7d1acf9 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/PlaceNavigator.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/PlaceNavigator.java @@ -2,8 +2,9 @@ package com.sap.sailing.gwt.home.client.app; public interface PlaceNavigator { void goToHome(); - void goToEvent(String eventUuidAsString, String baseUrl); - void goToRegattaOfEvent(String eventUuidAsString, String regattaIdAsNameString, String baseUrl); + void goToEvent(String eventUuidAsString, String baseUrl, boolean isOnRemoteServer); + void goToRegattaOfEvent(String eventUuidAsString, String leaderboardIdAsNameString, String baseUrl, boolean isOnRemoteServer); + void goToLeaderboard(String eventUuidAsString, String leaderboardIdAsNameString, String baseUrl, boolean isOnRemoteServer); void goToSearchResult(String searchQuery); void goToEvents(); void goToAboutUs(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/PlaceNavigatorImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/PlaceNavigatorImpl.java index cba1737d259..3731da8fd75 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/PlaceNavigatorImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/PlaceNavigatorImpl.java @@ -7,7 +7,9 @@ import com.google.gwt.user.client.Window; import com.sap.sailing.gwt.home.client.place.aboutus.AboutUsPlace; import com.sap.sailing.gwt.home.client.place.contact.ContactPlace; import com.sap.sailing.gwt.home.client.place.event.EventPlace; +import com.sap.sailing.gwt.home.client.place.event.EventPlace.NavigationTabs; import com.sap.sailing.gwt.home.client.place.events.EventsPlace; +import com.sap.sailing.gwt.home.client.place.leaderboard.LeaderboardPlace; import com.sap.sailing.gwt.home.client.place.searchresult.SearchResultPlace; import com.sap.sailing.gwt.home.client.place.solutions.SolutionsPlace; import com.sap.sailing.gwt.home.client.place.sponsoring.SponsoringPlace; @@ -15,8 +17,8 @@ import com.sap.sailing.gwt.home.client.place.start.StartPlace; public class PlaceNavigatorImpl implements PlaceNavigator { private final PlaceController placeController; - private final static String SAPSAILING_DOMAIN = "www.sapsailing.com"; - private final static String NEW_HOME_TEST_DOMAIN = "newhome.sapsailing.com"; + private final static String DEFAULT_SAPSAILING_SERVER = "www.sapsailing.com"; + private final static String DEFAULT_SAPSAILING_SERVER_URL = "http://" + DEFAULT_SAPSAILING_SERVER; protected PlaceNavigatorImpl(PlaceController placeController) { super(); @@ -25,89 +27,85 @@ public class PlaceNavigatorImpl implements PlaceNavigator { @Override public void goToHome() { - gotoPlace(Window.Location.getHostName(), new StartPlace(), new StartPlace.Tokenizer()); + gotoPlace(getLocationURL(), new StartPlace(), new StartPlace.Tokenizer()); } @Override - public void goToEvent(String eventUuidAsString, String baseUrl) { - EventPlace eventPlace = new EventPlace(eventUuidAsString, null); - gotoPlace(baseUrl, eventPlace, new EventPlace.Tokenizer()); - - -// if(isLocationOnLocalhost(baseUrl) || !baseUrl.contains(SAPSAILING_DOMAIN)) { -// placeController.goTo(eventPlace); -// } else { -// String remoteEventUrl = buildRemotePlaceUrl(baseUrl, eventPlace, new EventPlace.Tokenizer()); -// Window.Location.replace(remoteEventUrl); -// } + public void goToEvent(String eventUuidAsString, String baseUrl, boolean isOnRemoteServer) { + EventPlace eventPlace = new EventPlace(eventUuidAsString, NavigationTabs.Regattas, null); + gotoPlace(baseUrl, isOnRemoteServer, eventPlace, new EventPlace.Tokenizer()); } @Override - public void goToRegattaOfEvent(String eventUuidAsString, String leaderboardIdAsNameString, String baseUrl) { - EventPlace eventPlace = new EventPlace(eventUuidAsString, leaderboardIdAsNameString); - gotoPlace(baseUrl, eventPlace, new EventPlace.Tokenizer()); - -// if(isLocationOnLocalhost(baseUrl) || baseUrl.contains(SAPSAILING_DOMAIN)) { -// placeController.goTo(eventPlace); -// } else { -// String remoteEventUrl = buildRemotePlaceUrl(baseUrl, eventPlace, new EventPlace.Tokenizer()); -// Window.Location.replace(remoteEventUrl); -// } + public void goToRegattaOfEvent(String eventUuidAsString, String leaderboardIdAsNameString, String baseUrl, boolean isOnRemoteServer) { + EventPlace eventPlace = new EventPlace(eventUuidAsString, NavigationTabs.Regatta, leaderboardIdAsNameString); + gotoPlace(baseUrl, isOnRemoteServer, eventPlace, new EventPlace.Tokenizer()); + } + + @Override + public void goToLeaderboard(String eventUuidAsString, String leaderboardIdAsNameString, String baseUrl, boolean isOnRemoteServer) { + LeaderboardPlace leaderboardPlace = new LeaderboardPlace(eventUuidAsString, leaderboardIdAsNameString, true, true); + gotoPlace(baseUrl, isOnRemoteServer, leaderboardPlace, new LeaderboardPlace.Tokenizer()); } @Override public void goToSearchResult(String searchQuery) { - gotoPlace(Window.Location.getHostName(), new SearchResultPlace(searchQuery), new SearchResultPlace.Tokenizer()); + gotoPlace(getLocationURL(), new SearchResultPlace(searchQuery), new SearchResultPlace.Tokenizer()); } @Override public void goToEvents() { - gotoPlace(Window.Location.getHostName(), new EventsPlace(), new EventsPlace.Tokenizer()); + gotoPlace(getLocationURL(), new EventsPlace(), new EventsPlace.Tokenizer()); } @Override public void goToAboutUs() { - gotoPlace(new AboutUsPlace()); + gotoPlace(getLocationURL(), new AboutUsPlace(), new AboutUsPlace.Tokenizer()); } @Override public void goToContact() { - gotoPlace(new ContactPlace()); + gotoPlace(getLocationURL(), new ContactPlace(), new ContactPlace.Tokenizer()); } @Override public void goToSolutions() { - gotoPlace(new SolutionsPlace()); + gotoPlace(getLocationURL(), new SolutionsPlace(), new SolutionsPlace.Tokenizer()); } @Override public void goToSponsoring() { - gotoPlace(new SponsoringPlace()); + gotoPlace(getLocationURL(), new SponsoringPlace(), new SponsoringPlace.Tokenizer()); } - private void gotoPlace(T destinationPlace) { - placeController.goTo(destinationPlace); - } - private void gotoPlace(String baseUrl, T destinationPlace, PlaceTokenizer tokenizer) { - if(isLocationOnLocalhost(baseUrl) || isLocationOnSapSailingCom(baseUrl) || isLocationOnNewHomeTest(baseUrl)) { + if(isLocationOnLocalhost(baseUrl) || isLocationOnDefaultSapSailingServer(baseUrl)) { + placeController.goTo(destinationPlace); + } else { + String homeUrl = buildRemotePlaceUrl(DEFAULT_SAPSAILING_SERVER_URL, destinationPlace, tokenizer); + Window.Location.replace(homeUrl); + } + } + + private void gotoPlace(String baseUrl, boolean isOnRemoteServer, T destinationPlace, PlaceTokenizer tokenizer) { + if((baseUrl != null && isLocationOnLocalhost(baseUrl)) || !isOnRemoteServer) { placeController.goTo(destinationPlace); } else { String homeUrl = buildRemotePlaceUrl(baseUrl, destinationPlace, tokenizer); Window.Location.replace(homeUrl); } } + + private String getLocationURL() { + return Window.Location.getProtocol() + "//" + Window.Location.getHostName(); + } private String buildRemotePlaceUrl(String baseUrl, T destinationPlace, PlaceTokenizer tokenizer) { return baseUrl + "/gwt/Home.html#" + destinationPlace.getClass().getSimpleName() + ":" + tokenizer.getToken(destinationPlace); } - private boolean isLocationOnNewHomeTest(String urlToCheck) { - return urlToCheck.contains(NEW_HOME_TEST_DOMAIN); - } - - private boolean isLocationOnSapSailingCom(String urlToCheck) { - return urlToCheck.contains(SAPSAILING_DOMAIN); + private boolean isLocationOnDefaultSapSailingServer(String urlToCheck) { + return urlToCheck.contains(DEFAULT_SAPSAILING_SERVER); } private boolean isLocationOnLocalhost(String urlToCheck) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/SmartphoneApplicationClientFactory.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/SmartphoneApplicationClientFactory.java index ab5e46d4bf8..d7f9018888a 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/SmartphoneApplicationClientFactory.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/SmartphoneApplicationClientFactory.java @@ -5,11 +5,16 @@ import java.util.List; import com.google.gwt.event.shared.SimpleEventBus; import com.google.gwt.place.shared.PlaceController; import com.google.web.bindery.event.shared.EventBus; +import com.sap.sailing.gwt.home.client.place.error.TabletAndDesktopErrorView; import com.sap.sailing.gwt.home.client.place.event.EventView; +import com.sap.sailing.gwt.home.client.place.event.EventWithoutRegattasView; import com.sap.sailing.gwt.home.client.place.event.TabletAndDesktopEventView; +import com.sap.sailing.gwt.home.client.place.event.TabletAndDesktopEventWithoutRegattasView; import com.sap.sailing.gwt.home.client.place.events.EventsActivity; import com.sap.sailing.gwt.home.client.place.events.EventsView; import com.sap.sailing.gwt.home.client.place.events.TabletAndDesktopEventsView; +import com.sap.sailing.gwt.home.client.place.leaderboard.LeaderboardView; +import com.sap.sailing.gwt.home.client.place.leaderboard.TabletAndDesktopLeaderboardView; import com.sap.sailing.gwt.home.client.place.searchresult.SearchResultView; import com.sap.sailing.gwt.home.client.place.searchresult.TabletAndDesktopSearchResultView; import com.sap.sailing.gwt.home.client.place.solutions.SolutionsActivity; @@ -35,12 +40,17 @@ public class SmartphoneApplicationClientFactory extends AbstractApplicationClien } private SmartphoneApplicationClientFactory(EventBus eventBus, PlaceController placeController) { - super(new SmartphoneApplicationView(new PlaceNavigatorImpl(placeController)), eventBus, placeController); + super(new TabletAndDesktopApplicationView(new PlaceNavigatorImpl(placeController)), eventBus, placeController); } @Override public EventView createEventView(EventDTO event, List raceGroups, String leaderboardName, Timer timerForClientServerOffset) { - return new TabletAndDesktopEventView(getSailingService(), event, raceGroups, leaderboardName, timerForClientServerOffset); + return new TabletAndDesktopEventView(getSailingService(), event, raceGroups, leaderboardName, timerForClientServerOffset, getPlaceNavigator()); + } + + @Override + public EventWithoutRegattasView createEventWithoutRegattasView(EventDTO event) { + return new TabletAndDesktopEventWithoutRegattasView(getSailingService(), event); } @Override @@ -48,10 +58,14 @@ public class SmartphoneApplicationClientFactory extends AbstractApplicationClien return new TabletAndDesktopEventsView(getPlaceNavigator()); } + @Override + public TabletAndDesktopErrorView createErrorView(String errorMessage, Throwable errorReason) { + return new TabletAndDesktopErrorView(errorMessage, errorReason); + } + @Override public StartView createStartView() { return new TabletAndDesktopStartView(getPlaceNavigator()); -// return new SmartphoneStartView(getPlaceNavigator()); } @Override @@ -64,8 +78,13 @@ public class SmartphoneApplicationClientFactory extends AbstractApplicationClien return new TabletAndDesktopSolutionsView(); } + @Override + public LeaderboardView createLeaderboardView(EventDTO event, String leaderboardName, Timer timerForClientServerOffset) { + return new TabletAndDesktopLeaderboardView(event, leaderboardName, timerForClientServerOffset, getPlaceNavigator()); + } + @Override public SearchResultView createSearchResultView() { - return new TabletAndDesktopSearchResultView(getPlaceNavigator(), getEventBus()); + return new TabletAndDesktopSearchResultView(getPlaceNavigator()); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/TabletAndDesktopApplicationClientFactory.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/TabletAndDesktopApplicationClientFactory.java index c58a34f6f62..6100a0d2510 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/TabletAndDesktopApplicationClientFactory.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/TabletAndDesktopApplicationClientFactory.java @@ -5,11 +5,16 @@ import java.util.List; import com.google.gwt.event.shared.SimpleEventBus; import com.google.gwt.place.shared.PlaceController; import com.google.web.bindery.event.shared.EventBus; +import com.sap.sailing.gwt.home.client.place.error.TabletAndDesktopErrorView; import com.sap.sailing.gwt.home.client.place.event.EventView; +import com.sap.sailing.gwt.home.client.place.event.EventWithoutRegattasView; import com.sap.sailing.gwt.home.client.place.event.TabletAndDesktopEventView; +import com.sap.sailing.gwt.home.client.place.event.TabletAndDesktopEventWithoutRegattasView; import com.sap.sailing.gwt.home.client.place.events.EventsActivity; import com.sap.sailing.gwt.home.client.place.events.EventsView; import com.sap.sailing.gwt.home.client.place.events.TabletAndDesktopEventsView; +import com.sap.sailing.gwt.home.client.place.leaderboard.LeaderboardView; +import com.sap.sailing.gwt.home.client.place.leaderboard.TabletAndDesktopLeaderboardView; import com.sap.sailing.gwt.home.client.place.searchresult.SearchResultView; import com.sap.sailing.gwt.home.client.place.searchresult.TabletAndDesktopSearchResultView; import com.sap.sailing.gwt.home.client.place.solutions.SolutionsActivity; @@ -40,7 +45,17 @@ public class TabletAndDesktopApplicationClientFactory extends AbstractApplicatio @Override public EventView createEventView(EventDTO event, List raceGroups, String leaderboardName, Timer timerForClientServerOffset) { - return new TabletAndDesktopEventView(getSailingService(), event, raceGroups, leaderboardName, timerForClientServerOffset); + return new TabletAndDesktopEventView(getSailingService(), event, raceGroups, leaderboardName, timerForClientServerOffset, getPlaceNavigator()); + } + + @Override + public TabletAndDesktopErrorView createErrorView(String errorMessage, Throwable errorReason) { + return new TabletAndDesktopErrorView(errorMessage, errorReason); + } + + @Override + public EventWithoutRegattasView createEventWithoutRegattasView(EventDTO event) { + return new TabletAndDesktopEventWithoutRegattasView(getSailingService(), event); } @Override @@ -63,8 +78,13 @@ public class TabletAndDesktopApplicationClientFactory extends AbstractApplicatio return new TabletAndDesktopSolutionsView(); } + @Override + public LeaderboardView createLeaderboardView(EventDTO event, String leaderboardName, Timer timerForClientServerOffset) { + return new TabletAndDesktopLeaderboardView(event, leaderboardName, timerForClientServerOffset, getPlaceNavigator()); + } + @Override public SearchResultView createSearchResultView() { - return new TabletAndDesktopSearchResultView(getPlaceNavigator(), getEventBus()); + return new TabletAndDesktopSearchResultView(getPlaceNavigator()); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/TabletAndDesktopApplicationView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/TabletAndDesktopApplicationView.java index 915f726ee10..ae32e3212eb 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/TabletAndDesktopApplicationView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/TabletAndDesktopApplicationView.java @@ -1,8 +1,6 @@ package com.sap.sailing.gwt.home.client.app; import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Style.Visibility; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.ui.AcceptsOneWidget; @@ -31,9 +29,6 @@ public class TabletAndDesktopApplicationView extends Composite implements Applic @UiField SimplePanel mainContentPanel; - @UiField - Element loadingMessage; - public TabletAndDesktopApplicationView(PlaceNavigator navigator) { headerPanel = new Header(navigator); initWidget(uiBinder.createAndBindUi(this)); @@ -45,7 +40,6 @@ public class TabletAndDesktopApplicationView extends Composite implements Applic } @Override - public void showLoading(boolean visibile) { - loadingMessage.getStyle().setVisibility(visibile ? Visibility.VISIBLE : Visibility.HIDDEN); + public void showLoading(boolean visible) { } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/TabletAndDesktopApplicationView.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/TabletAndDesktopApplicationView.ui.xml index c53727b7ab3..c0926ee297e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/TabletAndDesktopApplicationView.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/TabletAndDesktopApplicationView.ui.xml @@ -2,14 +2,9 @@ - -

Loading...
- - - diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/i18n/TextMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/i18n/TextMessages.java index 073766f0b23..c0431620b40 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/i18n/TextMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/i18n/TextMessages.java @@ -1,8 +1,11 @@ package com.sap.sailing.gwt.home.client.i18n; +import com.google.gwt.core.client.GWT; import com.google.gwt.i18n.client.Messages; public interface TextMessages extends Messages { + public static final TextMessages INSTANCE = GWT.create(TextMessages.class); + String chooseALanguage(); String home(); String events(); @@ -10,10 +13,22 @@ public interface TextMessages extends Messages { String contact(); String sponsoring(); String solutions(); - String search(); String german(); String english(); String fullEventSchedule(); + String officalEventWebsite(); + String leg(); + String races(); + String moreInfo(); + String viewAnalysis(); + String overallLeaderboard(); + String pleaseEnterASearchTerm(); + String pleaseTryAgain(); + String reloadPage(); + String errorMessageLoadingData(); + String errorMessageNoSuchLeaderboard(); + String raceNotScheduled(); + String regattaDetails(); String countdownDays(); String countdownHours(); @@ -27,7 +42,7 @@ public interface TextMessages extends Messages { String finished(); String watchNow(); String lastUpdate(); - String leaderBoard(); + String leaderboard(); String boxNewsUpdateMoreLink(); String boxNewsUpdateTitle(); String boxUpcomingRacesCountdownStarted1(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/i18n/TextMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/i18n/TextMessages.properties index 30566ad2258..0f8ea69634d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/i18n/TextMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/i18n/TextMessages.properties @@ -5,10 +5,22 @@ aboutUs=About us contact=Contact sponsoring=Sponsoring solutions=Solutions -search=Search german=Deutsch english=English fullEventSchedule=Full event schedule +officalEventWebsite=Official event website +leg=Leg +races=Races +moreInfo=More info +viewAnalysis=View analysis +overallLeaderboard=Overall leaderboard +pleaseEnterASearchTerm=Please enter a search term. +pleaseTryAgain=Please try again... +reloadPage=Reload page +errorMessageLoadingData=An error occured while loading the data for this page. +errorMessageNoSuchLeaderboard=Could not find a leaderboard with this name. +raceNotScheduled=Not scheduled. +regattaDetails=Regatta details countdownDays=Days countdownHours=Hrs @@ -22,7 +34,7 @@ live=Live finished=Finished watchNow=Watch now lastUpdate=Last Update -leaderBoard=LeaderBoard +leaderboard=Leaderboard boxNewsUpdateMoreLink=Report news boxNewsUpdateTitle=News Update boxUpcomingRacesCountdownStarted1=Started @@ -79,8 +91,8 @@ eventRegattaRaceStarted=Started at: eventRegattaRaceScheduled=Scheduled for: eventRegattaRaceRace=Race eventRegattaRaceWind=Wind Strength: -eventRegattaRaceWinner=Winner: -eventRegattaRaceLeader=Leader: +eventRegattaRaceWinner=Winner +eventRegattaRaceLeader=Leader eventRegattaRacesLegendGps=Tracking eventRegattaRacesLegendWind=Wind Data eventRegattaRacesLegendVideo=Video Stream @@ -95,7 +107,7 @@ headerEvents=Events headerHome=Home headerLogo=SAP Sailing Analytics headerSearch=Search -headerSearchPlaceholder=Search +headerSearchPlaceholder=Search SAPSailing.com headerSolutions=Solutions headerSponsoring=Sponsoring latestRaceResultsHeadline=Latest Official Race Results @@ -127,15 +139,15 @@ regattaStartedAt=Started at regattasResultsTitle=Winner and Results searchResultHeaderButton=Search searchResultHeaderLabel=Your Search -searchResultHeaderPlaceholder=Suchbegriff eingeben +searchResultHeaderPlaceholder=Type in your searchkey searchResultMore=More searchResultSearch=Search searchResultSearchAgain=Search again searchResultSortLabel=Sort by -searchResultSortNewest=Neueste -searchResultSortOldest=Älteste +searchResultSortNewest=Recent +searchResultSortOldest=Oldest searchResultSortRelevance=Relevance -searchResultCount=About 2,450 reslts where found for 'Schnitzel' +searchResultCount=About 2,450 results where found for 'Schnitzel' sharingEmail=Send an email sharingFacebook=Share on Facebook sharingTwitter=Tweet about it diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/i18n/TextMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/i18n/TextMessages_de.properties index aec0f82d9c4..248856f4317 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/i18n/TextMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/i18n/TextMessages_de.properties @@ -1,14 +1,26 @@ -chooseALanguage=Wähle eine Sprache +chooseALanguage=Wähle eine Sprache events=Veranstaltungen home=Startseite -aboutUs=Über uns +aboutUs=Über uns contact=Kontakt sponsoring=Sponsoring -solutions=Lösungen -search=Suche +solutions=Lösungen german=Deutsch english=English -fullEventSchedule=Full event schedule +fullEventSchedule=Gesamter Veranstaltungsplan +officalEventWebsite=Offizielle Internetseite +leg=Schenkel +races=Rennen +moreInfo=Weitere Informationen +viewAnalysis=Analyzen ansehen +overallLeaderboard=Gesamtergebnis +pleaseEnterASearchTerm=Bitte gebe einen Suchbegriff ein. +pleaseTryAgain=Bitte versuchen sie es erneut... +reloadPage=Seite neu laden +errorMessageLoadingData=Beim Laden der Daten für diese Seite ist ein Fehler aufgetreten. +errorMessageNoSuchLeaderboard=Es wurde keine Ergebnisliste mit diesem Namen gefunden. +raceNotScheduled=Keine Startzeit bekannt +regattaDetails=Regatta anzeigen countdownDays=Tage countdownHours=Std. @@ -19,141 +31,141 @@ countdownHour=Std. countdownMinute=Min. countdownSecond=Sek. live=Live -finished=Finished -watchNow=Watch now -lastUpdate=Last Update -leaderBoard=LeaderBoard -boxNewsUpdateMoreLink=Report news +finished=Beendet +watchNow=Jetzt mitverfolgen +lastUpdate=Letztes Update +leaderboard=Ergebnisliste +boxNewsUpdateMoreLink=Neuigkeiten berichten boxNewsUpdateTitle=News Update -boxUpcomingRacesCountdownStarted1=Started -boxUpcomingRacesCountdownStarted2=hrs and -boxUpcomingRacesCountdownStarted3=minutes ago -boxUpcomingRacesCountdownStarting1=Starting in -boxUpcomingRacesCountdownStarting2=hrs and -boxUpcomingRacesCountdownStarting3=minutes -boxUpcomingRacesMoreLink=Event Schedule -boxUpcomingRacesTitle=Upcoming Races Today -boxWeather=Weather -boxWeatherUnit=KTS +boxUpcomingRacesCountdownStarted1=Gestartet +boxUpcomingRacesCountdownStarted2=Std. und +boxUpcomingRacesCountdownStarted3=Minuten vorbei +boxUpcomingRacesCountdownStarting1=Startet in +boxUpcomingRacesCountdownStarting2=Std. in +boxUpcomingRacesCountdownStarting3=Minuten +boxUpcomingRacesMoreLink=Veranstaltungszeitplan +boxUpcomingRacesTitle=Kommende Rennen heute +boxWeather=Wetter +boxWeatherUnit=KTN defaultTitle=SAP Sailing Analytics eventHeaderMedia=Media -eventHeaderOverview=Overview -eventHeaderRaces=Schedule -eventHeaderRegattas=Regattas -seriesHeaderOverview=About the Series -seriesHeaderUpcoming=Upcoming Events -seriesHeaderRecent=Recent Events -seriesHeaderLeaderboard=Leaderboard -seriesOverviewHeaderRecent=Latest Series Events -seriesOverviewReadMore=More -eventsOverviewRecentEvents=Events -eventsOverviewRecentCountries=Countries -eventsOverviewRecentSailors=Sailors -eventsOverviewRecentTrackedRaces=Tracked Races -eventResultsHeadline=Winners and Results -eventResultsBoatClassesDropDown=All Boat Classes -eventOverviewMoreEvents=More Events -eventOverviewRecentEvents=Recent Events -eventOverviewUpcomingDate=Date -eventOverviewUpcomingEvent=Event -eventOverviewUpcomingLocation=Location -eventOverviewUpcomingSchedule=Upcoming Schedule -eventOverviewUpcomingSeries=Series -eventOverviewUpcomingTeaser=More Events To Be Announced Soon! -eventQuickfinderTitle=Event Quick-Finder -eventRegattaHeaderLeaderboard=Leaderboard -eventRegattaHeaderRaceRaces=Races -eventRegattaListAllBoatClasses=All boat classes -eventRegattaLeaderboardCompetitor=Competitor -eventRegattaLeaderboardExtendLeaderboard=Extend Leaderboard -eventRegattaLeaderboardFinishedMessage=No more races -eventRegattaLeaderboardLastScoreUpdate=Last Score Update +eventHeaderOverview=Übersicht +eventHeaderRaces=Zeitplan +eventHeaderRegattas=Regatten +seriesHeaderOverview=Über die Serie +seriesHeaderUpcoming=Noch kommende Veranstaltungen +seriesHeaderRecent=Letzte Veranstaltungen +seriesHeaderLeaderboard=Ergebnisliste +seriesOverviewHeaderRecent=Letzte Serienveranstaltungen +seriesOverviewReadMore=Mehr +eventsOverviewRecentEvents=Veranstaltungen +eventsOverviewRecentCountries=Länder +eventsOverviewRecentSailors=Segler +eventsOverviewRecentTrackedRaces=mitverfolgte Rennen +eventResultsHeadline=Gewinner und Ergebnisse +eventResultsBoatClassesDropDown=Alle Bootklassen +eventOverviewMoreEvents=Mehr Veranstaltungen +eventOverviewRecentEvents=Letzte Veranstaltungen +eventOverviewUpcomingDate=Datum +eventOverviewUpcomingEvent=Veranstaltungen +eventOverviewUpcomingLocation=Ort +eventOverviewUpcomingSchedule=Kommendes Programm +eventOverviewUpcomingSeries=Serien +eventOverviewUpcomingTeaser=Mehr Veranstaltungen werden bald veröffentlicht +eventQuickfinderTitle=Veranstaltung Schnellsuche +eventRegattaHeaderLeaderboard=Ergebnisliste +eventRegattaHeaderRaceRaces=Rennen +eventRegattaListAllBoatClasses=Alle Bootklassen +eventRegattaLeaderboardCompetitor=Teilnehmer +eventRegattaLeaderboardExtendLeaderboard=Erweitere Ergebnisliste +eventRegattaLeaderboardFinishedMessage=Keine weiteren rennen +eventRegattaLeaderboardLastScoreUpdate=Letztes Ergebnisupdate eventRegattaLeaderboardName=Name -eventRegattaLeaderboardRegattaRank=Regatta Rank -eventRegattaLeaderboardTotalPoints=Total Points -eventRegattaRaceWatch=Watch live -eventRegattaRaceAnalyse=Analyze Race -eventRegattaRaceNotTracked=Not Tracked -eventRegattaRaceFinished=Finished at: -eventRegattaRaceStarted=Started at: -eventRegattaRaceScheduled=Scheduled for: -eventRegattaRaceRace=Race -eventRegattaRaceWind=Wind Strength: -eventRegattaRaceWinner=Winner: -eventRegattaRaceLeader=Leader: +eventRegattaLeaderboardRegattaRank=Platz in der Regatta +eventRegattaLeaderboardTotalPoints=Gesamtpunkte +eventRegattaRaceWatch=Live ansehen +eventRegattaRaceAnalyse=Rennen analysieren +eventRegattaRaceNotTracked=Nicht getracked +eventRegattaRaceFinished=Beendet um: +eventRegattaRaceStarted=Gestartet um: +eventRegattaRaceScheduled=Geplant um: +eventRegattaRaceRace=Rennen +eventRegattaRaceWind=Windstärke: +eventRegattaRaceWinner=Gewinner +eventRegattaRaceLeader=Führender eventRegattaRacesLegendGps=Tracking -eventRegattaRacesLegendWind=Wind Data +eventRegattaRacesLegendWind=Winddaten eventRegattaRacesLegendVideo=Video Stream eventRegattaRacesLegendAudio=Audio Stream -eventSponsorsTitle=Event Sponsorships +eventSponsorsTitle=Veranstaltungssponsoren footerCopyright=\u00A9 2014 SAP Sailing Analytics -footerLanguage=SAP Sailing offered in -footerLegal=Legal Disclosure -footerPrivacy=Privacy +footerLanguage=SAP Sailing in +footerLegal=Impressum +footerPrivacy=Datenschutz footerFeedback=Feedback -headerEvents=Events -headerHome=Home +headerEvents=Veranstaltungen +headerHome=Startseite headerLogo=SAP Sailing Analytics -headerSearch=Search -headerSearchPlaceholder=Search -headerSolutions=Solutions -headerSponsoring=Sponsoring -latestRaceResultsHeadline=Latest Official Race Results -latestRaceResultsIsNew=New -latestRaceResultsIsUpdated=Updated -latestRaceResultsRaceCountLabel=Result after -latestRaceResultsRaceCountUnit=races -latestRaceResultsRefresh=refresh -leaderboardQuickAccessLabel=Leaderboard Quick Access -mainEventShowAll=Show All Events -mainEventTitle=Latest Events from the Sailing World -mainMediaSubtitle=Get lost in the latest and most beautiful impressions from your favorite sailing event. -mainMediaTitle=Sailing loves Photography -mainMediaVideosSubtitle=Immerse yourself in the world of sailing and check out our latest events for more videos. -mainMediaVideosTitle=Recent Video Entries -mainSponsorsReadMore=Read More -mediaTeaserPhotosTitle=Photos +headerSearch=Suche in SAPSailing.com +headerSearchPlaceholder=Suche +headerSolutions=Lösungen +headerSponsoring=Sponsoren +latestRaceResultsHeadline=Letzten ofiziellen Ergebnisse +latestRaceResultsIsNew=Neu +latestRaceResultsIsUpdated=Aktualisiert +latestRaceResultsRaceCountLabel=Ergebnis nach +latestRaceResultsRaceCountUnit=Rennen +latestRaceResultsRefresh=aktualisieren +leaderboardQuickAccessLabel=Ergebnisliste Schnellzugang +mainEventShowAll=Zeige alle Veranstaltungen +mainEventTitle=Die letzten Veranstaltungen aus der Segelwelt +mainMediaSubtitle=Hier gibt es die aktuellsten und schönsten Impressionen aus deiner beliebtesten Segelveranstaltung. +mainMediaTitle=Segeln liebt Fotographie +mainMediaVideosSubtitle=Tauche ein in die Welt des Segelns und besuche auch die anderen Veranstaltungen für mehr Videos. +mainMediaVideosTitle= Die Letzten Videoeinträge +mainSponsorsReadMore=Lese mehr +mediaTeaserPhotosTitle=Fotos mediaTeaserVideosTitle=Videos -regattaCompetitors=Competitors -regattaRaces=Races -regattaTracked=Tracked -regattaRegattaStarts=Regatta will start -regattaPts=Pts. -regattaCourse=Course -regattaArea=Area -regattaFinishedAt=Finished at -regattaScheduledAt=Scheduled at -regattaStartedAt=Started at -regattasResultsTitle=Winner and Results -searchResultHeaderButton=Search -searchResultHeaderLabel=Your Search +regattaCompetitors=Teilnehmer +regattaRaces=Rennen +regattaTracked=Getracked +regattaRegattaStarts=Regatta wird starten +regattaPts=Pkt. +regattaCourse=Kurs +regattaArea=Regattabahn +regattaFinishedAt=Beendet um +regattaScheduledAt=Geplant um +regattaStartedAt=Gestartet um +regattasResultsTitle=Gewinner und Ergebnisse +searchResultHeaderButton=Suche +searchResultHeaderLabel=Deine Suche searchResultHeaderPlaceholder=Suchbegriff eingeben -searchResultMore=More -searchResultSearch=Search -searchResultSearchAgain=Search again -searchResultSortLabel=Sort by +searchResultMore=Mehr +searchResultSearch=Suche +searchResultSearchAgain=Noch einmal suchen +searchResultSortLabel=Sortiert nach searchResultSortNewest=Neueste -searchResultSortOldest=Älteste -searchResultSortRelevance=Relevance -searchResultCount=About 2,450 reslts where found for 'Schnitzel' -sharingEmail=Send an email -sharingFacebook=Share on Facebook -sharingTwitter=Tweet about it -socialFooterShare=Share this on -socialFooterTitle=Recommend SAP Sailing Analytics +searchResultSortOldest=Älteste +searchResultSortRelevance=Relevanz +searchResultCount=Um die 2.450 Ergebnisse wurden gefunden für "Schnitzel" +sharingEmail=Email versenden +sharingFacebook=Auf Facebook teilen +sharingTwitter=Twittern +socialFooterShare=Teile es auf +socialFooterTitle=Empfehle SAP Sailing Analytics solutionsSAPNav=SAP Sailing Analytics solutionsSAPHeadline=SAP Sailing Analytics -solutionsSAPText1=By delivering real time analytics around race rankings, speeds, maneuvers, bearings etc., SAP is helping to simplify and demystify the complex sport of sailing. -solutionsSAPText2=SAP Sailing Analytics provide insights and transparency to the world of sailing by utilizing Cloud and In-Memory Technology, processing GPS and wind measurement data in real time and visualizing contents in various frontends accessible from anywhere. +solutionsSAPText1=Bei Echtzeitanalysen rund um Rennplätze, Schnelligkeit, Manöver, Verhalten etc., SAP hilf dabei zu vereinfachen und den Mythos von der Komplexität des Segelns zu nehmen. +solutionsSAPText2=SAP Sailing Analytics ermöglicht Einsichten und Transparenz in die Welt des Segelns durch Nutzung der Cloud und In-Memory Technologien, Erheben von GPS und Windmessungen in echtzeit und den Inhalt auf verschiedenen Oberflächen von überall zu visualisieren. solutionsRaceNav=Race Committee App solutionsRaceHeadline=Race Committee App -solutionsRaceText=The SAP Race Committee App delivers greater efficiency and control to race organizers by automating and simplifying their manual tasks and communication during running sailing regattas. The Race Committee App helps simplify the operation of running and managing race events to help race committees operate smarter. -solutionsPostNav=Post-Race Analytics -solutionsPostHeadline=Post-Race Analytics -solutionsPostText=Access to data collected at sailing events gives sailors the opportunity to assess and analyse their performances to help develop strategies and sail better.
SAP Business Analytics solutions provide sailors, coaches, fans and media with tools to browse and analyse historical data across many regattas and races. SAP HANA allows them to learn about performance patterns, tendencies and sailor profiles in a simple, fast and efficient way in order to optimize their own performance and develop successful strategies for future regattas. -solutionsTrainingNav=Training Diary -solutionsTrainingHeadline=SAP Sail Better \u2013 STG Training Diary -solutionsTrainingText=The STG Training Diary supports the daily documentation and analysis of an athlete\u2019s physical condition and training efforts in the areas of water training, physical training, boat work, mental, theory as well as other activities like education or travel. -solutionsSimulatorNav=Strategy Simulator -solutionsSimulatorHeadline=Strategy Simulator -solutionsSimulatorText=Choosing the right strategy can provide great competitive advantage during a race. The Strategy Simulator simplifies determining the best sailing strategy for various wind conditions and water currents. Since wind and current can be changed in the simulation right away, the robustness of a strategy can be easily evaluated even for uncertain weather conditions. +solutionsRaceText=Die SAP Race Committee App bietet mehr Effizienz und Kontrolle für die Rennleiter durch Automatisieren und vereinfachen der manuellen Aufgaben und der Kommunikation während laufender Segelregatten. Die Race Committee App hilft Aufgaben zu vereinfachen und Segelveranstaltungen zu leiten, um das Segel Komitee besser Handeln zu lassen. +solutionsPostNav=Nachträgliche Rennen Analyse. +solutionsPostHeadline=Nachträgliche Rennen Analyse +solutionsPostText=Zugang zu gesammelten Daten bei Segelveranstaltungen geben Seglern die Möglichkeit die eigene Performance zu beurteilen und zu analysieren, um Strategien zu entwickeln und besser zu segeln.
SAP Business Analytics Lösungen stellen Seglern, Trainern, Fans und Medien Tools zu verfügung, um die historischen Daten über mehrere Regatten und Rennen anzusehen und zu analysieren. SAP HANA erlaubt es ihnen über Verhaltensmuster, Tendenzen und Segelprofilen in einem einfachen, schnellen und effizienten Weg zu erlernen, damit sie ihre Performance und Entwicklung der Strategien in zukünftigen Regatten zu optimieren. +solutionsTrainingNav=Training Tagebuch +solutionsTrainingHeadline=SAP Segelt Besser \u2013 STG Training Tagebuch +solutionsTrainingText=Das STG Trainingstagebuch unterstützt die tägliche Dokumentation und Analyse von der physischen Kondition und Trainingserfolge eines Athleten in Wassertraining, physischen Training, Boot Arbeiten, Theorie und andere Aufgaben wie Bildung oder Reisen. +solutionsSimulatorNav=Strategie Simulator +solutionsSimulatorHeadline=Strategie Simulator +solutionsSimulatorText=Die richtige Strategie zu wählen kann einen großen Vorteil während eines Rennens bringen. Der Strategie Simulator vereinfacht ausschlaggebend die besten Segelstrategien für verschiedene Windkonditionen und Wasserströmungen. Seit dem der Wind und die Strömung während der Simulation direkt geändert werden kann, kann die Strategie sogar für unsichere Wetterverhältnisse einfach bewertet werden. diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/error/ErrorActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/error/ErrorActivity.java new file mode 100644 index 00000000000..189b6a60a33 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/error/ErrorActivity.java @@ -0,0 +1,18 @@ +package com.sap.sailing.gwt.home.client.place.error; + +import com.google.gwt.activity.shared.AbstractActivity; +import com.google.gwt.event.shared.EventBus; +import com.google.gwt.user.client.ui.AcceptsOneWidget; + +public class ErrorActivity extends AbstractActivity { + + public ErrorActivity(ErrorPlace place, ErrorClientFactory clientFactory) { + } + + @Override + public void start(AcceptsOneWidget panel, EventBus eventBus) { + // TODO: the error place should get the error message from the place + panel.setWidget(new TabletAndDesktopErrorView(null, null)); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/error/ErrorActivityProxy.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/error/ErrorActivityProxy.java new file mode 100644 index 00000000000..99ee5c0ecfe --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/error/ErrorActivityProxy.java @@ -0,0 +1,25 @@ +package com.sap.sailing.gwt.home.client.place.error; + +import com.google.gwt.core.client.GWT; +import com.sap.sse.gwt.client.mvp.AbstractActivityProxy; + +public class ErrorActivityProxy extends AbstractActivityProxy { + + private final ErrorClientFactory clientFactory; + private final ErrorPlace place; + + public ErrorActivityProxy(ErrorPlace place, ErrorClientFactory clientFactory) { + this.place = place; + this.clientFactory = clientFactory; + } + + @Override + protected void startAsync() { + GWT.runAsync(new AbstractRunAsyncCallback() { + @Override + public void onSuccess() { + super.onSuccess(new ErrorActivity(place, clientFactory)); + } + }); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/error/ErrorClientFactory.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/error/ErrorClientFactory.java new file mode 100644 index 00000000000..2be2c58653c --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/error/ErrorClientFactory.java @@ -0,0 +1,7 @@ +package com.sap.sailing.gwt.home.client.place.error; + +import com.sap.sailing.gwt.ui.client.SailingClientFactory; + +public interface ErrorClientFactory extends SailingClientFactory { + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/error/ErrorPlace.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/error/ErrorPlace.java new file mode 100644 index 00000000000..75e36136479 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/error/ErrorPlace.java @@ -0,0 +1,19 @@ +package com.sap.sailing.gwt.home.client.place.error; + +import com.google.gwt.place.shared.PlaceTokenizer; +import com.sap.sailing.gwt.home.client.AbstractBasePlace; + +public class ErrorPlace extends AbstractBasePlace { + + public static class Tokenizer implements PlaceTokenizer { + @Override + public String getToken(ErrorPlace place) { + return null; + } + + @Override + public ErrorPlace getPlace(String token) { + return new ErrorPlace(); + } + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/error/TabletAndDesktopErrorView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/error/TabletAndDesktopErrorView.java new file mode 100644 index 00000000000..f36be64b41d --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/error/TabletAndDesktopErrorView.java @@ -0,0 +1,24 @@ +package com.sap.sailing.gwt.home.client.place.error; + +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.client.shared.error.ErrorMessage; +import com.sap.sse.gwt.client.mvp.ErrorView; + +public class TabletAndDesktopErrorView extends Composite implements ErrorView { + private static ErrorViewUiBinder uiBinder = GWT.create(ErrorViewUiBinder.class); + + interface ErrorViewUiBinder extends UiBinder { + } + + @UiField(provided=true) ErrorMessage errorMessage; + + public TabletAndDesktopErrorView(String errorMessageText, Throwable errorReason) { + errorMessage = new ErrorMessage(errorMessageText, errorReason); + + initWidget(uiBinder.createAndBindUi(this)); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/error/TabletAndDesktopErrorView.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/error/TabletAndDesktopErrorView.ui.xml new file mode 100644 index 00000000000..6672708ed5c --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/error/TabletAndDesktopErrorView.ui.xml @@ -0,0 +1,9 @@ + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/AbstractEventComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/AbstractEventComposite.java new file mode 100644 index 00000000000..d34b2cb7cb5 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/AbstractEventComposite.java @@ -0,0 +1,27 @@ +package com.sap.sailing.gwt.home.client.place.event; + +import com.google.gwt.user.client.ui.Composite; +import com.sap.sailing.gwt.ui.shared.EventDTO; + +/** + * A base class for a content composite related to an sailing event + * @author Frank + * + */ +public class AbstractEventComposite extends Composite { + private final EventDTO event; + private final EventPageNavigator pageNavigator; + + public AbstractEventComposite(EventDTO event, EventPageNavigator pageNavigator) { + this.event = event; + this.pageNavigator = pageNavigator; + } + + public EventDTO getEvent() { + return event; + } + + public EventPageNavigator getPageNavigator() { + return pageNavigator; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventActivity.java index 4c47059a9ef..f9e1fdbf044 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventActivity.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventActivity.java @@ -9,12 +9,14 @@ import com.google.gwt.event.shared.EventBus; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.AcceptsOneWidget; +import com.sap.sailing.gwt.home.client.shared.placeholder.Placeholder; import com.sap.sailing.gwt.ui.regattaoverview.RegattaRaceStatesSettings; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sailing.gwt.ui.shared.RaceGroupDTO; import com.sap.sailing.gwt.ui.shared.RegattaOverviewEntryDTO; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; +import com.sap.sse.gwt.client.mvp.ErrorView; import com.sap.sse.gwt.client.player.TimeListener; import com.sap.sse.gwt.client.player.Timer; import com.sap.sse.gwt.client.player.Timer.PlayModes; @@ -30,35 +32,47 @@ public class EventActivity extends AbstractActivity { private final RegattaRaceStatesSettings raceStatesSettings; private EventView view; + private EventDTO event; public EventActivity(EventPlace place, EventClientFactory clientFactory) { this.clientFactory = clientFactory; this.eventPlace = place; + this.view = null; + this.event = null; + timerForClientServerOffset = new Timer(PlayModes.Replay); serverUpdateTimer = new Timer(PlayModes.Live, serverUpdateRateInMs); raceStatesSettings = new RegattaRaceStatesSettings(); - serverUpdateTimer.addTimeListener(new TimeListener() { - - @Override - public void timeChanged(Date newTime, Date oldTime) { - // loadAndUpdateEventRaceStatesLog(); - } - }); } @Override public void start(final AcceptsOneWidget panel, final EventBus eventBus) { + panel.setWidget(new Placeholder()); + final long clientTimeWhenRequestWasSent = System.currentTimeMillis(); UUID eventUUID = UUID.fromString(eventPlace.getEventUuidAsString()); - clientFactory.getSailingService().getEventById(eventUUID, new AsyncCallback() { + clientFactory.getSailingService().getEventById(eventUUID, true, new AsyncCallback() { @Override public void onSuccess(final EventDTO event) { - - clientFactory.getSailingService().getRegattaStructureForEvent(event.id, new AsyncCallback>() { - @Override - public void onSuccess(List raceGroups) { + loadRegattaStructure(panel, clientTimeWhenRequestWasSent, event); + } + + @Override + public void onFailure(Throwable caught) { + createErrorView("Error while loading the event with service getEventById()", caught, panel); + } + }); + } + + private void loadRegattaStructure(final AcceptsOneWidget panel, final long clientTimeWhenRequestWasSent, final EventDTO event) { + this.event = event; + if(event.getLeaderboardGroups().size() > 0) { + clientFactory.getSailingService().getRegattaStructureOfEvent(event.id, new AsyncCallback>() { + @Override + public void onSuccess(List raceGroups) { + if(raceGroups.size() > 0) { for(LeaderboardGroupDTO leaderboardGroupDTO: event.getLeaderboardGroups()) { final long clientTimeWhenResponseWasReceived = System.currentTimeMillis(); if (leaderboardGroupDTO.getAverageDelayToLiveInMillis() != null) { @@ -66,51 +80,76 @@ public class EventActivity extends AbstractActivity { } timerForClientServerOffset.adjustClientServerOffset(clientTimeWhenRequestWasSent, leaderboardGroupDTO.getCurrentServerTime(), clientTimeWhenResponseWasReceived); } - - view = clientFactory.createEventView(event, raceGroups, eventPlace.getLeaderboardIdAsNameString(), timerForClientServerOffset); - panel.setWidget(view.asWidget()); - } - - @Override - public void onFailure(Throwable caught) { - Window.alert("Shit happens at getRegattaStructureForEvent()"); - } - }); - } + createEventView(event, raceGroups, panel); - @Override - public void onFailure(Throwable caught) { - Window.alert("Shit happens at getRegattaStructureForEvent()"); - } - }); + if(event.isRunning()) { + // create update time for race states only for running events + serverUpdateTimer.addTimeListener(new TimeListener() { + @Override + public void timeChanged(Date newTime, Date oldTime) { + loadAndUpdateEventRaceStatesLog(); + } + }); + } + + } else { + createEventWithoutRegattasView(event, panel); + } + } + + @Override + public void onFailure(Throwable caught) { + createErrorView("Error while loading the regatta structure with service getRegattaStructureOfEvent()", caught, panel); + } + }); + } else { + createEventWithoutRegattasView(event, panel); + } + } + + private void createErrorView(String errorMessage, Throwable errorReason, AcceptsOneWidget panel) { + ErrorView view = clientFactory.createErrorView(errorMessage, errorReason); + panel.setWidget(view.asWidget()); + } + + private void createEventView(EventDTO event, List raceGroups, AcceptsOneWidget panel) { + view = clientFactory.createEventView(event, raceGroups, eventPlace.getLeaderboardIdAsNameString(), timerForClientServerOffset); + panel.setWidget(view.asWidget()); } + private void createEventWithoutRegattasView(EventDTO event, AcceptsOneWidget panel) { + // no leaderboard groups defined yet -> show a teaser page + EventWithoutRegattasView view = clientFactory.createEventWithoutRegattasView(event); + panel.setWidget(view.asWidget()); + } + protected void loadAndUpdateEventRaceStatesLog() { - final long clientTimeWhenRequestWasSent = System.currentTimeMillis(); - UUID eventUUID = UUID.fromString(eventPlace.getEventUuidAsString()); + if(view != null && event != null) { + final long clientTimeWhenRequestWasSent = System.currentTimeMillis(); - clientFactory.getSailingService().getRaceStateEntriesForRaceGroup(eventUUID, raceStatesSettings.getVisibleCourseAreas(), - raceStatesSettings.getVisibleRegattas(), raceStatesSettings.isShowOnlyCurrentlyRunningRaces(), raceStatesSettings.isShowOnlyRacesOfSameDay(), - new MarkedAsyncCallback>( - new AsyncCallback>() { - @Override - public void onFailure(Throwable cause) { - Window.alert("Shit happens at getRaceStateEntriesForRaceGroup()"); - } - - @Override - public void onSuccess(List result) { - final long clientTimeWhenResponseWasReceived = System.currentTimeMillis(); - Date serverTimeDuringRequest = null; - for (RegattaOverviewEntryDTO entryDTO : result) { - if (entryDTO.currentServerTime != null) { - serverTimeDuringRequest = entryDTO.currentServerTime; - } + clientFactory.getSailingService().getRaceStateEntriesForRaceGroup(event.id, raceStatesSettings.getVisibleCourseAreas(), + raceStatesSettings.getVisibleRegattas(), raceStatesSettings.isShowOnlyCurrentlyRunningRaces(), raceStatesSettings.isShowOnlyRacesOfSameDay(), + new MarkedAsyncCallback>( + new AsyncCallback>() { + @Override + public void onFailure(Throwable cause) { + Window.setStatus("Error while loading the race states with service getRaceStateEntriesForRaceGroup()"); } - view.updateEventRaceStates(result); - timerForClientServerOffset.adjustClientServerOffset(clientTimeWhenRequestWasSent, serverTimeDuringRequest, clientTimeWhenResponseWasReceived); - } - })); + + @Override + public void onSuccess(List result) { + final long clientTimeWhenResponseWasReceived = System.currentTimeMillis(); + Date serverTimeDuringRequest = null; + for (RegattaOverviewEntryDTO entryDTO : result) { + if (entryDTO.currentServerTime != null) { + serverTimeDuringRequest = entryDTO.currentServerTime; + } + } + view.updateEventRaceStates(result); + timerForClientServerOffset.adjustClientServerOffset(clientTimeWhenRequestWasSent, serverTimeDuringRequest, clientTimeWhenResponseWasReceived); + } + })); + } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventClientFactory.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventClientFactory.java index 7d07e0c4625..6d79fe73c60 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventClientFactory.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventClientFactory.java @@ -9,4 +9,6 @@ import com.sap.sse.gwt.client.player.Timer; public interface EventClientFactory extends SailingClientFactory { EventView createEventView(EventDTO event, List raceGroups, String leaderboardName, Timer timerForClientServerOffset); + + EventWithoutRegattasView createEventWithoutRegattasView(EventDTO event); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventPageNavigator.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventPageNavigator.java index 24fdcdaccc1..f61b6f9234d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventPageNavigator.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventPageNavigator.java @@ -10,7 +10,8 @@ public interface EventPageNavigator { void goToRegattas(); void goToSchedule(); void goToMedia(); - void goToRegattaRaces(RaceGroupDTO raceGroup, StrippedLeaderboardDTO leaderboard); + void goToRegattaRaces(LeaderboardGroupDTO leaderboardGroup, StrippedLeaderboardDTO leaderboard, RaceGroupDTO raceGroup); void openRaceViewer(StrippedLeaderboardDTO leaderboard, RaceDTO race); + void openOverallLeaderboardViewer(LeaderboardGroupDTO leaderboardGroup); void openLeaderboardViewer(LeaderboardGroupDTO leaderboardGroup, StrippedLeaderboardDTO leaderboard); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventPlace.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventPlace.java index 81c9450c1ad..3a90c9bb2e5 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventPlace.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventPlace.java @@ -6,16 +6,30 @@ import com.sap.sailing.gwt.home.client.AbstractBasePlace; public class EventPlace extends AbstractBasePlace { private final String eventUuidAsString; private final String leaderboardIdAsNameString; + private final NavigationTabs navigationTab; + + private final static String PARAM_EVENTID = "eventId"; + private final static String PARAM_LEADEROARD_NAME = "leaderboardName"; + private final static String PARAM_NAVIGATION_TAB = "navigationTab"; + + public enum NavigationTabs { Overview, Regattas, Regatta, Media, Schedule }; public EventPlace(String url) { super(url); - eventUuidAsString = getParameter("eventId"); - leaderboardIdAsNameString = getParameter("leaderboardName"); + eventUuidAsString = getParameter(PARAM_EVENTID); + leaderboardIdAsNameString = getParameter(PARAM_LEADEROARD_NAME); + String paramNavTab = getParameter(PARAM_NAVIGATION_TAB); + if(paramNavTab != null) { + navigationTab = NavigationTabs.valueOf(paramNavTab); + } else { + navigationTab = NavigationTabs.Regattas; + } } - public EventPlace(String eventUuidAsString, String leaderboardIdAsNameString) { - super("eventId", eventUuidAsString, "leaderboardName", leaderboardIdAsNameString); + public EventPlace(String eventUuidAsString, NavigationTabs navigationTab, String leaderboardIdAsNameString) { + super(PARAM_EVENTID, eventUuidAsString, PARAM_NAVIGATION_TAB, navigationTab.name(), PARAM_LEADEROARD_NAME, leaderboardIdAsNameString); this.eventUuidAsString = eventUuidAsString; + this.navigationTab = navigationTab; this.leaderboardIdAsNameString = leaderboardIdAsNameString; } @@ -27,6 +41,10 @@ public class EventPlace extends AbstractBasePlace { return leaderboardIdAsNameString; } + public NavigationTabs getNavigationTab() { + return navigationTab; + } + public static class Tokenizer implements PlaceTokenizer { @Override public String getToken(EventPlace place) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventWithoutRegattasView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventWithoutRegattasView.java new file mode 100644 index 00000000000..24eacd20417 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventWithoutRegattasView.java @@ -0,0 +1,7 @@ +package com.sap.sailing.gwt.home.client.place.event; + +import com.google.gwt.user.client.ui.Widget; + +public interface EventWithoutRegattasView { + Widget asWidget(); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/TabletAndDesktopEventView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/TabletAndDesktopEventView.java index adc9629fa21..73d7a3d87fe 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/TabletAndDesktopEventView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/TabletAndDesktopEventView.java @@ -6,13 +6,19 @@ import java.util.List; import java.util.Map; import com.google.gwt.core.client.GWT; +import com.google.gwt.place.shared.Place; +import com.google.gwt.place.shared.PlaceTokenizer; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.History; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.LeaderboardNameConstants; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.dto.RaceDTO; +import com.sap.sailing.gwt.home.client.place.event.EventPlace.NavigationTabs; +import com.sap.sailing.gwt.home.client.app.PlaceNavigator; import com.sap.sailing.gwt.home.client.place.event.header.EventHeader; import com.sap.sailing.gwt.home.client.place.event.media.EventMedia; import com.sap.sailing.gwt.home.client.place.event.overview.EventOverview; @@ -28,7 +34,7 @@ import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sailing.gwt.ui.shared.RaceGroupDTO; import com.sap.sailing.gwt.ui.shared.RegattaOverviewEntryDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; -import com.sap.sse.common.Util.Pair; +import com.sap.sse.common.Util.Triple; import com.sap.sse.gwt.client.player.Timer; public class TabletAndDesktopEventView extends Composite implements EventView, EventPageNavigator { @@ -47,62 +53,45 @@ public class TabletAndDesktopEventView extends Composite implements EventView, E @UiField EventSponsors eventSponsors; private final List pageElements; - private final Map> leaderboardsWithLeaderboardGroup; + private final EventDTO event; public TabletAndDesktopEventView(SailingServiceAsync sailingService, EventDTO event, List raceGroups, String leaderboardName, - Timer timerForClientServerOffset) { - leaderboardsWithLeaderboardGroup = new HashMap>(); - for(LeaderboardGroupDTO leaderboardGroup: event.getLeaderboardGroups()) { - for(StrippedLeaderboardDTO leaderboard: leaderboardGroup.getLeaderboards()) { - leaderboardsWithLeaderboardGroup.put(leaderboard.name, new Pair(leaderboard, leaderboardGroup)); - } - } - + Timer timerForClientServerOffset, PlaceNavigator navigator) { + this.event = event; + Map> regattaStructure = getRegattaStructure(event, raceGroups); + eventHeader = new EventHeader(event, this); - eventRegattaList = new EventRegattaList(event, raceGroups, leaderboardsWithLeaderboardGroup, timerForClientServerOffset, this); - eventRegattaRaces = new EventRegattaRaces(event, timerForClientServerOffset, this); - eventOverview = new EventOverview(event); - eventSchedule = new EventSchedule(event); - eventMedia = new EventMedia(event); + eventRegattaList = new EventRegattaList(event, regattaStructure, timerForClientServerOffset, navigator, this); + eventRegattaRaces = new EventRegattaRaces(event, timerForClientServerOffset, navigator, this); + eventOverview = new EventOverview(event, this); + eventSchedule = new EventSchedule(event, this); + eventMedia = new EventMedia(event, this); initWidget(uiBinder.createAndBindUi(this)); pageElements = Arrays.asList(new Widget[] { eventOverview, eventRegattaList, eventRegattaRaces, eventMedia, eventSchedule }); - int leaderboardsCount = raceGroups.size(); - if(leaderboardsCount == 0) { - Window.alert("There is no data available for this event."); + Triple selectedRegatta = null; + if(leaderboardName != null) { + selectedRegatta = regattaStructure.get(leaderboardName); + } + + // in case we only have one regatta/leaderboard we go directly to the 'races' page + if(selectedRegatta == null && regattaStructure.size() == 1) { + selectedRegatta = regattaStructure.get(0); + } + + if(selectedRegatta != null && !event.isFakeSeries()) { + goToRegattaRaces(selectedRegatta.getC(), selectedRegatta.getB(), selectedRegatta.getA()); } else { - RaceGroupDTO selectedRaceGroup = null; - - // find the preselected leaderboard (if one exist) - if (leaderboardName != null) { - for (RaceGroupDTO raceGroup : raceGroups) { - if(raceGroup.getName().equals(leaderboardName)) { - selectedRaceGroup = raceGroup; - break; - } - } - } - - // in case we only have one regatta/leaderboard we go directly to the 'races' page - if(selectedRaceGroup == null && leaderboardsCount == 1) { - selectedRaceGroup = raceGroups.get(0); - } - - if(selectedRaceGroup != null) { - goToRegattaRaces(selectedRaceGroup, leaderboardsWithLeaderboardGroup.get(selectedRaceGroup.getName()).getA()); - } else { - goToRegattas(); - } - if(event.getSponsorImageURLs() != null && event.getSponsorImageURLs().size() > 0) { - eventSponsors.setVisible(false); - eventSponsors.setEventSponsors(event.getSponsorImageURLs()); - } else { - eventSponsors.setVisible(false); - eventSponsors.setEventSponsors(null); - } - + goToRegattas(); + } + if(event.getSponsorImageURLs() != null && event.getSponsorImageURLs().size() > 0) { + eventSponsors.setVisible(false); + eventSponsors.setEventSponsors(event.getSponsorImageURLs()); + } else { + eventSponsors.setVisible(false); + eventSponsors.setEventSponsors(null); } } @@ -115,31 +104,46 @@ public class TabletAndDesktopEventView extends Composite implements EventView, E public void goToOverview() { setVisibleEventElement(eventOverview); eventHeader.setDataNavigationType("normal"); + + EventPlace place = new EventPlace(event.id.toString(), NavigationTabs.Overview, null); + pushPlaceToHistoryStack(place, new EventPlace.Tokenizer()); } @Override public void goToRegattas() { setVisibleEventElement(eventRegattaList); eventHeader.setDataNavigationType("normal"); + + EventPlace place = new EventPlace(event.id.toString(), NavigationTabs.Regattas, null); + pushPlaceToHistoryStack(place, new EventPlace.Tokenizer()); } @Override - public void goToRegattaRaces(RaceGroupDTO raceGroup, StrippedLeaderboardDTO leaderboard) { - eventRegattaRaces.setRacesFromRaceGroup(raceGroup, leaderboard); + public void goToRegattaRaces(LeaderboardGroupDTO leaderboardGroup, StrippedLeaderboardDTO leaderboard, RaceGroupDTO raceGroup) { + eventRegattaRaces.setRaces(leaderboardGroup, false, leaderboard, raceGroup); eventHeader.setDataNavigationType("compact"); setVisibleEventElement(eventRegattaRaces); + + EventPlace place = new EventPlace(event.id.toString(), NavigationTabs.Regatta, leaderboard.name); + pushPlaceToHistoryStack(place, new EventPlace.Tokenizer()); } @Override public void goToSchedule() { setVisibleEventElement(eventSchedule); eventHeader.setDataNavigationType("normal"); + + EventPlace place = new EventPlace(event.id.toString(), NavigationTabs.Schedule, null); + pushPlaceToHistoryStack(place, new EventPlace.Tokenizer()); } @Override public void goToMedia() { setVisibleEventElement(eventMedia); eventHeader.setDataNavigationType("normal"); + + EventPlace place = new EventPlace(event.id.toString(), NavigationTabs.Media, null); + pushPlaceToHistoryStack(place, new EventPlace.Tokenizer()); } @Override @@ -155,11 +159,31 @@ public class TabletAndDesktopEventView extends Composite implements EventView, E Window.open(link, "_blank", ""); } + @Override + public void openOverallLeaderboardViewer(LeaderboardGroupDTO leaderboardGroup) { + String link = EntryPointLinkFactory.createLeaderboardLink(createOverallLeaderboardLinkParameters(leaderboardGroup)); + Window.open(link, "_blank", ""); + } + + private Map createOverallLeaderboardLinkParameters(LeaderboardGroupDTO leaderboardGroup) { + Map linkParams = new HashMap(); + linkParams.put("eventId", event.id.toString()); + linkParams.put("name", leaderboardGroup.getName() + " " + LeaderboardNameConstants.OVERALL); + linkParams.put("showRaceDetails", "true"); + if(leaderboardGroup.getDisplayName() != null) { + linkParams.put("displayName", leaderboardGroup.getDisplayName()); + } + linkParams.put("leaderboardGroupName", leaderboardGroup.getName()); + return linkParams; + } + private Map createRaceBoardLinkParameters(String leaderboardName, RegattaAndRaceIdentifier raceIdentifier) { Map linkParams = new HashMap(); + linkParams.put("eventId", event.id.toString()); linkParams.put("leaderboardName", leaderboardName); linkParams.put("raceName", raceIdentifier.getRaceName()); - linkParams.put(RaceBoardViewConfiguration.PARAM_CAN_REPLAY_DURING_LIVE_RACES, "true"); + // TODO this must only be forwarded if there is a logged-on user +// linkParams.put(RaceBoardViewConfiguration.PARAM_CAN_REPLAY_DURING_LIVE_RACES, "true"); linkParams.put(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_MAPCONTROLS, "true"); linkParams.put(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_NAVIGATION_PANEL, "true"); linkParams.put("regattaName", raceIdentifier.getRegattaName()); @@ -168,9 +192,9 @@ public class TabletAndDesktopEventView extends Composite implements EventView, E private Map createLeaderboardLinkParameters(LeaderboardGroupDTO leaderboardGroup, StrippedLeaderboardDTO leaderboard) { Map linkParams = new HashMap(); + linkParams.put("eventId", event.id.toString()); linkParams.put("name", leaderboard.name); linkParams.put("showRaceDetails", "true"); - linkParams.put("embedded", "true"); if (leaderboard.displayName != null) { linkParams.put("displayName", leaderboard.displayName); } @@ -186,4 +210,26 @@ public class TabletAndDesktopEventView extends Composite implements EventView, E } } + private Map> getRegattaStructure(EventDTO event, List raceGroups) { + Map> result = new HashMap<>(); + Map raceGroupsMap = new HashMap<>(); + for (RaceGroupDTO raceGroup: raceGroups) { + raceGroupsMap.put(raceGroup.getName(), raceGroup); + } + + for (LeaderboardGroupDTO leaderboardGroup : event.getLeaderboardGroups()) { + for(StrippedLeaderboardDTO leaderboard: leaderboardGroup.getLeaderboards()) { + String leaderboardName = leaderboard.name; + result.put(leaderboardName, new Triple(raceGroupsMap.get(leaderboardName), + leaderboard, leaderboardGroup)); + } + } + return result; + } + + private void pushPlaceToHistoryStack(T destinationPlace, PlaceTokenizer tokenizer) { + String placeHistoryToken = destinationPlace.getClass().getSimpleName() + ":" + tokenizer.getToken(destinationPlace); + History.newItem(placeHistoryToken, false); + } + } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/TabletAndDesktopEventWithoutRegattasView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/TabletAndDesktopEventWithoutRegattasView.java new file mode 100644 index 00000000000..f140e40cbc6 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/TabletAndDesktopEventWithoutRegattasView.java @@ -0,0 +1,25 @@ +package com.sap.sailing.gwt.home.client.place.event; + +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.client.place.event.header.EventHeader; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.shared.EventDTO; + +public class TabletAndDesktopEventWithoutRegattasView extends Composite implements EventWithoutRegattasView { + private static EventViewUiBinder uiBinder = GWT.create(EventViewUiBinder.class); + + interface EventViewUiBinder extends UiBinder { + } + + @UiField(provided=true) EventHeader eventHeader; + + public TabletAndDesktopEventWithoutRegattasView(SailingServiceAsync sailingService, EventDTO event) { + eventHeader = new EventHeader(event); + + initWidget(uiBinder.createAndBindUi(this)); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/TabletAndDesktopEventWithoutRegattasView.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/TabletAndDesktopEventWithoutRegattasView.ui.xml new file mode 100644 index 00000000000..35264d68afd --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/TabletAndDesktopEventWithoutRegattasView.ui.xml @@ -0,0 +1,21 @@ + + + + + .infoPanel { + height: 300px; + padding-top: 2em; + } + + + +
+
+ There is no information available about the regattas of this event yet. +
+
+
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/CompactEventHeader.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/CompactEventHeader.java new file mode 100644 index 00000000000..80ca5dac1ad --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/CompactEventHeader.java @@ -0,0 +1,95 @@ +package com.sap.sailing.gwt.home.client.place.event.header; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.ImageElement; +import com.google.gwt.dom.client.SpanElement; +import com.google.gwt.dom.client.StyleInjector; +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.ui.Anchor; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.home.client.app.PlaceNavigator; +import com.sap.sailing.gwt.ui.shared.CourseAreaDTO; +import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; +import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; + +public class CompactEventHeader extends Composite { + private static CompactHeaderUiBinder uiBinder = GWT.create(CompactHeaderUiBinder.class); + + interface CompactHeaderUiBinder extends UiBinder { + } + + @UiField Anchor regattaLink; + @UiField Anchor regattaLink2; + @UiField ImageElement eventLogo; + @UiField SpanElement eventNameSpan; + + private final EventDTO event; + private final PlaceNavigator placeNavigator; + private final String leaderboardName; + + private final String defaultLogoUrl = "http://static.sapsailing.com/ubilabsimages/default/default_event_logo.jpg"; + + public CompactEventHeader(EventDTO event, String leaderboardName, PlaceNavigator placeNavigator) { + this.event = event; + this.leaderboardName = leaderboardName; + this.placeNavigator = placeNavigator; + + EventHeaderResources.INSTANCE.css().ensureInjected(); + StyleInjector.injectAtEnd("@media (min-width: 50em) { "+EventHeaderResources.INSTANCE.largeCss().getText()+"}"); + + initWidget(uiBinder.createAndBindUi(this)); + updateUI(); + } + + private StrippedLeaderboardDTO findLeaderboardWithSameCourseArea(EventDTO event) { + for(LeaderboardGroupDTO leaderboardGroup: event.getLeaderboardGroups()) { + for(StrippedLeaderboardDTO leaderboard: leaderboardGroup.getLeaderboards()) { + for(CourseAreaDTO courseArea: event.venue.getCourseAreas()) { + if(leaderboard.defaultCourseAreaId != null && leaderboard.defaultCourseAreaId.equals(courseArea.id)) { + return leaderboard; + } + } + } + } + return null; + } + + private void updateUI() { + boolean isSeries = event.isFakeSeries(); + + String eventName = event.getName(); + if(isSeries) { + LeaderboardGroupDTO leaderboardGroupDTO = event.getLeaderboardGroups().get(0); + eventName = leaderboardGroupDTO.getDisplayName() != null ? leaderboardGroupDTO.getDisplayName() : leaderboardGroupDTO.getName(); + + StrippedLeaderboardDTO leaderboardFittingToEvent = findLeaderboardWithSameCourseArea(event); + if(leaderboardFittingToEvent != null) { + } else { + } + } else { + } + eventNameSpan.setInnerText(eventName); + String logoUrl = event.getLogoImageURL() != null ? event.getLogoImageURL() : defaultLogoUrl; + eventLogo.setSrc(logoUrl); + } + + @UiHandler("regattaLink") + void gotoRegattaClicked(ClickEvent event) { + showRegattaOfEvent(); + } + + @UiHandler("regattaLink2") + void gotoRegatta2Clicked(ClickEvent event) { + showRegattaOfEvent(); + } + + private void showRegattaOfEvent() { + placeNavigator.goToRegattaOfEvent(event.id.toString(), leaderboardName, event.getBaseURL(), event.isOnRemoteServer()); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/CompactEventHeader.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/CompactEventHeader.ui.xml new file mode 100644 index 00000000000..f9f591e74e7 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/CompactEventHeader.ui.xml @@ -0,0 +1,24 @@ + + + + + + + + +
+
+
+ + + + + + +
+
+
+
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/EventHeader.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/EventHeader.css index d39b7c45b44..222152e00aa 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/EventHeader.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/EventHeader.css @@ -1,10 +1,7 @@ -/** - * The header of the pages to an event - */ .dropdown { - font-size: 16px; + font-size: 15px; font-size: 1rem; - width: 18.75em; + width: 20em; max-width: 100%; display: inline-block; text-align: left; @@ -17,9 +14,9 @@ cursor: pointer; background: #f8f8f8; color: #666; - height: 2.5625em; - line-height: 2.5em; - padding-left: 0.9375em; + height: 2.733333333333333em; + line-height: 2.666666666666667em; + padding-left: 1em; font-weight: 700; } .dropdown_head_titlebutton { @@ -30,18 +27,18 @@ z-index: 1; background: #f6f9fc; display: none; - width: 18.75em; + width: 20em; height: auto; - max-height: 21.875em; + max-height: 23.333333333333332em; overflow: auto; - -webkit-box-shadow: 0 0.0625em 0.25em #ccc; - box-shadow: 0 0.0625em 0.25em #ccc; + -webkit-box-shadow: 0 0.066666666666667em 0.266666666666667em #ccc; + box-shadow: 0 0.066666666666667em 0.266666666666667em #ccc; border-bottom: solid 1px #ccc; } .dropdown_content_link { display: block; cursor: pointer; - padding: 1.25em 0.9375em; + padding: 1.333333333333333em 1em; border-bottom: 1px solid #ddd; } .dropdown_content_link:hover, @@ -57,19 +54,19 @@ color: #008fcc; display: block; font-weight: 700; - font-size: 1.125em; + font-size: 1.2em; } .dropdown_content_link_subtitle { color: #666; - font-size: 1em; + font-size: 1.066666666666667em; } .leaderboardquickaccess { - font-size: 16px; + font-size: 15px; font-size: 1rem; text-align: right; } .eventheaderwrapper { - font-size: 16px; + font-size: 15px; font-size: 1rem; } .eventheaderwrapper[data-navigationtype='compact'] .eventheader, @@ -87,35 +84,43 @@ transition: top 0.4s; } .eventheader { - font-size: 16px; + font-size: 15px; font-size: 1rem; background: #f2f2f2; - padding: 1.875em 0; + padding: 2em 0; + border-bottom: 1px solid darkgray; } .eventheader_intro { - padding-left: 9.6875em; - min-height: 8.75em; + padding-left: 10.333333333333334em; + min-height: 9.333333333333334em; position: relative; } .eventheader_intro_description { - font-size: 16px; + font-size: 15px; font-size: 1rem; } .eventheader_intro_logo { - border: solid 5px #fff; -webkit-box-shadow: 0 0 5px rgba(204,204,204,0.25); box-shadow: 0 0 5px rgba(204,204,204,0.25); position: absolute; top: 0; left: 0; + max-width: 140px; + max-height: 140px; + width: auto; + height: auto; +} +.eventheader_intro_logo_image { + border: solid 5px #fff; + max-height: 100%; } .eventheader_intro_name { display: inline-block; - margin: 0 0.3125em 0 0; + margin: 0 0.333333333333333em 0 0; } .eventheader_intro_details { - margin-bottom: 0.625em; - padding-bottom: 0.625em; + margin-bottom: 0.666666666666667em; + padding-bottom: 0.666666666666667em; border-bottom: 1px solid #ddd; font-weight: 600; color: #333; @@ -123,8 +128,8 @@ .eventheader_intro_details_item { display: inline-block; border-right: 1px solid #ddd; - padding: 0.25em 0.625em 0.25em 0; - margin-right: 0.625em; + padding: 0.266666666666667em 0.666666666666667em 0.266666666666667em 0; + margin-right: 0.666666666666667em; } .eventheader_intro_details_itemlink { color: inherit; @@ -146,9 +151,9 @@ } .eventheader_sharing_item { display: inline-block; - height: 2.5em; - width: 2.5em; - margin-left: 0.3125em; + height: 2.666666666666667em; + width: 2.666666666666667em; + margin-left: 0.333333333333333em; background-position: center center; background-repeat: no-repeat; } @@ -156,22 +161,59 @@ background-color: #f0ab00; background-image: url("images/home/email-icon.png"); } +.eventheader_sharing_itememail:hover, +.eventheader_sharing_itememail:focus { + background-color: #d89a00; +} .eventheader_sharing_itemtwitter { background-color: #32b5e2; background-image: url("images/home/twitter-icon.png"); } +.eventheader_sharing_itemtwitter:hover, +.eventheader_sharing_itemtwitter:focus { + background-color: #1faada; +} .eventheader_sharing_itemfacebook { background-color: #3b5998; background-image: url("images/home/facebook-icon.png"); } +.eventheader_sharing_itemfacebook:hover, +.eventheader_sharing_itemfacebook:focus { + background-color: #355089; +} .locationicon { vertical-align: text-bottom; - margin-right: 0.3125em; + margin-right: 0.333333333333333em; } .eventnavigation { - font-size: 16px; + font-size: 15px; font-size: 1rem; - padding: 1.875em 0; +} +.eventnavigationnormal { + position: relative; +} +.eventnavigationcompact, +.eventnavigationcompactfloating { + background: #f2f2f2; + margin: 0; + padding: 1em 0; +} +.eventnavigationcompact_logo, +.eventnavigationcompactfloating_logo { + border: solid 2px #fff; + -webkit-box-shadow: 0 0 5px rgba(204,204,204,0.25); + box-shadow: 0 0 5px rgba(204,204,204,0.25); + margin-right: 0.666666666666667em; +} +.eventnavigationcompactfloating { + position: fixed; + top: -8em; + left: 0; + right: 0; + z-index: 5; +} +.eventnavigationcompactfloating.eventnavigationfixed { + top: 0; } .eventnavigation_link { text-transform: uppercase; @@ -188,30 +230,4 @@ .eventnavigation_linkactive:hover, .eventnavigation_linkactive:focus { background-color: #d89a00; -} -.eventnavigationnormal { - border-bottom: 1px solid #f2f2f2; -} -.eventnavigationcompact, -.eventnavigationcompactfloating { - background: #f2f2f2; - margin: 0; - padding: 0.9375em 0; -} -.eventnavigationcompact_logo, -.eventnavigationcompactfloating_logo { - border: solid 2px #fff; - -webkit-box-shadow: 0 0 5px rgba(204,204,204,0.25); - box-shadow: 0 0 5px rgba(204,204,204,0.25); - margin-right: 0.625em; -} -.eventnavigationcompactfloating { - position: fixed; - top: -5em; - left: 0; - right: 0; - z-index: 5; -} -.eventnavigationcompactfloating.eventnavigationfixed { - top: 0; -} +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/EventHeader.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/EventHeader.java index 8039adc309a..8a9e58dff39 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/EventHeader.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/EventHeader.java @@ -4,25 +4,30 @@ import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.ImageElement; import com.google.gwt.dom.client.SpanElement; +import com.google.gwt.dom.client.Style.Display; +import com.google.gwt.dom.client.StyleInjector; 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.ui.Anchor; -import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.home.client.app.PlaceNavigator; +import com.sap.sailing.gwt.home.client.i18n.TextMessages; +import com.sap.sailing.gwt.home.client.place.event.AbstractEventComposite; import com.sap.sailing.gwt.home.client.place.event.EventPageNavigator; import com.sap.sailing.gwt.home.client.shared.EventDatesFormatterUtil; +import com.sap.sailing.gwt.ui.shared.CourseAreaDTO; import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; +import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; -public class EventHeader extends Composite { +public class EventHeader extends AbstractEventComposite { private static EventHeaderUiBinder uiBinder = GWT.create(EventHeaderUiBinder.class); interface EventHeaderUiBinder extends UiBinder { } - private final EventDTO event; - // @UiField Anchor overviewLink; @UiField Anchor regattasLink; // @UiField Anchor scheduleLink; @@ -37,34 +42,37 @@ public class EventHeader extends Composite { // @UiField Anchor regattasLink3; // @UiField Anchor scheduleLink3; // @UiField Anchor mediaLink3; - + @UiField Anchor officalWebsiteAnchor; + @UiField Anchor twitterAnchor; + @UiField Anchor facebookAnchor; + @UiField DivElement seriesLeaderboardDiv; + @UiField Anchor seriesLeaderboardAnchor; + @UiField DivElement eventHeaderWrapperDiv; - @UiField SpanElement eventName; - @UiField SpanElement eventName2; + @UiField DivElement regattaNameInSeriesDiv; + @UiField SpanElement eventNameSpan; + @UiField SpanElement eventName2Span; // @UiField SpanElement eventName3; - @UiField SpanElement eventDate; - @UiField SpanElement eventDescription; - @UiField SpanElement venueName; - @UiField SpanElement eventWebsite; + @UiField DivElement eventDateDiv; + @UiField DivElement eventDescriptionDiv; + @UiField SpanElement venueNameSpan; + @UiField DivElement isLiveDiv; + @UiField DivElement isFinishedDiv; @UiField ImageElement eventLogo; @UiField ImageElement eventLogo2; // @UiField ImageElement eventLogo3; - private final String defaultLogoUrl = "http://static.sapsailing.com/newhome/default_event_logo.png"; + private final String defaultLogoUrl = "http://static.sapsailing.com/ubilabsimages/default/default_event_logo.jpg"; // private final List links1; // private final List links2; // private final List links3; - - private final EventPageNavigator pageNavigator; public EventHeader(EventDTO event, EventPageNavigator pageNavigator) { - this.event = event; - this.pageNavigator = pageNavigator; + super(event, pageNavigator); - EventHeaderResources.INSTANCE.css().ensureInjected(); - initWidget(uiBinder.createAndBindUi(this)); + initResources(); // links1 = Arrays.asList(new Anchor[] { overviewLink, regattasLink, scheduleLink, mediaLink }); // links2 = Arrays.asList(new Anchor[] { overviewLink2, regattasLink2, scheduleLink2, mediaLink2 }); @@ -73,26 +81,118 @@ public class EventHeader extends Composite { // setActiveLink(links2, overviewLink2); // setActiveLink(links3, overviewLink3); + if(event.isRunning()) { + isFinishedDiv.getStyle().setDisplay(Display.NONE); + } else if (event.isFinished()) { + isLiveDiv.getStyle().setDisplay(Display.NONE); + } else { + isFinishedDiv.getStyle().setDisplay(Display.NONE); + isLiveDiv.getStyle().setDisplay(Display.NONE); + } + setDataNavigationType("normal"); updateUI(); } + + public EventHeader(EventDTO event) { + super(event, null); + + initResources(); + + isFinishedDiv.getStyle().setDisplay(Display.NONE); + isLiveDiv.getStyle().setDisplay(Display.NONE); + + setDataNavigationType("normal"); + updateUI(); + } + + public EventHeader(EventDTO event, PlaceNavigator placeNavigator) { + super(event, null); + + initResources(); + + isFinishedDiv.getStyle().setDisplay(Display.NONE); + isLiveDiv.getStyle().setDisplay(Display.NONE); + + setDataNavigationType("normal"); + updateUI(); + } + + private void initResources() { + EventHeaderResources.INSTANCE.css().ensureInjected(); + StyleInjector.injectAtEnd("@media (min-width: 50em) { "+EventHeaderResources.INSTANCE.largeCss().getText()+"}"); + + initWidget(uiBinder.createAndBindUi(this)); + } public void setDataNavigationType(String dataNavigationType) { eventHeaderWrapperDiv.setAttribute("data-navigationtype", dataNavigationType); } + + private StrippedLeaderboardDTO findLeaderboardWithSameCourseArea(EventDTO event) { + for(LeaderboardGroupDTO leaderboardGroup: event.getLeaderboardGroups()) { + for(StrippedLeaderboardDTO leaderboard: leaderboardGroup.getLeaderboards()) { + for(CourseAreaDTO courseArea: event.venue.getCourseAreas()) { + if(leaderboard.defaultCourseAreaId != null && leaderboard.defaultCourseAreaId.equals(courseArea.id)) { + return leaderboard; + } + } + } + } + return null; + } private void updateUI() { - eventName.setInnerHTML(event.getName()); - eventName2.setInnerHTML(event.getName()); + EventDTO event = getEvent(); + boolean isSeries = event.isFakeSeries(); + + String eventName = event.getName(); + if(isSeries) { + LeaderboardGroupDTO leaderboardGroupDTO = event.getLeaderboardGroups().get(0); + eventName = leaderboardGroupDTO.getDisplayName() != null ? leaderboardGroupDTO.getDisplayName() : leaderboardGroupDTO.getName(); + + StrippedLeaderboardDTO leaderboardFittingToEvent = findLeaderboardWithSameCourseArea(event); + if(leaderboardFittingToEvent != null) { + regattaNameInSeriesDiv.setInnerText(leaderboardFittingToEvent.displayName); + } else { + regattaNameInSeriesDiv.getStyle().setDisplay(Display.NONE); + } + } else { + seriesLeaderboardDiv.getStyle().setDisplay(Display.NONE); + regattaNameInSeriesDiv.getStyle().setDisplay(Display.NONE); + } + + eventNameSpan.setInnerHTML(eventName); + eventName2Span.setInnerHTML(eventName); // eventName3.setInnerHTML(event.getName()); - venueName.setInnerHTML(event.venue.getName()); - eventDate.setInnerHTML(EventDatesFormatterUtil.formatDateRangeWithYear(event.startDate, event.endDate)); + venueNameSpan.setInnerHTML(event.venue.getName()); + eventDateDiv.setInnerHTML(EventDatesFormatterUtil.formatDateRangeWithYear(event.startDate, event.endDate)); if(event.getDescription() != null) { - eventDescription.setInnerHTML(event.getDescription()); + eventDescriptionDiv.setInnerHTML(event.getDescription()); } if(event.getOfficialWebsiteURL() != null) { - eventWebsite.setInnerHTML(event.getOfficialWebsiteURL()); + String title = event.getOfficialWebsiteURL(); + if(title.startsWith("http://")) { + title = title.substring("http://".length(), title.length()); + } + if(title.length() > 35) { + title = TextMessages.INSTANCE.officalEventWebsite(); + } + officalWebsiteAnchor.setText(title); + officalWebsiteAnchor.setHref(event.getOfficialWebsiteURL()); + } else { + officalWebsiteAnchor.setVisible(false); + } + if(event.getTwitterURL() != null) { + twitterAnchor.setHref(event.getTwitterURL()); + } else { + twitterAnchor.setVisible(false); + } + if(event.getFacebookURL() != null) { + facebookAnchor.setHref(event.getFacebookURL()); + } else { + facebookAnchor.setVisible(false); } String logoUrl = event.getLogoImageURL() != null ? event.getLogoImageURL() : defaultLogoUrl; @@ -133,13 +233,20 @@ public class EventHeader extends Composite { showRegattas(); } + @UiHandler("seriesLeaderboardAnchor") + void seriesLeaderboardClicked(ClickEvent clickevent) { + if(getEvent().isFakeSeries()) { + getPageNavigator().openOverallLeaderboardViewer(getEvent().getLeaderboardGroups().get(0)); + } + } + // @UiHandler("regattasLink3") // void regattas3Clicked(ClickEvent event) { // showRegattas(); // } // private void showRegattas() { - pageNavigator.goToRegattas(); + getPageNavigator().goToRegattas(); // setActiveLink(links1, regattasLink); // setActiveLink(links2, regattasLink2); // setActiveLink(links3, regattasLink3); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/EventHeader.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/EventHeader.ui.xml index a3f9dab8703..f02e2b9342d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/EventHeader.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/EventHeader.ui.xml @@ -5,6 +5,23 @@ + + .action { + float: right; + text-align: center; + text-transform: uppercase; + cursor: pointer; + margin-top: 0.5em; + margin-bottom: 0.5em; + } + .action_link { + background-color: #008FCC; + color: white; + } + .action_link:hover { + background-color: #0177ab; + } + @@ -12,29 +29,45 @@
-
-

-
+
+
+

+
+
-
-
-
+
+
+
+ + +
+
-
+
-
+
+ + +
+
+
+ +
+
- + - + + +
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/EventHeaderLarge.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/EventHeaderLarge.css new file mode 100644 index 00000000000..3b8bede771c --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/EventHeaderLarge.css @@ -0,0 +1,42 @@ +.eventheader_intro_details_item:nth-child\(2\) { + border-right: 1px solid #ddd; +} +.eventheader_intro_details_item:last-child { + display: inline-block; +} +.dropdown {} +.dropdown_head {} +.dropdown_head_title {} +.dropdown_head_titlebutton {} +.dropdown_content {} +.dropdown_content_link {} +.dropdown_content_link_title {} +.dropdown_content_link_subtitle {} +.leaderboardquickaccess {} +.eventheaderwrapper {} +.eventheader {} +.eventheader_intro {} +.eventheader_intro_description {} +.eventheader_intro_logo {} +.eventheader_intro_logo_image {} +.eventheader_intro_name {} +.eventheader_intro_details {} +.eventheader_intro_details_item {} +.eventheader_intro_details_itemlink {} +.eventheader_sharing {} +.eventheader_sharing_item {} +.eventheader_sharing_itememail {} +.eventheader_sharing_itemtwitter {} +.eventheader_sharing_itemfacebook {} +.locationicon {} +.eventnavigation {} +.eventnavigationnormal {} +.eventnavigationcompact, +.eventnavigationcompactfloating {} +.eventnavigationcompact_logo, +.eventnavigationcompactfloating_logo {} +.eventnavigationcompactfloating {} +.eventnavigationcompactfloating.eventnavigationfixed {} +.eventnavigation_link {} +.eventnavigation_linkactive {} + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/EventHeaderResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/EventHeaderResources.java index 26b03780885..a737137fa0c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/EventHeaderResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/EventHeaderResources.java @@ -3,6 +3,7 @@ package com.sap.sailing.gwt.home.client.place.event.header; import com.google.gwt.core.client.GWT; import com.google.gwt.resources.client.ClientBundle; import com.google.gwt.resources.client.CssResource; +import com.google.gwt.resources.client.CssResource.Shared; public interface EventHeaderResources extends ClientBundle { public static final EventHeaderResources INSTANCE = GWT.create(EventHeaderResources.class); @@ -10,6 +11,13 @@ public interface EventHeaderResources extends ClientBundle { @Source("com/sap/sailing/gwt/home/client/place/event/header/EventHeader.css") LocalCss css(); + public interface MinWidth50emCss extends LocalCss { + } + + @Source("com/sap/sailing/gwt/home/client/place/event/header/EventHeaderLarge.css") + MinWidth50emCss largeCss(); + + @Shared public interface LocalCss extends CssResource { String dropdown(); String dropdown_head(); @@ -28,6 +36,7 @@ public interface EventHeaderResources extends ClientBundle { String eventheader_intro(); String eventheader_intro_description(); String eventheader_intro_logo(); + String eventheader_intro_logo_image(); String eventheader_intro_name(); String eventheader_intro_details(); String eventheader_intro_details_item(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/media/EventMedia.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/media/EventMedia.java index ade11aa8a5f..4f046441b8c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/media/EventMedia.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/media/EventMedia.java @@ -2,21 +2,19 @@ package com.sap.sailing.gwt.home.client.place.event.media; import com.google.gwt.core.client.GWT; import com.google.gwt.uibinder.client.UiBinder; -import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.home.client.place.event.AbstractEventComposite; +import com.sap.sailing.gwt.home.client.place.event.EventPageNavigator; import com.sap.sailing.gwt.ui.shared.EventDTO; -public class EventMedia extends Composite { +public class EventMedia extends AbstractEventComposite { private static EventMediaUiBinder uiBinder = GWT.create(EventMediaUiBinder.class); interface EventMediaUiBinder extends UiBinder { } - @SuppressWarnings("unused") - private final EventDTO event; - - public EventMedia(EventDTO event) { - this.event = event; + public EventMedia(EventDTO event, EventPageNavigator placeNavigator) { + super(event, placeNavigator); EventMediaResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/overview/EventOverview.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/overview/EventOverview.css index c2ac3b6301c..e9deba83975 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/overview/EventOverview.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/overview/EventOverview.css @@ -381,7 +381,7 @@ background: #000; opacity: 0.4; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=40)"; - filter: alpha(opacity=40); + filter: literal("alpha(opacity=40)"); position: absolute; bottom: 0px; height: 5em; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/overview/EventOverview.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/overview/EventOverview.java index dcf1d2d48b0..4e5f644b9af 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/overview/EventOverview.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/overview/EventOverview.java @@ -2,21 +2,19 @@ package com.sap.sailing.gwt.home.client.place.event.overview; import com.google.gwt.core.client.GWT; import com.google.gwt.uibinder.client.UiBinder; -import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.home.client.place.event.AbstractEventComposite; +import com.sap.sailing.gwt.home.client.place.event.EventPageNavigator; import com.sap.sailing.gwt.ui.shared.EventDTO; -public class EventOverview extends Composite { +public class EventOverview extends AbstractEventComposite { private static EventOverviewUiBinder uiBinder = GWT.create(EventOverviewUiBinder.class); interface EventOverviewUiBinder extends UiBinder { } - @SuppressWarnings("unused") - private final EventDTO event; - - public EventOverview(EventDTO event) { - this.event = event; + public EventOverview(EventDTO event, EventPageNavigator pageNavigator) { + super(event, pageNavigator); EventOverviewResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/Regatta.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/Regatta.css index 914afb8559f..3be9d68d82a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/Regatta.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/Regatta.css @@ -85,11 +85,11 @@ padding-bottom: 0.666666666666667em; } .regatta_intro_phases_phase { - display: inline-block; + display: block; margin-right: 1.333333333333333em; padding: 0.133333333333333em 1.333333333333333em 0.133333333333333em 0; min-height: 2.666666666666667em; - border-right: 1px solid #ccc; + } .regatta_intro_phases_phase_name { font-size: 0.866666666666667em; @@ -102,6 +102,7 @@ background-color: #ccc; height: 0.666666666666667em; width: 1.333333333333333em; + margin-right: 3px; } .regatta_intro_phases_phase_races_race[data-status='live'] { background-color: #f00; @@ -110,16 +111,18 @@ background-color: #f0ab00; } .regatta_intro_phases_leaderboardlink { - font-size: 0.933333333333333em; - font-weight: 700; - border: 1px solid #ccc; - padding: 0.714285714285714em 0.928571428571429em; text-transform: uppercase; z-index: 2; position: absolute; top: 0; right: 0; + background-color: #008FCC; + color: white; } +.regatta_intro_phases_leaderboardlink:hover { + background-color: #0177ab; +} + .regatta_intro_scheduled { padding-top: 0.8em; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/Regatta.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/Regatta.java index f1380f181bb..68539c84c6b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/Regatta.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/Regatta.java @@ -1,20 +1,28 @@ package com.sap.sailing.gwt.home.client.place.event.regatta; +import java.util.ArrayList; +import java.util.List; + 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.HeadingElement; import com.google.gwt.dom.client.SpanElement; import com.google.gwt.dom.client.Style.Display; -import com.google.gwt.dom.client.Style.Visibility; -import com.google.gwt.dom.client.TableElement; +import com.google.gwt.dom.client.StyleInjector; 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.Event; import com.google.gwt.user.client.EventListener; 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.gwt.home.client.BoatClassImageResolver; +import com.sap.sailing.gwt.home.client.BoatClassImageResources; +import com.sap.sailing.gwt.home.client.app.PlaceNavigator; import com.sap.sailing.gwt.home.client.place.event.EventPageNavigator; +import com.sap.sailing.gwt.home.client.shared.LongNamesUtil; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sailing.gwt.ui.shared.RaceGroupDTO; @@ -28,139 +36,155 @@ public class Regatta extends Composite { interface RegattaUiBinder extends UiBinder { } - @SuppressWarnings("unused") private final EventDTO event; private final Timer timerForClientServerOffset; private final EventPageNavigator pageNavigator; + @SuppressWarnings("unused") + private final PlaceNavigator placeNavigator; private StrippedLeaderboardDTO leaderboard; private RaceGroupDTO raceGroup; - - @SuppressWarnings("unused") + private final List phasesElements; private LeaderboardGroupDTO leaderboardGroup; + private final boolean isSingleView; @UiField DivElement regattaDiv; - @UiField HeadingElement regattaName1; - @UiField HeadingElement regattaName2; + @UiField HeadingElement regattaNameHeading; @UiField DivElement leaderboardGroupName; - @UiField SpanElement scheduledStart; + @UiField DivElement leaderboardGroupNameDiv; +// @UiField SpanElement scheduledStart; @UiField DivElement regattaPhasesPanel; @UiField DivElement regattaPhasesInfoDiv; @UiField SpanElement competitorsCount; + @UiField DivElement competitorsCountDiv; @UiField SpanElement racesCount; @UiField SpanElement trackedRacesCount; @UiField DivElement isLiveDiv; - @UiField TableElement isLiveDiv2; - @UiField DivElement isFinishedDiv; - @UiField DivElement isFinishedDiv2; - @UiField DivElement isScheduledDiv; - @UiField AnchorElement leaderboardLink; - @UiField DivElement liveRaceInfosPerFleetPanel; +// @UiField TableElement isLiveDiv2; +// @UiField DivElement isFinishedDiv; +// @UiField DivElement isFinishedDiv2; +// @UiField DivElement isScheduledDiv; +// @UiField AnchorElement leaderboardLink; +// @UiField DivElement liveRaceInfosPerFleetPanel; // @UiField(provided=true) RegattaCompetitor competitorWithRank1; // @UiField(provided=true) RegattaCompetitor competitorWithRank2; // @UiField(provided=true) RegattaCompetitor competitorWithRank3; + + @UiField AnchorElement regattaDetailsLink; + @UiField AnchorElement leaderboardLink; - @UiField DivElement regattaImageWithoutLink; - @UiField AnchorElement regattaImageWithLink; - - @UiField DivElement regattaNameWithoutLink; - @UiField AnchorElement regattaNameWithLink; - - public Regatta(EventDTO event, boolean isNavigatable, Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { + @UiField Image boatClassImage; + + public Regatta(EventDTO event, Timer timerForClientServerOffset, boolean isSingleView, PlaceNavigator placeNavigator, EventPageNavigator pageNavigator) { this.event = event; this.timerForClientServerOffset = timerForClientServerOffset; + this.isSingleView = isSingleView; + this.placeNavigator = placeNavigator; this.pageNavigator = pageNavigator; + phasesElements = new ArrayList(); + // competitorWithRank1 = new RegattaCompetitor(1, null); // competitorWithRank2 = new RegattaCompetitor(2, null); // competitorWithRank3 = new RegattaCompetitor(3, null); RegattaResources.INSTANCE.css().ensureInjected(); - initWidget(uiBinder.createAndBindUi(this)); + StyleInjector.injectAtEnd("@media (min-width: 50em) { "+RegattaResources.INSTANCE.largeCss().getText()+"}"); - if(isNavigatable) { - regattaDiv.setAttribute("data-rendermode", "link"); - regattaImageWithoutLink.getStyle().setDisplay(Display.NONE); - regattaNameWithoutLink.getStyle().setDisplay(Display.NONE); - } else { - regattaImageWithLink.getStyle().setDisplay(Display.NONE); - regattaNameWithLink.getStyle().setDisplay(Display.NONE); - } - - registerEvents(); + initWidget(uiBinder.createAndBindUi(this)); } - public void setData(RaceGroupDTO raceGroup, StrippedLeaderboardDTO leaderboard, LeaderboardGroupDTO leaderboardGroup) { + public void setData(LeaderboardGroupDTO leaderboardGroup, boolean hasMultipleLeaderboardGroups, StrippedLeaderboardDTO leaderboard, + RaceGroupDTO raceGroup) { this.raceGroup = raceGroup; this.leaderboard = leaderboard; this.leaderboardGroup = leaderboardGroup; - + + registerEvents(leaderboardGroup); + boolean hasLiveRace = leaderboard.hasLiveRace(timerForClientServerOffset.getLiveTimePointInMillis()); if(!hasLiveRace) { isLiveDiv.getStyle().setDisplay(Display.NONE); } - boolean isFinished = !hasLiveRace; - if(!isFinished) { - isFinishedDiv.getStyle().setDisplay(Display.NONE); - isFinishedDiv2.getStyle().setDisplay(Display.NONE); + // boolean isFinished... TODO + if(raceGroup.boatClass != null) { + boatClassImage.setResource(BoatClassImageResolver.getBoatClassIconResource(raceGroup.boatClass)); + } else { + boatClassImage.setResource(BoatClassImageResources.INSTANCE.genericBoatClass()); } String regattaDisplayName = leaderboard.displayName != null ? leaderboard.displayName : leaderboard.name; - regattaName1.setInnerText(regattaDisplayName); - regattaName2.setInnerText(regattaDisplayName); - if(raceGroup.leaderboardGroupName != null) { - leaderboardGroupName.setInnerText(raceGroup.leaderboardGroupName); + regattaNameHeading.setInnerText(regattaDisplayName); + if(hasMultipleLeaderboardGroups && leaderboardGroup.getName() != null) { + leaderboardGroupName.setInnerText(LongNamesUtil.shortenLeaderboardGroupName(event.getName(), leaderboardGroup.getName())); leaderboardGroupName.setAttribute("data-labeltype", "group1"); } else { - leaderboardGroupName.getStyle().setDisplay(Display.NONE); + leaderboardGroupNameDiv.getStyle().setDisplay(Display.NONE); } - if(leaderboard.rows != null) { - competitorsCount.setInnerText(String.valueOf(leaderboard.rows.size())); + if(leaderboard.competitorsCount > 0) { + competitorsCount.setInnerText(String.valueOf(leaderboard.competitorsCount)); } else { - competitorsCount.getStyle().setVisibility(Visibility.HIDDEN); + competitorsCountDiv.getStyle().setDisplay(Display.NONE); } - racesCount.setInnerText("tbd"); - trackedRacesCount.setInnerText("tbd"); - - setRegattaProgress(); + setRegattaProgress(leaderboard); } - private void setRegattaProgress() { - if(raceGroup.getSeries().size() == 0) { - regattaPhasesInfoDiv.getStyle().setDisplay(Display.NONE); - } else { - for(RaceGroupSeriesDTO series: raceGroup.getSeries()) { - RegattaPhase regattaPhase = new RegattaPhase(series); - regattaPhasesPanel.appendChild(regattaPhase.getElement()); + private void setRegattaProgress(StrippedLeaderboardDTO leaderboard) { + // clear first + regattaPhasesPanel.removeAllChildren(); + phasesElements.clear(); + + if(raceGroup != null) { + if(raceGroup.getSeries().size() == 0) { + regattaPhasesInfoDiv.getStyle().setDisplay(Display.NONE); + } else { + for(RaceGroupSeriesDTO series: raceGroup.getSeries()) { + RegattaPhase regattaPhase = new RegattaPhase(series, leaderboardGroup, + leaderboard, raceGroup, pageNavigator, timerForClientServerOffset); + regattaPhasesPanel.appendChild(regattaPhase.getElement()); + phasesElements.add(regattaPhase); + } } } + + racesCount.setInnerText(String.valueOf(leaderboard.getRacesCount())); + trackedRacesCount.setInnerText(String.valueOf(leaderboard.getTrackedRacesCount())); } - private void registerEvents() { - Event.sinkEvents(leaderboardLink, Event.ONCLICK); - Event.setEventListener(leaderboardLink, new EventListener() { - @Override - public void onBrowserEvent(Event event) { - pageNavigator.openLeaderboardViewer(null, leaderboard); - } - }); + private void registerEvents(final LeaderboardGroupDTO leaderboardGroup) { + if(isSingleView) { + regattaDetailsLink.getStyle().setDisplay(Display.NONE); - Event.sinkEvents(regattaImageWithLink, Event.ONCLICK); - Event.setEventListener(regattaImageWithLink, new EventListener() { - @Override - public void onBrowserEvent(Event event) { - pageNavigator.goToRegattaRaces(raceGroup, leaderboard); - } - }); + Event.sinkEvents(leaderboardLink, Event.ONCLICK); + Event.setEventListener(leaderboardLink, new EventListener() { + @Override + public void onBrowserEvent(Event browserEvent) { + switch (DOM.eventGetType(browserEvent)) { + case Event.ONCLICK: + pageNavigator.openLeaderboardViewer(leaderboardGroup, leaderboard); + + // the new leaderboard view integrated into the new design + //placeNavigator.goToLeaderboard(event.id.toString(), leaderboard.name, event.getBaseURL(), event.isOnRemoteServer()); + break; + } + } + }); + } else { + leaderboardLink.getStyle().setDisplay(Display.NONE); - Event.sinkEvents(regattaNameWithLink, Event.ONCLICK); - Event.setEventListener(regattaNameWithLink, new EventListener() { - @Override - public void onBrowserEvent(Event event) { - pageNavigator.goToRegattaRaces(raceGroup, leaderboard); - } - }); + Event.sinkEvents(regattaDetailsLink, Event.ONCLICK); + Event.setEventListener(regattaDetailsLink, new EventListener() { + @Override + public void onBrowserEvent(Event browserEvent) { + switch (DOM.eventGetType(browserEvent)) { + case Event.ONCLICK: + pageNavigator.goToRegattaRaces(leaderboardGroup, leaderboard, raceGroup); + break; + } + } + }); + } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/Regatta.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/Regatta.ui.xml index ff8005f5220..796f9e94b52 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/Regatta.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/Regatta.ui.xml @@ -11,30 +11,24 @@
- - - -
- +
+
- -

- -
-

-
+

+
-
 
+
 
 
 
-
+
@@ -43,23 +37,22 @@
-
- -
+ +
+
+ -->
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaLarge.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaLarge.css new file mode 100644 index 00000000000..72dd98d48f4 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaLarge.css @@ -0,0 +1,33 @@ +.regatta_intro_phases_phase { + display: inline-block; + border-right: 1px solid #ccc; +} +.regatta {} +.regatta_intro {} +.regatta_intro_image {} +.regatta_intro_header {} +.regatta_intro_header_name {} +.regatta_intro_header_namelink:hover, +.regatta_intro_header_namelink:focus {} +.regatta_intro_header_details {} +.regatta_intro_header_details_item {} +.regatta_intro_header_details_item_value {} +.regatta_intro_header_details_label {} +.regatta_intro_phases {} +.regatta_intro_phases_phase_name {} +.regatta_intro_phases_phase_races {} +.regatta_intro_phases_phase_races_race {} +.regatta_intro_phases_leaderboardlink {} +.regatta_intro_scheduled {} +.regatta_intro_results {} +.regatta_intro_results_competitor {} +.regatta_intro_results_competitor_name {} +.regatta_intro_fleets {} +.regatta_intro_fleets_fleet {} +.regatta_intro_fleets_fleet:last-child {} +.regatta_intro_fleets_fleet_item {} +.regatta_intro_fleets_fleet_itemfirst {} +.regatta_intro_fleets_fleet_item_value {} +.regatta_intro_fleets_fleet_item_flag {} +.regatta_intro_fleets_fleet_live {} +.regatta_intro_details_itemhideoncompact {} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaPhase.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaPhase.java index ef95e340305..0d75a10f947 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaPhase.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaPhase.java @@ -1,14 +1,23 @@ package com.sap.sailing.gwt.home.client.place.event.regatta; 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.SpanElement; 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.Event; +import com.google.gwt.user.client.EventListener; import com.google.gwt.user.client.ui.UIObject; import com.sap.sailing.domain.common.LeaderboardNameConstants; import com.sap.sailing.domain.common.dto.RaceColumnDTO; +import com.sap.sailing.gwt.home.client.place.event.EventPageNavigator; +import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; +import com.sap.sailing.gwt.ui.shared.RaceGroupDTO; import com.sap.sailing.gwt.ui.shared.RaceGroupSeriesDTO; +import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; +import com.sap.sse.gwt.client.player.Timer; public class RegattaPhase extends UIObject { private static RegattaPhaseUiBinder uiBinder = GWT.create(RegattaPhaseUiBinder.class); @@ -17,9 +26,11 @@ public class RegattaPhase extends UIObject { } @UiField SpanElement phaseName; - @UiField DivElement phaseRacesPanel; + @UiField DivElement phaseRacesDiv; + @UiField AnchorElement phaseRacesAnchor; - public RegattaPhase(RaceGroupSeriesDTO series) { + public RegattaPhase(RaceGroupSeriesDTO series, final LeaderboardGroupDTO leaderboardGroup, + final StrippedLeaderboardDTO leaderboard, final RaceGroupDTO raceGroup, final EventPageNavigator pageNavigator, Timer timerForClientServerOffset) { RegattaResources.INSTANCE.css().ensureInjected(); setElement(uiBinder.createAndBindUi(this)); @@ -29,10 +40,25 @@ public class RegattaPhase extends UIObject { phaseName.setInnerText(series.getName()); } - series.getRaceColumns(); for(RaceColumnDTO raceColumn: series.getRaceColumns()) { - RegattaPhaseRace race = new RegattaPhaseRace(raceColumn); - phaseRacesPanel.appendChild(race.getElement()); + // the raceColumn from the RaceGroupSeriesDTO does not contain all required information for the race state. + // therefore we take it from the LeaderboardDTO + // TODO: try to have only one RaceColumnDTO + RaceColumnDTO raceColumn2 = leaderboard.getRaceColumnByName(raceColumn.getName()); + RegattaPhaseRace race = new RegattaPhaseRace(raceColumn2, timerForClientServerOffset); + phaseRacesDiv.appendChild(race.getElement()); + + Event.sinkEvents(phaseRacesAnchor, Event.ONCLICK); + Event.setEventListener(phaseRacesAnchor, new EventListener() { + @Override + public void onBrowserEvent(Event browserEvent) { + switch (DOM.eventGetType(browserEvent)) { + case Event.ONCLICK: + pageNavigator.goToRegattaRaces(leaderboardGroup, leaderboard, raceGroup); + break; + } + } + }); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaPhase.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaPhase.ui.xml index 5378ec65da4..67224964e3d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaPhase.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaPhase.ui.xml @@ -6,7 +6,9 @@
-
-
+
+ + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaPhaseRace.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaPhaseRace.java index 88c2e8062fb..c0ba7b96fdc 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaPhaseRace.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaPhaseRace.java @@ -6,6 +6,7 @@ import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.ui.UIObject; import com.sap.sailing.domain.common.dto.RaceColumnDTO; +import com.sap.sse.gwt.client.player.Timer; public class RegattaPhaseRace extends UIObject { private static RegattaPhaseRaceUiBinder uiBinder = GWT.create(RegattaPhaseRaceUiBinder.class); @@ -14,11 +15,30 @@ public class RegattaPhaseRace extends UIObject { } @UiField DivElement raceStatus; - - public RegattaPhaseRace(RaceColumnDTO raceColumn) { + + private enum DataStatusEnum { + FINISHED ("finished"), LIVE("live"), SCHEDULED("scheduled"); + + private final String attributeValue; + private static final String ATTRIBUTE_NAME = "data-status"; + + DataStatusEnum(String attributeValue) { + this.attributeValue = attributeValue; + } + } + + public RegattaPhaseRace(RaceColumnDTO raceColumn, Timer timerForClientServerOffset) { RegattaResources.INSTANCE.css().ensureInjected(); setElement(uiBinder.createAndBindUi(this)); - - raceStatus.setAttribute("data-status", "finished"); + + DataStatusEnum dataStatus; + if(raceColumn.hasLiveRaces(timerForClientServerOffset.getLiveTimePointInMillis())) { + dataStatus = DataStatusEnum.LIVE; + } else if (raceColumn.hasTrackedRaces()) { + dataStatus = DataStatusEnum.FINISHED; + } else { + dataStatus = DataStatusEnum.SCHEDULED; + } + raceStatus.setAttribute(DataStatusEnum.ATTRIBUTE_NAME, dataStatus.attributeValue); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaResources.java index 72e848dd155..d976e611cf2 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaResources.java @@ -3,13 +3,20 @@ package com.sap.sailing.gwt.home.client.place.event.regatta; import com.google.gwt.core.client.GWT; import com.google.gwt.resources.client.ClientBundle; import com.google.gwt.resources.client.CssResource; +import com.google.gwt.resources.client.CssResource.Shared; public interface RegattaResources extends ClientBundle { public static final RegattaResources INSTANCE = GWT.create(RegattaResources.class); @Source("com/sap/sailing/gwt/home/client/place/event/regatta/Regatta.css") LocalCss css(); + public interface MinWidth50emCss extends LocalCss { + } + @Source("com/sap/sailing/gwt/home/client/place/event/regatta/RegattaLarge.css") + MinWidth50emCss largeCss(); + + @Shared public interface LocalCss extends CssResource { String regatta(); String regatta_intro_details_itemhideoncompact(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaleaderboard/EventRegattaLeaderboard.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaleaderboard/EventRegattaLeaderboard.css new file mode 100644 index 00000000000..8d8535fded2 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaleaderboard/EventRegattaLeaderboard.css @@ -0,0 +1,181 @@ +.leaderboardcompetitor { + line-height: 1.75; + padding: 0.466666666666667em 0; + border-bottom: 1px solid #ddd; +} +.leaderboardcompetitorshort[datarank='3'] { + border-bottom: 0; +} +.leaderboardcompetitor[datarank='1'] .leaderboardcompetitor_rank, +.leaderboardcompetitor[datarank='2'] .leaderboardcompetitor_rank, +.leaderboardcompetitor[datarank='3'] .leaderboardcompetitor_rank { + display: inline-block; + border-radius: 50%; + color: #fff; + font-weight: 700; + line-height: 2em; + height: 2em; + width: 2em; + padding: 0; +} +.leaderboardcompetitor[datarank='1'] .leaderboardcompetitor_rank { + background: #de8a2e; +} +.leaderboardcompetitor[datarank='2'] .leaderboardcompetitor_rank { + background: #c0c0c0; +} +.leaderboardcompetitor[datarank='3'] .leaderboardcompetitor_rank { + background: #8c7953; +} +.leaderboardcompetitor_image { + vertical-align: middle; +} +.leaderboardcompetitor_nationality { + vertical-align: middle; + margin-right: 0.8em; +} +.leaderboardcompetitor_itemcenter { + text-align: center; +} +.eventregattaleaderboard_table { + padding: 0 1rem; +} +.eventregattaleaderboard { + font-size: 15px; + font-size: 1rem; + width: 100%; +} +.eventregattaleaderboard_meta { + margin: 2.333333333333333em auto; +} +.eventregattaleaderboard_meta_title { + margin-bottom: 0; +} +.eventregattaleaderboard_meta_update { + font-weight: 600; + color: #999; +} +.eventregattaleaderboard_meta_button { + float: right; + margin-top: 1em; +} +.eventregattaleaderboard_meta_reload { + float: right; + width: 39px; + height: 39px; + margin: 0 0 10px 0; + background-color: #f0ab00; + background-image: url("images/home/reload.png"); + background-repeat: no-repeat; + background-position: center center; +} +.eventregattaleaderboard_meta_reload:hover { + background-color: #d89a00; +} +.eventregattaleaderboard_meta_settings { + float: right; + width: 39px; + height: 39px; + margin: 0 0 10px 0; + background-color: #f0ab00; + background-image: url("images/home/settings.png"); + background-repeat: no-repeat; + background-position: center center; +} +.eventregattaleaderboard_meta_settings:hover { + background-color: #d89a00; +} + +.eventregattaleaderboard_header { + background: #f2f2f2; + color: #008fcc; + text-transform: uppercase; + font-weight: 600; + padding: 2.333333333333333em 0; + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; +} +.eventregattaleaderboard_header_item { + margin: 0; + padding: 2.333333333333333em 0 2.333333333333333em 1.666666666666667em; + font-weight: 600; +} +.eventregattaleaderboard_header_itemrank { + min-width: 12rem; + text-align: center; +} +.eventregattaleaderboard_header_itemcompetitor { + min-width: 16rem; +} +.eventregattaleaderboard_header_itemname { + min-width: 16rem; +} +.eventregattaleaderboard_header_itemdata { + min-width: 24rem; +} +.eventregattaleaderboard_header_itemdata_element, +.eventregattaleaderboard_header_itemdata_elementascending, +.eventregattaleaderboard_header_itemdata_elementdescending { + width: 50px; + text-align: right; +} +.eventregattaleaderboard_header_itempoints { + min-width: 12rem; + text-align: center; +} +.eventregattaleaderboard_header_item_name, +.eventregattaleaderboard_header_itemdata_element_name { + display: inline-block; +} +.eventregattaleaderboard_header_itemdescending:before, +.eventregattaleaderboard_header_itemdata_elementdescending:before { + content: ''; + display: inline-block; + width: 12px; + height: 12px; + line-height: 20px; + margin-right: 5px; + background-repeat: no-repeat; + background-position: center center; + background-image: url("images/home/arrow-down-yellow.png"); +} +.eventregattaleaderboard_header_itemascending:before, +.eventregattaleaderboard_header_itemdata_elementascending:before { + content: ''; + display: inline-block; + width: 12px; + height: 12px; + line-height: 20px; + margin-right: 5px; + background-repeat: no-repeat; + background-position: center center; + background-image: url("images/home/arrow-up-yellow.png"); +} +.eventregattaleaderboard_header_table { + width: 100%; + table-layout: fixed; +} +.eventregattaleaderboard_body { + width: 100%; + max-width: 100%; +} +.eventregattaleaderboard_data_element { + width: 50px; + text-align: right; +} +.leaderboardcompetitor:last-child { + border: 0; +} +.leaderboardcompetitor_item, +.leaderboardcompetitor_itemcenter { + padding-left: 1.666666666666667em; +} +.leaderboardcompetitor_itemcenter, +.leaderboardcompetitor_itemcentercenter { + text-align: center; +} +.leaderboardcompetitor_item_table, +.leaderboardcompetitor_itemcenter_table { + width: 100%; + table-layout: fixed; +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaleaderboard/EventRegattaLeaderboard.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaleaderboard/EventRegattaLeaderboard.ui.xml new file mode 100644 index 00000000000..d58c6aade51 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaleaderboard/EventRegattaLeaderboard.ui.xml @@ -0,0 +1,36 @@ + + + + + + + + +
+
+

{leaderboard.scoringComment}{#if navigation.isFinished}{/if navigation.isFinished} +

+
:   {leaderboard.lastScoringUpdate}
+
+
+
+
+ + + + + + + + {#each competitor in leaderboard.competitors}{/each leaderboard.competitors} +
+ + {#each columnName in leaderboard.columnNames}{/each leaderboard.columnNames} + +
+
+
+
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaleaderboard/EventRegattaLeaderboardResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaleaderboard/EventRegattaLeaderboardResources.java new file mode 100644 index 00000000000..8284bec3b8a --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaleaderboard/EventRegattaLeaderboardResources.java @@ -0,0 +1,50 @@ +package com.sap.sailing.gwt.home.client.place.event.regattaleaderboard; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +public interface EventRegattaLeaderboardResources extends ClientBundle { + public static final EventRegattaLeaderboardResources INSTANCE = GWT.create(EventRegattaLeaderboardResources.class); + + @Source("com/sap/sailing/gwt/home/client/place/event/regattaleaderboard/EventRegattaLeaderboard.css") + LocalCss css(); + + public interface LocalCss extends CssResource { + String leaderboardcompetitor(); + String leaderboardcompetitor_rank(); + String leaderboardcompetitor_image(); + String leaderboardcompetitor_nationality(); + String leaderboardcompetitor_itemcenter(); + String eventregattaleaderboard_table(); + String eventregattaleaderboard(); + String eventregattaleaderboard_meta(); + String eventregattaleaderboard_meta_title(); + String eventregattaleaderboard_meta_update(); + String eventregattaleaderboard_meta_button(); + String eventregattaleaderboard_meta_reload(); + String eventregattaleaderboard_meta_settings(); + String eventregattaleaderboard_header(); + String eventregattaleaderboard_header_item(); + String eventregattaleaderboard_header_itemrank(); + String eventregattaleaderboard_header_itemcompetitor(); + String eventregattaleaderboard_header_itemname(); + String eventregattaleaderboard_header_itemdata(); + String eventregattaleaderboard_header_itemdata_element(); + String eventregattaleaderboard_header_itemdata_elementascending(); + String eventregattaleaderboard_header_itemdata_elementdescending(); + String eventregattaleaderboard_header_itempoints(); + String eventregattaleaderboard_header_item_name(); + String eventregattaleaderboard_header_itemdata_element_name(); + String eventregattaleaderboard_header_itemdescending(); + String eventregattaleaderboard_header_itemascending(); + String eventregattaleaderboard_header_table(); + String eventregattaleaderboard_body(); + String eventregattaleaderboard_data_element(); + String leaderboardcompetitor_item(); + String leaderboardcompetitor_itemcentercenter(); + String leaderboardcompetitor_item_table(); + String leaderboardcompetitor_itemcenter_table(); + String leaderboardcompetitorshort(); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaleaderboard/RegattaLeaderboardCompetitor.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaleaderboard/RegattaLeaderboardCompetitor.ui.xml new file mode 100644 index 00000000000..06c5d72a78e --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaleaderboard/RegattaLeaderboardCompetitor.ui.xml @@ -0,0 +1,23 @@ + + + + + + + + + {competitor.rank} + {competitor.nationality}{competitor.sailID} + {competitor.name} + + + {#each raceScore in competitor.raceScores}{/each competitor.raceScores} + +
+ + {competitor.totalPoints} + +
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListNavigationButton.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaleaderboard/RegattaLeaderboardCompetitorRaceScore.ui.xml similarity index 60% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListNavigationButton.ui.xml rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaleaderboard/RegattaLeaderboardCompetitorRaceScore.ui.xml index f9772e70697..80f39316687 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListNavigationButton.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaleaderboard/RegattaLeaderboardCompetitorRaceScore.ui.xml @@ -4,5 +4,9 @@ xmlns:s="urn:import:com.sap.sailing.gwt.home.client.shared"> - + + + +{raceScore.totalPoints} + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/recentevent/RecentEvents.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaleaderboard/RegattaLeaderboardHeaderColumn.ui.xml similarity index 52% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/recentevent/RecentEvents.ui.xml rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaleaderboard/RegattaLeaderboardHeaderColumn.ui.xml index e56e5b3d333..07d1962a879 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/recentevent/RecentEvents.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaleaderboard/RegattaLeaderboardHeaderColumn.ui.xml @@ -4,17 +4,9 @@ xmlns:s="urn:import:com.sap.sailing.gwt.home.client.shared"> - + -
-
- -

- - - - -

-
+ +{columnName}
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaList.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaList.java index f84fd9c6edb..4109c098c99 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaList.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaList.java @@ -1,50 +1,138 @@ package com.sap.sailing.gwt.home.client.place.event.regattalist; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; 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.Document; +import com.google.gwt.dom.client.SpanElement; import com.google.gwt.dom.client.Style.Display; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.EventListener; import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.home.client.HomeResources; +import com.sap.sailing.gwt.home.client.app.PlaceNavigator; +import com.sap.sailing.gwt.home.client.place.event.AbstractEventComposite; import com.sap.sailing.gwt.home.client.place.event.EventPageNavigator; import com.sap.sailing.gwt.home.client.place.event.regatta.Regatta; +import com.sap.sailing.gwt.home.client.shared.LongNamesUtil; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sailing.gwt.ui.shared.RaceGroupDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; -import com.sap.sse.common.Util.Pair; +import com.sap.sse.common.Util.Triple; import com.sap.sse.gwt.client.player.Timer; -public class EventRegattaList extends Composite { +public class EventRegattaList extends AbstractEventComposite { private static EventRegattaHeaderUiBinder uiBinder = GWT.create(EventRegattaHeaderUiBinder.class); interface EventRegattaHeaderUiBinder extends UiBinder { } +// @UiField DivElement seriesLeaderboardDiv; +// @UiField AnchorElement seriesLeaderboardAnchor; + @UiField DivElement regattaGroupsNavigationPanel; - @UiField DivElement regattaListNavgiationDiv; + @UiField DivElement regattaListNavigationDiv; @UiField DivElement regattaListItemsDiv; - @UiField AnchorElement allRegattasLink; - - public EventRegattaList(EventDTO event, List raceGroups, - Map> leaderboardsWithLeaderboardGroup, - Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { + @UiField AnchorElement filterNoLeaderboardGroupsAnchor; + @UiField SpanElement allBoatClassesSelected; + + private final Map> regattaElementsByLeaderboardGroup; + + public EventRegattaList(EventDTO event, Map> regattaStructure, + Timer timerForClientServerOffset, PlaceNavigator placeNavigator, EventPageNavigator pageNavigator) { + super(event, pageNavigator); + EventRegattaListResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); - regattaListNavgiationDiv.getStyle().setDisplay(Display.NONE); + regattaElementsByLeaderboardGroup = new HashMap<>(); + + boolean isSeries = event.isFakeSeries(); + boolean hasMultipleLeaderboardGroups = event.getLeaderboardGroups().size() > 1; - for (RaceGroupDTO raceGroup: raceGroups) { - Regatta regatta = new Regatta(event, true, timerForClientServerOffset, pageNavigator); - Pair leaderboardWithLeaderboardGroup = leaderboardsWithLeaderboardGroup.get(raceGroup.getName()); - regatta.setData(raceGroup, leaderboardWithLeaderboardGroup != null ? leaderboardWithLeaderboardGroup.getA() : null, - leaderboardWithLeaderboardGroup != null ? leaderboardWithLeaderboardGroup.getB() : null); - regattaListItemsDiv.appendChild(regatta.getElement()); + if(isSeries) { + regattaListNavigationDiv.getStyle().setDisplay(Display.NONE); + } else { + if(hasMultipleLeaderboardGroups) { + // fill the navigation panel + registerFilterLeaderboardGroupEvent(filterNoLeaderboardGroupsAnchor, null); + for(LeaderboardGroupDTO leaderboardGroup: event.getLeaderboardGroups()) { + AnchorElement filterLeaderboardGroupAnchor = Document.get().createAnchorElement(); + filterLeaderboardGroupAnchor.setClassName(HomeResources.INSTANCE.mainCss().navbar_button()); + filterLeaderboardGroupAnchor.setHref("javascript:;"); + + String leaderboardGroupName = LongNamesUtil.shortenLeaderboardGroupName(event.getName(), leaderboardGroup.getName()); + filterLeaderboardGroupAnchor.setInnerText(leaderboardGroupName); + regattaGroupsNavigationPanel.appendChild(filterLeaderboardGroupAnchor); + + registerFilterLeaderboardGroupEvent(filterLeaderboardGroupAnchor, leaderboardGroup); + } + } else { + regattaListNavigationDiv.getStyle().setDisplay(Display.NONE); + } + } + + for (LeaderboardGroupDTO leaderboardGroup : event.getLeaderboardGroups()) { + for (StrippedLeaderboardDTO leaderboard : leaderboardGroup.getLeaderboards()) { + Triple r = regattaStructure.get(leaderboard.name); + if (r != null) { + Regatta regatta = new Regatta(event, timerForClientServerOffset, false, placeNavigator, pageNavigator); + regatta.setData(r.getC(), hasMultipleLeaderboardGroups, r.getB(), r.getA()); + regattaListItemsDiv.appendChild(regatta.getElement()); + List regattaElements = regattaElementsByLeaderboardGroup.get(leaderboardGroup.getName()); + if (regattaElements == null) { + regattaElements = new ArrayList(); + regattaElementsByLeaderboardGroup.put(leaderboardGroup.getName(), regattaElements); + } + regattaElements.add(regatta); + } + } + } + } + + private void registerFilterLeaderboardGroupEvent(AnchorElement filterLeaderboardGroupAnchor, final LeaderboardGroupDTO leaderboardGroup) { + Event.sinkEvents(filterLeaderboardGroupAnchor, Event.ONCLICK); + Event.setEventListener(filterLeaderboardGroupAnchor, new EventListener() { + @Override + public void onBrowserEvent(Event event) { + switch (DOM.eventGetType(event)) { + case Event.ONCLICK: + filterRegattaListByLeaderboardGroup(leaderboardGroup); + break; + } + } + }); + } + + private void filterRegattaListByLeaderboardGroup(LeaderboardGroupDTO leaderboardGroup) { + if (leaderboardGroup != null) { + allBoatClassesSelected.getStyle().setDisplay(Display.NONE); + // hide all regattas of the not selected leaderboardgroup + for (LeaderboardGroupDTO lg : getEvent().getLeaderboardGroups()) { + boolean isVisible = leaderboardGroup.getName().equals(lg.getName()); + List regattaElements = regattaElementsByLeaderboardGroup.get(lg.getName()); + for (Regatta regatta : regattaElements) { + regatta.setVisible(isVisible); + } + } + } else { + // make all regattas visible + allBoatClassesSelected.getStyle().setDisplay(Display.INLINE_BLOCK); + for (LeaderboardGroupDTO lg : getEvent().getLeaderboardGroups()) { + List regattaElements = regattaElementsByLeaderboardGroup.get(lg.getName()); + for (Regatta regatta : regattaElements) { + regatta.setVisible(true); + } + } } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaList.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaList.ui.xml index c6780f0c73e..64a280b6c3d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaList.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaList.ui.xml @@ -8,11 +8,10 @@
-
+
-
- -
+
+ ✓ 
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListNavigationButton.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListNavigationButton.java deleted file mode 100644 index aa4ab30ca37..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListNavigationButton.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.sap.sailing.gwt.home.client.place.event.regattalist; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.AnchorElement; -import com.google.gwt.dom.client.DivElement; -import com.google.gwt.uibinder.client.UiBinder; -import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.user.client.ui.UIObject; -import com.sap.sailing.domain.leaderboard.LeaderboardGroup; - -public class EventRegattaListNavigationButton extends UIObject { - private static EventRegattaListNavigationButtonUiBinder uiBinder = GWT.create(EventRegattaListNavigationButtonUiBinder.class); - - interface EventRegattaListNavigationButtonUiBinder extends UiBinder { - } - - @UiField AnchorElement regattaGroupLink; - - public EventRegattaListNavigationButton(LeaderboardGroup leaderboardGroup) { - EventRegattaListResources.INSTANCE.css().ensureInjected(); - - setElement(uiBinder.createAndBindUi(this)); - - regattaGroupLink.setInnerText(leaderboardGroup.getName()); - } -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRaces.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRaces.css index a1eaad80e4d..cfd3dddf088 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRaces.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRaces.css @@ -69,7 +69,7 @@ .eventregattaraces_legend_item:before { content: ' '; position: absolute; - top: -0.2em; + top: literal("-0.2em"); left: 0.666666666666667em; width: 1.733333333333333em; height: 1.733333333333333em; @@ -130,7 +130,7 @@ display: block; float: none; margin-right: 0.555555555555556em; - margin-top: -0.194444444444444em; + margin-top: literal("-0.194444444444444em"); font-size: 2.4em; color: #ccc; font-weight: 700; @@ -229,17 +229,26 @@ .eventregattarace_featuregps { background-position: 0 0; } +.eventregattarace_featuregps.eventregattarace_featureunavailable { + background-position: 0 100%; +} .eventregattarace_featurewind { background-position: 33% 0; } +.eventregattarace_featurewind.eventregattarace_featureunavailable { + background-position: 33% 100%; +} .eventregattarace_featurevideo { background-position: 66% 0; } +.eventregattarace_featurevideo.eventregattarace_featureunavailable { + background-position: 66% 100%; +} .eventregattarace_featureaudio { background-position: 100% 0; } -.eventregattarace_featureunavailable { - background-position-y: 100%; +.eventregattarace_featureaudio.eventregattarace_featureunavailable { + background-position: 100% 100%; } .regattanavigation { font-size: 15px; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRaces.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRaces.java index b6abcdaf056..fd6dd47e5b4 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRaces.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRaces.java @@ -1,20 +1,26 @@ package com.sap.sailing.gwt.home.client.place.event.regattaraces; +import java.util.ArrayList; +import java.util.List; + import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.StyleInjector; 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.HTMLPanel; import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.home.client.app.PlaceNavigator; +import com.sap.sailing.gwt.home.client.place.event.AbstractEventComposite; import com.sap.sailing.gwt.home.client.place.event.EventPageNavigator; import com.sap.sailing.gwt.home.client.place.event.regatta.Regatta; import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sailing.gwt.ui.shared.RaceGroupDTO; import com.sap.sailing.gwt.ui.shared.RaceGroupSeriesDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; import com.sap.sse.gwt.client.player.Timer; -public class EventRegattaRaces extends Composite { +public class EventRegattaRaces extends AbstractEventComposite { private static EventRegattaRacesUiBinder uiBinder = GWT.create(EventRegattaRacesUiBinder.class); interface EventRegattaRacesUiBinder extends UiBinder { @@ -25,36 +31,41 @@ public class EventRegattaRaces extends Composite { @UiField HTMLPanel regattaPhasesPanel; @UiField HTMLPanel rootPanel; - @SuppressWarnings("unused") - private final EventDTO event; - private final Timer timerForClientServerOffset; - private final EventPageNavigator pageNavigator; + private final List phaseElements; - public EventRegattaRaces(EventDTO event, Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { - this.event = event; + public EventRegattaRaces(EventDTO event, Timer timerForClientServerOffset, PlaceNavigator placeNavigator, EventPageNavigator pageNavigator) { + super(event, pageNavigator); this.timerForClientServerOffset = timerForClientServerOffset; - this.pageNavigator = pageNavigator; - regatta = new Regatta(event, false, timerForClientServerOffset, pageNavigator); + phaseElements = new ArrayList(); + + regatta = new Regatta(event, timerForClientServerOffset, true, placeNavigator, pageNavigator); EventRegattaRacesResources.INSTANCE.css().ensureInjected(); + StyleInjector.injectAtEnd("@media (min-width: 25em) { "+EventRegattaRacesResources.INSTANCE.mediumCss().getText()+"}"); initWidget(uiBinder.createAndBindUi(this)); } - public void setRacesFromRaceGroup(RaceGroupDTO raceGroup, StrippedLeaderboardDTO leaderboard) { - regatta.setData(raceGroup, leaderboard, null); + public void setRaces(LeaderboardGroupDTO leaderboardGroup, boolean hasMultipleLeaderboardGroups, StrippedLeaderboardDTO leaderboard, RaceGroupDTO raceGroup) { + // clear all existing child elements first + regattaPhasesPanel.getElement().removeAllChildren(); + phaseElements.clear(); + + regatta.setData(leaderboardGroup, hasMultipleLeaderboardGroups, leaderboard, raceGroup); int regattaPhases = raceGroup.getSeries().size(); if(regattaPhases > 1) { for(RaceGroupSeriesDTO series: raceGroup.getSeries()) { - EventRegattaRacesPhase regattaPhase = new EventRegattaRacesPhase(leaderboard, series, timerForClientServerOffset, pageNavigator); + EventRegattaRacesPhase regattaPhase = new EventRegattaRacesPhase(leaderboard, series, timerForClientServerOffset, getPageNavigator()); regattaPhasesPanel.getElement().appendChild(regattaPhase.getElement()); + phaseElements.add(regattaPhase); } } else { RaceGroupSeriesDTO raceGroupSeriesDTO = raceGroup.getSeries().get(0); - EventRegattaRacesPhase regattaPhase = new EventRegattaRacesPhase(leaderboard, raceGroupSeriesDTO, timerForClientServerOffset, pageNavigator); + EventRegattaRacesPhase regattaPhase = new EventRegattaRacesPhase(leaderboard, raceGroupSeriesDTO, timerForClientServerOffset, getPageNavigator()); regattaPhasesPanel.getElement().appendChild(regattaPhase.getElement()); + phaseElements.add(regattaPhase); } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesFleet.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesFleet.java index 8c74a36ca30..fb7091b32d7 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesFleet.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesFleet.java @@ -2,13 +2,13 @@ package com.sap.sailing.gwt.home.client.place.event.regattaraces; 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.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.UIObject; import com.sap.sailing.domain.common.dto.FleetDTO; -public class EventRegattaRacesFleet { +public class EventRegattaRacesFleet extends UIObject { private static EventRegattaRacesFleetUiBinder uiBinder = GWT.create(EventRegattaRacesFleetUiBinder.class); interface EventRegattaRacesFleetUiBinder extends UiBinder { @@ -16,21 +16,17 @@ public class EventRegattaRacesFleet { @UiField SpanElement fleetName; @UiField DivElement fleetCountAndColor; - - private DivElement root; - public EventRegattaRacesFleet(FleetDTO fleet) { + public EventRegattaRacesFleet(FleetDTO fleet, int maxNumberOfCompetitorsInFleetRace) { EventRegattaRacesResources.INSTANCE.css().ensureInjected(); - root = uiBinder.createAndBindUi(this); + setElement(uiBinder.createAndBindUi(this)); fleetName.setInnerText(fleet.getName()); if(fleet.getColor() != null) { fleetCountAndColor.getStyle().setBackgroundColor(fleet.getColor().getAsHtml()); } - fleetCountAndColor.setInnerText("tbd."); - } - - public Element getElement() { - return root; + if(maxNumberOfCompetitorsInFleetRace > 0) { + fleetCountAndColor.setInnerText(String.valueOf(maxNumberOfCompetitorsInFleetRace)); + } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesFleetGroup.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesFleetGroup.java index e91d069f878..046bf2e3256 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesFleetGroup.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesFleetGroup.java @@ -1,6 +1,7 @@ package com.sap.sailing.gwt.home.client.place.event.regattaraces; import java.util.ArrayList; +import java.util.Date; import java.util.List; import com.google.gwt.core.client.GWT; @@ -13,6 +14,7 @@ import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.domain.common.dto.FleetDTO; import com.sap.sailing.domain.common.dto.RaceColumnDTO; import com.sap.sailing.gwt.home.client.place.event.EventPageNavigator; +import com.sap.sailing.gwt.home.client.shared.EventDatesFormatterUtil; import com.sap.sailing.gwt.ui.shared.RaceGroupSeriesDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; import com.sap.sse.gwt.client.player.Timer; @@ -24,6 +26,7 @@ public class EventRegattaRacesFleetGroup extends Composite { } @UiField DivElement fleetsPanel; + @UiField DivElement racesFromToDate; @UiField DivElement racesFleetPanel; public EventRegattaRacesFleetGroup(StrippedLeaderboardDTO leaderboard, RaceGroupSeriesDTO series, List fleetsToShow, Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { @@ -31,15 +34,10 @@ public class EventRegattaRacesFleetGroup extends Composite { initWidget(uiBinder.createAndBindUi(this)); for(FleetDTO fleet: fleetsToShow) { - EventRegattaRacesFleet eventRegattaRacesFleet = new EventRegattaRacesFleet(fleet); + EventRegattaRacesFleet eventRegattaRacesFleet = new EventRegattaRacesFleet(fleet, 0); fleetsPanel.appendChild(eventRegattaRacesFleet.getElement()); - - List racesOfFleet = getRacesOfFleet(leaderboard, series, fleet); - for(RaceColumnDTO raceColumn: racesOfFleet) { - RaceColumnDTO raceColumnFromLeaderboard = leaderboard.getRaceColumnByName(raceColumn.getName()); - EventRegattaRacesRace race = new EventRegattaRacesRace(leaderboard, fleet, raceColumnFromLeaderboard, timerForClientServerOffset, pageNavigator); - racesFleetPanel.appendChild(race.getElement()); - } + + updateFleetRacesUI(leaderboard, series, fleet, timerForClientServerOffset, pageNavigator); } } @@ -48,13 +46,36 @@ public class EventRegattaRacesFleetGroup extends Composite { initWidget(uiBinder.createAndBindUi(this)); fleetsPanel.getStyle().setDisplay(Display.NONE); - + FleetDTO defaultFleet = series.getFleets().get(0); - List racesOfFleet = getRacesOfFleet(leaderboard, series, defaultFleet); + updateFleetRacesUI(leaderboard, series, defaultFleet, timerForClientServerOffset, pageNavigator); + } + + private void updateFleetRacesUI(StrippedLeaderboardDTO leaderboard, RaceGroupSeriesDTO series, FleetDTO fleet, Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { + Date fromDate = null; + Date toDate = null; + + List racesOfFleet = getRacesOfFleet(leaderboard, series, fleet); for(RaceColumnDTO raceColumn: racesOfFleet) { RaceColumnDTO raceColumnFromLeaderboard = leaderboard.getRaceColumnByName(raceColumn.getName()); - EventRegattaRacesRace race = new EventRegattaRacesRace(leaderboard, defaultFleet, raceColumnFromLeaderboard, timerForClientServerOffset, pageNavigator); + EventRegattaRacesRace race = new EventRegattaRacesRace(leaderboard, fleet, raceColumnFromLeaderboard, timerForClientServerOffset, pageNavigator); racesFleetPanel.appendChild(race.getElement()); + + Date startDate = raceColumnFromLeaderboard.getStartDate(fleet); + if(startDate != null) { + if(fromDate == null || startDate.before(fromDate)) { + fromDate = startDate; + } + if(toDate == null || startDate.after(toDate)) { + toDate = startDate; + } + } + } + if(fromDate != null && toDate != null) { + racesFromToDate.setInnerText(EventDatesFormatterUtil.formatDateRangeWithYear(fromDate, toDate)); + } else { + // no single race with a date + racesFromToDate.getStyle().setDisplay(Display.NONE); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesFleetGroup.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesFleetGroup.ui.xml index 5a5fddf7aaa..9e763849cff 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesFleetGroup.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesFleetGroup.ui.xml @@ -7,6 +7,7 @@
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesMedium.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesMedium.css new file mode 100644 index 00000000000..eef89812601 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesMedium.css @@ -0,0 +1,50 @@ +.eventregattarace_nameshort { + float: left; +} +.eventregattarace_flag { + display: inline-block; +} +.eventregattaraces {} +.eventregattaraces_phase {} +.eventregattaraces_phase_name {} +.eventregattaraces_phase_day {} +.eventregattaraces_phase_races {} +.eventregattaraces_phase_fleets {} +.eventregattaraces_phase_fleet {} +.eventregattaraces_phase_fleet_name {} +.eventregattaraces_phase_fleet_count {} +.eventregattaraces_legend {} +.eventregattaraces_legend_item {} +.eventregattarace {} +.eventregattarace_fleetindicator {} +.eventregattarace_details {} +.eventregattarace_name {} +.eventregattarace_flag {} +.eventregattarace_wind {} +.eventregattarace_wind_strength {} +.eventregattarace_leader {} +.eventregattarace_leader_name {} +.eventregattarace_features, +.eventregattarace_legs {} +.eventregattarace_features_list, +.eventregattarace_legs_list {} +.eventregattarace_features {} +.eventregattarace_legs_leg {} +.eventregattarace_action {} +.eventregattarace_action_watch, +.eventregattarace_action_analyze {} +.eventregattarace_action_watch {} +.eventregattarace_actiondisabled {} +.eventregattarace_feature {} +.eventregattarace_featuregps {} +.eventregattarace_featurewind {} +.eventregattarace_featurevideo {} +.eventregattarace_featureaudio {} +.eventregattarace_featureunavailable {} +.regattanavigation {} +.regattanavigation_link {} +.regattanavigation_linkactive {} +.eventregattaraces_legend_itemgps {} +.eventregattaraces_legend_itemwind {} +.eventregattaraces_legend_itemvideo {} +.eventregattaraces_legend_itemaudio {} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesPhase.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesPhase.java index a9ec4ffc8ab..41cd0f686f3 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesPhase.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesPhase.java @@ -9,7 +9,9 @@ import com.google.gwt.dom.client.SpanElement; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.ui.UIObject; +import com.sap.sailing.domain.common.LeaderboardNameConstants; import com.sap.sailing.domain.common.dto.FleetDTO; +import com.sap.sailing.gwt.home.client.i18n.TextMessages; import com.sap.sailing.gwt.home.client.place.event.EventPageNavigator; import com.sap.sailing.gwt.ui.shared.RaceGroupSeriesDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; @@ -28,7 +30,11 @@ public class EventRegattaRacesPhase extends UIObject { EventRegattaRacesResources.INSTANCE.css().ensureInjected(); setElement(uiBinder.createAndBindUi(this)); - phaseName.setInnerText(series.getName()); + if(series.getName().equals(LeaderboardNameConstants.DEFAULT_SERIES_NAME)) { + phaseName.setInnerText(TextMessages.INSTANCE.races()); + } else { + phaseName.setInnerText(series.getName()); + } int numberOfFleets = series.getFleets().size(); if(numberOfFleets > 1) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesRace.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesRace.java index a26e5dc7561..3f9f7f4d4e6 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesRace.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesRace.java @@ -7,11 +7,14 @@ import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.SpanElement; import com.google.gwt.dom.client.Style.Display; import com.google.gwt.dom.client.Style.Visibility; +import com.google.gwt.i18n.shared.DateTimeFormat; 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.Event; import com.google.gwt.user.client.EventListener; import com.google.gwt.user.client.ui.UIObject; +import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.domain.common.dto.FleetDTO; import com.sap.sailing.domain.common.dto.RaceColumnDTO; import com.sap.sailing.domain.common.dto.RaceDTO; @@ -22,19 +25,20 @@ import com.sap.sse.gwt.client.player.Timer; public class EventRegattaRacesRace extends UIObject { private static EventRegattaRacesRaceUiBinder uiBinder = GWT.create(EventRegattaRacesRaceUiBinder.class); - private enum SimpleRaceStates { NOT_TRACKED, TRACKED_AND_NOT_LIVE, TRACKED_AND_LIVE }; + private enum SimpleRaceStates { NOT_TRACKED, TRACKED, TRACKED_AND_LIVE, TRACKED_BUT_NOT_SCHEDULED }; interface EventRegattaRacesRaceUiBinder extends UiBinder { } @UiField DivElement fleetColor; @UiField SpanElement raceName; -// @UiField SpanElement raceName2; -// @UiField SpanElement raceState; -// @UiField SpanElement raceTime; + @UiField SpanElement raceName2; + @UiField SpanElement raceState; + @UiField SpanElement raceTime; @UiField SpanElement averageRaceWind; @UiField DivElement raceNotTrackedDiv; + @UiField DivElement raceNotScheduledDiv; @UiField DivElement watchRaceDiv; @UiField DivElement analyzeRaceDiv; @UiField AnchorElement watchRaceLink; @@ -55,14 +59,15 @@ public class EventRegattaRacesRace extends UIObject { @UiField DivElement featureAudio; @UiField DivElement featureVideo; - // local_res.css.eventregattarace_featureunavailable + @UiField SpanElement currentLegNo; + @UiField SpanElement totalLegsCount; -// private final DateTimeFormat raceTimeFormat = DateTimeFormat.getFormat("EEE, h:mm a"); -// private final TextMessages textMessages = GWT.create(TextMessages.class); + private final DateTimeFormat raceTimeFormat = DateTimeFormat.getFormat("EEE, h:mm a"); private final StrippedLeaderboardDTO leaderboard; private final FleetDTO fleet; private final RaceColumnDTO raceColumn; + private final RaceDTO race; private final EventPageNavigator pageNavigator; private final Timer timerForClientServerOffset; @@ -74,11 +79,12 @@ public class EventRegattaRacesRace extends UIObject { this.raceColumn = raceColumn; this.pageNavigator = pageNavigator; this.timerForClientServerOffset = timerForClientServerOffset; + race = raceColumn.getRace(fleet); EventRegattaRacesResources.INSTANCE.css().ensureInjected(); setElement(uiBinder.createAndBindUi(this)); - allConditionalElements = new Element[] {raceWinnerDiv, raceLeaderDiv, watchRaceDiv, analyzeRaceDiv, raceNotTrackedDiv, + allConditionalElements = new Element[] {raceWinnerDiv, raceLeaderDiv, watchRaceDiv, analyzeRaceDiv, raceNotTrackedDiv, raceNotScheduledDiv, raceFeaturesDiv, legProgressDiv, raceFlagDiv, windStatusDiv }; if(fleet.getColor() != null) { @@ -86,7 +92,7 @@ public class EventRegattaRacesRace extends UIObject { } raceName.setInnerText(raceColumn.getName()); -// raceName2.setInnerText(raceColumn.getName()); + raceName2.setInnerText(raceColumn.getName()); registerEvents(); updateUI(); @@ -97,7 +103,11 @@ public class EventRegattaRacesRace extends UIObject { Event.setEventListener(analyzeRaceLink, new EventListener() { @Override public void onBrowserEvent(Event event) { - openRaceViewer(); + switch (DOM.eventGetType(event)) { + case Event.ONCLICK: + openRaceViewer(); + break; + } } }); @@ -105,7 +115,11 @@ public class EventRegattaRacesRace extends UIObject { Event.setEventListener(watchRaceLink, new EventListener() { @Override public void onBrowserEvent(Event event) { - openRaceViewer(); + switch (DOM.eventGetType(event)) { + case Event.ONCLICK: + openRaceViewer(); + break; + } } }); } @@ -113,10 +127,14 @@ public class EventRegattaRacesRace extends UIObject { private SimpleRaceStates getSimpleRaceState() { SimpleRaceStates simpleRaceState = SimpleRaceStates.NOT_TRACKED; - RaceDTO race = raceColumn.getRace(fleet); if(race != null && race.trackedRace != null) { - simpleRaceState = isLive() ? SimpleRaceStates.TRACKED_AND_LIVE : SimpleRaceStates.TRACKED_AND_NOT_LIVE; -// String startOfTrackingTime = raceTimeFormat.format(race.trackedRace.startOfTracking); + simpleRaceState = SimpleRaceStates.TRACKED; + if(isLive()) { + simpleRaceState = SimpleRaceStates.TRACKED_AND_LIVE; + if(race.startOfRace == null) { + simpleRaceState = SimpleRaceStates.TRACKED_BUT_NOT_SCHEDULED; + } + } } return simpleRaceState; } @@ -136,19 +154,70 @@ public class EventRegattaRacesRace extends UIObject { case NOT_TRACKED: showElement(raceNotTrackedDiv); break; + case TRACKED_BUT_NOT_SCHEDULED: + showElement(raceNotScheduledDiv); + break; case TRACKED_AND_LIVE: showElement(watchRaceDiv); - showElement(raceLeaderDiv); - showElement(legProgressDiv); + + if(race.startOfRace != null) { + raceTime.setInnerText(raceTimeFormat.format(race.startOfRace)); + } + if(race.trackedRaceStatistics.hasLegProgressData) { + showElement(legProgressDiv); + currentLegNo.setInnerText(String.valueOf(race.trackedRaceStatistics.currentLegNo)); + totalLegsCount.setInnerText(String.valueOf(race.trackedRaceStatistics.totalLegsCount)); + } + if(race.trackedRaceStatistics.hasLeaderOrWinnerData && race.trackedRaceStatistics.leaderOrWinner != null) { + showElement(raceLeaderDiv); + updateWinnerOrLeader(raceLeader, race.trackedRaceStatistics.leaderOrWinner); + } + break; - case TRACKED_AND_NOT_LIVE: + case TRACKED: showElement(analyzeRaceDiv); - showElement(raceWinnerDiv); + + if(race.trackedRaceStatistics.hasLeaderOrWinnerData && race.trackedRaceStatistics.leaderOrWinner != null) { + showElement(raceWinnerDiv); + updateWinnerOrLeader(raceWinner, race.trackedRaceStatistics.leaderOrWinner); + } + if(race.startOfRace != null) { + raceTime.setInnerText(raceTimeFormat.format(race.startOfRace)); + } showElement(raceFeaturesDiv); + updateRaceFeatures(); + break; } } + private void updateWinnerOrLeader(SpanElement winnerOrLeaderElement, CompetitorDTO winnerOrLeader) { + String winnerOrLeaderName = winnerOrLeader.getName(); + if(winnerOrLeader.getThreeLetterIocCountryCode() != null) { + winnerOrLeaderName += " (" + winnerOrLeader.getThreeLetterIocCountryCode() + ")"; + } + winnerOrLeaderElement.setInnerText(winnerOrLeaderName); + } + + private void updateRaceFeatures() { + if(!race.trackedRaceStatistics.hasGPSData) { + featureGPS.addClassName(EventRegattaRacesResources.INSTANCE.css().eventregattarace_featureunavailable()); + } + if(!race.trackedRaceStatistics.hasMeasuredWindData) { + featureWind.addClassName(EventRegattaRacesResources.INSTANCE.css().eventregattarace_featureunavailable()); + } + if(!race.trackedRaceStatistics.hasVideoData) { + featureVideo.addClassName(EventRegattaRacesResources.INSTANCE.css().eventregattarace_featureunavailable()); + } else { + featureVideo.setTitle(String.valueOf(race.trackedRaceStatistics.videoTracksCount)); + } + if(!race.trackedRaceStatistics.hasAudioData) { + featureAudio.addClassName(EventRegattaRacesResources.INSTANCE.css().eventregattarace_featureunavailable()); + } else { + featureAudio.setTitle(String.valueOf(race.trackedRaceStatistics.audioTracksCount)); + } + } + private void hideElement(Element el) { el.getStyle().setVisibility(Visibility.HIDDEN); el.getStyle().setDisplay(Display.NONE); @@ -160,7 +229,6 @@ public class EventRegattaRacesRace extends UIObject { } private void openRaceViewer() { - RaceDTO race = raceColumn.getRace(fleet); if(race != null && race.trackedRace != null) { pageNavigator.openRaceViewer(leaderboard, race); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesRace.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesRace.ui.xml index 9837731999b..69bc2435f9e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesRace.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesRace.ui.xml @@ -10,10 +10,8 @@
-
@@ -24,12 +22,12 @@
-

-   +

- Leader:

-   +

-
Leg (race.leg of race.totalLegs): +
 /
@@ -43,6 +41,9 @@
+
+ +
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesRaceday.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesRaceday.java index 7f2df8dba55..9a2430eeac1 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesRaceday.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesRaceday.java @@ -9,6 +9,11 @@ import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.ui.shared.RaceGroupSeriesDTO; +/** + * Not used yet + * @author Frank + * + */ public class EventRegattaRacesRaceday extends Composite { private static EventRegattaRacesRacedayUiBinder uiBinder = GWT.create(EventRegattaRacesRacedayUiBinder.class); @@ -19,7 +24,6 @@ public class EventRegattaRacesRaceday extends Composite { @UiField HTMLPanel racesPanel; public EventRegattaRacesRaceday(RaceGroupSeriesDTO series) { -// private final EventPageNavigator pageNavigator; EventRegattaRacesResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesResources.java index e0907f95721..4e4f4e4e35c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesResources.java @@ -3,13 +3,21 @@ package com.sap.sailing.gwt.home.client.place.event.regattaraces; import com.google.gwt.core.client.GWT; import com.google.gwt.resources.client.ClientBundle; import com.google.gwt.resources.client.CssResource; +import com.google.gwt.resources.client.CssResource.Shared; public interface EventRegattaRacesResources extends ClientBundle { public static final EventRegattaRacesResources INSTANCE = GWT.create(EventRegattaRacesResources.class); + public interface MinWidth25emCss extends LocalCss { + } + + @Source("com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesMedium.css") + MinWidth25emCss mediumCss(); + @Source("com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRaces.css") LocalCss css(); + @Shared public interface LocalCss extends CssResource { String eventregattaraces(); String eventregattaraces_phase(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/schedule/EventSchedule.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/schedule/EventSchedule.java index 20c88876c70..059faf76134 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/schedule/EventSchedule.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/schedule/EventSchedule.java @@ -2,21 +2,19 @@ package com.sap.sailing.gwt.home.client.place.event.schedule; import com.google.gwt.core.client.GWT; import com.google.gwt.uibinder.client.UiBinder; -import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.home.client.place.event.AbstractEventComposite; +import com.sap.sailing.gwt.home.client.place.event.EventPageNavigator; import com.sap.sailing.gwt.ui.shared.EventDTO; -public class EventSchedule extends Composite { +public class EventSchedule extends AbstractEventComposite { private static EventScheduleUiBinder uiBinder = GWT.create(EventScheduleUiBinder.class); interface EventScheduleUiBinder extends UiBinder { } - - @SuppressWarnings("unused") - private final EventDTO event; - public EventSchedule(EventDTO event) { - this.event = event; + public EventSchedule(EventDTO event, EventPageNavigator placeNavigator) { + super(event, placeNavigator); EventScheduleResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/AbstractEventsView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/AbstractEventsView.java index 3b843f1430c..2584751d1fd 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/AbstractEventsView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/AbstractEventsView.java @@ -12,25 +12,25 @@ import com.sap.sailing.gwt.home.client.DateUtil; import com.sap.sailing.gwt.ui.shared.EventBaseDTO; public abstract class AbstractEventsView extends Composite implements EventsView { - private final Map> recentEventsOrderedByYear; + private final Map> recentEventsByYearOrderedByEndDate; private final List upcomingEvents; public AbstractEventsView() { - recentEventsOrderedByYear = new HashMap>(); + recentEventsByYearOrderedByEndDate = new HashMap>(); upcomingEvents = new ArrayList(); } @Override public void setEvents(List events) { - for(EventBaseDTO event: events) { - if(event.startDate != null) { - if(DateUtil.isDayInPast(event.startDate)) { - // recent event + for (EventBaseDTO event : events) { + if (event.startDate != null && event.endDate != null) { + if (DateUtil.isDayInPast(event.startDate) || event.isRunning()) { + // recent event or live event int year = DateUtil.getYear(event.startDate); - List eventsOfYear = recentEventsOrderedByYear.get(year); - if(eventsOfYear == null) { + List eventsOfYear = recentEventsByYearOrderedByEndDate.get(year); + if (eventsOfYear == null) { eventsOfYear = new ArrayList(); - recentEventsOrderedByYear.put(year, eventsOfYear); + recentEventsByYearOrderedByEndDate.put(year, eventsOfYear); } eventsOfYear.add(event); } else { @@ -39,8 +39,7 @@ public abstract class AbstractEventsView extends Composite implements EventsView } } } - - for(List eventsPerYear: recentEventsOrderedByYear.values()) { + for (List eventsPerYear : recentEventsByYearOrderedByEndDate.values()) { Collections.sort(eventsPerYear, EVENTS_BY_DESCENDING_DATE_COMPARATOR); } Collections.sort(upcomingEvents, EVENTS_BY_ASCENDING_DATE_COMPARATOR); @@ -49,8 +48,13 @@ public abstract class AbstractEventsView extends Composite implements EventsView protected abstract void updateEventsUI(); - public Map> getRecentEventsOrderedByYear() { - return recentEventsOrderedByYear; + /** + * Returns a map whose keys denote years and whose values are event lists ordered by descending + * {@link EventBaseDTO#endDate end date}. Note that the iteration order for the map entries is undefined + * and in particular is not enumerating the years in any well-defined order. + */ + public Map> getRecentEventsByYearOrderedByEndDate() { + return recentEventsByYearOrderedByEndDate; } public List getUpcomingEvents() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/EventsActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/EventsActivity.java index fb1d33e130e..dd8bfe889af 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/EventsActivity.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/EventsActivity.java @@ -4,10 +4,11 @@ import java.util.List; import com.google.gwt.activity.shared.AbstractActivity; import com.google.gwt.event.shared.EventBus; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.AcceptsOneWidget; +import com.sap.sailing.gwt.home.client.shared.placeholder.Placeholder; import com.sap.sailing.gwt.ui.shared.EventBaseDTO; +import com.sap.sse.gwt.client.mvp.ErrorView; public class EventsActivity extends AbstractActivity { @@ -19,17 +20,21 @@ public class EventsActivity extends AbstractActivity { @Override public void start(final AcceptsOneWidget panel, EventBus eventBus) { - final EventsView eventsView = clientFactory.createEventsView(EventsActivity.this); - panel.setWidget(eventsView.asWidget()); + panel.setWidget(new Placeholder()); + clientFactory.getSailingService().getPublicEventsOfAllSailingServers(new AsyncCallback>() { @Override public void onSuccess(List events) { + final EventsView eventsView = clientFactory.createEventsView(EventsActivity.this); + panel.setWidget(eventsView.asWidget()); + eventsView.setEvents(events); } @Override public void onFailure(Throwable caught) { - Window.alert("Shit happens"); + final ErrorView view = clientFactory.createErrorView("Error while loading the sailing server instances with service getPublicEventsOfAllSailingServers()", caught); + panel.setWidget(view.asWidget()); } }); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/TabletAndDesktopEventsView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/TabletAndDesktopEventsView.java index 739aadfb821..5629bc3b25a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/TabletAndDesktopEventsView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/TabletAndDesktopEventsView.java @@ -54,7 +54,7 @@ public class TabletAndDesktopEventsView extends AbstractEventsView { @Override protected void updateEventsUI() { - recentEventsWidget.updateEvents(getRecentEventsOrderedByYear()); + recentEventsWidget.updateEvents(getRecentEventsByYearOrderedByEndDate()); upcomingEventsWidget.updateEvents(getUpcomingEvents()); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/TabletAndDesktopEventsView.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/TabletAndDesktopEventsView.ui.xml index e19a810e021..8a059ed0abc 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/TabletAndDesktopEventsView.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/TabletAndDesktopEventsView.ui.xml @@ -3,6 +3,7 @@ xmlns:s="urn:import:com.sap.sailing.gwt.home.client.shared" xmlns:p="urn:import:com.sap.sailing.gwt.home.client.place.events" xmlns:g="urn:import:com.google.gwt.user.client.ui"> + .eventsoverviewnavigation { @@ -20,16 +21,14 @@
- - + +
- - diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/recent/EventsOverviewRecent.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/recent/EventsOverviewRecent.css index d7258c42961..34c5bc4815b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/recent/EventsOverviewRecent.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/recent/EventsOverviewRecent.css @@ -3,6 +3,7 @@ font-size: 1rem; text-align: center; width: 100%; + height: 24em; } .eventteaser { font-size: 15px; @@ -52,7 +53,6 @@ .eventsoverviewrecent { font-size: 15px; font-size: 1rem; - padding-top: 2em; } .eventsoverviewrecent_year { border-bottom: 1px solid #ccc; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/recent/EventsOverviewRecent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/recent/EventsOverviewRecent.java index 9a515d8713b..166925a6ba9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/recent/EventsOverviewRecent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/recent/EventsOverviewRecent.java @@ -1,9 +1,13 @@ package com.sap.sailing.gwt.home.client.place.events.recent; -import java.util.Date; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; +import java.util.ListIterator; import java.util.Map; +import java.util.Map.Entry; import com.google.gwt.core.client.GWT; import com.google.gwt.uibinder.client.UiBinder; @@ -11,7 +15,6 @@ import com.google.gwt.uibinder.client.UiField; 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.gwt.home.client.DateUtil; import com.sap.sailing.gwt.home.client.app.PlaceNavigator; import com.sap.sailing.gwt.ui.shared.EventBaseDTO; @@ -36,37 +39,37 @@ public class EventsOverviewRecent extends Composite { initWidget(uiBinder.createAndBindUi(this)); } - public void updateEvents(Map> recentEventsOrderedByYear) { + public void updateEvents(Map> recentEventsByYearOrderedByEndDate) { // remove old widgets recentEventsPerYearPanel.clear(); recentEventsComposites.clear(); - // recent events of this year - Date now = new Date(); - int currentYear = DateUtil.getYear(now); + LinkedHashMap> recentEventsOrderedDescendingByYearOrderedDescendingByEndDate = + sortEventsDescendingByYear(recentEventsByYearOrderedByEndDate); boolean oneYearIsExpanded = false; - - if(recentEventsOrderedByYear.get(currentYear) != null) { - EventsOverviewRecentYear recentEventsOfOneYear = new EventsOverviewRecentYear(currentYear, recentEventsOrderedByYear.get(currentYear), navigator); + for (Entry> e : recentEventsOrderedDescendingByYearOrderedDescendingByEndDate.entrySet()) { + int currentYear = e.getKey(); + EventsOverviewRecentYear recentEventsOfOneYear = new EventsOverviewRecentYear(currentYear, e.getValue(), navigator); recentEventsPerYearPanel.add(recentEventsOfOneYear); recentEventsComposites.put(currentYear, recentEventsOfOneYear); - recentEventsOfOneYear.showContent(); - oneYearIsExpanded = true; - } - currentYear--; - while (currentYear > 2010) { - if(recentEventsOrderedByYear.get(currentYear) != null) { - EventsOverviewRecentYear recentEventsOfOneYear = new EventsOverviewRecentYear(currentYear, recentEventsOrderedByYear.get(currentYear), navigator); - recentEventsPerYearPanel.add(recentEventsOfOneYear); - recentEventsComposites.put(currentYear, recentEventsOfOneYear); - if(oneYearIsExpanded == true) { - recentEventsOfOneYear.hideContent(); - } else { - recentEventsOfOneYear.showContent(); - oneYearIsExpanded = true; - } - } - currentYear--; + if (oneYearIsExpanded == true) { + recentEventsOfOneYear.hideContent(); + } else { + recentEventsOfOneYear.showContent(); + oneYearIsExpanded = true; + } } } + + private LinkedHashMap> sortEventsDescendingByYear( + Map> recentEventsByYearOrderedByEndDate) { + LinkedHashMap> result = new LinkedHashMap<>(); + List yearKeysInDescendingOrder = new ArrayList<>(recentEventsByYearOrderedByEndDate.keySet()); + Collections.sort(yearKeysInDescendingOrder); + for (ListIterator i=yearKeysInDescendingOrder.listIterator(yearKeysInDescendingOrder.size()); i.hasPrevious(); ) { + Integer year = i.previous(); + result.put(year, recentEventsByYearOrderedByEndDate.get(year)); + } + return result; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/recent/EventsOverviewRecentYear.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/recent/EventsOverviewRecentYear.java index ce1b4840166..29d6ba17a7e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/recent/EventsOverviewRecentYear.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/recent/EventsOverviewRecentYear.java @@ -3,6 +3,7 @@ package com.sap.sailing.gwt.home.client.place.events.recent; import java.util.List; import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.SpanElement; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; @@ -27,7 +28,7 @@ public class EventsOverviewRecentYear extends Composite { // @UiField SpanElement countriesCount; // @UiField SpanElement sailorsCount; // @UiField SpanElement trackedRacesCount; - @UiField HTMLPanel recentEventsTeaserPanel; + @UiField DivElement recentEventsTeaserPanel; @UiField HTMLPanel contentDiv; @UiField HTMLPanel headerDiv; @@ -36,20 +37,16 @@ public class EventsOverviewRecentYear extends Composite { public EventsOverviewRecentYear(Integer year, List events, PlaceNavigator navigator) { EventsOverviewRecentResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); - this.year.setInnerText(String.valueOf(year)); this.eventsCount.setInnerText(String.valueOf(events.size())); // this.countriesCount.setInnerText("tbd."); // this.sailorsCount.setInnerText("tbd."); // this.trackedRacesCount.setInnerText("tbd."); - - for(EventBaseDTO eventDTO: events) { - RecentEvent recentEvent = new RecentEvent(navigator); - recentEvent.setEvent(eventDTO); - recentEventsTeaserPanel.add(recentEvent); + for (EventBaseDTO eventDTO : events) { + RecentEvent recentEvent = new RecentEvent(navigator, eventDTO); + recentEventsTeaserPanel.appendChild(recentEvent.getElement()); } isContentVisible = true; - headerDiv.addDomHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/recent/EventsOverviewRecentYear.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/recent/EventsOverviewRecentYear.ui.xml index d3513d36850..521c7c3d6e3 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/recent/EventsOverviewRecentYear.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/recent/EventsOverviewRecentYear.ui.xml @@ -38,8 +38,7 @@
-
- +
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/upcoming/EventsOverviewUpcoming.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/upcoming/EventsOverviewUpcoming.java index a6f54560d21..c19c2ec3a10 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/upcoming/EventsOverviewUpcoming.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/upcoming/EventsOverviewUpcoming.java @@ -28,7 +28,6 @@ public class EventsOverviewUpcoming extends Composite { public EventsOverviewUpcoming(PlaceNavigator navigator) { this.navigator = navigator; upcomingEventComposites = new ArrayList(); - EventsOverviewUpcomingResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); } @@ -36,13 +35,10 @@ public class EventsOverviewUpcoming extends Composite { public void updateEvents(List events) { eventsPlaceholder.clear(); upcomingEventComposites.clear(); - - for(EventBaseDTO event: events) { - UpcomingEvent upcomingEvent = new UpcomingEvent(navigator); - - upcomingEvent.setEvent(event); + for (EventBaseDTO event : events) { + UpcomingEvent upcomingEvent = new UpcomingEvent(event, navigator); upcomingEventComposites.add(upcomingEvent); - eventsPlaceholder.add(upcomingEvent); + eventsPlaceholder.getElement().appendChild(upcomingEvent.getElement()); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/upcoming/EventsOverviewUpcoming.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/upcoming/EventsOverviewUpcoming.ui.xml index 0b755dcc8bb..54964bc4411 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/upcoming/EventsOverviewUpcoming.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/upcoming/EventsOverviewUpcoming.ui.xml @@ -12,15 +12,15 @@
-

Coming soon

+

-
Date
-
Event
-
Location
-
Series
+
+
+
+
@@ -29,7 +29,7 @@
-
More Events To Be Announced Soon!
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/upcoming/UpcomingEvent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/upcoming/UpcomingEvent.java index 37d1d91cdba..705b1d27a95 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/upcoming/UpcomingEvent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/upcoming/UpcomingEvent.java @@ -1,48 +1,55 @@ package com.sap.sailing.gwt.home.client.place.events.upcoming; 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.SpanElement; 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.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.EventListener; +import com.google.gwt.user.client.ui.UIObject; import com.sap.sailing.gwt.home.client.app.PlaceNavigator; import com.sap.sailing.gwt.home.client.shared.EventDatesFormatterUtil; import com.sap.sailing.gwt.ui.shared.EventBaseDTO; -public class UpcomingEvent extends Composite { +public class UpcomingEvent extends UIObject { @UiField SpanElement eventName; @UiField SpanElement seriesName; @UiField SpanElement venueName; @UiField SpanElement eventStartDate; + @UiField AnchorElement eventOverviewLink; @SuppressWarnings("unused") private final PlaceNavigator navigator; - private EventBaseDTO event; - - - interface UpcomingEventUiBinder extends UiBinder { + interface UpcomingEventUiBinder extends UiBinder { } private static UpcomingEventUiBinder uiBinder = GWT.create(UpcomingEventUiBinder.class); - public UpcomingEvent(PlaceNavigator navigator) { + public UpcomingEvent(final EventBaseDTO event, final PlaceNavigator navigator) { this.navigator = navigator; EventsOverviewUpcomingResources.INSTANCE.css().ensureInjected(); - initWidget(uiBinder.createAndBindUi(this)); - } - - public void setEvent(EventBaseDTO event) { - this.event = event; - updateUI(); - } + setElement(uiBinder.createAndBindUi(this)); + + Event.sinkEvents(eventOverviewLink, Event.ONCLICK); + Event.setEventListener(eventOverviewLink, new EventListener() { + @Override + public void onBrowserEvent(Event browserEvent) { + switch (DOM.eventGetType(browserEvent)) { + case Event.ONCLICK: + navigator.goToEvent(event.id.toString(), event.getBaseURL(), event.isOnRemoteServer()); + break; + } + } + }); - private void updateUI() { eventName.setInnerText(event.getName()); venueName.setInnerText(event.venue.getName()); eventStartDate.setInnerText(EventDatesFormatterUtil.formatDateRangeWithoutYear(event.startDate, event.endDate)); - } + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/upcoming/UpcomingEvent.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/upcoming/UpcomingEvent.ui.xml index bc8dec1a699..b7867cdda5a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/upcoming/UpcomingEvent.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/upcoming/UpcomingEvent.ui.xml @@ -5,16 +5,14 @@ - - diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/leaderboard/LeaderboardActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/leaderboard/LeaderboardActivity.java new file mode 100644 index 00000000000..1b7050b0952 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/leaderboard/LeaderboardActivity.java @@ -0,0 +1,239 @@ +package com.sap.sailing.gwt.home.client.place.leaderboard; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import com.google.gwt.activity.shared.AbstractActivity; +import com.google.gwt.event.shared.EventBus; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.AcceptsOneWidget; +import com.sap.sailing.domain.common.DetailType; +import com.sap.sailing.domain.common.LeaderboardType; +import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.RegattaNameAndRaceName; +import com.sap.sailing.gwt.home.client.i18n.TextMessages; +import com.sap.sailing.gwt.home.client.shared.placeholder.Placeholder; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings; +import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings.RaceColumnSelectionStrategies; +import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettingsFactory; +import com.sap.sailing.gwt.ui.leaderboard.LeaderboardUrlSettings; +import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sse.common.Util; +import com.sap.sse.gwt.client.async.AsyncActionsExecutor; +import com.sap.sse.gwt.client.async.MarkedAsyncCallback; +import com.sap.sse.gwt.client.mvp.ErrorView; +import com.sap.sse.gwt.client.player.Timer; +import com.sap.sse.gwt.client.player.Timer.PlayModes; +import com.sap.sse.gwt.client.player.Timer.PlayStates; +import com.sap.sse.gwt.client.useragent.UserAgentDetails; +import com.sap.sse.gwt.shared.GwtHttpRequestUtils; + +public class LeaderboardActivity extends AbstractActivity implements ErrorReporter { + private final LeaderboardClientFactory clientFactory; + private final LeaderboardPlace leaderboardPlace; + private final Timer timerForClientServerOffset; + + private final UserAgentDetails userAgent = new UserAgentDetails(Window.Navigator.getUserAgent()); + + public LeaderboardActivity(LeaderboardPlace place, LeaderboardClientFactory clientFactory) { + this.clientFactory = clientFactory; + this.leaderboardPlace = place; + + timerForClientServerOffset = new Timer(PlayModes.Replay); + } + + @Override + public void start(final AcceptsOneWidget panel, EventBus eventBus) { + panel.setWidget(new Placeholder()); + + UUID eventUUID = UUID.fromString(leaderboardPlace.getEventUuidAsString()); + final String leaderboardName = leaderboardPlace.getLeaderboardIdAsNameString(); + final boolean showRaceDetails = leaderboardPlace.getShowRaceDetails(); + + clientFactory.getSailingService().getEventById(eventUUID, true, new AsyncCallback() { + @Override + public void onSuccess(final EventDTO event) { + if (leaderboardName != null) { + clientFactory.getSailingService().checkLeaderboardName(leaderboardName, new MarkedAsyncCallback>( + new AsyncCallback>() { + @Override + public void onSuccess(Util.Pair leaderboardNameAndType) { + if (leaderboardNameAndType != null && leaderboardName.equals(leaderboardNameAndType.getA())) { + createLeaderboardViewer(panel, event, leaderboardNameAndType.getA(), leaderboardNameAndType.getB(), showRaceDetails); + } else { + createErrorView(TextMessages.INSTANCE.errorMessageNoSuchLeaderboard(), null, panel); + } + } + + @Override + public void onFailure(Throwable caught) { + createErrorView("Error while checking the leaderboard name with service checkLeaderboardName()", caught, panel); + } + })); + } else { + createErrorView(TextMessages.INSTANCE.errorMessageNoSuchLeaderboard(), null, panel); + } + } + + @Override + public void onFailure(Throwable caught) { + createErrorView("Error while loading the event with service getEventById()", caught, panel); + } + }); + } + + private void createLeaderboardViewer(AcceptsOneWidget panel, EventDTO event, String leaderboardName, LeaderboardType leaderboardType, boolean showRaceDetails) { + String leaderboardDisplayName = Window.Location.getParameter("displayName"); + if (leaderboardDisplayName == null || leaderboardDisplayName.isEmpty()) { + leaderboardDisplayName = leaderboardName; + } + + long delayBetweenAutoAdvancesInMilliseconds = 3000l; + final RaceIdentifier preselectedRace = getPreselectedRace(Window.Location.getParameterMap()); + // make a single live request as the default but don't continue to play by default + Timer timer = new Timer(PlayModes.Live, PlayStates.Paused, delayBetweenAutoAdvancesInMilliseconds); + final LeaderboardSettings leaderboardSettings = createLeaderboardSettingsFromURLParameters(Window.Location.getParameterMap()); + if (leaderboardSettings.getDelayBetweenAutoAdvancesInMilliseconds() != null) { + timer.setPlayMode(PlayModes.Live); // the leaderboard, viewed via the entry point, goes "live" and "playing" if an auto-refresh + } // interval has been specified + boolean autoExpandLastRaceColumn = GwtHttpRequestUtils.getBooleanParameter(LeaderboardUrlSettings.PARAM_AUTO_EXPAND_LAST_RACE_COLUMN, false); + boolean showOverallLeaderboard = GwtHttpRequestUtils.getBooleanParameter(LeaderboardUrlSettings.PARAM_SHOW_OVERALL_LEADERBOARD, false); + boolean showSeriesLeaderboards = GwtHttpRequestUtils.getBooleanParameter(LeaderboardUrlSettings.PARAM_SHOW_SERIES_LEADERBOARDS, false); + + LeaderboardView leaderboardView = clientFactory.createLeaderboardView(event, leaderboardName, timerForClientServerOffset); + + if (leaderboardType.isMetaLeaderboard()) { + leaderboardView.createMetaLeaderboardViewer(clientFactory.getSailingService(), new AsyncActionsExecutor(), + timer, leaderboardSettings, null, preselectedRace, "leaderboardGroupName", leaderboardName, this, StringMessages.INSTANCE, + userAgent, showRaceDetails, autoExpandLastRaceColumn, showSeriesLeaderboards); + } else { + leaderboardView.createLeaderboardViewer(clientFactory.getSailingService(), new AsyncActionsExecutor(), + timer, leaderboardSettings, preselectedRace, "leaderboardGroupName", leaderboardName, this, StringMessages.INSTANCE, + userAgent, showRaceDetails, autoExpandLastRaceColumn, showOverallLeaderboard); + } + + panel.setWidget(leaderboardView.asWidget()); + } + + /** + * Constructs {@link LeaderboardSettings} from the URL parameters found + */ + private LeaderboardSettings createLeaderboardSettingsFromURLParameters(Map> parameterMap) { + LeaderboardSettings result; + Long refreshIntervalMillis = parameterMap.containsKey(LeaderboardUrlSettings.PARAM_REFRESH_INTERVAL_MILLIS) ? Long + .valueOf(parameterMap.get(LeaderboardUrlSettings.PARAM_REFRESH_INTERVAL_MILLIS).get(0)) : null; + RaceColumnSelectionStrategies raceColumnSelectionStrategy; + final Integer numberOfLastRacesToShow; + if (parameterMap.containsKey(LeaderboardUrlSettings.PARAM_NAME_LAST_N)) { + raceColumnSelectionStrategy = RaceColumnSelectionStrategies.LAST_N; + numberOfLastRacesToShow = Integer.valueOf(parameterMap.get(LeaderboardUrlSettings.PARAM_NAME_LAST_N).get(0)); + } else if (isSmallWidth()) { + raceColumnSelectionStrategy = RaceColumnSelectionStrategies.LAST_N; + int width = Window.getClientWidth(); + numberOfLastRacesToShow = (width-275)/40; + } else { + raceColumnSelectionStrategy = RaceColumnSelectionStrategies.EXPLICIT; + numberOfLastRacesToShow = null; + } + if (parameterMap.containsKey(LeaderboardUrlSettings.PARAM_RACE_NAME) || parameterMap.containsKey(LeaderboardUrlSettings.PARAM_RACE_DETAIL) || + parameterMap.containsKey(LeaderboardUrlSettings.PARAM_LEG_DETAIL) || parameterMap.containsKey(LeaderboardUrlSettings.PARAM_MANEUVER_DETAIL) || + parameterMap.containsKey(LeaderboardUrlSettings.PARAM_OVERALL_DETAIL) || parameterMap.containsKey(LeaderboardUrlSettings.PARAM_SHOW_ADDED_SCORES) || + parameterMap.containsKey(LeaderboardUrlSettings.PARAM_SHOW_OVERALL_COLUMN_WITH_NUMBER_OF_RACES_COMPLETED)) { + List maneuverDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_MANEUVER_DETAIL)); + List raceDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_RACE_DETAIL)); + List overallDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_OVERALL_DETAIL)); + List legDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_LEG_DETAIL)); + List namesOfRacesToShow = getStringListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_RACE_NAME)); + boolean showAddedScores = parameterMap.containsKey(LeaderboardUrlSettings.PARAM_SHOW_ADDED_SCORES) ? + Boolean.valueOf(parameterMap.get(LeaderboardUrlSettings.PARAM_SHOW_ADDED_SCORES).get(0)) : false; + boolean showOverallColumnWithNumberOfRacesSailedPerCompetitor = parameterMap.containsKey(LeaderboardUrlSettings.PARAM_SHOW_OVERALL_COLUMN_WITH_NUMBER_OF_RACES_COMPLETED) ? + Boolean.valueOf(parameterMap.get(LeaderboardUrlSettings.PARAM_SHOW_OVERALL_COLUMN_WITH_NUMBER_OF_RACES_COMPLETED).get(0)) : false; + boolean autoExpandPreSelectedRace = parameterMap.containsKey(LeaderboardUrlSettings.PARAM_AUTO_EXPAND_PRESELECTED_RACE) ? + Boolean.valueOf(parameterMap.get(LeaderboardUrlSettings.PARAM_AUTO_EXPAND_PRESELECTED_RACE).get(0)) : + (namesOfRacesToShow != null && namesOfRacesToShow.size() == 1); + result = new LeaderboardSettings(maneuverDetails, legDetails, raceDetails, overallDetails, + /* namesOfRaceColumnsToShow */ null, + namesOfRacesToShow, numberOfLastRacesToShow, + autoExpandPreSelectedRace, refreshIntervalMillis, /* sort by column */ (namesOfRacesToShow != null && !namesOfRacesToShow.isEmpty()) ? + namesOfRacesToShow.get(0) : null, + /* ascending */ true, /* updateUponPlayStateChange */ raceDetails.isEmpty() && legDetails.isEmpty(), + raceColumnSelectionStrategy, showAddedScores, showOverallColumnWithNumberOfRacesSailedPerCompetitor); + + } else { + final List overallDetails = Collections.emptyList(); + result = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, + /* overallDetails */ overallDetails, null, + /* autoExpandFirstRace */false, refreshIntervalMillis, numberOfLastRacesToShow, + raceColumnSelectionStrategy); + } + return result; + } + + private RaceIdentifier getPreselectedRace(Map> parameterMap) { + RaceIdentifier result; + if (parameterMap.containsKey(LeaderboardUrlSettings.PARAM_RACE_NAME) && parameterMap.get(LeaderboardUrlSettings.PARAM_RACE_NAME).size() == 1 && + parameterMap.containsKey(LeaderboardUrlSettings.PARAM_REGATTA_NAME) && parameterMap.get(LeaderboardUrlSettings.PARAM_REGATTA_NAME).size() == 1) { + result = new RegattaNameAndRaceName(parameterMap.get(LeaderboardUrlSettings.PARAM_REGATTA_NAME).get(0), parameterMap.get(LeaderboardUrlSettings.PARAM_RACE_NAME).get(0)); + } else { + result = null; + } + return result; + } + + private List getDetailTypeListFromParamValue(List list) { + List result = new ArrayList(); + if (list != null) { + for (String entry : list) { + try { + result.add(DetailType.valueOf(entry)); + } catch (IllegalArgumentException e) { + } + } + } + return result; + } + + private List getStringListFromParamValue(List list) { + List result = new ArrayList(); + if (list != null) { + result.addAll(list); + } + return result; + } + + private void createErrorView(String errorMessage, Throwable errorReason, AcceptsOneWidget panel) { + ErrorView view = clientFactory.createErrorView(errorMessage, errorReason); + panel.setWidget(view.asWidget()); + } + + public boolean isSmallWidth() { + int width = Window.getClientWidth(); + return width <= 720; + } + + @Override + public void reportError(String message) { + Window.alert(message); + } + + @Override + public void reportError(String message, boolean silentMode) { + if (silentMode) { + Window.setStatus(message); + } else { + reportError(message); + } + } + + @Override + public void reportPersistentInformation(String message) { + Window.setStatus(message); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/leaderboard/LeaderboardActivityProxy.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/leaderboard/LeaderboardActivityProxy.java new file mode 100644 index 00000000000..9d186021e7c --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/leaderboard/LeaderboardActivityProxy.java @@ -0,0 +1,25 @@ +package com.sap.sailing.gwt.home.client.place.leaderboard; + +import com.google.gwt.core.client.GWT; +import com.sap.sse.gwt.client.mvp.AbstractActivityProxy; + +public class LeaderboardActivityProxy extends AbstractActivityProxy { + + private final LeaderboardClientFactory clientFactory; + private final LeaderboardPlace place; + + public LeaderboardActivityProxy(LeaderboardPlace place, LeaderboardClientFactory clientFactory) { + this.place = place; + this.clientFactory = clientFactory; + } + + @Override + protected void startAsync() { + GWT.runAsync(new AbstractRunAsyncCallback() { + @Override + public void onSuccess() { + super.onSuccess(new LeaderboardActivity(place, clientFactory)); + } + }); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/leaderboard/LeaderboardClientFactory.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/leaderboard/LeaderboardClientFactory.java new file mode 100644 index 00000000000..a45201dccb5 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/leaderboard/LeaderboardClientFactory.java @@ -0,0 +1,9 @@ +package com.sap.sailing.gwt.home.client.place.leaderboard; + +import com.sap.sailing.gwt.ui.client.SailingClientFactory; +import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sse.gwt.client.player.Timer; + +public interface LeaderboardClientFactory extends SailingClientFactory { + LeaderboardView createLeaderboardView(EventDTO event, String leaderboardName, Timer timerForClientServerOffset); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/leaderboard/LeaderboardPlace.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/leaderboard/LeaderboardPlace.java new file mode 100644 index 00000000000..9835d705db4 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/leaderboard/LeaderboardPlace.java @@ -0,0 +1,61 @@ +package com.sap.sailing.gwt.home.client.place.leaderboard; + +import com.google.gwt.place.shared.PlaceTokenizer; +import com.sap.sailing.gwt.home.client.AbstractBasePlace; + +public class LeaderboardPlace extends AbstractBasePlace { + private final String eventUuidAsString; + private final String leaderboardIdAsNameString; + private final Boolean showRaceDetails; + private final Boolean showSettings; + + private final static String PARAM_EVENTID = "eventId"; + private final static String PARAM_LEADERBOARD_NAME = "leaderboardName"; + private final static String PARAM_SHOW_RACE_DETAILS = "showRaceDetails"; + private final static String PARAM_SHOW_SETTINGS = "showSettings"; + + public LeaderboardPlace(String url) { + super(url); + eventUuidAsString = getParameter(PARAM_EVENTID); + leaderboardIdAsNameString = getParameter(PARAM_LEADERBOARD_NAME); + showRaceDetails = Boolean.valueOf(getParameter(PARAM_SHOW_RACE_DETAILS)); + showSettings = Boolean.valueOf(getParameter(PARAM_SHOW_SETTINGS)); + } + + public LeaderboardPlace(String eventUuidAsString, String leaderboardIdAsNameString, Boolean showRaceDetails, Boolean showSettings) { + super(PARAM_EVENTID, eventUuidAsString, PARAM_LEADERBOARD_NAME, leaderboardIdAsNameString, + PARAM_SHOW_RACE_DETAILS, String.valueOf(showRaceDetails), PARAM_SHOW_SETTINGS, String.valueOf(showSettings)); + this.eventUuidAsString = eventUuidAsString; + this.leaderboardIdAsNameString = leaderboardIdAsNameString; + this.showRaceDetails = showRaceDetails; + this.showSettings = showSettings; + } + + public String getEventUuidAsString() { + return eventUuidAsString; + } + + public String getLeaderboardIdAsNameString() { + return leaderboardIdAsNameString; + } + + public Boolean getShowRaceDetails() { + return showRaceDetails; + } + + public Boolean getShowSettings() { + return showSettings; + } + + public static class Tokenizer implements PlaceTokenizer { + @Override + public String getToken(LeaderboardPlace place) { + return place.getParametersAsToken(); + } + + @Override + public LeaderboardPlace getPlace(String url) { + return new LeaderboardPlace(url); + } + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/leaderboard/LeaderboardView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/leaderboard/LeaderboardView.java new file mode 100644 index 00000000000..a278ac15cb9 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/leaderboard/LeaderboardView.java @@ -0,0 +1,27 @@ +package com.sap.sailing.gwt.home.client.place.leaderboard; + +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings; +import com.sap.sse.gwt.client.async.AsyncActionsExecutor; +import com.sap.sse.gwt.client.player.Timer; +import com.sap.sse.gwt.client.useragent.UserAgentDetails; + +public interface LeaderboardView { + Widget asWidget(); + + void createLeaderboardViewer(final SailingServiceAsync sailingService, final AsyncActionsExecutor asyncActionsExecutor, + final Timer timer, final LeaderboardSettings leaderboardSettings, final RaceIdentifier preselectedRace, + final String leaderboardGroupName, String leaderboardName, final ErrorReporter errorReporter, + final StringMessages stringMessages, final UserAgentDetails userAgent, boolean showRaceDetails, + boolean autoExpandLastRaceColumn, boolean showOverallLeaderboard); + + void createMetaLeaderboardViewer(SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, + Timer timer, LeaderboardSettings leaderboardSettings, String preselectedLeaderboardName, RaceIdentifier preselectedRace, + String leaderboardGroupName, String metaLeaderboardName, ErrorReporter errorReporter, + StringMessages stringMessages, UserAgentDetails userAgent, boolean showRaceDetails, + boolean autoExpandLastRaceColumn, boolean showSeriesLeaderboards); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/leaderboard/TabletAndDesktopLeaderboardView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/leaderboard/TabletAndDesktopLeaderboardView.java new file mode 100644 index 00000000000..004239b1abc --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/leaderboard/TabletAndDesktopLeaderboardView.java @@ -0,0 +1,105 @@ +package com.sap.sailing.gwt.home.client.place.leaderboard; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.HeadingElement; +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.ui.Anchor; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.HTMLPanel; +import com.google.gwt.user.client.ui.ScrollPanel; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.gwt.home.client.app.PlaceNavigator; +import com.sap.sailing.gwt.home.client.i18n.TextMessages; +import com.sap.sailing.gwt.home.client.place.event.header.CompactEventHeader; +import com.sap.sailing.gwt.home.client.place.event.regattaleaderboard.EventRegattaLeaderboardResources; +import com.sap.sailing.gwt.home.client.shared.leaderboard.AbstractLeaderboardViewer; +import com.sap.sailing.gwt.home.client.shared.leaderboard.LeaderboardViewer; +import com.sap.sailing.gwt.home.client.shared.leaderboard.MetaLeaderboardViewer; +import com.sap.sailing.gwt.ui.client.DebugIdHelper; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialog; +import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel; +import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings; +import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sse.gwt.client.async.AsyncActionsExecutor; +import com.sap.sse.gwt.client.player.Timer; +import com.sap.sse.gwt.client.useragent.UserAgentDetails; + +public class TabletAndDesktopLeaderboardView extends Composite implements LeaderboardView { + private static LeaderboardPageViewUiBinder uiBinder = GWT.create(LeaderboardPageViewUiBinder.class); + + interface LeaderboardPageViewUiBinder extends UiBinder { + } + + @UiField HTMLPanel leaderboardPanel; + @UiField(provided=true) CompactEventHeader eventHeader; + + // temp fields --> will be moved to a leaderboard partial later on + @UiField HeadingElement title; + @UiField Anchor settingsAnchor; + @UiField Anchor reloadAnchor; + + private AbstractLeaderboardViewer leaderboardViewer; + + public TabletAndDesktopLeaderboardView(EventDTO event, String leaderboardName, Timer timerForClientServerOffset, PlaceNavigator placeNavigator) { + eventHeader = new CompactEventHeader(event, leaderboardName, placeNavigator); + + EventRegattaLeaderboardResources.INSTANCE.css().ensureInjected(); + + initWidget(uiBinder.createAndBindUi(this)); + + reloadAnchor.setVisible(false); + title.setInnerText(TextMessages.INSTANCE.leaderboard() + ": " + leaderboardName); + } + + public void createLeaderboardViewer(final SailingServiceAsync sailingService, final AsyncActionsExecutor asyncActionsExecutor, + final Timer timer, final LeaderboardSettings leaderboardSettings, final RaceIdentifier preselectedRace, + final String leaderboardGroupName, String leaderboardName, final ErrorReporter errorReporter, + final StringMessages stringMessages, final UserAgentDetails userAgent, boolean showRaceDetails, + boolean autoExpandLastRaceColumn, boolean showOverallLeaderboard) { + leaderboardViewer = new LeaderboardViewer(sailingService, new AsyncActionsExecutor(), + timer, leaderboardSettings, preselectedRace, leaderboardGroupName, leaderboardName, errorReporter, stringMessages, + userAgent, showRaceDetails, autoExpandLastRaceColumn, showOverallLeaderboard); + + ScrollPanel contentScrollPanel = new ScrollPanel(); + contentScrollPanel.setWidget(leaderboardViewer); + leaderboardPanel.add(contentScrollPanel); + } + + public void createMetaLeaderboardViewer(SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, + Timer timer, LeaderboardSettings leaderboardSettings, String preselectedLeaderboardName, RaceIdentifier preselectedRace, + String leaderboardGroupName, String metaLeaderboardName, ErrorReporter errorReporter, + StringMessages stringMessages, UserAgentDetails userAgent, boolean showRaceDetails, + boolean autoExpandLastRaceColumn, boolean showSeriesLeaderboards) { + leaderboardViewer = new MetaLeaderboardViewer(sailingService, new AsyncActionsExecutor(), + timer, leaderboardSettings, null, preselectedRace, leaderboardGroupName, metaLeaderboardName, errorReporter, stringMessages, + userAgent, showRaceDetails, autoExpandLastRaceColumn, showSeriesLeaderboards); + + ScrollPanel contentScrollPanel = new ScrollPanel(); + contentScrollPanel.setWidget(leaderboardViewer); + leaderboardPanel.add(contentScrollPanel); + } + + @UiHandler("reloadAnchor") + void reloadClicked(ClickEvent event) { + } + + @UiHandler("settingsAnchor") + void settingsClicked(ClickEvent event) { + LeaderboardPanel leaderboardComponent = leaderboardViewer.getLeaderboardPanel(); + + final String componentName = leaderboardComponent.getLocalizedShortName(); + final String debugIdPrefix = DebugIdHelper.createDebugId(componentName); + + SettingsDialog dialog = new SettingsDialog(leaderboardComponent, + StringMessages.INSTANCE); + dialog.ensureDebugId(debugIdPrefix + "SettingsDialog"); + dialog.show(); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/leaderboard/TabletAndDesktopLeaderboardView.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/leaderboard/TabletAndDesktopLeaderboardView.ui.xml new file mode 100644 index 00000000000..af92d04d783 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/leaderboard/TabletAndDesktopLeaderboardView.ui.xml @@ -0,0 +1,36 @@ + + + + + + + .leaderboardPanel { + background-color:#f8f8f8; + padding: 0 10px 10px 10px; + } + + + + +
+
+

+

+ +
+
+ + +
+
+ + +
+
+
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/searchresult/SearchResultActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/searchresult/SearchResultActivity.java index 87c0a0d1485..fbacfa52c14 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/searchresult/SearchResultActivity.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/searchresult/SearchResultActivity.java @@ -2,11 +2,12 @@ package com.sap.sailing.gwt.home.client.place.searchresult; import com.google.gwt.activity.shared.AbstractActivity; import com.google.gwt.event.shared.EventBus; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.AcceptsOneWidget; +import com.sap.sailing.gwt.home.client.shared.placeholder.Placeholder; import com.sap.sailing.gwt.ui.shared.LeaderboardSearchResultDTO; import com.sap.sse.common.search.KeywordQuery; +import com.sap.sse.gwt.client.mvp.ErrorView; public class SearchResultActivity extends AbstractActivity { private final SearchResultClientFactory clientFactory; @@ -14,6 +15,7 @@ public class SearchResultActivity extends AbstractActivity { private final SearchResultPlace searchResultPlace; private SearchResultView view; + private AcceptsOneWidget panel; public SearchResultActivity(SearchResultPlace place, SearchResultClientFactory clientFactory) { this.clientFactory = clientFactory; @@ -25,13 +27,18 @@ public class SearchResultActivity extends AbstractActivity { private void bindEvents() { clientFactory.getEventBus().addHandler(SearchEvent.TYPE, new SearchEventHandler() { public void onDoSearch(SearchEvent event) { - doSearch(event.getSearchText()); + if(view != null) { + doSearch(event.getSearchText()); + } } }); } @Override public void start(final AcceptsOneWidget panel, final EventBus eventBus) { + this.panel = panel; + panel.setWidget(new Placeholder()); + view = clientFactory.createSearchResultView(); panel.setWidget(view.asWidget()); doSearch(searchResultPlace.getSearchText()); @@ -47,7 +54,8 @@ public class SearchResultActivity extends AbstractActivity { @Override public void onFailure(Throwable caught) { - Window.alert("Shit happens"); + final ErrorView view = clientFactory.createErrorView("Error while seaching with service search()", caught); + panel.setWidget(view.asWidget()); } }); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/searchresult/TabletAndDesktopSearchResultView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/searchresult/TabletAndDesktopSearchResultView.java index 42ac68387b0..4f09e948d86 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/searchresult/TabletAndDesktopSearchResultView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/searchresult/TabletAndDesktopSearchResultView.java @@ -5,7 +5,6 @@ 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.google.web.bindery.event.shared.EventBus; import com.sap.sailing.gwt.home.client.app.PlaceNavigator; import com.sap.sailing.gwt.home.client.shared.searchresult.SearchResult; import com.sap.sailing.gwt.ui.shared.LeaderboardSearchResultDTO; @@ -18,8 +17,8 @@ public class TabletAndDesktopSearchResultView extends Composite implements Searc @UiField(provided=true) SearchResult searchResult; - public TabletAndDesktopSearchResultView(PlaceNavigator navigator, EventBus eventBus) { - searchResult = new SearchResult(navigator, eventBus); + public TabletAndDesktopSearchResultView(PlaceNavigator navigator) { + searchResult = new SearchResult(navigator); initWidget(uiBinder.createAndBindUi(this)); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/SmartphoneStartView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/SmartphoneStartView.java index 5cd975756a1..a3c35ae8f44 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/SmartphoneStartView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/SmartphoneStartView.java @@ -11,7 +11,6 @@ import com.sap.sailing.gwt.home.client.app.PlaceNavigator; import com.sap.sailing.gwt.home.client.shared.mainevents.MainEvents; import com.sap.sailing.gwt.home.client.shared.mainmedia.MainMedia; import com.sap.sailing.gwt.home.client.shared.mainsponsors.MainSponsors; -import com.sap.sailing.gwt.home.client.shared.socialfooter.SocialFooter; import com.sap.sailing.gwt.home.client.shared.stage.Stage; import com.sap.sailing.gwt.home.client.shared.stage.StageEventType; import com.sap.sailing.gwt.ui.shared.EventBaseDTO; @@ -27,7 +26,6 @@ public class SmartphoneStartView extends Composite implements StartView { @UiField(provided=true) MainSponsors mainSponsors; @UiField(provided=true) MainEvents mainEvents; @UiField(provided=true) MainMedia mainMedia; - @UiField SocialFooter socialFooter; public SmartphoneStartView(PlaceNavigator navigator) { stage = new Stage(navigator); @@ -47,4 +45,9 @@ public class SmartphoneStartView extends Composite implements StartView { mainEvents.setRecentEvents(recentEvents); mainMedia.setRecentEvents(recentEvents); } + + @Override + public void adjustSizes() { + stage.adjustSize(); + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/SmartphoneStartView.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/SmartphoneStartView.ui.xml index 750f7d032ac..b7e1ef6bbb3 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/SmartphoneStartView.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/SmartphoneStartView.ui.xml @@ -9,7 +9,7 @@ - + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/StartActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/StartActivity.java index 1ae3e14afd2..15e097ff42a 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/StartActivity.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/StartActivity.java @@ -1,13 +1,19 @@ package com.sap.sailing.gwt.home.client.place.start; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.Date; import java.util.List; import com.google.gwt.activity.shared.AbstractActivity; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; 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.client.DateUtil; +import com.sap.sailing.gwt.home.client.shared.placeholder.Placeholder; import com.sap.sailing.gwt.home.client.shared.stage.StageEventType; import com.sap.sailing.gwt.ui.shared.EventBaseDTO; import com.sap.sse.common.Util.Pair; @@ -20,13 +26,13 @@ public class StartActivity extends AbstractActivity { } @Override - public void start(AcceptsOneWidget panel, EventBus eventBus) { - final StartView view = clientFactory.createStartView(); - panel.setWidget(view.asWidget()); - + public void start(final AcceptsOneWidget panel, EventBus eventBus) { + panel.setWidget(new Placeholder()); clientFactory.getSailingService().getPublicEventsOfAllSailingServers(new AsyncCallback>() { @Override public void onSuccess(List result) { + final StartView view = clientFactory.createStartView(); + panel.setWidget(view.asWidget()); fillStartPageEvents(view, result); } @@ -36,50 +42,78 @@ public class StartActivity extends AbstractActivity { }); } - @SuppressWarnings("deprecation") - protected void fillStartPageEvents(StartView view, List events) { + protected void fillStartPageEvents(final StartView view, List events) { List> featuredEvents = new ArrayList>(); - List recentEventsOfSameYear = new ArrayList(); List upcomingSoonEvents = new ArrayList(); List popularEvents = new ArrayList(); Date now = new Date(); - int currentYear = now.getYear(); - final int MAX_STAGE_EVENTS = 2; + int currentYear = DateUtil.getYear(now); + final int MAX_STAGE_EVENTS = 5; final long FOUR_WEEK_IN_MS = 4L * (1000 * 60 * 60 * 24 * 7); - - for(EventBaseDTO event: events) { - if(event.startDate != null && event.endDate != null) { + for (EventBaseDTO event : events) { + if (event.startDate != null && event.endDate != null) { if (now.after(event.startDate) && now.before(event.endDate)) { featuredEvents.add(new Pair(StageEventType.RUNNING, event)); } else if (event.startDate.after(now) && event.startDate.getTime() - now.getTime() < FOUR_WEEK_IN_MS) { upcomingSoonEvents.add(event); - } else if (event.endDate.before(now) && event.endDate.getYear() == currentYear) { + } else if (event.endDate.before(now) && DateUtil.getYear(event.endDate) == currentYear) { recentEventsOfSameYear.add(event); } } } - - if(featuredEvents.size() < MAX_STAGE_EVENTS) { + if (featuredEvents.size() < MAX_STAGE_EVENTS) { fillingUpEventsList(MAX_STAGE_EVENTS, featuredEvents, StageEventType.UPCOMING_SOON, upcomingSoonEvents); } - if(featuredEvents.size() < MAX_STAGE_EVENTS) { + if (featuredEvents.size() < MAX_STAGE_EVENTS) { fillingUpEventsList(MAX_STAGE_EVENTS, featuredEvents, StageEventType.POPULAR, popularEvents); } // fallback for the case we did not find any events - if(featuredEvents.size() < MAX_STAGE_EVENTS) { + if (featuredEvents.size() < MAX_STAGE_EVENTS) { fillingUpEventsList(MAX_STAGE_EVENTS, featuredEvents, StageEventType.POPULAR, recentEventsOfSameYear); } - + Collections.sort(featuredEvents, new FeaturedEventsComparator()); view.setFeaturedEvents(featuredEvents); view.setRecentEvents(recentEventsOfSameYear); + // See bug 2232: the stage image sizes are scaled incorrectly. https://github.com/ubilabs/sap-sailing-analytics/issues/421 and + // http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=2232 have the details. A quick fix may be to send a resize event + // after everything has been rendered. + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + @Override + public void execute() { + view.adjustSizes(); + } + }); } - private void fillingUpEventsList(int maxAmountOfElements, List> resultList, StageEventType type, List listToTakeElementsFrom) { + private void fillingUpEventsList(int maxAmountOfElements, List> resultList, + StageEventType type, List listToTakeElementsFrom) { int maxElementsToFill = maxAmountOfElements - resultList.size(); - int elementsToTransfer = listToTakeElementsFrom.size() > maxElementsToFill ? maxElementsToFill : listToTakeElementsFrom.size(); - for(int i = 0; i < elementsToTransfer; i++) { + int elementsToTransfer = listToTakeElementsFrom.size() > maxElementsToFill ? maxElementsToFill + : listToTakeElementsFrom.size(); + for (int i = 0; i < elementsToTransfer; i++) { resultList.add(new Pair(type, listToTakeElementsFrom.get(i))); } } + + /** + * Comparator for sorting a pair of event and stageType first by order number of stage type and then by event start + * date. + * + * @author Frank + */ + private class FeaturedEventsComparator implements Comparator> { + @Override + public int compare(Pair eventAndStageType1, + Pair eventAndStageType2) { + int result; + if (eventAndStageType1.getA().ordinal() == eventAndStageType2.getA().ordinal()) { + result = eventAndStageType1.getB().startDate.compareTo(eventAndStageType2.getB().startDate); + } else { + result = eventAndStageType1.getA().ordinal() - eventAndStageType2.getA().ordinal(); + } + return result; + } + } + } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/StartView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/StartView.java index 89ef4422020..9fa87e4af72 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/StartView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/StartView.java @@ -13,4 +13,6 @@ public interface StartView { void setFeaturedEvents(List> featuredEvents); void setRecentEvents(List recentEvents); + + void adjustSizes(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/TabletAndDesktopStartView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/TabletAndDesktopStartView.java index 545bc31f00a..dfe18976777 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/TabletAndDesktopStartView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/TabletAndDesktopStartView.java @@ -10,7 +10,6 @@ import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.home.client.app.PlaceNavigator; import com.sap.sailing.gwt.home.client.shared.mainevents.MainEvents; import com.sap.sailing.gwt.home.client.shared.mainmedia.MainMedia; -import com.sap.sailing.gwt.home.client.shared.socialfooter.SocialFooter; import com.sap.sailing.gwt.home.client.shared.stage.Stage; import com.sap.sailing.gwt.home.client.shared.stage.StageEventType; import com.sap.sailing.gwt.ui.shared.EventBaseDTO; @@ -26,7 +25,6 @@ public class TabletAndDesktopStartView extends Composite implements StartView { // @UiField(provided=true) MainSponsors mainSponsors; @UiField(provided=true) MainEvents mainEvents; @UiField(provided=true) MainMedia mainMedia; - @UiField SocialFooter socialFooter; public TabletAndDesktopStartView(PlaceNavigator navigator) { stage = new Stage(navigator); @@ -49,4 +47,9 @@ public class TabletAndDesktopStartView extends Composite implements StartView { mainMedia.setRecentEvents(recentEvents); } + @Override + public void adjustSizes() { + stage.adjustSize(); + } + } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/TabletAndDesktopStartView.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/TabletAndDesktopStartView.ui.xml index fe17dfbe93c..51dee5455b0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/TabletAndDesktopStartView.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/TabletAndDesktopStartView.ui.xml @@ -6,10 +6,12 @@ - + - + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/EventDatesFormatterUtil.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/EventDatesFormatterUtil.java index add95120f06..d3c52128a10 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/EventDatesFormatterUtil.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/EventDatesFormatterUtil.java @@ -15,11 +15,19 @@ public class EventDatesFormatterUtil { private final static DateTimeFormat monthFormat = DateTimeFormat.getFormat("MMMM"); private final static DateTimeFormat yearFormat = DateTimeFormat.getFormat("yyyy"); + private final static int ONE_DAY = 24 * 60 * 60 * 1000; + @SuppressWarnings("deprecation") public static String formatDateRangeWithYear(Date from, Date to) { String result = ""; if(from.getMonth() == to.getMonth()) { - result = monthFormat.format(from) + " " + dayFormat.format(from) + " - " + dayFormat.format(to) + ", " + yearFormat.format(from); + // same month + if(from.getDay() == to.getDay() && to.getTime() - to.getTime() <= ONE_DAY) { + // same day + result = monthFormat.format(from) + " " + dayFormat.format(from) + ", " + yearFormat.format(from); + } else { + result = monthFormat.format(from) + " " + dayFormat.format(from) + " - " + dayFormat.format(to) + ", " + yearFormat.format(from); + } } else { result = dayAndMonthFormat.format(from) + " - " + dayAndMonthFormat.format(to) + ", " + yearFormat.format(from); } @@ -31,7 +39,13 @@ public class EventDatesFormatterUtil { public static String formatDateRangeWithoutYear(Date from, Date to) { String result = ""; if(from.getMonth() == to.getMonth()) { - result = monthFormat.format(from) + " " + dayFormat.format(from) + " - " + dayFormat.format(to); + // same month + if(from.getDay() == to.getDay() && to.getTime() - to.getTime() <= ONE_DAY) { + // same day + result = monthFormat.format(from) + " " + dayFormat.format(from); + } else { + result = monthFormat.format(from) + " " + dayFormat.format(from) + " - " + dayFormat.format(to); + } } else { result = dayAndMonthFormat.format(from) + " - " + dayAndMonthFormat.format(to); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/LongNamesUtil.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/LongNamesUtil.java new file mode 100644 index 00000000000..03a7eb6a7cb --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/LongNamesUtil.java @@ -0,0 +1,59 @@ +package com.sap.sailing.gwt.home.client.shared; + +import com.google.gwt.safehtml.shared.SafeHtml; +import com.google.gwt.safehtml.shared.SafeHtmlBuilder; + +/** + * A utility class for handling long names + * @author Frank + * + */ +public class LongNamesUtil { + private static String NAME_SPLIT_RULE1 = " - "; + + public static String shortenLongName(String name, int maxLength) { + String result = name; + if (name.length() > maxLength) { + name = name.substring(0, maxLength - 3) + "..."; + } + return result; + } + + public static SafeHtml breakLongName(String name) { + SafeHtmlBuilder builder = new SafeHtmlBuilder(); + if(name != null) { + if (name.contains(NAME_SPLIT_RULE1)) { + String[] splittedName = name.split(NAME_SPLIT_RULE1); + for (int i = 0; i < splittedName.length; i++) { + builder.appendEscaped(splittedName[i]); + if (i != splittedName.length - 1) { + builder.appendHtmlConstant("
"); + } + } + } else { + builder.appendEscaped(name); + } + } else { + builder.appendEscaped(""); + } + return builder.toSafeHtml(); + } + + /** + * Temp -> remove as soon as we have a display name for a leaderboardGroup + */ + public static String shortenLeaderboardGroupName(String eventName, String leaderboardGroupName) { + String result = leaderboardGroupName; + if (result.startsWith(eventName) && !eventName.equals(leaderboardGroupName)) { + result = result.substring(eventName.length(), result.length()); + result = result.trim(); + + if (result.startsWith("-") || result.startsWith("/")) { + result = result.substring(1, result.length()); + result = result.trim(); + } + } + return result; + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/error/ErrorMessage.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/error/ErrorMessage.css new file mode 100644 index 00000000000..797f79e0d8b --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/error/ErrorMessage.css @@ -0,0 +1,15 @@ +.error_content { + margin-top: 3em; + margin-bottom: 3em; +} +.error_message { + font-weight: bold; + font-size: 20px; +} +.error_message_detail { + font-size: 15px; +} +.reload_page_message { + font-size: 20px; + margin-right: 20px; +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/error/ErrorMessage.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/error/ErrorMessage.java new file mode 100644 index 00000000000..f07a833007f --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/error/ErrorMessage.java @@ -0,0 +1,40 @@ +package com.sap.sailing.gwt.home.client.shared.error; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.DivElement; +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.Window; +import com.google.gwt.user.client.ui.Anchor; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.home.client.i18n.TextMessages; + +public class ErrorMessage extends Composite { + + interface ErrorUiBinder extends UiBinder { + } + + private static ErrorUiBinder uiBinder = GWT.create(ErrorUiBinder.class); + + @UiField DivElement errorMessage; + @UiField DivElement errorMessageDetail; + @UiField Anchor reloadPageAnchor; + + public ErrorMessage(String detailMessage, Throwable errorReason) { + ErrorMessageResources.INSTANCE.css().ensureInjected(); + initWidget(uiBinder.createAndBindUi(this)); + + this.setHeight(Window.getClientHeight() + "px"); + errorMessage.setInnerText(TextMessages.INSTANCE.errorMessageLoadingData()); + + Window.setStatus(detailMessage); + } + + @UiHandler("reloadPageAnchor") + void reloadPage(ClickEvent e) { + Window.Location.reload(); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/error/ErrorMessage.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/error/ErrorMessage.ui.xml new file mode 100644 index 00000000000..17cfbe94034 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/error/ErrorMessage.ui.xml @@ -0,0 +1,22 @@ + + + + + + +
+
+
+
+
+
+
+ + +
+
+
+
+
\ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/error/ErrorMessageResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/error/ErrorMessageResources.java new file mode 100644 index 00000000000..f995675cd40 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/error/ErrorMessageResources.java @@ -0,0 +1,19 @@ +package com.sap.sailing.gwt.home.client.shared.error; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +public interface ErrorMessageResources extends ClientBundle { + public static final ErrorMessageResources INSTANCE = GWT.create(ErrorMessageResources.class); + + @Source("com/sap/sailing/gwt/home/client/shared/error/ErrorMessage.css") + LocalCss css(); + + public interface LocalCss extends CssResource { + String error_content(); + String error_message(); + String error_message_detail(); + String reload_page_message(); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/Footer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/Footer.java index 31eae3acd14..df38170fa80 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/Footer.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/Footer.java @@ -5,6 +5,8 @@ import java.util.Map; import java.util.Map.Entry; import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Style.Display; import com.google.gwt.dom.client.StyleInjector; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.http.client.UrlBuilder; @@ -22,6 +24,7 @@ public class Footer extends Composite { private static FooterPanelUiBinder uiBinder = GWT.create(FooterPanelUiBinder.class); @UiField Anchor changeLanguageLink; + @UiField DivElement languageSelectionDiv; private final Map localeAndLanguages; @@ -40,6 +43,7 @@ public class Footer extends Composite { initWidget(uiBinder.createAndBindUi(this)); + languageSelectionDiv.getStyle().setDisplay(Display.NONE); updateUI(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/Footer.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/Footer.ui.xml index b3cbffafd8d..df25806aa4a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/Footer.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/Footer.ui.xml @@ -23,7 +23,9 @@
- : +
+ : +
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/Header.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/Header.java index 1716c150233..8bd856a6fce 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/Header.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/Header.java @@ -4,6 +4,7 @@ import java.util.Arrays; import java.util.List; import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.AnchorElement; import com.google.gwt.dom.client.StyleInjector; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.KeyCodes; @@ -12,6 +13,9 @@ import com.google.gwt.event.dom.client.KeyPressHandler; 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.Event; +import com.google.gwt.user.client.EventListener; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Anchor; import com.google.gwt.user.client.ui.Button; @@ -19,11 +23,13 @@ import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.home.client.app.PlaceNavigator; +import com.sap.sailing.gwt.home.client.i18n.TextMessages; public class Header extends Composite { @UiField Anchor startPageLink; @UiField Anchor eventsPageLink; @UiField Anchor solutionsPageLink; + @UiField AnchorElement homeLink; // @UiField Anchor sponsoringPageLink; @UiField TextBox searchText; @@ -37,7 +43,7 @@ public class Header extends Composite { private static HeaderUiBinder uiBinder = GWT.create(HeaderUiBinder.class); - public Header(PlaceNavigator navigator) { + public Header(final PlaceNavigator navigator) { this.navigator = navigator; HeaderResources.INSTANCE.css().ensureInjected(); @@ -46,7 +52,7 @@ public class Header extends Composite { initWidget(uiBinder.createAndBindUi(this)); links = Arrays.asList(new Anchor[] { startPageLink, eventsPageLink, solutionsPageLink }); - searchText.getElement().setAttribute("placeholder", "Search SAPSailing.com"); + searchText.getElement().setAttribute("placeholder", TextMessages.INSTANCE.headerSearchPlaceholder()); searchText.addKeyPressHandler(new KeyPressHandler() { @Override public void onKeyPress(KeyPressEvent event) { @@ -55,6 +61,18 @@ public class Header extends Composite { } } }); + + Event.sinkEvents(homeLink, Event.ONCLICK); + Event.setEventListener(homeLink, new EventListener() { + @Override + public void onBrowserEvent(Event event) { + switch (DOM.eventGetType(event)) { + case Event.ONCLICK: + navigator.goToHome(); + break; + } + } + }); } @UiHandler("startPageLink") @@ -66,11 +84,13 @@ public class Header extends Composite { @UiHandler("eventsPageLink") public void goToEvents(ClickEvent e) { navigator.goToEvents(); + setActiveLink(eventsPageLink); } @UiHandler("solutionsPageLink") public void goToSolutions(ClickEvent e) { navigator.goToSolutions(); + setActiveLink(solutionsPageLink); } // @UiHandler("sponsoringPageLink") @@ -81,7 +101,7 @@ public class Header extends Composite { @UiHandler("searchButton") void searchButtonClick(ClickEvent event) { if(searchText.getText().isEmpty()) { - Window.alert("Please enter a search term."); + Window.alert(TextMessages.INSTANCE.pleaseEnterASearchTerm()); } else { navigator.goToSearchResult(searchText.getText()); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/Header.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/Header.ui.xml index d63fc69e7ed..d0360d6214b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/Header.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/Header.ui.xml @@ -9,10 +9,11 @@
- + + +

Sailing Analytics

-
-
- - - +
+
- +
+ +
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.java index d534564a6d6..8fccb67ec01 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.java @@ -6,6 +6,8 @@ import java.util.List; import java.util.Random; import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.DivElement; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.uibinder.client.UiBinder; @@ -24,6 +26,8 @@ import com.sap.sse.common.Util.Pair; public class MainMedia extends Composite { + private static final int MAX_VIDEO_COUNT = 3; + private static final MainMediaResources.LocalCss STYLES = MainMediaResources.INSTANCE.css(); @UiField HTMLPanel videosPanel; @@ -48,7 +52,7 @@ public class MainMedia extends Composite { public void setFeaturedEvents(List> featuredEvents) { for(Pair featuredEventTypeAndEvent: featuredEvents) { - if(featuredEventTypeAndEvent.getB().getVideoURLs().size() > 0 && videoCounter < 3) { + if(featuredEventTypeAndEvent.getB().getVideoURLs().size() > 0 && videoCounter < MAX_VIDEO_COUNT) { addVideoToVideoPanel(featuredEventTypeAndEvent.getB()); } } @@ -56,31 +60,35 @@ public class MainMedia extends Composite { public void setRecentEvents(List recentEvents) { List photoGalleryUrls = new ArrayList(); - - for(EventBaseDTO event: recentEvents) { + for (EventBaseDTO event : recentEvents) { photoGalleryUrls.addAll(event.getPhotoGalleryImageURLs()); - - if(event.getVideoURLs().size() > 0 && videoCounter < 3) { + if (!event.getVideoURLs().isEmpty() && videoCounter < MAX_VIDEO_COUNT) { addVideoToVideoPanel(event); } } - // shuffle the image url list (Remark: Collections.shuffle() is not implemented in GWT) int gallerySize = photoGalleryUrls.size(); - Random random = new Random(gallerySize); - for(int i = 0; i < gallerySize; i++) { - Collections.swap(photoGalleryUrls, i, random.nextInt(gallerySize)); + Random random = new Random(gallerySize); + for (int i = 0; i < gallerySize; i++) { + Collections.swap(photoGalleryUrls, i, random.nextInt(gallerySize)); } - - for(String url : photoGalleryUrls) { + for (String url : photoGalleryUrls) { SimplePanel imageContainer = new SimplePanel(); imageContainer.addStyleName(STYLES.media_swiperslide()); String image = "url(" + url + ")"; imageContainer.getElement().getStyle().setBackgroundImage(image); mediaSlides.add(imageContainer); } - this.swiper = Swiper.createWithLoopOption(STYLES.media_swipecontainer(), STYLES.media_swipewrapper(), STYLES.media_swiperslide()); + // See bug 2232: the stage image sizes are scaled incorrectly. https://github.com/ubilabs/sap-sailing-analytics/issues/421 and + // http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=2232 have the details. A quick fix may be to send a resize event + // after everything has been rendered. + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + @Override + public void execute() { + swiper.reInit(); + } + }); } private void addVideoToVideoPanel(EventBaseDTO event) { @@ -95,19 +103,12 @@ public class MainMedia extends Composite { } private String getRandomVideoURL(EventBaseDTO event) { - String result = null; - int videosCount = event.getVideoURLs().size(); - if(videosCount > 0) { - if(videosCount == 1) { - result = event.getVideoURLs().get(0); - } else { - List videoUrls = new ArrayList(event.getVideoURLs()); - Random random = new Random(videosCount); - for(int i = 0; i < videosCount; i++) { - Collections.swap(videoUrls, i, random.nextInt(videosCount)); - } - result = videoUrls.get(0); - } + final String result; + List videoURLs = event.getVideoURLs(); + if (!videoURLs.isEmpty()) { + result = event.getVideoURLs().get(new Random(videoURLs.size()).nextInt(videoURLs.size())); + } else { + result = null; } return result; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideo.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideo.java index e5509fe7017..c928d42ea2d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideo.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideo.java @@ -3,10 +3,12 @@ package com.sap.sailing.gwt.home.client.shared.mainmedia; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.IFrameElement; import com.google.gwt.dom.client.SpanElement; +import com.google.gwt.safehtml.shared.SafeHtml; 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.client.shared.LongNamesUtil; public class MainMediaVideo extends Composite { @@ -24,7 +26,9 @@ public class MainMediaVideo extends Composite { initWidget(uiBinder.createAndBindUi(this)); youtubeEmbed.setSrc("//www.youtube.com/embed/" + youtubeId + "?modestbranding=1&rel=0&showinfo=0&autohide=1&fs=1"); - videoTitle.setInnerHTML(eventName); + + SafeHtml safeHtmlEventName = LongNamesUtil.breakLongName(eventName); + videoTitle.setInnerSafeHtml(safeHtmlEventName); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/placeholder/Placeholder.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/placeholder/Placeholder.css new file mode 100644 index 00000000000..9646968a0d2 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/placeholder/Placeholder.css @@ -0,0 +1,5 @@ +.content { + margin-top: 3em; + font-weight: bold; + vertical-align: center; +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/placeholder/Placeholder.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/placeholder/Placeholder.java new file mode 100644 index 00000000000..5a2fe3f4e55 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/placeholder/Placeholder.java @@ -0,0 +1,23 @@ +package com.sap.sailing.gwt.home.client.shared.placeholder; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; + +public class Placeholder extends Composite { + + interface PlaceholderUiBinder extends UiBinder { + } + + private static PlaceholderUiBinder uiBinder = GWT.create(PlaceholderUiBinder.class); + + public Placeholder() { + PlaceholderResources.INSTANCE.css().ensureInjected(); + + initWidget(uiBinder.createAndBindUi(this)); + + this.setHeight(Window.getClientHeight() + "px"); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/placeholder/Placeholder.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/placeholder/Placeholder.ui.xml new file mode 100644 index 00000000000..731fa0667e1 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/placeholder/Placeholder.ui.xml @@ -0,0 +1,18 @@ + + + + + + +
+
+
+ Loading... + +
+
+
+
+
\ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/placeholder/PlaceholderResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/placeholder/PlaceholderResources.java new file mode 100644 index 00000000000..f1e54d39dd7 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/placeholder/PlaceholderResources.java @@ -0,0 +1,16 @@ +package com.sap.sailing.gwt.home.client.shared.placeholder; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +public interface PlaceholderResources extends ClientBundle { + public static final PlaceholderResources INSTANCE = GWT.create(PlaceholderResources.class); + + @Source("com/sap/sailing/gwt/home/client/shared/placeholder/Placeholder.css") + LocalCss css(); + + public interface LocalCss extends CssResource { + String content(); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/recentevent/RecentEvent.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/recentevent/RecentEvent.css index 5fba8465670..c68f10f5773 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/recentevent/RecentEvent.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/recentevent/RecentEvent.css @@ -1,11 +1,17 @@ -/** - * Events on the main page - */ +.eventteasercontainer { + font-size: 15px; + font-size: 1rem; + text-align: center; + width: 100%; + height: 24em; +} .eventteaser { - font-size: 16px; + font-size: 15px; font-size: 1rem; display: inline-block; - margin-bottom: 2.8125em; + margin-bottom: 3em; + text-align: left; + max-width: 100%; } .eventteaser:hover { text-decoration: none; @@ -19,7 +25,9 @@ filter: literal("alpha(opacity=90)"); } .eventteaser_image { - height: auto; + position: relative; + display: inline-block; + max-width: 100%; } .eventteaser_series { position: absolute; @@ -27,16 +35,11 @@ right: 0; } .eventteaser_name { - font-size: 1.375em; + font-size: 1.466666666666667em; font-weight: bold; color: #008fcc; - margin-top: 0.3125em; + margin-top: 0.333333333333333em; } .eventteaser_location { color: #222; } - -.eventsoverviewrecent { - font-size: 16px; - font-size: 1rem; -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/recentevent/RecentEvent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/recentevent/RecentEvent.java index b621c4dc5ef..89e8897df92 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/recentevent/RecentEvent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/recentevent/RecentEvent.java @@ -1,64 +1,74 @@ package com.sap.sailing.gwt.home.client.shared.recentevent; +import java.util.List; + 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.ImageElement; import com.google.gwt.dom.client.SpanElement; -import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.dom.client.Style.Display; +import com.google.gwt.safehtml.shared.SafeHtml; 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.ui.Anchor; -import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.Widget; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.EventListener; +import com.google.gwt.user.client.ui.UIObject; import com.sap.sailing.gwt.home.client.app.PlaceNavigator; import com.sap.sailing.gwt.home.client.shared.EventDatesFormatterUtil; +import com.sap.sailing.gwt.home.client.shared.LongNamesUtil; import com.sap.sailing.gwt.ui.shared.EventBaseDTO; -public class RecentEvent extends Composite { +public class RecentEvent extends UIObject { @UiField SpanElement eventName; @UiField SpanElement venueName; @UiField SpanElement eventStartDate; - @UiField Anchor eventOverviewLink; + @UiField AnchorElement eventOverviewLink; @UiField ImageElement eventImage; - - private EventBaseDTO event; + @UiField DivElement isLiveDiv; + + private final EventBaseDTO event; private final String defaultImageUrl = "http://static.sapsailing.com/ubilabsimages/default/default_event_photo.jpg"; - interface RecentEventUiBinder extends UiBinder { + interface RecentEventUiBinder extends UiBinder { } private static RecentEventUiBinder uiBinder = GWT.create(RecentEventUiBinder.class); - private final PlaceNavigator navigator; - - public RecentEvent(PlaceNavigator navigator) { - this.navigator = navigator; - RecentEventResources.INSTANCE.css().ensureInjected(); - - initWidget(uiBinder.createAndBindUi(this)); - } - - public void setEvent(EventBaseDTO event) { + public RecentEvent(final PlaceNavigator navigator, final EventBaseDTO event) { this.event = event; + RecentEventResources.INSTANCE.css().ensureInjected(); + setElement(uiBinder.createAndBindUi(this)); + Event.sinkEvents(eventOverviewLink, Event.ONCLICK); + Event.setEventListener(eventOverviewLink, new EventListener() { + @Override + public void onBrowserEvent(Event browserEvent) { + switch (DOM.eventGetType(browserEvent)) { + case Event.ONCLICK: + navigator.goToEvent(event.id.toString(), event.getBaseURL(), event.isOnRemoteServer()); + break; + } + } + }); updateUI(); } - - @UiHandler("eventOverviewLink") - public void goToEventPlace(ClickEvent e) { - navigator.goToEvent(event.id.toString(), event.getBaseURL()); - } - + private void updateUI() { - eventName.setInnerText(event.getName()); + SafeHtml safeHtmlEventName = LongNamesUtil.breakLongName(event.getName()); + eventName.setInnerSafeHtml(safeHtmlEventName); + if (!event.isRunning()) { + isLiveDiv.getStyle().setDisplay(Display.NONE); + } venueName.setInnerText(event.venue.getName()); eventStartDate.setInnerText(EventDatesFormatterUtil.formatDateRangeWithoutYear(event.startDate, event.endDate)); - - if(event.getImageURLs().size() == 0) { + List photoGalleryImageURLs = event.getPhotoGalleryImageURLs(); + if (photoGalleryImageURLs.isEmpty()) { eventImage.setSrc(defaultImageUrl); } else { - eventImage.setSrc(event.getImageURLs().get(0)); + eventImage.setSrc(photoGalleryImageURLs.get(0)); } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/recentevent/RecentEvent.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/recentevent/RecentEvent.ui.xml index f4322426071..2592bd30cfc 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/recentevent/RecentEvent.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/recentevent/RecentEvent.ui.xml @@ -5,23 +5,17 @@ - -.img-fixedsize-temp { - width: 370px; - height: auto; -} - -
- -
-
-
-
-
- ,  -
-
+
-
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/recentevent/RecentEventResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/recentevent/RecentEventResources.java index 25aff2b9124..6f06bda51d2 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/recentevent/RecentEventResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/recentevent/RecentEventResources.java @@ -16,6 +16,6 @@ public interface RecentEventResources extends ClientBundle { String eventteaser_image(); String eventteaser_series(); String eventteaser_location(); - String eventsoverviewrecent(); + String eventteasercontainer(); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/recentevent/RecentEvents.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/recentevent/RecentEvents.java deleted file mode 100644 index a1c4e4fcc4c..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/recentevent/RecentEvents.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.sap.sailing.gwt.home.client.shared.recentevent; - -import java.util.ArrayList; -import java.util.List; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.DivElement; -import com.google.gwt.dom.client.HeadingElement; -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.HTMLPanel; -import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.gwt.home.client.app.PlaceNavigator; -import com.sap.sailing.gwt.ui.shared.EventBaseDTO; - -public class RecentEvents extends Composite { - - @UiField DivElement recentEventsGrid; - @UiField HTMLPanel headline; - @UiField HeadingElement titleHeading; - - /** Attention: This must be a widget -> otherwise the events of the child widgets would not be propagated */ - @UiField HTMLPanel eventsPlaceholder; - - private final List recentEventComposites; - - interface RecentEventsUiBinder extends UiBinder { - } - - private static RecentEventsUiBinder uiBinder = GWT.create(RecentEventsUiBinder.class); - - private final PlaceNavigator navigator; - - public RecentEvents(PlaceNavigator navigator) { - this.navigator = navigator; - recentEventComposites = new ArrayList(); - - RecentEventResources.INSTANCE.css().ensureInjected(); - initWidget(uiBinder.createAndBindUi(this)); - } - - public void setEvents(List events, String headlineText) { - eventsPlaceholder.clear(); - recentEventComposites.clear(); - - if(headlineText != null) { - titleHeading.setInnerText(headlineText); - headline.setVisible(true); - } else { - titleHeading.setInnerText(""); - headline.setVisible(false); - } - - for(EventBaseDTO event: events) { - RecentEvent recentEvent = new RecentEvent(navigator); - - recentEvent.setEvent(event); - recentEventComposites.add(recentEvent); - eventsPlaceholder.add(recentEvent); - } - - } - -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/searchresult/SearchResult.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/searchresult/SearchResult.java index c3130b3168e..a0272cdda02 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/searchresult/SearchResult.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/searchresult/SearchResult.java @@ -18,9 +18,7 @@ import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.Widget; -import com.google.web.bindery.event.shared.EventBus; import com.sap.sailing.gwt.home.client.app.PlaceNavigator; -import com.sap.sailing.gwt.home.client.place.searchresult.SearchEvent; import com.sap.sailing.gwt.ui.shared.LeaderboardSearchResultDTO; public class SearchResult extends Composite { @@ -40,11 +38,9 @@ public class SearchResult extends Composite { private final List searchResultItemComposites; private final PlaceNavigator navigator; - private final EventBus eventBus; - public SearchResult(PlaceNavigator navigator, EventBus eventBus) { + public SearchResult(PlaceNavigator navigator) { this.navigator = navigator; - this.eventBus = eventBus; this.searchResultItemComposites = new ArrayList(); SearchResultResources.INSTANCE.css().ensureInjected(); @@ -84,7 +80,7 @@ public class SearchResult extends Composite { if(searchText.isEmpty()) { Window.alert("Please enter a search term."); } else { - eventBus.fireEvent(new SearchEvent(searchText)); + navigator.goToSearchResult(searchText); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/searchresult/SearchResultItem.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/searchresult/SearchResultItem.java index e0709cba666..38eed35a50b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/searchresult/SearchResultItem.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/searchresult/SearchResultItem.java @@ -55,12 +55,12 @@ public class SearchResultItem extends Composite { @UiHandler("regattaLink") public void goToRegatta(ClickEvent e) { EventBaseDTO event = searchResult.getEvent(); - placeNavigator.goToRegattaOfEvent(event.id.toString(), searchResult.getLeaderboardName(), searchResult.getBaseURL()); + placeNavigator.goToRegattaOfEvent(event.id.toString(), searchResult.getLeaderboardName(), searchResult.getBaseURL(), searchResult.isOnRemoteServer()); } @UiHandler("eventOverviewLink") public void goToEventPlace(ClickEvent e) { EventBaseDTO event = searchResult.getEvent(); - placeNavigator.goToEvent(event.id.toString(), searchResult.getBaseURL()); + placeNavigator.goToEvent(event.id.toString(), searchResult.getBaseURL(), searchResult.isOnRemoteServer()); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/searchresult/SearchResultItem.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/searchresult/SearchResultItem.ui.xml index 771fcfc84e0..ffcc85a9ebb 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/searchresult/SearchResultItem.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/searchresult/SearchResultItem.ui.xml @@ -7,12 +7,12 @@
- +
- + | |
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/socialfooter/SocialFooter.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/socialfooter/SocialFooter.css index 5333ffd1fbe..fa6a138c0a9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/socialfooter/SocialFooter.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/socialfooter/SocialFooter.css @@ -26,7 +26,7 @@ width: 3.125em; height: 3.75em; left: 0; - background: url("../images/social--icons.png"); + background: url("images/home/social-icons.png"); } .socialfooter_buttontwitter { background: #58aeee; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/socialfooter/SocialFooter.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/socialfooter/SocialFooter.ui.xml index 12a16fc293b..ceecd7c16bb 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/socialfooter/SocialFooter.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/socialfooter/SocialFooter.ui.xml @@ -11,17 +11,17 @@
-

Recommend SAP Sailing Analytics

+

Follow SAP Sports

-
-
Share this on Twitter
-
-
-
-
Share this on Facebook
-
-
+ +
SAP Sports on Twitter
+
+
+ +
SAP Sports on Facebook
+
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/solutions/Solutions.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/solutions/Solutions.css index 5b47e3416ae..712d17087a6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/solutions/Solutions.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/solutions/Solutions.css @@ -1,23 +1,38 @@ -/** - * The solutions page - */ .solutions { - font-size: 16px; + font-size: 15px; font-size: 1rem; + padding-top: 3.4em; } .solutions_nav { + position: absolute; + top: 4.666666666666667em; + right: 0; + left: 0; border-bottom: 1px solid #ddd; + background: #fff; + z-index: 10; + text-align: center; +} +.solutions_navsticky { + position: fixed; + top: 0; } .solutions_nav_link { display: inline-block; - padding: 0.9375em 0 0.6875em 0; - border-bottom: 0.25em solid transparent; + padding: 1em 0 0.733333333333333em 0; + border-bottom: 0.266666666666667em solid transparent; font-weight: 600; color: #666; - margin-right: 0.3125em; + -webkit-transition: 0.2s border ease; + -moz-transition: 0.2s border ease; + -o-transition: 0.2s border ease; + -ms-transition: 0.2s border ease; + transition: 0.2s border ease; + margin-right: 1em; } .solutions_nav_link:hover, -.solutions_nav_link:focus { +.solutions_nav_link:focus, +.solutions_nav_linkactive { border-bottom-color: #f0ab00; text-decoration: none; color: #333; @@ -43,12 +58,12 @@ right: 0; z-index: -1; } -.solutions_content .global-grid { +.solutions_content .gridalternator { height: 100%; - padding: 3.125em 0; + padding: 3.333333333333333em 0; } .solutions_contentsap { - max-width: 82.5em; + max-width: 88em; margin: auto; background-repeat: no-repeat; background-position: center center; @@ -57,86 +72,89 @@ background-size: 100%; background-image: url("images/home/solutions-sap.png"); } -.solutions_contentsap .global-grid { - padding: 3.75em 0 4.0625em; +.solutions_contentsap .gridalternator { + padding: 4em 0 4.333333333333333em; } .solutions_contentrace { - font-size: 16px; + font-size: 15px; font-size: 1rem; } -.solutions_contentrace .global-grid { - padding: 2.5em 0 10%; +.solutions_contentrace .gridalternator { + padding: 2.666666666666667em 0 10%; } .solutions_contentrace .parallax { - font-size: 16px; + font-size: 15px; font-size: 1rem; } .solutions_contentrace .parallax .background { margin: auto; height: 100%; - max-width: 82.5em; + max-width: 88em; background-repeat: no-repeat; background-image: url("images/home/solutions-race.png"); -webkit-background-size: 100% auto; -moz-background-size: 100% auto; background-size: 100% auto; - background-position: center 18.75em; + background-position: center 20em; } .solutions_contentrace .parallax.noparallax .background { + margin-left: 40px; + margin-right: -80px; + margin-top: 110px; background-position: center bottom; } .solutions_contentpost { - font-size: 16px; + font-size: 15px; font-size: 1rem; } -.solutions_contentpost .global-grid { +.solutions_contentpost .gridalternator { background-repeat: no-repeat; background-image: url("images/home/solutions-post.png"); background-position: left center; -webkit-background-size: 100%; -moz-background-size: 100%; background-size: 100%; - padding: 1.5625em 0; + padding: 1.666666666666667em 0; } .solutions_contenttraining { - font-size: 16px; + font-size: 15px; font-size: 1rem; } -.solutions_contenttraining .global-grid { - padding-top: 4.6875em; +.solutions_contenttraining .gridalternator { + padding-top: 5em; } .solutions_contenttraining .parallax { - font-size: 16px; + font-size: 15px; font-size: 1rem; } .solutions_contenttraining .parallax .background { margin: auto; height: 100%; - max-width: 75em; + max-width: 80em; background-repeat: no-repeat; background-image: url("images/home/solutions-training.png"); -webkit-background-size: 100% auto; -moz-background-size: 100% auto; background-size: 100% auto; - background-position: center 12.5em; + background-position: center 13.333333333333334em; } .solutions_contenttraining .parallax.noparallax .background { - background-position: center 6.25em; + background-position: center 6.666666666666667em; } .solutions_contentsimulator { - font-size: 16px; + font-size: 15px; font-size: 1rem; } -.solutions_contentsimulator .global-grid { +.solutions_contentsimulator .gridalternator { background-repeat: no-repeat; background-image: url("images/home/solutions-simulator.png"); background-position: left center; -webkit-background-size: 100%; -moz-background-size: 100%; background-size: 100%; - padding: 3.125em 0 3.125em; + padding: 3.333333333333333em 0 3.333333333333333em; } .solutions_content_linkappstore { display: inline-block; - margin-top: 2.5em; + margin-top: 2.666666666666667em; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/solutions/Solutions.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/solutions/Solutions.java index c1eb12c271c..f032f05bf97 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/solutions/Solutions.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/solutions/Solutions.java @@ -19,11 +19,11 @@ public class Solutions extends Composite { @UiField Anchor trainingDiaryAnchor; @UiField Anchor simulatorAnchor; - @UiField DivElement sailingAnalytics; - @UiField DivElement race; - @UiField DivElement postRace; - @UiField DivElement trainingDiary; - @UiField DivElement simulator; + @UiField DivElement sailingAnalyticsDiv; + @UiField DivElement raceDiv; + @UiField DivElement postRaceDiv; + @UiField DivElement trainingDiaryDiv; + @UiField DivElement simulatorDiv; interface SolutionsUiBinder extends UiBinder { } @@ -41,25 +41,25 @@ public class Solutions extends Composite { @UiHandler("sailingAnalyticsAnchor") public void scrollToSailingAnalytics(ClickEvent e) { - sailingAnalytics.scrollIntoView(); + sailingAnalyticsDiv.scrollIntoView(); } @UiHandler("raceAnchor") public void scrollToRace(ClickEvent e) { - race.scrollIntoView(); + raceDiv.scrollIntoView(); } @UiHandler("postRaceAnchor") public void scrollToPostRace(ClickEvent e) { - postRace.scrollIntoView(); + postRaceDiv.scrollIntoView(); } @UiHandler("trainingDiaryAnchor") public void scrollToTrainingDiary(ClickEvent e) { - trainingDiary.scrollIntoView(); + trainingDiaryDiv.scrollIntoView(); } @UiHandler("simulatorAnchor") public void scrollToSimulator(ClickEvent e) { - simulator.scrollIntoView(); + simulatorDiv.scrollIntoView(); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/solutions/Solutions.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/solutions/Solutions.ui.xml index 8110b8fc28e..5b08b4d340b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/solutions/Solutions.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/solutions/Solutions.ui.xml @@ -10,66 +10,64 @@
-
-
- - - - - -
+
+
+ + + + + +
-
-
+
+

- +
-
+
-
-
+
+

-
-
+
+

-
+
-
+

- - - +
-
-
+
+

- +
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/solutions/SolutionsLarge.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/solutions/SolutionsLarge.css index 55aba5f9fac..b4241f0e940 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/solutions/SolutionsLarge.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/solutions/SolutionsLarge.css @@ -1,29 +1,30 @@ .solutions_nav_link { - margin-right: 1.875em; + margin-right: 2em; } -.solutions_content .global-grid { - padding: 6.25em 0; +.solutions_content .gridalternator { + padding: 6.666666666666667em 0; } -.solutions_contentsap .global-grid { - padding: 6.25em 0 11.25em; +.solutions_contentsap .gridalternator { + padding: 6.666666666666667em 0 12em; } -.solutions_contentrace .global-grid { - padding: 3.75em 0 20%; +.solutions_contentrace .gridalternator { + padding: 4em 0 20%; } .solutions_contentrace .parallax .background { - background-position: center 12.5em; + background-position: center 13.333333333333334em; } -.solutions_contentpost .global-grid { - padding: 4.6875em 0; +.solutions_contentpost .gridalternator { + padding: 5em 0; } -.solutions_contenttraining .global-grid { - padding-top: 7.8125em; +.solutions_contenttraining .gridalternator { + padding-top: 8.333333333333334em; } -.solutions_contentsimulator .global-grid { - padding: 6.25em 0 12.5em; +.solutions_contentsimulator .gridalternator { + padding: 6.666666666666667em 0 13.333333333333334em; } - .solutions_content_linkappstore{} .noparallax{} .solutions{} .solutions_nav{} +.solutions_nav_linkactive{} +.solutions_navsticky{} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/solutions/SolutionsMedium.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/solutions/SolutionsMedium.css index 261e286ccd3..8cbe5257a12 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/solutions/SolutionsMedium.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/solutions/SolutionsMedium.css @@ -1,29 +1,30 @@ .solutions_nav_link { - margin-right: 0.8125em; + margin-right: 1em; } -.solutions_content .global-grid { - padding: 4.6875em 0; +.solutions_content .gridalternator { + padding: 5em 0; } -.solutions_contentsap .global-grid { - padding: 4.6875em 0 5.3125em; +.solutions_contentsap .gridalternator { + padding: 5em 0 5.666666666666667em; } -.solutions_contentrace .global-grid { - padding: 3.75em 0 15%; +.solutions_contentrace .gridalternator { + padding: 4em 0 15%; } .solutions_contentrace .parallax .background { - background-position: center 15.625em; + background-position: center 16.666666666666668em; } -.solutions_contentpost .global-grid { - padding: 3.125em 0; +.solutions_contentpost .gridalternator { + padding: 3.333333333333333em 0; } -.solutions_contenttraining .global-grid { - padding-top: 6.25em; +.solutions_contenttraining .gridalternator { + padding-top: 6.666666666666667em; } -.solutions_contentsimulator .global-grid { - padding: 4.6875em 0 6.25em; +.solutions_contentsimulator .gridalternator { + padding: 5em 0 6.666666666666667em; } - .solutions_content_linkappstore{} .noparallax{} .solutions{} .solutions_nav{} +.solutions_nav_linkactive{} +.solutions_navsticky{} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/solutions/SolutionsResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/solutions/SolutionsResources.java index 11b869e374d..cb774a8e237 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/solutions/SolutionsResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/solutions/SolutionsResources.java @@ -3,33 +3,23 @@ package com.sap.sailing.gwt.home.client.shared.solutions; import com.google.gwt.core.client.GWT; import com.google.gwt.resources.client.ClientBundle; import com.google.gwt.resources.client.CssResource; -import com.google.gwt.resources.client.CssResource.Import; -import com.google.gwt.resources.client.CssResource.ImportedWithPrefix; import com.google.gwt.resources.client.CssResource.Shared; -import com.sap.sailing.gwt.home.client.HomeResources.MainCss; public interface SolutionsResources extends ClientBundle { public static final SolutionsResources INSTANCE = GWT.create(SolutionsResources.class); - @ImportedWithPrefix("global") - interface ImportedMainCss extends MainCss { - } - - @Import(value = {ImportedMainCss.class}) @Source("com/sap/sailing/gwt/home/client/shared/solutions/Solutions.css") LocalCss css(); public interface MinWidth25emCss extends LocalCss { } - @Import(value = {ImportedMainCss.class}) @Source("com/sap/sailing/gwt/home/client/shared/solutions/SolutionsMedium.css") MinWidth25emCss mediumCss(); public interface MinWidth50emCss extends LocalCss { } - @Import(value = {ImportedMainCss.class}) @Source("com/sap/sailing/gwt/home/client/shared/solutions/SolutionsLarge.css") MinWidth50emCss largeCss(); @@ -37,9 +27,12 @@ public interface SolutionsResources extends ClientBundle { public interface LocalCss extends CssResource { String solutions(); String solutions_nav(); + String solutions_navsticky(); String solutions_nav_link(); + String solutions_nav_linkactive(); String solutions_content(); String parallax(); + String gridalternator(); String solutions_contentsap(); String solutions_contentrace(); String background(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/LiveEventStageTeaser.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/LiveEventStageTeaser.java index f94a2bf5ee8..5de38baf9e6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/LiveEventStageTeaser.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/LiveEventStageTeaser.java @@ -18,7 +18,7 @@ public class LiveEventStageTeaser extends StageTeaser { bandCount.setAttribute("data-bandcount", "1"); - stageTeaserBandsPanel.add(new LiveStageTeaserBand(event, placeNavigator)); + stageTeaserBandsPanel.getElement().appendChild(new LiveStageTeaserBand(event, placeNavigator).getElement()); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/LiveStageTeaserBand.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/LiveStageTeaserBand.java index 4919469058b..04abbbd0557 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/LiveStageTeaserBand.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/LiveStageTeaserBand.java @@ -1,9 +1,16 @@ package com.sap.sailing.gwt.home.client.shared.stage; +import com.google.gwt.dom.client.Style.Display; import com.sap.sailing.gwt.home.client.app.PlaceNavigator; +import com.sap.sailing.gwt.home.client.i18n.TextMessages; import com.sap.sailing.gwt.home.client.shared.EventDatesFormatterUtil; import com.sap.sailing.gwt.ui.shared.EventBaseDTO; +/** + * Teaser band for a live event on the homepage stage + * @author Frank + * + */ public class LiveStageTeaserBand extends StageTeaserBand { public LiveStageTeaserBand(EventBaseDTO event, PlaceNavigator placeNavigator) { @@ -12,13 +19,14 @@ public class LiveStageTeaserBand extends StageTeaserBand { bandTitle.setInnerText(event.getName()); bandSubtitle.setInnerText(EventDatesFormatterUtil.formatDateRangeWithYear(event.startDate, event.endDate)); - actionLink.setVisible(true); - actionLink.setText("Show event"); + isLiveDiv.getStyle().setDisplay(Display.INLINE_BLOCK); + actionLink.getStyle().setDisplay(Display.INLINE_BLOCK); + actionLink.setInnerText(TextMessages.INSTANCE.watchNow()); } @Override public void actionLinkClicked() { EventBaseDTO event = getEvent(); - getPlaceNavigator().goToEvent(event.id.toString(), event.getBaseURL()); + getPlaceNavigator().goToEvent(event.id.toString(), event.getBaseURL(), event.isOnRemoteServer()); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/NoActionStageTeaserBand.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/NoActionStageTeaserBand.java index d87c26b3d3e..23808063c66 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/NoActionStageTeaserBand.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/NoActionStageTeaserBand.java @@ -4,6 +4,10 @@ import com.sap.sailing.gwt.home.client.app.PlaceNavigator; import com.sap.sailing.gwt.home.client.shared.EventDatesFormatterUtil; import com.sap.sailing.gwt.ui.shared.EventBaseDTO; +/** + * Teaser band with no action on the homepage stage + * + */ public class NoActionStageTeaserBand extends StageTeaserBand { public NoActionStageTeaserBand(EventBaseDTO event, PlaceNavigator placeNavigator) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/PopularEventStageTeaser.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/PopularEventStageTeaser.java index 55079b54e77..e6c3321f665 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/PopularEventStageTeaser.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/PopularEventStageTeaser.java @@ -1,7 +1,6 @@ package com.sap.sailing.gwt.home.client.shared.stage; import com.google.gwt.dom.client.Style.Display; -import com.google.gwt.dom.client.Style.Visibility; import com.sap.sailing.gwt.home.client.app.PlaceNavigator; import com.sap.sailing.gwt.ui.shared.EventBaseDTO; @@ -14,10 +13,9 @@ public class PopularEventStageTeaser extends StageTeaser { subtitle.setInnerText(event.venue.getName()); countdown.getStyle().setDisplay(Display.NONE); - countdown.getStyle().setVisibility(Visibility.HIDDEN); bandCount.setAttribute("data-bandcount", "1"); - - stageTeaserBandsPanel.add(new NoActionStageTeaserBand(event, placeNavigator)); + + stageTeaserBandsPanel.getElement().appendChild(new PopularEventStageTeaserBand(event, placeNavigator).getElement()); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/PopularEventStageTeaserBand.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/PopularEventStageTeaserBand.java new file mode 100644 index 00000000000..bf38f0f2ac8 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/PopularEventStageTeaserBand.java @@ -0,0 +1,31 @@ +package com.sap.sailing.gwt.home.client.shared.stage; + +import com.google.gwt.dom.client.Style.Display; +import com.sap.sailing.gwt.home.client.app.PlaceNavigator; +import com.sap.sailing.gwt.home.client.i18n.TextMessages; +import com.sap.sailing.gwt.home.client.shared.EventDatesFormatterUtil; +import com.sap.sailing.gwt.ui.shared.EventBaseDTO; + +/** + * Teaser band for a popular but finished event on the homepage stage + * @author Frank + * + */ +public class PopularEventStageTeaserBand extends StageTeaserBand { + + public PopularEventStageTeaserBand(EventBaseDTO event, PlaceNavigator placeNavigator) { + super(event, placeNavigator); + + bandTitle.setInnerText(event.getName()); + bandSubtitle.setInnerText(EventDatesFormatterUtil.formatDateRangeWithYear(event.startDate, event.endDate)); + + actionLink.getStyle().setDisplay(Display.INLINE_BLOCK); + actionLink.setInnerText(TextMessages.INSTANCE.viewAnalysis()); + } + + @Override + public void actionLinkClicked() { + EventBaseDTO event = getEvent(); + getPlaceNavigator().goToEvent(event.id.toString(), event.getBaseURL(), event.isOnRemoteServer()); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.java index 35ec279d200..f3bc7ae6b98 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.java @@ -67,25 +67,30 @@ public class Stage extends Composite { stageTeaserComposites.add(stageTeaser); } - this.swiper = Swiper.createWithoutLoopOption(StageResources.INSTANCE.css().stage_teasers(), - StageResources.INSTANCE.css().swiperwrapper(), - StageResources.INSTANCE.css().swiperslide(), new Swiper.PageChangeListener() { - - @Override - public void pageChanged(int newPageIndex, int pageCount) { - boolean isFirstSlide = newPageIndex == 0; - boolean isLastSlide = newPageIndex == pageCount - 1; - nextStageTeaserLink.setVisible(!isLastSlide); - prevStageTeaserLink.setVisible(!isFirstSlide); - } - }); if (featuredEvents.size() <= 1) { prevStageTeaserLink.setVisible(false); nextStageTeaserLink.setVisible(false); } else { + swiper = Swiper.createWithoutLoopOption(StageResources.INSTANCE.css().stage_teasers(), + StageResources.INSTANCE.css().swiperwrapper(), + StageResources.INSTANCE.css().swiperslide(), new Swiper.PageChangeListener() { + + @Override + public void pageChanged(int newPageIndex, int pageCount) { + boolean isFirstSlide = newPageIndex == 0; + boolean isLastSlide = newPageIndex == pageCount - 1; + nextStageTeaserLink.setVisible(!isLastSlide); + prevStageTeaserLink.setVisible(!isFirstSlide); + } + }); + prevStageTeaserLink.setVisible(false); } } + + public void adjustSize() { + swiper.reInit(); + } @UiHandler("nextStageTeaserLink") public void nextStageTeaserLinkClicked(ClickEvent e) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaserBand.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaserBand.java index 9ec38db2aa0..0ab51b1d82e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaserBand.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaserBand.java @@ -1,27 +1,30 @@ package com.sap.sailing.gwt.home.client.shared.stage; 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.SpanElement; -import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.dom.client.Style.Display; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.uibinder.client.UiHandler; -import com.google.gwt.user.client.ui.Anchor; -import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.Widget; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.EventListener; +import com.google.gwt.user.client.ui.UIObject; import com.sap.sailing.gwt.home.client.app.PlaceNavigator; import com.sap.sailing.gwt.ui.shared.EventBaseDTO; -public abstract class StageTeaserBand extends Composite { +public abstract class StageTeaserBand extends UIObject { - interface StageTeaserBandUiBinder extends UiBinder { + interface StageTeaserBandUiBinder extends UiBinder { } private static StageTeaserBandUiBinder uiBinder = GWT.create(StageTeaserBandUiBinder.class); @UiField SpanElement bandTitle; @UiField SpanElement bandSubtitle; - @UiField Anchor actionLink; + @UiField AnchorElement actionLink; + @UiField DivElement isLiveDiv; private final PlaceNavigator placeNavigator; private final EventBaseDTO event; @@ -31,9 +34,23 @@ public abstract class StageTeaserBand extends Composite { this.placeNavigator = placeNavigator; StageResources.INSTANCE.css().ensureInjected(); - initWidget(uiBinder.createAndBindUi(this)); + setElement(uiBinder.createAndBindUi(this)); - actionLink.setVisible(false); + isLiveDiv.getStyle().setDisplay(Display.NONE); + + Event.sinkEvents(actionLink, Event.ONCLICK); + Event.setEventListener(actionLink, new EventListener() { + @Override + public void onBrowserEvent(Event event) { + switch (DOM.eventGetType(event)) { + case Event.ONCLICK: + actionLinkClicked(); + break; + } + } + }); + + actionLink.getStyle().setDisplay(Display.NONE); } public PlaceNavigator getPlaceNavigator() { @@ -44,12 +61,6 @@ public abstract class StageTeaserBand extends Composite { return event; } - @UiHandler("actionLink") - public void actionLinkClicked(ClickEvent e) { - actionLinkClicked(); - } - protected void actionLinkClicked() { } - } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaserBand.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaserBand.ui.xml index ad7fb44b52a..0fd9a33f625 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaserBand.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaserBand.ui.xml @@ -5,15 +5,14 @@ - -
-
+
+     +
+
- +
- -
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/UpcomingEventStageTeaser.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/UpcomingEventStageTeaser.java index 8d51c7f9de7..0f9bc071768 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/UpcomingEventStageTeaser.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/UpcomingEventStageTeaser.java @@ -12,6 +12,6 @@ public class UpcomingEventStageTeaser extends StageTeaser { subtitle.setInnerText(event.venue.getName()); bandCount.setAttribute("data-bandcount", "1"); - stageTeaserBandsPanel.add(new NoActionStageTeaserBand(event, placeNavigator)); + stageTeaserBandsPanel.getElement().appendChild(new UpcomingEventStageTeaserBand(event, placeNavigator).getElement()); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/UpcomingEventStageTeaserBand.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/UpcomingEventStageTeaserBand.java new file mode 100644 index 00000000000..8601f2f550d --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/UpcomingEventStageTeaserBand.java @@ -0,0 +1,31 @@ +package com.sap.sailing.gwt.home.client.shared.stage; + +import com.google.gwt.dom.client.Style.Display; +import com.sap.sailing.gwt.home.client.app.PlaceNavigator; +import com.sap.sailing.gwt.home.client.i18n.TextMessages; +import com.sap.sailing.gwt.home.client.shared.EventDatesFormatterUtil; +import com.sap.sailing.gwt.ui.shared.EventBaseDTO; + +/** + * Teaser band for an upcoming event on the homepage stage + * @author Frank + * + */ +public class UpcomingEventStageTeaserBand extends StageTeaserBand { + + public UpcomingEventStageTeaserBand(EventBaseDTO event, PlaceNavigator placeNavigator) { + super(event, placeNavigator); + + bandTitle.setInnerText(event.getName()); + bandSubtitle.setInnerText(EventDatesFormatterUtil.formatDateRangeWithYear(event.startDate, event.endDate)); + + actionLink.getStyle().setDisplay(Display.INLINE_BLOCK); + actionLink.setInnerText(TextMessages.INSTANCE.moreInfo()); + } + + @Override + public void actionLinkClicked() { + EventBaseDTO event = getEvent(); + getPlaceNavigator().goToEvent(event.id.toString(), event.getBaseURL(), event.isOnRemoteServer()); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/idangerous/Swiper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/idangerous/Swiper.java index ca3d60c539f..60fadfe80bf 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/idangerous/Swiper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/idangerous/Swiper.java @@ -31,7 +31,7 @@ public final class Swiper extends JavaScriptObject { var options = { loop: loop, wrapperClass: wrapperClass, - slideClass: slideClass + slideClass: slideClass, }; if (pageChangeListener) { options.onSlideChangeEnd = function(swiper) { @@ -43,6 +43,10 @@ public final class Swiper extends JavaScriptObject { return new $wnd.Swiper('.'+containerClass, options); }-*/; + public native void reInit() /*-{ + this.reInit(true); + }-*/; + public native void swipeNext() /*-{ this.swipeNext(); }-*/; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractLeaderboardConfigPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractLeaderboardConfigPanel.java index 94936ba80e1..c219551f79d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractLeaderboardConfigPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractLeaderboardConfigPanel.java @@ -1,6 +1,7 @@ package com.sap.sailing.gwt.ui.adminconsole; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import java.util.Map; @@ -8,6 +9,7 @@ import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.user.cellview.client.CellTable; +import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; @@ -37,6 +39,7 @@ import com.sap.sailing.gwt.ui.client.RegattaRefresher; import com.sap.sailing.gwt.ui.client.RegattasDisplayer; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; import com.sap.sse.common.Util; @@ -157,17 +160,14 @@ public abstract class AbstractLeaderboardConfigPanel extends FormPanel implement } }; leaderboardConfigControlsPanel.add(filterLeaderboardPanel); - addLeaderboardConfigControls(leaderboardConfigControlsPanel); - leaderboardsPanel.add(leaderboardConfigControlsPanel); leaderboardTable.ensureDebugId("AvailableLeaderboardsTable"); - - addColumnsToLeaderboardTable(leaderboardTable); - + addColumnsToLeaderboardTableAndSetSelectionModel(leaderboardTable, tableRes); + @SuppressWarnings("unchecked") + MultiSelectionModel multiSelectionModel = (MultiSelectionModel) leaderboardTable.getSelectionModel(); + leaderboardSelectionModel = multiSelectionModel; leaderboardTable.setWidth("100%"); - leaderboardSelectionModel = new MultiSelectionModel(); - leaderboardTable.setSelectionModel(leaderboardSelectionModel); leaderboardSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { public void onSelectionChange(SelectionChangeEvent event) { leaderboardSelectionChanged(); @@ -179,10 +179,7 @@ public abstract class AbstractLeaderboardConfigPanel extends FormPanel implement HorizontalPanel leaderboardButtonPanel = new HorizontalPanel(); leaderboardButtonPanel.setSpacing(5); leaderboardsPanel.add(leaderboardButtonPanel); - addLeaderboardCreateControls(leaderboardButtonPanel); - - mainPanel.add(new Grid(1, 1)); // caption panels for the selected leaderboard and tracked races @@ -257,9 +254,33 @@ public abstract class AbstractLeaderboardConfigPanel extends FormPanel implement protected abstract void addLeaderboardConfigControls(Panel configPanel); protected abstract void addLeaderboardCreateControls(Panel createPanel); protected abstract void addSelectedLeaderboardRacesControls(Panel racesPanel); - protected abstract void addColumnsToLeaderboardTable(CellTable leaderboardTable); + protected abstract void addColumnsToLeaderboardTableAndSetSelectionModel(CellTable leaderboardTable, AdminConsoleTableResources tableRes); protected abstract void addColumnsToRacesTable(CellTable racesTable); + protected SelectionCheckboxColumn createSortableSelectionCheckboxColumn( + final CellTable leaderboardTable, AdminConsoleTableResources tableResources, + ListHandler leaderboardColumnListHandler) { + SelectionCheckboxColumn selectionCheckboxColumn = new SelectionCheckboxColumn(tableResources.cellTableStyle().cellTableCheckboxSelected(), + tableResources.cellTableStyle().cellTableCheckboxDeselected(), tableResources.cellTableStyle().cellTableCheckboxColumnCell()) { + @Override + protected ListDataProvider getListDataProvider() { + return leaderboardList; + } + + @Override + public Boolean getValue(StrippedLeaderboardDTO row) { + return leaderboardTable.getSelectionModel().isSelected(row); + } + }; + selectionCheckboxColumn.setSortable(true); + leaderboardColumnListHandler.setComparator(selectionCheckboxColumn, new Comparator() { + @Override + public int compare(StrippedLeaderboardDTO o1, StrippedLeaderboardDTO o2) { + return (leaderboardTable.getSelectionModel().isSelected(o1) ? 1 : 0) - (leaderboardTable.getSelectionModel().isSelected(o2) ? 1 : 0); + } + }); + return selectionCheckboxColumn; + } @Override public void fillLeaderboards(Iterable leaderboards) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractTrackedRacesListComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractTrackedRacesListComposite.java index 6a288255e69..54681a79bc7 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractTrackedRacesListComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractTrackedRacesListComposite.java @@ -25,7 +25,6 @@ import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; import com.google.gwt.view.client.ListDataProvider; -import com.google.gwt.view.client.MultiSelectionModel; import com.google.gwt.view.client.SelectionChangeEvent; import com.google.gwt.view.client.SelectionChangeEvent.Handler; import com.google.gwt.view.client.SelectionModel; @@ -44,6 +43,7 @@ import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.Component; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent; +import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn; import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; @@ -59,6 +59,8 @@ public abstract class AbstractTrackedRacesListComposite extends SimplePanel impl private boolean dontFireNextSelectionChangeEvent; private final SelectionModel selectionModel; + + private final SelectionCheckboxColumn selectionCheckboxColumn; private final CellTable raceTable; @@ -98,9 +100,21 @@ public abstract class AbstractTrackedRacesListComposite extends SimplePanel impl this.multiSelection = hasMultiSelection; this.raceSelectionProvider = raceSelectionProvider; this.stringMessages = stringMessages; + AdminConsoleTableResources tableResources = GWT.create(AdminConsoleTableResources.class); + this.selectionCheckboxColumn = new SelectionCheckboxColumn(tableResources.cellTableStyle().cellTableCheckboxSelected(), + tableResources.cellTableStyle().cellTableCheckboxDeselected(), tableResources.cellTableStyle().cellTableCheckboxColumnCell()) { + @Override + protected ListDataProvider getListDataProvider() { + return raceList; + } + + @Override + public Boolean getValue(RaceDTO row) { + return raceTable.getSelectionModel().isSelected(row); + } + }; raceList = new ListDataProvider(); - selectionModel = multiSelection ? new MultiSelectionModel() : new SingleSelectionModel(); settings = new TrackedRacesSettings(); settings.setDelayToLiveInSeconds(DEFAULT_LIVE_DELAY_IN_MILLISECONDS / 1000l); @@ -123,7 +137,13 @@ public abstract class AbstractTrackedRacesListComposite extends SimplePanel impl raceTable.ensureDebugId("TrackedRacesCellTable"); ListHandler columnSortHandler = setupTableColumns(stringMessages); raceTable.setWidth("300px"); - raceTable.setSelectionModel(selectionModel); + if (multiSelection) { + selectionModel = this.selectionCheckboxColumn.getSelectionModel(); + raceTable.setSelectionModel(selectionModel, this.selectionCheckboxColumn.getSelectionManager()); + } else { + selectionModel = new SingleSelectionModel(); + raceTable.setSelectionModel(selectionModel); + } raceTable.setVisible(false); panel.add(raceTable); raceList.addDataDisplay(raceTable); @@ -341,7 +361,10 @@ public abstract class AbstractTrackedRacesListComposite extends SimplePanel impl return race.isTracked && race.trackedRace != null ? race.trackedRace.delayToLiveInMs : null; } }); - + if (multiSelection) { + columnSortHandler.setComparator(selectionCheckboxColumn, selectionCheckboxColumn.getComparator()); + raceTable.addColumn(selectionCheckboxColumn, selectionCheckboxColumn.getHeader()); + } raceTable.addColumn(regattaNameColumn, stringMessages.regatta()); raceTable.addColumn(boatClassNameColumn, stringMessages.boatClass()); raceTable.addColumn(raceNameColumn, stringMessages.race()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleTable.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleTable.css index 1cfb8080acb..1ae3a5988c9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleTable.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleTable.css @@ -131,3 +131,32 @@ .cellTableTotalColumn { background-color: #e0e0e0 !important; } + +.cellTableCheckboxColumnCell { + padding: 1px 1px 1px !important; + vertical-align: middle !important; + text-align: center; +} + +.cellTableCheckboxSelected { + margin-left: 0px; /* account for the selection border that pushes a selected checkbox to the side */ + margin-top: 0px; + margin-bottom: 0px; + margin-right: 2px; + height: 18px; + width: 18px; + border: solid 1px #acacac; + background-color: #cacaca; + vertical-align: middle; +} + +.cellTableCheckboxDeselected { + margin-left: 2px; /* account for the selection border that pushes a selected checkbox to the side */ + margin-top: 0px; + margin-bottom: 0px; + margin-right: 2px; + border: solid 1px #acacac; + height: 18px; + width: 18px; + vertical-align: middle; +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleTableResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleTableResources.java index 0f62b46ce26..2339dd34314 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleTableResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleTableResources.java @@ -39,6 +39,21 @@ public interface AdminConsoleTableResources extends CellTable.Resources { */ String cellTableTotalColumn(); + /** + * Applied to checkbox column + */ + String cellTableCheckboxColumnCell(); + + /** + * Applied to the checkbox div when the checkbox is selected + */ + String cellTableCheckboxSelected(); + + /** + * Applied to the checkbox div when the checkbox is deselected + */ + String cellTableCheckboxDeselected(); + } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventManagementPanel.java index 222ae308568..42a3094c02a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventManagementPanel.java @@ -387,7 +387,11 @@ public class EventManagementPanel extends SimplePanel implements EventsRefresher TextColumn leaderboardGroupDescriptionColumn = new TextColumn() { @Override public String getValue(LeaderboardGroupDTO event) { - return event.description; + String result = event.description; + if(event.description != null && event.description.length() > 40) { + result = event.description.substring(0, 40) + "..."; + } + return result; } }; final SafeHtmlCell leaderboardsCell = new SafeHtmlCell(); @@ -468,7 +472,11 @@ public class EventManagementPanel extends SimplePanel implements EventsRefresher TextColumn leaderboardGroupDescriptionColumn = new TextColumn() { @Override public String getValue(LeaderboardGroupDTO event) { - return event.description; + String result = event.description; + if(event.description != null && event.description.length() > 40) { + result = event.description.substring(0, 40) + "..."; + } + return result; } }; final SafeHtmlCell leaderboardsCell = new SafeHtmlCell(); @@ -824,8 +832,10 @@ public class EventManagementPanel extends SimplePanel implements EventsRefresher for (CourseAreaDTO courseAreaDTO : newEvent.venue.getCourseAreas()) { courseAreaNames.add(courseAreaDTO.getName()); } - sailingService.createEvent(newEvent.getName(), newEvent.startDate, newEvent.endDate, newEvent.venue.getName(), - newEvent.isPublic, courseAreaNames, newEvent.getImageURLs(), newEvent.getVideoURLs(), new AsyncCallback() { + sailingService.createEvent(newEvent.getName(), newEvent.getDescription(), newEvent.startDate, newEvent.endDate, + newEvent.venue.getName(), newEvent.isPublic, courseAreaNames, newEvent.getImageURLs(), + newEvent.getVideoURLs(), newEvent.getSponsorImageURLs(), newEvent.getLogoImageURL(), + newEvent.getOfficialWebsiteURL(), new AsyncCallback() { @Override public void onFailure(Throwable t) { errorReporter.reportError("Error trying to create new event " + newEvent.getName() + ": " + t.getMessage()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java index 18f29beb961..2208115f801 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java @@ -48,6 +48,7 @@ import com.sap.sailing.gwt.ui.client.RegattasDisplayer; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.URLEncoder; +import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardEntryPoint; import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter; import com.sap.sailing.gwt.ui.shared.EventDTO; @@ -113,10 +114,11 @@ TrackedRaceChangedListener, LeaderboardsDisplayer { } @Override - protected void addColumnsToLeaderboardTable(final CellTable leaderboardTable) { + protected void addColumnsToLeaderboardTableAndSetSelectionModel(final CellTable leaderboardTable, AdminConsoleTableResources tableResources) { ListHandler leaderboardColumnListHandler = new ListHandler( leaderboardList.getList()); - + SelectionCheckboxColumn selectionCheckboxColumn = createSortableSelectionCheckboxColumn( + leaderboardTable, tableResources, leaderboardColumnListHandler); AnchorCell anchorCell = new AnchorCell(); Column linkColumn = new Column(anchorCell) { @Override @@ -262,6 +264,7 @@ TrackedRaceChangedListener, LeaderboardsDisplayer { } } }); + leaderboardTable.addColumn(selectionCheckboxColumn, selectionCheckboxColumn.getHeader()); leaderboardTable.addColumn(linkColumn, stringMessages.name()); leaderboardTable.addColumn(leaderboardDisplayNameColumn, stringMessages.displayName()); leaderboardTable.addColumn(discardingOptionsColumn, stringMessages.discarding()); @@ -270,8 +273,9 @@ TrackedRaceChangedListener, LeaderboardsDisplayer { leaderboardTable.addColumn(courseAreaColumn, stringMessages.courseArea()); leaderboardTable.addColumn(leaderboardActionColumn, stringMessages.actions()); leaderboardTable.addColumnSortHandler(leaderboardColumnListHandler); + leaderboardTable.setSelectionModel(selectionCheckboxColumn.getSelectionModel(), selectionCheckboxColumn.getSelectionManager()); } - + protected void addLeaderboardCreateControls(Panel createPanel) { Button createFlexibleLeaderboardBtn = new Button(stringMessages.createFlexibleLeaderboard() + "..."); createFlexibleLeaderboardBtn.ensureDebugId("CreateFlexibleLeaderboardButton"); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigPanel.java index 7ad46e47426..4e8c4d9798f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigPanel.java @@ -492,6 +492,13 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements return group.description.length() <= 100 ? group.description : group.description.substring(0, 98) + "..."; } }; + TextColumn groupDisplayNameColumn = new TextColumn() { + @Override + public String getValue(LeaderboardGroupDTO group) { + return group.getDisplayName() == null ? "" : + group.getDisplayName().length() <= 100 ? group.getDisplayName() : group.getDisplayName().substring(0, 98) + "..."; + } + }; TextColumn hasOverallLeaderboardColumn = new TextColumn() { @Override public String getValue(LeaderboardGroupDTO group) { @@ -533,6 +540,7 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements groupsTable.setWidth("100%"); groupsTable.addColumn(groupNameColumn, stringMessages.name()); groupsTable.addColumn(groupDescriptionColumn, stringMessages.description()); + groupsTable.addColumn(groupDisplayNameColumn, stringMessages.displayName()); groupsTable.addColumn(hasOverallLeaderboardColumn, stringMessages.useOverallLeaderboard()); groupsTable.addColumn(groupActionsColumn, stringMessages.actions()); groupsTable.addColumnSortHandler(leaderboardGroupsListHandler); @@ -629,8 +637,8 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements private void createNewGroup(final LeaderboardGroupDescriptor newGroup) { sailingService.createLeaderboardGroup(newGroup.getName(), newGroup.getDescription(), - newGroup.isDisplayLeaderboardsInReverseOrder(), newGroup.getOverallLeaderboardDiscardThresholds(), - newGroup.getOverallLeaderboardScoringSchemeType(), new MarkedAsyncCallback( + newGroup.getDisplayName(), newGroup.isDisplayLeaderboardsInReverseOrder(), + newGroup.getOverallLeaderboardDiscardThresholds(), newGroup.getOverallLeaderboardScoringSchemeType(), new MarkedAsyncCallback( new AsyncCallback() { @Override public void onFailure(Throwable t) { @@ -654,6 +662,7 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements leaderboardNames.add(leaderboardDTO.name); } sailingService.updateLeaderboardGroup(oldGroupName, updateDescriptor.getName(), updateDescriptor.getDescription(), + updateDescriptor.getDisplayName(), leaderboardNames, updateDescriptor.getOverallLeaderboardDiscardThresholds(), updateDescriptor.getOverallLeaderboardScoringSchemeType(), new MarkedAsyncCallback( new AsyncCallback() { @@ -670,6 +679,7 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements if (oldGroupName.equals(group.getName())) { groupToUpdate.setName(updateDescriptor.getName()); groupToUpdate.description = updateDescriptor.getDescription(); + groupToUpdate.setDisplayName(updateDescriptor.getDisplayName()); groupToUpdate.displayLeaderboardsInReverseOrder = updateDescriptor.isDisplayLeaderboardsInReverseOrder(); groupToUpdate.setOverallLeaderboardDiscardThresholds(updateDescriptor.getOverallLeaderboardDiscardThresholds()); groupToUpdate.setOverallLeaderboardScoringSchemeType(updateDescriptor.getOverallLeaderboardScoringSchemeType()); @@ -696,6 +706,7 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements leaderboardNames.add(leaderboardDTO.name); } sailingService.updateLeaderboardGroup(group.getName(), group.getName(), group.description, + group.getDisplayName(), leaderboardNames, group.getOverallLeaderboardDiscardThresholds(), group.getOverallLeaderboardScoringSchemeType(), new MarkedAsyncCallback( new AsyncCallback() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupCreateDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupCreateDialog.java index 713af2506dd..e013b6fd83c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupCreateDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupCreateDialog.java @@ -9,7 +9,7 @@ public class LeaderboardGroupCreateDialog extends LeaderboardGroupDialog { public LeaderboardGroupCreateDialog(Collection existingGroups, StringMessages stringMessages, DialogCallback callback) { - super(new LeaderboardGroupDTO(/* ID */ null), stringMessages, + super(new LeaderboardGroupDTO(/* ID */ null, /* displayName */ null), stringMessages, callback, existingGroups); nameEntryField = createTextBox(null, 50); nameEntryField.ensureDebugId("NameTextBox"); @@ -17,6 +17,9 @@ public class LeaderboardGroupCreateDialog extends LeaderboardGroupDialog { descriptionEntryField = createTextArea(null); descriptionEntryField.ensureDebugId("DescriptionTextArea"); + displayNameEntryField = createTextBox(""); + displayNameEntryField.ensureDebugId("DisplayNameTextArea"); + displayLeaderboardsInReverseOrderCheckBox.setValue(false); useOverallLeaderboardCheckBox.setValue(false); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupDialog.java index 30874d31cea..c9ffaffecd4 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupDialog.java @@ -27,6 +27,7 @@ public class LeaderboardGroupDialog extends DataEntryDialog() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupEditDialog.java index 276cf48d100..424c5495a66 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupEditDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupEditDialog.java @@ -13,6 +13,7 @@ public class LeaderboardGroupEditDialog extends LeaderboardGroupDialog { getOverallLeaderboardScoringSchemeListBox().setEnabled(!group.hasOverallLeaderboard()); nameEntryField = createTextBox(group.getName(), 50); descriptionEntryField = createTextArea(group.description); + displayNameEntryField = createTextBox(group.getDisplayName()); displayLeaderboardsInReverseOrderCheckBox.setValue(group.displayLeaderboardsInReverseOrder); displayLeaderboardsInReverseOrderCheckBox.setEnabled(false); useOverallLeaderboardCheckBox.setValue(group.hasOverallLeaderboard()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaPanel.java index 5de53e1016d..21708c2f7e2 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaPanel.java @@ -6,6 +6,7 @@ import java.util.Date; import com.google.gwt.cell.client.EditTextCell; 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.Style.Unit; import com.google.gwt.event.dom.client.ClickEvent; @@ -136,6 +137,22 @@ public class MediaPanel extends FlowPanel { // cellTable.addColumn(checkColumn, SafeHtmlUtils.fromSafeConstant("
")); // cellTable.setColumnWidth(checkColumn, 40, Unit.PX); + // db id + Column dbIdColumn = new Column(new TextCell()) { + @Override + public String getValue(MediaTrack mediaTrack) { + return mediaTrack.dbId; + } + }; + dbIdColumn.setSortable(true); + sortHandler.setComparator(dbIdColumn, new Comparator() { + public int compare(MediaTrack mediaTrack1, MediaTrack mediaTrack2) { + return mediaTrack1.dbId.compareTo(mediaTrack2.dbId); + } + }); + mediaTracksTable.addColumn(dbIdColumn, stringMessages.id()); + mediaTracksTable.setColumnWidth(dbIdColumn, 10, Unit.PCT); + // media title Column titleColumn = new Column(new EditTextCell()) { @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceColumnsInLeaderboardDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceColumnsInLeaderboardDialog.java index 28bc2af0037..478e501b6cc 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceColumnsInLeaderboardDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceColumnsInLeaderboardDialog.java @@ -110,7 +110,7 @@ public class RaceColumnsInLeaderboardDialog extends DataEntryDialog leaderboardTable) { + protected void addColumnsToLeaderboardTableAndSetSelectionModel(CellTable leaderboardTable, AdminConsoleTableResources tableResources) { ListHandler leaderboardColumnListHandler = new ListHandler( leaderboardList.getList()); - + SelectionCheckboxColumn selectionCheckboxColumn = createSortableSelectionCheckboxColumn( + leaderboardTable, tableResources, leaderboardColumnListHandler); TextColumn leaderboardNameColumn = new TextColumn() { @Override public String getValue(StrippedLeaderboardDTO leaderboard) { return leaderboard.name; } }; - TextColumn leaderboardDisplayNameColumn = new TextColumn() { @Override public String getValue(StrippedLeaderboardDTO leaderboard) { return leaderboard.getDisplayName() != null ? leaderboard.getDisplayName() : ""; } }; - ImagesBarColumn leaderboardActionColumn = new ImagesBarColumn( new RaceLogTrackingEventManagementImagesBarCell(stringMessages)); @@ -91,11 +91,13 @@ public class RaceLogTrackingEventManagementPanel extends AbstractLeaderboardConf } } }); - + leaderboardTable.addColumn(selectionCheckboxColumn, selectionCheckboxColumn.getHeader()); leaderboardTable.addColumn(leaderboardNameColumn, stringMessages.name()); leaderboardTable.addColumn(leaderboardDisplayNameColumn, stringMessages.displayName()); leaderboardTable.addColumn(leaderboardActionColumn, stringMessages.actions()); leaderboardTable.addColumnSortHandler(leaderboardColumnListHandler); + leaderboardTable.setSelectionModel(new MultiSelectionModel()); + leaderboardTable.setSelectionModel(selectionCheckboxColumn.getSelectionModel(), selectionCheckboxColumn.getSelectionManager()); } private RaceLogTrackingState getTrackingState(RaceColumnDTOAndFleetDTOWithNameBasedEquality race) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java index a3bb7970e8a..5d37866f5ea 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java @@ -25,7 +25,9 @@ import com.google.gwt.user.client.ui.TabPanel; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.LeaderboardNameConstants; import com.sap.sailing.domain.common.dto.RaceColumnDTO; +import com.sap.sailing.domain.common.dto.RaceColumnInSeriesDTO; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.controls.listedit.StringListEditorComposite; import com.sap.sailing.gwt.ui.client.shared.controls.listedit.StringListInlineEditorComposite; @@ -127,7 +129,7 @@ public class SeriesEditDialog extends DataEntryDialog { for (String name : raceNamesEditor.getValue()) { RaceColumnDTO raceColumnDTO = findRaceColumnInSeriesByName(selectedSeries, name); if (raceColumnDTO == null) { - raceColumnDTO = new RaceColumnDTO(/* isValidInTotalScore not relevant here; not in scope of a leaderboard */ null); + raceColumnDTO = new RaceColumnInSeriesDTO(selectedSeries.getName(), regatta.getName()); raceColumnDTO.setName(name); } races.add(raceColumnDTO); @@ -371,7 +373,7 @@ public class SeriesEditDialog extends DataEntryDialog { addRacesPanel.add(new Label(stringMessages.withNamePrefix())); raceNamePrefixTextBox.setWidth("20px"); - if ("Default".equals(seriesName)) { + if (LeaderboardNameConstants.DEFAULT_SERIES_NAME.equals(seriesName)) { raceNamePrefixTextBox.setText("R"); } else { raceNamePrefixTextBox.setText(seriesName.substring(0, 1).toUpperCase()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesWithFleetsCreateDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesWithFleetsCreateDialog.java index 3de52465f11..d134afa3d44 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesWithFleetsCreateDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesWithFleetsCreateDialog.java @@ -16,6 +16,7 @@ import com.google.gwt.user.client.ui.TabPanel; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.LeaderboardNameConstants; import com.sap.sailing.domain.common.dto.FleetDTO; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.controls.listedit.ListEditorComposite; @@ -133,7 +134,7 @@ public class SeriesWithFleetsCreateDialog extends DataEntryDialog { widget.ensureDebugId("DiscardThresholdBoxes"); widget.setVisible(false); - fleetListComposite = new FleetListEditorComposite(Arrays.asList(new FleetDTO("Default", 0, null)), stringMessages, resources.removeIcon()); + fleetListComposite = new FleetListEditorComposite(Arrays.asList(new FleetDTO(LeaderboardNameConstants.DEFAULT_FLEET_NAME, 0, null)), stringMessages, resources.removeIcon()); fleetListComposite.ensureDebugId("FleetListEditorComposite"); fleetListComposite.addValueChangeHandler(new ValueChangeHandler>() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java index 95e44038906..b95ceb4b380 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java @@ -32,7 +32,6 @@ import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; import com.google.gwt.view.client.ListDataProvider; -import com.google.gwt.view.client.MultiSelectionModel; import com.google.gwt.view.client.SelectionModel; import com.sap.sailing.domain.common.RegattaIdentifier; import com.sap.sailing.domain.common.RegattaName; @@ -42,6 +41,7 @@ import com.sap.sailing.gwt.ui.client.RaceSelectionModel; import com.sap.sailing.gwt.ui.client.RegattaRefresher; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.TracTracConfigurationDTO; import com.sap.sailing.gwt.ui.shared.TracTracRaceRecordDTO; @@ -346,14 +346,27 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { }; raceStatusColumn.setSortable(true); + SelectionCheckboxColumn selectionCheckboxColumn = new SelectionCheckboxColumn(tableResources.cellTableStyle().cellTableCheckboxSelected(), + tableResources.cellTableStyle().cellTableCheckboxDeselected(), tableResources.cellTableStyle().cellTableCheckboxColumnCell()) { + @Override + public Boolean getValue(TracTracRaceRecordDTO row) { + return racesTable.getSelectionModel().isSelected(row); + } + + @Override + protected ListDataProvider getListDataProvider() { + return raceList; + } + }; + racesTable.addColumn(selectionCheckboxColumn, selectionCheckboxColumn.getHeader()); racesTable.addColumn(regattaNameColumn, stringMessages.event()); racesTable.addColumn(raceNameColumn, stringMessages.race()); racesTable.addColumn(boatClassColumn, stringMessages.boatClass()); racesTable.addColumn(raceStartTrackingColumn, stringMessages.startTime()); racesTable.addColumn(raceStatusColumn, stringMessages.raceStatusColumn()); - racesTable.addColumnSortHandler(getRaceTableColumnSortHandler(this.raceList.getList(), raceNameColumn, - boatClassColumn, raceStartTrackingColumn, raceStatusColumn)); - racesTable.setSelectionModel(new MultiSelectionModel()); + racesTable.addColumnSortHandler(getRaceTableColumnSortHandler(selectionCheckboxColumn, this.raceList.getList(), + raceNameColumn, boatClassColumn, raceStartTrackingColumn, raceStatusColumn)); + racesTable.setSelectionModel(selectionCheckboxColumn.getSelectionModel(), selectionCheckboxColumn.getSelectionManager()); racesTable.setWidth("100%"); raceList.addDataDisplay(racesTable); @@ -392,10 +405,11 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { this.errorReporter.reportError(message); } - private ListHandler getRaceTableColumnSortHandler(List raceRecords, - Column nameColumn, Column boatClassColumn, - Column trackingStartColumn, Column raceStatusColumn) { + private ListHandler getRaceTableColumnSortHandler(SelectionCheckboxColumn selectionCheckboxColumn, + List raceRecords, Column nameColumn, + Column boatClassColumn, Column trackingStartColumn, Column raceStatusColumn) { ListHandler result = new ListHandler(raceRecords); + result.setComparator(selectionCheckboxColumn, selectionCheckboxColumn.getComparator()); result.setComparator(nameColumn, new Comparator() { @Override public int compare(TracTracRaceRecordDTO o1, TracTracRaceRecordDTO o2) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesListComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesListComposite.java index dd14590c951..e7568b83637 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesListComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesListComposite.java @@ -178,5 +178,10 @@ public class TrackedRacesListComposite extends AbstractTrackedRacesListComposite } } + @Override + public String getDependentCssClassName() { + return "trackedRacesListComposite"; + } + } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/ClientResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/ClientResources.java index 60f869badde..d3718bcf024 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/ClientResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/ClientResources.java @@ -34,4 +34,8 @@ public interface ClientResources extends ClientBundle { @Source("com/sap/sailing/gwt/ui/client/images/timeslider-playstate-replay-inactive.png") ImageResource timesliderPlayStateReplayInactiveIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/sap-logo-overlay.png") + ImageResource sapLogoOverlay(); + } \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/CompetitorSelectionChangeListener.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/CompetitorSelectionChangeListener.java index 780346b9da5..026452f3229 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/CompetitorSelectionChangeListener.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/CompetitorSelectionChangeListener.java @@ -1,6 +1,8 @@ package com.sap.sailing.gwt.ui.client; import com.sap.sailing.domain.common.dto.CompetitorDTO; +import com.sap.sse.common.filter.Filter; +import com.sap.sse.common.filter.FilterSet; /** @@ -9,6 +11,18 @@ import com.sap.sailing.domain.common.dto.CompetitorDTO; public interface CompetitorSelectionChangeListener { void competitorsListChanged(Iterable competitors); + + /** + * Notifies listeners of a change of the competitor filter set. Note that if the list of filtered competitors has + * changed, a separate call to filteredCompetitorsListChanged will occur, and that this does not necessarily have to + * be the case, e.g., if old and new filter match the same set of competitors. + */ + void filterChanged(FilterSet> oldFilterSet, FilterSet> newFilterSet); + + /** + * Notifies listeners that the list of filtered competitors has changed. Possible reasons include a change of the + * filter condition or a change in the overall list of competitors. + */ void filteredCompetitorsListChanged(Iterable filteredCompetitors); void addedToSelection(CompetitorDTO competitor); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/CompetitorSelectionModel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/CompetitorSelectionModel.java index 819d0e29e57..5b43f31e1c1 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/CompetitorSelectionModel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/CompetitorSelectionModel.java @@ -12,6 +12,7 @@ import com.sap.sailing.domain.common.Color; import com.sap.sailing.domain.common.ColorMap; import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.domain.common.impl.ColorMapImpl; +import com.sap.sse.common.Util; import com.sap.sse.common.filter.Filter; import com.sap.sse.common.filter.FilterSet; @@ -26,13 +27,13 @@ public class CompetitorSelectionModel implements CompetitorSelectionProvider { private final ColorMap competitorsColorMap; - private FilterSet> competitorsFilterSet; + private FilterSet> competitorsFilterSet; public CompetitorSelectionModel(boolean hasMultiSelection) { this(hasMultiSelection, null); } - public CompetitorSelectionModel(boolean hasMultiSelection, FilterSet> competitorsFilterSet) { + private CompetitorSelectionModel(boolean hasMultiSelection, FilterSet> competitorsFilterSet) { super(); this.hasMultiSelection = hasMultiSelection; this.competitorsFilterSet = competitorsFilterSet; @@ -112,6 +113,13 @@ public class CompetitorSelectionModel implements CompetitorSelectionProvider { return Collections.unmodifiableCollection(selectedCompetitors); } + @Override + public Iterable getSelectedFilteredCompetitors() { + Set result = new HashSet<>(selectedCompetitors); + result.retainAll(getFilteredCompetitors()); + return result; + } + @Override public Iterable getAllCompetitors() { return Collections.unmodifiableCollection(allCompetitors); @@ -237,15 +245,54 @@ public class CompetitorSelectionModel implements CompetitorSelectionProvider { } @Override - public FilterSet> getCompetitorsFilterSet() { + public FilterSet> getCompetitorsFilterSet() { return competitorsFilterSet; } + + @Override + public FilterSet> getOrCreateCompetitorsFilterSet(String nameToAssignToNewFilterSet) { + if (competitorsFilterSet == null) { + competitorsFilterSet = new FilterSet>(nameToAssignToNewFilterSet); + } + return getCompetitorsFilterSet(); + } @Override - public void setCompetitorsFilterSet(FilterSet> competitorsFilterSet) { + public void setCompetitorsFilterSet(FilterSet> competitorsFilterSet) { + FilterSet> oldFilterSet = this.competitorsFilterSet; this.competitorsFilterSet = competitorsFilterSet; + if (!Util.equalsWithNull(competitorsFilterSet, oldFilterSet)) { + for (CompetitorSelectionChangeListener listener : listeners) { + listener.filterChanged(oldFilterSet, competitorsFilterSet); + } + } for (CompetitorSelectionChangeListener listener : listeners) { listener.filteredCompetitorsListChanged(getFilteredCompetitors()); } } + + @Override + public boolean hasActiveFilters() { + return (competitorsFilterSet != null && !competitorsFilterSet.getFilters().isEmpty() + && getFilteredCompetitors().size() != allCompetitors.size()); + } + + @Override + public void clearAllFilters() { + if (hasActiveFilters()) { + Iterator selIter = getSelectedCompetitors().iterator(); + while (selIter.hasNext()) { + CompetitorDTO selected = selIter.next(); + setSelected(selected, false); + selIter = getSelectedCompetitors().iterator(); + } + competitorsFilterSet = null; + fireListChanged(getAllCompetitors()); + } + } + + @Override + public int getFilteredCompetitorsListSize() { + return getFilteredCompetitors().size(); + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/CompetitorSelectionProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/CompetitorSelectionProvider.java index 25c450a57e9..d3566407a78 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/CompetitorSelectionProvider.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/CompetitorSelectionProvider.java @@ -14,6 +14,11 @@ public interface CompetitorSelectionProvider { */ Iterable getSelectedCompetitors(); + /** + * The intersection of {@link #getSelectedCompetitors()} and {@link #getFilteredCompetitors()} + */ + Iterable getSelectedFilteredCompetitors(); + /** * @return a non-null sequence of all competitors which may be empty. */ @@ -61,7 +66,24 @@ public interface CompetitorSelectionProvider { void removeCompetitorSelectionChangeListener(CompetitorSelectionChangeListener listener); - public FilterSet> getCompetitorsFilterSet(); + public FilterSet> getCompetitorsFilterSet(); - public void setCompetitorsFilterSet(FilterSet> competitorsFilterSet); + public void setCompetitorsFilterSet(FilterSet> competitorsFilterSet); + + FilterSet> getOrCreateCompetitorsFilterSet(String nameToAssignToNewFilterSet); + + /** + * Returns true if the provider has any filters that will restrain the selection. + */ + public boolean hasActiveFilters(); + + /** + * Removes all filters and notifies listeners about the change. + */ + public void clearAllFilters(); + + /** + * @return the size of all filtered competitors + */ + public int getFilteredCompetitorsListSize(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/EntryPointLinkFactory.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/EntryPointLinkFactory.java index f698296d310..71e5d9c436e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/EntryPointLinkFactory.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/EntryPointLinkFactory.java @@ -17,25 +17,34 @@ public class EntryPointLinkFactory { public static String createLeaderboardGroupLink(Map parameters) { return createEntryPointLink("/gwt/Spectator.html", parameters); } + + public static String createEventLink(Map parameters, String eventId) { + return createEntryPointLink("/gwt/Home.html#EventPlace:eventId="+eventId, parameters); + } private static String createEntryPointLink(String baseLink, Map parameters) { String debugParam = Window.Location.getParameter("gwt.codesvr"); String localeParam = Window.Location.getParameter("locale"); - String link = baseLink; + String[] documentAndFragment = baseLink.split("#", 2); + StringBuilder link = new StringBuilder(documentAndFragment[0]); int i = 1; for(Entry entry: parameters.entrySet()) { - link += i == 1 ? "?" : "&"; - link += entry.getKey() + "=" + entry.getValue(); + link.append(i == 1 ? "?" : "&"); + link.append(entry.getKey() + "=" + entry.getValue()); i++; } if (debugParam != null && !debugParam.isEmpty()) { - link += i == 1 ? "?" : "&"; - link += "gwt.codesvr=" + debugParam; + link.append(i == 1 ? "?" : "&"); + link.append("gwt.codesvr=" + debugParam); } if (localeParam != null && !localeParam.isEmpty()) { - link += i == 1 ? "?" : "&"; - link += "locale=" + localeParam; + link.append(i == 1 ? "?" : "&"); + link.append("locale=" + localeParam); } - return URLEncoder.encode(link); + if (documentAndFragment.length > 1) { + link.append('#'); + link.append(documentAndFragment[1]); // append the fragment following the "#" again + } + return URLEncoder.encode(link.toString()); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/GlobalNavigationPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/GlobalNavigationPanel.java index 0eee9e29353..e8a44c39520 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/GlobalNavigationPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/GlobalNavigationPanel.java @@ -8,6 +8,7 @@ import com.google.gwt.safehtml.client.SafeHtmlTemplates; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HTML; +import com.sap.sailing.gwt.ui.shared.EventDTO; public class GlobalNavigationPanel extends FlowPanel { @@ -17,20 +18,37 @@ public class GlobalNavigationPanel extends FlowPanel { } public static final AnchorTemplates ANCHORTEMPLATE = GWT.create(AnchorTemplates.class); - public static final String STYLE_NAME_PREFIX = "globalNavigation-"; + + private final String styleName; - public GlobalNavigationPanel(StringMessages stringMessages, boolean showHomeNavigation, String leaderboardName, String leaderboardGroupName) { + /** + * @param event an optional event; may be null. + * @param styleNamePrefix an optional prefix for the style name; may be null - if not null the prefix + * is prepended to the style settings. + */ + public GlobalNavigationPanel(StringMessages stringMessages, boolean showHomeNavigation, String leaderboardName, + String leaderboardGroupName, EventDTO event, String styleNamePrefix) { super(); - setStyleName("globalNavigation"); - if (showHomeNavigation) { - if (leaderboardGroupName != null && !leaderboardGroupName.isEmpty()) { - Map leaderboardGroupLinkParameters = new HashMap(); - leaderboardGroupLinkParameters.put("showRaceDetails", "true"); - leaderboardGroupLinkParameters.put("leaderboardGroupName", leaderboardGroupName); - String leaderBoardGroupLink = EntryPointLinkFactory.createLeaderboardGroupLink(leaderboardGroupLinkParameters); - addNavigationLink(leaderboardGroupName, leaderBoardGroupLink, "leaderBoardGroup", "Go to the Event overview."); - } else { - addNavigationLink(stringMessages.home(), "/", "home", "Go to the Event overview."); + if (styleNamePrefix != null && !styleNamePrefix.equalsIgnoreCase("")) { + this.styleName = styleNamePrefix+"-globalNavigation"; + } else { + this.styleName = "globalNavigation"; + } + setStyleName(this.styleName); + if (event != null && event.getName() != null) { + String eventLink = EntryPointLinkFactory.createEventLink(new HashMap(), event.id.toString()); + addNavigationLink(event.getName(), eventLink, "event", stringMessages.goToEventOverview()); + } else { + if (showHomeNavigation) { + if (leaderboardGroupName != null && !leaderboardGroupName.isEmpty()) { + Map leaderboardGroupLinkParameters = new HashMap(); + leaderboardGroupLinkParameters.put("showRaceDetails", "true"); + leaderboardGroupLinkParameters.put("leaderboardGroupName", leaderboardGroupName); + String leaderBoardGroupLink = EntryPointLinkFactory.createLeaderboardGroupLink(leaderboardGroupLinkParameters); + addNavigationLink(leaderboardGroupName, leaderBoardGroupLink, "leaderBoardGroup", stringMessages.goToEventOverview()); + } else { + addNavigationLink(stringMessages.home(), "/", "home", stringMessages.goToEventOverview()); + } } } @@ -42,15 +60,15 @@ public class GlobalNavigationPanel extends FlowPanel { leaderboardLinkParameters.put("leaderboardGroupName", leaderboardGroupName); } String leaderBoardLink = EntryPointLinkFactory.createLeaderboardLink(leaderboardLinkParameters); - addNavigationLink(leaderboardName, leaderBoardLink, "leaderBoard", "Go to the overview and see all Races in one Leaderboard"); + addNavigationLink(leaderboardName, leaderBoardLink, "leaderBoard", stringMessages.goToOverviewAndSeeLeaderboard()); } } private void addNavigationLink(String linkName, String linkUrl, String styleNameExtension, String htmlTitle) { String setHtmlTitle = htmlTitle; HTML linkHtml = new HTML(ANCHORTEMPLATE.anchor(linkUrl, linkName)); - linkHtml.addStyleName("globalNavigationLink"); - linkHtml.addStyleName(STYLE_NAME_PREFIX + styleNameExtension); + linkHtml.addStyleName(styleName+"-"+"Link"); + linkHtml.addStyleName(styleName+"-"+styleNameExtension); linkHtml.setTitle(setHtmlTitle); add(linkHtml); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/LeaderboardUpdateListener.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/LeaderboardUpdateListener.java new file mode 100644 index 00000000000..271a722658a --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/LeaderboardUpdateListener.java @@ -0,0 +1,30 @@ +package com.sap.sailing.gwt.ui.client; + +import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.dto.LeaderboardDTO; +import com.sap.sailing.domain.common.dto.RaceColumnDTO; +import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel; +import com.sap.sailing.gwt.ui.raceboard.RaceBoardPanel; + +/** + * Listener interface that gets information about refresh of leaderboard. + * Currently implemented by {@link RaceBoardPanel} and triggered by + * {@link LeaderboardPanel}. + * + * @author Simon Marcel Pamies + */ +public interface LeaderboardUpdateListener { + + /** + * Called whenever the leaderboard has been updated. + * @param leaderboard + */ + void updatedLeaderboard(LeaderboardDTO leaderboard); + + /** + * Called whenever a current race for the leaderboard could + * be determined. One can not rely on this method being called + * as a leaderboard could contain more than one race. + */ + void currentRaceSelected(RaceIdentifier raceIdentifier, RaceColumnDTO raceColumn); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingClientFactory.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingClientFactory.java index 33bcbb9cee4..a7cc4f633f4 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingClientFactory.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingClientFactory.java @@ -1,6 +1,7 @@ package com.sap.sailing.gwt.ui.client; import com.sap.sse.gwt.client.mvp.ClientFactory; +import com.sap.sse.gwt.client.mvp.ErrorView; /** * A client factory that can deliver the sailing-specific services. @@ -10,4 +11,6 @@ import com.sap.sse.gwt.client.mvp.ClientFactory; */ public interface SailingClientFactory extends ClientFactory { SailingServiceAsync getSailingService(); + + ErrorView createErrorView(String errorMessage, Throwable errorReason); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java index c3f7b57dbca..edf81cfb7ad 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java @@ -258,12 +258,12 @@ public interface SailingService extends RemoteService { void removeLeaderboardGroups(Set groupNames); - LeaderboardGroupDTO createLeaderboardGroup(String groupName, String description, + LeaderboardGroupDTO createLeaderboardGroup(String groupName, String description, String displayName, boolean displayGroupsInReverseOrder, int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType); - void updateLeaderboardGroup(String oldName, String newName, String description, List leaderboardNames, - int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType); + void updateLeaderboardGroup(String oldName, String newName, String description, String newDisplayName, + List leaderboardNames, int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType); CompetitorsRaceDataDTO getCompetitorsRaceData(RegattaAndRaceIdentifier race, List competitors, Date from, Date to, long stepSizeInMs, DetailType detailType, String leaderboardGroupName, String leaderboardName) throws NoWindException; @@ -285,8 +285,9 @@ public interface SailingService extends RemoteService { String logoImageURL, Iterable imageURLs, Iterable videoURLs, Iterable sponsorImageURLs) throws Exception; - EventDTO createEvent(String eventName, Date startDate, Date endDate, String description, boolean isPublic, - List courseAreaNames, Iterable imageURLs, Iterable videoURLs) throws Exception; + EventDTO createEvent(String eventName, String eventDescription, Date startDate, Date endDate, String venue, + boolean isPublic, List courseAreaNames, Iterable imageURLs, + Iterable videoURLs, Iterable sponsorImageURLs, String logoImageURL, String officialWebsiteURL) throws Exception; void removeEvent(UUID eventId); @@ -294,9 +295,7 @@ public interface SailingService extends RemoteService { void renameEvent(UUID eventId, String newName); - EventDTO getEventByName(String eventName); - - EventDTO getEventById(UUID id); + EventDTO getEventById(UUID id, boolean withStatisticalData); Iterable getScoreCorrectionProviderNames(); @@ -354,12 +353,16 @@ public interface SailingService extends RemoteService { CourseAreaDTO createCourseArea(UUID eventId, String courseAreaName); - List> getLeaderboardsNamesOfMetaleaderboard(String metaLeaderboardName); + List> getLeaderboardsNamesOfMetaLeaderboard(String metaLeaderboardName); Util.Pair checkLeaderboardName(String leaderboardName); + /** for backward compatibility with the regatta overview */ List getRegattaStructureForEvent(UUID eventId); - + + /** the replacement service for getRegattaStructureForEvent() */ + List getRegattaStructureOfEvent(UUID eventId); + List getRaceStateEntriesForRaceGroup(UUID eventId, List visibleCourseAreas, List visibleRegattas, boolean showOnlyCurrentlyRunningRaces, boolean showOnlyRacesOfSameDay) throws Exception; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java index af91d6b9132..87ec7703baf 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java @@ -303,22 +303,16 @@ public interface SailingServiceAsync { /** * Creates a new group with the name groupname, the description description and an empty list of leaderboards.
+ * @param displayName TODO * @param displayGroupsInReverseOrder TODO */ void createLeaderboardGroup(String groupName, String description, - boolean displayGroupsInReverseOrder, int[] overallLeaderboardDiscardThresholds, - ScoringSchemeType overallLeaderboardScoringSchemeType, AsyncCallback callback); + String displayName, boolean displayGroupsInReverseOrder, + int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType, AsyncCallback callback); - /** - * Updates the data of the group with the name oldName. - * - * @param oldName The old name of the group - * @param newName The new name of the group - * @param description The new description of the group - * @param leaderboardNames The list of names of the new leaderboards of the group - */ - void updateLeaderboardGroup(String oldName, String newName, String description, - List leaderboardNames, int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType, AsyncCallback callback); + void updateLeaderboardGroup(String oldName, String newName, String description, String newDisplayName, + List leaderboardNames, int[] overallLeaderboardDiscardThresholds, + ScoringSchemeType overallLeaderboardScoringSchemeType, AsyncCallback callback); void setRaceIsKnownToStartUpwind(RegattaAndRaceIdentifier raceIdentifier, boolean raceIsKnownToStartUpwind, @@ -327,6 +321,11 @@ public interface SailingServiceAsync { void setWindSourcesToExclude(RegattaAndRaceIdentifier raceIdentifier, Iterable windSourcesToExclude, AsyncCallback callback); + /** + * @param date + * use null to indicate "live" in which case the server live time stamp for the race + * identified by raceIdentifier will be used, considering that race's delay. + */ void getRaceMapData(RegattaAndRaceIdentifier raceIdentifier, Date date, Map fromPerCompetitorIdAsString, Map toPerCompetitorIdAsString, boolean extrapolate, AsyncCallback callback); @@ -339,13 +338,6 @@ public interface SailingServiceAsync { void getPublicEventsOfAllSailingServers(AsyncCallback> callback); - /** - * Creates a {@link EventDTO} for the {@link com.sap.sailing.domain.base.Event} with the name eventName, which contains the - * name, the description and a list with {@link RegattaDTO RegattaDTOs} contained in the event.
- * If no event with the name eventName is known, an {@link IllegalArgumentException} is thrown. - */ - void getEventByName(String eventName, AsyncCallback callback); - /** * Renames the event with the name oldName to the newName.
* If there's no event with the name oldName or there's already a event with the name @@ -360,9 +352,9 @@ public interface SailingServiceAsync { void removeEvents(Collection eventIds, AsyncCallback asyncCallback); - void createEvent(String eventName, Date startDate, Date endDate, String description, boolean isPublic, + void createEvent(String eventName, String eventDescription, Date startDate, Date endDate, String venue, boolean isPublic, List courseAreaNames, Iterable imageURLs, Iterable videoURLs, - AsyncCallback callback); + Iterable sponsorImageURLs, String logoImageURL, String officialWebsiteURL, AsyncCallback callback); void updateEvent(UUID eventId, String eventName, String eventDescription, Date startDate, Date endDate, VenueDTO venue, boolean isPublic, Iterable leaderboardGroupIds, String officialWebsiteURL, @@ -477,7 +469,7 @@ public interface SailingServiceAsync { void getLeaderboardDataEntriesForAllRaceColumns(String leaderboardName, Date date, DetailType detailType, AsyncCallback, List>>> callback); - void getLeaderboardsNamesOfMetaleaderboard(String metaLeaderboardName, + void getLeaderboardsNamesOfMetaLeaderboard(String metaLeaderboardName, AsyncCallback>> callback); void checkLeaderboardName(String leaderboardName, AsyncCallback> callback); @@ -488,6 +480,8 @@ public interface SailingServiceAsync { void getRegattaStructureForEvent(UUID eventId, AsyncCallback> asyncCallback); + void getRegattaStructureOfEvent(UUID eventId, AsyncCallback> callback); + void getRaceStateEntriesForRaceGroup(UUID eventId, List visibleCourseAreas, List visibleRegattas, boolean showOnlyCurrentlyRunningRaces, boolean showOnlyRacesOfSameDay, AsyncCallback> markedAsyncCallback); @@ -554,7 +548,7 @@ public interface SailingServiceAsync { void importWindFromIgtimi(List selectedRaces, boolean correctByDeclination, AsyncCallback> asyncCallback); - void getEventById(UUID id, AsyncCallback callback); + void getEventById(UUID id, boolean withStatisticalData, AsyncCallback callback); void getLeaderboardsByEvent(EventDTO event, AsyncCallback> callback); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java index 67e0696bf49..370ddf5f574 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java @@ -1,8 +1,11 @@ package com.sap.sailing.gwt.ui.client; +import com.google.gwt.core.client.GWT; import com.google.gwt.i18n.client.Messages; public interface StringMessages extends Messages { + public static final StringMessages INSTANCE = GWT.create(StringMessages.class); + String helloWorld(); String kielWeel2011(); String stgAccount(); @@ -384,6 +387,7 @@ public interface StringMessages extends Messages { String scoringSchemeWinnerGetsFive(); String scoringSchemeWinnerGetsFiveIgnoringRaceCount(); String scoringSchemeWinnerGetsSix(); + String scoringSchemeWinnerGetsSixIgnoringRaceCount(); String scoringSystem(); String createFlexibleLeaderboard(); String createRegattaLeaderboard(); @@ -1066,4 +1070,20 @@ public interface StringMessages extends Messages { String currentOrAverageSpeedOverGroundInKnotsTooltip(); String currentOrAverageSpeedOverGroundInKnots(); String scoringSchemeHighPointFirstGetsTenOrEight(); + String videoComponentShortName(); + String competitorSearchFilter(); + String searchCompetitorsBySailNumberOrName(); + String goToEventOverview(); + String goToOverviewAndSeeLeaderboard(); + String noSuchEvent(); + String leaderboardNotContainedInLeaderboardGroup(String leaderboardName, String leaderboardGroupName); + String leaderboardGroupNotContainedInEvent(String leaderboardGroupName, String name); + String raceIsInLiveTimePanelMode(); + String backToLiveTimePanelMode(); + String windChart(); + String showVideoPopup(); + String hideVideoPopup(); + String manageMedia(); + String manageMediaTooltip(); + String showAll(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties index 541654081f1..1bc6c166892 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties @@ -16,7 +16,7 @@ race=Race races=Races tracked=tracked time=Time -timeTooltip=Time the competitor sailed on this leg. For the first leg this is counted from the start, not when the competitor passed the start line. +timeTooltip=Time the competitor sailed on this leg. For the first leg this is counted from when the competitor passed the start line. playSpeed=Play speed playSpeedHelp=Use "1" for regular play-back speed, "2" for double-speed playback and so on playModeLive=Live @@ -281,7 +281,7 @@ stepSizeMustBeGreaterThanNull=Step size must be greater than 0 stepSizeInSeconds=Step size in seconds refreshInterval=Refresh interval (s) refreshIntervalMustBeGreaterThanXSeconds=Refresh interval must be greater than {0} seconds -competitorCharts=Competitor charts +competitorCharts=Competitor Chart selectAtLeastOneCompetitor=To show the competitor charts, select at least one competitor directionChange=Direction change speedChange=Speed change @@ -411,6 +411,7 @@ scoringSchemeLowPointWinnerGetsZero=Low Point System, Winner Gets Zero scoringSchemeWinnerGetsFive=High Point System, Winner Gets 5 Points scoringSchemeWinnerGetsFiveIgnoringRaceCount=High Point System, Winner Gets 5 Points, Ignoring Race Count scoringSchemeWinnerGetsSix=High Point System, Winner Gets 6 Points +scoringSchemeWinnerGetsSixIgnoringRaceCount=High Point System, Winner Gets 6 Points, Ignoring Race Count scoringSystem=Scoring system createFlexibleLeaderboard=Create flexible leaderboard createRegattaLeaderboard=Create regatta leaderboard @@ -544,7 +545,7 @@ competitorColumnTooltip=Names of the members of this competitor. sailIdColumnTooltip=Shows the flag of the country and the sail number of the competitor. rankColumnTooltip=The overall rank of this competitor in the regatta. carryColumnTooltip=Points carried over from previous races. -totalsColumnTooltip=The total of all points of a competitor in the regatta. +totalsColumnTooltip=The total of all points of a competitor in the regatta.\nSorting based on rank, not points. windData=Wind data gpsData=GPS data status=Status @@ -728,7 +729,7 @@ stopAllReplicas=Drop connection to all replicas (Dangerous!) warningServerIsReplica=ATTENTION: This server is configured to be a replica. Do NOT change anything or this will lead to strange effects! dropReplicaConnection=Drop connection timePanelName=Time Control -competitorFilterExplanation=The filter will show all competitors matching the criterias:dropReplicaConnection=Drop connection +competitorFilterExplanation=The filter will show all competitors matching the criterias: protestTimeFinishesAt=Protest time ends at protestTimeFinishedAt=Protest time ended at flags=Flags @@ -1042,11 +1043,27 @@ setStartTimeReceivedDescription=This sets the startTimeReceived of the selected lastScoreCorrectionsTime=Last score correction time lastScoreCorrectionsComment=Last score correction comment setTimeToNow=Set time to ''now'' -useStartTimeInference=Use start time inference from start mark passings +useStartTimeInference=Use start time inference from start mark passings numberOfQueueMessagesSent=Number of queue messages sent totalSize=Total Size averageMessageSize=Average size per message in bytes totalNumberOfOperations=Total number of operations currentOrAverageSpeedOverGroundInKnotsTooltip=Current speed over ground in knots while leg is still running; average speed over ground when leg is completed currentOrAverageSpeedOverGroundInKnots=SOG (\u2205 at end) -scoringSchemeHighPointFirstGetsTenOrEight=High Point, winner gets 10 points (or 8) \ No newline at end of file +scoringSchemeHighPointFirstGetsTenOrEight=High Point, winner gets 10 points (or 8) +videoComponentShortName=Video +competitorSearchFilter=Competitor Search Filter +searchCompetitorsBySailNumberOrName=Search +goToEventOverview=Go to event overview +goToOverviewAndSeeLeaderboard=Go to the overview and see all Races in one Leaderboard +noSuchEvent=No such event +leaderboardNotContainedInLeaderboardGroup=The leaderboard {0} is not contained in leaderboard group {1}. +leaderboardGroupNotContainedInEvent=The leaderboard group {0} is not contained in event {1}. +raceIsInLiveTimePanelMode=Race is Live +backToLiveTimePanelMode=Back to Live +windChart=Wind Chart +showVideoPopup=Show Video +hideVideoPopup=Hide Video +manageMedia=Manage Media +manageMediaTooltip=Configure audio and video clips, synchronize times +showAll=Show all \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties index 63b2c048b23..8d3f5d296f8 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties @@ -16,7 +16,7 @@ race=Wettfahrt races=Wettfahrten tracked=getracked time=Zeit -timeTooltip=Zeit, die der Teilnehmer auf diesem Schenkel gesegelt ist. +timeTooltip=Zeit, die der Teilnehmer auf diesem Schenkel gesegelt ist. Beim ersten Schenkel zählt die Zeit ab Überquerung der Startlinie. playSpeed=Abspielgeschwindigkeit playSpeedHelp="1" für normale Abspielgeschwindigkeit, "2" für doppelte Geschwindigkeit usw. playModeLive=Live @@ -412,6 +412,7 @@ scoringSchemeLowPointWinnerGetsZero=Low Point System, Gewinner erhält 0 Punkte scoringSchemeWinnerGetsFive=High Point System, Gewinner erhält 5 Punkte scoringSchemeWinnerGetsFiveIgnoringRaceCount=High Point System, Gewinner erhält 5 Punkte, Anzahl Wettfahrten egal scoringSchemeWinnerGetsSix=High Point System, Gewinner erhält 6 Punkte +scoringSchemeWinnerGetsSixIgnoringRaceCount=High Point System, Gewinner erhält 6 Punkte, Anzahl Wettfahrten egal scoringSystem=Bepunktungssystem createFlexibleLeaderboard=Flexible Rangliste anlegen createRegattaLeaderboard=Rangliste für Regatta anlegen @@ -544,7 +545,7 @@ competitorColumnTooltip=Namen der Segler dieses Teilnehmers sailIdColumnTooltip=Zeigt die Länderflagge und die Segelnummer des Teilnehmers rankColumnTooltip=Der Gesamtplatz des Teilnehmers in der Regatta carryColumnTooltip=Übertrag vergangener Rennen -totalsColumnTooltip=Summe aller Punkte eines Teilnehmers in der Regatta +totalsColumnTooltip=Summe aller Punkte eines Teilnehmers in der Regatta.\nSortierung nach Rang, nicht Punkten. windData=Winddaten gpsData=GPS-Daten status=Status @@ -1044,4 +1045,20 @@ averageMessageSize=Durchschnittsgrösse einer Nachricht totalNumberOfOperations=Gesamtanzahl gesendete Operationen currentOrAverageSpeedOverGroundInKnotsTooltip=Momentane Fahrt über Grund während der Teilnehmer sich im Bahnabschnitt befindet; Durchschnitt, nachdem der Teilnehmer den Abschnitt beendet hat. currentOrAverageSpeedOverGroundInKnots=FüG (\u2205 nach Abschluss) -scoringSchemeHighPointFirstGetsTenOrEight=High Point, Gewinner bekommt 10 Punkte (oder 8) \ No newline at end of file +scoringSchemeHighPointFirstGetsTenOrEight=High Point, Gewinner bekommt 10 Punkte (oder 8) +videoComponentShortName=Video +competitorSearchFilter=Teilnehmer-Suchfilter +searchCompetitorsBySailNumberOrName=Suchen +goToEventOverview=Zur Veranstaltungsübersicht +goToOverviewAndSeeLeaderboard=Zur Ergebnisübersicht mit allen Wettfahrten der Regatta +noSuchEvent=Veranstaltung nicht gefunden +leaderboardNotContainedInLeaderboardGroup=Ergebnisliste {0} nicht in Wettfahrtgruppe {1} enthalten. +leaderboardGroupNotContainedInEvent=Die Wettfahrtgruppe {0} gehört nicht zur Veranstaltung {1}. +raceIsInLiveTimePanelMode=Rennen ist live +backToLiveTimePanelMode=Zurück zu live +windChart=Wind Chart +showVideoPopup=Video zeigen +hideVideoPopup=Video verstecken +manageMedia=Audio/Video verwalten +manageMediaTooltip=Audio- und Video-Clips verwalten, Zeiten synchronisieren +showAll=Alle zeigen diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties index 683a984b2b2..b3ee56dc535 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties @@ -17,7 +17,8 @@ race=Гонка races=Гонки tracked=Отслежена time=Время -timeTooltip=Учет времени участника, выступавшего на этом этапе. На первом этапе учет времени ведется с начала гонки, а не при пересечении участником линии старта. +timeTooltip=Time the competitor sailed on this leg. For the first leg this is counted from when the competitor passed the start line. +# timeTooltip=Учет времени участника, выступавшего на этом этапе. На первом этапе учет времени ведется с начала гонки, а не при пересечении участником линии старта. playSpeed=Скорость воспроизведения playSpeedHelp=Используйте "1" для обычной скорости воспроизведения, "2" для удвоенной скорости воспроизведения и т. д. playModeLive=В прямом эфире @@ -402,6 +403,7 @@ scoringSchemeLowPointWinnerGetsZero=Низкобальная система по scoringSchemeWinnerGetsFive=Высокобальная система подсчета, победитель получает 5 очков scoringSchemeWinnerGetsFiveIgnoringRaceCount=Высокобальная система подсчета, победитель получает 5 очков, Ignoring Race Count scoringSchemeWinnerGetsSix=Высокобальная система подсчета, победитель получает 6 очков +scoringSchemeWinnerGetsSixIgnoringRaceCount=Высокобальная система подсчета, победитель получает 6 очков, Ignoring Race Count scoringSystem=Система подсчета очков createFlexibleLeaderboard=Создание адаптивной таблицы лидеров createRegattaLeaderboard=Создание таблицы лидеров регаты @@ -999,4 +1001,20 @@ averageMessageSize=Average Size per message totalNumberOfOperations=Total number of operations currentOrAverageSpeedOverGroundInKnotsTooltip=Current speed over ground in knots while leg is still running; average speed over ground when leg is completed currentOrAverageSpeedOverGroundInKnots=SOG (\u2205 at end) -scoringSchemeHighPointFirstGetsTenOrEight=High Point, winner gets 10 points (or 8) \ No newline at end of file +scoringSchemeHighPointFirstGetsTenOrEight=High Point, winner gets 10 points (or 8) +videoComponentShortName=Video +competitorSearchFilter=Competitor Search Filter +searchCompetitorsBySailNumberOrName=Search +goToEventOverview=Go to the event overview +goToOverviewAndSeeLeaderboard=Go to the overview and see all Races in one Leaderboard +noSuchEvent=No such event +leaderboardNotContainedInLeaderboardGroup=The leaderboard {0} is not contained in leaderboard group {1}. +leaderboardGroupNotContainedInEvent=The leaderboard group {0} is not contained in event {1}. +raceIsInLiveTimePanelMode=Race is Live +backToLiveTimePanelMode=Back to Live +windChart=Wind Chart +showVideoPopup=Show Video +hideVideoPopup=Hide Video +manageMedia=Manage Media +manageMediaTooltip=Configure audio and video clips, synchronize times +showAll=Show all diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanel.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanel.css new file mode 100755 index 00000000000..838b305e5a8 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanel.css @@ -0,0 +1,24 @@ +.settingsButtonStyle { + position: absolute; + right: 0px; + bottom: 5px; + z-index: 1; + margin-top: 4px; + margin-right: 15px; + border: none; +} + +.settingsButtonStyle:focus { + outline: 0 !important; +} + +.settingsButtonStyle:hover { + border: 1px solid gray; +} + +@sprite .settingsButtonBackgroundImage { + gwt-image: "settingsButton"; + width: 2em; + height: 2em; + background-size: contain; +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanel.java index 0128d6198d8..2789631e1ad 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanel.java @@ -11,8 +11,6 @@ import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.i18n.client.DateTimeFormat; import com.google.gwt.resources.client.ImageResource; -import com.google.gwt.user.client.ui.AbstractImagePrototype; -import com.google.gwt.user.client.ui.Anchor; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.Image; @@ -21,6 +19,7 @@ import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.RequiresResize; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.ui.client.TimePanelCssResources.TimePanelCss; import com.sap.sailing.gwt.ui.client.shared.components.Component; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialog; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent; @@ -70,6 +69,7 @@ public class TimePanel extends SimplePanel implemen protected Date lastReceivedDataTimepoint; private final Button slowDownButton; private final Button speedUpButton; + private final Button toggleAdvancedModeButton; private final FlowPanel controlsPanel; private final SimplePanel timePanelSlider; @@ -81,21 +81,10 @@ public class TimePanel extends SimplePanel implemen * the minimum time the slider extends it's time when the end of the slider is reached */ private long MINIMUM_AUTO_ADVANCE_TIME_IN_MS = 5 * 60 * 1000; // 5 minutes + private boolean advancedModeShown; private static ClientResources resources = GWT.create(ClientResources.class); - - private class SettingsClickHandler implements ClickHandler { - private final StringMessages stringConstants; - - private SettingsClickHandler(StringMessages stringConstants) { - this.stringConstants = stringConstants; - } - - @Override - public void onClick(ClickEvent event) { - new SettingsDialog(TimePanel.this, stringConstants).show(); - } - } + protected static TimePanelCss timePanelCss = TimePanelCssResources.INSTANCE.css(); public TimePanel(Timer timer, TimeRangeWithZoomProvider timeRangeProvider, StringMessages stringMessages, boolean canReplayWhileLiveIsPossible) { this.timer = timer; @@ -162,6 +151,9 @@ public class TimePanel extends SimplePanel implemen playControlPanel.setStyleName("timePanel-controls-play"); controlsPanel.add(playControlPanel); + toggleAdvancedModeButton = createToggleAdvancedModeButton(); + playControlPanel.add(toggleAdvancedModeButton); + playPauseButton.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { @@ -176,10 +168,10 @@ public class TimePanel extends SimplePanel implemen } }); playPauseButton.setTitle(stringMessages.startStopPlaying()); - playPauseButton.getElement().addClassName("playPauseButton"); + playPauseButton.setStyleName("playPauseButton"); playControlPanel.add(playPauseButton); - backToLivePlayButton = new Button(stringMessages.playModeLive()); + backToLivePlayButton = new Button(stringMessages.raceIsInLiveTimePanelMode()); backToLivePlayButton.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { @@ -187,7 +179,7 @@ public class TimePanel extends SimplePanel implemen TimePanel.this.timer.play(); } }); - backToLivePlayButton.addStyleName("backToLivePlayButton"); + backToLivePlayButton.setStyleName("backToLivePlayButton"); backToLivePlayButton.setTitle(stringMessages.backToLive()); playControlPanel.add(backToLivePlayButton); @@ -224,7 +216,6 @@ public class TimePanel extends SimplePanel implemen FlowPanel playSpeedControlPanel = new FlowPanel(); playSpeedControlPanel.setStyleName("timePanel-controls-playSpeed"); - playSpeedBox = new IntegerBox(); playSpeedBox.setVisibleLength(3); playSpeedBox.setWidth("25px"); @@ -252,6 +243,7 @@ public class TimePanel extends SimplePanel implemen } }); slowDownButton.setTitle(stringMessages.slowPlaySpeedDown()); + slowDownButton.addStyleName("timePanelButton-SlowDown"); playSpeedControlPanel.add(slowDownButton); speedUpButton = new Button("+1"); @@ -263,6 +255,7 @@ public class TimePanel extends SimplePanel implemen } }); speedUpButton.setTitle(stringMessages.speedPlaySpeedUp()); + speedUpButton.addStyleName("timePanelButton-SpeedUp"); playSpeedControlPanel.add(speedUpButton); playSpeedImage.getElement().getStyle().setFloat(Style.Float.LEFT); @@ -283,14 +276,9 @@ public class TimePanel extends SimplePanel implemen timeDelayLabel.getElement().getStyle().setFloat(Style.Float.LEFT); timeDelayLabel.getElement().getStyle().setPadding(3, Style.Unit.PX); - - // settings - ImageResource settingsIcon = resources.settingsIcon(); - Anchor settingsAnchor = new Anchor(AbstractImagePrototype.create(settingsIcon).getSafeHtml()); - settingsAnchor.setTitle(stringMessages.settings()); - settingsAnchor.setStyleName("timePanelSettings"); - settingsAnchor.addClickHandler(new SettingsClickHandler(stringMessages)); - controlsPanel.add(settingsAnchor); + + timePanelCss.ensureInjected(); + controlsPanel.add(createSettingsButton()); setWidget(timePanelInnerWrapper); playStateChanged(timer.getPlayState(), timer.getPlayMode()); @@ -299,20 +287,43 @@ public class TimePanel extends SimplePanel implemen controlsPanel.add(timeDelayPanel); controlsPanel.add(timeControlPanel); controlsPanel.add(timeToStartControlPanel); + + hideControlsPanel(); + } + + private Button createSettingsButton() { + Button settingsButton = SettingsDialog.createSettingsButton(this, stringMessages); + settingsButton.setStyleName(timePanelCss.settingsButtonStyle()); + settingsButton.addStyleName(timePanelCss.settingsButtonBackgroundImage()); + return settingsButton; + } + + private Button createToggleAdvancedModeButton() { + Button toggleAdvancedModeButton = new Button(); + toggleAdvancedModeButton.setStyleName("TimePanel-ShowExtended-Button"); + toggleAdvancedModeButton.addStyleDependentName("Closed"); + return toggleAdvancedModeButton; + } + + public boolean toggleAdvancedMode() { + if (advancedModeShown) { + hideControlsPanel(); + } else { + showControlsPanel(); + } + this.advancedModeShown = !advancedModeShown; + return this.advancedModeShown; } - public void hideControlsPanelAndMovePlayButtonUp() { + protected void hideControlsPanel() { controlsPanel.remove(playControlPanel); timePanelInnerWrapper.remove(controlsPanel); timePanelSliderFlowWrapper.insert(playControlPanel, 0); playControlPanel.setStyleName("timePanel-timeslider-play"); } - public void showControlsPanelAndMovePlayButtonDown() { + protected void showControlsPanel() { timePanelInnerWrapper.add(controlsPanel); - timePanelSliderFlowWrapper.remove(playControlPanel); - controlsPanel.insert(playControlPanel, 0); - playControlPanel.setStyleName("timePanel-controls-play"); } @Override @@ -501,6 +512,15 @@ public class TimePanel extends SimplePanel implemen */ protected void setJumpToLiveEnablement(boolean enabled) { backToLivePlayButton.setEnabled(enabled); + if (enabled) { + backToLivePlayButton.setText(stringMessages.backToLiveTimePanelMode()); + backToLivePlayButton.removeStyleDependentName("Inactive"); + backToLivePlayButton.addStyleDependentName("Active"); + } else { + backToLivePlayButton.setText(stringMessages.raceIsInLiveTimePanelMode()); + backToLivePlayButton.removeStyleDependentName("Active"); + backToLivePlayButton.addStyleDependentName("Inactive"); + } } /** @@ -566,4 +586,13 @@ public class TimePanel extends SimplePanel implemen protected boolean canReplayWhileLiveIsPossible() { return canReplayWhileLiveIsPossible; } + + public Button getAdvancedToggleButton() { + return this.toggleAdvancedModeButton; + } + + @Override + public String getDependentCssClassName() { + return "timePanel"; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanelCssResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanelCssResources.java new file mode 100755 index 00000000000..fb7294d639d --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanelCssResources.java @@ -0,0 +1,22 @@ +package com.sap.sailing.gwt.ui.client; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; +import com.google.gwt.resources.client.ImageResource; + +public interface TimePanelCssResources extends ClientBundle { + public static final TimePanelCssResources INSTANCE = GWT.create(TimePanelCssResources.class); + + @Source("com/sap/sailing/gwt/ui/client/TimePanel.css") + TimePanelCss css(); + + @Source("com/sap/sailing/gwt/ui/client/images/SAP_RV_Settings.png") + ImageResource settingsButton(); + + public interface TimePanelCss extends CssResource { + String settingsButtonStyle(); + + String settingsButtonBackgroundImage(); + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/AbstractEmbeddedMediaPlayer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/AbstractHtmlMediaPlayer.java similarity index 55% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/AbstractEmbeddedMediaPlayer.java rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/AbstractHtmlMediaPlayer.java index ca0e02cbb80..08ef4804b75 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/AbstractEmbeddedMediaPlayer.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/AbstractHtmlMediaPlayer.java @@ -5,7 +5,7 @@ import com.google.gwt.media.client.MediaBase; import com.sap.sailing.domain.common.media.MediaTrack; import com.sap.sailing.gwt.ui.client.media.shared.AbstractMediaPlayer; -abstract public class AbstractEmbeddedMediaPlayer extends AbstractMediaPlayer { +abstract public class AbstractHtmlMediaPlayer extends AbstractMediaPlayer { /** * isReady and the ..Cache fields are required because setting the audio element's play state @@ -17,29 +17,29 @@ abstract public class AbstractEmbeddedMediaPlayer extends AbstractMediaPlayer { private boolean deferredIsPlaying; private boolean deferredIsMuted; - protected final MediaBase mediaControl; + protected final MediaBase mediaElement; - public AbstractEmbeddedMediaPlayer(MediaTrack mediaTrack) { + public AbstractHtmlMediaPlayer(MediaTrack mediaTrack) { super(mediaTrack); - mediaControl = createMediaControl(); - if (mediaControl != null) { - mediaControl.setControls(false); - mediaControl.setAutoplay(false); - mediaControl.setLoop(false); - mediaControl.setMuted(false); - mediaControl.setPreload(MediaElement.PRELOAD_AUTO); - addNativeEventHandlers(mediaControl.getMediaElement()); - mediaControl.setSrc(mediaTrack.url); - mediaControl.setTitle(mediaTrack.title); + mediaElement = createMediaElement(); + if (mediaElement != null) { + mediaElement.setControls(false); + mediaElement.setAutoplay(false); + mediaElement.setLoop(false); + mediaElement.setMuted(false); + mediaElement.setPreload(MediaElement.PRELOAD_AUTO); + addNativeEventHandlers(mediaElement.getMediaElement()); + mediaElement.setSrc(mediaTrack.url); + mediaElement.setTitle(mediaTrack.title); } } - abstract protected MediaBase createMediaControl(); + abstract protected MediaBase createMediaElement(); native void addNativeEventHandlers(MediaElement mediaElement) /*-{ var that = this; mediaElement.addEventListener('canplay', function() { - that.@com.sap.sailing.gwt.ui.client.media.AbstractEmbeddedMediaPlayer::initPlayState()(); + that.@com.sap.sailing.gwt.ui.client.media.AbstractHtmlMediaPlayer::initPlayState()(); }); mediaElement.addEventListener('timeupdate', function(event) { that.@com.sap.sailing.gwt.ui.client.media.shared.AbstractMediaPlayer::onMediaTimeUpdate()(); @@ -47,14 +47,14 @@ abstract public class AbstractEmbeddedMediaPlayer extends AbstractMediaPlayer { }-*/; private void initPlayState() { - if (!isReady && (mediaControl != null)) { - mediaControl.setCurrentTime(deferredMediaTime); - mediaControl.setMuted(deferredIsMuted); - mediaControl.setPlaybackRate(deferredPlaybackSpeed); + if (!isReady && (mediaElement != null)) { + mediaElement.setCurrentTime(deferredMediaTime); + mediaElement.setMuted(deferredIsMuted); + mediaElement.setPlaybackRate(deferredPlaybackSpeed); if (deferredIsPlaying) { - mediaControl.play(); + mediaElement.play(); } else { - mediaControl.pause(); + mediaElement.pause(); } } isReady = true; @@ -63,7 +63,7 @@ abstract public class AbstractEmbeddedMediaPlayer extends AbstractMediaPlayer { @Override public boolean isMediaPaused() { if (isReady) { - return mediaControl.isPaused(); + return mediaElement.isPaused(); } else { return !deferredIsPlaying; } @@ -72,39 +72,39 @@ abstract public class AbstractEmbeddedMediaPlayer extends AbstractMediaPlayer { @Override public void pauseMedia() { deferredIsPlaying = false; - if (isReady && mediaControl != null) { - mediaControl.pause(); + if (isReady && mediaElement != null) { + mediaElement.pause(); } } @Override public void playMedia() { deferredIsPlaying = true; - if (isReady && mediaControl != null) { - mediaControl.play(); + if (isReady && mediaElement != null) { + mediaElement.play(); } } @Override public void setPlaybackSpeed(double playbackSpeed) { this.deferredPlaybackSpeed = playbackSpeed; - if (isReady && mediaControl != null) { - mediaControl.setPlaybackRate(playbackSpeed); + if (isReady && mediaElement != null) { + mediaElement.setPlaybackRate(playbackSpeed); } } @Override public void setMuted(boolean isToBeMuted) { deferredIsMuted = isToBeMuted; - if (isReady && mediaControl != null) { - mediaControl.setMuted(isToBeMuted); + if (isReady && mediaElement != null) { + mediaElement.setMuted(isToBeMuted); } } @Override public double getDuration() { if (isReady) { - return mediaControl.getDuration(); + return mediaElement.getDuration(); } else { return Double.NaN; } @@ -113,7 +113,7 @@ abstract public class AbstractEmbeddedMediaPlayer extends AbstractMediaPlayer { @Override public double getCurrentMediaTime() { if (isReady) { - return mediaControl.getCurrentTime(); + return mediaElement.getCurrentTime(); } else { return Double.NaN; } @@ -122,15 +122,15 @@ abstract public class AbstractEmbeddedMediaPlayer extends AbstractMediaPlayer { @Override public void setCurrentMediaTime(double mediaTime) { this.deferredMediaTime = mediaTime; - if (isReady && mediaControl != null) { - mediaControl.setCurrentTime(mediaTime); + if (isReady && mediaElement != null) { + mediaElement.setCurrentTime(mediaTime); } } @Override - public void close() { - mediaControl.pause(); - mediaControl.setSrc(null); + public void shutDown() { + mediaElement.pause(); + mediaElement.setSrc(null); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/AudioPlayer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/AudioHtmlPlayer.java similarity index 54% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/AudioPlayer.java rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/AudioHtmlPlayer.java index dc6b8e515d4..62fdd87e7fb 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/AudioPlayer.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/AudioHtmlPlayer.java @@ -3,15 +3,16 @@ package com.sap.sailing.gwt.ui.client.media; import com.google.gwt.media.client.Audio; import com.google.gwt.media.client.MediaBase; import com.sap.sailing.domain.common.media.MediaTrack; +import com.sap.sailing.gwt.ui.client.media.shared.AudioPlayer; -public class AudioPlayer extends AbstractEmbeddedMediaPlayer { +public class AudioHtmlPlayer extends AbstractHtmlMediaPlayer implements AudioPlayer { - public AudioPlayer(MediaTrack mediaTrack) { + public AudioHtmlPlayer(MediaTrack mediaTrack) { super(mediaTrack); } @Override - protected MediaBase createMediaControl() { + protected MediaBase createMediaElement() { return Audio.createIfSupported(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaComponent.java new file mode 100644 index 00000000000..7dbb61b82d6 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaComponent.java @@ -0,0 +1,331 @@ +package com.sap.sailing.gwt.ui.client.media; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; + +import com.google.gwt.dom.client.AudioElement; +import com.google.gwt.dom.client.MediaElement; +import com.google.gwt.media.client.Audio; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.common.media.MediaTrack; +import com.sap.sailing.domain.common.media.MediaTrack.MediaType; +import com.sap.sailing.domain.common.media.MediaTrack.Status; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.MediaServiceAsync; +import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.media.shared.VideoPlayerWithWidget; +import com.sap.sailing.gwt.ui.client.shared.components.Component; +import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent; +import com.sap.sailing.gwt.ui.shared.UserDTO; +import com.sap.sse.gwt.client.player.PlayStateListener; +import com.sap.sse.gwt.client.player.TimeListener; +import com.sap.sse.gwt.client.player.Timer; +import com.sap.sse.gwt.client.player.Timer.PlayModes; +import com.sap.sse.gwt.client.player.Timer.PlayStates; +import com.sap.sse.gwt.client.useragent.UserAgentDetails; +import com.sap.sse.gwt.client.useragent.UserAgentDetails.AgentTypes; + +public class MediaComponent implements Component, PlayStateListener, TimeListener { + + private VideoPlayerWithWidget activeVideoPlayer; + + private final SimplePanel rootPanel = new SimplePanel(); + + private final Collection mediaTracks = new ArrayList(); + private final RegattaAndRaceIdentifier raceIdentifier; + private final RaceTimesInfoProvider raceTimesInfoProvider; + private final Timer raceTimer; + private final StringMessages stringMessages; + private final ErrorReporter errorReporter; + private final UserAgentDetails userAgent; + private final UserDTO user; + private final MediaServiceAsync mediaService; + private final boolean autoSelectMedia; + private final String defaultMediaId; + + private final MediaSelector mediaSelector; + + private Date currentRaceTime; + private double currentPlaybackSpeed = 1.0d; + private PlayStates currentPlayState = PlayStates.Paused; + + + public MediaComponent(RegattaAndRaceIdentifier selectedRaceIdentifier, RaceTimesInfoProvider raceTimesInfoProvider, + Timer raceTimer, MediaServiceAsync mediaService, StringMessages stringMessages, + ErrorReporter errorReporter, UserAgentDetails userAgent, UserDTO user, boolean autoSelectMedia, String defaultMedia) { + this.raceIdentifier = selectedRaceIdentifier; + this.raceTimesInfoProvider = raceTimesInfoProvider; + this.raceTimer = raceTimer; + this.stringMessages = stringMessages; + this.errorReporter = errorReporter; + this.userAgent = userAgent; + this.user = user; + this.mediaService = mediaService; + this.autoSelectMedia = autoSelectMedia; + this.defaultMediaId = defaultMedia; + this.mediaSelector = new MediaSelector(selectedRaceIdentifier, raceTimesInfoProvider, raceTimer, mediaService, stringMessages, errorReporter, userAgent, user, autoSelectMedia); + } + + public Button getMediaSelectionButton() { + return this.mediaSelector.getManageMediaButton(); + } + + public boolean isPotentiallyPlayable(MediaTrack mediaTrack) { + boolean result = false; + if (mediaTrack != null) { + result = MediaTrack.Status.REACHABLE.equals(mediaTrack.status) + || MediaTrack.Status.UNDEFINED.equals(mediaTrack.status); + } + return result; + } + + private void setStatus(final MediaTrack mediaTrack) { + if (!mediaTrack.isYoutube()) { + // firefox crashes in the current version when trying to read the metadata from mp4 files + if(!userAgent.getType().equals(AgentTypes.FIREFOX)) { + Audio audio = Audio.createIfSupported(); + if (audio != null) { + AudioElement mediaReachableTester = audio.getAudioElement(); + addLoadMetadataHandler(mediaReachableTester, mediaTrack); + mediaReachableTester.setPreload(MediaElement.PRELOAD_METADATA); + mediaReachableTester.setSrc(mediaTrack.url); + mediaReachableTester.load(); + } else { + mediaTrack.status = Status.CANNOT_PLAY; + } + } else { + mediaTrack.status = Status.REACHABLE; + } + } else { + mediaTrack.status = Status.REACHABLE; + } + } + + native void addLoadMetadataHandler(MediaElement mediaElement, MediaTrack mediaTrack) /*-{ + var that = this; + mediaElement + .addEventListener( + 'loadedmetadata', + function() { + that.@com.sap.sailing.gwt.ui.client.media.MediaSelector::loadedmetadata(Lcom/sap/sailing/domain/common/media/MediaTrack;)(mediaTrack); + }); + mediaElement + .addEventListener( + 'error', + function() { + that.@com.sap.sailing.gwt.ui.client.media.MediaSelector::mediaError(Lcom/sap/sailing/domain/common/media/MediaTrack;)(mediaTrack); + }); + }-*/; + + public void loadedmetadata(MediaTrack mediaTrack) { + mediaTrack.status = Status.REACHABLE; + } + + public void mediaError(MediaTrack mediaTrack) { + mediaTrack.status = Status.NOT_REACHABLE; + } + + private void playDefault() { + MediaTrack defaultVideo = getDefaultVideo(); + videoChanged(defaultVideo); + } + + public MediaTrack getDefaultVideo() { + for (MediaTrack mediaTrack : mediaTracks) { + if (defaultMediaId != null) { + if (mediaTrack.dbId.equals(defaultMediaId)) { + return mediaTrack; + } + } else { + if (MediaType.video.equals(mediaTrack.mimeType.mediaType) && isPotentiallyPlayable(mediaTrack)) { + return mediaTrack; + } + } + } + return null; + } + + private void stopAll() { + clear(); + } + + @Override + public void playStateChanged(PlayStates playState, PlayModes playMode) { + this.currentPlayState = playState; + if (PlayModes.Replay.equals(playMode)) { + switch (this.currentPlayState) { + case Playing: + startPlaying(); + break; + case Paused: + pausePlaying(); + default: + break; + } + } else { + // TODO: Live mode not supported, yet. + } + } + + @Override + public void playSpeedFactorChanged(double newPlaySpeedFactor) { + this.currentPlaybackSpeed = newPlaySpeedFactor; + if (activeVideoPlayer != null) { + activeVideoPlayer.setPlaybackSpeed(this.currentPlaybackSpeed); + } + } + + private void pausePlaying() { + if (activeVideoPlayer != null) { + activeVideoPlayer.pauseMedia(); + } + } + + private void startPlaying() { + if ((activeVideoPlayer != null) && activeVideoPlayer.isCoveringCurrentRaceTime()) { + activeVideoPlayer.playMedia(); + } + } + + @Override + public void timeChanged(Date newRaceTime, Date oldRaceTime) { + this.currentRaceTime = newRaceTime; + if (activeVideoPlayer != null) { + activeVideoPlayer.raceTimeChanged(this.currentRaceTime); + ensurePlayState(); + } + } + + /** + * Wraps the callback handling functions in an object to better document their purpose. + * onSuccess and onError are simply too generic to tell about their concrete use. + * @return + */ + public AsyncCallback> getMediaLibraryCallback() { + return new AsyncCallback>() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Remote Procedure Call getMediaTracksForRace(...) - Failure: " + caught.getMessage()); + } + @Override + public void onSuccess(Collection mediaTracks) { + mediaSelector.setMediaTracks(mediaTracks); + MediaComponent.this.mediaTracks.clear(); + MediaComponent.this.mediaTracks.addAll(mediaTracks); + for (MediaTrack mediaTrack : MediaComponent.this.mediaTracks) { + setStatus(mediaTrack); + } + + if (autoSelectMedia) { + setVisible(true); + } + } + }; + } + + public void videoChanged(MediaTrack videoTrack) { + if (activeVideoPlayer != null) { // --> then reset active audio player + + if (activeVideoPlayer.getMediaTrack() == videoTrack) { + return; // nothing changed + } + + activeVideoPlayer = null; + rootPanel.clear(); + } + if (videoTrack != null) { + if (videoTrack.isYoutube()) { + activeVideoPlayer = new VideoYoutubePlayer(videoTrack, getRaceStartTime(), false, raceTimer); + } else { + activeVideoPlayer = new VideoHtmlPlayer(videoTrack, getRaceStartTime(), false, raceTimer); + } + rootPanel.add(activeVideoPlayer.getWidget()); + synchPlayState(); + } + } + + private long getRaceStartTime() { + return raceTimesInfoProvider.getRaceTimesInfo(raceIdentifier).startOfRace.getTime(); + } + + private void synchPlayState() { + if (this.currentRaceTime != null) { + activeVideoPlayer.setPlaybackSpeed(currentPlaybackSpeed); + activeVideoPlayer.raceTimeChanged(this.currentRaceTime); + ensurePlayState(); + } + } + + private void ensurePlayState() { + switch (this.currentPlayState) { + case Playing: + if (activeVideoPlayer.isMediaPaused() && activeVideoPlayer.isCoveringCurrentRaceTime()) { + activeVideoPlayer.playMedia(); + } + break; + case Paused: + if (!activeVideoPlayer.isMediaPaused()) { + activeVideoPlayer.pauseMedia(); + } + default: + break; + } + } + + private void clear() { + if (activeVideoPlayer != null) { + activeVideoPlayer.shutDown(); + activeVideoPlayer = null; + } + } + + @Override + public String getLocalizedShortName() { + return stringMessages.videoComponentShortName(); + } + + @Override + public boolean hasSettings() { + return false; + } + + @Override + public SettingsDialogComponent getSettingsDialogComponent() { + throw new UnsupportedOperationException(); + } + + @Override + public void updateSettings(Void newSettings) { + throw new UnsupportedOperationException(); + } + + @Override + public Widget getEntryWidget() { + return rootPanel; + } + + @Override + public boolean isVisible() { + return rootPanel.isVisible(); + } + + @Override + public void setVisible(boolean visibility) { + rootPanel.setVisible(visibility); + if (visibility) { + playDefault(); + } + } + + @Override + public String getDependentCssClassName() { + return "media"; + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaSelector.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaSelector.java index 5c46c79d550..11359287957 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaSelector.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaSelector.java @@ -33,11 +33,12 @@ import com.sap.sailing.gwt.ui.client.MediaServiceAsync; import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.media.MediaSelectionDialog.MediaSelectionListener; -import com.sap.sailing.gwt.ui.client.media.popup.PopupWindowPlayer; +import com.sap.sailing.gwt.ui.client.media.popup.PopoutWindowPlayer; import com.sap.sailing.gwt.ui.client.media.popup.VideoWindowPlayer; import com.sap.sailing.gwt.ui.client.media.popup.YoutubeWindowPlayer; import com.sap.sailing.gwt.ui.client.media.shared.MediaPlayer; import com.sap.sailing.gwt.ui.client.media.shared.VideoPlayer; +import com.sap.sailing.gwt.ui.client.media.shared.VideoSynchPlayer; import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO; import com.sap.sailing.gwt.ui.shared.UserDTO; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; @@ -46,30 +47,26 @@ import com.sap.sse.gwt.client.player.TimeListener; import com.sap.sse.gwt.client.player.Timer; import com.sap.sse.gwt.client.player.Timer.PlayModes; import com.sap.sse.gwt.client.player.Timer.PlayStates; +import com.sap.sse.gwt.client.useragent.UserAgentDetails; +import com.sap.sse.gwt.client.useragent.UserAgentDetails.AgentTypes; -/** - * The media selector is used as an {@link AsyncCallback} that receives a collection of {@link MediaTrack}s in case the - * call was successful. This could, e.g., be a call to - * {@link MediaServiceAsync#getMediaTracksForRace(RegattaAndRaceIdentifier, AsyncCallback)}. When the call returns the - * track collection, the UI is updated accordingly. - */ -public class MediaSelector implements PlayStateListener, TimeListener, - AsyncCallback>, MediaSelectionListener, CloseHandler, ClosingHandler { +public class MediaSelector implements PlayStateListener, TimeListener, MediaSelectionListener, CloseHandler, ClosingHandler { private final CheckBox toggleMediaButton; private final Button manageMediaButton; private final MediaSelectionDialog mediaSelectionDialog; - private final Map videoPlayers = new HashMap(); + private final Map activeVideoContainers = new HashMap(); private final Collection mediaTracks = new ArrayList(); private final RegattaAndRaceIdentifier raceIdentifier; private final RaceTimesInfoProvider raceTimesInfoProvider; - private Timer raceTimer; + private final Timer raceTimer; private final MediaServiceAsync mediaService; - private StringMessages stringMessages; + private final StringMessages stringMessages; private final ErrorReporter errorReporter; + private final UserAgentDetails userAgent; private final UserDTO user; private boolean autoSelectMedia; @@ -77,16 +74,18 @@ public class MediaSelector implements PlayStateListener, TimeListener, private Date currentRaceTime; private double currentPlaybackSpeed = 1.0d; private PlayStates currentPlayState = PlayStates.Paused; + private boolean showingMediaSelection; public MediaSelector(RegattaAndRaceIdentifier selectedRaceIdentifier, RaceTimesInfoProvider raceTimesInfoProvider, Timer raceTimer, MediaServiceAsync mediaService, StringMessages stringMessages, - ErrorReporter errorReporter, UserDTO user, boolean autoSelectMedia) { + ErrorReporter errorReporter, UserAgentDetails userAgent, UserDTO user, boolean autoSelectMedia) { this.raceIdentifier = selectedRaceIdentifier; this.raceTimesInfoProvider = raceTimesInfoProvider; this.raceTimer = raceTimer; this.mediaService = mediaService; this.stringMessages = stringMessages; this.errorReporter = errorReporter; + this.userAgent = userAgent; this.user = user; this.autoSelectMedia = autoSelectMedia; @@ -95,7 +94,7 @@ public class MediaSelector implements PlayStateListener, TimeListener, mediaSelectionDialog = new MediaSelectionDialog(this); - manageMediaButton = new Button(); + manageMediaButton = new Button(stringMessages.manageMedia()); manageMediaButton.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { @@ -108,7 +107,7 @@ public class MediaSelector implements PlayStateListener, TimeListener, }); manageMediaButton.addStyleName("raceBoardNavigation-settingsButton"); manageMediaButton.getElement().getStyle().setFloat(Style.Float.LEFT); - manageMediaButton.setTitle("Configure Media"); + manageMediaButton.setTitle(stringMessages.manageMediaTooltip()); toggleMediaButton = new CheckBox("Audio & Video"); toggleMediaButton.addStyleName("raceBoardNavigation-innerElement"); @@ -130,6 +129,23 @@ public class MediaSelector implements PlayStateListener, TimeListener, setWidgetsVisible(false); } + + public Button getManageMediaButton() { + return this.manageMediaButton; + } + + public void setMediaTracks(Collection mediaTracks) { + this.mediaTracks.clear(); + this.mediaTracks.addAll(mediaTracks); + for (MediaTrack mediaTrack : MediaSelector.this.mediaTracks) { + setStatus(mediaTrack); + } + setWidgetsVisible((MediaSelector.this.mediaTracks.size() > 0) || (MediaSelector.this.user != null)); + RaceTimesInfoDTO raceTimesInfo = raceTimesInfoProvider.getRaceTimesInfo(raceIdentifier); + if(raceTimesInfo != null && raceTimesInfo.startOfRace != null) { + timeChanged(raceTimesInfoProvider.getRaceTimesInfo(raceIdentifier).startOfRace, null); + } + } private boolean isPotentiallyPlayable(MediaTrack mediaTrack) { return MediaTrack.Status.REACHABLE.equals(mediaTrack.status) @@ -138,15 +154,20 @@ public class MediaSelector implements PlayStateListener, TimeListener, private void setStatus(final MediaTrack mediaTrack) { if (!mediaTrack.isYoutube()) { - Audio audio = Audio.createIfSupported(); - if (audio != null) { - AudioElement mediaReachableTester = audio.getAudioElement(); - addLoadMetadataHandler(mediaReachableTester, mediaTrack); - mediaReachableTester.setPreload(MediaElement.PRELOAD_METADATA); - mediaReachableTester.setSrc(mediaTrack.url); - mediaReachableTester.load(); + // firefox crashes in the current version when trying to read the metadata from mp4 files + if(!userAgent.getType().equals(AgentTypes.FIREFOX)) { + Audio audio = Audio.createIfSupported(); + if (audio != null) { + AudioElement mediaReachableTester = audio.getAudioElement(); + addLoadMetadataHandler(mediaReachableTester, mediaTrack); + mediaReachableTester.setPreload(MediaElement.PRELOAD_METADATA); + mediaReachableTester.setSrc(mediaTrack.url); + mediaReachableTester.load(); + } else { + mediaTrack.status = Status.CANNOT_PLAY; + } } else { - mediaTrack.status = Status.CANNOT_PLAY; + mediaTrack.status = Status.REACHABLE; } } else { mediaTrack.status = Status.REACHABLE; @@ -235,7 +256,8 @@ public class MediaSelector implements PlayStateListener, TimeListener, if (activeAudioPlayer != null) { activeAudioPlayer.setPlaybackSpeed(this.currentPlaybackSpeed); } - for (MediaPlayer videoPlayer : videoPlayers.values()) { + for (VideoContainer videoContainer : activeVideoContainers.values()) { + VideoPlayer videoPlayer = videoContainer.getVideoPlayer(); videoPlayer.setPlaybackSpeed(this.currentPlaybackSpeed); } } @@ -245,9 +267,10 @@ public class MediaSelector implements PlayStateListener, TimeListener, activeAudioPlayer.pauseMedia(); } - for (MediaPlayer player : videoPlayers.values()) { - if (!player.isMediaPaused()) { - player.pauseMedia(); + for (VideoContainer videoContainer : activeVideoContainers.values()) { + VideoPlayer videoPlayer = videoContainer.getVideoPlayer(); + if (!videoPlayer.isMediaPaused()) { + videoPlayer.pauseMedia(); } } } @@ -256,7 +279,8 @@ public class MediaSelector implements PlayStateListener, TimeListener, if ((activeAudioPlayer != null) && activeAudioPlayer.isCoveringCurrentRaceTime()) { activeAudioPlayer.playMedia(); } - for (MediaPlayer videoPlayer : videoPlayers.values()) { + for (VideoContainer videoContainer : activeVideoContainers.values()) { + VideoPlayer videoPlayer = videoContainer.getVideoPlayer(); if (videoPlayer.isMediaPaused() && videoPlayer.isCoveringCurrentRaceTime()) { videoPlayer.playMedia(); } @@ -270,44 +294,50 @@ public class MediaSelector implements PlayStateListener, TimeListener, activeAudioPlayer.raceTimeChanged(this.currentRaceTime); ensurePlayState(activeAudioPlayer); } - for (MediaPlayer player : videoPlayers.values()) { - player.raceTimeChanged(this.currentRaceTime); - ensurePlayState(player); + for (VideoContainer videoContainer : activeVideoContainers.values()) { + VideoPlayer videoPlayer = videoContainer.getVideoPlayer(); + videoPlayer.raceTimeChanged(this.currentRaceTime); + ensurePlayState(videoPlayer); } } + + /** + * Wraps the callback handling functions in an object to better document their purpose. + * onSuccess and onError are simply too generic to tell about their concrete use. + * @return + */ + public AsyncCallback> getMediaLibraryCallback() { - @Override - public void onFailure(Throwable caught) { - setWidgetsVisible((this.user != null)); - errorReporter.reportError("Remote Procedure Call getMediaTracksForRace(...) - Failure: " + caught.getMessage()); - } - - @Override - public void onSuccess(Collection mediaTracks) { - this.mediaTracks.clear(); - this.mediaTracks.addAll(mediaTracks); - for (MediaTrack mediaTrack : this.mediaTracks) { - setStatus(mediaTrack); - } - setWidgetsVisible((this.mediaTracks.size() > 0) || (this.user != null)); + return new AsyncCallback>() { + + @Override + public void onFailure(Throwable caught) { + setWidgetsVisible((MediaSelector.this.user != null)); + errorReporter.reportError("Remote Procedure Call getMediaTracksForRace(...) - Failure: " + caught.getMessage()); + } - toggleMediaButton.setValue(autoSelectMedia); - if (autoSelectMedia) { - playDefault(); - } - } + @Override + public void onSuccess(Collection mediaTracks) { + MediaSelector.this.mediaTracks.clear(); + MediaSelector.this.mediaTracks.addAll(mediaTracks); + for (MediaTrack mediaTrack : MediaSelector.this.mediaTracks) { + setStatus(mediaTrack); + } + setWidgetsVisible((MediaSelector.this.mediaTracks.size() > 0) || (MediaSelector.this.user != null)); + + toggleMediaButton.setValue(autoSelectMedia); + if (autoSelectMedia) { + playDefault(); + } + } + }; + } private void setWidgetsVisible(boolean isVisible) { manageMediaButton.setVisible(isVisible); toggleMediaButton.setVisible(isVisible); } - // @Override - public void _raceTimesInfosReceived(Map raceTimesInfo) { - // TODO Auto-generated method stub - - } - @Override public void audioChanged(MediaTrack audioTrack) { if (activeAudioPlayer != null) { // --> then reset active audio player @@ -316,11 +346,11 @@ public class MediaSelector implements PlayStateListener, TimeListener, return; // nothing changed } - if (videoPlayers.containsKey(activeAudioPlayer.getMediaTrack())) { // pre-change audioPlayer is one of the + if (activeVideoContainers.containsKey(activeAudioPlayer.getMediaTrack())) { // pre-change audioPlayer is one of the // videoPlayers activeAudioPlayer.setMuted(true); } else { // pre-change audioPlayer is a dedicated audio-only player - activeAudioPlayer.close(); + activeAudioPlayer.shutDown(); } activeAudioPlayer = null; } @@ -329,9 +359,10 @@ public class MediaSelector implements PlayStateListener, TimeListener, videoSelected(audioTrack); mediaSelectionDialog.selectVideo(audioTrack); } - MediaPlayer playingVideo = videoPlayers.get(audioTrack); - if (playingVideo != null) { - activeAudioPlayer = playingVideo; + VideoContainer playingVideoContainer = activeVideoContainers.get(audioTrack); + if (playingVideoContainer != null) { + VideoPlayer playingVideoPlayer = playingVideoContainer.getVideoPlayer(); + activeAudioPlayer = playingVideoPlayer; activeAudioPlayer.setMuted(false); } else { assignNewAudioPlayer(audioTrack); @@ -343,74 +374,75 @@ public class MediaSelector implements PlayStateListener, TimeListener, @Override public void videoSelected(final MediaTrack videoTrack) { - MediaPlayer playingVideo = videoPlayers.get(videoTrack); - if (playingVideo == null) { - final PopupWindowPlayer.PopupCloseListener popupCloseListener = new PopupWindowPlayer.PopupCloseListener() { + VideoContainer activeVideoContainer = activeVideoContainers.get(videoTrack); + if (activeVideoContainer == null) { + final PopoutWindowPlayer.PopoutCloseListener popupCloseListener = new PopoutWindowPlayer.PopoutCloseListener() { - private VideoPlayer popoutPlayer; + private VideoContainer videoContainer; @Override - public void popupClosed() { - if (popoutPlayer == null) { + public void popoutClosed() { + if (videoContainer == null) { videoDeselected(videoTrack); } else { mediaSelectionDialog.hide(); - registerVideoPlayer(videoTrack, popoutPlayer); - popoutPlayer = null; + registerVideoContainer(videoTrack, videoContainer); + videoContainer = null; } } @Override - public void setPopoutPlayer(VideoPlayer popoutPlayer) { - this.popoutPlayer = popoutPlayer; + public void setVideoContainer(VideoContainer videoContainer) { + this.videoContainer = videoContainer; } }; PopoutListener popoutListener = new PopoutListener() { @Override public void popoutVideo(MediaTrack videoTrack) { - VideoPlayer popoutPlayer; + VideoContainer videoContainer; if (videoTrack.isYoutube()) { - popoutPlayer = new YoutubeWindowPlayer(videoTrack, popupCloseListener); + videoContainer = new YoutubeWindowPlayer(videoTrack, popupCloseListener); } else { - popoutPlayer = new VideoWindowPlayer(videoTrack, popupCloseListener); + videoContainer = new VideoWindowPlayer(videoTrack, popupCloseListener); } - popupCloseListener.setPopoutPlayer(popoutPlayer); + popupCloseListener.setVideoContainer(videoContainer); videoDeselected(videoTrack); } }; - final VideoPlayer popupPlayer; - + final VideoSynchPlayer videoPlayer; boolean showSynchControls = this.user != null; + if (videoTrack.isYoutube()) { // popupPlayer = new YoutubeWindowPlayer(videoTrack, popCloseListener); - popupPlayer = new YoutubeEmbeddedPlayer(videoTrack, getRaceStartTime(), showSynchControls, raceTimer, - mediaService, errorReporter, popupCloseListener, popoutListener); + videoPlayer = new VideoYoutubePlayer(videoTrack, getRaceStartTime(), showSynchControls, raceTimer); } else { // popupPlayer = new VideoWindowPlayer(videoTrack, popCloseListener); - popupPlayer = new VideoEmbeddedPlayer(videoTrack, getRaceStartTime(), showSynchControls, raceTimer, - mediaService, errorReporter, popupCloseListener, popoutListener); + videoPlayer = new VideoHtmlPlayer(videoTrack, getRaceStartTime(), showSynchControls, raceTimer); } - registerVideoPlayer(videoTrack, popupPlayer); + VideoFloatingContainer videoFloatingContainer = new VideoFloatingContainer(videoPlayer, showSynchControls, mediaService, errorReporter, popupCloseListener, popoutListener); + + registerVideoContainer(videoTrack, videoFloatingContainer); } else { // nothing changed } } - private void registerVideoPlayer(final MediaTrack videoTrack, final VideoPlayer popupPlayer) { - videoPlayers.put(videoTrack, popupPlayer); + private void registerVideoContainer(final MediaTrack videoTrack, final VideoContainer videoContainer) { + VideoPlayer videoPlayer = videoContainer.getVideoPlayer(); + activeVideoContainers.put(videoTrack, videoContainer); if ((activeAudioPlayer != null) && (activeAudioPlayer.getMediaTrack() == videoTrack)) { // selected video track // has been playing as // audio-only activeAudioPlayer.pauseMedia(); - activeAudioPlayer = popupPlayer; - popupPlayer.setMuted(false); + activeAudioPlayer = videoContainer.getVideoPlayer(); + videoPlayer.setMuted(false); } else { - popupPlayer.setMuted(true); + videoPlayer.setMuted(true); } - synchPlayState(popupPlayer); + synchPlayState(videoPlayer); updateToggleButton(); } @@ -421,9 +453,10 @@ public class MediaSelector implements PlayStateListener, TimeListener, @Override public void videoDeselected(MediaTrack videoTrack) { mediaSelectionDialog.unselectVideo(videoTrack); - MediaPlayer removedVideoPlayer = videoPlayers.remove(videoTrack); - if (removedVideoPlayer != null) { - removedVideoPlayer.close(); + VideoContainer removedVideoContainer = activeVideoContainers.remove(videoTrack); + if (removedVideoContainer != null) { + removedVideoContainer.shutDown(); + VideoPlayer removedVideoPlayer = removedVideoContainer.getVideoPlayer(); if (removedVideoPlayer == activeAudioPlayer) { // in case this video has been the sound source, replace the // video player with a dedicated audio player if (removedVideoPlayer.getMediaTrack().isYoutube()) { @@ -439,12 +472,12 @@ public class MediaSelector implements PlayStateListener, TimeListener, } private void updateToggleButton() { - toggleMediaButton.setValue((activeAudioPlayer != null) || (!videoPlayers.isEmpty())); + toggleMediaButton.setValue((activeAudioPlayer != null) || (!activeVideoContainers.isEmpty())); } private void assignNewAudioPlayer(MediaTrack audioTrack) { if (audioTrack != null) { - activeAudioPlayer = new AudioPlayer(audioTrack); + activeAudioPlayer = new AudioHtmlPlayer(audioTrack); synchPlayState(activeAudioPlayer); } else { @@ -490,15 +523,15 @@ public class MediaSelector implements PlayStateListener, TimeListener, private void clear() { if (activeAudioPlayer != null) { - videoPlayers.remove(activeAudioPlayer); // just to ensure that a potentially audio-playing video player is + activeVideoContainers.remove(activeAudioPlayer.getMediaTrack()); // just to ensure that a potentially audio-playing video player is // not destroyed a second time in the following video loop. - activeAudioPlayer.close(); + activeAudioPlayer.shutDown(); activeAudioPlayer = null; } - for (MediaPlayer videoControl : videoPlayers.values()) { - videoControl.close(); + for (VideoContainer videoContainer : activeVideoContainers.values()) { + videoContainer.shutDown(); } - videoPlayers.clear(); + activeVideoContainers.clear(); updateToggleButton(); } @@ -563,7 +596,7 @@ public class MediaSelector implements PlayStateListener, TimeListener, private void showSelectionDialog() { MediaTrack playingAudioTrack = activeAudioPlayer != null ? activeAudioPlayer.getMediaTrack() : null; - Set playingVideoTracks = videoPlayers.keySet(); + Set playingVideoTracks = activeVideoContainers.keySet(); Collection reachableVideoTracks = new ArrayList(); Collection reachableAudioTracks = new ArrayList(); @@ -573,7 +606,14 @@ public class MediaSelector implements PlayStateListener, TimeListener, case video: reachableVideoTracks.add(mediaTrack); case audio: // intentional fall through - reachableAudioTracks.add(mediaTrack); + if(userAgent.getType().equals(AgentTypes.FIREFOX)) { + if(mediaTrack.isYoutube()) { + // only youtube audio tracks work with firefox + reachableAudioTracks.add(mediaTrack); + } + } else { + reachableAudioTracks.add(mediaTrack); + } } } } @@ -581,10 +621,12 @@ public class MediaSelector implements PlayStateListener, TimeListener, boolean showAddButton = MediaSelector.this.user != null; mediaSelectionDialog.show(reachableVideoTracks, playingVideoTracks, reachableAudioTracks, playingAudioTrack, showAddButton, toggleMediaButton); + showingMediaSelection = true; } private void hideSelectionDialog() { mediaSelectionDialog.hide(); + showingMediaSelection = false; } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaSynchAdapter.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaSynchAdapter.java index 758c19cb60b..a99e38bd441 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaSynchAdapter.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaSynchAdapter.java @@ -3,6 +3,10 @@ package com.sap.sailing.gwt.ui.client.media; import com.sap.sailing.domain.common.media.MediaTrack; public interface MediaSynchAdapter { + + public interface EditFlag { + boolean isEditing(); + } long getOffset(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaSynchControl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaSynchControl.java index d2f4dc6046b..e3509adb91e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaSynchControl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaSynchControl.java @@ -10,8 +10,9 @@ import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.domain.common.media.MediaTrack; import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.MediaServiceAsync; +import com.sap.sailing.gwt.ui.client.media.MediaSynchAdapter.EditFlag; -public class MediaSynchControl { +public class MediaSynchControl implements EditFlag { private static final int FAST = 1000; private static final int SLOW = 100; @@ -208,6 +209,7 @@ public class MediaSynchControl { return mainPanel; } + @Override public boolean isEditing() { return isEditing; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/VideoContainer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/VideoContainer.java new file mode 100644 index 00000000000..bb6eca45c1e --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/VideoContainer.java @@ -0,0 +1,19 @@ +package com.sap.sailing.gwt.ui.client.media; + +import com.sap.sailing.gwt.ui.client.media.shared.VideoPlayer; + +/** + * Interface to decouple container lifecycle from VideoPlayer functionality. + * @author D047974 + * + */ +public interface VideoContainer { + + /** + * Terminate all playing, release resources and make the container disappear from the UI. + */ + void shutDown(); + + VideoPlayer getVideoPlayer(); + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/VideoEmbeddedPlayer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/VideoEmbeddedPlayer.java deleted file mode 100644 index a3ccd8f26c4..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/VideoEmbeddedPlayer.java +++ /dev/null @@ -1,142 +0,0 @@ -package com.sap.sailing.gwt.ui.client.media; - -import java.util.Date; - -import com.google.gwt.event.logical.shared.CloseEvent; -import com.google.gwt.event.logical.shared.CloseHandler; -import com.google.gwt.media.client.MediaBase; -import com.google.gwt.media.client.Video; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.PopupPanel; -import com.sap.sailing.domain.common.media.MediaTrack; -import com.sap.sailing.gwt.ui.client.ErrorReporter; -import com.sap.sailing.gwt.ui.client.MediaServiceAsync; -import com.sap.sailing.gwt.ui.client.media.popup.PopupWindowPlayer.PopupCloseListener; -import com.sap.sailing.gwt.ui.client.media.shared.VideoPlayer; -import com.sap.sse.gwt.client.dialog.WindowBox; -import com.sap.sse.gwt.client.player.Timer; - -public class VideoEmbeddedPlayer extends AbstractEmbeddedMediaPlayer implements VideoPlayer, CloseHandler, MediaSynchAdapter { - - private final WindowBox dialogBox; - private MediaSynchControl mediaSynchControl; - - private final long raceStartTimeMillis; - - private final Timer raceTimer; - private final PopupCloseListener popupCloseListener; - private final PopoutListener popoutListener; - - public VideoEmbeddedPlayer(final MediaTrack videoTrack, long raceStartTimeMillis, boolean showSynchControls, Timer raceTimer, MediaServiceAsync mediaService, ErrorReporter errorReporter, PopupCloseListener popCloseListener, PopoutListener popoutListener) { - super(videoTrack); - this.raceTimer = raceTimer; - this.popoutListener = popoutListener; - - - this.raceStartTimeMillis = raceStartTimeMillis; - this.popupCloseListener = popCloseListener; - - FlowPanel rootPanel = new FlowPanel(); - rootPanel.addStyleName("video-root-panel"); - if (mediaControl != null) { - - rootPanel.add(mediaControl); - - if (showSynchControls) { - mediaSynchControl = new MediaSynchControl(this, mediaService, errorReporter); - mediaSynchControl.widget().addStyleName("media-synch-control"); - - rootPanel.add(mediaSynchControl.widget()); - } - - } - this.dialogBox = new WindowBox(videoTrack.title, videoTrack.toString(), rootPanel, new WindowBox.PopoutHandler() { - - @Override - public void popout() { - VideoEmbeddedPlayer.this.popoutListener.popoutVideo(videoTrack); - - } - }); - dialogBox.addCloseHandler(this); - - show(); - } - - @Override - protected MediaBase createMediaControl() { - return Video.createIfSupported(); - } - - private void show() { - dialogBox.show(); - } - - private void hide() { - dialogBox.hide(); - } - - @Override - public void onClose(CloseEvent event) { - pauseMedia(); - this.popupCloseListener.popupClosed(); - } - - @Override - public void close() { - super.close(); - hide(); - } - - @Override - public long getOffset() { - return getMediaTrack().startTime.getTime() - raceStartTimeMillis; - } - - @Override - public void changeOffsetBy(long delta) { - getMediaTrack().startTime = new Date(getMediaTrack().startTime.getTime() + delta); - forceAlign(); - } - - @Override - public void updateOffset() { - getMediaTrack().startTime = new Date(raceTimer.getTime().getTime() - getCurrentMediaTimeMillis()); - } - - @Override - public void setControlsVisible(boolean isVisible) { - mediaControl.setControls(isVisible); - } - - @Override - public void pauseRace() { - raceTimer.pause(); - } - - @Override - public void playMedia() { - if (!isEdited()) { - super.playMedia(); - } - } - - @Override - public void pauseMedia() { - if (!isEdited()) { - super.pauseMedia(); - } - } - - @Override - protected void alignTime() { - if (!isEdited()) { - super.alignTime(); - } - } - - private boolean isEdited() { - return mediaSynchControl != null && mediaSynchControl.isEditing(); - } - -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/VideoFloatingContainer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/VideoFloatingContainer.java new file mode 100644 index 00000000000..807efb0542b --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/VideoFloatingContainer.java @@ -0,0 +1,80 @@ +package com.sap.sailing.gwt.ui.client.media; + +import com.google.gwt.event.logical.shared.CloseEvent; +import com.google.gwt.event.logical.shared.CloseHandler; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.PopupPanel; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.MediaServiceAsync; +import com.sap.sailing.gwt.ui.client.media.popup.PopoutWindowPlayer.PopoutCloseListener; +import com.sap.sailing.gwt.ui.client.media.shared.VideoPlayer; +import com.sap.sailing.gwt.ui.client.media.shared.VideoSynchPlayer; +import com.sap.sse.gwt.client.dialog.WindowBox; + +public class VideoFloatingContainer implements VideoContainer { + + private final WindowBox dialogBox; + private final MediaSynchControl mediaSynchControl; + + private final PopoutCloseListener popupCloseListener; + private final PopoutListener popoutListener; + private final VideoSynchPlayer videoPlayer; + + public VideoFloatingContainer(VideoSynchPlayer videoPlayer, boolean showSynchControls, MediaServiceAsync mediaService, ErrorReporter errorReporter, PopoutCloseListener popCloseListener, PopoutListener popoutListener) { + this.videoPlayer = videoPlayer; + this.popoutListener = popoutListener; + + this.popupCloseListener = popCloseListener; + + FlowPanel rootPanel = new FlowPanel(); + rootPanel.addStyleName("video-root-panel"); + rootPanel.add(videoPlayer.getWidget()); + + if (showSynchControls) { + mediaSynchControl = new MediaSynchControl(this.videoPlayer, mediaService, errorReporter); + mediaSynchControl.widget().addStyleName("media-synch-control"); + + rootPanel.add(mediaSynchControl.widget()); + } else { + mediaSynchControl = null; + } + + videoPlayer.setEditFlag(mediaSynchControl); + + this.dialogBox = new WindowBox(videoPlayer.getMediaTrack().title, videoPlayer.getMediaTrack().toString(), rootPanel, new WindowBox.PopoutHandler() { + + @Override + public void popout() { + VideoFloatingContainer.this.popoutListener.popoutVideo(VideoFloatingContainer.this.videoPlayer.getMediaTrack()); + + } + }); + dialogBox.addCloseHandler(new CloseHandler() { + + @Override + public void onClose(CloseEvent event) { + VideoFloatingContainer.this.videoPlayer.pauseMedia(); + VideoFloatingContainer.this.popupCloseListener.popoutClosed(); + } + + }); + + show(); + } + + void show() { + dialogBox.show(); + } + + @Override + public void shutDown() { + videoPlayer.shutDown(); + dialogBox.hide(); + } + + @Override + public VideoPlayer getVideoPlayer() { + return videoPlayer; + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/VideoHtmlPlayer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/VideoHtmlPlayer.java new file mode 100644 index 00000000000..0de5460a807 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/VideoHtmlPlayer.java @@ -0,0 +1,90 @@ +package com.sap.sailing.gwt.ui.client.media; + +import java.util.Date; + +import com.google.gwt.media.client.MediaBase; +import com.google.gwt.media.client.Video; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.media.MediaTrack; +import com.sap.sailing.gwt.ui.client.media.shared.VideoSynchPlayer; +import com.sap.sailing.gwt.ui.client.media.shared.WithWidget; +import com.sap.sse.gwt.client.player.Timer; + +public class VideoHtmlPlayer extends AbstractHtmlMediaPlayer implements VideoSynchPlayer, MediaSynchAdapter, WithWidget { + + private final long raceStartTimeMillis; + private final Timer raceTimer; + private EditFlag editFlag; + + public VideoHtmlPlayer(final MediaTrack videoTrack, long raceStartTimeMillis, boolean showSynchControls, Timer raceTimer) { + super(videoTrack); + this.raceTimer = raceTimer; + this.raceStartTimeMillis = raceStartTimeMillis; + } + + @Override + protected MediaBase createMediaElement() { + return Video.createIfSupported(); + } + + @Override + public long getOffset() { + return getMediaTrack().startTime.getTime() - raceStartTimeMillis; + } + + @Override + public void changeOffsetBy(long delta) { + getMediaTrack().startTime = new Date(getMediaTrack().startTime.getTime() + delta); + forceAlign(); + } + + @Override + public void updateOffset() { + getMediaTrack().startTime = new Date(raceTimer.getTime().getTime() - getCurrentMediaTimeMillis()); + } + + @Override + public void setControlsVisible(boolean isVisible) { + mediaElement.setControls(isVisible); + } + + @Override + public void pauseRace() { + raceTimer.pause(); + } + + @Override + public void playMedia() { + if (!isEditing()) { + super.playMedia(); + } + } + + @Override + public void pauseMedia() { + if (!isEditing()) { + super.pauseMedia(); + } + } + + @Override + protected void alignTime() { + if (!isEditing()) { + super.alignTime(); + } + } + + @Override + public Widget getWidget() { + return mediaElement; + } + + private boolean isEditing() { + return editFlag != null && editFlag.isEditing(); + } + + public void setEditFlag(EditFlag editFlag) { + this.editFlag = editFlag; + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/VideoYoutubePlayer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/VideoYoutubePlayer.java new file mode 100644 index 00000000000..ec337dd3f5c --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/VideoYoutubePlayer.java @@ -0,0 +1,213 @@ +package com.sap.sailing.gwt.ui.client.media; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import com.google.gwt.event.logical.shared.AttachEvent; +import com.google.gwt.event.logical.shared.AttachEvent.Handler; +import com.google.gwt.user.client.ui.Panel; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.media.MediaTrack; +import com.sap.sailing.gwt.ui.client.media.shared.AbstractMediaPlayer; +import com.sap.sailing.gwt.ui.client.media.shared.VideoSynchPlayer; +import com.sap.sailing.gwt.ui.client.media.shared.WithWidget; +import com.sap.sse.gwt.client.player.Timer; + +public class VideoYoutubePlayer extends AbstractMediaPlayer implements VideoSynchPlayer, MediaSynchAdapter, WithWidget { + + private interface DeferredAction { + void execute(); + } + + private final Panel videoContainer; + + private static int videoCounter; + + private final long raceStartTimeMillis; + + private final Timer raceTimer; + + private EditFlag editFlag; + + private YoutubeVideoControl videoControl; + + /** + * Required to indicate whether this control has been requested to close. + * In this case it must not call any player functions anymore + * not to cause null-access error due to missing DOM elements. + * + */ + private boolean closing = false; + + private final List deferredActions = new ArrayList(); + + + public VideoYoutubePlayer(final MediaTrack videoTrack, long raceStartTimeMillis, final boolean showControls, Timer raceTimer) { + super(videoTrack); + this.raceTimer = raceTimer; + this.raceStartTimeMillis = raceStartTimeMillis; + + this.videoContainer = new SimplePanel(); + final String videoContainerId = "videoContainer-" + videoTrack.url + ++videoCounter; + this.videoContainer.getElement().setId(videoContainerId); + this.videoContainer.getElement().setInnerText("When the Youtube video doesn't show up, click the popout button at the upper right corner to open the video in a dedicated browser window."); + this.videoContainer.addAttachHandler(new Handler() { + + @Override + public void onAttachOrDetach(AttachEvent event) { + //The videoContainer must be attached to the DOM before the Youtube player can be created. + //If not, Youtube API won't find the container which is referenced by its id attribute. + if (event.isAttached()) { + videoControl = new YoutubeVideoControl(videoTrack.url, videoContainerId, showControls); + for (DeferredAction deferredAction : deferredActions) { + deferredAction.execute(); + } + } + } + }); + + } + + private void defer(DeferredAction deferredAction) { + deferredActions.add(deferredAction); + } + + @Override + public void shutDown() { + if (videoControl != null) { + videoControl.pause(); + } else { + defer(new DeferredAction() {public void execute() {shutDown();}}); + } + } + + @Override + public long getOffset() { + return getMediaTrack().startTime.getTime() - raceStartTimeMillis; + } + + @Override + public void changeOffsetBy(long delta) { + getMediaTrack().startTime = new Date(getMediaTrack().startTime.getTime() + delta); + forceAlign(); + } + + @Override + public void updateOffset() { + getMediaTrack().startTime = new Date(raceTimer.getTime().getTime() - getCurrentMediaTimeMillis()); + } + + @Override + public void pauseRace() { + raceTimer.pause(); + } + + @Override + public void setControlsVisible(final boolean isVisible) { + if (videoControl != null) { + videoControl.setControlsVisible(isVisible); + } else { + defer(new DeferredAction() {public void execute() {setControlsVisible(isVisible);}}); + } + } + + @Override + public boolean isMediaPaused() { + if (videoControl != null) { + return videoControl.isPaused(); + } else { + return true; + } + } + + @Override + public void pauseMedia() { + if (!isEditing() && !this.closing) { + if (videoControl != null) { + videoControl.pause(); + } else { + defer(new DeferredAction() {public void execute() {pauseMedia();}}); + } + } + } + + @Override + public void playMedia() { + if (!isEditing() && !this.closing) { + if (videoControl != null) { + videoControl.play(); + } else { + defer(new DeferredAction() {public void execute() {playMedia();}}); + } + } + } + + @Override + public double getDuration() { + if (videoControl != null) { + return videoControl.getDuration(); + } else { + return 0; + } + } + + @Override + public double getCurrentMediaTime() { + if (videoControl != null) { + return videoControl.getCurrentTime(); + } else { + return 0; + } + } + + @Override + public void setCurrentMediaTime(final double mediaTime) { + if (videoControl != null) { + videoControl.setCurrentTime(mediaTime); + } else { + defer(new DeferredAction() {public void execute() {setCurrentMediaTime(mediaTime);}}); + } + } + + @Override + public void setPlaybackSpeed(final double newPlaySpeedFactor) { + if (videoControl != null) { + videoControl.setPlaybackSpeed(newPlaySpeedFactor); + } else { + defer(new DeferredAction() {public void execute() {setPlaybackSpeed(newPlaySpeedFactor);}}); + } + } + + @Override + public void setMuted(final boolean isToBeMuted) { + if (videoControl != null) { + videoControl.setMuted(isToBeMuted); + } else { + defer(new DeferredAction() {public void execute() {setMuted(isToBeMuted);}}); + } + } + + @Override + protected void alignTime() { + if (!isEditing()) { + super.alignTime(); + } + } + + @Override + public Widget getWidget() { + return videoContainer; + } + + @Override + public void setEditFlag(EditFlag editFlag) { + this.editFlag = editFlag; + } + + private boolean isEditing() { + return editFlag != null && editFlag.isEditing(); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/YoutubeEmbeddedPlayer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/YoutubeEmbeddedPlayer.java deleted file mode 100644 index 436ded64779..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/YoutubeEmbeddedPlayer.java +++ /dev/null @@ -1,184 +0,0 @@ -package com.sap.sailing.gwt.ui.client.media; - -import java.util.Date; - -import com.google.gwt.event.logical.shared.CloseEvent; -import com.google.gwt.event.logical.shared.CloseHandler; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.Panel; -import com.google.gwt.user.client.ui.PopupPanel; -import com.google.gwt.user.client.ui.SimplePanel; -import com.sap.sailing.domain.common.media.MediaTrack; -import com.sap.sailing.gwt.ui.client.ErrorReporter; -import com.sap.sailing.gwt.ui.client.MediaServiceAsync; -import com.sap.sailing.gwt.ui.client.media.popup.PopupWindowPlayer.PopupCloseListener; -import com.sap.sailing.gwt.ui.client.media.shared.AbstractMediaPlayer; -import com.sap.sailing.gwt.ui.client.media.shared.VideoPlayer; -import com.sap.sse.gwt.client.dialog.WindowBox; -import com.sap.sse.gwt.client.player.Timer; - -public class YoutubeEmbeddedPlayer extends AbstractMediaPlayer implements VideoPlayer, CloseHandler, MediaSynchAdapter { - - private static int videoCounter; - - private final WindowBox dialogBox; - private MediaSynchControl mediaSynchControl; - - private final long raceStartTimeMillis; - - private final Timer raceTimer; - private final PopupCloseListener popupCloseListener; - private final YoutubeVideoControl videoControl; - private final PopoutListener popoutListener; - - /** - * Required to indicate whether this control has been requested to close. - * Then, it must not call any player functions anymore - * not to cause null-access error due to missing DOM elements. - */ - private boolean closing = false; - - public YoutubeEmbeddedPlayer(final MediaTrack videoTrack, long raceStartTimeMillis, boolean showSynchControls, Timer raceTimer, MediaServiceAsync mediaService, ErrorReporter errorReporter, PopupCloseListener popupCloseListener, PopoutListener popoutListener) { - super(videoTrack); - this.raceTimer = raceTimer; - this.raceStartTimeMillis = raceStartTimeMillis; - this.popupCloseListener = popupCloseListener; - this.popoutListener = popoutListener; - - FlowPanel rootPanel = new FlowPanel(); - rootPanel.addStyleName("video-root-panel"); - - Panel videoContainer = new SimplePanel(); - - String videoContainerId = "videoContainer-" + videoTrack.url + ++videoCounter; - videoContainer.getElement().setId(videoContainerId); - videoContainer.getElement().setInnerText("When the Youtube video doesn't show up, click the popout button at the upper right corner to open the video in a dedicated browser window."); - - rootPanel.add(videoContainer.asWidget()); - - this.dialogBox = new WindowBox(videoTrack.title, videoTrack.toString(), rootPanel, new WindowBox.PopoutHandler() { - - @Override - public void popout() { - YoutubeEmbeddedPlayer.this.popoutListener.popoutVideo(videoTrack); - - } - }); - dialogBox.addCloseHandler(this); - - //first show dialog to make video container visible in DOM - show(); - - //then use Youtube API to render load video into video container - videoControl = new YoutubeVideoControl(videoTrack.url, videoContainerId, showSynchControls); - - //then show media synch controls which refer to the video control - if (showSynchControls) { - mediaSynchControl = new MediaSynchControl(this, mediaService, errorReporter); - mediaSynchControl.widget().addStyleName("media-synch-control"); - rootPanel.add(mediaSynchControl.widget()); - } - - } - - private void show() { - dialogBox.show(); - } - - private void hide() { - dialogBox.hide(); - } - - @Override - public void onClose(CloseEvent event) { - this.closing = true; - this.popupCloseListener.popupClosed(); - } - - @Override - public void close() { - hide(); - } - - @Override - public long getOffset() { - return getMediaTrack().startTime.getTime() - raceStartTimeMillis; - } - - @Override - public void changeOffsetBy(long delta) { - getMediaTrack().startTime = new Date(getMediaTrack().startTime.getTime() + delta); - forceAlign(); - } - - @Override - public void updateOffset() { - getMediaTrack().startTime = new Date(raceTimer.getTime().getTime() - getCurrentMediaTimeMillis()); - } - - @Override - public void setControlsVisible(boolean isVisible) { - videoControl.setControlsVisible(isVisible); - } - - @Override - public void pauseRace() { - raceTimer.pause(); - } - - @Override - public boolean isMediaPaused() { - return videoControl.isPaused(); - } - - @Override - public void pauseMedia() { - if (!isEdited() && !this.closing) { - videoControl.pause(); - } - } - - @Override - public void playMedia() { - if (!isEdited() && !this.closing) { - videoControl.play(); - } - } - - @Override - public double getDuration() { - return videoControl.getDuration(); - } - - @Override - public double getCurrentMediaTime() { - return videoControl.getCurrentTime(); - } - - @Override - public void setCurrentMediaTime(double mediaTime) { - videoControl.setCurrentTime(mediaTime); - } - - @Override - public void setPlaybackSpeed(double newPlaySpeedFactor) { - videoControl.setPlaybackSpeed(newPlaySpeedFactor); - } - - @Override - public void setMuted(boolean isToBeMuted) { - videoControl.setMuted(isToBeMuted); - } - - @Override - protected void alignTime() { - if (!isEdited()) { - super.alignTime(); - } - } - - private boolean isEdited() { - return mediaSynchControl != null && mediaSynchControl.isEditing(); - } - -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/popup/PopupWindowPlayer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/popup/PopoutWindowPlayer.java similarity index 81% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/popup/PopupWindowPlayer.java rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/popup/PopoutWindowPlayer.java index a3f11ca7955..3cf773e46c3 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/popup/PopupWindowPlayer.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/popup/PopoutWindowPlayer.java @@ -3,21 +3,22 @@ package com.sap.sailing.gwt.ui.client.media.popup; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.user.client.Window; import com.sap.sailing.domain.common.media.MediaTrack; +import com.sap.sailing.gwt.ui.client.media.VideoContainer; import com.sap.sailing.gwt.ui.client.media.shared.AbstractMediaPlayer; import com.sap.sailing.gwt.ui.client.media.shared.VideoPlayer; -public abstract class PopupWindowPlayer extends AbstractMediaPlayer implements VideoPlayer { +public abstract class PopoutWindowPlayer extends AbstractMediaPlayer implements VideoPlayer, VideoContainer { - public interface PopupCloseListener { - void popupClosed(); + public interface PopoutCloseListener { + void popoutClosed(); - void setPopoutPlayer(VideoPlayer popoutPlayer); + void setVideoContainer(VideoContainer videoContainer); } private final JavaScriptObject playerWindow; - private final PopupCloseListener popupCloseHandler; + private final PopoutCloseListener popupCloseHandler; - protected PopupWindowPlayer(MediaTrack mediaTrack, PopupCloseListener popupCloseListener) { + protected PopoutWindowPlayer(MediaTrack mediaTrack, PopoutCloseListener popupCloseListener) { super(mediaTrack); this.popupCloseHandler = popupCloseListener; @@ -39,8 +40,8 @@ public abstract class PopupWindowPlayer extends AbstractMediaPlayer implements V }-*/; @Override - public native void close() /*-{ - var playerWindow = this.@com.sap.sailing.gwt.ui.client.media.popup.PopupWindowPlayer::playerWindow; + public native void shutDown() /*-{ + var playerWindow = this.@com.sap.sailing.gwt.ui.client.media.popup.PopoutWindowPlayer::playerWindow; if (!playerWindow.closed) { playerWindow.close(); } @@ -48,9 +49,9 @@ public abstract class PopupWindowPlayer extends AbstractMediaPlayer implements V native JavaScriptObject registerNativeStuff() /*-{ var that = this; - var window = that.@com.sap.sailing.gwt.ui.client.media.popup.PopupWindowPlayer::playerWindow; + var window = that.@com.sap.sailing.gwt.ui.client.media.popup.PopoutWindowPlayer::playerWindow; window.onbeforeunload = function() { - that.@com.sap.sailing.gwt.ui.client.media.popup.PopupWindowPlayer::onClosingPopup()(); + that.@com.sap.sailing.gwt.ui.client.media.popup.PopoutWindowPlayer::onClosingPopup()(); } window.parent.deferredPlayState = { deferredIsPlaying: false, @@ -62,12 +63,12 @@ public abstract class PopupWindowPlayer extends AbstractMediaPlayer implements V private void onClosingPopup() { pauseMedia(); - popupCloseHandler.popupClosed(); + popupCloseHandler.popoutClosed(); } @Override public native void playMedia() /*-{ - var window = this.@com.sap.sailing.gwt.ui.client.media.popup.PopupWindowPlayer::playerWindow; + var window = this.@com.sap.sailing.gwt.ui.client.media.popup.PopoutWindowPlayer::playerWindow; if (!window.parent.videoPlayer) { window.parent.deferredPlayState.deferredIsPlaying = true; } else { @@ -78,7 +79,7 @@ public abstract class PopupWindowPlayer extends AbstractMediaPlayer implements V @Override public native void pauseMedia() /*-{ - var window = this.@com.sap.sailing.gwt.ui.client.media.popup.PopupWindowPlayer::playerWindow; + var window = this.@com.sap.sailing.gwt.ui.client.media.popup.PopoutWindowPlayer::playerWindow; if (!window.parent.videoPlayer) { window.parent.deferredPlayState.deferredIsPlaying = false; } else { @@ -89,7 +90,7 @@ public abstract class PopupWindowPlayer extends AbstractMediaPlayer implements V @Override public native void setCurrentMediaTime(double mediaTime) /*-{ - var window = this.@com.sap.sailing.gwt.ui.client.media.popup.PopupWindowPlayer::playerWindow; + var window = this.@com.sap.sailing.gwt.ui.client.media.popup.PopoutWindowPlayer::playerWindow; if (!window.parent.videoPlayer) { window.parent.deferredPlayState.deferredMediaTime = mediaTime; } else { @@ -100,7 +101,7 @@ public abstract class PopupWindowPlayer extends AbstractMediaPlayer implements V @Override public native void setMuted(boolean muted) /*-{ - var window = this.@com.sap.sailing.gwt.ui.client.media.popup.PopupWindowPlayer::playerWindow; + var window = this.@com.sap.sailing.gwt.ui.client.media.popup.PopoutWindowPlayer::playerWindow; if (!window.parent.videoPlayer) { window.parent.deferredPlayState.deferredIsMuted = muted; } else { @@ -110,7 +111,7 @@ public abstract class PopupWindowPlayer extends AbstractMediaPlayer implements V @Override public native boolean isMediaPaused() /*-{ - var window = this.@com.sap.sailing.gwt.ui.client.media.popup.PopupWindowPlayer::playerWindow; + var window = this.@com.sap.sailing.gwt.ui.client.media.popup.PopoutWindowPlayer::playerWindow; if (!window.parent.videoPlayer) { return !window.parent.deferredPlayState.deferredIsPlaying; } else { @@ -120,7 +121,7 @@ public abstract class PopupWindowPlayer extends AbstractMediaPlayer implements V @Override public native double getDuration() /*-{ - var window = this.@com.sap.sailing.gwt.ui.client.media.popup.PopupWindowPlayer::playerWindow; + var window = this.@com.sap.sailing.gwt.ui.client.media.popup.PopoutWindowPlayer::playerWindow; if (!window.parent.videoPlayer) { return NaN; } else { @@ -130,7 +131,7 @@ public abstract class PopupWindowPlayer extends AbstractMediaPlayer implements V @Override public native double getCurrentMediaTime() /*-{ - var window = this.@com.sap.sailing.gwt.ui.client.media.popup.PopupWindowPlayer::playerWindow; + var window = this.@com.sap.sailing.gwt.ui.client.media.popup.PopoutWindowPlayer::playerWindow; if (!window.parent.videoPlayer) { return window.parent.deferredPlayState.deferredMediaTime; } else { @@ -140,12 +141,18 @@ public abstract class PopupWindowPlayer extends AbstractMediaPlayer implements V @Override public native void setPlaybackSpeed(double playbackSpeed) /*-{ - var window = this.@com.sap.sailing.gwt.ui.client.media.popup.PopupWindowPlayer::playerWindow; + var window = this.@com.sap.sailing.gwt.ui.client.media.popup.PopoutWindowPlayer::playerWindow; if (!window.parent.videoPlayer) { window.parent.deferredPlayState.deferredPlaybackSpeed = playbackSpeed; } else { window.parent.videoPlayer.setPlaybackSpeed(playbackSpeed); } }-*/; + + + @Override + public VideoPlayer getVideoPlayer() { + return this; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/popup/VideoWindowPlayer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/popup/VideoWindowPlayer.java index 682dd6d36d3..5068e3c1eeb 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/popup/VideoWindowPlayer.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/popup/VideoWindowPlayer.java @@ -3,9 +3,9 @@ package com.sap.sailing.gwt.ui.client.media.popup; import com.google.gwt.http.client.URL; import com.sap.sailing.domain.common.media.MediaTrack; -public class VideoWindowPlayer extends PopupWindowPlayer { +public class VideoWindowPlayer extends PopoutWindowPlayer { - public VideoWindowPlayer(MediaTrack mediaTrack, PopupCloseListener popupCloseListener) { + public VideoWindowPlayer(MediaTrack mediaTrack, PopoutCloseListener popupCloseListener) { super(mediaTrack, popupCloseListener); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/popup/YoutubeWindowPlayer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/popup/YoutubeWindowPlayer.java index 8413bd50562..df9b8030261 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/popup/YoutubeWindowPlayer.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/popup/YoutubeWindowPlayer.java @@ -3,9 +3,9 @@ package com.sap.sailing.gwt.ui.client.media.popup; import com.google.gwt.http.client.URL; import com.sap.sailing.domain.common.media.MediaTrack; -public class YoutubeWindowPlayer extends PopupWindowPlayer { +public class YoutubeWindowPlayer extends PopoutWindowPlayer { - public YoutubeWindowPlayer(MediaTrack mediaTrack, PopupCloseListener popupCloseListener) { + public YoutubeWindowPlayer(MediaTrack mediaTrack, PopoutCloseListener popupCloseListener) { super(mediaTrack, popupCloseListener); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/shared/AudioPlayer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/shared/AudioPlayer.java new file mode 100644 index 00000000000..dac95076a4e --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/shared/AudioPlayer.java @@ -0,0 +1,5 @@ +package com.sap.sailing.gwt.ui.client.media.shared; + +public interface AudioPlayer extends MediaPlayer { + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/shared/MediaPlayer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/shared/MediaPlayer.java index 6eeecd65843..1e96a42903b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/shared/MediaPlayer.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/shared/MediaPlayer.java @@ -29,7 +29,7 @@ public interface MediaPlayer { void setMuted(boolean isToBeMuted); - void close(); + void shutDown(); boolean isCoveringCurrentRaceTime(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/shared/VideoPlayer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/shared/VideoPlayer.java index 1650478a2f8..630b74534c0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/shared/VideoPlayer.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/shared/VideoPlayer.java @@ -1,7 +1,5 @@ package com.sap.sailing.gwt.ui.client.media.shared; - - public interface VideoPlayer extends MediaPlayer { } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/shared/VideoPlayerWithWidget.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/shared/VideoPlayerWithWidget.java new file mode 100644 index 00000000000..11997c70bfd --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/shared/VideoPlayerWithWidget.java @@ -0,0 +1,6 @@ +package com.sap.sailing.gwt.ui.client.media.shared; + + +public interface VideoPlayerWithWidget extends VideoPlayer, WithWidget { + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/shared/VideoSynchPlayer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/shared/VideoSynchPlayer.java new file mode 100644 index 00000000000..9e3cb83f3bb --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/shared/VideoSynchPlayer.java @@ -0,0 +1,9 @@ +package com.sap.sailing.gwt.ui.client.media.shared; + +import com.sap.sailing.gwt.ui.client.media.MediaSynchAdapter; + +public interface VideoSynchPlayer extends VideoPlayerWithWidget, MediaSynchAdapter { + + void setEditFlag(EditFlag editFlag); + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/shared/WithWidget.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/shared/WithWidget.java new file mode 100644 index 00000000000..72d2315135b --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/shared/WithWidget.java @@ -0,0 +1,9 @@ +package com.sap.sailing.gwt.ui.client.media.shared; + +import com.google.gwt.user.client.ui.Widget; + +public interface WithWidget { + + Widget getWidget(); + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java index cc0cafb22a9..c4bad113fb0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java @@ -42,6 +42,8 @@ import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.AbstractLazyComponent; import com.sap.sailing.gwt.ui.client.shared.components.Component; import com.sap.sse.common.Util; +import com.sap.sse.common.filter.Filter; +import com.sap.sse.common.filter.FilterSet; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; import com.sap.sse.gwt.client.player.TimeListener; import com.sap.sse.gwt.client.player.Timer; @@ -381,4 +383,9 @@ public abstract class AbstractCompetitorLeaderboardChart extends A @Override public void filteredCompetitorsListChanged(Iterable filteredCompetitors) { } + + @Override + public void filterChanged(FilterSet> oldFilterSet, + FilterSet> newFilterSet) { + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorRaceChart.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorRaceChart.java index a5cf8195332..cb5b0dbc970 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorRaceChart.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorRaceChart.java @@ -33,6 +33,7 @@ import org.moxieapps.gwt.highcharts.client.plotOptions.ScatterPlotOptions; import com.google.gwt.i18n.client.NumberFormat; import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.RequiresResize; import com.google.gwt.user.client.ui.Widget; @@ -52,6 +53,8 @@ import com.sap.sailing.gwt.ui.client.shared.components.Component; import com.sap.sailing.gwt.ui.shared.CompetitorRaceDataDTO; import com.sap.sailing.gwt.ui.shared.CompetitorsRaceDataDTO; import com.sap.sse.common.Util; +import com.sap.sse.common.filter.Filter; +import com.sap.sse.common.filter.FilterSet; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; import com.sap.sse.gwt.client.player.TimeRangeWithZoomProvider; import com.sap.sse.gwt.client.player.Timer; @@ -71,7 +74,7 @@ import com.sap.sse.gwt.client.player.Timer.PlayModes; */ public abstract class AbstractCompetitorRaceChart extends AbstractRaceChart implements CompetitorSelectionChangeListener, RequiresResize { - public static final String LODA_COMPETITOR_CHART_DATA_CATEGORY = "loadCompetitorChartData"; + public static final String LOAD_COMPETITOR_CHART_DATA_CATEGORY = "loadCompetitorChartData"; private static final int LINE_WIDTH = 1; @@ -90,16 +93,16 @@ public abstract class AbstractCompetitorRaceChart competitorsToLoad = new ArrayList(); for (CompetitorDTO competitorDTO : getSelectedCompetitors()) { competitorsToLoad.add(competitorDTO); } loadData(from, to, competitorsToLoad, append); } else { - setWidget(noCompetitorsSelectedLabel); + remove(chart); + if (!getChildren().contains(noCompetitorsSelectedLabel)) { + add(noCompetitorsSelectedLabel); + } } } private void loadData(final Date from, final Date to, final List competitors, final boolean append) { - showLoading(stringMessages.loadingCompetitorData()); - ArrayList competitorsToLoad = new ArrayList(); - for (CompetitorDTO competitorDTO : competitors) { - competitorsToLoad.add(competitorDTO); - } + if (isVisible()) { + showLoading(stringMessages.loadingCompetitorData()); + ArrayList competitorsToLoad = new ArrayList(); + for (CompetitorDTO competitorDTO : competitors) { + competitorsToLoad.add(competitorDTO); + } - GetCompetitorsRaceDataAction getCompetitorsRaceDataAction = new GetCompetitorsRaceDataAction(sailingService, - selectedRaceIdentifier, competitorsToLoad, from, to, getStepSize(), getSelectedDetailType(), - leaderboardGroupName, leaderboardName); - asyncActionsExecutor.execute(getCompetitorsRaceDataAction, LODA_COMPETITOR_CHART_DATA_CATEGORY, - new AsyncCallback() { - @Override - public void onSuccess(final CompetitorsRaceDataDTO result) { - hideLoading(); - if (result != null) { - if (result.isEmpty() && chartContainsNoData()) { - setWidget(noDataFoundLabel); + GetCompetitorsRaceDataAction getCompetitorsRaceDataAction = new GetCompetitorsRaceDataAction(sailingService, + selectedRaceIdentifier, competitorsToLoad, from, to, getStepSize(), getSelectedDetailType(), + leaderboardGroupName, leaderboardName); + asyncActionsExecutor.execute(getCompetitorsRaceDataAction, LOAD_COMPETITOR_CHART_DATA_CATEGORY, + new AsyncCallback() { + @Override + public void onSuccess(final CompetitorsRaceDataDTO result) { + hideLoading(); + if (result != null) { + if (result.isEmpty() && chartContainsNoData()) { + setWidget(noDataFoundLabel); + } else { + updateChartSeries(result, append); + } } else { - updateChartSeries(result, append); - } - } else { - if (!append) { - clearChart(); + if (!append) { + clearChart(); + } } } - } - - @Override - public void onFailure(Throwable caught) { - hideLoading(); - errorReporter.reportError(stringMessages.errorFetchingChartData(caught.getMessage()), - timer.getPlayMode() == PlayModes.Live); - } - }); - + + @Override + public void onFailure(Throwable caught) { + hideLoading(); + errorReporter.reportError(stringMessages.errorFetchingChartData(caught.getMessage()), + timer.getPlayMode() == PlayModes.Live); + } + }); + } } private boolean chartContainsNoData() { @@ -280,7 +292,7 @@ public abstract class AbstractCompetitorRaceChart the old title appears - chart.setTitle(new ChartTitle().setText(DetailTypeFormatter.format(selectedDetailType)), - null); + chart.setTitle(new ChartTitle().setText(DetailTypeFormatter.format(selectedDetailType)), null); } } @@ -655,4 +666,10 @@ public abstract class AbstractCompetitorRaceChart filteredCompetitors) { timeChanged(timer.getTime(), null); } + + @Override + public void filterChanged(FilterSet> oldFilterSet, + FilterSet> newFilterSet) { + // nothing to do; if it changes the filtered competitor list, a separate call to filteredCompetitorsListChanged will occur + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractRaceChart.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractRaceChart.java index a531abfc0ee..c4bd1a49dce 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractRaceChart.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractRaceChart.java @@ -1,6 +1,7 @@ package com.sap.sailing.gwt.ui.client.shared.charts; import java.util.Date; +import java.util.Iterator; import org.moxieapps.gwt.highcharts.client.Chart; import org.moxieapps.gwt.highcharts.client.PlotLine; @@ -10,14 +11,21 @@ import org.moxieapps.gwt.highcharts.client.XAxis; import org.moxieapps.gwt.highcharts.client.events.ChartClickEvent; import org.moxieapps.gwt.highcharts.client.events.ChartSelectionEvent; +import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.i18n.client.DateTimeFormat; +import com.google.gwt.user.client.ui.AbsolutePanel; +import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.charts.ChartCssResources.ChartsCss; +import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialog; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; +import com.sap.sse.gwt.client.controls.busyindicator.SimpleBusyIndicator; import com.sap.sse.gwt.client.player.TimeListener; import com.sap.sse.gwt.client.player.TimeRangeChangeListener; import com.sap.sse.gwt.client.player.TimeRangeWithZoomProvider; @@ -25,7 +33,7 @@ import com.sap.sse.gwt.client.player.TimeZoomChangeListener; import com.sap.sse.gwt.client.player.Timer; import com.sap.sse.gwt.client.player.Timer.PlayModes; -public abstract class AbstractRaceChart extends SimplePanel implements RaceSelectionChangeListener, +public abstract class AbstractRaceChart extends AbsolutePanel implements RaceSelectionChangeListener, TimeListener, TimeZoomChangeListener, TimeRangeChangeListener { /** * Used as the turboThreshold for the Highcharts series; this is basically the maximum number of points in a series @@ -52,12 +60,18 @@ public abstract class AbstractRaceChart extends SimplePanel implements RaceSelec protected boolean isLoading = false; protected boolean isZoomed = false; + protected static ChartsCss chartsCss = ChartCssResources.INSTANCE.css(); + /** the tick count must be the same as TimeSlider.TICKCOUNT, otherwise the time ticks will be not synchronized */ private final int TICKCOUNT = 10; private boolean ignoreNextClickEvent; - public AbstractRaceChart(SailingServiceAsync sailingService, Timer timer, TimeRangeWithZoomProvider timeRangeWithZoomProvider, final StringMessages stringMessages, + private final SimpleBusyIndicator busyIndicator; + + private final Button settingsButton; + + protected AbstractRaceChart(SailingServiceAsync sailingService, Timer timer, TimeRangeWithZoomProvider timeRangeWithZoomProvider, final StringMessages stringMessages, AsyncActionsExecutor asyncActionsExecutor, ErrorReporter errorReporter) { this.sailingService = sailingService; this.timer = timer; @@ -68,11 +82,58 @@ public abstract class AbstractRaceChart extends SimplePanel implements RaceSelec timer.addTimeListener(this); timeRangeWithZoomProvider.addTimeZoomChangeListener(this); timeRangeWithZoomProvider.addTimeRangeChangeListener(this); + chartsCss.ensureInjected(); + busyIndicator = new SimpleBusyIndicator(/* busy */ true, 2.0f, chartsCss.busyIndicatorStyle(), chartsCss.busyIndicatorImageStyle()); + settingsButton = createSettingsButton(); + settingsButton.setStyleName(chartsCss.settingsButtonStyle()); + settingsButton.addStyleName(chartsCss.settingsButtonBackgroundImage()); + add(settingsButton); + getElement().getStyle().setMarginRight(12, Unit.PX); + getElement().getStyle().setMarginLeft(12, Unit.PX); + } + + /** + * Subclasses implement this, e.g., by calling {@link SettingsDialog#createSettingsButton(com.sap.sailing.gwt.ui.client.shared.components.Component, StringMessages)}. + * This class's constructor will add the {@link ChartsCss#settingsButtonStyle()} and the {@link ChartsCss#settingsButtonBackgroundImage()}. + */ + protected abstract Button createSettingsButton(); + + /** + * Subclasses need to provide a settings button which will be displayed at a useful position in the layout of this + * complex panel, e.g., in the top right corner. + */ + private Button getSettingsButton() { + return settingsButton; + } + + /** + * Simulates a {@link SimplePanel} behavior by replacing all widgets but the {@link #getSettingsButton() settings button} which is always + * supposed to be visible. If widget is already a child of this panel, it is left unchanged, and all other widgets except for + * the settings button are removed. + */ + protected void setWidget(Widget widget) { + Button settingsButton = getSettingsButton(); + boolean foundWidget = false; + for (Iterator i=getChildren().iterator(); i.hasNext(); ) { + Widget child = i.next(); + if (child == widget) { + foundWidget = true; + } else if (child != settingsButton) { + i.remove(); + } + } + if (!foundWidget) { + add(widget); + } } protected void showLoading(String message) { if (timer.getPlayMode() != PlayModes.Live) { - chart.showLoading(message); + if (chart.isRendered()) { + chart.showLoading(message); + } else { + add(busyIndicator); + } } isLoading = true; } @@ -82,6 +143,7 @@ public abstract class AbstractRaceChart extends SimplePanel implements RaceSelec chart.hideLoading(); } isLoading = false; + remove(busyIndicator); } protected boolean onXAxisSelectionChange(ChartSelectionEvent chartSelectionEvent) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/ChartCssResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/ChartCssResources.java new file mode 100755 index 00000000000..347d997be35 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/ChartCssResources.java @@ -0,0 +1,28 @@ +package com.sap.sailing.gwt.ui.client.shared.charts; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; +import com.google.gwt.resources.client.ImageResource; + +public interface ChartCssResources extends ClientBundle { + public static final ChartCssResources INSTANCE = GWT.create(ChartCssResources.class); + + @Source("com/sap/sailing/gwt/ui/client/shared/charts/Charts.css") + ChartsCss css(); + + @Source("com/sap/sailing/gwt/ui/client/images/SAP_RV_Settings.png") + ImageResource settingsButton(); + + public interface ChartsCss extends CssResource { + String busyIndicatorStyle(); + + String busyIndicatorImageStyle(); + + String settingsButtonStyle(); + + String settingsButtonBackgroundImage(); + + String chartStyle(); + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/Charts.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/Charts.css new file mode 100755 index 00000000000..682a5885950 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/Charts.css @@ -0,0 +1,39 @@ +.busyIndicatorStyle { + display: table-cell; + vertical-align: middle; + text-align: center; +} + +.busyIndicatorImageStyle { + +} + +.settingsButtonStyle { + position: absolute; + right: 0px; + z-index: 1; + margin-top: 14px; + margin-right: 4px; + border: none; +} + +.settingsButtonStyle:focus { + outline: 0 !important; +} + +.settingsButtonStyle:hover { + border: 1px solid #ececec; +} + +.chartStyle { + position: absolute; + left: 0px; + top: 0px; +} + +@sprite .settingsButtonBackgroundImage { + gwt-image: "settingsButton"; + width: 2em; + height: 2em; + background-size: contain; +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MultiCompetitorRaceChart.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MultiCompetitorRaceChart.java index 76eed35adc6..4796de5b3cd 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MultiCompetitorRaceChart.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MultiCompetitorRaceChart.java @@ -1,6 +1,7 @@ package com.sap.sailing.gwt.ui.client.shared.charts; import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Button; import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider; import com.sap.sailing.gwt.ui.client.ErrorReporter; @@ -8,6 +9,7 @@ import com.sap.sailing.gwt.ui.client.RaceSelectionProvider; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.Component; +import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialog; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; @@ -54,6 +56,12 @@ public class MultiCompetitorRaceChart extends AbstractCompetitorRaceChart getSettingsDialogComponent() { @@ -80,4 +88,9 @@ public class MultiCompetitorRaceChart extends AbstractCompetitorRaceChart colorMap; - /** * @param raceSelectionProvider * if null, this chart won't update its contents automatically upon race selection change; @@ -106,7 +107,8 @@ public class WindChart extends AbstractRaceChart implements Component() { - @Override - public void onSuccess(WindInfoForRaceDTO result) { - if (result != null) { - updateChartSeries(result, append); - updateVisibleSeries(); - } else { - if (!append) { - clearChart(); // no wind known for untracked race + if (isVisible()) { + if (selectedRaceIdentifier == null) { + clearChart(); + } else if (needsDataLoading() && from != null && to != null) { + setWidget(chart); + showLoading("Loading wind data..."); + GetWindInfoAction getWindInfoAction = new GetWindInfoAction(sailingService, selectedRaceIdentifier, + from, to, settings.getResolutionInMilliseconds(), null, /* onlyUpToNewestEvent==true because we don't want + to overshoot the evidence so far */ true); + asyncActionsExecutor.execute(getWindInfoAction, LODA_WIND_CHART_DATA_CATEGORY, + new AsyncCallback() { + @Override + public void onSuccess(WindInfoForRaceDTO result) { + if (result != null) { + updateChartSeries(result, append); + updateVisibleSeries(); + } else { + if (!append) { + clearChart(); // no wind known for untracked race + } } + hideLoading(); } - hideLoading(); - } - - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError(stringMessages.errorFetchingWindInformationForRace() + " " - + selectedRaceIdentifier + ": " + caught.getMessage(), timer.getPlayMode() == PlayModes.Live); - hideLoading(); - } - }); + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError(stringMessages.errorFetchingWindInformationForRace() + " " + + selectedRaceIdentifier + ": " + caught.getMessage(), timer.getPlayMode() == PlayModes.Live); + hideLoading(); + } + }); + } } } @@ -609,5 +620,11 @@ public class WindChart extends AbstractRaceChart implements Component extends LazyPanel impl ensureWidget(); return getWidget(); } + + @Override + public String getDependentCssClassName() { + return null; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/components/Component.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/components/Component.java index e835a7ebe00..fb89fb84b8c 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/components/Component.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/components/Component.java @@ -18,4 +18,11 @@ public interface Component extends ComponentSettings boolean isVisible(); void setVisible(boolean visibility); + + /** + * If the component wants to add its specific styling rules, e.g., for the expand/collapse toggle buttons or + * the dragger handle, it should return a non-null value from this method which is then used as + * a dependent CSS class name for those components. + */ + String getDependentCssClassName(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/components/CompositeTabbedSettingsDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/components/CompositeTabbedSettingsDialog.java index 83838f90170..536379cb447 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/components/CompositeTabbedSettingsDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/components/CompositeTabbedSettingsDialog.java @@ -74,6 +74,11 @@ public class CompositeTabbedSettingsDialog extends SettingsDialog, SettingsDialogComponent>(); for (Component component : components) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/components/SettingsDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/components/SettingsDialog.java index c6704dc9deb..e6512343023 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/components/SettingsDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/components/SettingsDialog.java @@ -1,5 +1,8 @@ package com.sap.sailing.gwt.ui.client.shared.components; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.FocusWidget; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.ui.client.StringMessages; @@ -12,6 +15,24 @@ public class SettingsDialog extends DataEntryDialog this(component, stringMessages, /* animationEnabled */ true); } + /** + * Creates a settings button for component that, when clicked, opens a settings dialog for that + * component and when confirmed, updates that component's settings. The button has no CSS style attached to + * give callers full flexibility as to how to style the button. + */ + public static Button createSettingsButton(final Component component, final StringMessages stringMessages) { + Button settingsButton = new Button(); + settingsButton.setTitle(stringMessages.settings()); + settingsButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + new SettingsDialog(component, stringMessages).show(); + } + }); + return settingsButton; + } + + /** * This auxiliary constructor is required to avoid duplicate calls to {@link Component#getSettingsDialogComponent()} * which may choose to create a new instance each call. Such duplicate instances would cause the validator to diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/AbstractSortableColumnWithMinMax.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/AbstractSortableColumnWithMinMax.java new file mode 100755 index 00000000000..09101e49517 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/AbstractSortableColumnWithMinMax.java @@ -0,0 +1,12 @@ +package com.sap.sailing.gwt.ui.client.shared.controls; + +import com.google.gwt.cell.client.Cell; +import com.sap.sailing.domain.common.SortingOrder; +import com.sap.sailing.gwt.ui.leaderboard.SortableColumnWithMinMax; + +public abstract class AbstractSortableColumnWithMinMax extends SortableColumn implements + SortableColumnWithMinMax { + protected AbstractSortableColumnWithMinMax(Cell cell, SortingOrder preferredSortingOrder) { + super(cell, preferredSortingOrder); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/BetterCheckboxCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/BetterCheckboxCell.java new file mode 100755 index 00000000000..e6570c143a4 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/BetterCheckboxCell.java @@ -0,0 +1,88 @@ +package com.sap.sailing.gwt.ui.client.shared.controls; + +import com.google.gwt.cell.client.AbstractEditableCell; +import com.google.gwt.cell.client.ValueUpdater; +import com.google.gwt.dom.client.BrowserEvents; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.InputElement; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.safehtml.shared.SafeHtml; +import com.google.gwt.safehtml.shared.SafeHtmlBuilder; +import com.google.gwt.safehtml.shared.SafeHtmlUtils; + +/** + * A checkbox cell that can be styled to a certain degree. + * + * @author Axel Uhl (D043530) + * + */ +public class BetterCheckboxCell extends AbstractEditableCell { + public final static String DEFAULT_CSS_CLASS_SELECTED = "betterCheckboxCellSelected"; + public final static String DEFAULT_CSS_CLASS_DESELECTED = "betterCheckboxCellDeselected"; + + /** + * An html string representation of a checked input box. + */ + private final SafeHtml INPUT_CHECKED; + + /** + * An html string representation of an unchecked input box. + */ + private final SafeHtml INPUT_UNCHECKED; + + public BetterCheckboxCell() { + this(DEFAULT_CSS_CLASS_SELECTED, DEFAULT_CSS_CLASS_DESELECTED); + } + + public BetterCheckboxCell(String cssClassSelected, String cssClassDeselected) { + super(BrowserEvents.CHANGE, BrowserEvents.KEYDOWN, BrowserEvents.CLICK); + INPUT_CHECKED = SafeHtmlUtils.fromSafeConstant("
"); + INPUT_UNCHECKED = SafeHtmlUtils.fromSafeConstant("
"); + } + + + @Override + public void onBrowserEvent(Context context, Element parent, Boolean value, NativeEvent event, + ValueUpdater valueUpdater) { + String type = event.getType(); + + boolean enterPressed = BrowserEvents.KEYDOWN.equals(type) && event.getKeyCode() == KeyCodes.KEY_ENTER; + if (BrowserEvents.CLICK.equals(type) || BrowserEvents.CHANGE.equals(type) || enterPressed) { + InputElement input = parent.getFirstChild().cast(); + Boolean isChecked = input.isChecked(); + + /* + * Toggle the value if the enter key was pressed and the cell handles selection or doesn't depend on + * selection. If the cell depends on selection but doesn't handle selection, then ignore the enter key and + * let the SelectionEventManager determine which keys will trigger a change. + */ + if (enterPressed && (handlesSelection() || !dependsOnSelection())) { + isChecked = !isChecked; + input.setChecked(isChecked); + } + if (valueUpdater != null) { + valueUpdater.update(isChecked); + } + event.stopPropagation(); + } + } + + @Override + public void render(com.google.gwt.cell.client.Cell.Context context, Boolean value, SafeHtmlBuilder sb) { + if (value != null && value) { + sb.append(INPUT_CHECKED); + } else { + sb.append(INPUT_UNCHECKED); + } + } + + @Override + public boolean isEditing(com.google.gwt.cell.client.Cell.Context context, Element parent, Boolean value) { + // A checkbox is never in "edit mode". There is no intermediate state + // between checked and unchecked. + return false; + } + + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/SelectionCheckboxColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/SelectionCheckboxColumn.java new file mode 100755 index 00000000000..95c93ef6037 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/SelectionCheckboxColumn.java @@ -0,0 +1,225 @@ +package com.sap.sailing.gwt.ui.client.shared.controls; + +import java.util.List; + +import com.google.gwt.cell.client.Cell.Context; +import com.google.gwt.dom.client.BrowserEvents; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.safehtml.shared.SafeHtmlBuilder; +import com.google.gwt.user.cellview.client.CellTable; +import com.google.gwt.user.cellview.client.Column; +import com.google.gwt.user.cellview.client.Header; +import com.google.gwt.user.cellview.client.SafeHtmlHeader; +import com.google.gwt.view.client.CellPreviewEvent; +import com.google.gwt.view.client.DefaultSelectionEventManager; +import com.google.gwt.view.client.DefaultSelectionEventManager.EventTranslator; +import com.google.gwt.view.client.DefaultSelectionEventManager.SelectAction; +import com.google.gwt.view.client.ListDataProvider; +import com.google.gwt.view.client.MultiSelectionModel; +import com.sap.sailing.domain.common.InvertibleComparator; +import com.sap.sailing.domain.common.SortingOrder; +import com.sap.sailing.domain.common.dto.LeaderboardRowDTO; +import com.sap.sailing.domain.common.impl.InvertibleComparatorAdapter; + +/** + * A column to be used in a {@link CellTable} that controls and reflects a table's selection model using stylable + * "check boxes". To make things work, clients have to also call + * {@link CellTable#setSelectionModel(com.google.gwt.view.client.SelectionModel, com.google.gwt.view.client.CellPreviewEvent.Handler)} + * with the result of this columns {@link #getSelectionModel} as the first and the result of the + * {@link #getSelectionManager()} method as second argument. This will ensure that the event handling and selection + * updates work properly. + *

+ * + * If clients don't choose to use this columns own {@link #getSelectionModel() selection model}, they have to ensure + * that selection changes with subsequent {@link #redrawRow(Object, List) redraw requests} are triggered properly upon + * selection status changes. Otherwise, the checkboxes will run out of sync with the selection status. + *

+ * + * The column uses the {@link BetterCheckboxCell} cell to implement the display properties. Three CSS styles can be used + * to parameterize this column: one for the <td> element rendering the cell, and two for the + * <div> element representing a selected or deselected element. + * + * @author Axel Uhl (D043530) + * + * @param + */ +public abstract class SelectionCheckboxColumn extends AbstractSortableColumnWithMinMax { + private final BetterCheckboxCell cell; + private final String checkboxColumnCellCSSClass; + private final EventTranslator selectionEventTranslator; + private final MultiSelectionModel selectionModel; + + /** + * @param selectedCheckboxCSSClass + * CSS class for the <div> element representing a selected element + * @param deselectedCheckboxCSSClass + * CSS class for the <div> element representing a deselected element + * @param checkboxColumnCellCSSClass + * CSS class for the <td> element rendering the cell + */ + protected SelectionCheckboxColumn(String selectedCheckboxCSSClass, + String deselectedCheckboxCSSClass, String checkboxColumnCellCSSClass) { + this(new BetterCheckboxCell(selectedCheckboxCSSClass, deselectedCheckboxCSSClass), checkboxColumnCellCSSClass); + } + + private SelectionCheckboxColumn(BetterCheckboxCell checkboxCell, String checkboxColumnCellCSSClass) { + super(checkboxCell, SortingOrder.DESCENDING); + this.cell = checkboxCell; + this.checkboxColumnCellCSSClass = checkboxColumnCellCSSClass; + this.selectionEventTranslator = createSelectionEventTranslator(); + this.selectionModel = createSelectionModel(); + } + + /** + * Subclasses need to tell this column what the data list is. This is necessary in order to reach all rows after the + * selection is cleared, as well as to trigger the redraw of a row by setting it again in the list data provider which + * triggers the necessary redraw operation. + */ + protected abstract ListDataProvider getListDataProvider(); + + /** + * @return a selection manager that should be used for the table to which this column is added; use + * {@link CellTable#setSelectionModel(com.google.gwt.view.client.SelectionModel, com.google.gwt.view.client.CellPreviewEvent.Handler)} + * to set the selection manager together with the selection model on the table. + */ + public CellPreviewEvent.Handler getSelectionManager() { + return DefaultSelectionEventManager.createCustomManager(getSelectionEventTranslator()); + } + + public MultiSelectionModel getSelectionModel() { + return selectionModel; + } + + /** + * Clients should use the multi-selection model returned by this method for the {@link CellTable} to which they add this column. + * If they do so, the {@link #redrawRow(LeaderboardRowDTO, List)} method will be triggered correctly for all selection changes. + * Otherwise, clients or subclasses are responsible to issue the necessary calls to {@link #redrawRow(LeaderboardRowDTO, List)} + * after selection changes. + */ + private MultiSelectionModel createSelectionModel() { + return new MultiSelectionModel() { + @Override + public void clear() { + super.clear(); + final List list = getListDataProvider().getList(); + for (T item : list) { + redrawRow(item, list); + } + } + + @Override + public void setSelected(T item, boolean selected) { + final boolean redrawRequired = isSelected(item) != selected; + super.setSelected(item, selected); + if (redrawRequired) { + redrawRow(item, getListDataProvider().getList()); + } + } + + }; + } + + /** + * @return a selection event translator that works nicely with + * {@link DefaultSelectionEventManager#createCustomManager(EventTranslator)} to ensure that this selection + * checkbox column does what it is supposed to do. Client may want to obtain the custom event manager + * directly by using {@link #getSelectionManager()}. + */ + public EventTranslator getSelectionEventTranslator() { + return selectionEventTranslator; + } + + /** + * Subclass implementation has to tell whether the checkbox is selected or deselected for row + */ + @Override + public abstract Boolean getValue(T row); + + /** + * The default header display is a check mark. Subclasses may redefine this. + */ + @Override + public Header getHeader() { + return new SafeHtmlHeader(new SafeHtmlBuilder().appendEscaped("\u2713").toSafeHtml()); + } + + @Override + public String getCellStyleNames(Context context, T object) { + String basicStyles = super.getCellStyleNames(context, object); + return basicStyles == null ? checkboxColumnCellCSSClass : (basicStyles + " " + checkboxColumnCellCSSClass); + } + + @Override + public BetterCheckboxCell getCell() { + return cell; + } + + @Override + public InvertibleComparator getComparator() { + return new InvertibleComparatorAdapter() { + @Override + public int compare(T a, T b) { + return getValue(a) ? getValue(b) ? 0 : 1 : getValue(b) ? -1 : 0; + } + }; + } + + /** + * No reasonable min/max display for this column; does nothing. + */ + @Override + public void updateMinMax() {} + + private EventTranslator createSelectionEventTranslator() { + return new EventTranslator() { + /** + * Don't clear the selection when the user has clicked on the checkbox column + */ + @Override + public boolean clearCurrentSelection(CellPreviewEvent event) { + NativeEvent nativeEvent = event.getNativeEvent(); + boolean ctrlOrMeta = nativeEvent.getCtrlKey() || nativeEvent.getMetaKey(); + return !isSelectionCheckboxColumn(event) && !ctrlOrMeta; + } + + private boolean isSelectionCheckboxColumn(CellPreviewEvent event) { + Column column = getColumn(event); + return column == SelectionCheckboxColumn.this; + } + + private Column getColumn(CellPreviewEvent event) { + CellTable table = (CellTable) event.getDisplay(); + return table.getColumn(event.getContext().getColumn()); + } + + @Override + public SelectAction translateSelectionEvent(CellPreviewEvent event) { + final SelectAction result; + if (BrowserEvents.CLICK.equals(event.getNativeEvent().getType()) && isSelectionCheckboxColumn(event)) { + result = SelectAction.TOGGLE; + } else { + result = SelectAction.DEFAULT; + } + return result; + } + }; + } + + /** + * Subclasses shall use this after an update to the selection state of a row to ensure the checkboxes are + * displayed correctly and consistently again. + * + * @param row + * the row object to be re-drawn + * @param dataList + * the result of calling {@link ListDataProvider#getList()} on the table's data provider which is + * actually a list wrapper where updates to which are reflected in the table display + */ + protected void redrawRow(final T row, final List dataList) { + int rowIndex = dataList.indexOf(row); + if (rowIndex >= 0) { + dataList.set(rowIndex, row); // trigger row redraw to have check box shown in correct state + } + getListDataProvider().flush(); + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SortableColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/SortableColumn.java similarity index 66% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SortableColumn.java rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/SortableColumn.java index 8d13ff79bec..b5d303db1a0 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SortableColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/SortableColumn.java @@ -1,56 +1,43 @@ -package com.sap.sailing.gwt.ui.leaderboard; - -import com.google.gwt.cell.client.Cell; -import com.google.gwt.user.cellview.client.Column; -import com.google.gwt.user.cellview.client.Header; -import com.sap.sailing.domain.common.InvertibleComparator; -import com.sap.sailing.domain.common.SortingOrder; - -public abstract class SortableColumn extends Column { - private SortingOrder preferredSortingOrder; - private final DisplayedLeaderboardRowsProvider displayedLeaderboardRowsProvider; - - protected SortableColumn(Cell cell, SortingOrder preferredSortingOrder, - DisplayedLeaderboardRowsProvider displayedLeaderboardRowsProvider) { - super(cell); - setSortable(true); - this.preferredSortingOrder = preferredSortingOrder; - this.displayedLeaderboardRowsProvider = displayedLeaderboardRowsProvider; - } - - /** - * Based on the rows returned by the {@link DisplayedLeaderboardRowsProvider} instance passed to the - * constructor, updates the min/max values for this column. - */ - protected void updateMinMax() {} - - protected DisplayedLeaderboardRowsProvider getDisplayedLeaderboardRowsProvider() { - return displayedLeaderboardRowsProvider; - } - - public abstract InvertibleComparator getComparator(); - - public abstract Header getHeader(); - - /** - * Allows a column to specify a style/CSS class to use to format the <th> header cell. - * This default implementation returns null, meaning that no additional style other - * than the GWT-provided default styles will be used. - */ - public String getHeaderStyle() { - return null; - } - - /** - * Allows a column to specify a style/CSS class to use to format the <col> element. - * This default implementation returns null, meaning that no additional style other - * than the GWT-provided default styles will be used. - */ - public String getColumnStyle() { - return null; - } - - public SortingOrder getPreferredSortingOrder() { - return preferredSortingOrder; - } -} +package com.sap.sailing.gwt.ui.client.shared.controls; + +import com.google.gwt.cell.client.Cell; +import com.google.gwt.user.cellview.client.Column; +import com.google.gwt.user.cellview.client.Header; +import com.sap.sailing.domain.common.InvertibleComparator; +import com.sap.sailing.domain.common.SortingOrder; + +public abstract class SortableColumn extends Column { + private SortingOrder preferredSortingOrder; + + protected SortableColumn(Cell cell, SortingOrder preferredSortingOrder) { + super(cell); + setSortable(true); + this.preferredSortingOrder = preferredSortingOrder; + } + + public abstract InvertibleComparator getComparator(); + + public abstract Header getHeader(); + + /** + * Allows a column to specify a style/CSS class to use to format the <th> header cell. + * This default implementation returns null, meaning that no additional style other + * than the GWT-provided default styles will be used. + */ + public String getHeaderStyle() { + return null; + } + + /** + * Allows a column to specify a style/CSS class to use to format the <col> element. + * This default implementation returns null, meaning that no additional style other + * than the GWT-provided default styles will be used. + */ + public String getColumnStyle() { + return null; + } + + public SortingOrder getPreferredSortingOrder() { + return preferredSortingOrder; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/CompetitorFilterWithUIFactory.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/CompetitorFilterWithUIFactory.java index 0ce84e6de3f..6735f4e27cc 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/CompetitorFilterWithUIFactory.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/CompetitorFilterWithUIFactory.java @@ -18,7 +18,8 @@ public class CompetitorFilterWithUIFactory { return new CompetitorSailNumbersFilter(); } else if(SelectedCompetitorsFilter.FILTER_NAME.equals(filterName)) { return new SelectedCompetitorsFilter(); + } else { + throw new UnsupportedOperationException(); } - return null; } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/CompetitorRaceRankFilter.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/CompetitorRaceRankFilter.java index de299fc8835..babd08ec0e4 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/CompetitorRaceRankFilter.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/CompetitorRaceRankFilter.java @@ -13,17 +13,23 @@ import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sse.common.filter.AbstractNumberFilter; /** - * A filter filtering competitors by their race rank + * A filter filtering competitors by their net points in a race * * @author Frank * */ public class CompetitorRaceRankFilter extends AbstractNumberFilter implements LeaderboardFilterContext, SelectedRaceFilterContext, FilterWithUI { - public static final String FILTER_NAME = "CompetitorRaceRankFilter"; + public static final String FILTER_NAME = "Race Rank"; private LeaderboardFetcher leaderboardFetcher; private RaceIdentifier selectedRace; + + /** + * An optional quick rank provider; if null, a more expensive algorithm based on the {@link #selectedRace} + * and the leaderboard that can be obtained through the {@link #leaderboardFetcher} needs to be employed to obtain the rank. + */ + private QuickRankProvider quickRankProvider; public CompetitorRaceRankFilter() { } @@ -34,50 +40,79 @@ public class CompetitorRaceRankFilter extends AbstractNumberFilternull if no rank could be determined for competitorDTO, a 1-based rank otherwise + */ + private Integer getRankFromLeaderboard(CompetitorDTO competitorDTO) { + Integer raceRank = null; + RaceColumnDTO theRaceColumnDTOThatContainsCompetitorRace = null; + for (RaceColumnDTO raceColumnDTO : getLeaderboard().getRaceList()) { + if (raceColumnDTO.containsRace(getSelectedRace())) { + theRaceColumnDTOThatContainsCompetitorRace = raceColumnDTO; + break; + } + } + // There may be competitors that have no tracked race assigned in that column and therefore won't + // have a fleet; + // those competitors are to be considered "worse". However, in one column it may also be the case + // that the "selected race" + // has no scores yet (e.g., not yet started) and that other races in the column do have a score. The + // competitorsFromBestToWorst then would not contain those competitors that don't have a score + // assigned yet. + final LeaderboardRowDTO competitorRow = getLeaderboard().rows.get(competitorDTO); + LinkedHashSet competitorsRankedInColumn = new LinkedHashSet(); + if (theRaceColumnDTOThatContainsCompetitorRace != null && competitorRow != null) { + LeaderboardEntryDTO entryDTO = competitorRow.fieldsByRaceColumnName + .get(theRaceColumnDTOThatContainsCompetitorRace.getName()); + + // first collect those competitors in their order that have a rank + for (CompetitorDTO competitor : getLeaderboard().getCompetitorsFromBestToWorst( + theRaceColumnDTOThatContainsCompetitorRace)) { + competitorsRankedInColumn.add(competitor); + } + // then add all competitors for which no ranking has been provided + for (CompetitorDTO competitor : getLeaderboard().competitors) { + if (!competitorsRankedInColumn.contains(competitor)) { + competitorsRankedInColumn.add(competitor); + } + } + raceRank = 0; + for (Iterator competitorIter = competitorsRankedInColumn.iterator(); competitorIter + .hasNext();) { + CompetitorDTO competitor = competitorIter.next(); + LeaderboardEntryDTO entryDTOIterated = getLeaderboard().rows.get(competitor).fieldsByRaceColumnName + .get(theRaceColumnDTOThatContainsCompetitorRace.getName()); + // the competitor counts for the selected race if the fleet matches or is unknown + if (entryDTOIterated.fleet == null || entryDTO.fleet == null + || entryDTOIterated.fleet.equals(entryDTO.fleet)) { + raceRank++; + } + if (competitor.equals(competitorDTO)) { break; } } - // There may be competitors that have no tracked race assigned in that column and therefore won't have a fleet; - // those competitors are to be considered "worse". However, in one column it may also be the case that the "selected race" - // has no scores yet (e.g., not yet started) and that other races in the column do have a score. The - // competitorsFromBestToWorst then would not contain those competitors that don't have a score assigned yet. - LinkedHashSet competitorsRankedInColumn = new LinkedHashSet(); - final LeaderboardRowDTO competitorRow = getLeaderboard().rows.get(competitorDTO); - if (theRaceColumnDTOThatContainsCompetitorRace != null && competitorRow != null) { - LeaderboardEntryDTO entryDTO = competitorRow.fieldsByRaceColumnName.get(theRaceColumnDTOThatContainsCompetitorRace.getName()); - // first collect those competitors in their order that have a rank - for (CompetitorDTO competitor : getLeaderboard().getCompetitorsFromBestToWorst(theRaceColumnDTOThatContainsCompetitorRace)) { - competitorsRankedInColumn.add(competitor); - } - // then add all competitors for which no ranking has been provided - for (CompetitorDTO competitor : getLeaderboard().competitors) { - if (!competitorsRankedInColumn.contains(competitor)) { - competitorsRankedInColumn.add(competitor); - } - } - int raceRank = 1; - for (Iterator competitorIter=competitorsRankedInColumn.iterator(); competitorIter.hasNext(); ) { - CompetitorDTO competitor = competitorIter.next(); - LeaderboardEntryDTO entryDTOIterated = getLeaderboard().rows.get(competitor).fieldsByRaceColumnName - .get(theRaceColumnDTOThatContainsCompetitorRace.getName()); - // the competitor counts for the selected race if the fleet matches or is unknown - if (entryDTOIterated.fleet == null || entryDTO.fleet == null || entryDTOIterated.fleet.equals(entryDTO.fleet)) { - raceRank++; - } - if (competitor.equals(competitorDTO)) { - break; - } - } - result = operator.matchValues(value, raceRank); - } } - return result; + return raceRank; } @Override @@ -90,7 +125,10 @@ public class CompetitorRaceRankFilter extends AbstractNumberFilter createUIFactory() { return new CompetitorRaceRankFilterUIFactory(this); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/CompetitorRaceRankFilterUIFactory.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/CompetitorRaceRankFilterUIFactory.java index 82eeb4d00c3..a725d3cca94 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/CompetitorRaceRankFilterUIFactory.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/CompetitorRaceRankFilterUIFactory.java @@ -15,8 +15,8 @@ public class CompetitorRaceRankFilterUIFactory extends AbstractCompetitorNumberF this(new CompetitorRaceRankFilter()); } - public CompetitorRaceRankFilterUIFactory(CompetitorRaceRankFilter competitorRaceRankFilter) { - super(competitorRaceRankFilter, BinaryOperator.Operators.LessThanEquals); + public CompetitorRaceRankFilterUIFactory(CompetitorRaceRankFilter competitorRaceNetPointsFilter) { + super(competitorRaceNetPointsFilter, BinaryOperator.Operators.LessThanEquals); supportedOperators.add(BinaryOperator.Operators.LessThanEquals); supportedOperators.add(BinaryOperator.Operators.GreaterThanEquals); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/CompetitorTotalRankFilter.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/CompetitorTotalRankFilter.java index f7ad3db84dd..e3ea3c88a24 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/CompetitorTotalRankFilter.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/CompetitorTotalRankFilter.java @@ -47,7 +47,7 @@ public class CompetitorTotalRankFilter extends AbstractNumberFilter> getActiveFilterSetWithGeneralizedType() { + FilterSet> result = null; + if(activeFilterSet != null) { + result = new FilterSet>(activeFilterSet.getName()); + for (Filter filter : activeFilterSet.getFilters()) { + result.addFilter(filter); + } + } + return result; + } + public FilterSet> getActiveFilterSet() { return activeFilterSet; } public FilterSet> findFilterSetByName(String filterSetName) { FilterSet> result = null; - for(FilterSet> filterSet: filterSets) { - if(filterSet.getName().equals(filterSetName)) { + for (FilterSet> filterSet : filterSets) { + if (filterSet.getName().equals(filterSetName)) { result = filterSet; break; } @@ -32,8 +44,8 @@ public class CompetitorsFilterSets { } public void setActiveFilterSet(FilterSet> newActiveFilterSet) { - if(newActiveFilterSet != null) { - if(filterSets.contains(newActiveFilterSet)) { + if (newActiveFilterSet != null) { + if (filterSets.contains(newActiveFilterSet)) { this.activeFilterSet = newActiveFilterSet; } } else { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/QuickRankProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/QuickRankProvider.java new file mode 100755 index 00000000000..7d9cce20b7e --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/QuickRankProvider.java @@ -0,0 +1,16 @@ +package com.sap.sailing.gwt.ui.client.shared.filter; + +import com.sap.sailing.domain.common.dto.CompetitorDTO; + +/** + * Can provide a race rank for a {@link CompetitorDTO} quickly. + * + * @author Axel Uhl (D043530) + * + */ +public interface QuickRankProvider { + /** + * @return the 1-based rank of the competitor if found, or null otherwise + */ + Integer getRank(CompetitorDTO competitor); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/SelectedRaceFilterContext.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/SelectedRaceFilterContext.java index 376aab7ac1a..9f1d5b2f644 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/SelectedRaceFilterContext.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/SelectedRaceFilterContext.java @@ -5,5 +5,7 @@ import com.sap.sailing.domain.common.RaceIdentifier; public interface SelectedRaceFilterContext { void setSelectedRace(RaceIdentifier selectedRace); + void setQuickRankProvider(QuickRankProvider quickRankProvider); + RaceIdentifier getSelectedRace(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/BoundsUtil.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/BoundsUtil.java new file mode 100755 index 00000000000..34c3cef65e9 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/BoundsUtil.java @@ -0,0 +1,43 @@ +package com.sap.sailing.gwt.ui.client.shared.racemap; + +import com.google.gwt.maps.client.base.LatLng; +import com.google.gwt.maps.client.base.LatLngBounds; +import com.sap.sailing.domain.common.Bounds; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.dto.PositionDTO; +import com.sap.sailing.domain.common.impl.BoundsImpl; +import com.sap.sailing.domain.common.impl.DegreePosition; + +/** + * Converts between {@link Bounds} and {@link LatLngBounds}. + * + * @author Axel Uhl (D043530) + */ +public class BoundsUtil { + public static Bounds getAsBounds(LatLngBounds latLngBounds) { + return new BoundsImpl(new DegreePosition(latLngBounds.getSouthWest().getLatitude(), latLngBounds.getSouthWest().getLongitude()), + new DegreePosition(latLngBounds.getNorthEast().getLatitude(), latLngBounds.getNorthEast().getLongitude())); + } + + public static LatLngBounds getAsLatLngBounds(Bounds bounds) { + return LatLngBounds.newInstance(LatLng.newInstance(bounds.getSouthWest().getLatDeg(), bounds.getSouthWest().getLngDeg()), + LatLng.newInstance(bounds.getNorthEast().getLatDeg(), bounds.getNorthEast().getLngDeg())); + } + + public static Bounds getAsBounds(PositionDTO positionDTO) { + Position pos = getAsPosition(positionDTO); + return new BoundsImpl(pos, pos); + } + + public static Position getAsPosition(PositionDTO positionDTO) { + return new DegreePosition(positionDTO.latDeg, positionDTO.lngDeg); + } + + public static Position getAsPosition(LatLng latLngPosition) { + return new DegreePosition(latLngPosition.getLatitude(), latLngPosition.getLongitude()); + } + + public static Bounds getAsBounds(Position position) { + return new BoundsImpl(position, position); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CourseMarkOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CourseMarkOverlay.java index 857472b7d3c..e08b6d74e92 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CourseMarkOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CourseMarkOverlay.java @@ -17,7 +17,7 @@ import com.sap.sailing.gwt.ui.shared.racemap.MarkVectorGraphics; import com.sap.sse.common.Util; /** - * A google map overlay based on a HTML5 canvas for drawing course marks (images) and the buoy zone if the mark is a buoy. + * A google map overlay based on a HTML5 canvas for drawing course marks (canvases) and the buoy zone if the mark is a buoy. */ public class CourseMarkOverlay extends CanvasOverlayV3 { @@ -37,6 +37,12 @@ public class CourseMarkOverlay extends CanvasOverlayV3 { private final MarkVectorGraphics markVectorGraphics; private Map> markScaleAndSizePerZoomCache; + + private Double lastWidth; + private Double lastHeight; + private Double lastScaleFactor; + private Boolean lastShowBuoyZone; + private Double lastBuoyZoneRadiusInMeter; public CourseMarkOverlay(MapWidget map, int zIndex, MarkDTO markDTO) { super(map, zIndex); @@ -44,68 +50,78 @@ public class CourseMarkOverlay extends CanvasOverlayV3 { this.position = markDTO.position; this.buoyZoneRadiusInMeter = 0.0; this.showBuoyZone = false; - markVectorGraphics = new MarkVectorGraphics(markDTO.type, markDTO.color, markDTO.shape, markDTO.pattern); markScaleAndSizePerZoomCache = new HashMap>(); - setCanvasSize(50, 50); } - @Override protected void draw() { if (mapProjection != null && mark != null && position != null) { int zoom = map.getZoom(); - Util.Pair markScaleAndSize = markScaleAndSizePerZoomCache.get(zoom); - if(markScaleAndSize == null) { + if (markScaleAndSize == null) { markScaleAndSize = getMarkScaleAndSize(position); markScaleAndSizePerZoomCache.put(zoom, markScaleAndSize); } double markSizeScaleFactor = markScaleAndSize.getA(); - getCanvas().setTitle(getTitle()); - LatLng latLngPosition = LatLng.newInstance(position.latDeg, position.lngDeg); - // calculate canvas size double canvasWidth = markScaleAndSize.getB().getWidth(); double canvasHeight = markScaleAndSize.getB().getHeight(); double buoyZoneRadiusInPixel = -1; - if(showBuoyZone && mark.type == MarkType.BUOY) { - buoyZoneRadiusInPixel = calculateRadiusOfBoundingBox(mapProjection, latLngPosition, buoyZoneRadiusInMeter); - if(buoyZoneRadiusInPixel > MIN_BUOYZONE_RADIUS_IN_PX) { - canvasWidth = (buoyZoneRadiusInPixel + 1)* 2; + if (showBuoyZone && mark.type == MarkType.BUOY) { + buoyZoneRadiusInPixel = calculateRadiusOfBoundingBox(mapProjection, latLngPosition, + buoyZoneRadiusInMeter); + if (buoyZoneRadiusInPixel > MIN_BUOYZONE_RADIUS_IN_PX) { + canvasWidth = (buoyZoneRadiusInPixel + 1) * 2; canvasHeight = (buoyZoneRadiusInPixel + 1) * 2; } } - setCanvasSize((int) canvasWidth, (int) canvasHeight); - - Context2d context2d = getCanvas().getContext2d(); - - // draw the course mark - markVectorGraphics.drawMarkToCanvas(context2d, showBuoyZone, canvasWidth, canvasHeight, markSizeScaleFactor); - + if (needToDraw(showBuoyZone, buoyZoneRadiusInMeter, canvasWidth, canvasHeight, markSizeScaleFactor)) { + setCanvasSize((int) canvasWidth, (int) canvasHeight); + Context2d context2d = getCanvas().getContext2d(); + // draw the course mark + markVectorGraphics.drawMarkToCanvas(context2d, showBuoyZone, canvasWidth, canvasHeight, markSizeScaleFactor); + // draw the buoy zone + if (showBuoyZone && mark.type == MarkType.BUOY && buoyZoneRadiusInPixel > MIN_BUOYZONE_RADIUS_IN_PX) { + CssColor grayTransparentColor = CssColor.make("rgba(50,90,135,0.75)"); + // this translation is important for drawing lines with a real line width of 1 pixel + context2d.setStrokeStyle(grayTransparentColor); + context2d.setLineWidth(1.0); + context2d.beginPath(); + context2d.arc(buoyZoneRadiusInPixel + 1, buoyZoneRadiusInPixel + 1, buoyZoneRadiusInPixel, 0, + Math.PI * 2, true); + context2d.closePath(); + context2d.stroke(); + } + lastBuoyZoneRadiusInMeter = buoyZoneRadiusInMeter; + lastScaleFactor = markSizeScaleFactor; + lastShowBuoyZone = showBuoyZone; + lastWidth = canvasWidth; + lastHeight = canvasHeight; + } Point buoyPositionInPx = mapProjection.fromLatLngToDivPixel(latLngPosition); - - // draw the buoy zone - if(showBuoyZone && mark.type == MarkType.BUOY && buoyZoneRadiusInPixel > MIN_BUOYZONE_RADIUS_IN_PX) { - CssColor grayTransparentColor = CssColor.make("rgba(50,90,135,0.75)"); - - // this translation is important for drawing lines with a real line width of 1 pixel - context2d.setStrokeStyle(grayTransparentColor); - context2d.setLineWidth(1.0); - context2d.beginPath(); - context2d.arc(buoyZoneRadiusInPixel+1, buoyZoneRadiusInPixel+1, buoyZoneRadiusInPixel, 0, Math.PI*2, true); - context2d.closePath(); - context2d.stroke(); - + if (showBuoyZone && mark.type == MarkType.BUOY && buoyZoneRadiusInPixel > MIN_BUOYZONE_RADIUS_IN_PX) { setCanvasPosition(buoyPositionInPx.getX() - buoyZoneRadiusInPixel, buoyPositionInPx.getY() - buoyZoneRadiusInPixel); } else { setCanvasPosition(buoyPositionInPx.getX() - canvasWidth / 2.0, buoyPositionInPx.getY() - canvasHeight / 2.0); } } } + + /** + * Compares the drawing parameters to {@link #lastLegType} and the other last.... If anything has + * changed, the result is true. + */ + private boolean needToDraw(boolean showBuoyZone, double buoyZoneRadiusInMeters, double width, double height, double scaleFactor) { + return lastShowBuoyZone == null || lastShowBuoyZone != showBuoyZone || + lastBuoyZoneRadiusInMeter == null || lastBuoyZoneRadiusInMeter != buoyZoneRadiusInMeters || + lastScaleFactor == null || lastScaleFactor != scaleFactor || + lastWidth == null || lastWidth != width || + lastHeight == null || lastHeight != height; + } public Util.Pair getMarkScaleAndSize(PositionDTO markPosition) { double minMarkHeight = 20; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/LatLngBoundsCalculator.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/LatLngBoundsCalculator.java index 8c19cba08a6..c9301fae9ec 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/LatLngBoundsCalculator.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/LatLngBoundsCalculator.java @@ -1,7 +1,7 @@ package com.sap.sailing.gwt.ui.client.shared.racemap; -import com.google.gwt.maps.client.base.LatLngBounds; +import com.sap.sailing.domain.common.Bounds; public interface LatLngBoundsCalculator { - LatLngBounds calculateNewBounds(RaceMap forMap); + Bounds calculateNewBounds(RaceMap forMap); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java index 8d27a4c3d9a..5cb909644a2 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java @@ -8,13 +8,18 @@ import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import com.google.gwt.canvas.dom.client.CssColor; +import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.i18n.client.NumberFormat; import com.google.gwt.maps.client.LoadApi; import com.google.gwt.maps.client.LoadApi.LoadLibrary; @@ -23,9 +28,9 @@ import com.google.gwt.maps.client.MapWidget; import com.google.gwt.maps.client.base.LatLng; import com.google.gwt.maps.client.base.LatLngBounds; import com.google.gwt.maps.client.controls.ControlPosition; +import com.google.gwt.maps.client.controls.MapTypeControlOptions; import com.google.gwt.maps.client.controls.MapTypeStyle; import com.google.gwt.maps.client.controls.PanControlOptions; -import com.google.gwt.maps.client.controls.ScaleControlOptions; import com.google.gwt.maps.client.controls.ZoomControlOptions; import com.google.gwt.maps.client.events.bounds.BoundsChangeMapEvent; import com.google.gwt.maps.client.events.bounds.BoundsChangeMapHandler; @@ -33,6 +38,8 @@ import com.google.gwt.maps.client.events.click.ClickMapEvent; import com.google.gwt.maps.client.events.click.ClickMapHandler; import com.google.gwt.maps.client.events.dragend.DragEndMapEvent; import com.google.gwt.maps.client.events.dragend.DragEndMapHandler; +import com.google.gwt.maps.client.events.idle.IdleMapEvent; +import com.google.gwt.maps.client.events.idle.IdleMapHandler; import com.google.gwt.maps.client.events.mouseout.MouseOutMapEvent; import com.google.gwt.maps.client.events.mouseout.MouseOutMapHandler; import com.google.gwt.maps.client.events.mouseover.MouseOverMapEvent; @@ -49,14 +56,20 @@ import com.google.gwt.maps.client.overlays.Polygon; import com.google.gwt.maps.client.overlays.PolygonOptions; import com.google.gwt.maps.client.overlays.Polyline; import com.google.gwt.maps.client.overlays.PolylineOptions; +import com.google.gwt.resources.client.ImageResource; +import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.AbsolutePanel; +import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.Image; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.RequiresResize; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.Bounds; import com.sap.sailing.domain.common.ManeuverType; +import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.RaceIdentifier; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.Tack; @@ -64,11 +77,13 @@ import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.domain.common.dto.PositionDTO; +import com.sap.sailing.domain.common.impl.BoundsImpl; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.RGBColor; import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing; import com.sap.sailing.gwt.ui.actions.GetRaceMapDataAction; import com.sap.sailing.gwt.ui.actions.GetWindInfoAction; +import com.sap.sailing.gwt.ui.client.ClientResources; import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener; import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider; import com.sap.sailing.gwt.ui.client.ErrorReporter; @@ -80,7 +95,9 @@ import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.WindSourceTypeFormatter; import com.sap.sailing.gwt.ui.client.shared.components.Component; +import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialog; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent; +import com.sap.sailing.gwt.ui.client.shared.filter.QuickRankProvider; import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapHelpLinesSettings.HelpLineTypes; import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapZoomSettings.ZoomTypes; import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil; @@ -102,21 +119,29 @@ import com.sap.sailing.gwt.ui.shared.racemap.GoogleMapStyleHelper; import com.sap.sailing.gwt.ui.shared.racemap.WindStreamletsRaceboardOverlay; import com.sap.sse.common.Util; import com.sap.sse.common.Util.Triple; +import com.sap.sse.common.filter.Filter; +import com.sap.sse.common.filter.FilterSet; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; import com.sap.sse.gwt.client.player.TimeListener; import com.sap.sse.gwt.client.player.Timer; +import com.sap.sse.gwt.client.player.Timer.PlayModes; import com.sap.sse.gwt.client.player.Timer.PlayStates; public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSelectionChangeListener, RaceSelectionChangeListener, - RaceTimesInfoProviderListener, TailFactory, Component, RequiresDataInitialization, RequiresResize { + RaceTimesInfoProviderListener, TailFactory, Component, RequiresDataInitialization, RequiresResize, QuickRankProvider { public static final String GET_RACE_MAP_DATA_CATEGORY = "getRaceMapData"; public static final String GET_WIND_DATA_CATEGORY = "getWindData"; private MapWidget map; + private FlowPanel headerPanel; + private AbsolutePanel panelForLeftHeaderLabels; + private AbsolutePanel panelForRightHeaderLabels; private final SailingServiceAsync sailingService; private final ErrorReporter errorReporter; + private final static ClientResources resources = GWT.create(ClientResources.class); + /** * Polyline for the start line (connecting two marks representing the start gate). */ @@ -262,7 +287,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe * The last quick ranks received from a call to {@link SailingServiceAsync#getQuickRanks(RaceIdentifier, Date, AsyncCallback)} upon * the last {@link #timeChanged(Date, Date)} event. Therefore, the ranks listed here correspond to the {@link #timer}'s time. */ - private List quickRanks; + private LinkedHashMap quickRanks; private final CombinedWindPanel combinedWindPanel; @@ -271,7 +296,13 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe /** * The map bounds as last received by map callbacks; used to determine whether to suppress the boat animation during zoom/pan */ - private LatLngBounds currentMapBounds; + private LatLngBounds currentMapBounds; // bounds to which bounds-changed-handler compares + private int currentZoomLevel; // zoom-level to which bounds-changed-handler compares + + private boolean autoZoomIn = false; // flags auto-zoom-in in progress + private boolean autoZoomOut = false; // flags auto-zoom-out in progress + private int autoZoomLevel; // zoom-level to which auto-zoom-in/-out is zooming + LatLngBounds autoZoomLatLngBounds; // bounds to which auto-zoom-in/-out is panning&zooming private WindStreamletsRaceboardOverlay streamletOverlay; private final boolean showViewStreamlets; @@ -303,6 +334,10 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe lastTimeChangeBeforeInitialization = null; isMapInitialized = false; this.showViewStreamlets = showViewStreamlets; + headerPanel = new FlowPanel(); + headerPanel.setStyleName("RaceMap-HeaderPanel"); + panelForLeftHeaderLabels = new AbsolutePanel(); + panelForRightHeaderLabels = new AbsolutePanel(); initializeData(showMapControls); combinedWindPanel = new CombinedWindPanel(raceMapImageManager, stringMessages); @@ -338,59 +373,77 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe // set water color mapTypeStyles[3] = GoogleMapStyleHelper.createColorStyle(MapTypeStyleFeatureType.WATER, new RGBColor(0, 136, 255), -35, -34); + MapTypeControlOptions mapTypeControlOptions = MapTypeControlOptions.newInstance(); + mapTypeControlOptions.setPosition(ControlPosition.BOTTOM_RIGHT); + mapOptions.setMapTypeControlOptions(mapTypeControlOptions); + mapOptions.setMapTypeStyles(mapTypeStyles); - - ScaleControlOptions scaleControlOptions = ScaleControlOptions.newInstance(); - scaleControlOptions.setPosition(ControlPosition.BOTTOM_RIGHT); - mapOptions.setScaleControlOptions(scaleControlOptions); + // no need to try to position the scale control; it always ends up at the right bottom corner mapOptions.setStreetViewControl(false); if (showMapControls) { ZoomControlOptions zoomControlOptions = ZoomControlOptions.newInstance(); - zoomControlOptions.setPosition(ControlPosition.TOP_RIGHT); + zoomControlOptions.setPosition(ControlPosition.RIGHT_TOP); mapOptions.setZoomControlOptions(zoomControlOptions); - PanControlOptions panControlOptions = PanControlOptions.newInstance(); - panControlOptions.setPosition(ControlPosition.TOP_RIGHT); + panControlOptions.setPosition(ControlPosition.RIGHT_TOP); mapOptions.setPanControlOptions(panControlOptions); } map = new MapWidget(mapOptions); RaceMap.this.add(map, 0, 0); - RaceMap.this.add(combinedWindPanel, 10, 10); + Image sapLogo = createSAPLogo(); + RaceMap.this.add(sapLogo); + RaceMap.this.add(combinedWindPanel, 10, 10+sapLogo.getHeight()+/*spacing*/15); RaceMap.this.raceMapImageManager.loadMapIcons(map); map.setSize("100%", "100%"); map.addZoomChangeHandler(new ZoomChangeMapHandler() { @Override public void onEvent(ZoomChangeMapEvent event) { - // stop automatic zoom after a manual zoom event; automatic zoom in zoomMapToNewBounds will restore old settings - final List emptyList = Collections.emptyList(); - settings.getZoomSettings().setTypesToConsiderOnZoom(emptyList); + if (!autoZoomIn && !autoZoomOut) { + // stop automatic zoom after a manual zoom event; automatic zoom in zoomMapToNewBounds will restore old settings + final List emptyList = Collections.emptyList(); + settings.getZoomSettings().setTypesToConsiderOnZoom(emptyList); + } } }); map.addDragEndHandler(new DragEndMapHandler() { @Override public void onEvent(DragEndMapEvent event) { // stop automatic zoom after a manual drag event + autoZoomIn = false; + autoZoomOut = false; final List emptyList = Collections.emptyList(); settings.getZoomSettings().setTypesToConsiderOnZoom(emptyList); } }); + map.addIdleHandler(new IdleMapHandler() { + @Override + public void onEvent(IdleMapEvent event) { + // the "idle"-event is raised at the end of map-animations + if (autoZoomIn) { + // finalize zoom-in that was started with panTo() in zoomMapToNewBounds() + map.setZoom(autoZoomLevel); + autoZoomIn = false; + } + if (autoZoomOut) { + // finalize zoom-out that was started with setZoom() in zoomMapToNewBounds() + map.panTo(autoZoomLatLngBounds.getCenter()); + autoZoomOut = false; + } + } + }); map.addBoundsChangeHandler(new BoundsChangeMapHandler() { @Override public void onEvent(BoundsChangeMapEvent event) { - if (!isAutoZoomInProgress() && !map.getBounds().equals(currentMapBounds)) { - // remove the canvas animations for boats - for (BoatOverlay boatOverlay : RaceMap.this.getBoatOverlays().values()) { - boatOverlay.removeCanvasPositionAndRotationTransition(); - } - // remove the canvas animations for the info overlays of the selected boats - for(CompetitorInfoOverlay infoOverlay: competitorInfoOverlays.values()) { - infoOverlay.removeCanvasPositionAndRotationTransition(); - } + int newZoomLevel = map.getZoom(); + if (!isAutoZoomInProgress() && (newZoomLevel != currentZoomLevel)) { + removeTransitions(); } - if (streamletOverlay != null) { - streamletOverlay.onBoundsChanged(); + if ((streamletOverlay != null) && !map.getBounds().equals(currentMapBounds)) { + streamletOverlay.onBoundsChanged(newZoomLevel != currentZoomLevel); } currentMapBounds = map.getBounds(); + currentZoomLevel = newZoomLevel; + headerPanel.getElement().getStyle().setWidth(map.getOffsetWidth(), Unit.PX); } }); @@ -406,7 +459,10 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe if (showViewStreamlets) { streamletOverlay.setVisible(true); } - //Data has been initialized + createHeaderPanel(map); + createSettingsButton(map); + + // Data has been initialized RaceMap.this.isMapInitialized = true; RaceMap.this.redraw(); } @@ -414,7 +470,60 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe LoadApi.go(onLoad, loadLibraries, sensor, "key="+GoogleMapAPIKey.V3_APIKey); } - + + /** + * Creates a header panel where additional information can be displayed by using + * {@link #getLeftHeaderPanel()} or {@link #getRightHeaderPanel()}. + * + * This panel is transparent and configured in such a way that it moves other controls + * down by its height. To achieve the goal of not having added widgets transparent + * this widget consists of two parts: First one is the transparent panel and the + * second one is the panel for the controls. The controls then need to moved onto + * the panel by using CSS. + */ + private void createHeaderPanel(MapWidget map) { + // we need a panel that does not have any transparency to have the + // labels shown in the right color. This panel also needs to have + // a higher z-index than other elements on the map + map.setControls(ControlPosition.TOP_LEFT, panelForLeftHeaderLabels); + panelForLeftHeaderLabels.getElement().getParentElement().getStyle().setProperty("zIndex", "1"); + panelForLeftHeaderLabels.getElement().getStyle().setProperty("overflow", "visible"); + add(panelForRightHeaderLabels); + panelForRightHeaderLabels.getElement().getStyle().setProperty("zIndex", "1"); + panelForRightHeaderLabels.getElement().getStyle().setProperty("overflow", "visible"); + // need to initialize size before css kicks in to make sure + // that controls get positioned right + headerPanel.getElement().getStyle().setHeight(60, Unit.PX); + headerPanel.getElement().getStyle().setWidth(map.getOffsetWidth(), Unit.PX); + // some sort of hack: not positioning TOP_LEFT because then the + // controls at RIGHT would not get the correct top setting + map.setControls(ControlPosition.TOP_RIGHT, headerPanel); + } + private void createSettingsButton(MapWidget map) { + final Component component = this; + Button settingsButton = new Button(); + settingsButton.setStyleName("gwt-MapSettingsButton"); + settingsButton.setTitle(stringMessages.settings()); + settingsButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + new SettingsDialog(component, stringMessages).show(); + } + }); + map.setControls(ControlPosition.RIGHT_TOP, settingsButton); + } + + private void removeTransitions() { + // remove the canvas animations for boats + for (BoatOverlay boatOverlay : RaceMap.this.getBoatOverlays().values()) { + boatOverlay.removeCanvasPositionAndRotationTransition(); + } + // remove the canvas animations for the info overlays of the selected boats + for (CompetitorInfoOverlay infoOverlay : competitorInfoOverlays.values()) { + infoOverlay.removeCanvasPositionAndRotationTransition(); + } + } + public void redraw() { timeChanged(timer.getTime(), null); } @@ -427,6 +536,17 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe return map; } + /** + * @return the Panel where labels or other controls for the header can be positioned + */ + public AbsolutePanel getLeftHeaderPanel() { + return panelForLeftHeaderLabels; + } + + public AbsolutePanel getRightHeaderPanel() { + return panelForRightHeaderLabels; + } + @Override public void onRaceSelectionChange(List selectedRaces) { mapFirstZoomDone = false; @@ -440,6 +560,16 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe this.lastRaceTimesInfo = raceTimesInfos.get(selectedRaces.get(0)); } + /** + * In {@link PlayModes#Live live mode}, when {@link #loadCompleteLeaderboard(Date) loading the leaderboard contents}, null + * is used as time point. The condition for this is encapsulated in this method so others can find out. For example, when a time change + * is signaled due to local offset / delay adjustments, no additional call to {@link #loadCompleteLeaderboard(Date)} would be required + * as null will be passed in any case, not being affected by local time offsets. + */ + private boolean useNullAsTimePoint() { + return timer.getPlayMode() == PlayModes.Live; + } + @Override public void timeChanged(final Date newTime, final Date oldTime) { if (newTime != null && isMapInitialized) { @@ -463,7 +593,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe // next, do the full thing; being the later call, if request throttling kicks in, the later call // supersedes the earlier call which may get dropped then GetRaceMapDataAction getRaceMapDataAction = new GetRaceMapDataAction(sailingService, competitorSelection.getAllCompetitors(), race, - newTime, fromAndToAndOverlap.getA(), fromAndToAndOverlap.getB(), /* extrapolate */ true); + useNullAsTimePoint() ? null : newTime, fromAndToAndOverlap.getA(), fromAndToAndOverlap.getB(), /* extrapolate */ true); asyncActionsExecutor.execute(getRaceMapDataAction, GET_RACE_MAP_DATA_CATEGORY, getRaceMapDataCallback(oldTime, newTime, fromAndToAndOverlap.getC(), competitorsToShow, requestID)); @@ -541,7 +671,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe final GetRaceMapDataAction result; if (!fromTimes.isEmpty()) { result = new GetRaceMapDataAction(sailingService, competitorSelection.getAllCompetitors(), - race, newTime, fromTimes, toTimes, /* extrapolate */true); + race, useNullAsTimePoint() ? null : newTime, fromTimes, toTimes, /* extrapolate */true); } else { result = null; } @@ -571,7 +701,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe long timeForPositionTransitionMillis = calculateTimeForPositionTransition(newTime, oldTime); fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, timeForPositionTransitionMillis); showBoatsOnMap(newTime, timeForPositionTransitionMillis, getCompetitorsToShow()); - showCompetitorInfoOnMap(newTime, timeForPositionTransitionMillis, competitorSelection.getSelectedCompetitors()); + showCompetitorInfoOnMap(newTime, timeForPositionTransitionMillis, competitorSelection.getSelectedFilteredCompetitors()); if (douglasMarkers != null) { removeAllMarkDouglasPeuckerpoints(); } @@ -590,7 +720,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe // Rezoom the map // TODO make this a loop across the LatLngBoundsCalculators, pulling them from a collection updated in updateSettings if (!settings.getZoomSettings().containsZoomType(ZoomTypes.NONE)) { // Auto zoom if setting is not manual - LatLngBounds bounds = settings.getZoomSettings().getNewBounds(RaceMap.this); + Bounds bounds = settings.getZoomSettings().getNewBounds(RaceMap.this); zoomMapToNewBounds(bounds); mapFirstZoomDone = true; } else if (!mapFirstZoomDone) { // Zoom once to the marks @@ -666,7 +796,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe } } } - + protected void showCourseMarksOnMap(CoursePositionsDTO courseDTO) { if (map != null && courseDTO != null) { Map toRemoveCourseMarks = new HashMap(courseMarkOverlays); @@ -847,7 +977,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe CompetitorDTO leadingCompetitorDTO = null; int legOfLeaderCompetitor = -1; // this only works because the quickRanks are sorted - for (QuickRankDTO quickRank : quickRanks) { + for (QuickRankDTO quickRank : quickRanks.values()) { if (Util.contains(competitorsToShow, quickRank.competitor)) { leadingCompetitorDTO = quickRank.competitor; legOfLeaderCompetitor = quickRank.legNumberOneBased; @@ -1175,17 +1305,76 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe } } - private void zoomMapToNewBounds(LatLngBounds newBounds) { + private int getZoomLevel(LatLngBounds bounds) { + int GLOBE_PXSIZE = 256; // a constant in Google's map projection + int MAX_ZOOM = 20; // maximum zoom-level that should be automatically selected + double LOG2 = Math.log(2.0); + double deltaLng = bounds.getNorthEast().getLongitude() - bounds.getSouthWest().getLongitude(); + double deltaLat = bounds.getNorthEast().getLatitude() - bounds.getSouthWest().getLatitude(); + if ((deltaLng == 0) && (deltaLat == 0)) { + return MAX_ZOOM; + } + if (deltaLng < 0) { + deltaLng += 360; + } + int zoomLng = (int) Math.floor(Math.log(map.getDiv().getClientWidth() * 360 / deltaLng / GLOBE_PXSIZE) / LOG2); + if (deltaLat < 0) { + deltaLat += 180; + } + int zoomLat = (int) Math.floor(Math.log(map.getDiv().getClientHeight() * 180 / deltaLat / GLOBE_PXSIZE) / LOG2); + return Math.min(Math.min(zoomLat, zoomLng), MAX_ZOOM); + } + + private void zoomMapToNewBounds(Bounds newBounds) { if (newBounds != null) { - List oldZoomSettings = settings.getZoomSettings().getTypesToConsiderOnZoom(); - setAutoZoomInProgress(true); - map.setCenter(newBounds.getCenter()); - map.fitBounds(newBounds); - settings.getZoomSettings().setTypesToConsiderOnZoom(oldZoomSettings); - setAutoZoomInProgress(false); + Bounds currentMapBounds; + if (map.getBounds() == null + || !(currentMapBounds = BoundsUtil.getAsBounds(map.getBounds())).contains(newBounds) + || graticuleAreaRatio(currentMapBounds, newBounds) > 10) { + // only change bounds if the new bounds don't fit into the current map zoom + List oldZoomSettings = settings.getZoomSettings().getTypesToConsiderOnZoom(); + setAutoZoomInProgress(true); + autoZoomLatLngBounds = BoundsUtil.getAsLatLngBounds(newBounds); + int newZoomLevel = getZoomLevel(autoZoomLatLngBounds); + if (newZoomLevel != map.getZoom()) { + // distinguish between zoom-in and zoom-out, because the sequence of panTo() and setZoom() + // appears different on the screen due to map-animations + // following sequences keep the selected boats allways visible: + // zoom-in : 1. panTo(), 2. setZoom() + // zoom-out: 1. setZoom(), 2. panTo() + autoZoomIn = newZoomLevel > map.getZoom(); + autoZoomOut = !autoZoomIn; + autoZoomLevel = newZoomLevel; + removeTransitions(); + if (autoZoomIn) { + map.panTo(autoZoomLatLngBounds.getCenter()); + } else { + map.setZoom(autoZoomLevel); + } + } else { + map.panTo(autoZoomLatLngBounds.getCenter()); + } + settings.getZoomSettings().setTypesToConsiderOnZoom(oldZoomSettings); + setAutoZoomInProgress(false); + } } } + private double graticuleAreaRatio(Bounds containing, Bounds contained) { + assert containing.contains(contained); + double containingAreaRatio = getGraticuleArea(containing) / getGraticuleArea(contained); + return containingAreaRatio; + } + + /** + * A much simplified "area" calculation for a {@link Bounds} object, multiplying the differences in latitude and longitude degrees. + * The result therefore is in the order of magnitude of 60*60 square nautical miles. + */ + private double getGraticuleArea(Bounds bounds) { + return ((bounds.isCrossesDateLine() ? bounds.getNorthEast().getLngDeg()+360 : bounds.getNorthEast().getLngDeg())-bounds.getSouthWest().getLngDeg()) * + (bounds.getNorthEast().getLatDeg() - bounds.getSouthWest().getLatDeg()); + } + private void setAutoZoomInProgress(boolean autoZoomInProgress) { this.autoZoomInProgress = autoZoomInProgress; } @@ -1381,11 +1570,9 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe vPanel.add(createInfoWindowLabelAndValue(stringMessages.sailNumber(), competitorDTO.getSailID())); Integer rank = null; if (quickRanks != null) { - for (QuickRankDTO quickRank : quickRanks) { - if (quickRank.competitor.equals(competitorDTO)) { - rank = quickRank.rank; - break; - } + QuickRankDTO quickRank = quickRanks.get(competitorDTO); + if (quickRank != null) { + rank = quickRank.rank; } } if (rank != null) { @@ -1621,7 +1808,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe i.remove(); // only this way a ConcurrentModificationException while looping can be avoided } } - showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedCompetitors()); + showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedFilteredCompetitors()); } else { // adding a single competitor; may need to re-load data, so refresh: timeChanged(timer.getTime(), null); @@ -1632,7 +1819,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe if (boatCanvas != null) { boatCanvas.setSelected(displayHighlighted(competitor)); boatCanvas.draw(); - showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedCompetitors()); + showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedFilteredCompetitors()); } else { // seems like an internal error not to find the lowlighted marker; but maybe the // competitor was added late to the race; @@ -1674,7 +1861,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe removedBoatOverlay.removeFromMap(); } fixesAndTails.removeTail(competitor); - showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedCompetitors()); + showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedFilteredCompetitors()); } } else { // "lowlight" currently selected competitor @@ -1683,7 +1870,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe boatCanvas.setSelected(displayHighlighted(competitor)); boatCanvas.draw(); } - showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedCompetitors()); + showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedFilteredCompetitors()); } } //Trigger auto-zoom if needed @@ -1760,8 +1947,9 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe requiredRedraw = true; } if (!newSettings.getZoomSettings().equals(settings.getZoomSettings())) { - settings.setZoomSettings(newSettings.getZoomSettings()); + settings.setZoomSettings(newSettings.getZoomSettings()); if (!settings.getZoomSettings().containsZoomType(ZoomTypes.NONE)) { + removeTransitions(); zoomMapToNewBounds(settings.getZoomSettings().getNewBounds(this)); } } @@ -1781,8 +1969,8 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe public static class BoatsBoundsCalculator extends LatLngBoundsCalculatorForSelected { @Override - public LatLngBounds calculateNewBounds(RaceMap forMap) { - LatLngBounds newBounds = null; + public Bounds calculateNewBounds(RaceMap forMap) { + Bounds newBounds = null; Iterable selectedCompetitors = forMap.competitorSelection.getSelectedCompetitors(); Iterable competitors = new ArrayList(); if (selectedCompetitors == null || !selectedCompetitors.iterator().hasNext()) { @@ -1794,16 +1982,11 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe try { GPSFixDTO competitorFix = forMap.getBoatFix(competitor, forMap.timer.getTime()); PositionDTO competitorPosition = competitorFix != null ? competitorFix.position : null; - LatLng competitorLatLng = competitorPosition != null ? LatLng.newInstance(competitorPosition.latDeg, - competitorPosition.lngDeg) : null; - LatLngBounds bounds = competitorLatLng != null ? LatLngBounds.newInstance(competitorLatLng, - competitorLatLng) : null; - if (bounds != null) { + if (competitorPosition != null) { if (newBounds == null) { - newBounds = bounds; + newBounds = BoundsUtil.getAsBounds(competitorPosition); } else { - newBounds.extend(bounds.getNorthEast()); - newBounds.extend(bounds.getSouthWest()); + newBounds = newBounds.extend(BoundsUtil.getAsPosition(competitorPosition)); } } } catch (IndexOutOfBoundsException e) { @@ -1817,50 +2000,46 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe } public static class TailsBoundsCalculator extends LatLngBoundsCalculatorForSelected { - @Override - public LatLngBounds calculateNewBounds(RaceMap racemap) { - LatLngBounds newBounds = null; + public Bounds calculateNewBounds(RaceMap racemap) { + Bounds newBounds = null; Iterable competitors = isZoomOnlyToSelectedCompetitors(racemap) ? racemap.competitorSelection.getSelectedCompetitors() : racemap.getCompetitorsToShow(); for (CompetitorDTO competitor : competitors) { Polyline tail = racemap.fixesAndTails.getTail(competitor); - LatLngBounds bounds = null; + Bounds bounds = null; // TODO: Find a replacement for missing Polyline function getBounds() from v2 // see also http://stackoverflow.com/questions/3284808/getting-the-bounds-of-a-polyine-in-google-maps-api-v3; // optionally, consider providing a bounds cache with two sorted sets that organize the LatLng objects for O(1) bounds calculation and logarithmic add, ideally O(1) remove - if(tail != null && tail.getPath().getLength() >= 2) { - bounds = LatLngBounds.newInstance(tail.getPath().get(0), tail.getPath().get(1)); - for(int i = 2; i < tail.getPath().getLength(); i++) { - bounds.extend(tail.getPath().get(i)); + if (tail != null && tail.getPath().getLength() >= 1) { + bounds = BoundsUtil.getAsBounds(BoundsUtil.getAsPosition(tail.getPath().get(0))); + for (int i = 1; i < tail.getPath().getLength(); i++) { + bounds = bounds.extend(BoundsUtil.getAsPosition(tail.getPath().get(i))); } } if (bounds != null) { if (newBounds == null) { newBounds = bounds; } else { - newBounds.extend(bounds.getNorthEast()); - newBounds.extend(bounds.getSouthWest()); + newBounds = newBounds.extend(bounds); } } } return newBounds; } - } public static class CourseMarksBoundsCalculator implements LatLngBoundsCalculator { @Override - public LatLngBounds calculateNewBounds(RaceMap forMap) { - LatLngBounds newBounds = null; + public Bounds calculateNewBounds(RaceMap forMap) { + Bounds newBounds = null; Iterable marksToZoom = forMap.markDTOs.values(); if (marksToZoom != null) { for (MarkDTO markDTO : marksToZoom) { - LatLng markLatLng = LatLng.newInstance(markDTO.position.latDeg, markDTO.position.lngDeg); - LatLngBounds bounds = LatLngBounds.newInstance(markLatLng, markLatLng); + Bounds bounds = BoundsUtil.getAsBounds(markDTO.position); if (newBounds == null) { newBounds = bounds; } else { - newBounds.extend(markLatLng); + newBounds = newBounds.extend(bounds); } } } @@ -1870,18 +2049,18 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe public static class WindSensorsBoundsCalculator implements LatLngBoundsCalculator { @Override - public LatLngBounds calculateNewBounds(RaceMap forMap) { - LatLngBounds newBounds = null; + public Bounds calculateNewBounds(RaceMap forMap) { + Bounds newBounds = null; Collection marksToZoom = forMap.windSensorOverlays.values(); if (marksToZoom != null) { - for (WindSensorOverlay windSensorOverlay: marksToZoom) { - LatLng windSensorLatLng = windSensorOverlay.getLatLngPosition(); - if(windSensorLatLng != null) { - LatLngBounds bounds = LatLngBounds.newInstance(windSensorLatLng, windSensorLatLng); + for (WindSensorOverlay windSensorOverlay : marksToZoom) { + Position windSensorPosition = BoundsUtil.getAsPosition(windSensorOverlay.getLatLngPosition()); + if (windSensorPosition != null) { + Bounds bounds = new BoundsImpl(windSensorPosition, windSensorPosition); if (newBounds == null) { newBounds = bounds; } else { - newBounds.extend(windSensorLatLng); + newBounds = newBounds.extend(windSensorPosition); } } } @@ -1917,6 +2096,12 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe public void filteredCompetitorsListChanged(Iterable filteredCompetitors) { timeChanged(timer.getTime(), null); } + + @Override + public void filterChanged(FilterSet> oldFilterSet, + FilterSet> newFilterSet) { + // nothing to do; if the list of filtered competitors has changed, a separate call to filteredCompetitorsListChanged will occur + } @Override public PolylineOptions createTailStyle(CompetitorDTO competitor, boolean isHighlighted) { @@ -1967,4 +2152,34 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe }); return result; } + + @Override + public Integer getRank(CompetitorDTO competitor) { + final Integer result; + QuickRankDTO quickRank = quickRanks.get(competitor); + if (quickRank != null) { + result = quickRank.rank; + } else { + result = null; + } + return result; + } + + private Image createSAPLogo() { + ImageResource sapLogoResource = resources.sapLogoOverlay(); + Image sapLogo = new Image(sapLogoResource); + sapLogo.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + Window.open("http://www.sap.com", "_blank", null); + } + }); + sapLogo.setStyleName("raceBoard-Logo"); + return sapLogo; + } + + @Override + public String getDependentCssClassName() { + return "raceMap"; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettingsDialogComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettingsDialogComponent.java index 2093ecd246b..64dc5cfe30a 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettingsDialogComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettingsDialogComponent.java @@ -212,7 +212,10 @@ public class RaceMapSettingsDialogComponent implements SettingsDialogComponent, CompetitorSelectionChangeListener { + private final static String LOCAL_STORAGE_COMPETITORS_FILTER_SETS_KEY = "sailingAnalytics.raceBoard.competitorsFilterSets"; + private final static CompetitorFilterCss css = CompetitorFilterResources.INSTANCE.css(); + private final TextBox searchTextBox; + private final Button clearTextBoxButton; + private final Button advancedSettingsButton; + private final StringMessages stringMessages; + private final AbstractListFilter filter; + private final CompetitorSelectionProvider competitorSelectionProvider; + private String lastFilterSetNameWithoutThis; + private final CompetitorsFilterSets competitorsFilterSets; + private FilterSet> lastActiveCompetitorFilterSet; + private final LeaderboardFetcher leaderboardFetcher; + private final RaceMap raceMap; + private final RaceIdentifier selectedRaceIdentifier; + private final Button settingsButton; + private final FlowPanel searchBoxPanel; + + public CompetitorFilterPanel(final CompetitorSelectionProvider competitorSelectionProvider, + final StringMessages stringMessages, RaceMap raceMap, LeaderboardFetcher leaderboardFetcher, + RaceIdentifier selectedRaceIdentifier) { + css.ensureInjected(); + this.stringMessages = stringMessages; + this.raceMap = raceMap; + this.leaderboardFetcher = leaderboardFetcher; + this.selectedRaceIdentifier = selectedRaceIdentifier; + this.competitorSelectionProvider = competitorSelectionProvider; + this.setStyleName(css.competitorFilterContainer()); + CompetitorsFilterSets loadedCompetitorsFilterSets = loadCompetitorsFilterSets(); + if (loadedCompetitorsFilterSets != null) { + competitorsFilterSets = loadedCompetitorsFilterSets; + insertSelectedCompetitorsFilter(competitorsFilterSets); + } else { + competitorsFilterSets = createAndAddDefaultCompetitorsFilter(); + storeCompetitorsFilterSets(competitorsFilterSets); + } + + filter = new AbstractListFilter() { + @Override + public Iterable getStrings(CompetitorDTO competitor) { + return Arrays.asList(competitor.getName().toLowerCase(), competitor.getSailID().toLowerCase()); + } + }; + settingsButton = new Button(); + settingsButton.setTitle(stringMessages.settings()); + settingsButton.setStyleName(css.button()); + settingsButton.addStyleName(css.settingsButton()); + settingsButton.addStyleName(css.settingsButtonBackgroundImage()); + Button submitButton = new Button(); + submitButton.setStyleName(css.button()); + submitButton.addStyleName(css.searchButton()); + submitButton.addStyleName(css.searchButtonBackgroundImage()); + searchTextBox = new TextBox(); + searchTextBox.getElement().setAttribute("placeholder", stringMessages.searchCompetitorsBySailNumberOrName()); + searchTextBox.addKeyUpHandler(this); + searchTextBox.setStyleName(css.searchInput()); + clearTextBoxButton = new Button(); + clearTextBoxButton.setStyleName(css.button()); + clearTextBoxButton.addStyleName(css.clearButton()); + clearTextBoxButton.addStyleName(css.clearButtonBackgroundImage()); + clearTextBoxButton.addStyleName(css.hiddenButton()); + clearTextBoxButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + clearSelection(); + } + }); + advancedSettingsButton = new Button(""); + advancedSettingsButton.setStyleName(css.button()); + advancedSettingsButton.addStyleName(css.filterButton()); + advancedSettingsButton.setTitle(stringMessages.competitorsFilter()); + advancedSettingsButton.addStyleName(css.filterInactiveButtonBackgroundImage()); + advancedSettingsButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + showEditCompetitorsFiltersDialog(); + } + }); + searchBoxPanel = new FlowPanel(); + searchBoxPanel.setStyleName(css.searchBox()); + searchBoxPanel.add(submitButton); + searchBoxPanel.add(searchTextBox); + searchBoxPanel.add(clearTextBoxButton); + add(searchBoxPanel); + add(settingsButton); + add(advancedSettingsButton); + } + + public CompetitorsFilterSets getCompetitorsFilterSets() { + return competitorsFilterSets; + } + + /** + * @param event ignored; may be null + */ + @Override + public void onKeyUp(KeyUpEvent event) { + String newValue = searchTextBox.getValue(); + if (newValue.trim().isEmpty()) { + removeSearchFilter(); + clearTextBoxButton.addStyleName(css.hiddenButton()); + } else { + if (newValue.length() >= 2) { + clearTextBoxButton.removeStyleName(css.hiddenButton()); + ensureSearchFilterIsSet(); + competitorSelectionProvider.setCompetitorsFilterSet(competitorSelectionProvider.getCompetitorsFilterSet()); // + } + } + } + + private void ensureSearchFilterIsSet() { + if (competitorSelectionProvider.getCompetitorsFilterSet() == null || !Util.contains(competitorSelectionProvider.getCompetitorsFilterSet().getFilters(), this)) { + FilterSet> newFilterSetWithThis = new FilterSet<>(getName()); + if (competitorSelectionProvider.getCompetitorsFilterSet() != null) { + for (Filter oldFilter : competitorSelectionProvider.getCompetitorsFilterSet() + .getFilters()) { + newFilterSetWithThis.addFilter(oldFilter); + } + } + newFilterSetWithThis.addFilter(this); + competitorSelectionProvider.setCompetitorsFilterSet(newFilterSetWithThis); + } + } + + private void removeSearchFilter() { + if (competitorSelectionProvider.getCompetitorsFilterSet() != null + && Util.contains(competitorSelectionProvider.getCompetitorsFilterSet().getFilters(), this)) { + FilterSet> newFilterSetWithThis = new FilterSet<>(lastFilterSetNameWithoutThis); + for (Filter oldFilter : competitorSelectionProvider.getCompetitorsFilterSet().getFilters()) { + if (oldFilter != this) { + newFilterSetWithThis.addFilter(oldFilter); + } + } + competitorSelectionProvider.setCompetitorsFilterSet(newFilterSetWithThis); + } + } + + public void clearSelection() { + searchTextBox.setText(""); + clearTextBoxButton.addStyleName(css.hiddenButton()); + onKeyUp(null); + } + + @Override + public boolean matches(CompetitorDTO competitor) { + final String[] keywords = searchTextBox.getText().split(" "); + final List lowercaseKeywords = new ArrayList<>(keywords.length); + for (String keyword : keywords) { + lowercaseKeywords.add(keyword.toLowerCase()); + } + return !Util.isEmpty(filter.applyFilter(lowercaseKeywords, Collections.singleton(competitor))); + } + + @Override + public String getName() { + return stringMessages.competitorSearchFilter(); + } + + public void clearAllActiveFilters() { + competitorsFilterSets.setActiveFilterSet(null); + competitorSelectionProvider.clearAllFilters(); + } + + private void showEditCompetitorsFiltersDialog() { + CompetitorsFilterSetsDialog competitorsFilterSetsDialog = new CompetitorsFilterSetsDialog(competitorsFilterSets, + stringMessages, new DialogCallback() { + @Override + public void ok(final CompetitorsFilterSets newCompetitorsFilterSets) { + competitorsFilterSets.getFilterSets().clear(); + competitorsFilterSets.getFilterSets().addAll(newCompetitorsFilterSets.getFilterSets()); + competitorsFilterSets.setActiveFilterSet(newCompetitorsFilterSets.getActiveFilterSet()); + + updateCompetitorsFilterContexts(newCompetitorsFilterSets); + competitorSelectionProvider.setCompetitorsFilterSet(newCompetitorsFilterSets.getActiveFilterSetWithGeneralizedType()); + updateCompetitorsFilterControlState(newCompetitorsFilterSets); + storeCompetitorsFilterSets(newCompetitorsFilterSets); + } + + @Override + public void cancel() { + } + }); + + competitorsFilterSetsDialog .show(); + } + + private void insertSelectedCompetitorsFilter(CompetitorsFilterSets filterSet) { + // selected competitors filter + FilterSet> selectedCompetitorsFilterSet = + new FilterSet>(stringMessages.selectedCompetitors()); + selectedCompetitorsFilterSet.setEditable(false); + SelectedCompetitorsFilter selectedCompetitorsFilter = new SelectedCompetitorsFilter(); + selectedCompetitorsFilter.setCompetitorSelectionProvider(competitorSelectionProvider); + selectedCompetitorsFilterSet.addFilter(selectedCompetitorsFilter); + + filterSet.addFilterSet(0, selectedCompetitorsFilterSet); + } + + private void updateCompetitorsFilterContexts(CompetitorsFilterSets filterSets) { + for (FilterSet> filterSet : filterSets.getFilterSets()) { + for (Filter filter : filterSet.getFilters()) { + if (leaderboardFetcher != null && filter instanceof LeaderboardFilterContext) { + ((LeaderboardFilterContext) filter).setLeaderboardFetcher(leaderboardFetcher); + } + if (filter instanceof SelectedRaceFilterContext) { + if (selectedRaceIdentifier != null) { + ((SelectedRaceFilterContext) filter).setSelectedRace(selectedRaceIdentifier); + } + if (raceMap != null) { + ((SelectedRaceFilterContext) filter).setQuickRankProvider(raceMap); + } + } + if (filter instanceof CompetitorSelectionProviderFilterContext) { + ((CompetitorSelectionProviderFilterContext) filter) + .setCompetitorSelectionProvider(competitorSelectionProvider); + } + } + } + } + + /** + * Updates the competitor filter checkbox state by setting its check mark and updating its labal according to the + * current filter selected + */ + private void updateCompetitorsFilterControlState(CompetitorsFilterSets filterSets) { + String competitorsFilterTitle = stringMessages.competitorsFilter(); + FilterSet> activeFilterSet = filterSets.getActiveFilterSet(); + if (activeFilterSet != null) { + if (lastActiveCompetitorFilterSet == null) { + advancedSettingsButton.removeStyleName(css.filterInactiveButtonBackgroundImage()); + advancedSettingsButton.addStyleName(css.filterActiveButtonBackgroundImage()); + } + lastActiveCompetitorFilterSet = activeFilterSet; + } else { + if (lastActiveCompetitorFilterSet != null) { + advancedSettingsButton.removeStyleName(css.filterActiveButtonBackgroundImage()); + advancedSettingsButton.addStyleName(css.filterInactiveButtonBackgroundImage()); + } + lastActiveCompetitorFilterSet = null; + } + if (lastActiveCompetitorFilterSet != null) { + advancedSettingsButton.setTitle(competitorsFilterTitle+" ("+lastActiveCompetitorFilterSet.getName()+")"); + } else { + advancedSettingsButton.setTitle(competitorsFilterTitle); + } + } + + private CompetitorsFilterSets loadCompetitorsFilterSets() { + CompetitorsFilterSets result = null; + Storage localStorage = Storage.getLocalStorageIfSupported(); + if (localStorage != null) { + try { + String jsonAsLocalStore = localStorage.getItem(LOCAL_STORAGE_COMPETITORS_FILTER_SETS_KEY); + if (jsonAsLocalStore != null && !jsonAsLocalStore.isEmpty()) { + CompetitorsFilterSetsJsonDeSerializer deserializer = new CompetitorsFilterSetsJsonDeSerializer(); + JSONValue value = JSONParser.parseStrict(jsonAsLocalStore); + if (value.isObject() != null) { + result = deserializer.deserialize((JSONObject) value); + } + } + } catch (Exception e) { + // exception during loading of competitor filters from local storage + } + } + return result; + } + + private void storeCompetitorsFilterSets(CompetitorsFilterSets newCompetitorsFilterSets) { + Storage localStorage = Storage.getLocalStorageIfSupported(); + if(localStorage != null) { + // delete old value + localStorage.removeItem(LOCAL_STORAGE_COMPETITORS_FILTER_SETS_KEY); + + // store the competiors filter set + CompetitorsFilterSetsJsonDeSerializer serializer = new CompetitorsFilterSetsJsonDeSerializer(); + JSONObject jsonObject = serializer.serialize(newCompetitorsFilterSets); + localStorage.setItem(LOCAL_STORAGE_COMPETITORS_FILTER_SETS_KEY, jsonObject.toString()); + } + } + + private CompetitorsFilterSets createAndAddDefaultCompetitorsFilter() { + CompetitorsFilterSets filterSets = new CompetitorsFilterSets(); + + // 1. selected competitors filter + insertSelectedCompetitorsFilter(filterSets); + + // 2. Top 30 competitors by race rank + int maxRaceRank = 30; + FilterSet> topNRaceRankCompetitorsFilterSet = + new FilterSet>(stringMessages.topNCompetitorsByRaceRank(maxRaceRank)); + CompetitorRaceRankFilter raceRankFilter = new CompetitorRaceRankFilter(); + raceRankFilter.setOperator(new BinaryOperator(BinaryOperator.Operators.LessThanEquals)); + raceRankFilter.setValue(maxRaceRank); + topNRaceRankCompetitorsFilterSet.addFilter(raceRankFilter); + filterSets.addFilterSet(topNRaceRankCompetitorsFilterSet); + + // 3. Top 30 competitors by total rank + int maxTotalRank = 30; + FilterSet> topNTotalRankCompetitorsFilterSet = + new FilterSet>(stringMessages.topNCompetitorsByTotalRank(maxTotalRank)); + CompetitorTotalRankFilter totalRankFilter = new CompetitorTotalRankFilter(); + totalRankFilter.setOperator(new BinaryOperator(BinaryOperator.Operators.LessThanEquals)); + totalRankFilter.setValue(50); + topNTotalRankCompetitorsFilterSet.addFilter(totalRankFilter); + filterSets.addFilterSet(topNTotalRankCompetitorsFilterSet); + // set default active filter + filterSets.setActiveFilterSet(topNRaceRankCompetitorsFilterSet); + return filterSets; + } + + /** + * Provides the settings button shown in the panel for clients to add an event handler to it + */ + public Button getSettingsButton() { + return settingsButton; + } + + /** + * @return the {@link QuickRankProvider} if set or null if there is none + */ + public QuickRankProvider getQuickRankProvider() { + return this.raceMap; + } + + @Override + public void filterChanged(FilterSet> oldFilterSet, + FilterSet> newFilterSet) { + if (newFilterSet != null && !Util.contains(newFilterSet.getFilters(), this)) { + lastFilterSetNameWithoutThis = newFilterSet.getName(); + } + if (!Util.contains(newFilterSet.getFilters(), this)) { + onKeyUp(null); // ensure that if the search box has text, this filter is in the current filter set + } // else, this filter is part of the current filter set, and it will be so if and only if it shall be, e.g., because this object + // has added itself as a filter + } + + @Override public void competitorsListChanged(Iterable competitors) {} + @Override public void filteredCompetitorsListChanged(Iterable filteredCompetitors) {} + @Override public void addedToSelection(CompetitorDTO competitor) {} + @Override public void removedFromSelection(CompetitorDTO competitor) {} + + @Override + public String validate(StringMessages stringMessages) { + return null; + } + + @Override + public String getLocalizedName(StringMessages stringMessages) { + return getName(); + } + + @Override + public String getLocalizedDescription(StringMessages stringMessages) { + return getName(); + } + + @Override + public FilterWithUI copy() { + return null; + } + + @Override + public FilterUIFactory createUIFactory() { + return null; + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/CompetitorFilterResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/CompetitorFilterResources.java new file mode 100755 index 00000000000..1a774664431 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/CompetitorFilterResources.java @@ -0,0 +1,46 @@ +package com.sap.sailing.gwt.ui.leaderboard; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; +import com.google.gwt.resources.client.ImageResource; + +public interface CompetitorFilterResources extends ClientBundle { + public static final CompetitorFilterResources INSTANCE = GWT.create(CompetitorFilterResources.class); + + @Source("com/sap/sailing/gwt/ui/client/images/SAP_RV_Clear.png") + ImageResource clearButton(); + + @Source("com/sap/sailing/gwt/ui/client/images/SAP_RV_CompetitorsFilter_INACTIVE.png") + ImageResource filterInactiveButton(); + + @Source("com/sap/sailing/gwt/ui/client/images/SAP_RV_CompetitorsFilter_ACTIVE.png") + ImageResource filterActiveButton(); + + @Source("com/sap/sailing/gwt/ui/client/images/SAP_RV_Search.png") + ImageResource searchButton(); + + @Source("com/sap/sailing/gwt/ui/client/images/SAP_RV_Settings.png") + ImageResource settingsButton(); + + @Source("com/sap/sailing/gwt/ui/leaderboard/CompetitorFilter.css") + CompetitorFilterCss css(); + + public interface CompetitorFilterCss extends CssResource { + String button(); + String hiddenButton(); + String clearButton(); + String searchButton(); + String settingsButton(); + String filterButton(); + String competitorFilterContainer(); + String searchBox(); + String searchInput(); + String filterInactiveButtonBackgroundImage(); + String filterActiveButtonBackgroundImage(); + String clearButtonBackgroundImage(); + String searchButtonBackgroundImage(); + String settingsButtonBackgroundImage(); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/DetailTypeColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/DetailTypeColumn.java index 5ad8712e6cb..c45ca6446d7 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/DetailTypeColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/DetailTypeColumn.java @@ -10,7 +10,7 @@ import com.sap.sailing.domain.common.impl.InvertibleComparatorAdapter; import com.sap.sailing.gwt.ui.client.DetailTypeFormatter; public abstract class DetailTypeColumn, RenderingType> extends - SortableColumn { + LeaderboardSortableColumnWithMinMax { private static final String HtmlConstantToInlineHeadersWithAndWithoutUnit = " "; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/DialogBoxExt.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/DialogBoxExt.java index 80c191d8e72..d7ad333026d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/DialogBoxExt.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/DialogBoxExt.java @@ -25,14 +25,15 @@ public class DialogBoxExt extends DialogBox { * You have to provide a widget here. If click on the widget the dialog box will be closed. * @param closeDialogBox */ - public DialogBoxExt(Widget closeDialogBox) { - super(); + public DialogBoxExt(Widget closeDialogBox, String title, boolean modal) { + super(/*autohide*/false, /*modal*/modal); assert closeDialogBox != null; captionPanel = new HorizontalPanel(); closeWidget = closeDialogBox; closeWidget.getElement().addClassName("chartCloseButton"); // an empty header could cause a problem! - setHTML(" "); + setHTML(title); + } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/DisplayedLeaderboardRowsProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/DisplayedLeaderboardRowsProvider.java index 6bc5947128f..2fcb0773291 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/DisplayedLeaderboardRowsProvider.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/DisplayedLeaderboardRowsProvider.java @@ -1,8 +1,9 @@ package com.sap.sailing.gwt.ui.leaderboard; -import java.util.Collection; +import java.util.Map; import com.sap.sailing.domain.common.dto.AbstractLeaderboardDTO; +import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.domain.common.dto.LeaderboardRowDTO; /** @@ -20,5 +21,5 @@ public interface DisplayedLeaderboardRowsProvider { * rows} in case {@link #preSelectedRace} is null, or only the rows of the competitors who scored in * the race identified by {@link #preSelectedRace} otherwise. */ - public Collection getRowsToDisplay(); + public Map getRowsToDisplay(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ExpandableSortableColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ExpandableSortableColumn.java index a27387f278f..15fad2b3fc8 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ExpandableSortableColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ExpandableSortableColumn.java @@ -16,6 +16,7 @@ import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.SortingOrder; import com.sap.sailing.domain.common.dto.LeaderboardRowDTO; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.controls.AbstractSortableColumnWithMinMax; /** * A column that is sortable and offers an expand/collapse button in its column header. @@ -23,12 +24,12 @@ import com.sap.sailing.gwt.ui.client.StringMessages; * @author Axel Uhl (D043530) * */ -public abstract class ExpandableSortableColumn extends SortableColumn { +public abstract class ExpandableSortableColumn extends LeaderboardSortableColumnWithMinMax { private boolean enableExpansion; private boolean togglingInProcess; private boolean suppressSortingOnce; private final LeaderboardPanel leaderboardPanel; - private final Map> detailColumnsMap; + private final Map> detailColumnsMap; private final List detailSelection; /** @@ -38,7 +39,7 @@ public abstract class ExpandableSortableColumn extends SortableColumn> directChildren; + protected List> directChildren; /** * Tells if this race column is currently displayed in expanded form which includes a visualization @@ -60,7 +61,7 @@ public abstract class ExpandableSortableColumn extends SortableColumn> getDetailColumnMap( + protected Map> getDetailColumnMap( LeaderboardPanel leaderboardPanel, StringMessages stringConstants, String detailHeaderStyle, String detailColumnStyle) { return Collections.emptyMap(); @@ -89,12 +90,12 @@ public abstract class ExpandableSortableColumn extends SortableColumn> getDirectChildren() { - List> result; + protected Iterable> getDirectChildren() { + List> result; if (isExpanded()) { - result = new ArrayList>(); + result = new ArrayList>(); for (DetailType detailColumnType : detailSelection) { - SortableColumn selectedColumn = detailColumnsMap.get(detailColumnType); + AbstractSortableColumnWithMinMax selectedColumn = detailColumnsMap.get(detailColumnType); if (selectedColumn != null) { result.add(selectedColumn); } @@ -105,7 +106,7 @@ public abstract class ExpandableSortableColumn extends SortableColumn createExpansionColumn(DetailType detailColumnType) { + protected AbstractSortableColumnWithMinMax createExpansionColumn(DetailType detailColumnType) { throw new RuntimeException("Detail column type "+detailColumnType+" not supported by column of type "+getClass().getName()); } @@ -122,10 +123,10 @@ public abstract class ExpandableSortableColumn extends SortableColumn> getAllVisibleChildren() { - List> transitiveChildren = new ArrayList>(); + private Collection> getAllVisibleChildren() { + List> transitiveChildren = new ArrayList<>(); if (isExpanded()) { - for (SortableColumn childColumn : getDirectChildren()) { + for (AbstractSortableColumnWithMinMax childColumn : getDirectChildren()) { transitiveChildren.add(childColumn); if (childColumn instanceof ExpandableSortableColumn) { @SuppressWarnings("unchecked") @@ -153,7 +154,7 @@ public abstract class ExpandableSortableColumn extends SortableColumn table = getLeaderboardPanel().getLeaderboardTable(); if (isExpanded()) { - for (SortableColumn column : getAllVisibleChildren()) { + for (AbstractSortableColumnWithMinMax column : getAllVisibleChildren()) { getLeaderboardPanel().removeColumn(column); // removes only the children currently displayed } getLeaderboardPanel().getBusyIndicator().setBusy(false); @@ -170,7 +171,7 @@ public abstract class ExpandableSortableColumn extends SortableColumn column : getAllVisibleChildren()) { + for (AbstractSortableColumnWithMinMax column : getAllVisibleChildren()) { column.updateMinMax(); getLeaderboardPanel().insertColumn(insertIndex++, column); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/FormattedDoubleDetailTypeColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/FormattedDoubleDetailTypeColumn.java index 0ff31c19742..68cb66762d9 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/FormattedDoubleDetailTypeColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/FormattedDoubleDetailTypeColumn.java @@ -38,7 +38,7 @@ public class FormattedDoubleDetailTypeColumn extends DetailTypeColumn>( - new AsyncCallback>() { - @Override - public void onSuccess(Util.Pair leaderboardNameAndType) { - if (leaderboardNameAndType != null && leaderboardName.equals(leaderboardNameAndType.getA())) { - Window.setTitle(leaderboardName); - leaderboardType = leaderboardNameAndType.getB(); - createUI(showRaceDetails, embedded, hideToolbar); - } else { - RootPanel.get().add(new Label(stringMessages.noSuchLeaderboard())); + final Runnable checkLeaderboardNameAndCreateUI = new Runnable() { + @Override + public void run() { + sailingService.checkLeaderboardName(leaderboardName, + new MarkedAsyncCallback>( + new AsyncCallback>() { + @Override + public void onSuccess(Util.Pair leaderboardNameAndType) { + if (leaderboardNameAndType != null + && leaderboardName.equals(leaderboardNameAndType.getA())) { + Window.setTitle(leaderboardName); + leaderboardType = leaderboardNameAndType.getB(); + createUI(showRaceDetails, embedded, hideToolbar, event); + } else { + RootPanel.get().add(new Label(stringMessages.noSuchLeaderboard())); + } + } + + @Override + public void onFailure(Throwable t) { + reportError("Error trying to obtain list of leaderboard names: " + + t.getMessage()); + } + })); + } + }; + if (eventId == null) { + checkLeaderboardNameAndCreateUI.run(); // use null-initialized event field + } else { + sailingService.getEventById(eventId, /* withStatisticalData */false, new MarkedAsyncCallback( + new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + reportError("Error trying to obtain event "+eventId+": " + caught.getMessage()); } - } - - @Override - public void onFailure(Throwable t) { - reportError("Error trying to obtain list of leaderboard names: " + t.getMessage()); - } - })); + + @Override + public void onSuccess(EventDTO result) { + event = result; + checkLeaderboardNameAndCreateUI.run(); + } + })); + } } else { RootPanel.get().add(new Label(stringMessages.noSuchLeaderboard())); } - final String zoomTo = Window.Location.getParameter(PARAM_ZOOM_TO); + final String zoomTo = Window.Location.getParameter(LeaderboardUrlSettings.PARAM_ZOOM_TO); if (zoomTo != null) { RootPanel.getBodyElement().setAttribute("style", "zoom: "+zoomTo+";-moz-transform: scale("+zoomTo+");-moz-transform-origin: 0 0;-o-transform: scale("+zoomTo+");-o-transform-origin: 0 0;-webkit-transform: scale("+zoomTo+");-webkit-transform-origin: 0 0;"); } } - private void createUI(boolean showRaceDetails, boolean embedded, boolean hideToolbar) { + private void createUI(boolean showRaceDetails, boolean embedded, boolean hideToolbar, EventDTO event) { DockLayoutPanel mainPanel = new DockLayoutPanel(Unit.PX); RootLayoutPanel.get().add(mainPanel); LogoAndTitlePanel logoAndTitlePanel = null; @@ -137,7 +132,7 @@ public class LeaderboardEntryPoint extends AbstractEntryPoint { if (leaderboardDisplayName == null || leaderboardDisplayName.isEmpty()) { leaderboardDisplayName = leaderboardName; } - globalNavigationPanel = new GlobalNavigationPanel(stringMessages, true, null, leaderboardGroupName); + globalNavigationPanel = new GlobalNavigationPanel(stringMessages, true, null, leaderboardGroupName, event, null); logoAndTitlePanel = new LogoAndTitlePanel(leaderboardGroupName, leaderboardDisplayName, stringMessages, this) { @Override public void onResize() { @@ -164,11 +159,11 @@ public class LeaderboardEntryPoint extends AbstractEntryPoint { if (leaderboardSettings.getDelayBetweenAutoAdvancesInMilliseconds() != null) { timer.setPlayMode(PlayModes.Live); // the leaderboard, viewed via the entry point, goes "live" and "playing" if an auto-refresh } // interval has been specified - boolean autoExpandLastRaceColumn = GwtHttpRequestUtils.getBooleanParameter(PARAM_AUTO_EXPAND_LAST_RACE_COLUMN, false); - boolean showCharts = GwtHttpRequestUtils.getBooleanParameter(PARAM_SHOW_CHARTS, false); - boolean showOverallLeaderboard = GwtHttpRequestUtils.getBooleanParameter(PARAM_SHOW_OVERALL_LEADERBOARD, false); - boolean showSeriesLeaderboards = GwtHttpRequestUtils.getBooleanParameter(PARAM_SHOW_SERIES_LEADERBOARDS, false); - String chartDetailParam = GwtHttpRequestUtils.getStringParameter(PARAM_CHART_DETAIL, null); + boolean autoExpandLastRaceColumn = GwtHttpRequestUtils.getBooleanParameter(LeaderboardUrlSettings.PARAM_AUTO_EXPAND_LAST_RACE_COLUMN, false); + boolean showCharts = GwtHttpRequestUtils.getBooleanParameter(LeaderboardUrlSettings.PARAM_SHOW_CHARTS, false); + boolean showOverallLeaderboard = GwtHttpRequestUtils.getBooleanParameter(LeaderboardUrlSettings.PARAM_SHOW_OVERALL_LEADERBOARD, false); + boolean showSeriesLeaderboards = GwtHttpRequestUtils.getBooleanParameter(LeaderboardUrlSettings.PARAM_SHOW_SERIES_LEADERBOARDS, false); + String chartDetailParam = GwtHttpRequestUtils.getStringParameter(LeaderboardUrlSettings.PARAM_CHART_DETAIL, null); DetailType chartDetailType; if (chartDetailParam != null && (DetailType.REGATTA_RANK.name().equals(chartDetailParam) || DetailType.OVERALL_RANK.name().equals(chartDetailParam) || DetailType.REGATTA_TOTAL_POINTS_SUM.name().equals(chartDetailParam))) { @@ -193,9 +188,9 @@ public class LeaderboardEntryPoint extends AbstractEntryPoint { private RaceIdentifier getPreselectedRace(Map> parameterMap) { RaceIdentifier result; - if (parameterMap.containsKey(PARAM_RACE_NAME) && parameterMap.get(PARAM_RACE_NAME).size() == 1 && - parameterMap.containsKey(PARAM_REGATTA_NAME) && parameterMap.get(PARAM_REGATTA_NAME).size() == 1) { - result = new RegattaNameAndRaceName(parameterMap.get(PARAM_REGATTA_NAME).get(0), parameterMap.get(PARAM_RACE_NAME).get(0)); + if (parameterMap.containsKey(LeaderboardUrlSettings.PARAM_RACE_NAME) && parameterMap.get(LeaderboardUrlSettings.PARAM_RACE_NAME).size() == 1 && + parameterMap.containsKey(LeaderboardUrlSettings.PARAM_REGATTA_NAME) && parameterMap.get(LeaderboardUrlSettings.PARAM_REGATTA_NAME).size() == 1) { + result = new RegattaNameAndRaceName(parameterMap.get(LeaderboardUrlSettings.PARAM_REGATTA_NAME).get(0), parameterMap.get(LeaderboardUrlSettings.PARAM_RACE_NAME).get(0)); } else { result = null; } @@ -207,13 +202,13 @@ public class LeaderboardEntryPoint extends AbstractEntryPoint { */ private LeaderboardSettings createLeaderboardSettingsFromURLParameters(Map> parameterMap) { LeaderboardSettings result; - Long refreshIntervalMillis = parameterMap.containsKey(PARAM_REFRESH_INTERVAL_MILLIS) ? Long - .valueOf(parameterMap.get(PARAM_REFRESH_INTERVAL_MILLIS).get(0)) : null; + Long refreshIntervalMillis = parameterMap.containsKey(LeaderboardUrlSettings.PARAM_REFRESH_INTERVAL_MILLIS) ? Long + .valueOf(parameterMap.get(LeaderboardUrlSettings.PARAM_REFRESH_INTERVAL_MILLIS).get(0)) : null; RaceColumnSelectionStrategies raceColumnSelectionStrategy; final Integer numberOfLastRacesToShow; - if (parameterMap.containsKey(PARAM_NAME_LAST_N)) { + if (parameterMap.containsKey(LeaderboardUrlSettings.PARAM_NAME_LAST_N)) { raceColumnSelectionStrategy = RaceColumnSelectionStrategies.LAST_N; - numberOfLastRacesToShow = Integer.valueOf(parameterMap.get(PARAM_NAME_LAST_N).get(0)); + numberOfLastRacesToShow = Integer.valueOf(parameterMap.get(LeaderboardUrlSettings.PARAM_NAME_LAST_N).get(0)); } else if (isSmallWidth()) { raceColumnSelectionStrategy = RaceColumnSelectionStrategies.LAST_N; int width = Window.getClientWidth(); @@ -222,21 +217,21 @@ public class LeaderboardEntryPoint extends AbstractEntryPoint { raceColumnSelectionStrategy = RaceColumnSelectionStrategies.EXPLICIT; numberOfLastRacesToShow = null; } - if (parameterMap.containsKey(PARAM_RACE_NAME) || parameterMap.containsKey(PARAM_RACE_DETAIL) || - parameterMap.containsKey(PARAM_LEG_DETAIL) || parameterMap.containsKey(PARAM_MANEUVER_DETAIL) || - parameterMap.containsKey(PARAM_OVERALL_DETAIL) || parameterMap.containsKey(PARAM_SHOW_ADDED_SCORES) || - parameterMap.containsKey(PARAM_SHOW_OVERALL_COLUMN_WITH_NUMBER_OF_RACES_COMPLETED)) { - List maneuverDetails = getDetailTypeListFromParamValue(parameterMap.get(PARAM_MANEUVER_DETAIL)); - List raceDetails = getDetailTypeListFromParamValue(parameterMap.get(PARAM_RACE_DETAIL)); - List overallDetails = getDetailTypeListFromParamValue(parameterMap.get(PARAM_OVERALL_DETAIL)); - List legDetails = getDetailTypeListFromParamValue(parameterMap.get(PARAM_LEG_DETAIL)); - List namesOfRacesToShow = getStringListFromParamValue(parameterMap.get(PARAM_RACE_NAME)); - boolean showAddedScores = parameterMap.containsKey(PARAM_SHOW_ADDED_SCORES) ? - Boolean.valueOf(parameterMap.get(PARAM_SHOW_ADDED_SCORES).get(0)) : false; - boolean showOverallColumnWithNumberOfRacesSailedPerCompetitor = parameterMap.containsKey(PARAM_SHOW_OVERALL_COLUMN_WITH_NUMBER_OF_RACES_COMPLETED) ? - Boolean.valueOf(parameterMap.get(PARAM_SHOW_OVERALL_COLUMN_WITH_NUMBER_OF_RACES_COMPLETED).get(0)) : false; - boolean autoExpandPreSelectedRace = parameterMap.containsKey(PARAM_AUTO_EXPAND_PRESELECTED_RACE) ? - Boolean.valueOf(parameterMap.get(PARAM_AUTO_EXPAND_PRESELECTED_RACE).get(0)) : + if (parameterMap.containsKey(LeaderboardUrlSettings.PARAM_RACE_NAME) || parameterMap.containsKey(LeaderboardUrlSettings.PARAM_RACE_DETAIL) || + parameterMap.containsKey(LeaderboardUrlSettings.PARAM_LEG_DETAIL) || parameterMap.containsKey(LeaderboardUrlSettings.PARAM_MANEUVER_DETAIL) || + parameterMap.containsKey(LeaderboardUrlSettings.PARAM_OVERALL_DETAIL) || parameterMap.containsKey(LeaderboardUrlSettings.PARAM_SHOW_ADDED_SCORES) || + parameterMap.containsKey(LeaderboardUrlSettings.PARAM_SHOW_OVERALL_COLUMN_WITH_NUMBER_OF_RACES_COMPLETED)) { + List maneuverDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_MANEUVER_DETAIL)); + List raceDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_RACE_DETAIL)); + List overallDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_OVERALL_DETAIL)); + List legDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_LEG_DETAIL)); + List namesOfRacesToShow = getStringListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_RACE_NAME)); + boolean showAddedScores = parameterMap.containsKey(LeaderboardUrlSettings.PARAM_SHOW_ADDED_SCORES) ? + Boolean.valueOf(parameterMap.get(LeaderboardUrlSettings.PARAM_SHOW_ADDED_SCORES).get(0)) : false; + boolean showOverallColumnWithNumberOfRacesSailedPerCompetitor = parameterMap.containsKey(LeaderboardUrlSettings.PARAM_SHOW_OVERALL_COLUMN_WITH_NUMBER_OF_RACES_COMPLETED) ? + Boolean.valueOf(parameterMap.get(LeaderboardUrlSettings.PARAM_SHOW_OVERALL_COLUMN_WITH_NUMBER_OF_RACES_COMPLETED).get(0)) : false; + boolean autoExpandPreSelectedRace = parameterMap.containsKey(LeaderboardUrlSettings.PARAM_AUTO_EXPAND_PRESELECTED_RACE) ? + Boolean.valueOf(parameterMap.get(LeaderboardUrlSettings.PARAM_AUTO_EXPAND_PRESELECTED_RACE).get(0)) : (namesOfRacesToShow != null && namesOfRacesToShow.size() == 1); result = new LeaderboardSettings(maneuverDetails, legDetails, raceDetails, overallDetails, /* namesOfRaceColumnsToShow */ null, @@ -247,7 +242,7 @@ public class LeaderboardEntryPoint extends AbstractEntryPoint { raceColumnSelectionStrategy, showAddedScores, showOverallColumnWithNumberOfRacesSailedPerCompetitor); } else { - final List overallDetails = Collections.emptyList(); + final List overallDetails = Collections.singletonList(DetailType.REGATTA_RANK); result = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, /* overallDetails */ overallDetails, null, /* autoExpandFirstRace */false, refreshIntervalMillis, numberOfLastRacesToShow, @@ -278,82 +273,13 @@ public class LeaderboardEntryPoint extends AbstractEntryPoint { return result; } - public static class LeaderboardUrlSettings { - private final LeaderboardSettings leaderboardSettings; - private final boolean embedded; - private final boolean hideToolbar; - private final boolean showRaceDetails; - private final boolean autoExpandLastRaceColumn; - private final boolean autoRefresh; - private final boolean showCharts; - private final DetailType chartDetail; - private final boolean showOverallLeaderboard; - private final boolean showSeriesLeaderboards; - - public LeaderboardUrlSettings(LeaderboardSettings leaderboardSettings, boolean embedded, - boolean hideToolbar, boolean showRaceDetails, boolean autoRefresh, boolean autoExpandLastRaceColumn, - boolean showCharts, DetailType chartDetail, boolean showOverallLeaderboard, boolean showSeriesLeaderboards) { - super(); - this.leaderboardSettings = leaderboardSettings; - this.embedded = embedded; - this.hideToolbar = hideToolbar; - this.showRaceDetails = showRaceDetails; - this.autoRefresh = autoRefresh; - this.autoExpandLastRaceColumn = autoExpandLastRaceColumn; - this.showCharts = showCharts; - this.chartDetail = chartDetail; - this.showOverallLeaderboard = showOverallLeaderboard; - this.showSeriesLeaderboards = showSeriesLeaderboards; - } - - public LeaderboardSettings getLeaderboardSettings() { - return leaderboardSettings; - } - - public boolean isEmbedded() { - return embedded; - } - - public boolean isShowRaceDetails() { - return showRaceDetails; - } - - public boolean isAutoRefresh() { - return autoRefresh; - } - - public boolean isAutoExpandLastRaceColumn() { - return autoExpandLastRaceColumn; - } - - public boolean isHideToolbar() { - return hideToolbar; - } - - public boolean isShowCharts() { - return showCharts; - } - - public DetailType getChartDetail() { - return chartDetail; - } - - public boolean isShowOverallLeaderboard() { - return showOverallLeaderboard; - } - - public boolean isShowSeriesLeaderboards() { - return showSeriesLeaderboards; - } - } - /** * Assembles a dialog that other parts of the application can use to let the user parameterize a leaderboard and * obtain the according URL for it. This keeps the "secrets" of which URL parameters have which meaning encapsulated * within this class.

* * The implementation by and large uses the {@link LeaderboardSettingsDialogComponent}'s widget and adds to it a checkbox - * for driving the {@link #PARAM_EMBEDDED} field. + * for driving the {@link #LeaderboardUrlSettings.PARAM_EMBEDDED} field. * * @see LeaderboardEntryPoint#getUrl(String, LeaderboardSettings, boolean) */ @@ -362,67 +288,4 @@ public class LeaderboardEntryPoint extends AbstractEntryPoint { return new LeaderboardUrlConfigurationDialog(stringMessages, leaderboard); } - /** - * Assembles a URL for a leaderboard that displays with the settings and embedded mode - * as specified by the parameters. - */ - public static String getUrl(String leaderboardName, String leaderboardDisplayName, LeaderboardUrlSettings settings) { - StringBuilder overallDetails = new StringBuilder(); - for (DetailType overallDetail : settings.getLeaderboardSettings().getOverallDetailsToShow()) { - overallDetails.append('&'); - overallDetails.append(PARAM_OVERALL_DETAIL); - overallDetails.append('='); - overallDetails.append(overallDetail.name()); - } - StringBuilder legDetails = new StringBuilder(); - for (DetailType legDetail : settings.getLeaderboardSettings().getLegDetailsToShow()) { - legDetails.append('&'); - legDetails.append(PARAM_LEG_DETAIL); - legDetails.append('='); - legDetails.append(legDetail.name()); - } - StringBuilder raceDetails = new StringBuilder(); - for (DetailType raceDetail : settings.getLeaderboardSettings().getRaceDetailsToShow()) { - raceDetails.append('&'); - raceDetails.append(PARAM_RACE_DETAIL); - raceDetails.append('='); - raceDetails.append(raceDetail.name()); - } - StringBuilder maneuverDetails = new StringBuilder(); - for (DetailType maneuverDetail : settings.getLeaderboardSettings().getManeuverDetailsToShow()) { - maneuverDetails.append('&'); - maneuverDetails.append(PARAM_MANEUVER_DETAIL); - maneuverDetails.append('='); - maneuverDetails.append(maneuverDetail.name()); - } - StringBuilder showAddedScores = new StringBuilder(); - showAddedScores.append('&'); - showAddedScores.append(PARAM_SHOW_ADDED_SCORES); - showAddedScores.append('='); - showAddedScores.append(settings.getLeaderboardSettings().isShowAddedScores()); - - String debugParam = Window.Location.getParameter("gwt.codesvr"); - String link = URLEncoder.encode("/gwt/Leaderboard.html?name=" + leaderboardName - + (settings.isShowRaceDetails() ? "&"+PARAM_SHOW_RACE_DETAILS+"=true" : "") - + (leaderboardDisplayName != null ? "&displayName="+leaderboardDisplayName : "") - + (settings.isEmbedded() ? "&"+PARAM_EMBEDDED+"=true" : "") - + (settings.isHideToolbar() ? "&"+PARAM_HIDE_TOOLBAR+"=true" : "") - + (settings.isShowCharts() ? "&"+PARAM_SHOW_CHARTS+"=true" : "") - + (settings.isShowCharts() ? "&"+PARAM_CHART_DETAIL+"="+settings.getChartDetail().name() : "") - + (settings.isShowOverallLeaderboard() ? "&"+PARAM_SHOW_OVERALL_LEADERBOARD+"=true" : "") - + (settings.isShowSeriesLeaderboards() ? "&"+PARAM_SHOW_SERIES_LEADERBOARDS+"=true" : "") - + (!settings.isAutoRefresh() || (settings.getLeaderboardSettings().getDelayBetweenAutoAdvancesInMilliseconds() == null && - settings.getLeaderboardSettings().getDelayBetweenAutoAdvancesInMilliseconds() != 0) ? "" : - "&"+PARAM_REFRESH_INTERVAL_MILLIS+"="+settings.getLeaderboardSettings().getDelayBetweenAutoAdvancesInMilliseconds()) - + legDetails.toString() - + raceDetails.toString() - + overallDetails.toString() - + maneuverDetails.toString() - + (settings.isAutoExpandLastRaceColumn() ? "&"+PARAM_AUTO_EXPAND_LAST_RACE_COLUMN+"=true" : "") - + (settings.getLeaderboardSettings().getNumberOfLastRacesToShow() == null ? "" : - "&"+PARAM_NAME_LAST_N+"="+settings.getLeaderboardSettings().getNumberOfLastRacesToShow()) - + showAddedScores.toString() - + (debugParam != null && !debugParam.isEmpty() ? "&gwt.codesvr=" + debugParam : "")); - return link; - } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java index e935b6e752b..7fa3ffd15bb 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java @@ -7,8 +7,10 @@ import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import com.google.gwt.cell.client.Cell; @@ -19,9 +21,13 @@ import com.google.gwt.cell.client.TextCell; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.BrowserEvents; +import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Style.FontWeight; +import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.i18n.client.DateTimeFormat; import com.google.gwt.i18n.client.NumberFormat; import com.google.gwt.resources.client.ImageResource; @@ -35,6 +41,7 @@ import com.google.gwt.user.cellview.client.SafeHtmlHeader; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.AbstractImagePrototype; import com.google.gwt.user.client.ui.Anchor; +import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.DockPanel; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HasHorizontalAlignment; @@ -46,12 +53,10 @@ import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; import com.google.gwt.view.client.CellPreviewEvent; -import com.google.gwt.view.client.DefaultSelectionEventManager; import com.google.gwt.view.client.ListDataProvider; import com.google.gwt.view.client.MultiSelectionModel; import com.google.gwt.view.client.SelectionChangeEvent; import com.google.gwt.view.client.SelectionChangeEvent.Handler; -import com.google.gwt.view.client.SelectionModel; import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.InvertibleComparator; import com.sap.sailing.domain.common.MaxPointsReason; @@ -76,18 +81,27 @@ import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener; import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider; import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.FlagImageResolver; +import com.sap.sailing.gwt.ui.client.LeaderboardUpdateListener; import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider; import com.sap.sailing.gwt.ui.client.RaceTimesInfoProviderListener; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.Component; import com.sap.sailing.gwt.ui.client.shared.components.IsEmbeddableComponent; +import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialog; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent; +import com.sap.sailing.gwt.ui.client.shared.controls.AbstractSortableColumnWithMinMax; +import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn; +import com.sap.sailing.gwt.ui.client.shared.filter.CompetitorRaceRankFilter; +import com.sap.sailing.gwt.ui.client.shared.filter.FilterWithUI; import com.sap.sailing.gwt.ui.client.shared.filter.LeaderboardFetcher; import com.sap.sailing.gwt.ui.leaderboard.DetailTypeColumn.LegDetailField; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings.RaceColumnSelectionStrategies; import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO; import com.sap.sse.common.Util; +import com.sap.sse.common.filter.BinaryOperator; +import com.sap.sse.common.filter.Filter; +import com.sap.sse.common.filter.FilterSet; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.gwt.client.controls.busyindicator.BusyIndicator; @@ -108,11 +122,6 @@ import com.sap.sse.gwt.client.useragent.UserAgentDetails; public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayStateListener, DisplayedLeaderboardRowsProvider, Component, IsEmbeddableComponent, CompetitorSelectionChangeListener, LeaderboardFetcher { public static final String LOAD_LEADERBOARD_DATA_CATEGORY = "loadLeaderboardData"; - private static final int RANK_COLUMN_INDEX = 0; - - private static final int SAIL_ID_COLUMN_INDEX = 1; - - private static final int CARRY_COLUMN_INDEX = 3; protected static final NumberFormat scoreFormat = NumberFormat.getFormat("0.##"); @@ -149,11 +158,13 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS private final SortedCellTable leaderboardTable; - private final SelectionModel leaderboardSelectionModel; + private final MultiSelectionModel leaderboardSelectionModel; private LeaderboardDTO leaderboard; private final TotalRankColumn totalRankColumn; + + private final SelectionCheckboxColumn selectionCheckboxColumn; /** * Passed to the {@link ManeuverCountRaceColumn}. Modifications to this list will modify the column's children list @@ -175,7 +186,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS private final List selectedOverallDetailColumns; - private final Map> overallDetailColumnMap; + private final Map> overallDetailColumnMap; private RaceColumnSelection raceColumnSelection; @@ -233,6 +244,24 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS private final Anchor playPause; private final CompetitorSelectionProvider competitorSelectionProvider; + private final HorizontalPanel filterControlPanel; + private Label filterStatusLabel; + private Button filterClearButton; + + /** + * The handler for changes in the leaderboard table's selection; its registration is kept in + * {@link #leaderboardAsTableSelectionModelRegistration} while it is registered as a selection handler + * on the {@link #leaderboardTable}. + */ + private final Handler selectionChangeHandler; + + /** + * While the {@link #selectionChangeHandler} is registered as a selection change handler on the + * {@link #leaderboardTable}'s selection model, this field holds the registration which can be used to + * remove the registration again. We'll use this to temporarily suspend selection events when actively + * modifying / adjusting the table selection to match the {@link #competitorSelectionProvider}. + */ + private HandlerRegistration leaderboardAsTableSelectionModelRegistration; /** * If this is null, all leaderboard columns added by updating the leaderboard from the server are @@ -302,11 +331,21 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS private RaceTimesInfoProvider raceTimesInfoProvider; private RaceTimesInfoProviderListener raceTimesInfoProviderListener; - /** - * Used to set the focus to avoid undesirable table scolling - */ - private final Anchor dummyFocusElement; private int blurInOnSelectionChanged; + + /** + * When an element in the leaderboard receives focus, it needs to be blurred again to keep the surrounding scroll panel + * from scrolling anything into view + */ + private Element elementToBlur; + private boolean showSelectionCheckbox; + + private List leaderboardUpdateListener; + + private boolean initialCompetitorFilterHasBeenApplied = false; + private final boolean showCompetitorFilterStatus; + + private CompetitorFilterPanel competitorFilterPanel; protected StringMessages getStringMessages() { return stringMessages; @@ -342,7 +381,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS return isEmbedded; } - protected VerticalPanel getContentPanel() { + public VerticalPanel getContentPanel() { return contentPanel; } @@ -534,7 +573,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS return raceTimesInfoProvider; } - protected class CompetitorColumn extends SortableColumn { + protected class CompetitorColumn extends LeaderboardSortableColumnWithMinMax { private final CompetitorColumnBase base; protected CompetitorColumn(CompetitorColumnBase base) { @@ -591,7 +630,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS * @author Axel Uhl (d043530) * */ - private class SailIDColumn extends SortableColumn { + private class SailIDColumn extends LeaderboardSortableColumnWithMinMax { private final CompetitorFetcher competitorFetcher; protected SailIDColumn(CompetitorFetcher competitorFetcher) { @@ -815,7 +854,8 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS } public static DetailType[] getAvailableOverallDetailColumnTypes() { - return new DetailType[] { DetailType.TOTAL_DISTANCE_TRAVELED, + return new DetailType[] { DetailType.REGATTA_RANK, + DetailType.TOTAL_DISTANCE_TRAVELED, DetailType.TOTAL_AVERAGE_SPEED_OVER_GROUND, DetailType.TOTAL_TIME_SAILED_IN_SECONDS, DetailType.MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS }; @@ -851,10 +891,10 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS } @Override - protected Map> getDetailColumnMap( + protected Map> getDetailColumnMap( LeaderboardPanel leaderboardPanel, StringMessages stringMessages, String detailHeaderStyle, String detailColumnStyle) { - Map> result = new HashMap>(); + Map> result = new HashMap<>(); result.put(DetailType.RACE_RATIO_BETWEEN_TIME_SINCE_LAST_POSITION_FIX_AND_AVERAGE_SAMPLING_INTERVAL, new TimeSinceLastGpsFixColumn(DetailType.RACE_RATIO_BETWEEN_TIME_SINCE_LAST_POSITION_FIX_AND_AVERAGE_SAMPLING_INTERVAL, new RaceRatioBetweenTimeSinceLastPositionFixAndAverageSamplingInterval(), LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, LeaderboardPanel.this)); @@ -918,9 +958,9 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS } @Override - protected Iterable> getDirectChildren() { - List> result = new ArrayList>(); - for (SortableColumn column : super.getDirectChildren()) { + protected Iterable> getDirectChildren() { + List> result = new ArrayList<>(); + for (AbstractSortableColumnWithMinMax column : super.getDirectChildren()) { result.add(column); } if (isExpanded() && selectedRaceDetails.contains(DetailType.DISPLAY_LEGS)) { @@ -1287,7 +1327,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS * * @author Simon Marcel Pamies */ - private class TotalRacesCompletedColumn extends SortableColumn { + private class TotalRacesCompletedColumn extends LeaderboardSortableColumnWithMinMax { private final String columnStyle; private final MaxPointsReason[] MAX_POINTS_REASONS_THAT_IDENTIFY_NON_FINISHED_RACES = new MaxPointsReason[] {MaxPointsReason.DNS, MaxPointsReason.DNF, MaxPointsReason.DNC}; @@ -1356,7 +1396,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS * @author Axel Uhl (D043530) * */ - private class TotalsColumn extends SortableColumn { + private class TotalsColumn extends LeaderboardSortableColumnWithMinMax { private final String columnStyle; protected TotalsColumn(String columnStyle) { @@ -1402,7 +1442,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS } } - protected class CarryColumn extends SortableColumn { + protected class CarryColumn extends LeaderboardSortableColumnWithMinMax { public CarryColumn() { super(new TextCell(), SortingOrder.ASCENDING, LeaderboardPanel.this); setSortable(true); @@ -1442,7 +1482,61 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS } } - private class TotalRankColumn extends SortableColumn { + private class LeaderboardSelectionCheckboxColumn extends com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn + implements CompetitorSelectionChangeListener { + protected LeaderboardSelectionCheckboxColumn(final CompetitorSelectionProvider competitorSelectionProvider) { + super(tableResources.cellTableStyle().cellTableCheckboxSelected(), tableResources.cellTableStyle().cellTableCheckboxDeselected(), tableResources.cellTableStyle().cellTableCheckboxColumnCell()); + competitorSelectionProvider.addCompetitorSelectionChangeListener(this); + } + + @Override + public Boolean getValue(LeaderboardRowDTO row) { + return competitorSelectionProvider.isSelected(row.competitor); + } + + @Override public void competitorsListChanged(Iterable competitors) {} + @Override public void filterChanged(FilterSet> oldFilterSet, FilterSet> newFilterSet) {} + @Override public void filteredCompetitorsListChanged(Iterable filteredCompetitors) {} + + /** + * Ensure that the checkbox is redrawn when the competitor selection changes + */ + @Override + public void addedToSelection(CompetitorDTO competitor) { + final LeaderboardRowDTO row = getRow(competitor); + if (row != null) { + redrawRow(row); + } + } + + private void redrawRow(final LeaderboardRowDTO row) { + final List leaderboardDataList = getData().getList(); + synchronized (leaderboardDataList) { + redrawRow(row, leaderboardDataList); + } + } + + /** + * Ensure that the checkbox is redrawn when the competitor selection changes + */ + @Override + public void removedFromSelection(CompetitorDTO competitor) { + final LeaderboardRowDTO row = getRow(competitor); + if (row != null) { + redrawRow(row); + } + } + + @Override + public void updateMinMax() {} + + @Override + protected ListDataProvider getListDataProvider() { + return getData(); + } + } + + private class TotalRankColumn extends LeaderboardSortableColumnWithMinMax { public TotalRankColumn() { super(new TextCell(), SortingOrder.ASCENDING, LeaderboardPanel.this); setHorizontalAlignment(ALIGN_CENTER); @@ -1501,16 +1595,19 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS // perform the first request as "live" but don't by default auto-play PlayModes.Live, PlayStates.Paused, /* delayBetweenAutoAdvancesInMilliseconds */3000l), leaderboardGroupName, leaderboardName, errorReporter, stringMessages, userAgent, showRaceDetails, - /* optionalRaceTimesInfoProvider */ null, /* autoExpandLastRaceColumn */ false, /* adjustTimerDelay */ true); + /* competitorSearchTextBox */ null, /* showSelectionCheckbox */ true, /* optionalRaceTimesInfoProvider */ null, + /* autoExpandLastRaceColumn */ false, /* adjustTimerDelay */ true, /*autoApplyTop30Filter*/ false, false); } public LeaderboardPanel(SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, LeaderboardSettings settings, RaceIdentifier preSelectedRace, CompetitorSelectionProvider competitorSelectionProvider, Timer timer, String leaderboardGroupName, String leaderboardName, ErrorReporter errorReporter, final StringMessages stringMessages, - final UserAgentDetails userAgent, boolean showRaceDetails, - RaceTimesInfoProvider optionalRaceTimesInfoProvider, boolean autoExpandLastRaceColumn, boolean adjustTimerDelay) { - this.dummyFocusElement = new Anchor(""); + final UserAgentDetails userAgent, boolean showRaceDetails, CompetitorFilterPanel competitorSearchTextBox, + boolean showSelectionCheckbox, RaceTimesInfoProvider optionalRaceTimesInfoProvider, + boolean autoExpandLastRaceColumn, boolean adjustTimerDelay, boolean autoApplyTopNFilter, boolean showCompetitorFilterStatus) { + this.setTitle(stringMessages.leaderboard()); + this.showSelectionCheckbox = showSelectionCheckbox; this.showRaceDetails = showRaceDetails; this.sailingService = sailingService; this.asyncActionsExecutor = asyncActionsExecutor; @@ -1526,8 +1623,11 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS this.raceTimesInfoProvider = optionalRaceTimesInfoProvider; this.selectedManeuverDetails = new ArrayList(); this.adjustTimerDelay = adjustTimerDelay; + this.initialCompetitorFilterHasBeenApplied = !autoApplyTopNFilter; + this.showCompetitorFilterStatus = showCompetitorFilterStatus; overallDetailColumnMap = createOverallDetailColumnMap(); settingsUpdatedExplicitly = !settings.isUpdateUponPlayStateChange(); + this.leaderboardUpdateListener = new ArrayList(); if (settings.getLegDetailsToShow() != null) { selectedLegDetails.addAll(settings.getLegDetailsToShow()); } @@ -1561,6 +1661,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS break; } totalRankColumn = new TotalRankColumn(); + selectionCheckboxColumn = new LeaderboardSelectionCheckboxColumn(competitorSelectionProvider); RACE_COLUMN_HEADER_STYLE = tableResources.cellTableStyle().cellTableRaceColumnHeader(); LEG_COLUMN_HEADER_STYLE = tableResources.cellTableStyle().cellTableLegColumnHeader(); LEG_DETAIL_COLUMN_HEADER_STYLE = tableResources.cellTableStyle().cellTableLegDetailColumnHeader(); @@ -1573,8 +1674,9 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS leaderboardTable.addCellPreviewHandler(new CellPreviewEvent.Handler() { @Override public void onCellPreview(CellPreviewEvent event) { - boolean isClick = "click".equals(event.getNativeEvent().getType()); - if (isClick) { + if (BrowserEvents.FOCUS.equals(event.getNativeEvent().getType())) { + elementToBlur = event.getNativeEvent().getEventTarget().cast(); + elementToBlur.blur(); blurInOnSelectionChanged = 2; // blur a couple of times; doing it one time only doesn't seem to work reliably blurFocusedElementAfterSelectionChange(); } @@ -1583,7 +1685,8 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS leaderboardTable.ensureDebugId("LeaderboardCellTable"); getLeaderboardTable().setWidth("100%"); leaderboardSelectionModel = new MultiSelectionModel(); - leaderboardSelectionModel.addSelectionChangeHandler(new Handler() { + // remember handler registration so we can temporarily remove it and re-add it to suspend selection events while we're actively changing it + selectionChangeHandler = new Handler() { @Override public void onSelectionChange(SelectionChangeEvent event) { List selection = new ArrayList(); @@ -1591,21 +1694,14 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS selection.add(row.competitor); } LeaderboardPanel.this.competitorSelectionProvider.setSelection(selection, /* listenersNotToNotify */LeaderboardPanel.this); - updateLeaderboard(getLeaderboard()); if (blurInOnSelectionChanged > 0) { blurInOnSelectionChanged--; blurFocusedElementAfterSelectionChange(); } } - }); - if (userAgent.isMobile() == UserAgentDetails.PlatformTypes.MOBILE) { - //Setting up the toggle selection - leaderboardTable.addCellPreviewHandler(new ToggleSelectionCellPreviewHandler()); - DefaultSelectionEventManager selectionEventManager = DefaultSelectionEventManager.createCheckboxManager(); - leaderboardTable.setSelectionModel(leaderboardSelectionModel, selectionEventManager); - } else { - leaderboardTable.setSelectionModel(leaderboardSelectionModel); - } + }; + leaderboardAsTableSelectionModelRegistration = leaderboardSelectionModel.addSelectionChangeHandler(selectionChangeHandler); + leaderboardTable.setSelectionModel(leaderboardSelectionModel, selectionCheckboxColumn.getSelectionManager()); setShowAddedScores(settings.isShowAddedScores()); setShowOverallColumnWithNumberOfRacesCompletedPerCompetitor(settings.isShowOverallColumnWithNumberOfRacesCompletedPerCompetitor()); if (timer.isInitialized()) { @@ -1678,12 +1774,76 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS if (!isEmbedded) { contentPanel.add(toolbarPanel); } - contentPanel.add(getLeaderboardTable()); - contentPanel.add(dummyFocusElement); // ensure dummy element is part of the DOM + if (competitorSearchTextBox != null) { + contentPanel.add(competitorSearchTextBox); + contentPanel.add(busyIndicator); + competitorSearchTextBox.getSettingsButton().addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + new SettingsDialog(LeaderboardPanel.this, stringMessages).show(); + } + }); + this.competitorFilterPanel = competitorSearchTextBox; + } + SortedCellTable leaderboardTable = getLeaderboardTable(); + leaderboardTable.getElement().getStyle().setMarginTop(5, Unit.PX); + filterControlPanel = new HorizontalPanel(); + filterControlPanel.setStyleName("LeaderboardPanel-FilterControl-Panel"); + contentPanel.add(leaderboardTable); + if (showCompetitorFilterStatus) { + contentPanel.add(createFilterDeselectionControl()); + } setWidget(contentPanel); raceNameForDefaultSorting = settings.getNameOfRaceToSort(); } + private Widget createFilterDeselectionControl() { + filterStatusLabel = new Label(); + filterStatusLabel.setStyleName("LeaderboardPanel-FilterControl-StatusLabel"); + filterStatusLabel.setText(""); + filterControlPanel.add(filterStatusLabel); + filterClearButton = new Button(stringMessages.showAll()); + filterClearButton.setStyleName("LeaderboardPanel-FilterClear-Button"); + filterClearButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + getBusyIndicator().setBusy(true); + competitorFilterPanel.clearAllActiveFilters(); + timeChanged(new Date(), null); + } + }); + filterControlPanel.add(filterClearButton); + filterControlPanel.setCellHorizontalAlignment(filterClearButton, HasHorizontalAlignment.ALIGN_RIGHT); + setFilterControlStatus(); + return filterControlPanel; + } + + private void setFilterControlStatus() { + if (showCompetitorFilterStatus) { + boolean filtersActive = competitorSelectionProvider.hasActiveFilters(); + if (filtersActive) { + String labelText = ""; + for (Filter filter : competitorSelectionProvider.getCompetitorsFilterSet().getFilters()) { + if (filter instanceof FilterWithUI) { + labelText += ((FilterWithUI)filter).getLocalizedDescription(stringMessages) + ", "; + } else { + labelText += filter.getName() + ", "; + } + } + filterStatusLabel.setText("Active Filter(s): " + labelText.substring(0, labelText.length()-2)); + filterClearButton.setVisible(true); + filterControlPanel.setVisible(true); + } else { + filterStatusLabel.setText(""); + filterClearButton.setVisible(false); + filterControlPanel.setVisible(false); + if (competitorFilterPanel != null) { + competitorFilterPanel.clearSelection(); + } + } + } + } + private static class TotalDistanceTraveledInMetersField implements LegDetailField { @Override public Double get(LeaderboardRowDTO row) { @@ -1704,8 +1864,8 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS } } - private Map> createOverallDetailColumnMap() { - Map> result = new HashMap>(); + private Map> createOverallDetailColumnMap() { + Map> result = new HashMap<>(); result.put(DetailType.TOTAL_DISTANCE_TRAVELED, new FormattedDoubleDetailTypeColumn(DetailType.TOTAL_DISTANCE_TRAVELED, new TotalDistanceTraveledInMetersField(), RACE_COLUMN_HEADER_STYLE, RACE_COLUMN_STYLE, this)); @@ -1849,9 +2009,9 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS /** * adds the column to the right end of the {@link #getLeaderboardTable() leaderboard table} and sets - * the column style according to the {@link SortableColumn#getColumnStyle() column's style definition}. + * the column style according to the {@link LeaderboardSortableColumnWithMinMax#getColumnStyle() column's style definition}. */ - protected void addColumn(SortableColumn column) { + protected void addColumn(LeaderboardSortableColumnWithMinMax column) { leaderboardTable.addColumn(column, column.getHeader(), column.getComparator(), column .getPreferredSortingOrder().isAscending()); String columnStyle = column.getColumnStyle(); @@ -1860,7 +2020,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS } } - protected void insertColumn(int beforeIndex, SortableColumn column) { + protected void insertColumn(int beforeIndex, AbstractSortableColumnWithMinMax column) { // remove column styles of those columns whose index will shift right by // one: removeColumnStyles(beforeIndex); @@ -1871,7 +2031,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS private void addColumnStyles(int startColumn) { for (int i = startColumn; i < getLeaderboardTable().getColumnCount(); i++) { - SortableColumn columnToRemoveStyleFor = (SortableColumn) getLeaderboardTable() + AbstractSortableColumnWithMinMax columnToRemoveStyleFor = (AbstractSortableColumnWithMinMax) getLeaderboardTable() .getColumn(i); String columnStyle = columnToRemoveStyleFor.getColumnStyle(); if (columnStyle != null) { @@ -1882,7 +2042,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS private void removeColumnStyles(int startColumn) { for (int i = startColumn; i < getLeaderboardTable().getColumnCount(); i++) { - SortableColumn columnToRemoveStyleFor = (SortableColumn) getLeaderboardTable() + AbstractSortableColumnWithMinMax columnToRemoveStyleFor = (AbstractSortableColumnWithMinMax) getLeaderboardTable() .getColumn(i); String columnStyle = columnToRemoveStyleFor.getColumnStyle(); if (columnStyle != null) { @@ -1983,6 +2143,22 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS private boolean shallAddOverallDetails() { return !selectedOverallDetailColumns.isEmpty(); } + + private void applyTop30FilterIfCompetitorSizeGreaterEqual40(LeaderboardDTO leaderboard) { + int maxRaceRank = 30; + if (leaderboard.competitors.size() >= 40) { + CompetitorRaceRankFilter raceRankFilter = new CompetitorRaceRankFilter(); + raceRankFilter.setLeaderboardFetcher(this); + raceRankFilter.setSelectedRace(preSelectedRace); + raceRankFilter.setQuickRankProvider(this.competitorFilterPanel.getQuickRankProvider()); + raceRankFilter.setOperator(new BinaryOperator(BinaryOperator.Operators.LessThanEquals)); + raceRankFilter.setValue(maxRaceRank); + FilterSet> activeFilterSet = + competitorSelectionProvider.getOrCreateCompetitorsFilterSet(stringMessages.topNCompetitorsByRaceRank(maxRaceRank)); + activeFilterSet.addFilter(raceRankFilter); + competitorSelectionProvider.setCompetitorsFilterSet(activeFilterSet); + } + } /** * Assigns leaderboard to {@link #leaderboard} and updates the UI accordingly. Also updates the min/max @@ -1995,6 +2171,10 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS columnToCollapseAndExpandAgain.toggleExpansion(); } competitorSelectionProvider.setCompetitors(leaderboard.competitors, /* listenersNotToNotify */this); + if (!initialCompetitorFilterHasBeenApplied) { + applyTop30FilterIfCompetitorSizeGreaterEqual40(leaderboard); + initialCompetitorFilterHasBeenApplied = true; + } raceColumnSelection.autoUpdateRaceColumnSelectionForUpdatedLeaderboard(getLeaderboard(), leaderboard); setLeaderboard(leaderboard); adjustColumnLayout(leaderboard); @@ -2003,8 +2183,31 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS columnToCollapseAndExpandAgain.toggleExpansion(); } adjustDelayToLive(); - getData().getList().clear(); - getData().getList().addAll(getRowsToDisplay()); + final Map rowsToDisplay = getRowsToDisplay(); + Set rowsToAdd = new HashSet<>(rowsToDisplay.values()); + Map rowsToUpdate = new HashMap<>(); + synchronized (getData().getList()) { + int index = 0; + for (Iterator i = getData().getList().iterator(); i.hasNext(); ) { + LeaderboardRowDTO oldRow = i.next(); + LeaderboardRowDTO newRow = rowsToDisplay.get(oldRow.competitor); + if (newRow != null) { + rowsToUpdate.put(index++, newRow); // update row in place, preserving its selection state + rowsToAdd.remove(newRow); // no need to add this row when it was updated in-place + } else { + i.remove(); // old row's competitor not found in new rows' competitors; remove old row from table + } + } + for (Entry updateEntry : rowsToUpdate.entrySet()) { + LeaderboardRowDTO oldElement = getData().getList().set(updateEntry.getKey(), updateEntry.getValue()); + leaderboardSelectionModel.setSelected(oldElement, false); // make sure the old element is no longer part of the selection + updateSelection(updateEntry.getValue()); + } + for (LeaderboardRowDTO rowToAdd : rowsToAdd) { + getData().getList().add(rowToAdd); + updateSelection(rowToAdd); + } + } RaceColumn lastRaceColumn = null; for (int i=getLeaderboardTable().getColumnCount()-1; i>=0; i--) { if (getLeaderboardTable().getColumn(i) instanceof RaceColumn) { @@ -2013,7 +2216,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS } } for (int i = 0; i < getLeaderboardTable().getColumnCount(); i++) { - SortableColumn c = (SortableColumn) getLeaderboardTable().getColumn(i); + AbstractSortableColumnWithMinMax c = (AbstractSortableColumnWithMinMax) getLeaderboardTable().getColumn(i); c.updateMinMax(); // Toggle pre-selected race, if the setting is set and it isn't open yet, or the last race column if that was requested if ((!autoExpandPerformedOnce && isAutoExpandPreSelectedRace() && c instanceof RaceColumn @@ -2025,20 +2228,17 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS autoExpandPerformedOnce = true; } } + if (c instanceof RaceColumn && ((RaceColumn) c).getRace().hasTrackedRace(preSelectedRace)) { + RaceColumnDTO raceColumn = ((RaceColumn) c).getRace(); + informLeaderboardUpdateListenersAboutRaceSelected(preSelectedRace, raceColumn); + } } if (leaderboardTable.getCurrentlySortedColumn() != null) { leaderboardTable.sort(); } else { - SortableColumn columnToSortFor = getDefaultSortColumn(); + AbstractSortableColumnWithMinMax columnToSortFor = getDefaultSortColumn(); leaderboardTable.sortColumn(columnToSortFor, columnToSortFor.getPreferredSortingOrder().isAscending()); } - // Reselect the selected rows - clearSelection(); - for (LeaderboardRowDTO row : getLeaderboardTable().getDataProvider().getList()) { - if (competitorSelectionProvider.isSelected(row.competitor)) { - leaderboardSelectionModel.setSelected(row, true); - } - } scoreCorrectionCommentLabel.setText(leaderboard.getComment() != null ? leaderboard.getComment() : ""); if (leaderboard.getTimePointOfLastCorrectionsValidity() != null) { @@ -2072,9 +2272,29 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS } scoreCorrectionLastUpdateTimeLabel.setVisible(!hasLiveRace); liveRaceLabel.setVisible(hasLiveRace); + informLeaderboardUpdateListenersAboutLeaderboardUpdated(leaderboard); + getBusyIndicator().setBusy(false); } } + /** + * Adjusts the row's selection in the {@link #leaderboardSelectionModel} so it matches its selection state in + * the {@link #competitorSelectionProvider}. + */ + private void updateSelection(LeaderboardRowDTO row) { + final boolean shallBeSelected = competitorSelectionProvider.isSelected(row.competitor); + if (leaderboardAsTableSelectionModelRegistration != null) { + // suspend selection events while actively adjusting the leaderboardSelectionModel to match the competitorSelectionProvider + leaderboardAsTableSelectionModelRegistration.removeHandler(); + leaderboardAsTableSelectionModelRegistration = null; + } + if (leaderboardSelectionModel.isSelected(row) != shallBeSelected) { + leaderboardSelectionModel.setSelected(row, shallBeSelected); + } + // register the selection change handler again + leaderboardAsTableSelectionModelRegistration = leaderboardTable.getSelectionModel().addSelectionChangeHandler(selectionChangeHandler); + } + /** * The race columns hold a now outdated copy of a {@link RaceColumnDTO} which needs to be updated from the {@link LeaderboardDTO} just received */ @@ -2085,7 +2305,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS raceColumn.setRace(newRace); } } - } + } /** * Due to a course change, a race may change its number of legs. All expanded race columns that show leg columns and @@ -2142,20 +2362,20 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS * the race identified by {@link #preSelectedRace} otherwise. */ @Override - public Collection getRowsToDisplay() { - Collection result; + public Map getRowsToDisplay() { + Map result; Iterable allFilteredCompetitors = competitorSelectionProvider.getFilteredCompetitors(); - result = new ArrayList(); + result = new HashMap(); if (preSelectedRace == null) { for (CompetitorDTO competitor : leaderboard.rows.keySet()) { if (Util.contains(allFilteredCompetitors, competitor)) { - result.add(leaderboard.rows.get(competitor)); + result.put(competitor, leaderboard.rows.get(competitor)); } } } else { for (CompetitorDTO competitorInPreSelectedRace : getCompetitors(preSelectedRace)) { if (Util.contains(allFilteredCompetitors, competitorInPreSelectedRace)) { - result.add(leaderboard.rows.get(competitorInPreSelectedRace)); + result.put(competitorInPreSelectedRace, leaderboard.rows.get(competitorInPreSelectedRace)); } } } @@ -2194,8 +2414,8 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS return result; } - private SortableColumn getDefaultSortColumn() { - SortableColumn defaultSortColumn = null; + private AbstractSortableColumnWithMinMax getDefaultSortColumn() { + AbstractSortableColumnWithMinMax defaultSortColumn = null; if (raceNameForDefaultSorting != null) { defaultSortColumn = getRaceColumnByRaceName(raceNameForDefaultSorting); } @@ -2205,12 +2425,6 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS return defaultSortColumn; } - private void clearSelection() { - for (LeaderboardRowDTO row : getData().getList()) { - leaderboardSelectionModel.setSelected(row, false); - } - } - private TotalRankColumn getRankColumn() { return totalRankColumn; } @@ -2225,10 +2439,12 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS } private void adjustColumnLayout(LeaderboardDTO leaderboard) { - ensureRankColumn(); - ensureSailIDAndCompetitorColumn(); - boolean hasCarryColumn = updateCarryColumn(leaderboard); - adjustOverallDetailColumns(leaderboard, hasCarryColumn?CARRY_COLUMN_INDEX+1:CARRY_COLUMN_INDEX); + int columnIndex = 0; + columnIndex = ensureSelectionCheckboxColumn(columnIndex); + columnIndex = ensureRankColumn(columnIndex); + columnIndex = ensureSailIDAndCompetitorColumn(columnIndex); + columnIndex = updateCarryColumn(leaderboard, columnIndex); + adjustOverallDetailColumns(leaderboard, columnIndex); // first remove race columns no longer needed: removeUnusedRaceColumns(leaderboard); if (leaderboard != null) { @@ -2247,10 +2463,10 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS * tells the column index for the first overall details column */ private void adjustOverallDetailColumns(LeaderboardDTO leaderboard, int indexOfFirstOverallDetailsColumn) { - List> overallDetailColumnsToShow = new ArrayList>(); + List> overallDetailColumnsToShow = new ArrayList>(); // ensure the ordering in overallDetailColumnsToShow conforms to the ordering of getAvailableOverallDetailColumnTypes() for (DetailType overallDetailType : getAvailableOverallDetailColumnTypes()) { - if (selectedOverallDetailColumns.contains(overallDetailType)) { + if (selectedOverallDetailColumns.contains(overallDetailType) && overallDetailColumnMap.containsKey(overallDetailType)) { overallDetailColumnsToShow.add(overallDetailColumnMap.get(overallDetailType)); } } @@ -2469,19 +2685,83 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS addColumn(raceColumn); } - private void ensureRankColumn() { - if (getLeaderboardTable().getColumnCount() == RANK_COLUMN_INDEX) { - addColumn(getDefaultSortColumn()); + /** + * The regatta rank column shall be displayed if an only if the {@link DetailType#REGATTA_RANK} detail is selected in the + * {@link #selectedOverallDetailColumns}. It will then be displayed after the selection checkbox column (if any) and + * before the sail number / competitor name columns. + */ + private boolean isShowRegattaRankColumn() { + return selectedOverallDetailColumns.contains(DetailType.REGATTA_RANK); + } + + /** + * @param rankColumnIndex + * the column index (0-based) where to put the rank column, if needed + * @return the column index (0-based) for the next column; will equal rankColumnIndex if the rank + * column is not {@link #isShowRegattaRankColumn() supposed to be shown}, or one greater otherwise. + */ + private int ensureRankColumn(int rankColumnIndex) { + final int indexOfNextColumn = rankColumnIndex + (isShowRegattaRankColumn() ? 1 : 0); + if (getLeaderboardTable().getColumnCount() > rankColumnIndex) { + if (isShowRegattaRankColumn()) { + if (getLeaderboardTable().getColumn(rankColumnIndex) != getRankColumn()) { + insertColumn(rankColumnIndex, getRankColumn()); + } // else, the column is needed and is already in place + } else { + if (getLeaderboardTable().getColumn(rankColumnIndex) == getRankColumn()) { + removeColumn(rankColumnIndex); + } + } } else { - if (!(getLeaderboardTable().getColumn(RANK_COLUMN_INDEX) instanceof TotalRankColumn)) { - throw new RuntimeException("The first column must always be the rank column but it was of type " - + getLeaderboardTable().getColumn(RANK_COLUMN_INDEX).getClass().getName()); + if (isShowRegattaRankColumn()) { + insertColumn(rankColumnIndex, getRankColumn()); } } + return indexOfNextColumn; } - private void ensureSailIDAndCompetitorColumn() { - if (getLeaderboardTable().getColumnCount() <= SAIL_ID_COLUMN_INDEX) { + /** + * @param selectionCheckboxColumnIndex + * the column index (0-based) where to put the selection checkbox column, if needed + * @return the column index (0-based) for the next column; will equal selectionCheckboxColumnIndex if + * the selection checkbox column is not {@link #showSelectionCheckbox supposed to be shown}, or one greater + * otherwise. + */ + private int ensureSelectionCheckboxColumn(int selectionCheckboxColumnIndex) { + final int indexOfNextColumn = selectionCheckboxColumnIndex + (showSelectionCheckbox ? 1 : 0); + if (getLeaderboardTable().getColumnCount() > selectionCheckboxColumnIndex) { + if (showSelectionCheckbox) { + if (getLeaderboardTable().getColumn(selectionCheckboxColumnIndex) != selectionCheckboxColumn) { + insertColumn(selectionCheckboxColumnIndex, selectionCheckboxColumn); + } // else, the column is needed and is already in place + } else { + if (getLeaderboardTable().getColumn(selectionCheckboxColumnIndex) == selectionCheckboxColumn) { + removeColumn(selectionCheckboxColumnIndex); + } + } + } else { + if (showSelectionCheckbox) { + insertColumn(selectionCheckboxColumnIndex, selectionCheckboxColumn); + } + } + return indexOfNextColumn; + } + + /** + * Assumes that the sail ID and competitor column are mandatory elements of a leaderboard; hence, if the leaderboard + * has columns where sail ID and competitor names are expected, it is silently assumed that those are already in the + * correct place because these columns are never optional. + * + * @param sailIdColumnIndex + * the 0-based index where the sail ID column is to be placed; the competitor name column will be at the + * index greater by one + * @return the 0-based index for the next column; two greater than sailIdColumnIndex because this + * method will always make sure that two columns are placed in the layout, one at + * sailIdColumnIndex and one at sailIdColumnIndex+1. + */ + private int ensureSailIDAndCompetitorColumn(int sailIdColumnIndex) { + final int nextColumnIndex; + if (getLeaderboardTable().getColumnCount() <= sailIdColumnIndex) { addColumn(new SailIDColumn(new CompetitorFetcher() { @Override public CompetitorDTO getCompetitor(LeaderboardRowDTO t) { @@ -2490,11 +2770,13 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS })); addColumn(createCompetitorColumn()); } else { - if (!(getLeaderboardTable().getColumn(SAIL_ID_COLUMN_INDEX) instanceof SailIDColumn)) { + if (!(getLeaderboardTable().getColumn(sailIdColumnIndex) instanceof SailIDColumn)) { throw new RuntimeException("The second column must always be the sail ID column but it was of type " - + getLeaderboardTable().getColumn(SAIL_ID_COLUMN_INDEX).getClass().getName()); + + getLeaderboardTable().getColumn(sailIdColumnIndex).getClass().getName()); } } + nextColumnIndex = sailIdColumnIndex + 2; + return nextColumnIndex; } protected CompetitorColumn createCompetitorColumn() { @@ -2544,31 +2826,33 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS * starting from #1 will be removed and a {@link CarryColumn} will be added. If the leaderboard has no carried * points but the display still shows a carry column, the column is removed. * - * @return true if a carry column is now part of the table (regardless of whether it existed before), - * false otherwise + * @param zeroBasedIndexOfCarryColumn + * the 0-based column index where to put the carry column, if any + * @return the 0-based index of the next column following this column if the carry column is to be shown, or + * zeroBasedIndexOfCarryColumn otherwise */ - protected boolean updateCarryColumn(LeaderboardDTO leaderboard) { + protected int updateCarryColumn(LeaderboardDTO leaderboard, int zeroBasedIndexOfCarryColumn) { final boolean needsCarryColumn = leaderboard != null && leaderboard.hasCarriedPoints; if (needsCarryColumn) { - ensureCarryColumn(); + ensureCarryColumn(zeroBasedIndexOfCarryColumn); } else { - ensureNoCarryColumn(); + ensureNoCarryColumn(zeroBasedIndexOfCarryColumn); } - return needsCarryColumn; + return needsCarryColumn ? zeroBasedIndexOfCarryColumn+1 : zeroBasedIndexOfCarryColumn; } - private void ensureNoCarryColumn() { - if (getLeaderboardTable().getColumnCount() > CARRY_COLUMN_INDEX - && getLeaderboardTable().getColumn(CARRY_COLUMN_INDEX) instanceof CarryColumn) { - removeColumn(CARRY_COLUMN_INDEX); + private void ensureNoCarryColumn(int zeroBasedIndexOfCarryColumn) { + if (getLeaderboardTable().getColumnCount() > zeroBasedIndexOfCarryColumn + && getLeaderboardTable().getColumn(zeroBasedIndexOfCarryColumn) instanceof CarryColumn) { + removeColumn(zeroBasedIndexOfCarryColumn); } } - protected void ensureCarryColumn() { - if (getLeaderboardTable().getColumnCount() <= CARRY_COLUMN_INDEX - || !(getLeaderboardTable().getColumn(CARRY_COLUMN_INDEX) instanceof CarryColumn)) { - while (getLeaderboardTable().getColumnCount() > CARRY_COLUMN_INDEX) { - removeColumn(CARRY_COLUMN_INDEX); + protected void ensureCarryColumn(int zeroBasedIndexOfCarryColumn) { + if (getLeaderboardTable().getColumnCount() <= zeroBasedIndexOfCarryColumn + || !(getLeaderboardTable().getColumn(zeroBasedIndexOfCarryColumn) instanceof CarryColumn)) { + while (getLeaderboardTable().getColumnCount() > zeroBasedIndexOfCarryColumn) { + removeColumn(zeroBasedIndexOfCarryColumn); } addColumn(createCarryColumn()); } @@ -2627,7 +2911,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS : preSelectedRace.getRaceName(), /* don't change nameOfRaceColumnToShow */null, /* set nameOfRaceToShow if race was pre-selected */preSelectedRace == null ? null : preSelectedRace - .getRaceName(), getRaceColumnSelection())); + .getRaceName(), getRaceColumnSelection(), /* leave showRegattaRank and overall details unchanged */ null)); } currentlyHandlingPlayStateChange = false; oldPlayMode = playMode; @@ -2675,9 +2959,11 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS } private LeaderboardRowDTO getRow(CompetitorDTO competitor) { - for (LeaderboardRowDTO row : getData().getList()) { - if (row.competitor.equals(competitor)) { - return row; + synchronized (getData().getList()) { + for (LeaderboardRowDTO row : getData().getList()) { + if (row.competitor.equals(competitor)) { + return row; + } } } return null; @@ -2710,17 +2996,12 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS } private Iterable getSelectedRows() { - ArrayList selectedRows = new ArrayList(); - for (LeaderboardRowDTO row : getData().getList()) { - if (leaderboardSelectionModel.isSelected(row)) { - selectedRows.add(row); - } - } - return selectedRows; + return leaderboardSelectionModel.getSelectedSet(); } @Override public void competitorsListChanged(Iterable competitors) { + setFilterControlStatus(); if (timer.isInitialized()) { timeChanged(timer.getTime(), null); } @@ -2728,9 +3009,17 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS @Override public void filteredCompetitorsListChanged(Iterable filteredCompetitors) { + setFilterControlStatus(); updateLeaderboard(getLeaderboard()); } - + + @Override + public void filterChanged(FilterSet> oldFilterSet, + FilterSet> newFilterSet) { + // nothing to do; if the list of filtered competitors has changed, a separate call to filteredCompetitorsListChanged will occur + setFilterControlStatus(); + } + public RaceColumnSelection getRaceColumnSelection() { return raceColumnSelection; } @@ -2766,7 +3055,37 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS } private void blur() { - dummyFocusElement.setFocus(true); - dummyFocusElement.getElement().focus(); + if (elementToBlur != null) { + elementToBlur.blur(); + } + } + + @Override + public void setVisible(boolean visible) { + super.setVisible(visible); + if (visible) { + timeChanged(getLeaderboardDisplayDate(), null); + } + } + + public void addLeaderboardUpdateListener(LeaderboardUpdateListener listener) { + this.leaderboardUpdateListener.add(listener); + } + + protected void informLeaderboardUpdateListenersAboutLeaderboardUpdated(LeaderboardDTO leaderboard) { + for (LeaderboardUpdateListener listener : this.leaderboardUpdateListener) { + listener.updatedLeaderboard(leaderboard); + } + } + + protected void informLeaderboardUpdateListenersAboutRaceSelected(RaceIdentifier raceIdentifier, RaceColumnDTO raceColumn) { + for (LeaderboardUpdateListener listener : this.leaderboardUpdateListener) { + listener.currentRaceSelected(raceIdentifier, raceColumn); + } + } + + @Override + public String getDependentCssClassName() { + return "leaderboard"; } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettings.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettings.java index 61828fb24c1..d97d4fb8152 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettings.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettings.java @@ -5,6 +5,42 @@ import java.util.List; import com.sap.sailing.domain.common.DetailType; public class LeaderboardSettings { + public static final String PARAM_LEADERBOARD_GROUP_NAME = "leaderboardGroupName"; + public static final String PARAM_EMBEDDED = "embedded"; + public static final String PARAM_HIDE_TOOLBAR = "hideToolbar"; + public static final String PARAM_SHOW_RACE_DETAILS = "showRaceDetails"; + public static final String PARAM_RACE_NAME = "raceName"; + public static final String PARAM_RACE_DETAIL = "raceDetail"; + public static final String PARAM_OVERALL_DETAIL = "overallDetail"; + public static final String PARAM_LEG_DETAIL = "legDetail"; + public static final String PARAM_MANEUVER_DETAIL = "maneuverDetail"; + public static final String PARAM_AUTO_EXPAND_PRESELECTED_RACE = "autoExpandPreselectedRace"; + public static final String PARAM_AUTO_EXPAND_LAST_RACE_COLUMN = "autoExpandLastRaceColumn"; + public static final String PARAM_REGATTA_NAME = "regattaName"; + public static final String PARAM_REFRESH_INTERVAL_MILLIS = "refreshIntervalMillis"; + public static final String PARAM_SHOW_CHARTS = "showCharts"; + public static final String PARAM_CHART_DETAIL = "chartDetail"; + public static final String PARAM_SHOW_OVERALL_LEADERBOARD = "showOverallLeaderboard"; + public static final String PARAM_SHOW_SERIES_LEADERBOARDS = "showSeriesLeaderboards"; + public static final String PARAM_SHOW_ADDED_SCORES = "showAddedScores"; + public static final String PARAM_SHOW_OVERALL_COLUMN_WITH_NUMBER_OF_RACES_COMPLETED = "showNumberOfRacesCompleted"; + + /** + * Parameter to support scaling the complete page by a given factor. This works by either using the + * CSS3 zoom property or by applying scale operation to the body element. This comes in handy + * when having to deal with screens that have high resolutions and that can't be controlled manually. + * It is also a very simple method of adapting the viewport to a tv resolution. This parameter works + * with value from 0.0 to 10.0 where 1.0 denotes the unchanged level (100%). + */ + public static final String PARAM_ZOOM_TO = "zoomTo"; + + /** + * Lets the client choose a different race column selection which displays only up to the last N races with N being the integer + * number specified by the parameter. + */ + public static final String PARAM_NAME_LAST_N = "lastN"; + + /** * Only one of {@link #namesOfRaceColumnsToShow} and {@link #namesOfRacesToShow} must be non-null. * Only valid when the {@link #activeRaceColumnSelectionStrategy} is set to EXPLIZIT diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsDialogComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsDialogComponent.java index 2c1e0f3df8c..6e7a4414627 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsDialogComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsDialogComponent.java @@ -397,6 +397,24 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone @Override public FocusWidget getFocusWidget() { - return refreshIntervalInSecondsBox; + final FocusWidget result; + if (raceDetailCheckboxes.isEmpty()) { + if (legDetailCheckboxes.isEmpty()) { + if (maneuverDetailCheckboxes.isEmpty()) { + if (overallDetailCheckboxes.isEmpty()) { + result = refreshIntervalInSecondsBox; + } else { + result = overallDetailCheckboxes.values().iterator().next(); + } + } else { + result = maneuverDetailCheckboxes.values().iterator().next(); + } + } else { + result = legDetailCheckboxes.values().iterator().next(); + } + } else { + result = raceDetailCheckboxes.values().iterator().next(); + } + return result; } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsFactory.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsFactory.java index 74d37df25b6..fff017843fd 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsFactory.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsFactory.java @@ -29,17 +29,23 @@ public class LeaderboardSettingsFactory { * unchanged during {@link LeaderboardPanel#updateSettings(LeaderboardSettings)}; otherwise, the settings * will show the single race column identified by this argument * @param nameOfRaceToShow - * alternatively to nameOfRaceColumnToShow, this argument may be used in case the race name is known, - * but its column name is not. If null, and nameOfRaceColumnToShow is also null, - * no change to the selected race columns will happen while updating the leaderboard settings. It is an error - * to pass non-null values for both, nameOfRaceColumnToShow and - * nameOfRaceToShow, and an {@link IllegalArgumentException} will be thrown in this case. - * @param raceColumnSelection - * the settings will be constructed such that the new settings will have the same race columns selected as those selected by - * this argument + * alternatively to nameOfRaceColumnToShow, this argument may be used in case the race name + * is known, but its column name is not. If null, and nameOfRaceColumnToShow is + * also null, no change to the selected race columns will happen while updating the + * leaderboard settings. It is an error to pass non-null values for both, + * nameOfRaceColumnToShow and nameOfRaceToShow, and an + * {@link IllegalArgumentException} will be thrown in this case. + * @param raceColumnSelection + * the settings will be constructed such that the new settings will have the same race columns selected + * as those selected by this argument + * @param showRegattaRank + * if null, the overall detail settings that are responsible for deciding whether or not to + * show the regatta rank are left unchanged; otherwise, a non-null overall details + * collection will be put to the settings and the {@link DetailType#REGATTA_RANK} will be added to the + * overall details if and only if this parameter is true */ public LeaderboardSettings createNewSettingsForPlayMode(PlayModes playMode, String nameOfRaceToSort, String nameOfRaceColumnToShow, - String nameOfRaceToShow, RaceColumnSelection raceColumnSelection) { + String nameOfRaceToShow, RaceColumnSelection raceColumnSelection, Boolean showRegattaRank) { if (nameOfRaceColumnToShow != null && nameOfRaceToShow != null) { throw new IllegalArgumentException("Can identify only one race to show, either by race name or by its column name, but not both"); } @@ -63,7 +69,7 @@ public class LeaderboardSettingsFactory { raceDetails.add(DetailType.RACE_DISTANCE_TO_LEADER_IN_METERS); raceDetails.add(DetailType.NUMBER_OF_MANEUVERS); raceDetails.add(DetailType.DISPLAY_LEGS); - List overallDetails = null; // lead overall details unchanged + final List overallDetails = createOverallDetailsForShowRegattaRank(showRegattaRank); settings = new LeaderboardSettings(maneuverDetails, legDetails, raceDetails, overallDetails, namesOfRaceColumnsToShow, namesOfRacesToShow, raceColumnSelection.getNumberOfLastRaceColumnsToShow(), @@ -74,12 +80,32 @@ public class LeaderboardSettingsFactory { break; case Replay: settings = createNewDefaultSettings(namesOfRaceColumnsToShow, namesOfRacesToShow, nameOfRaceToSort, /* autoExpandFirstRace */ - nameOfRaceColumnToShow != null); + nameOfRaceColumnToShow != null, showRegattaRank); break; } return settings; } + /** + * @param showRegattaRank + * if null, the overall detail settings that are responsible for deciding whether or not to + * show the regatta rank are left unchanged; otherwise, a non-null overall details + * collection will be put to the settings and the {@link DetailType#REGATTA_RANK} will be added to the + * overall details if and only if this parameter is true + */ + private List createOverallDetailsForShowRegattaRank(Boolean showRegattaRank) { + final List overallDetails; + if (showRegattaRank != null) { + overallDetails = new ArrayList<>(); + if (showRegattaRank) { + overallDetails.add(DetailType.REGATTA_RANK); + } + } else { + overallDetails = null; // leave overall details unchanged + } + return overallDetails; + } + /** * @param namesOfRaceColumnsToShow * if null, create settings which leave the list of races to show unchanged when applied @@ -89,13 +115,19 @@ public class LeaderboardSettingsFactory { * @param namesOfRacesToShow * alternatively, races to show can also be specified by their race names; if not null, * namesOfRaceColumnsToShow must be null + * @param showRegattaRank + * if null, the overall detail settings that are responsible for deciding whether or not to + * show the regatta rank are left unchanged; otherwise, a non-null overall details + * collection will be put to the settings and the {@link DetailType#REGATTA_RANK} will be added to the + * overall details if and only if this parameter is true * @param showOverallLeaderboardsOnSamePage * tells whether a meta leaderboard should be shown beneath the actual leaderboard on the same page, if * such a meta leaderboard exists; if multiple such meta leaderboards exist, they are all shown */ public LeaderboardSettings createNewDefaultSettings(List namesOfRaceColumnsToShow, - List namesOfRacesToShow, String nameOfRaceToSort, boolean autoExpandPreSelectedRace) { - return createNewDefaultSettings(namesOfRaceColumnsToShow, namesOfRacesToShow, /* leave overallDetailsToShow unchanged */ null, + List namesOfRacesToShow, String nameOfRaceToSort, boolean autoExpandPreSelectedRace, Boolean showRegattaRank) { + final List overallDetails = createOverallDetailsForShowRegattaRank(showRegattaRank); + return createNewDefaultSettings(namesOfRaceColumnsToShow, namesOfRacesToShow, overallDetails, nameOfRaceToSort, autoExpandPreSelectedRace, /* refreshIntervalMillis */ null); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSortableColumnWithMinMax.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSortableColumnWithMinMax.java new file mode 100755 index 00000000000..30edbf8fc84 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSortableColumnWithMinMax.java @@ -0,0 +1,38 @@ +package com.sap.sailing.gwt.ui.leaderboard; + +import com.google.gwt.cell.client.Cell; +import com.sap.sailing.domain.common.SortingOrder; +import com.sap.sailing.gwt.ui.client.shared.controls.AbstractSortableColumnWithMinMax; + +/** + * A sortable column whose elements can be compared for a minimum and maximum value whose magnitudes can + * reasonably be displayed in the cell, e.g. by a little background bar indicating this magnitude. + * + * @author Axel Uhl (D043530) + * + * @param + * @param + */ +public abstract class LeaderboardSortableColumnWithMinMax extends AbstractSortableColumnWithMinMax { + private final DisplayedLeaderboardRowsProvider displayedLeaderboardRowsProvider; + + protected LeaderboardSortableColumnWithMinMax(Cell cell, SortingOrder preferredSortingOrder, + DisplayedLeaderboardRowsProvider displayedLeaderboardRowsProvider) { + super(cell, preferredSortingOrder); + this.displayedLeaderboardRowsProvider = displayedLeaderboardRowsProvider; + } + + /** + * Based on the rows returned by the {@link DisplayedLeaderboardRowsProvider} instance passed to the + * constructor, updates the min/max values for this column. + */ + @Override + public void updateMinMax() {} + + /** + * @return the leaderboard rows provider passed to the constructor + */ + protected DisplayedLeaderboardRowsProvider getDisplayedLeaderboardRowsProvider() { + return displayedLeaderboardRowsProvider; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardTable.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardTable.css index 7533c15ee0d..9496e83a100 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardTable.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardTable.css @@ -10,13 +10,13 @@ position: relative; vertical-align: bottom; background-color: #e5e5e5; - border-right: 1px solid #ffffff !important; + border-right: 1px solid #c9caca !important; border-top: none !important; border-bottom: 1px solid #c9caca !important; padding: 3px 5px; padding-bottom: 5px; text-align: center; - font-family: 'OpenSansCondensedLight', 'OpenSansRegular', Arial, Verdana, sans-serif; + text-shadow: none !important; } .cellTableFirstColumnHeader { @@ -24,7 +24,7 @@ vertical-align: bottom; background-color: #e5e5e5; border-left: none !important; - border-right: 1px solid #ffffff !important; + border-right: 1px solid #c9caca !important; border-top: none !important; border-bottom: 1px solid #c9caca !important; text-align: center; @@ -55,9 +55,39 @@ border-left: none; border-top: none !important; border-bottom: 1px solid #c9caca !important; - padding: 5px 5px 5px !important; + padding: 7px 7px 7px; white-space: nowrap; - font-family: 'OpenSansCondensedLight', 'OpenSansRegular', Arial, Verdana, sans-serif; + font-size: 12px !important; + line-height: 1.5 !important; +} + +.cellTableCheckboxColumnCell { + padding: 1px 1px 1px !important; + vertical-align: middle !important; + text-align: center; +} + +.cellTableCheckboxSelected { + margin-left: 0px; /* account for the selection border that pushes a selected checkbox to the side */ + margin-top: 0px; + margin-bottom: 0px; + margin-right: 2px; + height: 18px; + width: 18px; + border: solid 1px #fcbb22; + background-color: #fcbb22; + vertical-align: middle; +} + +.cellTableCheckboxDeselected { + margin-left: 2px; /* account for the selection border that pushes a selected checkbox to the side */ + margin-top: 0px; + margin-bottom: 0px; + margin-right: 2px; + border: solid 1px #acacac; + height: 18px; + width: 18px; + vertical-align: middle; } .cellTableOddRow { @@ -70,9 +100,10 @@ border-right: 1px solid #c9caca !important; border-top: none !important; border-bottom: 1px solid #c9caca !important; - padding: 5px 5px 5px !important; + padding: 7px 7px 7px; white-space: nowrap; - font-family: 'OpenSansCondensedLight', 'OpenSansRegular', Arial, Verdana, sans-serif; + font-size: 12px !important; + line-height: 1.5 !important; } .cellTableHoveredRow { @@ -104,7 +135,7 @@ } .cellTableRaceColumnHeader { - background-color: #e0e0e0; + background-color: #e5e5e5; } .cellTableRaceColumnHeader span:first-child{ diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardTableResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardTableResources.java index 1d7ae2abe8f..55c5996280f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardTableResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardTableResources.java @@ -28,7 +28,22 @@ public interface LeaderboardTableResources extends CellTable.Resources { * Applied to race columns */ String cellTableRaceColumn(); + + /** + * Applied to checkbox column + */ + String cellTableCheckboxColumnCell(); + /** + * Applied to the checkbox div when the checkbox is selected + */ + String cellTableCheckboxSelected(); + + /** + * Applied to the checkbox div when the checkbox is deselected + */ + String cellTableCheckboxDeselected(); + /** * Applied to leg columns */ diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardUrlConfigurationDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardUrlConfigurationDialog.java index e5aae8817c4..0f0a6734a3b 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardUrlConfigurationDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardUrlConfigurationDialog.java @@ -1,7 +1,6 @@ package com.sap.sailing.gwt.ui.leaderboard; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import com.google.gwt.user.client.ui.Anchor; @@ -18,7 +17,7 @@ import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.Component; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialog; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent; -import com.sap.sailing.gwt.ui.leaderboard.LeaderboardEntryPoint.LeaderboardUrlSettings; +import com.sap.sailing.gwt.ui.leaderboard.LeaderboardUrlSettings; import com.sap.sse.gwt.client.dialog.DataEntryDialog; public class LeaderboardUrlConfigurationDialog extends SettingsDialog { @@ -71,6 +70,11 @@ public class LeaderboardUrlConfigurationDialog extends SettingsDialog { @@ -103,17 +107,16 @@ public class LeaderboardUrlConfigurationDialog extends SettingsDialog overallDetailsToShow = Collections.emptyList(); + false, /* showRegattaRank */ true); leaderboardSettingsDialogComponent = new LeaderboardSettingsDialogComponent(settings.getManeuverDetailsToShow(), - settings.getLegDetailsToShow(), settings.getRaceDetailsToShow(), overallDetailsToShow, raceList, + settings.getLegDetailsToShow(), settings.getRaceDetailsToShow(), settings.getOverallDetailsToShow(), raceList, /* select all races by default */ raceList, new ExplicitRaceColumnSelection(), /* autoExpandPreSelectedRace */ false, settings.isShowAddedScores(), /* delayBetweenAutoAdvancesInMilliseconds */ 3000l, settings.isShowOverallColumnWithNumberOfRacesCompletedPerCompetitor(), stringMessages); } private void updateURL(LeaderboardUrlSettings settings, String leaderboardName, String leaderboardDisplayName) { - resultingUrl.setHref(LeaderboardEntryPoint.getUrl(leaderboardName, leaderboardDisplayName, settings)); + resultingUrl.setHref(LeaderboardUrlSettings.getUrl(leaderboardName, leaderboardDisplayName, settings)); } /** diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardUrlSettings.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardUrlSettings.java new file mode 100644 index 00000000000..dde292ef51a --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardUrlSettings.java @@ -0,0 +1,175 @@ +package com.sap.sailing.gwt.ui.leaderboard; + +import com.google.gwt.user.client.Window; +import com.sap.sailing.domain.common.DetailType; +import com.sap.sailing.gwt.ui.client.URLEncoder; + +public class LeaderboardUrlSettings { + public static final String PARAM_LEADERBOARD_GROUP_NAME = "leaderboardGroupName"; + public static final String PARAM_EVENT_ID = "eventId"; + public static final String PARAM_EMBEDDED = "embedded"; + public static final String PARAM_HIDE_TOOLBAR = "hideToolbar"; + public static final String PARAM_SHOW_RACE_DETAILS = "showRaceDetails"; + public static final String PARAM_RACE_NAME = "raceName"; + public static final String PARAM_RACE_DETAIL = "raceDetail"; + public static final String PARAM_OVERALL_DETAIL = "overallDetail"; + public static final String PARAM_LEG_DETAIL = "legDetail"; + public static final String PARAM_MANEUVER_DETAIL = "maneuverDetail"; + public static final String PARAM_AUTO_EXPAND_PRESELECTED_RACE = "autoExpandPreselectedRace"; + public static final String PARAM_AUTO_EXPAND_LAST_RACE_COLUMN = "autoExpandLastRaceColumn"; + public static final String PARAM_REGATTA_NAME = "regattaName"; + public static final String PARAM_REFRESH_INTERVAL_MILLIS = "refreshIntervalMillis"; + public static final String PARAM_SHOW_CHARTS = "showCharts"; + public static final String PARAM_CHART_DETAIL = "chartDetail"; + public static final String PARAM_SHOW_OVERALL_LEADERBOARD = "showOverallLeaderboard"; + public static final String PARAM_SHOW_SERIES_LEADERBOARDS = "showSeriesLeaderboards"; + public static final String PARAM_SHOW_ADDED_SCORES = "showAddedScores"; + public static final String PARAM_SHOW_OVERALL_COLUMN_WITH_NUMBER_OF_RACES_COMPLETED = "showNumberOfRacesCompleted"; + + /** + * Parameter to support scaling the complete page by a given factor. This works by either using the + * CSS3 zoom property or by applying scale operation to the body element. This comes in handy + * when having to deal with screens that have high resolutions and that can't be controlled manually. + * It is also a very simple method of adapting the viewport to a tv resolution. This parameter works + * with value from 0.0 to 10.0 where 1.0 denotes the unchanged level (100%). + */ + public static final String PARAM_ZOOM_TO = "zoomTo"; + + /** + * Lets the client choose a different race column selection which displays only up to the last N races with N being the integer + * number specified by the parameter. + */ + public static final String PARAM_NAME_LAST_N = "lastN"; + + private final LeaderboardSettings leaderboardSettings; + private final boolean embedded; + private final boolean hideToolbar; + private final boolean showRaceDetails; + private final boolean autoExpandLastRaceColumn; + private final boolean autoRefresh; + private final boolean showCharts; + private final DetailType chartDetail; + private final boolean showOverallLeaderboard; + private final boolean showSeriesLeaderboards; + + public LeaderboardUrlSettings(LeaderboardSettings leaderboardSettings, boolean embedded, + boolean hideToolbar, boolean showRaceDetails, boolean autoRefresh, boolean autoExpandLastRaceColumn, + boolean showCharts, DetailType chartDetail, boolean showOverallLeaderboard, boolean showSeriesLeaderboards) { + super(); + this.leaderboardSettings = leaderboardSettings; + this.embedded = embedded; + this.hideToolbar = hideToolbar; + this.showRaceDetails = showRaceDetails; + this.autoRefresh = autoRefresh; + this.autoExpandLastRaceColumn = autoExpandLastRaceColumn; + this.showCharts = showCharts; + this.chartDetail = chartDetail; + this.showOverallLeaderboard = showOverallLeaderboard; + this.showSeriesLeaderboards = showSeriesLeaderboards; + } + + public LeaderboardSettings getLeaderboardSettings() { + return leaderboardSettings; + } + + public boolean isEmbedded() { + return embedded; + } + + public boolean isShowRaceDetails() { + return showRaceDetails; + } + + public boolean isAutoRefresh() { + return autoRefresh; + } + + public boolean isAutoExpandLastRaceColumn() { + return autoExpandLastRaceColumn; + } + + public boolean isHideToolbar() { + return hideToolbar; + } + + public boolean isShowCharts() { + return showCharts; + } + + public DetailType getChartDetail() { + return chartDetail; + } + + public boolean isShowOverallLeaderboard() { + return showOverallLeaderboard; + } + + public boolean isShowSeriesLeaderboards() { + return showSeriesLeaderboards; + } + + /** + * Assembles a URL for a leaderboard that displays with the settings and embedded mode + * as specified by the parameters. + */ + public static String getUrl(String leaderboardName, String leaderboardDisplayName, LeaderboardUrlSettings settings) { + StringBuilder overallDetails = new StringBuilder(); + for (DetailType overallDetail : settings.getLeaderboardSettings().getOverallDetailsToShow()) { + overallDetails.append('&'); + overallDetails.append(LeaderboardUrlSettings.PARAM_OVERALL_DETAIL); + overallDetails.append('='); + overallDetails.append(overallDetail.name()); + } + StringBuilder legDetails = new StringBuilder(); + for (DetailType legDetail : settings.getLeaderboardSettings().getLegDetailsToShow()) { + legDetails.append('&'); + legDetails.append(LeaderboardUrlSettings.PARAM_LEG_DETAIL); + legDetails.append('='); + legDetails.append(legDetail.name()); + } + StringBuilder raceDetails = new StringBuilder(); + for (DetailType raceDetail : settings.getLeaderboardSettings().getRaceDetailsToShow()) { + raceDetails.append('&'); + raceDetails.append(LeaderboardUrlSettings.PARAM_RACE_DETAIL); + raceDetails.append('='); + raceDetails.append(raceDetail.name()); + } + StringBuilder maneuverDetails = new StringBuilder(); + for (DetailType maneuverDetail : settings.getLeaderboardSettings().getManeuverDetailsToShow()) { + maneuverDetails.append('&'); + maneuverDetails.append(LeaderboardUrlSettings.PARAM_MANEUVER_DETAIL); + maneuverDetails.append('='); + maneuverDetails.append(maneuverDetail.name()); + } + StringBuilder showAddedScores = new StringBuilder(); + showAddedScores.append('&'); + showAddedScores.append(LeaderboardUrlSettings.PARAM_SHOW_ADDED_SCORES); + showAddedScores.append('='); + showAddedScores.append(settings.getLeaderboardSettings().isShowAddedScores()); + + String debugParam = Window.Location.getParameter("gwt.codesvr"); + String link = URLEncoder.encode("/gwt/Leaderboard.html?name=" + leaderboardName + + (settings.isShowRaceDetails() ? "&"+LeaderboardUrlSettings.PARAM_SHOW_RACE_DETAILS+"=true" : "") + + (leaderboardDisplayName != null ? "&displayName="+leaderboardDisplayName : "") + + (settings.isEmbedded() ? "&"+LeaderboardUrlSettings.PARAM_EMBEDDED+"=true" : "") + + (settings.isHideToolbar() ? "&"+LeaderboardUrlSettings.PARAM_HIDE_TOOLBAR+"=true" : "") + + (settings.isShowCharts() ? "&"+LeaderboardUrlSettings.PARAM_SHOW_CHARTS+"=true" : "") + + (settings.isShowCharts() ? "&"+LeaderboardUrlSettings.PARAM_CHART_DETAIL+"="+settings.getChartDetail().name() : "") + + (settings.isShowOverallLeaderboard() ? "&"+LeaderboardUrlSettings.PARAM_SHOW_OVERALL_LEADERBOARD+"=true" : "") + + (settings.isShowSeriesLeaderboards() ? "&"+LeaderboardUrlSettings.PARAM_SHOW_SERIES_LEADERBOARDS+"=true" : "") + + (!settings.isAutoRefresh() || (settings.getLeaderboardSettings().getDelayBetweenAutoAdvancesInMilliseconds() == null && + settings.getLeaderboardSettings().getDelayBetweenAutoAdvancesInMilliseconds() != 0) ? "" : + "&"+LeaderboardUrlSettings.PARAM_REFRESH_INTERVAL_MILLIS+"="+settings.getLeaderboardSettings().getDelayBetweenAutoAdvancesInMilliseconds()) + + legDetails.toString() + + raceDetails.toString() + + overallDetails.toString() + + maneuverDetails.toString() + + (settings.isAutoExpandLastRaceColumn() ? "&"+LeaderboardUrlSettings.PARAM_AUTO_EXPAND_LAST_RACE_COLUMN+"=true" : "") + + (settings.getLeaderboardSettings().getNumberOfLastRacesToShow() == null ? "" : + "&"+LeaderboardUrlSettings.PARAM_NAME_LAST_N+"="+settings.getLeaderboardSettings().getNumberOfLastRacesToShow()) + + showAddedScores.toString() + + (debugParam != null && !debugParam.isEmpty() ? "&gwt.codesvr=" + debugParam : "")); + return link; + } +} + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardViewer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardViewer.java index 399675f82c0..98311247f6b 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardViewer.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardViewer.java @@ -47,8 +47,8 @@ public class LeaderboardViewer extends AbstractLeaderboardViewer { super(competitorSelectionModel, asyncActionsExecutor, timer, stringMessages, hideToolbar, new LeaderboardPanel( sailingService, asyncActionsExecutor, leaderboardSettings, preselectedRace, competitorSelectionModel, timer, leaderboardGroupName, leaderboardName, errorReporter, - stringMessages, userAgent, showRaceDetails, /* raceTimesInfoProvider */null, autoExpandLastRaceColumn, /* adjustTimerDelay */ - true)); + stringMessages, userAgent, showRaceDetails, /* competitorSearchTextBox */ null, /* showSelectionCheckbox */ true, /* adjustTimerDelay */ + /* raceTimesInfoProvider */null, autoExpandLastRaceColumn, true, /*autoApplyTopNFilter*/ false, false)); final FlowPanel mainPanel = createViewerPanel(); setWidget(mainPanel); multiCompetitorChart = new MultiCompetitorLeaderboardChart(sailingService, asyncActionsExecutor, leaderboardName, chartDetailType, @@ -78,7 +78,8 @@ public class LeaderboardViewer extends AbstractLeaderboardViewer { overallLeaderboardPanel = new LeaderboardPanel(sailingService, asyncActionsExecutor, leaderboardSettings, preselectedRace, competitorSelectionProvider, timer, leaderboardGroupName, overallLeaderboardName, errorReporter, stringMessages, userAgent, - false, /* raceTimesInfoProvider */null, false, /* adjustTimerDelay */ true); + false, /* competitorSearchTextBox */ null, /* showSelectionCheckbox */ true, /* raceTimesInfoProvider */null, + false, /* adjustTimerDelay */ true, /*autoApplyTopNFilter*/ false, false); mainPanel.add(overallLeaderboardPanel); addComponentToNavigationMenu(overallLeaderboardPanel, true, stringMessages.seriesLeaderboard(), /* hasSettingsWhenComponentIsInvisible*/ true); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LegColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LegColumn.java index 6ef18e570fe..73db0d81429 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LegColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LegColumn.java @@ -17,6 +17,7 @@ import com.sap.sailing.domain.common.dto.LeaderboardRowDTO; import com.sap.sailing.domain.common.dto.LegEntryDTO; import com.sap.sailing.domain.common.impl.InvertibleComparatorAdapter; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.controls.AbstractSortableColumnWithMinMax; import com.sap.sailing.gwt.ui.leaderboard.DetailTypeColumn.LegDetailField; /** @@ -242,10 +243,10 @@ public class LegColumn extends ExpandableSortableColumn { } @Override - protected Map> getDetailColumnMap( + protected Map> getDetailColumnMap( LeaderboardPanel leaderboardPanel, StringMessages stringMessages, String detailHeaderStyle, String detailColumnStyle) { - Map> result = new HashMap>(); + Map> result = new HashMap<>(); result.put(DetailType.DISTANCE_TRAVELED, new FormattedDoubleDetailTypeColumn(DetailType.DISTANCE_TRAVELED, new DistanceTraveledInMeters(), detailHeaderStyle, detailColumnStyle, leaderboardPanel)); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ManeuverCountRaceColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ManeuverCountRaceColumn.java index 4c615dcb65a..1c1eb0cda48 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ManeuverCountRaceColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ManeuverCountRaceColumn.java @@ -17,6 +17,7 @@ import com.sap.sailing.domain.common.dto.LeaderboardRowDTO; import com.sap.sailing.domain.common.dto.LegEntryDTO; import com.sap.sailing.domain.common.impl.InvertibleComparatorAdapter; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.controls.AbstractSortableColumnWithMinMax; import com.sap.sailing.gwt.ui.leaderboard.DetailTypeColumn.LegDetailField; public class ManeuverCountRaceColumn extends ExpandableSortableColumn implements HasStringAndDoubleValue { @@ -228,15 +229,15 @@ public class ManeuverCountRaceColumn extends ExpandableSortableColumn im } @Override - protected void updateMinMax() { + public void updateMinMax() { minmaxRenderer.updateMinMax(getDisplayedLeaderboardRowsProvider()); } @Override - protected Map> getDetailColumnMap( + protected Map> getDetailColumnMap( LeaderboardPanel leaderboardPanel, StringMessages stringMessages, String detailHeaderStyle, String detailColumnStyle) { - Map> result = new HashMap>(); + Map> result = new HashMap<>(); result.put(DetailType.TACK, new FormattedDoubleDetailTypeColumn(DetailType.TACK, new NumberOfTacks(), detailHeaderStyle, detailColumnStyle, leaderboardPanel)); result.put(DetailType.JIBE, new FormattedDoubleDetailTypeColumn(DetailType.JIBE, new NumberOfJibes(), detailHeaderStyle, detailColumnStyle, leaderboardPanel)); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MetaLeaderboardViewer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MetaLeaderboardViewer.java index f98487972c5..6f8e2ff9cf3 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MetaLeaderboardViewer.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MetaLeaderboardViewer.java @@ -47,7 +47,8 @@ public class MetaLeaderboardViewer extends AbstractLeaderboardViewer { new LeaderboardPanel(sailingService, asyncActionsExecutor, leaderboardSettings, preselectedRace, competitorSelectionModel, timer, leaderboardGroupName, metaLeaderboardName, errorReporter, stringMessages, userAgent, - showRaceDetails, /* raceTimesInfoProvider */null, autoExpandLastRaceColumn, /* adjustTimerDelay */ true)); + showRaceDetails, /* competitorSearchTextBox */ null, /* showSelectionCheckbox */ true, /* raceTimesInfoProvider */null, + autoExpandLastRaceColumn, /* adjustTimerDelay */ true, /*autoApplyTopNFilter*/ false, false)); FlowPanel mainPanel = createViewerPanel(); setWidget(mainPanel); final Label overallStandingsLabel = new Label(stringMessages.overallStandings()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MinMaxRenderer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MinMaxRenderer.java index af57981af62..f498679ff84 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MinMaxRenderer.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MinMaxRenderer.java @@ -87,7 +87,7 @@ public class MinMaxRenderer { public void updateMinMax(DisplayedLeaderboardRowsProvider displayedLeaderboardRowsProvider) { LeaderboardRowDTO minimumRow = null; LeaderboardRowDTO maximumRow = null; - for (LeaderboardRowDTO row : displayedLeaderboardRowsProvider.getRowsToDisplay()) { + for (LeaderboardRowDTO row : displayedLeaderboardRowsProvider.getRowsToDisplay().values()) { if (valueProvider.getDoubleValue(row) != null && (minimumRow == null || comparator.compare(minimumRow, row) > 0)) { minimumRow = row; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MultiLeaderboardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MultiLeaderboardPanel.java index 0d2e6a8bf93..bd5e40850d1 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MultiLeaderboardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MultiLeaderboardPanel.java @@ -80,7 +80,7 @@ public class MultiLeaderboardPanel extends AbstractLazyComponent>>() { + sailingService.getLeaderboardsNamesOfMetaLeaderboard(metaLeaderboardName, new AsyncCallback>>() { @Override public void onSuccess(List> leaderboardNamesAndDisplayNames) { @@ -202,7 +202,8 @@ public class MultiLeaderboardPanel extends AbstractLazyComponent implements HasStringAndDoubleValue { @@ -47,10 +48,10 @@ public class OverallTimeTraveledColumn extends ExpandableSortableColumn } @Override - protected Map> getDetailColumnMap( + protected Map> getDetailColumnMap( LeaderboardPanel leaderboardPanel, StringMessages stringConstants, String detailHeaderStyle, String detailColumnStyle) { - Map> result = new HashMap>(); + Map> result = new HashMap<>(); result.put(DetailType.TOTAL_TIME_SAILED_UPWIND_IN_SECONDS, new TotalTimeColumn(DetailType.TOTAL_TIME_SAILED_UPWIND_IN_SECONDS, new TotalTimeSailedUpwindInSeconds(), @@ -140,7 +141,7 @@ public class OverallTimeTraveledColumn extends ExpandableSortableColumn } @Override - protected void updateMinMax() { + public void updateMinMax() { minmaxRenderer.updateMinMax(getDisplayedLeaderboardRowsProvider()); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SafeHtmlHeaderWithTooltip.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SafeHtmlHeaderWithTooltip.java index 6972a34404c..564b75abd64 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SafeHtmlHeaderWithTooltip.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SafeHtmlHeaderWithTooltip.java @@ -17,8 +17,6 @@ public class SafeHtmlHeaderWithTooltip extends SafeHtmlHeader { /** * Creates a new html header with the given title and tooltip.
* Use '\n', if you want a line break in the tooltip. - * @param title - * @param tooltip */ public SafeHtmlHeaderWithTooltip(SafeHtml title, String tooltip) { super(buildHeader(title, tooltip)); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ScoringSchemeTypeFormatter.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ScoringSchemeTypeFormatter.java index 152c2b00e46..97c8657070c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ScoringSchemeTypeFormatter.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ScoringSchemeTypeFormatter.java @@ -26,6 +26,8 @@ public class ScoringSchemeTypeFormatter { return stringMessages.scoringSchemeWinnerGetsFiveIgnoringRaceCount(); case HIGH_POINT_WINNER_GETS_SIX: return stringMessages.scoringSchemeWinnerGetsSix(); + case HIGH_POINT_WINNER_GETS_SIX_IGNORING_RACE_COUNT: + return stringMessages.scoringSchemeWinnerGetsSixIgnoringRaceCount(); case HIGH_POINT_FIRST_GETS_TEN_OR_EIGHT: return stringMessages.scoringSchemeHighPointFirstGetsTenOrEight(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SortableColumnWithMinMax.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SortableColumnWithMinMax.java new file mode 100755 index 00000000000..2081c98635f --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SortableColumnWithMinMax.java @@ -0,0 +1,8 @@ +package com.sap.sailing.gwt.ui.leaderboard; + +public interface SortableColumnWithMinMax { + /** + * Based on the table rows, updates the min/max values for this column. Triggered when the leaderboard updates its rows. + */ + void updateMinMax(); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SortedCellTableWithStylableHeaders.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SortedCellTableWithStylableHeaders.java index 02da5dcf49c..27440369177 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SortedCellTableWithStylableHeaders.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SortedCellTableWithStylableHeaders.java @@ -10,9 +10,10 @@ import com.google.gwt.dom.client.NodeList; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.user.cellview.client.DefaultCellTableBuilder; import com.google.gwt.view.client.SelectionModel; +import com.sap.sailing.gwt.ui.client.shared.controls.SortableColumn; /** - * Uses {@link SortableColumn#getHeaderStyle()} to optionally add a custom CSS style to the <th> table + * Uses {@link LeaderboardSortableColumnWithMinMax#getHeaderStyle()} to optionally add a custom CSS style to the <th> table * header cell for the respective column.

* * The implementation works by overriding {@link #renderRowValues(SafeHtmlBuilder, List, int, SelectionModel)} which diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/TimeTraveledRaceColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/TimeTraveledRaceColumn.java index 89653b954dd..bb41254b070 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/TimeTraveledRaceColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/TimeTraveledRaceColumn.java @@ -18,6 +18,7 @@ import com.sap.sailing.domain.common.dto.LegEntryDTO; import com.sap.sailing.domain.common.impl.InvertibleComparatorAdapter; import com.sap.sailing.gwt.ui.client.DetailTypeFormatter; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.controls.AbstractSortableColumnWithMinMax; import com.sap.sailing.gwt.ui.leaderboard.DetailTypeColumn.LegDetailField; public class TimeTraveledRaceColumn extends ExpandableSortableColumn implements HasStringAndDoubleValue { @@ -80,10 +81,10 @@ public class TimeTraveledRaceColumn extends ExpandableSortableColumn imp } @Override - protected Map> getDetailColumnMap( + protected Map> getDetailColumnMap( LeaderboardPanel leaderboardPanel, StringMessages stringConstants, String detailHeaderStyle, String detailColumnStyle) { - Map> result = new HashMap>(); + Map> result = new HashMap<>(); result.put(DetailType.RACE_TIME_TRAVELED_UPWIND, new FormattedDoubleDetailTypeColumn(DetailType.RACE_TIME_TRAVELED_UPWIND, new RaceTimeTraveledUpwindInSeconds(), @@ -171,7 +172,7 @@ public class TimeTraveledRaceColumn extends ExpandableSortableColumn imp } @Override - protected void updateMinMax() { + public void updateMinMax() { minmaxRenderer.updateMinMax(getDisplayedLeaderboardRowsProvider()); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditCompetitorNameDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditCompetitorNameDialog.java new file mode 100644 index 00000000000..457a59514e1 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditCompetitorNameDialog.java @@ -0,0 +1,40 @@ +package com.sap.sailing.gwt.ui.leaderboardedit; + +import com.google.gwt.user.client.ui.Grid; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.TextBox; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sse.gwt.client.dialog.DataEntryDialog; + +public class EditCompetitorNameDialog extends DataEntryDialog { + private final TextBox competitorNameBox; + private final StringMessages stringMessages; + + public EditCompetitorNameDialog(StringMessages stringMessages, String competitorName, + DialogCallback callback) { + super(stringMessages.competitor(), stringMessages.competitor(), + stringMessages.ok(), stringMessages.cancel(), /* validator */ null, /* animationEnabled */ true, + callback); + this.stringMessages = stringMessages; + this.competitorNameBox = createTextBox(competitorName); + } + + @Override + protected String getResult() { + return this.competitorNameBox.getValue(); + } + + @Override + protected Widget getAdditionalWidget() { + Grid grid = new Grid(2, 2); + grid.setWidget(0, 0, new Label(stringMessages.competitor())); + grid.setWidget(0, 1, competitorNameBox); + return grid; + } + + @Override + public void show() { + super.show(); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditableLeaderboardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditableLeaderboardPanel.java index e6ab927ee6a..807ed8b1dfc 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditableLeaderboardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditableLeaderboardPanel.java @@ -65,7 +65,7 @@ import com.sap.sailing.gwt.ui.leaderboard.CompetitorFetcher; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettingsFactory; -import com.sap.sailing.gwt.ui.leaderboard.SortableColumn; +import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSortableColumnWithMinMax; import com.sap.sse.common.Util; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; @@ -146,7 +146,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel { * @author Axel Uhl (d043530) * */ - private class SuppressedSailIDColumn extends SortableColumn { + private class SuppressedSailIDColumn extends LeaderboardSortableColumnWithMinMax { protected SuppressedSailIDColumn() { super(new TextCell(), SortingOrder.ASCENDING, EditableLeaderboardPanel.this); } @@ -189,7 +189,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel { } } - protected class SuppressedCompetitorColumn extends SortableColumn { + protected class SuppressedCompetitorColumn extends LeaderboardSortableColumnWithMinMax { private final CompetitorColumnBase base; protected SuppressedCompetitorColumn(CompetitorColumnBase base) { @@ -605,7 +605,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel { String leaderboardName, String leaderboardGroupName, final ErrorReporter errorReporter, final StringMessages stringMessages, UserAgentDetails userAgent) { super(sailingService, asyncActionsExecutor, LeaderboardSettingsFactory.getInstance().createNewDefaultSettings( - /* racesToShow */ null, /* namesOfRacesToShow */ null, null, /* autoExpandFirstRace */ false), + /* racesToShow */ null, /* namesOfRacesToShow */ null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true), new CompetitorSelectionModel(/* hasMultiSelection */true), leaderboardName, errorReporter, stringMessages, userAgent, /* showRaceDetails */ true); suppressedCompetitorsShown = new ListDataProvider(new ArrayList()); @@ -761,9 +761,9 @@ public class EditableLeaderboardPanel extends LeaderboardPanel { * the column must always be shown. */ @Override - protected boolean updateCarryColumn(LeaderboardDTO leaderboard) { - ensureCarryColumn(); - return true; + protected int updateCarryColumn(LeaderboardDTO leaderboard, int zeroBasedIndexOfCarryColumn) { + ensureCarryColumn(zeroBasedIndexOfCarryColumn); + return zeroBasedIndexOfCarryColumn+1; } @Override @@ -820,49 +820,49 @@ public class EditableLeaderboardPanel extends LeaderboardPanel { return getStringMessages().suppress(); } }); - result.add(new HasCell() { - final class OptionalBoldRenderer implements SafeHtmlRenderer { - private LeaderboardRowDTO currentRow; + final class OptionalBoldRenderer implements SafeHtmlRenderer { + private LeaderboardRowDTO currentRow; - @Override - public SafeHtml render(String object) { - SafeHtmlBuilder builder = new SafeHtmlBuilder(); - render(object, builder); - return builder.toSafeHtml(); + @Override + public SafeHtml render(String object) { + SafeHtmlBuilder builder = new SafeHtmlBuilder(); + render(object, builder); + return builder.toSafeHtml(); + } + + private boolean isDisplayNameSet() { + return currentRow != null && getLeaderboard().isDisplayNameSet(currentRow.competitor); + } + + @Override + public void render(String value, SafeHtmlBuilder builder) { + if (isDisplayNameSet()) { + builder.appendHtmlConstant(""); } - - private boolean isDisplayNameSet() { - return currentRow != null && getLeaderboard().isDisplayNameSet(currentRow.competitor); - } - - @Override - public void render(String value, SafeHtmlBuilder builder) { - if (isDisplayNameSet()) { - builder.appendHtmlConstant(""); - } - builder.appendEscaped(value); - if (isDisplayNameSet()) { - builder.appendHtmlConstant(""); - } - } - - public void setCurrentRow(LeaderboardRowDTO currentRow) { - this.currentRow = currentRow; + builder.appendEscaped(value); + if (isDisplayNameSet()) { + builder.appendHtmlConstant(""); } } - private final OptionalBoldRenderer renderer = new OptionalBoldRenderer(); + public void setCurrentRow(LeaderboardRowDTO currentRow) { + this.currentRow = currentRow; + } + } - private final EditTextCell cell = new EditTextCell(renderer) { - @Override - public void render(Context context, String value, SafeHtmlBuilder sb) { - renderer.setCurrentRow((LeaderboardRowDTO) context.getKey()); - super.render(context, value, sb); - } - }; + final OptionalBoldRenderer renderer = new OptionalBoldRenderer(); + + final EditTextCell cellForCompetitorName = new EditTextCell(renderer) { + @Override + public void render(Context context, String value, SafeHtmlBuilder sb) { + renderer.setCurrentRow((LeaderboardRowDTO) context.getKey()); + super.render(context, value, sb); + } + }; + result.add(new HasCell() { @Override public EditTextCell getCell() { - return cell; + return cellForCompetitorName; } @Override @@ -875,6 +875,56 @@ public class EditableLeaderboardPanel extends LeaderboardPanel { return getLeaderboard().getDisplayName(row.competitor); } }); + result.add(new HasCell() { + private final ButtonCell cell = new ButtonCell(); + @Override + public Cell getCell() { + return cell; + } + + @Override + public FieldUpdater getFieldUpdater() { + return new FieldUpdater() { + @Override + public void update(int index, final LeaderboardRowDTO row, String valueToUpdate) { + new EditCompetitorNameDialog(getStringMessages(), row.competitor.getName(), new DialogCallback() { + @Override + public void ok(final String value) { + getSailingService().updateCompetitorDisplayNameInLeaderboard(getLeaderboardName(), row.competitor.getIdAsString(), + value == null || value.length() == 0 ? null : value.trim(), + new AsyncCallback() { + @Override + public void onFailure(Throwable t) { + EditableLeaderboardPanel.this.getErrorReporter().reportError("Error trying to update display name for competitor "+ + row.competitor.getName()+" in leaderboard "+getLeaderboardName()+": "+t.getMessage()+ + "\nYou may have to refresh your view."); + } + + @Override + public void onSuccess(Void v) { + if (getLeaderboard().competitorDisplayNames == null) { + getLeaderboard().competitorDisplayNames = new HashMap(); + } + getLeaderboard().competitorDisplayNames.put(row.competitor, value == null || value.trim().length() == 0 ? null : value.trim()); + cellForCompetitorName.setViewData(row, null); // ensure that getValue() is called again + EditableLeaderboardPanel.this.getData().getList().set( + EditableLeaderboardPanel.this.getData().getList().indexOf(row), row); + } + }); + } + @Override + public void cancel() { + } + }).show(); + } + }; + } + + @Override + public String getValue(LeaderboardRowDTO object) { + return getStringMessages().edit(); + } + }); return result; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsChartPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsChartPanel.java index 3e5a14f9770..db392c63865 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsChartPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsChartPanel.java @@ -162,6 +162,7 @@ public class PolarSheetsChartPanel extends DockLayoutPanel { || result.getHistogramDataMap().get(windspeed).get(i) == null || result.getHistogramDataMap().get(windspeed).get(i).getConfidenceMeasure() < settings .getMinimumConfidenceMeasure()) { + points[i] = new Point(0); continue; } if (points[i] == null) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsPanel.java index 23d428e4c61..bd442944d0f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsPanel.java @@ -279,4 +279,9 @@ public class PolarSheetsPanel extends SplitLayoutPanel implements RaceSelectionC public Widget getEntryWidget() { return this; } + + @Override + public String getDependentCssClassName() { + return "polarSheet"; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsTrackedRacesList.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsTrackedRacesList.java index 28aae925114..64bad09c127 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsTrackedRacesList.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsTrackedRacesList.java @@ -89,4 +89,9 @@ public class PolarSheetsTrackedRacesList extends AbstractFilteredTrackedRacesLis btnPolarSheetGeneration.setEnabled(enable); } + @Override + public String getDependentCssClassName() { + return "polarSheetsTrackedRacesList"; + } + } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardEntryPoint.java index bb9de6a9e5d..0afede9ee3a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardEntryPoint.java @@ -1,41 +1,36 @@ package com.sap.sailing.gwt.ui.raceboard; +import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.UUID; import com.google.gwt.core.client.GWT; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.Event.NativePreviewEvent; -import com.google.gwt.user.client.Event.NativePreviewHandler; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.ServiceDefTarget; +import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.DockLayoutPanel; import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.RootLayoutPanel; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.dto.RaceDTO; import com.sap.sailing.gwt.ui.client.AbstractEntryPoint; -import com.sap.sailing.gwt.ui.client.GlobalNavigationPanel; -import com.sap.sailing.gwt.ui.client.LogoAndTitlePanel; import com.sap.sailing.gwt.ui.client.MediaService; import com.sap.sailing.gwt.ui.client.MediaServiceAsync; import com.sap.sailing.gwt.ui.client.ParallelExecutionCallback; import com.sap.sailing.gwt.ui.client.ParallelExecutionHolder; import com.sap.sailing.gwt.ui.client.RaceSelectionModel; -import com.sap.sailing.gwt.ui.client.RaceTimePanel; import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider; import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants; import com.sap.sailing.gwt.ui.client.SailingService; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.UserManagementService; import com.sap.sailing.gwt.ui.client.UserManagementServiceAsync; +import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; @@ -44,7 +39,6 @@ import com.sap.sse.gwt.client.EntryPointHelper; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; import com.sap.sse.gwt.client.player.Timer; import com.sap.sse.gwt.client.player.Timer.PlayModes; -import com.sap.sse.gwt.client.useragent.UserAgentChecker; import com.sap.sse.gwt.shared.GwtHttpRequestUtils; public class RaceBoardEntryPoint extends AbstractEntryPoint { @@ -54,21 +48,19 @@ public class RaceBoardEntryPoint extends AbstractEntryPoint { private static final String PARAM_RACE_NAME = "raceName"; private static final String PARAM_LEADERBOARD_NAME = "leaderboardName"; private static final String PARAM_LEADERBOARD_GROUP_NAME = "leaderboardGroupName"; + private static final String PARAM_EVENT_ID = "eventId"; private String regattaName; private String raceName; private String leaderboardName; private String leaderboardGroupName; + private UUID eventId; private RaceBoardViewConfiguration raceboardViewConfig; - private GlobalNavigationPanel globalNavigationPanel; - private final SailingServiceAsync sailingService = GWT.create(SailingService.class); private final MediaServiceAsync mediaService = GWT.create(MediaService.class); private final UserManagementServiceAsync userManagementService = GWT.create(UserManagementService.class); - private boolean toolbarAndLogoAndTitleBarHidden; - @Override protected void doOnModuleLoad() { super.doOnModuleLoad(); @@ -88,6 +80,13 @@ public class RaceBoardEntryPoint extends AbstractEntryPoint { if (leaderboardGroupNameParamValue != null && !leaderboardGroupNameParamValue.isEmpty()) { leaderboardGroupName = leaderboardGroupNameParamValue; } + String eventIdParamValue = Window.Location.getParameter(PARAM_EVENT_ID); + if (eventIdParamValue != null && !eventIdParamValue.isEmpty()) { + eventId = UUID.fromString(eventIdParamValue); + } + if (leaderboardGroupNameParamValue != null && !leaderboardGroupNameParamValue.isEmpty()) { + leaderboardGroupName = leaderboardGroupNameParamValue; + } if (regattaName == null || regattaName.isEmpty() || raceName == null || raceName.isEmpty() || leaderboardName == null || leaderboardName.isEmpty()) { createErrorPage("This page requires a valid regatta name, race name and leaderboard name."); @@ -102,74 +101,94 @@ public class RaceBoardEntryPoint extends AbstractEntryPoint { String activeCompetitorsFilterSetName = GwtHttpRequestUtils.getStringParameter(RaceBoardViewConfiguration.PARAM_VIEW_COMPETITOR_FILTER, null /* default*/); final boolean canReplayWhileLiveIsPossible = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_CAN_REPLAY_DURING_LIVE_RACES, false); final boolean autoSelectMedia = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_AUTOSELECT_MEDIA, false); + final String defaultMedia = GwtHttpRequestUtils.getStringParameter(RaceBoardViewConfiguration.PARAM_DEFAULT_MEDIA, null /* default*/); final boolean showMapControls = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_MAPCONTROLS, true /* default*/); - final boolean showNavigationPanel = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_NAVIGATION_PANEL, true /* default */); raceboardViewConfig = new RaceBoardViewConfiguration(activeCompetitorsFilterSetName, showLeaderboard, - showWindChart, showCompetitorsChart, showViewStreamlets, canReplayWhileLiveIsPossible, autoSelectMedia, - showNavigationPanel); + showWindChart, showCompetitorsChart, showViewStreamlets, canReplayWhileLiveIsPossible, autoSelectMedia, defaultMedia); - final ParallelExecutionCallback> getLeaderboardNamesCallback = new ParallelExecutionCallback>(); - final ParallelExecutionCallback> getRegattasCallback = new ParallelExecutionCallback>(); - final ParallelExecutionCallback getLeaderboardGroupByNameCallback = new ParallelExecutionCallback(); - final ParallelExecutionCallback getUserCallback = new ParallelExecutionCallback(); + final ParallelExecutionCallback> getLeaderboardNamesCallback = new ParallelExecutionCallback>(); + final ParallelExecutionCallback> getRegattasCallback = new ParallelExecutionCallback>(); + final ParallelExecutionCallback getLeaderboardGroupByNameCallback = new ParallelExecutionCallback(); + final ParallelExecutionCallback getEventByIdCallback = new ParallelExecutionCallback(); + final ParallelExecutionCallback getUserCallback = new ParallelExecutionCallback(); + List> callbacks = new ArrayList<>(); + callbacks.add(getLeaderboardNamesCallback); + callbacks.add(getRegattasCallback); + callbacks.add(getUserCallback); if (leaderboardGroupName != null) { - new ParallelExecutionHolder(getLeaderboardNamesCallback, getLeaderboardGroupByNameCallback, getRegattasCallback, getUserCallback) { - @Override - public void handleSuccess() { - checkUrlParameters(getLeaderboardNamesCallback.getData(), - getLeaderboardGroupByNameCallback.getData(), canReplayWhileLiveIsPossible, getRegattasCallback.getData(), getUserCallback.getData(), showMapControls); - } - @Override - public void handleFailure(Throwable t) { - reportError("Error trying to create the raceboard: " + t.getMessage()); - } - }; - } else { - new ParallelExecutionHolder(getLeaderboardNamesCallback, getRegattasCallback, getUserCallback) { - @Override - public void handleSuccess() { - checkUrlParameters(getLeaderboardNamesCallback.getData(), null, canReplayWhileLiveIsPossible, getRegattasCallback.getData(), getUserCallback.getData(), showMapControls); - } - @Override - public void handleFailure(Throwable t) { - reportError("Error trying to create the raceboard: " + t.getMessage()); - } - }; + callbacks.add(getLeaderboardGroupByNameCallback); } + if (eventId != null) { + callbacks.add(getEventByIdCallback); + } + ParallelExecutionCallback[] callbackArray = callbacks.toArray(new ParallelExecutionCallback[0]); + new ParallelExecutionHolder(callbackArray) { + @Override + public void handleSuccess() { + checkUrlParameters(getLeaderboardNamesCallback.getData(), + leaderboardGroupName == null ? null : getLeaderboardGroupByNameCallback.getData(), + canReplayWhileLiveIsPossible, getRegattasCallback.getData(), eventId == null ? null : getEventByIdCallback.getData(), + getUserCallback.getData(), showMapControls); + } + + @Override + public void handleFailure(Throwable t) { + reportError("Error trying to create the raceboard: " + t.getMessage()); + } + }; sailingService.getRegattas(getRegattasCallback); sailingService.getLeaderboardNames(getLeaderboardNamesCallback); + if (eventId != null) { + sailingService.getEventById(eventId, /* withStatisticalData */ false, getEventByIdCallback); + } if (leaderboardGroupName != null) { sailingService.getLeaderboardGroupByName(leaderboardGroupNameParamValue, false /*withGeoLocationData*/, getLeaderboardGroupByNameCallback); } userManagementService.getUser(getUserCallback); } - private void checkUrlParameters(List leaderboardNames, LeaderboardGroupDTO leaderboardGroup, boolean canReplayWhileLiveIsPossible, List regattas, UserDTO user, boolean showMapControls) { + private void checkUrlParameters(List leaderboardNames, LeaderboardGroupDTO leaderboardGroup, + boolean canReplayWhileLiveIsPossible, List regattas, EventDTO event, UserDTO user, + boolean showMapControls) { if (!leaderboardNames.contains(leaderboardName)) { - createErrorPage(stringMessages.noSuchLeaderboard()); + createErrorPage(stringMessages.noSuchLeaderboard()); return; } + if (eventId != null && event == null) { + createErrorPage(stringMessages.noSuchEvent()); + } if (leaderboardGroupName != null && leaderboardGroup != null) { - boolean foundLeaderboard = false; - for(StrippedLeaderboardDTO leaderBoard: leaderboardGroup.leaderboards) { - if(leaderBoard.name.equals(leaderboardName)) { + boolean foundLeaderboard = false; + for (StrippedLeaderboardDTO leaderboard : leaderboardGroup.leaderboards) { + if (leaderboard.name.equals(leaderboardName)) { foundLeaderboard = true; break; } } if (!foundLeaderboard) { - createErrorPage("the leaderboard is not contained in this leaderboard group."); + createErrorPage(stringMessages.leaderboardNotContainedInLeaderboardGroup(leaderboardName, leaderboardGroupName)); return; } + if (event != null) { + boolean foundLeaderboardGroupInEvent = false; + for (LeaderboardGroupDTO lg : event.getLeaderboardGroups()) { + if (lg.getId().equals(leaderboardGroup.getId())) { + foundLeaderboardGroupInEvent = true; + break; + } + } + if (!foundLeaderboardGroupInEvent) { + createErrorPage(stringMessages.leaderboardGroupNotContainedInEvent(leaderboardGroupName, event.getName())); + } + } } - selectedRace = findRace(regattaName, raceName, regattas); + RaceDTO race = findRace(regattaName, raceName, regattas); + selectedRace = race; if (selectedRace == null) { createErrorPage("Could not obtain a race with name " + raceName + " for a regatta with name " + regattaName); return; } - Window.setTitle(selectedRace.getName()); - RaceSelectionModel raceSelectionModel = new RaceSelectionModel(); List singletonList = Collections.singletonList(selectedRace.getRaceIdentifier()); raceSelectionModel.setSelection(singletonList); @@ -177,7 +196,7 @@ public class RaceBoardEntryPoint extends AbstractEntryPoint { AsyncActionsExecutor asyncActionsExecutor = new AsyncActionsExecutor(); RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(sailingService, asyncActionsExecutor, this, singletonList, 5000l /* requestInterval*/); RaceBoardPanel raceBoardPanel = new RaceBoardPanel(sailingService, mediaService, asyncActionsExecutor, user, timer, raceSelectionModel, leaderboardName, - leaderboardGroupName, raceboardViewConfig, RaceBoardEntryPoint.this, stringMessages, userAgent, raceTimesInfoProvider, showMapControls); + leaderboardGroupName, event, raceboardViewConfig, RaceBoardEntryPoint.this, stringMessages, userAgent, raceTimesInfoProvider, showMapControls); raceBoardPanel.fillRegattas(regattas); createRaceBoardInOneScreenMode(raceBoardPanel, raceboardViewConfig); } @@ -211,82 +230,29 @@ public class RaceBoardEntryPoint extends AbstractEntryPoint { return timelinePanel; } - private FlowPanel createLogoAndTitlePanel(RaceBoardPanel raceBoardPanel) { - globalNavigationPanel = new GlobalNavigationPanel(stringMessages, true, leaderboardName, leaderboardGroupName); - LogoAndTitlePanel logoAndTitlePanel = new LogoAndTitlePanel(regattaName, selectedRace.getName(), stringMessages, this) { - @Override - public void onResize() { - super.onResize(); - if (isSmallWidth()) { - remove(globalNavigationPanel); - } else { - add(globalNavigationPanel); - } - } - }; - logoAndTitlePanel.addStyleName("LogoAndTitlePanel"); - if (!isSmallWidth()) { - logoAndTitlePanel.add(globalNavigationPanel); - } - return logoAndTitlePanel; - } - - private void createRaceBoardInOneScreenMode(RaceBoardPanel raceBoardPanel, + private void createRaceBoardInOneScreenMode(final RaceBoardPanel raceBoardPanel, RaceBoardViewConfiguration raceboardViewConfiguration) { final DockLayoutPanel p = new DockLayoutPanel(Unit.PX); RootLayoutPanel.get().add(p); - final Panel toolbarPanel = raceBoardPanel.getToolbarPanel(); - if (!UserAgentChecker.INSTANCE.isUserAgentSupported(userAgent)) { - HTML lbl = new HTML(stringMessages.warningBrowserUnsupported()); - lbl.setStyleName("browserOptimizedMessage"); - toolbarPanel.add(lbl); - } - final FlowPanel logoAndTitlePanel = createLogoAndTitlePanel(raceBoardPanel); - FlowPanel timePanel = createTimePanel(raceBoardPanel); - p.addNorth(logoAndTitlePanel, 68); - p.addNorth(toolbarPanel, 40); - toolbarAndLogoAndTitleBarHidden = false; - if (!raceboardViewConfiguration.isShowNavigationPanel()) { - p.setWidgetHidden(toolbarPanel, true); - globalNavigationPanel.setVisible(false); - toolbarAndLogoAndTitleBarHidden = true; - } - - p.addSouth(timePanel, 90); - p.add(raceBoardPanel); - p.addStyleName("dockLayoutPanel"); - - addModeratorShortkeyFunctionality(p, toolbarPanel, raceBoardPanel, timePanel); - } - - private void addModeratorShortkeyFunctionality(final DockLayoutPanel p, final Panel toolbarPanel, - final RaceBoardPanel raceBoardPanel, final Panel timeWrapperPanel) { - Event.addNativePreviewHandler(new NativePreviewHandler() { + final FlowPanel timePanel = createTimePanel(raceBoardPanel); + final Button toggleButton = raceBoardPanel.getTimePanel().getAdvancedToggleButton(); + toggleButton.addClickHandler(new ClickHandler() { @Override - public void onPreviewNativeEvent(NativePreviewEvent event) { - NativeEvent ne = event.getNativeEvent(); - if ("keydown".equals(ne.getType()) && ne.getCtrlKey() - && (ne.getKeyCode() == 'm' || ne.getKeyCode() == 'M')) { - ne.preventDefault(); - Scheduler.get().scheduleDeferred(new Command() { - @Override - public void execute() { - p.setWidgetHidden(toolbarPanel, !toolbarAndLogoAndTitleBarHidden); - globalNavigationPanel.setVisible(toolbarAndLogoAndTitleBarHidden); - toolbarAndLogoAndTitleBarHidden = !toolbarAndLogoAndTitleBarHidden; - RaceTimePanel timePanel = raceBoardPanel.getTimePanel(); - if (toolbarAndLogoAndTitleBarHidden) { - timePanel.hideControlsPanelAndMovePlayButtonUp(); - p.setWidgetSize(timeWrapperPanel, 53); - } else { - timePanel.showControlsPanelAndMovePlayButtonDown(); - p.setWidgetSize(timeWrapperPanel, 90); - } - } - }); - + public void onClick(ClickEvent event) { + boolean advancedModeShown = raceBoardPanel.getTimePanel().toggleAdvancedMode(); + if (advancedModeShown) { + p.setWidgetSize(timePanel, 96); + toggleButton.removeStyleDependentName("Closed"); + toggleButton.addStyleDependentName("Open"); + } else { + p.setWidgetSize(timePanel, 67); + toggleButton.addStyleDependentName("Closed"); + toggleButton.removeStyleDependentName("Open"); } } }); + p.addSouth(timePanel, 67); + p.add(raceBoardPanel); + p.addStyleName("dockLayoutPanel"); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java index b187dfcc706..fead0d0cbee 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java @@ -1,585 +1,418 @@ -package com.sap.sailing.gwt.ui.raceboard; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import com.google.gwt.dom.client.Style; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.event.logical.shared.ValueChangeEvent; -import com.google.gwt.event.logical.shared.ValueChangeHandler; -import com.google.gwt.json.client.JSONObject; -import com.google.gwt.json.client.JSONParser; -import com.google.gwt.json.client.JSONValue; -import com.google.gwt.storage.client.Storage; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.ui.Button; -import com.google.gwt.user.client.ui.CheckBox; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.MenuBar; -import com.google.gwt.user.client.ui.Panel; -import com.google.gwt.user.client.ui.SimplePanel; -import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.domain.common.RaceIdentifier; -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; -import com.sap.sailing.domain.common.dto.CompetitorDTO; -import com.sap.sailing.domain.common.dto.RaceDTO; -import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; -import com.sap.sailing.gwt.ui.client.ErrorReporter; -import com.sap.sailing.gwt.ui.client.MediaServiceAsync; -import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; -import com.sap.sailing.gwt.ui.client.RaceSelectionProvider; -import com.sap.sailing.gwt.ui.client.RaceTimePanel; -import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider; -import com.sap.sailing.gwt.ui.client.RegattasDisplayer; -import com.sap.sailing.gwt.ui.client.SailingServiceAsync; -import com.sap.sailing.gwt.ui.client.StringMessages; -import com.sap.sailing.gwt.ui.client.media.MediaSelector; -import com.sap.sailing.gwt.ui.client.shared.charts.MultiCompetitorRaceChart; -import com.sap.sailing.gwt.ui.client.shared.charts.WindChart; -import com.sap.sailing.gwt.ui.client.shared.charts.WindChartSettings; -import com.sap.sailing.gwt.ui.client.shared.components.Component; -import com.sap.sailing.gwt.ui.client.shared.components.ComponentViewer; -import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialog; -import com.sap.sailing.gwt.ui.client.shared.filter.CompetitorRaceRankFilter; -import com.sap.sailing.gwt.ui.client.shared.filter.CompetitorSelectionProviderFilterContext; -import com.sap.sailing.gwt.ui.client.shared.filter.CompetitorTotalRankFilter; -import com.sap.sailing.gwt.ui.client.shared.filter.CompetitorsFilterSets; -import com.sap.sailing.gwt.ui.client.shared.filter.CompetitorsFilterSetsDialog; -import com.sap.sailing.gwt.ui.client.shared.filter.CompetitorsFilterSetsJsonDeSerializer; -import com.sap.sailing.gwt.ui.client.shared.filter.FilterWithUI; -import com.sap.sailing.gwt.ui.client.shared.filter.LeaderboardFilterContext; -import com.sap.sailing.gwt.ui.client.shared.filter.SelectedCompetitorsFilter; -import com.sap.sailing.gwt.ui.client.shared.filter.SelectedRaceFilterContext; -import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMap; -import com.sap.sailing.gwt.ui.leaderboard.ExplicitRaceColumnSelectionWithPreselectedRace; -import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel; -import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings; -import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettingsFactory; -import com.sap.sailing.gwt.ui.shared.RegattaDTO; -import com.sap.sailing.gwt.ui.shared.UserDTO; -import com.sap.sse.common.filter.BinaryOperator; -import com.sap.sse.common.filter.Filter; -import com.sap.sse.common.filter.FilterSet; -import com.sap.sse.gwt.client.async.AsyncActionsExecutor; -import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; -import com.sap.sse.gwt.client.player.TimeListener; -import com.sap.sse.gwt.client.player.TimeRangeWithZoomModel; -import com.sap.sse.gwt.client.player.Timer; -import com.sap.sse.gwt.client.useragent.UserAgentDetails; - -/** - * A view showing a list of components visualizing a race from the regattas announced by calls to {@link #fillRegattas(List)}. - * The race selection is provided by a {@link RaceSelectionProvider} for which this is a {@link RaceSelectionChangeListener listener}. - * {@link RaceIdentifier}-based race selection changes are converted to {@link RaceDTO} objects using the {@link #racesByIdentifier} - * map maintained during {@link #fillRegattas(List)}. The race selection provider is expected to be single selection only. - * - * @author Frank Mittag, Axel Uhl (d043530) - * - */ -public class RaceBoardPanel extends SimplePanel implements RegattasDisplayer, RaceSelectionChangeListener { - private final SailingServiceAsync sailingService; - private final MediaServiceAsync mediaService; - private final UserDTO user; - private final StringMessages stringMessages; - private final ErrorReporter errorReporter; - private final RaceBoardViewConfiguration raceboardViewConfiguration; - private String raceBoardName; - - /** - * Updated upon each {@link #fillRegattas(List)} - */ - private final Map racesByIdentifier; - - private final List componentViewers; - private final FlowPanel componentControlsPanel; - private final FlowPanel viewControlsPanel; - private final FlowPanel toolbarPanel; - private RaceTimePanel timePanel; - private final Timer timer; - private final RaceSelectionProvider raceSelectionProvider; - private final UserAgentDetails userAgent; - private final CompetitorSelectionModel competitorSelectionModel; - private final TimeRangeWithZoomModel timeRangeWithZoomModel; - private final RegattaAndRaceIdentifier selectedRaceIdentifier; - private final CompetitorsFilterSets competitorsFilterSets; - - private final LeaderboardPanel leaderboardPanel; - private WindChart windChart; - private MultiCompetitorRaceChart competitorChart; - - private CheckBox competitorsFilterCheckBox; - private FilterSet> lastActiveCompetitorFilterSet; - - /** - * The component viewer in ONESCREEN view mode. null if in CASCADE view mode - */ - private SideBySideComponentViewer leaderboardAndMapViewer; - - private final AsyncActionsExecutor asyncActionsExecutor; - - private final RaceTimesInfoProvider raceTimesInfoProvider; - - private final static String LOCAL_STORAGE_COMPETITORS_FILTER_SETS_KEY = "sailingAnalytics.raceBoard.competitorsFilterSets"; - - public RaceBoardPanel(SailingServiceAsync sailingService, MediaServiceAsync mediaService, AsyncActionsExecutor asyncActionsExecutor, UserDTO theUser, Timer timer, - RaceSelectionProvider theRaceSelectionProvider, String leaderboardName, String leaderboardGroupName, - RaceBoardViewConfiguration raceboardViewConfiguration, ErrorReporter errorReporter, final StringMessages stringMessages, - UserAgentDetails userAgent, RaceTimesInfoProvider raceTimesInfoProvider, boolean showMapControls) { - this.sailingService = sailingService; - this.mediaService = mediaService; - this.user = theUser; - this.stringMessages = stringMessages; - this.raceboardViewConfiguration = raceboardViewConfiguration; - this.raceSelectionProvider = theRaceSelectionProvider; - this.raceTimesInfoProvider = raceTimesInfoProvider; - this.errorReporter = errorReporter; - this.userAgent = userAgent; - this.timer = timer; - raceSelectionProvider.addRaceSelectionChangeListener(this); - racesByIdentifier = new HashMap(); - selectedRaceIdentifier = raceSelectionProvider.getSelectedRaces().iterator().next(); - this.setRaceBoardName(selectedRaceIdentifier.getRaceName()); - this.asyncActionsExecutor = asyncActionsExecutor; - FlowPanel mainPanel = new FlowPanel(); - mainPanel.setSize("100%", "100%"); - setWidget(mainPanel); - timeRangeWithZoomModel = new TimeRangeWithZoomModel(); - componentViewers = new ArrayList(); - competitorSelectionModel = new CompetitorSelectionModel(/* hasMultiSelection */ true); - - toolbarPanel = new FlowPanel(); - toolbarPanel.setWidth("100%"); - componentControlsPanel = new FlowPanel(); - componentControlsPanel.addStyleName("raceBoardNavigation"); - toolbarPanel.add(componentControlsPanel); - - viewControlsPanel = new FlowPanel(); - viewControlsPanel.addStyleName("raceBoardControls"); - toolbarPanel.add(viewControlsPanel); - - leaderboardPanel = createLeaderboardPanel(leaderboardName, leaderboardGroupName); - createOneScreenView(leaderboardName, leaderboardGroupName, mainPanel, showMapControls); - getElement().getStyle().setMarginLeft(12, Unit.PX); - getElement().getStyle().setMarginRight(12, Unit.PX); - - // add busy indicator to toolbar panel because the embedded leaderboard has no space for it - toolbarPanel.add(leaderboardPanel.getBusyIndicator()); - - CompetitorsFilterSets loadedCompetitorsFilterSets = loadCompetitorsFilterSets(); - if (loadedCompetitorsFilterSets != null) { - competitorsFilterSets = loadedCompetitorsFilterSets; - insertSelectedCompetitorsFilter(competitorsFilterSets); - } else { - competitorsFilterSets = createAndAddDefaultCompetitorsFilter(); - storeCompetitorsFilterSets(competitorsFilterSets); - } - - // in case the URL configuration contains the name of a competitors filter set we try to activate it +package com.sap.sailing.gwt.ui.raceboard; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.i18n.client.DateTimeFormat; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Anchor; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.MenuBar; +import com.google.gwt.user.client.ui.SimplePanel; +import com.sap.sailing.domain.common.LeaderboardNameConstants; +import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.common.dto.CompetitorDTO; +import com.sap.sailing.domain.common.dto.FleetDTO; +import com.sap.sailing.domain.common.dto.LeaderboardDTO; +import com.sap.sailing.domain.common.dto.RaceColumnDTO; +import com.sap.sailing.domain.common.dto.RaceDTO; +import com.sap.sailing.domain.common.media.MediaTrack; +import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.GlobalNavigationPanel; +import com.sap.sailing.gwt.ui.client.LeaderboardUpdateListener; +import com.sap.sailing.gwt.ui.client.MediaServiceAsync; +import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; +import com.sap.sailing.gwt.ui.client.RaceSelectionProvider; +import com.sap.sailing.gwt.ui.client.RaceTimePanel; +import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider; +import com.sap.sailing.gwt.ui.client.RegattasDisplayer; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.media.MediaComponent; +import com.sap.sailing.gwt.ui.client.shared.charts.MultiCompetitorRaceChart; +import com.sap.sailing.gwt.ui.client.shared.charts.WindChart; +import com.sap.sailing.gwt.ui.client.shared.charts.WindChartSettings; +import com.sap.sailing.gwt.ui.client.shared.components.Component; +import com.sap.sailing.gwt.ui.client.shared.components.ComponentViewer; +import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialog; +import com.sap.sailing.gwt.ui.client.shared.filter.FilterWithUI; +import com.sap.sailing.gwt.ui.client.shared.filter.LeaderboardFetcher; +import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMap; +import com.sap.sailing.gwt.ui.leaderboard.CompetitorFilterPanel; +import com.sap.sailing.gwt.ui.leaderboard.ExplicitRaceColumnSelectionWithPreselectedRace; +import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel; +import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings; +import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettingsFactory; +import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sailing.gwt.ui.shared.RegattaDTO; +import com.sap.sailing.gwt.ui.shared.UserDTO; +import com.sap.sse.common.filter.FilterSet; +import com.sap.sse.gwt.client.async.AsyncActionsExecutor; +import com.sap.sse.gwt.client.player.TimeRangeWithZoomModel; +import com.sap.sse.gwt.client.player.Timer; +import com.sap.sse.gwt.client.useragent.UserAgentDetails; + +/** + * A view showing a list of components visualizing a race from the regattas announced by calls to {@link #fillRegattas(List)}. + * The race selection is provided by a {@link RaceSelectionProvider} for which this is a {@link RaceSelectionChangeListener listener}. + * {@link RaceIdentifier}-based race selection changes are converted to {@link RaceDTO} objects using the {@link #racesByIdentifier} + * map maintained during {@link #fillRegattas(List)}. The race selection provider is expected to be single selection only. + * + * @author Frank Mittag, Axel Uhl (d043530) + * + */ +public class RaceBoardPanel extends SimplePanel implements RegattasDisplayer, RaceSelectionChangeListener, LeaderboardUpdateListener { + private final SailingServiceAsync sailingService; + private final MediaServiceAsync mediaService; + private final UserDTO user; + private final EventDTO event; + private final StringMessages stringMessages; + private final ErrorReporter errorReporter; + private final RaceBoardViewConfiguration raceboardViewConfiguration; + private String raceBoardName; + + /** + * Updated upon each {@link #fillRegattas(List)} + */ + private final Map racesByIdentifier; + + private final List componentViewers; + private RaceTimePanel timePanel; + private final Timer timer; + private final RaceSelectionProvider raceSelectionProvider; + private final UserAgentDetails userAgent; + private final CompetitorSelectionModel competitorSelectionModel; + private final TimeRangeWithZoomModel timeRangeWithZoomModel; + private final RegattaAndRaceIdentifier selectedRaceIdentifier; + + private final String leaderboardName; + private final LeaderboardPanel leaderboardPanel; + private WindChart windChart; + private MultiCompetitorRaceChart competitorChart; + + /** + * The component viewer in ONESCREEN view mode. null if in CASCADE view mode + */ + private SideBySideComponentViewer leaderboardAndMapViewer; + + private final AsyncActionsExecutor asyncActionsExecutor; + + private final RaceTimesInfoProvider raceTimesInfoProvider; + private RaceMap raceMap; + + private final FlowPanel raceInformationHeader; + private final FlowPanel regattaAndRaceTimeInformationHeader; + private boolean currentRaceHasBeenSelectedOnce; + + /** + * @param event + * an optional event that can be used for "back"-navigation in case the race board shows a race in the + * context of an event; may be null. + */ + public RaceBoardPanel(SailingServiceAsync sailingService, MediaServiceAsync mediaService, + AsyncActionsExecutor asyncActionsExecutor, UserDTO theUser, Timer timer, + RaceSelectionProvider theRaceSelectionProvider, String leaderboardName, String leaderboardGroupName, + EventDTO event, RaceBoardViewConfiguration raceboardViewConfiguration, ErrorReporter errorReporter, + final StringMessages stringMessages, UserAgentDetails userAgent, + RaceTimesInfoProvider raceTimesInfoProvider, boolean showMapControls) { + this.sailingService = sailingService; + this.mediaService = mediaService; + this.user = theUser; + this.stringMessages = stringMessages; + this.raceboardViewConfiguration = raceboardViewConfiguration; + this.raceSelectionProvider = theRaceSelectionProvider; + this.raceTimesInfoProvider = raceTimesInfoProvider; + this.errorReporter = errorReporter; + this.userAgent = userAgent; + this.timer = timer; + this.event = event; + this.currentRaceHasBeenSelectedOnce = false; + this.leaderboardName = leaderboardName; + raceSelectionProvider.addRaceSelectionChangeListener(this); + racesByIdentifier = new HashMap(); + selectedRaceIdentifier = raceSelectionProvider.getSelectedRaces().iterator().next(); + this.setRaceBoardName(selectedRaceIdentifier.getRaceName()); + this.asyncActionsExecutor = asyncActionsExecutor; + FlowPanel mainPanel = new FlowPanel(); + mainPanel.setSize("100%", "100%"); + setWidget(mainPanel); + raceInformationHeader = new FlowPanel(); + raceInformationHeader.setStyleName("RegattaRaceInformation-Header"); + regattaAndRaceTimeInformationHeader = new FlowPanel(); + regattaAndRaceTimeInformationHeader.setStyleName("RegattaAndRaceTime-Header"); + timeRangeWithZoomModel = new TimeRangeWithZoomModel(); + componentViewers = new ArrayList(); + competitorSelectionModel = new CompetitorSelectionModel(/* hasMultiSelection */ true); + + raceMap = new RaceMap(sailingService, asyncActionsExecutor, errorReporter, timer, + competitorSelectionModel, stringMessages, showMapControls, getConfiguration().isShowViewStreamlets(), selectedRaceIdentifier); + CompetitorFilterPanel competitorSearchTextBox = new CompetitorFilterPanel(competitorSelectionModel, stringMessages, raceMap, + new LeaderboardFetcher() { + @Override + public LeaderboardDTO getLeaderboard() { + return leaderboardPanel.getLeaderboard(); + } + }, selectedRaceIdentifier); + raceMap.getLeftHeaderPanel().add(raceInformationHeader); + raceMap.getRightHeaderPanel().add(regattaAndRaceTimeInformationHeader); + + leaderboardPanel = createLeaderboardPanel(leaderboardName, leaderboardGroupName, competitorSearchTextBox); + leaderboardPanel.setTitle(stringMessages.leaderboard()); + leaderboardPanel.getElement().getStyle().setMarginLeft(6, Unit.PX); + leaderboardPanel.getElement().getStyle().setMarginTop(4, Unit.PX); + createOneScreenView(leaderboardName, leaderboardGroupName, event, mainPanel, showMapControls, raceMap); // initializes the raceMap field + leaderboardPanel.addLeaderboardUpdateListener(this); + // in case the URL configuration contains the name of a competitors filter set we try to activate it + // FIXME the competitorsFilterSets has now moved to CompetitorSearchTextBox (which should probably be renamed); pass on the parameters to the LeaderboardPanel and see what it does with it if (raceboardViewConfiguration.getActiveCompetitorsFilterSetName() != null) { - for (FilterSet> filterSet: competitorsFilterSets.getFilterSets()) { + for (FilterSet> filterSet : competitorSearchTextBox.getCompetitorsFilterSets() + .getFilterSets()) { if (filterSet.getName().equals(raceboardViewConfiguration.getActiveCompetitorsFilterSetName())) { - competitorsFilterSets.setActiveFilterSet(filterSet); - break; - } - } - } - /* TODO: Disabling automatic filter loading for now. Do NOT enable before - there are tests especially for the pre-start phase! - competitorSelectionModel.setCompetitorsFilterSet(competitorsFilterSets.getActiveFilterSet()); - updateCompetitorsFilterContexts(competitorsFilterSets); - updateCompetitorsFilterControlState(competitorsFilterSets);*/ - - timePanel = new RaceTimePanel(timer, timeRangeWithZoomModel, stringMessages, raceTimesInfoProvider, raceboardViewConfiguration.isCanReplayDuringLiveRaces()); - timeRangeWithZoomModel.addTimeZoomChangeListener(timePanel); - raceTimesInfoProvider.addRaceTimesInfoProviderListener(timePanel); - raceSelectionProvider.addRaceSelectionChangeListener(timePanel); - timePanel.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces()); - } - - private void createOneScreenView(String leaderboardName, String leaderboardGroupName, FlowPanel mainPanel, boolean showMapControls) { - // create the default leaderboard and select the right race - RaceMap raceMap = new RaceMap(sailingService, asyncActionsExecutor, errorReporter, timer, - competitorSelectionModel, stringMessages, showMapControls, getConfiguration().isShowViewStreamlets(), selectedRaceIdentifier); - raceTimesInfoProvider.addRaceTimesInfoProviderListener(raceMap); - raceMap.onRaceSelectionChange(Collections.singletonList(selectedRaceIdentifier)); - List> components = new ArrayList>(); - competitorChart = new MultiCompetitorRaceChart(sailingService, asyncActionsExecutor, competitorSelectionModel, raceSelectionProvider, - timer, timeRangeWithZoomModel, stringMessages, errorReporter, true, true, leaderboardGroupName, leaderboardName); - competitorChart.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces()); - components.add(competitorChart); - windChart = new WindChart(sailingService, raceSelectionProvider, timer, timeRangeWithZoomModel, new WindChartSettings(), - stringMessages, asyncActionsExecutor, errorReporter, /* compactChart */ true); - windChart.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces()); - components.add(windChart); - leaderboardAndMapViewer = new SideBySideComponentViewer(leaderboardPanel, raceMap, components); - componentViewers.add(leaderboardAndMapViewer); - for (ComponentViewer componentViewer : componentViewers) { - mainPanel.add(componentViewer.getViewerWidget()); - } - setLeaderboardVisible(getConfiguration().isShowLeaderboard()); - setWindChartVisible(getConfiguration().isShowWindChart()); - setCompetitorChartVisible(getConfiguration().isShowCompetitorsChart()); - - addComponentToNavigationMenu(leaderboardAndMapViewer, leaderboardPanel, true); - addComponentToNavigationMenu(leaderboardAndMapViewer, windChart, true); - addComponentToNavigationMenu(leaderboardAndMapViewer, competitorChart, true); - addComponentToNavigationMenu(leaderboardAndMapViewer, raceMap, false); - addCompetitorsFilterControl(viewControlsPanel); - addMediaSelectorToNavigationMenu(); - } - - private CompetitorsFilterSets createAndAddDefaultCompetitorsFilter() { - CompetitorsFilterSets filterSets = new CompetitorsFilterSets(); - - // 1. selected competitors filter - insertSelectedCompetitorsFilter(filterSets); - - // 2. Top 50 competitors by race rank - int maxRaceRank = 50; - FilterSet> topNRaceRankCompetitorsFilterSet = - new FilterSet>(stringMessages.topNCompetitorsByRaceRank(maxRaceRank)); - CompetitorRaceRankFilter raceRankFilter = new CompetitorRaceRankFilter(); - raceRankFilter.setOperator(new BinaryOperator(BinaryOperator.Operators.LessThanEquals)); - raceRankFilter.setValue(maxRaceRank); - topNRaceRankCompetitorsFilterSet.addFilter(raceRankFilter); - filterSets.addFilterSet(topNRaceRankCompetitorsFilterSet); - - // 3. Top 50 competitors by total rank - int maxTotalRank = 50; - FilterSet> topNTotalRankCompetitorsFilterSet = - new FilterSet>(stringMessages.topNCompetitorsByTotalRank(maxTotalRank)); - CompetitorTotalRankFilter totalRankFilter = new CompetitorTotalRankFilter(); - totalRankFilter.setOperator(new BinaryOperator(BinaryOperator.Operators.LessThanEquals)); - totalRankFilter.setValue(50); - topNTotalRankCompetitorsFilterSet.addFilter(totalRankFilter); - filterSets.addFilterSet(topNTotalRankCompetitorsFilterSet); - - // set default active filter - filterSets.setActiveFilterSet(topNRaceRankCompetitorsFilterSet); - - return filterSets; - } - - /** - * Adds the media selector drop down and fills it with media for the current race. - * NOTE: - * Media selector will NOT listen and react to changes of the start time during a race board session. - * This case and its consequences are considered negligible. - * Consequently, media selector will also NOT react on media added newly during a race board session. - */ - private void addMediaSelectorToNavigationMenu() { - MediaSelector mediaSelector = new MediaSelector(selectedRaceIdentifier, raceTimesInfoProvider, timer, mediaService, stringMessages, errorReporter, this.user, getConfiguration().isAutoSelectMedia()); - timer.addPlayStateListener(mediaSelector); - timer.addTimeListener(mediaSelector); - mediaService.getMediaTracksForRace(selectedRaceIdentifier, mediaSelector); - for (Widget widget : mediaSelector.widgets()) { - componentControlsPanel.add(widget); - } - } - - @SuppressWarnings("unused") - private void addSettingsMenuItem(MenuBar settingsMenu, final Component component) { - if (component.hasSettings()) { - settingsMenu.addItem(component.getLocalizedShortName(), new Command() { - public void execute() { - new SettingsDialog(component, stringMessages).show(); - } - }); - } - } - - private LeaderboardPanel createLeaderboardPanel(String leaderboardName, String leaderboardGroupName) { - LeaderboardSettings leaderBoardSettings = LeaderboardSettingsFactory.getInstance() - .createNewSettingsForPlayMode(timer.getPlayMode(), - /* nameOfRaceToSort */ selectedRaceIdentifier.getRaceName(), - /* nameOfRaceColumnToShow */ null, /* nameOfRaceToShow */ selectedRaceIdentifier.getRaceName(), - new ExplicitRaceColumnSelectionWithPreselectedRace(selectedRaceIdentifier)); - return new LeaderboardPanel(sailingService, asyncActionsExecutor, leaderBoardSettings, selectedRaceIdentifier, - competitorSelectionModel, timer, leaderboardGroupName, leaderboardName, errorReporter, stringMessages, - userAgent, /* showRaceDetails */ true, raceTimesInfoProvider, /* autoExpandLastRaceColumn */ false, - /* don't adjust the timer's delay from the leaderboard; control it solely from the RaceTimesInfoProvider */ false); - } - - private void updateCompetitorsFilterContexts(CompetitorsFilterSets filterSets) { - for(FilterSet> filterSet: filterSets.getFilterSets()) { - for(Filter filter: filterSet.getFilters()) { - if(filter instanceof LeaderboardFilterContext) { - ((LeaderboardFilterContext) filter).setLeaderboardFetcher(leaderboardPanel); - } - if(filter instanceof SelectedRaceFilterContext) { - ((SelectedRaceFilterContext) filter).setSelectedRace(selectedRaceIdentifier); - } - if(filter instanceof CompetitorSelectionProviderFilterContext) { - ((CompetitorSelectionProviderFilterContext) filter).setCompetitorSelectionProvider(competitorSelectionModel); - } - } - } - } - - private void updateCompetitorsFilterControlState(CompetitorsFilterSets filterSets) { - String competitorsFilterTitle = stringMessages.competitorsFilter(); - FilterSet> activeFilterSet = filterSets.getActiveFilterSet(); - if(activeFilterSet != null) { - lastActiveCompetitorFilterSet = activeFilterSet; - } else { - if(filterSets.getFilterSets().size() == 0) { - lastActiveCompetitorFilterSet = null; - } - } - competitorsFilterCheckBox.setValue(activeFilterSet != null, false /* fireChangeValue*/); - - if(lastActiveCompetitorFilterSet != null) { - competitorsFilterCheckBox.setText(competitorsFilterTitle + " (" + lastActiveCompetitorFilterSet.getName() + ")"); - } else { - competitorsFilterCheckBox.setText(competitorsFilterTitle); - } - } - - private void addCompetitorsFilterControl(Panel parentPanel) { - String competitorsFilterTitle = stringMessages.competitorsFilter(); - competitorsFilterCheckBox = new CheckBox(competitorsFilterTitle); - competitorsFilterCheckBox.getElement().getStyle().setFloat(Style.Float.LEFT); - competitorsFilterCheckBox.setTitle(competitorsFilterTitle); - competitorsFilterCheckBox.addStyleName("raceBoardNavigation-innerElement"); - competitorsFilterCheckBox.addValueChangeHandler(new ValueChangeHandler() { - @Override - public void onValueChange(ValueChangeEvent newValue) { - boolean isChecked = competitorsFilterCheckBox.getValue(); - if (isChecked) { - if(lastActiveCompetitorFilterSet != null) { - competitorsFilterSets.setActiveFilterSet(lastActiveCompetitorFilterSet); - competitorSelectionModel.setCompetitorsFilterSet(competitorsFilterSets.getActiveFilterSet()); - updateCompetitorsFilterControlState(competitorsFilterSets); - } else { - showEditCompetitorsFiltersDialog(); - } - } else { - competitorsFilterSets.setActiveFilterSet(null); - competitorSelectionModel.setCompetitorsFilterSet(competitorsFilterSets.getActiveFilterSet()); - updateCompetitorsFilterControlState(competitorsFilterSets); - } - } - }); - parentPanel.add(competitorsFilterCheckBox); - Button filterButton = new Button(""); - filterButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - showEditCompetitorsFiltersDialog(); - } - }); - filterButton.addStyleName("raceBoardNavigation-filterButton"); - filterButton.getElement().getStyle().setFloat(Style.Float.LEFT); - filterButton.setTitle(competitorsFilterTitle); - parentPanel.add(filterButton); - } - - private void showEditCompetitorsFiltersDialog() { - CompetitorsFilterSetsDialog competitorsFilterSetsDialog = new CompetitorsFilterSetsDialog(competitorsFilterSets, - stringMessages, new DialogCallback() { - @Override - public void ok(final CompetitorsFilterSets newCompetitorsFilterSets) { - competitorsFilterSets.getFilterSets().clear(); - competitorsFilterSets.getFilterSets().addAll(newCompetitorsFilterSets.getFilterSets()); - competitorsFilterSets.setActiveFilterSet(newCompetitorsFilterSets.getActiveFilterSet()); - - updateCompetitorsFilterContexts(newCompetitorsFilterSets); - competitorSelectionModel.setCompetitorsFilterSet(newCompetitorsFilterSets.getActiveFilterSet()); - updateCompetitorsFilterControlState(newCompetitorsFilterSets); - storeCompetitorsFilterSets(newCompetitorsFilterSets); - } - - @Override - public void cancel() { - } - }); - - competitorsFilterSetsDialog .show(); - } - - private void insertSelectedCompetitorsFilter(CompetitorsFilterSets filterSet) { - // selected competitors filter - FilterSet> selectedCompetitorsFilterSet = - new FilterSet>(stringMessages.selectedCompetitors()); - selectedCompetitorsFilterSet.setEditable(false); - SelectedCompetitorsFilter selectedCompetitorsFilter = new SelectedCompetitorsFilter(); - selectedCompetitorsFilter.setCompetitorSelectionProvider(competitorSelectionModel); - selectedCompetitorsFilterSet.addFilter(selectedCompetitorsFilter); - - filterSet.addFilterSet(0, selectedCompetitorsFilterSet); - } - - private CompetitorsFilterSets loadCompetitorsFilterSets() { - CompetitorsFilterSets result = null; - Storage localStorage = Storage.getLocalStorageIfSupported(); - if(localStorage != null) { - try { - String jsonAsLocalStore = localStorage.getItem(LOCAL_STORAGE_COMPETITORS_FILTER_SETS_KEY); - if(jsonAsLocalStore != null && !jsonAsLocalStore.isEmpty()) { - CompetitorsFilterSetsJsonDeSerializer deserializer = new CompetitorsFilterSetsJsonDeSerializer(); - JSONValue value = JSONParser.parseStrict(jsonAsLocalStore); - if(value.isObject() != null) { - result = deserializer.deserialize((JSONObject) value); - } - } - } catch (Exception e) { - // exception during loading of competitor filters from local storage - } - } - return result; - } - - private void storeCompetitorsFilterSets(CompetitorsFilterSets newCompetitorsFilterSets) { - Storage localStorage = Storage.getLocalStorageIfSupported(); - if(localStorage != null) { - // delete old value - localStorage.removeItem(LOCAL_STORAGE_COMPETITORS_FILTER_SETS_KEY); - - // store the competiors filter set - CompetitorsFilterSetsJsonDeSerializer serializer = new CompetitorsFilterSetsJsonDeSerializer(); - JSONObject jsonObject = serializer.serialize(newCompetitorsFilterSets); - localStorage.setItem(LOCAL_STORAGE_COMPETITORS_FILTER_SETS_KEY, jsonObject.toString()); - } - } - - private void addComponentToNavigationMenu(final ComponentViewer componentViewer, - final Component component, boolean isToogleCheckboxEnabled) { - final CheckBox checkBox = new CheckBox(component.getLocalizedShortName()); - checkBox.getElement().getStyle().setFloat(Style.Float.LEFT); - - checkBox.setEnabled(isToogleCheckboxEnabled); - checkBox.setValue(component.isVisible()); - checkBox.setTitle(stringMessages.showHideComponent(component.getLocalizedShortName())); - checkBox.addStyleName("raceBoardNavigation-innerElement"); - - checkBox.addValueChangeHandler(new ValueChangeHandler() { - @Override - public void onValueChange(ValueChangeEvent newValue) { - // make the map invisible is this is not supported yet due to problems with disabling the center element - // of a DockPanel - if (component instanceof RaceMap) - return; - - boolean visible = checkBox.getValue(); - setComponentVisible(componentViewer, component, visible); - - if (visible && component instanceof TimeListener) { - // trigger the component to update its data - ((TimeListener) component).timeChanged(timer.getTime(), null); - } - } - }); - - componentControlsPanel.add(checkBox); - + competitorSearchTextBox.getCompetitorsFilterSets().setActiveFilterSet(filterSet); + break; + } + } + } + timePanel = new RaceTimePanel(timer, timeRangeWithZoomModel, stringMessages, raceTimesInfoProvider, + raceboardViewConfiguration.isCanReplayDuringLiveRaces()); + timeRangeWithZoomModel.addTimeZoomChangeListener(timePanel); + raceTimesInfoProvider.addRaceTimesInfoProviderListener(timePanel); + raceSelectionProvider.addRaceSelectionChangeListener(timePanel); + timePanel.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces()); + } + + /** + * @param event an optional event; may be null or else can be used to show some context information in + * the {@link GlobalNavigationPanel}. + */ + private void createOneScreenView(String leaderboardName, String leaderboardGroupName, EventDTO event, FlowPanel mainPanel, + boolean showMapControls, RaceMap raceMap) { + // create the default leaderboard and select the right race + raceTimesInfoProvider.addRaceTimesInfoProviderListener(raceMap); + raceMap.onRaceSelectionChange(Collections.singletonList(selectedRaceIdentifier)); + List> components = new ArrayList>(); + competitorChart = new MultiCompetitorRaceChart(sailingService, asyncActionsExecutor, competitorSelectionModel, raceSelectionProvider, + timer, timeRangeWithZoomModel, stringMessages, errorReporter, true, true, leaderboardGroupName, leaderboardName); + competitorChart.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces()); + competitorChart.getEntryWidget().setTitle(stringMessages.competitorCharts()); + competitorChart.setVisible(false); + components.add(competitorChart); + windChart = new WindChart(sailingService, raceSelectionProvider, timer, timeRangeWithZoomModel, new WindChartSettings(), + stringMessages, asyncActionsExecutor, errorReporter, /* compactChart */ true); + windChart.setVisible(false); + windChart.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces()); + windChart.getEntryWidget().setTitle(stringMessages.windChart()); + components.add(windChart); + MediaComponent mediaComponent = createMediaComponent(); + leaderboardAndMapViewer = new SideBySideComponentViewer(leaderboardPanel, raceMap, mediaComponent, components, stringMessages, this.user); + componentViewers.add(leaderboardAndMapViewer); + for (ComponentViewer componentViewer : componentViewers) { + mainPanel.add(componentViewer.getViewerWidget()); + } + boolean showLeaderboard = getConfiguration().isShowLeaderboard(); + if (Document.get().getClientWidth() <= 1024) { + showLeaderboard = false; + } + setLeaderboardVisible(showLeaderboard); + setWindChartVisible(getConfiguration().isShowWindChart()); + setCompetitorChartVisible(getConfiguration().isShowCompetitorsChart()); + // make sure to load leaderboard data for filtering to work + if (!showLeaderboard) { + leaderboardPanel.setVisible(true); + leaderboardPanel.setVisible(false); + } + } + + private MediaComponent createMediaComponent() { + boolean autoSelectMedia = getConfiguration().isAutoSelectMedia(); + String defaultMedia = getConfiguration().getDefaultMedia(); + final MediaComponent mediaComponent = new MediaComponent(selectedRaceIdentifier, raceTimesInfoProvider, timer, mediaService, stringMessages, errorReporter, userAgent, this.user, autoSelectMedia, defaultMedia); + timer.addPlayStateListener(mediaComponent); + timer.addTimeListener(mediaComponent); + mediaComponent.setVisible(false); + mediaService.getMediaTracksForRace(selectedRaceIdentifier, new AsyncCallback>() { + @Override + public void onSuccess(Collection result) { + mediaComponent.getMediaLibraryCallback().onSuccess(result); + if (mediaComponent.isPotentiallyPlayable(mediaComponent.getDefaultVideo())) { + if (leaderboardAndMapViewer != null) { + leaderboardAndMapViewer.getVideoControlButton().setVisible(true); + } + } + } + + @Override + public void onFailure(Throwable caught) { + mediaComponent.getMediaLibraryCallback().onFailure(caught); + } + }); // load media tracks + return mediaComponent; + } + + @SuppressWarnings("unused") + private void addSettingsMenuItem(MenuBar settingsMenu, final Component component) { if (component.hasSettings()) { - Button settingsButton = new Button(""); - settingsButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - new SettingsDialog(component, stringMessages).show(); - } - }); - settingsButton.addStyleName("raceBoardNavigation-settingsButton"); - settingsButton.getElement().getStyle().setFloat(Style.Float.LEFT); - settingsButton.setTitle(stringMessages.settingsForComponent(component.getLocalizedShortName())); - - componentControlsPanel.add(settingsButton); - } - } - - private void setComponentVisible(ComponentViewer componentViewer, Component component, boolean visible) { - component.setVisible(visible); - componentViewer.forceLayout(); - } - - /** - * Sets the collapsable panel for the leaderboard open or close, if in CASCADE view mode.
- * Displays or hides the leaderboard, if in ONESCREEN view mode.

- * - * The race board should be completely rendered before this method is called, or a few exceptions could be thrown. - * - * @param visible true if the leaderboard shall be open/visible - */ - public void setLeaderboardVisible(boolean visible) { - setComponentVisible(leaderboardAndMapViewer, leaderboardPanel, visible); - } - - /** - * Sets the collapsable panel for the wind chart open or close, if in CASCADE view mode.
- * Displays or hides the wind chart, if in ONESCREEN view mode.

- * - * The race board should be completely rendered before this method is called, or a few exceptions could be thrown. - * - * @param visible true if the wind chart shall be open/visible - */ - public void setWindChartVisible(boolean visible) { - setComponentVisible(leaderboardAndMapViewer, windChart, visible); - } - - /** - * Sets the collapsable panel for the competitor chart open or close, if in CASCADE view mode.
- * Displays or hides the competitor chart, if in ONESCREEN view mode.

- * - * The race board should be completely rendered before this method is called, or a few exceptions could be thrown. - * - * @param visible true if the competitor chart shall be open/visible - */ - public void setCompetitorChartVisible(boolean visible) { - setComponentVisible(leaderboardAndMapViewer, competitorChart, visible); - } - - public Panel getToolbarPanel() { - return toolbarPanel; - } - - public RaceTimePanel getTimePanel() { - return timePanel; - } - - protected SailingServiceAsync getSailingService() { - return sailingService; - } - - protected String getRaceBoardName() { - return raceBoardName; - } - - protected void setRaceBoardName(String raceBoardName) { - this.raceBoardName = raceBoardName; - } - - protected ErrorReporter getErrorReporter() { - return errorReporter; - } - - @Override - public void fillRegattas(List regattas) { - racesByIdentifier.clear(); - for (RegattaDTO regatta : regattas) { - for (RaceDTO race : regatta.races) { - if (race != null && race.getRaceIdentifier() != null) { - racesByIdentifier.put(race.getRaceIdentifier(), race); - } - } - } - } - - @Override - public void onRaceSelectionChange(List selectedRaces) { - } - - public RaceBoardViewConfiguration getConfiguration() { - return raceboardViewConfiguration; - } - - public FlowPanel getComponentControlsPanel() { - return componentControlsPanel; - } -} - + settingsMenu.addItem(component.getLocalizedShortName(), new Command() { + public void execute() { + new SettingsDialog(component, stringMessages).show(); + } + }); + } + } + + private LeaderboardPanel createLeaderboardPanel(String leaderboardName, String leaderboardGroupName, CompetitorFilterPanel competitorSearchTextBox) { + LeaderboardSettings leaderBoardSettings = LeaderboardSettingsFactory.getInstance() + .createNewSettingsForPlayMode(timer.getPlayMode(), + /* nameOfRaceToSort */ selectedRaceIdentifier.getRaceName(), + /* nameOfRaceColumnToShow */ null, /* nameOfRaceToShow */ selectedRaceIdentifier.getRaceName(), + new ExplicitRaceColumnSelectionWithPreselectedRace(selectedRaceIdentifier), /* showRegattaRank */ false); + return new LeaderboardPanel(sailingService, asyncActionsExecutor, leaderBoardSettings, selectedRaceIdentifier, + competitorSelectionModel, timer, leaderboardGroupName, leaderboardName, errorReporter, stringMessages, + userAgent, /* showRaceDetails */ true, competitorSearchTextBox, + /* showSelectionCheckbox */ true, raceTimesInfoProvider, /* autoExpandLastRaceColumn */ false, + /* don't adjust the timer's delay from the leaderboard; control it solely from the RaceTimesInfoProvider */ false, + /*autoApplyTopNFilter*/ false, /*showCompetitorFilterStatus*/ false); + } + + private void setComponentVisible(ComponentViewer componentViewer, Component component, boolean visible) { + component.setVisible(visible); + componentViewer.forceLayout(); + } + + /** + * Sets the collapsable panel for the leaderboard open or close, if in CASCADE view mode.
+ * Displays or hides the leaderboard, if in ONESCREEN view mode.

+ * + * The race board should be completely rendered before this method is called, or a few exceptions could be thrown. + * + * @param visible true if the leaderboard shall be open/visible + */ + public void setLeaderboardVisible(boolean visible) { + setComponentVisible(leaderboardAndMapViewer, leaderboardPanel, visible); + } + + /** + * Sets the collapsable panel for the wind chart open or close, if in CASCADE view mode.
+ * Displays or hides the wind chart, if in ONESCREEN view mode.

+ * + * The race board should be completely rendered before this method is called, or a few exceptions could be thrown. + * + * @param visible true if the wind chart shall be open/visible + */ + public void setWindChartVisible(boolean visible) { + setComponentVisible(leaderboardAndMapViewer, windChart, visible); + } + + /** + * Sets the collapsable panel for the competitor chart open or close, if in CASCADE view mode.
+ * Displays or hides the competitor chart, if in ONESCREEN view mode.

+ * + * The race board should be completely rendered before this method is called, or a few exceptions could be thrown. + * + * @param visible true if the competitor chart shall be open/visible + */ + public void setCompetitorChartVisible(boolean visible) { + setComponentVisible(leaderboardAndMapViewer, competitorChart, visible); + } + + public RaceTimePanel getTimePanel() { + return timePanel; + } + + protected SailingServiceAsync getSailingService() { + return sailingService; + } + + protected String getRaceBoardName() { + return raceBoardName; + } + + protected void setRaceBoardName(String raceBoardName) { + this.raceBoardName = raceBoardName; + } + + protected ErrorReporter getErrorReporter() { + return errorReporter; + } + + @Override + public void fillRegattas(List regattas) { + racesByIdentifier.clear(); + for (RegattaDTO regatta : regattas) { + for (RaceDTO race : regatta.races) { + if (race != null && race.getRaceIdentifier() != null) { + racesByIdentifier.put(race.getRaceIdentifier(), race); + } + } + } + } + + @Override + public void onRaceSelectionChange(List selectedRaces) { + } + + public RaceBoardViewConfiguration getConfiguration() { + return raceboardViewConfiguration; + } + + @Override + public void updatedLeaderboard(LeaderboardDTO leaderboard) { + leaderboardAndMapViewer.setLeftComponentWidth(leaderboardPanel.getContentPanel().getOffsetWidth()); + } + + @Override + public void currentRaceSelected(RaceIdentifier raceIdentifier, RaceColumnDTO raceColumn) { + if (!currentRaceHasBeenSelectedOnce) { + FleetDTO fleet = raceColumn.getFleet(raceIdentifier); + String seriesName = raceColumn.getSeriesName(); + if (LeaderboardNameConstants.DEFAULT_SERIES_NAME.equals(seriesName)) { + seriesName = ""; + } + String fleetForRaceName = fleet==null?"":fleet.getName(); + if (fleetForRaceName.equals(LeaderboardNameConstants.DEFAULT_FLEET_NAME)) { + fleetForRaceName = ""; + } else { + fleetForRaceName = " - "+fleetForRaceName; + } + Label raceNameLabel = new Label(stringMessages.race() + " " + raceColumn.getRaceColumnName()); + raceNameLabel.setStyleName("RaceName-Label"); + Label raceAdditionalInformationLabel = new Label(seriesName + fleetForRaceName); + raceAdditionalInformationLabel.setStyleName("RaceSeriesAndFleet-Label"); + raceInformationHeader.clear(); + raceInformationHeader.add(raceNameLabel); + raceInformationHeader.add(raceAdditionalInformationLabel); + Anchor regattaNameAnchor = new Anchor(raceIdentifier.getRegattaName()); + if (event != null) { + regattaNameAnchor.setHref("/gwt/Home.html#EventPlace:eventId="+event.id.toString()+"&navigationTab=Regattas&leaderboardName="+leaderboardName); + } else { + regattaNameAnchor.setHref("javascript:window.history.back();"); + } + regattaNameAnchor.setStyleName("RegattaName-Anchor"); + Label raceTimeLabel = computeRaceInformation(raceColumn, fleet); + raceTimeLabel.setStyleName("RaceTime-Label"); + regattaAndRaceTimeInformationHeader.clear(); + regattaAndRaceTimeInformationHeader.add(regattaNameAnchor); + regattaAndRaceTimeInformationHeader.add(raceTimeLabel); + currentRaceHasBeenSelectedOnce = true; + } + } + + private Label computeRaceInformation(RaceColumnDTO raceColumn, FleetDTO fleet) { + Label raceInformationLabel = new Label(); + raceInformationLabel.setStyleName("Race-Time-Label"); + DateTimeFormat formatter = DateTimeFormat.getFormat("E d/M/y"); + raceInformationLabel.setText(formatter.format(raceColumn.getStartDate(fleet))); + return raceInformationLabel; + } +} + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardViewConfiguration.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardViewConfiguration.java index 17847fb8852..b825a372d08 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardViewConfiguration.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardViewConfiguration.java @@ -12,8 +12,8 @@ public class RaceBoardViewConfiguration { private final String activeCompetitorsFilterSetName; private final boolean canReplayDuringLiveRaces; private final boolean autoSelectMedia; + private final String defaultMedia; private final boolean showViewStreamlets; - private boolean showNavigationPanel; public static final String PARAM_VIEW_MODE = "viewMode"; public static final String PARAM_VIEW_SHOW_LEADERBOARD = "viewShowLeaderboard"; @@ -25,16 +25,17 @@ public class RaceBoardViewConfiguration { public static final String PARAM_VIEW_COMPETITOR_FILTER = "viewCompetitorFilter"; public static final String PARAM_CAN_REPLAY_DURING_LIVE_RACES = "canReplayDuringLiveRaces"; public static final String PARAM_AUTOSELECT_MEDIA = "autoSelectMedia"; + public static final String PARAM_DEFAULT_MEDIA = "defaultMedia"; public RaceBoardViewConfiguration() { this(/* activeCompetitorsFilterSetName */ null, /* showLeaderboard */ true, /* showWindChart */ false, /* showCompetitorsChart */ false, /* showViewStreamlets */ false, - /* canReplayDuringLiveRaces */false, /* autoSelectMedia */false, /* showNavigationPanel */true); + /* canReplayDuringLiveRaces */false, /* autoSelectMedia */false, null); } public RaceBoardViewConfiguration(String activeCompetitorsFilterSetName, boolean showLeaderboard, boolean showWindChart, boolean showCompetitorsChart, boolean showViewStreamlets, boolean canReplayDuringLiveRaces, - boolean autoSelectMedia, boolean showNavigationPanel) { + boolean autoSelectMedia, String defaultMedia) { this.activeCompetitorsFilterSetName = activeCompetitorsFilterSetName; this.showLeaderboard = showLeaderboard; this.showWindChart = showWindChart; @@ -42,7 +43,7 @@ public class RaceBoardViewConfiguration { this.showViewStreamlets = showViewStreamlets; this.canReplayDuringLiveRaces = canReplayDuringLiveRaces; this.autoSelectMedia = autoSelectMedia; - this.showNavigationPanel = showNavigationPanel; + this.defaultMedia = defaultMedia; } public boolean isShowLeaderboard() { @@ -73,8 +74,8 @@ public class RaceBoardViewConfiguration { return autoSelectMedia; } - public boolean isShowNavigationPanel() { - return showNavigationPanel; + public String getDefaultMedia() { + return defaultMedia; } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SideBySideComponentViewer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SideBySideComponentViewer.java old mode 100644 new mode 100755 index 836db73b4af..09c103787c3 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SideBySideComponentViewer.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SideBySideComponentViewer.java @@ -1,89 +1,219 @@ package com.sap.sailing.gwt.ui.raceboard; +import java.util.ArrayList; import java.util.List; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.logical.shared.CloseEvent; +import com.google.gwt.event.logical.shared.CloseHandler; +import com.google.gwt.safehtml.shared.SafeHtml; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.AbsolutePanel; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.DockLayoutPanel.Direction; import com.google.gwt.user.client.ui.LayoutPanel; import com.google.gwt.user.client.ui.Panel; +import com.google.gwt.user.client.ui.PopupPanel; +import com.google.gwt.user.client.ui.RequiresResize; import com.google.gwt.user.client.ui.ScrollPanel; -import com.google.gwt.user.client.ui.SplitLayoutPanel; import com.google.gwt.user.client.ui.Widget; +import com.google.gwt.user.client.ui.WidgetCollection; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.media.MediaComponent; import com.sap.sailing.gwt.ui.client.shared.components.Component; import com.sap.sailing.gwt.ui.client.shared.components.ComponentViewer; +import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialog; +import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel; +import com.sap.sailing.gwt.ui.shared.UserDTO; +import com.sap.sse.common.Util.Pair; +import com.sap.sse.gwt.client.dialog.WindowBox; +/** + * Component Viewer that uses a {@link TouchSplitLayoutPanel} + * to display its components. + * + * TODO: Refactor to make sure it is really only performing operations + * that are related to view. Currently it is digging too deep into + * components and setting titles or even creating video buttons. + */ public class SideBySideComponentViewer implements ComponentViewer { - /** there is no easy replacement for the HorizontalSplitPanel available */ - private final Component leftComponent; + private static final int DEFAULT_SOUTH_SPLIT_PANEL_HEIGHT = 200; + private final int MIN_LEADERBOARD_WIDTH = 432; // works well for 505 and ESS + + /** + * Absolute Panel that informs its children about a resize + */ + class ResizableAbsolutePanel extends AbsolutePanel implements RequiresResize { + public void onResize() { + WidgetCollection children = getChildren(); + for (Widget widget : children) { + if (widget instanceof RequiresResize) { + ((RequiresResize)widget).onResize(); + } + } + } + } + + private final LeaderboardPanel leftComponent; private final Component rightComponent; private final List> components; private final ScrollPanel leftScrollPanel; - + private final Button videoControlButton; + private final StringMessages stringMessages; + private LayoutPanel mainPanel; - private SplitLayoutPanel splitLayoutPanel; + private TouchSplitLayoutPanel splitLayoutPanel; private int savedSplitPosition = -1; + private boolean layoutForLeftComponentForcedOnce = false; - public SideBySideComponentViewer(Component leftComponent, Component rightComponent, List> components) { - this.leftComponent = leftComponent; - this.rightComponent = rightComponent; + public SideBySideComponentViewer(final LeaderboardPanel leftComponentP, final Component rightComponentP, final MediaComponent mediaComponent, List> components, final StringMessages stringMessages, UserDTO user) { + this.stringMessages = stringMessages; + this.leftComponent = leftComponentP; + this.rightComponent = rightComponentP; this.components = components; - leftScrollPanel = new ScrollPanel(); - leftScrollPanel.add(leftComponent.getEntryWidget()); - mainPanel = new LayoutPanel(); - mainPanel.setSize("100%", "100%"); - splitLayoutPanel = new SplitLayoutPanel(); - mainPanel.add(splitLayoutPanel); - for (Component component : components) { - if (component.isVisible()) { - splitLayoutPanel.addSouth(component.getEntryWidget(), 200); - } + this.videoControlButton = createVideoControlButton(mediaComponent); + this.leftScrollPanel = new ScrollPanel(); + this.leftScrollPanel.add(leftComponentP.getEntryWidget()); + this.leftScrollPanel.setTitle(leftComponentP.getEntryWidget().getTitle()); + this.mainPanel = new LayoutPanel(); + this.mainPanel.setSize("100%", "100%"); + this.mainPanel.getElement().getStyle().setMarginTop(-12, Unit.PX); + this.mainPanel.setStyleName("SideBySideComponentViewer-MainPanel"); + this.splitLayoutPanel = new TouchSplitLayoutPanel(/* horizontal splitter width */ 3, /*vertical splitter height*/ 25); + this.mainPanel.add(splitLayoutPanel); + + // initialize components - they need to be added before other widgets to get the right width + initializeComponents(); + + // initialize the leaderboard component + savedSplitPosition = MIN_LEADERBOARD_WIDTH; + splitLayoutPanel.insert(leftScrollPanel, leftComponent, Direction.WEST, savedSplitPosition); + + // create a panel that will contain the horizontal toggle buttons + ResizableAbsolutePanel panelForMapAndHorizontalToggleButtons = new ResizableAbsolutePanel(); + panelForMapAndHorizontalToggleButtons.add(rightComponent.getEntryWidget()); + splitLayoutPanel.insert(panelForMapAndHorizontalToggleButtons, rightComponent, Direction.CENTER, 0); + + // add additional toggle buttons panel that currently only contains the video button + List>> additionalVerticalButtons = new ArrayList>>(); + additionalVerticalButtons.add(new Pair>(videoControlButton, mediaComponent)); + if (user != null) { + additionalVerticalButtons.add(new Pair>(mediaComponent.getMediaSelectionButton(), mediaComponent)); } - savedSplitPosition = 500; - splitLayoutPanel.addWest(leftScrollPanel, savedSplitPosition); - splitLayoutPanel.add(rightComponent.getEntryWidget()); + + // ensure that toggle buttons are positioned right + splitLayoutPanel.lastComponentHasBeenAdded(this, panelForMapAndHorizontalToggleButtons, additionalVerticalButtons); + } + + /** + * Create the video control button that shows or hides the video popup + */ + private Button createVideoControlButton(final MediaComponent mediaComponent) { + final Button videoControlButton = new Button(new SafeHtml() { + private static final long serialVersionUID = 8679639887708833213L; + @Override + public String asString() { + if (Document.get().getClientWidth() <= 1024) { + return " "; + } else { + return stringMessages.showVideoPopup(); + } + } + }); + videoControlButton.setTitle(stringMessages.showVideoPopup()); + Button closeButton = new Button(); + closeButton.setStyleName("VideoPopup-Close-Button"); + final WindowBox dialog = new WindowBox(stringMessages.videoComponentShortName(), stringMessages.videoComponentShortName(), mediaComponent.getEntryWidget(), null); + dialog.addCloseHandler(new CloseHandler() { + @Override + public void onClose(CloseEvent event) { + mediaComponent.setVisible(false); + if (Document.get().getClientWidth() > 1024) { + videoControlButton.setText(stringMessages.showVideoPopup()); + } + } + }); + videoControlButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + if (!dialog.isShowing()) { + if (mediaComponent.isPotentiallyPlayable(mediaComponent.getDefaultVideo())) { + mediaComponent.setVisible(true); + dialog.setPopupPosition(47, Document.get().getClientHeight()-355); + dialog.show(); + if (Document.get().getClientWidth() > 1024) { + videoControlButton.setText(stringMessages.hideVideoPopup()); + } + } else { + Window.alert("This race has no default video associated."); + } + } else { + mediaComponent.setVisible(false); + dialog.hide(); + if (Document.get().getClientWidth() > 1024) { + videoControlButton.setText(stringMessages.showVideoPopup()); + } + } + } + }); + // hide button initially as we defer showing the button to the asynchroneous + // task that gets launched by the media service to get video tracks + videoControlButton.setVisible(false); + return videoControlButton; + } + + private void initializeComponents() { + for (final Component component : components) { + splitLayoutPanel.insert(component.getEntryWidget(), component, Direction.SOUTH, 200); + } + } + + public void showSettingsDialog(Component component) { + if (component.hasSettings()) { + new SettingsDialog(component, stringMessages).show(); + } + } + + public Button getVideoControlButton() { + return videoControlButton; } + /** + * Called whenever the layout of {@link TouchSplitLayoutPanel} or other components + * change. Controls the visibility based on the {@link Component}s visibility. + * Each {@link Component} is in charge to not display any data or update itself + * when it is not visible. + */ public void forceLayout() { - if (leftComponent.isVisible() && !rightComponent.isVisible()) { - // the leaderboard is visible, but not the map - if (isWidgetInSplitPanel(rightComponent.getEntryWidget())) { - splitLayoutPanel.remove(rightComponent.getEntryWidget()); - } - } else if (!leftComponent.isVisible() && rightComponent.isVisible()) { + if (!leftComponent.isVisible() && rightComponent.isVisible()) { // the leaderboard is not visible, but the map is - if (isWidgetInSplitPanel(leftScrollPanel)) - splitLayoutPanel.remove(leftScrollPanel); + if (isWidgetInSplitPanel(leftScrollPanel)) { + if (leftScrollPanel.getOffsetWidth() > 0) { + savedSplitPosition = leftScrollPanel.getOffsetWidth(); + } + splitLayoutPanel.setWidgetVisibility(leftScrollPanel, leftComponent, /*hidden*/true, savedSplitPosition); + } } else if (leftComponent.isVisible() && rightComponent.isVisible()) { // the leaderboard and the map are visible - if (!isWidgetInSplitPanel(leftScrollPanel) || !isWidgetInSplitPanel(rightComponent.getEntryWidget())) { - if (!isWidgetInSplitPanel(leftScrollPanel)) - splitLayoutPanel.insertWest(leftScrollPanel, savedSplitPosition, rightComponent.getEntryWidget()); - else - splitLayoutPanel.insertEast(rightComponent.getEntryWidget(), savedSplitPosition, leftScrollPanel); - } + splitLayoutPanel.setWidgetVisibility(leftScrollPanel, leftComponent, /*hidden*/false, savedSplitPosition); } else if (!leftComponent.isVisible() && !rightComponent.isVisible()) { } for (Component component : components) { - boolean isComponentInSplitPanel = isWidgetInSplitPanel(component.getEntryWidget()); - if (component.isVisible()) { - if (!isComponentInSplitPanel) { - splitLayoutPanel.insertSouth(component.getEntryWidget(), 200, splitLayoutPanel.getWidget(0)); - } - } else { - if (isComponentInSplitPanel) - splitLayoutPanel.remove(component.getEntryWidget()); - } + final boolean isComponentVisible = component.isVisible(); + splitLayoutPanel.setWidgetVisibility(component.getEntryWidget(), component, !isComponentVisible, DEFAULT_SOUTH_SPLIT_PANEL_HEIGHT); } splitLayoutPanel.forceLayout(); } private boolean isWidgetInSplitPanel(Widget widget) { int widgetIndex = splitLayoutPanel.getWidgetIndex(widget); - if(widgetIndex < 0) - return false; - - return true; + return widgetIndex >= 0; } public Panel getViewerWidget() { @@ -97,4 +227,14 @@ public class SideBySideComponentViewer implements ComponentViewer { public String getViewerName() { return ""; } + + public void setLeftComponentWidth(int width) { + // TODO: The information provided by width is wrong + // need to find a way to get the correct information + if (!layoutForLeftComponentForcedOnce) { + savedSplitPosition = MIN_LEADERBOARD_WIDTH; + forceLayout(); + } + layoutForLeftComponentForcedOnce = true; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/TouchSplitLayoutPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/TouchSplitLayoutPanel.java new file mode 100755 index 00000000000..dd1a082b424 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/TouchSplitLayoutPanel.java @@ -0,0 +1,951 @@ +/* + * Copyright 2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.sap.sailing.gwt.ui.raceboard; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import com.google.gwt.core.client.Duration; +import com.google.gwt.core.client.JsArray; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Style.Overflow; +import com.google.gwt.dom.client.Style.Position; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.dom.client.Touch; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.AbsolutePanel; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.DockLayoutPanel; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.Panel; +import com.google.gwt.user.client.ui.RequiresResize; +import com.google.gwt.user.client.ui.Widget; +import com.google.gwt.user.client.ui.WidgetCollection; +import com.sap.sailing.gwt.ui.client.shared.components.Component; +import com.sap.sse.common.Util.Pair; +import com.sap.sse.gwt.client.player.TimeListener; + +/** + * A panel that adds user-positioned splitters between each of its child widgets. These splitters have draggers + * that are designed that way, that they work on touch enabled devices. Splitters can be hidden or shown. + * Each dragger has a toggle button associated that shows or hides the splitter it is associated to. + * + *

+ * Splitters have a {@link Widget} and a {@link Component} associated. The widget is applied to the split panel, + * the component is being used to determine the visibility. + *

+ * + *

+ * Make sure that you insert {@link Widget}s only by using {@link #insert(Widget, Component, Direction, double)}. + * The {@link Widget} provides the title for the toggle button and the {@link Component} provides the visibility. + *

+ * + *

+ * For each splitter the visibility is determined by checking its {@link Component}s visibility. If the associated + * component is not visible then the splitter will hide itself. + *

+ * + *

+ * This panel is used in the same way as {@link DockLayoutPanel}, except that its children's sizes are always specified + * in {@link Unit#PX} units, and each pair of child widgets has a splitter between them that the user can drag. + *

+ * + *

+ * This widget will only work in standards mode, which requires that the HTML page in which it is run have an + * explicit <!DOCTYPE> declaration. + *

+ * + *

CSS Style Rules

+ *
    + *
  • .gwt-SplitLayoutPanel { the panel itself }
  • + *
  • .gwt-SplitLayoutPanel .gwt-SplitLayoutPanel-HDragger { horizontal dragger }
  • + *
  • .gwt-SplitLayoutPanel .gwt-SplitLayoutPanel-VDragger { vertical dragger }
  • + *
+ * + * @author Simon Marcel Pamies, Axel Uhl + */ +public class TouchSplitLayoutPanel extends DockLayoutPanel { + + /** + * A dragger that is connected to a splitter. It captures + * mouse events and changes the size of the associated + * {@link Splitter} once the mouse has been released. It is designed + * in a way to support touch events. + * + * @author Simon Marcel Pamies, Axel Uhl + */ + public class Dragger extends Widget { + private final Splitter associatedSplitter; + + public Dragger(Splitter associatedSplitter) { + this.associatedSplitter = associatedSplitter; + this.setElement(Document.get().createDivElement()); + // needed to make sure this custom element gets events + this.sinkEvents(Event.ONMOUSEDOWN | Event.ONMOUSEUP | Event.ONMOUSEMOVE | Event.ONDBLCLICK + | Event.ONTOUCHSTART | Event.ONTOUCHEND | Event.ONTOUCHMOVE); + } + + @Override + public void onBrowserEvent(Event event) { + switch (event.getTypeInt()) { + case Event.ONMOUSEDOWN: + case Event.ONTOUCHSTART: + associatedSplitter.mouseDown = true; + + /* + * Resize glassElem to take up the entire scrollable window area, which is the greater of the scroll + * size and the client size. + */ + int width = Math.max(Window.getClientWidth(), Document.get().getScrollWidth()); + int height = Math.max(Window.getClientHeight(), Document.get().getScrollHeight()); + glassElem.getStyle().setHeight(height, Unit.PX); + glassElem.getStyle().setWidth(width, Unit.PX); + Document.get().getBody().appendChild(glassElem); + + associatedSplitter.offset = associatedSplitter.getEventPosition(event) + - associatedSplitter.getAbsolutePosition(); + Event.setCapture(getElement()); + event.preventDefault(); + break; + + case Event.ONMOUSEUP: + case Event.ONTOUCHEND: + associatedSplitter.mouseDown = false; + + glassElem.removeFromParent(); + + // Handle double-clicks. + // Fake them since the double-click event aren't fired. + if (associatedSplitter.toggleDisplayAllowed) { + double now = Duration.currentTimeMillis(); + if (now - associatedSplitter.lastClick < DOUBLE_CLICK_TIMEOUT) { + now = 0; + LayoutData layout = (LayoutData) associatedSplitter.target.getLayoutData(); + if (layout.size == 0) { + // Restore the old size. + associatedSplitter.setAssociatedWidgetSize(layout.oldSize, /* defer */true); + } else { + /* + * Collapse to size 0. We change the size instead of hiding the widget because hiding the + * widget can cause issues if the widget contains a flash component. + */ + layout.oldSize = layout.size; + associatedSplitter.setAssociatedWidgetSize(0, /* defer */true); + } + } + associatedSplitter.lastClick = now; + } + + Event.releaseCapture(getElement()); + event.preventDefault(); + break; + + case Event.ONMOUSEMOVE: + case Event.ONTOUCHMOVE: + if (associatedSplitter.mouseDown) { + int size; + if (associatedSplitter.reverse) { + size = associatedSplitter.getTargetPosition() + associatedSplitter.getTargetSize() + - associatedSplitter.getSplitterSize() - associatedSplitter.getEventPosition(event) + + associatedSplitter.offset; + } else { + size = associatedSplitter.getEventPosition(event) - associatedSplitter.getTargetPosition() + - associatedSplitter.offset; + } + ((LayoutData) associatedSplitter.target.getLayoutData()).hidden = false; + associatedSplitter.setAssociatedWidgetSize(size, /* defer */true); + event.preventDefault(); + } + break; + } + } + } + + /** + *

+ * Splitter that has a {@link Dragger} associated that + * controls the size of this widget. Each splitter also has a + * {@link Widget} associated that is under its direct control. + * A splitter can be hidden or visible depending on the associated + * {@link Component}s state. + *

+ * + *

+ * Splitters can also hold a panel that contains {@link Button}s that + * toggle all Splitters in the same direction. The idea is that if only + * one splitter is visible one wants to still display all other splitter + * toggle buttons. The logic to move that panel to the right splitter + * is contained in {@link TouchSplitLayoutPanel}. + *

+ * + * @author Simon Marcel Pamies, Axel Uhl + */ + abstract class Splitter extends AbsolutePanel { + protected final Widget target; + + private int offset; + private boolean mouseDown; + private ScheduledCommand layoutCommand; + + private final boolean reverse; + private int minSize; + private int snapClosedSize = -1; + private double centerSize, syncedCenterSize; + + private boolean toggleDisplayAllowed = false; + private double lastClick = 0; + + private final Component associatedComponent; + protected Button togglerButton; + private final Widget dragger; + + private boolean hasToggleButtonsAssociated; + private final Button splitterToggleButton; + private Panel toggleButtonsPanel; + + /** + * A splitter is an {@link AbsolutePanel} that contains a horizontal line and a dragger. + * + * @param reverse if set to true the size change during {@link Dragger} operations will + * be reversed. That means that when dragging to top for a {@link VSplitter} + * the splitters height will get less. + */ + public Splitter(Widget target, Component associatedComponent, boolean reverse) { + super(); + + getElement().getStyle().setOverflow(Overflow.VISIBLE); + + // add dragger - style will be set by implementing classes + dragger = new Dragger(this); + add(dragger); + + this.target = target; + this.reverse = reverse; + this.associatedComponent = associatedComponent; + this.hasToggleButtonsAssociated = false; + this.splitterToggleButton = new Button(target.getTitle()); + } + + public Component getAssociatedComponent() { + return associatedComponent; + } + + public Button getToggleButton() { + return this.splitterToggleButton; + } + + public Widget getTarget() { + return this.target; + } + + public void setDraggerVisible(boolean visible) { + this.dragger.setVisible(visible); + } + + public Widget getDragger() { + return this.dragger; + } + + public void addToogleButtons(Panel panel) { + add(panel); + toggleButtonsPanel = panel; + hasToggleButtonsAssociated = true; + } + + public Panel removeToggleButtons() { + toggleButtonsPanel.removeFromParent(); + hasToggleButtonsAssociated = false; + return toggleButtonsPanel; + } + + public Panel getToggleButtonsPanel() { + return toggleButtonsPanel; + } + + public boolean hasToggleButtonsAssociated() { + return this.hasToggleButtonsAssociated; + } + + public void setMinSize(int minSize) { + this.minSize = minSize; + LayoutData layout = (LayoutData) target.getLayoutData(); + + // Try resetting the associated widget's size, which will enforce the new + // minSize value. + setAssociatedWidgetSize((int) layout.size, /* defer */true); + } + + public void setSnapClosedSize(int snapClosedSize) { + this.snapClosedSize = snapClosedSize; + } + + public void setToggleDisplayAllowed(boolean allowed) { + this.toggleDisplayAllowed = allowed; + } + + protected void setSplitterSize(double splitterSize) { + LayoutData layout = (LayoutData)getLayoutData(); + layout.size = splitterSize; + } + + private double getMaxSize() { + // To avoid seeing stale center size values due to deferred layout + // updates, maintain our own copy up to date and resync when the + // DockLayoutPanel value changes. + double newCenterSize = getCenterSize(); + if (syncedCenterSize != newCenterSize) { + syncedCenterSize = newCenterSize; + centerSize = newCenterSize; + } + + return Math.max(((LayoutData) target.getLayoutData()).size + centerSize, 0); + } + + /** + * Sets the size of the associated {@link Widget} thus moving it down, up, left or right + * depending of the position. Informs the {@link DockLayoutPanel} about the change. + * + * @param defer if set to true then the layout change will be deferred until the browser + * event loop returns. + */ + private void setAssociatedWidgetSize(double size, boolean defer) { + double maxSize = getMaxSize(); + if (size > maxSize) { + size = maxSize; + } + + if (snapClosedSize > 0 && size < snapClosedSize) { + size = 0; + } else if (size < minSize) { + size = minSize; + } + + LayoutData layout = (LayoutData) target.getLayoutData(); + if (size == layout.size) { + return; + } + + // Adjust our view until the deferred layout gets scheduled. + centerSize += layout.size - size; + layout.size = size; + + // Defer actually updating the layout, so that if we receive many + // mouse events before layout/paint occurs, we'll only update once. + if (layoutCommand == null && defer) { + layoutCommand = new ScheduledCommand() { + @Override + public void execute() { + layoutCommand = null; + forceLayout(); + } + }; + Scheduler.get().scheduleDeferred(layoutCommand); + } else { + // force layout on DockPanel + forceLayout(); + } + if (getAssociatedComponent() instanceof RequiresResize) { + Scheduler.get().scheduleFinally(new ScheduledCommand() { + @Override + public void execute() { + ((RequiresResize) getAssociatedComponent()).onResize(); + } + }); + } + } + + protected abstract int getAbsolutePosition(); + protected abstract double getCenterSize(); + protected abstract int getEventPosition(Event event); + protected abstract int getTargetPosition(); + protected abstract int getTargetSize(); + protected abstract int getSplitterSize(); + + } + + /** + * Horizontal {@link Splitter} implementation that will only work when + * set to {@link Direction#EAST} or {@link Direction#WEST}. + * + * @author Simon Marcel Pamies, Axel Uhl + */ + class HSplitter extends Splitter { + public HSplitter(Widget target, Component associatedComponent, boolean reverse) { + super(target, associatedComponent, reverse); + addStyleName("SplitLayoutPanel-Divider-Horizontal"); + getDragger().getElement().getStyle().setPropertyPx("width", horizontalSplitterSize); + getDragger().setStyleName("gwt-SplitLayoutPanel-HDragger"); + } + + @Override + protected int getAbsolutePosition() { + return getAbsoluteLeft(); + } + + @Override + protected double getCenterSize() { + return getCenterWidth(); + } + + @Override + protected int getEventPosition(Event event) { + JsArray touches = event.getTouches(); + if (touches != null) { + Touch touch = touches.get(0).cast(); + if (touch != null) { + return touch.getClientX(); + } + } + return event.getClientX(); + } + + @Override + protected int getTargetPosition() { + return target.getAbsoluteLeft(); + } + + @Override + protected int getTargetSize() { + return target.getOffsetWidth(); + } + + @Override + protected int getSplitterSize() { + return horizontalSplitterSize; + } + + } + + /** + * Vertical Splitter that will only work when applied to + * {@link Direction#NORTH} or {@link Direction#SOUTH}. + * + * @author Simon Marcel Pamies, Axel Uhl + */ + class VSplitter extends Splitter { + public VSplitter(final Widget target, final Component associatedComponent, boolean reverse) { + super(target, associatedComponent, reverse); + addStyleName("SplitLayoutPanel-Divider-Vertical"); + getDragger().getElement().getStyle().setPropertyPx("height", verticalSplitterSize); + getDragger().setStyleName("gwt-SplitLayoutPanel-VDragger"); + } + + @Override + protected int getAbsolutePosition() { + return getAbsoluteTop(); + } + + @Override + protected double getCenterSize() { + return getCenterHeight(); + } + + @Override + protected int getEventPosition(Event event) { + JsArray touches = event.getTouches(); + if (touches != null) { + Touch touch = touches.get(0).cast(); + if (touch != null) { + return touch.getClientY(); + } + } + return event.getClientY(); + } + + @Override + protected int getTargetPosition() { + return target.getAbsoluteTop(); + } + + @Override + protected int getTargetSize() { + return target.getOffsetHeight(); + } + + @Override + protected int getSplitterSize() { + return verticalSplitterSize; + } + } + + private static final int DEFAULT_SPLITTER_SIZE = 8; + private static final int DOUBLE_CLICK_TIMEOUT = 500; + + /** + * The element that masks the screen so we can catch mouse events over iframes. + */ + private static Element glassElem = null; + + private final int horizontalSplitterSize; + private final int verticalSplitterSize; + + /** + * Construct a new {@link TouchSplitLayoutPanel} with the default splitter size of 8px. + */ + public TouchSplitLayoutPanel() { + this(DEFAULT_SPLITTER_SIZE, DEFAULT_SPLITTER_SIZE); + } + + /** + * Construct a new {@link TouchSplitLayoutPanel} with the specified splitter sizes in pixels. + * {@link Splitter}s can be of different sizes. + * + */ + public TouchSplitLayoutPanel(int horizonatalSplitterSize, int verticalSplitterSize) { + super(Unit.PX); + this.horizontalSplitterSize = horizonatalSplitterSize; + this.verticalSplitterSize = verticalSplitterSize; + setStyleName("gwt-SplitLayoutPanel"); + + if (glassElem == null) { + glassElem = Document.get().createDivElement(); + glassElem.getStyle().setPosition(Position.ABSOLUTE); + glassElem.getStyle().setTop(0, Unit.PX); + glassElem.getStyle().setLeft(0, Unit.PX); + glassElem.getStyle().setMargin(0, Unit.PX); + glassElem.getStyle().setPadding(0, Unit.PX); + glassElem.getStyle().setBorderWidth(0, Unit.PX); + + // We need to set the background color or mouse events will go right + // through the glassElem. If the SplitPanel contains an iframe, the + // iframe will capture the event and the slider will stop moving. + glassElem.getStyle().setProperty("background", "white"); + glassElem.getStyle().setOpacity(0.0); + } + } + + /** + * Return the size of the splitter in pixels. + * + * @return the splitter size + */ + public int getHorizontalSplitterSize() { + return horizontalSplitterSize; + } + + public int getVerticalSplitterSize() { + return verticalSplitterSize; + } + + @Override + /** + * This method should never be used directly as the implementation requires + * a Component to be available. + */ + public void insert(Widget child, Direction direction, double size, Widget before) { + this.insert(child, null, direction, size); + } + + /** + * Inserts a new child to this panel. When using this method make absolutely sure to + * call {@link #lastComponentHasBeenAdded(SideBySideComponentViewer, AbsolutePanel, List)} after + * all components have been added. + * + * @param child the child {@link Widget} that will be added + * @param associatedComponent the {@link Component} that will be used to determine visibility + * @param size the size of the widget. This will define the size of the split panel. + */ + public void insert(Widget child, Component associatedComponent, Direction direction, double size) { + super.insert(child, direction, size, null); + if (direction != Direction.CENTER) { + super.insert(child, direction, size, null); + insertSplitter(child, associatedComponent); + LayoutData widgetLayoutData = (LayoutData) child.getLayoutData(); + widgetLayoutData.oldSize = size; + } + } + + /** + *

+ * This method makes sure to layout the toggle buttons right. It needs to be the last call + * after all widgets have been added by using {@link #insert(Widget, Component, Direction, double)}. + * Be aware of the fact that one needs to provide the panel for the horizontal buttons. + *

+ * + *

+ * With this call one can provide additional vertical toggle buttons that won't be related + * to any {@link Splitter} but will be under control of the algorithm that makes sure that + * all toggle buttons are positioned right. + *

+ * + * @param cm the component that will be informed about layout changes by calling forceLayout(). + * @param panelForHorizontalButtons this panel assumes that the horizontal buttons will be positioned + * outside of the horizontal splitter panel. In most cases one will create such a panel + * on top of the widget that has position {@link Direction#CENTER}. + */ + public void lastComponentHasBeenAdded(final SideBySideComponentViewer cm, AbsolutePanel panelForHorizontalButtons, List>> additionalVerticalButtons) { + WidgetCollection splitterChildren = super.getChildren(); + HSplitter lastHorizontalSplitter = null; + VSplitter lastVerticalSplitter = null; + List allVerticalSplitters = new ArrayList(); + List allHorizontalSplitters = new ArrayList(); + for (Widget widget : splitterChildren) { + if (widget instanceof VSplitter) { + allVerticalSplitters.add((VSplitter) widget); + ((Splitter)widget).setVisible(true); + ((Splitter)widget).setDraggerVisible(false); + lastVerticalSplitter = (VSplitter)widget; + } else if (widget instanceof HSplitter) { + lastHorizontalSplitter = (HSplitter) widget; + allHorizontalSplitters.add((HSplitter) widget); + } + } + if (lastVerticalSplitter != null) { + Panel panel = createToggleButtonPanel(allVerticalSplitters, + "gwt-SplitLayoutPanel-NorthSouthToggleButton-Panel", "gwt-SplitLayoutPanel-NorthSouthToggleButton", + cm, additionalVerticalButtons); + lastVerticalSplitter.addToogleButtons(panel); + lastVerticalSplitter.setVisible(true); + lastVerticalSplitter.setDraggerVisible(false); + } + ensureVerticalToggleButtonPosition(); + if (lastHorizontalSplitter != null) { + Panel horizontalButtonsPanel = createToggleButtonPanel(allHorizontalSplitters, + "gwt-SplitLayoutPanel-EastToggleButton-Panel", "gwt-SplitLayoutPanel-EastToggleButton", cm, null); + panelForHorizontalButtons.add(horizontalButtonsPanel); + lastHorizontalSplitter.setVisible(false); + lastHorizontalSplitter.setDraggerVisible(false); + } + } + + /** + * Create toggle buttons and their click handlers. The click handlers will show or hide associated + * {@link Splitter}s including the {@link Component} and {@link Widget}. + */ + private Panel createToggleButtonPanel(List splitters, String panelStyleName, String buttonStyleName, + final SideBySideComponentViewer componentViewer, List>> additionalButtons) { + FlowPanel buttonFlowPanel = new FlowPanel(); + buttonFlowPanel.setStyleName(panelStyleName); + if (additionalButtons != null) { + for (Pair> buttonAndComponentPair : additionalButtons) { + Button button = buttonAndComponentPair.getA(); + button.setStyleName(buttonStyleName); + button.addStyleDependentName("Closed-"+buttonAndComponentPair.getB().getDependentCssClassName()); + if (Document.get().getClientWidth() <= 1024) { + button.addStyleDependentName("Small-"+buttonAndComponentPair.getB().getDependentCssClassName()); + } + buttonFlowPanel.add(button); + } + } + for (final Splitter splitter : splitters) { + final Component associatedComponent = splitter.getAssociatedComponent(); + final Button splitterTogglerButton = splitter.getToggleButton(); + splitterTogglerButton.setStyleName(buttonStyleName); + splitterTogglerButton.addStyleDependentName("Closed"); + splitterTogglerButton.addStyleDependentName("Closed-"+associatedComponent.getDependentCssClassName()); + splitterTogglerButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + boolean componentWasVisibleUntilNow = associatedComponent.isVisible(); + associatedComponent.setVisible(!componentWasVisibleUntilNow); + splitter.setVisible(!componentWasVisibleUntilNow); + splitter.setDraggerVisible(!componentWasVisibleUntilNow); + if (!componentWasVisibleUntilNow == true) { + if (associatedComponent instanceof TimeListener) { + ((TimeListener) associatedComponent).timeChanged(new Date(), null); + } + } + if (!componentWasVisibleUntilNow) { + splitterTogglerButton.removeStyleDependentName("Closed"); + splitterTogglerButton.removeStyleDependentName("Closed-"+associatedComponent.getDependentCssClassName()); + splitterTogglerButton.addStyleDependentName("Open"); + splitterTogglerButton.addStyleDependentName("Open-"+associatedComponent.getDependentCssClassName()); + } else { + splitterTogglerButton.removeStyleDependentName("Open"); + splitterTogglerButton.removeStyleDependentName("Open-"+associatedComponent.getDependentCssClassName()); + splitterTogglerButton.addStyleDependentName("Closed"); + splitterTogglerButton.addStyleDependentName("Closed-"+associatedComponent.getDependentCssClassName()); + } + ensureVerticalToggleButtonPosition(); + componentViewer.forceLayout(); + } + }); + buttonFlowPanel.add(splitterTogglerButton); + } + return buttonFlowPanel; + } + + /** + * This method ensures that the toggle buttons always are positioned on top of all split panels. This requires + * a bit of trickery as there are some edge cases (e.g. when no splitter is visible at all). + */ + private void ensureVerticalToggleButtonPosition() { + List splitterVisibleAndComponentVisible = new ArrayList(); + List splitterVisibleButComponentnNotVisible = new ArrayList(); + List splitterInvisible = new ArrayList(); + VSplitter splitterWithToggleButtons = null; + for (Widget widget : super.getChildren()) { + if (widget instanceof VSplitter) { + VSplitter vSplitter = (VSplitter)widget; + if (vSplitter.hasToggleButtonsAssociated()) { + splitterWithToggleButtons = vSplitter; + vSplitter.setVisible(true); + } + if (vSplitter.isVisible()) { + if (vSplitter.getTarget().isVisible()) { + splitterVisibleAndComponentVisible.add(vSplitter); + } else { + splitterVisibleButComponentnNotVisible.add(vSplitter); + } + } else { + splitterInvisible.add(vSplitter); + } + } + } + if (!splitterVisibleAndComponentVisible.isEmpty()) { + VSplitter lastSplitterVisibleAndComponentVisible = splitterVisibleAndComponentVisible.get(splitterVisibleAndComponentVisible.size()-1); + if (!lastSplitterVisibleAndComponentVisible.hasToggleButtonsAssociated()) { + Panel panel = splitterWithToggleButtons.removeToggleButtons(); + lastSplitterVisibleAndComponentVisible.addToogleButtons(panel); + lastSplitterVisibleAndComponentVisible.setSplitterSize(verticalSplitterSize); + } + } else { + if (!splitterVisibleButComponentnNotVisible.isEmpty()) { + // there is no splitter that has a component visible + // take the last one and add buttons there + VSplitter lastSplitterVisibleButComponentNotVisible = splitterVisibleButComponentnNotVisible.get(splitterVisibleButComponentnNotVisible.size()-1); + if (!lastSplitterVisibleButComponentNotVisible.hasToggleButtonsAssociated()) { + Panel panel = splitterWithToggleButtons.removeToggleButtons(); + lastSplitterVisibleButComponentNotVisible.addToogleButtons(panel); + lastSplitterVisibleButComponentNotVisible.setSplitterSize(verticalSplitterSize); + } + } + } + for (VSplitter vsplitterThatHasNoComponentVisible : splitterVisibleButComponentnNotVisible) { + if (!vsplitterThatHasNoComponentVisible.hasToggleButtonsAssociated()) { + vsplitterThatHasNoComponentVisible.setSplitterSize(0.0); + } else { + vsplitterThatHasNoComponentVisible.setSplitterSize((double)verticalSplitterSize); + } + } + for (VSplitter vSplitterWithComponentVisible : splitterVisibleAndComponentVisible) { + vSplitterWithComponentVisible.setSplitterSize((double)verticalSplitterSize); + } + if (!splitterVisibleAndComponentVisible.isEmpty()) { + for (VSplitter vSplitterInvisible : splitterInvisible) { + vSplitterInvisible.setSplitterSize(0.0); + } + } else { + // special case related to css issues: when all components are invisible + // then we need to at least show the splitter containers to get the right height + for (VSplitter vSplitterInvisible : splitterVisibleButComponentnNotVisible) { + if (vSplitterInvisible.hasToggleButtonsAssociated()) { + vSplitterInvisible.setSplitterSize(verticalSplitterSize*2); + } else { + vSplitterInvisible.setSplitterSize(0); + } + } + + } + forceLayout(); + } + + public boolean hidePanelContaining(Widget child) { + int idx = getWidgetIndex(child); + if (idx >= 0) { + return true; + } + return false; + } + + @Override + public boolean remove(Widget child) { + assert !(child instanceof Splitter) : "Splitters may not be directly removed"; + + int idx = getWidgetIndex(child); + if (super.remove(child)) { + // Remove the associated splitter, if any. + // Now that the widget is removed, idx is the index of the splitter. + if (idx < getWidgetCount()) { + // Call super.remove(), or we'll end up recursing. + super.remove(getWidget(idx)); + } + return true; + } + return false; + } + + @Override + public void setWidgetHidden(Widget widget, boolean hidden) { + super.setWidgetHidden(widget, hidden); + Splitter splitter = getAssociatedSplitter(widget); + if (splitter != null) { + // The splitter is null for the center element. + super.setWidgetHidden(splitter, hidden); + } + } + + /** + *

+ * Sets the widget visibility and makes sure to also hide or show associated + * {@link Splitter}s and their {@link Dragger}s. Also control the associated + * toggle button by setting the correct style name. + *

+ * + *

+ * As this method has no knowledge about the connection between a {@link Widget} and + * a {@link Component} the component needs to be provided. + *

+ * + * @param hidden set this to true if the panel should be hidden + * @param size the size of the panel. Always provide the visible size even when hidden is true. + */ + public void setWidgetVisibility(Widget widget, Component associatedComponentToWidget, + final boolean hidden, final int size) { + super.setWidgetHidden(widget, hidden); + final Splitter splitter = getAssociatedSplitter(widget); + if (splitter != null) { + LayoutData layoutData = (LayoutData) widget.getLayoutData(); + if (hidden) { + layoutData.oldSize = layoutData.size; + widget.setVisible(false); + if (associatedComponentToWidget != null) { + if (associatedComponentToWidget.isVisible()) { + associatedComponentToWidget.setVisible(false); + } + } + splitter.setAssociatedWidgetSize(0, /* defer */false); + } else { + widget.setVisible(true); + if (associatedComponentToWidget != null) { + if (!associatedComponentToWidget.isVisible()) { + associatedComponentToWidget.setVisible(true); + } + } + splitter.setAssociatedWidgetSize((layoutData.oldSize > 0 ? layoutData.oldSize : size), /* defer */false); + splitter.setDraggerVisible(!hidden); + splitter.setVisible(!hidden); + splitter.getToggleButton().removeStyleDependentName("Closed"); + splitter.getToggleButton().removeStyleDependentName("Closed-"+associatedComponentToWidget.getDependentCssClassName()); + splitter.getToggleButton().addStyleDependentName("Open"); + splitter.getToggleButton().addStyleDependentName("Open-"+associatedComponentToWidget.getDependentCssClassName()); + } + } + } + + private void assertIsChild2(Widget widget) { + assert (widget == null) || (widget.getParent() == this) : "The specified widget is not a child of this panel"; + } + + /** + * Sets the minimum allowable size for the given widget. + * + *

+ * Its associated splitter cannot be dragged to a position that would make it smaller than this size. This method + * has no effect for the {@link DockLayoutPanel.Direction#CENTER} widget. + *

+ * + * @param child + * the child whose minimum size will be set + * @param minSize + * the minimum size for this widget + */ + public void setWidgetMinSize(Widget child, int minSize) { + assertIsChild2(child); + Splitter splitter = getAssociatedSplitter(child); + // The splitter is null for the center element. + if (splitter != null) { + splitter.setMinSize(minSize); + } + } + + /** + * Sets a size below which the slider will close completely. This can be used in conjunction with + * {@link #setWidgetMinSize} to provide a speed-bump effect where the slider will stick to a preferred minimum size + * before closing completely. + * + *

+ * This method has no effect for the {@link DockLayoutPanel.Direction#CENTER} widget. + *

+ * + * @param child + * the child whose slider should snap closed + * @param snapClosedSize + * the width below which the widget will close or -1 to disable. + */ + public void setWidgetSnapClosedSize(Widget child, int snapClosedSize) { + assertIsChild2(child); + Splitter splitter = getAssociatedSplitter(child); + // The splitter is null for the center element. + if (splitter != null) { + splitter.setSnapClosedSize(snapClosedSize); + } + } + + /** + * Sets whether or not double-clicking on the splitter should toggle the display of the widget. + * + * @param child + * the child whose display toggling will be allowed or not. + * @param allowed + * whether or not display toggling is allowed for this widget + */ + public void setWidgetToggleDisplayAllowed(Widget child, boolean allowed) { + assertIsChild2(child); + Splitter splitter = getAssociatedSplitter(child); + // The splitter is null for the center element. + if (splitter != null) { + splitter.setToggleDisplayAllowed(allowed); + } + } + + public Splitter getAssociatedSplitter(Widget child) { + // If a widget has a next sibling, it must be a splitter, because the only + // widget that *isn't* followed by a splitter must be the CENTER, which has + // no associated splitter. + int idx = getWidgetIndex(child); + if (idx > -1 && idx < getWidgetCount() - 1) { + Widget splitter = getWidget(idx + 1); + assert splitter instanceof Splitter : "Expected child widget to be splitter"; + return (Splitter) splitter; + } + return null; + } + + private Splitter insertSplitter(Widget widget, Component associatedComponent) { + assert getChildren().size() > 0 : "Can't add a splitter before any children"; + + LayoutData layout = (LayoutData) widget.getLayoutData(); + Splitter splitter = null; + switch (getResolvedDirection(layout.direction)) { + case WEST: + splitter = new HSplitter(widget, associatedComponent, false); + break; + case EAST: + splitter = new HSplitter(widget, associatedComponent, true); + break; + case NORTH: + splitter = new VSplitter(widget, associatedComponent, false); + break; + case SOUTH: + splitter = new VSplitter(widget, associatedComponent, true); + break; + default: + assert false : "Unexpected direction"; + } + super.insert(splitter, layout.direction, splitter.getSplitterSize(), null); + // this is needed to make the Draggers visible + ((LayoutData) splitter.getLayoutData()).layer.getContainerElement().getStyle().setOverflow(Overflow.VISIBLE); + return splitter; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/regattaoverview/RegattaOverviewPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/regattaoverview/RegattaOverviewPanel.java index 1d97a7d21a3..491be9adc3a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/regattaoverview/RegattaOverviewPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/regattaoverview/RegattaOverviewPanel.java @@ -242,7 +242,7 @@ public class RegattaOverviewPanel extends SimplePanel { if (leaderboardsTabPanel != null) { if (showLeaderboard) { final CompetitorSelectionModel competitorSelectionProvider = new CompetitorSelectionModel(/* hasMultiSelection */ true); - final LeaderboardSettings leaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, /* autoExpandFirstRace */ false); + final LeaderboardSettings leaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true); sailingService.getLeaderboardsByEvent(eventDTO, new MarkedAsyncCallback>( new AsyncCallback>() { @Override @@ -297,7 +297,7 @@ public class RegattaOverviewPanel extends SimplePanel { } private void retrieveEvent() { - sailingService.getEventById(eventId, new MarkedAsyncCallback( + sailingService.getEventById(eventId, false, new MarkedAsyncCallback( new AsyncCallback() { @Override public void onFailure(Throwable cause) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/regattaoverview/RegattaRaceStatesComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/regattaoverview/RegattaRaceStatesComponent.java index 00dafbafea0..5cd5f50fa5a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/regattaoverview/RegattaRaceStatesComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/regattaoverview/RegattaRaceStatesComponent.java @@ -772,4 +772,9 @@ public class RegattaRaceStatesComponent extends SimplePanel implements Component return ""; } } + + @Override + public String getDependentCssClassName() { + return "regattaRaceStates"; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/Activator.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/Activator.java index 0f01985ec19..779b28fd7b3 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/Activator.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/Activator.java @@ -5,6 +5,12 @@ import org.osgi.framework.BundleContext; public class Activator implements BundleActivator { private static BundleContext context; + private SailingServiceImpl sailingServiceToStopWhenStopping; + private static Activator INSTANCE; + + public Activator() { + INSTANCE = this; + } @Override public void start(BundleContext context) throws Exception { @@ -13,10 +19,24 @@ public class Activator implements BundleActivator { @Override public void stop(BundleContext context) throws Exception { + if (sailingServiceToStopWhenStopping != null) { + sailingServiceToStopWhenStopping.stop(); + } + } + + public static Activator getInstance() { + if (INSTANCE == null) { + INSTANCE = new Activator(); + } + return INSTANCE; } public static BundleContext getDefault() { return context; } + public void setSailingService(SailingServiceImpl sailingServiceImpl) { + sailingServiceToStopWhenStopping = sailingServiceImpl; + } + } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/QuickRanksLiveCache.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/QuickRanksLiveCache.java new file mode 100755 index 00000000000..a0218d3c173 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/QuickRanksLiveCache.java @@ -0,0 +1,224 @@ +package com.sap.sailing.gwt.ui.server; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.WindSource; +import com.sap.sailing.domain.tracking.GPSFix; +import com.sap.sailing.domain.tracking.GPSFixMoving; +import com.sap.sailing.domain.tracking.MarkPassing; +import com.sap.sailing.domain.tracking.RaceChangeListener; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.tracking.TrackedRaceStatus; +import com.sap.sailing.domain.tracking.Wind; +import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener; +import com.sap.sailing.gwt.ui.shared.QuickRankDTO; +import com.sap.sailing.server.masterdata.DummyTrackedRace; +import com.sap.sailing.util.SmartFutureCache; +import com.sap.sailing.util.SmartFutureCache.AbstractCacheUpdater; +import com.sap.sailing.util.SmartFutureCache.UpdateInterval; + +/** + * Calculating the quick ranks for many clients for a live race is expensive and therefore benefits from consolidation + * in a single cache. This cache needs to listen for changes in the races for which it manages those {@link QuickRankDTO} objects + * and trigger a re-calculation. It uses a {@link SmartFutureCache} to store and update the cache entries. The keys of the + * {@link SmartFutureCache} are {@link RegattaAndRaceIdentifier}s. In order to properly evict cache entries when the race + * is no longer reachable, each {@link TrackedRace} is referenced by a {@link WeakReference} which has a queue associated. + * The cache runs a thread that fetches collected references from the queue and evicts the cache entries for the respective + * race identifiers. + * + * @author Axel Uhl (D043530) + * + */ +public class QuickRanksLiveCache extends AbstractRaceChangeListener { + private static final Logger logger = Logger.getLogger(QuickRanksLiveCache.class.getName()); + /** + * For each weak reference to a tracked race, remembers the race's {@link RegattaAndRaceIdentifier} which is then the key + * into the {@link SmartFutureCache} from which entries that are no longer referenced shall be removed. + */ + private final Map, RegattaAndRaceIdentifier> fromRefToRaceIdentifier; + + private final ReferenceQueue referencesToGarbageCollectedRaces; + + /** + * To reliably stop the thread we need a specific reference getting enqueued that we can recognize. Therefore, + * we create a dummy tracked race here and release the reference to it as soon as the {@link #stop} method is called. + * When this reference is later enqueued, the thread will terminate. + */ + private TrackedRace dummyTrackedRace = new DummyTrackedRace("Dummy for QuickRanksLiveCache stopping", /* raceId */ + "Dummy for QuickRanksLiveCache stopping"); + + private final WeakReference stopRef = new WeakReference(dummyTrackedRace); + + private final SmartFutureCache, CalculateOrPurge> cache; + + private final SailingServiceImpl service; + + private static class CalculateOrPurge implements UpdateInterval { + private static final CalculateOrPurge CALCULATE = new CalculateOrPurge(); + private static final CalculateOrPurge PURGE = new CalculateOrPurge(); + + @Override + public CalculateOrPurge join(CalculateOrPurge otherUpdateInterval) { + final CalculateOrPurge result; + if (this == PURGE || otherUpdateInterval == PURGE) { + result = PURGE; + } else { + result = CALCULATE; + } + return result; + } + } + + public QuickRanksLiveCache(final SailingServiceImpl service) { + this.service = service; + cache = new SmartFutureCache, CalculateOrPurge>( + new AbstractCacheUpdater, CalculateOrPurge>() { + @Override + public List computeCacheUpdate(RegattaAndRaceIdentifier key, + CalculateOrPurge updateInterval) throws Exception { + logger.fine("Computing cache update for live QuickRanks of race "+key); + final List result; + if (updateInterval == CalculateOrPurge.PURGE) { + result = null; + } else { + result = service.computeQuickRanks(key, /* time point; null means live */ null); + } + return result; + } + }, getClass().getName()); + fromRefToRaceIdentifier = new HashMap<>(); + new Thread("QuickRanksLiveCache garbage collector") { + @Override + public void run() { + Reference ref; + do { + try { + ref = referencesToGarbageCollectedRaces.remove(); + if (ref != stopRef) { + RegattaAndRaceIdentifier raceIdentifier = fromRefToRaceIdentifier.get(ref); + remove(raceIdentifier); + } + } catch (InterruptedException e) { + logger.log(Level.INFO, "Interrupted while waiting for reference in reference queue; quitting", e); + break; + } + } while (ref != stopRef); + logger.info("Received stop in QuickRanksLiveCache garbage collector; terminating"); + } + }.start(); + referencesToGarbageCollectedRaces = new ReferenceQueue<>(); + } + + private void remove(RegattaAndRaceIdentifier raceIdentifier) { + cache.remove(raceIdentifier); + } + + public void stop() { + dummyTrackedRace = null; // release the dummy tracked race, causing the stopRef to be enqueued + } + + public List get(RegattaAndRaceIdentifier raceIdentifier) { + List result = cache.get(raceIdentifier, false); + if (result == null) { + TrackedRace trackedRace = service.getExistingTrackedRace(raceIdentifier); + if (trackedRace != null) { + trackedRace.addListener(new Listener(raceIdentifier)); // register for all changes that may affect the quick ranks + cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE); + } + } + result = cache.get(raceIdentifier, /* wait for latest result */ true); + return result; + } + + private class Listener implements RaceChangeListener { + private final RegattaAndRaceIdentifier raceIdentifier; + + public Listener(RegattaAndRaceIdentifier raceIdentifier) { + this.raceIdentifier = raceIdentifier; + } + + @Override + public void waypointAdded(int zeroBasedIndex, Waypoint waypointThatGotAdded) { + cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE); + } + + @Override + public void waypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) { + cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE); + } + + @Override + public void competitorPositionChanged(GPSFixMoving fix, Competitor competitor) { + cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE); + } + + @Override + public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) { + cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE); + } + + @Override + public void markPassingReceived(Competitor competitor, Map oldMarkPassings, + Iterable markPassings) { + cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE); + } + + @Override + public void speedAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) { + cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE); + } + + @Override + public void windDataReceived(Wind wind, WindSource windSource) { + cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE); + } + + @Override + public void windDataRemoved(Wind wind, WindSource windSource) { + cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE); + } + + @Override + public void windAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) { + cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE); + } + + @Override + public void raceTimesChanged(TimePoint startOfTracking, TimePoint endOfTracking, TimePoint startTimeReceived) { + cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE); + } + + @Override + public void startOfRaceChanged(TimePoint oldStartOfRace, TimePoint newStartOfRace) { + cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE); + } + + @Override + public void delayToLiveChanged(long delayToLiveInMillis) { + cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE); + } + + @Override + public void windSourcesToExcludeChanged(Iterable windSourcesToExclude) { + cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE); + } + + @Override + public void statusChanged(TrackedRaceStatus newStatus) { + cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE); + } + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 9292a246115..026bb39a604 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -146,6 +146,7 @@ import com.sap.sailing.domain.common.dto.IncrementalOrFullLeaderboardDTO; import com.sap.sailing.domain.common.dto.LeaderboardDTO; import com.sap.sailing.domain.common.dto.PositionDTO; import com.sap.sailing.domain.common.dto.RaceColumnDTO; +import com.sap.sailing.domain.common.dto.RaceColumnDTOFactory; import com.sap.sailing.domain.common.dto.RaceColumnInSeriesDTO; import com.sap.sailing.domain.common.dto.RaceDTO; import com.sap.sailing.domain.common.dto.RaceLogTrackingInfoDTO; @@ -161,6 +162,7 @@ import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; import com.sap.sailing.domain.common.impl.PolarSheetGenerationResponseImpl; import com.sap.sailing.domain.common.impl.TimeRangeImpl; import com.sap.sailing.domain.common.impl.WindSourceImpl; +import com.sap.sailing.domain.common.media.MediaTrack; import com.sap.sailing.domain.common.racelog.FlagPole; import com.sap.sailing.domain.common.racelog.Flags; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; @@ -451,10 +453,15 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S private final SwissTimingReplayService swissTimingReplayService; - private final BundleContext context; + private final QuickRanksLiveCache quickRanksLiveCache; public SailingServiceImpl() { - context = Activator.getDefault(); + BundleContext context = Activator.getDefault(); + Activator activator = Activator.getInstance(); + if (context != null) { + activator.setSailingService(this); // register so this service is informed when the bundle shuts down + } + quickRanksLiveCache = new QuickRanksLiveCache(this); racingEventServiceTracker = createAndOpenRacingEventServiceTracker(context); replicationServiceTracker = createAndOpenReplicationServiceTracker(context); resultUrlRegistryServiceTracker = createAndOpenResultUrlRegistryServiceTracker(context); @@ -499,6 +506,14 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S /* keepAliveTime */ 60, TimeUnit.SECONDS, /* workQueue */ new LinkedBlockingQueue()); } + + /** + * Stops this service and frees its resources. In particular, caching services and threads owned by this service will be + * notified to stop their jobs. + */ + public void stop() { + quickRanksLiveCache.stop(); + } protected SwissTimingReplayService getSwissTimingReplayService(BundleContext context) { return createAndOpenSwissTimingReplayServiceTracker(context).getService().createSwissTimingReplayService(getSwissTimingAdapter().getSwissTimingDomainFactory()); @@ -776,7 +791,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S regattaDTO.series = convertToSeriesDTOs(regatta); BoatClass boatClass = regatta.getBoatClass(); if (boatClass != null) { - regattaDTO.boatClass = new BoatClassDTO(boatClass.getName(), boatClass.getHullLength().getMeters()); + regattaDTO.boatClass = new BoatClassDTO(boatClass.getName(), boatClass.getDisplayName(), boatClass.getHullLength().getMeters()); } if (regatta.getDefaultCourseArea() != null) { regattaDTO.defaultCourseAreaUuid = regatta.getDefaultCourseArea().getId(); @@ -808,13 +823,20 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S return result; } + private void fillRaceColumnDTO(RaceColumn raceColumn, RaceColumnDTO raceColumnDTO) { + raceColumnDTO.setName(raceColumn.getName()); + raceColumnDTO.setMedalRace(raceColumn.isMedalRace()); + raceColumnDTO.setExplicitFactor(raceColumn.getExplicitFactor()); + } + private List convertToRaceColumnDTOs(Iterable raceColumns) { List raceColumnDTOs = new ArrayList(); + RaceColumnDTOFactory columnFactory = RaceColumnDTOFactory.INSTANCE; for (RaceColumn raceColumn : raceColumns) { - RaceColumnDTO raceColumnDTO = new RaceColumnDTO(/* isValidInTotalScore not relevant here because no scores conveyed */ null); - raceColumnDTO.setName(raceColumn.getName()); - raceColumnDTO.setMedalRace(raceColumn.isMedalRace()); - raceColumnDTO.setExplicitFactor(raceColumn.getExplicitFactor()); + final RaceColumnDTO raceColumnDTO = columnFactory.createRaceColumnDTO(raceColumn.getName(), + raceColumn.isMedalRace(), raceColumn.getExplicitFactor(), + raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null, + raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null); raceColumnDTOs.add(raceColumnDTO); } return raceColumnDTOs; @@ -1712,21 +1734,24 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S private List getCourseSidelines(RegattaAndRaceIdentifier raceIdentifier, Date date) { List result = new ArrayList(); - if (date != null) { - TimePoint dateAsTimePoint = new MillisecondsTimePoint(date); - TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier); - if (trackedRace != null) { - for (Sideline sideline : trackedRace.getCourseSidelines()) { - List markDTOs = new ArrayList(); - for (Mark mark : sideline.getMarks()) { - GPSFixTrack track = trackedRace.getOrCreateTrack(mark); - Position positionAtDate = track.getEstimatedPosition(dateAsTimePoint, /* extrapolate */false); - if (positionAtDate != null) { - markDTOs.add(convertToMarkDTO(mark, positionAtDate)); - } + final TimePoint dateAsTimePoint; + TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier); + if (trackedRace != null) { + if (date == null) { + dateAsTimePoint = MillisecondsTimePoint.now().minus(trackedRace.getDelayToLiveInMillis()); + } else { + dateAsTimePoint = new MillisecondsTimePoint(date); + } + for (Sideline sideline : trackedRace.getCourseSidelines()) { + List markDTOs = new ArrayList(); + for (Mark mark : sideline.getMarks()) { + GPSFixTrack track = trackedRace.getOrCreateTrack(mark); + Position positionAtDate = track.getEstimatedPosition(dateAsTimePoint, /* extrapolate */false); + if (positionAtDate != null) { + markDTOs.add(convertToMarkDTO(mark, positionAtDate)); } - result.add(new SidelineDTO(sideline.getName(), markDTOs)); } + result.add(new SidelineDTO(sideline.getName(), markDTOs)); } } return result; @@ -1735,59 +1760,71 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S @Override public CoursePositionsDTO getCoursePositions(RegattaAndRaceIdentifier raceIdentifier, Date date) { CoursePositionsDTO result = new CoursePositionsDTO(); - if (date != null) { - TimePoint dateAsTimePoint = new MillisecondsTimePoint(date); - TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier); - if (trackedRace != null) { - result.marks = new HashSet(); - result.waypointPositions = new ArrayList(); - Set marks = new HashSet(); - Course course = trackedRace.getRace().getCourse(); - for (Waypoint waypoint : course.getWaypoints()) { - Position waypointPosition = trackedRace.getApproximatePosition(waypoint, dateAsTimePoint); - if (waypointPosition != null) { - result.waypointPositions.add(new PositionDTO(waypointPosition.getLatDeg(), waypointPosition.getLngDeg())); - } - for (Mark b : waypoint.getMarks()) { - marks.add(b); - } + TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier); + if (trackedRace != null) { + final TimePoint dateAsTimePoint; + if (date == null) { + dateAsTimePoint = MillisecondsTimePoint.now().minus(trackedRace.getDelayToLiveInMillis()); + } else { + dateAsTimePoint = new MillisecondsTimePoint(date); + } + result.marks = new HashSet(); + result.waypointPositions = new ArrayList(); + Set marks = new HashSet(); + Course course = trackedRace.getRace().getCourse(); + for (Waypoint waypoint : course.getWaypoints()) { + Position waypointPosition = trackedRace.getApproximatePosition(waypoint, dateAsTimePoint); + if (waypointPosition != null) { + result.waypointPositions.add(new PositionDTO(waypointPosition.getLatDeg(), waypointPosition + .getLngDeg())); } - for (Mark mark : marks) { - GPSFixTrack track = trackedRace.getOrCreateTrack(mark); - Position positionAtDate = track.getEstimatedPosition(dateAsTimePoint, /* extrapolate */false); - if (positionAtDate != null) { - result.marks.add(convertToMarkDTO(mark, positionAtDate)); - } + for (Mark b : waypoint.getMarks()) { + marks.add(b); } + } + for (Mark mark : marks) { + GPSFixTrack track = trackedRace.getOrCreateTrack(mark); + Position positionAtDate = track.getEstimatedPosition(dateAsTimePoint, /* extrapolate */false); + if (positionAtDate != null) { + result.marks.add(convertToMarkDTO(mark, positionAtDate)); + } + } - // set the positions of start and finish - Waypoint firstWaypoint = course.getFirstWaypoint(); - if (firstWaypoint != null && Util.size(firstWaypoint.getMarks())==2) { - final LineDetails markPositionDTOsAndLineAdvantage = trackedRace.getStartLine(dateAsTimePoint); - if (markPositionDTOsAndLineAdvantage != null) { - final List startMarkPositionDTOs = getMarkPositionDTOs(dateAsTimePoint, trackedRace, firstWaypoint); - result.startMarkPositions = startMarkPositionDTOs; - result.startLineLengthInMeters = markPositionDTOsAndLineAdvantage.getLength().getMeters(); - Bearing absoluteAngleDifferenceToTrueWind = markPositionDTOsAndLineAdvantage.getAbsoluteAngleDifferenceToTrueWind(); - result.startLineAngleToCombinedWind = absoluteAngleDifferenceToTrueWind==null?null:absoluteAngleDifferenceToTrueWind.getDegrees(); - result.startLineAdvantageousSide = markPositionDTOsAndLineAdvantage.getAdvantageousSideWhileApproachingLine(); - Distance advantage = markPositionDTOsAndLineAdvantage.getAdvantage(); - result.startLineAdvantageInMeters = advantage==null?null:advantage.getMeters(); - } - } - Waypoint lastWaypoint = course.getLastWaypoint(); - if (lastWaypoint != null && Util.size(lastWaypoint.getMarks())==2) { - final LineDetails markPositionDTOsAndLineAdvantage = trackedRace.getFinishLine(dateAsTimePoint); - if (markPositionDTOsAndLineAdvantage != null) { - final List finishMarkPositionDTOs = getMarkPositionDTOs(dateAsTimePoint, trackedRace, lastWaypoint); - result.finishMarkPositions = finishMarkPositionDTOs; - result.finishLineLengthInMeters = markPositionDTOsAndLineAdvantage.getLength().getMeters(); - Bearing absoluteAngleDifferenceToTrueWind = markPositionDTOsAndLineAdvantage.getAbsoluteAngleDifferenceToTrueWind(); - result.finishLineAngleToCombinedWind = absoluteAngleDifferenceToTrueWind==null?null:absoluteAngleDifferenceToTrueWind.getDegrees(); - result.finishLineAdvantageousSide = markPositionDTOsAndLineAdvantage.getAdvantageousSideWhileApproachingLine(); - Distance advantage = markPositionDTOsAndLineAdvantage.getAdvantage(); - result.finishLineAdvantageInMeters = advantage==null?null:advantage.getMeters(); - } + // set the positions of start and finish + Waypoint firstWaypoint = course.getFirstWaypoint(); + if (firstWaypoint != null && Util.size(firstWaypoint.getMarks()) == 2) { + final LineDetails markPositionDTOsAndLineAdvantage = trackedRace.getStartLine(dateAsTimePoint); + if (markPositionDTOsAndLineAdvantage != null) { + final List startMarkPositionDTOs = getMarkPositionDTOs(dateAsTimePoint, trackedRace, + firstWaypoint); + result.startMarkPositions = startMarkPositionDTOs; + result.startLineLengthInMeters = markPositionDTOsAndLineAdvantage.getLength().getMeters(); + Bearing absoluteAngleDifferenceToTrueWind = markPositionDTOsAndLineAdvantage + .getAbsoluteAngleDifferenceToTrueWind(); + result.startLineAngleToCombinedWind = absoluteAngleDifferenceToTrueWind == null ? null + : absoluteAngleDifferenceToTrueWind.getDegrees(); + result.startLineAdvantageousSide = markPositionDTOsAndLineAdvantage + .getAdvantageousSideWhileApproachingLine(); + Distance advantage = markPositionDTOsAndLineAdvantage.getAdvantage(); + result.startLineAdvantageInMeters = advantage == null ? null : advantage.getMeters(); + } + } + Waypoint lastWaypoint = course.getLastWaypoint(); + if (lastWaypoint != null && Util.size(lastWaypoint.getMarks()) == 2) { + final LineDetails markPositionDTOsAndLineAdvantage = trackedRace.getFinishLine(dateAsTimePoint); + if (markPositionDTOsAndLineAdvantage != null) { + final List finishMarkPositionDTOs = getMarkPositionDTOs(dateAsTimePoint, trackedRace, + lastWaypoint); + result.finishMarkPositions = finishMarkPositionDTOs; + result.finishLineLengthInMeters = markPositionDTOsAndLineAdvantage.getLength().getMeters(); + Bearing absoluteAngleDifferenceToTrueWind = markPositionDTOsAndLineAdvantage + .getAbsoluteAngleDifferenceToTrueWind(); + result.finishLineAngleToCombinedWind = absoluteAngleDifferenceToTrueWind == null ? null + : absoluteAngleDifferenceToTrueWind.getDegrees(); + result.finishLineAdvantageousSide = markPositionDTOsAndLineAdvantage + .getAdvantageousSideWhileApproachingLine(); + Distance advantage = markPositionDTOsAndLineAdvantage.getAdvantage(); + result.finishLineAdvantageInMeters = advantage == null ? null : advantage.getMeters(); } } } @@ -1901,24 +1938,44 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S return markPositionDTOs; } - private List getQuickRanks(RegattaAndRaceIdentifier raceIdentifier, Date date) throws NoWindException { + /** + * @param timePoint + * null means "live" and is then replaced by "now" minus the tracked race's + * {@link TrackedRace#getDelayToLiveInMillis() delay}. + */ + public List computeQuickRanks(RegattaAndRaceIdentifier raceIdentifier, TimePoint timePoint) + throws NoWindException { List result = new ArrayList(); - if (date != null) { - TimePoint dateAsTimePoint = new MillisecondsTimePoint(date); - TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier); - if (trackedRace != null) { - RaceDefinition race = trackedRace.getRace(); - int rank = 1; - for (Competitor competitor : trackedRace.getCompetitorsFromBestToWorst(dateAsTimePoint)) { - TrackedLegOfCompetitor trackedLeg = trackedRace.getTrackedLeg(competitor, dateAsTimePoint); - if (trackedLeg != null) { - int legNumberOneBased = race.getCourse().getLegs().indexOf(trackedLeg.getLeg()) + 1; - QuickRankDTO quickRankDTO = new QuickRankDTO(baseDomainFactory.convertToCompetitorDTO(competitor), rank, legNumberOneBased); - result.add(quickRankDTO); - } - rank++; - } + TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier); + if (trackedRace != null) { + final TimePoint actualTimePoint; + if (timePoint == null) { + actualTimePoint = MillisecondsTimePoint.now().minus(trackedRace.getDelayToLiveInMillis()); + } else { + actualTimePoint = timePoint; } + RaceDefinition race = trackedRace.getRace(); + int rank = 1; + for (Competitor competitor : trackedRace.getCompetitorsFromBestToWorst(actualTimePoint)) { + TrackedLegOfCompetitor trackedLeg = trackedRace.getTrackedLeg(competitor, actualTimePoint); + if (trackedLeg != null) { + int legNumberOneBased = race.getCourse().getLegs().indexOf(trackedLeg.getLeg()) + 1; + QuickRankDTO quickRankDTO = new QuickRankDTO(baseDomainFactory.convertToCompetitorDTO(competitor), + rank, legNumberOneBased); + result.add(quickRankDTO); + } + rank++; + } + } + return result; + } + + private List getQuickRanks(RegattaAndRaceIdentifier raceIdentifier, Date date) throws NoWindException { + final List result; + if (date == null) { + result = quickRanksLiveCache.get(raceIdentifier); + } else { + result = computeQuickRanks(raceIdentifier, date == null ? null : new MillisecondsTimePoint(date)); } return result; } @@ -2030,11 +2087,11 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S public StrippedLeaderboardDTO createFlexibleLeaderboard(String leaderboardName, String leaderboardDisplayName, int[] discardThresholds, ScoringSchemeType scoringSchemeType, UUID courseAreaId) { return createStrippedLeaderboardDTO(getService().apply(new CreateFlexibleLeaderboard(leaderboardName, leaderboardDisplayName, discardThresholds, - baseDomainFactory.createScoringScheme(scoringSchemeType), courseAreaId)), false); + baseDomainFactory.createScoringScheme(scoringSchemeType), courseAreaId)), false, false); } public StrippedLeaderboardDTO createRegattaLeaderboard(RegattaIdentifier regattaIdentifier, String leaderboardDisplayName, int[] discardThresholds) { - return createStrippedLeaderboardDTO(getService().apply(new CreateRegattaLeaderboard(regattaIdentifier, leaderboardDisplayName, discardThresholds)), false); + return createStrippedLeaderboardDTO(getService().apply(new CreateRegattaLeaderboard(regattaIdentifier, leaderboardDisplayName, discardThresholds)), false, false); } @Override @@ -2042,7 +2099,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S Map leaderboards = getService().getLeaderboards(); List results = new ArrayList(); for(Leaderboard leaderboard: leaderboards.values()) { - StrippedLeaderboardDTO dao = createStrippedLeaderboardDTO(leaderboard, false); + StrippedLeaderboardDTO dao = createStrippedLeaderboardDTO(leaderboard, false, false); results.add(dao); } return results; @@ -2054,7 +2111,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S StrippedLeaderboardDTO result = null; Leaderboard leaderboard = leaderboards.get(leaderboardName); if (leaderboard != null) { - result = createStrippedLeaderboardDTO(leaderboard, false); + result = createStrippedLeaderboardDTO(leaderboard, false, false); } return result; } @@ -2104,7 +2161,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S if (trackedRace != null) { RaceDefinition trackedRaceDef = trackedRace.getRace(); if (trackedRaceDef.getName().equals(race.getName())) { - results.add(createStrippedLeaderboardDTO(leaderboard, false)); + results.add(createStrippedLeaderboardDTO(leaderboard, false, false)); break; } } @@ -2123,13 +2180,14 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S * is used for {@link LeaderboardDTO#competitorDisplayNames}.
* If withGeoLocationData is true the geographical location of all races will be determined. */ - private StrippedLeaderboardDTO createStrippedLeaderboardDTO(Leaderboard leaderboard, boolean withGeoLocationData) { + private StrippedLeaderboardDTO createStrippedLeaderboardDTO(Leaderboard leaderboard, boolean withGeoLocationData, boolean withStatisticalData) { StrippedLeaderboardDTO leaderboardDTO = new StrippedLeaderboardDTO(); TimePoint startOfLatestRace = null; Long delayToLiveInMillisForLatestRace = null; leaderboardDTO.name = leaderboard.getName(); leaderboardDTO.displayName = leaderboard.getDisplayName(); leaderboardDTO.competitorDisplayNames = new HashMap(); + leaderboardDTO.competitorsCount = Util.size(leaderboard.getCompetitors()); if (leaderboard instanceof RegattaLeaderboard) { RegattaLeaderboard regattaLeaderboard = (RegattaLeaderboard) leaderboard; Regatta regatta = regattaLeaderboard.getRegatta(); @@ -2162,11 +2220,16 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } raceIdentifier = new RegattaNameAndRaceName(trackedRace.getTrackedRegatta().getRegatta().getName(), trackedRace.getRace().getName()); raceDTO = baseDomainFactory.createRaceDTO(getService(), withGeoLocationData, raceIdentifier, trackedRace); + if(withStatisticalData) { + Collection mediaTracksForRace = getService().getMediaTracksForRace(raceIdentifier); + raceDTO.trackedRaceStatistics = baseDomainFactory.createTrackedRaceStatisticsDTO(trackedRace, leaderboard, raceColumn, fleet, mediaTracksForRace); + } } final FleetDTO fleetDTO = baseDomainFactory.convertToFleetDTO(fleet); - RaceColumnDTO raceColumnDTO = leaderboardDTO.addRace(raceColumn.getName(), raceColumn.getExplicitFactor(), - raceColumn.getFactor(), fleetDTO, raceColumn.isMedalRace(), raceIdentifier, raceDTO); - + RaceColumnDTO raceColumnDTO = leaderboardDTO.addRace(raceColumn.getName(), raceColumn.getExplicitFactor(), raceColumn.getFactor(), + raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null, + raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null, + fleetDTO, raceColumn.isMedalRace(), raceIdentifier, raceDTO); RaceLog raceLog = raceColumn.getRaceLog(fleet); RaceLogTrackingState raceLogTrackingState = raceLog == null ? RaceLogTrackingState.NOT_A_RACELOG_TRACKED_RACE : new RaceLogTrackingStateAnalyzer(raceLog).analyze(); @@ -2183,7 +2246,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S @Override public StrippedLeaderboardDTO updateLeaderboard(String leaderboardName, String newLeaderboardName, String newLeaderboardDisplayName, int[] newDiscardingThresholds, UUID newCourseAreaId) { Leaderboard updatedLeaderboard = getService().apply(new UpdateLeaderboard(leaderboardName, newLeaderboardName, newLeaderboardDisplayName, newDiscardingThresholds, newCourseAreaId)); - return createStrippedLeaderboardDTO(updatedLeaderboard, false); + return createStrippedLeaderboardDTO(updatedLeaderboard, false, false); } @Override @@ -2730,17 +2793,17 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public List> getLeaderboardsNamesOfMetaleaderboard(String metaLeaderboardName) { + public List> getLeaderboardsNamesOfMetaLeaderboard(String metaLeaderboardName) { Leaderboard leaderboard = getService().getLeaderboardByName(metaLeaderboardName); if (leaderboard == null) { throw new IllegalArgumentException("Couldn't find leaderboard named "+metaLeaderboardName); } - if(!(leaderboard instanceof MetaLeaderboard)) { + if (!(leaderboard instanceof MetaLeaderboard)) { throw new IllegalArgumentException("The leaderboard "+metaLeaderboardName + " is not a metaleaderboard"); } List> result = new ArrayList>(); MetaLeaderboard metaLeaderboard = (MetaLeaderboard) leaderboard; - for(Leaderboard containedLeaderboard: metaLeaderboard.getLeaderboards()) { + for (Leaderboard containedLeaderboard: metaLeaderboard.getLeaderboards()) { result.add(new com.sap.sse.common.Util.Pair(containedLeaderboard.getName(), containedLeaderboard.getDisplayName() != null ? containedLeaderboard.getDisplayName() : containedLeaderboard.getName())); } @@ -2913,7 +2976,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S Map leaderboardGroups = getService().getLeaderboardGroups(); for (LeaderboardGroup leaderboardGroup : leaderboardGroups.values()) { - leaderboardGroupDTOs.add(convertToLeaderboardGroupDTO(leaderboardGroup, withGeoLocationData)); + leaderboardGroupDTOs.add(convertToLeaderboardGroupDTO(leaderboardGroup, withGeoLocationData, false)); } return leaderboardGroupDTOs; @@ -2921,14 +2984,15 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S @Override public LeaderboardGroupDTO getLeaderboardGroupByName(String groupName, boolean withGeoLocationData) { - return convertToLeaderboardGroupDTO(getService().getLeaderboardGroupByName(groupName), withGeoLocationData); + return convertToLeaderboardGroupDTO(getService().getLeaderboardGroupByName(groupName), withGeoLocationData, false); } - private LeaderboardGroupDTO convertToLeaderboardGroupDTO(LeaderboardGroup leaderboardGroup, boolean withGeoLocationData) { - LeaderboardGroupDTO groupDTO = new LeaderboardGroupDTO(leaderboardGroup.getId(), leaderboardGroup.getName(), leaderboardGroup.getDescription()); + private LeaderboardGroupDTO convertToLeaderboardGroupDTO(LeaderboardGroup leaderboardGroup, boolean withGeoLocationData, boolean withStatisticalData) { + LeaderboardGroupDTO groupDTO = new LeaderboardGroupDTO(leaderboardGroup.getId(), leaderboardGroup.getName(), + leaderboardGroup.getDisplayName(), leaderboardGroup.getDescription()); groupDTO.displayLeaderboardsInReverseOrder = leaderboardGroup.isDisplayGroupsInReverseOrder(); for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) { - groupDTO.leaderboards.add(createStrippedLeaderboardDTO(leaderboard, withGeoLocationData)); + groupDTO.leaderboards.add(createStrippedLeaderboardDTO(leaderboard, withGeoLocationData, withStatisticalData)); } Leaderboard overallLeaderboard = leaderboardGroup.getOverallLeaderboard(); if (overallLeaderboard != null) { @@ -2959,19 +3023,20 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public LeaderboardGroupDTO createLeaderboardGroup(String groupName, String description, boolean displayGroupsInReverseOrder, + public LeaderboardGroupDTO createLeaderboardGroup(String groupName, String description, String displayName, + boolean displayGroupsInReverseOrder, int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType) { - CreateLeaderboardGroup createLeaderboardGroupOp = new CreateLeaderboardGroup(groupName, description, displayGroupsInReverseOrder, - new ArrayList(), overallLeaderboardDiscardThresholds, overallLeaderboardScoringSchemeType); - return convertToLeaderboardGroupDTO(getService().apply(createLeaderboardGroupOp), false); + CreateLeaderboardGroup createLeaderboardGroupOp = new CreateLeaderboardGroup(groupName, description, displayName, + displayGroupsInReverseOrder, new ArrayList(), overallLeaderboardDiscardThresholds, overallLeaderboardScoringSchemeType); + return convertToLeaderboardGroupDTO(getService().apply(createLeaderboardGroupOp), false, false); } @Override - public void updateLeaderboardGroup(String oldName, String newName, String description, List leaderboardNames, - int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType) { + public void updateLeaderboardGroup(String oldName, String newName, String newDescription, String newDisplayName, + List leaderboardNames, int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType) { getService().apply( - new UpdateLeaderboardGroup(oldName, newName, description, leaderboardNames, - overallLeaderboardDiscardThresholds, overallLeaderboardScoringSchemeType)); + new UpdateLeaderboardGroup(oldName, newName, newDescription, newDisplayName, + leaderboardNames, overallLeaderboardDiscardThresholds, overallLeaderboardScoringSchemeType)); } @Override @@ -3014,8 +3079,9 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S String requestBaseURL = getRequestBaseURL().toString(); List result = new ArrayList(); for (Event event : getService().getAllEvents()) { - EventDTO eventDTO = convertToEventDTO(event); + EventDTO eventDTO = convertToEventDTO(event, false); eventDTO.setBaseURL(requestBaseURL); + eventDTO.setIsOnRemoteServer(false); result.add(eventDTO); } return result; @@ -3025,7 +3091,9 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S public List getPublicEventsOfAllSailingServers() throws MalformedURLException { List result = new ArrayList<>(); for (EventDTO localEvent : getEvents()) { - result.add(localEvent); + if (localEvent.isPublic) { + result.add(localEvent); + } } for (Entry, Exception>> serverRefAndEventsOrException : getService().getPublicEventsOfAllSailingServers().entrySet()) { @@ -3037,6 +3105,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S for (EventBase remoteEvent : remoteEvents) { EventBaseDTO remoteEventDTO = convertToEventDTO(remoteEvent); remoteEventDTO.setBaseURL(baseURL); + remoteEventDTO.setIsOnRemoteServer(true); result.add(remoteEventDTO); } } @@ -3104,7 +3173,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S new UpdateEvent(eventId, eventName, eventDescription, startTimePoint, endTimePoint, venue.getName(), isPublic, leaderboardGroupIds, logoImageURL, officialWebsiteURL, imageURLs, videoURLs, sponsorimagieURLs)); - return getEventById(eventId); + return getEventById(eventId, false); } /** @@ -3121,18 +3190,22 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public EventDTO createEvent(String eventName, Date startDate, Date endDate, String venue, boolean isPublic, List courseAreaNames, - Iterable imageURLs, Iterable videoURLs) throws MalformedURLException { + public EventDTO createEvent(String eventName, String eventDescription, Date startDate, Date endDate, String venue, + boolean isPublic, List courseAreaNames, Iterable imageURLs, + Iterable videoURLs, Iterable sponsorImageURLs, String logoImageURLAsString, String officialWebsiteURLAsString) + throws MalformedURLException { UUID eventUuid = UUID.randomUUID(); TimePoint startTimePoint = startDate != null ? new MillisecondsTimePoint(startDate) : null; TimePoint endTimePoint = endDate != null ? new MillisecondsTimePoint(endDate) : null; getService().apply( - new CreateEvent(eventName, startTimePoint, endTimePoint, venue, isPublic, eventUuid, createURLsFromStrings(imageURLs), - createURLsFromStrings(videoURLs))); + new CreateEvent(eventName, eventDescription, startTimePoint, endTimePoint, venue, isPublic, eventUuid, + createURLsFromStrings(imageURLs), createURLsFromStrings(videoURLs), + createURLsFromStrings(sponsorImageURLs), + logoImageURLAsString == null ? null : new URL(logoImageURLAsString), officialWebsiteURLAsString == null ? null : new URL(officialWebsiteURLAsString))); for (String courseAreaName : courseAreaNames) { createCourseArea(eventUuid, courseAreaName); } - return getEventById(eventUuid); + return getEventById(eventUuid, false); } @Override @@ -3159,23 +3232,11 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public EventDTO getEventByName(String eventName) { - EventDTO result = null; - for (Event event : getService().getAllEvents()) { - if(event.getName().equals(eventName)) { - result = convertToEventDTO(event); - break; - } - } - return result; - } - - @Override - public EventDTO getEventById(UUID id) { + public EventDTO getEventById(UUID id, boolean withStatisticalData) { EventDTO result = null; Event event = getService().getEvent(id); if (event != null) { - result = convertToEventDTO(event); + result = convertToEventDTO(event, withStatisticalData); } return result; } @@ -3199,7 +3260,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S private LeaderboardGroupBaseDTO convertToLeaderboardGroupBaseDTO(LeaderboardGroupBase leaderboardGroupBase) { return new LeaderboardGroupBaseDTO(leaderboardGroupBase.getId(), leaderboardGroupBase.getName(), - leaderboardGroupBase.getDescription(), leaderboardGroupBase.hasOverallLeaderboard()); + leaderboardGroupBase.getDescription(), leaderboardGroupBase.getDisplayName(), + leaderboardGroupBase.hasOverallLeaderboard()); } private void copyEventBaseFieldsToDTO(EventBase event, EventBaseDTO eventDTO) { @@ -3211,19 +3273,34 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S eventDTO.id = (UUID) event.getId(); eventDTO.setDescription(event.getDescription()); eventDTO.setOfficialWebsiteURL(event.getOfficialWebsiteURL() != null ? event.getOfficialWebsiteURL().toString() : null); - eventDTO.setLogoImageURL(event.getLogoImageURL() != null ? event.getLogoImageURL().toString() : null); + if (event.getLogoImageURL() == null) { + eventDTO.setLogoImageURL(null); + } else { + eventDTO.setLogoImageURL(event.getLogoImageURL().toString()); + setImageSize(event, eventDTO, event.getLogoImageURL()); + } for(URL url: event.getSponsorImageURLs()) { eventDTO.addSponsorImageURL(url.toString()); + setImageSize(event, eventDTO, url); } for(URL url: event.getImageURLs()) { eventDTO.addImageURL(url.toString()); + setImageSize(event, eventDTO, url); } for(URL url: event.getVideoURLs()) { eventDTO.addVideoURL(url.toString()); } } + + private void setImageSize(EventBase event, EventBaseDTO eventDTO, URL imageURL) { + try { + eventDTO.setImageSize(imageURL.toString(), event.getImageSize(imageURL)); + } catch (InterruptedException | ExecutionException e) { + logger.log(Level.FINE, "Was unable to obtain image size for "+imageURL+" earlier.", e); + } + } - private EventDTO convertToEventDTO(Event event) { + private EventDTO convertToEventDTO(Event event, boolean withStatisticalData) { EventDTO eventDTO = new EventDTO(event.getName()); copyEventBaseFieldsToDTO(event, eventDTO); eventDTO.regattas = new ArrayList(); @@ -3239,16 +3316,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S eventDTO.venue.getCourseAreas().add(courseAreaDTO); } for (LeaderboardGroup lg : event.getLeaderboardGroups()) { - eventDTO.addLeaderboardGroup(convertToLeaderboardGroupDTO(lg, /* withGeoLocationData */false)); - } - for (URL imageURL : event.getImageURLs()) { - eventDTO.addImageURL(imageURL.toString()); - } - for (URL videoURL : event.getVideoURLs()) { - eventDTO.addVideoURL(videoURL.toString()); - } - for (URL sponsorImageURL : event.getSponsorImageURLs()) { - eventDTO.addSponsorImageURL(sponsorImageURL.toString()); + eventDTO.addLeaderboardGroup(convertToLeaderboardGroupDTO(lg, /* withGeoLocationData */false, withStatisticalData)); } return eventDTO; } @@ -3259,6 +3327,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S return courseAreaDTO; } + /** for backward compatibility with the regatta overview */ @Override public List getRegattaStructureForEvent(UUID eventId) { List raceGroups = new ArrayList(); @@ -3305,6 +3374,63 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S return raceGroups; } + /** the replacement service for getRegattaStructureForEvent() */ + @Override + public List getRegattaStructureOfEvent(UUID eventId) { + List raceGroups = new ArrayList(); + Event event = getService().getEvent(eventId); + Map leaderboardWithLeaderboardGroups = new HashMap(); + for(LeaderboardGroup leaderboardGroup: event.getLeaderboardGroups()) { + for(Leaderboard leaderboard: leaderboardGroup.getLeaderboards()) { + leaderboardWithLeaderboardGroups.put(leaderboard, leaderboardGroup); + } + } + if (event != null) { + for(LeaderboardGroup leaderboardGroup: event.getLeaderboardGroups()) { + for(Leaderboard leaderboard: leaderboardGroup.getLeaderboards()) { + RaceGroupDTO raceGroup = new RaceGroupDTO(leaderboard.getName()); + for (CourseArea courseArea : event.getVenue().getCourseAreas()) { + if (leaderboard.getDefaultCourseArea() != null && leaderboard.getDefaultCourseArea() == courseArea) { + raceGroup.courseAreaIdAsString = courseArea.getId().toString(); + break; + } + } + raceGroup.displayName = getRegattaNameFromLeaderboard(leaderboard); + if(leaderboardWithLeaderboardGroups.containsKey(leaderboard)) { + raceGroup.leaderboardGroupName = leaderboardWithLeaderboardGroups.get(leaderboard).getName(); + } + if (leaderboard instanceof RegattaLeaderboard) { + RegattaLeaderboard regattaLeaderboard = (RegattaLeaderboard) leaderboard; + raceGroup.boatClass = regattaLeaderboard.getRegatta().getBoatClass().getDisplayName(); + for (Series series : regattaLeaderboard.getRegatta().getSeries()) { + RaceGroupSeriesDTO seriesDTO = new RaceGroupSeriesDTO(series.getName()); + raceGroup.getSeries().add(seriesDTO); + for (Fleet fleet : series.getFleets()) { + FleetDTO fleetDTO = new FleetDTO(fleet.getName(), fleet.getOrdering(), fleet.getColor()); + seriesDTO.getFleets().add(fleetDTO); + } + seriesDTO.getRaceColumns().addAll(convertToRaceColumnDTOs(series.getRaceColumns())); + } + } else { + RaceGroupSeriesDTO seriesDTO = new RaceGroupSeriesDTO(LeaderboardNameConstants.DEFAULT_SERIES_NAME); + raceGroup.getSeries().add(seriesDTO); + FleetDTO fleetDTO = new FleetDTO(LeaderboardNameConstants.DEFAULT_FLEET_NAME, 0, null); + seriesDTO.getFleets().add(fleetDTO); + seriesDTO.getRaceColumns().addAll(convertToRaceColumnDTOs(leaderboard.getRaceColumns())); + for(Competitor c: leaderboard.getCompetitors()) { + if(c.getBoat() != null && c.getBoat().getBoatClass() != null) { + raceGroup.boatClass = c.getBoat().getBoatClass().getDisplayName(); + } + } + } + raceGroups.add(raceGroup); + } + } + } + return raceGroups; + } + + /** * The name of the regatta to be shown on the regatta overview webpage is retrieved from the name of the {@link Leaderboard}. Since regattas are * not always represented by a {@link Regatta} object in the Sailing Suite but need to be shown on the regatta overview page, the leaderboard is @@ -3351,8 +3477,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S private RaceColumnInSeriesDTO convertToRaceColumnInSeriesDTO(RaceColumnInSeries raceColumnInSeries) { RaceColumnInSeriesDTO raceColumnInSeriesDTO = new RaceColumnInSeriesDTO(raceColumnInSeries.getSeries().getName(), raceColumnInSeries.getRegatta().getName()); - raceColumnInSeriesDTO.setName(raceColumnInSeries.getName()); - raceColumnInSeriesDTO.setMedalRace(raceColumnInSeries.isMedalRace()); + fillRaceColumnDTO(raceColumnInSeries, raceColumnInSeriesDTO); return raceColumnInSeriesDTO; } @@ -4756,24 +4881,25 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S if (serverNameOrNullForMain == null) { Result searchResult = getService().search(query); for (LeaderboardSearchResult hit : searchResult.getHits()) { - result.add(createLeaderboardSearchResultDTO(hit, getRequestBaseURL())); + result.add(createLeaderboardSearchResultDTO(hit, getRequestBaseURL(), false)); } } else { RemoteSailingServerReference remoteRef = getService().getRemoteServerReferenceByName(serverNameOrNullForMain); for (LeaderboardSearchResultBase hit : getService().searchRemotely(serverNameOrNullForMain, query).getHits()) { - result.add(createLeaderboardSearchResultDTO(hit, remoteRef.getURL())); + result.add(createLeaderboardSearchResultDTO(hit, remoteRef.getURL(), true)); } } return result; } - private LeaderboardSearchResultDTO createLeaderboardSearchResultDTO(LeaderboardSearchResultBase leaderboardSearchResult, URL baseURL) { + private LeaderboardSearchResultDTO createLeaderboardSearchResultDTO(LeaderboardSearchResultBase leaderboardSearchResult, URL baseURL, + boolean isOnRemoteServer) { ArrayList leaderboardGroups = new ArrayList<>(); for (LeaderboardGroupBase lgb : leaderboardSearchResult.getLeaderboardGroups()) { LeaderboardGroupBaseDTO leaderboardGroupDTO = convertToLeaderboardGroupBaseDTO(lgb); leaderboardGroups.add(leaderboardGroupDTO); } - return new LeaderboardSearchResultDTO(baseURL.toString(), leaderboardSearchResult.getLeaderboard().getName(), + return new LeaderboardSearchResultDTO(baseURL.toString(), isOnRemoteServer, leaderboardSearchResult.getLeaderboard().getName(), leaderboardSearchResult.getLeaderboard().getDisplayName(), leaderboardSearchResult.getRegattaName(), leaderboardSearchResult.getBoatClassName(), convertToEventDTO(leaderboardSearchResult.getEvent()), leaderboardGroups); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SimulatorServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SimulatorServiceImpl.java index 3ba765dc2fa..57fb96c658a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SimulatorServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SimulatorServiceImpl.java @@ -320,7 +320,7 @@ public class SimulatorServiceImpl extends RemoteServiceServlet implements Simula } for (BoatClassProperties tuple : ConfigurationManager.INSTANCE.getBoatClassesInfo()) { - boatClassesDTOs.add(new BoatClassDTO(tuple.getName(), tuple.getLength())); + boatClassesDTOs.add(new BoatClassDTO(tuple.getName(), null, tuple.getLength())); } result.setBoatClassDTOs(boatClassesDTOs.toArray(new BoatClassDTO[boatClassesDTOs.size()])); @@ -747,7 +747,7 @@ public class SimulatorServiceImpl extends RemoteServiceServlet implements Simula } windFieldDTO.curBearing = wf.getWindParameters().curBearing; windFieldDTO.curSpeed = wf.getWindParameters().curSpeed; - if (params.isShowStreamlets2()) { + if (params.isShowStreamlets()) { WindData windData = new WindData(); windData.rcStart = params.getNorthWest(); windData.rcEnd = params.getSouthEast(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/CompactRaceMapDataDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/CompactRaceMapDataDTO.java index 6d9910d3847..cec85136aa1 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/CompactRaceMapDataDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/CompactRaceMapDataDTO.java @@ -2,6 +2,7 @@ package com.sap.sailing.gwt.ui.shared; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -33,9 +34,12 @@ public class CompactRaceMapDataDTO implements IsSerializable { for (Map.Entry> e : boatPositions.entrySet()) { this.boatPositionsByCompetitorIdAsString.put(e.getKey().getIdAsString(), e.getValue()); } - this.quickRanks = new ArrayList(quickRanks.size()); - for (QuickRankDTO quickRank : quickRanks) { - this.quickRanks.add(new CompactQuickRankDTO(quickRank.competitor.getIdAsString(), quickRank.rank, quickRank.legNumberOneBased)); + this.quickRanks = new ArrayList(quickRanks == null ? 0 : quickRanks.size()); + if (quickRanks != null) { + for (QuickRankDTO quickRank : quickRanks) { + this.quickRanks.add(new CompactQuickRankDTO(quickRank.competitor.getIdAsString(), quickRank.rank, + quickRank.legNumberOneBased)); + } } this.courseSidelines = courseSidelines; this.coursePositions = coursePositions; @@ -47,11 +51,11 @@ public class CompactRaceMapDataDTO implements IsSerializable { competitorsByIdAsString.put(competitor.getIdAsString(), competitor); } RaceMapDataDTO result = new RaceMapDataDTO(); - result.quickRanks = new ArrayList(this.quickRanks.size()); + result.quickRanks = new LinkedHashMap(this.quickRanks.size()); for (CompactQuickRankDTO compactQuickRank : this.quickRanks) { final CompetitorDTO competitorDTO = competitorsByIdAsString.get(compactQuickRank.getCompetitorIdAsString()); if (competitorDTO != null) { - result.quickRanks.add(new QuickRankDTO(competitorDTO, compactQuickRank.getRank(), compactQuickRank.getLegNumber())); + result.quickRanks.put(competitorDTO, new QuickRankDTO(competitorDTO, compactQuickRank.getRank(), compactQuickRank.getLegNumber())); } } result.courseSidelines = courseSidelines; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/EventBaseDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/EventBaseDTO.java index 6224884280c..c8bda9ba95c 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/EventBaseDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/EventBaseDTO.java @@ -1,15 +1,22 @@ package com.sap.sailing.gwt.ui.shared; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.UUID; import com.google.gwt.user.client.rpc.IsSerializable; +import com.sap.sailing.domain.common.ImageSize; import com.sap.sailing.domain.common.dto.NamedDTO; +import com.sap.sse.common.Util; public class EventBaseDTO extends NamedDTO implements IsSerializable { private static final long serialVersionUID = 818666323178097939L; + private static final String STAGE_IMAGE_URL_SUBSTRING_INDICATOR_CASE_INSENSITIVE = "stage"; public VenueDTO venue; public Date startDate; public Date endDate; @@ -23,6 +30,14 @@ public class EventBaseDTO extends NamedDTO implements IsSerializable { private List sponsorImageURLs = new ArrayList<>(); private String logoImageURL; private String officialWebsiteURL; + /** placeholder for social media URL's -> attributes will be implemented later on */ + private String facebookURL; + private String twitterURL; + /** + * For the image URL keys holds the sizes of these images if known. An image size is "known" by this object if it was + * provided to the {@link #setImageSize} method. + */ + private Map imageSizes; /** * The base URL for the server instance on which the data for this event can be reached. Could be something like @@ -32,18 +47,33 @@ public class EventBaseDTO extends NamedDTO implements IsSerializable { * resolve to the correct host IP and Java server instance. */ private String baseURL; + + /** + * Indicates whether the event is hosted on a remote server or not + */ + private boolean isOnRemoteServer; EventBaseDTO() {} // for serialization only public EventBaseDTO(List leaderboardGroups) { this.leaderboardGroups = leaderboardGroups; + this.imageSizes = new HashMap(); } public EventBaseDTO(String name, List leaderboardGroups) { super(name); this.leaderboardGroups = leaderboardGroups; + this.imageSizes = new HashMap(); } + public boolean isRunning() { + Date now = new Date(); + if(startDate != null && endDate != null && (now.after(startDate) && now.before(endDate))) { + return true; + } + return false; + } + public String getDescription() { return description; } @@ -97,21 +127,63 @@ public class EventBaseDTO extends NamedDTO implements IsSerializable { return imageURLs; } + /** + * The stage image is determined from the {@link #imageURLs} collection by a series of heuristics and fall-back rules: + *
    + *
  1. If one or more image URLs has "stage" (ignoring case) in its name, only they are considered candidates.
  2. + *
  3. If no image URL has "stage" (ignoring case) in its name, all images from {@link #imageURLs} are considered candidates.
  4. + *
  5. From all candidates, the one with the biggest known size (determined by the product of width and height) is chosen.
  6. + *
  7. If the size isn't known for any candidate, the first candidate in {@link #imageURLs} is picked.
  8. + *
+ */ public String getStageImageURL() { - String result = null; - for(String imageUrl: imageURLs) { - if(imageUrl.contains("stage") || imageUrl.contains("STAGE")) { - result = imageUrl; - break; - } + final String result; + if (imageURLs.isEmpty()) { + result = null; + } else { + Comparator stageImageComparator = new Comparator() { + @Override + public int compare(String o1, String o2) { + final int result; + if (o1.toLowerCase().contains(STAGE_IMAGE_URL_SUBSTRING_INDICATOR_CASE_INSENSITIVE)) { + if (o2.toLowerCase().contains(STAGE_IMAGE_URL_SUBSTRING_INDICATOR_CASE_INSENSITIVE)) { + result = compareBySize(o1, o2); + } else { + // o1 has stage indicator substring in its URL but o2 doesn't; o1 ranks greater + result = 1; + } + } else { + if (o2.toLowerCase().contains(STAGE_IMAGE_URL_SUBSTRING_INDICATOR_CASE_INSENSITIVE)) { + result = -1; // o1 does not and o2 does have stage indicator substring, so o2 ranks greater + } else { + // both don't have stage indicator; compare by size + result = compareBySize(o1, o2); + } + } + return result; + } + + private int compareBySize(String o1, String o2) { + final int result; + final ImageSize o1Size = getImageSize(o1); + final ImageSize o2Size = getImageSize(o2); + result = (o1Size == null ? 0 : (o1Size.getWidth() * o1Size.getHeight())) + - (o2Size == null ? 0 : (o2Size.getWidth() * o2Size.getHeight())); + return result; + } + }; + List sortedImageURLs = new ArrayList<>(imageURLs); + Collections.sort(sortedImageURLs, stageImageComparator); + result = sortedImageURLs.get(sortedImageURLs.size() - 1); } return result; } public List getPhotoGalleryImageURLs() { + String stageImageURL = getStageImageURL(); // if set, exclude stage image from photo gallery List result = new ArrayList(); - for(String imageUrl: imageURLs) { - if(!imageUrl.contains("stage") && !imageUrl.contains("STAGE")) { + for (String imageUrl : imageURLs) { + if (imageURLs.size() == 1 || !Util.equalsWithNull(imageUrl, stageImageURL)) { result.add(imageUrl); } } @@ -130,4 +202,44 @@ public class EventBaseDTO extends NamedDTO implements IsSerializable { return leaderboardGroups; } + public boolean isOnRemoteServer() { + return isOnRemoteServer; + } + + public void setIsOnRemoteServer(boolean isOnRemoteServer) { + this.isOnRemoteServer = isOnRemoteServer; + } + + public String getFacebookURL() { + return facebookURL; + } + + public void setFacebookURL(String facebookURL) { + this.facebookURL = facebookURL; + } + + public String getTwitterURL() { + return twitterURL; + } + + public void setTwitterURL(String twitterURL) { + this.twitterURL = twitterURL; + } + + public void setImageSize(String imageURL, ImageSize imageSize) { + if (imageSize == null) { + imageSizes.remove(imageURL); + } else { + imageSizes.put(imageURL, imageSize); + } + } + + /** + * @return the size of the image referenced by imageURL or null if that size is not known + * of the image URL is none of those known to this event, in particular neither of {@link #getImageURLs()} + * or {@link #getSponsorImageURLs()}. + */ + public ImageSize getImageSize(String imageURL) { + return imageSizes.get(imageURL); + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/EventDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/EventDTO.java index f2d41860557..4da9e173c6b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/EventDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/EventDTO.java @@ -33,6 +33,18 @@ public class EventDTO extends EventBaseDTO { regattas = new ArrayList(); } + public boolean isFakeSeries() { + return leaderboardGroups.size() == 1 && leaderboardGroups.get(0).hasOverallLeaderboard(); + } + + public boolean isRunning() { + return getCurrentServerTime().after(startDate) && getCurrentServerTime().before(endDate); + } + + public boolean isFinished() { + return getCurrentServerTime().after(endDate); + } + private void initCurrentServerTime() { currentServerTime = new Date(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/LeaderboardGroupBaseDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/LeaderboardGroupBaseDTO.java index 44ffc3daded..31429ac85ad 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/LeaderboardGroupBaseDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/LeaderboardGroupBaseDTO.java @@ -8,17 +8,19 @@ public class LeaderboardGroupBaseDTO extends NamedDTO { private static final long serialVersionUID = -9023865026348923430L; private UUID id; public String description; + private String displayName; private boolean hasOverallLeaderboard; LeaderboardGroupBaseDTO() {} // for deserialization - public LeaderboardGroupBaseDTO(UUID id, String name) { + public LeaderboardGroupBaseDTO(UUID id, String name, String displayName) { super(name); this.id = id; + this.displayName = displayName; } - public LeaderboardGroupBaseDTO(UUID id, String name, String description, boolean hasOverallLeaderboard) { - this(id, name); + public LeaderboardGroupBaseDTO(UUID id, String name, String description, String displayName, boolean hasOverallLeaderboard) { + this(id, name, displayName); this.description = description; this.hasOverallLeaderboard = hasOverallLeaderboard; } @@ -27,6 +29,14 @@ public class LeaderboardGroupBaseDTO extends NamedDTO { return id; } + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + public boolean isHasOverallLeaderboard() { return hasOverallLeaderboard; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/LeaderboardGroupDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/LeaderboardGroupDTO.java index 471bb280043..061b282949a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/LeaderboardGroupDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/LeaderboardGroupDTO.java @@ -38,25 +38,26 @@ public class LeaderboardGroupDTO extends LeaderboardGroupBaseDTO { *

* The additional data (start dates and places for the races) will be initialized but empty. */ - public LeaderboardGroupDTO(UUID id) { - this(id, ""); + public LeaderboardGroupDTO(UUID id, String displayName) { + this(id, "", displayName); } - public LeaderboardGroupDTO(UUID id, String name) { - this(id, name, ""); + public LeaderboardGroupDTO(UUID id, String name, String displayName) { + this(id, name, displayName, ""); } - public LeaderboardGroupDTO(UUID id, String name, String description) { - this(id, name, description, new ArrayList()); + public LeaderboardGroupDTO(UUID id, String name, String displayName, String description) { + this(id, name, description, displayName, new ArrayList()); } /** * Creates a new LeaderboardGroupDTO with the given parameters as attributes.
* All parameters can be null but then the attributes will also be null.
* The additional data (start dates and places for the races) will be initialized but empty. + * @param displayName TODO */ - private LeaderboardGroupDTO(UUID id, String name, String description, List leaderboards) { - super(id, name); + private LeaderboardGroupDTO(UUID id, String name, String description, String displayName, List leaderboards) { + super(id, name, displayName); currentServerTime = new Date(); this.description = description; this.leaderboards = leaderboards; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/LeaderboardSearchResultDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/LeaderboardSearchResultDTO.java index db9b8bf2392..b91cdb995ae 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/LeaderboardSearchResultDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/LeaderboardSearchResultDTO.java @@ -17,6 +17,7 @@ import com.sap.sailing.domain.base.RemoteSailingServerReference; */ public class LeaderboardSearchResultDTO implements IsSerializable { private String baseURL; + private boolean isOnRemoteServer; private String leaderboardName; private String leaderboardDisplayName; private String regattaName; @@ -26,11 +27,12 @@ public class LeaderboardSearchResultDTO implements IsSerializable { LeaderboardSearchResultDTO() {} // for GWT RPC serialization only - public LeaderboardSearchResultDTO(String baseURL, String leaderboardName, String leaderboardDisplayName, + public LeaderboardSearchResultDTO(String baseURL, boolean isOnRemoteServer, String leaderboardName, String leaderboardDisplayName, String regattaName, String boatClassName, EventBaseDTO event, Iterable leaderboardGroups) { super(); this.baseURL = baseURL; + this.isOnRemoteServer = isOnRemoteServer; this.leaderboardName = leaderboardName; this.leaderboardDisplayName = leaderboardDisplayName; this.regattaName = regattaName; @@ -91,4 +93,8 @@ public class LeaderboardSearchResultDTO implements IsSerializable { sb.append(getRegattaName()); return sb.toString(); } + + public boolean isOnRemoteServer() { + return isOnRemoteServer; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceGroupDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceGroupDTO.java index 72168c356c9..02fe7171ed5 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceGroupDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceGroupDTO.java @@ -12,6 +12,7 @@ public class RaceGroupDTO extends NamedDTO implements IsSerializable { public String courseAreaIdAsString; public String displayName; public String leaderboardGroupName; + public String boatClass; private List series = new ArrayList(); public RaceGroupDTO() {} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceMapDataDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceMapDataDTO.java index 081335b4c09..6bafc2986b1 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceMapDataDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceMapDataDTO.java @@ -1,5 +1,6 @@ package com.sap.sailing.gwt.ui.shared; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -10,5 +11,5 @@ public class RaceMapDataDTO implements IsSerializable { public Map> boatPositions; public CoursePositionsDTO coursePositions; public List courseSidelines; - public List quickRanks; + public LinkedHashMap quickRanks; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RegattaDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RegattaDTO.java index ce90b7909bd..76350535e89 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RegattaDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RegattaDTO.java @@ -4,12 +4,16 @@ import java.util.Date; import java.util.List; import java.util.UUID; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaIdentifier; import com.sap.sailing.domain.common.RegattaName; import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.dto.BoatClassDTO; +import com.sap.sailing.domain.common.dto.FleetDTO; import com.sap.sailing.domain.common.dto.NamedDTO; +import com.sap.sailing.domain.common.dto.RaceColumnDTO; import com.sap.sailing.domain.common.dto.RaceDTO; +import com.sap.sse.common.Util.Pair; public class RegattaDTO extends NamedDTO { private static final long serialVersionUID = -4594784946348402759L; @@ -31,6 +35,18 @@ public class RegattaDTO extends NamedDTO { super(name); this.scoringScheme = scoringScheme; } + + public Pair getSeriesAndFleet(RegattaAndRaceIdentifier raceIdentifier) { + for (SeriesDTO s : series) { + for (RaceColumnDTO raceColumn : s.getRaceColumns()) { + FleetDTO fleet = raceColumn.getFleet(raceIdentifier); + if (fleet != null) { + return new Pair(s, fleet); + } + } + } + return null; + } public RegattaIdentifier getRegattaIdentifier() { return new RegattaName(getName()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/StrippedLeaderboardDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/StrippedLeaderboardDTO.java index 93a82730df4..c4b27be0242 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/StrippedLeaderboardDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/StrippedLeaderboardDTO.java @@ -5,6 +5,8 @@ import com.sap.sailing.domain.common.dto.AbstractLeaderboardDTO; public class StrippedLeaderboardDTO extends AbstractLeaderboardDTO { private static final long serialVersionUID = 3285029720177137625L; + public int competitorsCount; + public void removeRace(String raceColumnName) { getRaceList().remove(getRaceColumnByName(raceColumnName)); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/WindFieldGenParamsDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/WindFieldGenParamsDTO.java index 3e0d7935891..f2513b6ddbb 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/WindFieldGenParamsDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/WindFieldGenParamsDTO.java @@ -37,8 +37,8 @@ public class WindFieldGenParamsDTO implements IsSerializable { */ private boolean showGrid; + private boolean showLineGuides; private boolean showStreamlets; - private boolean showStreamlets2; private boolean showLines; @@ -158,6 +158,14 @@ public class WindFieldGenParamsDTO implements IsSerializable { this.showGrid = showGrid; } + public boolean isShowLineGuides() { + return showLineGuides; + } + + public void setShowLineGuides(boolean showLineGuides) { + this.showLineGuides = showLineGuides; + } + public boolean isShowStreamlets() { return showStreamlets; } @@ -166,14 +174,6 @@ public class WindFieldGenParamsDTO implements IsSerializable { this.showStreamlets = showStreamlets; } - public boolean isShowStreamlets2() { - return showStreamlets2; - } - - public void setShowStreamlets2(boolean showStreamlets2) { - this.showStreamlets2 = showStreamlets2; - } - public boolean isShowLines() { return showLines; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/MarkVectorGraphics.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/MarkVectorGraphics.java index d4ff840ef78..245b5971dfd 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/MarkVectorGraphics.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/MarkVectorGraphics.java @@ -36,20 +36,14 @@ public class MarkVectorGraphics { this.markWidthInMeters = 1.5; } - public void drawMarkToCanvas(Context2d ctx, boolean isSelected, - double width, double height, double scaleFactor) { - + public void drawMarkToCanvas(Context2d ctx, boolean isSelected, double width, double height, double scaleFactor) { ctx.save(); ctx.clearRect(0, 0, width, height); - ctx.translate(width / 2.0, height / 2.0); ctx.scale(scaleFactor, scaleFactor); - ctx.translate(-anchorPointX * 100,- anchorPointY * 100); - String markColor = color != null ? color : DEFAULT_MARK_COLOR; drawMark(ctx, isSelected, markColor); - ctx.restore(); } @@ -61,19 +55,19 @@ public class MarkVectorGraphics { if(Shape.CYLINDER.name().equalsIgnoreCase(shape) && pattern != null && Pattern.CHECKERED.name().equalsIgnoreCase(pattern)) { drawBuoyWithFinishFlag(ctx, isSelected, color); } else if (Shape.CONICAL.name().equalsIgnoreCase(shape)) { - drawConicalBuoy(ctx, isSelected, color); + drawConicalBuoy(ctx, color); } else { - drawSimpleBuoy(ctx, isSelected, color); + drawSimpleBuoy(ctx, color); } } else { - drawSimpleBuoy(ctx, isSelected, color); + drawSimpleBuoy(ctx, color); } break; case FINISHBOAT: drawFinishBoat(ctx, isSelected, color); break; case LANDMARK: - drawLandmark(ctx, isSelected, color); + drawLandmark(ctx, color); break; case STARTBOAT: drawStartBoat(ctx, isSelected, color); @@ -87,7 +81,7 @@ public class MarkVectorGraphics { } } - protected void drawSimpleBuoy(Context2d ctx, boolean isSelected, String color) { + void drawSimpleBuoy(Context2d ctx, String color) { ctx.setStrokeStyle("rgba(0,0,0,0)"); ctx.save(); @@ -336,7 +330,7 @@ public class MarkVectorGraphics { ctx.restore(); } - protected void drawLandmark(Context2d ctx, boolean isSelected, String color) { + protected void drawLandmark(Context2d ctx, String color) { ctx.setStrokeStyle("rgba(0,0,0,0)"); ctx.save(); @@ -401,7 +395,7 @@ public class MarkVectorGraphics { ctx.restore(); } - protected void drawConicalBuoy(Context2d ctx, boolean isSelected, String color) { + protected void drawConicalBuoy(Context2d ctx, String color) { ctx.setStrokeStyle("rgba(0,0,0,0)"); ctx.save(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java index 6076e8dfbaa..658dde36612 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java @@ -21,7 +21,7 @@ import com.sap.sailing.gwt.ui.shared.WindDTO; import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO; import com.sap.sailing.gwt.ui.shared.WindTrackInfoDTO; import com.sap.sailing.gwt.ui.simulator.StreamletParameters; -import com.sap.sailing.gwt.ui.simulator.racemap.FullCanvasOverlay; +import com.sap.sailing.gwt.ui.simulator.racemap.MovingCanvasOverlay; import com.sap.sailing.gwt.ui.simulator.streamlets.Swarm; import com.sap.sailing.gwt.ui.simulator.streamlets.VectorField; import com.sap.sailing.gwt.ui.simulator.streamlets.WindInfoForRaceVectorField; @@ -38,7 +38,7 @@ import com.sap.sse.gwt.client.player.Timer; * @author Axel Uhl (D043530) * */ -public class WindStreamletsRaceboardOverlay extends FullCanvasOverlay { +public class WindStreamletsRaceboardOverlay extends MovingCanvasOverlay { public static final String LODA_WIND_STREAMLET_DATA_CATEGORY = "loadWindStreamletData"; private static final int animationIntervalMillis = 40; private static final long RESOLUTION_IN_MILLIS = 5000; @@ -73,7 +73,7 @@ public class WindStreamletsRaceboardOverlay extends FullCanvasOverlay { windInfoForRace.windSourcesToExclude = new HashSet<>(); windInfoForRace.windTrackInfoByWindSource = new HashMap<>(); updateAverageLatitudeDeg(windInfoForRace); - this.windField = new WindInfoForRaceVectorField(windInfoForRace); + this.windField = new WindInfoForRaceVectorField(windInfoForRace, /* frames per second */ 1000.0/animationIntervalMillis); this.timer = timer; getCanvas().getElement().setId("swarm-display"); } @@ -102,6 +102,7 @@ public class WindStreamletsRaceboardOverlay extends FullCanvasOverlay { setCanvasSettings(); this.swarm = new Swarm(this, map, timer, windField, new StreamletParameters()); } + initCanvasOrigin(); this.swarm.start(animationIntervalMillis); } @@ -232,9 +233,10 @@ public class WindStreamletsRaceboardOverlay extends FullCanvasOverlay { protected void drawCenterChanged() { } - public void onBoundsChanged() { + public void onBoundsChanged(boolean zoomChanged) { if (swarm != null) { - swarm.onBoundsChanged(); + int swarmPause = (zoomChanged ? 5 : 1); + swarm.onBoundsChanged(zoomChanged, swarmPause); } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/PathCanvasOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/PathCanvasOverlay.java index ad974392326..5b76fea4714 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/PathCanvasOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/PathCanvasOverlay.java @@ -12,6 +12,7 @@ import com.google.gwt.maps.client.MapWidget; import com.google.gwt.maps.client.base.LatLng; import com.google.gwt.maps.client.base.Point; import com.google.gwt.maps.client.geometrylib.SphericalUtils; +import com.sap.sailing.domain.common.AbstractBearing; import com.sap.sailing.domain.common.Mile; import com.sap.sailing.domain.common.dto.PositionDTO; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; @@ -164,7 +165,7 @@ public class PathCanvasOverlay extends WindFieldCanvasOverlay implements Named { //if ((displayWindAlongPath) && ((index % arrowInterleave) == 0)) { if (displayWindAlongPath) { if (checkPointsAreFarEnough(windDTO,prevWindDTO)) { - DegreeBearingImpl dbi = new DegreeBearingImpl(windDTO.trueWindBearingDeg); + AbstractBearing dbi = new DegreeBearingImpl(windDTO.trueWindBearingDeg); // System.out.print("index: "+index+"\n"); drawScaledArrow(windDTO, dbi.getRadians(), index, true); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/PathLegendCanvasOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/PathLegendCanvasOverlay.java index 4fe916d694b..9bad7fa93ce 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/PathLegendCanvasOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/PathLegendCanvasOverlay.java @@ -9,6 +9,7 @@ import com.google.gwt.canvas.dom.client.TextMetrics; import com.google.gwt.i18n.client.DateTimeFormat; import com.google.gwt.i18n.client.TimeZone; import com.google.gwt.maps.client.MapWidget; +import com.sap.sailing.domain.common.AbstractBearing; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.gwt.ui.simulator.racemap.FullCanvasOverlay; import com.sap.sailing.simulator.util.SailingSimulatorConstants; @@ -102,7 +103,7 @@ public class PathLegendCanvasOverlay extends FullCanvasOverlay { // // TODO: draw current arrow // - DegreeBearingImpl curBear = new DegreeBearingImpl(this.curBearing); + AbstractBearing curBear = new DegreeBearingImpl(this.curBearing); //Context2d context2d = canvas.getContext2d(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/RegattaAreaCanvasOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/RegattaAreaCanvasOverlay.java index 80e2d0afa8d..c6c355a29dd 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/RegattaAreaCanvasOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/RegattaAreaCanvasOverlay.java @@ -126,7 +126,7 @@ public class RegattaAreaCanvasOverlay extends FullCanvasOverlay { String bgColor; - if (simulatorMap.getWindParams().isShowStreamlets2()) { + if (simulatorMap.getWindParams().isShowStreamlets()) { bgColor = "#505050"; } else { bgColor = "#DEDEDE"; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/ReplayPathCanvasOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/ReplayPathCanvasOverlay.java index fa668523a9a..c4e24d1f623 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/ReplayPathCanvasOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/ReplayPathCanvasOverlay.java @@ -51,7 +51,7 @@ public class ReplayPathCanvasOverlay extends PathCanvasOverlay { if (!isVisible() || windDTOToDraw == null || windFieldDTO == null) { return true; } - if (windDTOToDraw.size() == windFieldDTO.getMatrix().size()) { + if (windDTOToDraw.size() >= (windFieldDTO.getMatrix().size()-1)) { return true; } else { return false; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorEntryPoint.java index c568b392593..cfaac88b123 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorEntryPoint.java @@ -10,12 +10,10 @@ import com.google.gwt.user.client.ui.DockLayoutPanel; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.RootLayoutPanel; import com.sap.sailing.gwt.ui.client.AbstractEntryPoint; -import com.sap.sailing.gwt.ui.client.JavaScriptInjector; import com.sap.sailing.gwt.ui.client.LogoAndTitlePanel; import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants; import com.sap.sailing.gwt.ui.client.SimulatorService; import com.sap.sailing.gwt.ui.client.SimulatorServiceAsync; -import com.sap.sailing.gwt.ui.simulator.streamlets.SimulatorJSBundle; import com.sap.sailing.simulator.util.SailingSimulatorConstants; import com.sap.sse.gwt.client.EntryPointHelper; @@ -35,31 +33,21 @@ public class SimulatorEntryPoint extends AbstractEntryPoint { private char event = SailingSimulatorConstants.EventKielerWoche; // default event: 'k'ieler woche private boolean showArrows = false; // show the wind arrows in wind display and replay modes. - private boolean showGrid = true; // show the "heat map" in the wind display and replay modes. + private boolean showGrid = false; // show the "heat map" in the wind display and replay modes. private boolean showLines = false; // show the wind lines in the wind display and replay modes. private boolean showMapControls = true; // show the map controls such as zoom and pan private char seedLines = 'b'; // seed lines at: 'b'ack, 'f'ront - private boolean showStreamlets = true; // show the wind streamlets in the wind display and replay modes. - private boolean showStreamlets2 = false; // show animated wind streamlets in the wind display and replay modes. - private boolean injectWindDataJS = false; + private boolean showLineGuides = false; // show the wind streamlets in the wind display and replay modes. + private boolean showStreamlets = true; // show animated wind streamlets in the wind display and replay modes. private static Logger logger = Logger.getLogger(SimulatorEntryPoint.class.getName()); @Override protected void doOnModuleLoad() { - super.doOnModuleLoad(); - EntryPointHelper.registerASyncService((ServiceDefTarget) simulatorService, RemoteServiceMappingConstants.simulatorServiceRemotePath); - checkUrlParameters(); - - if (this.injectWindDataJS) { - SimulatorJSBundle bundle = GWT.create(SimulatorJSBundle.class); - JavaScriptInjector.inject(bundle.windStreamletsDataJS().getText()); - } - createSimulatorPanel(); } @@ -83,17 +71,11 @@ public class SimulatorEntryPoint extends AbstractEntryPoint { this.border = Integer.parseInt(border); } - streamletPars.particles = 0; + streamletPars.macroWeather = false; streamletPars.motionScale = 1.0; streamletPars.swarmScale = 1.0; streamletPars.detailZoom = 15; - String particlesStr = Window.Location.getParameter("particles"); - if (particlesStr == null || particlesStr.isEmpty()) { - logger.config("Using default particles " + this.streamletPars.particles); - } else { - this.streamletPars.particles = Integer.parseInt(particlesStr); - } String tmpStr = Window.Location.getParameter("motionScale"); if (tmpStr == null || tmpStr.isEmpty()) { logger.config("Using default motionScale."); @@ -146,7 +128,7 @@ public class SimulatorEntryPoint extends AbstractEntryPoint { showArrows = true; // show the wind arrows in wind display and replay modes. showGrid = false; // show the "heat map" in the wind display and replay modes. showLines = false; // show the wind lines in the wind display and replay modes. - showStreamlets = false; // show the wind streamlets in the wind display and replay modes. + showLineGuides = false; // show the wind streamlets in the wind display and replay modes. } } String eventStr = Window.Location.getParameter("event"); @@ -175,28 +157,27 @@ public class SimulatorEntryPoint extends AbstractEntryPoint { showArrows = false; } if (windDisplayStr.contains("s")) { + showLineGuides = true; + } else { + showLineGuides = false; + } + if (windDisplayStr.contains("z")) { showStreamlets = true; } else { showStreamlets = false; } - if (windDisplayStr.contains("z")) { - showStreamlets2 = true; - } else { - showStreamlets2 = false; - } - if (windDisplayStr.contains("y")) { - showStreamlets2 = true; - injectWindDataJS = true; - } - if ((showStreamlets2)&&(border==null)) { - this.border = 10; - } if (windDisplayStr.contains("b")) { seedLines = 'b'; } if (windDisplayStr.contains("f")) { seedLines = 'f'; } + if (windDisplayStr.contains("m")) { + streamletPars.macroWeather = true; + } + } + if ((showStreamlets)&&(border==null)) { + this.border = 10; } } @@ -220,7 +201,7 @@ public class SimulatorEntryPoint extends AbstractEntryPoint { private void createSimulatorPanel() { SimulatorMainPanel simulatorPanel = new SimulatorMainPanel(simulatorService, stringMessages, this, xRes, yRes, border, streamletPars, - autoUpdate, mode, event, showGrid, showLines, seedLines, showArrows, showStreamlets, showStreamlets2, injectWindDataJS, showMapControls); + autoUpdate, mode, event, showGrid, showLines, seedLines, showArrows, showLineGuides, showStreamlets, showMapControls); DockLayoutPanel p = new DockLayoutPanel(Unit.PX); RootLayoutPanel.get().add(p); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorMainPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorMainPanel.java index a7cd0468e56..e3cb4a02aa1 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorMainPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorMainPanel.java @@ -74,7 +74,7 @@ public class SimulatorMainPanel extends SimplePanel { } } - public int particles; + public boolean macroWeather; private DockLayoutPanel mainPanel; private FlowPanel leftPanel; @@ -126,7 +126,6 @@ public class SimulatorMainPanel extends SimplePanel { private CheckBox isPathPolylineFreeMode; private Chart chart; - private boolean injectWindDataJS = false; private boolean warningAlreadyShown = false; private DialogBox polarDiagramDialogBox; private Button polarDiagramDialogCloseButton; @@ -230,10 +229,10 @@ public class SimulatorMainPanel extends SimplePanel { } public SimulatorMainPanel(SimulatorServiceAsync svc, StringMessages stringMessages, ErrorReporter errorReporter, int xRes, int yRes, int border, StreamletParameters streamletPars, boolean autoUpdate, - char mode, char event, boolean showGrid, boolean showLines, char seedLines, boolean showArrows, boolean showStreamlets, boolean showStreamlets2, boolean injectWindDataJS, boolean showMapControls) { + char mode, char event, boolean showGrid, boolean showLines, char seedLines, boolean showArrows, boolean showLineGuides, boolean showStreamlets, boolean showMapControls) { super(); - this.particles = streamletPars.particles; + this.macroWeather = streamletPars.macroWeather; this.simulatorSvc = svc; this.stringMessages = stringMessages; @@ -299,10 +298,9 @@ public class SimulatorMainPanel extends SimplePanel { windParams.setShowGrid(showGrid); windParams.setShowLines(showLines); windParams.setSeedLines(seedLines); + windParams.setShowLineGuides(showLineGuides); windParams.setShowStreamlets(showStreamlets); - windParams.setShowStreamlets2(showStreamlets2); this.setDefaultTimeSettings(); - this.injectWindDataJS = injectWindDataJS; timer = new Timer(PlayModes.Replay, 1000l); TimeRangeWithZoomProvider timeRangeProvider = new TimeRangeWithZoomModel(); @@ -851,7 +849,7 @@ public class SimulatorMainPanel extends SimplePanel { private void update() { - if (this.windParams.isShowStreamlets2()) { + if (this.windParams.isShowStreamlets()) { this.setMapInstance(this.simulatorMap.getMap().getJso()); this.setCanvasProjectionInstance(this.simulatorMap.getRegattaAreaCanvasOverlay().getMapProjection()); @@ -861,43 +859,36 @@ public class SimulatorMainPanel extends SimplePanel { } - if (this.injectWindDataJS) { + // int selectedBoatClassIndex = boatClassSelector.getSelectedIndex(); + // int selectedRaceIndex = raceSelector.getSelectedIndex(); + // int selectedCompetitorIndex = competitorSelector.getSelectedIndex(); + int selectedLegIndex = legSelector.getSelectedIndex(); - this.startStreamlets(); + SimulatorUISelectionDTO selection = new SimulatorUISelectionDTO(boatClassSelector.getSelectedIndex(), + raceSelector.getSelectedIndex(), competitorSelector.getSelectedIndex(), legSelector.getSelectedIndex()); - } else { + if (windDisplayButton.getValue()) { + showTimePanel(true); + simulatorMap.refreshView(SimulatorMap.ViewName.WINDDISPLAY, currentWPDisplay, selection, true); + } else if (summaryButton.getValue()) { + showTimePanel(false); + simulatorMap.getRegattaAreaCanvasOverlay().draw(); + simulatorMap.refreshView(SimulatorMap.ViewName.SUMMARY, currentWPDisplay, selection, true); + } else if (replayButton.getValue()) { + showTimePanel(true); + simulatorMap.refreshView(SimulatorMap.ViewName.REPLAY, currentWPDisplay, selection, true); + } else { + if (mode == SailingSimulatorConstants.ModeMeasured) { - // int selectedBoatClassIndex = boatClassSelector.getSelectedIndex(); - // int selectedRaceIndex = raceSelector.getSelectedIndex(); - // int selectedCompetitorIndex = competitorSelector.getSelectedIndex(); - int selectedLegIndex = legSelector.getSelectedIndex(); - - SimulatorUISelectionDTO selection = new SimulatorUISelectionDTO(boatClassSelector.getSelectedIndex(), raceSelector.getSelectedIndex(), - competitorSelector.getSelectedIndex(), legSelector.getSelectedIndex()); - - if (windDisplayButton.getValue()) { - showTimePanel(true); - simulatorMap.refreshView(SimulatorMap.ViewName.WINDDISPLAY, currentWPDisplay, selection, true); - } else if (summaryButton.getValue()) { - showTimePanel(false); - simulatorMap.getRegattaAreaCanvasOverlay().draw(); - simulatorMap.refreshView(SimulatorMap.ViewName.SUMMARY, currentWPDisplay, selection, true); - } else if (replayButton.getValue()) { - showTimePanel(true); - simulatorMap.refreshView(SimulatorMap.ViewName.REPLAY, currentWPDisplay, selection, true); - } else { - if (mode == SailingSimulatorConstants.ModeMeasured) { - - if (selectedLegIndex % 2 != 0) { - errorReporter.reportError("Downwind legs are NOT supported yet. Sorry about that :)"); - } else { - simulatorMap.removePolyline(); - showTimePanel(false); - simulatorMap.refreshView(SimulatorMap.ViewName.SUMMARY, currentWPDisplay, selection, true); - } - } - } - } + if (selectedLegIndex % 2 != 0) { + errorReporter.reportError("Downwind legs are NOT supported yet. Sorry about that :)"); + } else { + simulatorMap.removePolyline(); + showTimePanel(false); + simulatorMap.refreshView(SimulatorMap.ViewName.SUMMARY, currentWPDisplay, selection, true); + } + } + } } private void initPolarDiagramButton() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorMap.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorMap.java index 5af3f2a9054..88850a61f1e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorMap.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorMap.java @@ -216,7 +216,7 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ timePanel.setMinMax(new Date(startTime), endDate, true); timePanel.resetTimeSlider(); timePanel.timeChanged(windParams.getStartTime(), null); - if (windParams.isShowStreamlets2()) { + if (windParams.isShowStreamlets()) { windStreamletsCanvasOverlay.setEndDate(endDate); } } @@ -236,10 +236,10 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ if (windParams.isShowArrows()) { windFieldCanvasOverlay.addToMap(); } - if (windParams.isShowStreamlets()) { + if (windParams.isShowLineGuides()) { windLineGuidesCanvasOverlay.addToMap(); } - if (windParams.isShowStreamlets2()) { + if (windParams.isShowStreamlets()) { windStreamletsCanvasOverlay.addToMap(); } @@ -250,10 +250,10 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ if (windParams.isShowArrows()) { timeListeners.add(windFieldCanvasOverlay); } - if (windParams.isShowStreamlets()) { + if (windParams.isShowLineGuides()) { timeListeners.add(windLineGuidesCanvasOverlay); } - if (windParams.isShowStreamlets2()) { + if (windParams.isShowStreamlets()) { timeListeners.add(windStreamletsCanvasOverlay); } if (windParams.isShowGrid()) { @@ -376,7 +376,7 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ MapTypeStyle[] mapTypeStyles; - if (windParams.isShowStreamlets2()) { + if (windParams.isShowStreamlets()) { mapTypeStyles = new MapTypeStyle[11]; @@ -469,7 +469,7 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ add(map, 0, 0); map.setSize("100%", "100%"); - if (windParams.isShowStreamlets2()) { + if (windParams.isShowStreamlets()) { map.addBoundsChangeHandler(new BoundsChangeMapHandler() { @Override public void onEvent(BoundsChangeMapEvent event) { @@ -477,7 +477,7 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() { public void execute() { if (windStreamletsCanvasOverlay.getSwarm() != null) { - windStreamletsCanvasOverlay.getSwarm().onBoundsChanged(); + windStreamletsCanvasOverlay.getSwarm().onBoundsChanged(true, 5); } } }); @@ -576,10 +576,10 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ if (windParams.isShowArrows()) { windFieldCanvasOverlay = new WindFieldCanvasOverlay(map, SimulatorMapOverlaysZIndexes.WINDFIELD_ZINDEX, timer, windParams); } - if (windParams.isShowStreamlets()) { + if (windParams.isShowLineGuides()) { windLineGuidesCanvasOverlay = new WindLineGuidesCanvasOverlay(map, SimulatorMapOverlaysZIndexes.WINDSTREAMLETS_ZINDEX, timer, xRes); } - if (windParams.isShowStreamlets2()) { + if (windParams.isShowStreamlets()) { windStreamletsCanvasOverlay = new WindStreamletsCanvasOverlay(this, SimulatorMapOverlaysZIndexes.WINDFIELD_ZINDEX, timer, streamletPars, windParams); } if (windParams.isShowGrid()) { @@ -668,10 +668,10 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ if (windParams.isShowArrows()) { windFieldCanvasOverlay.addToMap(); } - if (windParams.isShowStreamlets()) { + if (windParams.isShowLineGuides()) { windLineGuidesCanvasOverlay.addToMap(); } - if (windParams.isShowStreamlets2()) { + if (windParams.isShowStreamlets()) { windStreamletsCanvasOverlay.addToMap(); } refreshWindFieldOverlay(wl); @@ -679,10 +679,10 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ if (windParams.isShowArrows()) { timeListeners.add(windFieldCanvasOverlay); } - if (windParams.isShowStreamlets()) { + if (windParams.isShowLineGuides()) { timeListeners.add(windLineGuidesCanvasOverlay); } - if (windParams.isShowStreamlets2()) { + if (windParams.isShowStreamlets()) { timeListeners.add(windStreamletsCanvasOverlay); windStreamletsCanvasOverlay.setVisible(true); } @@ -695,7 +695,7 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ timePanel.setMinMax(windParams.getStartTime(), windParams.getEndTime(), true); timePanel.resetTimeSlider(); timePanel.timeChanged(windParams.getStartTime(), null); - if (windParams.isShowStreamlets2()) { + if (windParams.isShowStreamlets()) { windStreamletsCanvasOverlay.setEndDate(windParams.getEndTime()); } @@ -728,7 +728,7 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ if (this.windParams.isShowArrows()) { this.windFieldCanvasOverlay.draw(); } - if (this.windParams.isShowStreamlets()) { + if (this.windParams.isShowLineGuides()) { this.windLineGuidesCanvasOverlay.draw(); } if (this.windParams.isShowGrid()) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/StreamletParameters.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/StreamletParameters.java index 9441dadb635..1781281d0e0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/StreamletParameters.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/StreamletParameters.java @@ -2,7 +2,7 @@ package com.sap.sailing.gwt.ui.simulator; public class StreamletParameters { - public int particles = 0; + public boolean macroWeather = false; public double motionScale = 1.0; public double swarmScale = 1.0; public int detailZoom = 15; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/WindFieldCanvasOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/WindFieldCanvasOverlay.java index 11e003fe135..0498388d308 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/WindFieldCanvasOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/WindFieldCanvasOverlay.java @@ -14,6 +14,7 @@ import java.util.logging.Logger; import com.google.gwt.canvas.dom.client.Context2d; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.maps.client.MapWidget; +import com.sap.sailing.domain.common.AbstractBearing; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.gwt.ui.shared.SimulatorWindDTO; import com.sap.sailing.gwt.ui.shared.WindFieldDTO; @@ -149,7 +150,7 @@ public class WindFieldCanvasOverlay extends FullCanvasOverlay implements TimeLis while (windDTOIter.hasNext()) { final SimulatorWindDTO windDTO = windDTOIter.next(); //System.out.println("wind angle: "+windDTO.trueWindBearingDeg); - final DegreeBearingImpl dbi = new DegreeBearingImpl(windDTO.trueWindBearingDeg); + final AbstractBearing dbi = new DegreeBearingImpl(windDTO.trueWindBearingDeg); drawScaledArrow(windDTO, dbi.getRadians(), ++index, drawHead); } final String title = "Wind Field at " + windDTOList.size() + " points."; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/WindLineGuidesCanvasOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/WindLineGuidesCanvasOverlay.java index c1a7a3799d8..36aefd80beb 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/WindLineGuidesCanvasOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/WindLineGuidesCanvasOverlay.java @@ -15,6 +15,7 @@ import com.google.gwt.canvas.dom.client.Context2d; import com.google.gwt.maps.client.MapWidget; import com.google.gwt.maps.client.base.LatLng; import com.google.gwt.maps.client.base.Point; +import com.sap.sailing.domain.common.AbstractBearing; import com.sap.sailing.domain.common.dto.PositionDTO; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.gwt.ui.shared.SimulatorWindDTO; @@ -158,7 +159,7 @@ public class WindLineGuidesCanvasOverlay extends FullCanvasOverlay implements Ti SimulatorWindDTO windDTO = windDTOIter.next(); //System.out.println("wind angle: "+index+", "+windDTO.trueWindBearingDeg); if (((index % xRes) > 0)&&((index % xRes) < (xRes-1))) { - DegreeBearingImpl dbi = new DegreeBearingImpl(windDTO.trueWindBearingDeg); + AbstractBearing dbi = new DegreeBearingImpl(windDTO.trueWindBearingDeg); drawScaledArrow(windDTO, dbi.getRadians(), index, pxLength, drawHead); } index++; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/WindStreamletsCanvasOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/WindStreamletsCanvasOverlay.java index ac1f5398734..8b5ca2ee308 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/WindStreamletsCanvasOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/WindStreamletsCanvasOverlay.java @@ -30,7 +30,7 @@ public class WindStreamletsCanvasOverlay extends FullCanvasOverlay implements Ti private final Timer timer; private Date endDate; - private int nParticles; + private boolean macroWeather; private Swarm swarm; public WindStreamletsCanvasOverlay(SimulatorMap simulatorMap, int zIndex, final Timer timer, final StreamletParameters streamletPars, @@ -40,7 +40,7 @@ public class WindStreamletsCanvasOverlay extends FullCanvasOverlay implements Ti this.timer = timer; this.windParams = windParams; this.streamletPars = streamletPars; - this.nParticles = this.simulatorMap.getMainPanel().particles; + this.macroWeather = this.simulatorMap.getMainPanel().macroWeather; windFieldDTO = null; getCanvas().getElement().setId("swarm-display"); } @@ -122,7 +122,8 @@ public class WindStreamletsCanvasOverlay extends FullCanvasOverlay implements Ti protected void draw() { super.draw(); if (mapProjection != null) { - if ((nParticles > 0) && (swarm == null)) { + if ((macroWeather) && (swarm == null)) { + macroWeather = false; SimulatorJSBundle bundle = GWT.create(SimulatorJSBundle.class); String jsonStr = bundle.windStreamletsDataJS().getText(); RectField f = RectField.read(jsonStr.substring(19, jsonStr.length() - 1), false, streamletPars); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/racemap/FullCanvasOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/racemap/FullCanvasOverlay.java index c02d2d098e9..770c07cdd44 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/racemap/FullCanvasOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/racemap/FullCanvasOverlay.java @@ -13,6 +13,7 @@ import com.google.gwt.user.client.ui.RequiresResize; import com.sap.sailing.domain.common.dto.PositionDTO; import com.sap.sailing.gwt.ui.shared.SimulatorWindDTO; import com.sap.sailing.gwt.ui.shared.racemap.CanvasOverlayV3; +import com.sap.sailing.gwt.ui.simulator.streamlets.Vector; /** * This class extends @CanvasOverlayV3 to provide the functionality that the canvas always covers the @@ -25,10 +26,12 @@ import com.sap.sailing.gwt.ui.shared.racemap.CanvasOverlayV3; public abstract class FullCanvasOverlay extends CanvasOverlayV3 implements RequiresResize { /** the x coordinate where the canvas is placed */ - private double widgetPosLeft = 0; + protected double widgetPosLeft = 0; /** the y coordinate where the canvas element is placed */ - private double widgetPosTop = 0; + protected double widgetPosTop = 0; + + protected Vector diffPx; public String pointColor = "Red"; public String textColor = "Black"; @@ -43,6 +46,7 @@ public abstract class FullCanvasOverlay extends CanvasOverlayV3 implements Requi drawCenterChanged(); }; }); + diffPx = new Vector(0, 0); } /** @@ -268,4 +272,8 @@ public abstract class FullCanvasOverlay extends CanvasOverlayV3 implements Requi public void setWidgetPosTop(double widgetPosTop) { this.widgetPosTop = widgetPosTop; } + + public Vector getDiffPx() { + return diffPx; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/racemap/MovingCanvasOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/racemap/MovingCanvasOverlay.java new file mode 100644 index 00000000000..a697adabf3a --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/racemap/MovingCanvasOverlay.java @@ -0,0 +1,80 @@ +package com.sap.sailing.gwt.ui.simulator.racemap; + +import com.google.gwt.canvas.dom.client.Context2d; +import com.google.gwt.canvas.dom.client.ImageData; +import com.google.gwt.maps.client.MapWidget; +import com.google.gwt.maps.client.base.LatLng; +import com.google.gwt.maps.client.base.Point; +import com.sap.sailing.gwt.ui.simulator.streamlets.Vector; + +/** + * This class extends {@link FullCanvasOverlay} to provide the functionality that the canvas covers the + * full viewable area of the map and moves previously drawn content according to drag and zoom. + * + * @author Christopher Ronnewinkel (D036654) + * + */ +public abstract class MovingCanvasOverlay extends FullCanvasOverlay { + + private LatLng nw; + + public MovingCanvasOverlay(MapWidget map, int zIndex) { + super(map, zIndex); + } + + @Override + public void setCanvasSettings() { + + // do nothing, if mapProjection is not available + if (mapProjection == null) + return; + + int canvasWidth = getMap().getDiv().getClientWidth(); + int canvasHeight = getMap().getDiv().getClientHeight(); + + // calculate pixel-positions of old and new canvas-bounds using the same, current mapProjection + // start from LatLng, as the canvas might have jumped to new bounds that do not intersect with the old bounds + Point nwOldPx; + if (nw == null) { + nwOldPx = null; + } else { + nwOldPx = mapProjection.fromLatLngToDivPixel(nw); + } + nw = LatLng.newInstance(getMap().getBounds().getNorthEast().getLatitude(), getMap().getBounds().getSouthWest().getLongitude()); + Point nwNewPx = mapProjection.fromLatLngToDivPixel(nw); + widgetPosLeft = Math.round(nwNewPx.getX()); + widgetPosTop = Math.round(nwNewPx.getY()); + + // calculate the translation-vector between old and new origin in pixels + if (nwOldPx == null) { + diffPx = new Vector(0, 0); + } else { + double oldPosLeft = Math.round(nwOldPx.getX()); + double oldPosTop = Math.round(nwOldPx.getY()); + diffPx = new Vector(oldPosLeft - widgetPosLeft, oldPosTop - widgetPosTop); + } + + // store canvas-content, because setWidth() and setHeight() will clear canvas and change Context2d + Context2d ctxt = canvas.getContext2d(); + ImageData canvasContent = ctxt.getImageData(0, 0, canvas.getElement().getClientWidth(), canvas.getElement().getClientHeight()); + canvas.setWidth(String.valueOf(canvasWidth)); + canvas.setHeight(String.valueOf(canvasHeight)); + canvas.setCoordinateSpaceWidth(canvasWidth); + canvas.setCoordinateSpaceHeight(canvasHeight); + // get updated Context2d and restore canvas-content moved by translation-vector + ctxt = canvas.getContext2d(); + ctxt.putImageData(canvasContent, diffPx.x, diffPx.y); + + // update canvas position + setCanvasPosition(widgetPosLeft, widgetPosTop); + } + + @Override + protected void draw() { + // do nothing; setCanvasSettings() is called directly + } + + public void initCanvasOrigin() { + nw = null; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/RectField.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/RectField.java index 0b26c72da41..378b9c742ef 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/RectField.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/RectField.java @@ -35,7 +35,7 @@ public class RectField implements VectorField { this.x1 = x1; this.y0 = y0; this.y1 = y1; - this.motionFactor = 0.9 * parameters.motionScale; + this.motionFactor = 0.009 * parameters.motionScale; this.field = field; this.w = field.length; this.h = field[0].length; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/SimulatorField.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/SimulatorField.java index 30f545edcc0..c26918cfedf 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/SimulatorField.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/SimulatorField.java @@ -69,7 +69,7 @@ public class SimulatorField implements VectorField { this.bdB = new DegreePosition((this.rcStart.getLatDeg() - this.rcEnd.getLatDeg()) * bdPhi, (this.rcStart.getLngDeg() - this.rcEnd.getLngDeg()) * bdPhi); this.xScale = windData.windData.xScale; - this.motionFactor = 0.07 * parameters.motionScale; + this.motionFactor = 0.0007 * parameters.motionScale; this.lineBase = parameters.lineBase; this.lineScale = parameters.lineScale; List gridData = windData.getMatrix(); @@ -137,6 +137,9 @@ public class SimulatorField implements VectorField { private Vector interpolate(Position p, Date at) { int step = (int) ((at.getTime() - startTime.getTime()) / timeStep.asMillis()); + if (step >= this.data.length) { + step = this.data.length - 1; + } Neighbors idx = getNeighbors(p); if (swarmDebug && ((idx.xTop >= (this.resX + 2 * this.borderX)) || (idx.yTop >= (this.resY + 2 * this.borderY)))) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Swarm.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Swarm.java index a94ba2586c4..b302eb5b2f0 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Swarm.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Swarm.java @@ -24,6 +24,9 @@ public class Swarm implements TimeListener { private Mercator projection; private final VectorField field; + private boolean zoomChanged = false; + private Vector diffPx; + /** * The number of particles to show. After {@link #updateBounds()} has been run, this also reflects the size of the * {@link #particles} array. Note that since elements in {@link #particles} can be null, this number @@ -59,21 +62,21 @@ public class Swarm implements TimeListener { this.parameters = streamletPars; timePoint = timer.getTime(); cosineOfAverageLatitude = 1.0; // default to equator + diffPx = new Vector(0, 0); } public void start(int animationIntervalMillis) { + fullcanvas.setCanvasSettings(); projection = new Mercator(fullcanvas, map); projection.calibrate(); - this.updateBounds(); - Context2d ctxt = canvas.getContext2d(); - ctxt.setFillStyle("red"); + updateBounds(); particles = this.createParticles(); - this.swarmContinue = true; + swarmContinue = true; startLoop(animationIntervalMillis); } public void stop() { - this.swarmContinue = false; + swarmContinue = false; } private Particle createParticle() { @@ -125,9 +128,12 @@ public class Swarm implements TimeListener { return newParticles; } - public void onBoundsChanged() { - projection.clearCanvas(); - swarmPause = 5; + public void onBoundsChanged(boolean zoomChanged, int swarmPause) { + this.zoomChanged |= zoomChanged; + if (this.zoomChanged) { + projection.clearCanvas(); + } + this.swarmPause = swarmPause; } private void updateBounds() { @@ -141,11 +147,14 @@ public class Swarm implements TimeListener { Vector boundsNEpx = this.projection.latlng2pixel(visibleBoundsOfField.getNorthEast()); double boundsWidthpx = Math.abs(boundsNEpx.x - boundsSWpx.x); double boundsHeightpx = Math.abs(boundsSWpx.y - boundsNEpx.y); - this.nParticles = (int) Math.round(Math.sqrt(boundsWidthpx * boundsHeightpx) * this.field.getParticleFactor() - * this.parameters.swarmScale); + this.nParticles = (int)Math.round(Math.sqrt(boundsWidthpx * boundsHeightpx) * this.field.getParticleFactor() * this.parameters.swarmScale); cosineOfAverageLatitude = Math.cos((visibleBoundsOfField.getSouthWest().getLatRad()+visibleBoundsOfField.getNorthEast().getLatRad())/2); }; + //native void log(String message) /*-{ + // console.log( message ); + //}-*/; + private void startLoop(final int animationIntervalMillis) { // Create animation-loop based on timer timeout loopTimer = new com.google.gwt.user.client.Timer() { @@ -157,16 +166,25 @@ public class Swarm implements TimeListener { fullcanvas.setCanvasSettings(); projection.calibrate(); updateBounds(); - particles = createParticles(); + if (zoomChanged) { + diffPx = new Vector(0,0); + particles = createParticles(); + zoomChanged = false; + } else { + diffPx = fullcanvas.getDiffPx(); + } swarmPause = 0; } if ((!swarmOffScreen) && (swarmPause == 0)) { - execute(); + execute(diffPx); + diffPx = new Vector(0, 0); } Date time1 = new Date(); if (swarmContinue) { // wait at least 10ms for the next iteration; try to get one iteration done every animationIntervalMillis if possible - loopTimer.schedule((int) Math.max(10, animationIntervalMillis - (time1.getTime() - time0.getTime()))); + long timeDelta = time1.getTime() - time0.getTime(); + //log("fps: "+(1000.0/timeDelta)); + loopTimer.schedule((int) Math.max(10, animationIntervalMillis - timeDelta)); } else { projection.clearCanvas(); } @@ -203,8 +221,8 @@ public class Swarm implements TimeListener { * Moves each particle by its vector {@link Particle#v} multiplied by the speed which is 0.01 times the * {@link VectorField#getMotionScale(int)} at the map's current zoom level. */ - private boolean execute() { - double speed = 0.01 * field.getMotionScale(map.getZoom()); + private boolean execute(Vector diffPx) { + double speed = field.getMotionScale(map.getZoom()); for (int idx = 0; idx < particles.length; idx++) { Particle particle = particles[idx]; if (particle != null && particle.stepsToLive > 0 && particle.v != null) { @@ -212,6 +230,12 @@ public class Swarm implements TimeListener { // update its currentPosition, currentPixelCoordinate and previousPixelCoordinate fields; // also, its particle.v field is updated based on its new position from the vector field particle.previousPixelCoordinate = particle.currentPixelCoordinate; + if (diffPx.x != 0) { + particle.previousPixelCoordinate.x += diffPx.x; + } + if (diffPx.y != 0) { + particle.previousPixelCoordinate.y += diffPx.y; + } double latDeg = particle.currentPosition.getLatDeg() + speed * particle.v.y; double lngDeg = particle.currentPosition.getLngDeg() + speed * particle.v.x / cosineOfAverageLatitude; particle.currentPosition = new DegreePosition(latDeg, lngDeg); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/WindInfoForRaceVectorField.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/WindInfoForRaceVectorField.java index 6ac8207570d..9da6d31e785 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/WindInfoForRaceVectorField.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/WindInfoForRaceVectorField.java @@ -48,6 +48,7 @@ public class WindInfoForRaceVectorField implements VectorField, AverageLatitudeP private final Weigher> weigher; private double averageLatitudeDeg; private double averageLatitudeCosine; + private double knotsInDegreePerFrame; private final Comparator windByMeasureTimePointComparator = new Comparator() { @Override @@ -57,8 +58,9 @@ public class WindInfoForRaceVectorField implements VectorField, AverageLatitudeP }; private final WindInfoForRaceDTO windInfoForRace; - public WindInfoForRaceVectorField(WindInfoForRaceDTO windInfoForRace) { + public WindInfoForRaceVectorField(WindInfoForRaceDTO windInfoForRace, double framesPerSecond) { this.windInfoForRace = windInfoForRace; + this.knotsInDegreePerFrame = 1.0 / (60*3600) / framesPerSecond; // 1kn = 1/60 deg/h = 1/(60*3600) deg/s weigher = new PositionDTOAndDateWeigher(/* half confidence after milliseconds */3000, /* halfConfidenceDistance */new MeterDistance(100), this); } @@ -156,7 +158,7 @@ public class WindInfoForRaceVectorField implements VectorField, AverageLatitudeP public double getMotionScale(int zoomLevel) { // This implementation is copied from SimulatorField, hoping it does something useful in combination with // the Swarm implementation. - return 0.05 * Math.pow(1.6, Math.min(1.0, 6.0 - zoomLevel)); + return 2.0 * knotsInDegreePerFrame * Math.pow(1.8, Math.min(15.0, 20.0 - zoomLevel)); } @Override @@ -188,7 +190,7 @@ public class WindInfoForRaceVectorField implements VectorField, AverageLatitudeP */ @Override public String getColor(double speed) { - return "rgba(255,255,255,"+Math.min(1.0, 0.4+0.8*speed/MAX_WIND_SPEED_IN_KNOTS)+")"; + return "rgba(255,255,255,"+Math.min(1.0, 0.5+0.6*speed/MAX_WIND_SPEED_IN_KNOTS)+")"; } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupPanel.java index 443924b8975..b6becd130b0 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupPanel.java @@ -327,7 +327,7 @@ public class LeaderboardGroupPanel extends SimplePanel implements HasWelcomeWidg int seriesRow = 0; for (SeriesDTO series : regatta.series) { // render the series name - if (! "Default".equals(series.getName())) { + if (!LeaderboardNameConstants.DEFAULT_SERIES_NAME.equals(series.getName())) { seriesGrid.setHTML(seriesRow, 0, TEXTTEMPLATE.textWithClass(series.getName(), 50, STYLE_TABLE_TEXT)); } seriesGridFormatter.setVerticalAlignment(seriesRow, 0, HasVerticalAlignment.ALIGN_MIDDLE); @@ -355,7 +355,7 @@ public class LeaderboardGroupPanel extends SimplePanel implements HasWelcomeWidg FleetDTO fleet = series.getFleets().get(0); List raceColumnsOfSeries = getRacesOfFleet(leaderboard, series, fleet); String displayName = fleet.getName(); - if (! "Default".equals(fleet.getName())) { + if (!LeaderboardNameConstants.DEFAULT_FLEET_NAME.equals(fleet.getName())) { Grid fleetsGrid = new Grid(1, 2); CellFormatter fleetGridsFormatter = fleetsGrid.getCellFormatter(); fleetsGrid.setHTML(0, 0, TEXTTEMPLATE.textWithClass(displayName, 50, STYLE_TABLE_TEXT)); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/SpectatorEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/SpectatorEntryPoint.java index 3a28e470c76..c5124ef84dd 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/SpectatorEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/SpectatorEntryPoint.java @@ -76,7 +76,7 @@ public class SpectatorEntryPoint extends AbstractEntryPoint implements RegattaRe String title = groupName != null ? groupName : stringMessages.overview(); LogoAndTitlePanel logoAndTitlePanel = new LogoAndTitlePanel(title, stringMessages, this); logoAndTitlePanel.addStyleName("LogoAndTitlePanel"); - FlowPanel globalNavigationPanel = new GlobalNavigationPanel(stringMessages, true, null, null); + FlowPanel globalNavigationPanel = new GlobalNavigationPanel(stringMessages, true, null, null, /* event */ null, null); logoAndTitlePanel.add(globalNavigationPanel); rootPanel.add(logoAndTitlePanel); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/tv/TVEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/tv/TVEntryPoint.java index c7eb37cc035..682be7a699b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/tv/TVEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/tv/TVEntryPoint.java @@ -48,8 +48,6 @@ public class TVEntryPoint extends AbstractEntryPoint { final boolean embedded = GwtHttpRequestUtils.getBooleanParameter(PARAM_EMBEDDED, false); final long delayToLiveMillis = Window.Location.getParameter(PARAM_DELAY_TO_LIVE_MILLIS) != null ? Long .valueOf(Window.Location.getParameter(PARAM_DELAY_TO_LIVE_MILLIS)) : 5000l; // default 5s - final boolean showNavigationPanel = GwtHttpRequestUtils.getBooleanParameter( - RaceBoardViewConfiguration.PARAM_VIEW_SHOW_NAVIGATION_PANEL, false /* default */); final boolean showLeaderboard = GwtHttpRequestUtils.getBooleanParameter( RaceBoardViewConfiguration.PARAM_VIEW_SHOW_LEADERBOARD, true /* default */); final boolean showWindChart = GwtHttpRequestUtils.getBooleanParameter( @@ -59,10 +57,10 @@ public class TVEntryPoint extends AbstractEntryPoint { final boolean showCompetitorsChart = GwtHttpRequestUtils.getBooleanParameter( RaceBoardViewConfiguration.PARAM_VIEW_SHOW_COMPETITORSCHART, false /* default */); String activeCompetitorsFilterSetName = GwtHttpRequestUtils.getStringParameter(RaceBoardViewConfiguration.PARAM_VIEW_COMPETITOR_FILTER, null /* default*/); + final String defaultMedia = GwtHttpRequestUtils.getStringParameter(RaceBoardViewConfiguration.PARAM_DEFAULT_MEDIA, null /* default */); final boolean autoSelectMedia = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_AUTOSELECT_MEDIA, false); raceboardViewConfig = new RaceBoardViewConfiguration(activeCompetitorsFilterSetName, showLeaderboard, - showWindChart, showCompetitorsChart, showViewStreamlets, /* canReplayWhileLiveIsPossible */false, autoSelectMedia, - showNavigationPanel); + showWindChart, showCompetitorsChart, showViewStreamlets, /* canReplayWhileLiveIsPossible */false, autoSelectMedia, defaultMedia); sailingService.getLeaderboardNames(new AsyncCallback>() { @Override @@ -70,7 +68,7 @@ public class TVEntryPoint extends AbstractEntryPoint { leaderboardName = Window.Location.getParameter(PARAM_LEADERBOARD_NAME); leaderboardGroupName = Window.Location.getParameter(PARAM_LEADERBOARD_GROUP_NAME); if (leaderboardNames.contains(leaderboardName)) { - createUI(showRaceDetails, embedded, showNavigationPanel, delayToLiveMillis); + createUI(showRaceDetails, embedded, delayToLiveMillis); } else { RootPanel.get().add(new Label(stringMessages.noSuchLeaderboard())); } @@ -83,7 +81,7 @@ public class TVEntryPoint extends AbstractEntryPoint { }); } - private void createUI(boolean showRaceDetails, boolean embedded, boolean showNavigationPanel, long delayToLiveMillis) { + private void createUI(boolean showRaceDetails, boolean embedded, long delayToLiveMillis) { DockLayoutPanel mainPanel = new DockLayoutPanel(Unit.PX); RootLayoutPanel.get().add(mainPanel); LogoAndTitlePanel logoAndTitlePanel = null; @@ -99,7 +97,7 @@ public class TVEntryPoint extends AbstractEntryPoint { tvViewController = new TVViewController(sailingService, mediaService, stringMessages, this, leaderboardGroupName, leaderboardName, userAgent, logoAndTitlePanel, mainPanel, delayToLiveMillis, - showRaceDetails, showNavigationPanel, raceboardViewConfig); + showRaceDetails, raceboardViewConfig); tvViewController.updateTvView(TVViews.Leaderboard); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/tv/TVViewController.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/tv/TVViewController.java index 241a1fd7a79..09e8a974c05 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/tv/TVViewController.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/tv/TVViewController.java @@ -67,7 +67,6 @@ public class TVViewController implements RaceTimesInfoProviderListener { private boolean showRaceDetails; private boolean showWindChart; private final RaceBoardViewConfiguration raceboardViewConfig; - private final boolean showNavigationPanel; /** * @param logoAndTitlePanel @@ -80,8 +79,7 @@ public class TVViewController implements RaceTimesInfoProviderListener { StringMessages stringMessages, ErrorReporter errorReporter, String leaderboardGroupName, String leaderboardName, UserAgentDetails userAgent, LogoAndTitlePanel logoAndTitlePanel, DockLayoutPanel dockPanel, long delayToLiveInMillis, boolean showRaceDetails, - boolean showNavigationPanel, RaceBoardViewConfiguration raceboardViewConfig) { - this.showNavigationPanel = showNavigationPanel; + RaceBoardViewConfiguration raceboardViewConfig) { this.raceboardViewConfig = raceboardViewConfig; this.sailingService = sailingService; this.mediaService = mediaService; @@ -97,7 +95,7 @@ public class TVViewController implements RaceTimesInfoProviderListener { asyncActionsExecutor = new AsyncActionsExecutor(); showWindChart = false; leaderboard = null; - leaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, /* autoExpandFirstRace */ false); + leaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true); leaderboardTimer = new Timer(PlayModes.Live, /* delayBetweenAutoAdvancesInMilliseconds */1000l); leaderboardTimer.setLivePlayDelayInMillis(delayToLiveInMillis); @@ -114,13 +112,16 @@ public class TVViewController implements RaceTimesInfoProviderListener { private LeaderboardPanel createLeaderboardPanel(String leaderboardGroupName, String leaderboardName, boolean showRaceDetails) { CompetitorSelectionModel selectionModel = new CompetitorSelectionModel(/* hasMultiSelection */ true); - LeaderboardPanel leaderboardPanel = new LeaderboardPanel(sailingService, asyncActionsExecutor, leaderboardSettings, - /* preSelectedRace */null, selectionModel, leaderboardTimer, leaderboardGroupName, leaderboardName, errorReporter, stringMessages, - userAgent, showRaceDetails, /* raceTimesInfoProvider */ null, /* autoExpandLastRaceColumn */ false, /* adjustTimerDelay */ true) { + LeaderboardPanel leaderboardPanel = new LeaderboardPanel(sailingService, asyncActionsExecutor, + leaderboardSettings, + /* preSelectedRace */null, selectionModel, leaderboardTimer, leaderboardGroupName, leaderboardName, + errorReporter, stringMessages, userAgent, showRaceDetails, /* competitorSearchTextBox */ null, /* showRegattaRank */ + /* showSelectionCheckbox */false, /* raceTimesInfoProvider */null, false, /* autoExpandLastRaceColumn */ + /* adjustTimerDelay */true, /*autoApplyTopNFilter*/ false, false) { @Override protected void setLeaderboard(LeaderboardDTO leaderboard) { super.setLeaderboard(leaderboard); - TVViewController.this.leaderboard = leaderboard; + TVViewController.this.leaderboard = leaderboard; updateRaceTimesInfoProvider(); } }; @@ -148,8 +149,9 @@ public class TVViewController implements RaceTimesInfoProviderListener { RaceSelectionModel raceSelectionModel = new RaceSelectionModel(); List singletonList = Collections.singletonList(raceToShow); raceSelectionModel.setSelection(singletonList); - RaceBoardPanel raceBoardPanel = new RaceBoardPanel(sailingService, mediaService, asyncActionsExecutor,null, raceboardTimer, raceSelectionModel, leaderboardName, - null, raceboardViewConfig, errorReporter, stringMessages, userAgent, raceTimesInfoProvider, /* showMapControls */ false); + RaceBoardPanel raceBoardPanel = new RaceBoardPanel(sailingService, mediaService, asyncActionsExecutor, null, + raceboardTimer, raceSelectionModel, leaderboardName, null, /* event */null, raceboardViewConfig, + errorReporter, stringMessages, userAgent, raceTimesInfoProvider, /* showMapControls */false); return raceBoardPanel; } @@ -189,11 +191,6 @@ public class TVViewController implements RaceTimesInfoProviderListener { if (showWindChart) { raceBoardPanel.setWindChartVisible(true); } - if (showNavigationPanel) { - FlowPanel toolbarPanel = new FlowPanel(); - toolbarPanel.add(raceBoardPanel.getComponentControlsPanel()); - dockPanel.addNorth(toolbarPanel, 40); - } FlowPanel timePanel = createTimePanel(raceBoardPanel); dockPanel.addSouth(timePanel, 90); dockPanel.add(raceBoardPanel); diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/Home.gwt.xml b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/Home.gwt.xml index 8464cfab79b..9816453223a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/Home.gwt.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/Home.gwt.xml @@ -4,9 +4,12 @@ - + + + + @@ -56,7 +59,10 @@ + + @@ -74,6 +80,7 @@ + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/RaceBoard.gwt.xml b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/RaceBoard.gwt.xml old mode 100644 new mode 100755 index 362f099acc5..43aa2f4ea4c --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/RaceBoard.gwt.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/RaceBoard.gwt.xml @@ -30,9 +30,11 @@ - + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/SAP_RV_Clear.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/SAP_RV_Clear.png new file mode 100755 index 00000000000..86749c782d6 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/SAP_RV_Clear.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/SAP_RV_CompetitorsFilter_ACTIVE.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/SAP_RV_CompetitorsFilter_ACTIVE.png new file mode 100644 index 00000000000..a04e4677f48 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/SAP_RV_CompetitorsFilter_ACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/SAP_RV_CompetitorsFilter_INACTIVE.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/SAP_RV_CompetitorsFilter_INACTIVE.png new file mode 100755 index 00000000000..cf0bcf0d2be Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/SAP_RV_CompetitorsFilter_INACTIVE.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/SAP_RV_Search.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/SAP_RV_Search.png new file mode 100755 index 00000000000..ea9b88030f9 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/SAP_RV_Search.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/SAP_RV_Settings.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/SAP_RV_Settings.png new file mode 100755 index 00000000000..dbaf291466e Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/SAP_RV_Settings.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/12MR.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/12MR.png new file mode 100644 index 00000000000..615225c63f8 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/12MR.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/29ER.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/29ER.png new file mode 100644 index 00000000000..676c0875b07 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/29ER.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/2_4M.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/2_4M.png new file mode 100644 index 00000000000..56919613066 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/2_4M.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/420.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/420.png new file mode 100644 index 00000000000..af658f2c62e Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/420.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/470.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/470.png new file mode 100644 index 00000000000..9c9d4efff31 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/470.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/49ER.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/49ER.png new file mode 100644 index 00000000000..5560377123f Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/49ER.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/49ER_FX.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/49ER_FX.png new file mode 100644 index 00000000000..22d9bb386b4 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/49ER_FX.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/505.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/505.png new file mode 100644 index 00000000000..4ad024f0371 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/505.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/ALBIN_EXPRESS.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/ALBIN_EXPRESS.png new file mode 100644 index 00000000000..86c1207660a Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/ALBIN_EXPRESS.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/A_CAT.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/A_CAT.png new file mode 100644 index 00000000000..b20ba830842 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/A_CAT.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/B_ONE.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/B_ONE.png new file mode 100644 index 00000000000..b9872adacd8 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/B_ONE.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/CONTENDER.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/CONTENDER.png new file mode 100644 index 00000000000..3955d5f1195 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/CONTENDER.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/DRAGON.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/DRAGON.png new file mode 100644 index 00000000000..aee2642bf46 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/DRAGON.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/DYAS.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/DYAS.png new file mode 100644 index 00000000000..7026aa36b76 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/DYAS.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/EUROPE.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/EUROPE.png new file mode 100644 index 00000000000..3adad7c5c9d Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/EUROPE.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/EXTREME40.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/EXTREME40.png new file mode 100644 index 00000000000..8ba00784fa8 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/EXTREME40.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/F16.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/F16.png new file mode 100644 index 00000000000..48ef1c019a0 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/F16.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/F18.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/F18.png new file mode 100644 index 00000000000..d89110cab36 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/F18.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/FARR_30.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/FARR_30.png new file mode 100644 index 00000000000..8c1e9b4f674 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/FARR_30.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/FINN.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/FINN.png new file mode 100644 index 00000000000..7acca0555cc Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/FINN.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/FLYING_DUTCHMAN.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/FLYING_DUTCHMAN.png new file mode 100644 index 00000000000..575f2921ecf Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/FLYING_DUTCHMAN.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/FOLKBOAT.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/FOLKBOAT.png new file mode 100644 index 00000000000..7f3361ef958 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/FOLKBOAT.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/GENERIC_BOATCLASS.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/GENERIC_BOATCLASS.png new file mode 100644 index 00000000000..b6c4902eea3 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/GENERIC_BOATCLASS.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/HOBIE.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/HOBIE.png new file mode 100644 index 00000000000..888e1e8ef78 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/HOBIE.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/H_BOAT.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/H_BOAT.png new file mode 100644 index 00000000000..a642b8343a3 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/H_BOAT.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/INTERNATIONAL_14.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/INTERNATIONAL_14.png new file mode 100644 index 00000000000..115828d475f Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/INTERNATIONAL_14.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/J24.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/J24.png new file mode 100644 index 00000000000..6321c52bcfb Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/J24.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/J70.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/J70.png new file mode 100644 index 00000000000..13a944cbaad Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/J70.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/J80.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/J80.png new file mode 100644 index 00000000000..a25dd79b435 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/J80.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/LASER.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/LASER.png new file mode 100644 index 00000000000..19abde3e557 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/LASER.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/LASER_SB3.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/LASER_SB3.png new file mode 100644 index 00000000000..651cb0c1b18 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/LASER_SB3.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/MELGES_24.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/MELGES_24.png new file mode 100644 index 00000000000..a7e3f071da6 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/MELGES_24.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/MINI_TRANSAT.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/MINI_TRANSAT.png new file mode 100644 index 00000000000..3eea1d31ca3 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/MINI_TRANSAT.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/MUSTO_SKIFF.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/MUSTO_SKIFF.png new file mode 100644 index 00000000000..7be66a7b5e4 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/MUSTO_SKIFF.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/NACRA_17.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/NACRA_17.png new file mode 100644 index 00000000000..9d4c9c8b9bf Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/NACRA_17.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/OK.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/OK.png new file mode 100644 index 00000000000..35c86a89266 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/OK.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/OPENBIC.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/OPENBIC.png new file mode 100644 index 00000000000..856dd4fa0af Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/OPENBIC.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/OPTIMIST.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/OPTIMIST.png new file mode 100644 index 00000000000..585f8e8a359 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/OPTIMIST.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/PLATU_25.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/PLATU_25.png new file mode 100644 index 00000000000..bf12a761b7d Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/PLATU_25.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/RS_X.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/RS_X.png new file mode 100644 index 00000000000..5eaa0078def Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/RS_X.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/SONAR.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/SONAR.png new file mode 100644 index 00000000000..5862653b560 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/SONAR.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/STAR.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/STAR.png new file mode 100644 index 00000000000..581a7b8d803 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/STAR.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/STREAMLINE.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/STREAMLINE.png new file mode 100644 index 00000000000..6cb8b3acc4a Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/STREAMLINE.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/SWAN_45.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/SWAN_45.png new file mode 100644 index 00000000000..895a1753231 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/SWAN_45.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/TORNADO.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/TORNADO.png new file mode 100644 index 00000000000..f9a80ba0b6e Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/TORNADO.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/X_99.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/X_99.png new file mode 100644 index 00000000000..37cdf8ae78c Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/X_99.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/sap-logo-overlay.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/sap-logo-overlay.png new file mode 100755 index 00000000000..00a339fdc10 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/sap-logo-overlay.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/12MR.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/12MR.svg new file mode 100644 index 00000000000..b82fff62d46 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/12MR.svg @@ -0,0 +1,20 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/29ER.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/29ER.svg new file mode 100644 index 00000000000..ca5ba68a6cf --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/29ER.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/2_4M.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/2_4M.svg new file mode 100644 index 00000000000..5ef8e54f9cd --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/2_4M.svg @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/420.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/420.svg new file mode 100644 index 00000000000..1cee92da76e --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/420.svg @@ -0,0 +1,9 @@ + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/470.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/470.svg new file mode 100644 index 00000000000..067a3d03f77 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/470.svg @@ -0,0 +1,97 @@ + + + +image/svg+xml470 + 470 + + + + + + + + + + Pagina-1 + + + Blad.320 + + + + \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/49ER_FX.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/49ER_FX.svg new file mode 100644 index 00000000000..0b5f691f2fa --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/49ER_FX.svg @@ -0,0 +1,21 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/49er.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/49er.svg new file mode 100644 index 00000000000..10ff819eb9f --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/49er.svg @@ -0,0 +1,88 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/505.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/505.svg new file mode 100644 index 00000000000..bcb050bb1b0 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/505.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/ALBIN_EXPRESS.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/ALBIN_EXPRESS.svg new file mode 100644 index 00000000000..54b59b6c0b1 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/ALBIN_EXPRESS.svg @@ -0,0 +1,67 @@ + + + + + + image/svg+xml + + + + + + + + 'express.blend', Plane.002, (Blender 2.63 (sub 0)) + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/A_CAT.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/A_CAT.svg new file mode 100644 index 00000000000..675515a7c8f --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/A_CAT.svg @@ -0,0 +1,19 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/B_ONE.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/B_ONE.svg new file mode 100644 index 00000000000..6c201317dd5 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/B_ONE.svg @@ -0,0 +1,36 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/CONTENDER.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/CONTENDER.svg new file mode 100644 index 00000000000..c9a1bb97ef5 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/CONTENDER.svg @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/DRAGON.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/DRAGON.svg new file mode 100644 index 00000000000..7f552ce387a --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/DRAGON.svg @@ -0,0 +1,58 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/DYAS.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/DYAS.svg new file mode 100644 index 00000000000..02bbd131359 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/DYAS.svg @@ -0,0 +1,76 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/EUROPE.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/EUROPE.svg new file mode 100644 index 00000000000..1c1bd4fd6dc --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/EUROPE.svg @@ -0,0 +1,141 @@ + + + + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/EXTREME40.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/EXTREME40.svg new file mode 100644 index 00000000000..914f0f65c94 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/EXTREME40.svg @@ -0,0 +1,309 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/F16.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/F16.svg new file mode 100644 index 00000000000..831c21ea859 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/F16.svg @@ -0,0 +1,44 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/F18.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/F18.svg new file mode 100644 index 00000000000..401533dfa7f --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/F18.svg @@ -0,0 +1,167 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/FARR_30.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/FARR_30.svg new file mode 100644 index 00000000000..7ba7c3b8bda --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/FARR_30.svg @@ -0,0 +1,50 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/FINN.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/FINN.svg new file mode 100644 index 00000000000..a9aadc6b34c --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/FINN.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/FLYING_DUTCHMAN.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/FLYING_DUTCHMAN.svg new file mode 100644 index 00000000000..22f456c39bf --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/FLYING_DUTCHMAN.svg @@ -0,0 +1,23 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/FOLKBOAT.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/FOLKBOAT.svg new file mode 100644 index 00000000000..da461b3b38b --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/FOLKBOAT.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/HOBIE.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/HOBIE.svg new file mode 100644 index 00000000000..f6426917099 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/HOBIE.svg @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/H_BOAT.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/H_BOAT.svg new file mode 100644 index 00000000000..031d0f60929 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/H_BOAT.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/INTERNATIONAL_14.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/INTERNATIONAL_14.svg new file mode 100644 index 00000000000..40e526db541 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/INTERNATIONAL_14.svg @@ -0,0 +1,106 @@ + + + + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eJzdWntz27gR79+a0XdAp5OO3cYSQRJ8OJ3O8Jlzzw/VchJP44yGIiGZZ4pU+bBjf/ouwKclutKl +aeNeFMng7gK7v93FAiT45veT6ZERJHN6JI0ENBy8eWOl1MuT9BhxMjqJoiLLU0Y6uDxEWBgJTMo4 +0WaV5EeaZmESHzc8l/U+sECa/TDKVZhHFGg38O+Mpn6RPt7c+Lepd3Nj0+wuT9Y3Nz9HXpbR+ImG +/i2NR9n98ubmJM4Rlo8WSZLTdJbSgJEPGytBq+3lMLA+xmQsCoICNhwTGU3OmIyZFHEQxksz+XqM +FAmJAtIEgjQsM+5P4SXNNkRGWFR1kBspoohBWB5pKhA0LI0kVcSsm534xYrG+SRNfJplVhIlaXaM +zMjz74BvnJCZG0YUfLDycqRwjxgn0owLfsi8JZjL25yuzk5WQJrSPAcrYBzuwMv3Znd4oPLPwecJ +cxEMnN7R+MthGQelHHpK117KXQKDQSzgcwBMZNOFV0R5qRO1UtDMD3nA/4wu1oyS8V4KKMLVt/zg +8qM1hPpzhNn/Vmy7xdoikpBc6plMbKZDLAUUAckE2GLVAW8p4KC5kcyDV3S1jiDcPFaySEYEfiFC +uqRsXNYdIA5cGIs6IiOJCaosrnJ5UYm1EaX3IX04RudJTKtQGmk+DZ8gZBoRygHqRiVwWUQ0/RCH +OXhP5DS9jMdZEtAIVLcDuZHHI9wBy38riSsvXdIccjKJipzPJ02oeefF6tR7pCzRcKXkYk3jq+Qj +N/iIKCzztcpEAVyBBUFEiiwjsQwoBthSJ5z1yGwYNkg9vMoiNYGUvEjDZRgft64rs02aTbw1TS+p +nx/DdJJFRHSChJp5BiCgVxW8KsXfp2HQZrgqIq384ehHWuer198KJLgsz2lcTQsnDqxkxUKV8elN +QRXMkyhZVtz2gvNAZ7GucHLCDCI7ScOYWTIcnAMPIzPuSrxPvSAEBVCrPsSxt6IBWlYkhGHC9VEB +hYjMYDj4PBz8ZThwXddxbddyTddwdVdzVVdxiSu7kiu62BUc13Ec27Ec0zEc3dEc1VEc4siO5IgO +dgTbtR3bti3btA1bt7XhwFZtxSa2bEu2aGNbsFzLsWzLskzLsHRLs1RLsYglW5IlWtgSTNd0TNu0 +TNM0TN3UTNVUTGLKpmSKJjbBl4ZrOIZtWIZpGIZuaIZqKAYxZEMyRAMbgu7qjm7rlm7qhq7rmq7q +ik50WZd0Uce6oLmao9mapZmaoeka2KipmqIRTdYkTdSwJqiu6qi2aqmmaqi6CmVUVVSiyirUURWr +guIqjmIrlmIqhqIrmqIqikIUWZEUUcEK2Ehc4hCbWMQkBuSYRlSiEEJkIhGRYCLIruzItmzJpmzI +uqzJqqzIRJZlCcoAlgXJlRzJlizJlAzIX7BRUiVFIpIsSZIoYUkQXdERbdESTdEQdVETVVERiSiL +kgjlXxSwix1sYwub2MA61rCKFUywjCUsYozBRgHCKUBABFMAp8EEBGACmCbA4AJm0/evIITezMyU +pwebFURgk5PRppB5sw7JzFhC8suW3xAY17SrZIVkr1O1pXTT++jo3yc+n16cx0pc/hhRGH78c5w8 +xPwKHUOyf57mXhx4afDlEI3PIe/RWxCahlCHaSMloIsSWFtcrj1GuazJdWG/NhiluXpkV3+D1i9A +e0AyOkOfvwgoGALvknkCBoJZNZ54YDh6NxygMSxmrMGNB8Ad03f7YOJFUINoadRkvmnc5uLKWddP +zAsbjIk/HEzMVmUz8K/z92mY5c99vA2slPnG+GKhZE4fV/MkYkP9rqbDYJvUb9GgzSZRwatmUqxP +4kUCeMpd48TLb2FPROMgg11gSTPiZcQ2d0W8pPHRP4rMy59WSVBETKLLG3nh+nC3AtgSxhdxqSkt +slt0lSRRo+0Mdq00XYdxRqMNZWYYBcgrFmiy8IJ9lEE4XlbERvs+aiwvikJYWNa3od+n6eeWT3s1 +9gh8oundEy2W++if+typfaqna/oUelGv1ue8/RQyZy7COABt0yLMaaOIDc2W+CXNaP6UU8Q20sBl +HdxOh33wQLWAzqhkNxp+upFUOYq2UvCnghER3A8guKO5Swtmxl5u4zMpzFattzqUsr2XvbDbSWlr +Lr+s/7b9X5qoVXmpd7NwY+Sx9WNcE1itZpehz3aXXvpYEa7PTs9hr1peHfwhqMSh4k9z0LBEB19X +UQwSR+yH7X4O2TLwZtYvcO9FRS2hozG7g3vGzx/XNXtspKnXb8aK5l4ACL6HFfibrbj30tCbR+z2 +LHs1lrwOQ/7X/njXSvm3UFhTGtdSmxndEYV6lobzImfmvi15b/tAHUPRgT0NmzM/INa/AtuG4azb +HtYe3Ob5+ng8jrORxyrNyE9W4/c0pmnoW7BOJiu2ucvWnk/HeCSMXxixC0/8AaGr4faFkLOO77/R +Fx/rrH798BnGt3um+bcnFkxzmAjn3G07PToP+TM0/Fr91gXTlztR4t/RYA+gcRK/5I8fDrIG8d/P +jf+k6PwfTbT9Xbnhn2zx8GPX6CwK/d/WAs0RTZMi9Sl/Yv+KwW1Y/rjb0vq4YXxJveiVzYTHF1bb +3ajqw5TXiOprP6qHMMhvdyNTZTxSyasEViHoA3dLw+XtHjcPqi6+VnQ1hD548ySHbewpXeTlwcVu +oDDhzCR5bRi3cbyeBf2PcTaDxe3db2TZ/r4r1veD0XkEU1ZSQiRS+dM40ZGX5vPESwPk8yNdjNJm +37dTdJnSxq6dwvM2nuxURHtZVuzYsFO0a8NO4fn2er0tt5F0TCBPvRhuaCEK/iNoDAOUhU/1SEI9 +9Znkmp1nAndVRPxgvJKRBKEugkzqosjXRY4uPfZUOXwqj9Dbw9raRPKs0xnNbnd12coOuUIByGpg +ao0sWIejTQxeFGabtJWX3dU4lNqitRd07g+ME2QUedKY13iZ4a7ciAW0KE9gwUMpFzy6p34OkZl7 +kRf7PQ591icp8iiMKcrp13xP0SxPkzu6hWhD2o/CNcSePbT5Crm3ZK8v7OiyTmlG03uKknuartmz +03bOgYf8JA1o0BMTND5P8ud83M2fOGlHRGHMrUoy/kz7WVZOP753kzifFvOsPAvfSOuKf5r43TTs +cJ3VnAZluOrYtsxJSv0wa/t1tDqxnwStPrnl8DOmq7YsN7iAxx9JN4+FegTq1zF6WJPlYjMYQL32 +1j2yU9jat8a96/F258Bv4+E2O/PfOuTnbzKUx4e485oFbEUxlBqRkPpA9XQ+HBw4c0gOdoh/GlcL +U963Wn44sSETSukZSL8r04u91gGsw9bQGTtX5Oec1w/DQTH855AVEiPd7+QT85PPeutcv3aEVsPB +1rtIKOoQ61uIqO3cQ+p2vR1+GsbDnYChdWLP8GwX4outs16ID1bVUjEmGBolEIwVMKJLjDpEVVMB +UUWsu/cSn3XvIy6GKpFK92BwRu3IhqjixkGaBs1+UtM16nTdSVwM/1R0WKKmNPgVRd8gRh1iFwDR +8LanGmK3uwh7dm5tL1HStQYW0YVtRzfae4mSKjTEBlEvsau9l7gYyiKujKoHAJc0RBmrjQsboIpI +GlUNsat/e8yXiBCWD8PB34c6e6vs+tNw8KFt7v9iApcJAGLd9bTzMgCvPvxEjr9FdZV6YcTK0TLz +YOnx4jjJoWaugcX2QRkspbDk3SYPjMI6NR2guF24w8G/AICu24A= + + + \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/J24.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/J24.svg new file mode 100644 index 00000000000..3da5e8e4b2f --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/J24.svg @@ -0,0 +1,84 @@ + + + + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/J70.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/J70.svg new file mode 100644 index 00000000000..f9f2196a20a --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/J70.svg @@ -0,0 +1,28 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/J80.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/J80.svg new file mode 100644 index 00000000000..8c073225dfe --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/J80.svg @@ -0,0 +1,96 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/LASER.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/LASER.svg new file mode 100644 index 00000000000..02a73070d30 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/LASER.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/LASER_SB3.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/LASER_SB3.svg new file mode 100644 index 00000000000..f97a2666bd1 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/LASER_SB3.svg @@ -0,0 +1,57 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/MELGES_24.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/MELGES_24.svg new file mode 100644 index 00000000000..5311ad99954 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/MELGES_24.svg @@ -0,0 +1,47 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/MINI_TRANSAT.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/MINI_TRANSAT.svg new file mode 100644 index 00000000000..6eba17c3d04 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/MINI_TRANSAT.svg @@ -0,0 +1,154 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + classemini + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/MUSTO_SKIFF.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/MUSTO_SKIFF.svg new file mode 100644 index 00000000000..e9a780d0371 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/MUSTO_SKIFF.svg @@ -0,0 +1,91 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/NACRA_17.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/NACRA_17.svg new file mode 100644 index 00000000000..679a79c91fb --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/NACRA_17.svg @@ -0,0 +1,42 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/OK.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/OK.svg new file mode 100644 index 00000000000..e0e53ba946b --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/OK.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/OPTIMIST.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/OPTIMIST.svg new file mode 100644 index 00000000000..f70e05a62bb --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/OPTIMIST.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/PLATU_25.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/PLATU_25.svg new file mode 100644 index 00000000000..91b75574684 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/PLATU_25.svg @@ -0,0 +1,70 @@ + + + + + image/svg+xml + + + + + + + + + + + +viewBox="0 0 600 800" + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/RS_X.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/RS_X.svg new file mode 100644 index 00000000000..8d1d80a9ed0 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/RS_X.svg @@ -0,0 +1,93 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/SONAR.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/SONAR.svg new file mode 100644 index 00000000000..5f9050278e5 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/SONAR.svg @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/STAR.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/STAR.svg new file mode 100644 index 00000000000..30230a103ea --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/STAR.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/STREAMLINE.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/STREAMLINE.svg new file mode 100644 index 00000000000..8b7c0b70234 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/STREAMLINE.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/SWAN_45.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/SWAN_45.svg new file mode 100644 index 00000000000..b135c3b1d1d --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/SWAN_45.svg @@ -0,0 +1,87 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/TORNADO.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/TORNADO.svg new file mode 100644 index 00000000000..84e1f6c9f8d --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/TORNADO.svg @@ -0,0 +1,22 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/X_99.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/X_99.svg new file mode 100644 index 00000000000..8b065973e7c --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/svg/boatclass/X_99.svg @@ -0,0 +1,24 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + + diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java index 40d0712554b..4a84092253d 100755 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java @@ -94,7 +94,7 @@ public class TestStoringAndLoadingEventsAndRegattas extends AbstractMongoDBTest } private LeaderboardGroup createLeaderboardGroup(String name) { - return new LeaderboardGroupImpl(name, "Description for "+name, /* displayInReverseOrder */ false, Collections.emptyList()); + return new LeaderboardGroupImpl(name, "Description for "+name, /* displayName */ null, /* displayInReverseOrder */ false, Collections.emptyList()); } @Test diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboardGroups.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboardGroups.java index 217133e099d..6b19419b06d 100755 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboardGroups.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboardGroups.java @@ -96,7 +96,7 @@ public class TestStoringAndRetrievingLeaderboardGroups extends AbstractMongoDBTe new LowPoint(), null); leaderboards.add(leaderboard); - final LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl(groupName, groupDescription, false, leaderboards); + final LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl(groupName, groupDescription, /* displayName */ null, false, leaderboards); final Leaderboard overallLeaderboard = new LeaderboardGroupMetaLeaderboard(leaderboardGroup, new HighPointExtremeSailingSeriesOverall(), new ThresholdBasedResultDiscardingRuleImpl(new int[0])); leaderboardGroup.setOverallLeaderboard(overallLeaderboard); @@ -146,8 +146,9 @@ public class TestStoringAndRetrievingLeaderboardGroups extends AbstractMongoDBTe new LowPoint(), null); racingEventService.addLeaderboard(leaderboard); - LeaderboardGroup leaderboardGroup = racingEventService.addLeaderboardGroup(UUID.randomUUID(), groupName, groupDescription, false, Arrays.asList(leaderboardNames), - new int[0], ScoringSchemeType.HIGH_POINT_ESS_OVERALL); + LeaderboardGroup leaderboardGroup = racingEventService.addLeaderboardGroup(UUID.randomUUID(), groupName, groupDescription, + "The LG Display Name", false, + Arrays.asList(leaderboardNames), new int[0], ScoringSchemeType.HIGH_POINT_ESS_OVERALL); final Leaderboard overallLeaderboard = leaderboardGroup.getOverallLeaderboard(); mongoObjectFactory.storeLeaderboardGroup(leaderboardGroup); racingEventService.apply(new UpdateLeaderboardScoreCorrection(overallLeaderboard.getName(), "Leaderboard 3", @@ -202,7 +203,7 @@ public class TestStoringAndRetrievingLeaderboardGroups extends AbstractMongoDBTe new LowPoint(), null); leaderboards.add(leaderboard); - final LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl(groupName, groupDescription, false, leaderboards); + final LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl(groupName, groupDescription, /* displayName */ null, false, leaderboards); final Leaderboard overallLeaderboard = new LeaderboardGroupMetaLeaderboard(leaderboardGroup, new HighPointExtremeSailingSeriesOverall(), new ThresholdBasedResultDiscardingRuleImpl(new int[0])); leaderboardGroup.setOverallLeaderboard(overallLeaderboard); @@ -258,7 +259,7 @@ public class TestStoringAndRetrievingLeaderboardGroups extends AbstractMongoDBTe new LowPoint(), null); leaderboards.add(leaderboard); - final LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl(groupName, groupDescription, false, leaderboards); + final LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl(groupName, groupDescription, /* displayName */ null, false, leaderboards); final Leaderboard overallLeaderboard = new LeaderboardGroupMetaLeaderboard(leaderboardGroup, new HighPointExtremeSailingSeriesOverall(), new ThresholdBasedResultDiscardingRuleImpl(new int[0])); leaderboardGroup.setOverallLeaderboard(overallLeaderboard); @@ -300,7 +301,7 @@ public class TestStoringAndRetrievingLeaderboardGroups extends AbstractMongoDBTe leaderboard = new FlexibleLeaderboardImpl(leaderboardNames[2], new ThresholdBasedResultDiscardingRuleImpl(discardIndexResultsStartingWithHowManyRaces), new LowPoint(), null); leaderboards1.add(leaderboard); - final LeaderboardGroup leaderboardGroup1 = new LeaderboardGroupImpl(groupName1, groupDescription1, false, leaderboards1); + final LeaderboardGroup leaderboardGroup1 = new LeaderboardGroupImpl(groupName1, groupDescription1, /* displayName */ null, false, leaderboards1); mongoObjectFactory.storeLeaderboardGroup(leaderboardGroup1); final String groupName2 = "Leaderboard Group 2"; @@ -312,7 +313,7 @@ public class TestStoringAndRetrievingLeaderboardGroups extends AbstractMongoDBTe leaderboard = new FlexibleLeaderboardImpl(leaderboardNames[3], new ThresholdBasedResultDiscardingRuleImpl(discardIndexResultsStartingWithHowManyRaces), new LowPoint(), null); leaderboards2.add(leaderboard); - final LeaderboardGroup leaderboardGroup2 = new LeaderboardGroupImpl(groupName2, groupDescription2, false, leaderboards2); + final LeaderboardGroup leaderboardGroup2 = new LeaderboardGroupImpl(groupName2, groupDescription2, /* displayName */ null, false, leaderboards2); mongoObjectFactory.storeLeaderboardGroup(leaderboardGroup2); // the leaderboard named leaderboardNames[2] occurs in both groups @@ -335,6 +336,7 @@ public class TestStoringAndRetrievingLeaderboardGroups extends AbstractMongoDBTe final String groupName = "Leaderboard Group"; final String groupDescription = "A leaderboard group"; + final String groupDisplayName = "Some short name"; final ArrayList leaderboards = new ArrayList<>(); Leaderboard leaderboard = new FlexibleLeaderboardImpl(leaderboardNames[0], new ThresholdBasedResultDiscardingRuleImpl(discardIndexResultsStartingWithHowManyRaces), @@ -350,7 +352,7 @@ public class TestStoringAndRetrievingLeaderboardGroups extends AbstractMongoDBTe new LowPoint(), null); leaderboards.add(leaderboard); - final LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl(groupName, groupDescription, false, leaderboards); + final LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl(groupName, groupDescription, groupDisplayName, false, leaderboards); mongoObjectFactory.storeLeaderboardGroup(leaderboardGroup); final LeaderboardGroup loadedLeaderboardGroup = domainObjectFactory.loadLeaderboardGroup(groupName, /* regattaRegistry */ null, @@ -358,6 +360,7 @@ public class TestStoringAndRetrievingLeaderboardGroups extends AbstractMongoDBTe Assert.assertEquals(groupName, loadedLeaderboardGroup.getName()); Assert.assertEquals(groupDescription, loadedLeaderboardGroup.getDescription()); + Assert.assertEquals(groupDisplayName, loadedLeaderboardGroup.getDisplayName()); int c = 0; for (Leaderboard board : leaderboardGroup.getLeaderboards()) { @@ -382,7 +385,7 @@ public class TestStoringAndRetrievingLeaderboardGroups extends AbstractMongoDBTe new LowPoint(), null); leaderboards.add(leaderboard); - final LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl(groupName, groupDescription, false, leaderboards); + final LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl(groupName, groupDescription, /* displayName */ null, false, leaderboards); mongoObjectFactory.storeLeaderboardGroup(leaderboardGroup); final String[] ungroupedLeaderboardNames = {"Ungrouped Leaderboard 0", "Ungrouped Leaderboard 1", "Ungrouped Leaderboard 2"}; @@ -435,7 +438,7 @@ public class TestStoringAndRetrievingLeaderboardGroups extends AbstractMongoDBTe final RaceColumn race = leaderboard.addRaceColumn(columnName, false); leaderboards.add(leaderboard); - final LeaderboardGroup group = new LeaderboardGroupImpl(groupName, groupDescription, false, leaderboards); + final LeaderboardGroup group = new LeaderboardGroupImpl(groupName, groupDescription, /* displayName */ null, false, leaderboards); mongoObjectFactory.storeLeaderboardGroup(group); //Name change test diff --git a/java/com.sap.sailing.operationaltransformation/.classpath b/java/com.sap.sailing.operationaltransformation/.classpath index 6e16b23d695..e8ea977a694 100755 --- a/java/com.sap.sailing.operationaltransformation/.classpath +++ b/java/com.sap.sailing.operationaltransformation/.classpath @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/java/com.sap.sailing.selenium.test/local-test-environment.xml b/java/com.sap.sailing.selenium.test/local-test-environment.xml index 129a19b2877..9dc16b96c7c 100755 --- a/java/com.sap.sailing.selenium.test/local-test-environment.xml +++ b/java/com.sap.sailing.selenium.test/local-test-environment.xml @@ -38,7 +38,11 @@ webdriver.firefox.bin + c:/Program Files (x86)/Mozilla Firefox/firefox.exe + + + diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/leaderboard/LeaderboardConfigurationPanelPO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/leaderboard/LeaderboardConfigurationPanelPO.java index 7a09f669505..5ccdb541fc2 100755 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/leaderboard/LeaderboardConfigurationPanelPO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/leaderboard/LeaderboardConfigurationPanelPO.java @@ -32,7 +32,7 @@ public class LeaderboardConfigurationPanelPO extends PageArea { } public String getName() { - return getColumnContent(0); + return getColumnContent("Name"); } public String getLeaderboardURL() { diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/leaderboard/LeaderboardDetailsPanelPO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/leaderboard/LeaderboardDetailsPanelPO.java index fb93508f8c6..801efe1bc6c 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/leaderboard/LeaderboardDetailsPanelPO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/leaderboard/LeaderboardDetailsPanelPO.java @@ -128,11 +128,11 @@ public class LeaderboardDetailsPanelPO extends PageArea { CellTablePO racesTable = getRacesTable(); for(DataEntryPO entry : racesTable.getEntries()) { - String name = entry.getColumnContent(0); - String fleet = entry.getColumnContent(1); - //String medalRace = entry.getColumnContent(2); - //String linked = entry.getColumnContent(3); - //String factor = entry.getColumnContent(4); + String name = entry.getColumnContent("Race"); + String fleet = entry.getColumnContent("Fleet"); + //String medalRace = entry.getColumnContent("Medal Race"); + //String linked = entry.getColumnContent("Is linked"); + //String factor = entry.getColumnContent("Factor"); result.add(new RaceDescriptor(name, fleet, false, false, 0.0)); } @@ -185,11 +185,11 @@ public class LeaderboardDetailsPanelPO extends PageArea { CellTablePO racesTable = getRacesTable(); for(DataEntryPO entry : racesTable.getEntries()) { - String name = entry.getColumnContent(0); - String fleet = entry.getColumnContent(1); - //String medalRace = entry.getColumnContent(2); - //String linked = entry.getColumnContent(3); - //String factor = entry.getColumnContent(4); + String name = entry.getColumnContent("Race"); + String fleet = entry.getColumnContent("Fleet"); + //String medalRace = entry.getColumnContent("Medal Race"); + //String linked = entry.getColumnContent("Is linked"); + //String factor = entry.getColumnContent("Factor"); RaceDescriptor descriptor = new RaceDescriptor(name, fleet, false, false, 0.0); @@ -204,9 +204,9 @@ public class LeaderboardDetailsPanelPO extends PageArea { CellTablePO trackingTable = getTrackedRacesTable(); for(DataEntryPO entry : trackingTable.getEntries()) { - String regatta = entry.getColumnContent(0); - String boatClass = entry.getColumnContent(1); - String raceName = entry.getColumnContent(2); + String regatta = entry.getColumnContent("Regatta"); + String boatClass = entry.getColumnContent("Boat Class"); + String raceName = entry.getColumnContent("Race"); TrackedRaceDescriptor descriptor = new TrackedRaceDescriptor(regatta, boatClass, raceName); diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/RegattaDetailsCompositePO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/RegattaDetailsCompositePO.java index a266d021278..433906aee29 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/RegattaDetailsCompositePO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/RegattaDetailsCompositePO.java @@ -75,7 +75,7 @@ public class RegattaDetailsCompositePO extends PageArea { private DataEntryPO findSeries(String series) { CellTablePO table = getSeriesTable(); for (DataEntryPO entry : table.getEntries()) { - String name = entry.getColumnContent(0); + String name = entry.getColumnContent("Series"); if (series.equals(name)) { return entry; } diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/RegattaListCompositePO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/RegattaListCompositePO.java index 580fe6d5b69..6e30507b8b8 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/RegattaListCompositePO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/RegattaListCompositePO.java @@ -104,8 +104,8 @@ public class RegattaListCompositePO extends PageArea { CellTablePO table = getRegattasTable(); for(DataEntryPO entry : table.getEntries()) { - String name = entry.getColumnContent(0); - String boatClass = entry.getColumnContent(1); + String name = entry.getColumnContent("Regatta name"); + String boatClass = entry.getColumnContent("Boat Class"); descriptors.add(new RegattaDescriptor(name, boatClass)); } @@ -150,7 +150,7 @@ public class RegattaListCompositePO extends PageArea { CellTablePO table = getRegattasTable(); for(DataEntryPO entry : table.getEntries()) { - String name = entry.getColumnContent(0); + String name = entry.getColumnContent("Regatta name"); // String boatClass = columns.getColumnContent(1); // // if(regatta.equals(new RegattaDescriptor(name, boatClass))) diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tracking/TrackedRacesListPO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tracking/TrackedRacesListPO.java index b520f6b2946..b143b337e53 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tracking/TrackedRacesListPO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tracking/TrackedRacesListPO.java @@ -153,10 +153,10 @@ public class TrackedRacesListPO extends PageArea { CellTablePO table = getTrackedRacesTable(); for(DataEntryPO entry : table.getEntries()) { - String regatta = entry.getColumnContent(0); - String boatClass = entry.getColumnContent(1); - String race = entry.getColumnContent(2); - String status = entry.getColumnContent(6); + String regatta = entry.getColumnContent("Regatta"); + String boatClass = entry.getColumnContent("Boat Class"); + String race = entry.getColumnContent("Race"); + String status = entry.getColumnContent("Status"); TrackedRaceDescriptor descriptor = new TrackedRaceDescriptor(regatta, boatClass, race); @@ -277,9 +277,9 @@ public class TrackedRacesListPO extends PageArea { while(iterator.hasNext()) { DataEntryPO entry = iterator.next(); - String regatta = entry.getColumnContent(0); - String boatClass = entry.getColumnContent(1); - String race = entry.getColumnContent(2); + String regatta = entry.getColumnContent("Regatta"); + String boatClass = entry.getColumnContent("Boat Class"); + String race = entry.getColumnContent("Race"); TrackedRaceDescriptor descriptor = new TrackedRaceDescriptor(regatta, boatClass, race); diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tractrac/TracTracEventManagementPanelPO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tractrac/TracTracEventManagementPanelPO.java index 9365da31bd9..357f936bbe4 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tractrac/TracTracEventManagementPanelPO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tractrac/TracTracEventManagementPanelPO.java @@ -170,9 +170,9 @@ public class TracTracEventManagementPanelPO extends PageArea { CellTablePO table = getTrackableRacesTable(); for(DataEntryPO entry : table.getEntries()) { - String event = entry.getColumnContent(0); - String race = entry.getColumnContent(1); - String boatClass = entry.getColumnContent(2); + String event = entry.getColumnContent("Event"); + String race = entry.getColumnContent("Race"); + String boatClass = entry.getColumnContent("Boat Class"); descriptors.add(new TrackableRaceDescriptor(event, race, boatClass)); } @@ -239,9 +239,9 @@ public class TracTracEventManagementPanelPO extends PageArea { Iterator iterator = entries.iterator(); while (iterator.hasNext()) { DataEntryPO entry = iterator.next(); - String event = entry.getColumnContent(0); - String race = entry.getColumnContent(1); - String boatClass = entry.getColumnContent(2); + String event = entry.getColumnContent("Event"); + String race = entry.getColumnContent("Race"); + String boatClass = entry.getColumnContent("Boat Class"); TrackableRaceDescriptor descriptor = new TrackableRaceDescriptor(event, race, boatClass); if (!races.contains(descriptor)) { iterator.remove(); diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/CellTablePO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/CellTablePO.java index 84a24a31fe2..13973eb1ef9 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/CellTablePO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/CellTablePO.java @@ -51,10 +51,7 @@ public abstract class CellTablePO extends PageArea { private static final String LOADING_ANIMATION_XPATH = "./td/div/div/div/img"; - // It seems that the css class is not allways the same - //private static final String CELL_TABLE_CSS_CLASS = "GJTB2DRDFQ"; //$NON-NLS-1$ - - protected static final String SELECTED_ROW_CSS_CLASS = "GJTB2DRDPP"; //$NON-NLS-1$ + protected static final String SELECTED_ROW_CSS_CLASS = "GN5USXKCCQ"; //$NON-NLS-1$ private static final String LOADING_ANIMATION_IMAGE = "data:image/gif;base64," + //$NON-NLS-1$ "R0lGODlhKwALAPEAAP///0tKSqampktKSiH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGx" + //$NON-NLS-1$ @@ -235,13 +232,13 @@ public abstract class CellTablePO extends PageArea { */ public void selectEntries(List entries) { // First deselect all selected entries - for(T entry : getSelectedEntries()) { + for (T entry : getSelectedEntries()) { entry.deselect(); } - + // Select all specified entries - for(T entry : entries) { - if(this.getWebElement().equals(entry.table.getWebElement())) { + for (T entry : entries) { + if (this.getWebElement().equals(entry.table.getWebElement())) { entry.appendToSelection(); } } @@ -256,14 +253,12 @@ public abstract class CellTablePO extends PageArea { public List getSelectedEntries() { List entries = getEntries(); Iterator iterator = entries.iterator(); - - while(iterator.hasNext()) { + while (iterator.hasNext()) { T entry = iterator.next(); - - if(!entry.isSelected()) + if (!entry.isSelected()) { iterator.remove(); + } } - return entries; } diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/DataEntryPO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/DataEntryPO.java index a1df817df27..53329c9f2ca 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/DataEntryPO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/DataEntryPO.java @@ -67,14 +67,14 @@ public class DataEntryPO extends CellTableRowPO { } public void deselect() { - if(isSelected()) { + if (isSelected()) { Action action = getModifiedSelectAction(); action.perform(); } } public void appendToSelection() { - if(!isSelected()) { + if (!isSelected()) { Action action = getModifiedSelectAction(); action.perform(); } @@ -97,7 +97,14 @@ public class DataEntryPO extends CellTableRowPO { * The element on which to click to select or deselect the data entry in the table. */ protected WebElement getElementForSelect() { - return (this.columns.isEmpty() ? getWebElement() : this.columns.get(0)); + final int columnToUseForSelection; + if (this.columns.size() > 1) { + // could be a selection checkbox column in the first column, so use second column to be on the safe side: + columnToUseForSelection = 1; + } else { + columnToUseForSelection = 0; + } + return (this.columns.isEmpty() ? getWebElement() : this.columns.get(columnToUseForSelection)); } protected Action getModifiedSelectAction() { diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/leaderboard/LeaderboardTablePO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/leaderboard/LeaderboardTablePO.java index 6485d3c4849..2528f99169a 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/leaderboard/LeaderboardTablePO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/leaderboard/LeaderboardTablePO.java @@ -17,9 +17,9 @@ import com.sap.sailing.selenium.pages.leaderboard.LeaderboardTablePO.Leaderboard public class LeaderboardTablePO extends CellTablePO { public static class LeaderboardEntry extends DataEntryPO { - private static final int TOTAL_RANK_COLUMN = 0; - private static final int COMPETITOR_COLUMN = 1; - private static final int NAME_COLUMN = 2; + private static final String TOTAL_RANK_COLUMN = "Regatta Rank"; + private static final String COMPETITOR_COLUMN = "Competitor"; + private static final String NAME_COLUMN = "Name"; private static final String TOTAL_POINTS_COLUMN = "Σ"; diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestColumnsInLeaderboard.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestColumnsInLeaderboard.java index bb3ee525ee0..f580f12d56a 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestColumnsInLeaderboard.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestColumnsInLeaderboard.java @@ -69,17 +69,17 @@ public class TestColumnsInLeaderboard extends AbstractSeleniumTest { stateHasChanged = false; // Since this test is very expensive we iterate from the end to the beginning because the lower // indexes are still valid after the expansion of a column. - for(int i = headers.size() - 1; i >= 0; i--) { - if(leaderboardTable.isColumnExpandable(i) && !leaderboardTable.isColumnExpanded(i)) { + for (int i = headers.size() - 1; i >= 0; i--) { + if (leaderboardTable.isColumnExpandable(i) && !leaderboardTable.isColumnExpanded(i)) { leaderboardTable.expandColumn(i); stateHasChanged = true; List entries = leaderboardTable.getEntries(); // NOTE: We have to resolve the headers again for the assertion since there should be more now - // because of the expansion + // because of the expansion assertThat(entries.get(0).getNumberOfColumns(), equalTo(leaderboardTable.getColumnHeaders().size())); } } - } while(stateHasChanged); + } while (stateHasChanged); } private void configureLeaderboard() { diff --git a/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EventDataJsonSerializerTest.java b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EventDataJsonSerializerTest.java index c4b2315c91f..da407f47f20 100755 --- a/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EventDataJsonSerializerTest.java +++ b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EventDataJsonSerializerTest.java @@ -42,7 +42,7 @@ public class EventDataJsonSerializerTest { protected final Venue expectedVenue = new VenueImpl("Expected Venue"); protected final URL expectedOfficialWebsiteURL; protected final URL expectedLogoImageURL; - protected final LeaderboardGroup expectedLeaderbaordGroup = mock(LeaderboardGroup.class); + protected final LeaderboardGroup expectedLeaderboardGroup = mock(LeaderboardGroup.class); protected JsonSerializer venueSerializer; protected EventBaseJsonSerializer serializer; @@ -74,11 +74,12 @@ public class EventDataJsonSerializerTest { serializer = new EventBaseJsonSerializer(new VenueJsonSerializer(new CourseAreaJsonSerializer()), new LeaderboardGroupBaseJsonSerializer()); deserializer = new EventBaseJsonDeserializer(new VenueJsonDeserializer(new CourseAreaJsonDeserializer(DomainFactory.INSTANCE)), new LeaderboardGroupBaseJsonDeserializer()); - when(expectedLeaderbaordGroup.getId()).thenReturn(UUID.randomUUID()); - when(expectedLeaderbaordGroup.getName()).thenReturn("LG"); - when(expectedLeaderbaordGroup.getDescription()).thenReturn("LG Description"); - when(expectedLeaderbaordGroup.hasOverallLeaderboard()).thenReturn(false); - doReturn(Collections.singleton(expectedLeaderbaordGroup)).when(event).getLeaderboardGroups(); + when(expectedLeaderboardGroup.getId()).thenReturn(UUID.randomUUID()); + when(expectedLeaderboardGroup.getName()).thenReturn("LG"); + when(expectedLeaderboardGroup.getDescription()).thenReturn("LG Description"); + when(expectedLeaderboardGroup.getDisplayName()).thenReturn("LG Display Name"); + when(expectedLeaderboardGroup.hasOverallLeaderboard()).thenReturn(false); + doReturn(Collections.singleton(expectedLeaderboardGroup)).when(event).getLeaderboardGroups(); } @Test @@ -118,10 +119,11 @@ public class EventDataJsonSerializerTest { assertEquals(expectedName, deserializedEvent.getName()); assertEquals(expectedStartDate, deserializedEvent.getStartDate()); assertEquals(expectedEndDate, deserializedEvent.getEndDate()); - assertEquals(expectedLeaderbaordGroup.getName(), deserializedEvent.getLeaderboardGroups().iterator().next().getName()); - assertEquals(expectedLeaderbaordGroup.getDescription(), deserializedEvent.getLeaderboardGroups().iterator().next().getDescription()); - assertEquals(expectedLeaderbaordGroup.getId(), deserializedEvent.getLeaderboardGroups().iterator().next().getId()); - assertEquals(expectedLeaderbaordGroup.hasOverallLeaderboard(), deserializedEvent.getLeaderboardGroups().iterator().next().hasOverallLeaderboard()); + assertEquals(expectedLeaderboardGroup.getName(), deserializedEvent.getLeaderboardGroups().iterator().next().getName()); + assertEquals(expectedLeaderboardGroup.getDescription(), deserializedEvent.getLeaderboardGroups().iterator().next().getDescription()); + assertEquals(expectedLeaderboardGroup.getDisplayName(), deserializedEvent.getLeaderboardGroups().iterator().next().getDisplayName()); + assertEquals(expectedLeaderboardGroup.getId(), deserializedEvent.getLeaderboardGroups().iterator().next().getId()); + assertEquals(expectedLeaderboardGroup.hasOverallLeaderboard(), deserializedEvent.getLeaderboardGroups().iterator().next().hasOverallLeaderboard()); } @Test diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/coursedata/impl/ControlPointDeserializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/coursedata/impl/ControlPointDeserializer.java index 26550e23dab..243887f04b5 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/coursedata/impl/ControlPointDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/coursedata/impl/ControlPointDeserializer.java @@ -24,7 +24,7 @@ public class ControlPointDeserializer implements JsonDeserializer @Override public ControlPoint deserialize(JSONObject object) throws JsonDeserializationException { - String classFieldValue = object.get(BaseControlPointJsonSerializer.FIELD_CLASS).toString(); + String classFieldValue = (String) object.get(BaseControlPointJsonSerializer.FIELD_CLASS); ControlPoint controlPoint = null; if (classFieldValue.equals(ControlPointWithTwoMarks.class.getSimpleName())) { diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/coursedata/impl/GateDeserializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/coursedata/impl/GateDeserializer.java index 0ee9dd6d869..57a2e7bd86a 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/coursedata/impl/GateDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/coursedata/impl/GateDeserializer.java @@ -32,7 +32,7 @@ public class GateDeserializer implements JsonDeserializer { } public BoatClass deserialize(JSONObject object) throws JsonDeserializationException { - String name = object.get(BoatClassJsonSerializer.FIELD_NAME).toString(); + String name = (String) object.get(BoatClassJsonSerializer.FIELD_NAME); if (object.containsKey(BoatClassJsonSerializer.FIELD_TYPICALLY_STARTS_UPWIND)) { boolean typicallyStartsUpwind = Boolean.parseBoolean(object.get(BoatClassJsonSerializer.FIELD_TYPICALLY_STARTS_UPWIND).toString()); diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/BoatJsonDeserializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/BoatJsonDeserializer.java index b53e7e037d0..22759b45efc 100755 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/BoatJsonDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/BoatJsonDeserializer.java @@ -18,7 +18,7 @@ public class BoatJsonDeserializer implements JsonDeserializer { } public DynamicBoat deserialize(JSONObject object) throws JsonDeserializationException { - String name = object.get(BoatJsonSerializer.FIELD_NAME).toString(); + String name = (String) object.get(BoatJsonSerializer.FIELD_NAME); final Object sailID = object.get(BoatJsonSerializer.FIELD_SAIL_ID); String sailId = sailID == null ? null : sailID.toString(); BoatClass boatClass = null; diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/CourseAreaJsonDeserializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/CourseAreaJsonDeserializer.java index 183d4fdbe19..5169d219a67 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/CourseAreaJsonDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/CourseAreaJsonDeserializer.java @@ -21,7 +21,7 @@ public class CourseAreaJsonDeserializer implements JsonDeserializer public CourseArea deserialize(JSONObject object) throws JsonDeserializationException { - String name = object.get(CourseAreaJsonSerializer.FIELD_NAME).toString(); + String name = (String) object.get(CourseAreaJsonSerializer.FIELD_NAME); Serializable id = (Serializable) object.get(CourseAreaJsonSerializer.FIELD_ID); return factory.getOrCreateCourseArea((UUID) Helpers.tryUuidConversion(id), name); diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/EventBaseJsonDeserializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/EventBaseJsonDeserializer.java index b8b51b1db32..b329b762b79 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/EventBaseJsonDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/EventBaseJsonDeserializer.java @@ -12,8 +12,8 @@ import org.json.simple.JSONObject; import com.sap.sailing.domain.base.EventBase; import com.sap.sailing.domain.base.LeaderboardGroupBase; import com.sap.sailing.domain.base.Venue; -import com.sap.sailing.domain.base.impl.EventBaseImpl; import com.sap.sailing.domain.base.impl.StrippedEventImpl; +import com.sap.sailing.domain.common.impl.ImageSizeImpl; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; import com.sap.sailing.server.gateway.deserialization.JsonDeserializer; @@ -30,7 +30,7 @@ public class EventBaseJsonDeserializer implements JsonDeserializer { public EventBase deserialize(JSONObject object) throws JsonDeserializationException { UUID id = UUID.fromString((String) object.get(EventBaseJsonSerializer.FIELD_ID)); - String name = object.get(EventBaseJsonSerializer.FIELD_NAME).toString(); + String name = (String) object.get(EventBaseJsonSerializer.FIELD_NAME); String description = (String) object.get(EventBaseJsonSerializer.FIELD_DESCRIPTION); String officialWebsiteURLAsString = (String) object.get(EventBaseJsonSerializer.FIELD_OFFICIAL_WEBSITE_URL); String logoImageURLAsString = (String) object.get(EventBaseJsonSerializer.FIELD_LOGO_IMAGE_URL); @@ -50,7 +50,7 @@ public class EventBaseJsonDeserializer implements JsonDeserializer { leaderboardGroups.add(leaderboardGroupDeserializer.deserialize((JSONObject) lgJson)); } } - EventBaseImpl result = new StrippedEventImpl(name, startDate == null ? null : new MillisecondsTimePoint(startDate.longValue()), + StrippedEventImpl result = new StrippedEventImpl(name, startDate == null ? null : new MillisecondsTimePoint(startDate.longValue()), endDate == null ? null : new MillisecondsTimePoint(endDate.longValue()), venue, /* is public */ true, id, leaderboardGroups); result.setDescription(description); if (officialWebsiteURLAsString != null) { @@ -88,6 +88,21 @@ public class EventBaseJsonDeserializer implements JsonDeserializer { throw new JsonDeserializationException("Error deserializing sponsor image URLs for event "+name, e); } } + JSONArray imageSizes = (JSONArray) object.get(EventBaseJsonSerializer.FIELD_IMAGE_SIZES); + if (imageSizes != null) { + for (Object imageURLAndSizeObject : imageSizes) { + JSONObject imageURLAndSizeJson = (JSONObject) imageURLAndSizeObject; + try { + result.setImageSize( + new URL((String) imageURLAndSizeJson.get(EventBaseJsonSerializer.FIELD_IMAGE_URL)), + new ImageSizeImpl( + ((Number) imageURLAndSizeJson.get(EventBaseJsonSerializer.FIELD_IMAGE_WIDTH)).intValue(), + ((Number) imageURLAndSizeJson.get(EventBaseJsonSerializer.FIELD_IMAGE_HEIGHT)).intValue())); + } catch (MalformedURLException e) { + throw new JsonDeserializationException(e); + } + } + } return result; } diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/FleetDeserializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/FleetDeserializer.java old mode 100644 new mode 100755 index 213c76b1ce3..fe384773d9a --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/FleetDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/FleetDeserializer.java @@ -21,7 +21,7 @@ public class FleetDeserializer implements JsonDeserializer { if (object.containsKey(FleetJsonSerializer.FIELD_COLOR)) { color = colorDeserializer.deserialize(Helpers.getNestedObjectSafe(object, FleetJsonSerializer.FIELD_COLOR)); } - String name = object.get(FleetJsonSerializer.FIELD_NAME).toString(); + String name = (String) object.get(FleetJsonSerializer.FIELD_NAME); Number ordering = (Number) object.get(FleetJsonSerializer.FIELD_ORDERING); return new FleetImpl(name, ordering.intValue(), color); diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/LeaderboardGroupBaseJsonDeserializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/LeaderboardGroupBaseJsonDeserializer.java index 64e66fb2664..1710552e196 100755 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/LeaderboardGroupBaseJsonDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/LeaderboardGroupBaseJsonDeserializer.java @@ -18,10 +18,12 @@ public class LeaderboardGroupBaseJsonDeserializer implements JsonDeserializer { - private final JsonDeserializer personDeserializer; + private final JsonDeserializer personDeserializer; - public static TeamJsonDeserializer create(SharedDomainFactory baseDomainFactory) { - return new TeamJsonDeserializer(new PersonJsonDeserializer( - new NationalityJsonDeserializer(baseDomainFactory))); - } + public static TeamJsonDeserializer create(SharedDomainFactory baseDomainFactory) { + return new TeamJsonDeserializer(new PersonJsonDeserializer(new NationalityJsonDeserializer(baseDomainFactory))); + } - public TeamJsonDeserializer(JsonDeserializer personDeserializer) { - this.personDeserializer = personDeserializer; - } + public TeamJsonDeserializer(JsonDeserializer personDeserializer) { + this.personDeserializer = personDeserializer; + } - @Override - public DynamicTeam deserialize(JSONObject object) - throws JsonDeserializationException { - String name = (String) object.get(TeamJsonSerializer.FIELD_NAME); - DynamicPerson coach = personDeserializer.deserialize((JSONObject) object - .get(TeamJsonSerializer.FIELD_COACH)); - Set sailors = new HashSet(); + @Override + public DynamicTeam deserialize(JSONObject object) throws JsonDeserializationException { + String name = (String) object.get(TeamJsonSerializer.FIELD_NAME); + DynamicPerson coach = personDeserializer.deserialize((JSONObject) object.get(TeamJsonSerializer.FIELD_COACH)); + Set sailors = new HashSet(); - JSONArray sailorsJson = (JSONArray) object - .get(TeamJsonSerializer.FIELD_SAILORS); - for (Object sailorObject : sailorsJson) { - sailors.add(personDeserializer - .deserialize((JSONObject) sailorObject)); - } - return new TeamImpl(name, sailors, coach); - } + JSONArray sailorsJson = (JSONArray) object.get(TeamJsonSerializer.FIELD_SAILORS); + for (Object sailorObject : sailorsJson) { + sailors.add(personDeserializer.deserialize((JSONObject) sailorObject)); + } + return new TeamImpl(name, sailors, coach); + } } diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/VenueJsonDeserializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/VenueJsonDeserializer.java index 6530df399ef..71108d59998 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/VenueJsonDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/VenueJsonDeserializer.java @@ -17,21 +17,15 @@ public class VenueJsonDeserializer implements JsonDeserializer { this.courseAreaDeserializer = courseAreaDeserializer; } - public Venue deserialize(JSONObject object) - throws JsonDeserializationException { - - String name = object.get(VenueJsonSerializer.FIELD_NAME).toString(); + public Venue deserialize(JSONObject object) throws JsonDeserializationException { + String name = (String) object.get(VenueJsonSerializer.FIELD_NAME); Venue venue = new VenueImpl(name); - JSONArray courseAreaArray = Helpers.getNestedArraySafe( - object, - VenueJsonSerializer.FIELD_COURSE_AREAS); + JSONArray courseAreaArray = Helpers.getNestedArraySafe(object, VenueJsonSerializer.FIELD_COURSE_AREAS); for (Object element : courseAreaArray) { JSONObject courseAreaObject = Helpers.toJSONObjectSafe(element); venue.addCourseArea(courseAreaDeserializer.deserialize(courseAreaObject)); } - return venue; } - } diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/EventBaseJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/EventBaseJsonSerializer.java index b229dcde41b..d5d682ce31e 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/EventBaseJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/EventBaseJsonSerializer.java @@ -1,6 +1,9 @@ package com.sap.sailing.server.gateway.serialization.impl; import java.net.URL; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import java.util.logging.Logger; import org.json.simple.JSONArray; import org.json.simple.JSONObject; @@ -8,9 +11,12 @@ import org.json.simple.JSONObject; import com.sap.sailing.domain.base.EventBase; import com.sap.sailing.domain.base.LeaderboardGroupBase; import com.sap.sailing.domain.base.Venue; +import com.sap.sailing.domain.common.ImageSize; import com.sap.sailing.server.gateway.serialization.JsonSerializer; public class EventBaseJsonSerializer implements JsonSerializer { + private static final Logger logger = Logger.getLogger(EventBaseJsonSerializer.class.getName()); + public static final String FIELD_ID = "id"; public static final String FIELD_NAME = "name"; public static final String FIELD_DESCRIPTION = "description"; @@ -21,6 +27,10 @@ public class EventBaseJsonSerializer implements JsonSerializer { public static final String FIELD_VIDEO_URLS = "videoURLs"; public static final String FIELD_SPONSOR_IMAGE_URLS = "sponsorImageURLs"; public static final String FIELD_LOGO_IMAGE_URL = "logoImageURL"; + public static final String FIELD_IMAGE_SIZES = "imageSizes"; + public static final String FIELD_IMAGE_URL = "imageURL"; + public static final String FIELD_IMAGE_WIDTH = "imageWidth"; + public static final String FIELD_IMAGE_HEIGHT = "imageHeight"; public static final String FIELD_OFFICIAL_WEBSITE_URL = "officialWebsiteURL"; public static final String FIELDS_LEADERBOARD_GROUPS = "leaderboardGroups"; @@ -50,9 +60,42 @@ public class EventBaseJsonSerializer implements JsonSerializer { for (LeaderboardGroupBase lg : event.getLeaderboardGroups()) { leaderboardGroups.add(leaderboardGroupBaseSerializer.serialize(lg)); } + JSONArray imageSizes = new JSONArray(); + result.put(FIELD_IMAGE_SIZES, imageSizes); + for (URL imageURL : event.getImageURLs()) { + addImageSize(imageURL, imageSizes, event); + } + if (event.getLogoImageURL() != null) { + addImageSize(event.getLogoImageURL(), imageSizes, event); + } + for (URL sponsorImageURL : event.getSponsorImageURLs()) { + addImageSize(sponsorImageURL, imageSizes, event); + } return result; } + /** + * If eventBase knows the size of the image with URL imageURL, that size is serialized as + * JSON object to imageSizes + */ + private void addImageSize(URL imageURL, JSONArray imageSizes, EventBase eventBase) { + ImageSize imageSize; + try { + imageSize = eventBase.getImageSize(imageURL); + if (imageSize != null) { + JSONObject imageSizeJson = new JSONObject(); + imageSizes.add(imageSizeJson); + imageSizeJson.put(FIELD_IMAGE_URL, imageURL.toString()); + imageSizeJson.put(FIELD_IMAGE_WIDTH, imageSize.getWidth()); + imageSizeJson.put(FIELD_IMAGE_HEIGHT, imageSize.getHeight()); + } + } catch (InterruptedException e) { + logger.log(Level.FINE, "Couldn't retrieve image size for URL "+imageURL, e); + } catch (ExecutionException e) { + logger.log(Level.FINE, "Couldn't retrieve image size for URL "+imageURL, e); + } + } + private JSONArray getURLsAsStringArray(Iterable urls) { JSONArray jsonImageURLs = new JSONArray(); for (URL url : urls) { diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/LeaderboardGroupBaseJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/LeaderboardGroupBaseJsonSerializer.java index 4ea25dbd8e7..1b9a0b02f41 100755 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/LeaderboardGroupBaseJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/LeaderboardGroupBaseJsonSerializer.java @@ -9,6 +9,7 @@ public class LeaderboardGroupBaseJsonSerializer implements JsonSerializer leaderboardNames = new ArrayList(); leaderboardNames.add(regatta.getName()); leaderboardGroup = racingEventService.addLeaderboardGroup(UUID.randomUUID(), leaderboardGroupName, "description", - /* displayGroupsInReverseOrder */ false, leaderboardNames, /* overallLeaderboardDiscardThresholds */ null, /* overallLeaderboardScoringSchemeType */ null); + "The LG displayName", /* displayGroupsInReverseOrder */ false, + leaderboardNames, /* overallLeaderboardDiscardThresholds */ null, /* overallLeaderboardScoringSchemeType */ null); } @Test public void testExportLeaderboardGroupsAsJson() throws Exception { String jsonString = callJsonHttpServlet(new LeaderboardGroupsJsonGetServlet(), "GET", null); - - Object obj= JSONValue.parse(jsonString); - JSONArray array= (JSONArray) obj; - + Object obj = JSONValue.parse(jsonString); + JSONArray array = (JSONArray) obj; assertTrue(array.size() == 1); - String jsonFirstLeaderboardGroup = (String) array.get(0); - assertTrue(leaderboardGroup.getName().equals(jsonFirstLeaderboardGroup)); } } diff --git a/java/com.sap.sailing.server.gateway/WEB-INF/web.xml b/java/com.sap.sailing.server.gateway/WEB-INF/web.xml index a3f4166834a..95efad80403 100644 --- a/java/com.sap.sailing.server.gateway/WEB-INF/web.xml +++ b/java/com.sap.sailing.server.gateway/WEB-INF/web.xml @@ -250,7 +250,7 @@ com.sun.jersey.spi.container.ContainerResponseFilters - com.sap.sailing.server.gateway.jaxrs.AccessControlResponseFilter + com.sap.sailing.server.gateway.jaxrs.AccessControlAndEncodingResponseFilter 1 @@ -267,7 +267,7 @@ com.sun.jersey.spi.container.ContainerResponseFilters - com.sap.sailing.server.gateway.jaxrs.AccessControlResponseFilter + com.sap.sailing.server.gateway.jaxrs.AccessControlAndEncodingResponseFilter 1 diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/LeaderboardGroupJsonGetServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/LeaderboardGroupJsonGetServlet.java index 4b25b670f95..ae19af6d41d 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/LeaderboardGroupJsonGetServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/LeaderboardGroupJsonGetServlet.java @@ -42,6 +42,7 @@ public class LeaderboardGroupJsonGetServlet extends AbstractJsonHttpServlet { JSONObject jsonLeaderboardGroup = new JSONObject(); jsonLeaderboardGroup.put("name", leaderboardGroup.getName()); jsonLeaderboardGroup.put("description", leaderboardGroup.getDescription()); + jsonLeaderboardGroup.put("displayName", leaderboardGroup.getDisplayName()); jsonLeaderboardGroup.put("timepoint", timePoint.toString()); JSONArray jsonLeaderboardEntries = new JSONArray(); jsonLeaderboardGroup.put("leaderboards", jsonLeaderboardEntries); diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/AbstractSailingServerResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/AbstractSailingServerResource.java index 8a25a293f9d..fa90f81ec05 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/AbstractSailingServerResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/AbstractSailingServerResource.java @@ -1,5 +1,8 @@ package com.sap.sailing.server.gateway.jaxrs; +import java.math.BigDecimal; +import java.math.RoundingMode; + import javax.servlet.ServletContext; import javax.ws.rs.core.Context; @@ -63,4 +66,10 @@ public abstract class AbstractSailingServerResource { } return trackedRace; } + + protected static Double roundDouble(Double value, int places) { + BigDecimal bigDecimal = new BigDecimal(value); + bigDecimal = bigDecimal.setScale(places, RoundingMode.HALF_UP); + return bigDecimal.doubleValue(); + } } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/AccessControlAndEncodingResponseFilter.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/AccessControlAndEncodingResponseFilter.java new file mode 100644 index 00000000000..0031f89ddd0 --- /dev/null +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/AccessControlAndEncodingResponseFilter.java @@ -0,0 +1,22 @@ +package com.sap.sailing.server.gateway.jaxrs; + +import javax.ws.rs.core.MultivaluedMap; + +import com.sun.jersey.spi.container.ContainerRequest; +import com.sun.jersey.spi.container.ContainerResponse; +import com.sun.jersey.spi.container.ContainerResponseFilter; + +public class AccessControlAndEncodingResponseFilter implements ContainerResponseFilter { + + @Override + public ContainerResponse filter(ContainerRequest request, ContainerResponse response) { + MultivaluedMap httpHeaders = response.getHttpHeaders(); + httpHeaders.add("Access-Control-Allow-Origin", "*"); + httpHeaders.add("Access-Control-Allow-Headers", "Authorization, Origin, X-Requested-With, Content-Type"); + httpHeaders.add("Access-Control-Expose-Headers", "Location, Content-Disposition"); + httpHeaders.add("Access-Control-Allow-Methods", "POST, PUT, GET, DELETE, HEAD, OPTIONS"); + httpHeaders.remove("Content-Type"); + httpHeaders.add("Content-Type", "application/json;charset=UTF-8"); + return response; + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/AccessControlResponseFilter.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/AccessControlResponseFilter.java deleted file mode 100644 index fb3f2cee49c..00000000000 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/AccessControlResponseFilter.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.sap.sailing.server.gateway.jaxrs; - -import javax.ws.rs.core.MultivaluedMap; - -import com.sun.jersey.spi.container.ContainerRequest; -import com.sun.jersey.spi.container.ContainerResponse; -import com.sun.jersey.spi.container.ContainerResponseFilter; - -public class AccessControlResponseFilter implements ContainerResponseFilter { - - @Override - public ContainerResponse filter(ContainerRequest request, ContainerResponse response) { - MultivaluedMap httpHeaders = response.getHttpHeaders(); - httpHeaders.add("Access-Control-Allow-Origin", "*"); - httpHeaders.add("Access-Control-Allow-Headers", "Authorization, Origin, X-Requested-With, Content-Type"); - httpHeaders.add("Access-Control-Expose-Headers", "Location, Content-Disposition"); - httpHeaders.add("Access-Control-Allow-Methods", "POST, PUT, GET, DELETE, HEAD, OPTIONS"); - return response; - } -} \ No newline at end of file diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java index 9439bd090eb..273b7e1f04c 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java @@ -667,5 +667,85 @@ public class RegattasResource extends AbstractSailingServerResource { return response; } + @GET + @Produces("application/json;charset=UTF-8") + @Path("{regattaname}/races/{racename}/competitors/live") + public Response getCompetitorLiveRanks(@PathParam("regattaname") String regattaName, @PathParam("racename") String raceName, + @DefaultValue("-1") @QueryParam("topN") Integer topN) { + Response response; + Regatta regatta = findRegattaByName(regattaName); + if (regatta == null) { + response = Response.status(Status.NOT_FOUND).entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN).build(); + } else { + RaceDefinition race = findRaceByName(regatta, raceName); + if (race == null) { + response = Response.status(Status.NOT_FOUND).entity("Could not find a race with name '" + raceName + "'.").type(MediaType.TEXT_PLAIN).build(); + } else { + TrackedRace trackedRace = findTrackedRace(regattaName, raceName); + Course course = trackedRace.getRace().getCourse(); + TimePoint timePoint = trackedRace.getTimePointOfNewestEvent() == null ? MillisecondsTimePoint.now() + : trackedRace.getTimePointOfNewestEvent(); + // if(trackedRace.isLive(timePoint)) { + + JSONObject jsonLiveData = new JSONObject(); + jsonLiveData.put("name", trackedRace.getRace().getName()); + jsonLiveData.put("regatta", regatta.getName()); + + JSONArray jsonCompetitors = new JSONArray(); + try { + List competitorsFromBestToWorst = trackedRace.getCompetitorsFromBestToWorst(timePoint); + Integer rank = 1; + for (Competitor competitor : competitorsFromBestToWorst) { + JSONObject jsonCompetitorInLeg = new JSONObject(); + + if(topN != null && topN > 0 && rank > topN) { + break; + } + TrackedLegOfCompetitor currentLegOfCompetitor = trackedRace.getCurrentLeg(competitor, timePoint); + if (currentLegOfCompetitor != null) { + jsonCompetitorInLeg.put("id", competitor.getId() != null ? competitor.getId().toString() : null); + jsonCompetitorInLeg.put("name", competitor.getName()); + jsonCompetitorInLeg.put("sailNumber", competitor.getBoat().getSailID()); + jsonCompetitorInLeg.put("color", competitor.getColor() != null ? competitor.getColor().getAsHtml() : null); + + jsonCompetitorInLeg.put("rank", rank++); + + int indexOfWaypoint = course.getIndexOfWaypoint(currentLegOfCompetitor.getLeg().getFrom()); + jsonCompetitorInLeg.put("leg", indexOfWaypoint); + + Speed speedOverGround = currentLegOfCompetitor.getSpeedOverGround(timePoint); + if(speedOverGround != null) { + jsonCompetitorInLeg.put("speedOverGround-kts", roundDouble(speedOverGround.getKnots(), 2)); + } + + Distance distanceTraveled = currentLegOfCompetitor.getDistanceTraveled(timePoint); + if (distanceTraveled != null) { + jsonCompetitorInLeg.put("distanceTraveled-m", roundDouble(distanceTraveled.getMeters(), 2)); + } + + Double gapToLeaderInSeconds = currentLegOfCompetitor.getGapToLeaderInSeconds(timePoint, WindPositionMode.LEG_MIDDLE); + if(gapToLeaderInSeconds != null) { + jsonCompetitorInLeg.put("gapToLeader-s", roundDouble(gapToLeaderInSeconds, 2)); + } + + Distance windwardDistanceToOverallLeader = currentLegOfCompetitor.getWindwardDistanceToOverallLeader(timePoint, WindPositionMode.LEG_MIDDLE); + if(windwardDistanceToOverallLeader != null) { + jsonCompetitorInLeg.put("gapToLeader-m", roundDouble(windwardDistanceToOverallLeader.getMeters(), 2)); + } + + jsonCompetitors.add(jsonCompetitorInLeg); + } + } + } catch (NoWindException e1) { + // well, we don't know the wind direction... then no gap to leader will be shown... + } + jsonLiveData.put("competitors", jsonCompetitors); + + String json = jsonLiveData.toJSONString(); + return Response.ok(json, MediaType.APPLICATION_JSON).build(); + } + } + return response; + } } \ No newline at end of file diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ConnectionResetAndReconnectTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ConnectionResetAndReconnectTest.java index 45700611a21..6f01ccc7d10 100644 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ConnectionResetAndReconnectTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ConnectionResetAndReconnectTest.java @@ -122,7 +122,7 @@ public class ConnectionResetAndReconnectTest extends AbstractServerReplicationTe List regattas = new ArrayList(); regattas.add("Day1"); regattas.add("Day2"); - return master.addEvent(eventName, eventStartDate, eventEndDate, venueName, isPublic, UUID.randomUUID()); + return master.addEvent(eventName, /* eventDescription */ null, eventStartDate, eventEndDate, venueName, isPublic, UUID.randomUUID()); } private void stopMessagingExchange() { diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/EventReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/EventReplicationTest.java index da7b7e6733b..e830dab24dd 100644 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/EventReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/EventReplicationTest.java @@ -35,7 +35,7 @@ public class EventReplicationTest extends AbstractServerReplicationTest { List regattas = new ArrayList(); regattas.add("Day1"); regattas.add("Day2"); - Event masterEvent = master.addEvent(eventName, eventStartDate, eventEndDate, venueName, isPublic, UUID.randomUUID()); + Event masterEvent = master.addEvent(eventName, /* eventDescription */ null, eventStartDate, eventEndDate, venueName, isPublic, UUID.randomUUID()); Thread.sleep(1000); Event replicatedEvent = replica.getEvent(masterEvent.getId()); @@ -54,12 +54,12 @@ public class EventReplicationTest extends AbstractServerReplicationTest { List regattas = new ArrayList(); regattas.add("Day1"); regattas.add("Day2"); - final Event masterEvent = master.addEvent(eventName, null, null, venueName, isPublic, UUID.randomUUID()); + final Event masterEvent = master.addEvent(eventName, /* eventDescription */ null, null, null, venueName, isPublic, UUID.randomUUID()); final String leaderboardGroupName = "LGName"; LeaderboardGroup lg = master.apply(new CreateLeaderboardGroup(leaderboardGroupName, "LGDescription", /* displayGroupsInReverseOrder */ - false, /* leaderboardNames */ - Collections. emptyList(), /* overallLeaderboardDiscardThresholds */null, /* overallLeaderboardScoringSchemeType */ - null)); + "displayName", /* leaderboardNames */ + false, Collections. emptyList(), /* overallLeaderboardScoringSchemeType */ + /* overallLeaderboardDiscardThresholds */null, null)); master.apply(new AddLeaderboardGroupToEvent(masterEvent.getId(), lg.getId())); Thread.sleep(1000); Event replicatedEvent = replica.getEvent(masterEvent.getId()); @@ -78,7 +78,7 @@ public class EventReplicationTest extends AbstractServerReplicationTest { List regattas = new ArrayList(); regattas.add("Day1"); regattas.add("Day2"); - Event masterEvent = master.addEvent(eventName, null, null, venueName, isPublic, UUID.randomUUID()); + Event masterEvent = master.addEvent(eventName, /* eventDescription */ null, null, null, venueName, isPublic, UUID.randomUUID()); Thread.sleep(1000); Event replicatedEvent = replica.getEvent(masterEvent.getId()); @@ -97,7 +97,7 @@ public class EventReplicationTest extends AbstractServerReplicationTest { final String courseArea = "Alpha"; final TimePoint eventStartDate = new MillisecondsTimePoint(new Date()); final TimePoint eventEndDate = new MillisecondsTimePoint(new Date()); - Event masterEvent = master.addEvent(eventName, eventStartDate, eventEndDate, venueName, isPublic, UUID.randomUUID()); + Event masterEvent = master.addEvent(eventName, /* eventDescription */ null, eventStartDate, eventEndDate, venueName, isPublic, UUID.randomUUID()); CourseArea masterCourseArea = master.addCourseArea(masterEvent.getId(), courseArea, UUID.randomUUID()); Thread.sleep(1000); diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java index fac883e9654..6bb79e96c33 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java @@ -86,7 +86,7 @@ public class InitialLoadReplicationObjectIdentityTest extends AbstractServerRepl final TimePoint eventStartDate = new MillisecondsTimePoint(new Date()); final TimePoint eventEndDate = new MillisecondsTimePoint(new Date()); - Event event = master.addEvent(eventName, eventStartDate, eventEndDate, venue, false, eventId); + Event event = master.addEvent(eventName, /* eventDescription */ null, eventStartDate, eventEndDate, venue, false, eventId); assertNotNull(master.getEvent(eventId)); assertNull(replica.getEvent(eventId)); @@ -115,7 +115,8 @@ public class InitialLoadReplicationObjectIdentityTest extends AbstractServerRepl leaderboardNames.add(leaderboardName); int[] overallLeaderboardDiscardThresholds = new int[] {}; ScoringSchemeType overallLeaderboardScoringSchemeType = ScoringSchemeType.HIGH_POINT; - LeaderboardGroup leaderboardGroup = master.addLeaderboardGroup(UUID.randomUUID(), leaderBoardGroupName, "Some descriptive Description", false, leaderboardNames, overallLeaderboardDiscardThresholds, overallLeaderboardScoringSchemeType); + LeaderboardGroup leaderboardGroup = master.addLeaderboardGroup(UUID.randomUUID(), leaderBoardGroupName, "Some descriptive Description", + "displayName", false, leaderboardNames, overallLeaderboardDiscardThresholds, overallLeaderboardScoringSchemeType); assertNotNull(master.getLeaderboardGroupByName(leaderBoardGroupName)); assertNull(replica.getLeaderboardGroupByName(leaderBoardGroupName)); diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RegattaReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RegattaReplicationTest.java index 3c356b8c6e2..82b3e9e6e1d 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RegattaReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RegattaReplicationTest.java @@ -66,7 +66,7 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest { final TimePoint eventStartDate = new MillisecondsTimePoint(new Date()); final TimePoint eventEndDate = new MillisecondsTimePoint(new Date()); - Event event = master.addEvent("Event", eventStartDate, eventEndDate, "Venue", true, UUID.randomUUID()); + Event event = master.addEvent("Event", /* eventDescription */ null, eventStartDate, eventEndDate, "Venue", true, UUID.randomUUID()); master.addCourseArea(event.getId(), "Alpha", alphaCourseAreaId); master.addCourseArea(event.getId(), "TV", tvCourseAreaId); @@ -112,7 +112,7 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest { final UUID golfCourseAreaId = UUID.randomUUID(); final TimePoint eventStartDate = new MillisecondsTimePoint(new Date()); final TimePoint eventEndDate = new MillisecondsTimePoint(new Date()); - Event event = master.addEvent("Event", eventStartDate, eventEndDate, "Venue", /*isPublic*/true, UUID.randomUUID()); + Event event = master.addEvent("Event", /* eventDescription */ null, eventStartDate, eventEndDate, "Venue", /*isPublic*/true, UUID.randomUUID()); master.addCourseArea(event.getId(), "TV", tvCourseAreaId); master.addCourseArea(event.getId(), "Golf", golfCourseAreaId); final String regattaName = RegattaImpl.getDefaultName("Kiel Week 2012", "49er"); @@ -137,7 +137,7 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest { final TimePoint eventStartDate = new MillisecondsTimePoint(new Date()); final TimePoint eventEndDate = new MillisecondsTimePoint(new Date()); - Event event = master.addEvent("Event", eventStartDate, eventEndDate, "Venue", true, UUID.randomUUID()); + Event event = master.addEvent("Event", /* eventDescription */ null, eventStartDate, eventEndDate, "Venue", true, UUID.randomUUID()); master.addCourseArea(event.getId(), "Alpha", alphaCourseAreaId); UUID currentCourseAreaId = null; @@ -295,7 +295,7 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest { final String courseArea = "Alpha"; final TimePoint eventStartDate = new MillisecondsTimePoint(new Date()); final TimePoint eventEndDate = new MillisecondsTimePoint(new Date()); - Event masterEvent = master.addEvent(eventName, eventStartDate, eventEndDate, venueName, isPublic, UUID.randomUUID()); + Event masterEvent = master.addEvent(eventName, /* eventDescription */ null, eventStartDate, eventEndDate, venueName, isPublic, UUID.randomUUID()); CourseArea masterCourseArea = master.addCourseArea(masterEvent.getId(), courseArea, UUID.randomUUID()); Regatta masterRegatta = master.createRegatta(RegattaImpl.getDefaultName(eventName, boatClassName), boatClassName, UUID.randomUUID(), series, diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ReplicationInstancesManagerLoggingPerformanceTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ReplicationInstancesManagerLoggingPerformanceTest.java index c77029eb38c..9cd9a06253c 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ReplicationInstancesManagerLoggingPerformanceTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ReplicationInstancesManagerLoggingPerformanceTest.java @@ -25,8 +25,9 @@ public class ReplicationInstancesManagerLoggingPerformanceTest { replicationInstanceManager = new ReplicationInstancesManager(); replica = new ReplicaDescriptor(InetAddress.getLocalHost(), UUID.randomUUID(), ""); replicationInstanceManager.registerReplica(replica); - operation = new CreateLeaderboardGroup("Test Leaderboard Group", "Description of Test Leaderboard Group", /* displayGroupsInReverseOrder */ false, Arrays.asList(new String[] { "Default Leaderboard" }), - /* overallLeaderboardDiscardThresholds */ null, /* overallLeaderboardScoringSchemeType */ null); + operation = new CreateLeaderboardGroup("Test Leaderboard Group", "Description of Test Leaderboard Group", /* displayName */ null, + /* displayGroupsInReverseOrder */ false, + Arrays.asList(new String[] { "Default Leaderboard" }), /* overallLeaderboardDiscardThresholds */ null, /* overallLeaderboardScoringSchemeType */ null); } @Test diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ReplicationStopTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ReplicationStopTest.java index aa24846fd06..acf2e40f76a 100644 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ReplicationStopTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ReplicationStopTest.java @@ -38,7 +38,7 @@ public class ReplicationStopTest extends AbstractServerReplicationTest { List regattas = new ArrayList(); regattas.add("Day1"); regattas.add("Day2"); - return master.addEvent(eventName, eventStartDate, eventEndDate, venueName, isPublic, UUID.randomUUID()); + return master.addEvent(eventName, /* eventDescription */ null, eventStartDate, eventEndDate, venueName, isPublic, UUID.randomUUID()); } diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java index 6a17ee26eb6..2e4fa97b377 100755 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java @@ -63,12 +63,16 @@ import com.sap.sailing.domain.common.MasterDataImportObjectCreationCount; import com.sap.sailing.domain.common.MaxPointsReason; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.common.SpeedWithBearing; import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.WindSource; +import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.impl.DataImportProgressImpl; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; +import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID; import com.sap.sailing.domain.common.media.MediaTrack; import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; import com.sap.sailing.domain.leaderboard.Leaderboard; @@ -92,8 +96,10 @@ import com.sap.sailing.domain.racelog.impl.CompetitorResultsImpl; import com.sap.sailing.domain.racelog.impl.RaceLogEventAuthorImpl; import com.sap.sailing.domain.racelog.impl.RaceLogEventFactoryImpl; import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore; +import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.Wind; +import com.sap.sailing.domain.tracking.WindTrack; import com.sap.sailing.domain.tracking.impl.EmptyWindStore; import com.sap.sailing.domain.tracking.impl.WindImpl; import com.sap.sailing.mongodb.MongoDBConfiguration; @@ -120,6 +126,8 @@ public class MasterDataImportTest { private final TimePoint eventStartDate = new MillisecondsTimePoint(new Date()); private final TimePoint eventEndDate = new MillisecondsTimePoint(new Date()); + + private final UUID raceId = UUID.randomUUID(); /** * Log Events created when running test. Will be removed from db at teardown @@ -154,7 +162,7 @@ public class MasterDataImportTest { ClassNotFoundException { // Setup source service RacingEventService sourceService = new RacingEventServiceImpl(); - Event event = sourceService.addEvent(TEST_EVENT_NAME, eventStartDate, eventEndDate, "testVenue", false, eventUUID); + Event event = sourceService.addEvent(TEST_EVENT_NAME, /* eventDescription */ null, eventStartDate, eventEndDate, "testVenue", false, eventUUID); UUID courseAreaUUID = UUID.randomUUID(); CourseArea courseArea = new CourseAreaImpl("testArea", courseAreaUUID); event.getVenue().addCourseArea(courseArea); @@ -188,7 +196,7 @@ public class MasterDataImportTest { List leaderboardNames = new ArrayList(); leaderboardNames.add(leaderboard.getName()); LeaderboardGroup group = sourceService.addLeaderboardGroup(UUID.randomUUID(), TEST_GROUP_NAME, "testGroupDesc", - false, leaderboardNames, null, null); + /* displayName */ null, false, leaderboardNames, null, null); event.addLeaderboardGroup(group); // Set tracked Race with competitors @@ -216,7 +224,7 @@ public class MasterDataImportTest { boat2); competitors.add(competitorToSuppress); RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); - TrackedRace trackedRace = new DummyTrackedRace(competitors, regatta, null); + TrackedRace trackedRace = new DummyTrackedRace(raceId, competitors, regatta, null, sourceService.getWindStore()); raceColumn.setTrackedRace(testFleet1, trackedRace); @@ -306,8 +314,8 @@ public class MasterDataImportTest { Assert.assertNotNull(raceColumnOnTarget); Assert.assertNull(raceColumnOnTarget.getTrackedRace(raceColumnOnTarget.getFleetByName(testFleet1Name))); - raceColumnOnTarget.setTrackedRace(raceColumnOnTarget.getFleets().iterator().next(), new DummyTrackedRace( - competitors, regattaOnTarget, null)); + raceColumnOnTarget.setTrackedRace(raceColumnOnTarget.getFleets().iterator().next(), new DummyTrackedRace(raceId, + competitors, regattaOnTarget, null, sourceService.getWindStore())); Assert.assertTrue(leaderboardOnTarget.getScoreCorrection().hasCorrectionFor(raceColumnOnTarget)); Competitor competitorOnTarget = domainFactory.getExistingCompetitorById(competitorUUID); @@ -315,7 +323,7 @@ public class MasterDataImportTest { competitorsCreatedOnTarget.add(competitorOnTarget); Fleet fleet1OnTarget = raceColumnOnTarget.getFleetByName(testFleet1.getName()); - TrackedRace trackedRaceForTarget = new DummyTrackedRace(competitorsCreatedOnTarget, regattaOnTarget, null); + TrackedRace trackedRaceForTarget = new DummyTrackedRace(raceId, competitorsCreatedOnTarget, regattaOnTarget, null, sourceService.getWindStore()); raceColumnOnTarget.setTrackedRace(fleet1OnTarget, trackedRaceForTarget); @@ -370,7 +378,7 @@ public class MasterDataImportTest { InterruptedException, ClassNotFoundException { // Setup source service RacingEventService sourceService = new RacingEventServiceImpl(); - Event event = sourceService.addEvent(TEST_EVENT_NAME, eventStartDate, eventEndDate, "testVenue", false, eventUUID); + Event event = sourceService.addEvent(TEST_EVENT_NAME, /* eventDescription */ null, eventStartDate, eventEndDate, "testVenue", false, eventUUID); UUID courseAreaUUID = UUID.randomUUID(); CourseArea courseArea = new CourseAreaImpl("testArea", courseAreaUUID); event.getVenue().addCourseArea(courseArea); @@ -401,7 +409,7 @@ public class MasterDataImportTest { List leaderboardNames = new ArrayList(); leaderboardNames.add(leaderboard.getName()); LeaderboardGroup group = sourceService.addLeaderboardGroup(UUID.randomUUID(), TEST_GROUP_NAME, "testGroupDesc", - false, leaderboardNames, null, null); + /* displayName */ null, false, leaderboardNames, null, null); // Set tracked Race with competitors Set competitors = new HashSet(); @@ -425,7 +433,7 @@ public class MasterDataImportTest { CompetitorImpl competitor2 = new CompetitorImpl(competitor2UUID, "Froderik", Color.RED, team2, boat2); competitors.add(competitor2); RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); - TrackedRace trackedRace = new DummyTrackedRace(competitors, regatta, null); + TrackedRace trackedRace = new DummyTrackedRace(raceId, competitors, regatta, null, sourceService.getWindStore()); raceColumn.setTrackedRace(testFleet1, trackedRace); @@ -497,7 +505,7 @@ public class MasterDataImportTest { competitorsCreatedOnTarget.add(competitorOnTarget); Fleet fleet1OnTarget = raceColumnOnTarget.getFleetByName(testFleet1.getName()); - TrackedRace trackedRaceForTarget = new DummyTrackedRace(competitorsCreatedOnTarget, regattaOnTarget, null); + TrackedRace trackedRaceForTarget = new DummyTrackedRace(raceId, competitorsCreatedOnTarget, regattaOnTarget, null, sourceService.getWindStore()); raceColumnOnTarget.setTrackedRace(fleet1OnTarget, trackedRaceForTarget); @@ -526,7 +534,7 @@ public class MasterDataImportTest { ClassNotFoundException { // Setup source service RacingEventService sourceService = new RacingEventServiceImpl(); - Event event = sourceService.addEvent(TEST_EVENT_NAME, eventStartDate, eventEndDate, "testVenue", false, eventUUID); + Event event = sourceService.addEvent(TEST_EVENT_NAME, /* eventDescription */ null, eventStartDate, eventEndDate, "testVenue", false, eventUUID); UUID courseAreaUUID = UUID.randomUUID(); CourseArea courseArea = new CourseAreaImpl("testArea", courseAreaUUID); event.getVenue().addCourseArea(courseArea); @@ -555,7 +563,7 @@ public class MasterDataImportTest { List leaderboardNames = new ArrayList(); leaderboardNames.add(leaderboard.getName()); LeaderboardGroup group = sourceService.addLeaderboardGroup(UUID.randomUUID(), TEST_GROUP_NAME, "testGroupDesc", - false, leaderboardNames, null, null); + /* displayName */ null, false, leaderboardNames, null, null); // Set tracked Race with competitors Set competitors = new HashSet(); @@ -570,11 +578,20 @@ public class MasterDataImportTest { DynamicBoat boat = new BoatImpl("Wingy", boatClass, "GER70133"); CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, team, boat); competitors.add(competitor); - + RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); - TrackedRace trackedRace = new DummyTrackedRace(competitors, regatta, null); + DynamicTrackedRegatta trackedRegatta = sourceService.getOrCreateTrackedRegatta(regatta); + TrackedRace trackedRace = new DummyTrackedRace(raceId, competitors, regatta, trackedRegatta, sourceService.getWindStore()); regatta.addRace(trackedRace.getRace()); // make sure the competitor is serialized together with the regatta raceColumn.setTrackedRace(testFleet1, trackedRace); + WindSource windSource = new WindSourceWithAdditionalID(WindSourceType.EXPEDITION, "Igtimi1"); + WindTrack windTrackOnSource = trackedRace.getOrCreateWindTrack(windSource); + + Position p = new DegreePosition(54, 30); + TimePoint at = new MillisecondsTimePoint(20000); + SpeedWithBearing windSpeedWithBearing = new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(12)); + windTrackOnSource.add(new WindImpl(p, at, windSpeedWithBearing)); + // Serialize List groupNamesToExport = new ArrayList(); groupNamesToExport.add(group.getName()); @@ -628,11 +645,25 @@ public class MasterDataImportTest { competitorsCreatedOnTarget.add(competitorOnTarget); Fleet fleet1OnTarget = raceColumnOnTarget.getFleetByName(testFleet1.getName()); - TrackedRace trackedRaceForTarget = new DummyTrackedRace(competitorsCreatedOnTarget, regattaOnTarget, null); + DynamicTrackedRegatta trackedRegattaForTarget = destService.getOrCreateTrackedRegatta(regattaOnTarget); + TrackedRace trackedRaceForTarget = new DummyTrackedRace(raceId, competitorsCreatedOnTarget, regattaOnTarget, trackedRegattaForTarget, sourceService.getWindStore()); raceColumnOnTarget.setTrackedRace(fleet1OnTarget, trackedRaceForTarget); - // TODO Somehow have check if wind was really imported. DummyTrackedRace not sufficient + WindTrack windTrackOnDest = trackedRaceForTarget.getOrCreateWindTrack(windSource); + windTrackOnDest.lockForRead(); + try { + Iterable fixes = windTrackOnDest.getFixes(); + Assert.assertNotNull(fixes); + Iterator iterator = fixes.iterator(); + Assert.assertTrue(iterator.hasNext()); + Wind firstWindFix = iterator.next(); + Assert.assertNotNull(firstWindFix); + + Assert.assertFalse(iterator.hasNext()); + } finally { + windTrackOnDest.unlockAfterRead(); + } } @@ -645,7 +676,7 @@ public class MasterDataImportTest { PersistenceFactory.INSTANCE.getDomainObjectFactory(MongoDBService.INSTANCE, sourceDomainFactory), PersistenceFactory.INSTANCE.getMongoObjectFactory(MongoDBService.INSTANCE), MediaDBFactory.INSTANCE.getDefaultMediaDB(), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE); - Event event = sourceService.addEvent(TEST_EVENT_NAME, eventStartDate, eventEndDate, "testVenue", false, eventUUID); + Event event = sourceService.addEvent(TEST_EVENT_NAME, /* eventDescription */ null, eventStartDate, eventEndDate, "testVenue", false, eventUUID); UUID courseAreaUUID = UUID.randomUUID(); CourseArea courseArea = new CourseAreaImpl("testArea", courseAreaUUID); event.getVenue().addCourseArea(courseArea); @@ -676,7 +707,7 @@ public class MasterDataImportTest { List leaderboardNames = new ArrayList(); leaderboardNames.add(leaderboard.getName()); LeaderboardGroup group = sourceService.addLeaderboardGroup(UUID.randomUUID(), TEST_GROUP_NAME, "testGroupDesc", - false, leaderboardNames, null, null); + /* displayName */ null, false, leaderboardNames, null, null); // Set tracked Race with competitors Set competitors = new HashSet(); @@ -702,7 +733,7 @@ public class MasterDataImportTest { "Froderik", Color.RED, team2, boat2); competitors.add(competitor2); RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); - TrackedRace trackedRace = new DummyTrackedRace(competitors, regatta, null); + TrackedRace trackedRace = new DummyTrackedRace(raceId, competitors, regatta, null, sourceService.getWindStore()); raceColumn.setTrackedRace(testFleet1, trackedRace); @@ -779,7 +810,7 @@ public class MasterDataImportTest { competitorsCreatedOnTarget.add(competitorOnTarget); Fleet fleet1OnTarget = raceColumnOnTarget.getFleetByName(testFleet1.getName()); - TrackedRace trackedRaceForTarget = new DummyTrackedRace(competitorsCreatedOnTarget, regattaOnTarget, null); + TrackedRace trackedRaceForTarget = new DummyTrackedRace(raceId, competitorsCreatedOnTarget, regattaOnTarget, null, sourceService.getWindStore()); raceColumnOnTarget.setTrackedRace(fleet1OnTarget, trackedRaceForTarget); @@ -800,7 +831,7 @@ public class MasterDataImportTest { InterruptedException, ClassNotFoundException { // Setup source service RacingEventService sourceService = new RacingEventServiceImpl(); - Event event = sourceService.addEvent(TEST_EVENT_NAME, eventStartDate, eventEndDate, "testVenue", false, eventUUID); + Event event = sourceService.addEvent(TEST_EVENT_NAME, /* eventDescription */ null, eventStartDate, eventEndDate, "testVenue", false, eventUUID); UUID courseAreaUUID = UUID.randomUUID(); CourseArea courseArea = new CourseAreaImpl("testArea", courseAreaUUID); event.getVenue().addCourseArea(courseArea); @@ -831,7 +862,7 @@ public class MasterDataImportTest { List leaderboardNames = new ArrayList(); leaderboardNames.add(leaderboard.getName()); LeaderboardGroup group = sourceService.addLeaderboardGroup(UUID.randomUUID(), TEST_GROUP_NAME, "testGroupDesc", - false, leaderboardNames, null, null); + /* displayName */ null, false, leaderboardNames, null, null); // Set tracked Race with competitors Set competitors = new HashSet(); @@ -858,7 +889,7 @@ public class MasterDataImportTest { boat2); competitors.add(competitorToSuppress); RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); - TrackedRace trackedRace = new DummyTrackedRace(competitors, regatta, null); + TrackedRace trackedRace = new DummyTrackedRace(raceId, competitors, regatta, null, sourceService.getWindStore()); raceColumn.setTrackedRace(testFleet1, trackedRace); @@ -914,8 +945,8 @@ public class MasterDataImportTest { domainFactory = destService.getBaseDomainFactory(); // Create existing data on target venueNameNotToOverride = "doNotOverride"; - Event eventNotToOverride = destService.addEvent(TEST_EVENT_NAME, eventStartDate, eventEndDate, venueNameNotToOverride, false, - eventUUID); + Event eventNotToOverride = destService.addEvent(TEST_EVENT_NAME, /* eventDescription */ null, eventStartDate, eventEndDate, venueNameNotToOverride, + false, eventUUID); courseAreaNotToOverride = new CourseAreaImpl("testAreaNotToOverride", courseAreaUUID); eventNotToOverride.getVenue().addCourseArea(courseAreaNotToOverride); @@ -944,7 +975,7 @@ public class MasterDataImportTest { List leaderboardNamesNotToOverride = new ArrayList(); leaderboardNamesNotToOverride.add(leaderboardNotToOverride.getName()); groupNotToOverride = destService.addLeaderboardGroup(UUID.randomUUID(), - TEST_GROUP_NAME, "testGroupDescNotToOverride", false, leaderboardNamesNotToOverride, null, null); + TEST_GROUP_NAME, "testGroupDescNotToOverride", /* displayName */ null, false, leaderboardNamesNotToOverride, null, null); inputStream = new ByteArrayInputStream(os.toByteArray()); MasterDataImporter importer = new MasterDataImporter(domainFactory, destService); @@ -998,7 +1029,7 @@ public class MasterDataImportTest { InterruptedException, ClassNotFoundException { // Setup source service RacingEventService sourceService = new RacingEventServiceImpl(); - Event event = sourceService.addEvent(TEST_EVENT_NAME, eventStartDate, eventEndDate, "testVenue", false, eventUUID); + Event event = sourceService.addEvent(TEST_EVENT_NAME, /* eventDescription */ null, eventStartDate, eventEndDate, "testVenue", false, eventUUID); UUID courseAreaUUID = UUID.randomUUID(); CourseArea courseArea = new CourseAreaImpl("testArea", courseAreaUUID); event.getVenue().addCourseArea(courseArea); @@ -1029,7 +1060,7 @@ public class MasterDataImportTest { List leaderboardNames = new ArrayList(); leaderboardNames.add(leaderboard.getName()); LeaderboardGroup group = sourceService.addLeaderboardGroup(UUID.randomUUID(), TEST_GROUP_NAME, "testGroupDesc", - false, leaderboardNames, null, null); + /* displayName */ null, false, leaderboardNames, null, null); // Set tracked Race with competitors Set competitors = new HashSet(); @@ -1056,7 +1087,7 @@ public class MasterDataImportTest { boat2); competitors.add(competitorToSuppress); RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); - TrackedRace trackedRace = new DummyTrackedRace(competitors, regatta, null); + TrackedRace trackedRace = new DummyTrackedRace(raceId, competitors, regatta, null, sourceService.getWindStore()); raceColumn.setTrackedRace(testFleet1, trackedRace); @@ -1108,7 +1139,7 @@ public class MasterDataImportTest { domainFactory = destService.getBaseDomainFactory(); // Create existing data on target String venueNameToOverride = "Override"; - Event eventToOverride = destService.addEvent(TEST_EVENT_NAME, eventStartDate, eventEndDate, venueNameToOverride, false, eventUUID); + Event eventToOverride = destService.addEvent(TEST_EVENT_NAME, /* eventDescription */ null, eventStartDate, eventEndDate, venueNameToOverride, false, eventUUID); CourseArea courseAreaToOverride = new CourseAreaImpl("testAreaToOverride", courseAreaUUID); eventToOverride.getVenue().addCourseArea(courseAreaToOverride); @@ -1149,14 +1180,14 @@ public class MasterDataImportTest { Leaderboard leaderboardToOverride = destService.addRegattaLeaderboard( regattaToOverride.getRegattaIdentifier(), "testDisplayNameNotToOverride", discardRule); - TrackedRace trackedRace2 = new DummyTrackedRace(competitorsToOverride, regattaToOverride, null); + TrackedRace trackedRace2 = new DummyTrackedRace(raceId, competitorsToOverride, regattaToOverride, null, sourceService.getWindStore()); RaceColumn columnToOverride = leaderboardToOverride.getRaceColumns().iterator().next(); columnToOverride.setTrackedRace(testFleet1ToOverride, trackedRace2); identifierOfRegattaTrackedRace = regattaToOverride .getRaceIdentifier(columnToOverride.getRaceDefinition(testFleet1ToOverride)); - destService.addLeaderboardGroup(UUID.randomUUID(), TEST_GROUP_NAME, "testGroupDescToOverride", false, - new ArrayList(), null, null); + destService.addLeaderboardGroup(UUID.randomUUID(), TEST_GROUP_NAME, "testGroupDescToOverride", /* displayName */ null, + false, new ArrayList(), null, null); destService.getLeaderboardGroupByName(TEST_GROUP_NAME).addLeaderboard(leaderboardToOverride); destService.addLeaderboard(leaderboardToOverride); inputStream = new ByteArrayInputStream(os.toByteArray()); @@ -1225,12 +1256,15 @@ public class MasterDataImportTest { Iterable imageURLs = new HashSet<>(); Iterable videoURLs = new HashSet<>(); - Event event = sourceService.createEventWithoutReplication("Test Event", new MillisecondsTimePoint(0), - new MillisecondsTimePoint(10), "testvenue", false, UUID.randomUUID(), imageURLs, videoURLs); + Iterable sponsorImageURLs = new HashSet<>(); + Event event = sourceService.createEventWithoutReplication("Test Event", /* eventDescription */ null, + new MillisecondsTimePoint(0), new MillisecondsTimePoint(10), "testvenue", false, UUID.randomUUID(), imageURLs, + videoURLs, sponsorImageURLs, /* logoImageURL */ null, /* officialWebsiteURL */ null); CourseArea defaultCourseArea = sourceService.addCourseArea(event.getId(), "ECHO", UUID.randomUUID()); - Regatta regatta = sourceService.createRegatta(RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, UUID.randomUUID(), - new ArrayList(), true, new LowPoint(), defaultCourseArea.getId(), /* useStartTimeInference */ true); + Regatta regatta = sourceService.createRegatta( + RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, + UUID.randomUUID(), new ArrayList(), true, new LowPoint(), defaultCourseArea.getId(), /* useStartTimeInference */ true); // Let's use the setters directly because we are not testing replication RegattaConfigurationImpl configuration = new RegattaConfigurationImpl(); configuration.setDefaultCourseDesignerMode(CourseDesignerMode.BY_MAP); @@ -1241,7 +1275,7 @@ public class MasterDataImportTest { List leaderboardNames = new ArrayList(); leaderboardNames.add(leaderboard.getName()); LeaderboardGroup group = sourceService.addLeaderboardGroup(UUID.randomUUID(), TEST_GROUP_NAME, "testGroupDesc", - false, leaderboardNames, null, null); + /* displayName */ null, false, leaderboardNames, null, null); // Serialize List groupNamesToExport = new ArrayList(); @@ -1289,8 +1323,10 @@ public class MasterDataImportTest { Iterable imageURLs = new HashSet<>(); Iterable videoURLs = new HashSet<>(); - Event event = sourceService.createEventWithoutReplication("Test Event", new MillisecondsTimePoint(0), - new MillisecondsTimePoint(10), "testvenue", false, UUID.randomUUID(), imageURLs, videoURLs); + Iterable sponsorImageURLs = new HashSet<>(); + Event event = sourceService.createEventWithoutReplication("Test Event", /* eventDescription */ null, + new MillisecondsTimePoint(0), new MillisecondsTimePoint(10), "testvenue", false, UUID.randomUUID(), imageURLs, + videoURLs, sponsorImageURLs, /* logoImageURL */ null, /* officialWebsiteURL */ null); CourseArea defaultCourseArea = sourceService.addCourseArea(event.getId(), "ECHO", UUID.randomUUID()); List raceColumnNames = new ArrayList(); @@ -1318,7 +1354,7 @@ public class MasterDataImportTest { List leaderboardNames = new ArrayList(); leaderboardNames.add(leaderboard.getName()); LeaderboardGroup group = sourceService.addLeaderboardGroup(UUID.randomUUID(), TEST_GROUP_NAME, "testGroupDesc", - false, leaderboardNames, null, null); + /* displayName */ null, false, leaderboardNames, null, null); // Set tracked Race with competitors Set competitors = new HashSet(); @@ -1345,7 +1381,7 @@ public class MasterDataImportTest { boat2); competitors.add(competitorToSuppress); RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); - TrackedRace trackedRace = new DummyTrackedRace(competitors, regatta, null); + TrackedRace trackedRace = new DummyTrackedRace(raceId, competitors, regatta, null, sourceService.getWindStore()); raceColumn.setTrackedRace(testFleet1, trackedRace); @@ -1419,7 +1455,7 @@ public class MasterDataImportTest { IOException, InterruptedException, ClassNotFoundException { // Setup source service RacingEventService sourceService = new RacingEventServiceImpl(); - Event event = sourceService.addEvent(TEST_EVENT_NAME, eventStartDate, eventEndDate, "testVenue", false, eventUUID); + Event event = sourceService.addEvent(TEST_EVENT_NAME, /* eventDescription */ null, eventStartDate, eventEndDate, "testVenue", false, eventUUID); UUID courseAreaUUID = UUID.randomUUID(); CourseArea courseArea = new CourseAreaImpl("testArea", courseAreaUUID); event.getVenue().addCourseArea(courseArea); @@ -1451,7 +1487,7 @@ public class MasterDataImportTest { List leaderboardNames = new ArrayList(); leaderboardNames.add(leaderboard.getName()); LeaderboardGroup group = sourceService.addLeaderboardGroup(UUID.randomUUID(), TEST_GROUP_NAME, "testGroupDesc", - false, leaderboardNames, null, null); + /* displayName */ null, false, leaderboardNames, null, null); // Set tracked Race with competitors Set competitors = new HashSet(); @@ -1478,7 +1514,7 @@ public class MasterDataImportTest { boat2); competitors.add(competitorToSuppress); RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); - TrackedRace trackedRace = new DummyTrackedRace(competitors, regatta, null); + TrackedRace trackedRace = new DummyTrackedRace(raceId, competitors, regatta, null, sourceService.getWindStore()); raceColumn.setTrackedRace(testFleet1, trackedRace); sourceService.setRegattaForRace(regatta, "dummy"); @@ -1625,7 +1661,7 @@ public class MasterDataImportTest { InterruptedException, ClassNotFoundException { // Setup source service RacingEventService sourceService = new RacingEventServiceImpl(); - Event event = sourceService.addEvent(TEST_EVENT_NAME, eventStartDate, eventEndDate, "testVenue", false, eventUUID); + Event event = sourceService.addEvent(TEST_EVENT_NAME, /* eventDescription */ null, eventStartDate, eventEndDate, "testVenue", false, eventUUID); UUID courseAreaUUID = UUID.randomUUID(); CourseArea courseArea = new CourseAreaImpl("testArea", courseAreaUUID); event.getVenue().addCourseArea(courseArea); @@ -1656,9 +1692,9 @@ public class MasterDataImportTest { List leaderboardNames = new ArrayList(); leaderboardNames.add(leaderboard.getName()); LeaderboardGroup group1 = sourceService.addLeaderboardGroup(UUID.randomUUID(), TEST_GROUP_NAME, "testGroupDesc", - false, leaderboardNames, null, null); + /* displayName */ null, false, leaderboardNames, null, null); LeaderboardGroup group2 = sourceService.addLeaderboardGroup(UUID.randomUUID(), TEST_GROUP_NAME2, "testGroupDesc2", - false, leaderboardNames, null, null); + /* displayName */ null, false, leaderboardNames, null, null); // Set tracked Race with competitors Set competitors = new HashSet(); @@ -1686,7 +1722,7 @@ public class MasterDataImportTest { competitors.add(competitorToSuppress); RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); - TrackedRace trackedRace = new DummyTrackedRace(competitors, regatta, null); + TrackedRace trackedRace = new DummyTrackedRace(raceId, competitors, regatta, null, sourceService.getWindStore()); raceColumn.setTrackedRace(testFleet1, trackedRace); @@ -1752,7 +1788,7 @@ public class MasterDataImportTest { ScoringScheme scheme = new LowPoint(); List leaderboardNames = new ArrayList(); LeaderboardGroup sourceGroup = sourceService.addLeaderboardGroup(UUID.randomUUID(), TEST_GROUP_NAME, "testGroupDesc", - false, leaderboardNames, discardRule, scheme.getType()); + /* displayName */ null, false, leaderboardNames, discardRule, scheme.getType()); FlexibleLeaderboard sourceLeaderboard1 = new FlexibleLeaderboardImpl("Leaderboard1", null, scheme, null); sourceService.addLeaderboard(sourceLeaderboard1); sourceGroup.addLeaderboard(sourceLeaderboard1); @@ -1884,7 +1920,7 @@ public class MasterDataImportTest { List leaderboardNames = new ArrayList(); leaderboardNames.add(leaderboard.getName()); LeaderboardGroup group = sourceService.addLeaderboardGroup(UUID.randomUUID(), TEST_GROUP_NAME, "testGroupDesc", - false, leaderboardNames, null, null); + /* displayName */ null, false, leaderboardNames, null, null); // Set tracked Race with competitors @@ -1911,7 +1947,7 @@ public class MasterDataImportTest { CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, team2, boat2); competitors.add(competitorToSuppress); - TrackedRace trackedRace = new DummyTrackedRace(competitors, regatta, null); + TrackedRace trackedRace = new DummyTrackedRace(raceId, competitors, regatta, null, sourceService.getWindStore()); leaderboard.addRace(trackedRace, raceColumnName, false); RaceColumn raceColumnOnLeaderboard = leaderboard.getRaceColumnByName(raceColumnName); @@ -2001,8 +2037,8 @@ public class MasterDataImportTest { series, true, new LowPoint(), courseAreaUUID, /* useStartTimeInference */true); raceColumnOnTarget.setTrackedRace(defaultFleetOnTarget, - new DummyTrackedRace( - competitors, regattaOnTarget, null)); + new DummyTrackedRace(raceId, + competitors, regattaOnTarget, null, sourceService.getWindStore())); Assert.assertTrue(leaderboardOnTarget.getScoreCorrection().hasCorrectionFor(raceColumnOnTarget)); Competitor competitorOnTarget = domainFactory.getExistingCompetitorById(competitorUUID); @@ -2010,7 +2046,7 @@ public class MasterDataImportTest { competitorsCreatedOnTarget.add(competitorOnTarget); Assert.assertEquals(defaultFleetOnTarget, leaderboardOnTarget.getFleet(null)); - TrackedRace trackedRaceForTarget = new DummyTrackedRace(competitorsCreatedOnTarget, regattaOnTarget, null); + TrackedRace trackedRaceForTarget = new DummyTrackedRace(raceId, competitorsCreatedOnTarget, regattaOnTarget, null, sourceService.getWindStore()); raceColumnOnTarget.setTrackedRace(defaultFleetOnTarget, trackedRaceForTarget); diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/SearchServiceTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/SearchServiceTest.java index c106ee9064a..852734d46ad 100755 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/SearchServiceTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/SearchServiceTest.java @@ -117,8 +117,10 @@ public class SearchServiceTest { final TimePoint pfingstbuschStartDate = new MillisecondsTimePoint(cal.getTime()); cal.set(2014, 5, 8, 16, 00); final TimePoint pfingstbuschEndDate = new MillisecondsTimePoint(cal.getTime()); - pfingstbusch = server.apply(new CreateEvent("Pfingsbusch", pfingstbuschStartDate, pfingstbuschEndDate, "Kiel", /* isPublic */ - true, UUID.randomUUID(), Collections.emptySet(), Collections.emptySet())); + pfingstbusch = server.apply(new CreateEvent("Pfingsbusch", /* eventDescription */ null, pfingstbuschStartDate, pfingstbuschEndDate, /* isPublic */ + "Kiel", true, UUID.randomUUID(), Collections.emptySet(), + Collections.emptySet(), + /* sponsorImageURLs */ Collections.emptySet(), /* logoImageURLAsString */ null, /* officialWebsiteURLAsString */ null)); kiel = pfingstbusch.getVenue(); final CourseAreaImpl kielAlpha = new CourseAreaImpl("Alpha", UUID.randomUUID()); kiel.addCourseArea(kielAlpha); @@ -148,8 +150,10 @@ public class SearchServiceTest { final TimePoint aalStartDate = new MillisecondsTimePoint(cal.getTime()); cal.set(2014, 5, 8, 18, 00); final TimePoint aalEndDate = new MillisecondsTimePoint(cal.getTime()); - aalEvent = server.apply(new CreateEvent("Aalregatta", aalStartDate, aalEndDate, "Flensburg", /* isPublic */ - true, UUID.randomUUID(), Collections.emptySet(), Collections.emptySet())); + aalEvent = server.apply(new CreateEvent("Aalregatta", /* eventDescription */ null, aalStartDate, aalEndDate, /* isPublic */ + "Flensburg", true, UUID.randomUUID(), Collections.emptySet(), + Collections.emptySet(), /* sponsorImageURLs */ Collections.emptySet(), + /* logoimageURL */ null, /* officialWebsiteURLAsString */ null)); flensburg = aalEvent.getVenue(); final CourseAreaImpl flensburgStandard = new CourseAreaImpl("Standard", UUID.randomUUID()); flensburg.addCourseArea(flensburgStandard); diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy).launch b/java/com.sap.sailing.server/SailingServer (No Proxy).launch index 95e2b7af28e..6b937085b04 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy).launch @@ -19,7 +19,7 @@ - + diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java index 1c7c64d5998..c6aafedf57f 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java @@ -236,15 +236,15 @@ public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetch * The name of the new group * @param description * The description of the new group + * @param displayName TODO * @param displayGroupsInReverseOrder TODO * @param leaderboardNames * The names of the leaderboards, which should be contained by the new group.
* If there isn't a leaderboard with one of these names an {@link IllegalArgumentException} is thrown. - * * @return The new leaderboard group */ LeaderboardGroup addLeaderboardGroup(UUID id, String groupName, String description, - boolean displayGroupsInReverseOrder, List leaderboardNames, int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType); + String displayName, boolean displayGroupsInReverseOrder, List leaderboardNames, int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType); /** * Removes the group with the name groupName from the service and the database. @@ -289,8 +289,8 @@ public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetch */ void addRace(RegattaIdentifier addToRegatta, RaceDefinition raceDefinition); - void updateLeaderboardGroup(String oldName, String newName, String description, List leaderboardNames, - int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType); + void updateLeaderboardGroup(String oldName, String newName, String description, String displayName, + List leaderboardNames, int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType); /** * Executes an operation whose effects need to be replicated to any replica of this service known and @@ -340,6 +340,7 @@ public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetch * * @param eventName * The name of the new event + * @param eventDescription TODO * @param startDate * The start date of the event * @param endDate @@ -352,7 +353,7 @@ public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetch * The name of the venue of the new event * @return The new event */ - Event addEvent(String eventName, TimePoint startDate, TimePoint endDate, String venueName, boolean isPublic, UUID id); + Event addEvent(String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, String venueName, boolean isPublic, UUID id); /** * Updates a sailing event with the name eventName, the venuevenue and the regattas with @@ -459,8 +460,8 @@ public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetch */ ConcurrentHashMap getPersistentRegattasForRaceIDs(); - Event createEventWithoutReplication(String eventName, TimePoint startDate, TimePoint endDate, String venue, boolean isPublic, - UUID id, Iterable imageURLs, Iterable videoURLs); + Event createEventWithoutReplication(String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, String venue, + boolean isPublic, UUID id, Iterable imageURLs, Iterable videoURLs, Iterable sponsorImageURLs, URL logoImageURL, URL officialWebsiteURL); void setRegattaForRace(Regatta regatta, String raceIdAsString); diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java index f22f5d3e356..f28a35623ab 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java @@ -1892,8 +1892,8 @@ public class RacingEventServiceImpl implements RacingEventServiceWithTestSupport @Override public LeaderboardGroup addLeaderboardGroup(UUID id, String groupName, - String description, boolean displayGroupsInReverseOrder, - List leaderboardNames, int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType) { + String description, String displayName, + boolean displayGroupsInReverseOrder, List leaderboardNames, int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType) { ArrayList leaderboards = new ArrayList<>(); for (String leaderboardName : leaderboardNames) { Leaderboard leaderboard = leaderboardsByName.get(leaderboardName); @@ -1903,8 +1903,8 @@ public class RacingEventServiceImpl implements RacingEventServiceWithTestSupport leaderboards.add(leaderboard); } } - LeaderboardGroup result = new LeaderboardGroupImpl(id, groupName, description, displayGroupsInReverseOrder, - leaderboards); + LeaderboardGroup result = new LeaderboardGroupImpl(id, groupName, description, displayName, + displayGroupsInReverseOrder, leaderboards); if (overallLeaderboardScoringSchemeType != null) { // create overall leaderboard and its discards settings addOverallLeaderboardToLeaderboardGroup(result, @@ -1984,8 +1984,8 @@ public class RacingEventServiceImpl implements RacingEventServiceWithTestSupport @Override public void updateLeaderboardGroup(String oldName, String newName, String description, - List leaderboardNames, int[] overallLeaderboardDiscardThresholds, - ScoringSchemeType overallLeaderboardScoringSchemeType) { + String displayName, List leaderboardNames, + int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType) { if (!oldName.equals(newName)) { renameLeaderboardGroup(oldName, newName); } @@ -1993,6 +1993,9 @@ public class RacingEventServiceImpl implements RacingEventServiceWithTestSupport if (!description.equals(group.getDescription())) { group.setDescriptiom(description); } + if (!Util.equalsWithNull(displayName, group.getDisplayName())) { + group.setDisplayName(displayName); + } group.clearLeaderboards(); for (String leaderboardName : leaderboardNames) { Leaderboard leaderboard = getLeaderboardByName(leaderboardName); @@ -2291,11 +2294,14 @@ public class RacingEventServiceImpl implements RacingEventServiceWithTestSupport // Used for TESTING only @Override - public Event addEvent(String eventName, TimePoint startDate, TimePoint endDate, String venue, boolean isPublic, UUID id) { - Event result = createEventWithoutReplication(eventName, startDate, endDate, venue, isPublic, id, - /* imageURLs */ Collections.emptyList(), /* videoURLs */ Collections.emptyList()); - replicate(new CreateEvent(eventName, startDate, endDate, venue, isPublic, id, /* imageURLs */ - Collections. emptyList(), /* videoURLs */Collections. emptyList())); + public Event addEvent(String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, String venue, boolean isPublic, UUID id) { + Event result = createEventWithoutReplication(eventName, eventDescription, startDate, endDate, venue, isPublic, + id, /* imageURLs */ Collections.emptyList(), + /* videoURLs */ Collections.emptyList(), /* sponsorImageURLs */ Collections.emptyList(), /* logoImageURL */ null, /* officialWebsiteURL */ null); + replicate(new CreateEvent(eventName, eventDescription, startDate, endDate, venue, isPublic, /* imageURLs */ + id, Collections. emptyList(), + /* videoURLs */Collections. emptyList(), /* sponsorImageURLs */ Collections. emptyList(), + /* logoimageURL */ null, /* officialWebsiteURLAsString */ null)); return result; } @@ -2305,12 +2311,17 @@ public class RacingEventServiceImpl implements RacingEventServiceWithTestSupport } @Override - public Event createEventWithoutReplication(String eventName, TimePoint startDate, TimePoint endDate, String venue, - boolean isPublic, UUID id, Iterable imageURLs, Iterable videoURLs) { + public Event createEventWithoutReplication(String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, + String venue, boolean isPublic, UUID id, Iterable imageURLs, + Iterable videoURLs, Iterable sponsorImageURLs, URL logoImageURL, URL officialWebsiteURL) { Event result = new EventImpl(eventName, startDate, endDate, venue, isPublic, id); addEvent(result); + result.setDescription(eventDescription); result.setImageURLs(imageURLs); result.setVideoURLs(videoURLs); + result.setSponsorImageURLs(sponsorImageURLs); + result.setLogoImageURL(logoImageURL); + result.setOfficialWebsiteURL(officialWebsiteURL); return result; } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RemoteSailingServerSet.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RemoteSailingServerSet.java index 1d23a8ceaca..4db4fae5870 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RemoteSailingServerSet.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RemoteSailingServerSet.java @@ -103,7 +103,7 @@ public class RemoteSailingServerSet { logger.fine("Updating events for remote server "+ref+" from URL "+eventsURL); URLConnection urlConnection = eventsURL.openConnection(); urlConnection.connect(); - bufferedReader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); + bufferedReader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), "UTF-8")); JSONParser parser = new JSONParser(); Object eventsAsObject = parser.parse(bufferedReader); EventBaseJsonDeserializer deserializer = new EventBaseJsonDeserializer(new VenueJsonDeserializer( diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java index 85b23f469a7..db6a5b63fd1 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java @@ -55,20 +55,25 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials { private static final long serialVersionUID = -11522605089325440L; private Regatta regatta; - public DummyTrackedRace(final Iterable competitors, final Regatta regatta, final TrackedRegatta trackedRegatta) { + public DummyTrackedRace(final Serializable raceId, final Iterable competitors, final Regatta regatta, final TrackedRegatta trackedRegatta, final WindStore windStore) { this(competitors, regatta, trackedRegatta, new RaceDefinitionImpl("DummyRace", new CourseImpl("Dummy Course", Collections.emptyList()), - regatta.getBoatClass(), competitors)); + regatta.getBoatClass(), competitors, raceId), windStore); } public DummyTrackedRace(final Iterable competitors, final Regatta regatta, final TrackedRegatta trackedRegatta, - RaceDefinition race) { - super(race, trackedRegatta, EmptyWindStore.INSTANCE, -1); + RaceDefinition race, WindStore windStore) { + super(race, trackedRegatta, windStore, -1); this.regatta = regatta; } + public DummyTrackedRace(final Iterable competitors, final Regatta regatta, final TrackedRegatta trackedRegatta, + RaceDefinition race) { + this(competitors, regatta, trackedRegatta, race, EmptyWindStore.INSTANCE); + + } public DummyTrackedRace(final String raceName, final Serializable raceId) { super(new RaceDefinitionImpl(raceName, new CourseImpl("Dummy Course", Collections. emptyList()), - /* boatClass */ null, new HashSet()), null, EmptyWindStore.INSTANCE, -1); + /* boatClass */ null, new HashSet(), raceId), null, EmptyWindStore.INSTANCE, -1); } @Override diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/CreateEvent.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/CreateEvent.java index a41191d62e4..0768a94c4a0 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/CreateEvent.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/CreateEvent.java @@ -22,19 +22,27 @@ public class CreateEvent extends AbstractEventOperation { private final TimePoint endDate; private final boolean isPublic; private final String eventName; + private final String eventDescription; private final Iterable videoURLs; private final Iterable imageURLs; + private final Iterable sponsorImageURLs; + private final URL logoImageURL; + private final URL officialWebsiteURL; - public CreateEvent(String eventName, TimePoint startDate, TimePoint endDate, String venue, boolean isPublic, - UUID id, Iterable imageURLs, Iterable videoURLs) { + public CreateEvent(String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, String venue, + boolean isPublic, UUID id, Iterable imageURLs, Iterable videoURLs, Iterable sponsorImageURLs, URL logoImageURL, URL officialWebsiteURL) { super(id); this.eventName = eventName; + this.eventDescription = eventDescription; this.startDate = startDate; this.endDate = endDate; this.venue = venue; this.isPublic = isPublic; this.imageURLs = imageURLs; this.videoURLs = videoURLs; + this.sponsorImageURLs = sponsorImageURLs; + this.logoImageURL = logoImageURL; + this.officialWebsiteURL = officialWebsiteURL; } @Override @@ -55,7 +63,8 @@ public class CreateEvent extends AbstractEventOperation { @Override public Event internalApplyTo(RacingEventService toState) { - return toState.createEventWithoutReplication(getEventName(), startDate, endDate, venue, isPublic, getId(), imageURLs, videoURLs); + return toState.createEventWithoutReplication(getEventName(), eventDescription, startDate, endDate, venue, isPublic, + getId(), imageURLs, videoURLs, sponsorImageURLs, logoImageURL, officialWebsiteURL); } } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/CreateLeaderboardGroup.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/CreateLeaderboardGroup.java index c9652bfd394..c9aa898afb9 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/CreateLeaderboardGroup.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/CreateLeaderboardGroup.java @@ -12,16 +12,18 @@ public class CreateLeaderboardGroup extends AbstractLeaderboardGroupOperation leaderboardNames; private final int[] overallLeaderboardDiscardThresholds; private final ScoringSchemeType overallLeaderboardScoringSchemeType; - public CreateLeaderboardGroup(String leaderboardGroupName, String description, boolean displayGroupsInReverseOrder, List leaderboardNames, - int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType) { + public CreateLeaderboardGroup(String leaderboardGroupName, String description, String displayName, boolean displayGroupsInReverseOrder, + List leaderboardNames, int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType) { super(leaderboardGroupName); this.id = UUID.randomUUID(); this.description = description; + this.displayName = displayName; this.displayGroupsInReverseOrder = displayGroupsInReverseOrder; this.leaderboardNames = leaderboardNames; this.overallLeaderboardDiscardThresholds = overallLeaderboardDiscardThresholds; @@ -43,8 +45,8 @@ public class CreateLeaderboardGroup extends AbstractLeaderboardGroupOperation fleetIterator = fleets.iterator(); if (fleetIterator.hasNext()) { fleet = fleetIterator.next(); - DummyTrackedRace dummy = new DummyTrackedRace(leaderboard.getAllCompetitors(), regatta, null); + DummyTrackedRace dummy = new DummyTrackedRace(UUID.randomUUID(), leaderboard.getAllCompetitors(), regatta, null, EmptyWindStore.INSTANCE); raceColumn.setTrackedRace(fleet, dummy); } } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateLeaderboardGroup.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateLeaderboardGroup.java index 22ea6982131..b74924ec312 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateLeaderboardGroup.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateLeaderboardGroup.java @@ -10,15 +10,17 @@ public class UpdateLeaderboardGroup extends AbstractLeaderboardGroupOperation leaderboardNames; private final int[] overallLeaderboardDiscardThresholds; private final ScoringSchemeType overallLeaderboardScoringSchemeType; public UpdateLeaderboardGroup(String leaderboardGroupName, String newName, String newDescription, - List leaderboardNames, int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType) { + String newDisplayName, List leaderboardNames, int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType) { super(leaderboardGroupName); this.newName = newName; this.newDescription = newDescription; + this.newDisplayName = newDisplayName; this.leaderboardNames = leaderboardNames; this.overallLeaderboardDiscardThresholds = overallLeaderboardDiscardThresholds; this.overallLeaderboardScoringSchemeType = overallLeaderboardScoringSchemeType; @@ -38,8 +40,8 @@ public class UpdateLeaderboardGroup extends AbstractLeaderboardGroupOperation { - public PathCandidate(TimedPosition pos, double vrt, double hrz, int trn, String path, char sid, Wind wind) { + public PathCandidate(TimedPosition pos, boolean reached, double vrt, double hrz, int trn, String path, char sid, Wind wind) { this.pos = pos; // time and position + this.reached = reached; this.vrt = vrt; // height of target projected onto wind this.hrz = hrz; // distance from middle line this.trn = trn; // number of turns @@ -15,6 +16,7 @@ public class PathCandidate implements Comparable { } TimedPosition pos; + boolean reached; double vrt; double hrz; int trn; @@ -22,9 +24,9 @@ public class PathCandidate implements Comparable { char sid; @Override - // sort descending by length, width, height + // sort descending by time, -#turns, width public int compareTo(PathCandidate other) { - if (this.path.length() == other.path.length()) { + if (Math.abs(this.pos.getTimePoint().asMillis() - other.pos.getTimePoint().asMillis()) <= 1000) { if (this.trn == other.trn) { if (Math.abs(this.hrz) == Math.abs(other.hrz)) { return 0; @@ -35,7 +37,7 @@ public class PathCandidate implements Comparable { return (this.trn < other.trn ? -1 : +1); } } else { - return (this.path.length() < other.path.length() ? -1 : +1); + return (this.pos.getTimePoint().asMillis() < other.pos.getTimePoint().asMillis() ? -1 : +1); } } diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorTreeGrowWind.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorTreeGrowWind.java index f398ffe6eb1..6dee9aa366b 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorTreeGrowWind.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorTreeGrowWind.java @@ -31,7 +31,7 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase { private static Logger logger = Logger.getLogger("com.sap.sailing"); private boolean debugMsgOn = false; - double oobFact = 0.75; // out-of-bounds factor + double oobFact = 2.0; // out-of-bounds factor int maxTurns = 0; boolean upwindLeg = false; String initPathStr = "0"; @@ -218,21 +218,21 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase { } double vrtDist = vrtSide*Math.round(posHeight.getDistance(posEnd).getMeters()*1000.0)/1000.0; - // - // TODO: scale last step to exactly reach height of posEnd, and adjust time correspondingly - // - /*if (vrtDist > tgtHeight) { - // scale last step so that vrtDist ~ tgtHeight - Position prevPos = path.pos.getPosition(); - TimePoint prevTime = path.pos.getTimePoint(); - double heightFrac = (tgtHeight - path.vrt) / (vrtDist - path.vrt); - Position newPos = prevPos.translateGreatCircle(prevPos.getBearingGreatCircle(pathPos.getPosition()), prevPos.getDistance(pathPos.getPosition()).scale(heightFrac)); - TimePoint newTime = new MillisecondsTimePoint(Math.round(prevTime.asMillis() + (pathPos.getTimePoint().asMillis()-prevTime.asMillis())*heightFrac)); - pathPos = new TimedPositionImpl(newTime, newPos); - posHeight = pathPos.getPosition().projectToLineThrough(posStart, bearVrt); - }*/ - - // calculate horizontal side: left or right in reference to race course + // scale last step to exactly reach height of posEnd (in reference to appWind) and adjust time correspondingly + boolean reachedEnd = false; + if ((!path.reached) && (vrtDist > 0.0)) { + // scale last step so that vrtDist ~ tgtHeight + Position prevPos = path.pos.getPosition(); + TimePoint prevTime = path.pos.getTimePoint(); + double heightFrac = path.vrt / (path.vrt - vrtDist); + Position newPos = prevPos.translateGreatCircle(prevPos.getBearingGreatCircle(pathPos.getPosition()), prevPos.getDistance(pathPos.getPosition()).scale(heightFrac)); + long newTimeMillis = Math.round((prevTime.asMillis() + (pathPos.getTimePoint().asMillis() - prevTime.asMillis()) * heightFrac)/1000.0)*1000; + TimePoint newTime = new MillisecondsTimePoint(newTimeMillis); + pathPos = new TimedPositionImpl(newTime, newPos); + reachedEnd = true; + } + + // calculate horizontal side: left or right in reference to race course double posSide = 1; Bearing posBear = posStart.getBearingGreatCircle(pathPos.getPosition()); Bearing bearVrt = posStart.getBearingGreatCircle(posEnd); @@ -250,7 +250,7 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase { String pathStr = path.path + nextDirection; char nextBaseDirection = this.getBaseDirection(nextDirection); - return (new PathCandidate(pathPos, vrtDist, hrzDist, turnCount, pathStr, nextBaseDirection, appWind)); + return (new PathCandidate(pathPos, reachedEnd, vrtDist, hrzDist, turnCount, pathStr, nextBaseDirection, appWind)); } @@ -303,6 +303,10 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase { List rightPaths = new ArrayList(); for(PathCandidate curPath : oldPaths) { + if (curPath.reached) { + continue; + } + newPathCands = this.getPathCandsBeatWind(curPath, timeStep, turnLoss, posStart, posEnd, tgtHeight); for (PathCandidate curNewPath : newPathCands) { // check whether path is *outside* regatta-area @@ -522,24 +526,22 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase { } logger.info("Leg Direction: "+legType); - long timeStep = wf.getTimeStep().asMillis()/2; - if (!this.upwindLeg) { - timeStep = timeStep / 2; - } - this.usedTimeStep = timeStep; - logger.info("Time step :" + timeStep); - long turnLoss = pd.getTurnLoss(); // 4000; // time lost when doing a turn + long turnLoss = pd.getTurnLoss(); // time lost when doing a turn if (!this.upwindLeg) { turnLoss = turnLoss / 2; } + logger.info("Turnloss :" + turnLoss); + + this.usedTimeStep = turnLoss + 1000; // time-step larger than turn-loss is required (this may be removed by extended handling of turn-loss) + logger.info("Time step :" + usedTimeStep); // calculate initial position according to initPathStr - PathCandidate initPath = new PathCandidate(new TimedPositionImpl(currentTime, currentPosition), 0.0, 0.0, 0, "0", '0', wndStart); + PathCandidate initPath = new PathCandidate(new TimedPositionImpl(currentTime, currentPosition), false, 0.0, 0.0, 0, "0", '0', wndStart); if (initPathStr.length()>1) { char nextDirection = '0'; for(int idx=1; idx,List> newPaths = this.generateCandidate(allPaths, timeStep, turnLoss, startPos, middlePos, endPos, distStartEndMeters); + Util.Pair,List> newPaths = this.generateCandidate(allPaths, usedTimeStep, turnLoss, startPos, middlePos, endPos, distStartEndMeters); // select good candidates @@ -604,10 +605,10 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase { for(PathCandidate curPath : allPaths) { // terminate path-search if paths are found that are close enough to target //if ((curPath.vrt > distStartEndMeters)) { - if ((curPath.vrt > 0.0)) { + if (curPath.reached) { //logger.info("\ntPath: " + curPath.path + "\n Time: " + (Math.round((curPath.pos.getTimePoint().asMillis()-startTime.asMillis())/1000.0/60.0*10.0)/10.0)+", Height: "+curPath.vrt+" of "+(Math.round(startPos.getDistance(endPos).getMeters()*100.0)/100.0)+", Dist: "+curPath.hrz+"m ~ "+(Math.round(curPath.pos.getPosition().getDistance(endPos).getMeters()*100.0)/100.0)+"m"); int curBin = (int)Math.round(Math.floor( (curPath.hrz + hrzBinSize/2.0) / hrzBinSize )); - if ((Math.abs(curBin) <= 2)) { + if ((Math.abs(curBin) <= 4)) { reachedEnd = true; trgPaths.add(curPath); // add path to list of target-paths } @@ -702,7 +703,7 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase { // debug output for(PathCandidate curPath : trgPaths) { - logger.info("\nPath: " + curPath.path + "\n Time: " + (Math.round((curPath.pos.getTimePoint().asMillis()-startTime.asMillis())/1000.0/60.0*10.0)/10.0)+", Height: "+curPath.vrt+" of "+(Math.round(startPos.getDistance(endPos).getMeters()*100.0)/100.0)+", Dist: "+curPath.hrz+"m ~ "+(Math.round(curPath.pos.getPosition().getDistance(endPos).getMeters()*100.0)/100.0)+"m"); + logger.info("\nPath: " + curPath.path + "\n Time: " + (curPath.pos.getTimePoint().asMillis()-startTime.asMillis()) +", Height: "+curPath.vrt+" of "+(Math.round(startPos.getDistance(endPos).getMeters()*100.0)/100.0)+", Dist: "+curPath.hrz+"m ~ "+(Math.round(curPath.pos.getPosition().getDistance(endPos).getMeters()*100.0)/100.0)+"m"); //System.out.print(""+curPath.path+": "+curPath.pos.getTimePoint().asMillis()+", "+curPath.pos.getPosition().getLatDeg()+", "+curPath.pos.getPosition().getLngDeg()+", "); //System.out.println(" height:"+curPath.vrt+" of "+startPos.getDistance(endPos).getMeters()+", dist:"+curPath.hrz+" ~ "+curPath.pos.getPosition().getDistance(endPos)); } @@ -728,7 +729,7 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase { } else { boolean sameBaseDirection = this.isSameDirection(prevDirection, nextDirection); - TimedPosition newPosition = this.getStep(curPosition, timeStep, turnLoss, sameBaseDirection, nextDirection); + TimedPosition newPosition = this.getStep(curPosition, usedTimeStep, turnLoss, sameBaseDirection, nextDirection); curPosition = new TimedPositionWithSpeedImpl(newPosition.getTimePoint(), newPosition.getPosition(), null); path.add(curPosition); @@ -738,7 +739,7 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase { } // add final position (rescaled before to end on height of target) - path.add(new TimedPositionWithSpeedImpl(bestCand.pos.getTimePoint(), bestCand.pos.getPosition(), null)); + path.add(new TimedPositionWithSpeedImpl(bestCand.pos.getTimePoint(), bestCand.pos.getPosition(), null)); return new PathImpl(path, wf); diff --git a/java/com.sap.sailing.www/index.html b/java/com.sap.sailing.www/index.html index 6df1384f866..c8912c3359f 100644 --- a/java/com.sap.sailing.www/index.html +++ b/java/com.sap.sailing.www/index.html @@ -45,25 +45,25 @@

Upcoming and Live


-
+
@@ -92,6 +92,27 @@

2014

+
Jun 26 - 29 diff --git a/java/com.sap.sailing.www/release_notes_analyze.html b/java/com.sap.sailing.www/release_notes_analyze.html old mode 100644 new mode 100755 index a3e020f4f1e..420be070781 --- a/java/com.sap.sailing.www/release_notes_analyze.html +++ b/java/com.sap.sailing.www/release_notes_analyze.html @@ -35,7 +35,48 @@

Analyzing Races, Leaderboards

-

July 2014

+

August 2014

+ +
    +
  • The 2D race viewer, also known as the "Race Board," now comes with a fresh, new design. The orange + checkbox toggle buttons that open or close views such as the leaderboard or the charts for wind + and competitor comparison, have been replaced by tabs with ">>" symbols. Clicking on those + will expand or collapse the respective view. The views can now be resized using the handles attached + to the dividers separating the views. They work much better on mobile and touch devices than the splitters + used previously did. The settings buttons that used to be integrated with the orange toggle + checkbox buttons have now been moved into the views that they control. For example, the map settings + button is now displayed on the map, below the zoom control on the right map border. +
  • +
  • The leaderboard now has an improved search and filter control. Above the table displaying the + competitors and their figures is a new field with a text input that you can use to filter for + competitors, by their sail number or their name. While filtered, use the checkboxes in front of + the table row to add the competitor to or remove it from the selection. Use the "X" button to the + right of the search input field to clear the search. To the left of the search input field is a + filter symbol that you can use to select and create advanced filtering rules, such as filtering + by rank or nationality. Left of the filter button is the leaderboard settings button. +
  • +
  • The leaderboard has a new first column: a selection check box. We hope this makes it easier in + particular for users on mobile or other touch devices to select more than one competitor at a time. + The old-style multi-selection using the Control and Shift keys, as known well from typical desktop + file explorers, still work like they did before. Note that it is possible to sort for the checkbox + column, putting the selected competitors at the top of the leaderboard. +
  • +
  • When using the auto-zoom feature from the map settings, boats and wind streamlets will now + continue to move smoothly as long as the map only needs panning. Please also note that auto-zooming + to "tails" may slow down the display considerable, particularly when tracking at high frequency. +
  • +
  • When viewing a single race, the leaderboard now by default does no longer show the regatta rank as + the first column (and not as the second one either, given that the first column now usually shows + the checkboxes used for selecting items). If you'd like to get the regatta rank column back, also when + looking at a single race, please choose it from the leaderboard settings dialog where you can find it + in the "Overall Details" section. It may still be a nice column to have when following the last race + in a regatta. If you're just interested in the relative regatta rank ordering you may as well sort by + the right-most total points column which (surprising to some but welcome by most) sorts by the regatta + rank and not by the plain points sum (taking into account medal race and gold/silver/... fleet participations). +
  • +
+ +

July 2014

  • The advantage line now uses the wind at the position in the middle of the leg that the diff --git a/java/com.sap.sailing.xmlexport/src/com/sap/sailing/xmlexport/LeaderboardData.java b/java/com.sap.sailing.xmlexport/src/com/sap/sailing/xmlexport/LeaderboardData.java index a1088b59165..960cae902e8 100644 --- a/java/com.sap.sailing.xmlexport/src/com/sap/sailing/xmlexport/LeaderboardData.java +++ b/java/com.sap.sailing.xmlexport/src/com/sap/sailing/xmlexport/LeaderboardData.java @@ -38,6 +38,7 @@ import com.sap.sailing.domain.common.Tack; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.KnotSpeedImpl; import com.sap.sailing.domain.common.impl.MeterDistance; +import com.sap.sailing.domain.common.impl.MillisecondsDurationImpl; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.tracking.GPSFixMoving; @@ -539,7 +540,13 @@ public class LeaderboardData extends ExportAction { TimePoint endOfRaceForLastTrackedRaceInLeaderboard = null; if (lastRaceColumnWithTrackedRace != null) { TrackedRace lastTrackedRace = lastRaceColumnWithTrackedRace.getTrackedRace(competitor); - endOfRaceForLastTrackedRaceInLeaderboard = lastTrackedRace.getEndOfRace(); + if (lastTrackedRace != null) { + endOfRaceForLastTrackedRaceInLeaderboard = lastTrackedRace.getEndOfRace(); + } else { + // can happen that for this competitor there is no tracked race + // leave time null so that leaderboard latest modification time + // will kick in + } } if (endOfRaceForLastTrackedRaceInLeaderboard != null) { timePointOfLatestModification = endOfRaceForLastTrackedRaceInLeaderboard; @@ -622,7 +629,11 @@ public class LeaderboardData extends ExportAction { addNamedElementWithValue(competitorLegDataElement, "average_velocity_made_good_in_knots", averageVelocityMadeGood != null ? averageVelocityMadeGood.getKnots() : 0); addNamedElementWithValue(competitorLegDataElement, "leg_finished_time_as_millis", handleValue(legFinishTime)); addNamedElementWithValue(competitorLegDataElement, "total_race_time_elapsed_as_millis", handleValue(legFinishTime)-handleValue(trackedLeg.getTrackedRace().getStartOfRace())); - addNamedElementWithValue(competitorLegDataElement, "time_spend_in_this_leg_as_millis", competitorLeg.getTime(legFinishTime).asMillis()); + Duration timeSpentInThisLeg = competitorLeg.getTime(legFinishTime); + if (timeSpentInThisLeg == null) { + legConfidenceAndErrorMessages = updateConfidence("Competitor " + competitor.getName() + " seems to not have finished this leg before end of tracking time.", 0.1, legConfidenceAndErrorMessages); + } + addNamedElementWithValue(competitorLegDataElement, "time_spend_in_this_leg_as_millis", timeSpentInThisLeg != null ? timeSpentInThisLeg.asMillis() : new MillisecondsDurationImpl(0).asMillis()); addNamedElementWithValue(competitorLegDataElement, "gap_to_leader_at_finish_in_seconds", competitorLeg.getGapToLeaderInSeconds(legFinishTime, WindPositionMode.LEG_MIDDLE)); Distance windwardDistanceToOverallLeader = competitorLeg.getWindwardDistanceToOverallLeader(legFinishTime, WindPositionMode.LEG_MIDDLE); addNamedElementWithValue(competitorLegDataElement, "windward_distance_to_overall_leader_that_has_finished_this_leg_in_meters", windwardDistanceToOverallLeader != null ? windwardDistanceToOverallLeader.getMeters() : 0); diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/Util.java b/java/com.sap.sse.common/src/com/sap/sse/common/Util.java index 859e698345b..5fdcc398449 100755 --- a/java/com.sap.sse.common/src/com/sap/sse/common/Util.java +++ b/java/com.sap.sse.common/src/com/sap/sse/common/Util.java @@ -239,7 +239,7 @@ public class Util { * A null-safe check whether t is contained in ts. For ts==null the method * immediately returns false. */ - public static boolean contains(Iterable ts, T t) { + public static boolean contains(Iterable ts, Object t) { if (ts == null) { return false; } diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/busyindicator/SimpleBusyIndicator.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/busyindicator/SimpleBusyIndicator.java index 573b8c58338..5a607e233d6 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/busyindicator/SimpleBusyIndicator.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/busyindicator/SimpleBusyIndicator.java @@ -23,13 +23,16 @@ public class SimpleBusyIndicator extends BusyIndicator { * @param scale Scales the displayed image. 1.0 is 100%, 0.50 is 50%, ... */ public SimpleBusyIndicator(boolean busy, float scale) { - this.setStyleName(STYLE_NAME_PREFIX + "Simple"); + this(busy, scale, STYLE_NAME_PREFIX + "Simple", STYLE_NAME_PREFIX + "Circle"); + } + + public SimpleBusyIndicator(boolean busy, float scale, String panelCssClass, String imageCssClass) { + this.setStyleName(panelCssClass); ImageResource resource = RESOURCES.busyIndicatorCircle(); busyIndicator = new Image(resource.getSafeUri()); - busyIndicator.setStyleName(STYLE_NAME_PREFIX + "Circle"); + busyIndicator.setStyleName(imageCssClass); busyIndicator.setPixelSize((int) (resource.getWidth() * scale), (int) (resource.getHeight() * scale)); add(busyIndicator); - setBusy(busy); } @@ -38,5 +41,4 @@ public class SimpleBusyIndicator extends BusyIndicator { super.setBusy(busy); busyIndicator.setVisible(busy); } - } diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/SliderBar.css b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/SliderBar.css index 888040ecde1..324c623cb1f 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/SliderBar.css +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/SliderBar.css @@ -4,7 +4,7 @@ } .gwt-SliderBar-shell .gwt-SliderBar-line { - background: #efefef; + background: #333; height: 12px; width: 100%; top: 18px; @@ -25,7 +25,7 @@ top: 13px; width: 2px; height: 5px; - background: #efefef; + background: #333; overflow: hidden; } @@ -34,13 +34,21 @@ } .gwt-SliderBar-shell .gwt-SliderBar-mark { - top: 18px; - width: 3px; - height: 12px; + top: 13px; + width: 24px; + height: 24px; background: #fcbb22; overflow: hidden; - border-left: 3px solid #7c7b7c; - border-right: 3px solid #7c7b7c; + border-radius: 15px; +} + +.gwt-SliderBar-shell .gwt-SliderBar-mark-tooClose { + top: 12px; + width: 16px; + height: 16px; + background: #fcbb22; + overflow: hidden; + border-radius: 15px; } .gwt-SliderBar-shell .gwt-SliderBar-tick-disabled { @@ -55,9 +63,17 @@ } .gwt-SliderBar-shell .gwt-SliderBar-markerlabel { - top: 31px; + top: 20px; font-size: 12px; - color: #fcbb22; + color: #333; + cursor: default; + font-family: 'OpenSansSemiBold',Arial,Verdana,sans-serif; +} + +.gwt-SliderBar-shell .gwt-SliderBar-markerlabel-tooClose { + top: 13px; + font-size: 8px; + color: #333; cursor: default; font-family: 'OpenSansSemiBold',Arial,Verdana,sans-serif; } diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/SliderBar.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/SliderBar.java index 1d0f47604d5..793d1f382ca 100755 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/SliderBar.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/SliderBar.java @@ -976,6 +976,8 @@ public class SliderBar extends FocusPanel implements RequiresResize, HasValue 0) { // Create the markers or make them visible + Element lastMarkerElement = null; + int lastMarkerOffsetLeft = 0; for (int i = 0; i < numMarkers; i++) { Marker marker = markers.get(i); Element markerElem = null; @@ -1002,6 +1004,15 @@ public class SliderBar extends FocusPanel implements RequiresResize, HasValue 0 && (markerLeftOffset-lastMarkerOffsetLeft) <= markerWidth) { + if (lastMarkerElement != null) { + lastMarkerElement.setPropertyString("className", "gwt-SliderBar-mark-tooClose"); + lastMarkerElement.getStyle().setLeft(markerLeftOffset-10, Unit.PX); + } + } + lastMarkerElement = markerElem; + lastMarkerOffsetLeft = markerLeftOffset; } // Hide unused markers @@ -1025,6 +1036,8 @@ public class SliderBar extends FocusPanel implements RequiresResize, HasValue 0) { + Element lastMarkerLabel = null; + int lastMarkerLabelOffsetLeft = 0; // Create the labels or make them visible for (int i = 0; i < numMarkers; i++) { Marker marker = markers.get(i); @@ -1060,6 +1073,23 @@ public class SliderBar extends FocusPanel implements RequiresResize, HasValue 0 && (labelLeftOffset-lastMarkerLabelOffsetLeft) <= 20) { + if (lastMarkerLabel != null) { + lastMarkerLabel.setPropertyString("className", "gwt-SliderBar-markerlabel-tooClose"); + lastMarkerLabel.setPropertyString("innerHTML", "M"); // no space for more + int currentMarkerOffsetLeft = labelLeftOffset; + try { + currentMarkerOffsetLeft = Integer.parseInt(markerElements.get(i).getStyle().getLeft().replace("px", "")); + } catch (NumberFormatException ex) { + // ignore, can't do anything + } + lastMarkerLabel.getStyle().setLeft(currentMarkerOffsetLeft-8, Unit.PX); + } + } + lastMarkerLabel = label; + lastMarkerLabelOffsetLeft = labelLeftOffset; } // Hide unused labels diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/TimeSlider.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/TimeSlider.java index 169c2b50c29..1b208b91529 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/TimeSlider.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/TimeSlider.java @@ -12,6 +12,13 @@ import com.google.gwt.dom.client.Style.Visibility; import com.sap.sse.gwt.client.controls.slider.TimeTicksCalculator.NormalizedInterval; import com.sap.sse.gwt.client.controls.slider.TimeTicksCalculator.TickPosition; +/** + * A slider bar whose values are assumed to be Unix time stamps (milliseconds since the epoch, Jan 1st 1970 00:00:00 UTC). + * The ticks are correspondingly rendered as HH:mm time points. + * + * @author Axel Uhl (D043530) + * + */ public class TimeSlider extends SliderBar { private final TimeTicksCalculator calc = new TimeTicksCalculator(); diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/slider_blue.png b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/slider_blue.png index 83c13c371a9..f809570dffb 100644 Binary files a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/slider_blue.png and b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/slider_blue.png differ diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/dialog/DialogUtils.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/dialog/DialogUtils.java index 4a2971c925f..4948f2b254b 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/dialog/DialogUtils.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/dialog/DialogUtils.java @@ -32,6 +32,14 @@ public abstract class DialogUtils { } } }); + widget.addKeyUpHandler(new KeyUpHandler() { + @Override + public void onKeyUp(KeyUpEvent event) { + if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ESCAPE) { + button.click(); + } + } + }); } } diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/dialog/WindowBox.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/dialog/WindowBox.java index dee683bc680..c17f3d3cc13 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/dialog/WindowBox.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/dialog/WindowBox.java @@ -183,9 +183,15 @@ public class WindowBox extends DialogBox { } }); - Grid ctrlGrid = new Grid(1, 2); - ctrlGrid.setWidget(0, 0, this.minimize); - ctrlGrid.setWidget(0, 1, this.close); + Grid ctrlGrid = null; + if (popoutHandler != null) { + ctrlGrid = new Grid(1, 2); + ctrlGrid.setWidget(0, 0, this.minimize); + ctrlGrid.setWidget(0, 1, this.close); + } else { + ctrlGrid = new Grid(1, 1); + ctrlGrid.setWidget(0, 0, this.close); + } this.controlsPanel = new FlowPanel(); this.controlsPanel.setStyleName("gwt-extras-dialog-controls"); diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/mvp/ErrorView.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/mvp/ErrorView.java new file mode 100644 index 00000000000..3feb85e2046 --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/mvp/ErrorView.java @@ -0,0 +1,7 @@ +package com.sap.sse.gwt.client.mvp; + +import com.google.gwt.user.client.ui.Widget; + +public interface ErrorView { + Widget asWidget(); +} diff --git a/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF b/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF index 9846b034790..13809386cfc 100755 --- a/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF +++ b/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: TracTrac Client module Bundle-SymbolicName: com.tractrac.clientmodule -Bundle-Version: 3.0.3 +Bundle-Version: 3.0.5 Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Bundle-ClassPath: ., lib/TracAPI.jar diff --git a/java/com.tractrac.clientmodule/README.txt b/java/com.tractrac.clientmodule/README.txt index 98d24e49111..89db6f52ae3 100644 --- a/java/com.tractrac.clientmodule/README.txt +++ b/java/com.tractrac.clientmodule/README.txt @@ -16,11 +16,62 @@ It contains also some files: - test.sh -> script that compiles the code in the src folder, creates the test.jar library and execute the code of the example. - Manifest.txt -> manifest used to create the test.jar file +******************************************** +************* TracAPI 3.0.5 **************** +******************************************** +This is a final version. Only fixes bugs in the implementation + +Release date: 05/08/2014 + +1) Bugs + +- The previous version 3.0.4 added only changes in the synchronization. The lists were synchronized by hand + but the implementation continued using synchronized lists. The result was that we had a double + synchronization for all the lists of the model: the synchronization by hand and the synchronization of + the list. (Reported by Axel Uhl, 04/08/2014) + + - The new subscription library sends the static positions (from the parameters file) as positions events. + The problem here is that the subscription library retrieves the positions from the model of control + points and when some races are loaded in parallel these values can be invalid (values loaded by + other race). Now the subscription library sends the static positions from the parameters file. + (Reported by Jorge Piera, 04/08/2014) + + +******************************************** +************* TracAPI 3.0.4 **************** +******************************************** +This is a final version. Only fixes bugs in the implementation + +Release date: 04/08/2014 + +1) Bugs + + - Some lists are not thread-safety. The model project exposes some lists based on a + CopyOnWriteArrayList implementation that is thread-safety if you get its iterator. + But if before to invoke the iterator() method of the list, other thread is editing the list, + it is possible to get a list in an invalid status. (Reported by Axel Uhl, 31/07/2014) + + +******************************************** +************* TracAPI 3.0.3 **************** +******************************************** +This is a final version. Only fixes bugs in the implementation + +Release date: 25/07/2014 + +1) Bugs + + - When a race is reloaded more than one time it keeps the entries despite of they have been removed. The new entries + are correctly added but the removed competitors are kept. (Reported by Axel Uhl, 25/07/2014) + + ******************************************** ************* TracAPI 3.0.2 **************** ******************************************** This is a final version. Only fixes bugs in the implementation +Release date: 24/07/2014 + 1) New features - The IRacesListener.updateRace() event is thrown when the live delay for a race has changed. The IRace.getLiveDelay() method @@ -31,13 +82,15 @@ This is a final version. Only fixes bugs in the implementation - The events IRacesListener.abandonRace() and IRacesListener.startTracking() have been reviewed. Now they are always sent when the races are updated either using the event manager or using the external JSON service "update_race_status" - The controls are not updated when they are retrieved a second time from a parameters file. The library always returns - the first control thas was read the first time. + the first control thas was read the first time. (Reported by Axel Uhl, 24/07/2014) ******************************************** ************* TracAPI 3.0.1 **************** ******************************************** This is a final version. Only fixes bugs in the implementation +Release date: 09/06/2014 + 1) Bugs - Error reading JSON of races. @@ -47,6 +100,8 @@ This is a final version. Only fixes bugs in the implementation ******************************************** This is a final version. +Release date: 04/06/2014 + 1) New features - The positions of the static marks are sent like an event diff --git a/java/com.tractrac.clientmodule/lib/TracAPI.jar b/java/com.tractrac.clientmodule/lib/TracAPI.jar index 6a65537bc97..f621dd692ad 100644 Binary files a/java/com.tractrac.clientmodule/lib/TracAPI.jar and b/java/com.tractrac.clientmodule/lib/TracAPI.jar differ diff --git a/java/com.tractrac.clientmodule/pom.xml b/java/com.tractrac.clientmodule/pom.xml index cb120865e99..2c8bd8b8ff8 100755 --- a/java/com.tractrac.clientmodule/pom.xml +++ b/java/com.tractrac.clientmodule/pom.xml @@ -8,6 +8,6 @@ 1.0.0-SNAPSHOT com.tractrac.clientmodule - 3.0.3 + 3.0.5 eclipse-plugin diff --git a/mobile/com.sap.sailing.racecommittee.app/res/layout/confirm_dialog.xml b/mobile/com.sap.sailing.racecommittee.app/res/layout/confirm_dialog.xml new file mode 100755 index 00000000000..e66e4bf6cb7 --- /dev/null +++ b/mobile/com.sap.sailing.racecommittee.app/res/layout/confirm_dialog.xml @@ -0,0 +1,21 @@ + + + + + + + \ No newline at end of file diff --git a/mobile/com.sap.sailing.racecommittee.app/res/values-de/strings.xml b/mobile/com.sap.sailing.racecommittee.app/res/values-de/strings.xml index 8ea76639119..a586c48e09d 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/values-de/strings.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/values-de/strings.xml @@ -28,6 +28,9 @@ Abbruch Einzelrückruf Gesamtrückruf + General Recall ausführen? + Bestätigen + Abbruch Zieleinlauf beginnen AP Flagge November diff --git a/mobile/com.sap.sailing.racecommittee.app/res/values/strings.xml b/mobile/com.sap.sailing.racecommittee.app/res/values/strings.xml index a3adfdf64e4..7fdfebe25ef 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/values/strings.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/values/strings.xml @@ -30,6 +30,9 @@ Display Individual Recall Remove Individual Recall General Recall + Execute General Recall? + Confirm + Cancel Begin finish phase AP Flag November diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/running/BaseRunningRaceFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/running/BaseRunningRaceFragment.java index eb7968ae986..1bc1f758a07 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/running/BaseRunningRaceFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/running/BaseRunningRaceFragment.java @@ -19,6 +19,7 @@ import com.sap.sailing.racecommittee.app.ui.fragments.dialogs.AbortTypeSelection import com.sap.sailing.racecommittee.app.ui.fragments.dialogs.RaceDialogFragment; import com.sap.sailing.racecommittee.app.ui.fragments.dialogs.RaceFinishingTimeDialog; import com.sap.sailing.racecommittee.app.ui.fragments.raceinfo.BaseRaceInfoRaceFragment; +import com.sap.sailing.racecommittee.app.ui.fragments.raceinfo.running.ConfirmDialog.ConfirmRecallListener; import com.sap.sailing.racecommittee.app.ui.utils.FlagPoleStateRenderer; import com.sap.sailing.racecommittee.app.utils.TimeUtils; @@ -83,10 +84,20 @@ public abstract class BaseRunningRaceFragment Default User Agent -> Mobile Devices -> Browsers -> Firefox Fennec 10.0.1 + + **alternatively** + + 2.1. Change default UA [HowToGeek](http://www.howtogeek.com/113439/how-to-change-your-browsers-user-agent-without-installing-any-extensions/) to the one of a Samsung Galaxy Note Tablet with Chrome: +
    +Mozilla/5.0 (Linux; Android 4.1.2; GT-N8000 Build/JZO54K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.166 Safari/537.36 OPR/20.0.1396.73172
    +
    + +#### Installation Keyboard & Setup + +1. [ Hot Virtual Keyboard - Download & Install](http://hot-virtual-keyboard.com/files/vk_setup.exe) +2. Hot Virtual Keyboard Settings: + - Check Main -> Run at Startup + - Check OnScreen Keyboard -> Show the on-screen keyboard when the text cursor is visible + - Check OnScreen Keyboard -> Auto Hide + - Optional: Check Word Autocomplete -> Enabled \ No newline at end of file diff --git a/wiki/amazon-ec2.md b/wiki/amazon-ec2.md index df6372eb584..79419097317 100644 --- a/wiki/amazon-ec2.md +++ b/wiki/amazon-ec2.md @@ -266,14 +266,15 @@ A closer look reveals that an ELB instance consists itself of many other invisib Here are the steps to create a load balanced setup: - Create a master instance holding all data (see http://wiki.sapsailing.com/wiki/amazon-ec2#Setting-up-Master-and-Replica) +- When using the Race Committee App (RCApp), make sure the app is configured to send its data to the master instance and not the ELB (otherwise, write requests may end up at replicas which they wouldn't know how to handle). You may want to configure a separate URL for the master server for this purpose, so you don't have to re-configure the RCApp devices when switching to a different master server. - Create `n` instances that are configured to connect to the master server - Create a load balancer that redirects everything from HTTP port 8888 to HTTP port 8888 and leave the other switches and checkboxes on their default value - As "Ping Port" enter HTTP port 8888 and use /index.html as the "Ping Path." Leave the other values on their defaults again. - Put the ELB into the "Sailing Analytics App" security group as it will appear in the landscape as a regular sailing analytics application server. - Associate all your instances -- Connect your domain with the IP of the load balancer. It could be a good idea to use an Elastic IP that always stays the same for the domain and associate it with your load balancer. That way you can also easily switch between a load balancer and a single instance setup. +- Connect your domain with the IP of the load balancer. It could be a good idea to use an Elastic IP that always stays the same for the domain and associate it with your load balancer. That way you can also easily switch between a load balancer and a single instance setup. Again, remember not to let the RCApp devices point to the ELB domain as their updates could hit a replica which wouldn't know how to handle! -Amazon ELB is designed to handle unlimited concurrent requests per second with “gradually increasing” load pattern (although it's initial capacity is described to reach 20k requests/secs). It is not designed to handle heavy sudden spike of load or flash traffic because of it's internal structure where it needs to fire up more instances when load increases. ELB's can be pre-warmed though by writing to the AWS Support Team. +Amazon ELB is designed to handle unlimited concurrent requests per second with “gradually increasing” load pattern (although it's initial capacity is described to reach 20k requests/secs). It is not designed to handle heavy sudden spike of load or flash traffic because of its internal structure where it needs to fire up more instances when load increases. ELB's can be pre-warmed though by writing to the AWS Support Team. ### Access MongoDB database diff --git a/wiki/extreme-sailing-series.md b/wiki/extreme-sailing-series.md index c46243facb4..cf014ff791e 100644 --- a/wiki/extreme-sailing-series.md +++ b/wiki/extreme-sailing-series.md @@ -18,27 +18,20 @@ Usually not more than 8 competitors race against each other. One of the competit ## Equipment Needed * **From WDF** - * 1x Smartphone - * 5x SIM Cards + * 4x SIM Cards (2x WindBot, Spares) * 1x World Adapter - * 1x Plug Strip * **In Container** - * 1x Toughbook * 2x Samsung Tablet - * 1x iPad - * Expedition Wind Kit -* **In Container (after Qingdao)** - * 1x TFT - * 1x USB-Keyboard - * 1x USB-Mouse - * 2x Switch (8 Port) - * 1x Router (TP-Link) - * 3x CAT6 Network Cable + * 2x WindBots + * 1x Satellite Modem + * 1x Plug Strip +* **Provided by OC** + * 1x Rack with servers and switches + * 2x radio -## Notice of Race -The current notice of race can be downloaded by using the link below. It is worth to mention that the NOR lacks the information about the fact that the last race breaks tie break. +## Official information -Download [[Notice of Race 2013 Final|wiki/uploads/NOR2013_ESS_final.pdf]] +The current notice of race and other information can be accessed on the following page: https://octpftp.egnyte.com/. The username is `ess_main` and the password is `x402014ess`. ## Boats To achieve the goal of providing invitees an exciting event a new type of boats have been designed. Capable of reaching speeds usually reserved to motorboats even in medium wind conditions, the Extreme 40 has been designed by Olympic champions Yves Loday and Mitch Booth, with the aim to provide the international sailing arena with a visually stunning and 100% performance-focused multihull. @@ -48,7 +41,7 @@ To achieve the goal of providing invitees an exciting event a new type of boats Flying a hull in as little as 8 knots of breeze (15 kph), the 40-foot (12m) long carbon speed machine requires coordination, finesse but also sheer muscular power from the crews who battle it out. The generous sail area allows the Extreme 40s to sail faster than the wind, which might seem puzzling at first - in just 15 knots of wind, an Extreme 40 is capable of traveling at over 25 knots ## Scoring -Each race is scored using a high point system where the winner gets 10 points, the second gets 9 and so on (going not further than 3 points). At the end of an event the last race points get doubled (20 for the winner). If there is a tie break between two competitors then the last race sets the winner (breaks tie break). Points from each race are accumulated and result in the overall score for an event. +Each race is scored using a high point system where the winner gets 10 points, the second gets 9 and so on (going not further than 3 points). At the end of an event the last race points get doubled (20 for the winner). If there is a tie break between two competitors then the last race sets the winner (breaks tie break). Points from each race are accumulated and result in the overall score for an event. In addition to the overall leaderboard specific to an event a global leaderboard is being maintained that denotes positions for all events during a year. This scoring scheme used for the global leaderboard has the same rules as an event specific leaderboard. The winner of an event gets 10 points and so on. @@ -56,12 +49,20 @@ In addition to the overall leaderboard specific to an event a global leaderboard Scoring can be altered by the race committee using standard rules like DNS (DidNotStart), DNF (DidNotFinish), DNC (DidNotCount) or RDG (RedressGiven). These rules (except the last one) usually lead to a score of 0 for the race. +### Group Racing +There is a special format where either a group of 6 or 8 boats race against each other. This format is applied whenever the conditions do not allow 10 or more boats to race on the selected race area. + +* Group of 6: 2 groups of 6 boats each will race agains each other. The winner will get 8 points, the second 2 points and so on. +* Group of 8: Three groups A,B and C with 4 competitors each will race against each other. There will always be two groups racing together so that the fleet size is 8. + +If group racing is announced then make sure to select the right scoring scheme that is called `HighPoint, winner gets 10 points (or 8)`. In order to inform the Sailing Analytics about a race being a group race, there is an activator in the race committee app that allows the race committee to mark a race as a group race. In the background the server will add a new event to the RaceLog that is called `AdditionalScoringInformation`. + ## Event Setup The setup for such an event usually consists of the following departments: * Race Committee * Boat on the water - * On the beach + * On the beach (Shore Control) * Commentator * For online streaming and video (beach) * Live (beach) @@ -75,81 +76,421 @@ The setup for such an event usually consists of the following departments: * Wind information (SAP) * Live and official result provider (SAP Sailing Analytics) +## Roles + +* OC Sport (Event Organizer) + * Clementine d'Oiron (Senior SAP Account Manager) + * Jonathan Meadowcroft (CTO, in charge of IT and network infrastructure) + * Liam Lavers (IT Support) +* GMR (SAP Lounge) + * Mark Angell (Senior SAP Account Manager) + * Nick Houchin (SAP Account Manager, Host Manager) +* TracTrac + * Jakob Oedum (Senior Event Manager) + * Jorge Llodra (IT Specialist) +* Sunset & Vine APP (TV) + * Sarah Greene (Manager) + ## Technical Architecture -For the department of Visualization the technical infrastructure can be divided into three major parts. +The technical infrastructure can be divided into two major parts. -1. The first parts (CLOUD) contains external servers accessible over the internet. The main purpose of these servers is to serve as the endpoint for data sent by various tracking systems. The setup of tracking systems consists of the following three trackers: - * A wind tracking system that can transfer information about direction and strength. The transfer is only possible using a publicly accessible IP address. - * A system transferring buoy positions (GPS fixes) over TCP. This system also can only be configured to transfer data over a TCP channel identified by an IP address. - * A tracker that is attached to each boat and that transfers GPS fixes in an regular interval. For this tracker the same limitation as above regarding the addressable endpoint holds +### ON-PREMISE +This part physically consists of a rack that holds all servers needed to run the event in terms of tracking and network setup. All data (gps, wind, ...) is gathered here and then replicated to the cloud. -2. The second part (ON PREMISE INTERNAL) contains all components that are needed to provide analytics. Data sent by trackers to the cloud is retrieved in this section and being analyzed. The connections to TV streaming and other visualization providers takes place here. + * The **DNS-Server** serves not only as a Gateway to the internet but also has a Nameserver installed that redirects some urls to the local setup. In addition to that a caching proxy makes sure that redundant bits of information are cached. + * The **Sailing Analytics** server holds an Apache webserver that answers to *.sapsailing.com requests and of course the Java application running the analytics itself. + * A **TracTrac** server that receives tracking data and provides the API to the analytics server to get GPS fixes in realtime. + * Last but not least a **Hot-Spare** server that can be used in case of failure of another server. -3. The third part (ON PREMISE PUBLIC) describes all components analytical data are distributed on during an event. This includes private mobile phones, iPads being available for guests, flatscreens displaying leaderboards and tv streaming. +In addition to the servers some switches make sure to have enough space for connecting additional networks and machines. To power the whole thing in the not so unlikely event of a power failure two mid-size UPS give enough power to at least shut down the servers cleanly. -### Actual (Muscat 2013) -The actual setup is depicted in the following image. It is easy to see that all of the components in the ON PREMISE PUBLIC area are heavily dependent on a reliable internet connection. This becomes also problematic when the connection is slow because display of analytics requires some bandwidth. +### CLOUD +The cloud consists of one or more Sailing Analytics servers that present the data to the general public not being on premise. The on-premise servers replicate their data constantly to the cloud. - - -### Target (Singapore 2013?) -The following image depicts the setup that is desirable for the next events but not yet implemented. It features a local setup where the dependency on a reliable and fast internet connection is minimized as much as possible. +### Detailed Setup +The following image depicts the setup that is currently implemented. It features a local setup where the dependency on a reliable and fast internet connection is minimized as much as possible. The core of this setup is a server that not only hosts a SAP Sailing Analytics but also the TracTrac server. This way the distribution of analytical information is not dependent on the speed and bandwidth of the local internet connection. By adding a DNS server in front of this analytics server local requests can be directed to the local server even when guests use a public internet address (e.g. www.sapsailing.com). In case of a problem with the local server requests can be redirected to the external analytics server. This server is constantly fed with data by a replication channel that gets information bits from the local analytics server. -The main changes to the actual setup are as follows: - * The SAP Sailing Analytics server that is authoritative for computing the results is no longer in the cloud but installed on premise. That way it is no longer dependent on a replicated TracTrac server but gets data directly from local TracTrac server. -* TracTrac Server is integrated with SAP Sailing Analytics on one physical appliance. That eases maintenance and data exchange between SAP and TracTrac services. +* TracTrac Server is integrated with SAP Sailing Analytics on two physical appliances. That eases maintenance and data exchange between SAP and TracTrac services. * Every leaderboard related information is gathered by accessing the local server. That way the dependency from the internet is drastically mitigated. Everyone on site always gets the right information without delay. * A routing server manages the DNS resolution and in case of a local failure is able to transparently redirect data to the cloud. * Score corrections also are not longer dependent on the internet connection but get fed directly into the local server. -* Wind information for BeTomorrow can be provided without internet connection. - -Two weak points still remain (highlighted by red dotted lines): - -1. Wind data must be sent to a server that is reachable by a public ip address. This problem could be solved by extending the main router with 3G/4G functionality and putting the SAP Sailing Analytics server into DMZ. -2. The same holds for buoy positions. -To not be dependent on a shaky power source that can be restored by "wiggling pieces a little to fix the generator" it has been decided to introduce UPS that can fed important hardware with power up to half an hour. The following picture depicts a quick shot on how this could look like. +To not be dependent on a shaky power source that can be restored by "wiggling pieces a little to fix the generator" it has been decided to introduce UPS that can fed important hardware with power up to half an hour. The following picture depicts a quick shot on how this looks: -### Mapping of URLs for iPhone application +### DNS Server Setup +The DNS Server acts as a gateway/router, caching proxy and nameserver. -We've just mapped the event names to the CSV URLs from your system. The mappings are as follows: +#### Gateway +All traffic to and from the internet is going through this server. To achieve that goal it routes all internal connections by default to an interface called `em2`. This interface is in most cases connected to an external router and must be configured according to the specifications sent over by the ISP. The routing is achieved by first having the right configuration for the interface (example from Cardiff ISP): -'rio' = "http://ess40-2013.sapsailing.com/gateway-ess40/csv/leaderboard?leaderboardName=ESS%202013%20Rio%20%28Extreme40%29" +
    +[root@SAPDNS ~]# more /etc/sysconfig/network-scripts/ifcfg-em2
    +DEVICE="em2"
    +BOOTPROTO="static"
    +HWADDR="D4:AE:52:C9:F4:78"
    +NM_CONTROLLED="no"
    +TYPE="Ethernet"
    +UUID="e72740f5-ce09-4d3f-becf-a58f605211db"
    +ONBOOT="yes"
    +IPADDR=188.164.228.178
    +NETMASK=255.255.255.240
    +GATEWAY=188.164.228.177
    +DNS1=192.168.1.202
    +NAMESERVER=194.150.200.22
    +
    -'nice' = "http://ess40-2013.sapsailing.com/gateway-ess40/csv/leaderboard?leaderboardName=ESS%202013%20Nice%20%28Extreme40%29" +Then the firewall needs to be configured to act as a router. There is a script at `/root/start_proxy.sh` that takes care of activating the right settings for the firewall but also for the proxy: -'cardiff' = "http://ess40-2013.sapsailing.com/gateway-ess40/csv/leaderboard?leaderboardName=ESS%202013%20Cardiff%20%28Extreme40%29" +
    +#!/bin/sh
    +# Squid server IP
    +SQUID_SERVER="192.168.1.202"
    +# Interface connected to Internet
    +INTERNET="em2"
    +NETWORK="rename4"
    +MOBILE="p1p2"
    +# Address connected to LAN
    +LOCAL="192.168.1.0/24"
    +LOCAL2="192.168.4.0/24"
    +# Squid port
    +SQUID_PORT="3128"
    +# Clean old firewall
    +iptables -F
    +iptables -X
    +iptables -t nat -F
    +iptables -t nat -X
    +iptables -t mangle -F
    +iptables -t mangle -X
    +# Enable Forwarding
    +echo 1 > /proc/sys/net/ipv4/ip_forward
    +echo 1024 32768 > /proc/sys/net/ipv4/ip_local_port_range
    +echo 8192 > /proc/sys/net/ipv4/tcp_max_syn_backlog
    +ulimit -HSd unlimited
    +ulimit -HSn 16384
    +# Setting default filter policy
    +iptables -P INPUT DROP
    +iptables -P OUTPUT ACCEPT
    +# Unlimited access to loop back
    +iptables -A INPUT -i lo -j ACCEPT
    +iptables -A OUTPUT -o lo -j ACCEPT
    +# Allow UDP, DNS and Passive FTP
    +iptables -A INPUT -i $INTERNET -m state --state ESTABLISHED,RELATED -j ACCEPT
    +iptables -A INPUT -i $NETWORK -m state --state ESTABLISHED,RELATED -j ACCEPT
    +iptables -A INPUT -i $MOBILE -m state --state ESTABLISHED,RELATED -j ACCEPT
    +# set this system as a router for Rest of LAN
    +iptables -t nat -A POSTROUTING -o $INTERNET -j MASQUERADE
    +iptables -t nat -A POSTROUTING -o $NETWORK -j MASQUERADE
    +iptables -t nat -A POSTROUTING -o $MOBILE -j MASQUERADE
    +iptables -A FORWARD -s $LOCAL -j ACCEPT
    +iptables -A FORWARD -s $LOCAL2 -j ACCEPT
    +# unlimited access to LAN
    +iptables -A INPUT -s $LOCAL -j ACCEPT
    +iptables -A OUTPUT -s $LOCAL -j ACCEPT
    +iptables -A INPUT -s $LOCAL2 -j ACCEPT
    +iptables -A OUTPUT -s $LOCAL2 -j ACCEPT
    +# DNAT port 80 request comming from LAN systems to squid 3128 ($SQUID_PORT) aka transparent proxy
    +iptables -t nat -A PREROUTING -s $LOCAL -p tcp --dport 80 -j DNAT --to $SQUID_SERVER:$SQUID_PORT
    +# if it is same system
    +iptables -t nat -A PREROUTING -i $INTERNET -p tcp --dport 80 -j REDIRECT --to-port $SQUID_PORT
    +#open everything
    +iptables -A INPUT -i $INTERNET -j ACCEPT
    +iptables -A OUTPUT -o $INTERNET  -j ACCEPT
    +iptables -A INPUT -i $NETWORK -j ACCEPT
    +iptables -A OUTPUT -o $NETWORK  -j ACCEPT
    +iptables -A INPUT -i $MOBILE -j ACCEPT
    +iptables -A OUTPUT -o $MOBILE  -j ACCEPT
    +# udp DNS
    +iptables -A OUTPUT -p udp --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT
    +iptables -A INPUT -p udp --sport 53 -m state --state NEW,ESTABLISHED -j ACCEPT
    +# tcp DNS
    +iptables -A OUTPUT -p tcp --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT
    +iptables -A INPUT -p tcp --sport 53 -m state --state NEW,ESTABLISHED -j ACCEPT
    +# DROP everything and Log it
    +iptables -A INPUT -j LOG
    +iptables -A INPUT -j DROP
    +echo "Started firewall"
    +rndc querylog
    +echo "Started query logging for named"
    +
    -'porto' = "http://ess40-2013.sapsailing.com/gateway-ess40/csv/leaderboard?leaderboardName=ESS%202013%20Porto%20%28Extreme40%29" +On interface `p1p2` in most cases there is a 4G router connected that provides an internet connection in case the main internet on `em2` is failing. In case of a failure on `em2` it does make sense to only provide internet to selected clients because the bandwidth on the backup line is very limited. In order to achieve this there is a script at `/root/activate_4gmodem.sh` that can be used to reroute some clients to the 4g line. This script needs the `iproute` package installed. -'istanbul' = "http://ess40-2013.sapsailing.com/gateway-ess40/csv/leaderboard?leaderboardName=ESS%202013%20Istanbul%20%28Extreme40%29" -'qingdao' = "http://ess40-2013.sapsailing.com/gateway-ess40/csv/leaderboard?leaderboardName=ESS%202013%20Qingdao%20%28Extreme40%29" +
    +[root@SAPDNS ~]# more configuration 
    +# Hosts that are routed via 4G modem
    +MODEM_4G_HOSTS="192.168.1.184 192.168.1.201"
     
    -'muscat' = "http://ess40-2013.sapsailing.com/gateway-ess40/csv/leaderboard?leaderboardName=ESS%202013%20Muscat%20%28Extreme40%29"
    +[root@SAPDNS ~]# more activate_4gmodem.sh 
    +source /root/configuration
    +echo "Activating redirection for hosts $MODEM_4G_HOSTS to 4G modem (192.168.200.1)..."
    +ip route add default via 192.168.200.1 dev p1p2 table 4gmodem
    +for host in $MODEM_4G_HOSTS; do
    +    echo "  $host"
    +    ip rule add from $host lookup 4gmodem prio 1000
    +done
    +ip route flush table 4gmodem
    +ip route flush table main
    +sh /root/restart_firewall.sh
     
    -'singapore' = "http://ess40-2013.sapsailing.com/gateway-ess40/csv/leaderboard?leaderboardName=ESS%202013%20Singapore%20%28Extreme40%29"
    +[root@SAPDNS ~]# more deactivate_4gmodem.sh 
    +source /root/configuration
    +echo "Removing all configured connections to 4G modem line..."
    +for host in $MODEM_4G_HOSTS; do
    +    echo "  $host"
    +    ip rule del from $host
    +done
    +ip route flush table 4gmodem
    +ip route flush table main
    +sh /root/restart_firewall.sh
    +
    -'total' = "http://ess40-2013.sapsailing.com/gateway-ess40/csv/leaderboard?leaderboardName=Extreme%20Sailing%20Series%202013%20Overall" +#### Nameserver +The nameserver makes sure to redirect some urls to the local server. In the current configuration it will redirect all ess40-2014.sapsailing.com requests to local servers unless disabled. The basic configuration can be found in `/etc/named.conf` -To update the results for a given event simply access the URL: +
    +options {
    +	listen-on port 53 { 192.168.1.202; 192.168.4.202; };
    +	listen-on-v6 port 53 { ::1; };
    +	directory 	"/var/named";
    +	dump-file 	"/var/named/data/cache_dump.db";
    +        statistics-file "/var/named/data/named_stats.txt";
    +        memstatistics-file "/var/named/data/named_mem_stats.txt";
    +	allow-query     { any; };
    +	recursion yes;
     
    -http://www.extremesailingseries.com/app/results/sap_results.php?event=istanbul
    +	dnssec-enable yes;
    +	dnssec-validation yes;
    +	dnssec-lookaside auto;
     
    -Replacing istanbul with whatever the event name is for the results that you want to update. To update the overall series results just hit:
    +	/* Path to ISC DLV key */
    +	bindkeys-file "/etc/named.iscdlv.key";
     
    -http://www.extremesailingseries.com/app/results/sap_results.php?event=total
    +	managed-keys-directory "/var/named/dynamic";
    +};
    +logging {
    +        channel default_debug {
    +                file "data/named.run";
    +                severity dynamic;
    +        };
    +};
    +zone "." IN {
    +	type hint;
    +	file "named.ca";
    +};
    +zone "0.0.127.in-addr.arpa" IN {
    +	type master;
    +	allow-query { any; };
    +	allow-update { none; };
    +	file "localhost.db";
    +};
    +zone "1.168.192.in-addr.arpa" IN {
    +	type master;
    +	allow-query { any; };
    +	allow-update { none; };
    +	file "1.168.192.in-addr.arpa.db";
    +};
    +zone "4.168.192.in-addr.arpa" IN {
    +	type master;
    +	allow-query { any; };
    +	allow-update { none; };
    +	file "4.168.192.in-addr.arpa.db";
    +};
    +zone "portal.extremesailingseries.com" IN {
    +	type master;
    +	allow-query { any; };
    +	allow-update { none; };
    +	file "portal.extremesailingseries.com.db";
    +};
     
    -http://www.extremesailingseries.com/app/results/csv_uploads/
    +zone "www.sapsailing.com" IN {
    +	type master;
    +	allow-query { any; };
    +	allow-update { none; };
    +	file "sapsailing.com.db";
    +};
    +zone "ess40-2014.sapsailing.com" IN {
    +	type master;
    +	allow-query { any; };
    +	allow-update { none; };
    +	file "sapsailing.com.db";
    +};
    +
    +zone "live.ess40-2014.sapsailing.com" IN {
    +	type master;
    +	allow-query { any; };
    +	allow-update { none; };
    +	file "sapsailing.com.db";
    +};
    +zone "ess40-mobile.sapsailing.com" IN {
    +	type master;
    +	allow-query { any; };
    +	allow-update { none; };
    +	file "sapsailing.com.db";
    +};
    +zone "ess40.local.sapsailing.com" IN {
    +	type master;
    +	allow-query { any; };
    +	allow-update { none; };
    +	file "sapsailing.com.db";
    +};
    +zone "live1.local.sapsailing.com" IN {
    +	type master;
    +	allow-query { any; };
    +	allow-update { none; };
    +	file "sapsailing.com.db";
    +};
    +zone "live2.local.sapsailing.com" IN {
    +	type master;
    +	allow-query { any; };
    +	allow-update { none; };
    +	file "sapsailing.com.db";
    +};
    +zone "ocsportmobile" IN {
    +	type master;
    +	allow-query { any; };
    +	allow-update { none; };
    +	file "portal.extremesailingseries.com.db";
    +};
    +include "/etc/named.rfc1912.zones";
    +include "/etc/named.root.key";
    +
    + +That basic configuration only specifies the urls the nameserver will answer requests. For each url you then need to configure the answer at `/var/named/sapsailing.com.db`. Make absolutely sure to end each url entry with a dot! + +
    +$TTL 86400
    +@ 	IN 	SOA ns.ocsportmobile. admin.ocsportmobile. (
    +			201304131
    +			1H
    +			7200
    +			1D
    +			1H)
    +		NS 	ns.ocsportmobile.
    +		TXT	"ESS DNS"
    +
    +ess40-2014.sapsailing.com.	A	192.168.1.201
    +live.ess40-2014.sapsailing.com.	A	192.168.1.201
    +ess40-2014.ru.sapsailing.com.	A	192.168.1.201
    +live.ru.ess40-2014.sapsailing.com.	A	192.168.1.201
    +ess40-mobile.sapsailing.com.	A	192.168.1.201
    +ess40-local.sapsailing.com.	A	192.168.1.201
    +ess40.local.sapsailing.com.	A	192.168.1.201
    +live1.local.sapsailing.com.	A	192.168.1.201
    +live2.local.sapsailing.com.	A	192.168.1.201
    +www.sapsailing.com.	        A	192.168.1.201
    +
    + +The tool to show configuration and manage entries at runtime is called `rndc`. If you want to see stats then you have to dump them using `rndc stats`. They will then get dumped to `/var/named/data/named_stats.txt`. + +#### Caching Proxy +The transparent caching proxy (Squid) intercepts all connections on port 80 (configured by the firewall script that forwards all connections on port 80 to port 3128 where the proxy runs) and caches some information bits like images or stylesheets. This helps reducing the bandwith. The configuration can be found at `/etc/squid/squid.conf`. The configuration is optimized for high traffic - make sure you understand what you're doing before changing anything. + +
    +http_port 3128 transparent
    +cache_mem 2048 MB
    +cache_store_log none
    +cache_swap_high 100%
    +cache_swap_low 80%
    +half_closed_clients off
    +maximum_object_size 512 KB
    +memory_pools on
    +memory_pools_limit 2048 MB
    +maximum_object_size_in_memory 512 KB
    +ipcache_size 16000
    +ipcache_low 90
    +ipcache_high 97
    +debug_options ALL,2
    +dns_nameservers 192.168.1.202
    +positive_dns_ttl 8 hours
    +negative_dns_ttl 120 seconds
    +fqdncache_size 10000
    +#hierarchy_stoplist cgi-bin ?
    +#acl QUERY urlpath_regex cgi-bin \?
    +#cache deny QUERY
    +acl ads dstdomain "/etc/squid/ads.txt"
    +http_access deny ads
    +acl apache rep_header Server ^Apache
    +access_log /var/log/squid/access.log squid
    +hosts_file /etc/hosts
    +refresh_pattern ^ftp: 1440 20% 10080
    +refresh_pattern ^gopher: 1440 0% 1440
    +refresh_pattern -i (/cgi-bin/|\?.+) 0 0% 0
    +refresh_pattern .*?\?$ 14400 50% 18000 override-expire override-lastmod reload-into-ims ignore-reload ignore-no-cache ignore-private ignore-auth
    +refresh_pattern -i \.jpg$ 14400 50% 18000 override-expire override-lastmod reload-into-ims ignore-reload ignore-no-cache ignore-private ignore-auth
    +refresh_pattern -i \.gif$ 14400 50% 18000 override-expire override-lastmod reload-into-ims ignore-reload ignore-no-cache ignore-private ignore-auth
    +refresh_pattern -i \.png$ 14400 50% 18000 override-expire override-lastmod reload-into-ims ignore-reload ignore-no-cache ignore-private ignore-auth
    +refresh_pattern -i \.css$ 14400 50% 18000 override-expire override-lastmod reload-into-ims ignore-reload ignore-no-cache ignore-private ignore-auth
    +refresh_pattern -i \.js$ 14400 50% 18000 override-expire override-lastmod reload-into-ims ignore-reload ignore-no-cache ignore-private ignore-auth
    +refresh_pattern -i \.woff$ 14400 50% 18000 override-expire override-lastmod reload-into-ims ignore-reload ignore-no-cache ignore-private ignore-auth
    +refresh_pattern -i . 0 20% 4320
    +acl all src all
    +#0.0.0.0/0.0.0.0
    +acl manager proto cache_object
    +acl localhost src 127.0.0.1/32
    +acl to_localhost dst 127.0.0.0/8
    +acl lan src 192.168.1.0/24
    +acl lan src 192.168.4.0/24
    +acl doNotCacheDestinationIP dst 192.168.1.201 192.168.1.203
    +acl doNotCacheDomains dstdomain .sapsailing.com
    +acl SSL_ports port 443 563 # https, snews
    +acl SSL_ports port 873 # rsync
    +acl Safe_ports port 80 # http
    +acl Safe_ports port 21 # ftp
    +acl Safe_ports port 443 563 # https, snews
    +acl Safe_ports port 70 # gopher
    +acl Safe_ports port 210 # wais
    +acl Safe_ports port 1025-65535 # unregistered ports
    +acl Safe_ports port 280 # http-mgmt
    +acl Safe_ports port 488 # gss-http
    +acl Safe_ports port 591 # filemaker
    +acl Safe_ports port 777 # multiling http
    +acl Safe_ports port 631 # cups
    +acl Safe_ports port 873 # rsync
    +acl Safe_ports port 901 # SWAT
    +acl purge method PURGE
    +acl CONNECT method CONNECT
    +http_access allow manager localhost
    +http_access deny manager
    +http_access allow purge localhost
    +http_access deny purge
    +http_access deny !Safe_ports
    +http_access deny CONNECT !SSL_ports
    +http_access allow localhost
    +http_access allow localhost
    +http_access allow lan
    +http_access deny all
    +http_reply_access allow all
    +cache deny doNotCacheDestinationIP
    +cache deny doNotCacheDomains
    +icp_access allow all
    +visible_hostname ocsportmobileproxy
    +always_direct allow all
    +coredump_dir /var/spool/squid
    +
    + +There is a web interface that is configued in `/etc/httpd/conf.d/squid.conf` that enables you to access some stats from http://192.168.1.202/Squid/cgi-bin/cachemgr.cgi. + +## Additional information bits + +### Useful tools to get information about system + +* (Traffic) iptraf +* (CPU) htop +* (DHCP) dhcpstatus -s 192.168.1.0 | grep Active +* (General) multitail /var/log/messages /var/log/squid/access.log /var/log/squid/cache.log /var/named/data/named.run +* (Caching) squidclient -h 192.168.1.202 cache_object://localhost/ mgr:utilization +* (Caching) squidclient -h 192.168.1.202 cache_object://localhost/ mgr:info +* (Caching) http://192.168.1.202/Squid/cgi-bin/cachemgr.cgi?host=localhost&port=3128&user_name=&operation=counters&auth= ### Competitor colors for 2014 @@ -165,4 +506,33 @@ http://www.extremesailingseries.com/app/results/csv_uploads/ #000099 Realteam #990099 Red Bull #16A6ED The Wave + + +### Opening replication channel for Sailing Analytics server + +The local server acts as a master and replicates data through a RabbitMQ running locally to an external server. The external server needs to connect to localhost on 9087. + +
    +autossh -M 20000 -R 5672:localhost:5672 -R 9087:localhost:8887 -L 2012:localhost:2012 sailing@54.194.10.242
    +
    + +### Replicate Race Logs + +
    +#/bin/bash
    +EVENT_NAME="ESS 2014 Qingdao (Extreme40)"
    +REMOTE_USER=mongodb
    +REMOTE_SERVER=54.246.250.138
    +PARAM=$EVENT_NAME
    +
    +echo "Exporting RACE_LOGS for $PARAM"
    +mongoexport --port 10201 -d winddb -c RACE_LOGS -q "{\"RACE_LOG_IDENTIFIER.a\" : \"$PARAM\"}" > race_logs.json
    +mongoexport --port 10201 -d winddb -c LEADERBOARDS -q "{\"REGATTA_NAME\" : \"$PARAM\"}" > leaderboards.json
    +ssh $REMOTE_USER@$REMOTE_SERVER 'rm /tmp/*.json'
    +scp race_logs.json $REMOTE_USER@$REMOTE_SERVER:/tmp 
    +scp leaderboards.json $REMOTE_USER@$REMOTE_SERVER:/tmp 
    +echo "Saved race logs and leaderboard"
    +ssh $REMOTE_USER@$REMOTE_SERVER '/opt/mongodb-linux-x86_64-1.8.1/bin/mongoimport --port 10201 --upsert -d winddb -c RACE_LOGS < /tmp/race_logs.json' 
    +ssh $REMOTE_USER@$REMOTE_SERVER '/opt/mongodb-linux-x86_64-1.8.1/bin/mongoimport --port 10201 --upsert -d winddb -c LEADERBOARDS < /tmp/leaderboards.json' 
    +echo "Finished"
     
    \ No newline at end of file diff --git a/wiki/images/winscp/winscp-create-folder.png b/wiki/images/winscp/winscp-create-folder.png new file mode 100644 index 00000000000..5a73dfa3bf4 Binary files /dev/null and b/wiki/images/winscp/winscp-create-folder.png differ diff --git a/wiki/images/winscp/winscp-install-1.png b/wiki/images/winscp/winscp-install-1.png new file mode 100644 index 00000000000..c3db21fb777 Binary files /dev/null and b/wiki/images/winscp/winscp-install-1.png differ diff --git a/wiki/images/winscp/winscp-keys.png b/wiki/images/winscp/winscp-keys.png new file mode 100644 index 00000000000..fb826324342 Binary files /dev/null and b/wiki/images/winscp/winscp-keys.png differ diff --git a/wiki/images/winscp/winscp-login.png b/wiki/images/winscp/winscp-login.png new file mode 100644 index 00000000000..7f31dae8077 Binary files /dev/null and b/wiki/images/winscp/winscp-login.png differ diff --git a/wiki/images/winscp/winscp-var-www-static.png b/wiki/images/winscp/winscp-var-www-static.png new file mode 100644 index 00000000000..5bb7822494c Binary files /dev/null and b/wiki/images/winscp/winscp-var-www-static.png differ diff --git a/wiki/onboarding.md b/wiki/onboarding.md index 6c12491f91e..492d3e5ce81 100644 --- a/wiki/onboarding.md +++ b/wiki/onboarding.md @@ -2,6 +2,8 @@ This document describes the onboarding process for a new team member (developer) +First of all, make sure you've looked at http://www.amazon.de/Patterns-Elements-Reusable-Object-Oriented-Software/dp/0201633612. That's a great book, and knowing at least some of it will help you a great deal finding your way around our solution. + ### Race Analysis Development Setup #### Installations diff --git a/wiki/profiling.md b/wiki/profiling.md new file mode 100644 index 00000000000..0fb5a921aa0 --- /dev/null +++ b/wiki/profiling.md @@ -0,0 +1,9 @@ +# Profiling + +Use the SAP JVM 7 available at http://sapjvm.wdf.sap.corp:1080 and its Eclipse profiler plug-in available from the same location. The update site to add to your Eclipse is http://sapjvm.wdf.sap.corp:1080/profiling. After unpacking the VM, make it known by your Eclipse installation and use it for the launch configuration that you want to profile. + +## Profiling GWT Client Code + +Using GWT's DevMode, you can also profile GWT Java code running in the client. You have to keep in mind that many things work differently when in DevMode and that significant overhead for the Browser-to-JavaVM communication is generated. However, your core algorithms that require attention performance-wise will often still stick out, assuming some clever filtering in the Eclipse profiler plug-in which is easily possible. + +For GWT profiling please note that there is no immediate support to launch an SAP JVM in profiling mode for a GWT launch config. Instead, use the `Sailing GWT Remote Profiling (Run-Mode only)` launch configuration in Run mode. It will pause at start-up and wait for the profiler to get connected. Then use the profiling launch configuration `Profile SailingGWT` to get started. \ No newline at end of file diff --git a/wiki/racecommittee-app-environment.md b/wiki/racecommittee-app-environment.md index ededef0ca90..00b2296f8d4 100644 --- a/wiki/racecommittee-app-environment.md +++ b/wiki/racecommittee-app-environment.md @@ -19,6 +19,12 @@ On Ubuntu, use the following instead: apt-get update apt-get install libncurses5:i386 libstdc++6:i386 zlib1g:i386 +On a Debian version 7.6 I succeeded with the following: + + dpkg --add-architecture i386 + apt-get update + apt-get install ia32-libs lib32asound2 lib32v4l-0 libv4l-0=0.8.0-1 libasound2=1.0.23-2.1 + Now we are ready to install the SDK. Pick up the [SDK Tools](http://developer.android.com/sdk/index.html) and install them on your server. Set an environment variable _ANDROID_HOME_ pointing to the install directory. It's a good idea to append some of the tools to your path: PATH=$PATH:$ANDROID_HOME/tools diff --git a/wiki/racelog-tracking/app-spec/app-spec.css b/wiki/racelog-tracking/app-spec/app-spec.css new file mode 100644 index 00000000000..3a1b3cc34e1 --- /dev/null +++ b/wiki/racelog-tracking/app-spec/app-spec.css @@ -0,0 +1,47 @@ +div.admonition { + font-size: 0.9em; + margin: 1em 0 1em 0; + border: 1px solid #555; + border-radius: 2px; + padding: 0; +} + +div.admonition p { + margin: 0.5em 1em 0.5em 1em; + padding: 0; +} + +div.admonition pre { + margin: 0.4em 1em 0.4em 1em; +} + +div.admonition p.admonition-title { + padding-top: 0.5em; + font-weight: bold; +} + +div.admonition ul, div.admonition ol { + margin: 0.1em 0.5em 0.5em 3em; + padding: 0; +} + +div.app { + background-color: #99D6FF; +} + +div.server { + background-color: #FFFF66; +} + +div.both { + background-color: #99E699; +} + +div.image { + text-align: center; + margin: 2em 0 1em 0; +} + +dt { + font-style: italic; +} diff --git a/wiki/racelog-tracking/app-spec/app-spec.md b/wiki/racelog-tracking/app-spec/app-spec.md index 625a32de95c..c816899993c 100644 --- a/wiki/racelog-tracking/app-spec/app-spec.md +++ b/wiki/racelog-tracking/app-spec/app-spec.md @@ -3,14 +3,25 @@ Integrating smartphones into the _Sailing Analytics_ has long been talked about, and some effort has been invested on the server side to implement this idea. So far, the smartphone side of things has received substantially less attention, even though it is probably the most essential part when it comes to making the solution desirable for sailors. The missing parts on both sides and a high-level specification for the app are presented here. ## Table of Contents -[[_TOC_]] + - + [TOC] ## A lengthier Introduction -The _SAP Sailing Analytics_ use tracking data to allow users to analyze sailing regattas in their browser - both on a regatta level, covering multiple races in tables called _Leaderboards_, and on a per-race level through map-centric interactive views called _RaceMaps_. The Sailing Analytics are currently targeted at large sailing events. Dedicated tracking providers collect and send the positional data of the sailing boats along with competitor metadata, and trained personnel that knows the Sailing Analytics inside out operates them on-site, for example by maintaining the regatta data (regattas, races, fleets, result corrections etc.). +The _SAP Sailing Analytics_ use tracking data to allow users to analyze sailing regattas in their browser - both on a regatta level, covering multiple races in tables called _Leaderboards_, and on a per-race level through map-centric interactive views called _RaceBoards_. The Sailing Analytics are currently targeted at large sailing events. Dedicated tracking providers collect and send the positional data of the sailing boats along with competitor metadata, and trained personnel that knows the Sailing Analytics inside out operates them on-site, for example by maintaining the regatta data (regattas, races, fleets, result corrections etc.). + + +
    + +

    Leaderboard: a tabular view of races

    +
    + +
    + +

    RaceBoard: a customizable combination of different elements (e.g. Leaderboard, map, charts)

    +
    We believe that the Sailing Analytics's functionality can benefit sailors not only at large sailing events, but for many more occasions. Among other devices, smartphones can serve as a source of tracking data, and sailors can define their own training races or even club regattas, so that they can then analyze their tracks. This would shift the Sailing Analytics from being a fully-provided event solution to a (possibly freely) availabe online tool that sailors can use whenever they want. One can see how this idea has the potential to make the Sailing Analytics and all the work that has been put into them so far more visible and accessible, ultimately transforming it into a tool every sailor knows and loves to use. @@ -20,7 +31,7 @@ Obviously, the base functionality of such an app is to provide a service that ga This document details out what is still missing both on server and smartphone side, aiming to serve as a specification for the furhter implementation. As the app plays such a crucial part in the overall story, and as it has received so little attention so far, one possibility is to outsource its design and implementation, so that the the document also serves as a high-level requirements specification. -Missing parts and requirements are summarized at the end of a section with the following color-coding: +Missing parts and requirements are summarized throughout this document with the following color-coding: !!! app Requirements concerning the app only. @@ -30,15 +41,187 @@ Missing parts and requirements are summarized at the end of a section with the f !!! both Requirements involving both the smartphone and server tier. + -First an overview of the current status is given, which is focussed mainly on the server side. However, certain requirements relevant for the app are already mentioned along the way. Then the focus shifts to use-cases, to provide a description of how the app and smartphone tracking as a whole should perform from the viewpoint of a user, with less technical details and without regarding server-side constraints and issues. Finally, further technical issues are discussed for the app, such as an enumeration of the pros and cons of a native vs. hybrid solution. +First the use cases for the tracking app are presented, which should give a good understanding of the app strcuture and scenarious we envision the app to be used in. This is followed up with other features that are relevant for the app, apart from the main uses cases. Finally, a technical addendum introduces more technical aspects, beginning with the app, and moving to backend implementation details. -## Status Quo -On the server side, the necessary parts to enable smartphone tracking have been implemented. This section gives a short overview of what is possible right now. -### Receiving and storing Tracking Data +## Use Cases +The proposed smartphone app should enable casual users to apply the Sailing Analytics to their own tracking data. The following paragraphs describe the most important use cases in this context from the viewpoint of the users, leaving the technical details for a later section. The use cases are ordered by their importance - the ones that should be supported first are presented first. + +### Individual Training +This can be considered as being the simplest use-case. After downloading the app, the sailor creates a user account. Alternatively, he may log in to an existing account. As is common for apps this login should persist across app and phone restarts, until the users explicitly logs out. The user data could also serve as the basis for the competitor data (for which additional information about the country, sail-number and boatclass is required), but this discussed later on in the technical addendum. + +!!! server + Clarify how user and competitor management play together. Are they entirely separate, and a user can create different competitors for his account, and switch between them, or is a user the same thing as a competitor? What happens, e.g., if a user switches to a different boat - changing the boat class and sail-number in his user preferences would cause wrong information for old races. Instead of having to login with a different user, instead the user might select from the list of competitors he has created from that account, or choose to add a new one. + +
    + +On the following start screen there is a dominant button for starting the tracking - this functionality has to be readily available at all times, as missing out on tracking data is the potentially worst thing that can happen - everything else can be done later on. During tracking, the following information should be shown: + +* warning if GPS signal is unavailable +* connectivity status (pending fixes that need to be sent to the server) +* a few basic metrics (speed, heading) +* possibly a map representation of the track + +All the above information can be presented in an offline situation - the user can start tracking, in which case the fixes are only buffered locally, and speed and heading can be directly extracted from the GPS module. Other data interesting for the user, which oftentimes relies on wind information, could be provided in an online setting: + +* wind strip charts +* VMG +* manoeuvre loss + +There are probably plenty more figures one might want to display, and one screen for displaying this may not be enough. Different variations are possible: the user might configure what information to show, be able to switch between different information screens, or these might alternate automatically (e.g. every 5 seconds) to allow hands-free operation. + +The tracking itself should be able to run in the background - the tracking screen itself then displays additional information. But without influencing the tracking, the user should be able to continue using other apps, or also this app and e.g. already define a race structure, or share a link to this training session without interrupting tracking. + +!!! server + Providing the calculated figures, most of which are wind-related, requires the existence of a TrackedRace on the server. In the absence of such, either the user could be prompted to create one, or a placeholder TrackedRace could be created for every tracking session for which a user has not manually created a TrackedRace or accepted an invitation. + +After stopping the tracking, a summary of the data can be given, including the transmission status. For this simple scenario, selecting an _Analyze_ button sets up a race with this competitor and all necessary data automatically, so that the user can proceed to directly analyze his training session in the RaceBoard. + +!!! app + As already mentioned, tracking, storing and transmitting the fixes should be handled in a crash-safe background process, with the goal of not loosing any tracking data. Switching to a different app, or moving away from the tracking screen within the app (e.g. to start defining the race) should not influence the tracking - it should only be stopped when explicitly requested. + +### Race +Multiple competitors want to compete in a race and let their friends on shore get a real-time impression of what is happening on the water. One of them defines the race on his smartphone, for which he enters the basic data and then invites his friends to join through the app. On the water, they lay out the marks for the course, and while laying out each buoy they ping their location at that moment. From these pinged buoys they can define the course layout. + +!!! app + Users should be able maintain friend lists, so that they can easily invite other users they often interact with. But if a group of competitors sail together that do not know each other, the process of adding each of the competitors by his username is slow and tedious for the user managing the race. Instead it should be possible to exchange invitations via NFC and/or a QRCode that is displayed on the screen of the managing user. Everybody who scans this QRCode or touches their phone to that managing user's phone while in _invitation mode_ could be automatically added to the race. + +If no other wind data is available (e.g. no wind sensor is tethered to one of the smartphones), the opportunity to manually enter the wind data should exist. Different possibilities exist, either by entering the wind angle and strength in numbers, or for example hitting a _Set Wind_ button at the moment when the boat is pointed directly into the wind - then relying on compass data and a proper orientation of the smartphone within the boat (or possibly corrected by COG information if the boat was previously moving). + +After entering a start time, everything happens automatically. A countdown commences on all of the smartphones - all have been automatically informed of the start time. The app automatically begins tracking a few minutes before the start of the race, and stops once the boat has crossed the finish line. + +The analyze button that links to the RaceBoard is visible to the other invited users as well. To let others view the race analysis, a link can be shared - which should include a sufficiently long random component to make links of races that should not be shared non-guessible (cf. to the _Anyone who has the link_ sharing option of Google Docs). Having been sent this link, the friends on shore see a live RaceBoard view of the sailboats. For example, after the race as finished, a standard _Share_ button for that mobile platform should be displayed. + +If no mobile connection is available, the steps of defining the race, and inviting the other competitors can be postponed. Instead, everybody just starts and stops tracking manually. Once everybody is back on shore and has internet access (e.g. through a WiFi hotspot int he club house), the race can the be defined for a timespan in the past. The invitations are sent out just the same, and by accepting, the others make their tracking data for the span of the race available. + +After having a look at the race in the evening, our sailors notice that they apparently pinged the marks of the windward gate in the wrong position, and decide to adjust the course through the app. + +!!! server + This use-case gives some insight into the further details of user management. An authorization mechanism will have to be put into place, that encompasses the following data: Event-Race structure, RaceLog and tracking data. An initial approach could be to allow editing of such structures only by the user that created them, and grant explicit read permissions to other users. The other participants in the race can grant read access to their tracking data for the duration of the race, and the creator of the race in turn grants read access to his TrackedRace (and the RaceLog, and implicitly the RaceBoard). We then have to start enforcing these access rights, which might be possible on multiple levels. This might be done on a servlet level by adding security constraints to the ``web.xml``s, or checked within the application (or perhaps both). Also, one would have to go about securing the GWT-RPC communicatin channel. + +!!! both + The app in turn must support managing these rights - in part this may be implicit (accepting an invitation = granting read access to tracking data), but for other parts an explicit interface might be necessary (sharing a race with other users). The topic may be extended to managing groups, and group-based access permissions (e.g. for sailing clubs). + +!!! both + The first method described to define a course is the simple case: individual marks are pinged with a fixes location, and then combined into gates, lines or kept as individual marks and brought into order that makes up the course. This should be possible from the app, and a map-view of the current course (mark positions, gate/line combinations, directions of the legs) should be offered to provide a visual feedback on whether the course layout is correct. The post-race course definition and course editing is slightly more complex, and should ideally show the competitor tracks, as these offer a good indication of where which WayPoint was. Also this might offer the possibility to slightly alter the positions of the marks throughout the duration of the race in case of drifting buoys. To give full flexibility, users should ideally be able to add and delete individual pings, which act similarly to keyframes with automatic interpolation of the position inbetween - the only problem is that right now fixes cannot be deleted from a track. What functionality should be available from the app or a desktop version is discussed later. + +### Regatta +If the solution becomes nice enough to use, sailing clubs will possibly want to use the Sailing Analytics for their own regattas in the same fashion they see them used at larger events. In this case, the functionality for defining the Event-Race and Leaderboard structure currently only accessible through the admin console would have to be exposed through the app. + +Defining an easy-to-use wizard for defining such a regatta structure is probably time-consuming. It should contain sensible defaults, so that for the common cases creating such a regatta is as easy as possible, but potentially the wizard should offer full flexibility. + +!!! both + The management of events, associated regattas, series, fleets, races and leaderboards should probably best be exposed through a RESTful API. When this point is reached, it makes sense to discuss if every possible detail must and should be exposed, or if a subset may be enough to cover most cases, and make it as easy as possible for users. + +### Combining individual Training Sessions +A user might wish to compare several of his individual sessions on one course, or different users that sailed the course at different times might want to virtually combine their tracks into a race, as if they had sailed at the same time. + +As soon as the start line is defined, the first crossing of this line after the beginning of a tracking session can be used as the start time for that competitor. By aligning the competitor tracks by their individual start times, a virtual race can be created. + +!!! server + This is a new feature, as up to now we have only considered physical times and had no need to build virtual views with adjusted times. A simple possibility might be to define a time offset for every track on the creation of a TrackedRace, so that incoming fixes are adjusted by that offset. Whether this feature is needed should be discussed. + + +## Further Features + +### Multimedia Integration +Of course a smartphone is the perfect source for further multimedia that enriches the race. This might be videos, photos, notes or audio recordings that are linked to the timepoint at which they were taken/added. During tracking, the app should offer possibilities to take a photo/video/notes, which are then automatically uploaded when an internet connection is available, and linked to that point in time. + +!!! server + Managing arbitrary multimedia is a totally new feature. On the one hand, this multimedia has to be stored - though it might be possible to outsource this (photos on Flickr, videos on Youtube). Then these should somehow be linked to the corresponding timepoints, and might appear in the timeline of the RaceBoard, or maybe as a list in the app. + +### Additional Sensors +Smartphones can act as the intermediary for data from further sensors. On the one hand, the integrated sensors (e.g. gyroscope/accelerometer) can provide potentially useful information - these can be polled in a standardized API. On the other hand wind sensors or others connected via bluetooth/WiFi can be an extremely valuable source of information. + +The app should allow configuration of what additional sensor data to collect. This sensor data should be handled similarly to the tracking data, in that it is uploaded whenever a mobile connection is available. If the bandwidth is limited, or the connection is on and off, then the tracking data should be prioritized, so that if possible the newest tracking data is the first thing to arrive at the server. + +To keep down the complexity, it is probably sensible to only support such sensors which can be polled from within the app. In this case, the user can configure what sensors data to collect, and this data is automatically linked to that race the user participated in during that time. Of course, this would probably restrict the sensors to a list of those sensors we explicitly support. + +!!! server + Wind data is already part of the domain model, but with more types of data becoming available (heartrate, gyroscope), we have to think about a suitable mechanism for storing, linking and somehow displaying arbitrary data. For example, sensible aggregations on certain data types might be defined, and if present these could then be added as columns to the Leaderboard, and also presented as charts. + +!!! both + Whether the proposed approach is viable depends on how the available external sensors that can be tethered to smartphones can be polled - is this even possible from within the tracking app, or are these devices built to only work with the dedicated app the manufacturer of the sensor offers? This approach also limits the user's choices in sensors to those that we explicitly support, unless there is a standardized access to e.g. all external wind sensors. But this would allow us to offer live transmission of additional sensor data to Sailing Analytics - most valuable probably for wind. Another possibility is to offer import capabilities for standard formats (e.g. wind data in NMEA or gpx format). What formats are supported depends on what existing devices offer as an output format, this would open us up to support more devices, but create an additional step for the user. Offering both possibilities might be best. + +### Battery Life Management +After a few hours of tracking with the display turned on the whole time - displaying the tracking status information - the smartphone battery will likely have been depleted rapidly. To guarantee that the most important data - the tracking data - is collected in any case, the app should act in a battery-level-aware fashion. + +There are plenty of different options for extending the battery life that can be influenced by the app: + +* polling rate (GPS position, additional sensors) + * to stretch the battery life as long as possible, all sensors but the GPS module could be disabled, and the polling rate for GPS could be reduced to the slowest sensible setting +* connectivity + * disabling sending of tracking and other sensor data completely and only buffering locally will extend battery life + * if calculated figures from the TrackedRace, which have to be synchronized via the mobile network, (e.g. VMG) are integrated, this polling can be disabled to extend battery life +* display (brightness, turn off automatically) + +So in the most battery-saving mode, the app could let the display turn off after a few seconds of inactivity, and only poll the GPS position every second, writing it directly to local storage without trying to send it. + +For some cases it makes sense to allow the user to choose a battery-saving mode even if the battery is full, e.g. if he knows he will want to use the app throughout the day and won't have a chance to recharge the phone. This could e.g. be accomplished by providing different preconfigured _energy profiles_ (as known from Microsoft Windows) that are automatically selected depending on the battery level, but which may be overriden by the user. + + +### Adapted RaceBoard for Smartphones +As mentioned in the introduction, next to the Leaderboards, the RaceBoards are the main user-facing views on the tracking data. Currently, such a RaceBoard is available only as a web-application in the browser, and targeted at desktop computers. It can be viewed with tablets that provide a decent screen resolution, but the interaction (button size, interacting with the map view) is not aimed at smaller touch-enabled devices. + + +
    + +

    Existing RaceBoard (in-browser)

    +
    + +On smartphones, there is definitely not enough screen estate to display the RaceBoard in its current status. However, users that defined a race and tracked their boats with the app should also be able to analyze the race using their smartphones. + +To integrate decently with the rest of the app, this smartphone-RaceBoard should be implemented using the same technology stack - that is as part of the native app, if the native app is chosen (see Technical Considerations on this). +Instead of combining everything into one single screen, the suggested approach for the app would be to divide the functionality up into different screens, possibly leaving some functionality out. The main components _Leaderboard_ and _RaceMap_ might be separated into two tabs, between which one can easily switch, where the time slider remains fixed at the bottom for both these tabs. Further tabs could show charts for wind or competitors. + +The Leaderboard is a pretty complex table. It includes several levels of hierarchy, where the user can drill down into races and legs with his web-browser. This hierarchical structure is presented as expandable colums, so that e.g. expanding the column for one race results in several additional columns being displayed for each leg of that race. This is basically a drill-down operation, which might need to be implemented with some different interaction mechanism in the app. Perhaps standard interaction patterns exist for this type of functionality. Possibly, instead of expanding the entire column, by selecting a row (representing one competitor) further details on the races/legs for that competitor can be displayed on a different screen. + +!!! server + To enable other RaceBoard and Leaderboard clients than the GWT-RaceBoard, all necessary data has to be exposed not only through the GWT-RPC channel, but also e.g. through RESTlets. + + +### Managing Tracks and Races +An important part of the app surely also lies in managing the existing tracks and races generated from these tracks. On the one hand, the user may simply wish to see what tracks are on his phone (and what their synchronization status to the server is), and browser through his races. The list of races then serves as an entrypoint for analysis or sharing. + + + +## Technical Addendum +This addendum addresses more technical issues that seem obvious from the point in time of writing this documentation. More technical issues will surely arise during implementation, but the following sections try to cover the larger areas that can already be foreseen. + +As the use cases for the app were described above already, the following paragraphs rather focus on the technical considerations for the app, which functionality should also/only be available through a desktop computer, and changes to the backend. + + +### Technical Considerations for the App + +As we ultimately want to reach as many users as possible, we will want to target different smartphone platforms. With this in mind, the different possibilities for developing cross-platform apps should be considered. + +hybrid / container-based +: Cross-platform development options such as PhoneGap supposedly offer the advantage of only having to develop the app once, and use it on any supported platform. This may come at the price of reduced performance, and not having the native look-and-feel in all aspects, which users have come to expect for apps. A major topic to address here is whether the tracking, buffering and transmission part of the app can feasibly be implemented in such a hybrid solution. + +native +: While native apps offer full flexibility, they come at the cost of having to maintain multiple code bases, which are supposed to provide the same functionality, but for different platforms. If starting out with Android as the first supported platform, code reuse as for the RaceCommittee App would at least be possible. + + +### Smartphone vs. PC +So far all functionality for smartphone tracking has been described in such a way, that all steps from defining a race, tracking it (obviously, as the smartphone is the tracker), up to sharing and analyzing it are possible using the smartphone. At least for the last part, some users may want to deviate from this, and take advantage of the screen real estate and more advanced interaction capabilities offered by a computer to view the RaceBoard, if they spend some time to analyze the race. + +The question arises, which other steps should also be made accessible through a web interface. Possible steps include + +* defining a regatta, where multiple steps and quite some text entry is required +* inviting competitors, if e.g. they sent their user names per email +* defining the race course post-hoc, while being able to view the tracks in the background to see where the waypoints should go +* managing one's list of tracked races including sharing options (who may see what), and the ability to share races using the desktop computer + +Basically, this means duplicating functionality (maybe even for the third time: complex regatta management in the AdminConsole, simple wizard on the smartphone, intermediate one in the Smartphone-Tracking web interface) to allow user to perform difficult or more complex tasks at their computer. This does not have to be implemented immediately, at least we can wait to see for which steps users would like to see this possibility most. + + +### Backend Implementation Details + +#### Receiving and storing Tracking Data So far, the Sailing Analytics did not store any competitor or tracking data. Rather, only the regatta structure was persistently defined, and all other information was provided by the tracking provider. We have begun digressing from this concept in the interest of enabling smartphone tracking. - +user Along these lines, one of the major extensions is storing tracking data. GPS fixes can now be added to the so-called _GPSFixStore_ at any time. A _DeviceIdentifier_ is stored with every fix which identifies the device that generated the fix. An exemplary servlet exists that can process ``HTTP POST`` requests containing a JSON representation of the DeviceIdentifier and multiple GPSFixes. For the final app different alternatives with less protocol overhead should be explored. Options might include websockets or direct socket communication (possibly UDP datagrams in a fire-and forget style, which would require application level handling of lost data). !!! both @@ -49,7 +232,7 @@ On the smartphone, a separate service should gather the tracking data and transm !!! app Implement a tracking and sending service that can handle app crashes, restarts and network disconnects and guarantee that eventually all fixes are transmitted to the server. -### Domain Model surrounding a Race +#### Domain Model surrounding a Race As mentioned, the entire solution is currently focused at large events. For these, there is a well-defined regatta structure with a specific boat class, competitor lists, fleets, races, and scoring rules. This means, that every race is located at the very bottom of a hierarchy of domain objects (the following is simplified): * **Event:** such as _Kieler Woche 2014_ @@ -61,23 +244,14 @@ As mentioned, the entire solution is currently focused at large events. For thes This structure only models the race structure within a regatta, and can contain information on scoring rules, disqualifications and official result lists. The tracking data comes into play in the following fashion: a _TrackedRace_ is attached to the Race on the lowest level and serves as a sink for all tracking data (and wind data, start time information, mark passings, ...), providing the basis for the actual analytical calculations. Adding a Leaderboard for a Regatta then lets everything fall in place: the mentioned data tables and map-centric views apper in the web frontend. -
    - -

    Leaderboard

    -
    -
    - -

    RaceMap

    -
    - -Obviously the hierarchical domain model spanning multiple levels from Event to Race is important for sailing events, but probably isn't the best fit for the regular casual use-case. Rather, it will be the exception, and mostly users will want to track a single training session or race. Either a default structure is created every single time, or the model is relaxed further so that users can access a RaceMap without having to create the rest of the structure. (A further possibility is to use the _FlexibleLeaderboard_, which can hold arbitrary RaceColumns, and simply add a new RaceColumn with a default Fleet to a default FlexibleLeaderboard whenever a user does not care for the surrounding Regatta structure). +Obviously the hierarchical domain model spanning multiple levels from Event to Race is important for sailing events, but probably isn't the best fit for the regular casual use-case. Rather, it will be the exception, and mostly users will want to track a single training session or race. Either a default structure is created every single time, or the model is relaxed further so that users can access a RaceBoard without having to create the rest of the structure. (A further possibility is to use the _FlexibleLeaderboard_, which can hold arbitrary RaceColumns, and simply add a new RaceColumn with a default Fleet to a default FlexibleLeaderboard whenever a user does not care for the surrounding Regatta structure). !!! server - Allow casual users to create and analyze races without having to deal with Regattas and Leaderboards. Either generate default placeholders or actually relax the technical requirements for these structures. + Allow casual users to create and analyze races without having to deal with Regattas and Leaderboards. Either generate default placeholders or actually relax the technical requirements for these structures. Even though the FlexibleLeaderboards are a relict, maybe they can be put to good use whenever users wish to analyze individual training sessions and races lacking a surrounding regatta structure. -### Defining a Race -The _TrackedRace_ that serves as the sink for the actual tracking and other data has already been briefly introduced. To be able to create such a TrackedRace, which again is the preliminary requirement for displaying a RaceMap, the following information is necessary. +#### Defining a Race +The _TrackedRace_ that serves as the sink for the actual tracking and other data has already been briefly introduced. To be able to create such a TrackedRace, which again is the preliminary requirement for displaying a RaceBoard, the following information is necessary. participating competitors : A list of participating competitors. This is immutable, meaning it cannot be changed after the TrackedRace has been created. @@ -95,10 +269,12 @@ start time The necessary information has to be assembled in a ``RaceLog``. This is a log of race-related entries, ranging in content from the definition of a start-time, events signalling recalls during the start-procedure of the race, or adding a competitor to the list of registered participants. This is an actual log in the sense that nothing is deleted, but every change is inserted as an event (including events that can revert others). Once all necessary information is available, a TrackedRace can be created. After this point, most information can still be changed by adding further events to the RaceLog. +It may be desirable to create the TrackedRace early on, even if the competitor list is not yet final. One reason might be that the participants want to see the additional metrics which are calculated in such a TrackedRace while sailing, but one competitor was not yet invited because he forgot to make his user name known. For such cases, the TrackedRace could simply be recreated on a change to a data structure that is immutable, such as the competitor list for that race. + As the RaceLog was created to connect the TrackedRace to the _RaceCommittee App_, which is an Android app, JSON (de)serialization logic in Java and a servlet for adding these events already exist. The existing app shares parts of the domain model code and other functionality with the server application, which is possible since both are Java-based. -### Further Preconditions for a meaningful Analysis -The steps described this far where the hard requirements of what has to exist, before the user can view the tracking data in a RaceMap. To make the RaceMap useful, there are a few more preconditions. +#### Further Preconditions for a meaningful Analysis +Of the above steps for defining a race, both the competitor list, and a course (potentially empty, and can be changed later) actually have to be set, as otherwise the TrackedRace cannot be created. To actually make the RaceBoard useful, there are a few more preconditions. Mainly, there needs to be a sensible course definition. Most of the analysis is grouped around the concept of legs (where a leg is the part of the course between two ControlPoints). We already infer the mark passings (the times at which a boat passed a mark, depending on the type, e.g. single mark vs. line vs. gate), which provides the basis for analying the performance of a competitor on each such leg. @@ -116,59 +292,9 @@ To also infer some course there are still some open problems. This would likely !!! app Allow the user to set a start and finish time. -When these steps are implemented, a fully-automatic bootstrapping of all information that is necessary to allow users to analyze a race for which they provide only the competitor list, the tracking data and a start and end time is possible. In a further step, users should be enabled to manaully define the course layout beforehand (if they wish to get a live view of the race), or adapt or overwrite the automatically bootstrapped course after the race. Whether this functionality should be available only on the smartphone (allow managing everything from the smartphone), only from the desktop (graphical and functional overlap between RaceMap and course editing functionality) or both - and a sensible timeline for this implementation, needs to be discussed. +When these steps are implemented, a fully-automatic bootstrapping of all information that is necessary to allow users to analyze a race for which they provide only the competitor list, the tracking data and a start and end time is possible. In a further step, users should be enabled to manaully define the course layout beforehand (if they wish to get a live view of the race), or adapt or overwrite the automatically bootstrapped course after the race. Whether this functionality should be available only on the smartphone (allow managing everything from the smartphone), only from the desktop (graphical and functional overlap between RaceBoard and course editing functionality) or both - and a sensible timeline for this implementation, needs to be discussed. A prototype for a map-based course designer has already been implemented for the RaceCommittee App. The basic requirements include the definition and placement of marks, grouping them into a line or gate, and defining a sequence of these ControlPoints as a course. !!! both - Allow the user to enter a custom course prior to the creation of the TrackedRace. If the user did not do so, and instead a course was automatically bootstrapped, allow the user to either edit this course or overwrite it completely and define his own course. - - -## Use Cases -After having a look at what is currently implemented on the server side and mentioning technical issues and requirements, now the focus is shifted to use-cases. The workflows for tracking and analyzing races are presented from the user's viewpoint. These are ordered by their importance - the more important use-cases that should be supported first are presented first. These use-cases should not be seen as set in stone, and are open for discussion. - -### Individual Training -This can be considered as being the simplest use-case. After downloading the app, the sailor creates a user account (that serves both for login purposes, and to provide the necessary competitor data). Alternatively, he may log in to an existing account. - -
    - -On the following start screen there is a dominant button for starting the tracking - this functionality has to be readily available at all times, as missing out on tracking data is the potentially worst thing that can happen - everything else can be done later on. While tracking, a good visual feedback for the user probably is a map representation. Also, there should be some information on connectivity available, showing whether the tracking data is being successfully transmitted to the backend. - -After stopping the tracking, a summary of the data can be given, including the transmission status. For this simple scenario, selecting an _Analyze_ button sets up a race with this competitor and all necessary data automatically, so that the user can proceed to directly analyze his training session in the RaceMap. - -!!! server - Clarify how user and competitor management play together. Are they entirely separate, and a user can create different competitors for his account, and switch between them, or is a user the same thing as a competitor? - -!!! app - As already mentioned, tracking, storing and transmitting the fixes should be handled in a crash-safe background process, with the goal of not loosing any tracking data. Switching to a different app, or moving away from the tracking screen within the app (e.g. to start defining the race) should not influence the tracking - it should only be stopped when explicitly requested. - -### Race -Multiple competitors want to compete in a race and let their friends on shore get a real-time impression of what is happening on the water. One of them defines the race on his smartphone, for which he defines the basic data and then invites his friends to join through the app. On the water, they lay out the marks for the course, and while laying out each buoy they ping their location at that moment. From these pinged buoys they can define the course layout. - -After entering a start time, everything happens automatically. A countdown commences on all of the smartphones - all have been automatically informed of the start time. The app automatically begins tracking a few minutes before the start of the race, and stops once the boat has crossed the finish line. In the meantime, their friends on shore see a live RaceMap view of the sailboats. - -In case of a bad mobile network connection, the race can still be defined on one of the smartphones, and the according data is buffered on that phone until the connectivity is restored. In this case, the other competitors have to manually start and stop tracking, and can accept the invitation only once they themselves regain network connectivity. - -After having a look at the race in the evening, they notice that they apparently pinged the marks of the windward gate in the wrong position, and decide adjust the course through the app. - -!!! server - This use-case gives some insight into the further details of user management. An authorization mechanism will have to be put into place, that encompasses the following data: Event-Race structure, RaceLog and tracking data. An initial approach could be to allow editing of such structures only by the user that created them, and grant explicit read permissions to other users. The other participants in the race can grant read access to their tracking data for the duration of the race, and the creator of the race in turn grants read access to his TrackedRace (and the RaceLog, and implicitly the RaceMap). We then have to start enforcing these access rights, which might be possible on multiple levels. This might be done on a servlet level by adding security constraints to the ``web.xml``s, or checked within the application (or perhaps both). Also, one would have to go about securing the GWT-RPC communicatin channel. - -!!! both - The app in turn must support managing these rights - in part this may be implicit (accepting an invitation = granting read access to tracking data), but for other parts an explicit interface might be necessary (sharing a race with other users). The topic may be extended to managing groups, and group-based access permissions (e.g. for sailing clubs). - -### Regatta -If the solution becomes nice enough to use, sailing clubs will possibly want to use the Sailing Analytics for their own regattas in the same fashion they see them used at larger events. In this case, the functionality for defining the Event-Race and Leaderboard structure currently only accessible through the admin console would have to be exposed through the app. - -!!! both - The management of events, associated regattas, series, fleets, races and leaderboards should probably best be exposed through a RESTful API. When this point is reached, it makes sense to discuss if every possible detail must and should be exposed, or if a subset may be enough to cover most cases, and make it as easy as possible for users. - - -## Technical Considerations for the App -As we ultimately want to reach as many users as possible, we will want to target different smartphone platforms. With this in mind, the different possibilities for developing cross-platform apps should be considered. - -hybrid / container-based -: Cross-platform development options such as PhoneGap supposedly offer the advantage of only having to develop the app once, and use it on any supported platform. This may come at the price of reduced performance, and not having the native look-and-feel in all aspects, which users have come to expect for apps. A major topic to address here is whether the tracking, buffering and transmission part of the app can feasibly be implemented in such a hybrid solution. - -native -: While native apps offer full flexibility, they come at the cost of having to maintain multiple code bases, which are supposed to provide the same functionality, but for different platforms. If starting out with Android as the first supported platform, code reuse as for the RaceCommittee App would at least be possible. \ No newline at end of file + Allow the user to enter a custom course prior to the creation of the TrackedRace. If the user did not do so, and instead a course was automatically bootstrapped, allow the user to either edit this course or overwrite it completely and define his own course. \ No newline at end of file diff --git a/wiki/uploading-media-content.md b/wiki/uploading-media-content.md new file mode 100644 index 00000000000..036150c5609 --- /dev/null +++ b/wiki/uploading-media-content.md @@ -0,0 +1,31 @@ +# Uploading Media Content + +We have a place to store media content such as images and videos, e.g., created at sailing events. This place is the directory `/var/www/static` on our web server, and it is publicly reachable by HTTP at `http://static.sapsailing.com/...`. For example, the file `/var/www/static/images/splash-screen-compressed-02.png` is reachable via the URL `http://static.sapsailing.com/images/splash-screen-compressed-02.png`. + +Uploading content to this directory works best using the "secure copy" (SCP) protocol, sometimes also referred to as SFTP. It is basically sending files through a secure shell (SSH) connection. Command line aficionados will know the "scp" command and its options and will understand what it means to have a public/private key pair that allows them to log on to the `sapsailing.com` web server. + +For those who don't, we can easily create username/password accounts on the web server which spares the technical tidbits of public/private key pair creation and registration. A good graphical tool (for the less command line-oriented folks) is WinSCP which can be downloaded at [http://winscp.net](http://winscp.net/eng/download.php#download2). + +For those in an open Internet environment (outside the SAP VPN or logged on to the "SAP Internet" WiFi) the hostname to use is `sapsailing.com` and the port number is `22`. Inside the SAP VPN we have to use a little trick to make the connection. Use `10.18.22.50` as the hostname and `12349` as the port number. + +## Quick Introduction to the Use of WinSCP + +After downloading WinSCP from the URL shown above, run the installer. When it asks you about your preferred user interface style you may want to choose "Explorer" which may be most familiar for Windows users. + +![WinSCP Install](/wiki/images/winscp-install-1.png) + +Then, launch WinSCP. It welcomes you with a login screen: + +![WinSCP Login](/wiki/images/winscp-login.png) + +Enter the hostname and port number as described above, depending on your network environment (within SAP network or outside of SAP network). Add your user name and password, then click the "Login" button. You should then see an Explorer-like window that shows the web server's file system hierarchy. + +Navigate to /var/www/static: + +![WinSCP /var/www/static](/wiki/images/winscp-var-www-static.png) + +You can create new folders using the "Create Folder" button from the toolbar: + +![WinSCP Create Folder](/wiki/images/winscp-create-folder.png) + +Files can be uploaded from your local disk by dragging and dropping them from a local Windows Explorer running on your desktop onto the content panel in the right part of the WinSCP window.![WinSCP Intall](/wiki/images/winscp-install-1.png). \ No newline at end of file