mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
new project wizard concept
This commit is contained in:
@@ -21,6 +21,7 @@ package com.intellij.ide.impl;
|
||||
|
||||
import com.intellij.ide.GeneralSettings;
|
||||
import com.intellij.ide.util.newProjectWizard.AddModuleWizard;
|
||||
import com.intellij.ide.util.newProjectWizard.AddModuleWizardPro;
|
||||
import com.intellij.ide.util.projectWizard.ProjectBuilder;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.ModalityState;
|
||||
@@ -44,6 +45,7 @@ import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.util.SystemInfo;
|
||||
import com.intellij.openapi.util.ThrowableComputable;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.openapi.vfs.VfsUtil;
|
||||
import com.intellij.openapi.wm.*;
|
||||
import com.intellij.openapi.wm.ex.WindowManagerEx;
|
||||
@@ -68,7 +70,9 @@ public class NewProjectUtil {
|
||||
}
|
||||
}, ProjectBundle.message("project.new.wizard.progress.title"), true, null);
|
||||
if (!proceed) return;
|
||||
final AddModuleWizard dialog = new AddModuleWizard(null, ModulesProvider.EMPTY_MODULES_PROVIDER, defaultPath);
|
||||
final AddModuleWizard dialog = Registry.is("new.project.wizard")
|
||||
? new AddModuleWizardPro(null, ModulesProvider.EMPTY_MODULES_PROVIDER, defaultPath)
|
||||
: new AddModuleWizard(null, ModulesProvider.EMPTY_MODULES_PROVIDER, defaultPath);
|
||||
dialog.show();
|
||||
if (!dialog.isOK()) {
|
||||
return;
|
||||
|
||||
@@ -56,7 +56,7 @@ public class AddModuleWizard extends AbstractWizard<ModuleWizardStep> {
|
||||
private final Project myCurrentProject;
|
||||
private final ModulesProvider myModulesProvider;
|
||||
private WizardContext myWizardContext;
|
||||
private ProjectCreateModeStep myRootStep;
|
||||
protected ProjectCreateModeStep myRootStep;
|
||||
|
||||
|
||||
/**
|
||||
@@ -166,7 +166,7 @@ public class AddModuleWizard extends AbstractWizard<ModuleWizardStep> {
|
||||
super.doOKAction();
|
||||
}
|
||||
|
||||
private boolean commitStepData(final ModuleWizardStep step) {
|
||||
protected boolean commitStepData(final ModuleWizardStep step) {
|
||||
try {
|
||||
if (!step.validate()) {
|
||||
return false;
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
package com.intellij.ide.util.newProjectWizard;
|
||||
|
||||
import com.intellij.ide.util.projectWizard.ModuleWizardStep;
|
||||
import com.intellij.ide.util.projectWizard.WizardContext;
|
||||
import com.intellij.ide.wizard.CommitStepException;
|
||||
import com.intellij.ide.wizard.Step;
|
||||
import com.intellij.openapi.actionSystem.AnAction;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.actionSystem.CustomShortcutSet;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.roots.ui.configuration.ModulesProvider;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.ui.ValidationInfo;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.Enumeration;
|
||||
|
||||
/**
|
||||
* @author Konstantin Bulenkov
|
||||
*/
|
||||
public class AddModuleWizardPro extends AddModuleWizard {
|
||||
protected JPanel myStepsPanel;
|
||||
|
||||
public AddModuleWizardPro(Project project, @NotNull ModulesProvider modulesProvider, @Nullable String defaultPath) {
|
||||
super(project, modulesProvider, defaultPath);
|
||||
getWizardContext().addContextListener(new WizardContext.Listener() {
|
||||
@Override
|
||||
public void buttonsUpdateRequested() {
|
||||
updateStepsPanel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nextStepRequested() {
|
||||
updateStepsPanel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateButtons() {
|
||||
super.updateButtons();
|
||||
if (isLastStep()) {
|
||||
getNextButton().setText("Done");
|
||||
}
|
||||
|
||||
getPreviousButton().setVisible(false);
|
||||
getCancelButton().setVisible(false);
|
||||
}
|
||||
|
||||
public AddModuleWizardPro(Component parent, Project project, @NotNull ModulesProvider modulesProvider) {
|
||||
super(parent, project, modulesProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createDefaultActions() {
|
||||
super.createDefaultActions();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JComponent createCenterPanel() {
|
||||
myStepsPanel = new JPanel() {
|
||||
@Override
|
||||
public void addNotify() {
|
||||
super.addNotify();
|
||||
getNextStepObject().updateDataModel();
|
||||
updateStepsPanel();
|
||||
new AnAction(){
|
||||
@Override
|
||||
public void actionPerformed(AnActionEvent e) {
|
||||
if (!isLastStep()) doNextAction();
|
||||
}
|
||||
}.registerCustomShortcutSet(CustomShortcutSet.fromString("control TAB"), getContentPanel(), getDisposable());
|
||||
new AnAction(){
|
||||
@Override
|
||||
public void actionPerformed(AnActionEvent e) {
|
||||
doPreviousAction();
|
||||
}
|
||||
}.registerCustomShortcutSet(CustomShortcutSet.fromString("control shift TAB"), getContentPanel(), getDisposable());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintComponent(Graphics g) {
|
||||
super.paintComponent(g);
|
||||
final Component c = getComponent(getComponentCount() - 1);
|
||||
int y = c.getBounds().y + c.getBounds().height + 4;
|
||||
g.translate(0, y);
|
||||
myIcon.paintIcon(g);
|
||||
g.translate(0, -y);
|
||||
}
|
||||
};
|
||||
myStepsPanel.setLayout(new BoxLayout(myStepsPanel, BoxLayout.Y_AXIS) {
|
||||
@Override
|
||||
public void layoutContainer(Container target) {
|
||||
super.layoutContainer(target);
|
||||
int maxWidth = -1;
|
||||
for (Component c : target.getComponents()) {
|
||||
maxWidth = Math.max(maxWidth, c.getWidth());
|
||||
}
|
||||
for (Component c : target.getComponents()) {
|
||||
final Rectangle b = c.getBounds();
|
||||
c.setBounds(b.x, b.y, maxWidth, b.height);
|
||||
}
|
||||
}
|
||||
});
|
||||
myStepsPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 4));
|
||||
updateStepsPanel();
|
||||
|
||||
final JPanel panel = new JPanel(new BorderLayout());
|
||||
panel.add(myStepsPanel, BorderLayout.WEST);
|
||||
panel.add(myContentPanel, BorderLayout.CENTER);
|
||||
return panel;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doNextAction() {
|
||||
super.doNextAction();
|
||||
updateStepsPanel();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPreviousAction() {
|
||||
super.doPreviousAction();
|
||||
updateStepsPanel();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected ValidationInfo doValidate() {
|
||||
final ValidationInfo result = super.doValidate();
|
||||
updateStepsPanel();
|
||||
return result;
|
||||
}
|
||||
|
||||
private void updateStepsPanel() {
|
||||
myStepsPanel.removeAll();
|
||||
int index = 0;
|
||||
final ButtonGroup group = new ButtonGroup();
|
||||
while (index != -1) {
|
||||
final int ind = index;
|
||||
final ModuleWizardStep step = mySteps.get(index);
|
||||
final JRadioButton rb = new JRadioButton(step.getName(), index == myCurrentStep);
|
||||
rb.setUI(new WizardArrowUI(rb, index < myCurrentStep));
|
||||
myStepsPanel.add(rb);
|
||||
group.add(rb);
|
||||
rb.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
final ModuleWizardStep step = getCurrentStepObject();
|
||||
if (ind > getCurrentStep() && !commitStepData(step)) {
|
||||
updateStepsPanel();
|
||||
return;
|
||||
}
|
||||
step.onStepLeaving();
|
||||
|
||||
// Commit data of current step
|
||||
final Step currentStep = mySteps.get(myCurrentStep);
|
||||
try {
|
||||
currentStep._commit(false);
|
||||
}
|
||||
catch (final CommitStepException exc) {
|
||||
Messages.showErrorDialog(
|
||||
myContentPanel,
|
||||
exc.getMessage()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
myCurrentStep = ind;
|
||||
updateStep();
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
final int next = getNextStep(index);
|
||||
index = index == next ? -1 : next;
|
||||
}
|
||||
|
||||
final Enumeration<AbstractButton> buttons = group.getElements();
|
||||
while (buttons.hasMoreElements()) {
|
||||
final JRadioButton b = (JRadioButton)buttons.nextElement();
|
||||
b.setUI(new WizardArrowUI(b, index < myCurrentStep));
|
||||
}
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
myStepsPanel.revalidate();
|
||||
myStepsPanel.repaint();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ import com.intellij.ide.util.projectWizard.WizardContext;
|
||||
import com.intellij.openapi.extensions.Extensions;
|
||||
import com.intellij.openapi.options.ConfigurationException;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.ui.DoubleClickListener;
|
||||
import com.intellij.ui.ClickListener;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -93,11 +93,15 @@ public class ProjectCreateModeStep extends ModuleWizardStep {
|
||||
setMode(mode);
|
||||
}
|
||||
});
|
||||
new DoubleClickListener() {
|
||||
new ClickListener() {
|
||||
@Override
|
||||
protected boolean onDoubleClick(MouseEvent e) {
|
||||
wizardContext.requestNextStep();
|
||||
return true;
|
||||
public boolean onClick(MouseEvent event, int clickCount) {
|
||||
if (mode.getAdditionalSettings(wizardContext) == null) {
|
||||
wizardContext.requestNextStep();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}.installOn(rb);
|
||||
|
||||
@@ -139,6 +143,11 @@ public class ProjectCreateModeStep extends ModuleWizardStep {
|
||||
return myWizardContext.getStepIcon();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return myMode.getShortName();
|
||||
}
|
||||
|
||||
public WizardMode getMode() {
|
||||
return myMode;
|
||||
}
|
||||
|
||||
@@ -467,4 +467,9 @@ public class ProjectNameWithTypeStep extends ProjectNameStep {
|
||||
myInternalPanel.add(component, new GridBagConstraints(0, 2, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(10, 0, 0, 0), 0, 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Name and Type";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,6 +359,11 @@ public class SelectTemplateStep extends ModuleWizardStep {
|
||||
Disposer.dispose(myBuilder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Template Type";
|
||||
}
|
||||
|
||||
private void createUIComponents() {
|
||||
mySearchField = new SearchTextField(false);
|
||||
}
|
||||
|
||||
@@ -400,4 +400,9 @@ public class SourcePathsStep extends AbstractStepWithProgress<List<JavaModuleSou
|
||||
public String getHelpId() {
|
||||
return myHelpId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Path to Sources";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,11 @@ public class SupportForFrameworksStep extends ModuleWizardStep {
|
||||
public void updateDataModel() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Add Frameworks";
|
||||
}
|
||||
|
||||
private static class FrameworkSupportModelInWizard extends FrameworkSupportModelBase {
|
||||
private final ModuleBuilder myBuilder;
|
||||
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2000-2012 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.ide.util.newProjectWizard;
|
||||
|
||||
import com.intellij.openapi.ui.GraphicsConfig;
|
||||
import com.intellij.ui.Gray;
|
||||
import com.intellij.util.ui.GraphicsUtil;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import sun.swing.SwingUtilities2;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import javax.swing.plaf.ComponentUI;
|
||||
import javax.swing.plaf.basic.BasicButtonUI;
|
||||
import java.awt.*;
|
||||
import java.awt.geom.GeneralPath;
|
||||
import java.awt.geom.Path2D;
|
||||
|
||||
/**
|
||||
* @author Konstantin Bulenkov
|
||||
*/
|
||||
class WizardArrowUI extends BasicButtonUI {
|
||||
private final AbstractButton myButton;
|
||||
private static Rectangle viewRect = new Rectangle();
|
||||
private static Rectangle textRect = new Rectangle();
|
||||
private static Rectangle iconRect = new Rectangle();
|
||||
|
||||
|
||||
public WizardArrowUI(AbstractButton b, boolean valid) {
|
||||
myButton = b;
|
||||
//b.setIcon(EmptyIcon.create(1));
|
||||
b.setOpaque(false);
|
||||
b.setBorder(new EmptyBorder(8, 0, 8, 40));
|
||||
}
|
||||
|
||||
@SuppressWarnings({"MethodOverridesStaticMethodOfSuperclass", "UnusedDeclaration"})
|
||||
public static ComponentUI createUI(JComponent c) {
|
||||
return new WizardArrowUI((AbstractButton)c, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintIcon(Graphics g, JComponent c, Rectangle iconRect) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getTextShiftOffset() {
|
||||
return 5;
|
||||
}
|
||||
|
||||
private String layout(AbstractButton b, FontMetrics fm,
|
||||
int width, int height) {
|
||||
Insets i = b.getInsets();
|
||||
viewRect.x = i.left;
|
||||
viewRect.y = i.top;
|
||||
viewRect.width = width - (i.right + viewRect.x);
|
||||
viewRect.height = height - (i.bottom + viewRect.y);
|
||||
|
||||
textRect.x = textRect.y = textRect.width = textRect.height = 0;
|
||||
iconRect.x = iconRect.y = iconRect.width = iconRect.height = 0;
|
||||
|
||||
// layout the text and icon
|
||||
return SwingUtilities.layoutCompoundLabel(
|
||||
b, fm, b.getText(), b.getIcon(),
|
||||
b.getVerticalAlignment(), b.getHorizontalAlignment(),
|
||||
b.getVerticalTextPosition(), b.getHorizontalTextPosition(),
|
||||
viewRect, iconRect, textRect,
|
||||
b.getText() == null ? 0 : b.getIconTextGap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paint(Graphics g, JComponent c) {
|
||||
int w = c.getWidth();
|
||||
int h = c.getHeight();
|
||||
layout(myButton, SwingUtilities2.getFontMetrics(c, g), w, h);
|
||||
|
||||
final GraphicsConfig config = GraphicsUtil.setupAAPainting(g);
|
||||
h-=4;
|
||||
if (!myButton.isSelected()) {
|
||||
w-=15;
|
||||
}
|
||||
final Path2D.Double path = new GeneralPath.Double();
|
||||
path.moveTo(0, 0);
|
||||
path.lineTo(w - h / 2, 0);
|
||||
path.lineTo(w, h / 2);
|
||||
path.lineTo(w-h/2, h);
|
||||
path.lineTo(0, h);
|
||||
path.lineTo(0, 0);
|
||||
g.setColor(myButton.isSelected() ? UIUtil.getListSelectionBackground() : Gray._255.withAlpha(200));
|
||||
((Graphics2D)g).fill(path);
|
||||
g.setColor(Gray._0.withAlpha(50));
|
||||
((Graphics2D)g).draw(path);
|
||||
config.restore();
|
||||
textRect.x = 2;
|
||||
textRect.y-=7;
|
||||
c.setForeground(UIUtil.getListForeground(myButton.isSelected()));
|
||||
paintText(g, c, textRect, myButton.getText());
|
||||
}
|
||||
}
|
||||
+5
@@ -96,6 +96,11 @@ public class CreateFromScratchMode extends WizardMode {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getShortName() {
|
||||
return "Create from Scratch";
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
myBuildersMap.clear();
|
||||
|
||||
+5
@@ -96,6 +96,11 @@ public abstract class CreateFromSourcesMode extends WizardMode {
|
||||
public void onChosen(final boolean enabled) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getShortName() {
|
||||
return "Create from Sources";
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
myProjectBuilder = null;
|
||||
super.dispose();
|
||||
|
||||
+5
@@ -69,6 +69,11 @@ public class CreateFromTemplateMode extends WizardMode {
|
||||
return template.createModuleBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getShortName() {
|
||||
return "Create from Template";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChosen(boolean enabled) {
|
||||
}
|
||||
|
||||
@@ -121,6 +121,11 @@ public class ImportImlMode extends WizardMode {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getShortName() {
|
||||
return "Import from *.iml";
|
||||
}
|
||||
|
||||
private static class ModuleFileChooserDescriptor extends FileChooserDescriptor {
|
||||
public ModuleFileChooserDescriptor() {
|
||||
super(true, false, false, false, false, false);
|
||||
|
||||
@@ -94,4 +94,8 @@ public class ImportMode extends WizardMode {
|
||||
|
||||
public void onChosen(final boolean enabled) {}
|
||||
|
||||
@Override
|
||||
public String getShortName() {
|
||||
return "Import from External Model";
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,10 @@ public abstract class WizardMode implements Disposable {
|
||||
myStepSequence = null;
|
||||
}
|
||||
|
||||
public String getShortName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getFootnote(final WizardContext wizardContext) {
|
||||
return null;
|
||||
|
||||
@@ -99,6 +99,11 @@ public class ProjectJdkStep extends ModuleWizardStep {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Project JDK";
|
||||
}
|
||||
|
||||
public void disposeUIResources() {
|
||||
super.disposeUIResources();
|
||||
myProjectJdksConfigurable.disposeUIResources();
|
||||
|
||||
@@ -150,6 +150,11 @@ public class ProjectNameStep extends ModuleWizardStep {
|
||||
return myNamePathComponent.getNameValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Name";
|
||||
}
|
||||
|
||||
public boolean isStepVisible() {
|
||||
final ProjectBuilder builder = myWizardContext.getProjectBuilder();
|
||||
if (builder != null && builder.isUpdate()) return false;
|
||||
|
||||
@@ -90,6 +90,11 @@ public class ProjectWizardStepFactoryImpl extends ProjectWizardStepFactory {
|
||||
return icon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Specify JDK";
|
||||
}
|
||||
|
||||
public String getHelpId() {
|
||||
return helpId;
|
||||
}
|
||||
|
||||
@@ -127,6 +127,11 @@ public class ImportChooserStep extends ProjectImportWizardStep {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Choose External Model";
|
||||
}
|
||||
|
||||
@NonNls
|
||||
public String getHelpId() {
|
||||
return "reference.dialogs.new.project.import";
|
||||
|
||||
@@ -85,8 +85,9 @@ public class JavaModuleType extends ModuleType<JavaModuleBuilder> {
|
||||
steps.add(wizardFactory.createProjectJdkStep(wizardContext, JavaSdk.getInstance(), moduleBuilder, new Computable<Boolean>() {
|
||||
@Override
|
||||
public Boolean compute() {
|
||||
final Sdk projectJdk = wizardFactory.getNewProjectSdk(wizardContext);
|
||||
return projectJdk == null || projectJdk.getSdkType() != JavaSdk.getInstance() ? Boolean.TRUE : Boolean.FALSE;
|
||||
//final Sdk projectJdk = wizardFactory.getNewProjectSdk(wizardContext);
|
||||
//return projectJdk == null || projectJdk.getSdkType() != JavaSdk.getInstance() ? Boolean.TRUE : Boolean.FALSE;
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
}, getWizardIcon(), "reference.dialogs.new.project.fromScratch.sdk"));
|
||||
final ModuleWizardStep supportForFrameworksStep = wizardFactory.createSupportForFrameworksStep(wizardContext, moduleBuilder, modulesProvider);
|
||||
|
||||
Reference in New Issue
Block a user