From 212f40606f395ac77a9ea88095c547429465b495 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sun, 22 Jun 2014 09:22:51 +0200 Subject: [PATCH 001/323] simplified while-loop in Replicator --- .../server/replication/impl/ReplicationServiceImpl.java | 4 ++++ .../sap/sailing/server/replication/impl/Replicator.java | 7 +------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java index f38272ec148..ba3aac83715 100644 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java @@ -283,6 +283,10 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec } } + /** + * {@link #broadcastOperation(RacingEventServiceOperation) Broadcasts} the operation to all registered + * replicas by publishing it to the fan-out exchange. + */ @Override public void executed(RacingEventServiceOperation operation) { try { diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java index 3d86f9f5f0c..d31f7bae874 100755 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java @@ -65,7 +65,6 @@ public class Replicator implements Runnable { * them directly. * @param consumer * the RabbitMQ consumer from which to load messages - * @param baseDomainFactory TODO */ public Replicator(ReplicationMasterDescriptor master, HasRacingEventService racingEventServiceTracker, boolean startSuspended, QueueingConsumer consumer, DomainFactory baseDomainFactory) { this.queue = new ArrayList>(); @@ -83,10 +82,7 @@ public class Replicator implements Runnable { @Override public void run() { ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); - while (true) { - if (isBeingStopped()) { - break; - } + while (!isBeingStopped()) { try { Delivery delivery = consumer.nextDelivery(); byte[] bytesFromMessage = delivery.getBody(); @@ -152,7 +148,6 @@ public class Replicator implements Runnable { Thread.currentThread().setContextClassLoader(oldClassLoader); } } - logger.info("Stopped replicator thread. This server will no longer receive events from a master."); } From 881cb39e9d45c9784de28063390ee3fce1cd4be9 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sun, 22 Jun 2014 09:37:25 +0200 Subject: [PATCH 002/323] prepared the Replicator and ReplicationServiceImpl for replicating groups of operations in one sweep --- .../impl/ReplicationServiceImpl.java | 16 ++++++++++--- .../server/replication/impl/Replicator.java | 24 ++++++++++++------- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java index ba3aac83715..75dce1aa12a 100644 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java @@ -8,7 +8,9 @@ import java.io.ObjectOutputStream; import java.net.ConnectException; import java.net.URL; import java.net.URLConnection; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.UUID; import java.util.logging.Level; @@ -181,15 +183,23 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec getRacingEventService().removeOperationExecutionListener(this); } - private void broadcastOperation(RacingEventServiceOperation operation) throws Exception { + private void broadcastOperation(RacingEventServiceOperation operation) throws IOException { + List> operationsList = new ArrayList>(); + operationsList.add(operation); + broadcastOperations(operationsList); + } + + private void broadcastOperations(Iterable> operationsList) throws IOException { // serialize operation into message ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); - oos.writeObject(operation); + oos.writeObject(operationsList); oos.close(); if (masterChannel != null) { masterChannel.basicPublish(exchangeName, /* routingKey */"", /* properties */null, bos.toByteArray()); - replicationInstancesManager.log(operation); + for (RacingEventServiceOperation operation : operationsList) { + replicationInstancesManager.log(operation); + } } } diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java index d31f7bae874..afafa65bdbd 100755 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java @@ -75,9 +75,12 @@ public class Replicator implements Runnable { } /** - * Starts fetching messages from the {@link #consumer}. After receiving a single message, assumes it's a serialized - * {@link RacingEventServiceOperation}, and applies it to the {@link RacingEventService} which is obtained from the - * service tracker passed to this replicator at construction time. + * Starts fetching messages from the {@link #consumer}. After receiving a single message, assumes it's an + * {@link Iterable} of serialized {@link RacingEventServiceOperation} objects, and applies it to the + * {@link RacingEventService} which is obtained from the service tracker passed to this replicator at construction + * time. + * + * @see ReplicationServiceImpl#executed(RacingEventServiceOperation) */ @Override public void run() { @@ -92,8 +95,9 @@ public class Replicator implements Runnable { Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); ObjectInputStream ois = racingEventServiceTracker.getRacingEventService().getBaseDomainFactory() .createObjectInputStreamResolvingAgainstThisFactory(new ByteArrayInputStream(bytesFromMessage)); - RacingEventServiceOperation operation = (RacingEventServiceOperation) ois.readObject(); - applyOrQueue(operation); + @SuppressWarnings("unchecked") + Iterable> operations = (Iterable>) ois.readObject(); + applyOrQueue(operations); } catch (ConsumerCancelledException cce) { logger.info("Consumer has been shut down properly."); break; @@ -159,11 +163,15 @@ public class Replicator implements Runnable { * If the replicator is currently {@link #suspended}, the operation is queued, otherwise immediately applied to * the receiving replica. */ - private synchronized void applyOrQueue(RacingEventServiceOperation operation) { + private synchronized void applyOrQueue(Iterable> operations) { if (suspended) { - queue(operation); + for (RacingEventServiceOperation operation : operations) { + queue(operation); + } } else { - apply(operation); + for (RacingEventServiceOperation operation : operations) { + apply(operation); + } } } From 2d07c65d373a15a006d08b46ee6a4a8f80208ffc Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sun, 22 Jun 2014 10:06:54 +0200 Subject: [PATCH 003/323] avoid NPE in RaceMap in case a GPSFixDTO doesn't have an estimated speed/bearing --- .../gwt/ui/client/shared/racemap/RaceMap.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) 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 0421993e833..58ff6a7a49f 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 @@ -1486,9 +1486,20 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe double factorForBefore = 1-factorForAfter; PositionDTO betweenPosition = new PositionDTO(factorForBefore*fixBefore.position.latDeg + factorForAfter*fixAfter.position.latDeg, factorForBefore*fixBefore.position.lngDeg + factorForAfter*fixAfter.position.lngDeg); - double betweenBearing = new ScalableBearing(new DegreeBearingImpl(fixBefore.speedWithBearing.bearingInDegrees)). - multiply(factorForBefore).add(new ScalableBearing(new DegreeBearingImpl(fixAfter.speedWithBearing.bearingInDegrees)). - multiply(factorForAfter)).divide(1).getDegrees(); + final double betweenBearing; + if (fixBefore.speedWithBearing == null) { + if (fixAfter.speedWithBearing == null) { + betweenBearing = 0; + } else { + betweenBearing = fixAfter.speedWithBearing.bearingInDegrees; + } + } else if (fixAfter.speedWithBearing == null) { + betweenBearing = fixBefore.speedWithBearing.bearingInDegrees; + } else { + betweenBearing = new ScalableBearing(new DegreeBearingImpl(fixBefore.speedWithBearing.bearingInDegrees)). + multiply(factorForBefore).add(new ScalableBearing(new DegreeBearingImpl(fixAfter.speedWithBearing.bearingInDegrees)). + multiply(factorForAfter)).divide(1).getDegrees(); + } SpeedWithBearingDTO betweenSpeed = new SpeedWithBearingDTO( factorForBefore*fixBefore.speedWithBearing.speedInKnots + factorForAfter*fixAfter.speedWithBearing.speedInKnots, betweenBearing); From 6f2a77e9da7115a7f1eaf6b0a8456ae59f395fc3 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sun, 22 Jun 2014 11:06:27 +0200 Subject: [PATCH 004/323] log operations per messages average --- ...nstancesManagerLoggingPerformanceTest.java | 14 +- .../impl/ReplicationInstancesManager.java | 64 +++++++-- .../impl/ReplicationServiceImpl.java | 126 +++++++++++++----- 3 files changed, 164 insertions(+), 40 deletions(-) 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 dabf92a4cd7..27671a96376 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 @@ -5,11 +5,13 @@ import static org.junit.Assert.assertEquals; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Arrays; +import java.util.Collections; import java.util.UUID; import org.junit.Before; import org.junit.Test; +import com.sap.sailing.server.RacingEventServiceOperation; import com.sap.sailing.server.operationaltransformation.CreateLeaderboardGroup; import com.sap.sailing.server.replication.impl.ReplicaDescriptor; import com.sap.sailing.server.replication.impl.ReplicationInstancesManager; @@ -32,9 +34,19 @@ public class ReplicationInstancesManagerLoggingPerformanceTest { public void testLoggingPerformance() { final int count = 10000000; for (int i=0; i>singletonList(operation)); } assertEquals(count, replicationInstanceManager.getStatistics(replica).get(operation.getClass()).intValue()); + assertEquals(1.0, replicationInstanceManager.getAverageNumberOfOperationsPerMessage(replica), 0.0000001); } + @Test + public void testLoggingAverages() { + final int count = 1000; + for (int i=0; i[] { operation, operation })); + } + assertEquals(2*count, replicationInstanceManager.getStatistics(replica).get(operation.getClass()).intValue()); + assertEquals(2.0, replicationInstanceManager.getAverageNumberOfOperationsPerMessage(replica), 0.0000001); + } } diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationInstancesManager.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationInstancesManager.java index 7ce3e343df6..248762bbd09 100644 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationInstancesManager.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationInstancesManager.java @@ -8,6 +8,7 @@ import java.util.Set; import com.sap.sailing.server.RacingEventServiceOperation; import com.sap.sailing.server.replication.ReplicationMasterDescriptor; +import com.sap.sse.common.Util; public class ReplicationInstancesManager { @@ -19,6 +20,20 @@ public class ReplicationInstancesManager { private Map>, Integer>> replicationCounts; + /** + * Used to calculate the average number of operations per message sent/received for each replica. + * + * @see #totalMessageCount + */ + private final Map totalNumberOfOperations; + + /** + * Used to calculate the average number of operations per message sent/received for each replica. + * + * @see #totalNumberOfOperations + */ + private final Map totalMessageCount; + /** * The descriptor of the replication master */ @@ -27,6 +42,8 @@ public class ReplicationInstancesManager { public ReplicationInstancesManager() { replicaDescriptors = new HashSet(); replicationCounts = new HashMap>,Integer>>(); + totalMessageCount = new HashMap<>(); + totalNumberOfOperations = new HashMap<>(); } /** @@ -63,28 +80,57 @@ public class ReplicationInstancesManager { * * @see #getStatistics */ - public void log(RacingEventServiceOperation replicatedOperation) { + public void log(Iterable> replicatedOperations) { for (ReplicaDescriptor replica : getReplicaDescriptors()) { Map>, Integer> counts = replicationCounts.get(replica); if (counts == null) { counts = new HashMap>, Integer>(); replicationCounts.put(replica, counts); } - @SuppressWarnings("unchecked") // safe because replicatedOperation is declared of type RacingEventserviceOperation - Class> operationClass = (Class>) replicatedOperation - .getClass(); - Integer count = counts.get(operationClass); - if (count == null) { - count = 0; + for (RacingEventServiceOperation replicatedOperation : replicatedOperations) { + @SuppressWarnings("unchecked") + // safe because replicatedOperation is declared of type RacingEventserviceOperation + Class> operationClass = + (Class>) replicatedOperation.getClass(); + Integer count = counts.get(operationClass); + if (count == null) { + count = 0; + } + counts.put(operationClass, ++count); } - count++; - counts.put(operationClass, count); + Long totalOps = totalNumberOfOperations.get(replica); + if (totalOps == null) { + totalOps = 0l; + } + totalOps += Util.size(replicatedOperations); + totalNumberOfOperations.put(replica, totalOps); + Long totalMessages = totalMessageCount.get(replica); + if (totalMessages == null) { + totalMessages = 0l; + } + totalMessageCount.put(replica, ++totalMessages); } } public Map>, Integer> getStatistics(ReplicaDescriptor replica) { return replicationCounts.get(replica); } + + public double getAverageNumberOfOperationsPerMessage(ReplicaDescriptor replica) { + final double result; + Long messageCount = totalMessageCount.get(replica); + if (messageCount != null) { + Long operationsCount = totalNumberOfOperations.get(replica); + if (operationsCount == null) { + result = 0; + } else { + result = ((double) operationsCount)/(double) messageCount; + } + } else { + result = 0; + } + return result; + } public void removeAll() { replicationCounts.clear(); diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java index 75dce1aa12a..296edc17391 100644 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java @@ -12,6 +12,8 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; @@ -50,7 +52,7 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec private final ReplicationInstancesManager replicationInstancesManager; - private ServiceTracker racingEventServiceTracker; + private final ServiceTracker racingEventServiceTracker; private final RacingEventService localService; @@ -84,39 +86,82 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec private Replicator replicator; private Thread replicatorThread; - public ReplicationServiceImpl(String exchangeName, String exchangeHost, final ReplicationInstancesManager replicationInstancesManager) throws IOException { - this.replicationInstancesManager = replicationInstancesManager; - replicaUUIDs = new HashMap(); - racingEventServiceTracker = getRacingEventServiceTracker(); - racingEventServiceTracker.open(); - localService = null; - this.exchangeName = exchangeName; - this.exchangeHost = exchangeHost; - replicator = null; - serverUUID = UUID.randomUUID(); - logger.info("Setting " + serverUUID.toString() + " as unique replication identifier."); - } - - protected ServiceTracker getRacingEventServiceTracker() { - return new ServiceTracker( - Activator.getDefaultContext(), RacingEventService.class.getName(), null); - } + /** + * Sending operations as serialized Java objects using binary RabbitMQ messages comes at an overhead. To reduce the overhead, + * several operations can be serialized into a single message. The actual serialization of the buffer happens after a short duration + * has passed since the last sending, managed by a {@link Timer}. Writers need to synchronize on this buffer. This includes the + * addition of an operation to the buffer as well as the atomic sending and clearing. + */ + private final List> outboundBuffer; /** - * Like {@link #ReplicationServiceImpl(String, ReplicationInstancesManager)}, only that instead of using - * an OSGi service tracker to discover the {@link RacingEventService}, the service to replicate is "injected" here. - * @param exchangeName the name of the exchange to which replicas can bind + * Used to schedule the sending of all operations in {@link #outboundBuffer} using the {@link #sendingTask}. */ - public ReplicationServiceImpl(String exchangeName, String exchangeHost, - final ReplicationInstancesManager replicationInstancesManager, RacingEventService localService) throws IOException { + private final Timer timer; + + /** + * Sends all operations in {@link #outboundBuffer}. When holding the monitor of {@link #outboundBuffer}, + * the following rules hold: + * + *
    + *
  • if null, adding an operation to {@link #outboundBuffer} needs to create and assign a new timer that + * schedules a sending task. + *
  • + *
  • if not null, an operation added to {@link #outboundBuffer} is guaranteed to be sent by the timer + *
  • + *
+ * + */ + private TimerTask sendingTask; + + /** + * Defines for how many milliseconds the {@link #timer} will wait since the first operation has been added to an empty + * {@link #outboundBuffer} until it carries out the actual transmission task. The longer this duration, the more operations + * are likely to be sent per message transmitted, reducing overhead but correspondingly increasing latency. + */ + private final long TRANSMISSION_DELAY_MILLIS = 100; + + public ReplicationServiceImpl(String exchangeName, String exchangeHost, final ReplicationInstancesManager replicationInstancesManager) throws IOException { + this(exchangeName, exchangeHost, replicationInstancesManager, /* localService */ null, /* create RacingEventServiceTracker */ true); + } + + private ReplicationServiceImpl(String exchangeName, String exchangeHost, + final ReplicationInstancesManager replicationInstancesManager, RacingEventService localService, + boolean createRacingEventServiceTracker) throws IOException { + timer = new Timer("ReplicationServiceImpl timer for delayed task sending"); this.replicationInstancesManager = replicationInstancesManager; - replicaUUIDs = new HashMap(); // XXX why is this a map? there should be only one connection to a master + replicaUUIDs = new HashMap(); + if (createRacingEventServiceTracker) { + racingEventServiceTracker = getRacingEventServiceTracker(); + racingEventServiceTracker.open(); + } else { + racingEventServiceTracker = null; + } this.localService = localService; this.exchangeName = exchangeName; this.exchangeHost = exchangeHost; replicator = null; serverUUID = UUID.randomUUID(); + outboundBuffer = new ArrayList<>(); logger.info("Setting " + serverUUID.toString() + " as unique replication identifier."); + + } + + /** + * Like {@link #ReplicationServiceImpl(String, ReplicationInstancesManager)}, only that instead of using an OSGi + * service tracker to discover the {@link RacingEventService}, the service to replicate is "injected" here. + * + * @param exchangeName + * the name of the exchange to which replicas can bind + */ + public ReplicationServiceImpl(String exchangeName, String exchangeHost, + final ReplicationInstancesManager replicationInstancesManager, RacingEventService localService) throws IOException { + this(exchangeName, exchangeHost, replicationInstancesManager, localService, /* create RacingEventServiceTracker */ false); + } + + protected ServiceTracker getRacingEventServiceTracker() { + return new ServiceTracker( + Activator.getDefaultContext(), RacingEventService.class.getName(), null); } private Channel createMasterChannel(String exchangeName, String exchangeHost) throws IOException { @@ -183,10 +228,33 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec getRacingEventService().removeOperationExecutionListener(this); } + /** + * Schedules a single operation for broadcast. The operation is added to {@link #outboundBuffer}, and if not already scheduled, + * a {@link #timer} is created and scheduled to send in {@link #TRANSMISSION_DELAY_MILLIS} milliseconds. + */ private void broadcastOperation(RacingEventServiceOperation operation) throws IOException { - List> operationsList = new ArrayList>(); - operationsList.add(operation); - broadcastOperations(operationsList); + synchronized (outboundBuffer) { + outboundBuffer.add(operation); + if (sendingTask == null) { + sendingTask = new TimerTask() { + @Override + public void run() { + final Iterable> listToSend; + synchronized (outboundBuffer) { + listToSend = new ArrayList<>(outboundBuffer); + outboundBuffer.clear(); + sendingTask = null; + } + try { + broadcastOperations(listToSend); + } catch (IOException e) { + logger.log(Level.SEVERE, "Error trying to replicate the following operations: "+listToSend, e); + } + } + }; + timer.schedule(sendingTask, TRANSMISSION_DELAY_MILLIS); + } + } } private void broadcastOperations(Iterable> operationsList) throws IOException { @@ -197,9 +265,7 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec oos.close(); if (masterChannel != null) { masterChannel.basicPublish(exchangeName, /* routingKey */"", /* properties */null, bos.toByteArray()); - for (RacingEventServiceOperation operation : operationsList) { - replicationInstancesManager.log(operation); - } + replicationInstancesManager.log(operationsList); } } From 1b7a4e4891fa5f5b7c2e150eb0ccef2f81f904c6 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sun, 22 Jun 2014 11:54:51 +0200 Subject: [PATCH 005/323] started working on bundling operations to replicate; still buggy as now the operations are serialized later, upon sending, and objects may have changed until then --- .../sailing/gwt/ui/adminconsole/RegattaListComposite.java | 4 +++- .../sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java | 4 ++++ .../java/com/sap/sailing/gwt/ui/client/StringMessages.java | 1 + .../sap/sailing/gwt/ui/client/StringMessages.properties | 3 ++- .../sap/sailing/gwt/ui/client/StringMessages_de.properties | 3 ++- .../sap/sailing/gwt/ui/client/StringMessages_ru.properties | 3 ++- .../com/sap/sailing/gwt/ui/server/SailingServiceImpl.java | 5 +++-- .../java/com/sap/sailing/gwt/ui/shared/ReplicaDTO.java | 7 ++++++- .../sap/sailing/server/replication/ReplicationService.java | 2 ++ .../server/replication/impl/ReplicationServiceImpl.java | 5 +++++ 10 files changed, 30 insertions(+), 7 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaListComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaListComposite.java index 2d32c597eec..ef4c25924f0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaListComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaListComposite.java @@ -96,7 +96,9 @@ public class RegattaListComposite extends Composite implements RegattasDisplayer public Iterable getSearchableStrings(RegattaDTO t) { List string = new ArrayList(); string.add(t.getName()); - string.add(t.boatClass.getName()); + if (t.boatClass != null) { + string.add(t.boatClass.getName()); + } return string; } }; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java index 00226b4eb71..f4f44fcf2fb 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java @@ -216,6 +216,10 @@ public class ReplicationPanel extends FlowPanel { }); registeredReplicas.setWidget(i, 2, removeReplicaButton); i++; + registeredReplicas.insertRow(i); + registeredReplicas.setWidget(i, 1, new Label(stringMessages.averageNumberOfOperationsPerMessage())); + registeredReplicas.setWidget(i, 2, new Label(""+replica.getAverageNumberOfOperationsPerMessage())); + i++; for (Map.Entry e : replica.getOperationCountByOperationClassName().entrySet()) { registeredReplicas.insertRow(i); registeredReplicas.setWidget(i, 1, new Label(e.getKey())); 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 48e054d91be..06a978eb951 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 @@ -1047,4 +1047,5 @@ public interface StringMessages extends Messages { String showNumberOfRacesScored(); String racesScored(); String racesScoredTooltip(); + String averageNumberOfOperationsPerMessage(); } 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 fa93e3cf9e9..a639cbd3498 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 @@ -1032,4 +1032,5 @@ showPingMarkMappings=Show Mappings for pinged Marks regattaNameMustNotContainSlashes=Regatta name must not contain slashes showNumberOfRacesScored=Number of races scored racesScored=Races scored -racesScoredTooltip=Number of races the competitor has completed or gotten a score for in this leaderboard. \ No newline at end of file +racesScoredTooltip=Number of races the competitor has completed or gotten a score for in this leaderboard. +averageNumberOfOperationsPerMessage=Average number of operations per message \ 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 bb61930cd64..4818ffbd7e2 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 @@ -1026,4 +1026,5 @@ showPingMarkMappings=Mappings für gepingte Marks zeigen regattaNameMustNotContainSlashes=Regattaname darf keine Schrägstriche enthalten showNumberOfRacesScored=Anzahl bepunkteter Rennen racesScored=Bepunktete Rennen -racesScoredTooltip=Anzahl von Rennen, die der Segler vollendet oder für die er Punkte bekommen hat \ No newline at end of file +racesScoredTooltip=Anzahl von Rennen, die der Segler vollendet oder für die er Punkte bekommen hat +averageNumberOfOperationsPerMessage=Durchschnittliche Anzahl Operationen pro Nachricht \ 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_ru.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties index 7db8a9032a2..127bfdec419 100644 --- 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 @@ -985,4 +985,5 @@ showPingMarkMappings=Show Mappings for pinged Marks showNumberOfRacesScored=Показать количество завершенных гонок racesScored=Завершенные гонки racesScoredTooltip=Количество гонок завершенное участником в данной таблице лидеров -regattaNameMustNotContainSlashes=Regatta name must not contain slashes \ No newline at end of file +regattaNameMustNotContainSlashes=Regatta name must not contain slashes +averageNumberOfOperationsPerMessage=Average number of operations per message \ No newline at end of file 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 a48b65e4c5e..312a56de57e 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 @@ -2881,8 +2881,9 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S for (Map.Entry>, Integer> e : statistics.entrySet()) { replicationCountByOperationClassName.put(e.getKey().getName(), e.getValue()); } - replicaDTOs.add(new ReplicaDTO(replicaDescriptor.getIpAddress().getHostName(), replicaDescriptor.getRegistrationTime().asDate(), replicaDescriptor.getUuid().toString(), - replicationCountByOperationClassName)); + replicaDTOs.add(new ReplicaDTO(replicaDescriptor.getIpAddress().getHostName(), replicaDescriptor + .getRegistrationTime().asDate(), replicaDescriptor.getUuid().toString(), + replicationCountByOperationClassName, service.getAverageNumberOfOperationsPerMessage(replicaDescriptor))); } ReplicationMasterDTO master; ReplicationMasterDescriptor replicatingFromMaster = service.isReplicatingFromMaster(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ReplicaDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ReplicaDTO.java index 3528961b5df..2cf302efc2e 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ReplicaDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ReplicaDTO.java @@ -10,13 +10,15 @@ public class ReplicaDTO implements IsSerializable { private String identifier; private Date registrationTime; private Map operationCountByOperationClassName; + private double averageNumberOfOperationsPerMessage; ReplicaDTO() {} public ReplicaDTO(String hostname, Date registrationTime, - String identifier, Map operationCountByOperationClassName) { + String identifier, Map operationCountByOperationClassName, double averageNumberOfOperationsPerMessage) { this.hostname = hostname; this.identifier = identifier; this.registrationTime = registrationTime; this.operationCountByOperationClassName = operationCountByOperationClassName; + this.averageNumberOfOperationsPerMessage = averageNumberOfOperationsPerMessage; } public String getHostname() { return hostname; @@ -27,6 +29,9 @@ public class ReplicaDTO implements IsSerializable { public Map getOperationCountByOperationClassName() { return operationCountByOperationClassName; } + public double getAverageNumberOfOperationsPerMessage() { + return averageNumberOfOperationsPerMessage; + } public String getIdentifier() { return identifier; } diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/ReplicationService.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/ReplicationService.java index 35230083a75..74992a0dfa3 100644 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/ReplicationService.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/ReplicationService.java @@ -43,6 +43,8 @@ public interface ReplicationService { * replica by type, where the operation type is the key, represented as the operation's class name */ Map>, Integer> getStatistics(ReplicaDescriptor replicaDescriptor); + + double getAverageNumberOfOperationsPerMessage(ReplicaDescriptor replicaDescriptor); /** * Stops the currently running replication. As there can be only one replication running diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java index 296edc17391..1983ba895ba 100644 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java @@ -380,6 +380,11 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec public Map>, Integer> getStatistics(ReplicaDescriptor replicaDescriptor) { return replicationInstancesManager.getStatistics(replicaDescriptor); } + + @Override + public double getAverageNumberOfOperationsPerMessage(ReplicaDescriptor replicaDescriptor) { + return replicationInstancesManager.getAverageNumberOfOperationsPerMessage(replicaDescriptor); + } @Override public void stopToReplicateFromMaster() throws IOException { From 0a2c7fd42286daeb4871ead3d72bdb4bbda70977 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sun, 22 Jun 2014 12:49:48 +0200 Subject: [PATCH 006/323] serialize the operations when they are handed to replication service for broadcast; still group many byte[] into one message by delayed sending --- ...nstancesManagerLoggingPerformanceTest.java | 5 ++- .../impl/ReplicationInstancesManager.java | 12 +++---- .../impl/ReplicationServiceImpl.java | 32 +++++++++++++------ .../server/replication/impl/Replicator.java | 20 ++++++------ 4 files changed, 41 insertions(+), 28 deletions(-) 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 27671a96376..e6853066917 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 @@ -11,7 +11,6 @@ import java.util.UUID; import org.junit.Before; import org.junit.Test; -import com.sap.sailing.server.RacingEventServiceOperation; import com.sap.sailing.server.operationaltransformation.CreateLeaderboardGroup; import com.sap.sailing.server.replication.impl.ReplicaDescriptor; import com.sap.sailing.server.replication.impl.ReplicationInstancesManager; @@ -34,7 +33,7 @@ public class ReplicationInstancesManagerLoggingPerformanceTest { public void testLoggingPerformance() { final int count = 10000000; for (int i=0; i>singletonList(operation)); + replicationInstanceManager.log(Collections.>singletonList(operation.getClass())); } assertEquals(count, replicationInstanceManager.getStatistics(replica).get(operation.getClass()).intValue()); assertEquals(1.0, replicationInstanceManager.getAverageNumberOfOperationsPerMessage(replica), 0.0000001); @@ -44,7 +43,7 @@ public class ReplicationInstancesManagerLoggingPerformanceTest { public void testLoggingAverages() { final int count = 1000; for (int i=0; i[] { operation, operation })); + replicationInstanceManager.log(Arrays.asList(new Class[] { operation.getClass(), operation.getClass() })); } assertEquals(2*count, replicationInstanceManager.getStatistics(replica).get(operation.getClass()).intValue()); assertEquals(2.0, replicationInstanceManager.getAverageNumberOfOperationsPerMessage(replica), 0.0000001); diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationInstancesManager.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationInstancesManager.java index 248762bbd09..baf87b5564e 100644 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationInstancesManager.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationInstancesManager.java @@ -3,6 +3,7 @@ package com.sap.sailing.server.replication.impl; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -80,18 +81,17 @@ public class ReplicationInstancesManager { * * @see #getStatistics */ - public void log(Iterable> replicatedOperations) { + public void log(List> classes) { for (ReplicaDescriptor replica : getReplicaDescriptors()) { Map>, Integer> counts = replicationCounts.get(replica); if (counts == null) { counts = new HashMap>, Integer>(); replicationCounts.put(replica, counts); } - for (RacingEventServiceOperation replicatedOperation : replicatedOperations) { - @SuppressWarnings("unchecked") + for (Class replicatedOperationClass : classes) { // safe because replicatedOperation is declared of type RacingEventserviceOperation - Class> operationClass = - (Class>) replicatedOperation.getClass(); + @SuppressWarnings("unchecked") + Class> operationClass = (Class>) replicatedOperationClass; Integer count = counts.get(operationClass); if (count == null) { count = 0; @@ -102,7 +102,7 @@ public class ReplicationInstancesManager { if (totalOps == null) { totalOps = 0l; } - totalOps += Util.size(replicatedOperations); + totalOps += Util.size(classes); totalNumberOfOperations.put(replica, totalOps); Long totalMessages = totalMessageCount.get(replica); if (totalMessages == null) { diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java index 1983ba895ba..457e1d25ab3 100644 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java @@ -31,6 +31,7 @@ import com.sap.sailing.server.RacingEventServiceOperation; import com.sap.sailing.server.replication.ReplicationMasterDescriptor; import com.sap.sailing.server.replication.ReplicationService; import com.sap.sailing.util.BuildVersion; +import com.sap.sse.common.Util.Pair; /** * Can observe a {@link RacingEventService} for the operations it performs that require replication. Only observes as @@ -92,7 +93,7 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec * has passed since the last sending, managed by a {@link Timer}. Writers need to synchronize on this buffer. This includes the * addition of an operation to the buffer as well as the atomic sending and clearing. */ - private final List> outboundBuffer; + private final List, byte[]>> outboundBuffer; /** * Used to schedule the sending of all operations in {@link #outboundBuffer} using the {@link #sendingTask}. @@ -233,13 +234,18 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec * a {@link #timer} is created and scheduled to send in {@link #TRANSMISSION_DELAY_MILLIS} milliseconds. */ private void broadcastOperation(RacingEventServiceOperation operation) throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(operation); + oos.close(); + final byte[] bytes = bos.toByteArray(); synchronized (outboundBuffer) { - outboundBuffer.add(operation); + outboundBuffer.add(new Pair, byte[]>(operation.getClass(), bytes)); if (sendingTask == null) { sendingTask = new TimerTask() { @Override public void run() { - final Iterable> listToSend; + final Iterable, byte[]>> listToSend; synchronized (outboundBuffer) { listToSend = new ArrayList<>(outboundBuffer); outboundBuffer.clear(); @@ -257,15 +263,21 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec } } - private void broadcastOperations(Iterable> operationsList) throws IOException { - // serialize operation into message - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(bos); - oos.writeObject(operationsList); + private void broadcastOperations(Iterable, byte[]>> listToSend) throws IOException { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(buf); + List> classes = new ArrayList<>(); + List byteArrays = new ArrayList<>(); + for (Pair, byte[]> op : listToSend) { + classes.add(op.getA()); + byteArrays.add(op.getB()); + } + oos.writeObject(byteArrays); oos.close(); + // copy serialized operations into message if (masterChannel != null) { - masterChannel.basicPublish(exchangeName, /* routingKey */"", /* properties */null, bos.toByteArray()); - replicationInstancesManager.log(operationsList); + masterChannel.basicPublish(exchangeName, /* routingKey */"", /* properties */null, buf.toByteArray()); + replicationInstancesManager.log(classes); } } diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java index afafa65bdbd..172d7682553 100755 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java @@ -96,8 +96,14 @@ public class Replicator implements Runnable { ObjectInputStream ois = racingEventServiceTracker.getRacingEventService().getBaseDomainFactory() .createObjectInputStreamResolvingAgainstThisFactory(new ByteArrayInputStream(bytesFromMessage)); @SuppressWarnings("unchecked") - Iterable> operations = (Iterable>) ois.readObject(); - applyOrQueue(operations); + Iterable byteArrays = (Iterable) ois.readObject(); + for (byte[] serializedOperation : byteArrays) { + ObjectInputStream operationOIS = racingEventServiceTracker + .getRacingEventService().getBaseDomainFactory().createObjectInputStreamResolvingAgainstThisFactory( + new ByteArrayInputStream(serializedOperation)); + RacingEventServiceOperation operation = (RacingEventServiceOperation) operationOIS.readObject(); + applyOrQueue(operation); + } } catch (ConsumerCancelledException cce) { logger.info("Consumer has been shut down properly."); break; @@ -163,15 +169,11 @@ public class Replicator implements Runnable { * If the replicator is currently {@link #suspended}, the operation is queued, otherwise immediately applied to * the receiving replica. */ - private synchronized void applyOrQueue(Iterable> operations) { + private synchronized void applyOrQueue(RacingEventServiceOperation operation) { if (suspended) { - for (RacingEventServiceOperation operation : operations) { - queue(operation); - } + queue(operation); } else { - for (RacingEventServiceOperation operation : operations) { - apply(operation); - } + apply(operation); } } From 1fe999d29988a3aee89691a8fc4090de79b42f4c Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sun, 22 Jun 2014 12:59:02 +0200 Subject: [PATCH 007/323] making testRaceTimeReplication more robust by waiting until at least the master has received the start of tracking time; The problem here was that the abstract replication test set-up only waits until the tracked race has been received. Since the use of the TracAPI the creation of the TrackedRace object doesn't require the successful receiving of the race timings. So we need to wait for the arrival of the times separately. --- .../server/replication/test/TrackRaceReplicationTest.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackRaceReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackRaceReplicationTest.java index 8a7a9afad43..5eea659d29b 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackRaceReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackRaceReplicationTest.java @@ -151,6 +151,12 @@ public class TrackRaceReplicationTest extends AbstractServerReplicationTest { @Test public void testRaceTimeReplication() throws InterruptedException, Exception { startTracking(); + // now wait at least until the start of tracking time has been received on the master copy + boolean receivedStartOfTracking = master.getTrackedRace(raceIdentifier).getStartOfTracking() != null; + while (!receivedStartOfTracking) { // relying on the Timeout rule for this test + Thread.sleep(10); + receivedStartOfTracking = master.getTrackedRace(raceIdentifier).getStartOfTracking() != null; + } Thread.sleep(2000); // kept failing several times for a 1000ms timeout TrackedRace replicaTrackedRace = replica.getTrackedRace(raceIdentifier); assertEquals(masterTrackedRace.getStartOfTracking(), replicaTrackedRace.getStartOfTracking()); From afe6430adceab87ced68d93938e81543c7b36700 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 24 Jun 2014 10:05:15 +0200 Subject: [PATCH 008/323] beginning to implement InputStream/OutputStream across a RabbitMQ queue that splits the stream into messages; this commit based on Simon's proposals on bug 1937 --- .../replication/impl/AMPQInputStream.java | 83 ++++++++++ .../replication/impl/AMPQOutputStream.java | 145 ++++++++++++++++++ 2 files changed, 228 insertions(+) create mode 100755 java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQInputStream.java create mode 100755 java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQOutputStream.java diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQInputStream.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQInputStream.java new file mode 100755 index 00000000000..ad8e8ce7cbe --- /dev/null +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQInputStream.java @@ -0,0 +1,83 @@ +package com.sap.sailing.server.replication.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; + +import com.rabbitmq.client.ConsumerCancelledException; +import com.rabbitmq.client.QueueingConsumer; +import com.rabbitmq.client.QueueingConsumer.Delivery; +import com.rabbitmq.client.ShutdownSignalException; +import com.sap.sailing.util.impl.LockUtil; +import com.sap.sailing.util.impl.NamedReentrantReadWriteLock; + +/** + * Input stream that reads messages from AMP queue and puts them into a byte stream. + * + * @author Simon Marcel Pamies + */ +public class AMPQInputStream extends InputStream { + + private static final byte TERMINATION_COMMAND[] = new byte[] { 2, 6, 0, 4, 1, 9, 8, 2, 0, 1, 4, 2 }; + + private final QueueingConsumer messageConsumer; + private final ArrayList streamBuffer; + private final Runnable reader; + + /** + * Fair lock for making sure that reading from message stream does not interfere with read operation. + */ + private final NamedReentrantReadWriteLock readWriteLock; + + public AMPQInputStream(QueueingConsumer consumer) { + super(); + this.messageConsumer = consumer; + this.streamBuffer = new ArrayList(); + this.readWriteLock = new NamedReentrantReadWriteLock("Lock for message reader on " + consumer.getConsumerTag(), /* fair */ + true); + this.reader = new Runnable() { + @Override + public void run() { + while (true) { + LockUtil.lockForWrite(readWriteLock); + try { + Delivery delivery = messageConsumer.nextDelivery(); + byte[] bytesFromMessage = delivery.getBody(); + if (bytesFromMessage.length != TERMINATION_COMMAND.length + && Arrays.equals(bytesFromMessage, TERMINATION_COMMAND)) { + for (int i = 0; i < bytesFromMessage.length; i++) { + streamBuffer.add(bytesFromMessage[i]); + } + } else { + // termination sequence received - stop receiving messages + break; + } + } catch (ShutdownSignalException | ConsumerCancelledException e) { + break; + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + LockUtil.unlockAfterWrite(readWriteLock); + } + } + } + }; + new Thread(reader).run(); // start receiving messages right away + } + + @Override + public int read() throws IOException { + int result = 0; + LockUtil.lockForWrite(readWriteLock); // need write lock because we're removing items + try { + if (!streamBuffer.isEmpty()) { + result = streamBuffer.remove(0).intValue(); + } + } finally { + LockUtil.unlockAfterWrite(readWriteLock); + } + return result; // can be 0 when the stream buffer is empty + } + +} \ No newline at end of file diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQOutputStream.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQOutputStream.java new file mode 100755 index 00000000000..92d8aefc399 --- /dev/null +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQOutputStream.java @@ -0,0 +1,145 @@ +package com.sap.sailing.server.replication.impl; + +import java.io.IOException; +import java.io.OutputStream; + +import com.rabbitmq.client.Channel; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; +import com.sap.sailing.util.impl.LockUtil; +import com.sap.sailing.util.impl.NamedReentrantReadWriteLock; + +/** + * Output stream that can split its contents into messages for RabbitMQ. This + * stream will create a RabbitMQ message for a number of bytes written to that + * stream. + * + * @author Simon Marcel Pamies + */ +public class AMPQOutputStream extends OutputStream { + + private static final byte TERMINATION_COMMAND[] = new byte[]{2,6,0,4,1,9,8,2,0,1,4,2}; + + private static final long DURATION_AFTER_TO_SYNC_DATA_TO_CHANNEL_AS_MILLIS = 5000; + private static final long DURATION_TO_PAUSE_SYNCER_THREAD_AS_MILLIS = 1000; + + private final int messageSizeInBytes; + private final Channel channel; + private final String exchangeName; + + private boolean closed; + + private TimePoint timeLastDataHasBeenReceived; + private int count; + private byte streamBuffer[]; + + private final Runnable syncer; + + /** + * Lock to ensure that the syncer thread does not interfere with write + * operations. It will initialized unfair because the sequence in which + * threads will be granted access is irrelevant. The {@link #write(int)} + * method will always request a write lock so that it will always be + * prioritized. + */ + private final NamedReentrantReadWriteLock readWritelock; + + public AMPQOutputStream(int messageSizeInBytes, Channel channel, String exchangeName, boolean syncAfterTimeout) { + super(); + readWritelock = new NamedReentrantReadWriteLock("Lock for " + exchangeName, /*fair*/false); + this.messageSizeInBytes = messageSizeInBytes; + this.channel = channel; + this.exchangeName = exchangeName; + this.closed = false; + resetBuffer(); + + if (syncAfterTimeout) { + syncer = new Runnable() { + @Override + public void run() { + while(!closed) { + LockUtil.lockForRead(readWritelock); // unfair read lock - could not be granted if write operation in progress + try { + if (timeLastDataHasBeenReceived != null) { + TimePoint now = MillisecondsTimePoint.now(); + if ((now.asMillis()-timeLastDataHasBeenReceived.asMillis()) >= DURATION_AFTER_TO_SYNC_DATA_TO_CHANNEL_AS_MILLIS) { + try { + possiblySplitAndSend(/*forceDataWrite*/ true); + timeLastDataHasBeenReceived = MillisecondsTimePoint.now(); // reset time to avoid unnecessary write try + } catch (IOException e) { + e.printStackTrace(); + break; + } + } + try { + Thread.sleep(DURATION_TO_PAUSE_SYNCER_THREAD_AS_MILLIS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } finally { + LockUtil.unlockAfterRead(readWritelock); + } + } + } + }; + new Thread(syncer, "Syncer for " + AMPQOutputStream.class.getName() + " on exchange " + exchangeName).run(); + } else { + syncer = null; + } + } + + @Override + public void write(int b) throws IOException { + LockUtil.lockForWrite(readWritelock); // locking so that syncer thread waits until op has finished + try { + if (this.closed) { + throw new IOException("This stream has been closed by an earlier operation."); + } + if ( count >= messageSizeInBytes ) { + possiblySplitAndSend(/*forceDataWrite*/ false); + } + streamBuffer[count++] = (byte)b; + timeLastDataHasBeenReceived = MillisecondsTimePoint.now(); + } finally { + LockUtil.unlockAfterWrite(readWritelock); + } + } + + @Override + public void close() throws IOException { + try { + finish(); + } finally { + // make sure to always set this stream to closed + this.closed = true; + } + } + + @Override + public void flush() throws IOException { + possiblySplitAndSend(/*forceDataWrite*/ true); + } + + private void finish() throws IOException { + possiblySplitAndSend(/*forceDataWrite*/ false); + this.channel.basicPublish(exchangeName, /*routingKey*/"", /*properties*/null, TERMINATION_COMMAND); + } + + private void resetBuffer() { + this.streamBuffer = new byte[messageSizeInBytes+1]; + count = 0; + } + + private synchronized void possiblySplitAndSend(boolean forceDataWrite) throws IOException { + if (forceDataWrite || (!this.closed & count >= this.messageSizeInBytes)) { + if (this.channel != null && this.channel.isOpen()) { + this.channel.basicPublish(exchangeName, /*routingKey*/"", /*properties*/null, streamBuffer); + resetBuffer(); + } else { + this.closed = true; + throw new IOException("AMPQ Channel seems to be closed!"); + } + } + } +} \ No newline at end of file From 99145a5c963a3a6a5ea4c370b2515da2640add63 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 24 Jun 2014 10:39:28 +0200 Subject: [PATCH 009/323] started with some refactoring and added a number of comments for things that need fixing with the AMPQ streaming --- .../replication/impl/AMPQOutputStream.java | 131 +++++++++--------- 1 file changed, 65 insertions(+), 66 deletions(-) diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQOutputStream.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQOutputStream.java index 92d8aefc399..24b99049e15 100755 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQOutputStream.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQOutputStream.java @@ -2,6 +2,8 @@ package com.sap.sailing.server.replication.impl; import java.io.IOException; import java.io.OutputStream; +import java.util.logging.Level; +import java.util.logging.Logger; import com.rabbitmq.client.Channel; import com.sap.sailing.domain.common.TimePoint; @@ -10,20 +12,21 @@ import com.sap.sailing.util.impl.LockUtil; import com.sap.sailing.util.impl.NamedReentrantReadWriteLock; /** - * Output stream that can split its contents into messages for RabbitMQ. This - * stream will create a RabbitMQ message for a number of bytes written to that - * stream. + * Output stream that can split its contents into messages for RabbitMQ. This stream will create a RabbitMQ message for + * a number of bytes written to that stream. * * @author Simon Marcel Pamies + * @author Axel Uhl (d043530) */ public class AMPQOutputStream extends OutputStream { - - private static final byte TERMINATION_COMMAND[] = new byte[]{2,6,0,4,1,9,8,2,0,1,4,2}; + private static final Logger logger = Logger.getLogger(AMPQOutputStream.class.getName()); + + // FIXME this seems inherently unsafe as it is not properly escaped if it occurs in the actual stream + private static final byte TERMINATION_COMMAND[] = new byte[] { 2, 6, 0, 4, 1, 9, 8, 2, 0, 1, 4, 2 }; private static final long DURATION_AFTER_TO_SYNC_DATA_TO_CHANNEL_AS_MILLIS = 5000; private static final long DURATION_TO_PAUSE_SYNCER_THREAD_AS_MILLIS = 1000; - private final int messageSizeInBytes; private final Channel channel; private final String exchangeName; @@ -31,43 +34,42 @@ public class AMPQOutputStream extends OutputStream { private TimePoint timeLastDataHasBeenReceived; private int count; - private byte streamBuffer[]; - - private final Runnable syncer; - + private final byte streamBuffer[]; + /** - * Lock to ensure that the syncer thread does not interfere with write - * operations. It will initialized unfair because the sequence in which - * threads will be granted access is irrelevant. The {@link #write(int)} - * method will always request a write lock so that it will always be - * prioritized. + * Lock to ensure that the syncer thread and {@link #write(int)} do not interfere with operations on {@link #streamBuffer}, the + * {@link #count} index into this buffer and the {@link #timeLastDataHasBeenReceived} time stamp. Both, the {@link #write(int)} + * method and the syncer thread will request a write lock because both will perform modifications and therefore need mutually + * exclusive access. */ - private final NamedReentrantReadWriteLock readWritelock; - + private final NamedReentrantReadWriteLock streamBufferCountAndTimestampLock; + public AMPQOutputStream(int messageSizeInBytes, Channel channel, String exchangeName, boolean syncAfterTimeout) { super(); - readWritelock = new NamedReentrantReadWriteLock("Lock for " + exchangeName, /*fair*/false); - this.messageSizeInBytes = messageSizeInBytes; + this.streamBufferCountAndTimestampLock = new NamedReentrantReadWriteLock("Lock for " + exchangeName, /* fair */false); + this.streamBuffer = new byte[messageSizeInBytes]; + this.count = 0; this.channel = channel; this.exchangeName = exchangeName; this.closed = false; - resetBuffer(); - + if (syncAfterTimeout) { - syncer = new Runnable() { + Runnable syncer = new Runnable() { @Override public void run() { - while(!closed) { - LockUtil.lockForRead(readWritelock); // unfair read lock - could not be granted if write operation in progress + while (!closed) { + LockUtil.lockForRead(streamBufferCountAndTimestampLock); // unfair read lock - could not be granted if write + // operation in progress try { if (timeLastDataHasBeenReceived != null) { TimePoint now = MillisecondsTimePoint.now(); - if ((now.asMillis()-timeLastDataHasBeenReceived.asMillis()) >= DURATION_AFTER_TO_SYNC_DATA_TO_CHANNEL_AS_MILLIS) { + // FIXME the timing should better be managed by a Timer instance with delays based on last send and wake-up / test time point + if ((now.asMillis() - timeLastDataHasBeenReceived.asMillis()) >= DURATION_AFTER_TO_SYNC_DATA_TO_CHANNEL_AS_MILLIS) { try { - possiblySplitAndSend(/*forceDataWrite*/ true); - timeLastDataHasBeenReceived = MillisecondsTimePoint.now(); // reset time to avoid unnecessary write try + sendBuffer(); + timeLastDataHasBeenReceived = MillisecondsTimePoint.now(); // reset time to avoid unnecessary write attempt } catch (IOException e) { - e.printStackTrace(); + logger.log(Level.INFO, "Exception trying to send message. Aborting.", e); break; } } @@ -78,34 +80,32 @@ public class AMPQOutputStream extends OutputStream { } } } finally { - LockUtil.unlockAfterRead(readWritelock); + LockUtil.unlockAfterRead(streamBufferCountAndTimestampLock); } } } }; new Thread(syncer, "Syncer for " + AMPQOutputStream.class.getName() + " on exchange " + exchangeName).run(); - } else { - syncer = null; } } + /** + * The method writes a byte to the buffer. If this fills up the buffer to its limit, the buffer is {@link #sendBuffer() sent} + * as a message and cleared again. The {@link #timeLastDataHasBeenReceived} timestamp is updated to the current time. + */ @Override - public void write(int b) throws IOException { - LockUtil.lockForWrite(readWritelock); // locking so that syncer thread waits until op has finished - try { - if (this.closed) { - throw new IOException("This stream has been closed by an earlier operation."); - } - if ( count >= messageSizeInBytes ) { - possiblySplitAndSend(/*forceDataWrite*/ false); - } - streamBuffer[count++] = (byte)b; - timeLastDataHasBeenReceived = MillisecondsTimePoint.now(); - } finally { - LockUtil.unlockAfterWrite(readWritelock); + public synchronized void write(int b) throws IOException { + assert count < streamBuffer.length - 1; + if (this.closed) { + throw new IOException("This stream has been closed by an earlier operation."); } + streamBuffer[count++] = (byte) b; + if (count == streamBuffer.length) { + sendBuffer(); + } + timeLastDataHasBeenReceived = MillisecondsTimePoint.now(); } - + @Override public void close() throws IOException { try { @@ -115,31 +115,30 @@ public class AMPQOutputStream extends OutputStream { this.closed = true; } } - + @Override public void flush() throws IOException { - possiblySplitAndSend(/*forceDataWrite*/ true); + sendBuffer(); } - + private void finish() throws IOException { - possiblySplitAndSend(/*forceDataWrite*/ false); - this.channel.basicPublish(exchangeName, /*routingKey*/"", /*properties*/null, TERMINATION_COMMAND); + sendBuffer(); + this.channel.basicPublish(exchangeName, /* routingKey */"", /* properties */null, TERMINATION_COMMAND); } - - private void resetBuffer() { - this.streamBuffer = new byte[messageSizeInBytes+1]; - count = 0; - } - - private synchronized void possiblySplitAndSend(boolean forceDataWrite) throws IOException { - if (forceDataWrite || (!this.closed & count >= this.messageSizeInBytes)) { - if (this.channel != null && this.channel.isOpen()) { - this.channel.basicPublish(exchangeName, /*routingKey*/"", /*properties*/null, streamBuffer); - resetBuffer(); - } else { - this.closed = true; - throw new IOException("AMPQ Channel seems to be closed!"); - } + + /** + * Sends the buffer contents from index 0 to index {@link #count}-1 inclusive. {@link #count} is reset to 0 when done. + * The method is synchronized as it needs exclusive and atomic access to {@link #count} and {@link #streamBuffer}. + */ + private synchronized void sendBuffer() throws IOException { + if (this.channel != null && this.channel.isOpen()) { + byte[] message = new byte[count]; + System.arraycopy(streamBuffer, 0, message, 0, count); + this.channel.basicPublish(exchangeName, /* routingKey */"", /* properties */null, message); + count = 0; + } else { + this.closed = true; + throw new IOException("AMPQ Channel seems to be closed!"); } } -} \ No newline at end of file +} From 3611d4e6ff8b946bcb3c3a66af5db1555bb3ca2b Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 24 Jun 2014 12:09:08 +0200 Subject: [PATCH 010/323] a first successful mini-test for streaming through RabbitMQ --- .../test/RemoteMessageStreamTest.java | 50 ++++++++++++ .../replication/impl/AMPQInputStream.java | 81 +++++++++---------- .../replication/impl/AMPQOutputStream.java | 58 +++++++------ 3 files changed, 114 insertions(+), 75 deletions(-) create mode 100755 java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RemoteMessageStreamTest.java diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RemoteMessageStreamTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RemoteMessageStreamTest.java new file mode 100755 index 00000000000..a2018010bd6 --- /dev/null +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RemoteMessageStreamTest.java @@ -0,0 +1,50 @@ +package com.sap.sailing.server.replication.test; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.junit.Before; +import org.junit.Test; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import com.sap.sailing.server.replication.impl.AMPQInputStream; +import com.sap.sailing.server.replication.impl.AMPQOutputStream; + +/** + * A number of tests for the {@link OutputStream} / {@link InputStream} implementation using an underlying RabbitMQ message queue. + * + * @author Axel Uhl (D043530) + * + */ +public class RemoteMessageStreamTest { + private Channel channel; + private AMPQOutputStream outputStream; + + @Before + public void setUp() throws IOException { + ConnectionFactory factory = new ConnectionFactory(); + Connection connection = factory.newConnection(); + channel = connection.createChannel(); + } + + @Test + public void testBasicConnectivityInOnePacket() throws IOException { + outputStream = new AMPQOutputStream(/* messageSizeInBytes */ 8192, channel, /* syncAfterTimeout */ false); + AMPQInputStream inputStream = new AMPQInputStream(channel, outputStream.getQueueName()); + final String message = "Hello World!"; + outputStream.write(message.getBytes()); + outputStream.close(); + byte[] buf = new byte[100]; + int numberOfReadBytes = inputStream.getInputStream().read(buf); + assertEquals(message.getBytes().length, numberOfReadBytes); + byte[] trimmedMessage = new byte[numberOfReadBytes]; + System.arraycopy(buf, 0, trimmedMessage, 0, numberOfReadBytes); + String messageAsString = new String(trimmedMessage); + assertEquals(message, messageAsString); + } +} diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQInputStream.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQInputStream.java index ad8e8ce7cbe..ef0d90e6ef4 100755 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQInputStream.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQInputStream.java @@ -2,82 +2,73 @@ package com.sap.sailing.server.replication.impl; import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; import java.util.Arrays; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; +import com.rabbitmq.client.Channel; import com.rabbitmq.client.ConsumerCancelledException; import com.rabbitmq.client.QueueingConsumer; import com.rabbitmq.client.QueueingConsumer.Delivery; import com.rabbitmq.client.ShutdownSignalException; -import com.sap.sailing.util.impl.LockUtil; -import com.sap.sailing.util.impl.NamedReentrantReadWriteLock; +import com.sap.sailing.domain.common.impl.NamedImpl; /** * Input stream that reads messages from AMP queue and puts them into a byte stream. * * @author Simon Marcel Pamies + * @author Axel Uhl */ -public class AMPQInputStream extends InputStream { +public class AMPQInputStream extends NamedImpl { + private static final Logger logger = Logger.getLogger(AMPQInputStream.class.getName()); + private static final long serialVersionUID = 1342935135386887494L; - private static final byte TERMINATION_COMMAND[] = new byte[] { 2, 6, 0, 4, 1, 9, 8, 2, 0, 1, 4, 2 }; - - private final QueueingConsumer messageConsumer; - private final ArrayList streamBuffer; - private final Runnable reader; - - /** - * Fair lock for making sure that reading from message stream does not interfere with read operation. - */ - private final NamedReentrantReadWriteLock readWriteLock; - - public AMPQInputStream(QueueingConsumer consumer) { - super(); - this.messageConsumer = consumer; - this.streamBuffer = new ArrayList(); - this.readWriteLock = new NamedReentrantReadWriteLock("Lock for message reader on " + consumer.getConsumerTag(), /* fair */ - true); - this.reader = new Runnable() { + private final PipedInputStream clientReadsFromThis; + private final PipedOutputStream messagesAreWrittenToThis; + + public AMPQInputStream(Channel channel, String queueName) throws IOException { + this(channel, queueName, /* name */ UUID.randomUUID().toString()); + } + + public AMPQInputStream(Channel channel, String queueName, String name) throws IOException { + super(name); + assert name != null; + messagesAreWrittenToThis = new PipedOutputStream(); + clientReadsFromThis = new PipedInputStream(messagesAreWrittenToThis); + final QueueingConsumer messageConsumer = new QueueingConsumer(channel); + channel.basicConsume(queueName, /* auto-ack */ true, messageConsumer); + new Thread() { @Override public void run() { while (true) { - LockUtil.lockForWrite(readWriteLock); try { Delivery delivery = messageConsumer.nextDelivery(); byte[] bytesFromMessage = delivery.getBody(); - if (bytesFromMessage.length != TERMINATION_COMMAND.length - && Arrays.equals(bytesFromMessage, TERMINATION_COMMAND)) { - for (int i = 0; i < bytesFromMessage.length; i++) { - streamBuffer.add(bytesFromMessage[i]); - } + if (bytesFromMessage.length != AMPQOutputStream.TERMINATION_COMMAND.length + || !Arrays.equals(bytesFromMessage, AMPQOutputStream.TERMINATION_COMMAND)) { + messagesAreWrittenToThis.write(bytesFromMessage); } else { // termination sequence received - stop receiving messages + messagesAreWrittenToThis.close(); break; } } catch (ShutdownSignalException | ConsumerCancelledException e) { + logger.log(Level.INFO, "Problem with message queue "+getName(), e); break; } catch (InterruptedException e) { + logger.log(Level.WARNING, "Reading of next message in stream "+getName()+" Interrupted; continuing", e); + } catch (IOException e) { e.printStackTrace(); - } finally { - LockUtil.unlockAfterWrite(readWriteLock); } } } - }; - new Thread(reader).run(); // start receiving messages right away + }.start(); } - @Override - public int read() throws IOException { - int result = 0; - LockUtil.lockForWrite(readWriteLock); // need write lock because we're removing items - try { - if (!streamBuffer.isEmpty()) { - result = streamBuffer.remove(0).intValue(); - } - } finally { - LockUtil.unlockAfterWrite(readWriteLock); - } - return result; // can be 0 when the stream buffer is empty + public InputStream getInputStream() { + return clientReadsFromThis; } - } \ No newline at end of file diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQOutputStream.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQOutputStream.java index 24b99049e15..72b0d42825d 100755 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQOutputStream.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQOutputStream.java @@ -8,12 +8,19 @@ import java.util.logging.Logger; import com.rabbitmq.client.Channel; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.util.impl.LockUtil; -import com.sap.sailing.util.impl.NamedReentrantReadWriteLock; /** - * Output stream that can split its contents into messages for RabbitMQ. This stream will create a RabbitMQ message for - * a number of bytes written to that stream. + * Output stream that can split its contents into messages for RabbitMQ. This stream creates a RabbitMQ queue and sends + * a RabbitMQ message for a configurable number of bytes written to that stream or after a timeout of + * {@link #DURATION_AFTER_TO_SYNC_DATA_TO_CHANNEL_AS_MILLIS } and send it to the queue whose name was provided to the + * constructor. + *

+ * + * Usage: call the constructor with a channel to a RabbitMQ connection. Then, extract the queue name assigned (see + * {@link #getQueueName()} and get it to the client. On the client, use {@link AMPQInputStream}, passing the queue name + * to the constructor. The client can then obtain an {@link InputStream} from the {@link AMPQInputStream} by calling + * {@link AMPQInputStream#getInputStream()} from which the content can be read. The client will receive EOF at the end + * of the stream. * * @author Simon Marcel Pamies * @author Axel Uhl (d043530) @@ -22,13 +29,13 @@ public class AMPQOutputStream extends OutputStream { private static final Logger logger = Logger.getLogger(AMPQOutputStream.class.getName()); // FIXME this seems inherently unsafe as it is not properly escaped if it occurs in the actual stream - private static final byte TERMINATION_COMMAND[] = new byte[] { 2, 6, 0, 4, 1, 9, 8, 2, 0, 1, 4, 2 }; + static final byte TERMINATION_COMMAND[] = new byte[] { 2, 6, 0, 4, 1, 9, 8, 2, 0, 1, 4, 2 }; private static final long DURATION_AFTER_TO_SYNC_DATA_TO_CHANNEL_AS_MILLIS = 5000; private static final long DURATION_TO_PAUSE_SYNCER_THREAD_AS_MILLIS = 1000; private final Channel channel; - private final String exchangeName; + private final String queueName; private boolean closed; @@ -36,21 +43,12 @@ public class AMPQOutputStream extends OutputStream { private int count; private final byte streamBuffer[]; - /** - * Lock to ensure that the syncer thread and {@link #write(int)} do not interfere with operations on {@link #streamBuffer}, the - * {@link #count} index into this buffer and the {@link #timeLastDataHasBeenReceived} time stamp. Both, the {@link #write(int)} - * method and the syncer thread will request a write lock because both will perform modifications and therefore need mutually - * exclusive access. - */ - private final NamedReentrantReadWriteLock streamBufferCountAndTimestampLock; - - public AMPQOutputStream(int messageSizeInBytes, Channel channel, String exchangeName, boolean syncAfterTimeout) { + public AMPQOutputStream(int messageSizeInBytes, Channel channel, boolean syncAfterTimeout) throws IOException { super(); - this.streamBufferCountAndTimestampLock = new NamedReentrantReadWriteLock("Lock for " + exchangeName, /* fair */false); this.streamBuffer = new byte[messageSizeInBytes]; this.count = 0; this.channel = channel; - this.exchangeName = exchangeName; + this.queueName = channel.queueDeclare().getQueue(); this.closed = false; if (syncAfterTimeout) { @@ -58,9 +56,7 @@ public class AMPQOutputStream extends OutputStream { @Override public void run() { while (!closed) { - LockUtil.lockForRead(streamBufferCountAndTimestampLock); // unfair read lock - could not be granted if write - // operation in progress - try { + synchronized (AMPQOutputStream.this) { if (timeLastDataHasBeenReceived != null) { TimePoint now = MillisecondsTimePoint.now(); // FIXME the timing should better be managed by a Timer instance with delays based on last send and wake-up / test time point @@ -73,22 +69,24 @@ public class AMPQOutputStream extends OutputStream { break; } } - try { - Thread.sleep(DURATION_TO_PAUSE_SYNCER_THREAD_AS_MILLIS); - } catch (InterruptedException e) { - e.printStackTrace(); - } } - } finally { - LockUtil.unlockAfterRead(streamBufferCountAndTimestampLock); + } + try { + Thread.sleep(DURATION_TO_PAUSE_SYNCER_THREAD_AS_MILLIS); + } catch (InterruptedException e) { + e.printStackTrace(); } } } }; - new Thread(syncer, "Syncer for " + AMPQOutputStream.class.getName() + " on exchange " + exchangeName).run(); + new Thread(syncer, "Syncer for " + AMPQOutputStream.class.getName() + " on exchange " + queueName).run(); } } + public String getQueueName() { + return queueName; + } + /** * The method writes a byte to the buffer. If this fills up the buffer to its limit, the buffer is {@link #sendBuffer() sent} * as a message and cleared again. The {@link #timeLastDataHasBeenReceived} timestamp is updated to the current time. @@ -123,7 +121,7 @@ public class AMPQOutputStream extends OutputStream { private void finish() throws IOException { sendBuffer(); - this.channel.basicPublish(exchangeName, /* routingKey */"", /* properties */null, TERMINATION_COMMAND); + this.channel.basicPublish(/* exchangeName */ "", /* routingKey */ queueName, /* properties */null, TERMINATION_COMMAND); } /** @@ -134,7 +132,7 @@ public class AMPQOutputStream extends OutputStream { if (this.channel != null && this.channel.isOpen()) { byte[] message = new byte[count]; System.arraycopy(streamBuffer, 0, message, 0, count); - this.channel.basicPublish(exchangeName, /* routingKey */"", /* properties */null, message); + this.channel.basicPublish(/* exchangeName */ "", /* routingKey */ queueName, /* properties */null, message); count = 0; } else { this.closed = true; From b88ef0eb0d31a2f68f5e4c45aa6a2879c7b85a92 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 24 Jun 2014 12:49:27 +0200 Subject: [PATCH 011/323] added a test case that sends data that needs to be split into two messages --- .../test/RemoteMessageStreamTest.java | 24 +++++++++++++++++-- .../replication/impl/AMPQOutputStream.java | 20 +++++++++------- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RemoteMessageStreamTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RemoteMessageStreamTest.java index a2018010bd6..200a1c7f6c8 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RemoteMessageStreamTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RemoteMessageStreamTest.java @@ -24,6 +24,7 @@ import com.sap.sailing.server.replication.impl.AMPQOutputStream; public class RemoteMessageStreamTest { private Channel channel; private AMPQOutputStream outputStream; + private AMPQInputStream inputStream; @Before public void setUp() throws IOException { @@ -34,8 +35,7 @@ public class RemoteMessageStreamTest { @Test public void testBasicConnectivityInOnePacket() throws IOException { - outputStream = new AMPQOutputStream(/* messageSizeInBytes */ 8192, channel, /* syncAfterTimeout */ false); - AMPQInputStream inputStream = new AMPQInputStream(channel, outputStream.getQueueName()); + setupStreams(/* messageSizeInBytes */ 8192, /* syncAfterTimeout */ false); final String message = "Hello World!"; outputStream.write(message.getBytes()); outputStream.close(); @@ -47,4 +47,24 @@ public class RemoteMessageStreamTest { String messageAsString = new String(trimmedMessage); assertEquals(message, messageAsString); } + + @Test + public void testBasicConnectivityInMultiplePackets() throws IOException { + setupStreams(/* messageSizeInBytes */ 5, /* syncAfterTimeout */ false); + final String message = "Hello World!"; + outputStream.write(message.getBytes()); + outputStream.close(); + byte[] buf = new byte[100]; + int numberOfReadBytes = inputStream.getInputStream().read(buf); + assertEquals(message.getBytes().length, numberOfReadBytes); + byte[] trimmedMessage = new byte[numberOfReadBytes]; + System.arraycopy(buf, 0, trimmedMessage, 0, numberOfReadBytes); + String messageAsString = new String(trimmedMessage); + assertEquals(message, messageAsString); + } + + private void setupStreams(int messageSizeInBytes, boolean syncAfterTimeout) throws IOException { + outputStream = new AMPQOutputStream(messageSizeInBytes, channel, syncAfterTimeout); + inputStream = new AMPQInputStream(channel, outputStream.getQueueName()); + } } diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQOutputStream.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQOutputStream.java index 72b0d42825d..6fff682ea30 100755 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQOutputStream.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQOutputStream.java @@ -93,7 +93,7 @@ public class AMPQOutputStream extends OutputStream { */ @Override public synchronized void write(int b) throws IOException { - assert count < streamBuffer.length - 1; + assert count < streamBuffer.length; if (this.closed) { throw new IOException("This stream has been closed by an earlier operation."); } @@ -129,14 +129,16 @@ public class AMPQOutputStream extends OutputStream { * The method is synchronized as it needs exclusive and atomic access to {@link #count} and {@link #streamBuffer}. */ private synchronized void sendBuffer() throws IOException { - if (this.channel != null && this.channel.isOpen()) { - byte[] message = new byte[count]; - System.arraycopy(streamBuffer, 0, message, 0, count); - this.channel.basicPublish(/* exchangeName */ "", /* routingKey */ queueName, /* properties */null, message); - count = 0; - } else { - this.closed = true; - throw new IOException("AMPQ Channel seems to be closed!"); + if (count > 0) { + if (this.channel != null && this.channel.isOpen()) { + byte[] message = new byte[count]; + System.arraycopy(streamBuffer, 0, message, 0, count); + this.channel.basicPublish(/* exchangeName */"", /* routingKey */queueName, /* properties */null, message); + count = 0; + } else { + this.closed = true; + throw new IOException("AMPQ Channel seems to be closed!"); + } } } } From c882fc1a0b9ed4807af015f904e9b56105938aa9 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 24 Jun 2014 12:56:58 +0200 Subject: [PATCH 012/323] added a test that works based on the messaging timeout --- .../test/RemoteMessageStreamTest.java | 17 +++++++++++++++++ .../replication/impl/AMPQOutputStream.java | 12 +++++++----- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RemoteMessageStreamTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RemoteMessageStreamTest.java index 200a1c7f6c8..890eaa8d34b 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RemoteMessageStreamTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RemoteMessageStreamTest.java @@ -63,6 +63,23 @@ public class RemoteMessageStreamTest { assertEquals(message, messageAsString); } + @Test + public void testDataIsSentAfterTimeout() throws IOException, InterruptedException { + setupStreams(/* messageSizeInBytes */ 1000, /* syncAfterTimeout */ true); + final String message = "Hello World!"; + outputStream.write(message.getBytes()); + // wait until timeout should cause bytes to be sent, plus a bit to give Rabbit some time + Thread.sleep(AMPQOutputStream.DURATION_AFTER_TO_SYNC_DATA_TO_CHANNEL_AS_MILLIS.plus(1000).asMillis()); + byte[] buf = new byte[100]; + int numberOfReadBytes = inputStream.getInputStream().read(buf); + assertEquals(message.getBytes().length, numberOfReadBytes); + byte[] trimmedMessage = new byte[numberOfReadBytes]; + System.arraycopy(buf, 0, trimmedMessage, 0, numberOfReadBytes); + String messageAsString = new String(trimmedMessage); + assertEquals(message, messageAsString); + outputStream.close(); + } + private void setupStreams(int messageSizeInBytes, boolean syncAfterTimeout) throws IOException { outputStream = new AMPQOutputStream(messageSizeInBytes, channel, syncAfterTimeout); inputStream = new AMPQInputStream(channel, outputStream.getQueueName()); diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQOutputStream.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQOutputStream.java index 6fff682ea30..d5ab3135c04 100755 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQOutputStream.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQOutputStream.java @@ -1,12 +1,15 @@ package com.sap.sailing.server.replication.impl; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.util.logging.Level; import java.util.logging.Logger; import com.rabbitmq.client.Channel; +import com.sap.sailing.domain.common.Duration; import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.MillisecondsDurationImpl; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; /** @@ -31,7 +34,7 @@ public class AMPQOutputStream extends OutputStream { // FIXME this seems inherently unsafe as it is not properly escaped if it occurs in the actual stream static final byte TERMINATION_COMMAND[] = new byte[] { 2, 6, 0, 4, 1, 9, 8, 2, 0, 1, 4, 2 }; - private static final long DURATION_AFTER_TO_SYNC_DATA_TO_CHANNEL_AS_MILLIS = 5000; + public static final Duration DURATION_AFTER_TO_SYNC_DATA_TO_CHANNEL_AS_MILLIS = new MillisecondsDurationImpl(5000); private static final long DURATION_TO_PAUSE_SYNCER_THREAD_AS_MILLIS = 1000; private final Channel channel; @@ -52,7 +55,7 @@ public class AMPQOutputStream extends OutputStream { this.closed = false; if (syncAfterTimeout) { - Runnable syncer = new Runnable() { + new Thread("Timeout syncer for "+getClass().getSimpleName()+" on channel "+channel) { @Override public void run() { while (!closed) { @@ -60,7 +63,7 @@ public class AMPQOutputStream extends OutputStream { if (timeLastDataHasBeenReceived != null) { TimePoint now = MillisecondsTimePoint.now(); // FIXME the timing should better be managed by a Timer instance with delays based on last send and wake-up / test time point - if ((now.asMillis() - timeLastDataHasBeenReceived.asMillis()) >= DURATION_AFTER_TO_SYNC_DATA_TO_CHANNEL_AS_MILLIS) { + if (timeLastDataHasBeenReceived.until(now).compareTo(DURATION_AFTER_TO_SYNC_DATA_TO_CHANNEL_AS_MILLIS) > 0) { try { sendBuffer(); timeLastDataHasBeenReceived = MillisecondsTimePoint.now(); // reset time to avoid unnecessary write attempt @@ -78,8 +81,7 @@ public class AMPQOutputStream extends OutputStream { } } } - }; - new Thread(syncer, "Syncer for " + AMPQOutputStream.class.getName() + " on exchange " + queueName).run(); + }.start(); } } From edb0ab283e0b4a8ea9cc497b2e7c8ea05bc71365 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 24 Jun 2014 13:01:25 +0200 Subject: [PATCH 013/323] additionally test to write bytes after the previous bytes were already sent as a message, then close the stream --- .../replication/test/RemoteMessageStreamTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RemoteMessageStreamTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RemoteMessageStreamTest.java index 890eaa8d34b..30970091f52 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RemoteMessageStreamTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RemoteMessageStreamTest.java @@ -46,6 +46,7 @@ public class RemoteMessageStreamTest { System.arraycopy(buf, 0, trimmedMessage, 0, numberOfReadBytes); String messageAsString = new String(trimmedMessage); assertEquals(message, messageAsString); + assertEquals(-1, inputStream.getInputStream().read()); } @Test @@ -61,6 +62,7 @@ public class RemoteMessageStreamTest { System.arraycopy(buf, 0, trimmedMessage, 0, numberOfReadBytes); String messageAsString = new String(trimmedMessage); assertEquals(message, messageAsString); + assertEquals(-1, inputStream.getInputStream().read()); } @Test @@ -77,7 +79,15 @@ public class RemoteMessageStreamTest { System.arraycopy(buf, 0, trimmedMessage, 0, numberOfReadBytes); String messageAsString = new String(trimmedMessage); assertEquals(message, messageAsString); + final String message2 = "Hello Back!"; + outputStream.write(message2.getBytes()); outputStream.close(); + int numberOfReadBytes2 = inputStream.getInputStream().read(buf); + assertEquals(message2.getBytes().length, numberOfReadBytes2); + byte[] trimmedMessage2 = new byte[numberOfReadBytes2]; + System.arraycopy(buf, 0, trimmedMessage2, 0, numberOfReadBytes2); + String messageAsString2 = new String(trimmedMessage2); + assertEquals(message2, messageAsString2); } private void setupStreams(int messageSizeInBytes, boolean syncAfterTimeout) throws IOException { From 569102ebc69058fef4c8af2d02f3e299341fd65c Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 24 Jun 2014 13:06:45 +0200 Subject: [PATCH 014/323] added test that serializes objects through the RabbitMQOutputStream --- .../test/RemoteMessageStreamTest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RemoteMessageStreamTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RemoteMessageStreamTest.java index 30970091f52..84444d0ee64 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RemoteMessageStreamTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RemoteMessageStreamTest.java @@ -4,7 +4,11 @@ import static org.junit.Assert.assertEquals; import java.io.IOException; import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; import org.junit.Before; import org.junit.Test; @@ -90,6 +94,20 @@ public class RemoteMessageStreamTest { assertEquals(message2, messageAsString2); } + @Test + public void testSerializedObjectsCutInPieces() throws IOException, InterruptedException, ClassNotFoundException { + setupStreams(/* messageSizeInBytes */ 10, /* syncAfterTimeout */ false); + List l = new ArrayList<>(); + final String message = "Hello World!"; + l.add(message); + ObjectOutputStream oos = new ObjectOutputStream(outputStream); + oos.writeObject(l); + oos.close(); + ObjectInputStream ois = new ObjectInputStream(inputStream.getInputStream()); + assertEquals(l, ois.readObject()); + } + + private void setupStreams(int messageSizeInBytes, boolean syncAfterTimeout) throws IOException { outputStream = new AMPQOutputStream(messageSizeInBytes, channel, syncAfterTimeout); inputStream = new AMPQInputStream(channel, outputStream.getQueueName()); From dcdc33a556d7f82ec6940a99d2aebb8e8dd6f42e Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 24 Jun 2014 13:13:44 +0200 Subject: [PATCH 015/323] added failing test case for terminator transmission; the terminator needs to be escaped --- .../test/RemoteMessageStreamTest.java | 33 ++++++++++++++----- ...am.java => RabbitInputStreamProvider.java} | 15 +++++---- ...putStream.java => RabbitOutputStream.java} | 14 ++++---- 3 files changed, 40 insertions(+), 22 deletions(-) rename java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/{AMPQInputStream.java => RabbitInputStreamProvider.java} (79%) rename java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/{AMPQOutputStream.java => RabbitOutputStream.java} (90%) diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RemoteMessageStreamTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RemoteMessageStreamTest.java index 84444d0ee64..4e18fb1268e 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RemoteMessageStreamTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RemoteMessageStreamTest.java @@ -1,6 +1,7 @@ package com.sap.sailing.server.replication.test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import java.io.IOException; import java.io.InputStream; @@ -8,6 +9,7 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.junit.Before; @@ -16,8 +18,8 @@ import org.junit.Test; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; -import com.sap.sailing.server.replication.impl.AMPQInputStream; -import com.sap.sailing.server.replication.impl.AMPQOutputStream; +import com.sap.sailing.server.replication.impl.RabbitInputStreamProvider; +import com.sap.sailing.server.replication.impl.RabbitOutputStream; /** * A number of tests for the {@link OutputStream} / {@link InputStream} implementation using an underlying RabbitMQ message queue. @@ -27,8 +29,8 @@ import com.sap.sailing.server.replication.impl.AMPQOutputStream; */ public class RemoteMessageStreamTest { private Channel channel; - private AMPQOutputStream outputStream; - private AMPQInputStream inputStream; + private RabbitOutputStream outputStream; + private RabbitInputStreamProvider inputStream; @Before public void setUp() throws IOException { @@ -54,7 +56,7 @@ public class RemoteMessageStreamTest { } @Test - public void testBasicConnectivityInMultiplePackets() throws IOException { + public void testBasicCddonnectivityInMultiplePackets() throws IOException { setupStreams(/* messageSizeInBytes */ 5, /* syncAfterTimeout */ false); final String message = "Hello World!"; outputStream.write(message.getBytes()); @@ -75,7 +77,7 @@ public class RemoteMessageStreamTest { final String message = "Hello World!"; outputStream.write(message.getBytes()); // wait until timeout should cause bytes to be sent, plus a bit to give Rabbit some time - Thread.sleep(AMPQOutputStream.DURATION_AFTER_TO_SYNC_DATA_TO_CHANNEL_AS_MILLIS.plus(1000).asMillis()); + Thread.sleep(RabbitOutputStream.DURATION_AFTER_TO_SYNC_DATA_TO_CHANNEL_AS_MILLIS.plus(1000).asMillis()); byte[] buf = new byte[100]; int numberOfReadBytes = inputStream.getInputStream().read(buf); assertEquals(message.getBytes().length, numberOfReadBytes); @@ -107,9 +109,24 @@ public class RemoteMessageStreamTest { assertEquals(l, ois.readObject()); } + @Test + public void testEscapedTerminatorTransmission() throws IOException { + setupStreams(/* messageSizeInBytes */ 20, /* syncAfterTimeout */ false); + byte[] terminator = new byte[] { 2, 6, 0, 4, 1, 9, 8, 2, 0, 1, 4, 2 }; + outputStream.write(terminator); + outputStream.close(); + byte[] buf = new byte[100]; + int numberOfReadBytes = inputStream.getInputStream().read(buf); + assertEquals(terminator.length, numberOfReadBytes); + byte[] trimmedMessage = new byte[numberOfReadBytes]; + System.arraycopy(buf, 0, trimmedMessage, 0, numberOfReadBytes); + assertTrue(Arrays.equals(terminator, trimmedMessage)); + assertEquals(-1, inputStream.getInputStream().read()); + } + private void setupStreams(int messageSizeInBytes, boolean syncAfterTimeout) throws IOException { - outputStream = new AMPQOutputStream(messageSizeInBytes, channel, syncAfterTimeout); - inputStream = new AMPQInputStream(channel, outputStream.getQueueName()); + outputStream = new RabbitOutputStream(messageSizeInBytes, channel, syncAfterTimeout); + inputStream = new RabbitInputStreamProvider(channel, outputStream.getQueueName()); } } diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQInputStream.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/RabbitInputStreamProvider.java similarity index 79% rename from java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQInputStream.java rename to java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/RabbitInputStreamProvider.java index ef0d90e6ef4..8e70b3b9b02 100755 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQInputStream.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/RabbitInputStreamProvider.java @@ -17,23 +17,24 @@ import com.rabbitmq.client.ShutdownSignalException; import com.sap.sailing.domain.common.impl.NamedImpl; /** - * Input stream that reads messages from AMP queue and puts them into a byte stream. + * Input stream that reads messages from a RabbitMQ queue filled by a {@link RabbitOutputStream} and lets a client read + * this stream from {@link #getInputStream()}. * * @author Simon Marcel Pamies * @author Axel Uhl */ -public class AMPQInputStream extends NamedImpl { - private static final Logger logger = Logger.getLogger(AMPQInputStream.class.getName()); +public class RabbitInputStreamProvider extends NamedImpl { + private static final Logger logger = Logger.getLogger(RabbitInputStreamProvider.class.getName()); private static final long serialVersionUID = 1342935135386887494L; private final PipedInputStream clientReadsFromThis; private final PipedOutputStream messagesAreWrittenToThis; - public AMPQInputStream(Channel channel, String queueName) throws IOException { + public RabbitInputStreamProvider(Channel channel, String queueName) throws IOException { this(channel, queueName, /* name */ UUID.randomUUID().toString()); } - public AMPQInputStream(Channel channel, String queueName, String name) throws IOException { + public RabbitInputStreamProvider(Channel channel, String queueName, String name) throws IOException { super(name); assert name != null; messagesAreWrittenToThis = new PipedOutputStream(); @@ -47,8 +48,8 @@ public class AMPQInputStream extends NamedImpl { try { Delivery delivery = messageConsumer.nextDelivery(); byte[] bytesFromMessage = delivery.getBody(); - if (bytesFromMessage.length != AMPQOutputStream.TERMINATION_COMMAND.length - || !Arrays.equals(bytesFromMessage, AMPQOutputStream.TERMINATION_COMMAND)) { + if (bytesFromMessage.length != RabbitOutputStream.TERMINATION_COMMAND.length + || !Arrays.equals(bytesFromMessage, RabbitOutputStream.TERMINATION_COMMAND)) { messagesAreWrittenToThis.write(bytesFromMessage); } else { // termination sequence received - stop receiving messages diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQOutputStream.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/RabbitOutputStream.java similarity index 90% rename from java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQOutputStream.java rename to java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/RabbitOutputStream.java index d5ab3135c04..2ab9a888d19 100755 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/AMPQOutputStream.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/RabbitOutputStream.java @@ -20,16 +20,16 @@ import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; *

* * Usage: call the constructor with a channel to a RabbitMQ connection. Then, extract the queue name assigned (see - * {@link #getQueueName()} and get it to the client. On the client, use {@link AMPQInputStream}, passing the queue name - * to the constructor. The client can then obtain an {@link InputStream} from the {@link AMPQInputStream} by calling - * {@link AMPQInputStream#getInputStream()} from which the content can be read. The client will receive EOF at the end + * {@link #getQueueName()} and get it to the client. On the client, use {@link RabbitInputStreamProvider}, passing the queue name + * to the constructor. The client can then obtain an {@link InputStream} from the {@link RabbitInputStreamProvider} by calling + * {@link RabbitInputStreamProvider#getInputStream()} from which the content can be read. The client will receive EOF at the end * of the stream. * * @author Simon Marcel Pamies * @author Axel Uhl (d043530) */ -public class AMPQOutputStream extends OutputStream { - private static final Logger logger = Logger.getLogger(AMPQOutputStream.class.getName()); +public class RabbitOutputStream extends OutputStream { + private static final Logger logger = Logger.getLogger(RabbitOutputStream.class.getName()); // FIXME this seems inherently unsafe as it is not properly escaped if it occurs in the actual stream static final byte TERMINATION_COMMAND[] = new byte[] { 2, 6, 0, 4, 1, 9, 8, 2, 0, 1, 4, 2 }; @@ -46,7 +46,7 @@ public class AMPQOutputStream extends OutputStream { private int count; private final byte streamBuffer[]; - public AMPQOutputStream(int messageSizeInBytes, Channel channel, boolean syncAfterTimeout) throws IOException { + public RabbitOutputStream(int messageSizeInBytes, Channel channel, boolean syncAfterTimeout) throws IOException { super(); this.streamBuffer = new byte[messageSizeInBytes]; this.count = 0; @@ -59,7 +59,7 @@ public class AMPQOutputStream extends OutputStream { @Override public void run() { while (!closed) { - synchronized (AMPQOutputStream.this) { + synchronized (RabbitOutputStream.this) { if (timeLastDataHasBeenReceived != null) { TimePoint now = MillisecondsTimePoint.now(); // FIXME the timing should better be managed by a Timer instance with delays based on last send and wake-up / test time point From 6a8501e5a274369062a926558d5c6b0bd9a58551 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 24 Jun 2014 13:37:18 +0200 Subject: [PATCH 016/323] made the RabbitOutputStream safe for sending the termination message on purpose in the payload --- .../impl/RabbitInputStreamProvider.java | 25 +++++++++++++------ .../replication/impl/RabbitOutputStream.java | 23 +++++++++++++++-- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/RabbitInputStreamProvider.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/RabbitInputStreamProvider.java index 8e70b3b9b02..caa2bbc84b2 100755 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/RabbitInputStreamProvider.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/RabbitInputStreamProvider.java @@ -4,7 +4,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; -import java.util.Arrays; import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; @@ -48,14 +47,24 @@ public class RabbitInputStreamProvider extends NamedImpl { try { Delivery delivery = messageConsumer.nextDelivery(); byte[] bytesFromMessage = delivery.getBody(); - if (bytesFromMessage.length != RabbitOutputStream.TERMINATION_COMMAND.length - || !Arrays.equals(bytesFromMessage, RabbitOutputStream.TERMINATION_COMMAND)) { - messagesAreWrittenToThis.write(bytesFromMessage); - } else { - // termination sequence received - stop receiving messages - messagesAreWrittenToThis.close(); - break; + if (RabbitOutputStream.startsWithTerminationCommand(bytesFromMessage, bytesFromMessage.length)) { + if (bytesFromMessage.length == RabbitOutputStream.TERMINATION_COMMAND.length) { + // received exactly TERMINATION_COMMAND + messagesAreWrittenToThis.close(); + break; + } else { + // more to come; skip one byte which is the "escape" symbol ensuring that the message + // sizes don't match + byte[] newBytesFromMessage = new byte[bytesFromMessage.length - 1]; + System.arraycopy(bytesFromMessage, 0, newBytesFromMessage, 0, + RabbitOutputStream.TERMINATION_COMMAND.length); + System.arraycopy(bytesFromMessage, RabbitOutputStream.TERMINATION_COMMAND.length + 1, + newBytesFromMessage, RabbitOutputStream.TERMINATION_COMMAND.length, + bytesFromMessage.length - RabbitOutputStream.TERMINATION_COMMAND.length - 1); + bytesFromMessage = newBytesFromMessage; + } } + messagesAreWrittenToThis.write(bytesFromMessage); } catch (ShutdownSignalException | ConsumerCancelledException e) { logger.log(Level.INFO, "Problem with message queue "+getName(), e); break; diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/RabbitOutputStream.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/RabbitOutputStream.java index 2ab9a888d19..f542399ddc9 100755 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/RabbitOutputStream.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/RabbitOutputStream.java @@ -31,7 +31,6 @@ import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; public class RabbitOutputStream extends OutputStream { private static final Logger logger = Logger.getLogger(RabbitOutputStream.class.getName()); - // FIXME this seems inherently unsafe as it is not properly escaped if it occurs in the actual stream static final byte TERMINATION_COMMAND[] = new byte[] { 2, 6, 0, 4, 1, 9, 8, 2, 0, 1, 4, 2 }; public static final Duration DURATION_AFTER_TO_SYNC_DATA_TO_CHANNEL_AS_MILLIS = new MillisecondsDurationImpl(5000); @@ -133,7 +132,15 @@ public class RabbitOutputStream extends OutputStream { private synchronized void sendBuffer() throws IOException { if (count > 0) { if (this.channel != null && this.channel.isOpen()) { - byte[] message = new byte[count]; + final byte[] message; + // check for the unlikely case that a client has coincidentally submitted the TERMINATION_COMMAND; we escape by + // appending a random byte which makes the length differ; the input stream provider will skip one byte after receiving + // the TERMINATION_COMMAND at the beginning of a message whose length is greater than that of the TERMINATION_COMMAND. + if (startsWithTerminationCommand(streamBuffer, count)) { + message = new byte[count+1]; + } else { + message = new byte[count]; + } System.arraycopy(streamBuffer, 0, message, 0, count); this.channel.basicPublish(/* exchangeName */"", /* routingKey */queueName, /* properties */null, message); count = 0; @@ -143,4 +150,16 @@ public class RabbitOutputStream extends OutputStream { } } } + + static boolean startsWithTerminationCommand(byte[] bytes, int count) { + boolean result = true; + if (count < TERMINATION_COMMAND.length) { + result = false; + } else { + for (int i = 0; i < TERMINATION_COMMAND.length && result; i++) { + result = bytes[i] == TERMINATION_COMMAND[i]; + } + } + return result; + } } From aaa6955faca80e3b6e0e6a774460b8339d6d7d97 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 24 Jun 2014 15:50:02 +0200 Subject: [PATCH 017/323] converting initial load to using RabbitOutputStream and RabbitInputStream --- .../test/AbstractServerReplicationTest.java | 13 +++++++--- .../ReplicationMasterDescriptor.java | 6 +++++ .../replication/ReplicationService.java | 4 ++++ .../impl/ReplicationMasterDescriptorImpl.java | 23 +++++++++++------- .../impl/ReplicationServiceImpl.java | 24 +++++++++++++------ .../replication/impl/ReplicationServlet.java | 18 +++++++++++++- 6 files changed, 68 insertions(+), 20 deletions(-) diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractServerReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractServerReplicationTest.java index 5ae6434616d..e8489a70f50 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractServerReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractServerReplicationTest.java @@ -1,5 +1,7 @@ package com.sap.sailing.server.replication.test; +import static org.junit.Assert.assertFalse; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -23,8 +25,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.rules.Timeout; -import static org.junit.Assert.assertFalse; - +import com.rabbitmq.client.Channel; import com.rabbitmq.client.QueueingConsumer; import com.sap.sailing.domain.base.DomainFactory; import com.sap.sailing.domain.base.impl.DomainFactoryImpl; @@ -39,6 +40,7 @@ import com.sap.sailing.server.impl.RacingEventServiceImpl; import com.sap.sailing.server.replication.ReplicationMasterDescriptor; import com.sap.sailing.server.replication.ReplicationService; import com.sap.sailing.server.replication.impl.Activator; +import com.sap.sailing.server.replication.impl.RabbitOutputStream; import com.sap.sailing.server.replication.impl.ReplicaDescriptor; import com.sap.sailing.server.replication.impl.ReplicationInstancesManager; import com.sap.sailing.server.replication.impl.ReplicationMasterDescriptorImpl; @@ -165,6 +167,7 @@ public abstract class AbstractServerReplicationTest { } static class ReplicationServiceTestImpl extends ReplicationServiceImpl { + protected static final int INITIAL_LOAD_PACKAGE_SIZE = 1024*1024; private final DomainFactory resolveAgainst; private final RacingEventService master; private final ReplicaDescriptor replicaDescriptor; @@ -214,10 +217,14 @@ public abstract class AbstractServerReplicationTest { registerReplicaUuidForMaster(uuid, masterDescriptor); pw.print(uuid.getBytes()); } else if (request.contains("INITIAL_LOAD")) { - final GZIPOutputStream gzipOutputStream = new GZIPOutputStream(s.getOutputStream()); + Channel channel = masterReplicationService.createMasterChannel(); + RabbitOutputStream ros = new RabbitOutputStream(INITIAL_LOAD_PACKAGE_SIZE, channel, /* syncAfterTimeout */ false); + pw.println(ros.getQueueName().getBytes()); + final GZIPOutputStream gzipOutputStream = new GZIPOutputStream(ros); final ObjectOutputStream oos = new ObjectOutputStream(gzipOutputStream); master.serializeForInitialReplication(oos); gzipOutputStream.finish(); + ros.close(); } else if (request.contains("STOP")) { stop = true; } diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/ReplicationMasterDescriptor.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/ReplicationMasterDescriptor.java index 4b4305ccf03..977834d00a5 100644 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/ReplicationMasterDescriptor.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/ReplicationMasterDescriptor.java @@ -6,6 +6,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.UUID; +import com.rabbitmq.client.Channel; import com.rabbitmq.client.QueueingConsumer; /** @@ -40,4 +41,9 @@ public interface ReplicationMasterDescriptor { void stopConnection(); String getMessagingHostname(); + + /** + * @return a RabbitMQ channel created with the replication connectivity parameters defined by this descriptor + */ + Channel createChannel() throws IOException; } diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/ReplicationService.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/ReplicationService.java index 74992a0dfa3..6481da2da5d 100644 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/ReplicationService.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/ReplicationService.java @@ -1,9 +1,11 @@ package com.sap.sailing.server.replication; import java.io.IOException; +import java.net.ConnectException; import java.util.Map; import java.util.UUID; +import com.rabbitmq.client.Channel; import com.sap.sailing.server.RacingEventServiceOperation; import com.sap.sailing.server.replication.impl.ReplicaDescriptor; import com.sap.sailing.server.replication.impl.ReplicationServlet; @@ -64,4 +66,6 @@ public interface ReplicationService { * @return */ UUID getServerIdentifier(); + + Channel createMasterChannel() throws IOException, ConnectException; } diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationMasterDescriptorImpl.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationMasterDescriptorImpl.java index 712968904c1..008454cd8f6 100755 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationMasterDescriptorImpl.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationMasterDescriptorImpl.java @@ -62,15 +62,7 @@ public class ReplicationMasterDescriptorImpl implements ReplicationMasterDescrip @Override public synchronized QueueingConsumer getConsumer() throws IOException { - ConnectionFactory connectionFactory = new ConnectionFactory(); - connectionFactory.setHost(getMessagingHostname()); - int port = getMessagingPort(); - if (port != 0) { - connectionFactory.setPort(port); - } - Connection connection = connectionFactory.newConnection(); - Channel channel = connection.createChannel(); - + Channel channel = createChannel(); /* * Connect a queue to the given exchange that has already * been created by the master server. @@ -116,6 +108,19 @@ public class ReplicationMasterDescriptorImpl implements ReplicationMasterDescrip this.consumer = consumer; return consumer; } + + @Override + public Channel createChannel() throws IOException { + ConnectionFactory connectionFactory = new ConnectionFactory(); + connectionFactory.setHost(getMessagingHostname()); + int port = getMessagingPort(); + if (port != 0) { + connectionFactory.setPort(port); + } + Connection connection = connectionFactory.newConnection(); + Channel channel = connection.createChannel(); + return channel; + } @Override public synchronized void stopConnection() { diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java index 457e1d25ab3..fc376385125 100644 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java @@ -1,8 +1,10 @@ package com.sap.sailing.server.replication.impl; +import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.ConnectException; @@ -165,10 +167,17 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec Activator.getDefaultContext(), RacingEventService.class.getName(), null); } - private Channel createMasterChannel(String exchangeName, String exchangeHost) throws IOException { + private Channel createMasterChannelAndDeclareFanoutExchange() throws IOException { + Channel result = createMasterChannel(); + result.exchangeDeclare(exchangeName, "fanout"); + logger.info("Created fanout exchange "+exchangeName+" successfully."); + return result; + } + + @Override + public Channel createMasterChannel() throws IOException, ConnectException { final ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost(exchangeHost); // ...and use default port - Channel result = null; try { result = connectionFactory.newConnection().createChannel(); @@ -177,9 +186,7 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec logger.severe("Could not connect to messaging queue on " + connectionFactory.getHost() + ":" + connectionFactory.getPort() + "/" + exchangeName); throw ex; } - - logger.info("Connected to " + connectionFactory.getHost() + ":" + connectionFactory.getPort() + "/" + exchangeName); - result.exchangeDeclare(exchangeName, "fanout"); + logger.info("Connected to " + connectionFactory.getHost() + ":" + connectionFactory.getPort()); return result; } @@ -200,7 +207,7 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec addAsListenerToRacingEventService(); synchronized (this) { if (masterChannel == null) { - masterChannel = createMasterChannel(exchangeName, exchangeHost); + masterChannel = createMasterChannelAndDeclareFanoutExchange(); } } } @@ -322,8 +329,11 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec replicatorThread.start(); logger.info("Started replicator thread"); InputStream is = initialLoadURL.openStream(); + String queueName = new BufferedReader(new InputStreamReader(is)).readLine(); + RabbitInputStreamProvider rabbitInputStreamProvider = new RabbitInputStreamProvider(master.createChannel(), queueName); final RacingEventService racingEventService = getRacingEventService(); - ObjectInputStream ois = racingEventService.getBaseDomainFactory().createObjectInputStreamResolvingAgainstThisFactory(new GZIPInputStream(is)); + ObjectInputStream ois = racingEventService.getBaseDomainFactory().createObjectInputStreamResolvingAgainstThisFactory(new GZIPInputStream( + rabbitInputStreamProvider.getInputStream())); logger.info("Starting to receive initial load"); racingEventService.initiallyFillFrom(ois); logger.info("Done receiving initial load"); diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServlet.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServlet.java index 0b6c53841c3..25f388a4693 100755 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServlet.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServlet.java @@ -2,6 +2,8 @@ package com.sap.sailing.server.replication.impl; import java.io.IOException; import java.io.ObjectOutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Arrays; @@ -17,6 +19,9 @@ import javax.servlet.http.HttpServletResponse; import org.osgi.framework.BundleContext; import org.osgi.util.tracker.ServiceTracker; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; import com.sap.sailing.server.RacingEventService; import com.sap.sailing.server.gateway.SailingServerHttpServlet; import com.sap.sailing.server.replication.ReplicationService; @@ -39,6 +44,11 @@ public class ReplicationServlet extends SailingServerHttpServlet { public static final String SERVER_UUID = "uuid"; public static final String ADDITIONAL_INFORMATION = "additional"; + /** + * The size of the packages into which the initial load is split + */ + private static final int INITIAL_LOAD_PACKAGE_SIZE = 1024*1024; + private ServiceTracker replicationServiceTracker; public ReplicationServlet() throws Exception { @@ -69,8 +79,13 @@ public class ReplicationServlet extends SailingServerHttpServlet { deregisterClientWithReplicationService(req, resp); break; case INITIAL_LOAD: + Connection rabbitConnection = new ConnectionFactory().newConnection(); + Channel channel = rabbitConnection.createChannel(); + RabbitOutputStream ros = new RabbitOutputStream(INITIAL_LOAD_PACKAGE_SIZE, channel, /* syncAfterTimeout */ false); + PrintWriter br = new PrintWriter(new OutputStreamWriter(resp.getOutputStream())); + br.println(ros.getQueueName().getBytes()); final GZIPOutputStream gzipOutputStream = new GZIPOutputStream(new CountingOutputStream( - resp.getOutputStream(), /* log every megabyte */1024l * 1024l, Level.INFO, + ros, /* log every megabyte */1024l * 1024l, Level.INFO, "HTTP output for initial load for " + req.getRemoteHost())); ObjectOutputStream oos = new ObjectOutputStream(gzipOutputStream); try { @@ -82,6 +97,7 @@ public class ReplicationServlet extends SailingServerHttpServlet { resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); e.printStackTrace(resp.getWriter()); } + ros.close(); break; default: resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Action " + action + " not understood. Must be one of " From e090c78f7fd30786661cf076531e85e5b2a4c6ea Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 24 Jun 2014 23:33:14 +0200 Subject: [PATCH 018/323] pass queue name to RabbitOutputStream; letting the server choose a queue name only works for exclusive queues which is not what we need here --- .../replication/test/AbstractServerReplicationTest.java | 5 +++-- .../server/replication/test/RemoteMessageStreamTest.java | 3 ++- .../sailing/server/replication/impl/RabbitOutputStream.java | 5 +++-- .../sailing/server/replication/impl/ReplicationServlet.java | 3 ++- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractServerReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractServerReplicationTest.java index e8489a70f50..eea7e7785b2 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractServerReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractServerReplicationTest.java @@ -218,8 +218,9 @@ public abstract class AbstractServerReplicationTest { pw.print(uuid.getBytes()); } else if (request.contains("INITIAL_LOAD")) { Channel channel = masterReplicationService.createMasterChannel(); - RabbitOutputStream ros = new RabbitOutputStream(INITIAL_LOAD_PACKAGE_SIZE, channel, /* syncAfterTimeout */ false); - pw.println(ros.getQueueName().getBytes()); + RabbitOutputStream ros = new RabbitOutputStream(INITIAL_LOAD_PACKAGE_SIZE, channel, + /* queueName */ "initial-load-for-TestClient", /* syncAfterTimeout */ false); + pw.println(ros.getQueueName()); final GZIPOutputStream gzipOutputStream = new GZIPOutputStream(ros); final ObjectOutputStream oos = new ObjectOutputStream(gzipOutputStream); master.serializeForInitialReplication(oos); diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RemoteMessageStreamTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RemoteMessageStreamTest.java index 4e18fb1268e..831eec665ab 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RemoteMessageStreamTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RemoteMessageStreamTest.java @@ -11,6 +11,7 @@ import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.UUID; import org.junit.Before; import org.junit.Test; @@ -126,7 +127,7 @@ public class RemoteMessageStreamTest { private void setupStreams(int messageSizeInBytes, boolean syncAfterTimeout) throws IOException { - outputStream = new RabbitOutputStream(messageSizeInBytes, channel, syncAfterTimeout); + outputStream = new RabbitOutputStream(messageSizeInBytes, channel, UUID.randomUUID().toString(), syncAfterTimeout); inputStream = new RabbitInputStreamProvider(channel, outputStream.getQueueName()); } } diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/RabbitOutputStream.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/RabbitOutputStream.java index f542399ddc9..9415e8b6227 100755 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/RabbitOutputStream.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/RabbitOutputStream.java @@ -45,12 +45,13 @@ public class RabbitOutputStream extends OutputStream { private int count; private final byte streamBuffer[]; - public RabbitOutputStream(int messageSizeInBytes, Channel channel, boolean syncAfterTimeout) throws IOException { + public RabbitOutputStream(int messageSizeInBytes, Channel channel, String queueName, boolean syncAfterTimeout) throws IOException { super(); this.streamBuffer = new byte[messageSizeInBytes]; this.count = 0; this.channel = channel; - this.queueName = channel.queueDeclare().getQueue(); + this.queueName = channel.queueDeclare(queueName, /* durable */ false, /* exclusive */ false, /* autoDelete */ false, + /* args */ null).getQueue(); this.closed = false; if (syncAfterTimeout) { diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServlet.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServlet.java index 25f388a4693..037abb28493 100755 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServlet.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServlet.java @@ -81,7 +81,8 @@ public class ReplicationServlet extends SailingServerHttpServlet { case INITIAL_LOAD: Connection rabbitConnection = new ConnectionFactory().newConnection(); Channel channel = rabbitConnection.createChannel(); - RabbitOutputStream ros = new RabbitOutputStream(INITIAL_LOAD_PACKAGE_SIZE, channel, /* syncAfterTimeout */ false); + RabbitOutputStream ros = new RabbitOutputStream(INITIAL_LOAD_PACKAGE_SIZE, channel, + /* queueName */ "initialLoad-for-"+req.getRemoteHost(), /* syncAfterTimeout */ false); PrintWriter br = new PrintWriter(new OutputStreamWriter(resp.getOutputStream())); br.println(ros.getQueueName().getBytes()); final GZIPOutputStream gzipOutputStream = new GZIPOutputStream(new CountingOutputStream( From a9c1c147b28b6dea1404b61db844bd7a53d6cb4e Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 26 Jun 2014 15:53:12 +0200 Subject: [PATCH 019/323] use a unique test queue name for each initial load test --- .../server/replication/test/AbstractServerReplicationTest.java | 2 +- .../com/sap/sailing/server/replication/impl/Replicator.java | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractServerReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractServerReplicationTest.java index eea7e7785b2..dc671a0fae1 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractServerReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractServerReplicationTest.java @@ -219,7 +219,7 @@ public abstract class AbstractServerReplicationTest { } else if (request.contains("INITIAL_LOAD")) { Channel channel = masterReplicationService.createMasterChannel(); RabbitOutputStream ros = new RabbitOutputStream(INITIAL_LOAD_PACKAGE_SIZE, channel, - /* queueName */ "initial-load-for-TestClient", /* syncAfterTimeout */ false); + /* queueName */ "initial-load-for-TestClient-"+UUID.randomUUID(), /* syncAfterTimeout */ false); pw.println(ros.getQueueName()); final GZIPOutputStream gzipOutputStream = new GZIPOutputStream(ros); final ObjectOutputStream oos = new ObjectOutputStream(gzipOutputStream); diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java index 87d7d8389a5..9f866006f42 100755 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java @@ -116,7 +116,6 @@ public class Replicator implements Runnable { _queue = null; } while (!isBeingStopped()) { - try { Delivery delivery = consumer.nextDelivery(); messageCount++; @@ -157,12 +156,10 @@ public class Replicator implements Runnable { if (isBeingStopped()) { break; } - if (sse.isInitiatedByApplication()) { logger.severe("Application shut down messaging queue for " + this.toString()); break; } - logger.info(sse.getMessage()); if (checksPerformed <= CHECK_COUNT) { try { From df49ce2d04edd4f5b3e4c2727ed7c62d86b42294 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 26 Jun 2014 17:04:15 +0200 Subject: [PATCH 020/323] added missing rabbit dependency to gwt.ui.test --- java/com.sap.sailing.gwt.ui.test/META-INF/MANIFEST.MF | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sailing.gwt.ui.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.gwt.ui.test/META-INF/MANIFEST.MF index bad1c3b6fe0..55ec275d144 100755 --- a/java/com.sap.sailing.gwt.ui.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.gwt.ui.test/META-INF/MANIFEST.MF @@ -12,7 +12,8 @@ Require-Bundle: com.sap.sailing.server, org.objenesis;bundle-version="1.3.0", org.hamcrest;bundle-version="1.1.0", com.sap.sailing.domain.igtimiadapter.persistence;bundle-version="1.0.0", - com.sap.sailing.domain.racelogtrackingadapter.testsupport;bundle-version="1.0.0" + com.sap.sailing.domain.racelogtrackingadapter.testsupport;bundle-version="1.0.0", + com.rabbitmq.client;bundle-version="2.8.4" Bundle-ClassPath: . Import-Package: com.sap.sailing.domain.racelogtracking.impl, javax.jms;version="1.1.0", From 7b7515a5914cdb7081721d63e22573747adf8ee4 Mon Sep 17 00:00:00 2001 From: fmittag Date: Sun, 29 Jun 2014 21:27:30 +0200 Subject: [PATCH 021/323] addded language selection --- .../gwt/home/client/shared/footer/Footer.java | 43 ++++++++++++++++--- .../home/client/shared/footer/Footer.ui.xml | 2 +- 2 files changed, 37 insertions(+), 8 deletions(-) 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 c052e43b701..2cd34270ae7 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 @@ -1,12 +1,18 @@ package com.sap.sailing.gwt.home.client.shared.footer; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.SpanElement; import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.http.client.UrlBuilder; import com.google.gwt.i18n.client.LocaleInfo; 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.Window.Location; import com.google.gwt.user.client.ui.Anchor; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Widget; @@ -15,24 +21,47 @@ public class Footer extends Composite { private static FooterPanelUiBinder uiBinder = GWT.create(FooterPanelUiBinder.class); @UiField Anchor changeLanguageLink; - @UiField SpanElement currentLanguage; - - private LocaleInfo currentLocale; + private final Map localeAndLanguages; + + /** set-configuration-property name='locale.searchorder' value='queryparam,cookie,useragent' /> */ + interface FooterPanelUiBinder extends UiBinder { } public Footer() { + localeAndLanguages = new HashMap(); + localeAndLanguages.put("default", "English"); + localeAndLanguages.put("de", "Deutsch"); + FooterResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); - currentLocale = LocaleInfo.getCurrentLocale(); - - currentLanguage.setInnerHTML(currentLocale.getLocaleName()); + updateUI(); } @UiHandler("changeLanguageLink") public void changeLanguage(ClickEvent e) { + String selectedLanguage = changeLanguageLink.getText(); + String selectedLocale = null; + for (Entry entry: localeAndLanguages.entrySet()) { + if (entry.getValue().equals(selectedLanguage)) { + selectedLocale = entry.getKey(); + } + } + if(selectedLocale != null) { + UrlBuilder builder = Location.createUrlBuilder().setParameter("locale", selectedLocale); + Window.Location.replace(builder.buildString()); + } } + + private void updateUI() { + LocaleInfo currentLocale = LocaleInfo.getCurrentLocale(); + HashMap languagesToSelectFrom = new HashMap(localeAndLanguages); + languagesToSelectFrom.remove(currentLocale.getLocaleName()); + if(languagesToSelectFrom.size() == 1) { + changeLanguageLink.setText(languagesToSelectFrom.values().iterator().next()); + } + } } 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 de21ed59bf7..3baa3c934b0 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 @@ -20,7 +20,7 @@

- :
+ : From f91e6ddb458749031dc1c51cb21f378688b75e51 Mon Sep 17 00:00:00 2001 From: fmittag Date: Tue, 1 Jul 2014 23:26:47 +0200 Subject: [PATCH 022/323] some preparation work of regatta races page --- ...letAndDesktopApplicationClientFactory.java | 2 +- .../event/TabletAndDesktopEventView.java | 37 ++++++++++++++++++- .../regattaschedule/EventRegattaSchedule.java | 14 +++++++ .../EventRegattaSchedule.ui.xml | 20 +++------- .../EventRegattaScheduleFleet.java | 26 +++++++++++++ .../EventRegattaScheduleFleet.ui.xml | 16 ++++++++ .../EventRegattaScheduleRace.java | 26 +++++++++++++ .../EventRegattaScheduleRace.ui.xml | 15 ++++++++ .../EventRegattaScheduleSeries.java | 26 +++++++++++++ .../EventRegattaScheduleSeries.ui.xml | 14 +++++++ .../sailing/gwt/ui/client/SailingService.java | 4 +- .../gwt/ui/client/SailingServiceAsync.java | 5 ++- .../gwt/ui/server/SailingServiceImpl.java | 14 ++++++- 13 files changed, 198 insertions(+), 21 deletions(-) create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.ui.xml create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.java create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.ui.xml create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.java create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.ui.xml 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 4b84d46c542..2c9da593075 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 @@ -36,7 +36,7 @@ public class TabletAndDesktopApplicationClientFactory extends AbstractApplicatio @Override public EventView createEventView(EventDTO event) { - return new TabletAndDesktopEventView(event); + return new TabletAndDesktopEventView(getSailingService(), event); } @Override 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 bb4743c75e0..2609d7c60f5 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,8 +6,11 @@ import java.util.List; 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.Window; +import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.LeaderboardType; 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; @@ -15,7 +18,9 @@ import com.sap.sailing.gwt.home.client.place.event.regattalist.EventRegattaList; import com.sap.sailing.gwt.home.client.place.event.regattaschedule.EventRegattaSchedule; import com.sap.sailing.gwt.home.client.place.event.schedule.EventSchedule; import com.sap.sailing.gwt.home.client.shared.eventsponsors.EventSponsors; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; public class TabletAndDesktopEventView extends Composite implements EventView, EventPageNavigator { @@ -35,7 +40,10 @@ public class TabletAndDesktopEventView extends Composite implements EventView, E private final List pageElements; - public TabletAndDesktopEventView(EventDTO event) { + private final SailingServiceAsync sailingService; + + public TabletAndDesktopEventView(SailingServiceAsync sailingService, EventDTO event) { + this.sailingService = sailingService; eventHeader = new EventHeader(event, this); eventRegattaList = new EventRegattaList(event, this); eventRegattaSchedule = new EventRegattaSchedule(event, this); @@ -63,7 +71,32 @@ public class TabletAndDesktopEventView extends Composite implements EventView, E @Override public void goToRegattaRaces(StrippedLeaderboardDTO leaderboard) { - setVisibleEventElement(eventRegattaSchedule); + switch (leaderboard.type) { + case RegattaLeaderboard: + sailingService.getRegattaByName(leaderboard.regattaName, new AsyncCallback() { + @Override + public void onSuccess(RegattaDTO regatta) { + eventRegattaSchedule.setRacesFromRegatta(regatta); + setVisibleEventElement(eventRegattaSchedule); + } + + @Override + public void onFailure(Throwable caught) { + Window.alert("Shit happens"); + } + }); + break; + case FlexibleLeaderboard: + eventRegattaSchedule.setRacesFromFlexibleLeaderboard(leaderboard); + setVisibleEventElement(eventRegattaSchedule); + break; + case FlexibleMetaLeaderboard: + setVisibleEventElement(eventRegattaSchedule); + break; + case RegattaMetaLeaderboard: + setVisibleEventElement(eventRegattaSchedule); + break; + } } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.java index 4d2a2a451bd..646a8b02fd7 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.java @@ -1,11 +1,15 @@ package com.sap.sailing.gwt.home.client.place.event.regattaschedule; import com.google.gwt.core.client.GWT; +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.sap.sailing.gwt.home.client.place.event.EventPageNavigator; import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sailing.gwt.ui.shared.RegattaDTO; +import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; public class EventRegattaSchedule extends Composite { private static EventRegattaScheduleUiBinder uiBinder = GWT.create(EventRegattaScheduleUiBinder.class); @@ -13,6 +17,8 @@ public class EventRegattaSchedule extends Composite { interface EventRegattaScheduleUiBinder extends UiBinder { } + @UiField SpanElement regattaName; + @SuppressWarnings("unused") private final EventDTO event; @@ -23,4 +29,12 @@ public class EventRegattaSchedule extends Composite { initWidget(uiBinder.createAndBindUi(this)); } + public void setRacesFromRegatta(RegattaDTO regatta) { + regattaName.setInnerHTML(regatta.getName()); + } + + public void setRacesFromFlexibleLeaderboard(StrippedLeaderboardDTO flexibleLeaderboard) { + + } + } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.ui.xml index 30ad210e8ad..598722ca7f9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.ui.xml @@ -6,25 +6,15 @@ - - -

Races of regatta xyz

- + -->
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java new file mode 100644 index 00000000000..7b1f50fa244 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java @@ -0,0 +1,26 @@ +package com.sap.sailing.gwt.home.client.place.event.regattaschedule; + +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.EventPageNavigator; +import com.sap.sailing.gwt.ui.shared.EventDTO; + +public class EventRegattaScheduleFleet extends Composite { + private static EventRegattaScheduleSeriesUiBinder uiBinder = GWT.create(EventRegattaScheduleSeriesUiBinder.class); + + interface EventRegattaScheduleSeriesUiBinder extends UiBinder { + } + + @SuppressWarnings("unused") + private final EventDTO event; + + public EventRegattaScheduleFleet(EventDTO event, EventPageNavigator pageNavigator) { + this.event = event; + + EventRegattaScheduleResources.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/regattaschedule/EventRegattaScheduleFleet.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.ui.xml new file mode 100644 index 00000000000..4ab800c9c9f --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.ui.xml @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.java new file mode 100644 index 00000000000..8e6908398a4 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.java @@ -0,0 +1,26 @@ +package com.sap.sailing.gwt.home.client.place.event.regattaschedule; + +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.EventPageNavigator; +import com.sap.sailing.gwt.ui.shared.EventDTO; + +public class EventRegattaScheduleRace extends Composite { + private static EventRegattaScheduleSeriesUiBinder uiBinder = GWT.create(EventRegattaScheduleSeriesUiBinder.class); + + interface EventRegattaScheduleSeriesUiBinder extends UiBinder { + } + + @SuppressWarnings("unused") + private final EventDTO event; + + public EventRegattaScheduleRace(EventDTO event, EventPageNavigator pageNavigator) { + this.event = event; + + EventRegattaScheduleResources.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/regattaschedule/EventRegattaScheduleRace.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.ui.xml new file mode 100644 index 00000000000..d19e6ca5d0f --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.ui.xml @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.java new file mode 100644 index 00000000000..b254f81e7dd --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.java @@ -0,0 +1,26 @@ +package com.sap.sailing.gwt.home.client.place.event.regattaschedule; + +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.EventPageNavigator; +import com.sap.sailing.gwt.ui.shared.EventDTO; + +public class EventRegattaScheduleSeries extends Composite { + private static EventRegattaScheduleSeriesUiBinder uiBinder = GWT.create(EventRegattaScheduleSeriesUiBinder.class); + + interface EventRegattaScheduleSeriesUiBinder extends UiBinder { + } + + @SuppressWarnings("unused") + private final EventDTO event; + + public EventRegattaScheduleSeries(EventDTO event, EventPageNavigator pageNavigator) { + this.event = event; + + EventRegattaScheduleResources.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/regattaschedule/EventRegattaScheduleSeries.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.ui.xml new file mode 100644 index 00000000000..387b0f02900 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.ui.xml @@ -0,0 +1,14 @@ + + + + + + + +
+

{series.name}

{series.date}{#each fleet in series.trackedRaces.fleets}{/each series.trackedRaces.fleets} +
+
+
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 f549e40196e..9a0649829c1 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 @@ -97,6 +97,8 @@ public interface SailingService extends RemoteService { List getRegattas(); + RegattaDTO getRegattaByName(String regattaName); + List getEvents() throws Exception; List getPublicEventsOfAllSailingServers() throws Exception; @@ -517,5 +519,5 @@ public interface SailingService extends RemoteService { /** * @return The RaceDTO of the modified race or null, if the given newStartTimeReceived was null. */ - RaceDTO setStartTimeReceivedForRace(RaceIdentifier raceIdentifier, Date newStartTimeReceived); + RaceDTO setStartTimeReceivedForRace(RaceIdentifier raceIdentifier, Date newStartTimeReceived); } 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 396fcf0c495..2caebc96939 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 @@ -84,6 +84,9 @@ public interface SailingServiceAsync { void getRegattas(AsyncCallback> callback); + void getRegattaByName(String regattaName, AsyncCallback asyncCallback); + + /** * The string returned in the callback's pair is the common event name * @param listHiddenRaces @@ -478,7 +481,7 @@ public interface SailingServiceAsync { void getBuildVersion(AsyncCallback callback); void stopReplicatingFromMaster(AsyncCallback asyncCallback); - + void getRegattaStructureForEvent(UUID eventId, AsyncCallback> asyncCallback); void getRaceStateEntriesForRaceGroup(UUID eventId, List visibleCourseAreas, 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 671022d2f73..6afeb34df3d 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 @@ -733,7 +733,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public List getRegattas() throws IllegalArgumentException { + public List getRegattas() { List result = new ArrayList(); for (Regatta regatta : getService().getAllRegattas()) { result.add(convertToRegattaDTO(regatta)); @@ -741,6 +741,18 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S return result; } + @Override + public RegattaDTO getRegattaByName(String regattaName) { + RegattaDTO result = null; + if (regattaName != null && !regattaName.isEmpty()) { + Regatta regatta = getService().getRegatta(new RegattaName(regattaName)); + if (regatta != null) { + result = convertToRegattaDTO(regatta); + } + } + return result; + } + private MarkDTO convertToMarkDTO(Mark mark, Position position) { MarkDTO markDTO; if(position != null) { From ff5d5456c1dfdc9b0865002877b71109f20fd584 Mon Sep 17 00:00:00 2001 From: fmittag Date: Wed, 2 Jul 2014 11:28:29 +0200 Subject: [PATCH 023/323] implemented regatta/series/fleet/race structure on regatta races page (draft version) --- .../event/TabletAndDesktopEventView.java | 1 - .../event/regattalist/EventRegattaList.ui.xml | 3 +- .../regattaschedule/EventRegattaSchedule.java | 8 +++++ .../EventRegattaSchedule.ui.xml | 4 +-- .../EventRegattaScheduleFleet.java | 32 ++++++++++++++++--- .../EventRegattaScheduleFleet.ui.xml | 10 +++--- .../EventRegattaScheduleRace.java | 28 +++++++++++++--- .../EventRegattaScheduleRace.ui.xml | 9 +++--- .../EventRegattaScheduleSeries.java | 26 ++++++++++++--- .../EventRegattaScheduleSeries.ui.xml | 9 +++--- 10 files changed, 94 insertions(+), 36 deletions(-) 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 2609d7c60f5..5bff2d948df 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 @@ -10,7 +10,6 @@ import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.domain.common.LeaderboardType; 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; 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 46e589e0cbb..d99e54e4381 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 @@ -7,8 +7,7 @@
- - +
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.java index 646a8b02fd7..6542ddd904b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.java @@ -5,10 +5,12 @@ 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.HTMLPanel; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.home.client.place.event.EventPageNavigator; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO; +import com.sap.sailing.gwt.ui.shared.SeriesDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; public class EventRegattaSchedule extends Composite { @@ -18,6 +20,7 @@ public class EventRegattaSchedule extends Composite { } @UiField SpanElement regattaName; + @UiField HTMLPanel seriesListPanel; @SuppressWarnings("unused") private final EventDTO event; @@ -31,6 +34,11 @@ public class EventRegattaSchedule extends Composite { public void setRacesFromRegatta(RegattaDTO regatta) { regattaName.setInnerHTML(regatta.getName()); + + for(SeriesDTO series: regatta.series) { + EventRegattaScheduleSeries eventRegattaScheduleSeries = new EventRegattaScheduleSeries(regatta, series); + seriesListPanel.add(eventRegattaScheduleSeries); + } } public void setRacesFromFlexibleLeaderboard(StrippedLeaderboardDTO flexibleLeaderboard) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.ui.xml index 598722ca7f9..ea9bffdf2f2 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.ui.xml @@ -7,14 +7,12 @@

Races of Regatta

-
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java index 7b1f50fa244..e7d20570e51 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java @@ -1,11 +1,16 @@ package com.sap.sailing.gwt.home.client.place.event.regattaschedule; import com.google.gwt.core.client.GWT; +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.HTMLPanel; import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.gwt.home.client.place.event.EventPageNavigator; -import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sailing.domain.common.dto.FleetDTO; +import com.sap.sailing.domain.common.dto.RaceColumnDTO; +import com.sap.sailing.gwt.ui.shared.RegattaDTO; +import com.sap.sailing.gwt.ui.shared.SeriesDTO; public class EventRegattaScheduleFleet extends Composite { private static EventRegattaScheduleSeriesUiBinder uiBinder = GWT.create(EventRegattaScheduleSeriesUiBinder.class); @@ -14,13 +19,30 @@ public class EventRegattaScheduleFleet extends Composite { } @SuppressWarnings("unused") - private final EventDTO event; + private final RegattaDTO regatta; + @SuppressWarnings("unused") + private final SeriesDTO series; + + @SuppressWarnings("unused") + private final FleetDTO fleet; - public EventRegattaScheduleFleet(EventDTO event, EventPageNavigator pageNavigator) { - this.event = event; + @UiField SpanElement fleetName; + @UiField HTMLPanel racesListPanel; + + public EventRegattaScheduleFleet(RegattaDTO regatta, SeriesDTO series, FleetDTO fleet) { + this.regatta = regatta; + this.series = series; + this.fleet = fleet; EventRegattaScheduleResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); + + fleetName.setInnerText(fleet.getName()); + + for(RaceColumnDTO raceColumn: series.getRaceColumns()) { + EventRegattaScheduleRace eventRegattaScheduleRace = new EventRegattaScheduleRace(fleet, raceColumn); + racesListPanel.add(eventRegattaScheduleRace); + } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.ui.xml index 4ab800c9c9f..85494614f29 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.ui.xml @@ -6,11 +6,9 @@ - +
+
+ +
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.java index 8e6908398a4..dab7aa932df 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.java @@ -1,11 +1,14 @@ package com.sap.sailing.gwt.home.client.place.event.regattaschedule; import com.google.gwt.core.client.GWT; +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.sap.sailing.gwt.home.client.place.event.EventPageNavigator; -import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sailing.domain.common.dto.FleetDTO; +import com.sap.sailing.domain.common.dto.RaceColumnDTO; +import com.sap.sailing.domain.common.dto.RaceDTO; public class EventRegattaScheduleRace extends Composite { private static EventRegattaScheduleSeriesUiBinder uiBinder = GWT.create(EventRegattaScheduleSeriesUiBinder.class); @@ -14,13 +17,28 @@ public class EventRegattaScheduleRace extends Composite { } @SuppressWarnings("unused") - private final EventDTO event; + private final FleetDTO fleet; + @SuppressWarnings("unused") + private final RaceColumnDTO raceColumn; - public EventRegattaScheduleRace(EventDTO event, EventPageNavigator pageNavigator) { - this.event = event; + @UiField SpanElement raceName; + @UiField SpanElement raceDetails; + + public EventRegattaScheduleRace(FleetDTO fleet, RaceColumnDTO raceColumn) { + this.fleet = fleet; + this.raceColumn = raceColumn; EventRegattaScheduleResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); + + raceName.setInnerText(raceColumn.getName()); + + RaceDTO race = raceColumn.getRace(fleet); + if(race != null) { + raceDetails.setInnerText(race.isTracked ? "tracked" : "untracked"); + } else { + raceDetails.setInnerText("no race"); + } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.ui.xml index d19e6ca5d0f..741e3e6d26f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.ui.xml @@ -6,10 +6,9 @@ - + +
+
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.java index b254f81e7dd..231644caa40 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.java @@ -1,11 +1,15 @@ package com.sap.sailing.gwt.home.client.place.event.regattaschedule; import com.google.gwt.core.client.GWT; +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.HTMLPanel; import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.gwt.home.client.place.event.EventPageNavigator; -import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sailing.domain.common.dto.FleetDTO; +import com.sap.sailing.gwt.ui.shared.RegattaDTO; +import com.sap.sailing.gwt.ui.shared.SeriesDTO; public class EventRegattaScheduleSeries extends Composite { private static EventRegattaScheduleSeriesUiBinder uiBinder = GWT.create(EventRegattaScheduleSeriesUiBinder.class); @@ -14,13 +18,25 @@ public class EventRegattaScheduleSeries extends Composite { } @SuppressWarnings("unused") - private final EventDTO event; + private final RegattaDTO regatta; - public EventRegattaScheduleSeries(EventDTO event, EventPageNavigator pageNavigator) { - this.event = event; + @UiField SpanElement seriesName; + @UiField SpanElement seriesDate; + @UiField HTMLPanel fleetsListPanel; + + public EventRegattaScheduleSeries(RegattaDTO regatta, SeriesDTO series) { + this.regatta = regatta; EventRegattaScheduleResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); + + seriesName.setInnerText(series.getName()); + seriesDate.setInnerText("A Date of the series???"); + + for(FleetDTO fleet: series.getFleets()) { + EventRegattaScheduleFleet eventRegattaScheduleFleet = new EventRegattaScheduleFleet(regatta, series, fleet); + fleetsListPanel.add(eventRegattaScheduleFleet); + } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.ui.xml index 387b0f02900..8298091c965 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.ui.xml @@ -6,9 +6,10 @@ - -
-

{series.name}

{series.date}{#each fleet in series.trackedRaces.fleets}{/each series.trackedRaces.fleets} -
+
+

+ + +
From 783723c582432976da2f218fc64a3897468428c4 Mon Sep 17 00:00:00 2001 From: fmittag Date: Wed, 2 Jul 2014 19:08:58 +0200 Subject: [PATCH 024/323] implemented fleet colors --- .../place/event/regattaschedule/EventRegattaSchedule.ui.xml | 5 +++++ .../event/regattaschedule/EventRegattaScheduleFleet.java | 5 ++++- .../event/regattaschedule/EventRegattaScheduleFleet.ui.xml | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.ui.xml index ea9bffdf2f2..f7316363422 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.ui.xml @@ -6,7 +6,12 @@ +
+

Races of Regatta

+
+
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java index e7d20570e51..b4ff29bc4e2 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java @@ -1,6 +1,7 @@ package com.sap.sailing.gwt.home.client.place.event.regattaschedule; 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.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; @@ -25,7 +26,8 @@ public class EventRegattaScheduleFleet extends Composite { @SuppressWarnings("unused") private final FleetDTO fleet; - + + @UiField DivElement fleetDiv; @UiField SpanElement fleetName; @UiField HTMLPanel racesListPanel; @@ -38,6 +40,7 @@ public class EventRegattaScheduleFleet extends Composite { initWidget(uiBinder.createAndBindUi(this)); fleetName.setInnerText(fleet.getName()); + fleetDiv.getStyle().setBackgroundColor(fleet.getColor().getAsHtml()); for(RaceColumnDTO raceColumn: series.getRaceColumns()) { EventRegattaScheduleRace eventRegattaScheduleRace = new EventRegattaScheduleRace(fleet, raceColumn); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.ui.xml index 85494614f29..61fed4d4c22 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.ui.xml @@ -7,7 +7,7 @@
-
+
From 565f01e30fb1fd9126eecbdcc668edad70d6b80b Mon Sep 17 00:00:00 2001 From: fmittag Date: Thu, 3 Jul 2014 19:37:43 +0200 Subject: [PATCH 025/323] updated 'races of regatta' page (with the reduced menu) --- .../SmartphoneApplicationClientFactory.java | 3 +- ...letAndDesktopApplicationClientFactory.java | 5 +- .../client/place/event/EventActivity.java | 18 +- .../place/event/EventClientFactory.java | 3 +- .../event/TabletAndDesktopEventView.java | 19 +- .../client/place/event/header/EventHeader.css | 83 ++++- .../place/event/header/EventHeader.java | 293 +++++++++--------- .../place/event/header/EventHeader.ui.xml | 83 ++--- .../event/header/EventHeaderResources.java | 9 + .../regattaheader/EventRegattaHeader.css | 47 +-- .../regattaheader/EventRegattaHeader.java | 18 +- .../regattaheader/EventRegattaHeader.ui.xml | 38 +-- .../EventRegattaHeaderResources.java | 5 +- .../regattalist/EventRegattaListItem.java | 26 +- .../regattalist/EventRegattaListItem.ui.xml | 12 - .../regattaschedule/EventRegattaSchedule.java | 18 +- .../EventRegattaSchedule.ui.xml | 8 +- .../EventRegattaScheduleFleet.java | 29 +- .../EventRegattaScheduleRace.java | 27 +- .../EventRegattaScheduleRace.ui.xml | 4 +- .../EventRegattaScheduleSeries.java | 12 +- 21 files changed, 425 insertions(+), 335 deletions(-) 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 27fff443d4b..61b5b0c9d17 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 @@ -14,6 +14,7 @@ import com.sap.sailing.gwt.home.client.place.sponsoring.SponsoringView; import com.sap.sailing.gwt.home.client.place.start.SmartphoneStartView; import com.sap.sailing.gwt.home.client.place.start.StartView; import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sse.gwt.client.player.Timer; public class SmartphoneApplicationClientFactory extends AbstractApplicationClientFactory implements ApplicationClientFactory { @@ -30,7 +31,7 @@ public class SmartphoneApplicationClientFactory extends AbstractApplicationClien } @Override - public EventView createEventView(EventDTO event) { + public EventView createEventView(EventDTO event, Timer timerForClientServerOffset) { return null; } 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 2c9da593075..e828b2824ee 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 @@ -19,6 +19,7 @@ import com.sap.sailing.gwt.home.client.place.sponsoring.TabletAndDesktopSponsori import com.sap.sailing.gwt.home.client.place.start.StartView; import com.sap.sailing.gwt.home.client.place.start.TabletAndDesktopStartView; import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sse.gwt.client.player.Timer; public class TabletAndDesktopApplicationClientFactory extends AbstractApplicationClientFactory implements ApplicationClientFactory { @@ -35,8 +36,8 @@ public class TabletAndDesktopApplicationClientFactory extends AbstractApplicatio } @Override - public EventView createEventView(EventDTO event) { - return new TabletAndDesktopEventView(getSailingService(), event); + public EventView createEventView(EventDTO event, Timer timerForClientServerOffset) { + return new TabletAndDesktopEventView(getSailingService(), event, timerForClientServerOffset); } @Override 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 f9989955d8a..8ae04d28f61 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 @@ -8,23 +8,39 @@ 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.ui.shared.EventDTO; +import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; +import com.sap.sse.gwt.client.player.Timer; +import com.sap.sse.gwt.client.player.Timer.PlayModes; public class EventActivity extends AbstractActivity { private final EventClientFactory clientFactory; private final EventPlace eventPlace; + private final Timer timerForClientServerOffset; + public EventActivity(EventPlace place, EventClientFactory clientFactory) { this.clientFactory = clientFactory; this.eventPlace = place; + + timerForClientServerOffset = new Timer(PlayModes.Replay); } @Override public void start(final AcceptsOneWidget panel, final EventBus eventBus) { + final long clientTimeWhenRequestWasSent = System.currentTimeMillis(); clientFactory.getSailingService().getEventById(UUID.fromString(eventPlace.getEventUuidAsString()), new AsyncCallback() { @Override public void onSuccess(EventDTO event) { - final EventView view = clientFactory.createEventView(event); + for(LeaderboardGroupDTO leaderboardGroupDTO: event.getLeaderboardGroups()) { + final long clientTimeWhenResponseWasReceived = System.currentTimeMillis(); + if (leaderboardGroupDTO.getAverageDelayToLiveInMillis() != null) { + timerForClientServerOffset.setLivePlayDelayInMillis(leaderboardGroupDTO.getAverageDelayToLiveInMillis()); + } + timerForClientServerOffset.adjustClientServerOffset(clientTimeWhenRequestWasSent, leaderboardGroupDTO.getCurrentServerTime(), clientTimeWhenResponseWasReceived); + } + + final EventView view = clientFactory.createEventView(event, timerForClientServerOffset); panel.setWidget(view.asWidget()); } 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 57e2e5495e8..daf77287169 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 @@ -2,7 +2,8 @@ package com.sap.sailing.gwt.home.client.place.event; 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 EventClientFactory extends SailingClientFactory { - EventView createEventView(EventDTO event); + EventView createEventView(EventDTO event, Timer timerForClientServerOffset); } 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 5bff2d948df..f256a28e296 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 @@ -21,6 +21,7 @@ import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; +import com.sap.sse.gwt.client.player.Timer; public class TabletAndDesktopEventView extends Composite implements EventView, EventPageNavigator { private static EventViewUiBinder uiBinder = GWT.create(EventViewUiBinder.class); @@ -41,11 +42,11 @@ public class TabletAndDesktopEventView extends Composite implements EventView, E private final SailingServiceAsync sailingService; - public TabletAndDesktopEventView(SailingServiceAsync sailingService, EventDTO event) { + public TabletAndDesktopEventView(SailingServiceAsync sailingService, EventDTO event, Timer timerForClientServerOffset) { this.sailingService = sailingService; eventHeader = new EventHeader(event, this); eventRegattaList = new EventRegattaList(event, this); - eventRegattaSchedule = new EventRegattaSchedule(event, this); + eventRegattaSchedule = new EventRegattaSchedule(event, timerForClientServerOffset, this); eventOverview = new EventOverview(event); eventSchedule = new EventSchedule(event); eventMedia = new EventMedia(event); @@ -53,7 +54,7 @@ public class TabletAndDesktopEventView extends Composite implements EventView, E initWidget(uiBinder.createAndBindUi(this)); pageElements = Arrays.asList(new Widget[] { eventOverview, eventRegattaList, eventRegattaSchedule, eventMedia, eventSchedule }); - setVisibleEventElement(eventOverview); + goToRegattas(); eventSponsors.setEventSponsors(event); } @@ -61,21 +62,24 @@ public class TabletAndDesktopEventView extends Composite implements EventView, E @Override public void goToOverview() { setVisibleEventElement(eventOverview); + eventHeader.setDataNavigationType("normal"); } @Override public void goToRegattas() { setVisibleEventElement(eventRegattaList); + eventHeader.setDataNavigationType("normal"); } @Override - public void goToRegattaRaces(StrippedLeaderboardDTO leaderboard) { + public void goToRegattaRaces(final StrippedLeaderboardDTO leaderboard) { switch (leaderboard.type) { case RegattaLeaderboard: sailingService.getRegattaByName(leaderboard.regattaName, new AsyncCallback() { @Override public void onSuccess(RegattaDTO regatta) { - eventRegattaSchedule.setRacesFromRegatta(regatta); + eventRegattaSchedule.setRacesFromRegatta(regatta, leaderboard); + eventHeader.setDataNavigationType("compact"); setVisibleEventElement(eventRegattaSchedule); } @@ -87,12 +91,15 @@ public class TabletAndDesktopEventView extends Composite implements EventView, E break; case FlexibleLeaderboard: eventRegattaSchedule.setRacesFromFlexibleLeaderboard(leaderboard); + eventHeader.setDataNavigationType("compact"); setVisibleEventElement(eventRegattaSchedule); break; case FlexibleMetaLeaderboard: + eventHeader.setDataNavigationType("compact"); setVisibleEventElement(eventRegattaSchedule); break; case RegattaMetaLeaderboard: + eventHeader.setDataNavigationType("compact"); setVisibleEventElement(eventRegattaSchedule); break; } @@ -101,11 +108,13 @@ public class TabletAndDesktopEventView extends Composite implements EventView, E @Override public void goToSchedule() { setVisibleEventElement(eventSchedule); + eventHeader.setDataNavigationType("normal"); } @Override public void goToMedia() { setVisibleEventElement(eventMedia); + eventHeader.setDataNavigationType("normal"); } private void setVisibleEventElement(Widget visibleWidget) { 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 00b88d3281d..bb5beb0a919 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,6 +1,73 @@ /** * The header of the pages to an event */ +.dropdown { + font-size: 16px; + font-size: 1rem; + width: 18.75em; + max-width: 100%; + display: inline-block; + text-align: left; +} +.dropdown_head { + cursor: pointer; + display: block; +} +.dropdown_head_title { + cursor: pointer; + background: #f8f8f8; + color: #666; + height: 2.5625em; + line-height: 2.5em; + padding-left: 0.9375em; + font-weight: 700; +} +.dropdown_head_titlebutton { + float: right; +} +.dropdown_content { + position: absolute; + z-index: 1; + background: #f6f9fc; + display: none; + width: 18.75em; + height: auto; + max-height: 21.875em; + overflow: auto; + -webkit-box-shadow: 0 0.0625em 0.25em #ccc; + box-shadow: 0 0.0625em 0.25em #ccc; + border-bottom: solid 1px #ccc; +} +.dropdown_content_link { + display: block; + cursor: pointer; + padding: 1.25em 0.9375em; + border-bottom: 1px solid #ddd; +} +.dropdown_content_link:hover, +.dropdown_content_link:focus { + background: #f8f8f8; + text-decoration: none; +} +.dropdown_content_link:hover .dropdown_content_link_title, +.dropdown_content_link:focus .dropdown_content_link_title { + text-decoration: underline; +} +.dropdown_content_link_title { + color: #008fcc; + display: block; + font-weight: 700; + font-size: 1.125em; +} +.dropdown_content_link_subtitle { + color: #666; + font-size: 1em; +} +.leaderboardquickaccess { + font-size: 16px; + font-size: 1rem; + text-align: right; +} .eventheaderwrapper { font-size: 16px; font-size: 1rem; @@ -23,7 +90,7 @@ font-size: 16px; font-size: 1rem; background: #f2f2f2; - padding: 1.875em; + padding: 1.875em 0; } .eventheader_intro { padding-left: 9.6875em; @@ -56,7 +123,8 @@ .eventheader_intro_details_item { display: inline-block; border-right: 1px solid #ddd; - padding: 0.25em 0.625em; + padding: 0.25em 0.625em 0.25em 0; + margin-right: 0.625em; } .eventheader_intro_details_itemlink { color: inherit; @@ -64,9 +132,14 @@ .eventheader_intro_details_item:first-child { padding-left: 0; } +.eventheader_intro_details_item:nth-child(2) { + border: none; +} .eventheader_intro_details_item:last-child { - border-right: none; - padding-right: 0; + display: block; + padding: 0; + border: none; + margin: 0; } .eventheader_sharing { text-align: right; @@ -134,7 +207,7 @@ } .eventnavigationcompactfloating { position: fixed; - top: -10em; + top: -5em; left: 0; right: 0; z-index: 5; 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 f3d096a0e8a..6c69ff0b614 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 @@ -1,18 +1,11 @@ package com.sap.sailing.gwt.home.client.place.event.header; -import java.util.Arrays; -import java.util.List; - 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.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.place.event.EventPageNavigator; @@ -27,24 +20,25 @@ public class EventHeader extends Composite { private final EventDTO event; - @UiField Anchor overviewLink; - @UiField Anchor regattasLink; - @UiField Anchor scheduleLink; - @UiField Anchor mediaLink; - - @UiField Anchor overviewLink2; - @UiField Anchor regattasLink2; - @UiField Anchor scheduleLink2; - @UiField Anchor mediaLink2; - - @UiField Anchor overviewLink3; - @UiField Anchor regattasLink3; - @UiField Anchor scheduleLink3; - @UiField Anchor mediaLink3; +// @UiField Anchor overviewLink; +// @UiField Anchor regattasLink; +// @UiField Anchor scheduleLink; +// @UiField Anchor mediaLink; +// +// @UiField Anchor overviewLink2; +// @UiField Anchor regattasLink2; +// @UiField Anchor scheduleLink2; +// @UiField Anchor mediaLink2; +// +// @UiField Anchor overviewLink3; +// @UiField Anchor regattasLink3; +// @UiField Anchor scheduleLink3; +// @UiField Anchor mediaLink3; + @UiField DivElement eventHeaderWrapperDiv; @UiField SpanElement eventName; @UiField SpanElement eventName2; - @UiField SpanElement eventName3; +// @UiField SpanElement eventName3; @UiField SpanElement eventDate; @UiField SpanElement eventDescription; @UiField SpanElement venueName; @@ -52,19 +46,15 @@ public class EventHeader extends Composite { @UiField ImageElement eventLogo; @UiField ImageElement eventLogo2; - @UiField ImageElement eventLogo3; - - @UiField DivElement eventHeaderDiv; - @UiField DivElement eventNavigationNormalDiv; - @UiField DivElement eventNavigationCompactDiv; - @UiField DivElement eventNavigationFloatingDiv; +// @UiField ImageElement eventLogo3; private final String defaultLogoUrl = "http://static.sapsailing.com/newhome/default_event_logo.png"; - private final List links1; - private final List links2; - private final List links3; +// private final List links1; +// private final List links2; +// private final List links3; + @SuppressWarnings("unused") private final EventPageNavigator pageNavigator; public EventHeader(EventDTO event, EventPageNavigator pageNavigator) { @@ -74,20 +64,25 @@ public class EventHeader extends Composite { EventHeaderResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); - links1 = Arrays.asList(new Anchor[] { overviewLink, regattasLink, scheduleLink, mediaLink }); - links2 = Arrays.asList(new Anchor[] { overviewLink2, regattasLink2, scheduleLink2, mediaLink2 }); - links3 = Arrays.asList(new Anchor[] { overviewLink3, regattasLink3, scheduleLink3, mediaLink3 }); - setActiveLink(links1, overviewLink); - setActiveLink(links2, overviewLink2); - setActiveLink(links3, overviewLink3); +// links1 = Arrays.asList(new Anchor[] { overviewLink, regattasLink, scheduleLink, mediaLink }); +// links2 = Arrays.asList(new Anchor[] { overviewLink2, regattasLink2, scheduleLink2, mediaLink2 }); +// links3 = Arrays.asList(new Anchor[] { overviewLink3, regattasLink3, scheduleLink3, mediaLink3 }); +// setActiveLink(links1, overviewLink); +// setActiveLink(links2, overviewLink2); +// setActiveLink(links3, overviewLink3); + setDataNavigationType("normal"); updateUI(); } + public void setDataNavigationType(String dataNavigationType) { + eventHeaderWrapperDiv.setAttribute("data-navigationtype", dataNavigationType); + } + private void updateUI() { eventName.setInnerHTML(event.getName()); - eventName2.setInnerHTML(event.getName()+"2"); - eventName3.setInnerHTML(event.getName()+"3"); + eventName2.setInnerHTML(event.getName()); +// eventName3.setInnerHTML(event.getName()); venueName.setInnerHTML(event.venue.getName()); eventDate.setInnerHTML(EventDatesFormatterUtil.formatDateRangeWithYear(event.startDate, event.endDate)); @@ -101,96 +96,96 @@ public class EventHeader extends Composite { String logoUrl = event.getLogoImageURL() != null ? event.getLogoImageURL() : defaultLogoUrl; eventLogo.setSrc(logoUrl); eventLogo2.setSrc(logoUrl); - eventLogo3.setSrc(logoUrl); +// eventLogo3.setSrc(logoUrl); } - @UiHandler("overviewLink") - void overviewClicked(ClickEvent event) { - showOverview(); - } +// @UiHandler("overviewLink") +// void overviewClicked(ClickEvent event) { +// showOverview(); +// } +// +// @UiHandler("overviewLink2") +// void overview2Clicked(ClickEvent event) { +// showOverview(); +// } +// +// @UiHandler("overviewLink3") +// void overview3Clicked(ClickEvent event) { +// showOverview(); +// } - @UiHandler("overviewLink2") - void overview2Clicked(ClickEvent event) { - showOverview(); - } - - @UiHandler("overviewLink3") - void overview3Clicked(ClickEvent event) { - showOverview(); - } - - private void showOverview() { - pageNavigator.goToOverview(); - setActiveLink(links1, overviewLink); - setActiveLink(links2, overviewLink2); - setActiveLink(links3, overviewLink3); - } - - @UiHandler("regattasLink") - void regattasClicked(ClickEvent event) { - showRegattas(); - } - - @UiHandler("regattasLink2") - void regattas2Clicked(ClickEvent event) { - showRegattas(); - } - - @UiHandler("regattasLink3") - void regattas3Clicked(ClickEvent event) { - showRegattas(); - } - - private void showRegattas() { - pageNavigator.goToRegattas(); - setActiveLink(links1, regattasLink); - setActiveLink(links2, regattasLink2); - setActiveLink(links3, regattasLink3); - } - - @UiHandler("scheduleLink") - void scheduleClicked(ClickEvent event) { - showSchedule(); - } - - @UiHandler("scheduleLink2") - void schedule2Clicked(ClickEvent event) { - showSchedule(); - } - - @UiHandler("scheduleLink3") - void schedule3Clicked(ClickEvent event) { - showSchedule(); - } - - private void showSchedule() { - pageNavigator.goToSchedule(); - setActiveLink(links1, scheduleLink); - setActiveLink(links2, scheduleLink2); - setActiveLink(links3, scheduleLink3); - } - - @UiHandler("mediaLink") - void mediaClicked(ClickEvent event) { - showMedia(); - } - - @UiHandler("mediaLink2") - void media2Clicked(ClickEvent event) { - showMedia(); - } - - @UiHandler("mediaLink3") - void media3Clicked(ClickEvent event) { - showMedia(); - } - - private void showMedia() { - pageNavigator.goToMedia(); - setActiveLink(links1, mediaLink); - setActiveLink(links2, mediaLink2); - setActiveLink(links3, mediaLink3); - } +// private void showOverview() { +// pageNavigator.goToOverview(); +// setActiveLink(links1, overviewLink); +// setActiveLink(links2, overviewLink2); +// setActiveLink(links3, overviewLink3); +// } +// +// @UiHandler("regattasLink") +// void regattasClicked(ClickEvent event) { +// showRegattas(); +// } +// +// @UiHandler("regattasLink2") +// void regattas2Clicked(ClickEvent event) { +// showRegattas(); +// } +// +// @UiHandler("regattasLink3") +// void regattas3Clicked(ClickEvent event) { +// showRegattas(); +// } +// +// private void showRegattas() { +// pageNavigator.goToRegattas(); +// setActiveLink(links1, regattasLink); +// setActiveLink(links2, regattasLink2); +// setActiveLink(links3, regattasLink3); +// } +// +// @UiHandler("scheduleLink") +// void scheduleClicked(ClickEvent event) { +// showSchedule(); +// } +// +// @UiHandler("scheduleLink2") +// void schedule2Clicked(ClickEvent event) { +// showSchedule(); +// } +// +// @UiHandler("scheduleLink3") +// void schedule3Clicked(ClickEvent event) { +// showSchedule(); +// } +// +// private void showSchedule() { +// pageNavigator.goToSchedule(); +// setActiveLink(links1, scheduleLink); +// setActiveLink(links2, scheduleLink2); +// setActiveLink(links3, scheduleLink3); +// } +// +// @UiHandler("mediaLink") +// void mediaClicked(ClickEvent event) { +// showMedia(); +// } +// +// @UiHandler("mediaLink2") +// void media2Clicked(ClickEvent event) { +// showMedia(); +// } +// +// @UiHandler("mediaLink3") +// void media3Clicked(ClickEvent event) { +// showMedia(); +// } +// +// private void showMedia() { +// pageNavigator.goToMedia(); +// setActiveLink(links1, mediaLink); +// setActiveLink(links2, mediaLink2); +// setActiveLink(links3, mediaLink3); +// } @Override protected void onLoad() { @@ -200,29 +195,29 @@ public class EventHeader extends Composite { } private void initNormalNavigation() { - Window.addWindowScrollHandler(new Window.ScrollHandler() { - public void onWindowScroll(Window.ScrollEvent event) { - int scrollY = Math.max(0, Window.getScrollTop()); - int navNormalOffset = eventNavigationNormalDiv.getOffsetTop(); - - if (scrollY > navNormalOffset) { - eventNavigationFloatingDiv.addClassName(EventHeaderResources.INSTANCE.css().eventnavigationfixed()); - } else { - eventNavigationFloatingDiv.removeClassName(EventHeaderResources.INSTANCE.css() - .eventnavigationfixed()); - } - } - }); +// Window.addWindowScrollHandler(new Window.ScrollHandler() { +// public void onWindowScroll(Window.ScrollEvent event) { +// int scrollY = Math.max(0, Window.getScrollTop()); +// int navNormalOffset = eventNavigationNormalDiv.getOffsetTop(); +// +// if (scrollY > navNormalOffset) { +// eventNavigationFloatingDiv.addClassName(EventHeaderResources.INSTANCE.css().eventnavigationfixed()); +// } else { +// eventNavigationFloatingDiv.removeClassName(EventHeaderResources.INSTANCE.css() +// .eventnavigationfixed()); +// } +// } +// }); } - private void setActiveLink(List linksToSet, Anchor link) { - final String activeStyle = EventHeaderResources.INSTANCE.css().eventnavigation_linkactive(); - for (Anchor l : linksToSet) { - if (l == link) { - l.addStyleName(activeStyle); - } else { - l.removeStyleName(activeStyle); - } - } - } +// private void setActiveLink(List linksToSet, Anchor link) { +// final String activeStyle = EventHeaderResources.INSTANCE.css().eventnavigation_linkactive(); +// for (Anchor l : linksToSet) { +// if (l == link) { +// l.addStyleName(activeStyle); +// } else { +// l.removeStyleName(activeStyle); +// } +// } +// } } 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 455b28c1f39..5bb4170b6b7 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 @@ -7,14 +7,14 @@ - -
-
+ +
+
-
+

-
Live
+
@@ -23,74 +23,29 @@
-
-
- - - -
+
+
-
+
-
- - - - - +
+ + + +
-
+ -
-
-
-
-
-
- - - - - - - - - - -
+
{#if navigation.isEvent}{event.name}{/if navigation.isEvent}{#if navigation.isSeries}{event.name} +
+ -->
- + 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 70bc294433e..26b03780885 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 @@ -11,6 +11,15 @@ public interface EventHeaderResources extends ClientBundle { LocalCss css(); public interface LocalCss extends CssResource { + String dropdown(); + String dropdown_head(); + String dropdown_head_title(); + String dropdown_head_titlebutton(); + String dropdown_content(); + String dropdown_content_link(); + String dropdown_content_link_title(); + String dropdown_content_link_subtitle(); + String leaderboardquickaccess(); String eventheaderwrapper(); String eventheader(); String eventnavigationnormal(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.css index 54276168f56..8197a9e24f2 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.css @@ -52,15 +52,12 @@ .regattalink:hover .regatta_intro_image { opacity: 0.9; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=90)"; - filter: alpha(opacity=90); + filter: literal("alpha(opacity=90)"); } -.regattalink:hover .regatta_intro_header_name { - text-decoration: underline; -} -.regattalink:nth-child(even) { +.regattalink:nth-child\(even\) { background-color: #f8f8f8; } -.regattalink:nth-child(even):hover { +.regattalink:nth-child\(even\):hover { background-color: #f3f3f3; } .regatta_intro { @@ -81,8 +78,9 @@ } .regatta_intro_header { margin-bottom: 0.625em; + padding-top: 0.3125em; padding-bottom: 0.625em; - border-bottom: 1px solid #f2f2f2; + border-bottom: 1px solid #d9d9d9; position: relative; } .regatta_intro_header_logo { @@ -90,11 +88,12 @@ } .regatta_intro_header_name { display: inline-block; - font-size: 1.125em; + font-size: 1.25em; margin: 0; - margin-left: 0.25em; - padding-left: 0.5em; - border-left: 1px solid #f2f2f2; + margin-right: 0.25em; +} +.regatta_intro_header_name:hover { + text-decoration: underline; } .regatta_intro_header_labels { display: inline-block; @@ -106,11 +105,12 @@ vertical-align: baseline; } .regatta_intro_races { - min-height: 3.75em; + position: relative; + min-height: 1.875em; margin-bottom: 0.625em; padding-bottom: 0.625em; padding-left: 0.3125em; - border-bottom: 1px solid #f2f2f2; + border-bottom: 1px solid #d9d9d9; } .regatta_intro_races_race { display: inline-block; @@ -127,11 +127,24 @@ .regatta_intro_races_race[data-finished='data-finished'] { background-color: #f0ab00; } +.regatta_intro_meta { + position: relative; + font-weight: 600; +} +.regatta_intro_meta_scoring { + color: #008fcc; + display: inline-block; +} +.regatta_intro_meta_leaderboardlink { + position: absolute; + top: 0; + right: 0; + text-transform: uppercase; + line-height: 2.8125em; +} .regatta_intro_details { - font-size: 0.875em; font-weight: 600; color: #666; - text-transform: uppercase; } .regatta_intro_details_item { display: inline-block; @@ -143,10 +156,6 @@ .regatta_intro_details_item_sub { display: inline; } -.regatta_intro_details_item_sub:after { - content: ' | '; - display: inline; -} .regatta_intro_details_item_sub:last-child:after { content: ''; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.java index 0b0dc47d03e..44745fdf2ef 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.java @@ -1,7 +1,10 @@ package com.sap.sailing.gwt.home.client.place.event.regattaheader; 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.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.ui.shared.EventDTO; @@ -14,12 +17,23 @@ public class EventRegattaHeader extends Composite { @SuppressWarnings("unused") private final EventDTO event; - + + @UiField SpanElement regattaName; + @UiField SpanElement leaderboardGroupName; + @UiField SpanElement competitorsCount; + @UiField DivElement isLiveElement; + public EventRegattaHeader(EventDTO event) { this.event = event; EventRegattaHeaderResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); } - + + public void setData(String theRegattaName, String theLeaderboardGroupName, int theCompetitorCount, boolean isLive) { + regattaName.setInnerText(theRegattaName); + leaderboardGroupName.setInnerText(theLeaderboardGroupName); + + competitorsCount.setInnerText(String.valueOf(theCompetitorCount)); + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.ui.xml index 6b5129270d7..d9b80049221 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.ui.xml @@ -8,43 +8,45 @@ - - diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeaderResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeaderResources.java index b07b8466fc5..517a4563c55 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeaderResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeaderResources.java @@ -20,14 +20,17 @@ public interface EventRegattaHeaderResources extends ClientBundle { String regatta_intro_details_itemhideoncompact(); String regattalink(); String regatta_intro_image(); - String regatta_intro_header_name(); String regatta_intro(); String regatta_intro_header(); String regatta_intro_header_logo(); + String regatta_intro_header_name(); String regatta_intro_header_labels(); String label(); String regatta_intro_races(); String regatta_intro_races_race(); + String regatta_intro_meta(); + String regatta_intro_meta_scoring(); + String regatta_intro_meta_leaderboardlink(); String regatta_intro_details(); String regatta_intro_details_item(); String regatta_intro_details_item_sub(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.java index aca55159fc0..9af18201058 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.java @@ -23,49 +23,31 @@ public class EventRegattaListItem extends Composite { @UiField SpanElement regattaName; @UiField SpanElement leaderboardGroupName; - @UiField SpanElement competitorsCount; - @UiField SpanElement boatDesign; - @UiField SpanElement scoringSystem; - @UiField SpanElement averageWind; - @UiField SpanElement plannedRacesCount; - @UiField SpanElement regattaSeriesName; - @UiField DivElement isLiveElement; @UiField Anchor regattaRacesLink; @SuppressWarnings("unused") private RegattaDTO regatta; - private final LeaderboardGroupDTO leaderboardGroup; private final StrippedLeaderboardDTO leaderboard; private final EventPageNavigator pageNavigator; public EventRegattaListItem(EventPageNavigator pageNavigator, LeaderboardGroupDTO leaderboardGroup, StrippedLeaderboardDTO leaderboard) { this.pageNavigator = pageNavigator; - this.leaderboardGroup = leaderboardGroup; this.leaderboard = leaderboard; EventRegattaListResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); - updateUI(); + regattaName.setInnerText(leaderboard.displayName != null ? leaderboard.displayName : leaderboard.name); + leaderboardGroupName.setInnerText(leaderboardGroup.getName()); + + competitorsCount.setInnerText("XYZ"); } @UiHandler("regattaRacesLink") public void goToRegattaRaces(ClickEvent e) { pageNavigator.goToRegattaRaces(leaderboard); } - - private void updateUI() { - regattaName.setInnerText(leaderboard.displayName != null ? leaderboard.displayName : leaderboard.name); - leaderboardGroupName.setInnerText(leaderboardGroup.getName()); - - competitorsCount.setInnerText("XYZ"); - boatDesign.setInnerText("XYZ"); - scoringSystem.setInnerText("XYZ"); - averageWind.setInnerText("XYZ"); - plannedRacesCount.setInnerText("XYZ"); - regattaSeriesName.setInnerText("ABC"); - } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.ui.xml index 25c0d87d518..0588246090f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.ui.xml @@ -31,18 +31,6 @@
 
-
-
 
-
-
-
 
-
-
-
 
-
-
-
 
-
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.java index 6542ddd904b..fe0ed337ead 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.java @@ -1,17 +1,18 @@ package com.sap.sailing.gwt.home.client.place.event.regattaschedule; import com.google.gwt.core.client.GWT; -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.HTMLPanel; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.home.client.place.event.EventPageNavigator; +import com.sap.sailing.gwt.home.client.place.event.regattaheader.EventRegattaHeader; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.SeriesDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; +import com.sap.sse.gwt.client.player.Timer; public class EventRegattaSchedule extends Composite { private static EventRegattaScheduleUiBinder uiBinder = GWT.create(EventRegattaScheduleUiBinder.class); @@ -19,24 +20,29 @@ public class EventRegattaSchedule extends Composite { interface EventRegattaScheduleUiBinder extends UiBinder { } - @UiField SpanElement regattaName; + @UiField(provided=true) EventRegattaHeader regattaHeader; @UiField HTMLPanel seriesListPanel; @SuppressWarnings("unused") private final EventDTO event; - public EventRegattaSchedule(EventDTO event, EventPageNavigator pageNavigator) { + private final Timer timerForClientServerOffset; + + public EventRegattaSchedule(EventDTO event, Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { this.event = event; + this.timerForClientServerOffset = timerForClientServerOffset; + + regattaHeader = new EventRegattaHeader(event); EventRegattaScheduleResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); } - public void setRacesFromRegatta(RegattaDTO regatta) { - regattaName.setInnerHTML(regatta.getName()); + public void setRacesFromRegatta(RegattaDTO regatta, StrippedLeaderboardDTO regattaLeaderboard) { + regattaHeader.setData(regattaLeaderboard.displayName != null ? regattaLeaderboard.displayName : regattaLeaderboard.name, "???", 100, true); for(SeriesDTO series: regatta.series) { - EventRegattaScheduleSeries eventRegattaScheduleSeries = new EventRegattaScheduleSeries(regatta, series); + EventRegattaScheduleSeries eventRegattaScheduleSeries = new EventRegattaScheduleSeries(regattaLeaderboard, series, timerForClientServerOffset); seriesListPanel.add(eventRegattaScheduleSeries); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.ui.xml index f7316363422..6f8b49ec780 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.ui.xml @@ -1,16 +1,12 @@ + xmlns:s="urn:import:com.sap.sailing.gwt.home.client.place.event"> -
-
-

Races of Regatta

-
-
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java index b4ff29bc4e2..8907cc1ed84 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java @@ -1,5 +1,8 @@ package com.sap.sailing.gwt.home.client.place.event.regattaschedule; +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.SpanElement; @@ -10,8 +13,9 @@ import com.google.gwt.user.client.ui.HTMLPanel; 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.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.SeriesDTO; +import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; +import com.sap.sse.gwt.client.player.Timer; public class EventRegattaScheduleFleet extends Composite { private static EventRegattaScheduleSeriesUiBinder uiBinder = GWT.create(EventRegattaScheduleSeriesUiBinder.class); @@ -19,8 +23,6 @@ public class EventRegattaScheduleFleet extends Composite { interface EventRegattaScheduleSeriesUiBinder extends UiBinder { } - @SuppressWarnings("unused") - private final RegattaDTO regatta; @SuppressWarnings("unused") private final SeriesDTO series; @@ -31,8 +33,7 @@ public class EventRegattaScheduleFleet extends Composite { @UiField SpanElement fleetName; @UiField HTMLPanel racesListPanel; - public EventRegattaScheduleFleet(RegattaDTO regatta, SeriesDTO series, FleetDTO fleet) { - this.regatta = regatta; + public EventRegattaScheduleFleet(StrippedLeaderboardDTO leaderboard, SeriesDTO series, FleetDTO fleet, Timer timerForClientServerOffset) { this.series = series; this.fleet = fleet; @@ -42,10 +43,24 @@ public class EventRegattaScheduleFleet extends Composite { fleetName.setInnerText(fleet.getName()); fleetDiv.getStyle().setBackgroundColor(fleet.getColor().getAsHtml()); - for(RaceColumnDTO raceColumn: series.getRaceColumns()) { - EventRegattaScheduleRace eventRegattaScheduleRace = new EventRegattaScheduleRace(fleet, raceColumn); + List racesOfFleet = getRacesOfFleet(leaderboard, series, fleet); + for(RaceColumnDTO raceColumn: racesOfFleet) { + EventRegattaScheduleRace eventRegattaScheduleRace = new EventRegattaScheduleRace(fleet, raceColumn, timerForClientServerOffset); racesListPanel.add(eventRegattaScheduleRace); } } + private List getRacesOfFleet(StrippedLeaderboardDTO leaderboard, SeriesDTO series, FleetDTO fleet) { + List racesColumnsOfFleet = new ArrayList(); + for (RaceColumnDTO raceColumn : series.getRaceColumns()) { + for (FleetDTO fleetOfRaceColumn : series.getFleets()) { + if (fleet.equals(fleetOfRaceColumn)) { + // We have to get the race column from the leaderboard, because the race column of the series + // have no tracked race and would be displayed as inactive race. + racesColumnsOfFleet.add(leaderboard.getRaceColumnByName(raceColumn.getName())); + } + } + } + return racesColumnsOfFleet; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.java index dab7aa932df..34cc2a2cf13 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.java @@ -4,11 +4,14 @@ import com.google.gwt.core.client.GWT; 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.Anchor; import com.google.gwt.user.client.ui.Composite; 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.domain.common.dto.RaceDTO; +import com.sap.sailing.gwt.home.client.place.event.regattaschedule.EventRegattaScheduleResources.LocalCss; +import com.sap.sse.gwt.client.player.Timer; public class EventRegattaScheduleRace extends Composite { private static EventRegattaScheduleSeriesUiBinder uiBinder = GWT.create(EventRegattaScheduleSeriesUiBinder.class); @@ -21,23 +24,39 @@ public class EventRegattaScheduleRace extends Composite { @SuppressWarnings("unused") private final RaceColumnDTO raceColumn; + @UiField Anchor raceBox; @UiField SpanElement raceName; @UiField SpanElement raceDetails; - public EventRegattaScheduleRace(FleetDTO fleet, RaceColumnDTO raceColumn) { + public EventRegattaScheduleRace(FleetDTO fleet, RaceColumnDTO raceColumn, Timer timerForClientServerOffset) { this.fleet = fleet; this.raceColumn = raceColumn; - EventRegattaScheduleResources.INSTANCE.css().ensureInjected(); + LocalCss css = EventRegattaScheduleResources.INSTANCE.css(); + css.ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); raceName.setInnerText(raceColumn.getName()); RaceDTO race = raceColumn.getRace(fleet); if(race != null) { - raceDetails.setInnerText(race.isTracked ? "tracked" : "untracked"); + boolean live = raceColumn.isLive(fleet, timerForClientServerOffset.getLiveTimePointInMillis()); + + // tracked race + raceBox.getElement().addClassName(css.eventregattaschedule_series_fleet_racetracked()); + if(race.trackedRace != null) { + if(race.trackedRace.startOfTracking != null) { + raceDetails.setInnerText(race.trackedRace.startOfTracking.toString()); + } else { + raceDetails.setInnerText("tracked"); + } + if(live) { + raceBox.getElement().setAttribute("data-live", "data-live"); + } + } } else { - raceDetails.setInnerText("no race"); + raceBox.getElement().addClassName(css.eventregattaschedule_series_fleet_raceuntracked()); + raceDetails.setInnerText("untracked"); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.ui.xml index 741e3e6d26f..38c46324eaf 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.ui.xml @@ -6,9 +6,9 @@ - +
-
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.java index 231644caa40..bd310e6b5b5 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.java @@ -8,8 +8,9 @@ import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.domain.common.dto.FleetDTO; -import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.SeriesDTO; +import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; +import com.sap.sse.gwt.client.player.Timer; public class EventRegattaScheduleSeries extends Composite { private static EventRegattaScheduleSeriesUiBinder uiBinder = GWT.create(EventRegattaScheduleSeriesUiBinder.class); @@ -17,16 +18,11 @@ public class EventRegattaScheduleSeries extends Composite { interface EventRegattaScheduleSeriesUiBinder extends UiBinder { } - @SuppressWarnings("unused") - private final RegattaDTO regatta; - @UiField SpanElement seriesName; @UiField SpanElement seriesDate; @UiField HTMLPanel fleetsListPanel; - public EventRegattaScheduleSeries(RegattaDTO regatta, SeriesDTO series) { - this.regatta = regatta; - + public EventRegattaScheduleSeries(StrippedLeaderboardDTO leaderboard, SeriesDTO series, Timer timerForClientServerOffset) { EventRegattaScheduleResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); @@ -34,7 +30,7 @@ public class EventRegattaScheduleSeries extends Composite { seriesDate.setInnerText("A Date of the series???"); for(FleetDTO fleet: series.getFleets()) { - EventRegattaScheduleFleet eventRegattaScheduleFleet = new EventRegattaScheduleFleet(regatta, series, fleet); + EventRegattaScheduleFleet eventRegattaScheduleFleet = new EventRegattaScheduleFleet(leaderboard, series, fleet, timerForClientServerOffset); fleetsListPanel.add(eventRegattaScheduleFleet); } } From 1565bc356a0d541efc9da0d5b82d46c572f6ad49 Mon Sep 17 00:00:00 2001 From: fmittag Date: Fri, 4 Jul 2014 10:35:33 +0200 Subject: [PATCH 026/323] removed 'read more' buttons from solutions page --- .../gwt/home/client/shared/solutions/Solutions.ui.xml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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 d288ccb5802..8110b8fc28e 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 @@ -25,7 +25,8 @@

-

Read More +

+
@@ -36,7 +37,8 @@

-

Start Tour +

+
@@ -66,7 +68,8 @@

-

Learn More +

+
From 29627264f870775ee00e0ca8c82aaed902ca30a1 Mon Sep 17 00:00:00 2001 From: fmittag Date: Fri, 4 Jul 2014 10:38:04 +0200 Subject: [PATCH 027/323] removed 'sponsors and solutions teaser' from home page for now --- .../home/client/place/start/TabletAndDesktopStartView.java | 5 ++--- .../home/client/place/start/TabletAndDesktopStartView.ui.xml | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) 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 1fb9395a7f4..e524dd9667e 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.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.ui.shared.EventDTO; @@ -22,13 +21,13 @@ public class TabletAndDesktopStartView extends Composite implements StartView { } @UiField Stage stage; - @UiField(provided=true) MainSponsors mainSponsors; +// @UiField(provided=true) MainSponsors mainSponsors; @UiField(provided=true) MainEvents mainEvents; @UiField(provided=true) MainMedia mainMedia; @UiField SocialFooter socialFooter; public TabletAndDesktopStartView(PlaceNavigator navigator) { - mainSponsors = new MainSponsors(navigator); +// mainSponsors = new MainSponsors(navigator); mainEvents = new MainEvents(navigator); mainMedia = new MainMedia(navigator); 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 750f7d032ac..fe17dfbe93c 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,7 +6,7 @@ - + From 7ce6bdcde95c1f353f02848563f0cf5e756ca03e Mon Sep 17 00:00:00 2001 From: fmittag Date: Fri, 4 Jul 2014 12:12:41 +0200 Subject: [PATCH 028/323] further implementation of regatta races page... Header, skip carry forward columns, link to raceboard, etc. --- .../place/event/EventPageNavigator.java | 6 ++- .../event/TabletAndDesktopEventView.java | 34 ++++++++++++++++- .../place/event/header/EventHeader.java | 38 ++++++++++--------- .../place/event/header/EventHeader.ui.xml | 6 +-- .../regattaheader/EventRegattaHeader.java | 32 +++++++++++++--- .../regattaheader/EventRegattaHeader.ui.xml | 6 +-- .../regattalist/EventRegattaListItem.java | 12 +++++- .../regattalist/EventRegattaListItem.ui.xml | 2 +- .../regattaschedule/EventRegattaSchedule.java | 11 ++++-- .../EventRegattaScheduleFleet.java | 11 ++++-- .../EventRegattaScheduleRace.java | 26 ++++++++++--- .../EventRegattaScheduleSeries.java | 5 ++- 12 files changed, 139 insertions(+), 50 deletions(-) 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 9c693af947e..f6b0104553d 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 @@ -1,5 +1,7 @@ package com.sap.sailing.gwt.home.client.place.event; +import com.sap.sailing.domain.common.dto.RaceDTO; +import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; public interface EventPageNavigator { @@ -7,5 +9,7 @@ public interface EventPageNavigator { void goToRegattas(); void goToSchedule(); void goToMedia(); - void goToRegattaRaces(StrippedLeaderboardDTO leaderboard); + void goToRegattaRaces(LeaderboardGroupDTO leaderboardGroup, StrippedLeaderboardDTO leaderboard); + void openRaceViewer(StrippedLeaderboardDTO leaderboard, RaceDTO race); + 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/TabletAndDesktopEventView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/TabletAndDesktopEventView.java index f256a28e296..4360d404622 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 @@ -1,7 +1,9 @@ package com.sap.sailing.gwt.home.client.place.event; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import com.google.gwt.core.client.GWT; import com.google.gwt.uibinder.client.UiBinder; @@ -10,6 +12,8 @@ import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.common.dto.RaceDTO; 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; @@ -17,8 +21,11 @@ import com.sap.sailing.gwt.home.client.place.event.regattalist.EventRegattaList; import com.sap.sailing.gwt.home.client.place.event.regattaschedule.EventRegattaSchedule; import com.sap.sailing.gwt.home.client.place.event.schedule.EventSchedule; import com.sap.sailing.gwt.home.client.shared.eventsponsors.EventSponsors; +import com.sap.sailing.gwt.ui.client.EntryPointLinkFactory; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.raceboard.RaceBoardViewConfiguration; 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; import com.sap.sse.gwt.client.player.Timer; @@ -72,13 +79,13 @@ public class TabletAndDesktopEventView extends Composite implements EventView, E } @Override - public void goToRegattaRaces(final StrippedLeaderboardDTO leaderboard) { + public void goToRegattaRaces(final LeaderboardGroupDTO leaderboardGroup, final StrippedLeaderboardDTO leaderboard) { switch (leaderboard.type) { case RegattaLeaderboard: sailingService.getRegattaByName(leaderboard.regattaName, new AsyncCallback() { @Override public void onSuccess(RegattaDTO regatta) { - eventRegattaSchedule.setRacesFromRegatta(regatta, leaderboard); + eventRegattaSchedule.setRacesFromRegatta(regatta, leaderboardGroup, leaderboard); eventHeader.setDataNavigationType("compact"); setVisibleEventElement(eventRegattaSchedule); } @@ -116,7 +123,30 @@ public class TabletAndDesktopEventView extends Composite implements EventView, E setVisibleEventElement(eventMedia); eventHeader.setDataNavigationType("normal"); } + + @Override + public void openRaceViewer(StrippedLeaderboardDTO leaderboard, RaceDTO race) { + RegattaAndRaceIdentifier raceIdentifier = race.getRaceIdentifier(); + String link = EntryPointLinkFactory.createRaceBoardLink(createRaceBoardLinkParameters(leaderboard.name, raceIdentifier)); + Window.open(link, "_blank", ""); + } + @Override + public void openLeaderboardViewer(LeaderboardGroupDTO leaderboardGroup, StrippedLeaderboardDTO leaderboard) { + + } + + private Map createRaceBoardLinkParameters(String leaderboardName, RegattaAndRaceIdentifier raceIdentifier) { + Map linkParams = new HashMap(); + linkParams.put("leaderboardName", leaderboardName); + linkParams.put("raceName", raceIdentifier.getRaceName()); + 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()); + return linkParams; + } + private void setVisibleEventElement(Widget visibleWidget) { for (Widget element : pageElements) { element.setVisible(element == visibleWidget); 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 6c69ff0b614..8039adc309a 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,8 +4,11 @@ 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.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.place.event.EventPageNavigator; @@ -21,12 +24,12 @@ public class EventHeader extends Composite { private final EventDTO event; // @UiField Anchor overviewLink; -// @UiField Anchor regattasLink; + @UiField Anchor regattasLink; // @UiField Anchor scheduleLink; // @UiField Anchor mediaLink; // // @UiField Anchor overviewLink2; -// @UiField Anchor regattasLink2; + @UiField Anchor regattasLink2; // @UiField Anchor scheduleLink2; // @UiField Anchor mediaLink2; // @@ -54,7 +57,6 @@ public class EventHeader extends Composite { // private final List links2; // private final List links3; - @SuppressWarnings("unused") private final EventPageNavigator pageNavigator; public EventHeader(EventDTO event, EventPageNavigator pageNavigator) { @@ -120,29 +122,29 @@ public class EventHeader extends Composite { // setActiveLink(links2, overviewLink2); // setActiveLink(links3, overviewLink3); // } -// -// @UiHandler("regattasLink") -// void regattasClicked(ClickEvent event) { -// showRegattas(); -// } -// -// @UiHandler("regattasLink2") -// void regattas2Clicked(ClickEvent event) { -// showRegattas(); -// } -// + + @UiHandler("regattasLink") + void regattasClicked(ClickEvent event) { + showRegattas(); + } + + @UiHandler("regattasLink2") + void regattas2Clicked(ClickEvent event) { + showRegattas(); + } + // @UiHandler("regattasLink3") // void regattas3Clicked(ClickEvent event) { // showRegattas(); // } // -// private void showRegattas() { -// pageNavigator.goToRegattas(); + private void showRegattas() { + pageNavigator.goToRegattas(); // setActiveLink(links1, regattasLink); // setActiveLink(links2, regattasLink2); // setActiveLink(links3, regattasLink3); -// } -// + } + // @UiHandler("scheduleLink") // void scheduleClicked(ClickEvent event) { // showSchedule(); 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 5bb4170b6b7..a3f9dab8703 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 @@ -31,10 +31,10 @@
- + - - + +
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.java index 44745fdf2ef..1dd601d3cd6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.java @@ -3,11 +3,16 @@ package com.sap.sailing.gwt.home.client.place.event.regattaheader; 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.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.Composite; import com.google.gwt.user.client.ui.Widget; 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; +import com.sap.sse.gwt.client.player.Timer; public class EventRegattaHeader extends Composite { private static EventRegattaHeaderUiBinder uiBinder = GWT.create(EventRegattaHeaderUiBinder.class); @@ -17,23 +22,38 @@ public class EventRegattaHeader extends Composite { @SuppressWarnings("unused") private final EventDTO event; + private final Timer timerForClientServerOffset; @UiField SpanElement regattaName; @UiField SpanElement leaderboardGroupName; + @UiField SpanElement regattaPhase; @UiField SpanElement competitorsCount; - @UiField DivElement isLiveElement; + @UiField DivElement isLiveDiv; - public EventRegattaHeader(EventDTO event) { + public EventRegattaHeader(EventDTO event, Timer timerForClientServerOffset) { this.event = event; + this.timerForClientServerOffset = timerForClientServerOffset; EventRegattaHeaderResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); } - public void setData(String theRegattaName, String theLeaderboardGroupName, int theCompetitorCount, boolean isLive) { - regattaName.setInnerText(theRegattaName); - leaderboardGroupName.setInnerText(theLeaderboardGroupName); + public void setData(RegattaDTO regatta, LeaderboardGroupDTO leaderboardGroup, StrippedLeaderboardDTO leaderboard) { + boolean hasLiveRace = leaderboard.hasLiveRace(timerForClientServerOffset.getLiveTimePointInMillis()); + isLiveDiv.setAttribute("data-labeltype", hasLiveRace ? "live" : ""); - competitorsCount.setInnerText(String.valueOf(theCompetitorCount)); + regattaName.setInnerText(leaderboard.displayName != null ? leaderboard.displayName : leaderboard.name); + leaderboardGroupName.setInnerText(leaderboardGroup.getName()); + + if(leaderboard.rows != null) { + competitorsCount.setInnerText(String.valueOf(leaderboard.rows.size())); + } else { + competitorsCount.getStyle().setVisibility(Visibility.HIDDEN); + } + regattaPhase.setInnerText(calculateCurrentRegattaPhase(leaderboard)); + } + + private String calculateCurrentRegattaPhase(StrippedLeaderboardDTO leaderboard) { + return ""; } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.ui.xml index d9b80049221..baf7d007245 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.ui.xml @@ -17,14 +17,14 @@

-
+
-
+
Phase - diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.java index 9af18201058..1053c7eca50 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.java @@ -3,6 +3,7 @@ package com.sap.sailing.gwt.home.client.place.event.regattalist; 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.dom.client.Style.Visibility; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; @@ -24,6 +25,7 @@ public class EventRegattaListItem extends Composite { @UiField SpanElement regattaName; @UiField SpanElement leaderboardGroupName; @UiField SpanElement competitorsCount; + @UiField SpanElement regattaPhase; @UiField DivElement isLiveElement; @UiField Anchor regattaRacesLink; @@ -31,10 +33,12 @@ public class EventRegattaListItem extends Composite { private RegattaDTO regatta; private final StrippedLeaderboardDTO leaderboard; + private final LeaderboardGroupDTO leaderboardGroup; private final EventPageNavigator pageNavigator; public EventRegattaListItem(EventPageNavigator pageNavigator, LeaderboardGroupDTO leaderboardGroup, StrippedLeaderboardDTO leaderboard) { this.pageNavigator = pageNavigator; + this.leaderboardGroup = leaderboardGroup; this.leaderboard = leaderboard; EventRegattaListResources.INSTANCE.css().ensureInjected(); @@ -43,11 +47,15 @@ public class EventRegattaListItem extends Composite { regattaName.setInnerText(leaderboard.displayName != null ? leaderboard.displayName : leaderboard.name); leaderboardGroupName.setInnerText(leaderboardGroup.getName()); - competitorsCount.setInnerText("XYZ"); + if(leaderboard.rows != null) { + competitorsCount.setInnerText(String.valueOf(leaderboard.rows.size())); + } else { + competitorsCount.getStyle().setVisibility(Visibility.HIDDEN); + } } @UiHandler("regattaRacesLink") public void goToRegattaRaces(ClickEvent e) { - pageNavigator.goToRegattaRaces(leaderboard); + pageNavigator.goToRegattaRaces(leaderboardGroup, leaderboard); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.ui.xml index 0588246090f..3d2d87e9a1e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.ui.xml @@ -20,7 +20,7 @@
Phase - diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.java index fe0ed337ead..20c050d866c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.java @@ -9,6 +9,7 @@ import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.home.client.place.event.EventPageNavigator; import com.sap.sailing.gwt.home.client.place.event.regattaheader.EventRegattaHeader; 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.SeriesDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; @@ -27,22 +28,24 @@ public class EventRegattaSchedule extends Composite { private final EventDTO event; private final Timer timerForClientServerOffset; + private final EventPageNavigator pageNavigator; public EventRegattaSchedule(EventDTO event, Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { this.event = event; this.timerForClientServerOffset = timerForClientServerOffset; + this.pageNavigator = pageNavigator; - regattaHeader = new EventRegattaHeader(event); + regattaHeader = new EventRegattaHeader(event, timerForClientServerOffset); EventRegattaScheduleResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); } - public void setRacesFromRegatta(RegattaDTO regatta, StrippedLeaderboardDTO regattaLeaderboard) { - regattaHeader.setData(regattaLeaderboard.displayName != null ? regattaLeaderboard.displayName : regattaLeaderboard.name, "???", 100, true); + public void setRacesFromRegatta(RegattaDTO regatta, LeaderboardGroupDTO leaderboardGroup, StrippedLeaderboardDTO regattaLeaderboard) { + regattaHeader.setData(regatta, leaderboardGroup, regattaLeaderboard); for(SeriesDTO series: regatta.series) { - EventRegattaScheduleSeries eventRegattaScheduleSeries = new EventRegattaScheduleSeries(regattaLeaderboard, series, timerForClientServerOffset); + EventRegattaScheduleSeries eventRegattaScheduleSeries = new EventRegattaScheduleSeries(regattaLeaderboard, series, timerForClientServerOffset, pageNavigator); seriesListPanel.add(eventRegattaScheduleSeries); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java index 8907cc1ed84..2d67652c54d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java @@ -13,6 +13,7 @@ import com.google.gwt.user.client.ui.HTMLPanel; 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.ui.shared.SeriesDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; import com.sap.sse.gwt.client.player.Timer; @@ -33,7 +34,8 @@ public class EventRegattaScheduleFleet extends Composite { @UiField SpanElement fleetName; @UiField HTMLPanel racesListPanel; - public EventRegattaScheduleFleet(StrippedLeaderboardDTO leaderboard, SeriesDTO series, FleetDTO fleet, Timer timerForClientServerOffset) { + public EventRegattaScheduleFleet(StrippedLeaderboardDTO leaderboard, SeriesDTO series, FleetDTO fleet, + Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { this.series = series; this.fleet = fleet; @@ -45,21 +47,24 @@ public class EventRegattaScheduleFleet extends Composite { List racesOfFleet = getRacesOfFleet(leaderboard, series, fleet); for(RaceColumnDTO raceColumn: racesOfFleet) { - EventRegattaScheduleRace eventRegattaScheduleRace = new EventRegattaScheduleRace(fleet, raceColumn, timerForClientServerOffset); + EventRegattaScheduleRace eventRegattaScheduleRace = new EventRegattaScheduleRace(leaderboard, fleet, raceColumn, timerForClientServerOffset, pageNavigator); racesListPanel.add(eventRegattaScheduleRace); } } private List getRacesOfFleet(StrippedLeaderboardDTO leaderboard, SeriesDTO series, FleetDTO fleet) { List racesColumnsOfFleet = new ArrayList(); + int raceColumnCounter = 1; for (RaceColumnDTO raceColumn : series.getRaceColumns()) { + boolean skipCarryForwardColumn = series.isFirstColumnIsNonDiscardableCarryForward(); for (FleetDTO fleetOfRaceColumn : series.getFleets()) { - if (fleet.equals(fleetOfRaceColumn)) { + if (fleet.equals(fleetOfRaceColumn) && !(skipCarryForwardColumn == true && raceColumnCounter == 1)) { // We have to get the race column from the leaderboard, because the race column of the series // have no tracked race and would be displayed as inactive race. racesColumnsOfFleet.add(leaderboard.getRaceColumnByName(raceColumn.getName())); } } + raceColumnCounter++; } return racesColumnsOfFleet; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.java index 34cc2a2cf13..2189ef70178 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.java @@ -2,15 +2,20 @@ package com.sap.sailing.gwt.home.client.place.event.regattaschedule; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.SpanElement; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.i18n.shared.DateTimeFormat; 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.domain.common.dto.FleetDTO; import com.sap.sailing.domain.common.dto.RaceColumnDTO; import com.sap.sailing.domain.common.dto.RaceDTO; +import com.sap.sailing.gwt.home.client.place.event.EventPageNavigator; import com.sap.sailing.gwt.home.client.place.event.regattaschedule.EventRegattaScheduleResources.LocalCss; +import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; import com.sap.sse.gwt.client.player.Timer; public class EventRegattaScheduleRace extends Composite { @@ -19,18 +24,22 @@ public class EventRegattaScheduleRace extends Composite { interface EventRegattaScheduleSeriesUiBinder extends UiBinder { } - @SuppressWarnings("unused") private final FleetDTO fleet; - @SuppressWarnings("unused") private final RaceColumnDTO raceColumn; + private final StrippedLeaderboardDTO leaderboard; @UiField Anchor raceBox; @UiField SpanElement raceName; @UiField SpanElement raceDetails; - public EventRegattaScheduleRace(FleetDTO fleet, RaceColumnDTO raceColumn, Timer timerForClientServerOffset) { + private final DateTimeFormat raceTimeFormat = DateTimeFormat.getFormat("EEE, h:mm a"); + private final EventPageNavigator pageNavigator; + + public EventRegattaScheduleRace(StrippedLeaderboardDTO leaderboard, FleetDTO fleet, RaceColumnDTO raceColumn, Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { + this.leaderboard = leaderboard; this.fleet = fleet; this.raceColumn = raceColumn; + this.pageNavigator = pageNavigator; LocalCss css = EventRegattaScheduleResources.INSTANCE.css(); css.ensureInjected(); @@ -46,7 +55,7 @@ public class EventRegattaScheduleRace extends Composite { raceBox.getElement().addClassName(css.eventregattaschedule_series_fleet_racetracked()); if(race.trackedRace != null) { if(race.trackedRace.startOfTracking != null) { - raceDetails.setInnerText(race.trackedRace.startOfTracking.toString()); + raceDetails.setInnerText(raceTimeFormat.format(race.trackedRace.startOfTracking)); } else { raceDetails.setInnerText("tracked"); } @@ -59,5 +68,12 @@ public class EventRegattaScheduleRace extends Composite { raceDetails.setInnerText("untracked"); } } - + + @UiHandler("raceBox") + public void goToRaceboard(ClickEvent e) { + 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/regattaschedule/EventRegattaScheduleSeries.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.java index bd310e6b5b5..3fe986290d4 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.java @@ -8,6 +8,7 @@ import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.domain.common.dto.FleetDTO; +import com.sap.sailing.gwt.home.client.place.event.EventPageNavigator; import com.sap.sailing.gwt.ui.shared.SeriesDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; import com.sap.sse.gwt.client.player.Timer; @@ -22,7 +23,7 @@ public class EventRegattaScheduleSeries extends Composite { @UiField SpanElement seriesDate; @UiField HTMLPanel fleetsListPanel; - public EventRegattaScheduleSeries(StrippedLeaderboardDTO leaderboard, SeriesDTO series, Timer timerForClientServerOffset) { + public EventRegattaScheduleSeries(StrippedLeaderboardDTO leaderboard, SeriesDTO series, Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { EventRegattaScheduleResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); @@ -30,7 +31,7 @@ public class EventRegattaScheduleSeries extends Composite { seriesDate.setInnerText("A Date of the series???"); for(FleetDTO fleet: series.getFleets()) { - EventRegattaScheduleFleet eventRegattaScheduleFleet = new EventRegattaScheduleFleet(leaderboard, series, fleet, timerForClientServerOffset); + EventRegattaScheduleFleet eventRegattaScheduleFleet = new EventRegattaScheduleFleet(leaderboard, series, fleet, timerForClientServerOffset, pageNavigator); fleetsListPanel.add(eventRegattaScheduleFleet); } } From d4e0ccef12cd99ff0ed4e7f26d31c707151c84ff Mon Sep 17 00:00:00 2001 From: fmittag Date: Fri, 4 Jul 2014 12:36:08 +0200 Subject: [PATCH 029/323] added links to old Analytics leaderboards --- .../place/event/TabletAndDesktopEventView.java | 17 ++++++++++++++++- .../event/regattaheader/EventRegattaHeader.java | 17 ++++++++++++++++- .../regattaheader/EventRegattaHeader.ui.xml | 4 +++- .../event/regattalist/EventRegattaListItem.java | 6 ++++++ .../regattalist/EventRegattaListItem.ui.xml | 4 +++- .../regattaschedule/EventRegattaSchedule.java | 2 +- 6 files changed, 45 insertions(+), 5 deletions(-) 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 4360d404622..f0deba11c70 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 @@ -133,7 +133,8 @@ public class TabletAndDesktopEventView extends Composite implements EventView, E @Override public void openLeaderboardViewer(LeaderboardGroupDTO leaderboardGroup, StrippedLeaderboardDTO leaderboard) { - + String link = EntryPointLinkFactory.createLeaderboardLink(createLeaderboardLinkParameters(leaderboardGroup, leaderboard)); + Window.open(link, "_blank", ""); } private Map createRaceBoardLinkParameters(String leaderboardName, RegattaAndRaceIdentifier raceIdentifier) { @@ -147,6 +148,20 @@ public class TabletAndDesktopEventView extends Composite implements EventView, E return linkParams; } + private Map createLeaderboardLinkParameters(LeaderboardGroupDTO leaderboardGroup, StrippedLeaderboardDTO leaderboard) { + Map linkParams = new HashMap(); + linkParams.put("name", leaderboard.name); + linkParams.put("showRaceDetails", "true"); + linkParams.put("embedded", "true"); + if (leaderboard.displayName != null) { + linkParams.put("displayName", leaderboard.displayName); + } + if(leaderboardGroup != null) { + linkParams.put("leaderboardGroupName", leaderboardGroup.getName()); + } + return linkParams; + } + private void setVisibleEventElement(Widget visibleWidget) { for (Widget element : pageElements) { element.setVisible(element == visibleWidget); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.java index 1dd601d3cd6..3fb26726d8a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.java @@ -4,10 +4,14 @@ 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.dom.client.Style.Visibility; +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.place.event.EventPageNavigator; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO; @@ -23,22 +27,28 @@ public class EventRegattaHeader extends Composite { @SuppressWarnings("unused") private final EventDTO event; private final Timer timerForClientServerOffset; + private final EventPageNavigator pageNavigator; + private StrippedLeaderboardDTO leaderboard; @UiField SpanElement regattaName; @UiField SpanElement leaderboardGroupName; @UiField SpanElement regattaPhase; @UiField SpanElement competitorsCount; @UiField DivElement isLiveDiv; + @UiField Anchor leaderboardLink; - public EventRegattaHeader(EventDTO event, Timer timerForClientServerOffset) { + public EventRegattaHeader(EventDTO event, Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { this.event = event; this.timerForClientServerOffset = timerForClientServerOffset; + this.pageNavigator = pageNavigator; EventRegattaHeaderResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); } public void setData(RegattaDTO regatta, LeaderboardGroupDTO leaderboardGroup, StrippedLeaderboardDTO leaderboard) { + this.leaderboard = leaderboard; + boolean hasLiveRace = leaderboard.hasLiveRace(timerForClientServerOffset.getLiveTimePointInMillis()); isLiveDiv.setAttribute("data-labeltype", hasLiveRace ? "live" : ""); @@ -56,4 +66,9 @@ public class EventRegattaHeader extends Composite { private String calculateCurrentRegattaPhase(StrippedLeaderboardDTO leaderboard) { return ""; } + + @UiHandler("leaderboardLink") + public void goToLeaderboard(ClickEvent e) { + pageNavigator.openLeaderboardViewer(null, leaderboard); + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.ui.xml index baf7d007245..80b13e3946d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.ui.xml @@ -26,7 +26,9 @@
Phase
- +
+ Leaderboard +
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.java index 1053c7eca50..70b1a18a39b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.java @@ -58,4 +58,10 @@ public class EventRegattaListItem extends Composite { public void goToRegattaRaces(ClickEvent e) { pageNavigator.goToRegattaRaces(leaderboardGroup, leaderboard); } + + @UiHandler("leaderboardLink") + public void goToLeaderboard(ClickEvent e) { + pageNavigator.openLeaderboardViewer(leaderboardGroup, leaderboard); + } + } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.ui.xml index 3d2d87e9a1e..a3cc1e10b72 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.ui.xml @@ -22,7 +22,9 @@
Phase
- +
+ Leaderboard +
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.java index 20c050d866c..f0ee7d11a59 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.java @@ -35,7 +35,7 @@ public class EventRegattaSchedule extends Composite { this.timerForClientServerOffset = timerForClientServerOffset; this.pageNavigator = pageNavigator; - regattaHeader = new EventRegattaHeader(event, timerForClientServerOffset); + regattaHeader = new EventRegattaHeader(event, timerForClientServerOffset, pageNavigator); EventRegattaScheduleResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); From 5abe50a4269897d199d6820a62709ff80fd497c5 Mon Sep 17 00:00:00 2001 From: fmittag Date: Mon, 7 Jul 2014 15:19:17 +0200 Subject: [PATCH 030/323] add support for FlexibleLeaderboard's --- .../event/TabletAndDesktopEventView.java | 21 +++++++++++++--- .../regattaheader/EventRegattaHeader.java | 3 +-- .../regattaschedule/EventRegattaSchedule.java | 9 ++++--- .../EventRegattaScheduleFleet.java | 24 ++++++++++++++----- .../sap/sailing/gwt/ui/shared/EventDTO.java | 2 +- 5 files changed, 44 insertions(+), 15 deletions(-) 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 f0deba11c70..88a2c044620 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 @@ -61,9 +61,24 @@ public class TabletAndDesktopEventView extends Composite implements EventView, E initWidget(uiBinder.createAndBindUi(this)); pageElements = Arrays.asList(new Widget[] { eventOverview, eventRegattaList, eventRegattaSchedule, eventMedia, eventSchedule }); - goToRegattas(); + + int leaderboardsCount = 0; + int leaderboardsGroupCount = event.getLeaderboardGroups().size(); + for(LeaderboardGroupDTO leaderboardGroup: event.getLeaderboardGroups()) { + leaderboardsCount += leaderboardGroup.getLeaderboards().size(); + } - eventSponsors.setEventSponsors(event); + if(leaderboardsGroupCount == 0 || leaderboardsCount == 0) { + Window.alert("There is no further data available for this event."); + } else { + if(leaderboardsCount == 1) { + LeaderboardGroupDTO firstLeaderboardGroup = event.getLeaderboardGroups().get(0); + goToRegattaRaces(firstLeaderboardGroup, firstLeaderboardGroup.getLeaderboards().get(0)); + } else { + goToRegattas(); + } + eventSponsors.setEventSponsors(event); + } } @Override @@ -97,7 +112,7 @@ public class TabletAndDesktopEventView extends Composite implements EventView, E }); break; case FlexibleLeaderboard: - eventRegattaSchedule.setRacesFromFlexibleLeaderboard(leaderboard); + eventRegattaSchedule.setRacesFromFlexibleLeaderboard(leaderboardGroup, leaderboard); eventHeader.setDataNavigationType("compact"); setVisibleEventElement(eventRegattaSchedule); break; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.java index 3fb26726d8a..7bd6c56e95a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.java @@ -14,7 +14,6 @@ import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.home.client.place.event.EventPageNavigator; 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; import com.sap.sse.gwt.client.player.Timer; @@ -46,7 +45,7 @@ public class EventRegattaHeader extends Composite { initWidget(uiBinder.createAndBindUi(this)); } - public void setData(RegattaDTO regatta, LeaderboardGroupDTO leaderboardGroup, StrippedLeaderboardDTO leaderboard) { + public void setData(LeaderboardGroupDTO leaderboardGroup, StrippedLeaderboardDTO leaderboard) { this.leaderboard = leaderboard; boolean hasLiveRace = leaderboard.hasLiveRace(timerForClientServerOffset.getLiveTimePointInMillis()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.java index f0ee7d11a59..52a1d3c7003 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.java @@ -42,7 +42,7 @@ public class EventRegattaSchedule extends Composite { } public void setRacesFromRegatta(RegattaDTO regatta, LeaderboardGroupDTO leaderboardGroup, StrippedLeaderboardDTO regattaLeaderboard) { - regattaHeader.setData(regatta, leaderboardGroup, regattaLeaderboard); + regattaHeader.setData(leaderboardGroup, regattaLeaderboard); for(SeriesDTO series: regatta.series) { EventRegattaScheduleSeries eventRegattaScheduleSeries = new EventRegattaScheduleSeries(regattaLeaderboard, series, timerForClientServerOffset, pageNavigator); @@ -50,8 +50,11 @@ public class EventRegattaSchedule extends Composite { } } - public void setRacesFromFlexibleLeaderboard(StrippedLeaderboardDTO flexibleLeaderboard) { - + public void setRacesFromFlexibleLeaderboard(LeaderboardGroupDTO leaderboardGroup, StrippedLeaderboardDTO flexibleLeaderboard) { + regattaHeader.setData(leaderboardGroup, flexibleLeaderboard); + + EventRegattaScheduleFleet eventRegattaScheduleFleet = new EventRegattaScheduleFleet(flexibleLeaderboard, timerForClientServerOffset, pageNavigator); + seriesListPanel.add(eventRegattaScheduleFleet); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java index 2d67652c54d..c15ab5ceed5 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java @@ -11,6 +11,7 @@ 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.domain.common.LeaderboardNameConstants; 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; @@ -24,10 +25,6 @@ public class EventRegattaScheduleFleet extends Composite { interface EventRegattaScheduleSeriesUiBinder extends UiBinder { } - @SuppressWarnings("unused") - private final SeriesDTO series; - - @SuppressWarnings("unused") private final FleetDTO fleet; @UiField DivElement fleetDiv; @@ -36,7 +33,6 @@ public class EventRegattaScheduleFleet extends Composite { public EventRegattaScheduleFleet(StrippedLeaderboardDTO leaderboard, SeriesDTO series, FleetDTO fleet, Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { - this.series = series; this.fleet = fleet; EventRegattaScheduleResources.INSTANCE.css().ensureInjected(); @@ -51,7 +47,23 @@ public class EventRegattaScheduleFleet extends Composite { racesListPanel.add(eventRegattaScheduleRace); } } - + + public EventRegattaScheduleFleet(StrippedLeaderboardDTO leaderboard, Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { + this.fleet = new FleetDTO(LeaderboardNameConstants.DEFAULT_FLEET_NAME, 0, null); + + EventRegattaScheduleResources.INSTANCE.css().ensureInjected(); + initWidget(uiBinder.createAndBindUi(this)); + + fleetName.setInnerText("Races"); +// fleetDiv.getStyle().setBackgroundColor(fleet.getColor().getAsHtml()); + + List racesOfFleet = leaderboard.getRaceList(); + for(RaceColumnDTO raceColumn: racesOfFleet) { + EventRegattaScheduleRace eventRegattaScheduleRace = new EventRegattaScheduleRace(leaderboard, fleet, raceColumn, timerForClientServerOffset, pageNavigator); + racesListPanel.add(eventRegattaScheduleRace); + } + } + private List getRacesOfFleet(StrippedLeaderboardDTO leaderboard, SeriesDTO series, FleetDTO fleet) { List racesColumnsOfFleet = new ArrayList(); int raceColumnCounter = 1; 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 5bbdc1dd04c..f2d41860557 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 @@ -45,7 +45,7 @@ public class EventDTO extends EventBaseDTO { leaderboardGroups.add(leaderboardGroup); } - public Iterable getLeaderboardGroups() { + public List getLeaderboardGroups() { return leaderboardGroups; } From d7796bc13c5e3f6e0cea8cb989795221fe4c6fb7 Mon Sep 17 00:00:00 2001 From: fmittag Date: Mon, 7 Jul 2014 16:19:01 +0200 Subject: [PATCH 031/323] hide event sponsors partial if we don't have sponsors --- .../place/event/TabletAndDesktopEventView.java | 9 ++++++++- .../client/shared/eventsponsors/EventSponsors.java | 13 +++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) 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 88a2c044620..d8048133622 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 @@ -77,7 +77,14 @@ public class TabletAndDesktopEventView extends Composite implements EventView, E } else { goToRegattas(); } - eventSponsors.setEventSponsors(event); + if(event.getSponsorImageURLs() != null && event.getSponsorImageURLs().size() > 0) { + eventSponsors.setVisible(false); + eventSponsors.setEventSponsors(event.getSponsorImageURLs()); + } else { + eventSponsors.setVisible(false); + eventSponsors.setEventSponsors(null); + } + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/eventsponsors/EventSponsors.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/eventsponsors/EventSponsors.java index 4dd4fb88ca9..27e3d048b54 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/eventsponsors/EventSponsors.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/eventsponsors/EventSponsors.java @@ -9,7 +9,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.ui.shared.EventBaseDTO; public class EventSponsors extends Composite { private static EventSponsorsPanelUiBinder uiBinder = GWT.create(EventSponsorsPanelUiBinder.class); @@ -28,15 +27,17 @@ public class EventSponsors extends Composite { eventSponsorComposites = new ArrayList(); } - public void setEventSponsors(EventBaseDTO event) { + public void setEventSponsors(List sponsorImageUrls) { sponsorsPlaceholder.clear(); eventSponsorComposites.clear(); - for (String sponsorImageUrl : event.getSponsorImageURLs()) { - EventSponsor eventSponor = new EventSponsor(null /* name */, sponsorImageUrl, null /* link */); + if(sponsorImageUrls != null) { + for (String sponsorImageUrl : sponsorImageUrls) { + EventSponsor eventSponor = new EventSponsor(null /* name */, sponsorImageUrl, null /* link */); - eventSponsorComposites.add(eventSponor); - sponsorsPlaceholder.add(eventSponor); + eventSponsorComposites.add(eventSponor); + sponsorsPlaceholder.add(eventSponor); + } } } } From a735982c1e54ce4ec21ae63acf317e3f14476918 Mon Sep 17 00:00:00 2001 From: fmittag Date: Mon, 7 Jul 2014 19:01:24 +0200 Subject: [PATCH 032/323] updated stage partial css and uibinder file --- .../gwt/home/client/shared/stage/Stage.css | 285 +++++++++++++++--- .../gwt/home/client/shared/stage/Stage.java | 21 +- .../gwt/home/client/shared/stage/Stage.ui.xml | 62 ++-- .../client/shared/stage/StageResources.java | 37 ++- 4 files changed, 322 insertions(+), 83 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.css index b353c6d1a14..b9f5e30ea40 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.css @@ -1,85 +1,280 @@ /** * Main stage area */ -.stage { - display: -webkit-box; - display: -moz-box; - display: -webkit-flex; - display: -ms-flexbox; - display: box; - display: flex; - -webkit-box-orient: vertical; - -moz-box-orient: vertical; - -o-box-orient: vertical; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - -webkit-box-pack: start; - -moz-box-pack: start; - -o-box-pack: start; - -ms-flex-pack: start; - -webkit-justify-content: flex-start; - justify-content: flex-start; +.countdown { font-size: 16px; font-size: 1rem; - line-height: 1; - background-position: top center; +} +.countdown_pane { + border: 0.25em solid #fff; + background: #008fcc; + color: #fff; + text-align: center; + display: inline-block; + margin: 0.3125em; + padding: 0.625em; + -webkit-border-radius: 0.625em; + border-radius: 0.625em; + width: 4.375em; + height: 4.375em; + text-shadow: none; + -webkit-box-shadow: 0 0 18px rgba(0,0,0,0.13), 0 2px 6px rgba(0,0,0,0.25); + box-shadow: 0 0 18px rgba(0,0,0,0.13), 0 2px 6px rgba(0,0,0,0.25); +} +.countdown_pane_count { + font-weight: bold; + font-size: 1.875em; + line-height: 0.8125em; +} +.countdown_pane_unit { + font-size: 1.125em; +} +.stageteaser { + font-size: 16px; + font-size: 1rem; + position: relative; + width: 100%; +} +.stageteaser_background { + height: 36.25em; + overflow: hidden; +} +.stageteaser_background_image { + display: block; + background-position: center center; background-repeat: no-repeat; -webkit-background-size: cover; -moz-background-size: cover; background-size: cover; - height: 36.25em; - position: relative; + height: 100%; + position: absolute; + left: 0; + right: 0; + -webkit-transition: margin 1s ease; + -moz-transition: margin 1s ease; + -o-transition: margin 1s ease; + -ms-transition: margin 1s ease; + transition: margin 1s ease; } -.stage_header { +.stageteaser_content { + height: 36.25em; + margin-top: -36.25em; +} +.stageteaser_content_header { + position: absolute; + width: 100%; padding: 4.875em; + line-height: 0.9; text-shadow: 0 0 18px rgba(0,0,0,0.13), 0 2px 6px rgba(0,0,0,0.25); text-align: center; + opacity: 1; + -ms-filter: none; + filter: none; + -webkit-transition: opacity 1s ease; + -moz-transition: opacity 1s ease; + -o-transition: opacity 1s ease; + -ms-transition: opacity 1s ease; + transition: opacity 1s ease; } -.stage_header_subtitle { +.stageteaser_content_headerhidden { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: literal("alpha(opacity=0)"); +} +.stageteaser_content_header_subtitle { font-size: 1.875em; font-weight: 400; color: #fff; - margin: 0; + margin: 0 0 0.5em; } -.stage_header_title { +.stageteaser_content_header_title { font-size: 4.5em; font-weight: 700; color: #fff; margin: 0; } -.stage_header_message { - font-size: 1.875em; - font-weight: 600; - color: #fff; - margin: 0; +.stageteaser_content_header_countdown { + font-size: 16px; + font-size: 1rem; } -.stage_band { +.stageteaser_content_band { position: absolute; bottom: 0; - right: 0; left: 0; + right: 0; background-color: rgba(51,51,51,0.6); - text-align: center; - padding: 1.5625em; + padding: 1.3125em 1.5625em; } -.stage_band_message { +.stageteaser_content_band_item { + padding: 0 1.875em; +} +.stageteaser_content_band_item[data-bandcount="2"] { + width: 50%; + float: left; +} +.stageteaser_content_band_item[data-bandcount="2"]:first-child { + border-right: solid 1px #82868b; +} +.stageteaser_content_band_text { display: inline-block; +} +.stageteaser_content_band_text_headline { color: #f0ab00; - font-size: 1.5em; + font-size: 1.125em; font-weight: 700; text-shadow: 0 0 0.625em rgba(51,51,51,0.45); text-transform: uppercase; + margin: 0.4375em 0 0.1875em; } -.stage_band_name { - display: inline-block; +.stageteaser_content_band_text_name { color: #fff; - font-size: 1.5em; + font-size: 1em; text-shadow: 0 0 0.625em rgba(51,51,51,0.45); } -.stage_band_action { +.stageteaser_content_band_action { + float: right; font-size: 1.125em; - padding: 0.875em 1.875em; - margin-left: 1.25em; - text-transform: none; + padding: 0.375em 0.875em; + margin: 0.7em 0 0.5em; +} +.stageteaser_content_band_item[data-bandcount="1"] { + text-align: center; + padding: 0.7em 0.7em 0.55em; +} +.stageteaser_content_band_item[data-bandcount="1"] .stageteaser_content_band_text { + font-size: 19.2px; + font-size: 1.2rem; +} +.stageteaser_content_band_item[data-bandcount="1"] .stageteaser_content_band_text_headline { + display: inline-block; + margin: 5px 0 0; +} +.stageteaser_content_band_item[data-bandcount="1"] .stageteaser_content_band_text_headline:after { + content: "–"; + margin: 0 0.3125em 0 0.625em; +} +.stageteaser_content_band_item[data-bandcount="1"] .stageteaser_content_band_text_name { + display: inline-block; +} +.stageteaser_content_band_item[data-bandcount="1"] .stageteaser_content_band_action { + float: none; + margin: 0 0 0 0.9375em; +} +.swipercontainer { + margin: 0 auto; + position: relative; + overflow: hidden; + -webkit-backface-visibility: hidden; + -moz-backface-visibility: hidden; + -ms-backface-visibility: hidden; + -o-backface-visibility: hidden; + -webkit-backface-visibility: hidden; + -moz-backface-visibility: hidden; + -ms-backface-visibility: hidden; + backface-visibility: hidden; + z-index: 1; +} +.swiperwrapper { + position: relative; + width: 100%; + -webkit-transition-property: -webkit-transform, left, top; + -webkit-transition-duration: 0s; + -webkit-transform: translate3d(0px, 0, 0); + -webkit-transition-timing-function: ease; + -moz-transition-property: -moz-transform, left, top; + -moz-transition-duration: 0s; + -moz-transform: translate3d(0px, 0, 0); + -moz-transition-timing-function: ease; + -o-transition-property: -o-transform, left, top; + -o-transition-duration: 0s; + -o-transform: translate3d(0px, 0, 0); + -o-transition-timing-function: ease; + -o-transform: translate(0px, 0px); + -ms-transition-property: -ms-transform, left, top; + -ms-transition-duration: 0s; + -ms-transform: translate3d(0px, 0, 0); + -ms-transition-timing-function: ease; + -webkit-transition-property: -webkit-transform, left, top; + -moz-transition-property: -moz-transform, left, top; + -o-transition-property: -o-transform, left, top; + -ms-transition-property: -ms-transform, left, top; + transition-property: transform, left, top; + -webkit-transition-duration: 0s; + -moz-transition-duration: 0s; + -o-transition-duration: 0s; + -ms-transition-duration: 0s; + transition-duration: 0s; + -webkit-transform: translate3d(0px, 0, 0); + -moz-transform: translate3d(0px, 0, 0); + -o-transform: translate3d(0px, 0, 0); + -ms-transform: translate3d(0px, 0, 0); + transform: translate3d(0px, 0, 0); + -webkit-transition-timing-function: ease; + -moz-transition-timing-function: ease; + -o-transition-timing-function: ease; + -ms-transition-timing-function: ease; + transition-timing-function: ease; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} +.swiperslide { + font-size: 16px; + font-size: 1rem; + float: left; +} +.swiperwp8horizontal { + -ms-touch-action: pan-y; +} +.swiperwp8vertical { + -ms-touch-action: pan-x; +} +.stage { + font-size: 16px; + font-size: 1rem; + height: 36.25em; + position: relative; + clear: both; +} +.stage_teasers { + height: 36.25em; + width: 100%; +} +.stage .media_slideshow_controls { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: literal("alpha(opacity=0)"); + -webkit-transition: opacity 0.5s ease; + -moz-transition: opacity 0.5s ease; + -o-transition: opacity 0.5s ease; + -ms-transition: opacity 0.5s ease; + transition: opacity 0.5s ease; +} +.stage:hover .media_slideshow_controls, +.stage:focus .media_slideshow_controls { + opacity: 1; + -ms-filter: none; + filter: none; +} +.stage_controls { + font-size: 16px; + font-size: 1rem; +} +.stage_progress { + height: 0.3125em; + margin-top: -0.3125em; + z-index: 10; + position: relative; +} +.stage_progress_bar { + background-color: #f0ab00; + height: 0.3125em; + width: 0%; + -webkit-transition: width 0.1s linear; + -moz-transition: width 0.1s linear; + -o-transition: width 0.1s linear; + -ms-transition: width 0.1s linear; + transition: width 0.1s linear; } 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 4cbc1f9b66b..f8d6ba300b7 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 @@ -5,7 +5,6 @@ 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.Label; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.home.client.shared.EventDatesFormatterUtil; import com.sap.sailing.gwt.ui.shared.EventDTO; @@ -15,11 +14,11 @@ public class Stage extends Composite { @SuppressWarnings("unused") private EventDTO featuredEvent; - @UiField Label subtitle; - @UiField Label title; - @UiField Label message; - @UiField SpanElement regattaState; - @UiField SpanElement actionMessage; + @UiField SpanElement subtitle; + @UiField SpanElement title; + @UiField SpanElement bandTitle; + @UiField SpanElement bandSubtitle; + @UiField SpanElement bandAction; interface StageUiBinder extends UiBinder { } @@ -34,12 +33,12 @@ public class Stage extends Composite { public void setFeaturedEvent(EventDTO featuredEvent) { this.featuredEvent = featuredEvent; - title.setText(featuredEvent.getName()); - subtitle.setText(featuredEvent.venue.getName()); + title.setInnerText(featuredEvent.getName()); + subtitle.setInnerText(featuredEvent.venue.getName()); - message.setText(EventDatesFormatterUtil.formatDateRangeWithYear(featuredEvent.startDate, featuredEvent.endDate)); - actionMessage.setInnerText("[Race 6]"); - regattaState.setInnerText("Next Race"); + bandTitle.setInnerText(EventDatesFormatterUtil.formatDateRangeWithYear(featuredEvent.startDate, featuredEvent.endDate)); + bandSubtitle.setInnerText("[Race 6]"); + bandAction.setInnerText("Next Race"); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.ui.xml index 93d3b5c29a0..972af5dea08 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.ui.xml @@ -3,27 +3,51 @@ xmlns:g="urn:import:com.google.gwt.user.client.ui" xmlns:s="urn:import:com.sap.sailing.gwt.home.client.shared"> + -
-
-

-

-
-
-
-
-
-
 - -
-
- -
-
-
-
+
+
+
+
+
+
+
+
+
+

+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageResources.java index 749273fd8cb..463a87a8615 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageResources.java @@ -11,14 +11,35 @@ public interface StageResources extends ClientBundle { LocalCss css(); public interface LocalCss extends CssResource { + String countdown(); + String countdown_pane(); + String countdown_pane_count(); + String countdown_pane_unit(); + String stageteaser(); + String stageteaser_background(); + String stageteaser_background_image(); + String stageteaser_content(); + String stageteaser_content_header(); + String stageteaser_content_headerhidden(); + String stageteaser_content_header_subtitle(); + String stageteaser_content_header_title(); + String stageteaser_content_header_countdown(); + String stageteaser_content_band(); + String stageteaser_content_band_item(); + String stageteaser_content_band_text(); + String stageteaser_content_band_text_headline(); + String stageteaser_content_band_text_name(); + String stageteaser_content_band_action(); + String swipercontainer(); + String swiperwrapper(); + String swiperslide(); + String swiperwp8horizontal(); + String swiperwp8vertical(); String stage(); - String stage_header(); - String stage_header_subtitle(); - String stage_header_title(); - String stage_header_message(); - String stage_band(); - String stage_band_message(); - String stage_band_name(); - String stage_band_action(); + String stage_teasers(); + String media_slideshow_controls(); + String stage_controls(); + String stage_progress(); + String stage_progress_bar(); } } \ No newline at end of file From 66ac1070f70038c1b6aab8501e629cf31275a796 Mon Sep 17 00:00:00 2001 From: fmittag Date: Tue, 8 Jul 2014 12:01:46 +0200 Subject: [PATCH 033/323] fixed parsing error in eventheader.css --- .../sailing/gwt/home/client/place/event/header/EventHeader.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 bb5beb0a919..d39b7c45b44 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 @@ -132,7 +132,7 @@ .eventheader_intro_details_item:first-child { padding-left: 0; } -.eventheader_intro_details_item:nth-child(2) { +.eventheader_intro_details_item:nth-child\(2\) { border: none; } .eventheader_intro_details_item:last-child { From af4d81513b33f041c1593ba0cf35874e600ef929 Mon Sep 17 00:00:00 2001 From: fmittag Date: Tue, 8 Jul 2014 19:37:28 +0200 Subject: [PATCH 034/323] implemented 3 stage types (live events, upcoming events, popular events) --- .../place/start/SmartphoneStartView.java | 9 +- .../client/place/start/StartActivity.java | 46 ++- .../home/client/place/start/StartView.java | 4 +- .../start/TabletAndDesktopStartView.java | 9 +- .../client/shared/mainmedia/MainMedia.css | 2 + .../shared/stage/LiveEventStageTeaser.java | 26 ++ .../shared/stage/PopularEventStageTeaser.java | 24 ++ .../gwt/home/client/shared/stage/Stage.css | 355 +++++++++--------- .../gwt/home/client/shared/stage/Stage.java | 95 +++-- .../gwt/home/client/shared/stage/Stage.ui.xml | 36 +- .../client/shared/stage/StageEventType.java | 5 + .../client/shared/stage/StageResources.java | 1 - .../home/client/shared/stage/StageTeaser.java | 31 ++ .../client/shared/stage/StageTeaser.ui.xml | 37 ++ .../client/shared/stage/StageTeaserBand.java | 46 +++ .../shared/stage/StageTeaserBand.ui.xml | 19 + .../stage/UpcomingEventStageTeaser.java | 22 ++ 17 files changed, 509 insertions(+), 258 deletions(-) create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/LiveEventStageTeaser.java create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/PopularEventStageTeaser.java create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageEventType.java create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.java create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.ui.xml create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaserBand.java create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaserBand.ui.xml create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/UpcomingEventStageTeaser.java 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 8974f2d8338..c19df307ee4 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 @@ -13,7 +13,9 @@ 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.EventDTO; +import com.sap.sse.common.Util.Pair; public class SmartphoneStartView extends Composite implements StartView { private static StartPageMobileViewUiBinder uiBinder = GWT.create(StartPageMobileViewUiBinder.class); @@ -21,13 +23,14 @@ public class SmartphoneStartView extends Composite implements StartView { interface StartPageMobileViewUiBinder extends UiBinder { } - @UiField Stage stage; + @UiField(provided=true) Stage stage; @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); mainSponsors = new MainSponsors(navigator); mainEvents = new MainEvents(navigator); mainMedia = new MainMedia(navigator); @@ -35,8 +38,8 @@ public class SmartphoneStartView extends Composite implements StartView { } @Override - public void setFeaturedEvent(EventDTO featuredEvent) { - stage.setFeaturedEvent(featuredEvent); + public void setFeaturedEvents(List> featuredEvents) { + stage.setFeaturedEvents(featuredEvents); } @Override 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 e3641f43a9c..77da1bb36b7 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 @@ -8,7 +8,9 @@ import com.google.gwt.activity.shared.AbstractActivity; 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.shared.stage.StageEventType; import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sse.common.Util.Pair; public class StartActivity extends AbstractActivity { private final StartClientFactory clientFactory; @@ -36,22 +38,48 @@ public class StartActivity extends AbstractActivity { @SuppressWarnings("deprecation") protected void fillStartPageEvents(StartView view, List events) { - EventDTO featuredEvent = null; - List recentEvents = new ArrayList(); + 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; + final long TWO_WEEK_IN_MS = 1000 * 60 * 24 * 7; for(EventDTO event: events) { + // ignore events with no start and end date if(event.startDate != null && event.endDate != null) { - if(featuredEvent == null && event.startDate.after(now)) { - featuredEvent = event; - view.setFeaturedEvent(event); - } - if(event.endDate.before(now) && event.endDate.getYear() == currentYear) { - recentEvents.add(event); + if (now.after(event.startDate) && now.before(event.endDate)) { + featuredEvents.add(new Pair(StageEventType.RUNNING, event)); + } else if (event.endDate.after(now) && event.endDate.getTime() - now.getTime() < TWO_WEEK_IN_MS) { + upcomingSoonEvents.add(event); + } else if (event.endDate.before(now) && event.endDate.getYear() == currentYear) { + recentEventsOfSameYear.add(event); } } } - view.setRecentEvents(recentEvents); + + if(featuredEvents.size() < MAX_STAGE_EVENTS) { + fillingUpEventsList(MAX_STAGE_EVENTS, featuredEvents, StageEventType.UPCOMING_SOON, upcomingSoonEvents); + } + if(featuredEvents.size() < MAX_STAGE_EVENTS) { + fillingUpEventsList(MAX_STAGE_EVENTS, featuredEvents, StageEventType.POPULAR, popularEvents); + } + if(featuredEvents.size() < MAX_STAGE_EVENTS) { + fillingUpEventsList(MAX_STAGE_EVENTS, featuredEvents, StageEventType.POPULAR, recentEventsOfSameYear); + } + + view.setFeaturedEvents(featuredEvents); + view.setRecentEvents(recentEventsOfSameYear); + } + + 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++) { + resultList.add(new Pair(type, listToTakeElementsFrom.get(i))); + } } } 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 e14799f0990..1a22a8e509c 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 @@ -3,12 +3,14 @@ package com.sap.sailing.gwt.home.client.place.start; import java.util.List; import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.home.client.shared.stage.StageEventType; import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sse.common.Util.Pair; public interface StartView { Widget asWidget(); - void setFeaturedEvent(EventDTO featuredEvent); + void setFeaturedEvents(List> featuredEvents); void setRecentEvents(List recentEvents); } 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 e524dd9667e..bdfa661fb0a 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 @@ -12,7 +12,9 @@ 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.EventDTO; +import com.sap.sse.common.Util.Pair; public class TabletAndDesktopStartView extends Composite implements StartView { private static StartPageViewUiBinder uiBinder = GWT.create(StartPageViewUiBinder.class); @@ -20,13 +22,14 @@ public class TabletAndDesktopStartView extends Composite implements StartView { interface StartPageViewUiBinder extends UiBinder { } - @UiField Stage stage; + @UiField(provided=true) Stage stage; // @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); // mainSponsors = new MainSponsors(navigator); mainEvents = new MainEvents(navigator); mainMedia = new MainMedia(navigator); @@ -35,8 +38,8 @@ public class TabletAndDesktopStartView extends Composite implements StartView { } @Override - public void setFeaturedEvent(EventDTO featuredEvent) { - stage.setFeaturedEvent(featuredEvent); + public void setFeaturedEvents(List> featuredEvents) { + stage.setFeaturedEvents(featuredEvents); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css index b10cdf3afff..64bf287924f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css @@ -21,7 +21,9 @@ background-position: 0 0; background-repeat: no-repeat; cursor: pointer; + z-index: 10; } + .media_slideshow_controlsnext, .media_slideshow_controlsprev { top: 50%; 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 new file mode 100644 index 00000000000..d42a9781f88 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/LiveEventStageTeaser.java @@ -0,0 +1,26 @@ +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.EventDTO; + +public class LiveEventStageTeaser extends StageTeaser { + + public LiveEventStageTeaser(EventDTO event, PlaceNavigator placeNavigator) { + super(event); + + title.setInnerText(event.getName()); + subtitle.setInnerText(event.venue.getName()); + + // EventDatesFormatterUtil.formatDateRangeWithYear(featuredEvent.startDate, featuredEvent.endDate)) + countDown.getStyle().setDisplay(Display.NONE); + countDown.getStyle().setVisibility(Visibility.HIDDEN); + + bandCount.setAttribute("data-bandcount", "2"); + + stageTeaserBandsPanel.add(new StageTeaserBand(event, placeNavigator)); + stageTeaserBandsPanel.add(new StageTeaserBand(event, 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 new file mode 100644 index 00000000000..ebb0a078ade --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/PopularEventStageTeaser.java @@ -0,0 +1,24 @@ +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.EventDTO; + +public class PopularEventStageTeaser extends StageTeaser { + + public PopularEventStageTeaser(EventDTO event, PlaceNavigator placeNavigator) { + super(event); + + title.setInnerText(event.getName()); + subtitle.setInnerText(event.venue.getName()); + + // EventDatesFormatterUtil.formatDateRangeWithYear(featuredEvent.startDate, featuredEvent.endDate)) + countDown.getStyle().setDisplay(Display.NONE); + countDown.getStyle().setVisibility(Visibility.HIDDEN); + + bandCount.setAttribute("data-bandcount", "1"); + + stageTeaserBandsPanel.add(new StageTeaserBand(event, placeNavigator)); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.css index b9f5e30ea40..f9f3565787d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.css @@ -1,165 +1,3 @@ -/** - * Main stage area - */ -.countdown { - font-size: 16px; - font-size: 1rem; -} -.countdown_pane { - border: 0.25em solid #fff; - background: #008fcc; - color: #fff; - text-align: center; - display: inline-block; - margin: 0.3125em; - padding: 0.625em; - -webkit-border-radius: 0.625em; - border-radius: 0.625em; - width: 4.375em; - height: 4.375em; - text-shadow: none; - -webkit-box-shadow: 0 0 18px rgba(0,0,0,0.13), 0 2px 6px rgba(0,0,0,0.25); - box-shadow: 0 0 18px rgba(0,0,0,0.13), 0 2px 6px rgba(0,0,0,0.25); -} -.countdown_pane_count { - font-weight: bold; - font-size: 1.875em; - line-height: 0.8125em; -} -.countdown_pane_unit { - font-size: 1.125em; -} -.stageteaser { - font-size: 16px; - font-size: 1rem; - position: relative; - width: 100%; -} -.stageteaser_background { - height: 36.25em; - overflow: hidden; -} -.stageteaser_background_image { - display: block; - background-position: center center; - background-repeat: no-repeat; - -webkit-background-size: cover; - -moz-background-size: cover; - background-size: cover; - height: 100%; - position: absolute; - left: 0; - right: 0; - -webkit-transition: margin 1s ease; - -moz-transition: margin 1s ease; - -o-transition: margin 1s ease; - -ms-transition: margin 1s ease; - transition: margin 1s ease; -} -.stageteaser_content { - height: 36.25em; - margin-top: -36.25em; -} -.stageteaser_content_header { - position: absolute; - width: 100%; - padding: 4.875em; - line-height: 0.9; - text-shadow: 0 0 18px rgba(0,0,0,0.13), 0 2px 6px rgba(0,0,0,0.25); - text-align: center; - opacity: 1; - -ms-filter: none; - filter: none; - -webkit-transition: opacity 1s ease; - -moz-transition: opacity 1s ease; - -o-transition: opacity 1s ease; - -ms-transition: opacity 1s ease; - transition: opacity 1s ease; -} -.stageteaser_content_headerhidden { - opacity: 0; - -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; - filter: literal("alpha(opacity=0)"); -} -.stageteaser_content_header_subtitle { - font-size: 1.875em; - font-weight: 400; - color: #fff; - margin: 0 0 0.5em; -} -.stageteaser_content_header_title { - font-size: 4.5em; - font-weight: 700; - color: #fff; - margin: 0; -} -.stageteaser_content_header_countdown { - font-size: 16px; - font-size: 1rem; -} -.stageteaser_content_band { - position: absolute; - bottom: 0; - left: 0; - right: 0; - background-color: rgba(51,51,51,0.6); - padding: 1.3125em 1.5625em; -} -.stageteaser_content_band_item { - padding: 0 1.875em; -} -.stageteaser_content_band_item[data-bandcount="2"] { - width: 50%; - float: left; -} -.stageteaser_content_band_item[data-bandcount="2"]:first-child { - border-right: solid 1px #82868b; -} -.stageteaser_content_band_text { - display: inline-block; -} -.stageteaser_content_band_text_headline { - color: #f0ab00; - font-size: 1.125em; - font-weight: 700; - text-shadow: 0 0 0.625em rgba(51,51,51,0.45); - text-transform: uppercase; - margin: 0.4375em 0 0.1875em; -} -.stageteaser_content_band_text_name { - color: #fff; - font-size: 1em; - text-shadow: 0 0 0.625em rgba(51,51,51,0.45); -} -.stageteaser_content_band_action { - float: right; - font-size: 1.125em; - padding: 0.375em 0.875em; - margin: 0.7em 0 0.5em; -} -.stageteaser_content_band_item[data-bandcount="1"] { - text-align: center; - padding: 0.7em 0.7em 0.55em; -} -.stageteaser_content_band_item[data-bandcount="1"] .stageteaser_content_band_text { - font-size: 19.2px; - font-size: 1.2rem; -} -.stageteaser_content_band_item[data-bandcount="1"] .stageteaser_content_band_text_headline { - display: inline-block; - margin: 5px 0 0; -} -.stageteaser_content_band_item[data-bandcount="1"] .stageteaser_content_band_text_headline:after { - content: "–"; - margin: 0 0.3125em 0 0.625em; -} -.stageteaser_content_band_item[data-bandcount="1"] .stageteaser_content_band_text_name { - display: inline-block; -} -.stageteaser_content_band_item[data-bandcount="1"] .stageteaser_content_band_action { - float: none; - margin: 0 0 0 0.9375em; -} .swipercontainer { margin: 0 auto; position: relative; @@ -221,7 +59,7 @@ box-sizing: content-box; } .swiperslide { - font-size: 16px; + font-size: 15px; font-size: 1rem; float: left; } @@ -231,46 +69,189 @@ .swiperwp8vertical { -ms-touch-action: pan-x; } -.stage { - font-size: 16px; +.countdown { + font-size: 15px; font-size: 1rem; - height: 36.25em; +} +.countdown_pane { + border: 0.266666666666667em solid #fff; + background: #008fcc; + color: #fff; + text-align: center; + display: inline-block; + margin: 0.333333333333333em; + padding: 0.666666666666667em; + -webkit-border-radius: 0.666666666666667em; + border-radius: 0.666666666666667em; + width: 4.666666666666667em; + height: 4.666666666666667em; + text-shadow: none; + -webkit-box-shadow: 0 0 18px rgba(0,0,0,0.13), 0 2px 6px rgba(0,0,0,0.25); + box-shadow: 0 0 18px rgba(0,0,0,0.13), 0 2px 6px rgba(0,0,0,0.25); +} +.countdown_pane_count { + font-weight: bold; + font-size: 2em; + line-height: 0.866666666666667em; +} +.countdown_pane_unit { + font-size: 1.2em; +} +.stageteaser { + font-size: 15px; + font-size: 1rem; + position: relative; + width: 100%; +} +.stageteaser_background { + height: 38.666666666666664em; + overflow: hidden; +} +.stageteaser_background_image { + display: block; + background-position: center center; + background-repeat: no-repeat; + -webkit-background-size: cover; + -moz-background-size: cover; + background-size: cover; + height: 100%; + position: absolute; + left: 0; + right: 0; + -webkit-transition: margin 1s ease; + -moz-transition: margin 1s ease; + -o-transition: margin 1s ease; + -ms-transition: margin 1s ease; + transition: margin 1s ease; +} +.stageteaser_content { + height: 38.666666666666664em; + margin-top: -38.666666666666664em; +} +.stageteaser_content_header { + position: absolute; + width: 100%; + padding: 5.2em; + line-height: 0.9; + text-shadow: 0 0 18px rgba(0,0,0,0.13), 0 2px 6px rgba(0,0,0,0.25); + text-align: center; + opacity: 1; + -ms-filter: none; + filter: none; + -webkit-transition: opacity 1s ease; + -moz-transition: opacity 1s ease; + -o-transition: opacity 1s ease; + -ms-transition: opacity 1s ease; + transition: opacity 1s ease; +} +.stageteaser_content_headerhidden { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: literal("alpha(opacity=0)"); +} +.stageteaser_content_header_subtitle { + font-size: 2em; + font-weight: 400; + color: #fff; + margin: 0 0 0.533333333333333em; +} +.stageteaser_content_header_title { + font-size: 4.8em; + font-weight: 700; + color: #fff; + margin: 0; +} +.stageteaser_content_header_countdown { + font-size: 15px; + font-size: 1rem; +} +.stageteaser_content_band { + position: absolute; + bottom: 0; + left: 0; + right: 0; + background-color: rgba(51,51,51,0.6); + padding: 1.4em 1.666666666666667em; +} +.stageteaser_content_band_item { + padding: 0 2em; +} +.stageteaser_content_band[data-bandcount="2"] .stageteaser_content_band_item { + width: 50%; + float: left; +} +.stageteaser_content_band[data-bandcount="2"] .stageteaser_content_band_item:first-child { + border-right: solid 1px #82868b; +} +.stageteaser_content_band_text { + display: inline-block; +} +.stageteaser_content_band_text_headline { + color: #f0ab00; + font-size: 1.2em; + font-weight: 700; + text-shadow: 0 0 0.666666666666667em rgba(51,51,51,0.45); + text-transform: uppercase; + margin: 0.466666666666667em 0 0.2em; +} +.stageteaser_content_band_text_name { + color: #fff; + font-size: 1.066666666666667em; + text-shadow: 0 0 0.666666666666667em rgba(51,51,51,0.45); +} +.stageteaser_content_band_action { + float: right; + font-size: 1.2em; + padding: 0.4em 0.933333333333333em; + margin: 0.7em 0 0.5em; +} +.stageteaser_content_band_item[data-bandcount="1"] { + text-align: center; + padding: 0.7em 0.7em 0.55em; +} +.stageteaser_content_band_item[data-bandcount="1"] .stageteaser_content_band_text { + font-size: 18px; + font-size: 1.2rem; +} +.stageteaser_content_band_item[data-bandcount="1"] .stageteaser_content_band_text_headline { + display: inline-block; + margin: 5px 0 0; +} +.stageteaser_content_band_item[data-bandcount="1"] .stageteaser_content_band_text_headline:after { + content: "–"; + margin: 0 0.333333333333333em 0 0.666666666666667em; +} +.stageteaser_content_band_item[data-bandcount="1"] .stageteaser_content_band_text_name { + display: inline-block; +} +.stageteaser_content_band_item[data-bandcount="1"] .stageteaser_content_band_action { + float: none; + margin: 0 0 0 1em; +} +.stage { + font-size: 15px; + font-size: 1rem; + height: 38.666666666666664em; position: relative; clear: both; } .stage_teasers { - height: 36.25em; + height: 38.666666666666664em; width: 100%; } -.stage .media_slideshow_controls { - opacity: 0; - -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; - filter: literal("alpha(opacity=0)"); - -webkit-transition: opacity 0.5s ease; - -moz-transition: opacity 0.5s ease; - -o-transition: opacity 0.5s ease; - -ms-transition: opacity 0.5s ease; - transition: opacity 0.5s ease; -} -.stage:hover .media_slideshow_controls, -.stage:focus .media_slideshow_controls { - opacity: 1; - -ms-filter: none; - filter: none; -} .stage_controls { - font-size: 16px; + font-size: 15px; font-size: 1rem; } .stage_progress { - height: 0.3125em; - margin-top: -0.3125em; + height: 0.333333333333333em; + margin-top: -0.333333333333333em; z-index: 10; position: relative; } .stage_progress_bar { background-color: #f0ab00; - height: 0.3125em; + height: 0.333333333333333em; width: 0%; -webkit-transition: width 0.1s linear; -moz-transition: width 0.1s linear; 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 f8d6ba300b7..b5975f565c5 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 @@ -1,44 +1,97 @@ package com.sap.sailing.gwt.home.client.shared.stage; +import java.util.ArrayList; +import java.util.List; + import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.SpanElement; +import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.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.Widget; -import com.sap.sailing.gwt.home.client.shared.EventDatesFormatterUtil; +import com.sap.sailing.gwt.home.client.app.PlaceNavigator; import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sse.common.Util.Pair; public class Stage extends Composite { - - @SuppressWarnings("unused") - private EventDTO featuredEvent; - @UiField SpanElement subtitle; - @UiField SpanElement title; - @UiField SpanElement bandTitle; - @UiField SpanElement bandSubtitle; - @UiField SpanElement bandAction; + @SuppressWarnings("unused") + private List> featuredEvents; + private List stageTeaserComposites; + private int indexOfVisibleStageTeaser; + + @UiField HTMLPanel stageElementsPanel; + @UiField Anchor nextStageTeaserLink; + @UiField Anchor prevStageTeaserLink; + + private StageTeaser stageTeaser; + + private final PlaceNavigator placeNavigator; + interface StageUiBinder extends UiBinder { } private static StageUiBinder uiBinder = GWT.create(StageUiBinder.class); - public Stage() { + public Stage(PlaceNavigator placeNavigator) { + this.placeNavigator = placeNavigator; + StageResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); - } - - public void setFeaturedEvent(EventDTO featuredEvent) { - this.featuredEvent = featuredEvent; - title.setInnerText(featuredEvent.getName()); - subtitle.setInnerText(featuredEvent.venue.getName()); + stageTeaserComposites = new ArrayList(); + indexOfVisibleStageTeaser = -1; + } - bandTitle.setInnerText(EventDatesFormatterUtil.formatDateRangeWithYear(featuredEvent.startDate, featuredEvent.endDate)); - bandSubtitle.setInnerText("[Race 6]"); - bandAction.setInnerText("Next Race"); + public void setFeaturedEvents(List> featuredEvents) { + this.featuredEvents = featuredEvents; + + for(Pair typeAndEvent: featuredEvents) { + switch (typeAndEvent.getA()) { + case POPULAR: + stageTeaser = new PopularEventStageTeaser(typeAndEvent.getB(), placeNavigator); + break; + case RUNNING: + stageTeaser = new LiveEventStageTeaser(typeAndEvent.getB(), placeNavigator); + break; + case UPCOMING_SOON: + stageTeaser = new UpcomingEventStageTeaser(typeAndEvent.getB(), placeNavigator); + break; + } + stageElementsPanel.add(stageTeaser); + stageTeaser.setVisible(false); + stageTeaserComposites.add(stageTeaser); + + indexOfVisibleStageTeaser = 0; + stageTeaserComposites.get(indexOfVisibleStageTeaser).setVisible(true); + } + } + + @UiHandler("nextStageTeaserLink") + public void nextStageTeaserLinkClicked(ClickEvent e) { + StageTeaser oldStageTeaser = stageTeaserComposites.get(indexOfVisibleStageTeaser); + indexOfVisibleStageTeaser = indexOfVisibleStageTeaser+1; + if(indexOfVisibleStageTeaser >= stageTeaserComposites.size()) { + indexOfVisibleStageTeaser = 0; + } + StageTeaser currentStageTeaser = stageTeaserComposites.get(indexOfVisibleStageTeaser); + oldStageTeaser.setVisible(false); + currentStageTeaser.setVisible(true); + } + + @UiHandler("prevStageTeaserLink") + public void prevStageTeaserLinkClicked(ClickEvent e) { + StageTeaser oldStageTeaser = stageTeaserComposites.get(indexOfVisibleStageTeaser); + indexOfVisibleStageTeaser = indexOfVisibleStageTeaser-1; + if(indexOfVisibleStageTeaser < 0) { + indexOfVisibleStageTeaser = stageTeaserComposites.size()-1; + } + StageTeaser currentStageTeaser = stageTeaserComposites.get(indexOfVisibleStageTeaser); + oldStageTeaser.setVisible(false); + currentStageTeaser.setVisible(true); } - } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.ui.xml index 972af5dea08..4e7a82efa42 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.ui.xml @@ -10,44 +10,14 @@
-
-
-
-
-
-
-

-

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
+ +
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageEventType.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageEventType.java new file mode 100644 index 00000000000..cd74414e927 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageEventType.java @@ -0,0 +1,5 @@ +package com.sap.sailing.gwt.home.client.shared.stage; + +public enum StageEventType { + RUNNING, UPCOMING_SOON, POPULAR +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageResources.java index 463a87a8615..af01e834515 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageResources.java @@ -37,7 +37,6 @@ public interface StageResources extends ClientBundle { String swiperwp8vertical(); String stage(); String stage_teasers(); - String media_slideshow_controls(); String stage_controls(); String stage_progress(); String stage_progress_bar(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.java new file mode 100644 index 00000000000..d817ff0e01e --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.java @@ -0,0 +1,31 @@ +package com.sap.sailing.gwt.home.client.shared.stage; + +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.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.ui.shared.EventDTO; + +public abstract class StageTeaser extends Composite { + @UiField DivElement bandCount; + @UiField SpanElement subtitle; + @UiField SpanElement title; + @UiField DivElement countDown; + @UiField HTMLPanel stageTeaserBandsPanel; + + //
+ + interface StageTeaserUiBinder extends UiBinder { + } + + private static StageTeaserUiBinder uiBinder = GWT.create(StageTeaserUiBinder.class); + + public StageTeaser(EventDTO event) { + StageResources.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/shared/stage/StageTeaser.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.ui.xml new file mode 100644 index 00000000000..0c259f29210 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.ui.xml @@ -0,0 +1,37 @@ + + + + + + + +
+
+
+
+
+
+

+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
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 new file mode 100644 index 00000000000..73449c22535 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaserBand.java @@ -0,0 +1,46 @@ +package com.sap.sailing.gwt.home.client.shared.stage; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.SpanElement; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.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.EventDTO; + +public class StageTeaserBand extends Composite { + + interface StageTeaserBandUiBinder extends UiBinder { + } + + private static StageTeaserBandUiBinder uiBinder = GWT.create(StageTeaserBandUiBinder.class); + + @UiField SpanElement bandTitle; + @UiField SpanElement bandSubtitle; + @UiField Anchor actionLink; + + private final PlaceNavigator placeNavigator; + private final EventDTO event; + + public StageTeaserBand(EventDTO event, PlaceNavigator placeNavigator) { + this.event = event; + this.placeNavigator = placeNavigator; + + StageResources.INSTANCE.css().ensureInjected(); + initWidget(uiBinder.createAndBindUi(this)); + + bandTitle.setInnerText("bandTitle"); + bandSubtitle.setInnerText("bandSubtitle"); + + actionLink.setText("Show event"); + } + + @UiHandler("actionLink") + public void actionLinkClicked(ClickEvent e) { + placeNavigator.goToEvent(event.id.toString()); + } +} 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 new file mode 100644 index 00000000000..ad7fb44b52a --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaserBand.ui.xml @@ -0,0 +1,19 @@ + + + + + + + +
+
+
+
+
+ +
+ +
+
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 new file mode 100644 index 00000000000..0cb58d8a527 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/UpcomingEventStageTeaser.java @@ -0,0 +1,22 @@ +package com.sap.sailing.gwt.home.client.shared.stage; + +import com.sap.sailing.gwt.home.client.app.PlaceNavigator; +import com.sap.sailing.gwt.ui.shared.EventDTO; + +public class UpcomingEventStageTeaser extends StageTeaser { + + public UpcomingEventStageTeaser(EventDTO event, PlaceNavigator placeNavigator) { + super(event); + + title.setInnerText(event.getName()); + subtitle.setInnerText(event.venue.getName()); + + // EventDatesFormatterUtil.formatDateRangeWithYear(featuredEvent.startDate, featuredEvent.endDate)) + + bandCount.setAttribute("data-bandcount", "2"); + + stageTeaserBandsPanel.add(new StageTeaserBand(event, placeNavigator)); + stageTeaserBandsPanel.add(new StageTeaserBand(event, placeNavigator)); + } + +} From 78876120bd64e5f5d3481e701e3a20bbe063e47f Mon Sep 17 00:00:00 2001 From: fmittag Date: Tue, 8 Jul 2014 20:18:28 +0200 Subject: [PATCH 035/323] improved view of different stage types --- .../gwt/home/client/place/start/StartActivity.java | 5 +++-- .../home/client/shared/EventDatesFormatterUtil.java | 4 ++-- .../client/shared/stage/LiveEventStageTeaser.java | 1 - .../client/shared/stage/PopularEventStageTeaser.java | 1 - .../gwt/home/client/shared/stage/StageTeaser.ui.xml | 2 +- .../home/client/shared/stage/StageTeaserBand.java | 5 +++-- .../shared/stage/UpcomingEventStageTeaser.java | 12 +++++++----- 7 files changed, 16 insertions(+), 14 deletions(-) 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 77da1bb36b7..9f9fef4b4c9 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 @@ -46,14 +46,14 @@ public class StartActivity extends AbstractActivity { Date now = new Date(); int currentYear = now.getYear(); final int MAX_STAGE_EVENTS = 2; - final long TWO_WEEK_IN_MS = 1000 * 60 * 24 * 7; + final long TWO_WEEK_IN_MS = 1000 * 60 * 60 * 24 * 7; for(EventDTO event: events) { // ignore events with no start and end date 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.endDate.after(now) && event.endDate.getTime() - now.getTime() < TWO_WEEK_IN_MS) { + } else if (event.startDate.after(now) && event.startDate.getTime() - now.getTime() < TWO_WEEK_IN_MS) { upcomingSoonEvents.add(event); } else if (event.endDate.before(now) && event.endDate.getYear() == currentYear) { recentEventsOfSameYear.add(event); @@ -67,6 +67,7 @@ public class StartActivity extends AbstractActivity { 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) { fillingUpEventsList(MAX_STAGE_EVENTS, featuredEvents, StageEventType.POPULAR, recentEventsOfSameYear); } 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 8fb8118c819..add95120f06 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 @@ -19,9 +19,9 @@ public class EventDatesFormatterUtil { 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); + result = monthFormat.format(from) + " " + dayFormat.format(from) + " - " + dayFormat.format(to) + ", " + yearFormat.format(from); } else { - result = dayAndMonthFormat.format(from) + " - " + dayAndMonthFormat.format(to) + " " + yearFormat.format(from); + result = dayAndMonthFormat.format(from) + " - " + dayAndMonthFormat.format(to) + ", " + yearFormat.format(from); } return result; 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 d42a9781f88..544d6220826 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 @@ -13,7 +13,6 @@ public class LiveEventStageTeaser extends StageTeaser { title.setInnerText(event.getName()); subtitle.setInnerText(event.venue.getName()); - // EventDatesFormatterUtil.formatDateRangeWithYear(featuredEvent.startDate, featuredEvent.endDate)) countDown.getStyle().setDisplay(Display.NONE); countDown.getStyle().setVisibility(Visibility.HIDDEN); 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 ebb0a078ade..a99430b743f 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 @@ -13,7 +13,6 @@ public class PopularEventStageTeaser extends StageTeaser { title.setInnerText(event.getName()); subtitle.setInnerText(event.venue.getName()); - // EventDatesFormatterUtil.formatDateRangeWithYear(featuredEvent.startDate, featuredEvent.endDate)) countDown.getStyle().setDisplay(Display.NONE); countDown.getStyle().setVisibility(Visibility.HIDDEN); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.ui.xml index 0c259f29210..f39aad3af4f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.ui.xml @@ -15,7 +15,7 @@

-
+
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 73449c22535..7b35376fecf 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 @@ -10,6 +10,7 @@ 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.shared.EventDatesFormatterUtil; import com.sap.sailing.gwt.ui.shared.EventDTO; public class StageTeaserBand extends Composite { @@ -33,8 +34,8 @@ public class StageTeaserBand extends Composite { StageResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); - bandTitle.setInnerText("bandTitle"); - bandSubtitle.setInnerText("bandSubtitle"); + bandTitle.setInnerText(event.getName()); + bandSubtitle.setInnerText(EventDatesFormatterUtil.formatDateRangeWithYear(event.startDate, event.endDate)); actionLink.setText("Show event"); } 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 0cb58d8a527..42205dd38a3 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 @@ -11,11 +11,13 @@ public class UpcomingEventStageTeaser extends StageTeaser { title.setInnerText(event.getName()); subtitle.setInnerText(event.venue.getName()); - // EventDatesFormatterUtil.formatDateRangeWithYear(featuredEvent.startDate, featuredEvent.endDate)) - - bandCount.setAttribute("data-bandcount", "2"); - - stageTeaserBandsPanel.add(new StageTeaserBand(event, placeNavigator)); + countDown.setAttribute("data-starttime", ""); + countDown.setAttribute("data-days", "3"); + countDown.setAttribute("data-hours", "2"); + countDown.setAttribute("data-minutes", ""); + countDown.setAttribute("data-seconds", ""); + + bandCount.setAttribute("data-bandcount", "1"); stageTeaserBandsPanel.add(new StageTeaserBand(event, placeNavigator)); } From bb7c173f5b9db7430cbe2fd21674c935fe4a5e2b Mon Sep 17 00:00:00 2001 From: fmittag Date: Tue, 8 Jul 2014 21:01:32 +0200 Subject: [PATCH 036/323] read stage image from real URL --- .../sailing/gwt/home/client/shared/stage/StageTeaser.java | 8 ++++++-- .../gwt/home/client/shared/stage/StageTeaser.ui.xml | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.java index d817ff0e01e..76aefe7d2a0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.java @@ -16,8 +16,7 @@ public abstract class StageTeaser extends Composite { @UiField SpanElement title; @UiField DivElement countDown; @UiField HTMLPanel stageTeaserBandsPanel; - - //
+ @UiField DivElement teaserImage; interface StageTeaserUiBinder extends UiBinder { } @@ -27,5 +26,10 @@ public abstract class StageTeaser extends Composite { public StageTeaser(EventDTO event) { StageResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); + + String imageUrl = "http://static.sapsailing.com/ubilabsimages/Bundesliga2014/Bundesliga2014Regatta2_stagebackground.jpg"; + String backgroundImage = "url(" + imageUrl + ")"; + + teaserImage.getStyle().setBackgroundImage(backgroundImage); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.ui.xml index f39aad3af4f..055c7fd3df6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.ui.xml @@ -9,7 +9,7 @@
-
+
From a81aa9f6828134c6e3466a6bbb12c2ea2e2fd7c5 Mon Sep 17 00:00:00 2001 From: fmittag Date: Wed, 9 Jul 2014 12:05:55 +0200 Subject: [PATCH 037/323] improved the way to get the image URLs for the stage --- .../shared/recentevent/RecentEvent.java | 2 +- .../home/client/shared/stage/StageTeaser.java | 6 ++++-- .../sailing/gwt/ui/shared/EventBaseDTO.java | 21 +++++++++++++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) 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 572ab5bdf71..0ce4e48cb40 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 @@ -27,7 +27,7 @@ public class RecentEvent extends Composite { private EventBaseDTO event; - private final String defaultImageUrl = "http://static.sapsailing.com/newhome/default_event_photo.jpg"; + private final String defaultImageUrl = "http://static.sapsailing.com/ubilabsimages/default/default_event_photo.jpg"; interface RecentEventUiBinder extends UiBinder { } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.java index 76aefe7d2a0..97776f87fc1 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.java @@ -18,6 +18,8 @@ public abstract class StageTeaser extends Composite { @UiField HTMLPanel stageTeaserBandsPanel; @UiField DivElement teaserImage; + private final static String DEFAULT_STAGE_IMAGE_URL = "http://static.sapsailing.com/ubilabsimages/default/default_stage_event_teaser.jpg"; + interface StageTeaserUiBinder extends UiBinder { } @@ -27,9 +29,9 @@ public abstract class StageTeaser extends Composite { StageResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); - String imageUrl = "http://static.sapsailing.com/ubilabsimages/Bundesliga2014/Bundesliga2014Regatta2_stagebackground.jpg"; - String backgroundImage = "url(" + imageUrl + ")"; + String stageImageUrl = event.getStageImageURL() != null ? event.getStageImageURL() : DEFAULT_STAGE_IMAGE_URL; + String backgroundImage = "url(" + stageImageUrl + ")"; teaserImage.getStyle().setBackgroundImage(backgroundImage); } } 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 5fc80786ef8..6224884280c 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 @@ -97,6 +97,27 @@ public class EventBaseDTO extends NamedDTO implements IsSerializable { return imageURLs; } + public String getStageImageURL() { + String result = null; + for(String imageUrl: imageURLs) { + if(imageUrl.contains("stage") || imageUrl.contains("STAGE")) { + result = imageUrl; + break; + } + } + return result; + } + + public List getPhotoGalleryImageURLs() { + List result = new ArrayList(); + for(String imageUrl: imageURLs) { + if(!imageUrl.contains("stage") && !imageUrl.contains("STAGE")) { + result.add(imageUrl); + } + } + return result; + } + public List getVideoURLs() { return videoURLs; } From 582730208712a5418d27df4ea5c5279642b09ec2 Mon Sep 17 00:00:00 2001 From: Jens Rommel Date: Wed, 9 Jul 2014 14:36:12 +0200 Subject: [PATCH 038/323] Ported Swiper functionality from JavaScript to GWT --- java/com.sap.sailing.gwt.ui/Home.html | 1 + .../js/newhome/idangerous.swiper.js | 2803 +++++++++++++++++ .../gwt/home/client/shared/stage/Stage.java | 33 +- .../gwt/home/client/shared/stage/Stage.ui.xml | 8 +- .../client/shared/stage/StageTeaser.ui.xml | 69 +- .../sap/sailing/gwt/idangerous/Swiper.java | 33 + .../com/sap/sailing/gwt/Home.gwt.xml | 1 + 7 files changed, 2888 insertions(+), 60 deletions(-) create mode 100644 java/com.sap.sailing.gwt.ui/js/newhome/idangerous.swiper.js create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/idangerous/Swiper.java diff --git a/java/com.sap.sailing.gwt.ui/Home.html b/java/com.sap.sailing.gwt.ui/Home.html index 2b6cb645632..319a40e0491 100644 --- a/java/com.sap.sailing.gwt.ui/Home.html +++ b/java/com.sap.sailing.gwt.ui/Home.html @@ -26,6 +26,7 @@ + diff --git a/java/com.sap.sailing.gwt.ui/js/newhome/idangerous.swiper.js b/java/com.sap.sailing.gwt.ui/js/newhome/idangerous.swiper.js new file mode 100644 index 00000000000..7c0916c5c5f --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/js/newhome/idangerous.swiper.js @@ -0,0 +1,2803 @@ +/* + * Swiper 2.6.1 + * Mobile touch slider and framework with hardware accelerated transitions + * + * http://www.idangero.us/sliders/swiper/ + * + * Copyright 2010-2014, Vladimir Kharlampidi + * The iDangero.us + * http://www.idangero.us/ + * + * Licensed under GPL & MIT + * + * Released on: May 6, 2014 +*/ +var Swiper = function (selector, params) { + 'use strict'; + + /*========================= + A little bit dirty but required part for IE8 and old FF support + ===========================*/ + if (document.body.__defineGetter__) { + if (HTMLElement) { + var element = HTMLElement.prototype; + if (element.__defineGetter__) { + element.__defineGetter__('outerHTML', function () { return new XMLSerializer().serializeToString(this); }); + } + } + } + + if (!window.getComputedStyle) { + window.getComputedStyle = function (el, pseudo) { + this.el = el; + this.getPropertyValue = function (prop) { + var re = /(\-([a-z]){1})/g; + if (prop === 'float') prop = 'styleFloat'; + if (re.test(prop)) { + prop = prop.replace(re, function () { + return arguments[2].toUpperCase(); + }); + } + return el.currentStyle[prop] ? el.currentStyle[prop] : null; + }; + return this; + }; + } + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function (obj, start) { + for (var i = (start || 0), j = this.length; i < j; i++) { + if (this[i] === obj) { return i; } + } + return -1; + }; + } + if (!document.querySelectorAll) { + if (!window.jQuery) return; + } + function $$(selector, context) { + if (document.querySelectorAll) + return (context || document).querySelectorAll(selector); + else + return jQuery(selector, context); + } + + /*========================= + Check for correct selector + ===========================*/ + if (typeof selector === 'undefined') return; + + if (!(selector.nodeType)) { + if ($$(selector).length === 0) return; + } + + /*========================= + _this + ===========================*/ + var _this = this; + + /*========================= + Default Flags and vars + ===========================*/ + _this.touches = { + start: 0, + startX: 0, + startY: 0, + current: 0, + currentX: 0, + currentY: 0, + diff: 0, + abs: 0 + }; + _this.positions = { + start: 0, + abs: 0, + diff: 0, + current: 0 + }; + _this.times = { + start: 0, + end: 0 + }; + + _this.id = (new Date()).getTime(); + _this.container = (selector.nodeType) ? selector : $$(selector)[0]; + _this.isTouched = false; + _this.isMoved = false; + _this.activeIndex = 0; + _this.centerIndex = 0; + _this.activeLoaderIndex = 0; + _this.activeLoopIndex = 0; + _this.previousIndex = null; + _this.velocity = 0; + _this.snapGrid = []; + _this.slidesGrid = []; + _this.imagesToLoad = []; + _this.imagesLoaded = 0; + _this.wrapperLeft = 0; + _this.wrapperRight = 0; + _this.wrapperTop = 0; + _this.wrapperBottom = 0; + _this.isAndroid = navigator.userAgent.toLowerCase().indexOf('android') >= 0; + var wrapper, slideSize, wrapperSize, direction, isScrolling, containerSize; + + /*========================= + Default Parameters + ===========================*/ + var defaults = { + eventTarget: 'wrapper', // or 'container' + mode : 'horizontal', // or 'vertical' + touchRatio : 1, + speed : 300, + freeMode : false, + freeModeFluid : false, + momentumRatio: 1, + momentumBounce: true, + momentumBounceRatio: 1, + slidesPerView : 1, + slidesPerGroup : 1, + slidesPerViewFit: true, //Fit to slide when spv "auto" and slides larger than container + simulateTouch : true, + followFinger : true, + shortSwipes : true, + longSwipesRatio: 0.5, + moveStartThreshold: false, + onlyExternal : false, + createPagination : true, + pagination : false, + paginationElement: 'span', + paginationClickable: false, + paginationAsRange: true, + resistance : true, // or false or 100% + scrollContainer : false, + preventLinks : true, + preventLinksPropagation: false, + noSwiping : false, // or class + noSwipingClass : 'swiper-no-swiping', //:) + initialSlide: 0, + keyboardControl: false, + mousewheelControl : false, + mousewheelControlForceToAxis : false, + useCSS3Transforms : true, + // Autoplay + autoplay: false, + autoplayDisableOnInteraction: true, + autoplayStopOnLast: false, + //Loop mode + loop: false, + loopAdditionalSlides: 0, + // Round length values + roundLengths: false, + //Auto Height + calculateHeight: false, + cssWidthAndHeight: false, + //Images Preloader + updateOnImagesReady : true, + //Form elements + releaseFormElements : true, + //Watch for active slide, useful when use effects on different slide states + watchActiveIndex: false, + //Slides Visibility Fit + visibilityFullFit : false, + //Slides Offset + offsetPxBefore : 0, + offsetPxAfter : 0, + offsetSlidesBefore : 0, + offsetSlidesAfter : 0, + centeredSlides: false, + //Queue callbacks + queueStartCallbacks : false, + queueEndCallbacks : false, + //Auto Resize + autoResize : true, + resizeReInit : false, + //DOMAnimation + DOMAnimation : true, + //Slides Loader + loader: { + slides: [], //array with slides + slidesHTMLType: 'inner', // or 'outer' + surroundGroups: 1, //keep preloaded slides groups around view + logic: 'reload', //or 'change' + loadAllSlides: false + }, + //Namespace + slideElement: 'div', + slideClass: 'swiper-slide', + slideActiveClass: 'swiper-slide-active', + slideVisibleClass: 'swiper-slide-visible', + slideDuplicateClass: 'swiper-slide-duplicate', + wrapperClass: 'swiper-wrapper', + paginationElementClass: 'swiper-pagination-switch', + paginationActiveClass: 'swiper-active-switch', + paginationVisibleClass: 'swiper-visible-switch' + }; + params = params || {}; + for (var prop in defaults) { + if (prop in params && typeof params[prop] === 'object') { + for (var subProp in defaults[prop]) { + if (! (subProp in params[prop])) { + params[prop][subProp] = defaults[prop][subProp]; + } + } + } + else if (! (prop in params)) { + params[prop] = defaults[prop]; + } + } + _this.params = params; + if (params.scrollContainer) { + params.freeMode = true; + params.freeModeFluid = true; + } + if (params.loop) { + params.resistance = '100%'; + } + var isH = params.mode === 'horizontal'; + + /*========================= + Define Touch Events + ===========================*/ + var desktopEvents = ['mousedown', 'mousemove', 'mouseup']; + if (_this.browser.ie10) desktopEvents = ['MSPointerDown', 'MSPointerMove', 'MSPointerUp']; + if (_this.browser.ie11) desktopEvents = ['pointerdown', 'pointermove', 'pointerup']; + + _this.touchEvents = { + touchStart : _this.support.touch || !params.simulateTouch ? 'touchstart' : desktopEvents[0], + touchMove : _this.support.touch || !params.simulateTouch ? 'touchmove' : desktopEvents[1], + touchEnd : _this.support.touch || !params.simulateTouch ? 'touchend' : desktopEvents[2] + }; + + /*========================= + Wrapper + ===========================*/ + for (var i = _this.container.childNodes.length - 1; i >= 0; i--) { + if (_this.container.childNodes[i].className) { + var _wrapperClasses = _this.container.childNodes[i].className.split(/\s+/); + for (var j = 0; j < _wrapperClasses.length; j++) { + if (_wrapperClasses[j] === params.wrapperClass) { + wrapper = _this.container.childNodes[i]; + } + } + } + } + + _this.wrapper = wrapper; + /*========================= + Slide API + ===========================*/ + _this._extendSwiperSlide = function (el) { + el.append = function () { + if (params.loop) { + el.insertAfter(_this.slides.length - _this.loopedSlides); + } + else { + _this.wrapper.appendChild(el); + _this.reInit(); + } + + return el; + }; + el.prepend = function () { + if (params.loop) { + _this.wrapper.insertBefore(el, _this.slides[_this.loopedSlides]); + _this.removeLoopedSlides(); + _this.calcSlides(); + _this.createLoop(); + } + else { + _this.wrapper.insertBefore(el, _this.wrapper.firstChild); + } + _this.reInit(); + return el; + }; + el.insertAfter = function (index) { + if (typeof index === 'undefined') return false; + var beforeSlide; + + if (params.loop) { + beforeSlide = _this.slides[index + 1 + _this.loopedSlides]; + if (beforeSlide) { + _this.wrapper.insertBefore(el, beforeSlide); + } + else { + _this.wrapper.appendChild(el); + } + _this.removeLoopedSlides(); + _this.calcSlides(); + _this.createLoop(); + } + else { + beforeSlide = _this.slides[index + 1]; + _this.wrapper.insertBefore(el, beforeSlide); + } + _this.reInit(); + return el; + }; + el.clone = function () { + return _this._extendSwiperSlide(el.cloneNode(true)); + }; + el.remove = function () { + _this.wrapper.removeChild(el); + _this.reInit(); + }; + el.html = function (html) { + if (typeof html === 'undefined') { + return el.innerHTML; + } + else { + el.innerHTML = html; + return el; + } + }; + el.index = function () { + var index; + for (var i = _this.slides.length - 1; i >= 0; i--) { + if (el === _this.slides[i]) index = i; + } + return index; + }; + el.isActive = function () { + if (el.index() === _this.activeIndex) return true; + else return false; + }; + if (!el.swiperSlideDataStorage) el.swiperSlideDataStorage = {}; + el.getData = function (name) { + return el.swiperSlideDataStorage[name]; + }; + el.setData = function (name, value) { + el.swiperSlideDataStorage[name] = value; + return el; + }; + el.data = function (name, value) { + if (typeof value === 'undefined') { + return el.getAttribute('data-' + name); + } + else { + el.setAttribute('data-' + name, value); + return el; + } + }; + el.getWidth = function (outer, round) { + return _this.h.getWidth(el, outer, round); + }; + el.getHeight = function (outer, round) { + return _this.h.getHeight(el, outer, round); + }; + el.getOffset = function () { + return _this.h.getOffset(el); + }; + return el; + }; + + //Calculate information about number of slides + _this.calcSlides = function (forceCalcSlides) { + var oldNumber = _this.slides ? _this.slides.length : false; + _this.slides = []; + _this.displaySlides = []; + for (var i = 0; i < _this.wrapper.childNodes.length; i++) { + if (_this.wrapper.childNodes[i].className) { + var _className = _this.wrapper.childNodes[i].className; + var _slideClasses = _className.split(/\s+/); + for (var j = 0; j < _slideClasses.length; j++) { + if (_slideClasses[j] === params.slideClass) { + _this.slides.push(_this.wrapper.childNodes[i]); + } + } + } + } + for (i = _this.slides.length - 1; i >= 0; i--) { + _this._extendSwiperSlide(_this.slides[i]); + } + if (oldNumber === false) return; + if (oldNumber !== _this.slides.length || forceCalcSlides) { + + // Number of slides has been changed + removeSlideEvents(); + addSlideEvents(); + _this.updateActiveSlide(); + if (_this.params.pagination) _this.createPagination(); + _this.callPlugins('numberOfSlidesChanged'); + } + }; + + //Create Slide + _this.createSlide = function (html, slideClassList, el) { + slideClassList = slideClassList || _this.params.slideClass; + el = el || params.slideElement; + var newSlide = document.createElement(el); + newSlide.innerHTML = html || ''; + newSlide.className = slideClassList; + return _this._extendSwiperSlide(newSlide); + }; + + //Append Slide + _this.appendSlide = function (html, slideClassList, el) { + if (!html) return; + if (html.nodeType) { + return _this._extendSwiperSlide(html).append(); + } + else { + return _this.createSlide(html, slideClassList, el).append(); + } + }; + _this.prependSlide = function (html, slideClassList, el) { + if (!html) return; + if (html.nodeType) { + return _this._extendSwiperSlide(html).prepend(); + } + else { + return _this.createSlide(html, slideClassList, el).prepend(); + } + }; + _this.insertSlideAfter = function (index, html, slideClassList, el) { + if (typeof index === 'undefined') return false; + if (html.nodeType) { + return _this._extendSwiperSlide(html).insertAfter(index); + } + else { + return _this.createSlide(html, slideClassList, el).insertAfter(index); + } + }; + _this.removeSlide = function (index) { + if (_this.slides[index]) { + if (params.loop) { + if (!_this.slides[index + _this.loopedSlides]) return false; + _this.slides[index + _this.loopedSlides].remove(); + _this.removeLoopedSlides(); + _this.calcSlides(); + _this.createLoop(); + } + else _this.slides[index].remove(); + return true; + } + else return false; + }; + _this.removeLastSlide = function () { + if (_this.slides.length > 0) { + if (params.loop) { + _this.slides[_this.slides.length - 1 - _this.loopedSlides].remove(); + _this.removeLoopedSlides(); + _this.calcSlides(); + _this.createLoop(); + } + else _this.slides[_this.slides.length - 1].remove(); + return true; + } + else { + return false; + } + }; + _this.removeAllSlides = function () { + for (var i = _this.slides.length - 1; i >= 0; i--) { + _this.slides[i].remove(); + } + }; + _this.getSlide = function (index) { + return _this.slides[index]; + }; + _this.getLastSlide = function () { + return _this.slides[_this.slides.length - 1]; + }; + _this.getFirstSlide = function () { + return _this.slides[0]; + }; + + //Currently Active Slide + _this.activeSlide = function () { + return _this.slides[_this.activeIndex]; + }; + + /*========================= + Wrapper for Callbacks : Allows additive callbacks via function arrays + ===========================*/ + _this.fireCallback = function () { + var callback = arguments[0]; + if (Object.prototype.toString.call(callback) === '[object Array]') { + for (var i = 0; i < callback.length; i++) { + if (typeof callback[i] === 'function') { + callback[i](arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]); + } + } + } else if (Object.prototype.toString.call(callback) === '[object String]') { + if (params['on' + callback]) _this.fireCallback(params['on' + callback], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]); + } else { + callback(arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]); + } + }; + function isArray(obj) { + if (Object.prototype.toString.apply(obj) === '[object Array]') return true; + return false; + } + + /** + * Allows user to add callbacks, rather than replace them + * @param callback + * @param func + * @return {*} + */ + _this.addCallback = function (callback, func) { + var _this = this, tempFunc; + if (_this.params['on' + callback]) { + if (isArray(this.params['on' + callback])) { + return this.params['on' + callback].push(func); + } else if (typeof this.params['on' + callback] === 'function') { + tempFunc = this.params['on' + callback]; + this.params['on' + callback] = []; + this.params['on' + callback].push(tempFunc); + return this.params['on' + callback].push(func); + } + } else { + this.params['on' + callback] = []; + return this.params['on' + callback].push(func); + } + }; + _this.removeCallbacks = function (callback) { + if (_this.params['on' + callback]) { + _this.params['on' + callback] = null; + } + }; + + /*========================= + Plugins API + ===========================*/ + var _plugins = []; + for (var plugin in _this.plugins) { + if (params[plugin]) { + var p = _this.plugins[plugin](_this, params[plugin]); + if (p) _plugins.push(p); + } + } + _this.callPlugins = function (method, args) { + if (!args) args = {}; + for (var i = 0; i < _plugins.length; i++) { + if (method in _plugins[i]) { + _plugins[i][method](args); + } + } + }; + + /*========================= + Windows Phone 8 Fix + ===========================*/ + if ((_this.browser.ie10 || _this.browser.ie11) && !params.onlyExternal) { + _this.wrapper.classList.add('swiper-wp8-' + (isH ? 'horizontal' : 'vertical')); + } + + /*========================= + Free Mode Class + ===========================*/ + if (params.freeMode) { + _this.container.className += ' swiper-free-mode'; + } + + /*================================================== + Init/Re-init/Resize Fix + ====================================================*/ + _this.initialized = false; + _this.init = function (force, forceCalcSlides) { + var _width = _this.h.getWidth(_this.container, false, params.roundLengths); + var _height = _this.h.getHeight(_this.container, false, params.roundLengths); + if (_width === _this.width && _height === _this.height && !force) return; + + _this.width = _width; + _this.height = _height; + + var slideWidth, slideHeight, slideMaxHeight, wrapperWidth, wrapperHeight, slideLeft; + var i; // loop index variable to avoid JSHint W004 / W038 + containerSize = isH ? _width : _height; + var wrapper = _this.wrapper; + + if (force) { + _this.calcSlides(forceCalcSlides); + } + + if (params.slidesPerView === 'auto') { + //Auto mode + var slidesWidth = 0; + var slidesHeight = 0; + + //Unset Styles + if (params.slidesOffset > 0) { + wrapper.style.paddingLeft = ''; + wrapper.style.paddingRight = ''; + wrapper.style.paddingTop = ''; + wrapper.style.paddingBottom = ''; + } + wrapper.style.width = ''; + wrapper.style.height = ''; + if (params.offsetPxBefore > 0) { + if (isH) _this.wrapperLeft = params.offsetPxBefore; + else _this.wrapperTop = params.offsetPxBefore; + } + if (params.offsetPxAfter > 0) { + if (isH) _this.wrapperRight = params.offsetPxAfter; + else _this.wrapperBottom = params.offsetPxAfter; + } + + if (params.centeredSlides) { + if (isH) { + _this.wrapperLeft = (containerSize - this.slides[0].getWidth(true, params.roundLengths)) / 2; + _this.wrapperRight = (containerSize - _this.slides[_this.slides.length - 1].getWidth(true, params.roundLengths)) / 2; + } + else { + _this.wrapperTop = (containerSize - _this.slides[0].getHeight(true, params.roundLengths)) / 2; + _this.wrapperBottom = (containerSize - _this.slides[_this.slides.length - 1].getHeight(true, params.roundLengths)) / 2; + } + } + + if (isH) { + if (_this.wrapperLeft >= 0) wrapper.style.paddingLeft = _this.wrapperLeft + 'px'; + if (_this.wrapperRight >= 0) wrapper.style.paddingRight = _this.wrapperRight + 'px'; + } + else { + if (_this.wrapperTop >= 0) wrapper.style.paddingTop = _this.wrapperTop + 'px'; + if (_this.wrapperBottom >= 0) wrapper.style.paddingBottom = _this.wrapperBottom + 'px'; + } + slideLeft = 0; + var centeredSlideLeft = 0; + _this.snapGrid = []; + _this.slidesGrid = []; + + slideMaxHeight = 0; + for (i = 0; i < _this.slides.length; i++) { + slideWidth = _this.slides[i].getWidth(true, params.roundLengths); + slideHeight = _this.slides[i].getHeight(true, params.roundLengths); + if (params.calculateHeight) { + slideMaxHeight = Math.max(slideMaxHeight, slideHeight); + } + var _slideSize = isH ? slideWidth : slideHeight; + if (params.centeredSlides) { + var nextSlideWidth = i === _this.slides.length - 1 ? 0 : _this.slides[i + 1].getWidth(true, params.roundLengths); + var nextSlideHeight = i === _this.slides.length - 1 ? 0 : _this.slides[i + 1].getHeight(true, params.roundLengths); + var nextSlideSize = isH ? nextSlideWidth : nextSlideHeight; + if (_slideSize > containerSize) { + if (params.slidesPerViewFit) { + _this.snapGrid.push(slideLeft + _this.wrapperLeft); + _this.snapGrid.push(slideLeft + _slideSize - containerSize + _this.wrapperLeft); + } + else { + for (var j = 0; j <= Math.floor(_slideSize / (containerSize + _this.wrapperLeft)); j++) { + if (j === 0) _this.snapGrid.push(slideLeft + _this.wrapperLeft); + else _this.snapGrid.push(slideLeft + _this.wrapperLeft + containerSize * j); + } + } + _this.slidesGrid.push(slideLeft + _this.wrapperLeft); + } + else { + _this.snapGrid.push(centeredSlideLeft); + _this.slidesGrid.push(centeredSlideLeft); + } + centeredSlideLeft += _slideSize / 2 + nextSlideSize / 2; + } + else { + if (_slideSize > containerSize) { + if (params.slidesPerViewFit) { + _this.snapGrid.push(slideLeft); + _this.snapGrid.push(slideLeft + _slideSize - containerSize); + } + else { + if (containerSize !== 0) { + for (var k = 0; k <= Math.floor(_slideSize / containerSize); k++) { + _this.snapGrid.push(slideLeft + containerSize * k); + } + } + else { + _this.snapGrid.push(slideLeft); + } + } + + } + else { + _this.snapGrid.push(slideLeft); + } + _this.slidesGrid.push(slideLeft); + } + + slideLeft += _slideSize; + + slidesWidth += slideWidth; + slidesHeight += slideHeight; + } + if (params.calculateHeight) _this.height = slideMaxHeight; + if (isH) { + wrapperSize = slidesWidth + _this.wrapperRight + _this.wrapperLeft; + wrapper.style.width = (slidesWidth) + 'px'; + wrapper.style.height = (_this.height) + 'px'; + } + else { + wrapperSize = slidesHeight + _this.wrapperTop + _this.wrapperBottom; + wrapper.style.width = (_this.width) + 'px'; + wrapper.style.height = (slidesHeight) + 'px'; + } + + } + else if (params.scrollContainer) { + //Scroll Container + wrapper.style.width = ''; + wrapper.style.height = ''; + wrapperWidth = _this.slides[0].getWidth(true, params.roundLengths); + wrapperHeight = _this.slides[0].getHeight(true, params.roundLengths); + wrapperSize = isH ? wrapperWidth : wrapperHeight; + wrapper.style.width = wrapperWidth + 'px'; + wrapper.style.height = wrapperHeight + 'px'; + slideSize = isH ? wrapperWidth : wrapperHeight; + + } + else { + //For usual slides + if (params.calculateHeight) { + slideMaxHeight = 0; + wrapperHeight = 0; + //ResetWrapperSize + if (!isH) _this.container.style.height = ''; + wrapper.style.height = ''; + + for (i = 0; i < _this.slides.length; i++) { + //ResetSlideSize + _this.slides[i].style.height = ''; + slideMaxHeight = Math.max(_this.slides[i].getHeight(true), slideMaxHeight); + if (!isH) wrapperHeight += _this.slides[i].getHeight(true); + } + slideHeight = slideMaxHeight; + _this.height = slideHeight; + + if (isH) wrapperHeight = slideHeight; + else { + containerSize = slideHeight; + _this.container.style.height = containerSize + 'px'; + } + } + else { + slideHeight = isH ? _this.height : _this.height / params.slidesPerView; + if (params.roundLengths) slideHeight = Math.round(slideHeight); + wrapperHeight = isH ? _this.height : _this.slides.length * slideHeight; + } + slideWidth = isH ? _this.width / params.slidesPerView : _this.width; + if (params.roundLengths) slideWidth = Math.round(slideWidth); + wrapperWidth = isH ? _this.slides.length * slideWidth : _this.width; + slideSize = isH ? slideWidth : slideHeight; + + if (params.offsetSlidesBefore > 0) { + if (isH) _this.wrapperLeft = slideSize * params.offsetSlidesBefore; + else _this.wrapperTop = slideSize * params.offsetSlidesBefore; + } + if (params.offsetSlidesAfter > 0) { + if (isH) _this.wrapperRight = slideSize * params.offsetSlidesAfter; + else _this.wrapperBottom = slideSize * params.offsetSlidesAfter; + } + if (params.offsetPxBefore > 0) { + if (isH) _this.wrapperLeft = params.offsetPxBefore; + else _this.wrapperTop = params.offsetPxBefore; + } + if (params.offsetPxAfter > 0) { + if (isH) _this.wrapperRight = params.offsetPxAfter; + else _this.wrapperBottom = params.offsetPxAfter; + } + if (params.centeredSlides) { + if (isH) { + _this.wrapperLeft = (containerSize - slideSize) / 2; + _this.wrapperRight = (containerSize - slideSize) / 2; + } + else { + _this.wrapperTop = (containerSize - slideSize) / 2; + _this.wrapperBottom = (containerSize - slideSize) / 2; + } + } + if (isH) { + if (_this.wrapperLeft > 0) wrapper.style.paddingLeft = _this.wrapperLeft + 'px'; + if (_this.wrapperRight > 0) wrapper.style.paddingRight = _this.wrapperRight + 'px'; + } + else { + if (_this.wrapperTop > 0) wrapper.style.paddingTop = _this.wrapperTop + 'px'; + if (_this.wrapperBottom > 0) wrapper.style.paddingBottom = _this.wrapperBottom + 'px'; + } + + wrapperSize = isH ? wrapperWidth + _this.wrapperRight + _this.wrapperLeft : wrapperHeight + _this.wrapperTop + _this.wrapperBottom; + if (!params.cssWidthAndHeight) { + if (parseFloat(wrapperWidth) > 0) { + wrapper.style.width = wrapperWidth + 'px'; + } + if (parseFloat(wrapperHeight) > 0) { + wrapper.style.height = wrapperHeight + 'px'; + } + } + slideLeft = 0; + _this.snapGrid = []; + _this.slidesGrid = []; + for (i = 0; i < _this.slides.length; i++) { + _this.snapGrid.push(slideLeft); + _this.slidesGrid.push(slideLeft); + slideLeft += slideSize; + if (!params.cssWidthAndHeight) { + if (parseFloat(slideWidth) > 0) { + _this.slides[i].style.width = slideWidth + 'px'; + } + if (parseFloat(slideHeight) > 0) { + _this.slides[i].style.height = slideHeight + 'px'; + } + } + } + + } + + if (!_this.initialized) { + _this.callPlugins('onFirstInit'); + if (params.onFirstInit) _this.fireCallback(params.onFirstInit, _this); + } + else { + _this.callPlugins('onInit'); + if (params.onInit) _this.fireCallback(params.onInit, _this); + } + _this.initialized = true; + }; + + _this.reInit = function (forceCalcSlides) { + _this.init(true, forceCalcSlides); + }; + + _this.resizeFix = function (reInit) { + _this.callPlugins('beforeResizeFix'); + + _this.init(params.resizeReInit || reInit); + + // swipe to active slide in fixed mode + if (!params.freeMode) { + _this.swipeTo((params.loop ? _this.activeLoopIndex : _this.activeIndex), 0, false); + // Fix autoplay + if (params.autoplay) { + if (_this.support.transitions && typeof autoplayTimeoutId !== 'undefined') { + if (typeof autoplayTimeoutId !== 'undefined') { + clearTimeout(autoplayTimeoutId); + autoplayTimeoutId = undefined; + _this.startAutoplay(); + } + } + else { + if (typeof autoplayIntervalId !== 'undefined') { + clearInterval(autoplayIntervalId); + autoplayIntervalId = undefined; + _this.startAutoplay(); + } + } + } + } + // move wrapper to the beginning in free mode + else if (_this.getWrapperTranslate() < -maxWrapperPosition()) { + _this.setWrapperTransition(0); + _this.setWrapperTranslate(-maxWrapperPosition()); + } + + _this.callPlugins('afterResizeFix'); + }; + + /*========================================== + Max and Min Positions + ============================================*/ + function maxWrapperPosition() { + var a = (wrapperSize - containerSize); + if (params.freeMode) { + a = wrapperSize - containerSize; + } + // if (params.loop) a -= containerSize; + if (params.slidesPerView > _this.slides.length && !params.centeredSlides) { + a = 0; + } + if (a < 0) a = 0; + return a; + } + + /*========================================== + Event Listeners + ============================================*/ + function initEvents() { + var bind = _this.h.addEventListener; + var eventTarget = params.eventTarget === 'wrapper' ? _this.wrapper : _this.container; + //Touch Events + if (! (_this.browser.ie10 || _this.browser.ie11)) { + if (_this.support.touch) { + bind(eventTarget, 'touchstart', onTouchStart); + bind(eventTarget, 'touchmove', onTouchMove); + bind(eventTarget, 'touchend', onTouchEnd); + } + if (params.simulateTouch) { + bind(eventTarget, 'mousedown', onTouchStart); + bind(document, 'mousemove', onTouchMove); + bind(document, 'mouseup', onTouchEnd); + } + } + else { + bind(eventTarget, _this.touchEvents.touchStart, onTouchStart); + bind(document, _this.touchEvents.touchMove, onTouchMove); + bind(document, _this.touchEvents.touchEnd, onTouchEnd); + } + + //Resize Event + if (params.autoResize) { + bind(window, 'resize', _this.resizeFix); + } + //Slide Events + addSlideEvents(); + //Mousewheel + _this._wheelEvent = false; + if (params.mousewheelControl) { + if (document.onmousewheel !== undefined) { + _this._wheelEvent = 'mousewheel'; + } + if (!_this._wheelEvent) { + try { + new WheelEvent('wheel'); + _this._wheelEvent = 'wheel'; + } catch (e) {} + } + if (!_this._wheelEvent) { + _this._wheelEvent = 'DOMMouseScroll'; + } + if (_this._wheelEvent) { + bind(_this.container, _this._wheelEvent, handleMousewheel); + } + } + + //Keyboard + function _loadImage(src) { + var image = new Image(); + image.onload = function () { + if (_this && _this.imagesLoaded !== undefined) _this.imagesLoaded++; + if (_this.imagesLoaded === _this.imagesToLoad.length) { + _this.reInit(); + if (params.onImagesReady) _this.fireCallback(params.onImagesReady, _this); + } + }; + image.src = src; + } + + if (params.keyboardControl) { + bind(document, 'keydown', handleKeyboardKeys); + } + if (params.updateOnImagesReady) { + _this.imagesToLoad = $$('img', _this.container); + + for (var i = 0; i < _this.imagesToLoad.length; i++) { + _loadImage(_this.imagesToLoad[i].getAttribute('src')); + } + } + } + + //Remove Event Listeners + _this.destroy = function () { + var unbind = _this.h.removeEventListener; + var eventTarget = params.eventTarget === 'wrapper' ? _this.wrapper : _this.container; + //Touch Events + if (! (_this.browser.ie10 || _this.browser.ie11)) { + if (_this.support.touch) { + unbind(eventTarget, 'touchstart', onTouchStart); + unbind(eventTarget, 'touchmove', onTouchMove); + unbind(eventTarget, 'touchend', onTouchEnd); + } + if (params.simulateTouch) { + unbind(eventTarget, 'mousedown', onTouchStart); + unbind(document, 'mousemove', onTouchMove); + unbind(document, 'mouseup', onTouchEnd); + } + } + else { + unbind(eventTarget, _this.touchEvents.touchStart, onTouchStart); + unbind(document, _this.touchEvents.touchMove, onTouchMove); + unbind(document, _this.touchEvents.touchEnd, onTouchEnd); + } + + //Resize Event + if (params.autoResize) { + unbind(window, 'resize', _this.resizeFix); + } + + //Init Slide Events + removeSlideEvents(); + + //Pagination + if (params.paginationClickable) { + removePaginationEvents(); + } + + //Mousewheel + if (params.mousewheelControl && _this._wheelEvent) { + unbind(_this.container, _this._wheelEvent, handleMousewheel); + } + + //Keyboard + if (params.keyboardControl) { + unbind(document, 'keydown', handleKeyboardKeys); + } + + //Stop autoplay + if (params.autoplay) { + _this.stopAutoplay(); + } + _this.callPlugins('onDestroy'); + + //Destroy variable + _this = null; + }; + + function addSlideEvents() { + var bind = _this.h.addEventListener, + i; + + //Prevent Links Events + if (params.preventLinks) { + var links = $$('a', _this.container); + for (i = 0; i < links.length; i++) { + bind(links[i], 'click', preventClick); + } + } + //Release Form Elements + if (params.releaseFormElements) { + var formElements = $$('input, textarea, select', _this.container); + for (i = 0; i < formElements.length; i++) { + bind(formElements[i], _this.touchEvents.touchStart, releaseForms, true); + } + } + + //Slide Clicks & Touches + if (params.onSlideClick) { + for (i = 0; i < _this.slides.length; i++) { + bind(_this.slides[i], 'click', slideClick); + } + } + if (params.onSlideTouch) { + for (i = 0; i < _this.slides.length; i++) { + bind(_this.slides[i], _this.touchEvents.touchStart, slideTouch); + } + } + } + function removeSlideEvents() { + var unbind = _this.h.removeEventListener, + i; + + //Slide Clicks & Touches + if (params.onSlideClick) { + for (i = 0; i < _this.slides.length; i++) { + unbind(_this.slides[i], 'click', slideClick); + } + } + if (params.onSlideTouch) { + for (i = 0; i < _this.slides.length; i++) { + unbind(_this.slides[i], _this.touchEvents.touchStart, slideTouch); + } + } + //Release Form Elements + if (params.releaseFormElements) { + var formElements = $$('input, textarea, select', _this.container); + for (i = 0; i < formElements.length; i++) { + unbind(formElements[i], _this.touchEvents.touchStart, releaseForms, true); + } + } + //Prevent Links Events + if (params.preventLinks) { + var links = $$('a', _this.container); + for (i = 0; i < links.length; i++) { + unbind(links[i], 'click', preventClick); + } + } + } + /*========================================== + Keyboard Control + ============================================*/ + function handleKeyboardKeys(e) { + var kc = e.keyCode || e.charCode; + if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey) return; + if (kc === 37 || kc === 39 || kc === 38 || kc === 40) { + var inView = false; + //Check that swiper should be inside of visible area of window + var swiperOffset = _this.h.getOffset(_this.container); + var scrollLeft = _this.h.windowScroll().left; + var scrollTop = _this.h.windowScroll().top; + var windowWidth = _this.h.windowWidth(); + var windowHeight = _this.h.windowHeight(); + var swiperCoord = [ + [swiperOffset.left, swiperOffset.top], + [swiperOffset.left + _this.width, swiperOffset.top], + [swiperOffset.left, swiperOffset.top + _this.height], + [swiperOffset.left + _this.width, swiperOffset.top + _this.height] + ]; + for (var i = 0; i < swiperCoord.length; i++) { + var point = swiperCoord[i]; + if ( + point[0] >= scrollLeft && point[0] <= scrollLeft + windowWidth && + point[1] >= scrollTop && point[1] <= scrollTop + windowHeight + ) { + inView = true; + } + + } + if (!inView) return; + } + if (isH) { + if (kc === 37 || kc === 39) { + if (e.preventDefault) e.preventDefault(); + else e.returnValue = false; + } + if (kc === 39) _this.swipeNext(); + if (kc === 37) _this.swipePrev(); + } + else { + if (kc === 38 || kc === 40) { + if (e.preventDefault) e.preventDefault(); + else e.returnValue = false; + } + if (kc === 40) _this.swipeNext(); + if (kc === 38) _this.swipePrev(); + } + } + + _this.disableKeyboardControl = function () { + params.keyboardControl = false; + _this.h.removeEventListener(document, 'keydown', handleKeyboardKeys); + }; + + _this.enableKeyboardControl = function () { + params.keyboardControl = true; + _this.h.addEventListener(document, 'keydown', handleKeyboardKeys); + }; + + /*========================================== + Mousewheel Control + ============================================*/ + var lastScrollTime = (new Date()).getTime(); + function handleMousewheel(e) { + var we = _this._wheelEvent; + var delta = 0; + + //Opera & IE + if (e.detail) delta = -e.detail; + //WebKits + else if (we === 'mousewheel') { + if (params.mousewheelControlForceToAxis) { + if (isH) { + if (Math.abs(e.wheelDeltaX) > Math.abs(e.wheelDeltaY)) delta = e.wheelDeltaX; + else return; + } + else { + if (Math.abs(e.wheelDeltaY) > Math.abs(e.wheelDeltaX)) delta = e.wheelDeltaY; + else return; + } + } + else { + delta = e.wheelDelta; + } + } + //Old FireFox + else if (we === 'DOMMouseScroll') delta = -e.detail; + //New FireFox + else if (we === 'wheel') { + if (params.mousewheelControlForceToAxis) { + if (isH) { + if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) delta = -e.deltaX; + else return; + } + else { + if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) delta = -e.deltaY; + else return; + } + } + else { + delta = Math.abs(e.deltaX) > Math.abs(e.deltaY) ? - e.deltaX : - e.deltaY; + } + } + + if (!params.freeMode) { + if ((new Date()).getTime() - lastScrollTime > 60) { + if (delta < 0) _this.swipeNext(); + else _this.swipePrev(); + } + lastScrollTime = (new Date()).getTime(); + + } + else { + //Freemode or scrollContainer: + var position = _this.getWrapperTranslate() + delta; + + if (position > 0) position = 0; + if (position < -maxWrapperPosition()) position = -maxWrapperPosition(); + + _this.setWrapperTransition(0); + _this.setWrapperTranslate(position); + _this.updateActiveSlide(position); + + // Return page scroll on edge positions + if (position === 0 || position === -maxWrapperPosition()) return; + } + if (params.autoplay) _this.stopAutoplay(true); + + if (e.preventDefault) e.preventDefault(); + else e.returnValue = false; + return false; + } + _this.disableMousewheelControl = function () { + if (!_this._wheelEvent) return false; + params.mousewheelControl = false; + _this.h.removeEventListener(_this.container, _this._wheelEvent, handleMousewheel); + return true; + }; + + _this.enableMousewheelControl = function () { + if (!_this._wheelEvent) return false; + params.mousewheelControl = true; + _this.h.addEventListener(_this.container, _this._wheelEvent, handleMousewheel); + return true; + }; + + /*========================= + Grab Cursor + ===========================*/ + if (params.grabCursor) { + var containerStyle = _this.container.style; + containerStyle.cursor = 'move'; + containerStyle.cursor = 'grab'; + containerStyle.cursor = '-moz-grab'; + containerStyle.cursor = '-webkit-grab'; + } + + /*========================= + Slides Events Handlers + ===========================*/ + + _this.allowSlideClick = true; + function slideClick(event) { + if (_this.allowSlideClick) { + setClickedSlide(event); + _this.fireCallback(params.onSlideClick, _this, event); + } + } + + function slideTouch(event) { + setClickedSlide(event); + _this.fireCallback(params.onSlideTouch, _this, event); + } + + function setClickedSlide(event) { + + // IE 6-8 support + if (!event.currentTarget) { + var element = event.srcElement; + do { + if (element.className.indexOf(params.slideClass) > -1) { + break; + } + element = element.parentNode; + } while (element); + _this.clickedSlide = element; + } + else { + _this.clickedSlide = event.currentTarget; + } + + _this.clickedSlideIndex = _this.slides.indexOf(_this.clickedSlide); + _this.clickedSlideLoopIndex = _this.clickedSlideIndex - (_this.loopedSlides || 0); + } + + _this.allowLinks = true; + function preventClick(e) { + if (!_this.allowLinks) { + if (e.preventDefault) e.preventDefault(); + else e.returnValue = false; + if (params.preventLinksPropagation && 'stopPropagation' in e) { + e.stopPropagation(); + } + return false; + } + } + function releaseForms(e) { + if (e.stopPropagation) e.stopPropagation(); + else e.returnValue = false; + return false; + + } + + /*================================================== + Event Handlers + ====================================================*/ + var isTouchEvent = false; + var allowThresholdMove; + var allowMomentumBounce = true; + function onTouchStart(event) { + if (params.preventLinks) _this.allowLinks = true; + //Exit if slider is already was touched + if (_this.isTouched || params.onlyExternal) { + return false; + } + + if (params.noSwiping && (event.target || event.srcElement) && noSwipingSlide(event.target || event.srcElement)) return false; + allowMomentumBounce = false; + //Check For Nested Swipers + _this.isTouched = true; + isTouchEvent = event.type === 'touchstart'; + + if (!isTouchEvent || event.targetTouches.length === 1) { + _this.callPlugins('onTouchStartBegin'); + + if (!isTouchEvent && !_this.isAndroid) { + if (event.preventDefault) event.preventDefault(); + else event.returnValue = false; + } + + var pageX = isTouchEvent ? event.targetTouches[0].pageX : (event.pageX || event.clientX); + var pageY = isTouchEvent ? event.targetTouches[0].pageY : (event.pageY || event.clientY); + + //Start Touches to check the scrolling + _this.touches.startX = _this.touches.currentX = pageX; + _this.touches.startY = _this.touches.currentY = pageY; + + _this.touches.start = _this.touches.current = isH ? pageX : pageY; + + //Set Transition Time to 0 + _this.setWrapperTransition(0); + + //Get Start Translate Position + _this.positions.start = _this.positions.current = _this.getWrapperTranslate(); + + //Set Transform + _this.setWrapperTranslate(_this.positions.start); + + //TouchStartTime + _this.times.start = (new Date()).getTime(); + + //Unset Scrolling + isScrolling = undefined; + + //Set Treshold + if (params.moveStartThreshold > 0) { + allowThresholdMove = false; + } + + //CallBack + if (params.onTouchStart) _this.fireCallback(params.onTouchStart, _this, event); + _this.callPlugins('onTouchStartEnd'); + + } + } + var velocityPrevPosition, velocityPrevTime; + function onTouchMove(event) { + // If slider is not touched - exit + if (!_this.isTouched || params.onlyExternal) return; + if (isTouchEvent && event.type === 'mousemove') return; + + var pageX = isTouchEvent ? event.targetTouches[0].pageX : (event.pageX || event.clientX); + var pageY = isTouchEvent ? event.targetTouches[0].pageY : (event.pageY || event.clientY); + + //check for scrolling + if (typeof isScrolling === 'undefined' && isH) { + isScrolling = !!(isScrolling || Math.abs(pageY - _this.touches.startY) > Math.abs(pageX - _this.touches.startX)); + } + if (typeof isScrolling === 'undefined' && !isH) { + isScrolling = !!(isScrolling || Math.abs(pageY - _this.touches.startY) < Math.abs(pageX - _this.touches.startX)); + } + if (isScrolling) { + _this.isTouched = false; + return; + } + + //Check For Nested Swipers + if (event.assignedToSwiper) { + _this.isTouched = false; + return; + } + event.assignedToSwiper = true; + + //Block inner links + if (params.preventLinks) { + _this.allowLinks = false; + } + if (params.onSlideClick) { + _this.allowSlideClick = false; + } + + //Stop AutoPlay if exist + if (params.autoplay) { + _this.stopAutoplay(true); + } + if (!isTouchEvent || event.touches.length === 1) { + + //Moved Flag + if (!_this.isMoved) { + _this.callPlugins('onTouchMoveStart'); + + if (params.loop) { + _this.fixLoop(); + _this.positions.start = _this.getWrapperTranslate(); + } + if (params.onTouchMoveStart) _this.fireCallback(params.onTouchMoveStart, _this); + } + _this.isMoved = true; + + // cancel event + if (event.preventDefault) event.preventDefault(); + else event.returnValue = false; + + _this.touches.current = isH ? pageX : pageY; + + _this.positions.current = (_this.touches.current - _this.touches.start) * params.touchRatio + _this.positions.start; + + //Resistance Callbacks + if (_this.positions.current > 0 && params.onResistanceBefore) { + _this.fireCallback(params.onResistanceBefore, _this, _this.positions.current); + } + if (_this.positions.current < -maxWrapperPosition() && params.onResistanceAfter) { + _this.fireCallback(params.onResistanceAfter, _this, Math.abs(_this.positions.current + maxWrapperPosition())); + } + //Resistance + if (params.resistance && params.resistance !== '100%') { + var resistance; + //Resistance for Negative-Back sliding + if (_this.positions.current > 0) { + resistance = 1 - _this.positions.current / containerSize / 2; + if (resistance < 0.5) + _this.positions.current = (containerSize / 2); + else + _this.positions.current = _this.positions.current * resistance; + } + //Resistance for After-End Sliding + if (_this.positions.current < -maxWrapperPosition()) { + + var diff = (_this.touches.current - _this.touches.start) * params.touchRatio + (maxWrapperPosition() + _this.positions.start); + resistance = (containerSize + diff) / (containerSize); + var newPos = _this.positions.current - diff * (1 - resistance) / 2; + var stopPos = -maxWrapperPosition() - containerSize / 2; + + if (newPos < stopPos || resistance <= 0) + _this.positions.current = stopPos; + else + _this.positions.current = newPos; + } + } + if (params.resistance && params.resistance === '100%') { + //Resistance for Negative-Back sliding + if (_this.positions.current > 0 && !(params.freeMode && !params.freeModeFluid)) { + _this.positions.current = 0; + } + //Resistance for After-End Sliding + if (_this.positions.current < -maxWrapperPosition() && !(params.freeMode && !params.freeModeFluid)) { + _this.positions.current = -maxWrapperPosition(); + } + } + //Move Slides + if (!params.followFinger) return; + + if (!params.moveStartThreshold) { + _this.setWrapperTranslate(_this.positions.current); + } + else { + if (Math.abs(_this.touches.current - _this.touches.start) > params.moveStartThreshold || allowThresholdMove) { + if (!allowThresholdMove) { + allowThresholdMove = true; + _this.touches.start = _this.touches.current; + return; + } + _this.setWrapperTranslate(_this.positions.current); + } + else { + _this.positions.current = _this.positions.start; + } + } + + if (params.freeMode || params.watchActiveIndex) { + _this.updateActiveSlide(_this.positions.current); + } + + //Grab Cursor + if (params.grabCursor) { + _this.container.style.cursor = 'move'; + _this.container.style.cursor = 'grabbing'; + _this.container.style.cursor = '-moz-grabbin'; + _this.container.style.cursor = '-webkit-grabbing'; + } + //Velocity + if (!velocityPrevPosition) velocityPrevPosition = _this.touches.current; + if (!velocityPrevTime) velocityPrevTime = (new Date()).getTime(); + _this.velocity = (_this.touches.current - velocityPrevPosition) / ((new Date()).getTime() - velocityPrevTime) / 2; + if (Math.abs(_this.touches.current - velocityPrevPosition) < 2) _this.velocity = 0; + velocityPrevPosition = _this.touches.current; + velocityPrevTime = (new Date()).getTime(); + //Callbacks + _this.callPlugins('onTouchMoveEnd'); + if (params.onTouchMove) _this.fireCallback(params.onTouchMove, _this, event); + + return false; + } + } + function onTouchEnd(event) { + //Check For scrolling + if (isScrolling) { + _this.swipeReset(); + } + // If slider is not touched exit + if (params.onlyExternal || !_this.isTouched) return; + _this.isTouched = false; + + //Return Grab Cursor + if (params.grabCursor) { + _this.container.style.cursor = 'move'; + _this.container.style.cursor = 'grab'; + _this.container.style.cursor = '-moz-grab'; + _this.container.style.cursor = '-webkit-grab'; + } + + //Check for Current Position + if (!_this.positions.current && _this.positions.current !== 0) { + _this.positions.current = _this.positions.start; + } + + //For case if slider touched but not moved + if (params.followFinger) { + _this.setWrapperTranslate(_this.positions.current); + } + + // TouchEndTime + _this.times.end = (new Date()).getTime(); + + //Difference + _this.touches.diff = _this.touches.current - _this.touches.start; + _this.touches.abs = Math.abs(_this.touches.diff); + + _this.positions.diff = _this.positions.current - _this.positions.start; + _this.positions.abs = Math.abs(_this.positions.diff); + + var diff = _this.positions.diff; + var diffAbs = _this.positions.abs; + var timeDiff = _this.times.end - _this.times.start; + + if (diffAbs < 5 && (timeDiff) < 300 && _this.allowLinks === false) { + if (!params.freeMode && diffAbs !== 0) _this.swipeReset(); + //Release inner links + if (params.preventLinks) { + _this.allowLinks = true; + } + if (params.onSlideClick) { + _this.allowSlideClick = true; + } + } + + setTimeout(function () { + //Release inner links + if (params.preventLinks) { + _this.allowLinks = true; + } + if (params.onSlideClick) { + _this.allowSlideClick = true; + } + }, 100); + + var maxPosition = maxWrapperPosition(); + + //Not moved or Prevent Negative Back Sliding/After-End Sliding + if (!_this.isMoved && params.freeMode) { + _this.isMoved = false; + if (params.onTouchEnd) _this.fireCallback(params.onTouchEnd, _this, event); + _this.callPlugins('onTouchEnd'); + return; + } + if (!_this.isMoved || _this.positions.current > 0 || _this.positions.current < -maxPosition) { + _this.swipeReset(); + if (params.onTouchEnd) _this.fireCallback(params.onTouchEnd, _this, event); + _this.callPlugins('onTouchEnd'); + return; + } + + _this.isMoved = false; + + //Free Mode + if (params.freeMode) { + if (params.freeModeFluid) { + var momentumDuration = 1000 * params.momentumRatio; + var momentumDistance = _this.velocity * momentumDuration; + var newPosition = _this.positions.current + momentumDistance; + var doBounce = false; + var afterBouncePosition; + var bounceAmount = Math.abs(_this.velocity) * 20 * params.momentumBounceRatio; + if (newPosition < -maxPosition) { + if (params.momentumBounce && _this.support.transitions) { + if (newPosition + maxPosition < -bounceAmount) newPosition = -maxPosition - bounceAmount; + afterBouncePosition = -maxPosition; + doBounce = true; + allowMomentumBounce = true; + } + else newPosition = -maxPosition; + } + if (newPosition > 0) { + if (params.momentumBounce && _this.support.transitions) { + if (newPosition > bounceAmount) newPosition = bounceAmount; + afterBouncePosition = 0; + doBounce = true; + allowMomentumBounce = true; + } + else newPosition = 0; + } + //Fix duration + if (_this.velocity !== 0) momentumDuration = Math.abs((newPosition - _this.positions.current) / _this.velocity); + + _this.setWrapperTranslate(newPosition); + + _this.setWrapperTransition(momentumDuration); + + if (params.momentumBounce && doBounce) { + _this.wrapperTransitionEnd(function () { + if (!allowMomentumBounce) return; + if (params.onMomentumBounce) _this.fireCallback(params.onMomentumBounce, _this); + _this.callPlugins('onMomentumBounce'); + + _this.setWrapperTranslate(afterBouncePosition); + _this.setWrapperTransition(300); + }); + } + + _this.updateActiveSlide(newPosition); + } + if (!params.freeModeFluid || timeDiff >= 300) _this.updateActiveSlide(_this.positions.current); + + if (params.onTouchEnd) _this.fireCallback(params.onTouchEnd, _this, event); + _this.callPlugins('onTouchEnd'); + return; + } + + //Direction + direction = diff < 0 ? 'toNext' : 'toPrev'; + + //Short Touches + if (direction === 'toNext' && (timeDiff <= 300)) { + if (diffAbs < 30 || !params.shortSwipes) _this.swipeReset(); + else _this.swipeNext(true); + } + + if (direction === 'toPrev' && (timeDiff <= 300)) { + if (diffAbs < 30 || !params.shortSwipes) _this.swipeReset(); + else _this.swipePrev(true); + } + + //Long Touches + var targetSlideSize = 0; + if (params.slidesPerView === 'auto') { + //Define current slide's width + var currentPosition = Math.abs(_this.getWrapperTranslate()); + var slidesOffset = 0; + var _slideSize; + for (var i = 0; i < _this.slides.length; i++) { + _slideSize = isH ? _this.slides[i].getWidth(true, params.roundLengths) : _this.slides[i].getHeight(true, params.roundLengths); + slidesOffset += _slideSize; + if (slidesOffset > currentPosition) { + targetSlideSize = _slideSize; + break; + } + } + if (targetSlideSize > containerSize) targetSlideSize = containerSize; + } + else { + targetSlideSize = slideSize * params.slidesPerView; + } + if (direction === 'toNext' && (timeDiff > 300)) { + if (diffAbs >= targetSlideSize * params.longSwipesRatio) { + _this.swipeNext(true); + } + else { + _this.swipeReset(); + } + } + if (direction === 'toPrev' && (timeDiff > 300)) { + if (diffAbs >= targetSlideSize * params.longSwipesRatio) { + _this.swipePrev(true); + } + else { + _this.swipeReset(); + } + } + if (params.onTouchEnd) _this.fireCallback(params.onTouchEnd, _this, event); + _this.callPlugins('onTouchEnd'); + } + + + /*================================================== + noSwiping Bubble Check by Isaac Strack + ====================================================*/ + function noSwipingSlide(el) { + /*This function is specifically designed to check the parent elements for the noSwiping class, up to the wrapper. + We need to check parents because while onTouchStart bubbles, _this.isTouched is checked in onTouchStart, which stops the bubbling. + So, if a text box, for example, is the initial target, and the parent slide container has the noSwiping class, the _this.isTouched + check will never find it, and what was supposed to be noSwiping is able to be swiped. + This function will iterate up and check for the noSwiping class in parents, up through the wrapperClass.*/ + + // First we create a truthy variable, which is that swiping is allowd (noSwiping = false) + var noSwiping = false; + + // Now we iterate up (parentElements) until we reach the node with the wrapperClass. + do { + + // Each time, we check to see if there's a 'swiper-no-swiping' class (noSwipingClass). + if (el.className.indexOf(params.noSwipingClass) > -1) + { + noSwiping = true; // If there is, we set noSwiping = true; + } + + el = el.parentElement; // now we iterate up (parent node) + + } while (!noSwiping && el.parentElement && el.className.indexOf(params.wrapperClass) === -1); // also include el.parentElement truthy, just in case. + + // because we didn't check the wrapper itself, we do so now, if noSwiping is false: + if (!noSwiping && el.className.indexOf(params.wrapperClass) > -1 && el.className.indexOf(params.noSwipingClass) > -1) + noSwiping = true; // if the wrapper has the noSwipingClass, we set noSwiping = true; + + return noSwiping; + } + + function addClassToHtmlString(klass, outerHtml) { + var par = document.createElement('div'); + var child; + + par.innerHTML = outerHtml; + child = par.firstChild; + child.className += ' ' + klass; + + return child.outerHTML; + } + + + /*================================================== + Swipe Functions + ====================================================*/ + _this.swipeNext = function (internal) { + if (!internal && params.loop) _this.fixLoop(); + if (!internal && params.autoplay) _this.stopAutoplay(true); + _this.callPlugins('onSwipeNext'); + var currentPosition = _this.getWrapperTranslate(); + var newPosition = currentPosition; + if (params.slidesPerView === 'auto') { + for (var i = 0; i < _this.snapGrid.length; i++) { + if (-currentPosition >= _this.snapGrid[i] && -currentPosition < _this.snapGrid[i + 1]) { + newPosition = -_this.snapGrid[i + 1]; + break; + } + } + } + else { + var groupSize = slideSize * params.slidesPerGroup; + newPosition = -(Math.floor(Math.abs(currentPosition) / Math.floor(groupSize)) * groupSize + groupSize); + } + if (newPosition < -maxWrapperPosition()) { + newPosition = -maxWrapperPosition(); + } + if (newPosition === currentPosition) return false; + swipeToPosition(newPosition, 'next'); + return true; + }; + _this.swipePrev = function (internal) { + if (!internal && params.loop) _this.fixLoop(); + if (!internal && params.autoplay) _this.stopAutoplay(true); + _this.callPlugins('onSwipePrev'); + + var currentPosition = Math.ceil(_this.getWrapperTranslate()); + var newPosition; + if (params.slidesPerView === 'auto') { + newPosition = 0; + for (var i = 1; i < _this.snapGrid.length; i++) { + if (-currentPosition === _this.snapGrid[i]) { + newPosition = -_this.snapGrid[i - 1]; + break; + } + if (-currentPosition > _this.snapGrid[i] && -currentPosition < _this.snapGrid[i + 1]) { + newPosition = -_this.snapGrid[i]; + break; + } + } + } + else { + var groupSize = slideSize * params.slidesPerGroup; + newPosition = -(Math.ceil(-currentPosition / groupSize) - 1) * groupSize; + } + + if (newPosition > 0) newPosition = 0; + + if (newPosition === currentPosition) return false; + swipeToPosition(newPosition, 'prev'); + return true; + + }; + _this.swipeReset = function () { + _this.callPlugins('onSwipeReset'); + var currentPosition = _this.getWrapperTranslate(); + var groupSize = slideSize * params.slidesPerGroup; + var newPosition; + var maxPosition = -maxWrapperPosition(); + if (params.slidesPerView === 'auto') { + newPosition = 0; + for (var i = 0; i < _this.snapGrid.length; i++) { + if (-currentPosition === _this.snapGrid[i]) return; + if (-currentPosition >= _this.snapGrid[i] && -currentPosition < _this.snapGrid[i + 1]) { + if (_this.positions.diff > 0) newPosition = -_this.snapGrid[i + 1]; + else newPosition = -_this.snapGrid[i]; + break; + } + } + if (-currentPosition >= _this.snapGrid[_this.snapGrid.length - 1]) newPosition = -_this.snapGrid[_this.snapGrid.length - 1]; + if (currentPosition <= -maxWrapperPosition()) newPosition = -maxWrapperPosition(); + } + else { + newPosition = currentPosition < 0 ? Math.round(currentPosition / groupSize) * groupSize : 0; + } + if (params.scrollContainer) { + newPosition = currentPosition < 0 ? currentPosition : 0; + } + if (newPosition < -maxWrapperPosition()) { + newPosition = -maxWrapperPosition(); + } + if (params.scrollContainer && (containerSize > slideSize)) { + newPosition = 0; + } + + if (newPosition === currentPosition) return false; + + swipeToPosition(newPosition, 'reset'); + return true; + }; + + _this.swipeTo = function (index, speed, runCallbacks) { + index = parseInt(index, 10); + _this.callPlugins('onSwipeTo', {index: index, speed: speed}); + if (params.loop) index = index + _this.loopedSlides; + var currentPosition = _this.getWrapperTranslate(); + if (index > (_this.slides.length - 1) || index < 0) return; + var newPosition; + if (params.slidesPerView === 'auto') { + newPosition = -_this.slidesGrid[index]; + } + else { + newPosition = -index * slideSize; + } + if (newPosition < - maxWrapperPosition()) { + newPosition = - maxWrapperPosition(); + } + + if (newPosition === currentPosition) return false; + + runCallbacks = runCallbacks === false ? false : true; + swipeToPosition(newPosition, 'to', {index: index, speed: speed, runCallbacks: runCallbacks}); + return true; + }; + + function swipeToPosition(newPosition, action, toOptions) { + var speed = (action === 'to' && toOptions.speed >= 0) ? toOptions.speed : params.speed; + var timeOld = + new Date(); + + function anim() { + var timeNew = + new Date(); + var time = timeNew - timeOld; + currentPosition += animationStep * time / (1000 / 60); + condition = direction === 'toNext' ? currentPosition > newPosition : currentPosition < newPosition; + if (condition) { + _this.setWrapperTranslate(Math.round(currentPosition)); + _this._DOMAnimating = true; + window.setTimeout(function () { + anim(); + }, 1000 / 60); + } + else { + if (params.onSlideChangeEnd) { + if (action === 'to') { + if (toOptions.runCallbacks === true) _this.fireCallback(params.onSlideChangeEnd, _this); + } + else { + _this.fireCallback(params.onSlideChangeEnd, _this); + } + + } + _this.setWrapperTranslate(newPosition); + _this._DOMAnimating = false; + } + } + + if (_this.support.transitions || !params.DOMAnimation) { + _this.setWrapperTranslate(newPosition); + _this.setWrapperTransition(speed); + } + else { + //Try the DOM animation + var currentPosition = _this.getWrapperTranslate(); + var animationStep = Math.ceil((newPosition - currentPosition) / speed * (1000 / 60)); + var direction = currentPosition > newPosition ? 'toNext' : 'toPrev'; + var condition = direction === 'toNext' ? currentPosition > newPosition : currentPosition < newPosition; + if (_this._DOMAnimating) return; + + anim(); + } + + //Update Active Slide Index + _this.updateActiveSlide(newPosition); + + //Callbacks + if (params.onSlideNext && action === 'next') { + _this.fireCallback(params.onSlideNext, _this, newPosition); + } + if (params.onSlidePrev && action === 'prev') { + _this.fireCallback(params.onSlidePrev, _this, newPosition); + } + //'Reset' Callback + if (params.onSlideReset && action === 'reset') { + _this.fireCallback(params.onSlideReset, _this, newPosition); + } + + //'Next', 'Prev' and 'To' Callbacks + if (action === 'next' || action === 'prev' || (action === 'to' && toOptions.runCallbacks === true)) + slideChangeCallbacks(action); + } + /*================================================== + Transition Callbacks + ====================================================*/ + //Prevent Multiple Callbacks + _this._queueStartCallbacks = false; + _this._queueEndCallbacks = false; + function slideChangeCallbacks(direction) { + //Transition Start Callback + _this.callPlugins('onSlideChangeStart'); + if (params.onSlideChangeStart) { + if (params.queueStartCallbacks && _this.support.transitions) { + if (_this._queueStartCallbacks) return; + _this._queueStartCallbacks = true; + _this.fireCallback(params.onSlideChangeStart, _this, direction); + _this.wrapperTransitionEnd(function () { + _this._queueStartCallbacks = false; + }); + } + else _this.fireCallback(params.onSlideChangeStart, _this, direction); + } + //Transition End Callback + if (params.onSlideChangeEnd) { + if (_this.support.transitions) { + if (params.queueEndCallbacks) { + if (_this._queueEndCallbacks) return; + _this._queueEndCallbacks = true; + _this.wrapperTransitionEnd(function (swiper) { + _this.fireCallback(params.onSlideChangeEnd, swiper, direction); + }); + } + else { + _this.wrapperTransitionEnd(function (swiper) { + _this.fireCallback(params.onSlideChangeEnd, swiper, direction); + }); + } + } + else { + if (!params.DOMAnimation) { + setTimeout(function () { + _this.fireCallback(params.onSlideChangeEnd, _this, direction); + }, 10); + } + } + } + } + + /*================================================== + Update Active Slide Index + ====================================================*/ + _this.updateActiveSlide = function (position) { + if (!_this.initialized) return; + if (_this.slides.length === 0) return; + _this.previousIndex = _this.activeIndex; + if (typeof position === 'undefined') position = _this.getWrapperTranslate(); + if (position > 0) position = 0; + var i; + if (params.slidesPerView === 'auto') { + var slidesOffset = 0; + _this.activeIndex = _this.slidesGrid.indexOf(-position); + if (_this.activeIndex < 0) { + for (i = 0; i < _this.slidesGrid.length - 1; i++) { + if (-position > _this.slidesGrid[i] && -position < _this.slidesGrid[i + 1]) { + break; + } + } + var leftDistance = Math.abs(_this.slidesGrid[i] + position); + var rightDistance = Math.abs(_this.slidesGrid[i + 1] + position); + if (leftDistance <= rightDistance) _this.activeIndex = i; + else _this.activeIndex = i + 1; + } + } + else { + _this.activeIndex = Math[params.visibilityFullFit ? 'ceil' : 'round'](-position / slideSize); + } + + if (_this.activeIndex === _this.slides.length) _this.activeIndex = _this.slides.length - 1; + if (_this.activeIndex < 0) _this.activeIndex = 0; + + // Check for slide + if (!_this.slides[_this.activeIndex]) return; + + // Calc Visible slides + _this.calcVisibleSlides(position); + + // Mark visible and active slides with additonal classes + if (_this.support.classList) { + var slide; + for (i = 0; i < _this.slides.length; i++) { + slide = _this.slides[i]; + slide.classList.remove(params.slideActiveClass); + if (_this.visibleSlides.indexOf(slide) >= 0) { + slide.classList.add(params.slideVisibleClass); + } else { + slide.classList.remove(params.slideVisibleClass); + } + } + _this.slides[_this.activeIndex].classList.add(params.slideActiveClass); + } else { + var activeClassRegexp = new RegExp('\\s*' + params.slideActiveClass); + var inViewClassRegexp = new RegExp('\\s*' + params.slideVisibleClass); + + for (i = 0; i < _this.slides.length; i++) { + _this.slides[i].className = _this.slides[i].className.replace(activeClassRegexp, '').replace(inViewClassRegexp, ''); + if (_this.visibleSlides.indexOf(_this.slides[i]) >= 0) { + _this.slides[i].className += ' ' + params.slideVisibleClass; + } + } + _this.slides[_this.activeIndex].className += ' ' + params.slideActiveClass; + } + + //Update loop index + if (params.loop) { + var ls = _this.loopedSlides; + _this.activeLoopIndex = _this.activeIndex - ls; + if (_this.activeLoopIndex >= _this.slides.length - ls * 2) { + _this.activeLoopIndex = _this.slides.length - ls * 2 - _this.activeLoopIndex; + } + if (_this.activeLoopIndex < 0) { + _this.activeLoopIndex = _this.slides.length - ls * 2 + _this.activeLoopIndex; + } + if (_this.activeLoopIndex < 0) _this.activeLoopIndex = 0; + } + else { + _this.activeLoopIndex = _this.activeIndex; + } + //Update Pagination + if (params.pagination) { + _this.updatePagination(position); + } + }; + /*================================================== + Pagination + ====================================================*/ + _this.createPagination = function (firstInit) { + if (params.paginationClickable && _this.paginationButtons) { + removePaginationEvents(); + } + _this.paginationContainer = params.pagination.nodeType ? params.pagination : $$(params.pagination)[0]; + if (params.createPagination) { + var paginationHTML = ''; + var numOfSlides = _this.slides.length; + var numOfButtons = numOfSlides; + if (params.loop) numOfButtons -= _this.loopedSlides * 2; + for (var i = 0; i < numOfButtons; i++) { + paginationHTML += '<' + params.paginationElement + ' class="' + params.paginationElementClass + '">'; + } + _this.paginationContainer.innerHTML = paginationHTML; + } + _this.paginationButtons = $$('.' + params.paginationElementClass, _this.paginationContainer); + if (!firstInit) _this.updatePagination(); + _this.callPlugins('onCreatePagination'); + if (params.paginationClickable) { + addPaginationEvents(); + } + }; + function removePaginationEvents() { + var pagers = _this.paginationButtons; + if (pagers) { + for (var i = 0; i < pagers.length; i++) { + _this.h.removeEventListener(pagers[i], 'click', paginationClick); + } + } + } + function addPaginationEvents() { + var pagers = _this.paginationButtons; + if (pagers) { + for (var i = 0; i < pagers.length; i++) { + _this.h.addEventListener(pagers[i], 'click', paginationClick); + } + } + } + function paginationClick(e) { + var index; + var target = e.target || e.srcElement; + var pagers = _this.paginationButtons; + for (var i = 0; i < pagers.length; i++) { + if (target === pagers[i]) index = i; + } + _this.swipeTo(index); + } + _this.updatePagination = function (position) { + if (!params.pagination) return; + if (_this.slides.length < 1) return; + var activePagers = $$('.' + params.paginationActiveClass, _this.paginationContainer); + if (!activePagers) return; + + //Reset all Buttons' class to not active + var pagers = _this.paginationButtons; + if (pagers.length === 0) return; + for (var i = 0; i < pagers.length; i++) { + pagers[i].className = params.paginationElementClass; + } + + var indexOffset = params.loop ? _this.loopedSlides : 0; + if (params.paginationAsRange) { + if (!_this.visibleSlides) _this.calcVisibleSlides(position); + //Get Visible Indexes + var visibleIndexes = []; + var j; // lopp index - avoid JSHint W004 / W038 + for (j = 0; j < _this.visibleSlides.length; j++) { + var visIndex = _this.slides.indexOf(_this.visibleSlides[j]) - indexOffset; + + if (params.loop && visIndex < 0) { + visIndex = _this.slides.length - _this.loopedSlides * 2 + visIndex; + } + if (params.loop && visIndex >= _this.slides.length - _this.loopedSlides * 2) { + visIndex = _this.slides.length - _this.loopedSlides * 2 - visIndex; + visIndex = Math.abs(visIndex); + } + visibleIndexes.push(visIndex); + } + + for (j = 0; j < visibleIndexes.length; j++) { + if (pagers[visibleIndexes[j]]) pagers[visibleIndexes[j]].className += ' ' + params.paginationVisibleClass; + } + + if (params.loop) { + if (pagers[_this.activeLoopIndex] !== undefined) { + pagers[_this.activeLoopIndex].className += ' ' + params.paginationActiveClass; + } + } + else { + pagers[_this.activeIndex].className += ' ' + params.paginationActiveClass; + } + } + else { + if (params.loop) { + if (pagers[_this.activeLoopIndex]) pagers[_this.activeLoopIndex].className += ' ' + params.paginationActiveClass + ' ' + params.paginationVisibleClass; + } + else { + pagers[_this.activeIndex].className += ' ' + params.paginationActiveClass + ' ' + params.paginationVisibleClass; + } + } + }; + _this.calcVisibleSlides = function (position) { + var visibleSlides = []; + var _slideLeft = 0, _slideSize = 0, _slideRight = 0; + if (isH && _this.wrapperLeft > 0) position = position + _this.wrapperLeft; + if (!isH && _this.wrapperTop > 0) position = position + _this.wrapperTop; + + for (var i = 0; i < _this.slides.length; i++) { + _slideLeft += _slideSize; + if (params.slidesPerView === 'auto') + _slideSize = isH ? _this.h.getWidth(_this.slides[i], true, params.roundLengths) : _this.h.getHeight(_this.slides[i], true, params.roundLengths); + else _slideSize = slideSize; + + _slideRight = _slideLeft + _slideSize; + var isVisibile = false; + if (params.visibilityFullFit) { + if (_slideLeft >= -position && _slideRight <= -position + containerSize) isVisibile = true; + if (_slideLeft <= -position && _slideRight >= -position + containerSize) isVisibile = true; + } + else { + if (_slideRight > -position && _slideRight <= ((-position + containerSize))) isVisibile = true; + if (_slideLeft >= -position && _slideLeft < ((-position + containerSize))) isVisibile = true; + if (_slideLeft < -position && _slideRight > ((-position + containerSize))) isVisibile = true; + } + + if (isVisibile) visibleSlides.push(_this.slides[i]); + + } + if (visibleSlides.length === 0) visibleSlides = [_this.slides[_this.activeIndex]]; + + _this.visibleSlides = visibleSlides; + }; + + /*========================================== + Autoplay + ============================================*/ + var autoplayTimeoutId, autoplayIntervalId; + _this.startAutoplay = function () { + if (_this.support.transitions) { + if (typeof autoplayTimeoutId !== 'undefined') return false; + if (!params.autoplay) return; + _this.callPlugins('onAutoplayStart'); + if (params.onAutoplayStart) _this.fireCallback(params.onAutoplayStart, _this); + autoplay(); + } + else { + if (typeof autoplayIntervalId !== 'undefined') return false; + if (!params.autoplay) return; + _this.callPlugins('onAutoplayStart'); + if (params.onAutoplayStart) _this.fireCallback(params.onAutoplayStart, _this); + autoplayIntervalId = setInterval(function () { + if (params.loop) { + _this.fixLoop(); + _this.swipeNext(true); + } + else if (!_this.swipeNext(true)) { + if (!params.autoplayStopOnLast) _this.swipeTo(0); + else { + clearInterval(autoplayIntervalId); + autoplayIntervalId = undefined; + } + } + }, params.autoplay); + } + }; + _this.stopAutoplay = function (internal) { + if (_this.support.transitions) { + if (!autoplayTimeoutId) return; + if (autoplayTimeoutId) clearTimeout(autoplayTimeoutId); + autoplayTimeoutId = undefined; + if (internal && !params.autoplayDisableOnInteraction) { + _this.wrapperTransitionEnd(function () { + autoplay(); + }); + } + _this.callPlugins('onAutoplayStop'); + if (params.onAutoplayStop) _this.fireCallback(params.onAutoplayStop, _this); + } + else { + if (autoplayIntervalId) clearInterval(autoplayIntervalId); + autoplayIntervalId = undefined; + _this.callPlugins('onAutoplayStop'); + if (params.onAutoplayStop) _this.fireCallback(params.onAutoplayStop, _this); + } + }; + function autoplay() { + autoplayTimeoutId = setTimeout(function () { + if (params.loop) { + _this.fixLoop(); + _this.swipeNext(true); + } + else if (!_this.swipeNext(true)) { + if (!params.autoplayStopOnLast) _this.swipeTo(0); + else { + clearTimeout(autoplayTimeoutId); + autoplayTimeoutId = undefined; + } + } + _this.wrapperTransitionEnd(function () { + if (typeof autoplayTimeoutId !== 'undefined') autoplay(); + }); + }, params.autoplay); + } + /*================================================== + Loop + ====================================================*/ + _this.loopCreated = false; + _this.removeLoopedSlides = function () { + if (_this.loopCreated) { + for (var i = 0; i < _this.slides.length; i++) { + if (_this.slides[i].getData('looped') === true) _this.wrapper.removeChild(_this.slides[i]); + } + } + }; + + _this.createLoop = function () { + if (_this.slides.length === 0) return; + if (params.slidesPerView === 'auto') { + _this.loopedSlides = params.loopedSlides || 1; + } + else { + _this.loopedSlides = params.slidesPerView + params.loopAdditionalSlides; + } + + if (_this.loopedSlides > _this.slides.length) { + _this.loopedSlides = _this.slides.length; + } + + var slideFirstHTML = '', + slideLastHTML = '', + i; + var slidesSetFullHTML = ''; + /** + loopedSlides is too large if loopAdditionalSlides are set. + Need to divide the slides by maximum number of slides existing. + + @author Tomaz Lovrec + */ + var numSlides = _this.slides.length; + var fullSlideSets = Math.floor(_this.loopedSlides / numSlides); + var remainderSlides = _this.loopedSlides % numSlides; + // assemble full sets of slides + for (i = 0; i < (fullSlideSets * numSlides); i++) { + var j = i; + if (i >= numSlides) { + var over = Math.floor(i / numSlides); + j = i - (numSlides * over); + } + slidesSetFullHTML += _this.slides[j].outerHTML; + } + // assemble remainder slides + // assemble remainder appended to existing slides + for (i = 0; i < remainderSlides;i++) { + slideLastHTML += addClassToHtmlString(params.slideDuplicateClass, _this.slides[i].outerHTML); + } + // assemble slides that get preppended to existing slides + for (i = numSlides - remainderSlides; i < numSlides;i++) { + slideFirstHTML += addClassToHtmlString(params.slideDuplicateClass, _this.slides[i].outerHTML); + } + // assemble all slides + var slides = slideFirstHTML + slidesSetFullHTML + wrapper.innerHTML + slidesSetFullHTML + slideLastHTML; + // set the slides + wrapper.innerHTML = slides; + + _this.loopCreated = true; + _this.calcSlides(); + + //Update Looped Slides with special class + for (i = 0; i < _this.slides.length; i++) { + if (i < _this.loopedSlides || i >= _this.slides.length - _this.loopedSlides) _this.slides[i].setData('looped', true); + } + _this.callPlugins('onCreateLoop'); + + }; + + _this.fixLoop = function () { + var newIndex; + //Fix For Negative Oversliding + if (_this.activeIndex < _this.loopedSlides) { + newIndex = _this.slides.length - _this.loopedSlides * 3 + _this.activeIndex; + _this.swipeTo(newIndex, 0, false); + } + //Fix For Positive Oversliding + else if ((params.slidesPerView === 'auto' && _this.activeIndex >= _this.loopedSlides * 2) || (_this.activeIndex > _this.slides.length - params.slidesPerView * 2)) { + newIndex = -_this.slides.length + _this.activeIndex + _this.loopedSlides; + _this.swipeTo(newIndex, 0, false); + } + }; + + /*================================================== + Slides Loader + ====================================================*/ + _this.loadSlides = function () { + var slidesHTML = ''; + _this.activeLoaderIndex = 0; + var slides = params.loader.slides; + var slidesToLoad = params.loader.loadAllSlides ? slides.length : params.slidesPerView * (1 + params.loader.surroundGroups); + for (var i = 0; i < slidesToLoad; i++) { + if (params.loader.slidesHTMLType === 'outer') slidesHTML += slides[i]; + else { + slidesHTML += '<' + params.slideElement + ' class="' + params.slideClass + '" data-swiperindex="' + i + '">' + slides[i] + ''; + } + } + _this.wrapper.innerHTML = slidesHTML; + _this.calcSlides(true); + //Add permanent transitionEnd callback + if (!params.loader.loadAllSlides) { + _this.wrapperTransitionEnd(_this.reloadSlides, true); + } + }; + + _this.reloadSlides = function () { + var slides = params.loader.slides; + var newActiveIndex = parseInt(_this.activeSlide().data('swiperindex'), 10); + if (newActiveIndex < 0 || newActiveIndex > slides.length - 1) return; //<-- Exit + _this.activeLoaderIndex = newActiveIndex; + var firstIndex = Math.max(0, newActiveIndex - params.slidesPerView * params.loader.surroundGroups); + var lastIndex = Math.min(newActiveIndex + params.slidesPerView * (1 + params.loader.surroundGroups) - 1, slides.length - 1); + //Update Transforms + if (newActiveIndex > 0) { + var newTransform = -slideSize * (newActiveIndex - firstIndex); + _this.setWrapperTranslate(newTransform); + _this.setWrapperTransition(0); + } + var i; // loop index + //New Slides + if (params.loader.logic === 'reload') { + _this.wrapper.innerHTML = ''; + var slidesHTML = ''; + for (i = firstIndex; i <= lastIndex; i++) { + slidesHTML += params.loader.slidesHTMLType === 'outer' ? slides[i] : '<' + params.slideElement + ' class="' + params.slideClass + '" data-swiperindex="' + i + '">' + slides[i] + ''; + } + _this.wrapper.innerHTML = slidesHTML; + } + else { + var minExistIndex = 1000; + var maxExistIndex = 0; + + for (i = 0; i < _this.slides.length; i++) { + var index = _this.slides[i].data('swiperindex'); + if (index < firstIndex || index > lastIndex) { + _this.wrapper.removeChild(_this.slides[i]); + } + else { + minExistIndex = Math.min(index, minExistIndex); + maxExistIndex = Math.max(index, maxExistIndex); + } + } + for (i = firstIndex; i <= lastIndex; i++) { + var newSlide; + if (i < minExistIndex) { + newSlide = document.createElement(params.slideElement); + newSlide.className = params.slideClass; + newSlide.setAttribute('data-swiperindex', i); + newSlide.innerHTML = slides[i]; + _this.wrapper.insertBefore(newSlide, _this.wrapper.firstChild); + } + if (i > maxExistIndex) { + newSlide = document.createElement(params.slideElement); + newSlide.className = params.slideClass; + newSlide.setAttribute('data-swiperindex', i); + newSlide.innerHTML = slides[i]; + _this.wrapper.appendChild(newSlide); + } + } + } + //reInit + _this.reInit(true); + }; + + /*================================================== + Make Swiper + ====================================================*/ + function makeSwiper() { + _this.calcSlides(); + if (params.loader.slides.length > 0 && _this.slides.length === 0) { + _this.loadSlides(); + } + if (params.loop) { + _this.createLoop(); + } + _this.init(); + initEvents(); + if (params.pagination) { + _this.createPagination(true); + } + + if (params.loop || params.initialSlide > 0) { + _this.swipeTo(params.initialSlide, 0, false); + } + else { + _this.updateActiveSlide(0); + } + if (params.autoplay) { + _this.startAutoplay(); + } + /** + * Set center slide index. + * + * @author Tomaz Lovrec + */ + _this.centerIndex = _this.activeIndex; + + // Callbacks + if (params.onSwiperCreated) _this.fireCallback(params.onSwiperCreated, _this); + _this.callPlugins('onSwiperCreated'); + } + + makeSwiper(); +}; + +Swiper.prototype = { + plugins : {}, + + /*================================================== + Wrapper Operations + ====================================================*/ + wrapperTransitionEnd : function (callback, permanent) { + 'use strict'; + var a = this, + el = a.wrapper, + events = ['webkitTransitionEnd', 'transitionend', 'oTransitionEnd', 'MSTransitionEnd', 'msTransitionEnd'], + i; + + function fireCallBack() { + callback(a); + if (a.params.queueEndCallbacks) a._queueEndCallbacks = false; + if (!permanent) { + for (i = 0; i < events.length; i++) { + a.h.removeEventListener(el, events[i], fireCallBack); + } + } + } + + if (callback) { + for (i = 0; i < events.length; i++) { + a.h.addEventListener(el, events[i], fireCallBack); + } + } + }, + + getWrapperTranslate : function (axis) { + 'use strict'; + var el = this.wrapper, + matrix, curTransform, curStyle, transformMatrix; + + // automatic axis detection + if (typeof axis === 'undefined') { + axis = this.params.mode === 'horizontal' ? 'x' : 'y'; + } + + if (this.support.transforms && this.params.useCSS3Transforms) { + curStyle = window.getComputedStyle(el, null); + if (window.WebKitCSSMatrix) { + // Some old versions of Webkit choke when 'none' is passed; pass + // empty string instead in this case + transformMatrix = new WebKitCSSMatrix(curStyle.webkitTransform === 'none' ? '' : curStyle.webkitTransform); + } + else { + transformMatrix = curStyle.MozTransform || curStyle.OTransform || curStyle.MsTransform || curStyle.msTransform || curStyle.transform || curStyle.getPropertyValue('transform').replace('translate(', 'matrix(1, 0, 0, 1,'); + matrix = transformMatrix.toString().split(','); + } + + if (axis === 'x') { + //Latest Chrome and webkits Fix + if (window.WebKitCSSMatrix) + curTransform = transformMatrix.m41; + //Crazy IE10 Matrix + else if (matrix.length === 16) + curTransform = parseFloat(matrix[12]); + //Normal Browsers + else + curTransform = parseFloat(matrix[4]); + } + if (axis === 'y') { + //Latest Chrome and webkits Fix + if (window.WebKitCSSMatrix) + curTransform = transformMatrix.m42; + //Crazy IE10 Matrix + else if (matrix.length === 16) + curTransform = parseFloat(matrix[13]); + //Normal Browsers + else + curTransform = parseFloat(matrix[5]); + } + } + else { + if (axis === 'x') curTransform = parseFloat(el.style.left, 10) || 0; + if (axis === 'y') curTransform = parseFloat(el.style.top, 10) || 0; + } + return curTransform || 0; + }, + + setWrapperTranslate : function (x, y, z) { + 'use strict'; + var es = this.wrapper.style, + coords = {x: 0, y: 0, z: 0}, + translate; + + // passed all coordinates + if (arguments.length === 3) { + coords.x = x; + coords.y = y; + coords.z = z; + } + + // passed one coordinate and optional axis + else { + if (typeof y === 'undefined') { + y = this.params.mode === 'horizontal' ? 'x' : 'y'; + } + coords[y] = x; + } + + if (this.support.transforms && this.params.useCSS3Transforms) { + translate = this.support.transforms3d ? 'translate3d(' + coords.x + 'px, ' + coords.y + 'px, ' + coords.z + 'px)' : 'translate(' + coords.x + 'px, ' + coords.y + 'px)'; + es.webkitTransform = es.MsTransform = es.msTransform = es.MozTransform = es.OTransform = es.transform = translate; + } + else { + es.left = coords.x + 'px'; + es.top = coords.y + 'px'; + } + this.callPlugins('onSetWrapperTransform', coords); + if (this.params.onSetWrapperTransform) this.fireCallback(this.params.onSetWrapperTransform, this, coords); + }, + + setWrapperTransition : function (duration) { + 'use strict'; + var es = this.wrapper.style; + es.webkitTransitionDuration = es.MsTransitionDuration = es.msTransitionDuration = es.MozTransitionDuration = es.OTransitionDuration = es.transitionDuration = (duration / 1000) + 's'; + this.callPlugins('onSetWrapperTransition', {duration: duration}); + if (this.params.onSetWrapperTransition) this.fireCallback(this.params.onSetWrapperTransition, this, duration); + + }, + + /*================================================== + Helpers + ====================================================*/ + h : { + getWidth: function (el, outer, round) { + 'use strict'; + var width = window.getComputedStyle(el, null).getPropertyValue('width'); + var returnWidth = parseFloat(width); + //IE Fixes + if (isNaN(returnWidth) || width.indexOf('%') > 0) { + returnWidth = el.offsetWidth - parseFloat(window.getComputedStyle(el, null).getPropertyValue('padding-left')) - parseFloat(window.getComputedStyle(el, null).getPropertyValue('padding-right')); + } + if (outer) returnWidth += parseFloat(window.getComputedStyle(el, null).getPropertyValue('padding-left')) + parseFloat(window.getComputedStyle(el, null).getPropertyValue('padding-right')); + if (round) return Math.round(returnWidth); + else return returnWidth; + }, + getHeight: function (el, outer, round) { + 'use strict'; + if (outer) return el.offsetHeight; + + var height = window.getComputedStyle(el, null).getPropertyValue('height'); + var returnHeight = parseFloat(height); + //IE Fixes + if (isNaN(returnHeight) || height.indexOf('%') > 0) { + returnHeight = el.offsetHeight - parseFloat(window.getComputedStyle(el, null).getPropertyValue('padding-top')) - parseFloat(window.getComputedStyle(el, null).getPropertyValue('padding-bottom')); + } + if (outer) returnHeight += parseFloat(window.getComputedStyle(el, null).getPropertyValue('padding-top')) + parseFloat(window.getComputedStyle(el, null).getPropertyValue('padding-bottom')); + if (round) return Math.round(returnHeight); + else return returnHeight; + }, + getOffset: function (el) { + 'use strict'; + var box = el.getBoundingClientRect(); + var body = document.body; + var clientTop = el.clientTop || body.clientTop || 0; + var clientLeft = el.clientLeft || body.clientLeft || 0; + var scrollTop = window.pageYOffset || el.scrollTop; + var scrollLeft = window.pageXOffset || el.scrollLeft; + if (document.documentElement && !window.pageYOffset) { + //IE7-8 + scrollTop = document.documentElement.scrollTop; + scrollLeft = document.documentElement.scrollLeft; + } + return { + top: box.top + scrollTop - clientTop, + left: box.left + scrollLeft - clientLeft + }; + }, + windowWidth : function () { + 'use strict'; + if (window.innerWidth) return window.innerWidth; + else if (document.documentElement && document.documentElement.clientWidth) return document.documentElement.clientWidth; + }, + windowHeight : function () { + 'use strict'; + if (window.innerHeight) return window.innerHeight; + else if (document.documentElement && document.documentElement.clientHeight) return document.documentElement.clientHeight; + }, + windowScroll : function () { + 'use strict'; + if (typeof pageYOffset !== 'undefined') { + return { + left: window.pageXOffset, + top: window.pageYOffset + }; + } + else if (document.documentElement) { + return { + left: document.documentElement.scrollLeft, + top: document.documentElement.scrollTop + }; + } + }, + + addEventListener : function (el, event, listener, useCapture) { + 'use strict'; + if (typeof useCapture === 'undefined') { + useCapture = false; + } + + if (el.addEventListener) { + el.addEventListener(event, listener, useCapture); + } + else if (el.attachEvent) { + el.attachEvent('on' + event, listener); + } + }, + + removeEventListener : function (el, event, listener, useCapture) { + 'use strict'; + if (typeof useCapture === 'undefined') { + useCapture = false; + } + + if (el.removeEventListener) { + el.removeEventListener(event, listener, useCapture); + } + else if (el.detachEvent) { + el.detachEvent('on' + event, listener); + } + } + }, + setTransform : function (el, transform) { + 'use strict'; + var es = el.style; + es.webkitTransform = es.MsTransform = es.msTransform = es.MozTransform = es.OTransform = es.transform = transform; + }, + setTranslate : function (el, translate) { + 'use strict'; + var es = el.style; + var pos = { + x : translate.x || 0, + y : translate.y || 0, + z : translate.z || 0 + }; + var transformString = this.support.transforms3d ? 'translate3d(' + (pos.x) + 'px,' + (pos.y) + 'px,' + (pos.z) + 'px)' : 'translate(' + (pos.x) + 'px,' + (pos.y) + 'px)'; + es.webkitTransform = es.MsTransform = es.msTransform = es.MozTransform = es.OTransform = es.transform = transformString; + if (!this.support.transforms) { + es.left = pos.x + 'px'; + es.top = pos.y + 'px'; + } + }, + setTransition : function (el, duration) { + 'use strict'; + var es = el.style; + es.webkitTransitionDuration = es.MsTransitionDuration = es.msTransitionDuration = es.MozTransitionDuration = es.OTransitionDuration = es.transitionDuration = duration + 'ms'; + }, + /*================================================== + Feature Detection + ====================================================*/ + support: { + + touch : (window.Modernizr && Modernizr.touch === true) || (function () { + 'use strict'; + return !!(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch); + })(), + + transforms3d : (window.Modernizr && Modernizr.csstransforms3d === true) || (function () { + 'use strict'; + var div = document.createElement('div').style; + return ('webkitPerspective' in div || 'MozPerspective' in div || 'OPerspective' in div || 'MsPerspective' in div || 'perspective' in div); + })(), + + transforms : (window.Modernizr && Modernizr.csstransforms === true) || (function () { + 'use strict'; + var div = document.createElement('div').style; + return ('transform' in div || 'WebkitTransform' in div || 'MozTransform' in div || 'msTransform' in div || 'MsTransform' in div || 'OTransform' in div); + })(), + + transitions : (window.Modernizr && Modernizr.csstransitions === true) || (function () { + 'use strict'; + var div = document.createElement('div').style; + return ('transition' in div || 'WebkitTransition' in div || 'MozTransition' in div || 'msTransition' in div || 'MsTransition' in div || 'OTransition' in div); + })(), + + classList : (function () { + 'use strict'; + var div = document.createElement('div').style; + return 'classList' in div; + })() + }, + + browser : { + + ie8 : (function () { + 'use strict'; + var rv = -1; // Return value assumes failure. + if (navigator.appName === 'Microsoft Internet Explorer') { + var ua = navigator.userAgent; + var re = new RegExp(/MSIE ([0-9]{1,}[\.0-9]{0,})/); + if (re.exec(ua) !== null) + rv = parseFloat(RegExp.$1); + } + return rv !== -1 && rv < 9; + })(), + + ie10 : window.navigator.msPointerEnabled, + ie11 : window.navigator.pointerEnabled + } +}; + +/*========================= + jQuery & Zepto Plugins + ===========================*/ +if (window.jQuery || window.Zepto) { + (function ($) { + 'use strict'; + $.fn.swiper = function (params) { + var s = new Swiper($(this)[0], params); + $(this).data('swiper', s); + return s; + }; + })(window.jQuery || window.Zepto); +} + +// component +if (typeof(module) !== 'undefined') +{ + module.exports = Swiper; +} + +// requirejs support +if (typeof define === 'function' && define.amd) { + define([], function () { + 'use strict'; + return Swiper; + }); +} 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 b5975f565c5..e4e873efe35 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 @@ -13,6 +13,7 @@ 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.idangerous.Swiper; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sse.common.Util.Pair; @@ -22,13 +23,14 @@ public class Stage extends Composite { private List> featuredEvents; private List stageTeaserComposites; - private int indexOfVisibleStageTeaser; @UiField HTMLPanel stageElementsPanel; @UiField Anchor nextStageTeaserLink; @UiField Anchor prevStageTeaserLink; private StageTeaser stageTeaser; + + private Swiper swiper; private final PlaceNavigator placeNavigator; @@ -44,9 +46,8 @@ public class Stage extends Composite { initWidget(uiBinder.createAndBindUi(this)); stageTeaserComposites = new ArrayList(); - indexOfVisibleStageTeaser = -1; } - + public void setFeaturedEvents(List> featuredEvents) { this.featuredEvents = featuredEvents; @@ -63,35 +64,25 @@ public class Stage extends Composite { break; } stageElementsPanel.add(stageTeaser); - stageTeaser.setVisible(false); stageTeaserComposites.add(stageTeaser); - - indexOfVisibleStageTeaser = 0; - stageTeaserComposites.get(indexOfVisibleStageTeaser).setVisible(true); } + + this.swiper = Swiper.createWithDefaultOptions(StageResources.INSTANCE.css().stage_teasers(), + StageResources.INSTANCE.css().swiperwrapper(), + StageResources.INSTANCE.css().swiperslide()); } @UiHandler("nextStageTeaserLink") public void nextStageTeaserLinkClicked(ClickEvent e) { - StageTeaser oldStageTeaser = stageTeaserComposites.get(indexOfVisibleStageTeaser); - indexOfVisibleStageTeaser = indexOfVisibleStageTeaser+1; - if(indexOfVisibleStageTeaser >= stageTeaserComposites.size()) { - indexOfVisibleStageTeaser = 0; + if (this.swiper != null) { + this.swiper.swipeNext(); } - StageTeaser currentStageTeaser = stageTeaserComposites.get(indexOfVisibleStageTeaser); - oldStageTeaser.setVisible(false); - currentStageTeaser.setVisible(true); } @UiHandler("prevStageTeaserLink") public void prevStageTeaserLinkClicked(ClickEvent e) { - StageTeaser oldStageTeaser = stageTeaserComposites.get(indexOfVisibleStageTeaser); - indexOfVisibleStageTeaser = indexOfVisibleStageTeaser-1; - if(indexOfVisibleStageTeaser < 0) { - indexOfVisibleStageTeaser = stageTeaserComposites.size()-1; + if (this.swiper != null) { + this.swiper.swipePrev(); } - StageTeaser currentStageTeaser = stageTeaserComposites.get(indexOfVisibleStageTeaser); - oldStageTeaser.setVisible(false); - currentStageTeaser.setVisible(true); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.ui.xml index 4e7a82efa42..c3c299ca3f7 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.ui.xml @@ -9,15 +9,13 @@
-
- -
+
- - + +
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.ui.xml index 055c7fd3df6..3f4d546d56b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.ui.xml @@ -1,37 +1,38 @@ - - - - - -
-
-
-
-
-
-

-

-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
- + xmlns:g="urn:import:com.google.gwt.user.client.ui" xmlns:s="urn:import:com.sap.sailing.gwt.home.client.shared"> + + + + + +
+
+
+
+
+

+ +

+

+ +

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ 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 new file mode 100644 index 00000000000..5934c4b8aff --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/idangerous/Swiper.java @@ -0,0 +1,33 @@ +package com.sap.sailing.gwt.idangerous; + +import com.google.gwt.core.client.JavaScriptObject; + +/** + * Wrapping http://www.idangero.us/sliders/swiper/ + * + */ +public final class Swiper extends JavaScriptObject { + + protected Swiper() {}; + + public static native Swiper createWithDefaultOptions(String containerClass, String wrapperClass, String slideClass) /*-{ + var options = { + loop: true, +// onSlideChangeStart: this.resetProgressbar, +// onTouchMoveStart: this.stopProgressbar, +// onTouchEnd: this.onProgress, + wrapperClass: wrapperClass, + slideClass: slideClass + }; + return new $wnd.Swiper('.'+containerClass, options); + }-*/; + + public native void swipeNext() /*-{ + this.swipeNext(); + }-*/; + + public native void swipePrev() /*-{ + this.swipePrev(); + }-*/; + +} 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 c29cd5c3947..8dd534000e0 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 @@ -74,4 +74,5 @@ + From 57f2ae4bb44bba0863517eda8df5ec422fa81869 Mon Sep 17 00:00:00 2001 From: fmittag Date: Wed, 9 Jul 2014 15:06:07 +0200 Subject: [PATCH 039/323] draft of new version of mainMedia partial --- .../images/home/x_lightbox.png | Bin 0 -> 211 bytes .../gwt/home/client/HomeResources.java | 12 +++ .../place/event/overview/EventOverview.css | 91 ---------------- .../overview/EventOverviewResources.java | 10 -- .../client/shared/mainmedia/MainMedia.css | 30 ++++-- .../client/shared/mainmedia/MainMedia.java | 58 ++++------- .../client/shared/mainmedia/MainMedia.ui.xml | 25 +++-- .../shared/mainmedia/MainMediaPhotoThumb.java | 23 ++++ .../mainmedia/MainMediaPhotoThumb.ui.xml | 11 ++ .../shared/mainmedia/MainMediaResources.java | 1 + .../shared/mainmedia/MainMediaVideo.java | 32 ++++++ .../shared/mainmedia/MainMediaVideo.ui.xml | 18 ++++ .../shared/mainmedia/MainMediaVideoThumb.java | 22 ++++ .../mainmedia/MainMediaVideoThumb.ui.xml | 11 ++ .../stage/UpcomingEventStageTeaser.java | 6 +- .../com/sap/sailing/gwt/home/main.css | 98 ++++++++++++++++++ 16 files changed, 287 insertions(+), 161 deletions(-) create mode 100644 java/com.sap.sailing.gwt.ui/images/home/x_lightbox.png create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaPhotoThumb.java create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaPhotoThumb.ui.xml create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideo.java create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideo.ui.xml create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideoThumb.java create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideoThumb.ui.xml diff --git a/java/com.sap.sailing.gwt.ui/images/home/x_lightbox.png b/java/com.sap.sailing.gwt.ui/images/home/x_lightbox.png new file mode 100644 index 0000000000000000000000000000000000000000..108bc616c66a82af9b0d1efb4c79ba0572da3998 GIT binary patch literal 211 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5XN~qncT?9pvd9+bk@P%*#KVN!rwpbZtK>3ZaETRVm|HFA3UmO2r>mdK II;Vst01YNj2mk;8 literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/HomeResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/HomeResources.java index 8f8ebfb6f57..9bdbf18c858 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/HomeResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/HomeResources.java @@ -42,6 +42,18 @@ public interface HomeResources extends ClientBundle { String mainsection_header(); String mainsection_header_title(); String mainsection_navigation(); + + String lightbox(); + String lightbox_overlay(); + String lightbox_overlayhidden(); + String lightbox_content(); + String lightbox_contenthidden(); + String lightbox_content_enlargedimage(); + String lightbox_content_enlargedimage_caption(); + String lightbox_content_enlargedimage_close(); + String lightbox_data(); + String lightbox_photo(); + String lightbox_item(); } @Shared 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 663f606747e..c2ac3b6301c 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 @@ -74,97 +74,6 @@ .columns .latestraceresults_detail_wrapperimage { max-width: none; } -.lightbox { - font-size: 16px; - font-size: 1rem; -} -.lightbox_overlay { - font-size: 16px; - font-size: 1rem; - background: #000; - opacity: 0.7; - -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=70)"; - filter: literal("alpha(opacity=70)"); - width: 100%; - height: 100%; - position: fixed; - left: 0; - top: 0; - z-index: 2000; - -webkit-transition: opacity 0.3s ease; - -moz-transition: opacity 0.3s ease; - -o-transition: opacity 0.3s ease; - -ms-transition: opacity 0.3s ease; - transition: opacity 0.3s ease; -} -.lightbox_overlayhidden { - opacity: 0; - -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; - filter: alpha(opacity=0); -} -.lightbox_content { - position: fixed; - width: 100%; - height: 100%; - opacity: 1; - -ms-filter: none; - filter: none; - top: 0; - left: 0; - z-index: 2001; - background-repeat: no-repeat; - background-position: center center; - -webkit-transition: opacity 0.3s ease; - -moz-transition: opacity 0.3s ease; - -o-transition: opacity 0.3s ease; - -ms-transition: opacity 0.3s ease; - transition: opacity 0.3s ease; -} -.lightbox_contenthidden { - opacity: 0; - -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; - filter: alpha(opacity=0); -} -.lightbox_content_enlargedimage { - -webkit-transition: margin 0.7s linear; - -moz-transition: margin 0.7s linear; - -o-transition: margin 0.7s linear; - -ms-transition: margin 0.7s linear; - transition: margin 0.7s linear; - display: inline-block; - position: absolute; - left: 50%; - top: 50%; -} -.lightbox_content_enlargedimage_caption { - text-align: center; - color: #f6f9fc; - font-weight: 700; -} -.lightbox_content_enlargedimage_close { - display: inline-block; - background-image: url("../images/x_lightbox.png"); - background-repeat: no-repeat; - background-position: center center; - width: 1.25em; - height: 0; - overflow: hidden; - padding-top: 1.25em; - position: absolute; - top: 1.25em; - right: 1.25em; - cursor: pointer; - z-index: 2003; -} -.lightbox_content .media_slideshow_controls { - z-index: 2002; -} -.lightbox_data { - display: none; -} -.lightbox_photo { - cursor: pointer; -} .mediateaser { font-size: 16px; font-size: 1rem; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/overview/EventOverviewResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/overview/EventOverviewResources.java index aac78042d84..d714efdc02a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/overview/EventOverviewResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/overview/EventOverviewResources.java @@ -29,17 +29,7 @@ public interface EventOverviewResources extends ClientBundle { String latestraceresults_detail_datetime(); String columns(); String latestraceresults_detail_wrapperimage(); - String lightbox(); - String lightbox_overlay(); - String lightbox_overlayhidden(); - String lightbox_content(); - String lightbox_contenthidden(); - String lightbox_content_enlargedimage(); - String lightbox_content_enlargedimage_caption(); - String lightbox_content_enlargedimage_close(); String media_slideshow_controls(); - String lightbox_data(); - String lightbox_photo(); String mediateaser(); String mediateaser_title(); String mediateaser_title_link(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css index 64bf287924f..170a9066b69 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css @@ -1,20 +1,29 @@ -/** - * Websites main media element - */ .media { - background: #f8f8f8; - font-size: 16px; + font-size: 15px; font-size: 1rem; } .media_photo { width: 100%; height: 100%; + -webkit-transition: margin 1s ease; + -moz-transition: margin 1s ease; + -o-transition: margin 1s ease; + -ms-transition: margin 1s ease; + transition: margin 1s ease; + position: absolute; + background-position: center center; + background-repeat: no-repeat; + cursor: pointer; } .media_slideshow { position: relative; height: 550px; width: 830px; margin: 0 auto; + overflow: hidden; +} +.media_slideshow_data { + display: none; } .media_slideshow_controls { position: absolute; @@ -23,7 +32,6 @@ cursor: pointer; z-index: 10; } - .media_slideshow_controlsnext, .media_slideshow_controlsprev { top: 50%; @@ -65,9 +73,9 @@ color: #fff; } .videopreview { - font-size: 16px; + font-size: 15px; font-size: 1rem; - margin-bottom: 2.5em; + margin-bottom: 2.666666666666667em; cursor: pointer; } .videopreview:before { @@ -88,7 +96,11 @@ background-size: contain; } .videopreview_title { - font-size: 1.375em; + font-size: 1.466666666666667em; font-weight: bold; margin: 0; + color: #fff; +} +.videopreview time { + display: block; } 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 b8ea0418bf1..b067a922b74 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 @@ -1,6 +1,5 @@ package com.sap.sailing.gwt.home.client.shared.mainmedia; -import java.util.ArrayList; import java.util.List; import com.google.gwt.core.client.GWT; @@ -8,23 +7,17 @@ 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.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.EventDTO; public class MainMedia extends Composite { + @UiField HTMLPanel videoThumbsPanel; + @UiField HTMLPanel videoPanel; + @UiField HTMLPanel photoThumbsPanel; - @UiField DivElement photoImage; - - @UiField(provided=true) RecentEventVideo eventVideo1; - @UiField(provided=true) RecentEventVideo eventVideo2; - @UiField(provided=true) RecentEventVideo eventVideo3; - - private List recentEvents; - - private final String basePathToImages = "http://static.sapsailing.com/newhome/media/"; - - private String[] images = { "main-media-photo-1.jpg" }; + @UiField DivElement photoUrl; interface MainMediaUiBinder extends UiBinder { } @@ -32,38 +25,27 @@ public class MainMedia extends Composite { private static MainMediaUiBinder uiBinder = GWT.create(MainMediaUiBinder.class); public MainMedia(PlaceNavigator navigator) { - eventVideo1 = new RecentEventVideo(navigator); - eventVideo2 = new RecentEventVideo(navigator); - eventVideo3 = new RecentEventVideo(navigator); MainMediaResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); - String imagePath = "url(" + basePathToImages + images[0] + ")"; - photoImage.getStyle().setBackgroundImage(imagePath); - - recentEvents = new ArrayList(); } - public void setRecentEvents(List theRecentEvents) { - recentEvents.clear(); - recentEvents.addAll(theRecentEvents); + public void setRecentEvents(List recentEvents) { + int videoCounter = 0; + for(EventDTO event: recentEvents) { + if(event.getVideoURLs().size() > 0) { + MainMediaVideoThumb videoThumb = new MainMediaVideoThumb(); + if(videoCounter <= 3) { + videoThumbsPanel.add(videoThumb); + videoCounter++; + } + } + } + + photoUrl.setAttribute("data-url", "#1"); +// String imagePath = "url(" + imageX + ")"; +// photoUrl.getStyle().setBackgroundImage(imagePath); - int size = recentEvents.size(); - if(size > 0) { - eventVideo1.setEvent(recentEvents.get(0)); - } else { - eventVideo1.setVisible(false); - } - if(size > 1) { - eventVideo2.setEvent(recentEvents.get(1)); - } else { - eventVideo2.setVisible(false); - } - if(size > 2) { - eventVideo3.setEvent(recentEvents.get(2)); - } else { - eventVideo3.setVisible(false); - } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.ui.xml index 5043e1ded14..bc0bc703304 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.ui.xml @@ -6,22 +6,26 @@ + +
-

Sailing loves Photography

-

Get lost in the latest and most beautiful impressions from your favorite sailing event.

+

+

-
+
+ +
+
-
@@ -30,15 +34,16 @@
-

Recent Video Entries

-

Immerse yourself in the world of sailing and check out our latest events for more videos.

+

+

-
- - - +
+
+ +
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaPhotoThumb.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaPhotoThumb.java new file mode 100644 index 00000000000..175ed433f14 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaPhotoThumb.java @@ -0,0 +1,23 @@ +package com.sap.sailing.gwt.home.client.shared.mainmedia; + +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.app.PlaceNavigator; + +public class MainMediaPhotoThumb extends Composite { + + + interface MainMediaPhotoThumbUiBinder extends UiBinder { + } + + private static MainMediaPhotoThumbUiBinder uiBinder = GWT.create(MainMediaPhotoThumbUiBinder.class); + + public MainMediaPhotoThumb(PlaceNavigator navigator) { + MainMediaResources.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/shared/mainmedia/MainMediaPhotoThumb.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaPhotoThumb.ui.xml new file mode 100644 index 00000000000..7962f6be02a --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaPhotoThumb.ui.xml @@ -0,0 +1,11 @@ + + + + + + + {photo.file},{photo.url}; + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaResources.java index db9f2033ba9..fd765b09e76 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaResources.java @@ -14,6 +14,7 @@ public interface MainMediaResources extends ClientBundle { String media(); String media_photo(); String media_slideshow(); + String media_slideshow_data(); String media_slideshow_controls(); String media_slideshow_controlsnext(); String media_slideshow_controlsprev(); 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 new file mode 100644 index 00000000000..717b067819c --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideo.java @@ -0,0 +1,32 @@ +package com.sap.sailing.gwt.home.client.shared.mainmedia; + +import com.google.gwt.core.client.GWT; +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.sap.sailing.gwt.home.client.app.PlaceNavigator; + +public class MainMediaVideo extends Composite { + + + interface MainMediaVideoThumbUiBinder extends UiBinder { + } + + private static MainMediaVideoThumbUiBinder uiBinder = GWT.create(MainMediaVideoThumbUiBinder.class); + + @UiField SpanElement videoTime; + @UiField SpanElement videoTitle; + +// @UiField videoThumbImage +// @UiField videoFile +// @UiField videoLink + + public MainMediaVideo(PlaceNavigator navigator) { + MainMediaResources.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/shared/mainmedia/MainMediaVideo.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideo.ui.xml new file mode 100644 index 00000000000..40cfa271f78 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideo.ui.xml @@ -0,0 +1,18 @@ + + + + + + + +
+
+
+ +
+
+
+
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideoThumb.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideoThumb.java new file mode 100644 index 00000000000..a84512ee10d --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideoThumb.java @@ -0,0 +1,22 @@ +package com.sap.sailing.gwt.home.client.shared.mainmedia; + +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; + +public class MainMediaVideoThumb extends Composite { + + + interface MainMediaVideoThumbUiBinder extends UiBinder { + } + + private static MainMediaVideoThumbUiBinder uiBinder = GWT.create(MainMediaVideoThumbUiBinder.class); + + public MainMediaVideoThumb() { + MainMediaResources.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/shared/mainmedia/MainMediaVideoThumb.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideoThumb.ui.xml new file mode 100644 index 00000000000..6a4b9b3f369 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideoThumb.ui.xml @@ -0,0 +1,11 @@ + + + + + + +{video.thumb},{video.file},{video.title},video; + + 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 42205dd38a3..ebe38edcfcc 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 @@ -11,9 +11,9 @@ public class UpcomingEventStageTeaser extends StageTeaser { title.setInnerText(event.getName()); subtitle.setInnerText(event.venue.getName()); - countDown.setAttribute("data-starttime", ""); - countDown.setAttribute("data-days", "3"); - countDown.setAttribute("data-hours", "2"); + countDown.setAttribute("data-starttime", event.startDate.toString()); + countDown.setAttribute("data-days", ""); + countDown.setAttribute("data-hours", ""); countDown.setAttribute("data-minutes", ""); countDown.setAttribute("data-seconds", ""); diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/main.css b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/main.css index 8f4b3b06eb5..eff5c0be943 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/main.css +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/main.css @@ -238,3 +238,101 @@ figcaption { .mainsection_navigation { text-align: center; } + +.lightbox { + font-size: 16px; + font-size: 1rem; +} +.lightbox_overlay { + font-size: 16px; + font-size: 1rem; + background: #000; + opacity: 0.7; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=70)"; + filter: literal("alpha(opacity=70)"); + width: 100%; + height: 100%; + position: fixed; + left: 0; + top: 0; + z-index: 2000; + -webkit-transition: opacity 0.3s ease; + -moz-transition: opacity 0.3s ease; + -o-transition: opacity 0.3s ease; + -ms-transition: opacity 0.3s ease; + transition: opacity 0.3s ease; +} +.lightbox_overlayhidden { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); +} +.lightbox_content { + position: fixed; + width: 100%; + height: 100%; + opacity: 1; + -ms-filter: none; + filter: none; + top: 0; + left: 0; + z-index: 2001; + background-repeat: no-repeat; + background-position: center center; + -webkit-transition: opacity 0.3s ease; + -moz-transition: opacity 0.3s ease; + -o-transition: opacity 0.3s ease; + -ms-transition: opacity 0.3s ease; + transition: opacity 0.3s ease; +} +.lightbox_contenthidden { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); +} +.lightbox_content_enlargedimage { + -webkit-transition: margin 0.7s linear; + -moz-transition: margin 0.7s linear; + -o-transition: margin 0.7s linear; + -ms-transition: margin 0.7s linear; + transition: margin 0.7s linear; + display: inline-block; + position: absolute; + left: 50%; + top: 50%; +} +.lightbox_content_enlargedimage_caption { + text-align: center; + color: #f6f9fc; + font-weight: 700; +} +.lightbox_content_enlargedimage_close { + display: inline-block; + background-image: url("images/home/x_lightbox.png"); + background-repeat: no-repeat; + background-position: center center; + width: 1.25em; + height: 0; + overflow: hidden; + padding-top: 1.25em; + position: absolute; + top: 1.25em; + right: 1.25em; + cursor: pointer; + z-index: 2003; +} +/** +.lightbox_content .media_slideshow_controls { + z-index: 2002; +} +*/ + +.lightbox_data { + display: none; +} +.lightbox_photo { + cursor: pointer; +} + +.lightbox_item { +} From d55a5c5b53872842d342f5f585c852aa6161b33d Mon Sep 17 00:00:00 2001 From: fmittag Date: Wed, 9 Jul 2014 20:45:40 +0200 Subject: [PATCH 040/323] reimplemented photo gallery and recent videos on homepage --- .../client/shared/mainmedia/MainMedia.java | 47 +++++++++---- .../client/shared/mainmedia/MainMedia.ui.xml | 12 ++-- .../shared/mainmedia/MainMediaPhotoThumb.java | 23 ------- .../mainmedia/MainMediaPhotoThumb.ui.xml | 11 --- .../shared/mainmedia/MainMediaVideo.java | 48 +++++++++++-- .../shared/mainmedia/MainMediaVideo.ui.xml | 4 +- .../shared/mainmedia/MainMediaVideoThumb.java | 22 ------ .../mainmedia/MainMediaVideoThumb.ui.xml | 11 --- .../shared/mainmedia/RecentEventVideo.java | 68 ------------------- .../shared/mainmedia/RecentEventVideo.ui.xml | 19 ------ 10 files changed, 81 insertions(+), 184 deletions(-) delete mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaPhotoThumb.java delete mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaPhotoThumb.ui.xml delete mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideoThumb.java delete mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideoThumb.ui.xml delete mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/RecentEventVideo.java delete mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/RecentEventVideo.ui.xml 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 b067a922b74..d8f85a5c255 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 @@ -1,6 +1,9 @@ package com.sap.sailing.gwt.home.client.shared.mainmedia; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Random; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.DivElement; @@ -13,11 +16,11 @@ import com.sap.sailing.gwt.home.client.app.PlaceNavigator; import com.sap.sailing.gwt.ui.shared.EventDTO; public class MainMedia extends Composite { - @UiField HTMLPanel videoThumbsPanel; - @UiField HTMLPanel videoPanel; - @UiField HTMLPanel photoThumbsPanel; + @UiField HTMLPanel videosPanel; + @UiField DivElement videoLightBoxData; - @UiField DivElement photoUrl; + @UiField DivElement photoUrlsData; + @UiField DivElement currentPhotoUrl; interface MainMediaUiBinder extends UiBinder { } @@ -32,20 +35,38 @@ public class MainMedia extends Composite { } public void setRecentEvents(List recentEvents) { + List photoGalleryUrls = new ArrayList(); + String lightBoxData = ""; + int videoCounter = 0; for(EventDTO event: recentEvents) { - if(event.getVideoURLs().size() > 0) { - MainMediaVideoThumb videoThumb = new MainMediaVideoThumb(); - if(videoCounter <= 3) { - videoThumbsPanel.add(videoThumb); - videoCounter++; - } + photoGalleryUrls.addAll(event.getPhotoGalleryImageURLs()); + + if(event.getVideoURLs().size() > 0 && videoCounter < 3) { + MainMediaVideo video = new MainMediaVideo(event); + lightBoxData += video.getDataForLightBox(); + videosPanel.add(video); + videoCounter++; } } + + videoLightBoxData.setInnerText(lightBoxData); - photoUrl.setAttribute("data-url", "#1"); -// String imagePath = "url(" + imageX + ")"; -// photoUrl.getStyle().setBackgroundImage(imagePath); + // 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)); + } + + currentPhotoUrl.setAttribute("data-url", "#1"); + String imagePath = "url(" + photoGalleryUrls.get(0) + ")"; + currentPhotoUrl.getStyle().setBackgroundImage(imagePath); + String slideShowData = ""; + for(int i = 0; i < gallerySize; i++) { + slideShowData += photoGalleryUrls.get(0) + ",#" + (i+1) + ";"; + } + photoUrlsData.setInnerText(slideShowData); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.ui.xml index bc0bc703304..15358d70bf5 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.ui.xml @@ -20,10 +20,8 @@
-
- -
-
+
+
@@ -40,10 +38,8 @@
-
- -
- +
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaPhotoThumb.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaPhotoThumb.java deleted file mode 100644 index 175ed433f14..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaPhotoThumb.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.sap.sailing.gwt.home.client.shared.mainmedia; - -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.app.PlaceNavigator; - -public class MainMediaPhotoThumb extends Composite { - - - interface MainMediaPhotoThumbUiBinder extends UiBinder { - } - - private static MainMediaPhotoThumbUiBinder uiBinder = GWT.create(MainMediaPhotoThumbUiBinder.class); - - public MainMediaPhotoThumb(PlaceNavigator navigator) { - MainMediaResources.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/shared/mainmedia/MainMediaPhotoThumb.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaPhotoThumb.ui.xml deleted file mode 100644 index 7962f6be02a..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaPhotoThumb.ui.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - {photo.file},{photo.url}; - - 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 717b067819c..3e0a234a006 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 @@ -1,32 +1,66 @@ package com.sap.sailing.gwt.home.client.shared.mainmedia; import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.ImageElement; 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.sap.sailing.gwt.home.client.app.PlaceNavigator; +import com.sap.sailing.gwt.ui.shared.EventDTO; public class MainMediaVideo extends Composite { - interface MainMediaVideoThumbUiBinder extends UiBinder { + interface MainMediaVideoUiBinder extends UiBinder { } - private static MainMediaVideoThumbUiBinder uiBinder = GWT.create(MainMediaVideoThumbUiBinder.class); + private static MainMediaVideoUiBinder uiBinder = GWT.create(MainMediaVideoUiBinder.class); + + private final static String DEFAULT_VIDEO_THUMB_IMAGE_URL = "http://static.sapsailing.com/ubilabsimages/default/default_video_preview.jpg"; @UiField SpanElement videoTime; @UiField SpanElement videoTitle; -// @UiField videoThumbImage -// @UiField videoFile -// @UiField videoLink + @UiField ImageElement videoThumbImage; + @UiField Element videoFile; - public MainMediaVideo(PlaceNavigator navigator) { + // Format: {video.thumb},{video.file},{video.title},video; + private String lightBoxData; + + public MainMediaVideo(EventDTO event) { MainMediaResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); + String videoThumbImageUrl; + if(event.getPhotoGalleryImageURLs().size() > 0) { + videoThumbImageUrl = event.getPhotoGalleryImageURLs().get(0); + } else { + videoThumbImageUrl = DEFAULT_VIDEO_THUMB_IMAGE_URL; + } + videoThumbImage.setSrc(videoThumbImageUrl); + lightBoxData = videoThumbImageUrl + "," + event.getVideoURLs().get(0) + ";"; + + videoTime.setInnerHTML("3:40 min"); + videoTitle.setInnerHTML(event.getName()); + videoFile.setAttribute("data-src", event.getVideoURLs().get(0)); + } + + public String getDataForLightBox() { + return lightBoxData; } +// @UiHandler("eventMediaPageLink") +// public void goToEventMediaPage(ClickEvent e) { +// if(event.getBaseURL().contains("localhost") || event.getBaseURL().contains("127.0.0.1")) { +// navigator.goToEvent(event.id.toString()); +// } else { +// EventPlace eventPlace = new EventPlace(event.id.toString()); +// EventPlace.Tokenizer t = new Tokenizer(); +// String remoteEventUrl = event.getBaseURL() + "/gwt/Home.html#" + EventPlace.class.getSimpleName() + ":" + t.getToken(eventPlace); +// Window.Location.replace(remoteEventUrl); +// } +// } + } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideo.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideo.ui.xml index 40cfa271f78..346a6dcfa69 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideo.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideo.ui.xml @@ -8,8 +8,8 @@
-
-
+
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideoThumb.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideoThumb.java deleted file mode 100644 index a84512ee10d..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideoThumb.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.sap.sailing.gwt.home.client.shared.mainmedia; - -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; - -public class MainMediaVideoThumb extends Composite { - - - interface MainMediaVideoThumbUiBinder extends UiBinder { - } - - private static MainMediaVideoThumbUiBinder uiBinder = GWT.create(MainMediaVideoThumbUiBinder.class); - - public MainMediaVideoThumb() { - MainMediaResources.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/shared/mainmedia/MainMediaVideoThumb.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideoThumb.ui.xml deleted file mode 100644 index 6a4b9b3f369..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideoThumb.ui.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - -{video.thumb},{video.file},{video.title},video; - - diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/RecentEventVideo.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/RecentEventVideo.java deleted file mode 100644 index b403abfe74e..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/RecentEventVideo.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.sap.sailing.gwt.home.client.shared.mainmedia; - -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.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.app.PlaceNavigator; -import com.sap.sailing.gwt.home.client.place.event.EventPlace; -import com.sap.sailing.gwt.home.client.place.event.EventPlace.Tokenizer; -import com.sap.sailing.gwt.ui.shared.EventDTO; - -public class RecentEventVideo extends Composite { - - @UiField SpanElement eventVideoLength; - @UiField SpanElement eventName; - @UiField ImageElement eventVideoPreviewImage; - @UiField Anchor eventMediaPageLink; - - private final String defaultVideoImageUrl = "http://static.sapsailing.com/newhome/default_video_preview.jpg"; - - private EventDTO event; - - interface RecentEventVideoUiBinder extends UiBinder { - } - - private static RecentEventVideoUiBinder uiBinder = GWT.create(RecentEventVideoUiBinder.class); - - private final PlaceNavigator navigator; - - public RecentEventVideo(PlaceNavigator navigator) { - this.navigator = navigator; - initWidget(uiBinder.createAndBindUi(this)); - } - - public void setEvent(EventDTO event) { - this.event = event; - updateUI(); - } - - @UiHandler("eventMediaPageLink") - public void goToEventMediaPage(ClickEvent e) { - if(event.getBaseURL().contains("localhost") || event.getBaseURL().contains("127.0.0.1")) { - navigator.goToEvent(event.id.toString()); - } else { - EventPlace eventPlace = new EventPlace(event.id.toString()); - EventPlace.Tokenizer t = new Tokenizer(); - String remoteEventUrl = event.getBaseURL() + "/gwt/Home.html#" + EventPlace.class.getSimpleName() + ":" + t.getToken(eventPlace); - Window.Location.replace(remoteEventUrl); - } - } - - private void updateUI() { - eventName.setInnerText(event.getName()); - - if(event.getVideoURLs().size() == 0) { - eventVideoPreviewImage.setSrc(defaultVideoImageUrl); - } else { - eventVideoPreviewImage.setSrc(event.getVideoURLs().get(0)); - } - } -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/RecentEventVideo.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/RecentEventVideo.ui.xml deleted file mode 100644 index 105d3da0acd..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/RecentEventVideo.ui.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - -
-
-
-

-
-
Show More
-
-
-
-
-
From ed63e04d1db16a62ac48de28ab41feef0813ecbb Mon Sep 17 00:00:00 2001 From: fmittag Date: Thu, 10 Jul 2014 10:01:12 +0200 Subject: [PATCH 041/323] fiexe EventOVevrviewRessource file --- .../home/client/place/event/overview/EventOverviewResources.java | 1 - 1 file changed, 1 deletion(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/overview/EventOverviewResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/overview/EventOverviewResources.java index d714efdc02a..2dffd7be71e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/overview/EventOverviewResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/overview/EventOverviewResources.java @@ -29,7 +29,6 @@ public interface EventOverviewResources extends ClientBundle { String latestraceresults_detail_datetime(); String columns(); String latestraceresults_detail_wrapperimage(); - String media_slideshow_controls(); String mediateaser(); String mediateaser_title(); String mediateaser_title_link(); From 24de9384a06c3df8b07c7156fe8f5ba19ce2c0f6 Mon Sep 17 00:00:00 2001 From: Jens Rommel Date: Thu, 10 Jul 2014 17:10:59 +0200 Subject: [PATCH 042/323] Swiper is working, though still layout issues: - not centered - strange padding at left side --- .../client/shared/mainmedia/MainMedia.css | 24 +++-------- .../client/shared/mainmedia/MainMedia.java | 43 ++++++++++++++----- .../client/shared/mainmedia/MainMedia.ui.xml | 12 +++--- .../shared/mainmedia/MainMediaResources.java | 5 ++- .../sap/sailing/gwt/idangerous/Swiper.java | 2 + 5 files changed, 50 insertions(+), 36 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css index 170a9066b69..e1351f53a8f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css @@ -2,28 +2,18 @@ font-size: 15px; font-size: 1rem; } -.media_photo { - width: 100%; - height: 100%; - -webkit-transition: margin 1s ease; - -moz-transition: margin 1s ease; - -o-transition: margin 1s ease; - -ms-transition: margin 1s ease; - transition: margin 1s ease; - position: absolute; - background-position: center center; - background-repeat: no-repeat; - cursor: pointer; -} -.media_slideshow { - position: relative; +.media_swipecontainer { height: 550px; width: 830px; margin: 0 auto; overflow: hidden; } -.media_slideshow_data { - display: none; +.media_swipewrapper { +} +.media_swiperslide { +} +.media_photo { + cursor: pointer; } .media_slideshow_controls { position: absolute; 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 d8f85a5c255..714ef599d00 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 @@ -7,20 +7,28 @@ import java.util.Random; 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.ui.Composite; import com.google.gwt.user.client.ui.HTMLPanel; +import com.google.gwt.user.client.ui.Image; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.home.client.app.PlaceNavigator; +import com.sap.sailing.gwt.idangerous.Swiper; import com.sap.sailing.gwt.ui.shared.EventDTO; public class MainMedia extends Composite { + + private static final MainMediaResources.LocalCss STYLES = MainMediaResources.INSTANCE.css(); + @UiField HTMLPanel videosPanel; @UiField DivElement videoLightBoxData; + + @UiField HTMLPanel mediaSlides; - @UiField DivElement photoUrlsData; - @UiField DivElement currentPhotoUrl; + private Swiper swiper; interface MainMediaUiBinder extends UiBinder { } @@ -59,14 +67,29 @@ public class MainMedia extends Composite { Collections.swap(photoGalleryUrls, i, random.nextInt(gallerySize)); } - currentPhotoUrl.setAttribute("data-url", "#1"); - String imagePath = "url(" + photoGalleryUrls.get(0) + ")"; - currentPhotoUrl.getStyle().setBackgroundImage(imagePath); - - String slideShowData = ""; - for(int i = 0; i < gallerySize; i++) { - slideShowData += photoGalleryUrls.get(0) + ",#" + (i+1) + ";"; + for(String url : photoGalleryUrls) { + Image imageSlide = new Image(url); + imageSlide.addStyleName(STYLES.media_photo()); + imageSlide.addStyleName(STYLES.media_swiperslide()); + mediaSlides.add(imageSlide); } - photoUrlsData.setInnerText(slideShowData); + + this.swiper = Swiper.createWithDefaultOptions(STYLES.media_swipecontainer(), STYLES.media_swipewrapper(), STYLES.media_swiperslide()); + } + + @UiHandler("nextPictureLink") + public void nextStageTeaserLinkClicked(ClickEvent e) { + if (this.swiper != null) { + this.swiper.swipeNext(); + } + } + + @UiHandler("prevPictureLink") + public void prevStageTeaserLinkClicked(ClickEvent e) { + if (this.swiper != null) { + this.swiper.swipePrev(); + } + } + } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.ui.xml index 15358d70bf5..27338d4ce00 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.ui.xml @@ -18,13 +18,11 @@
-
-
-
-
-
-
-
+
+ + + +
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaResources.java index fd765b09e76..5a1960e7df0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaResources.java @@ -12,9 +12,10 @@ public interface MainMediaResources extends ClientBundle { public interface LocalCss extends CssResource { String media(); + String media_swipecontainer(); + String media_swipewrapper(); + String media_swiperslide(); String media_photo(); - String media_slideshow(); - String media_slideshow_data(); String media_slideshow_controls(); String media_slideshow_controlsnext(); String media_slideshow_controlsprev(); 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 5934c4b8aff..1f378c99dc7 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 @@ -13,6 +13,8 @@ public final class Swiper extends JavaScriptObject { public static native Swiper createWithDefaultOptions(String containerClass, String wrapperClass, String slideClass) /*-{ var options = { loop: true, + mode: 'horizontal', + centeredSlides: true, // onSlideChangeStart: this.resetProgressbar, // onTouchMoveStart: this.stopProgressbar, // onTouchEnd: this.onProgress, From 75bacf0a7925af52a58773a06d8c0ae599e1cde6 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 10 Jul 2014 23:41:48 +0200 Subject: [PATCH 043/323] AdminConsole depends on raceboarod because of a constant that it requires for URL construction --- .../.settings/com.google.gwt.eclipse.core.prefs | 8 ++++---- .../resources/com/sap/sailing/gwt/ui/AdminConsole.gwt.xml | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) 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 85f0d558292..1c83ff05b6b 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,4 @@ -eclipse.preferences.version=1 -entryPointModules= -filesCopiedToWebInfLib=gwt-servlet.jar -gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDMgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0UgLWNvbXBpbGVSZXBvcnQgLVhzb3ljRGV0YWlsZWRdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5BZG1pbkNvbnNvbGU8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuUmFjZUJvYXJkPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLkxlYWRlcmJvYXJkPC9lbnRyeS1wb2ludC1tb2R1bGU+PC9nd3QtY29tcGlsZS1zZXR0aW5ncz4\= +eclipse.preferences.version=1 +entryPointModules= +filesCopiedToWebInfLib=gwt-servlet.jar +gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDMgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0UgLWNvbXBpbGVSZXBvcnQgLVhzb3ljRGV0YWlsZWRdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5BZG1pbkNvbnNvbGU8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuUmFjZUJvYXJkPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLkxlYWRlcmJvYXJkPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLllvdXR1YmVQb3B1cDwvZW50cnktcG9pbnQtbW9kdWxlPjwvZ3d0LWNvbXBpbGUtc2V0dGluZ3M+ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/AdminConsole.gwt.xml b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/AdminConsole.gwt.xml index 3c4267e675b..52b718b7c8c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/AdminConsole.gwt.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/AdminConsole.gwt.xml @@ -64,6 +64,7 @@ + From 4473a4a0e494f1b787c645d50ecfc8147bab5846 Mon Sep 17 00:00:00 2001 From: Jens Rommel Date: Fri, 11 Jul 2014 09:35:32 +0200 Subject: [PATCH 044/323] Fixed ugly padding and centering issues. Introduced new issue with prev/next buttons. --- .../gwt/home/client/shared/mainmedia/MainMedia.css | 2 +- .../gwt/home/client/shared/mainmedia/MainMedia.ui.xml | 11 ++++++----- .../java/com/sap/sailing/gwt/idangerous/Swiper.java | 5 ----- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css index e1351f53a8f..980492c57e0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css @@ -3,8 +3,8 @@ font-size: 1rem; } .media_swipecontainer { - height: 550px; width: 830px; + height: 550px; margin: 0 auto; overflow: hidden; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.ui.xml index 27338d4ce00..334ce1b1beb 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.ui.xml @@ -18,11 +18,12 @@
-
- - - - +
+
+ + + +
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 1f378c99dc7..ec70192040d 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 @@ -13,11 +13,6 @@ public final class Swiper extends JavaScriptObject { public static native Swiper createWithDefaultOptions(String containerClass, String wrapperClass, String slideClass) /*-{ var options = { loop: true, - mode: 'horizontal', - centeredSlides: true, -// onSlideChangeStart: this.resetProgressbar, -// onTouchMoveStart: this.stopProgressbar, -// onTouchEnd: this.onProgress, wrapperClass: wrapperClass, slideClass: slideClass }; From 335000e2a4ffc071e9679aa3485ba4115c3d56d6 Mon Sep 17 00:00:00 2001 From: Jens Rommel Date: Fri, 11 Jul 2014 13:05:28 +0200 Subject: [PATCH 045/323] Solved issue with wrongly positioned prev/next buttons (adding missing "position: relative") Still open: handling of differently sized images --- .../client/shared/mainmedia/MainMedia.css | 1 + .../client/shared/mainmedia/MainMedia.ui.xml | 67 ++++++++++--------- 2 files changed, 38 insertions(+), 30 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css index 980492c57e0..b631b994f5b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css @@ -3,6 +3,7 @@ font-size: 1rem; } .media_swipecontainer { + position: relative; width: 830px; height: 550px; margin: 0 auto; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.ui.xml index 334ce1b1beb..f77e4f6babb 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.ui.xml @@ -1,45 +1,52 @@ - - - - - + + + + + - -
-
+ +
+
-
-

-

-
+
+

+ +

+

+ +

+
-
-
+
+
- - - + + +
+
-
-
-
+
+
-
-

-

-
+
+

+ +

+

+ +

+
-
-
+
+
+
-
- + From f73c553734521cee4e1724e4eadb9c63f5ac674b Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 11 Jul 2014 13:16:52 +0200 Subject: [PATCH 046/323] fixing bug 2047 by introducing DateTimeBox on EventDialog for start/end date --- .../ui/adminconsole/EventCreateDialog.java | 13 ++--- .../gwt/ui/adminconsole/EventDialog.java | 10 ++-- .../gwt/ui/adminconsole/EventEditDialog.java | 11 ++-- .../adminconsole/LeaderboardConfigPanel.java | 4 +- .../ui/adminconsole/SetStartTimeDialog.java | 26 +-------- .../client/DataEntryDialogWithBootstrap.java | 55 +++++++++++++++++++ .../gwt/ui/shared/BetterDateTimeBox.java | 4 ++ 7 files changed, 79 insertions(+), 44 deletions(-) create mode 100755 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DataEntryDialogWithBootstrap.java diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventCreateDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventCreateDialog.java index 23f69853ef1..036b6dccb4c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventCreateDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventCreateDialog.java @@ -2,10 +2,8 @@ package com.sap.sailing.gwt.ui.adminconsole; import java.util.Collection; import java.util.Collections; +import java.util.Date; -import com.google.gwt.i18n.client.DateTimeFormat; -import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat; -import com.google.gwt.user.datepicker.client.DateBox; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; @@ -23,10 +21,11 @@ public class EventCreateDialog extends EventDialog { descriptionEntryField.getElement().getStyle().setProperty("resize", "none"); venueEntryField = createTextBox(null); venueEntryField.setVisibleLength(35); - startDateBox = createDateBox(12); - startDateBox.setFormat(new DateBox.DefaultFormat(DateTimeFormat.getFormat(PredefinedFormat.DATE_SHORT))); - endDateBox = createDateBox(12); - endDateBox.setFormat(new DateBox.DefaultFormat(DateTimeFormat.getFormat(PredefinedFormat.DATE_SHORT))); + final Date now = new Date(); + startDateBox = createDateTimeBox(now); + startDateBox.setFormat("dd/mm/yyyy hh:ii"); + endDateBox = createDateTimeBox(now); + endDateBox.setFormat("dd/mm/yyyy hh:ii"); officialWebsiteURLEntryField = createTextBox(null); officialWebsiteURLEntryField.setVisibleLength(50); logoImageURLEntryField = createTextBox(null); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDialog.java index c3adf420094..36720104dd9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDialog.java @@ -18,23 +18,23 @@ import com.google.gwt.user.client.ui.TextArea; 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.user.datepicker.client.DateBox; +import com.sap.sailing.gwt.ui.client.DataEntryDialogWithBootstrap; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.controls.listedit.StringListInlineEditorComposite; +import com.sap.sailing.gwt.ui.shared.BetterDateTimeBox; 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.VenueDTO; -import com.sap.sse.gwt.client.dialog.DataEntryDialog; -public abstract class EventDialog extends DataEntryDialog { +public abstract class EventDialog extends DataEntryDialogWithBootstrap { private final AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); protected StringMessages stringMessages; protected TextBox nameEntryField; protected TextArea descriptionEntryField; protected TextBox venueEntryField; - protected DateBox startDateBox; - protected DateBox endDateBox; + protected BetterDateTimeBox startDateBox; + protected BetterDateTimeBox endDateBox; protected CheckBox isPublicCheckBox; protected UUID id; protected TextBox officialWebsiteURLEntryField; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventEditDialog.java index 43c7b6a1459..e5b5684f3ec 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventEditDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventEditDialog.java @@ -4,9 +4,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import com.google.gwt.i18n.client.DateTimeFormat; -import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat; -import com.google.gwt.user.datepicker.client.DateBox; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.CourseAreaDTO; import com.sap.sailing.gwt.ui.shared.EventDTO; @@ -23,10 +20,10 @@ public class EventEditDialog extends EventDialog { descriptionEntryField.getElement().getStyle().setProperty("resize", "none"); venueEntryField = createTextBox(event.venue.getName()); venueEntryField.setVisibleLength(35); - startDateBox = createDateBox(event.startDate, 12); - startDateBox.setFormat(new DateBox.DefaultFormat(DateTimeFormat.getFormat(PredefinedFormat.DATE_SHORT))); - endDateBox = createDateBox(event.endDate, 12); - endDateBox.setFormat(new DateBox.DefaultFormat(DateTimeFormat.getFormat(PredefinedFormat.DATE_SHORT))); + startDateBox = createDateTimeBox(event.startDate); + startDateBox.setFormat("dd/mm/yyyy hh:ii"); + endDateBox = createDateTimeBox(event.endDate); + endDateBox.setFormat("dd/mm/yyyy hh:ii"); isPublicCheckBox = createCheckbox(""); isPublicCheckBox.setValue(event.isPublic); id = event.id; 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 caef5f0b85a..18f29beb961 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 @@ -348,9 +348,9 @@ TrackedRaceChangedListener, LeaderboardsDisplayer { unlinkRaceColumnFromTrackedRace(object.getA().getRaceColumnName(), object.getB()); } else if (LeaderboardRaceConfigImagesBarCell.ACTION_REFRESH_RACELOG.equals(value)) { refreshRaceLog(object.getA(), object.getB(), true); - } else if(LeaderboardRaceConfigImagesBarCell.ACTION_SET_STARTTIME.equals(value)) { + } else if (LeaderboardRaceConfigImagesBarCell.ACTION_SET_STARTTIME.equals(value)) { setStartTime(object.getA(), object.getB()); - } else if(LeaderboardRaceConfigImagesBarCell.ACTION_SHOW_RACELOG.equals(value)) { + } else if (LeaderboardRaceConfigImagesBarCell.ACTION_SHOW_RACELOG.equals(value)) { showRaceLog(object.getA(), object.getB()); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SetStartTimeDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SetStartTimeDialog.java index 0b734961f9d..2632d411b2e 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SetStartTimeDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SetStartTimeDialog.java @@ -2,14 +2,9 @@ package com.sap.sailing.gwt.ui.adminconsole; import java.util.Date; -import com.github.gwtbootstrap.datetimepicker.client.ui.base.HasViewMode.ViewMode; 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.event.logical.shared.AttachEvent; -import com.google.gwt.event.logical.shared.AttachEvent.Handler; -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.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.CaptionPanel; @@ -25,6 +20,7 @@ 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.racelog.RacingProcedureType; +import com.sap.sailing.gwt.ui.client.DataEntryDialogWithBootstrap; import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; @@ -32,7 +28,7 @@ import com.sap.sailing.gwt.ui.shared.BetterDateTimeBox; import com.sap.sailing.gwt.ui.shared.RaceLogSetStartTimeAndProcedureDTO; import com.sap.sse.gwt.client.dialog.DataEntryDialog; -public class SetStartTimeDialog extends DataEntryDialog { +public class SetStartTimeDialog extends DataEntryDialogWithBootstrap { private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); @@ -103,23 +99,7 @@ public class SetStartTimeDialog extends DataEntryDialog() { - @Override - public void onValueChange(ValueChangeEvent event) { - validate(); - } - }); - timeBox.addAttachHandler(new Handler() { - @Override - public void onAttachOrDetach(AttachEvent event) { - if (event.isAttached()) { - addAutoHidePartner(timeBox.getPicker()); - } - } - }); - timeBox.setAutoClose(true); - timeBox.setStartView(ViewMode.HOUR); + timeBox = createDateTimeBox(new Date()); timeBox.setFormat("dd/mm/yyyy hh:ii"); content.setWidget(0, 0, createLabel(stringMessages.startTime())); content.setWidget(0, 1, timeBox); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DataEntryDialogWithBootstrap.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DataEntryDialogWithBootstrap.java new file mode 100755 index 00000000000..65ec2c12dda --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DataEntryDialogWithBootstrap.java @@ -0,0 +1,55 @@ +package com.sap.sailing.gwt.ui.client; + +import java.util.Date; + +import com.github.gwtbootstrap.datetimepicker.client.ui.base.HasViewMode.ViewMode; +import com.google.gwt.event.logical.shared.AttachEvent; +import com.google.gwt.event.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.google.gwt.event.logical.shared.AttachEvent.Handler; +import com.sap.sailing.gwt.ui.shared.BetterDateTimeBox; +import com.sap.sse.gwt.client.dialog.DataEntryDialog; +import com.sap.sse.gwt.client.dialog.DialogUtils; + +public abstract class DataEntryDialogWithBootstrap extends DataEntryDialog { + + public DataEntryDialogWithBootstrap(String title, String message, String okButtonName, String cancelButtonName, + com.sap.sse.gwt.client.dialog.DataEntryDialog.Validator validator, boolean animationEnabled, + com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback callback) { + super(title, message, okButtonName, cancelButtonName, validator, animationEnabled, callback); + } + + protected DataEntryDialogWithBootstrap(String title, String message, String okButtonName, String cancelButtonName, + com.sap.sse.gwt.client.dialog.DataEntryDialog.Validator validator, + com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback callback) { + super(title, message, okButtonName, cancelButtonName, validator, callback); + } + + /** + * Call something like setFormat("dd/mm/yyyy hh:ii") on the result to set the date / time entry format. + */ + public BetterDateTimeBox createDateTimeBox(Date initialValue) { + final BetterDateTimeBox result = new BetterDateTimeBox(); + result.setValue(initialValue); + result.addValueChangeHandler(new ValueChangeHandler() { + @Override + public void onValueChange(ValueChangeEvent event) { + validate(); + } + }); + result.addAttachHandler(new Handler() { + @Override + public void onAttachOrDetach(AttachEvent event) { + if (event.isAttached()) { + addAutoHidePartner(result.getPicker()); + } + } + }); + result.setAutoClose(true); + result.setStartView(ViewMode.HOUR); + DialogUtils.linkEnterToButton(getOkButton(), result.getBox()); + DialogUtils.linkEscapeToButton(getCancelButton(), result.getBox()); + return result; + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/BetterDateTimeBox.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/BetterDateTimeBox.java index 9098a8e273e..a88f545dcac 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/BetterDateTimeBox.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/BetterDateTimeBox.java @@ -26,4 +26,8 @@ public class BetterDateTimeBox extends DateTimeBox { return $wnd.jQuery(boxElement).data('datetimepicker').picker[0]; }-*/; + @Override + public com.github.gwtbootstrap.client.ui.TextBox getBox() { + return super.getBox(); + } } From 60f3c4811d3e91470db08c888f9978688347599f Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Fri, 11 Jul 2014 13:33:26 +0200 Subject: [PATCH 047/323] ImportMasterDataOperation now initializes RaceLogInformation in readObject fixes bug2023 --- .../ImportMasterDataOperation.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/ImportMasterDataOperation.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/ImportMasterDataOperation.java index 8a60ff1d703..0785f10195b 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/ImportMasterDataOperation.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/ImportMasterDataOperation.java @@ -1,6 +1,7 @@ package com.sap.sailing.server.operationaltransformation; import java.io.IOException; +import java.io.ObjectInputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; @@ -15,6 +16,7 @@ import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.impl.RegattaImpl; import com.sap.sailing.domain.common.DataImportProgress; import com.sap.sailing.domain.common.RaceIdentifier; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; @@ -99,6 +101,9 @@ public class ImportMasterDataOperation extends createWindTracks(toState); toState.getDataImportLock().getProgress(importOperationId).setResult(creationCount); return creationCount; + } catch (Exception e) { + logger.severe("Error during execution of ImportMasterDataOperation"); + throw new RuntimeException("Error during execution of ImportMasterDataOperation", e); } finally { toState.getDataImportLock().unlock(); } @@ -406,4 +411,18 @@ public class ImportMasterDataOperation extends return null; } + /** + * in order to restore all listeners we need to initialize the regatta after the whole object graph has been + * restored. This applies to all replicas that receive this operation "over the wire". + * + * Fixes bug2023 + */ + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + ois.defaultReadObject(); + for (Regatta regatta : masterData.getAllRegattas()) { + RegattaImpl regattaImpl = (RegattaImpl) regatta; + regattaImpl.initializeSeriesAfterDeserialize(); + } + } + } From c372147c1e3b97e42dce92217f52abc60dcb34e8 Mon Sep 17 00:00:00 2001 From: Jens Rommel Date: Fri, 11 Jul 2014 14:17:36 +0200 Subject: [PATCH 048/323] Fixed last remaing issue, no supporting different resolutions --- .../client/shared/mainmedia/MainMedia.css | 150 ++++++++++-------- .../client/shared/mainmedia/MainMedia.java | 11 +- .../shared/mainmedia/MainMediaResources.java | 1 - 3 files changed, 90 insertions(+), 72 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css index b631b994f5b..d523037693f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css @@ -1,97 +1,115 @@ .media { - font-size: 15px; - font-size: 1rem; + font-size: 15px; + font-size: 1rem; } + .media_swipecontainer { - position: relative; - width: 830px; - height: 550px; - margin: 0 auto; - overflow: hidden; + position: relative; + width: 830px; + height: 550px; + margin: 0 auto; + overflow: hidden; } + .media_swipewrapper { } + .media_swiperslide { + display: inline-block; + background: no-repeat center center; + -webkit-background-size: contain; + -moz-background-size: contain; + -o-background-size: contain; + background-size: contain; } -.media_photo { - cursor: pointer; -} + .media_slideshow_controls { - position: absolute; - background-position: 0 0; - background-repeat: no-repeat; - cursor: pointer; - z-index: 10; + position: absolute; + background-position: 0 0; + background-repeat: no-repeat; + cursor: pointer; + z-index: 10; } -.media_slideshow_controlsnext, + +.media_slideshow_controlsnext,.media_slideshow_controlsprev { + top: 50%; + -webkit-transform: translate(0, -50%); + -moz-transform: translate(0, -50%); + -o-transform: translate(0, -50%); + -ms-transform: translate(0, -50%); + transform: translate(0, -50%); + width: 60px; + height: 60px; + background-image: url("images/home/media-controls__arrows.png"); +} + .media_slideshow_controlsprev { - top: 50%; - -webkit-transform: translate(0, -50%); - -moz-transform: translate(0, -50%); - -o-transform: translate(0, -50%); - -ms-transform: translate(0, -50%); - transform: translate(0, -50%); - width: 60px; - height: 60px; - background-image: url("images/home/media-controls__arrows.png"); -} -.media_slideshow_controlsprev { - left: 0; - background-position: 0 0; + left: 0; + background-position: 0 0; } + .media_slideshow_controlsprev:hover { - background-position: 0 100%; + background-position: 0 100%; } + .media_slideshow_controlsnext { - right: 0; - background-position: 100% 0; + right: 0; + background-position: 100% 0; } + .media_slideshow_controlsnext:hover { - background-position: 100% 100%; + background-position: 100% 100%; } + .media_slideshow_controlsfullsize { - background-image: url("images/home/media-controls__fullsize.png"); - right: 0; - bottom: 0; - width: 40px; - height: 40px; + background-image: url("images/home/media-controls__fullsize.png"); + right: 0; + bottom: 0; + width: 40px; + height: 40px; } + .mediavideos { - background: #333; - color: #fff; + background: #333; + color: #fff; } + .mediavideos .mainsection_header_title { - color: #fff; + color: #fff; } + .videopreview { - font-size: 15px; - font-size: 1rem; - margin-bottom: 2.666666666666667em; - cursor: pointer; + font-size: 15px; + font-size: 1rem; + margin-bottom: 2.666666666666667em; + cursor: pointer; } + .videopreview:before { - content: ' '; - position: absolute; - left: 50%; - top: 70px; - -webkit-transform: translate(-50%, 0); - -moz-transform: translate(-50%, 0); - -o-transform: translate(-50%, 0); - -ms-transform: translate(-50%, 0); - transform: translate(-50%, 0); - width: 69px; - height: 69px; - background: url("images/home/video__preview--play.png"); - -webkit-background-size: contain; - -moz-background-size: contain; - background-size: contain; + content: ' '; + position: absolute; + left: 50%; + top: 70px; + -webkit-transform: translate(-50%, 0); + -moz-transform: translate(-50%, 0); + -o-transform: translate(-50%, 0); + -ms-transform: translate(-50%, 0); + transform: translate(-50%, 0); + width: 69px; + height: 69px; + background: url("images/home/video__preview--play.png"); + -webkit-background-size: contain; + -moz-background-size: contain; + background-size: contain; } + .videopreview_title { - font-size: 1.466666666666667em; - font-weight: bold; - margin: 0; - color: #fff; + font-size: 1.466666666666667em; + font-weight: bold; + margin: 0; + color: #fff; } + .videopreview time { - display: block; -} + display: block; +} \ 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/mainmedia/MainMedia.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.java index 714ef599d00..642b4dfbbae 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 @@ -13,7 +13,7 @@ import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiHandler; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.HTMLPanel; -import com.google.gwt.user.client.ui.Image; +import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.home.client.app.PlaceNavigator; import com.sap.sailing.gwt.idangerous.Swiper; @@ -68,10 +68,11 @@ public class MainMedia extends Composite { } for(String url : photoGalleryUrls) { - Image imageSlide = new Image(url); - imageSlide.addStyleName(STYLES.media_photo()); - imageSlide.addStyleName(STYLES.media_swiperslide()); - mediaSlides.add(imageSlide); + SimplePanel imageContainer = new SimplePanel(); + imageContainer.addStyleName(STYLES.media_swiperslide()); + String image = "url(" + url + ")"; + imageContainer.getElement().getStyle().setBackgroundImage(image); + mediaSlides.add(imageContainer); } this.swiper = Swiper.createWithDefaultOptions(STYLES.media_swipecontainer(), STYLES.media_swipewrapper(), STYLES.media_swiperslide()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaResources.java index 5a1960e7df0..d6f8749985a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaResources.java @@ -15,7 +15,6 @@ public interface MainMediaResources extends ClientBundle { String media_swipecontainer(); String media_swipewrapper(); String media_swiperslide(); - String media_photo(); String media_slideshow_controls(); String media_slideshow_controlsnext(); String media_slideshow_controlsprev(); From a310926e362be1b60a9f46cd9941223c83357af9 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 11 Jul 2014 14:46:10 +0200 Subject: [PATCH 049/323] refactored AbstractLeaderboardViewer and subclasses such that the leaderboard panel can be retrieved through a getter --- .../AbstractLeaderboardViewer.java | 8 ++++- .../gwt/ui/leaderboard/LeaderboardViewer.java | 31 +++++++++------- .../ui/leaderboard/MetaLeaderboardViewer.java | 36 +++++++++++++------ 3 files changed, 51 insertions(+), 24 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/AbstractLeaderboardViewer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/AbstractLeaderboardViewer.java index 9dd56e6f9ca..7497b9b919e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/AbstractLeaderboardViewer.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/AbstractLeaderboardViewer.java @@ -26,6 +26,7 @@ import com.sap.sse.gwt.client.player.Timer; */ public abstract class AbstractLeaderboardViewer extends SimplePanel { protected final StringMessages stringMessages; + private final LeaderboardPanel leaderboardPanel; protected final CompetitorSelectionModel competitorSelectionProvider; protected final AsyncActionsExecutor asyncActionsExecutor; @@ -39,14 +40,19 @@ public abstract class AbstractLeaderboardViewer extends SimplePanel { private final static String STYLE_VIEWER_TOOLBAR_SETTINGS_BUTTON = "viewerToolbar-settingsButton"; public AbstractLeaderboardViewer(CompetitorSelectionModel competitorSelectionProvider, AsyncActionsExecutor asyncActionsExecutor, - Timer timer, StringMessages stringMessages, boolean hideToolbar) { + Timer timer, StringMessages stringMessages, boolean hideToolbar, LeaderboardPanel leaderboardPanel) { this.competitorSelectionProvider = competitorSelectionProvider; + this.leaderboardPanel = leaderboardPanel; this.asyncActionsExecutor = asyncActionsExecutor; this.stringMessages = stringMessages; this.timer = timer; this.hideToolbar = hideToolbar; } + public LeaderboardPanel getLeaderboardPanel() { + return leaderboardPanel; + } + protected FlowPanel createViewerPanel() { FlowPanel mainPanel = new FlowPanel(); mainPanel.setSize("100%", "100%"); 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 92670502c3a..399675f82c0 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 @@ -23,8 +23,6 @@ import com.sap.sse.gwt.client.useragent.UserAgentDetails; * */ public class LeaderboardViewer extends AbstractLeaderboardViewer { - - private final LeaderboardPanel leaderboardPanel; private final MultiCompetitorLeaderboardChart multiCompetitorChart; private LeaderboardPanel overallLeaderboardPanel; @@ -33,27 +31,36 @@ public class LeaderboardViewer extends AbstractLeaderboardViewer { final String leaderboardGroupName, String leaderboardName, final ErrorReporter errorReporter, final StringMessages stringMessages, final UserAgentDetails userAgent, boolean showRaceDetails, boolean hideToolbar, boolean autoExpandLastRaceColumn, boolean showCharts, DetailType chartDetailType, boolean showOverallLeaderboard) { - super(new CompetitorSelectionModel(/* hasMultiSelection */true), asyncActionsExecutor, - timer, stringMessages, hideToolbar); + this(new CompetitorSelectionModel(/* hasMultiSelection */true), sailingService, asyncActionsExecutor, timer, + leaderboardSettings, preselectedRace, leaderboardGroupName, leaderboardName, errorReporter, + stringMessages, userAgent, showRaceDetails, hideToolbar, autoExpandLastRaceColumn, showCharts, + chartDetailType, showOverallLeaderboard); + } + private LeaderboardViewer(CompetitorSelectionModel competitorSelectionModel, + 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 hideToolbar, boolean autoExpandLastRaceColumn, boolean showCharts, DetailType chartDetailType, + boolean showOverallLeaderboard) { + 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)); final FlowPanel mainPanel = createViewerPanel(); setWidget(mainPanel); - - leaderboardPanel = new LeaderboardPanel(sailingService, asyncActionsExecutor, - leaderboardSettings, preselectedRace, competitorSelectionProvider, timer, - leaderboardGroupName, leaderboardName, errorReporter, stringMessages, userAgent, - showRaceDetails, /* raceTimesInfoProvider */null, autoExpandLastRaceColumn, /* adjustTimerDelay */ true); - multiCompetitorChart = new MultiCompetitorLeaderboardChart(sailingService, asyncActionsExecutor, leaderboardName, chartDetailType, competitorSelectionProvider, timer, stringMessages, errorReporter); multiCompetitorChart.setVisible(showCharts); multiCompetitorChart.getElement().getStyle().setMarginTop(10, Unit.PX); multiCompetitorChart.getElement().getStyle().setMarginBottom(10, Unit.PX); - mainPanel.add(leaderboardPanel); + mainPanel.add(getLeaderboardPanel()); mainPanel.add(multiCompetitorChart); - addComponentToNavigationMenu(leaderboardPanel, false, null, /* hasSettingsWhenComponentIsInvisible*/ true); + addComponentToNavigationMenu(getLeaderboardPanel(), false, null, /* hasSettingsWhenComponentIsInvisible*/ true); addComponentToNavigationMenu(multiCompetitorChart, true, null, /* hasSettingsWhenComponentIsInvisible*/ true); // Remark: For now we only add the overallLeaderboardPanel to the navigation menu in case it's visible from the beginning 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 e4cdbfc7580..f98487972c5 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 @@ -15,30 +15,43 @@ import com.sap.sse.gwt.client.player.Timer; import com.sap.sse.gwt.client.useragent.UserAgentDetails; /** - * A viewer for an overall series leaderboard. - * Additionally the viewer can render a chart for the series leaderboard and a MultiLeaderboardPanel - * where the user can select to show one leaderboard of the series. + * A viewer for an overall series leaderboard. Additionally the viewer can render a chart for the series leaderboard and + * a MultiLeaderboardPanel where the user can select to show one leaderboard of the series. + * * @author Frank Mittag (c163874) + * @author Axel Uhl (d043530) */ public class MetaLeaderboardViewer extends AbstractLeaderboardViewer { - private final LeaderboardPanel metaLeaderboardPanel; private final MultiLeaderboardPanel multiLeaderboardPanel; private final MultiCompetitorLeaderboardChart multiCompetitorChart; + public MetaLeaderboardViewer(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 hideToolbar, boolean autoExpandLastRaceColumn, boolean showCharts, DetailType chartDetailType, boolean showSeriesLeaderboards) { - super(new CompetitorSelectionModel(/* hasMultiSelection */true), asyncActionsExecutor, timer, stringMessages, hideToolbar); + this(new CompetitorSelectionModel(/* hasMultiSelection */true), sailingService, asyncActionsExecutor, timer, + leaderboardSettings, preselectedLeaderboardName, preselectedRace, leaderboardGroupName, metaLeaderboardName, + errorReporter, stringMessages, userAgent, showRaceDetails, hideToolbar, + autoExpandLastRaceColumn, showCharts, chartDetailType, showSeriesLeaderboards); + } + + private MetaLeaderboardViewer(CompetitorSelectionModel competitorSelectionModel, 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 hideToolbar, boolean autoExpandLastRaceColumn, + boolean showCharts, DetailType chartDetailType, boolean showSeriesLeaderboards) { + super(competitorSelectionModel, asyncActionsExecutor, timer, stringMessages, hideToolbar, + new LeaderboardPanel(sailingService, asyncActionsExecutor, + leaderboardSettings, preselectedRace, competitorSelectionModel, timer, + leaderboardGroupName, metaLeaderboardName, errorReporter, stringMessages, userAgent, + showRaceDetails, /* raceTimesInfoProvider */null, autoExpandLastRaceColumn, /* adjustTimerDelay */ true)); FlowPanel mainPanel = createViewerPanel(); setWidget(mainPanel); final Label overallStandingsLabel = new Label(stringMessages.overallStandings()); overallStandingsLabel.setStyleName("leaderboardHeading"); - metaLeaderboardPanel = new LeaderboardPanel(sailingService, asyncActionsExecutor, - leaderboardSettings, preselectedRace, competitorSelectionProvider, timer, - leaderboardGroupName, metaLeaderboardName, errorReporter, stringMessages, userAgent, - showRaceDetails, /* raceTimesInfoProvider */null, autoExpandLastRaceColumn, /* adjustTimerDelay */ true); multiCompetitorChart = new MultiCompetitorLeaderboardChart(sailingService, asyncActionsExecutor, metaLeaderboardName, chartDetailType, competitorSelectionProvider, timer, stringMessages, errorReporter); multiCompetitorChart.setVisible(showCharts); @@ -48,11 +61,12 @@ public class MetaLeaderboardViewer extends AbstractLeaderboardViewer { preselectedLeaderboardName, preselectedRace, errorReporter, stringMessages, userAgent, showRaceDetails, autoExpandLastRaceColumn); multiLeaderboardPanel.setVisible(showSeriesLeaderboards); - mainPanel.add(metaLeaderboardPanel); + mainPanel.add(getLeaderboardPanel()); mainPanel.add(multiCompetitorChart); mainPanel.add(multiLeaderboardPanel); - addComponentToNavigationMenu(metaLeaderboardPanel, false, stringMessages.seriesLeaderboard(), /* hasSettingsWhenComponentIsInvisible*/ true); + addComponentToNavigationMenu(getLeaderboardPanel(), false, stringMessages.seriesLeaderboard(), /* hasSettingsWhenComponentIsInvisible*/ true); addComponentToNavigationMenu(multiCompetitorChart, true, null, /* hasSettingsWhenComponentIsInvisible*/ true); addComponentToNavigationMenu(multiLeaderboardPanel, true , stringMessages.regattaLeaderboards(), /* hasSettingsWhenComponentIsInvisible*/ false); } + } From 9743a40dc174a71407ea9fd549f01d7664f7fdb6 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 11 Jul 2014 15:07:35 +0200 Subject: [PATCH 050/323] attempted to change to lastN column selection strategy upon resize below the smallWidth threshold; hard to control; could be too surprising --- .../gwt/ui/leaderboard/LeaderboardEntryPoint.java | 13 ++++++++++++- .../gwt/ui/leaderboard/LeaderboardPanel.java | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java index 90e34269819..b589fc99306 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java @@ -155,7 +155,18 @@ public class LeaderboardEntryPoint extends AbstractEntryPoint { } mainPanel.addNorth(logoAndTitlePanel, 68); } - ScrollPanel contentScrollPanel = new ScrollPanel(); + ScrollPanel contentScrollPanel = new ScrollPanel() { + @Override + public void onResize() { + super.onResize(); + if (isSmallWidth()) { + final AbstractLeaderboardViewer leaderboardViewer = (AbstractLeaderboardViewer) getWidget(); + final LeaderboardPanel leaderboardPanel = leaderboardViewer.getLeaderboardPanel(); + int width = leaderboardPanel.getElement().getClientWidth(); + leaderboardPanel.setRaceColumnSelectionToLastNStrategy((width-500)/50); + } + } + }; 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 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 33b4ade4ac4..1690c1909f6 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 @@ -483,7 +483,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS })); } - private void setRaceColumnSelectionToLastNStrategy(final Integer numberOfLastRacesToShow) { + void setRaceColumnSelectionToLastNStrategy(final Integer numberOfLastRacesToShow) { raceColumnSelection = new LastNRacesColumnSelection(numberOfLastRacesToShow, getRaceTimesInfoProvider()); if (timer.getPlayState() != Timer.PlayStates.Playing) { // wait for the first update and adjust leaderboard once the race times have been received From 62cad0588f26f9feb1a57fcc30cca4322f42dfe5 Mon Sep 17 00:00:00 2001 From: fmittag Date: Fri, 11 Jul 2014 15:33:52 +0200 Subject: [PATCH 051/323] implemented direct navigation to regatta from search result --- .../gwt/home/client/AbstractBasePlace.java | 92 +++++++++++++++++++ .../gwt/home/client/app/PlaceNavigator.java | 3 +- .../home/client/app/PlaceNavigatorImpl.java | 25 ++++- .../SmartphoneApplicationClientFactory.java | 5 +- ...letAndDesktopApplicationClientFactory.java | 7 +- .../client/place/event/EventActivity.java | 35 ++++--- .../place/event/EventClientFactory.java | 5 +- .../home/client/place/event/EventPlace.java | 22 +++-- .../event/TabletAndDesktopEventView.java | 39 +++++++- .../EventRegattaScheduleFleet.java | 6 +- .../EventRegattaScheduleSeries.java | 2 +- .../shared/recentevent/RecentEvent.java | 12 +-- .../shared/searchresult/SearchResultItem.java | 32 +++---- .../searchresult/SearchResultItem.ui.xml | 1 + .../client/shared/stage/StageTeaserBand.java | 2 +- 15 files changed, 226 insertions(+), 62 deletions(-) create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/AbstractBasePlace.java diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/AbstractBasePlace.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/AbstractBasePlace.java new file mode 100644 index 00000000000..5a650d9c322 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/AbstractBasePlace.java @@ -0,0 +1,92 @@ +package com.sap.sailing.gwt.home.client; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import com.google.gwt.place.shared.Place; + +/** + * An abstract Place which is able to manage and parse place parameters. + * @author Frank + * + */ +public abstract class AbstractBasePlace extends Place { + private static final Logger logger = Logger.getLogger(AbstractBasePlace.class.getName()); + private static final String VALIDATE_TOKENS_MESSAGE_PREFIX = "Missing token(s): "; + + private String placeParametersAsToken; + private Map params = new HashMap(); + + public AbstractBasePlace(String... paramKeysAndValues) { + if (paramKeysAndValues.length % 2 == 0) { + StringBuilder stringBuilder = new StringBuilder(); + + for (int i = 0; i < paramKeysAndValues.length; i++) { + String paramName = paramKeysAndValues[i++]; + String paramValue = paramKeysAndValues[i]; + // for now we don't add 'null' parameters + if(paramValue != null) { + params.put(paramName, paramValue); + if(stringBuilder.length() > 0) { + stringBuilder.append("&"); + } + stringBuilder.append(paramName + "=" + paramValue); + } + } + + placeParametersAsToken = stringBuilder.toString(); + } else { + logger.warning("Invalid number of arguments received! Must be key,value,key,value ... ignoring arguments"); + } + } + + public AbstractBasePlace(String url) { + if (url != null && !url.isEmpty()) { + this.placeParametersAsToken = url; + + List list = Arrays.asList(url.split("&")); + + if (list == null || list.size() < 1) { + logger.warning("Token empty, no-op"); + return; + } + + for (String listItem : list) { + List nvPair = Arrays.asList(listItem.split("=")); + if (nvPair == null || nvPair.size() != 2) { + logger.warning("Invalid parameters"); + continue; + } + params.put(nvPair.get(0), nvPair.get(1)); + } + } + } + + protected void validate(String... paramNames) { + StringBuffer message = new StringBuffer(VALIDATE_TOKENS_MESSAGE_PREFIX); + + for (String name : paramNames) { + if (!params.containsKey(name)) + message.append(name + " "); + } + + if (!message.toString().equals(VALIDATE_TOKENS_MESSAGE_PREFIX)) { + logger.warning(message.toString()); + } + } + + public String getParametersAsToken() { + return placeParametersAsToken; + } + + public String getParameter(String name) { + return params.get(name); + } + + public boolean hasParameter(String name) { + return params.containsKey(name); + } +} \ No newline at end of file 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 94d71ab819a..e0cff6053bb 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,7 +2,8 @@ package com.sap.sailing.gwt.home.client.app; public interface PlaceNavigator { void goToHome(); - void goToEvent(String eventUuidAsString); + void goToEvent(String eventUuidAsString, String baseUrl); + void goToRegattaOfEvent(String eventUuidAsString, String regattaIdAsNameString, String baseUrl); 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 82f444748ff..a3eefff3d6e 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 @@ -1,9 +1,11 @@ package com.sap.sailing.gwt.home.client.app; import com.google.gwt.place.shared.PlaceController; +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.Tokenizer; import com.sap.sailing.gwt.home.client.place.events.EventsPlace; import com.sap.sailing.gwt.home.client.place.searchresult.SearchResultPlace; import com.sap.sailing.gwt.home.client.place.solutions.SolutionsPlace; @@ -24,8 +26,27 @@ public class PlaceNavigatorImpl implements PlaceNavigator { } @Override - public void goToEvent(String eventUuidAsString) { - placeController.goTo(new EventPlace(eventUuidAsString)); + public void goToEvent(String eventUuidAsString, String baseUrl) { + if(baseUrl.contains("localhost") || baseUrl.contains("127.0.0.1")) { + placeController.goTo(new EventPlace(eventUuidAsString)); + } else { + EventPlace eventPlace = new EventPlace(eventUuidAsString); + EventPlace.Tokenizer t = new Tokenizer(); + String remoteEventUrl = baseUrl + "/gwt/Home.html#" + EventPlace.class.getSimpleName() + ":" + t.getToken(eventPlace); + Window.Location.replace(remoteEventUrl); + } + } + + @Override + public void goToRegattaOfEvent(String eventUuidAsString, String leaderboardIdAsNameString, String baseUrl) { + if(baseUrl.contains("localhost") || baseUrl.contains("127.0.0.1")) { + placeController.goTo(new EventPlace(eventUuidAsString, leaderboardIdAsNameString)); + } else { + EventPlace eventPlace = new EventPlace(eventUuidAsString); + EventPlace.Tokenizer t = new Tokenizer(); + String remoteEventUrl = baseUrl + "/gwt/Home.html#" + EventPlace.class.getSimpleName() + ":" + t.getToken(eventPlace); + Window.Location.replace(remoteEventUrl); + } } @Override 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 61b5b0c9d17..5c9624d1279 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 @@ -1,5 +1,7 @@ package com.sap.sailing.gwt.home.client.app; +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; @@ -14,6 +16,7 @@ import com.sap.sailing.gwt.home.client.place.sponsoring.SponsoringView; import com.sap.sailing.gwt.home.client.place.start.SmartphoneStartView; import com.sap.sailing.gwt.home.client.place.start.StartView; import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sailing.gwt.ui.shared.RaceGroupDTO; import com.sap.sse.gwt.client.player.Timer; @@ -31,7 +34,7 @@ public class SmartphoneApplicationClientFactory extends AbstractApplicationClien } @Override - public EventView createEventView(EventDTO event, Timer timerForClientServerOffset) { + public EventView createEventView(EventDTO event, List raceGroups, String leaderboardName, Timer timerForClientServerOffset) { return null; } 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 e828b2824ee..c58a34f6f62 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 @@ -1,5 +1,7 @@ package com.sap.sailing.gwt.home.client.app; +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; @@ -19,6 +21,7 @@ import com.sap.sailing.gwt.home.client.place.sponsoring.TabletAndDesktopSponsori import com.sap.sailing.gwt.home.client.place.start.StartView; import com.sap.sailing.gwt.home.client.place.start.TabletAndDesktopStartView; import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sailing.gwt.ui.shared.RaceGroupDTO; import com.sap.sse.gwt.client.player.Timer; @@ -36,8 +39,8 @@ public class TabletAndDesktopApplicationClientFactory extends AbstractApplicatio } @Override - public EventView createEventView(EventDTO event, Timer timerForClientServerOffset) { - return new TabletAndDesktopEventView(getSailingService(), event, timerForClientServerOffset); + public EventView createEventView(EventDTO event, List raceGroups, String leaderboardName, Timer timerForClientServerOffset) { + return new TabletAndDesktopEventView(getSailingService(), event, raceGroups, leaderboardName, timerForClientServerOffset); } @Override 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 8ae04d28f61..ede8ac05461 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 @@ -1,5 +1,6 @@ package com.sap.sailing.gwt.home.client.place.event; +import java.util.List; import java.util.UUID; import com.google.gwt.activity.shared.AbstractActivity; @@ -9,6 +10,7 @@ import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.AcceptsOneWidget; 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.sse.gwt.client.player.Timer; import com.sap.sse.gwt.client.player.Timer.PlayModes; @@ -31,24 +33,35 @@ public class EventActivity extends AbstractActivity { final long clientTimeWhenRequestWasSent = System.currentTimeMillis(); clientFactory.getSailingService().getEventById(UUID.fromString(eventPlace.getEventUuidAsString()), new AsyncCallback() { @Override - public void onSuccess(EventDTO event) { - for(LeaderboardGroupDTO leaderboardGroupDTO: event.getLeaderboardGroups()) { - final long clientTimeWhenResponseWasReceived = System.currentTimeMillis(); - if (leaderboardGroupDTO.getAverageDelayToLiveInMillis() != null) { - timerForClientServerOffset.setLivePlayDelayInMillis(leaderboardGroupDTO.getAverageDelayToLiveInMillis()); - } - timerForClientServerOffset.adjustClientServerOffset(clientTimeWhenRequestWasSent, leaderboardGroupDTO.getCurrentServerTime(), clientTimeWhenResponseWasReceived); - } + public void onSuccess(final EventDTO event) { - final EventView view = clientFactory.createEventView(event, timerForClientServerOffset); - panel.setWidget(view.asWidget()); + clientFactory.getSailingService().getRegattaStructureForEvent(event.id, new AsyncCallback>() { + @Override + public void onSuccess(List raceGroups) { + for(LeaderboardGroupDTO leaderboardGroupDTO: event.getLeaderboardGroups()) { + final long clientTimeWhenResponseWasReceived = System.currentTimeMillis(); + if (leaderboardGroupDTO.getAverageDelayToLiveInMillis() != null) { + timerForClientServerOffset.setLivePlayDelayInMillis(leaderboardGroupDTO.getAverageDelayToLiveInMillis()); + } + timerForClientServerOffset.adjustClientServerOffset(clientTimeWhenRequestWasSent, leaderboardGroupDTO.getCurrentServerTime(), clientTimeWhenResponseWasReceived); + } + + final EventView view = clientFactory.createEventView(event, raceGroups, eventPlace.getLeaderboardIdAsNameString(), timerForClientServerOffset); + panel.setWidget(view.asWidget()); + } + + @Override + public void onFailure(Throwable caught) { + Window.alert("Shit happens"); + } + }); } @Override public void onFailure(Throwable caught) { Window.alert("Shit happens"); } - }); + }); } } 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 daf77287169..7d07e0c4625 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 @@ -1,9 +1,12 @@ package com.sap.sailing.gwt.home.client.place.event; +import java.util.List; + import com.sap.sailing.gwt.ui.client.SailingClientFactory; import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sailing.gwt.ui.shared.RaceGroupDTO; import com.sap.sse.gwt.client.player.Timer; public interface EventClientFactory extends SailingClientFactory { - EventView createEventView(EventDTO event, Timer timerForClientServerOffset); + EventView createEventView(EventDTO event, List raceGroups, String leaderboardName, Timer timerForClientServerOffset); } 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 b62b27ad7af..12070c058ca 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 @@ -1,29 +1,39 @@ package com.sap.sailing.gwt.home.client.place.event; -import com.google.gwt.place.shared.Place; import com.google.gwt.place.shared.PlaceTokenizer; +import com.sap.sailing.gwt.home.client.AbstractBasePlace; -public class EventPlace extends Place { +public class EventPlace extends AbstractBasePlace { private final String eventUuidAsString; + private final String leaderboardIdAsNameString; public EventPlace(String eventUuidAsString) { - super(); + this(eventUuidAsString, null); + } + + public EventPlace(String eventUuidAsString, String leaderboardIdAsNameString) { + super("eventId", eventUuidAsString, "leaderboardName", leaderboardIdAsNameString); this.eventUuidAsString = eventUuidAsString; + this.leaderboardIdAsNameString = leaderboardIdAsNameString; } public String getEventUuidAsString() { return eventUuidAsString; } + public String getLeaderboardIdAsNameString() { + return leaderboardIdAsNameString; + } + public static class Tokenizer implements PlaceTokenizer { @Override public String getToken(EventPlace place) { - return place.getEventUuidAsString(); + return place.getParametersAsToken(); } @Override - public EventPlace getPlace(String eventUuidAsString) { - return new EventPlace(eventUuidAsString); + public EventPlace getPlace(String url) { + return new EventPlace(url); } } } 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 d8048133622..8d70714f0f7 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 @@ -26,6 +26,7 @@ import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.raceboard.RaceBoardViewConfiguration; 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.RegattaDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; import com.sap.sse.gwt.client.player.Timer; @@ -49,7 +50,8 @@ public class TabletAndDesktopEventView extends Composite implements EventView, E private final SailingServiceAsync sailingService; - public TabletAndDesktopEventView(SailingServiceAsync sailingService, EventDTO event, Timer timerForClientServerOffset) { + public TabletAndDesktopEventView(SailingServiceAsync sailingService, EventDTO event, List raceGroups, String leaderboardName, + Timer timerForClientServerOffset) { this.sailingService = sailingService; eventHeader = new EventHeader(event, this); eventRegattaList = new EventRegattaList(event, this); @@ -62,6 +64,7 @@ public class TabletAndDesktopEventView extends Composite implements EventView, E pageElements = Arrays.asList(new Widget[] { eventOverview, eventRegattaList, eventRegattaSchedule, eventMedia, eventSchedule }); + int leaderboardsCount = 0; int leaderboardsGroupCount = event.getLeaderboardGroups().size(); for(LeaderboardGroupDTO leaderboardGroup: event.getLeaderboardGroups()) { @@ -69,11 +72,37 @@ public class TabletAndDesktopEventView extends Composite implements EventView, E } if(leaderboardsGroupCount == 0 || leaderboardsCount == 0) { - Window.alert("There is no further data available for this event."); + Window.alert("There is no data available for this event."); } else { - if(leaderboardsCount == 1) { - LeaderboardGroupDTO firstLeaderboardGroup = event.getLeaderboardGroups().get(0); - goToRegattaRaces(firstLeaderboardGroup, firstLeaderboardGroup.getLeaderboards().get(0)); + + LeaderboardGroupDTO selectedLeaderboardGroup = null; + StrippedLeaderboardDTO selectedLeaderboard = null; + + // find the preselected leaderboard (if one exist) + if (leaderboardName != null) { + for (LeaderboardGroupDTO leaderboardGroup : event.getLeaderboardGroups()) { + for (StrippedLeaderboardDTO leaderboard : leaderboardGroup.getLeaderboards()) { + if (leaderboard.name.equals(leaderboardName)) { + selectedLeaderboardGroup = leaderboardGroup; + selectedLeaderboard = leaderboard; + break; + } + } + } + } + + // in case we only have one regatta/leaderboard we go directly to the 'races' page + if(selectedLeaderboard == null && leaderboardsCount == 1) { + for (LeaderboardGroupDTO leaderboardGroup : event.getLeaderboardGroups()) { + if(leaderboardGroup.getLeaderboards().size() > 0) { + selectedLeaderboard = leaderboardGroup.getLeaderboards().get(0); + selectedLeaderboardGroup = leaderboardGroup; + } + } + } + + if(selectedLeaderboard != null && selectedLeaderboardGroup != null) { + goToRegattaRaces(selectedLeaderboardGroup, selectedLeaderboard); } else { goToRegattas(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java index c15ab5ceed5..dace5004dc4 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java @@ -38,8 +38,10 @@ public class EventRegattaScheduleFleet extends Composite { EventRegattaScheduleResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); - fleetName.setInnerText(fleet.getName()); - fleetDiv.getStyle().setBackgroundColor(fleet.getColor().getAsHtml()); +// if(!fleet.getName().equals(LeaderboardNameConstants.DEFAULT_FLEET_NAME)) { +// fleetName.setInnerText(fleet.getName()); +// fleetDiv.getStyle().setBackgroundColor(fleet.getColor().getAsHtml()); +// } List racesOfFleet = getRacesOfFleet(leaderboard, series, fleet); for(RaceColumnDTO raceColumn: racesOfFleet) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.java index 3fe986290d4..ded215cb5f1 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.java @@ -28,7 +28,7 @@ public class EventRegattaScheduleSeries extends Composite { initWidget(uiBinder.createAndBindUi(this)); seriesName.setInnerText(series.getName()); - seriesDate.setInnerText("A Date of the series???"); + seriesDate.setInnerText("Some series data"); for(FleetDTO fleet: series.getFleets()) { EventRegattaScheduleFleet eventRegattaScheduleFleet = new EventRegattaScheduleFleet(leaderboard, series, fleet, timerForClientServerOffset, pageNavigator); 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 0ce4e48cb40..b621c4dc5ef 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 @@ -7,13 +7,10 @@ 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.app.PlaceNavigator; -import com.sap.sailing.gwt.home.client.place.event.EventPlace; -import com.sap.sailing.gwt.home.client.place.event.EventPlace.Tokenizer; import com.sap.sailing.gwt.home.client.shared.EventDatesFormatterUtil; import com.sap.sailing.gwt.ui.shared.EventBaseDTO; @@ -50,14 +47,7 @@ public class RecentEvent extends Composite { @UiHandler("eventOverviewLink") public void goToEventPlace(ClickEvent e) { - if(event.getBaseURL().contains("localhost") || event.getBaseURL().contains("127.0.0.1")) { - navigator.goToEvent(event.id.toString()); - } else { - EventPlace eventPlace = new EventPlace(event.id.toString()); - EventPlace.Tokenizer t = new Tokenizer(); - String remoteEventUrl = event.getBaseURL() + "/gwt/Home.html#" + EventPlace.class.getSimpleName() + ":" + t.getToken(eventPlace); - Window.Location.replace(remoteEventUrl); - } + navigator.goToEvent(event.id.toString(), event.getBaseURL()); } private void updateUI() { 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 0467cc2b906..e0709cba666 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 @@ -6,13 +6,10 @@ 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.app.PlaceNavigator; -import com.sap.sailing.gwt.home.client.place.event.EventPlace; -import com.sap.sailing.gwt.home.client.place.event.EventPlace.Tokenizer; import com.sap.sailing.gwt.home.client.shared.EventDatesFormatterUtil; import com.sap.sailing.gwt.ui.shared.EventBaseDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardSearchResultDTO; @@ -26,21 +23,28 @@ public class SearchResultItem extends Composite { @UiField Anchor regattaLink; @UiField SpanElement resultRegattaDetails; @UiField SpanElement resultEventDate; + @UiField SpanElement resultEventVenue; @UiField Anchor eventOverviewLink; - private final PlaceNavigator navigator; + private final PlaceNavigator placeNavigator; private LeaderboardSearchResultDTO searchResult; public SearchResultItem(PlaceNavigator navigator, LeaderboardSearchResultDTO searchResult) { - this.navigator = navigator; + this.placeNavigator = navigator; this.searchResult = searchResult; SearchResultResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); - regattaLink.setText(searchResult.getRegattaName()); - resultRegattaDetails.setInnerText("I have no idea yet what to show here..."); + String headlineLink = searchResult.getLeaderboardDisplayName(); + if(headlineLink == null) { + headlineLink = searchResult.getRegattaName() != null ? searchResult.getRegattaName() : searchResult.getLeaderboardName(); + } + + regattaLink.setText(headlineLink); +// resultRegattaDetails.setInnerText("I have no idea yet what to show here..."); eventOverviewLink.setText(searchResult.getEvent().getName()); + resultEventVenue.setInnerText(searchResult.getEvent().venue.getName()); if(searchResult.getEvent().startDate != null) { resultEventDate.setInnerText(EventDatesFormatterUtil.formatDateRangeWithYear(searchResult.getEvent().startDate, searchResult.getEvent().endDate)); } else { @@ -50,21 +54,13 @@ public class SearchResultItem extends Composite { @UiHandler("regattaLink") public void goToRegatta(ClickEvent e) { - Window.alert("Not implemented yet."); + EventBaseDTO event = searchResult.getEvent(); + placeNavigator.goToRegattaOfEvent(event.id.toString(), searchResult.getLeaderboardName(), searchResult.getBaseURL()); } @UiHandler("eventOverviewLink") public void goToEventPlace(ClickEvent e) { EventBaseDTO event = searchResult.getEvent(); - - if(searchResult.getBaseURL().contains("localhost") || searchResult.getBaseURL().contains("127.0.0.1")) { - navigator.goToEvent(event.id.toString()); - } else { - EventPlace eventPlace = new EventPlace(event.id.toString()); - EventPlace.Tokenizer t = new Tokenizer(); - String remoteEventUrl = searchResult.getBaseURL() + "/gwt/Home.html#" + EventPlace.class.getSimpleName() + ":" + t.getToken(eventPlace); - Window.Location.replace(remoteEventUrl); - } + placeNavigator.goToEvent(event.id.toString(), searchResult.getBaseURL()); } - } 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 b4b76e2e6e6..771fcfc84e0 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 @@ -13,6 +13,7 @@
+ | |
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 7b35376fecf..b535afb6a24 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 @@ -42,6 +42,6 @@ public class StageTeaserBand extends Composite { @UiHandler("actionLink") public void actionLinkClicked(ClickEvent e) { - placeNavigator.goToEvent(event.id.toString()); + placeNavigator.goToEvent(event.id.toString(), event.getBaseURL()); } } From 646c0bb0a27e6073a70a7948df0e226278be6159 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 11 Jul 2014 15:34:39 +0200 Subject: [PATCH 052/323] use Window.getClientWidth() to determine default leaderboard settings with lastN --- .../com.google.gwt.eclipse.core.prefs | 2 +- .../ui/leaderboard/LeaderboardEntryPoint.java | 24 ++++++------------- 2 files changed, 8 insertions(+), 18 deletions(-) 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 1c83ff05b6b..cddb7488fa8 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,4 @@ eclipse.preferences.version=1 entryPointModules= filesCopiedToWebInfLib=gwt-servlet.jar -gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDMgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0UgLWNvbXBpbGVSZXBvcnQgLVhzb3ljRGV0YWlsZWRdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5BZG1pbkNvbnNvbGU8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuUmFjZUJvYXJkPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLkxlYWRlcmJvYXJkPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLllvdXR1YmVQb3B1cDwvZW50cnktcG9pbnQtbW9kdWxlPjwvZ3d0LWNvbXBpbGUtc2V0dGluZ3M+ +gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDMgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0UgLWNvbXBpbGVSZXBvcnQgLVhzb3ljRGV0YWlsZWRdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5BZG1pbkNvbnNvbGU8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuUmFjZUJvYXJkPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLkxlYWRlcmJvYXJkPC9lbnRyeS1wb2ludC1tb2R1bGU+PC9nd3QtY29tcGlsZS1zZXR0aW5ncz4\= diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java index b589fc99306..48979e67e51 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java @@ -155,18 +155,7 @@ public class LeaderboardEntryPoint extends AbstractEntryPoint { } mainPanel.addNorth(logoAndTitlePanel, 68); } - ScrollPanel contentScrollPanel = new ScrollPanel() { - @Override - public void onResize() { - super.onResize(); - if (isSmallWidth()) { - final AbstractLeaderboardViewer leaderboardViewer = (AbstractLeaderboardViewer) getWidget(); - final LeaderboardPanel leaderboardPanel = leaderboardViewer.getLeaderboardPanel(); - int width = leaderboardPanel.getElement().getClientWidth(); - leaderboardPanel.setRaceColumnSelectionToLastNStrategy((width-500)/50); - } - } - }; + ScrollPanel contentScrollPanel = new ScrollPanel(); 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 @@ -221,15 +210,16 @@ public class LeaderboardEntryPoint extends AbstractEntryPoint { Long refreshIntervalMillis = parameterMap.containsKey(PARAM_REFRESH_INTERVAL_MILLIS) ? Long .valueOf(parameterMap.get(PARAM_REFRESH_INTERVAL_MILLIS).get(0)) : null; RaceColumnSelectionStrategies raceColumnSelectionStrategy; - if (parameterMap.containsKey(PARAM_NAME_LAST_N)) { - raceColumnSelectionStrategy = RaceColumnSelectionStrategies.LAST_N; - } else { - raceColumnSelectionStrategy = RaceColumnSelectionStrategies.EXPLICIT; - } final Integer numberOfLastRacesToShow; if (parameterMap.containsKey(PARAM_NAME_LAST_N)) { + raceColumnSelectionStrategy = RaceColumnSelectionStrategies.LAST_N; numberOfLastRacesToShow = Integer.valueOf(parameterMap.get(PARAM_NAME_LAST_N).get(0)); + } else if (isSmallWidth()) { + raceColumnSelectionStrategy = RaceColumnSelectionStrategies.LAST_N; + int width = Window.getClientWidth(); + numberOfLastRacesToShow = (width-275)/35; } else { + raceColumnSelectionStrategy = RaceColumnSelectionStrategies.EXPLICIT; numberOfLastRacesToShow = null; } if (parameterMap.containsKey(PARAM_RACE_NAME) || parameterMap.containsKey(PARAM_RACE_DETAIL) || From f800d014d66df728ff738157642dacf020559e3e Mon Sep 17 00:00:00 2001 From: fmittag Date: Fri, 11 Jul 2014 15:45:45 +0200 Subject: [PATCH 053/323] removed solutions link from header (we have no content so far) --- .../gwt/home/client/shared/header/Header.java | 15 +++++++-------- .../gwt/home/client/shared/header/Header.ui.xml | 5 +++-- 2 files changed, 10 insertions(+), 10 deletions(-) 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 36600d638b7..9da82dc1fb3 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 @@ -23,13 +23,12 @@ public class Header extends Composite { @UiField Anchor startPageLink; @UiField Anchor eventsPageLink; @UiField Anchor solutionsPageLink; - @UiField Anchor sponsoringPageLink; +// @UiField Anchor sponsoringPageLink; @UiField TextBox searchText; @UiField Button searchButton; - - private final List links; + private final List links; private final PlaceNavigator navigator; interface HeaderUiBinder extends UiBinder { @@ -41,7 +40,7 @@ public class Header extends Composite { this.navigator = navigator; HeaderResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); - links = Arrays.asList(new Anchor[] { startPageLink, eventsPageLink, solutionsPageLink, sponsoringPageLink }); + links = Arrays.asList(new Anchor[] { startPageLink, eventsPageLink, solutionsPageLink }); searchText.getElement().setAttribute("placeholder", "Search SAPSailing.com"); searchText.addKeyPressHandler(new KeyPressHandler() { @@ -70,10 +69,10 @@ public class Header extends Composite { navigator.goToSolutions(); } - @UiHandler("sponsoringPageLink") - public void goToSponsoring(ClickEvent e) { - navigator.goToSponsoring(); - } +// @UiHandler("sponsoringPageLink") +// public void goToSponsoring(ClickEvent e) { +// navigator.goToSponsoring(); +// } @UiHandler("searchButton") void searchButtonClick(ClickEvent event) { 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 c29ce3a17d1..1204cd6f979 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 @@ -19,9 +19,10 @@ styleName="{local_res.css.sitenavigation_link}" text="{i18n.events}" href="javascript:;"/> +
From 4508ab527fcb41da9685cb3125ea62ce2ef9ca69 Mon Sep 17 00:00:00 2001 From: fmittag Date: Fri, 11 Jul 2014 17:02:01 +0200 Subject: [PATCH 054/323] first try to manage that the main navigation always jumps to www.sapsailing.com even when you are on different event server. --- .../home/client/app/PlaceNavigatorImpl.java | 56 +++++++++++++------ 1 file changed, 38 insertions(+), 18 deletions(-) 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 a3eefff3d6e..fa589792ca5 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 @@ -1,11 +1,12 @@ package com.sap.sailing.gwt.home.client.app; +import com.google.gwt.place.shared.Place; import com.google.gwt.place.shared.PlaceController; +import com.google.gwt.place.shared.PlaceTokenizer; 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.Tokenizer; import com.sap.sailing.gwt.home.client.place.events.EventsPlace; import com.sap.sailing.gwt.home.client.place.searchresult.SearchResultPlace; import com.sap.sailing.gwt.home.client.place.solutions.SolutionsPlace; @@ -14,6 +15,7 @@ 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"; protected PlaceNavigatorImpl(PlaceController placeController) { super(); @@ -22,61 +24,79 @@ public class PlaceNavigatorImpl implements PlaceNavigator { @Override public void goToHome() { - placeController.goTo(new StartPlace()); + gotoPlace(new StartPlace(), new StartPlace.Tokenizer()); } @Override public void goToEvent(String eventUuidAsString, String baseUrl) { - if(baseUrl.contains("localhost") || baseUrl.contains("127.0.0.1")) { - placeController.goTo(new EventPlace(eventUuidAsString)); + EventPlace eventPlace = new EventPlace(eventUuidAsString); + if(isLocationOnLocalhost() || baseUrl.contains(SAPSAILING_DOMAIN)) { + placeController.goTo(eventPlace); } else { - EventPlace eventPlace = new EventPlace(eventUuidAsString); - EventPlace.Tokenizer t = new Tokenizer(); - String remoteEventUrl = baseUrl + "/gwt/Home.html#" + EventPlace.class.getSimpleName() + ":" + t.getToken(eventPlace); + String remoteEventUrl = buildRemotePlaceUrl(baseUrl, eventPlace, new EventPlace.Tokenizer()); Window.Location.replace(remoteEventUrl); } } @Override public void goToRegattaOfEvent(String eventUuidAsString, String leaderboardIdAsNameString, String baseUrl) { - if(baseUrl.contains("localhost") || baseUrl.contains("127.0.0.1")) { - placeController.goTo(new EventPlace(eventUuidAsString, leaderboardIdAsNameString)); + EventPlace eventPlace = new EventPlace(eventUuidAsString, leaderboardIdAsNameString); + if(isLocationOnLocalhost() || baseUrl.contains(SAPSAILING_DOMAIN)) { + placeController.goTo(eventPlace); } else { - EventPlace eventPlace = new EventPlace(eventUuidAsString); - EventPlace.Tokenizer t = new Tokenizer(); - String remoteEventUrl = baseUrl + "/gwt/Home.html#" + EventPlace.class.getSimpleName() + ":" + t.getToken(eventPlace); + String remoteEventUrl = buildRemotePlaceUrl(baseUrl, eventPlace, new EventPlace.Tokenizer()); Window.Location.replace(remoteEventUrl); } } @Override public void goToSearchResult(String searchQuery) { - placeController.goTo(new SearchResultPlace(searchQuery)); + gotoPlace(new SearchResultPlace(searchQuery), new SearchResultPlace.Tokenizer()); } @Override public void goToEvents() { - placeController.goTo(new EventsPlace()); + gotoPlace(new EventsPlace(), new EventsPlace.Tokenizer()); } @Override public void goToAboutUs() { - placeController.goTo(new AboutUsPlace()); + gotoPlace(new AboutUsPlace(), new AboutUsPlace.Tokenizer()); } @Override public void goToContact() { - placeController.goTo(new ContactPlace()); + gotoPlace(new ContactPlace(), new ContactPlace.Tokenizer()); } @Override public void goToSolutions() { - placeController.goTo(new SolutionsPlace()); + gotoPlace(new SolutionsPlace(), new SolutionsPlace.Tokenizer()); } @Override public void goToSponsoring() { - placeController.goTo(new SponsoringPlace()); + gotoPlace(new SponsoringPlace(), new SponsoringPlace.Tokenizer()); } + private void gotoPlace(T destinationPlace, PlaceTokenizer tokenizer) { + if(isLocationOnLocalhost() || isLocationOnSapSailingCom()) { + placeController.goTo(destinationPlace); + } else { + String homeUrl = buildRemotePlaceUrl("http://" + SAPSAILING_DOMAIN, destinationPlace, tokenizer); + Window.Location.replace(homeUrl); + } + } + + private String buildRemotePlaceUrl(String baseUrl, T destinationPlace, PlaceTokenizer tokenizer) { + return baseUrl + "/gwt/Home.html#" + destinationPlace.getClass().getSimpleName() + ":" + tokenizer.getToken(destinationPlace); + } + + private boolean isLocationOnSapSailingCom() { + return Window.Location.getHostName().contains(SAPSAILING_DOMAIN); + } + + private boolean isLocationOnLocalhost() { + return Window.Location.getHostName().contains("localhost") || Window.Location.getHostName().contains("127.0.0.1"); + } } From d41fd00de4cc572dc4f830fbb354f6c5fe1e6d93 Mon Sep 17 00:00:00 2001 From: "Christopher Ronnewinkel (D036654)" Date: Fri, 11 Jul 2014 20:54:49 +0200 Subject: [PATCH 055/323] fix long-delay of streamlet-initialization; --- .../racemap/WindStreamletsRaceboardOverlay.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) 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 537de8e5bee..18a4764eca5 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 @@ -106,6 +106,11 @@ public class WindStreamletsRaceboardOverlay extends FullCanvasOverlay { } private void scheduleWindDataRefresh() { + // initial update + updateWindSourcesToObserve(); + updateWindField(); + + // schedule delayed repeated updates scheduler.scheduleFixedPeriod(new RepeatingCommand() { @Override public boolean execute() { @@ -146,8 +151,12 @@ public class WindStreamletsRaceboardOverlay extends FullCanvasOverlay { windInfoForRace.raceIsKnownToStartUpwind = result.raceIsKnownToStartUpwind; windInfoForRace.windSourcesToExclude = result.windSourcesToExclude; for (Entry e : result.windTrackInfoByWindSource.entrySet()) { - if (!windInfoForRace.windTrackInfoByWindSource.containsKey(e.getKey()) && - !Util.contains(result.windSourcesToExclude, e.getKey()) && e.getKey().getType() != WindSourceType.COMBINED) { + // generally exclude combined wind, course-based wind and racecommittee + if (!windInfoForRace.windTrackInfoByWindSource.containsKey(e.getKey()) + && !Util.contains(result.windSourcesToExclude, e.getKey()) + && e.getKey().getType() != WindSourceType.COMBINED + && e.getKey().getType() != WindSourceType.COURSE_BASED + && e.getKey().getType() != WindSourceType.RACECOMMITTEE) { windInfoForRace.windTrackInfoByWindSource.put(e.getKey(), e.getValue()); } } From 70f5fa50f7af1fc8cf3ea66640da85beb036b06d Mon Sep 17 00:00:00 2001 From: fmittag Date: Sat, 12 Jul 2014 19:28:06 +0200 Subject: [PATCH 056/323] implemented expand/collapse on new events page with accordion per year --- .../images/home/arrow-down-yellow.png | Bin 0 -> 233 bytes .../gwt/home/client/i18n/TextMessages.java | 51 ++++++-- .../home/client/i18n/TextMessages.properties | 59 +++++++-- .../client/i18n/TextMessages_de.properties | 59 +++++++-- .../regattaheader/EventRegattaHeader.ui.xml | 2 +- .../regattalist/EventRegattaListItem.ui.xml | 2 +- .../events/recent/EventsOverviewRecent.css | 116 ++++++++++++++++++ .../events/recent/EventsOverviewRecent.java | 32 +++-- .../events/recent/EventsOverviewRecent.ui.xml | 7 +- .../recent/EventsOverviewRecentResources.java | 19 +++ .../recent/EventsOverviewRecentYear.java | 84 +++++++++++++ .../recent/EventsOverviewRecentYear.ui.xml | 57 +++++++++ 12 files changed, 436 insertions(+), 52 deletions(-) create mode 100644 java/com.sap.sailing.gwt.ui/images/home/arrow-down-yellow.png create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/recent/EventsOverviewRecentYear.java create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/recent/EventsOverviewRecentYear.ui.xml diff --git a/java/com.sap.sailing.gwt.ui/images/home/arrow-down-yellow.png b/java/com.sap.sailing.gwt.ui/images/home/arrow-down-yellow.png new file mode 100644 index 0000000000000000000000000000000000000000..c2db256075b04ec8a4afbc26b705b6136fcd5a3e GIT binary patch literal 233 zcmeAS@N?(olHy`uVBq!ia0vp^d_c^}!3HFmd}T9%lw^r(L`iUdT1k0gQ7VIDN`6wR zf@f}GdTLN=VoGJ<$y6JlqHa$Y$B>F!Nq;uXVq{BdtdJD=pL&3AMxzB+hj_DvPryM= zhdmq};SQn+m$xvjN@Ma_taQapfaBmI$z_@>CsVr=dcQZ`;O)4dc(G^QOWBaOwQef* zQp_Lq6vUc7U00B7S@Tk-qj`@>EVSreGq+0HGplwPftBX!X<`6>&Oj>RLMWiuIP es|hf07%;F(tInUu*RdApECx?kKbLh*2~7ZItxWj< literal 0 HcmV?d00001 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 bc5b127d2a3..8591adb04ed 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 @@ -20,6 +20,8 @@ public interface TextMessages extends Messages { String countdownMinutes(); String countdownSeconds(); String live(); + String finished(); + String watchNow(); String lastUpdate(); String leaderBoard(); String boxNewsUpdateMoreLink(); @@ -39,9 +41,19 @@ public interface TextMessages extends Messages { String eventHeaderOverview(); String eventHeaderRaces(); String eventHeaderRegattas(); - String eventHeaderSchedule(); + String seriesHeaderOverview(); + String seriesHeaderUpcoming(); + String seriesHeaderRecent(); + String seriesHeaderLeaderboard(); + String seriesOverviewHeaderRecent(); + String seriesOverviewReadMore(); + String eventsOverviewRecentEvents(); + String eventsOverviewRecentCountries(); + String eventsOverviewRecentSailors(); + String eventsOverviewRecentTrackedRaces(); String eventResultsHeadline(); String eventResultsBoatClassesDropDown(); + String eventOverviewMoreEvents(); String eventOverviewRecentEvents(); String eventOverviewUpcomingDate(); String eventOverviewUpcomingEvent(); @@ -51,7 +63,8 @@ public interface TextMessages extends Messages { String eventOverviewUpcomingTeaser(); String eventQuickfinderTitle(); String eventRegattaHeaderLeaderboard(); - String eventRegattaHeaderRaceSchedule(); + String eventRegattaHeaderRaceRaces(); + String eventRegattaListAllBoatClasses(); String eventRegattaLeaderboardCompetitor(); String eventRegattaLeaderboardExtendLeaderboard(); String eventRegattaLeaderboardFinishedMessage(); @@ -59,11 +72,22 @@ public interface TextMessages extends Messages { String eventRegattaLeaderboardName(); String eventRegattaLeaderboardRegattaRank(); String eventRegattaLeaderboardTotalPoints(); + String eventRegattaRaceWatch(); + String eventRegattaRaceAnalyse(); + String eventRegattaRaceNotTracked(); + String eventRegattaRaceFinished(); + String eventRegattaRaceStarted(); + String eventRegattaRaceScheduled(); + String eventRegattaRacesLegendGps(); + String eventRegattaRacesLegendWind(); + String eventRegattaRacesLegendVideo(); + String eventRegattaRacesLegendAudio(); String eventSponsorsTitle(); String footerCopyright(); String footerLanguage(); String footerLegal(); String footerPrivacy(); + String footerFeedback(); String headerEvents(); String headerHome(); String headerLogo(); @@ -80,7 +104,6 @@ public interface TextMessages extends Messages { String leaderboardQuickAccessLabel(); String mainEventShowAll(); String mainEventTitle(); - String mainMediaShowMore(); String mainMediaSubtitle(); String mainMediaTitle(); String mainMediaVideosSubtitle(); @@ -88,12 +111,16 @@ public interface TextMessages extends Messages { String mainSponsorsReadMore(); String mediaTeaserPhotosTitle(); String mediaTeaserVideosTitle(); - String regattaIntroContentCompetitors(); - String regattaIntroContentCourse(); - String regattaIntroContentRaces(); - String regattaIntroContentScoring(); - String regattaIntroContentMethod(); - String regattaIntroContentWindAvg(); + String regattaCompetitors(); + String regattaRaces(); + String regattaTracked(); + String regattaRegattaStarts(); + String regattaPts(); + String regattaCourse(); + String regattaArea(); + String regattaFinishedAt(); + String regattaScheduledAt(); + String regattaStartedAt(); String regattasResultsTitle(); String searchResultHeaderButton(); String searchResultHeaderLabel(); @@ -105,20 +132,26 @@ public interface TextMessages extends Messages { String searchResultSortNewest(); String searchResultSortOldest(); String searchResultSortRelevance(); + String searchResultCount(); String sharingEmail(); String sharingFacebook(); String sharingTwitter(); String socialFooterShare(); String socialFooterTitle(); + String solutionsSAPNav(); String solutionsSAPHeadline(); String solutionsSAPText1(); String solutionsSAPText2(); + String solutionsRaceNav(); String solutionsRaceHeadline(); String solutionsRaceText(); + String solutionsPostNav(); String solutionsPostHeadline(); String solutionsPostText(); + String solutionsTrainingNav(); String solutionsTrainingHeadline(); String solutionsTrainingText(); + String solutionsSimulatorNav(); String solutionsSimulatorHeadline(); String solutionsSimulatorText(); } 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 24f5514f74a..b8edfb0f7a4 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 @@ -15,6 +15,8 @@ countdownHours=Hours countdownMinutes=Mins countdownSeconds=Secs live=Live +finished=Finished +watchNow=Watch now lastUpdate=Last Update leaderBoard=LeaderBoard boxNewsUpdateMoreLink=Report news @@ -32,11 +34,21 @@ boxWeatherUnit=KTS defaultTitle=SAP Sailing Analytics eventHeaderMedia=Media eventHeaderOverview=Overview -eventHeaderRaces=Races +eventHeaderRaces=Schedule eventHeaderRegattas=Regattas -eventHeaderSchedule=Schedule +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 @@ -46,7 +58,8 @@ eventOverviewUpcomingSeries=Series eventOverviewUpcomingTeaser=More Events To Be Announced Soon! eventQuickfinderTitle=Event Quick-Finder eventRegattaHeaderLeaderboard=Leaderboard -eventRegattaHeaderRaceSchedule=Race Schedule +eventRegattaHeaderRaceRaces=Races +eventRegattaListAllBoatClasses=All boat classes eventRegattaLeaderboardCompetitor=Competitor eventRegattaLeaderboardExtendLeaderboard=Extend Leaderboard eventRegattaLeaderboardFinishedMessage=No more races @@ -54,16 +67,27 @@ eventRegattaLeaderboardLastScoreUpdate=Last Score Update 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: +eventRegattaRacesLegendGps=Tracking +eventRegattaRacesLegendWind=Wind Data +eventRegattaRacesLegendVideo=Video Stream +eventRegattaRacesLegendAudio=Audio Stream eventSponsorsTitle=Event Sponsorships -footerCopyright=\u00A9 2014 SAP Sailing Analytics +footerCopyright=© 2014 SAP Sailing Analytics footerLanguage=SAP Sailing offered in footerLegal=Legal Disclosure footerPrivacy=Privacy +footerFeedback=Feedback headerEvents=Events headerHome=Home headerLogo=SAP Sailing Analytics headerSearch=Search -headerSearchPlaceholder=Search SAPSailing.com +headerSearchPlaceholder=Search headerSolutions=Solutions headerSponsoring=Sponsoring latestRaceResultsHeadline=Latest Official Race Results @@ -75,7 +99,6 @@ latestRaceResultsRefresh=refresh leaderboardQuickAccessLabel=Leaderboard Quick Access mainEventShowAll=Show All Events mainEventTitle=Latest Events from the Sailing World -mainMediaShowMore=Show More 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. @@ -83,12 +106,16 @@ mainMediaVideosTitle=Recent Video Entries mainSponsorsReadMore=Read More mediaTeaserPhotosTitle=Photos mediaTeaserVideosTitle=Videos -regattaIntroContentCompetitors=Competitors -regattaIntroContentCourse=Course -regattaIntroContentRaces=Planned Races -regattaIntroContentScoring=Scoring -regattaIntroContentMethod=Method -regattaIntroContentWindAvg=Wind Average +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 @@ -100,19 +127,25 @@ searchResultSortLabel=Sort by 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 +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. +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. +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. 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 f0f4b3e86b2..c040ddcb38e 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 @@ -15,6 +15,8 @@ countdownHours=Hours countdownMinutes=Mins countdownSeconds=Secs live=Live +finished=Finished +watchNow=Watch now lastUpdate=Last Update leaderBoard=LeaderBoard boxNewsUpdateMoreLink=Report news @@ -32,11 +34,21 @@ boxWeatherUnit=KTS defaultTitle=SAP Sailing Analytics eventHeaderMedia=Media eventHeaderOverview=Overview -eventHeaderRaces=Races +eventHeaderRaces=Schedule eventHeaderRegattas=Regattas -eventHeaderSchedule=Schedule +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 @@ -46,7 +58,8 @@ eventOverviewUpcomingSeries=Series eventOverviewUpcomingTeaser=More Events To Be Announced Soon! eventQuickfinderTitle=Event Quick-Finder eventRegattaHeaderLeaderboard=Leaderboard -eventRegattaHeaderRaceSchedule=Race Schedule +eventRegattaHeaderRaceRaces=Races +eventRegattaListAllBoatClasses=All boat classes eventRegattaLeaderboardCompetitor=Competitor eventRegattaLeaderboardExtendLeaderboard=Extend Leaderboard eventRegattaLeaderboardFinishedMessage=No more races @@ -54,16 +67,27 @@ eventRegattaLeaderboardLastScoreUpdate=Last Score Update 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: +eventRegattaRacesLegendGps=Tracking +eventRegattaRacesLegendWind=Wind Data +eventRegattaRacesLegendVideo=Video Stream +eventRegattaRacesLegendAudio=Audio Stream eventSponsorsTitle=Event Sponsorships -footerCopyright=\u00A9 2014 SAP Sailing Analytics +footerCopyright=© 2014 SAP Sailing Analytics footerLanguage=SAP Sailing offered in footerLegal=Legal Disclosure footerPrivacy=Privacy +footerFeedback=Feedback headerEvents=Events headerHome=Home headerLogo=SAP Sailing Analytics headerSearch=Search -headerSearchPlaceholder=Search SAPSailing.com +headerSearchPlaceholder=Search headerSolutions=Solutions headerSponsoring=Sponsoring latestRaceResultsHeadline=Latest Official Race Results @@ -75,7 +99,6 @@ latestRaceResultsRefresh=refresh leaderboardQuickAccessLabel=Leaderboard Quick Access mainEventShowAll=Show All Events mainEventTitle=Latest Events from the Sailing World -mainMediaShowMore=Show More 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. @@ -83,12 +106,16 @@ mainMediaVideosTitle=Recent Video Entries mainSponsorsReadMore=Read More mediaTeaserPhotosTitle=Photos mediaTeaserVideosTitle=Videos -regattaIntroContentCompetitors=Competitors -regattaIntroContentCourse=Course -regattaIntroContentRaces=Planned Races -regattaIntroContentScoring=Scoring -regattaIntroContentMethod=Method -regattaIntroContentWindAvg=Wind Average +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 @@ -100,19 +127,25 @@ searchResultSortLabel=Sort by 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 +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. +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. +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. diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.ui.xml index 80b13e3946d..e4c3faa82ad 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaheader/EventRegattaHeader.ui.xml @@ -35,7 +35,7 @@
-
 
+
 
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.ui.xml index a3cc1e10b72..bd4bfc84330 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.ui.xml @@ -31,7 +31,7 @@
-
 
+
 
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 e69de29bb2d..d7258c42961 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 @@ -0,0 +1,116 @@ +.eventteasercontainer { + font-size: 15px; + font-size: 1rem; + text-align: center; + width: 100%; +} +.eventteaser { + font-size: 15px; + font-size: 1rem; + display: inline-block; + margin-bottom: 3em; + text-align: left; + max-width: 100%; +} +.eventteaser:hover { + text-decoration: none; +} +.eventteaser:hover .eventteaser_name { + text-decoration: underline; +} +.eventteaser:hover .eventteaser_image { + opacity: 0.9; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=90)"; + filter: literal("alpha(opacity=90)"); +} +.eventteaser_image { + position: relative; + display: inline-block; + max-width: 100%; +} +.eventteaser_series { + position: absolute; + bottom: 0; + right: 0; +} +.eventteaser_name { + font-size: 1.466666666666667em; + font-weight: bold; + color: #008fcc; + margin-top: 0.333333333333333em; +} +.eventteaser_location { + color: #222; +} +.eventsoverviewnavigation { + font-size: 15px; + font-size: 1rem; +} +.eventsoverviewnavigation .gridalternator { + overflow: visible; +} +.eventsoverviewrecent { + font-size: 15px; + font-size: 1rem; + padding-top: 2em; +} +.eventsoverviewrecent_year { + border-bottom: 1px solid #ccc; + transiton: opacity 0.1s; +} +.eventsoverviewrecent_year:nth-child\(even\) { + background: #f8f8f8; +} +.eventsoverviewrecent_year:nth-child\(even\) .eventsoverviewrecent_year_header:hover, +.eventsoverviewrecent_year:nth-child\(even\) .eventsoverviewrecent_year_header:focus { + background-color: #f3f3f3; +} +.eventsoverviewrecent_yearcollapsed .eventsoverviewrecent_year_header_info_arrow_image { + -webkit-transform: rotate(0deg); + -moz-transform: rotate(0deg); + -o-transform: rotate(0deg); + -ms-transform: rotate(0deg); + transform: rotate(0deg); +} +.eventsoverviewrecent_year_header { + padding: 0.666666666666667em; + cursor: pointer; +} +.eventsoverviewrecent_year_header:hover, +.eventsoverviewrecent_year_header:focus { + background-color: #fcfcfc; +} +.eventsoverviewrecent_year_header:hover .eventsoverviewrecent_year_header_title, +.eventsoverviewrecent_year_header:focus .eventsoverviewrecent_year_header_title { + text-decoration: underline; +} +.eventsoverviewrecent_year_header_title { + margin-bottom: 0; +} +.eventsoverviewrecent_year_header_info { + float: right; +} +.eventsoverviewrecent_year_header_info_arrow { + padding: 0.466666666666667em 1.133333333333333em; +} +.eventsoverviewrecent_year_header_info_arrow_image { + -webkit-transform: rotate(180deg); + -moz-transform: rotate(180deg); + -o-transform: rotate(180deg); + -ms-transform: rotate(180deg); + transform: rotate(180deg); + transiton: transform 0.4s; +} +.eventsoverviewrecent_year_header_info_item { + border-right: 1px solid #ccc; + padding: 0.466666666666667em 1.133333333333333em; +} +.eventsoverviewrecent_year_header_info_item_value { + font-weight: 700; + color: #008fcc; + margin-right: 0.533333333333333em; +} +.eventsoverviewrecent_year_content { + margin-top: 0.666666666666667em; + padding-bottom: 1.333333333333333em; +} 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 d8ee4613ea9..9a515d8713b 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 @@ -13,7 +13,6 @@ 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.home.client.shared.recentevent.RecentEvents; import com.sap.sailing.gwt.ui.shared.EventBaseDTO; public class EventsOverviewRecent extends Composite { @@ -25,13 +24,13 @@ public class EventsOverviewRecent extends Composite { private final PlaceNavigator navigator; - @UiField HTMLPanel htmlPanel; + @UiField HTMLPanel recentEventsPerYearPanel; - private final Map recentEventsComposites; + private final Map recentEventsComposites; public EventsOverviewRecent(PlaceNavigator navigator) { this.navigator = navigator; - recentEventsComposites = new HashMap(); + recentEventsComposites = new HashMap(); EventsOverviewRecentResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); @@ -39,26 +38,33 @@ public class EventsOverviewRecent extends Composite { public void updateEvents(Map> recentEventsOrderedByYear) { // remove old widgets - htmlPanel.clear(); + recentEventsPerYearPanel.clear(); recentEventsComposites.clear(); // recent events of this year Date now = new Date(); int currentYear = DateUtil.getYear(now); + boolean oneYearIsExpanded = false; if(recentEventsOrderedByYear.get(currentYear) != null) { - RecentEvents recentEvents = new RecentEvents(navigator); - recentEvents.setEvents(recentEventsOrderedByYear.get(currentYear), null); - htmlPanel.add(recentEvents); - recentEventsComposites.put(currentYear, recentEvents); + EventsOverviewRecentYear recentEventsOfOneYear = new EventsOverviewRecentYear(currentYear, recentEventsOrderedByYear.get(currentYear), navigator); + recentEventsPerYearPanel.add(recentEventsOfOneYear); + recentEventsComposites.put(currentYear, recentEventsOfOneYear); + recentEventsOfOneYear.showContent(); + oneYearIsExpanded = true; } currentYear--; while (currentYear > 2010) { if(recentEventsOrderedByYear.get(currentYear) != null) { - RecentEvents recentEvents = new RecentEvents(navigator); - recentEvents.setEvents(recentEventsOrderedByYear.get(currentYear), String.valueOf(currentYear)); - htmlPanel.add(recentEvents); - recentEventsComposites.put(currentYear, recentEvents); + 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--; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/recent/EventsOverviewRecent.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/recent/EventsOverviewRecent.ui.xml index 4a47f2e0a93..e700ec4e1b0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/recent/EventsOverviewRecent.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/recent/EventsOverviewRecent.ui.xml @@ -5,7 +5,10 @@ - - + + +
+ +
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/recent/EventsOverviewRecentResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/recent/EventsOverviewRecentResources.java index b01aa9a5e54..7886298d3e7 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/recent/EventsOverviewRecentResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/recent/EventsOverviewRecentResources.java @@ -11,5 +11,24 @@ public interface EventsOverviewRecentResources extends ClientBundle { LocalCss css(); public interface LocalCss extends CssResource { + String eventteasercontainer(); + String eventteaser(); + String eventteaser_name(); + String eventteaser_image(); + String eventteaser_series(); + String eventteaser_location(); + String eventsoverviewnavigation(); + String gridalternator(); + String eventsoverviewrecent(); + String eventsoverviewrecent_year(); + String eventsoverviewrecent_year_header(); + String eventsoverviewrecent_yearcollapsed(); + String eventsoverviewrecent_year_header_info_arrow_image(); + String eventsoverviewrecent_year_header_title(); + String eventsoverviewrecent_year_header_info(); + String eventsoverviewrecent_year_header_info_arrow(); + String eventsoverviewrecent_year_header_info_item(); + String eventsoverviewrecent_year_header_info_item_value(); + String eventsoverviewrecent_year_content(); } } 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 new file mode 100644 index 00000000000..d7d1117ac49 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/recent/EventsOverviewRecentYear.java @@ -0,0 +1,84 @@ +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.SpanElement; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.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.shared.recentevent.RecentEvent; +import com.sap.sailing.gwt.ui.shared.EventBaseDTO; + +public class EventsOverviewRecentYear extends Composite { + + interface EventsOverviewUiBinder extends UiBinder { + } + + private static EventsOverviewUiBinder uiBinder = GWT.create(EventsOverviewUiBinder.class); + + @UiField SpanElement year; + @UiField SpanElement eventsCount; + @UiField SpanElement countriesCount; + @UiField SpanElement sailorsCount; + @UiField SpanElement trackedRacesCount; + @UiField HTMLPanel recentEventsTeaserPanel; + @UiField HTMLPanel contentDiv; + @UiField HTMLPanel headerDiv; + + private boolean isContentVisible; + + 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); + } + isContentVisible = true; + + headerDiv.addDomHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + onHeaderCicked(); + } + }, ClickEvent.getType()); + } + + private void onHeaderCicked() { + isContentVisible = !isContentVisible; + updateContentVisibility(); + } + + public void hideContent() { + isContentVisible = false; + updateContentVisibility(); + } + + public void showContent() { + isContentVisible = true; + updateContentVisibility(); + } + + private void updateContentVisibility() { + contentDiv.setVisible(isContentVisible); + if(isContentVisible) { + headerDiv.getElement().removeClassName(EventsOverviewRecentResources.INSTANCE.css().eventsoverviewrecent_yearcollapsed()); + } else { + headerDiv.getElement().addClassName(EventsOverviewRecentResources.INSTANCE.css().eventsoverviewrecent_yearcollapsed()); + } + } +} 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 new file mode 100644 index 00000000000..316091d492f --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/recent/EventsOverviewRecentYear.ui.xml @@ -0,0 +1,57 @@ + + + + + + + + +
+ +
+
+

+
+
+ + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+
From 843f791fe8fa6935a4bd4980540b31728ffb6b90 Mon Sep 17 00:00:00 2001 From: fmittag Date: Sun, 13 Jul 2014 15:14:43 +0200 Subject: [PATCH 057/323] made the tablet/desktop views also the default views for smartphone as we don't have a smartphone specific version for now --- .../SmartphoneApplicationClientFactory.java | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) 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 5c9624d1279..ab5e46d4bf8 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 @@ -6,15 +6,20 @@ 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.event.EventView; +import com.sap.sailing.gwt.home.client.place.event.TabletAndDesktopEventView; 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.searchresult.SearchResultView; +import com.sap.sailing.gwt.home.client.place.searchresult.TabletAndDesktopSearchResultView; import com.sap.sailing.gwt.home.client.place.solutions.SolutionsActivity; import com.sap.sailing.gwt.home.client.place.solutions.SolutionsView; +import com.sap.sailing.gwt.home.client.place.solutions.TabletAndDesktopSolutionsView; import com.sap.sailing.gwt.home.client.place.sponsoring.SponsoringActivity; import com.sap.sailing.gwt.home.client.place.sponsoring.SponsoringView; -import com.sap.sailing.gwt.home.client.place.start.SmartphoneStartView; +import com.sap.sailing.gwt.home.client.place.sponsoring.TabletAndDesktopSponsoringView; import com.sap.sailing.gwt.home.client.place.start.StartView; +import com.sap.sailing.gwt.home.client.place.start.TabletAndDesktopStartView; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.RaceGroupDTO; import com.sap.sse.gwt.client.player.Timer; @@ -35,31 +40,32 @@ public class SmartphoneApplicationClientFactory extends AbstractApplicationClien @Override public EventView createEventView(EventDTO event, List raceGroups, String leaderboardName, Timer timerForClientServerOffset) { - return null; + return new TabletAndDesktopEventView(getSailingService(), event, raceGroups, leaderboardName, timerForClientServerOffset); } @Override public EventsView createEventsView(EventsActivity activity) { - return null; + return new TabletAndDesktopEventsView(getPlaceNavigator()); } @Override public StartView createStartView() { - return new SmartphoneStartView(getPlaceNavigator()); + return new TabletAndDesktopStartView(getPlaceNavigator()); +// return new SmartphoneStartView(getPlaceNavigator()); } @Override public SponsoringView createSponsoringView(SponsoringActivity activity) { - return null; + return new TabletAndDesktopSponsoringView(); } @Override public SolutionsView createSolutionsView(SolutionsActivity activity) { - return null; + return new TabletAndDesktopSolutionsView(); } @Override public SearchResultView createSearchResultView() { - return null; + return new TabletAndDesktopSearchResultView(getPlaceNavigator(), getEventBus()); } } From 1103edbf3c5ccf198dd5f2c9106b854240d385bd Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sun, 13 Jul 2014 18:23:35 +0200 Subject: [PATCH 058/323] minor renames and Javadoc for clarity --- .../sailing/gwt/ui/client/shared/racemap/RaceMap.java | 5 ++++- .../sap/sailing/gwt/ui/server/SailingServiceImpl.java | 4 ++-- .../sailing/gwt/ui/shared/CompactRaceMapDataDTO.java | 2 +- .../com/sap/sailing/gwt/ui/shared/QuickRankDTO.java | 10 +++++++--- 4 files changed, 14 insertions(+), 7 deletions(-) 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 1110ac6c826..ffc1139429a 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 @@ -782,6 +782,9 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe return LatLng.newInstance(lat2 / Math.PI * 180., lon2 / Math.PI * 180.); } + /** + * Returns a pair whose first component is the leg number (one-based) of the competitor returned as the second component. + */ private com.sap.sse.common.Util.Pair getLeadingVisibleCompetitorInfo(Iterable competitorsToShow) { CompetitorDTO leadingCompetitorDTO = null; int legOfLeaderCompetitor = -1; @@ -789,7 +792,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe for (QuickRankDTO quickRank : quickRanks) { if (Util.contains(competitorsToShow, quickRank.competitor)) { leadingCompetitorDTO = quickRank.competitor; - legOfLeaderCompetitor = quickRank.legNumber; + legOfLeaderCompetitor = quickRank.legNumberOneBased; return new com.sap.sse.common.Util.Pair(legOfLeaderCompetitor, leadingCompetitorDTO); } } 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 5ae54429a33..37b302d6a7f 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 @@ -1865,8 +1865,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S for (Competitor competitor : trackedRace.getCompetitorsFromBestToWorst(dateAsTimePoint)) { TrackedLegOfCompetitor trackedLeg = trackedRace.getTrackedLeg(competitor, dateAsTimePoint); if (trackedLeg != null) { - int legNumber = race.getCourse().getLegs().indexOf(trackedLeg.getLeg()) + 1; - QuickRankDTO quickRankDTO = new QuickRankDTO(baseDomainFactory.convertToCompetitorDTO(competitor), rank, legNumber); + int legNumberOneBased = race.getCourse().getLegs().indexOf(trackedLeg.getLeg()) + 1; + QuickRankDTO quickRankDTO = new QuickRankDTO(baseDomainFactory.convertToCompetitorDTO(competitor), rank, legNumberOneBased); result.add(quickRankDTO); } rank++; 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 b86d2cf1c33..6d9910d3847 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 @@ -35,7 +35,7 @@ public class CompactRaceMapDataDTO implements IsSerializable { } this.quickRanks = new ArrayList(quickRanks.size()); for (QuickRankDTO quickRank : quickRanks) { - this.quickRanks.add(new CompactQuickRankDTO(quickRank.competitor.getIdAsString(), quickRank.rank, quickRank.legNumber)); + this.quickRanks.add(new CompactQuickRankDTO(quickRank.competitor.getIdAsString(), quickRank.rank, quickRank.legNumberOneBased)); } this.courseSidelines = courseSidelines; this.coursePositions = coursePositions; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/QuickRankDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/QuickRankDTO.java index 4ec033960ad..c7e83f01a61 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/QuickRankDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/QuickRankDTO.java @@ -6,13 +6,17 @@ import com.sap.sailing.domain.common.dto.CompetitorDTO; public class QuickRankDTO implements IsSerializable { public CompetitorDTO competitor; public int rank; - public int legNumber; + + /** + * The {@link #competitor}'s leg number, starting with 1 for the first leg + */ + public int legNumberOneBased; public QuickRankDTO() {} - public QuickRankDTO(CompetitorDTO competitorDTO, int rank, int legNumber) { + public QuickRankDTO(CompetitorDTO competitorDTO, int rank, int legNumberOneBased) { this.competitor = competitorDTO; this.rank = rank; - this.legNumber = legNumber; + this.legNumberOneBased = legNumberOneBased; } } From b38714232bac942b778130a6b448a038ede7c753 Mon Sep 17 00:00:00 2001 From: fmittag Date: Sun, 13 Jul 2014 20:21:22 +0200 Subject: [PATCH 059/323] little parsing fix for main.css --- .../src/main/resources/com/sap/sailing/gwt/home/main.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/main.css b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/main.css index eff5c0be943..1c177831928 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/main.css +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/main.css @@ -265,7 +265,7 @@ figcaption { .lightbox_overlayhidden { opacity: 0; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; - filter: alpha(opacity=0); + filter: literal("alpha(opacity=0)"); } .lightbox_content { position: fixed; @@ -288,7 +288,7 @@ figcaption { .lightbox_contenthidden { opacity: 0; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; - filter: alpha(opacity=0); + filter: literal("alpha(opacity=0)"); } .lightbox_content_enlargedimage { -webkit-transition: margin 0.7s linear; From 27b0ca18823dfe8958a2c80b9707d7ff75c6e1ce Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 14 Jul 2014 09:34:47 +0200 Subject: [PATCH 060/323] use "Show Charts" instead of "Competitor Charts" for leaderboard charts; show only lastN columns when small width --- java/com.sap.sailing.gwt.ui/ViewerToolbar.css | 4 ++++ .../client/shared/charts/MultiCompetitorLeaderboardChart.java | 2 +- .../sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/ViewerToolbar.css b/java/com.sap.sailing.gwt.ui/ViewerToolbar.css index a78e3ce9b45..dcea3a8a731 100644 --- a/java/com.sap.sailing.gwt.ui/ViewerToolbar.css +++ b/java/com.sap.sailing.gwt.ui/ViewerToolbar.css @@ -29,6 +29,10 @@ margin: 9px 12px 0 0; } +.viewerToolbar-settingsButton:last-child { + margin: 0 0px 0 1px; +} + .viewerToolbar-settingsButton { background: url(images/settings_white.png), url(images/btn-cta-orangeleft.png); background-position: center, center 0; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MultiCompetitorLeaderboardChart.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MultiCompetitorLeaderboardChart.java index 9ae49c68d83..f4bd57bd667 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MultiCompetitorLeaderboardChart.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MultiCompetitorLeaderboardChart.java @@ -48,6 +48,6 @@ public class MultiCompetitorLeaderboardChart extends AbstractCompetitorLeaderboa @Override public String getLocalizedShortName() { - return stringMessages.competitorCharts(); + return stringMessages.showCharts(); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java index 48979e67e51..c61bbc0c076 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java @@ -217,7 +217,7 @@ public class LeaderboardEntryPoint extends AbstractEntryPoint { } else if (isSmallWidth()) { raceColumnSelectionStrategy = RaceColumnSelectionStrategies.LAST_N; int width = Window.getClientWidth(); - numberOfLastRacesToShow = (width-275)/35; + numberOfLastRacesToShow = (width-275)/40; } else { raceColumnSelectionStrategy = RaceColumnSelectionStrategies.EXPLICIT; numberOfLastRacesToShow = null; From 0d40c2e8b707cef2c5dde8c63aeb579d7e9e1c61 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 14 Jul 2014 10:44:19 +0200 Subject: [PATCH 061/323] perform initial load of wind data for streamlets immediately; wait for wind sources to be received before updating actual wind data --- .../shared/racemap/WindStreamletsRaceboardOverlay.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) 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 537de8e5bee..80a7ae839af 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 @@ -116,10 +116,12 @@ public class WindStreamletsRaceboardOverlay extends FullCanvasOverlay { scheduler.scheduleFixedPeriod(new RepeatingCommand() { @Override public boolean execute() { - updateWindSourcesToObserve(); + updateWindSourcesToObserve(/* runWhenDone */ null); return visible; } }, CHECK_WIND_SOURCE_INTERVAL_IN_MILLIS); + // Now run things once, first updating the wind sources, then grabbing the wind from those sources: + updateWindSourcesToObserve(new Runnable() { @Override public void run() { updateWindField(); } }); } private void stopStreamlets() { @@ -133,8 +135,9 @@ public class WindStreamletsRaceboardOverlay extends FullCanvasOverlay { * not yet observed (contained in the keys of {@link #windInfoForRace}'s * {@link WindInfoForRaceDTO#windTrackInfoByWindSource windTrackInfoByWindSource} map, the wind source is added to that map * unless it's the {@link WindSourceType#COMBINED} wind source or the wind source is marked as excluded. + * @param runWhenDone TODO */ - private void updateWindSourcesToObserve() { + private void updateWindSourcesToObserve(final Runnable runWhenDone) { sailingService.getWindSourcesInfo(raceIdentifier, new MarkedAsyncCallback<>(new AsyncCallback() { @Override public void onFailure(Throwable caught) { @@ -151,6 +154,9 @@ public class WindStreamletsRaceboardOverlay extends FullCanvasOverlay { windInfoForRace.windTrackInfoByWindSource.put(e.getKey(), e.getValue()); } } + if (runWhenDone != null) { + runWhenDone.run(); + } } })); } From a6d8f0bda212b256e921adc983c8098aef18898f Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 14 Jul 2014 08:45:21 +0000 Subject: [PATCH 062/323] Updated tw2014 (markdown) --- wiki/tw2014.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wiki/tw2014.md b/wiki/tw2014.md index 7a866c5e54e..d87eab260ff 100644 --- a/wiki/tw2014.md +++ b/wiki/tw2014.md @@ -1,4 +1,4 @@ -# Kieler Woche 2014 +# Travemünder Woche 2014 Two systems for seamless switching: From 349a0f0c992e3997d28591ecc719796711e16b40 Mon Sep 17 00:00:00 2001 From: Carolin Joppien Date: Mon, 14 Jul 2014 08:59:00 +0000 Subject: [PATCH 063/323] Updated home (markdown) --- home.md | 1 + 1 file changed, 1 insertion(+) diff --git a/home.md b/home.md index 90476d89362..3b92ad2e70a 100644 --- a/home.md +++ b/home.md @@ -44,6 +44,7 @@ Like businesses, sailors need the latest information to make strategic decisions * [[Racecommittee App|wiki/racecommittee-app]] * [[Web Services API|wiki/sailing-webservices]] * [[Theses (Bachelor, Master, ...)|wiki/theses]] + * [[Google Analytics (Web Page Tracking)|wiki/ganalytics]] * Planning and Event Information * [[Project Planning (bigger development)|wiki/planning]] * [[General Event Planning|wiki/general-event-planning]] From 2e1995bbcda44dd32fd73d2d46c38af4efe2f37b Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 14 Jul 2014 18:33:30 +0200 Subject: [PATCH 064/323] fixed issue with incomplete streamlet visualizations based on request dropping in AsyncActionExecutor; using single call for all wind sources now --- .../WindStreamletsRaceboardOverlay.java | 72 ++++++++++--------- 1 file changed, 39 insertions(+), 33 deletions(-) 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 80a7ae839af..6076e8dfbaa 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 @@ -1,10 +1,10 @@ package com.sap.sailing.gwt.ui.shared.racemap; -import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Map.Entry; +import java.util.Set; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.RepeatingCommand; @@ -135,7 +135,6 @@ public class WindStreamletsRaceboardOverlay extends FullCanvasOverlay { * not yet observed (contained in the keys of {@link #windInfoForRace}'s * {@link WindInfoForRaceDTO#windTrackInfoByWindSource windTrackInfoByWindSource} map, the wind source is added to that map * unless it's the {@link WindSourceType#COMBINED} wind source or the wind source is marked as excluded. - * @param runWhenDone TODO */ private void updateWindSourcesToObserve(final Runnable runWhenDone) { sailingService.getWindSourcesInfo(raceIdentifier, new MarkedAsyncCallback<>(new AsyncCallback() { @@ -162,41 +161,48 @@ public class WindStreamletsRaceboardOverlay extends FullCanvasOverlay { } private void updateWindField() { - Date beginningOfTime = null; - Date endOfTime = null; + Date timeOfLastFixOfSource = null; + Set windSourceTypeNames = new HashSet<>(); for (final Entry e : windInfoForRace.windTrackInfoByWindSource.entrySet()) { if (!Util.contains(windInfoForRace.windSourcesToExclude, e.getKey())) { - final Date timeOfLastFixOfSource = (e.getValue().windFixes != null && !e.getValue().windFixes.isEmpty()) - ? new Date(e.getValue().windFixes.get(e.getValue().windFixes.size()-1).measureTimepoint+1) - : beginningOfTime; - GetWindInfoAction getWind = new GetWindInfoAction(sailingService, raceIdentifier, timeOfLastFixOfSource, endOfTime, - RESOLUTION_IN_MILLIS, Arrays.asList(new String[] { e.getKey().getType().name() }), /* onlyUpToNewestEvent */ true); - asyncActionsExecutor.execute(getWind, LODA_WIND_STREAMLET_DATA_CATEGORY+" "+e.getKey().name(), new MarkedAsyncCallback<>( - new AsyncCallback() { - @Override - public void onFailure(Throwable caught) { - Window.setStatus(stringMessages.errorFetchingWindStreamletData(caught.getMessage())); - } - - @Override - public void onSuccess(WindInfoForRaceDTO result) { - updateAverageLatitudeDeg(result); - // merge the new wind fixes into the existing WindInfoForRaceDTO structure, updating min/max confidences - if (e.getValue().windFixes == null) { - e.getValue().windFixes = result.windTrackInfoByWindSource.get(e.getKey()).windFixes; - } else { - e.getValue().windFixes.addAll(result.windTrackInfoByWindSource.get(e.getKey()).windFixes); - } - if (result.windTrackInfoByWindSource.get(e.getKey()).maxWindConfidence > e.getValue().maxWindConfidence) { - e.getValue().maxWindConfidence = result.windTrackInfoByWindSource.get(e.getKey()).maxWindConfidence; - } - if (result.windTrackInfoByWindSource.get(e.getKey()).minWindConfidence < e.getValue().minWindConfidence) { - e.getValue().minWindConfidence = result.windTrackInfoByWindSource.get(e.getKey()).minWindConfidence; - } - } - })); + windSourceTypeNames.add(e.getKey().getType().name()); + if (e.getValue().windFixes != null && !e.getValue().windFixes.isEmpty()) { + // TODO this should better be a per wind source time range; furthermore, only real fixes should be requested / transmitted + timeOfLastFixOfSource = new Date( + e.getValue().windFixes.get(e.getValue().windFixes.size() - 1).measureTimepoint + 1); + } } } + GetWindInfoAction getWind = new GetWindInfoAction(sailingService, raceIdentifier, timeOfLastFixOfSource, + /* endOfTime */ null, RESOLUTION_IN_MILLIS, windSourceTypeNames, /* onlyUpToNewestEvent */ true); + asyncActionsExecutor.execute(getWind, LODA_WIND_STREAMLET_DATA_CATEGORY, + new MarkedAsyncCallback<>(new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + Window.setStatus(stringMessages.errorFetchingWindStreamletData(caught.getMessage())); + } + + @Override + public void onSuccess(WindInfoForRaceDTO result) { + updateAverageLatitudeDeg(result); + // merge the new wind fixes into the existing WindInfoForRaceDTO structure, updating min/max + // confidences + for (Entry e : result.windTrackInfoByWindSource.entrySet()) { + WindTrackInfoDTO windTrackForSource = windInfoForRace.windTrackInfoByWindSource.get(e.getKey()); + if (windTrackForSource.windFixes == null) { + windTrackForSource.windFixes = result.windTrackInfoByWindSource.get(e.getKey()).windFixes; + } else { + windTrackForSource.windFixes.addAll(result.windTrackInfoByWindSource.get(e.getKey()).windFixes); + } + if (result.windTrackInfoByWindSource.get(e.getKey()).maxWindConfidence > windTrackForSource.maxWindConfidence) { + windTrackForSource.maxWindConfidence = result.windTrackInfoByWindSource.get(e.getKey()).maxWindConfidence; + } + if (result.windTrackInfoByWindSource.get(e.getKey()).minWindConfidence < windTrackForSource.minWindConfidence) { + windTrackForSource.minWindConfidence = result.windTrackInfoByWindSource.get(e.getKey()).minWindConfidence; + } + } + } + })); } @Override From 4239fef1b52957b33d5f94dda3c257070d4c514e Mon Sep 17 00:00:00 2001 From: fmittag Date: Sun, 13 Jul 2014 20:21:42 +0200 Subject: [PATCH 065/323] rework of races list to show new design with integrated rcapp infos --- .../client/place/event/EventActivity.java | 2 +- .../place/event/EventPageNavigator.java | 3 +- .../event/TabletAndDesktopEventView.java | 99 +++---- .../event/TabletAndDesktopEventView.ui.xml | 2 +- .../client/place/event/regatta/Regatta.css | 181 +++++++++++++ .../client/place/event/regatta/Regatta.java | 140 ++++++++++ .../client/place/event/regatta/Regatta.ui.xml | 67 +++++ .../event/regatta/RegattaCompetitor.java | 29 +++ .../event/regatta/RegattaCompetitor.ui.xml | 14 + .../place/event/regatta/RegattaFleet.java | 19 ++ .../place/event/regatta/RegattaFleet.ui.xml | 39 +++ .../place/event/regatta/RegattaPhase.java | 33 +++ .../place/event/regatta/RegattaPhase.ui.xml | 15 ++ .../place/event/regatta/RegattaPhaseRace.java | 24 ++ .../event/regatta/RegattaPhaseRace.ui.xml | 11 + .../place/event/regatta/RegattaResources.java | 43 ++++ .../event/regattalist/EventRegattaList.css | 136 +--------- .../event/regattalist/EventRegattaList.java | 28 +- .../regattalist/EventRegattaListItem.java | 67 ----- .../regattalist/EventRegattaListItem.ui.xml | 42 --- .../EventRegattaListResources.java | 21 +- .../event/regattaraces/EventRegattaRaces.css | 241 ++++++++++++++++++ .../event/regattaraces/EventRegattaRaces.java | 64 +++++ .../regattaraces/EventRegattaRaces.ui.xml | 33 +++ .../EventRegattaRacesFleet.ui.xml | 15 ++ .../EventRegattaRacesFleetGroup.ui.xml | 13 + .../EventRegattaRacesNavigationLink.ui.xml | 11 + .../EventRegattaRacesPhase.ui.xml | 14 + .../regattaraces/EventRegattaRacesRace.ui.xml | 16 ++ .../EventRegattaRacesRaceday.ui.xml | 14 + .../EventRegattaRacesResources.java | 52 ++++ .../gwt/ui/server/SailingServiceImpl.java | 9 + .../sailing/gwt/ui/shared/RaceGroupDTO.java | 1 + 33 files changed, 1153 insertions(+), 345 deletions(-) create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/Regatta.css create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/Regatta.java create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/Regatta.ui.xml create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaCompetitor.java create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaCompetitor.ui.xml create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaFleet.java create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaFleet.ui.xml create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaPhase.java create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaPhase.ui.xml create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaPhaseRace.java create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaPhaseRace.ui.xml create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaResources.java delete mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.java delete mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.ui.xml create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRaces.css create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRaces.java create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRaces.ui.xml create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesFleet.ui.xml create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesFleetGroup.ui.xml create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesNavigationLink.ui.xml create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesPhase.ui.xml create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesRace.ui.xml create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesRaceday.ui.xml create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesResources.java 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 ede8ac05461..e5a11fae186 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 @@ -52,7 +52,7 @@ public class EventActivity extends AbstractActivity { @Override public void onFailure(Throwable caught) { - Window.alert("Shit happens"); + Window.alert("Shit happens at getRegattaStructureForEvent()"); } }); } 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 f6b0104553d..24fdcdaccc1 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 @@ -2,6 +2,7 @@ package com.sap.sailing.gwt.home.client.place.event; import com.sap.sailing.domain.common.dto.RaceDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; +import com.sap.sailing.gwt.ui.shared.RaceGroupDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; public interface EventPageNavigator { @@ -9,7 +10,7 @@ public interface EventPageNavigator { void goToRegattas(); void goToSchedule(); void goToMedia(); - void goToRegattaRaces(LeaderboardGroupDTO leaderboardGroup, StrippedLeaderboardDTO leaderboard); + void goToRegattaRaces(RaceGroupDTO raceGroup, StrippedLeaderboardDTO leaderboard); void openRaceViewer(StrippedLeaderboardDTO leaderboard, RaceDTO race); 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/TabletAndDesktopEventView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/TabletAndDesktopEventView.java index 8d70714f0f7..d58c4ff9b0b 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 @@ -9,7 +9,6 @@ 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.Window; -import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; @@ -18,7 +17,7 @@ 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; import com.sap.sailing.gwt.home.client.place.event.regattalist.EventRegattaList; -import com.sap.sailing.gwt.home.client.place.event.regattaschedule.EventRegattaSchedule; +import com.sap.sailing.gwt.home.client.place.event.regattaraces.EventRegattaRaces; import com.sap.sailing.gwt.home.client.place.event.schedule.EventSchedule; import com.sap.sailing.gwt.home.client.shared.eventsponsors.EventSponsors; import com.sap.sailing.gwt.ui.client.EntryPointLinkFactory; @@ -27,8 +26,8 @@ import com.sap.sailing.gwt.ui.raceboard.RaceBoardViewConfiguration; 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.RegattaDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; +import com.sap.sse.common.Util.Pair; import com.sap.sse.gwt.client.player.Timer; public class TabletAndDesktopEventView extends Composite implements EventView, EventPageNavigator { @@ -42,67 +41,56 @@ public class TabletAndDesktopEventView extends Composite implements EventView, E @UiField(provided=true) EventSchedule eventSchedule; @UiField(provided=true) EventMedia eventMedia; @UiField(provided=true) EventRegattaList eventRegattaList; - @UiField(provided=true) EventRegattaSchedule eventRegattaSchedule; + @UiField(provided=true) EventRegattaRaces eventRegattaRaces; @UiField EventSponsors eventSponsors; private final List pageElements; - - private final SailingServiceAsync sailingService; + private final Map> leaderboardsWithLeaderboardGroup; public TabletAndDesktopEventView(SailingServiceAsync sailingService, EventDTO event, List raceGroups, String leaderboardName, Timer timerForClientServerOffset) { - this.sailingService = sailingService; + leaderboardsWithLeaderboardGroup = new HashMap>(); + for(LeaderboardGroupDTO leaderboardGroup: event.getLeaderboardGroups()) { + for(StrippedLeaderboardDTO leaderboard: leaderboardGroup.getLeaderboards()) { + leaderboardsWithLeaderboardGroup.put(leaderboard.name, new Pair(leaderboard, leaderboardGroup)); + } + } + eventHeader = new EventHeader(event, this); - eventRegattaList = new EventRegattaList(event, this); - eventRegattaSchedule = new EventRegattaSchedule(event, timerForClientServerOffset, 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); initWidget(uiBinder.createAndBindUi(this)); - pageElements = Arrays.asList(new Widget[] { eventOverview, eventRegattaList, eventRegattaSchedule, eventMedia, eventSchedule }); - + pageElements = Arrays.asList(new Widget[] { eventOverview, eventRegattaList, eventRegattaRaces, eventMedia, eventSchedule }); - int leaderboardsCount = 0; - int leaderboardsGroupCount = event.getLeaderboardGroups().size(); - for(LeaderboardGroupDTO leaderboardGroup: event.getLeaderboardGroups()) { - leaderboardsCount += leaderboardGroup.getLeaderboards().size(); - } - - if(leaderboardsGroupCount == 0 || leaderboardsCount == 0) { + int leaderboardsCount = raceGroups.size(); + if(leaderboardsCount == 0) { Window.alert("There is no data available for this event."); } else { - - LeaderboardGroupDTO selectedLeaderboardGroup = null; - StrippedLeaderboardDTO selectedLeaderboard = null; + RaceGroupDTO selectedRaceGroup = null; // find the preselected leaderboard (if one exist) if (leaderboardName != null) { - for (LeaderboardGroupDTO leaderboardGroup : event.getLeaderboardGroups()) { - for (StrippedLeaderboardDTO leaderboard : leaderboardGroup.getLeaderboards()) { - if (leaderboard.name.equals(leaderboardName)) { - selectedLeaderboardGroup = leaderboardGroup; - selectedLeaderboard = leaderboard; - break; - } + 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(selectedLeaderboard == null && leaderboardsCount == 1) { - for (LeaderboardGroupDTO leaderboardGroup : event.getLeaderboardGroups()) { - if(leaderboardGroup.getLeaderboards().size() > 0) { - selectedLeaderboard = leaderboardGroup.getLeaderboards().get(0); - selectedLeaderboardGroup = leaderboardGroup; - } - } + // 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(selectedLeaderboard != null && selectedLeaderboardGroup != null) { - goToRegattaRaces(selectedLeaderboardGroup, selectedLeaderboard); + if(selectedRaceGroup != null) { + goToRegattaRaces(selectedRaceGroup, leaderboardsWithLeaderboardGroup.get(selectedRaceGroup.getName()).getA()); } else { goToRegattas(); } @@ -130,37 +118,10 @@ public class TabletAndDesktopEventView extends Composite implements EventView, E } @Override - public void goToRegattaRaces(final LeaderboardGroupDTO leaderboardGroup, final StrippedLeaderboardDTO leaderboard) { - switch (leaderboard.type) { - case RegattaLeaderboard: - sailingService.getRegattaByName(leaderboard.regattaName, new AsyncCallback() { - @Override - public void onSuccess(RegattaDTO regatta) { - eventRegattaSchedule.setRacesFromRegatta(regatta, leaderboardGroup, leaderboard); - eventHeader.setDataNavigationType("compact"); - setVisibleEventElement(eventRegattaSchedule); - } - - @Override - public void onFailure(Throwable caught) { - Window.alert("Shit happens"); - } - }); - break; - case FlexibleLeaderboard: - eventRegattaSchedule.setRacesFromFlexibleLeaderboard(leaderboardGroup, leaderboard); - eventHeader.setDataNavigationType("compact"); - setVisibleEventElement(eventRegattaSchedule); - break; - case FlexibleMetaLeaderboard: - eventHeader.setDataNavigationType("compact"); - setVisibleEventElement(eventRegattaSchedule); - break; - case RegattaMetaLeaderboard: - eventHeader.setDataNavigationType("compact"); - setVisibleEventElement(eventRegattaSchedule); - break; - } + public void goToRegattaRaces(RaceGroupDTO raceGroup, StrippedLeaderboardDTO leaderboard) { + eventRegattaRaces.setRacesFromRaceGroup(raceGroup, leaderboard); + eventHeader.setDataNavigationType("compact"); + setVisibleEventElement(eventRegattaRaces); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/TabletAndDesktopEventView.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/TabletAndDesktopEventView.ui.xml index f2152ad4805..c6bfb6f3e08 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/TabletAndDesktopEventView.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/TabletAndDesktopEventView.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/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 new file mode 100644 index 00000000000..914afb8559f --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/Regatta.css @@ -0,0 +1,181 @@ +.regatta { + font-size: 15px; + font-size: 1rem; + border-bottom: 1px solid #d9d9d9; + padding: 1.333333333333333em 0; +} +.regatta[data-rendermode='compact'] { + border-top: 1px solid #ccc; + padding-left: 4em; + padding-right: 4em; +} +.regatta[data-rendermode='compact'] .regatta_intro_details_itemhideoncompact { + display: none; +} +.regatta[data-rendermode='link']:hover, +.regatta[data-rendermode='link']:focus { + background-color: #fcfcfc; +} +.regatta[data-rendermode='link']:nth-child\(odd\) { + background-color: #f8f8f8; +} +.regatta[data-rendermode='link']:nth-child\(odd\):hover, +.regatta[data-rendermode='link']:nth-child\(odd\):focus { + background-color: #f3f3f3; +} +.regatta_intro { + display: block; + padding-left: 10.333333333333334em; + min-height: 9.333333333333334em; + position: relative; +} +.regatta_intro:hover, +.regatta_intro:focus { + text-decoration: none; +} +.regatta_intro_image { + border: 1px solid #ccc; + position: absolute; + top: 0; + left: 0; +} +.regatta_intro_header { + margin-bottom: 0.666666666666667em; + padding-top: 0.333333333333333em; + padding-bottom: 0.666666666666667em; + border-bottom: 1px solid #ccc; + position: relative; +} +.regatta_intro_header_name { + display: inline-block; + font-size: 1.333333333333333em; + margin: 0; + margin-right: 0.266666666666667em; +} +.regatta_intro_header_namelink:hover, +.regatta_intro_header_namelink:focus { + text-decoration: underline; +} +.regatta_intro_header_details { + text-align: right; +} +.regatta_intro_header_details_item { + display: inline-block; + padding: 0.2em 1em 0.2em 0; + margin-right: 1em; + border-right: 1px solid #ccc; +} +.regatta_intro_header_details_item:last-child { + padding-right: 0; + margin-right: 0; + border-right-width: 0; +} +.regatta_intro_header_details_item_value { + color: #008fcc; + font-weight: 700; +} +.regatta_intro_header_details_label { + vertical-align: middle; + margin-top: -0.2em; +} +.regatta_intro_phases { + position: relative; + padding-right: 10em; + border-bottom: 1px solid #ccc; + padding-bottom: 0.666666666666667em; +} +.regatta_intro_phases_phase { + display: inline-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; +} +.regatta_intro_phases_phase_races { + display: inline-block; +} +.regatta_intro_phases_phase_races_race { + display: inline-block; + background-color: #ccc; + height: 0.666666666666667em; + width: 1.333333333333333em; +} +.regatta_intro_phases_phase_races_race[data-status='live'] { + background-color: #f00; +} +.regatta_intro_phases_phase_races_race[data-status='finished'] { + 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; +} +.regatta_intro_scheduled { + padding-top: 0.8em; +} +.regatta_intro_results { + padding-top: 0.6em; +} +.regatta_intro_results_competitor { + white-space: nowrap; + overflow: hidden; + -o-text-overflow: ellipsis; + text-overflow: ellipsis; + display: inline-block; + max-width: 32%; + padding: 0.2em 1.333333333333333em; + border-right: 1px solid #ccc; +} +.regatta_intro_results_competitor:first-child { + padding-left: 0; +} +.regatta_intro_results_competitor:last-child { + padding-right: 0; + border-right-width: 0; +} +.regatta_intro_results_competitor_name { + font-weight: 700; + color: #008fcc; +} +.regatta_intro_fleets { + width: 100%; +} +.regatta_intro_fleets_fleet { + border-bottom: 1px solid #ccc; +} +.regatta_intro_fleets_fleet:last-child { + padding-bottom: 0; +} +.regatta_intro_fleets_fleet_item { + margin: 0.4em 0; + padding: 0.4em 1.333333333333333em; + border-left: 1px solid #ccc; + white-space: nowrap; +} +.regatta_intro_fleets_fleet_itemfirst { + margin-left: 0; + padding-left: 0; + border-left-width: 0; +} +.regatta_intro_fleets_fleet_item_value { + font-weight: 700; + color: #008fcc; +} +.regatta_intro_fleets_fleet_item_flag { + width: 2em; + vertical-align: middle; +} +.regatta_intro_fleets_fleet_live { + text-align: right; + width: 100%; +} 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 new file mode 100644 index 00000000000..5396cec31d6 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/Regatta.java @@ -0,0 +1,140 @@ +package com.sap.sailing.gwt.home.client.place.event.regatta; + +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.dom.client.Style.Display; +import com.google.gwt.dom.client.Style.Visibility; +import com.google.gwt.dom.client.TableElement; +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.Widget; +import com.sap.sailing.gwt.home.client.place.event.EventPageNavigator; +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 Regatta extends Composite { + private static RegattaUiBinder uiBinder = GWT.create(RegattaUiBinder.class); + + interface RegattaUiBinder extends UiBinder { + } + + @SuppressWarnings("unused") + private final EventDTO event; + private final Timer timerForClientServerOffset; + private final EventPageNavigator pageNavigator; + private StrippedLeaderboardDTO leaderboard; + private RaceGroupDTO raceGroup; + private LeaderboardGroupDTO leaderboardGroup; + + @UiField SpanElement regattaName1; + @UiField SpanElement regattaName2; + @UiField SpanElement leaderboardGroupName; + @UiField SpanElement scheduledStart; + @UiField HTMLPanel regattaPhasesPanel; + @UiField SpanElement competitorsCount; + @UiField SpanElement racesCount; + @UiField SpanElement trackedRacesCount; + @UiField DivElement isLiveDiv; + @UiField TableElement isLiveDiv2; + @UiField DivElement isFinishedDiv; + @UiField DivElement isFinishedDiv2; + @UiField DivElement isScheduledDiv; + @UiField Anchor leaderboardLink; + @UiField HTMLPanel liveRaceInfosPerFleetPanel; + @UiField(provided=true) RegattaCompetitor competitorWithRank1; + @UiField(provided=true) RegattaCompetitor competitorWithRank2; + @UiField(provided=true) RegattaCompetitor competitorWithRank3; + + @UiField HTMLPanel regattaImage; + @UiField Anchor regattaImageWithLink; + + @UiField HTMLPanel regattaNameDiv; + @UiField Anchor regattaNameWithLink; + + public Regatta(EventDTO event, boolean isNavigatable, Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { + this.event = event; + this.timerForClientServerOffset = timerForClientServerOffset; + this.pageNavigator = pageNavigator; + + competitorWithRank1 = new RegattaCompetitor(1, null); + competitorWithRank2 = new RegattaCompetitor(2, null); + competitorWithRank3 = new RegattaCompetitor(3, null); + + RegattaResources.INSTANCE.css().ensureInjected(); + initWidget(uiBinder.createAndBindUi(this)); + + regattaImageWithLink.getElement().setAttribute("data-rendermode", "link"); + regattaImageWithLink.setVisible(isNavigatable); + regattaImage.setVisible(!isNavigatable); + + regattaNameWithLink.getElement().setAttribute("data-rendermode", "link"); + regattaNameWithLink.setVisible(isNavigatable); + regattaNameDiv.setVisible(!isNavigatable); + } + + public void setData(RaceGroupDTO raceGroup, StrippedLeaderboardDTO leaderboard, LeaderboardGroupDTO leaderboardGroup) { + this.raceGroup = raceGroup; + this.leaderboard = leaderboard; + this.leaderboardGroup = leaderboardGroup; + + boolean hasLiveRace = leaderboard.hasLiveRace(timerForClientServerOffset.getLiveTimePointInMillis()); + isLiveDiv.setAttribute("data-labeltype", hasLiveRace ? "live" : ""); + + boolean isFinished = !hasLiveRace; + if(!isFinished) { + isFinishedDiv.getStyle().setDisplay(Display.NONE); + } + + String regattaDisplayName = leaderboard.displayName != null ? leaderboard.displayName : leaderboard.name; + regattaName1.setInnerText(regattaDisplayName); + regattaName2.setInnerText(regattaDisplayName); + if(raceGroup.leaderboardGroupName != null) { + leaderboardGroupName.setInnerText(raceGroup.leaderboardGroupName); + } else { + leaderboardGroupName.getStyle().setDisplay(Display.NONE); + } + + if(leaderboard.rows != null) { + competitorsCount.setInnerText(String.valueOf(leaderboard.rows.size())); + } else { + competitorsCount.getStyle().setVisibility(Visibility.HIDDEN); + } + racesCount.setInnerText("tbd"); + trackedRacesCount.setInnerText("tbd"); + +// regattaPhasesPanel.add(xxx); + setRegattaProgress(); + } + + private void setRegattaProgress() { + for(RaceGroupSeriesDTO series: raceGroup.getSeries()) { + RegattaPhase regattaPhase = new RegattaPhase(series); + regattaPhasesPanel.add(regattaPhase); + } + } + + @UiHandler("leaderboardLink") + public void goToLeaderboard(ClickEvent e) { + pageNavigator.openLeaderboardViewer(null, leaderboard); + } + + @UiHandler("regattaImageWithLink") + public void goToRegattaRaces(ClickEvent e) { + pageNavigator.goToRegattaRaces(raceGroup, leaderboard); + } + + @UiHandler("regattaNameWithLink") + public void goToRegattaRaces2(ClickEvent e) { + pageNavigator.goToRegattaRaces(raceGroup, leaderboard); + } +} 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 new file mode 100644 index 00000000000..648f8e1858b --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/Regatta.ui.xml @@ -0,0 +1,67 @@ + + + + + + + + +
+
+
+
+ + + + + + +
+
+
+ +

+
+ +

+
+
+
+
+
+
+
 
+
 
+
 
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+ :  +
+
+ + + +
+ + +
+
+
+
+
+
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaCompetitor.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaCompetitor.java new file mode 100644 index 00000000000..4ae86df3d8d --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaCompetitor.java @@ -0,0 +1,29 @@ +package com.sap.sailing.gwt.home.client.place.event.regatta; + +import com.google.gwt.core.client.GWT; +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.sap.sailing.domain.common.dto.CompetitorDTO; + +public class RegattaCompetitor extends Composite { + private static RegattaCompetitorUiBinder uiBinder = GWT.create(RegattaCompetitorUiBinder.class); + + interface RegattaCompetitorUiBinder extends UiBinder { + } + + @UiField SpanElement competitorRank; + @UiField SpanElement competitorNationality; + @UiField SpanElement competitorName; + @UiField SpanElement competitorTotalPoints; + + public RegattaCompetitor(Integer rank, CompetitorDTO competitor) { + + RegattaResources.INSTANCE.css().ensureInjected(); + initWidget(uiBinder.createAndBindUi(this)); + + competitorRank.setInnerText(String.valueOf(rank)); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaCompetitor.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaCompetitor.ui.xml new file mode 100644 index 00000000000..07fecdab299 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaCompetitor.ui.xml @@ -0,0 +1,14 @@ + + + + + + + +
+ .  -  -  +
+
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaFleet.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaFleet.java new file mode 100644 index 00000000000..477037c1095 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaFleet.java @@ -0,0 +1,19 @@ +package com.sap.sailing.gwt.home.client.place.event.regatta; + +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; + +public class RegattaFleet extends Composite { + private static RegattaFleetUiBinder uiBinder = GWT.create(RegattaFleetUiBinder.class); + + interface RegattaFleetUiBinder extends UiBinder { + } + + public RegattaFleet() { + + RegattaResources.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/RegattaFleet.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaFleet.ui.xml new file mode 100644 index 00000000000..bc6d3e705f1 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaFleet.ui.xml @@ -0,0 +1,39 @@ + + + + + + + + + +
{fleet.name}
+ + +
{fleet.phase.name} - {fleet.race.name} +
+ + +
+ {#if regatta.isScheduled} {/if regatta.isScheduled} + {#if regatta.isLive} {/if regatta.isLive} + {#if regatta.isFinished} {/if regatta.isFinished}{fleet.time} +
+ + +
+ + +
 {fleet.course}
+ + +
 {fleet.area}
+ + + {#if regatta.isLive}{/if regatta.isLive} + + +
+
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 new file mode 100644 index 00000000000..b6afbda1258 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaPhase.java @@ -0,0 +1,33 @@ +package com.sap.sailing.gwt.home.client.place.event.regatta; + +import com.google.gwt.core.client.GWT; +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.HTMLPanel; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.ui.shared.RaceGroupSeriesDTO; + +public class RegattaPhase extends Composite { + private static RegattaPhaseUiBinder uiBinder = GWT.create(RegattaPhaseUiBinder.class); + + interface RegattaPhaseUiBinder extends UiBinder { + } + + @UiField SpanElement phaseName; + @UiField HTMLPanel phaseRacesPanel; + + public RegattaPhase(RaceGroupSeriesDTO series) { + + RegattaResources.INSTANCE.css().ensureInjected(); + initWidget(uiBinder.createAndBindUi(this)); + + phaseName.setInnerText(series.getName()); + + for(int i = 0; i < 5; i++) { + RegattaPhaseRace race = new RegattaPhaseRace(); + phaseRacesPanel.add(race); + } + } +} 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 new file mode 100644 index 00000000000..360a1db382d --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaPhase.ui.xml @@ -0,0 +1,15 @@ + + + + + + + +
+
+ +
+
+
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 new file mode 100644 index 00000000000..1011a49b0b8 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaPhaseRace.java @@ -0,0 +1,24 @@ +package com.sap.sailing.gwt.home.client.place.event.regatta; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; + +public class RegattaPhaseRace extends Composite { + private static RegattaPhaseRaceUiBinder uiBinder = GWT.create(RegattaPhaseRaceUiBinder.class); + + interface RegattaPhaseRaceUiBinder extends UiBinder { + } + + @UiField DivElement raceStatus; + + public RegattaPhaseRace() { + RegattaResources.INSTANCE.css().ensureInjected(); + initWidget(uiBinder.createAndBindUi(this)); + + raceStatus.setAttribute("data-status", "raceStatus"); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaPhaseRace.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaPhaseRace.ui.xml new file mode 100644 index 00000000000..501d86c1634 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaPhaseRace.ui.xml @@ -0,0 +1,11 @@ + + + + + + +
+
+
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 new file mode 100644 index 00000000000..72e848dd155 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaResources.java @@ -0,0 +1,43 @@ +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; + +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 LocalCss extends CssResource { + String regatta(); + String regatta_intro_details_itemhideoncompact(); + String regatta_intro(); + String regatta_intro_image(); + String regatta_intro_header(); + String regatta_intro_header_name(); + String regatta_intro_header_namelink(); + String regatta_intro_header_details(); + String regatta_intro_header_details_item(); + String regatta_intro_header_details_item_value(); + String regatta_intro_header_details_label(); + String regatta_intro_phases(); + String regatta_intro_phases_phase(); + String regatta_intro_phases_phase_name(); + String regatta_intro_phases_phase_races(); + String regatta_intro_phases_phase_races_race(); + String regatta_intro_phases_leaderboardlink(); + String regatta_intro_scheduled(); + String regatta_intro_results(); + String regatta_intro_results_competitor(); + String regatta_intro_results_competitor_name(); + String regatta_intro_fleets(); + String regatta_intro_fleets_fleet(); + String regatta_intro_fleets_fleet_item(); + String regatta_intro_fleets_fleet_itemfirst(); + String regatta_intro_fleets_fleet_item_value(); + String regatta_intro_fleets_fleet_item_flag(); + String regatta_intro_fleets_fleet_live(); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaList.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaList.css index c6d1bc3646c..5ec8c1c90cd 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaList.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaList.css @@ -1,136 +1,8 @@ -/** - * A list of regattas to an event - */ .eventregattalist { - font-size: 16px; + font-size: 15px; font-size: 1rem; } -.regatta { - font-size: 16px; - font-size: 1rem; - border-bottom: 1px solid #d9d9d9; - padding: 1.25em 0; -} -.regattacompact { - border-top: 1px solid #ccc; - padding-left: 3.75em; - padding-right: 3.75em; -} -.regattacompact .regatta_intro_details_itemhideoncompact { - display: none; -} -.regattalink:hover { - background-color: #fafafa; -} -.regattalink:hover .regatta_intro_image { - opacity: 0.9; - -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=90)"; - filter: literal("alpha(opacity=90)"); -} -.regattalink:hover .regatta_intro_header_name { - text-decoration: underline; -} -/** -.regattalink:nth-child(even) { - background-color: #f8f8f8; -} -.regattalink:nth-child(even):hover { - background-color: #f3f3f3; -} -*/ -.regatta_intro { - display: block; - padding-left: 9.6875em; - min-height: 8.75em; - position: relative; -} -.regatta_intro:hover, -.regatta_intro:focus { - text-decoration: none; -} -.regatta_intro_image { - position: absolute; - top: 0; - left: 0; -} -.regatta_intro_header { - margin-bottom: 0.625em; - padding-bottom: 0.625em; - border-bottom: 1px solid #f2f2f2; - position: relative; -} -.regatta_intro_header_logo { - vertical-align: middle; -} -.regatta_intro_header_name { - display: inline-block; - font-size: 1.25em; - margin: 0; - margin-right: 0.25em; -} -.regatta_intro_header_labels { - display: inline-block; - position: absolute; - right: 0; - top: 0.1875em; -} -.regatta_intro_header_labels .label { - vertical-align: baseline; -} -.regatta_intro_races { - position: relative; - min-height: 2.5em; - margin-bottom: 0.625em; - padding-bottom: 0.625em; - padding-left: 0.3125em; - border-bottom: 1px solid #f2f2f2; -} -.regatta_intro_races_race { - display: inline-block; - background-color: #ccc; - height: 0.625em; - width: 0.9375em; - -webkit-transform: skew(-45deg); - -moz-transform: skew(-45deg); - -o-transform: skew(-45deg); - -ms-transform: skew(-45deg); - transform: skew(-45deg); -} -.regatta_intro_races_race[data-finished='true'], -.regatta_intro_races_race[data-finished='data-finished'] { - background-color: #f0ab00; -} -.regatta_intro_meta { - position: relative; - font-weight: 600; -} -.regatta_intro_meta_scoring { - color: #008fcc; - display: inline-block; -} -.regatta_intro_meta_leaderboardlink { - position: absolute; - top: 0; - right: 0; - text-transform: uppercase; -} -.regatta_intro_details { - font-weight: 600; - color: #666; -} -.regatta_intro_details_item { - display: inline-block; - margin-right: 2.1875em; -} -.regatta_intro_details_item:last-child { - margin-right: 0; -} -.regatta_intro_details_item_sub { - display: inline; -} -.regatta_intro_details_item_sub:last-child:after { - content: ''; -} -.regatta_intro_details_item_value { - color: #008fcc; +.eventregattalist_navigation { + padding: 1.333333333333333em 0; + border-bottom: 1px solid #ccc; } 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 770e58db6f4..6b403e94de5 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,5 +1,8 @@ package com.sap.sailing.gwt.home.client.place.event.regattalist; +import java.util.List; +import java.util.Map; + import com.google.gwt.core.client.GWT; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; @@ -7,9 +10,13 @@ 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.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.StrippedLeaderboardDTO; +import com.sap.sse.common.Util.Pair; +import com.sap.sse.gwt.client.player.Timer; public class EventRegattaList extends Composite { private static EventRegattaHeaderUiBinder uiBinder = GWT.create(EventRegattaHeaderUiBinder.class); @@ -17,23 +24,20 @@ public class EventRegattaList extends Composite { interface EventRegattaHeaderUiBinder extends UiBinder { } - @SuppressWarnings("unused") - private final EventDTO event; - @UiField HTMLPanel regattaListItemPanel; - public EventRegattaList(EventDTO event, EventPageNavigator pageNavigator) { - this.event = event; - + public EventRegattaList(EventDTO event, List raceGroups, + Map> leaderboardsWithLeaderboardGroup, + Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { EventRegattaListResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); - for (LeaderboardGroupDTO leaderboardGroup : event.getLeaderboardGroups()) { - for (StrippedLeaderboardDTO leaderboard : leaderboardGroup.getLeaderboards()) { - EventRegattaListItem eventRegattaListItem = new EventRegattaListItem(pageNavigator, leaderboardGroup, leaderboard); - regattaListItemPanel.add(eventRegattaListItem); - } + 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); + regattaListItemPanel.add(regatta); } } - } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.java deleted file mode 100644 index 70b1a18a39b..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.java +++ /dev/null @@ -1,67 +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.DivElement; -import com.google.gwt.dom.client.SpanElement; -import com.google.gwt.dom.client.Style.Visibility; -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.place.event.EventPageNavigator; -import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; -import com.sap.sailing.gwt.ui.shared.RegattaDTO; -import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; - -public class EventRegattaListItem extends Composite { - private static EventRegattaHeaderUiBinder uiBinder = GWT.create(EventRegattaHeaderUiBinder.class); - - interface EventRegattaHeaderUiBinder extends UiBinder { - } - - @UiField SpanElement regattaName; - @UiField SpanElement leaderboardGroupName; - @UiField SpanElement competitorsCount; - @UiField SpanElement regattaPhase; - @UiField DivElement isLiveElement; - @UiField Anchor regattaRacesLink; - - @SuppressWarnings("unused") - private RegattaDTO regatta; - - private final StrippedLeaderboardDTO leaderboard; - private final LeaderboardGroupDTO leaderboardGroup; - private final EventPageNavigator pageNavigator; - - public EventRegattaListItem(EventPageNavigator pageNavigator, LeaderboardGroupDTO leaderboardGroup, StrippedLeaderboardDTO leaderboard) { - this.pageNavigator = pageNavigator; - this.leaderboardGroup = leaderboardGroup; - this.leaderboard = leaderboard; - - EventRegattaListResources.INSTANCE.css().ensureInjected(); - initWidget(uiBinder.createAndBindUi(this)); - - regattaName.setInnerText(leaderboard.displayName != null ? leaderboard.displayName : leaderboard.name); - leaderboardGroupName.setInnerText(leaderboardGroup.getName()); - - if(leaderboard.rows != null) { - competitorsCount.setInnerText(String.valueOf(leaderboard.rows.size())); - } else { - competitorsCount.getStyle().setVisibility(Visibility.HIDDEN); - } - } - - @UiHandler("regattaRacesLink") - public void goToRegattaRaces(ClickEvent e) { - pageNavigator.goToRegattaRaces(leaderboardGroup, leaderboard); - } - - @UiHandler("leaderboardLink") - public void goToLeaderboard(ClickEvent e) { - pageNavigator.openLeaderboardViewer(leaderboardGroup, leaderboard); - } - -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.ui.xml deleted file mode 100644 index bd4bfc84330..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListItem.ui.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - -
-
-
-
-
- -

-
-
-
-
-
-
-
Phase -
-
-
- Leaderboard -
-
-
-
-
-
-
-
 
-
-
-
-
-
-
-
-
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListResources.java index bcc54b52ddf..2604af94c79 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListResources.java @@ -12,25 +12,6 @@ public interface EventRegattaListResources extends ClientBundle { public interface LocalCss extends CssResource { String eventregattalist(); - String regatta(); - String regattacompact(); - String regatta_intro_details_itemhideoncompact(); - String regattalink(); - String regatta_intro_image(); - String regatta_intro_header_name(); - String regatta_intro(); - String regatta_intro_header(); - String regatta_intro_header_logo(); - String regatta_intro_header_labels(); - String label(); - String regatta_intro_races(); - String regatta_intro_races_race(); - String regatta_intro_meta(); - String regatta_intro_meta_scoring(); - String regatta_intro_meta_leaderboardlink(); - String regatta_intro_details(); - String regatta_intro_details_item(); - String regatta_intro_details_item_sub(); - String regatta_intro_details_item_value(); + String eventregattalist_navigation(); } } 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 new file mode 100644 index 00000000000..168609bf9e0 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRaces.css @@ -0,0 +1,241 @@ +.regattanavigation { + border-bottom: 1px solid #DDDDDD; + font-size: 1rem; + text-align: center; +} +.eventregattaraces { + font-size: 15px; + font-size: 1rem; +} +.eventregattaraces_phase { + margin: 2em 0; +} +.eventregattaraces_phase_name { + display: block; + margin: 0 0 1.666666666666667em 0; + padding-bottom: 0.416666666666667em; + border-bottom: 1px solid #ccc; +} +.eventregattaraces_phase_day { + font-weight: 600; + color: #333; + margin-bottom: 0.533333333333333em; +} +.eventregattaraces_phase_races { + margin-bottom: 2.666666666666667em; +} +.eventregattaraces_phase_fleets { + margin-bottom: 2.333333333333333em; +} +.eventregattaraces_phase_fleet { + display: inline-block; + margin-right: 1em; +} +.eventregattaraces_phase_fleet_name { + display: table-cell; + height: 2.5em; + padding: 0.533333333333333em; + border: 1px solid #ccc; + border-left: none; + color: #666; + text-transform: uppercase; + font-size: 1.066666666666667em; + font-weight: 600; +} +.eventregattaraces_phase_fleet_count { + display: table-cell; + width: 3.333333333333333em; + height: 3.333333333333333em; + padding: 0.333333333333333em; + background-color: #008fcc; + background-image: url("../images/fleet-icon.png"); + background-position: center 4px; + background-repeat: no-repeat; + font-size: 12px; + color: #fff; + text-align: center; + vertical-align: bottom; +} +.eventregattaraces_legend { + position: absolute; + top: 2.333333333333333em; + right: 1.333333333333333em; +} +.eventregattaraces_legend_item { + position: relative; + display: inline-block; + margin-left: 0.666666666666667em; + padding-left: 3.333333333333333em; + border-left: 1px solid #ccc; +} +.eventregattaraces_legend_item:first-of-type { + border: none; +} +.eventregattaraces_legend_item:before { + content: ' '; + position: absolute; + top: -0.2em; + left: 0.666666666666667em; + width: 1.733333333333333em; + height: 1.733333333333333em; + background-image: url("../images/race-features-icons.png"); + -webkit-background-size: auto 200%; + -moz-background-size: auto 200%; + background-size: auto 200%; +} +.eventregattaraces_legend_itemgps:before { + background-position: 0 0; +} +.eventregattaraces_legend_itemwind:before { + background-position: 33% 0; +} +.eventregattaraces_legend_itemvideo:before { + background-position: 66% 0; +} +.eventregattaraces_legend_itemaudio:before { + background-position: 100% 0; +} +.eventregattarace { + position: relative; + border-top: 1px solid #ccc; + padding: 0.666666666666667em 0; + zoom: 1; +} +.eventregattarace:before, +.eventregattarace:after { + content: ""; + display: table; +} +.eventregattarace:after { + clear: both; +} +.eventregattarace:last-of-type { + border-bottom: 1px solid #ccc; +} +.eventregattarace_fleetindicator { + position: absolute; + width: 0.333333333333333em; + height: 3.333333333333333em; + top: 0.333333333333333em; +} +.eventregattarace_details { + display: inline-block; + float: left; + height: 2.666666666666667em; + padding: 0 1.333333333333333em; + border-right: 1px solid #ccc; +} +.eventregattarace_details:last-of-type { + border-right: 1px solid #ccc; +} +.eventregattarace_name { + width: 22%; +} +.eventregattarace_nameshort { + float: left; + margin-right: 0.555555555555556em; + margin-top: -0.194444444444444em; + font-size: 2.4em; + color: #ccc; + font-weight: 700; +} +.eventregattarace_flag { + width: 7%; +} +.eventregattarace_flag img { + position: relative; + top: 50%; + -webkit-transform: translate(0, -50%); + -moz-transform: translate(0, -50%); + -o-transform: translate(0, -50%); + -ms-transform: translate(0, -50%); + transform: translate(0, -50%); +} +.eventregattarace_wind { + width: 14%; +} +.eventregattarace_wind_strength { + color: #008fcc; + font-weight: 600; +} +.eventregattarace_leader { + width: 27%; + border-right: none; +} +.eventregattarace_leader_name { + color: #008fcc; + font-weight: 600; +} +.eventregattarace_features, +.eventregattarace_legs { + width: 17%; + border-left: 1px solid #ccc; +} +.eventregattarace_legs_leg { + display: inline-block; + height: 10px; + width: 20px; + background-color: #ccc; +} +.eventregattarace_legs_leg[data-status*="finished"] { + background-color: #f0ab00; +} +.eventregattarace_legs_leg[data-status*="running"] { + background-color: #f00; +} +.eventregattarace_action { + float: right; + width: 9.666666666666666em; + text-align: center; + text-transform: uppercase; + cursor: pointer; +} +.eventregattarace_actionwatch { + background-color: #f00; +} +.eventregattarace_actionwatch:hover { + background-color: #a71724; +} +.eventregattarace_actiondisabled { + background-color: transparent; + color: #666; + cursor: initial; +} +.eventregattarace_actiondisabled:hover { + background-color: transparent; +} +[class*="event-regatta-race__feature__"] { + position: relative; + top: 50%; + -webkit-transform: translate(0, -50%); + -moz-transform: translate(0, -50%); + -o-transform: translate(0, -50%); + -ms-transform: translate(0, -50%); + transform: translate(0, -50%); + display: inline-block; + width: 1.733333333333333em; + height: 1.733333333333333em; + margin-right: 0.666666666666667em; + background-image: url("../images/race-features-icons.png"); + -webkit-background-size: auto 200%; + -moz-background-size: auto 200%; + background-size: auto 200%; +} +[class*="event-regatta-race__feature__"]:last-of-type { + margin-right: 0; +} +.eventregattarace_feature_gps { + background-position: 0 0; +} +.eventregattarace_feature_wind { + background-position: 33% 0; +} +.eventregattarace_feature_video { + background-position: 66% 0; +} +.eventregattarace_feature_audio { + background-position: 100% 0; +} +.eventregattarace_feature_unavailable { + background-position-y: 100%; +} 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 new file mode 100644 index 00000000000..446b9b702d0 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRaces.java @@ -0,0 +1,64 @@ +package com.sap.sailing.gwt.home.client.place.event.regattaraces; + +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.HTMLPanel; +import com.google.gwt.user.client.ui.Widget; +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.place.event.regattaschedule.EventRegattaScheduleResources; +import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sailing.gwt.ui.shared.RaceGroupDTO; +import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; +import com.sap.sse.gwt.client.player.Timer; + +public class EventRegattaRaces extends Composite { + private static EventRegattaRacesUiBinder uiBinder = GWT.create(EventRegattaRacesUiBinder.class); + + interface EventRegattaRacesUiBinder extends UiBinder { + } + + @UiField(provided=true) Regatta regatta; + @UiField HTMLPanel regattaSeriesNavigationPanel; + @UiField HTMLPanel regattaSeriesPanel; + + @SuppressWarnings("unused") + private final EventDTO event; + + private final Timer timerForClientServerOffset; + private final EventPageNavigator pageNavigator; + + public EventRegattaRaces(EventDTO event, Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { + this.event = event; + this.timerForClientServerOffset = timerForClientServerOffset; + this.pageNavigator = pageNavigator; + + regatta = new Regatta(event, false, timerForClientServerOffset, pageNavigator); + + EventRegattaScheduleResources.INSTANCE.css().ensureInjected(); + initWidget(uiBinder.createAndBindUi(this)); + } + + public void setRacesFromRaceGroup(RaceGroupDTO raceGroup, StrippedLeaderboardDTO leaderboard) { + regatta.setData(raceGroup, leaderboard, null); + } + +// public void setRacesFromRegatta(RegattaDTO regattaDTO, LeaderboardGroupDTO leaderboardGroup, StrippedLeaderboardDTO regattaLeaderboard) { +// regatta.setData(leaderboardGroup, regattaLeaderboard); +// +// for(SeriesDTO series: regattaDTO.series) { +// EventRegattaScheduleSeries eventRegattaScheduleSeries = new EventRegattaScheduleSeries(regattaLeaderboard, series, timerForClientServerOffset, pageNavigator); +// regattaSeriesPanel.add(eventRegattaScheduleSeries); +// } +// } +// +// public void setRacesFromFlexibleLeaderboard(LeaderboardGroupDTO leaderboardGroup, StrippedLeaderboardDTO flexibleLeaderboard) { +// regatta.setData(leaderboardGroup, flexibleLeaderboard); +// +// EventRegattaScheduleFleet eventRegattaScheduleFleet = new EventRegattaScheduleFleet(flexibleLeaderboard, timerForClientServerOffset, pageNavigator); +// regattaSeriesPanel.add(eventRegattaScheduleFleet); +// } +// +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRaces.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRaces.ui.xml new file mode 100644 index 00000000000..90d3d4f1987 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRaces.ui.xml @@ -0,0 +1,33 @@ + + + + + + + + + +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesFleet.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesFleet.ui.xml new file mode 100644 index 00000000000..4feb2dbf1ed --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesFleet.ui.xml @@ -0,0 +1,15 @@ + + + + + + + +
+
+
{fleet.name}
+
+
+
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 new file mode 100644 index 00000000000..8de234198b8 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesFleetGroup.ui.xml @@ -0,0 +1,13 @@ + + + + + + + +
+
+ + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesNavigationLink.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesNavigationLink.ui.xml new file mode 100644 index 00000000000..7367f452438 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesNavigationLink.ui.xml @@ -0,0 +1,11 @@ + + + + + + +{phase.name} + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesPhase.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesPhase.ui.xml new file mode 100644 index 00000000000..4213c3349ab --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesPhase.ui.xml @@ -0,0 +1,14 @@ + + + + + + + +
+

{phase.name}

+
+
+
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 new file mode 100644 index 00000000000..8b3e27a8949 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesRace.ui.xml @@ -0,0 +1,16 @@ + + + + + + + +
+
+
Race

{#if race.isFinished} {/if race.isFinished} +
{#if race.isTracked}{/if race.isTracked}{#if race.isTracked}{/if race.isTracked}{#if race.isFinished}{/if race.isFinished} +
+
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesRaceday.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesRaceday.ui.xml new file mode 100644 index 00000000000..ff02a89ee20 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesRaceday.ui.xml @@ -0,0 +1,14 @@ + + + + + + + +
{race.date}
+
+
+
+
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 new file mode 100644 index 00000000000..d40806a3699 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesResources.java @@ -0,0 +1,52 @@ +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; + +public interface EventRegattaRacesResources extends ClientBundle { + public static final EventRegattaRacesResources INSTANCE = GWT.create(EventRegattaRacesResources.class); + + @Source("com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRaces.css") + LocalCss css(); + + public interface LocalCss extends CssResource { + String regattanavigation(); + String eventregattaraces(); + String eventregattaraces_phase(); + String eventregattaraces_phase_name(); + String eventregattaraces_phase_day(); + String eventregattaraces_phase_races(); + String eventregattaraces_phase_fleets(); + String eventregattaraces_phase_fleet(); + String eventregattaraces_phase_fleet_name(); + String eventregattaraces_phase_fleet_count(); + String eventregattaraces_legend(); + String eventregattaraces_legend_item(); + String eventregattaraces_legend_itemgps(); + String eventregattaraces_legend_itemwind(); + String eventregattaraces_legend_itemvideo(); + String eventregattaraces_legend_itemaudio(); + String eventregattarace(); + String eventregattarace_fleetindicator(); + String eventregattarace_details(); + String eventregattarace_name(); + String eventregattarace_nameshort(); + String eventregattarace_flag(); + String eventregattarace_wind(); + String eventregattarace_wind_strength(); + String eventregattarace_leader(); + String eventregattarace_leader_name(); + String eventregattarace_features(); + String eventregattarace_legs(); + String eventregattarace_legs_leg(); + String eventregattarace_action(); + String eventregattarace_actionwatch(); + String eventregattarace_actiondisabled(); + String eventregattarace_feature_gps(); + String eventregattarace_feature_wind(); + String eventregattarace_feature_video(); + String eventregattarace_feature_audio(); + String eventregattarace_feature_unavailable(); + } +} 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 2fcd8813c5b..aae2c8b2de6 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 @@ -3217,6 +3217,12 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S public List getRegattaStructureForEvent(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 (CourseArea courseArea : event.getVenue().getCourseAreas()) { for (Leaderboard leaderboard : getService().getLeaderboards().values()) { @@ -3224,6 +3230,9 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S RaceGroupDTO raceGroup = new RaceGroupDTO(leaderboard.getName()); raceGroup.courseAreaIdAsString = courseArea.getId().toString(); raceGroup.displayName = getRegattaNameFromLeaderboard(leaderboard); + if(leaderboardWithLeaderboardGroups.containsKey(leaderboard)) { + raceGroup.leaderboardGroupName = leaderboardWithLeaderboardGroups.get(leaderboard).getName(); + } if (leaderboard instanceof RegattaLeaderboard) { RegattaLeaderboard regattaLeaderboard = (RegattaLeaderboard) leaderboard; for (Series series : regattaLeaderboard.getRegatta().getSeries()) { 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 ce93db3b051..72168c356c9 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 @@ -11,6 +11,7 @@ public class RaceGroupDTO extends NamedDTO implements IsSerializable { private static final long serialVersionUID = 8432572954520767329L; public String courseAreaIdAsString; public String displayName; + public String leaderboardGroupName; private List series = new ArrayList(); public RaceGroupDTO() {} From 22d0d5a82d5faaf696ca1f96677d04b8ff9df6c9 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 14 Jul 2014 21:45:35 +0200 Subject: [PATCH 066/323] sort Igtimi WebSocket URLs to the top that don't do wss:// nor port 443; Jetty can't do wss:// --- .../impl/IgtimiConnectionFactoryImpl.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiConnectionFactoryImpl.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiConnectionFactoryImpl.java index 330a5d529a8..14760fad8e5 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiConnectionFactoryImpl.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiConnectionFactoryImpl.java @@ -7,6 +7,8 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -498,6 +500,15 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory { URI uri = new URI((String) serverUrl); result.add(uri); } + // sort those to the front that don't do port 443 nor wss:// + Collections.sort(result, new Comparator() { + @Override + public int compare(URI o1, URI o2) { + int o1Score = (o1.toString().contains("443") ? 1 : 0) + (o1.toString().contains("wss") ? 1 : 0); + int o2Score = (o2.toString().contains("443") ? 1 : 0) + (o2.toString().contains("wss") ? 1 : 0); + return o1Score - o2Score; + } + }); return result; } From 3a063f0f94889270ab05f884eac288b0fa09e23a Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 14 Jul 2014 22:12:34 +0200 Subject: [PATCH 067/323] replaced tabs by spaces, added some Javadoc --- .../sailing/gwt/ui/client/media/MediaSelector.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) 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 869f12db900..5c46c79d550 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 @@ -47,6 +47,12 @@ 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; +/** + * 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 { @@ -82,7 +88,7 @@ public class MediaSelector implements PlayStateListener, TimeListener, this.stringMessages = stringMessages; this.errorReporter = errorReporter; this.user = user; - this.autoSelectMedia = autoSelectMedia; + this.autoSelectMedia = autoSelectMedia; Window.addCloseHandler(this); Window.addWindowClosingHandler(this); @@ -91,7 +97,6 @@ public class MediaSelector implements PlayStateListener, TimeListener, manageMediaButton = new Button(); manageMediaButton.addClickHandler(new ClickHandler() { - @Override public void onClick(ClickEvent event) { if (mediaSelectionDialog.isShowing()) { @@ -288,7 +293,7 @@ public class MediaSelector implements PlayStateListener, TimeListener, toggleMediaButton.setValue(autoSelectMedia); if (autoSelectMedia) { - playDefault(); + playDefault(); } } From 426f9cf982d70e4432732944a967567d2af9ecba Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 15 Jul 2014 00:10:44 +0200 Subject: [PATCH 068/323] use NamedReentrantReadWriteLock in MediaLibrary --- .../impl/NamedReentrantReadWriteLock.java | 17 ++++++ .../sap/sailing/server/impl/MediaLibrary.java | 53 +++++++++---------- 2 files changed, 42 insertions(+), 28 deletions(-) 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 584619fd706..d465b4de2f3 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 @@ -11,6 +11,23 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import com.sap.sse.common.Named; +/** + * To be used in conjunction with {@link LockUtil}. Provides additional naming + * for locks which is helpful in the tracing that {@link LockUtil} provides. + * Use as in + *
+ *      NamedReentrantReadWriteLock lock = ...;
+ *      LockUtil.lockForRead(lock);
+ *      try {
+ *          // do something while holding the lock
+ *      } finally {
+ *          LockUtil.unlockAfterRead(lock);
+ *      }
+ * 
+ * + * @author Axel Uhl (d043530) + * + */ public class NamedReentrantReadWriteLock extends ReentrantReadWriteLock implements Named { private static final long serialVersionUID = 2906084982209339774L; private final String name; diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/MediaLibrary.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/MediaLibrary.java index d244702e18a..cae8f574886 100644 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/MediaLibrary.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/MediaLibrary.java @@ -15,12 +15,11 @@ import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; import com.sap.sailing.domain.common.media.MediaTrack; import com.sap.sailing.domain.common.media.MediaUtil; +import com.sap.sailing.util.impl.LockUtil; +import com.sap.sailing.util.impl.NamedReentrantReadWriteLock; class MediaLibrary { @@ -68,9 +67,7 @@ class MediaLibrary { */ private final ConcurrentMap> cacheByInterval = new ConcurrentHashMap>(); - private final ReadWriteLock lock = new ReentrantReadWriteLock(); - private final Lock readLock = lock.readLock(); - private final Lock writeLock = lock.writeLock(); + private final NamedReentrantReadWriteLock lock = new NamedReentrantReadWriteLock(MediaLibrary.class.getName(), /* fair */ false); // /** // * Sort in reverse order of start time! For equal start times compare dbId to distinguish different instances. @@ -112,7 +109,7 @@ class MediaLibrary { if (startTime != null) { Interval interval = new Interval(startTime, endTime); - readLock.lock(); + LockUtil.lockForRead(lock); try { Set cachedMediaTracks = cacheByInterval.get(interval); if (cachedMediaTracks == null) { @@ -134,7 +131,7 @@ class MediaLibrary { } } finally { - readLock.unlock(); + LockUtil.unlockAfterRead(lock); } } // else @@ -152,65 +149,65 @@ class MediaLibrary { } void addMediaTracks(Collection mediaTracks) { - writeLock.lock(); + LockUtil.lockForWrite(lock); try { for (MediaTrack mediaTrack : mediaTracks) { this.mediaTracksByDbId.put(mediaTrack, mediaTrack); updateCache_Add(mediaTrack); } } finally { - writeLock.unlock(); + LockUtil.unlockAfterWrite(lock); } } void deleteMediaTrack(MediaTrack mediaTrack) { - writeLock.lock(); + LockUtil.lockForWrite(lock); try { MediaTrack deletedMediaTrack = mediaTracksByDbId.remove(mediaTrack); updateCache_Remove(deletedMediaTrack); } finally { - writeLock.unlock(); + LockUtil.unlockAfterWrite(lock); } } void titleChanged(MediaTrack changedMediaTrack) { - writeLock.lock(); + LockUtil.lockForWrite(lock); try { MediaTrack mediaTrack = mediaTracksByDbId.get(changedMediaTrack); if (mediaTrack != null) { mediaTrack.title = changedMediaTrack.title; } } finally { - writeLock.unlock(); + LockUtil.unlockAfterWrite(lock); } } void urlChanged(MediaTrack changedMediaTrack) { - writeLock.lock(); + LockUtil.lockForWrite(lock); try { MediaTrack mediaTrack = mediaTracksByDbId.get(changedMediaTrack); if (mediaTrack != null) { mediaTrack.url = changedMediaTrack.url; } } finally { - writeLock.unlock(); + LockUtil.unlockAfterWrite(lock); } } void mimeTypeChanged(MediaTrack changedMediaTrack) { - writeLock.lock(); + LockUtil.lockForWrite(lock); try { MediaTrack mediaTrack = mediaTracksByDbId.get(changedMediaTrack); if (mediaTrack != null) { mediaTrack.mimeType = changedMediaTrack.mimeType; } } finally { - writeLock.unlock(); + LockUtil.unlockAfterWrite(lock); } } void startTimeChanged(MediaTrack changedMediaTrack) { - writeLock.lock(); + LockUtil.lockForWrite(lock); try { MediaTrack mediaTrack = mediaTracksByDbId.get(changedMediaTrack); if (mediaTrack != null) { @@ -218,12 +215,12 @@ class MediaLibrary { updateCache_Change(mediaTrack); } } finally { - writeLock.unlock(); + LockUtil.unlockAfterWrite(lock); } } void durationChanged(MediaTrack changedMediaTrack) { - writeLock.lock(); + LockUtil.lockForWrite(lock); try { MediaTrack mediaTrack = mediaTracksByDbId.get(changedMediaTrack); if (mediaTrack != null) { @@ -231,7 +228,7 @@ class MediaLibrary { updateCache_Change(mediaTrack); } } finally { - writeLock.unlock(); + LockUtil.unlockAfterWrite(lock); } } @@ -273,20 +270,20 @@ class MediaLibrary { * Returns a non-live copy of the media tracks */ Collection allTracks() { - readLock.lock(); + LockUtil.lockForRead(lock); try { return new ArrayList(mediaTracksByDbId.values()); } finally { - readLock.unlock(); + LockUtil.unlockAfterRead(lock); } } void serialize(ObjectOutputStream stream) throws IOException { - readLock.lock(); + LockUtil.lockForRead(lock); try { stream.writeObject(new ArrayList(mediaTracksByDbId.values())); } finally { - readLock.unlock(); + LockUtil.unlockAfterRead(lock); } } @@ -296,12 +293,12 @@ class MediaLibrary { } public void clear() { - writeLock.lock(); + LockUtil.lockForWrite(lock); try { mediaTracksByDbId.clear(); cacheByInterval.clear(); } finally { - writeLock.unlock(); + LockUtil.unlockAfterWrite(lock); } } From a34e36848cd8aca6b0c9888b54e88293838873fa Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 15 Jul 2014 00:26:47 +0200 Subject: [PATCH 069/323] updated link to interval tree --- .../src/com/sap/sailing/server/impl/MediaLibrary.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/MediaLibrary.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/MediaLibrary.java index cae8f574886..854ba105c03 100644 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/MediaLibrary.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/MediaLibrary.java @@ -91,7 +91,7 @@ class MediaLibrary { * performance gain. * * Actually, efficient lookup of overlapping intervals is supposed to be performed using an interval tree, e.g. - * http://thekevindolan.com/2010/02/interval-tree/ + * http://en.wikipedia.org/wiki/Interval_tree. * * However, considering the expected low number of media entries and the expected high rate of cache hits doesn't * justify providing a dedicated interval tree implementation (given that there's none readily available). From 6ee1305c81531ded25db77627549db3ddfeb3b09 Mon Sep 17 00:00:00 2001 From: Jens Rommel Date: Tue, 15 Jul 2014 09:16:55 +0200 Subject: [PATCH 070/323] Removed a confusingly commented line of code. --- .../com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 c619c0f9a16..3da1342b473 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 @@ -271,9 +271,15 @@ public class RaceBoardPanel extends SimplePanel implements RegattasDisplayer, Ra 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()); -// raceTimesInfoProvider.addRaceTimesInfoProviderListener(mediaSelector); timer.addPlayStateListener(mediaSelector); timer.addTimeListener(mediaSelector); mediaService.getMediaTracksForRace(selectedRaceIdentifier, mediaSelector); From 09b4a30a9c5da8fa9298ba20891a98a23c4d58bb Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 15 Jul 2014 15:30:40 +0200 Subject: [PATCH 071/323] fixed initial-load queue name, making it unique --- .../server/replication/impl/ReplicationServlet.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServlet.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServlet.java index 037abb28493..9e9cb27fbcb 100755 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServlet.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServlet.java @@ -7,6 +7,7 @@ import java.io.PrintWriter; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Arrays; +import java.util.Date; import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; @@ -82,9 +83,11 @@ public class ReplicationServlet extends SailingServerHttpServlet { Connection rabbitConnection = new ConnectionFactory().newConnection(); Channel channel = rabbitConnection.createChannel(); RabbitOutputStream ros = new RabbitOutputStream(INITIAL_LOAD_PACKAGE_SIZE, channel, - /* queueName */ "initialLoad-for-"+req.getRemoteHost(), /* syncAfterTimeout */ false); + /* queueName */ "initialLoad-for-"+req.getRemoteHost()+"@"+new Date()+"-"+UUID.randomUUID(), + /* syncAfterTimeout */ false); PrintWriter br = new PrintWriter(new OutputStreamWriter(resp.getOutputStream())); - br.println(ros.getQueueName().getBytes()); + br.println(ros.getQueueName()); + br.flush(); final GZIPOutputStream gzipOutputStream = new GZIPOutputStream(new CountingOutputStream( ros, /* log every megabyte */1024l * 1024l, Level.INFO, "HTTP output for initial load for " + req.getRemoteHost())); From c7876e3987e2eb956f772149b8d4e559757ed490 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 15 Jul 2014 15:31:11 +0200 Subject: [PATCH 072/323] fix for bug 2065, making writeObject(...) synchronized on LeaderboardGroup, protecting against concurrent modifications of leaderboard list --- .../domain/leaderboard/impl/LeaderboardGroupImpl.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 0133d018fe9..2e654c8ead7 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 @@ -1,5 +1,7 @@ package com.sap.sailing.domain.leaderboard.impl; +import java.io.IOException; +import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -32,6 +34,10 @@ public class LeaderboardGroupImpl extends LeaderboardGroupBaseImpl implements Le public LeaderboardGroupImpl(String name, String description, boolean displayGroupsInReverseOrder, List leaderboards) { this(UUID.randomUUID(), name, description, displayGroupsInReverseOrder, leaderboards); } + + private synchronized void writeObject(ObjectOutputStream oos) throws IOException { + oos.defaultWriteObject(); + } /** * Use this constructor when loading or deserializing a leaderboard group and the ID is known and is provided to the constructor. From 2dd7ad0a7913013cfe8adadaefa290f00e7b138c Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 15 Jul 2014 17:06:17 +0200 Subject: [PATCH 073/323] improved logging for sending initial load --- .../server/replication/impl/CountingOutputStream.java | 6 ++++++ .../server/replication/impl/ReplicationServlet.java | 7 ++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/CountingOutputStream.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/CountingOutputStream.java index 3c76db0fb33..bcac23e6ee6 100755 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/CountingOutputStream.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/CountingOutputStream.java @@ -51,4 +51,10 @@ public class CountingOutputStream extends FilterOutputStream { super.write(b, off, len); count(len); } + + @Override + public void close() throws IOException { + super.close(); + log(); + } } diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServlet.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServlet.java index 9e9cb27fbcb..2e63f0e8101 100755 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServlet.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServlet.java @@ -88,9 +88,10 @@ public class ReplicationServlet extends SailingServerHttpServlet { PrintWriter br = new PrintWriter(new OutputStreamWriter(resp.getOutputStream())); br.println(ros.getQueueName()); br.flush(); - final GZIPOutputStream gzipOutputStream = new GZIPOutputStream(new CountingOutputStream( + final CountingOutputStream countingOutputStream = new CountingOutputStream( ros, /* log every megabyte */1024l * 1024l, Level.INFO, - "HTTP output for initial load for " + req.getRemoteHost())); + "HTTP output for initial load for " + req.getRemoteHost()); + final GZIPOutputStream gzipOutputStream = new GZIPOutputStream(countingOutputStream); ObjectOutputStream oos = new ObjectOutputStream(gzipOutputStream); try { getService().serializeForInitialReplication(oos); @@ -101,7 +102,7 @@ public class ReplicationServlet extends SailingServerHttpServlet { resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); e.printStackTrace(resp.getWriter()); } - ros.close(); + countingOutputStream.close(); break; default: resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Action " + action + " not understood. Must be one of " From 43c2bef7e0937bb206059071dbe8c42851768a3a Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 15 Jul 2014 17:06:37 +0200 Subject: [PATCH 074/323] improved logging of picking the right Igtimi WebSocket server --- .../domain/igtimiadapter/impl/IgtimiConnectionFactoryImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiConnectionFactoryImpl.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiConnectionFactoryImpl.java index 14760fad8e5..77fa0d454e2 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiConnectionFactoryImpl.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiConnectionFactoryImpl.java @@ -509,6 +509,7 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory { return o1Score - o2Score; } }); + logger.info("Trying Igtimi WebSocket servers in the following order: "+result); return result; } From 1164b573fea6d8aa69c14e0f6b15bb1e4f868fc4 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 15 Jul 2014 17:06:37 +0200 Subject: [PATCH 075/323] improved logging of picking the right Igtimi WebSocket server --- .../domain/igtimiadapter/impl/IgtimiConnectionFactoryImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiConnectionFactoryImpl.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiConnectionFactoryImpl.java index 14760fad8e5..77fa0d454e2 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiConnectionFactoryImpl.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiConnectionFactoryImpl.java @@ -509,6 +509,7 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory { return o1Score - o2Score; } }); + logger.info("Trying Igtimi WebSocket servers in the following order: "+result); return result; } From 76ebd2a3fad78a79de755db5982e2759a3cb81f6 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 15 Jul 2014 15:31:11 +0200 Subject: [PATCH 076/323] fix for bug 2065, making writeObject(...) synchronized on LeaderboardGroup, protecting against concurrent modifications of leaderboard list --- .../domain/leaderboard/impl/LeaderboardGroupImpl.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 0133d018fe9..2e654c8ead7 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 @@ -1,5 +1,7 @@ package com.sap.sailing.domain.leaderboard.impl; +import java.io.IOException; +import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -32,6 +34,10 @@ public class LeaderboardGroupImpl extends LeaderboardGroupBaseImpl implements Le public LeaderboardGroupImpl(String name, String description, boolean displayGroupsInReverseOrder, List leaderboards) { this(UUID.randomUUID(), name, description, displayGroupsInReverseOrder, leaderboards); } + + private synchronized void writeObject(ObjectOutputStream oos) throws IOException { + oos.defaultWriteObject(); + } /** * Use this constructor when loading or deserializing a leaderboard group and the ID is known and is provided to the constructor. From 1a5afddfd0e6798abaed29de48932a4c83ce1ad1 Mon Sep 17 00:00:00 2001 From: fmittag Date: Tue, 15 Jul 2014 18:54:18 +0200 Subject: [PATCH 077/323] draft version of new regatta detail page design --- .../images/home/fleet-icon.png | Bin 0 -> 1132 bytes .../images/home/race-features-icons.png | Bin 0 -> 4618 bytes .../client/place/event/regatta/Regatta.java | 9 +- .../client/place/event/regatta/Regatta.ui.xml | 4 +- .../event/regatta/RegattaCompetitor.ui.xml | 8 +- .../place/event/regatta/RegattaPhase.java | 22 +-- .../place/event/regatta/RegattaPhase.ui.xml | 5 +- .../place/event/regatta/RegattaPhaseRace.java | 16 ++- .../event/regatta/RegattaPhaseRace.ui.xml | 4 +- .../event/regattaraces/EventRegattaRaces.css | 37 ++++- .../event/regattaraces/EventRegattaRaces.java | 35 ++--- .../regattaraces/EventRegattaRaces.ui.xml | 4 +- .../regattaraces/EventRegattaRacesFleet.java | 34 +++++ .../EventRegattaRacesFleet.ui.xml | 11 +- .../EventRegattaRacesFleetGroup.java | 59 ++++++++ .../EventRegattaRacesFleetGroup.ui.xml | 8 +- .../EventRegattaRacesNavigationLink.java | 26 ++++ .../EventRegattaRacesNavigationLink.ui.xml | 4 +- .../regattaraces/EventRegattaRacesPhase.java | 58 ++++++++ .../EventRegattaRacesPhase.ui.xml | 5 +- .../regattaraces/EventRegattaRacesRace.java | 63 ++++++++ .../regattaraces/EventRegattaRacesRace.ui.xml | 9 +- .../EventRegattaRacesRaceday.java | 34 +++++ .../EventRegattaRacesRaceday.ui.xml | 7 +- .../EventRegattaRacesResources.java | 2 + .../regattaschedule/EventRegattaSchedule.css | 135 ------------------ .../regattaschedule/EventRegattaSchedule.java | 60 -------- .../EventRegattaSchedule.ui.xml | 19 --- .../EventRegattaScheduleFleet.java | 85 ----------- .../EventRegattaScheduleFleet.ui.xml | 14 -- .../EventRegattaScheduleRace.java | 79 ---------- .../EventRegattaScheduleRace.ui.xml | 14 -- .../EventRegattaScheduleResources.java | 26 ---- .../EventRegattaScheduleSeries.java | 39 ----- .../EventRegattaScheduleSeries.ui.xml | 15 -- .../recent/EventsOverviewRecentYear.java | 2 +- .../gwt/ui/server/SailingServiceImpl.java | 23 +-- .../gwt/ui/shared/RaceGroupSeriesDTO.java | 21 ++- .../com/sap/sailing/gwt/home/main.css | 6 +- 39 files changed, 424 insertions(+), 578 deletions(-) create mode 100644 java/com.sap.sailing.gwt.ui/images/home/fleet-icon.png create mode 100644 java/com.sap.sailing.gwt.ui/images/home/race-features-icons.png create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesFleet.java create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesFleetGroup.java create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesNavigationLink.java create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesPhase.java create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesRace.java create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesRaceday.java delete mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.css delete mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.java delete mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.ui.xml delete mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java delete mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.ui.xml delete mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.java delete mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.ui.xml delete mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleResources.java delete mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.java delete mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.ui.xml diff --git a/java/com.sap.sailing.gwt.ui/images/home/fleet-icon.png b/java/com.sap.sailing.gwt.ui/images/home/fleet-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a8d5e1952e923d1c69b3e91efbd49d49afb6e36a GIT binary patch literal 1132 zcmaJ=U1$_X9G_&PH9<>kf*1`r?1=%%?d;xHdP~mi<@TZnZU~oy#y+&&>`pFAvOC+| zN$wIKjN1BDXadDAd{OCRDd|HC#fSKimx53PeNl+zUJ5N@)R#a*uCpcJ8ZEi=V` z64yk{&ERqC)U1bxX9o+$?1Ygt`Mwjdw=6RP8&e&Y?McU%%PD@7S7ztHjCi=|LMKxE zyQD_6A($Z^hH*g*8)8C&iI^b8#AGbl4I`o?A}NBzp0Fg!@g7-};MT{pXr4JH=hcC& zSnQVK$0>DXgi587P>Kq~DIYB9Soj2>UY*)yrYW57ih{>>HltQj0h+VAMy+ z6ix9g(|0AZVP-fkNKn%a%Ly!-2u+i7j!$*Rz&SO=GY-MB zOgRzBX0lQ?E{btUlCrUMe_BjOqs*L6^v4DwHLOaEDH}VqhBbGviMrT847ST6tJt%q zu{q!o8*UaYTlI5^*5%v6n)P#u)x{!~3<^g3-l%IPrk~)rEnIflmOgfv-aRJlPj7s6 zl|71;oSH6_pN+0pt9{&u51W@?bnF4gIxBxElfNrX53v$zjea*VccAq$oXOlQa0_;k ztGraYwI3Sb^Tx-^-`~5m+GYS@>EhncKA75n9Arc7hu2T;T2UU&AN!>}mpT}FaQE9Q z%6wyGdQuQK=9?FP1-m12N(avkD9iZ`rGJEFkLQMgzUKn{Ryd^TL{^bhqG(_y75rdxDA!tCy?G=btW$e>7CO jsyXh@^S9bepv*OZzu%lCUDu1xgZH1y4yr%)e{tboFA#Ba literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/home/race-features-icons.png b/java/com.sap.sailing.gwt.ui/images/home/race-features-icons.png new file mode 100644 index 0000000000000000000000000000000000000000..a8129e7dfe59671a49deed35c5903e508d215434 GIT binary patch literal 4618 zcmbVQ2UJs8n5H*@1Yszlh0sJuC6u6)Kqx^%2LTZcBtV1^2uSa}iIJid9S{K>ML= z4bGV}_H%BMkO1e|lbN)KGss0)dPKO;{3D`>bTSu~MDrzs@l;{}*_liv#e^}*2f4U- z11YW^5gr6PBR?7yM*N0>MN>mL+FV=*O`}7JenI32urE1)5@HNlYVU%8DI{aaVLbwZ z5NbgVq}asL$u6<>u70sWei#zO)C7Dm+K6L-N{%3cqp86m;YQKMkneVlIP-5}I0XD1 z5)ot!`4N-{!4Yghqm#jUFa*>Op^pUXqhUxi0)xirfKdn}0**w%5e85s!bs1+2!RCu zbwN1Z=p=t5XPnhvzBnsmNMJ-ns1Y0<6%_@G(uL9J0dOP+gMlMZa1;v4K|sS}LL!LK z(2#J|pB8ZBa6dXFG=f440e`bd^rfAQFotj<{gDDSltB2~a7g%Hh2oS99!(5|BVh_Y91?-Y8ACX4U?d932%%@7k4B=ZdL~Bs_u`;z$1Jm%{P;m#+E0>Ka+l z$;1d6-IYcQ{@DSJfwTx(cpxnlY~i8@LxKrJKT60q$+r#qkuQ!+r$mxTR&*K_{Jp|P zlz)ie|IYk}F6sYMH8>|2__y}>FYWSE#Oc9r)4!Xav-tb?kV80UhR!)0HYcAkxwym) z@i?q&^ox&2se!K6GA4;YpvZyiAhR3c%y3X0$zN(yk z(A?S_O9?M8QK&#tFshW|3>~SOF-Jn6*>)=9I+#!7&4IaB;mbytaMP&8 zDf|#`)x1SC3iVLZ>0wf_9Zkem9=8RnyMLmmm z>Z=`-U%hMJmJ8F=HqmkAjObBc1?Gv}Ucnouc{AtdOIon19Xi?81-i%=ONVbB!h13m zN5LV)PSF%Mddr!uoy@}Nk`4_}FSN>Z<=CPaQ^H0Fd;H(WD!!UgvNjewiZKH0DHDM)Vcvs{JSYqLo&%)OU zQNgQez0dBX$=x`oa!_UOYOj&5M(|}O+&5X03V6QV$j(<#8acoJ@jUlnM_=FEM2&{D zeg9tk=!m~NKBH!~-K2)3_Im2%iDT-KPs{ZAL7UvC6qI%=D^KPtJT2+4OloNX)%{}j zX7Mt9Qv4Kq=wM;b=`>toS6fC!=K;!%T(a*BUu7NMM^wd0R<0Yd756^;PEPvvj_KHr zVrHDwZ%Z$^sVmt>Y$MH72dDIodu+EBcJ(U+4PW%U^~>g(vUmP=nOQHh)uf-lyfi}= z;D%q$XsX1Y$}Upr9VZwcM)x0l-W#Q;df!TYE=TqW#r=4~s&Ge%sm==W+O3&^+Y-)6miMQZ2U_nwiLw^I1jG7IR3Ja@9IUEv zUrQJ{Uo)W1jZM~Lm~4FlQZSRU9lahJLZVAbqrdt!7G5Lry+3YOYlJb>5JM`2)mo?K zZ+|hhEk9wlhet}54%XTcP1x{P(6Px4x3xDUxl?PG6MA@N^gUx|HFLm9kwukjwNJ#q z3LLw~+6sTRt~jLWeVqbOd@D5T0-cco>23aU-a?t=^Hx#zm0U!&LhH(x;1q@1qr!&_ ze2^EUAi})J`@720xq{X`uSOAU^3PSnheCoi1Ni5)Eqi#!ks6Cx(KXOD>33;8=`pNpaRlB{2qq4>Ro)|kR0n-+FYFVaC=&8OmY}{k`?m_Z!=~SQLdN`49W?dQ9lB}l6rj>3(5$RW$9U-7d#`}4% zw>Fb6KWRb=w71GBSqJ8KT7u4uy?RiZ|+|Z1CH^A}T9djJ)(d3|1DR#u6 zZm&IiM;cFWVQzHTlsj%1^XTH9WY!P)`~lgJKLu{qAb|RHep@BhEp3ae8Yr>X+7?Il zCy9XK06WmNWn|~C_!#AJ1&@2A#>fPfhdet;skkdbV-JlpOFFtzw9y(kf4Ap+)Xg^8 zg{3n|u|s34VEm_ZnfN_!QD;0Sdg6!gsQl4*pcdx-B}HTKRepaMaIaLfKs$fyndAq1 z6C+bqvJC2`t=m#u-6MLiRuxIjZxd%#y86_tY>Lj?e`*sZsH$S>s;A6^-Uvuz*JC>f z-G*vq)!pU>r;cgW32X5&wb>sdRPl~Q=kq1ln_M9NHv&baspi0xAVbSWC;SQ&4-vxh z!HQ}gMOt&DkJR4Zc$JeL!w+NKT@c5m387N1VFgFQJSjjN7$|w@!TzmPk#sO94HSrw3T{C{B6xd=mp%r!on?cdQoQc{C+G=Au*kL^Iu|=J4)?9uAlB z{J;}>rbqU9A;cFaPMy#?p=WUO&%D0sK!#Lzn)33Um85-Jc&1X|(9GFXQuMt@L30MX zds^0x;T~gpf3E4Hi91P-p<>7HeWgGMy8@^mi!yxudAQ0+YehyqY5$fr6Ts*Irc`0` z?e06o19uFMw-#6@67l6pWyB9kEv4CSg*W4QGD*qQL9<_S-a0i znL*XqNuF)hV5-L9YLmow5>?Vgy#2#3+4A$+wo@)EzX5v{Q*lRy+o7g=+;{rtFV>gda6X^!lh{nQ3j`x*@~m#^2{5z~P_pAL@BVBM$N6x2*i-55LYggxP4U{QEn ztZ8K1<4szNG*#k-_dv43RP*cJ=S_C~-0b46A%>D3Q^sWVQ9Ty&;%@Bn2c7x1k=?Az zFWq%-Cs0*dZW^LjzO$Sr?uzGpMPOY2lO**3_NU}o*D@bK;4Bno(x`#F5o#Wr^|aK# zGRhg6TS<pq+VPu_YLFk)gl55_b->plYX>Fk7F9zNas%ArUWP}_FS zg}xca=wC9p6WSc5i947&XnOB5rBKGgeI0>HCa!FZ&4u)gN!Ez8+$@mo=U(%+SlbeR z(4R8#Vi^IF0X*t$LjO9lCR66OfI(lo%~Sa$EzikRGTTt@fnb}Nr0&qY3nfy=1a`{C zvkiqulSqe8+NBK?5#paBeS_2QOFXj4e=MC)ZK1_X@qZkpHZN+z%zB|WFc*u-&C%nF zPjoD)soUb4dZ;Y@XX(U?Q2&=gzx@tjEk*l#ADwSk&5}5WX#OxLyPlvlLeSne3AA^f zElCDN}?n=@` zz`J&tNsPB3pcFI1@FdTV3@z?Eb4lt2i?Q^zo$0Xc9F&!m*7$mP7ZbcAJ}23*{uj%Q zElfG39lM8F!_JoM>3%m_iS=%SGlXUj z(fZT+kWnJ+w0G<)TLXm;hK0D<-J!()?oJo5dwDMRB>nwhCYeJ>WuEh9{ zg?|YLu)@bv(KdZ&O4{x-u(_8EWbz73Hbhxf%Dg6iu`;Y@!~7EIFOynFcnb3wUBnM} zx`EXi;dg&~hMjE;Dp3ui^;BjXHn4;06c_849yesvwVy^xUgtZl|2{Us`R1m29?mX* zpWTTB0OPKX5Vqa{eI778I(@fHkZi(x3d&t1TGwGZdvs6|^Dt9?>5s8scGzRF&;Ntv z0RJn?{ejNDvzPNVW~r3+B>3N1&VQf;`YG@dOvtUgDuJMI9`#Ch-`Wus$T{+qzxV+7 zou-Sh=1M}I8s-H%&PR=KWk)H+6B5Mh9snnv**Rs!@GR00Bb{~KLOhO2;NO-9(6_y# z0!vK!PjzN}mfqeh^%<_|0Gtr7KYsIY`|m*VnWOR-AvW3@vhLeo1R6$4fn{!F$(ETp z-wj)63wuCFZp-2kh`)0skDi^X?6wgk)3QM!-^SR2RJav!B%Tk$9z z+I%jYsW4TVsQgyI*>hA(P>%X#@a#@m)~&n4^>2S?@Rs(t2J_>o{{gm;^uYiC literal 0 HcmV?d00001 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 5396cec31d6..2a2302b7a28 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 @@ -34,13 +34,15 @@ public class Regatta extends Composite { private final EventPageNavigator pageNavigator; private StrippedLeaderboardDTO leaderboard; private RaceGroupDTO raceGroup; + + @SuppressWarnings("unused") private LeaderboardGroupDTO leaderboardGroup; @UiField SpanElement regattaName1; @UiField SpanElement regattaName2; - @UiField SpanElement leaderboardGroupName; + @UiField DivElement leaderboardGroupName; @UiField SpanElement scheduledStart; - @UiField HTMLPanel regattaPhasesPanel; + @UiField DivElement regattaPhasesPanel; @UiField SpanElement competitorsCount; @UiField SpanElement racesCount; @UiField SpanElement trackedRacesCount; @@ -100,6 +102,7 @@ public class Regatta extends Composite { regattaName2.setInnerText(regattaDisplayName); if(raceGroup.leaderboardGroupName != null) { leaderboardGroupName.setInnerText(raceGroup.leaderboardGroupName); + leaderboardGroupName.setAttribute("data-labeltype", "group1"); } else { leaderboardGroupName.getStyle().setDisplay(Display.NONE); } @@ -119,7 +122,7 @@ public class Regatta extends Composite { private void setRegattaProgress() { for(RaceGroupSeriesDTO series: raceGroup.getSeries()) { RegattaPhase regattaPhase = new RegattaPhase(series); - regattaPhasesPanel.add(regattaPhase); + regattaPhasesPanel.appendChild(regattaPhase.getElement()); } } 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 648f8e1858b..c991e11dabe 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 @@ -36,14 +36,14 @@
 
 
-
+
- +
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaCompetitor.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaCompetitor.ui.xml index 07fecdab299..203c3f81258 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaCompetitor.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaCompetitor.ui.xml @@ -5,8 +5,12 @@ - - + + .panel { + display: inline-block; + } + +
.  -  - 
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 b6afbda1258..1b8e66838d0 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,33 +1,37 @@ package com.sap.sailing.gwt.home.client.place.event.regatta; 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.Composite; -import com.google.gwt.user.client.ui.HTMLPanel; -import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.ui.shared.RaceGroupSeriesDTO; -public class RegattaPhase extends Composite { +public class RegattaPhase { private static RegattaPhaseUiBinder uiBinder = GWT.create(RegattaPhaseUiBinder.class); - interface RegattaPhaseUiBinder extends UiBinder { + interface RegattaPhaseUiBinder extends UiBinder { } @UiField SpanElement phaseName; - @UiField HTMLPanel phaseRacesPanel; + @UiField DivElement phaseRacesPanel; + private DivElement root; + public RegattaPhase(RaceGroupSeriesDTO series) { - RegattaResources.INSTANCE.css().ensureInjected(); - initWidget(uiBinder.createAndBindUi(this)); + root = uiBinder.createAndBindUi(this); phaseName.setInnerText(series.getName()); for(int i = 0; i < 5; i++) { RegattaPhaseRace race = new RegattaPhaseRace(); - phaseRacesPanel.add(race); + phaseRacesPanel.appendChild(race.getElement()); } } + + public Element getElement() { + return root; + } } 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 360a1db382d..5378ec65da4 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 @@ -5,11 +5,8 @@ - -
- +
-
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 1011a49b0b8..a8fa6667745 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 @@ -2,23 +2,29 @@ package com.sap.sailing.gwt.home.client.place.event.regatta; 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.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; -public class RegattaPhaseRace extends Composite { +public class RegattaPhaseRace { private static RegattaPhaseRaceUiBinder uiBinder = GWT.create(RegattaPhaseRaceUiBinder.class); - interface RegattaPhaseRaceUiBinder extends UiBinder { + interface RegattaPhaseRaceUiBinder extends UiBinder { } @UiField DivElement raceStatus; + private DivElement root; + public RegattaPhaseRace() { RegattaResources.INSTANCE.css().ensureInjected(); - initWidget(uiBinder.createAndBindUi(this)); + root = uiBinder.createAndBindUi(this); raceStatus.setAttribute("data-status", "raceStatus"); } + + public Element getElement() { + return root; + } + } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaPhaseRace.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaPhaseRace.ui.xml index 501d86c1634..4b3294fca94 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaPhaseRace.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/RegattaPhaseRace.ui.xml @@ -5,7 +5,5 @@ - -
-
+
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 168609bf9e0..795e259af48 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 @@ -48,7 +48,7 @@ height: 3.333333333333333em; padding: 0.333333333333333em; background-color: #008fcc; - background-image: url("../images/fleet-icon.png"); + background-image: url("images/home/fleet-icon.png"); background-position: center 4px; background-repeat: no-repeat; font-size: 12px; @@ -78,7 +78,7 @@ left: 0.666666666666667em; width: 1.733333333333333em; height: 1.733333333333333em; - background-image: url("../images/race-features-icons.png"); + background-image: url("images/home/race-features-icons.png"); -webkit-background-size: auto 200%; -moz-background-size: auto 200%; background-size: auto 200%; @@ -204,7 +204,7 @@ .eventregattarace_actiondisabled:hover { background-color: transparent; } -[class*="event-regatta-race__feature__"] { +[class*="eventregattarace_feature_"] { position: relative; top: 50%; -webkit-transform: translate(0, -50%); @@ -216,12 +216,12 @@ width: 1.733333333333333em; height: 1.733333333333333em; margin-right: 0.666666666666667em; - background-image: url("../images/race-features-icons.png"); + background-image: url("images/home/race-features-icons.png"); -webkit-background-size: auto 200%; -moz-background-size: auto 200%; background-size: auto 200%; } -[class*="event-regatta-race__feature__"]:last-of-type { +[class*="eventregattarace_feature_"]:last-of-type { margin-right: 0; } .eventregattarace_feature_gps { @@ -239,3 +239,30 @@ .eventregattarace_feature_unavailable { background-position-y: 100%; } +.regattanavigation { + font-size: 15px; + font-size: 1rem; + text-align: center; + border-bottom: 1px solid #ddd; +} +.regattanavigation_link { + display: inline-block; + margin-right: 2em; + padding: 1em 0 0.733333333333333em 0; + border-bottom: 0.266666666666667em solid transparent; + font-weight: 600; + color: #666; +} +.regattanavigation_link:hover, +.regattanavigation_link:focus { + border-bottom-color: #ddd; + text-decoration: none; + color: #333; +} +.regattanavigation_linkactive { + border-bottom-color: #f0ab00; +} +.regattanavigation_linkactive:hover, +.regattanavigation_linkactive:focus { + border-bottom-color: #d89a00; +} \ 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/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 446b9b702d0..981c232a89e 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 @@ -8,9 +8,9 @@ import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.Widget; 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.place.event.regattaschedule.EventRegattaScheduleResources; import com.sap.sailing.gwt.ui.shared.EventDTO; 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; @@ -21,8 +21,8 @@ public class EventRegattaRaces extends Composite { } @UiField(provided=true) Regatta regatta; - @UiField HTMLPanel regattaSeriesNavigationPanel; - @UiField HTMLPanel regattaSeriesPanel; + @UiField HTMLPanel regattaPhasesNavigationPanel; + @UiField HTMLPanel regattaPhasesPanel; @SuppressWarnings("unused") private final EventDTO event; @@ -37,28 +37,21 @@ public class EventRegattaRaces extends Composite { regatta = new Regatta(event, false, timerForClientServerOffset, pageNavigator); - EventRegattaScheduleResources.INSTANCE.css().ensureInjected(); + EventRegattaRacesResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); } public void setRacesFromRaceGroup(RaceGroupDTO raceGroup, StrippedLeaderboardDTO leaderboard) { regatta.setData(raceGroup, leaderboard, null); + + int regattaPhases = raceGroup.getSeries().size(); + if(regattaPhases > 1) { + for(RaceGroupSeriesDTO series: raceGroup.getSeries()) { + EventRegattaRacesPhase regattaPhase = new EventRegattaRacesPhase(leaderboard, series, timerForClientServerOffset, pageNavigator); + regattaPhasesPanel.add(regattaPhase); + } + } else { + + } } - -// public void setRacesFromRegatta(RegattaDTO regattaDTO, LeaderboardGroupDTO leaderboardGroup, StrippedLeaderboardDTO regattaLeaderboard) { -// regatta.setData(leaderboardGroup, regattaLeaderboard); -// -// for(SeriesDTO series: regattaDTO.series) { -// EventRegattaScheduleSeries eventRegattaScheduleSeries = new EventRegattaScheduleSeries(regattaLeaderboard, series, timerForClientServerOffset, pageNavigator); -// regattaSeriesPanel.add(eventRegattaScheduleSeries); -// } -// } -// -// public void setRacesFromFlexibleLeaderboard(LeaderboardGroupDTO leaderboardGroup, StrippedLeaderboardDTO flexibleLeaderboard) { -// regatta.setData(leaderboardGroup, flexibleLeaderboard); -// -// EventRegattaScheduleFleet eventRegattaScheduleFleet = new EventRegattaScheduleFleet(flexibleLeaderboard, timerForClientServerOffset, pageNavigator); -// regattaSeriesPanel.add(eventRegattaScheduleFleet); -// } -// } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRaces.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRaces.ui.xml index 90d3d4f1987..e25fd50f512 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRaces.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRaces.ui.xml @@ -12,7 +12,7 @@
- +
@@ -25,7 +25,7 @@
- +
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 new file mode 100644 index 00000000000..a7c341f0e7f --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesFleet.java @@ -0,0 +1,34 @@ +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.sap.sailing.domain.common.dto.FleetDTO; + +public class EventRegattaRacesFleet { + private static EventRegattaRacesFleetUiBinder uiBinder = GWT.create(EventRegattaRacesFleetUiBinder.class); + + interface EventRegattaRacesFleetUiBinder extends UiBinder { + } + + @UiField SpanElement fleetName; + @UiField DivElement fleetCountAndColor; + + private DivElement root; + + public EventRegattaRacesFleet(FleetDTO fleet) { + EventRegattaRacesResources.INSTANCE.css().ensureInjected(); + root = uiBinder.createAndBindUi(this); + + fleetName.setInnerText(fleet.getName()); + fleetCountAndColor.getStyle().setBackgroundColor(fleet.getColor().getAsHtml()); + fleetCountAndColor.setInnerText("tbd."); + } + + public Element getElement() { + return root; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesFleet.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesFleet.ui.xml index 4feb2dbf1ed..c42bafd93de 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesFleet.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesFleet.ui.xml @@ -4,12 +4,9 @@ xmlns:s="urn:import:com.sap.sailing.gwt.home.client.shared"> - - - -
-
-
{fleet.name}
+ +
+
+
- 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 new file mode 100644 index 00000000000..0c23fbc1433 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesFleetGroup.java @@ -0,0 +1,59 @@ +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.DivElement; +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.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.ui.shared.RaceGroupSeriesDTO; +import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; +import com.sap.sse.gwt.client.player.Timer; + +public class EventRegattaRacesFleetGroup extends Composite { + private static EventRegattaRacesFleetGroupUiBinder uiBinder = GWT.create(EventRegattaRacesFleetGroupUiBinder.class); + + interface EventRegattaRacesFleetGroupUiBinder extends UiBinder { + } + + @UiField DivElement fleetsPanel; + @UiField HTMLPanel racesFleetPanel; + + public EventRegattaRacesFleetGroup(StrippedLeaderboardDTO leaderboard, RaceGroupSeriesDTO series, List fleetsToShow, Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { + EventRegattaRacesResources.INSTANCE.css().ensureInjected(); + initWidget(uiBinder.createAndBindUi(this)); + + for(FleetDTO fleet: fleetsToShow) { + EventRegattaRacesFleet eventRegattaRacesFleet = new EventRegattaRacesFleet(fleet); + fleetsPanel.appendChild(eventRegattaRacesFleet.getElement()); + + List racesOfFleet = getRacesOfFleet(leaderboard, series, fleet); + for(RaceColumnDTO raceColumn: racesOfFleet) { + EventRegattaRacesRace race = new EventRegattaRacesRace(fleet, raceColumn, pageNavigator); + racesFleetPanel.add(race); + } + } + } + + private List getRacesOfFleet(StrippedLeaderboardDTO leaderboard, RaceGroupSeriesDTO series, FleetDTO fleet) { + List racesColumnsOfFleet = new ArrayList(); + for (RaceColumnDTO raceColumn : series.getRaceColumns()) { + for (FleetDTO fleetOfRaceColumn : series.getFleets()) { + if (fleet.equals(fleetOfRaceColumn)) { + // We have to get the race column from the leaderboard, because the race column of the series + // have no tracked race and would be displayed as inactive race. + racesColumnsOfFleet.add(leaderboard.getRaceColumnByName(raceColumn.getName())); + } + } + } + return racesColumnsOfFleet; + } + +} 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 8de234198b8..1a2f0ce1d59 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 @@ -4,10 +4,10 @@ xmlns:s="urn:import:com.sap.sailing.gwt.home.client.shared"> - + - -
+
- + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesNavigationLink.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesNavigationLink.java new file mode 100644 index 00000000000..71be5c07fdc --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesNavigationLink.java @@ -0,0 +1,26 @@ +package com.sap.sailing.gwt.home.client.place.event.regattaraces; + +import com.google.gwt.core.client.GWT; +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.sap.sailing.gwt.ui.shared.RaceGroupSeriesDTO; + +public class EventRegattaRacesNavigationLink extends Composite { + private static EventRegattaRacesNavigationLinkUiBinder uiBinder = GWT.create(EventRegattaRacesNavigationLinkUiBinder.class); + + interface EventRegattaRacesNavigationLinkUiBinder extends UiBinder { + } + + @UiField SpanElement phaseName; + + public EventRegattaRacesNavigationLink(RaceGroupSeriesDTO series) { + EventRegattaRacesResources.INSTANCE.css().ensureInjected(); + initWidget(uiBinder.createAndBindUi(this)); + + phaseName.setInnerText(series.getName()); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesNavigationLink.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesNavigationLink.ui.xml index 7367f452438..4063219e485 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesNavigationLink.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesNavigationLink.ui.xml @@ -4,8 +4,8 @@ xmlns:s="urn:import:com.sap.sailing.gwt.home.client.shared"> - + -{phase.name} + 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 new file mode 100644 index 00000000000..9bb37706ccc --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesPhase.java @@ -0,0 +1,58 @@ +package com.sap.sailing.gwt.home.client.place.event.regattaraces; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.google.gwt.core.client.GWT; +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.HTMLPanel; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.dto.FleetDTO; +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; +import com.sap.sse.gwt.client.player.Timer; + +public class EventRegattaRacesPhase extends Composite { + private static EventRegattaRacesPhaseUiBinder uiBinder = GWT.create(EventRegattaRacesPhaseUiBinder.class); + + interface EventRegattaRacesPhaseUiBinder extends UiBinder { + } + + @UiField SpanElement phaseName; + @UiField HTMLPanel fleetGroupsPanel; + + public EventRegattaRacesPhase(StrippedLeaderboardDTO leaderboard, RaceGroupSeriesDTO series, Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { + EventRegattaRacesResources.INSTANCE.css().ensureInjected(); + initWidget(uiBinder.createAndBindUi(this)); + + phaseName.setInnerText(series.getName()); + + int numberOfFleets = series.getFleets().size(); + if(numberOfFleets > 1) { + if(series.hasOrderedFleets()) { + for(FleetDTO fleet: series.getFleets()) { + // ordered fleets are splitted into different fleet groups -> e.g. Gold Silver + List fleetsToShow = new ArrayList(); + fleetsToShow.add(fleet); + EventRegattaRacesFleetGroup fleetGroup = new EventRegattaRacesFleetGroup(leaderboard, series, fleetsToShow, timerForClientServerOffset, pageNavigator); + fleetGroupsPanel.add(fleetGroup); + } + } else { + // unordered fleets are NOT splitted into different fleet groups -> e.g. Blue, Yellow + List fleetsToShow = new ArrayList(series.getFleets()); + EventRegattaRacesFleetGroup fleetGroup = new EventRegattaRacesFleetGroup(leaderboard, series, fleetsToShow, timerForClientServerOffset, pageNavigator); + fleetGroupsPanel.add(fleetGroup); + } + } else { + // single fleet + EventRegattaRacesFleetGroup fleetGroup = new EventRegattaRacesFleetGroup(leaderboard, series, Collections.emptyList(), timerForClientServerOffset, pageNavigator); + fleetGroupsPanel.add(fleetGroup); + } + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesPhase.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesPhase.ui.xml index 4213c3349ab..22dd4bed951 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesPhase.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesPhase.ui.xml @@ -4,11 +4,12 @@ xmlns:s="urn:import:com.sap.sailing.gwt.home.client.shared"> - +
-

{phase.name}

+

+
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 new file mode 100644 index 00000000000..3c14c4bd34f --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesRace.java @@ -0,0 +1,63 @@ +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.i18n.shared.DateTimeFormat; +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.domain.common.dto.FleetDTO; +import com.sap.sailing.domain.common.dto.RaceColumnDTO; +import com.sap.sailing.domain.common.dto.RaceDTO; +import com.sap.sailing.gwt.home.client.place.event.EventPageNavigator; + +public class EventRegattaRacesRace extends Composite { + private static EventRegattaRacesRaceUiBinder uiBinder = GWT.create(EventRegattaRacesRaceUiBinder.class); + + interface EventRegattaRacesRaceUiBinder extends UiBinder { + } + + @UiField DivElement fleetColor; + + private final DateTimeFormat raceTimeFormat = DateTimeFormat.getFormat("EEE, h:mm a"); + + public EventRegattaRacesRace(FleetDTO fleet, RaceColumnDTO raceColumn, EventPageNavigator pageNavigator) { + EventRegattaRacesResources.INSTANCE.css().ensureInjected(); + initWidget(uiBinder.createAndBindUi(this)); + + fleetColor.getStyle().setBackgroundColor(fleet.getColor().getAsHtml()); + +// raceName.setInnerText(raceColumn.getName()); +// +// RaceDTO race = raceColumn.getRace(fleet); +// if(race != null) { +// boolean live = raceColumn.isLive(fleet, timerForClientServerOffset.getLiveTimePointInMillis()); +// +// // tracked race +// raceBox.getElement().addClassName(css.eventregattaschedule_series_fleet_racetracked()); +// if(race.trackedRace != null) { +// if(race.trackedRace.startOfTracking != null) { +// raceDetails.setInnerText(raceTimeFormat.format(race.trackedRace.startOfTracking)); +// } else { +// raceDetails.setInnerText("tracked"); +// } +// if(live) { +// raceBox.getElement().setAttribute("data-live", "data-live"); +// } +// } +// } else { +// raceBox.getElement().addClassName(css.eventregattaschedule_series_fleet_raceuntracked()); +// raceDetails.setInnerText("untracked"); +// } + + } + +// @UiHandler("raceLink") +// public void goToRaceboard(ClickEvent e) { +// 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 8b3e27a8949..8e9812ec9d3 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 @@ -4,13 +4,14 @@ xmlns:s="urn:import:com.sap.sailing.gwt.home.client.shared"> - +
-
-
Race

{#if race.isFinished} {/if race.isFinished} -
{#if race.isTracked}{/if race.isTracked}{#if race.isTracked}{/if race.isTracked}{#if race.isFinished}{/if race.isFinished} +
+
+ Race X

+
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 new file mode 100644 index 00000000000..2a6ac69e8b2 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesRaceday.java @@ -0,0 +1,34 @@ +package com.sap.sailing.gwt.home.client.place.event.regattaraces; + +import com.google.gwt.core.client.GWT; +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.HTMLPanel; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.home.client.place.event.EventPageNavigator; +import com.sap.sailing.gwt.ui.shared.RaceGroupSeriesDTO; +import com.sap.sse.gwt.client.player.Timer; + +public class EventRegattaRacesRaceday extends Composite { + private static EventRegattaRacesRacedayUiBinder uiBinder = GWT.create(EventRegattaRacesRacedayUiBinder.class); + + interface EventRegattaRacesRacedayUiBinder extends UiBinder { + } + + @UiField SpanElement raceDayDate; + @UiField HTMLPanel racesPanel; + + private final Timer timerForClientServerOffset; + private final EventPageNavigator pageNavigator; + + public EventRegattaRacesRaceday(RaceGroupSeriesDTO series, Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { + this.timerForClientServerOffset = timerForClientServerOffset; + this.pageNavigator = 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/EventRegattaRacesRaceday.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesRaceday.ui.xml index ff02a89ee20..7e633f4059b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesRaceday.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesRaceday.ui.xml @@ -4,11 +4,12 @@ xmlns:s="urn:import:com.sap.sailing.gwt.home.client.shared"> - + -
{race.date}
-
+
+
+
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 d40806a3699..06591ee7d2f 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 @@ -48,5 +48,7 @@ public interface EventRegattaRacesResources extends ClientBundle { String eventregattarace_feature_video(); String eventregattarace_feature_audio(); String eventregattarace_feature_unavailable(); + String regattanavigation_link(); + String regattanavigation_linkactive(); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.css deleted file mode 100644 index 1beb7e024e2..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.css +++ /dev/null @@ -1,135 +0,0 @@ -/** - * A list of races of a regatta at an event - */ -.eventregattaschedule { - font-size: 16px; - font-size: 1rem; -} -.eventregattaschedule_series { - margin: 1.875em 0; -} -.eventregattaschedule_series_name { - display: inline-block; - margin: 0 0 0.3125em 0; -} -.eventregattaschedule_series_date { - font-weight: 600; - color: #333; - margin-left: 0.5em; -} -.eventregattaschedule_series_fleet { - zoom: 1; -} -.eventregattaschedule_series_fleet:before, -.eventregattaschedule_series_fleet:after { - content: ""; - display: table; -} -.eventregattaschedule_series_fleet:after { - clear: both; -} -.eventregattaschedule_series_fleet_name { - color: #fff; - text-transform: uppercase; - font-size: 0.875em; - font-weight: 600; - padding: 0.5em; - background: #008fcc; - margin-bottom: 1px; - border-right: 1px solid #fff; -} -.eventregattaschedule_series_fleet_name[data-racescount="1"] { - width: 12.5%; -} -.eventregattaschedule_series_fleet_name[data-racescount="2"] { - width: 25%; -} -.eventregattaschedule_series_fleet_name[data-racescount="3"] { - width: 37.5%; -} -.eventregattaschedule_series_fleet_name[data-racescount="4"] { - width: 50%; -} -.eventregattaschedule_series_fleet_name[data-racescount="5"] { - width: 62.5%; -} -.eventregattaschedule_series_fleet_name[data-racescount="6"] { - width: 75%; -} -.eventregattaschedule_series_fleet_name[data-racescount="7"] { - width: 87.5%; -} -.eventregattaschedule_series_fleet_race { - height: 4.375em; - padding: 0.1875em; - background: #008fcc; - color: #fff; - border-right: 1px solid #fff; - margin-bottom: 0.75em; - width: 12.5%; - float: left; - position: relative; -} -.eventregattaschedule_series_fleet_racetracked { - background-image: url("../images/regatta-finished-icon.png"); - background-repeat: no-repeat; - background-position: right 0.1875em bottom 0.1875em; -} -.eventregattaschedule_series_fleet_racetracked:hover { - background-color: #0081b8; - text-decoration: none; -} -.eventregattaschedule_series_fleet_racetracked:hover .eventregattaschedule_series_fleet_race_name { - text-decoration: underline; -} -.eventregattaschedule_series_fleet_raceuntracked { - background: #40abd9; - background-image: -webkit-linear-gradient(135deg, #008fcc 33.333%, transparent 33.333%, transparent 50%, #008fcc 50%, #008fcc 83.3333%, transparent 83.3333%, transparent); - background-image: -moz-linear-gradient(135deg, #008fcc 33.333%, transparent 33.333%, transparent 50%, #008fcc 50%, #008fcc 83.3333%, transparent 83.3333%, transparent); - background-image: -o-linear-gradient(135deg, #008fcc 33.333%, transparent 33.333%, transparent 50%, #008fcc 50%, #008fcc 83.3333%, transparent 83.3333%, transparent); - background-image: -ms-linear-gradient(135deg, #008fcc 33.333%, transparent 33.333%, transparent 50%, #008fcc 50%, #008fcc 83.3333%, transparent 83.3333%, transparent); - background-image: linear-gradient(-45deg, #008fcc 33.333%, transparent 33.333%, transparent 50%, #008fcc 50%, #008fcc 83.3333%, transparent 83.3333%, transparent); - -webkit-background-size: 14px 14px; - -moz-background-size: 14px 14px; - background-size: 14px 14px; -} -.eventregattaschedule_series_fleet_race[data-upcoming='true'], -.eventregattaschedule_series_fleet_race[data-upcoming='data-upcoming'] { - color: #999; - background-color: #f2f2f2; - background-image: url("../images/regatta-upcoming-icon.png"); - background-repeat: no-repeat; - background-position: right 0.1875em bottom 0.1875em; -} -.eventregattaschedule_series_fleet_race[data-upcoming='true']:hover, -.eventregattaschedule_series_fleet_race[data-upcoming='data-upcoming']:hover { - background-color: #ededed; -} -.eventregattaschedule_series_fleet_race[data-live='true'], -.eventregattaschedule_series_fleet_race[data-live='data-live'] { - background: #f00; -} -.eventregattaschedule_series_fleet_race[data-live='true']:hover, -.eventregattaschedule_series_fleet_race[data-live='data-live']:hover { - background-color: #e60000; -} -.eventregattaschedule_series_fleet_race[data-live='true']:after, -.eventregattaschedule_series_fleet_race[data-live='data-live']:after { - content: 'live'; - text-transform: uppercase; - color: #fff; - position: absolute; - font-size: 0.75em; - font-weight: 600; - bottom: 0.25em; - right: 0.25em; -} -.eventregattaschedule_series_fleet_race_name { - font-size: 1.25em; - font-weight: 700; - text-align: center; - margin-top: 0.4375em; -} -.eventregattaschedule_series_fleet_race_details { - text-align: center; -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.java deleted file mode 100644 index 52a1d3c7003..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.sap.sailing.gwt.home.client.place.event.regattaschedule; - -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.HTMLPanel; -import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.gwt.home.client.place.event.EventPageNavigator; -import com.sap.sailing.gwt.home.client.place.event.regattaheader.EventRegattaHeader; -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.SeriesDTO; -import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; -import com.sap.sse.gwt.client.player.Timer; - -public class EventRegattaSchedule extends Composite { - private static EventRegattaScheduleUiBinder uiBinder = GWT.create(EventRegattaScheduleUiBinder.class); - - interface EventRegattaScheduleUiBinder extends UiBinder { - } - - @UiField(provided=true) EventRegattaHeader regattaHeader; - @UiField HTMLPanel seriesListPanel; - - @SuppressWarnings("unused") - private final EventDTO event; - - private final Timer timerForClientServerOffset; - private final EventPageNavigator pageNavigator; - - public EventRegattaSchedule(EventDTO event, Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { - this.event = event; - this.timerForClientServerOffset = timerForClientServerOffset; - this.pageNavigator = pageNavigator; - - regattaHeader = new EventRegattaHeader(event, timerForClientServerOffset, pageNavigator); - - EventRegattaScheduleResources.INSTANCE.css().ensureInjected(); - initWidget(uiBinder.createAndBindUi(this)); - } - - public void setRacesFromRegatta(RegattaDTO regatta, LeaderboardGroupDTO leaderboardGroup, StrippedLeaderboardDTO regattaLeaderboard) { - regattaHeader.setData(leaderboardGroup, regattaLeaderboard); - - for(SeriesDTO series: regatta.series) { - EventRegattaScheduleSeries eventRegattaScheduleSeries = new EventRegattaScheduleSeries(regattaLeaderboard, series, timerForClientServerOffset, pageNavigator); - seriesListPanel.add(eventRegattaScheduleSeries); - } - } - - public void setRacesFromFlexibleLeaderboard(LeaderboardGroupDTO leaderboardGroup, StrippedLeaderboardDTO flexibleLeaderboard) { - regattaHeader.setData(leaderboardGroup, flexibleLeaderboard); - - EventRegattaScheduleFleet eventRegattaScheduleFleet = new EventRegattaScheduleFleet(flexibleLeaderboard, timerForClientServerOffset, pageNavigator); - seriesListPanel.add(eventRegattaScheduleFleet); - } - -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.ui.xml deleted file mode 100644 index 6f8b49ec780..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.ui.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - -
-
-
- -
-
-
-
-
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java deleted file mode 100644 index dace5004dc4..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.sap.sailing.gwt.home.client.place.event.regattaschedule; - -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.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.HTMLPanel; -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.domain.common.dto.RaceColumnDTO; -import com.sap.sailing.gwt.home.client.place.event.EventPageNavigator; -import com.sap.sailing.gwt.ui.shared.SeriesDTO; -import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; -import com.sap.sse.gwt.client.player.Timer; - -public class EventRegattaScheduleFleet extends Composite { - private static EventRegattaScheduleSeriesUiBinder uiBinder = GWT.create(EventRegattaScheduleSeriesUiBinder.class); - - interface EventRegattaScheduleSeriesUiBinder extends UiBinder { - } - - private final FleetDTO fleet; - - @UiField DivElement fleetDiv; - @UiField SpanElement fleetName; - @UiField HTMLPanel racesListPanel; - - public EventRegattaScheduleFleet(StrippedLeaderboardDTO leaderboard, SeriesDTO series, FleetDTO fleet, - Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { - this.fleet = fleet; - - EventRegattaScheduleResources.INSTANCE.css().ensureInjected(); - initWidget(uiBinder.createAndBindUi(this)); - -// if(!fleet.getName().equals(LeaderboardNameConstants.DEFAULT_FLEET_NAME)) { -// fleetName.setInnerText(fleet.getName()); -// fleetDiv.getStyle().setBackgroundColor(fleet.getColor().getAsHtml()); -// } - - List racesOfFleet = getRacesOfFleet(leaderboard, series, fleet); - for(RaceColumnDTO raceColumn: racesOfFleet) { - EventRegattaScheduleRace eventRegattaScheduleRace = new EventRegattaScheduleRace(leaderboard, fleet, raceColumn, timerForClientServerOffset, pageNavigator); - racesListPanel.add(eventRegattaScheduleRace); - } - } - - public EventRegattaScheduleFleet(StrippedLeaderboardDTO leaderboard, Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { - this.fleet = new FleetDTO(LeaderboardNameConstants.DEFAULT_FLEET_NAME, 0, null); - - EventRegattaScheduleResources.INSTANCE.css().ensureInjected(); - initWidget(uiBinder.createAndBindUi(this)); - - fleetName.setInnerText("Races"); -// fleetDiv.getStyle().setBackgroundColor(fleet.getColor().getAsHtml()); - - List racesOfFleet = leaderboard.getRaceList(); - for(RaceColumnDTO raceColumn: racesOfFleet) { - EventRegattaScheduleRace eventRegattaScheduleRace = new EventRegattaScheduleRace(leaderboard, fleet, raceColumn, timerForClientServerOffset, pageNavigator); - racesListPanel.add(eventRegattaScheduleRace); - } - } - - private List getRacesOfFleet(StrippedLeaderboardDTO leaderboard, SeriesDTO series, FleetDTO fleet) { - List racesColumnsOfFleet = new ArrayList(); - int raceColumnCounter = 1; - for (RaceColumnDTO raceColumn : series.getRaceColumns()) { - boolean skipCarryForwardColumn = series.isFirstColumnIsNonDiscardableCarryForward(); - for (FleetDTO fleetOfRaceColumn : series.getFleets()) { - if (fleet.equals(fleetOfRaceColumn) && !(skipCarryForwardColumn == true && raceColumnCounter == 1)) { - // We have to get the race column from the leaderboard, because the race column of the series - // have no tracked race and would be displayed as inactive race. - racesColumnsOfFleet.add(leaderboard.getRaceColumnByName(raceColumn.getName())); - } - } - raceColumnCounter++; - } - return racesColumnsOfFleet; - } -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.ui.xml deleted file mode 100644 index 61fed4d4c22..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleFleet.ui.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - -
-
- -
-
-
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.java deleted file mode 100644 index 2189ef70178..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.sap.sailing.gwt.home.client.place.event.regattaschedule; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.SpanElement; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.i18n.shared.DateTimeFormat; -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.domain.common.dto.FleetDTO; -import com.sap.sailing.domain.common.dto.RaceColumnDTO; -import com.sap.sailing.domain.common.dto.RaceDTO; -import com.sap.sailing.gwt.home.client.place.event.EventPageNavigator; -import com.sap.sailing.gwt.home.client.place.event.regattaschedule.EventRegattaScheduleResources.LocalCss; -import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; -import com.sap.sse.gwt.client.player.Timer; - -public class EventRegattaScheduleRace extends Composite { - private static EventRegattaScheduleSeriesUiBinder uiBinder = GWT.create(EventRegattaScheduleSeriesUiBinder.class); - - interface EventRegattaScheduleSeriesUiBinder extends UiBinder { - } - - private final FleetDTO fleet; - private final RaceColumnDTO raceColumn; - private final StrippedLeaderboardDTO leaderboard; - - @UiField Anchor raceBox; - @UiField SpanElement raceName; - @UiField SpanElement raceDetails; - - private final DateTimeFormat raceTimeFormat = DateTimeFormat.getFormat("EEE, h:mm a"); - private final EventPageNavigator pageNavigator; - - public EventRegattaScheduleRace(StrippedLeaderboardDTO leaderboard, FleetDTO fleet, RaceColumnDTO raceColumn, Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { - this.leaderboard = leaderboard; - this.fleet = fleet; - this.raceColumn = raceColumn; - this.pageNavigator = pageNavigator; - - LocalCss css = EventRegattaScheduleResources.INSTANCE.css(); - css.ensureInjected(); - initWidget(uiBinder.createAndBindUi(this)); - - raceName.setInnerText(raceColumn.getName()); - - RaceDTO race = raceColumn.getRace(fleet); - if(race != null) { - boolean live = raceColumn.isLive(fleet, timerForClientServerOffset.getLiveTimePointInMillis()); - - // tracked race - raceBox.getElement().addClassName(css.eventregattaschedule_series_fleet_racetracked()); - if(race.trackedRace != null) { - if(race.trackedRace.startOfTracking != null) { - raceDetails.setInnerText(raceTimeFormat.format(race.trackedRace.startOfTracking)); - } else { - raceDetails.setInnerText("tracked"); - } - if(live) { - raceBox.getElement().setAttribute("data-live", "data-live"); - } - } - } else { - raceBox.getElement().addClassName(css.eventregattaschedule_series_fleet_raceuntracked()); - raceDetails.setInnerText("untracked"); - } - } - - @UiHandler("raceBox") - public void goToRaceboard(ClickEvent e) { - 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/regattaschedule/EventRegattaScheduleRace.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.ui.xml deleted file mode 100644 index 38c46324eaf..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleRace.ui.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - -
-
-
-
-
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleResources.java deleted file mode 100644 index 2fa1c92d153..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleResources.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.sap.sailing.gwt.home.client.place.event.regattaschedule; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.resources.client.ClientBundle; -import com.google.gwt.resources.client.CssResource; - -public interface EventRegattaScheduleResources extends ClientBundle { - public static final EventRegattaScheduleResources INSTANCE = GWT.create(EventRegattaScheduleResources.class); - - @Source("com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaSchedule.css") - LocalCss css(); - - public interface LocalCss extends CssResource { - String eventregattaschedule(); - String eventregattaschedule_series(); - String eventregattaschedule_series_name(); - String eventregattaschedule_series_date(); - String eventregattaschedule_series_fleet(); - String eventregattaschedule_series_fleet_name(); - String eventregattaschedule_series_fleet_race(); - String eventregattaschedule_series_fleet_racetracked(); - String eventregattaschedule_series_fleet_race_name(); - String eventregattaschedule_series_fleet_raceuntracked(); - String eventregattaschedule_series_fleet_race_details(); - } -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.java deleted file mode 100644 index ded215cb5f1..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.sap.sailing.gwt.home.client.place.event.regattaschedule; - -import com.google.gwt.core.client.GWT; -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.HTMLPanel; -import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.domain.common.dto.FleetDTO; -import com.sap.sailing.gwt.home.client.place.event.EventPageNavigator; -import com.sap.sailing.gwt.ui.shared.SeriesDTO; -import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; -import com.sap.sse.gwt.client.player.Timer; - -public class EventRegattaScheduleSeries extends Composite { - private static EventRegattaScheduleSeriesUiBinder uiBinder = GWT.create(EventRegattaScheduleSeriesUiBinder.class); - - interface EventRegattaScheduleSeriesUiBinder extends UiBinder { - } - - @UiField SpanElement seriesName; - @UiField SpanElement seriesDate; - @UiField HTMLPanel fleetsListPanel; - - public EventRegattaScheduleSeries(StrippedLeaderboardDTO leaderboard, SeriesDTO series, Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { - EventRegattaScheduleResources.INSTANCE.css().ensureInjected(); - initWidget(uiBinder.createAndBindUi(this)); - - seriesName.setInnerText(series.getName()); - seriesDate.setInnerText("Some series data"); - - for(FleetDTO fleet: series.getFleets()) { - EventRegattaScheduleFleet eventRegattaScheduleFleet = new EventRegattaScheduleFleet(leaderboard, series, fleet, timerForClientServerOffset, pageNavigator); - fleetsListPanel.add(eventRegattaScheduleFleet); - } - } - -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.ui.xml deleted file mode 100644 index 8298091c965..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaschedule/EventRegattaScheduleSeries.ui.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - -
-

- - -
-
-
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 d7d1117ac49..b9faaae5763 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 @@ -49,7 +49,7 @@ public class EventsOverviewRecentYear extends Composite { recentEventsTeaserPanel.add(recentEvent); } 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/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index aae2c8b2de6..61f2a633467 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 @@ -801,19 +801,24 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S for (Fleet fleet : series.getFleets()) { fleets.add(baseDomainFactory.convertToFleetDTO(fleet)); } - List raceColumns = new ArrayList(); - for (RaceColumnInSeries raceColumn : series.getRaceColumns()) { - 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()); - raceColumns.add(raceColumnDTO); - } + List raceColumns = convertToRaceColumnDTOs(series.getRaceColumns()); SeriesDTO result = new SeriesDTO(series.getName(), fleets, raceColumns, series.isMedal(), series.getResultDiscardingRule() == null ? null : series.getResultDiscardingRule().getDiscardIndexResultsStartingWithHowManyRaces(), series.isStartsWithZeroScore(), series.isFirstColumnIsNonDiscardableCarryForward(), series.hasSplitFleetContiguousScoring()); return result; } + + private List convertToRaceColumnDTOs(Iterable raceColumns) { + List raceColumnDTOs = new ArrayList(); + 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()); + raceColumnDTOs.add(raceColumnDTO); + } + return raceColumnDTOs; + } private RaceInfoDTO createRaceInfoDTO(String seriesName, RaceColumn raceColumn, Fleet fleet) { RaceInfoDTO raceInfoDTO = new RaceInfoDTO(); @@ -3242,12 +3247,14 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S 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())); } raceGroups.add(raceGroup); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceGroupSeriesDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceGroupSeriesDTO.java index d109899f0b2..ac09edcd0b9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceGroupSeriesDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceGroupSeriesDTO.java @@ -6,13 +6,15 @@ import java.util.List; import com.google.gwt.user.client.rpc.IsSerializable; import com.sap.sailing.domain.common.dto.FleetDTO; import com.sap.sailing.domain.common.dto.NamedDTO; +import com.sap.sailing.domain.common.dto.RaceColumnDTO; public class RaceGroupSeriesDTO extends NamedDTO implements IsSerializable { - + private static final long serialVersionUID = 8432572954520767329L; private List fleets = new ArrayList(); + private List raceColumns = new ArrayList(); - public RaceGroupSeriesDTO() {} + protected RaceGroupSeriesDTO() {} public RaceGroupSeriesDTO(String name) { super(name); @@ -22,4 +24,19 @@ public class RaceGroupSeriesDTO extends NamedDTO implements IsSerializable { return fleets; } + public List getRaceColumns() { + return raceColumns; + } + + public boolean hasOrderedFleets() { + boolean result = false; + int firstOrderNo = fleets.get(0).getOrderNo(); + for (FleetDTO fleet : fleets) { + if (fleet.getOrderNo() != firstOrderNo) { + result = true; + break; + } + } + return result; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/main.css b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/main.css index 1c177831928..35ed7ce761c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/main.css +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/main.css @@ -178,13 +178,13 @@ figcaption { text-transform: uppercase; vertical-align: middle; } -.label[data-labeltype='ess'] { +.label[data-labeltype='group1'] { background-color: #a71724; } -.label[data-labeltype='worldcup'] { +.label[data-labeltype='group2'] { background-color: #c79242; } -.label[data-labeltype='bundesliga'] { +.label[data-labeltype='group3'] { background-color: #005fa7; } .morelink { From c30ace8bb9bb0e2d3fd41b20ff6af53752cf6f44 Mon Sep 17 00:00:00 2001 From: "Christopher Ronnewinkel (D036654)" Date: Tue, 15 Jul 2014 20:11:43 +0200 Subject: [PATCH 078/323] adapt streamlets in simulator to recent changes for spatial-wind; tune parameters; fix long-delay of streamlet-initialization; (reverted from commit d41fd00de4cc572dc4f830fbb354f6c5fe1e6d93) --- .../racemap/WindStreamletsRaceboardOverlay.java | 13 ++----------- .../gwt/ui/simulator/StreamletParameters.java | 4 ++-- .../ui/simulator/WindStreamletsCanvasOverlay.java | 3 ++- .../gwt/ui/simulator/streamlets/SimulatorField.java | 10 +++++----- 4 files changed, 11 insertions(+), 19 deletions(-) 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 18a4764eca5..537de8e5bee 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 @@ -106,11 +106,6 @@ public class WindStreamletsRaceboardOverlay extends FullCanvasOverlay { } private void scheduleWindDataRefresh() { - // initial update - updateWindSourcesToObserve(); - updateWindField(); - - // schedule delayed repeated updates scheduler.scheduleFixedPeriod(new RepeatingCommand() { @Override public boolean execute() { @@ -151,12 +146,8 @@ public class WindStreamletsRaceboardOverlay extends FullCanvasOverlay { windInfoForRace.raceIsKnownToStartUpwind = result.raceIsKnownToStartUpwind; windInfoForRace.windSourcesToExclude = result.windSourcesToExclude; for (Entry e : result.windTrackInfoByWindSource.entrySet()) { - // generally exclude combined wind, course-based wind and racecommittee - if (!windInfoForRace.windTrackInfoByWindSource.containsKey(e.getKey()) - && !Util.contains(result.windSourcesToExclude, e.getKey()) - && e.getKey().getType() != WindSourceType.COMBINED - && e.getKey().getType() != WindSourceType.COURSE_BASED - && e.getKey().getType() != WindSourceType.RACECOMMITTEE) { + if (!windInfoForRace.windTrackInfoByWindSource.containsKey(e.getKey()) && + !Util.contains(result.windSourcesToExclude, e.getKey()) && e.getKey().getType() != WindSourceType.COMBINED) { windInfoForRace.windTrackInfoByWindSource.put(e.getKey(), e.getValue()); } } 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 40b49dd9578..9441dadb635 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 @@ -6,8 +6,8 @@ public class StreamletParameters { public double motionScale = 1.0; public double swarmScale = 1.0; public int detailZoom = 15; - public double lineScale = 1.0; - public double lineBase = 1.5; + public double lineScale = 0.1; + public double lineBase = 1.2; public StreamletParameters() { } 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 b0719510e6b..ac1f5398734 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 @@ -74,7 +74,8 @@ public class WindStreamletsCanvasOverlay extends FullCanvasOverlay implements Ti } public void setWindField(final WindFieldDTO windFieldDTO) { - this.windFieldDTO = windFieldDTO; + this.windFieldDTO = windFieldDTO; + this.swarm = null; } @Override 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 2cf76bda8bd..55461318fee 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 @@ -31,9 +31,9 @@ public class SimulatorField implements VectorField { private final Position bdC; private final double xScale; - private double motionFactor; - private double lineBase; - private double lineScale; + private double motionFactor; + private double lineBase; + private double lineScale; private final double maxLength; private final double particleFactor; @@ -100,7 +100,7 @@ public class SimulatorField implements VectorField { } } this.maxLength = maxWindSpeed; - this.particleFactor = 2.0; + this.particleFactor = 1.0; final double latAvg = (this.rcEnd.getLatDeg() + this.rcStart.getLatDeg()) / 2.; this.lngScale = Math.cos(latAvg * Math.PI / 180.0); final double difLat = this.rcEnd.getLatDeg() - this.rcStart.getLatDeg(); @@ -151,7 +151,7 @@ public class SimulatorField implements VectorField { + dataAtStep[idx.yTop][2 * idx.xBot + 1] * idx.yMod * (1 - idx.xMod) + dataAtStep[idx.yBot][2 * idx.xTop + 1] * (1 - idx.yMod) * idx.xMod + dataAtStep[idx.yTop][2 * idx.xTop + 1] * idx.yMod * idx.xMod; - return new Vector(avgX / this.lngScale, avgY); + return new Vector(avgX, avgY); } @Override From f8343cc20c0ff6d2e5e82285591138e358362dcd Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jul 2014 00:33:15 +0200 Subject: [PATCH 079/323] added a TODO comment --- .../sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java | 1 + 1 file changed, 1 insertion(+) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java index 75a95143493..6970689a298 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java @@ -133,6 +133,7 @@ public class FixesAndTails { int indexOfFirst = -1; int indexOfLast = -1; int i = 0; + // TODO consider binary search to find beginning of interesting segment faster for (Iterator fixIter = fixesForCompetitor.iterator(); fixIter.hasNext() && indexOfLast == -1;) { GPSFixDTO fix = fixIter.next(); if (!fix.timepoint.before(to)) { From 95a6cf8864d3d1a208dcd0c6d44ed57df68b7db2 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jul 2014 07:29:04 +0000 Subject: [PATCH 080/323] Added description for setting up the Android SDK on Ubuntu 14.04 LTS --- wiki/racecommittee-app-environment.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/wiki/racecommittee-app-environment.md b/wiki/racecommittee-app-environment.md index f2e63af43a2..ededef0ca90 100644 --- a/wiki/racecommittee-app-environment.md +++ b/wiki/racecommittee-app-environment.md @@ -13,6 +13,12 @@ On CentOS 6.4 you should issue the following commands: yum install libstdc++-4.4.7-4.el6.x86_64 # ensure that x86_x64 stdlib is up to date yum install glibc.i686 glibc-devel.i686 libstdc++.i686 zlib-devel.i686 ncurses-devel.i686 libX11-devel.i686 libXrender.i686 libXrandr.i686 +On Ubuntu, use the following instead: + + dpkg --add-architecture i386 + apt-get update + apt-get install libncurses5:i386 libstdc++6:i386 zlib1g:i386 + 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 From d7b09a14e130671beea005ab4a4f1a18f7d9c548 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jul 2014 10:14:58 +0200 Subject: [PATCH 081/323] improved logging for outbound replication queue --- .../server/replication/impl/ReplicationServiceImpl.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java index fc376385125..c692f85d33f 100644 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java @@ -123,6 +123,11 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec * are likely to be sent per message transmitted, reducing overhead but correspondingly increasing latency. */ private final long TRANSMISSION_DELAY_MILLIS = 100; + + /** + * Counts the messages sent out by this replicator + */ + private long messageCount; public ReplicationServiceImpl(String exchangeName, String exchangeHost, final ReplicationInstancesManager replicationInstancesManager) throws IOException { this(exchangeName, exchangeHost, replicationInstancesManager, /* localService */ null, /* create RacingEventServiceTracker */ true); @@ -267,6 +272,9 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec }; timer.schedule(sendingTask, TRANSMISSION_DELAY_MILLIS); } + if (++messageCount % 10000l == 0) { + logger.info("Handled "+messageCount+" messages for replication. Current outbound replication queue size: "+outboundBuffer.size()); + } } } From 8b4f4fa7e464df3548495ca0a67792b132251b84 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jul 2014 11:31:14 +0200 Subject: [PATCH 082/323] replaced #!/bin/sh by #!/bin/bash to ensure compatibility on Debian-like Linux systems such as Ubuntu --- configuration/backup.sh | 2 +- configuration/udpMirror.sh | 2 +- java/com.googlecode.java-diff-utils.test/test/mocks/5A.txt | 2 +- java/com.googlecode.java-diff-utils.test/test/mocks/5B.txt | 2 +- java/com.sap.sailing.domain.test/runUnicodeTest | 2 +- java/target/env.sh | 2 +- java/target/http2udpmirror | 2 +- java/target/http2udpmirror_axel | 2 +- java/target/http2udpmirror_axel_proxy | 3 +-- java/target/importdeclination | 3 +-- java/target/refreshInstance.sh | 2 +- java/target/start | 2 +- java/target/status | 2 +- java/target/swisstiminglistener | 2 +- java/target/udpmirror | 2 +- java/target/udpmirrorOnAStick | 2 +- java/target/updateIPToBoat2 | 2 +- stick/scripts/refreshInstance.sh | 2 +- stick/start.sh | 2 +- stick/templates/env.sh.template | 2 +- 20 files changed, 20 insertions(+), 22 deletions(-) diff --git a/configuration/backup.sh b/configuration/backup.sh index 4f1834afbbd..85163199727 100644 --- a/configuration/backup.sh +++ b/configuration/backup.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # This is a template for creating backup of data and persist them # to a central backup server. Make sure to adapt this script to your needs. diff --git a/configuration/udpMirror.sh b/configuration/udpMirror.sh index 0d5140a1d97..d9abd9c2677 100755 --- a/configuration/udpMirror.sh +++ b/configuration/udpMirror.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash export JAVA_HOME=/home/trac/jdk1.7.0_02 jar=`find plugins -name 'com.sap.sailing.expeditionconnector_*.jar' | sort | tail -1` echo Using JAR file $jar diff --git a/java/com.googlecode.java-diff-utils.test/test/mocks/5A.txt b/java/com.googlecode.java-diff-utils.test/test/mocks/5A.txt index 96e51b80277..cad583b657b 100755 --- a/java/com.googlecode.java-diff-utils.test/test/mocks/5A.txt +++ b/java/com.googlecode.java-diff-utils.test/test/mocks/5A.txt @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # # Copyright (c) 2006 Johannes E. Schindelin # diff --git a/java/com.googlecode.java-diff-utils.test/test/mocks/5B.txt b/java/com.googlecode.java-diff-utils.test/test/mocks/5B.txt index dd12cf4f7db..0cc81352675 100755 --- a/java/com.googlecode.java-diff-utils.test/test/mocks/5B.txt +++ b/java/com.googlecode.java-diff-utils.test/test/mocks/5B.txt @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # # Copyright (c) 2006 Johannes E. Schindelin # diff --git a/java/com.sap.sailing.domain.test/runUnicodeTest b/java/com.sap.sailing.domain.test/runUnicodeTest index b745b98b218..39d0ab0757e 100755 --- a/java/com.sap.sailing.domain.test/runUnicodeTest +++ b/java/com.sap.sailing.domain.test/runUnicodeTest @@ -1,2 +1,2 @@ -#!/bin/sh +#!/bin/bash java -Dfile.encoding=en_US.UTF-8 -cp bin/com.sap.sailing.domain.test-1.0.0-SNAPSHOT.jar:../com.sap.sailing.domain.tractracadapter/bin/com.sap.sailing.domain.tractracadapter-1.0.0-SNAPSHOT.jar:../com.sap.sailing.domain/bin/com.sap.sailing.domain-1.0.0-SNAPSHOT.jar:../com.tractrac.clientmodule/bin/com.tractrac.clientmodule-1.0.0-SNAPSHOT.jar:../com.tractrac.clientmodule/lib/TracTracClientModule.jar:../com.googlecode.java-diff-utils/bin/com.googlecode.java-diff-utils-1.3.0-SNAPSHOT.jar com.sap.sailing.domain.test.UnicodeCharactersInCompetitorNamesTest diff --git a/java/target/env.sh b/java/target/env.sh index 376d8c01fc6..c2779c92ede 100644 --- a/java/target/env.sh +++ b/java/target/env.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # This file contains the configuration for the Java server diff --git a/java/target/http2udpmirror b/java/target/http2udpmirror index de1e1ea58bb..2a3ab9e32ea 100755 --- a/java/target/http2udpmirror +++ b/java/target/http2udpmirror @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash target="`dirname \"$0\"`" pwd=`pwd` echo "$target" | grep "^/" diff --git a/java/target/http2udpmirror_axel b/java/target/http2udpmirror_axel index b15ffc64a73..04568157647 100755 --- a/java/target/http2udpmirror_axel +++ b/java/target/http2udpmirror_axel @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash target="`dirname \"$0\"`" pwd=`pwd` echo "$target" | grep "^/" diff --git a/java/target/http2udpmirror_axel_proxy b/java/target/http2udpmirror_axel_proxy index c5593666307..bfbda7c2d59 100755 --- a/java/target/http2udpmirror_axel_proxy +++ b/java/target/http2udpmirror_axel_proxy @@ -1,5 +1,4 @@ -#!/bin/sh -#!/bin/sh +#!/bin/bash target="`dirname \"$0\"`" pwd=`pwd` echo "$target" | grep "^/" diff --git a/java/target/importdeclination b/java/target/importdeclination index 073671a85f3..3b70aa414e9 100755 --- a/java/target/importdeclination +++ b/java/target/importdeclination @@ -1,5 +1,4 @@ -#!/bin/sh -#!/bin/sh +#!/bin/bash target="`dirname \"$0\"`" pwd=`pwd` echo "$target" | grep "^/" diff --git a/java/target/refreshInstance.sh b/java/target/refreshInstance.sh index 4eb0499e9f1..60ccbe3ea4f 100755 --- a/java/target/refreshInstance.sh +++ b/java/target/refreshInstance.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash source `pwd`/env.sh DATE_OF_EXECUTION=`date` diff --git a/java/target/start b/java/target/start index 5a435cc0fc6..8023b45cc0b 100755 --- a/java/target/start +++ b/java/target/start @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # source variables if [ "$TMP" = "" ]; then diff --git a/java/target/status b/java/target/status index 672a233c5b5..856f7deae0a 100755 --- a/java/target/status +++ b/java/target/status @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # source variables . `pwd`/env.sh diff --git a/java/target/swisstiminglistener b/java/target/swisstiminglistener index 6f0fadf5d6c..ad52f50b0fb 100755 --- a/java/target/swisstiminglistener +++ b/java/target/swisstiminglistener @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash MONGO_VERSION=2.6.2 cd plugins jar xvf com.mongodb.driver_${MONGO_VERSION}.jar lib/ diff --git a/java/target/udpmirror b/java/target/udpmirror index 012ae9189dd..23c86a0cebb 100755 --- a/java/target/udpmirror +++ b/java/target/udpmirror @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash jar=`find plugins -name 'com.sap.sailing.expeditionconnector_*.jar' | sort | tail -1` echo Using JAR file $jar java -cp "$jar" -Djava.util.logging.config.file=udpmirrorLog.properties com.sap.sailing.expeditionconnector.UDPMirror $* diff --git a/java/target/udpmirrorOnAStick b/java/target/udpmirrorOnAStick index 02355947036..26bde2746c9 100755 --- a/java/target/udpmirrorOnAStick +++ b/java/target/udpmirrorOnAStick @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash target="`dirname \"$0\"`" pwd=`pwd` echo "$target" | grep "^/" diff --git a/java/target/updateIPToBoat2 b/java/target/updateIPToBoat2 index 83adbc08fbd..d3d065b3e73 100755 --- a/java/target/updateIPToBoat2 +++ b/java/target/updateIPToBoat2 @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # updates a Windows machine's WiFi IP address to the sapsailing.com apache configuration # using the updateRedirect CGI-BIN script on the server side # Note the "grep" for the German output of ipconfig on a German Windows installation... diff --git a/stick/scripts/refreshInstance.sh b/stick/scripts/refreshInstance.sh index f6dc432fb0a..a79f0ad936c 100755 --- a/stick/scripts/refreshInstance.sh +++ b/stick/scripts/refreshInstance.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash source `pwd`/env.sh DATE_OF_EXECUTION=`date` diff --git a/stick/start.sh b/stick/start.sh index 17f16491232..ee318230c63 100755 --- a/stick/start.sh +++ b/stick/start.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # source variables . `pwd`/env.sh diff --git a/stick/templates/env.sh.template b/stick/templates/env.sh.template index aaccea1b66b..92023b2046f 100755 --- a/stick/templates/env.sh.template +++ b/stick/templates/env.sh.template @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # Set this to either Mac or Linux # Use env.bat to configure settings From 277f8c6a05db3609989a0a728aec91bc62ed4e50 Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Wed, 16 Jul 2014 11:42:14 +0200 Subject: [PATCH 083/323] Moved CountingOutputStream to com.sap.sse.common to make it reusable --- .../src/com/sap/sse/common}/CountingOutputStream.java | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename java/{com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl => com.sap.sse.common/src/com/sap/sse/common}/CountingOutputStream.java (100%) diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/CountingOutputStream.java b/java/com.sap.sse.common/src/com/sap/sse/common/CountingOutputStream.java similarity index 100% rename from java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/CountingOutputStream.java rename to java/com.sap.sse.common/src/com/sap/sse/common/CountingOutputStream.java From 732dac0da5a941ecde88ae441bdaf69a3787f7b5 Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Wed, 16 Jul 2014 11:42:54 +0200 Subject: [PATCH 084/323] Added more logging and show how many messages have been sent --- .../sailing/gwt/ui/adminconsole/ReplicationPanel.java | 4 ++++ .../com/sap/sailing/gwt/ui/client/StringMessages.java | 1 + .../sailing/gwt/ui/client/StringMessages.properties | 1 + .../gwt/ui/client/StringMessages_de.properties | 1 + .../gwt/ui/client/StringMessages_ru.properties | 1 + .../sap/sailing/gwt/ui/server/SailingServiceImpl.java | 3 ++- .../com/sap/sailing/gwt/ui/shared/ReplicaDTO.java | 8 +++++++- .../server/replication/ReplicationService.java | 2 ++ .../server/replication/impl/ReplicaDescriptor.java | 4 ++++ .../replication/impl/ReplicationInstancesManager.java | 11 +++++++++++ .../impl/ReplicationMasterDescriptorImpl.java | 1 + .../replication/impl/ReplicationServiceImpl.java | 7 +++++++ .../server/replication/impl/ReplicationServlet.java | 1 + .../src/com/sap/sse/common/CountingOutputStream.java | 10 ++++++++-- 14 files changed, 51 insertions(+), 4 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java index f4f44fcf2fb..232235288f6 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java @@ -220,6 +220,10 @@ public class ReplicationPanel extends FlowPanel { registeredReplicas.setWidget(i, 1, new Label(stringMessages.averageNumberOfOperationsPerMessage())); registeredReplicas.setWidget(i, 2, new Label(""+replica.getAverageNumberOfOperationsPerMessage())); i++; + registeredReplicas.insertRow(i); + registeredReplicas.setWidget(i, 1, new Label(stringMessages.numberOfQueueMessagesSent())); + registeredReplicas.setWidget(i, 2, new Label(""+replica.getNumberOfMessagesSent())); + i++; for (Map.Entry e : replica.getOperationCountByOperationClassName().entrySet()) { registeredReplicas.insertRow(i); registeredReplicas.setWidget(i, 1, new Label(e.getKey())); 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 513cfa45ac4..648f542cd0d 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 @@ -1057,4 +1057,5 @@ public interface StringMessages extends Messages { String lastScoreCorrectionsComment(); String setTimeToNow(); String useStartTimeInference(); + String numberOfQueueMessagesSent(); } 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 7820263b122..d6ed96cc3ca 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 @@ -1041,3 +1041,4 @@ lastScoreCorrectionsTime=Last score correction time lastScoreCorrectionsComment=Last score correction comment setTimeToNow=Set time to ''now'' useStartTimeInference=Use start time inference from start mark passings +numberOfQueueMessagesSent=Number of queue messages sent \ 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 89ca4a7bc5e..5a45812402e 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 @@ -1036,3 +1036,4 @@ lastScoreCorrectionsTime=Letzter Zeitpunkt der Punktzahl-Korrektur lastScoreCorrectionsComment=Letzter Kommentar der Punktzahl-Korrektur setTimeToNow=Setze den Zeitpunkt auf ''Jetzt'' useStartTimeInference=Startzeit aus Startlinienüberquerungen automatisch ableiten +numberOfQueueMessagesSent=Anzahl verschickter Queue Nachrichten \ 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_ru.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties index ee7a000e495..491397735ea 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 @@ -991,3 +991,4 @@ regattaNameMustNotContainSlashes=Regatta name must not contain slashes averageNumberOfOperationsPerMessage=Average number of operations per message showUncorrectedNetPoints=Show uncorrected net points useStartTimeInference=Use start time inference from start mark passings +numberOfQueueMessagesSent=Number of queue messages sent \ No newline at end of file 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 dcaf291917d..6d9999345d7 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 @@ -2951,7 +2951,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } replicaDTOs.add(new ReplicaDTO(replicaDescriptor.getIpAddress().getHostName(), replicaDescriptor .getRegistrationTime().asDate(), replicaDescriptor.getUuid().toString(), - replicationCountByOperationClassName, service.getAverageNumberOfOperationsPerMessage(replicaDescriptor))); + replicationCountByOperationClassName, service.getAverageNumberOfOperationsPerMessage(replicaDescriptor), + service.getNumberOfMessagesSent(replicaDescriptor))); } ReplicationMasterDTO master; ReplicationMasterDescriptor replicatingFromMaster = service.isReplicatingFromMaster(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ReplicaDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ReplicaDTO.java index 2cf302efc2e..95173f4d434 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ReplicaDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ReplicaDTO.java @@ -11,14 +11,17 @@ public class ReplicaDTO implements IsSerializable { private Date registrationTime; private Map operationCountByOperationClassName; private double averageNumberOfOperationsPerMessage; + private long numberOfMessagesSent; ReplicaDTO() {} public ReplicaDTO(String hostname, Date registrationTime, - String identifier, Map operationCountByOperationClassName, double averageNumberOfOperationsPerMessage) { + String identifier, Map operationCountByOperationClassName, double averageNumberOfOperationsPerMessage, + long numberOfMessagesSent) { this.hostname = hostname; this.identifier = identifier; this.registrationTime = registrationTime; this.operationCountByOperationClassName = operationCountByOperationClassName; this.averageNumberOfOperationsPerMessage = averageNumberOfOperationsPerMessage; + this.numberOfMessagesSent = numberOfMessagesSent; } public String getHostname() { return hostname; @@ -32,6 +35,9 @@ public class ReplicaDTO implements IsSerializable { public double getAverageNumberOfOperationsPerMessage() { return averageNumberOfOperationsPerMessage; } + public long getNumberOfMessagesSent() { + return numberOfMessagesSent; + } public String getIdentifier() { return identifier; } diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/ReplicationService.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/ReplicationService.java index 6481da2da5d..7deafe650cd 100644 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/ReplicationService.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/ReplicationService.java @@ -68,4 +68,6 @@ public interface ReplicationService { UUID getServerIdentifier(); Channel createMasterChannel() throws IOException, ConnectException; + + long getNumberOfMessagesSent(ReplicaDescriptor replica); } diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicaDescriptor.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicaDescriptor.java index 3ebcc7dc193..c698e9e2559 100644 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicaDescriptor.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicaDescriptor.java @@ -72,5 +72,9 @@ public class ReplicaDescriptor implements Serializable { return false; return true; } + + public String toString() { + return ""+uuid+": "+ipAddress+" ("+additionalInformation+")"; + } } diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationInstancesManager.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationInstancesManager.java index baf87b5564e..513fc79a45d 100644 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationInstancesManager.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationInstancesManager.java @@ -131,6 +131,17 @@ public class ReplicationInstancesManager { } return result; } + + public long getNumberOfMessagesSent(ReplicaDescriptor replica) { + final long result; + Long messageCount = totalMessageCount.get(replica); + if (messageCount != null) { + result = messageCount.longValue(); + } else { + result = 0l; + } + return result; + } public void removeAll() { replicationCounts.clear(); diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationMasterDescriptorImpl.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationMasterDescriptorImpl.java index 008454cd8f6..95072ce5e7e 100755 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationMasterDescriptorImpl.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationMasterDescriptorImpl.java @@ -134,6 +134,7 @@ public class ReplicationMasterDescriptorImpl implements ReplicationMasterDescrip } catch (Exception ex) { // ignore any exception during abort. close can yield a broad // number of exceptions that we don't want to know or to log. + ex.printStackTrace(); } } diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java index c692f85d33f..4aedd159b32 100644 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java @@ -217,6 +217,7 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec } } replicationInstancesManager.registerReplica(replica); + logger.info("Registered replica " + replica); } private void addAsListenerToRacingEventService() { @@ -225,6 +226,7 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec @Override public void unregisterReplica(ReplicaDescriptor replica) throws IOException { + logger.info("Unregistering replica " + replica); replicationInstancesManager.unregisterReplica(replica); if (!replicationInstancesManager.hasReplicas()) { removeAsListenerFromRacingEventService(); @@ -415,6 +417,11 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec public double getAverageNumberOfOperationsPerMessage(ReplicaDescriptor replicaDescriptor) { return replicationInstancesManager.getAverageNumberOfOperationsPerMessage(replicaDescriptor); } + + @Override + public long getNumberOfMessagesSent(ReplicaDescriptor replica) { + return replicationInstancesManager.getNumberOfMessagesSent(replica); + } @Override public void stopToReplicateFromMaster() throws IOException { diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServlet.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServlet.java index 2e63f0e8101..33be30edcea 100755 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServlet.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServlet.java @@ -26,6 +26,7 @@ import com.rabbitmq.client.ConnectionFactory; import com.sap.sailing.server.RacingEventService; import com.sap.sailing.server.gateway.SailingServerHttpServlet; import com.sap.sailing.server.replication.ReplicationService; +import com.sap.sse.common.CountingOutputStream; /** * As the response to any type of GET request, sends a serialized copy of the {@link RacingEventService} to diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/CountingOutputStream.java b/java/com.sap.sse.common/src/com/sap/sse/common/CountingOutputStream.java index bcac23e6ee6..1b4aed43055 100755 --- a/java/com.sap.sse.common/src/com/sap/sse/common/CountingOutputStream.java +++ b/java/com.sap.sse.common/src/com/sap/sse/common/CountingOutputStream.java @@ -1,4 +1,4 @@ -package com.sap.sailing.server.replication.impl; +package com.sap.sse.common; import java.io.FilterOutputStream; import java.io.IOException; @@ -6,6 +6,12 @@ import java.io.OutputStream; import java.util.logging.Level; import java.util.logging.Logger; +/** + * Stream decorator that counts the bytes sent through an {@link OutputStream}. Logs + * that number in configurable way using a {@link Logger}. + * + * @author Axel Uhl + */ public class CountingOutputStream extends FilterOutputStream { private static final Logger logger = Logger.getLogger(CountingOutputStream.class.getName()); @@ -37,7 +43,7 @@ public class CountingOutputStream extends FilterOutputStream { } private void log() { - logger.log(level, "wrote "+count+" bytes to "+name); + logger.log(level, "wrote "+count+" bytes"+(count>1000?"("+count/1000+" kbytes)":"")+" to "+name); } @Override From d640b5c1eaca1f3fb0c3cd0c3fc35b0b846c4ab3 Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Wed, 16 Jul 2014 12:07:30 +0200 Subject: [PATCH 085/323] Added more logging --- .../gwt/ui/adminconsole/ReplicationPanel.java | 8 ++++ .../sailing/gwt/ui/client/StringMessages.java | 2 + .../gwt/ui/client/StringMessages.properties | 4 +- .../ui/client/StringMessages_de.properties | 4 +- .../ui/client/StringMessages_ru.properties | 4 +- .../gwt/ui/server/SailingServiceImpl.java | 2 +- .../sap/sailing/gwt/ui/shared/ReplicaDTO.java | 12 +++++- ...nstancesManagerLoggingPerformanceTest.java | 4 +- .../replication/ReplicationService.java | 4 ++ .../impl/ReplicationInstancesManager.java | 40 ++++++++++++++++++- .../impl/ReplicationServiceImpl.java | 15 ++++++- 11 files changed, 89 insertions(+), 10 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java index 232235288f6..61b17c3def8 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java @@ -224,6 +224,14 @@ public class ReplicationPanel extends FlowPanel { registeredReplicas.setWidget(i, 1, new Label(stringMessages.numberOfQueueMessagesSent())); registeredReplicas.setWidget(i, 2, new Label(""+replica.getNumberOfMessagesSent())); i++; + registeredReplicas.insertRow(i); + registeredReplicas.setWidget(i, 1, new Label(stringMessages.averageMessageSize())); + registeredReplicas.setWidget(i, 2, new Label(""+replica.getAverageMessageSizeInBytes())); + i++; + registeredReplicas.insertRow(i); + registeredReplicas.setWidget(i, 1, new Label(stringMessages.totalSize())); + registeredReplicas.setWidget(i, 2, new Label(""+replica.getNumberOfBytesSent()+"b ("+replica.getNumberOfBytesSent()/1000.0+"kb)")); + i++; for (Map.Entry e : replica.getOperationCountByOperationClassName().entrySet()) { registeredReplicas.insertRow(i); registeredReplicas.setWidget(i, 1, new Label(e.getKey())); 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 648f542cd0d..bd565abe8f0 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 @@ -1058,4 +1058,6 @@ public interface StringMessages extends Messages { String setTimeToNow(); String useStartTimeInference(); String numberOfQueueMessagesSent(); + String totalSize(); + String averageMessageSize(); } 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 d6ed96cc3ca..0294a8ee6de 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 @@ -1041,4 +1041,6 @@ lastScoreCorrectionsTime=Last score correction time lastScoreCorrectionsComment=Last score correction comment setTimeToNow=Set time to ''now'' useStartTimeInference=Use start time inference from start mark passings -numberOfQueueMessagesSent=Number of queue messages sent \ No newline at end of file +numberOfQueueMessagesSent=Number of queue messages sent +totalSize=Total Size +averageMessageSize=Average Size per message \ 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 5a45812402e..a42909455ad 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 @@ -1036,4 +1036,6 @@ lastScoreCorrectionsTime=Letzter Zeitpunkt der Punktzahl-Korrektur lastScoreCorrectionsComment=Letzter Kommentar der Punktzahl-Korrektur setTimeToNow=Setze den Zeitpunkt auf ''Jetzt'' useStartTimeInference=Startzeit aus Startlinienüberquerungen automatisch ableiten -numberOfQueueMessagesSent=Anzahl verschickter Queue Nachrichten \ No newline at end of file +numberOfQueueMessagesSent=Anzahl verschickter Queue Nachrichten +totalSize=Gesamtgröße +averageMessageSize=Durchschnittsgrösse einer Nachricht \ 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_ru.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties index 491397735ea..cb28b5c1f37 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 @@ -991,4 +991,6 @@ regattaNameMustNotContainSlashes=Regatta name must not contain slashes averageNumberOfOperationsPerMessage=Average number of operations per message showUncorrectedNetPoints=Show uncorrected net points useStartTimeInference=Use start time inference from start mark passings -numberOfQueueMessagesSent=Number of queue messages sent \ No newline at end of file +numberOfQueueMessagesSent=Number of queue messages sent +totalSize=Total Size +averageMessageSize=Average Size per message \ No newline at end of file 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 6d9999345d7..73f9535a49b 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 @@ -2952,7 +2952,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S replicaDTOs.add(new ReplicaDTO(replicaDescriptor.getIpAddress().getHostName(), replicaDescriptor .getRegistrationTime().asDate(), replicaDescriptor.getUuid().toString(), replicationCountByOperationClassName, service.getAverageNumberOfOperationsPerMessage(replicaDescriptor), - service.getNumberOfMessagesSent(replicaDescriptor))); + service.getNumberOfMessagesSent(replicaDescriptor), service.getNumberOfBytesSent(replicaDescriptor), service.getAverageNumberOfBytesPerMessage(replicaDescriptor))); } ReplicationMasterDTO master; ReplicationMasterDescriptor replicatingFromMaster = service.isReplicatingFromMaster(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ReplicaDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ReplicaDTO.java index 95173f4d434..21019522bf6 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ReplicaDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ReplicaDTO.java @@ -12,16 +12,20 @@ public class ReplicaDTO implements IsSerializable { private Map operationCountByOperationClassName; private double averageNumberOfOperationsPerMessage; private long numberOfMessagesSent; + private double averageMessageSizeInBytes; + private long totalNumberOfBytesSent; ReplicaDTO() {} public ReplicaDTO(String hostname, Date registrationTime, String identifier, Map operationCountByOperationClassName, double averageNumberOfOperationsPerMessage, - long numberOfMessagesSent) { + long numberOfMessagesSent, long totalNumberOfBytesSent, double averageMessageSizeInBytes) { this.hostname = hostname; this.identifier = identifier; this.registrationTime = registrationTime; this.operationCountByOperationClassName = operationCountByOperationClassName; this.averageNumberOfOperationsPerMessage = averageNumberOfOperationsPerMessage; this.numberOfMessagesSent = numberOfMessagesSent; + this.averageMessageSizeInBytes = averageMessageSizeInBytes; + this.totalNumberOfBytesSent = totalNumberOfBytesSent; } public String getHostname() { return hostname; @@ -38,6 +42,12 @@ public class ReplicaDTO implements IsSerializable { public long getNumberOfMessagesSent() { return numberOfMessagesSent; } + public long getNumberOfBytesSent() { + return totalNumberOfBytesSent; + } + public double getAverageMessageSizeInBytes() { + return averageMessageSizeInBytes; + } public String getIdentifier() { return identifier; } 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 e6853066917..c77029eb38c 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 @@ -33,7 +33,7 @@ public class ReplicationInstancesManagerLoggingPerformanceTest { public void testLoggingPerformance() { final int count = 10000000; for (int i=0; i>singletonList(operation.getClass())); + replicationInstanceManager.log(Collections.>singletonList(operation.getClass()), 0l); } assertEquals(count, replicationInstanceManager.getStatistics(replica).get(operation.getClass()).intValue()); assertEquals(1.0, replicationInstanceManager.getAverageNumberOfOperationsPerMessage(replica), 0.0000001); @@ -43,7 +43,7 @@ public class ReplicationInstancesManagerLoggingPerformanceTest { public void testLoggingAverages() { final int count = 1000; for (int i=0; i[] { operation.getClass(), operation.getClass() })); + replicationInstanceManager.log(Arrays.asList(new Class[] { operation.getClass(), operation.getClass() }), 0l); } assertEquals(2*count, replicationInstanceManager.getStatistics(replica).get(operation.getClass()).intValue()); assertEquals(2.0, replicationInstanceManager.getAverageNumberOfOperationsPerMessage(replica), 0.0000001); diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/ReplicationService.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/ReplicationService.java index 7deafe650cd..2729c37df2a 100644 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/ReplicationService.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/ReplicationService.java @@ -70,4 +70,8 @@ public interface ReplicationService { Channel createMasterChannel() throws IOException, ConnectException; long getNumberOfMessagesSent(ReplicaDescriptor replica); + + long getNumberOfBytesSent(ReplicaDescriptor replica); + + double getAverageNumberOfBytesPerMessage(ReplicaDescriptor replica); } diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationInstancesManager.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationInstancesManager.java index 513fc79a45d..516e3925564 100644 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationInstancesManager.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationInstancesManager.java @@ -35,6 +35,11 @@ public class ReplicationInstancesManager { */ private final Map totalMessageCount; + /** + * Contains the size of all messages sent over + */ + private final Map totalQueueMessagesRawSizeInBytes; + /** * The descriptor of the replication master */ @@ -45,6 +50,7 @@ public class ReplicationInstancesManager { replicationCounts = new HashMap>,Integer>>(); totalMessageCount = new HashMap<>(); totalNumberOfOperations = new HashMap<>(); + totalQueueMessagesRawSizeInBytes = new HashMap(); } /** @@ -81,7 +87,7 @@ public class ReplicationInstancesManager { * * @see #getStatistics */ - public void log(List> classes) { + public void log(List> classes, long sizeOfQueueMessageInBytes) { for (ReplicaDescriptor replica : getReplicaDescriptors()) { Map>, Integer> counts = replicationCounts.get(replica); if (counts == null) { @@ -109,6 +115,11 @@ public class ReplicationInstancesManager { totalMessages = 0l; } totalMessageCount.put(replica, ++totalMessages); + Long totalQueueMessagesSizeInBytes = totalQueueMessagesRawSizeInBytes.get(replica); + if (totalQueueMessagesSizeInBytes == null) { + totalQueueMessagesSizeInBytes = 0l; + } + totalQueueMessagesRawSizeInBytes.put(replica, totalQueueMessagesSizeInBytes+sizeOfQueueMessageInBytes); } } @@ -116,6 +127,17 @@ public class ReplicationInstancesManager { return replicationCounts.get(replica); } + public long getNumberOfBytesSent(ReplicaDescriptor replica) { + final long result; + Long totalQueueMessageSize = totalQueueMessagesRawSizeInBytes.get(replica); + if (totalQueueMessageSize == null) { + result = 0l; + } else { + result = totalQueueMessageSize.longValue(); + } + return result; + } + public double getAverageNumberOfOperationsPerMessage(ReplicaDescriptor replica) { final double result; Long messageCount = totalMessageCount.get(replica); @@ -132,6 +154,22 @@ public class ReplicationInstancesManager { return result; } + public double getAverageNumberOfBytesPerMessage(ReplicaDescriptor replica) { + final double result; + Long messageCount = totalMessageCount.get(replica); + if (messageCount != null) { + Long messageSizeInTotal = totalQueueMessagesRawSizeInBytes.get(replica); + if (messageSizeInTotal == null) { + result = 0; + } else { + result = ((double) messageSizeInTotal)/(double) messageCount; + } + } else { + result = 0; + } + return result; + } + public long getNumberOfMessagesSent(ReplicaDescriptor replica) { final long result; Long messageCount = totalMessageCount.get(replica); diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java index 4aedd159b32..43d2bf01abb 100644 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java @@ -293,8 +293,9 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec oos.close(); // copy serialized operations into message if (masterChannel != null) { + final int queueMessageSize = buf.size(); masterChannel.basicPublish(exchangeName, /* routingKey */"", /* properties */null, buf.toByteArray()); - replicationInstancesManager.log(classes); + replicationInstancesManager.log(classes,queueMessageSize); } } @@ -422,7 +423,17 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec public long getNumberOfMessagesSent(ReplicaDescriptor replica) { return replicationInstancesManager.getNumberOfMessagesSent(replica); } - + + @Override + public long getNumberOfBytesSent(ReplicaDescriptor replica) { + return replicationInstancesManager.getNumberOfBytesSent(replica); + } + + @Override + public double getAverageNumberOfBytesPerMessage(ReplicaDescriptor replica) { + return replicationInstancesManager.getAverageNumberOfBytesPerMessage(replica); + } + @Override public void stopToReplicateFromMaster() throws IOException { ReplicationMasterDescriptor descriptor = isReplicatingFromMaster(); From 7d54a268db587c9465c1b4b85dd0be026522ffba Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Wed, 16 Jul 2014 12:25:44 +0200 Subject: [PATCH 086/323] Clarified message --- .../com/sap/sailing/gwt/ui/client/StringMessages.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 0294a8ee6de..3ed82c32022 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 @@ -1043,4 +1043,4 @@ setTimeToNow=Set time to ''now'' useStartTimeInference=Use start time inference from start mark passings numberOfQueueMessagesSent=Number of queue messages sent totalSize=Total Size -averageMessageSize=Average Size per message \ No newline at end of file +averageMessageSize=Average size per message in bytes \ No newline at end of file From 3833e36886d0c40711f87bf6b3f3ff2e3972cd49 Mon Sep 17 00:00:00 2001 From: wagnst Date: Wed, 16 Jul 2014 12:48:58 +0200 Subject: [PATCH 087/323] added user for d059727 (steffen.tobias.wagner@sap.com) --- java/target/configuration/jetty/etc/realm.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java/target/configuration/jetty/etc/realm.properties b/java/target/configuration/jetty/etc/realm.properties index 291402855fb..d8ae3f87494 100644 --- a/java/target/configuration/jetty/etc/realm.properties +++ b/java/target/configuration/jetty/etc/realm.properties @@ -23,6 +23,7 @@ stefan: stefan,spectator,moderator,administrator riccardo: riccardo,spectator,moderator,administrator christopher: christopher,spectator,moderator,administrator armin: armin,spectator,moderator,administrator +steffen: steffen,spectator,moderator,administrator jacob@sejlsport.dk: !hsf/sf67, eventmanager m.welte@stg-academy.org: mwelte, eventmanager -e.anders@stg-academy.org: eanders, eventmanager +e.anders@stg-academy.org: eanders, eventmanager \ No newline at end of file From 89a69ea777dadb8fa2b136181f76d150bf81b5d4 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jul 2014 11:00:42 +0000 Subject: [PATCH 088/323] Updated tw2014 (markdown) --- wiki/tw2014.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wiki/tw2014.md b/wiki/tw2014.md index d87eab260ff..1b08db80a05 100644 --- a/wiki/tw2014.md +++ b/wiki/tw2014.md @@ -3,9 +3,10 @@ Two systems for seamless switching: - "A": ec2-54-77-31-168.eu-west-1.compute.amazonaws.com (internal IP: 172.31.16.247; external IP: 54.77.31.168), AdminConsole at [http://ec2-54-77-31-168.eu-west-1.compute.amazonaws.com:8888/gwt/AdminConsole.html](http://ec2-54-77-31-168.eu-west-1.compute.amazonaws.com:8888/gwt/AdminConsole.html) - - "B": (not yet set up) + - "B": ec2-54-77-31-168.eu-west-1.compute.amazonaws.com (internal IP: 172.31.26.250; external IP: 54.77.58.45), AdminConsole at [http://ec2-54-77-58-45.eu-west-1.compute.amazonaws.com:8888/gwt/AdminConsole.html](http://ec2-54-77-58-45.eu-west-1.compute.amazonaws.com:8888/gwt/AdminConsole.html) Event URL: [http://tw2014.sapsailing.com](http://tw2014.sapsailing.com) +Regatta Overview: [http://tw2014ro.sapsailing.com](http://tw2014ro.sapsailing.com) "Advanced Details" for master: From 7ea81e9af2075b3bc84551f18d26f9d56718539b Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jul 2014 11:07:45 +0000 Subject: [PATCH 089/323] Updated tw2014 (markdown) --- wiki/tw2014.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wiki/tw2014.md b/wiki/tw2014.md index 1b08db80a05..18135083c7d 100644 --- a/wiki/tw2014.md +++ b/wiki/tw2014.md @@ -14,7 +14,7 @@ Regatta Overview: [http://tw2014ro.sapsailing.com](http://tw2014ro.sapsailing.co MONGODB_NAME=TW2014 REPLICATION_CHANNEL=TW2014 SERVER_STARTUP_NOTIFY=axel.uhl@sap.com -SERVER_NAME=KW2014 +SERVER_NAME=TW2014 USE_ENVIRONMENT=live-server INSTALL_FROM_RELEASE=tw2014-release-201406260007 MEMORY=20000m From 7f638d2873bcf2897e8b0c5cba08e064073200d7 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jul 2014 11:09:00 +0000 Subject: [PATCH 090/323] Added Manage2Sail URL to tw2014 event page --- wiki/tw2014.md | 1 + 1 file changed, 1 insertion(+) diff --git a/wiki/tw2014.md b/wiki/tw2014.md index 18135083c7d..ca3ef7c0600 100644 --- a/wiki/tw2014.md +++ b/wiki/tw2014.md @@ -7,6 +7,7 @@ Two systems for seamless switching: Event URL: [http://tw2014.sapsailing.com](http://tw2014.sapsailing.com) Regatta Overview: [http://tw2014ro.sapsailing.com](http://tw2014ro.sapsailing.com) +Manage2Sail Event URL: [http://manage2sail.com/api/public/links/event/4dbb137b-dbae-4e0c-85d3-e1ac056f5684?accesstoken=bDAv8CwsTM94ujZ&mediaType=json](http://manage2sail.com/api/public/links/event/4dbb137b-dbae-4e0c-85d3-e1ac056f5684?accesstoken=bDAv8CwsTM94ujZ&mediaType=json) "Advanced Details" for master: From d21f350d2ccbab901133d252bde0590affb55f46 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jul 2014 11:15:23 +0000 Subject: [PATCH 091/323] Formatting of tw2014 page --- wiki/tw2014.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wiki/tw2014.md b/wiki/tw2014.md index ca3ef7c0600..300ef0ff457 100644 --- a/wiki/tw2014.md +++ b/wiki/tw2014.md @@ -5,9 +5,9 @@ Two systems for seamless switching: - "A": ec2-54-77-31-168.eu-west-1.compute.amazonaws.com (internal IP: 172.31.16.247; external IP: 54.77.31.168), AdminConsole at [http://ec2-54-77-31-168.eu-west-1.compute.amazonaws.com:8888/gwt/AdminConsole.html](http://ec2-54-77-31-168.eu-west-1.compute.amazonaws.com:8888/gwt/AdminConsole.html) - "B": ec2-54-77-31-168.eu-west-1.compute.amazonaws.com (internal IP: 172.31.26.250; external IP: 54.77.58.45), AdminConsole at [http://ec2-54-77-58-45.eu-west-1.compute.amazonaws.com:8888/gwt/AdminConsole.html](http://ec2-54-77-58-45.eu-west-1.compute.amazonaws.com:8888/gwt/AdminConsole.html) -Event URL: [http://tw2014.sapsailing.com](http://tw2014.sapsailing.com) -Regatta Overview: [http://tw2014ro.sapsailing.com](http://tw2014ro.sapsailing.com) -Manage2Sail Event URL: [http://manage2sail.com/api/public/links/event/4dbb137b-dbae-4e0c-85d3-e1ac056f5684?accesstoken=bDAv8CwsTM94ujZ&mediaType=json](http://manage2sail.com/api/public/links/event/4dbb137b-dbae-4e0c-85d3-e1ac056f5684?accesstoken=bDAv8CwsTM94ujZ&mediaType=json) + - Event URL: [http://tw2014.sapsailing.com](http://tw2014.sapsailing.com) + - Regatta Overview: [http://tw2014ro.sapsailing.com](http://tw2014ro.sapsailing.com) + - Manage2Sail Event URL: [http://manage2sail.com/api/public/links/event/4dbb137b-dbae-4e0c-85d3-e1ac056f5684?accesstoken=bDAv8CwsTM94ujZ&mediaType=json](http://manage2sail.com/api/public/links/event/4dbb137b-dbae-4e0c-85d3-e1ac056f5684?accesstoken=bDAv8CwsTM94ujZ&mediaType=json) "Advanced Details" for master: From 8765d7c6de042fce7a0a704e85c2559eacac365a Mon Sep 17 00:00:00 2001 From: Jens Rommel Date: Wed, 16 Jul 2014 13:22:12 +0200 Subject: [PATCH 092/323] Successfully ported Youtube video embedding. --- .../client/shared/mainmedia/MainMedia.css | 29 +++++++---- .../client/shared/mainmedia/MainMedia.java | 16 ++++--- .../shared/mainmedia/MainMediaResources.java | 2 + .../shared/mainmedia/MainMediaVideo.java | 48 +++---------------- .../shared/mainmedia/MainMediaVideo.ui.xml | 9 ++-- .../gwt/ui/client/media/NewMediaDialog.java | 18 +------ .../gwt/ui/common/client/YoutubeApi.java | 46 ++++++++++++++++++ 7 files changed, 92 insertions(+), 76 deletions(-) create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/YoutubeApi.java diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css index d523037693f..c774dce192b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.css @@ -6,7 +6,7 @@ .media_swipecontainer { position: relative; width: 830px; - height: 550px; + height: 622px; margin: 0 auto; overflow: hidden; } @@ -40,7 +40,7 @@ transform: translate(0, -50%); width: 60px; height: 60px; - background-image: url("images/home/media-controls__arrows.png"); + background-image: url("images/home/media-controls__arrows.png"); } .media_slideshow_controlsprev { @@ -62,7 +62,7 @@ } .media_slideshow_controlsfullsize { - background-image: url("images/home/media-controls__fullsize.png"); + background-image: url("images/home/media-controls__fullsize.png"); right: 0; bottom: 0; width: 40px; @@ -82,7 +82,6 @@ font-size: 15px; font-size: 1rem; margin-bottom: 2.666666666666667em; - cursor: pointer; } .videopreview:before { @@ -97,19 +96,33 @@ transform: translate(-50%, 0); width: 69px; height: 69px; - background: url("images/home/video__preview--play.png"); + background: url("images/home/video__preview--play.png"); -webkit-background-size: contain; -moz-background-size: contain; background-size: contain; } - +.videopreview_videocontainer { + position: relative; + width: 100%; + height: 0; + padding-bottom: 56.25%; +} +.videopreview_videocontainer_video { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; +} .videopreview_title { font-size: 1.466666666666667em; font-weight: bold; margin: 0; color: #fff; } - +.videopreview img { + margin: 0 auto; +} .videopreview time { display: block; -} \ 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/mainmedia/MainMedia.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.java index 642b4dfbbae..0b7c4303670 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 @@ -17,6 +17,7 @@ import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.home.client.app.PlaceNavigator; import com.sap.sailing.gwt.idangerous.Swiper; +import com.sap.sailing.gwt.ui.common.client.YoutubeApi; import com.sap.sailing.gwt.ui.shared.EventDTO; public class MainMedia extends Composite { @@ -44,22 +45,23 @@ public class MainMedia extends Composite { public void setRecentEvents(List recentEvents) { List photoGalleryUrls = new ArrayList(); - String lightBoxData = ""; int videoCounter = 0; for(EventDTO event: recentEvents) { photoGalleryUrls.addAll(event.getPhotoGalleryImageURLs()); if(event.getVideoURLs().size() > 0 && videoCounter < 3) { - MainMediaVideo video = new MainMediaVideo(event); - lightBoxData += video.getDataForLightBox(); - videosPanel.add(video); - videoCounter++; + String eventName = event.getName(); + String youtubeUrl = event.getVideoURLs().get(0); + String youtubeId = YoutubeApi.getIdByUrl(youtubeUrl); + if (youtubeId != null && !youtubeId.trim().isEmpty()) { + MainMediaVideo video = new MainMediaVideo(eventName, youtubeId); + videosPanel.add(video); + videoCounter++; + } } } - videoLightBoxData.setInnerText(lightBoxData); - // shuffle the image url list (Remark: Collections.shuffle() is not implemented in GWT) int gallerySize = photoGalleryUrls.size(); Random random = new Random(gallerySize); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaResources.java index d6f8749985a..621a94f9348 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaResources.java @@ -22,6 +22,8 @@ public interface MainMediaResources extends ClientBundle { String mediavideos(); String mainsection_header_title(); String videopreview(); + String videopreview_videocontainer(); + String videopreview_videocontainer_video(); String videopreview_title(); } } 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 3e0a234a006..e5509fe7017 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 @@ -1,14 +1,12 @@ package com.sap.sailing.gwt.home.client.shared.mainmedia; import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.ImageElement; +import com.google.gwt.dom.client.IFrameElement; 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.sap.sailing.gwt.ui.shared.EventDTO; public class MainMediaVideo extends Composite { @@ -18,49 +16,15 @@ public class MainMediaVideo extends Composite { private static MainMediaVideoUiBinder uiBinder = GWT.create(MainMediaVideoUiBinder.class); - private final static String DEFAULT_VIDEO_THUMB_IMAGE_URL = "http://static.sapsailing.com/ubilabsimages/default/default_video_preview.jpg"; - - @UiField SpanElement videoTime; + @UiField IFrameElement youtubeEmbed; @UiField SpanElement videoTitle; - @UiField ImageElement videoThumbImage; - @UiField Element videoFile; - - // Format: {video.thumb},{video.file},{video.title},video; - private String lightBoxData; - - public MainMediaVideo(EventDTO event) { + public MainMediaVideo(String eventName, String youtubeId) { MainMediaResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); - - String videoThumbImageUrl; - if(event.getPhotoGalleryImageURLs().size() > 0) { - videoThumbImageUrl = event.getPhotoGalleryImageURLs().get(0); - } else { - videoThumbImageUrl = DEFAULT_VIDEO_THUMB_IMAGE_URL; - } - videoThumbImage.setSrc(videoThumbImageUrl); - lightBoxData = videoThumbImageUrl + "," + event.getVideoURLs().get(0) + ";"; - - videoTime.setInnerHTML("3:40 min"); - videoTitle.setInnerHTML(event.getName()); - videoFile.setAttribute("data-src", event.getVideoURLs().get(0)); + + youtubeEmbed.setSrc("//www.youtube.com/embed/" + youtubeId + "?modestbranding=1&rel=0&showinfo=0&autohide=1&fs=1"); + videoTitle.setInnerHTML(eventName); } - public String getDataForLightBox() { - return lightBoxData; - } - -// @UiHandler("eventMediaPageLink") -// public void goToEventMediaPage(ClickEvent e) { -// if(event.getBaseURL().contains("localhost") || event.getBaseURL().contains("127.0.0.1")) { -// navigator.goToEvent(event.id.toString()); -// } else { -// EventPlace eventPlace = new EventPlace(event.id.toString()); -// EventPlace.Tokenizer t = new Tokenizer(); -// String remoteEventUrl = event.getBaseURL() + "/gwt/Home.html#" + EventPlace.class.getSimpleName() + ":" + t.getToken(eventPlace); -// Window.Location.replace(remoteEventUrl); -// } -// } - } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideo.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideo.ui.xml index 346a6dcfa69..dcdb061528a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideo.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMediaVideo.ui.xml @@ -8,9 +8,12 @@
-
-
- +
+
+ +
+
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/NewMediaDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/NewMediaDialog.java index 4c961e12f61..7edcf05cfd5 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/NewMediaDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/NewMediaDialog.java @@ -8,8 +8,6 @@ import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.media.client.Audio; import com.google.gwt.media.client.MediaBase; -import com.google.gwt.regexp.shared.MatchResult; -import com.google.gwt.regexp.shared.RegExp; import com.google.gwt.user.client.ui.Grid; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.TextBox; @@ -18,6 +16,7 @@ import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.domain.common.media.MediaTrack; import com.sap.sailing.domain.common.media.MediaTrack.MimeType; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.common.client.YoutubeApi; import com.sap.sse.gwt.client.dialog.DataEntryDialog; public class NewMediaDialog extends DataEntryDialog { @@ -131,7 +130,7 @@ public class NewMediaDialog extends DataEntryDialog { protected void updateFromUrl() { String url = urlBox.getValue(); - String youtubeId = extractYoutubeId(url); + String youtubeId = YoutubeApi.getIdByUrl(url); if (youtubeId != null) { mediaTrack.url = youtubeId; @@ -164,19 +163,6 @@ public class NewMediaDialog extends DataEntryDialog { refreshUI(); } - private String extractYoutubeId(String url) { - RegExp YOUTUBE_ID_REGEX = RegExp.compile("^.*(youtu.be/|v/|u/\\w/|embed/|watch\\?v=|\\&v=)([^#\\&\\?]*).*"); // from http://stackoverflow.com/questions/3452546/javascript-regex-how-to-get-youtube-video-id-from-url, mantish Mar 4 at 15:33 - RegExp HTTP_FTP_REGEX = RegExp.compile("^(http|ftp).*"); // starting with http, https or ftp - MatchResult match = YOUTUBE_ID_REGEX.exec(url); - if ((match != null) && (match.getGroupCount() == 3)) { - return match.getGroup(2); - } else if (HTTP_FTP_REGEX.exec(url) == null) { //--> doesn't start with either http, https or ftp --> supposed to be a naked youtube id - return url; - } else { - return null; // --> plain http, https or ftp URL --> no youtube - } - } - protected void setUiEnabled(boolean isEnabled) { urlBox.setEnabled(isEnabled); getOkButton().setEnabled(isEnabled); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/YoutubeApi.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/YoutubeApi.java new file mode 100644 index 00000000000..5ec1cca6b9e --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/YoutubeApi.java @@ -0,0 +1,46 @@ +package com.sap.sailing.gwt.ui.common.client; + +import com.google.gwt.regexp.shared.MatchResult; +import com.google.gwt.regexp.shared.RegExp; + +/** + * See also: + * "10 Youtube URL Tricks You Should Know About" http://www.makeuseof.com/tag/10-youtube-url-tricks-you-should-know-about/ + * "Useful YouTube URL Tricks" http://www.techairlines.com/useful-youtube-url-tricks/ + * "Useful YouTube Player Parameters" http://www.techairlines.com/youtube-parameters/ + * @author D047974 + * + */ +public class YoutubeApi { + + private static final RegExp YOUTUBE_ID_REGEX = RegExp.compile("^.*(youtu.be/|v/|u/\\w/|embed/|watch\\?v=|\\&v=)([^#\\&\\?]*).*"); + private static final RegExp HTTP_FTP_REGEX = RegExp.compile("^(http|ftp).*"); // starting with http, https or ftp + + /** + * Extracts the youtube id of the passed youtube video URL. + * Also accepts + * From http://stackoverflow.com/questions/3452546/javascript-regex-how-to-get-youtube-video-id-from-url, mantish Mar 4 at 15:33 + * @param url + * @return The youtube + */ + public static String getIdByUrl(String url) { + MatchResult match = YOUTUBE_ID_REGEX.exec(url); + if ((match != null) && (match.getGroupCount() == 3)) { + return match.getGroup(2); + } else if (HTTP_FTP_REGEX.exec(url) == null) { //--> doesn't start with either http, https or ftp --> supposed to be a naked youtube id + return url.trim(); + } else { + return null; // --> plain http, https or ftp URL --> no youtube + } + } + + /** + * From http://stackoverflow.com/questions/2068344/how-do-i-get-a-youtube-video-thumbnail-from-the-youtube-api + * @param id + * @return + */ + public static String getThumbnailUrl(String id) { + return "//img.youtube.com/vi/" + id + "/mqdefault.jpg"; + } + +} From 9fabc331b7473a23b2f5f9eecd75a5695a978cb7 Mon Sep 17 00:00:00 2001 From: fmittag Date: Wed, 16 Jul 2014 14:25:52 +0200 Subject: [PATCH 093/323] some progress on the regatta detail page --- .../home/client/app/PlaceNavigatorImpl.java | 2 +- .../gwt/home/client/i18n/TextMessages.java | 4 + .../home/client/i18n/TextMessages.properties | 4 + .../client/i18n/TextMessages_de.properties | 4 + .../home/client/place/event/EventPlace.java | 6 +- .../event/regattaraces/EventRegattaRaces.css | 76 ++++---- .../EventRegattaRacesFleetGroup.java | 3 +- .../regattaraces/EventRegattaRacesRace.java | 170 ++++++++++++++---- .../regattaraces/EventRegattaRacesRace.ui.xml | 40 ++++- .../EventRegattaRacesResources.java | 18 +- 10 files changed, 247 insertions(+), 80 deletions(-) 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 fa589792ca5..f506a658504 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 @@ -29,7 +29,7 @@ public class PlaceNavigatorImpl implements PlaceNavigator { @Override public void goToEvent(String eventUuidAsString, String baseUrl) { - EventPlace eventPlace = new EventPlace(eventUuidAsString); + EventPlace eventPlace = new EventPlace(eventUuidAsString, null); if(isLocationOnLocalhost() || baseUrl.contains(SAPSAILING_DOMAIN)) { placeController.goTo(eventPlace); } else { 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 8591adb04ed..dfe7d0e5e92 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 @@ -78,6 +78,10 @@ public interface TextMessages extends Messages { String eventRegattaRaceFinished(); String eventRegattaRaceStarted(); String eventRegattaRaceScheduled(); + String eventRegattaRaceRace(); + String eventRegattaRaceWind(); + String eventRegattaRaceWinner(); + String eventRegattaRaceLeader(); String eventRegattaRacesLegendGps(); String eventRegattaRacesLegendWind(); String eventRegattaRacesLegendVideo(); 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 b8edfb0f7a4..37ac433fe0e 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 @@ -73,6 +73,10 @@ eventRegattaRaceNotTracked=Not Tracked eventRegattaRaceFinished=Finished at: eventRegattaRaceStarted=Started at: eventRegattaRaceScheduled=Scheduled for: +eventRegattaRaceRace=Race +eventRegattaRaceWind=Wind Strength: +eventRegattaRaceWinner=Winner: +eventRegattaRaceLeader=Leader: eventRegattaRacesLegendGps=Tracking eventRegattaRacesLegendWind=Wind Data eventRegattaRacesLegendVideo=Video Stream 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 c040ddcb38e..3dd98ea6b40 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 @@ -73,6 +73,10 @@ eventRegattaRaceNotTracked=Not Tracked eventRegattaRaceFinished=Finished at: eventRegattaRaceStarted=Started at: eventRegattaRaceScheduled=Scheduled for: +eventRegattaRaceRace=Race +eventRegattaRaceWind=Wind Strength: +eventRegattaRaceWinner=Winner: +eventRegattaRaceLeader=Leader: eventRegattaRacesLegendGps=Tracking eventRegattaRacesLegendWind=Wind Data eventRegattaRacesLegendVideo=Video Stream 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 12070c058ca..81c9450c1ad 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 @@ -7,8 +7,10 @@ public class EventPlace extends AbstractBasePlace { private final String eventUuidAsString; private final String leaderboardIdAsNameString; - public EventPlace(String eventUuidAsString) { - this(eventUuidAsString, null); + public EventPlace(String url) { + super(url); + eventUuidAsString = getParameter("eventId"); + leaderboardIdAsNameString = getParameter("leaderboardName"); } public EventPlace(String eventUuidAsString, String leaderboardIdAsNameString) { 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 795e259af48..a1eaad80e4d 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 @@ -1,8 +1,3 @@ -.regattanavigation { - border-bottom: 1px solid #DDDDDD; - font-size: 1rem; - text-align: center; -} .eventregattaraces { font-size: 15px; font-size: 1rem; @@ -115,13 +110,13 @@ .eventregattarace_fleetindicator { position: absolute; width: 0.333333333333333em; - height: 3.333333333333333em; top: 0.333333333333333em; + bottom: 0.333333333333333em; } .eventregattarace_details { display: inline-block; float: left; - height: 2.666666666666667em; + min-height: 2.666666666666667em; padding: 0 1.333333333333333em; border-right: 1px solid #ccc; } @@ -132,7 +127,8 @@ width: 22%; } .eventregattarace_nameshort { - float: left; + display: block; + float: none; margin-right: 0.555555555555556em; margin-top: -0.194444444444444em; font-size: 2.4em; @@ -140,16 +136,10 @@ font-weight: 700; } .eventregattarace_flag { - width: 7%; -} -.eventregattarace_flag img { - position: relative; - top: 50%; - -webkit-transform: translate(0, -50%); - -moz-transform: translate(0, -50%); - -o-transform: translate(0, -50%); - -ms-transform: translate(0, -50%); - transform: translate(0, -50%); + display: none; + width: 9%; + min-height: 0; + padding-top: 0.666666666666667em; } .eventregattarace_wind { width: 14%; @@ -159,8 +149,12 @@ font-weight: 600; } .eventregattarace_leader { - width: 27%; + width: 24%; border-right: none; + white-space: nowrap; + -o-text-overflow: ellipsis; + text-overflow: ellipsis; + overflow: hidden; } .eventregattarace_leader_name { color: #008fcc; @@ -171,6 +165,16 @@ width: 17%; border-left: 1px solid #ccc; } +.eventregattarace_features_list, +.eventregattarace_legs_list { + font-size: 15px; + font-size: 1rem; +} +.eventregattarace_features { + min-height: 0; + padding-top: 0.666666666666667em; + text-align: center; +} .eventregattarace_legs_leg { display: inline-block; height: 10px; @@ -185,15 +189,20 @@ } .eventregattarace_action { float: right; - width: 9.666666666666666em; + width: 13%; text-align: center; text-transform: uppercase; cursor: pointer; } -.eventregattarace_actionwatch { +.eventregattarace_action_watch, +.eventregattarace_action_analyze { + width: 9.666666666666666em; + max-width: 100%; +} +.eventregattarace_action_watch { background-color: #f00; } -.eventregattarace_actionwatch:hover { +.eventregattarace_action_watch:hover { background-color: #a71724; } .eventregattarace_actiondisabled { @@ -204,14 +213,7 @@ .eventregattarace_actiondisabled:hover { background-color: transparent; } -[class*="eventregattarace_feature_"] { - position: relative; - top: 50%; - -webkit-transform: translate(0, -50%); - -moz-transform: translate(0, -50%); - -o-transform: translate(0, -50%); - -ms-transform: translate(0, -50%); - transform: translate(0, -50%); +.eventregattarace_feature { display: inline-block; width: 1.733333333333333em; height: 1.733333333333333em; @@ -221,22 +223,22 @@ -moz-background-size: auto 200%; background-size: auto 200%; } -[class*="eventregattarace_feature_"]:last-of-type { +.eventregattarace_feature:last-of-type { margin-right: 0; } -.eventregattarace_feature_gps { +.eventregattarace_featuregps { background-position: 0 0; } -.eventregattarace_feature_wind { +.eventregattarace_featurewind { background-position: 33% 0; } -.eventregattarace_feature_video { +.eventregattarace_featurevideo { background-position: 66% 0; } -.eventregattarace_feature_audio { +.eventregattarace_featureaudio { background-position: 100% 0; } -.eventregattarace_feature_unavailable { +.eventregattarace_featureunavailable { background-position-y: 100%; } .regattanavigation { @@ -265,4 +267,4 @@ .regattanavigation_linkactive:hover, .regattanavigation_linkactive:focus { border-bottom-color: #d89a00; -} \ 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/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 0c23fbc1433..06663bcd53b 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 @@ -36,7 +36,8 @@ public class EventRegattaRacesFleetGroup extends Composite { List racesOfFleet = getRacesOfFleet(leaderboard, series, fleet); for(RaceColumnDTO raceColumn: racesOfFleet) { - EventRegattaRacesRace race = new EventRegattaRacesRace(fleet, raceColumn, pageNavigator); + RaceColumnDTO raceColumnFromLeaderboard = leaderboard.getRaceColumnByName(raceColumn.getName()); + EventRegattaRacesRace race = new EventRegattaRacesRace(leaderboard, fleet, raceColumnFromLeaderboard, timerForClientServerOffset, pageNavigator); racesFleetPanel.add(race); } } 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 3c14c4bd34f..e57d5f24645 100644 --- 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 @@ -2,62 +2,170 @@ 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.dom.client.Style.Display; +import com.google.gwt.dom.client.Style.Visibility; +import com.google.gwt.event.dom.client.ClickEvent; 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.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.domain.common.dto.FleetDTO; import com.sap.sailing.domain.common.dto.RaceColumnDTO; import com.sap.sailing.domain.common.dto.RaceDTO; +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.StrippedLeaderboardDTO; +import com.sap.sse.gwt.client.player.Timer; public class EventRegattaRacesRace extends Composite { private static EventRegattaRacesRaceUiBinder uiBinder = GWT.create(EventRegattaRacesRaceUiBinder.class); + private enum SimpleRaceStates { NOT_TRACKED, TRACKED_AND_NOT_LIVE, TRACKED_AND_LIVE }; + interface EventRegattaRacesRaceUiBinder extends UiBinder { } @UiField DivElement fleetColor; + @UiField SpanElement raceName; + @UiField SpanElement raceName2; + @UiField SpanElement raceState; + @UiField SpanElement raceTime; + @UiField SpanElement averageRaceWind; + + @UiField DivElement raceNotTrackedDiv; + @UiField DivElement watchRaceDiv; + @UiField DivElement analyzeRaceDiv; + @UiField Anchor watchRaceLink; + @UiField Anchor analyzeRaceLink; + + @UiField DivElement raceWinnerDiv; + @UiField SpanElement raceWinner; + @UiField DivElement raceLeaderDiv; + @UiField SpanElement raceLeader; + + @UiField DivElement raceFeaturesDiv; + @UiField DivElement legProgressDiv; + @UiField DivElement raceFlagDiv; + @UiField DivElement windStatusDiv; + + @UiField DivElement featureGPS; + @UiField DivElement featureWind; + @UiField DivElement featureAudio; + @UiField DivElement featureVideo; + + // local_res.css.eventregattarace_featureunavailable private final DateTimeFormat raceTimeFormat = DateTimeFormat.getFormat("EEE, h:mm a"); - public EventRegattaRacesRace(FleetDTO fleet, RaceColumnDTO raceColumn, EventPageNavigator pageNavigator) { + private final TextMessages textMessages = GWT.create(TextMessages.class); + + private final StrippedLeaderboardDTO leaderboard; + private final FleetDTO fleet; + private final RaceColumnDTO raceColumn; + private final EventPageNavigator pageNavigator; + private final Timer timerForClientServerOffset; + + private Element[] allConditionalElements; + + public EventRegattaRacesRace(StrippedLeaderboardDTO leaderboard, FleetDTO fleet, RaceColumnDTO raceColumn, Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { + this.leaderboard = leaderboard; + this.fleet = fleet; + this.raceColumn = raceColumn; + this.pageNavigator = pageNavigator; + this.timerForClientServerOffset = timerForClientServerOffset; + EventRegattaRacesResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); + allConditionalElements = new Element[] {raceWinnerDiv, raceLeaderDiv, watchRaceDiv, analyzeRaceDiv, raceNotTrackedDiv, + raceFeaturesDiv, legProgressDiv, raceFlagDiv, windStatusDiv }; + fleetColor.getStyle().setBackgroundColor(fleet.getColor().getAsHtml()); -// raceName.setInnerText(raceColumn.getName()); -// -// RaceDTO race = raceColumn.getRace(fleet); -// if(race != null) { -// boolean live = raceColumn.isLive(fleet, timerForClientServerOffset.getLiveTimePointInMillis()); -// -// // tracked race -// raceBox.getElement().addClassName(css.eventregattaschedule_series_fleet_racetracked()); -// if(race.trackedRace != null) { -// if(race.trackedRace.startOfTracking != null) { -// raceDetails.setInnerText(raceTimeFormat.format(race.trackedRace.startOfTracking)); -// } else { -// raceDetails.setInnerText("tracked"); -// } -// if(live) { -// raceBox.getElement().setAttribute("data-live", "data-live"); -// } -// } -// } else { -// raceBox.getElement().addClassName(css.eventregattaschedule_series_fleet_raceuntracked()); -// raceDetails.setInnerText("untracked"); -// } - + raceName.setInnerText(raceColumn.getName()); + raceName2.setInnerText(raceColumn.getName()); + +// {#if race.isFinished} +// +// {/if race.isFinished} +// {#if race.isLive} +// {race.startTime} +// {/if race.isLive} +// {#if race.isScheduled} +// {race.startTime} +// {/if race.isScheduled} + + updateUI(); } -// @UiHandler("raceLink") -// public void goToRaceboard(ClickEvent e) { -// RaceDTO race = raceColumn.getRace(fleet); -// if(race != null && race.trackedRace != null) { -// pageNavigator.openRaceViewer(leaderboard, race); -// } -// } + 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); + } + return simpleRaceState; + } + + private boolean isLive() { + return raceColumn.isLive(fleet, timerForClientServerOffset.getLiveTimePointInMillis()); + } + + private void updateUI() { + averageRaceWind.setInnerText("tbd."); + + for(Element el: allConditionalElements) { + hideElement(el); + } + + switch(getSimpleRaceState()) { + case NOT_TRACKED: + showElement(raceNotTrackedDiv); + break; + case TRACKED_AND_LIVE: + showElement(watchRaceDiv); + showElement(raceLeaderDiv); + showElement(legProgressDiv); + break; + case TRACKED_AND_NOT_LIVE: + showElement(analyzeRaceDiv); + showElement(raceWinnerDiv); + showElement(raceFeaturesDiv); + break; + } + } + + @UiHandler("watchRaceLink") + public void watchRaceClicked(ClickEvent e) { + openRaceViewer(); + } + + @UiHandler("analyzeRaceLink") + public void analyzeRaceClicked(ClickEvent e) { + openRaceViewer(); + } + + private void hideElement(Element el) { + el.getStyle().setVisibility(Visibility.HIDDEN); + el.getStyle().setDisplay(Display.NONE); + } + + private void showElement(Element el) { + el.getStyle().setVisibility(Visibility.VISIBLE); + el.getStyle().setDisplay(Display.INLINE_BLOCK); + } + + 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 8e9812ec9d3..65de9704a71 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 @@ -9,9 +9,47 @@
+
- Race X

+ +

+   +
+ +
+ +
+
+

+ +
+
+

-   +
+
+ Leader:

-   +
+
Leg (race.leg of race.totalLegs): +
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+ 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 06591ee7d2f..e0907f95721 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 @@ -11,7 +11,6 @@ public interface EventRegattaRacesResources extends ClientBundle { LocalCss css(); public interface LocalCss extends CssResource { - String regattanavigation(); String eventregattaraces(); String eventregattaraces_phase(); String eventregattaraces_phase_name(); @@ -39,15 +38,20 @@ public interface EventRegattaRacesResources extends ClientBundle { String eventregattarace_leader_name(); String eventregattarace_features(); String eventregattarace_legs(); + String eventregattarace_features_list(); + String eventregattarace_legs_list(); String eventregattarace_legs_leg(); String eventregattarace_action(); - String eventregattarace_actionwatch(); + String eventregattarace_action_watch(); + String eventregattarace_action_analyze(); String eventregattarace_actiondisabled(); - String eventregattarace_feature_gps(); - String eventregattarace_feature_wind(); - String eventregattarace_feature_video(); - String eventregattarace_feature_audio(); - String eventregattarace_feature_unavailable(); + String eventregattarace_feature(); + String eventregattarace_featuregps(); + String eventregattarace_featurewind(); + String eventregattarace_featurevideo(); + String eventregattarace_featureaudio(); + String eventregattarace_featureunavailable(); + String regattanavigation(); String regattanavigation_link(); String regattanavigation_linkactive(); } From e1cfeba7c13f9fee7c868cc3741b88a8f110b283 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jul 2014 14:45:50 +0200 Subject: [PATCH 094/323] use binary search to find location where to merge fixes received from server --- .../client/shared/racemap/FixesAndTails.java | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java index 6970689a298..f196da48cb5 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java @@ -2,6 +2,7 @@ package com.sap.sailing.gwt.ui.client.shared.racemap; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.Iterator; @@ -176,13 +177,13 @@ public class FixesAndTails { * not trimmed according to the specification for the tail length. This has to happen elsewhere (see also * {@link #updateTail}). * - * @param result + * @param fixesForCompetitors * For each list the invariant must hold that an {@link GPSFixDTO#extrapolated extrapolated} fix must be * the last one in the list */ - protected void updateFixes(Map> result, + protected void updateFixes(Map> fixesForCompetitors, Map overlapsWithKnownFixes, TailFactory tailFactory, long timeForPositionTransitionMillis) { - for (Map.Entry> e : result.entrySet()) { + for (Map.Entry> e : fixesForCompetitors.entrySet()) { if (e.getValue() != null && !e.getValue().isEmpty()) { List fixesForCompetitor = fixes.get(e.getKey()); if (fixesForCompetitor == null) { @@ -239,10 +240,14 @@ public class FixesAndTails { final Polyline tail = getTail(competitorDTO); int intoThisIndex = 0; for (GPSFixDTO mergeThisFix : mergeThis) { - while (intoThisIndex < intoThis.size() - && intoThis.get(intoThisIndex).timepoint.before(mergeThisFix.timepoint)) { - // TODO how about using Collections.binarySearch - intoThisIndex++; + intoThisIndex = Collections.binarySearch(intoThis, mergeThisFix, new Comparator() { + @Override + public int compare(GPSFixDTO o1, GPSFixDTO o2) { + return o1.timepoint.compareTo(o2.timepoint); + } + }); + if (intoThisIndex < 0) { + intoThisIndex = -intoThisIndex-1; } if (intoThisIndex < intoThis.size() && intoThis.get(intoThisIndex).timepoint.equals(mergeThisFix.timepoint)) { // exactly same time point; replace with fix from mergeThis unless the new fix is extrapolated and there is a later fix in intoThis; From ace20d9ab60c7aac6669eaf1633db810bd71eb73 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jul 2014 14:56:34 +0200 Subject: [PATCH 095/323] re-added raceRank to leaderboard JSON output --- .../server/gateway/jaxrs/api/LeaderboardsResource.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java index f3e8d80360a..eeaa253be7c 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java @@ -139,7 +139,7 @@ public class LeaderboardsResource extends AbstractSailingServerResource { JSONObject jsonRaceColumns = new JSONObject(); jsonCompetitor.put("raceScores", jsonRaceColumns); for (RaceColumnDTO raceColumn : leaderboardDTO.getRaceList()) { - List rankedCompetitorsForColumn = leaderboardDTO.getCompetitorOrderingPerRaceColumnName().get(raceColumn.getName()); + List regattaRankedCompetitorsForColumn = leaderboardDTO.getCompetitorOrderingPerRaceColumnName().get(raceColumn.getName()); JSONObject jsonEntry = new JSONObject(); jsonRaceColumns.put(raceColumn.getName(), jsonEntry); LeaderboardEntryDTO leaderboardEntry = leaderboardRowDTO.fieldsByRaceColumnName.get(raceColumn.getName()); @@ -151,11 +151,9 @@ public class LeaderboardsResource extends AbstractSailingServerResource { jsonEntry.put("totalPoints", leaderboardEntry.totalPoints); MaxPointsReason maxPointsReason = leaderboardEntry.reasonForMaxPoints; jsonEntry.put("maxPointsReason", maxPointsReason != null ? maxPointsReason.toString(): null); - jsonEntry.put("rank", rankedCompetitorsForColumn.indexOf(competitor)+1); -// final TrackedRace trackedRace = raceColumn.getTrackedRace(competitor); -// if (trackedRace != null) { -// jsonEntry.put("raceRank", trackedRace.getRank(competitor, resultTimePoint)); -// } + jsonEntry.put("rank", regattaRankedCompetitorsForColumn.indexOf(competitor)+1); + List raceRankedCompetitorsInColumn = leaderboardDTO.getCompetitorsFromBestToWorst(raceColumn); + jsonEntry.put("raceRank", raceRankedCompetitorsInColumn.indexOf(competitor)+1); jsonEntry.put("isDiscarded", leaderboardEntry.discarded); jsonEntry.put("isCorrected", leaderboardEntry.hasScoreCorrection()); } From fd2f74273e4b1d4df6aee4648db9b89e51a053f2 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jul 2014 14:58:18 +0200 Subject: [PATCH 096/323] fixed replication error message parameter set --- .../sailing/gwt/ui/adminconsole/ReplicationPanel.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java index 61b17c3def8..7652aba7065 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java @@ -147,6 +147,10 @@ public class ReplicationPanel extends FlowPanel { AddReplicationDialog dialog = new AddReplicationDialog(null, new DialogCallback, Integer, Integer>>() { @Override + /** + * @param masterNameAndExchangeNameAndMessagingPortNumberAndServletPortNumber a triple containing the RabbitMQ exchange hostname, the servlet hostname and the RabbitMQ exchange name, + * followed by the RabbitMQ messaging port and the servlet port + */ public void ok(final Util.Triple, Integer, Integer> masterNameAndExchangeNameAndMessagingPortNumberAndServletPortNumber) { registeredMasters.removeRow(0); registeredMasters.insertRow(0); @@ -163,7 +167,7 @@ public class ReplicationPanel extends FlowPanel { addButton.setEnabled(true); errorReporter.reportError(stringMessages.errorStartingReplication( masterNameAndExchangeNameAndMessagingPortNumberAndServletPortNumber.getA().getB(), - masterNameAndExchangeNameAndMessagingPortNumberAndServletPortNumber.getA().getA(), e.getMessage())); + masterNameAndExchangeNameAndMessagingPortNumberAndServletPortNumber.getA().getC(), e.getMessage())); updateReplicaList(); } @@ -286,7 +290,9 @@ public class ReplicationPanel extends FlowPanel { /** * A text entry dialog with ok/cancel button and configurable validation rule. Subclasses may provide a redefinition for - * {@link #getAdditionalWidget()} to add a widget below the text field, e.g., for capturing additional data. + * {@link #getAdditionalWidget()} to add a widget below the text field, e.g., for capturing additional data. The result of + * this dialog is a triple containing the RabbitMQ exchange hostname, the servlet hostname and the RabbitMQ exchange name, + * followed by the RabbitMQ messaging port and the servlet port. * * @author Axel Uhl (d043530) * From ee94537cea11d9b133df43ac3c43d8e4fc024823 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jul 2014 14:59:53 +0200 Subject: [PATCH 097/323] minor capitalizatoin fix in log message --- .../com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java index 7652aba7065..3b141672ecc 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java @@ -234,7 +234,7 @@ public class ReplicationPanel extends FlowPanel { i++; registeredReplicas.insertRow(i); registeredReplicas.setWidget(i, 1, new Label(stringMessages.totalSize())); - registeredReplicas.setWidget(i, 2, new Label(""+replica.getNumberOfBytesSent()+"b ("+replica.getNumberOfBytesSent()/1000.0+"kb)")); + registeredReplicas.setWidget(i, 2, new Label(""+replica.getNumberOfBytesSent()+"b ("+replica.getNumberOfBytesSent()/1000.0+"kB)")); i++; for (Map.Entry e : replica.getOperationCountByOperationClassName().entrySet()) { registeredReplicas.insertRow(i); From 5410a6e87113bca7ac32ac78187540d834ca2545 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jul 2014 15:27:40 +0200 Subject: [PATCH 098/323] fix for bug 1937 comment #7: the connection to RabbitMQ needs to consider that RabbitMQ may be running somewhere else; use ReplicationService.createMasterChannel() --- .../sailing/server/replication/impl/ReplicationServlet.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServlet.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServlet.java index 33be30edcea..a3c0a7223bb 100755 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServlet.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServlet.java @@ -21,8 +21,6 @@ import org.osgi.framework.BundleContext; import org.osgi.util.tracker.ServiceTracker; import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; import com.sap.sailing.server.RacingEventService; import com.sap.sailing.server.gateway.SailingServerHttpServlet; import com.sap.sailing.server.replication.ReplicationService; @@ -73,6 +71,7 @@ public class ReplicationServlet extends SailingServerHttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action = req.getParameter(ACTION); + logger.info("Received replication request, action is "+action); switch (Action.valueOf(action)) { case REGISTER: registerClientWithReplicationService(req, resp); @@ -81,8 +80,7 @@ public class ReplicationServlet extends SailingServerHttpServlet { deregisterClientWithReplicationService(req, resp); break; case INITIAL_LOAD: - Connection rabbitConnection = new ConnectionFactory().newConnection(); - Channel channel = rabbitConnection.createChannel(); + Channel channel = getReplicationService().createMasterChannel(); RabbitOutputStream ros = new RabbitOutputStream(INITIAL_LOAD_PACKAGE_SIZE, channel, /* queueName */ "initialLoad-for-"+req.getRemoteHost()+"@"+new Date()+"-"+UUID.randomUUID(), /* syncAfterTimeout */ false); From baa69cd8c0d0533c71070e815f9b1561daeb53fc Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jul 2014 15:51:40 +0200 Subject: [PATCH 099/323] another minor capitalization fix in replication panel message --- .../com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java index 3b141672ecc..9bbc7ebf7da 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java @@ -234,7 +234,7 @@ public class ReplicationPanel extends FlowPanel { i++; registeredReplicas.insertRow(i); registeredReplicas.setWidget(i, 1, new Label(stringMessages.totalSize())); - registeredReplicas.setWidget(i, 2, new Label(""+replica.getNumberOfBytesSent()+"b ("+replica.getNumberOfBytesSent()/1000.0+"kB)")); + registeredReplicas.setWidget(i, 2, new Label(""+replica.getNumberOfBytesSent()+"B ("+replica.getNumberOfBytesSent()/1000.0+"kB)")); i++; for (Map.Entry e : replica.getOperationCountByOperationClassName().entrySet()) { registeredReplicas.insertRow(i); From a7f7cc7834dbc2d7d25a438878adc74086428137 Mon Sep 17 00:00:00 2001 From: Carolin Joppien Date: Wed, 16 Jul 2014 14:22:29 +0000 Subject: [PATCH 100/323] Created ganalytics (markdown) --- wiki/ganalytics.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 wiki/ganalytics.md diff --git a/wiki/ganalytics.md b/wiki/ganalytics.md new file mode 100644 index 00000000000..127b2803610 --- /dev/null +++ b/wiki/ganalytics.md @@ -0,0 +1,32 @@ +**_Bug 2043_** +Google Analytics Tracking ID: **UA-52789785-1** +GAnalytics Account: gmsponsorship@gmail.com +GAnalytics PW: + +**code for standard tracking (page view counter):** +We need to embed the following code in our HTML entry point documents: + +> + +**track Events (Click on Links): ** +insert onClick() method for each link +e.g. +> Play +https://developers.google.com/analytics/devguides/collection/gajs/eventTrackerGuide + +**track page load time:** +https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_gat.GA_Tracker_._trackPageview + + + From 3ae041aa8de127d767a7909a0222d1e3f4647fef Mon Sep 17 00:00:00 2001 From: Carolin Joppien Date: Wed, 16 Jul 2014 14:24:00 +0000 Subject: [PATCH 101/323] Updated ganalytics (markdown) --- wiki/ganalytics.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wiki/ganalytics.md b/wiki/ganalytics.md index 127b2803610..edb99676fc4 100644 --- a/wiki/ganalytics.md +++ b/wiki/ganalytics.md @@ -1,4 +1,5 @@ **_Bug 2043_** + Google Analytics Tracking ID: **UA-52789785-1** GAnalytics Account: gmsponsorship@gmail.com GAnalytics PW: @@ -19,7 +20,7 @@ We need to embed the following code in our HTML entry point documents: > -**track Events (Click on Links): ** +**track Events (Click on Links):** insert onClick() method for each link e.g. > Play From 3c4f536ba280e21b485dfc585110870188de2d36 Mon Sep 17 00:00:00 2001 From: Carolin Joppien Date: Wed, 16 Jul 2014 14:26:18 +0000 Subject: [PATCH 102/323] Updated ganalytics (markdown) --- wiki/ganalytics.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wiki/ganalytics.md b/wiki/ganalytics.md index edb99676fc4..a16e949d0f4 100644 --- a/wiki/ganalytics.md +++ b/wiki/ganalytics.md @@ -1,8 +1,8 @@ **_Bug 2043_** Google Analytics Tracking ID: **UA-52789785-1** -GAnalytics Account: gmsponsorship@gmail.com -GAnalytics PW: +###GAnalytics Account: gmsponsorship@gmail.com +###GAnalytics PW: **code for standard tracking (page view counter):** We need to embed the following code in our HTML entry point documents: From c362a72ad17ff6261213d0c8c58e21ffb47507f1 Mon Sep 17 00:00:00 2001 From: Carolin Joppien Date: Wed, 16 Jul 2014 14:27:29 +0000 Subject: [PATCH 103/323] Updated ganalytics (markdown) --- wiki/ganalytics.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/wiki/ganalytics.md b/wiki/ganalytics.md index a16e949d0f4..bdc854ecdb0 100644 --- a/wiki/ganalytics.md +++ b/wiki/ganalytics.md @@ -1,8 +1,9 @@ -**_Bug 2043_** +##Bug 2043
-Google Analytics Tracking ID: **UA-52789785-1** -###GAnalytics Account: gmsponsorship@gmail.com -###GAnalytics PW: +Google Analytics Tracking ID: **UA-52789785-1**
+ +GAnalytics Account: gmsponsorship@gmail.com +GAnalytics PW: **code for standard tracking (page view counter):** We need to embed the following code in our HTML entry point documents: From 3d6ecfebcd824d7d158fcd631f0e08d08bef70e7 Mon Sep 17 00:00:00 2001 From: Carolin Joppien Date: Wed, 16 Jul 2014 14:28:22 +0000 Subject: [PATCH 104/323] Updated ganalytics (markdown) --- wiki/ganalytics.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/wiki/ganalytics.md b/wiki/ganalytics.md index bdc854ecdb0..6627cfd985e 100644 --- a/wiki/ganalytics.md +++ b/wiki/ganalytics.md @@ -1,13 +1,11 @@ ##Bug 2043
Google Analytics Tracking ID: **UA-52789785-1**
+GAnalytics Account: gmsponsorship@gmail.com
+GAnalytics PW:
-GAnalytics Account: gmsponsorship@gmail.com -GAnalytics PW: - -**code for standard tracking (page view counter):** -We need to embed the following code in our HTML entry point documents: - +**code for standard tracking (page view counter):**
+We need to embed the following code in our HTML entry point documents:
> -**track Events (Click on Links):** -insert onClick() method for each link +**track Events (Click on Links):**
+insert onClick() method for each link
e.g. > Play https://developers.google.com/analytics/devguides/collection/gajs/eventTrackerGuide -**track page load time:** +**track page load time:**
https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_gat.GA_Tracker_._trackPageview From 7cea0286b54931e18ef85162c7cbd5c2e385cf90 Mon Sep 17 00:00:00 2001 From: Carolin Joppien Date: Wed, 16 Jul 2014 14:29:25 +0000 Subject: [PATCH 105/323] Updated ganalytics (markdown) --- wiki/ganalytics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wiki/ganalytics.md b/wiki/ganalytics.md index 6627cfd985e..291e19b9333 100644 --- a/wiki/ganalytics.md +++ b/wiki/ganalytics.md @@ -17,7 +17,7 @@ We need to embed the following code in our HTML entry point documents:
> ga('create', 'UA-52789785-1', 'auto'); > ga('send', 'pageview'); -> +>
**track Events (Click on Links):**
insert onClick() method for each link
From d307741c8ce99a01b5fc6f6b7fd5a1a105d424ae Mon Sep 17 00:00:00 2001 From: Carolin Joppien Date: Wed, 16 Jul 2014 14:30:03 +0000 Subject: [PATCH 106/323] Updated ganalytics (markdown) --- wiki/ganalytics.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/wiki/ganalytics.md b/wiki/ganalytics.md index 291e19b9333..2e4a595cda4 100644 --- a/wiki/ganalytics.md +++ b/wiki/ganalytics.md @@ -18,11 +18,10 @@ We need to embed the following code in our HTML entry point documents:
> ga('send', 'pageview'); >
- **track Events (Click on Links):**
insert onClick() method for each link
e.g. -> Play +> Play
https://developers.google.com/analytics/devguides/collection/gajs/eventTrackerGuide **track page load time:**
From 91ae53f6c57882fd72af4156566f39401a3dfb73 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jul 2014 16:32:27 +0200 Subject: [PATCH 107/323] more replication logging --- .../com/sap/sailing/server/replication/impl/Replicator.java | 5 +++++ .../src/com/sap/sse/common/CountingOutputStream.java | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java index 9f866006f42..59b54696372 100755 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java @@ -108,6 +108,7 @@ public class Replicator implements Runnable { public void run() { ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); long messageCount = 0; + long operationCount = 0; Field _queue; try { _queue = QueueingConsumer.class.getDeclaredField("_queue"); @@ -143,6 +144,10 @@ public class Replicator implements Runnable { .getRacingEventService().getBaseDomainFactory().createObjectInputStreamResolvingAgainstThisFactory( new ByteArrayInputStream(serializedOperation)); RacingEventServiceOperation operation = (RacingEventServiceOperation) operationOIS.readObject(); + operationCount++; + if (operationCount % 10000l == 0) { + logger.info("Received "+operationCount+" operations so far"); + } applyOrQueue(operation); } } catch (ConsumerCancelledException cce) { diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/CountingOutputStream.java b/java/com.sap.sse.common/src/com/sap/sse/common/CountingOutputStream.java index 1b4aed43055..2a345b90082 100755 --- a/java/com.sap.sse.common/src/com/sap/sse/common/CountingOutputStream.java +++ b/java/com.sap.sse.common/src/com/sap/sse/common/CountingOutputStream.java @@ -43,7 +43,7 @@ public class CountingOutputStream extends FilterOutputStream { } private void log() { - logger.log(level, "wrote "+count+" bytes"+(count>1000?"("+count/1000+" kbytes)":"")+" to "+name); + logger.log(level, "wrote "+count+" bytes "+(count>1000?"("+count/1000+" kB)":"")+" to "+name); } @Override From b1c9937be84c855095e92f7d9c921c5c72532391 Mon Sep 17 00:00:00 2001 From: Carolin Joppien Date: Wed, 16 Jul 2014 14:37:28 +0000 Subject: [PATCH 108/323] Updated ganalytics (markdown) --- wiki/ganalytics.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/wiki/ganalytics.md b/wiki/ganalytics.md index 2e4a595cda4..dc0499a715b 100644 --- a/wiki/ganalytics.md +++ b/wiki/ganalytics.md @@ -6,22 +6,22 @@ GAnalytics PW:
**code for standard tracking (page view counter):**
We need to embed the following code in our HTML entry point documents:
->
**track Events (Click on Links):**
insert onClick() method for each link
e.g. -> Play
+Play
https://developers.google.com/analytics/devguides/collection/gajs/eventTrackerGuide **track page load time:**
From 05c25eb5d6dd5722e38bff4ee9b2cd3753c5a443 Mon Sep 17 00:00:00 2001 From: Carolin Joppien Date: Wed, 16 Jul 2014 14:38:31 +0000 Subject: [PATCH 109/323] Updated ganalytics (markdown) --- wiki/ganalytics.md | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/wiki/ganalytics.md b/wiki/ganalytics.md index dc0499a715b..b7f909e2ca3 100644 --- a/wiki/ganalytics.md +++ b/wiki/ganalytics.md @@ -4,19 +4,6 @@ Google Analytics Tracking ID: **UA-52789785-1**
GAnalytics Account: gmsponsorship@gmail.com
GAnalytics PW:
-**code for standard tracking (page view counter):**
-We need to embed the following code in our HTML entry point documents:
- (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new -Date();a=s.createElement(o), - -m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-52789785-1', 'auto'); - ga('send', 'pageview'); - - **track Events (Click on Links):**
insert onClick() method for each link
From e853acf6a5444522b7d56636bfd2a0e6b88aba2b Mon Sep 17 00:00:00 2001 From: Carolin Joppien Date: Wed, 16 Jul 2014 14:39:26 +0000 Subject: [PATCH 110/323] Updated ganalytics (markdown) --- wiki/ganalytics.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/wiki/ganalytics.md b/wiki/ganalytics.md index b7f909e2ca3..8776e43dfd0 100644 --- a/wiki/ganalytics.md +++ b/wiki/ganalytics.md @@ -7,8 +7,7 @@ GAnalytics PW:
**track Events (Click on Links):**
insert onClick() method for each link
-e.g. -Play
+e.g. onClick="_gaq.push(['_trackEvent', 'Videos', 'Play', 'Baby\'s First Birthday'])
https://developers.google.com/analytics/devguides/collection/gajs/eventTrackerGuide **track page load time:**
From 07f82a1d4709797ae4063c984edf941590c1c2f0 Mon Sep 17 00:00:00 2001 From: Carolin Joppien Date: Wed, 16 Jul 2014 14:43:28 +0000 Subject: [PATCH 111/323] Updated ganalytics (markdown) --- wiki/ganalytics.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wiki/ganalytics.md b/wiki/ganalytics.md index 8776e43dfd0..04a01823690 100644 --- a/wiki/ganalytics.md +++ b/wiki/ganalytics.md @@ -7,11 +7,11 @@ GAnalytics PW:
**track Events (Click on Links):**
insert onClick() method for each link
-e.g. onClick="_gaq.push(['_trackEvent', 'Videos', 'Play', 'Baby\'s First Birthday'])
-https://developers.google.com/analytics/devguides/collection/gajs/eventTrackerGuide +Syntax: onClick="_gaq.push(['_trackEvent', category, action, label])
+see also: https://developers.google.com/analytics/devguides/collection/gajs/eventTrackerGuide **track page load time:**
-https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_gat.GA_Tracker_._trackPageview +see: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_gat.GA_Tracker_._trackPageview From 1ad01d3a2f2f57da449ab2ca2e66e267e6f0b264 Mon Sep 17 00:00:00 2001 From: Carolin Joppien Date: Wed, 16 Jul 2014 14:43:47 +0000 Subject: [PATCH 112/323] Updated ganalytics (markdown) --- wiki/ganalytics.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/wiki/ganalytics.md b/wiki/ganalytics.md index 04a01823690..0554b43282a 100644 --- a/wiki/ganalytics.md +++ b/wiki/ganalytics.md @@ -1,9 +1,8 @@ -##Bug 2043
+#Bug 2043
Google Analytics Tracking ID: **UA-52789785-1**
GAnalytics Account: gmsponsorship@gmail.com
-GAnalytics PW:
- +GAnalytics PW:
**track Events (Click on Links):**
insert onClick() method for each link
@@ -11,7 +10,4 @@ Syntax: onClick="_gaq.push(['_trackEvent', category, action, label])
see also: https://developers.google.com/analytics/devguides/collection/gajs/eventTrackerGuide **track page load time:**
-see: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_gat.GA_Tracker_._trackPageview - - - +see: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_gat.GA_Tracker_._trackPageview \ No newline at end of file From a58cddc970f105b237b4c7d2dc6b089a779b6299 Mon Sep 17 00:00:00 2001 From: Carolin Joppien Date: Wed, 16 Jul 2014 14:44:17 +0000 Subject: [PATCH 113/323] Updated ganalytics (markdown) --- wiki/ganalytics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wiki/ganalytics.md b/wiki/ganalytics.md index 0554b43282a..7a02f0f7653 100644 --- a/wiki/ganalytics.md +++ b/wiki/ganalytics.md @@ -6,7 +6,7 @@ GAnalytics PW:
**track Events (Click on Links):**
insert onClick() method for each link
-Syntax: onClick="_gaq.push(['_trackEvent', category, action, label])
+Syntax: _onClick="_gaq.push(['_trackEvent', category, action, label])_
see also: https://developers.google.com/analytics/devguides/collection/gajs/eventTrackerGuide **track page load time:**
From 0287b83705b9e588e19acb63445948ef8a7d0076 Mon Sep 17 00:00:00 2001 From: Carolin Joppien Date: Wed, 16 Jul 2014 14:45:35 +0000 Subject: [PATCH 114/323] Updated ganalytics (markdown) --- wiki/ganalytics.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wiki/ganalytics.md b/wiki/ganalytics.md index 7a02f0f7653..4a305d516ba 100644 --- a/wiki/ganalytics.md +++ b/wiki/ganalytics.md @@ -4,10 +4,10 @@ Google Analytics Tracking ID: **UA-52789785-1**
GAnalytics Account: gmsponsorship@gmail.com
GAnalytics PW:
-**track Events (Click on Links):**
+**track events (clicks on links):**
insert onClick() method for each link
Syntax: _onClick="_gaq.push(['_trackEvent', category, action, label])_
-see also: https://developers.google.com/analytics/devguides/collection/gajs/eventTrackerGuide +see also: https://developers.google.com/analytics/devguides/collection/gajs/eventTrackerGuide **track page load time:**
see: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_gat.GA_Tracker_._trackPageview \ No newline at end of file From 7fe540d897a324805abc40265d112f306d801e28 Mon Sep 17 00:00:00 2001 From: Carolin Joppien Date: Wed, 16 Jul 2014 14:51:36 +0000 Subject: [PATCH 115/323] Updated ganalytics (markdown) --- wiki/ganalytics.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wiki/ganalytics.md b/wiki/ganalytics.md index 4a305d516ba..9a54471e39d 100644 --- a/wiki/ganalytics.md +++ b/wiki/ganalytics.md @@ -1,13 +1,15 @@ #Bug 2043
Google Analytics Tracking ID: **UA-52789785-1**
+http://www.google.com/intl/en/analytics/
GAnalytics Account: gmsponsorship@gmail.com
GAnalytics PW:
**track events (clicks on links):**
insert onClick() method for each link
Syntax: _onClick="_gaq.push(['_trackEvent', category, action, label])_
-see also: https://developers.google.com/analytics/devguides/collection/gajs/eventTrackerGuide +see also: https://developers.google.com/analytics/devguides/collection/gajs/eventTrackerGuide
+It might take a day, until the results are available on the dashboard (it will appear under "Behavior/Verhalten")
**track page load time:**
see: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_gat.GA_Tracker_._trackPageview \ No newline at end of file From 182edccc8e09e953356075e41d5de88735e7bd03 Mon Sep 17 00:00:00 2001 From: Carolin Joppien Date: Wed, 16 Jul 2014 14:59:47 +0000 Subject: [PATCH 116/323] Updated ganalytics (markdown) --- wiki/ganalytics.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/wiki/ganalytics.md b/wiki/ganalytics.md index 9a54471e39d..aaa852047a2 100644 --- a/wiki/ganalytics.md +++ b/wiki/ganalytics.md @@ -12,4 +12,7 @@ see also: https://developers.google.com/analytics/devguides/collection/gajs/even It might take a day, until the results are available on the dashboard (it will appear under "Behavior/Verhalten")
**track page load time:**
-see: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_gat.GA_Tracker_._trackPageview \ No newline at end of file +see: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_gat.GA_Tracker_._trackPageview + +**useful information:**
+https://developers.google.com/analytics/devguides/collection/gajs/ From 74ab1c70b5a328dd8c06a5149fd1232602f65a63 Mon Sep 17 00:00:00 2001 From: Carolin Joppien Date: Wed, 16 Jul 2014 15:00:14 +0000 Subject: [PATCH 117/323] Updated ganalytics (markdown) --- wiki/ganalytics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wiki/ganalytics.md b/wiki/ganalytics.md index aaa852047a2..944f848205d 100644 --- a/wiki/ganalytics.md +++ b/wiki/ganalytics.md @@ -14,5 +14,5 @@ It might take a day, until the results are available on the dashboard (it will a **track page load time:**
see: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_gat.GA_Tracker_._trackPageview -**useful information:**
+**useful information for web page tracking with ganalytics:**
https://developers.google.com/analytics/devguides/collection/gajs/ From 3b71e313dd928afdfe21a93f35e12f57c267f91d Mon Sep 17 00:00:00 2001 From: Carolin Joppien Date: Wed, 16 Jul 2014 15:00:50 +0000 Subject: [PATCH 118/323] Updated ganalytics (markdown) --- wiki/ganalytics.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wiki/ganalytics.md b/wiki/ganalytics.md index 944f848205d..ea118d86d9a 100644 --- a/wiki/ganalytics.md +++ b/wiki/ganalytics.md @@ -1,9 +1,9 @@ #Bug 2043
- -Google Analytics Tracking ID: **UA-52789785-1**
http://www.google.com/intl/en/analytics/
GAnalytics Account: gmsponsorship@gmail.com
GAnalytics PW:
+
+Google Analytics Tracking ID: **UA-52789785-1**
**track events (clicks on links):**
insert onClick() method for each link
From f8ab63aeb52a371958e7d5e73447530f22e2ad01 Mon Sep 17 00:00:00 2001 From: Carolin Joppien Date: Wed, 16 Jul 2014 15:01:27 +0000 Subject: [PATCH 119/323] Updated ganalytics (markdown) --- wiki/ganalytics.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wiki/ganalytics.md b/wiki/ganalytics.md index ea118d86d9a..827a080502a 100644 --- a/wiki/ganalytics.md +++ b/wiki/ganalytics.md @@ -1,9 +1,10 @@ #Bug 2043
-http://www.google.com/intl/en/analytics/
+ +GAnalytics Homepage: http://www.google.com/intl/en/analytics/
GAnalytics Account: gmsponsorship@gmail.com
GAnalytics PW:

-Google Analytics Tracking ID: **UA-52789785-1**
+GAnalytics Tracking ID: **UA-52789785-1**
**track events (clicks on links):**
insert onClick() method for each link
From cdba0c6c436d1fc7637c0ed5f38e49462b1a47b5 Mon Sep 17 00:00:00 2001 From: Carolin Joppien Date: Wed, 16 Jul 2014 15:01:49 +0000 Subject: [PATCH 120/323] Updated ganalytics (markdown) --- wiki/ganalytics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wiki/ganalytics.md b/wiki/ganalytics.md index 827a080502a..f6915669c6a 100644 --- a/wiki/ganalytics.md +++ b/wiki/ganalytics.md @@ -5,7 +5,7 @@ GAnalytics Account: gmsponsorship@gmail.com
GAnalytics PW:

GAnalytics Tracking ID: **UA-52789785-1**
- +
**track events (clicks on links):**
insert onClick() method for each link
Syntax: _onClick="_gaq.push(['_trackEvent', category, action, label])_
From 1a89a4c962bce31b46b62652765c89ef4d7bac93 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jul 2014 17:17:06 +0200 Subject: [PATCH 121/323] added TODO for how to first load one fix, then the others --- .../com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java | 3 +++ 1 file changed, 3 insertions(+) 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 c5215f64f34..d91d8cdc607 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 @@ -447,6 +447,9 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe fixesAndTails.computeFromAndTo(newTime, competitorsToShow, settings.getEffectiveTailLengthInMilliseconds()); final int requestID = ++boatPositionRequestIDCounter; + // TODO For those competitors for which the tails don't overlap (and therefore will be replaced by the new tail coming from the server) + // we expect some potential delay in computing the full tail. Therefore, in those cases we fire two requests: one fetching only the + // boat positions at newTime with zero tail length; and another one fetching everything else. GetRaceMapDataAction getRaceMapDataAction = new GetRaceMapDataAction(sailingService, competitorSelection.getAllCompetitors(), race, newTime, fromAndToAndOverlap.getA(), fromAndToAndOverlap.getB(), /* extrapolate */ true); asyncActionsExecutor.execute(getRaceMapDataAction, GET_RACE_MAP_DATA_CATEGORY, new AsyncCallback() { From 86d4bdea462fad4939b558d2c377f410801b9bd4 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jul 2014 17:18:14 +0200 Subject: [PATCH 122/323] let RecordCompetitorGPSFix only transmit competitor ID instead of full competitor, reducing message size for mass data --- .../sap/sailing/domain/base/RaceDefinition.java | 4 ++++ .../domain/base/impl/RaceDefinitionImpl.java | 17 ++++++++++------- .../gwt/ui/adminconsole/ReplicationPanel.java | 2 +- .../RecordCompetitorGPSFix.java | 7 +++++-- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceDefinition.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceDefinition.java index 9cde39936a9..40e7bddd570 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceDefinition.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceDefinition.java @@ -1,5 +1,7 @@ package com.sap.sailing.domain.base; +import java.io.Serializable; + import com.sap.sailing.domain.common.WithID; import com.sap.sse.common.Named; @@ -21,4 +23,6 @@ public interface RaceDefinition extends Named, WithID { Course getCourse(); Iterable getCompetitors(); + + Competitor getCompetitorById(Serializable competitorID); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceDefinitionImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceDefinitionImpl.java index 672c5879e8f..26b86e85960 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceDefinitionImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceDefinitionImpl.java @@ -1,8 +1,7 @@ package com.sap.sailing.domain.base.impl; import java.io.Serializable; -import java.util.LinkedHashSet; -import java.util.Set; +import java.util.LinkedHashMap; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; @@ -13,7 +12,7 @@ import com.sap.sailing.domain.common.impl.NamedImpl; public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition { private static final long serialVersionUID = -1900955198751393727L; private final Course course; - private final Iterable competitors; + private final LinkedHashMap competitorsById; private final BoatClass boatClass; private final Serializable id; @@ -26,11 +25,10 @@ public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition { assert name != null; this.course = course; - Set competitorsAsLinkedHashSet = new LinkedHashSet(); + this.competitorsById = new LinkedHashMap<>(); for (Competitor competitor : competitors) { - competitorsAsLinkedHashSet.add(competitor); + competitorsById.put(competitor.getId(), competitor); } - this.competitors = competitorsAsLinkedHashSet; this.boatClass = boatClass; this.id = id; } @@ -47,7 +45,12 @@ public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition { @Override public Iterable getCompetitors() { - return competitors; + return competitorsById.values(); + } + + @Override + public Competitor getCompetitorById(Serializable competitorID) { + return competitorsById.get(competitorID); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java index 3b141672ecc..eed1683ec07 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java @@ -234,7 +234,7 @@ public class ReplicationPanel extends FlowPanel { i++; registeredReplicas.insertRow(i); registeredReplicas.setWidget(i, 1, new Label(stringMessages.totalSize())); - registeredReplicas.setWidget(i, 2, new Label(""+replica.getNumberOfBytesSent()+"b ("+replica.getNumberOfBytesSent()/1000.0+"kB)")); + registeredReplicas.setWidget(i, 2, new Label(""+replica.getNumberOfBytesSent()+"B ("+replica.getNumberOfBytesSent()/1024.0/1024.0+"MB)")); i++; for (Map.Entry e : replica.getOperationCountByOperationClassName().entrySet()) { registeredReplicas.insertRow(i); diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/RecordCompetitorGPSFix.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/RecordCompetitorGPSFix.java index 9b674d723d6..d36e67c31bd 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/RecordCompetitorGPSFix.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/RecordCompetitorGPSFix.java @@ -1,5 +1,7 @@ package com.sap.sailing.server.operationaltransformation; +import java.io.Serializable; + import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.tracking.DynamicTrackedRace; @@ -9,12 +11,12 @@ import com.sap.sailing.server.RacingEventServiceOperation; public class RecordCompetitorGPSFix extends AbstractRaceOperation { private static final long serialVersionUID = 5847067037829132465L; - private final Competitor competitor; + private final Serializable competitorID; private final GPSFixMoving gpsFix; public RecordCompetitorGPSFix(RegattaAndRaceIdentifier raceIdentifier, Competitor competitor, GPSFixMoving gpsFix) { super(raceIdentifier); - this.competitor = competitor; + this.competitorID = competitor.getId(); this.gpsFix = gpsFix; } @@ -30,6 +32,7 @@ public class RecordCompetitorGPSFix extends AbstractRaceOperation { @Override public Void internalApplyTo(RacingEventService toState) throws Exception { DynamicTrackedRace trackedRace = (DynamicTrackedRace) toState.getTrackedRace(getRaceIdentifier()); + Competitor competitor = trackedRace.getRace().getCompetitorById(competitorID); trackedRace.recordFix(competitor, gpsFix); return null; } From dd5da5fa965eb453cf65952ccce343387baf3bd3 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jul 2014 17:22:11 +0200 Subject: [PATCH 123/323] use mark ID in RecordMarkGPSFix operation to reduce replication volume --- .../RecordMarkGPSFix.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/RecordMarkGPSFix.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/RecordMarkGPSFix.java index 9bf8d9d790e..e0ed4ded9f8 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/RecordMarkGPSFix.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/RecordMarkGPSFix.java @@ -1,5 +1,7 @@ package com.sap.sailing.server.operationaltransformation; +import java.io.Serializable; + import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.tracking.DynamicTrackedRace; @@ -9,12 +11,12 @@ import com.sap.sailing.server.RacingEventServiceOperation; public class RecordMarkGPSFix extends AbstractRaceOperation { private static final long serialVersionUID = -2149936580623244814L; - private final Mark mark; + private final Serializable markID; private final GPSFix fix; public RecordMarkGPSFix(RegattaAndRaceIdentifier raceIdentifier, Mark mark, GPSFix fix) { super(raceIdentifier); - this.mark = mark; + this.markID = mark.getId(); this.fix = fix; } @@ -30,10 +32,22 @@ public class RecordMarkGPSFix extends AbstractRaceOperation { @Override public Void internalApplyTo(RacingEventService toState) throws Exception { DynamicTrackedRace trackedRace = (DynamicTrackedRace) toState.getTrackedRace(getRaceIdentifier()); + Mark mark = getMarkById(trackedRace); trackedRace.recordFix(mark, fix); return null; } + private Mark getMarkById(DynamicTrackedRace trackedRace) { + Mark result = null; + for (Mark mark : trackedRace.getMarks()) { + if (mark.getId().equals(markID)) { + result = mark; + break; + } + } + return result; + } + @Override public RacingEventServiceOperation transformClientOp(RacingEventServiceOperation serverOp) { // TODO Auto-generated method stub From e57886fd5b18a19a66ae52399817d89844d777f9 Mon Sep 17 00:00:00 2001 From: fmittag Date: Wed, 16 Jul 2014 17:29:11 +0200 Subject: [PATCH 124/323] updated main.css and base.css and removed proxima font --- java/com.sap.sailing.gwt.ui/base.css | 25 +- .../fonts/proximanova-bold-webfont.eot | Bin 22648 -> 0 bytes .../fonts/proximanova-bold-webfont.svg | 553 ------------------ .../fonts/proximanova-bold-webfont.ttf | Bin 52632 -> 0 bytes .../fonts/proximanova-bold-webfont.woff | Bin 25844 -> 0 bytes .../fonts/proximanova-regular-webfont.eot | Bin 22477 -> 0 bytes .../fonts/proximanova-regular-webfont.svg | 553 ------------------ .../fonts/proximanova-regular-webfont.ttf | Bin 52848 -> 0 bytes .../fonts/proximanova-regular-webfont.woff | Bin 25664 -> 0 bytes .../fonts/proximanova-semibold-webfont.eot | Bin 22606 -> 0 bytes .../fonts/proximanova-semibold-webfont.svg | 553 ------------------ .../fonts/proximanova-semibold-webfont.ttf | Bin 52772 -> 0 bytes .../fonts/proximanova-semibold-webfont.woff | Bin 25756 -> 0 bytes .../gwt/home/client/HomeResources.java | 13 +- .../com/sap/sailing/gwt/home/main.css | 226 ++++--- 15 files changed, 149 insertions(+), 1774 deletions(-) delete mode 100644 java/com.sap.sailing.gwt.ui/fonts/proximanova-bold-webfont.eot delete mode 100644 java/com.sap.sailing.gwt.ui/fonts/proximanova-bold-webfont.svg delete mode 100644 java/com.sap.sailing.gwt.ui/fonts/proximanova-bold-webfont.ttf delete mode 100644 java/com.sap.sailing.gwt.ui/fonts/proximanova-bold-webfont.woff delete mode 100644 java/com.sap.sailing.gwt.ui/fonts/proximanova-regular-webfont.eot delete mode 100644 java/com.sap.sailing.gwt.ui/fonts/proximanova-regular-webfont.svg delete mode 100644 java/com.sap.sailing.gwt.ui/fonts/proximanova-regular-webfont.ttf delete mode 100644 java/com.sap.sailing.gwt.ui/fonts/proximanova-regular-webfont.woff delete mode 100644 java/com.sap.sailing.gwt.ui/fonts/proximanova-semibold-webfont.eot delete mode 100644 java/com.sap.sailing.gwt.ui/fonts/proximanova-semibold-webfont.svg delete mode 100644 java/com.sap.sailing.gwt.ui/fonts/proximanova-semibold-webfont.ttf delete mode 100644 java/com.sap.sailing.gwt.ui/fonts/proximanova-semibold-webfont.woff diff --git a/java/com.sap.sailing.gwt.ui/base.css b/java/com.sap.sailing.gwt.ui/base.css index 9ea6b783a7e..e5a3a6a364f 100644 --- a/java/com.sap.sailing.gwt.ui/base.css +++ b/java/com.sap.sailing.gwt.ui/base.css @@ -1,7 +1,3 @@ -/** - * Resets - */ -/* normalize.css v3.0.0 | MIT License | git.io/normalize */ html { font-family: sans-serif; -ms-text-size-adjust: 100%; @@ -187,27 +183,24 @@ td, th { padding: 0; } -/** - * Fonts - */ @font-face { - font-family: 'Proxima Nova'; - src: url("data:application/vnd.msfontobject;base64,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"); - src: url("data:application/vnd.msfontobject;base64,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#iefix") format('embedded-opentype'), url("data:application/font-woff;base64,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") format('woff'), url("fonts/proximanova-bold-webfont.ttf") format('truetype'), url("fonts/proximanova-bold-webfont.svg#proxima_nova_rgbold") format('svg'); + font-family: 'Open Sans'; + src: url("data:application/vnd.msfontobject;base64,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"); + src: url("data:application/vnd.msfontobject;base64,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#iefix") format('embedded-opentype'), url("data:application/font-woff;base64,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") format('woff'), url("fonts/opensans-bold-webfont.ttf") format('truetype'), url("fonts/opensans-bold-webfont.svg#open_sansbold") format('svg'); font-weight: 700; font-style: normal; } @font-face { - font-family: 'Proxima Nova'; - src: url("data:application/vnd.msfontobject;base64,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"); - src: url("data:application/vnd.msfontobject;base64,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#iefix") format('embedded-opentype'), url("data:application/font-woff;base64,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") format('woff'), url("fonts/proximanova-regular-webfont.ttf") format('truetype'), url("fonts/proximanova-regular-webfont.svg#proxima_nova_rgregular") format('svg'); + font-family: 'Open Sans'; + src: url("data:application/vnd.msfontobject;base64,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"); + src: url("data:application/vnd.msfontobject;base64,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#iefix") format('embedded-opentype'), url("data:application/font-woff;base64,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") format('woff'), url("fonts/opensans-regular-webfont.ttf") format('truetype'), url("fonts/opensans-regular-webfont.svg#open_sansregular") format('svg'); font-weight: normal; font-style: normal; } @font-face { - font-family: 'Proxima Nova'; - src: url("data:application/vnd.msfontobject;base64,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"); - src: url("data:application/vnd.msfontobject;base64,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#iefix") format('embedded-opentype'), url("data:application/font-woff;base64,d09GRgABAAAAAGScABMAAAAAziQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAABqAAAABwAAAAcXkJ9q0dERUYAAAHEAAAALQAAADIDAwHyR1BPUwAAAfQAAAYgAAAkqL8s4V9HU1VCAAAIFAAAAgcAAAYUTJ51TU9TLzIAAAocAAAAWgAAAGB+WcMsY21hcAAACngAAAGNAAAB6rnlRJ5jdnQgAAAMCAAAAEQAAABEEDAUbGZwZ20AAAxMAAABsQAAAmVTtC+nZ2FzcAAADgAAAAAIAAAACAAAABBnbHlmAAAOCAAATfYAAI9w6kDRcWhlYWQAAFwAAAAAMwAAADYFrjHvaGhlYQAAXDQAAAAgAAAAJA7BBtNobXR4AABcVAAAAlEAAAOitmw6rGxvY2EAAF6oAAAByAAAAdQHNCtmbWF4cAAAYHAAAAAgAAAAIAIGAT5uYW1lAABgkAAAAWEAAANiU496H3Bvc3QAAGH0AAAB6QAAAtuMD3tocHJlcAAAY+AAAACxAAABLRMVH9p3ZWJmAABklAAAAAYAAAAGqZZTTwAAAAEAAAAAzD2izwAAAADCj4DHAAAAAM91WhR42mNgZGBg4ANiLQYQYGJgYWBkqAHiWoZ6IK+B4QmQ/ZThBVgGJM8AAF7rBPYAAAB42s1aTWwbRRR+DolJggopMUUFWqckkB+SojS0QFpXQAtYICEwICFUISHBXkBIRhyQfOFixM1CHBB74WIJTpa4mQtCLNe9IVkI9eBDb3tEPvSQ4Zu3s7O7ju3MrB2HeRrv7OzOvpn3vvczk1COiBbpPG1T7tMPv/iM5mkWPSQEySe5Tz7+XPZReIdnM3ydp5nVP+WbK9cu/Euv0j56iyIQNdEWHqormmg7ooR2k39Loiv+EXdEB1dZA/x2hEtjF7EHDl1wdUEO2nXudfD1W6n3JE8PvS0qoNUci2c3+c3Uk2DUiGFPjXi21LUj20fPX/g0sSLuih4k10OrZ7IGvNXLzKumZduVmh0t2zSnLFxZnn7UlviQPUfqk9+RWOffrr20gVBGDlDriVuRduNvp7kJX3LRcvGl5SRlY8gzoKmWSJeWo0ILbkITgZJQYC5fKUn7dYaeCHL1QzRI2dqhKXobc+1Jv2A3Mpu9pFCTktHg72W3ysQ3mhqTPfj13pSQ1NA6Cq2gKqrqiQekdEQdaKmLBkeeVoigfmtivXgcKToGHH2OVJ0wYhnIoxm3B7zVNoxlDlt6GesjUTxoiqJwDnqidCAx0oaOG/iVtYPfpigPQVNg7hvEpqiwFEuQyyJaBf2k2B/BMKu62BtTk4UsmAy5wk86aiZlzLgldSPnfkyYc5L6jfCXxd/Kq7id6ukNzCd6AyOcxKFn6sGh+2C4v2d5BVEUl9EzYR/BsDzHxDoHR65j0kyFUd5JZzlApxvnPrCkFpO8+kybysZq4SggvoFagQW47DsqKoP0RuUKJpiNvxBhKLJ/9lPGuWc2earcpKsxY20fyVzTDu9JxFuMK2nPlcy9tCbYF7ucbQex91ajXK31MvvGGmTs4+qzRx7p602zioSNOCrmdHT0sZBu7EFS6+zo3KHLESrMs72+3MTVkaam9gAS122Fdm8UJo3X6aejmox+WhcW2aYo6HGtlNWWIn/BMa7OpHdpMvqw3da0Puv8Ldm3iBlIyy2PiqTG6/T6kNfSGKqCTyP7jlBpqn7M/s+jqZco7rOEiunM89h4VtVewIWGKkf5LPiGCcgdESHkWbXZrYzBbzHTqE0VTUqZ+W4mItJiMrcx1Wfsccyy2zFklBFh0rfofYLcJVSsRrdZOhnOqMIowZj0o2xjcjuwo2JL1n13FlvR/rwlfULsBU3XOa3941g890jiRtY6qEZVim3exPorVAH6hp2JNagyEkMux0p/4mJwDp/V6CiIbMLOs3Cu5YzjC3lXW8h6GmF/mhCd9xj7eX/o2Y1vZivwJR5nQbX0vh272vDcqQYLaqhz6ubweQ3BUUWixHQ1kym58og8AXlc9uhkn5eY2HX/XgCS34sjhV2GpfdIvtyTZMkX7aJRcpZAh/yrStNupzjeGSOfcreng6tj932j1tmF77PyY7wba2eJuppnYJtHMn5a6f2VLU+5ThtM6HU2MkrYS2bNWse8g4bvb8m/G07O59vveSd1/jIWCn2aerH30vE5G8gZi3eb/i+le4K8905A6+5JLBRcC5SjOfqbrqiuNbRu4Poky2GXZfFCatCL3PMUKFlmaJbyuC7QfXx/lu6nJW7l6UHUAj1ED6P3UTpH16lIF5hLVFZpnbZw3aaL9DTuZLnE/fLJOvqepefoedqnq3SNXqISWsnxW4YL3u27f6bvfvbQiDzXiGT7rKIi3yfL9oB57PTdr+tWCe0SjyoazHsXerkCbYS/G6jy/z/uQEpL6p0nuLXL8l/DTNZwvZxY4WWl4x09J3nWsYIv5FEv6WtO3YX3ss7QPcDIvTRPj0O/qylJyvVsMJ1Gja3nImhD1W0gYJkRsElncD9IE/1lqe/+sGRXUpTXs05SWnNzihYOfWtdzT1ZNoben+GaR8/CkZrbAi2B1tTvOdZLcpWnFc3QYzy70IZydAo0Sw/gyRyktwzpPwJawFvnobk1zPkU5LmF5zuwj2Ws9jpmdZNegYbK9Dos+A3QFr1JH8BWPwLt05egq/QVfQ0r+gZ0g74F3aTv6Ht6mX6gH+k1+ol+prfoF9Db9CvoHfqNfqd36Q/y6D36i27T+/8BkK0ScXjajZTPShxBEMa/6u7dGAkhWXdml4nIECSIhCV48gEWFlEQl0Rz3SSYBJaN+AfEoF48+ASefQDxCXwAjx5CDuJJMOYRJBdTXdO76ZnsyBymu6f41ddf9VQPCMAoJvEe1O1s9jACwxHc38tMUNAwq+udj4i6Xz938PLb+qcepja21jbQYEIJZZ+E1y7rEZ5hCm8wiy9YwzbKHH/MqyOc4ZpGqEFvaYeOJU50mmjQubwH9JPuVKSaqqeO1YX6o2Pd1Kv6QJ/qH/rORGbWfDCH5sRcJbuay2QulROd0oz40KgiQuwclngkjtixImNQiKrlUmEhytcKc6mgEOVrvSjkK5+qF6KiDJWoVxxTHnz7Cnaxh33+8k+Y2sEmtqQbqlxZKHn1TB6hyTRhDOOy9zCinSKUuLOnkFax4/MCZHsoOY5XrqrXHrviUf9Ofp4zNMdiTEuOrz4v62yW3aGOCXeGky6+7GlW3X7anbZ9jz3d5Qxla5v4r7YV9yX6e4bsgfgOwkVbXpdVc067NbgVwzwvpRQSPs+zz7ZyPbcGnoMHPIcFPIc5nt+lFB727LNFPIcppelM7yjugjGO1KSmfu8M1+x3TjanwY/9H49yTbaff7PCHBZxK/MCfnHGIm54XBB+TnpYMR/zWMZTvpGacwO+m3auyU3dH6wV+wiFV+jiu0SaaP8F4QtIaQB42mNgYp7MFMHAysDCOovVmIGBUR5CM19kSGNaxcDAxMDKxgyiWBoYGNYHMDz4zQAFuTnFxQwKDLy/WdiC/gUxMLDrM6UrMDDO9gXKMd9lDQNSCgwsAM8GD7oAAHjaY2BgYGaAYBkGRgYQeALkMYL5LAwngLQegwKQxQdk8TLUMWxh+M8YzFjBdIzpjgKXgoiClIKcgpKCmoK+gpVCvMIaRSXVP79Z/v8Hm8QL1LmAYRtjEFQ9g4KAgoSCDFS9JVw9I1A94/+v/x//P/T/4P/8/95///19+eDYg4MP9j3Y+2DXg+0P1j9Y9qDpgdn9g7desD6BupMEwMjGANfEyAQkmNAVAIOAhZWNnYOTi5uHl49fQFBIWERUTFxCUkpaRlZOXkFRSVlFVU1dQ1NLW0dXT9/A0MjYxNTM3MLSytrG1s7ewdHJ2cXVzd3D08vbx9fPPyAwKDgkNCw8IjIqOiY2Lj4hkaG1raNr0vS5ixYuXrpk2YpVK1evWbd2/YZNWzZv3b5t9649exmKUlIz75QvKMh+XJbF0D6ToZiBIR3iupxqhuU7G5LzQOzcmrtJjS3TDh66cvXmrWvXdzAcOMzw6P6Dp88YKm7cZmjuburp7Ouf0DtlKsPk2XNmMRw5WgjUVAnEAG4MjRcAAAAAAAPdBVYA0wEKAJoArACwALoAvwDPANcAlADXAQwA1wDdAOEA8AD2AQoAzwC9AMgAtADFAHoBAgA/AGUAtgCDAEQFEXjaXVG7TltBEN0NDwOBxNggOdoUs5mQxnuhBQnE1Y1iZDuF5QhpN3KRi3EBH0CBRA3arxmgoaRImwYhF0h8Qj4hEjNriKI0Ozuzc86ZM0vKkap36WvPU+ckkMLdBs02/U5ItbMA96Tr642MtIMHWmxm9Mp1+/4LBpvRlDtqAOU9bykPGU07gVq0p/7R/AqG+/wf8zsYtDTT9NQ6CekhBOabcUuD7xnNussP+oLV4WIwMKSYpuIuP6ZS/rc052rLsLWR0byDMxH5yTRAU2ttBJr+1CHV83EUS5DLprE2mJiy/iQTwYXJdFVTtcz42sFdsrPoYIMqzYEH2MNWeQweDg8mFNK3JMosDRH2YqvECBGTHAo55dzJ/qRA+UgSxrxJSjvjhrUGxpHXwKA2T7P/PJtNbW8dwvhZHMF3vxlLOvjIhtoYEWI7YimACURCRlX5hhrPvSwG5FL7z0CUgOXxj3+dCLTu2EQ8l7V1DjFWCHp+29zyy4q7VrnOi0J3b6pqqNIpzftezr7HA54eC8NBY8Gbz/v+SoH6PCyuNGgOBEN6N3r/orXqiKu8Fz6yJ9O/sVoAAAAAAQAB//8AD3jaxb0JYFTV1Tj+7nuzZpbMmyWTPZlMkkkIyZCZJMMQwhJWIQSEEJYiAiIgGBbZRUCKFDCyuSJqpZZSi0vfm4ygVBFca9VaS4Vaa61tXeKHlc9P/Swmj9859743mSwg/X3f7/8XZ+a9N5P3zj333LOfczmeG8lx/Hz9VE7gjFyFTLjg4JhRl/95SDbo/zw4JvBwyMkCXtbj5ZjR4OsYHCN4PSz6xCKf6BvJ5yuFZL+ySD/1wmMjdW9ycEuu5eI5slPfzJk5GzeNi5k4rkwWdO2xFJ4rI5I9KHFnpJSQrLe340uyhNqses5UFrd5uAm6MskWjFvpkZxKymSrTXS2CUYTX+iNcnKKIDola3RAZaSqJpTmcRsKil0+wd8yddWUKaumphBD0fEN0aamaG1Tkz6to5Gj8MwTfPyj+sWclUvnVnEwMK5MSg3HLTrOCI9LCxEpA2GSDc72thQDQmLxcKnwlSUYT2FHBodsA2Cc8Is0pw1+4WXXvcF4Gj2SM+HrFAtAJ0RlLwefqVHJKUqOKDeg0lVVE6bQphI/X6RCjsfzxoTCowudznljSdPoBexI92V41Kiw49MbW8iT/q5DGEeA43RxwGsWlwdzF8sEvEqecMyI47GEY3oCeDbbwmFZZ22XU3NDoThHMvW2MlnMhoscXHR5QzDa/KDsI2UIVyTs8sMrLNCX0U9ffhe+8KtA+dsb3i57Z+1P17y15tD6t0vf2fhu+Xtrf7rud2sPrT/95i2/JQt2k6W7yQLlAL52K3fuZkf0OuJe4MZdDOu2G4q4Ei7I1XAPcrEAQl0IMFrapQGhWECXUtY2LFBsLos5cByusFwutEvpoZijHL9xiGagmUhQsp2RS13tUqlDLiZlMZ2jfwjGV+ThvLqymDN9AJxJRQ45DLOQ52qXB8JnKVCORKJyXlh0ymmeKJCPLgCXuKhULh4ltrS8/uEaoCrJ4ZQ8QFJDSS7xihWG6qqaSHXYk+Y1FgfEXJ3HbeeNHn91ocud5hXthAwh1VXFgXHb6+RFVY3XjhxVlnXupUNfH96+/FDDklsn1lQvfmL5IeW9Jdf+oHk+mX3ddQvmnDo+cr6D7LJ5i0vqRs8e+PD74nPPZz0cW+5Tfuz2j7x688RJd6wcl/LMM2nCuYGjs0m9ZWbHXvui+mGNuYhDPRe9+In+FNCvhfPC3BcBJh/hYumAx1ghLq1Sa3ssA3BHCVv2WNvjFXmFAkx7BRymmulhqrWdSAOQynFtAc4kq0N2krK4gZ0BfWfDWTE7K3bIZXDmo2dyJRK+FVahWUjPAnzJZcWi86mM3Dx/YSZdlBXpgOBsXzQqe1LhyMBFo0k07y8ojrjTwqEawJq/wOAiYTOpqsnHr/LxG/pFgSH63H37n33uvntPHFzQOGnhwkmNC4LCDa0d95Adz5GK504ofzjx0MKDCxYcXKjz3nnq5F37Tp3aN2P58hkzb7rpuz365gtHSPSuU6fY5WXLZsxcuZLRXx3gzgC4C3Bhbhh3ExfLQ7z5EW9lBsaS5CF6QM5wygJKxPY2YwmwALnK1i6VOORaGLzN0y7Xw2dVCYxOXxmNSrViPMVfVu5C6rE5Za8PiassD4jLF5WGiE9xRlt2JdIW4mEIHymuVrFhF4xp3jRvTcRrgANfcQDQU8FT/ESKA8UBO3G5vUMEhqq6iulz6+f84eG9J0uj1zcOjTZOrp4aerJp0d6blY9Hhx/7w6wfrxv1j9b5q9feVNNcP+7qZ0fWjB/lnzB41LWkpWHTtaN8j2/Z+fQ9WwYvHDO2LHfSzOujm34y6ZXd962ur94buva+5TfeOnfd6OvmVK7ipzSvzSyvHd1vyJg5gDOC/JI0UX5Zz7ilyioJsGaVT16GV8r2Lk7Yk/d18btkLofPbFYW8U79Bnimi6PPIWdkIzzHThlVjcMJGOM9bicsyuaf3bmtZcHCG7fdeYGcJ3+V57+m7Gr/WNn5ykKZo/AXKYvIB8n3Es7IKYl7uWqc1VV8IJLmdPDGoiULFizZtu/QoX3KW6+SNR9/Slb+Zr6s5ClOJVfGe9UKPN8KuBCBg8X0iItUFRfOoJR6RtKF4nZVRIRkl8pVAxFvIGyMeI1eY8DrjxhrX3990MmUFZZT0TffjJ6yrEg5Kby16IZ5Gz/6aOO8GxbNu/mjj26mz+JeFl7T3QXrfConcUHJGJaJtV3Sh2IcQV7IpQCXJBweEgHZojUopZyR+FDczNatLhQzp+DXZiP8MsWMhymcuQxlFwJW7RNBcHt8ol+sJRv2kw3Ktv38sp3kAeX6nUozOQIwtCjnyb3ExDm4ci6WqkluI4eSW6SzYgCxjS/gHqAdwIKXDEDmNdo0GwNDSASouuXqQfWzFk2bF55mtnrDI2+aPL954TNDJuYuDxtwrHXkLr6Ub4AVWoBjlYmpHV9E0gVlDniPYOfMQEt6DXBPHTlH7nrmGfzblovtpBVgNHOlSdqFdkCkFEqlqnohW+gtuvQF0BWmwv9MTZg2De7HXTzLb9NvBljSOAQAuSRvx1lWn+8hYUI+OqhkleofvTCd6hTNFz/RHQC68AIMIIud+PAcoT1mQIYSEACKfhSKDJBZGQ65ADUZkEvAVuWCDGAjTgOyjEAOHFpVfjlECIdyBeQRwBOEGncuHw4N4aurKvgCO2kONG98Zvmypzc2B+Do6WXLn4Gjr3yj1l09ad1IX/6otY+sG8UfWfzqse1XX7392KuLb/j1sZ2TJ+889uu33nhs16RJux5bsPCx1okTWxF/wAL59aBHGECWxHTIDwnVzIxBSXdGFmClmAiikopPRD8JA8UY88jOt89/ILxGdl04ovN9xtZaE+gkIwEPuVwDF3MgFmyABSqJsoX2tlyHYII75wWldLqeY+lGJMp0BxBlPhKQzQFPEaNSrigb04GzZqN8xkVaCMgglGECwVYP0QMeiL/A2DSdWO/d/ufbB8554PHV/DUdBXcdP75wqm/c0vq/7tPtWbJ89gtbx993x0P7pp8+sXzfA0tWXBsaObffIwzWSpizKMBaBusrVoqw6gQmOmWX0B63m0szQFTacfL6w+o7Ixc72yUx/4woZwNOyhFcMyAFVmNxFEC1i8jtXc6YJy07GlUVCCrnfAXGapR4yMVxPYRDTIvA2dX5Cwort9b/pmXbfuXtVSXebbceeGjtb++5uanjpx8QW9W0BTVVS+bWKZ/8xxPKVy8/NGHGTde+qHuXnxm/fv891616+Rf1KxoqzYaKiZvGyx/cAnQYBfwvhrlMAV17Ohcz42zyyKY4i5m3AUsAxQ/0KUOIKt7mM5I1JJtgWEIoZqL8wWQAVmE2Ua6BrCIVh2nh6NxLvCjZcDaqCXAOjx+oAJT+KF9Jxh09+oUyhxwk1aeEOR2x08rrpPo0v5bheTXgGekrG7XtLLosAc82xLMD8Ow2Z9kAz+4UwHNOULKekUUgDNGKIIip5jJJdMgGIMB0WC+58CmC7gGrGDBuENsEszuLKh1CFlPlzGIbsVjTURK7kZJwGhLahjFA2R3OABKTzp+/emfrPzbff8/GJzYdETZ3Lo88cs2al25R3v9Eefb44UO3L2rZtrqSv3BaWRuuv/6NHb/vTKz1LUA3+VwFt0wdUTGMCMWBbIERufpnoZrtwhEF6bJ3AIp9IcnhkEthBF4YyQDUSIHY2yx6F45A8oqyyYhMoD+oUxJccIky54BRWpySKSrpRcmoqg/hkNeIxG8nOUQbip0gk/DmCgnFqrnx7k/3nr9v6aCts5YdnlL1m+Wb3rx1w6f3rD6xZXrgG33xyNXjN9398NppP+GP3KOcbLkvXjt40+s3ByasmvfmHZt+t3HcD+O/jdTeOHfQXT9Yu7GZzWMEaMtB+UQByD6NTyA9Ia9AJsGB1UgFPePRxEciwrud55VveZ1u6+nD303SbQWuivhbBvjLAZlSxy1RMegDDFrxfoPxfkMo2nKBW+Y6pApcc6BhSIagXOHES3IhQ6MUxa9EwOdQuBCtEJ1Hre7MLF+pQIlisA80U84gekurVOWrJlIB6jplrTxKJ4a7/Aqi6aQVfMAuuNy5AlO7AI9Pbtv80KC5P9w1fvaTd07/8eZtv7z7M+Vv/3HnVwfXrfnpkDnNRcXNs4ekBQaOr80JNEyP7P3HXjJnw/H+JY+sH7d+8sCMfpP3/GD9IyX9j29Y+2RT05P3zli8ZHrllMocvT6ncjJfUTikJM+hN6SXjS1vnjuX2aeTLrbrNuhbAMuV3ApV6no0LhoE+sorTkUlPg/pK0QR5Qes+B1odEqlIdkM7AktHz9MBiwmoKFMsS3VI+RRpKBSjiw2T5ScUanYGTNnIr+SgiJjtVVcl9wJRJCFodQJUApT0RSpALZlmETS/0rJqfK63S+t+ucDywf98JoZu6f250O/uWnTG5u/fHj9tIerdRkDRsyv23S38MiLxDRuS/y3q9b+Qb51zP6jdYMn/PDHzVnWhpVz37h73S3TxwWbm4cE7qT+ApDnwjmQ5y6wbbokug0NWidKdHeSz0BKZa4DTw+5LvaU8N3lvJBdO3VqLZwwnLco35B74Zkil8FVczEPPipDBzcPMh0ikz7RGdLUHSktJGd103giPTWeYgdCAAAkqT6mrPCopjVNU1dtjTIFaFLusrDuKv7dVU1Nq6ZSne9p0PkOwRrjgMt6zMRTK+zuTOM/4+c8SB49pDyuxA4jvLXkkPCaEKe+mkymMYFqCMqSHi07U1A2JzQlAi+4ScdNwm5yqLWVbN+5k63npGdFqs0EHlfLn+t0C7ufPkwaSdMhZfqDFDdZF9uF9bBe80C3uZ6LleB8+HUqPeaouo37jJQfku1gGhUBduyUFmWDquLY3aIzLjhz/AHkdEWwYlG2ZzolC9Bjjh9kqMGZhzQoqDSIbA3NI73HjXY3rljGwF2JWc1q/GTnib+VDJ/QOLwk3XN07Mi5c0fOn/3ksvuacZ6bdQfn3hDfNWTRVUNrqoYXZVbZh4y6cdSyq+onB4c17e1YrWl7OL6xF1foL+izwB6cwH0EFiHMvZQfbkvNy7KXyeP17VJdMD7QSpV6IDxbWB4J14pDUkUwrqOXidRIlcQw0/zDDtkLgx7uaZeGM8vZxCyhibA8azKPD/n4q/6cpyxFyquwS2NO6uX8tH/ZJd9JaYyjLXdMnqusbSy+x+A9//b82/0GO6xhKTfKteX6xoytgP/IU7l5+epxRYU0LJPIZcNF5zGbzp1VMbC2jq5z90BY/YNqwQwfPxL4Imfy+svCosoHnaBP6ZyohQChBgkubsQyot0bCQsGD65zXPecr0CHlhZqn2BzgbbCFzEe4EIZZBj7D7L298+T0odGL982sul6Ex85NP3Ox7a/u3bWNn/aqWW6YFH+qOGlyquKslf58uU10+7/48ZX5NJf/+2YcuZR8uv506bcVl7tvHbMtmPkq7NkzZ+eUD745doX1w6dVW9vnP3cfTvO7Zhcv7rzK3fGgFBx464ffEsi615Vvt617g/3z1hZu+UIKX5rRsuMesIPGM98jaC+6ueAjDKCBtSfi3HoXRLCVFDFDSaOANs0IMVagrKVyisTLN8UQEkYhJVP8Akun6DjM0gZn66cn9+56/pHyblvqD/BpHxL3uNbYcXtAzn4NjzDDtp+Pncde4qcChyaysN84NDpXvqodHyUj3LoVCd6saRUB1qFsgVEVzaKLj2sEDQGXNkiinlO9iI/sUSldBGlfr5T0iNwoo+xFhRXBn+Rjwl5X7V2sI8/eOy3O+cvu//tZ5U60jp36dK5ymqyZcaCBTOUzfrml+/b8AuPeOqWn/329PUzrl56eu7ECdcxHgCyWD8WZE02F2Y+JNmpaTJWgSlkCDxTvTjZ6oSFDJaWJ02jI1TMdUwf4TxuDgnDy/sKCov55mUnienek8Qx5fXw8SWvK18R6+uLfxV+82Xl4L3KtyeX8UceICnHFv5i5+Lpy5TP/viO8snyaQuU3yw6RlIYTwY866dTv7GbG6hiOUXDshuh81DowJaXUhzUO4wMJw0BdaeobBlRB4Tu4HyANXTVAaS+faTjNBkdU1YrLyhfEuveP7bccPYu5Ut985PKb15R3la2nt7yztZtpzczHOF8RwEOCzdChcKsQQEGQ1zPyEqPAFkpQCB/qe1NTXOzBbRo3hAKqVY3s7jZa5/waOdY/khnM/+0vvm0cvA0PLn7M82gIdFn9v28lD6eB7a+xIdUO7fraYlnsSd1Sur8f6RvgvnPQ01WU2Cl7KAsWkDGBWUvnJmC6CPGB9ldlLW7AdMZoKO7M/CB7jQYoA8uuYFPxSx6qkxkaNpsmggTkRGVTUDgMmdHfVbTZJF28l3dSKcaSFo0oE3EN684RYx3k2tfebH2eAujnrm/DCsL+e2LlL899opy/BR/5MfE8tT1ykcrrl7ACOiq0OnTCr9625NkNMOhPkLnbajKCYyME0j6cFxIoVgUumbNAljkQ5LFgZos4JP6m7Q5wwAHWr2Ax/P8q1980QmT03maL79whG/uPJKYM7IFnidwvqQ5Uz0I1I7Glz5xx33nkbGwv3Vc/EQYB39rQ43HqtlIRi1CYqCTzIwxwcosHaMIJlnCvIH7oW/VsWL+thtvO6+89eH6oyt3LhMe6Zj14WcqPZ2juBjQAxe6cHcE0CFTPV4WzNEoA5X4zMAfjTh4LyntfIdvVs4qETDqOtfyOzs3dpbz9buVBnjOFo4TXofn6DXuS8fB8GDQ8BATKKUKeiAcYxeCPVvO8zP1zd9t7FoDhhy4l5Ubrd7LYA4nQc28j1aA2urA+1BHJHobjWgh8jokPxxHTDBZomwkOAyYReIn4j4ymawmK8nk88rsBcosfXNHs3DkwhFd3ncf4kuln0q6BtXnS4akp3ctPbNDFtSnW9BRYob54dE6BTrXMGlSMUmQiuDh3/C/+uKTTqDOjkYhduGI8HZHULMt9a/DekzlsrgxXMzGMWdETIdLMwOJNZt63RywEsGaBF4nm4Hn5cBnGliTso2OOsMFhiXhzCbKpZ0OKu3RwUmXlsvtDIfQ6ekv4JtlMvQ39xHd04sWPa103Pcb5dSob994/cKF19/4lvyV3E1GHF3yK+WC8nvlwq+WHOVtZ5RPSfqZs8SrfJbg0bpHqb/Bhd7hLk6FHhSzha4xs6Cp7JRVW0Iat0bhh2q77EL3cLKg8/sySJds+8Fbn//zt8o9ZNyCLVsWKEf1zV8fe+prheNPr50zby2n4i1O8eYEq2ma6nlK0/CWjwD4Kd5cgDeXg+IL8YZmZA4gK2Z36JBtmdHBwTFUWpFZ5YuAR4u5Ox49DvTkGjRUon+3pic6j604vPaB5xg+F0878uiRWT1wep/y3qubW7dNZlgtPdV6cutRDbEMrxjbS+XS0TtFqc/CqA+1irjdQVFLnVMZCQXDQRUMt4pa1MDdGIGxpiBROOxU1UHNIgnVdt4IK9ufjO7pv/nvyDU3DSOTlM+U0+cB61t/uBCx/vnz0/fNrUvp/IQoCs+/ve66uWvoOo2CPb8WcF/BzeVi5ZofzY2gZgrMBSKeATS3SzlsnQZU/0eOyNxKRvEpHZjtBeVoHQSccpoX4c3Uga7BEdFLjQa3KKUl+9QqjNVVID6Y5gHqaq6RWqz50R0T/rJt5/aaWetu27puVg1x/4UMemn7xLeWLdoE17beBtcCTTfLS5TDp45PuXFZY8XIsgJfYPDVmyY98dGp2MSJ144vrQsUFJQMa9py9ahbbmwsZ7wI/WrzqV45iIsZkn0fkhBCgwtZtB5Ym96ArE0vmMtiBj0eGtCL1mWNIfuJ6sqU687rtp4+/d1G3VbG62DdI69xcBEuZqe8TuWbkjlMXe3IOgGFAhVPqECix102YQDcTr2yKAHS8qkkAG0nn9v31VOk8amvYO3+RflAeZvPEaIdbx4+deqwEO54VflOeYtUsrFpOrMB5RYdG8dTtbnLrcMZKE+Dp1BB4NMRB/kx/HN0fqq8Ayxb0k1CSUZAJ+aM5ZRn71PlmNkCPJuj8UidIRxO8G0CZiJhnj2jnfJttI3qfvfFYmobCWAb8SfllLR/6SXLyeMvhL+YRa+bK2RLiklKOWmX9Wb4TndS4GK8PgWtoqO8oNObUywVCbsIbg/0b7JGKYYA9jAFH97sxEJSSObzJAs+rMr6i5zyrfL1RQ4G85EuC1/Alb/ssMOYTICfkVR2Fqsr0RSmTlXQI1B2MonJI5/XpVA+b6aPoP+bYLH4SB6ZQ2YTn5LHc8qHyhZlC/8sf0Cxkq86F3XW897Odm0evPAcE8poY7d5MFP/M0qYFJQ0RjobMC3GbtMCpKUjLrKGrCWuzi++AOn8ND+248PO1/gI3n8W0PAsKs8qVHlq1DikwJRJqjTKRubthVWs6dDVxEfdeZ5Z/NOdY4UvO0fwr78onDx9sqNOldUzlQ3kAvAAuj6YqmptR7eE6o9A89ho51J0qJvG9TQ+hUGdFBYpU9eHqqzOJAcvwj2VDYYv775QfRd3JTEZEK3+g+Sjg/rNF6bTmN8G8jKFB3R2NkSAB8iZU+ExnoGFGzeoEDjihIXMSBBPVMAEzY3iBahAMfUVEQ4AO6jM+VL/+t3/QtoI8EeE6XTtiFwibELXDI2TEL+FBEjDQ2Tsof/6ij/CP97ZxGYbxnTxwsUs3c6LT8KYvBw+CzMyKHjoxqF3wFiHQbfnuxWT70Ic6FaSc/oD8Psi+nuia09ChRYek4kgsqAJouRNkndAf0B5F2XKZNAxTwnfgM1azi1XMwaykQwKre0xB3q8LCYwL8qyHWheICAVdKX6bO2SzyGn40oNgB3jtbXLQTjxIcXbUEqmi216iyObuh30qOgbM5iib6PZFPYux7Xqhq+hLkbRX029irzqVaymvHzy4OvXDH17+a0Lj2yYefjCqLWLG/r/MfLovLp18+vOrp81dlNR0aaxs9aT4OQt44s33zX1uk33HPTWTNowoXNZy7DRZRNvbZg0r7pyf2X1dTjmVpBN0ylfzeGuUfUTypbcZsZeYwYceKYZRpvLXPUwOl4MUWc9unHMjnY5D73NqBMYkJPIbjWfJ1OUzKDnMYc3qrDoP3F63OhTCXhEVVKhM97Yerb28DUvfv31i7MPDyraO37T/fdvGr+nGFaoMnGK8vG5/1Q+nTp+OykdOP7pO/cea4jSNTUJ5us1mC8XN0SNKNoAaAHhNZmZSgUajcWhKlE2sM0lR1QyocpiSEWhKTCtyungMGaAgDjSVDfOJJL3wbdbd0+6a9vhw9v2NO3e/tUHJJW8QIqkLTcdVf7yzNptMvNFf6LL6sJfCuLPruHPSLk6wpPVhT/R0Y4RG8qnvEAtZhvDX4qIerCd4i+F+Tiy0NuHQBqYPYjOJ9SPKVVoRCFSopj09alrfzbo7NlBP7v21Nfv33/ruL2FhXvHbT5Azp8jmVMm8vyFI9vHTyXp/3nn0xMGku0DJxzTfOmf6MoAhx4ul5vBxVwIutUIXA+hzja2x9PMLh2QexrmfOQFJe8Z2ehpj3lpWNLLsbCkbPQC7KkOhN3qYlgGTREoIE0NT1LPGZdDfHTOI14cAOfyVQgI+uZP9/yZmDsNtsM/anigat74t1f/TfnzB3w57x/WMnrTXnJ+9+etyulz1aseHFS1a3QDySe1FdMnBh9GGsiBQXykX8y5uZkMehZRtsAQCPop7WGZM6HiR50hLoz+y05Yr7ZQzOnCMTgdoIm4nHjowuFQ74gFpCIlXXQyoV4SpgkpQCIFhhzCIns5h47sevDqporbxvz612cF//HIw8eat5U1TY4c73hf8FPcKst0WZSf9OeWcrFcykqQLIhGFmVm6kH3GAG55ZQ6CoA6ClTncECljgoMgiMvcbpYqEJI9eRSXpLrAThdUbkMtVguK0qN3iRiqQaVB8MUxYEIdVfWUUcm73R0Uc6kL1+afWjw2cFzd9w+YetLM+aveH3gddPCtT+b/cJXQEZX7Sku3nPVreTC5yR1ysSdHc0Lf3LdsIz6qx/cnF23rGHFhBnERjYpf3uiYSC5KYL27S7gwqAjwJoco+oCKeEkPpJqTpg6Lht1J4CxgUZhio2uUtDbqEzlZEOq2J1vpAE7FBmzEHedHfh0y8MHz14/r3F7qb5Z4etnHTzQOZJftmxGQ6SzAXC/Eohjo34OyDcLN5zFSJg/3qhrj5kJx7JBgD3o3e34ajPpiQkeb6MuDWD0mEVpMNIsSmAQaqRCAFhWTlg+YcLy6rO6Q0sbG5c2fndEeL/Dj2vpYqWyjD7TDjJ4AnAkfFyajs03qkECTrOXOSts7W1eg8MOfAKej5LDDBpkm+ikjsNePgwhkuzDKOZUcN4aNmT8iMazh38yeR6xUrCUowcjU6bq0r777JGj+QYPhZDyylaYl1Kqo4W6+zfoSk94OGxdHg5d3x6O1rPkKZKiLCCPKF8qp+8AA/0IeV8Z1vl3cmCv8o32LDLv0n4eQDG+uvw8rWe7/DzwtwYfrBkft0jVwRwZ6NMgKgVJ1rCcA6vGAwu6ICF9gY58bNFk2Gj+iAiHKKj88JnpoyQlZVBpRPNhQQnISR6dhzn2e5IZHNDP1rMlTy1csjOz/JnFO+8+O6lp5Cb/H38yYmOx8P62yMK5U8beeO89ndX8tqbZE2o6G/htscbqzgYV5xyVT8lrgfz7a4EkrQVPTyBbz0Z+1fLjn5xdNLdhRz+AaPg1hw50juUXL5tNl4LK48cCHKrPhIp3l1nl8Rlm5jNBye5I+EyMYpfPpE0nWKyUKtFtAtqvmn5D8mlwmE/oJ0yUT1r/GQl9tn7MllMfn9oy5oMDW2/bv/+2rSCI9ny2a9dnexY/t2fKlD3PLb795POttz93guogynZdswrfD5Lg60JVuhl1ZQ3OVNRAHCFqy6epGggFNjWhgbis1OxDWx7mnKghdxV7TAMJZDBT2aHqHwN/fu1LX/7XiydupXLzwOZxe4qU7fpmkJrKZ+dB/3ikU+EPRxufJoVPjR9IZWcDwO3tgtumwd3F30FtAqtBgztZ8qepvD0nIfltFG4bk/yZVPIbnT2YuV809GDfDV++dO3Pon/8463Pvfhf7yPQhUW7x20m58+TNFCYLhzh+UdIOtmq/PnpxiiI/fHqGuMPAdw2rlJbnwlMmzFmTlPOZRtjBtSnSszdKdCj0d7gx36QFRh9IF94f8+42e8YJtZ0zma5EKCbbYBnlHIfsBxmWWdojznpZBpYRDT1jJzlaZeymM+j0ENDoWjjnje8YERb1i5ZHZL5pJzj/ZeUfxJO2lKsZldZmwXfpRxHW25OPpzm4XsMjpOCgXnRGPwKj1Ki3DEwea05uXn5qt1bQXpdoSHCrFTN3dKmc6ZTr0qhU3Z3y4QGTZ6kegrxO6dTcnfzuOhVJUHzuBjUbN7rIr/6wbUL6m+49dYb6n/31D8emBl8cOaU68JNNy6/sSn8pxc2/3nrgejVYwcHqnIySwcvn7D58Z37q0aMqsnr781MDw69cfSOJ6+mOHVf/ITfrDsH0mWOqu9a1VUChjZVc4yhbvmOIGs8VOMBSqMBEE8i2dFDk5k8qPGka4ErVNs4VNuoxiOGaQoTUzJRIy6uFt2Hzr72Wqg8p8oWHvPDjVtA6yGpyn8e77xQXZ+WemjY1o38YKQvmHtFeJ/xPVwVMZE5p1VgU8Ia3wM5gA4bkDeSOSRbVb5nE2gCBugANlE1jqjvhhK9pv4Wt55dOG/C9pKzZ2ufuvHHjxAf/2zn3UuvGV/DH+3wb62fyXLYLnYAD04BWJJ8N+RSvhshbEaLVPcJukA+Ulo+Fd7vzOI/AukOSAW5xMF9rNzLjEPFTOi4CdPbxYmgMxgLvV2+G0eX70ZM+G5eaP2nn/poOIdkPWmHX0j8yeOD3zqXglf1klAhE94E1+xySjpz67xYeC7Uy61jpG6d43Wr2e1knd4ERrmkP6mXjA7JlOTwGWZGj48Jqb0iidTR9g5nEfwfDXB/ygdvnzvx2dsfKm9++OlHf4NRV/Jv4avDz7/TWUZpD8YvnIfxd/fxkMv6ePD+ZuI3kxTSohz5z8/+40vlCGn58iKnKKSDfKjESKOSp+jgy7vYXIG+jHMlcmWcNk2YqmFDEeUM0nRh2cYC0kCoMm+JJmbNTowFVMmtGUq0CfRXHt4wOpxXVB4Z4alkc/ndinXX8/aTjuIBK5i+MQfGtQKemeTzMamSUTBdmc9nDvlUSRdMiki+PcRvPv5A583PsnunKBt4t74F9P9loIFSs8TaLmUGZS91/cSJmhzhC0o5NDPazXws9lDMnUMjhxlmXBixHDee5RBYrBgLd+eo64NDs8uVA2o/ETFjyeKU9alUOlejtlhVU0s8yIpEtxE0BncaPYVlXFWcUhV5Ys0ttwxsnDVrYuSWW9Y8ESHxzRMHk407jtw9bZTSsicQ2KssGznzrp/vIJsHT1TjTRfPkiLqY8pJ+JgE1VPFSh+YToc26r67tdxfwvkBD+UaHlIRD3ZAQD516Ejpwbi3Cw+6MzDeeDbDgysUy6blLtle4Fi6bDzU5ZsRQRQP2ZhxiyGkdCQ7jKtKXowpSXanbHYhHrxUa66K0DGXkWqqOQEj8VCkGMqIf2MEUNAY2bhxzRM1VVU1T6zZ+OXMkWTf3kBgD7lr1LS7j+xQtg6eCKhRtuz4Oco1oUl4F3TbfG4eGwuy3lyrmnyFE5sfUlMa4vlsGPk0mBN3MBQh4PlqumiqKOvTULrYvEy65IpyClpxqciLMc0H8/D04ZDLa9cXVOj9BULEVDBs+YTqqwcUZmaVZq/w3WNw5vQfPHWxdiFXWLDw4D2TS0dMmjiilKx3jFy6dvpVxaX1EyfVlyo7mc2/WNlOtgtf0XqzqZzqMNExTaVXtZlNqzazsGozS/dqM0tytZmJ+S1oHqGWhUTCrsXNWG3WbCs6HhDmD2qmWWXfzRLiHQ2aDyIsvKbbBnIth1vLMWGBkk1NcXXB5Ls5TUGxwBcGCw0c6MxMzdI8K17gul6WPyJkhkJxp0qUFtC18qiQE5lfyiLGObM9k6aVGpzMxK9ydhUx0XITj7+6GBVbrGGa1P78wReW7R46f97w3Tc9f/DEZ++3zJ7dQs6/RjJz7rnLe8+WaZ7ODZ5Z2+7xPvOrbJL1+uojOfzqvMNAL+UXP9JV6xdzA7mfcbEQ8s2ysFwkqJU0orldHhBy5J8MygWgKpZgBVooxVYmZdD6MykLaCmKBqMUDqGlGDO4tZBJW43bAAZrrq09lluDF3OzgD0MgrHXgDyL8aGB0WhUznVj6VF2fkHFADpDBQPgu/zSMMbygIPm1sBvRIL5ZeinZIK/gtcSnRKaDO/1efwwoUZM4dP0gQqeZWtXVwXKjxT3C9z+pw0Pfzz4RMbmSYt2Z2cOM5Pyzw17gpHhPw/XzffOu/5E3Y31M8tn3Tn5pluz+MCu8LClA9eeXLB+zYqR02++rv/U8hn2Xbn5d891NgTzTlyf48+ZdKR695TIEKCPcdz7wt91a2kt180ccow8lm1WGIynqLyjGKtWkHHg9WwH5lvERZZVFkBWIaDlgkvLJx7Tm6yprrRMgtOPHlqq4OVhUZYbbBupUDxG9PALUyba4lIK2BR2ihosS6KxTFDyIl4j4oOFOQNGF40sVRXnG8bdtGDBTdHV637+yC0ra/G4duUtj/z0ltXVvyUFb76p/OW3JOOmzQd/esuqQSsXLFg5aNUtPz24+abBq2+4YTV5c9+bb+678403kmMdaZgTbk+KdcQdop2zYdRLdlgxWAxSg14APuRGF36I6n12moxpc7ZLplDMZkcCsWHiS0ooZrfhmd0NZ44Q9T3Y7Fq8xNM9jOVh//yYdoL/EtETZTy5XckkHyuZXyjpKP9IK4umdLbccbz1yy9bj9+hxr8TMsPIgR7FIh0o6wS1kE470iIJIDsEKj/uvruU3965XvDhO71X9OJMYQOspTA3GuiAVmLIIX07M2EG4JuXZlyiq3IMrTZyh+QqD7JkqYpaGdLQkByEC/1C8lgsUEso/Ed1Fqc3pyiEEx50xjIyy6NIFDkYYiXuzMEjWQa6lIFCJZKs8Ku1at3irAYjK+RzuXP1XppTxQfsJDp34GPzZq1gdsCmGWffebJj28zwgUnTFpaOmnnddTNHlV4zLf7Rse+GO9z984blF40ZnT94SNmiJkNrXXPDiP51Bbn9Bi9tmLstszG+4ae7Ng+sG1uXX5Ht9WZV1M0d0rQz0PDknT8XrN7i7Ey7y2TOnzy4MFLstWRMpb6pd3QL+JXU/+PlBnfV9JotXCbMgifE6tbYKYElptatYaGjlRXyyhl0goSkOsWipOMyLFUbHQ6NIU+OYkejdO+zz9GV6idQwcyL7foP9MtAxysCOLYw2lY1AKk2GK9mR8FgvJQd+bqSSOuoVHWyNe504MKOh9hZiNVjmtmKH4IrPiQ6j9m9Ol9pWbSW8r5gtegclmJ2ZhcHyrj+A6PJmZ5p6BNgeVWRpEIif4EdrO3k6U1wv5knyOjn9xLDs8uXP6tc2Pu8cvzE6AWPnbt1y+dHrr/+yLkf3nrusQXv/XfbM6uiJbeNvePXr7VetbWkdt2xGP/4STL6ueXPwd/sUS6cWI63wd/i3/7wHP7t58prZNU322trhv3mwfvfGh6JtmIOLt/KH9QHuUwuwG2iPDCX4acoGBfYkTkYt6uYKqGYymJLDAxstKr0vpCmYdIiiyxUmixWYHgeLBOxpzOPMmWDoJRIRVrwQU+d+RZUq2gpOa1QTA5U8Rio6qpbVcNUY3f8oLWhuX5Vw7AVj9bOm1zx44k7Ztz+0ITa/lenp1/dv3aCMGd+y6TZNcOn37AoIzJ3hDJ3VPPKeVWDCvJm5vkH0/Vex20RnhRmcXqgWo7WwfrZex0Z+6byNLx5yBh2QL4ki5V7lT1kBftk+dtqDrgeOIaaAa5VBurMLN9Kp2mwMZ1A1UsukXKF1X9+sVbYvVco29l5nmznutfhcd2q7NDVN5Y3CI+rdfPFXJD7qVoNmsbmJyUYd2hzJvnC8QA9iRUF8MFFqOoXhJDwM9VJZOXHNkbgNgeuPyw4xrNASKs5zmUX4C9zqeYTN7IlgEXItO44xZEm+H10bgNFoKwZbS6OcjlRytUmk2XN9qg7FtWl7fUH/B6RXS8jY5X42l+v7Hwqrgx998ADf3r3wQPvvvn68Nmz/D9q2jJ9e8HAgQXbhaef6lz567VxRYnv/9O79z/47rvKYvJl04ixDa++/DIRwkWBamZPcLrj/Cf6KLUnuuyhhFmUiPKKgGj+E93xzZuZTCkXpvNDaX23h2tUcexKrIFU9c/TtOrtVFa9LbLqbUSOV6uSMgvU6XbJKmxWkmYoP7C05aGHWpYe2DB5zdSptYOm6OYsffDBpS0PPNAy6OqrVzVPZXDVgi35mu4EjCeVm69ak6zqQLKEMWCMtpyOJr3o7KC76qlxozciLTqoyAZpaGOLtktqC92kNtAnpl6KzHnOqhe6ilSxikErVG1t5Q+0kvuVha3KdPIorT0UZvOnDT8GnWIyR2MgcSvDmkvjIVR1cJzRqtzhsQ6aruOwan4iMzpWbVh8FTM40mhJgrNbSYKB1h8WanHdpoGPzdwRf/pFsqVzDzGemDZi5C0+Q9bw+i1rF711x7YNx5+eOHTY4DJGDw3CJP4swFfCzeZAoVP5fhdfQzjTVThLaYaCIxT3q0wuJPejUYtircxNJv4o5Vm5tFTMg8pfupMmSbk0Ce5nhYddvIwlkQuMABqaSrcMb5z+wkinZ/jRa8Zsndm48td7H116Vf6IwcVt97T8Qji9uDIyrHqtcJuwrCpyzdwtaxc0XGcVsoMN5Yu3XaXRuJaTkMN4UJ85CUDjWDKb96byJPw+gOkI1IcaFvw0V2i8SuUmxjJMglaJCBRFfU4pZ1TZJ5kdcUJvjKkaZuqDwnQStdAcY1kgz5BY8F9B5K07SeuKI8vvFOaTwt/+63197EKjcB3WDJMFZCe/FWzEAg59MIKu/dLFwq7kUiJsLEK+ofUjU6cyHCgryVHQO9K5kRwsS01emTR+SDP0DGfirkRnEE4W0tg8msQYSXEhnTlAWlE9XGTBMJy8QCRs7FqwwauuCdbX98+ePGRFla900ITNs4evVmKzawP+urwBR27zThzSMLsQ/ajCbPKIYR9n4HI5tYpXU0J7FPJGaCGvW9l7eLv+3u863uTn7IJ1Phpk8bcgi1GXauC6eI7k6VpH6ZfsHYF8nPaHULnPFfSAGH3iwEPPnXjwwInD105pnjevecq1wvu7X3pxz+6XXt51zapV18xesaKXvIzQXC72P8hL5ek38W0vGaM88wYebdFEZZLIBBkGhrYhQvN1UoC7pqPViplWkkvN7DWF45Y0I7GxjjBs2GIolkYt8jQb2KUGIzGxKdWdAWtEU/QFmncpeUM4zaqXy0WtWZeIBj4NxruxFiBTnQOZpEQxr7HNZLaxqGiahUbvJIMI4jodiYKwBA51ojChqKiaUMc1GC2BsEinrlVYfHvHPbyJTP546dJD58/rZnbQqexIwbjjs6t5XrFuffbZrWQh64GBOFik4sDH7ediXhYvjbvZ8tMQYc/1AiJU5tRm8NJxF/QedxazRb1M0c4ScaRZXhipv9tIs8S4yWyxu1lxEnqE0tEjFNeJQhYKcORllqhMDGLPQZtJ38w3efRzBj4+c0cbZcR7ifG55hGjNvi6I4J/dtiIW1W2fAzZcj+ghwbARYd+HvDk/lyEG8T9jlWCS9XhWDqioV9YFb3x8oGl6UAWleF4OSOLcCg2kPakGRg0g+aiw6/lYistHE9P4I2A1t+TlWOpZg0QSzQUr2bXBoRi1dTLUR0GYqmpxsOackDhYCzjxCy//lGpRmzrVxYcSPE3sByuVUST5EGM+AdFe0qEK5YF5l6kpUmHcVb72KOzxt42o4d0II3C4p0d9/IGMvmjZcsOffEFvx/ExdDqtfxWfllVzaw5KC7mqeLiR1d1/rQPYuS5sLJM2Cd8xeVz/bgbuFghusBKde1SbpAVT5VRduxxtUselvhq99DaO7tD9tnbwXqS+2MmmofSDHBT2eKiOIhb03MLSymq0nPhy6yoVCrGOGsm9QJ5q7CBAGaNeNx2HbV6WM5OMaEsCll9uKu0sPKq5qsq589+fEXrxI93nvgbyZk6CHj/mqZJQ264alh1tVpeuKR23rDh4atKh03Zc2DO4rhueO1ULEVka47m2Bou0HwNe+/KNGtXZVpqUHbQkKRVDQ4lKtPCxC/0qE5rxPy+b5NK1AwXlHc7THxr92em9PVMUx/P7F0NZyFAGb0q4u4juSDLk+viSIsq15Ofawc7uNdzU7ueqwU9CCtJ7nouulX9AWPP4W58/qWXtx+879qkAetP43NffZUOWnv2P+HZPq6Q29Hz2QXas9HCFNk6zg2qjJ6g4WI6g32OqGVOOXrcw6R2MZKZCUVZAeXaccJb0mmc1OOU9X5aa1IAoyiMyiLYmDETNkBC/kbjjQl8UnOEFumHcd3RBDFPz2E2L72+ZGipv5+3eMDqpdcHhpYW9vMWBYuSR/3erXf5gkHfuKtuvRM/x1/VwcH4der4j8L4XaAHF3CLe2LAncCAGJTyQcYxD0lmiFZImM7Idiet8UIbIsfJKiTsJvSgEneURSDSolKOM6bnPdSHRNwi0zq7jVGV8q7up91GOUnzqEyHgc3RnCoJQq6j5yNHffcWENdZ9YTKcDpGY5DStYvL4zZdkrJRo1Z9Lzld3oN8bDKBrgJa0UvzajB6gbNMy9iszGuWKcZtdqcrRW0Jhv5DdX3IOV6sdLM6WFVRV+UorymKgSTtptfieXvj4+vXP+4e0r9syJCy/kO6raKDj2+8ZVrzhrfLhg4t6z90KPVtdXCcsYHWgjgxMo7RWYkLdy9CtDusaj1IXG+yJuoDXdgoDRSYRImgTYuQs8JEDJ67qV2hh5E5YTR+0lWeCC+B+ESdMFZn6hxD6xSf6ZiumH5MUsn2RMFiojKW9W3aQOtJq7gfJtWTUuelVlSKLi/mG5D6B+NZ6qRUa4WmmPXSL2H5o5yU+6WiZ8uqd6ZngKYQZK7+/gGYg9yiiihVLeSMYDR6peWp+iSlOpCsbVcIlypafVZpy84rozr3Gv+A8TOD9cPLsyaX7fiPX8ztu471rG18GlXIs64dDAp6bnCt7ab4d4nanijwRwvM6Pjvqyh1XK6iFNPAzBxL+epeWYqR76Tq0o73aCK4NmUoJbhesIz4n8LSAwbUKZJg6Lyb5L2BQkMFgnHubjA4vx8frsvB4L40PlxUoCSDYz7x8ksgTHrA8+qrVIYxmI5SG8iPfeIuBxWm3vrC8VTGSjGoVXgZKNusZmwo52SVgxifyAU+W4RWkxnTjD3RXsBfwiedPMF/Hc0Y6Gjy5Eh6VDmqa7o1XlrZxUaBrxyFBfsI4B1jF1rnJz3NZDTRzk9Y/2rGFA+d1rxNQBVEDIvjkJrWnkcyulCuJjfy3Gp4m/Nv3Q8NGrzfaqpObDzPpiBxS4DxI3grh3uirVKqVsWY6D0ttCQG+7ihAz8FdGSYGn2UIk9gs41pNVlMZSg4r02vdnfh4nG493RDnNJ+FjdLzXux0ZqroJQRjgtsQl0hmuFmpTV2bYZUK8we56TVLdj2zkEvpDlZsptVSytl6XquDAZQskzEEFDS+WhNDp49n6FJQUOtOlP/oiI/cUrxDPabfhetFc7mFqr5hHatPwwWB3LEKqhBVgMLstK+Ssjs7bQNQcxOQ8/2LDA2rNQNZ0XHm50Fl01atT8tXTW51LipVkhfSkRXVyk9kqi7jdbTy8oa5QWF/4LY1IL6/ySjleP8uCf52VpdPT9O2clK65XVT2o1/pWgr5lhld3dq8pWygxqns/8YNymiorC5MrbFBAVfibK/TRdMtGKtasqF9dWCqvKldP8GBLNjCZX5squTKCelFx/lFZ8YqqJjVUhJtfs9q27aZW8npa+dLbk+t7eOhvrhaR/Heg7lXZxHddnxW9+XxW/Pi171abLzk1kr15Z0S9aEpcp/F3/Bsm7/3uqf4V1KEH+f4IfBcvlCpfLqaD5nhHwVzODpfsYCi4xBn9fYyhMGkPevzkG1cK5zDAOM/l0RcMAqaWN4590HNXc7j7GgQpXEVtPIS3LgEg1yWPDMGM1W0HVDrkEzkrZWWnXuCPwWVItOp+yuTLydP17jVwu6g/rKXtANa6nkCj5v48e+1xZl0HNRjSQSnoutu/D1Ixe60+n4uwoxRn2hF3eF9bALKwMx/OYNCgDdlrVA2NygZOWtQC6aIOyii5cVSOuCpBGXBm6f3edXkLuXwYza3urAd+Hll/1Vg4Itx4EfauumkYmzSRiJl4zMZrJelKqnJ1HAqR0jvIuCcxRzsLHtu7n8/A3iXUFtkA6l8+VcGu4mCeRK+tXm1swHOfiCitN7o9ZrPbH7IclD8imPRiyLRafMopuXTb1FlqdstmEbNzvQTmbRo3tNiuXVYLfGsFQN0cTAXBvJGxMxMADRn8EU5+8pBiRnEbxHaBIPfVgeeyGpTdTtL7xSLhtxcLl3yzmw3PXvfHtuzf88PULG/gjvwFcNg/dN1f57NB9iM1rRz84l9gffn2bYKpRPutcNwQQ+04ryw35BGTbBc4BEru2r6puT19V3WlqVXfMLrqYNtOrspsq992ruwmPKllfNd76vyV0/f9X8FBFvwc8z1CVri+AdL/S9P4EPN6+4UnvC56MLnjcl8KPquz3AGkgY6iXhknT/SlcRwGubKDbG3pDJmUFpeJw3MMYAiZEliZDinX56ahnsT4Tam8+Df5+9Fvmr8wRsYjDT6tgeo/DRKq7Fn03vb/HwPTqoieL2JoPjexzjF+pK3xNYqGzfllemAMTZ8XKqe5V57ZE1bldrTqXeVp+0rvuXEAvYVLteY5q63VVoAsPMnnb1TPExqVp9h5z3Fjt1KKyggorpHW15/Gycg0Ag7eHQpgq4FS1VBqyTbOLLMcRndc+Ndif3D/kPFnS+ub87h1EPlp3cs3Rr5RPyLm1c+atZ7HMXRc/0b2rX8wN4s6pUGXSxpRGVRTUYJ52LQVmkK1dGuSgvmd0SNtZp3mjo10yOmjYw+tpR989psX/c8kLa1m5R4lDKj4pO7z/klwn20SHy1XW5sR3+KItUFLsAubocCVVeTijMbgMR9xR0DJcxYESrcCjxzkt7+g/CPCQg31oYzpMfIxKYadsraRFk7HMfB/11mHNNv6oRowZvblqVxytGbmuq6gDGz/SHDBDddUQHXNkecRd5175yX+UDR0/tGxA4/wb5jcO+MMzO345eXb5I0/PUy+U1I2pK1ky9pqbhQ9//rcDt1TPiET6DfJhqYc3OHTJ6O1tUybePWjC+IEFoayc9Ira6+tD06I1gVBGZMjSazrymK+c1qnrD4DUKOQqwapMqlQv6VapHuyqVGdtMItgWorUSvUytVIdJ6YIuwDZ7NQjmy4exVr1An/h/3W5OvXEX2HJ+pY3Se79HVdSt657Rnm343iieD0ZD+WAh/VXVLEfumTFfrhHxT7FQkWwkmb+OaUB/xul+2jPX1n5/iKaDHAFRfz8fyfiCl34CHBV3C3J+Cjrho9QFz6YW7GENe+n+KhQ8YGOxZJudHGM0UVRQEVJ4f8lSrTgxRVSyFHqodi/6EpoRLgTsfHKK33QyS7AyyBuDPfrZLwM6YaXURpepMpgvB+zAyLBeKFqB4yluKoFXNU6pEH5Z8REJCQYH8SOalUkDmdIbCv3ppvK4hXMVXsVfDPIx/Jn0sVhZtoSol/lkFFoEpRzrFGNPAoz8DjEKG1pjO2NEa8oCeV+lTAfvupB0ej3LsG+AylXiPLWPgMsZVc0AwfAeqhQrQj62RFLzIVOnYtmWhMf5UZxP0+ejQEwG4UoXOvC8X5MeRgRSpqfSBfdjqZzUQ4o9pWjtzAASkS5ivwwQ77kdUjDsIfkIPhuUFAeBgJyDHZX8iFRZ6MrTBoktlkcAyKU00UujfgrQbbmvUpGcbJj8nI8UPNwNSdheLrm67oMrjvUxNqOLRqKhblarq2Ga4Xyxxqunnv+CjgkGLvxQYzyhwXjlSrlj0jmmkGweSOM3CPdeWhbmJJ7HfuyLqg2fJVHwm8iQdz0BJAu1YnHLIVlAyqraxwU72WXIfhhILNjFcHw9xN8Nz7r7CvOdGWMt5wGnzxD+5fV1ZX1H3oFHFiY0SsmRXmO4VnhG8B8LTeSe4ijW6hIpWHsqcG2U6lUJ0Byh3EOpPpQfHCGH6dhsL5dGuyQ9Ei7UST2URT9FTZqONep2tNotKUT8qpOHGYBfuI2ZvtLM6oGMw4t5+XTbnfZ2OU7r7iiim7fMxixLFv0+KfOvvCajxUOPZp1FAe6obmY4bmIdfCYPHDOzSN/t0LFLLGM2fz5O8TQabQd/tH4B6rnjX97zdbfjxz+2+0jNi0cAqhubC3LKNo8ZtZ6fj9t8rF09KY9pHzipon9Nu6jeH7mGdruY39teN/ohoXTJ7f0a7y1cdK8EeMG1IUj171SMb1xwEPY/0VZS/u/9Oc2com2L2B7xH2MgIuDcY9KwKy5hgX0TwvtjR5PZ5SJrTUKLRg9Mwk2p8uTlVvajxKlD9vl56JJHUvPK0FW4QEjujRK28dwBme/pPYxtCN4t/YxqeQSAbRivmdfmTV/yFPjZqv9LIEN42av/ouc/zCp18xDzqvVaBlNZ8sNbnCTPKQz2r8FZL6Hy+AKendwyTe2xzNZB5dMPXPTZbEOLlm0g0sW5jujm86YdYkOLpmX7eACih65XBeXQ6jdKe2X6eWiu0t5t/MttZ9L8nhyYTw/+L6ONP6+OtIUqh1pYqkOptT/+z1pUFH7vr4065iKdqnuNOQNzX/aNaZsrqj3mAphTLlsTLk4puKglMfGlEfHlIdjwiImYx4dU0YfY8q93JhUfYs30rH1Pax3Trz88o6H968zasPrPS7hRqZbKVXX4wBZviAbWzOMzcf1AynzQNLocKcQqSLhGKwKsfGWwHgL2HgLcLyRoJR2BrcMwbot7IZe6Wxv81eWgVRHJPgpEvyIBNylLDsNkGDuV4BLskxss7p01J/ld8oVVXCpkqLDiKhRk5XhtEC83Ix3S8egs58stvtG16MgsUeNAokd6SLt5UxgV47uA3VnVKnceS+lDb5Gk9IYwzsExvUemiOcFBckSXFB26XijLNweS05iw3Mvtul9aHhZsPbuH/rflqccTYl6ZazLAk5cUuA8TS8ZcE9u8UZSVKc0fY9ccZyRmBB9d6vvKLdXbj4KNx7NK1NQi/zbDXOKLDt7bQSpYwQLbFNRBpt/w8ijdM1PezvZ63aZCaKmL5rQngTp1pfFf2TwHmLsErPS/c0MrfTjfnigt+LspwWkRdT6ZMJ0ifTQftlucV2uqIz0b4yma0srnZMMOptjhSvM7E5Cyf7c7SyRAHbkoJ9FTearKlOLX8DBE9xQA+CBcQMFiVyopsDou7WkOW7599a23ReOVv2cfGC1bsXnC1/hCxRPm//h/LpBwe2bruPdmd55x3ifvoXT2xdMj9j6JPTG2YvGKucVH70T+UdkvbHzdIvb930y19S2roX+ymALeUCzea2S3S1wbrMHCaC+3VVd1donW5kvjyEvW4SGUbBpMY3Un6XXUWb6OHWOrKhMNrVEkrOKQSsuUvKo9FLtMQR+jR+7j0bOa42ytneb1mXhbOKWjh+tHCE97fVz0pun6Pb0NOaUfn5WFgLqTS7apIaYfZqHXVyzO1aP3A3TDlLoKJ9IGj2FMf4dqZ4FPvqiC4n1TcwY6rP1jpUxF6yvc4RKl//fokmOzo3yNaNiU47XDfYs5Jg794NKL+vbkBaPJJCnZ2TR4MIWEN0hW2BqETtuzXQVlWQ9tkgiFxI+De6YE9jXYwp7Jka7PlmphB0VYLnqLDTzXBUvOeIx3AETreYxuIgkguGkJ95iSF081X0NQPvAV/bfnD/tktMAf8543Zd06CNYxeMo5Sr5vao4yjXxhE2Y3OEeEZCiXV1xSFhbP0cwOj6YUW+Gnjs56DNgmGgbTnGAXBdq4KGVcjWEcYjs0tF5zFBZ7W48svDlOTC5eqY5Yx8WFCOwtJoH+R3GT9CX+iI9+k5aLgUdr7uubw6V2t40ql4ak7EHreomMoDTLl4Wkofz2DSoVLVLYqQBqoonrIRT9kcEwioWhSoSKowFsDFErhYQrdLoiFI1tFaAD5bIsp5RfBZ4ZQttFl4Ud6llmYvs59hpJv60A07j2sCJpxAyIYuraEbaoRpmsowUsUIPz+hNAhgVb4qvKfbD0cWzs1VcLhhh9HSrm104wrKqRa2AYT1DC0Yc2AlOO1uyPosaz3kBPWzuFY42Hmqftbw4bPq4b1+5gh+2c6dygF6QZhfPmzorFGjkngI2F1poKMX4/5UtDsDbSnu09pfMW0dZyOQvCJxJVpgRZZoK9KJ9fZxg0OXyRIEfG7sc+jBne3ENguXUdwjWImsngYr2dYsNFSpRioNLjYFAcD5/Q9tHrDrhjsA7Ssfuzl8R8u2DyrJ9voZWw+sHzvrtv3VFNeTIhvv/Atiu7lu674/r5xM3g+eUOZVP996UzOLB9I+RsBvXJwXK+Yu0cko/RKdjDAOJ+AQ3VHJJraJnjQvHeFlmxoht+/e2KgFOXzv7kZ6kcWK/ucwYrelNtHt8dKgMWYvfw+MGM3s0XzJzzh4LyiFz7SYVhecmWilXgLOrEvAmd0Nl3HAZXomi3FL34tSlYN3h3g/hjof3n8piF95BemcwdxMpXwZ+tO7Q43NBiR/OJ7DWFBpqGsc/bVxtLkF5ECZzh4jws5mxejfyXfSTjjFbMNEOdPN9h7LF1kTzMvSyiWyH7qPdEXPunzgNL1GrWvqWalP86qxHxPMmQjWXR8dmXD3kyvsyESjn311ZSqlhNOrN5Nuc4JusDegKgO69Qbk/5d6AxoSvQHFf7M3oOEthbtEc0BBxV0z4C4HpPwiLuZBavGF41mMWgKhXvjsF5ScZ7TAOG7kWOhku4NhNLzNZCO4NxUWTPDYlw4NtytC/SWopM/p+KA3sfSem/Y+aAVzWUyz9YvBFirm7lJ9AA6hXTWK4MCk7p3DQsWFQnvcmurV2dRtlAI0YSjTlbCQjK5QiG7ZYAnFUvPRA5DqxH0CbHAZhQdV2HR0HwSHizGGVFHm8qOUKWRiOEomPnTXOeluOiyNKM1bnZS9T5cRSyhSuynyzUdI1UvbvhyTlDg8fcx/3faS8kbDd7+/40+RyLu7fv8d+YDcS4bHl9/wspo//OqS1Ud521mlfe92Ett2h3IuISeNo0FOFtDaubvUHehzw5SALYRmcMphIzvGXsDxckemBVBSjm6RgWzjQgdNjsAhO8R2qZy23a5gYXU5iiE6zNfM1WGBrCgbsqniErO4MtmOq+pWEYiitrTsomK6HTmijjMkyVNsM9XdLZKf2GhbbWujp54RO4+ytWNP+pBrt034M7F26myP/mjcg+F5V729Vvnqjn0XVqJe/yDhH3hAUR4UMvhy3jds6ej6xcOLqMj9pmzCxCDzjNSGd41u+OYb1PT3PPfcnl3Hj9dWzJgwoGLCjESNlNdwlDOBdkP7hiVnXuBe0rnhuJUtpXTWcItlYrSlGozAcGnya5DmI3vohSwn20fMiCnkVqe69zomvGYZ4Ep67iXSNi61epJSOU73TCkLjUzO69AZ+sof43Qr+bHUn1JC964Wwqw03Mi6qrHO/V07hqKbWWTOFGZvaF4Udq9l/D6wJzK4mZzkCMbNzHJI66pGprszxjOYXZBBW7qojSWwOFW24g7LAnX+mh24Ya8bfYy0BjtmtNqoWSD2aQj0aUX3YTR/T++M/9F3ui30O2+vfbGTNsPWjaR/Qn9vaPn+3xsGqL+fLjSRLfoW4OOVWtecoGzW0do8uruSvZ2+aJmeOZUVmwqipFN3U4XFlFRMMx13uZxutnqrRuGelzr7wuNDJuEW31cNrJ08n+7VDc+7V9s/nSasp3Y9TNtK3NVtY01Xr401e2yoqfuAbqZ5bEgj3UyT4u3/m33LeW620CToKP78WhcDyRMGJIIsRDxKGVo1CGY32buK5XPpGaamM7zKnJM6wWRdWrRP3Bb1ujJ78bQ5oWkpTkf16MnR+tlkA5yH4Vxk5yr2l1UZxkYHTQGw1bmgZ3TPU4DdQefCj7vOqLDjhEh5iRoW3OvUoOa02dsZvAJVNtKjfU5O0fdNF7l58qD6HyxUz6t6Tt/AHpOJe7OeFxx0Ln3Y6zxVlTIwoZI7xBhnOp3YAm1iNYi9dtoiW4OYJvT3muHCy085qe4CtycFjKMnx+tUcgC6205M/Ez+XdpvEztACDrsVaF+qN0l4nrawEL96NVjYvvOF3bseGEnOT93x465c3bspPKi5eI5/Wq6x70btPVRan9cY044LNt0oBJ6QyF2iTaxyNeaWEh61o851U7r6rBPUqadOru67ZVbpB75ux1qzS6+xv6Gg2qnkm+mrmpqGlTbxPbQbWqqZZ9TB6nnyH+4Lfxk2qMB92kxaI1ATLpE26HuHY60xkZct7/1aX+Lm8XoEn9Oe7az1hWRHo0fEu0e4D7VFz/R435U/dGjUqqlG1u1Xck4Umq1lUk5rAVhXihuMtIL6WHZZMWNfWk4VXdGznK1x7JoD5usPDO2dojpsrQmSpIxRNWULGxvYO5PpStqKsUBLCcxqcFGWjeDXfX81eEaLlGJSNsYGDxuL3yoOgnvK+AC1ZN5XdOyerLqW60A8c6/R/iizrhOxzd0vhv5+93q/pn/UnYOI2dbvZXpNa1/JSm09PCeW1tr0iu9rVv30200ie3vuHb8wl3CTLpvlZtbp+7MY7aGw3GOCVEhpLWMwn3fTXYxFGJbSRjOqG1V+t7RCgkLxHdSFS32ALUzmYuGSSo6OlLUjZdA5GA7FiJiy1xUO/yC6CeRzVHCT9+6dXr0gOOA3lddrdxEdsPrRaQMpY6sVlrxxfQJWAQfCh/CSggk9fROdGml+oSmVjA5Vyn6RPyTDhpHzoG3b4DXeeDIxz3IfDrARbq208igvfV0zFqXU/PCrOW0yBrxu2n1FPax77OvB2LExawaH259kOu1A224WYN+L/qPDVhn5MsFPTUjO4f1IrO4MdSXlsX2y4yZvJlai0ptNw5/13YcXNIWDTmHHm9tGR4sDNXj3hx8WSPug1BDN+l4JF74SJq2SQfZ0n0LB6AHxMN7FA8Z2IuUYiEtTG39HjuLpHaNP/NKx5/GdiigSleaNuqu7Ua8vbYbSRog6T2wEb2H1PG+Vhuow7EYWujeKP24Km4g955ql5VUh8OxLJ5WkmGGAPZ5qsCxhdXZbbOnFdYUeukAYw5PMaX56JVsn4Jt+UppmxeaU+gPxUpK8buSYvhZaQkelnrMmNBLva4DkRaq+wMtVALXxealpU6NFvqXwFFWDhxVVsNRRRgxlY+GjDm3jGZlVohtppz+A1gorK8tWnyXohTawsKv0krSNi7knZ8+oSJ47GuvneVLGyMDGxsHRqp7b/ByWVKi68kwWcV9BfeshvkywHx/pKUe6KfxM8B6ONwd6cH/ZaSjO6HCxnYX7EJ1mYZqQDDuB2cGa0jqL8qmnOi/h1ryfQit/15EagT8fwDf/Ku7AAB42mNgZGBgYDQ2XtEb8jee3+YrgzwHAwicL40SgdH/Df9lcMSx6zMwMnAwMIFEAVIqC1YAeNpjYGRgYNf/O5OBgWPff8P/kzniGIAiKOAFAJAMBqV42m2TX0hTURzHv/ec37kbI4bIiKBmD2IFIWEhY4w9RJGz9jBERkhIDNqDMJKaUC+BRERchtyXfJIYFRSIj3uQkBghQTGCiB5EQmL0YNKDiJjC+p47ViY+fPief79zz/l+z1U/cTkMwKK6AGcTJR1BQVZwWr7iqtlB0pxB2qmhoAaRVwn0qWWkRJBytlBSRc5tUx8B8gt51p6UOkZlFgPyHEn5iClpIC8XkZB31AZyThMlW2v36KDncdy9hIwpQMwAfFNG3izAlzkyzf4b9t/DV93w9Qq6ZJvjRzDtco2pw3d7uPe3tpow597y23M8O+fsnqEkomYYYXMMIjXcUD0Ys2em9ukHvOvD1m9nnXe4hhF5CU/vIkfNSRk5dQ/xoO2h4mzgrrPRGpAEPLY9dx4exz3eNydVahVZ+uPpZSRUBTF5wjG3tedGENE7iNi2buAmfYzQU5/aK1GubXs/EXx3GP3ST+95VrtGf0JSDbW+uEmM6Sgyeh1pOYqU9V5vIqN+8B4TrLE+nsIoydq76FkkbDbcA+oEYjqNK7bWvYOYu4gYPcnqs7gQeH4IoYXWns0hyGAfqrtVYw5T1CZZNN8R62RwEJ7pfpBJvZ3TX2wOzEt2UQk8P4SQi5HAj/L/0P8X9H6c+pm8Crz38DTQg9CnQG0O+2EOQV7U8BLyoRnk7JnUEL3yA+8QOvdP1S3m84Gcb4Mm9Ta1yDnm0EGW6O9r/hNbuG7fuEVPYpxYLalVPDZrHGetmmENsfuaZxjUVfTqNb7BIt8bcVcRR/wP3N3OPwAAAHjaY2Bg0IHCEoYbjEaMV5iKmHmY3ZhzmCcxn2DhYrFjiWFpYZnH8odVibWC9RGbDtsUdgF2E/YjHC4cdRyHOK5x/ODk4azhSuKawvWL24O7h/sY9y+eDJ4FPPt43vEq8XrwdvAe4pPii+M7xi/D38e/j/+PgJJAj8ABgQ+CcoJ+gmmCswR3CF4Q/CCUJXRCmEs4S/iUiITIJJFrogKicaJTRDeJSYjFiK0R5xBPEZ8m4SAxReKWxD/JOskNkq+kJKQapOZIM0mnAOEaGSmZPbJmsrNkD8jFyS2St5PfIX9DgUlBQGGFwhFFAcUExQ1KUkoWSmVKc5SeKLMoBygXKX9RqVK1Ul2n+kJNRS1J7ZF6iPoZDSWNLRq/NG002zR/aCloeWlVaF3RttLepSOlU6PzQLdKj01PQ2+F3gd9C/0W/V8GJgYNhi6GJ4wCjO4YLzKJMBUzPWHmZbbJXMa8y0LHIsSizmKNJYdlkxWLVZLVAWsV6x02bjYtNj9svWzn2f6wq7DbZS9m3+cg4bDNUc3Rx3EBDrjOcY/jKcdHjr+cjJzSnGY5/XD2cV7kfM5FCQgdXFKA8JWrn2ue6ye3CHcx9x0ADiqTSgABAAAA6QBOAAUAAAAAAAIAAQACABYAAAEAAOwAAAAAeNqdks1OwkAUhc8U/CEaxY0xLkwXLjDRSgnWpDt1q8aA0YQdxapECoRWxHdw6aO48gl042P4HJ5OLw2BlIWZdOa7M3fOub0tgHV8IgeVLwDo8klYYYdRwgaW8SacQw3vwnmU8Cu8gF21JryIkqoIL6GhGsIFbKsP4RU46kd4FY6RFy5iy3CFN1A0asJf2DQ84W+UjRHO0EMfrxigjQc8IoLJelrY43qBJvefSHWeBszsItTzPvcqKHMcwSKfoMNhTqiEOvK5+lyHnO+YeUXuYaTVmsy4ZDTUdM47p4w6zBv7Tru6zMtWqNMj4L4nKu5EjfN9zdT5RlcbMi/2M2Gz5ljBztQaKx3M+Gd7tnV3YoqY02Sur3OSXvdwP7f31pyzuPMRv6eLQ44XPaxUO5xRanEN/nkr6/2m+3DL2OM7xXejtKfXdH3Wf06fOvHuMecyHFZho8q5yl4nXWf8B1H1gvoAAAB42m3QRWzTcRTA8e/bKlvn7hvu0v+/7QRvt/1xd2ewrS2MbXQUGE4YLoGQcINgFyC4BgIcgOAWJMCBMx4OwBW6/X/ceJdP3kue5BFFW/yx0o3/xSeQKIkmGgtWbNiJIRYHccSTQCJJJJNCKmmkk0EmWWSTQy555FNAIUW0oz0d6EgnOtOFrpE93elBT3rRmz70xYmGjgs3HoopoZQy+tGfAQxkEIMZghcf5VRQicFQhjGcEYxkFKMZw1jGMZ4JTGQSk5nCVKYxnRnMZBazmcNc5lElFo7SwiZusJ+PbGY3OzjAcY6Jle28ZyP7xCZ2drGV23yQGA5ygl/85DdHOMUD7nGa+SxgD9U8oob7POQZj3nC08iPannJc15wBj8/2MsbXvGaAF/4xjYWEmQRi6mjnkM0sIRGQjQRZinLWM5nVrCSZlaxhtVc5TDrWMt6NvCV71zjLOe4zlveSaw4JE7iJUESJUmSJUVSJU3SJUMyOc8FLnOFO1zkEnfZwknJ4ia3JFty2Cm5kif5UiCFNn9dc2NAs4frg06ns8LU61Sq3KcrXcqyVvVIg1JT6kqX0q30KIuVJcpS5b95XlNNzdU0R23QHw7VVFc1BcySbph6DEtlONTQlniM8lYNn3lHRF3pUrr/AvZom/gAAAB42j3NOwrCQBSF4UzeD2NeI9pEEkEEZwk2FiYIacQqAWuXYG1jqWu5sXJR7kEPOpnufKf5X+xzI3bXGnIPbc/Yo+trW7QlxV1D/Ihx7XKyxanVyCgqMsSOzKJ6GjNd/GAB5gAbsPYSDmBvJFzAKSU8wH1L+IA3lwgAP5cYAcHiD0ahrI/xhlNd9EZ9ASNwvFSMweismIDxVjEFE6GYgelakYPZSnEC8nxgR1x8AaPGU+QAAAAAAVNPqZUAAA==") format('woff'), url("fonts/proximanova-semibold-webfont.ttf") format('truetype'), url("fonts/proximanova-semibold-webfont.svg#proxima_novasemibold") format('svg'); + font-family: 'Open Sans'; + src: url("data:application/vnd.msfontobject;base64,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"); + src: url("data:application/vnd.msfontobject;base64,M1MAAD1SAAACAAIABAAAAAILBwYDCAQCAgQBAFgCAAAEAExQ7wIA4FsgAEAoAAAAAAAAAJ8BACAAAAAAGOLx6gAAAAAAAAAAAAAAAAAAAAAAACQATwBwAGUAbgAgAFMAYQBuAHMAIABTAGUAbQBpAGIAbwBsAGQAAAAOAFIAZQBnAHUAbABhAHIAAAAYAFYAZQByAHMAaQBvAG4AIAAxAC4AMQAwAAAANABPAHAAZQBuACAAUwBhAG4AcwAgAFMAZQBtAGkAYgBvAGwAZAAgAFIAZQBnAHUAbABhAHIAAAAAAEJTR1AAAAAAAAAAAAAAAAAAAAAAAwB0NAAxlwAxnQAsGhLN6YrIeFpXyWhbcUp/eCJjunIsZyxF9yb3BUOC3BrEtor29PyUCKNAnHDYqMAZWRi8OSb01BqbFCsFcbnFx0xGTbxsiQw0CArJQz6dAXFgfgwLIs5iZXyx5aoSmmHZmMzgFXLMaiy0G5HWst/TIMPJt5mMm0yKe5J5Q1QWyHXFOu7QbVKyAPpipov7at1aBhaVCwEBQxGvTdTu22CqBnvTpx5Jl61WhPU7UvAQrqWnYgSkr6iF/eKOfEEt08kPwhPwmeGl2Jf1Ml0ZWVbbyKp4ixsw78GnP+BI5g+ZOH3GIyMcPAHmDvSjJAIOLJNgLwDsqcfB/0M5WHiAuUojCgnZOFEVri0Ty6P+n/mJ9YX3FOYYQxdlS/AYX7QMZ+mRw1mndJ28AfOgRLMAExLjSIlxS5nUZazBoPyV9icJOAOa4Usb90yWLfJ2TqYSh1s2LBpxg8gGUPQQmaWEv0OC3ZRxhCCydI7ZmBmtFZPQsXa0mPJDEJ84ejDIgCOBxkBUxSH7pDUJB5xa5VKYBShigoZI+KYvxR/uMBR9cOpAyWEsEIf1NqE1NmrhwSTHUQpR3GY0piTh4uG+svFJeGRCLQRSofDmjjUFmjS0E7Joj4fFpwAhkgBZA6mLxyEceIzUmjPG/R3H954UeDCwZXol9Sw5XmMJcykYkjGlkYiXdMX9n5XElKFN0NhSqDOuAWBl7OUeGUKDQsy+2lzSJUaymnbJmEgx4o9ngQ4UCf3X7vzS6ND3eeuV9rUsLz0uDFe/ItlVPnulYAW6mk/Vmnq/SjN8enW/nfWfrXrb5/1Q70Hzo80dfYraPzhJvPFrZn9y5bRG46XKZt2sZlDTA3mHTFtHpj00cdvKP16vUZUX0xM1nIMAkIyTIw9Z1kdKhRNrRJtfVRR8beBX9QokIVeoQrMou4JRQnAKCmh4NXqtITrM5UgqvL6vutxHYGsHWFekI3BADXwEgtS2HHyUeDjedhxAbzIcepg/TfVYffXfZb2Bd73qHoagFaJlkISmFYQoQaWSvEEcRs9We78yZn2z9rD6x6vqrP5PxrW7RLNWfFvyz7JVtYnEAzq2/Jl0UNaLtGWKWjb17Rw+j7SF/FpG/qYksTWKJHJFUiw7UXSMJGUmq0hM0ucdodk7aMSMYvMcEncN0tkCaWJWqWrUDWsWtO2TkEHKT5Q++UJTGBwj8FhCDlxVhFJx1rqGKGlDbjsFDiB1zB6wgtYrCJeA7EEgQ3pPV3KfIfpInZCoG0NkDiRCZCXIAJkE6CpBRBWl0gsQWoLkuyF5XhIAkCSCJVJBkhAhCkhiQ5IIlnGUyZwzv9RPhWhzTAGTcjuhL8v5ZRZbquLv/Bzc+/pOtC/roqO9puF814IXCSC6SSSUovr8mgJtYtctghAtkmktotstwmot0mst4t8uAk0uElCXod53z6f6j0fS/BPaCxqaIX2gkoe9L/Y5U5KEpCXAAbkBC9HSvrGAWksFKkNCaUcTFD9Wily0m3QWCPwGL5PApoeBQu0sKWaFJqB4WfHSGX6rfO/bDgWLhemGIh7sOmML8pIdtB86/ODvgEDdo6eIQKDY9j7YNkIGyBahZfCpn2XSOtgSbLWfspFnZ+zBM7KYW/yws6Do3RkWG+0IkQxbFCSAGzn76IBCRiMDUcGEYLMhCcNNvcQwhF6tWojDwJQKXXJqnq67YRgTRbqKowlZSwjGiV2dJGWp1rBaV0aSEhVOEuourRpoCHQbrocpdckFnHmdIT+bI6Q1CuIzZLnLJEKIFiGe+ju/diHAHXhDIC8ShDIJfExNhIFWmHvQQZBU5urnmOyq8QQwQQyKXC1Yoz9VGSxAj6hHqwFysFRah/jRYPzqP0mlg7kHhxodxrtLoMsjHsLqGRihLwUzvrFo0AG1/jugx7nOV8M8xBoXUKOzhRhmbs+nfPmAmiSjYDLomD+3GQ1jJCKH/7rPjfhT4CkUhGxuhYNkWvm+xOhxWlElB5Tx+hHtTZVg5i8pjM06BKz+tHYO45WfxXLTIMwK9PbArUFqx8qvDCTsjZxNOFR9YwdGHzD0aWEhXiTh0Xh0EqezI4M8RS9dA84xBoSEv6iMtYZBwZGQiYCP0OcHgC0F47QmKVg/AW4B4AngFgB0BfgSQJH/MfjjKo59mTmEsijAIm+eXGzTz35q+fYdhUSIhTNoO4L0VbrQgAqGy2rRMbqUXjdPIpAxDNHwezwN4HPJWyjaiuMkmFD6rjX0ctKw2VapZt18tCxD5RVBrTawCACBXETZqN03hYFkUZZnZO+jsLGoGxTPzCSAgpz4q9TbBMFaHKfPqNd7IV6wFAxx6pDVcwBkLevd0YEDMKxooENdBWZpxAgaBjQCeAjBwUlYQnwQqzANZeXqBvs1NxULLXjdG9XJ/rIBwK4zB4eAXlNFbGm8DpH80bhg5AyFx3QbfEFshCTLIJ5Zk0AmhEAuyDn+aU3jzXGUzSExx2mcJabx5TunjBMegRMzpCc7Bz48CaAf9NJxZkdVmvvHqKFfWIlowGinJ067+cGnUGO0N9OSuMRVA0/MVdKJIS6xYHgS/ggyZA8HX2HNGMWaxWUOCfupn06ylOBglF+ki6gDSnMND12Zq4TQKF5BIwF+QIBFRAztTwDUvK5TW1p97y3oyZnO73zgBB8o2afWEYsUZxNzfXcCn3pVDyjp0KkN/0B+Vu3JQQVBf8cPBQ5EPael2b8ZmmHni6u2svxwCHIE9Bs6EX4lUAtQY+pQm3dSlCfgcQwBsXQHaJZnmwKewkXjhFAG+wENIanZaPijbaxWIlyHxnQPG4RisayH+rlWktPQqIrtAI4x4PbdIjgkGV9gQRfhvfEgsFqhlhKOE4dtTDHu5WtIkKhYQjqBiSkTFCiFym2RgxZ8C+NgPjsjEX2HujJv0ODl6MBBku58xnKNxaGjTgTJBmOCKiQlDZlGgti+ZFPiEJKBmiicW9YhiTPqGMKlU5pVEzN0JoYt+TdQka/vBAMSIQtjyCrYGsFwQloVxLgKd/wVUTCFTTwCAlfWpAjFi41WIiqHmFhry4ucKhrPlrVvtykdh1x4kKuLpKQMweYYGxSnRknAvBHA21G+WFhXCGkx2j1dRaBqX5kdLU68WDkkEAVgHRJKdm3CIUTijSKsKWUEoMCOS8clqrqLiJwCU00syT52eklOJ3Uw1+UQjeLs2q7ysRRKc5g9xpL384DJ9oU6uDMWXNYarwqGYjIveYD9hOQwhQ4P9/PEOcKKZJkkARGs6Ntrk7Uy1YaKKJKLW9ZfDmZdPxAa8ij7JKeNdqcpIrriIVRttI8mtRIu59OXsCpCKf0TezNUpfZr0bhihdLhYYj4VIx2qffNF4UsX006XhCER+rjYoLkI6wTNLL2yoQKdhbTCXnq1Q1CqgLGKCZsfpoSXCwvq0hTyofkPC7DAWkn+xXoq+ZKtQ6YSNJBwKAooWlGKBb8fJPpNGU00Fx9BdbjRybv6T5QNXQwHx4Pb3XynFnQteeHOt7Y2GZ7l8ZYQGtAOBQ0qyT7zrIEir59SynoDZG99Z8tdpN8mXZEZFpZDckPDGgkI1FpPRYUjdYKC12J4bCSP1s5fX9PpDiVhO6NEloaHV8plsm4MTRz42m1o38lvXCJORBRgmhJH9+EbmP6yJ94q9vsrHxGkgd27J2AGdfadxxz6bJdnz8mWmBMGmoQbHEIFQQsYBFPK7rcydsT2GlUezv0ot7MPurZWOFDJDExPNTmSoGr2aQgMKrGSVXUTbCALJgQA5MMgHpUfiS1nPex2jTaeybRPmP0GGMxOtZ42ZSkLREBxb8lnw6TGEz2vh88323yNB9yIpoN09LosR1dFUS5+ynSTqdzhW8wWIWc7TWRsAyygWDTaJGOOWRYIwyRaMI8q4isyhESGUbhf/P8rdbdd9T3p2zoLPf5sPu2OHSQ5HSTNL5kNt3apPUqqXKZcN/EUyALeAe42mIZiIaYd63SYgisa8fypvAqhBbkfiqxelQsUUbJMtCeFd9DA0TQoLLbUin1faZ/GCrZ9ubnJ7xui5e/XglyIhXsYPEUhlGRAVYY0gHyUMm2EcJw73KYqWnjJvIyQoIksGhykQOl7wOIA08Jvm3agId7d9e9YjaMpZyCGppa5wDcdJiIAxJ+RCMvTDRK+1HxMAfviYJ6TkszwLGc2UZBlwXP3sJodYmoSkx2UaoTHcKJhSl/wDdt1GFm84l7t6Jq3CITn6/F+UyD/wcjhDTOO/tAgAiIg8Zd3GlUyn/LoV0PMDYicXZb/vKY690khQsOgm/YKRQZvTSTGmeKTVB4wOxYjRSZgLm+ZUJT/UchpHEcr8k0ISzxxYhegl7WlT+6J5AlVQstdwYLEAOMCsPT8YNqxzWAAV4cCjj21YH3nE4vLCGPtQ3VYRBpCP+fZkYNDA/ws/z0gGHkNsYOzq8iDpqAfTmL6WhJujub7ONHkjEod74ejNcBULQVkD1TDQ6DzOX/n4lBNCIrb9Ca3fA6lsvR4pkhblabscSPLMXKwVAw2mImga3x/OBCAJ/TmeYTF/pwpX8s+6BtlMobaHAbQqoo65GgfgdDyMvBdsxaOAzDiBFaOvcglAHL3yqZq0DY/izi4TRIqP83tWwQc0v5JawaZ1uVL9IkNMCDn+PLLl08EhKh0eWxQkUCbhO8IVR9KGj6PnPhVpPhURCJAC89lY7v54OClFLigiJrOON0BoIJOksZ0AlgEMBSUsEIRkdozh1NuRYl04rGj4Oti3a47IF0JjxVuX8BuRTEl0aqs7UFD/aAmhN4q2ex2WOGAi8REB3XAHOpyhpuQGGlvDMiYW/LWLKRxu5yNHWYGB05wIiLJYQOBAh90pZnBAKv/QtGFu/gZ2s1XNTqpyd0YB2x7ekNqsl8Cb6Cx5Tlz1lSSiREuQJa4pgrpgB11s/2iEAEVXwTsQKnph2ACE9ttCHOFG+2sQjoKQ+XI+89e7GsR7o03f2MhYD3dRts3B80C+A+4gMf6FYCqR7ytpLDEHn4ZBSX4h4FX3rMj8IMkmAv0we8DCdKw+yLU7/YVBusYsJtawgEPDLgb6aOW7BskjMGuOqTsRKOyE4TySr4dKXg6kIeKuP+ckAQcuJzn/yhCwU37bU70WKqzv1ZJACObO3YOsGB4mCbgBs/wPNc8L2sQji783JBKJWRgplteETpfBidltpLbAbRwgYfMqCGSGGbZdoGYvYxhmNjWcELI+Cky82mG2fAAzMB5CJNJ/TGzo3cTELUILSv6L1QbabDgwMagNFqAjTqDDYDrYrKt1kbf6+83TzlBt5XdGxHUDwA8m05FhtaQduSpOmMbWCXFnLxbQyC5BeIVTQGGeEVju7UY+d74X7YcJ3LqXSHy9nPyLCvWkgDwetWMAr/9saSGPBZJ/hMroJUc3L1He+Rc/n0K/1LeOcoBMU9/tRwOUaDytPMJ7HoE4TcTVdVEjS3pxlDBHMMhCy9BXdOSiQ4W8Tx/Ro9o6BU5HrjorxK2n8Sng/qIVG5IRowJl7yGvIo+i15jqQHo89Dng3qRTprEAr4lUbQWfQjo3jogMtz7Qz17+DbhNm+PQbb2c3yyF4gPVKK42pQU6ZGEtWBiploXkar+hsaxv43goZCJTih++GlOwVpVoa0mcpiQvr5stIX5gxjHiuPAx0aiUU/xmHP14duzMsB4q0gQya8l9Wx2fhQa4GlZ0WFFVe1hxIjBE20Gk6RGkgC8Euiivr1Rx0dvsG9cyTdTMwa9YKBY6ge/0Yznb7a57XcXmNYBn7HfXnBlFqfLrU6Tt8QSP3W6ot1yy8F3EvnTlxkoLbOIw6rGtpvbuwm9WO9+g4nyW6B5wuD1NzuvTPEsZilc6aIm1jaVXkpL5utZTZXsYY6EWF0NTSzM6GeSwGO2oPRELESPYEohE7pfzR2l1BIgzAoUvTm7gmgALldAhOO/ME9lZKcXbbIIBWYL48p5Lxj3edj0vYB1rws8xVTYRixjGWG6IIPSOynGIChsnBVeMH0YpaxUUZx1vMbC8A5k5EnOQbdvhwzd7G87e7is9IyckIj+1/9wHPM5CVyBgWg6Aq1xCUIORgoRm6mJ1ijDz2CIL7VzFF8ENkITsIj6BoQOCfETiuRaABQzUJiXcbAU7Uxbkw6g8xjmOEGhkgIdbL0l5MWG6sy77U4HitljtXPbiX/Ei0YnzGkgK2gctyab0KWCocu59XEdcm6PGVpiEU6Y8dvrmIBmJ2Bg0tTmOUDRbeioSYdFsjsXN4o0I2HpULBJhLBlZn52GyWBOLIk2ThA05vBmfSskLsVkm1FJ94uRVH+eEIwQmkBHt9woPILfcIXnBLQULNZoEBUwMVrm3orW4HzFsqwwxLvo27EaULgKE9YBwLsTZe3ktEYsYC9kRgSxRbhJwayWYW/odc9E/pMzK6YIzGfAxOlhE1MUBEgOJvCoJhGF1DD/+YEs2+A/swERbqkduhmfwvokNiIxEUVxeVsuBJEG/oC5dyLcgXuM4uma7gXN3SMSe6DwqMmQJJZLSe7Bcr8g/7CcW95HRLrYifRGDSiZlVoLwnZKzxmKBMl/xkNXnw0fqWA45lIBh8ihY/rrjXvpAu5WM/iMJsbnUlQ48slvUlrdeamRIuGdCsQqiRr4yOBpiU4jSQ+H2bgmpiqZck96j8nlUjQ4KAoMUyqlrJWlZtFzTGwlTVrwc0zYEw0qbHYiIFXzaT8Lm+jxrqR31F1ZTf0hE5XooYkF1UASF69KyYulOQ/Me6qaaG2nDJr1RdQAAoQGVIGkgjKETTDHb6KVdNGxblA+PBo7b4JuIsTbzQRyxKd3FKAdQPv/4/FEGxAj4TqcNCrKYuAZ5ITG4fmo2eIcbCRTwR+9SX4AbwQLbuBp+bERzk99IYZu0ZalITLAys5BjNURFj1PcduAw6pBIVBynMB2yLUImS4DOMNUNqSAQJhjLwrBEZPABrYdFRNJ7O/GluBo4rIM2AsV5u3XLC1Insw0DBeqGDKj7F3ARlGhftzi+2H33kJDGT3Qe1LSn8osyoAgBA0QLgoogLFqYVpC7CG0id2ivxNXV63QgwClYBxzGzUyhqnPvk9PDTFJ/39nKKPMIUpfhCDQyRggiLouKjhHh3TlIRTic2RCFTEjBMwE8gwDMWUwEcziJdW1S1G05B+U8ucdCDGuwOOySRx5iEy8D0gI5b1FK57PtkFMFCLxfiZeqLiXrSFQ8lVrmnA2GOwpDgy4M8ai5c1ycEFSAjXjOlSRgsimU0AEUPvAgqQmwjzqBeIiVXMGxFhsAGZfgYljHgYS0T1NkYYu/0vJe0Pble2jr/llth+3FDX9KcvgAUx67ACigRJsYtAjJxTjN0atav9EA7NSycoOHa5eXBbUlySEJuOLGwcDGFCLjCqY9OzgRxGyZD/fEmLGki76mBcMSwZtffJXSOfUQ8N17tjZb0rL7JcSM/PGdhR8cNjDPhyyVRZK376phA22tsOHV82QkSGnRlknFEdQSvVT1Wsi2kFEaLIotCgnFl9WCA7eQ3kcgGNPLRsY5rTIfsHcQ/P+ZeQ6mhx7nH6U+NDkq8EF16br4Gg7UEFe5yEiJOvI/7C/RICYPKjCHvDflZIciyGgLRFoFazpbY1O1jIU3kFaNVFqvVkyRKcD2dYIFVtIZM0WJ9/w4hzKgj/8WrNAQU8sxocgWNeirMdOLawYd4/dzTpIAnoxC9QcqouDSKbDzydHg1GzDPGYx88jnm19AOKmyJsPSWHpb5r1B8oKSm8N19FKSJafpC9kQiZurRcKmHociDIZF8fdVHR1EV5EKcp4BdXPwGunuPI4YWUIUD4M6XCkYC1RyhZa28BwAk8XmJUcwaCXWhJQ0K+2nyTWSHdFdEQDIb04kKElKv+YzmCwQ2MMsAyG9YNxDSltK4mvA+X2YnrTxtQhotYRwDUla4FLUxS1VB1gSAWrEkwHlw6zW5b0F8Sz0c5B+k6znWfy0c6n3zU2P5BEKRGuHGaRaMA8+zs7fC6TOFHlsmsi36c8FKChSmAzTqSV1/0sqouZWSJEUioRuak/vbTts6Fu1NQHFRWx1OScJi73Go8/wsFXMh/pR3o+RB00cQRgmHeRuiNMQokJOu7g5HPGCIkHXhIA99ZeRyfac4pJIwEUYBEdtAP9jQoYacStEfba3aPLUGNRGn7dX6tvuw7M0R3qOkdhTFF0yrJHS/C6+rG8vz+33WueMYGP7qYQFQJju1XpSRRoPTZr7GGWEnAikrL6d0nVcxQ+BltRDWntQUVD2aLxCMziWt5LquLUNL2kFDbP2B1DsFiuOzy7EdXQSb+ydn5iCeKEdd5C9rqKZILCIyXGsnzJugvrSNcpytDTK09ys1yvqotAYrSFLO0SPWdqJsIzj7AhxYKw2Nl1kvnav4TCBwJV6atrxfBh4QLk8jP2sSnEiGH3slZx0OU5m469Asn8aoNlIspqd1YA7Ej2aU4XfErUL3zauvoNv/cldQZDEsZp2l2J8ZxqlTa/XGr34wuvfbmXanMmDRjxl62Pw1OymwBucskUDrmslkOjohga2CDTKgYPsHroofZm3QWL4aRLMgpLwTtBo63Px0mElKpTSS4OK8BMcq6fRJT419Sn7Jnc+llgUTcds0N4bHtcIAPBSyTpXxw772xuk7HvyVbuk1cYuqzkvz3JbEZqyE24yNNlYIwhDCH3/UxtMmy1OGHVew4ChKZuNLNQ1SG0scZTsrNkUzBwHCM41dOJtNZWVkqYzoMyCwKJ/MfwqaUqvO9QKY8BClk6LG2Mz5Z32y4/1xWb6agxek0tDqDPyNIDYUv931jdX+wDUWaIQU1zmS/6k24CMPOmZmDtUUPrNohsTWbeAithTmukKEqxUBwUB3RAN5dBmrRCKMpt2gxrjA7FNs0XxsrVnzdZqAoSpaluZqmd4ipkoriGn8Wm8AgLCvBBw0CGBKQm2coFo7epF36Zkp9CkmjV+j6XJhOBilwebMs8ylVgpZxztrOUUVu6pJ2L9Tgc1cJJ3PCLyK9dQuzGsKS3NXNY9meunbwaGjaVIZKUAMPTfIZRLNb3jOceM0MppRMZJNSM/cEjP20jBJsPsIEEiipbKmTarRHA7YwmznrSH4ExxJWaBXPgirCLoJMlhADPzWSzy1gLAXTS+Js2na9zh4tRPLvsgy61l3PXAZveCTdPhw3isvJwAlVMUeOC1l2pBT/iHkG3xrZWF72VjoS4CjIHHo4uCSXeBq4RTb/YFvNVzCpUwRg55KIAqGgWlMQU00qhJQwC+b5MaxKsWAYBiXqiZwQk1rY+GIU8OZgfM60EXkYNciW3dzTqCIVHCVTeEgF+SmBTz2E3Gg1tHr8li3NaK42UwF2b+WXHM9YNyDvJ2HB0XUqq6XGd6CBaJs9C7u1pyKzWNed95a6ZqJ8RV36oSeo52ZIg0KyWGaJdU+qqbAuKwDkzAqGqdiO2YXwMgtcNb2vNRIq8XizO7s0K/4d57fLb9wl+Wj1hbcN4OxbkUi0ofuGs61IjdASwM7JucaIinCXNzDELdgoFVfiefCFPh4vnzm8h31auT83+KlNGLonx7tkiyU2vqZwT1ghXGBEnznwdMGjiRFIsIsYHNiNr0LajA9rRuoYuFUmwZh6oS4LYFb4/pirQqZSioyKaR1I2HThUpZtKg0Bi2ZrrjbQd4Hk+ivlkdEScmGElWuc5OqjSz82WQ/MPz4hzcXdP1xGSPelNtYTMT7zR6n79AbNNloSGgGXSwBgquY7Zd5jDKJZJlPU1vQDoWsmmUBVaBvm3rTzMEVzkO4T2pccEnHQpJRd1/Sy0Vysepy19qhwYTGYH1oKjWiGlu1ggZaX6PL7MVJRcpNeUc4U/ipuPOhzs0QT4RKvMqnDW6PzQnrpi4ocmMPxJihHRbUKDCQCwjHEq0xx5vgMp8e64KoF2NWxw3HdhnbUDtCYF2+g2JH6VYgbDvY/HJk6IwBPkJHSi0Xca+/e8I0pqFbL4FPB31jNTvapUFyWqwWdW8+jA2VSmqm8FJbW0aiLrSRAr/Mz+F9ZZTxxBxNAAieasgnCL6RAW0eyhy37AIMb/WXy7PisvGOfD/ImCjDeQ3v0lqQdqQkJ2djagGxzUbbVBaVqfjRg/oY7tlNa1gH5Qt8wI40L3BKPUgIYHPbA8E1zKfCwxbS6Nou/fy6tUw6HXCxbYWN/IEqjoJb1DCHe1Ul0TcyEqvA6kseDt2Je58n37UPBWXDC/73DCQQpgUk7K2J6xMmQgYFa6dOVAuhfvEFDtu5RH+mFjZxhTbggee1Oqmy/t81hgshhtBIaI1UJIoJQ4uUejaqTf24w3QaV7VlS0XHzmSceOqO/Y5ScySbBGn0S29Lp2FVpkJDNhBVwKldqspNBJfUuysDpdACb8ApHm/qkdpDQoD8qI1Olnp5LS2RvMkNGHnpraEti+iSXTDZSqU7byyQTw36qbCTWKNFrqGMqCKITBXoXTw3OUDPDWSJxaD9K8hxBBzS26YCPOYmiHjoBCKh9pYTESg4Vjmr82E3aX20TxfLWcY7KPxTSkefVHZtxOkq8DMF2UjEcLkUByc7wXtUoMaC4/r5VvemBZg6npWsZK56BpU2Sp3H0kko+mlwX/W6sRya4EPjEpEY7k3SK2hbDYBrq438uNVelDhUBkHabtJ+NMN4novEzAMpcY5xkguH9DSSM+nJdsUalrb3urG1ocfle0SzEPoYAAl1LOhsBXAYvjrwmGFwlfrimW//uTGvJW/8h/85mNW9NWxgPgaUCQmQ70MshluLz7HnoU8dSUCCwI0efgEJoGXCbhuR8anRAKnzPV6aRIQI4IzFcG7F75Si4PXfmA1vLoXl0FcCNxPHpamuvXpGDGeZBHb4qHlh0LfxT61vF6ifixdu051FQrrwtmLFYg6cm+Zmqxyf0CwEMdhg9bK0hEPCG/X/dM1S0TLB02VXZp7WdDTDaGouHQsUEpQh2KWuUFVoPvgwfypxeozqIE6GiNXWWWPsPVCjK1IcIBVMgYd6F3Uojm+EChHpslNvsB/RpRIArk/frXP0fjG/7PF50Q3gARoVdTmVC74KPmbLzRss/aVn0rkXcxsqCRrexBI+G7gQnQV+FibaJ3vwsWlG8wBHXGyCxpfsUArcJ0P1vPX8l6hyamrZUzeFuRe2oR+rziiNabKMElfn1uugGCF2qf5LEY0NjMitYxA2+8H9qoHrkBcmSHQyWHYw0rHggToZp432l2jLNS6E16KBBs/gvpTHcLrVIXlqUjveysWDF4i+sM0vzHHCzs1a2pF+G5qS2uhNkzNmd+kAkTYawFiZ5hofvzEkVzxvB+bYlgP6xkcBU9fIn4RBF2UBfNz5i5SqGvKQh3a+bKyMdBelBIxdJMYS0KK/wWxpBQ4sJrWLYMNQoK+0Yhwoc7FP9ZDqILhNpeh01n4AHO+O/6tlwIw/oeGhId7n+fL9uYnaN9VwitM3s6fJnkyDXM9hPJl8SB7zfP2+QKo3MIikj2ojPGAWMxvNZnz9f5n93GqdbkIXpagkCUx6yVRHwntpkfYH5Maxe9mnvhO8TFGexlIMrm4h3ssFycwxzeAvTpAGW4+7yCVkiqpOw/N92Rj4BzTdDBnqZfk5IaGeipz/U934DAps/oWyYfUd20jBxOS7gqedS54KGS2BwN50qxfujosNQ1EvkXjyGAESVoIOuHImYx9x8aYsXJ/N1TYcUOYAzhHHhC256Rb8ZKNTzbBiWLe6TiuG5BSdWxOYNZrsImlVAOYYLHm2zhY0U8HLVboy2QEdRx4zvIIAqqpmZ5odZQNRFmDQPaokrS42VJmdGJGWMyEw/gOjeBApUsa+rwA/LeUBDAlm+wK3NzbL9wgKheUUQlOnyLeVIMim6asPH4obyiBLQalybA0RwhUboqOzCUyylzpQ1fETsAMOiFqrhJoqcXmUTmoQN2cWJ8DqJh/JMJ39dIBhPUySQncRyLEZYmMljDSOFezrJBhpyTQoXQLpdXyA301wYmuEWA+fLNykQEJni4uOkBZiYgUi0AcBGPJYLEYypwkImHrQ8jAuUmnIW8cJq6ImyqMJMoIUvckikibPBAPtOGd5GznSgLo57TBqpjzk7vBi1iGk1L9T1NCe+dCQfkSCUUo5hlJ3ISj6pJOwE1fJ6yJrZmNsqrZSqp1VSIc0K7onFD+g+nN8HzSP+nHy6icon5YQymDFyoVcRxwfDvxjovYZP8qJPjewXEC+VapzsPPAc4DVKVpMw4p7JN6n5EReBTet5W6eqEGy+thHH0DRX26170CFEhYfAh+/VOfnBCug7ek/Tus+AUElJliE1SHqIh2ZOdyB10Cr45t7MN6OBYUpgVcjEwUZQlWxwkUwUjNeVNZUqULmd05ewKL6Tj+x8LyWNjqxe7WrKBOjsuryvxqBag2MezV0floU8DwSiYTRi1kAjVvrXOAgG1sjjQOpfayenVcLy1a8sT4Zfxl9Sxg5LjVKBa+gx7GNlmZJuTh4L2JEI3cF5kudF73oucYQqFuBAtNuT3YxGBiq64zw7zlZs7INqxtP4f4IZ+wumPJCs7SqzQaCS8+cqmFBHQKKqE0f8Xgq9ifnVQQEuQRimapSiSw9CQEQXiVpFxGLWmor69ja5Hl/8WG1H5rdKzKeqlXmNE9QCRdxZL4CIGIXuBsxHNZTi7mz4Q6mcSTFmS63ivoDhu+018bRHzM4LArrvxPdGSJ9BNiEtFfgjYP7HSsvSQPthtlicSLAN5jpC1bH0UUKVInFvps4hajReVh6b5bRVC29xTTjrEQlhubWG5gRNgJx2XEFHc9QfYiY5YMy5EiFGvG5FbjRVSTNZsdGo5BWMqwAfK+827pD8pu6g19Hui2cSCQbEgoYzPkYCs4RlUKHaJrDcZHuhQmVkpO5B0mjz0VtyG0rmB9Z/mbfXxVFIsO+F0i2IdaheqZDOZkYtdTaez5AnJ/lOveKln3lP6CxDJtFSaOhRHlye/hSkAS+D3g7q+YQWO1cPAT+VCBmp4I2IZ0ikaYMD8yTo9bBD+dcGgM0bmJD//vDkMFUu4327ZS35LQ54AYVeprTZHKplKSX1loak0sz/SXcnHJ47Ul+R7ZuYOTBaownISnj9o9xxQwXWM3eDdcGOFQSdsWo9LHM0CTMqAaVZODkn0kf0g5V0QwCgp6MRAb3QPS/H96AXfd9IzAVbBKpB7EeY/aAcTOUdO0OYUGDqli72AJvV6NyfdqC1t6WIzUDDqKJyfxA9f6vUV6wQLpDp8SfnRRmh418d3S7JLdRoOl2Ia0U1RPFcnVHrmE2JtkMWDRWi6xFCSJk4LQV8Tl/bMHcyGy9xEGKs2ngUMa4zobj9N1LuSM+Qu/Sx+IuWbpxnghxAYS2Jc9nBGBhy2itQ6qqj6QDHGwMUchEtSMsZso0ZAjLS2NW8CuIYneeJUV7M0IQ0kJngjM3NTZlnkqh+ITD7qxOQpsw+KCZDgfEVJ2P+tLLSaty4cp7zeW8W4vNbQPOKqbqBk+0ydRr024oEzJUyk6SinGhKmutubdU2QYglhi/G3ea2+fynZvwFZKhNeZnUnztm1pYoQMNBirwgIvUZPj8MZPj/IAoyLhmLzF8IJjFPMPJ9IaIqbBEKMAmqU1AX4tCmjqpgI5bBjP/w2PDtGtXBOCt7SKrYQwtC7kWUGqQCXx6SQr1cavqLT91hjV+DgMZmgSpKjw/y+IqLA+8owDkMaEtChnh5JYKASA0Cg7PIVJ06HRZScGzYNKPicuhQkCEDD4YN0SDswxDL9AzREzpYtxxmg1FSxp9FuFO+jElvE2PHDrlXEoGViq90gZnyaEDQHpWAex9RYYyTlI/PHuv0qvHUFX8LM0WB8KlaaQES2ByE8Pt/I7ai86Bq0F9HL5f44SVRwc/fAYhTsHJGimo1WJ5h6C8bn5EJODqearm0taVs3kRbmseKhcszFuBZ3N7hWpQmGNM9U4NFjuEkF6u8zUolj0s/J0nMo0JceQ4Y0fTVgeu8Gkh9l4b3gtVsE4ljsNgeCKeaOoG1mrDB3H4bMPOLTd4W53AVB7FYOBWbw+3sFda0YqEhvKUyAPnkGJJ2qDRWtoR/o4Ct1Zouf2sanpun1cc3p5od3wB9DAyIm1hB4EoRZDJ8Bq1JAtEgc+yXK0kUM78HFwiZTdFDBOALJH0TpJKiJtkfSqX5JcCsSZWLpZQdgcPBLhPDS1YvfRFEhTmZq1uQEwgQRmNFewSGGGMxBUpxCbmeK1mFKK3sauY9+mIbV+E2Fx2tJdR0sshmFBav18dhvwXa2PPpUulifRuX/kerkEOErfQA+T2bcVrQzuFHidg2Ve+4997FP+SVf3F9og9RfNTTZDQrKrMTuwIwjI76+vhLrfAHzHGgBgcFpknWYkqdMNiAnNFI1O9twEFnYZhjIWfTLUxyaNHma7gnhMlyqHFFKpQ8CTLpDg8k83NMKajepYOPyzC9TpdGuj40BfcnF1+YKDz2thTWqObw5CgzolOCnxk3T1iFsAQ0phkJbOZAuRSvgDfwKN2nE76ygYGpv/v5SLbWC/tXI2IkQgl1ZRjXSaK7gaAjYZvUBY8XMOX8U2HF5C2XH4FLmeC6g89SxwJn14GRKIylVxrA+t/ilBc6IWnRHtgJVrFNs1saBRjz2T2rGAsns+FLz7+c6i6upvGMAHlO6cLjuOME64D6FmrYBrK0rB8As3aOvQihbPY8WVS/nBW0n8RovboV62B60m0JtrcOfyUoj/243zmHhnv9PH+mPmwR8IEr9l4j5BLty48Gds7/tvYSJS2PQtuUlIRWzh4CfxBBw/qO11ziRJrBkR+51gK2Yrr8Iyr/4QTQ3SA2M84KxnIIxs5GnSxLHr07QFhE3zj9VWG3DYRGGjV9s5apH6yhRzL1eWRvBKtJqaqGeoBWVvKGUUMhphyFWMZRrMZmRc7cTggQX1Jep2UF6hBZgsTDlMCZ/wXPu6JjJ2SQO8xqxmbVCFwEe+E7j4EgQgWX8Y/C3oJ4XAPrS1MzTxXUnPqaAuvOUx/1NB5gGoJoFBlcBWk/maSSdaoKtJPpmp59exHV8KdI6MFLcOIBlU4jlVLh6IlJlzJXHdtairvKmFOk6Tjgx39OEkW4PbQn4WTcY63AskJULpESApWrGL5NQsQMUARxdUqDn/6peAmEzkGXBRd+vBZF4OS6VOcZzhtqNlqRYHqvY1CHnVlVLKYFfo7TKDNQiSsr+ofpv1M1mz7BRU/ZUih+b8WAWSFb73Hvr501SUYOoA3LxouqpyUFiMgb0zWGrGanOHzpLl+8fU6jJVAhyCMmwcbY3EAGgZEa3bGgYlNr81qobtYM94xmY8eSApISo1YPO78jXtFchhwLmP8ezLjoPeH+fCQoJPpOhuHijXvG7B4ON4uxneju7A5blJs1LJ8oNglbrsOPZ2bIjQ5ifUS6j1zKvEoOAjb9Cmdn53Z8cA4mcowmB3xsDdAHtI030azPZR9jU48HkaTYVaKHwpBhQ7VI36FDHw2eVmCWJ6TN5EUCwgzDsAJrQhiXH+9qXE+IkSrkxocjReYgyOvQqwDqJbQAwNQKWHi4CX/KK6wceY3KSPScwlIJyggTMLIITJmSA8D4+HMEnCFSKI5Hh4LAipKUAFkQh/EXVoo4agiNcA8Fdc6Cqw9Fs8iBU0UCt8ys0/EwroACcRSfJk+HHaAGjv4e++V0k9Xpil8BuwMQULpq6H3J7bmCtLO178mY9Fqx78n/dC3rOmp4k4CqHqmZcNbUl2QTABRffE2Grpf+t82ssX3ntUgOSTb1hTA6wbM3WvPUp4sHKCmb/AKUB5mbmVZuEDXJlIgjXI1S3JPktUwcqxzhxtjQJ17bDPlpxwNo3nynJ0ANSGT+AkNIRiECRQSQQlF+QqxDGqCFbmsM71n1Z1ZpboSJVhOHUQ0VwZXuQeAg1wCS/gOMhcqOsJcALRVAWa/v8MEQWA1e7ghHSR+zs/U3Wgg0fftpR4XBsHA8xb2RQPcoM+qSADGLAqwpD3iikb8wKH+54wrsu7R6TvIBQLnt8haW1b6tae9a5CYjAOgce5AQs4eyz3bIroxIHArgs24552HkYXgoR5vS807prTHilX+6Xf4si/0sADcyjucNhfhVk9Qh3Z4pVSQo+DlODPGEX7kcNqXcs24GM14WZYtuAvW67yxGFgGWm05RWP7aA4Gbh6FwUR+lx5NQoJKwyJl+ZD6++KdFfyt2Eg/6iJO3zhngkDq0FlxNWeECXGF4qiZ6YX7cCkhrtTQFZvD7DX4HHQ6Lg62O3C9rZYBJ4ChYlsEQaPLSeBlSyPfZ3apkxO0bkkjo4MRTQOKnJt46kb7gySs450c2MEiKi7ogtjSxDUZCTbdK+4oS0EaLFGI6zvxAmYdJAiRxE8xLfKQIgsQJHfxbjxjfBH2km4gi3g8K2r79OFUPeFgW77O+Cv07yu8PH0ULpLY6RubTpWEnnQOpXZ7qFsGCiC7a8LTySK56DTYcDXjltOA1QpgJI4Bf7QNkEownjDNlBjoPbd7RUMt+FTXQ3catxeKs+xFCpCHdg5ZbGURmmwPiyNF50xev3LBKyotHLLy4QoPqxFIjPeFI5JXKkXp2zlEyi9FwINe/3p2q/uUNe9AvwLbJOnqX8W8MiGJcFlCj6QUZg175w2N0mZkjmLELBSlWOJ4jiIIZRLi6VQPTkAs0DNyTlGQRrkkbictJTLzrcOswmZVcQLvj1DYQKtC3UCFR+Qw7a+2mrjU5/Qb+kpBRAEubvzxsSFFn4aAsGh1zHGeS6hfyP+Ph1/RiDAyvx18iJgz/AMqGs2VQIB6fElEmusLFWgb3gyKM5DNeRb2Qxml7O/YDuirs4BvH73+Doe8fxH7HErxjxMzj44hjUJZBghHQYXxn/3jdPsGwRWB33YOezYJMgKx7rff7xOr5zssSyxLcC+wS8szO+N6jEDn8A2Y47nc5koSx6oAAAEDAWAAIwstM+6bFozBhd3N1nA4zIKhTGgIrP0RHRlEEaqIcSAiROwCFg2ROdxFWP22fjffYHbIvuVRNVRm4erQCVU8+V3JUhGqXUYHMd2BFaHkWFvDWSinIvIxFoijuCQiLkdOsRUc1m2mof5J0skueB5NuEHGX7TpxeUnAVq0H63w0RNlo844nxaKMuSsbTHjYhwIr6w9hWJxYeDVR+yVmCk3Zkkiu0HtOHfLHcu1T+B25ZveQYa/IaHcv4unWSnykZ6iTH2HMkA80ByTvbPnwIcZeOair+82Cu/AecZi+RASa9XDuOCFKE7LDvbYRjEbJDS+JBQBsqBL655jDyQSFtya+cxMCcqpgSeI4iq/4rEWi5PGPPnbkJNd5NBFBQFoYKlCUiD3vu0rHEhNlzpkJEieEn5G8mHVbpMCK16PbqnrOE+Vq8vnAXEFAQEjli1RAU3OjwEHansuUVHPVgUuhw24Rcbt6GqIOjmTbF1gKDggBRbkOQ/mfSLHHdljGqG8O23S31QyEJ25Alfb4xc7pgRuANNlXRWeUIEuQuILMujzFgtbxCMcPLnDKwnpkPqUbybSRVkKkhjqQ3wf8QNzMzDjkhbZpOUtS12SBphkCOUZdrYDhY1fmhXKmgNbFlSPTPxjS3zs5ffStkBq/0rb51Lr05ewp2Vc6pJNy3ZMmXyQyt9x8ngxaPLB2wyThFAUkUjstLZiuevGA37ppTOA5WjtdRTa5ORf1Q4JoAjYWlvxTJc2c2LdPqroCMaTS36LQK9AwVtUADdhEKgJyHzRc6AB3eBBsde8P0wSBImYAm3L4/OBiiHouGobY9ftzqAvSTHPaIcEroMB2YtXC7tRgBjhkl/mudoI9K2f3GzUCZDv5FkULicuGwvnpEQA6CRrjaTBjGBv4mLGC9JuLxAnw3nuch5wByL+okGYQwVgpnD8t63gEnsMdqM5loDWtEFtg6ccHxG+AP9ELcWCZ141LekAZW4c4wCzIwpAoDf78+WGXVv5I0r5+Tok+jcroKBWmRCXZab+idYe8W/DLYTq0u8zSCl90f7h7VEiqY/TUs/5kc39tQD2oFHzAFyB4hPfAhS4z5Go/q1HBCki2Nz8LomLPJW3sHz2tnwg1rrCv0huSpqvsNADNbeF+QEOBEFNyiIdSlWBI8wSzDgA/X3/M/idQunQy5Fz0jXTddiy+IrCF/FhJGtUHYAAINIJE0Ev9SBMiGVUQQGo8YXtMIls4O5lJQYcdvcBBLnSAqxyV8YiFW/oT5ZkBqB3N6TcBVaCT8tIT6PS4UZQkUybYGRC6mlx2ofCaR31oTwZKKKoiDIyfWLWsWN3DwwomfwQYIXkiIr3Dz411nvYcRxdIxlDPJg4FY+TLQfbujMTe9MZm7jZYTeVLAtcg0e+KJbMCp4lE9xpSYIKImj+QZU2Gi9VK9QvAXHhnRaRMKLghedVoplZodpg5O+bDqOBMwfxFqUA2uRpvRGdQoY66kXpzqZFquYoHQMOiN4rH52HPu8xr0U2p+xtUVbqR7vqVdkKMR8kDMZkcfiXybxXtJkNq5jKbdMZ0jM1uNWUU/Mw70yUyARMeUKWsWDFtxprn59XBjh3coJCySmxcvxEga1a+pmGbC/nJdFGK7nSnd0qcn3ARJWSGBDeYDzoRa4H6cKU8R0DVOcUKXYAmO8YsnhwcddEDoeeTveS700aFb7aYyVH7AVR7ER8TT6ZLR+ZmArHFN07pkOhhVcJfCpm4btY+oWEq0GJUdpO6FpscLsxIBfJvw9Up2hlnO0XzRafAIvNJWWHhPuoeZKabxUW9XfXD4bFGzzT0kf3jdSJgfF2JjehNtsHwWUcdDFrnH8AWNkm5hbvCv9J12yC0Ff1tAKnp27Y2aRmCtuPpX7wMi2C7zcfyFH7VpuoG3/0c+mKH3H5e8RnBgOottxW7APxU9vYdZmcUnLIPn0oGGe6eCy6kJChJNCfY3JrOwAUM1AUFGrAqoOhDh2M3iLdh7QfMvhjE2mMST9ZMHOssQOm+8aahOO631zNDuGoE8doyawjN2emgIAU0tuG19gtoHp0AY8PvJdbiwvAu1rdCSR3HtemFPD8FSbJDvCMM6UC6daxvDSHPhcPa6pabYByKASGgnMF84Ofl4UGIQ9od7EXNFlHswIJH3NK6wn7zQfx8c76/xHZQCKne/BpXbsHaeNurZ7hH3gFNERR2W2OLS1exTAiMYDjsfRlXbeTH39NTaxsxIGQBSuxEF4WzMa4RMm4NhbRSLRlVsQqkMjrZpARiATrQDk5vKsbtcMGFuaii1L2ct2YOBLmwPJzhNgSCX2wwxx4UJB85hg4TPOnpFwze5ahIqgOly47/4a+HJM+yvJM4k46L+ydIKxOUt4yMTeN42KhHHWqw+oqJ4aHDaTSxO8WI2BWDIP9Wsk0PFJ1ekREzaYiCyhsR/JV2ZPD3KQ2kEpIEnQ7K+WdgBRFORbDMjZKE3DVqRRnQDBgGE+RDHxpeCJ5gaZxFWHrTIVDDnE7458x44MIebmTc2KYd/YFQYei5yAUDtBjAcWhFKhp7oU1nEuSupo+RsPKEigjsplQISE9YL970qKZ3cx44+KD+4MrNBePzIccXdbp3zOAP/h0CiH+cFsMTXIjt4aYyAEdyo9RqgkCHwSA1ylkQyBtw8REreEZ2eEeQAzd/gCHhitAQBoKRQFNqAGStfcKS5X8fRwRACafARQ/cDA+Hlsf3CLV46qnS0FfY6Q62PD8ImGxSOMH0K8ZjlD2oATqzg1bL+rMXWP2sBweIsDQqMtn5pAyQsmoIfBsCltMGwO8EslfJxp2r293wGTtowISoPudA6/HkGZjNn2WmJdEmXRTN3eIEKWkUgAAigDHo+AGGLRFOHq2DxUzQFbwJKMvzNDz6ceBOqmCJQlt8ghGcLCBpTS9QjzKuRLw5BRUGnkYCxyBGnHwRooGClxC8x9s/2/EgwDfYThEANIegpXOd/RP+IdAAjbDHXwwk/X26UB5aVme9elRAsEBXXSGPLXRjP66m1gC0gfybBLadYYkRyjkClWirIIxJPnKt7GuSDKo+txrGzwcsjlheyxe8pIYhLil75k66seUUvnwhslTgbM+T8J6MeOEVYj4KPmOW5TOqM0KeF9FqWYoMHMDAAfTjzzL9OXRXs/6a8PHGRkeZDgoA6QD+4f73h0A3zPdjYpt3U8o3YKqDnswZtwIHCdhN3MT9sWF+egSE5xigTnnoHcqvicEXH17hrJCNcDcdmcmVFmKb17MVIbiWi94CJh+8oVctleBsFkwG8b0ilFwACIC01AeOidRNWl4tyLxKAXD/DYr1GmwSqWwRoBbmLICmNCQGw8Qmrpz5pAlbjwpAA8h5zifwhFRAgCaQto/VQSoyC0GoZluoUaM0vx1oEKvlcyYamIlEb107iSZcAHv4m/Z0VOxEF88PqL2HaQl2cUQGdoZwO1MeHcWOsGB9WwmNzmYzWcmD/2JloqcW/yzP63cnyAGwlCZKDSNBH0tJRCj4btLmPZIASxHuIakxqqM+cWI6JSy2cwCDeqDYEqLDALAxNZRgssFL80NKj0qTt42ZvnE2p06UwMAKKJBaZzAj90FE2Cu4b5gb8AVk2DNo386nzI5AWMGtxfGIXXnHk23Qm0B9oTU7AJg4JBA6W2DTw+JL5BxGYTHWqi+gOJZYUgPxgqwfst83gYB041CiBmo0KkhkVMEmmYnyR5yFVsNiMe4fesIHY9sJoOdzwJtmhGZ2g4UE1+bMDnVPUPspIEJ/ZEkCsFRQ2V5vJOAyKCsoxAVkxVg6MFKJbh6k7EPc6DXYcQ2Wr8Qg32aGcsSwpsEmw5N8AMdrbEoJt4bU2Mms4hutkXBMYhDe0MSYxDpwtSKFGXasnOKbIhbuFVBkRcT9A+xJ0Chrk4TmOKattVg4LLNNu5p7mgTz8X25gB0xs8ZqPoFT/uyRvp4rv7x3iAl3QNOy0D+jw8CwSqbTEYw7/LrS2WzJFWOLxx/Fwe01jozmXytm8MncBarg0Y7VZXNWL1UHOcB98rCfFKCX5qUMO2NpNW6T06PrremcG+M9zyKwIPPDxxJgMVlFcGwJY7MDBVtEmyFj5B9vCo1YyHTNwUQLvkyRjUcCVUuFzEcodkgFIWPJUUWhwvC7hsdGu4QRuFK2cFXkdiGKM9Ab7Zwn4aizFpdTz83UBVRo72ePqOiD+jrzxwfxPAGDiz/RHH2KVMDjn4hnm+VDc4orrwJsF0X6r5MEzuVkVSnCL1lN0Zq1tZeZXKYsYUbWaE/GRK1V2DBxRJ0nS4q/pmgMEiqpn6Vscp4Rk+KKVh1ZxUb01RsFktwDYCVzhZcqx4o429Pp6U3q56iKzXwGTk0gx/uQYCQFpvgIC/XUUMeHWeZTlKbgzO3ActJc3wmhElaDa47+DzxYGyDMOHYdTBhsZ0pE5cDLzAPqJTahYwAQkcpYKl5dBBkrjDl42YpFCKnNjlJo7GgwUzhePQADGyMtmIbhugpcoIsaADaIED9mhXQ6T4hQ7gJZ58Dbd8K3E5NGMOLpKQDvhfAtFK8BA0wuDkhaachxw3sGn4CS4DtavzI0YiCBe5DZkUXzII1MY7MigwtrctFHkojYGmTMdCoSLRQsNaYfqGcj2bJaCrjQcCnwoh6TBABKddVLokVAQOjamsMPdq1hrRuauH3kEVBEae0UKmJntGjS1Au6UOQvzTbmG38qHuGM1MuicUvMBkJJ9eHOuJ5FdL5h8qTxkXDlJigApS5dasqA86IVHPRZtyNdIAywJS66encRGmcSSpMdEw4xG6VNpUhVUaTkKW3oQOOolCtSOM2HmZ2cQxQBENvaZ8AhthMUu86D075ZMAB6LHoQYKG0d2kJnmcBwHapEV8pWvbHLtJQqmpVGLMMKksJcSsZ9KnkxE75gGKiceaeilfiHFwXRm7epH7xvvSdlwqzWRdtvlAPKvdytdZOFMNDl9Qg2jsjqbwOkZki3tqBrtHmZI10ZnRSemnqpsEOxTXviVdCZ7orWcakBACns5WmKWKCLATZHK7QzBZM4UE2fvUPHq8J+85W12P+SGibKzsauNpPBwwjGAl8YmzomJb5XVbC3X+kqM7sO0e5QWVBCEcQ6Qo4wBUmMAcRw2mWQKGvFXPCSSzUd8zz92Njg0dSEz/VWnobooNkCXcBGRNqneHcRmFWoQAt//oDKSKDlmzQ6R6r0FPZxLaS66mKUkGnksFof8lzvg6N7EqUEcna2maRyEVrFKNbdpyFhzboaiUzD1/TO+H1nfs6Jy/bRGGClq2wS8vZ/TjMuAIXdUyb01vRtQD9KpdbE6/aNiOiJoditf667VXY6/6xVSb4sJOfgN9KEiydJQH3t/2MhsbGqGteWFFiZ4p2E0hqX0bQ9vC5Z4hQzmx16OWH8VrABJP0PFKwvPwpuKsSJAYWtKLg3xOyVEbIBh4cR5gjmVfQ7tnDu7ql6IoMCV0jD14mXYK+bgSSNVZ+KVXaAaQMKumcLIQVf4hWRY/KVYzBc6zBuh4JkkIjHOHyTeNNFmRBC7BHe9IskzwdgPrgmbhfqWsyGCHC5kjgEbsGYrRUMbNnVpPA6uZpWI3ckmyFEvMgd3dP2T6TtX43AAUVgQvuAVGoSxpzUhqBEWbNQugPhblkDGJFWlDZcNJcTgMB5guXtJB8S1FQbTVGPKqA/7bNQIkYg4+QOlgEiuXV/YK76t+/TvrynvdlkPFdiQVc9tntzH0MJto+VIDYZ5pbjO051uLUffN/mw1XDxcr5a7Ud2wHh9A0IdEMwh1UI7Cam0y21KA52JGs64CwiToJsegIRhBQastowE/7OHDZNXi5O0oxymjBkoOU6OJL5fisgBiC7xSpjKrcqujmA9es3Y3eYUmQZDnlQBRhJcaWPTTKuX5JTCq5DwuMOykK9yHeWr6QFL61+lpZzPLq/fMqtz4tpLvAQAsf4H+T6hbDAmBkNBDJHq0EGTP1D2vihBnGgocA0MEf3Rb/q1qbtsVQIP/pAnNYF0pkWS2PIePBhHTJqglN8LE1viaiOM36DKkGVClfc64SwdmwpICCtqPfmK9UHBW2MZWjhkuWimB8BgcBt95yCtPV6ckl61NIz7gFlXQIBgwiE0CYbIv60c8Xfwlboq4Ah/i8QIn2cmyIlrRz3XwL/qmb1iNHls8KZ4kKNwReQ4UJDVkcGFvi5LGo9qHmeeFPd1CQ6hRfdoka1Bwb5z3LIYqvqGTBqyYOlWPdF5f5fNvRIasDFayYe5oSMTIQaIDJi5UmDNKCwmbi0ouptDZ52WiCRCPo/QRGhxP2hUj7Adu0ue1heuYM1J2tUEnzXwxv6AlIslwSpF7NHUOUqLNpRqQTlB5WtCihQ47RwMpzI7yshGDaa8JICcIxXgfA2qbWdksmic4rDS91OcpHHGXG1kwcow7y1IhSGNYCYGARfmM6V9wHdnQpeWtDvlCMkJ4QlqapaX2SGP8jgOD3KoQkW6Gn8SMGEh7OuLgBSFrn/OAdN47zpvFWcYISHBuzK4sRtnWPrx2FIcDurxyFyAIOoCC8Y/mI30zU6BUkJ6JIwnXei3m1hspOrh9w5wvu5TsBU2q9GUp3PkwaQ6jrFH+WjbAmC63zLgGjG1JxuFHA6GnUd4lRxOaKhswZABQUBBeJRbs4B5I5Az1kL2AI3yHiMdTYWXTW+RQgdVrBgwahw0oPy0+HtHi9xX7Tlsy3NGqPA6AHeObgoxZ9DzMDvEwhZHFHKzu9pCpOTcjnf7nbmK0VyuHXytFawb7a+CcZqDI/rGctM9Y25jTLYT0uRQrJif2TofyQWAwmG80uAxoR3IJsX2bTLe2Oafwy3W9vWhuhnHTO5ZgLOioG8VFMQ6C6OjbFIOpVIusfPLsPT0f5YfulssTIUjUapbd2BC6GhYpDgWTKLVZ8S9XHZyynOOlflYDXI7iw7rxJNYkaLnJMT1XuDHGIxjeUmSdCYkYikJ3A2dNiIlG6K/j7dFF8cSGRQaQ97XOf3Xs/kqVnW/sPv2wfbguUoiQIk1fP+XlB5JFXioLuQrQErBZzYAF6OGhEfpNH1gAWQ7ygRySFa/h7txkmTELmwWWFl4tHH/4h4cuuxhIWtM8gY1jZUc6AV4TEGdsQzTNdtztNBmGaEID2CU5Vou8+nERy4PhSjLfFFTDwEWbTJWNiJXYgoVD0Py3dAQ7DeU5b4lkXCLi1LkJeq27bIXhsudsHouLaZtG6xSobsqtCG4tvdG6RSA0IkZvA2l5IYThFSUWiYcnTEPnRmY3JhB/5Ejmn3IgP+aqZowgxuRBzTRZ5zRCSy0T5MUoe7u5TgW028RG7rSimZRmb4fk50VNnvV4mx1ZKv908hnD4xADJsZri72tgDNqhBmoxdQxDLCsYcREewPNA6klYeWCRaihWzSv753DZe/Tv06z6ur07wHXeGx08pKGvXuS6l9pAXc2indY88HPpQDkXVeIc3vCwZhr75ttDfcRp8xJeFCFDCoN1E/2YwabZGCOkCu9KckAYHEhuFKvOefWhhKqke6q/lGzZWLK85+C/4pPUz5EdKfYd6pbH5iARFTyUG0kFB4pTfS7diWkNMW5pkaDQ+W5V0Ow9Yz6aTzcaknuLkr3fAaCXQxuqOgSlKprb/seMejAQ9vJLOeKWrgGtujfi0rmlmBfKVktpbTtZlvNuIEjiNTqSgy9Un/0xDj6IYyyVrawBqwgFugUpAS3XBh0BW3z1hff3HXbJeXsffGsBvx8NVVMGZFKAd3DhT4piiBoaHBMyf9eaTTtnM2RWNm72GMrxk3xZDx2H9BAgA0Np6zugXL4j31EhlUdZ4VR1nftWL+88KU4CM3qCcSxHqueyEnQtWwnBogEjTPikmoIjIQ8+J55I9EjM8YverBViZo4gWSALc56wLlw5SEO4RVFApOm9gc4VPSMa6uWTJFCBNBoUzB1TNhWtMi6wgvKWW5WBnVgS7D6nmZYM7xdIDyYI3s1IW3d/D30HGSM07UUlpsmUhohyUwI7vBRepvjYmjF8wyLYPlvKEMps5urqTK2cIOahh4XR5E5R+suIem12oIy0ZxabEoK68q/ZwMiqGTBYC7wLCRjj1IXo6nALbY80CQAQESEEkbcq2DIMzfyY4JJjnedv4e3EwbWwerVFbNdhNQxCBMwJ7vgMvXYIQ/UdImzi/1RYckrw+I3Kuv+x7F/7J/UcqSEVa9WlKeGISaQdm8ZaN0//GEXopGejfmG5PJKFIIgKBlAXJHPSJrsv1FTO500dE/yJOvD2wbuu+kvXFt4CAVVlaYlgnw6YtJHHXUwsYo5GrGgilsSHvzKzbN9BrWJIkUEeEAE4bEHHpjMf4+2he8Y4A0OFBPdZoaM2XeMMw/YnHi+psWWciIjE6HG2XA62C0pxmFxBxFFyWkOKMwWjhG0924GNL7JlfV8eXB7dEgxV2bvJmxl5RiKJdp6SrQxnWPpnJVCmm0xRGZTIsUW3WgNb6kNRLVDF30PeUNir9Q8zF/BJJB7qPXCJiJ2vfDBLa4yOhkrMshCkLVm/K7OxjJz5U1K9uOITOWxqw8bX4EsMBuen1mK9yFnYyHQvExqv8HfIqQi7todI0dKEIG09CFMLJqmFBizduHyvd5+bSF5MYDs/XwuXKzJF0GliSJa5eBQB8gdwwCfsUI569LBsMoHLTTqOJSDcEjUbnojI+Em6A6TQJQ72WZlZwtuog4MSLsCyehFXIgAx+Yr32gqCQgAC8FeOYMzcG+IgEBKmCsPVyqFIJumah/SdI2buGAAxi+5CLPi4JniimAs9+qKWqWWSh8WIU3m7rKuGR6YQO6yQxMsGiZFCE1Cre/ckWBVNrlCF+6SMC1r2P1tVSKFreUrabPJc4/QySCHuEtkV8OM1kdVmWCRkpB5jDuxAhuDa6++Q0RDQogfqVkTYFGMOb72840QkHxPGczWHZfDkZua3n0yRXGboknrOLcatQxU6FBO6K+5OFSjZnb9cUZwswGdzNRWWtDvX1Q1EoI5IEUGbdhxXPyxTSoQVxUUayugkPwcUMERlWNAcJi0qPhbEujgnLQcJPeAFHAthLpRtN2W7wUZ/4OtgeyOggYhMHFZAYByoMqDzGkEWD8GoIHsJ0UxlaZMAJe4kDTRLIfLh/A8N3DTlmapqliG80KvTKGtQpVNUtYB4UMMXEqpPc8ssvBUfgU9EKKqloB6izcYUdAWeZQDgDnMDGabHJ2BwCECXgjSeXsEseLj4EwyvV3i6eVjhug5sCHkfDig7gSOGhDCTATB+YiXBh/Qj2iH3BPQEL0Sjkd1ugzD1/FPFGxeiUcjuuoMC7JqDEQm0912h2D1st6RcBeowRj6jCc47SkbGwV/maTmt0osB9dSLI1dUKrcZG6oofuXvAaLtcHKzC4PmamEYK5Hmky78yzCVtI6QiGMZhB3UlRO5IOsAHSVKXV+uw+030bekA+OwIHrqsXWzjJ5VakngF3vB5OQhs2n9ROKL08ykT7QFUiC7TQykfmZVdU6vCU1UGcZ9+EYM5E+l4/G00qi/GoyMZgx+zj/Z9xz/zYE+hNn3fT5OOUkZ9jKl3j2BJMGnt9r89LQDNEm0OgsUQQP9ZO8ZOSRJgC5e1HNdL9saKMdC7kESYDK2Iz9YDW1egWN+LAMGBjlgKJ5FeQmkiNS2nglU+jpKkGpYKgTGUksa7rfujpgcADSAGSjnLUDozS/UZG4ELFRsXckQmb3vSnboDF1XuMrHPRdCfVmCEH2e2QWqMrLwovUgnnUPe1H50kwJA+4Vug5VZoG8QM+DRsW8kjwyB2AhfoRKas28FHcdEUsVmBAoLI1Bd1B/w29RPVMMyEbRYZlbdzrwuyEUrQioMDT2M4Rso5Z4YEpLPjU1DyAcQmqiaTFeR6+f1xEHN7YtoBKDFsvbq5iFLNswqpu3xoqn+nL9prCIeRPjEf0j4A0yaMZahMSgpkOggZTEIz6bYDA5siorfx2HlI3uMGU8FB5OKsDIpMIAop/SyPZ27e08co+BPBH1wOvTAz1OhTMLex/0O1fhAgWUF0mLN08AU8QIESs9bsPUcXEey5XpqQHrbuFgrnmxahky2FjkxViBPKxpOkzNK/dShHcS+eBb5n7yaORj/M/0smQeBfEw6PhB8Fu6B8NkHCvewE39SxW9KppPkHzPtgyZhfdsiiN8DLcgq0HOLUyeRtRbjgG5Kqmq4hQxbUTddlPcKc+um0GYD3iKGW5q6nwyIyKNOuYNEYdVg66YOFVLxv6BpqhgjLGrjEa0urRQWMU1kgufnDXi1U5Jk74ZQ6I5+RGw/DBtJ4ZYci4jRL4olyf9gaqMpu4OhLkG0Lv3MivpdVyPHTuxb2pzSn9omYdWpBoARGaCSNwG9ioBYpIDnXgJRaCgvij4T262C2yQoTB6oBg8bSK/DjAbQMhLe5rNmp751jKWMKrQX7dl/NLml17ZIb07qDK/Uq2VKqyoehnWVc92I77mQa8k63oTRz63F6EY+gu/G14xxxqSgIvyzoLg4oeI0XdIPyiX5/3YkF9Ld0VAszYyJPr3smuJqbtvluHXqxx07iRR+643wHZmFSqJIA/mumVb+lyTSm3GI9+xUz6Yj6KnbxHqxX7JNsBIaXxM9VlXVJwTXgATC7PDB2UghJmmE71JUyxWdoYRuUbmrAP67ARHVQiB7pDPfrtrg3NHuiLMhUC4BAH8QiCzxRsn45QCbCUME2S09kDh02Z6Mttr2J3dRnOkhTu29CJZfqNrhRZRk5MH47k5YY/pP64Ml96g21ObCQ4RWJG8mvEZoJYlxdvfoCSDyGbwHYxWV25RcwXwidwTNuYdJwORHKI72hvhXOAYL4RM4GduICLqaiOO9aClIK+oiwUBFgRBkKFIn92eGRBlcfbKFCrfHWZgHhGjs1CKtwhfr8u1MEMEumpYB7NnJXTS2JUeH6u1X2B8o4qFhM+X8slYlhAlq0/ukEMQbbF3YTijJpCAKDwqi02k3+2KE5vqVLSU/0yNCRGTXYQaimAu3z4Lq1XI/9WuCFCqX96FkCRv18HAWTJsBcCQCYtIwagS/Unm6nONWsJm6gm7X22zMrYM7ay+UFF06RoCHRW0eh9LzvqcKmHr0kO67BMuG52RwpFgH69GwMiTZV9ASmzaC5Ph7GKTogVDbNreYA=#iefix") format('embedded-opentype'), url("data:application/font-woff;base64,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") format('woff'), url("fonts/opensans-semibold-webfont.ttf") format('truetype'), url("fonts/opensans-semibold-webfont.svg#open_sanssemibold") format('svg'); font-weight: 600; font-style: normal; } diff --git a/java/com.sap.sailing.gwt.ui/fonts/proximanova-bold-webfont.eot b/java/com.sap.sailing.gwt.ui/fonts/proximanova-bold-webfont.eot deleted file mode 100644 index 59dc21df31196c9046b96bc435368ecfdb2fd836..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22648 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 z 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 H0~e@3 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/fonts/proximanova-bold-webfont.ttf b/java/com.sap.sailing.gwt.ui/fonts/proximanova-bold-webfont.ttf deleted file mode 100644 index ce1cadabe667ee38535706d44ba01120b0e781cc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52632 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�K$dahLd8@vP*M8l?Hs&C-79_tH0p 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

Llc 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 RZ(JeQed(TW9sDuTe*>7fb!z|s diff --git a/java/com.sap.sailing.gwt.ui/fonts/proximanova-bold-webfont.woff b/java/com.sap.sailing.gwt.ui/fonts/proximanova-bold-webfont.woff deleted file mode 100644 index 63826fd4662b873bf2864fffeaf0acf5a7cfdb1e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25844 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 X6Oc~_cErUOXE0N~00C1^sg(c#Bgh8G diff --git a/java/com.sap.sailing.gwt.ui/fonts/proximanova-regular-webfont.eot b/java/com.sap.sailing.gwt.ui/fonts/proximanova-regular-webfont.eot deleted file mode 100644 index 49e4b8723229913eb94b5e82d3fe1539afd5b8ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22477 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 diff --git a/java/com.sap.sailing.gwt.ui/fonts/proximanova-regular-webfont.svg b/java/com.sap.sailing.gwt.ui/fonts/proximanova-regular-webfont.svg deleted file mode 100644 index 96e7c2348ae..00000000000 --- a/java/com.sap.sailing.gwt.ui/fonts/proximanova-regular-webfont.svg +++ /dev/null @@ -1,553 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/fonts/proximanova-regular-webfont.ttf b/java/com.sap.sailing.gwt.ui/fonts/proximanova-regular-webfont.ttf deleted file mode 100644 index d29cc757487cfc591bd54ad74810971816aa9697..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52848 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 diff --git a/java/com.sap.sailing.gwt.ui/fonts/proximanova-regular-webfont.woff b/java/com.sap.sailing.gwt.ui/fonts/proximanova-regular-webfont.woff deleted file mode 100644 index 0e2f85a8d55ccf42fe7948d4b0b4fb35cc1dbdad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25664 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 diff --git a/java/com.sap.sailing.gwt.ui/fonts/proximanova-semibold-webfont.eot b/java/com.sap.sailing.gwt.ui/fonts/proximanova-semibold-webfont.eot deleted file mode 100644 index ec807c8c36d3fb5a5a3a173ef426d1c1af333c7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22606 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 Q*z1!jHH?*l?gAl@$N^<-j{pDw diff --git a/java/com.sap.sailing.gwt.ui/fonts/proximanova-semibold-webfont.svg b/java/com.sap.sailing.gwt.ui/fonts/proximanova-semibold-webfont.svg deleted file mode 100644 index f55eb7b85fb..00000000000 --- a/java/com.sap.sailing.gwt.ui/fonts/proximanova-semibold-webfont.svg +++ /dev/null @@ -1,553 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/fonts/proximanova-semibold-webfont.ttf b/java/com.sap.sailing.gwt.ui/fonts/proximanova-semibold-webfont.ttf deleted file mode 100644 index f212bc1a5e4de39521d396167d2878d75bf01379..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52772 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 diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/HomeResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/HomeResources.java index 9bdbf18c858..cbfe7baf580 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/HomeResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/HomeResources.java @@ -30,6 +30,10 @@ public interface HomeResources extends ClientBundle { public interface MainCss extends CssResource { String grid(); String stackenblochen(); + String wrapper(); + String navbar(); + String navbar_button(); + String navbar_buttonactive(); String button(); String buttoninactive(); String buttonrefresh(); @@ -42,17 +46,16 @@ public interface HomeResources extends ClientBundle { String mainsection_header(); String mainsection_header_title(); String mainsection_navigation(); - String lightbox(); String lightbox_overlay(); String lightbox_overlayhidden(); String lightbox_content(); String lightbox_contenthidden(); - String lightbox_content_enlargedimage(); - String lightbox_content_enlargedimage_caption(); - String lightbox_content_enlargedimage_close(); + String lightbox_content_enlargeditem(); + String lightbox_content_enlargeditem_caption(); + String lightbox_content_enlargeditem_close(); + String media_slideshow_controls(); String lightbox_data(); - String lightbox_photo(); String lightbox_item(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/main.css b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/main.css index 35ed7ce761c..7637b05043d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/main.css +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/main.css @@ -1,6 +1,3 @@ -/** - * The main styling styles - */ *, *:before, *:after { @@ -10,18 +7,75 @@ } html, body { - font-family: 'Proxima Nova', sans-serif; - font-size: 100%; + height: 100%; + font-family: 'Open Sans', sans-serif; + font-size: 93.75%; line-height: 1.333; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; + -ms-content-zooming: none; +} +.wrapper { + min-height: 100%; + margin-bottom: -5em; + padding-bottom: 5em; +} +figure { + margin: 0; +} +a { + text-decoration: none; + color: #008fcc; +} +a:hover, +a:focus { + text-decoration: underline; +} +a img { + vertical-align: bottom; +} +hr { + font-size: 15px; + font-size: 1rem; + margin: 0; + height: 1px; + border: none; + border-top: 1px solid #ccc; +} +h2 { + font-size: 2.4em; + font-weight: bold; + color: #008fcc; + margin-top: 0.7em; + margin-bottom: 0.7em; +} +h3 { + font-size: 1.6em; + font-weight: bold; + color: #008fcc; + margin: 0 0 0.5em; +} +h4 { + font-size: 1.333333333333333em; + font-weight: bold; + color: #008fcc; +} +h5 { + font-size: 1.066666666666667em; + font-weight: 600; + color: #008fcc; +} +figure img { + display: block; +} +figcaption { + margin-top: 1.333333333333333em; } .grid, .stackenblochen { display: block; width: 100%; - max-width: 75rem; - overflow: hidden; + max-width: 80rem; list-style: none; margin: 0 auto; zoom: 1; @@ -49,67 +103,53 @@ body { .stackenblochen .stackenblochen { width: auto; max-width: none; - margin-left: -0.9375rem; - margin-right: -0.9375rem; + margin-left: -1rem; + margin-right: -1rem; } -figure { - margin: 0; -} -a { - text-decoration: none; - color: #008fcc; -} -a:hover, -a:focus { - text-decoration: underline; -} -a img { - vertical-align: bottom; -} -hr { - font-size: 16px; + +.navbar { + font-size: 15px; font-size: 1rem; - margin: 0; - height: 1px; - border: none; - border-top: 1px solid #ccc; + padding: 1.333333333333333em 0; + border-bottom: 1px solid #eee; } -h2 { - font-size: 2.25em; - font-weight: bold; - color: #008fcc; - margin-top: 0.7em; - margin-bottom: 0.7em; -} -h3 { - font-size: 1.5em; - font-weight: bold; - color: #008fcc; -} -h4 { - font-size: 1.25em; - font-weight: bold; - color: #008fcc; -} -h5 { - font-size: 1em; +.navbar_button { + display: inline-block; font-weight: 600; - color: #008fcc; + color: #666; + border: none; + padding: 0.666666666666667em 1.133333333333333em; + background-color: #eee; + -webkit-transition: background-color 0.2s; + -moz-transition: background-color 0.2s; + -o-transition: background-color 0.2s; + -ms-transition: background-color 0.2s; + transition: background-color 0.2s; + text-decoration: none; + text-transform: uppercase; + margin-right: 1em; } -figure img { - display: block; +.navbar_button:hover, +.navbar_button:focus { + background-color: #d6d6d6; + outline: none; + text-decoration: none; } -figcaption { - margin-top: 1.25em; +.navbar_buttonactive { + color: #fff; + background-color: #999; +} +.navbar_buttonactive:hover { + background-color: #8a8a8a; } .button { - font-size: 16px; + font-size: 15px; font-size: 1rem; display: inline-block; font-weight: 600; color: #fff; border: none; - padding: 0.625em 1.0625em; + padding: 0.666666666666667em 1.133333333333333em; background-color: #f0ab00; -webkit-transition: background-color 0.2s; -moz-transition: background-color 0.2s; @@ -141,36 +181,40 @@ figcaption { -webkit-background-size: cover; -moz-background-size: cover; background-size: cover; - width: 2.1875em; + width: 2.333333333333333em; height: 0px; - padding: 2.1875em 0 0 0; + padding: 2.333333333333333em 0 0 0; border: 0; overflow: hidden; } .buttonsearch { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAVCAYAAABG1c6oAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDowMzgwMTE3NDA3MjA2ODExODhDNkY0MENFRkI5ODA4MiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo5MUFEMjM1NkJDQzExMUUzQjg3OTk5M0I5MTJGQ0VFNSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo5MUFEMjM1NUJDQzExMUUzQjg3OTk5M0I5MTJGQ0VFNSIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1LjEgTWFjaW50b3NoIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6RkREN0JFQTYyNjIxNjgxMTg3MUZENzgzOTY3OTA2QzciIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MDM4MDExNzQwNzIwNjgxMTg4QzZGNDBDRUZCOTgwODIiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4wYIhVAAABZklEQVR42qyUwUdEURTGe28S8VbDEPGIiHaJFhlepFXUv9AiQ6tGtJ7N/AFRRCRatCxaJWpTilYRbzUMj9EjRSQmpb7LF8edd++drg4/9zhz53POPeed4L37OfCfFhbEquAYPINv0AXnYKUfwUBkOAyaYMNy/wysgo5LcIhZJIxn4BQ8gJjxKn97AlMm0UGem0JsH9TBm3a3BrZBBRyCeVOGEc5HEDGrZUvJ62CL/iy4LWpKQjFlDceb74IX+kumLo/TfwX3DsEPcE1/wiRYEpf7sd97kUkwo19hR102zbNlElQlfIlHt9kiGKN/YhLMwZ4QXDOIzYADUXZqG2xV7o1o0CU4YpNURgucQ2kpZzE3fXoxBzb5wy7oEZXLQTVnjl9Jpv1Rzd5OQSMmwQUYKcpQt5hPocTajI2CK9GYnkwDj31oFQ09dmiHT9PW4mVF6LmYM000Z8mpr6AUveOZupriZT8CDADoqmLlEaD/6QAAAABJRU5ErkJggg=="); background-color: transparent; - width: 1.25em; - padding: 1.3125em 0 0 0; + width: 1.333333333333333em; + padding: 1.4em 0 0 0; } .buttonarrowdown { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAALFJREFUeNrs1TEOgjAUxnFx5iDscgPvwXkYuYdXwBPgAYwrsxPGIAPPvKQkxLQO5UE0+X9J06Ff0186tImI7H45CUCAAAECBAgQIMA1s1+y+dkUpUXna/QGY0Z/q0QztCcJddyauG7UOdHA8XWXKT7khNO47rbArj5kIeQnTrubA0NIS9xioA9piTMB+pBWODPgHGmJ02H6UD/OeaZzerxc+eoAAgQIECBAgAD/AvgWYACm/8hEADYaXwAAAABJRU5ErkJggg=="); - width: 2.5em; - padding: 2.5625em 0 0 0; + width: 2.666666666666667em; + padding: 2.733333333333333em 0 0 0; } .label { display: inline-block; - font-size: 12px; + font-size: 11.25px; font-size: 0.75rem; font-weight: 600; color: #fff; - padding: 0.125em 0.875em; + padding: 0.133333333333333em 0.933333333333333em; } .label[data-labeltype='live'] { background-color: #f00; - text-transform: uppercase; vertical-align: middle; - margin-top: -0.625em; + margin-top: -0.666666666666667em; +} +.label[data-labeltype='finished'] { + background-color: #8ab54e; + vertical-align: middle; + margin-top: -0.666666666666667em; } .label[data-labeltype='new'], .label[data-labeltype='updated'] { @@ -178,22 +222,22 @@ figcaption { text-transform: uppercase; vertical-align: middle; } -.label[data-labeltype='group1'] { +.label[data-labeltype='ess'] { background-color: #a71724; } -.label[data-labeltype='group2'] { +.label[data-labeltype='worldcup'] { background-color: #c79242; } -.label[data-labeltype='group3'] { +.label[data-labeltype='bundesliga'] { background-color: #005fa7; } .morelink { color: #008fcc; text-transform: uppercase; text-align: center; - font-size: 0.875em; + font-size: 0.933333333333333em; font-weight: 700; - padding: 1.75em; + padding: 1.866666666666667em; display: block; text-decoration: none; } @@ -202,7 +246,7 @@ figcaption { text-decoration: underline; } .dummy { - font-size: 16px; + font-size: 15px; font-size: 1rem; min-height: 25em; position: relative; @@ -219,32 +263,31 @@ figcaption { margin: -100px 0 0 -350px; } .mainsection { - padding-top: 5em; - padding-bottom: 5em; + padding-top: 5.333333333333333em; + padding-bottom: 5.333333333333333em; } .mainsection_header { text-align: center; - margin-bottom: 3.4375em; + margin-bottom: 3.666666666666667em; } .mainsection_header p { margin-top: 0; - font-size: 1.125em; + font-size: 1.2em; } .mainsection_header_title { margin: 0; - font-size: 2.25em; + font-size: 2.4em; text-align: center; } .mainsection_navigation { text-align: center; } - .lightbox { - font-size: 16px; + font-size: 15px; font-size: 1rem; } .lightbox_overlay { - font-size: 16px; + font-size: 15px; font-size: 1rem; background: #000; opacity: 0.7; @@ -290,7 +333,7 @@ figcaption { -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; filter: literal("alpha(opacity=0)"); } -.lightbox_content_enlargedimage { +.lightbox_content_enlargeditem { -webkit-transition: margin 0.7s linear; -moz-transition: margin 0.7s linear; -o-transition: margin 0.7s linear; @@ -301,38 +344,33 @@ figcaption { left: 50%; top: 50%; } -.lightbox_content_enlargedimage_caption { +.lightbox_content_enlargeditem_caption { text-align: center; color: #f6f9fc; font-weight: 700; } -.lightbox_content_enlargedimage_close { +.lightbox_content_enlargeditem_close { display: inline-block; - background-image: url("images/home/x_lightbox.png"); + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAHVJREFUeNqsk4EJACEIReWawY2bMLh5LME7JNQEE4So/x9pCUTUVvaVuBKSieJpIAuOkYSgaDn6t/EmIdrMHtQHJ4g2/5pd4EFM8w7whK7ZAliQEZWWaVbY3AeqcbuEUhMj4fEZ8XTV6CNlzC7kyjCVxnkKMABEXRUAnXsRRQAAAABJRU5ErkJggg=="); background-repeat: no-repeat; background-position: center center; - width: 1.25em; + width: 1.333333333333333em; height: 0; overflow: hidden; - padding-top: 1.25em; + padding-top: 1.333333333333333em; position: absolute; - top: 1.25em; - right: 1.25em; + top: 1.333333333333333em; + right: 1.333333333333333em; cursor: pointer; z-index: 2003; } -/** .lightbox_content .media_slideshow_controls { z-index: 2002; } -*/ - .lightbox_data { display: none; } -.lightbox_photo { - cursor: pointer; -} - .lightbox_item { + font-size: 15px; + font-size: 1rem; } From 9e5549fb67840b6afb4ca5a23ac60bf9b8db8be1 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jul 2014 18:17:57 +0200 Subject: [PATCH 125/323] replicate RecordMarkGPSFix more cleverly, only serializing the full Mark object when needed, upon first occurrence --- .../leaderboard/LeaderboardCacheManager.java | 2 +- .../impl/AbstractSimpleLeaderboardImpl.java | 2 +- .../MarkPassingUpdateListener.java | 2 +- .../domain/tracking/GPSTrackListener.java | 2 +- .../domain/tracking/RaceChangeListener.java | 2 +- .../impl/AbstractRaceChangeListener.java | 2 +- .../tracking/impl/CrossTrackErrorCache.java | 2 +- .../tracking/impl/DynamicTrackedRaceImpl.java | 18 +++---- .../domain/tracking/impl/GPSFixTrackImpl.java | 4 +- .../domain/tracking/impl/MaxSpeedCache.java | 2 +- .../TrackBasedEstimationWindTrackImpl.java | 2 +- .../domain/tracking/impl/TrackedLegImpl.java | 2 +- .../domain/tracking/impl/TrackedRaceImpl.java | 2 +- .../SailingServer (No Proxy).launch | 2 +- ...ingServer (No Proxy, Jetty on 8889).launch | 2 +- .../SailingServer (Proxy).launch | 2 +- ...ailingServer (Proxy, Jetty on 8889).launch | 2 +- .../server/impl/RacingEventServiceImpl.java | 12 ++++- .../RecordMarkGPSFix.java | 36 +++----------- .../RecordMarkGPSFixForExistingTrack.java | 48 +++++++++++++++++++ .../RecordMarkGPSFixForNewMarkTrack.java | 31 ++++++++++++ 21 files changed, 123 insertions(+), 56 deletions(-) create mode 100755 java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/RecordMarkGPSFixForExistingTrack.java create mode 100755 java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/RecordMarkGPSFixForNewMarkTrack.java diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardCacheManager.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardCacheManager.java index 3cd5b837ca6..f6e7ef2b115 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardCacheManager.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardCacheManager.java @@ -70,7 +70,7 @@ public class LeaderboardCacheManager { } @Override - public void markPositionChanged(GPSFix fix, Mark mark) { + public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) { removeFromCache(leaderboard); } 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 b16aa9d93d8..18206de4caa 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 @@ -312,7 +312,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race } @Override - public void markPositionChanged(GPSFix fix, Mark mark) { + public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) { invalidateCacheAndRemoveThisListenerFromTrackedRace(); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingUpdateListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingUpdateListener.java index 86d441482f8..c1636f24013 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingUpdateListener.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingUpdateListener.java @@ -40,7 +40,7 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener { } @Override - public void markPositionChanged(GPSFix fix, Mark mark) { + public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) { queue.add(new Util.Pair(mark, fix)); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSTrackListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSTrackListener.java index dd0671903b2..1aaa7c23943 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSTrackListener.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSTrackListener.java @@ -3,7 +3,7 @@ package com.sap.sailing.domain.tracking; import java.io.Serializable; public interface GPSTrackListener extends Serializable { - void gpsFixReceived(FixType fix, ItemType item); + void gpsFixReceived(FixType fix, ItemType item, boolean firstFixInTrack); void speedAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceChangeListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceChangeListener.java index 09ecba9616c..5fe31595664 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceChangeListener.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceChangeListener.java @@ -13,7 +13,7 @@ import com.sap.sailing.domain.common.WindSource; public interface RaceChangeListener extends CourseListener { void competitorPositionChanged(GPSFixMoving fix, Competitor competitor); - void markPositionChanged(GPSFix fix, Mark mark); + void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack); /** * Invoked after the mark passings have been updated in the {@link TrackedRace}. diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/AbstractRaceChangeListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/AbstractRaceChangeListener.java index 433b5488217..cb76097f12b 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/AbstractRaceChangeListener.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/AbstractRaceChangeListener.java @@ -33,7 +33,7 @@ public abstract class AbstractRaceChangeListener implements RaceChangeListener { } @Override - public void markPositionChanged(GPSFix fix, Mark mark) { + public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) { } @Override diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java index 20c12f64114..f56a09c6b79 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java @@ -397,7 +397,7 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener { } @Override - public void markPositionChanged(GPSFix fix, Mark mark) { + public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) { TimePoint from = owner.getOrCreateTrack(mark).getEstimatedPositionTimePeriodAffectedBy(fix).getA(); final List shuffledCompetitors = new ArrayList<>(cachePerCompetitor.keySet()); Collections.shuffle(shuffledCompetitors); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java index 5752ab13fae..c8511c9cc7a 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java @@ -212,20 +212,20 @@ DynamicTrackedRace, GPSTrackListener { /** * In addition to creating the track which is performed by the superclass implementation, this implementation registers - * a {@link GPSTrackListener} with the mark's track and {@link #notifyListeners(GPSFix, Mark) notifies the listeners} + * a {@link GPSTrackListener} with the mark's track and {@link #notifyListeners(GPSFix, Mark, boolean) notifies the listeners} * about updates. The {@link #updated(TimePoint)} method is not called with the mark fix's time point because * mark fixes may be received also from marks that don't belong to this race. */ @Override protected DynamicGPSFixTrackImpl createMarkTrack(Mark mark) { - DynamicGPSFixTrackImpl result = super.createMarkTrack(mark); + final DynamicGPSFixTrackImpl result = super.createMarkTrack(mark); result.addListener(new GPSTrackListener() { private static final long serialVersionUID = -2855787105725103732L; @Override - public void gpsFixReceived(GPSFix fix, Mark mark) { + public void gpsFixReceived(GPSFix fix, Mark mark, boolean firstFixInTrack) { triggerManeuverCacheRecalculationForAllCompetitors(); - notifyListeners(fix, mark); + notifyListeners(fix, mark, firstFixInTrack); } @Override @@ -306,8 +306,10 @@ DynamicTrackedRace, GPSTrackListener { GPSFixTrack markTrack = getOrCreateTrack(mark); markTrack.lockForRead(); try { + boolean firstInTrack = true; for (GPSFix fix : markTrack.getRawFixes()) { - listener.markPositionChanged(fix, mark); + listener.markPositionChanged(fix, mark, firstInTrack); + firstInTrack = false; } } finally { markTrack.unlockAfterRead(); @@ -437,14 +439,14 @@ DynamicTrackedRace, GPSTrackListener { notifyListenersWaypointRemoved(zeroBasedIndex, waypointThatGotRemoved); } - private void notifyListeners(GPSFix fix, Mark mark) { + private void notifyListeners(GPSFix fix, Mark mark, boolean firstInTrack) { RaceChangeListener[] listeners; synchronized (getListeners()) { listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]); } for (RaceChangeListener listener : listeners) { try { - listener.markPositionChanged(fix, mark); + listener.markPositionChanged(fix, mark, firstInTrack); } catch (Exception e) { logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage()); logger.log(Level.SEVERE, "notifyListeners(GPSFix, Competitor)", e); @@ -800,7 +802,7 @@ DynamicTrackedRace, GPSTrackListener { } @Override - public void gpsFixReceived(GPSFixMoving fix, Competitor competitor) { + public void gpsFixReceived(GPSFixMoving fix, Competitor competitor, boolean firstFixInTrack) { updated(fix.getTimePoint()); triggerManeuverCacheRecalculation(competitor); notifyListeners(fix, competitor); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java index 4745c89906a..dfee0e5a6e6 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java @@ -1009,8 +1009,10 @@ public class GPSFixTrackImpl extends TrackImpl @Override protected boolean add(FixType fix) { final boolean result; + final boolean firstFixInTrack; lockForWrite(); try { + firstFixInTrack = getRawFixes().isEmpty(); result = addWithoutLocking(fix); invalidateValidityAndDistanceCaches(fix); } finally { @@ -1034,7 +1036,7 @@ public class GPSFixTrackImpl extends TrackImpl } } for (GPSTrackListener listener : getListeners()) { - listener.gpsFixReceived(fix, getTrackedItem()); + listener.gpsFixReceived(fix, getTrackedItem(), firstFixInTrack); } return result; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/MaxSpeedCache.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/MaxSpeedCache.java index 25faf6bb51e..48cdcacc693 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/MaxSpeedCache.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/MaxSpeedCache.java @@ -79,7 +79,7 @@ public class MaxSpeedCache implements GPSTrack * outside of the invalidation interval, crop cache entry's interval such that it's overlap-free, otherwise remove. */ @Override - public void gpsFixReceived(FixType fix, ItemType item) { + public void gpsFixReceived(FixType fix, ItemType item, boolean firstFixInTrack) { // find the invalidation interval such that getFixesRelevantForSpeedEstimation, when passed any time point from that interval, produces "fix" Util.Pair invalidationInterval = track.getTimeIntervalWhoseEstimatedSpeedMayHaveChangedAfterAddingFix(fix); LockUtil.lockForWrite(lock); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java index e23f82ee947..c2f4f47f4e8 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java @@ -693,7 +693,7 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl } @Override - public void markPositionChanged(GPSFix fix, Mark mark) { + public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) { assert fix != null && fix.getTimePoint() != null; // A mark position change can mean a leg type change. The interval over which the wind estimation is affected // depends on how the GPS track computes the estimated mark position. Ask it: 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 57ef50b7f71..8c96a34843f 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 @@ -285,7 +285,7 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener { } @Override - public void markPositionChanged(GPSFix fix, Mark mark) { + public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) { clearCaches(); } 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 88412e6958e..549a4e1d88c 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 @@ -2487,7 +2487,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl } @Override - public void gpsFixReceived(GPSFix fix, Mark mark) { + public void gpsFixReceived(GPSFix fix, Mark mark, boolean firstFixInTrack) { clearDirectionFromStartToNextMarkCache(); } diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy).launch b/java/com.sap.sailing.server/SailingServer (No Proxy).launch index c132dcbaaf8..95e2b7af28e 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/SailingServer (No Proxy, Jetty on 8889).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch index 13b887d9041..a3cf7523b4d 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch @@ -18,7 +18,7 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy).launch b/java/com.sap.sailing.server/SailingServer (Proxy).launch index f9e884ac7d2..a28538e295b 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy).launch @@ -19,7 +19,7 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch b/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch index 63313b848a1..3d135d34a1c 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch @@ -18,7 +18,7 @@ - + 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 5f4aa764398..443665d3d93 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 @@ -163,6 +163,8 @@ import com.sap.sailing.server.operationaltransformation.CreateTrackedRace; import com.sap.sailing.server.operationaltransformation.DataImportFailed; import com.sap.sailing.server.operationaltransformation.RecordCompetitorGPSFix; import com.sap.sailing.server.operationaltransformation.RecordMarkGPSFix; +import com.sap.sailing.server.operationaltransformation.RecordMarkGPSFixForExistingTrack; +import com.sap.sailing.server.operationaltransformation.RecordMarkGPSFixForNewMarkTrack; import com.sap.sailing.server.operationaltransformation.RecordWindFix; import com.sap.sailing.server.operationaltransformation.RemoveDeviceConfiguration; import com.sap.sailing.server.operationaltransformation.RemoveEvent; @@ -1370,8 +1372,14 @@ public class RacingEventServiceImpl implements RacingEventServiceWithTestSupport } @Override - public void markPositionChanged(GPSFix fix, Mark mark) { - replicate(new RecordMarkGPSFix(getRaceIdentifier(), mark, fix)); + public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) { + final RecordMarkGPSFix operation; + if (firstInTrack) { + operation = new RecordMarkGPSFixForNewMarkTrack(getRaceIdentifier(), mark, fix); + } else { + operation = new RecordMarkGPSFixForExistingTrack(getRaceIdentifier(), mark, fix); + } + replicate(operation); } @Override diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/RecordMarkGPSFix.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/RecordMarkGPSFix.java index e0ed4ded9f8..fc9144eed29 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/RecordMarkGPSFix.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/RecordMarkGPSFix.java @@ -1,51 +1,27 @@ package com.sap.sailing.server.operationaltransformation; -import java.io.Serializable; - -import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.GPSFix; import com.sap.sailing.server.RacingEventService; import com.sap.sailing.server.RacingEventServiceOperation; -public class RecordMarkGPSFix extends AbstractRaceOperation { +public abstract class RecordMarkGPSFix extends AbstractRaceOperation { private static final long serialVersionUID = -2149936580623244814L; - private final Serializable markID; private final GPSFix fix; - public RecordMarkGPSFix(RegattaAndRaceIdentifier raceIdentifier, Mark mark, GPSFix fix) { + public RecordMarkGPSFix(RegattaAndRaceIdentifier raceIdentifier, GPSFix fix) { super(raceIdentifier); - this.markID = mark.getId(); this.fix = fix; } - /** - * Operations of this type can be run in parallel to other operations; subsequent operations do not have to wait - * for this operation's completion. - */ - @Override - public boolean requiresSynchronousExecution() { - return false; + protected GPSFix getFix() { + return fix; } - @Override - public Void internalApplyTo(RacingEventService toState) throws Exception { + protected DynamicTrackedRace getTrackedRace(RacingEventService toState) { DynamicTrackedRace trackedRace = (DynamicTrackedRace) toState.getTrackedRace(getRaceIdentifier()); - Mark mark = getMarkById(trackedRace); - trackedRace.recordFix(mark, fix); - return null; - } - - private Mark getMarkById(DynamicTrackedRace trackedRace) { - Mark result = null; - for (Mark mark : trackedRace.getMarks()) { - if (mark.getId().equals(markID)) { - result = mark; - break; - } - } - return result; + return trackedRace; } @Override diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/RecordMarkGPSFixForExistingTrack.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/RecordMarkGPSFixForExistingTrack.java new file mode 100755 index 00000000000..5ab7436755d --- /dev/null +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/RecordMarkGPSFixForExistingTrack.java @@ -0,0 +1,48 @@ +package com.sap.sailing.server.operationaltransformation; + +import java.io.Serializable; + +import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.domain.tracking.GPSFix; +import com.sap.sailing.server.RacingEventService; + +public class RecordMarkGPSFixForExistingTrack extends RecordMarkGPSFix { + private static final long serialVersionUID = -3953907832324217209L; + private final Serializable markId; + + public RecordMarkGPSFixForExistingTrack(RegattaAndRaceIdentifier raceIdentifier, Mark mark, GPSFix fix) { + super(raceIdentifier, fix); + this.markId = mark.getId(); + } + + private Mark getMarkById(DynamicTrackedRace trackedRace) { + Mark result = null; + for (Mark mark : trackedRace.getMarks()) { + if (mark.getId().equals(markId)) { + result = mark; + break; + } + } + return result; + } + + @Override + public Void internalApplyTo(RacingEventService toState) throws Exception { + DynamicTrackedRace trackedRace = getTrackedRace(toState); + Mark mark = getMarkById(trackedRace); + trackedRace.recordFix(mark, getFix()); + return null; + } + + /** + * Operations of this type can be run in parallel to other operations; subsequent operations do not have to wait + * for this operation's completion. + */ + @Override + public boolean requiresSynchronousExecution() { + return false; + } + +} diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/RecordMarkGPSFixForNewMarkTrack.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/RecordMarkGPSFixForNewMarkTrack.java new file mode 100755 index 00000000000..7fc79dc1e2e --- /dev/null +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/RecordMarkGPSFixForNewMarkTrack.java @@ -0,0 +1,31 @@ +package com.sap.sailing.server.operationaltransformation; + +import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.domain.tracking.GPSFix; +import com.sap.sailing.server.RacingEventService; + +/** + * Records a mark fix for a mark whose track was just created. Such an operation carries the full {@link Mark} object + * because the receiver needs it in order to construct the mark track. + * + * @author Axel Uhl (d043530) + * + */ +public class RecordMarkGPSFixForNewMarkTrack extends RecordMarkGPSFix { + private static final long serialVersionUID = 6462303849651033783L; + private final Mark mark; + + public RecordMarkGPSFixForNewMarkTrack(RegattaAndRaceIdentifier raceIdentifier, Mark mark, GPSFix fix) { + super(raceIdentifier, fix); + this.mark = mark; + } + + @Override + public Void internalApplyTo(RacingEventService toState) throws Exception { + DynamicTrackedRace trackedRace = getTrackedRace(toState); + trackedRace.recordFix(mark, getFix()); + return null; + } +} From 6d0ea3c0a0b6d087490f0b950473745dde319127 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jul 2014 19:03:20 +0200 Subject: [PATCH 126/323] fixed compile errors after introducing RaceDefinition.getCompetitorByID --- .../test/mock/MockedTrackedRaceWithFixedRank.java | 9 +++++++++ ...ckedTrackedRaceWithFixedRankAndManyCompetitors.java | 10 ++++++++++ 2 files changed, 19 insertions(+) diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithFixedRank.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithFixedRank.java index 18ee0bc4be7..ab35163756e 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithFixedRank.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithFixedRank.java @@ -66,6 +66,15 @@ public class MockedTrackedRaceWithFixedRank extends MockedTrackedRace { public Serializable getId() { return null; } + + @Override + public Competitor getCompetitorById(Serializable competitorID) { + if (competitorID.equals(MockedTrackedRaceWithFixedRank.this.competitor.getId())) { + return MockedTrackedRaceWithFixedRank.this.competitor; + } else { + return null; + } + } } @Override diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithFixedRankAndManyCompetitors.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithFixedRankAndManyCompetitors.java index 93b47c73b3c..64c53934cb9 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithFixedRankAndManyCompetitors.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithFixedRankAndManyCompetitors.java @@ -32,6 +32,16 @@ public class MockedTrackedRaceWithFixedRankAndManyCompetitors extends MockedTrac public Iterable getCompetitors() { return competitors; } + @Override + public Competitor getCompetitorById(Serializable competitorID) { + for (Competitor competitor : competitors) { + if (competitorID.equals(competitor.getId())) { + return competitor; + } + } + return null; + } + @Override public BoatClass getBoatClass() { return null; From f32bbd6158f3f7d3ae23eb74573c3431b0965de4 Mon Sep 17 00:00:00 2001 From: fmittag Date: Wed, 16 Jul 2014 19:17:20 +0200 Subject: [PATCH 127/323] made the shitty css work for the races! Hell! --- .../event/regattaraces/EventRegattaRaces.java | 2 +- .../regattaraces/EventRegattaRacesFleet.java | 4 +- .../EventRegattaRacesFleetGroup.java | 23 +++++- .../EventRegattaRacesFleetGroup.ui.xml | 5 +- .../regattaraces/EventRegattaRacesPhase.java | 22 +++--- .../EventRegattaRacesPhase.ui.xml | 5 +- .../regattaraces/EventRegattaRacesRace.java | 79 +++++++++---------- .../regattaraces/EventRegattaRacesRace.ui.xml | 9 +-- .../EventRegattaRacesRaceday.java | 17 ++-- .../EventRegattaRacesRaceday.ui.xml | 2 +- .../com/sap/sailing/gwt/home/main.css | 6 +- 11 files changed, 88 insertions(+), 86 deletions(-) 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 981c232a89e..554a1ab883b 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 @@ -48,7 +48,7 @@ public class EventRegattaRaces extends Composite { if(regattaPhases > 1) { for(RaceGroupSeriesDTO series: raceGroup.getSeries()) { EventRegattaRacesPhase regattaPhase = new EventRegattaRacesPhase(leaderboard, series, timerForClientServerOffset, pageNavigator); - regattaPhasesPanel.add(regattaPhase); + regattaPhasesPanel.getElement().appendChild(regattaPhase.getElement()); } } else { 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 a7c341f0e7f..8c74a36ca30 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 @@ -24,7 +24,9 @@ public class EventRegattaRacesFleet { root = uiBinder.createAndBindUi(this); fleetName.setInnerText(fleet.getName()); - fleetCountAndColor.getStyle().setBackgroundColor(fleet.getColor().getAsHtml()); + if(fleet.getColor() != null) { + fleetCountAndColor.getStyle().setBackgroundColor(fleet.getColor().getAsHtml()); + } fleetCountAndColor.setInnerText("tbd."); } 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 06663bcd53b..e91d069f878 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 @@ -5,10 +5,10 @@ import java.util.List; 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.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.domain.common.dto.FleetDTO; import com.sap.sailing.domain.common.dto.RaceColumnDTO; @@ -24,7 +24,7 @@ public class EventRegattaRacesFleetGroup extends Composite { } @UiField DivElement fleetsPanel; - @UiField HTMLPanel racesFleetPanel; + @UiField DivElement racesFleetPanel; public EventRegattaRacesFleetGroup(StrippedLeaderboardDTO leaderboard, RaceGroupSeriesDTO series, List fleetsToShow, Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { EventRegattaRacesResources.INSTANCE.css().ensureInjected(); @@ -38,11 +38,26 @@ public class EventRegattaRacesFleetGroup extends Composite { for(RaceColumnDTO raceColumn: racesOfFleet) { RaceColumnDTO raceColumnFromLeaderboard = leaderboard.getRaceColumnByName(raceColumn.getName()); EventRegattaRacesRace race = new EventRegattaRacesRace(leaderboard, fleet, raceColumnFromLeaderboard, timerForClientServerOffset, pageNavigator); - racesFleetPanel.add(race); + racesFleetPanel.appendChild(race.getElement()); } } } - + + public EventRegattaRacesFleetGroup(StrippedLeaderboardDTO leaderboard, RaceGroupSeriesDTO series, Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { + EventRegattaRacesResources.INSTANCE.css().ensureInjected(); + initWidget(uiBinder.createAndBindUi(this)); + + fleetsPanel.getStyle().setDisplay(Display.NONE); + + FleetDTO defaultFleet = series.getFleets().get(0); + List racesOfFleet = getRacesOfFleet(leaderboard, series, defaultFleet); + for(RaceColumnDTO raceColumn: racesOfFleet) { + RaceColumnDTO raceColumnFromLeaderboard = leaderboard.getRaceColumnByName(raceColumn.getName()); + EventRegattaRacesRace race = new EventRegattaRacesRace(leaderboard, defaultFleet, raceColumnFromLeaderboard, timerForClientServerOffset, pageNavigator); + racesFleetPanel.appendChild(race.getElement()); + } + } + private List getRacesOfFleet(StrippedLeaderboardDTO leaderboard, RaceGroupSeriesDTO series, FleetDTO fleet) { List racesColumnsOfFleet = new ArrayList(); for (RaceColumnDTO raceColumn : series.getRaceColumns()) { 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 1a2f0ce1d59..5a5fddf7aaa 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 @@ -6,8 +6,7 @@ -

-
- +
+
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 9bb37706ccc..a9ec4ffc8ab 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 @@ -1,34 +1,32 @@ package com.sap.sailing.gwt.home.client.place.event.regattaraces; import java.util.ArrayList; -import java.util.Collections; 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.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.google.gwt.user.client.ui.UIObject; import com.sap.sailing.domain.common.dto.FleetDTO; 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; import com.sap.sse.gwt.client.player.Timer; -public class EventRegattaRacesPhase extends Composite { +public class EventRegattaRacesPhase extends UIObject { private static EventRegattaRacesPhaseUiBinder uiBinder = GWT.create(EventRegattaRacesPhaseUiBinder.class); - interface EventRegattaRacesPhaseUiBinder extends UiBinder { + interface EventRegattaRacesPhaseUiBinder extends UiBinder { } @UiField SpanElement phaseName; - @UiField HTMLPanel fleetGroupsPanel; + @UiField DivElement fleetGroupsPanel; public EventRegattaRacesPhase(StrippedLeaderboardDTO leaderboard, RaceGroupSeriesDTO series, Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { EventRegattaRacesResources.INSTANCE.css().ensureInjected(); - initWidget(uiBinder.createAndBindUi(this)); + setElement(uiBinder.createAndBindUi(this)); phaseName.setInnerText(series.getName()); @@ -40,18 +38,18 @@ public class EventRegattaRacesPhase extends Composite { List fleetsToShow = new ArrayList(); fleetsToShow.add(fleet); EventRegattaRacesFleetGroup fleetGroup = new EventRegattaRacesFleetGroup(leaderboard, series, fleetsToShow, timerForClientServerOffset, pageNavigator); - fleetGroupsPanel.add(fleetGroup); + fleetGroupsPanel.appendChild(fleetGroup.getElement()); } } else { // unordered fleets are NOT splitted into different fleet groups -> e.g. Blue, Yellow List fleetsToShow = new ArrayList(series.getFleets()); EventRegattaRacesFleetGroup fleetGroup = new EventRegattaRacesFleetGroup(leaderboard, series, fleetsToShow, timerForClientServerOffset, pageNavigator); - fleetGroupsPanel.add(fleetGroup); + fleetGroupsPanel.appendChild(fleetGroup.getElement()); } } else { // single fleet - EventRegattaRacesFleetGroup fleetGroup = new EventRegattaRacesFleetGroup(leaderboard, series, Collections.emptyList(), timerForClientServerOffset, pageNavigator); - fleetGroupsPanel.add(fleetGroup); + EventRegattaRacesFleetGroup fleetGroup = new EventRegattaRacesFleetGroup(leaderboard, series, timerForClientServerOffset, pageNavigator); + fleetGroupsPanel.appendChild(fleetGroup.getElement()); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesPhase.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesPhase.ui.xml index 22dd4bed951..dbdc420b1d0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesPhase.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesPhase.ui.xml @@ -5,11 +5,8 @@ - -

- +
-
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 e57d5f24645..73ea2547b07 100644 --- 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 @@ -1,19 +1,18 @@ package com.sap.sailing.gwt.home.client.place.event.regattaraces; import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.AnchorElement; import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.SpanElement; import com.google.gwt.dom.client.Style.Display; import com.google.gwt.dom.client.Style.Visibility; -import com.google.gwt.event.dom.client.ClickEvent; 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.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.Event; +import com.google.gwt.user.client.EventListener; +import com.google.gwt.user.client.ui.UIObject; 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,26 +21,26 @@ import com.sap.sailing.gwt.home.client.place.event.EventPageNavigator; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; import com.sap.sse.gwt.client.player.Timer; -public class EventRegattaRacesRace extends Composite { +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 }; - interface EventRegattaRacesRaceUiBinder extends UiBinder { + 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 watchRaceDiv; @UiField DivElement analyzeRaceDiv; - @UiField Anchor watchRaceLink; - @UiField Anchor analyzeRaceLink; + @UiField AnchorElement watchRaceLink; + @UiField AnchorElement analyzeRaceLink; @UiField DivElement raceWinnerDiv; @UiField SpanElement raceWinner; @@ -60,9 +59,8 @@ public class EventRegattaRacesRace extends Composite { // local_res.css.eventregattarace_featureunavailable - 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 TextMessages textMessages = GWT.create(TextMessages.class); private final StrippedLeaderboardDTO leaderboard; private final FleetDTO fleet; @@ -80,29 +78,40 @@ public class EventRegattaRacesRace extends Composite { this.timerForClientServerOffset = timerForClientServerOffset; EventRegattaRacesResources.INSTANCE.css().ensureInjected(); - initWidget(uiBinder.createAndBindUi(this)); - + setElement(uiBinder.createAndBindUi(this)); + allConditionalElements = new Element[] {raceWinnerDiv, raceLeaderDiv, watchRaceDiv, analyzeRaceDiv, raceNotTrackedDiv, raceFeaturesDiv, legProgressDiv, raceFlagDiv, windStatusDiv }; - fleetColor.getStyle().setBackgroundColor(fleet.getColor().getAsHtml()); + if(fleet.getColor() != null) { + fleetColor.getStyle().setBackgroundColor(fleet.getColor().getAsHtml()); + } raceName.setInnerText(raceColumn.getName()); - raceName2.setInnerText(raceColumn.getName()); - -// {#if race.isFinished} -// -// {/if race.isFinished} -// {#if race.isLive} -// {race.startTime} -// {/if race.isLive} -// {#if race.isScheduled} -// {race.startTime} -// {/if race.isScheduled} - +// raceName2.setInnerText(raceColumn.getName()); + + registerEvents(); updateUI(); } + private void registerEvents() { + Event.sinkEvents(analyzeRaceLink, Event.ONCLICK); + Event.setEventListener(analyzeRaceLink, new EventListener() { + @Override + public void onBrowserEvent(Event event) { + openRaceViewer(); + } + }); + + Event.sinkEvents(watchRaceLink, Event.ONCLICK); + Event.setEventListener(watchRaceLink, new EventListener() { + @Override + public void onBrowserEvent(Event event) { + openRaceViewer(); + } + }); + } + private SimpleRaceStates getSimpleRaceState() { SimpleRaceStates simpleRaceState = SimpleRaceStates.NOT_TRACKED; @@ -142,16 +151,6 @@ public class EventRegattaRacesRace extends Composite { } } - @UiHandler("watchRaceLink") - public void watchRaceClicked(ClickEvent e) { - openRaceViewer(); - } - - @UiHandler("analyzeRaceLink") - public void analyzeRaceClicked(ClickEvent e) { - openRaceViewer(); - } - private void hideElement(Element el) { el.getStyle().setVisibility(Visibility.HIDDEN); el.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/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 65de9704a71..9837731999b 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 @@ -5,15 +5,15 @@ - -
+
@@ -44,12 +44,11 @@
- +
- +
-
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 2a6ac69e8b2..a5cf124368a 100644 --- 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 @@ -1,15 +1,13 @@ package com.sap.sailing.gwt.home.client.place.event.regattaraces; import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.SpanElement; +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.Composite; import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.gwt.home.client.place.event.EventPageNavigator; import com.sap.sailing.gwt.ui.shared.RaceGroupSeriesDTO; -import com.sap.sse.gwt.client.player.Timer; public class EventRegattaRacesRaceday extends Composite { private static EventRegattaRacesRacedayUiBinder uiBinder = GWT.create(EventRegattaRacesRacedayUiBinder.class); @@ -17,18 +15,13 @@ public class EventRegattaRacesRaceday extends Composite { interface EventRegattaRacesRacedayUiBinder extends UiBinder { } - @UiField SpanElement raceDayDate; + @UiField DivElement raceDayDate; @UiField HTMLPanel racesPanel; - - private final Timer timerForClientServerOffset; - private final EventPageNavigator pageNavigator; - public EventRegattaRacesRaceday(RaceGroupSeriesDTO series, Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { - this.timerForClientServerOffset = timerForClientServerOffset; - this.pageNavigator = pageNavigator; - + public EventRegattaRacesRaceday(RaceGroupSeriesDTO series) { EventRegattaRacesResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); + + raceDayDate.setInnerText("A date to fill in here"); } - } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesRaceday.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesRaceday.ui.xml index 7e633f4059b..e72bd5b8626 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesRaceday.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesRaceday.ui.xml @@ -7,7 +7,7 @@ -
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/main.css b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/main.css index 7637b05043d..b5b50459cb7 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/main.css +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/main.css @@ -222,13 +222,13 @@ figcaption { text-transform: uppercase; vertical-align: middle; } -.label[data-labeltype='ess'] { +.label[data-labeltype='group1'] { background-color: #a71724; } -.label[data-labeltype='worldcup'] { +.label[data-labeltype='group2'] { background-color: #c79242; } -.label[data-labeltype='bundesliga'] { +.label[data-labeltype='group3'] { background-color: #005fa7; } .morelink { From 0600c185069ce200fb1e67404ee13ca5627e7ce2 Mon Sep 17 00:00:00 2001 From: Jens Rommel Date: Wed, 16 Jul 2014 20:10:42 +0200 Subject: [PATCH 128/323] Ported Countdown behavior to GWT. Still open: - ugly issue with Swiper cloning the event stages, making all countdown updates go to nirvana - i18n of time units (DAYS --> Tage) - plural/singular of time units --- .../client/place/start/StartActivity.java | 4 +- .../shared/stage/LiveEventStageTeaser.java | 4 +- .../shared/stage/PopularEventStageTeaser.java | 4 +- .../home/client/shared/stage/StageTeaser.java | 58 ++++++++- .../client/shared/stage/StageTeaser.ui.xml | 14 +-- .../stage/UpcomingEventStageTeaser.java | 6 - .../gwt/ui/common/client/Countdown.java | 119 ++++++++++++++++++ 7 files changed, 189 insertions(+), 20 deletions(-) create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/Countdown.java 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 9f9fef4b4c9..912c438e8b3 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 @@ -46,14 +46,14 @@ public class StartActivity extends AbstractActivity { Date now = new Date(); int currentYear = now.getYear(); final int MAX_STAGE_EVENTS = 2; - final long TWO_WEEK_IN_MS = 1000 * 60 * 60 * 24 * 7; + final long FOUR_WEEK_IN_MS = 4L * (1000 * 60 * 60 * 24 * 7); for(EventDTO event: events) { // ignore events with no start and end date 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() < TWO_WEEK_IN_MS) { + } 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) { recentEventsOfSameYear.add(event); 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 544d6220826..44400c5dcc5 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 @@ -13,8 +13,8 @@ public class LiveEventStageTeaser extends StageTeaser { title.setInnerText(event.getName()); subtitle.setInnerText(event.venue.getName()); - countDown.getStyle().setDisplay(Display.NONE); - countDown.getStyle().setVisibility(Visibility.HIDDEN); + countdown.getStyle().setDisplay(Display.NONE); + countdown.getStyle().setVisibility(Visibility.HIDDEN); bandCount.setAttribute("data-bandcount", "2"); 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 a99430b743f..32c688cfd56 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 @@ -13,8 +13,8 @@ public class PopularEventStageTeaser extends StageTeaser { title.setInnerText(event.getName()); subtitle.setInnerText(event.venue.getName()); - countDown.getStyle().setDisplay(Display.NONE); - countDown.getStyle().setVisibility(Visibility.HIDDEN); + countdown.getStyle().setDisplay(Display.NONE); + countdown.getStyle().setVisibility(Visibility.HIDDEN); bandCount.setAttribute("data-bandcount", "1"); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.java index 97776f87fc1..8bad5edda42 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.java @@ -3,18 +3,31 @@ package com.sap.sailing.gwt.home.client.shared.stage; 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.dom.client.Style.Display; +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.Composite; import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; +import com.sap.sailing.gwt.ui.common.client.Countdown; +import com.sap.sailing.gwt.ui.common.client.Countdown.CountdownListener; +import com.sap.sailing.gwt.ui.common.client.Countdown.Time; import com.sap.sailing.gwt.ui.shared.EventDTO; public abstract class StageTeaser extends Composite { @UiField DivElement bandCount; @UiField SpanElement subtitle; @UiField SpanElement title; - @UiField DivElement countDown; + @UiField DivElement countdown; + @UiField DivElement countdownMajor; + @UiField DivElement countdownMajorValue; + @UiField DivElement countdownMajorUnit; + @UiField DivElement countdownMinor; + @UiField DivElement countdownMinorValue; + @UiField DivElement countdownMinorUnit; @UiField HTMLPanel stageTeaserBandsPanel; @UiField DivElement teaserImage; @@ -33,5 +46,48 @@ public abstract class StageTeaser extends Composite { String backgroundImage = "url(" + stageImageUrl + ")"; teaserImage.getStyle().setBackgroundImage(backgroundImage); + + if (event.startDate != null) { + TimePoint eventStart = new MillisecondsTimePoint(event.startDate); + CountdownListener countdownListener = new CountdownListener() { + + @Override + public void changed(Time major, Time minor) { + updateCountdown(major, minor); + } + + }; + new Countdown(eventStart, countdownListener); + } + } + + private void updateCountdown(Time major, Time minor) { + if (major == null && minor == null) { + countdownMajorValue.setInnerHTML(null); + countdownMajorUnit.setInnerHTML(null); + countdownMinorValue.setInnerHTML(null); + countdownMinorUnit.setInnerHTML(null); + countdown.getStyle().setDisplay(Display.NONE); + countdown.getStyle().setVisibility(Visibility.HIDDEN); + } else { + if (major != null) { + countdownMajorValue.setInnerHTML(String.valueOf(major.value)); + countdownMajorUnit.setInnerHTML(String.valueOf(major.unit)); + } else { + countdownMajorValue.setInnerHTML(null); + countdownMajorUnit.setInnerHTML(null); + countdownMajor.getStyle().setDisplay(Display.NONE); + countdownMajor.getStyle().setVisibility(Visibility.HIDDEN); + } + if (minor != null) { + countdownMinorValue.setInnerHTML(String.valueOf(minor.value)); + countdownMinorUnit.setInnerHTML(String.valueOf(minor.unit)); + } else { + countdownMinorValue.setInnerHTML(null); + countdownMinorUnit.setInnerHTML(null); + countdownMinor.getStyle().setDisplay(Display.NONE); + countdownMinor.getStyle().setVisibility(Visibility.HIDDEN); + } + } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.ui.xml index 3f4d546d56b..087b671fc2c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.ui.xml @@ -17,14 +17,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 ebe38edcfcc..c3776d401ac 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 @@ -11,12 +11,6 @@ public class UpcomingEventStageTeaser extends StageTeaser { title.setInnerText(event.getName()); subtitle.setInnerText(event.venue.getName()); - countDown.setAttribute("data-starttime", event.startDate.toString()); - countDown.setAttribute("data-days", ""); - countDown.setAttribute("data-hours", ""); - countDown.setAttribute("data-minutes", ""); - countDown.setAttribute("data-seconds", ""); - bandCount.setAttribute("data-bandcount", "1"); stageTeaserBandsPanel.add(new StageTeaserBand(event, placeNavigator)); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/Countdown.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/Countdown.java new file mode 100644 index 00000000000..11d356cf5bd --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/Countdown.java @@ -0,0 +1,119 @@ +package com.sap.sailing.gwt.ui.common.client; + +import com.google.gwt.user.client.Timer; +import com.sap.sailing.domain.common.Duration; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; + +/** + * A countdown notifying about changes in the two most significant time units. I.e. days+hours or hours+minutes or minutes+seconds or seconds only. + */ +public class Countdown { + + private static final int UPDATE_INTERVALL_MILLIS = 1000; + private final Timer timer; + private final TimePoint zeroDate; + private final CountdownListener countdownListener; + + private Time previousMajor; + private Time previousMinor; + + public enum Unit {DAYS, HOURS, MINUTES, SECONDS} + + public static class Time { + public Time(long value, Unit unit) { + this.value = value; + this.unit = unit; + } + public final long value; + public final Unit unit; + + public static boolean equal(Time t1, Time t2) { + if (t1 == null) { + if (t2 == null) { + return true; + } else { + return false; + } + } else { + if (t2 == null) { + return false; + } else { + return t1.value == t2.value && t1.unit == t2.unit; + } + } + } + } + + public interface CountdownListener { + void changed(Time major, Time minor); + } + + public Countdown(TimePoint zeroDate, CountdownListener countdownListener) { + this.zeroDate = zeroDate; + this.countdownListener = countdownListener; + update(); + timer = new Timer() { + + @Override + public void run() { + update(); + } + + }; + timer.scheduleRepeating(UPDATE_INTERVALL_MILLIS); + } + + private void update() { + Duration diff = MillisecondsTimePoint.now().until(zeroDate); + double asDays = diff.asDays(); + long daysRemaining = (long) asDays; + double asHours = diff.asHours(); + long hoursRemaining = (long) (asHours - daysRemaining * 24.0); + double asMinutes = diff.asMinutes(); + long minutesRemaining = (long) (asMinutes - hoursRemaining * 60.0); + double asSeconds = diff.asSeconds(); + long secondsRemaining = (long) (asSeconds - minutesRemaining * 60.0); + + Time major = null; + Time minor = null; + if (daysRemaining > 0) { + major = new Time(daysRemaining, Unit.DAYS); + } + if (hoursRemaining > 0) { + Time time = new Time(hoursRemaining, Unit.HOURS); + if (major == null) { + major = time; + } else { + minor = time; + } + } + if (minutesRemaining > 0) { + Time time = new Time(minutesRemaining, Unit.MINUTES); + if (major == null) { + major = time; + } else if (major.unit == Unit.HOURS) { + minor = time; + } + } + if (secondsRemaining > 0) { + Time time = new Time(secondsRemaining, Unit.SECONDS); + if (major == null) { + major = time; + } else if (major.unit == Unit.MINUTES) { + minor = time; + } + } + + if (!Time.equal(previousMajor, major) || !Time.equal(previousMinor, minor)) { + countdownListener.changed(major, minor); + previousMajor = major; + previousMinor = minor; + } + } + + public void cancel() { + timer.cancel(); + } + +} From ec91fe9c37147c808662389b44ef3cdd8e8437bf Mon Sep 17 00:00:00 2001 From: fmittag Date: Wed, 16 Jul 2014 20:31:19 +0200 Subject: [PATCH 129/323] updated all media.css files and the header partial --- .../gwt/home/client/shared/header/Header.css | 98 ++++-- .../home/client/shared/header/Header.ui.xml | 46 +-- .../client/shared/header/HeaderResources.java | 11 +- .../sailing/gwt/home/images/logo-small@2x.png | Bin 0 -> 6388 bytes .../com/sap/sailing/gwt/home/large.css | 282 ++++++++---------- .../com/sap/sailing/gwt/home/media.css | 65 ++-- .../com/sap/sailing/gwt/home/medium.css | 238 ++++++--------- 7 files changed, 340 insertions(+), 400 deletions(-) create mode 100644 java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/images/logo-small@2x.png diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/Header.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/Header.css index 29967a200b4..513a03a0564 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/Header.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/Header.css @@ -1,60 +1,118 @@ -/** - * Website header bar - */ .siteheader { - font-size: 16px; + font-size: 15px; font-size: 1rem; background: #333; border-bottom: 5px solid #f0ab00; - height: 4.0625em; - line-height: 3.75em; + min-height: 4.333333333333333em; + line-height: 4em; width: 100%; + overflow: hidden; +} +.siteheader_full { + border-width: 3.333333333333333em; + height: 7.333333333333333em; } .logo { - margin: 0.625em 0; + margin: 0.8em 0; float: left; - width: 210px; - height: 40px; +} +.logotitle { + display: inline; + height: 4.333333333333333em; + line-height: 4.333333333333333em; + color: #f6f9fc; } .sitenavigation { float: right; + text-align: right; + width: 100%; + height: 4.333333333333333em; } .sitenavigation_link { display: inline-block; - font-size: 0.875em; + height: 4.642857142857143em; + line-height: 4.642857142857143em; + font-size: 0.933333333333333em; font-weight: 600; color: #f6f9fc; text-transform: uppercase; - padding: 0 2.1875em; + padding: 0 2.5em; } .sitenavigation_link:hover, .sitenavigation_link:focus { text-decoration: none; + background-color: #2d2d2d; } .sitenavigation_linkactive, .sitenavigation_link:hover, .sitenavigation_link:focus { color: #f0ab00; } -.sitenavigation_linksearch { - padding: 0 0 0 2.1875em; - font-weight: inherit; +.sitenavigation_search { + display: inline-block; + height: 4.333333333333333em; + font-weight: 600; + color: #f6f9fc; + text-transform: uppercase; + text-align: right; +} +.sitenavigationexpanded .searchsmall { + width: 14.666666666666666em; +} +.sitenavigationexpanded .searchsmall .search_textfield { + opacity: 1; + -ms-filter: none; + filter: none; + pointer-events: initial; + -webkit-box-shadow: -10px 0 10px rgba(51,51,51,0.9); + box-shadow: -10px 0 10px rgba(51,51,51,0.9); } .search { - font-size: 16px; + font-size: 15px; font-size: 1rem; display: inline-block; - width: 14.875em; + width: 100%; + margin-top: 0.2em; + position: relative; } .search_textfield { + width: 100%; + padding: 0.785714285714286em 2.857142857142857em 0.785714285714286em 1.071428571428571em; background: #222; border: 1px solid #666; - padding: 0.9375em 2.8125em 0.9375em 0.9375em; + font-size: 0.933333333333333em; color: #fff; - width: 14.6875em; + outline: none; } .search_button { position: absolute; - margin-left: 12.4375em; - margin-top: 1.125em; + right: 1.5em; + top: 50%; + margin-top: -0.666666666666667em; +} +.search_button:hover, +.search_button:focus { + background-color: transparent; +} +.searchsmall { + position: absolute; + right: 2em; + width: 0; +} +.searchsmall .search_textfield { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: literal("alpha(opacity=0)"); + pointer-events: none; + -webkit-transition: height 0.1s ease; + -moz-transition: height 0.1s ease; + -o-transition: height 0.1s ease; + -ms-transition: height 0.1s ease; + transition: height 0.1s ease; +} +.searchsmall .search_button { + right: 0.666666666666667em; +} +.submit { + font-size: inherit; } 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 1204cd6f979..d63fc69e7ed 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 @@ -8,28 +8,32 @@
-
- +
+ +

Sailing Analytics

-
- -
-
+ +
+ +
+ +
+ + +
+
+
\ 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/header/HeaderResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/HeaderResources.java index 7c65a600e30..11c2c1061d4 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/HeaderResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/HeaderResources.java @@ -13,17 +13,22 @@ public interface HeaderResources extends ClientBundle { public interface LocalCss extends CssResource { String siteheader(); + String siteheader_full(); String logo(); + String logotitle(); String sitenavigation(); String sitenavigation_link(); String sitenavigation_linkactive(); - String sitenavigation_linksearch(); - String search(); + String sitenavigation_search(); + String sitenavigationexpanded(); + String searchsmall(); String search_textfield(); + String search(); String search_button(); + String submit(); } - @Source("com/sap/sailing/gwt/home/images/logo@2x.png") + @Source("com/sap/sailing/gwt/home/images/logo-small@2x.png") ImageResource logo(); @Source("com/sap/sailing/gwt/home/images/search-icon.png") diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/images/logo-small@2x.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/images/logo-small@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..e5d2daf91f3962deb5be2991141b8145186e982a GIT binary patch literal 6388 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 NVW?-STdnOB^*{MHr`-Sm literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/large.css b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/large.css index bd645540ac3..d11efc56638 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/large.css +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/large.css @@ -1,167 +1,3 @@ -.column, -.columns { - float: left; - padding: 0 0.9375rem; - position: relative; - width: 100%; -} -.column img, -.columns img { - max-width: 100%; - height: auto; - ms-interpolation-mode: bicubic; -} - -.column.smallcentered, -.columns.smallcentered { -} -.column.smalluncentered, -.columns.smalluncentered { -} - -.column.mediumcentered, -.columns.mediumcentered { -} -.column.mediumuncentered, -.columns.mediumuncentered { -} - -.column.largecentered, -.columns.largecentered { - margin-left: auto; - margin-right: auto; - float: none; -} -.column.largeuncentered, -.columns.largeuncentered { - margin-left: 0; - margin-right: 0; - float: left !important; -} - -.small1 { - width: 8.3333333333333%; -} -.small2 { - width: 16.6666666666667%; -} -.small3 { - width: 25%; -} -.small4 { - width: 33.3333333333333%; -} -.small5 { - width: 41.6666666666667%; -} -.small6 { - width: 50%; -} -.small7 { - width: 58.3333333333333%; -} -.small8 { - width: 66.6666666666667%; -} -.small9 { - width: 75%; -} -.small10 { - width: 83.3333333333333%; -} -.small11 { - width: 91.6666666666667%; -} -.small12 { - width: 100%; -} -.smalloffset0 { - margin-left: 0% !important; -} -.smalloffset1 { - margin-left: 8.3333333333333% !important; -} -.smalloffset2 { - margin-left: 16.6666666666667% !important; -} -.smalloffset3 { - margin-left: 25% !important; -} -.smalloffset4 { - margin-left: 33.3333333333333% !important; -} -.smalloffset5 { - margin-left: 41.6666666666667% !important; -} -.smalloffset6 { - margin-left: 50% !important; -} -.smalloffset7 { - margin-left: 58.3333333333333% !important; -} -.smalloffset8 { - margin-left: 66.6666666666667% !important; -} -.smalloffset9 { - margin-left: 75% !important; -} -.smalloffset10 { - margin-left: 83.3333333333333% !important; -} -.smalloffset11 { - margin-left: 91.6666666666667% !important; -} - -.medium1 { -} -.medium2 { -} -.medium3 { -} -.medium4 { -} -.medium5 { -} -.medium6 { -} -.medium7 { -} -.medium8 { -} -.medium9 { -} -.medium10 { -} -.medium11 { -} -.medium12 { -} -.mediumoffset0 { -} -.mediumoffset1 { -} -.mediumoffset2 { -} -.mediumoffset3 { -} -.mediumoffset4 { -} -.mediumoffset5 { -} -.mediumoffset6 { -} -.mediumoffset7 { -} -.mediumoffset8 { -} -.mediumoffset9 { -} -.mediumoffset10 { -} -.mediumoffset11 { -} - - .large1 { width: 8.3333333333333%; } @@ -252,4 +88,122 @@ .hideonlarge { display: none !important; } +.column.largecentered, +.columns.largecentered { + margin-left: auto; + margin-right: auto; + float: none; +} +.column.largeuncentered, +.columns.largeuncentered { + margin-left: 0; + margin-right: 0; + float: left !important; +} +.smallcentered { +} +.smalluncentered { +} +.mediumcentered { +} +.mediumuncentered { +} + +.small1 { +} +.small2 { +} +.small3 { +} +.small4 { +} +.small5 { +} +.small6 { +} +.small7 { +} +.small8 { +} +.small9 { +} +.small10 { +} +.small11 { +} +.small12 { +} +.smalloffset0 { +} +.smalloffset1 { +} +.smalloffset2 { +} +.smalloffset3 { +} +.smalloffset4 { +} +.smalloffset5 { +} +.smalloffset6 { +} +.smalloffset7 { +} +.smalloffset8 { +} +.smalloffset9 { +} +.smalloffset10 { +} +.smalloffset11 { +} + +.medium1 { +} +.medium2 { +} +.medium3 { +} +.medium4 { +} +.medium5 { +} +.medium6 { +} +.medium7 { +} +.medium8 { +} +.medium9 { +} +.medium10 { +} +.medium11 { +} +.medium12 { +} +.mediumoffset0 { +} +.mediumoffset1 { +} +.mediumoffset2 { +} +.mediumoffset3 { +} +.mediumoffset4 { +} +.mediumoffset5 { +} +.mediumoffset6 { +} +.mediumoffset7 { +} +.mediumoffset8 { +} +.mediumoffset9 { +} +.mediumoffset10 { +} +.mediumoffset11 { +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/media.css b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/media.css index ec079b36ddb..598c991e761 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/media.css +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/media.css @@ -1,7 +1,7 @@ .column, .columns { float: left; - padding: 0 0.9375rem; + padding: 0 1rem; position: relative; width: 100%; } @@ -11,48 +11,6 @@ height: auto; ms-interpolation-mode: bicubic; } - -.column, -.columns { - float: left; - padding: 0 0.9375rem; - position: relative; - width: 100%; -} -.column img, -.columns img { - max-width: 100%; - height: auto; - ms-interpolation-mode: bicubic; -} - -.column.smallcentered, -.columns.smallcentered { -} -.column.smalluncentered, -.columns.smalluncentered { -} - -.column.mediumcentered, -.columns.mediumcentered { - margin-left: auto; - margin-right: auto; - float: none; -} -.column.mediumuncentered, -.columns.mediumuncentered { - margin-left: 0; - margin-right: 0; - float: left !important; -} - -.column.largecentered, -.columns.largecentered { -} -.column.largeuncentered, -.columns.largeuncentered { -} - .small1 { width: 8.3333333333333%; } @@ -143,6 +101,27 @@ .hideonlarge { display: inherit !important; } +.column.smallcentered, +.columns.smallcentered { + margin-left: auto; + margin-right: auto; + float: none; +} +.column.smalluncentered, +.columns.smalluncentered { + margin-left: 0; + margin-right: 0; + float: left !important; +} + +.largecentered { +} +.largeuncentered { +} +.mediumcentered { +} +.mediumuncentered { +} .medium1 { } diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/medium.css b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/medium.css index 45f79882e46..7634ab13486 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/medium.css +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/medium.css @@ -1,132 +1,3 @@ -.column, -.columns { - float: left; - padding: 0 0.9375rem; - position: relative; - width: 100%; -} -.column img, -.columns img { - max-width: 100%; - height: auto; - ms-interpolation-mode: bicubic; -} - -.column, -.columns { - float: left; - padding: 0 0.9375rem; - position: relative; - width: 100%; -} -.column img, -.columns img { - max-width: 100%; - height: auto; - ms-interpolation-mode: bicubic; -} - - -.column.smallcentered, -.columns.smallcentered { -} -.column.smalluncentered, -.columns.smalluncentered { -} - -.column.mediumcentered, -.columns.mediumcentered { - margin-left: auto; - margin-right: auto; - float: none; -} -.column.mediumuncentered, -.columns.mediumuncentered { - margin-left: 0; - margin-right: 0; - float: left !important; -} - -.column.largecentered, -.columns.largecentered { -} -.column.largeuncentered, -.columns.largeuncentered { -} - -.small1 { - width: 8.3333333333333%; -} -.small2 { - width: 16.6666666666667%; -} -.small3 { - width: 25%; -} -.small4 { - width: 33.3333333333333%; -} -.small5 { - width: 41.6666666666667%; -} -.small6 { - width: 50%; -} -.small7 { - width: 58.3333333333333%; -} -.small8 { - width: 66.6666666666667%; -} -.small9 { - width: 75%; -} -.small10 { - width: 83.3333333333333%; -} -.small11 { - width: 91.6666666666667%; -} -.small12 { - width: 100%; -} -.smalloffset0 { - margin-left: 0% !important; -} -.smalloffset1 { - margin-left: 8.3333333333333% !important; -} -.smalloffset2 { - margin-left: 16.6666666666667% !important; -} -.smalloffset3 { - margin-left: 25% !important; -} -.smalloffset4 { - margin-left: 33.3333333333333% !important; -} -.smalloffset5 { - margin-left: 41.6666666666667% !important; -} -.smalloffset6 { - margin-left: 50% !important; -} -.smalloffset7 { - margin-left: 58.3333333333333% !important; -} -.smalloffset8 { - margin-left: 66.6666666666667% !important; -} -.smalloffset9 { - margin-left: 75% !important; -} -.smalloffset10 { - margin-left: 83.3333333333333% !important; -} -.smalloffset11 { - margin-left: 91.6666666666667% !important; -} - .medium1 { width: 8.3333333333333%; } @@ -199,6 +70,94 @@ .mediumoffset11 { margin-left: 91.6666666666667% !important; } +.showonsmall { + display: none !important; +} +.hideonsmall { + display: inherit !important; +} +.showonmedium { + display: inherit !important; +} +.hideonmedium { + display: none !important; +} +.showonlarge { + display: none !important; +} +.hideonlarge { + display: inherit !important; +} +.column.mediumcentered, +.columns.mediumcentered { + margin-left: auto; + margin-right: auto; + float: none; +} +.column.mediumuncentered, +.columns.mediumuncentered { + margin-left: 0; + margin-right: 0; + float: left !important; +} + +.smallcentered { +} +.smalluncentered { +} +.largecentered { +} +.largeuncentered { +} + +.small1 { +} +.small2 { +} +.small3 { +} +.small4 { +} +.small5 { +} +.small6 { +} +.small7 { +} +.small8 { +} +.small9 { +} +.small10 { +} +.small11 { +} +.small12 { +} +.smalloffset0 { +} +.smalloffset1 { +} +.smalloffset2 { +} +.smalloffset3 { +} +.smalloffset4 { +} +.smalloffset5 { +} +.smalloffset6 { +} +.smalloffset7 { +} +.smalloffset8 { +} +.smalloffset9 { +} +.smalloffset10 { +} +.smalloffset11 { +} .large1 { } @@ -247,23 +206,4 @@ .largeoffset10 { } .largeoffset11 { -} - -.showonsmall { - display: none !important; -} -.hideonsmall { - display: inherit !important; -} -.showonmedium { - display: inherit !important; -} -.hideonmedium { - display: none !important; -} -.showonlarge { - display: none !important; -} -.hideonlarge { - display: inherit !important; -} +} \ No newline at end of file From 12b6edc87308682474545f19c29a10c349adab48 Mon Sep 17 00:00:00 2001 From: Jens Rommel Date: Wed, 16 Jul 2014 20:35:16 +0200 Subject: [PATCH 130/323] Workaround for the countdown update bug caused by Swiper's cloning: - switch off loop mode for Swiper --> no cloning --> reliable updates --- .../sailing/gwt/home/client/shared/mainmedia/MainMedia.java | 2 +- .../com/sap/sailing/gwt/home/client/shared/stage/Stage.java | 2 +- .../src/main/java/com/sap/sailing/gwt/idangerous/Swiper.java | 4 ++-- .../java/com/sap/sailing/gwt/ui/common/client/Countdown.java | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) 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 0b7c4303670..2caaee1a9da 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 @@ -77,7 +77,7 @@ public class MainMedia extends Composite { mediaSlides.add(imageContainer); } - this.swiper = Swiper.createWithDefaultOptions(STYLES.media_swipecontainer(), STYLES.media_swipewrapper(), STYLES.media_swiperslide()); + this.swiper = Swiper.createWithDefaultOptions(STYLES.media_swipecontainer(), STYLES.media_swipewrapper(), STYLES.media_swiperslide(), true); } 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 e4e873efe35..c2a5c73bb88 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 @@ -69,7 +69,7 @@ public class Stage extends Composite { this.swiper = Swiper.createWithDefaultOptions(StageResources.INSTANCE.css().stage_teasers(), StageResources.INSTANCE.css().swiperwrapper(), - StageResources.INSTANCE.css().swiperslide()); + StageResources.INSTANCE.css().swiperslide(), false); } @UiHandler("nextStageTeaserLink") 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 ec70192040d..99c0edc2281 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 @@ -10,9 +10,9 @@ public final class Swiper extends JavaScriptObject { protected Swiper() {}; - public static native Swiper createWithDefaultOptions(String containerClass, String wrapperClass, String slideClass) /*-{ + public static native Swiper createWithDefaultOptions(String containerClass, String wrapperClass, String slideClass, boolean loop) /*-{ var options = { - loop: true, + loop: loop, wrapperClass: wrapperClass, slideClass: slideClass }; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/Countdown.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/Countdown.java index 11d356cf5bd..53ff858f885 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/Countdown.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/Countdown.java @@ -96,7 +96,7 @@ public class Countdown { minor = time; } } - if (secondsRemaining > 0) { + if (secondsRemaining >= 0) { Time time = new Time(secondsRemaining, Unit.SECONDS); if (major == null) { major = time; From 3fa42300a086c6289897c56afd69f53b638cde1e Mon Sep 17 00:00:00 2001 From: fmittag Date: Wed, 16 Jul 2014 20:55:34 +0200 Subject: [PATCH 131/323] fixed copyright sign in footer --- .../sap/sailing/gwt/home/client/i18n/TextMessages.properties | 2 +- .../sap/sailing/gwt/home/client/i18n/TextMessages_de.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 37ac433fe0e..8e11127cf20 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 @@ -82,7 +82,7 @@ eventRegattaRacesLegendWind=Wind Data eventRegattaRacesLegendVideo=Video Stream eventRegattaRacesLegendAudio=Audio Stream eventSponsorsTitle=Event Sponsorships -footerCopyright=© 2014 SAP Sailing Analytics +footerCopyright=\u00A9 2014 SAP Sailing Analytics footerLanguage=SAP Sailing offered in footerLegal=Legal Disclosure footerPrivacy=Privacy 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 3dd98ea6b40..799ed153233 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 @@ -82,7 +82,7 @@ eventRegattaRacesLegendWind=Wind Data eventRegattaRacesLegendVideo=Video Stream eventRegattaRacesLegendAudio=Audio Stream eventSponsorsTitle=Event Sponsorships -footerCopyright=© 2014 SAP Sailing Analytics +footerCopyright=\u00A9 2014 SAP Sailing Analytics footerLanguage=SAP Sailing offered in footerLegal=Legal Disclosure footerPrivacy=Privacy From 74aab09c6f08a262ca869b5bf2f6fdfaadb658e4 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jul 2014 21:31:25 +0200 Subject: [PATCH 132/323] more replication logging for FINE level --- .../server/replication/impl/ReplicationServiceImpl.java | 9 ++++++++- .../sap/sailing/server/replication/impl/Replicator.java | 6 ++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java index 43d2bf01abb..55410ef4183 100644 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java @@ -33,6 +33,7 @@ import com.sap.sailing.server.RacingEventServiceOperation; import com.sap.sailing.server.replication.ReplicationMasterDescriptor; import com.sap.sailing.server.replication.ReplicationService; import com.sap.sailing.util.BuildVersion; +import com.sap.sse.common.Util; import com.sap.sse.common.Util.Pair; /** @@ -259,15 +260,18 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec sendingTask = new TimerTask() { @Override public void run() { + logger.fine("Running timer task to send "+outboundBuffer.size()+" messages"); final Iterable, byte[]>> listToSend; synchronized (outboundBuffer) { + logger.fine("Copying "+outboundBuffer.size()+" messages to new list"); listToSend = new ArrayList<>(outboundBuffer); outboundBuffer.clear(); sendingTask = null; } try { broadcastOperations(listToSend); - } catch (IOException e) { + logger.fine("Successfully handed "+Util.size(listToSend)+" messages to broadcaster"); + } catch (Exception e) { logger.log(Level.SEVERE, "Error trying to replicate the following operations: "+listToSend, e); } } @@ -281,6 +285,7 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec } private void broadcastOperations(Iterable, byte[]>> listToSend) throws IOException { + logger.fine("broadcasting "+Util.size(listToSend)+" operations"); ByteArrayOutputStream buf = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(buf); List> classes = new ArrayList<>(); @@ -294,7 +299,9 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec // copy serialized operations into message if (masterChannel != null) { final int queueMessageSize = buf.size(); + logger.fine("buffer to broadcast has "+queueMessageSize+" bytes ("+(queueMessageSize/1024/1024)+"MB)"); masterChannel.basicPublish(exchangeName, /* routingKey */"", /* properties */null, buf.toByteArray()); + logger.fine("successfully published "+queueMessageSize+" bytes"); replicationInstancesManager.log(classes,queueMessageSize); } } diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java index 59b54696372..9ddd5f95bec 100755 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java @@ -116,14 +116,16 @@ public class Replicator implements Runnable { } catch (Exception e) { _queue = null; } + final boolean logsFine = logger.isLoggable(Level.FINE); while (!isBeingStopped()) { try { Delivery delivery = consumer.nextDelivery(); messageCount++; if (_queue != null) { - if (messageCount % 10000l == 0) { + if (logsFine || messageCount % 10l == 0) { try { - logger.info("Inbound replication queue size: "+((BlockingQueue) _queue.get(consumer)).size()); + logger.log(messageCount%10l==0 ? Level.INFO : Level.FINE, + "Inbound replication queue size: "+((BlockingQueue) _queue.get(consumer)).size()); } catch (Exception e) { // it didn't work; but it's a log message only... logger.info("Received another 10000 replication messages"); From 8f1c888f061792420b2c9b01e26661b545c48d2c Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jul 2014 23:00:09 +0200 Subject: [PATCH 133/323] write buffered operations into stream directly, avoiding copying the potentially huge message buffer; more replication logging still --- .../sailing/domain/base/CompetitorStore.java | 11 +++ .../impl/TransientCompetitorStoreImpl.java | 19 ++++ .../gwt/ui/adminconsole/ReplicationPanel.java | 6 ++ .../sailing/gwt/ui/client/StringMessages.java | 1 + .../gwt/ui/client/StringMessages.properties | 3 +- .../ui/client/StringMessages_de.properties | 3 +- .../ui/client/StringMessages_ru.properties | 3 +- .../test/CompetitorReplicationTest.java | 2 +- .../impl/ReplicationServiceImpl.java | 95 ++++++++++++------- .../server/replication/impl/Replicator.java | 28 +++--- .../server/impl/RacingEventServiceImpl.java | 11 ++- 11 files changed, 131 insertions(+), 51 deletions(-) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CompetitorStore.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CompetitorStore.java index 31b1170f772..112c1531213 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CompetitorStore.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CompetitorStore.java @@ -15,6 +15,9 @@ import com.sap.sailing.domain.common.dto.CompetitorDTO; * */ public interface CompetitorStore extends CompetitorFactory { + public interface CompetitorUpdateListener { + void competitorUpdated(Competitor competitor); + } /** * If a valid competitor is returned and the caller has information available that could be used to update the competitor, @@ -62,4 +65,12 @@ public interface CompetitorStore extends CompetitorFactory { Competitor updateCompetitor(String idAsString, String newName, Color newDisplayColor, String newSailId, Nationality newNationality); CompetitorDTO convertToCompetitorDTO(Competitor c); + + /** + * Listeners added here are notified whenever {@link #updateCompetitor(String, String, Color, String, Nationality)} is called + * for any competitor in this store. + */ + void addCompetitorUpdateListener(CompetitorUpdateListener listener); + + void removeCompetitorUpdateListener(CompetitorUpdateListener listener); } 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 84d77198394..a27469b908f 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 @@ -4,6 +4,7 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -25,6 +26,7 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab private static final long serialVersionUID = -4198298775476586931L; private final Map competitorCache; private final Map competitorsByIdAsString; + private transient Set listeners; /** * The competitors contained in this map will have their changeable properties @@ -43,13 +45,25 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab competitorsByIdAsString = new HashMap(); competitorsToUpdateDuringGetOrCreate = new HashSet(); weakCompetitorDTOCache = new WeakHashMap(); + listeners = Collections.synchronizedSet(new HashSet()); } private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.defaultReadObject(); weakCompetitorDTOCache = new WeakHashMap(); + listeners = Collections.synchronizedSet(new HashSet()); } + @Override + public void addCompetitorUpdateListener(CompetitorUpdateListener listener) { + listeners.add(listener); + } + + @Override + public void removeCompetitorUpdateListener(CompetitorUpdateListener listener) { + listeners.remove(listener); + } + private Competitor createCompetitor(Serializable id, String name, Color displayColor, DynamicTeam team, DynamicBoat boat) { Competitor result = new CompetitorImpl(id, name, displayColor, team, boat); addNewCompetitor(id, result); @@ -179,6 +193,11 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab LockUtil.unlockAfterWrite(lock); } } + synchronized (listeners) { + for (CompetitorUpdateListener listener : listeners) { + listener.competitorUpdated(competitor); + } + } return competitor; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java index eed1683ec07..41ecaf78efd 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java @@ -236,12 +236,18 @@ public class ReplicationPanel extends FlowPanel { registeredReplicas.setWidget(i, 1, new Label(stringMessages.totalSize())); registeredReplicas.setWidget(i, 2, new Label(""+replica.getNumberOfBytesSent()+"B ("+replica.getNumberOfBytesSent()/1024.0/1024.0+"MB)")); i++; + long totalNumberOfOperations = 0; for (Map.Entry e : replica.getOperationCountByOperationClassName().entrySet()) { registeredReplicas.insertRow(i); registeredReplicas.setWidget(i, 1, new Label(e.getKey())); registeredReplicas.setWidget(i, 2, new Label(e.getValue().toString())); + totalNumberOfOperations += e.getValue(); i++; } + registeredReplicas.insertRow(i); + registeredReplicas.setWidget(i, 1, new Label(stringMessages.totalNumberOfOperations())); + registeredReplicas.setWidget(i, 2, new Label(""+totalNumberOfOperations)); + i++; replicaRegistered = true; } 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 bd565abe8f0..ba7657e9af5 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 @@ -1060,4 +1060,5 @@ public interface StringMessages extends Messages { String numberOfQueueMessagesSent(); String totalSize(); String averageMessageSize(); + String totalNumberOfOperations(); } 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 3ed82c32022..ecfb042949f 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 @@ -1043,4 +1043,5 @@ setTimeToNow=Set time to ''now'' 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 \ No newline at end of file +averageMessageSize=Average size per message in bytes +totalNumberOfOperations=Total number of operations \ 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 a42909455ad..a1ed016ddcc 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 @@ -1038,4 +1038,5 @@ setTimeToNow=Setze den Zeitpunkt auf ''Jetzt'' useStartTimeInference=Startzeit aus Startlinienüberquerungen automatisch ableiten numberOfQueueMessagesSent=Anzahl verschickter Queue Nachrichten totalSize=Gesamtgröße -averageMessageSize=Durchschnittsgrösse einer Nachricht \ No newline at end of file +averageMessageSize=Durchschnittsgrösse einer Nachricht +totalNumberOfOperations=Gesamtanzahl gesendete Operationen \ 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_ru.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties index cb28b5c1f37..371de6a3607 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 @@ -993,4 +993,5 @@ showUncorrectedNetPoints=Show uncorrected net points useStartTimeInference=Use start time inference from start mark passings numberOfQueueMessagesSent=Number of queue messages sent totalSize=Total Size -averageMessageSize=Average Size per message \ No newline at end of file +averageMessageSize=Average Size per message +totalNumberOfOperations=Total number of operations \ No newline at end of file diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/CompetitorReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/CompetitorReplicationTest.java index 7219cbf24d6..795e96016c3 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/CompetitorReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/CompetitorReplicationTest.java @@ -55,7 +55,7 @@ import com.sap.sse.common.Util; */ public class CompetitorReplicationTest extends AbstractServerReplicationTest { /** - * Add a tracked race to the master that includes a competitor; check that the competiotr was properly replicated to + * Add a tracked race to the master that includes a competitor; check that the competitor was properly replicated to * the replica's {@link CompetitorStore}. Afterwards, use the {@link UpdateCompetitor} operation on the master to * perform an explicit update; ensure that the update arrived on the replica. Then execute an * {@link AllowCompetitorResetToDefaults} operation on the master, afterwards update the competitor on the master, diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java index 55410ef4183..b0682946cd5 100644 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java @@ -33,8 +33,6 @@ import com.sap.sailing.server.RacingEventServiceOperation; import com.sap.sailing.server.replication.ReplicationMasterDescriptor; import com.sap.sailing.server.replication.ReplicationService; import com.sap.sailing.util.BuildVersion; -import com.sap.sse.common.Util; -import com.sap.sse.common.Util.Pair; /** * Can observe a {@link RacingEventService} for the operations it performs that require replication. Only observes as @@ -90,13 +88,35 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec private Replicator replicator; private Thread replicatorThread; + /** + * Used to synchronize write access and replacements of {@link #outboundBuffer}, {@link #outboundObjectBuffer} and + * {@link #outboundBufferClasses} when the timer scoops up the messages to send. + */ + private final Object outboundBufferMonitor = ""; + /** * Sending operations as serialized Java objects using binary RabbitMQ messages comes at an overhead. To reduce the overhead, * several operations can be serialized into a single message. The actual serialization of the buffer happens after a short duration - * has passed since the last sending, managed by a {@link Timer}. Writers need to synchronize on this buffer. This includes the - * addition of an operation to the buffer as well as the atomic sending and clearing. + * has passed since the last sending, managed by a {@link Timer}. Writers need to synchronize on {@link #outboundBufferMonitor} + * which protects all of {@link #outboundBuffer}, {@link #outboundObjectBuffer} and {@link #outboundBufferClasses} which + * are replaced or cleared when the timer scoops up the currently buffered operations to send them out. */ - private final List, byte[]>> outboundBuffer; + private ByteArrayOutputStream outboundBuffer; + + /** + * An object output stream that writes to {@link #outboundBuffer}. Operations are serialized into this stream until the timer + * acquires the {@link #outboundBufferMonitor}, closes the stream and transmits the contents of {@link #outboundBuffer} as a + * RabbitMQ message. While still holding the monitor, the timer task creates a new {@link #outboundBuffer} and a new + * {@link #outboundObjectBuffer} wrapping the {@link #outboundBuffer}. + */ + private ObjectOutputStream outboundObjectBuffer; + + /** + * Remembers the classes of the operations serialized into {@link #outboundObjectBuffer}. The list of classes in this list + * matches with the sequence of objects written to {@link #outboundObjectBuffer} as long as the {@link #outboundBufferMonitor} + * is being held. + */ + private List> outboundBufferClasses; /** * Used to schedule the sending of all operations in {@link #outboundBuffer} using the {@link #sendingTask}. @@ -151,7 +171,6 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec this.exchangeHost = exchangeHost; replicator = null; serverUUID = UUID.randomUUID(); - outboundBuffer = new ArrayList<>(); logger.info("Setting " + serverUUID.toString() + " as unique replication identifier."); } @@ -249,30 +268,47 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec * a {@link #timer} is created and scheduled to send in {@link #TRANSMISSION_DELAY_MILLIS} milliseconds. */ private void broadcastOperation(RacingEventServiceOperation operation) throws IOException { + // need to write the operations one by one, making sure the ObjectOutputStream always writes + // identical objects again if required because they may have changed state in between ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(operation); oos.close(); final byte[] bytes = bos.toByteArray(); - synchronized (outboundBuffer) { - outboundBuffer.add(new Pair, byte[]>(operation.getClass(), bytes)); + synchronized (outboundBufferMonitor) { + if (outboundBuffer == null) { + outboundBuffer = new ByteArrayOutputStream(); + outboundObjectBuffer = new ObjectOutputStream(outboundBuffer); + outboundBufferClasses = new ArrayList<>(); + } + outboundObjectBuffer.writeObject(bytes); + outboundBufferClasses.add(operation.getClass()); if (sendingTask == null) { sendingTask = new TimerTask() { @Override public void run() { - logger.fine("Running timer task to send "+outboundBuffer.size()+" messages"); - final Iterable, byte[]>> listToSend; - synchronized (outboundBuffer) { - logger.fine("Copying "+outboundBuffer.size()+" messages to new list"); - listToSend = new ArrayList<>(outboundBuffer); - outboundBuffer.clear(); + logger.fine("Running timer task to send "+outboundBufferClasses.size()+" operations in one message"); + final byte[] bytesToSend; + final List> classesOfOperationsToSend; + synchronized (outboundBufferMonitor) { + logger.fine("Preparing "+outboundBufferClasses.size()+" operations for sending to RabbitMQ exchange"); + try { + outboundObjectBuffer.close(); + } catch (IOException e) { + logger.log(Level.SEVERE, "Error trying to replicate "+outboundBufferClasses.size()+" operations", e); + } + bytesToSend = outboundBuffer.toByteArray(); + classesOfOperationsToSend = outboundBufferClasses; + outboundBuffer = null; + outboundObjectBuffer = null; + outboundBufferClasses = null; sendingTask = null; } try { - broadcastOperations(listToSend); - logger.fine("Successfully handed "+Util.size(listToSend)+" messages to broadcaster"); + broadcastOperations(bytesToSend, classesOfOperationsToSend); + logger.fine("Successfully handed "+classesOfOperationsToSend.size()+" messages to broadcaster"); } catch (Exception e) { - logger.log(Level.SEVERE, "Error trying to replicate the following operations: "+listToSend, e); + logger.log(Level.SEVERE, "Error trying to replicate "+classesOfOperationsToSend.size()+" operations", e); } } }; @@ -284,25 +320,14 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec } } - private void broadcastOperations(Iterable, byte[]>> listToSend) throws IOException { - logger.fine("broadcasting "+Util.size(listToSend)+" operations"); - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(buf); - List> classes = new ArrayList<>(); - List byteArrays = new ArrayList<>(); - for (Pair, byte[]> op : listToSend) { - classes.add(op.getA()); - byteArrays.add(op.getB()); - } - oos.writeObject(byteArrays); - oos.close(); - // copy serialized operations into message + private void broadcastOperations(byte[] bytesToSend, List> classesOfOperationsToSend) throws IOException { + logger.fine("broadcasting "+classesOfOperationsToSend.size()+" operations as "+bytesToSend.length+" bytes"); if (masterChannel != null) { - final int queueMessageSize = buf.size(); - logger.fine("buffer to broadcast has "+queueMessageSize+" bytes ("+(queueMessageSize/1024/1024)+"MB)"); - masterChannel.basicPublish(exchangeName, /* routingKey */"", /* properties */null, buf.toByteArray()); - logger.fine("successfully published "+queueMessageSize+" bytes"); - replicationInstancesManager.log(classes,queueMessageSize); + logger.fine("buffer to broadcast has "+bytesToSend.length+" bytes ("+(bytesToSend.length/1024/1024)+"MB)"); + long startTime = System.currentTimeMillis(); + masterChannel.basicPublish(exchangeName, /* routingKey */"", /* properties */null, bytesToSend); + logger.fine("successfully published "+bytesToSend.length+" bytes, taking "+(System.currentTimeMillis()-startTime)+"ms"); + replicationInstancesManager.log(classesOfOperationsToSend, bytesToSend.length); } } diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java index 9ddd5f95bec..d0f383bf66b 100755 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java @@ -1,6 +1,7 @@ package com.sap.sailing.server.replication.impl; import java.io.ByteArrayInputStream; +import java.io.EOFException; import java.io.IOException; import java.io.ObjectInputStream; import java.lang.reflect.Field; @@ -139,18 +140,23 @@ public class Replicator implements Runnable { Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); ObjectInputStream ois = racingEventServiceTracker.getRacingEventService().getBaseDomainFactory() .createObjectInputStreamResolvingAgainstThisFactory(new ByteArrayInputStream(bytesFromMessage)); - @SuppressWarnings("unchecked") - Iterable byteArrays = (Iterable) ois.readObject(); - for (byte[] serializedOperation : byteArrays) { - ObjectInputStream operationOIS = racingEventServiceTracker - .getRacingEventService().getBaseDomainFactory().createObjectInputStreamResolvingAgainstThisFactory( - new ByteArrayInputStream(serializedOperation)); - RacingEventServiceOperation operation = (RacingEventServiceOperation) operationOIS.readObject(); - operationCount++; - if (operationCount % 10000l == 0) { - logger.info("Received "+operationCount+" operations so far"); + int operationsInMessage = 0; + try { + while (true) { + byte[] serializedOperation = (byte[]) ois.readObject(); + ObjectInputStream operationOIS = racingEventServiceTracker.getRacingEventService().getBaseDomainFactory() + .createObjectInputStreamResolvingAgainstThisFactory(new ByteArrayInputStream(serializedOperation)); + RacingEventServiceOperation operation = (RacingEventServiceOperation) operationOIS.readObject(); + operationCount++; + operationsInMessage++; + if (operationCount % 10000l == 0) { + logger.info("Received " + operationCount + " operations so far"); + } + applyOrQueue(operation); } - applyOrQueue(operation); + } catch (EOFException eof) { + logger.fine("Reached EOF on replication message after having read "+operationsInMessage+" operations"); + // reached EOF; expected } } catch (ConsumerCancelledException cce) { logger.info("Consumer has been shut down properly."); 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 443665d3d93..cf775d02352 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 @@ -41,6 +41,7 @@ import org.osgi.util.tracker.ServiceTracker; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CompetitorStore; +import com.sap.sailing.domain.base.CompetitorStore.CompetitorUpdateListener; import com.sap.sailing.domain.base.ControlPoint; import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.DomainFactory; @@ -126,10 +127,10 @@ 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.RaceHandle; import com.sap.sailing.domain.tracking.RaceListener; import com.sap.sailing.domain.tracking.RaceTracker; import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters; -import com.sap.sailing.domain.tracking.RaceHandle; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRaceStatus; import com.sap.sailing.domain.tracking.TrackedRegatta; @@ -173,6 +174,7 @@ import com.sap.sailing.server.operationaltransformation.RemoveWindFix; import com.sap.sailing.server.operationaltransformation.RenameEvent; import com.sap.sailing.server.operationaltransformation.SetDataImportDeleteProgressFromMapTimer; import com.sap.sailing.server.operationaltransformation.TrackRegatta; +import com.sap.sailing.server.operationaltransformation.UpdateCompetitor; import com.sap.sailing.server.operationaltransformation.UpdateMarkPassings; import com.sap.sailing.server.operationaltransformation.UpdateMediaTrackDurationOperation; import com.sap.sailing.server.operationaltransformation.UpdateMediaTrackStartTimeOperation; @@ -412,6 +414,13 @@ public class RacingEventServiceImpl implements RacingEventServiceWithTestSupport this.mongoObjectFactory = mongoObjectFactory; this.mediaDB = mediaDb; this.competitorStore = competitorStore; + this.competitorStore.addCompetitorUpdateListener(new CompetitorUpdateListener() { + @Override + public void competitorUpdated(Competitor competitor) { + replicate(new UpdateCompetitor(competitor.getId().toString(), competitor.getName(), + competitor.getColor(), competitor.getBoat().getSailID(), competitor.getTeam().getNationality())); + } + }); this.windStore = windStore; this.dataImportLock = new DataImportLockWithProgress(); From ab666aef1af1760b45b2621edfd0af378e4ca69a Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jul 2014 23:24:42 +0200 Subject: [PATCH 134/323] refactored the callback for getRaceMapData so it becomes re-usable for a second call that asks only for the tail ends --- .../client/shared/racemap/FixesAndTails.java | 3 +- .../gwt/ui/client/shared/racemap/RaceMap.java | 124 ++++++++++-------- 2 files changed, 67 insertions(+), 60 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java index f196da48cb5..fe0911e8699 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java @@ -457,8 +457,7 @@ public class FixesAndTails { && !tailstart.before(timepointOfFirstKnownFix) && timepointOfLastKnownFix != null && !tailstart.after(timepointOfLastKnownFix)) { // the beginning of what we need is contained in the interval we already have; skip what we already have - // FIXME requests the lastKnownFix again because "from" is *inclusive*; could lead to bug 319 - fromDate = timepointOfLastKnownFix; + fromDate = new Date(timepointOfLastKnownFix.getTime()+1l); // "from" is "inclusive", so add 1ms to also skip the last fix we have overlap = true; } else { fromDate = tailstart; 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 d91d8cdc607..3a8c18792be 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 @@ -446,73 +446,19 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe final com.sap.sse.common.Util.Triple, Map, Map> fromAndToAndOverlap = fixesAndTails.computeFromAndTo(newTime, competitorsToShow, settings.getEffectiveTailLengthInMilliseconds()); final int requestID = ++boatPositionRequestIDCounter; - - // TODO For those competitors for which the tails don't overlap (and therefore will be replaced by the new tail coming from the server) + // TODO bug2026: For those competitors for which the tails don't overlap (and therefore will be replaced by the new tail coming from the server) // we expect some potential delay in computing the full tail. Therefore, in those cases we fire two requests: one fetching only the // boat positions at newTime with zero tail length; and another one fetching everything else. GetRaceMapDataAction getRaceMapDataAction = new GetRaceMapDataAction(sailingService, competitorSelection.getAllCompetitors(), race, newTime, fromAndToAndOverlap.getA(), fromAndToAndOverlap.getB(), /* extrapolate */ true); - asyncActionsExecutor.execute(getRaceMapDataAction, GET_RACE_MAP_DATA_CATEGORY, new AsyncCallback() { - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError("Error obtaining racemap data: " + caught.getMessage(), true /*silentMode */); - } - - @Override - public void onSuccess(RaceMapDataDTO raceMapDataDTO) { - if (map != null && raceMapDataDTO != null) { - quickRanks = raceMapDataDTO.quickRanks; - // process response only if not received out of order - if (startedProcessingRequestID < requestID) { - startedProcessingRequestID = requestID; - // Do boat specific actions - Map> boatData = raceMapDataDTO.boatPositions; - long timeForPositionTransitionMillis = calculateTimeForPositionTransition(newTime, oldTime); - fixesAndTails.updateFixes(boatData, fromAndToAndOverlap.getC(), RaceMap.this, timeForPositionTransitionMillis); - showBoatsOnMap(newTime, timeForPositionTransitionMillis, getCompetitorsToShow()); - showCompetitorInfoOnMap(newTime, timeForPositionTransitionMillis, competitorSelection.getSelectedCompetitors()); - if (douglasMarkers != null) { - removeAllMarkDouglasPeuckerpoints(); - } - if (maneuverMarkers != null) { - removeAllManeuverMarkers(); - } - - // Do mark specific actions - showCourseMarksOnMap(raceMapDataDTO.coursePositions); - showCourseSidelinesOnMap(raceMapDataDTO.courseSidelines); - showStartAndFinishLines(raceMapDataDTO.coursePositions); - showAdvantageLine(competitorsToShow, newTime); - - // 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); - zoomMapToNewBounds(bounds); - mapFirstZoomDone = true; - } else if (!mapFirstZoomDone) { // Zoom once to the marks - zoomMapToNewBounds(new CourseMarksBoundsCalculator().calculateNewBounds(RaceMap.this)); - mapFirstZoomDone = true; - /* - * Reset the mapZoomedOrPannedSinceLastRaceSelection: In spite of the fact that - * the map was just zoomed to the bounds of the marks, it was not a zoom or pan - * triggered by the user. As a consequence the - * mapZoomedOrPannedSinceLastRaceSelection option has to reset again. - */ - // TODO bug 494: consider initial user-specific zoom settings - } - } - } else { - lastTimeChangeBeforeInitialization = newTime; - } - } - }); + asyncActionsExecutor.execute(getRaceMapDataAction, GET_RACE_MAP_DATA_CATEGORY, + getRaceMapDataCallback(oldTime, newTime, fromAndToAndOverlap.getC(), competitorsToShow, requestID)); // draw the wind into the map, get the combined wind + // TODO bug2057 also fetch wind for LEG_MIDDLE for all legs because this needs to be the basis for the advantage line display List windSourceTypeNames = new ArrayList(); windSourceTypeNames.add(WindSourceType.EXPEDITION.name()); windSourceTypeNames.add(WindSourceType.COMBINED.name()); - GetWindInfoAction getWindInfoAction = new GetWindInfoAction(sailingService, race, newTime, 1000L, 1, windSourceTypeNames, /* onlyUpToNewestEvent==false means get us any data we can get by a best effort */ false); asyncActionsExecutor.execute(getWindInfoAction, GET_WIND_DATA_CATEGORY, new AsyncCallback() { @@ -558,6 +504,68 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe } } + private AsyncCallback getRaceMapDataCallback( + final Date oldTime, + final Date newTime, + final Map hasTailOverlapForCompetitor, + final Iterable competitorsToShow, final int requestID) { + return new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Error obtaining racemap data: " + caught.getMessage(), true /*silentMode */); + } + + @Override + public void onSuccess(RaceMapDataDTO raceMapDataDTO) { + if (map != null && raceMapDataDTO != null) { + quickRanks = raceMapDataDTO.quickRanks; + // process response only if not received out of order + if (startedProcessingRequestID < requestID) { + startedProcessingRequestID = requestID; + // Do boat specific actions + Map> boatData = raceMapDataDTO.boatPositions; + long timeForPositionTransitionMillis = calculateTimeForPositionTransition(newTime, oldTime); + fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, timeForPositionTransitionMillis); + showBoatsOnMap(newTime, timeForPositionTransitionMillis, getCompetitorsToShow()); + showCompetitorInfoOnMap(newTime, timeForPositionTransitionMillis, competitorSelection.getSelectedCompetitors()); + if (douglasMarkers != null) { + removeAllMarkDouglasPeuckerpoints(); + } + if (maneuverMarkers != null) { + removeAllManeuverMarkers(); + } + + // Do mark specific actions + showCourseMarksOnMap(raceMapDataDTO.coursePositions); + showCourseSidelinesOnMap(raceMapDataDTO.courseSidelines); + showStartAndFinishLines(raceMapDataDTO.coursePositions); + showAdvantageLine(competitorsToShow, newTime); + + // 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); + zoomMapToNewBounds(bounds); + mapFirstZoomDone = true; + } else if (!mapFirstZoomDone) { // Zoom once to the marks + zoomMapToNewBounds(new CourseMarksBoundsCalculator().calculateNewBounds(RaceMap.this)); + mapFirstZoomDone = true; + /* + * Reset the mapZoomedOrPannedSinceLastRaceSelection: In spite of the fact that + * the map was just zoomed to the bounds of the marks, it was not a zoom or pan + * triggered by the user. As a consequence the + * mapZoomedOrPannedSinceLastRaceSelection option has to reset again. + */ + // TODO bug 494: consider initial user-specific zoom settings + } + } + } else { + lastTimeChangeBeforeInitialization = newTime; + } + } + }; + } + private void showCourseSidelinesOnMap(List sidelinesDTOs) { if (map != null && sidelinesDTOs != null ) { Map toRemoveSidelines = new HashMap(courseSidelines); From 6356a2b6d962c5bb1ca494ed0d208373e1b70658 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jul 2014 23:32:28 +0200 Subject: [PATCH 135/323] fixed replication log message --- .../sailing/server/replication/impl/ReplicationServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java index b0682946cd5..1f6297016ee 100644 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java @@ -306,7 +306,7 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec } try { broadcastOperations(bytesToSend, classesOfOperationsToSend); - logger.fine("Successfully handed "+classesOfOperationsToSend.size()+" messages to broadcaster"); + logger.fine("Successfully handed "+classesOfOperationsToSend.size()+" operations to broadcaster"); } catch (Exception e) { logger.log(Level.SEVERE, "Error trying to replicate "+classesOfOperationsToSend.size()+" operations", e); } From 8cb4b7f5487c93e5cfcc88e37eda615d5a2d2cf3 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jul 2014 23:37:29 +0200 Subject: [PATCH 136/323] adjusted replication log message --- .../sailing/server/replication/impl/ReplicationServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java index 1f6297016ee..3c2e55059c9 100644 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java @@ -315,7 +315,7 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec timer.schedule(sendingTask, TRANSMISSION_DELAY_MILLIS); } if (++messageCount % 10000l == 0) { - logger.info("Handled "+messageCount+" messages for replication. Current outbound replication queue size: "+outboundBuffer.size()); + logger.info("Handled "+messageCount+" messages for replication. Current outbound replication queue size: "+outboundBufferClasses.size()); } } } From f36df6af13db3f9848ea7e5725ebc7651e4d210b Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 17 Jul 2014 00:46:50 +0200 Subject: [PATCH 137/323] added test that shows that an exception in a TimerTask cancels the timer and makes it unusable --- .../impl/SwissTimingReplayServiceImpl.java | 8 +-- .../sap/sailing/domain/test/TimerTest.java | 49 +++++++++++++++++++ ...yteArrayOutputStreamWithVisibleBuffer.java | 9 ++++ .../impl/ReplicationServiceImpl.java | 4 +- 4 files changed, 62 insertions(+), 8 deletions(-) create mode 100755 java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TimerTest.java create mode 100755 java/com.sap.sailing.domain/src/com/sap/sailing/util/ByteArrayOutputStreamWithVisibleBuffer.java diff --git a/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayServiceImpl.java b/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayServiceImpl.java index 5371e3992e6..e24a8f7bc64 100644 --- a/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayServiceImpl.java +++ b/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayServiceImpl.java @@ -1,7 +1,6 @@ package com.sap.sailing.domain.swisstimingreplayadapter.impl; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -25,6 +24,7 @@ import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayListener import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayRace; import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayService; import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; +import com.sap.sailing.util.ByteArrayOutputStreamWithVisibleBuffer; public class SwissTimingReplayServiceImpl implements SwissTimingReplayService { @@ -119,12 +119,6 @@ public class SwissTimingReplayServiceImpl implements SwissTimingReplayService { return startTimeFormat; } - private static class ByteArrayOutputStreamWithVisibleBuffer extends ByteArrayOutputStream { - public byte[] getBuffer() { - return buf; - } - } - @Override public void loadRaceData(String link, SwissTimingReplayListener replayListener) { final URL raceDataUrl; diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TimerTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TimerTest.java new file mode 100755 index 00000000000..eacb20503a7 --- /dev/null +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TimerTest.java @@ -0,0 +1,49 @@ +package com.sap.sailing.domain.test; + +import static org.junit.Assert.fail; + +import java.util.Timer; +import java.util.TimerTask; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.Timeout; + +/** + * Tests around the Timer class; e.g., does it kill a timer if a task throws an exception? + * + * @author Axel Uhl (d043530) + * + */ +public class TimerTest { + @Rule + public Timeout AbstractTracTracLiveTestTimeout = new Timeout(10 * 1000); + + @Test + public void exceptionInTask() throws InterruptedException { + Timer t = new Timer("Test"); + final TimerTask taskThrowingException = new TimerTask() { + @Override + public void run() { + throw new RuntimeException("An exception that may or may not kill the timer"); + } + }; + t.schedule(taskThrowingException, 1l); + Thread.sleep(10); + final boolean[] result = new boolean[1]; + try { + t.schedule(new TimerTask() { + @Override + public void run() { + synchronized (result) { + result[0] = true; + result.notifyAll(); + } + } + }, 1l); + fail("Expected IllegalStateException because task terminating abnormally cancels the timer"); + } catch (IllegalStateException e) { + // expected + } + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/util/ByteArrayOutputStreamWithVisibleBuffer.java b/java/com.sap.sailing.domain/src/com/sap/sailing/util/ByteArrayOutputStreamWithVisibleBuffer.java new file mode 100755 index 00000000000..d6f60ca7bfb --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/util/ByteArrayOutputStreamWithVisibleBuffer.java @@ -0,0 +1,9 @@ +package com.sap.sailing.util; + +import java.io.ByteArrayOutputStream; + +public class ByteArrayOutputStreamWithVisibleBuffer extends ByteArrayOutputStream { + public byte[] getBuffer() { + return buf; + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java index 3c2e55059c9..7c2dd65f344 100644 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java @@ -287,17 +287,19 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec sendingTask = new TimerTask() { @Override public void run() { - logger.fine("Running timer task to send "+outboundBufferClasses.size()+" operations in one message"); + logger.fine("Running timer task, trying to acquire monitor"); final byte[] bytesToSend; final List> classesOfOperationsToSend; synchronized (outboundBufferMonitor) { logger.fine("Preparing "+outboundBufferClasses.size()+" operations for sending to RabbitMQ exchange"); try { outboundObjectBuffer.close(); + logger.fine("Sucessfully closed ObjectOutputStream"); } catch (IOException e) { logger.log(Level.SEVERE, "Error trying to replicate "+outboundBufferClasses.size()+" operations", e); } bytesToSend = outboundBuffer.toByteArray(); + logger.fine("Successfully produced bytesToSend array of length "+bytesToSend.length); classesOfOperationsToSend = outboundBufferClasses; outboundBuffer = null; outboundObjectBuffer = null; From 5fc172a15d4ced3192e1ac60607c5b3144bea1be Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 17 Jul 2014 01:08:09 +0200 Subject: [PATCH 138/323] send operations before delay has passed if buffer exceeds 1MB; made timer task exception-safe --- .../impl/ReplicationServiceImpl.java | 82 ++++++++++++------- 1 file changed, 53 insertions(+), 29 deletions(-) diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java index 7c2dd65f344..30b187861a5 100644 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java @@ -144,6 +144,11 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec * are likely to be sent per message transmitted, reducing overhead but correspondingly increasing latency. */ private final long TRANSMISSION_DELAY_MILLIS = 100; + + /** + * Defines at which message size in bytes the message will be sent regardless the {@link #TRANSMISSION_DELAY_MILLIS}. + */ + private final int TRIGGER_MESSAGE_SIZE_IN_BYTES = 1024*1024; /** * Counts the messages sent out by this replicator @@ -283,38 +288,26 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec } outboundObjectBuffer.writeObject(bytes); outboundBufferClasses.add(operation.getClass()); - if (sendingTask == null) { - sendingTask = new TimerTask() { - @Override - public void run() { - logger.fine("Running timer task, trying to acquire monitor"); - final byte[] bytesToSend; - final List> classesOfOperationsToSend; - synchronized (outboundBufferMonitor) { - logger.fine("Preparing "+outboundBufferClasses.size()+" operations for sending to RabbitMQ exchange"); + if (outboundBuffer.size() > TRIGGER_MESSAGE_SIZE_IN_BYTES) { + logger.info("Triggering replication because buffer holds " + outboundBuffer.size() + + "B which exceeds trigger size " + TRIGGER_MESSAGE_SIZE_IN_BYTES); + flushBufferToRabbitMQ(); + } else { + if (sendingTask == null) { + sendingTask = new TimerTask() { + @Override + public void run() { try { - outboundObjectBuffer.close(); - logger.fine("Sucessfully closed ObjectOutputStream"); - } catch (IOException e) { - logger.log(Level.SEVERE, "Error trying to replicate "+outboundBufferClasses.size()+" operations", e); + sendingTask = null; + logger.fine("Running timer task, flushing buffer"); + flushBufferToRabbitMQ(); + } catch (Exception e) { + logger.log(Level.SEVERE, "Exception while trying to replicate operations", e); } - bytesToSend = outboundBuffer.toByteArray(); - logger.fine("Successfully produced bytesToSend array of length "+bytesToSend.length); - classesOfOperationsToSend = outboundBufferClasses; - outboundBuffer = null; - outboundObjectBuffer = null; - outboundBufferClasses = null; - sendingTask = null; } - try { - broadcastOperations(bytesToSend, classesOfOperationsToSend); - logger.fine("Successfully handed "+classesOfOperationsToSend.size()+" operations to broadcaster"); - } catch (Exception e) { - logger.log(Level.SEVERE, "Error trying to replicate "+classesOfOperationsToSend.size()+" operations", e); - } - } - }; - timer.schedule(sendingTask, TRANSMISSION_DELAY_MILLIS); + }; + timer.schedule(sendingTask, TRANSMISSION_DELAY_MILLIS); + } } if (++messageCount % 10000l == 0) { logger.info("Handled "+messageCount+" messages for replication. Current outbound replication queue size: "+outboundBufferClasses.size()); @@ -508,4 +501,35 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec return serverUUID; } + /** + * Obtains the monitor on {@link #outboundBufferMonitor}, copies the references to the buffers, nulls out + * the buffers, then releases the monitor and broadcasts the buffer. + */ + private void flushBufferToRabbitMQ() { + logger.fine("Running timer task, trying to acquire monitor"); + final byte[] bytesToSend; + final List> classesOfOperationsToSend; + synchronized (outboundBufferMonitor) { + logger.fine("Preparing "+outboundBufferClasses.size()+" operations for sending to RabbitMQ exchange"); + try { + outboundObjectBuffer.close(); + logger.fine("Sucessfully closed ObjectOutputStream"); + } catch (IOException e) { + logger.log(Level.SEVERE, "Error trying to replicate "+outboundBufferClasses.size()+" operations", e); + } + bytesToSend = outboundBuffer.toByteArray(); + logger.fine("Successfully produced bytesToSend array of length "+bytesToSend.length); + classesOfOperationsToSend = outboundBufferClasses; + outboundBuffer = null; + outboundObjectBuffer = null; + outboundBufferClasses = null; + } + try { + broadcastOperations(bytesToSend, classesOfOperationsToSend); + logger.fine("Successfully handed "+classesOfOperationsToSend.size()+" operations to broadcaster"); + } catch (Exception e) { + logger.log(Level.SEVERE, "Error trying to replicate "+classesOfOperationsToSend.size()+" operations", e); + } + } + } From 8a8dec05b7840b6c7d702e726a7ccfd246cda180 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 17 Jul 2014 01:34:31 +0200 Subject: [PATCH 139/323] minor replication logging change --- .../sailing/server/replication/impl/ReplicationServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java index 30b187861a5..8e6fe08b534 100644 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java @@ -506,7 +506,7 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec * the buffers, then releases the monitor and broadcasts the buffer. */ private void flushBufferToRabbitMQ() { - logger.fine("Running timer task, trying to acquire monitor"); + logger.fine("Trying to acquire monitor"); final byte[] bytesToSend; final List> classesOfOperationsToSend; synchronized (outboundBufferMonitor) { From 96ca743b2c90abc34ef1a13ea881a2014dfa1bfc Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 17 Jul 2014 01:36:11 +0200 Subject: [PATCH 140/323] another minor replication logging change --- .../sailing/server/replication/impl/ReplicationServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java index 8e6fe08b534..e4a01db4363 100644 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java @@ -290,7 +290,7 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec outboundBufferClasses.add(operation.getClass()); if (outboundBuffer.size() > TRIGGER_MESSAGE_SIZE_IN_BYTES) { logger.info("Triggering replication because buffer holds " + outboundBuffer.size() - + "B which exceeds trigger size " + TRIGGER_MESSAGE_SIZE_IN_BYTES); + + " bytes which exceeds trigger size " + TRIGGER_MESSAGE_SIZE_IN_BYTES+" bytes"); flushBufferToRabbitMQ(); } else { if (sendingTask == null) { From 15b449754019bed988ba92d5aee4b80400f070c2 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 17 Jul 2014 01:52:45 +0200 Subject: [PATCH 141/323] use GZIP compression also for regular replication operations; by bundling several operations into one message, this could pay off --- .../server/replication/impl/ReplicationServiceImpl.java | 4 +++- .../com/sap/sailing/server/replication/impl/Replicator.java | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java index 30b187861a5..ebd4db618b0 100644 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java @@ -20,6 +20,7 @@ import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; import org.osgi.util.tracker.ServiceTracker; @@ -283,7 +284,8 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec synchronized (outboundBufferMonitor) { if (outboundBuffer == null) { outboundBuffer = new ByteArrayOutputStream(); - outboundObjectBuffer = new ObjectOutputStream(outboundBuffer); + GZIPOutputStream zipper = new GZIPOutputStream(outboundBuffer); + outboundObjectBuffer = new ObjectOutputStream(zipper); outboundBufferClasses = new ArrayList<>(); } outboundObjectBuffer.writeObject(bytes); diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java index d0f383bf66b..42616d25c24 100755 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java @@ -15,6 +15,7 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.zip.GZIPInputStream; import com.rabbitmq.client.ConsumerCancelledException; import com.rabbitmq.client.QueueingConsumer; @@ -139,7 +140,8 @@ public class Replicator implements Runnable { // of all required bundles/packages can be deserialized at least Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); ObjectInputStream ois = racingEventServiceTracker.getRacingEventService().getBaseDomainFactory() - .createObjectInputStreamResolvingAgainstThisFactory(new ByteArrayInputStream(bytesFromMessage)); + .createObjectInputStreamResolvingAgainstThisFactory( + new GZIPInputStream(new ByteArrayInputStream(bytesFromMessage))); int operationsInMessage = 0; try { while (true) { From 4ba92a78dc6bb8999b2847b66ccd85bea0bfc8e3 Mon Sep 17 00:00:00 2001 From: Jens Rommel Date: Thu, 17 Jul 2014 09:30:09 +0200 Subject: [PATCH 142/323] Improved Countdown, added test. --- .../gwt/ui/common/client/CountdownTest.java | 107 ++++++++++++++++++ .../home/client/shared/stage/StageTeaser.java | 6 +- .../gwt/ui/common/client/Countdown.java | 87 +++++++------- 3 files changed, 158 insertions(+), 42 deletions(-) create mode 100644 java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/common/client/CountdownTest.java diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/common/client/CountdownTest.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/common/client/CountdownTest.java new file mode 100644 index 00000000000..f34ecf79d0c --- /dev/null +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/common/client/CountdownTest.java @@ -0,0 +1,107 @@ +package com.sap.sailing.gwt.ui.common.client; + +import org.junit.Ignore; +import org.junit.Test; + +import com.sap.sailing.domain.common.Duration; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; +import com.sap.sailing.gwt.ui.common.client.Countdown.CountdownListener; +import com.sap.sailing.gwt.ui.common.client.Countdown.RemainingTime; +import com.sap.sailing.gwt.ui.common.client.Countdown.Unit; + +import static org.junit.Assert.*; + +import static org.hamcrest.Matchers.*; + +public class CountdownTest { + + @Test + public void testDaysAndHours() throws Exception { + final long days = 2; + final long hours = 13; + final long minutes = 28; + final long seconds = 2; + TimePoint zeroDate = MillisecondsTimePoint.now().plus(Duration.ONE_DAY.times(days)).plus(Duration.ONE_HOUR.times(hours)).plus(Duration.ONE_MINUTE.times(minutes)).plus(Duration.ONE_SECOND.times(seconds)); + CountdownListener countdownListener = createCountDownAssertionListener(days, Unit.DAYS, hours, Unit.HOURS); + new Countdown(zeroDate, countdownListener, true); + } + + private CountdownListener createCountDownAssertionListener(final long majorValue, final Unit majorUnit, final long minorValue, final Unit minorUnit) { + return new CountdownListener() { + + @Override + public void changed(RemainingTime major, RemainingTime minor) { + assertThat(major, is(notNullValue())); + assertThat(major.value, is(majorValue)); + assertThat(major.unit, is(majorUnit)); + + if (minorUnit != null) { + assertThat(minor, is(notNullValue())); + assertThat(minor.value, is(minorValue)); + assertThat(minor.unit, is(minorUnit)); + } else { + assertThat(minor, is(nullValue())); + } + + + } + }; + } + + @Test + public void testDaysNoHours() throws Exception { + final long days = 2; + final long hours = 0; + final long minutes = 28; + final long seconds = 2; + TimePoint zeroDate = MillisecondsTimePoint.now().plus(Duration.ONE_DAY.times(days)).plus(Duration.ONE_HOUR.times(hours)).plus(Duration.ONE_MINUTE.times(minutes)).plus(Duration.ONE_SECOND.times(seconds)); + CountdownListener countdownListener = createCountDownAssertionListener(days, Unit.DAYS, hours, Unit.HOURS); + new Countdown(zeroDate, countdownListener, true); + } + + @Test + public void testOnlyDays() throws Exception { + final long days = 3; + final long hours = 0; + final long minutes = 28; + final long seconds = 2; + TimePoint zeroDate = MillisecondsTimePoint.now().plus(Duration.ONE_DAY.times(days)).plus(Duration.ONE_HOUR.times(hours)).plus(Duration.ONE_MINUTE.times(minutes)).plus(Duration.ONE_SECOND.times(seconds)); + CountdownListener countdownListener = createCountDownAssertionListener(days, Unit.DAYS, 0, null); + new Countdown(zeroDate, countdownListener, true); + } + + @Test + public void testHoursAndMinutes() throws Exception { + final long days = 0; + final long hours = 13; + final long minutes = 28; + final long seconds = 2; + TimePoint zeroDate = MillisecondsTimePoint.now().plus(Duration.ONE_DAY.times(days)).plus(Duration.ONE_HOUR.times(hours)).plus(Duration.ONE_MINUTE.times(minutes)).plus(Duration.ONE_SECOND.times(seconds)); + CountdownListener countdownListener = createCountDownAssertionListener(hours, Unit.HOURS, minutes, Unit.MINUTES); + new Countdown(zeroDate, countdownListener, true); + } + + @Test + public void testMinutesAndSeconds() throws Exception { + final long days = 0; + final long hours = 0; + final long minutes = 28; + final long seconds = 2; + TimePoint zeroDate = MillisecondsTimePoint.now().plus(Duration.ONE_DAY.times(days)).plus(Duration.ONE_HOUR.times(hours)).plus(Duration.ONE_MINUTE.times(minutes)).plus(Duration.ONE_SECOND.times(seconds)); + CountdownListener countdownListener = createCountDownAssertionListener(minutes, Unit.MINUTES, seconds, Unit.SECONDS); + new Countdown(zeroDate, countdownListener, true); + } + + @Test + public void testSecondOnly() throws Exception { + final long days = 0; + final long hours = 0; + final long minutes = 0; + final long seconds = 2; + TimePoint zeroDate = MillisecondsTimePoint.now().plus(Duration.ONE_DAY.times(days)).plus(Duration.ONE_HOUR.times(hours)).plus(Duration.ONE_MINUTE.times(minutes)).plus(Duration.ONE_SECOND.times(seconds)); + CountdownListener countdownListener = createCountDownAssertionListener(seconds, Unit.SECONDS, 0, null); + new Countdown(zeroDate, countdownListener, true); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.java index 8bad5edda42..75dd672f5aa 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.java @@ -14,7 +14,7 @@ import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; import com.sap.sailing.gwt.ui.common.client.Countdown; import com.sap.sailing.gwt.ui.common.client.Countdown.CountdownListener; -import com.sap.sailing.gwt.ui.common.client.Countdown.Time; +import com.sap.sailing.gwt.ui.common.client.Countdown.RemainingTime; import com.sap.sailing.gwt.ui.shared.EventDTO; public abstract class StageTeaser extends Composite { @@ -52,7 +52,7 @@ public abstract class StageTeaser extends Composite { CountdownListener countdownListener = new CountdownListener() { @Override - public void changed(Time major, Time minor) { + public void changed(RemainingTime major, RemainingTime minor) { updateCountdown(major, minor); } @@ -61,7 +61,7 @@ public abstract class StageTeaser extends Composite { } } - private void updateCountdown(Time major, Time minor) { + private void updateCountdown(RemainingTime major, RemainingTime minor) { if (major == null && minor == null) { countdownMajorValue.setInnerHTML(null); countdownMajorUnit.setInnerHTML(null); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/Countdown.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/Countdown.java index 53ff858f885..c5835ab2a97 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/Countdown.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/Countdown.java @@ -7,28 +7,31 @@ import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; /** * A countdown notifying about changes in the two most significant time units. I.e. days+hours or hours+minutes or minutes+seconds or seconds only. + * If more than WHOLE_DAYS_LIMIT are left, suppress hours. + * If only seconds are left, only provide seconds. */ public class Countdown { + private static final int WHOLE_DAYS_LIMIT = 3; private static final int UPDATE_INTERVALL_MILLIS = 1000; private final Timer timer; private final TimePoint zeroDate; private final CountdownListener countdownListener; - private Time previousMajor; - private Time previousMinor; + private RemainingTime previousMajor; + private RemainingTime previousMinor; public enum Unit {DAYS, HOURS, MINUTES, SECONDS} - public static class Time { - public Time(long value, Unit unit) { + public static class RemainingTime { + public RemainingTime(long value, Unit unit) { this.value = value; this.unit = unit; } public final long value; public final Unit unit; - public static boolean equal(Time t1, Time t2) { + public static boolean equal(RemainingTime t1, RemainingTime t2) { if (t1 == null) { if (t2 == null) { return true; @@ -46,10 +49,20 @@ public class Countdown { } public interface CountdownListener { - void changed(Time major, Time minor); + /** + * + * @param major Whole remaining days, hours or seconds + * @param minor Remainder measured in the next lower unit. May be null if more than WHOLE_DAYS_LIMIT days are left or if only seconds are left. + */ + void changed(RemainingTime major, RemainingTime minor); } - public Countdown(TimePoint zeroDate, CountdownListener countdownListener) { + /** + * Constructor for testing purpose only. Allows to suppress starting of scheduler (which doesn't work on Java environment). + * @param zeroDate + * @param countdownListener + */ + Countdown(TimePoint zeroDate, CountdownListener countdownListener, boolean dontSchedule) { this.zeroDate = zeroDate; this.countdownListener = countdownListener; update(); @@ -61,51 +74,47 @@ public class Countdown { } }; - timer.scheduleRepeating(UPDATE_INTERVALL_MILLIS); + if (!dontSchedule) { + timer.scheduleRepeating(UPDATE_INTERVALL_MILLIS); + } + } + + public Countdown(TimePoint zeroDate, CountdownListener countdownListener) { + this(zeroDate, countdownListener, false); } private void update() { Duration diff = MillisecondsTimePoint.now().until(zeroDate); double asDays = diff.asDays(); - long daysRemaining = (long) asDays; + long wholeDaysRemaining = (long) asDays; double asHours = diff.asHours(); - long hoursRemaining = (long) (asHours - daysRemaining * 24.0); + long wholeHoursRemaining = (long) (asHours - wholeDaysRemaining * 24.0); double asMinutes = diff.asMinutes(); - long minutesRemaining = (long) (asMinutes - hoursRemaining * 60.0); + long wholeMinutesRemaining = (long) (asMinutes - wholeHoursRemaining * 60.0); double asSeconds = diff.asSeconds(); - long secondsRemaining = (long) (asSeconds - minutesRemaining * 60.0); + long wholeSecondsRemaining = (long) (asSeconds - wholeMinutesRemaining * 60.0); - Time major = null; - Time minor = null; - if (daysRemaining > 0) { - major = new Time(daysRemaining, Unit.DAYS); - } - if (hoursRemaining > 0) { - Time time = new Time(hoursRemaining, Unit.HOURS); - if (major == null) { - major = time; + RemainingTime major; + RemainingTime minor; + if (wholeDaysRemaining > 0) { + major = new RemainingTime(wholeDaysRemaining, Unit.DAYS); + if (wholeDaysRemaining < WHOLE_DAYS_LIMIT) { //show hours only if less than 3 days left + minor = new RemainingTime(wholeHoursRemaining, Unit.HOURS); } else { - minor = time; + minor = null; } + } else if (wholeHoursRemaining > 0) { + major = new RemainingTime(wholeHoursRemaining, Unit.HOURS); + minor = new RemainingTime(wholeMinutesRemaining, Unit.MINUTES); } - if (minutesRemaining > 0) { - Time time = new Time(minutesRemaining, Unit.MINUTES); - if (major == null) { - major = time; - } else if (major.unit == Unit.HOURS) { - minor = time; - } + else if (wholeMinutesRemaining > 0) { + major = new RemainingTime(wholeMinutesRemaining, Unit.MINUTES); + minor = new RemainingTime(wholeSecondsRemaining, Unit.SECONDS); + } else { + major = new RemainingTime(wholeSecondsRemaining, Unit.SECONDS); + minor = null; } - if (secondsRemaining >= 0) { - Time time = new Time(secondsRemaining, Unit.SECONDS); - if (major == null) { - major = time; - } else if (major.unit == Unit.MINUTES) { - minor = time; - } - } - - if (!Time.equal(previousMajor, major) || !Time.equal(previousMinor, minor)) { + if (!RemainingTime.equal(previousMajor, major) || !RemainingTime.equal(previousMinor, minor)) { countdownListener.changed(major, minor); previousMajor = major; previousMinor = minor; From d3ffd4b549ca0b0c6715211ba487a4c5676c0bd4 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 17 Jul 2014 10:06:08 +0200 Subject: [PATCH 143/323] fixed competitor construction for tests; use UUID.randomUUID() now that competitors must not have the same ID within one race at least --- .../src/com/sap/sailing/domain/test/TrackBasedTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java index 0405da944e3..02eb08435cc 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.UUID; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.ControlPoint; @@ -59,7 +60,7 @@ public abstract class TrackBasedTest { } public static CompetitorImpl createCompetitor(String competitorName) { - return new CompetitorImpl(123, competitorName, Color.RED, new TeamImpl("STG", Collections.singleton( + return new CompetitorImpl(UUID.randomUUID(), competitorName, Color.RED, new TeamImpl("STG", Collections.singleton( new PersonImpl(competitorName, new NationalityImpl("GER"), /* dateOfBirth */null, "This is famous " + competitorName)), new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), From bbad54fa756f42c679d4a62ad0a11946222be683 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 17 Jul 2014 10:09:06 +0200 Subject: [PATCH 144/323] throw IllegalArgumentException in case two distinct competitors with equal ID are passed to a single RaceDefinition constructor --- .../com/sap/sailing/domain/base/impl/RaceDefinitionImpl.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceDefinitionImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceDefinitionImpl.java index 26b86e85960..4aad9851975 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceDefinitionImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceDefinitionImpl.java @@ -27,7 +27,10 @@ public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition { this.course = course; this.competitorsById = new LinkedHashMap<>(); for (Competitor competitor : competitors) { - competitorsById.put(competitor.getId(), competitor); + Competitor competitorWithEqualID = competitorsById.put(competitor.getId(), competitor); + if (competitorWithEqualID != null && competitorWithEqualID != competitor) { + throw new IllegalArgumentException("Two distinct competitors with equal ID "+competitor.getId()+" are not allowed within the single race "+name); + } } this.boatClass = boatClass; this.id = id; From f23619a4d7ff581305dbb55ec8258c18d8c1b7cc Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 17 Jul 2014 10:39:38 +0200 Subject: [PATCH 145/323] fixed texts and header title and tooltip for leg's current SOG --- .../com/sap/sailing/gwt/ui/client/DetailTypeFormatter.java | 3 ++- .../java/com/sap/sailing/gwt/ui/client/StringMessages.java | 2 ++ .../com/sap/sailing/gwt/ui/client/StringMessages.properties | 4 +++- .../sap/sailing/gwt/ui/client/StringMessages_de.properties | 4 +++- .../sap/sailing/gwt/ui/client/StringMessages_ru.properties | 4 +++- .../charts/MultiCompetitorRaceChartSettingsComponent.java | 2 +- .../com/sap/sailing/gwt/ui/server/SailingServiceImpl.java | 2 +- 7 files changed, 15 insertions(+), 6 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DetailTypeFormatter.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DetailTypeFormatter.java index 74ef75acfd9..e29690ee8c8 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DetailTypeFormatter.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DetailTypeFormatter.java @@ -33,6 +33,7 @@ public class DetailTypeFormatter { case NUMBER_OF_MANEUVERS: return stringMessages.numberOfManeuvers(); case CURRENT_SPEED_OVER_GROUND_IN_KNOTS: + return stringMessages.currentOrAverageSpeedOverGroundInKnots(); case RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS: return stringMessages.currentSpeedOverGroundInKnots(); case ESTIMATED_TIME_TO_NEXT_WAYPOINT_IN_SECONDS: @@ -265,7 +266,7 @@ public class DetailTypeFormatter { case RACE_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS: return stringMessages.raceAverageSpeedInKnotsTooltip(); case CURRENT_SPEED_OVER_GROUND_IN_KNOTS: - return stringMessages.currentSpeedOverGroundInKnotsTooltip(); + return stringMessages.currentOrAverageSpeedOverGroundInKnotsTooltip(); case RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS: return stringMessages.currentSpeedOverGroundInKnotsTooltip(); case RACE_DISTANCE_TO_LEADER_IN_METERS: 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 ba7657e9af5..f5ab6b223d8 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 @@ -1061,4 +1061,6 @@ public interface StringMessages extends Messages { String totalSize(); String averageMessageSize(); String totalNumberOfOperations(); + String currentOrAverageSpeedOverGroundInKnotsTooltip(); + String currentOrAverageSpeedOverGroundInKnots(); } 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 ecfb042949f..93a6a0dbc8f 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 @@ -1044,4 +1044,6 @@ 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 \ No newline at end of file +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) \ 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 a1ed016ddcc..4b127156325 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 @@ -1039,4 +1039,6 @@ useStartTimeInference=Startzeit aus Startlinienüberquerungen automatisch ableit numberOfQueueMessagesSent=Anzahl verschickter Queue Nachrichten totalSize=Gesamtgröße averageMessageSize=Durchschnittsgrösse einer Nachricht -totalNumberOfOperations=Gesamtanzahl gesendete Operationen \ No newline at end of file +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) \ 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_ru.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties index 371de6a3607..944d3e320db 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 @@ -994,4 +994,6 @@ useStartTimeInference=Use start time inference from start mark passings numberOfQueueMessagesSent=Number of queue messages sent totalSize=Total Size averageMessageSize=Average Size per message -totalNumberOfOperations=Total number of operations \ No newline at end of file +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) \ 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/MultiCompetitorRaceChartSettingsComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MultiCompetitorRaceChartSettingsComponent.java index 32075897f07..598326d3994 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MultiCompetitorRaceChartSettingsComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MultiCompetitorRaceChartSettingsComponent.java @@ -28,7 +28,7 @@ public class MultiCompetitorRaceChartSettingsComponent extends AbstractChartSett availableDetailsTypes.add(DetailType.DISTANCE_TRAVELED); availableDetailsTypes.add(DetailType.VELOCITY_MADE_GOOD_IN_KNOTS); availableDetailsTypes.add(DetailType.GAP_TO_LEADER_IN_SECONDS); - availableDetailsTypes.add(DetailType.CURRENT_SPEED_OVER_GROUND_IN_KNOTS); + availableDetailsTypes.add(DetailType.RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS); availableDetailsTypes.add(DetailType.RACE_RANK); availableDetailsTypes.add(DetailType.REGATTA_RANK); availableDetailsTypes.add(DetailType.DISTANCE_TO_START_LINE); 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 698cb19fade..39b70218885 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 @@ -2491,7 +2491,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S try { TrackedLegOfCompetitor trackedLeg = trackedRace.getTrackedLeg(competitor, timePoint); switch (dataType) { - case CURRENT_SPEED_OVER_GROUND_IN_KNOTS: + case RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS: final GPSFixTrack track = trackedRace.getTrack(competitor); if (track != null) { SpeedWithBearing speedOverGround = track.getEstimatedSpeed(timePoint); From d00054f0177d0e71894180cf3836c7ae208fbb6e Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 17 Jul 2014 12:35:29 +0200 Subject: [PATCH 146/323] fixed issue with competitor with equal ID in more test cases --- .../sailing/server/gateway/test/AbstractJsonExportTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/AbstractJsonExportTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/AbstractJsonExportTest.java index 234a0145141..2c8bed66f7f 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/AbstractJsonExportTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/AbstractJsonExportTest.java @@ -14,6 +14,7 @@ import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; +import java.util.UUID; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -88,7 +89,7 @@ public abstract class AbstractJsonExportTest { BoatClass boatClass = new BoatClassImpl("505", /* typicallyStartsUpwind */ true); for (int i = 1; i <= numberOfCompetitorsToCreate; i++) { String competitorName = "C" + i; - Competitor competitor = new CompetitorImpl(123, competitorName, Color.RED, new TeamImpl("STG", Collections.singleton( + Competitor competitor = new CompetitorImpl(UUID.randomUUID(), competitorName, Color.RED, new TeamImpl("STG", Collections.singleton( new PersonImpl(competitorName, new NationalityImpl("GER"), /* dateOfBirth */ null, "This is famous "+competitorName)), new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), From 8816eeab7d77cf622321cb39e19e8d40606d6209 Mon Sep 17 00:00:00 2001 From: fmittag Date: Thu, 17 Jul 2014 12:28:36 +0200 Subject: [PATCH 147/323] added header media queries --- .../gwt/home/client/shared/header/Header.css | 2 +- .../gwt/home/client/shared/header/Header.java | 4 +++ .../home/client/shared/header/HeaderLarge.css | 25 +++++++++++++++++++ .../client/shared/header/HeaderResources.java | 8 ++++++ 4 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/HeaderLarge.css diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/Header.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/Header.css index 513a03a0564..befaa556476 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/Header.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/Header.css @@ -17,7 +17,7 @@ float: left; } .logotitle { - display: inline; + display: none; height: 4.333333333333333em; line-height: 4.333333333333333em; color: #f6f9fc; 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 9da82dc1fb3..1716c150233 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.StyleInjector; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.dom.client.KeyPressEvent; @@ -38,7 +39,10 @@ public class Header extends Composite { public Header(PlaceNavigator navigator) { this.navigator = navigator; + HeaderResources.INSTANCE.css().ensureInjected(); + StyleInjector.injectAtEnd("@media (min-width: 50em) { "+HeaderResources.INSTANCE.largeCss().getText()+"}"); + initWidget(uiBinder.createAndBindUi(this)); links = Arrays.asList(new Anchor[] { startPageLink, eventsPageLink, solutionsPageLink }); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/HeaderLarge.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/HeaderLarge.css new file mode 100644 index 00000000000..3a6091ea707 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/HeaderLarge.css @@ -0,0 +1,25 @@ +.logotitle { + display: inline; +} +.siteheader {} +.siteheader_full {} +.logo {} +.sitenavigation {} +.sitenavigation_link {} +.sitenavigation_link:hover, +.sitenavigation_link:focus {} +.sitenavigation_linkactive, +.sitenavigation_link:hover, +.sitenavigation_link:focus {} +.sitenavigation_search {} +.sitenavigationexpanded .searchsmall {} +.sitenavigationexpanded .searchsmall .search_textfield {} +.search {} +.search_textfield {} +.search_button {} +.search_button:hover, +.search_button:focus {} +.searchsmall {} +.searchsmall .search_textfield {} +.searchsmall .search_button {} +.submit {} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/HeaderResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/HeaderResources.java index 11c2c1061d4..bf80474e882 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/HeaderResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/header/HeaderResources.java @@ -3,6 +3,7 @@ package com.sap.sailing.gwt.home.client.shared.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; import com.google.gwt.resources.client.ImageResource; public interface HeaderResources extends ClientBundle { @@ -11,6 +12,13 @@ public interface HeaderResources extends ClientBundle { @Source("com/sap/sailing/gwt/home/client/shared/header/Header.css") LocalCss css(); + public interface MinWidth50emCss extends LocalCss { + } + + @Source("com/sap/sailing/gwt/home/client/shared/header/HeaderLarge.css") + MinWidth50emCss largeCss(); + + @Shared public interface LocalCss extends CssResource { String siteheader(); String siteheader_full(); From 95010a1ffcc7bab2228d07e1d00666d11b791afe Mon Sep 17 00:00:00 2001 From: fmittag Date: Thu, 17 Jul 2014 12:52:30 +0200 Subject: [PATCH 148/323] Updated footer resources and added feedback link --- .../gwt/home/client/shared/footer/Footer.css | 20 ++++------ .../gwt/home/client/shared/footer/Footer.java | 3 ++ .../home/client/shared/footer/Footer.ui.xml | 38 ++++++++++--------- .../client/shared/footer/FooterMedium.css | 18 +++++++++ .../client/shared/footer/FooterResources.java | 8 ++++ 5 files changed, 58 insertions(+), 29 deletions(-) create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/FooterMedium.css diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/Footer.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/Footer.css index 898eb3f9e6c..dac94600d63 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/Footer.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/Footer.css @@ -1,41 +1,37 @@ -/** - * Website footer styling - */ .sitefooter { - font-size: 16px; + font-size: 15px; font-size: 1rem; background: #333; border-top: 5px solid #fff; color: #fff; - line-height: 4.375em; + line-height: 3em; width: 100%; } .sitefooter_copyright { font-weight: 600; - float: left; - margin-right: 1.875em; + margin-right: 2em; } .sitefooter_links { text-align: left; - font-size: 0.9375em; + font-size: 1em; font-weight: 600; text-transform: uppercase; } .sitefooter_links_link { - display: inline-block; + display: block; color: #fff; text-decoration: none; - margin-right: 1.875em; + margin-right: 2em; } .sitefooter_links_link:last-child { margin-right: 0; } .sitefooter_language { - text-align: right; + text-align: left; } .sitefooter_language_link { color: #f0ab00; display: inline-block; cursor: pointer; - margin-left: 0.625em; + margin-left: 0.666666666666667em; } 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 2cd34270ae7..31eae3acd14 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,7 @@ import java.util.Map; import java.util.Map.Entry; import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.StyleInjector; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.http.client.UrlBuilder; import com.google.gwt.i18n.client.LocaleInfo; @@ -35,6 +36,8 @@ public class Footer extends Composite { localeAndLanguages.put("de", "Deutsch"); FooterResources.INSTANCE.css().ensureInjected(); + StyleInjector.injectAtEnd("@media (min-width: 25em) { "+FooterResources.INSTANCE.mediumCss().getText()+"}"); + initWidget(uiBinder.createAndBindUi(this)); 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 3baa3c934b0..b3cbffafd8d 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 @@ -6,22 +6,26 @@ - - + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/FooterMedium.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/FooterMedium.css new file mode 100644 index 00000000000..9bf1be73265 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/FooterMedium.css @@ -0,0 +1,18 @@ +.sitefooter { + line-height: 4.666666666666667em; +} +.sitefooter_copyright { + float: left; +} +.sitefooter_links_link { + display: inline-block; +} +.sitefooter_language { + text-align: right; +} +.sitefooter_copyright {} +.sitefooter_links {} +.sitefooter_links_link {} +.sitefooter_links_link:last-child {} +.sitefooter_language {} +.sitefooter_language_link {} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/FooterResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/FooterResources.java index d1e07dd9cb6..ae4b3ef8c26 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/FooterResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/FooterResources.java @@ -3,6 +3,7 @@ package com.sap.sailing.gwt.home.client.shared.footer; 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 FooterResources extends ClientBundle { public static final FooterResources INSTANCE = GWT.create(FooterResources.class); @@ -10,6 +11,13 @@ public interface FooterResources extends ClientBundle { @Source("com/sap/sailing/gwt/home/client/shared/footer/Footer.css") LocalCss css(); + public interface MinWidth25emCss extends LocalCss { + } + + @Source("com/sap/sailing/gwt/home/client/shared/footer/FooterMedium.css") + MinWidth25emCss mediumCss(); + + @Shared public interface LocalCss extends CssResource { String sitefooter(); String sitefooter_copyright(); From 69c4d7604f5ee79bd6e241c1f9e79a0942f51a02 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 17 Jul 2014 13:03:11 +0200 Subject: [PATCH 149/323] avoid NPE in the unlikely case where two timer tasks are scheduled concurrently or the buffer got sent early based on size --- .../impl/ReplicationServiceImpl.java | 74 +++++++++++-------- 1 file changed, 43 insertions(+), 31 deletions(-) diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java index ebd4db618b0..20e0d469332 100644 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java @@ -317,6 +317,49 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec } } + /** + * Obtains the monitor on {@link #outboundBufferMonitor}, copies the references to the buffers, nulls out + * the buffers, then releases the monitor and broadcasts the buffer. + */ + private void flushBufferToRabbitMQ() { + logger.fine("Running timer task, trying to acquire monitor"); + final byte[] bytesToSend; + final List> classesOfOperationsToSend; + final boolean doSend; + synchronized (outboundBufferMonitor) { + if (outboundBuffer != null) { + logger.fine("Preparing " + outboundBufferClasses.size() + " operations for sending to RabbitMQ exchange"); + try { + outboundObjectBuffer.close(); + logger.fine("Sucessfully closed ObjectOutputStream"); + } catch (IOException e) { + logger.log(Level.SEVERE, "Error trying to replicate " + outboundBufferClasses.size() + + " operations", e); + } + bytesToSend = outboundBuffer.toByteArray(); + logger.fine("Successfully produced bytesToSend array of length " + bytesToSend.length); + classesOfOperationsToSend = outboundBufferClasses; + doSend = true; + outboundBuffer = null; + outboundObjectBuffer = null; + outboundBufferClasses = null; + } else { + logger.fine("No buffer set; probably two timer tasks were scheduled concurrently. No problem, just not sending this time around."); + doSend = false; + bytesToSend = null; + classesOfOperationsToSend = null; + } + } + if (doSend) { + try { + broadcastOperations(bytesToSend, classesOfOperationsToSend); + logger.fine("Successfully handed " + classesOfOperationsToSend.size() + " operations to broadcaster"); + } catch (Exception e) { + logger.log(Level.SEVERE, "Error trying to replicate " + classesOfOperationsToSend.size() + " operations", e); + } + } + } + private void broadcastOperations(byte[] bytesToSend, List> classesOfOperationsToSend) throws IOException { logger.fine("broadcasting "+classesOfOperationsToSend.size()+" operations as "+bytesToSend.length+" bytes"); if (masterChannel != null) { @@ -503,35 +546,4 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec return serverUUID; } - /** - * Obtains the monitor on {@link #outboundBufferMonitor}, copies the references to the buffers, nulls out - * the buffers, then releases the monitor and broadcasts the buffer. - */ - private void flushBufferToRabbitMQ() { - logger.fine("Running timer task, trying to acquire monitor"); - final byte[] bytesToSend; - final List> classesOfOperationsToSend; - synchronized (outboundBufferMonitor) { - logger.fine("Preparing "+outboundBufferClasses.size()+" operations for sending to RabbitMQ exchange"); - try { - outboundObjectBuffer.close(); - logger.fine("Sucessfully closed ObjectOutputStream"); - } catch (IOException e) { - logger.log(Level.SEVERE, "Error trying to replicate "+outboundBufferClasses.size()+" operations", e); - } - bytesToSend = outboundBuffer.toByteArray(); - logger.fine("Successfully produced bytesToSend array of length "+bytesToSend.length); - classesOfOperationsToSend = outboundBufferClasses; - outboundBuffer = null; - outboundObjectBuffer = null; - outboundBufferClasses = null; - } - try { - broadcastOperations(bytesToSend, classesOfOperationsToSend); - logger.fine("Successfully handed "+classesOfOperationsToSend.size()+" operations to broadcaster"); - } catch (Exception e) { - logger.log(Level.SEVERE, "Error trying to replicate "+classesOfOperationsToSend.size()+" operations", e); - } - } - } From a0dada2b0bf41fad285ae810f535002934815e37 Mon Sep 17 00:00:00 2001 From: fmittag Date: Thu, 17 Jul 2014 13:10:28 +0200 Subject: [PATCH 150/323] hidded some event summary roperties which we don't know right now (r.g. countries count) --- .../recent/EventsOverviewRecentYear.java | 12 ++++++------ .../recent/EventsOverviewRecentYear.ui.xml | 18 ++++-------------- 2 files changed, 10 insertions(+), 20 deletions(-) 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 b9faaae5763..ce1b4840166 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 @@ -24,9 +24,9 @@ public class EventsOverviewRecentYear extends Composite { @UiField SpanElement year; @UiField SpanElement eventsCount; - @UiField SpanElement countriesCount; - @UiField SpanElement sailorsCount; - @UiField SpanElement trackedRacesCount; +// @UiField SpanElement countriesCount; +// @UiField SpanElement sailorsCount; +// @UiField SpanElement trackedRacesCount; @UiField HTMLPanel recentEventsTeaserPanel; @UiField HTMLPanel contentDiv; @UiField HTMLPanel headerDiv; @@ -39,9 +39,9 @@ public class EventsOverviewRecentYear extends Composite { 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."); +// this.countriesCount.setInnerText("tbd."); +// this.sailorsCount.setInnerText("tbd."); +// this.trackedRacesCount.setInnerText("tbd."); for(EventBaseDTO eventDTO: events) { RecentEvent recentEvent = new RecentEvent(navigator); 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 316091d492f..d3513d36850 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 @@ -6,30 +6,19 @@ - -
-
+

-
+
+ From 44d3079ab146a20a692e13d44c7140248e613ab9 Mon Sep 17 00:00:00 2001 From: fmittag Date: Thu, 17 Jul 2014 13:11:54 +0200 Subject: [PATCH 151/323] removed unused annotation --- .../sailing/gwt/home/client/shared/footer/FooterResources.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/FooterResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/FooterResources.java index ae4b3ef8c26..4367af2c3e5 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/FooterResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/footer/FooterResources.java @@ -3,6 +3,7 @@ package com.sap.sailing.gwt.home.client.shared.footer; 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.NotStrict; import com.google.gwt.resources.client.CssResource.Shared; public interface FooterResources extends ClientBundle { @@ -13,7 +14,7 @@ public interface FooterResources extends ClientBundle { public interface MinWidth25emCss extends LocalCss { } - +@NotStrict @Source("com/sap/sailing/gwt/home/client/shared/footer/FooterMedium.css") MinWidth25emCss mediumCss(); From 4332378ee2b4535c30846ccd244fdf58162efe68 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 17 Jul 2014 13:18:49 +0200 Subject: [PATCH 152/323] producing a separate GetRaceMapDataAction to fetch the "head" of the tail separately --- .../gwt/ui/client/shared/racemap/RaceMap.java | 36 ++++++++++++++++++- .../gwt/ui/server/SailingServiceImpl.java | 6 ++-- 2 files changed, 38 insertions(+), 4 deletions(-) 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 3a8c18792be..a63fb735b9b 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 @@ -101,6 +101,7 @@ import com.sap.sailing.gwt.ui.shared.racemap.GoogleMapAPIKey; 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.gwt.client.async.AsyncActionsExecutor; import com.sap.sse.gwt.client.player.TimeListener; import com.sap.sse.gwt.client.player.Timer; @@ -445,10 +446,15 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe if (race != null) { final com.sap.sse.common.Util.Triple, Map, Map> fromAndToAndOverlap = fixesAndTails.computeFromAndTo(newTime, competitorsToShow, settings.getEffectiveTailLengthInMilliseconds()); - final int requestID = ++boatPositionRequestIDCounter; + int requestID = ++boatPositionRequestIDCounter; // TODO bug2026: For those competitors for which the tails don't overlap (and therefore will be replaced by the new tail coming from the server) // we expect some potential delay in computing the full tail. Therefore, in those cases we fire two requests: one fetching only the // boat positions at newTime with zero tail length; and another one fetching everything else. + GetRaceMapDataAction getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping = getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping(fromAndToAndOverlap, race, newTime); + asyncActionsExecutor.execute(getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping, GET_RACE_MAP_DATA_CATEGORY, + getRaceMapDataCallback(oldTime, newTime, fromAndToAndOverlap.getC(), competitorsToShow, requestID)); + // 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 + requestID = ++boatPositionRequestIDCounter; GetRaceMapDataAction getRaceMapDataAction = new GetRaceMapDataAction(sailingService, competitorSelection.getAllCompetitors(), race, newTime, fromAndToAndOverlap.getA(), fromAndToAndOverlap.getB(), /* extrapolate */ true); asyncActionsExecutor.execute(getRaceMapDataAction, GET_RACE_MAP_DATA_CATEGORY, @@ -504,6 +510,34 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe } } + /** + * We assume that overlapping segments usually don't require a lot of loading time as the most typical case will be to update a longer + * tail with a few new fixes that were received since the last time tick. Non-overlapping position requests typically occur for the + * first request when no fix at all is known for the competitor yet, and when the user has radically moved the time slider to some + * other time such that given the current tail length setting the new tail segment does not overlap with the old one, requiring a full + * load of the entire tail data for that competitor.

+ * + * For the non-overlapping requests, this method creates a separate request which only loads boat positions, quick ranks, sidelines and + * mark positions for the zero-length interval at newTime, assuming that this will work fairly fast and in particular in + * O(1) time regardless of tail length, compared to fetching the entire tail for all competitors. + */ + private GetRaceMapDataAction getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping( + Triple, Map, Map> fromAndToAndOverlap, + RegattaAndRaceIdentifier race, Date newTime) { + Map fromTimes = new HashMap<>(); + Map toTimes = new HashMap<>(); + for (Map.Entry e : fromAndToAndOverlap.getC().entrySet()) { + if (!e.getValue()) { + // no overlap; add competitor to request + fromTimes.put(e.getKey(), newTime); + toTimes.put(e.getKey(), newTime); + } + } + GetRaceMapDataAction result = new GetRaceMapDataAction(sailingService, competitorSelection.getAllCompetitors(), + race, newTime, fromTimes, toTimes, /* extrapolate */true); + return result; + } + private AsyncCallback getRaceMapDataCallback( final Date oldTime, final Date newTime, 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 698cb19fade..70d804912c3 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 @@ -1486,8 +1486,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S Iterator fixIter = track.getFixesIterator(fromTimePoint, /* inclusive */true); while (fixIter.hasNext()) { GPSFixMoving fix = fixIter.next(); - if (fix.getTimePoint().compareTo(toTimePointExcluding) < 0) { - if (logger.getLevel() != null && logger.getLevel().equals(Level.FINEST)) { + if (fix.getTimePoint().before(toTimePointExcluding)) { + if (logger.isLoggable(Level.FINEST)) { logger.finest(""+competitor.getName()+": " + fix); } fixes.add(fix); @@ -1514,7 +1514,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S Iterator fixIter = fixes.iterator(); if (fixIter.hasNext()) { GPSFixMoving fix = fixIter.next(); - while (fix != null && (fix.getTimePoint().compareTo(toTimePointExcluding) < 0 || + while (fix != null && (fix.getTimePoint().before(toTimePointExcluding) || (fix.getTimePoint().equals(toTimePointExcluding) && toTimePointExcluding.equals(fromTimePoint)))) { Wind wind = trackedRace.getWind(fix.getPosition(), fix.getTimePoint()); final SpeedWithBearing estimatedSpeed = track.getEstimatedSpeed(fix.getTimePoint()); From 5e7d0389834b66263288925155f1152d3bc122d9 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 17 Jul 2014 13:34:56 +0200 Subject: [PATCH 153/323] commented code merged from newhome that produced unused warnings --- .../regattaraces/EventRegattaRacesRace.java | 5 ++--- .../regattaraces/EventRegattaRacesRaceday.java | 9 +++++---- .../gwt/ui/client/shared/racemap/RaceMap.java | 18 +++++++++++++----- 3 files changed, 20 insertions(+), 12 deletions(-) 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 3c14c4bd34f..ef34705d61a 100644 --- 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 @@ -2,14 +2,12 @@ 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.i18n.shared.DateTimeFormat; 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.domain.common.dto.FleetDTO; import com.sap.sailing.domain.common.dto.RaceColumnDTO; -import com.sap.sailing.domain.common.dto.RaceDTO; import com.sap.sailing.gwt.home.client.place.event.EventPageNavigator; public class EventRegattaRacesRace extends Composite { @@ -20,7 +18,8 @@ public class EventRegattaRacesRace extends Composite { @UiField DivElement fleetColor; - private final DateTimeFormat raceTimeFormat = DateTimeFormat.getFormat("EEE, h:mm a"); + // TODO will be used soon: +// private final DateTimeFormat raceTimeFormat = DateTimeFormat.getFormat("EEE, h:mm a"); public EventRegattaRacesRace(FleetDTO fleet, RaceColumnDTO raceColumn, EventPageNavigator pageNavigator) { EventRegattaRacesResources.INSTANCE.css().ensureInjected(); 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 2a6ac69e8b2..f38514e6277 100644 --- 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 @@ -20,12 +20,13 @@ public class EventRegattaRacesRaceday extends Composite { @UiField SpanElement raceDayDate; @UiField HTMLPanel racesPanel; - private final Timer timerForClientServerOffset; - private final EventPageNavigator pageNavigator; + // TODO will be used soon: +// private final Timer timerForClientServerOffset; +// private final EventPageNavigator pageNavigator; public EventRegattaRacesRaceday(RaceGroupSeriesDTO series, Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { - this.timerForClientServerOffset = timerForClientServerOffset; - this.pageNavigator = pageNavigator; +// this.timerForClientServerOffset = timerForClientServerOffset; +// this.pageNavigator = 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/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 a63fb735b9b..86ce5967fd7 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 @@ -451,10 +451,13 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe // we expect some potential delay in computing the full tail. Therefore, in those cases we fire two requests: one fetching only the // boat positions at newTime with zero tail length; and another one fetching everything else. GetRaceMapDataAction getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping = getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping(fromAndToAndOverlap, race, newTime); - asyncActionsExecutor.execute(getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping, GET_RACE_MAP_DATA_CATEGORY, - getRaceMapDataCallback(oldTime, newTime, fromAndToAndOverlap.getC(), competitorsToShow, requestID)); - // 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 - requestID = ++boatPositionRequestIDCounter; + if (getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping != null) { + asyncActionsExecutor.execute(getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping, GET_RACE_MAP_DATA_CATEGORY, + getRaceMapDataCallback(oldTime, newTime, fromAndToAndOverlap.getC(), competitorsToShow, requestID)); + requestID = ++boatPositionRequestIDCounter; + } + // 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); asyncActionsExecutor.execute(getRaceMapDataAction, GET_RACE_MAP_DATA_CATEGORY, @@ -533,8 +536,13 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe toTimes.put(e.getKey(), newTime); } } - GetRaceMapDataAction result = new GetRaceMapDataAction(sailingService, competitorSelection.getAllCompetitors(), + final GetRaceMapDataAction result; + if (!fromTimes.isEmpty()) { + result = new GetRaceMapDataAction(sailingService, competitorSelection.getAllCompetitors(), race, newTime, fromTimes, toTimes, /* extrapolate */true); + } else { + result = null; + } return result; } From 2b36374b2f04753ecb3d3f6c524dfa179340cbf9 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 17 Jul 2014 13:49:27 +0200 Subject: [PATCH 154/323] moved CountingOutputStream again away from com.sap.sse.common because its base class FilterOutputStream is not available in GWT's JRE emulation --- .../src/com/sap/sailing/util/impl}/CountingOutputStream.java | 2 +- .../sap/sailing/server/replication/impl/ReplicationServlet.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename java/{com.sap.sse.common/src/com/sap/sse/common => com.sap.sailing.domain/src/com/sap/sailing/util/impl}/CountingOutputStream.java (98%) diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/CountingOutputStream.java b/java/com.sap.sailing.domain/src/com/sap/sailing/util/impl/CountingOutputStream.java similarity index 98% rename from java/com.sap.sse.common/src/com/sap/sse/common/CountingOutputStream.java rename to java/com.sap.sailing.domain/src/com/sap/sailing/util/impl/CountingOutputStream.java index 2a345b90082..8f77c8e4275 100755 --- a/java/com.sap.sse.common/src/com/sap/sse/common/CountingOutputStream.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/util/impl/CountingOutputStream.java @@ -1,4 +1,4 @@ -package com.sap.sse.common; +package com.sap.sailing.util.impl; import java.io.FilterOutputStream; import java.io.IOException; diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServlet.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServlet.java index a3c0a7223bb..5e7c90e1f5f 100755 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServlet.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServlet.java @@ -24,7 +24,7 @@ import com.rabbitmq.client.Channel; import com.sap.sailing.server.RacingEventService; import com.sap.sailing.server.gateway.SailingServerHttpServlet; import com.sap.sailing.server.replication.ReplicationService; -import com.sap.sse.common.CountingOutputStream; +import com.sap.sailing.util.impl.CountingOutputStream; /** * As the response to any type of GET request, sends a serialized copy of the {@link RacingEventService} to From c2cb573ccec3bd29ac8d357c71afa99385a8c6b4 Mon Sep 17 00:00:00 2001 From: Carolin Joppien Date: Thu, 17 Jul 2014 14:54:22 +0000 Subject: [PATCH 155/323] Updated ganalytics (markdown) --- wiki/ganalytics.md | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/wiki/ganalytics.md b/wiki/ganalytics.md index f6915669c6a..c2e9ea8dfa3 100644 --- a/wiki/ganalytics.md +++ b/wiki/ganalytics.md @@ -4,16 +4,48 @@ GAnalytics Homepage: http://www.google.com/intl/en/analytics/
GAnalytics Account: gmsponsorship@gmail.com
GAnalytics PW:

-GAnalytics Tracking ID: **UA-52789785-1**
+GAnalytics Tracking ID: **UA-52789785-1** (to make tracking possible for the webpage the tracking code has to be embeedded in the html code)

**track events (clicks on links):**
-insert onClick() method for each link
-Syntax: _onClick="_gaq.push(['_trackEvent', category, action, label])_
+1. for event tracking please embed the following javascript-code in the html code of the web page to create the object _gaq +When calling method _gaq.push(['_setAccount', 'UA-XXXXX-X']) please use the specific ganalytics tracking code as param + +Coding: + + +2. to embed tracking for hyperlinks use the onClick() method for each link
+Syntax: **track page load time:**
see: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_gat.GA_Tracker_._trackPageview **useful information for web page tracking with ganalytics:**
-https://developers.google.com/analytics/devguides/collection/gajs/ +https://developers.google.com/analytics/devguides/collection/gajs/ \ No newline at end of file From 77853883a3e83bef00b9da06ce0e60195d5dd057 Mon Sep 17 00:00:00 2001 From: Carolin Joppien Date: Thu, 17 Jul 2014 14:56:06 +0000 Subject: [PATCH 156/323] Updated ganalytics (markdown) --- wiki/ganalytics.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/wiki/ganalytics.md b/wiki/ganalytics.md index c2e9ea8dfa3..308dac80fc1 100644 --- a/wiki/ganalytics.md +++ b/wiki/ganalytics.md @@ -1,4 +1,4 @@ -#Bug 2043
+**Bug 2043
** GAnalytics Homepage: http://www.google.com/intl/en/analytics/
GAnalytics Account: gmsponsorship@gmail.com
@@ -7,8 +7,8 @@ GAnalytics PW:
GAnalytics Tracking ID: **UA-52789785-1** (to make tracking possible for the webpage the tracking code has to be embeedded in the html code)

**track events (clicks on links):**
-1. for event tracking please embed the following javascript-code in the html code of the web page to create the object _gaq -When calling method _gaq.push(['_setAccount', 'UA-XXXXX-X']) please use the specific ganalytics tracking code as param +1. for event tracking please embed the following javascript-code in the html code of the web page to create the object _gaq
+When calling method _gaq.push(['_setAccount', 'UA-XXXXX-X']) please use the specific ganalytics tracking code as param
Coding: +
2. to embed tracking for hyperlinks use the onClick() method for each link
Syntax:
- var _gaq = _gaq || []; _gaq.push(['_setAccount', 'UA-XXXXX-X']); _gaq.push(['_trackPageview']); - (function() { - var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; - ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; - var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); - })(); - + var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; + ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; + var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
-2. to embed tracking for hyperlinks use the onClick() method for each link
+ * * * 2. to embed tracking for hyperlinks use the onClick() method for each link
Syntax:
>( + 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; + } + } + 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/EventView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventView.java index 9c398e791f2..19d2679f743 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventView.java @@ -1,7 +1,12 @@ package com.sap.sailing.gwt.home.client.place.event; +import java.util.List; + import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.ui.shared.RegattaOverviewEntryDTO; public interface EventView { Widget asWidget(); + + void updateEventRaceStates(List racesStateEntries); } 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 d58c4ff9b0b..adc9629fa21 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 @@ -26,6 +26,7 @@ import com.sap.sailing.gwt.ui.raceboard.RaceBoardViewConfiguration; 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.sailing.gwt.ui.shared.StrippedLeaderboardDTO; import com.sap.sse.common.Util.Pair; import com.sap.sse.gwt.client.player.Timer; @@ -105,6 +106,11 @@ public class TabletAndDesktopEventView extends Composite implements EventView, E } } + @Override + public void updateEventRaceStates(List racesStateEntries) { + + } + @Override public void goToOverview() { setVisibleEventElement(eventOverview); 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 1b8e66838d0..f2259db96b1 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 @@ -2,13 +2,13 @@ package com.sap.sailing.gwt.home.client.place.event.regatta; 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.gwt.ui.shared.RaceGroupSeriesDTO; -public class RegattaPhase { +public class RegattaPhase extends UIObject { private static RegattaPhaseUiBinder uiBinder = GWT.create(RegattaPhaseUiBinder.class); interface RegattaPhaseUiBinder extends UiBinder { @@ -17,11 +17,9 @@ public class RegattaPhase { @UiField SpanElement phaseName; @UiField DivElement phaseRacesPanel; - private DivElement root; - public RegattaPhase(RaceGroupSeriesDTO series) { RegattaResources.INSTANCE.css().ensureInjected(); - root = uiBinder.createAndBindUi(this); + setElement(uiBinder.createAndBindUi(this)); phaseName.setInnerText(series.getName()); @@ -30,8 +28,4 @@ public class RegattaPhase { phaseRacesPanel.appendChild(race.getElement()); } } - - public Element getElement() { - return root; - } } 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 a8fa6667745..771ffee0f25 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 @@ -2,11 +2,11 @@ package com.sap.sailing.gwt.home.client.place.event.regatta; 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.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.UIObject; -public class RegattaPhaseRace { +public class RegattaPhaseRace extends UIObject { private static RegattaPhaseRaceUiBinder uiBinder = GWT.create(RegattaPhaseRaceUiBinder.class); interface RegattaPhaseRaceUiBinder extends UiBinder { @@ -14,17 +14,10 @@ public class RegattaPhaseRace { @UiField DivElement raceStatus; - private DivElement root; - public RegattaPhaseRace() { RegattaResources.INSTANCE.css().ensureInjected(); - root = uiBinder.createAndBindUi(this); + setElement(uiBinder.createAndBindUi(this)); raceStatus.setAttribute("data-status", "raceStatus"); } - - public Element getElement() { - return root; - } - } 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 6b403e94de5..9f45168686f 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 @@ -4,6 +4,9 @@ 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.Style.Display; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.ui.Composite; @@ -24,7 +27,10 @@ public class EventRegattaList extends Composite { interface EventRegattaHeaderUiBinder extends UiBinder { } + @UiField DivElement regattaGroupsNavigationPanel; + @UiField DivElement regattaListNavgiationDiv; @UiField HTMLPanel regattaListItemPanel; + @UiField AnchorElement allRegattasLink; public EventRegattaList(EventDTO event, List raceGroups, Map> leaderboardsWithLeaderboardGroup, @@ -32,6 +38,8 @@ public class EventRegattaList extends Composite { EventRegattaListResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); + regattaListNavgiationDiv.getStyle().setDisplay(Display.NONE); + for (RaceGroupDTO raceGroup: raceGroups) { Regatta regatta = new Regatta(event, true, timerForClientServerOffset, pageNavigator); Pair leaderboardWithLeaderboardGroup = leaderboardsWithLeaderboardGroup.get(raceGroup.getName()); 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 d99e54e4381..ba3fb0f4f79 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 @@ -6,8 +6,17 @@ -

+ +
+
+
+
+ +
+
+
+
+ +
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 new file mode 100644 index 00000000000..aa4ab30ca37 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListNavigationButton.java @@ -0,0 +1,26 @@ +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/regattalist/EventRegattaListNavigationButton.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListNavigationButton.ui.xml new file mode 100644 index 00000000000..f9772e70697 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattalist/EventRegattaListNavigationButton.ui.xml @@ -0,0 +1,8 @@ + + + + + + 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 73ea2547b07..a26e5dc7561 100644 --- 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,7 +7,6 @@ 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.Event; @@ -16,7 +15,6 @@ import com.google.gwt.user.client.ui.UIObject; import com.sap.sailing.domain.common.dto.FleetDTO; import com.sap.sailing.domain.common.dto.RaceColumnDTO; import com.sap.sailing.domain.common.dto.RaceDTO; -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.StrippedLeaderboardDTO; import com.sap.sse.gwt.client.player.Timer; 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 44400c5dcc5..9d089a486f6 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 @@ -16,10 +16,9 @@ public class LiveEventStageTeaser extends StageTeaser { countdown.getStyle().setDisplay(Display.NONE); countdown.getStyle().setVisibility(Visibility.HIDDEN); - bandCount.setAttribute("data-bandcount", "2"); + bandCount.setAttribute("data-bandcount", "1"); - stageTeaserBandsPanel.add(new StageTeaserBand(event, placeNavigator)); - stageTeaserBandsPanel.add(new StageTeaserBand(event, placeNavigator)); + stageTeaserBandsPanel.add(new LiveStageTeaserBand(event, placeNavigator)); } } 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 new file mode 100644 index 00000000000..b59952af8d9 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/LiveStageTeaserBand.java @@ -0,0 +1,26 @@ +package com.sap.sailing.gwt.home.client.shared.stage; + +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.uibinder.client.UiHandler; +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.EventDTO; + +public class LiveStageTeaserBand extends StageTeaserBand { + + public LiveStageTeaserBand(EventDTO event, PlaceNavigator placeNavigator) { + super(event, placeNavigator); + + bandTitle.setInnerText(event.getName()); + bandSubtitle.setInnerText(EventDatesFormatterUtil.formatDateRangeWithYear(event.startDate, event.endDate)); + + actionLink.setVisible(true); + actionLink.setText("Show event"); + } + + @UiHandler("actionLink") + public void actionLinkClicked(ClickEvent e) { + EventDTO event = getEvent(); + getPlaceNavigator().goToEvent(event.id.toString(), event.getBaseURL()); + } +} 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 new file mode 100644 index 00000000000..d8f257fb907 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/NoActionStageTeaserBand.java @@ -0,0 +1,15 @@ +package com.sap.sailing.gwt.home.client.shared.stage; + +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.EventDTO; + +public class NoActionStageTeaserBand extends StageTeaserBand { + + public NoActionStageTeaserBand(EventDTO event, PlaceNavigator placeNavigator) { + super(event, placeNavigator); + + bandTitle.setInnerText(event.getName()); + bandSubtitle.setInnerText(EventDatesFormatterUtil.formatDateRangeWithYear(event.startDate, event.endDate)); + } +} 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 32c688cfd56..9295922a176 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 @@ -18,6 +18,6 @@ public class PopularEventStageTeaser extends StageTeaser { bandCount.setAttribute("data-bandcount", "1"); - stageTeaserBandsPanel.add(new StageTeaserBand(event, placeNavigator)); + stageTeaserBandsPanel.add(new NoActionStageTeaserBand(event, placeNavigator)); } } 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 b535afb6a24..3e8dc0e8f0a 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 @@ -2,18 +2,15 @@ package com.sap.sailing.gwt.home.client.shared.stage; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.SpanElement; -import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.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.shared.EventDatesFormatterUtil; import com.sap.sailing.gwt.ui.shared.EventDTO; -public class StageTeaserBand extends Composite { +public abstract class StageTeaserBand extends Composite { interface StageTeaserBandUiBinder extends UiBinder { } @@ -34,14 +31,14 @@ public class StageTeaserBand extends Composite { StageResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); - bandTitle.setInnerText(event.getName()); - bandSubtitle.setInnerText(EventDatesFormatterUtil.formatDateRangeWithYear(event.startDate, event.endDate)); - - actionLink.setText("Show event"); + actionLink.setVisible(false); } - - @UiHandler("actionLink") - public void actionLinkClicked(ClickEvent e) { - placeNavigator.goToEvent(event.id.toString(), event.getBaseURL()); + + public PlaceNavigator getPlaceNavigator() { + return placeNavigator; + } + + public EventDTO getEvent() { + return event; } } 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 c3776d401ac..1d2329351af 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,7 +12,6 @@ public class UpcomingEventStageTeaser extends StageTeaser { subtitle.setInnerText(event.venue.getName()); bandCount.setAttribute("data-bandcount", "1"); - stageTeaserBandsPanel.add(new StageTeaserBand(event, placeNavigator)); + stageTeaserBandsPanel.add(new NoActionStageTeaserBand(event, placeNavigator)); } - } 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 8dd534000e0..8464cfab79b 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 @@ -73,6 +73,7 @@ + From 02a5b26c019b9d9c686c130e7d5716b01a025243 Mon Sep 17 00:00:00 2001 From: Jens Rommel Date: Thu, 17 Jul 2014 19:46:21 +0200 Subject: [PATCH 160/323] Removed redundant import causing a warning. --- .../com/sap/sailing/gwt/ui/common/client/CountdownTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/common/client/CountdownTest.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/common/client/CountdownTest.java index f34ecf79d0c..01ed0d076e0 100644 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/common/client/CountdownTest.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/common/client/CountdownTest.java @@ -1,6 +1,5 @@ package com.sap.sailing.gwt.ui.common.client; -import org.junit.Ignore; import org.junit.Test; import com.sap.sailing.domain.common.Duration; @@ -12,7 +11,7 @@ import com.sap.sailing.gwt.ui.common.client.Countdown.Unit; import static org.junit.Assert.*; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.*; public class CountdownTest { From fae641c71266256fb63cd158664e313e83e28f99 Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Thu, 17 Jul 2014 18:42:51 +0000 Subject: [PATCH 161/323] Updated walkthrough (markdown) --- wiki/racelog-tracking/walkthrough.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wiki/racelog-tracking/walkthrough.md b/wiki/racelog-tracking/walkthrough.md index 0d8e3b5a266..97012635385 100644 --- a/wiki/racelog-tracking/walkthrough.md +++ b/wiki/racelog-tracking/walkthrough.md @@ -1,5 +1,7 @@ # Tracking a Race using the RaceLog +![walkthrough](walkthrough/01_regatta.png) + [[walkthrough/01_regatta.png]] *** From 909d7df17fe0f1337080d4f345694dbc60d1c9cb Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Thu, 17 Jul 2014 18:43:11 +0000 Subject: [PATCH 162/323] Updated walkthrough (markdown) --- wiki/racelog-tracking/walkthrough.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/wiki/racelog-tracking/walkthrough.md b/wiki/racelog-tracking/walkthrough.md index 97012635385..0d8e3b5a266 100644 --- a/wiki/racelog-tracking/walkthrough.md +++ b/wiki/racelog-tracking/walkthrough.md @@ -1,7 +1,5 @@ # Tracking a Race using the RaceLog -![walkthrough](walkthrough/01_regatta.png) - [[walkthrough/01_regatta.png]] *** From e1567308c3a27667d4597a4f23df3c3c5daa231d Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 17 Jul 2014 21:30:11 +0200 Subject: [PATCH 163/323] fixing issue with null competitor in MasterDataImportTest by attaching dummy race to regatta, ensuring competitor is serialized --- .../server/test/MasterDataImportTest.java | 3 +-- .../server/masterdata/DummyTrackedRace.java | 21 +++++++++++-------- 2 files changed, 13 insertions(+), 11 deletions(-) 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 1f146b00ee3..d3c46dfee56 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 @@ -573,9 +573,8 @@ public class MasterDataImportTest { RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); TrackedRace trackedRace = new DummyTrackedRace(competitors, regatta, null); - + regatta.addRace(trackedRace.getRace()); // make sure the competitor is serialized together with the regatta raceColumn.setTrackedRace(testFleet1, trackedRace); - // Serialize List groupNamesToExport = new ArrayList(); groupNamesToExport.add(group.getName()); 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 44213111e6e..7102e2cc678 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 @@ -1,6 +1,7 @@ package com.sap.sailing.server.masterdata; import java.io.Serializable; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.NavigableSet; @@ -13,6 +14,7 @@ import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.Sideline; import com.sap.sailing.domain.base.SpeedWithConfidence; import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.base.impl.CourseImpl; import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.NoWindException; @@ -52,25 +54,26 @@ import com.sap.sse.common.Util; public class DummyTrackedRace extends TrackedRaceWithWindEssentials { private static final long serialVersionUID = -11522605089325440L; private Regatta regatta; - private String raceName = "DummyRace"; - private Serializable raceId; - private Iterable competitors = new HashSet(); public DummyTrackedRace(final Iterable competitors, final Regatta regatta, final TrackedRegatta trackedRegatta) { - super(new RaceDefinitionImpl("DummyRace", null, null, competitors), trackedRegatta, EmptyWindStore.INSTANCE, -1); - this.competitors = competitors; + this(competitors, regatta, trackedRegatta, new RaceDefinitionImpl("DummyRace", new CourseImpl("Dummy Course", Collections.emptyList()), + regatta.getBoatClass(), competitors)); + } + + public DummyTrackedRace(final Iterable competitors, final Regatta regatta, final TrackedRegatta trackedRegatta, + RaceDefinition race) { + super(race, trackedRegatta, EmptyWindStore.INSTANCE, -1); this.regatta = regatta; } public DummyTrackedRace(final String raceName, final Serializable raceId) { - super(new RaceDefinitionImpl(raceName, null, null, new HashSet()), null, EmptyWindStore.INSTANCE, -1); - this.raceName = raceName; - this.raceId = raceId; + super(new RaceDefinitionImpl(raceName, new CourseImpl("Dummy Course", Collections. emptyList()), + /* boatClass */ null, new HashSet()), null, EmptyWindStore.INSTANCE, -1); } @Override public RaceDefinition getRace() { - return new RaceDefinitionImpl(raceName, null, null, competitors, raceId); + return race; } @Override From f2bccd1bd3ffa2143b9a27b99668f23446590214 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 17 Jul 2014 21:38:35 +0200 Subject: [PATCH 164/323] added Travemuender Woche and YES to event list --- java/com.sap.sailing.www/index.html | 53 ++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/java/com.sap.sailing.www/index.html b/java/com.sap.sailing.www/index.html index 95b4b2f8ab4..2aab9099981 100644 --- a/java/com.sap.sailing.www/index.html +++ b/java/com.sap.sailing.www/index.html @@ -44,6 +44,31 @@

Upcoming and Live


+ + +

2014

@@ -87,10 +112,30 @@ Official website
-
- -

2014

-
+ + + +
May 30 - June 1 From 60aeced4d830f55af95e1a07862275a6b64369ab Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 17 Jul 2014 22:50:36 +0200 Subject: [PATCH 165/323] bug 2057: showing the advantage line based on the LEG_MIDDLE wind that is now fetched for all legs in the RaceMap GetWindDataAction --- .../sailing/domain/tracking/TrackedLeg.java | 6 + .../domain/tracking/impl/TrackedLegImpl.java | 11 +- .../gwt/ui/actions/GetWindInfoAction.java | 2 +- .../gwt/ui/adminconsole/WindPanel.java | 2 +- .../sailing/gwt/ui/client/SailingService.java | 3 +- .../gwt/ui/client/SailingServiceAsync.java | 5 +- .../gwt/ui/client/shared/racemap/RaceMap.java | 204 +++++++++--------- .../gwt/ui/server/SailingServiceImpl.java | 106 +++++---- .../gwt/ui/shared/WindInfoForRaceDTO.java | 11 + 9 files changed, 204 insertions(+), 146 deletions(-) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedLeg.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedLeg.java index ae2566009b1..9e3afe0911d 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedLeg.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedLeg.java @@ -102,4 +102,10 @@ public interface TrackedLeg extends Serializable { */ Distance getWindwardDistance(Position pos1, Position pos2, TimePoint at, WindPositionMode windPositionMode) throws NoWindException; + /** + * The middle (traveling half the length) of the course middle line, connecting the center of gravity of the leg's start + * waypoint's position at time point at and the position of the leg's end waypoint at time point at. + */ + Position getMiddleOfLeg(TimePoint at); + } 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 8c96a34843f..13307cf8299 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 @@ -198,11 +198,10 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener { * of the first mark at the beginning of the leg and moving half way to the first mark of leg's end. If either of * the mark positions cannot be determined, null is returned. */ + @Override public Position getMiddleOfLeg(TimePoint at) { - Position approximateLegStartPosition = getTrackedRace().getOrCreateTrack( - getLeg().getFrom().getMarks().iterator().next()).getEstimatedPosition(at, false); - Position approximateLegEndPosition = getTrackedRace().getOrCreateTrack( - getLeg().getTo().getMarks().iterator().next()).getEstimatedPosition(at, false); + Position approximateLegStartPosition = getTrackedRace().getApproximatePosition(getLeg().getFrom(), at); + Position approximateLegEndPosition = getTrackedRace().getApproximatePosition(getLeg().getTo(), at); final Position middleOfLeg; if (approximateLegStartPosition == null || approximateLegEndPosition == null) { middleOfLeg = null; @@ -217,12 +216,12 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener { return middleOfLeg; } - public Position getEffectiveWindPosition(Callable positionProvider, TimePoint at, WindPositionMode mode) { + public Position getEffectiveWindPosition(Callable exactPositionProvider, TimePoint at, WindPositionMode mode) { final Position effectivePosition; switch (mode) { case EXACT: try { - effectivePosition = positionProvider.call(); + effectivePosition = exactPositionProvider.call(); } catch (Exception e) { throw new RuntimeException(e); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetWindInfoAction.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetWindInfoAction.java index c7a061c99f6..f22a90ba863 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetWindInfoAction.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetWindInfoAction.java @@ -60,7 +60,7 @@ public class GetWindInfoAction implements AsyncAction { switch (callVariant) { case Variant1: sailingService.getAveragedWindInfo(raceIdentifier, from, millisecondsStepWidth, numberOfFixes, windSourceTypeNames, - onlyUpToNewestEvent, callback); + onlyUpToNewestEvent, /* includeCombinedWindForAllLegMiddles */ true, callback); break; case Variant2: sailingService.getAveragedWindInfo(raceIdentifier, fromDate, toDate, resolutionInMilliseconds, windSourceTypeNames, diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java index ed0c21ac98d..fea21741f1e 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java @@ -576,7 +576,7 @@ public class WindPanel extends FormPanel implements RegattasDisplayer, WindShowe List windSourceTypeNames = new ArrayList(); windSourceTypeNames.add(WindSourceType.COMBINED.name()); sailingService.getAveragedWindInfo(selectedRace, raceDTO.startOfRace, 30000L, 100, windSourceTypeNames, - /* onlyUpToNewestEvent==true means to only use data "based on facts" */ true, new AsyncCallback() { + /* onlyUpToNewestEvent==true means to only use data "based on facts" */ true, /* includeCombinedWindForAllLegMiddles */ false, new AsyncCallback() { @Override public void onFailure(Throwable caught) { 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 a3448849ae4..c3f7b57dbca 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 @@ -127,7 +127,8 @@ public interface SailingService extends RemoteService { void setWind(RegattaAndRaceIdentifier raceIdentifier, WindDTO wind); WindInfoForRaceDTO getAveragedWindInfo(RegattaAndRaceIdentifier raceIdentifier, Date from, long millisecondsStepWidth, - int numberOfFixes, Collection windSourceTypeNames, boolean onlyUpToNewestEvent) throws NoWindException; + int numberOfFixes, Collection windSourceTypeNames, boolean onlyUpToNewestEvent, + boolean includeCombinedWindForAllLegMiddles) throws NoWindException; WindInfoForRaceDTO getAveragedWindInfo(RegattaAndRaceIdentifier raceIdentifier, Date from, Date to, long resolutionInMilliseconds, Collection windSourceTypeNames, boolean onlyUpToNewestEvent); 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 7a4c5a95b0b..af91d6b9132 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 @@ -174,10 +174,13 @@ public interface SailingServiceAsync { * "best effort" readings are provided for the time interval requested, no matter if based on any sensor * evidence or not, regardless of {@link TrackedRace#getTimePointOfNewestEvent() * trackedRace.getTimePointOfNewestEvent()}. + * @param includeCombinedWindForAllLegMiddles + * if true, the result will return non-null results for calls to + * {@link WindInfoForRaceDTO#getCombinedWindOnLegMiddle(int)}. */ void getAveragedWindInfo(RegattaAndRaceIdentifier raceIdentifier, Date from, long millisecondsStepWidth, int numberOfFixes, Collection windSourceTypeNames, boolean onlyUpToNewestEvent, - AsyncCallback callback); + boolean includeCombinedWindForAllLegMiddles, AsyncCallback callback); void setWind(RegattaAndRaceIdentifier raceIdentifier, WindDTO wind, AsyncCallback callback); 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 86ce5967fd7..656686b7c59 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 @@ -165,7 +165,11 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe private Map courseSidelines; - private WindTrackInfoDTO lastCombinedWindTrackInfoDTO; + /** + * Wind data used to display the advantage line. Retrieved by a {@link GetWindInfoAction} execution and used in + * {@link #showAdvantageLine(Iterable, Date)}. + */ + private WindInfoForRaceDTO lastCombinedWindTrackInfoDTO; /** * Manages the cached set of {@link GPSFixDTO}s for the boat positions as well as their graphical counterpart in the @@ -480,6 +484,8 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe public void onSuccess(WindInfoForRaceDTO windInfo) { List> windSourcesToShow = new ArrayList>(); if (windInfo != null) { + lastCombinedWindTrackInfoDTO = windInfo; + showAdvantageLine(competitorsToShow, newTime); for (WindSource windSource: windInfo.windTrackInfoByWindSource.keySet()) { WindTrackInfoDTO windTrackInfoDTO = windInfo.windTrackInfoByWindSource.get(windSource); switch (windSource.getType()) { @@ -491,10 +497,6 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe break; case COMBINED: showCombinedWindOnMap(windSource, windTrackInfoDTO); - if (windTrackInfoDTO != null) { - lastCombinedWindTrackInfoDTO = windTrackInfoDTO; - showAdvantageLine(competitorsToShow, newTime); - } break; default: // Which wind sources are requested is defined in a list above this @@ -581,6 +583,8 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe showCourseMarksOnMap(raceMapDataDTO.coursePositions); showCourseSidelinesOnMap(raceMapDataDTO.courseSidelines); showStartAndFinishLines(raceMapDataDTO.coursePositions); + // even though the wind data is retrieved by a separate call, re-draw the advantage line because it needs to + // adjust to new boat positions showAdvantageLine(competitorsToShow, newTime); // Rezoom the map @@ -838,7 +842,8 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe /** * Returns a pair whose first component is the leg number (one-based) of the competitor returned as the second component. */ - private com.sap.sse.common.Util.Pair getLeadingVisibleCompetitorInfo(Iterable competitorsToShow) { + private com.sap.sse.common.Util.Pair getLeadingVisibleCompetitorWithOneBasedLegNumber( + Iterable competitorsToShow) { CompetitorDTO leadingCompetitorDTO = null; int legOfLeaderCompetitor = -1; // this only works because the quickRanks are sorted @@ -853,103 +858,108 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe } private void showAdvantageLine(Iterable competitorsToShow, Date date) { - if (map != null && lastRaceTimesInfo != null && quickRanks != null && lastCombinedWindTrackInfoDTO != null - && !lastCombinedWindTrackInfoDTO.windFixes.isEmpty()) { + if (map != null && lastRaceTimesInfo != null && quickRanks != null && lastCombinedWindTrackInfoDTO != null) { boolean drawAdvantageLine = false; if (settings.getHelpLinesSettings().isVisible(HelpLineTypes.ADVANTAGELINE)) { // find competitor with highest rank - com.sap.sse.common.Util.Pair visibleLeaderInfo = getLeadingVisibleCompetitorInfo(competitorsToShow); - // the boat fix may be null; may mean that no positions were loaded yet for the leading visible boat; - // don't show anything - GPSFixDTO lastBoatFix = null; - boolean isVisibleLeaderInfoComplete = false; - boolean isLegTypeKnown = false; - if (visibleLeaderInfo != null && visibleLeaderInfo.getA() > 0 - && visibleLeaderInfo.getA() <= lastRaceTimesInfo.getLegInfos().size()) { - isVisibleLeaderInfoComplete = true; - LegInfoDTO legInfoDTO = lastRaceTimesInfo.getLegInfos().get(visibleLeaderInfo.getA() - 1); - if (legInfoDTO.legType != null) { - isLegTypeKnown = true; - } - lastBoatFix = getBoatFix(visibleLeaderInfo.getB(), date); - } - if (isVisibleLeaderInfoComplete && isLegTypeKnown && lastBoatFix != null) { - LegInfoDTO legInfoDTO = lastRaceTimesInfo.getLegInfos().get(visibleLeaderInfo.getA() - 1); - double advantageLineLengthInKm = 1.0; // TODO this should probably rather scale with the visible - // area of the map; bug 616 - double distanceFromBoatPositionInKm = visibleLeaderInfo.getB().getBoatClass().getHullLengthInMeters() / 1000.; // one hull length - // implement and use Position.translateRhumb() - double bearingOfBoatInDeg = lastBoatFix.speedWithBearing.bearingInDegrees; - LatLng boatPosition = LatLng.newInstance(lastBoatFix.position.latDeg, lastBoatFix.position.lngDeg); - LatLng posAheadOfFirstBoat = calculatePositionAlongRhumbline(boatPosition, bearingOfBoatInDeg, - distanceFromBoatPositionInKm); - final WindDTO windFix = lastCombinedWindTrackInfoDTO.windFixes.get(0); - double bearingOfCombinedWindInDeg = windFix.trueWindBearingDeg; - double rotatedBearingDeg1 = 0.0; - double rotatedBearingDeg2 = 0.0; - switch (legInfoDTO.legType) { - case UPWIND: - case DOWNWIND: { - rotatedBearingDeg1 = bearingOfCombinedWindInDeg + 90.0; - if (rotatedBearingDeg1 >= 360.0) { - rotatedBearingDeg1 -= 360.0; - } - rotatedBearingDeg2 = bearingOfCombinedWindInDeg - 90.0; - if (rotatedBearingDeg2 < 0.0) { - rotatedBearingDeg2 += 360.0; + com.sap.sse.common.Util.Pair visibleLeaderInfo = getLeadingVisibleCompetitorWithOneBasedLegNumber(competitorsToShow); + // get wind at middle of leg for leading visible competitor + WindTrackInfoDTO windDataForLegMiddle = lastCombinedWindTrackInfoDTO.getCombinedWindOnLegMiddle(visibleLeaderInfo.getA()-1); + if (windDataForLegMiddle != null && !windDataForLegMiddle.windFixes.isEmpty()) { + // the boat fix may be null; may mean that no positions were loaded yet for the leading visible boat; + // don't show anything + GPSFixDTO lastBoatFix = null; + boolean isVisibleLeaderInfoComplete = false; + boolean isLegTypeKnown = false; + if (visibleLeaderInfo != null && visibleLeaderInfo.getA() > 0 + && visibleLeaderInfo.getA() <= lastRaceTimesInfo.getLegInfos().size()) { + isVisibleLeaderInfoComplete = true; + LegInfoDTO legInfoDTO = lastRaceTimesInfo.getLegInfos().get(visibleLeaderInfo.getA() - 1); + if (legInfoDTO.legType != null) { + isLegTypeKnown = true; } + lastBoatFix = getBoatFix(visibleLeaderInfo.getB(), date); } - break; - case REACHING: { - rotatedBearingDeg1 = legInfoDTO.legBearingInDegrees + 90.0; - if (rotatedBearingDeg1 >= 360.0) { - rotatedBearingDeg1 -= 360.0; - } - rotatedBearingDeg2 = legInfoDTO.legBearingInDegrees - 90.0; - if (rotatedBearingDeg2 < 0.0) { - rotatedBearingDeg2 += 360.0; - } - } - break; - } - LatLng advantageLinePos1 = calculatePositionAlongRhumbline(posAheadOfFirstBoat, rotatedBearingDeg1, - advantageLineLengthInKm / 2.0); - LatLng advantageLinePos2 = calculatePositionAlongRhumbline(posAheadOfFirstBoat, rotatedBearingDeg2, - advantageLineLengthInKm / 2.0); - - if (advantageLine == null) { - PolylineOptions options = PolylineOptions.newInstance(); - options.setClickable(true); - options.setGeodesic(true); - options.setStrokeColor("#000000"); - options.setStrokeWeight(1); - options.setStrokeOpacity(0.5); - - advantageLine = Polyline.newInstance(options); - MVCArray pointsAsArray = MVCArray.newInstance(); - pointsAsArray.insertAt(0, advantageLinePos1); - pointsAsArray.insertAt(1, advantageLinePos2); - advantageLine.setPath(pointsAsArray); - - advantageLineMouseOverHandler = new AdvantageLineMouseOverMapHandler(bearingOfCombinedWindInDeg, - new Date(windFix.measureTimepoint)); - advantageLine.addMouseOverHandler(advantageLineMouseOverHandler); - advantageLine.addMouseOutMoveHandler(new MouseOutMapHandler() { - @Override - public void onEvent(MouseOutMapEvent event) { - map.setTitle(""); + if (isVisibleLeaderInfoComplete && isLegTypeKnown && lastBoatFix != null) { + LegInfoDTO legInfoDTO = lastRaceTimesInfo.getLegInfos().get(visibleLeaderInfo.getA() - 1); + double advantageLineLengthInKm = 1.0; // TODO this should probably rather scale with the visible + // area of the map; bug 616 + double distanceFromBoatPositionInKm = visibleLeaderInfo.getB().getBoatClass() + .getHullLengthInMeters() / 1000.; // one hull length + // implement and use Position.translateRhumb() + double bearingOfBoatInDeg = lastBoatFix.speedWithBearing.bearingInDegrees; + LatLng boatPosition = LatLng.newInstance(lastBoatFix.position.latDeg, + lastBoatFix.position.lngDeg); + LatLng posAheadOfFirstBoat = calculatePositionAlongRhumbline(boatPosition, bearingOfBoatInDeg, + distanceFromBoatPositionInKm); + final WindDTO windFix = windDataForLegMiddle.windFixes.get(0); + double bearingOfCombinedWindInDeg = windFix.trueWindBearingDeg; + double rotatedBearingDeg1 = 0.0; + double rotatedBearingDeg2 = 0.0; + switch (legInfoDTO.legType) { + case UPWIND: + case DOWNWIND: { + rotatedBearingDeg1 = bearingOfCombinedWindInDeg + 90.0; + if (rotatedBearingDeg1 >= 360.0) { + rotatedBearingDeg1 -= 360.0; } - }); - advantageLine.setMap(map); - } else { - advantageLine.getPath().removeAt(1); - advantageLine.getPath().removeAt(0); - advantageLine.getPath().insertAt(0, advantageLinePos1); - advantageLine.getPath().insertAt(1, advantageLinePos2); - advantageLineMouseOverHandler.setTrueWindBearing(bearingOfCombinedWindInDeg); - advantageLineMouseOverHandler.setDate(new Date(windFix.measureTimepoint)); + rotatedBearingDeg2 = bearingOfCombinedWindInDeg - 90.0; + if (rotatedBearingDeg2 < 0.0) { + rotatedBearingDeg2 += 360.0; + } + } + break; + case REACHING: { + rotatedBearingDeg1 = legInfoDTO.legBearingInDegrees + 90.0; + if (rotatedBearingDeg1 >= 360.0) { + rotatedBearingDeg1 -= 360.0; + } + rotatedBearingDeg2 = legInfoDTO.legBearingInDegrees - 90.0; + if (rotatedBearingDeg2 < 0.0) { + rotatedBearingDeg2 += 360.0; + } + } + break; + } + LatLng advantageLinePos1 = calculatePositionAlongRhumbline(posAheadOfFirstBoat, + rotatedBearingDeg1, advantageLineLengthInKm / 2.0); + LatLng advantageLinePos2 = calculatePositionAlongRhumbline(posAheadOfFirstBoat, + rotatedBearingDeg2, advantageLineLengthInKm / 2.0); + + if (advantageLine == null) { + PolylineOptions options = PolylineOptions.newInstance(); + options.setClickable(true); + options.setGeodesic(true); + options.setStrokeColor("#000000"); + options.setStrokeWeight(1); + options.setStrokeOpacity(0.5); + + advantageLine = Polyline.newInstance(options); + MVCArray pointsAsArray = MVCArray.newInstance(); + pointsAsArray.insertAt(0, advantageLinePos1); + pointsAsArray.insertAt(1, advantageLinePos2); + advantageLine.setPath(pointsAsArray); + + advantageLineMouseOverHandler = new AdvantageLineMouseOverMapHandler( + bearingOfCombinedWindInDeg, new Date(windFix.measureTimepoint)); + advantageLine.addMouseOverHandler(advantageLineMouseOverHandler); + advantageLine.addMouseOutMoveHandler(new MouseOutMapHandler() { + @Override + public void onEvent(MouseOutMapEvent event) { + map.setTitle(""); + } + }); + advantageLine.setMap(map); + } else { + advantageLine.getPath().removeAt(1); + advantageLine.getPath().removeAt(0); + advantageLine.getPath().insertAt(0, advantageLinePos1); + advantageLine.getPath().insertAt(1, advantageLinePos2); + advantageLineMouseOverHandler.setTrueWindBearing(bearingOfCombinedWindInDeg); + advantageLineMouseOverHandler.setDate(new Date(windFix.measureTimepoint)); + } + drawAdvantageLine = true; } - drawAdvantageLine = true; } } if (!drawAdvantageLine) { @@ -972,7 +982,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe private void showStartAndFinishLines(final CoursePositionsDTO courseDTO) { if (map != null && courseDTO != null && lastRaceTimesInfo != null) { - com.sap.sse.common.Util.Pair leadingVisibleCompetitorInfo = getLeadingVisibleCompetitorInfo(getCompetitorsToShow()); + com.sap.sse.common.Util.Pair leadingVisibleCompetitorInfo = getLeadingVisibleCompetitorWithOneBasedLegNumber(getCompetitorsToShow()); int legOfLeadingCompetitor = leadingVisibleCompetitorInfo == null ? -1 : leadingVisibleCompetitorInfo.getA(); int numberOfLegs = lastRaceTimesInfo.legInfos.size(); // draw the start line 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 2894aa3c7e4..019ce988e3d 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 @@ -1281,12 +1281,12 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S */ @Override public WindInfoForRaceDTO getAveragedWindInfo(RegattaAndRaceIdentifier raceIdentifier, Date from, long millisecondsStepWidth, - int numberOfFixes, Collection windSourceTypeNames, boolean onlyUpToNewestEvent) + int numberOfFixes, Collection windSourceTypeNames, boolean onlyUpToNewestEvent, boolean includeCombinedWindForAllLegMiddles) throws NoWindException { assert from != null; TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier); WindInfoForRaceDTO result = getAveragedWindInfo(new MillisecondsTimePoint(from), millisecondsStepWidth, numberOfFixes, - windSourceTypeNames, trackedRace, /* onlyUpToNewestEvent */ true); + windSourceTypeNames, trackedRace, /* onlyUpToNewestEvent */ true, includeCombinedWindForAllLegMiddles); return result; } @@ -1298,9 +1298,13 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S * "best effort" readings are provided for the time interval requested, no matter if based on any sensor * evidence or not, regardless of {@link TrackedRace#getTimePointOfNewestEvent() * trackedRace.getTimePointOfNewestEvent()}. + * @param includeCombinedWindForAllLegMiddles + * if true, the result will return non-null results for calls to + * {@link WindInfoForRaceDTO#getCombinedWindOnLegMiddle(int)}. */ private WindInfoForRaceDTO getAveragedWindInfo(TimePoint from, long millisecondsStepWidth, int numberOfFixes, - Collection windSourceTypeNames, TrackedRace trackedRace, boolean onlyUpToNewestEvent) { + Collection windSourceTypeNames, TrackedRace trackedRace, boolean onlyUpToNewestEvent, + boolean includeCombinedWindForAllLegMiddles) { WindInfoForRaceDTO result = null; if (trackedRace != null) { TimePoint newestEvent = trackedRace.getTimePointOfNewestEvent(); @@ -1315,51 +1319,75 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S result.windTrackInfoByWindSource = windTrackInfoDTOs; List windSourcesToDeliver = new ArrayList(); Util.addAll(trackedRace.getWindSources(), windSourcesToDeliver); - windSourcesToDeliver.add(new WindSourceImpl(WindSourceType.COMBINED)); + final WindSource combinedWindSource = new WindSourceImpl(WindSourceType.COMBINED); + windSourcesToDeliver.add(combinedWindSource); for (WindSource windSource : windSourcesToDeliver) { // TODO consider parallelizing if (windSourceTypeNames == null || windSourceTypeNames.contains(windSource.getType().name())) { - WindTrackInfoDTO windTrackInfoDTO = new WindTrackInfoDTO(); + WindTrackInfoDTO windTrackInfoDTO = createWindTrackInfoDTO(from, millisecondsStepWidth, + numberOfFixes, trackedRace, onlyUpToNewestEvent, newestEvent, windSource, + new PositionAtTimeProvider() { @Override public Position getPosition(TimePoint at) { return null; }}); windTrackInfoDTOs.put(windSource, windTrackInfoDTO); - windTrackInfoDTO.windFixes = new ArrayList(); - WindTrack windTrack = trackedRace.getOrCreateWindTrack(windSource); - windTrackInfoDTO.dampeningIntervalInMilliseconds = windTrack - .getMillisecondsOverWhichToAverageWind(); - TimePoint timePoint = from; - Double minWindConfidence = 2.0; - Double maxWindConfidence = -1.0; - for (int i = 0; i < numberOfFixes && (!onlyUpToNewestEvent || - (newestEvent != null && timePoint.before(newestEvent))); i++) { - WindWithConfidence> averagedWindWithConfidence = windTrack.getAveragedWindWithConfidence(null, timePoint); - if (averagedWindWithConfidence != null) { - if (logger.getLevel() != null && logger.getLevel().equals(Level.FINEST)) { - logger.finest("Found averaged wind: " + averagedWindWithConfidence); - } - double confidence = averagedWindWithConfidence.getConfidence(); - WindDTO windDTO = createWindDTOFromAlreadyAveraged(averagedWindWithConfidence.getObject(), timePoint); - windDTO.confidence = confidence; - windTrackInfoDTO.windFixes.add(windDTO); - if (confidence < minWindConfidence) { - minWindConfidence = confidence; - } - if (confidence > maxWindConfidence) { - maxWindConfidence = confidence; - } - } else { - if (logger.getLevel() != null && logger.getLevel().equals(Level.FINEST)) { - logger.finest("Did NOT find any averaged wind for timepoint " + timePoint + " and tracked race " + trackedRace.getRaceIdentifier().getRaceName()); - } - } - timePoint = new MillisecondsTimePoint(timePoint.asMillis() + millisecondsStepWidth); - } - windTrackInfoDTO.minWindConfidence = minWindConfidence; - windTrackInfoDTO.maxWindConfidence = maxWindConfidence; + } + } + if (includeCombinedWindForAllLegMiddles) { + int zeroBasedLegNumber = 0; + for (final TrackedLeg trackedLeg : trackedRace.getTrackedLegs()) { + WindTrackInfoDTO windTrackInfoForLegMiddle = createWindTrackInfoDTO(from, millisecondsStepWidth, + numberOfFixes, trackedRace, onlyUpToNewestEvent, newestEvent, combinedWindSource, + new PositionAtTimeProvider() { @Override public Position getPosition(TimePoint at) { return trackedLeg.getMiddleOfLeg(at); }}); + result.addWindOnLegMiddle(zeroBasedLegNumber, windTrackInfoForLegMiddle); + zeroBasedLegNumber++; } } } return result; } + private interface PositionAtTimeProvider { + Position getPosition(TimePoint at); + } + + private WindTrackInfoDTO createWindTrackInfoDTO(TimePoint from, long millisecondsStepWidth, int numberOfFixes, + TrackedRace trackedRace, boolean onlyUpToNewestEvent, TimePoint newestEvent, WindSource windSource, + PositionAtTimeProvider positionProvider) { + WindTrack windTrack = trackedRace.getOrCreateWindTrack(windSource); + WindTrackInfoDTO windTrackInfoDTO = new WindTrackInfoDTO(); + windTrackInfoDTO.windFixes = new ArrayList(); + windTrackInfoDTO.dampeningIntervalInMilliseconds = windTrack.getMillisecondsOverWhichToAverageWind(); + TimePoint timePoint = from; + Double minWindConfidence = 2.0; + Double maxWindConfidence = -1.0; + for (int i = 0; i < numberOfFixes && (!onlyUpToNewestEvent || + (newestEvent != null && timePoint.before(newestEvent))); i++) { + WindWithConfidence> averagedWindWithConfidence = + windTrack.getAveragedWindWithConfidence(positionProvider.getPosition(timePoint), timePoint); + if (averagedWindWithConfidence != null) { + if (logger.getLevel() != null && logger.getLevel().equals(Level.FINEST)) { + logger.finest("Found averaged wind: " + averagedWindWithConfidence); + } + double confidence = averagedWindWithConfidence.getConfidence(); + WindDTO windDTO = createWindDTOFromAlreadyAveraged(averagedWindWithConfidence.getObject(), timePoint); + windDTO.confidence = confidence; + windTrackInfoDTO.windFixes.add(windDTO); + if (confidence < minWindConfidence) { + minWindConfidence = confidence; + } + if (confidence > maxWindConfidence) { + maxWindConfidence = confidence; + } + } else { + if (logger.getLevel() != null && logger.getLevel().equals(Level.FINEST)) { + logger.finest("Did NOT find any averaged wind for timepoint " + timePoint + " and tracked race " + trackedRace.getRaceIdentifier().getRaceName()); + } + } + timePoint = new MillisecondsTimePoint(timePoint.asMillis() + millisecondsStepWidth); + } + windTrackInfoDTO.minWindConfidence = minWindConfidence; + windTrackInfoDTO.maxWindConfidence = maxWindConfidence; + return windTrackInfoDTO; + } + /** * @param to * if null, data is returned up to end of race; if the end of race is not known and @@ -1383,7 +1411,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S if (fromTimePoint != null && toTimePoint != null) { int numberOfFixes = (int) ((toTimePoint.asMillis() - fromTimePoint.asMillis())/resolutionInMilliseconds); result = getAveragedWindInfo(fromTimePoint, resolutionInMilliseconds, numberOfFixes, - windSourceTypeNames, trackedRace, onlyUpToNewestEvent); + windSourceTypeNames, trackedRace, onlyUpToNewestEvent, /* includeCombinedWindForAllLegMiddles */ false); } } return result; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/WindInfoForRaceDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/WindInfoForRaceDTO.java index 736810a6a48..7d33ea46ada 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/WindInfoForRaceDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/WindInfoForRaceDTO.java @@ -1,5 +1,6 @@ package com.sap.sailing.gwt.ui.shared; +import java.util.HashMap; import java.util.Map; import com.google.gwt.user.client.rpc.IsSerializable; @@ -10,5 +11,15 @@ public class WindInfoForRaceDTO implements IsSerializable { public boolean raceIsKnownToStartUpwind; public Iterable windSourcesToExclude; + private Map combinedWindOnLegMiddle = new HashMap<>(); + public WindInfoForRaceDTO() {} + + public WindTrackInfoDTO getCombinedWindOnLegMiddle(int zeroBasedLegNumber) { + return combinedWindOnLegMiddle.get(zeroBasedLegNumber); + } + + public void addWindOnLegMiddle(int zeroBasedLegNumber, WindTrackInfoDTO windTrackInfoForLegMiddle) { + combinedWindOnLegMiddle.put(zeroBasedLegNumber, windTrackInfoForLegMiddle); + } } From 42b37b108a3f97662da1009f2135eb28bce76836 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 17 Jul 2014 22:56:07 +0200 Subject: [PATCH 166/323] fixing NPE for LEG_MIDDLE wind retrieval --- .../gwt/ui/client/shared/racemap/RaceMap.java | 187 +++++++++--------- 1 file changed, 94 insertions(+), 93 deletions(-) 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 656686b7c59..e92e582d929 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 @@ -863,103 +863,104 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe if (settings.getHelpLinesSettings().isVisible(HelpLineTypes.ADVANTAGELINE)) { // find competitor with highest rank com.sap.sse.common.Util.Pair visibleLeaderInfo = getLeadingVisibleCompetitorWithOneBasedLegNumber(competitorsToShow); - // get wind at middle of leg for leading visible competitor - WindTrackInfoDTO windDataForLegMiddle = lastCombinedWindTrackInfoDTO.getCombinedWindOnLegMiddle(visibleLeaderInfo.getA()-1); - if (windDataForLegMiddle != null && !windDataForLegMiddle.windFixes.isEmpty()) { - // the boat fix may be null; may mean that no positions were loaded yet for the leading visible boat; - // don't show anything - GPSFixDTO lastBoatFix = null; - boolean isVisibleLeaderInfoComplete = false; - boolean isLegTypeKnown = false; - if (visibleLeaderInfo != null && visibleLeaderInfo.getA() > 0 - && visibleLeaderInfo.getA() <= lastRaceTimesInfo.getLegInfos().size()) { - isVisibleLeaderInfoComplete = true; - LegInfoDTO legInfoDTO = lastRaceTimesInfo.getLegInfos().get(visibleLeaderInfo.getA() - 1); - if (legInfoDTO.legType != null) { - isLegTypeKnown = true; - } - lastBoatFix = getBoatFix(visibleLeaderInfo.getB(), date); + // the boat fix may be null; may mean that no positions were loaded yet for the leading visible boat; + // don't show anything + GPSFixDTO lastBoatFix = null; + boolean isVisibleLeaderInfoComplete = false; + boolean isLegTypeKnown = false; + WindTrackInfoDTO windDataForLegMiddle = null; + if (visibleLeaderInfo != null + && visibleLeaderInfo.getA() > 0 + && visibleLeaderInfo.getA() <= lastRaceTimesInfo.getLegInfos().size() + // get wind at middle of leg for leading visible competitor + && (windDataForLegMiddle = lastCombinedWindTrackInfoDTO + .getCombinedWindOnLegMiddle(visibleLeaderInfo.getA() - 1)) != null + && !windDataForLegMiddle.windFixes.isEmpty()) { + isVisibleLeaderInfoComplete = true; + LegInfoDTO legInfoDTO = lastRaceTimesInfo.getLegInfos().get(visibleLeaderInfo.getA() - 1); + if (legInfoDTO.legType != null) { + isLegTypeKnown = true; } - if (isVisibleLeaderInfoComplete && isLegTypeKnown && lastBoatFix != null) { - LegInfoDTO legInfoDTO = lastRaceTimesInfo.getLegInfos().get(visibleLeaderInfo.getA() - 1); - double advantageLineLengthInKm = 1.0; // TODO this should probably rather scale with the visible - // area of the map; bug 616 - double distanceFromBoatPositionInKm = visibleLeaderInfo.getB().getBoatClass() - .getHullLengthInMeters() / 1000.; // one hull length - // implement and use Position.translateRhumb() - double bearingOfBoatInDeg = lastBoatFix.speedWithBearing.bearingInDegrees; - LatLng boatPosition = LatLng.newInstance(lastBoatFix.position.latDeg, - lastBoatFix.position.lngDeg); - LatLng posAheadOfFirstBoat = calculatePositionAlongRhumbline(boatPosition, bearingOfBoatInDeg, - distanceFromBoatPositionInKm); - final WindDTO windFix = windDataForLegMiddle.windFixes.get(0); - double bearingOfCombinedWindInDeg = windFix.trueWindBearingDeg; - double rotatedBearingDeg1 = 0.0; - double rotatedBearingDeg2 = 0.0; - switch (legInfoDTO.legType) { - case UPWIND: - case DOWNWIND: { - rotatedBearingDeg1 = bearingOfCombinedWindInDeg + 90.0; - if (rotatedBearingDeg1 >= 360.0) { - rotatedBearingDeg1 -= 360.0; - } - rotatedBearingDeg2 = bearingOfCombinedWindInDeg - 90.0; - if (rotatedBearingDeg2 < 0.0) { - rotatedBearingDeg2 += 360.0; - } + lastBoatFix = getBoatFix(visibleLeaderInfo.getB(), date); + } + if (isVisibleLeaderInfoComplete && isLegTypeKnown && lastBoatFix != null) { + LegInfoDTO legInfoDTO = lastRaceTimesInfo.getLegInfos().get(visibleLeaderInfo.getA() - 1); + double advantageLineLengthInKm = 1.0; // TODO this should probably rather scale with the visible + // area of the map; bug 616 + double distanceFromBoatPositionInKm = visibleLeaderInfo.getB().getBoatClass() + .getHullLengthInMeters() / 1000.; // one hull length + // implement and use Position.translateRhumb() + double bearingOfBoatInDeg = lastBoatFix.speedWithBearing.bearingInDegrees; + LatLng boatPosition = LatLng.newInstance(lastBoatFix.position.latDeg, lastBoatFix.position.lngDeg); + LatLng posAheadOfFirstBoat = calculatePositionAlongRhumbline(boatPosition, bearingOfBoatInDeg, + distanceFromBoatPositionInKm); + final WindDTO windFix = windDataForLegMiddle.windFixes.get(0); + double bearingOfCombinedWindInDeg = windFix.trueWindBearingDeg; + double rotatedBearingDeg1 = 0.0; + double rotatedBearingDeg2 = 0.0; + switch (legInfoDTO.legType) { + case UPWIND: + case DOWNWIND: { + rotatedBearingDeg1 = bearingOfCombinedWindInDeg + 90.0; + if (rotatedBearingDeg1 >= 360.0) { + rotatedBearingDeg1 -= 360.0; } - break; - case REACHING: { - rotatedBearingDeg1 = legInfoDTO.legBearingInDegrees + 90.0; - if (rotatedBearingDeg1 >= 360.0) { - rotatedBearingDeg1 -= 360.0; - } - rotatedBearingDeg2 = legInfoDTO.legBearingInDegrees - 90.0; - if (rotatedBearingDeg2 < 0.0) { - rotatedBearingDeg2 += 360.0; - } + rotatedBearingDeg2 = bearingOfCombinedWindInDeg - 90.0; + if (rotatedBearingDeg2 < 0.0) { + rotatedBearingDeg2 += 360.0; } - break; - } - LatLng advantageLinePos1 = calculatePositionAlongRhumbline(posAheadOfFirstBoat, - rotatedBearingDeg1, advantageLineLengthInKm / 2.0); - LatLng advantageLinePos2 = calculatePositionAlongRhumbline(posAheadOfFirstBoat, - rotatedBearingDeg2, advantageLineLengthInKm / 2.0); - - if (advantageLine == null) { - PolylineOptions options = PolylineOptions.newInstance(); - options.setClickable(true); - options.setGeodesic(true); - options.setStrokeColor("#000000"); - options.setStrokeWeight(1); - options.setStrokeOpacity(0.5); - - advantageLine = Polyline.newInstance(options); - MVCArray pointsAsArray = MVCArray.newInstance(); - pointsAsArray.insertAt(0, advantageLinePos1); - pointsAsArray.insertAt(1, advantageLinePos2); - advantageLine.setPath(pointsAsArray); - - advantageLineMouseOverHandler = new AdvantageLineMouseOverMapHandler( - bearingOfCombinedWindInDeg, new Date(windFix.measureTimepoint)); - advantageLine.addMouseOverHandler(advantageLineMouseOverHandler); - advantageLine.addMouseOutMoveHandler(new MouseOutMapHandler() { - @Override - public void onEvent(MouseOutMapEvent event) { - map.setTitle(""); - } - }); - advantageLine.setMap(map); - } else { - advantageLine.getPath().removeAt(1); - advantageLine.getPath().removeAt(0); - advantageLine.getPath().insertAt(0, advantageLinePos1); - advantageLine.getPath().insertAt(1, advantageLinePos2); - advantageLineMouseOverHandler.setTrueWindBearing(bearingOfCombinedWindInDeg); - advantageLineMouseOverHandler.setDate(new Date(windFix.measureTimepoint)); - } - drawAdvantageLine = true; } + break; + case REACHING: { + rotatedBearingDeg1 = legInfoDTO.legBearingInDegrees + 90.0; + if (rotatedBearingDeg1 >= 360.0) { + rotatedBearingDeg1 -= 360.0; + } + rotatedBearingDeg2 = legInfoDTO.legBearingInDegrees - 90.0; + if (rotatedBearingDeg2 < 0.0) { + rotatedBearingDeg2 += 360.0; + } + } + break; + } + LatLng advantageLinePos1 = calculatePositionAlongRhumbline(posAheadOfFirstBoat, rotatedBearingDeg1, + advantageLineLengthInKm / 2.0); + LatLng advantageLinePos2 = calculatePositionAlongRhumbline(posAheadOfFirstBoat, rotatedBearingDeg2, + advantageLineLengthInKm / 2.0); + + if (advantageLine == null) { + PolylineOptions options = PolylineOptions.newInstance(); + options.setClickable(true); + options.setGeodesic(true); + options.setStrokeColor("#000000"); + options.setStrokeWeight(1); + options.setStrokeOpacity(0.5); + + advantageLine = Polyline.newInstance(options); + MVCArray pointsAsArray = MVCArray.newInstance(); + pointsAsArray.insertAt(0, advantageLinePos1); + pointsAsArray.insertAt(1, advantageLinePos2); + advantageLine.setPath(pointsAsArray); + + advantageLineMouseOverHandler = new AdvantageLineMouseOverMapHandler( + bearingOfCombinedWindInDeg, new Date(windFix.measureTimepoint)); + advantageLine.addMouseOverHandler(advantageLineMouseOverHandler); + advantageLine.addMouseOutMoveHandler(new MouseOutMapHandler() { + @Override + public void onEvent(MouseOutMapEvent event) { + map.setTitle(""); + } + }); + advantageLine.setMap(map); + } else { + advantageLine.getPath().removeAt(1); + advantageLine.getPath().removeAt(0); + advantageLine.getPath().insertAt(0, advantageLinePos1); + advantageLine.getPath().insertAt(1, advantageLinePos2); + advantageLineMouseOverHandler.setTrueWindBearing(bearingOfCombinedWindInDeg); + advantageLineMouseOverHandler.setDate(new Date(windFix.measureTimepoint)); + } + drawAdvantageLine = true; } } if (!drawAdvantageLine) { From 64232cf0f359dd7011fe747d67cd468067d61b77 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 17 Jul 2014 21:12:31 +0000 Subject: [PATCH 167/323] Updated ganalytics (markdown) --- wiki/ganalytics.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/wiki/ganalytics.md b/wiki/ganalytics.md index 07179941125..b8196c2159d 100644 --- a/wiki/ganalytics.md +++ b/wiki/ganalytics.md @@ -19,8 +19,7 @@ Coding:
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
- -*2. to embed tracking for hyperlinks use the onClick() method for each link
+* 2. to embed tracking for hyperlinks use the onClick() method for each link
Syntax:

July 2014

+
+
+ 2014 Season +
+
+ +
    +
  • + German Sailing League / Bundesliga 2014 +
  • +
  • + Germany +
  • +
+
+
+

German Sailing League / Bundesliga 2014

+ SAP Sailing Analytics
+ Official website +
+
+

2014

From 71825588ebe197cfb9fd1cc3e5f90a964c849b3c Mon Sep 17 00:00:00 2001 From: Jens Rommel Date: Sun, 20 Jul 2014 09:01:43 +0200 Subject: [PATCH 189/323] Added patch to hide prev/next buttons on the events swiper when there's only one event. --- .../sap/sailing/gwt/home/client/shared/stage/Stage.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 163df4a9232..f495d2af7d9 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 @@ -79,7 +79,12 @@ public class Stage extends Composite { prevStageTeaserLink.setVisible(!isFirstSlide); } }); - prevStageTeaserLink.setVisible(false); + if (featuredEvents.size() <= 1) { + prevStageTeaserLink.setVisible(false); + nextStageTeaserLink.setVisible(false); + } else { + prevStageTeaserLink.setVisible(false); + } } @UiHandler("nextStageTeaserLink") From 43954bc0fab31649a9d37294b93208fad5e72c35 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sun, 20 Jul 2014 11:10:09 +0200 Subject: [PATCH 190/323] fixed typos in german string messages --- .../sap/sailing/gwt/ui/client/StringMessages_de.properties | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 4b127156325..c67dea86662 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 @@ -143,7 +143,7 @@ overallDetailsToShow=Regatta-Details legDetailsToShow=Schenkeldetails columnMoveUp=Hoch columnMoveDown=Runter -port=Port +port=Backbord raceStartTimeColumn=Rennstart showOnlySelectedCompetitors=Nur ausgewählte Teilnehmer anzeigen showSelectedCompetitorsInfo=Infobox für ausgewählte Teilnehmer anzeigen @@ -860,7 +860,7 @@ errorRunningDataMiningQuery=Es ist ein Felher bei der Datenabfrage aufgetreten hideToolbar=Toolbar ausblenden showSeriesLeaderboards=Ergebnisse der Serie anzeigen showOverallLeaderboard=Gesamtergebnisse anzeigen -lineAngleToWindAndAdvantage=LängeÖ {0}m; Winkel zum Wind: {1}°; {2}-Seite ist {3}m bevorteilt +lineAngleToWindAndAdvantage=Länge: {0}m; Winkel zum Wind: {1}°; {2}-Seite ist {3}m bevorteilt doYouReallyWantToRemoveEvents=Möchten Sie diese Veranstaltungen wirklich entfernen? doYouReallyWantToRemoveEvent=Möchten Sie wirklich die Veranstaltung {0} entfernen? line=Linie @@ -1041,4 +1041,4 @@ totalSize=Gesamtgröße 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) \ No newline at end of file +currentOrAverageSpeedOverGroundInKnots=FüG (\u2205 nach Abschluss) From 1e486ea8e532980b05dbd210b613c673dc68132b Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sun, 20 Jul 2014 15:15:35 +0200 Subject: [PATCH 191/323] allow streamlets to load during race; getEndOfRace() delivers null until the first boat crosses the finish line --- .../java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 019ce988e3d..89ed512f993 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 @@ -1407,7 +1407,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S WindInfoForRaceDTO result = null; if (trackedRace != null) { TimePoint fromTimePoint = from == null ? trackedRace.getStartOfTracking() : new MillisecondsTimePoint(from); - TimePoint toTimePoint = to == null ? trackedRace.getEndOfRace() : new MillisecondsTimePoint(to); + TimePoint toTimePoint = to == null ? trackedRace.getEndOfRace() == null ? + MillisecondsTimePoint.now().minus(trackedRace.getDelayToLiveInMillis()) : trackedRace.getEndOfRace() : new MillisecondsTimePoint(to); if (fromTimePoint != null && toTimePoint != null) { int numberOfFixes = (int) ((toTimePoint.asMillis() - fromTimePoint.asMillis())/resolutionInMilliseconds); result = getAveragedWindInfo(fromTimePoint, resolutionInMilliseconds, numberOfFixes, From 4a0ff54d52bcc2d101d7abc10c92ffe971000bc8 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sun, 20 Jul 2014 16:02:46 +0200 Subject: [PATCH 192/323] bug 2077: protecting mark passing structures during TrackedRace serialization; don't serialize rank cache in TrackedLegImpl, avoiding CME --- .../tracking/impl/DynamicTrackedRaceImpl.java | 170 +++++++++--------- .../domain/tracking/impl/TrackedLegImpl.java | 14 +- 2 files changed, 100 insertions(+), 84 deletions(-) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java index c8511c9cc7a..caeec4852b6 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java @@ -581,96 +581,106 @@ DynamicTrackedRace, GPSTrackListener { @Override public void updateMarkPassings(Competitor competitor, Iterable markPassings) { - Map oldMarkPassings = new HashMap(); - MarkPassing oldStartMarkPassing = null; - TimePoint oldStartOfRace = getStartOfRace(); // getStartOfRace() may respond with a new result already after updating the mark passings - boolean requiresStartTimeUpdate = true; - final NavigableSet markPassingsForCompetitor = getMarkPassings(competitor); - lockForRead(markPassingsForCompetitor); + LockUtil.lockForRead(getSerializationLock()); // keep serializer from reading the mark passings collections try { - for (MarkPassing oldMarkPassing : markPassingsForCompetitor) { - if (oldStartMarkPassing == null) { - oldStartMarkPassing = oldMarkPassing; - } - oldMarkPassings.put(oldMarkPassing.getWaypoint(), oldMarkPassing); - } - } finally { - unlockAfterRead(markPassingsForCompetitor); - } - final NamedReentrantReadWriteLock markPassingsLock = getMarkPassingsLock(markPassingsForCompetitor); - TimePoint timePointOfLatestEvent = new MillisecondsTimePoint(0); - // Make sure that clearMarkPassings and the re-adding of the mark passings are non-interruptible by readers. - // Note that the write lock for the mark passings in order per waypoint is obtained inside clearMarkPassings(...) - // as well as inside the subsequent for-loop. It is important to always first obtain the mark passings lock for the competitor - // mark passings before obtaining the lock for the mark passings in order for the waypoint to avoid deadlocks. - LockUtil.lockForWrite(markPassingsLock); - try { - clearMarkPassings(competitor); - for (MarkPassing markPassing : markPassings) { - // try to find corresponding old start mark passing - if (oldStartMarkPassing != null - && markPassing.getWaypoint().equals(oldStartMarkPassing.getWaypoint())) { - if (markPassing.getTimePoint() != null && oldStartMarkPassing.getTimePoint() != null - && markPassing.getTimePoint().equals(oldStartMarkPassing.getTimePoint())) { - requiresStartTimeUpdate = false; + Map oldMarkPassings = new HashMap(); + MarkPassing oldStartMarkPassing = null; + TimePoint oldStartOfRace = getStartOfRace(); // getStartOfRace() may respond with a new result already after + // updating the mark passings + boolean requiresStartTimeUpdate = true; + final NavigableSet markPassingsForCompetitor = getMarkPassings(competitor); + lockForRead(markPassingsForCompetitor); + try { + for (MarkPassing oldMarkPassing : markPassingsForCompetitor) { + if (oldStartMarkPassing == null) { + oldStartMarkPassing = oldMarkPassing; } + oldMarkPassings.put(oldMarkPassing.getWaypoint(), oldMarkPassing); } - if (!Util.contains(getRace().getCourse().getWaypoints(), markPassing.getWaypoint())) { - StringBuilder courseWaypointsWithID = new StringBuilder(); - boolean first = true; - for (Waypoint courseWaypoint : getRace().getCourse().getWaypoints()) { - if (first) { - first = false; - } else { - courseWaypointsWithID.append(" -> "); + } finally { + unlockAfterRead(markPassingsForCompetitor); + } + final NamedReentrantReadWriteLock markPassingsLock = getMarkPassingsLock(markPassingsForCompetitor); + TimePoint timePointOfLatestEvent = new MillisecondsTimePoint(0); + // Make sure that clearMarkPassings and the re-adding of the mark passings are non-interruptible by readers. + // Note that the write lock for the mark passings in order per waypoint is obtained inside + // clearMarkPassings(...) as well as inside the subsequent for-loop. It is important to always first obtain the mark passings lock + // for the competitor mark passings before obtaining the lock for the mark passings in order for the waypoint to avoid + // deadlocks. + LockUtil.lockForWrite(markPassingsLock); + try { + clearMarkPassings(competitor); + for (MarkPassing markPassing : markPassings) { + // try to find corresponding old start mark passing + if (oldStartMarkPassing != null + && markPassing.getWaypoint().equals(oldStartMarkPassing.getWaypoint())) { + if (markPassing.getTimePoint() != null && oldStartMarkPassing.getTimePoint() != null + && markPassing.getTimePoint().equals(oldStartMarkPassing.getTimePoint())) { + requiresStartTimeUpdate = false; } - courseWaypointsWithID.append(courseWaypoint.toString()); - courseWaypointsWithID.append(" (ID="); - courseWaypointsWithID.append(courseWaypoint.getId()); - courseWaypointsWithID.append(")"); } - logger.severe("Received mark passing " + markPassing + " for race " + getRace() - + " for waypoint ID" + markPassing.getWaypoint().getId() - + " but the waypoint does not exist in course " + courseWaypointsWithID); - } else { - markPassingsForCompetitor.add(markPassing); + if (!Util.contains(getRace().getCourse().getWaypoints(), markPassing.getWaypoint())) { + StringBuilder courseWaypointsWithID = new StringBuilder(); + boolean first = true; + for (Waypoint courseWaypoint : getRace().getCourse().getWaypoints()) { + if (first) { + first = false; + } else { + courseWaypointsWithID.append(" -> "); + } + courseWaypointsWithID.append(courseWaypoint.toString()); + courseWaypointsWithID.append(" (ID="); + courseWaypointsWithID.append(courseWaypoint.getId()); + courseWaypointsWithID.append(")"); + } + logger.severe("Received mark passing " + markPassing + " for race " + getRace() + + " for waypoint ID" + markPassing.getWaypoint().getId() + + " but the waypoint does not exist in course " + courseWaypointsWithID); + } else { + markPassingsForCompetitor.add(markPassing); + } + Collection markPassingsInOrderForWaypoint = getOrCreateMarkPassingsInOrderAsNavigableSet(markPassing + .getWaypoint()); + final NamedReentrantReadWriteLock markPassingsLock2 = getMarkPassingsLock(markPassingsInOrderForWaypoint); + LockUtil.lockForWrite(markPassingsLock2); + try { + // The mark passings of competitor have been removed by the call to + // clearMarkPassings(competitor) above + // from both, the collection that holds the mark passings by waypoint and the one that holds the + // mark passings per competitor; so we can simply add here: + markPassingsInOrderForWaypoint.add(markPassing); + } finally { + LockUtil.unlockAfterWrite(markPassingsLock2); + } + if (markPassing.getTimePoint().compareTo(timePointOfLatestEvent) > 0) { + timePointOfLatestEvent = markPassing.getTimePoint(); + } } - Collection markPassingsInOrderForWaypoint = getOrCreateMarkPassingsInOrderAsNavigableSet(markPassing - .getWaypoint()); - final NamedReentrantReadWriteLock markPassingsLock2 = getMarkPassingsLock(markPassingsInOrderForWaypoint); - LockUtil.lockForWrite(markPassingsLock2); - try { - // The mark passings of competitor have been removed by the call to clearMarkPassings(competitor) above - // from both, the collection that holds the mark passings by waypoint and the one that holds the - // mark passings per competitor; so we can simply add here: - markPassingsInOrderForWaypoint.add(markPassing); - } finally { - LockUtil.unlockAfterWrite(markPassingsLock2); - } - if (markPassing.getTimePoint().compareTo(timePointOfLatestEvent) > 0) { - timePointOfLatestEvent = markPassing.getTimePoint(); + } finally { + LockUtil.unlockAfterWrite(markPassingsLock); + } + updated(timePointOfLatestEvent); + triggerManeuverCacheRecalculation(competitor); + // update the race times like start, end and the leg times + if (requiresStartTimeUpdate) { + TimePoint interimsStartOfRace = getStartOfRace(); + invalidateStartTime(); + TimePoint newStartOfRace = getStartOfRace(); + if (Util.equalsWithNull(interimsStartOfRace, newStartOfRace) + && !Util.equalsWithNull(oldStartOfRace, newStartOfRace)) { + // invalidateStartTime() will not have thrown a startOfRaceChanged event notification because it + // already saw the new + // start of race time; we have to throw the notification here: + notifyListenersStartOfRaceChanged(oldStartOfRace, newStartOfRace); } } + invalidateMarkPassingTimes(); + invalidateEndTime(); + // notify *after* all mark passings have been re-established; should avoid flicker + notifyListeners(competitor, oldMarkPassings, markPassings); } finally { - LockUtil.unlockAfterWrite(markPassingsLock); + LockUtil.unlockAfterRead(getSerializationLock()); } - updated(timePointOfLatestEvent); - triggerManeuverCacheRecalculation(competitor); - // update the race times like start, end and the leg times - if (requiresStartTimeUpdate) { - TimePoint interimsStartOfRace = getStartOfRace(); - invalidateStartTime(); - TimePoint newStartOfRace = getStartOfRace(); - if (Util.equalsWithNull(interimsStartOfRace, newStartOfRace) && !Util.equalsWithNull(oldStartOfRace, newStartOfRace)) { - // invalidateStartTime() will not have thrown a startOfRaceChanged event notification because it already saw the new - // start of race time; we have to throw the notification here: - notifyListenersStartOfRaceChanged(oldStartOfRace, newStartOfRace); - } - } - invalidateMarkPassingTimes(); - invalidateEndTime(); - // notify *after* all mark passings have been re-established; should avoid flicker - notifyListeners(competitor, oldMarkPassings, markPassings); } @Override 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 13307cf8299..f725b950405 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 @@ -1,5 +1,7 @@ package com.sap.sailing.domain.tracking.impl; +import java.io.IOException; +import java.io.ObjectInputStream; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -31,8 +33,8 @@ import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; 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.WindPositionMode; import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache; +import com.sap.sailing.domain.tracking.WindPositionMode; import com.sap.sse.common.Util; public class TrackedLegImpl implements TrackedLeg, RaceChangeListener { @@ -45,7 +47,7 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener { private final Leg leg; private final Map trackedLegsOfCompetitors; private TrackedRaceImpl trackedRace; - private final Map> competitorTracksOrderedByRank; + private transient Map> competitorTracksOrderedByRank; public TrackedLegImpl(DynamicTrackedRaceImpl trackedRace, Leg leg, Iterable competitors) { super(); @@ -56,7 +58,12 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener { trackedLegsOfCompetitors.put(competitor, new TrackedLegOfCompetitorImpl(this, competitor)); } trackedRace.addListener(this); - competitorTracksOrderedByRank = new HashMap>(); + competitorTracksOrderedByRank = new HashMap<>(); + } + + private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { + ois.defaultReadObject(); + competitorTracksOrderedByRank = new HashMap<>(); } @Override @@ -114,7 +121,6 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener { // race may be updated while calculation is going on, but each individual calculation is properly // synchronized, usually by read-write locks, so there is no major difference in synchronization issues // an the asynchronous nature of how the data is being received - // TODO See bug 469; competitors already disqualified may need to be ranked worst Collections.sort(rankedCompetitorList, new WindwardToGoComparator(this, timePoint, cache)); rankedCompetitorList = Collections.unmodifiableList(rankedCompetitorList); synchronized (competitorTracksOrderedByRank) { From 094dcb621656f8cb3018e775b0acf3c7d7dcb8d2 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sun, 20 Jul 2014 19:34:17 +0200 Subject: [PATCH 193/323] deleting initialLoad queue when done --- .../impl/ReplicationServiceImpl.java | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java index aaec9a2e0f4..b51558bd61f 100755 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/ReplicationServiceImpl.java @@ -25,6 +25,7 @@ import java.util.zip.GZIPOutputStream; import org.osgi.util.tracker.ServiceTracker; import com.rabbitmq.client.AMQP.Exchange; +import com.rabbitmq.client.AMQP.Queue.DeleteOk; import com.rabbitmq.client.Channel; import com.rabbitmq.client.ConnectionFactory; import com.rabbitmq.client.QueueingConsumer; @@ -412,15 +413,22 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec replicatorThread.start(); logger.info("Started replicator thread"); InputStream is = initialLoadURL.openStream(); - String queueName = new BufferedReader(new InputStreamReader(is)).readLine(); - RabbitInputStreamProvider rabbitInputStreamProvider = new RabbitInputStreamProvider(master.createChannel(), queueName); - final RacingEventService racingEventService = getRacingEventService(); - ObjectInputStream ois = racingEventService.getBaseDomainFactory().createObjectInputStreamResolvingAgainstThisFactory(new GZIPInputStream( - rabbitInputStreamProvider.getInputStream())); - logger.info("Starting to receive initial load"); - racingEventService.initiallyFillFrom(ois); - logger.info("Done receiving initial load"); - replicator.setSuspended(false); // apply queued operations + final String queueName = new BufferedReader(new InputStreamReader(is)).readLine(); + try { + RabbitInputStreamProvider rabbitInputStreamProvider = new RabbitInputStreamProvider(master.createChannel(), queueName); + final RacingEventService racingEventService = getRacingEventService(); + ObjectInputStream ois = racingEventService.getBaseDomainFactory() + .createObjectInputStreamResolvingAgainstThisFactory( + new GZIPInputStream(rabbitInputStreamProvider.getInputStream())); + logger.info("Starting to receive initial load"); + racingEventService.initiallyFillFrom(ois); + logger.info("Done receiving initial load"); + replicator.setSuspended(false); // apply queued operations + } finally { + // delete initial load queue + DeleteOk deleteOk = consumer.getChannel().queueDelete(queueName); + logger.info("Deleted queue "+queueName+" used for initial load: "+deleteOk.toString()); + } } /** From ade5b5a35f6a6826f220ccb64c0d4211e0014ef0 Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Sun, 20 Jul 2014 23:20:30 +0200 Subject: [PATCH 194/323] Added new high point scoring scheme that checks if additional scoring information is available and iff gives a score of 8 for the winner --- .../domain/common/ScoringSchemeType.java | 3 +- .../impl/DomainObjectFactoryImpl.java | 8 +++ .../impl/MongoObjectFactoryImpl.java | 15 +++++ .../impl/MongoRaceLogStoreVisitor.java | 7 ++ .../META-INF/MANIFEST.MF | 2 + .../racelog/RaceLogEventRestoreFactory.java | 4 ++ .../domain/racelog/RaceLogEventVisitor.java | 3 + .../AdditionalScoringInformationFinder.java | 24 +++++++ .../racelog/impl/BaseRaceLogEventVisitor.java | 6 ++ .../racelog/impl/RaceLogChangedVisitor.java | 6 ++ .../impl/RaceLogEventRestoreFactoryImpl.java | 8 +++ .../AdditionalScoringInformationEvent.java | 15 +++++ ...AdditionalScoringInformationEventImpl.java | 26 ++++++++ .../LeaderboardScoringAndRankingTest.java | 32 ++++++++++ .../domain/base/impl/DomainFactoryImpl.java | 3 + .../impl/RaceColumnRaceLogReplicator.java | 6 ++ .../leaderboard/FlexibleLeaderboard.java | 5 +- ...ghPointFirstGets10Or8AndLastBreaksTie.java | 64 +++++++++++++++++++ .../impl/HighPointFirstGetsFixedScore.java | 4 +- .../sailing/gwt/ui/client/StringMessages.java | 1 + .../gwt/ui/client/StringMessages.properties | 3 +- .../ScoringSchemeTypeFormatter.java | 2 + .../racelog/RaceLogEventSerializerTest.java | 4 +- ...dditionalScoringInformationSerializer.java | 34 ++++++++++ .../racelog/impl/RaceLogEventSerializer.java | 14 +++- 25 files changed, 289 insertions(+), 10 deletions(-) create mode 100644 java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/AdditionalScoringInformationFinder.java create mode 100644 java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/AdditionalScoringInformationEvent.java create mode 100644 java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/impl/AdditionalScoringInformationEventImpl.java create mode 100644 java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10Or8AndLastBreaksTie.java create mode 100644 java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/racelog/impl/RaceLogAdditionalScoringInformationSerializer.java 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 0102c273dda..cfd4e07a62d 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 @@ -2,5 +2,6 @@ package com.sap.sailing.domain.common; 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_ONE, LOW_POINT_WINNER_GETS_ZERO, HIGH_POINT_WINNER_GETS_SIX, HIGH_POINT_WINNER_GETS_FIVE, + HIGH_POINT_FIRST_GETS_TEN_OR_EIGHT } 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 7edcb5ea232..b97e852c1de 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 @@ -136,6 +136,7 @@ import com.sap.sailing.domain.racelog.RevokeEvent; import com.sap.sailing.domain.racelog.impl.CompetitorResultsImpl; import com.sap.sailing.domain.racelog.impl.RaceLogEventAuthorImpl; import com.sap.sailing.domain.racelog.impl.RaceLogImpl; +import com.sap.sailing.domain.racelog.scoring.impl.AdditionalScoringInformationEventImpl; import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; @@ -1347,6 +1348,8 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { return loadRaceLogDefineMarkEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); } else if (eventClass.equals(CloseOpenEndedDeviceMappingEvent.class.getSimpleName())) { return loadRaceLogCloseOpenEndedDeviceMappingEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(AdditionalScoringInformationEventImpl.class.getSimpleName())) { + return loadRaceLogAdditionalScoringInformationEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); } throw new IllegalStateException(String.format("Unknown RaceLogEvent type %s", eventClass)); @@ -1432,6 +1435,11 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { deviceMappingEventId, closingTimePoint); } + private RaceLogEvent loadRaceLogAdditionalScoringInformationEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, Integer passId, List competitors, DBObject dbObject) { + return raceLogEventFactory.createAdditionalScoringInformationEvent(createdAt, author, logicalTimePoint, id, competitors, passId); + } + private RaceLogEvent loadRaceLogRaceLogProtestStartTimeEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { TimePoint protestStartTime = loadTimePoint(dbObject, FieldNames.RACE_LOG_PROTEST_START_TIME); 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 55c4eb1ed35..c0ee1cc4d8c 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 @@ -82,6 +82,7 @@ import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent; import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; import com.sap.sailing.domain.racelog.RaceLogWindFixEvent; import com.sap.sailing.domain.racelog.RevokeEvent; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent; import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; @@ -842,6 +843,20 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { return result; } + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, AdditionalScoringInformationEvent event) { + BasicDBObject result = new BasicDBObject(); + storeRaceLogIdentifier(raceLogIdentifier, result); + result.put(FieldNames.RACE_LOG_EVENT.name(), storeAdditionalScoringInformation(event)); + return result; + } + + private Object storeAdditionalScoringInformation(AdditionalScoringInformationEvent event) { + DBObject result = new BasicDBObject(); + storeRaceLogEventProperties(event, result); + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), event.getClass().getSimpleName()); + return result; + } + private Object storeRaceLogWindFix(RaceLogWindFixEvent event) { DBObject result = new BasicDBObject(); storeRaceLogEventProperties(event, result); diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoRaceLogStoreVisitor.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoRaceLogStoreVisitor.java index 39b0108f90e..7fd0e2517ed 100644 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoRaceLogStoreVisitor.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoRaceLogStoreVisitor.java @@ -20,6 +20,7 @@ import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent; import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; import com.sap.sailing.domain.racelog.RaceLogWindFixEvent; import com.sap.sailing.domain.racelog.RevokeEvent; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent; import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; @@ -166,4 +167,10 @@ public class MongoRaceLogStoreVisitor implements RaceLogEventVisitor { DBObject object = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); mongoObjectFactory.storeRaceLogEventEvent(object); } + + @Override + public void visit(AdditionalScoringInformationEvent event) { + DBObject object = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); + mongoObjectFactory.storeRaceLogEventEvent(object); + } } diff --git a/java/com.sap.sailing.domain.shared.android/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.shared.android/META-INF/MANIFEST.MF index e5aef624aa3..d9ad7470bde 100644 --- a/java/com.sap.sailing.domain.shared.android/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.shared.android/META-INF/MANIFEST.MF @@ -17,6 +17,8 @@ Export-Package: com.sap.sailing.domain.base, com.sap.sailing.domain.racelog, com.sap.sailing.domain.racelog.analyzing.impl, com.sap.sailing.domain.racelog.impl, + com.sap.sailing.domain.racelog.scoring, + com.sap.sailing.domain.racelog.scoring.impl, com.sap.sailing.domain.racelog.state, com.sap.sailing.domain.racelog.state.impl, com.sap.sailing.domain.racelog.state.racingprocedure, diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventRestoreFactory.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventRestoreFactory.java index 9c73db4e60e..5c810591187 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventRestoreFactory.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventRestoreFactory.java @@ -12,6 +12,7 @@ import com.sap.sailing.domain.common.racelog.Flags; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.common.racelog.RacingProcedureType; import com.sap.sailing.domain.racelog.impl.RaceLogEventRestoreFactoryImpl; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent; import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; @@ -90,4 +91,7 @@ public interface RaceLogEventRestoreFactory extends RaceLogEventFactory { CloseOpenEndedDeviceMappingEvent createCloseOpenEndedDeviceMappingEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, Serializable pId, int passId, Serializable deviceMappingEventId, TimePoint closingTimePoint); + + AdditionalScoringInformationEvent createAdditionalScoringInformationEvent(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable pId, List competitors, int passId); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventVisitor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventVisitor.java index 21f4c7f52c4..c746af2090c 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventVisitor.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventVisitor.java @@ -1,5 +1,6 @@ package com.sap.sailing.domain.racelog; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent; import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; @@ -51,4 +52,6 @@ public interface RaceLogEventVisitor { public void visit(DefineMarkEvent event); public void visit(CloseOpenEndedDeviceMappingEvent event); + + public void visit(AdditionalScoringInformationEvent additionalScoringInformation); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/AdditionalScoringInformationFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/AdditionalScoringInformationFinder.java new file mode 100644 index 00000000000..c2e2952652f --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/AdditionalScoringInformationFinder.java @@ -0,0 +1,24 @@ +package com.sap.sailing.domain.racelog.analyzing.impl; + +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent; + +public class AdditionalScoringInformationFinder extends RaceLogAnalyzer { + + public AdditionalScoringInformationFinder(RaceLog raceLog) { + super(raceLog); + } + + @Override + protected AdditionalScoringInformationEvent performAnalysis() { + AdditionalScoringInformationEvent result = null; + for (RaceLogEvent event : getAllEventsDescending()) { + if (event instanceof AdditionalScoringInformationEvent) { + AdditionalScoringInformationEvent scoringEvent = (AdditionalScoringInformationEvent) event; + result = scoringEvent; + } + } + return result; + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/BaseRaceLogEventVisitor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/BaseRaceLogEventVisitor.java index 16bca0b04ec..cc86383f8f7 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/BaseRaceLogEventVisitor.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/BaseRaceLogEventVisitor.java @@ -15,6 +15,7 @@ import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent; import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; import com.sap.sailing.domain.racelog.RaceLogWindFixEvent; import com.sap.sailing.domain.racelog.RevokeEvent; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent; import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; @@ -132,4 +133,9 @@ public abstract class BaseRaceLogEventVisitor implements RaceLogEventVisitor { public void visit(CloseOpenEndedDeviceMappingEvent event) { } + + @Override + public void visit(AdditionalScoringInformationEvent event) { + + } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogChangedVisitor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogChangedVisitor.java index d3e57ea8001..b339da55d33 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogChangedVisitor.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogChangedVisitor.java @@ -16,6 +16,7 @@ import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent; import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; import com.sap.sailing.domain.racelog.RaceLogWindFixEvent; import com.sap.sailing.domain.racelog.RevokeEvent; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent; import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; @@ -130,4 +131,9 @@ public class RaceLogChangedVisitor implements RaceLogEventVisitor { public void visit(CloseOpenEndedDeviceMappingEvent event) { listener.eventAdded(event); } + + @Override + public void visit(AdditionalScoringInformationEvent additionalScoringInformation) { + listener.eventAdded(additionalScoringInformation); + } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventRestoreFactoryImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventRestoreFactoryImpl.java index 0f61f469175..ee2660f2de8 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventRestoreFactoryImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventRestoreFactoryImpl.java @@ -28,6 +28,8 @@ import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent; import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; import com.sap.sailing.domain.racelog.RaceLogWindFixEvent; import com.sap.sailing.domain.racelog.RevokeEvent; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent; +import com.sap.sailing.domain.racelog.scoring.impl.AdditionalScoringInformationEventImpl; import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; @@ -178,4 +180,10 @@ public class RaceLogEventRestoreFactoryImpl extends RaceLogEventFactoryImpl impl return new CloseOpenEndedDeviceMappingEventImpl(createdAt, author, logicalTimePoint, pId, passId, deviceMappingEventId, closingTimePoint); } + + @Override + public AdditionalScoringInformationEvent createAdditionalScoringInformationEvent(TimePoint createdAt, + RaceLogEventAuthor author, TimePoint logicalTimePoint, Serializable pId, List competitors, int passId) { + return new AdditionalScoringInformationEventImpl(createdAt, author, logicalTimePoint, pId, competitors, passId); + } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/AdditionalScoringInformationEvent.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/AdditionalScoringInformationEvent.java new file mode 100644 index 00000000000..b4719ee0e4d --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/AdditionalScoringInformationEvent.java @@ -0,0 +1,15 @@ +package com.sap.sailing.domain.racelog.scoring; + +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.Revokable; + +/** + * Event that could possibly overwrite scoring for a leaderboard column. The idea + * is that the scoring scheme checks if such an event is available and if it applies + * extended scoring rules. + * + * @author Simon Marcel Pamies + */ +public interface AdditionalScoringInformationEvent extends RaceLogEvent, Revokable { + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/impl/AdditionalScoringInformationEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/impl/AdditionalScoringInformationEventImpl.java new file mode 100644 index 00000000000..9088a1a8550 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/impl/AdditionalScoringInformationEventImpl.java @@ -0,0 +1,26 @@ +package com.sap.sailing.domain.racelog.scoring.impl; + +import java.io.Serializable; +import java.util.List; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.RaceLogEventVisitor; +import com.sap.sailing.domain.racelog.impl.RaceLogEventImpl; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent; + +public class AdditionalScoringInformationEventImpl extends RaceLogEventImpl implements AdditionalScoringInformationEvent { + private static final long serialVersionUID = -7627714111951381979L; + + public AdditionalScoringInformationEventImpl(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable pId, List competitors, int pPassId) { + super(createdAt, author, logicalTimePoint, pId, competitors, pPassId); + } + + @Override + public void accept(RaceLogEventVisitor visitor) { + visitor.visit(this); + } + +} 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 e855d5ab6b1..3175ce7143f 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 @@ -46,11 +46,15 @@ import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl; import com.sap.sailing.domain.leaderboard.impl.HighPoint; import com.sap.sailing.domain.leaderboard.impl.HighPointExtremeSailingSeriesOverall; import com.sap.sailing.domain.leaderboard.impl.HighPointFirstGets10LastBreaksTie; +import com.sap.sailing.domain.leaderboard.impl.HighPointFirstGets10Or8AndLastBreaksTie; import com.sap.sailing.domain.leaderboard.impl.LeaderboardGroupImpl; import com.sap.sailing.domain.leaderboard.impl.LowPoint; import com.sap.sailing.domain.leaderboard.impl.RegattaLeaderboardImpl; import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl; import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.impl.RaceLogEventAuthorImpl; +import com.sap.sailing.domain.racelog.scoring.impl.AdditionalScoringInformationEventImpl; import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.TrackedRace; @@ -791,6 +795,34 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { assertEquals(rankedCompetitors.indexOf(c[0]), rankedCompetitors.indexOf(c[1])-1); } + @Test + public void testAdditionalScoreInformationLeadsToChangedScoreForOneColumn() throws NoWindException { + Competitor[] competitors = createCompetitors(10).toArray(new Competitor[0]); + TimePoint now = MillisecondsTimePoint.now(); + TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000); + FlexibleLeaderboard leaderboardHighPoint10Or8AndLastBreaksTie = new FlexibleLeaderboardImpl("Test ESS Highpoint 10Or8AndLastBreaksTie", new ThresholdBasedResultDiscardingRuleImpl(/* discarding thresholds */ new int[0]), + new HighPointFirstGets10Or8AndLastBreaksTie(), null); + leaderboardHighPoint10Or8AndLastBreaksTie.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors)), "R1", /* medalRace */ false, + leaderboardHighPoint10Or8AndLastBreaksTie.getFleet(null)); + assertTrue(leaderboardHighPoint10Or8AndLastBreaksTie.getScoringScheme().getScoreComparator(/* nullScoresAreBetter */ false).compare( + leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[3], later)) < 0); // c0 better than c3 + assertEquals(10, leaderboardHighPoint10Or8AndLastBreaksTie.getCompetitorsFromBestToWorst(later).size()); + assertEquals(new Double(10), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); + assertEquals(new Double(1), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); + leaderboardHighPoint10Or8AndLastBreaksTie.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors)), "R2", /* medalRace */ false, + leaderboardHighPoint10Or8AndLastBreaksTie.getFleet(null)); + assertEquals(new Double(20), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); + assertEquals(new Double(2), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); + RaceLog raceLogForRace2 = leaderboardHighPoint10Or8AndLastBreaksTie.getRaceColumnByName("R2").getRaceLog(leaderboardHighPoint10Or8AndLastBreaksTie.getFleet(null)); + raceLogForRace2.add(new AdditionalScoringInformationEventImpl(now, new RaceLogEventAuthorImpl("Plopp", 1), later, "12345678", Collections.emptyList(), 0)); + assertEquals(new Double(18), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); + assertEquals(new Double(2), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); + leaderboardHighPoint10Or8AndLastBreaksTie.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors)), "R3", /* medalRace */ false, + leaderboardHighPoint10Or8AndLastBreaksTie.getFleet(null)); + assertEquals(new Double(28), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); + assertEquals(new Double(3), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); + } + @Test public void testCompetitorsRankedEleventhOrLowerGetOnePointScore() throws NoWindException { Competitor[] competitors = createCompetitors(16).toArray(new Competitor[0]); 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 d8e827a02ea..67536690da5 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 @@ -32,6 +32,7 @@ import com.sap.sailing.domain.leaderboard.ScoringScheme; import com.sap.sailing.domain.leaderboard.impl.HighPoint; import com.sap.sailing.domain.leaderboard.impl.HighPointExtremeSailingSeriesOverall; import com.sap.sailing.domain.leaderboard.impl.HighPointFirstGets10LastBreaksTie; +import com.sap.sailing.domain.leaderboard.impl.HighPointFirstGets10Or8AndLastBreaksTie; import com.sap.sailing.domain.leaderboard.impl.HighPointFirstGets1LastBreaksTie; import com.sap.sailing.domain.leaderboard.impl.HighPointLastBreaksTie; import com.sap.sailing.domain.leaderboard.impl.HighPointWinnerGetsFive; @@ -92,6 +93,8 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain return new HighPointWinnerGetsFive(); case HIGH_POINT_WINNER_GETS_SIX: return new HighPointWinnerGetsSix(); + case HIGH_POINT_FIRST_GETS_TEN_OR_EIGHT: + return new HighPointFirstGets10Or8AndLastBreaksTie(); } throw new RuntimeException("Unknown scoring scheme type "+scoringSchemeType.name()); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnRaceLogReplicator.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnRaceLogReplicator.java index 67f0f9dc36f..f9477ea027f 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnRaceLogReplicator.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnRaceLogReplicator.java @@ -20,6 +20,7 @@ import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent; import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; import com.sap.sailing.domain.racelog.RaceLogWindFixEvent; import com.sap.sailing.domain.racelog.RevokeEvent; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent; import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; @@ -150,4 +151,9 @@ public class RaceColumnRaceLogReplicator implements RaceLogEventVisitor, Seriali public void visit(CloseOpenEndedDeviceMappingEvent event) { notifyOnAdd(event); } + + @Override + public void visit(AdditionalScoringInformationEvent additionalScoringInformation) { + notifyOnAdd(additionalScoringInformation); + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/FlexibleLeaderboard.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/FlexibleLeaderboard.java index c8d43274927..1e07f78ad26 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/FlexibleLeaderboard.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/FlexibleLeaderboard.java @@ -58,9 +58,8 @@ public interface FlexibleLeaderboard extends Leaderboard, Renamable { * @param medalRace * tells if the column to add represents a medal race which has double score and cannot be discarded; * ignored if the column named columnName already exists - * @param fleets - * the fleets to add to the {@link RaceColumn} created. If no fleets are specified, a single default - * fleet will be assigned to the race column created. + * @param fleet + * the fleet to link the race to when the {@link RaceColumn} is created. * * @return the race column in the leaderboard used to represent the tracked race */ diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10Or8AndLastBreaksTie.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10Or8AndLastBreaksTie.java new file mode 100644 index 00000000000..6f9e49fbfdd --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10Or8AndLastBreaksTie.java @@ -0,0 +1,64 @@ +package com.sap.sailing.domain.leaderboard.impl; + +import java.util.List; +import java.util.concurrent.Callable; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.RaceColumn; +import com.sap.sailing.domain.common.ScoringSchemeType; +import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.analyzing.impl.AdditionalScoringInformationFinder; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent; +import com.sap.sse.common.Util; + +/** + * {@link HighPointFirstGetsFixedScore} scheme that in most cases applies + * 10 points for the winner. Iff for one race column a {@link AdditionalScoringInformationEvent} + * is found then 8 points are applied for the winner. + * + * @author Simon Marcel Pamies + */ +public class HighPointFirstGets10Or8AndLastBreaksTie extends HighPointFirstGetsFixedScore { + private static final long serialVersionUID = 1L; + + private final double SCORE_FOR_WINNER_IF_OVERWRITTEN = 8.0; + + public HighPointFirstGets10Or8AndLastBreaksTie() { + super(10.0); + } + + @Override + public ScoringSchemeType getType() { + return ScoringSchemeType.HIGH_POINT_FIRST_GETS_TEN; + } + + @Override + public int compareByBetterScore(List> o1Scores, List> o2Scores, boolean nullScoresAreBetter) { + return 0; + } + + @Override + public Double getScoreForRank(RaceColumn raceColumn, Competitor competitor, int rank, Callable numberOfCompetitorsInRaceFetcher, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher) { + Double effectiveScore = super.getScoreForRank(raceColumn, competitor, rank, numberOfCompetitorsInRaceFetcher, numberOfCompetitorsInLeaderboardFetcher); + return checkForOverwrittenScore(raceColumn, rank, effectiveScore); + } + + private Double checkForOverwrittenScore(RaceColumn raceColumn, int rank, Double effectiveScore) { + Double result = effectiveScore; + for (Fleet fleet : raceColumn.getFleets()) { + RaceLog raceLog = raceColumn.getRaceLog(fleet); + AdditionalScoringInformationFinder finder = new AdditionalScoringInformationFinder(raceLog); + AdditionalScoringInformationEvent event = finder.analyze(); + if (event != null) { + if (rank == 0) { + result = null; + } else { + result = Math.max(getMinimumScoreFromRank(), (double) (SCORE_FOR_WINNER_IF_OVERWRITTEN - rank + 1)); + } + } + } + return result; + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGetsFixedScore.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGetsFixedScore.java index 5742b5211ad..f9ef8bf135e 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGetsFixedScore.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGetsFixedScore.java @@ -32,11 +32,11 @@ public abstract class HighPointFirstGetsFixedScore extends HighPoint { this.minimumScoreFromRank = minimumScoreFromRank; } - protected double getScoreForRaceWinner() { + double getScoreForRaceWinner() { return scoreForRaceWinner; } - private double getMinimumScoreFromRank() { + double getMinimumScoreFromRank() { return minimumScoreFromRank; } 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 f5ab6b223d8..305af3985cd 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 @@ -1063,4 +1063,5 @@ public interface StringMessages extends Messages { String totalNumberOfOperations(); String currentOrAverageSpeedOverGroundInKnotsTooltip(); String currentOrAverageSpeedOverGroundInKnots(); + String scoringSchemeHighPointFirstGetsTenOrEight(); } 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 93a6a0dbc8f..e9066d6bcf8 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 @@ -1046,4 +1046,5 @@ 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) \ No newline at end of file +currentOrAverageSpeedOverGroundInKnots=SOG (\u2205 at end) +scoringSchemeHighPointFirstGetsTenOrEight=High Point, winner gets 10 points (or 8) \ No newline at end of file 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 113e14d586e..97c96956535 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 @@ -24,6 +24,8 @@ public class ScoringSchemeTypeFormatter { return stringMessages.scoringSchemeWinnerGetsFive(); case HIGH_POINT_WINNER_GETS_SIX: return stringMessages.scoringSchemeWinnerGetsSix(); + case HIGH_POINT_FIRST_GETS_TEN_OR_EIGHT: + return stringMessages.scoringSchemeHighPointFirstGetsTenOrEight(); } return null; } diff --git a/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/racelog/RaceLogEventSerializerTest.java b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/racelog/RaceLogEventSerializerTest.java index aa1f4fb4216..bc352c8be15 100644 --- a/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/racelog/RaceLogEventSerializerTest.java +++ b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/racelog/RaceLogEventSerializerTest.java @@ -41,6 +41,7 @@ public class RaceLogEventSerializerTest { private JsonSerializer registerCompetitorEventSerializer; private JsonSerializer defineMarkEventSerializer; private JsonSerializer closeOpenEndedDeviceMappingEventSerializer; + private JsonSerializer additionalScoringInformationSerializer; private RaceLogEventFactory factory; private RaceLogEventAuthor author = new RaceLogEventAuthorImpl("Test Author", 1); @@ -69,6 +70,7 @@ public class RaceLogEventSerializerTest { registerCompetitorEventSerializer = mock(JsonSerializer.class); defineMarkEventSerializer = mock(JsonSerializer.class); closeOpenEndedDeviceMappingEventSerializer = mock(JsonSerializer.class); + additionalScoringInformationSerializer = mock(JsonSerializer.class); serializer = new RaceLogEventSerializer(flagEventSerializer, startTimeSerializer, raceStatusSerializer, courseAreaChangedEventSerializer, passChangedSerializer, courseDesignChangedEventSerializer, @@ -77,7 +79,7 @@ public class RaceLogEventSerializerTest { startProcedureTypeChangedEventSerializer, protestStartTimeEventSerializer, windFixEventSerializer, deviceCompetitorMappingEventSerializer, deviceMarkMappingEventSerializer, denoteForTrackingEventSerializer, startTrackingEventSerializer, revokeEventSerializer, registerCompetitorEventSerializer, defineMarkEventSerializer, - closeOpenEndedDeviceMappingEventSerializer); + closeOpenEndedDeviceMappingEventSerializer, additionalScoringInformationSerializer); factory = RaceLogEventFactory.INSTANCE; } diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/racelog/impl/RaceLogAdditionalScoringInformationSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/racelog/impl/RaceLogAdditionalScoringInformationSerializer.java new file mode 100644 index 00000000000..c69e1aff1b3 --- /dev/null +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/racelog/impl/RaceLogAdditionalScoringInformationSerializer.java @@ -0,0 +1,34 @@ +package com.sap.sailing.server.gateway.serialization.racelog.impl; + +import org.json.simple.JSONObject; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent; +import com.sap.sailing.server.gateway.serialization.JsonSerializer; + +public class RaceLogAdditionalScoringInformationSerializer extends BaseRaceLogEventSerializer { + public static final String VALUE_CLASS = AdditionalScoringInformationEvent.class.getSimpleName(); + + public RaceLogAdditionalScoringInformationSerializer(JsonSerializer competitorSerializer) { + super(competitorSerializer); + } + + @Override + protected String getClassFieldValue() { + return VALUE_CLASS; + } + + @Override + public JSONObject serialize(RaceLogEvent object) { + AdditionalScoringInformationEvent event = (AdditionalScoringInformationEvent) object; + + JSONObject result = super.serialize(event); + //TODO + //result.put(RaceLogCloseOpenEndedDeviceMappingEventDeserializer.FIELD_DEVICE_MAPPING_EVENT_ID, event.getDeviceMappingEventId()); + //result.put(RaceLogCloseOpenEndedDeviceMappingEventDeserializer.FIELD_CLOSING_TIMEPOINT_MILLIS, event.getClosingTimePoint().asMillis()); + + return result; + } + +} diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/racelog/impl/RaceLogEventSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/racelog/impl/RaceLogEventSerializer.java index 0ad67cc8799..436a5e5b0fa 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/racelog/impl/RaceLogEventSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/racelog/impl/RaceLogEventSerializer.java @@ -19,6 +19,7 @@ import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent; import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; import com.sap.sailing.domain.racelog.RaceLogWindFixEvent; import com.sap.sailing.domain.racelog.RevokeEvent; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent; import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; @@ -75,7 +76,8 @@ public class RaceLogEventSerializer implements JsonSerializer, Rac new RaceLogRevokeEventSerializer(competitorSerializer), new RaceLogRegisterCompetitorEventSerializer(competitorSerializer), new RaceLogDefineMarkEventSerializer(competitorSerializer, new MarkJsonSerializer()), - new RaceLogCloseOpenEndedDeviceMappingEventSerializer(competitorSerializer)); + new RaceLogCloseOpenEndedDeviceMappingEventSerializer(competitorSerializer), + new RaceLogAdditionalScoringInformationSerializer(competitorSerializer)); } private final JsonSerializer flagEventSerializer; @@ -99,6 +101,7 @@ public class RaceLogEventSerializer implements JsonSerializer, Rac private final JsonSerializer registerCompetitorSerializer; private final JsonSerializer defineMarkSerializer; private final JsonSerializer closeOpenEndedDeviceMappingEventSerializer; + private final JsonSerializer additionalScoringInformationSerializer; private JsonSerializer chosenSerializer; @@ -123,7 +126,8 @@ public class RaceLogEventSerializer implements JsonSerializer, Rac JsonSerializer revokeSerializer, JsonSerializer registerCompetitorSerializer, JsonSerializer defineMarkSerializer, - JsonSerializer closeOpenEndedDeviceMappingEventSerializer) { + JsonSerializer closeOpenEndedDeviceMappingEventSerializer, + JsonSerializer additionalScoringInformationSerializer) { this.flagEventSerializer = flagEventSerializer; this.startTimeSerializer = startTimeSerializer; this.raceStatusSerializer = raceStatusSerializer; @@ -145,6 +149,7 @@ public class RaceLogEventSerializer implements JsonSerializer, Rac this.registerCompetitorSerializer = registerCompetitorSerializer; this.defineMarkSerializer = defineMarkSerializer; this.closeOpenEndedDeviceMappingEventSerializer = closeOpenEndedDeviceMappingEventSerializer; + this.additionalScoringInformationSerializer = additionalScoringInformationSerializer; this.chosenSerializer = null; } @@ -266,4 +271,9 @@ public class RaceLogEventSerializer implements JsonSerializer, Rac public void visit(CloseOpenEndedDeviceMappingEvent event) { chosenSerializer = closeOpenEndedDeviceMappingEventSerializer; } + + @Override + public void visit(AdditionalScoringInformationEvent additionalScoringInformation) { + chosenSerializer = additionalScoringInformationSerializer; + } } From e9ee01288bb7dc3508155169c4d8cc70683f6ad4 Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Sun, 20 Jul 2014 23:48:43 +0200 Subject: [PATCH 195/323] Extended RaceState with setter for additional scoring information, added button that is only enabled for ESS startphase --- .../domain/racelog/RaceLogEventFactory.java | 3 +++ .../racelog/impl/RaceLogEventFactoryImpl.java | 9 +++++++++ .../domain/racelog/state/RaceState.java | 7 +++++++ .../racelog/state/impl/RaceStateImpl.java | 19 +++++++++++++++++++ .../race_startphase_base_view.xml | 10 ++++++++++ .../res/values/strings.xml | 3 +++ .../BaseStartphaseRaceFragment.java | 7 ++++++- .../startphase/ESSStartphaseRaceFragment.java | 13 +++++++++++++ 8 files changed, 70 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventFactory.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventFactory.java index 26ec9195352..e3c0fe4988a 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventFactory.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventFactory.java @@ -12,6 +12,7 @@ import com.sap.sailing.domain.common.racelog.Flags; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.common.racelog.RacingProcedureType; import com.sap.sailing.domain.racelog.impl.RaceLogEventFactoryImpl; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent; import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; @@ -117,4 +118,6 @@ public interface RaceLogEventFactory { CloseOpenEndedDeviceMappingEvent createCloseOpenEndedDeviceMappingEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId, Serializable deviceMappingEventId, TimePoint closingTimePoint); + + AdditionalScoringInformationEvent createAdditionalScoringInformationEvent(TimePoint timePoint, RaceLogEventAuthor author, int currentPassId); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventFactoryImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventFactoryImpl.java index 6838ddef26b..8ec4255d194 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventFactoryImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventFactoryImpl.java @@ -2,6 +2,7 @@ package com.sap.sailing.domain.racelog.impl; import java.io.Serializable; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.UUID; @@ -30,6 +31,8 @@ import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent; import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; import com.sap.sailing.domain.racelog.RaceLogWindFixEvent; import com.sap.sailing.domain.racelog.RevokeEvent; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent; +import com.sap.sailing.domain.racelog.scoring.impl.AdditionalScoringInformationEventImpl; import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; @@ -268,4 +271,10 @@ public class RaceLogEventFactoryImpl implements RaceLogEventFactory { return new CloseOpenEndedDeviceMappingEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, UUID.randomUUID(), passId, deviceMappingEventId, closingTimePoint); } + + @Override + public AdditionalScoringInformationEvent createAdditionalScoringInformationEvent(TimePoint timePoint, + RaceLogEventAuthor author, int currentPassId) { + return new AdditionalScoringInformationEventImpl(MillisecondsTimePoint.now(), author, timePoint, UUID.randomUUID(), Collections.emptyList(), currentPassId); + } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/RaceState.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/RaceState.java index 5b02fd93a54..f4ea5570a57 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/RaceState.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/RaceState.java @@ -8,6 +8,7 @@ import com.sap.sailing.domain.racelog.CompetitorResults; import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.RaceLogEvent; import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent; import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedure; import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedurePrerequisite; import com.sap.sailing.domain.racelog.state.racingprocedure.impl.RacingProcedurePrerequisiteAutoResolver; @@ -138,5 +139,11 @@ public interface RaceState extends ReadonlyRaceState { * Enters a new wind fix for this race. */ void setWindFix(TimePoint now, Wind wind); + + /** + * Marks this race state with a new {@link AdditionalScoringInformationEvent} or revokes + * an already existing one. + */ + void setAdditionalScoringInformationEnabled(TimePoint timePoint, boolean enable); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateImpl.java index dcd929c3e60..7477c5069c0 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateImpl.java @@ -8,11 +8,14 @@ import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.racelog.Flags; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.common.racelog.RacingProcedureType; +import com.sap.sailing.domain.common.racelog.tracking.NotRevokableException; import com.sap.sailing.domain.racelog.CompetitorResults; import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.RaceLogEventAuthor; import com.sap.sailing.domain.racelog.RaceLogEventFactory; +import com.sap.sailing.domain.racelog.analyzing.impl.AdditionalScoringInformationFinder; import com.sap.sailing.domain.racelog.analyzing.impl.RaceStatusAnalyzer; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent; import com.sap.sailing.domain.racelog.state.RaceState; import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedure; import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedureFactory; @@ -158,4 +161,20 @@ public class RaceStateImpl extends ReadonlyRaceStateImpl implements RaceState { raceLog.add(factory.createWindFixEvent(timePoint, author, raceLog.getCurrentPassId(), wind)); } + @Override + public void setAdditionalScoringInformationEnabled(TimePoint timePoint, boolean enable) { + if (enable) { + raceLog.add(factory.createAdditionalScoringInformationEvent(timePoint, author, raceLog.getCurrentPassId())); + } else { + final AdditionalScoringInformationEvent event = new AdditionalScoringInformationFinder(raceLog).analyze(); + if (event != null) { + try { + raceLog.revokeEvent(author, event); + } catch (NotRevokableException e) { + e.printStackTrace(); + } + } + } + } + } diff --git a/mobile/com.sap.sailing.racecommittee.app/res/layout-sw600dp/race_startphase_base_view.xml b/mobile/com.sap.sailing.racecommittee.app/res/layout-sw600dp/race_startphase_base_view.xml index e7a3b603783..8a50598371a 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/layout-sw600dp/race_startphase_base_view.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/layout-sw600dp/race_startphase_base_view.xml @@ -120,6 +120,16 @@ android:layout_width="200dp" android:layout_height="50dp" android:text="@string/reset_time" /> + + + 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 cb1a8f51c8f..a3adfdf64e4 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/values/strings.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/values/strings.xml @@ -254,4 +254,7 @@ Reset course No course active + Group Race? + No Group Racing + Group Racing \ No newline at end of file diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/startphase/BaseStartphaseRaceFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/startphase/BaseStartphaseRaceFragment.java index 7b13ec4a762..3e3614a2e32 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/startphase/BaseStartphaseRaceFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/startphase/BaseStartphaseRaceFragment.java @@ -10,6 +10,7 @@ import android.widget.Button; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.TextView; +import android.widget.ToggleButton; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; @@ -27,11 +28,12 @@ public abstract class BaseStartphaseRaceFragment Date: Mon, 21 Jul 2014 01:06:00 +0200 Subject: [PATCH 196/323] Added deserializer, still needing to correctly implement revoke event --- .../racelog/state/impl/RaceStateImpl.java | 6 +++-- ...ghPointFirstGets10Or8AndLastBreaksTie.java | 2 +- ...alScoringInformationEventDeserializer.java | 25 +++++++++++++++++++ .../impl/RaceLogEventDeserializer.java | 11 ++++++-- .../rc/AddEntryToRaceLogJsonPostServlet.java | 8 +++++- 5 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogAdditionalScoringInformationEventDeserializer.java diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateImpl.java index 7477c5069c0..a094c877137 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateImpl.java @@ -163,10 +163,12 @@ public class RaceStateImpl extends ReadonlyRaceStateImpl implements RaceState { @Override public void setAdditionalScoringInformationEnabled(TimePoint timePoint, boolean enable) { + final AdditionalScoringInformationEvent event = new AdditionalScoringInformationFinder(raceLog).analyze(); if (enable) { - raceLog.add(factory.createAdditionalScoringInformationEvent(timePoint, author, raceLog.getCurrentPassId())); + if (event == null) { + raceLog.add(factory.createAdditionalScoringInformationEvent(timePoint, author, raceLog.getCurrentPassId())); + } } else { - final AdditionalScoringInformationEvent event = new AdditionalScoringInformationFinder(raceLog).analyze(); if (event != null) { try { raceLog.revokeEvent(author, event); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10Or8AndLastBreaksTie.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10Or8AndLastBreaksTie.java index 6f9e49fbfdd..8c812c3178b 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10Or8AndLastBreaksTie.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10Or8AndLastBreaksTie.java @@ -31,7 +31,7 @@ public class HighPointFirstGets10Or8AndLastBreaksTie extends HighPointFirstGetsF @Override public ScoringSchemeType getType() { - return ScoringSchemeType.HIGH_POINT_FIRST_GETS_TEN; + return ScoringSchemeType.HIGH_POINT_FIRST_GETS_TEN_OR_EIGHT; } @Override diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogAdditionalScoringInformationEventDeserializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogAdditionalScoringInformationEventDeserializer.java new file mode 100644 index 00000000000..2ec55367efd --- /dev/null +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogAdditionalScoringInformationEventDeserializer.java @@ -0,0 +1,25 @@ +package com.sap.sailing.server.gateway.deserialization.racelog.impl; + +import java.io.Serializable; +import java.util.List; + +import org.json.simple.JSONObject; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; +import com.sap.sailing.server.gateway.deserialization.JsonDeserializer; + +public class RaceLogAdditionalScoringInformationEventDeserializer extends BaseRaceLogEventDeserializer { + public RaceLogAdditionalScoringInformationEventDeserializer(JsonDeserializer competitorDeserializer) { + super(competitorDeserializer); + } + + @Override + protected RaceLogEvent deserialize(JSONObject object, Serializable id, TimePoint createdAt, RaceLogEventAuthor author, TimePoint timePoint, int passId, List competitors) + throws JsonDeserializationException { + return factory.createAdditionalScoringInformationEvent(timePoint, author, passId); + } +} diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogEventDeserializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogEventDeserializer.java index f7bba85e088..7899c079a19 100755 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogEventDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogEventDeserializer.java @@ -19,6 +19,7 @@ import com.sap.sailing.server.gateway.deserialization.impl.DeviceIdentifierJsonD import com.sap.sailing.server.gateway.deserialization.impl.PositionJsonDeserializer; import com.sap.sailing.server.gateway.deserialization.impl.WindJsonDeserializer; import com.sap.sailing.server.gateway.serialization.racelog.impl.BaseRaceLogEventSerializer; +import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogAdditionalScoringInformationSerializer; import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogCloseOpenEndedDeviceMappingEventSerializer; import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogCourseAreaChangedEventSerializer; import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogCourseDesignChangedEventSerializer; @@ -80,7 +81,8 @@ public class RaceLogEventDeserializer implements JsonDeserializer new RaceLogRevokeEventDeserializer(competitorDeserializer), new RaceLogRegisterCompetitorEventDeserializer(competitorDeserializer), new RaceLogDefineMarkEventDeserializer(competitorDeserializer, new MarkDeserializer(domainFactory)), - new RaceLogCloseOpenEndedDeviceMappingEventDeserializer(competitorDeserializer)); + new RaceLogCloseOpenEndedDeviceMappingEventDeserializer(competitorDeserializer), + new RaceLogAdditionalScoringInformationEventDeserializer(competitorDeserializer)); } protected final JsonDeserializer flagEventDeserializer; @@ -104,6 +106,7 @@ public class RaceLogEventDeserializer implements JsonDeserializer protected final JsonDeserializer registerCompetitorEventDeserializer; protected final JsonDeserializer defineMarkEventDeserializer; protected final JsonDeserializer closeOpenEndedDeviceMappingEventDeserializer; + protected final JsonDeserializer additionalScoringInformationEventDeserializer; public RaceLogEventDeserializer(JsonDeserializer flagEventDeserializer, JsonDeserializer startTimeEventDeserializer, @@ -125,7 +128,8 @@ public class RaceLogEventDeserializer implements JsonDeserializer JsonDeserializer revokeEventDeserializer, JsonDeserializer registerCompetitorEventDeserializer, JsonDeserializer defineMarkEventDeserializer, - JsonDeserializer closeOpenEndedDeviceMappingEventDeserializer) { + JsonDeserializer closeOpenEndedDeviceMappingEventDeserializer, + JsonDeserializer additionalScoringInformationEventDeserializer) { this.flagEventDeserializer = flagEventDeserializer; this.startTimeEventDeserializer = startTimeEventDeserializer; this.raceStatusEventDeserializer = raceStatusEventDeserializer; @@ -147,6 +151,7 @@ public class RaceLogEventDeserializer implements JsonDeserializer this.registerCompetitorEventDeserializer = registerCompetitorEventDeserializer; this.defineMarkEventDeserializer = defineMarkEventDeserializer; this.closeOpenEndedDeviceMappingEventDeserializer = closeOpenEndedDeviceMappingEventDeserializer; + this.additionalScoringInformationEventDeserializer = additionalScoringInformationEventDeserializer; } protected JsonDeserializer getDeserializer(JSONObject object) throws JsonDeserializationException { @@ -194,6 +199,8 @@ public class RaceLogEventDeserializer implements JsonDeserializer return defineMarkEventDeserializer; } else if (type.equals(RaceLogCloseOpenEndedDeviceMappingEventSerializer.VALUE_CLASS)) { return closeOpenEndedDeviceMappingEventDeserializer; + } else if (type.equals(RaceLogAdditionalScoringInformationSerializer.VALUE_CLASS)) { + return additionalScoringInformationEventDeserializer; } throw new JsonDeserializationException(String.format("There is no deserializer defined for event type %s.", diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/AddEntryToRaceLogJsonPostServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/AddEntryToRaceLogJsonPostServlet.java index d0b4d5f7ba4..cba73d7534b 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/AddEntryToRaceLogJsonPostServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/AddEntryToRaceLogJsonPostServlet.java @@ -129,7 +129,13 @@ public class AddEntryToRaceLogJsonPostServlet extends AbstractJsonHttpServlet { Object requestObject = JSONValue.parseWithException(requestBody.toString()); JSONObject requestJsonObject = Helpers.toJSONObjectSafe(requestObject); logger.fine("JSON requestObject is: " + requestObject.toString()); - RaceLogEvent logEvent = deserializer.deserialize(requestJsonObject); + RaceLogEvent logEvent = null; + try { + logEvent = deserializer.deserialize(requestJsonObject); + } catch (Exception ex) { + ex.printStackTrace(); + throw ex; + } logger.fine("JSON is deserialized to a RaceLogEvent"); Iterable eventsToSendBackToClient = raceLog.add(logEvent, clientUuid); sendResponse(response, clientUuid, raceLog, eventsToSendBackToClient); From 5c62ab436435955e13443489f19164d6f3096af3 Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Mon, 21 Jul 2014 10:06:04 +0200 Subject: [PATCH 197/323] Changed groovy-eclipse version in all target platforms older version is not available anymore --- .../race-analysis-p2-ide-local.target | 33 +++++++++---------- .../race-analysis-p2-ide-p2admin.target | 3 +- .../race-analysis-p2-maven-central.target | 3 +- .../race-analysis-p2-maven-local.target | 3 +- 4 files changed, 19 insertions(+), 23 deletions(-) diff --git a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-ide-local.target b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-ide-local.target index 7fa7dbcdc9d..39c0a5e8f5c 100755 --- a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-ide-local.target +++ b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-ide-local.target @@ -1,5 +1,5 @@ - + @@ -36,22 +36,21 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-ide-p2admin.target b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-ide-p2admin.target index 775fd610fc7..592949a761e 100644 --- a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-ide-p2admin.target +++ b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-ide-p2admin.target @@ -45,8 +45,7 @@ - - + diff --git a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-maven-central.target b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-maven-central.target index 15d25c00b43..b850942a967 100644 --- a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-maven-central.target +++ b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-maven-central.target @@ -51,8 +51,7 @@ - - + diff --git a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-maven-local.target b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-maven-local.target index c899d7e49eb..b0c510595d8 100644 --- a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-maven-local.target +++ b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-maven-local.target @@ -51,8 +51,7 @@ - - + From 2b526f07b17e71b49c22c37b9488142c3c52ac40 Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Mon, 21 Jul 2014 10:06:47 +0200 Subject: [PATCH 198/323] FlexibleLeaderboards now do not allow multiple fleets for race creation The default fleet is a singleton and the interface of the FlexibleLeaderboard was cleaned up. Before the cleanup, the user was allowed to add mutiple fleets when creating a race or a race column. This is not allowed in the persistence model --- .../impl/DomainObjectFactoryImpl.java | 3 +- .../test/LeaderboardForKielWeekTest.java | 10 +- .../domain/test/LeaderboardOfflineTest.java | 20 +- .../LeaderboardScoringAndRankingTest.java | 68 +++--- .../test/RaceLogAttachOnTrackedRaceTest.java | 15 +- .../leaderboard/FlexibleLeaderboard.java | 22 +- .../leaderboard/impl/DefaultFleetImpl.java | 21 ++ .../impl/FlexibleLeaderboardImpl.java | 29 ++- .../leaderboard/impl/RaceColumnImpl.java | 19 +- ...StoringAndRetrievingLeaderboardGroups.java | 11 +- .../TestStoringAndRetrievingLeaderboards.java | 74 +++--- ...ingAndRetrievingRaceLogInLeaderboards.java | 7 +- .../test/LeaderboardDiscardingRulesTest.java | 32 ++- .../server/test/MasterDataImportTest.java | 211 ++++++++++++++++++ .../server/impl/RacingEventServiceImpl.java | 6 +- .../ImportMasterDataOperation.java | 1 + 16 files changed, 360 insertions(+), 189 deletions(-) create mode 100644 java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/DefaultFleetImpl.java 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 7edcb5ea232..f33c4bc3869 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 @@ -519,8 +519,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { String columnName = (String) dbRaceColumn.get(FieldNames.LEADERBOARD_COLUMN_NAME.name()); RaceColumn raceColumn = result.addRaceColumn(columnName, - (Boolean) dbRaceColumn.get(FieldNames.LEADERBOARD_IS_MEDAL_RACE_COLUMN.name()), - fleets.toArray(new Fleet[0])); + (Boolean) dbRaceColumn.get(FieldNames.LEADERBOARD_IS_MEDAL_RACE_COLUMN.name())); for (Map.Entry e : raceIdentifiers.entrySet()) { raceColumn.setRaceIdentifier(fleetsByName.get(e.getKey()), e.getValue()); } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardForKielWeekTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardForKielWeekTest.java index a4bdebf753f..782638935fa 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardForKielWeekTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardForKielWeekTest.java @@ -14,7 +14,6 @@ import java.util.logging.Logger; import org.junit.Test; import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.WindSourceType; @@ -27,8 +26,8 @@ import com.sap.sailing.domain.leaderboard.impl.LowPoint; import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl; import com.sap.sailing.domain.tracking.impl.WindImpl; import com.sap.sailing.domain.tractracadapter.ReceiverType; -import com.tractrac.model.lib.api.event.CreateModelException; import com.sap.sse.common.Util; +import com.tractrac.model.lib.api.event.CreateModelException; import com.tractrac.subscription.lib.api.SubscriberInitializationException; public class LeaderboardForKielWeekTest extends OnlineTracTracBasedTest { @@ -44,24 +43,23 @@ public class LeaderboardForKielWeekTest extends OnlineTracTracBasedTest { public void leaderboardWithOneRaceTest() throws URISyntaxException, NoWindException, IOException, InterruptedException, SubscriberInitializationException, CreateModelException { leaderboard = new FlexibleLeaderboardImpl("Kiel Week 2011 505s", new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); - Fleet defaultFleet = leaderboard.getFleet(null); MillisecondsTimePoint now = MillisecondsTimePoint.now(); loadRace("event_20110609_KielerWoch-505_Race_2.txt", "event_20110609_KielerWoch-505_Race_2.mtb"); // 505 Race 2 Competitor kevin = getCompetitorByName("Kevin"); - RaceColumn column = leaderboard.addRace(getTrackedRace(), "Test Race 1", /* medalRace */ false, defaultFleet); + RaceColumn column = leaderboard.addRace(getTrackedRace(), "Test Race 1", /* medalRace */false); assertEquals(17, leaderboard.getTotalPoints(kevin, now), 0.000000001); Util.Pair key = new Util.Pair(kevin, column); assertEquals(17, leaderboard.getContent(now).get(key).getTotalPoints(), 0.000000001); assertEquals(17, leaderboard.getEntry(kevin, column, now).getTotalPoints(), 0.000000001); loadRace("event_20110609_KielerWoch-505_Race_3.txt", "event_20110609_KielerWoch-505_Race_3.mtb"); // 505 Race 3 - column = leaderboard.addRace(getTrackedRace(), "Test Race 2", /* medalRace */ false, defaultFleet); + column = leaderboard.addRace(getTrackedRace(), "Test Race 2", /* medalRace */false); key = new Util.Pair(kevin, column); // In Race 3, Hasso ranked 31st assertEquals(29, leaderboard.getTotalPoints(kevin, now), 0.000000001); assertEquals(12, leaderboard.getContent(now).get(key).getTotalPoints(), 0.000000001); assertEquals(12, leaderboard.getEntry(kevin, column, now).getTotalPoints(), 0.000000001); loadRace("event_20110609_KielerWoch-505_race_4.txt", "event_20110609_KielerWoch-505_race_4.mtb"); // 505 Race 4 - column = leaderboard.addRace(getTrackedRace(), "Test Race 3", /* medalRace */ false, defaultFleet); + column = leaderboard.addRace(getTrackedRace(), "Test Race 3", /* medalRace */false); key = new Util.Pair(kevin, column); // now the first race is discarded because Kevin ranked worst compared to the other two; in race 4 he ranked 10th assertEquals(22, leaderboard.getTotalPoints(kevin, now), 0.000000001); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardOfflineTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardOfflineTest.java index 84e424534af..42678bb910f 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardOfflineTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardOfflineTest.java @@ -102,12 +102,11 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest { public void ensureMedalRaceParamIsIgnoredIfRaceColumnAlreadyExists() { FlexibleLeaderboard leaderboard = new FlexibleLeaderboardImpl("Test Leaderboard", new ThresholdBasedResultDiscardingRuleImpl( new int[] { 5, 8 }), new LowPoint(), null); - Fleet defaultFleet = leaderboard.getFleet(null); final String columnName = "abc"; setupRaces(1, 0); leaderboard.addRaceColumn(columnName, /* medalRace */ true); - leaderboard.addRace(testRaces.iterator().next(), columnName, /* medalRace */ false, defaultFleet); + leaderboard.addRace(testRaces.iterator().next(), columnName, /* medalRace */false); assertTrue(leaderboard.getRaceColumnByName(columnName).isMedalRace()); } @@ -119,7 +118,7 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest { final String columnName = "abc"; setupRaces(1, 0); leaderboard.addRaceColumn(columnName, /* medalRace */ true); - leaderboard.addRace(testRaces.iterator().next(), columnName, /* medalRace */ false, defaultFleet); + leaderboard.addRace(testRaces.iterator().next(), columnName, /* medalRace */false); final Set emptySet = Collections.emptySet(); final MillisecondsTimePoint now = MillisecondsTimePoint.now(); final TrackedRegattaRegistry trackedRegattaRegistry = new TrackedRegattaRegistry() { @@ -180,7 +179,7 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest { RaceColumn bestScoringRaceColumn = null; for (TrackedRace race : testRaces) { i++; - RaceColumn raceColumn = leaderboard.addRace(race, "Test Race " + i, /* medalRace */ false, defaultFleet); + RaceColumn raceColumn = leaderboard.addRace(race, "Test Race " + i, /* medalRace */false); raceColumnsInLeaderboard.put(race, raceColumn); if (race.getRank(competitor) < bestScore) { bestScore = race.getRank(competitor); @@ -207,8 +206,7 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest { testRace.addCompetitor(c3); // this makes maxPoints==4 FlexibleLeaderboard leaderboard = new FlexibleLeaderboardImpl("Test Leaderboard", new ThresholdBasedResultDiscardingRuleImpl( new int[] { 2 }), new LowPoint(), null); - Fleet defaultFleet = leaderboard.getFleet(null); - leaderboard.addRace(testRace, "R1", /* medalRace */ false, defaultFleet); + leaderboard.addRace(testRace, "R1", /* medalRace */false); assertEquals(1., leaderboard.getTotalPoints(competitor, now), 0.00000001); assertEquals(1., leaderboard.getTotalPoints(c2, now), 0.00000001); assertEquals(1., leaderboard.getTotalPoints(c3, now), 0.00000001); @@ -235,10 +233,9 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest { testRace.addCompetitor(c3); // this makes maxPoints==4 FlexibleLeaderboard leaderboard = new FlexibleLeaderboardImpl("Test Leaderboard", new ThresholdBasedResultDiscardingRuleImpl( new int[] { 2 }), new LowPoint(), null); - Fleet defaultFleet = leaderboard.getFleet(null); - RaceColumn r1 = leaderboard.addRace(testRace, "R1", /* medalRace */ false, defaultFleet); + RaceColumn r1 = leaderboard.addRace(testRace, "R1", /* medalRace */false); raceColumnsInLeaderboard.put(testRace, r1); - RaceColumn r2 = leaderboard.addRaceColumn("R2", /* medalRace */ false, defaultFleet); + RaceColumn r2 = leaderboard.addRaceColumn("R2", /* medalRace */false); leaderboard.getScoreCorrection().setMaxPointsReason(competitor, r2, MaxPointsReason.DND); // non-discardable disqualification // assert that max points were given before discarding... assertEquals(4.0, leaderboard.getEntry(competitor, r2, MillisecondsTimePoint.now()).getNetPoints(), 0.000000001); @@ -268,7 +265,7 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest { RaceColumn bestScoringRaceColumn = null; for (TrackedRace race : testRaces) { i++; - RaceColumn raceColumn = leaderboard.addRace(race, "Test Race " + i, /* medalRace */ false, defaultFleet); + RaceColumn raceColumn = leaderboard.addRace(race, "Test Race " + i, /* medalRace */false); raceColumnsInLeaderboard.put(race, raceColumn); if (race.getRank(competitor) < bestScore) { bestScore = race.getRank(competitor); @@ -294,8 +291,7 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest { for (TrackedRace race : testRaces) { i++; raceColumnsInLeaderboard.put(race, leaderboard.addRace(race, "Test Race "+i, - /* medalRace */ numberOfUntrackedRaces == 0 && addOneMedalRace && i == testRaces.size(), - defaultFleet)); + /* medalRace */numberOfUntrackedRaces == 0 && addOneMedalRace && i == testRaces.size())); } // add a few race columns not yet connected to a tracked race for (int j=0; j rankedCompetitorsInLeaderboard = leaderboard2.getCompetitorsFromBestToWorst(later); @@ -958,9 +959,8 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { public TimePoint getEndOfRace() { return endOfR1; } - }, "R1", /* medalRace */ false, - leaderboard1.getFleet(null)); - RaceColumn r2 = leaderboard1.addRaceColumn("R2", /* medalRace */ false, new Fleet[] { new FleetImpl("Default Fleet") }); + }, "R1", /* medalRace */false); + RaceColumn r2 = leaderboard1.addRaceColumn("R2", /* medalRace */false); leaderboard1.getScoreCorrection().correctScore(c[0], r2, 123.); assertEquals(2. + 123., leaderboard1.getTotalPoints(c[0], afterEndOfR1), 0.00000001); // correction expected to apply after end of last tracked race before the corrected column assertEquals(2. + 0., leaderboard1.getTotalPoints(c[0], withinR1), 0.00000001); // correction expected NOT to apply before end of last tracked race before the corrected column @@ -986,15 +986,13 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { public TimePoint getEndOfRace() { return endOfR1; } - }, "R1", /* medalRace */ false, - leaderboard1.getFleet(null)); + }, "R1", /* medalRace */false); RaceColumn r2 = leaderboard1.addRace(new MockedTrackedRaceWithStartTimeAndRanks(afterEndOfR1, Arrays.asList(c2)) { private static final long serialVersionUID = 8705622361027154428L; public TimePoint getEndOfRace() { return afterEndOfR1; } - }, "R2", /* medalRace */ false, - leaderboard1.getFleet(null)); + }, "R2", /* medalRace */false); leaderboard1.getScoreCorrection().correctScore(c1[0], r2, .9); assertEquals(1. + .9, leaderboard1.getTotalPoints(c1[0], afterEndOfR1), 0.00000001); assertEquals(1, leaderboard1.getTotalRankOfCompetitor(c1[0], afterEndOfR1)); @@ -1034,8 +1032,8 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { }; trackedRace.getRace().getCourse().addWaypoint(0, start); trackedRace.getRace().getCourse().addWaypoint(1, finish); - RaceColumn r1 = leaderboard1.addRace(trackedRace, "R1", /* medalRace */ false, leaderboard1.getFleet(null)); - RaceColumn r2 = leaderboard1.addRaceColumn("R2", /* medalRace */ false, new Fleet[] { new FleetImpl("Default Fleet") }); + RaceColumn r1 = leaderboard1.addRace(trackedRace, "R1", /* medalRace */false); + RaceColumn r2 = leaderboard1.addRaceColumn("R2", /* medalRace */false); leaderboard1.getScoreCorrection().correctScore(c[0], r2, 123.); assertEquals(2. + 123., leaderboard1.getTotalPoints(c[0], afterEndOfR1), 0.00000001); // correction expected to apply after end of last tracked race before the corrected column assertEquals(2. + 0., leaderboard1.getTotalPoints(c[0], withinR1), 0.00000001); // correction expected NOT to apply before end of last tracked race before the corrected column diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RaceLogAttachOnTrackedRaceTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RaceLogAttachOnTrackedRaceTest.java index 07de1bddc6e..0a1b637c61d 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RaceLogAttachOnTrackedRaceTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RaceLogAttachOnTrackedRaceTest.java @@ -1,6 +1,5 @@ package com.sap.sailing.domain.test; -import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; @@ -11,7 +10,6 @@ import org.junit.Test; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.RaceColumn; -import com.sap.sailing.domain.base.impl.FleetImpl; import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl; import com.sap.sailing.domain.leaderboard.impl.LowPoint; @@ -76,18 +74,15 @@ public class RaceLogAttachOnTrackedRaceTest { } @Test - public void testAttachSpecificFleets() { - Fleet[] fleets = new Fleet[] { new FleetImpl("A"), new FleetImpl("B") }; - Fleet aFleet = fleets[0]; - Fleet bFleet = fleets[1]; - RaceColumn column = leaderboard.addRaceColumn("R1", false, fleets); + public void testAttachToDefaultFleet() { + RaceColumn column = leaderboard.addRaceColumn("R1", false); TrackedRace trackedRace = new MyMockedTrackedRace(); - column.setTrackedRace(aFleet, trackedRace); + Fleet defaultFleet = leaderboard.getFleet(null); + column.setTrackedRace(defaultFleet, trackedRace); - assertSame(column.getRaceLog(aFleet), trackedRace.getRaceLog(column.getRaceLogIdentifier(aFleet))); - assertNotSame(column.getRaceLog(bFleet), trackedRace.getRaceLog(column.getRaceIdentifier(bFleet))); + assertSame(column.getRaceLog(defaultFleet), trackedRace.getRaceLog(column.getRaceLogIdentifier(defaultFleet))); } @Test diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/FlexibleLeaderboard.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/FlexibleLeaderboard.java index c8d43274927..041df9131e0 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/FlexibleLeaderboard.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/FlexibleLeaderboard.java @@ -35,36 +35,32 @@ public interface FlexibleLeaderboard extends Leaderboard, Renamable { void moveRaceColumnDown(String name); /** - * Adds a new {@link RaceColumn} that has no {@link TrackedRace} associated yet to this leaderboard. + * Adds a new {@link RaceColumn} that has no {@link TrackedRace} associated yet to this leaderboard. The default + * fleet will be used. * * @param name - * the name for the new race column such that none of the columns in {@link #getRaceColumns()} - * has that name yet; otherwise, an message will be logged, no race column will be added and - * the existing column will be returned for robustness reasons + * the name for the new race column such that none of the columns in {@link #getRaceColumns()} has that + * name yet; otherwise, an message will be logged, no race column will be added and the existing column + * will be returned for robustness reasons * @param medalRace * tells if the column to add represents a medal race which has double score and cannot be discarded - * @param fleets - * the fleets to add to the {@link RaceColumn} created. If no fleets are specified, a single default - * fleet will be assigned to the race column created. * @return the race column in the leaderboard used to represent the tracked race */ - RaceColumn addRaceColumn(String name, boolean medalRace, Fleet... fleets); + RaceColumn addRaceColumn(String name, boolean medalRace); /** * Adds a tracked race to this leaderboard. If a {@link RaceColumn} with name columnName already exists * in this leaderboard, race is {@link RaceColumn#setTrackedRace(Fleet, TrackedRace) set as its tracked * race} and medalRace is ignored. Otherwise, a new {@link RaceColumn} column, with race - * as its tracked race, is created and added to this leaderboard. + * as its tracked race, is created and added to this leaderboard. The default fleet will be used. + * * @param medalRace * tells if the column to add represents a medal race which has double score and cannot be discarded; * ignored if the column named columnName already exists - * @param fleets - * the fleets to add to the {@link RaceColumn} created. If no fleets are specified, a single default - * fleet will be assigned to the race column created. * * @return the race column in the leaderboard used to represent the tracked race */ - RaceColumn addRace(TrackedRace race, String columnName, boolean medalRace, Fleet fleet); + RaceColumn addRace(TrackedRace race, String columnName, boolean medalRace); void removeRaceColumn(String columnName); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/DefaultFleetImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/DefaultFleetImpl.java new file mode 100644 index 00000000000..34635a370c2 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/DefaultFleetImpl.java @@ -0,0 +1,21 @@ +package com.sap.sailing.domain.leaderboard.impl; + +import com.sap.sailing.domain.base.IsManagedBySharedDomainFactory; +import com.sap.sailing.domain.base.SharedDomainFactory; +import com.sap.sailing.domain.base.impl.FleetImpl; +import com.sap.sailing.domain.common.LeaderboardNameConstants; + +public class DefaultFleetImpl extends FleetImpl implements IsManagedBySharedDomainFactory { + + private static final long serialVersionUID = 6233489616955255401L; + + public DefaultFleetImpl() { + super(LeaderboardNameConstants.DEFAULT_FLEET_NAME); + } + + @Override + public IsManagedBySharedDomainFactory resolve(SharedDomainFactory domainFactory) { + return FlexibleLeaderboardImpl.defaultFleet; + } + +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/FlexibleLeaderboardImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/FlexibleLeaderboardImpl.java index 120933181e4..df57ffb89d7 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/FlexibleLeaderboardImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/FlexibleLeaderboardImpl.java @@ -13,8 +13,6 @@ import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.base.RaceColumnListener; -import com.sap.sailing.domain.base.impl.FleetImpl; -import com.sap.sailing.domain.common.LeaderboardNameConstants; import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; import com.sap.sailing.domain.leaderboard.FlexibleRaceColumn; import com.sap.sailing.domain.leaderboard.ScoringScheme; @@ -39,7 +37,7 @@ import com.sap.sailing.domain.tracking.TrackedRace; public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements FlexibleLeaderboard { private static Logger logger = Logger.getLogger(FlexibleLeaderboardImpl.class.getName()); - protected static final Fleet defaultFleet = new FleetImpl(LeaderboardNameConstants.DEFAULT_FLEET_NAME); + protected static final DefaultFleetImpl defaultFleet = new DefaultFleetImpl(); private static final long serialVersionUID = -5708971849158747846L; private final List races; private final ScoringScheme scoringScheme; @@ -96,24 +94,25 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements } @Override - public RaceColumn addRace(TrackedRace race, String columnName, boolean medalRace, Fleet fleet) { - FlexibleRaceColumn column = addRaceColumn(columnName, medalRace, /* logAlreadyExistingColumn */ false, fleet); - column.setTrackedRace(fleet, race); // triggers listeners because this object was registered above as race column listener on the column + public RaceColumn addRace(TrackedRace race, String columnName, boolean medalRace) { + FlexibleRaceColumn column = addRaceColumn(columnName, medalRace, /* logAlreadyExistingColumn */false); + column.setTrackedRace(defaultFleet, race); // triggers listeners because this object was registered above as + // race column listener on the column return column; } @Override - public FlexibleRaceColumn addRaceColumn(String name, boolean medalRace, Fleet... fleets) { - return addRaceColumn(name, medalRace, /* logAlreadyExistingColumn */ true, fleets); + public FlexibleRaceColumn addRaceColumn(String name, boolean medalRace) { + return addRaceColumn(name, medalRace, /* logAlreadyExistingColumn */true); } - private FlexibleRaceColumn addRaceColumn(String name, boolean medalRace, boolean logAlreadyExistingColumn, Fleet... fleets) { + private FlexibleRaceColumn addRaceColumn(String name, boolean medalRace, boolean logAlreadyExistingColumn) { FlexibleRaceColumn column = getRaceColumnByName(name); if (column != null) { final String msg = "Trying to create race column with duplicate name "+name+" in leaderboard +"+getName(); logger.severe(msg); } else { - column = createRaceColumn(name, medalRace, fleets); + column = createRaceColumn(name, medalRace); column.addRaceColumnListener(this); races.add(column); column.setRaceLogInformation( @@ -159,17 +158,15 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements return Collections.unmodifiableCollection(new ArrayList(races)); } - protected RaceColumnImpl createRaceColumn(String column, boolean medalRace, Fleet... fleets) { - return new RaceColumnImpl( - column, - medalRace, - turnNullOrEmptyFleetsIntoDefaultFleet(fleets)); + protected RaceColumnImpl createRaceColumn(String column, boolean medalRace) { + return new RaceColumnImpl(column, medalRace); } protected Iterable turnNullOrEmptyFleetsIntoDefaultFleet(Fleet... fleets) { Iterable theFleets; if (fleets == null || fleets.length == 0) { - theFleets = Collections.singleton(defaultFleet); + Fleet defaultfleetCasted = defaultFleet; + theFleets = Collections.singleton(defaultfleetCasted); } else { theFleets = Arrays.asList(fleets); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RaceColumnImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RaceColumnImpl.java index 2b8eb35c6ea..0182667e6ce 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RaceColumnImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RaceColumnImpl.java @@ -1,8 +1,6 @@ package com.sap.sailing.domain.leaderboard.impl; -import java.util.ArrayList; import java.util.Collections; -import java.util.List; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.impl.AbstractRaceColumn; @@ -12,23 +10,12 @@ public class RaceColumnImpl extends AbstractRaceColumn implements FlexibleRaceCo private static final long serialVersionUID = -7801617988982540470L; private String name; - /** - * All fleets for which this column can contain a race. This is the maximum set of keys possible for - * {@link #trackedRaces} and {@link #raceIdentifiers}. - */ - private final Iterable fleets; private boolean medalRace; - public RaceColumnImpl(String name, boolean medalRace, Iterable fleets) { + public RaceColumnImpl(String name, boolean medalRace) { super(); this.name = name; this.medalRace = medalRace; - List myFleets = new ArrayList(); - for (Fleet fleet : fleets) { - myFleets.add(fleet); - } - Collections.sort(myFleets); - this.fleets = myFleets; } @Override @@ -53,8 +40,8 @@ public class RaceColumnImpl extends AbstractRaceColumn implements FlexibleRaceCo } @Override - public Iterable getFleets() { - return fleets; + public Iterable getFleets() { + return Collections.singleton(FlexibleLeaderboardImpl.defaultFleet); } } 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 3cea521e0a9..217133e099d 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 @@ -135,8 +135,7 @@ public class TestStoringAndRetrievingLeaderboardGroups extends AbstractMongoDBTe FlexibleLeaderboard leaderboard = new FlexibleLeaderboardImpl(leaderboardNames[0], new ThresholdBasedResultDiscardingRuleImpl(discardIndexResultsStartingWithHowManyRaces), new LowPoint(), null); racingEventService.addLeaderboard(leaderboard); - leaderboard.addRace(raceWithTwoCompetitors, raceColumnName1, /* medalRace */ false, - leaderboard.getFleet(null)); + leaderboard.addRace(raceWithTwoCompetitors, raceColumnName1, /* medalRace */false); leaderboard = new FlexibleLeaderboardImpl(leaderboardNames[1], new ThresholdBasedResultDiscardingRuleImpl(discardIndexResultsStartingWithHowManyRaces), new LowPoint(), null); racingEventService.addLeaderboard(leaderboard); @@ -192,8 +191,7 @@ public class TestStoringAndRetrievingLeaderboardGroups extends AbstractMongoDBTe FlexibleLeaderboard leaderboard = new FlexibleLeaderboardImpl(leaderboardNames[0], new ThresholdBasedResultDiscardingRuleImpl(discardIndexResultsStartingWithHowManyRaces), new LowPoint(), null); leaderboards.add(leaderboard); - leaderboard.addRace(raceWithTwoCompetitors, raceColumnName1, /* medalRace */ false, - leaderboard.getFleet(null)); + leaderboard.addRace(raceWithTwoCompetitors, raceColumnName1, /* medalRace */false); leaderboard = new FlexibleLeaderboardImpl(leaderboardNames[1], new ThresholdBasedResultDiscardingRuleImpl(discardIndexResultsStartingWithHowManyRaces), new LowPoint(), null); leaderboards.add(leaderboard); @@ -249,8 +247,7 @@ public class TestStoringAndRetrievingLeaderboardGroups extends AbstractMongoDBTe FlexibleLeaderboard leaderboard = new FlexibleLeaderboardImpl(leaderboardNames[0], new ThresholdBasedResultDiscardingRuleImpl(discardIndexResultsStartingWithHowManyRaces), new LowPoint(), null); leaderboards.add(leaderboard); - leaderboard.addRace(raceWithTwoCompetitors, raceColumnName1, /* medalRace */ false, - leaderboard.getFleet(null)); + leaderboard.addRace(raceWithTwoCompetitors, raceColumnName1, /* medalRace */false); leaderboard = new FlexibleLeaderboardImpl(leaderboardNames[1], new ThresholdBasedResultDiscardingRuleImpl(discardIndexResultsStartingWithHowManyRaces), new LowPoint(), null); leaderboards.add(leaderboard); @@ -435,7 +432,7 @@ public class TestStoringAndRetrievingLeaderboardGroups extends AbstractMongoDBTe final FlexibleLeaderboard leaderboard = new FlexibleLeaderboardImpl(leaderboardName, new ThresholdBasedResultDiscardingRuleImpl(discardIndexResultsStartingWithHowManyRaces), new LowPoint(), null); final Fleet fleet = leaderboard.getFleet(null); - final RaceColumn race = leaderboard.addRaceColumn(columnName, false, fleet); + final RaceColumn race = leaderboard.addRaceColumn(columnName, false); leaderboards.add(leaderboard); final LeaderboardGroup group = new LeaderboardGroupImpl(groupName, groupDescription, false, leaderboards); diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboards.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboards.java index f6b2a132cca..4e786b69d9b 100755 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboards.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboards.java @@ -84,7 +84,7 @@ public class TestStoringAndRetrievingLeaderboards extends AbstractMongoDBTest { final String raceColumnName1 = "My First Race 1"; MockedTrackedRaceWithFixedRankAndManyCompetitors raceWithTwoCompetitors = new MockedTrackedRaceWithFixedRankAndManyCompetitors(wolfgang, /* rank */ 1, /* started */ true); raceWithTwoCompetitors.addCompetitor(hasso); - leaderboard.addRace(raceWithTwoCompetitors, raceColumnName1, /* medalRace */ false, leaderboard.getFleet(null)); + leaderboard.addRace(raceWithTwoCompetitors, raceColumnName1, /* medalRace */ false); leaderboard.setSuppressed(wolfgang, true); assertTrue(Util.contains(leaderboard.getSuppressedCompetitors(), wolfgang)); assertFalse(Util.contains(leaderboard.getSuppressedCompetitors(), hasso)); @@ -121,14 +121,14 @@ public class TestStoringAndRetrievingLeaderboards extends AbstractMongoDBTest { Competitor competitor = createCompetitor(); TrackedRace raceWithOneCompetitor1 = new MockedTrackedRaceWithFixedRank(competitor, /* rank */ 1, /* started */ true); final String raceColumnName1 = "My First Race 1"; - leaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace */ false, leaderboard.getFleet(null)); + leaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace */ false); final String displayName = "$$$ ... The Renamed Competitor ... $$$"; leaderboard.setDisplayName(competitor, displayName); new MongoObjectFactoryImpl(db).storeLeaderboard(leaderboard); FlexibleLeaderboard loadedLeaderboard = (FlexibleLeaderboard) new DomainObjectFactoryImpl(db, DomainFactory.INSTANCE).loadLeaderboard( leaderboardName, /* regattaRegistry */null); // attach tracked race to leaderboard to ensure that competitor object is assigned properly - loadedLeaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace, ignored */ false, leaderboard.getFleet(null)); + loadedLeaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace, ignored */ false); Competitor loadedCompetitor = loadedLeaderboard.getCompetitorByName(competitor.getName()); assertEquals(displayName, loadedLeaderboard.getDisplayName(loadedCompetitor)); } @@ -168,12 +168,12 @@ public class TestStoringAndRetrievingLeaderboards extends AbstractMongoDBTest { new LowPoint(), null); Competitor competitor = createCompetitor(); TrackedRace raceWithOneCompetitor = new MockedTrackedRaceWithFixedRank(competitor, /* rank */ 1, /* started */ true); - leaderboard.addRace(raceWithOneCompetitor, raceColumnName, /* medalRace */ false, leaderboard.getFleet(null)); + leaderboard.addRace(raceWithOneCompetitor, raceColumnName, /* medalRace */ false); leaderboard.setCarriedPoints(competitor, carriedPointsForWolfgangHunger); new MongoObjectFactoryImpl(db).storeLeaderboard(leaderboard); FlexibleLeaderboard loadedLeaderboard = (FlexibleLeaderboard) new DomainObjectFactoryImpl(db, DomainFactory.INSTANCE).loadLeaderboard(leaderboardName, /* regattaRegistry */ null); // attach tracked race to leaderboard to ensure that competitor object is assigned properly - loadedLeaderboard.addRace(raceWithOneCompetitor, raceColumnName, /* medalRace, ignored */ false, leaderboard.getFleet(null)); + loadedLeaderboard.addRace(raceWithOneCompetitor, raceColumnName, /* medalRace, ignored */ false); assertEquals(leaderboardName, loadedLeaderboard.getName()); assertTrue(Arrays.equals(discardIndexResultsStartingWithHowManyRaces, ((ThresholdBasedResultDiscardingRule) loadedLeaderboard.getResultDiscardingRule()).getDiscardIndexResultsStartingWithHowManyRaces())); @@ -194,16 +194,14 @@ public class TestStoringAndRetrievingLeaderboards extends AbstractMongoDBTest { Competitor competitor = createCompetitor(); TrackedRace raceWithOneCompetitor1 = new MockedTrackedRaceWithFixedRank(competitor, /* rank */ 1, /* started */ true); TrackedRace raceWithOneCompetitor2 = new MockedTrackedRaceWithFixedRank(competitor, /* rank */ 2, /* started */ true); - leaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace */ false, leaderboard.getFleet(null)); - leaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace */ true, leaderboard.getFleet(null)); + leaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace */ false); + leaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace */ true); leaderboard.getScoreCorrection().correctScore(competitor, leaderboard.getRaceColumnByName(raceColumnName1), correctedPoints); new MongoObjectFactoryImpl(db).storeLeaderboard(leaderboard); FlexibleLeaderboard loadedLeaderboard = (FlexibleLeaderboard) new DomainObjectFactoryImpl(db, DomainFactory.INSTANCE).loadLeaderboard(leaderboardName, /* regattaRegistry */ null); // attach tracked race to leaderboard to ensure that competitor object is assigned properly - RaceColumn loadedColumn1 = loadedLeaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace, ignored */ false, - leaderboard.getFleet(null)); - RaceColumn loadedColumn2 = loadedLeaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace, ignored */ false, - leaderboard.getFleet(null)); + RaceColumn loadedColumn1 = loadedLeaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace, ignored */ false); + RaceColumn loadedColumn2 = loadedLeaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace, ignored */ false); assertEquals(leaderboardName, loadedLeaderboard.getName()); assertTrue(loadedLeaderboard.getScoreCorrection().isScoreCorrected(competitor, loadedColumn1, MillisecondsTimePoint.now())); assertEquals(correctedPoints, (double) loadedLeaderboard.getScoreCorrection().getExplicitScoreCorrection(competitor, loadedColumn1), 0.00000001); @@ -222,14 +220,14 @@ public class TestStoringAndRetrievingLeaderboards extends AbstractMongoDBTest { Competitor competitor = createCompetitor(); TrackedRace raceWithOneCompetitor1 = new MockedTrackedRaceWithFixedRank(competitor, /* rank */ 1, /* started */ true); TrackedRace raceWithOneCompetitor2 = new MockedTrackedRaceWithFixedRank(competitor, /* rank */ 2, /* started */ true); - leaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace */ false, leaderboard.getFleet(null)); - leaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace */ true, leaderboard.getFleet(null)); + leaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace */ false); + leaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace */ true); leaderboard.getScoreCorrection().correctScore(competitor, leaderboard.getRaceColumnByName(raceColumnName1), correctedPoints); new MongoObjectFactoryImpl(db).storeLeaderboard(leaderboard); FlexibleLeaderboard loadedLeaderboard = (FlexibleLeaderboard) new DomainObjectFactoryImpl(db, DomainFactory.INSTANCE).loadLeaderboard(leaderboardName, /* regattaRegistry */ null); // attach tracked race to leaderboard to ensure that competitor object is assigned properly - RaceColumn loadedColumn1 = loadedLeaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace, ignored */ false, leaderboard.getFleet(null)); - RaceColumn loadedColumn2 = loadedLeaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace, ignored */ false, leaderboard.getFleet(null)); + RaceColumn loadedColumn1 = loadedLeaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace, ignored */ false); + RaceColumn loadedColumn2 = loadedLeaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace, ignored */ false); assertEquals(leaderboardName, loadedLeaderboard.getName()); assertTrue(loadedLeaderboard.getScoreCorrection().isScoreCorrected(competitor, loadedColumn1, MillisecondsTimePoint.now())); assertEquals(correctedPoints, (double) loadedLeaderboard.getScoreCorrection().getExplicitScoreCorrection(competitor, loadedColumn1), 0.00000001); @@ -248,16 +246,14 @@ public class TestStoringAndRetrievingLeaderboards extends AbstractMongoDBTest { Competitor competitor = createCompetitor(); TrackedRace raceWithOneCompetitor1 = new MockedTrackedRaceWithFixedRank(competitor, /* rank */ 1, /* started */ true); TrackedRace raceWithOneCompetitor2 = new MockedTrackedRaceWithFixedRank(competitor, /* rank */ 2, /* started */ true); - leaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace */ false, leaderboard.getFleet(null)); - leaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace */ true, leaderboard.getFleet(null)); + leaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace */ false); + leaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace */ true); leaderboard.getScoreCorrection().setMaxPointsReason(competitor, leaderboard.getRaceColumnByName(raceColumnName2), maxPointsReason); new MongoObjectFactoryImpl(db).storeLeaderboard(leaderboard); FlexibleLeaderboard loadedLeaderboard = (FlexibleLeaderboard) new DomainObjectFactoryImpl(db, DomainFactory.INSTANCE).loadLeaderboard(leaderboardName, /* regattaRegistry */ null); // attach tracked race to leaderboard to ensure that competitor object is assigned properly - RaceColumn loadedColumn1 = loadedLeaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace, ignored */ false, - leaderboard.getFleet(null)); - RaceColumn loadedColumn2 = loadedLeaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace, ignored */ false, - leaderboard.getFleet(null)); + RaceColumn loadedColumn1 = loadedLeaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace, ignored */ false); + RaceColumn loadedColumn2 = loadedLeaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace, ignored */ false); assertEquals(leaderboardName, loadedLeaderboard.getName()); assertFalse(loadedLeaderboard.getScoreCorrection().isScoreCorrected(competitor, loadedColumn1, MillisecondsTimePoint.now())); assertTrue(loadedLeaderboard.getScoreCorrection().isScoreCorrected(competitor, loadedColumn2, MillisecondsTimePoint.now())); @@ -277,19 +273,15 @@ public class TestStoringAndRetrievingLeaderboards extends AbstractMongoDBTest { Competitor competitor = createCompetitor(); TrackedRace raceWithOneCompetitor1 = new MockedTrackedRaceWithFixedRank(competitor, /* rank */ 1, /* started */ true); TrackedRace raceWithOneCompetitor2 = new MockedTrackedRaceWithFixedRank(competitor, /* rank */ 2, /* started */ true); - leaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace */ false, - leaderboard.getFleet(null)); - leaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace */ true, - leaderboard.getFleet(null)); + leaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace */ false); + leaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace */ true); leaderboard.getScoreCorrection().correctScore(competitor, leaderboard.getRaceColumnByName(raceColumnName1), correctedPoints); leaderboard.getScoreCorrection().setMaxPointsReason(competitor, leaderboard.getRaceColumnByName(raceColumnName2), maxPointsReason); new MongoObjectFactoryImpl(db).storeLeaderboard(leaderboard); FlexibleLeaderboard loadedLeaderboard = (FlexibleLeaderboard) new DomainObjectFactoryImpl(db, DomainFactory.INSTANCE).loadLeaderboard(leaderboardName, /* regattaRegistry */ null); // attach tracked race to leaderboard to ensure that competitor object is assigned properly - RaceColumn loadedColumn1 = loadedLeaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace, ignored */ false, - leaderboard.getFleet(null)); - RaceColumn loadedColumn2 = loadedLeaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace, ignored */ false, - leaderboard.getFleet(null)); + RaceColumn loadedColumn1 = loadedLeaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace, ignored */ false); + RaceColumn loadedColumn2 = loadedLeaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace, ignored */ false); assertEquals(leaderboardName, loadedLeaderboard.getName()); assertTrue(loadedLeaderboard.getScoreCorrection().isScoreCorrected(competitor, loadedColumn1, MillisecondsTimePoint.now())); assertEquals(correctedPoints, (double) loadedLeaderboard.getScoreCorrection().getExplicitScoreCorrection(competitor, loadedColumn1), 0.000000001); @@ -319,10 +311,8 @@ public class TestStoringAndRetrievingLeaderboards extends AbstractMongoDBTest { Competitor competitor = createCompetitor(); TrackedRace raceWithOneCompetitor1 = new MockedTrackedRaceWithFixedRank(competitor, /* rank */ 1, /* started */ true); TrackedRace raceWithOneCompetitor2 = new MockedTrackedRaceWithFixedRank(competitor, /* rank */ 2, /* started */ true); - RaceColumn r1 = leaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace */ false, - leaderboard.getFleet(null)); - RaceColumn r2 = leaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace */ true, - leaderboard.getFleet(null)); + RaceColumn r1 = leaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace */ false); + RaceColumn r2 = leaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace */ true); leaderboard.getScoreCorrection().correctScore(competitor, leaderboard.getRaceColumnByName(raceColumnName1), correctedPoints); leaderboard.getScoreCorrection().setMaxPointsReason(competitor, leaderboard.getRaceColumnByName(raceColumnName2), maxPointsReason); // now release the tracked races; leaving no competitors in leaderboard.getCompetitors() @@ -331,10 +321,8 @@ public class TestStoringAndRetrievingLeaderboards extends AbstractMongoDBTest { new MongoObjectFactoryImpl(db).storeLeaderboard(leaderboard); FlexibleLeaderboard loadedLeaderboard = (FlexibleLeaderboard) new DomainObjectFactoryImpl(db, DomainFactory.INSTANCE).loadLeaderboard(leaderboardName, /* regattaRegistry */ null); // attach tracked race to leaderboard to ensure that competitor object is assigned properly - RaceColumn loadedColumn1 = loadedLeaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace, ignored */ false, - leaderboard.getFleet(null)); - RaceColumn loadedColumn2 = loadedLeaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace, ignored */ false, - leaderboard.getFleet(null)); + RaceColumn loadedColumn1 = loadedLeaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace, ignored */ false); + RaceColumn loadedColumn2 = loadedLeaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace, ignored */ false); assertEquals(leaderboardName, loadedLeaderboard.getName()); assertTrue(loadedLeaderboard.getScoreCorrection().isScoreCorrected(competitor, loadedColumn1, MillisecondsTimePoint.now())); assertEquals(correctedPoints, (double) loadedLeaderboard.getScoreCorrection().getExplicitScoreCorrection(competitor, loadedColumn1), 0.000000001); @@ -368,10 +356,8 @@ public class TestStoringAndRetrievingLeaderboards extends AbstractMongoDBTest { /* dateOfBirth */ null, "This is Rigo, the coach")), new BoatImpl("Dr. Wolfgang Hunger's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); TrackedRace raceWithOneCompetitor1 = new MockedTrackedRaceWithFixedRank(competitor, /* rank */ 1, /* started */ true); TrackedRace raceWithOneCompetitor2 = new MockedTrackedRaceWithFixedRank(competitor, /* rank */ 2, /* started */ true); - leaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace */ false, - leaderboard.getFleet(null)); - leaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace */ true, - leaderboard.getFleet(null)); + leaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace */ false); + leaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace */ true); leaderboard.getScoreCorrection().correctScore(competitor, leaderboard.getRaceColumnByName(raceColumnName1), correctedPoints); leaderboard.getScoreCorrection().setMaxPointsReason(competitor, leaderboard.getRaceColumnByName(raceColumnName2), maxPointsReason); final MongoObjectFactoryImpl mongoObjectFactory = new MongoObjectFactoryImpl(db); @@ -381,10 +367,8 @@ public class TestStoringAndRetrievingLeaderboards extends AbstractMongoDBTest { // don't attach tracked race to leaderboard and immediately store again mongoObjectFactory.storeLeaderboard(loadedLeaderboard); FlexibleLeaderboard loadedLeaderboard2 = (FlexibleLeaderboard) domainObjectFactory.loadLeaderboard(leaderboardName, /* regattaRegistry */ null); - RaceColumn loadedColumn1 = loadedLeaderboard2.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace, ignored */ false, - leaderboard.getFleet(null)); - RaceColumn loadedColumn2 = loadedLeaderboard2.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace, ignored */ false, - leaderboard.getFleet(null)); + RaceColumn loadedColumn1 = loadedLeaderboard2.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace, ignored */ false); + RaceColumn loadedColumn2 = loadedLeaderboard2.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace, ignored */ false); assertEquals(leaderboardName, loadedLeaderboard2.getName()); assertTrue(loadedLeaderboard2.getScoreCorrection().isScoreCorrected(competitor, loadedColumn1, MillisecondsTimePoint.now())); assertEquals(correctedPoints, (double) loadedLeaderboard2.getScoreCorrection().getExplicitScoreCorrection(competitor, loadedColumn1), 0.000000001); diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingRaceLogInLeaderboards.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingRaceLogInLeaderboards.java index ac578eb78e1..d7e0325ad4a 100644 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingRaceLogInLeaderboards.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingRaceLogInLeaderboards.java @@ -85,8 +85,7 @@ public class TestStoringAndRetrievingRaceLogInLeaderboards extends RaceLogMongoD RaceLogStore raceLogStore = MongoRaceLogStoreFactory.INSTANCE.getMongoRaceLogStore(mongoObjectFactory, domainObjectFactory); leaderboard = new FlexibleLeaderboardImpl(raceLogStore, leaderboardName, new ThresholdBasedResultDiscardingRuleImpl(discardIndexResultsStartingWithHowManyRaces), new LowPoint(), null); - Fleet defaultFleet = leaderboard.getFleet(null); - leaderboard.addRaceColumn(raceColumnName, /* medalRace */ false, defaultFleet); + leaderboard.addRaceColumn(raceColumnName, /* medalRace */false); } private void addAndStoreRaceLogEvent(FlexibleLeaderboard leaderboard, String raceColumnName, RaceLogEvent event) { @@ -130,7 +129,7 @@ public class TestStoringAndRetrievingRaceLogInLeaderboards extends RaceLogMongoD db.getLastError(); // sync DB leaderboard.removeRaceColumn(raceColumnName); db.getLastError(); // sync DB - leaderboard.addRaceColumn(raceColumnName, /* medalRace */ false, defaultFleet); + leaderboard.addRaceColumn(raceColumnName, /* medalRace */false); // now assert that the race log is empty because the column was removed and so should have been the race log RaceLog loadedRaceLog = leaderboard.getRaceColumnByName(raceColumnName).getRaceLog(leaderboard.getRaceColumnByName(raceColumnName).getFleets().iterator().next()); loadedRaceLog.lockForRead(); @@ -173,7 +172,7 @@ public class TestStoringAndRetrievingRaceLogInLeaderboards extends RaceLogMongoD } finally { reloadedRenamedRaceLog.unlockAfterRead(); } - leaderboard.addRaceColumn(raceColumnName, /* medalRace */ false, defaultFleet); + leaderboard.addRaceColumn(raceColumnName, /* medalRace */false); mongoObjectFactory.storeLeaderboard(leaderboard); // now assert that the race log is empty because the renamed column's persistent race log is expected to have // been removed diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/LeaderboardDiscardingRulesTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/LeaderboardDiscardingRulesTest.java index 343f87f3aed..fa490b435f1 100644 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/LeaderboardDiscardingRulesTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/LeaderboardDiscardingRulesTest.java @@ -10,9 +10,6 @@ import org.junit.Test; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.DomainFactory; -import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.impl.FleetImpl; -import com.sap.sailing.domain.common.LeaderboardNameConstants; import com.sap.sailing.domain.common.MaxPointsReason; import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.TimePoint; @@ -68,19 +65,18 @@ public class LeaderboardDiscardingRulesTest { FlexibleLeaderboard leaderboard = (FlexibleLeaderboard) racingEventService.getLeaderboardByName(LEADERBOARDNAME); assertNotNull(leaderboard); BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true); - Fleet fleet = new FleetImpl(LeaderboardNameConstants.DEFAULT_FLEET_NAME); Competitor hasso = AbstractLeaderboardTest.createCompetitor("Dr. Hasso Plattner"); final TrackedRace race1 = new MockedTrackedRaceWithFixedRank(hasso, /* rank */ 123, /* started */ true, boatClass); - leaderboard.addRace(race1, "R1", /* medalRace */ false, fleet); + leaderboard.addRace(race1, "R1", /* medalRace */false); final TrackedRace race2 = new MockedTrackedRaceWithFixedRank(hasso, /* rank */ 123, /* started */ true, boatClass); - leaderboard.addRace(race2, "R2", /* medalRace */ false, fleet); + leaderboard.addRace(race2, "R2", /* medalRace */false); Competitor wolfgang = AbstractLeaderboardTest.createCompetitor("Wolfgang Hunger"); final TrackedRace race3 = new MockedTrackedRaceWithFixedRank(wolfgang, /* rank */ 123, /* started */ true, boatClass); - leaderboard.addRace(race3, "R3", /* medalRace */ false, fleet); + leaderboard.addRace(race3, "R3", /* medalRace */false); final TrackedRace race4 = new MockedTrackedRaceWithFixedRank(wolfgang, /* rank */ 124, /* started */ true, boatClass); - leaderboard.addRace(race4, "R4", /* medalRace */ false, fleet); + leaderboard.addRace(race4, "R4", /* medalRace */false); leaderboard.getScoreCorrection().correctScore(hasso, leaderboard.getRaceColumnByName("R3"), 123.); leaderboard.getScoreCorrection().correctScore(hasso, leaderboard.getRaceColumnByName("R4"), 123.); @@ -108,23 +104,22 @@ public class LeaderboardDiscardingRulesTest { FlexibleLeaderboard leaderboard = (FlexibleLeaderboard) racingEventService.getLeaderboardByName(LEADERBOARDNAME); assertNotNull(leaderboard); BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("ESS40", /* typicallyStartsUpwind */ true); - Fleet fleet = new FleetImpl(LeaderboardNameConstants.DEFAULT_FLEET_NAME); Competitor rasmus = AbstractLeaderboardTest.createCompetitor("Rasmus"); final TrackedRace race1 = new MockedTrackedRaceWithFixedRank(rasmus, /* rank */ 2, /* started */ true, boatClass); // score 2.0 - leaderboard.addRace(race1, "R1", /* medalRace */ false, fleet); + leaderboard.addRace(race1, "R1", /* medalRace */false); final TrackedRace race2 = new MockedTrackedRaceWithFixedRank(rasmus, /* rank */ 2, /* started */ true, boatClass); // score 2.0 - leaderboard.addRace(race2, "R2", /* medalRace */ false, fleet); + leaderboard.addRace(race2, "R2", /* medalRace */false); Competitor jes = AbstractLeaderboardTest.createCompetitor("Jes"); final TrackedRace race3 = new MockedTrackedRaceWithFixedRank(jes, /* rank */ 2, /* started */ true, boatClass); // score 2.0 - leaderboard.addRace(race3, "R3", /* medalRace */ false, fleet); + leaderboard.addRace(race3, "R3", /* medalRace */false); final TrackedRace race4 = new MockedTrackedRaceWithFixedRank(jes, /* rank */ 3, /* started */ true, boatClass); // score 1.0 - leaderboard.addRace(race4, "R4", /* medalRace */ false, fleet); + leaderboard.addRace(race4, "R4", /* medalRace */false); Competitor nobody = AbstractLeaderboardTest.createCompetitor("Nobody"); final TrackedRace race5 = new MockedTrackedRaceWithFixedRank(nobody, /* rank */ 3, /* started */ true, boatClass); // score 1.0 - leaderboard.addRace(race5, "R5", /* medalRace */ false, fleet); + leaderboard.addRace(race5, "R5", /* medalRace */false); leaderboard.getScoreCorrection().correctScore(rasmus, leaderboard.getRaceColumnByName("R3"), 2.0); leaderboard.getScoreCorrection().correctScore(rasmus, leaderboard.getRaceColumnByName("R4"), 2.0); @@ -153,19 +148,18 @@ public class LeaderboardDiscardingRulesTest { FlexibleLeaderboard leaderboard = (FlexibleLeaderboard) racingEventService.getLeaderboardByName(LEADERBOARDNAME); assertNotNull(leaderboard); BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true); - Fleet fleet = new FleetImpl(LeaderboardNameConstants.DEFAULT_FLEET_NAME); Competitor hasso = AbstractLeaderboardTest.createCompetitor("Dr. Hasso Plattner"); final TrackedRace race1 = new MockedTrackedRaceWithFixedRank(hasso, /* rank */ 123, /* started */ true, boatClass); - leaderboard.addRace(race1, "R1", /* medalRace */ false, fleet); + leaderboard.addRace(race1, "R1", /* medalRace */false); final TrackedRace race2 = new MockedTrackedRaceWithFixedRank(hasso, /* rank */ 123, /* started */ true, boatClass); - leaderboard.addRace(race2, "R2", /* medalRace */ false, fleet); + leaderboard.addRace(race2, "R2", /* medalRace */false); Competitor wolfgang = AbstractLeaderboardTest.createCompetitor("Wolfgang Hunger"); final TrackedRace race3 = new MockedTrackedRaceWithFixedRank(wolfgang, /* rank */ 123, /* started */ true, boatClass); - leaderboard.addRace(race3, "R3", /* medalRace */ false, fleet); + leaderboard.addRace(race3, "R3", /* medalRace */false); final TrackedRace race4 = new MockedTrackedRaceWithFixedRank(wolfgang, /* rank */ 124, /* started */ true, boatClass); - leaderboard.addRace(race4, "R4", /* medalRace */ false, fleet); + leaderboard.addRace(race4, "R4", /* medalRace */false); leaderboard.getScoreCorrection().correctScore(hasso, leaderboard.getRaceColumnByName("R3"), 123.); leaderboard.getScoreCorrection().correctScore(hasso, leaderboard.getRaceColumnByName("R4"), 123.); 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 d3c46dfee56..6a17ee26eb6 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 @@ -1844,4 +1844,215 @@ public class MasterDataImportTest { Assert.assertEquals(factor, metaColumn.getFactor()); } + + @Test + public void testMasterDataImportWithFlexibleLeaderboard() throws MalformedURLException, IOException, + InterruptedException, ClassNotFoundException { + // Setup source service + RacingEventService sourceService = new RacingEventServiceImpl(); + UUID courseAreaUUID = UUID.randomUUID(); + + List raceColumnNames = new ArrayList(); + String raceColumnName = "T1"; + raceColumnNames.add(raceColumnName); + raceColumnNames.add("T2"); + final List emptyRaceColumnNamesList = Collections.emptyList(); + + List series = new ArrayList(); + List fleets = new ArrayList(); + String testFleet1Name = "testFleet1"; + FleetImpl testFleet1 = new FleetImpl(testFleet1Name); + fleets.add(testFleet1); + fleets.add(new FleetImpl("testFleet2")); + series.add(new SeriesImpl("testSeries", false, fleets, emptyRaceColumnNamesList, sourceService)); + UUID regattaUUID = UUID.randomUUID(); + Regatta regatta = sourceService.createRegatta( + RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, regattaUUID, + series, true, new LowPoint(), courseAreaUUID, /* useStartTimeInference */true); + for (String name : raceColumnNames) { + series.get(0).addRaceColumn(name, sourceService); + } + double factor = 3.0; + series.get(0).getRaceColumnByName(raceColumnName).setFactor(factor); + + int[] discardRule = { 1, 2, 3, 4 }; + ScoringScheme scoringScheme = new LowPoint(); + String flexLeaderboardName = "FlexName"; + FlexibleLeaderboard leaderboard = sourceService.addFlexibleLeaderboard(flexLeaderboardName, "TestFlex", + discardRule, + scoringScheme, courseAreaUUID); + List leaderboardNames = new ArrayList(); + leaderboardNames.add(leaderboard.getName()); + LeaderboardGroup group = sourceService.addLeaderboardGroup(UUID.randomUUID(), TEST_GROUP_NAME, "testGroupDesc", + false, leaderboardNames, null, null); + + + // Set tracked Race with competitors + List competitors = new ArrayList(); + UUID competitorUUID = UUID.randomUUID(); + Set sailors = new HashSet(); + sailors.add(new PersonImpl("Froderik Poterson", new NationalityImpl("GER"), new Date(645487200000L), + "Oberhoschy")); + DynamicPerson coach = new PersonImpl("Lennart Hensler", new NationalityImpl("GER"), new Date(645487200000L), + "Der Lennart halt"); + DynamicTeam team = new TeamImpl("Pros", sailors, coach); + BoatClass boatClass = new BoatClassImpl("H16", true); + DynamicBoat boat = new BoatImpl("Wingy", boatClass, "GER70133"); + CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, team, boat); + competitors.add(competitor); + UUID competitorToSuppressUUID = UUID.randomUUID(); + Set sailors2 = new HashSet(); + sailors2.add(new PersonImpl("Angela Merkel", new NationalityImpl("GER"), new Date(645487200000L), + "segelt auch mit")); + DynamicPerson coach2 = new PersonImpl("Peer Steinbrueck", new NationalityImpl("GER"), new Date(645487200000L), + "Bester Coach"); + DynamicTeam team2 = new TeamImpl("Noobs", sailors2, coach2); + DynamicBoat boat2 = new BoatImpl("LahmeEnte", boatClass, "GER1337"); + CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, team2, + boat2); + competitors.add(competitorToSuppress); + TrackedRace trackedRace = new DummyTrackedRace(competitors, regatta, null); + + leaderboard.addRace(trackedRace, raceColumnName, false); + RaceColumn raceColumnOnLeaderboard = leaderboard.getRaceColumnByName(raceColumnName); + raceColumnOnLeaderboard.setFactor(3.0); + + // Set log event + RaceLogEventFactory factory = new RaceLogEventFactoryImpl(); + TimePoint logTimePoint = new MillisecondsTimePoint(1372489200000L); + TimePoint logTimePoint2 = new MillisecondsTimePoint(1372489201000L); + RaceLogStartTimeEvent logEvent = factory.createStartTimeEvent(logTimePoint, author, 1, logTimePoint); + Fleet defaultFleet = leaderboard.getFleet(null); + raceColumnOnLeaderboard.getRaceLog(defaultFleet).add(logEvent); + Position p = new DegreePosition(3, 3); + Wind wind = new WindImpl(p, logTimePoint2, new KnotSpeedWithBearingImpl(5, new DegreeBearingImpl(12))); + RaceLogWindFixEvent windEvent = factory.createWindFixEvent(logTimePoint2, author, UUID.randomUUID(), + new ArrayList(), 2, wind); + raceColumnOnLeaderboard.getRaceLog(defaultFleet).add(windEvent); + storedLogUUIDs.add(logEvent.getId()); + storedLogUUIDs.add(windEvent.getId()); + + // Set score correction + double scoreCorrection = 12.0; + leaderboard.getScoreCorrection().correctScore(competitor, raceColumnOnLeaderboard, scoreCorrection); + MaxPointsReason maxPointsReason = MaxPointsReason.DNS; + leaderboard.getScoreCorrection().setMaxPointsReason(competitor, raceColumnOnLeaderboard, maxPointsReason); + + // Set carried Points + double carriedPoints = 2.0; + leaderboard.setCarriedPoints(competitor, carriedPoints); + + // Set suppressed competitor + leaderboard.setSuppressed(competitorToSuppress, true); + + // Set display name + String nickName = "Angie"; + leaderboard.setDisplayName(competitorToSuppress, nickName); + + // Serialize + List groupNamesToExport = new ArrayList(); + groupNamesToExport.add(group.getName()); + + RacingEventService destService; + DomainFactory domainFactory; + MasterDataResource resource = new MasterDataResource(); + MasterDataResource spyResource = spyResource(resource, sourceService); + Response response = spyResource.getMasterDataByLeaderboardGroups(groupNamesToExport, false, true); + StreamingOutput streamingOutput = (StreamingOutput) response.getEntity(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + UUID randomUUID = UUID.randomUUID(); + InputStream inputStream = null; + try { + streamingOutput.write(os); + os.flush(); + // Delete all data above from the database, to allow recreating all of it on target server + deleteAllDataFromDatabase(); + // Import in new service + destService = new RacingEventServiceImplMock(new DataImportProgressImpl(randomUUID)); + domainFactory = destService.getBaseDomainFactory(); + inputStream = new ByteArrayInputStream(os.toByteArray()); + + MasterDataImporter importer = new MasterDataImporter(domainFactory, destService); + importer.importFromStream(inputStream, randomUUID, false); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(e); + } finally { + os.close(); + inputStream.close(); + } + + MasterDataImportObjectCreationCount creationCount = destService.getDataImportLock().getProgress(randomUUID) + .getResult(); + + Assert.assertNotNull(creationCount); + LeaderboardGroup leaderboardGroupOnTarget = destService.getLeaderboardGroupByName(TEST_GROUP_NAME); + Assert.assertNotNull(leaderboardGroupOnTarget); + Leaderboard leaderboardOnTarget = destService.getLeaderboardByName(flexLeaderboardName); + Assert.assertNotNull(leaderboardOnTarget); + + RaceColumn raceColumnOnTarget = leaderboardOnTarget.getRaceColumns().iterator().next(); + Fleet defaultFleetOnTarget = raceColumnOnTarget.getFleets().iterator().next(); + Assert.assertNotNull(raceColumnOnTarget); + Assert.assertNull(raceColumnOnTarget.getTrackedRace(defaultFleetOnTarget)); + + Regatta regattaOnTarget = destService.createRegatta( + RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, regattaUUID, + series, true, new LowPoint(), courseAreaUUID, /* useStartTimeInference */true); + + raceColumnOnTarget.setTrackedRace(defaultFleetOnTarget, + new DummyTrackedRace( + competitors, regattaOnTarget, null)); + + Assert.assertTrue(leaderboardOnTarget.getScoreCorrection().hasCorrectionFor(raceColumnOnTarget)); + Competitor competitorOnTarget = domainFactory.getExistingCompetitorById(competitorUUID); + Set competitorsCreatedOnTarget = new HashSet(); + competitorsCreatedOnTarget.add(competitorOnTarget); + + Assert.assertEquals(defaultFleetOnTarget, leaderboardOnTarget.getFleet(null)); + TrackedRace trackedRaceForTarget = new DummyTrackedRace(competitorsCreatedOnTarget, regattaOnTarget, null); + + raceColumnOnTarget.setTrackedRace(defaultFleetOnTarget, trackedRaceForTarget); + + Assert.assertEquals(factor, raceColumnOnTarget.getFactor()); + + // Check for score corrections + Assert.assertEquals( + scoreCorrection, + leaderboardOnTarget.getScoreCorrection().getExplicitScoreCorrection(competitorOnTarget, + raceColumnOnTarget)); + Assert.assertEquals( + maxPointsReason, + leaderboardOnTarget.getScoreCorrection().getMaxPointsReason(competitorOnTarget, raceColumnOnTarget, + MillisecondsTimePoint.now())); + + // Check for carried points + Assert.assertEquals(carriedPoints, leaderboardOnTarget.getCarriedPoints(competitorOnTarget)); + + // Check for suppressed competitor + Assert.assertTrue(leaderboardOnTarget.getSuppressedCompetitors().iterator().hasNext()); + Competitor suppressedCompetitorOnTarget = domainFactory.getCompetitorStore().getExistingCompetitorById( + competitorToSuppressUUID); + Assert.assertEquals(suppressedCompetitorOnTarget, leaderboardOnTarget.getSuppressedCompetitors().iterator() + .next()); + + // Check for competitor display name + Assert.assertEquals(nickName, leaderboardOnTarget.getDisplayName(suppressedCompetitorOnTarget)); + + // Check for race log event + Assert.assertNotNull(raceColumnOnTarget.getRaceLog(defaultFleetOnTarget).getFirstRawFixAtOrAfter(logTimePoint)); + Assert.assertEquals(logEvent.getId(), raceColumnOnTarget.getRaceLog(defaultFleetOnTarget) + .getFirstRawFixAtOrAfter(logTimePoint).getId()); + Assert.assertNotNull(raceColumnOnTarget.getRaceLog(defaultFleetOnTarget).getFirstFixAtOrAfter(logTimePoint2)); + Assert.assertEquals(wind, ((RaceLogWindFixEvent) raceColumnOnTarget.getRaceLog(defaultFleetOnTarget) + .getFirstFixAtOrAfter(logTimePoint2)).getWindFix()); + + // Check for persisting of race log events: + RacingEventService dest2 = new RacingEventServiceImplMock(); + Leaderboard lb2 = dest2.getLeaderboardByName(flexLeaderboardName); + RaceColumn raceColumn2 = lb2.getRaceColumns().iterator().next(); + RaceLog raceLog2 = raceColumn2.getRaceLog(raceColumn2.getFleets().iterator().next()); + Assert.assertEquals(logEvent.getId(), raceLog2.getFirstRawFixAtOrAfter(logTimePoint).getId()); + } + } 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 cf775d02352..1e3b93a177e 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 @@ -664,8 +664,7 @@ public class RacingEventServiceImpl implements RacingEventServiceWithTestSupport if (leaderboard != null) { if (leaderboard instanceof FlexibleLeaderboard) { // uses the default fleet as the single fleet for the new column - RaceColumn result = ((FlexibleLeaderboard) leaderboard).addRaceColumn(columnName, medalRace, - leaderboard.getFleet(null)); + RaceColumn result = ((FlexibleLeaderboard) leaderboard).addRaceColumn(columnName, medalRace); updateStoredLeaderboard((FlexibleLeaderboard) leaderboard); return result; } else { @@ -1308,8 +1307,7 @@ public class RacingEventServiceImpl implements RacingEventServiceWithTestSupport .get(LeaderboardNameConstants.DEFAULT_LEADERBOARD_NAME); if (defaultLeaderboard != null) { String columnName = trackedRace.getRace().getName(); - defaultLeaderboard.addRace(trackedRace, columnName, /* medalRace */false, - defaultLeaderboard.getFleet(null)); + defaultLeaderboard.addRace(trackedRace, columnName, /* medalRace */false); } TrackedRaceReplicator trackedRaceReplicator = new TrackedRaceReplicator(trackedRace); trackedRaceReplicators.put(trackedRace, trackedRaceReplicator); diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/ImportMasterDataOperation.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/ImportMasterDataOperation.java index 0785f10195b..e6a99d64159 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/ImportMasterDataOperation.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/ImportMasterDataOperation.java @@ -199,6 +199,7 @@ public class ImportMasterDataOperation extends storeRaceLogEvents(leaderboard, toState.getMongoObjectFactory()); creationCount.addOneLeaderboard(leaderboard.getName()); relinkTrackedRacesIfPossible(toState, leaderboard); + toState.updateStoredLeaderboard(leaderboard); } } From 380d01cc0489f816bbf614a9bab3795177cf27a7 Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Mon, 21 Jul 2014 10:50:21 +0200 Subject: [PATCH 199/323] Removed unused groovy-eclipse dependency --- .../race-analysis-p2-ide-local.target | 28 ++++++++----------- .../race-analysis-p2-ide-p2admin.target | 4 --- .../race-analysis-p2-maven-central.target | 4 --- .../race-analysis-p2-maven-local.target | 4 --- 4 files changed, 12 insertions(+), 28 deletions(-) diff --git a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-ide-local.target b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-ide-local.target index 39c0a5e8f5c..1630dd4c936 100755 --- a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-ide-local.target +++ b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-ide-local.target @@ -1,5 +1,5 @@ - + @@ -36,21 +36,17 @@ - - - - - - - - - - + + + + + + + + + + - - - - - + \ No newline at end of file diff --git a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-ide-p2admin.target b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-ide-p2admin.target index 592949a761e..b4e1d160518 100644 --- a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-ide-p2admin.target +++ b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-ide-p2admin.target @@ -44,9 +44,5 @@ - - - - diff --git a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-maven-central.target b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-maven-central.target index b850942a967..23f669c680e 100644 --- a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-maven-central.target +++ b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-maven-central.target @@ -50,9 +50,5 @@ - - - - diff --git a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-maven-local.target b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-maven-local.target index b0c510595d8..112ebfed49d 100644 --- a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-maven-local.target +++ b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-maven-local.target @@ -50,9 +50,5 @@ - - - - From f993ff8fe5e1022a0d94a80322e1a2e65ca57a5d Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Mon, 21 Jul 2014 12:44:05 +0200 Subject: [PATCH 200/323] Added interface to mark race log events with so that add operation invalidates leaderboard cache --- .../domain/racelog/InvalidatesLeaderboardCache.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/InvalidatesLeaderboardCache.java diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/InvalidatesLeaderboardCache.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/InvalidatesLeaderboardCache.java new file mode 100644 index 00000000000..5282ce66d41 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/InvalidatesLeaderboardCache.java @@ -0,0 +1,11 @@ +package com.sap.sailing.domain.racelog; + +/** + * Marker interface that all classes implementing {@link RaceLogEvent} should + * also implement, if the addition of that event should invalidate leaderboard cache. + * + * @author Simon Marcel Pamies + */ +public interface InvalidatesLeaderboardCache { + +} From 7493d55261f9f7756e0251dd2eca114b0524ad9a Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Mon, 21 Jul 2014 12:44:35 +0200 Subject: [PATCH 201/323] Make sure to generate new UUID only for new events --- .../domain/racelog/state/impl/RaceStateImpl.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateImpl.java index a094c877137..113207f37f5 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateImpl.java @@ -1,5 +1,7 @@ package com.sap.sailing.domain.racelog.state.impl; +import java.util.UUID; + import com.sap.sailing.domain.base.CourseBase; import com.sap.sailing.domain.base.configuration.ConfigurationLoader; import com.sap.sailing.domain.base.configuration.RegattaConfiguration; @@ -166,7 +168,7 @@ public class RaceStateImpl extends ReadonlyRaceStateImpl implements RaceState { final AdditionalScoringInformationEvent event = new AdditionalScoringInformationFinder(raceLog).analyze(); if (enable) { if (event == null) { - raceLog.add(factory.createAdditionalScoringInformationEvent(timePoint, author, raceLog.getCurrentPassId())); + raceLog.add(factory.createAdditionalScoringInformationEvent(timePoint, UUID.randomUUID(), author, raceLog.getCurrentPassId())); } } else { if (event != null) { @@ -178,5 +180,11 @@ public class RaceStateImpl extends ReadonlyRaceStateImpl implements RaceState { } } } + + @Override + public boolean isAdditionalScoringInformationEnabled() { + final AdditionalScoringInformationEvent event = new AdditionalScoringInformationFinder(raceLog).analyze(); + return event != null; + } } From df1ccb9be1fc6b81d666dd18380bf83629df2213 Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Mon, 21 Jul 2014 12:45:00 +0200 Subject: [PATCH 202/323] Always using the UUID --- .../sailing/domain/racelog/impl/RaceLogEventFactoryImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventFactoryImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventFactoryImpl.java index 8ec4255d194..949678c072f 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventFactoryImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventFactoryImpl.java @@ -273,8 +273,8 @@ public class RaceLogEventFactoryImpl implements RaceLogEventFactory { } @Override - public AdditionalScoringInformationEvent createAdditionalScoringInformationEvent(TimePoint timePoint, + public AdditionalScoringInformationEvent createAdditionalScoringInformationEvent(TimePoint timePoint, Serializable id, RaceLogEventAuthor author, int currentPassId) { - return new AdditionalScoringInformationEventImpl(MillisecondsTimePoint.now(), author, timePoint, UUID.randomUUID(), Collections.emptyList(), currentPassId); + return new AdditionalScoringInformationEventImpl(MillisecondsTimePoint.now(), author, timePoint, id, Collections.emptyList(), currentPassId); } } From 807c0f8da5566767ee5f9d76bef5b985b6501cde Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 21 Jul 2014 12:45:49 +0200 Subject: [PATCH 203/323] while working down the queued replicated operations, catch and log exception per operation, then continue --- .../.settings/com.google.gwt.eclipse.core.prefs | 2 +- .../com/sap/sailing/server/replication/impl/Replicator.java | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) 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 cddb7488fa8..581bf904451 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,4 @@ eclipse.preferences.version=1 entryPointModules= filesCopiedToWebInfLib=gwt-servlet.jar -gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDMgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0UgLWNvbXBpbGVSZXBvcnQgLVhzb3ljRGV0YWlsZWRdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5BZG1pbkNvbnNvbGU8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuUmFjZUJvYXJkPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLkxlYWRlcmJvYXJkPC9lbnRyeS1wb2ludC1tb2R1bGU+PC9nd3QtY29tcGlsZS1zZXR0aW5ncz4\= +gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDMgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0UgLWNvbXBpbGVSZXBvcnQgLVhzb3ljRGV0YWlsZWRdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5BZG1pbkNvbnNvbGU8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuUmFjZUJvYXJkPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLkxlYWRlcmJvYXJkPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLlNwZWN0YXRvcjwvZW50cnktcG9pbnQtbW9kdWxlPjwvZ3d0LWNvbXBpbGUtc2V0dGluZ3M+ diff --git a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java index 42616d25c24..b99dad2f670 100755 --- a/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java +++ b/java/com.sap.sailing.server.replication/src/com/sap/sailing/server/replication/impl/Replicator.java @@ -266,7 +266,11 @@ public class Replicator implements Runnable { for (Iterator> i=queue.iterator(); i.hasNext(); ) { RacingEventServiceOperation operation = i.next(); i.remove(); - apply(operation); + try { + apply(operation); + } catch (Exception e) { + logger.log(Level.SEVERE, "Error applying queued, replicated operation "+operation+". Continuing with next queued operation.", e); + } } assert queue.isEmpty(); notifyAll(); From 4a92a1e8e07ba35d449db21c6e39b104119bf51a Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Mon, 21 Jul 2014 12:46:06 +0200 Subject: [PATCH 204/323] Fixed case where readObject would not initialize the revoked events correctly, also no longer stopping whenever the event the revoke event refers to could not be found --- .../domain/racelog/impl/RaceLogImpl.java | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogImpl.java index 04e3674d891..ef353127213 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogImpl.java @@ -153,7 +153,6 @@ public class RaceLogImpl extends TrackImpl implements RaceLog { RevokeEvent revokeEvent = (RevokeEvent) newEvent; try { checkIfSuccessfullyRevokes(revokeEvent); - lockForWrite(); revokedEventIds.add(revokeEvent.getRevokedEventId()); unlockAfterWrite(); @@ -169,15 +168,20 @@ public class RaceLogImpl extends TrackImpl implements RaceLog { unlockAfterRead(); if (revokedEvent == null) { - throw new NotRevokableException("RevokeEvent added, that refers to non-existent event to be revoked"); - } + // it can happen that the event that has been revoked is not yet loaded - as e assume + // that race log events never get removed we can safely continue and assume that + // the event will be loaded later + logger.warning("RevokeEvent for "+revokeEvent.getShortInfo()+" added, that refers to non-existent event to be revoked. Could also happen that the revoke event is before the event to be revoked. Check the times."); + } else { + if (! (revokedEvent instanceof Revokable)) { + throw new NotRevokableException("RevokeEvent trying to revoke non-revokable event"); + } - if (! (revokedEvent instanceof Revokable)) { - throw new NotRevokableException("RevokeEvent trying to revoke non-revokable event"); - } - - if (getInternalRawFixes().comparator().compare(revokeEvent, revokedEvent) <= 0) { - throw new NotRevokableException("RevokeEvent does not have sufficient priority"); + // make sure to compare only author priorities - assuming that revoke events are + // independent of passes and times + if (revokeEvent.getAuthor().compareTo(revokedEvent.getAuthor()) < 0) { + throw new NotRevokableException("RevokeEvent does not have sufficient priority"); + } } } @@ -301,7 +305,7 @@ public class RaceLogImpl extends TrackImpl implements RaceLog { try { for (RaceLogEvent event : getRawFixes()) { if (event instanceof RevokeEvent) { - revokedEventIds.add(event.getId()); + revokedEventIds.add(((RevokeEvent)event).getRevokedEventId()); } } } finally { From f42a834d495d004343a1f5af611be945e1b7224e Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Mon, 21 Jul 2014 12:46:33 +0200 Subject: [PATCH 205/323] Only searching for unrevoked events in finder --- .../analyzing/impl/AdditionalScoringInformationFinder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/AdditionalScoringInformationFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/AdditionalScoringInformationFinder.java index c2e2952652f..5e7cf9eecbb 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/AdditionalScoringInformationFinder.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/AdditionalScoringInformationFinder.java @@ -13,7 +13,7 @@ public class AdditionalScoringInformationFinder extends RaceLogAnalyzer Date: Mon, 21 Jul 2014 12:47:07 +0200 Subject: [PATCH 206/323] Alwayys requesting UUID for additional scoring information events --- .../src/com/sap/sailing/domain/racelog/RaceLogEventFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventFactory.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventFactory.java index e3c0fe4988a..7aa26159a64 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventFactory.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventFactory.java @@ -119,5 +119,5 @@ public interface RaceLogEventFactory { CloseOpenEndedDeviceMappingEvent createCloseOpenEndedDeviceMappingEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId, Serializable deviceMappingEventId, TimePoint closingTimePoint); - AdditionalScoringInformationEvent createAdditionalScoringInformationEvent(TimePoint timePoint, RaceLogEventAuthor author, int currentPassId); + AdditionalScoringInformationEvent createAdditionalScoringInformationEvent(TimePoint timePoint, Serializable id, RaceLogEventAuthor author, int currentPassId); } From 20f7ec22a5563c8ad12935fa252fe7228a7fd31e Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Mon, 21 Jul 2014 12:47:46 +0200 Subject: [PATCH 207/323] Adding method to get the current state of additional scoring information --- .../src/com/sap/sailing/domain/racelog/state/RaceState.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/RaceState.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/RaceState.java index f4ea5570a57..4f8601a09ce 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/RaceState.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/RaceState.java @@ -146,4 +146,6 @@ public interface RaceState extends ReadonlyRaceState { */ void setAdditionalScoringInformationEnabled(TimePoint timePoint, boolean enable); + boolean isAdditionalScoringInformationEnabled(); + } From 735f2c8e762bbc7f51cdf9288c1cc0f4832b5e45 Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Mon, 21 Jul 2014 12:48:20 +0200 Subject: [PATCH 208/323] Making additional scoring information invalidate leaderboard cache --- .../racelog/scoring/AdditionalScoringInformationEvent.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/AdditionalScoringInformationEvent.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/AdditionalScoringInformationEvent.java index b4719ee0e4d..946bcdee009 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/AdditionalScoringInformationEvent.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/AdditionalScoringInformationEvent.java @@ -1,5 +1,6 @@ package com.sap.sailing.domain.racelog.scoring; +import com.sap.sailing.domain.racelog.InvalidatesLeaderboardCache; import com.sap.sailing.domain.racelog.RaceLogEvent; import com.sap.sailing.domain.racelog.Revokable; @@ -10,6 +11,6 @@ import com.sap.sailing.domain.racelog.Revokable; * * @author Simon Marcel Pamies */ -public interface AdditionalScoringInformationEvent extends RaceLogEvent, Revokable { +public interface AdditionalScoringInformationEvent extends RaceLogEvent, Revokable, InvalidatesLeaderboardCache { } From 7246e1b950195ddcbca77ce0aa18c3a3e2f51a98 Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Mon, 21 Jul 2014 12:49:13 +0200 Subject: [PATCH 209/323] Fixed loading of reace columns for flexible leaderboard now only default fleet is allowed for flexible leaderboard --- .../impl/DomainObjectFactoryImpl.java | 38 +++++++------------ 1 file changed, 13 insertions(+), 25 deletions(-) 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 f33c4bc3869..0480f3e61f1 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 @@ -485,43 +485,31 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { result = new FlexibleLeaderboardImpl(raceLogStore, (String) dbLeaderboard.get(FieldNames.LEADERBOARD_NAME .name()), resultDiscardingRule, scoringScheme, courseArea); - // For a FlexibleLeaderboard, fleets are owned by the leaderboard's RaceColumn objects. We need to manage - // them here: - Map fleetsByName = new HashMap(); + // For a FlexibleLeaderboard, there should be only the default fleet for any race column for (Object dbRaceColumnAsObject : dbRaceColumns) { BasicDBObject dbRaceColumn = (BasicDBObject) dbRaceColumnAsObject; + String columnName = (String) dbRaceColumn.get(FieldNames.LEADERBOARD_COLUMN_NAME.name()); + + RaceColumn raceColumn = result.addRaceColumn(columnName, + (Boolean) dbRaceColumn.get(FieldNames.LEADERBOARD_IS_MEDAL_RACE_COLUMN.name())); + Map raceIdentifiers = loadRaceIdentifiers(dbRaceColumn); - RaceIdentifier defaultFleetRaceIdentifier = raceIdentifiers.get(null); + RaceIdentifier defaultFleetRaceIdentifier = raceIdentifiers.get(result.getFleet(null).getName()); + if (defaultFleetRaceIdentifier == null) { + // Backward compatibility + defaultFleetRaceIdentifier = raceIdentifiers.get(null); + } if (defaultFleetRaceIdentifier != null) { Fleet defaultFleet = result.getFleet(null); if (defaultFleet != null) { - raceIdentifiers.put(defaultFleet.getName(), defaultFleetRaceIdentifier); + + raceColumn.setRaceIdentifier(defaultFleet, defaultFleetRaceIdentifier); } else { // leaderboard has no default fleet; don't know what to do with default RaceIdentifier logger.warning("Discarding RaceIdentifier " + defaultFleetRaceIdentifier + " for default fleet for leaderboard " + result.getName() + " because no default fleet was found in leaderboard"); } - raceIdentifiers.remove(null); - } - List fleets = new ArrayList(); - for (String fleetName : raceIdentifiers.keySet()) { - Fleet fleet = fleetsByName.get(fleetName); - if (fleet == null) { - fleet = new FleetImpl(fleetName); - fleetsByName.put(fleetName, fleet); - } - fleets.add(fleet); - } - if (fleets.isEmpty()) { - fleets.add(result.getFleet(null)); - } - String columnName = (String) dbRaceColumn.get(FieldNames.LEADERBOARD_COLUMN_NAME.name()); - - RaceColumn raceColumn = result.addRaceColumn(columnName, - (Boolean) dbRaceColumn.get(FieldNames.LEADERBOARD_IS_MEDAL_RACE_COLUMN.name())); - for (Map.Entry e : raceIdentifiers.entrySet()) { - raceColumn.setRaceIdentifier(fleetsByName.get(e.getKey()), e.getValue()); } } From ffd6506d90744ff06c171beb195df85d4fbdaf4f Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Mon, 21 Jul 2014 12:49:16 +0200 Subject: [PATCH 210/323] All race log events that implement InvalidatesLeaderboardCache will do what they got promised --- .../impl/AbstractSimpleLeaderboardImpl.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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 18206de4caa..2ab607fbb0e 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 @@ -68,6 +68,7 @@ import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCache; import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache; import com.sap.sailing.domain.leaderboard.caching.LiveLeaderboardUpdater; +import com.sap.sailing.domain.racelog.InvalidatesLeaderboardCache; import com.sap.sailing.domain.racelog.RaceLogEvent; import com.sap.sailing.domain.racelog.RaceLogIdentifier; import com.sap.sailing.domain.tracking.GPSFix; @@ -83,8 +84,8 @@ import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRaceStatus; import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; import com.sap.sailing.domain.tracking.Wind; -import com.sap.sailing.domain.tracking.WindPositionMode; import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache; +import com.sap.sailing.domain.tracking.WindPositionMode; import com.sap.sailing.util.impl.LockUtil; import com.sap.sailing.util.impl.NamedReentrantReadWriteLock; import com.sap.sailing.util.impl.RaceColumnListeners; @@ -1215,6 +1216,13 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race @Override public void raceLogEventAdded(RaceColumn raceColumn, RaceLogIdentifier raceLogIdentifier, RaceLogEvent event) { getRaceColumnListeners().notifyListenersAboutRaceLogEventAdded(raceColumn, raceLogIdentifier, event); + if (event instanceof InvalidatesLeaderboardCache) { + // make sure to invalidate the cache as this event indicates that + // it changes values the cache could still hold + if (leaderboardDTOCache != null) { + leaderboardDTOCache.invalidate(this); + } + } } @Override From dcce82e4a0fd2469c84b240d0dee67b9f5b01e87 Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Mon, 21 Jul 2014 12:49:43 +0200 Subject: [PATCH 211/323] Updated test that checks if additional scoring information really has an impact --- .../test/LeaderboardScoringAndRankingTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) 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 3175ce7143f..a7b281e5e87 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 @@ -38,6 +38,7 @@ import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; +import com.sap.sailing.domain.common.racelog.tracking.NotRevokableException; import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.LeaderboardGroup; @@ -53,7 +54,9 @@ import com.sap.sailing.domain.leaderboard.impl.RegattaLeaderboardImpl; import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl; import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard; import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.analyzing.impl.AdditionalScoringInformationFinder; import com.sap.sailing.domain.racelog.impl.RaceLogEventAuthorImpl; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent; import com.sap.sailing.domain.racelog.scoring.impl.AdditionalScoringInformationEventImpl; import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks; import com.sap.sailing.domain.tracking.MarkPassing; @@ -821,6 +824,18 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { leaderboardHighPoint10Or8AndLastBreaksTie.getFleet(null)); assertEquals(new Double(28), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); assertEquals(new Double(3), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); + AdditionalScoringInformationFinder finder = new AdditionalScoringInformationFinder(raceLogForRace2); + AdditionalScoringInformationEvent event = finder.analyze(); + assert event != null; + try { + raceLogForRace2.revokeEvent(new RaceLogEventAuthorImpl("Plopp", 1), event); + } catch (NotRevokableException e) { + e.printStackTrace(); + } + event = finder.analyze(); + assert event == null; + assertEquals(new Double(30), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); + assertEquals(new Double(3), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); } @Test From 71fc6a3d0372c68d8c38153420d5ff745f04317d Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Mon, 21 Jul 2014 12:50:04 +0200 Subject: [PATCH 212/323] Showing UUID in information panels --- .../scoring/impl/AdditionalScoringInformationEventImpl.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/impl/AdditionalScoringInformationEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/impl/AdditionalScoringInformationEventImpl.java index 9088a1a8550..d2c04cd4500 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/impl/AdditionalScoringInformationEventImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/impl/AdditionalScoringInformationEventImpl.java @@ -22,5 +22,9 @@ public class AdditionalScoringInformationEventImpl extends RaceLogEventImpl impl public void accept(RaceLogEventVisitor visitor) { visitor.visit(this); } - + + @Override + public String getShortInfo() { + return "UUID: " + getId(); + } } From 63ab134cf66d178fd4ab5ca8dc3d58b08e5981be Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Mon, 21 Jul 2014 12:50:25 +0200 Subject: [PATCH 213/323] Showing UUID of revoked event in information panel --- .../domain/racelog/tracking/events/RevokeEventImpl.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/RevokeEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/RevokeEventImpl.java index 01c45e24e86..d38756f9afd 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/RevokeEventImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/tracking/events/RevokeEventImpl.java @@ -30,4 +30,9 @@ public class RevokeEventImpl extends RaceLogEventImpl implements RevokeEvent { public Serializable getRevokedEventId() { return revokedEventId; } + + @Override + public String getShortInfo() { + return super.getShortInfo() + "Revoked: " + revokedEventId; + } } From 49d66ee510c91c57b34f7c726fc943154037c33c Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Mon, 21 Jul 2014 12:50:54 +0200 Subject: [PATCH 214/323] Also using UUID during deserialize to avoid creating new ones --- .../RaceLogAdditionalScoringInformationEventDeserializer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogAdditionalScoringInformationEventDeserializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogAdditionalScoringInformationEventDeserializer.java index 2ec55367efd..5b82ad85e7a 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogAdditionalScoringInformationEventDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogAdditionalScoringInformationEventDeserializer.java @@ -20,6 +20,6 @@ public class RaceLogAdditionalScoringInformationEventDeserializer extends BaseRa @Override protected RaceLogEvent deserialize(JSONObject object, Serializable id, TimePoint createdAt, RaceLogEventAuthor author, TimePoint timePoint, int passId, List competitors) throws JsonDeserializationException { - return factory.createAdditionalScoringInformationEvent(timePoint, author, passId); + return factory.createAdditionalScoringInformationEvent(timePoint, id, author, passId); } } From c50c57dc11fd3e2db6f3b9b4f35fd83021d54be5 Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Mon, 21 Jul 2014 12:51:28 +0200 Subject: [PATCH 215/323] Load additional scoring information state translated to group racing in ESS startphase view --- .../raceinfo/startphase/ESSStartphaseRaceFragment.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/startphase/ESSStartphaseRaceFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/startphase/ESSStartphaseRaceFragment.java index 69d42f3247d..b1d9e5d66f9 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/startphase/ESSStartphaseRaceFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/startphase/ESSStartphaseRaceFragment.java @@ -43,14 +43,13 @@ public class ESSStartphaseRaceFragment extends BaseStartphaseRaceFragment Date: Mon, 21 Jul 2014 13:26:39 +0200 Subject: [PATCH 216/323] added logging for the registration of a TracTrac update handler, so we know for which races this is active --- .../sap/sailing/domain/tractracadapter/impl/UpdateHandler.java | 1 + 1 file changed, 1 insertion(+) diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/UpdateHandler.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/UpdateHandler.java index 293b58b2ed0..cb743fb75b2 100644 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/UpdateHandler.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/UpdateHandler.java @@ -57,6 +57,7 @@ public class UpdateHandler { this.raceId = raceId; this.updateDeserializer = new UpdateResponseDeserializer(); if (updateURI != null && !updateURI.toString().equals("")) { + logger.info("Activating TracTrac update handler "+this+" for race with ID "+raceId); this.active = true; } else { this.active = false; From ca6009883484b28b97cfd0bfd54090aa0af10d68 Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Mon, 21 Jul 2014 13:26:46 +0200 Subject: [PATCH 217/323] addRace no longer requires a fleet - removing parameter --- .../domain/test/LeaderboardScoringAndRankingTest.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) 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 028270f9721..a8caf87418f 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 @@ -805,23 +805,20 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000); FlexibleLeaderboard leaderboardHighPoint10Or8AndLastBreaksTie = new FlexibleLeaderboardImpl("Test ESS Highpoint 10Or8AndLastBreaksTie", new ThresholdBasedResultDiscardingRuleImpl(/* discarding thresholds */ new int[0]), new HighPointFirstGets10Or8AndLastBreaksTie(), null); - leaderboardHighPoint10Or8AndLastBreaksTie.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors)), "R1", /* medalRace */ false, - leaderboardHighPoint10Or8AndLastBreaksTie.getFleet(null)); + leaderboardHighPoint10Or8AndLastBreaksTie.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors)), "R1", /* medalRace */ false); assertTrue(leaderboardHighPoint10Or8AndLastBreaksTie.getScoringScheme().getScoreComparator(/* nullScoresAreBetter */ false).compare( leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[3], later)) < 0); // c0 better than c3 assertEquals(10, leaderboardHighPoint10Or8AndLastBreaksTie.getCompetitorsFromBestToWorst(later).size()); assertEquals(new Double(10), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); assertEquals(new Double(1), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); - leaderboardHighPoint10Or8AndLastBreaksTie.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors)), "R2", /* medalRace */ false, - leaderboardHighPoint10Or8AndLastBreaksTie.getFleet(null)); + leaderboardHighPoint10Or8AndLastBreaksTie.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors)), "R2", /* medalRace */ false); assertEquals(new Double(20), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); assertEquals(new Double(2), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); RaceLog raceLogForRace2 = leaderboardHighPoint10Or8AndLastBreaksTie.getRaceColumnByName("R2").getRaceLog(leaderboardHighPoint10Or8AndLastBreaksTie.getFleet(null)); raceLogForRace2.add(new AdditionalScoringInformationEventImpl(now, new RaceLogEventAuthorImpl("Plopp", 1), later, "12345678", Collections.emptyList(), 0)); assertEquals(new Double(18), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); assertEquals(new Double(2), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); - leaderboardHighPoint10Or8AndLastBreaksTie.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors)), "R3", /* medalRace */ false, - leaderboardHighPoint10Or8AndLastBreaksTie.getFleet(null)); + leaderboardHighPoint10Or8AndLastBreaksTie.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors)), "R3", /* medalRace */ false); assertEquals(new Double(28), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); assertEquals(new Double(3), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); AdditionalScoringInformationFinder finder = new AdditionalScoringInformationFinder(raceLogForRace2); From 2dad273c4fdd4a6e0477496b475bb0fff4fde0a9 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 21 Jul 2014 15:42:02 +0200 Subject: [PATCH 218/323] added scoring scheme HighPointWinnerGetsFiveIgnoringRaceCount as a variant that ignores the race count; geared towards Bundesliga scoring --- .../com/sap/sailing/domain/common/ScoringSchemeType.java | 3 ++- .../com/sap/sailing/domain/base/impl/DomainFactoryImpl.java | 3 +++ .../domain/leaderboard/impl/AbstractScoringSchemeImpl.java | 6 +++++- .../java/com/sap/sailing/gwt/ui/client/StringMessages.java | 3 ++- .../com/sap/sailing/gwt/ui/client/StringMessages.properties | 1 + .../sap/sailing/gwt/ui/client/StringMessages_de.properties | 1 + .../sap/sailing/gwt/ui/client/StringMessages_ru.properties | 1 + .../gwt/ui/leaderboard/ScoringSchemeTypeFormatter.java | 2 ++ 8 files changed, 17 insertions(+), 3 deletions(-) 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 0102c273dda..763ee9280e2 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 @@ -2,5 +2,6 @@ package com.sap.sailing.domain.common; 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_ONE, LOW_POINT_WINNER_GETS_ZERO, HIGH_POINT_WINNER_GETS_SIX, HIGH_POINT_WINNER_GETS_FIVE, + HIGH_POINT_WINNER_GETS_FIVE_IGNORING_RACE_COUNT } 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 d8e827a02ea..c49b68642a3 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 @@ -35,6 +35,7 @@ import com.sap.sailing.domain.leaderboard.impl.HighPointFirstGets10LastBreaksTie import com.sap.sailing.domain.leaderboard.impl.HighPointFirstGets1LastBreaksTie; 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.LowPoint; import com.sap.sailing.domain.leaderboard.impl.LowPointWinnerGetsZero; @@ -90,6 +91,8 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain return new LowPointWinnerGetsZero(); case HIGH_POINT_WINNER_GETS_FIVE: return new HighPointWinnerGetsFive(); + case HIGH_POINT_WINNER_GETS_FIVE_IGNORING_RACE_COUNT: + return new HighPointWinnerGetsFiveIgnoringRaceCount(); case HIGH_POINT_WINNER_GETS_SIX: return new HighPointWinnerGetsSix(); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractScoringSchemeImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractScoringSchemeImpl.java index ea9eb79de19..b08d2977d34 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractScoringSchemeImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractScoringSchemeImpl.java @@ -128,7 +128,6 @@ public abstract class AbstractScoringSchemeImpl implements ScoringScheme { */ @Override public int compareByBetterScore(List> o1Scores, List> o2Scores, boolean nullScoresAreBetter) { - assert o1Scores.size() == o2Scores.size(); final Comparator pureScoreComparator = getScoreComparator(nullScoresAreBetter); // needs to compare net points; therefore, divide the total points by the column factor for comparison: List o1NetScores = new ArrayList<>(); @@ -148,6 +147,11 @@ public abstract class AbstractScoringSchemeImpl implements ScoringScheme { while (result == 0 && o1Iter.hasNext() && o2Iter.hasNext()) { result = pureScoreComparator.compare(o1Iter.next(), o2Iter.next()); } + if (o1Iter.hasNext() != o2Iter.hasNext()) { + // if, as may be allowed by some scoring scheme variants, competitors with different numbers of scored races are compared + // and are equal for all races of the competitor who scored fewer races, the competitor who scored more races is preferred + result = o1Iter.hasNext() ? -1 : 1; + } return result; } 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 f5ab6b223d8..dc2526965bd 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 @@ -380,7 +380,8 @@ public interface StringMessages extends Messages { String scoringSchemeLowPointSystem(); String scoringSchemeHighPointSystem(); String scoringSchemeLowPointWinnerGetsZero(); - String scoringSchemeWinnerGetsFive(); + String scoringSchemeWinnerGetsFive(); + String scoringSchemeWinnerGetsFiveIgnoringRaceCount(); String scoringSchemeWinnerGetsSix(); String scoringSystem(); String createFlexibleLeaderboard(); 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 93a6a0dbc8f..c782331654d 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 @@ -408,6 +408,7 @@ scoringSchemeHighPointEssOverall=High Point System ESS Overall scoringSchemeHighPointLastBreaksTie=High Point System, Last Race Breaks Tie 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 scoringSystem=Scoring system createFlexibleLeaderboard=Create flexible leaderboard 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 c67dea86662..4bc982605ef 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 @@ -409,6 +409,7 @@ scoringSchemeHighPointEssOverall=High Point System ESS Gesamtwertung scoringSchemeHighPointLastBreaksTie=High Point System, letztes Rennen entscheidet bei Gleichstand 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 scoringSystem=Bepunktungssystem createFlexibleLeaderboard=Flexible Rangliste anlegen 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 944d3e320db..bcab85f9bf1 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 @@ -399,6 +399,7 @@ scoringSchemeHighPointEssOverall=Высокобальная система по scoringSchemeHighPointLastBreaksTie=Высокобальная система подсчета, тай-брейк на последней гонке scoringSchemeLowPointWinnerGetsZero=Низкобальная система подсчета очков, победитель получает ноль очков scoringSchemeWinnerGetsFive=Высокобальная система подсчета, победитель получает 5 очков +scoringSchemeWinnerGetsFiveIgnoringRaceCount=Высокобальная система подсчета, победитель получает 5 очков, Ignoring Race Count scoringSchemeWinnerGetsSix=Высокобальная система подсчета, победитель получает 6 очков scoringSystem=Система подсчета очков createFlexibleLeaderboard=Создание адаптивной таблицы лидеров 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 113e14d586e..e94239f3c86 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 @@ -22,6 +22,8 @@ public class ScoringSchemeTypeFormatter { return stringMessages.scoringSchemeLowPointWinnerGetsZero(); case HIGH_POINT_WINNER_GETS_FIVE: return stringMessages.scoringSchemeWinnerGetsFive(); + case HIGH_POINT_WINNER_GETS_FIVE_IGNORING_RACE_COUNT: + return stringMessages.scoringSchemeWinnerGetsFiveIgnoringRaceCount(); case HIGH_POINT_WINNER_GETS_SIX: return stringMessages.scoringSchemeWinnerGetsSix(); } From 68855813f4d0382ff1b78d70fc00776dda8ea7b6 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 21 Jul 2014 17:48:52 +0200 Subject: [PATCH 219/323] added missing scoring scheme class to git --- ...hPointWinnerGetsFiveIgnoringRaceCount.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100755 java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointWinnerGetsFiveIgnoringRaceCount.java diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointWinnerGetsFiveIgnoringRaceCount.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointWinnerGetsFiveIgnoringRaceCount.java new file mode 100755 index 00000000000..b9dd4b43f6b --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointWinnerGetsFiveIgnoringRaceCount.java @@ -0,0 +1,20 @@ +package com.sap.sailing.domain.leaderboard.impl; + +import com.sap.sailing.domain.common.ScoringSchemeType; + +public class HighPointWinnerGetsFiveIgnoringRaceCount extends HighPointWinnerGetsFive { + private static final long serialVersionUID = 3936338558024129174L; + + /** + * 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_FIVE_IGNORING_RACE_COUNT; + } +} From 603171a3712bda80b6bf6bbdfb988ef35b82db3f Mon Sep 17 00:00:00 2001 From: fmittag Date: Mon, 21 Jul 2014 18:00:01 +0200 Subject: [PATCH 220/323] fix for bug 2082 and some other stuff --- .../home/client/app/PlaceNavigatorImpl.java | 68 +++++++++++-------- .../place/start/SmartphoneStartView.java | 6 +- .../client/place/start/StartActivity.java | 25 ++++--- .../home/client/place/start/StartView.java | 6 +- .../start/TabletAndDesktopStartView.java | 6 +- .../client/shared/mainevents/MainEvents.java | 8 +-- .../client/shared/mainmedia/MainMedia.java | 6 +- .../shared/stage/LiveEventStageTeaser.java | 4 +- .../shared/stage/LiveStageTeaserBand.java | 6 +- .../shared/stage/NoActionStageTeaserBand.java | 4 +- .../shared/stage/PopularEventStageTeaser.java | 4 +- .../gwt/home/client/shared/stage/Stage.java | 8 +-- .../home/client/shared/stage/StageTeaser.java | 4 +- .../client/shared/stage/StageTeaserBand.java | 8 +-- .../stage/UpcomingEventStageTeaser.java | 4 +- .../gwt/ui/server/SailingServiceImpl.java | 9 +++ 16 files changed, 99 insertions(+), 77 deletions(-) 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 f506a658504..cba1737d259 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 @@ -16,6 +16,7 @@ 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"; protected PlaceNavigatorImpl(PlaceController placeController) { super(); @@ -24,66 +25,75 @@ public class PlaceNavigatorImpl implements PlaceNavigator { @Override public void goToHome() { - gotoPlace(new StartPlace(), new StartPlace.Tokenizer()); + gotoPlace(Window.Location.getHostName(), new StartPlace(), new StartPlace.Tokenizer()); } @Override public void goToEvent(String eventUuidAsString, String baseUrl) { EventPlace eventPlace = new EventPlace(eventUuidAsString, null); - if(isLocationOnLocalhost() || baseUrl.contains(SAPSAILING_DOMAIN)) { - placeController.goTo(eventPlace); - } else { - String remoteEventUrl = buildRemotePlaceUrl(baseUrl, eventPlace, new EventPlace.Tokenizer()); - Window.Location.replace(remoteEventUrl); - } + 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); +// } } @Override public void goToRegattaOfEvent(String eventUuidAsString, String leaderboardIdAsNameString, String baseUrl) { EventPlace eventPlace = new EventPlace(eventUuidAsString, leaderboardIdAsNameString); - if(isLocationOnLocalhost() || baseUrl.contains(SAPSAILING_DOMAIN)) { - placeController.goTo(eventPlace); - } else { - String remoteEventUrl = buildRemotePlaceUrl(baseUrl, eventPlace, new EventPlace.Tokenizer()); - Window.Location.replace(remoteEventUrl); - } + 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); +// } } @Override public void goToSearchResult(String searchQuery) { - gotoPlace(new SearchResultPlace(searchQuery), new SearchResultPlace.Tokenizer()); + gotoPlace(Window.Location.getHostName(), new SearchResultPlace(searchQuery), new SearchResultPlace.Tokenizer()); } @Override public void goToEvents() { - gotoPlace(new EventsPlace(), new EventsPlace.Tokenizer()); + gotoPlace(Window.Location.getHostName(), new EventsPlace(), new EventsPlace.Tokenizer()); } @Override public void goToAboutUs() { - gotoPlace(new AboutUsPlace(), new AboutUsPlace.Tokenizer()); + gotoPlace(new AboutUsPlace()); } @Override public void goToContact() { - gotoPlace(new ContactPlace(), new ContactPlace.Tokenizer()); + gotoPlace(new ContactPlace()); } @Override public void goToSolutions() { - gotoPlace(new SolutionsPlace(), new SolutionsPlace.Tokenizer()); + gotoPlace(new SolutionsPlace()); } @Override public void goToSponsoring() { - gotoPlace(new SponsoringPlace(), new SponsoringPlace.Tokenizer()); + gotoPlace(new SponsoringPlace()); } - private void gotoPlace(T destinationPlace, PlaceTokenizer tokenizer) { - if(isLocationOnLocalhost() || isLocationOnSapSailingCom()) { + private void gotoPlace(T destinationPlace) { + placeController.goTo(destinationPlace); + } + + private void gotoPlace(String baseUrl, T destinationPlace, PlaceTokenizer tokenizer) { + if(isLocationOnLocalhost(baseUrl) || isLocationOnSapSailingCom(baseUrl) || isLocationOnNewHomeTest(baseUrl)) { placeController.goTo(destinationPlace); } else { - String homeUrl = buildRemotePlaceUrl("http://" + SAPSAILING_DOMAIN, destinationPlace, tokenizer); + String homeUrl = buildRemotePlaceUrl(baseUrl, destinationPlace, tokenizer); Window.Location.replace(homeUrl); } } @@ -91,12 +101,16 @@ public class PlaceNavigatorImpl implements PlaceNavigator { private String buildRemotePlaceUrl(String baseUrl, T destinationPlace, PlaceTokenizer tokenizer) { return baseUrl + "/gwt/Home.html#" + destinationPlace.getClass().getSimpleName() + ":" + tokenizer.getToken(destinationPlace); } - - private boolean isLocationOnSapSailingCom() { - return Window.Location.getHostName().contains(SAPSAILING_DOMAIN); + + private boolean isLocationOnNewHomeTest(String urlToCheck) { + return urlToCheck.contains(NEW_HOME_TEST_DOMAIN); } - private boolean isLocationOnLocalhost() { - return Window.Location.getHostName().contains("localhost") || Window.Location.getHostName().contains("127.0.0.1"); + private boolean isLocationOnSapSailingCom(String urlToCheck) { + return urlToCheck.contains(SAPSAILING_DOMAIN); + } + + private boolean isLocationOnLocalhost(String urlToCheck) { + return urlToCheck.contains("localhost") || urlToCheck.contains("127.0.0.1"); } } 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 c19df307ee4..5cd975756a1 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 @@ -14,7 +14,7 @@ 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.EventDTO; +import com.sap.sailing.gwt.ui.shared.EventBaseDTO; import com.sap.sse.common.Util.Pair; public class SmartphoneStartView extends Composite implements StartView { @@ -38,12 +38,12 @@ public class SmartphoneStartView extends Composite implements StartView { } @Override - public void setFeaturedEvents(List> featuredEvents) { + public void setFeaturedEvents(List> featuredEvents) { stage.setFeaturedEvents(featuredEvents); } @Override - public void setRecentEvents(List recentEvents) { + public void setRecentEvents(List recentEvents) { mainEvents.setRecentEvents(recentEvents); mainMedia.setRecentEvents(recentEvents); } 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 912c438e8b3..1ae3e14afd2 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 @@ -9,7 +9,7 @@ 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.shared.stage.StageEventType; -import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sailing.gwt.ui.shared.EventBaseDTO; import com.sap.sse.common.Util.Pair; public class StartActivity extends AbstractActivity { @@ -24,9 +24,9 @@ public class StartActivity extends AbstractActivity { final StartView view = clientFactory.createStartView(); panel.setWidget(view.asWidget()); - clientFactory.getSailingService().getEvents(new AsyncCallback>() { + clientFactory.getSailingService().getPublicEventsOfAllSailingServers(new AsyncCallback>() { @Override - public void onSuccess(List result) { + public void onSuccess(List result) { fillStartPageEvents(view, result); } @@ -37,22 +37,21 @@ public class StartActivity extends AbstractActivity { } @SuppressWarnings("deprecation") - protected void fillStartPageEvents(StartView view, List events) { - List> featuredEvents = new ArrayList>(); + protected void fillStartPageEvents(StartView view, List events) { + List> featuredEvents = new ArrayList>(); - List recentEventsOfSameYear = new ArrayList(); - List upcomingSoonEvents = new ArrayList(); - List popularEvents = 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; final long FOUR_WEEK_IN_MS = 4L * (1000 * 60 * 60 * 24 * 7); - for(EventDTO event: events) { - // ignore events with no start and end date + 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)); + 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) { @@ -76,11 +75,11 @@ public class StartActivity extends AbstractActivity { view.setRecentEvents(recentEventsOfSameYear); } - 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++) { - resultList.add(new Pair(type, listToTakeElementsFrom.get(i))); + resultList.add(new Pair(type, listToTakeElementsFrom.get(i))); } } } 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 1a22a8e509c..89ef4422020 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 @@ -4,13 +4,13 @@ import java.util.List; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.home.client.shared.stage.StageEventType; -import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sailing.gwt.ui.shared.EventBaseDTO; import com.sap.sse.common.Util.Pair; public interface StartView { Widget asWidget(); - void setFeaturedEvents(List> featuredEvents); + void setFeaturedEvents(List> featuredEvents); - void setRecentEvents(List recentEvents); + void setRecentEvents(List recentEvents); } 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 bdfa661fb0a..d047a9001ba 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 @@ -13,7 +13,7 @@ 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.EventDTO; +import com.sap.sailing.gwt.ui.shared.EventBaseDTO; import com.sap.sse.common.Util.Pair; public class TabletAndDesktopStartView extends Composite implements StartView { @@ -38,12 +38,12 @@ public class TabletAndDesktopStartView extends Composite implements StartView { } @Override - public void setFeaturedEvents(List> featuredEvents) { + public void setFeaturedEvents(List> featuredEvents) { stage.setFeaturedEvents(featuredEvents); } @Override - public void setRecentEvents(List recentEvents) { + public void setRecentEvents(List recentEvents) { mainEvents.setRecentEvents(recentEvents); mainMedia.setRecentEvents(recentEvents); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainevents/MainEvents.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainevents/MainEvents.java index 1a0a34a2860..4a7891f777d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainevents/MainEvents.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainevents/MainEvents.java @@ -10,11 +10,11 @@ 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.shared.recentevent.RecentEvent; -import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sailing.gwt.ui.shared.EventBaseDTO; public class MainEvents extends Composite { - private List recentEvents; + private List recentEvents; @UiField(provided=true) RecentEvent event1; @UiField(provided=true) RecentEvent event2; @@ -32,10 +32,10 @@ public class MainEvents extends Composite { MainEventsResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); - recentEvents = new ArrayList(); + recentEvents = new ArrayList(); } - public void setRecentEvents(List theRecentEvents) { + public void setRecentEvents(List theRecentEvents) { recentEvents.clear(); recentEvents.addAll(theRecentEvents); 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 e3b0d0108d6..2e6a9a2ecec 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 @@ -18,7 +18,7 @@ import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.home.client.app.PlaceNavigator; import com.sap.sailing.gwt.idangerous.Swiper; import com.sap.sailing.gwt.ui.common.client.YoutubeApi; -import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sailing.gwt.ui.shared.EventBaseDTO; public class MainMedia extends Composite { @@ -43,11 +43,11 @@ public class MainMedia extends Composite { } - public void setRecentEvents(List recentEvents) { + public void setRecentEvents(List recentEvents) { List photoGalleryUrls = new ArrayList(); int videoCounter = 0; - for(EventDTO event: recentEvents) { + for(EventBaseDTO event: recentEvents) { photoGalleryUrls.addAll(event.getPhotoGalleryImageURLs()); if(event.getVideoURLs().size() > 0 && videoCounter < 3) { 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 9d089a486f6..f94a2bf5ee8 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 @@ -3,11 +3,11 @@ 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.EventDTO; +import com.sap.sailing.gwt.ui.shared.EventBaseDTO; public class LiveEventStageTeaser extends StageTeaser { - public LiveEventStageTeaser(EventDTO event, PlaceNavigator placeNavigator) { + public LiveEventStageTeaser(EventBaseDTO event, PlaceNavigator placeNavigator) { super(event); title.setInnerText(event.getName()); 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 2836b1226e6..4919469058b 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 @@ -2,11 +2,11 @@ package com.sap.sailing.gwt.home.client.shared.stage; 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.EventDTO; +import com.sap.sailing.gwt.ui.shared.EventBaseDTO; public class LiveStageTeaserBand extends StageTeaserBand { - public LiveStageTeaserBand(EventDTO event, PlaceNavigator placeNavigator) { + public LiveStageTeaserBand(EventBaseDTO event, PlaceNavigator placeNavigator) { super(event, placeNavigator); bandTitle.setInnerText(event.getName()); @@ -18,7 +18,7 @@ public class LiveStageTeaserBand extends StageTeaserBand { @Override public void actionLinkClicked() { - EventDTO event = getEvent(); + EventBaseDTO event = getEvent(); getPlaceNavigator().goToEvent(event.id.toString(), event.getBaseURL()); } } 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 d8f257fb907..d87c26b3d3e 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 @@ -2,11 +2,11 @@ package com.sap.sailing.gwt.home.client.shared.stage; 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.EventDTO; +import com.sap.sailing.gwt.ui.shared.EventBaseDTO; public class NoActionStageTeaserBand extends StageTeaserBand { - public NoActionStageTeaserBand(EventDTO event, PlaceNavigator placeNavigator) { + public NoActionStageTeaserBand(EventBaseDTO event, PlaceNavigator placeNavigator) { super(event, placeNavigator); bandTitle.setInnerText(event.getName()); 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 9295922a176..55079b54e77 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 @@ -3,11 +3,11 @@ 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.EventDTO; +import com.sap.sailing.gwt.ui.shared.EventBaseDTO; public class PopularEventStageTeaser extends StageTeaser { - public PopularEventStageTeaser(EventDTO event, PlaceNavigator placeNavigator) { + public PopularEventStageTeaser(EventBaseDTO event, PlaceNavigator placeNavigator) { super(event); title.setInnerText(event.getName()); 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 163df4a9232..d66875090af 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 @@ -14,13 +14,13 @@ 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.idangerous.Swiper; -import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sailing.gwt.ui.shared.EventBaseDTO; import com.sap.sse.common.Util.Pair; public class Stage extends Composite { @SuppressWarnings("unused") - private List> featuredEvents; + private List> featuredEvents; private List stageTeaserComposites; @@ -48,10 +48,10 @@ public class Stage extends Composite { stageTeaserComposites = new ArrayList(); } - public void setFeaturedEvents(List> featuredEvents) { + public void setFeaturedEvents(List> featuredEvents) { this.featuredEvents = featuredEvents; - for(Pair typeAndEvent: featuredEvents) { + for(Pair typeAndEvent: featuredEvents) { switch (typeAndEvent.getA()) { case POPULAR: stageTeaser = new PopularEventStageTeaser(typeAndEvent.getB(), placeNavigator); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.java index b588364fb79..d8b0d1ff6e7 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.java @@ -15,7 +15,7 @@ import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; import com.sap.sailing.gwt.ui.common.client.Countdown; import com.sap.sailing.gwt.ui.common.client.Countdown.CountdownListener; import com.sap.sailing.gwt.ui.common.client.Countdown.RemainingTime; -import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sailing.gwt.ui.shared.EventBaseDTO; public abstract class StageTeaser extends Composite { @UiField DivElement bandCount; @@ -38,7 +38,7 @@ public abstract class StageTeaser extends Composite { private static StageTeaserUiBinder uiBinder = GWT.create(StageTeaserUiBinder.class); - public StageTeaser(EventDTO event) { + public StageTeaser(EventBaseDTO event) { StageResources.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/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 0cacad7dd65..9ec38db2aa0 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 @@ -10,7 +10,7 @@ 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.EventDTO; +import com.sap.sailing.gwt.ui.shared.EventBaseDTO; public abstract class StageTeaserBand extends Composite { @@ -24,9 +24,9 @@ public abstract class StageTeaserBand extends Composite { @UiField Anchor actionLink; private final PlaceNavigator placeNavigator; - private final EventDTO event; + private final EventBaseDTO event; - public StageTeaserBand(EventDTO event, PlaceNavigator placeNavigator) { + public StageTeaserBand(EventBaseDTO event, PlaceNavigator placeNavigator) { this.event = event; this.placeNavigator = placeNavigator; @@ -40,7 +40,7 @@ public abstract class StageTeaserBand extends Composite { return placeNavigator; } - public EventDTO getEvent() { + public EventBaseDTO getEvent() { return event; } 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 1d2329351af..8d51c7f9de7 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 @@ -1,11 +1,11 @@ package com.sap.sailing.gwt.home.client.shared.stage; import com.sap.sailing.gwt.home.client.app.PlaceNavigator; -import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sailing.gwt.ui.shared.EventBaseDTO; public class UpcomingEventStageTeaser extends StageTeaser { - public UpcomingEventStageTeaser(EventDTO event, PlaceNavigator placeNavigator) { + public UpcomingEventStageTeaser(EventBaseDTO event, PlaceNavigator placeNavigator) { super(event); title.setInnerText(event.getName()); 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 c091a6fa4a7..4f62a47eb06 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 @@ -3180,6 +3180,15 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S eventDTO.setDescription(event.getDescription()); eventDTO.setOfficialWebsiteURL(event.getOfficialWebsiteURL() != null ? event.getOfficialWebsiteURL().toString() : null); eventDTO.setLogoImageURL(event.getLogoImageURL() != null ? event.getLogoImageURL().toString() : null); + for(URL url: event.getSponsorImageURLs()) { + eventDTO.addSponsorImageURL(url.toString()); + } + for(URL url: event.getImageURLs()) { + eventDTO.addImageURL(url.toString()); + } + for(URL url: event.getVideoURLs()) { + eventDTO.addVideoURL(url.toString()); + } } private EventDTO convertToEventDTO(Event event) { From a48f483de76d17285ece7906ab248b611a810b47 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 21 Jul 2014 23:11:05 +0200 Subject: [PATCH 221/323] using weak reference for the listener registration of a RaceState object with the RaceLog --- .../impl/AbstractRaceLogChangedVisitor.java | 128 ++++++++++++++++++ .../racelog/impl/RaceLogChangedVisitor.java | 124 +---------------- .../racelog/state/impl/RaceStateImpl.java | 2 +- .../state/impl/ReadonlyRaceStateImpl.java | 16 ++- .../impl/DynamicTrackedRaceLogListener.java | 2 +- .../gwt/ui/server/SailingServiceImpl.java | 1 - .../impl/EventRaceStatesJsonGetServlet.java | 4 - 7 files changed, 144 insertions(+), 133 deletions(-) create mode 100755 java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/AbstractRaceLogChangedVisitor.java diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/AbstractRaceLogChangedVisitor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/AbstractRaceLogChangedVisitor.java new file mode 100755 index 00000000000..138ac662ab8 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/AbstractRaceLogChangedVisitor.java @@ -0,0 +1,128 @@ +package com.sap.sailing.domain.racelog.impl; + +import com.sap.sailing.domain.racelog.RaceLogCourseAreaChangedEvent; +import com.sap.sailing.domain.racelog.RaceLogCourseDesignChangedEvent; +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.RaceLogEventVisitor; +import com.sap.sailing.domain.racelog.RaceLogFinishPositioningConfirmedEvent; +import com.sap.sailing.domain.racelog.RaceLogFinishPositioningListChangedEvent; +import com.sap.sailing.domain.racelog.RaceLogFlagEvent; +import com.sap.sailing.domain.racelog.RaceLogGateLineOpeningTimeEvent; +import com.sap.sailing.domain.racelog.RaceLogPassChangeEvent; +import com.sap.sailing.domain.racelog.RaceLogPathfinderEvent; +import com.sap.sailing.domain.racelog.RaceLogProtestStartTimeEvent; +import com.sap.sailing.domain.racelog.RaceLogRaceStatusEvent; +import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent; +import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; +import com.sap.sailing.domain.racelog.RaceLogWindFixEvent; +import com.sap.sailing.domain.racelog.RevokeEvent; +import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; +import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceCompetitorMappingEvent; +import com.sap.sailing.domain.racelog.tracking.DeviceMarkMappingEvent; +import com.sap.sailing.domain.racelog.tracking.RegisterCompetitorEvent; +import com.sap.sailing.domain.racelog.tracking.StartTrackingEvent; + +public abstract class AbstractRaceLogChangedVisitor implements RaceLogEventVisitor { + protected abstract void notifyListenerAboutEventAdded(RaceLogEvent event); + + public void visit(RaceLogFlagEvent event) { + notifyListenerAboutEventAdded(event); + } + + public void visit(RaceLogPassChangeEvent event) { + notifyListenerAboutEventAdded(event); + } + + public void visit(RaceLogRaceStatusEvent event) { + notifyListenerAboutEventAdded(event); + } + + public void visit(RaceLogStartTimeEvent event) { + notifyListenerAboutEventAdded(event); + } + + public void visit(RaceLogCourseAreaChangedEvent event) { + notifyListenerAboutEventAdded(event); + } + + public void visit(RaceLogCourseDesignChangedEvent event) { + notifyListenerAboutEventAdded(event); + } + + @Override + public void visit(RaceLogFinishPositioningListChangedEvent event) { + notifyListenerAboutEventAdded(event); + } + + @Override + public void visit(RaceLogFinishPositioningConfirmedEvent event) { + notifyListenerAboutEventAdded(event); + } + + @Override + public void visit(RaceLogPathfinderEvent event) { + notifyListenerAboutEventAdded(event); + } + + @Override + public void visit(RaceLogGateLineOpeningTimeEvent event) { + notifyListenerAboutEventAdded(event); + } + + @Override + public void visit(RaceLogStartProcedureChangedEvent event) { + notifyListenerAboutEventAdded(event); + } + + @Override + public void visit(RaceLogProtestStartTimeEvent event) { + notifyListenerAboutEventAdded(event); + } + + @Override + public void visit(RaceLogWindFixEvent event) { + notifyListenerAboutEventAdded(event); + } + + @Override + public void visit(DeviceCompetitorMappingEvent event) { + notifyListenerAboutEventAdded(event); + } + + @Override + public void visit(DeviceMarkMappingEvent event) { + notifyListenerAboutEventAdded(event); + } + + @Override + public void visit(DenoteForTrackingEvent event) { + notifyListenerAboutEventAdded(event); + } + + @Override + public void visit(StartTrackingEvent event) { + notifyListenerAboutEventAdded(event); + } + + @Override + public void visit(RevokeEvent event) { + notifyListenerAboutEventAdded(event); + } + + @Override + public void visit(RegisterCompetitorEvent event) { + notifyListenerAboutEventAdded(event); + } + + @Override + public void visit(DefineMarkEvent event) { + notifyListenerAboutEventAdded(event); + } + + @Override + public void visit(CloseOpenEndedDeviceMappingEvent event) { + notifyListenerAboutEventAdded(event); + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogChangedVisitor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogChangedVisitor.java index d3e57ea8001..4df2cd19f56 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogChangedVisitor.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogChangedVisitor.java @@ -1,133 +1,17 @@ package com.sap.sailing.domain.racelog.impl; import com.sap.sailing.domain.racelog.RaceLogChangedListener; -import com.sap.sailing.domain.racelog.RaceLogCourseAreaChangedEvent; -import com.sap.sailing.domain.racelog.RaceLogCourseDesignChangedEvent; -import com.sap.sailing.domain.racelog.RaceLogEventVisitor; -import com.sap.sailing.domain.racelog.RaceLogFinishPositioningConfirmedEvent; -import com.sap.sailing.domain.racelog.RaceLogFinishPositioningListChangedEvent; -import com.sap.sailing.domain.racelog.RaceLogFlagEvent; -import com.sap.sailing.domain.racelog.RaceLogGateLineOpeningTimeEvent; -import com.sap.sailing.domain.racelog.RaceLogPassChangeEvent; -import com.sap.sailing.domain.racelog.RaceLogPathfinderEvent; -import com.sap.sailing.domain.racelog.RaceLogProtestStartTimeEvent; -import com.sap.sailing.domain.racelog.RaceLogRaceStatusEvent; -import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent; -import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; -import com.sap.sailing.domain.racelog.RaceLogWindFixEvent; -import com.sap.sailing.domain.racelog.RevokeEvent; -import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; -import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; -import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; -import com.sap.sailing.domain.racelog.tracking.DeviceCompetitorMappingEvent; -import com.sap.sailing.domain.racelog.tracking.DeviceMarkMappingEvent; -import com.sap.sailing.domain.racelog.tracking.RegisterCompetitorEvent; -import com.sap.sailing.domain.racelog.tracking.StartTrackingEvent; +import com.sap.sailing.domain.racelog.RaceLogEvent; -public class RaceLogChangedVisitor implements RaceLogEventVisitor { - - private RaceLogChangedListener listener; +public class RaceLogChangedVisitor extends AbstractRaceLogChangedVisitor { + private final RaceLogChangedListener listener; public RaceLogChangedVisitor(RaceLogChangedListener listener) { this.listener = listener; } - public void visit(RaceLogFlagEvent event) { - listener.eventAdded(event); - } - - public void visit(RaceLogPassChangeEvent event) { - listener.eventAdded(event); - } - - public void visit(RaceLogRaceStatusEvent event) { - listener.eventAdded(event); - } - - public void visit(RaceLogStartTimeEvent event) { - listener.eventAdded(event); - } - - public void visit(RaceLogCourseAreaChangedEvent event) { - listener.eventAdded(event); - } - - public void visit(RaceLogCourseDesignChangedEvent event) { - listener.eventAdded(event); - } - @Override - public void visit(RaceLogFinishPositioningListChangedEvent event) { - listener.eventAdded(event); - } - - @Override - public void visit(RaceLogFinishPositioningConfirmedEvent event) { - listener.eventAdded(event); - } - - @Override - public void visit(RaceLogPathfinderEvent event) { - listener.eventAdded(event); - } - - @Override - public void visit(RaceLogGateLineOpeningTimeEvent event) { - listener.eventAdded(event); - } - - @Override - public void visit(RaceLogStartProcedureChangedEvent event) { - listener.eventAdded(event); - } - - @Override - public void visit(RaceLogProtestStartTimeEvent event) { - listener.eventAdded(event); - } - - @Override - public void visit(RaceLogWindFixEvent event) { - listener.eventAdded(event); - } - - @Override - public void visit(DeviceCompetitorMappingEvent event) { - listener.eventAdded(event); - } - - @Override - public void visit(DeviceMarkMappingEvent event) { - listener.eventAdded(event); - } - - @Override - public void visit(DenoteForTrackingEvent event) { - listener.eventAdded(event); - } - - @Override - public void visit(StartTrackingEvent event) { - listener.eventAdded(event); - } - - @Override - public void visit(RevokeEvent event) { - listener.eventAdded(event); - } - - @Override - public void visit(RegisterCompetitorEvent event) { - listener.eventAdded(event); - } - - @Override - public void visit(DefineMarkEvent event) { - listener.eventAdded(event); - } - - @Override - public void visit(CloseOpenEndedDeviceMappingEvent event) { + protected void notifyListenerAboutEventAdded(RaceLogEvent event) { listener.eventAdded(event); } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateImpl.java index dcd929c3e60..1cd3a7a1cd8 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateImpl.java @@ -51,7 +51,7 @@ public class RaceStateImpl extends ReadonlyRaceStateImpl implements RaceState { private RaceStateImpl(RaceLog raceLog, RaceLogEventAuthor author, RaceLogEventFactory eventFactory, RaceStatusAnalyzer.Clock analyzersClock, RacingProcedureFactory procedureFactory) { - super(raceLog, analyzersClock, procedureFactory); + super(raceLog, analyzersClock, procedureFactory, /* update */ true); this.author = author; this.factory = eventFactory; } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/ReadonlyRaceStateImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/ReadonlyRaceStateImpl.java index 75133ffeb0b..8162e34d111 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/ReadonlyRaceStateImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/ReadonlyRaceStateImpl.java @@ -22,7 +22,7 @@ import com.sap.sailing.domain.racelog.analyzing.impl.RaceStatusAnalyzer; import com.sap.sailing.domain.racelog.analyzing.impl.RaceStatusAnalyzer.Clock; import com.sap.sailing.domain.racelog.analyzing.impl.RacingProcedureTypeAnalyzer; import com.sap.sailing.domain.racelog.analyzing.impl.StartTimeFinder; -import com.sap.sailing.domain.racelog.impl.RaceLogChangedVisitor; +import com.sap.sailing.domain.racelog.impl.WeakRaceLogChangedVisitor; import com.sap.sailing.domain.racelog.state.RaceStateChangedListener; import com.sap.sailing.domain.racelog.state.RaceStateEvent; import com.sap.sailing.domain.racelog.state.RaceStateEventScheduler; @@ -105,10 +105,15 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL private Wind cachedWindFix; private ReadonlyRaceStateImpl(RaceLog raceLog, RacingProcedureFactory procedureFactory) { - this(raceLog, new RaceStatusAnalyzer.StandardClock(), procedureFactory); + this(raceLog, new RaceStatusAnalyzer.StandardClock(), procedureFactory, /* update */ true); } - protected ReadonlyRaceStateImpl(RaceLog raceLog, Clock analyzersClock, RacingProcedureFactory procedureFactory) { + /** + * @param update + * if true, the race state will be updated whenever the underlying raceLog + * changes. + */ + protected ReadonlyRaceStateImpl(RaceLog raceLog, Clock analyzersClock, RacingProcedureFactory procedureFactory, boolean update) { this.raceLog = raceLog; this.procedureFactory = procedureFactory; this.changedListeners = new RaceStateChangedListeners(); @@ -128,9 +133,8 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL this.cachedRacingProcedureType = determineInitialProcedureType(); this.cachedRaceStatus = RaceLogRaceStatus.UNKNOWN; this.cachedPassId = raceLog.getCurrentPassId(); - - this.raceLog.addListener(new RaceLogChangedVisitor(this)); - + // TODO bug 2083: make sure the listener registration is "weak" in the sense that it is removed when this race state is no longer strongly referenced + this.raceLog.addListener(new WeakRaceLogChangedVisitor(this.raceLog, this)); // We known that recreateRacingProcedure calls update() when done, therefore this RaceState // will be fully initialized after this line recreateRacingProcedure(); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java index 70d80ea99c8..f11e21ff146 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java @@ -115,7 +115,7 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor { } private void analyzeStartTime(TimePoint startTimeProvidedByEvent) { - /* start time will be set by StartTimeAnalyzer in TrackedRace.getStartTime() */ + /* start time will be set by StartTimeFinder in TrackedRace.getStartTime() */ trackedRace.invalidateStartTime(); TimePoint startTime = startTimeFinder.analyze(); 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 89ed512f993..6febe37bb5a 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 @@ -912,7 +912,6 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S raceInfoDTO.fleetOrdering = fleet.getOrdering(); raceInfoDTO.raceIdentifier = raceColumn.getRaceIdentifier(fleet); raceInfoDTO.isTracked = raceColumn.getTrackedRace(fleet) != null ? true : false; - return raceInfoDTO; } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/EventRaceStatesJsonGetServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/EventRaceStatesJsonGetServlet.java index a637b1715d5..fdfa9c792c2 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/EventRaceStatesJsonGetServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/EventRaceStatesJsonGetServlet.java @@ -131,24 +131,20 @@ public class EventRaceStatesJsonGetServlet extends AbstractJsonHttpServlet { result.put("trackedRaceId", raceIdentifier != null ? raceIdentifier.toString() : null); RaceLog raceLog = raceColumn.getRaceLog(fleet); if (raceLog != null && !raceLog.isEmpty()) { - ReadonlyRaceState state = ReadonlyRaceStateImpl.create(raceLog); RaceLogRaceStatus status = state.getStatus(); TimePoint startTime = state.getStartTime(); TimePoint finishedTime = state.getFinishedTime(); - JSONObject raceLogStateJson = new JSONObject(); result.put("raceState", raceLogStateJson); raceLogStateJson.put("startTime", startTime != null ? startTime.toString() : null); raceLogStateJson.put("endTime", finishedTime != null ? finishedTime.toString() : null); raceLogStateJson.put("lastStatus", status.name()); - ReadonlyGateStartRacingProcedure procedure = state.getTypedReadonlyRacingProcedure(ReadonlyGateStartRacingProcedure.class); if (procedure != null) { raceLogStateJson.put("pathfinderId", procedure.getPathfinder()); raceLogStateJson.put("gateLineOpeningTime", procedure.getGateLaunchStopTime()); } - AbortingFlagFinder abortingFlagFinder = new AbortingFlagFinder(raceLog); RaceLogFlagEvent abortingFlagEvent = abortingFlagFinder.analyze(); LastFlagsFinder lastFlagFinder = new LastFlagsFinder(raceLog); From e2aaa40d4c6d079a5d955af3f106c958a3796c61 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 21 Jul 2014 23:53:39 +0200 Subject: [PATCH 222/323] use weak reference queue and cleaner thread --- .../impl/WeakRaceLogChangedVisitor.java | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100755 java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/WeakRaceLogChangedVisitor.java diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/WeakRaceLogChangedVisitor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/WeakRaceLogChangedVisitor.java new file mode 100755 index 00000000000..873ef08fadb --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/WeakRaceLogChangedVisitor.java @@ -0,0 +1,65 @@ +package com.sap.sailing.domain.racelog.impl; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogChangedListener; +import com.sap.sailing.domain.racelog.RaceLogEvent; + +/** + * A listener that unregisters itself from the race log's listeners if its notification target is no longer strongly + * referenced. + * + * @author Axel Uhl (d043530) + * + */ +public class WeakRaceLogChangedVisitor extends AbstractRaceLogChangedVisitor { + private static final Logger logger = Logger.getLogger(WeakRaceLogChangedVisitor.class.getName()); + private static ReferenceQueue queue = new ReferenceQueue(); + private static Map, WeakRaceLogChangedVisitor> referenceToVisitor = new HashMap, WeakRaceLogChangedVisitor>(); + static { + new Thread(WeakRaceLogChangedVisitor.class.getSimpleName() + " weak reference cleaner") { + @Override + public void run() { + while (true) { + try { + Reference ref = queue.remove(); + WeakRaceLogChangedVisitor visitor = referenceToVisitor.get(ref); + visitor.removeListener(); + } catch (InterruptedException e) { + logger.log(Level.SEVERE, "Error trying to clean weak reference", e); + } + } + } + }.start(); + } + + private final WeakReference listenerRef; + private final RaceLog removeFromThisRaceLogWhenListenerNoLongerStronglyReferenced; + + public WeakRaceLogChangedVisitor(RaceLog removeFromThisRaceLogWhenListenerNoLongerStronglyReferenced, RaceLogChangedListener listener) { + this.removeFromThisRaceLogWhenListenerNoLongerStronglyReferenced = removeFromThisRaceLogWhenListenerNoLongerStronglyReferenced; + listenerRef = new WeakReference(listener, queue); + } + + @Override + protected void notifyListenerAboutEventAdded(RaceLogEvent event) { + RaceLogChangedListener listener = listenerRef.get(); + if (listener == null) { + removeListener(); + } else { + listener.eventAdded(event); + } + } + + private void removeListener() { + removeFromThisRaceLogWhenListenerNoLongerStronglyReferenced.removeListener(this); + } + +} From b83e00405787ca34cd56732e5fa08ed542ed918d Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 21 Jul 2014 23:54:32 +0200 Subject: [PATCH 223/323] disable directory listing for /gwt context root --- java/com.sap.sailing.gwt.ui/WEB-INF/web.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/java/com.sap.sailing.gwt.ui/WEB-INF/web.xml b/java/com.sap.sailing.gwt.ui/WEB-INF/web.xml index 1da7d1d7306..0545f92fbeb 100755 --- a/java/com.sap.sailing.gwt.ui/WEB-INF/web.xml +++ b/java/com.sap.sailing.gwt.ui/WEB-INF/web.xml @@ -4,6 +4,17 @@ xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> + + + default + org.eclipse.jetty.servlet.DefaultServlet + + + dirAllowed + false + + + GWTCacheControlServletFilter com.sap.sse.gwt.server.GWTCacheControlServletFilter From 4707d527ab593c3da43c5662c5508273be222dd9 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 21 Jul 2014 23:58:37 +0200 Subject: [PATCH 224/323] disable directory listing for further web bundles --- .../WEB-INF/web.xml | 11 +++++++++++ .../WEB-INF/web.xml | 11 +++++++++++ .../WEB-INF/web.xml | 11 +++++++++++ java/com.sap.sailing.server.gateway/WEB-INF/web.xml | 11 +++++++++++ .../WEB-INF/web.xml | 11 +++++++++++ java/com.sap.sailing.www.events/WEB-INF/web.xml | 11 +++++++++++ java/com.sap.sailing.xmlexport/WEB-INF/web.xml | 12 ++++++++++++ 7 files changed, 78 insertions(+) diff --git a/java/com.sap.sailing.domain.igtimiadapter/WEB-INF/web.xml b/java/com.sap.sailing.domain.igtimiadapter/WEB-INF/web.xml index 0d5553d15f6..3ac1df87e4d 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/WEB-INF/web.xml +++ b/java/com.sap.sailing.domain.igtimiadapter/WEB-INF/web.xml @@ -5,6 +5,17 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> + + + default + org.eclipse.jetty.servlet.DefaultServlet + + + dirAllowed + false + + + Igtimi Oauth Callback REST API com.sun.jersey.spi.container.servlet.ServletContainer diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/WEB-INF/web.xml b/java/com.sap.sailing.domain.racelogtrackingadapter/WEB-INF/web.xml index 7f11b027765..c309ea109ab 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/WEB-INF/web.xml +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/WEB-INF/web.xml @@ -5,6 +5,17 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> + + + default + org.eclipse.jetty.servlet.DefaultServlet + + + dirAllowed + false + + + RecordFixesPostServlet com.sap.sailing.domain.racelogtracking.servlet.RecordFixesPostServlet diff --git a/java/com.sap.sailing.server.gateway.ess40/WEB-INF/web.xml b/java/com.sap.sailing.server.gateway.ess40/WEB-INF/web.xml index a97f97e999b..12863624767 100644 --- a/java/com.sap.sailing.server.gateway.ess40/WEB-INF/web.xml +++ b/java/com.sap.sailing.server.gateway.ess40/WEB-INF/web.xml @@ -5,6 +5,17 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> + + + default + org.eclipse.jetty.servlet.DefaultServlet + + + dirAllowed + false + + + ESS40ResultsAsCSVServlet com.sap.sailing.server.gateway.ess40.ESS40ResultsAsCSVServlet 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 6e6da9d8096..a3f4166834a 100644 --- a/java/com.sap.sailing.server.gateway/WEB-INF/web.xml +++ b/java/com.sap.sailing.server.gateway/WEB-INF/web.xml @@ -5,7 +5,18 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> + + default + org.eclipse.jetty.servlet.DefaultServlet + + + dirAllowed + false + + + + AdminApp com.sap.sailing.server.gateway.impl.AdminApp diff --git a/java/com.sap.sailing.server.replication/WEB-INF/web.xml b/java/com.sap.sailing.server.replication/WEB-INF/web.xml index cae166567be..478b9b77aab 100755 --- a/java/com.sap.sailing.server.replication/WEB-INF/web.xml +++ b/java/com.sap.sailing.server.replication/WEB-INF/web.xml @@ -5,6 +5,17 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> + + + default + org.eclipse.jetty.servlet.DefaultServlet + + + dirAllowed + false + + + Replication com.sap.sailing.server.replication.impl.ReplicationServlet diff --git a/java/com.sap.sailing.www.events/WEB-INF/web.xml b/java/com.sap.sailing.www.events/WEB-INF/web.xml index 75a1353b390..7beb149ff39 100644 --- a/java/com.sap.sailing.www.events/WEB-INF/web.xml +++ b/java/com.sap.sailing.www.events/WEB-INF/web.xml @@ -4,6 +4,17 @@ xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> + + + default + org.eclipse.jetty.servlet.DefaultServlet + + + dirAllowed + false + + + WinnerCircle2012Servlet com.sap.sailing.www.events.WinnerCircle2012Servlet diff --git a/java/com.sap.sailing.xmlexport/WEB-INF/web.xml b/java/com.sap.sailing.xmlexport/WEB-INF/web.xml index 9a42038e172..791d8ae4a56 100644 --- a/java/com.sap.sailing.xmlexport/WEB-INF/web.xml +++ b/java/com.sap.sailing.xmlexport/WEB-INF/web.xml @@ -3,6 +3,18 @@ xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> + + + + default + org.eclipse.jetty.servlet.DefaultServlet + + + dirAllowed + false + + + XmlExportServlet com.sap.sailing.xmlexport.XmlExportServlet From 80e0a17199ba0df56f8614e7528d46f5065d0a43 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 22 Jul 2014 00:10:39 +0200 Subject: [PATCH 225/323] made reference cleaner for RaceState listeners reliable --- .../sailing/domain/racelog/impl/WeakRaceLogChangedVisitor.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/WeakRaceLogChangedVisitor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/WeakRaceLogChangedVisitor.java index 873ef08fadb..7a70649e209 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/WeakRaceLogChangedVisitor.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/WeakRaceLogChangedVisitor.java @@ -34,6 +34,8 @@ public class WeakRaceLogChangedVisitor extends AbstractRaceLogChangedVisitor { visitor.removeListener(); } catch (InterruptedException e) { logger.log(Level.SEVERE, "Error trying to clean weak reference", e); + } catch (Exception e2) { + logger.severe("Shouldn't have gotten here. Continuing..."); } } } @@ -46,6 +48,7 @@ public class WeakRaceLogChangedVisitor extends AbstractRaceLogChangedVisitor { public WeakRaceLogChangedVisitor(RaceLog removeFromThisRaceLogWhenListenerNoLongerStronglyReferenced, RaceLogChangedListener listener) { this.removeFromThisRaceLogWhenListenerNoLongerStronglyReferenced = removeFromThisRaceLogWhenListenerNoLongerStronglyReferenced; listenerRef = new WeakReference(listener, queue); + referenceToVisitor.put(listenerRef, this); } @Override From f69f50d4badd91aafd80edbac5222830beb515d9 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 22 Jul 2014 00:26:35 +0200 Subject: [PATCH 226/323] cancel timer of ShortTimeWindCache and not only the task, reducing thread count considerably --- .../com/sap/sailing/domain/tracking/impl/ShortTimeWindCache.java | 1 + 1 file changed, 1 insertion(+) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/ShortTimeWindCache.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/ShortTimeWindCache.java index 038468bdf33..294cbe71078 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/ShortTimeWindCache.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/ShortTimeWindCache.java @@ -51,6 +51,7 @@ public class ShortTimeWindCache { synchronized (order) { if (order.isEmpty()) { cancel(); + timer.cancel(); timer = null; } } From 34d668dd9872c1d782306abd46c52239982f4996 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 22 Jul 2014 08:23:55 +0200 Subject: [PATCH 227/323] fixed course display, not showing "to gate" but "to port" etc. --- .../java/com/sap/sailing/gwt/ui/client/StringMessages.java | 1 + .../com/sap/sailing/gwt/ui/client/StringMessages.properties | 1 + .../sap/sailing/gwt/ui/client/StringMessages_de.properties | 3 ++- .../sap/sailing/gwt/ui/client/StringMessages_ru.properties | 1 + .../gwt/ui/regattaoverview/RegattaRaceStatesComponent.java | 6 +++--- 5 files changed, 8 insertions(+), 4 deletions(-) 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 dc2526965bd..179971b53d2 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 @@ -126,6 +126,7 @@ public interface StringMessages extends Messages { String errorWhileTryingToSetWindSourceForRace(); String inEvent(); String to(); + String toSide(); String showEstimatedWind(); String timing(); String raceDetailsToShow(); 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 c782331654d..1a66d45dea9 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 @@ -135,6 +135,7 @@ errorFetchingWindInformationForRace=Error fetching wind information for race errorWhileTryingToSetWindSourceForRace=Error while trying to set "wind starts upwind" flag inEvent=in event to=to +toSide=to showEstimatedWind=Show estimated wind timing=Timing raceDetailsToShow=Race Details 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 4bc982605ef..bf6bb40cd19 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 @@ -135,7 +135,8 @@ errorSettingWindForRace=Error setting wind for race errorFetchingWindInformationForRace=Error fetching wind information for race errorWhileTryingToSetWindSourceForRace=Error while trying to set "wind starts upwind" flag inEvent=in event -to=to +to=nach +toSide=an showEstimatedWind=Zeige den geschätzten Wind timing=Timing raceDetailsToShow=Renndetails 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 bcab85f9bf1..97da7df0dd2 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 @@ -136,6 +136,7 @@ errorFetchingWindInformationForRace=Ошибка при получении да errorWhileTryingToSetWindSourceForRace=Ошибка при попытке установить флажок «начинается встречный ветер» inEvent=В случае to=к +toSide=to showEstimatedWind=Показать оценочный ветер timing=Хронометраж raceDetailsToShow=Подробные сведения о гонке 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 3e084623587..00dafbafea0 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 @@ -752,16 +752,16 @@ public class RegattaRaceStatesComponent extends SimplePanel implements Component private String getWaypointNameLabel(WaypointDTO waypointDTO) { String result = waypointDTO.getName(); - result += (waypointDTO.passingInstructions == null) ? "" : ", to " + getPassingInstructionsAsText(waypointDTO.passingInstructions); + result += (waypointDTO.passingInstructions == null) ? "" : "," + getPassingInstructionsAsText(waypointDTO.passingInstructions); return result; } private String getPassingInstructionsAsText(PassingInstruction passingInstructions) { switch (passingInstructions) { case Port: - return stringMessages.portSide(); + return stringMessages.toSide() + " " + stringMessages.portSide(); case Starboard: - return stringMessages.starboardSide(); + return stringMessages.toSide() + " " + stringMessages.starboardSide(); case Gate: return stringMessages.gate(); case Line: From af401d0a2c5634a4d13729e597c8c44205fb8935 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 22 Jul 2014 09:02:31 +0200 Subject: [PATCH 228/323] locking course for read before serializing a TrackedRaceImpl; see also bug 2081 --- .../domain/tracking/impl/TrackedRaceImpl.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) 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 549a4e1d88c..c7372728b4c 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 @@ -445,11 +445,18 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl * Object serialization obtains a read lock for the course so that in cannot change while serializing this object. */ private void writeObject(ObjectOutputStream s) throws IOException { - LockUtil.lockForWrite(getSerializationLock()); + // obtain the course's read lock because a course change during serialization could lead to + // trackedLegs being inconsistent with getRace().getCourse().getLegs() + getRace().getCourse().lockForRead(); try { - s.defaultWriteObject(); + LockUtil.lockForWrite(getSerializationLock()); + try { + s.defaultWriteObject(); + } finally { + LockUtil.unlockAfterWrite(getSerializationLock()); + } } finally { - LockUtil.unlockAfterWrite(getSerializationLock()); + getRace().getCourse().unlockAfterRead(); } } @@ -1588,6 +1595,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl @Override public void waypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) { + // expecting to hold the course's write lock invalidateMarkPassingTimes(); LockUtil.lockForRead(getSerializationLock()); try { From 9a7182df3875a7f81434e36eaa98369d029e3966 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 22 Jul 2014 10:49:59 +0200 Subject: [PATCH 229/323] more logging regarding start time forwarding --- .../sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java index caeec4852b6..e8670fd8143 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java @@ -902,6 +902,7 @@ DynamicTrackedRace, GPSTrackListener { @Override public void onStartTimeChangedByRaceCommittee(TimePoint newStartTime) { + logger.info("Start time of race "+getRace().getName()+" updated by race committee to "+newStartTime); try { for (StartTimeChangedListener startTimeChangedListener : startTimeChangedListeners) { startTimeChangedListener.startTimeChanged(newStartTime); From b3b6ffed0bb8daa10af8d49bcf366faf40a6c039 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 22 Jul 2014 19:23:23 +0200 Subject: [PATCH 230/323] fixed typo in comment --- .../src/com/sap/sailing/domain/racelog/impl/RaceLogImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogImpl.java index ef353127213..9e4b6d1f07c 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogImpl.java @@ -168,7 +168,7 @@ public class RaceLogImpl extends TrackImpl implements RaceLog { unlockAfterRead(); if (revokedEvent == null) { - // it can happen that the event that has been revoked is not yet loaded - as e assume + // it can happen that the event that has been revoked is not yet loaded - as we assume // that race log events never get removed we can safely continue and assume that // the event will be loaded later logger.warning("RevokeEvent for "+revokeEvent.getShortInfo()+" added, that refers to non-existent event to be revoked. Could also happen that the revoke event is before the event to be revoked. Check the times."); From e86128b6af2156a03da58f419998707c261e2c0d Mon Sep 17 00:00:00 2001 From: fmittag Date: Tue, 22 Jul 2014 20:33:02 +0200 Subject: [PATCH 231/323] added support for videoUrls from featured events --- .../start/TabletAndDesktopStartView.java | 4 +- .../client/shared/mainmedia/MainMedia.java | 54 ++++++++++++++----- 2 files changed, 45 insertions(+), 13 deletions(-) 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 d047a9001ba..545bc31f00a 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 @@ -27,7 +27,7 @@ public class TabletAndDesktopStartView extends Composite implements StartView { @UiField(provided=true) MainEvents mainEvents; @UiField(provided=true) MainMedia mainMedia; @UiField SocialFooter socialFooter; - + public TabletAndDesktopStartView(PlaceNavigator navigator) { stage = new Stage(navigator); // mainSponsors = new MainSponsors(navigator); @@ -40,6 +40,7 @@ public class TabletAndDesktopStartView extends Composite implements StartView { @Override public void setFeaturedEvents(List> featuredEvents) { stage.setFeaturedEvents(featuredEvents); + mainMedia.setFeaturedEvents(featuredEvents); } @Override @@ -47,4 +48,5 @@ public class TabletAndDesktopStartView extends Composite implements StartView { mainEvents.setRecentEvents(recentEvents); mainMedia.setRecentEvents(recentEvents); } + } 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 2e6a9a2ecec..d534564a6d6 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 @@ -16,9 +16,11 @@ import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.home.client.app.PlaceNavigator; +import com.sap.sailing.gwt.home.client.shared.stage.StageEventType; import com.sap.sailing.gwt.idangerous.Swiper; import com.sap.sailing.gwt.ui.common.client.YoutubeApi; import com.sap.sailing.gwt.ui.shared.EventBaseDTO; +import com.sap.sse.common.Util.Pair; public class MainMedia extends Composite { @@ -30,6 +32,7 @@ public class MainMedia extends Composite { @UiField HTMLPanel mediaSlides; private Swiper swiper; + private int videoCounter; interface MainMediaUiBinder extends UiBinder { } @@ -37,28 +40,28 @@ public class MainMedia extends Composite { private static MainMediaUiBinder uiBinder = GWT.create(MainMediaUiBinder.class); public MainMedia(PlaceNavigator navigator) { + videoCounter = 0; MainMediaResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); - + } + + public void setFeaturedEvents(List> featuredEvents) { + for(Pair featuredEventTypeAndEvent: featuredEvents) { + if(featuredEventTypeAndEvent.getB().getVideoURLs().size() > 0 && videoCounter < 3) { + addVideoToVideoPanel(featuredEventTypeAndEvent.getB()); + } + } } public void setRecentEvents(List recentEvents) { List photoGalleryUrls = new ArrayList(); - int videoCounter = 0; for(EventBaseDTO event: recentEvents) { photoGalleryUrls.addAll(event.getPhotoGalleryImageURLs()); - + if(event.getVideoURLs().size() > 0 && videoCounter < 3) { - String eventName = event.getName(); - String youtubeUrl = event.getVideoURLs().get(0); - String youtubeId = YoutubeApi.getIdByUrl(youtubeUrl); - if (youtubeId != null && !youtubeId.trim().isEmpty()) { - MainMediaVideo video = new MainMediaVideo(eventName, youtubeId); - videosPanel.add(video); - videoCounter++; - } + addVideoToVideoPanel(event); } } @@ -78,7 +81,35 @@ public class MainMedia extends Composite { } this.swiper = Swiper.createWithLoopOption(STYLES.media_swipecontainer(), STYLES.media_swipewrapper(), STYLES.media_swiperslide()); + } + private void addVideoToVideoPanel(EventBaseDTO event) { + String youtubeUrl = getRandomVideoURL(event); + String eventName = event.getName(); + String youtubeId = YoutubeApi.getIdByUrl(youtubeUrl); + if (youtubeId != null && !youtubeId.trim().isEmpty()) { + MainMediaVideo video = new MainMediaVideo(eventName, youtubeId); + videosPanel.add(video); + videoCounter++; + } + } + + 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); + } + } + return result; } @UiHandler("nextPictureLink") @@ -94,5 +125,4 @@ public class MainMedia extends Composite { this.swiper.swipePrev(); } } - } From 26b9004cf4aca87f22cb591262b007a006966d6c Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 23 Jul 2014 09:50:07 +0000 Subject: [PATCH 232/323] Updated home (markdown) --- home.md | 1 + 1 file changed, 1 insertion(+) diff --git a/home.md b/home.md index 3b92ad2e70a..64e2970f0fe 100644 --- a/home.md +++ b/home.md @@ -15,6 +15,7 @@ Like businesses, sailors need the latest information to make strategic decisions * [[EC2 Backup Strategy|wiki/amazon-ec2-backup-strategy]] * [[TracTrac|wiki/tractrac-lifecycle]] * [[Analytics on a stick|wiki/analytics-on-a-stick]] + * [[Operating Igtimi WindBots/windbot-operations]] * [[Monitoring Apache and RabbitMQ|wiki/monitoring-apache-and-rabbitmq]] * [[JMX Support|wiki/jmx]] * Information for Developers From 09b01ef894008efdab867ef1fbbef173a630d629 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 23 Jul 2014 10:05:36 +0000 Subject: [PATCH 233/323] Created windbot operations (markdown) --- Operating-Igtimi-WindBots/windbot-operations.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 Operating-Igtimi-WindBots/windbot-operations.md diff --git a/Operating-Igtimi-WindBots/windbot-operations.md b/Operating-Igtimi-WindBots/windbot-operations.md new file mode 100644 index 00000000000..a7acb6b583f --- /dev/null +++ b/Operating-Igtimi-WindBots/windbot-operations.md @@ -0,0 +1,13 @@ +# Operating Igtimi WindBots + +For wind measurements we're trying to consistently use Igtimi WindBot equipment. The AdminConsole has a tab "Igtimi Accounts" where accounts for the Igtimi YachtBot web site can be managed. WindBots associated with any of these accounts will feed their wind readings, when active, into races being tracked. Operations of the WindBots mainly needs to take care of charging the devices, turning them on when they leave the harbor and turning them off when they leave the race course. This can also be delegated to race officials carrying these devices on their vessels. + +The devices have three LEDs whose status works as follows: + +1. The three LED’s are “power”, “connection”, and “GPS” in order from top to bottom. +2. All the lights should be on when operating normally. +3. Slow flashing is normal while booting. +4. Fast flashing means that function can’t start or has failed - e.g. SIM not inserted. i.e. you shouldn’t see this +5. During sync, the “power” light blips and the “connection” LED should stay on until the sync has finished. + +More information on running the WindBots can be found [here](https://igtimi.desk.com/customer/portal/articles/1494533-yachtbot-tracker-basic-operation?b_id=1690). \ No newline at end of file From 38e0b17e6afc2c1246a3d8999fe7ec81f46243f5 Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Wed, 23 Jul 2014 16:13:47 +0200 Subject: [PATCH 234/323] Introduced enum for AdditionalScoringInformationEvent that denotes the type of the information; extended various methods to include that type information; added string messages --- .../impl/DomainObjectFactoryImpl.java | 4 +- .../domain/persistence/impl/FieldNames.java | 5 +- .../impl/MongoObjectFactoryImpl.java | 1 + .../sap/sailing/domain/racelog/RaceLog.java | 4 +- .../domain/racelog/RaceLogEventFactory.java | 4 +- .../racelog/RaceLogEventRestoreFactory.java | 4 +- .../AdditionalScoringInformationFinder.java | 43 ++++++++++-- .../racelog/impl/RaceLogEventFactoryImpl.java | 6 +- .../impl/RaceLogEventRestoreFactoryImpl.java | 6 +- .../domain/racelog/impl/RaceLogImpl.java | 2 +- .../AdditionalScoringInformationEvent.java | 8 ++- .../AdditionalScoringInformationType.java | 17 +++++ ...AdditionalScoringInformationEventImpl.java | 18 ++++- .../domain/racelog/state/RaceState.java | 5 +- .../racelog/state/impl/RaceStateImpl.java | 12 ++-- .../domain/tracking/impl/TrackImpl.java | 2 +- .../LeaderboardScoringAndRankingTest.java | 66 +++++++++++++++++-- ...ockedTrackedRaceWithStartTimeAndRanks.java | 14 ++-- ...ghPointFirstGets10Or8AndLastBreaksTie.java | 20 ++++-- .../gwt/ui/client/StringMessages.properties | 2 +- .../ui/client/StringMessages_de.properties | 1 + .../ui/client/StringMessages_ru.properties | 3 +- ...alScoringInformationEventDeserializer.java | 6 +- ...dditionalScoringInformationSerializer.java | 8 +-- .../rc/AddEntryToRaceLogJsonPostServlet.java | 3 +- .../startphase/ESSStartphaseRaceFragment.java | 5 +- 26 files changed, 214 insertions(+), 55 deletions(-) create mode 100644 java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/AdditionalScoringInformationType.java 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 d785f892413..e80ea00f53a 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 @@ -136,6 +136,7 @@ import com.sap.sailing.domain.racelog.RevokeEvent; import com.sap.sailing.domain.racelog.impl.CompetitorResultsImpl; import com.sap.sailing.domain.racelog.impl.RaceLogEventAuthorImpl; import com.sap.sailing.domain.racelog.impl.RaceLogImpl; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationType; import com.sap.sailing.domain.racelog.scoring.impl.AdditionalScoringInformationEventImpl; import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; @@ -1424,7 +1425,8 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { private RaceLogEvent loadRaceLogAdditionalScoringInformationEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { - return raceLogEventFactory.createAdditionalScoringInformationEvent(createdAt, author, logicalTimePoint, id, competitors, passId); + AdditionalScoringInformationType informationType = AdditionalScoringInformationType.valueOf(dbObject.get(FieldNames.RACE_LOG_ADDITIONAL_SCORING_INFORMATION_TYPE.name()).toString()); + return raceLogEventFactory.createAdditionalScoringInformationEvent(createdAt, author, logicalTimePoint, id, competitors, passId, informationType); } private RaceLogEvent loadRaceLogRaceLogProtestStartTimeEvent(TimePoint createdAt, RaceLogEventAuthor author, 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 76aec13d21b..cd44dd0309a 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 @@ -111,5 +111,8 @@ public enum FieldNames { FROM_MILLIS, TO_MILLIS, // Result Import url fields - RESULT_PROVIDERNAME, RESULT_URL + RESULT_PROVIDERNAME, RESULT_URL, + + // type of additional scoring information + RACE_LOG_ADDITIONAL_SCORING_INFORMATION_TYPE } 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 c0ee1cc4d8c..1c95ae3b0c7 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 @@ -854,6 +854,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { DBObject result = new BasicDBObject(); storeRaceLogEventProperties(event, result); result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), event.getClass().getSimpleName()); + result.put(FieldNames.RACE_LOG_ADDITIONAL_SCORING_INFORMATION_TYPE.name(), event.getType().name()); return result; } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLog.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLog.java index 41a28e44349..060ee5685d5 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLog.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLog.java @@ -5,6 +5,7 @@ import java.util.HashSet; import java.util.NavigableSet; import java.util.UUID; +import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.WithID; import com.sap.sailing.domain.common.racelog.tracking.NotRevokableException; import com.sap.sailing.domain.racelog.impl.RaceLogEventComparator; @@ -109,7 +110,8 @@ public interface RaceLog extends Track, WithID { RaceLogEvent getEventById(Serializable id); /** - * Get a {@link NavigableSet} of unrevoked events regardless of the {@code pass}. + * Get a {@link NavigableSet} of unrevoked events regardless of the {@code pass}. Events are sorted by + * their {@link TimePoint} and the oldest is returned first. * @return */ NavigableSet getUnrevokedEvents(); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventFactory.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventFactory.java index 7aa26159a64..df3d771eff8 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventFactory.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventFactory.java @@ -13,6 +13,7 @@ import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.common.racelog.RacingProcedureType; import com.sap.sailing.domain.racelog.impl.RaceLogEventFactoryImpl; import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationType; import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; @@ -119,5 +120,6 @@ public interface RaceLogEventFactory { CloseOpenEndedDeviceMappingEvent createCloseOpenEndedDeviceMappingEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId, Serializable deviceMappingEventId, TimePoint closingTimePoint); - AdditionalScoringInformationEvent createAdditionalScoringInformationEvent(TimePoint timePoint, Serializable id, RaceLogEventAuthor author, int currentPassId); + AdditionalScoringInformationEvent createAdditionalScoringInformationEvent(TimePoint timePoint, Serializable id, RaceLogEventAuthor author, int currentPassId, + AdditionalScoringInformationType informationType); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventRestoreFactory.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventRestoreFactory.java index 5c810591187..d3896c46577 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventRestoreFactory.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventRestoreFactory.java @@ -13,6 +13,7 @@ import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.common.racelog.RacingProcedureType; import com.sap.sailing.domain.racelog.impl.RaceLogEventRestoreFactoryImpl; import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationType; import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent; @@ -93,5 +94,6 @@ public interface RaceLogEventRestoreFactory extends RaceLogEventFactory { TimePoint logicalTimePoint, Serializable pId, int passId, Serializable deviceMappingEventId, TimePoint closingTimePoint); AdditionalScoringInformationEvent createAdditionalScoringInformationEvent(TimePoint createdAt, RaceLogEventAuthor author, - TimePoint logicalTimePoint, Serializable pId, List competitors, int passId); + TimePoint logicalTimePoint, Serializable pId, List competitors, int passId, + AdditionalScoringInformationType informationType); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/AdditionalScoringInformationFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/AdditionalScoringInformationFinder.java index 5e7cf9eecbb..bbcc78b33b7 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/AdditionalScoringInformationFinder.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/AdditionalScoringInformationFinder.java @@ -1,22 +1,55 @@ package com.sap.sailing.domain.racelog.analyzing.impl; +import java.util.ArrayList; +import java.util.List; + import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.RaceLogEvent; import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationType; -public class AdditionalScoringInformationFinder extends RaceLogAnalyzer { +/** + * Returns all {@link AdditionalScoringInformationEvent}s from the race log + * that have not been revoked when using {@link #analyze()}. The resulting list + * has the newest event first. The pass id is not relevant for the result. + * + * The reason that this finder returns a list is that there could be more than + * one event of the same class but having a different {@link AdditionalScoringInformationType}. + * + * One can also use {@link #analyze(AdditionalScoringInformationType)} to check + * for the existence of an event with exactly the provided type. + * + * @author Simon Marcel Pamies + */ +public class AdditionalScoringInformationFinder extends RaceLogAnalyzer> { public AdditionalScoringInformationFinder(RaceLog raceLog) { super(raceLog); } + + /** + * Analyze the race log for all events that match the given type filter. Returns only the newest + * one as we assume that there can be only one unrevoked event of the same type. + */ + public AdditionalScoringInformationEvent analyze(AdditionalScoringInformationType filterBy) { + AdditionalScoringInformationEvent result = null; + final List allUnrevokedEventsFromNewestToOldest = performAnalysis(); + for (AdditionalScoringInformationEvent event : allUnrevokedEventsFromNewestToOldest) { + if (event != null && event.getType() == filterBy) { + result = event; + break; + } + } + return result; + } @Override - protected AdditionalScoringInformationEvent performAnalysis() { - AdditionalScoringInformationEvent result = null; + protected List performAnalysis() { + final List result = new ArrayList(); + // fetch all unrevoked events starting with newest going to oldest for (RaceLogEvent event : getRaceLog().getUnrevokedEventsDescending()) { if (event instanceof AdditionalScoringInformationEvent) { - AdditionalScoringInformationEvent scoringEvent = (AdditionalScoringInformationEvent) event; - result = scoringEvent; + result.add((AdditionalScoringInformationEvent) event); } } return result; diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventFactoryImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventFactoryImpl.java index 949678c072f..97b5e60f141 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventFactoryImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventFactoryImpl.java @@ -32,6 +32,7 @@ import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; import com.sap.sailing.domain.racelog.RaceLogWindFixEvent; import com.sap.sailing.domain.racelog.RevokeEvent; import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationType; import com.sap.sailing.domain.racelog.scoring.impl.AdditionalScoringInformationEventImpl; import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; @@ -274,7 +275,8 @@ public class RaceLogEventFactoryImpl implements RaceLogEventFactory { @Override public AdditionalScoringInformationEvent createAdditionalScoringInformationEvent(TimePoint timePoint, Serializable id, - RaceLogEventAuthor author, int currentPassId) { - return new AdditionalScoringInformationEventImpl(MillisecondsTimePoint.now(), author, timePoint, id, Collections.emptyList(), currentPassId); + RaceLogEventAuthor author, int currentPassId, AdditionalScoringInformationType informationType) { + return new AdditionalScoringInformationEventImpl(MillisecondsTimePoint.now(), author, timePoint, id, Collections.emptyList(), currentPassId, + informationType); } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventRestoreFactoryImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventRestoreFactoryImpl.java index ee2660f2de8..70e1241ce1d 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventRestoreFactoryImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventRestoreFactoryImpl.java @@ -29,6 +29,7 @@ import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; import com.sap.sailing.domain.racelog.RaceLogWindFixEvent; import com.sap.sailing.domain.racelog.RevokeEvent; import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationType; import com.sap.sailing.domain.racelog.scoring.impl.AdditionalScoringInformationEventImpl; import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent; import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent; @@ -183,7 +184,8 @@ public class RaceLogEventRestoreFactoryImpl extends RaceLogEventFactoryImpl impl @Override public AdditionalScoringInformationEvent createAdditionalScoringInformationEvent(TimePoint createdAt, - RaceLogEventAuthor author, TimePoint logicalTimePoint, Serializable pId, List competitors, int passId) { - return new AdditionalScoringInformationEventImpl(createdAt, author, logicalTimePoint, pId, competitors, passId); + RaceLogEventAuthor author, TimePoint logicalTimePoint, Serializable pId, List competitors, int passId, + AdditionalScoringInformationType informationType) { + return new AdditionalScoringInformationEventImpl(createdAt, author, logicalTimePoint, pId, competitors, passId, informationType); } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogImpl.java index 9e4b6d1f07c..2e7397f6e3e 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogImpl.java @@ -171,7 +171,7 @@ public class RaceLogImpl extends TrackImpl implements RaceLog { // it can happen that the event that has been revoked is not yet loaded - as we assume // that race log events never get removed we can safely continue and assume that // the event will be loaded later - logger.warning("RevokeEvent for "+revokeEvent.getShortInfo()+" added, that refers to non-existent event to be revoked. Could also happen that the revoke event is before the event to be revoked. Check the times."); + logger.warning("RevokeEvent for "+revokeEvent.getShortInfo()+" added, that refers to non-existent event to be revoked. Could also happen that the revoke event is before the event to be revoked."); } else { if (! (revokedEvent instanceof Revokable)) { throw new NotRevokableException("RevokeEvent trying to revoke non-revokable event"); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/AdditionalScoringInformationEvent.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/AdditionalScoringInformationEvent.java index 946bcdee009..07e570acead 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/AdditionalScoringInformationEvent.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/AdditionalScoringInformationEvent.java @@ -7,10 +7,16 @@ import com.sap.sailing.domain.racelog.Revokable; /** * Event that could possibly overwrite scoring for a leaderboard column. The idea * is that the scoring scheme checks if such an event is available and if it applies - * extended scoring rules. + * extended scoring rules. The additional information contains a type that indicates + * which action should be executed. * * @author Simon Marcel Pamies */ public interface AdditionalScoringInformationEvent extends RaceLogEvent, Revokable, InvalidatesLeaderboardCache { + /** + * @return the type of information this event holds + */ + AdditionalScoringInformationType getType(); + } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/AdditionalScoringInformationType.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/AdditionalScoringInformationType.java new file mode 100644 index 00000000000..964fe319bd2 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/AdditionalScoringInformationType.java @@ -0,0 +1,17 @@ +package com.sap.sailing.domain.racelog.scoring; + +public enum AdditionalScoringInformationType { + /** + * Indicates that for a scoring scheme adhering to a max point rule + * where the winner gets a higher number than its successors the + * maximum number of points is lowered. The concrete amount is + * decided by the scoring scheme. + */ + MAX_POINTS_DIMINISH_MAX_SCORE, + + /** + * Unknown information scoring type - should not be used except + * for tests + */ + UNKNOWN +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/impl/AdditionalScoringInformationEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/impl/AdditionalScoringInformationEventImpl.java index d2c04cd4500..f047869b925 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/impl/AdditionalScoringInformationEventImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/impl/AdditionalScoringInformationEventImpl.java @@ -9,13 +9,17 @@ import com.sap.sailing.domain.racelog.RaceLogEventAuthor; import com.sap.sailing.domain.racelog.RaceLogEventVisitor; import com.sap.sailing.domain.racelog.impl.RaceLogEventImpl; import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationType; public class AdditionalScoringInformationEventImpl extends RaceLogEventImpl implements AdditionalScoringInformationEvent { private static final long serialVersionUID = -7627714111951381979L; + private final AdditionalScoringInformationType informationType; + public AdditionalScoringInformationEventImpl(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, - Serializable pId, List competitors, int pPassId) { + Serializable pId, List competitors, int pPassId, AdditionalScoringInformationType informationType) { super(createdAt, author, logicalTimePoint, pId, competitors, pPassId); + this.informationType = informationType; } @Override @@ -25,6 +29,16 @@ public class AdditionalScoringInformationEventImpl extends RaceLogEventImpl impl @Override public String getShortInfo() { - return "UUID: " + getId(); + return "UUID: " + getId() + " Type: " + getType().name(); + } + + @Override + public String toString() { + return super.toString() + " Type: " + getType().name(); + } + + @Override + public AdditionalScoringInformationType getType() { + return informationType; } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/RaceState.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/RaceState.java index 4f8601a09ce..fc4b4d8badd 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/RaceState.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/RaceState.java @@ -9,6 +9,7 @@ import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.RaceLogEvent; import com.sap.sailing.domain.racelog.RaceLogEventAuthor; import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationType; import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedure; import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedurePrerequisite; import com.sap.sailing.domain.racelog.state.racingprocedure.impl.RacingProcedurePrerequisiteAutoResolver; @@ -144,8 +145,8 @@ public interface RaceState extends ReadonlyRaceState { * Marks this race state with a new {@link AdditionalScoringInformationEvent} or revokes * an already existing one. */ - void setAdditionalScoringInformationEnabled(TimePoint timePoint, boolean enable); + void setAdditionalScoringInformationEnabled(TimePoint creationTimePoint, boolean enable, AdditionalScoringInformationType informationType); - boolean isAdditionalScoringInformationEnabled(); + boolean isAdditionalScoringInformationEnabled(AdditionalScoringInformationType informationType); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateImpl.java index 2d6361f3603..151ced36627 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateImpl.java @@ -18,6 +18,7 @@ import com.sap.sailing.domain.racelog.RaceLogEventFactory; import com.sap.sailing.domain.racelog.analyzing.impl.AdditionalScoringInformationFinder; import com.sap.sailing.domain.racelog.analyzing.impl.RaceStatusAnalyzer; import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationType; import com.sap.sailing.domain.racelog.state.RaceState; import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedure; import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedureFactory; @@ -164,15 +165,16 @@ public class RaceStateImpl extends ReadonlyRaceStateImpl implements RaceState { } @Override - public void setAdditionalScoringInformationEnabled(TimePoint timePoint, boolean enable) { - final AdditionalScoringInformationEvent event = new AdditionalScoringInformationFinder(raceLog).analyze(); + public void setAdditionalScoringInformationEnabled(TimePoint timePoint, boolean enable, AdditionalScoringInformationType informationType) { + final AdditionalScoringInformationEvent event = new AdditionalScoringInformationFinder(raceLog).analyze(/*filterBy*/informationType); if (enable) { if (event == null) { - raceLog.add(factory.createAdditionalScoringInformationEvent(timePoint, UUID.randomUUID(), author, raceLog.getCurrentPassId())); + raceLog.add(factory.createAdditionalScoringInformationEvent(timePoint, UUID.randomUUID(), author, raceLog.getCurrentPassId(), informationType)); } } else { if (event != null) { try { + // revoke the newest one raceLog.revokeEvent(author, event); } catch (NotRevokableException e) { e.printStackTrace(); @@ -182,8 +184,8 @@ public class RaceStateImpl extends ReadonlyRaceStateImpl implements RaceState { } @Override - public boolean isAdditionalScoringInformationEnabled() { - final AdditionalScoringInformationEvent event = new AdditionalScoringInformationFinder(raceLog).analyze(); + public boolean isAdditionalScoringInformationEnabled(AdditionalScoringInformationType informationType) { + final AdditionalScoringInformationEvent event = new AdditionalScoringInformationFinder(raceLog).analyze(/*filterBy*/informationType); return event != null; } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TrackImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TrackImpl.java index 152e4071625..88e4e1d968d 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TrackImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TrackImpl.java @@ -110,7 +110,7 @@ public class TrackImpl implements Track { * Callers that want to iterate over the collection returned need to synchronize on this object to * avoid {@link ConcurrentModificationException}s. * - * @return the smoothened fixes; this implementation simply delegates to {@link #getInternalRawFixes()} because for + * @return the smoothened fixes ordered by their time points; this implementation simply delegates to {@link #getInternalRawFixes()} because for * only {@link Timed} fixes we can't know how to remove outliers. Subclasses that constrain the * FixType may provide smoothening implementations. */ 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 a8caf87418f..71aadc12f8d 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 @@ -4,6 +4,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; import java.util.ArrayList; import java.util.Arrays; @@ -15,6 +16,8 @@ import java.util.List; import java.util.Map; import java.util.NavigableSet; import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ExecutionException; import org.junit.Test; @@ -26,7 +29,9 @@ import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.Series; import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.base.impl.BoatClassImpl; import com.sap.sailing.domain.base.impl.ControlPointWithTwoMarksImpl; +import com.sap.sailing.domain.base.impl.CourseAreaImpl; import com.sap.sailing.domain.base.impl.FleetImpl; import com.sap.sailing.domain.base.impl.MarkImpl; import com.sap.sailing.domain.base.impl.RegattaImpl; @@ -37,6 +42,7 @@ import com.sap.sailing.domain.common.MaxPointsReason; import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.dto.LeaderboardDTO; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; import com.sap.sailing.domain.common.racelog.tracking.NotRevokableException; import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; @@ -55,12 +61,17 @@ import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRul import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard; import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.analyzing.impl.AdditionalScoringInformationFinder; +import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore; import com.sap.sailing.domain.racelog.impl.RaceLogEventAuthorImpl; import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationType; import com.sap.sailing.domain.racelog.scoring.impl.AdditionalScoringInformationEventImpl; +import com.sap.sailing.domain.racelog.state.RaceState; +import com.sap.sailing.domain.racelog.state.impl.RaceStateImpl; import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; import com.sap.sailing.domain.tracking.impl.MarkPassingImpl; import com.sap.sailing.domain.tracking.impl.TimedComparator; import com.sap.sailing.util.impl.ArrayListNavigableSet; @@ -800,39 +811,80 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { @Test public void testAdditionalScoreInformationLeadsToChangedScoreForOneColumn() throws NoWindException { + TrackedRegattaRegistry trackedRegattaRegistry = mock(TrackedRegattaRegistry.class); + Regatta dummyRegatta = new RegattaImpl(EmptyRaceLogStore.INSTANCE , "Dummy", new BoatClassImpl("Extreme40", false), trackedRegattaRegistry, new HighPointFirstGets10Or8AndLastBreaksTie(), "578876345345", new CourseAreaImpl("Humba", UUID.randomUUID())); + trackedRegattaRegistry.getOrCreateTrackedRegatta(dummyRegatta); Competitor[] competitors = createCompetitors(10).toArray(new Competitor[0]); TimePoint now = MillisecondsTimePoint.now(); TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000); FlexibleLeaderboard leaderboardHighPoint10Or8AndLastBreaksTie = new FlexibleLeaderboardImpl("Test ESS Highpoint 10Or8AndLastBreaksTie", new ThresholdBasedResultDiscardingRuleImpl(/* discarding thresholds */ new int[0]), new HighPointFirstGets10Or8AndLastBreaksTie(), null); - leaderboardHighPoint10Or8AndLastBreaksTie.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors)), "R1", /* medalRace */ false); + leaderboardHighPoint10Or8AndLastBreaksTie.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors), dummyRegatta), "R1", /* medalRace */ false); assertTrue(leaderboardHighPoint10Or8AndLastBreaksTie.getScoringScheme().getScoreComparator(/* nullScoresAreBetter */ false).compare( leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[3], later)) < 0); // c0 better than c3 assertEquals(10, leaderboardHighPoint10Or8AndLastBreaksTie.getCompetitorsFromBestToWorst(later).size()); assertEquals(new Double(10), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); + assertEquals(new Double(9), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[1], later)); assertEquals(new Double(1), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); - leaderboardHighPoint10Or8AndLastBreaksTie.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors)), "R2", /* medalRace */ false); + leaderboardHighPoint10Or8AndLastBreaksTie.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors), dummyRegatta), "R2", /* medalRace */ false); assertEquals(new Double(20), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); + assertEquals(new Double(18), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[1], later)); assertEquals(new Double(2), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); RaceLog raceLogForRace2 = leaderboardHighPoint10Or8AndLastBreaksTie.getRaceColumnByName("R2").getRaceLog(leaderboardHighPoint10Or8AndLastBreaksTie.getFleet(null)); - raceLogForRace2.add(new AdditionalScoringInformationEventImpl(now, new RaceLogEventAuthorImpl("Plopp", 1), later, "12345678", Collections.emptyList(), 0)); - assertEquals(new Double(18), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); + raceLogForRace2.add(new AdditionalScoringInformationEventImpl(now, new RaceLogEventAuthorImpl("Plopp", 1), later, "12345678", Collections.emptyList(), 0, AdditionalScoringInformationType.UNKNOWN)); + assertEquals(new Double(20), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); + assertEquals(new Double(18), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[1], later)); assertEquals(new Double(2), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); - leaderboardHighPoint10Or8AndLastBreaksTie.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors)), "R3", /* medalRace */ false); + raceLogForRace2.add(new AdditionalScoringInformationEventImpl(now, new RaceLogEventAuthorImpl("Plopp", 1), later.plus(Duration.ONE_MINUTE), "123456789873773762", Collections.emptyList(), 0, AdditionalScoringInformationType.MAX_POINTS_DIMINISH_MAX_SCORE)); + assertEquals(new Double(18), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); + assertEquals(new Double(16), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[1], later)); + assertEquals(new Double(2), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); + leaderboardHighPoint10Or8AndLastBreaksTie.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors), dummyRegatta), "R3", /* medalRace */ false); assertEquals(new Double(28), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); + assertEquals(new Double(25), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[1], later)); assertEquals(new Double(3), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); AdditionalScoringInformationFinder finder = new AdditionalScoringInformationFinder(raceLogForRace2); - AdditionalScoringInformationEvent event = finder.analyze(); + AdditionalScoringInformationEvent event = finder.analyze(/*filterBy*/AdditionalScoringInformationType.MAX_POINTS_DIMINISH_MAX_SCORE); assert event != null; try { raceLogForRace2.revokeEvent(new RaceLogEventAuthorImpl("Plopp", 1), event); } catch (NotRevokableException e) { e.printStackTrace(); } - event = finder.analyze(); + event = finder.analyze(/*filterBy*/AdditionalScoringInformationType.MAX_POINTS_DIMINISH_MAX_SCORE); assert event == null; assertEquals(new Double(30), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); assertEquals(new Double(3), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); + RaceState raceStateForRace2 = RaceStateImpl.create(raceLogForRace2, new RaceLogEventAuthorImpl("Simon", 1)); + assertFalse(raceStateForRace2.isAdditionalScoringInformationEnabled(AdditionalScoringInformationType.MAX_POINTS_DIMINISH_MAX_SCORE)); + raceStateForRace2.setAdditionalScoringInformationEnabled(later.plus(Duration.ONE_MINUTE).plus(Duration.ONE_SECOND), /*enable*/true, AdditionalScoringInformationType.MAX_POINTS_DIMINISH_MAX_SCORE); + assertTrue(raceStateForRace2.isAdditionalScoringInformationEnabled(AdditionalScoringInformationType.MAX_POINTS_DIMINISH_MAX_SCORE)); + assertEquals(new Double(28), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); + assertEquals(new Double(3), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); + raceStateForRace2.setAdditionalScoringInformationEnabled(later.plus(Duration.ONE_MINUTE).plus(Duration.ONE_SECOND.plus(10000)), /*enable*/false, AdditionalScoringInformationType.MAX_POINTS_DIMINISH_MAX_SCORE); + assertFalse(raceStateForRace2.isAdditionalScoringInformationEnabled(AdditionalScoringInformationType.MAX_POINTS_DIMINISH_MAX_SCORE)); + assertEquals(new Double(30), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); + assertEquals(new Double(3), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); + LeaderboardDTO leaderboardDTO = null; + try { + leaderboardDTO = leaderboardHighPoint10Or8AndLastBreaksTie.getLeaderboardDTO(later.plus(Duration.ONE_HOUR), Collections.emptyList(), false, trackedRegattaRegistry, DomainFactory.INSTANCE, false); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + assert leaderboardDTO != null; + assertEquals(30.0, leaderboardDTO.rows.get(leaderboardDTO.competitors.get(0)).totalPoints, 0.000000000001); + raceStateForRace2.setAdditionalScoringInformationEnabled(later.plus(Duration.ONE_MINUTE).plus(Duration.ONE_SECOND.plus(20000)), /*enable*/true, AdditionalScoringInformationType.MAX_POINTS_DIMINISH_MAX_SCORE); + try { + leaderboardDTO = leaderboardHighPoint10Or8AndLastBreaksTie.getLeaderboardDTO(later.plus(Duration.ONE_HOUR), Collections.emptyList(), false, trackedRegattaRegistry, DomainFactory.INSTANCE, false); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + assertEquals(28.0, leaderboardDTO.rows.get(leaderboardDTO.competitors.get(0)).totalPoints, 0.000000000001); + assertEquals(25.0, leaderboardDTO.rows.get(leaderboardDTO.competitors.get(1)).totalPoints, 0.000000000001); } @Test 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 d1c70ee4f26..2e05e29d664 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 @@ -10,6 +10,7 @@ import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Leg; import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.Sideline; import com.sap.sailing.domain.base.SpeedWithConfidence; import com.sap.sailing.domain.base.Waypoint; @@ -43,11 +44,12 @@ import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRaceStatus; import com.sap.sailing.domain.tracking.TrackedRegatta; import com.sap.sailing.domain.tracking.Wind; -import com.sap.sailing.domain.tracking.WindPositionMode; import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache; +import com.sap.sailing.domain.tracking.WindPositionMode; import com.sap.sailing.domain.tracking.WindStore; import com.sap.sailing.domain.tracking.WindTrack; import com.sap.sailing.domain.tracking.WindWithConfidence; +import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl; import com.sap.sse.common.Util; /** @@ -62,6 +64,7 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace { private static final long serialVersionUID = 2708044935347796930L; private final TimePoint startTime; private final List competitorsFromBestToWorst; + private final Regatta regatta; private RaceDefinition race; /** @@ -70,7 +73,11 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace { * mocked tracked race */ public MockedTrackedRaceWithStartTimeAndRanks(TimePoint startTime, List competitorsFromBestToWorst) { - super(); + this(startTime, competitorsFromBestToWorst, null); + } + + public MockedTrackedRaceWithStartTimeAndRanks(TimePoint startTime, List competitorsFromBestToWorst, Regatta regatta) { + this.regatta = regatta; this.startTime = startTime; // copies the list to make sure that later modifications to the list passed to this constructor don't affect the ranking produced by this race this.competitorsFromBestToWorst = new ArrayList(competitorsFromBestToWorst); @@ -328,8 +335,7 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace { @Override public TrackedRegatta getTrackedRegatta() { - // TODO Auto-generated method stub - return null; + return new DynamicTrackedRegattaImpl(regatta); } @Override diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10Or8AndLastBreaksTie.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10Or8AndLastBreaksTie.java index 8c812c3178b..5a6b26f0bd3 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10Or8AndLastBreaksTie.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10Or8AndLastBreaksTie.java @@ -11,6 +11,7 @@ import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetche import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.analyzing.impl.AdditionalScoringInformationFinder; import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationType; import com.sap.sse.common.Util; /** @@ -50,12 +51,19 @@ public class HighPointFirstGets10Or8AndLastBreaksTie extends HighPointFirstGetsF for (Fleet fleet : raceColumn.getFleets()) { RaceLog raceLog = raceColumn.getRaceLog(fleet); AdditionalScoringInformationFinder finder = new AdditionalScoringInformationFinder(raceLog); - AdditionalScoringInformationEvent event = finder.analyze(); - if (event != null) { - if (rank == 0) { - result = null; - } else { - result = Math.max(getMinimumScoreFromRank(), (double) (SCORE_FOR_WINNER_IF_OVERWRITTEN - rank + 1)); + List events = finder.analyze(); + for (AdditionalScoringInformationEvent event : events) { + if (event != null && event.getType() == AdditionalScoringInformationType.MAX_POINTS_DIMINISH_MAX_SCORE) { + if (rank == 0) { + result = null; + } else { + result = Math.max(getMinimumScoreFromRank(), (double) (SCORE_FOR_WINNER_IF_OVERWRITTEN - rank + 1)); + + // the list returned from finder has newest first - if we find an event + // that matches the type that we want then we can stop as it is the newest + // unrevoked that has been stored + break; + } } } } 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 6fd46149ca6..541654081f1 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 @@ -1049,4 +1049,4 @@ 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) +scoringSchemeHighPointFirstGetsTenOrEight=High Point, winner gets 10 points (or 8) \ 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 bf6bb40cd19..63b2c048b23 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 @@ -1044,3 +1044,4 @@ 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 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 97da7df0dd2..683a984b2b2 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 @@ -998,4 +998,5 @@ totalSize=Total Size 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) \ No newline at end of file +currentOrAverageSpeedOverGroundInKnots=SOG (\u2205 at end) +scoringSchemeHighPointFirstGetsTenOrEight=High Point, winner gets 10 points (or 8) \ No newline at end of file diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogAdditionalScoringInformationEventDeserializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogAdditionalScoringInformationEventDeserializer.java index 5b82ad85e7a..ac06ffe727c 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogAdditionalScoringInformationEventDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogAdditionalScoringInformationEventDeserializer.java @@ -9,10 +9,13 @@ import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.racelog.RaceLogEvent; import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationType; import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; import com.sap.sailing.server.gateway.deserialization.JsonDeserializer; public class RaceLogAdditionalScoringInformationEventDeserializer extends BaseRaceLogEventDeserializer { + public static final String FIELD_TYPE = "additionalScoringInformationType"; + public RaceLogAdditionalScoringInformationEventDeserializer(JsonDeserializer competitorDeserializer) { super(competitorDeserializer); } @@ -20,6 +23,7 @@ public class RaceLogAdditionalScoringInformationEventDeserializer extends BaseRa @Override protected RaceLogEvent deserialize(JSONObject object, Serializable id, TimePoint createdAt, RaceLogEventAuthor author, TimePoint timePoint, int passId, List competitors) throws JsonDeserializationException { - return factory.createAdditionalScoringInformationEvent(timePoint, id, author, passId); + AdditionalScoringInformationType informationType = AdditionalScoringInformationType.valueOf((String)object.get(FIELD_TYPE)); + return factory.createAdditionalScoringInformationEvent(timePoint, id, author, passId, informationType); } } diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/racelog/impl/RaceLogAdditionalScoringInformationSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/racelog/impl/RaceLogAdditionalScoringInformationSerializer.java index c69e1aff1b3..629801042f0 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/racelog/impl/RaceLogAdditionalScoringInformationSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/racelog/impl/RaceLogAdditionalScoringInformationSerializer.java @@ -5,6 +5,7 @@ import org.json.simple.JSONObject; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.racelog.RaceLogEvent; import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent; +import com.sap.sailing.server.gateway.deserialization.racelog.impl.RaceLogAdditionalScoringInformationEventDeserializer; import com.sap.sailing.server.gateway.serialization.JsonSerializer; public class RaceLogAdditionalScoringInformationSerializer extends BaseRaceLogEventSerializer { @@ -22,13 +23,8 @@ public class RaceLogAdditionalScoringInformationSerializer extends BaseRaceLogEv @Override public JSONObject serialize(RaceLogEvent object) { AdditionalScoringInformationEvent event = (AdditionalScoringInformationEvent) object; - JSONObject result = super.serialize(event); - //TODO - //result.put(RaceLogCloseOpenEndedDeviceMappingEventDeserializer.FIELD_DEVICE_MAPPING_EVENT_ID, event.getDeviceMappingEventId()); - //result.put(RaceLogCloseOpenEndedDeviceMappingEventDeserializer.FIELD_CLOSING_TIMEPOINT_MILLIS, event.getClosingTimePoint().asMillis()); - + result.put(RaceLogAdditionalScoringInformationEventDeserializer.FIELD_TYPE, event.getType().name()); return result; } - } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/AddEntryToRaceLogJsonPostServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/AddEntryToRaceLogJsonPostServlet.java index cba73d7534b..9f6220a9365 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/AddEntryToRaceLogJsonPostServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/AddEntryToRaceLogJsonPostServlet.java @@ -3,6 +3,7 @@ package com.sap.sailing.server.gateway.impl.rc; import java.io.BufferedReader; import java.io.IOException; import java.util.UUID; +import java.util.logging.Level; import java.util.logging.Logger; import javax.servlet.ServletConfig; @@ -133,7 +134,7 @@ public class AddEntryToRaceLogJsonPostServlet extends AbstractJsonHttpServlet { try { logEvent = deserializer.deserialize(requestJsonObject); } catch (Exception ex) { - ex.printStackTrace(); + logger.log(Level.SEVERE, "De-Serialization failed: "+ex.getMessage(), ex); throw ex; } logger.fine("JSON is deserialized to a RaceLogEvent"); diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/startphase/ESSStartphaseRaceFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/startphase/ESSStartphaseRaceFragment.java index b1d9e5d66f9..69692e93add 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/startphase/ESSStartphaseRaceFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/startphase/ESSStartphaseRaceFragment.java @@ -10,6 +10,7 @@ import android.widget.ToggleButton; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; import com.sap.sailing.domain.common.racelog.RacingProcedureType; +import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationType; import com.sap.sailing.domain.racelog.state.racingprocedure.ess.ESSRacingProcedure; import com.sap.sailing.racecommittee.app.R; @@ -43,11 +44,11 @@ public class ESSStartphaseRaceFragment extends BaseStartphaseRaceFragment Date: Wed, 23 Jul 2014 16:21:47 +0200 Subject: [PATCH 235/323] some improvements of the regatta header partial --- .../client/place/event/regatta/Regatta.java | 19 +++++++++++----- .../client/place/event/regatta/Regatta.ui.xml | 6 ++--- .../place/event/regatta/RegattaPhase.java | 22 +++++++++++++++---- .../place/event/regatta/RegattaPhaseRace.java | 5 +++-- .../event/regattalist/EventRegattaList.java | 5 ++--- .../event/regattalist/EventRegattaList.ui.xml | 2 +- 6 files changed, 40 insertions(+), 19 deletions(-) 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 2a2302b7a28..9dd60da707b 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 @@ -2,6 +2,7 @@ package com.sap.sailing.gwt.home.client.place.event.regatta; 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.dom.client.SpanElement; import com.google.gwt.dom.client.Style.Display; import com.google.gwt.dom.client.Style.Visibility; @@ -38,11 +39,13 @@ public class Regatta extends Composite { @SuppressWarnings("unused") private LeaderboardGroupDTO leaderboardGroup; - @UiField SpanElement regattaName1; - @UiField SpanElement regattaName2; + @UiField HeadingElement regattaName1; + @UiField HeadingElement regattaName2; @UiField DivElement leaderboardGroupName; @UiField SpanElement scheduledStart; @UiField DivElement regattaPhasesPanel; + @UiField DivElement regattaPhasesInfoDiv; + @UiField SpanElement competitorsCount; @UiField SpanElement racesCount; @UiField SpanElement trackedRacesCount; @@ -95,6 +98,7 @@ public class Regatta extends Composite { boolean isFinished = !hasLiveRace; if(!isFinished) { isFinishedDiv.getStyle().setDisplay(Display.NONE); + isFinishedDiv2.getStyle().setDisplay(Display.NONE); } String regattaDisplayName = leaderboard.displayName != null ? leaderboard.displayName : leaderboard.name; @@ -115,14 +119,17 @@ public class Regatta extends Composite { racesCount.setInnerText("tbd"); trackedRacesCount.setInnerText("tbd"); -// regattaPhasesPanel.add(xxx); setRegattaProgress(); } private void setRegattaProgress() { - for(RaceGroupSeriesDTO series: raceGroup.getSeries()) { - RegattaPhase regattaPhase = new RegattaPhase(series); - regattaPhasesPanel.appendChild(regattaPhase.getElement()); + 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()); + } } } 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 c991e11dabe..699ea6119a3 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 @@ -22,10 +22,10 @@
-

+

-

+

@@ -42,7 +42,7 @@

-
+
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 f2259db96b1..ef95e340305 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 @@ -6,6 +6,8 @@ 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.RaceColumnDTO; import com.sap.sailing.gwt.ui.shared.RaceGroupSeriesDTO; public class RegattaPhase extends UIObject { @@ -20,12 +22,24 @@ public class RegattaPhase extends UIObject { public RegattaPhase(RaceGroupSeriesDTO series) { RegattaResources.INSTANCE.css().ensureInjected(); setElement(uiBinder.createAndBindUi(this)); + + if(LeaderboardNameConstants.DEFAULT_SERIES_NAME.equals(series.getName())) { + phaseName.setInnerText("Races"); + } else { + phaseName.setInnerText(series.getName()); + } - phaseName.setInnerText(series.getName()); - - for(int i = 0; i < 5; i++) { - RegattaPhaseRace race = new RegattaPhaseRace(); + series.getRaceColumns(); + for(RaceColumnDTO raceColumn: series.getRaceColumns()) { + RegattaPhaseRace race = new RegattaPhaseRace(raceColumn); phaseRacesPanel.appendChild(race.getElement()); } } + + public RegattaPhase() { + RegattaResources.INSTANCE.css().ensureInjected(); + setElement(uiBinder.createAndBindUi(this)); + + phaseName.setInnerText("No series defined yet."); + } } 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 771ffee0f25..88c2e8062fb 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 @@ -5,6 +5,7 @@ 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.common.dto.RaceColumnDTO; public class RegattaPhaseRace extends UIObject { private static RegattaPhaseRaceUiBinder uiBinder = GWT.create(RegattaPhaseRaceUiBinder.class); @@ -14,10 +15,10 @@ public class RegattaPhaseRace extends UIObject { @UiField DivElement raceStatus; - public RegattaPhaseRace() { + public RegattaPhaseRace(RaceColumnDTO raceColumn) { RegattaResources.INSTANCE.css().ensureInjected(); setElement(uiBinder.createAndBindUi(this)); - raceStatus.setAttribute("data-status", "raceStatus"); + raceStatus.setAttribute("data-status", "finished"); } } 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 9f45168686f..f84fd9c6edb 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 @@ -10,7 +10,6 @@ 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.ui.HTMLPanel; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.home.client.place.event.EventPageNavigator; import com.sap.sailing.gwt.home.client.place.event.regatta.Regatta; @@ -29,7 +28,7 @@ public class EventRegattaList extends Composite { @UiField DivElement regattaGroupsNavigationPanel; @UiField DivElement regattaListNavgiationDiv; - @UiField HTMLPanel regattaListItemPanel; + @UiField DivElement regattaListItemsDiv; @UiField AnchorElement allRegattasLink; public EventRegattaList(EventDTO event, List raceGroups, @@ -45,7 +44,7 @@ public class EventRegattaList extends Composite { Pair leaderboardWithLeaderboardGroup = leaderboardsWithLeaderboardGroup.get(raceGroup.getName()); regatta.setData(raceGroup, leaderboardWithLeaderboardGroup != null ? leaderboardWithLeaderboardGroup.getA() : null, leaderboardWithLeaderboardGroup != null ? leaderboardWithLeaderboardGroup.getB() : null); - regattaListItemPanel.add(regatta); + regattaListItemsDiv.appendChild(regatta.getElement()); } } } 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 ba3fb0f4f79..c6780f0c73e 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 @@ -16,7 +16,7 @@
- +
From c67ecb5241bafe935fd583e45a9bd7517da8550b Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Wed, 23 Jul 2014 16:36:02 +0200 Subject: [PATCH 236/323] Using analyze(filterBy) providing the type we're searching for --- ...ghPointFirstGets10Or8AndLastBreaksTie.java | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10Or8AndLastBreaksTie.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10Or8AndLastBreaksTie.java index 5a6b26f0bd3..57e39d2a50e 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10Or8AndLastBreaksTie.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10Or8AndLastBreaksTie.java @@ -51,19 +51,12 @@ public class HighPointFirstGets10Or8AndLastBreaksTie extends HighPointFirstGetsF for (Fleet fleet : raceColumn.getFleets()) { RaceLog raceLog = raceColumn.getRaceLog(fleet); AdditionalScoringInformationFinder finder = new AdditionalScoringInformationFinder(raceLog); - List events = finder.analyze(); - for (AdditionalScoringInformationEvent event : events) { - if (event != null && event.getType() == AdditionalScoringInformationType.MAX_POINTS_DIMINISH_MAX_SCORE) { - if (rank == 0) { - result = null; - } else { - result = Math.max(getMinimumScoreFromRank(), (double) (SCORE_FOR_WINNER_IF_OVERWRITTEN - rank + 1)); - - // the list returned from finder has newest first - if we find an event - // that matches the type that we want then we can stop as it is the newest - // unrevoked that has been stored - break; - } + AdditionalScoringInformationEvent event = finder.analyze(AdditionalScoringInformationType.MAX_POINTS_DIMINISH_MAX_SCORE); + if (event != null) { + if (rank == 0) { + result = null; + } else { + result = Math.max(getMinimumScoreFromRank(), (double) (SCORE_FOR_WINNER_IF_OVERWRITTEN - rank + 1)); } } } From a583f3d595735d59cead9e2a0c4d8c0342657e37 Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Wed, 23 Jul 2014 16:39:40 +0200 Subject: [PATCH 237/323] Make sure to use analyze() as this guarantees locking of racelog --- .../analyzing/impl/AdditionalScoringInformationFinder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/AdditionalScoringInformationFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/AdditionalScoringInformationFinder.java index bbcc78b33b7..46133b89834 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/AdditionalScoringInformationFinder.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/AdditionalScoringInformationFinder.java @@ -33,7 +33,7 @@ public class AdditionalScoringInformationFinder extends RaceLogAnalyzer allUnrevokedEventsFromNewestToOldest = performAnalysis(); + final List allUnrevokedEventsFromNewestToOldest = analyze(); for (AdditionalScoringInformationEvent event : allUnrevokedEventsFromNewestToOldest) { if (event != null && event.getType() == filterBy) { result = event; From 66a3d1f68703428ad2dad64204569cc04236febc Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 23 Jul 2014 18:17:58 +0200 Subject: [PATCH 238/323] added a test case ensuring that empty leg details transition properly to non-empty through incremental leaderboard diffing --- .../test/LeaderboardDTODiffingTest.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) 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 ba5e9886d66..b9b1a1c3e74 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 @@ -14,8 +14,11 @@ import java.io.ObjectInputStream; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; import org.junit.Before; import org.junit.Test; @@ -72,6 +75,39 @@ public class LeaderboardDTODiffingTest { } return null; } + + @Test + public void testEmptyLegDetailsGetFilled() throws FileNotFoundException, IOException, ClassNotFoundException { + // remove legDetails for R9 for all competitors from previousVersion; remember which ones did contain data + Set hadLegDetailsInR9 = new HashSet<>(); + Map newRows = new HashMap<>(); + for (CompetitorDTO competitor : previousVersion.rows.keySet()) { + LeaderboardRowDTO competitorRow = new LeaderboardRowDTO(); + cloner.clone(newVersion.rows.get(competitor), competitorRow); + Map newFieldsByRaceColumnName = new HashMap<>(); + for (Entry e : competitorRow.fieldsByRaceColumnName.entrySet()) { + LeaderboardEntryDTO newEntry = new LeaderboardEntryDTO(); + cloner.clone(e.getValue(), newEntry); + newFieldsByRaceColumnName.put(e.getKey(), newEntry); + } + competitorRow.fieldsByRaceColumnName = newFieldsByRaceColumnName; + newRows.put(competitor, competitorRow); // use a copy of the row so we can null out legDetails for R9 in previousVersion only + LeaderboardRowDTO previousRow = previousVersion.rows.get(competitor); + assertNotSame(previousRow, competitorRow); + if (previousRow.fieldsByRaceColumnName.get("R9").legDetails != null) { + hadLegDetailsInR9.add(competitor); + previousRow.fieldsByRaceColumnName.get("R9").legDetails = null; + assertNotNull(competitorRow.fieldsByRaceColumnName.get("R9").legDetails != null); + } + } + newVersion.rows = newRows; + newVersion.strip(previousVersion); // should contain the leg details now + LeaderboardDTO applied = newVersion.getLeaderboardDTO(previousVersion); + // now applied must contain the leg details + for (CompetitorDTO competitor : hadLegDetailsInR9) { + assertNotNull(applied.rows.get(competitor).fieldsByRaceColumnName.get("R9").legDetails); + } + } @Test public void testLeaderboardSuccessfullyRead() { From 080be4d5becdd53caea0ba052497700ffb38ace0 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 23 Jul 2014 21:35:05 +0200 Subject: [PATCH 239/323] bug 2088: avoiding NPE when drawing advantage line with fixes lacking speed/bearing --- .../com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 53d16cdb0c1..86a2bb40188 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 @@ -883,7 +883,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe } lastBoatFix = getBoatFix(visibleLeaderInfo.getB(), date); } - if (isVisibleLeaderInfoComplete && isLegTypeKnown && lastBoatFix != null) { + if (isVisibleLeaderInfoComplete && isLegTypeKnown && lastBoatFix != null && lastBoatFix.speedWithBearing != null) { LegInfoDTO legInfoDTO = lastRaceTimesInfo.getLegInfos().get(visibleLeaderInfo.getA() - 1); double advantageLineLengthInKm = 1.0; // TODO this should probably rather scale with the visible // area of the map; bug 616 From 3b04aa0ad089e86bdb70950f3fa804976843b765 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 23 Jul 2014 22:33:54 +0200 Subject: [PATCH 240/323] renamed enum literal --- .../AdditionalScoringInformationType.java | 2 +- .../test/LeaderboardScoringAndRankingTest.java | 18 +++++++++--------- ...ighPointFirstGets10Or8AndLastBreaksTie.java | 2 +- .../startphase/ESSStartphaseRaceFragment.java | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/AdditionalScoringInformationType.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/AdditionalScoringInformationType.java index 964fe319bd2..004b3001ce4 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/AdditionalScoringInformationType.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/scoring/AdditionalScoringInformationType.java @@ -7,7 +7,7 @@ public enum AdditionalScoringInformationType { * maximum number of points is lowered. The concrete amount is * decided by the scoring scheme. */ - MAX_POINTS_DIMINISH_MAX_SCORE, + MAX_POINTS_DECREASE_MAX_SCORE, /** * Unknown information scoring type - should not be used except 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 71aadc12f8d..443d46cb43a 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 @@ -835,7 +835,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { assertEquals(new Double(20), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); assertEquals(new Double(18), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[1], later)); assertEquals(new Double(2), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); - raceLogForRace2.add(new AdditionalScoringInformationEventImpl(now, new RaceLogEventAuthorImpl("Plopp", 1), later.plus(Duration.ONE_MINUTE), "123456789873773762", Collections.emptyList(), 0, AdditionalScoringInformationType.MAX_POINTS_DIMINISH_MAX_SCORE)); + raceLogForRace2.add(new AdditionalScoringInformationEventImpl(now, new RaceLogEventAuthorImpl("Plopp", 1), later.plus(Duration.ONE_MINUTE), "123456789873773762", Collections.emptyList(), 0, AdditionalScoringInformationType.MAX_POINTS_DECREASE_MAX_SCORE)); assertEquals(new Double(18), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); assertEquals(new Double(16), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[1], later)); assertEquals(new Double(2), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); @@ -844,25 +844,25 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { assertEquals(new Double(25), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[1], later)); assertEquals(new Double(3), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); AdditionalScoringInformationFinder finder = new AdditionalScoringInformationFinder(raceLogForRace2); - AdditionalScoringInformationEvent event = finder.analyze(/*filterBy*/AdditionalScoringInformationType.MAX_POINTS_DIMINISH_MAX_SCORE); + AdditionalScoringInformationEvent event = finder.analyze(/*filterBy*/AdditionalScoringInformationType.MAX_POINTS_DECREASE_MAX_SCORE); assert event != null; try { raceLogForRace2.revokeEvent(new RaceLogEventAuthorImpl("Plopp", 1), event); } catch (NotRevokableException e) { e.printStackTrace(); } - event = finder.analyze(/*filterBy*/AdditionalScoringInformationType.MAX_POINTS_DIMINISH_MAX_SCORE); + event = finder.analyze(/*filterBy*/AdditionalScoringInformationType.MAX_POINTS_DECREASE_MAX_SCORE); assert event == null; assertEquals(new Double(30), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); assertEquals(new Double(3), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); RaceState raceStateForRace2 = RaceStateImpl.create(raceLogForRace2, new RaceLogEventAuthorImpl("Simon", 1)); - assertFalse(raceStateForRace2.isAdditionalScoringInformationEnabled(AdditionalScoringInformationType.MAX_POINTS_DIMINISH_MAX_SCORE)); - raceStateForRace2.setAdditionalScoringInformationEnabled(later.plus(Duration.ONE_MINUTE).plus(Duration.ONE_SECOND), /*enable*/true, AdditionalScoringInformationType.MAX_POINTS_DIMINISH_MAX_SCORE); - assertTrue(raceStateForRace2.isAdditionalScoringInformationEnabled(AdditionalScoringInformationType.MAX_POINTS_DIMINISH_MAX_SCORE)); + assertFalse(raceStateForRace2.isAdditionalScoringInformationEnabled(AdditionalScoringInformationType.MAX_POINTS_DECREASE_MAX_SCORE)); + raceStateForRace2.setAdditionalScoringInformationEnabled(later.plus(Duration.ONE_MINUTE).plus(Duration.ONE_SECOND), /*enable*/true, AdditionalScoringInformationType.MAX_POINTS_DECREASE_MAX_SCORE); + assertTrue(raceStateForRace2.isAdditionalScoringInformationEnabled(AdditionalScoringInformationType.MAX_POINTS_DECREASE_MAX_SCORE)); assertEquals(new Double(28), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); assertEquals(new Double(3), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); - raceStateForRace2.setAdditionalScoringInformationEnabled(later.plus(Duration.ONE_MINUTE).plus(Duration.ONE_SECOND.plus(10000)), /*enable*/false, AdditionalScoringInformationType.MAX_POINTS_DIMINISH_MAX_SCORE); - assertFalse(raceStateForRace2.isAdditionalScoringInformationEnabled(AdditionalScoringInformationType.MAX_POINTS_DIMINISH_MAX_SCORE)); + raceStateForRace2.setAdditionalScoringInformationEnabled(later.plus(Duration.ONE_MINUTE).plus(Duration.ONE_SECOND.plus(10000)), /*enable*/false, AdditionalScoringInformationType.MAX_POINTS_DECREASE_MAX_SCORE); + assertFalse(raceStateForRace2.isAdditionalScoringInformationEnabled(AdditionalScoringInformationType.MAX_POINTS_DECREASE_MAX_SCORE)); assertEquals(new Double(30), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); assertEquals(new Double(3), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); LeaderboardDTO leaderboardDTO = null; @@ -875,7 +875,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { } assert leaderboardDTO != null; assertEquals(30.0, leaderboardDTO.rows.get(leaderboardDTO.competitors.get(0)).totalPoints, 0.000000000001); - raceStateForRace2.setAdditionalScoringInformationEnabled(later.plus(Duration.ONE_MINUTE).plus(Duration.ONE_SECOND.plus(20000)), /*enable*/true, AdditionalScoringInformationType.MAX_POINTS_DIMINISH_MAX_SCORE); + raceStateForRace2.setAdditionalScoringInformationEnabled(later.plus(Duration.ONE_MINUTE).plus(Duration.ONE_SECOND.plus(20000)), /*enable*/true, AdditionalScoringInformationType.MAX_POINTS_DECREASE_MAX_SCORE); try { leaderboardDTO = leaderboardHighPoint10Or8AndLastBreaksTie.getLeaderboardDTO(later.plus(Duration.ONE_HOUR), Collections.emptyList(), false, trackedRegattaRegistry, DomainFactory.INSTANCE, false); } catch (InterruptedException e) { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10Or8AndLastBreaksTie.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10Or8AndLastBreaksTie.java index 57e39d2a50e..1ca7177aa00 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10Or8AndLastBreaksTie.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10Or8AndLastBreaksTie.java @@ -51,7 +51,7 @@ public class HighPointFirstGets10Or8AndLastBreaksTie extends HighPointFirstGetsF for (Fleet fleet : raceColumn.getFleets()) { RaceLog raceLog = raceColumn.getRaceLog(fleet); AdditionalScoringInformationFinder finder = new AdditionalScoringInformationFinder(raceLog); - AdditionalScoringInformationEvent event = finder.analyze(AdditionalScoringInformationType.MAX_POINTS_DIMINISH_MAX_SCORE); + AdditionalScoringInformationEvent event = finder.analyze(AdditionalScoringInformationType.MAX_POINTS_DECREASE_MAX_SCORE); if (event != null) { if (rank == 0) { result = null; diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/startphase/ESSStartphaseRaceFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/startphase/ESSStartphaseRaceFragment.java index 69692e93add..d3afec87d9b 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/startphase/ESSStartphaseRaceFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/startphase/ESSStartphaseRaceFragment.java @@ -44,11 +44,11 @@ public class ESSStartphaseRaceFragment extends BaseStartphaseRaceFragment Date: Wed, 23 Jul 2014 22:41:54 +0200 Subject: [PATCH 241/323] bug 2086: avoid NPE by no trying to correct declination with a null position when the Wind fix wouldn't be constructed anyway --- .../shared/IgtimiWindReceiver.java | 77 ++++++++++--------- 1 file changed, 41 insertions(+), 36 deletions(-) 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 f0f4b765eb9..c0b114ed3ac 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 @@ -185,42 +185,47 @@ public class IgtimiWindReceiver implements BulkFixReceiver { Speed aws = getAWS(timePoint, awsPair); com.sap.sse.common.Util.Pair gpsPair = getSurroundingFixes(getGpsTrack(deviceSerialNumber), timePoint); Position pos = getPosition(timePoint, gpsPair); - com.sap.sse.common.Util.Pair hdgPair = getSurroundingFixes(getHdgTrack(deviceSerialNumber), timePoint); - com.sap.sse.common.Util.Pair hdgmPair = getSurroundingFixes(getHdgmTrack(deviceSerialNumber), timePoint); - Bearing heading = getHeading(timePoint, hdgPair, hdgmPair, pos); - if (awa != null && aws != null && pos != null && heading != null) { - Bearing apparentWindDirection = heading.add(awa); - SpeedWithBearing apparentWindSpeedWithDirection = new KnotSpeedWithBearingImpl(aws.getKnots(), apparentWindDirection); - /* - * Hint from Brent Russell from Igtimi, at 2013-12-05 on the question whether to use GpsLatLong to improve - * precision of boat speed / coarse over SOG/COG measurements: - * - * "Personally I would use COG/SOG exclusively, and if unhappy with the result add a small amount of - * smoothing and consider dropping samples as outliers if they cause a SOG discontinuity. The latter might - * happen as a satellite is dropped/acquired - and I'd expect to see a time correlated position jump as - * well. Probably not though a direction/speed correlation :) - * - * All our GPS systems are using Doppler to calculate COG/SOG and this should be the most accurate measure. - * I don't believe that delta position really adds any more "truth" to the measurement of physical reality, - * if that makes sense. I'd trust d-p even less at low speeds, where you'll see the most disagreement. Also - * there is a significant quantisation noise error in the d-p calculations from the GPS resolution too, so - * you'd have to smooth it before averaging - possibly in a speed dependent way. - * - * Remember that Doppler COG/SOG is using the same raw satellite measurements that are being used to - * calculate position, just the algorithm is different. I suspect that merging the two might be, in - * practice, just a slightly indirect way of averaging. If you like the central limit theorem in action over - * the set of algorithms! - * - * So again, my personal preference would be to work with the data that should be the most accurate - * (COG/SOG) and consider algorithms that handle smoothing of that data best." - */ - com.sap.sse.common.Util.Pair sogPair = getSurroundingFixes(getSogTrack(deviceSerialNumber), timePoint); - 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 (pos != null) { + com.sap.sse.common.Util.Pair hdgPair = getSurroundingFixes(getHdgTrack(deviceSerialNumber), timePoint); + com.sap.sse.common.Util.Pair hdgmPair = getSurroundingFixes(getHdgmTrack(deviceSerialNumber), timePoint); + Bearing heading = getHeading(timePoint, hdgPair, hdgmPair, pos); + if (awa != null && aws != null && heading != null) { + Bearing apparentWindDirection = heading.add(awa); + SpeedWithBearing apparentWindSpeedWithDirection = new KnotSpeedWithBearingImpl(aws.getKnots(), apparentWindDirection); + /* + * Hint from Brent Russell from Igtimi, at 2013-12-05 on the question whether to use GpsLatLong to + * improve precision of boat speed / coarse over SOG/COG measurements: + * + * "Personally I would use COG/SOG exclusively, and if unhappy with the result add a small amount of + * smoothing and consider dropping samples as outliers if they cause a SOG discontinuity. The latter + * might happen as a satellite is dropped/acquired - and I'd expect to see a time correlated position + * jump as well. Probably not though a direction/speed correlation :) + * + * All our GPS systems are using Doppler to calculate COG/SOG and this should be the most accurate + * measure. I don't believe that delta position really adds any more "truth" to the measurement of + * physical reality, if that makes sense. I'd trust d-p even less at low speeds, where you'll see the + * most disagreement. Also there is a significant quantisation noise error in the d-p calculations from + * the GPS resolution too, so you'd have to smooth it before averaging - possibly in a speed dependent + * way. + * + * Remember that Doppler COG/SOG is using the same raw satellite measurements that are being used to + * calculate position, just the algorithm is different. I suspect that merging the two might be, in + * practice, just a slightly indirect way of averaging. If you like the central limit theorem in action + * over the set of algorithms! + * + * So again, my personal preference would be to work with the data that should be the most accurate + * (COG/SOG) and consider algorithms that handle smoothing of that data best." + */ + com.sap.sse.common.Util.Pair sogPair = getSurroundingFixes(getSogTrack(deviceSerialNumber), timePoint); + 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); + } else { + result = null; + } } else { result = null; } From 34923cda7542d5f427984b89f632ffa0d3176891 Mon Sep 17 00:00:00 2001 From: "Christopher Ronnewinkel (D036654)" Date: Wed, 23 Jul 2014 22:50:17 +0200 Subject: [PATCH 242/323] refactor Boundary to Grid with extension to a curved grid that is constructed from great circles; ensure symmetry along race course middle line; --- .../gwt/ui/server/SimulatorServiceImpl.java | 15 +- .../test/RectangularBoundaryTest.java | 8 +- .../sailing/simulator/test/SimulatorTest.java | 6 +- .../sailing/simulator/test/TreeGrowTest.java | 6 +- .../test/WindFieldGeneratorTest.java | 16 +- .../com/sap/sailing/simulator/Boundary.java | 46 ---- .../src/com/sap/sailing/simulator/Grid.java | 54 +++++ .../simulator/SimulationParameters.java | 2 +- .../sailing/simulator/impl/CurvedGrid.java | 218 ++++++++++++++++++ .../impl/PathGenerator1TurnerLeftDirect.java | 6 +- .../impl/PathGenerator1TurnerRightDirect.java | 6 +- .../simulator/impl/PathGeneratorDijkstra.java | 6 +- .../impl/PathGeneratorDynProgForward.java | 6 +- .../impl/PathGeneratorTreeGrowWind3.java | 4 +- ...ularBoundary.java => RectangularGrid.java} | 177 ++++---------- .../simulator/impl/SailingSimulatorImpl.java | 6 +- .../impl/SimulationParametersImpl.java | 6 +- .../simulator/windfield/WindField.java | 4 +- .../windfield/WindFieldGenerator.java | 4 +- .../windfield/WindFieldGeneratorFactory.java | 4 +- .../impl/WindFieldGeneratorBlastImpl.java | 4 +- .../impl/WindFieldGeneratorCombined.java | 6 +- .../impl/WindFieldGeneratorFactoryImpl.java | 4 +- .../impl/WindFieldGeneratorImpl.java | 14 +- .../impl/WindFieldGeneratorMeasured.java | 4 +- .../WindFieldGeneratorOscillationImpl.java | 4 +- 26 files changed, 392 insertions(+), 244 deletions(-) delete mode 100644 java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/Boundary.java create mode 100644 java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/Grid.java create mode 100644 java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/CurvedGrid.java rename java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/{RectangularBoundary.java => RectangularGrid.java} (51%) 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 0620b7f5082..3ba765dc2fa 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 @@ -55,6 +55,7 @@ import com.sap.sailing.gwt.ui.simulator.windpattern.WindPatternDisplayManager; import com.sap.sailing.gwt.ui.simulator.windpattern.WindPatternNotFoundException; import com.sap.sailing.gwt.ui.simulator.windpattern.WindPatternSetting; import com.sap.sailing.simulator.BoatClassProperties; +import com.sap.sailing.simulator.Grid; import com.sap.sailing.simulator.Path; import com.sap.sailing.simulator.PolarDiagram; import com.sap.sailing.simulator.SailingSimulator; @@ -62,11 +63,11 @@ import com.sap.sailing.simulator.SimulationParameters; import com.sap.sailing.simulator.TimedPosition; import com.sap.sailing.simulator.TimedPositionWithSpeed; import com.sap.sailing.simulator.impl.ConfigurationManager; +import com.sap.sailing.simulator.impl.CurvedGrid; import com.sap.sailing.simulator.impl.PathGenerator1Turner; import com.sap.sailing.simulator.impl.PathImpl; import com.sap.sailing.simulator.impl.PolarDiagramCSV; import com.sap.sailing.simulator.impl.ReadingConfigurationFileStatus; -import com.sap.sailing.simulator.impl.RectangularBoundary; import com.sap.sailing.simulator.impl.SailingSimulatorImpl; import com.sap.sailing.simulator.impl.SimulationParametersImpl; import com.sap.sailing.simulator.impl.TimedPositionImpl; @@ -185,9 +186,7 @@ public class SimulatorServiceImpl extends RemoteServiceServlet implements Simula course.add(nw); course.add(se); - RectangularBoundary bd = new RectangularBoundary(nw, se, 0.1); - // List lattice = bd.extractLattice(params.getxRes(), - // params.getyRes()); + Grid bd = new CurvedGrid(nw, se); controlParameters.resetBlastRandomStream = params.isKeepState(); retreiveWindControlParameters(pattern); @@ -201,7 +200,7 @@ public class SimulatorServiceImpl extends RemoteServiceServlet implements Simula throw new WindPatternNotFoundException("Please select a valid wind pattern."); } - Position[][] grid = bd.extractGrid(params.getxRes(), params.getyRes(), params.getBorderY(), params.getBorderX()); + Position[][] grid = bd.generatePositions(params.getxRes(), params.getyRes(), params.getBorderY(), params.getBorderX()); wf.setPositionGrid(grid); TimePoint startTime = new MillisecondsTimePoint(params.getStartTime().getTime()); @@ -549,9 +548,9 @@ public class SimulatorServiceImpl extends RemoteServiceServlet implements Simula SailingSimulator sailingSimulator = new SailingSimulatorImpl(simulationParameters); Path gpsWind = sailingSimulator.getLegGPSTrack(SimulatorServiceUtils.toSimulatorUISelection(requestData.selection)); - RectangularBoundary rectangularBoundry = new RectangularBoundary(oldMovedPosition, newMovedPosition, 0.1); - this.controlParameters.baseWindBearing += rectangularBoundry.getSouth().getDegrees(); - WindFieldGeneratorMeasured windFieldGenerator = new WindFieldGeneratorMeasured(rectangularBoundry, this.controlParameters); + Grid grid = new CurvedGrid(oldMovedPosition, newMovedPosition); + this.controlParameters.baseWindBearing += grid.getSouth().getDegrees(); + WindFieldGeneratorMeasured windFieldGenerator = new WindFieldGeneratorMeasured(grid, this.controlParameters); windFieldGenerator.setGPSWind(gpsWind); TimePoint startTime = new MillisecondsTimePoint(requestData.oldMovedPointTimePoint); diff --git a/java/com.sap.sailing.simulator.test/src/com/sap/sailing/simulator/test/RectangularBoundaryTest.java b/java/com.sap.sailing.simulator.test/src/com/sap/sailing/simulator/test/RectangularBoundaryTest.java index b5c6ca5b64c..05c8dd9191e 100644 --- a/java/com.sap.sailing.simulator.test/src/com/sap/sailing/simulator/test/RectangularBoundaryTest.java +++ b/java/com.sap.sailing.simulator.test/src/com/sap/sailing/simulator/test/RectangularBoundaryTest.java @@ -6,8 +6,8 @@ import org.junit.Test; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.impl.DegreePosition; -import com.sap.sailing.simulator.Boundary; -import com.sap.sailing.simulator.impl.RectangularBoundary; +import com.sap.sailing.simulator.Grid; +import com.sap.sailing.simulator.impl.RectangularGrid; public class RectangularBoundaryTest { @@ -16,8 +16,8 @@ public class RectangularBoundaryTest { Position p1 = new DegreePosition(25.661333, -90.752563); Position p2 = new DegreePosition(24.522137, -90.774536); - Boundary b = new RectangularBoundary(p1, p2, 0.1); - Position[][] grid = b.extractGrid(20,20,0,0); + Grid b = new RectangularGrid(p1, p2); + Position[][] grid = b.generatePositions(20,20,0,0); assertEquals("Number of lattice points",400,grid.length*grid[0].length); } diff --git a/java/com.sap.sailing.simulator.test/src/com/sap/sailing/simulator/test/SimulatorTest.java b/java/com.sap.sailing.simulator.test/src/com/sap/sailing/simulator/test/SimulatorTest.java index a29bb6039c2..16fd9731d41 100644 --- a/java/com.sap.sailing.simulator.test/src/com/sap/sailing/simulator/test/SimulatorTest.java +++ b/java/com.sap.sailing.simulator.test/src/com/sap/sailing/simulator/test/SimulatorTest.java @@ -20,7 +20,7 @@ import com.sap.sailing.simulator.Path; import com.sap.sailing.simulator.PolarDiagram; import com.sap.sailing.simulator.SimulationParameters; import com.sap.sailing.simulator.impl.PolarDiagram49STG; -import com.sap.sailing.simulator.impl.RectangularBoundary; +import com.sap.sailing.simulator.impl.RectangularGrid; import com.sap.sailing.simulator.impl.SailingSimulatorImpl; import com.sap.sailing.simulator.impl.SimulationParametersImpl; import com.sap.sailing.simulator.util.SailingSimulatorConstants; @@ -43,8 +43,8 @@ public class SimulatorTest { course.add(end); PolarDiagram pd = new PolarDiagram49STG();//PolarDiagram49.CreateStandard49(); - RectangularBoundary bd = new RectangularBoundary(start, end, 0.1); - Position[][] positions = bd.extractGrid(10, 10, 0, 0); + RectangularGrid bd = new RectangularGrid(start, end); + Position[][] positions = bd.generatePositions(10, 10, 0, 0); Bearing windBear = end.getBearingGreatCircle(start); WindControlParameters windParameters = new WindControlParameters(12.0, windBear.getDegrees()); WindFieldGenerator wf = new WindFieldGeneratorBlastImpl(bd, windParameters); diff --git a/java/com.sap.sailing.simulator.test/src/com/sap/sailing/simulator/test/TreeGrowTest.java b/java/com.sap.sailing.simulator.test/src/com/sap/sailing/simulator/test/TreeGrowTest.java index 9070c7b8565..4f50acfdbd8 100644 --- a/java/com.sap.sailing.simulator.test/src/com/sap/sailing/simulator/test/TreeGrowTest.java +++ b/java/com.sap.sailing.simulator.test/src/com/sap/sailing/simulator/test/TreeGrowTest.java @@ -22,7 +22,7 @@ import com.sap.sailing.simulator.PolarDiagram; import com.sap.sailing.simulator.SimulationParameters; import com.sap.sailing.simulator.impl.PathGeneratorTreeGrowWind3; import com.sap.sailing.simulator.impl.PolarDiagram49STG; -import com.sap.sailing.simulator.impl.RectangularBoundary; +import com.sap.sailing.simulator.impl.RectangularGrid; import com.sap.sailing.simulator.impl.SimulationParametersImpl; import com.sap.sailing.simulator.util.SailingSimulatorConstants; import com.sap.sailing.simulator.windfield.WindControlParameters; @@ -44,8 +44,8 @@ public class TreeGrowTest { course.add(start); course.add(end); PolarDiagram pd = new PolarDiagram49STG();//PolarDiagram49.CreateStandard49(); - RectangularBoundary bd = new RectangularBoundary(start, end, 0.1); - Position[][] positions = bd.extractGrid(10, 10, 0, 0); + RectangularGrid bd = new RectangularGrid(start, end); + Position[][] positions = bd.generatePositions(10, 10, 0, 0); //RectangularBoundary new_bd = new RectangularBoundary(start, end, 0.1); //Speed knotSpeed = new KnotSpeedImpl(8); WindControlParameters windParameters = new WindControlParameters(12, start.getBearingGreatCircle(end).reverse().getDegrees()); diff --git a/java/com.sap.sailing.simulator.test/src/com/sap/sailing/simulator/test/WindFieldGeneratorTest.java b/java/com.sap.sailing.simulator.test/src/com/sap/sailing/simulator/test/WindFieldGeneratorTest.java index 742748b1292..42ea29dc9b3 100644 --- a/java/com.sap.sailing.simulator.test/src/com/sap/sailing/simulator/test/WindFieldGeneratorTest.java +++ b/java/com.sap.sailing.simulator.test/src/com/sap/sailing/simulator/test/WindFieldGeneratorTest.java @@ -23,7 +23,7 @@ import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.impl.MillisecondsDurationImpl; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; import com.sap.sailing.domain.tracking.Wind; -import com.sap.sailing.simulator.impl.RectangularBoundary; +import com.sap.sailing.simulator.impl.RectangularGrid; import com.sap.sailing.simulator.impl.TimedPositionWithSpeedImpl; import com.sap.sailing.simulator.windfield.WindControlParameters; import com.sap.sailing.simulator.windfield.impl.WindFieldGeneratorBlastImpl; @@ -51,11 +51,11 @@ public class WindFieldGeneratorTest { course.add(end); WindControlParameters windParameters = new WindControlParameters(3, 180); - RectangularBoundary bd = new RectangularBoundary(start, end, 0.1); + RectangularGrid bd = new RectangularGrid(start, end); WindFieldGeneratorImpl wf = new WindFieldGeneratorBlastImpl(bd, windParameters); int hSteps = 10; int vSteps = 5; - Position[][] positions = bd.extractGrid(hSteps, vSteps, 0, 0); + Position[][] positions = bd.generatePositions(hSteps, vSteps, 0, 0); assert (positions.length*positions[0].length == hSteps * vSteps); int index = 0; for (int i = 0; i < positions.length; ++i) { @@ -63,7 +63,7 @@ public class WindFieldGeneratorTest { logger.info("P" + ++index + ":" + positions[i][j]); } } - wf.setPositionGrid(bd.extractGrid(hSteps, vSteps, 0, 0)); + wf.setPositionGrid(bd.generatePositions(hSteps, vSteps, 0, 0)); Position[][] positionGrid = wf.getPositionGrid(); assertNotNull("Position Grid is not null", positionGrid); assertEquals("Position Grid Number of Rows", vSteps, positionGrid.length); @@ -91,13 +91,13 @@ public class WindFieldGeneratorTest { windParameters.frequency = 0.375; windParameters.amplitude = 20.0; - RectangularBoundary bd = new RectangularBoundary(start, end, 0.1); + RectangularGrid bd = new RectangularGrid(start, end); WindFieldGeneratorOscillationImpl wf = new WindFieldGeneratorOscillationImpl(bd, windParameters); int hSteps = 30; int vSteps = 15; - wf.setPositionGrid(bd.extractGrid(hSteps, vSteps, 0, 0)); + wf.setPositionGrid(bd.generatePositions(hSteps, vSteps, 0, 0)); Position[][] positionGrid = wf.getPositionGrid(); TimePoint startTime = new MillisecondsTimePoint(0); Duration timeStep = new MillisecondsDurationImpl(30 * 1000); @@ -235,12 +235,12 @@ public class WindFieldGeneratorTest { windParameters.blastWindSpeed = 0.0; windParameters.blastWindSpeedVar = 0.0; - RectangularBoundary bd = new RectangularBoundary(start, end, 0.1); + RectangularGrid bd = new RectangularGrid(start, end); WindFieldGeneratorCombined wf = new WindFieldGeneratorCombined(bd, windParameters); int hSteps = 30; int vSteps = 15; - wf.setPositionGrid(bd.extractGrid(hSteps, vSteps, 0, 0)); + wf.setPositionGrid(bd.generatePositions(hSteps, vSteps, 0, 0)); Position[][] positionGrid = wf.getPositionGrid(); TimePoint startTime = new MillisecondsTimePoint(0); Duration timeStep = new MillisecondsDurationImpl(30 * 1000); diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/Boundary.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/Boundary.java deleted file mode 100644 index d89f2dab8bc..00000000000 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/Boundary.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.sap.sailing.simulator; - -import java.io.Serializable; -import java.util.Map; - -import com.sap.sailing.domain.common.Bearing; -import com.sap.sailing.domain.common.Distance; -import com.sap.sailing.domain.common.Position; -import com.sap.sailing.domain.common.impl.DegreeBearingImpl; -import com.sap.sse.common.Util; - -public interface Boundary extends Serializable { - - static Bearing TRUENORTH = new DegreeBearingImpl(0); - static Bearing TRUESOUTH = new DegreeBearingImpl(180); - static Bearing TRUEEAST = new DegreeBearingImpl(90); - static Bearing TRUEWEST = new DegreeBearingImpl(270); - - double getTolerance(); - - Map getCorners(); - - boolean isWithinBoundaries(Position P); - - //List extractLattice(int hPoints, int vPoints); - Position[][] extractGrid(int hPoints, int vPoints, int borderY, int borderX); - //List extractLattice(Distance hStep, Distance vstep); - public Util.Pair getGridIndex(Position x); - - public int getResY(); - public int getResX(); - public int getBorderY(); - public int getBorderX(); - - Bearing getNorth(); - Bearing getSouth(); - Bearing getEast(); - Bearing getWest(); - - Distance getHeight(); - Distance getWidth(); - - Map getRelativeCoordinates(Position p); - Position getRelativePoint(double x, double y); - -} diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/Grid.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/Grid.java new file mode 100644 index 00000000000..4d6ccb2a4a1 --- /dev/null +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/Grid.java @@ -0,0 +1,54 @@ +package com.sap.sailing.simulator; + +import java.io.Serializable; +import java.util.Map; + +import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.impl.DegreeBearingImpl; +import com.sap.sse.common.Util; + +/** + * The Grid interface defines access to a set of GPS-positions which form a lattice of supporting positions for + * representing a vectorfield or performing optimization. + * + * @author Christopher Ronnewinkel (D036654) + * + */ +public interface Grid extends Serializable { + + static Bearing relativeNorth = new DegreeBearingImpl(0); + static Bearing relativeSouth = new DegreeBearingImpl(180); + static Bearing relativeEast = new DegreeBearingImpl(90); + static Bearing relativeWest = new DegreeBearingImpl(270); + + Map getCorners(); + + boolean inBounds(Position P); + + Position[][] generatePositions(int hPoints, int vPoints, int borderY, int borderX); + + public Util.Pair getIndex(Position x); + + public int getResY(); + + public int getResX(); + + public int getBorderY(); + + public int getBorderX(); + + Bearing getNorth(); + + Bearing getSouth(); + + Bearing getEast(); + + Bearing getWest(); + + Distance getHeight(); + + Distance getWidth(); + +} diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/SimulationParameters.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/SimulationParameters.java index 768b28e05d8..29d23fa9afb 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/SimulationParameters.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/SimulationParameters.java @@ -18,7 +18,7 @@ public interface SimulationParameters { WindFieldGenerator getWindField(); - Boundary getBoundaries(); + Grid getGrid(); Map getSettings(); diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/CurvedGrid.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/CurvedGrid.java new file mode 100644 index 00000000000..2020c299a82 --- /dev/null +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/CurvedGrid.java @@ -0,0 +1,218 @@ +package com.sap.sailing.simulator.impl; + +import java.util.HashMap; +import java.util.Map; + +import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.simulator.Grid; +import com.sap.sse.common.Util; + +/** + * Implements the {@link Grid} interface by providing a grid of GPS-positions based on great circles and the + * corresponding index-calculation to associate an arbitrary GPS-position with the closest grid-GPS-position. + * + * Since the grid is constructed in spherical coordinates, we call it the curved grid. It stays perfectly symmetric no + * matter along which bearing to North it is aligned with. + * + * @author Christopher Ronnewinkel (D036654) + * + */ +public class CurvedGrid implements Grid { + + private static final long serialVersionUID = 3598121983120213464L; + private Position rcStart; // start position of race course + private Position rcEnd; // end position of race course + private int vPoints; // number of vertical steps + private int hPoints; // number of horizontal steps + private int borderY; + private int borderX; + + private Position gridNorthWest; + private Position gridSouthEast; + private Position gridSouthWest; + private Position gridNorthEast; + + private Bearing gridNorth; + private Bearing gridSouth; + private Bearing gridEast; + private Bearing gridWest; + + private Distance gridWidth; + private double xscale = 1.5; + private Distance gridHeight; + + public CurvedGrid(Position p1, Position p2) { + + rcStart = p1; + rcEnd = p2; + + gridNorth = p1.getBearingGreatCircle(p2); + gridSouth = gridNorth.reverse(); + gridEast = gridNorth.add(relativeEast); + gridWest = gridNorth.add(relativeWest); + + gridHeight = p1.getDistance(p2); + gridWidth = gridHeight.scale(2); + + gridNorthWest = p2.translateGreatCircle(gridWest, gridHeight); + gridNorthEast = p2.translateGreatCircle(gridEast, gridHeight); + + gridSouthWest = p1.translateGreatCircle(gridWest, gridHeight); + gridSouthEast = p1.translateGreatCircle(gridEast, gridHeight); + + } + + @Override + public Map getCorners() { + + Map map = new HashMap(); + map.put("NorthWest", gridNorthWest); + map.put("SouthWest", gridSouthWest); + map.put("SouthEast", gridSouthEast); + map.put("NorthEast", gridNorthEast); + + return map; + + } + + @Override + public boolean inBounds(Position p) { + + Position northProjection = p.projectToLineThrough(gridNorthWest, getEast()); + Position southProjection = p.projectToLineThrough(gridSouthWest, getEast()); + Position westProjection = p.projectToLineThrough(gridNorthWest, getNorth()); + Position eastProjection = p.projectToLineThrough(gridNorthEast, getNorth()); + + Distance northSouth = northProjection.getDistance(southProjection); + Distance eastWest = eastProjection.getDistance(westProjection); + + return (northSouth.compareTo(p.getDistance(northProjection)) >= 0) + && (northSouth.compareTo(p.getDistance(southProjection)) >= 0) + && (eastWest.compareTo(p.getDistance(eastProjection)) >= 0) + && (eastWest.compareTo(p.getDistance(westProjection)) >= 0); + + } + + @Override + public Position[][] generatePositions(int hPoints, int vPoints, int borderY, int borderX) { + + this.vPoints = vPoints; + this.hPoints = hPoints; + + this.borderY = borderY; + this.borderX = borderX; + + Distance vStep = gridHeight.scale(1.0 / (vPoints - 1)); + Distance hStep = gridHeight.scale(xscale / (hPoints - 1)); + + Position[][] grid = new Position[vPoints + 2 * borderY][hPoints + 2 * borderX]; + Position pv; + for (int i = -borderY; i < (vPoints + borderY); i++) { + + if (i == 0) { + pv = rcStart; + } else if (i == vPoints - 1) { + pv = rcEnd; + } else { + pv = rcStart.translateGreatCircle(gridNorth, vStep.scale(i)); + } + + int j = 0; + // left side + while (j < (hPoints + 2 * borderX) / 2) { + grid[i + borderY][j] = pv.translateGreatCircle(gridWest, + hStep.scale((hPoints + 2 * borderX - 1) / 2. - j)); + j++; + } + + // middle + if ((hPoints + 2 * borderX) % 2 == 1) { + grid[i + borderY][j] = pv; + j++; + } + + // right side + while (j < (hPoints + 2 * borderX)) { + grid[i + borderY][j] = pv.translateGreatCircle(gridEast, + hStep.scale(j - (hPoints + 2 * borderX - 1) / 2.)); + j++; + } + } + + return grid; + } + + public Util.Pair getIndex(Position x) { + + double vFlt = 0; + double hFlt = 0; + + if (!x.equals(rcStart)) { + Position h = x.projectToLineThrough(rcStart, gridNorth); + vFlt = vPoints * rcStart.getDistance(h).getMeters() / gridHeight.getMeters(); + int sign = +1; + if (Math.abs(h.getBearingGreatCircle(x).getDifferenceTo(gridWest).getDegrees()) < 90.0) { + sign = -1; + } + hFlt = sign * (hPoints - 1) * x.getDistance(h).getMeters() / gridHeight.getMeters() / xscale; + } + + int vIdx = Math.min(Math.max(-this.borderY, (int) Math.round(vFlt) + this.borderY), vPoints - 1 + this.borderY); + int hIdx = Math.min(Math.max(-this.borderX, (int) Math.round((hPoints - 1) / 2. + hFlt)), hPoints - 1 + this.borderX); + + return new Util.Pair(vIdx, hIdx); + } + + @Override + public int getResY() { + return this.vPoints; + } + + @Override + public int getResX() { + return this.hPoints; + } + + @Override + public int getBorderY() { + return this.borderY; + } + + @Override + public int getBorderX() { + return this.borderX; + } + + @Override + public Bearing getNorth() { + return gridNorth; + } + + @Override + public Bearing getSouth() { + return gridSouth; + } + + @Override + public Bearing getEast() { + return gridEast; + } + + @Override + public Bearing getWest() { + return gridWest; + } + + @Override + public Distance getWidth() { + return gridWidth; + } + + @Override + public Distance getHeight() { + return gridHeight; + } + +} diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGenerator1TurnerLeftDirect.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGenerator1TurnerLeftDirect.java index 6a7b4e88990..cea23d98d76 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGenerator1TurnerLeftDirect.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGenerator1TurnerLeftDirect.java @@ -9,7 +9,7 @@ import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.SpeedWithBearing; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.simulator.Boundary; +import com.sap.sailing.simulator.Grid; import com.sap.sailing.simulator.Path; import com.sap.sailing.simulator.PolarDiagram; import com.sap.sailing.simulator.SimulationParameters; @@ -26,7 +26,7 @@ public class PathGenerator1TurnerLeftDirect extends PathGeneratorBase { public Path getPath() { // retrieve simulation parameters - Boundary boundary = new RectangularBoundary(this.parameters.getCourse().get(0), this.parameters + Grid boundary = new RectangularGrid(this.parameters.getCourse().get(0), this.parameters .getCourse().get(1));// simulationParameters.getBoundaries(); WindFieldGenerator windField = this.parameters.getWindField(); PolarDiagram polarDiagram = this.parameters.getBoatPolarDiagram(); @@ -102,7 +102,7 @@ public class PathGenerator1TurnerLeftDirect extends PathGeneratorBase { // System.out.println("out of time"); break; } - if (!boundary.isWithinBoundaries(currentPosition)) { + if (!boundary.inBounds(currentPosition)) { // outOfBounds = true; // System.out.println("out of bounds"); break; diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGenerator1TurnerRightDirect.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGenerator1TurnerRightDirect.java index 44225dbe694..837e71af5e4 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGenerator1TurnerRightDirect.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGenerator1TurnerRightDirect.java @@ -9,7 +9,7 @@ import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.SpeedWithBearing; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.simulator.Boundary; +import com.sap.sailing.simulator.Grid; import com.sap.sailing.simulator.Path; import com.sap.sailing.simulator.PolarDiagram; import com.sap.sailing.simulator.SimulationParameters; @@ -26,7 +26,7 @@ public class PathGenerator1TurnerRightDirect extends PathGeneratorBase { public Path getPath() { // retrieve simulation parameters - Boundary boundary = new RectangularBoundary(this.parameters.getCourse().get(0), this.parameters + Grid boundary = new RectangularGrid(this.parameters.getCourse().get(0), this.parameters .getCourse().get(1));// simulationParameters.getBoundaries(); WindFieldGenerator windField = this.parameters.getWindField(); PolarDiagram polarDiagram = this.parameters.getBoatPolarDiagram(); @@ -102,7 +102,7 @@ public class PathGenerator1TurnerRightDirect extends PathGeneratorBase { // System.out.println("out of time"); break; } - if (!boundary.isWithinBoundaries(currentPosition)) { + if (!boundary.inBounds(currentPosition)) { // outOfBounds = true; // System.out.println("out of bounds"); break; diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorDijkstra.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorDijkstra.java index 3e17816a5cb..58d92730a11 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorDijkstra.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorDijkstra.java @@ -14,7 +14,7 @@ import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.SpeedWithBearing; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.simulator.Boundary; +import com.sap.sailing.simulator.Grid; import com.sap.sailing.simulator.Path; import com.sap.sailing.simulator.PolarDiagram; import com.sap.sailing.simulator.SimulationParameters; @@ -34,7 +34,7 @@ public class PathGeneratorDijkstra extends PathGeneratorBase { public Path getPath() { // retrieve simulation parameters - Boundary boundary = new RectangularBoundary(this.parameters.getCourse().get(0), this.parameters + Grid boundary = new RectangularGrid(this.parameters.getCourse().get(0), this.parameters .getCourse().get(1));// simulationParameters.getBoundaries(); WindFieldGenerator windField = this.parameters.getWindField(); PolarDiagram polarDiagram = this.parameters.getBoatPolarDiagram(); @@ -49,7 +49,7 @@ public class PathGeneratorDijkstra extends PathGeneratorBase { int gridv = this.parameters.getProperty("Djikstra.gridv[int]").intValue(); // number of vertical grid steps int gridh = this.parameters.getProperty("Djikstra.gridh[int]").intValue(); // number of horizontal grid // steps - Position[][] sailGrid = boundary.extractGrid(gridh, gridv, 0, 0); + Position[][] sailGrid = boundary.generatePositions(gridh, gridv, 0, 0); // create adjacency graph including start and end Map> graph = new HashMap>(); diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorDynProgForward.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorDynProgForward.java index f633e2dbe33..f74afafc7d8 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorDynProgForward.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorDynProgForward.java @@ -11,7 +11,7 @@ import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.SpeedWithBearing; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.simulator.Boundary; +import com.sap.sailing.simulator.Grid; import com.sap.sailing.simulator.Path; import com.sap.sailing.simulator.PolarDiagram; import com.sap.sailing.simulator.SimulationParameters; @@ -130,7 +130,7 @@ public class PathGeneratorDynProgForward extends PathGeneratorBase { public Path getPath() { // retrieve simulation parameters - Boundary boundary = new RectangularBoundary(this.parameters.getCourse().get(0), this.parameters + Grid boundary = new RectangularGrid(this.parameters.getCourse().get(0), this.parameters .getCourse().get(1));// simulationParameters.getBoundaries(); WindFieldGenerator windField = this.parameters.getWindField(); PolarDiagram polarDiagram = this.parameters.getBoatPolarDiagram(); @@ -162,7 +162,7 @@ public class PathGeneratorDynProgForward extends PathGeneratorBase { } // generate grid positions using sgridh and sgridv - Position[][] sailGrid = boundary.extractGrid(spatialGridsizeHorizontal, spatialGridsizeVertical, 0, 0); + Position[][] sailGrid = boundary.generatePositions(spatialGridsizeHorizontal, spatialGridsizeVertical, 0, 0); // optimization grid indexes: // vertical steps: 0, ..., v-1 diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorTreeGrowWind3.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorTreeGrowWind3.java index c5a903d8802..485d42209dd 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorTreeGrowWind3.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorTreeGrowWind3.java @@ -219,7 +219,7 @@ public class PathGeneratorTreeGrowWind3 extends PathGeneratorBase { if (Math.abs(bearHeightSide) > 170.0) { vrtSide = (this.upwindLeg ? +1.0 : -1.0); } - double vrtDist = vrtSide*Math.round(posHeight.getDistance(posEnd).getMeters()*100.0)/100.0; + double vrtDist = vrtSide*Math.round(posHeight.getDistance(posEnd).getMeters()*200.0)/200.0; /*if (vrtDist > tgtHeight) { // scale last step so that vrtDist ~ tgtHeight @@ -244,7 +244,7 @@ public class PathGeneratorTreeGrowWind3 extends PathGeneratorBase { } // calculate horizontal distance as distance of height-position to current position //double hrzDist = Math.round(posSide*posHeight.getDistance(pathPos.getPosition()).getMeters()*100.0)/100.0; - double hrzDist = Math.round(posSide*posHeightTrgt.getDistance(pathPos.getPosition()).getMeters()*100.0)/100.0; + double hrzDist = Math.round(posSide*posHeightTrgt.getDistance(pathPos.getPosition()).getMeters()*200.0)/200.0; //System.out.println(""+hrzDist+", "+vrtDist+", "+pathPos.getPosition().getLatDeg()+", "+pathPos.getPosition().getLngDeg()+", "+posHeight.getLatDeg()+", "+posHeight.getLngDeg()); diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/RectangularBoundary.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/RectangularGrid.java similarity index 51% rename from java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/RectangularBoundary.java rename to java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/RectangularGrid.java index 4dd22f70006..013c3d96a56 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/RectangularBoundary.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/RectangularGrid.java @@ -7,10 +7,23 @@ import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.impl.DegreePosition; -import com.sap.sailing.simulator.Boundary; +import com.sap.sailing.simulator.Grid; import com.sap.sse.common.Util; -public class RectangularBoundary implements Boundary { +/** + * Implements the {@link Grid} interface by providing a grid of GPS-positions based on the plane approximation of the + * earth and the corresponding index-calculation to associate an arbitrary GPS-position with the closest + * grid-GPS-position. + * + * Since the grid has rectangular shape in the plane approximation, we call it rectangular grid. The plane approximation + * allows for a simple index calculation based on CPU-light operations. If not aligned with North the grid gets slightly + * distorted leading to asymmetries which may influence the accuracy of path calculations or optimizations. + * Still, for typical race course diameters of up to 5 nautical miles, it is a very good and fast approximation. + * + * @author Christopher Ronnewinkel (D036654) + * + */ +public class RectangularGrid implements Grid { private static final long serialVersionUID = 3598121983120213464L; private Position rcStart; // start position of race course @@ -24,7 +37,7 @@ public class RectangularBoundary implements Boundary { private int borderX; private Position northWest; - //private Position southEast; + // private Position southEast; private Position southWest; private Position northEast; @@ -43,20 +56,17 @@ public class RectangularBoundary implements Boundary { double tolerance; - public RectangularBoundary(Position p1, Position p2, double tlr) { - - // System.out.println("Start:"+p1); - // System.out.println("End :"+p2); + public RectangularGrid(Position p1, Position p2) { rcStart = p1; rcEnd = p2; - tolerance = tlr; + tolerance = 0.1; north = p1.getBearingGreatCircle(p2); south = north.reverse(); - east = north.add(TRUEEAST); - west = north.add(TRUEWEST); + east = north.add(relativeEast); + west = north.add(relativeWest); appHeight = p1.getDistance(p2); appWidth = appHeight.scale(2); @@ -65,40 +75,21 @@ public class RectangularBoundary implements Boundary { appNorthWest = p2.translateGreatCircle(west, appHeight); appNorthEast = p2.translateGreatCircle(east, appHeight); - // the following test shows, that numerics appear to be instable: - // translating appNorthEast 2xEast should be appNorthWest, but numerically its NOT - // Position tstNorthEast = appNorthWest.translateGreatCircle(east, appHeight).translateGreatCircle(east, - // appHeight); - // appSouthEast = appNorthEast.translateGreatCircle(south, appHeight); - // appSouthWest = appNorthWest.translateGreatCircle(south, appHeight); - appSouthWest = p1.translateGreatCircle(west, appHeight); appSouthEast = p1.translateGreatCircle(east, appHeight); - // System.out.println("southWest:"+appSouthWest); - // System.out.println("southEast:"+appSouthEast); - // System.out.println("northWest:"+appNorthWest); - // System.out.println("northEast:"+appNorthEast); - // System.out.println("testnEast:"+tstNorthEast); - Distance diag = appNorthWest.getDistance(appSouthEast); Bearing diag1 = appSouthEast.getBearingGreatCircle(appNorthWest); northWest = appNorthWest.translateGreatCircle(diag1, diag.scale(tolerance)); - diag1 = diag1.reverse(); - //southEast = appSouthEast.translateGreatCircle(diag1, diag.scale(tolerance)); + // diag1 = diag1.reverse(); + // southEast = appSouthEast.translateGreatCircle(diag1, diag.scale(tolerance)); Bearing diag2 = appSouthWest.getBearingGreatCircle(appNorthEast); northEast = appNorthEast.translateGreatCircle(diag2, diag.scale(tolerance)); diag2 = diag2.reverse(); southWest = appSouthWest.translateGreatCircle(diag2, diag.scale(tolerance)); } - public RectangularBoundary(Position p1, Position p2) { - - this(p1, p2, 0.1); - - } - @Override public Map getCorners() { @@ -113,7 +104,7 @@ public class RectangularBoundary implements Boundary { } @Override - public boolean isWithinBoundaries(Position p) { + public boolean inBounds(Position p) { Position northProjection = p.projectToLineThrough(northWest, getEast()); Position southProjection = p.projectToLineThrough(southWest, getEast()); @@ -131,14 +122,14 @@ public class RectangularBoundary implements Boundary { } @Override - public Position[][] extractGrid(int hPoints, int vPoints, int borderY, int borderX) { + public Position[][] generatePositions(int hPoints, int vPoints, int borderY, int borderX) { this.vPoints = vPoints; this.hPoints = hPoints; this.borderY = borderY; this.borderX = borderX; - + double xscale = 1.5; double alat = (rcEnd.getLatDeg() + rcStart.getLatDeg()) / 2.; @@ -170,11 +161,9 @@ public class RectangularBoundary implements Boundary { nHor[0] = nscHor[0]; nHor[1] = nscHor[1] / lngScale; - // System.out.println("LngScale:"+lngScale+", Diff.Vrt:"+dVrt); - Position[][] grid = new Position[vPoints+2*borderY][hPoints+2*borderX]; + Position[][] grid = new Position[vPoints + 2 * borderY][hPoints + 2 * borderX]; Position pv; - for (int i = -borderY; i < (vPoints+borderY); i++) { - // System.out.print("nrow "+i+": "); + for (int i = -borderY; i < (vPoints + borderY); i++) { if (i == 0) { pv = rcStart; @@ -187,41 +176,32 @@ public class RectangularBoundary implements Boundary { int j = 0; // left side - while (j < (hPoints+2*borderX) / 2) { - grid[i+borderY][j] = new DegreePosition(pv.getLatDeg() - ((hPoints + 2*borderX - 1) / 2. - j) / (hPoints - 1) * nHor[0] - * xscale * lscVrt, pv.getLngDeg() - ((hPoints + 2*borderX - 1) / 2. - j) / (hPoints - 1) * nHor[1] * xscale - * lscVrt); - // System.out.print(""+grid[i][j]+"("+((hPoints-1)/2.-j)+"), "); + while (j < (hPoints + 2 * borderX) / 2) { + grid[i + borderY][j] = new DegreePosition(pv.getLatDeg() - ((hPoints + 2 * borderX - 1) / 2. - j) + / (hPoints - 1) * nHor[0] * xscale * lscVrt, pv.getLngDeg() + - ((hPoints + 2 * borderX - 1) / 2. - j) / (hPoints - 1) * nHor[1] * xscale * lscVrt); j++; } // middle - if ((hPoints+2*borderX) % 2 == 1) { - grid[i+borderY][j] = pv; - // System.out.print(""+grid[i][j]+", "); + if ((hPoints + 2 * borderX) % 2 == 1) { + grid[i + borderY][j] = pv; j++; } // right side - while (j < (hPoints+2*borderX)) { - grid[i+borderY][j] = new DegreePosition(pv.getLatDeg() + (j - (hPoints + 2*borderX - 1) / 2.) / (hPoints - 1) * nHor[0] - * xscale * lscVrt, pv.getLngDeg() + (j - (hPoints + 2*borderX - 1) / 2.) / (hPoints - 1) * nHor[1] * xscale - * lscVrt); - // System.out.print(""+grid[i][j]+"("+(j-(hPoints-1)/2.)+"), "); + while (j < (hPoints + 2 * borderX)) { + grid[i + borderY][j] = new DegreePosition(pv.getLatDeg() + (j - (hPoints + 2 * borderX - 1) / 2.) + / (hPoints - 1) * nHor[0] * xscale * lscVrt, pv.getLngDeg() + + (j - (hPoints + 2 * borderX - 1) / 2.) / (hPoints - 1) * nHor[1] * xscale * lscVrt); j++; } - // System.out.println(); } - /* - * System.out.println("Grid Index Test:"); for (int i =0; i getGridIndex(Position x) { + public Util.Pair getIndex(Position x) { double[] scX = new double[2]; scX[0] = x.getLatDeg() - rcStart.getLatDeg(); @@ -229,64 +209,35 @@ public class RectangularBoundary implements Boundary { double sPrd = scX[0] * nvHor[0] + scX[1] * nvHor[1]; - int vIdx = Math.min(Math.max(-this.borderY, (int) Math.round(scX[0] * nvVrt[0] + scX[1] * nvVrt[1])), vPoints - 1 + this.borderY); - int hIdx = Math.min(Math.max(-this.borderX, (int) Math.round(sPrd + (hPoints - 1) / 2.)), hPoints - 1 + this.borderX); + int vIdx = Math.min(Math.max(-this.borderY, (int) Math.round(scX[0] * nvVrt[0] + scX[1] * nvVrt[1])), vPoints + - 1 + this.borderY); + int hIdx = Math.min(Math.max(-this.borderX, (int) Math.round(sPrd + (hPoints - 1) / 2.)), hPoints - 1 + + this.borderX); - // System.out.println("getGridIndex: "+vIdx+","+hIdx+"("+vPoints+","+hPoints+")"); return new Util.Pair(vIdx, hIdx); } - /* - * @Override public List extractLattice(int hPoints, int vPoints) { - * - * Position[][] grid = extractGrid(hPoints, vPoints); List lst = new ArrayList(); for(Position[] - * line : grid) { for(Position p : line) { lst.add(p); } } return lst; } - * - * @Override //may not return a rectangular lattice! public List extractLattice(Distance hStep, Distance - * vStep) { - * - * Position startPoint = appSouthWest; - * - * Bearing vBearing = getNorth(); Bearing hBearing = getEast(); boolean hMode = true; - * - * List lst = new ArrayList(); - * - * Position current = startPoint; lst.add(current); Position next; - * - * while (true) { - * - * if (hMode) next = current.translateGreatCircle(hBearing, hStep); else next = - * current.translateGreatCircle(vBearing, vStep); - * - * if (isWithinBoundaries(next)) { current = next; lst.add(current); if (!hMode) { hMode = true; hBearing = - * hBearing.reverse(); } } else { if(hMode) hMode = false; else break; } - * - * } - * - * return lst; } - */ - @Override public int getResY() { - return this.vPoints; + return this.vPoints; } - + @Override public int getResX() { - return this.hPoints; + return this.hPoints; } - + @Override public int getBorderY() { - return this.borderY; + return this.borderY; } - + @Override public int getBorderX() { - return this.borderX; + return this.borderX; } - + @Override public Bearing getNorth() { return north; @@ -317,32 +268,4 @@ public class RectangularBoundary implements Boundary { return appHeight; } - @Override - public Map getRelativeCoordinates(Position p) { - if (!isWithinBoundaries(p)) - return null; - - Map map = new HashMap(); - Position px = p.projectToLineThrough(appSouthWest, getEast()); - Position py = p.projectToLineThrough(appSouthWest, getNorth()); - - map.put("X", px.getDistance(appSouthWest).getMeters() / getWidth().getMeters()); - map.put("Y", py.getDistance(appSouthWest).getMeters() / getHeight().getMeters()); - return map; - } - - @Override - public Position getRelativePoint(double x, double y) { - - Position point = appSouthWest; - point = point.translateGreatCircle(getEast(), getWidth().scale(x)); - point = point.translateGreatCircle(getNorth(), getHeight().scale(y)); - return point; - } - - @Override - public double getTolerance() { - return tolerance; - } - } diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/SailingSimulatorImpl.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/SailingSimulatorImpl.java index 6144fdffa4a..17288df9988 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/SailingSimulatorImpl.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/SailingSimulatorImpl.java @@ -12,7 +12,7 @@ import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.impl.MeterDistance; -import com.sap.sailing.simulator.Boundary; +import com.sap.sailing.simulator.Grid; import com.sap.sailing.simulator.Path; import com.sap.sailing.simulator.RaceProperties; import com.sap.sailing.simulator.SailingSimulator; @@ -79,7 +79,7 @@ public class SailingSimulatorImpl implements SailingSimulator { LOGGER.info("showOpportunist: "+this.simulationParameters.showOpportunist()); if (gridArea != null) { - Boundary bd = new RectangularBoundary(gridArea[0], gridArea[1], 0.1); + Grid bd = new CurvedGrid(gridArea[0], gridArea[1]); // set base wind bearing wf.getWindParameters().baseWindBearing += bd.getSouth().getDegrees(); @@ -103,7 +103,7 @@ public class SailingSimulatorImpl implements SailingSimulator { } wf.setBoundary(bd); - Position[][] positionGrid = bd.extractGrid(gridRes[0], gridRes[1], gridRes[2], gridRes[3]); + Position[][] positionGrid = bd.generatePositions(gridRes[0], gridRes[1], gridRes[2], gridRes[3]); wf.setPositionGrid(positionGrid); wf.generate(wf.getStartTime(), wf.getEndTime(), wf.getTimeStep()); } diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/SimulationParametersImpl.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/SimulationParametersImpl.java index 522fb79f62b..cc791cb732f 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/SimulationParametersImpl.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/SimulationParametersImpl.java @@ -5,7 +5,7 @@ import java.util.List; import java.util.Map; import com.sap.sailing.domain.common.Position; -import com.sap.sailing.simulator.Boundary; +import com.sap.sailing.simulator.Grid; import com.sap.sailing.simulator.PolarDiagram; import com.sap.sailing.simulator.SimulationParameters; import com.sap.sailing.simulator.windfield.WindFieldGenerator; @@ -64,8 +64,8 @@ public class SimulationParametersImpl implements SimulationParameters { } @Override - public Boundary getBoundaries() { - return windField.getBoundaries(); + public Grid getGrid() { + return windField.getGrid(); } @Override diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/WindField.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/WindField.java index 7ff6505ced7..4025fce637f 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/WindField.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/WindField.java @@ -3,7 +3,7 @@ package com.sap.sailing.simulator.windfield; import java.io.Serializable; import com.sap.sailing.domain.tracking.Wind; -import com.sap.sailing.simulator.Boundary; +import com.sap.sailing.simulator.Grid; import com.sap.sailing.simulator.Path; import com.sap.sailing.simulator.TimedPosition; @@ -11,7 +11,7 @@ public interface WindField extends Serializable { public Wind getWind(TimedPosition coordinates); - public Boundary getBoundaries(); + public Grid getGrid(); public Path getLine(TimedPosition seed, boolean forward); diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/WindFieldGenerator.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/WindFieldGenerator.java index 450b417995a..45748eb3cab 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/WindFieldGenerator.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/WindFieldGenerator.java @@ -7,13 +7,13 @@ import java.io.Serializable; import com.sap.sailing.domain.common.Duration; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.simulator.Boundary; +import com.sap.sailing.simulator.Grid; public interface WindFieldGenerator extends WindField, Serializable { public WindControlParameters getWindParameters(); - public void setBoundary(Boundary boundary); + public void setBoundary(Grid boundary); public void setPositionGrid(Position[][] positions); diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/WindFieldGeneratorFactory.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/WindFieldGeneratorFactory.java index ce293021260..6fdaa686138 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/WindFieldGeneratorFactory.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/WindFieldGeneratorFactory.java @@ -1,12 +1,12 @@ package com.sap.sailing.simulator.windfield; -import com.sap.sailing.simulator.Boundary; +import com.sap.sailing.simulator.Grid; import com.sap.sailing.simulator.windfield.impl.WindFieldGeneratorFactoryImpl; public interface WindFieldGeneratorFactory { static WindFieldGeneratorFactory INSTANCE = new WindFieldGeneratorFactoryImpl(); - public WindFieldGenerator createWindFieldGenerator(String patternName, Boundary boundary, + public WindFieldGenerator createWindFieldGenerator(String patternName, Grid boundary, WindControlParameters windParameters); } diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/impl/WindFieldGeneratorBlastImpl.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/impl/WindFieldGeneratorBlastImpl.java index 23d74cfffd8..6952aa2f325 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/impl/WindFieldGeneratorBlastImpl.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/impl/WindFieldGeneratorBlastImpl.java @@ -14,7 +14,7 @@ import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.tracking.Wind; import com.sap.sailing.domain.tracking.impl.WindImpl; -import com.sap.sailing.simulator.Boundary; +import com.sap.sailing.simulator.Grid; import com.sap.sailing.simulator.TimedPosition; import com.sap.sailing.simulator.windfield.WindControlParameters; import com.sap.sailing.simulator.windfield.WindFieldGenerator; @@ -44,7 +44,7 @@ public class WindFieldGeneratorBlastImpl extends WindFieldGeneratorImpl implemen private static Logger logger = Logger.getLogger(WindFieldGeneratorBlastImpl.class.getName()); - public WindFieldGeneratorBlastImpl(Boundary boundary, WindControlParameters windParameters) { + public WindFieldGeneratorBlastImpl(Grid boundary, WindControlParameters windParameters) { super(boundary, windParameters); } diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/impl/WindFieldGeneratorCombined.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/impl/WindFieldGeneratorCombined.java index 863891c471d..9e384c819a6 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/impl/WindFieldGeneratorCombined.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/impl/WindFieldGeneratorCombined.java @@ -7,7 +7,7 @@ import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.tracking.Wind; import com.sap.sailing.domain.tracking.impl.WindImpl; -import com.sap.sailing.simulator.Boundary; +import com.sap.sailing.simulator.Grid; import com.sap.sailing.simulator.TimedPosition; import com.sap.sailing.simulator.windfield.WindControlParameters; import com.sap.sailing.simulator.windfield.WindFieldGenerator; @@ -18,14 +18,14 @@ public class WindFieldGeneratorCombined extends WindFieldGeneratorImpl implement private WindFieldGeneratorBlastImpl blastGen; private WindFieldGeneratorOscillationImpl oscillationGen; - public WindFieldGeneratorCombined(Boundary boundary, WindControlParameters windParameters) { + public WindFieldGeneratorCombined(Grid boundary, WindControlParameters windParameters) { super(boundary, windParameters); blastGen = new WindFieldGeneratorBlastImpl(boundary, windParameters); oscillationGen = new WindFieldGeneratorOscillationImpl(boundary, windParameters); } @Override - public void setBoundary(Boundary boundary) { + public void setBoundary(Grid boundary) { super.setBoundary(boundary); blastGen.setBoundary(boundary); diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/impl/WindFieldGeneratorFactoryImpl.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/impl/WindFieldGeneratorFactoryImpl.java index d51cafe9d85..c20a53e8849 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/impl/WindFieldGeneratorFactoryImpl.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/impl/WindFieldGeneratorFactoryImpl.java @@ -1,7 +1,7 @@ package com.sap.sailing.simulator.windfield.impl; -import com.sap.sailing.simulator.Boundary; +import com.sap.sailing.simulator.Grid; import com.sap.sailing.simulator.windfield.WindControlParameters; import com.sap.sailing.simulator.windfield.WindFieldGenerator; import com.sap.sailing.simulator.windfield.WindFieldGeneratorFactory; @@ -9,7 +9,7 @@ import com.sap.sailing.simulator.windfield.WindFieldGeneratorFactory; public class WindFieldGeneratorFactoryImpl implements WindFieldGeneratorFactory { @Override - public WindFieldGenerator createWindFieldGenerator(String patternName, Boundary boundary, + public WindFieldGenerator createWindFieldGenerator(String patternName, Grid boundary, WindControlParameters windParameters) { if (patternName.equals("BLASTS")) { return new WindFieldGeneratorBlastImpl(boundary, windParameters); diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/impl/WindFieldGeneratorImpl.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/impl/WindFieldGeneratorImpl.java index f1d1e5ac3c7..c696bf8b115 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/impl/WindFieldGeneratorImpl.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/impl/WindFieldGeneratorImpl.java @@ -13,7 +13,7 @@ import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.KnotSpeedImpl; import com.sap.sailing.domain.tracking.Wind; import com.sap.sailing.domain.tracking.impl.WindImpl; -import com.sap.sailing.simulator.Boundary; +import com.sap.sailing.simulator.Grid; import com.sap.sailing.simulator.Path; import com.sap.sailing.simulator.TimedPosition; import com.sap.sailing.simulator.TimedPositionWithSpeed; @@ -27,7 +27,7 @@ import com.sap.sse.common.Util; public abstract class WindFieldGeneratorImpl implements WindFieldGenerator { private static final long serialVersionUID = -6366698648104363491L; - protected Boundary boundary; + protected Grid boundary; protected WindControlParameters windParameters; protected Position[][] positions; @@ -46,7 +46,7 @@ public abstract class WindFieldGeneratorImpl implements WindFieldGenerator { private static Logger logger = Logger.getLogger("com.sap.sailing.windfield"); - public WindFieldGeneratorImpl(Boundary boundary, WindControlParameters windParameters) { + public WindFieldGeneratorImpl(Grid boundary, WindControlParameters windParameters) { this.boundary = boundary; this.windParameters = windParameters; this.positions = null; @@ -58,7 +58,7 @@ public abstract class WindFieldGeneratorImpl implements WindFieldGenerator { } @Override - public void setBoundary(Boundary boundary) { + public void setBoundary(Grid boundary) { this.boundary = boundary; } @@ -77,7 +77,7 @@ public abstract class WindFieldGeneratorImpl implements WindFieldGenerator { } @Override - public Boundary getBoundaries() { + public Grid getGrid() { return boundary; } @@ -97,7 +97,7 @@ public abstract class WindFieldGeneratorImpl implements WindFieldGenerator { LinkedList path = new LinkedList(); path.add(new TimedPositionWithSpeedImpl(currentTime, currentPosition, null)); - while(boundary.isWithinBoundaries(currentPosition)) { + while(boundary.inBounds(currentPosition)) { Wind currentWind = this.getWind(new TimedPositionImpl(startTime, currentPosition)); TimePoint middleTime = currentTime.plus(timeStep/2); Position middlePosition; @@ -138,7 +138,7 @@ public abstract class WindFieldGeneratorImpl implements WindFieldGenerator { } public Util.Pair getPositionIndex(Position p) { - Util.Pair gIdx = boundary.getGridIndex(p); + Util.Pair gIdx = boundary.getIndex(p); if ((gIdx.getA() != null) && (gIdx.getB() != null)) { return gIdx; } else { diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/impl/WindFieldGeneratorMeasured.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/impl/WindFieldGeneratorMeasured.java index a8cdba604eb..f7ad8712628 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/impl/WindFieldGeneratorMeasured.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/impl/WindFieldGeneratorMeasured.java @@ -11,7 +11,7 @@ import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.tracking.Wind; import com.sap.sailing.domain.tracking.impl.WindImpl; -import com.sap.sailing.simulator.Boundary; +import com.sap.sailing.simulator.Grid; import com.sap.sailing.simulator.Path; import com.sap.sailing.simulator.TimedPosition; import com.sap.sailing.simulator.TimedPositionWithSpeed; @@ -30,7 +30,7 @@ public class WindFieldGeneratorMeasured extends WindFieldGeneratorImpl implement // super(); // } - public WindFieldGeneratorMeasured(Boundary boundary, WindControlParameters windParameters) { + public WindFieldGeneratorMeasured(Grid boundary, WindControlParameters windParameters) { super(boundary, windParameters); } diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/impl/WindFieldGeneratorOscillationImpl.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/impl/WindFieldGeneratorOscillationImpl.java index 012a949084f..67b2a1f53c7 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/impl/WindFieldGeneratorOscillationImpl.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/impl/WindFieldGeneratorOscillationImpl.java @@ -14,7 +14,7 @@ import com.sap.sailing.domain.common.impl.KnotSpeedImpl; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.tracking.Wind; import com.sap.sailing.domain.tracking.impl.WindImpl; -import com.sap.sailing.simulator.Boundary; +import com.sap.sailing.simulator.Grid; import com.sap.sailing.simulator.TimedPosition; import com.sap.sailing.simulator.windfield.WindControlParameters; import com.sap.sailing.simulator.windfield.WindFieldGenerator; @@ -32,7 +32,7 @@ public class WindFieldGeneratorOscillationImpl extends WindFieldGeneratorImpl im private static Logger logger = Logger.getLogger(WindFieldGeneratorOscillationImpl.class.getName()); - public WindFieldGeneratorOscillationImpl(Boundary boundary, WindControlParameters windParameters) { + public WindFieldGeneratorOscillationImpl(Grid boundary, WindControlParameters windParameters) { super(boundary, windParameters); } From 26b8c8affb71d3750c6990d1a508060fa3cc4acf Mon Sep 17 00:00:00 2001 From: "Christopher Ronnewinkel (D036654)" Date: Thu, 24 Jul 2014 01:38:37 +0200 Subject: [PATCH 243/323] fix artefactual-looking optimization results due to bad sorting of candidates; round to millimeters in path generation (ensure stable numerics); --- .../src/com/sap/sailing/simulator/impl/PathCandidate.java | 4 ++-- .../simulator/impl/PathGeneratorTreeGrowWind3.java | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathCandidate.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathCandidate.java index ee33d92f8e4..734a6e919d6 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathCandidate.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathCandidate.java @@ -25,14 +25,14 @@ public class PathCandidate implements Comparable { // sort descending by length, width, height public int compareTo(PathCandidate other) { if (this.path.length() == other.path.length()) { - if (this.vrt == other.vrt) { + if (this.trn == other.trn) { if (Math.abs(this.hrz) == Math.abs(other.hrz)) { return 0; } else { return (Math.abs(this.hrz) < Math.abs(other.hrz) ? -1 : +1); } } else { - return (this.vrt > other.vrt ? -1 : +1); + return (this.trn < other.trn ? -1 : +1); } } else { return (this.path.length() < other.path.length() ? -1 : +1); diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorTreeGrowWind3.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorTreeGrowWind3.java index 485d42209dd..6e80083d4d2 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorTreeGrowWind3.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorTreeGrowWind3.java @@ -219,7 +219,7 @@ public class PathGeneratorTreeGrowWind3 extends PathGeneratorBase { if (Math.abs(bearHeightSide) > 170.0) { vrtSide = (this.upwindLeg ? +1.0 : -1.0); } - double vrtDist = vrtSide*Math.round(posHeight.getDistance(posEnd).getMeters()*200.0)/200.0; + double vrtDist = vrtSide*Math.round(posHeight.getDistance(posEnd).getMeters()*1000.0)/1000.0; /*if (vrtDist > tgtHeight) { // scale last step so that vrtDist ~ tgtHeight @@ -244,7 +244,7 @@ public class PathGeneratorTreeGrowWind3 extends PathGeneratorBase { } // calculate horizontal distance as distance of height-position to current position //double hrzDist = Math.round(posSide*posHeight.getDistance(pathPos.getPosition()).getMeters()*100.0)/100.0; - double hrzDist = Math.round(posSide*posHeightTrgt.getDistance(pathPos.getPosition()).getMeters()*200.0)/200.0; + double hrzDist = Math.round(posSide*posHeightTrgt.getDistance(pathPos.getPosition()).getMeters()*1000.0)/1000.0; //System.out.println(""+hrzDist+", "+vrtDist+", "+pathPos.getPosition().getLatDeg()+", "+pathPos.getPosition().getLngDeg()+", "+posHeight.getLatDeg()+", "+posHeight.getLngDeg()); @@ -555,7 +555,7 @@ public class PathGeneratorTreeGrowWind3 extends PathGeneratorBase { tstPosition = this.getStep(new TimedPositionImpl(startTime, startPos), timeStep, turnLoss, true, 'R').getA(); double tstDist2 = startPos.getDistance(tstPosition.getPosition()).getMeters(); - double hrzBinSize = (tstDist1 + tstDist2)/3.0; // horizontal bin size in meters + double hrzBinSize = (tstDist1 + tstDist2)/6.0; // horizontal bin size in meters if (debugMsgOn) { System.out.println("Horizontal Bin Size: "+hrzBinSize); } @@ -609,7 +609,7 @@ public class PathGeneratorTreeGrowWind3 extends PathGeneratorBase { if ((curPath.vrt > 0.0)) { //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) <= 1)) { + if ((Math.abs(curBin) <= 2)) { reachedEnd = true; trgPaths.add(curPath); // add path to list of target-paths } From a4c27b078305152cb8116ab621b016fdfb972f29 Mon Sep 17 00:00:00 2001 From: "Christopher Ronnewinkel (D036654)" Date: Thu, 24 Jul 2014 02:32:27 +0200 Subject: [PATCH 244/323] sanitize default parameters for blasts; --- .../windpattern/impl/WindPatternDisplayManagerImpl.java | 2 +- .../simulator/windfield/WindControlParameters.java | 2 +- .../windfield/impl/WindFieldGeneratorBlastImpl.java | 8 +++----- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/windpattern/impl/WindPatternDisplayManagerImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/windpattern/impl/WindPatternDisplayManagerImpl.java index 82450651445..ec2296132f3 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/windpattern/impl/WindPatternDisplayManagerImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/windpattern/impl/WindPatternDisplayManagerImpl.java @@ -79,7 +79,7 @@ public class WindPatternDisplayManagerImpl implements WindPatternDisplayManager 1, 1, 10); display.addSetting(maxBlastSize); WindPatternSetting blastWindSpeed = new WindPatternSettingSliderBar("blastWindSpeed", - "Average Speed (%)", 0, 200, 5, 120, 10); + "Average Speed (%)", 0, 200, 5, 100, 10); display.addSetting(blastWindSpeed); WindPatternSetting blastWindSpeedVar = new WindPatternSettingSliderBar("blastWindSpeedVar", "Speed Variance (%)", 1e-4, 100, 5, 10, 10); diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/WindControlParameters.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/WindControlParameters.java index e5204649103..26e15352a5c 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/WindControlParameters.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/WindControlParameters.java @@ -140,7 +140,7 @@ public class WindControlParameters implements Serializable { blastProbability = 25.0; maxBlastSize = 1.0; - blastWindSpeed = 120.0; + blastWindSpeed = 100.0; blastWindSpeedVar = 10.0; if (blastRandomStreamManager == null || !resetBlastRandomStream) { diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/impl/WindFieldGeneratorBlastImpl.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/impl/WindFieldGeneratorBlastImpl.java index 6952aa2f325..af35125b379 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/impl/WindFieldGeneratorBlastImpl.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/impl/WindFieldGeneratorBlastImpl.java @@ -32,7 +32,7 @@ public class WindFieldGeneratorBlastImpl extends WindFieldGeneratorImpl implemen private double blastSizeProbability = 70; private double blastEdgeProbability = 30; private double blastBearingMean = 0; - private double blastBearingVar = 8; + private double blastBearingVar = 6; private double defaultWindSpeed = 0; private double defaultWindBearing = 0; private SpeedWithBearing defaultSpeedWithBearing; @@ -164,8 +164,7 @@ public class WindFieldGeneratorBlastImpl extends WindFieldGeneratorImpl implemen //System.out.println("blast speed mean: "+bSpeedMean+" var: "+bSpeedVar); //return NormalGen.nextDouble(new LFSR113("BlastSpeedStream"), bSpeedMean, bSpeedVar); RandomStream speedStream = windParameters.getBlastRandomStreamManager().getRandomStream(BlastRandomSeedManagerImpl.BlastStream.SPEED.name()); - return NormalGen.nextDouble(speedStream, bSpeedMean, bSpeedVar); - + return Math.max(0.5*bSpeedMean, Math.min(1.5*bSpeedMean, NormalGen.nextDouble(speedStream, bSpeedMean, bSpeedVar))); } private int getBlastSize() { @@ -177,8 +176,7 @@ public class WindFieldGeneratorBlastImpl extends WindFieldGeneratorImpl implemen private double getBlastAngle() { //return NormalGen.nextDouble(new LFSR113("BlastAngleStream"), blastBearingMean, blastBearingVar); RandomStream bearingStream = windParameters.getBlastRandomStreamManager().getRandomStream(BlastRandomSeedManagerImpl.BlastStream.BEARING.name()); - return NormalGen.nextDouble(bearingStream, blastBearingMean, blastBearingVar); - + return Math.max(-1.5*blastBearingVar, Math.min(1.5*blastBearingVar, NormalGen.nextDouble(bearingStream, blastBearingMean, blastBearingVar))); } private SpeedWithBearing getSpeedWithBearing(TimedPosition timedPosition) { From 78eac0ef947617b18e81931b06a3dc4bb1a71179 Mon Sep 17 00:00:00 2001 From: Jens Rommel Date: Thu, 24 Jul 2014 09:36:02 +0200 Subject: [PATCH 245/323] Merge remote-tracking branch 'external/master' into dev Added MimeType.byName() to avoid handling of valueOf's IllegalArgumentException. Returning null instead for unsupported MimeType names. --- .../com/sap/sailing/domain/common/media/MediaTrack.java | 9 +++++++++ .../sap/sailing/gwt/ui/client/media/NewMediaDialog.java | 8 +------- .../sap/sailing/server/impl/RacingEventServiceImpl.java | 2 +- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/media/MediaTrack.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/media/MediaTrack.java index 68e2f3fea62..b3643ff9426 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/media/MediaTrack.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/media/MediaTrack.java @@ -28,6 +28,15 @@ public class MediaTrack implements Serializable { public String toString() { return mediaType.name() + '/' + mediaSubType.toString(); } + + public static MimeType byName(String mimeTypeName) { + try { + return MimeType.valueOf(mimeTypeName); + } catch (IllegalArgumentException ex) { + return null; + } + } + } public enum MediaType { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/NewMediaDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/NewMediaDialog.java index 7edcf05cfd5..0bf6fdbed69 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/NewMediaDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/NewMediaDialog.java @@ -147,13 +147,7 @@ public class NewMediaDialog extends DataEntryDialog { mediaTrack.title = lastPathSegment.substring(0, dotPos); String fileEnding = lastPathSegment.substring(dotPos + 1).toLowerCase(); - try { - mediaTrack.mimeType = MimeType.valueOf(fileEnding); - } catch (IllegalArgumentException e) { - // ignore. TODO: Somehow put it into the error message. - // throw new IllegalArgumentException("Unsupported media type '" + mimeType + "'.", e); - mediaTrack.mimeType = null; - } + mediaTrack.mimeType = MimeType.byName(fileEnding); } else { mediaTrack.title = mediaTrack.url; mediaTrack.mimeType = null; 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 1e3b93a177e..f22f5d3e356 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 @@ -558,7 +558,7 @@ public class RacingEventServiceImpl implements RacingEventServiceWithTestSupport private void loadMediaLibary() { Collection allDBMediaTracks = mediaDB.loadAllMediaTracks(); for (DBMediaTrack dbMediaTrack : allDBMediaTracks) { - MimeType mimeType = dbMediaTrack.mimeType != null ? MimeType.valueOf(dbMediaTrack.mimeType) : null; + MimeType mimeType = dbMediaTrack.mimeType != null ? MimeType.byName(dbMediaTrack.mimeType) : null; MediaTrack mediaTrack = new MediaTrack(dbMediaTrack.dbId, dbMediaTrack.title, dbMediaTrack.url, dbMediaTrack.startTime, dbMediaTrack.durationInMillis, mimeType); mediaTrackAdded(mediaTrack); From 58caa12def2cdf3a2c1044789b971fb6874374db Mon Sep 17 00:00:00 2001 From: "Steffen Wagner (d059727)" Date: Thu, 24 Jul 2014 13:15:54 +0200 Subject: [PATCH 246/323] added 1st version of a section for sap cube replication setup --- wiki/server-replication.md | 72 +++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/wiki/server-replication.md b/wiki/server-replication.md index 148ae51fbe3..583f99aed1a 100644 --- a/wiki/server-replication.md +++ b/wiki/server-replication.md @@ -90,4 +90,74 @@ Fortunately, the dangerous-to-parallelize operations seem to be the ones that oc These encompass leaderboard and leaderboard group creation and manipulation, including score corrections. In a replica these operations must be executed in the same sequence they were executed in on the master. It probably encompasses the creation of a tracked race and its course, we well as the updates to a race course with waypoint additions and mark passings because mark passing events relate to the waypoints. Paralllel operations -These encompass the receiving of raw sensor data. The order in which this data is re-played doesn't matter. The data is time-stamped, and its history is not rewritten (as maybe the case for the mark passings or for the race tracking start/finish times). Applying these operations to a replica is idempotent, so serializing an initial state doesn't need to be synchronized with the receiving of such data. Duplicate application of these operations to a server doesn't do any harm. \ No newline at end of file +These encompass the receiving of raw sensor data. The order in which this data is re-played doesn't matter. The data is time-stamped, and its history is not rewritten (as maybe the case for the mark passings or for the race tracking start/finish times). Applying these operations to a replica is idempotent, so serializing an initial state doesn't need to be synchronized with the receiving of such data. Duplicate application of these operations to a server doesn't do any harm. + +### Replication in SAP Cube +This section will describe, how a working local replica can be set up at the SAP Cube. +The setup at the Travemünder Woche 2014 was done by Steffen Wagner as it follows: + +LTE router --> gigabit switch --> cube computers (dhcp) && local replica machine (with e.g. Ubuntu 14.04 LTS & a version of the analytics suite installed) + +Steps to get everything working: +* The LTE router (192.168.1.1) should have DHCP and NS activated. Dhcp was within range XXX.XXX.1.2 - XXX.XXX.1.49 and our local replica machine had a static ip address of XXX.XXX.1.50. Please make sure, that the ip address is outside of the dhcp range. For a better setup we should think of using the local replica server for dhcp and ns services. +* Setup networking (to your needs) on the local replica machine as follows: +/etc/networking/interfaces +
+auto eth0
+iface eth0 inet static
+	address 192.168.1.50
+	netmask 255.255.255.0
+	gateway 192.168.1.1
+
+/etc/hosts +
+127.0.0.1		localhost
+127.0.0.1		tw2014 tw2014.sapsailing.com
+192.168.1.50	tw2014 tw2014.sapsailing.com
+
+* For rewriting the URLs and Ports to the fitting Spectator URL when entering the URL we need a Apache installed (apt-get install apache2) and set up like on our sapsailing.com server. Please check 000-macros.conf, 001-events.conf and 000-main.conf and set it up as you need for the event. They need to be in the config dir of apache (/etc/apache2/mods-enabled and /etc/apache2/sites-enabled) +* For better remote administration of the local replica, set up and autossh tunnel to trac@sapsailing.com and put it into rc.local for autostarting, e.g. as the following: +
+autossh -M 20009 -f -N -L 1337:127.0.0.1:22 -i SSHKEY trac@sapsailing.com
+
+* Set up automatic replication in env.sh of the analystics server and start the server (not with root!) with startscript ~/servers/server/start +* On all Cube computers add the folling batch file to the windows task scheduling for a check every minute. The script switches hosts to local replica if available. Before the script can work, you have to edit host file manully and add the required hostnames: e.g. 192.168.1.50 tw2014.sapsailing.com. You also need to replace find and replace to the required ip addresses. +
+@echo off
+@setlocal enableextensions enabledelayedexpansion
+
+REM Set a variable for the Windows hosts file location
+set "hostpath=%systemroot%\system32\drivers\etc"
+set "hostfile=hosts"
+
+REM Make the hosts file writable
+attrib -r -s -h "%hostpath%\%hostfile%"
+
+ping -n 1 -w 1000 -l 2000 192.168.1.50
+REM if local replica is available
+IF %ERRORLEVEL% == 0 (
+	REM set the string you wish to find
+	set find=54.229.94.254
+	REM set the string you wish to replace with
+	set replace=192.168.1.50
+
+REM if local replica is not available
+) ELSE (
+	REM set ring you wish to find
+	set find=192.168.1.50
+	REM set the string you wish to replace with
+	set replace=54.229.94.254
+)	
+
+for /f "delims=" %%a in ('type "%hostpath%\%hostfile%"') do (
+set "string=%%a"
+set "string=!string:%find%=%replace%!"
+>> "newfile.txt" echo !string!
+)
+
+move /y "newfile.txt" "%hostpath%\%hostfile%"
+REM Make the hosts file un-writable - not necessary.
+REM attrib +r "%hostpath%\%hostfile%"
+echo hostfile done.
+
+* If server should be shutted down, then first stop replication manually in the backend and then call ~/servers/server/stop. If everything works fine, then the Cube computers should recognize, that the replica machine is not available anymore and then switch to the real sapsailing host. From 76d65681ee3cf7d1c0a18b85e4c336911f9fd92e Mon Sep 17 00:00:00 2001 From: "Steffen Wagner (d059727)" Date: Thu, 24 Jul 2014 13:18:05 +0200 Subject: [PATCH 247/323] Revert "added 1st version of a section for sap cube replication setup" This reverts commit 58caa12def2cdf3a2c1044789b971fb6874374db. --- wiki/server-replication.md | 72 +------------------------------------- 1 file changed, 1 insertion(+), 71 deletions(-) diff --git a/wiki/server-replication.md b/wiki/server-replication.md index 583f99aed1a..148ae51fbe3 100644 --- a/wiki/server-replication.md +++ b/wiki/server-replication.md @@ -90,74 +90,4 @@ Fortunately, the dangerous-to-parallelize operations seem to be the ones that oc These encompass leaderboard and leaderboard group creation and manipulation, including score corrections. In a replica these operations must be executed in the same sequence they were executed in on the master. It probably encompasses the creation of a tracked race and its course, we well as the updates to a race course with waypoint additions and mark passings because mark passing events relate to the waypoints. Paralllel operations -These encompass the receiving of raw sensor data. The order in which this data is re-played doesn't matter. The data is time-stamped, and its history is not rewritten (as maybe the case for the mark passings or for the race tracking start/finish times). Applying these operations to a replica is idempotent, so serializing an initial state doesn't need to be synchronized with the receiving of such data. Duplicate application of these operations to a server doesn't do any harm. - -### Replication in SAP Cube -This section will describe, how a working local replica can be set up at the SAP Cube. -The setup at the Travemünder Woche 2014 was done by Steffen Wagner as it follows: - -LTE router --> gigabit switch --> cube computers (dhcp) && local replica machine (with e.g. Ubuntu 14.04 LTS & a version of the analytics suite installed) - -Steps to get everything working: -* The LTE router (192.168.1.1) should have DHCP and NS activated. Dhcp was within range XXX.XXX.1.2 - XXX.XXX.1.49 and our local replica machine had a static ip address of XXX.XXX.1.50. Please make sure, that the ip address is outside of the dhcp range. For a better setup we should think of using the local replica server for dhcp and ns services. -* Setup networking (to your needs) on the local replica machine as follows: -/etc/networking/interfaces -
-auto eth0
-iface eth0 inet static
-	address 192.168.1.50
-	netmask 255.255.255.0
-	gateway 192.168.1.1
-
-/etc/hosts -
-127.0.0.1		localhost
-127.0.0.1		tw2014 tw2014.sapsailing.com
-192.168.1.50	tw2014 tw2014.sapsailing.com
-
-* For rewriting the URLs and Ports to the fitting Spectator URL when entering the URL we need a Apache installed (apt-get install apache2) and set up like on our sapsailing.com server. Please check 000-macros.conf, 001-events.conf and 000-main.conf and set it up as you need for the event. They need to be in the config dir of apache (/etc/apache2/mods-enabled and /etc/apache2/sites-enabled) -* For better remote administration of the local replica, set up and autossh tunnel to trac@sapsailing.com and put it into rc.local for autostarting, e.g. as the following: -
-autossh -M 20009 -f -N -L 1337:127.0.0.1:22 -i SSHKEY trac@sapsailing.com
-
-* Set up automatic replication in env.sh of the analystics server and start the server (not with root!) with startscript ~/servers/server/start -* On all Cube computers add the folling batch file to the windows task scheduling for a check every minute. The script switches hosts to local replica if available. Before the script can work, you have to edit host file manully and add the required hostnames: e.g. 192.168.1.50 tw2014.sapsailing.com. You also need to replace find and replace to the required ip addresses. -
-@echo off
-@setlocal enableextensions enabledelayedexpansion
-
-REM Set a variable for the Windows hosts file location
-set "hostpath=%systemroot%\system32\drivers\etc"
-set "hostfile=hosts"
-
-REM Make the hosts file writable
-attrib -r -s -h "%hostpath%\%hostfile%"
-
-ping -n 1 -w 1000 -l 2000 192.168.1.50
-REM if local replica is available
-IF %ERRORLEVEL% == 0 (
-	REM set the string you wish to find
-	set find=54.229.94.254
-	REM set the string you wish to replace with
-	set replace=192.168.1.50
-
-REM if local replica is not available
-) ELSE (
-	REM set ring you wish to find
-	set find=192.168.1.50
-	REM set the string you wish to replace with
-	set replace=54.229.94.254
-)	
-
-for /f "delims=" %%a in ('type "%hostpath%\%hostfile%"') do (
-set "string=%%a"
-set "string=!string:%find%=%replace%!"
->> "newfile.txt" echo !string!
-)
-
-move /y "newfile.txt" "%hostpath%\%hostfile%"
-REM Make the hosts file un-writable - not necessary.
-REM attrib +r "%hostpath%\%hostfile%"
-echo hostfile done.
-
-* If server should be shutted down, then first stop replication manually in the backend and then call ~/servers/server/stop. If everything works fine, then the Cube computers should recognize, that the replica machine is not available anymore and then switch to the real sapsailing host. +These encompass the receiving of raw sensor data. The order in which this data is re-played doesn't matter. The data is time-stamped, and its history is not rewritten (as maybe the case for the mark passings or for the race tracking start/finish times). Applying these operations to a replica is idempotent, so serializing an initial state doesn't need to be synchronized with the receiving of such data. Duplicate application of these operations to a server doesn't do any harm. \ No newline at end of file From 836323863e3c00ca1b0aeaf809b6d2ad7d0d0a4b Mon Sep 17 00:00:00 2001 From: Steffen Tobias Wagner Date: Thu, 24 Jul 2014 11:33:00 +0000 Subject: [PATCH 248/323] Added section for setting up replication for SAP Cube --- wiki/server-replication.md | 252 +++++++++++++++++++++++-------------- 1 file changed, 159 insertions(+), 93 deletions(-) diff --git a/wiki/server-replication.md b/wiki/server-replication.md index 148ae51fbe3..9b80a143f05 100644 --- a/wiki/server-replication.md +++ b/wiki/server-replication.md @@ -1,93 +1,159 @@ -# Replicating an SAP Sailing Analytics Server - -[[_TOC_]] - -### Requirements -Scaling to many (>100) concurrent users following a live sailing race requires providing more than one OSGi server instance. To an extent this is cause by an aspect of our architecture that decides how clients access server data. We use the Google Web Toolkit's (GWT) Remote Procedure Call (RPC) to let the clients request data from the server. These are handled as HTTP POST requests with a servlet composing the response message. This approach makes caching servlet responses to similar requests by a generic HTTP caching / proxy infrastructure difficult. - -Using more than one server instance is additionally motivated by the ManyPlayers client architecture. ManyPlayers wants to use our code base but run it on their premises and only be assured that once their instance connects to our "master" instance then all relevant state will be replicated to their instance. - -### Server State -The server's state at the current time roughly consists of the following: - -* list of races with their tracking data loaded by the server -* a race's state consists of - * RaceDefinition object with name, boat class and event association - * Course object with Waypoints and ControlPoints -* a tracked race's state consisting of - * wind tracks - * raw boat position tracks - * mark position tracks - * mark passings - * race tracking start/finish times -* list of leaderboard groups with their names and descriptions -* list of leaderboards and their association with leaderboard groups - * leaderboard names, columns and the columns' associated tracked races, if any - * discarding rules - * competitor renamings - * explicit score corrections - * disqualification / "max-points" reasons (DSQ, OCS, DNS, DNF, DND, ...) - -### Reliability -When a server is configured to be a replica of another "master" server, all updates to the master server must be replicated as quickly as possible to the replica until the replica permanently disconnects from the master server. If the connection breaks temporarily and is restored at a later point in time, all updates that occurred since the connection broke need to be updated to the client. - -### Connectivity -Replicated instances may be able to only connect to the master through an HTTP connection. In this case, the master server needs to push updates through an HTTP connection reliably. - -### Single Master, Multiple Replicas -For Kiel Week 2012 we want to support a single master server with multiple replicated copies, so-called "replica instances." The replicas are configured for their master node. This configuration doesn't change over the replica's life time. If the master dies, it need to be started again. Replicas then need to recover and re-sync once the new master instance is alive again. - -### Concurrency Challenges -Different types of updates to the master server can happen concurrently. Tracking data for multiple races is being received in multiple threads which can execute truly concurrently on a multi-core architecture. Wind data may be received in parallel to receiving other tracking data. Leaderboard and LeaderboardGroup configurations may happen concurrently to all other types of changes. New races may start to get tracked concurrently to the many other server operations. Do we need to ensure that all these events whose effects need to be replicated have to be serialized in an order that is kept the same for all replica to ensure equal resulting states in all replica? What would serializing all these many concurrent events mean to the server's internal concurrency which is important to improve performance? - -### Thoughts on Implementation -HTTP push protocols are implemented, e.g., by the Atmosphere framework and by our own, proprietary AbstractHttpPostServlet in the com.sap.sailing.server package. They both use a heartbeat mechanism to re-establish the HTTP connection after it got disconnected. However, both currently don't handle reliable data transmission. If a client is disconnected and re-connects later, it may have missed a number or messages sent while it was disconnected. This would need to be overcome in order to achieve the reliabiliy required for our replication scenario. - -### Initial Load -When a replica is first set up and connected to a master server, the master server's current state at some consistent snapshot needs to be replicated to the replica and from then on be kept in sync. How can we produce such a consistent snapshot while understanding which subsequent change events to broadcast to which client? - -One approach we're currently testing is using Java Serialization to copy an initial "image" of one server to the next. There are several tricky issues with this approach: - -* listeners / observers: several classes offer listeners to be attached; for example, a DynamicTrackedRaceImpl object supports RaceChangeListener objects to attach to it. Similarly, Course objects support CourseListeners to observe the course for changes. Currently, our strategy is to serialize the listener graph together with the object graph they observe by declaring the listener interfaces as "extends Serializable." This unfortunately requires all other listener implementations, even the inner anonymous classes used by test cases, to be serializable too. On the other hand this avoids NotSerializableExceptions. -* non-serializable JDK classes: For example, TrackedRaceImpl uses java.util.Timer to delay cache invalidation. In this fortunate case, making the field transient has no negative effects because the next action that would need the cache to be invalidated will set the timer correctly, so no explicit initialization in readObject() is required. There may be other cases, though, where a readObject() implementation is needed to establish some objects in transient fields after deserialization. -* synchronization: serializing a data structure while it is being modified is a bad idea. The java.util.* collection classes are not thread safe. In those cases where concurrent modifications have to be expected, a writeObject() definition is required that synchronizes the serialization with modifying operations. For example, serializing a TrackedRace object together with its GPSFixTracks and WindTracks needs to synchronize with inbound GPSFix / Wind updates. - -### Incremental Updates -Many updates to the server's state are small compared to the overall state. For example, adding a GPS fix to a boat's track is a very small change; so is the modification of a leaderboard name or a leaderboard group's description. Such events need to be replicated incrementally. A message queuing system with persistent message queues may be an implementation option that can guarantee delivery also through HTTP connections. - -### Implications for the MongoDB Store -A replica may or may not use the same MongoDB store that the master uses. If it uses the same store, replication events must not update the database. This then also holds for the initial load procedure. On the other hand, if the replica uses its own database instance, even the initial load has to update all structures that require persistent storage into the local database. - -Using MongoDB's built-in replication requires a robust TCP connection between the MongoDB replicas. This is hard to establish in case only HTTP connections are possible between the replica and the master. - -### Use of a Message Queuing System -An MQ system such as ActiveMQ could serve as basis for the implementation of the replication mechanism. We'd have to wrap all commands in messages. To be clarified: what happens when a client subscribes to a topic? Will it receive messages sent to that topic only after it connected? Or will it also receive older messages that have been queued persistently? - -### Design -Changes applied to a server that are relevant for replication have to be applied through a single entry point at RacingEventService, using the command pattern provided by the RacingEventServiceOperation interface. This will allow future extensions towards the use of an Operation Transformation (OT) algorithm in case operations can be performed not only on a single master but throughout the replicas. In addition to the course-grained leaderboard / group creations and editing operations, all tracking data recordings need to pass through this interface in order to be replicated properly. - -### RacingEventServiceOperations and RacingEventService -Most of the state changes relevant for replication are triggered through the RacingEventService interface. A few changes, such as the recording of GPS and wind data received, are currently immediately updated into internal data structures, such as a DynamicTrackedRace and the track objects it manages, or the Course object owned by a RaceDefinition object. It would be possible to restructure things in such a way that also the tracking data are funneled through the RacingEventService interface. - -A RacingEventServiceOperation describes serializable operations that can be applied to a RacingEventService and which can be transmitted to another server instance (a replica) to be applied to another RacingEventService, eventually leading to equal states of the RacingEventService instances. - -Question: Where and how do we ensure that all modifications to a RacingEventService that are relevant for replication are actually turned into RacingEventServiceOperations and then reliably serialized to all replicas? - -We could let RacingEventServiceImpl manage the creation of the operation objects whenever a changing method is called. This would require distinguishing between an "external" and an "internal" API where the external API methods create the operations which, when applied, use the internal API methods to perform the actual updates. - -Or we restrict the externally visible API of RacingEventService to applying RacingEventServiceOperations. Then, all modifications that are relevant for replication have to go through a RacingEventServiceOperation. This is very hard to ensure because internally all APIs, such as those of Leaderboard and LeaderboardGroup and everything are easily accessible. - -Answer: We create the operations already in SailingServiceImpl and only call apply on the RacingEventService whose implementation can then notify an optional replicator of the operations that were executed. At this time it would make sense to remove the fine-grained operations which are now only used by the operation implementations from the RacingEventService interface at least. The operation implementations can cast the RacingEventService object to RacingEventServiceImpl and then access the operations again. - -### Parallel Processing of Operations -Many types of operations can potentially be processed in parallel. For example, GPS fixes for different tracked races can be inserted into their respective tracks in parallel. So can edit operations for different leaderboards. A thread pool can be used to process the operations. However, each thread in this pool may need to obtain the monitors for the objects it will modify, and the scheduler that sequentially passes the operations to the thread pool for processing needs to wait for the processing thread's acknowledgement that all monitors required have been obtained before triggering the processing of the next operation. The general problem reminds of the versioning graph as created, e.g., by a git repository. Also, Leslie Lamport's vector clocks come to mind. - -Some operations are more tricky to parallelize, particularly if we have to assume race conditions during execution. For example, if an operation creates a leaderboard and a follow-up operation adds a column to the leaderboard just created, the leaderboard creation must be complete before the column can be added. Parallelizing such operations is generally dangerous. - -Fortunately, the dangerous-to-parallelize operations seem to be the ones that occur with low frequency (leaderboard creation, score corrections, leaderboard column configuration) and have relaxed requirements regarding the lag with which they have to be replicated. We can therefore partition the data and transactions to be replicated into two categories - -### Sequential operations -These encompass leaderboard and leaderboard group creation and manipulation, including score corrections. In a replica these operations must be executed in the same sequence they were executed in on the master. It probably encompasses the creation of a tracked race and its course, we well as the updates to a race course with waypoint additions and mark passings because mark passing events relate to the waypoints. -Paralllel operations - -These encompass the receiving of raw sensor data. The order in which this data is re-played doesn't matter. The data is time-stamped, and its history is not rewritten (as maybe the case for the mark passings or for the race tracking start/finish times). Applying these operations to a replica is idempotent, so serializing an initial state doesn't need to be synchronized with the receiving of such data. Duplicate application of these operations to a server doesn't do any harm. \ No newline at end of file +# Replicating an SAP Sailing Analytics Server + +[[_TOC_]] + +### Requirements +Scaling to many (>100) concurrent users following a live sailing race requires providing more than one OSGi server instance. To an extent this is cause by an aspect of our architecture that decides how clients access server data. We use the Google Web Toolkit's (GWT) Remote Procedure Call (RPC) to let the clients request data from the server. These are handled as HTTP POST requests with a servlet composing the response message. This approach makes caching servlet responses to similar requests by a generic HTTP caching / proxy infrastructure difficult. + +Using more than one server instance is additionally motivated by the ManyPlayers client architecture. ManyPlayers wants to use our code base but run it on their premises and only be assured that once their instance connects to our "master" instance then all relevant state will be replicated to their instance. + +### Server State +The server's state at the current time roughly consists of the following: + +* list of races with their tracking data loaded by the server +* a race's state consists of + * RaceDefinition object with name, boat class and event association + * Course object with Waypoints and ControlPoints +* a tracked race's state consisting of + * wind tracks + * raw boat position tracks + * mark position tracks + * mark passings + * race tracking start/finish times +* list of leaderboard groups with their names and descriptions +* list of leaderboards and their association with leaderboard groups + * leaderboard names, columns and the columns' associated tracked races, if any + * discarding rules + * competitor renamings + * explicit score corrections + * disqualification / "max-points" reasons (DSQ, OCS, DNS, DNF, DND, ...) + +### Reliability +When a server is configured to be a replica of another "master" server, all updates to the master server must be replicated as quickly as possible to the replica until the replica permanently disconnects from the master server. If the connection breaks temporarily and is restored at a later point in time, all updates that occurred since the connection broke need to be updated to the client. + +### Connectivity +Replicated instances may be able to only connect to the master through an HTTP connection. In this case, the master server needs to push updates through an HTTP connection reliably. + +### Single Master, Multiple Replicas +For Kiel Week 2012 we want to support a single master server with multiple replicated copies, so-called "replica instances." The replicas are configured for their master node. This configuration doesn't change over the replica's life time. If the master dies, it need to be started again. Replicas then need to recover and re-sync once the new master instance is alive again. + +### Concurrency Challenges +Different types of updates to the master server can happen concurrently. Tracking data for multiple races is being received in multiple threads which can execute truly concurrently on a multi-core architecture. Wind data may be received in parallel to receiving other tracking data. Leaderboard and LeaderboardGroup configurations may happen concurrently to all other types of changes. New races may start to get tracked concurrently to the many other server operations. Do we need to ensure that all these events whose effects need to be replicated have to be serialized in an order that is kept the same for all replica to ensure equal resulting states in all replica? What would serializing all these many concurrent events mean to the server's internal concurrency which is important to improve performance? + +### Thoughts on Implementation +HTTP push protocols are implemented, e.g., by the Atmosphere framework and by our own, proprietary AbstractHttpPostServlet in the com.sap.sailing.server package. They both use a heartbeat mechanism to re-establish the HTTP connection after it got disconnected. However, both currently don't handle reliable data transmission. If a client is disconnected and re-connects later, it may have missed a number or messages sent while it was disconnected. This would need to be overcome in order to achieve the reliabiliy required for our replication scenario. + +### Initial Load +When a replica is first set up and connected to a master server, the master server's current state at some consistent snapshot needs to be replicated to the replica and from then on be kept in sync. How can we produce such a consistent snapshot while understanding which subsequent change events to broadcast to which client? + +One approach we're currently testing is using Java Serialization to copy an initial "image" of one server to the next. There are several tricky issues with this approach: + +* listeners / observers: several classes offer listeners to be attached; for example, a DynamicTrackedRaceImpl object supports RaceChangeListener objects to attach to it. Similarly, Course objects support CourseListeners to observe the course for changes. Currently, our strategy is to serialize the listener graph together with the object graph they observe by declaring the listener interfaces as "extends Serializable." This unfortunately requires all other listener implementations, even the inner anonymous classes used by test cases, to be serializable too. On the other hand this avoids NotSerializableExceptions. +* non-serializable JDK classes: For example, TrackedRaceImpl uses java.util.Timer to delay cache invalidation. In this fortunate case, making the field transient has no negative effects because the next action that would need the cache to be invalidated will set the timer correctly, so no explicit initialization in readObject() is required. There may be other cases, though, where a readObject() implementation is needed to establish some objects in transient fields after deserialization. +* synchronization: serializing a data structure while it is being modified is a bad idea. The java.util.* collection classes are not thread safe. In those cases where concurrent modifications have to be expected, a writeObject() definition is required that synchronizes the serialization with modifying operations. For example, serializing a TrackedRace object together with its GPSFixTracks and WindTracks needs to synchronize with inbound GPSFix / Wind updates. + +### Incremental Updates +Many updates to the server's state are small compared to the overall state. For example, adding a GPS fix to a boat's track is a very small change; so is the modification of a leaderboard name or a leaderboard group's description. Such events need to be replicated incrementally. A message queuing system with persistent message queues may be an implementation option that can guarantee delivery also through HTTP connections. + +### Implications for the MongoDB Store +A replica may or may not use the same MongoDB store that the master uses. If it uses the same store, replication events must not update the database. This then also holds for the initial load procedure. On the other hand, if the replica uses its own database instance, even the initial load has to update all structures that require persistent storage into the local database. + +Using MongoDB's built-in replication requires a robust TCP connection between the MongoDB replicas. This is hard to establish in case only HTTP connections are possible between the replica and the master. + +### Use of a Message Queuing System +An MQ system such as ActiveMQ could serve as basis for the implementation of the replication mechanism. We'd have to wrap all commands in messages. To be clarified: what happens when a client subscribes to a topic? Will it receive messages sent to that topic only after it connected? Or will it also receive older messages that have been queued persistently? + +### Design +Changes applied to a server that are relevant for replication have to be applied through a single entry point at RacingEventService, using the command pattern provided by the RacingEventServiceOperation interface. This will allow future extensions towards the use of an Operation Transformation (OT) algorithm in case operations can be performed not only on a single master but throughout the replicas. In addition to the course-grained leaderboard / group creations and editing operations, all tracking data recordings need to pass through this interface in order to be replicated properly. + +### RacingEventServiceOperations and RacingEventService +Most of the state changes relevant for replication are triggered through the RacingEventService interface. A few changes, such as the recording of GPS and wind data received, are currently immediately updated into internal data structures, such as a DynamicTrackedRace and the track objects it manages, or the Course object owned by a RaceDefinition object. It would be possible to restructure things in such a way that also the tracking data are funneled through the RacingEventService interface. + +A RacingEventServiceOperation describes serializable operations that can be applied to a RacingEventService and which can be transmitted to another server instance (a replica) to be applied to another RacingEventService, eventually leading to equal states of the RacingEventService instances. + +Question: Where and how do we ensure that all modifications to a RacingEventService that are relevant for replication are actually turned into RacingEventServiceOperations and then reliably serialized to all replicas? + +We could let RacingEventServiceImpl manage the creation of the operation objects whenever a changing method is called. This would require distinguishing between an "external" and an "internal" API where the external API methods create the operations which, when applied, use the internal API methods to perform the actual updates. + +Or we restrict the externally visible API of RacingEventService to applying RacingEventServiceOperations. Then, all modifications that are relevant for replication have to go through a RacingEventServiceOperation. This is very hard to ensure because internally all APIs, such as those of Leaderboard and LeaderboardGroup and everything are easily accessible. + +Answer: We create the operations already in SailingServiceImpl and only call apply on the RacingEventService whose implementation can then notify an optional replicator of the operations that were executed. At this time it would make sense to remove the fine-grained operations which are now only used by the operation implementations from the RacingEventService interface at least. The operation implementations can cast the RacingEventService object to RacingEventServiceImpl and then access the operations again. + +### Parallel Processing of Operations +Many types of operations can potentially be processed in parallel. For example, GPS fixes for different tracked races can be inserted into their respective tracks in parallel. So can edit operations for different leaderboards. A thread pool can be used to process the operations. However, each thread in this pool may need to obtain the monitors for the objects it will modify, and the scheduler that sequentially passes the operations to the thread pool for processing needs to wait for the processing thread's acknowledgement that all monitors required have been obtained before triggering the processing of the next operation. The general problem reminds of the versioning graph as created, e.g., by a git repository. Also, Leslie Lamport's vector clocks come to mind. + +Some operations are more tricky to parallelize, particularly if we have to assume race conditions during execution. For example, if an operation creates a leaderboard and a follow-up operation adds a column to the leaderboard just created, the leaderboard creation must be complete before the column can be added. Parallelizing such operations is generally dangerous. + +Fortunately, the dangerous-to-parallelize operations seem to be the ones that occur with low frequency (leaderboard creation, score corrections, leaderboard column configuration) and have relaxed requirements regarding the lag with which they have to be replicated. We can therefore partition the data and transactions to be replicated into two categories + +### Sequential operations +These encompass leaderboard and leaderboard group creation and manipulation, including score corrections. In a replica these operations must be executed in the same sequence they were executed in on the master. It probably encompasses the creation of a tracked race and its course, we well as the updates to a race course with waypoint additions and mark passings because mark passing events relate to the waypoints. +Paralllel operations + +These encompass the receiving of raw sensor data. The order in which this data is re-played doesn't matter. The data is time-stamped, and its history is not rewritten (as maybe the case for the mark passings or for the race tracking start/finish times). Applying these operations to a replica is idempotent, so serializing an initial state doesn't need to be synchronized with the receiving of such data. Duplicate application of these operations to a server doesn't do any harm. + + +### Replication in SAP Cube +This section will describe, how a working local replica can be set up at the SAP Cube. +The setup at the Travemünder Woche 2014 was done by Steffen Wagner as it follows: + +LTE router --> gigabit switch --> cube computers (dhcp) && local replica machine (with e.g. Ubuntu 14.04 LTS & a version of the analytics suite installed) + +Steps to get it working: +* The LTE router ( _192.168.1.1_ ) should have DHCP and NS activated. Dhcp was within range _XXX.XXX.1.2 - XXX.XXX.1.49_ and our local replica machine had a static ip address of _XXX.XXX.1.50_. Please make sure, that the ip address is outside of the dhcp range. For a better setup we should think of using the local replica server for dhcp and ns services. +* Setup networking (to your needs) on the local replica machine as follows: + +**/etc/networking/interfaces** +
+auto eth0
+iface eth0 inet static
+	address 192.168.1.50
+	netmask 255.255.255.0
+	gateway 192.168.1.1
+
+**/etc/hosts** +
+127.0.0.1	localhost
+127.0.0.1	tw2014 tw2014.sapsailing.com
+192.168.1.50	tw2014 tw2014.sapsailing.com
+
+* For rewriting the URLs and Ports to the fitting Spectator URL when entering the URL we need a Apache installed (apt-get install apache2) and set up like on our sapsailing.com server. Please check 000-macros.conf, 001-events.conf and 000-main.conf and set it up as you need for the event. They need to be in the config dir of apache (/etc/apache2/mods-enabled and /etc/apache2/sites-enabled) +* For better remote administration of the local replica, set up and autossh tunnel to trac@sapsailing.com and put it into rc.local for autostarting, e.g. as the following: +
+autossh -M 20009 -f -N -L 1337:127.0.0.1:22 -i SSHKEY trac@sapsailing.com
+
+* Set up automatic replication in env.sh of the analystics server and start the server (not with root!) with startscript ~/servers/server/start +* On all Cube computers add the folling batch file to the windows task scheduling for a check every minute. The script switches hosts to local replica if available. Before the script can work, you have to edit host file manully and add the required hostnames: e.g. 192.168.1.50 tw2014.sapsailing.com. You also need to replace find and replace to the required ip addresses. Please note that all this would not be needed, if we would use the replica server also for dhcp and ns, so it makes really sense to set this up. +
+@echo off
+@setlocal enableextensions enabledelayedexpansion
+REM Set a variable for the Windows hosts file location
+set "hostpath=%systemroot%\system32\drivers\etc"
+set "hostfile=hosts"
+REM Make the hosts file writable
+attrib -r -s -h "%hostpath%\%hostfile%"
+ping -n 1 -w 1000 -l 2000 192.168.1.50
+REM if local replica is available
+IF %ERRORLEVEL% == 0 (
+   REM set the string you wish to find
+   set find=54.229.94.254
+   REM set the string you wish to replace with
+   set replace=192.168.1.50
+REM if local replica is not available
+) ELSE (
+   REM set ring you wish to find
+   set find=192.168.1.50
+   REM set the string you wish to replace with
+   set replace=54.229.94.254
+)	
+for /f "delims=" %%a in ('type "%hostpath%\%hostfile%"') do (
+set "string=%%a"
+set "string=!string:%find%=%replace%!"
+>> "newfile.txt" echo !string!
+)
+move /y "newfile.txt" "%hostpath%\%hostfile%"
+REM Make the hosts file un-writable - not necessary.
+REM attrib +r "%hostpath%\%hostfile%"
+echo hostfile done.
+
+* If server should be shut down, then first stop replication manually in the backend and then call ~/servers/server/stop. If everything works fine, then the Cube computers should recognize, that the replica machine is not available anymore and then switch to the real sapsailing host. \ No newline at end of file From 22c457e5b2bd256e7202b15b1546001172e7c731 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 24 Jul 2014 16:43:54 +0200 Subject: [PATCH 249/323] upgraded to TracAPI 3.0.2; supposed to eliminate caching problems of objects coming from the clientparams.php file, such as static mark positions --- .../META-INF/MANIFEST.MF | 2 +- java/com.tractrac.clientmodule/README.txt | 17 +++++++++++++++++ .../com.tractrac.clientmodule/lib/TracAPI.jar | Bin 813796 -> 748755 bytes java/com.tractrac.clientmodule/pom.xml | 2 +- .../app/tracapi/EventListener.java | 0 .../subscription/app/tracapi/Main.java | 0 6 files changed, 19 insertions(+), 2 deletions(-) mode change 100755 => 100644 java/com.tractrac.clientmodule/README.txt mode change 100755 => 100644 java/com.tractrac.clientmodule/lib/TracAPI.jar mode change 100755 => 100644 java/com.tractrac.clientmodule/src/com/tractrac/subscription/app/tracapi/EventListener.java mode change 100755 => 100644 java/com.tractrac.clientmodule/src/com/tractrac/subscription/app/tracapi/Main.java diff --git a/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF b/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF index c9187b45927..aedba8362ad 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.1 +Bundle-Version: 3.0.2 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 old mode 100755 new mode 100644 index a317506cdb7..98d24e49111 --- a/java/com.tractrac.clientmodule/README.txt +++ b/java/com.tractrac.clientmodule/README.txt @@ -16,6 +16,23 @@ 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.2 **************** +******************************************** +This is a final version. Only fixes bugs in the implementation + +1) New features + + - The IRacesListener.updateRace() event is thrown when the live delay for a race has changed. The IRace.getLiveDelay() method + returns the new value. + +2) Bugs + + - 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. + ******************************************** ************* TracAPI 3.0.1 **************** ******************************************** diff --git a/java/com.tractrac.clientmodule/lib/TracAPI.jar b/java/com.tractrac.clientmodule/lib/TracAPI.jar old mode 100755 new mode 100644 index f1ad42e204cfbfa5ee711656f4c545e68e7a2321..f9ddc9677453b0d85b8dce00d47c256e3e5185f3 GIT binary patch delta 82926 zcmZ_#1yqz>_dgCZLo;-jAl*`u5>g`F-Q5V%Intd2jC4tebU1{xNQpE`C@I~I#ebOb ze(vw{yua7AShKEucAtIr{+w&ZjamNbc^*7X6;w1*6f7((6jqn(G(2(CyANeSu(1Mn zkd8(cCn)~2KyLn=qc9EYeA(|SHGN}~h42{K%@fJfF^s%`Uc&DHj+b*}YvxH}CL-Ug zBOb`As%%tY=~lG<@o{+>ufqHcvR2 zv^Cw5^wxOan~4+blaFcmOCUNyR#;?eb4C=P-F9nX>u#w{i+WpnySC>fYWw zeL~7GZ`0L$hEwSwgCE-Dl=-;uryVY3t}gWK=VNDhuSTkOpv#-*Tjza2*h6nHxx~-i zVJd#$BjDlb+8WPqmp?oBZ2A&JQ{oT2UaGN)u8v^rv1NKu7Wg0A4b0 zJlWHJ|CAe_LiB8Nl&2Js;FY?+9GtZC>9EZT+)TgL0TkDmw`SsA?G&_j>39!QsejL5 zeIK&{J^V0C*OJF#9rPf9WBw<$qVE$?X*bmO;k~<8f{}^#)r!4pGP(!e2ii|!Oo48w zQ>)XFHAz)MtoIG{c1hN66W{8m__JmPapcuMNtl0BJ;GAD*mb|z1Ty~)XT?-0uJQbZ z2;>t9AJ{gk9)Hg?XeCdDTW7ZXThKWH32S`ySV(wOC0_?dv+N6jl*D+(zRTDzrqV(* z8wJk-hy^9e-&!=F$~7u338uW`W2k7g=@J?Oj#C5Pay+*2Pt>)@n0;!1?iZ|AWu_f( z$H-}R2pUbm;DTfui))3A*V1FOua|+DNhCyI*Wmi7TcUdj=`Yq?Bm2Z?zR(sF-2-`- zDcls+i@q^@611}P2mXsQXZ>trk zjttG_zF2?R=Em>y8VBW8yiJK$(-X8)^?6s?CuA-1?geuRLF^UmXW3EfkB+b1;h01H zl0*SuJf0*#n|o^%_vO=C_p*H`U)i_&f|51_^c(S8_Xn|!j^@(H7rFNL!HaaO|vdwbF zCPr%nAsdXm%I@Y%v{=^Hutei)Czj49558PPV#xdaX;Ik19#bO*C+B<_rRsCvP>vB~npL7mg+b@n>F%Nrns@Csk;S0$j~p{kLtj$Qnd@Jh zXG#Zy7DwcRD!;$7;fAv2>kmv?whoJY3HM(xuG=(%^eMQ}P*C^?|F_#DkUNp$ z53}nXbB<~8325;>d@CT(BJfZf%AkTy4p@VRJ;kJkh*8|5l8|)&?1h4Y5&-}EuZ9IJ z%EY3Btb}4ivM7)}1>UjX&v=lPzt6W6s&@?tIw_CF)f7O9fl35_l85X0&p%>l7!!yL zQbvPR@SeuxZ^sgEGNp9_BHGvxo%Mfv+9=RaFCeTBwR(yv4)Ld_qI%)aRPPVh83h0P zFBc&mz&nqKAoKJ{0{}r@sO3WtI)wW^f&l$Af{Ea`qf$cB?^hs9dL|**FrXt3LF7#_ z3@U*CB13SPdhZBLpP0%JKHhcW|ENKSx{87bn%Y<^k!zNkfd8|`Cj!ER6mlS~6M_&K zBtrNEg)d<>{ouev7(*ltYAFmNY?9{UL6T0|{jcL2X> zlBezek8|WrpZVC3$pr{%B0~Gb#0x1(!iFqo;5X$6<|76PG|vtD&U5l6_ec1M83UaZ zL%*XiHg(G=p&|_b<)Mb+KL%kmDJf_p$PmvYQK+;u2m?AU4#I2NdD?~4xQYw9lc$Cx z>k~JfYg(WqbR6d+>`*r)>z5-b_kSV6p>BF+I*N>4vlX2#OBO`atVaJ*4t-+&i-)|aI*|=oE*N^_N1{hMc(f;; z(X19j?x?_%`?fOjE4pdPOWCq89b(V*FQvLfPT1MXk?Td9lLpV<{5QmC|`i z{sH{?blZDWuzvE>o^^)_9a?bqZNuv*^4REkN_Xf@eys4sjqq-n0ZX7-$!C^8 zxe^f;amGm#;!d8)N5tSxuE`+c&Sho#X01mezfz^8ol8@8D&-43MBl#1n(~wi8L1!% z7N1uxY?Y=%xW9vlW`}7<>4EF z(g%~vnsj?@B?(#@rYa5$3du%l8q`WW5FKC7vX7O1UE1U9^VK;k7zvaV7^o^><^wrIu4#0$p8u72{ONWBGR&x#@ztpl&JOT64Zu_q#Jyh^+@`m55D zM&7zifeG);jMl&?Hwx&Ckx$aA3s3M(D`5g*0@pQAD;ao3A-J6=kz-Gm2YpF#5zl z??mC?ttEWqIe+I~>EyVk%qbPW{^TtAtVCC`^7Si$4DEiwR&)@<#m}_%Ry7R4P{XqjRy(kxE3FWtBNB`Q!Mw z9Zl)puIp}7TdtP0$jSQsut@QZUCY68a3$vSN94cDC$v_^#&8#;q1h468c`JLlC zAgR9Pu7e{V)in%R`t#Uj87r2;jc5f`gts)mY)INescO`~WJ^0Q4Fo{J?^)Hxmvp=d z-p60cf6QwLk>L&-78SM0gq4i3$?=(f<(mIQTH6}7Vd-4gwwy?&=D&Gh! zvbVBW$a`IG1NGTEF&gDCjFxnx>e3Y)y@h0h+0(sQ4N2TtI!zu;#n#F^p;5cTGO?{sfu>i8(eArYz>& zHjRvCrz=J))Rn^1$(yXX-5T;-^IC0*>(uX++d7dI{nGD-dPWc$${Tr8!qUE}t2EeD ze$7fAc<8$q=PamXUQ^eOSv#fQOnZdV`Ad<~i{31TCX}kXb|-KmX~tsx`wAJb4y}&Z zU+9`O4E!v*8xV%s>w?yW9{i>U7WNs37PXGVU(!FKho(0d?Hlel#^ZyPM`&Fn!O$L) z-bJ)7n&6b4tMDTdL%{m^NgGt;#jS4tmd7$o2pG;b{i z!D5LZ%4N&on3M0frsIPI&*c={tB2Ic+S(Xlx*K!x+D{v{E7nps=Do{!y#aXE|#LW9f^I>4TaqcG_ zo%JYV;oeDFl6Q*eM`JS+p7gaIue|V}zdt_@I9xXE^5=o>)qX$i0Q+(jTh2Z?uN*!q zc0GIMcJpDIlV_RKtR_U`E5Og|5OCG}X~)3vW^-GaXL-%!f%}P&z-R6?YWJ?ltA~PT zI_bZ$I?{$ji8)rN^TIdny0uPpNhGVh(rAW;BHtmKpwpsK#ah@^PC9kCJQ2KQY>>r@{P+P-~J*qV@HP4K#BT~1Z$JZ zU@#IO!RI_eN8<=5Qt}Q0A^d2v8TbAds{LcDiEz>i0URLzNJ8rS2%FkJMz;ELG?urb&3y7c)K|NI9fOcZ<4Khn` z9LxpnQGxG5+rcU#<&b|kn?9a?`pbj?XF7fjz=VvSzeU(A2!_`P-yb{#75Y&X1cJtJ z14$rXF0v8ph#G>gHx*ubAj?LJ8o<>g`3LJ?2NCk4Y9RcUFu>)13~|HCfdCi+&cWc3 z9U%Gdr0?8BhrPfC6eG815gQpJOcMn7gMVih24{;SKsW#|b}neDH3$nz$O-5`knWO# zu=z+6gb%~y0!SluBUtaEg9h;c5WpP6D)!cExsw4qL$kHyVfp5tj4Z5NO!hprd0s@e! zLKHKCSwtD2^gjyjnDJo)ssK#n!he{d(z+lT7@Y<{`fn~Z0S0$*!%P0J89rSA0qnOX z;1@y?4%Pp<1gGO55{|e@AYFpfFrkZjAW~SO0Ra8)A^RsV=wp2lEo{T|uO=w``21@E z7uu!|_mJNb!0j~AaG`MqAUv3W2S5Rd;Qw(73U>e(*6szc zM@9qhRa{WG5^Si3A&3y>-~$jux_noONOkpwAZ*xzKVTji-d%YhlDHcL03dnqE`&&4 z_|6c*1i%2&|4s#T+z3Pg%LoJ9M_RqR8U0593o304qK4r`0Hl!lfS2cApHdS5SkU-b z0P>`|`odcv+%z2&WCCJ>VZ{Nokb8A^&-u>+8LTD&07PDAG&L~jU~q|9Bp;#=A{2z0 zg2-Su8Gu-%#Z_Wt04&hK9KbFjo&V@$fx4Q3NTKkNKa8ON>jYc}0dyO_2ouf+Ag}Mx zI}5O$LcrhA@Q)HfsB{V72SWbt#K9a&0VYTf0kSy%Iq+k546vRmz%8<`cZ&2 z;_*Ly3Hlg*tYIDFfMnz*rClOACEnd}r-7zV0&4#azVWfNLQxL@pAZ87nm@LKo2NbmFd_vI+mBGLV-KQ-rJevJkadAL zI`9Q*80IHn&*oPGTCt7OQBqLA^nNhd{qA&$kASTl6zbrAFz%FD||4xBGZeS1XU9M8y2Fl?np4GIM5uK5G16!V^i}4 z-G?bk0L2gs@Zh*$wUR&!WG?R-!=1MKP<7eA+7MyFNdnM<=kUSSFAL;Gs+_n&1wmr_e}O6Of4~$zbmA!xhYEc^ z^zi=)q#(BxTu?p@AUc#u3F#la%OEH?P`|$vC;~6>=>ut?<9|u8LnR<7vUed*9efcc zr2!QFdqnR_11`V}ZPWf6^|A?2AG+)dKMX=Tzy;)n-O*r<`asctAED;IP%+;c0hbUY z7@Xtdzl$(5i@ya0)3gH8ArG>rEl>oRYdEtk?7$IdgLDF6u`Up9vECK<2XWM34L-nr z3Pfh%$>xK;umZBe#*%>6NQr-F#89kY_ykW)1Csu$3idk#sPcb&DTV^ukgLU{s6x;e zP#^=;G7A+G)}9O8M@|KV2P>&?8b&G50VxXy17c9;Q25;HDg$039Y@sCU4CK5Rlph~ z9~}OeA{+&L0%}9GYJsF+_ShHh_zY?9(Pz*T<74I9)<%FMjA$vT^Ya)_VzPbg$rVT- zFW;N8e#m3pY#fO%)G5O<&Qz|lmfElw8vI3(MRpp9fi15Ebe+9C`Ejuq+U*Q*z z5HPdyoY1A-;NegCX);-CVg~OblZQVQrb%RZ%`+w_-s96^F~mkNfNA@z_(M1-e)}7F za1f_6Y%npfJ3r7~DWOW`qm(6z4u4>n1yui;BUE?`Rn=GDH5)Xb@COY!Moi1h9SfJ`vi{pa8JsqJ7$1BGi5sha{p9o1y z(o6B#8oDBqZ~+cao4})0l+^T$kd34X%Sgq0LibwQg!*b6^ESucXL?k8RSQxIE;1K< zSP{1BZF{mtUb)fx;gk*o49Q-@4e%UWxtG1l9H*Nuu7$H!UbQV5Ulw;mI?1bPi4s98 zi0dx26YmB}%%P}xGH>5k=KqvmTl~mC@U0F0)inC~s}ef9p?GlYD2A@tdUSvN!7bhA z4fjN!DJrR&#A3csc`B_ED#hti{uq;ZhszfOdeh4Mx*w$;HwbxAYo@SK?>!G{;L4_= zT-kW0P@o&}l4jVC+N{yiNTAI(UF>{SO=5v*3Q=A}OPUE>GBIHE(hCb)VcZex_LTc1+1)A=~pnbn8=F~&#yaWqB^ z69lOCG4pse@pdv@*&qh!D=)(bN(POpl%a44%{*1Pva1^;m7=rYhCgk z?>cH8fvn^5@_w9lso}m*jr}~0D~>&UX2@*#7P2M! z^y1^Tmpm!YEWxhNy%eUss~ipUR0QNVX@1~x(Gwo&#@NSDexLdY!Be=gCMxxI^@?!U zx~*qC-6l?bQ^^TCXx!z|(_J=(7BSv?$?~?nDy!HI`+e4+{nM}94?0M{HwE{KC2DIP z$X;n%Q*R`1eSb)0R*6j!`g6ya=s5lXDpN3O+RH668agU4x;bI1QrJ=e3;O+8+L$Xy zbEtdeVTZMW?VqUT;N)G7>?_TYT&;J+rn=*HO<0VK&5)OrNfb9QseJojG`=9a1r zdU(G2)k}cw^t9p8HTGcV2%sIhs*ZDXGCt(_zBAmkcfhl7s0Q00it(uCj907{&qcN% zy(E=4eu-BRoJXQ+H`UJiv8SF>fTH9L6M^Y*fnj{{^N?hcKuzVk-^78pnT%Z07BdPj z0y&f1H3rh92!hWoVz4|jrz^uYLw7-3oOKa;)!q0O1JQd!Rjq(qjR$_{Er3{u?+%OJ|QVITOwmS7cEE!iF zBQR@pFvsU`+PV1Pw=^u=(Jih7!VSVRjiT1o_spszx;(`_qYnlG=Asg+X`o+1muw}k zGcVRe=C;E9Vx^j01?2IBi7I2T|7_J$&9^G`1T=HmN~FBSrl?0Z8AfXnRlIc@lBM9D zvFdPXT-0G@?@g&3JhP3=f5^oen&Lv?8Q_xCY%?eC;)A6+RSdu$BQ|DkR`!?abI0ai zc~Hf&-xiVg==iGw{v@jtY}}@-)5y{5D?_p`MK->-HB%B!>|2a)&oD!DgzsMy+`ZSJ zUYg_`LPtS~{r`Dw1-~8hK__Z}gwVoBIE;^M2L8PwBgVk*w?J&LiO%$6(!pDmG)t`K zM7#H@^m!EU{N>)sp}a0H(utu8@qD}EY%u+b6>w2{jUDX!# zUpzVdz}D;2@bLWd@Vw;Dlf7I&l;qVP_TJVL@)Y>Fls!ZwifIPvZq`@9SU`nCMWI>Ei)S?b65^^fqnV!C}R!bLsIr;KSI#9D!?C$j#0ym4S zgFw;;srC2w-@jRGh$WY|?Rce;YM}H+B+tcxV?m5+Xam@9>;8KcW0Nu`zn+m&+>|Od z`^GrC;J8sE%U$HWReF%=^?7uI5lcS`#v4L3#^=FwgW=pHd*JfnWX-@GP6E!Z)`PJ@ zCBqIil+f&;D=KX<^u@IsZ8Yru?Pc|?Z3g4QY&fF#U{a-`di18Zd7w{!^erD+$ZUtnYBY(-&Yq< zpNCVYzw_8}4~Z06dQd^^`u=N%g~ZvUFH(m`5ZcnVU9gIa^KxM)3GlMtR1Ew7;GyDN6rj!jur>7=cAeLCW z$n*)c;z~EZ`Jx!S47fXC%JPJ!_fSz#q%r^J#V`yGz(f$Q!=Q*GH8uoCR#;ZwHDwwf zzhGfj#d_Muv5_8yCqtoi9NW|+or?|0wzC(;`c}gJ4Yu}svq`VDNMGq@H^DA*DKl67SKy2X z?8jLX24#09y;%(9{Y)DSDpNfA0mm21DFW^ygW`KJ?VUvnYG_l(3v|7UL;>^>375V zL9F8kbM6j*ngS)mv9EA7*yl*JcO3XA${bfFG3tGp+coy9m9KA@{RL2Rr@imU^W%L& z=AHag_nb%UozYg)W8&8A&5BV5SY*|e%G0>68sb7{Z_{Y^&qBtb0hRlo-#Vs@vb8*g z&amsxP}uvjdvS04=IS7w0n=X`XlAf%Oq?!p*IjT`lF=&lHoAOC4maAFXvQaa8GxPZ zieaHp#Q3UQN2_SXscyxjWZ7PK%L3y=4w2W#S5@b2S?lO;b7=@y>!Eu5|JAqmFqLhi}m9e3Plnd%oSFA~$GOz1db!-uAJo!*tMo4r%Kzw1v=zkN-}<6Tw$ z%@2o~SC;=J6%o0RxplhuQHEN3-BPJa?E>+P$ma>jtPH65K-DojYQpPXUfSt%v05z~ zp9@mgLQYZ~XX59qUwT*E!Nat#)P;Cd&}7b{eRHh(r}=$Y#bh6`>q})gIPT20RJJ{5 ztnPVcXDzUm8Pgqx8?%8@|1O+n#MaMD^(_RFpGBmi?%-8Dq93J6x<5g&W|$np_B!S7S4NB)I1vrjdqs)OCKZ{B${;eZ#2U+;b>`~7E- znX&)JNs-H<*zP;|+wUWp#x4~#?!!2B0_)iB_q=wTUPDwB7@_10%QR4|?fV#)o(w^d zw@vJ6dAJ<2_&=VUF(geGm1P?_i*$cZuGDQX#1-SWL2=ZpH*v-q5-KG;Z=|E9o|6korHR*^S2NvPF=?LE zfqIj=)RX@*nhf({As7jeFG|}#npK#|`%uW^P?RV4Ds~}kj&WDHnzKD_Qu(9mq!CR* z*7gs^a~&qLU9pcpfOwdntbMB+V#QR{c{IOz#S&3x(#FZ|>mL`57ZN(M(f&Q8dyefjdEgUWqz5E(Qgk(wW8l7fBNowI3uRXSk zCE@H#tlCCg&pph7*XCniWG9}nv{MLC9Fy(py6Si)#2=(#>FEsMlYOPAuXipkZ!Rj? z0+KgT*A0hUjig@{7k(r9!HuzC*Ffe>fAf>#u2Gt|qNNkTp~xrD|1`?G_Gkij(;$%{ z41HVEc`3T6O!k@=%ebCrh>XZ$O$ArM#zrrlx-0#_Cv0HP&C9RmB!T{yBDK~!CisQ) zh-i>5+QUdg-uYZ;et@sNgMIhy^$)xrpCx6cD3u=~PK-$$Bov=~4 z&265}=$i1^(oB(z2j!FZo@S}!Oh!L%?f4M#`g@o_@w0FMuKw4vNDHq3Fd^1dHEzDL zXWF5?YeJBlLG`1~`?~o)I6ZEjTV!(YUPTDyR&706YnW+B{0-pm9{ZX1JYYkt7Uvc0 zMm5r;%FS}FkG9rGG%hiU$dJ5|-SB0MTamM6Z^@ttJlh< zCzsL>y>1K%QLldUqtA%LZfCC^zs`9UuIXL5teb-4TTXPKnXezR6E!wL@lMX+`)!Fm zU%T;h$x|*Ne1pPTT*$d^nUk;T`{!a`V^Fl5l>?Wpn;VBF4>{HjQ)$7Ub4K>@4ei`9 z8%7-7FDpiuo@>ZztdgF-H_2h!nid5~=B%^hS{9_YeB;uU3J;M9f4Wbi9{l(@R4^3P z{yd;M7B}~UXzo0wDJ9CF=YvM66p%d0`44r=L{Uh!c5GMST?wK+^dC2e|FpOoUWWhm zQ#g4PDC#Gm6pU;V$b@_=f*9FJ)4+Rp&DN+uY&3;f(wV0KPAF^|$P4Znc^EJ15Oel` z*3`q3T;;(7>&f262o?M)TpbRAH=GNTxGwjKF++*HtU8w0v6rzgRHSvo#$)1bIYXG% zbw7l5Jihsis?Tvf6)H65Z<{(b( z;gIOhEyJsb7CYYmM*{2MIqO3M6EyzLTB z8-Kd~9GiMsAmp%oYDPi$RS_CHyYe1xkM1qyeAF1eD##_ zmxV{0){hmd1sbE08t(BqnH&Pvq{v#9EY$pwW%}HDg3`swA%Xs=)DOCK%g$5J{4eOQ zO{9t6fBC)j61U;|W%m#3IOiBS?gH6b)3=EzPl+WfyEZDvt&@FK9(KqD7TCUSXe5|3 z{Hj=cLU$vQZI%7Of^i>gIN+HRzdFt5?Pq_`B>2TXS8F)RwxtYH;Fh)+T~i1Ks~t0|Kvs`5)N0T6uZZ z=(%~pf0Gvaz^Tcq<8dM>p$r8(OGuiUik2&()3iP#heAXRBaTR}N~2wq$l$%U#>V^;l7+NjYS)iS=Cv~Y6!huO2X z528dK1`j-*?8(Kb6=2r;nt<^fcbdgOn3n9hLAQD)SLkQxo8%+CJbQM7SR8D&v^IP)I|u)+1$q^#gLuu z-PS!Mr!i_)uZVefL83%sk1K( zj1kT(3(~0krIQg4YO-SQ7oD>ueIuCnElV%jy!c8WZ{Y)ABiP9E>YKzfUx;0n9hdh& zUPYALEzb46fW>OY%{;j0!fpe-C7^h>(YeTWmAwGwfqRBgYh>8t1A69kDm+5&zH+Zo z@&H}$k?(jPV;1f^msQE7=3^;$qiYm;0q(3!SK;CMn%%zI=aZ>nW<(F3DfC@tm~Exi z$ZF^wMB}1!ob=F*&X2fY8K=JGdi}n((LnO_P1$~>f?gaWDvSq=yPAxi8~`F)Dsg`q z4|=8Lh)pKRr{r*1ju+Sy?>4ZR7oSo=)qDdg==hi$+q>0&J%jR{2WVL_p`LW*dcS$+7{(eky#5P+nQn4|98%7ng{;cpx97H6XW~Y= zMk5DPq)X|qJl(uA@JyKOj|u752SqhINK{2M$MR)_@&8DTwOQf#MqZPqvti<`SIR7udiI%@ki4M#afDj2 zrpqt>DD)4;T^IP4!qznlzrT54{r5}&Ttnf%c@cuHyaj?_hVbvQkzbu4=0g1fkO<7J z_Lf-_ixh_xi!eMAl_0Yfi{Y)VE)KP@J z{$go>mq_Ss#zeu6>f#oz$=89^%cBE+v<51o+0Dley9ci66KPMiPxELfIi@7jTpY&p z!*u#lq8)RYw>&xHWz!tIti<#2rasdpxMoxp=aJ2+M$6w>WY zqjWiEbc{a)wd~C&FRiO?E?w#bE*aOCsV%riBCX0ag{7RHxHaD6+E0up@eX-SBde@O znjb^+TWaogsf+F)VW$4*ceJ8RVz9iKMKO_8w)*(QA^*Idac<|&LCek-8{I$)&ENZ| zxep&v)-xFYq4p{kO*r##dk#1d`$!V+>!5WMtC_5M6Td?6_x^{vVPW;1%-Uhyg0w4wSxocZLtc`3iiBgZK&s z@%=sG`>Xm05GGW*7G5XFF)~1nZAm(N(K*c8l&wmPrNoEEa(~U91Q$)-9h+gu8ef%> zva%NP6&u&pcb(2OK7Q^^OiN9R^`2IZf#hx`%9@@@%Y}1G#hmdto&KKLvx6Y8y@N=u zosq2TS!Z7wmYnrhfUao`LKGjt%2IDvanO}dL<^p!q^K;K~;Syu3YJ z$I5Kx0~aZI{z6P$Pd?Cd)zj7K9gDv!fPP(hPm_O5Qt&P=(?<}rPX5h{OXGGX4kmWu zThJBZaJaGdfa)jNmv~gwU3Dryj(R*y|M4B1!w(&HOQk)&?-V}-k%`hAK~6%^Kgpb9^y$RtbnkIg_c0d-mm+hi#dh%3+=a!(1tR5~&n`4A?JvkpO+FLh(e$=7m(=rC60SaFLPEOeR@ ztxi}9J9i?bOT?4<>Frs$2KK%U+{g!UQ9|ET(K4>6WlBZn7Ag78Ezb?jj$E)PX?!Br zV(GqB#z3Cnj1keu1@O%f?ImCLJ)uWUU}lXitt5)0mg3*Ue&SS0{@6p_QL=BT_ti~H zCkJ2JPuEw1Zhf%aU;(Df}flrH+M!4w4=pgaOkF^f98~=q(9%gFW z!fhh1fpYCA??>nCV?6W{&n;u~a>gsJ6y#PJe#|gF8Jcs5XmO@{ zh|@|l&A#;WeHPB>!fn1l+-R+-ikP4zEH}Jb@cj~}cnnD@!-SWI^~o~Vku|dcs-N_~ zq%wMHR?nhLRzn8UR25m2>a(Mr+9mBAyUTWW2z{EO6tG|)XV$9fiDpw0cKE-pNel;f z{@9dastzh;W;^Zvj%D*v4eiqeFsYbwQCK#OMfxjmv|~0AGtThfqK^f;Yf5@hHC|aV zIheB8*X)e<8}aw2rPK^EX|p^{41UpBgt_;M4ZjRFSF^@QM7_QY5;+O{)~*ivf!gkb zhc!z@dSsNY5(R(JM1F&j;mli8^>mY^_g-7o@l91UO~3S&N!~Cv&ObzZz_`4VlzRSXh%F-Y zw7Tqk@hOSRg>Bh=G{NCCd1Aa9S<n}2)1|E3BQp-tN*eSF6lUb>dB}J@XDtYHpnP_fi zNP7%x+m`ct?L(s2wvV7aV`ljr8o4L7Kcd)nM0tG5gcO9TucC!_J-?3m;pG~ZGe}#T z`?o96rKuKS7-?-~Nrl9@n`aDZ#b|`Z)Dni$5cX;Hzk1R0&}~yEewJswZYa{a_ZH0S z!etknsH@_h6k5e5Ig%nmGe$w_yKdh4mDKk3~pT;iM*~RQ<|h-Vsv^Kd&K{ zZKf^RHOqBj)!5C5&q7OJ8SSHx58at~Khc{ zdA%BQT*=mj+4DnQF1negy%I`LqHzMhwC?^KE|s7BG>UgBZD_-3dY35c5N3lXxg+Vi zGPUVss;bdbD3$bNE(PURb;>@PLu8v18Xsu9<;9%fgt387^s&_WqzlQzN0ZsTFV^z2 zhx3^f6AziKK3W&;JpnrU*42uBD}9of{T;ig2{ItGrLyMw>?SNc(n>>RSB-uK{Dyvp zr2UYRh**dIvv6{D1RoBKc*sUnBRCs7m)0tTiO5!-Sv~z`-!@yLPGc9lf4bW?BE4E|btG7F&*ODsx6aAcQd=UV zZArYMEvIZSun)C? zCL9f4r^X*aUL8xD2KNlaT&Km;ud@k+R;@W)i%B}5Lupza(@sEoK!1x5F?#+AvPS8; z9GDjAEHRwl@ngEK2VQPNPHmO+St>F#h~18V`hYWUPOG$8&@D<7<7UArx^J`rOrs+} zDx=-+$z=?q=~c>6+G+6$+WeC9kKUhES1G<5kKM_uny%9Ir}?I}%F8V5M4BD;(0IgP z6|cO!)yy=_jgg~ZVCM}>yvnhf9yL>Dm3FqS6fwSNgu+ApO#F1tC%`4ucGWjiqOYGb zzqmKtoJ5cPw8>CN@Ck4VtaV->u6e)8yWShCuTmb@Y0q3EI+D*p`kO&S?i({|ld zzg6f0U5-Kt!Rkw*kwE-4mV&RGR5`YCjbmOmUd-M#1{Yj7ch%kN^y?x% zGRj$7Chk;BK)+sHu&HjjKiO%cG(8o)p8KeCP*|wO(ebj{RhfG4y!l~(L34{AF~xk@ zqQbYa7C&-~?iF?q8X|Slhv;JS`xHjsXMYYcvZeCZ+WuUURkYw`+p%8dn-`=%=Xr9! z=f*c&r7lmRrcT2awH(|&wJ{ZqYosXcuplPQHMNcw`>lH2tUz@%fAW>ChnMq1z9!$C zi|?<5nd%~>@{hYpdg&{$pjJOE+un$W08Py+6pSy%T=QkL@YHdPZPSFqf0VzUAH1O| z9>=?VLmSbPMHA=D?KB(fKb8?+kDIBRO1g@#p+>rYk&C%C*GUW3dna{9W5`bsdt`V* zR6&$TqwE>UiWw5;)tZ%{I?tN)JJ6YYHy&K0ysZ-3rjSs}blDfFv(c5+$fDD7Prr3#Uu~>mVWZC@j_h@N9k+4MqJHNTcZ+%_Al_`ztQIV=7=J54_wM^r5YSKSfwi?aATi9h+A#uUtd-1qwnv$;R-DSJk z-};i4vyWtn1u4Bj4Le=_x$&#SopP~Tt_hh8=h z;a}l=4+MQs2zvWvy$Yw2Ez+Y~FSr_sm73{TLN>v%( z!&KZ^fT09R5^B9IN=~arHxq{C&OzTV!&HUvi8D)Jo(6_rn5DK*uaT;9UPX&$$YX9k zlH4V*nV(F<9 z99<}9e#K-{oRfClPwoxcXYk1go#Wo~&xy;}To@~@k$>32AR{!78QQWS85C(L1@fmy z4D>=nDkAAmvNN;g50#oPuZ}UN`kz~$r5v;Mf=^jZVsclg{Ie*z*=TMAcb*WpYsvgb z#L8H0OW}B;l;__Vxze;DnLS@(F{#J5km2*w?tW8#&eRFSo0t@e^{AC(gk4-$Q(iQq zL`L=tgvXhVWG{{E*SjPSO#a^J@B9tk4Do(x%g-LO+DDou@L)lyyjwAZAXw<{b#oP$Fp4BM%Gp+i$w!8k3EZdJl}S# zjIvjbIX^}zG37B?TTwz@UGn=cSQMog3 zONWfyROl7mv5y+dv7TMBv5127?UL#4cm0h+46|@OrGgnbSEyLJXBUi1&} z^wPU{iP2KdHoLYAQwFG*Z$z)u1tX8|(F=aEwjj$8s12Lz7xXZ|w%V7f+;f~`>df2l z2o8MxewqpN+j7bq)`4Ewim89@r+q*6@Gdsn3H2qnGSQFbtPzEz{pMMNw2RN%`c4Jh zJD+&4Oa|twZ_`c&M(D2HcU{CD&wa%V)>r4>2*wDd`bKOn+K?ChjkUQvy`VUWHlZD2 zOU4|AWrD3$niXmkQ|KAdr@^xGnm5MwLZI!81G_hoL^T9UaD_<}Uy4=bM`Yho1*xc1 zQ~#J})_vNI$9lf4J8#h^c?=aM1EI;7f=i_J_Z|=is}~&x9d`}b-6>Pe&CmA&b+v&x1u(_=X2>LSH8JM)8?s%sDaMsb8C~i zdY43F671^7v;Y|*Mh2SsV6S*hMKJb~-XQ_W6 z`j*pU-{P1^X30ur9nj;mCj3##QhR8EDf`;z{(e}E8Lhnfy%*Ek4Qk9B9qEjEuajy> zy;64+KaQaU_ zc{*s4GA%ZgzU*&gUpB=_(ZO6%jn^X`(0+jG&zwEO&Hlc$h}J=lWIz_hZBD73CGBSa_yjT6nCc#=F;|Y`ZL-2KMb?vyB_kN_B z3&biIx_Y+7YaVY3n1n7Wg|<8buU0<QB44HN$#K1f6iItOzd3L!~4sd;p{UNWfCwFC& z$RttM*I+87jCo*k`=!zZi%pvXG|@&(inu6Zo6Z)P<8p5(z%`-t**FRtcsv@S=utrC z@xjV2$FiN`d9|f;`2WY&TL(lLbnU~+vLJ$hbfdI%cXxMphja=EE}hb`(n@y-vUGPh z(w!ouDDAiG>hpZ>`+NU_`^?OlIcMfR6Zc%#ej436GqwNW%_ea#`#1@}Msre-68_cJ zTF+ebieLM4`pF<+O$m2Bik%$OSnNz`T}JfhCBcMa(UOb`+W9sf@~EU~Q*8}r#RDGl zFpZEI$qL%_90-slY_0F#p4T|V-zRIsOtJ$i3Rq^uImM=LOL!D}JQ_%WW=iX8PAug28cmo_Cd$vPR- zB^Or*Sf*g;Kq2r+xqZ4CO{5s5nL#ld>sGtpLqv}^BF^ZsK3XsFn~H(|mBo6Ck;{D+ zYm`(HiDusG1HtTW)dSsIq|*wcK=+Q@H}zW1kV8l9*4ff;7oJWri1pe3QCm04TfE{!~Z9Sr zQ~=6hz zxB%stutRDnj)4m%FNj4YgE&hA7~q9qI*#Q4BCtmYs}g_+u3Sk4VERxJQmq0Qg^Sc^ zP>GVBt-eTwViLja>Hxa`0|~0vIr%?Q$aF~Gq z2NGkrNehl zfH43FrZ@g8hhjfiQ!f#b!B8<3s6re50of_ZdWo}+^C$GiHI5wJXl}8oMj5B$mk))q zAL;kQGe!#>RxG!U3svtU{{&)OD0qCBJWJ|6&70YCN=yeYnh_M$zCBrW&vwt=TXk2t zzODkd1H8%Cdf9?9BpSFv;)g% zra%FN$*%7)T7j1DWRB&h6J{N23B0{L1sGfy_q6XswS$=sW>Q2QCMy>^6Gf>~Rw+35 zQXBbGA!VhiRmsV3Oj1b-f*Vsk>ho>Boy?hYdYj)M*!v7ud7IM5$^GvjfT zsP|*pXd1&%C1o_rZ4ktMQqH&+wej01{pj=gT!ee8P$Kb?w%FS*FBnEx6XeMYDQK7! zW)T9fd5;JBKY zrfqXLo)apyQTeBUUS7R7D&b43&r}q-OG$M{!00ewpQ~j-@r!XrjQ!KxqPM7~ZMwdM zNVb{KQ2|-u4a5#l4(b^CJElTi<@>|<1p@)OO5 zp0^(kPgMM6&85?599oTyPI5Y8X7Yp~${&l!1h#Fx0tj|T63|;PwJtn_98P^9RUSLG z(Z?oT!1rf7;lu-bIDHA*-a#VLfZJ%dvEjboI68{4L0jvyCNJKh8x>x~hDKxa4!+4f z2p|kY*gIMG0HF%5A;=^GZiFT@NGAp!|3n+B|F{-z_`&>G`!h*mf?Jf}N&E!- zm(u<6iA*lBCqHJ~0@rHImP=^radpPWCM6~$C__`3RA?E-&y? z=)NN7IdK?qggb%Vi!bCU+MY}SX26pcjiG{LxU6M?7uGJ`>=Ws$GD=0 z>r5=O-?&oe3%(U~td~+6SCPO6VF9|8v{c+eakARznZ zfGT(#g#~J8B0J>J3J`|{3tG_GlLs6RhEA)Dsi;pOBi?{yILZr(8sh9H_J&H6#ZuK z5%3OP_JMEyc6<^ThT{}{mgf4ZF( ze3Auqj`AenACZ>XKcnrlq3}rmX}~IM<};oF?81xwtBewCm;;65X3PWp;XeG2Y2f~c z1iNK`9=z^24q|%=$b~ET zPlF&(a3m8v9~A!x9~yAX;qE=)^gnb0f4fLOAKJ$+kr8`f{Rx4NMoYN9|JIR(iTD?4 z{7*&>o+yB>#jbD=UE!JkGcLi<0gDf5CqR^h_aYMMzbvrzQGf^{Dfr6^L^u!_w!V81 zK`>q-mcieX2S32(-xo!wh~UE_=qd+~4)OJWZTxSdX)!7(c!>dV0ruuWUBm;16@tp~ zz~73Y>sBHrM7?LQ)Q1lKZ%M5ZXeamZBWA#R|6#;KpzZiO&L6D&PhJRy1R`|#`>=w7 zzT2PS?t^tD%ob?FF(H$Rh_P^oKR~V_0V;^BaK*6E1Iq_(6D|axiTDEk^1%SV(6K-W z&as59H7t$(dISd9{;#~M9Qv}27$Mrj8USB+$aQ@k(PxIeAKes(?m=Erx zzT+kwXRTS6r2p11^r9_d5rYQZv4MY%@Qv6?l2)F{OK$BMF)SC9>@7|li%`6HP{lLZ zZY}BG=3MWIX-|B1g`+iH`cK?jX&N)JISVP~>;OTNWW{6ZEdf6zg5zX4Q{B5`5TpMT zeTWpPrQs3%#t53Z%VKdsI7lRxZnp5(3mARR*h*VMn}DFW?P1yR(i5o0M0?JUEq!yE zx)$JeFy>It>Z_HzuZSPX@%7O=LdQJP<4lzPmGETiFH;OBjA{NpJX!utjaGGw?%-e4 zY#F#hVTuf)WqX}f?{p7d=%ooBdijLpo&^N^;6Dj9XO+zoKq!yJE)@4yEA_CPZ)iap# z)JfJcW){o+9F9||t9VOnrVmcyvdO-8x`vQkEc|TJi1o@ot~a{GO77^#TIGG~?$EUq z{4Tq3ESzB!56p6OhKe%D-hdOY-Jsf$rZzR4!S!9}I`hKb|t*(st~ollk=%R7XJn?Y|$ zYf*Kfw?`kL{I9+7KMK#JBs^+xM?5N4%10nFcnknUPZHh0{l75uI{(4YGdygR3JnYs zO_I~Q;LJp=t9#lYXf;9qd#?n3g9sFb1*RrMpd{=c=w?h7_KzYEs091xf#Wfi28f>e z1c(G40|GB$(uXb6UnvgwJ^>jCjDZa7f+amre*R5@^O`{EH0Z!Duz`p$733(u9$3By z(wv8U;ErbKvg5^5pfr5z^{+fk4lZqi&J5M4Kz4ZIze1_as5oHiXTaS5(HeLW`({)$ zFeW;%?te7rA&nS}jsYxziJ@CTSfieTk1&AniQpj|!W!O&ikx}`J#_%JK*NR{Ca@eH zr2gXxXgY)e8+ZtB5*a>_9U_klgm3pD&_{TngmIt#)kh4OCIJ2=9);4}(1A_cpn;5t z2>1-H115vI2o+*P4BUcO_s?yp4{d?GB?G3y^Zs)^MDzua3O*x2If-D#pn}sWfx9sK z9%hq&#(<%Xg?@3S0+Pcu{IeYqeA)p$CBUKi>wteX!sLWtZyF$+Kn6BXtiJ-{g7rF~ zOM_ZkU@feUf7QbBBZK3+p=*f(Iv^3e7U859R3Kw_Q+891Or)n5Ktr z1*JjvjW{<%Lf>}Ob0KJ5PZn> z*D~nr!vhiJ1}ebY5-1F01g~)cQNd6dl;(~HIQ}1}L7*kkVc8(iQd)2aLn%ezV^vEC zIQx+Gf!_waE(XL0r*a{|&sd;Jss3yELm~lqRRjp5*a5?sC;o|^fR99haH<(-6^!7V zK2#Ji(*PRfC<>=F$dZrUWG^9;p*WB}a&H9p(D3tLz-zwi^E@X*maaD5p z!qy`cL~b*Ef=Opbll()IEgKyflhtdIfA;+%Ig=yokR!$n*vvc$5+Lh%Meiew8bPH{Xzo;kyWDy8G20#8ng_Na_=XKKPk>!-h)!QL0WE-F8G8=`jvv2NS1bK(HY%iy`I2TQJQsHK9CM!y_y{`xL+qv%S_l3%b)apL(73DW-Dk0!ha|^MZ0(}F!M3T4A`o~qX@M;26wWj z;%Pu6NRl{L+0$rGQuVD;iJv`kG(k0Ukq2Pz!=zjWDPG#w-~u(1LV zg^<+C>%ty7ojLtKQ(AxmP!2Zmp+Qa=_75zY#3utWQZ<0jAUcXbANYzCc8L5Suh#$~ zz*p4J^*I^%Z&o0LM;}NHfBpZX0<1I&UDf6o0b}3^P+lRGhe{yOP*?>QJxpH@I&f3R zP!S=ot$;G{Jt{1SKFGn?5Iivn4W*^Ye?tQ-(sYzVNo_JFpc_-X6rlBg7#qpJ4+)Uw zk3a{w!LS_@R520QVG>G+=2r~-%S8uUcRwUR+Dd^N@UZqzGaCri&tS*%5BZgA)y_8= zOMzIdCp1*10vOaU^Tp5~_ore9G8KK!T(_+7wOHdt&|^=`r1?%)UT5E&_@zK)$}8{Z z^J`P;z=6k*0#$jQdf|Fcrj?A!75%1*-Z@&zJJ4_Wxi3t?CGVzM_x#p;{PwO$g)X`% zO&(QiQ5ZY)QF0N0`riBcDGLdogvPRXpV77HM)#jKgt3&Q(9UcP8w*BO*o*n->c@H>bYW>qo{}Y4<*>y5zaId_(FybM`(KbmDtPewiJ6Vt=VZ`lnz6 z`SnJ)u|roa|GWJ7KXYdy{O`&UZzcOxJZ}~ImvDZ$Q{V8h+~fZ#+QJ_yHaWaZ@%^K} za7#?uu644-?HCbOT=u8pcL}7Cr>VvCdIfU@wF1Ck%#9#tw+gOl+NqOh);4^ z9XvQ>U+2lAeuKRF#n0M0_eI|*x~Tn8+GXR2uZ`V9kcf;N%%8R2EpPL%?i%q#ITXK< zNYHJmYNq8TAbsv|Os=GY=tk(Sd53^~z{Wr*OM{L)GAP->Z(ZOP=px&CEt@cw`7vV1 zTX0$7du(og8W9HC4twNG1J+uSvX#3vJ!DN$Sug0&+YjW-SRY4h85Lr&aeRN;iP?B} z=DpwN|0!Fyn1}59F-mW^`O4&n&yo`aZNEP}Jkwqx7h9jbo3iz;sfUN@*=v!GPOG+X&>4K=ScH;0SYvn4ov zkyl=d;r0aCnwmI-2&x&(o92s6|F$V>KEDrTz*XJNY_3@iX3NVj@RYFQFu|Tbst3Oj(~^_dt!KaczVO z$5^*~gjC5AD3I!c;Ca+>ArYKV%=g+VFO|>Q%KM`g=)3zfO`8e!xrFyALQtmfC7WDT zdiw(vn?40MB#br782XIYG^6kN5=h6W^Xs5B$4(Lk=CPh=@Ebr50 z4pApQPWd`ir2TqipWD;}B=%S}(b*+H=(S-q;5#XSd; zARZiQTo5WHqgkMgPfahME(Ph6BAOyMv?wd(_+ss}kt4_V(QYCd69#c4eOb=HA0LeSgk6&9v)tM~w-e{W zZWog=csz1BgQ(<|G|T3l$Aj@9$!quXmEBZ_fVdIH)IgbNl2!WUQJB7MzA}c@i5Bm{HC{@wl7wmIW`%wkgEEQKY@nH5SH58?{njUC2Xd)D_Yuc8laBo}vLiO&_Xz zBr!K&h2(_DlBcBL5E|MntF`*!rM*=a4VW0@7Fsv$Z(%(q=wQtRxN-gXRlv;wIm|=0 zXqt({qt$`cGS9*MoB%6+^pMJ6-?QjtZ;_^qIXxHAXRS(0BRqq*^H@s-=ZGu3MaGEs zT_qMrf)jPC=q#vN9MZS)Xt*s~EqVEX zfD6`do=Tr1shh8a2yDOZYx+vCRq&mynQN}jah0U5Jq)p6I6liQhNc;*L$gYv5Z0v;(c-GLx1+)%&UYi?Zq81F8AOAxc*1 z_|p)-Z{`{X<*p??Rv&d*Wlzjsm^8WDfu?K*$QFia8ZTA*EOORuwmLfcYHI3R-v(o2 zcQU$vH0PbmllvO~Z3ju>+Df$g?ss!>TJxCb_Ak{E^S%5Gjcdivq0r+PyW^GN*TV(T zXrsOF_};u|F^{Acl4&@sZ<&-%N*(Oh*_ry)Gow4RE}7qM@1#K)$|Gb~5GDNuZxDoU zILNR#>{M)4P*SJW;4oWSS-Jgg&x@P3Bv_-n&yDhH^zyN13#PnLoV$tMy_7Z8Io_S$ zRpL*t(X*o0TtCMW6n%5)tiNxXTkFx~zgEZXQ%J&>W?La4W(fIVdE;k?NO)+6=E4HqWqbV4cb4z>oBNtsb?RFnKAY@kWk)D1ctx+jrM}`w%i_twuNUC8)+T?I zd^>A3Lr&kV!Cb9RL7e_!B&!-DKZNFULW!kY0tgrtP#CVIID|Dp6=UXJxYj?U%4oH- zt`Pd#?s9XmCRr7~?|c|6J?JnxHfBql%Kl5BB%dhAC_Yby$+P=)$nQd}ZuZZmAjN4y z&$7uf|Io$X0tHoUUv3az5ecH1Dn+RAk9Cb|C3^XKuYkrGjCgdZ8D z7v;Q=AI}@*;G`2iZ~plliO?YW@vnweJMMg?m&?KOhS5YvQbJv}T^GDgC!mE}-oW|C z+#b>X=mk3$K&J4g`uaxt=f&z#b6~X@6IIRts|XjziGpHJZ8Zj5rNyXwOo&@tR4FK$ zpX^a~zW{mAo*ZYf4%sY*yCm|)P*uk~AHYAvrI+({u|dV|#%NwW2&r#?^AO2eRfIh_ zEa76>HnZ=uX+aK7Tf#sRD~Q`+B=4Z(097PQ?8iWupnhSEpzco9`Ip@q3f&bSyn7b& zg7!rSeO&a|mbrKt$}4I_mY3dmjv>s{$^AsgjU!^`x(fZOb4`NG7}xamJYP7k{Cx~v zt0eE*QVRtagVRlZc*YCvQ%{sU$_h|_UQYe74$0o{+Z~sWLj&~BxWs&t6ImRYlM(1j#q?<`8A zp(k(`(}@LhPN6z*A3eh*8o(D~FQ+P`Fyo8cTgo><-1gA`Ppwrz#W^QE zLp=|kVC9Yd?H8t)cikY9y{g|lm#y?e@3Pp&ENooTKxs)R^DQNE%I&AG)|hFJ<`>6M zN`$0Nnw{pvmXEyrBp)~1p!g19|IC(7KA6@SC=-esli|jVub>WD@a#rt!g31>UEG#? z%l>5HWJ%ZS(7%XYsO`l1s^qtwcC@cZ&+C$E`~7AICs7=tFx`9*@=@4AC)RRbSVNMM zb3n}Tql_i{uhlePlVfYn1V^6wk~nwFyAo&jk#7(#2ily`2!``)`Lsw8ooUDa*6Z9B~NhPGQN5#d~Da@ zpN;DsY{Mn|eIa5T*ylw-_XKjkw8O;MNhdUe!fmi$qsEk8})IDN$i^wmjs|~t~uDSfu z+s9Ae{0RtaU%#nEBCB?OgK=FQiQ;XbCGWw72^HzMUnhq8q2l<^Ghw@Oa8(I9g^LZa?}ewf3gJRjhxQ)2SMI{7tg^L>BjyA0rvaZ&K5B{nyt8>?C1S9E>e35w15(+6(LY5QDg zoZ(#RF*aFlq_`5=h3FeLMeOdzGudMfacs&fVR`JlD`9DDi!0;&Nb|P1=^nM)bMJS& z`q=%fH7MyHiX-Jp>CQR~+0w$ZHUme^>Di1FrzJrpVV*?UAEOS)bg8Uuy%TAM8gwfo zpSF$9^e|TOYHOZ%J1A+MVY*+MRnqL9`M1&Foo4l!6i04xx(c?tA?Pqo^+fv7=Dw>m z9Ko1H%az7`jFCx^YMYySLP_vby$z9Ym7SSs&lSZYOQsz2W0WsOIrhurm5!6lk)Ez} z+ZjoaB(4&f?s&{M!hU(J^3BcHZRKWT*=I$lR=N zxm|v{bGkBj8FO83MEJC^UMc(Qw;tx+xic ziehNgj=H9AGaLomU%D|_G{hp8h}qcw5?)2C~>g6WTcbqpK+VV`ejJXY&%CG&pPZjHXYhx&o&^pZlLry?PANgvsJOnGQPY6<3BE>E$B?Lh{_{_7 z7uf&Ex1V`6wg;v!*9L#NxR=%74Paea0P&Cg;ldEwb!2OgSz-H|tC1Lc5B-e%A zB)T7agVUE*R3i0QWKuqt%Fg#-B}|lx03|f?eB`c3a4?tl;P?B<9L+sv2g_Tn_8q#! zQj$^w&tfPpXjUsMokDP==7vM@L*xW2Ta+7`O0dXhPJYqedkj_v`7ulvs&$jICD4sbWLW$@-PHg4_Uvq6m@KR2c8UP zFFkDk7hk)@+FGAnUOe%};2#E`JMCEOH$@a!wJbMPimxs%u(Wau3r(Ma^P*mo*5f{N zxBOCOTv`!rj~V7M3Za!h0_6bjj;?*QFS~~akrYx=!YmW>`2?kUxSMdAO3mZx3pJ9J z2hygs!ZZwT*3^hMjr6~C@|4pY6G-8|-7V3RKJXz{%%R3lr#~FrB;vc9$ zdRc{v&VG@E>oC0X+49)iEZD=XpGVB0+n@>2ey%ikaJhg+5528V9Ym(RS44`dP~MNJ zq|8Ih#(=YZSC5)EOx8k1ctv?0=HZtSP z##f1aCO405nQ#2PFl=@GdR?&Gf;TX%7XABd1wYP*&@508Xo)Aircgj);n1y~8;G%% zDqvFeFi@iu7X#;tAA;;RHihJZ(j0irEq=eujyiVQ(h#jFXVWy%eIBJ~OzQAnF-NGj z>v)YUg+6d7;C=t*&6}-0AF-(U7)Hf6?^3OfJCaC~4xcD4&LzEHC6y+N0js|^2?d%Sh;cj$SCalYS(eDM!Z?&(PcH{)fyqQPVmg=;YU!VG`ia3ZQp*cnWy8PK&Q&w>bBoGm-9_EEsB&n)Ww*x} zPa9rO%tVis4ey{)lU%GZQm6a0;Ng87wMF;FfpYSr6eS&@VNzDf)6bpz&sDOS6#Qc* zbyJ!gkFZKrjgrkk^k-w$HsjkKeNo$7sK5K5KdB@DR>jX{?G`2qteyAB4Jtw$kp8{h zHXM0*MDVRy<{OR7)r!*M?pI>Hz}aVkhq4{4>xSbIgX6T1rPF1=J2Un0uc&CmT-YtW+8 z`>-x0rmT`fg+s7bLTTf$Bl*Rip6?11d)DD+u(7X-W-p!$je%0VceXTKUK0@<~9T_ z$u=SeH(cm49YYj2v6(}Iqp!-D1+a!<8J}4y%#>al;+sUUl*mNALk*h3Dg1EhY2-(J zpBr7(YoY}@3uC^g5zdvZQ^j>Y*gV-JMik{`T~+(=6{noCN)`VP=Gu87n=> zOyw+#xeZ@re^cfxiTR%T9ZfpW@WRHrxj z?YgGXEq$BBzLr!=b#C1eb^iN>CR?2VK_??IDNJ3E)XN*N5%yEgIr8U&()<0^J>=!i z$?rK$du=B$qoTcb<(utu8SLbnDvO`m)qxEZ@uhW^4O4oxw9d%lD!m(Oz^#*qdpk6I zPa1je7Zd=25taLH+wAICWdx~?GNgB{VE}ng?t_bJSsf~*tX^9$j^GU(YNV; z=i7?wYN4B7U3wJ6B@RZGs*i4{BIJ==vtfr`rG##bq?V~>*8S;`R zaX`ah3=8hBpN*$8^;puo39J|+qjtE<6?cc4FzsaYtYwPS*`)OG*gLg}Y{MtY) zswts<i=P9)K5>9AKqH<0&G_Bay%6@+~`_e{?*zyklFSPS{UKc;Rn^_-6Mz=KfP2nk(l(#WhN~hi7(Vea`GeSEpc+&$M!UqZ40=4ZM&NY{VDh*urYpdl@*pe zx=0hTT@veT`x|9DJMk~yvjVhV{XPb%*&7gsN%6J(uv=&koU|vkf2}G%^6?#QLE^+( zyM>r@^<_z z947)sgSo{Nj;u)U;<+IQa#0LY1o8B|ibdafg_NDZh#U+?%%-K9dNF3AurYs3HEsaWn^6h>f!z*EQ=v@A!JoXgqeZKqiC(}|%yvH_J83o+2 zD*SwZN(OLyMm!qSj_<~gR~WnS!r95LU_ zvnR`~6*TL|o$`6tM)s| zwhu2MkwVV^bt%Z!(pR(pD|?y@_V1!F7dk({8bX&yC&2$QAN3Jp4wCwc%I8~_>pO?H z_dIv^7H{uE-!(goHy=Ryf88?NEn18Zh|~$8Ucaih!M~ew7(a-AHh>KF@YEQ!=y)X= zP|#Qr(6}VZXbaqV?$Qg2va2!oEidki(l6hS#TBZ_<qU znWm}G_U@Me=i01J$pQ=#@uzd$+jh=!7K9$bJ2Rhy^bgG*g)r6us8}$PT1Fr)ds`bJ zXjZSH1lBs3eA3Xp6Qcl*1Hc?Z;-^gMD14LsWO3p-QMcdsK`$@;qYXzV^5`5Hehwd< zDA)%|)Z_dNIvSB!G7UhGvh!)HyXEFpe4JATL?0*liVz$%#SurRo}#9ox~l-1(sCyz zsd?rm8;N`~gEw$dU}M*PFm|$AFKV37q}{#y>kZYyErDJR{ek&&4Y?&mfByr+VnXBX zUyU4*X%gH)hah*2+Fulki7}PB78`Ubse4ERKN@QVh2Nl}IR=`2cJxjy0>*C7WqisQ zuQ=UerTI*HE&GJh0h1HO4AoVh688uNIb(`1ZUnc(uBV%$c*o(jflS4oC<`zCb*H}m z^F0YNL(#;hC$WLipAhG!gIW;9J)DDv_eY;;HYLjIbaH`QxR&MT->&O>KXM8PUzheq za0>eF!f_N}unu^AN5b7XpRXsy_?Ot>L(k*>Cm^l5c~zR*kp^klQ>eCzcP zwXCQuEkXTWi-qW1N&{K!GSUkEhiHl)bo$6X zjvXMtGLj6cZJ-4^?I}W2?fd8gRzvk0-yr=KWDrsrZ;WO!&mWtl_v;pF8tfR=Y(=SA zyEFzHL_1OP1&JSWr06*^lr;5?XMzf9OiR%^KeRLyCP`!Is5R9(#^!B>C==-wb}x1))f-F*Vff81>|;!6Ng0dNn&a7jO5<_K(oU*&iAiI z8;)EQk1z$giewSm%GniB7i3vdS|=uRex|NH)op~dgrr#d8-|!MjM4bo=~^|>^clD- z-Xs8rs$7br+y|{mYKbeU)b7Q-I!?QHCqd4_%&goMbv!5mV_jbcj~l2y3#>9D?93_? zVGkT5hI`j{QY4~h6ln9-$hx=rCf;c+dKibej^W>;3-RVBN#{ZOmgdd(7-nPd0e>!)1 z^KE~y0FB0;gJD9;$y1p%U&eFZC5f@)EKYf2&i>)*muYCkB>q{Bw6y&WT;YW7d9`eb zEzUiYkimrpb{~^6AKc$YS>LAh&6E=ApDF9DPe0Y)n4zeu?Y2Yr1^8!i4KwZhv>u>_ z9HITz;*ofh1CIX89xZRX ztpHlyw>V3ecL?NC@AK&7%c*j5vva$rUr{W}CaiNuA+F^`oLEGidzlga5_=|-m+?Lk zxok3;gf?{Gi(Xvosqsh`*mn5psef0fe~;N$6SOXg9;y+%=rdJU8gxvab zahKe4YGrjVi^wVErL=?P4Tq=Uc!{5=EkAC#TKcuBmMjPND{L858Aj2{PWv^v(J*&m z4<qSA@WIzxGJH*P7oa)L8Z4f8lpXR{41ZQqI0zuhrOBeC+JbKcYlR_c}ipH@S|$92KUPobSgqci!~JV9zxb24;MV` z4#BApPsW0t>7r!Fs${q_FM}j*exUM_3m1?!W&NyYv2!3hI8>dQnyjmjst)ry(JwWM zdPdMAZAim)hcorPE&pIRzi@AI+HJ~;B_=7xQxhzmca>jQq`~?Vsj|a&>gug&GvRTI z=!U8+5??-(_D8J9L!K10J3N9^{fY@i<#>HZ-WbXk{uo*bPmcpFRd(QU;i z)h@k4SE-$uotjO`%Z~7l)qWZiW$&0hAXVaIjbH%FU~U(kdSXijT8SnJTQOd`Dc?9oYy>rbLqd5Q>OHDK=RFF^h5^1{j%YD zlq)!oR-)~TXS?++4^H(J!sn$OIa3sgm1L2LxzpzqMx^7c9aFilbsAp1PEc6@+@U!- zO)Pz?#@>4Sdgiie^`n%`r*dyz>sd6m0`rL;y)kKODWzQt z9$BqCdcW}HHVoy7_CTIf$!Z5<&#ZI3D0WcDiOK;p`9kNM)Cj@5x(%wU!ZTqj2L zETheNU7Jz2n3ZjZmW9dRhBjMFv3=B8KU=gqbX90c8OihIo1UVg-xHK=zFPbR$Sr;$Es|hUZS-{jc+RZp^J3x4GJwQAI~Y6Q?xPS zZ7}5Gy@`O{=;Q=#*l&{f85u@6M-HDkx-zvfWG9Kggbxo~j8jV`_*al-vFsoh|R7GEe6n6gP1Ot#E2H7J;% z_tEE0@^W5`e{}!N6l+OKeV%ogd|}$BL^Erg8Yv>0XMWqD_0O)Z3R_c8kG(#ZvW=@q z-`ifl{jr1+P%&D~AZ;&@%u6EO%}iPgwysN8z~(Nq%pL7?UIu0?nz@)euq%Pu(s)|J zRNc-61$e4(*@rSd832p6P`6!W-u-OYYP%g}PwVu_-FKBCSN2)&?->xmmbjP9y+-ui zD4HM_NkXs!*<v6FyY+Mr7bibo|UqdOZL@f4NW+OqW7SAT*`UZsd# z{h>xMU7~Qx*XMXENZ$Zm53u}fn0JHW^5jObW<^Q!!`RoB5#-#1Rpb~Cmd5Rj7{8NL z?XUXVLso;rGfy$_3tWGJSKiC`+11=uz3exURd1GEZ0~v$sQLo&xfb-Gn;4)M(67(8 z{%IRPzz15V9Kc>DIep|t>NDB!F#wP8WYjL!;WVrmEc>oh;c%rq=oZg*aCF!?bgpGY zMk(+Y(~CEQ3^{gAh0`;02yuh>kkLG1yZx>60~8728jGm))@`VS3pas#rjiZ=Ce_;AjRm_=q zc5;-2Sj1zOYF@Nh%1GEXR!sg z{So{iyl%7YA4ad{H$w3s%ykW(W}ICCKMGU|C(L!lR-q3cJ=?*8EuJ3e5m9@)ZpP7gJN9dmZgh*zvCilJBL=_x6E?Z4D49_HQ ztiLW2o6gH@Gw6N0-TwByFmRZoJixBNSSI}_upxXqINHeOw$ph#RL(`oqa zXpM9wJA19+qJy4MV{O!HEZ1jfIFA>#z@ndYpC(DFj?rfHk;=ZCw%9#pA5CB2?0yw- z%n_=&ES+8b_|kZvM#S#`JWShM_x=B4>nh-?TDCrYl5V6srMncAkZuHNX$et6K@LbG zT^l7tN~HS$Dj-ORf`WjAw4#8Nfd~lSW*@x1ci-pldOc^&te7>kXJ*Zr{a+Yo?IEKp z1;5^_7zl-E<=j$&o%#?PL-$TvI@y7HxnQ7 z%X`WAW%jJpZRdJ(>eq1V*MR@W@ao@J%T*bBwnG0Q{89SbFX2;iBD*#BsPXjqh}abI ztfe)2jR$T~$A;#lxH5^Ch|H#;q; z@x~F)_{ciV6H_D7!ekpe@AKFd6w!#*{4x>D6n_M6eow{C~kaCE+-ng0s71W{Y`+hMYl^V9IHb)sD|f)GA@ z?*P0|*xWkau5!p60<%kapia=q#IZYoKT)0<_D-jB&3az+MC*_3uuZPcpVad(_*Mi_ z5DI^G74LwfdIN8l>!65wtB>Yv0MQp7B0I=WTI~2H_FEP>wZ7I$^X3Ex{P^BnoynB* zeTXe;vd;S{fv3oKk@0>kVc-uIRCqfSw=o^^ppU7x6c9l+r~?}UnNzI;1W!)0ZZ#0? z$`c$Y;fIFfb!dgz`QaThuPNf$hTBcxd+wI}=vl^d2a};cozKx+U?UG#l72qnjeq|8 z_ssWa55KA&za5*x8@-0&!Nb#Z{d@5Wx_9;G?O5>DA3QvuB7~Ut_)Qxica|6qzL=6R zmZ{1odDlnc+ed9RID0ajfmigy0|I=sc33z!KO%1a4u~Qa{z5nR?$*|Dd}I4sg@+LB zoCvMaS)MSflBUd=0PIX4cG|`>@bTk-z%}o{$1~L>B}dmfMCU>z?>-~K97_Gn5AFzl ztz5IO`iX4r_wO|y%Hfd1=$Pt~IZUt{DsQmJ^KP+&yI=8Dt)&cJ3nw7vt7Uq)^^y|# zO684-Zri)VB`<)|GxyZo`)d*^@t=$HRt8A)!iEa08Lx_}D07xVBN zy1z@ZMKw5qt8m$RmdisFkR zI`7=R0HIYgt@8+Ur~SAs7a)L^7;F~OWS%@g;PpCqa~=B+br+WR$C+dXY_CQPqs zl2tIgA4`1mV@)VOCGuU2y7G_kXNI@z`6)>?_t&h8e*bwS{-Lg<=Es)|#oCHF-5OtV=O*R}ET`uUA#_CA~wSdPs5vNQ-DwQ%3hAX|%1ukFGt z4GROBS1ry>c*yiJygcIyX%97pWDVCS8-hF9(SA60rdZj7% zeWO#)NC9s|ip*1~;A*5h#taj^)GO97$?Ja&@4?N`rQw)_$ZUB2!%;P&=B`WX>MPNv zArcGsLe6kM9}{^aS1#^*Iqi`}Y!>+;5q;S-lR-<0v3~t7{))vS^QDZ?wT#s|%U2g}-|#z2P-pLI8j!Rd zFG8Dq^*gfw=Pkll1c(~32V{@$Xe{#G_0D@b{7m^%8mIE6rFR`+-@&)=I85#v;YW7q z`0JV;2!=U7voE2#?x|FBhUbe%7-oH8dwVhr==#SP8rX29Wk9I_)~%8R>(2M&LLGWi zB5x*gn0?wgsGM*v;?{sk6jxFM@`3XTrOc3VKVec;nHx>;Euw_Pk+M+TVNcN{qChY7 za-wud!tX-gaX;ZDvgh8y445R6J@cMI!t5yl1T8ynmWiiDxPer(R=(^w>dFSi+r}>& zq-4BmK`ib{ml-axw>Tu+*37D}l|{9^p)71&P2sr-gu z+tqJ_2hnBTpGk9g${unl(5#VGBeg$f^SP9#lPcWG-vE{a@|ul1^rtb!2<--&dn_Z7 z?sz{18E-!w*4i`_<_&NspsP&2-lrC4?<7n&!NsUCajVQEGEAxGg^ee{wio|_)a;{g zr)R575*8ImjZ7cfjJ!W5yVeMJzjbH3rN5-CAp0g!VLf%Z;kr%9dk@DYCtQ`XW2R%13zCfbbsM>;q1sSbA{r!72oUi=Stry%&6U`#3wI^AGsmg zzU}qBS%2=sEd@-HYR&e#!rZe!PT}6*D1QZgzU#gA;3`JH|3Ow&9Pso zl*G-YegDui=Wgjdmp>1?9-6{7^6L)6*Y1PZq2#YX!51dXwQD+$`M+UUia-6RFpt|% z5RJLpqIOU8o09@dsXEiFa$98)Qy|+%&prg0$Riv zJnNfhyBl|(gsVh#7@b+&Nl5;lzCfAA85u6ig&H&c)NFYj_!YpCSDng%=`)>fB4{r; z$BJ)lZ=ZJ2M2fk$?)Edh`Q3ai(V+yb)XLC`yAB5TzB&!QZxm0PkVCy{FS&{e{_ zK0%krV|w!o-cacTqTx3c59XT0qf~*Fi35>B?<9>6G0G;sPNqM^r;;j(uwTn{zon6( zmPm9k#UeChFGV?$THyDrKU%ER*Q)-0EF*V$>I#+ZEylU=X!%!miZ+%t!iw6$InMIq z+2c8;W2s)5E_88Ag0qi}t4lANGt<{szj>bVFt3VgyqS1${n-rSmyIUIzr>7@<>Q95 zn1(RySHh?4ryIGlrJSNAYBh|nZ|+`j#ESloK}k+L<@#EMDunxWzR?AoW4pwAON8i{ zV>fg=o+B3o8~U8S&PXaMKKFipCiH=c0_~69+hImaM1&6S%t|EoFZAf)l?}tIF`O=C=r(`+h!u-a770^yByauP=J1LzFlb zmR4iZB~bdtT#X);NAXSvI}&Q54VpeCaumt;B|AqB&&IlpG&M$LSC>-V6Xrot?%A$( zU!u59rq{*B3X7k0+2LrI^WIT>#p*-+oyYjM(^0{_$M{Y(`{Q!q+bQdqef@;M$==7? zG4*!CL9}J8&W~5OsSK4_k>!2X__w5Y7D*#^$P&iW(wRTcw68L$=!ckAJ)y3GDH~C{ zkLm`7-*c!edc4BQd45L64v+r9Mg*}R&D=g|`z2Vre#kVey-%PI2uxrekt=zT*~^o- ze2ukx(#LSNbm@b%^97XC8TK+ShWN#)TE3%5nCt{7Ws z+;Jxrc|hh-P~k?L8!CV4DM`}z?u(ua1^#00ITCkX8@`ozsq#{IUl-E==c)F6Ls;-K z_O)Y!NfAu?rdE-t$9J*Po`7VA=9hP4OKe}|?PTg!_n#^i*w+@lYgGg{7(}$qv&*;c zylwWlyH(bI3%G7~enU*}ROt%sX=y))$aFy|>5=9k};Y$aM>X*4hBl0h*cwk1YBb(Zz!L44eQ3Rn^B+Fxm z%8Jj3Dc#qL60FfNNhAK>6;6K=xb!lx&N@WWEqnOup!-cz8?y)Pm-m(%zow}h`ElgM zBZmP23!~|?tu#%v8Kq@?XIg2S*n+>&@Bl+aO7Hco9(rc332W8pVB{?iT@%|K_t{L6 zO~_PKuhU^?Dhd~vg#=Vp?Rxt0zlBys^m86kx34al{(3~B_Cw4D{;9$i%yrTIC2!=l z!r6w8$lJL~A@NdtoTK6C>D8NR^VDJz^wmE*e?7FKpzdHWrm4QC&xu7JNFjtok+bo*?a9&tCxy)QCeukq53MwiFF}mTav`VL}kZM{IvaP~2WH8fH#_y(UiMaXe%J}KdU+!|Wc&RUo0Th@gwy-a zcAi~pxD{tBGkLl%;|k{buTrk(Z_^jJ-fS7=C|=4^ymXxiUoiEWl%%MuLQP+%Cx*_p zCi{tPQ_wn$4^c+4khxFtOD5CwKKteIshz2nZc_{E5B_MATVAQ&*MDhPu;K2m;4qX^d(Jp_?Dc=7ux}b01(DXgXo2L(w3h1)&A4{LKELS z=+1H@`k(n+PQZvLA11rfd>Bj!GR{rn^Kt1(_p#p)xjCI--!2e79hiIi^t}e2877R& z(o%DzoOGH9MXpA(TPEg;c!!nuyqL&mx-MxoISu$HxcJpa#M9-sbd`S&zvlW6|$U z5Bw5o`r!CoZ_hmXOm?<+*L~;4BSoj_gKr5r9eQ(&wCBgPd{&IUV^CjyiRsWhy>l;@ zw&pEw%~Rf*wltko-iJyWzl>YR-a5lpZm0FUc*Nr|K>OCQuiv?nNWS`y5~VTs3odgEm5f!9 zwE4c6Peyv4j z>jZBSVXs5k@8p#ic6kxi7YYs4yO&Ij7R!*DG>m#!t{Z=3tmKhqU$!nLcs@Z2KWB08 zTW-s#=btSofz%wnuIP_aaeTv^)df6Cj57DtM3ge8f|$HMtDmbv~@AwLX2EVS}7h?XmNgLD_#_vQ}y({h)H3#r+8&8 zeS)5TuMJByj-9PD@Y?Pc@V8&mdm5_a~b?b0JFVouSG* zweFkNk*1apE%+ycilwrNzTVnCt zY0j9eOz`11(MpnRWX*JtM0O_^ki1nLQN0-du?(y4S?butBD&hTtXuI&e_h){vcJ&E zgoW`AXFku+y+Fa-QbA1OD7t+xNM6>u+;re&9H5>_YuXE#5X+7T+iL%KAIaeJ!2iiW zx#B>IRRa8i9{r0E3xVb?V_omH*OfvuP8ym5omEepm^R<8rv2bsiDy>m(i9HA@Vw$l zmfv>iz>>M(LO_nP&Y;t^s~Jj*iB2=mUCYkdZU`R*xaMPDyc&6nvA_HEttqe8*VA1_ zd(_uU@CiS@NKZ`pLJ;KNjn28kASu$M0nTC_`6gAQ7f0L3JZ#9Dn$J|_Uw!lgKW)kF za&Age!f(cu)X#ID=Ojz+BGOzGMn=$^VF?ajx6WiqH2{a|+FK}Y>!MO*_FV$=b*1yC ztfRaX58KcL6d}k3d5o?FPqM_TtAt&rNmA}`X>(uc8LNb@x~Z#aGB0mkbPo)FFzY>XgIsSp=StSSd#iIVvJdPkCK{$6 zOI3|5SxdSU+Pyt;E)UjuD@fOVsZ^~g?Mq!-vm5QJCfC7LE!{R? ze8TeM#mcXrk5;%s0*k_S-&{6(G_Ib2xW0~P+c0Z8ETd9-Q9-uO*mD$!f!=hqUlVyv zLyd=bUHo4MZUBgKBLZ|Vk±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 delta 145420 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.0.0-SNAPSHOT com.tractrac.clientmodule - 3.0.1 + 3.0.2 eclipse-plugin diff --git a/java/com.tractrac.clientmodule/src/com/tractrac/subscription/app/tracapi/EventListener.java b/java/com.tractrac.clientmodule/src/com/tractrac/subscription/app/tracapi/EventListener.java old mode 100755 new mode 100644 diff --git a/java/com.tractrac.clientmodule/src/com/tractrac/subscription/app/tracapi/Main.java b/java/com.tractrac.clientmodule/src/com/tractrac/subscription/app/tracapi/Main.java old mode 100755 new mode 100644 From b74572ea728bdff34faf595abe498f7e9cf794b7 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 24 Jul 2014 18:08:06 +0200 Subject: [PATCH 250/323] adjusted manifest for new TracAPI packages --- .../META-INF/MANIFEST.MF | 51 ++++++++++++++++--- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF b/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF index aedba8362ad..9719eabfc57 100755 --- a/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF +++ b/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF @@ -12,6 +12,9 @@ Export-Package: com.tractrac.asio.lib.api, com.tractrac.asio.lib.api.protocol.session, com.tractrac.asio.lib.api.record, com.tractrac.asio.lib.api.session, + com.tractrac.asio.lib.spi, + com.tractrac.asio.lib.spi.record, + com.tractrac.common.lib.api.messenger, com.tractrac.common.lib.api.observer, com.tractrac.common.lib.api.service, com.tractrac.dataflow.lib.api, @@ -21,6 +24,20 @@ Export-Package: com.tractrac.asio.lib.api, com.tractrac.dataflow.lib.api.live.server, com.tractrac.dataflow.lib.api.protocol, com.tractrac.dataflow.lib.api.stored, + com.tractrac.dataflow.lib.impl, + com.tractrac.dataflow.lib.prov.live.ascii, + com.tractrac.dataflow.lib.prov.live.provider, + com.tractrac.dataflow.lib.prov.live.server, + com.tractrac.dataflow.lib.prov.live.tcp, + com.tractrac.dataflow.lib.prov.live.tcp.provider, + com.tractrac.dataflow.lib.prov.live.tcp.server, + com.tractrac.dataflow.lib.prov.stored, + com.tractrac.dataflow.lib.prov.stored.jdbc, + com.tractrac.dataflow.lib.prov.stored.stream, + com.tractrac.dataflow.lib.prov.stored.tcp, + com.tractrac.dataflow.lib.prov.stored.url, + com.tractrac.dataflow.lib.spi, + com.tractrac.dataflow.lib.spi.authentication, com.tractrac.model.lib.api, com.tractrac.model.lib.api.attachment, com.tractrac.model.lib.api.data, @@ -28,11 +45,30 @@ Export-Package: com.tractrac.asio.lib.api, com.tractrac.model.lib.api.metadata, com.tractrac.model.lib.api.route, com.tractrac.model.lib.api.spatial, + com.tractrac.model.lib.impl, + com.tractrac.model.lib.impl.attachment, + com.tractrac.model.lib.impl.data, + com.tractrac.model.lib.impl.event, + com.tractrac.model.lib.impl.event.json, + com.tractrac.model.lib.impl.metadata, + com.tractrac.model.lib.impl.route, + com.tractrac.model.lib.impl.spatial, + com.tractrac.osgi, + com.tractrac.subscription.app.tracapi, com.tractrac.subscription.lib.api, com.tractrac.subscription.lib.api.competitor, com.tractrac.subscription.lib.api.control, com.tractrac.subscription.lib.api.event, com.tractrac.subscription.lib.api.race, + com.tractrac.subscription.lib.impl, + com.tractrac.subscription.lib.impl.competitor, + com.tractrac.subscription.lib.impl.control, + com.tractrac.subscription.lib.impl.event, + com.tractrac.subscription.lib.impl.gms, + com.tractrac.subscription.lib.impl.io, + com.tractrac.subscription.lib.impl.io.controllers, + com.tractrac.subscription.lib.impl.io.receivers, + com.tractrac.subscription.lib.impl.race, com.tractrac.util.lib.api, com.tractrac.util.lib.api.autolog, com.tractrac.util.lib.api.classloader, @@ -40,17 +76,18 @@ Export-Package: com.tractrac.asio.lib.api, com.tractrac.util.lib.api.enumerable, com.tractrac.util.lib.api.exceptions, com.tractrac.util.lib.api.image, - com.tractrac.util.lib.api.priorityqueue, + com.tractrac.util.lib.api.map, com.tractrac.util.lib.api.programparameters, com.tractrac.util.lib.api.search, com.tractrac.util.lib.api.selector, - com.tractrac.util.lib.api.seqtimedatatable, - com.tractrac.util.lib.api.seqtimedatatable.btreemem, - com.tractrac.util.lib.api.seqtimedatatable.btreememdisk, - com.tractrac.util.lib.api.seqtimedatatable.simple, com.tractrac.util.lib.api.serialize, - com.tractrac.util.lib.api.sortedset, - com.tractrac.util.lib.api.sortedset.btree + com.tractrac.util.lib.impl.autolog, + com.tractrac.util.lib.impl.autolog.file, + com.tractrac.util.lib.impl.autolog.log4j, + com.tractrac.util.lib.impl.autolog.multiple, + com.tractrac.util.lib.impl.autolog.println, + com.tractrac.util.lib.impl.classloader, + com.tractrac.util.lib.impl.programparameters Bundle-ActivationPolicy: lazy Bundle-Vendor: TracTrac Import-Package: org.osgi.framework From 194edc0552b2dea11a6645fe7b555ce58918dcb4 Mon Sep 17 00:00:00 2001 From: "Steffen Wagner (d059727)" Date: Thu, 24 Jul 2014 19:33:58 +0200 Subject: [PATCH 251/323] fixed bug #2089. works fine in the RaceCommitteeApp. Added a new method to sort the TreeMap by Fleets. --- .../app/domain/impl/BoatClassSeriesFleet.java | 15 ++++++++++++++- .../BoatClassSeriesDataFleetComparator.java | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/BoatClassSeriesFleet.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/BoatClassSeriesFleet.java index 60c6d5bb08c..da318d32b67 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/BoatClassSeriesFleet.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/BoatClassSeriesFleet.java @@ -12,6 +12,7 @@ public class BoatClassSeriesFleet { private BoatClass boatClass; private SeriesBase series; private int seriesOrder; + private int fleetOrder; private Fleet fleet; private static BoatClass getBoatClassForRace(ManagedRace managedRace) { @@ -29,10 +30,15 @@ public class BoatClassSeriesFleet { this.boatClass = getBoatClassForRace(race); this.series = race.getSeries(); this.seriesOrder = getSeriesIndex(race, series); + this.fleetOrder = getFleetIndex(series.getFleets(), race.getFleet()); this.fleet = race.getFleet(); } - public String getBoatClassName() { + private int getFleetIndex(Iterable fleets, Fleet fleet) { + return Util.indexOf(fleets, fleet); + } + + public String getBoatClassName() { return boatClass.getName(); } @@ -86,6 +92,7 @@ public class BoatClassSeriesFleet { result = prime * result + ((fleet == null) ? 0 : fleet.hashCode()); result = prime * result + ((series == null) ? 0 : series.hashCode()); result = prime * result + seriesOrder; + result = prime * result + fleetOrder; return result; } @@ -115,7 +122,13 @@ public class BoatClassSeriesFleet { return false; if (seriesOrder != other.seriesOrder) return false; + if (fleetOrder != other.fleetOrder) + return false; return true; } + + public int getFleetOrder() { + return fleetOrder; + } } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/comparators/BoatClassSeriesDataFleetComparator.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/comparators/BoatClassSeriesDataFleetComparator.java index d67ee6655c2..d1d03872efd 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/comparators/BoatClassSeriesDataFleetComparator.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/comparators/BoatClassSeriesDataFleetComparator.java @@ -20,7 +20,7 @@ public class BoatClassSeriesDataFleetComparator implements Comparator Date: Thu, 24 Jul 2014 22:03:53 +0200 Subject: [PATCH 252/323] fix tree-grow by using wind of next step for height comparison (ensure comparability of path candidates); cleanup tree-grow variety; --- .../impl/WindPatternDisplayManagerImpl.java | 2 +- .../sailing/simulator/test/TreeGrowTest.java | 4 +- .../impl/PathGeneratorTreeGrowTarget.java | 421 ---------- .../impl/PathGeneratorTreeGrowWind.java | 584 ++++++++++---- .../impl/PathGeneratorTreeGrowWind2.java | 566 ------------- .../impl/PathGeneratorTreeGrowWind3.java | 751 ------------------ .../simulator/impl/SailingSimulatorImpl.java | 2 +- .../simulator/impl/SimulatorUtils.java | 2 +- .../windfield/WindControlParameters.java | 2 +- 9 files changed, 426 insertions(+), 1908 deletions(-) delete mode 100644 java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorTreeGrowTarget.java delete mode 100644 java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorTreeGrowWind2.java delete mode 100644 java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorTreeGrowWind3.java diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/windpattern/impl/WindPatternDisplayManagerImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/windpattern/impl/WindPatternDisplayManagerImpl.java index ec2296132f3..82450651445 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/windpattern/impl/WindPatternDisplayManagerImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/windpattern/impl/WindPatternDisplayManagerImpl.java @@ -79,7 +79,7 @@ public class WindPatternDisplayManagerImpl implements WindPatternDisplayManager 1, 1, 10); display.addSetting(maxBlastSize); WindPatternSetting blastWindSpeed = new WindPatternSettingSliderBar("blastWindSpeed", - "Average Speed (%)", 0, 200, 5, 100, 10); + "Average Speed (%)", 0, 200, 5, 120, 10); display.addSetting(blastWindSpeed); WindPatternSetting blastWindSpeedVar = new WindPatternSettingSliderBar("blastWindSpeedVar", "Speed Variance (%)", 1e-4, 100, 5, 10, 10); diff --git a/java/com.sap.sailing.simulator.test/src/com/sap/sailing/simulator/test/TreeGrowTest.java b/java/com.sap.sailing.simulator.test/src/com/sap/sailing/simulator/test/TreeGrowTest.java index 4f50acfdbd8..6d102285d06 100644 --- a/java/com.sap.sailing.simulator.test/src/com/sap/sailing/simulator/test/TreeGrowTest.java +++ b/java/com.sap.sailing.simulator.test/src/com/sap/sailing/simulator/test/TreeGrowTest.java @@ -20,7 +20,7 @@ import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; import com.sap.sailing.simulator.Path; import com.sap.sailing.simulator.PolarDiagram; import com.sap.sailing.simulator.SimulationParameters; -import com.sap.sailing.simulator.impl.PathGeneratorTreeGrowWind3; +import com.sap.sailing.simulator.impl.PathGeneratorTreeGrowWind; import com.sap.sailing.simulator.impl.PolarDiagram49STG; import com.sap.sailing.simulator.impl.RectangularGrid; import com.sap.sailing.simulator.impl.SimulationParametersImpl; @@ -62,7 +62,7 @@ public class TreeGrowTest { param.setProperty("Djikstra.gridv[int]", 10.0); param.setProperty("Djikstra.gridh[int]", 100.0);*/ - PathGeneratorTreeGrowWind3 treeGrow = new PathGeneratorTreeGrowWind3(param); + PathGeneratorTreeGrowWind treeGrow = new PathGeneratorTreeGrowWind(param); Path path = treeGrow.getPath(); diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorTreeGrowTarget.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorTreeGrowTarget.java deleted file mode 100644 index 33a01c330cc..00000000000 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorTreeGrowTarget.java +++ /dev/null @@ -1,421 +0,0 @@ -package com.sap.sailing.simulator.impl; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.logging.Logger; - -import com.sap.sailing.domain.common.Bearing; -import com.sap.sailing.domain.common.Distance; -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.DegreeBearingImpl; -import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.tracking.Wind; -import com.sap.sailing.simulator.Path; -import com.sap.sailing.simulator.PolarDiagram; -import com.sap.sailing.simulator.SimulationParameters; -import com.sap.sailing.simulator.TimedPosition; -import com.sap.sailing.simulator.TimedPositionWithSpeed; -import com.sap.sailing.simulator.windfield.WindFieldGenerator; - -public class PathGeneratorTreeGrowTarget extends PathGeneratorBase { - - private static Logger logger = Logger.getLogger("com.sap.sailing"); - - int maxLeft; - int maxRight; - boolean startLeft; - - public PathGeneratorTreeGrowTarget(SimulationParameters params) { - this.parameters = params; - } - - public void setEvaluationParameters(int maxLeftVal, int maxRightVal, boolean startLeftVal) { - this.maxLeft = maxLeftVal; - this.maxRight = maxRightVal; - this.startLeft = startLeftVal; - } - - class PathCand implements Comparable { - public PathCand(TimedPosition pos, double vrt, double hrz, String path) { - this.pos = pos; - this.vrt = vrt; - this.hrz = hrz; - this.path = path; - } - - TimedPosition pos; - double vrt; - double hrz; - String path; - - /*@Override - // sort ascending by horizontal distance - public int compareTo(PathCand other) { - if (Math.abs(this.hrz) == Math.abs(other.hrz)) - return 0; - return (Math.abs(this.hrz) < Math.abs(other.hrz) ? -1 : +1); - }*/ - @Override - // sort descending by height - public int compareTo(PathCand other) { - if (this.vrt == other.vrt) { - if (Math.abs(this.hrz) == Math.abs(other.hrz)) { - return 0; - } else { - return (Math.abs(this.hrz) < Math.abs(other.hrz) ? -1 : +1); - } - } else { - return (this.vrt > other.vrt ? -1 : +1); - } - } - - } - - // generate step - TimedPosition getStep(TimedPosition pos, long timeStep, long turnLoss, char prevDirection, char nextDirection) { - - double offDeg = 5.0; - WindFieldGenerator wf = this.parameters.getWindField(); - TimePoint curTime = pos.getTimePoint(); - Position curPosition = pos.getPosition(); - Wind posWind = wf.getWind(new TimedPositionWithSpeedImpl(curTime, curPosition, null)); - - PolarDiagram pd = this.parameters.getBoatPolarDiagram(); - pd.setWind(posWind); - - // get beat-angle left and right - Bearing travelBearing = null; - Bearing tmpBearing = null; - if (nextDirection == 'L') { - travelBearing = pd.optimalDirectionsUpwind()[0]; - } else if (nextDirection == 'R') { - travelBearing = pd.optimalDirectionsUpwind()[1]; - } else if (nextDirection == 'M') { - tmpBearing = pd.optimalDirectionsUpwind()[0]; - travelBearing = tmpBearing.add(new DegreeBearingImpl(+offDeg)); - } else if (nextDirection == 'S') { - tmpBearing = pd.optimalDirectionsUpwind()[1]; - travelBearing = tmpBearing.add(new DegreeBearingImpl(-offDeg)); - } - - // determine beat-speed left and right - SpeedWithBearing travelSpeed = pd.getSpeedAtBearing(travelBearing); - - TimePoint travelTime; - TimePoint nextTime = new MillisecondsTimePoint(curTime.asMillis()+timeStep); - char prevBaseDirection = prevDirection; - if (prevBaseDirection == 'M') { - prevBaseDirection = 'L'; - } - if (prevBaseDirection == 'S') { - prevBaseDirection = 'R'; - } - char nextBaseDirection = nextDirection; - if (nextBaseDirection == 'M') { - nextBaseDirection = 'L'; - } - if (nextBaseDirection == 'S') { - nextBaseDirection = 'R'; - } - boolean sameBaseDirection = (nextBaseDirection == prevBaseDirection)||(prevBaseDirection == '0'); - if (sameBaseDirection) { - travelTime = nextTime; - } else { - travelTime = new MillisecondsTimePoint(nextTime.asMillis() - turnLoss); - } - - return new TimedPositionImpl(nextTime, travelSpeed.travelTo(curPosition, curTime, travelTime)); - } - - PathCand getPathCand(PathCand path, char nextDirection, long timeStep, long turnLoss, Position posStart, Bearing bearVrt, double tgtHeight) { - - // calculate next path position (taking turn-loss into account) - TimedPosition pathPos = this.getStep(path.pos, timeStep, turnLoss, path.path.charAt(path.path.length()-1), nextDirection); - - // calculate height-position with reference to race course - Position posHeight = pathPos.getPosition().projectToLineThrough(posStart, bearVrt); - - // calculate vertical distance as distance of height-position to start - double vrtDist = Math.round(posHeight.getDistance(posStart).getMeters()*100.0)/100.0; - /*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 - double posSide = 1; - double posBear = posStart.getBearingGreatCircle(pathPos.getPosition()).getDegrees(); - if ((posBear < 0.0)||(posBear > 180.0)) { - posSide = -1; - } else if ((posBear == 0.0)||(posBear == 180.0)) { - posSide = 0; - } - // calculate horizontal distance as distance of height-position to current position - double hrzDist = Math.round(posSide*posHeight.getDistance(pathPos.getPosition()).getMeters()*100.0)/100.0; - - //System.out.println(""+hrzDist+", "+vrtDist+", "+pathPos.getPosition().getLatDeg()+", "+pathPos.getPosition().getLngDeg()+", "+posHeight.getLatDeg()+", "+posHeight.getLngDeg()); - - // extend path-string by step-direction - String pathStr = path.path + nextDirection; - - return (new PathCand(pathPos, vrtDist, hrzDist, pathStr)); - } - - // generate path candidates based on beat angles - List getPathCandsBeat(PathCand path, long timeStep, long turnLoss, Position posStart, Bearing bearVrt, double tgtHeight) { - - List result = new ArrayList(); - PathCand newPathCand; - - // step left - newPathCand = getPathCand(path, 'L', timeStep, turnLoss, posStart, bearVrt, tgtHeight); - result.add(newPathCand); - - // step wide left - newPathCand = getPathCand(path, 'M', timeStep, turnLoss, posStart, bearVrt, tgtHeight); - result.add(newPathCand); - - // step right - newPathCand = getPathCand(path, 'R', timeStep, turnLoss, posStart, bearVrt, tgtHeight); - result.add(newPathCand); - - // step wide right - newPathCand = getPathCand(path, 'S', timeStep, turnLoss, posStart, bearVrt, tgtHeight); - result.add(newPathCand); - - return result; - } - - @Override - public Path getPath() { - - WindFieldGenerator wf = this.parameters.getWindField(); - PolarDiagram pd = this.parameters.getBoatPolarDiagram(); - Position startPos = this.parameters.getCourse().get(0); - Position endPos = this.parameters.getCourse().get(1); - TimePoint startTime = wf.getStartTime();// new MillisecondsTimePoint(0); - List path = new ArrayList(); - - Position currentPosition = startPos; - TimePoint currentTime = startTime; - - Distance distStartEnd = startPos.getDistance(endPos); - double distStartEndMeters = distStartEnd.getMeters(); - - long timeStep = wf.getTimeStep().asMillis()/3; - logger.info("Time step :" + timeStep); - long turnLoss = pd.getTurnLoss(); // 4000; // time lost when doing a turn - - Wind wndStart = wf.getWind(new TimedPositionWithSpeedImpl(startTime, startPos, null)); - logger.fine("wndStart speed:" + wndStart.getKnots() + " angle:" + wndStart.getBearing().getDegrees()); - pd.setWind(wndStart); - Bearing bearVrt = startPos.getBearingGreatCircle(endPos); - //Bearing bearHrz = bearVrt.add(new DegreeBearingImpl(90.0)); - Position middlePos = startPos.translateGreatCircle(bearVrt, distStartEnd.scale(0.5)); - - System.out.println("start : "+startPos.getLatDeg()+", "+startPos.getLngDeg()); - System.out.println("middle: "+middlePos.getLatDeg()+", "+middlePos.getLngDeg()); - System.out.println("end : "+endPos.getLatDeg()+", "+endPos.getLngDeg()); - - // initialize list of paths - PathCand initPath = new PathCand(new TimedPositionImpl(currentTime, currentPosition), 0.0, 0.0, "0"); - String initPathStr = "0"; - //String initPathStr = "0RRRRRRRRRRRRRRRLLL"; - //String initPathStr = "0RRRRRRRRRRRRRRRRRR"; - //PathCand initPath = new PathCand(new TimedPositionImpl(currentTime, currentPosition), 0.0, 0.0, ); //"0RRRRRRRRRRRRRRR"); - if (initPathStr.length()>1) { - //char prevDirection = '0'; - char nextDirection = '0'; - for(int idx=1; idx allPaths = new ArrayList(); - List trgPaths = new ArrayList(); - allPaths.add(initPath); - - - TimedPosition tstPosition = this.getStep(new TimedPositionImpl(startTime, startPos), timeStep, turnLoss, '0', 'L'); - double tstDist1 = startPos.getDistance(tstPosition.getPosition()).getMeters(); - tstPosition = this.getStep(new TimedPositionImpl(startTime, startPos), timeStep, turnLoss, '0', 'R'); - double tstDist2 = startPos.getDistance(tstPosition.getPosition()).getMeters(); - - double hrzBinSize = (tstDist1 + tstDist2)/3; // horizontal bin size in meters - System.out.println("Horizontal Bin Size: "+hrzBinSize); - - double oobFact = 0.75; // out-of-bounds factor - //int hrzLeft = (int)Math.round(Math.floor( -distStartEndMeters / 2.0 / hrzBinSize / oobFact )); - //int hrzRight = (int)Math.round(Math.floor( distStartEndMeters / 2.0 / hrzBinSize / oobFact )); - - boolean reachedEnd = false; - int addSteps = 0; - int finalSteps = 0; // maximum number of additional steps after first target-path found - //for(int count=0; count<10; count++) { - while ((!reachedEnd)||(addSteps newPathCands; - List newPaths = new ArrayList(); - for(PathCand curPath : allPaths) { - - if ((curPath.vrt > distStartEndMeters)) { - continue; - } else { - newPathCands = this.getPathCandsBeat(curPath, timeStep, turnLoss, startPos, bearVrt, distStartEndMeters); - } - - for(PathCand newPath : newPathCands) { - - newPaths.add(newPath); - if (newPath.hrz < hrzMin) { - hrzMin = newPath.hrz; - } - if (newPath.hrz > hrzMax) { - hrzMax = newPath.hrz; - } - } - } - - int hrzLeft = (int)Math.round(Math.floor( (hrzMin + hrzBinSize/2.0) / hrzBinSize )); - int hrzRight = (int)Math.round(Math.floor( (hrzMax + hrzBinSize/2.0) / hrzBinSize )); - //System.out.println("hrzLeft: "+hrzLeft+", hrzRight: "+hrzRight); - - // build map of best path per hrz-bin - int countPath = 0; - int mapSize = hrzRight-hrzLeft+1; - //System.out.println("MapSize: "+mapSize); - - int[] binIdx = new int[mapSize]; // TODO: init with zero? - double[] binVal = new double[mapSize]; // TODO: init with zero? - for(int curIdx=0; curIdx oobFact*distStartEndMeters) { - continue; // ignore curPath - } - - // increase path-counter - countPath++; - - // determine map-key - int curKey = (int)Math.round(Math.floor( (curPath.hrz + hrzBinSize/2.0) / hrzBinSize )) - hrzLeft; - - /*String allR = "0" + (new String(new char[curPath.path.length()-1]).replace('\0', 'R')); - if (curPath.path.equals(allR)) { - System.out.println(""+curPath.path+": "+curPath.hrz+", "+curPath.vrt+", "+curPath.pos.getPosition().getLatDeg()+", "+curPath.pos.getPosition().getLngDeg()); - }*/ - - // check whether curPath is better then the ones looked at - if ((curKey>=0)&&(curKey < mapSize)) { - if (curPath.vrt > binVal[curKey]) { - binVal[curKey] = curPath.vrt; - binIdx[curKey] = curIdx+1; - } - } - } - - // check if there are still paths inside regatta-area - if (countPath > 0) { - // take best from each horizontal-bin and check if target is reached - allPaths = new ArrayList(); - for (int curKey = 0; curKey < mapSize; curKey++) { - - if (binIdx[curKey] > 0) { - PathCand curPath = newPaths.get(binIdx[curKey] - 1); - allPaths.add(curPath); - - // debug output - /*if ((Math.abs(curKey + hrzLeft) <= 3)) { - System.out.println("" + curPath.path + ": " + (curKey + hrzLeft) + ", vrt:" + curPath.vrt + ", hrz:" + curPath.hrz + ", bin:" + (Math.floor((curPath.hrz + hrzBinSize / 2.0) / hrzBinSize))); - }*/ - - // terminate path-search if paths close enough to target are found - if ((curPath.vrt > distStartEndMeters)) { - if ((Math.abs(curKey + hrzLeft) <= 2)) { - reachedEnd = true; - trgPaths.add(curPath); // add path to list of target-paths - } - } - } - } - } else { - // terminate path-search as no path inside regatta-area are left - reachedEnd = true; - } - } - - - // if no target-paths were found, return empty path - if (trgPaths.size() == 0) { - //trgPaths = allPaths; // TODO: only for testing; remove lateron - return new PathImpl(path, wf); // return empty path - } - - // sort target-paths ascending by distance-to-target - Collections.sort(trgPaths); - - // debug output - for(PathCand curPath : trgPaths) { - 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)); - } - - // - // fill gwt-path - // - - // generate intermediate steps - PathCand bstPath = trgPaths.get(0); // target-path ending closest to target - TimedPositionWithSpeed curPosition = null; - char nextDirection = '0'; - char prevDirection = '0'; - for(int step=0; step<(bstPath.path.length()-1); step++) { - - nextDirection = bstPath.path.charAt(step); - - if (nextDirection == '0') { - - curPosition = new TimedPositionWithSpeedImpl(startTime, startPos, null); - path.add(curPosition); - - } else { - - TimedPosition newPosition = this.getStep(curPosition, timeStep, turnLoss, prevDirection, nextDirection); - curPosition = new TimedPositionWithSpeedImpl(newPosition.getTimePoint(), newPosition.getPosition(), null); - path.add(curPosition); - - } - - prevDirection = nextDirection; - } - - // add final position (rescaled before to end on height of target) - path.add(new TimedPositionWithSpeedImpl(bstPath.pos.getTimePoint(), bstPath.pos.getPosition(), null)); - - return new PathImpl(path, wf); - - } - -} 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 6e843497337..f398ffe6eb1 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 @@ -1,7 +1,11 @@ package com.sap.sailing.simulator.impl; +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.logging.Logger; @@ -27,57 +31,62 @@ 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 int maxTurns = 0; + boolean upwindLeg = false; String initPathStr = "0"; PathCandidate bestCand = null; + long usedTimeStep = 0; + boolean gridStore = false; + ArrayList> gridPositions = null; + ArrayList> isocPositions = null; + String gridFile = null; public PathGeneratorTreeGrowWind(SimulationParameters params) { this.parameters = params; } - public void setEvaluationParameters(String startDirection, int maxTurns) { + public void setEvaluationParameters(String startDirection, int maxTurns, String gridFile) { if (startDirection != null) { this.initPathStr = "0" + startDirection; } else { this.initPathStr = "0"; } this.maxTurns = maxTurns; + this.gridFile = gridFile; + if (this.gridFile != null) { + this.gridStore = true; + this.gridPositions = new ArrayList>(); + this.isocPositions = new ArrayList>(); + } else { + this.gridStore = false; + this.gridPositions = null; + this.isocPositions = null; + } } - class PathCandidate implements Comparable { - public PathCandidate(TimedPosition pos, double vrt, double hrz, int trn, String path) { - this.pos = pos; - this.vrt = vrt; - this.hrz = hrz; - this.trn = trn; - this.path = path; + class SortPathCandsAbsHorizontally implements Comparator { + + @Override + public int compare(PathCandidate p1, PathCandidate p2) { + if (Math.abs(p1.hrz) == Math.abs(p2.hrz)) { + return 0; + } else { + return (Math.abs(p1.hrz) < Math.abs(p2.hrz) ? -1 : +1); + } } - TimedPosition pos; - double vrt; - double hrz; - int trn; - String path; + } + + class SortPathCandsHorizontally implements Comparator { - /*@Override - // sort ascending by horizontal distance - public int compareTo(PathCand other) { - if (Math.abs(this.hrz) == Math.abs(other.hrz)) - return 0; - return (Math.abs(this.hrz) < Math.abs(other.hrz) ? -1 : +1); - }*/ @Override - // sort descending by height - public int compareTo(PathCandidate other) { - if (this.vrt == other.vrt) { - if (Math.abs(this.hrz) == Math.abs(other.hrz)) { - return 0; - } else { - return (Math.abs(this.hrz) < Math.abs(other.hrz) ? -1 : +1); - } + public int compare(PathCandidate p1, PathCandidate p2) { + if (p1.hrz == p2.hrz) { + return 0; } else { - return (this.vrt > other.vrt ? -1 : +1); - } + return (p1.hrz < p2.hrz ? -1 : +1); + } } } @@ -86,36 +95,57 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase { PathCandidate getBestCand() { return this.bestCand; } + + long getUsedTimeStep() { + return this.usedTimeStep; + } // generate step in one of the possible directions // default: L - left, R - right // extended: M - wide left, S - wide right - Util.Pair getStep(TimedPosition pos, long timeStep, long turnLoss, boolean sameBaseDirection, char nextDirection) { + TimedPosition getStep(TimedPosition pos, long timeStep, long turnLoss, boolean sameBaseDirection, char nextDirection) { - double offDeg = 5.0; + double offDeg = 3.0; WindFieldGenerator wf = this.parameters.getWindField(); TimePoint curTime = pos.getTimePoint(); Position curPosition = pos.getPosition(); - Wind posWind = wf.getWind(new TimedPositionWithSpeedImpl(curTime, curPosition, null)); + Wind posWind = wf.getWind(new TimedPositionImpl(curTime, curPosition)); PolarDiagram pd = this.parameters.getBoatPolarDiagram(); pd.setWind(posWind); - Wind appWind = new WindImpl(posWind.getPosition(), posWind.getTimePoint(), pd.getWind());; // get beat-angle left and right Bearing travelBearing = null; Bearing tmpBearing = null; if (nextDirection == 'L') { - travelBearing = pd.optimalDirectionsUpwind()[0]; + if (this.upwindLeg) { + travelBearing = pd.optimalDirectionsUpwind()[0]; + } else { + travelBearing = pd.optimalDirectionsDownwind()[0]; + } } else if (nextDirection == 'R') { - travelBearing = pd.optimalDirectionsUpwind()[1]; + if (this.upwindLeg) { + travelBearing = pd.optimalDirectionsUpwind()[1]; + } else { + travelBearing = pd.optimalDirectionsDownwind()[1]; + } } else if (nextDirection == 'M') { - tmpBearing = pd.optimalDirectionsUpwind()[0]; - travelBearing = tmpBearing.add(new DegreeBearingImpl(+offDeg)); + if (this.upwindLeg) { + tmpBearing = pd.optimalDirectionsUpwind()[0]; + travelBearing = tmpBearing.add(new DegreeBearingImpl(-offDeg)); + } else { + tmpBearing = pd.optimalDirectionsDownwind()[0]; + travelBearing = tmpBearing.add(new DegreeBearingImpl(-offDeg)); + } } else if (nextDirection == 'S') { - tmpBearing = pd.optimalDirectionsUpwind()[1]; - travelBearing = tmpBearing.add(new DegreeBearingImpl(-offDeg)); + if (this.upwindLeg) { + tmpBearing = pd.optimalDirectionsUpwind()[1]; + travelBearing = tmpBearing.add(new DegreeBearingImpl(+offDeg)); + } else { + tmpBearing = pd.optimalDirectionsDownwind()[1]; + travelBearing = tmpBearing.add(new DegreeBearingImpl(+offDeg)); + } } // determine beat-speed left and right @@ -129,7 +159,8 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase { travelTime = new MillisecondsTimePoint(nextTime.asMillis() - turnLoss); } - return new Util.Pair(new TimedPositionImpl(nextTime, travelSpeed.travelTo(curPosition, curTime, travelTime)), appWind); + Position nextPosition = travelSpeed.travelTo(curPosition, curTime, travelTime); + return new TimedPositionImpl(nextTime, nextPosition); } // use base direction to distinguish direction changes that do or don't require a turn @@ -167,20 +198,29 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase { } // calculate next path position (taking turn-loss into account) - Util.Pair nextStep = this.getStep(path.pos, timeStep, turnLoss, sameBaseDirection, nextDirection); - TimedPosition pathPos = nextStep.getA(); - Wind posWind = nextStep.getB(); + TimedPosition pathPos = this.getStep(path.pos, timeStep, turnLoss, sameBaseDirection, nextDirection); + + // determine apparent wind at next path position & time + Wind posWind = this.parameters.getWindField().getWind(pathPos); + PolarDiagram pd = this.parameters.getBoatPolarDiagram(); + pd.setWind(posWind); + Wind appWind = new WindImpl(posWind.getPosition(), posWind.getTimePoint(), pd.getWind()); - // calculate height-position with reference to race course - Position posHeight = pathPos.getPosition().projectToLineThrough(posEnd, posWind.getBearing()); - Bearing bearVrt = posStart.getBearingGreatCircle(posEnd); - Position posHeightTrgt = pathPos.getPosition().projectToLineThrough(posStart, bearVrt); - //Position posHeightWind = pathPos.getPosition().projectToLineThrough(posRef, posWind.getBearing()); + // calculate height-position with reference to apparent wind + Position posHeight = pathPos.getPosition().projectToLineThrough(posEnd, appWind.getBearing()); - // calculate vertical distance as distance of height-position to start - //double vrtDist = tgtHeight - Math.round(posHeight.getDistance(posEnd).getMeters()*100.0)/100.0; - double vrtDist = Math.round(posHeight.getDistance(posStart).getMeters()*100.0)/100.0; + // calculate vertical distance as distance of height-position to end + Bearing bearHeight = posEnd.getBearingGreatCircle(posHeight); + double bearHeightSide = appWind.getBearing().getDifferenceTo(bearHeight).getDegrees(); + double vrtSide = (this.upwindLeg ? -1.0 : +1.0); + if (Math.abs(bearHeightSide) > 170.0) { + vrtSide = (this.upwindLeg ? +1.0 : -1.0); + } + 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(); @@ -194,23 +234,23 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase { // calculate horizontal side: left or right in reference to race course double posSide = 1; - //double posBear = posWind.getBearing().getDegrees() - posEnd.getBearingGreatCircle(pathPos.getPosition()).getDegrees(); - double posBear = posStart.getBearingGreatCircle(pathPos.getPosition()).getDegrees(); - if ((posBear < 0.0)||(posBear > 180.0)) { + Bearing posBear = posStart.getBearingGreatCircle(pathPos.getPosition()); + Bearing bearVrt = posStart.getBearingGreatCircle(posEnd); + double posBearDiff = bearVrt.getDifferenceTo(posBear).getDegrees(); + if ((posBearDiff < 0.0)||(posBearDiff > 180.0)) { posSide = -1; - } else if ((posBear == 0.0)||(posBear == 180.0)) { + } else if ((posBearDiff == 0.0)||(posBearDiff == 180.0)) { posSide = 0; } // calculate horizontal distance as distance of height-position to current position - //double hrzDist = Math.round(posSide*posHeight.getDistance(pathPos.getPosition()).getMeters()*100.0)/100.0; - double hrzDist = Math.round(posSide*posHeightTrgt.getDistance(pathPos.getPosition()).getMeters()*100.0)/100.0; - - //System.out.println(""+hrzDist+", "+vrtDist+", "+pathPos.getPosition().getLatDeg()+", "+pathPos.getPosition().getLngDeg()+", "+posHeight.getLatDeg()+", "+posHeight.getLngDeg()); + Position posHeightTrgt = pathPos.getPosition().projectToLineThrough(posStart, bearVrt); + double hrzDist = Math.round(posSide*posHeightTrgt.getDistance(pathPos.getPosition()).getMeters()*1000.0)/1000.0; // extend path-string by step-direction String pathStr = path.path + nextDirection; + char nextBaseDirection = this.getBaseDirection(nextDirection); - return (new PathCandidate(pathPos, vrtDist, hrzDist, turnCount, pathStr)); + return (new PathCandidate(pathPos, vrtDist, hrzDist, turnCount, pathStr, nextBaseDirection, appWind)); } @@ -256,13 +296,200 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase { return result; } + Util.Pair,List> generateCandidate(List oldPaths, long timeStep, long turnLoss, Position posStart, Position posMiddle, Position posEnd, double tgtHeight) { + + List newPathCands; + List leftPaths = new ArrayList(); + List rightPaths = new ArrayList(); + for(PathCandidate curPath : oldPaths) { + + newPathCands = this.getPathCandsBeatWind(curPath, timeStep, turnLoss, posStart, posEnd, tgtHeight); + for (PathCandidate curNewPath : newPathCands) { + // check whether path is *outside* regatta-area + double distFromMiddleMeters = posMiddle.getDistance(curPath.pos.getPosition()).getMeters(); + if (distFromMiddleMeters > oobFact * tgtHeight) { + continue; // ignore curPath + } + + if (curNewPath.sid == 'L') { + leftPaths.add(curNewPath); + } else if (curNewPath.sid == 'R') { + rightPaths.add(curNewPath); + } + + } + + } + + Util.Pair,List> newPaths = new Util.Pair,List>(leftPaths, rightPaths); + return newPaths; + } + + + List filterCandidates(List allCands, double hrzBinWidth) { + + boolean[] filterMap = new boolean[allCands.size()]; + + // sort candidates by horizontal distance + Comparator sortHorizontal = new SortPathCandsHorizontally(); + Collections.sort(allCands, sortHorizontal); + + // start scan with index 0 + int idxL = 0; + int idxR = 0; + + // for each candidate, check the neighborhoods and identify bad candidates + for(int idx = 0; idx < allCands.size(); idx++) { + + // current horizontal distance + double hrzDist = allCands.get(idx).hrz; + + // align left index + while(Math.abs(hrzDist - allCands.get(idxL).hrz) > hrzBinWidth) { + idxL++; + } + + // align right index + boolean finished = false; + while(!finished && (idxR < (allCands.size()-1))) { + if (Math.abs(hrzDist - allCands.get(idxR+1).hrz) <= hrzBinWidth) { + idxR++; + } else { + finished = true; + } + } + + // search maximum height + // in neighborhood idxL, ..., idxR + + // init max for search + int vrtIdx = idxL; + double vrtMax = allCands.get(vrtIdx).vrt; + filterMap[vrtIdx] = false; + + // evaluate remainder of neighborhood + if (idxL < idxR) { + for(int jdx = (idxL+1); jdx <= idxR; jdx++) { + if (allCands.get(jdx).vrt > vrtMax) { + // reset previous max candidate + filterMap[vrtIdx] = true; + // keep max height + vrtMax = allCands.get(jdx).vrt; + // keep max index + vrtIdx = jdx; + // set current max candidate + filterMap[vrtIdx] = false; + } else { + filterMap[jdx] = true; + } + } + } + + } // endfor each candidate + + // collect all good candidates (i.e. filterMap == false) + List filterCands = new ArrayList(); + for(int idx=0; idx < allCands.size(); idx++) { + if (!filterMap[idx]) { + filterCands.add(allCands.get(idx)); + } + } + + // return remaining good candidates + return filterCands; + } + + + List filterIsochrone(List allCands, double hrzBinWidth) { + + boolean[] filterMap = new boolean[allCands.size()]; + for(int idx = 0; idx < allCands.size(); idx++) { + filterMap[idx] = true; + } + + // sort candidates by horizontal distance + Comparator sortHorizontal = new SortPathCandsHorizontally(); + Collections.sort(allCands, sortHorizontal); + + // start scan with index 0 + int idxL = 0; + int idxR = 0; + + // for each candidate, check the neighborhoods and identify bad candidates + for(int idx = 0; idx < allCands.size(); idx++) { + + // current horizontal distance + double hrzDist = allCands.get(idx).hrz; + + // align left index + while(Math.abs(hrzDist - allCands.get(idxL).hrz) > hrzBinWidth) { + idxL++; + } + + // align right index + boolean finished = false; + while(!finished && (idxR < (allCands.size()-1))) { + if (Math.abs(hrzDist - allCands.get(idxR+1).hrz) <= hrzBinWidth) { + idxR++; + } else { + finished = true; + } + } + + // search maximum height + // in neighborhood idxL, ..., idxR + + // init max for search + ArrayList vrtIdx = new ArrayList(); + vrtIdx.add(idxL); + double vrtMax = allCands.get(idxL).vrt; + + // evaluate remainder of neighborhood + if (idxL < idxR) { + for(int jdx = (idxL+1); jdx <= idxR; jdx++) { + if (allCands.get(jdx).vrt > vrtMax) { + // keep max height + vrtMax = allCands.get(jdx).vrt; + // keep max index + vrtIdx = new ArrayList(); + vrtIdx.add(jdx); + } else if (allCands.get(jdx).vrt == vrtMax) { + // add further max indexes + vrtIdx.add(jdx); + } + } + } + + for(Integer jdx : vrtIdx) { + filterMap[jdx] = false; + } + + } // endfor each candidate + + // collect all good candidates (i.e. filterMap == false) + List filterCands = new ArrayList(); + for(int idx=0; idx < allCands.size(); idx++) { + if (!filterMap[idx]) { + filterCands.add(allCands.get(idx)); + } + } + + // return remaining good candidates + return filterCands; + } + @Override public Path getPath() { - WindFieldGenerator wf = this.parameters.getWindField(); PolarDiagram pd = this.parameters.getBoatPolarDiagram(); + Position startPos = this.parameters.getCourse().get(0); Position endPos = this.parameters.getCourse().get(1); + + // test downwind: exchange start and end + //Position startPos = this.parameters.getCourse().get(1); + //Position endPos = this.parameters.getCourse().get(0); + TimePoint startTime = wf.getStartTime();// new MillisecondsTimePoint(0); List path = new ArrayList(); @@ -272,25 +499,42 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase { Distance distStartEnd = startPos.getDistance(endPos); double distStartEndMeters = distStartEnd.getMeters(); - long timeStep = wf.getTimeStep().asMillis()/3; - logger.info("Time step :" + timeStep); - long turnLoss = pd.getTurnLoss(); // 4000; // time lost when doing a turn - Wind wndStart = wf.getWind(new TimedPositionWithSpeedImpl(startTime, startPos, null)); logger.fine("wndStart speed:" + wndStart.getKnots() + " angle:" + wndStart.getBearing().getDegrees()); pd.setWind(wndStart); Bearing bearVrt = startPos.getBearingGreatCircle(endPos); //Bearing bearHrz = bearVrt.add(new DegreeBearingImpl(90.0)); Position middlePos = startPos.translateGreatCircle(bearVrt, distStartEnd.scale(0.5)); - + + Bearing bearRCWind = wndStart.getBearing().getDifferenceTo(bearVrt); + String legType = "downwind"; + this.upwindLeg = false; + + if ((Math.abs(bearRCWind.getDegrees()) > 90.0)&&(Math.abs(bearRCWind.getDegrees()) < 270.0)) { + legType = "upwind"; + this.upwindLeg = true; + } + if (debugMsgOn) { System.out.println("start : "+startPos.getLatDeg()+", "+startPos.getLngDeg()); System.out.println("middle: "+middlePos.getLatDeg()+", "+middlePos.getLngDeg()); System.out.println("end : "+endPos.getLatDeg()+", "+endPos.getLngDeg()); } + 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 + if (!this.upwindLeg) { + turnLoss = turnLoss / 2; + } + // calculate initial position according to initPathStr - PathCandidate initPath = new PathCandidate(new TimedPositionImpl(currentTime, currentPosition), 0.0, 0.0, 0, "0"); + PathCandidate initPath = new PathCandidate(new TimedPositionImpl(currentTime, currentPosition), 0.0, 0.0, 0, "0", '0', wndStart); if (initPathStr.length()>1) { char nextDirection = '0'; for(int idx=1; idx newPathCands; - List newPaths = new ArrayList(); - for(PathCandidate curPath : allPaths) { + // generate new candidates (inside regatta-area) + Util.Pair,List> newPaths = this.generateCandidate(allPaths, timeStep, turnLoss, startPos, middlePos, endPos, distStartEndMeters); - if ((curPath.vrt > distStartEndMeters)) { - continue; - } else { - //newPathCands = this.getPathCandsBeat(curPath, timeStep, turnLoss, startPos, bearVrt, distStartEndMeters); - newPathCands = this.getPathCandsBeatWind(curPath, timeStep, turnLoss, startPos, endPos, distStartEndMeters); + + // select good candidates + List leftPaths = this.filterCandidates(newPaths.getA(), hrzBinSize/2.0); + List rightPaths = this.filterCandidates(newPaths.getB(), hrzBinSize/2.0); + + List nextPaths = new ArrayList (); + nextPaths.addAll(leftPaths); + nextPaths.addAll(rightPaths); + + allPaths = nextPaths; + + if (this.gridStore) { + + /*ArrayList isoChrone = new ArrayList(); + for(PathCand curCand : allPaths) { + isoChrone.add(curCand.pos); } + this.gridPositions.add(isoChrone);*/ - for(PathCandidate newPath : newPathCands) { + this.gridPositions.add(allPaths); + + List isocPaths = this.filterIsochrone(allPaths, hrzBinSize); + this.isocPositions.add(isocPaths); - newPaths.add(newPath); - if (newPath.hrz < hrzMin) { - hrzMin = newPath.hrz; - } - if (newPath.hrz > hrzMax) { - hrzMax = newPath.hrz; - } - } } - int hrzLeft = (int)Math.round(Math.floor( (hrzMin + hrzBinSize/2.0) / hrzBinSize )); - int hrzRight = (int)Math.round(Math.floor( (hrzMax + hrzBinSize/2.0) / hrzBinSize )); - //System.out.println("hrzLeft: "+hrzLeft+", hrzRight: "+hrzRight); + // check if there are still paths in the regatta-area + if (allPaths.size() > 0) { - // build map of best path per hrz-bin - int countPath = 0; - int mapSize = hrzRight-hrzLeft+1; - //System.out.println("MapSize: "+mapSize); - - int[] binIdx = new int[mapSize]; // TODO: init with zero? - double[] binVal = new double[mapSize]; - // TODO: init of binVal - /*for(int idx = 0; idx < mapSize; idx++) { - binVal[idx] = 2*distStartEndMeters; - }*/ - for(int curIdx=0; curIdx 3900) { - if (debugMsgOn) { - System.out.println(""+curPath.path+": "+curPath.hrz+", "+curPath.vrt+", "+curPath.pos.getPosition().getLatDeg()+", "+curPath.pos.getPosition().getLngDeg()); - } - //} - - // check whether path is *outside* regatta-area - double distFromMiddleMeters = middlePos.getDistance(curPath.pos.getPosition()).getMeters(); - if (distFromMiddleMeters > oobFact*distStartEndMeters) { - continue; // ignore curPath - } - - // increase path-counter - countPath++; - - // determine map-key - int curKey = (int)Math.round(Math.floor( (curPath.hrz + hrzBinSize/2.0) / hrzBinSize )) - hrzLeft; - - /*String allR = "0" + (new String(new char[curPath.path.length()-1]).replace('\0', 'R')); - if (curPath.path.equals(allR)) { - System.out.println(""+curPath.path+": "+curPath.hrz+", "+curPath.vrt+", "+curPath.pos.getPosition().getLatDeg()+", "+curPath.pos.getPosition().getLngDeg()); - }*/ - - // check whether curPath is better then the ones looked at - if ((curKey>=0)&&(curKey < mapSize)) { - if (curPath.vrt > binVal[curKey]) { - binVal[curKey] = curPath.vrt; - binIdx[curKey] = curIdx+1; - } - } - } - - // check if there are still paths inside regatta-area - if (countPath > 0) { - // take best from each horizontal-bin and check if target is reached - allPaths = new ArrayList(); - for (int curKey = 0; curKey < mapSize; curKey++) { - - if (binIdx[curKey] > 0) { - PathCandidate curPath = newPaths.get(binIdx[curKey] - 1); - allPaths.add(curPath); - - // debug output - /*if ((Math.abs(curKey + hrzLeft) <= 10)) { - System.out.println("" + curPath.path + ": " + (curKey + hrzLeft) + ", vrt:" + curPath.vrt + ", hrz:" + curPath.hrz + ", bin:" + (Math.floor((curPath.hrz + hrzBinSize / 2.0) / hrzBinSize))); - }*/ - - // terminate path-search if paths close enough to target are found - if ((curPath.vrt > distStartEndMeters)) { - if ((Math.abs(curKey + hrzLeft) <= 3)) { - reachedEnd = true; - trgPaths.add(curPath); // add path to list of target-paths - } + 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)) { + //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)) { + reachedEnd = true; + trgPaths.add(curPath); // add path to list of target-paths } } } + } else { // terminate path-search as no path inside regatta-area are left reachedEnd = true; } + + } + + if (this.gridStore) { + + double distResolution = distStartEndMeters*0.01; + BufferedWriter outputCSV; + try { + outputCSV = new BufferedWriter(new FileWriter(this.gridFile+"-grid.csv")); + outputCSV.write("step; lat; lng; time; side; path; vrt\n"); + outputCSV.write("0; "+startPos.getLatDeg()+"; "+startPos.getLngDeg()+"; "+(startTime.asMillis()/1000)+"; 0; 0; "+(-distStartEndMeters)+"\n"); + outputCSV.write("0; "+endPos.getLatDeg()+"; "+endPos.getLngDeg()+"; "+(startTime.asMillis()/1000)+"; 0; 0; 0\n"); + int stepCount = 0; + for(List isoChrone : this.gridPositions) { + stepCount++; + PathCandidate prevPos = null; + for(PathCandidate isoPos : isoChrone) { + + if (prevPos != null) { + if (prevPos.pos.getPosition().getDistance(isoPos.pos.getPosition()).getMeters() < distResolution) { + continue; + } + } + + String outStr = ""+stepCount+"; "+isoPos.pos.getPosition().getLatDeg()+"; "+isoPos.pos.getPosition().getLngDeg()+"; "+(isoPos.pos.getTimePoint().asMillis()/1000)+"; "+isoPos.sid; + outStr += "; "+isoPos.path+"; "+isoPos.vrt; + outStr += "\n"; + outputCSV.write(outStr); + + prevPos = isoPos; + } + } + outputCSV.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + try { + outputCSV = new BufferedWriter(new FileWriter(this.gridFile+"-isoc.csv")); + outputCSV.write("step; lat; lng; time; side; path; vrt\n"); + outputCSV.write("0; "+startPos.getLatDeg()+"; "+startPos.getLngDeg()+"; "+(startTime.asMillis()/1000)+"; 0; 0; "+(-distStartEndMeters)+"\n"); + outputCSV.write("0; "+endPos.getLatDeg()+"; "+endPos.getLngDeg()+"; "+(startTime.asMillis()/1000)+"; 0; 0; 0\n"); + int stepCount = 0; + for(List isoChrone : this.isocPositions) { + stepCount++; + PathCandidate prevPos = null; + for(PathCandidate isoPos : isoChrone) { + + if (prevPos != null) { + if (prevPos.pos.getPosition().getDistance(isoPos.pos.getPosition()).getMeters() < distResolution) { + continue; + } + } + + String outStr = ""+stepCount+"; "+isoPos.pos.getPosition().getLatDeg()+"; "+isoPos.pos.getPosition().getLngDeg()+"; "+(isoPos.pos.getTimePoint().asMillis()/1000)+"; "+isoPos.sid; + outStr += "; "+isoPos.path+"; "+isoPos.vrt; + outStr += "\n"; + outputCSV.write(outStr); + + prevPos = isoPos; + } + } + outputCSV.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } @@ -443,13 +701,11 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase { Collections.sort(trgPaths); // debug output - //if (debugMsgOn) { 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"); //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)); } - //} // // fill gwt-path @@ -472,7 +728,7 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase { } else { boolean sameBaseDirection = this.isSameDirection(prevDirection, nextDirection); - TimedPosition newPosition = this.getStep(curPosition, timeStep, turnLoss, sameBaseDirection, nextDirection).getA(); + TimedPosition newPosition = this.getStep(curPosition, timeStep, turnLoss, sameBaseDirection, nextDirection); curPosition = new TimedPositionWithSpeedImpl(newPosition.getTimePoint(), newPosition.getPosition(), null); path.add(curPosition); diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorTreeGrowWind2.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorTreeGrowWind2.java deleted file mode 100644 index 8cdac6d5ca6..00000000000 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorTreeGrowWind2.java +++ /dev/null @@ -1,566 +0,0 @@ -package com.sap.sailing.simulator.impl; - -import java.io.BufferedWriter; -import java.io.FileWriter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.logging.Logger; - -import com.sap.sailing.domain.common.Bearing; -import com.sap.sailing.domain.common.Distance; -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.DegreeBearingImpl; -import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.tracking.Wind; -import com.sap.sailing.domain.tracking.impl.WindImpl; -import com.sap.sailing.simulator.Path; -import com.sap.sailing.simulator.PolarDiagram; -import com.sap.sailing.simulator.SimulationParameters; -import com.sap.sailing.simulator.TimedPosition; -import com.sap.sailing.simulator.TimedPositionWithSpeed; -import com.sap.sailing.simulator.windfield.WindFieldGenerator; -import com.sap.sse.common.Util; - -public class PathGeneratorTreeGrowWind2 extends PathGeneratorBase { - - private static Logger logger = Logger.getLogger("com.sap.sailing"); - private boolean debugMsgOn = false; - - double oobFact = 0.75; // out-of-bounds factor - int maxTurns = 0; - String initPathStr = "0"; - PathCandidate bestCand = null; - boolean gridStore = false; - ArrayList> gridPositions = null; - String gridFile = null; - - public PathGeneratorTreeGrowWind2(SimulationParameters params) { - this.parameters = params; - } - - public void setEvaluationParameters(String startDirection, int maxTurns, String gridFile) { - if (startDirection != null) { - this.initPathStr = "0" + startDirection; - } else { - this.initPathStr = "0"; - } - this.maxTurns = maxTurns; - this.gridFile = gridFile; - if (this.gridFile != null) { - this.gridStore = true; - this.gridPositions = new ArrayList>(); - } else { - this.gridStore = false; - this.gridPositions = null; - } - } - - class SortPathCandsAbsHorizontally implements Comparator { - - @Override - public int compare(PathCandidate p1, PathCandidate p2) { - if (Math.abs(p1.hrz) == Math.abs(p2.hrz)) { - return 0; - } else { - return (Math.abs(p1.hrz) < Math.abs(p2.hrz) ? -1 : +1); - } - } - - } - - class SortPathCandsHorizontally implements Comparator { - - @Override - public int compare(PathCandidate p1, PathCandidate p2) { - if (p1.hrz == p2.hrz) { - return 0; - } else { - return (p1.hrz < p2.hrz ? -1 : +1); - } - } - - } - - // getter for evaluating best path cand propoerties further - PathCandidate getBestCand() { - return this.bestCand; - } - - - // generate step in one of the possible directions - // default: L - left, R - right - // extended: M - wide left, S - wide right - Util.Pair getStep(TimedPosition pos, long timeStep, long turnLoss, boolean sameBaseDirection, char nextDirection) { - - double offDeg = 3.0; - WindFieldGenerator wf = this.parameters.getWindField(); - TimePoint curTime = pos.getTimePoint(); - Position curPosition = pos.getPosition(); - Wind posWind = wf.getWind(new TimedPositionWithSpeedImpl(curTime, curPosition, null)); - - PolarDiagram pd = this.parameters.getBoatPolarDiagram(); - pd.setWind(posWind); - Wind appWind = new WindImpl(posWind.getPosition(), posWind.getTimePoint(), pd.getWind());; - - // get beat-angle left and right - Bearing travelBearing = null; - Bearing tmpBearing = null; - if (nextDirection == 'L') { - travelBearing = pd.optimalDirectionsUpwind()[0]; - } else if (nextDirection == 'R') { - travelBearing = pd.optimalDirectionsUpwind()[1]; - } else if (nextDirection == 'M') { - tmpBearing = pd.optimalDirectionsUpwind()[0]; - travelBearing = tmpBearing.add(new DegreeBearingImpl(-offDeg)); - } else if (nextDirection == 'S') { - tmpBearing = pd.optimalDirectionsUpwind()[1]; - travelBearing = tmpBearing.add(new DegreeBearingImpl(+offDeg)); - } - - // determine beat-speed left and right - SpeedWithBearing travelSpeed = pd.getSpeedAtBearing(travelBearing); - - TimePoint travelTime; - TimePoint nextTime = new MillisecondsTimePoint(curTime.asMillis()+timeStep); - if (sameBaseDirection) { - travelTime = nextTime; - } else { - travelTime = new MillisecondsTimePoint(nextTime.asMillis() - turnLoss); - } - - return new Util.Pair(new TimedPositionImpl(nextTime, travelSpeed.travelTo(curPosition, curTime, travelTime)), appWind); - } - - // use base direction to distinguish direction changes that do or don't require a turn - char getBaseDirection(char direction) { - char baseDirection = direction; - - if (direction == 'M') { - baseDirection = 'L'; - } - if (direction == 'S') { - baseDirection = 'R'; - } - - return baseDirection; - } - - // check whether nextDirection is same base direction as previous direction, i.e. no turn - boolean isSameDirection(char prevDirection, char nextDirection) { - - char prevBaseDirection = this.getBaseDirection(prevDirection); - char nextBaseDirection = this.getBaseDirection(nextDirection); - - return ((nextBaseDirection == prevBaseDirection)||(prevBaseDirection == '0')); - } - - // get path candidate measuring height towards (local, current-apparent) wind - PathCandidate getPathCandWind(PathCandidate path, char nextDirection, long timeStep, long turnLoss, Position posStart, Position posEnd, double tgtHeight) { - - char prevDirection = path.path.charAt(path.path.length()-1); - boolean sameBaseDirection = this.isSameDirection(prevDirection, nextDirection); - - int turnCount = path.trn; - if (!sameBaseDirection) { - turnCount++; - } - - // calculate next path position (taking turn-loss into account) - Util.Pair nextStep = this.getStep(path.pos, timeStep, turnLoss, sameBaseDirection, nextDirection); - TimedPosition pathPos = nextStep.getA(); - Wind posWind = nextStep.getB(); - - // calculate height-position with reference to race course - Position posHeight = pathPos.getPosition().projectToLineThrough(posEnd, posWind.getBearing()); - Bearing bearVrt = posStart.getBearingGreatCircle(posEnd); - Position posHeightTrgt = pathPos.getPosition().projectToLineThrough(posStart, bearVrt); - //Position posHeightWind = pathPos.getPosition().projectToLineThrough(posRef, posWind.getBearing()); - - // calculate vertical distance as distance of height-position to start - //double vrtDist = Math.round(posHeightTrgt.getDistance(posStart).getMeters()*100.0)/100.0; - double vrtDist = Math.round(posHeight.getDistance(posStart).getMeters()*100.0)/100.0; - - /*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 - double posSide = 1; - //double posBear = posWind.getBearing().getDegrees() - posEnd.getBearingGreatCircle(pathPos.getPosition()).getDegrees(); - Bearing posBear = posStart.getBearingGreatCircle(pathPos.getPosition()); - double posBearDiff = bearVrt.getDifferenceTo(posBear).getDegrees(); - if ((posBearDiff < 0.0)||(posBearDiff > 180.0)) { - posSide = -1; - } else if ((posBearDiff == 0.0)||(posBearDiff == 180.0)) { - posSide = 0; - } - - // calculate horizontal distance as distance of height-position to current position - //double hrzDist = Math.round(posSide*posHeight.getDistance(pathPos.getPosition()).getMeters()*100.0)/100.0; - double hrzDist = Math.round(posSide*posHeightTrgt.getDistance(pathPos.getPosition()).getMeters()*100.0)/100.0; - - //System.out.println(""+hrzDist+", "+vrtDist+", "+pathPos.getPosition().getLatDeg()+", "+pathPos.getPosition().getLngDeg()+", "+posHeight.getLatDeg()+", "+posHeight.getLngDeg()); - - // extend path-string by step-direction - String pathStr = path.path + nextDirection; - char nextBaseDirection = this.getBaseDirection(nextDirection); - - return (new PathCandidate(pathPos, vrtDist, hrzDist, turnCount, pathStr, nextBaseDirection, posWind)); - } - - - // generate path candidates based on beat angles - List getPathCandsBeatWind(PathCandidate path, long timeStep, long turnLoss, Position posStart, Position posEnd, double tgtHeight) { - - List result = new ArrayList(); - PathCandidate newPathCand; - - if (this.maxTurns > 0) { - - char prevDirection = path.path.charAt(path.path.length()-1); - if ((path.trn < this.maxTurns)||(this.isSameDirection(prevDirection, 'L'))) { - newPathCand = getPathCandWind(path, 'L', timeStep, turnLoss, posStart, posEnd, tgtHeight); - result.add(newPathCand); - } - - if ((path.trn < this.maxTurns)||(this.isSameDirection(prevDirection, 'R'))) { - newPathCand = getPathCandWind(path, 'R', timeStep, turnLoss, posStart, posEnd, tgtHeight); - result.add(newPathCand); - } - - } else { - - // step left - newPathCand = getPathCandWind(path, 'L', timeStep, turnLoss, posStart, posEnd, tgtHeight); - result.add(newPathCand); - - // step wide left - //newPathCand = getPathCandWind(path, 'M', timeStep, turnLoss, posStart, posEnd, tgtHeight); - //result.add(newPathCand); - - // step right - newPathCand = getPathCandWind(path, 'R', timeStep, turnLoss, posStart, posEnd, tgtHeight); - result.add(newPathCand); - - // step wide right - //newPathCand = getPathCandWind(path, 'S', timeStep, turnLoss, posStart, posEnd, tgtHeight); - //result.add(newPathCand); - - } - - return result; - } - - Util.Pair,List> generateCandidate(List oldPaths, long timeStep, long turnLoss, Position posStart, Position posMiddle, Position posEnd, double tgtHeight) { - - List newPathCands; - List leftPaths = new ArrayList(); - List rightPaths = new ArrayList(); - for(PathCandidate curPath : oldPaths) { - - newPathCands = this.getPathCandsBeatWind(curPath, timeStep, turnLoss, posStart, posEnd, tgtHeight); - for (PathCandidate curNewPath : newPathCands) { - // check whether path is *outside* regatta-area - double distFromMiddleMeters = posMiddle.getDistance(curPath.pos.getPosition()).getMeters(); - if (distFromMiddleMeters > oobFact * tgtHeight) { - continue; // ignore curPath - } - - if (curNewPath.sid == 'L') { - leftPaths.add(curNewPath); - } else if (curNewPath.sid == 'R') { - rightPaths.add(curNewPath); - } - - } - - } - - Util.Pair,List> newPaths = new Util.Pair,List>(leftPaths, rightPaths); - return newPaths; - } - - - List filterCandidates(List allCands, double hrzBinWidth) { - - boolean[] filterMap = new boolean[allCands.size()]; - - // sort candidates by horizontal distance - Comparator sortHorizontal = new SortPathCandsHorizontally(); - Collections.sort(allCands, sortHorizontal); - - // start scan with index 0 - int idxL = 0; - int idxR = 0; - - // for each candidate, check the neighborhoos and identify bad candidates - for(int idx = 0; idx < allCands.size(); idx++) { - - // current horizontal distance - double hrzDist = allCands.get(idx).hrz; - - // align left index - while(Math.abs(hrzDist - allCands.get(idxL).hrz) > hrzBinWidth) { - idxL++; - } - - // align right index - boolean finished = false; - while(!finished && (idxR < (allCands.size()-1))) { - if (Math.abs(hrzDist - allCands.get(idxR+1).hrz) <= hrzBinWidth) { - idxR++; - } else { - finished = true; - } - } - - // search maximum height - // in neighborhood idxL, ..., idxR - - // init max for search - int vrtIdx = idxL; - double vrtMax = allCands.get(vrtIdx).vrt; - filterMap[vrtIdx] = false; - - // evaluate remainder of neighborhood - if (idxL < idxR) { - for(int jdx = (idxL+1); jdx <= idxR; jdx++) { - if (allCands.get(jdx).vrt > vrtMax) { - // reset previous max candidate - filterMap[vrtIdx] = true; - // keep max height - vrtMax = allCands.get(jdx).vrt; - // keep max index - vrtIdx = jdx; - // set current max candidate - filterMap[vrtIdx] = false; - } else { - filterMap[jdx] = true; - } - } - } - - } // endfor each candidate - - // collect all good candidates (i.e. filterMap == false) - List filterCands = new ArrayList(); - for(int idx=0; idx < allCands.size(); idx++) { - if (!filterMap[idx]) { - filterCands.add(allCands.get(idx)); - } - } - - // return remaining good candidates - return filterCands; - } - - @Override - public Path getPath() { - - WindFieldGenerator wf = this.parameters.getWindField(); - PolarDiagram pd = this.parameters.getBoatPolarDiagram(); - Position startPos = this.parameters.getCourse().get(0); - Position endPos = this.parameters.getCourse().get(1); - TimePoint startTime = wf.getStartTime();// new MillisecondsTimePoint(0); - List path = new ArrayList(); - - Position currentPosition = startPos; - TimePoint currentTime = startTime; - - Distance distStartEnd = startPos.getDistance(endPos); - double distStartEndMeters = distStartEnd.getMeters(); - - long timeStep = wf.getTimeStep().asMillis()/3; - logger.info("Time step :" + timeStep); - long turnLoss = pd.getTurnLoss(); // 4000; // time lost when doing a turn - - Wind wndStart = wf.getWind(new TimedPositionWithSpeedImpl(startTime, startPos, null)); - logger.fine("wndStart speed:" + wndStart.getKnots() + " angle:" + wndStart.getBearing().getDegrees()); - pd.setWind(wndStart); - Bearing bearVrt = startPos.getBearingGreatCircle(endPos); - //Bearing bearHrz = bearVrt.add(new DegreeBearingImpl(90.0)); - Position middlePos = startPos.translateGreatCircle(bearVrt, distStartEnd.scale(0.5)); - - if (debugMsgOn) { - System.out.println("start : "+startPos.getLatDeg()+", "+startPos.getLngDeg()); - System.out.println("middle: "+middlePos.getLatDeg()+", "+middlePos.getLngDeg()); - System.out.println("end : "+endPos.getLatDeg()+", "+endPos.getLngDeg()); - } - - // calculate initial position according to initPathStr - PathCandidate initPath = new PathCandidate(new TimedPositionImpl(currentTime, currentPosition), 0.0, 0.0, 0, "0", '0', wndStart); - if (initPathStr.length()>1) { - char nextDirection = '0'; - for(int idx=1; idx allPaths = new ArrayList(); - List trgPaths = new ArrayList(); - allPaths.add(initPath); - - - TimedPosition tstPosition = this.getStep(new TimedPositionImpl(startTime, startPos), timeStep, turnLoss, true, 'L').getA(); - double tstDist1 = startPos.getDistance(tstPosition.getPosition()).getMeters(); - tstPosition = this.getStep(new TimedPositionImpl(startTime, startPos), timeStep, turnLoss, true, 'R').getA(); - double tstDist2 = startPos.getDistance(tstPosition.getPosition()).getMeters(); - - double hrzBinSize = (tstDist1 + tstDist2)/2.0; // horizontal bin size in meters - if (debugMsgOn) { - System.out.println("Horizontal Bin Size: "+hrzBinSize); - } - - //double oobFact = 0.75; // out-of-bounds factor - boolean reachedEnd = false; - int addSteps = 0; - int finalSteps = 0; // maximum number of additional steps after first target-path found - - while ((!reachedEnd)||(addSteps,List> newPaths = this.generateCandidate(allPaths, timeStep, turnLoss, startPos, middlePos, endPos, distStartEndMeters); - - - // select good candidates - List leftPaths = this.filterCandidates(newPaths.getA(), hrzBinSize/2.0); - List rightPaths = this.filterCandidates(newPaths.getB(), hrzBinSize/2.0); - - List nextPaths = new ArrayList (); - nextPaths.addAll(leftPaths); - nextPaths.addAll(rightPaths); - - allPaths = nextPaths; - - if (this.gridStore) { - - /*ArrayList isoChrone = new ArrayList(); - for(PathCand curCand : allPaths) { - isoChrone.add(curCand.pos); - } - this.gridPositions.add(isoChrone);*/ - - this.gridPositions.add(allPaths); - - } - - // check if there are still paths in the regatta-area - if (allPaths.size() > 0) { - - for(PathCandidate curPath : allPaths) { - // terminate path-search if paths are found that are close enough to target - if ((curPath.vrt > distStartEndMeters)) { - int curBin = (int)Math.round(Math.floor( (curPath.hrz + hrzBinSize/2.0) / hrzBinSize )); - if ((Math.abs(curBin) <= 2)) { - reachedEnd = true; - trgPaths.add(curPath); // add path to list of target-paths - } - } - } - - } else { - // terminate path-search as no path inside regatta-area are left - reachedEnd = true; - } - - } - - if (this.gridStore) { - - BufferedWriter outputCSV; - try { - outputCSV = new BufferedWriter(new FileWriter(this.gridFile)); - outputCSV.write("step; lat; lng; time; side\n"); - int stepCount = 0; - for(List isoChrone : this.gridPositions) { - stepCount++; - for(PathCandidate isoPos : isoChrone) { - - outputCSV.write(""+stepCount+"; "+isoPos.pos.getPosition().getLatDeg()+"; "+isoPos.pos.getPosition().getLngDeg()+"; "+(isoPos.pos.getTimePoint().asMillis()/1000)+"; "+isoPos.sid+"\n"); - - } - } - outputCSV.close(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - } - - - // if no target-paths were found, return empty path - if (trgPaths.size() == 0) { - //trgPaths = allPaths; // TODO: only for testing; remove lateron - TimedPositionWithSpeed curPosition = new TimedPositionWithSpeedImpl(startTime, startPos, null); - path.add(curPosition); - return new PathImpl(path, wf); // return empty path - } - - // sort target-paths ascending by distance-to-target - Collections.sort(trgPaths); - - // debug output - //if (debugMsgOn) { - 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"); - //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)); - } - //} - - // - // fill gwt-path - // - - // generate intermediate steps - bestCand = trgPaths.get(0); // target-path ending closest to target - TimedPositionWithSpeed curPosition = null; - char nextDirection = '0'; - char prevDirection = '0'; - for(int step=0; step<(bestCand.path.length()-1); step++) { - - nextDirection = bestCand.path.charAt(step); - - if (nextDirection == '0') { - - curPosition = new TimedPositionWithSpeedImpl(startTime, startPos, null); - path.add(curPosition); - - } else { - - boolean sameBaseDirection = this.isSameDirection(prevDirection, nextDirection); - TimedPosition newPosition = this.getStep(curPosition, timeStep, turnLoss, sameBaseDirection, nextDirection).getA(); - curPosition = new TimedPositionWithSpeedImpl(newPosition.getTimePoint(), newPosition.getPosition(), null); - path.add(curPosition); - - } - - prevDirection = nextDirection; - } - - // add final position (rescaled before to end on height of target) - path.add(new TimedPositionWithSpeedImpl(bestCand.pos.getTimePoint(), bestCand.pos.getPosition(), null)); - - return new PathImpl(path, wf); - - } - -} diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorTreeGrowWind3.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorTreeGrowWind3.java deleted file mode 100644 index 6e80083d4d2..00000000000 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorTreeGrowWind3.java +++ /dev/null @@ -1,751 +0,0 @@ -package com.sap.sailing.simulator.impl; - -import java.io.BufferedWriter; -import java.io.FileWriter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.logging.Logger; - -import com.sap.sailing.domain.common.Bearing; -import com.sap.sailing.domain.common.Distance; -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.DegreeBearingImpl; -import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.tracking.Wind; -import com.sap.sailing.domain.tracking.impl.WindImpl; -import com.sap.sailing.simulator.Path; -import com.sap.sailing.simulator.PolarDiagram; -import com.sap.sailing.simulator.SimulationParameters; -import com.sap.sailing.simulator.TimedPosition; -import com.sap.sailing.simulator.TimedPositionWithSpeed; -import com.sap.sailing.simulator.windfield.WindFieldGenerator; -import com.sap.sse.common.Util; - -public class PathGeneratorTreeGrowWind3 extends PathGeneratorBase { - - private static Logger logger = Logger.getLogger("com.sap.sailing"); - private boolean debugMsgOn = false; - - double oobFact = 0.75; // out-of-bounds factor - int maxTurns = 0; - boolean upwindLeg = false; - String initPathStr = "0"; - PathCandidate bestCand = null; - long usedTimeStep = 0; - boolean gridStore = false; - ArrayList> gridPositions = null; - ArrayList> isocPositions = null; - String gridFile = null; - - public PathGeneratorTreeGrowWind3(SimulationParameters params) { - this.parameters = params; - } - - public void setEvaluationParameters(String startDirection, int maxTurns, String gridFile) { - if (startDirection != null) { - this.initPathStr = "0" + startDirection; - } else { - this.initPathStr = "0"; - } - this.maxTurns = maxTurns; - this.gridFile = gridFile; - if (this.gridFile != null) { - this.gridStore = true; - this.gridPositions = new ArrayList>(); - this.isocPositions = new ArrayList>(); - } else { - this.gridStore = false; - this.gridPositions = null; - this.isocPositions = null; - } - } - - class SortPathCandsAbsHorizontally implements Comparator { - - @Override - public int compare(PathCandidate p1, PathCandidate p2) { - if (Math.abs(p1.hrz) == Math.abs(p2.hrz)) { - return 0; - } else { - return (Math.abs(p1.hrz) < Math.abs(p2.hrz) ? -1 : +1); - } - } - - } - - class SortPathCandsHorizontally implements Comparator { - - @Override - public int compare(PathCandidate p1, PathCandidate p2) { - if (p1.hrz == p2.hrz) { - return 0; - } else { - return (p1.hrz < p2.hrz ? -1 : +1); - } - } - - } - - // getter for evaluating best path cand propoerties further - PathCandidate getBestCand() { - return this.bestCand; - } - - long getUsedTimeStep() { - return this.usedTimeStep; - } - - - // generate step in one of the possible directions - // default: L - left, R - right - // extended: M - wide left, S - wide right - Util.Pair getStep(TimedPosition pos, long timeStep, long turnLoss, boolean sameBaseDirection, char nextDirection) { - - double offDeg = 3.0; - WindFieldGenerator wf = this.parameters.getWindField(); - TimePoint curTime = pos.getTimePoint(); - Position curPosition = pos.getPosition(); - Wind posWind = wf.getWind(new TimedPositionWithSpeedImpl(curTime, curPosition, null)); - - PolarDiagram pd = this.parameters.getBoatPolarDiagram(); - pd.setWind(posWind); - Wind appWind = new WindImpl(posWind.getPosition(), posWind.getTimePoint(), pd.getWind());; - - // get beat-angle left and right - Bearing travelBearing = null; - Bearing tmpBearing = null; - if (nextDirection == 'L') { - if (this.upwindLeg) { - travelBearing = pd.optimalDirectionsUpwind()[0]; - } else { - travelBearing = pd.optimalDirectionsDownwind()[0]; - } - } else if (nextDirection == 'R') { - if (this.upwindLeg) { - travelBearing = pd.optimalDirectionsUpwind()[1]; - } else { - travelBearing = pd.optimalDirectionsDownwind()[1]; - } - } else if (nextDirection == 'M') { - if (this.upwindLeg) { - tmpBearing = pd.optimalDirectionsUpwind()[0]; - travelBearing = tmpBearing.add(new DegreeBearingImpl(-offDeg)); - } else { - tmpBearing = pd.optimalDirectionsDownwind()[0]; - travelBearing = tmpBearing.add(new DegreeBearingImpl(-offDeg)); - } - } else if (nextDirection == 'S') { - if (this.upwindLeg) { - tmpBearing = pd.optimalDirectionsUpwind()[1]; - travelBearing = tmpBearing.add(new DegreeBearingImpl(+offDeg)); - } else { - tmpBearing = pd.optimalDirectionsDownwind()[1]; - travelBearing = tmpBearing.add(new DegreeBearingImpl(+offDeg)); - } - } - - // determine beat-speed left and right - SpeedWithBearing travelSpeed = pd.getSpeedAtBearing(travelBearing); - - TimePoint travelTime; - TimePoint nextTime = new MillisecondsTimePoint(curTime.asMillis()+timeStep); - if (sameBaseDirection) { - travelTime = nextTime; - } else { - travelTime = new MillisecondsTimePoint(nextTime.asMillis() - turnLoss); - } - - return new Util.Pair(new TimedPositionImpl(nextTime, travelSpeed.travelTo(curPosition, curTime, travelTime)), appWind); - } - - // use base direction to distinguish direction changes that do or don't require a turn - char getBaseDirection(char direction) { - char baseDirection = direction; - - if (direction == 'M') { - baseDirection = 'L'; - } - if (direction == 'S') { - baseDirection = 'R'; - } - - return baseDirection; - } - - // check whether nextDirection is same base direction as previous direction, i.e. no turn - boolean isSameDirection(char prevDirection, char nextDirection) { - - char prevBaseDirection = this.getBaseDirection(prevDirection); - char nextBaseDirection = this.getBaseDirection(nextDirection); - - return ((nextBaseDirection == prevBaseDirection)||(prevBaseDirection == '0')); - } - - // get path candidate measuring height towards (local, current-apparent) wind - PathCandidate getPathCandWind(PathCandidate path, char nextDirection, long timeStep, long turnLoss, Position posStart, Position posEnd, double tgtHeight) { - - char prevDirection = path.path.charAt(path.path.length()-1); - boolean sameBaseDirection = this.isSameDirection(prevDirection, nextDirection); - - int turnCount = path.trn; - if (!sameBaseDirection) { - turnCount++; - } - - // calculate next path position (taking turn-loss into account) - Util.Pair nextStep = this.getStep(path.pos, timeStep, turnLoss, sameBaseDirection, nextDirection); - TimedPosition pathPos = nextStep.getA(); - Wind posWind = nextStep.getB(); - - // calculate height-position with reference to race course - Position posHeight = pathPos.getPosition().projectToLineThrough(posEnd, posWind.getBearing()); - Bearing bearVrt = posStart.getBearingGreatCircle(posEnd); - Position posHeightTrgt = pathPos.getPosition().projectToLineThrough(posStart, bearVrt); - - // calculate vertical distance as distance of height-position to start - //double vrtDist = Math.round(posHeightTrgt.getDistance(posStart).getMeters()*100.0)/100.0; - - Bearing bearHeight = posEnd.getBearingGreatCircle(posHeight); - double bearHeightSide = posWind.getBearing().getDifferenceTo(bearHeight).getDegrees(); - /*if (Math.abs(bearHeightSide) > 5.0) { - System.out.println("bearHeightSide: "+bearHeightSide); - }*/ - double vrtSide = (this.upwindLeg ? -1.0 : +1.0); - if (Math.abs(bearHeightSide) > 170.0) { - vrtSide = (this.upwindLeg ? +1.0 : -1.0); - } - double vrtDist = vrtSide*Math.round(posHeight.getDistance(posEnd).getMeters()*1000.0)/1000.0; - - /*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 - double posSide = 1; - //double posBear = posWind.getBearing().getDegrees() - posEnd.getBearingGreatCircle(pathPos.getPosition()).getDegrees(); - Bearing posBear = posStart.getBearingGreatCircle(pathPos.getPosition()); - double posBearDiff = bearVrt.getDifferenceTo(posBear).getDegrees(); - if ((posBearDiff < 0.0)||(posBearDiff > 180.0)) { - posSide = -1; - } else if ((posBearDiff == 0.0)||(posBearDiff == 180.0)) { - posSide = 0; - } - // calculate horizontal distance as distance of height-position to current position - //double hrzDist = Math.round(posSide*posHeight.getDistance(pathPos.getPosition()).getMeters()*100.0)/100.0; - double hrzDist = Math.round(posSide*posHeightTrgt.getDistance(pathPos.getPosition()).getMeters()*1000.0)/1000.0; - - //System.out.println(""+hrzDist+", "+vrtDist+", "+pathPos.getPosition().getLatDeg()+", "+pathPos.getPosition().getLngDeg()+", "+posHeight.getLatDeg()+", "+posHeight.getLngDeg()); - - // extend path-string by step-direction - String pathStr = path.path + nextDirection; - char nextBaseDirection = this.getBaseDirection(nextDirection); - - return (new PathCandidate(pathPos, vrtDist, hrzDist, turnCount, pathStr, nextBaseDirection, posWind)); - } - - - // generate path candidates based on beat angles - List getPathCandsBeatWind(PathCandidate path, long timeStep, long turnLoss, Position posStart, Position posEnd, double tgtHeight) { - - List result = new ArrayList(); - PathCandidate newPathCand; - - if (this.maxTurns > 0) { - - char prevDirection = path.path.charAt(path.path.length()-1); - if ((path.trn < this.maxTurns)||(this.isSameDirection(prevDirection, 'L'))) { - newPathCand = getPathCandWind(path, 'L', timeStep, turnLoss, posStart, posEnd, tgtHeight); - result.add(newPathCand); - } - - if ((path.trn < this.maxTurns)||(this.isSameDirection(prevDirection, 'R'))) { - newPathCand = getPathCandWind(path, 'R', timeStep, turnLoss, posStart, posEnd, tgtHeight); - result.add(newPathCand); - } - - } else { - - // step left - newPathCand = getPathCandWind(path, 'L', timeStep, turnLoss, posStart, posEnd, tgtHeight); - result.add(newPathCand); - - // step wide left - //newPathCand = getPathCandWind(path, 'M', timeStep, turnLoss, posStart, posEnd, tgtHeight); - //result.add(newPathCand); - - // step right - newPathCand = getPathCandWind(path, 'R', timeStep, turnLoss, posStart, posEnd, tgtHeight); - result.add(newPathCand); - - // step wide right - //newPathCand = getPathCandWind(path, 'S', timeStep, turnLoss, posStart, posEnd, tgtHeight); - //result.add(newPathCand); - - } - - return result; - } - - Util.Pair,List> generateCandidate(List oldPaths, long timeStep, long turnLoss, Position posStart, Position posMiddle, Position posEnd, double tgtHeight) { - - List newPathCands; - List leftPaths = new ArrayList(); - List rightPaths = new ArrayList(); - for(PathCandidate curPath : oldPaths) { - - newPathCands = this.getPathCandsBeatWind(curPath, timeStep, turnLoss, posStart, posEnd, tgtHeight); - for (PathCandidate curNewPath : newPathCands) { - // check whether path is *outside* regatta-area - double distFromMiddleMeters = posMiddle.getDistance(curPath.pos.getPosition()).getMeters(); - if (distFromMiddleMeters > oobFact * tgtHeight) { - continue; // ignore curPath - } - - if (curNewPath.sid == 'L') { - leftPaths.add(curNewPath); - } else if (curNewPath.sid == 'R') { - rightPaths.add(curNewPath); - } - - } - - } - - Util.Pair,List> newPaths = new Util.Pair,List>(leftPaths, rightPaths); - return newPaths; - } - - - List filterCandidates(List allCands, double hrzBinWidth) { - - boolean[] filterMap = new boolean[allCands.size()]; - - // sort candidates by horizontal distance - Comparator sortHorizontal = new SortPathCandsHorizontally(); - Collections.sort(allCands, sortHorizontal); - - // start scan with index 0 - int idxL = 0; - int idxR = 0; - - // for each candidate, check the neighborhoods and identify bad candidates - for(int idx = 0; idx < allCands.size(); idx++) { - - // current horizontal distance - double hrzDist = allCands.get(idx).hrz; - - // align left index - while(Math.abs(hrzDist - allCands.get(idxL).hrz) > hrzBinWidth) { - idxL++; - } - - // align right index - boolean finished = false; - while(!finished && (idxR < (allCands.size()-1))) { - if (Math.abs(hrzDist - allCands.get(idxR+1).hrz) <= hrzBinWidth) { - idxR++; - } else { - finished = true; - } - } - - // search maximum height - // in neighborhood idxL, ..., idxR - - // init max for search - int vrtIdx = idxL; - double vrtMax = allCands.get(vrtIdx).vrt; - filterMap[vrtIdx] = false; - - // evaluate remainder of neighborhood - if (idxL < idxR) { - for(int jdx = (idxL+1); jdx <= idxR; jdx++) { - if (allCands.get(jdx).vrt > vrtMax) { - // reset previous max candidate - filterMap[vrtIdx] = true; - // keep max height - vrtMax = allCands.get(jdx).vrt; - // keep max index - vrtIdx = jdx; - // set current max candidate - filterMap[vrtIdx] = false; - } else { - filterMap[jdx] = true; - } - } - } - - } // endfor each candidate - - // collect all good candidates (i.e. filterMap == false) - List filterCands = new ArrayList(); - for(int idx=0; idx < allCands.size(); idx++) { - if (!filterMap[idx]) { - filterCands.add(allCands.get(idx)); - } - } - - // return remaining good candidates - return filterCands; - } - - - List filterIsochrone(List allCands, double hrzBinWidth) { - - boolean[] filterMap = new boolean[allCands.size()]; - for(int idx = 0; idx < allCands.size(); idx++) { - filterMap[idx] = true; - } - - // sort candidates by horizontal distance - Comparator sortHorizontal = new SortPathCandsHorizontally(); - Collections.sort(allCands, sortHorizontal); - - // start scan with index 0 - int idxL = 0; - int idxR = 0; - - // for each candidate, check the neighborhoods and identify bad candidates - for(int idx = 0; idx < allCands.size(); idx++) { - - // current horizontal distance - double hrzDist = allCands.get(idx).hrz; - - // align left index - while(Math.abs(hrzDist - allCands.get(idxL).hrz) > hrzBinWidth) { - idxL++; - } - - // align right index - boolean finished = false; - while(!finished && (idxR < (allCands.size()-1))) { - if (Math.abs(hrzDist - allCands.get(idxR+1).hrz) <= hrzBinWidth) { - idxR++; - } else { - finished = true; - } - } - - // search maximum height - // in neighborhood idxL, ..., idxR - - // init max for search - ArrayList vrtIdx = new ArrayList(); - vrtIdx.add(idxL); - double vrtMax = allCands.get(idxL).vrt; - - // evaluate remainder of neighborhood - if (idxL < idxR) { - for(int jdx = (idxL+1); jdx <= idxR; jdx++) { - if (allCands.get(jdx).vrt > vrtMax) { - // keep max height - vrtMax = allCands.get(jdx).vrt; - // keep max index - vrtIdx = new ArrayList(); - vrtIdx.add(jdx); - } else if (allCands.get(jdx).vrt == vrtMax) { - // add further max indexes - vrtIdx.add(jdx); - } - } - } - - for(Integer jdx : vrtIdx) { - filterMap[jdx] = false; - } - - } // endfor each candidate - - // collect all good candidates (i.e. filterMap == false) - List filterCands = new ArrayList(); - for(int idx=0; idx < allCands.size(); idx++) { - if (!filterMap[idx]) { - filterCands.add(allCands.get(idx)); - } - } - - // return remaining good candidates - return filterCands; - } - - @Override - public Path getPath() { - WindFieldGenerator wf = this.parameters.getWindField(); - PolarDiagram pd = this.parameters.getBoatPolarDiagram(); - - Position startPos = this.parameters.getCourse().get(0); - Position endPos = this.parameters.getCourse().get(1); - - // test downwind: exchange start and end - //Position startPos = this.parameters.getCourse().get(1); - //Position endPos = this.parameters.getCourse().get(0); - - TimePoint startTime = wf.getStartTime();// new MillisecondsTimePoint(0); - List path = new ArrayList(); - - Position currentPosition = startPos; - TimePoint currentTime = startTime; - - Distance distStartEnd = startPos.getDistance(endPos); - double distStartEndMeters = distStartEnd.getMeters(); - - Wind wndStart = wf.getWind(new TimedPositionWithSpeedImpl(startTime, startPos, null)); - logger.fine("wndStart speed:" + wndStart.getKnots() + " angle:" + wndStart.getBearing().getDegrees()); - pd.setWind(wndStart); - Bearing bearVrt = startPos.getBearingGreatCircle(endPos); - //Bearing bearHrz = bearVrt.add(new DegreeBearingImpl(90.0)); - Position middlePos = startPos.translateGreatCircle(bearVrt, distStartEnd.scale(0.5)); - - Bearing bearRCWind = wndStart.getBearing().getDifferenceTo(bearVrt); - String legType = "downwind"; - this.upwindLeg = false; - - if ((Math.abs(bearRCWind.getDegrees()) > 90.0)&&(Math.abs(bearRCWind.getDegrees()) < 270.0)) { - legType = "upwind"; - this.upwindLeg = true; - } - - if (debugMsgOn) { - System.out.println("start : "+startPos.getLatDeg()+", "+startPos.getLngDeg()); - System.out.println("middle: "+middlePos.getLatDeg()+", "+middlePos.getLngDeg()); - System.out.println("end : "+endPos.getLatDeg()+", "+endPos.getLngDeg()); - } - 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 - if (!this.upwindLeg) { - turnLoss = turnLoss / 2; - } - - // calculate initial position according to initPathStr - PathCandidate initPath = new PathCandidate(new TimedPositionImpl(currentTime, currentPosition), 0.0, 0.0, 0, "0", '0', wndStart); - if (initPathStr.length()>1) { - char nextDirection = '0'; - for(int idx=1; idx allPaths = new ArrayList(); - List trgPaths = new ArrayList(); - allPaths.add(initPath); - - - TimedPosition tstPosition = this.getStep(new TimedPositionImpl(startTime, startPos), timeStep, turnLoss, true, 'L').getA(); - double tstDist1 = startPos.getDistance(tstPosition.getPosition()).getMeters(); - tstPosition = this.getStep(new TimedPositionImpl(startTime, startPos), timeStep, turnLoss, true, 'R').getA(); - double tstDist2 = startPos.getDistance(tstPosition.getPosition()).getMeters(); - - double hrzBinSize = (tstDist1 + tstDist2)/6.0; // horizontal bin size in meters - if (debugMsgOn) { - System.out.println("Horizontal Bin Size: "+hrzBinSize); - } - - //double oobFact = 0.75; // out-of-bounds factor - boolean reachedEnd = false; - int addSteps = 0; - int finalSteps = 0; // maximum number of additional steps after first target-path found - - while ((!reachedEnd)||(addSteps,List> newPaths = this.generateCandidate(allPaths, timeStep, turnLoss, startPos, middlePos, endPos, distStartEndMeters); - - - // select good candidates - List leftPaths = this.filterCandidates(newPaths.getA(), hrzBinSize/2.0); - List rightPaths = this.filterCandidates(newPaths.getB(), hrzBinSize/2.0); - - List nextPaths = new ArrayList (); - nextPaths.addAll(leftPaths); - nextPaths.addAll(rightPaths); - - allPaths = nextPaths; - - if (this.gridStore) { - - /*ArrayList isoChrone = new ArrayList(); - for(PathCand curCand : allPaths) { - isoChrone.add(curCand.pos); - } - this.gridPositions.add(isoChrone);*/ - - this.gridPositions.add(allPaths); - - List isocPaths = this.filterIsochrone(allPaths, hrzBinSize); - this.isocPositions.add(isocPaths); - - } - - // check if there are still paths in the regatta-area - if (allPaths.size() > 0) { - - 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)) { - //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)) { - reachedEnd = true; - trgPaths.add(curPath); // add path to list of target-paths - } - } - } - - } else { - // terminate path-search as no path inside regatta-area are left - reachedEnd = true; - } - - } - - if (this.gridStore) { - - double distResolution = distStartEndMeters*0.01; - BufferedWriter outputCSV; - try { - outputCSV = new BufferedWriter(new FileWriter(this.gridFile+"-grid.csv")); - outputCSV.write("step; lat; lng; time; side; path; vrt\n"); - outputCSV.write("0; "+startPos.getLatDeg()+"; "+startPos.getLngDeg()+"; "+(startTime.asMillis()/1000)+"; 0; 0; "+(-distStartEndMeters)+"\n"); - outputCSV.write("0; "+endPos.getLatDeg()+"; "+endPos.getLngDeg()+"; "+(startTime.asMillis()/1000)+"; 0; 0; 0\n"); - int stepCount = 0; - for(List isoChrone : this.gridPositions) { - stepCount++; - PathCandidate prevPos = null; - for(PathCandidate isoPos : isoChrone) { - - if (prevPos != null) { - if (prevPos.pos.getPosition().getDistance(isoPos.pos.getPosition()).getMeters() < distResolution) { - continue; - } - } - - String outStr = ""+stepCount+"; "+isoPos.pos.getPosition().getLatDeg()+"; "+isoPos.pos.getPosition().getLngDeg()+"; "+(isoPos.pos.getTimePoint().asMillis()/1000)+"; "+isoPos.sid; - outStr += "; "+isoPos.path+"; "+isoPos.vrt; - outStr += "\n"; - outputCSV.write(outStr); - - prevPos = isoPos; - } - } - outputCSV.close(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - try { - outputCSV = new BufferedWriter(new FileWriter(this.gridFile+"-isoc.csv")); - outputCSV.write("step; lat; lng; time; side; path; vrt\n"); - outputCSV.write("0; "+startPos.getLatDeg()+"; "+startPos.getLngDeg()+"; "+(startTime.asMillis()/1000)+"; 0; 0; "+(-distStartEndMeters)+"\n"); - outputCSV.write("0; "+endPos.getLatDeg()+"; "+endPos.getLngDeg()+"; "+(startTime.asMillis()/1000)+"; 0; 0; 0\n"); - int stepCount = 0; - for(List isoChrone : this.isocPositions) { - stepCount++; - PathCandidate prevPos = null; - for(PathCandidate isoPos : isoChrone) { - - if (prevPos != null) { - if (prevPos.pos.getPosition().getDistance(isoPos.pos.getPosition()).getMeters() < distResolution) { - continue; - } - } - - String outStr = ""+stepCount+"; "+isoPos.pos.getPosition().getLatDeg()+"; "+isoPos.pos.getPosition().getLngDeg()+"; "+(isoPos.pos.getTimePoint().asMillis()/1000)+"; "+isoPos.sid; - outStr += "; "+isoPos.path+"; "+isoPos.vrt; - outStr += "\n"; - outputCSV.write(outStr); - - prevPos = isoPos; - } - } - outputCSV.close(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - } - - - // if no target-paths were found, return empty path - if (trgPaths.size() == 0) { - //trgPaths = allPaths; // TODO: only for testing; remove lateron - TimedPositionWithSpeed curPosition = new TimedPositionWithSpeedImpl(startTime, startPos, null); - path.add(curPosition); - return new PathImpl(path, wf); // return empty path - } - - // sort target-paths ascending by distance-to-target - Collections.sort(trgPaths); - - // debug output - //if (debugMsgOn) { - 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"); - //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)); - } - //} - - // - // fill gwt-path - // - - // generate intermediate steps - bestCand = trgPaths.get(0); // target-path ending closest to target - TimedPositionWithSpeed curPosition = null; - char nextDirection = '0'; - char prevDirection = '0'; - for(int step=0; step<(bestCand.path.length()-1); step++) { - - nextDirection = bestCand.path.charAt(step); - - if (nextDirection == '0') { - - curPosition = new TimedPositionWithSpeedImpl(startTime, startPos, null); - path.add(curPosition); - - } else { - - boolean sameBaseDirection = this.isSameDirection(prevDirection, nextDirection); - TimedPosition newPosition = this.getStep(curPosition, timeStep, turnLoss, sameBaseDirection, nextDirection).getA(); - curPosition = new TimedPositionWithSpeedImpl(newPosition.getTimePoint(), newPosition.getPosition(), null); - path.add(curPosition); - - } - - prevDirection = nextDirection; - } - - // add final position (rescaled before to end on height of target) - path.add(new TimedPositionWithSpeedImpl(bestCand.pos.getTimePoint(), bestCand.pos.getPosition(), null)); - - return new PathImpl(path, wf); - - } - -} diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/SailingSimulatorImpl.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/SailingSimulatorImpl.java index 17288df9988..e5b97963a33 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/SailingSimulatorImpl.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/SailingSimulatorImpl.java @@ -113,7 +113,7 @@ public class SailingSimulatorImpl implements SailingSimulator { // // get instance of heuristic searcher - PathGeneratorTreeGrowWind3 genTreeGrow = new PathGeneratorTreeGrowWind3(this.simulationParameters); + PathGeneratorTreeGrowWind genTreeGrow = new PathGeneratorTreeGrowWind(this.simulationParameters); // search best left-starting 1-turner genTreeGrow.setEvaluationParameters("L", 1, null); diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/SimulatorUtils.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/SimulatorUtils.java index ddd8b3a47ca..c4b41214098 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/SimulatorUtils.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/SimulatorUtils.java @@ -373,7 +373,7 @@ public class SimulatorUtils { Map paths = new HashMap(); // get instance of heuristic searcher - PathGeneratorTreeGrowWind3 genTreeGrow = new PathGeneratorTreeGrowWind3(parameters); + PathGeneratorTreeGrowWind genTreeGrow = new PathGeneratorTreeGrowWind(parameters); // search best left-starting 1-turner genTreeGrow.setEvaluationParameters("L", 1, null); diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/WindControlParameters.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/WindControlParameters.java index 26e15352a5c..e5204649103 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/WindControlParameters.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/windfield/WindControlParameters.java @@ -140,7 +140,7 @@ public class WindControlParameters implements Serializable { blastProbability = 25.0; maxBlastSize = 1.0; - blastWindSpeed = 100.0; + blastWindSpeed = 120.0; blastWindSpeedVar = 10.0; if (blastRandomStreamManager == null || !resetBlastRandomStream) { From 5ad019f00055edf801e9e9bc8352b8ade67566bd Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 24 Jul 2014 23:43:43 +0200 Subject: [PATCH 253/323] formatting changes only --- .../app/domain/impl/BoatClassSeriesFleet.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/BoatClassSeriesFleet.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/BoatClassSeriesFleet.java index da318d32b67..6c69f4a6b32 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/BoatClassSeriesFleet.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/BoatClassSeriesFleet.java @@ -35,10 +35,10 @@ public class BoatClassSeriesFleet { } private int getFleetIndex(Iterable fleets, Fleet fleet) { - return Util.indexOf(fleets, fleet); - } + return Util.indexOf(fleets, fleet); + } - public String getBoatClassName() { + public String getBoatClassName() { return boatClass.getName(); } @@ -127,8 +127,8 @@ public class BoatClassSeriesFleet { return true; } - public int getFleetOrder() { - return fleetOrder; - } - + public int getFleetOrder() { + return fleetOrder; + } + } From 1463ce7675f529e81d621f41ca677c00b0ed60f7 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 25 Jul 2014 00:18:47 +0200 Subject: [PATCH 254/323] fixed another NPE regarding null speedWithBearing --- .../com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 86a2bb40188..8d27a4c3d9a 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 @@ -1587,7 +1587,8 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe multiply(factorForAfter)).divide(1).getDegrees(); } SpeedWithBearingDTO betweenSpeed = new SpeedWithBearingDTO( - factorForBefore*fixBefore.speedWithBearing.speedInKnots + factorForAfter*fixAfter.speedWithBearing.speedInKnots, + factorForBefore*(fixBefore.speedWithBearing==null?0:fixBefore.speedWithBearing.speedInKnots) + + factorForAfter*(fixAfter.speedWithBearing==null?0:fixAfter.speedWithBearing.speedInKnots), betweenBearing); result = new GPSFixDTO(date, betweenPosition, betweenSpeed, closer.degreesBoatToTheWind, closer.tack, closer.legType, fixBefore.extrapolated || fixAfter.extrapolated); From c18b307de06223e4b0a65510444133d7625bc750 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 25 Jul 2014 00:23:51 +0200 Subject: [PATCH 255/323] reduced artifical wind speed for course-based wind fix from 1.0kts to 0.0kts to not get people confused --- .../src/com/sap/sailing/domain/tracking/TrackedRace.java | 2 +- .../com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java index 0c04c02c7ce..7ca16436693 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java @@ -397,7 +397,7 @@ public interface TrackedRace extends Serializable { /** * Computes a default wind direction based on the direction of the first leg at time at, with a default - * speed of one knot. Note that this wind direction can only be used if {@link #raceIsKnownToStartUpwind()} returns + * speed of zero knots. Note that this wind direction can only be used if {@link #raceIsKnownToStartUpwind()} returns * true. * * @param at 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 c7372728b4c..975bfe7b6d1 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 @@ -1378,7 +1378,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl Position firstLegEnd = getApproximatePosition(firstLeg.getTo(), at); Position firstLegStart = getApproximatePosition(firstLeg.getFrom(), at); if (firstLegStart != null && firstLegEnd != null) { - result = new WindImpl(firstLegStart, at, new KnotSpeedWithBearingImpl(1.0, + result = new WindImpl(firstLegStart, at, new KnotSpeedWithBearingImpl(0.0, firstLegEnd.getBearingGreatCircle(firstLegStart))); } else { result = null; From 686e8f3ce771501816d451a9e1ef1efae7c345c5 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 25 Jul 2014 00:35:16 +0200 Subject: [PATCH 256/323] added null check for access to legDetails, addressing bug 2085, as a follow-up to commit a437b2b7ef4aeddbeef6af33bce44adc6b63e2f6 --- .../java/com/sap/sailing/gwt/ui/leaderboard/LegColumn.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 10a30c6386f..6ef18e570fe 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 @@ -348,11 +348,10 @@ public class LegColumn extends ExpandableSortableColumn { @Override public String getValue(LeaderboardRowDTO row) { LeaderboardEntryDTO leaderboardEntryDTO = row.fieldsByRaceColumnName.get(raceColumnName); - int legDetailsCount = leaderboardEntryDTO.legDetails.size(); LegEntryDTO legEntry = getLegEntry(row); if (legEntry != null && legEntry.rank != 0) { return ""+legEntry.rank; - } else if (legIndex+1 > legDetailsCount) { + } else if (leaderboardEntryDTO.legDetails != null && legIndex+1 > leaderboardEntryDTO.legDetails.size()) { return "n/a"; } else { return ""; From 369a19453c6224b322cce6384cbe6572e1edc969 Mon Sep 17 00:00:00 2001 From: Carolin Joppien Date: Fri, 25 Jul 2014 08:53:59 +0000 Subject: [PATCH 257/323] Updated ganalytics (markdown) --- wiki/ganalytics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wiki/ganalytics.md b/wiki/ganalytics.md index 5b5239354c6..0eda98874af 100644 --- a/wiki/ganalytics.md +++ b/wiki/ganalytics.md @@ -2,7 +2,7 @@ GAnalytics Homepage: http://www.google.com/intl/en/analytics/
GAnalytics Account: gmsponsorship@gmail.com
-GAnalytics PW:
+GAnalytics PW: sailing123

GAnalytics Tracking ID: **UA-52789785-1** (to make tracking possible for the webpage the tracking code has to be embeedded in the html code)

From 7fb5764a25b9016818cb3753a7c6f815862b0242 Mon Sep 17 00:00:00 2001 From: "Steffen Wagner (d059727)" Date: Fri, 25 Jul 2014 11:06:56 +0200 Subject: [PATCH 258/323] Added a realease note as described in comment #3 in bug2089 by Axel --- java/com.sap.sailing.www/release_notes_RCApp.html | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/java/com.sap.sailing.www/release_notes_RCApp.html b/java/com.sap.sailing.www/release_notes_RCApp.html index bf159e32b64..e857a94bd77 100644 --- a/java/com.sap.sailing.www/release_notes_RCApp.html +++ b/java/com.sap.sailing.www/release_notes_RCApp.html @@ -34,6 +34,12 @@

Race Committee App

+

July 2014

+
    +
  • The race list is now ordered by fleets. This means we display the same order of our backend system in the RCApp. + Now the race officials can easily use the order which is displayed and do not have to care about the fleet color order.
  • +
+

March 2014

- +
-
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 554a1ab883b..3da0486c9ad 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 @@ -20,13 +20,15 @@ public class EventRegattaRaces extends Composite { interface EventRegattaRacesUiBinder extends UiBinder { } - @UiField(provided=true) Regatta regatta; +// @UiField(provided=true) Regatta regatta; @UiField HTMLPanel regattaPhasesNavigationPanel; - @UiField HTMLPanel regattaPhasesPanel; + @UiField HTMLPanel regattaPhasesPanel; + @UiField HTMLPanel rootPanel; @SuppressWarnings("unused") private final EventDTO event; - + + private final Regatta regatta; private final Timer timerForClientServerOffset; private final EventPageNavigator pageNavigator; @@ -39,6 +41,8 @@ public class EventRegattaRaces extends Composite { EventRegattaRacesResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); + + rootPanel.getElement().appendChild(regatta.getElement()); } public void setRacesFromRaceGroup(RaceGroupDTO raceGroup, StrippedLeaderboardDTO leaderboard) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRaces.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRaces.ui.xml index e25fd50f512..bb38c586338 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRaces.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRaces.ui.xml @@ -5,10 +5,12 @@ - + +
diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/main.css b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/main.css index b5b50459cb7..ed502290e40 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/main.css +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/main.css @@ -103,8 +103,8 @@ figcaption { .stackenblochen .stackenblochen { width: auto; max-width: none; - margin-left: -1rem; - margin-right: -1rem; + margin-left: literal("-1rem"); + margin-right: literal("-1rem"); } .navbar { From 31aebb5828ed50f88f41f6d729953d0d9368c7e4 Mon Sep 17 00:00:00 2001 From: fmittag Date: Fri, 25 Jul 2014 16:31:06 +0200 Subject: [PATCH 260/323] added simple view for regattas with a default series --- .../client/place/event/regattaraces/EventRegattaRaces.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 3da0486c9ad..83b537a5b93 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 @@ -55,7 +55,9 @@ public class EventRegattaRaces extends Composite { regattaPhasesPanel.getElement().appendChild(regattaPhase.getElement()); } } else { - + RaceGroupSeriesDTO raceGroupSeriesDTO = raceGroup.getSeries().get(0); + EventRegattaRacesPhase regattaPhase = new EventRegattaRacesPhase(leaderboard, raceGroupSeriesDTO, timerForClientServerOffset, pageNavigator); + regattaPhasesPanel.getElement().appendChild(regattaPhase.getElement()); } } } From e1ec03788fcbed885157a57e15718850f06e0ecf Mon Sep 17 00:00:00 2001 From: fmittag Date: Fri, 25 Jul 2014 16:57:57 +0200 Subject: [PATCH 261/323] let anchor tags look like real anchors on regattalist page --- .../home/client/place/event/regatta/Regatta.java | 13 ------------- .../home/client/place/event/regatta/Regatta.ui.xml | 6 +++--- 2 files changed, 3 insertions(+), 16 deletions(-) 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 ac43bba3ccf..749a0222618 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 @@ -80,7 +80,6 @@ public class Regatta extends UIObject { if(isNavigatable) { regattaDiv.setAttribute("data-rendermode", "link"); regattaImageWithoutLink.getStyle().setDisplay(Display.NONE); - regattaNameWithoutLink.getStyle().setDisplay(Display.NONE); } else { regattaImageWithLink.getStyle().setDisplay(Display.NONE); @@ -162,17 +161,5 @@ public class Regatta extends UIObject { pageNavigator.goToRegattaRaces(raceGroup, leaderboard); } }); - - } - -// -// @UiHandler("regattaImageWithLink") -// public void goToRegattaRaces(ClickEvent e) { -// pageNavigator.goToRegattaRaces(raceGroup, leaderboard); -// } -// -// @UiHandler("regattaNameWithLink") -// public void goToRegattaRaces2(ClickEvent e) { -// } } 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 fa6e6670fb1..97dfa82bbe8 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 @@ -10,7 +10,7 @@
- +
@@ -19,7 +19,7 @@
- +

@@ -43,7 +43,7 @@
- +
From 092fe110553942889aef7ff3b6c188fe0d04f889 Mon Sep 17 00:00:00 2001 From: fmittag Date: Fri, 25 Jul 2014 17:03:44 +0200 Subject: [PATCH 262/323] convert 'Regatta' back to be based on Composite --- .../gwt/home/client/place/event/regatta/Regatta.java | 9 +++++---- .../gwt/home/client/place/event/regatta/Regatta.ui.xml | 2 ++ .../place/event/regattaraces/EventRegattaRaces.java | 5 +---- .../place/event/regattaraces/EventRegattaRaces.ui.xml | 2 -- 4 files changed, 8 insertions(+), 10 deletions(-) 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 749a0222618..f1380f181bb 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 @@ -12,7 +12,8 @@ import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.EventListener; -import com.google.gwt.user.client.ui.UIObject; +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.EventPageNavigator; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; @@ -21,10 +22,10 @@ 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 Regatta extends UIObject { +public class Regatta extends Composite { private static RegattaUiBinder uiBinder = GWT.create(RegattaUiBinder.class); - interface RegattaUiBinder extends UiBinder { + interface RegattaUiBinder extends UiBinder { } @SuppressWarnings("unused") @@ -75,7 +76,7 @@ public class Regatta extends UIObject { // competitorWithRank3 = new RegattaCompetitor(3, null); RegattaResources.INSTANCE.css().ensureInjected(); - setElement(uiBinder.createAndBindUi(this)); + initWidget(uiBinder.createAndBindUi(this)); if(isNavigatable) { regattaDiv.setAttribute("data-rendermode", "link"); 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 97dfa82bbe8..ff8005f5220 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 @@ -5,6 +5,7 @@ +
@@ -63,4 +64,5 @@
+ 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 83b537a5b93..b6abcdaf056 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 @@ -20,7 +20,7 @@ public class EventRegattaRaces extends Composite { interface EventRegattaRacesUiBinder extends UiBinder { } -// @UiField(provided=true) Regatta regatta; + @UiField(provided=true) Regatta regatta; @UiField HTMLPanel regattaPhasesNavigationPanel; @UiField HTMLPanel regattaPhasesPanel; @UiField HTMLPanel rootPanel; @@ -28,7 +28,6 @@ public class EventRegattaRaces extends Composite { @SuppressWarnings("unused") private final EventDTO event; - private final Regatta regatta; private final Timer timerForClientServerOffset; private final EventPageNavigator pageNavigator; @@ -41,8 +40,6 @@ public class EventRegattaRaces extends Composite { EventRegattaRacesResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); - - rootPanel.getElement().appendChild(regatta.getElement()); } public void setRacesFromRaceGroup(RaceGroupDTO raceGroup, StrippedLeaderboardDTO leaderboard) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRaces.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRaces.ui.xml index bb38c586338..2a2bd48b6c3 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRaces.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRaces.ui.xml @@ -8,9 +8,7 @@ -
From 490322ffc6ea4e0be87d3ab4f15c9ca4d57a7399 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 25 Jul 2014 17:55:33 +0200 Subject: [PATCH 263/323] allow start time entry with second accuracy instead of minute accuracy only --- .../com/sap/sailing/gwt/ui/adminconsole/SetStartTimeDialog.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SetStartTimeDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SetStartTimeDialog.java index 2632d411b2e..8a37a64042a 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SetStartTimeDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SetStartTimeDialog.java @@ -100,7 +100,7 @@ public class SetStartTimeDialog extends DataEntryDialogWithBootstrap Date: Fri, 25 Jul 2014 18:57:17 +0200 Subject: [PATCH 264/323] moved to TracAPI 3.0.3, solving an issue with competitor removal from a race that did not reflect in TracAPI --- .../META-INF/MANIFEST.MF | 2 +- .../com.tractrac.clientmodule/lib/TracAPI.jar | Bin 748755 -> 748787 bytes java/com.tractrac.clientmodule/pom.xml | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF b/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF index 9719eabfc57..9846b034790 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.2 +Bundle-Version: 3.0.3 Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Bundle-ClassPath: ., lib/TracAPI.jar diff --git a/java/com.tractrac.clientmodule/lib/TracAPI.jar b/java/com.tractrac.clientmodule/lib/TracAPI.jar index f9ddc9677453b0d85b8dce00d47c256e3e5185f3..6a65537bc974dae8e46ee573828c02f0d28f251e 100644 GIT binary patch delta 51393 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 delta 51331 zcmaHS2RxN;`?xc#WAE&;vqM(0vNzd#6*96avqQto9#4p5l$8-V5!ovtDzZ08S*a-M z|D5Mh-}n3e-p{{Jb%P=J{zOvx<#Ol= zgPG$9x7nB_#_Y)qo!4$b`ac`JP4%V^u%>%N=5vSQh|97zG^(8;)DA(fHR)MD3{#9= z#L8Z~qT7`ZaJq$lROJhjU_kI@YO(*inYRApQBD7t=f36rfL8&hujla&)0Un(p-GQH zjOt*$eKP~^DKa_--rý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 diTD%pk$7|skQyv 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.0.0-SNAPSHOT com.tractrac.clientmodule - 3.0.2 + 3.0.3 eclipse-plugin From 5f48675dff3e3bdbd1a053fb3114b82345e043cc Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 25 Jul 2014 19:06:14 +0200 Subject: [PATCH 265/323] make the sporadically failing race end time replication test a bit more robust by extending the waiting time --- .../server/replication/test/TrackRaceReplicationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackRaceReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackRaceReplicationTest.java index 00a586a9f6b..1ca458d378c 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackRaceReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackRaceReplicationTest.java @@ -159,7 +159,7 @@ public class TrackRaceReplicationTest extends AbstractServerReplicationTest { receivedStartAndEndOfTracking = master.getTrackedRace(raceIdentifier).getStartOfTracking() != null && master.getTrackedRace(raceIdentifier).getEndOfTracking() != null; } - Thread.sleep(2000); // kept failing several times for a 1000ms timeout + Thread.sleep(3000); // kept failing several times for a 1000ms timeout TrackedRace replicaTrackedRace = replica.getTrackedRace(raceIdentifier); assertEquals(masterTrackedRace.getStartOfTracking(), replicaTrackedRace.getStartOfTracking()); assertEquals(masterTrackedRace.getEndOfTracking(), replicaTrackedRace.getEndOfTracking()); From 9d623d3f148b8b060cf5a8f2c5180a982b4172c5 Mon Sep 17 00:00:00 2001 From: fmittag Date: Fri, 25 Jul 2014 20:58:07 +0200 Subject: [PATCH 266/323] - reverted StageTeaserBand to be an UIObject - fixed anchor --- .../shared/stage/LiveEventStageTeaser.java | 2 +- .../shared/stage/LiveStageTeaserBand.java | 5 +-- .../shared/stage/PopularEventStageTeaser.java | 2 +- .../client/shared/stage/StageTeaserBand.java | 35 ++++++++++--------- .../shared/stage/StageTeaserBand.ui.xml | 6 +--- .../stage/UpcomingEventStageTeaser.java | 2 +- 6 files changed, 26 insertions(+), 26 deletions(-) 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..cf800d618a5 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,5 +1,6 @@ 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.shared.EventDatesFormatterUtil; import com.sap.sailing.gwt.ui.shared.EventBaseDTO; @@ -12,8 +13,8 @@ public class LiveStageTeaserBand extends StageTeaserBand { bandTitle.setInnerText(event.getName()); bandSubtitle.setInnerText(EventDatesFormatterUtil.formatDateRangeWithYear(event.startDate, event.endDate)); - actionLink.setVisible(true); - actionLink.setText("Show event"); + actionLink.getStyle().setDisplay(Display.INLINE_BLOCK); + actionLink.setInnerText("Show event"); } @Override 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..48f2607a28a 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 @@ -18,6 +18,6 @@ public class PopularEventStageTeaser extends StageTeaser { bandCount.setAttribute("data-bandcount", "1"); - stageTeaserBandsPanel.add(new NoActionStageTeaserBand(event, placeNavigator)); + stageTeaserBandsPanel.getElement().appendChild(new NoActionStageTeaserBand(event, placeNavigator).getElement()); } } 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..3a05df8ef70 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,28 @@ 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.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; private final PlaceNavigator placeNavigator; private final EventBaseDTO event; @@ -31,9 +32,17 @@ public abstract class StageTeaserBand extends Composite { this.placeNavigator = placeNavigator; StageResources.INSTANCE.css().ensureInjected(); - initWidget(uiBinder.createAndBindUi(this)); + setElement(uiBinder.createAndBindUi(this)); - actionLink.setVisible(false); + Event.sinkEvents(actionLink, Event.ONCLICK); + Event.setEventListener(actionLink, new EventListener() { + @Override + public void onBrowserEvent(Event event) { + actionLinkClicked(); + } + }); + + actionLink.getStyle().setDisplay(Display.NONE); } public PlaceNavigator getPlaceNavigator() { @@ -44,12 +53,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..c7e6077dc76 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,11 @@ - - - - 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..3ed439ede28 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 NoActionStageTeaserBand(event, placeNavigator).getElement()); } } From a879df0fa97a0fca105d83b138220f506a1b42be Mon Sep 17 00:00:00 2001 From: fmittag Date: Fri, 25 Jul 2014 21:09:09 +0200 Subject: [PATCH 267/323] fixed anchor in RecentEvent --- .../shared/recentevent/RecentEvent.java | 27 ++++++++++--------- .../shared/recentevent/RecentEvent.ui.xml | 4 +-- 2 files changed, 17 insertions(+), 14 deletions(-) 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..cae453e3d68 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,13 +1,15 @@ package com.sap.sailing.gwt.home.client.shared.recentevent; import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.AnchorElement; 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.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.Event; +import com.google.gwt.user.client.EventListener; 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; @@ -19,7 +21,7 @@ public class RecentEvent extends Composite { @UiField SpanElement eventName; @UiField SpanElement venueName; @UiField SpanElement eventStartDate; - @UiField Anchor eventOverviewLink; + @UiField AnchorElement eventOverviewLink; @UiField ImageElement eventImage; private EventBaseDTO event; @@ -31,25 +33,26 @@ public class RecentEvent extends Composite { private static RecentEventUiBinder uiBinder = GWT.create(RecentEventUiBinder.class); - private final PlaceNavigator navigator; - - public RecentEvent(PlaceNavigator navigator) { - this.navigator = navigator; + public RecentEvent(final PlaceNavigator navigator) { RecentEventResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); + + Event.sinkEvents(eventOverviewLink, Event.ONCLICK); + Event.setEventListener(eventOverviewLink, new EventListener() { + @Override + public void onBrowserEvent(Event browserEvent) { + navigator.goToEvent(event.id.toString(), event.getBaseURL()); + } + }); + } public void setEvent(EventBaseDTO event) { this.event = event; updateUI(); } - - @UiHandler("eventOverviewLink") - public void goToEventPlace(ClickEvent e) { - navigator.goToEvent(event.id.toString(), event.getBaseURL()); - } - + private void updateUI() { eventName.setInnerText(event.getName()); venueName.setInnerText(event.venue.getName()); 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..74b33a1f930 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 @@ -13,7 +13,7 @@ From d2e9726583b5d751bf0d8f572d94318b9120f7f8 Mon Sep 17 00:00:00 2001 From: fmittag Date: Fri, 25 Jul 2014 21:09:37 +0200 Subject: [PATCH 268/323] filter out regattas with no defined series in regatta list for now --- .../client/place/event/regattalist/EventRegattaList.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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..c0dccef50dc 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 @@ -42,9 +42,10 @@ public class EventRegattaList extends Composite { 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(leaderboardWithLeaderboardGroup != null) { + regatta.setData(raceGroup, leaderboardWithLeaderboardGroup.getA(), leaderboardWithLeaderboardGroup.getB()); + regattaListItemsDiv.appendChild(regatta.getElement()); + } } } } From 3d137feb6890d4f6bd1f3948330f84268f6e37b4 Mon Sep 17 00:00:00 2001 From: fmittag Date: Fri, 25 Jul 2014 21:13:35 +0200 Subject: [PATCH 269/323] filter out stage images for events overview gallery --- .../gwt/home/client/shared/recentevent/RecentEvent.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 cae453e3d68..80b716cfee2 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,5 +1,7 @@ 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.ImageElement; @@ -58,10 +60,11 @@ public class RecentEvent extends Composite { venueName.setInnerText(event.venue.getName()); eventStartDate.setInnerText(EventDatesFormatterUtil.formatDateRangeWithoutYear(event.startDate, event.endDate)); - if(event.getImageURLs().size() == 0) { + List photoGalleryImageURLs = event.getPhotoGalleryImageURLs(); + if(photoGalleryImageURLs.size() == 0) { eventImage.setSrc(defaultImageUrl); } else { - eventImage.setSrc(event.getImageURLs().get(0)); + eventImage.setSrc(photoGalleryImageURLs.get(0)); } } } From 5e37b5ef811d8a1ff59c1d79baccac95b815efbe Mon Sep 17 00:00:00 2001 From: fmittag Date: Fri, 25 Jul 2014 21:40:21 +0200 Subject: [PATCH 270/323] add races count to regatta header --- .../client/place/event/regatta/Regatta.java | 25 +++++++++++-------- .../client/place/event/regatta/Regatta.ui.xml | 6 +++-- .../event/regattaraces/EventRegattaRaces.css | 4 +-- .../shared/recentevent/RecentEvent.java | 2 -- 4 files changed, 20 insertions(+), 17 deletions(-) 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..46dbeb61e40 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 @@ -7,7 +7,6 @@ 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.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.Event; @@ -42,20 +41,20 @@ public class Regatta extends Composite { @UiField HeadingElement regattaName1; @UiField HeadingElement regattaName2; @UiField DivElement leaderboardGroupName; - @UiField SpanElement scheduledStart; +// @UiField SpanElement scheduledStart; @UiField DivElement regattaPhasesPanel; @UiField DivElement regattaPhasesInfoDiv; @UiField SpanElement competitorsCount; @UiField SpanElement racesCount; - @UiField SpanElement trackedRacesCount; +// @UiField SpanElement trackedRacesCount; @UiField DivElement isLiveDiv; - @UiField TableElement isLiveDiv2; +// @UiField TableElement isLiveDiv2; @UiField DivElement isFinishedDiv; - @UiField DivElement isFinishedDiv2; - @UiField DivElement isScheduledDiv; +// @UiField DivElement isFinishedDiv2; +// @UiField DivElement isScheduledDiv; @UiField AnchorElement leaderboardLink; - @UiField DivElement liveRaceInfosPerFleetPanel; +// @UiField DivElement liveRaceInfosPerFleetPanel; // @UiField(provided=true) RegattaCompetitor competitorWithRank1; // @UiField(provided=true) RegattaCompetitor competitorWithRank2; // @UiField(provided=true) RegattaCompetitor competitorWithRank3; @@ -103,7 +102,7 @@ public class Regatta extends Composite { boolean isFinished = !hasLiveRace; if(!isFinished) { isFinishedDiv.getStyle().setDisplay(Display.NONE); - isFinishedDiv2.getStyle().setDisplay(Display.NONE); +// isFinishedDiv2.getStyle().setDisplay(Display.NONE); } String regattaDisplayName = leaderboard.displayName != null ? leaderboard.displayName : leaderboard.name; @@ -121,21 +120,25 @@ public class Regatta extends Composite { } else { competitorsCount.getStyle().setVisibility(Visibility.HIDDEN); } - racesCount.setInnerText("tbd"); - trackedRacesCount.setInnerText("tbd"); - setRegattaProgress(); } private void setRegattaProgress() { + int racesCounter = 0; +// int trackedRacesCounter = 0; + if(raceGroup.getSeries().size() == 0) { regattaPhasesInfoDiv.getStyle().setDisplay(Display.NONE); } else { for(RaceGroupSeriesDTO series: raceGroup.getSeries()) { + racesCounter += series.getRaceColumns().size(); RegattaPhase regattaPhase = new RegattaPhase(series); regattaPhasesPanel.appendChild(regattaPhase.getElement()); } } + + racesCount.setInnerText(String.valueOf(racesCounter)); + // trackedRacesCount.setInnerText(String.valueOf(trackedRacesCounter)); } private void registerEvents() { 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..8c6ee934ea8 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 @@ -33,7 +33,9 @@
 
 
+
@@ -47,19 +49,19 @@
+
+ -->
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..135325dbed7 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; 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 80b716cfee2..3e18e397525 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 @@ -6,10 +6,8 @@ import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.AnchorElement; 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.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.uibinder.client.UiHandler; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.EventListener; import com.google.gwt.user.client.ui.Composite; From 68e4f2e478d8eba41ba736be1c6a655bce79e35c Mon Sep 17 00:00:00 2001 From: fmittag Date: Sat, 26 Jul 2014 13:42:45 +0200 Subject: [PATCH 271/323] fixed style problem in Regatta Races partial --- .../event/regattaraces/EventRegattaRaces.java | 2 + .../regattaraces/EventRegattaRacesMedium.css | 50 +++++++++++++++++++ .../regattaraces/EventRegattaRacesRace.java | 13 ++--- .../regattaraces/EventRegattaRacesRace.ui.xml | 4 +- .../EventRegattaRacesResources.java | 8 +++ 5 files changed, 68 insertions(+), 9 deletions(-) create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaraces/EventRegattaRacesMedium.css 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..5b80c4ce8c1 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,6 +1,7 @@ package com.sap.sailing.gwt.home.client.place.event.regattaraces; 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; @@ -39,6 +40,7 @@ public class EventRegattaRaces extends Composite { regatta = new Regatta(event, false, timerForClientServerOffset, pageNavigator); EventRegattaRacesResources.INSTANCE.css().ensureInjected(); + StyleInjector.injectAtEnd("@media (min-width: 25em) { "+EventRegattaRacesResources.INSTANCE.mediumCss().getText()+"}"); 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/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/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..beb468a7cbc 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,6 +7,7 @@ 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.Event; @@ -29,9 +30,9 @@ public class EventRegattaRacesRace extends UIObject { @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; @@ -57,7 +58,7 @@ public class EventRegattaRacesRace extends UIObject { // local_res.css.eventregattarace_featureunavailable -// private final DateTimeFormat raceTimeFormat = DateTimeFormat.getFormat("EEE, h:mm a"); + private final DateTimeFormat raceTimeFormat = DateTimeFormat.getFormat("EEE, h:mm a"); // private final TextMessages textMessages = GWT.create(TextMessages.class); private final StrippedLeaderboardDTO leaderboard; @@ -86,7 +87,7 @@ public class EventRegattaRacesRace extends UIObject { } raceName.setInnerText(raceColumn.getName()); -// raceName2.setInnerText(raceColumn.getName()); + raceName2.setInnerText(raceColumn.getName()); registerEvents(); updateUI(); @@ -116,7 +117,7 @@ public class EventRegattaRacesRace extends UIObject { 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); + String startOfTrackingTime = raceTimeFormat.format(race.trackedRace.startOfTracking); } return simpleRaceState; } 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..ac78a0f4a36 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 @@
-
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(); From 636dd7363471c54f32c3e110ed63945417091f60 Mon Sep 17 00:00:00 2001 From: fmittag Date: Sat, 26 Jul 2014 14:22:37 +0200 Subject: [PATCH 272/323] quick fix for margin between races in regatta header --- .../sap/sailing/gwt/home/client/place/event/regatta/Regatta.css | 1 + 1 file changed, 1 insertion(+) 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..65c6e24ea77 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 @@ -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; From 1666012d006da30002b398b8527e0b2ed53d4b72 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sat, 26 Jul 2014 15:28:27 +0200 Subject: [PATCH 273/323] also apply BFD (Black Flag Disqualification) at the start of the race already --- .../sap/sailing/domain/leaderboard/impl/ScoreCorrectionImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ScoreCorrectionImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ScoreCorrectionImpl.java index 5527e9ac1ed..3a4a3ee1e0a 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ScoreCorrectionImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ScoreCorrectionImpl.java @@ -296,6 +296,7 @@ public class ScoreCorrectionImpl implements SettableScoreCorrection { case DNS: case DNC: case OCS: + case BFD: result = true; break; default: From 13720a2f5f4a62637dab3f9e6e633266d0f4254e Mon Sep 17 00:00:00 2001 From: Steffen Tobias Wagner Date: Sat, 26 Jul 2014 14:22:02 +0000 Subject: [PATCH 274/323] added greyscale logo in headline --- home.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/home.md b/home.md index 64e2970f0fe..4d7684efad2 100644 --- a/home.md +++ b/home.md @@ -1,6 +1,6 @@ # Welcome to the SAP Sailing Wiki -This is the Sailing Analytics Wiki where useful information regarding this project can be found. +This is the Sailing Analytics Wiki where useful information regarding this project can be found. ### The Pitch From e5c7a5e55b5d9fa9a781e62ef9054f2f19667fd7 Mon Sep 17 00:00:00 2001 From: "Steffen Wagner (d059727)" Date: Sat, 26 Jul 2014 16:23:52 +0200 Subject: [PATCH 275/323] new grey font logo for wiki :-) --- .../com.sap.sailing.www/images/sap-logo_grey.png | Bin 0 -> 4041 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 java/com.sap.sailing.www/images/sap-logo_grey.png diff --git a/java/com.sap.sailing.www/images/sap-logo_grey.png b/java/com.sap.sailing.www/images/sap-logo_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..95b2c1a1de4817e70adbfe62512e99ba92edf12b GIT binary patch literal 4041 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 Date: Sun, 27 Jul 2014 01:26:40 +0200 Subject: [PATCH 276/323] trying to solve bug 2093: by introducing a dummy Anchor that can get the focus, blur the leaderboard row just selected to avoid jumping to top --- .../sailing/gwt/ui/leaderboard/LeaderboardPanel.java | 11 +++++++++++ 1 file changed, 11 insertions(+) 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 1690c1909f6..5ac6408a76d 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 @@ -299,6 +299,11 @@ 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; + protected StringMessages getStringMessages() { return stringMessages; } @@ -1501,6 +1506,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS String leaderboardName, ErrorReporter errorReporter, final StringMessages stringMessages, final UserAgentDetails userAgent, boolean showRaceDetails, RaceTimesInfoProvider optionalRaceTimesInfoProvider, boolean autoExpandLastRaceColumn, boolean adjustTimerDelay) { + this.dummyFocusElement = new Anchor(""); this.showRaceDetails = showRaceDetails; this.sailingService = sailingService; this.asyncActionsExecutor = asyncActionsExecutor; @@ -1572,6 +1578,10 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS } LeaderboardPanel.this.competitorSelectionProvider.setSelection(selection, /* listenersNotToNotify */LeaderboardPanel.this); updateLeaderboard(getLeaderboard()); + // now "blur" the selected leaderboard element because it seems to cause the cell table to scroll to its top; + // see bug 2093. + dummyFocusElement.setFocus(true); + dummyFocusElement.getElement().focus(); } }); if (userAgent.isMobile() == UserAgentDetails.PlatformTypes.MOBILE) { @@ -1655,6 +1665,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS contentPanel.add(toolbarPanel); } contentPanel.add(getLeaderboardTable()); + contentPanel.add(dummyFocusElement); // ensure dummy element is part of the DOM setWidget(contentPanel); raceNameForDefaultSorting = settings.getNameOfRaceToSort(); } From 9a9d4f453f563776146c6352aaa661f99b4ef1b9 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sun, 27 Jul 2014 01:39:38 +0200 Subject: [PATCH 277/323] don't do any scroll-avoiding focus/blur magic in EditableLeaderboardPanel; it would kill the efforts to select and focus the scores when pressing the Edit button --- .../gwt/ui/leaderboard/LeaderboardPanel.java | 15 +++++++++++---- .../leaderboardedit/EditableLeaderboardPanel.java | 10 ++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) 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 5ac6408a76d..a39ec752d1b 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 @@ -1578,10 +1578,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS } LeaderboardPanel.this.competitorSelectionProvider.setSelection(selection, /* listenersNotToNotify */LeaderboardPanel.this); updateLeaderboard(getLeaderboard()); - // now "blur" the selected leaderboard element because it seems to cause the cell table to scroll to its top; - // see bug 2093. - dummyFocusElement.setFocus(true); - dummyFocusElement.getElement().focus(); + blurFocusedElementAfterSelectionChange(); } }); if (userAgent.isMobile() == UserAgentDetails.PlatformTypes.MOBILE) { @@ -2736,4 +2733,14 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS protected Timer getTimer() { return timer; } + + /** + * + */ + protected void blurFocusedElementAfterSelectionChange() { + // now "blur" the selected leaderboard element because it seems to cause the cell table to scroll to its top; + // see bug 2093. + dummyFocusElement.setFocus(true); + dummyFocusElement.getElement().focus(); + } } 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 c0bfb11f520..e6ab927ee6a 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 @@ -918,5 +918,15 @@ public class EditableLeaderboardPanel extends LeaderboardPanel { suppressedCompetitorsShown.getList().add(suppressedCompetitor); } } + } + + /** + * When editing, scrolling is not really an issue because usually the editable version is not used in live mode. But when + * clicking the "Edit" button in a previously de-selected row, we still want the score to get the selection and the focus. + * So turn off any focus/blur magic for this subclass. + */ + @Override + protected void blurFocusedElementAfterSelectionChange() { } + } From 0432b0eeec99278da22bc986d9060bdd47a9652a Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sun, 27 Jul 2014 01:45:55 +0200 Subject: [PATCH 278/323] reverted the logo change that broke the SAP logo in the Wiki home page --- home.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/home.md b/home.md index 4d7684efad2..64e2970f0fe 100644 --- a/home.md +++ b/home.md @@ -1,6 +1,6 @@ # Welcome to the SAP Sailing Wiki -This is the Sailing Analytics Wiki where useful information regarding this project can be found. +This is the Sailing Analytics Wiki where useful information regarding this project can be found. ### The Pitch From 24e9604d059e241ceda64fbae31e6f6631f746df Mon Sep 17 00:00:00 2001 From: Steffen Tobias Wagner Date: Sun, 27 Jul 2014 08:44:31 +0000 Subject: [PATCH 279/323] fixed some typos --- wiki/server-replication.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/wiki/server-replication.md b/wiki/server-replication.md index 9b80a143f05..367996d99a1 100644 --- a/wiki/server-replication.md +++ b/wiki/server-replication.md @@ -103,27 +103,27 @@ Steps to get it working: * The LTE router ( _192.168.1.1_ ) should have DHCP and NS activated. Dhcp was within range _XXX.XXX.1.2 - XXX.XXX.1.49_ and our local replica machine had a static ip address of _XXX.XXX.1.50_. Please make sure, that the ip address is outside of the dhcp range. For a better setup we should think of using the local replica server for dhcp and ns services. * Setup networking (to your needs) on the local replica machine as follows: -**/etc/networking/interfaces** -
-auto eth0
-iface eth0 inet static
+ **/etc/networking/interfaces**
+ 
+ auto eth0
+ iface eth0 inet static
 	address 192.168.1.50
 	netmask 255.255.255.0
 	gateway 192.168.1.1
-
-**/etc/hosts** -
-127.0.0.1	localhost
-127.0.0.1	tw2014 tw2014.sapsailing.com
-192.168.1.50	tw2014 tw2014.sapsailing.com
-
-* For rewriting the URLs and Ports to the fitting Spectator URL when entering the URL we need a Apache installed (apt-get install apache2) and set up like on our sapsailing.com server. Please check 000-macros.conf, 001-events.conf and 000-main.conf and set it up as you need for the event. They need to be in the config dir of apache (/etc/apache2/mods-enabled and /etc/apache2/sites-enabled) +
+ **/etc/hosts** +
+ 127.0.0.1	localhost
+ 127.0.0.1	tw2014 tw2014.sapsailing.com
+ 192.168.1.50	tw2014 tw2014.sapsailing.com
+ 
+* For rewriting the URLs and Ports to the fitting spectator URL when entering the URL we need an Apache installed (apt-get install apache2) and set up like on our sapsailing.com server. Please check 000-macros.conf, 001-events.conf and 000-main.conf and set it up as you need for the event. They need to be in the config dir of apache (/etc/apache2/mods-enabled and /etc/apache2/sites-enabled) * For better remote administration of the local replica, set up and autossh tunnel to trac@sapsailing.com and put it into rc.local for autostarting, e.g. as the following:
 autossh -M 20009 -f -N -L 1337:127.0.0.1:22 -i SSHKEY trac@sapsailing.com
 
* Set up automatic replication in env.sh of the analystics server and start the server (not with root!) with startscript ~/servers/server/start -* On all Cube computers add the folling batch file to the windows task scheduling for a check every minute. The script switches hosts to local replica if available. Before the script can work, you have to edit host file manully and add the required hostnames: e.g. 192.168.1.50 tw2014.sapsailing.com. You also need to replace find and replace to the required ip addresses. Please note that all this would not be needed, if we would use the replica server also for dhcp and ns, so it makes really sense to set this up. +* On all Cube computers add the follwing batch file to the windows task scheduling for a check every minute. The script switches hosts to local replica if available. Before the script can work, you have to edit host file manully and add the required hostnames: e.g.
192.168.1.50	tw2014.sapsailing.com
You also need to replace "find" and "replace" to the required ip addresses. Please note that all this would not be needed, if we would use the replica server also for dhcp and ns, so it makes really sense to set this up.
 @echo off
 @setlocal enableextensions enabledelayedexpansion

From 1042ad6dc549028a572ab5deae0d25f31123ebc0 Mon Sep 17 00:00:00 2001
From: "Steffen Wagner (d059727)" 
Date: Sun, 27 Jul 2014 12:37:03 +0200
Subject: [PATCH 280/323] Revert "reverted the logo change that broke the SAP
 logo in the Wiki home page"

This reverts commit 0432b0eeec99278da22bc986d9060bdd47a9652a.
---
 home.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/home.md b/home.md
index 64e2970f0fe..4d7684efad2 100644
--- a/home.md
+++ b/home.md
@@ -1,6 +1,6 @@
 # Welcome to the SAP Sailing Wiki
 
-This is the  Sailing Analytics Wiki where useful information regarding this project can be found.
+This is the  Sailing Analytics Wiki where useful information regarding this project can be found.
 
 ### The Pitch
 

From 5e2325434d552667a90a0472f756f99879b62f30 Mon Sep 17 00:00:00 2001
From: Steffen Tobias Wagner 
Date: Sun, 27 Jul 2014 10:47:18 +0000
Subject: [PATCH 281/323] Removed double words

---
 home.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/home.md b/home.md
index 4d7684efad2..eac3ef3973a 100644
--- a/home.md
+++ b/home.md
@@ -1,6 +1,6 @@
 # Welcome to the SAP Sailing Wiki
 
-This is the  Sailing Analytics Wiki where useful information regarding this project can be found.
+This is the  Wiki where useful information regarding this project can be found.
 
 ### The Pitch
 

From 504ba822c1f24fe6ac17f0bd77c2ab596f04db79 Mon Sep 17 00:00:00 2001
From: Axel Uhl 
Date: Sun, 27 Jul 2014 13:36:11 +0200
Subject: [PATCH 282/323] fixing bug 2094: need to invalidate maneuver and
 cross track error cache after deserializing a TrackedRace

---
 .../sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java  | 2 +-
 .../com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java   | 2 ++
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java
index f56a09c6b79..5b5ddce5a03 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java
@@ -455,7 +455,7 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener {
     /**
      * Invalidates all cache contents for all competitors from the beginning of time
      */
-    private void invalidate() {
+    public void invalidate() {
         final List shuffledCompetitors = new ArrayList<>(cachePerCompetitor.keySet());
         Collections.shuffle(shuffledCompetitors);
         for (Competitor competitor : shuffledCompetitors) {
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 975bfe7b6d1..ac4b7572491 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
@@ -475,7 +475,9 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
         competitorRankingsLocks = createCompetitorRankingsLockMap();
         directionFromStartToNextMarkCache = new HashMap>();
         crossTrackErrorCache = new CrossTrackErrorCache(this);
+        crossTrackErrorCache.invalidate();
         maneuverCache = createManeuverCache();
+        triggerManeuverCacheRecalculationForAllCompetitors();
         logger.info("Deserialized race " + getRace().getName());
         shortTimeWindCache = new ShortTimeWindCache(this, millisecondsOverWhichToAverageWind / 2);
     }

From 9627d90851b74eb2d2b194229fc05c56548f7343 Mon Sep 17 00:00:00 2001
From: Axel Uhl 
Date: Sun, 27 Jul 2014 13:36:11 +0200
Subject: [PATCH 283/323] fixing bug 2094: need to invalidate maneuver and
 cross track error cache after deserializing a TrackedRace

---
 .../sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java  | 2 +-
 .../com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java   | 2 ++
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java
index f56a09c6b79..5b5ddce5a03 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java
@@ -455,7 +455,7 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener {
     /**
      * Invalidates all cache contents for all competitors from the beginning of time
      */
-    private void invalidate() {
+    public void invalidate() {
         final List shuffledCompetitors = new ArrayList<>(cachePerCompetitor.keySet());
         Collections.shuffle(shuffledCompetitors);
         for (Competitor competitor : shuffledCompetitors) {
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 975bfe7b6d1..ac4b7572491 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
@@ -475,7 +475,9 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
         competitorRankingsLocks = createCompetitorRankingsLockMap();
         directionFromStartToNextMarkCache = new HashMap>();
         crossTrackErrorCache = new CrossTrackErrorCache(this);
+        crossTrackErrorCache.invalidate();
         maneuverCache = createManeuverCache();
+        triggerManeuverCacheRecalculationForAllCompetitors();
         logger.info("Deserialized race " + getRace().getName());
         shortTimeWindCache = new ShortTimeWindCache(this, millisecondsOverWhichToAverageWind / 2);
     }

From c5157e08bd5dd058a8abc2d587a2235045ca7f89 Mon Sep 17 00:00:00 2001
From: Axel Uhl 
Date: Sun, 27 Jul 2014 16:22:18 +0200
Subject: [PATCH 284/323] refined solution for bug 2093 (leaderboard selection
 killing scroll position) by checking for a click event using CellPreviewEvent
 handler and checking for click

---
 .../FormattedDoubleDetailTypeColumn.java            |  2 +-
 .../gwt/ui/leaderboard/LeaderboardPanel.java        | 13 +++++++++++--
 2 files changed, 12 insertions(+), 3 deletions(-)

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 689671b9b52..0ff31c19742 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
@@ -24,7 +24,7 @@ public class FormattedDoubleDetailTypeColumn extends DetailTypeColumn(
         /* pageSize */10000, tableResources);
+        leaderboardTable.addCellPreviewHandler(new CellPreviewEvent.Handler() {
+            @Override
+            public void onCellPreview(CellPreviewEvent event) {
+                boolean isClick = "click".equals(event.getNativeEvent().getType());
+                if (isClick) {
+                    blurFocusedElementAfterSelectionChange();
+                }
+            }
+        });
         leaderboardTable.ensureDebugId("LeaderboardCellTable");
         getLeaderboardTable().setWidth("100%");
         leaderboardSelectionModel = new MultiSelectionModel();
@@ -1578,7 +1588,6 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
                 }
                 LeaderboardPanel.this.competitorSelectionProvider.setSelection(selection, /* listenersNotToNotify */LeaderboardPanel.this);
                 updateLeaderboard(getLeaderboard());
-                blurFocusedElementAfterSelectionChange();
             }
         });
         if (userAgent.isMobile() == UserAgentDetails.PlatformTypes.MOBILE) {

From 49b32c6f61f3356ded46685b081b1893b8da86c0 Mon Sep 17 00:00:00 2001
From: Axel Uhl 
Date: Sun, 27 Jul 2014 19:06:50 +0200
Subject: [PATCH 285/323] moved Countdown to home directory so it doesn't
 create dependency from non-home to home; also blur leaderboard in deferred
 command

---
 .../gwt/ui/common/client/CountdownTest.java      | 16 +++++++++-------
 .../client => home/client/shared}/Countdown.java |  4 ++--
 .../home/client/shared/stage/StageTeaser.java    |  6 +++---
 .../gwt/ui/leaderboard/LeaderboardPanel.java     |  9 +++++++++
 4 files changed, 23 insertions(+), 12 deletions(-)
 rename java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/{ui/common/client => home/client/shared}/Countdown.java (97%)

diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/common/client/CountdownTest.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/common/client/CountdownTest.java
index 01ed0d076e0..544c21cc430 100644
--- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/common/client/CountdownTest.java
+++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/common/client/CountdownTest.java
@@ -1,17 +1,19 @@
 package com.sap.sailing.gwt.ui.common.client;
 
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
+
 import org.junit.Test;
 
 import com.sap.sailing.domain.common.Duration;
 import com.sap.sailing.domain.common.TimePoint;
 import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
-import com.sap.sailing.gwt.ui.common.client.Countdown.CountdownListener;
-import com.sap.sailing.gwt.ui.common.client.Countdown.RemainingTime;
-import com.sap.sailing.gwt.ui.common.client.Countdown.Unit;
-
-import static org.junit.Assert.*;
-
-import static org.hamcrest.Matchers.*;
+import com.sap.sailing.gwt.home.client.shared.Countdown;
+import com.sap.sailing.gwt.home.client.shared.Countdown.CountdownListener;
+import com.sap.sailing.gwt.home.client.shared.Countdown.RemainingTime;
+import com.sap.sailing.gwt.home.client.shared.Countdown.Unit;
 
 public class CountdownTest {
     
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/Countdown.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/Countdown.java
similarity index 97%
rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/Countdown.java
rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/Countdown.java
index 30474d33e33..0cab44564f9 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/Countdown.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/Countdown.java
@@ -1,4 +1,4 @@
-package com.sap.sailing.gwt.ui.common.client;
+package com.sap.sailing.gwt.home.client.shared;
 
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.user.client.Timer;
@@ -97,7 +97,7 @@ public class Countdown {
      * @param zeroDate
      * @param countdownListener
      */
-    Countdown(TimePoint zeroDate, CountdownListener countdownListener, boolean dontSchedule) {
+    public Countdown(TimePoint zeroDate, CountdownListener countdownListener, boolean dontSchedule) {
         this.zeroDate = zeroDate;
         this.countdownListener = countdownListener;
         update();
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.java
index d8b0d1ff6e7..ed0e58d4f63 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/StageTeaser.java
@@ -12,9 +12,9 @@ import com.google.gwt.user.client.ui.HTMLPanel;
 import com.google.gwt.user.client.ui.Widget;
 import com.sap.sailing.domain.common.TimePoint;
 import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
-import com.sap.sailing.gwt.ui.common.client.Countdown;
-import com.sap.sailing.gwt.ui.common.client.Countdown.CountdownListener;
-import com.sap.sailing.gwt.ui.common.client.Countdown.RemainingTime;
+import com.sap.sailing.gwt.home.client.shared.Countdown;
+import com.sap.sailing.gwt.home.client.shared.Countdown.CountdownListener;
+import com.sap.sailing.gwt.home.client.shared.Countdown.RemainingTime;
 import com.sap.sailing.gwt.ui.shared.EventBaseDTO;
 
 public abstract class StageTeaser extends Composite {
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 89d3a2bc8d1..ee7aba06aed 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
@@ -17,6 +17,8 @@ import com.google.gwt.cell.client.CompositeCell;
 import com.google.gwt.cell.client.EditTextCell;
 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.Style.FontWeight;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
@@ -2751,5 +2753,12 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
         // see bug 2093.
         dummyFocusElement.setFocus(true);
         dummyFocusElement.getElement().focus();
+        Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+            @Override
+            public void execute() {
+                dummyFocusElement.setFocus(true);
+                dummyFocusElement.getElement().focus();
+            }
+        });
     }
 }

From a1964ea5b3ce38a9961bbab8fbb73428f7c34ac3 Mon Sep 17 00:00:00 2001
From: Axel Uhl 
Date: Sun, 27 Jul 2014 23:43:55 +0200
Subject: [PATCH 286/323] do the blur trick when clicked and twice after the
 selection was updated

---
 .../com.google.gwt.eclipse.core.prefs         |  2 +-
 .../gwt/ui/leaderboard/LeaderboardPanel.java  | 34 ++++++++++------
 .../gwt/ui/raceboard/RaceBoardPanel.java      | 19 ---------
 .../raceboard/SideBySideComponentViewer.java  | 40 ++++++++-----------
 4 files changed, 39 insertions(+), 56 deletions(-)

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 581bf904451..946db750b2f 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,4 @@
 eclipse.preferences.version=1
 entryPointModules=
 filesCopiedToWebInfLib=gwt-servlet.jar
-gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDMgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0UgLWNvbXBpbGVSZXBvcnQgLVhzb3ljRGV0YWlsZWRdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5BZG1pbkNvbnNvbGU8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuUmFjZUJvYXJkPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLkxlYWRlcmJvYXJkPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLlNwZWN0YXRvcjwvZW50cnktcG9pbnQtbW9kdWxlPjwvZ3d0LWNvbXBpbGUtc2V0dGluZ3M+
+gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDMgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0UgLWNvbXBpbGVSZXBvcnQgLVhzb3ljRGV0YWlsZWRdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5SYWNlQm9hcmQ8L2VudHJ5LXBvaW50LW1vZHVsZT48L2d3dC1jb21waWxlLXNldHRpbmdzPg\=\=
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 ee7aba06aed..e935b6e752b 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
@@ -306,6 +306,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
      * Used to set the focus to avoid undesirable table scolling
      */
     private final Anchor dummyFocusElement;
+    private int blurInOnSelectionChanged;
 
     protected StringMessages getStringMessages() {
         return stringMessages;
@@ -1574,6 +1575,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
             public void onCellPreview(CellPreviewEvent event) {
                 boolean isClick = "click".equals(event.getNativeEvent().getType());
                 if (isClick) {
+                    blurInOnSelectionChanged = 2; // blur a couple of times; doing it one time only doesn't seem to work reliably
                     blurFocusedElementAfterSelectionChange();
                 }
             }
@@ -1590,6 +1592,10 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
                 }
                 LeaderboardPanel.this.competitorSelectionProvider.setSelection(selection, /* listenersNotToNotify */LeaderboardPanel.this);
                 updateLeaderboard(getLeaderboard());
+                if (blurInOnSelectionChanged > 0) {
+                    blurInOnSelectionChanged--;
+                    blurFocusedElementAfterSelectionChange();
+                }
             }
         });
         if (userAgent.isMobile() == UserAgentDetails.PlatformTypes.MOBILE) {
@@ -2730,13 +2736,14 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
     }
     
     public void removeAllListeners() {
-        if(raceTimesInfoProviderListener != null) {
+        if (raceTimesInfoProviderListener != null) {
             getRaceTimesInfoProvider().removeRaceTimesInfoProviderListener(raceTimesInfoProviderListener);
         }
-        if(raceColumnSelection != null && raceColumnSelection.getType() == RaceColumnSelectionStrategies.LAST_N) {
-            getRaceTimesInfoProvider().removeRaceTimesInfoProviderListener((LastNRacesColumnSelection) raceColumnSelection);
+        if (raceColumnSelection != null && raceColumnSelection.getType() == RaceColumnSelectionStrategies.LAST_N) {
+            getRaceTimesInfoProvider().removeRaceTimesInfoProviderListener(
+                    (LastNRacesColumnSelection) raceColumnSelection);
         }
-        if(timer != null) {
+        if (timer != null) {
             timer.removeTimeListener(this);
         }
     }
@@ -2745,20 +2752,21 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
         return timer;
     }
 
-    /**
-     * 
-     */
     protected void blurFocusedElementAfterSelectionChange() {
         // now "blur" the selected leaderboard element because it seems to cause the cell table to scroll to its top;
         // see bug 2093.
-        dummyFocusElement.setFocus(true);
-        dummyFocusElement.getElement().focus();
-        Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+        blur();
+        final ScheduledCommand blurCommand = new ScheduledCommand() {
             @Override
             public void execute() {
-                dummyFocusElement.setFocus(true);
-                dummyFocusElement.getElement().focus();
+                blur();
             }
-        });
+        };
+        Scheduler.get().scheduleDeferred(blurCommand);
+    }
+
+    private void blur() {
+        dummyFocusElement.setFocus(true);
+        dummyFocusElement.getElement().focus();
     }
 }
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 3da1342b473..b187dfcc706 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
@@ -95,11 +95,6 @@ public class RaceBoardPanel extends SimplePanel implements RegattasDisplayer, Ra
      */
     private final Map racesByIdentifier;
     
-    /**
-     * The offset when scrolling with the menu entry anchors (in the top right corner).
-     */
-    private int scrollOffset;
-
     private final List componentViewers;
     private final FlowPanel componentControlsPanel;
     private final FlowPanel viewControlsPanel;
@@ -145,7 +140,6 @@ public class RaceBoardPanel extends SimplePanel implements RegattasDisplayer, Ra
         this.errorReporter = errorReporter;
         this.userAgent = userAgent;
         this.timer = timer;
-        this.scrollOffset = 0;        
         raceSelectionProvider.addRaceSelectionChangeListener(this);
         racesByIdentifier = new HashMap();
         selectedRaceIdentifier = raceSelectionProvider.getSelectedRaces().iterator().next();
@@ -564,19 +558,6 @@ public class RaceBoardPanel extends SimplePanel implements RegattasDisplayer, Ra
         return errorReporter;
     }
     
-    public int getScrollOffset() {
-        return scrollOffset;
-    }
-    
-    /**
-     * Sets the offset, when scrolling with the menu entry anchors (in the top right corner).
- * Only the absolute value of scrollOffset will be used. - * @param scrollOffset The new scrolling offset. Only the absolute value will be used. - */ - public void setScrollOffset(int scrollOffset) { - this.scrollOffset = Math.abs(scrollOffset); - } - @Override public void fillRegattas(List regattas) { racesByIdentifier.clear(); 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 index 727b9f076f8..836db73b4af 100644 --- 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 @@ -27,60 +27,54 @@ public class SideBySideComponentViewer implements ComponentViewer { this.leftComponent = leftComponent; this.rightComponent = rightComponent; 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()) + for (Component component : components) { + if (component.isVisible()) { splitLayoutPanel.addSouth(component.getEntryWidget(), 200); + } } - savedSplitPosition = 500; splitLayoutPanel.addWest(leftScrollPanel, savedSplitPosition); splitLayoutPanel.add(rightComponent.getEntryWidget()); } public void forceLayout() { - if(leftComponent.isVisible() && !rightComponent.isVisible()) { + if (leftComponent.isVisible() && !rightComponent.isVisible()) { // the leaderboard is visible, but not the map - if(isWidgetInSplitPanel(rightComponent.getEntryWidget())) { + if (isWidgetInSplitPanel(rightComponent.getEntryWidget())) { splitLayoutPanel.remove(rightComponent.getEntryWidget()); } - } - else if(!leftComponent.isVisible() && rightComponent.isVisible()) { + } else if (!leftComponent.isVisible() && rightComponent.isVisible()) { // the leaderboard is not visible, but the map is - if(isWidgetInSplitPanel(leftScrollPanel)) + if (isWidgetInSplitPanel(leftScrollPanel)) splitLayoutPanel.remove(leftScrollPanel); - } - else if(leftComponent.isVisible() && rightComponent.isVisible()) { + } else if (leftComponent.isVisible() && rightComponent.isVisible()) { // the leaderboard and the map are visible - if(!isWidgetInSplitPanel(leftScrollPanel) || !isWidgetInSplitPanel(rightComponent.getEntryWidget())) { - if(!isWidgetInSplitPanel(leftScrollPanel)) + if (!isWidgetInSplitPanel(leftScrollPanel) || !isWidgetInSplitPanel(rightComponent.getEntryWidget())) { + if (!isWidgetInSplitPanel(leftScrollPanel)) splitLayoutPanel.insertWest(leftScrollPanel, savedSplitPosition, rightComponent.getEntryWidget()); else splitLayoutPanel.insertEast(rightComponent.getEntryWidget(), savedSplitPosition, leftScrollPanel); } + } else if (!leftComponent.isVisible() && !rightComponent.isVisible()) { } - else if(!leftComponent.isVisible() && !rightComponent.isVisible()) { - } - - for(Component component: components) { + + for (Component component : components) { boolean isComponentInSplitPanel = isWidgetInSplitPanel(component.getEntryWidget()); - if(component.isVisible()) { - if(!isComponentInSplitPanel) { + if (component.isVisible()) { + if (!isComponentInSplitPanel) { splitLayoutPanel.insertSouth(component.getEntryWidget(), 200, splitLayoutPanel.getWidget(0)); } } else { - if(isComponentInSplitPanel) + if (isComponentInSplitPanel) splitLayoutPanel.remove(component.getEntryWidget()); } - } + } splitLayoutPanel.forceLayout(); } From af4ee3f002754a787de7495b6a295994ec936a19 Mon Sep 17 00:00:00 2001 From: fmittag Date: Mon, 28 Jul 2014 19:47:33 +0200 Subject: [PATCH 287/323] many improvements on races page --- .../common/dto/AbstractLeaderboardDTO.java | 16 ++++++++ .../domain/common/dto/TrackedRaceDTO.java | 12 ++++++ .../domain/base/impl/DomainFactoryImpl.java | 2 + .../place/event/header/EventHeader.java | 4 ++ .../place/event/header/EventHeader.ui.xml | 8 ++-- .../client/place/event/regatta/Regatta.java | 40 ++++++++++-------- .../client/place/event/regatta/Regatta.ui.xml | 4 +- .../event/regattaraces/EventRegattaRaces.java | 12 ++++++ .../EventRegattaRacesFleetGroup.java | 41 ++++++++++++++----- .../EventRegattaRacesFleetGroup.ui.xml | 1 + .../regattaraces/EventRegattaRacesPhase.java | 7 +++- .../regattaraces/EventRegattaRacesRace.java | 35 ++++++++++++++-- .../EventRegattaRacesRaceday.java | 6 ++- .../events/TabletAndDesktopEventsView.ui.xml | 4 +- .../shared/EventDatesFormatterUtil.java | 18 +++++++- .../gwt/ui/server/SailingServiceImpl.java | 14 +++++++ 16 files changed, 183 insertions(+), 41 deletions(-) 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..1327b223602 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 @@ -186,6 +186,22 @@ public abstract class AbstractLeaderboardDTO implements Serializable { return null; } + public int getRaceColumnsCount() { + return races.size(); + } + + 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/TrackedRaceDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/TrackedRaceDTO.java index 6e8e790fbe3..e6fcb0b95cd 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/TrackedRaceDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/TrackedRaceDTO.java @@ -13,6 +13,9 @@ public class TrackedRaceDTO implements Serializable { public boolean hasWindData; public boolean hasGPSData; + public boolean hasAudioData; + public boolean hasVideoData; + public boolean hasMeasuredWindData; public Date startOfTracking; public Date endOfTracking; @@ -27,6 +30,9 @@ public class TrackedRaceDTO implements Serializable { result = prime * result + ((endOfTracking == null) ? 0 : endOfTracking.hashCode()); result = prime * result + (hasGPSData ? 1231 : 1237); result = prime * result + (hasWindData ? 1231 : 1237); + result = prime * result + (hasAudioData ? 1231 : 1237); + result = prime * result + (hasVideoData ? 1231 : 1237); + result = prime * result + (hasMeasuredWindData ? 1231 : 1237); result = prime * result + ((startOfTracking == null) ? 0 : startOfTracking.hashCode()); result = prime * result + ((timePointOfNewestEvent == null) ? 0 : timePointOfNewestEvent.hashCode()); return result; @@ -52,6 +58,12 @@ public class TrackedRaceDTO implements Serializable { return false; if (hasWindData != other.hasWindData) return false; + if (hasAudioData != other.hasAudioData) + return false; + if (hasVideoData != other.hasVideoData) + return false; + if (hasMeasuredWindData != other.hasMeasuredWindData) + return false; if (startOfTracking == null) { if (other.startOfTracking != null) return false; 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 c49b68642a3..8aadca6823f 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 @@ -20,6 +20,7 @@ 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; @@ -140,6 +141,7 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain trackedRaceDTO.timePointOfNewestEvent = trackedRace.getTimePointOfNewestEvent() == null ? null : trackedRace.getTimePointOfNewestEvent().asDate(); trackedRaceDTO.hasWindData = trackedRace.hasWindData(); trackedRaceDTO.hasGPSData = trackedRace.hasGPSData(); + trackedRaceDTO.hasMeasuredWindData = Util.size(trackedRace.getWindSources(WindSourceType.EXPEDITION)) != 0; trackedRaceDTO.delayToLiveInMs = trackedRace.getDelayToLiveInMillis(); return trackedRaceDTO; } 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..5701abdb8b1 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,6 +4,7 @@ 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.event.dom.client.ClickEvent; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; @@ -46,6 +47,7 @@ public class EventHeader extends Composite { @UiField SpanElement eventDescription; @UiField SpanElement venueName; @UiField SpanElement eventWebsite; + @UiField DivElement isLiveDiv; @UiField ImageElement eventLogo; @UiField ImageElement eventLogo2; @@ -73,6 +75,8 @@ public class EventHeader extends Composite { // setActiveLink(links2, overviewLink2); // setActiveLink(links3, overviewLink3); + isLiveDiv.getStyle().setDisplay(Display.NONE); + setDataNavigationType("normal"); updateUI(); } 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..ba4462de43c 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 @@ -14,7 +14,7 @@

-
+
@@ -31,10 +31,12 @@
- + - + + +
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 46dbeb61e40..ecbc4703dc4 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,12 +1,14 @@ 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.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.Event; @@ -33,7 +35,8 @@ public class Regatta extends Composite { private final EventPageNavigator pageNavigator; private StrippedLeaderboardDTO leaderboard; private RaceGroupDTO raceGroup; - + private final List phasesElements; + @SuppressWarnings("unused") private LeaderboardGroupDTO leaderboardGroup; @@ -45,9 +48,9 @@ public class Regatta extends Composite { @UiField DivElement regattaPhasesPanel; @UiField DivElement regattaPhasesInfoDiv; - @UiField SpanElement competitorsCount; +// @UiField SpanElement competitorsCount; @UiField SpanElement racesCount; -// @UiField SpanElement trackedRacesCount; + @UiField SpanElement trackedRacesCount; @UiField DivElement isLiveDiv; // @UiField TableElement isLiveDiv2; @UiField DivElement isFinishedDiv; @@ -70,6 +73,8 @@ public class Regatta extends Composite { this.timerForClientServerOffset = timerForClientServerOffset; this.pageNavigator = pageNavigator; + phasesElements = new ArrayList(); + // competitorWithRank1 = new RegattaCompetitor(1, null); // competitorWithRank2 = new RegattaCompetitor(2, null); // competitorWithRank3 = new RegattaCompetitor(3, null); @@ -115,30 +120,31 @@ public class Regatta extends Composite { leaderboardGroupName.getStyle().setDisplay(Display.NONE); } - if(leaderboard.rows != null) { - competitorsCount.setInnerText(String.valueOf(leaderboard.rows.size())); - } else { - competitorsCount.getStyle().setVisibility(Visibility.HIDDEN); - } - setRegattaProgress(); +// if(leaderboard.rows != null) { +// competitorsCount.setInnerText(String.valueOf(leaderboard.rows.size())); +// } else { +// competitorsCount.getStyle().setVisibility(Visibility.HIDDEN); +// } + setRegattaProgress(leaderboard); } - private void setRegattaProgress() { - int racesCounter = 0; -// int trackedRacesCounter = 0; - + private void setRegattaProgress(StrippedLeaderboardDTO leaderboard) { + // clear first + regattaPhasesPanel.removeAllChildren(); + phasesElements.clear(); + if(raceGroup.getSeries().size() == 0) { regattaPhasesInfoDiv.getStyle().setDisplay(Display.NONE); } else { for(RaceGroupSeriesDTO series: raceGroup.getSeries()) { - racesCounter += series.getRaceColumns().size(); RegattaPhase regattaPhase = new RegattaPhase(series); regattaPhasesPanel.appendChild(regattaPhase.getElement()); + phasesElements.add(regattaPhase); } } - racesCount.setInnerText(String.valueOf(racesCounter)); - // trackedRacesCount.setInnerText(String.valueOf(trackedRacesCounter)); + racesCount.setInnerText(String.valueOf(leaderboard.getRaceColumnsCount())); + trackedRacesCount.setInnerText(String.valueOf(leaderboard.getTrackedRacesCount())); } private void registerEvents() { 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 8c6ee934ea8..e6550f21401 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 @@ -31,11 +31,11 @@
+
 
-
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 5b80c4ce8c1..3b0f869024e 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,5 +1,8 @@ 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; @@ -31,12 +34,15 @@ public class EventRegattaRaces extends Composite { private final Timer timerForClientServerOffset; private final EventPageNavigator pageNavigator; + private final List phaseElements; public EventRegattaRaces(EventDTO event, Timer timerForClientServerOffset, EventPageNavigator pageNavigator) { this.event = event; this.timerForClientServerOffset = timerForClientServerOffset; this.pageNavigator = pageNavigator; + phaseElements = new ArrayList(); + regatta = new Regatta(event, false, timerForClientServerOffset, pageNavigator); EventRegattaRacesResources.INSTANCE.css().ensureInjected(); @@ -45,6 +51,10 @@ public class EventRegattaRaces extends Composite { } public void setRacesFromRaceGroup(RaceGroupDTO raceGroup, StrippedLeaderboardDTO leaderboard) { + // clear all existing child elements first + regattaPhasesPanel.getElement().removeAllChildren(); + phaseElements.clear(); + regatta.setData(raceGroup, leaderboard, null); int regattaPhases = raceGroup.getSeries().size(); @@ -52,11 +62,13 @@ public class EventRegattaRaces extends Composite { for(RaceGroupSeriesDTO series: raceGroup.getSeries()) { EventRegattaRacesPhase regattaPhase = new EventRegattaRacesPhase(leaderboard, series, timerForClientServerOffset, pageNavigator); regattaPhasesPanel.getElement().appendChild(regattaPhase.getElement()); + phaseElements.add(regattaPhase); } } else { RaceGroupSeriesDTO raceGroupSeriesDTO = raceGroup.getSeries().get(0); EventRegattaRacesPhase regattaPhase = new EventRegattaRacesPhase(leaderboard, raceGroupSeriesDTO, timerForClientServerOffset, pageNavigator); 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/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..c45a2d33ed1 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) { @@ -33,13 +36,8 @@ public class EventRegattaRacesFleetGroup extends Composite { for(FleetDTO fleet: fleetsToShow) { EventRegattaRacesFleet eventRegattaRacesFleet = new EventRegattaRacesFleet(fleet); 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/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..9f300c3cf61 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,6 +9,7 @@ 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.place.event.EventPageNavigator; import com.sap.sailing.gwt.ui.shared.RaceGroupSeriesDTO; @@ -28,7 +29,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("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 beb468a7cbc..b22cd8230ab 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 @@ -5,7 +5,9 @@ import com.google.gwt.dom.client.AnchorElement; import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.SpanElement; +import com.google.gwt.dom.client.Style.BorderStyle; import com.google.gwt.dom.client.Style.Display; +import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.dom.client.Style.Visibility; import com.google.gwt.i18n.shared.DateTimeFormat; import com.google.gwt.uibinder.client.UiBinder; @@ -64,6 +66,7 @@ public class EventRegattaRacesRace extends UIObject { private final StrippedLeaderboardDTO leaderboard; private final FleetDTO fleet; private final RaceColumnDTO raceColumn; + private final RaceDTO race; private final EventPageNavigator pageNavigator; private final Timer timerForClientServerOffset; @@ -75,6 +78,7 @@ 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)); @@ -114,10 +118,8 @@ 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); } return simpleRaceState; } @@ -141,14 +143,42 @@ public class EventRegattaRacesRace extends UIObject { showElement(watchRaceDiv); showElement(raceLeaderDiv); showElement(legProgressDiv); + break; case TRACKED_AND_NOT_LIVE: showElement(analyzeRaceDiv); showElement(raceWinnerDiv); showElement(raceFeaturesDiv); + + updateRaceFeatures(); break; } } + + private void updateRaceFeatures() { + String startOfTrackingTime = raceTimeFormat.format(race.trackedRace.startOfTracking); + raceTime.setInnerText(startOfTrackingTime); + if(race.trackedRace.hasGPSData) { + featureGPS.getStyle().setBorderColor("red"); + featureGPS.getStyle().setBorderStyle(BorderStyle.SOLID); + featureGPS.getStyle().setBorderWidth(1, Unit.PX); + } + if(race.trackedRace.hasMeasuredWindData) { + featureWind.getStyle().setBorderColor("red"); + featureWind.getStyle().setBorderStyle(BorderStyle.SOLID); + featureWind.getStyle().setBorderWidth(1, Unit.PX); + } + if(race.trackedRace.hasVideoData) { + featureVideo.getStyle().setBorderColor("red"); + featureVideo.getStyle().setBorderStyle(BorderStyle.SOLID); + featureVideo.getStyle().setBorderWidth(1, Unit.PX); + } + if(race.trackedRace.hasAudioData) { + featureAudio.getStyle().setBorderColor("red"); + featureAudio.getStyle().setBorderStyle(BorderStyle.SOLID); + featureAudio.getStyle().setBorderWidth(1, Unit.PX); + } + } private void hideElement(Element el) { el.getStyle().setVisibility(Visibility.HIDDEN); @@ -161,7 +191,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/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/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..2a9ce3e88f4 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 @@ -20,8 +20,8 @@
- - + +
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/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index cf347d027d4..7d2b010ca65 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 @@ -161,6 +161,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; @@ -955,6 +956,19 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S TrackedRaceDTO trackedRaceDTO = null; if (trackedRace != null) { trackedRaceDTO = getBaseDomainFactory().createTrackedRaceDTO(trackedRace); + trackedRaceDTO.hasVideoData = false; + trackedRaceDTO.hasAudioData = false; + Collection mediaTracksForRace = getService().getMediaTracksForRace(raceIdentifier); + for(MediaTrack track: mediaTracksForRace) { + switch(track.mimeType.mediaType) { + case audio: + trackedRaceDTO.hasAudioData = true; + break; + case video: + trackedRaceDTO.hasVideoData = true; + break; + } + } } RaceWithCompetitorsDTO raceDTO = new RaceWithCompetitorsDTO(raceIdentifier, convertToCompetitorDTOs(r.getCompetitors()), trackedRaceDTO, getService().isRaceBeingTracked(regatta, r)); From 7fe2185ab7f54a941fbd54bab426afc1af991a88 Mon Sep 17 00:00:00 2001 From: fmittag Date: Mon, 28 Jul 2014 19:51:58 +0200 Subject: [PATCH 288/323] fixed race time on race detail page --- .../place/event/regattaraces/EventRegattaRacesRace.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 b22cd8230ab..09e76965809 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 @@ -144,20 +144,22 @@ public class EventRegattaRacesRace extends UIObject { showElement(raceLeaderDiv); showElement(legProgressDiv); + raceTime.setInnerText(raceTimeFormat.format(race.startOfRace)); + break; case TRACKED_AND_NOT_LIVE: showElement(analyzeRaceDiv); showElement(raceWinnerDiv); showElement(raceFeaturesDiv); + raceTime.setInnerText(raceTimeFormat.format(race.startOfRace)); updateRaceFeatures(); + break; } } private void updateRaceFeatures() { - String startOfTrackingTime = raceTimeFormat.format(race.trackedRace.startOfTracking); - raceTime.setInnerText(startOfTrackingTime); if(race.trackedRace.hasGPSData) { featureGPS.getStyle().setBorderColor("red"); featureGPS.getStyle().setBorderStyle(BorderStyle.SOLID); From 6534b255468e059fd10615472c78db7394e69bcf Mon Sep 17 00:00:00 2001 From: "Christopher Ronnewinkel (D036654)" Date: Mon, 28 Jul 2014 23:15:18 +0200 Subject: [PATCH 289/323] add scaling of last step to tree-grow; change sorting according to scaled time; fix auto-play to stop at time when omniscient reaches target; --- .../ui/simulator/ReplayPathCanvasOverlay.java | 2 +- .../sailing/simulator/impl/PathCandidate.java | 10 +-- .../impl/PathGeneratorTreeGrowWind.java | 66 ++++++++++--------- 3 files changed, 41 insertions(+), 37 deletions(-) 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.simulator/src/com/sap/sailing/simulator/impl/PathCandidate.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathCandidate.java index 734a6e919d6..3e7e5d82619 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathCandidate.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathCandidate.java @@ -5,8 +5,9 @@ import com.sap.sailing.simulator.TimedPosition; public class PathCandidate implements Comparable { - 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 (this.pos.getTimePoint().asMillis() == other.pos.getTimePoint().asMillis()) { 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..06c145f0544 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 @@ -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,7 +606,7 @@ 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)) { @@ -702,7 +704,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 +730,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 +740,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); From a9c360fd3738bcc365b1c6cc9a84c3f5ecd20c50 Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Tue, 29 Jul 2014 13:37:46 +0200 Subject: [PATCH 290/323] Updated jquery version for PolarSheets module --- java/com.sap.sailing.gwt.ui/PolarSheets.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 @@ - + From 2d09ef7ef1581e4178b64a2c28da29bee8b5ae43 Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Tue, 29 Jul 2014 13:51:34 +0200 Subject: [PATCH 291/323] Fixed temporary fix for NPE in polar sheets generation --- .../sap/sailing/gwt/ui/polarsheets/PolarSheetsChartPanel.java | 1 + 1 file changed, 1 insertion(+) 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) { From 387baae07052e08d4cfead2b1d11fd5499035ef1 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 29 Jul 2014 14:01:05 +0200 Subject: [PATCH 292/323] fixing bug 2017: delay changes are now received in an update handler --- .../impl/TracTracRaceTrackerImpl.java | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceTrackerImpl.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceTrackerImpl.java index cc5e8e43a74..a960e1e2d6a 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceTrackerImpl.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceTrackerImpl.java @@ -46,8 +46,8 @@ import com.sap.sailing.domain.tractracadapter.DomainFactory; import com.sap.sailing.domain.tractracadapter.Receiver; import com.sap.sailing.domain.tractracadapter.TracTracConnectionConstants; import com.sap.sailing.domain.tractracadapter.TracTracRaceTracker; -import com.tractrac.model.lib.api.ModelLocator; import com.sap.sse.common.Util; +import com.tractrac.model.lib.api.ModelLocator; import com.tractrac.model.lib.api.event.CreateModelException; import com.tractrac.model.lib.api.event.ICompetitor; import com.tractrac.model.lib.api.event.IEvent; @@ -62,6 +62,7 @@ import com.tractrac.subscription.lib.api.competitor.ICompetitorsListener; import com.tractrac.subscription.lib.api.event.IConnectionStatusListener; import com.tractrac.subscription.lib.api.event.ILiveDataEvent; import com.tractrac.subscription.lib.api.event.IStoredDataEvent; +import com.tractrac.subscription.lib.api.race.IRacesListener; public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements IConnectionStatusListener, TracTracRaceTracker, DynamicRaceDefinitionSet { private static final Logger logger = Logger.getLogger(TracTracRaceTrackerImpl.class.getName()); @@ -343,6 +344,25 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements public void addCompetitor(ICompetitor competitor) { } }); + eventSubscriber.subscribeRaces(new IRacesListener() { + @Override public void abandonRace(UUID raceId) {} + @Override public void addRace(IRace race) {} + @Override public void deleteRace(UUID raceId) {} + @Override public void reloadRace(UUID raceId) {} + @Override public void startTracking(UUID raceId) {} + @Override + public void updateRace(IRace race) { + if (Util.equalsWithNull(race, TracTracRaceTrackerImpl.this.tractracRace)) { + int delayToLiveInMillis = race.getLiveDelay()*1000; + for (RaceDefinition raceDefinition : getRaces()) { + DynamicTrackedRace trackedRace = getTrackedRegatta().getExistingTrackedRace(raceDefinition); + if (trackedRace != null) { + trackedRace.setDelayToLiveInMillis(delayToLiveInMillis); + } + } + } + } + }); // Start live and stored data streams Regatta effectiveRegatta = regatta; raceSubscriber = subscriberFactory.createRaceSubscriber(tractracRace, liveURI, storedURI); From 9b22ca61b05cfdc9cd657695d3fd56adbacb852d Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 29 Jul 2014 14:56:53 +0200 Subject: [PATCH 293/323] fixed mocking of tracked race because createTrackedRaceDTO now requires wind sources collection to at least be valid --- .../sap/sailing/domain/test/mock/MockedTrackedRace.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) 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 c7aa4f8615c..aa724fefb6d 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 From 93c8f66d41f7a1770b307c55e948072e10d2b4aa Mon Sep 17 00:00:00 2001 From: "Christopher Ronnewinkel (D036654)" Date: Tue, 29 Jul 2014 16:13:03 +0200 Subject: [PATCH 294/323] fix crash in simulator-field for too large times; --- .../sailing/gwt/ui/simulator/streamlets/SimulatorField.java | 3 +++ 1 file changed, 3 insertions(+) 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..b4710af3bf4 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 @@ -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)))) { From 7aa0647138e705c57a7ab693255a8924b2e56de7 Mon Sep 17 00:00:00 2001 From: "Christopher Ronnewinkel (D036654)" Date: Tue, 29 Jul 2014 16:15:30 +0200 Subject: [PATCH 295/323] fix time-panel pixel-to-value-conversion by setting step-size to 1 second; --- .../java/com/sap/sailing/gwt/ui/client/TimePanel.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) 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..4a3e05694f8 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 @@ -398,14 +398,9 @@ public class TimePanel extends SimplePanel implemen timeRangeProvider.setTimeRange(min, max, this); } - int numSteps = timeSlider.getElement().getClientWidth(); - if (numSteps > 0) { - timeSlider.setStepSize(numSteps, fireEvent); - } else { - timeSlider.setStepSize(1000, fireEvent); - } + // set timeSlider step-size to 1000 milliseconds (according to time represented as long) + timeSlider.setStepSize(1000, fireEvent); - // Christopher: following setCurrentValue requires stepsize to be set <> 0 (otherwise division by zero; NaN) if (timeSlider.getCurrentValue() == null) { timeSlider.setCurrentValue(new Double(min.getTime()), fireEvent); } From 14b7daf3a82db3d86e0cac31c982b358a6eca119 Mon Sep 17 00:00:00 2001 From: fmittag Date: Tue, 29 Jul 2014 16:43:07 +0200 Subject: [PATCH 296/323] fixed link in Upcoming events --- .../gwt/home/client/place/events/upcoming/UpcomingEvent.ui.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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..8f83f9bc781 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 @@ -7,7 +7,7 @@
- +
From 07b167b68f65269e1d874f0a59e76d4eb4b7f3db Mon Sep 17 00:00:00 2001 From: "Christopher Ronnewinkel (D036654)" Date: Tue, 29 Jul 2014 16:58:17 +0200 Subject: [PATCH 297/323] fix rounding of race times in tree-grow; --- .../sap/sailing/simulator/impl/PathGeneratorTreeGrowWind.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 06c145f0544..4cebe76ada6 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 @@ -226,7 +226,7 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase { 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; + long newTimeMillis = Math.round((prevTime.asMillis() + (pathPos.getTimePoint().asMillis() - prevTime.asMillis()) * heightFrac)/2000.0)*2000; TimePoint newTime = new MillisecondsTimePoint(newTimeMillis); pathPos = new TimedPositionImpl(newTime, newPos); reachedEnd = true; From 7b1a161ea38169fab4a9ad79e41e2558c6a87d37 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 29 Jul 2014 17:22:25 +0200 Subject: [PATCH 298/323] don't copy EventDTO fields twice after moving image URL and video URL filling to copyEventBaseFieldsToDTO --- .../sap/sailing/gwt/ui/server/SailingServiceImpl.java | 9 --------- 1 file changed, 9 deletions(-) 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 7d2b010ca65..b5153ee4a7c 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 @@ -3253,15 +3253,6 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S 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()); - } return eventDTO; } From 646e6aa0927589e506d9d63a1883e390439dcbfe Mon Sep 17 00:00:00 2001 From: fmittag Date: Tue, 29 Jul 2014 17:24:32 +0200 Subject: [PATCH 299/323] fixed upcoming event event link --- .../place/events/upcoming/UpcomingEvent.java | 36 ++++++++++--------- .../events/upcoming/UpcomingEvent.ui.xml | 6 ++-- 2 files changed, 21 insertions(+), 21 deletions(-) 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..f80d99bd2f8 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,50 @@ 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.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) { + navigator.goToEvent(event.id.toString(), event.getBaseURL()); + } + }); - 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 8f83f9bc781..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 @@ - - From 28d665160f4f3b9f1cbd9eff16dde282fc8e8b05 Mon Sep 17 00:00:00 2001 From: fmittag Date: Tue, 29 Jul 2014 17:27:59 +0200 Subject: [PATCH 300/323] further work on event header (competitors count, link to official homepage) --- .../sailing/gwt/home/client/i18n/TextMessages.java | 1 + .../gwt/home/client/i18n/TextMessages.properties | 1 + .../gwt/home/client/i18n/TextMessages_de.properties | 1 + .../home/client/place/event/header/EventHeader.java | 13 +++++++++---- .../client/place/event/header/EventHeader.ui.xml | 10 ++++++++-- .../client/place/event/overview/EventOverview.css | 2 +- .../home/client/place/event/regatta/Regatta.java | 13 +++++++------ .../home/client/place/event/regatta/Regatta.ui.xml | 4 +--- .../events/upcoming/EventsOverviewUpcoming.java | 5 ++--- .../sailing/gwt/ui/server/SailingServiceImpl.java | 1 + .../com/sap/sailing/gwt/ui/shared/EventDTO.java | 8 ++++++++ .../gwt/ui/shared/StrippedLeaderboardDTO.java | 2 ++ 12 files changed, 42 insertions(+), 19 deletions(-) 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..94709f68620 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 @@ -14,6 +14,7 @@ public interface TextMessages extends Messages { String german(); String english(); String fullEventSchedule(); + String officalEventWebsite(); String countdownDays(); String countdownHours(); 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..8543f69742d 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 @@ -9,6 +9,7 @@ search=Search german=Deutsch english=English fullEventSchedule=Full event schedule +officalEventWebsite=Offical event website countdownDays=Days countdownHours=Hrs 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..fbee95bab52 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 @@ -9,6 +9,7 @@ search=Suche german=Deutsch english=English fullEventSchedule=Full event schedule +officalEventWebsite=Offical event website countdownDays=Tage countdownHours=Std. 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 5701abdb8b1..f42d77bde93 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 @@ -38,7 +38,8 @@ public class EventHeader extends Composite { // @UiField Anchor regattasLink3; // @UiField Anchor scheduleLink3; // @UiField Anchor mediaLink3; - + @UiField Anchor officalWebsiteLink; + @UiField DivElement eventHeaderWrapperDiv; @UiField SpanElement eventName; @UiField SpanElement eventName2; @@ -46,8 +47,8 @@ public class EventHeader extends Composite { @UiField SpanElement eventDate; @UiField SpanElement eventDescription; @UiField SpanElement venueName; - @UiField SpanElement eventWebsite; @UiField DivElement isLiveDiv; + @UiField DivElement isFinishedDiv; @UiField ImageElement eventLogo; @UiField ImageElement eventLogo2; @@ -75,7 +76,11 @@ public class EventHeader extends Composite { // setActiveLink(links2, overviewLink2); // setActiveLink(links3, overviewLink3); - isLiveDiv.getStyle().setDisplay(Display.NONE); + if(event.isRunning()) { + isFinishedDiv.getStyle().setDisplay(Display.NONE); + } else if (event.isFinished()) { + isLiveDiv.getStyle().setDisplay(Display.NONE); + } setDataNavigationType("normal"); updateUI(); @@ -96,7 +101,7 @@ public class EventHeader extends Composite { eventDescription.setInnerHTML(event.getDescription()); } if(event.getOfficialWebsiteURL() != null) { - eventWebsite.setInnerHTML(event.getOfficialWebsiteURL()); + officalWebsiteLink.setHref(event.getOfficialWebsiteURL()); } String logoUrl = event.getLogoImageURL() != null ? event.getLogoImageURL() : defaultLogoUrl; 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 ba4462de43c..9360d635994 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 @@ -15,10 +15,16 @@

+
-
-
+
+ + +
+ + +
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/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 ecbc4703dc4..623322f5a06 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 @@ -48,7 +48,8 @@ public class Regatta extends Composite { @UiField DivElement regattaPhasesPanel; @UiField DivElement regattaPhasesInfoDiv; -// @UiField SpanElement competitorsCount; + @UiField SpanElement competitorsCount; + @UiField DivElement competitorsCountDiv; @UiField SpanElement racesCount; @UiField SpanElement trackedRacesCount; @UiField DivElement isLiveDiv; @@ -120,11 +121,11 @@ public class Regatta extends Composite { leaderboardGroupName.getStyle().setDisplay(Display.NONE); } -// if(leaderboard.rows != null) { -// competitorsCount.setInnerText(String.valueOf(leaderboard.rows.size())); -// } else { -// competitorsCount.getStyle().setVisibility(Visibility.HIDDEN); -// } + if(leaderboard.competitorsCount > 0) { + competitorsCount.setInnerText(String.valueOf(leaderboard.competitorsCount)); + } else { + competitorsCountDiv.getStyle().setDisplay(Display.NONE); + } setRegattaProgress(leaderboard); } 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 e6550f21401..871bacb25d6 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 @@ -31,9 +31,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..1f67bca5991 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 @@ -38,11 +38,10 @@ public class EventsOverviewUpcoming extends Composite { upcomingEventComposites.clear(); for(EventBaseDTO event: events) { - UpcomingEvent upcomingEvent = new UpcomingEvent(navigator); + UpcomingEvent upcomingEvent = new UpcomingEvent(event, navigator); - upcomingEvent.setEvent(event); 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/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 7d2b010ca65..1c6294fe776 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 @@ -2142,6 +2142,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S 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(); 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..b427b88859c 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,14 @@ public class EventDTO extends EventBaseDTO { regattas = new ArrayList(); } + 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/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)); } From a360bdf94d1ff087afa146ebc1fbac12756a7c6d Mon Sep 17 00:00:00 2001 From: fmittag Date: Tue, 29 Jul 2014 18:54:54 +0200 Subject: [PATCH 301/323] fix for building the redirect URL to www.sapsailing.com --- .../home/client/app/PlaceNavigatorImpl.java | 25 ++++++------------- 1 file changed, 7 insertions(+), 18 deletions(-) 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..c0b7af555de 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 @@ -25,44 +25,29 @@ 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); -// } } @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); -// } } @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 @@ -97,6 +82,10 @@ public class PlaceNavigatorImpl implements PlaceNavigator { Window.Location.replace(homeUrl); } } + + private String getLocationURL() { + return Window.Location.getProtocol() + "//" + Window.Location.getHost(); + } private String buildRemotePlaceUrl(String baseUrl, T destinationPlace, PlaceTokenizer tokenizer) { return baseUrl + "/gwt/Home.html#" + destinationPlace.getClass().getSimpleName() + ":" + tokenizer.getToken(destinationPlace); From 3828d180c700c073360828681eca7372e5f027b7 Mon Sep 17 00:00:00 2001 From: "Christopher Ronnewinkel (D036654)" Date: Tue, 29 Jul 2014 19:53:43 +0200 Subject: [PATCH 302/323] avoid rounding of race times in tree-grow by improving sorting of candidates; --- .../src/com/sap/sailing/simulator/impl/PathCandidate.java | 2 +- .../sap/sailing/simulator/impl/PathGeneratorTreeGrowWind.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathCandidate.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathCandidate.java index 3e7e5d82619..1a0c235d67f 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathCandidate.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathCandidate.java @@ -26,7 +26,7 @@ public class PathCandidate implements Comparable { @Override // sort descending by time, -#turns, width public int compareTo(PathCandidate other) { - if (this.pos.getTimePoint().asMillis() == other.pos.getTimePoint().asMillis()) { + 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; 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 4cebe76ada6..06c145f0544 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 @@ -226,7 +226,7 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase { 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)/2000.0)*2000; + 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; From 32e3546e561c33d9dae149635ffd43943df5bceb Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 29 Jul 2014 20:50:34 +0200 Subject: [PATCH 303/323] moved Travemuender Woche link down to 2014 section, out of live/upcoming --- java/com.sap.sailing.www/index.html | 42 ++++++++++++++--------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/java/com.sap.sailing.www/index.html b/java/com.sap.sailing.www/index.html index 6df1384f866..3c37fbcb979 100644 --- a/java/com.sap.sailing.www/index.html +++ b/java/com.sap.sailing.www/index.html @@ -45,27 +45,6 @@

Upcoming and Live


-
2014 Season @@ -92,6 +71,27 @@

2014

+
Jun 26 - 29 From 8758476fe33872b607ddff4c6d63187d96d52a4c Mon Sep 17 00:00:00 2001 From: "Christopher Ronnewinkel (D036654)" Date: Tue, 29 Jul 2014 21:12:56 +0200 Subject: [PATCH 304/323] fix display of polar-diagram; --- java/com.sap.sailing.gwt.ui/Simulator.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 @@ - + From bf3b1b78c956fea192ad4cb6c24d2fd34e63a8a7 Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Tue, 29 Jul 2014 22:18:40 +0200 Subject: [PATCH 305/323] incorporated Axel's feedback on the app spec --- wiki/racelog-tracking/app-spec/app-spec.md | 280 +++++++++++++++------ 1 file changed, 203 insertions(+), 77 deletions(-) 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 From 46268df65ab7c0c582e607b9b7a96903fceeadfe Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Tue, 29 Jul 2014 22:18:59 +0200 Subject: [PATCH 306/323] added css for the app-spec pdf to the repository --- wiki/racelog-tracking/app-spec/app-spec.css | 47 +++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 wiki/racelog-tracking/app-spec/app-spec.css 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; +} From cd896ee394e3d9a88657639202a2b6fe2c0d5c1e Mon Sep 17 00:00:00 2001 From: fmittag Date: Wed, 30 Jul 2014 13:23:29 +0200 Subject: [PATCH 307/323] fixed opening of leaderboard in embedded mode in regatta partial --- .../gwt/home/client/place/event/TabletAndDesktopEventView.java | 1 - 1 file changed, 1 deletion(-) 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..d8438a9df3b 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 @@ -170,7 +170,6 @@ public class TabletAndDesktopEventView extends Composite implements EventView, E Map linkParams = new HashMap(); linkParams.put("name", leaderboard.name); linkParams.put("showRaceDetails", "true"); - linkParams.put("embedded", "true"); if (leaderboard.displayName != null) { linkParams.put("displayName", leaderboard.displayName); } From 87fd43fe365af3573601030cd6b64bf34d40bc05 Mon Sep 17 00:00:00 2001 From: fmittag Date: Wed, 30 Jul 2014 13:45:27 +0200 Subject: [PATCH 308/323] fixed position of 'Official website' in event header --- .../home/client/i18n/TextMessages.properties | 2 +- .../client/i18n/TextMessages_de.properties | 2 +- .../place/event/header/EventHeader.java | 3 ++ .../place/event/header/EventHeaderLarge.css | 41 +++++++++++++++++++ .../event/header/EventHeaderResources.java | 8 ++++ 5 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/EventHeaderLarge.css 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 8543f69742d..4b81abd859e 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 @@ -9,7 +9,7 @@ search=Search german=Deutsch english=English fullEventSchedule=Full event schedule -officalEventWebsite=Offical event website +officalEventWebsite=Official event website countdownDays=Days countdownHours=Hrs 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 fbee95bab52..31779b5b2f6 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 @@ -9,7 +9,7 @@ search=Suche german=Deutsch english=English fullEventSchedule=Full event schedule -officalEventWebsite=Offical event website +officalEventWebsite=Official event website countdownDays=Tage countdownHours=Std. 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 f42d77bde93..47fb93e05df 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 @@ -5,6 +5,7 @@ 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; @@ -67,6 +68,8 @@ public class EventHeader extends Composite { this.pageNavigator = pageNavigator; EventHeaderResources.INSTANCE.css().ensureInjected(); + StyleInjector.injectAtEnd("@media (min-width: 50em) { "+EventHeaderResources.INSTANCE.largeCss().getText()+"}"); + initWidget(uiBinder.createAndBindUi(this)); // links1 = Arrays.asList(new Anchor[] { overviewLink, regattasLink, scheduleLink, mediaLink }); 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..f24f8a0890c --- /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,41 @@ +.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_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..27e81135966 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(); From 26f48815b7e53537736719ed9b1128e40db6b33e Mon Sep 17 00:00:00 2001 From: fmittag Date: Wed, 30 Jul 2014 14:03:11 +0200 Subject: [PATCH 309/323] shortened 'event description' display in EventManagementPanel --- .../gwt/ui/adminconsole/EventManagementPanel.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) 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..fe735f91acd 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(); From 376fd10d0041943229fbbb01464d125754de8672 Mon Sep 17 00:00:00 2001 From: Steffen Tobias Wagner Date: Wed, 30 Jul 2014 12:18:16 +0000 Subject: [PATCH 310/323] 505 worlds kiel 2014 setup for touch pcs --- 505-worlds-kiel-2014.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 505-worlds-kiel-2014.md diff --git a/505-worlds-kiel-2014.md b/505-worlds-kiel-2014.md new file mode 100644 index 00000000000..d6cbb5564f4 --- /dev/null +++ b/505-worlds-kiel-2014.md @@ -0,0 +1,4 @@ +#Setup touch displays +Hot Virtual Keyboard +Firefox Beta 32 +Firefox User Agent Switcher 0.7.3 From 33cf154c2adff7e7f95b7db4740ca12241a2a2bf Mon Sep 17 00:00:00 2001 From: Steffen Tobias Wagner Date: Wed, 30 Jul 2014 12:20:36 +0000 Subject: [PATCH 311/323] new event page 505 worlds --- home.md | 1 + 1 file changed, 1 insertion(+) diff --git a/home.md b/home.md index eac3ef3973a..ef9a7cf74a1 100644 --- a/home.md +++ b/home.md @@ -51,6 +51,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) From 79dbcb9e9d847e1920be413c2bd89ec5bc1d497d Mon Sep 17 00:00:00 2001 From: Steffen Tobias Wagner Date: Wed, 30 Jul 2014 12:23:51 +0000 Subject: [PATCH 312/323] Created 505 worlds kiel 2014 --- wiki/505-worlds-kiel-2014.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 wiki/505-worlds-kiel-2014.md diff --git a/wiki/505-worlds-kiel-2014.md b/wiki/505-worlds-kiel-2014.md new file mode 100644 index 00000000000..6e378dd46bb --- /dev/null +++ b/wiki/505-worlds-kiel-2014.md @@ -0,0 +1,4 @@ +#Setup touch displays +-Hot Virtual Keyboard +-Firefox Beta 32 +-Firefox User Agent Switcher 0.7.3 From 2d6ac69740cf6e82d08b19fa396d38c78fdf0e97 Mon Sep 17 00:00:00 2001 From: Generic Wiki User Date: Wed, 30 Jul 2014 12:24:32 +0000 Subject: [PATCH 313/323] Destroyed 505-worlds-kiel-2014 (markdown) --- 505-worlds-kiel-2014.md | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 505-worlds-kiel-2014.md diff --git a/505-worlds-kiel-2014.md b/505-worlds-kiel-2014.md deleted file mode 100644 index d6cbb5564f4..00000000000 --- a/505-worlds-kiel-2014.md +++ /dev/null @@ -1,4 +0,0 @@ -#Setup touch displays -Hot Virtual Keyboard -Firefox Beta 32 -Firefox User Agent Switcher 0.7.3 From f13e4a127e4a7a887fd3f090dc4a37821d6f9920 Mon Sep 17 00:00:00 2001 From: Steffen Tobias Wagner Date: Wed, 30 Jul 2014 12:49:19 +0000 Subject: [PATCH 314/323] Updated 505 worlds kiel 2014 (markdown) --- wiki/505-worlds-kiel-2014.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/wiki/505-worlds-kiel-2014.md b/wiki/505-worlds-kiel-2014.md index 6e378dd46bb..a2f052d8320 100644 --- a/wiki/505-worlds-kiel-2014.md +++ b/wiki/505-worlds-kiel-2014.md @@ -1,4 +1,14 @@ -#Setup touch displays --Hot Virtual Keyboard --Firefox Beta 32 --Firefox User Agent Switcher 0.7.3 +# 505 worlds Kiel 2014 + +### Touch Computer (Lenovo) Setup + +#### Installation Browser & Setup + +1. [ Firefox 32 Beta for best touch support - Download & Install ](https://www.mozilla.org/de/firefox/channel/#beta) +2. [ Firefox Addon User Agent Switcher 0.7.3 - Install](https://addons.mozilla.org/en-US/firefox/addon/user-agent-switcher/) +3. [ User Agent Switcher - Import UAs (XML)](http://techpatterns.com/downloads/firefox/useragentswitcher.xml) + +#### Installation Keyboard & Setup + +1. [ Hot Virtual Keyboard - Download & Install](http://hot-virtual-keyboard.com/files/vk_setup.exe) +2. Hot Virtual Keyboard Settings \ No newline at end of file From fa317af2a463b1ca26dea87a75e31756c4067de5 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 30 Jul 2014 14:59:53 +0200 Subject: [PATCH 315/323] trying to fix another NPE caused by insufficient mocking of wind sources in mocked tracked race implementation --- .../test/mock/MockedTrackedRaceWithStartTimeAndRanks.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) 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 2e05e29d664..8aade6f904d 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 From 1226e3f3e635dba1f57e3e7ac85cc09b14396269 Mon Sep 17 00:00:00 2001 From: Steffen Tobias Wagner Date: Wed, 30 Jul 2014 13:04:21 +0000 Subject: [PATCH 316/323] Updated 505 worlds kiel 2014 (markdown) --- wiki/505-worlds-kiel-2014.md | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/wiki/505-worlds-kiel-2014.md b/wiki/505-worlds-kiel-2014.md index a2f052d8320..c943af2f570 100644 --- a/wiki/505-worlds-kiel-2014.md +++ b/wiki/505-worlds-kiel-2014.md @@ -5,10 +5,24 @@ #### Installation Browser & Setup 1. [ Firefox 32 Beta for best touch support - Download & Install ](https://www.mozilla.org/de/firefox/channel/#beta) -2. [ Firefox Addon User Agent Switcher 0.7.3 - Install](https://addons.mozilla.org/en-US/firefox/addon/user-agent-switcher/) -3. [ User Agent Switcher - Import UAs (XML)](http://techpatterns.com/downloads/firefox/useragentswitcher.xml) + +2. User Firefox Addon OR set default UA of Firefox + + 2.1. [ Firefox Addon User Agent Switcher 0.7.3 - Install](https://addons.mozilla.org/en-US/firefox/addon/user-agent-switcher/) + + 2.2. [ User Agent Switcher - Import UAs (XML)](http://techpatterns.com/downloads/firefox/useragentswitcher.xml) + + 2.3. Set UA: Tools -> 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/) #### Installation Keyboard & Setup 1. [ Hot Virtual Keyboard - Download & Install](http://hot-virtual-keyboard.com/files/vk_setup.exe) -2. Hot Virtual Keyboard Settings \ No newline at end of file +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 From 3c4e710ecf38014e6430eb2a6b272e1d6b6c085f Mon Sep 17 00:00:00 2001 From: Steffen Tobias Wagner Date: Wed, 30 Jul 2014 13:35:32 +0000 Subject: [PATCH 317/323] Updated 505 worlds kiel 2014 (markdown) --- wiki/505-worlds-kiel-2014.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wiki/505-worlds-kiel-2014.md b/wiki/505-worlds-kiel-2014.md index c943af2f570..a530b0a5022 100644 --- a/wiki/505-worlds-kiel-2014.md +++ b/wiki/505-worlds-kiel-2014.md @@ -14,7 +14,7 @@ 2.3. Set UA: Tools -> Default User Agent -> Mobile Devices -> Browsers -> Firefox Fennec 10.0.1 -** alternatively** +**alternatively** 2.1. Change default UA [HowToGeek](http://www.howtogeek.com/113439/how-to-change-your-browsers-user-agent-without-installing-any-extensions/) From b407089fde66d806499696bf54747aeee5035a3f Mon Sep 17 00:00:00 2001 From: Steffen Tobias Wagner Date: Wed, 30 Jul 2014 13:37:12 +0000 Subject: [PATCH 318/323] Updated 505 worlds kiel 2014 (markdown) --- wiki/505-worlds-kiel-2014.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wiki/505-worlds-kiel-2014.md b/wiki/505-worlds-kiel-2014.md index a530b0a5022..de2679894e9 100644 --- a/wiki/505-worlds-kiel-2014.md +++ b/wiki/505-worlds-kiel-2014.md @@ -6,7 +6,7 @@ 1. [ Firefox 32 Beta for best touch support - Download & Install ](https://www.mozilla.org/de/firefox/channel/#beta) -2. User Firefox Addon OR set default UA of Firefox +2. User Agent Firefox Addon OR set default UA of Firefox 2.1. [ Firefox Addon User Agent Switcher 0.7.3 - Install](https://addons.mozilla.org/en-US/firefox/addon/user-agent-switcher/) From a5efb1f373b3db14e8128c0f111112bd44de4b06 Mon Sep 17 00:00:00 2001 From: Steffen Tobias Wagner Date: Wed, 30 Jul 2014 13:37:47 +0000 Subject: [PATCH 319/323] Updated 505 worlds kiel 2014 (markdown) --- wiki/505-worlds-kiel-2014.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wiki/505-worlds-kiel-2014.md b/wiki/505-worlds-kiel-2014.md index de2679894e9..9902ad88936 100644 --- a/wiki/505-worlds-kiel-2014.md +++ b/wiki/505-worlds-kiel-2014.md @@ -14,9 +14,9 @@ 2.3. Set UA: Tools -> Default User Agent -> Mobile Devices -> Browsers -> Firefox Fennec 10.0.1 -**alternatively** + **alternatively** - 2.1. Change default UA [HowToGeek](http://www.howtogeek.com/113439/how-to-change-your-browsers-user-agent-without-installing-any-extensions/) + 2.1. Change default UA [HowToGeek](http://www.howtogeek.com/113439/how-to-change-your-browsers-user-agent-without-installing-any-extensions/) #### Installation Keyboard & Setup From 7e3d8e1960346f6d8db0bf12dd552519f7e5a5e2 Mon Sep 17 00:00:00 2001 From: "Christopher Ronnewinkel (D036654)" Date: Wed, 30 Jul 2014 16:30:14 +0200 Subject: [PATCH 320/323] Revert "fix time-panel pixel-to-value-conversion by setting step-size to 1 second;" This reverts commit 7aa0647138e705c57a7ab693255a8924b2e56de7. --- .../java/com/sap/sailing/gwt/ui/client/TimePanel.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 4a3e05694f8..0128d6198d8 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 @@ -398,9 +398,14 @@ public class TimePanel extends SimplePanel implemen timeRangeProvider.setTimeRange(min, max, this); } - // set timeSlider step-size to 1000 milliseconds (according to time represented as long) - timeSlider.setStepSize(1000, fireEvent); + int numSteps = timeSlider.getElement().getClientWidth(); + if (numSteps > 0) { + timeSlider.setStepSize(numSteps, fireEvent); + } else { + timeSlider.setStepSize(1000, fireEvent); + } + // Christopher: following setCurrentValue requires stepsize to be set <> 0 (otherwise division by zero; NaN) if (timeSlider.getCurrentValue() == null) { timeSlider.setCurrentValue(new Double(min.getTime()), fireEvent); } From 3d921a050b82e0c5ab360e8dddac9e8105afd76f Mon Sep 17 00:00:00 2001 From: Steffen Tobias Wagner Date: Wed, 30 Jul 2014 17:09:47 +0000 Subject: [PATCH 321/323] inserted UA of a samsung tablet --- wiki/505-worlds-kiel-2014.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/wiki/505-worlds-kiel-2014.md b/wiki/505-worlds-kiel-2014.md index 9902ad88936..099cea621a1 100644 --- a/wiki/505-worlds-kiel-2014.md +++ b/wiki/505-worlds-kiel-2014.md @@ -16,7 +16,10 @@ **alternatively** - 2.1. Change default UA [HowToGeek](http://www.howtogeek.com/113439/how-to-change-your-browsers-user-agent-without-installing-any-extensions/) + 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 From 09cba3fe3fe14dcb927850e671713252d310b7a9 Mon Sep 17 00:00:00 2001 From: "Christopher Ronnewinkel (D036654)" Date: Tue, 29 Jul 2014 16:15:30 +0200 Subject: [PATCH 322/323] fix time-panel pixel-to-value-conversion by setting step-size to 1 second; --- .../java/com/sap/sailing/gwt/ui/client/TimePanel.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) 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..4a3e05694f8 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 @@ -398,14 +398,9 @@ public class TimePanel extends SimplePanel implemen timeRangeProvider.setTimeRange(min, max, this); } - int numSteps = timeSlider.getElement().getClientWidth(); - if (numSteps > 0) { - timeSlider.setStepSize(numSteps, fireEvent); - } else { - timeSlider.setStepSize(1000, fireEvent); - } + // set timeSlider step-size to 1000 milliseconds (according to time represented as long) + timeSlider.setStepSize(1000, fireEvent); - // Christopher: following setCurrentValue requires stepsize to be set <> 0 (otherwise division by zero; NaN) if (timeSlider.getCurrentValue() == null) { timeSlider.setCurrentValue(new Double(min.getTime()), fireEvent); } From 6b6c4d69530912c9d2b75e5042793f0ad8966262 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 31 Jul 2014 13:38:22 +0200 Subject: [PATCH 323/323] Javadoc comment improvements on TimeSlider --- .../com/sap/sse/gwt/client/controls/slider/TimeSlider.java | 7 +++++++ 1 file changed, 7 insertions(+) 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();