mirror of
https://github.com/magefree/mage.git
synced 2025-12-25 04:52:07 -08:00
Added user info text that user can set. Addd chat whisper command. Some minor changes to chat. Impoved display of user list.
This commit is contained in:
parent
e9dd478848
commit
c0323c168c
19 changed files with 522 additions and 356 deletions
|
|
@ -28,10 +28,11 @@
|
|||
|
||||
package mage.server;
|
||||
|
||||
import mage.view.ChatMessage.MessageColor;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import mage.view.ChatMessage.MessageColor;
|
||||
import mage.view.ChatMessage.MessageType;
|
||||
import mage.view.ChatMessage.SoundToPlay;
|
||||
|
||||
/**
|
||||
|
|
@ -69,17 +70,69 @@ public class ChatManager {
|
|||
}
|
||||
|
||||
public void broadcast(UUID chatId, String userName, String message, MessageColor color) {
|
||||
chatSessions.get(chatId).broadcast(userName, message, color);
|
||||
this.broadcast(chatId, userName, message, color, true);
|
||||
}
|
||||
|
||||
public void broadcast(UUID chatId, String userName, String message, MessageColor color, boolean withTime) {
|
||||
chatSessions.get(chatId).broadcast(userName, message, color, withTime);
|
||||
this.broadcast(chatId, userName, message, color, withTime, MessageType.TALK);
|
||||
}
|
||||
|
||||
public void broadcast(UUID chatId, String userName, String message, MessageColor color, boolean withTime, SoundToPlay soundToPlay) {
|
||||
chatSessions.get(chatId).broadcast(userName, message, color, withTime, soundToPlay);
|
||||
public void broadcast(UUID chatId, String userName, String message, MessageColor color, boolean withTime, MessageType messageType) {
|
||||
this.broadcast(chatId, userName, message, color, withTime, messageType, null);
|
||||
}
|
||||
|
||||
public void broadcast(UUID chatId, String userName, String message, MessageColor color, boolean withTime, MessageType messageType, SoundToPlay soundToPlay) {
|
||||
if (message.startsWith("\\")) {
|
||||
User user = UserManager.getInstance().findUser(userName);
|
||||
if (user != null && performUserCommand(user, message, chatId)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
chatSessions.get(chatId).broadcast(userName, message, color, withTime, messageType, soundToPlay);
|
||||
}
|
||||
|
||||
|
||||
private boolean performUserCommand(User user, String message, UUID chatId) {
|
||||
String command = message.trim().toUpperCase(Locale.ENGLISH);
|
||||
if (command.equals("\\I") || command.equals("\\INFO")) {
|
||||
user.setInfo("");
|
||||
chatSessions.get(chatId).broadcastInfoToUser(user,message);
|
||||
return true;
|
||||
}
|
||||
if (command.startsWith("\\I ") || command.startsWith("\\INFO ")) {
|
||||
user.setInfo(message.substring(command.startsWith("\\I ") ? 3 : 6));
|
||||
chatSessions.get(chatId).broadcastInfoToUser(user,message);
|
||||
return true;
|
||||
}
|
||||
if (command.startsWith("\\W ") || command.startsWith("\\WHISPER ")) {
|
||||
String rest = message.substring(command.startsWith("\\W ") ? 3 : 9);
|
||||
int first = rest.indexOf(" ");
|
||||
if (first > 1) {
|
||||
String userToName = rest.substring(0,first);
|
||||
rest = rest.substring(first + 1).trim();
|
||||
User userTo = UserManager.getInstance().findUser(userToName);
|
||||
if (userTo != null) {
|
||||
chatSessions.get(chatId).broadcastWhisperToUser(user, userTo, rest);
|
||||
} else {
|
||||
message += new StringBuilder("\nUser ").append(userToName).append(" not found").toString();
|
||||
chatSessions.get(chatId).broadcastInfoToUser(user,message);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (command.equals("\\L") || command.equals("\\LIST")) {
|
||||
message += new StringBuilder("\nList of commands:")
|
||||
.append("\n\\info <text> - set a info text to your player")
|
||||
.append("\n\\list - Show a list of commands")
|
||||
.append("\n\\whisper <player name> <text> - Whiper to a player").toString();
|
||||
chatSessions.get(chatId).broadcastInfoToUser(user,message);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* use mainly for announcing that a user connection was lost or that a user has reconnected
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
import mage.interfaces.callback.ClientCallback;
|
||||
import mage.view.ChatMessage;
|
||||
import mage.view.ChatMessage.MessageColor;
|
||||
import mage.view.ChatMessage.MessageType;
|
||||
import mage.view.ChatMessage.SoundToPlay;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
|
|
@ -47,6 +48,8 @@ import org.apache.log4j.Logger;
|
|||
public class ChatSession {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ChatSession.class);
|
||||
private static final Calendar cal = new GregorianCalendar();
|
||||
|
||||
private ConcurrentHashMap<UUID, String> clients = new ConcurrentHashMap<UUID, String>();
|
||||
private UUID chatId;
|
||||
private DateFormat timeFormatter = SimpleDateFormat.getTimeInstance(SimpleDateFormat.SHORT);
|
||||
|
|
@ -60,7 +63,7 @@ public class ChatSession {
|
|||
if (user != null && !clients.containsKey(userId)) {
|
||||
String userName = user.getName();
|
||||
clients.put(userId, userName);
|
||||
broadcast(userName, " has joined", MessageColor.BLUE);
|
||||
broadcast(null, new StringBuilder(userName).append(" has joined").toString(), MessageColor.BLUE, true, MessageType.STATUS);
|
||||
logger.debug(userName + " joined chat " + chatId);
|
||||
}
|
||||
}
|
||||
|
|
@ -80,22 +83,46 @@ public class ChatSession {
|
|||
default:
|
||||
message = " has left chat";
|
||||
}
|
||||
broadcast(userName, message, MessageColor.BLUE);
|
||||
broadcast(null, new StringBuilder(userName).append(message).toString(), MessageColor.BLUE, true, MessageType.STATUS);
|
||||
logger.debug(userName + message + " " + chatId);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean broadcastInfoToUser(User toUser, String message) {
|
||||
if (clients.containsKey(toUser.getId())) {
|
||||
toUser.fireCallback(new ClientCallback("chatMessage", chatId, new ChatMessage(null, message, timeFormatter.format(cal.getTime()), MessageColor.ORANGE, MessageType.USER_INFO, null)));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean broadcastWhisperToUser(User fromUser, User toUser, String message) {
|
||||
if (clients.containsKey(toUser.getId())) {
|
||||
toUser.fireCallback(new ClientCallback("chatMessage", chatId,
|
||||
new ChatMessage(new StringBuilder("Whisper from ").append(fromUser.getName()).toString(), message, timeFormatter.format(cal.getTime()), MessageColor.YELLOW, MessageType.WHISPER, SoundToPlay.PlayerWhispered)));
|
||||
if (clients.containsKey(fromUser.getId())) {
|
||||
fromUser.fireCallback(new ClientCallback("chatMessage", chatId,
|
||||
new ChatMessage(new StringBuilder("Whisper to ").append(toUser.getName()).toString(), message, timeFormatter.format(cal.getTime()), MessageColor.YELLOW, MessageType.WHISPER, null)));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void broadcast(String userName, String message, MessageColor color) {
|
||||
broadcast(userName, message, color, true);
|
||||
this.broadcast(userName, message, color, true);
|
||||
}
|
||||
|
||||
public void broadcast(String userName, String message, MessageColor color, boolean withTime) {
|
||||
broadcast(userName, message, color, withTime, null);
|
||||
this.broadcast(userName, message, color, withTime, MessageType.TALK);
|
||||
}
|
||||
|
||||
public void broadcast(String userName, String message, MessageColor color, boolean withTime, SoundToPlay soundToPlay) {
|
||||
if (!message.isEmpty()) {
|
||||
Calendar cal = new GregorianCalendar();
|
||||
public void broadcast(String userName, String message, MessageColor color, boolean withTime, MessageType messageType) {
|
||||
this.broadcast(userName, message, color, withTime, messageType, null);
|
||||
}
|
||||
|
||||
public void broadcast(String userName, String message, MessageColor color, boolean withTime, MessageType messageType, SoundToPlay soundToPlay) {
|
||||
if (!message.isEmpty()) {
|
||||
final String msg = message;
|
||||
final String time = (withTime ? timeFormatter.format(cal.getTime()):"");
|
||||
final String username = userName;
|
||||
|
|
@ -103,7 +130,7 @@ public class ChatSession {
|
|||
for (UUID userId: clients.keySet()) {
|
||||
User user = UserManager.getInstance().getUser(userId);
|
||||
if (user != null) {
|
||||
user.fireCallback(new ClientCallback("chatMessage", chatId, new ChatMessage(username, msg, time, color, soundToPlay)));
|
||||
user.fireCallback(new ClientCallback("chatMessage", chatId, new ChatMessage(username, msg, time, color, messageType, soundToPlay)));
|
||||
}
|
||||
else {
|
||||
kill(userId, User.DisconnectReason.CleaningUp);
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ import mage.view.TableView;
|
|||
import mage.view.TournamentView;
|
||||
import mage.view.UserDataView;
|
||||
import mage.view.UserView;
|
||||
import mage.view.UsersView;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
/**
|
||||
|
|
@ -291,7 +292,7 @@ public class MageServerImpl implements MageServer {
|
|||
|
||||
@Override
|
||||
//FIXME: why no sessionId here???
|
||||
public List<String> getConnectedPlayers(UUID roomId) throws MageException {
|
||||
public List<UsersView> getConnectedPlayers(UUID roomId) throws MageException {
|
||||
try {
|
||||
GamesRoom room = GamesRoomManager.getInstance().getRoom(roomId);
|
||||
if (room != null) {
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ public class User {
|
|||
private String userName;
|
||||
private String sessionId = "";
|
||||
private String host;
|
||||
private String info;
|
||||
private Date connectionTime = new Date();
|
||||
private Date lastActivity = new Date();
|
||||
private UserState userState;
|
||||
|
|
@ -305,7 +306,7 @@ public class User {
|
|||
return this.userData;
|
||||
}
|
||||
|
||||
public String getUserInfo() {
|
||||
public String getGameInfo() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (gameSessions.size() > 0) {
|
||||
sb.append("G: ").append(gameSessions.size());
|
||||
|
|
@ -316,10 +317,14 @@ public class User {
|
|||
}
|
||||
sb.append("T: ").append(tournamentSessions.size());
|
||||
}
|
||||
if (sb.length() > 0) {
|
||||
sb.insert(0, " - [");
|
||||
sb.append("]");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String getInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
public void setInfo(String Info) {
|
||||
this.info = Info;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,7 +76,9 @@ import mage.server.util.ThreadExecutor;
|
|||
import mage.utils.timer.PriorityTimer;
|
||||
import mage.view.AbilityPickerView;
|
||||
import mage.view.CardsView;
|
||||
import mage.view.ChatMessage;
|
||||
import mage.view.ChatMessage.MessageColor;
|
||||
import mage.view.ChatMessage.MessageType;
|
||||
import mage.view.GameView;
|
||||
import mage.view.PermanentView;
|
||||
import org.apache.log4j.Logger;
|
||||
|
|
@ -133,11 +135,11 @@ public class GameController implements GameCallback {
|
|||
updateGame();
|
||||
break;
|
||||
case INFO:
|
||||
ChatManager.getInstance().broadcast(chatId, "", event.getMessage(), MessageColor.BLACK);
|
||||
ChatManager.getInstance().broadcast(chatId, "", event.getMessage(), MessageColor.BLACK, true, ChatMessage.MessageType.GAME);
|
||||
logger.debug(game.getId() + " " + event.getMessage());
|
||||
break;
|
||||
case STATUS:
|
||||
ChatManager.getInstance().broadcast(chatId, "", event.getMessage(), MessageColor.ORANGE, event.getWithTime());
|
||||
ChatManager.getInstance().broadcast(chatId, "", event.getMessage(), MessageColor.ORANGE, event.getWithTime(), ChatMessage.MessageType.GAME);
|
||||
logger.debug(game.getId() + " " + event.getMessage());
|
||||
break;
|
||||
case ERROR:
|
||||
|
|
@ -272,7 +274,7 @@ public class GameController implements GameCallback {
|
|||
gameSession.setUserData(user.getUserData());
|
||||
user.addGame(playerId, gameSession);
|
||||
logger.debug(new StringBuilder("Player ").append(playerId).append(" has joined game ").append(game.getId()).toString());
|
||||
ChatManager.getInstance().broadcast(chatId, "", new StringBuilder(game.getPlayer(playerId).getName()).append(" has joined the game").toString(), MessageColor.BLACK);
|
||||
ChatManager.getInstance().broadcast(chatId, "", new StringBuilder(game.getPlayer(playerId).getName()).append(" has joined the game").toString(), MessageColor.ORANGE, true, MessageType.GAME);
|
||||
checkStart();
|
||||
}
|
||||
|
||||
|
|
@ -321,7 +323,7 @@ public class GameController implements GameCallback {
|
|||
GameWatcher gameWatcher = new GameWatcher(userId, game, false);
|
||||
watchers.put(userId, gameWatcher);
|
||||
gameWatcher.init();
|
||||
ChatManager.getInstance().broadcast(chatId, user.getName(), " has started watching", MessageColor.BLUE);
|
||||
ChatManager.getInstance().broadcast(chatId, user.getName(), " has started watching", MessageColor.BLUE, true, ChatMessage.MessageType.STATUS);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -329,7 +331,7 @@ public class GameController implements GameCallback {
|
|||
watchers.remove(userId);
|
||||
User user = UserManager.getInstance().getUser(userId);
|
||||
if (user != null) {
|
||||
ChatManager.getInstance().broadcast(chatId, user.getName(), " has stopped watching", MessageColor.BLUE);
|
||||
ChatManager.getInstance().broadcast(chatId, user.getName(), " has stopped watching", MessageColor.BLUE, true, ChatMessage.MessageType.STATUS);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -405,12 +407,11 @@ public class GameController implements GameCallback {
|
|||
|
||||
public void timeout(UUID userId) {
|
||||
if (userPlayerMap.containsKey(userId)) {
|
||||
;
|
||||
StringBuilder sb = new StringBuilder(game.getPlayer(userPlayerMap.get(userId)).getName())
|
||||
.append(" has timed out (player had priority and was not active for ")
|
||||
.append(ConfigSettings.getInstance().getMaxSecondsIdle())
|
||||
.append(" seconds ) - Auto concede.");
|
||||
ChatManager.getInstance().broadcast(chatId, "", sb.toString() , MessageColor.BLACK);
|
||||
ChatManager.getInstance().broadcast(chatId, "", sb.toString() , MessageColor.BLACK, true, MessageType.STATUS);
|
||||
concede(userId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import mage.game.tournament.TournamentOptions;
|
|||
import mage.server.Room;
|
||||
import mage.view.MatchView;
|
||||
import mage.view.TableView;
|
||||
import mage.view.UsersView;
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
@ -47,7 +48,7 @@ public interface GamesRoom extends Room {
|
|||
|
||||
List<TableView> getTables();
|
||||
List<MatchView> getFinished();
|
||||
List<String> getPlayers();
|
||||
List<UsersView> getPlayers();
|
||||
boolean joinTable(UUID userId, UUID tableId, String name, String playerType, int skill, DeckCardLists deckList) throws MageException;
|
||||
boolean joinTournamentTable(UUID userId, UUID tableId, String name, String playerType, int skill) throws GameException;
|
||||
TableView createTable(UUID userId, MatchOptions options);
|
||||
|
|
|
|||
|
|
@ -38,9 +38,9 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import mage.constants.TableState;
|
||||
import mage.MageException;
|
||||
import mage.cards.decks.DeckCardLists;
|
||||
import mage.constants.TableState;
|
||||
import mage.game.GameException;
|
||||
import mage.game.Table;
|
||||
import mage.game.match.MatchOptions;
|
||||
|
|
@ -51,9 +51,9 @@ import mage.server.User;
|
|||
import mage.server.UserManager;
|
||||
import mage.view.MatchView;
|
||||
import mage.view.TableView;
|
||||
import mage.view.UsersView;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
|
|
@ -65,7 +65,7 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable {
|
|||
private static ScheduledExecutorService updateExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||
private static List<TableView> tableView = new ArrayList<TableView>();
|
||||
private static List<MatchView> matchView = new ArrayList<MatchView>();
|
||||
private static List<String> playersView = new ArrayList<String>();
|
||||
private static List<UsersView> usersView = new ArrayList<UsersView>();
|
||||
|
||||
private ConcurrentHashMap<UUID, Table> tables = new ConcurrentHashMap<UUID, Table>();
|
||||
|
||||
|
|
@ -109,16 +109,16 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable {
|
|||
}
|
||||
tableView = tableList;
|
||||
matchView = matchList;
|
||||
List<String> players = new ArrayList<String>();
|
||||
List<UsersView> users = new ArrayList<UsersView>();
|
||||
for (User user : UserManager.getInstance().getUsers()) {
|
||||
StringBuilder sb = new StringBuilder(user.getName());
|
||||
sb.append(user.getUserInfo());
|
||||
StringBuilder sb = new StringBuilder(user.getGameInfo());
|
||||
if (!user.isConnected()) {
|
||||
sb.append(" (discon.)");
|
||||
}
|
||||
players.add(sb.toString());
|
||||
users.add(new UsersView(user.getName(), user.getInfo(), sb.toString()));
|
||||
}
|
||||
playersView = players;
|
||||
Collections.sort(users, new UserNameSorter());
|
||||
usersView = users;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -190,8 +190,8 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<String> getPlayers() {
|
||||
return playersView;
|
||||
public List<UsersView> getPlayers() {
|
||||
return usersView;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -202,3 +202,10 @@ class TimestampSorter implements Comparator<Table> {
|
|||
return one.getCreateTime().compareTo(two.getCreateTime());
|
||||
}
|
||||
}
|
||||
|
||||
class UserNameSorter implements Comparator<UsersView> {
|
||||
@Override
|
||||
public int compare(UsersView one, UsersView two) {
|
||||
return one.getUserName().compareTo(two.getUserName());
|
||||
}
|
||||
}
|
||||
|
|
@ -54,6 +54,7 @@ import mage.server.draft.DraftManager;
|
|||
import mage.server.game.GamesRoomManager;
|
||||
import mage.server.util.ThreadExecutor;
|
||||
import mage.view.ChatMessage.MessageColor;
|
||||
import mage.view.ChatMessage.MessageType;
|
||||
import mage.view.ChatMessage.SoundToPlay;
|
||||
import mage.view.TournamentView;
|
||||
import org.apache.log4j.Logger;
|
||||
|
|
@ -91,7 +92,7 @@ public class TournamentController {
|
|||
public void event(TableEvent event) {
|
||||
switch (event.getEventType()) {
|
||||
case INFO:
|
||||
ChatManager.getInstance().broadcast(chatId, "", event.getMessage(), MessageColor.BLACK);
|
||||
ChatManager.getInstance().broadcast(chatId, "", event.getMessage(), MessageColor.BLACK, true, MessageType.STATUS);
|
||||
logger.debug(tournament.getId() + " " + event.getMessage());
|
||||
break;
|
||||
case START_DRAFT:
|
||||
|
|
@ -137,7 +138,7 @@ public class TournamentController {
|
|||
if (!player.getPlayer().isHuman()) {
|
||||
player.setJoined();
|
||||
logger.debug("player " + player.getPlayer().getId() + " has joined tournament " + tournament.getId());
|
||||
ChatManager.getInstance().broadcast(chatId, "", player.getPlayer().getName() + " has joined the tournament", MessageColor.BLACK);
|
||||
ChatManager.getInstance().broadcast(chatId, "", player.getPlayer().getName() + " has joined the tournament", MessageColor.BLACK, true, MessageType.STATUS);
|
||||
}
|
||||
}
|
||||
checkStart();
|
||||
|
|
@ -151,7 +152,7 @@ public class TournamentController {
|
|||
TournamentPlayer player = tournament.getPlayer(playerId);
|
||||
player.setJoined();
|
||||
logger.debug("player " + playerId + " has joined tournament " + tournament.getId());
|
||||
ChatManager.getInstance().broadcast(chatId, "", player.getPlayer().getName() + " has joined the tournament", MessageColor.BLACK);
|
||||
ChatManager.getInstance().broadcast(chatId, "", player.getPlayer().getName() + " has joined the tournament", MessageColor.BLACK, true, MessageType.STATUS);
|
||||
checkStart();
|
||||
}
|
||||
|
||||
|
|
@ -247,7 +248,7 @@ public class TournamentController {
|
|||
TournamentPlayer player = tournament.getPlayer(playerId);
|
||||
if (player != null && !player.hasQuit()) {
|
||||
tournamentSessions.get(playerId).submitDeck(deck);
|
||||
ChatManager.getInstance().broadcast(chatId, "", player.getPlayer().getName() + " has submitted his tournament deck", MessageColor.BLACK, true, SoundToPlay.PlayerSubmittedDeck);
|
||||
ChatManager.getInstance().broadcast(chatId, "", player.getPlayer().getName() + " has submitted his tournament deck", MessageColor.BLACK, true, MessageType.STATUS, SoundToPlay.PlayerSubmittedDeck);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -275,7 +276,7 @@ public class TournamentController {
|
|||
TournamentPlayer tPlayer = tournament.getPlayer(playerId);
|
||||
if (tPlayer != null) {
|
||||
if (started) {
|
||||
ChatManager.getInstance().broadcast(chatId, "", tPlayer.getPlayer().getName() + " has quit the tournament", MessageColor.BLACK, true, SoundToPlay.PlayerLeft);
|
||||
ChatManager.getInstance().broadcast(chatId, "", tPlayer.getPlayer().getName() + " has quit the tournament", MessageColor.BLACK, true, MessageType.STATUS, SoundToPlay.PlayerLeft);
|
||||
String info;
|
||||
if (tournament.isDoneConstructing()) {
|
||||
info = new StringBuilder("during round ").append(tournament.getRounds().size()).toString();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue