javadoc standard + minor fix
This commit is contained in:
parent
a7e1991a81
commit
a7cf0debe3
5 changed files with 146 additions and 71 deletions
|
@ -126,13 +126,6 @@ public class ControleurConnexion implements ActionListener{
|
|||
Utilisateur.setSelf(this.username, pseudo, "localhost", this.portTCP);
|
||||
} catch (UnknownHostException e2) {
|
||||
}
|
||||
|
||||
//broadcast new pseudo
|
||||
try {
|
||||
this.comUDP.sendMessageInfoPseudo();
|
||||
} catch (UnknownHostException e1) {
|
||||
} catch (IOException e1) {
|
||||
}
|
||||
|
||||
try {
|
||||
this.resetView();
|
||||
|
|
|
@ -147,8 +147,7 @@ public class ControleurSession implements ActionListener, ObserverInputMessage,
|
|||
// ---------- KEY LISTENER METHODS ---------- //
|
||||
|
||||
@Override
|
||||
public void keyTyped(KeyEvent e) {
|
||||
}
|
||||
public void keyTyped(KeyEvent e) {}
|
||||
|
||||
|
||||
@Override
|
||||
|
@ -163,8 +162,7 @@ public class ControleurSession implements ActionListener, ObserverInputMessage,
|
|||
|
||||
|
||||
@Override
|
||||
public void keyReleased(KeyEvent e) {
|
||||
}
|
||||
public void keyReleased(KeyEvent e) {}
|
||||
|
||||
|
||||
// ---------- OTHERS ---------- //
|
||||
|
@ -232,7 +230,6 @@ public class ControleurSession implements ActionListener, ObserverInputMessage,
|
|||
ArrayList<Message> historique = this.sqlManager.getMessageRecord(idOther, pseudoOther);
|
||||
return historique;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return new ArrayList<Message>();
|
||||
|
||||
}
|
||||
|
@ -252,7 +249,6 @@ public class ControleurSession implements ActionListener, ObserverInputMessage,
|
|||
this.sqlManager.insertAllMessages(messagesOut, idSelf, idOther);
|
||||
this.sqlManager.insertAllMessages(messagesIn, idOther, idSelf);
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
this.vue = null;
|
||||
|
@ -313,7 +309,6 @@ public class ControleurSession implements ActionListener, ObserverInputMessage,
|
|||
this.answerFileTransfer(port);
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -331,7 +326,6 @@ public class ControleurSession implements ActionListener, ObserverInputMessage,
|
|||
ftc.sendFiles();
|
||||
this.files.clear();
|
||||
} catch (IOException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
|
@ -135,7 +135,6 @@ public class VueSession extends JPanel {
|
|||
Document doc = this.chatWindow.getDocument();
|
||||
doc.insertString(doc.getLength(), str, null);
|
||||
} catch (BadLocationException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,7 +151,6 @@ public class VueSession extends JPanel {
|
|||
sdoc.insertString(sdoc.getLength(), message.toString(), null);
|
||||
|
||||
} catch (BadLocationException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -176,7 +174,6 @@ public class VueSession extends JPanel {
|
|||
this.appendString("\n");
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,12 +25,11 @@ import connexion.VueConnexion;
|
|||
import database.SQLiteManager;
|
||||
import main.Utilisateur;
|
||||
import observers.ObserverInputMessage;
|
||||
import observers.ObserverSocketState;
|
||||
import observers.ObserverUserList;
|
||||
import session.VueSession;
|
||||
|
||||
public class ControleurStandard implements ActionListener, ListSelectionListener, WindowListener, ObserverInputMessage,
|
||||
ObserverUserList, ObserverSocketState {
|
||||
public class ControleurStandard
|
||||
implements ActionListener, ListSelectionListener, WindowListener, ObserverInputMessage, ObserverUserList {
|
||||
|
||||
private enum ModifPseudo {
|
||||
TERMINE, EN_COURS
|
||||
|
@ -48,17 +47,23 @@ public class ControleurStandard implements ActionListener, ListSelectionListener
|
|||
public ControleurStandard(VueStandard vue, CommunicationUDP commUDP, int portServerTCP, SQLiteManager sqlManager,
|
||||
VueConnexion vueConnexion) throws IOException {
|
||||
this.vue = vue;
|
||||
// Instruction to avoid closing the application when clicking the upper right
|
||||
// cross
|
||||
this.vue.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
|
||||
|
||||
this.vueConnexion = vueConnexion;
|
||||
|
||||
|
||||
// The TCP server waiting for session requests
|
||||
this.tcpServ = new TCPServer(portServerTCP);
|
||||
|
||||
|
||||
this.tcpServ.addObserver(this);
|
||||
this.tcpServ.start();
|
||||
|
||||
// The UDP communication (server + userlist manager)
|
||||
this.commUDP = commUDP;
|
||||
|
||||
|
||||
// An array to store the usernames of the users a session exists at any point in
|
||||
// time.
|
||||
this.idsSessionEnCours = new ArrayList<String>();
|
||||
|
||||
this.sqlManager = sqlManager;
|
||||
|
@ -86,16 +91,14 @@ public class ControleurStandard implements ActionListener, ListSelectionListener
|
|||
System.out.println("choix : " + choix);
|
||||
|
||||
if (choix == 0) {
|
||||
|
||||
|
||||
|
||||
int port = other.getPort();
|
||||
|
||||
|
||||
|
||||
System.out.println("port = " + port);
|
||||
try {
|
||||
|
||||
Socket socketComm = new Socket(InetAddress.getLocalHost(), port);
|
||||
|
||||
|
||||
this.sendMessage(socketComm, Utilisateur.getSelf().getPseudo());
|
||||
String reponse = this.readMessage(socketComm);
|
||||
|
||||
|
@ -116,7 +119,6 @@ public class ControleurStandard implements ActionListener, ListSelectionListener
|
|||
}
|
||||
|
||||
} catch (IOException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,7 +134,7 @@ public class ControleurStandard implements ActionListener, ListSelectionListener
|
|||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
|
||||
// Cas Modifier Pseudo
|
||||
// Case change pseudo
|
||||
if ((JButton) e.getSource() == this.vue.getButtonModifierPseudo()) {
|
||||
JButton modifierPseudo = (JButton) e.getSource();
|
||||
|
||||
|
@ -150,7 +152,6 @@ public class ControleurStandard implements ActionListener, ListSelectionListener
|
|||
try {
|
||||
this.commUDP.sendMessageInfoPseudo();
|
||||
} catch (IOException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
|
||||
} else {
|
||||
|
@ -164,16 +165,15 @@ public class ControleurStandard implements ActionListener, ListSelectionListener
|
|||
this.vue.toggleEditPseudo();
|
||||
}
|
||||
|
||||
// Cas deconnexion
|
||||
// Case logging off
|
||||
else if ((JButton) e.getSource() == this.vue.getButtonDeconnexion()) {
|
||||
try {
|
||||
this.setVueConnexion();
|
||||
} catch (IOException e1) {
|
||||
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// Case close session
|
||||
else if (this.vue.isButtonTab(e.getSource())) {
|
||||
JButton button = (JButton) e.getSource();
|
||||
int index = this.vue.removeSession(button);
|
||||
|
@ -189,44 +189,32 @@ public class ControleurStandard implements ActionListener, ListSelectionListener
|
|||
try {
|
||||
this.setVueConnexion();
|
||||
} catch (IOException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowOpened(WindowEvent e) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowClosed(WindowEvent e) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowIconified(WindowEvent e) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowDeiconified(WindowEvent e) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowActivated(WindowEvent e) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowDeactivated(WindowEvent e) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
// ------------SOCKET-------------//
|
||||
|
@ -237,27 +225,30 @@ public class ControleurStandard implements ActionListener, ListSelectionListener
|
|||
}
|
||||
|
||||
private String readMessage(Socket sock) throws IOException {
|
||||
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(sock.getInputStream()));
|
||||
return input.readLine();
|
||||
}
|
||||
|
||||
// ------------OBSERVERS------------- //
|
||||
|
||||
// Method called when there is a connection on the TCP server
|
||||
// This always means, in theory, that an user is asking to create a session
|
||||
@Override
|
||||
public void updateInput(Object o, Object arg) {
|
||||
|
||||
if (o == this.tcpServ) {
|
||||
|
||||
// TCP socket given from the TCP Server
|
||||
Socket sockAccept = (Socket) arg;
|
||||
|
||||
try {
|
||||
|
||||
// Read the other user's pseudo
|
||||
String pseudoOther = this.readMessage(sockAccept);
|
||||
String idOther = this.commUDP.getUserFromPseudo(pseudoOther).getId();
|
||||
|
||||
int reponse;
|
||||
|
||||
// Display the dialog box and wait for replay
|
||||
if (!this.idsSessionEnCours.contains(idOther)) {
|
||||
reponse = this.vue.displayJOptionAskForSession(pseudoOther);
|
||||
System.out.println("reponse : " + reponse);
|
||||
|
@ -265,6 +256,8 @@ public class ControleurStandard implements ActionListener, ListSelectionListener
|
|||
reponse = 1;
|
||||
}
|
||||
|
||||
// If the session is accepted
|
||||
// Create a new VueSession with the socket
|
||||
if (reponse == 0) {
|
||||
|
||||
this.idsSessionEnCours.add(idOther);
|
||||
|
@ -278,15 +271,16 @@ public class ControleurStandard implements ActionListener, ListSelectionListener
|
|||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method called when the userlist of the CommunicationUDP is updated
|
||||
@Override
|
||||
public void updateList(Object o, ArrayList<Utilisateur> userList) {
|
||||
|
||||
if (o == this.commUDP) {
|
||||
// Get every pseudo from the userlist and give the pseudo's list to the view
|
||||
ArrayList<String> pseudos = new ArrayList<String>();
|
||||
for (Utilisateur u : userList) {
|
||||
pseudos.add(u.getPseudo());
|
||||
|
@ -295,13 +289,13 @@ public class ControleurStandard implements ActionListener, ListSelectionListener
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateSocketState(Object o, Object arg) {
|
||||
VueSession session = (VueSession) arg;
|
||||
int index = this.vue.removeSession(session);
|
||||
this.idsSessionEnCours.remove(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the system message DECONNECTE. Reset the userlist and the user data.
|
||||
* Close all sessions. Set this view invisible and the connexion's view visible.
|
||||
*
|
||||
* @throws UnknownHostException
|
||||
* @throws IOException
|
||||
*/
|
||||
private void setVueConnexion() throws UnknownHostException, IOException {
|
||||
this.commUDP.sendMessageDelete();
|
||||
this.commUDP.removeAllUsers();
|
||||
|
@ -314,13 +308,20 @@ public class ControleurStandard implements ActionListener, ListSelectionListener
|
|||
this.vue.setVisible(false);
|
||||
this.vueConnexion.setVisible(true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the controler as the observer of the commUDP to receive the updates on
|
||||
* the userlist. Then send the system message JE_SUIS_CONNECTE and this
|
||||
* application's user data
|
||||
*
|
||||
* @throws UnknownHostException
|
||||
* @throws IOException
|
||||
*/
|
||||
protected void init() throws UnknownHostException, IOException {
|
||||
this.commUDP.setObserver(this);
|
||||
this.commUDP.sendMessageConnecte();
|
||||
this.commUDP.sendMessageInfoPseudo();
|
||||
this.modifPseudo = ModifPseudo.TERMINE;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -51,10 +51,23 @@ public class VueStandard extends Vue {
|
|||
private ArrayList<VueSession> sessions;
|
||||
private DefaultListModel<String> userList = new DefaultListModel<String>();
|
||||
|
||||
public VueStandard(String title, CommunicationUDP commUDP, int portServerTCP, SQLiteManager sqlManager, VueConnexion vueConnexion) throws IOException {
|
||||
/**
|
||||
* Create the main frame of the application.
|
||||
*
|
||||
* @param title
|
||||
* @param commUDP
|
||||
* @param portServerTCP
|
||||
* @param sqlManager
|
||||
* @param vueConnexion
|
||||
* @throws IOException
|
||||
*/
|
||||
public VueStandard(String title, CommunicationUDP commUDP, int portServerTCP, SQLiteManager sqlManager,
|
||||
VueConnexion vueConnexion) throws IOException {
|
||||
super(title);
|
||||
|
||||
//An array to keep tracks of the tabbed pane's close buttons
|
||||
this.tabButtons = new ArrayList<JButton>();
|
||||
//An array to keep tracks of the sessions' view
|
||||
this.sessions = new ArrayList<VueSession>();
|
||||
this.c = new ControleurStandard(this, commUDP, portServerTCP, sqlManager, vueConnexion);
|
||||
this.c.init();
|
||||
|
@ -63,12 +76,13 @@ public class VueStandard extends Vue {
|
|||
|
||||
JPanel left = new JPanel(new BorderLayout());
|
||||
|
||||
// -----------Tabbed Pane for the session's panels -----------//
|
||||
this.zoneSessions = new JTabbedPane();
|
||||
this.zoneSessions.setTabPlacement(JTabbedPane.BOTTOM);
|
||||
|
||||
this.zoneSessions.setPreferredSize(new Dimension(600, 600));
|
||||
|
||||
// --------Panel haut pseudo--------//
|
||||
// --------Panel up left pseudo--------//
|
||||
JPanel self = new JPanel(new FlowLayout());
|
||||
|
||||
this.pseudoSelf = new JTextField(Utilisateur.getSelf().getPseudo());
|
||||
|
@ -101,7 +115,7 @@ public class VueStandard extends Vue {
|
|||
}
|
||||
});
|
||||
|
||||
// --------Panel milieu liste utilisateurs--------//
|
||||
// --------Panel mid left userlist--------//
|
||||
this.activeUsersList = new JList<String>(this.userList);
|
||||
this.activeUsersList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
this.activeUsersList.setLayoutOrientation(JList.VERTICAL);
|
||||
|
@ -116,19 +130,19 @@ public class VueStandard extends Vue {
|
|||
listScroller.setBorder(BorderFactory.createCompoundBorder(
|
||||
BorderFactory.createTitledBorder("Utilisateurs Actifs"), BorderFactory.createEmptyBorder(5, 2, 2, 2)));
|
||||
|
||||
// --------Panel bas deconnexion--------//
|
||||
// --------Panel down left log off--------//
|
||||
JPanel deconnexion = new JPanel(new GridLayout(1, 2));
|
||||
|
||||
this.seDeconnecter = new JButton("Se Déconnecter");
|
||||
this.seDeconnecter.addActionListener(this.c);
|
||||
deconnexion.add(this.seDeconnecter);
|
||||
|
||||
if(Utilisateur.getSelf().getId() == "admin") {
|
||||
|
||||
if (Utilisateur.getSelf().getId() == "admin") {
|
||||
JButton addNewUser = new JButton("Ajouter un nouvel utilisateur");
|
||||
deconnexion.add(addNewUser);
|
||||
}
|
||||
|
||||
// --------Ajout à la vue--------//
|
||||
// --------Add the panels to the frame--------//
|
||||
left.add(self, BorderLayout.PAGE_START);
|
||||
left.add(listScroller, BorderLayout.CENTER);
|
||||
left.add(deconnexion, BorderLayout.PAGE_END);
|
||||
|
@ -186,36 +200,73 @@ public class VueStandard extends Vue {
|
|||
|
||||
// ------------ SETTERS -------------//
|
||||
|
||||
|
||||
/**
|
||||
* Set the list of active users to be displayed on the view. First remove the
|
||||
* old list.
|
||||
*
|
||||
* @param users The list of active users.
|
||||
*/
|
||||
protected void setActiveUsersList(ArrayList<String> users) {
|
||||
this.removeAllUsers();
|
||||
this.userList.addAll(users);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the pseudo displayed on the view with the given pseudo.
|
||||
*
|
||||
* @param pseudo
|
||||
*/
|
||||
protected void setDisplayedPseudo(String pseudo) {
|
||||
this.pseudoSelf.setText(pseudo);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set this application's user pseudo.
|
||||
*/
|
||||
public void setPseudoSelf() {
|
||||
this.setDisplayedPseudo(Utilisateur.getSelf().getPseudo());
|
||||
}
|
||||
|
||||
|
||||
// ------------ JOPTIONS -------------//
|
||||
|
||||
/**
|
||||
* Display the dialog box asking confirmation to create a session.
|
||||
*
|
||||
* @param pseudo The other user's pseudo.
|
||||
* @return The chosen value.
|
||||
*/
|
||||
protected int displayJOptionSessionCreation(String pseudo) {
|
||||
return JOptionPane.showConfirmDialog(this, "Voulez vous créer une session avec " + pseudo + " ?",
|
||||
"Confirmation session", JOptionPane.YES_NO_OPTION);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Display the dialog box to reply to a session request.
|
||||
*
|
||||
* @param pseudo The other user's pseudo.
|
||||
* @return The chosen value.
|
||||
*/
|
||||
protected int displayJOptionAskForSession(String pseudo) {
|
||||
return JOptionPane.showConfirmDialog(this, pseudo + " souhaite creer une session avec vous.",
|
||||
"Accepter demande", JOptionPane.YES_NO_OPTION);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Display an informative box with the answer to a session request
|
||||
* @param reponse
|
||||
*/
|
||||
protected void displayJOptionResponse(String reponse) {
|
||||
JOptionPane.showMessageDialog(this, "Demande de session " + reponse);
|
||||
}
|
||||
|
||||
// ------------ TOGGLEBUTTONS -------------//
|
||||
|
||||
// ------------ TOGGLE BUTTONS -------------//
|
||||
|
||||
protected void toggleEditPseudo() {
|
||||
this.pseudoSelf.setEditable(!this.pseudoSelf.isEditable());
|
||||
|
@ -223,20 +274,38 @@ public class VueStandard extends Vue {
|
|||
this.pseudoSelf.setEnabled(!this.pseudoSelf.isEnabled());
|
||||
}
|
||||
|
||||
|
||||
protected void toggleEnableButtonDeconnexion() {
|
||||
this.seDeconnecter.setEnabled(!this.seDeconnecter.isEnabled());
|
||||
}
|
||||
|
||||
|
||||
protected void toggleEnableButtonConnexion() {
|
||||
this.seConnecter.setEnabled(!this.seConnecter.isEnabled());
|
||||
}
|
||||
|
||||
|
||||
// ------------SESSION-------------//
|
||||
|
||||
|
||||
/**
|
||||
* Check if an object belongs to tabButtons.
|
||||
*
|
||||
* @param o
|
||||
* @return
|
||||
*/
|
||||
protected boolean isButtonTab(Object o) {
|
||||
return this.tabButtons.contains(o);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove the tab (the session) of the tabbed pane that corresponds
|
||||
* to the given "close" button.
|
||||
*
|
||||
* @param button
|
||||
* @return
|
||||
*/
|
||||
protected int removeSession(JButton button) {
|
||||
int index = this.tabButtons.indexOf(button);
|
||||
|
||||
|
@ -250,6 +319,13 @@ public class VueStandard extends Vue {
|
|||
return index;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a session on the view with the given pseudo as the tab title.
|
||||
*
|
||||
* @param pseudo
|
||||
* @param session
|
||||
*/
|
||||
protected void addSession(String pseudo, VueSession session) {
|
||||
JPanel tabTitle = new JPanel();
|
||||
|
||||
|
@ -267,7 +343,15 @@ public class VueStandard extends Vue {
|
|||
session.requestFocus();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove the tab (the session) of the tabbed pane that corresponds
|
||||
* to the given session's panel (view).
|
||||
*
|
||||
* @param vue
|
||||
* @return
|
||||
*/
|
||||
protected synchronized int removeSession(VueSession vue) {
|
||||
int index = this.sessions.indexOf(vue);
|
||||
|
||||
|
@ -280,32 +364,35 @@ public class VueStandard extends Vue {
|
|||
return index;
|
||||
}
|
||||
|
||||
|
||||
protected void closeAllSession() {
|
||||
Iterator<VueSession> it = this.sessions.iterator();
|
||||
while(it.hasNext()) {
|
||||
while (it.hasNext()) {
|
||||
VueSession session = it.next();
|
||||
this.zoneSessions.remove(session);
|
||||
session.destroyAll();
|
||||
it.remove();
|
||||
}
|
||||
|
||||
|
||||
this.tabButtons.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ------------ OTHERS -------------//
|
||||
|
||||
protected void removeAllUsers() {
|
||||
this.userList.removeAllElements();
|
||||
}
|
||||
|
||||
|
||||
public void initControleur() throws UnknownHostException, IOException {
|
||||
this.c.init();
|
||||
}
|
||||
|
||||
|
||||
// ------------- PRIVATE CLASSES FOR THE TABS BUTTON -------------//
|
||||
private class TabButton extends JButton {
|
||||
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public TabButton() {
|
||||
|
@ -328,10 +415,12 @@ public class VueStandard extends Vue {
|
|||
setRolloverEnabled(true);
|
||||
}
|
||||
|
||||
|
||||
// we don't want to update UI for this button
|
||||
public void updateUI() {
|
||||
}
|
||||
|
||||
|
||||
// paint the cross
|
||||
protected void paintComponent(Graphics g) {
|
||||
super.paintComponent(g);
|
||||
|
@ -361,6 +450,7 @@ public class VueStandard extends Vue {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public void mouseExited(MouseEvent e) {
|
||||
Component component = e.getComponent();
|
||||
if (component instanceof AbstractButton) {
|
||||
|
|
Loading…
Reference in a new issue