From a5a512acc679fcae672de777cee75ce5dc6de85c Mon Sep 17 00:00:00 2001 From: D067890 Date: Tue, 18 Sep 2018 12:30:06 +0200 Subject: [PATCH] bug4104: Sharing of tags is now supported --- .../sailing/gwt/ui/client/StringMessages.java | 5 ++ .../gwt/ui/client/StringMessages.properties | 5 ++ .../ui/client/StringMessages_de.properties | 5 ++ .../gwt/ui/raceboard/RaceBoardPanel.java | 10 ++- .../gwt/ui/raceboard/tagging/TagCell.java | 30 ++++++-- .../raceboard/tagging/TagPanelResources.java | 10 +++ .../raceboard/tagging/TagSharedURLDialog.java | 65 ++++++++++++++++++ .../ui/raceboard/tagging/TaggingPanel.java | 60 ++++++++++++++-- .../ui/raceboard/tagging/tagging-panel.gss | 28 +++++++- .../sailing/gwt/ui/client/images/share.png | Bin 0 -> 312 bytes 10 files changed, 205 insertions(+), 13 deletions(-) create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/TagSharedURLDialog.java create mode 100644 java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/share.png 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 d0e01cecddc..fcb7c56cab4 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 @@ -2113,6 +2113,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, String tagAddTag(); String tagEditTag(); String tagDeleteTag(); + String tagShareTag(); String tagAddTags(); String tagSavedSuccessfully(); String tagNotSaved(); @@ -2157,4 +2158,8 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, String close(); String username(); String tagPublicModificationPermissionMissing(); + String tagNotFound(); + String tagInvalidURL(); + String tagSharedDialog(); + String tagSharedURL(); } 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 0d948dfa07b..a5f588564a7 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 @@ -2109,6 +2109,7 @@ tagNoTagsFound=No tags found! tagAddTag=Add Tag tagEditTag=Edit Tag tagDeleteTag=Delete Tag +tagShareTag=Share Tag tagAddTags=Add Tags tagSavedSuccessfully=Saved tag successfully tagNotSaved=Could not save tag! @@ -2153,3 +2154,7 @@ username=User name confirm=Confirm cancel=Cancel close=Close +tagNotFound=Tag could not be found! +tagInvalidURL=Invalid URL parameter +tagSharedDialog=Shared tag +tagSharedURL=URL: \ 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 701b2385701..71fb2f56629 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 @@ -2104,6 +2104,7 @@ tagNoTagsFound=Keine Tags gefunden! tagAddTag=Tag hinzufügen tagEditTag=Tag bearbeiten tagDeleteTag=Tag löschen +tagShareTag=Tag teilen tagAddTags=Tags hinzufügen tagSavedSuccessfully=Tag erfolgreich gespeichert tagNotSaved=Tag konnte nicht gespeichert werden! @@ -2148,3 +2149,7 @@ username=Benutzername confirm=Fortfahren cancel=Abbrechen close=Schließen +tagNotFound=Tag wurde nicht gefunden! +tagInvalidURL=URL Parameter nicht gültig +tagSharedDialog=Geteilter Tag +tagSharedURL=URL: \ No newline at end of file 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 e79745949ae..cd18e5626c6 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 @@ -302,10 +302,16 @@ public class RaceBoardPanel raceMap.getRightHeaderPanel().add(userManagementMenuView); addChildComponent(raceMap); - // add panel for tagging functionality, hidden by default + // add panel for tagging functionality, hidden if no url parameter "tags" is passed default taggingPanel = new TaggingPanel(parent, componentContext, stringMessages, sailingService, userService, timer, raceTimesInfoProvider); addChildComponent(taggingPanel); - taggingPanel.setVisible(false); + + if(Window.Location.getParameter("tag") != null) { + taggingPanel.setVisible(true); + } + else { + taggingPanel.setVisible(false); + } // Determine if the screen is large enough to initially display the leaderboard panel on the left side of the // map based on the initial screen width. Afterwards, the leaderboard panel visibility can be toggled as usual. diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/TagCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/TagCell.java index 6aa70d23c69..5ec6c291f21 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/TagCell.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/TagCell.java @@ -14,10 +14,13 @@ import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.safehtml.shared.SafeHtmlUtils; import com.google.gwt.safehtml.shared.SafeUri; import com.google.gwt.safehtml.shared.UriUtils; +import com.google.gwt.user.client.Window; import com.sap.sailing.domain.common.dto.TagDTO; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.raceboard.tagging.TagPanelResources.TagPanelStyle; import com.sap.sailing.gwt.ui.raceboard.tagging.TaggingPanel.State; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.security.ui.client.UserService; /** @@ -29,7 +32,7 @@ public class TagCell extends AbstractCell { * Contains different configurations for {@link TagCell}s. Used as {@link SafeHtmlTemplates template} for * {@link TagCell}s.
*
- * Notice: CSS classes need to be provied as parameters as CSS classnames are context sensitive and can't be + * Notice: CSS classes need to be provided as parameters as CSS classnames are context sensitive and can't be * hardcoded. */ protected interface TagCellTemplate extends SafeHtmlTemplates { @@ -49,11 +52,13 @@ public class TagCell extends AbstractCell { * @param content * available configurations are {@link #contentWithCommentWithImage}, * {@link #contentWithCommentWithoutImage} and {@link #contentWithoutCommentWithImage}. + * @param shareButton + * @param content2 * @return {@link SafeHtml HTML template} */ - @Template("
{3}{4}{5}
{6}
{7}
") + @Template("
{3}{4}{5}{8}
{6}
{7}
") SafeHtml cell(String classTag, String classTagHeading, String classTagCreated, SafeHtml icon, SafeHtml buttons, - SafeHtml tag, SafeHtml created, SafeHtml content); + SafeHtml tag, SafeHtml created, SafeHtml content, SafeHtml shareButtonHeader); /** * Renders content with maximal configuration (comment and image). @@ -105,6 +110,14 @@ public class TagCell extends AbstractCell { SafeHtml headerButtonsDeletableAndModifyable(String classTagHeadingButtons, SafeHtml editButton, SafeHtml deleteButton); + /** + * Renders part of heading holding a button to share a tag. + * + * @return {@link SafeHtml HTML template} + */ + @Template("
{1}
") + SafeHtml shareButtonHeader(String classTagHeadingButtons, SafeHtml shareButton); + /** * Renders icon with given source. * @@ -185,6 +198,10 @@ public class TagCell extends AbstractCell { icon = tagCellTemplate.icon(safeIsPrivateImageUri); } + SafeHtml shareButton = tagCellTemplate.button(style.tagActionButton() + " " + style.tagShareButton(), + stringMessages.tagShareTag(), tagCellTemplate.icon(resources.shareIcon().getSafeUri())); + SafeHtml shareButtonHeader = tagCellTemplate.shareButtonHeader(style.tagCellHeadingButtons(), shareButton); + SafeHtml headingButtons = SafeHtmlUtils.EMPTY_SAFE_HTML; // preview cells do not show buttons if (!isPreviewCell) { @@ -213,7 +230,7 @@ public class TagCell extends AbstractCell { cellStyle = style.tagCell() + " " + style.tagCellActive(); } SafeHtml cell = tagCellTemplate.cell(cellStyle, style.tagCellHeading(), style.tagCellCreated(), icon, - headingButtons, safeTag, safeCreated, content); + headingButtons, safeTag, safeCreated, content, shareButtonHeader); htmlBuilder.append(cell); } @@ -251,6 +268,11 @@ public class TagCell extends AbstractCell { }); } else if (button.hasClassName(style.tagEditButton())) { taggingPanel.setCurrentState(State.EDIT_TAG); + } else if (button.hasClassName(style.tagShareButton())) { + String currentURL = Window.Location.getHref(); + String urlWithTagParam = currentURL + .concat("&tag=" + tag.getRaceTimepoint().asMillis() + tag.getTag()); + new TagSharedURLDialog(taggingPanel, urlWithTagParam).show(); } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/TagPanelResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/TagPanelResources.java index 8cc8066baea..10931bddfc2 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/TagPanelResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/TagPanelResources.java @@ -11,6 +11,9 @@ import com.google.gwt.resources.client.ImageResource; public interface TagPanelResources extends ClientBundle { public static final TagPanelResources INSTANCE = GWT.create(TagPanelResources.class); + @Source("com/sap/sailing/gwt/ui/client/images/share.png") + ImageResource shareIcon(); + @Source("com/sap/sailing/gwt/ui/client/images/edit.png") ImageResource editIcon(); @@ -62,6 +65,7 @@ public interface TagPanelResources extends ClientBundle { String tagActionButton(); String tagEditButton(); String tagDeleteButton(); + String tagShareButton(); String tagCellCreated(); String tagCellComment(); String tagCellImage(); @@ -100,6 +104,11 @@ public interface TagPanelResources extends ClientBundle { String tagFilterSearchBox(); String tagFilterSearchInput(); String tagFilterCurrentSelection(); + + //shared tag dialog + String tagSharedURLLabel(); + String tagSharedURLTextBox(); + String tagSharedURLDialog(); // images String imageActiveFilter(); @@ -108,6 +117,7 @@ public interface TagPanelResources extends ClientBundle { String imageClearSearch(); String imageSettings(); String imagePusTransparent(); + String imageShare(); String imageEdit(); String imageDelete(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/TagSharedURLDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/TagSharedURLDialog.java new file mode 100644 index 00000000000..9cbea3de075 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/TagSharedURLDialog.java @@ -0,0 +1,65 @@ +package com.sap.sailing.gwt.ui.raceboard.tagging; + +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.DialogBox; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.Panel; +import com.google.gwt.user.client.ui.TextBox; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.raceboard.tagging.TagPanelResources.TagPanelStyle; + +/** + * A Dialog to show the URL of shared tags + */ +public class TagSharedURLDialog extends DialogBox { + + private final TagPanelResources resources = TagPanelResources.INSTANCE; + private final TagPanelStyle style = resources.style(); + + /** + * Creates a Dialog which shows the user the URL of the tag he wants to share + * + * @param taggingPanel + * {@link TaggingPanel} which creates this {@link TagSharedURLDialog}. + * @param url + * A URL which if called opens the {@link TaggingPanel} and selects the shared tag. + */ + public TagSharedURLDialog(TaggingPanel taggingPanel, String url) { + StringMessages stringMessages = taggingPanel.getStringMessages(); + + Panel mainPanel = new FlowPanel(); + + Label descriptionLabel = new Label(); + descriptionLabel.setText(stringMessages.tagSharedURL()); + descriptionLabel.setStyleName(style.tagSharedURLLabel()); + descriptionLabel.addStyleName("gwt-Label"); + + TextBox urlTextBox = new TextBox(); + urlTextBox.setText(url); + urlTextBox.setStyleName(style.tagSharedURLTextBox()); + urlTextBox.addStyleName("gwt-TextBox"); + + Panel buttonPanel = new FlowPanel(); + buttonPanel.setStyleName(style.buttonsPanel()); + + Button closeButton = new Button(stringMessages.close()); + closeButton.setStyleName(style.tagDialogButton()); + closeButton.addStyleName("gwt-Button"); + closeButton.addClickHandler(event -> { + hide(); + }); + buttonPanel.add(closeButton); + + mainPanel.add(descriptionLabel); + mainPanel.add(urlTextBox); + mainPanel.add(buttonPanel); + + setGlassEnabled(true); + setText(stringMessages.tagSharedDialog()); + addStyleName(style.tagSharedURLDialog()); + setWidget(mainPanel); + center(); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/TaggingPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/TaggingPanel.java index e7d686f2f73..1d7eb14bd54 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/TaggingPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/TaggingPanel.java @@ -8,6 +8,7 @@ import java.util.Map; import com.google.gwt.core.shared.GWT; import com.google.gwt.user.cellview.client.CellList; import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy; +import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.FlowPanel; @@ -51,9 +52,9 @@ import com.sap.sse.security.ui.shared.UserDTO; * will be stored in the {@link com.sap.sse.security.UserStore UserStore}.
*
* The TaggingPanel is also used as a data provider for all of its subcomponents like header, footer and content - * section. Therefor the TaggingPanel provides references to important services, string messages, its current state ando + * section. Therefore the TaggingPanel provides references to important services, string messages, its current state ando * so on. - * Best practise: The constructor of subcomponents of the TaggingPanel contains only the TaggingPanel as a parameter. + * Best practice: The constructor of subcomponents of the TaggingPanel contains only the TaggingPanel as a parameter. * Every other required shared resource (string messages, service references, ...) can be requested from the * TaggingPanel itself. */ @@ -100,6 +101,11 @@ public class TaggingPanel extends ComponentWithoutSettings // current state of the Tagging-Panel private State currentState; + + //Needed for sharing Tags + private boolean firstTimePublicTagsLoaded = true; + private final TimePoint sharedTimePoint; + private final String sharedTag; public TaggingPanel(Component parent, ComponentContext context, StringMessages stringMessages, SailingServiceAsync sailingService, UserService userService, Timer timer, @@ -132,6 +138,26 @@ public class TaggingPanel extends ComponentWithoutSettings raceTimesInfoProvider.addRaceTimesInfoProviderListener(this); setCurrentState(State.VIEW); + + //Get the url parameter "tag" and divide it into the logical timepoint and the tag title + String urlParameter = Window.Location.getParameter("tag"); + if(urlParameter != null) { + if(urlParameter.length() > 13) { + String timeMillisString = urlParameter.substring(0, 13);//Works till "Nov 20 2286", afterwards timeMillis length increases from 13 to 14 chars + String tagString = urlParameter.substring(13, urlParameter.length()); + sharedTimePoint = new MillisecondsTimePoint(Long.parseLong(timeMillisString)); + sharedTag = tagString; + } + else { + Notification.notify(stringMessages.tagInvalidURL(), NotificationType.WARNING); + sharedTimePoint = null; + sharedTag = null; + } + } + else { + sharedTimePoint = null; + sharedTag = null; + } initializePanel(); } @@ -236,7 +262,7 @@ public class TaggingPanel extends ComponentWithoutSettings NotificationType.WARNING); } else if (!isLoggedInAndRaceLogAvailable()) { - // User is not logged in or race can not be identifed because regatta, race column or fleet are missing. + // User is not logged in or race can not be identified because regatta, race column or fleet are missing. Notification.notify(stringMessages.tagNotSaved(), NotificationType.ERROR); } else if (tag.isEmpty()) { @@ -540,7 +566,7 @@ public class TaggingPanel extends ComponentWithoutSettings for (TagDTO tag : raceInfo.getTags()) { if (tag.getRevokedAt() != null) { // received tag is revoked => latestReceivedTagTime will be revokedAt if revoke event - // occured before latestReceivedTagTime + // occurred before latestReceivedTagTime currentTags.remove(tag); modifiedTags = true; if (latestReceivedTagTime == null || (latestReceivedTagTime != null @@ -550,7 +576,7 @@ public class TaggingPanel extends ComponentWithoutSettings } } else if (!currentTags.contains(tag)) { // received tag is NOT revoked => latestReceivedTagTime will be createdAt if tag event - // occured before latestReceivedTagTime + // occurred before latestReceivedTagTime currentTags.add(tag); modifiedTags = true; if (latestReceivedTagTime == null || (latestReceivedTagTime != null @@ -569,6 +595,30 @@ public class TaggingPanel extends ComponentWithoutSettings if (modifiedTags) { updateContent(); } + //After tags were added for the first time, find tag which matches the URL Parameter "tag", hightlight it and jump to its logical timepoint + if(firstTimePublicTagsLoaded && raceInfo.getTags() != null) { + firstTimePublicTagsLoaded = false; + if(sharedTimePoint != null) { + timer.setTime(sharedTimePoint.asMillis()); + if(sharedTag != null) { + TagDTO matchingTag = null; + for(TagDTO tag: tagListProvider.getAllTags()) { + if(tag.getRaceTimepoint().equals(sharedTimePoint) && tag.getTag().equals(sharedTag)) { + matchingTag = tag; + break; + } + } + if(matchingTag != null) { + tagSelectionModel.clear(); + tagSelectionModel.setSelected(matchingTag, true); + } + else { + Notification.notify(stringMessages.tagNotFound(), NotificationType.WARNING); + } + } + + } + } }); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/tagging-panel.gss b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/tagging-panel.gss index e266addda59..9f853e968da 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/tagging-panel.gss +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/tagging/tagging-panel.gss @@ -70,6 +70,7 @@ display: inline-block; } + /**************************** Tag-Cells ****************************/ .tagCell { box-shadow: 1px 2px 3px lightgrey; @@ -380,6 +381,20 @@ margin-top: 5px; } +/**************************** SharedTag Dialog ****************************/ +.tagSharedURLLabel{ + margin-bottom: 5px; +} + +.tagSharedURLTextBox{ + width: 100%; + margin-bottom: 5px; +} + +.tagSharedURLDialog{ + +} + /**************************** Disabled tags (during editing) ****************************/ .taggingPanelDisabled .tagCell { background-color: lightgrey; @@ -389,19 +404,28 @@ .taggingPanelDisabled .tagCell.tagCellActive { background-color: #e5f2f9; } - .taggingPanelDisabled .tagCellHeadingButtons, .taggingPanelDisabled .tagCellHeadingButtons .tagEditButton, .taggingPanelDisabled .tagCellHeadingButtons .tagEditButton:hover, .taggingPanelDisabled .tagCellHeadingButtons .tagEditButton:focus, .taggingPanelDisabled .tagCellHeadingButtons .tagDeleteButton, .taggingPanelDisabled .tagCellHeadingButtons .tagDeleteButton:hover, -.taggingPanelDisabled .tagCellHeadingButtons .tagDeleteButton:focus { +.taggingPanelDisabled .tagCellHeadingButtons .tagDeleteButton:focus, +.taggingPanelDisabled .tagCellHeadingButtons .tagShareButton, +.taggingPanelDisabled .tagCellHeadingButtons .tagShareButton:hover, +.taggingPanelDisabled .tagCellHeadingButtons .tagShareButton:focus{ cursor: not-allowed; outline: none; } /**************************** Images ****************************/ +.imageShare { + gwt-sprite: "shareIcon"; + width: 2em; + height: 2em; + background-size: contain; +} + .imageEdit { gwt-sprite: "editIcon"; width: 2em; diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/share.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/share.png new file mode 100644 index 0000000000000000000000000000000000000000..4bf51459115ca9e1a4eedcaa953f7b92850ca7ee GIT binary patch literal 312 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qucLCF%=h?3y^w370~qEv>0#LT=By}Z;C1rt33 zJtM=93Yk-ZidJ~KIEGmGFTK2z_mG1?>qAkc64pM=rW-r87jUmyz?#u`Ms){^P?DCy zgMyTU|Ch*~eD=Zc_Y(b$WodgKmlmXm?kqaMT>j>3g5Q?x=I|wIlkcT;^7?H^+L^e) zu}xPm;Gm@Fmd#!J`w|YH+;~`2YfWGhg|h2HhHxy$$BX$?>J z4L1s6oc~O{Isaq)9r2gOQEzsgx~uZnXWBUd=L%*`eJ9V;;{U~gZf5Xw^>bP0l+XkK D-iUSk literal 0 HcmV?d00001