mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 11:18:01 +00:00
Support saving deck, prompting to save if changes made, and refreshing decks list when returning to previous screen
This commit is contained in:
@@ -273,6 +273,7 @@ public class CEditorWinstonProcess extends ACEditorBase<PaperCard, DeckGroup> {
|
||||
* @see forge.gui.deckeditor.ACEditorBase#show(forge.Command)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("serial")
|
||||
public void update() {
|
||||
this.getCatalogManager().setup(ItemManagerConfig.DRAFT_PACK);
|
||||
this.getDeckManager().setup(ItemManagerConfig.DRAFT_POOL);
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
package forge.deck;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
|
||||
import forge.assets.FImage;
|
||||
import forge.assets.FSkin;
|
||||
import forge.assets.FSkinImage;
|
||||
import forge.assets.FTextureRegionImage;
|
||||
import forge.deck.io.DeckPreferences;
|
||||
import forge.item.PaperCard;
|
||||
import forge.itemmanager.CardManager;
|
||||
import forge.itemmanager.ItemManagerConfig;
|
||||
@@ -13,19 +18,73 @@ import forge.screens.TabPageScreen;
|
||||
import forge.toolbox.FEvent;
|
||||
import forge.toolbox.FEvent.FEventHandler;
|
||||
import forge.toolbox.FLabel;
|
||||
import forge.toolbox.FOptionPane;
|
||||
import forge.toolbox.FTextField;
|
||||
import forge.util.Callback;
|
||||
import forge.util.ItemPool;
|
||||
import forge.util.Utils;
|
||||
import forge.util.storage.IStorage;
|
||||
|
||||
public class FDeckEditor extends TabPageScreen<FDeckEditor> {
|
||||
public enum EditorType {
|
||||
Constructed,
|
||||
Draft,
|
||||
Sealed,
|
||||
Commander,
|
||||
Archenemy,
|
||||
Planechase,
|
||||
Vanguard,
|
||||
Constructed(new DeckController<Deck>(FModel.getDecks().getConstructed(), new Supplier<Deck>() {
|
||||
@Override
|
||||
public Deck get() {
|
||||
return new Deck();
|
||||
}
|
||||
})),
|
||||
Draft(new DeckController<DeckGroup>(FModel.getDecks().getDraft(), new Supplier<DeckGroup>() {
|
||||
@Override
|
||||
public DeckGroup get() {
|
||||
return new DeckGroup("");
|
||||
}
|
||||
})),
|
||||
Sealed(new DeckController<DeckGroup>(FModel.getDecks().getSealed(), new Supplier<DeckGroup>() {
|
||||
@Override
|
||||
public DeckGroup get() {
|
||||
return new DeckGroup("");
|
||||
}
|
||||
})),
|
||||
Winston(new DeckController<DeckGroup>(FModel.getDecks().getWinston(), new Supplier<DeckGroup>() {
|
||||
@Override
|
||||
public DeckGroup get() {
|
||||
return new DeckGroup("");
|
||||
}
|
||||
})),
|
||||
Commander(new DeckController<Deck>(FModel.getDecks().getCommander(), new Supplier<Deck>() {
|
||||
@Override
|
||||
public Deck get() {
|
||||
return new Deck();
|
||||
}
|
||||
})),
|
||||
Archenemy(new DeckController<Deck>(FModel.getDecks().getScheme(), new Supplier<Deck>() {
|
||||
@Override
|
||||
public Deck get() {
|
||||
return new Deck();
|
||||
}
|
||||
})),
|
||||
Planechase(new DeckController<Deck>(FModel.getDecks().getPlane(), new Supplier<Deck>() {
|
||||
@Override
|
||||
public Deck get() {
|
||||
return new Deck();
|
||||
}
|
||||
})),
|
||||
Vanguard(new DeckController<Deck>(FModel.getDecks().getConstructed(), new Supplier<Deck>() {
|
||||
@Override
|
||||
public Deck get() {
|
||||
return new Deck();
|
||||
}
|
||||
}));
|
||||
|
||||
private final DeckController<? extends DeckBase> controller;
|
||||
|
||||
public DeckController<? extends DeckBase> getController() {
|
||||
return controller;
|
||||
}
|
||||
|
||||
private EditorType(DeckController<? extends DeckBase> controller0) {
|
||||
controller = controller0;
|
||||
}
|
||||
}
|
||||
|
||||
private static DeckEditorPage[] getPages(EditorType editorType) {
|
||||
@@ -93,9 +152,6 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
|
||||
private DeckSectionPage sideboardPage;
|
||||
private OptionsPage optionsPage;
|
||||
|
||||
public FDeckEditor(EditorType editorType0) {
|
||||
this(editorType0, null);
|
||||
}
|
||||
public FDeckEditor(EditorType editorType0, Deck deck0) {
|
||||
super(getPages(editorType0));
|
||||
editorType = editorType0;
|
||||
@@ -137,6 +193,20 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
|
||||
if (editorType == EditorType.Sealed && deck.getMain().isEmpty()) {
|
||||
setSelectedPage(sideboardPage);
|
||||
}
|
||||
|
||||
//set model for controller based on editor type
|
||||
if (!StringUtils.isEmpty(deck.getName())) {
|
||||
switch (editorType) {
|
||||
case Draft:
|
||||
case Sealed:
|
||||
case Winston:
|
||||
editorType.getController().setDeckBase(editorType.getController().currentFolder.get(deck.getName()));
|
||||
break;
|
||||
default:
|
||||
editorType.getController().setDeckBase(deck);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public EditorType getEditorType() {
|
||||
@@ -167,7 +237,91 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void save() {
|
||||
protected void save(final Callback<Boolean> callback) {
|
||||
final DeckController<?> controller = editorType.getController();
|
||||
final String name = getOptionsPage().txtName.getText();
|
||||
final String deckStr = DeckProxy.getDeckString(controller.getModelPath(), name);
|
||||
|
||||
// Warn if no name
|
||||
if (StringUtils.isEmpty(name)) {
|
||||
FOptionPane.showErrorDialog("Please name your deck using the 'Name' box.", "Save Error!");
|
||||
if (callback != null) {
|
||||
callback.run(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Confirm if overwrite
|
||||
if (controller.fileExists(name)) {
|
||||
//prompt only if name was changed
|
||||
if (!StringUtils.equals(name, controller.getModelName())) {
|
||||
FOptionPane.showConfirmDialog("There is already a deck named '" + name + "'. Overwrite?",
|
||||
"Overwrite Deck?", new Callback<Boolean>() {
|
||||
@Override
|
||||
public void run(Boolean result) {
|
||||
if (result) {
|
||||
controller.save();
|
||||
afterSave(deckStr);
|
||||
}
|
||||
if (callback != null) {
|
||||
callback.run(result);
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
controller.save();
|
||||
afterSave(deckStr);
|
||||
if (callback != null) {
|
||||
callback.run(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Confirm if a new deck will be created
|
||||
FOptionPane.showConfirmDialog("This will create a new deck named '" +
|
||||
name + "'. Continue?", "Create Deck?", new Callback<Boolean>() {
|
||||
@Override
|
||||
public void run(Boolean result) {
|
||||
if (result) {
|
||||
controller.saveAs(name);
|
||||
afterSave(deckStr);
|
||||
}
|
||||
if (callback != null) {
|
||||
callback.run(result);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void afterSave(String deckStr) {
|
||||
if (editorType == EditorType.Constructed) {
|
||||
DeckPreferences.setCurrentDeck(deckStr);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(final Callback<Boolean> canCloseCallback) {
|
||||
if (editorType.getController().isSaved() || canCloseCallback == null) {
|
||||
super.onClose(canCloseCallback); //can skip prompt if draft saved
|
||||
return;
|
||||
}
|
||||
FOptionPane.showOptionDialog("Save changes to current deck?", "Save Changes?",
|
||||
FOptionPane.QUESTION_ICON, new String[] {"Save", "Don't Save", "Cancel"}, new Callback<Integer>() {
|
||||
@Override
|
||||
public void run(Integer result) {
|
||||
if (result == 0) {
|
||||
save(canCloseCallback);
|
||||
}
|
||||
else if (result == 1) {
|
||||
editorType.getController().reload(); //reload if not saving changes
|
||||
canCloseCallback.run(true);
|
||||
}
|
||||
else {
|
||||
canCloseCallback.run(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected static abstract class DeckEditorPage extends TabPage<FDeckEditor> {
|
||||
@@ -194,11 +348,13 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
|
||||
|
||||
public void addCard(PaperCard card) {
|
||||
cardManager.addItem(card, 1);
|
||||
parentScreen.getEditorType().getController().notifyModelChanged();
|
||||
updateCaption();
|
||||
}
|
||||
|
||||
public void removeCard(PaperCard card) {
|
||||
cardManager.removeItem(card, 1);
|
||||
parentScreen.getEditorType().getController().notifyModelChanged();
|
||||
updateCaption();
|
||||
}
|
||||
|
||||
@@ -363,7 +519,7 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
|
||||
hideTab(); //hide this tab page when finished drafting
|
||||
parentScreen.getOptionsPage().showTab(); //show options page when finished drafting
|
||||
draft.finishedDrafting();
|
||||
parentScreen.save();
|
||||
parentScreen.save(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -374,9 +530,10 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
|
||||
private final FLabel lblName = add(new FLabel.Builder().text("Name:").build());
|
||||
private final FTextField txtName = add(new FTextField());
|
||||
private final FLabel btnSave = add(new FLabel.ButtonBuilder().text("Save Deck").icon(FSkinImage.SAVE).build());
|
||||
private final FLabel btnNew = add(new FLabel.ButtonBuilder().text("New Deck").icon(FSkinImage.NEW).build());
|
||||
private final FLabel btnAddLands = add(new FLabel.ButtonBuilder().text("Add Lands").icon(FSkinImage.LAND).build());
|
||||
/*private final FLabel btnNew = add(new FLabel.ButtonBuilder().text("New Deck").icon(FSkinImage.NEW).build());
|
||||
private final FLabel btnOpen = add(new FLabel.ButtonBuilder().text("Open Deck").icon(FSkinImage.OPEN).build());
|
||||
private final FLabel btnSaveAs = add(new FLabel.ButtonBuilder().text("Save Deck As").icon(FSkinImage.SAVEAS).build());
|
||||
private final FLabel btnSaveAs = add(new FLabel.ButtonBuilder().text("Save Deck As").icon(FSkinImage.SAVEAS).build());*/
|
||||
|
||||
protected OptionsPage() {
|
||||
super("Options", FSkinImage.SETTINGS);
|
||||
@@ -386,6 +543,18 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
|
||||
protected void initialize() {
|
||||
txtName.setGhostText("[New Deck]");
|
||||
txtName.setText(parentScreen.getDeck().getName());
|
||||
|
||||
btnSave.setCommand(new FEventHandler() {
|
||||
@Override
|
||||
public void handleEvent(FEvent e) {
|
||||
parentScreen.save(null);
|
||||
}
|
||||
});
|
||||
btnAddLands.setCommand(new FEventHandler() {
|
||||
@Override
|
||||
public void handleEvent(FEvent e) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -393,7 +562,6 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
|
||||
float x = PADDING;
|
||||
float y = PADDING;
|
||||
float w = width - 2 * PADDING;
|
||||
float h = height - 2 * PADDING;
|
||||
|
||||
lblName.setBounds(x, y, lblName.getAutoSizeBounds().width, txtName.getHeight());
|
||||
txtName.setBounds(x + lblName.getWidth(), y, w - lblName.getWidth(), txtName.getHeight());
|
||||
@@ -404,11 +572,176 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
|
||||
|
||||
btnSave.setBounds(x, y, w, buttonHeight);
|
||||
y += dy;
|
||||
btnNew.setBounds(x, y, w, buttonHeight);
|
||||
btnAddLands.setBounds(x, y, w, buttonHeight);
|
||||
y += dy;
|
||||
/*btnNew.setBounds(x, y, w, buttonHeight);
|
||||
y += dy;
|
||||
btnOpen.setBounds(x, y, w, buttonHeight);
|
||||
y += dy;
|
||||
btnSaveAs.setBounds(x, y, w, buttonHeight);
|
||||
btnSaveAs.setBounds(x, y, w, buttonHeight);*/
|
||||
}
|
||||
}
|
||||
|
||||
public static class DeckController<T extends DeckBase> {
|
||||
private T model;
|
||||
private boolean saved;
|
||||
private boolean modelInStorage;
|
||||
private final IStorage<T> rootFolder;
|
||||
private IStorage<T> currentFolder;
|
||||
private String modelPath;
|
||||
private final Supplier<T> newModelCreator;
|
||||
|
||||
protected DeckController(final IStorage<T> folder0, final Supplier<T> newModelCreator0) {
|
||||
rootFolder = folder0;
|
||||
currentFolder = rootFolder;
|
||||
model = null;
|
||||
saved = true;
|
||||
modelInStorage = false;
|
||||
modelPath = "";
|
||||
newModelCreator = newModelCreator0;
|
||||
}
|
||||
|
||||
public T getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public String getModelPath() {
|
||||
return modelPath;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void setDeckBase(final DeckBase deckBase) {
|
||||
setModel((T)deckBase, true);
|
||||
}
|
||||
|
||||
public void setModel(final T document) {
|
||||
setModel(document, false);
|
||||
}
|
||||
public void setModel(final T document, final boolean isStored) {
|
||||
modelInStorage = isStored;
|
||||
model = document;
|
||||
|
||||
if (isStored) {
|
||||
if (isModelInSyncWithFolder()) {
|
||||
setSaved(true);
|
||||
}
|
||||
else {
|
||||
notifyModelChanged();
|
||||
}
|
||||
}
|
||||
else { //TODO: Make this smarter
|
||||
currentFolder = rootFolder;
|
||||
modelPath = "";
|
||||
setSaved(true);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isModelInSyncWithFolder() {
|
||||
if (model.getName().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final T modelStored = currentFolder.get(model.getName());
|
||||
// checks presence in dictionary only.
|
||||
if (modelStored == model) {
|
||||
return true;
|
||||
}
|
||||
if (modelStored == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return modelStored.equals(model);
|
||||
}
|
||||
|
||||
public void notifyModelChanged() {
|
||||
if (saved) {
|
||||
setSaved(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void setSaved(boolean val) {
|
||||
saved = val;
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
String name = getModelName();
|
||||
if (name.isEmpty()) {
|
||||
newModel();
|
||||
}
|
||||
else {
|
||||
load(name);
|
||||
}
|
||||
}
|
||||
|
||||
public void load(final String path, final String name) {
|
||||
if (StringUtils.isBlank(path)) {
|
||||
currentFolder = rootFolder;
|
||||
}
|
||||
else {
|
||||
currentFolder = rootFolder.tryGetFolder(path);
|
||||
}
|
||||
modelPath = path;
|
||||
load(name);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void load(final String name) {
|
||||
T newModel = currentFolder.get(name);
|
||||
if (newModel != null) {
|
||||
setModel((T) newModel.copyTo(name), true);
|
||||
}
|
||||
else {
|
||||
setSaved(true);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void save() {
|
||||
if (model == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// copy to new instance before adding to current folder so further changes are auto-saved
|
||||
currentFolder.add((T) model.copyTo(model.getName()));
|
||||
modelInStorage = true;
|
||||
setSaved(true);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void saveAs(final String name0) {
|
||||
model = (T)model.copyTo(name0);
|
||||
modelInStorage = false;
|
||||
save();
|
||||
}
|
||||
|
||||
public boolean isSaved() {
|
||||
return saved;
|
||||
}
|
||||
|
||||
public boolean fileExists(final String deckName) {
|
||||
return currentFolder.contains(deckName);
|
||||
}
|
||||
|
||||
public void importDeck(final T newDeck) {
|
||||
setModel(newDeck);
|
||||
}
|
||||
|
||||
public void refreshModel() {
|
||||
if (model == null) {
|
||||
newModel();
|
||||
}
|
||||
else {
|
||||
setModel(model, modelInStorage);
|
||||
}
|
||||
}
|
||||
|
||||
public void newModel() {
|
||||
model = newModelCreator.get();
|
||||
setSaved(true);
|
||||
}
|
||||
|
||||
public String getModelName() {
|
||||
return model != null ? model.getName() : "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ public class DraftScreen extends LaunchScreen {
|
||||
public DraftScreen() {
|
||||
super("Booster Draft");
|
||||
|
||||
lstDecks.setPool(DeckProxy.getDraftDecks(FModel.getDecks().getDraft()));
|
||||
lstDecks.setup(ItemManagerConfig.DRAFT_DECKS);
|
||||
lstDecks.setItemActivateHandler(new FEventHandler() {
|
||||
@Override
|
||||
@@ -82,6 +81,11 @@ public class DraftScreen extends LaunchScreen {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivate() {
|
||||
lstDecks.setPool(DeckProxy.getDraftDecks(FModel.getDecks().getDraft()));
|
||||
}
|
||||
|
||||
private void editSelectedDeck() {
|
||||
final DeckProxy deck = lstDecks.getSelectedItem();
|
||||
if (deck == null) { return; }
|
||||
@@ -129,8 +133,8 @@ public class DraftScreen extends LaunchScreen {
|
||||
}*/
|
||||
|
||||
final int aiIndex = (int) Math.floor(Math.random() * 7);
|
||||
DeckGroup opponentDecks = FModel.getDecks().getDraft().get(humanDeck.getName());
|
||||
Deck aiDeck = opponentDecks.getAiDecks().get(aiIndex);
|
||||
DeckGroup deckGroup = FModel.getDecks().getDraft().get(humanDeck.getName());
|
||||
Deck aiDeck = deckGroup.getAiDecks().get(aiIndex);
|
||||
if (aiDeck == null) {
|
||||
throw new IllegalStateException("Draft: Computer deck is null!");
|
||||
}
|
||||
|
||||
@@ -11,11 +11,11 @@ import forge.toolbox.FOptionPane;
|
||||
import forge.util.Callback;
|
||||
|
||||
public class DraftingProcessScreen extends FDeckEditor {
|
||||
private boolean saved;
|
||||
private boolean isDraftSaved;
|
||||
private final BoosterDraft draft;
|
||||
|
||||
public DraftingProcessScreen(BoosterDraft draft0) {
|
||||
super(EditorType.Draft);
|
||||
super(EditorType.Draft, null);
|
||||
draft = draft0;
|
||||
getCatalogPage().refresh(); //must refresh after draft set
|
||||
}
|
||||
@@ -26,14 +26,17 @@ public class DraftingProcessScreen extends FDeckEditor {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void save() {
|
||||
if (saved) { return; }
|
||||
protected void save(final Callback<Boolean> callback) {
|
||||
if (isDraftSaved) { //if draft itself is saved, let base class handle saving deck changes
|
||||
super.save(callback);
|
||||
return;
|
||||
}
|
||||
|
||||
FOptionPane.showInputDialog("Save this draft as:", "Save Draft", FOptionPane.QUESTION_ICON, new Callback<String>() {
|
||||
@Override
|
||||
public void run(final String name) {
|
||||
if (StringUtils.isEmpty(name)) {
|
||||
save(); //re-prompt if user doesn't pick a name
|
||||
save(callback); //re-prompt if user doesn't pick a name
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -47,9 +50,12 @@ public class DraftingProcessScreen extends FDeckEditor {
|
||||
public void run(Boolean result) {
|
||||
if (result) {
|
||||
finishSave(name);
|
||||
if (callback != null) {
|
||||
callback.run(true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
save(); //If no overwrite, recurse
|
||||
save(callback); //If no overwrite, recurse
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -58,12 +64,15 @@ public class DraftingProcessScreen extends FDeckEditor {
|
||||
}
|
||||
|
||||
finishSave(name);
|
||||
if (callback != null) {
|
||||
callback.run(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void finishSave(String name) {
|
||||
saved = true;
|
||||
isDraftSaved = true;
|
||||
|
||||
// Construct computer's decks and save draft
|
||||
final Deck[] computer = draft.getDecks();
|
||||
@@ -73,11 +82,12 @@ public class DraftingProcessScreen extends FDeckEditor {
|
||||
finishedDraft.addAiDecks(computer);
|
||||
|
||||
FModel.getDecks().getDraft().add(finishedDraft);
|
||||
this.getEditorType().getController().setDeckBase(finishedDraft);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(Callback<Boolean> canCloseCallback) {
|
||||
if (saved || canCloseCallback == null) {
|
||||
if (isDraftSaved || canCloseCallback == null) {
|
||||
super.onClose(canCloseCallback); //can skip prompt if draft saved
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ public class SealedScreen extends LaunchScreen {
|
||||
public SealedScreen() {
|
||||
super("Sealed Deck");
|
||||
|
||||
lstDecks.setPool(DeckProxy.getAllSealedDecks(FModel.getDecks().getSealed()));
|
||||
lstDecks.setup(ItemManagerConfig.SEALED_DECKS);
|
||||
lstDecks.setItemActivateHandler(new FEventHandler() {
|
||||
@Override
|
||||
@@ -68,6 +67,11 @@ public class SealedScreen extends LaunchScreen {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivate() {
|
||||
lstDecks.setPool(DeckProxy.getAllSealedDecks(FModel.getDecks().getSealed()));
|
||||
}
|
||||
|
||||
private void editSelectedDeck() {
|
||||
final DeckProxy deck = lstDecks.getSelectedItem();
|
||||
if (deck == null) { return; }
|
||||
|
||||
Reference in New Issue
Block a user