mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-11-13 22:15:26 +00:00
Bug 5085: Dialog for mark properties: add actions to set device
identifier or fixed position
This commit is contained in:
1 parent
ebc9dd0925
commit
3f2bf3a6d1
9 files changed
+210
-37
No files matched your search
+1
-1
@@ -3,7 +3,7 @@ package com.sap.sailing.gwt.ui.adminconsole;
|
||||
import com.google.gwt.resources.client.ClientBundle;
|
||||
import com.google.gwt.resources.client.ImageResource;
|
||||
|
||||
interface AdminConsoleResources extends ClientBundle {
|
||||
public interface AdminConsoleResources extends ClientBundle {
|
||||
@Source("com/sap/sailing/gwt/ui/client/images/magnifier.png")
|
||||
ImageResource magnifierIcon();
|
||||
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package com.sap.sailing.gwt.ui.adminconsole.coursecreation;
|
||||
|
||||
import com.google.gwt.user.client.ui.Grid;
|
||||
import com.google.gwt.user.client.ui.Label;
|
||||
import com.google.gwt.user.client.ui.TextBox;
|
||||
import com.google.gwt.user.client.ui.Widget;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
import com.sap.sailing.gwt.ui.shared.DeviceIdentifierDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.courseCreation.MarkPropertiesDTO;
|
||||
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
|
||||
|
||||
public class MarkPropertiesDeviceIdentifierEditDialog extends DataEntryDialog<MarkPropertiesDTO> {
|
||||
|
||||
private final MarkPropertiesDTO markPropertiesToEdit;
|
||||
private final StringMessages stringMessages;
|
||||
protected final TextBox deviceIdTextBox;
|
||||
|
||||
public MarkPropertiesDeviceIdentifierEditDialog(final StringMessages stringMessages,
|
||||
MarkPropertiesDTO markPropertiesToEdit, DialogCallback<MarkPropertiesDTO> callback) {
|
||||
super(stringMessages.edit() + " " + stringMessages.markProperties() + ": "
|
||||
+ stringMessages.setDeviceIdentifier(), null, stringMessages.ok(), stringMessages.cancel(),
|
||||
new Validator<MarkPropertiesDTO>() {
|
||||
@Override
|
||||
public String getErrorMessage(MarkPropertiesDTO valueToValidate) {
|
||||
String result = null;
|
||||
if (valueToValidate.getDeviceIdentifier() != null
|
||||
&& (valueToValidate.getDeviceIdentifier().deviceId == null
|
||||
|| valueToValidate.getDeviceIdentifier().deviceId.isEmpty())) {
|
||||
return stringMessages.pleaseEnterA(stringMessages.deviceId());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}, /* animationEnabled */true, callback);
|
||||
this.ensureDebugId("MarkPropertiesDeviceIdentifierEditDialog");
|
||||
this.stringMessages = stringMessages;
|
||||
this.markPropertiesToEdit = markPropertiesToEdit;
|
||||
this.deviceIdTextBox = createTextBox(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MarkPropertiesDTO getResult() {
|
||||
return new MarkPropertiesDTO(markPropertiesToEdit.getUuid(), markPropertiesToEdit.getName(),
|
||||
markPropertiesToEdit.getTags(), new DeviceIdentifierDTO("smartphoneUUID", deviceIdTextBox.getValue()),
|
||||
/* position */ null, markPropertiesToEdit.getCommonMarkProperties().getShortName(),
|
||||
markPropertiesToEdit.getCommonMarkProperties().getColor(),
|
||||
markPropertiesToEdit.getCommonMarkProperties().getShape(),
|
||||
markPropertiesToEdit.getCommonMarkProperties().getPattern(),
|
||||
markPropertiesToEdit.getCommonMarkProperties().getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Widget getAdditionalWidget() {
|
||||
Grid result = new Grid(1, 2);
|
||||
result.setWidget(0, 0, new Label(stringMessages.deviceId()));
|
||||
result.setWidget(0, 1, deviceIdTextBox);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+4
-3
@@ -17,7 +17,6 @@ import com.google.gwt.user.client.ui.Widget;
|
||||
import com.sap.sailing.domain.common.MarkType;
|
||||
import com.sap.sailing.domain.common.impl.RadianPosition;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
import com.sap.sailing.gwt.ui.shared.DeviceIdentifierDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.courseCreation.MarkPropertiesDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.racemap.Pattern;
|
||||
import com.sap.sailing.gwt.ui.shared.racemap.Shape;
|
||||
@@ -38,6 +37,7 @@ public class MarkPropertiesEditDialog extends DataEntryDialog<MarkPropertiesDTO>
|
||||
private final Label labelShape;
|
||||
private final Label labelPattern;
|
||||
private final StringListEditorComposite tagsEditor;
|
||||
private final MarkPropertiesDTO markPropertiesToEdit;
|
||||
|
||||
private final UUID id;
|
||||
|
||||
@@ -61,6 +61,7 @@ public class MarkPropertiesEditDialog extends DataEntryDialog<MarkPropertiesDTO>
|
||||
this.ensureDebugId("MarkPropertiesToEditEditDialog");
|
||||
id = markPropertiesToEdit.getUuid();
|
||||
this.stringMessages = stringMessages;
|
||||
this.markPropertiesToEdit = markPropertiesToEdit;
|
||||
|
||||
this.markTypeValueListBox = new ValueListBox<>(new Renderer<MarkType>() {
|
||||
@Override
|
||||
@@ -216,8 +217,8 @@ public class MarkPropertiesEditDialog extends DataEntryDialog<MarkPropertiesDTO>
|
||||
}
|
||||
// TODO: device identifier, position
|
||||
final MarkPropertiesDTO markProperties = new MarkPropertiesDTO(id, nameTextBox.getValue(),
|
||||
tagsEditor.getValue(), new DeviceIdentifierDTO(null, null), new RadianPosition(0, 0),
|
||||
shortNameTextBox.getValue(), color,
|
||||
tagsEditor.getValue(), markPropertiesToEdit.getDeviceIdentifier(),
|
||||
(RadianPosition) markPropertiesToEdit.getPosition(), shortNameTextBox.getValue(), color,
|
||||
shapeValueListBox.getValue() == null ? "" : shapeValueListBox.getValue().name(),
|
||||
patternValueListBox.getValue() == null ? "" : patternValueListBox.getValue().name(),
|
||||
markTypeValueListBox.getValue());
|
||||
|
||||
+69
-29
@@ -9,6 +9,7 @@ import static com.sap.sse.security.ui.client.component.DefaultActionsImagesBarCe
|
||||
import static com.sap.sse.security.ui.client.component.DefaultActionsImagesBarCell.ACTION_UPDATE;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
@@ -32,6 +33,7 @@ import com.google.gwt.view.client.DefaultSelectionEventManager;
|
||||
import com.google.gwt.view.client.DefaultSelectionEventManager.SelectAction;
|
||||
import com.google.gwt.view.client.ListDataProvider;
|
||||
import com.sap.sailing.domain.common.security.SecuredDomainType;
|
||||
import com.sap.sailing.gwt.ui.adminconsole.AdminConsoleResources;
|
||||
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
import com.sap.sailing.gwt.ui.shared.courseCreation.MarkPropertiesDTO;
|
||||
@@ -297,8 +299,8 @@ public class MarkPropertiesPanel extends FlowPanel {
|
||||
|
||||
final HasPermissions type = SecuredDomainType.MARK_PROPERTIES;
|
||||
|
||||
final AccessControlledActionsColumn<MarkPropertiesDTO, DefaultActionsImagesBarCell> actionsColumn = create(
|
||||
new DefaultActionsImagesBarCell(stringMessages), userService);
|
||||
final AccessControlledActionsColumn<MarkPropertiesDTO, MarkPropertiesImagesbarCell> actionsColumn = create(
|
||||
new MarkPropertiesImagesbarCell(stringMessages), userService);
|
||||
final EditOwnershipDialog.DialogConfig<MarkPropertiesDTO> configOwnership = EditOwnershipDialog
|
||||
.create(userService.getUserManagementService(), type, markProperties -> {
|
||||
/* no refresh action */}, stringMessages);
|
||||
@@ -322,6 +324,10 @@ public class MarkPropertiesPanel extends FlowPanel {
|
||||
}
|
||||
});
|
||||
actionsColumn.addAction(ACTION_UPDATE, UPDATE, this::openEditMarkPropertiesDialog);
|
||||
actionsColumn.addAction(MarkPropertiesImagesbarCell.ACTION_SET_DEVICE_IDENTIFIER,
|
||||
this::openEditMarkPropertiesDeviceIdentifierDialog);
|
||||
actionsColumn.addAction(MarkPropertiesImagesbarCell.ACTION_SET_POSITION,
|
||||
this::openEditMarkPropertiesPositionDialog);
|
||||
actionsColumn.addAction(ACTION_CHANGE_OWNERSHIP, CHANGE_OWNERSHIP, configOwnership::openDialog);
|
||||
actionsColumn.addAction(DefaultActionsImagesBarCell.ACTION_CHANGE_ACL, DefaultActions.CHANGE_ACL,
|
||||
markProperties -> configACL.openDialog(markProperties));
|
||||
@@ -334,41 +340,75 @@ public class MarkPropertiesPanel extends FlowPanel {
|
||||
loadMarkProperties();
|
||||
}
|
||||
|
||||
void openEditMarkPropertiesDialog(final MarkPropertiesDTO originalMarkProperties) {
|
||||
final MarkPropertiesEditDialog dialog = new MarkPropertiesEditDialog(stringMessages, originalMarkProperties,
|
||||
new DialogCallback<MarkPropertiesDTO>() {
|
||||
DialogCallback<MarkPropertiesDTO> createEditDialogCallback(final MarkPropertiesDTO originalMarkProperties) {
|
||||
return new DialogCallback<MarkPropertiesDTO>() {
|
||||
@Override
|
||||
public void ok(MarkPropertiesDTO markProperties) {
|
||||
sailingService.addOrUpdateMarkProperties(markProperties, new AsyncCallback<MarkPropertiesDTO>() {
|
||||
@Override
|
||||
public void ok(MarkPropertiesDTO markProperties) {
|
||||
sailingService.addOrUpdateMarkProperties(markProperties,
|
||||
new AsyncCallback<MarkPropertiesDTO>() {
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
errorReporter.reportError(
|
||||
"Error trying to update mark properties: " + caught.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(MarkPropertiesDTO updatedMarkProperties) {
|
||||
int editedMarkPropertiesIndex = filterableMarkProperties
|
||||
.indexOf(originalMarkProperties);
|
||||
filterableMarkProperties.remove(originalMarkProperties);
|
||||
if (editedMarkPropertiesIndex >= 0) {
|
||||
filterableMarkProperties.add(editedMarkPropertiesIndex,
|
||||
updatedMarkProperties);
|
||||
} else {
|
||||
filterableMarkProperties.add(updatedMarkProperties);
|
||||
}
|
||||
markPropertiesListDataProvider.refresh();
|
||||
}
|
||||
});
|
||||
public void onFailure(Throwable caught) {
|
||||
errorReporter.reportError("Error trying to update mark properties: " + caught.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
public void onSuccess(MarkPropertiesDTO updatedMarkProperties) {
|
||||
int editedMarkPropertiesIndex = filterableMarkProperties.indexOf(originalMarkProperties);
|
||||
filterableMarkProperties.remove(originalMarkProperties);
|
||||
if (editedMarkPropertiesIndex >= 0) {
|
||||
filterableMarkProperties.add(editedMarkPropertiesIndex, updatedMarkProperties);
|
||||
} else {
|
||||
filterableMarkProperties.add(updatedMarkProperties);
|
||||
}
|
||||
markPropertiesListDataProvider.refresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void openEditMarkPropertiesDialog(final MarkPropertiesDTO originalMarkProperties) {
|
||||
final MarkPropertiesEditDialog dialog = new MarkPropertiesEditDialog(stringMessages, originalMarkProperties,
|
||||
createEditDialogCallback(originalMarkProperties));
|
||||
dialog.ensureDebugId("MarkPropertiesEditDialog");
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
void openEditMarkPropertiesDeviceIdentifierDialog(final MarkPropertiesDTO originalMarkProperties) {
|
||||
final MarkPropertiesDeviceIdentifierEditDialog dialog = new MarkPropertiesDeviceIdentifierEditDialog(
|
||||
stringMessages, originalMarkProperties, createEditDialogCallback(originalMarkProperties));
|
||||
dialog.ensureDebugId("MarkPropertiesDeviceIdentifierEditDialog");
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
void openEditMarkPropertiesPositionDialog(final MarkPropertiesDTO originalMarkProperties) {
|
||||
final MarkPropertiesPositionEditDialog dialog = new MarkPropertiesPositionEditDialog(stringMessages,
|
||||
originalMarkProperties, createEditDialogCallback(originalMarkProperties));
|
||||
dialog.ensureDebugId("MarkPropertiesPositionEditDialog");
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
private static class MarkPropertiesImagesbarCell extends DefaultActionsImagesBarCell {
|
||||
private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class);
|
||||
public static final String ACTION_SET_DEVICE_IDENTIFIER = "ACTION_SET_DEVICE_IDENTIFIER";
|
||||
public static final String ACTION_SET_POSITION = "ACTION_SET_POSITION";
|
||||
private final StringMessages stringMessages;
|
||||
|
||||
public MarkPropertiesImagesbarCell(StringMessages stringMessages) {
|
||||
super(stringMessages);
|
||||
this.stringMessages = stringMessages;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Iterable<ImageSpec> getImageSpecs() {
|
||||
return Arrays.asList(getUpdateImageSpec(),
|
||||
new ImageSpec(ACTION_SET_DEVICE_IDENTIFIER, stringMessages.setDeviceIdentifier(),
|
||||
resources.mapDevices()),
|
||||
new ImageSpec(ACTION_SET_POSITION, stringMessages.setPosition(), resources.ping()),
|
||||
getDeleteImageSpec(), getChangeOwnershipImageSpec(), getChangeACLImageSpec());
|
||||
}
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package com.sap.sailing.gwt.ui.adminconsole.coursecreation;
|
||||
|
||||
import com.google.gwt.user.client.ui.Grid;
|
||||
import com.google.gwt.user.client.ui.Label;
|
||||
import com.google.gwt.user.client.ui.Widget;
|
||||
import com.sap.sailing.domain.common.impl.RadianPosition;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
import com.sap.sailing.gwt.ui.shared.courseCreation.MarkPropertiesDTO;
|
||||
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
|
||||
import com.sap.sse.gwt.client.dialog.DoubleBox;
|
||||
|
||||
public class MarkPropertiesPositionEditDialog extends DataEntryDialog<MarkPropertiesDTO> {
|
||||
|
||||
private final MarkPropertiesDTO markPropertiesToEdit;
|
||||
private final DoubleBox latDoubleBox;
|
||||
private final DoubleBox lngDoubleBox;
|
||||
private final StringMessages stringMessages;
|
||||
|
||||
private static final double ERROR_VAL = Double.MIN_VALUE;
|
||||
|
||||
public MarkPropertiesPositionEditDialog(final StringMessages stringMessages, MarkPropertiesDTO markPropertiesToEdit,
|
||||
DialogCallback<MarkPropertiesDTO> callback) {
|
||||
super(stringMessages.edit() + " " + stringMessages.markProperties() + ": " + stringMessages.setPosition(), null,
|
||||
stringMessages.ok(), stringMessages.cancel(), new Validator<MarkPropertiesDTO>() {
|
||||
@Override
|
||||
public String getErrorMessage(MarkPropertiesDTO valueToValidate) {
|
||||
if (valueToValidate.getPosition().getLatRad() == ERROR_VAL) {
|
||||
return stringMessages.pleaseEnterA(stringMessages.latitude());
|
||||
}
|
||||
if (valueToValidate.getPosition().getLngRad() == ERROR_VAL) {
|
||||
return stringMessages.pleaseEnterA(stringMessages.longitude());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}, /* animationEnabled */true, callback);
|
||||
this.ensureDebugId("MarkPropertiesPositionEditDialog");
|
||||
this.stringMessages = stringMessages;
|
||||
this.markPropertiesToEdit = markPropertiesToEdit;
|
||||
this.latDoubleBox = createDoubleBox(10);
|
||||
this.lngDoubleBox = createDoubleBox(10);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MarkPropertiesDTO getResult() {
|
||||
return new MarkPropertiesDTO(markPropertiesToEdit.getUuid(), markPropertiesToEdit.getName(),
|
||||
markPropertiesToEdit.getTags(), /* deviceIdentifier */ null,
|
||||
new RadianPosition(latDoubleBox.getValue() != null ? latDoubleBox.getValue() : ERROR_VAL,
|
||||
lngDoubleBox.getValue() != null ? lngDoubleBox.getValue() : ERROR_VAL),
|
||||
markPropertiesToEdit.getCommonMarkProperties().getShortName(),
|
||||
markPropertiesToEdit.getCommonMarkProperties().getColor(),
|
||||
markPropertiesToEdit.getCommonMarkProperties().getShape(),
|
||||
markPropertiesToEdit.getCommonMarkProperties().getPattern(),
|
||||
markPropertiesToEdit.getCommonMarkProperties().getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Widget getAdditionalWidget() {
|
||||
Grid grid = new Grid(2, 2);
|
||||
grid.setWidget(0, 0, new Label(stringMessages.latitude() + " (" + stringMessages.angleInRadian() + ")"));
|
||||
grid.setWidget(0, 1, latDoubleBox);
|
||||
grid.setWidget(1, 0, new Label(stringMessages.longitude() + " (" + stringMessages.angleInRadian() + ")"));
|
||||
grid.setWidget(1, 1, lngDoubleBox);
|
||||
return grid;
|
||||
}
|
||||
}
|
||||
+2
@@ -2354,4 +2354,6 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
|
||||
String defaultNumberOfLaps();
|
||||
String markRoleUsedTwice();
|
||||
String invalidImageURL();
|
||||
String setDeviceIdentifier();
|
||||
String setPosition();
|
||||
}
|
||||
+2
@@ -2357,3 +2357,5 @@ wayPointMarkInSequenceMissing=Mark used in Sequence does not exist on Course Tem
|
||||
defaultNumberOfLaps=Default # of laps
|
||||
markRoleUsedTwice=One mark role is associated more than once
|
||||
invalidImageURL=Image URL is invalid
|
||||
setDeviceIdentifier=Set device identifier
|
||||
setPosition=Set position
|
||||
+2
@@ -2372,3 +2372,5 @@ wayPointMarkInSequenceMissing=In Wegpunkt verwendete Bahnmarke existiert nicht i
|
||||
defaultNumberOfLaps=Standard-Rundenanzahl
|
||||
markRoleUsedTwice=Eine Bahnmarkenfunktionen kann nur einmal zugeordnet werden
|
||||
invalidImageURL=Ungültige Bild-URL
|
||||
setDeviceIdentifier=Geräteidentifikator setzen
|
||||
setPosition=Position setzen
|
||||
+7
-4
@@ -375,10 +375,10 @@ import com.sap.sailing.domain.racelog.tracking.SensorFixStore;
|
||||
import com.sap.sailing.domain.racelogtracking.DeviceIdentifierStringSerializationHandler;
|
||||
import com.sap.sailing.domain.racelogtracking.DeviceMapping;
|
||||
import com.sap.sailing.domain.racelogtracking.DeviceMappingWithRegattaLogEvent;
|
||||
import com.sap.sailing.domain.racelogtracking.PlaceHolderDeviceIdentifier;
|
||||
import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapter;
|
||||
import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapterFactory;
|
||||
import com.sap.sailing.domain.racelogtracking.impl.DeviceMappingImpl;
|
||||
import com.sap.sailing.domain.racelogtracking.impl.SmartphoneUUIDIdentifierImpl;
|
||||
import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo;
|
||||
import com.sap.sailing.domain.regattalike.HasRegattaLike;
|
||||
import com.sap.sailing.domain.regattalike.IsRegattaLike;
|
||||
@@ -9915,7 +9915,7 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet
|
||||
}
|
||||
|
||||
private DeviceIdentifier convertDtoToDeviceIdentifier(DeviceIdentifierDTO deviceIdentifier) {
|
||||
return new PlaceHolderDeviceIdentifier(deviceIdentifier.deviceType, deviceIdentifier.deviceId);
|
||||
return new SmartphoneUUIDIdentifierImpl(UUID.fromString(deviceIdentifier.deviceId));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -9925,7 +9925,10 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet
|
||||
getSecurityService().checkCurrentUserUpdatePermission(createdOrUpdatedMarkProperties);
|
||||
getSharedSailingData().updateMarkProperties(markProperties.getUuid(),
|
||||
convertDtoToCommonMarkProperties(markProperties.getCommonMarkProperties()),
|
||||
markProperties.getPosition(), convertDtoToDeviceIdentifier(markProperties.getDeviceIdentifier()),
|
||||
markProperties.getPosition(),
|
||||
markProperties.getDeviceIdentifier() != null
|
||||
? convertDtoToDeviceIdentifier(markProperties.getDeviceIdentifier())
|
||||
: null,
|
||||
markProperties.getTags());
|
||||
createdOrUpdatedMarkProperties = getSharedSailingData().getMarkPropertiesById(createdOrUpdatedMarkProperties.getId());
|
||||
} else {
|
||||
@@ -10079,7 +10082,7 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet
|
||||
}
|
||||
|
||||
private MarkRoleDTO convertToMarkRoleDTO(final MarkRole markRole) {
|
||||
final MarkRoleDTO markRoleDTO = new MarkRoleDTO(markRole.getId(), markRole.getName());
|
||||
final MarkRoleDTO markRoleDTO = new MarkRoleDTO(markRole.getId(), markRole.getName());
|
||||
SecurityDTOUtil.addSecurityInformation(getSecurityService(), markRoleDTO, markRole.getIdentifier());
|
||||
return markRoleDTO;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user