Message types and server (#44)

* message type and handling

* deleted web-content and fixed bug

* edited main page
This commit is contained in:
Valentin Heiserer
2024-04-23 21:54:31 +02:00
committed by GitHub
parent cab2d36f48
commit a0a1cfaa4a
79 changed files with 1737 additions and 637 deletions

View File

@@ -41,7 +41,7 @@
</descriptorRefs>
<archive>
<manifest>
<mainClass>org.schafkopf.BackendServer</mainClass>
<mainClass>org.schafkopf.SchafkopfClient</mainClass>
</manifest>
</archive>
</configuration>

View File

@@ -1,63 +1,44 @@
package org.schafkopf;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import jakarta.servlet.DispatcherType;
import java.awt.Desktop;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.time.Duration;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlets.CrossOriginFilter;
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
import org.schafkopf.cardreader.UsbCardReader;
import org.schafkopf.player.BotPlayer;
import org.schafkopf.player.LocalPlayer;
import org.schafkopf.player.Player;
import org.schafkopf.SchafkopfMessage.SchafkopfBaseMessage;
/** Main Class that represents the Backend Server. */
/**
* Main Class that represents the Backend Server.
*/
public class BackendServer implements MessageSender {
private final Server server;
private final ServerConnector connector;
private CountDownLatch nfcLatch = new CountDownLatch(1);
private Boolean readingMode = false;
private String uidString = "";
/** Important variables. */
public final Schafkopf schafkopfGame;
private final Server server;
private final List<FrontendEndpoint> frontendEndpoints = new ArrayList<>();
private DedicatedServerConnection dedicatedServerConnection;
/** Creates an Instance of the Backend Server. */
public BackendServer() throws URISyntaxException, IOException {
/**
* Creates an Instance of the Backend Server.
*/
public BackendServer(String hostName, int port, boolean openFrontend,
MessageListener messageListener) throws Exception {
server = new Server();
InetSocketAddress address = new InetSocketAddress("localhost", 8080);
connector = new ServerConnector(server);
connector.setHost(address.getHostName());
connector.setPort(address.getPort());
ServerConnector connector = new ServerConnector(server);
connector.setHost(hostName);
connector.setPort(port);
server.addConnector(connector);
schafkopfGame = new Schafkopf(new Player[]{new BotPlayer(), new LocalPlayer(this),
new LocalPlayer(this),
new LocalPlayer(this)}, this);
new UsbCardReader(this);
// Setup the basic application "context" for this application at "/"
// This is also known as the handler tree (in jetty speak)
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
@@ -71,11 +52,9 @@ public class BackendServer implements MessageSender {
if (webContentUrl == null) {
throw new RuntimeException("Unable to find 'web-content' directory");
}
String webContentPath = webContentUrl.toExternalForm();
context.setResourceBase(webContentPath);
System.out.println("Web Content Path: " + webContentPath);
context.addServlet(new ServletHolder("frontend", DefaultServlet.class), "/");
// Configure specific websocket behavior
JettyWebSocketServletContainerInitializer.configure(
@@ -85,83 +64,25 @@ public class BackendServer implements MessageSender {
wsContainer.setMaxTextMessageSize(65535);
wsContainer.setIdleTimeout(Duration.ofDays(300000));
// Add websockets
wsContainer.addMapping("/schafkopf-events/*", new FrontendEndpointCreator(this));
wsContainer.addMapping("/schafkopf-events/*",
new FrontendEndpointCreator(this, messageListener));
});
// Integrate simple HTTP server
startHttpServer();
URI uri = new URI("http://localhost:8081"); // Replace with your target URL
Desktop.getDesktop().browse(uri);
startDedicatedServerConnectionThread();
}
private void startDedicatedServerConnectionThread() {
dedicatedServerConnection = new DedicatedServerConnection(this);
dedicatedServerConnection.connect();
}
private void startHttpServer() {
try {
HttpServer httpServer = HttpServer.create(new InetSocketAddress(8081), 0);
httpServer.createContext("/", new MyHandler());
httpServer.setExecutor(null);
httpServer.start();
System.out.println("HTTP Server started on port 8081");
} catch (IOException e) {
e.printStackTrace();
if (openFrontend) {
URI uri = new URI("http://" + hostName + ":" + port); // Replace with your target URL
Desktop.getDesktop().browse(uri);
}
}
static class MyHandler implements HttpHandler {
@Override
public void handle(HttpExchange t) throws IOException {
String path = t.getRequestURI().getPath();
if ("/".equals(path)) {
path = "/index.html"; // default to index.html
}
// Start the server in a separate thread
Thread serverThread = new Thread(() -> {
try {
InputStream fileStream =
getClass().getClassLoader().getResourceAsStream("web-content" + path);
if (fileStream != null) {
byte[] data = fileStream.readAllBytes();
// Set the appropriate MIME type for JavaScript files
String mimeType = getMimeType(path);
t.getResponseHeaders().set("Content-Type", mimeType);
t.sendResponseHeaders(200, data.length);
try (OutputStream os = t.getResponseBody()) {
os.write(data);
}
} else {
// File not found
t.sendResponseHeaders(404, -1);
}
} catch (IOException e) {
server.start();
server.join(); // Wait for server to finish execution
} catch (Exception e) {
e.printStackTrace();
t.sendResponseHeaders(500, -1);
}
}
private String getMimeType(String path) {
if (path.endsWith(".js")) {
return "application/javascript";
} else if (path.endsWith(".html")) {
return "text/html";
} else if (path.endsWith(".css")) {
return "text/css";
}
// Add more MIME types as needed
return "application/octet-stream";
}
}
/** The main entrypoint of the Application. */
public static void main(String[] args) throws Exception {
BackendServer server = new BackendServer();
server.start();
server.join();
});
serverThread.start();
}
private void configureCors(ServletContextHandler context) {
@@ -205,39 +126,14 @@ public class BackendServer implements MessageSender {
}
}
/** method to call to wait for NFC input. */
public String waitForCardScan() throws InterruptedException {
this.readingMode = true;
nfcLatch.await();
Thread.sleep(20);
this.readingMode = false;
nfcLatch = new CountDownLatch(1);
return this.uidString;
}
/**
* checks uid of scanned card and do nothing if Server is not in reading mode.
*
* @param uidString uid to check.
*/
public void nfcGelesen(String uidString) {
if (this.uidString.equals(uidString)) {
return;
}
if (!this.readingMode) {
return;
}
this.uidString = uidString;
nfcLatch.countDown();
}
public void startDedicatedServerGame() {
dedicatedServerConnection.start();
}
@Override
public void sendMessage(String message) {
public void sendMessage(SchafkopfBaseMessage message) {
sendMessageToAllFrontendEndpoints(
message.getBaseMessage().toString());
}
public void sendMessageTest(String message) {
sendMessageToAllFrontendEndpoints(message);
}
}

View File

@@ -1,6 +1,8 @@
package org.schafkopf;
import com.google.gson.JsonObject;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.CountDownLatch;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
@@ -10,30 +12,58 @@ import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.schafkopf.SchafkopfMessage.SchafkopfBaseMessage;
import org.schafkopf.SchafkopfMessage.SchafkopfMessageOrigin;
import org.schafkopf.SchafkopfMessage.SchafkopfMessageType;
/** Main Class that represents the Backend Server. */
/**
* Main Class that represents the Backend Server.
*/
@WebSocket
public class DedicatedServerConnection {
public class DedicatedServerConnection implements MessageSender {
private final MessageSender messageSender;
private final MessageListener messageListener;
private final CountDownLatch closeLatch;
private final CountDownLatch connectionLatch;
private static Session session;
public DedicatedServerConnection(MessageSender messageSender) {
this.messageSender = messageSender;
/**
* Class that represents one Frontend Connection.
*/
public DedicatedServerConnection(String address, MessageListener messageListener) {
URI uri = null;
try {
uri = new URI(address);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
this.messageListener = messageListener;
this.closeLatch = new CountDownLatch(1);
this.connectionLatch = new CountDownLatch(1);
String host = uri.getHost();
int port = uri.getPort();
connect("ws://" + host + ":" + port);
try {
connectionLatch.await(); // Wait until the connection is established
} catch (InterruptedException e) {
System.err.println("Error waiting for connection: " + e.getMessage());
}
}
/**
* Class that represents one Frontend Connection.
*/
@OnWebSocketConnect
public void onConnect(Session session) {
this.session = session;
System.out.println("Connected to server.");
connectionLatch.countDown();
}
@OnWebSocketMessage
public void onMessage(String message) {
messageSender.sendMessage(message);
System.out.println("Received message from server: " + message);
messageListener.receiveMessage(message);
}
@OnWebSocketClose
@@ -47,10 +77,14 @@ public class DedicatedServerConnection {
System.err.println("Error occurred: " + cause.getMessage());
}
/** Main Class that represents the Backend Server. */
public static void sendMessage(String message) {
/**
* Main Class that represents the Backend Server.
*/
@Override
public void sendMessage(SchafkopfBaseMessage message) {
try {
session.getRemote().sendString(message);
session.getRemote().sendString(
new SchafkopfMessage(SchafkopfMessageOrigin.BACKEND, message).getMessageAsString());
System.out.println("Sent message to server: " + message);
} catch (Exception e) {
System.err.println("Error sending message: " + e.getMessage());
@@ -61,15 +95,15 @@ public class DedicatedServerConnection {
closeLatch.await();
}
/** Main Class that represents the Backend Server. */
public void connect() {
/**
* Main Class that represents the Backend Server.
*/
public void connect(String serverUri) {
Thread connectionThread = new Thread(() -> {
try {
String serverUri = "ws://localhost:8085/";
WebSocketClient client = new WebSocketClient();
try {
client.start();
//DedicatedServerConnection socketClient = new DedicatedServerConnection();
HeartbeatSender heartbeatSender = new HeartbeatSender(this);
heartbeatSender.start(); // Start sending heartbeat messages
URI uri = new URI(serverUri);
@@ -95,7 +129,23 @@ public class DedicatedServerConnection {
start();
}
public static void start() {
sendMessage("START_GAME");
/**
* Class that represents one Frontend Connection.
*/
public void start() {
JsonObject messageObject = new JsonObject();
messageObject.addProperty("message_type", "START_GAME");
sendMessage(new SchafkopfBaseMessage(SchafkopfMessageType.START_GAME));
}
/**
* Class that represents one Frontend Connection.
*/
public void join() {
JsonObject messageObject = new JsonObject();
messageObject.addProperty("message_type", "JOIN_GAME");
sendMessage(new SchafkopfBaseMessage(SchafkopfMessageType.JOIN_GAME));
}
}

View File

@@ -5,16 +5,27 @@ import java.util.concurrent.CountDownLatch;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
/** Class that represents one Frontend Connection. */
/**
* Class that represents one Frontend Connection.
*/
public class FrontendEndpoint extends WebSocketAdapter {
private final CountDownLatch closureLatch = new CountDownLatch(1);
private BackendServer backendServer;
private final MessageListener messageListener;
public FrontendEndpoint(BackendServer backendServer) {
/**
* Class that represents one Frontend Connection.
*/
public FrontendEndpoint(BackendServer backendServer, MessageListener messageListener) {
this.messageListener = messageListener;
this.backendServer = backendServer;
System.out.println("new FrontendEndpoint");
}
/**
* Class that represents one Frontend Connection.
*/
@Override
public void onWebSocketConnect(Session session) {
super.onWebSocketConnect(session);
@@ -27,20 +38,9 @@ public class FrontendEndpoint extends WebSocketAdapter {
@Override
public void onWebSocketText(String message) {
super.onWebSocketText(message);
System.out.println("Received TEXT message:" + message);
if (message.contains("startsimulation")) {
backendServer.schafkopfGame.startGame();
if (messageListener != null) {
messageListener.receiveMessage(message); // Notify the listener
}
if (message.contains("stopsimulation")) {
backendServer.schafkopfGame.stopGame();
}
if (message.contains("startdedicated")) {
backendServer.startDedicatedServerGame();
}
}
@Override
@@ -59,7 +59,9 @@ public class FrontendEndpoint extends WebSocketAdapter {
cause.printStackTrace(System.err);
}
/** send a Message to the connected FrontEnd. */
/**
* send a Message to the connected FrontEnd.
*/
public void sendMessage(String message) {
try {
getRemote().sendString(message);

View File

@@ -9,8 +9,10 @@ import org.eclipse.jetty.websocket.server.JettyWebSocketCreator;
*/
public class FrontendEndpointCreator implements JettyWebSocketCreator {
private BackendServer backendServer;
private final MessageListener messageListener;
public FrontendEndpointCreator(BackendServer backendServer) {
public FrontendEndpointCreator(BackendServer backendServer, MessageListener messageListener) {
this.messageListener = messageListener;
this.backendServer = backendServer;
}
@@ -18,6 +20,6 @@ public class FrontendEndpointCreator implements JettyWebSocketCreator {
public Object createWebSocket(
JettyServerUpgradeRequest jettyServerUpgradeRequest,
JettyServerUpgradeResponse jettyServerUpgradeResponse) {
return new FrontendEndpoint(this.backendServer);
return new FrontendEndpoint(this.backendServer, messageListener);
}
}

View File

@@ -2,9 +2,13 @@ package org.schafkopf;
import java.util.Timer;
import java.util.TimerTask;
import org.schafkopf.SchafkopfMessage.SchafkopfBaseMessage;
/** Creates an Instance of the Backend Server. */
/**
* Creates an Instance of the Backend Server.
*/
public class HeartbeatSender {
private static final int HEARTBEAT_INTERVAL = 15000; // 1 minute
private final DedicatedServerConnection client;
@@ -13,13 +17,16 @@ public class HeartbeatSender {
this.client = client;
}
/** Creates an Instance of the Backend Server. */
/**
* Creates an Instance of the Backend Server.
*/
public void start() {
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
client.sendMessage("HEARTBEAT SYN"); // Send a heartbeat message
client.sendMessage(
new SchafkopfBaseMessage(SchafkopfMessage.SchafkopfMessageType.HEARTBEAT_SYN));
}
}, HEARTBEAT_INTERVAL, HEARTBEAT_INTERVAL);
}

View File

@@ -0,0 +1,81 @@
package org.schafkopf;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.schafkopf.SchafkopfMessage.SchafkopfBaseMessage;
import org.schafkopf.SchafkopfMessage.SchafkopfMessageOrigin;
import org.schafkopf.SchafkopfMessage.SchafkopfMessageType;
/**
* Class that represents one Frontend Connection.
*/
public class SchafkopfClient implements MessageListener {
private BackendServer backendServer;
private DedicatedServerConnection dedicatedServerConnection;
/**
* Class that represents one Frontend Connection.
*/
public SchafkopfClient() throws Exception {
this.backendServer = new BackendServer("localhost", 8080, true, this);
System.out.println("Client started.");
}
public static void main(String[] args) throws Exception {
new SchafkopfClient();
}
@Override
public void receiveMessage(String jsonMessage) {
Gson gson = new Gson();
JsonObject jsonObject = gson.fromJson(jsonMessage, JsonObject.class);
// Check if the origin is "frontend"
String origin = jsonObject.get("origin").getAsString();
if (SchafkopfMessageOrigin.FRONTEND.toString().equals(origin)) {
JsonObject message = jsonObject.getAsJsonObject("message");
JsonObject content = message.getAsJsonObject("content");
String messageType = message.get("message_type").getAsString();
if (SchafkopfMessageType.REQUEST_SERVER_CONNECTION.toString().equals(messageType)) {
dedicatedServerConnection = new DedicatedServerConnection(
content.get("serverAddress").getAsString(),
this);
} else if ("JOIN_GAME".equals(messageType)) {
dedicatedServerConnection.join();
} else if ("START_DEDICATED_GAME".equals(messageType)) {
dedicatedServerConnection.start();
} else if (SchafkopfMessageType.PLAYER_CARD.toString().equals(messageType)) {
dedicatedServerConnection.sendMessage(
new SchafkopfBaseMessage(SchafkopfMessageType.PLAYER_CARD, content));
}
System.out.println("Received message from frontend server: " + jsonMessage);
} else if (SchafkopfMessageOrigin.DEDICATED_SERVER.toString().equals(origin)) {
JsonObject message = jsonObject.getAsJsonObject("message");
JsonObject content = message.getAsJsonObject("content");
String messageType = message.get("message_type").getAsString();
if (SchafkopfMessageType.GET_CARD_ONLINE_PLAYER.toString().equals(messageType)) {
System.out.println("Received get_card_online_player message from dedicated server.");
} else if (SchafkopfMessageType.GAME_STATE.toString().equals(messageType)) {
backendServer.sendMessage(
new SchafkopfBaseMessage(SchafkopfMessage.SchafkopfMessageType.GAME_STATE, content));
} else if (SchafkopfMessageType.ONLINE_PLAYER_HAND.toString().equals(messageType)) {
backendServer.sendMessage(
new SchafkopfBaseMessage(SchafkopfMessage.SchafkopfMessageType.ONLINE_PLAYER_HAND,
content));
} else if (SchafkopfMessageType.SERVER_CONNECTION_SUCCESSFUL.toString().equals(messageType)) {
System.out.println("Received server_connection_successful message from dedicated server.");
backendServer.sendMessage(new SchafkopfBaseMessage(
SchafkopfMessage.SchafkopfMessageType.SERVER_CONNECTION_SUCCESSFUL));
} else if (SchafkopfMessageType.HEARTBEAT_ACK.toString().equals(messageType)) {
return;
}
System.out.println("Received message from dedicated server: " + jsonMessage);
}
}
}

View File

@@ -1,13 +1,42 @@
package org.schafkopf.cardreader;
import org.schafkopf.BackendServer;
import java.util.concurrent.CountDownLatch;
/** Class that represents one Card Reader. */
public abstract class CardReader {
protected static BackendServer server;
private CountDownLatch nfcLatch = new CountDownLatch(1);
private Boolean readingMode = false;
private String uidString = "";
public CardReader(BackendServer server) {
this.server = server;
public CardReader() {
}
/** method to call to wait for NFC input. */
public String waitForCardScan() throws InterruptedException {
this.readingMode = true;
nfcLatch.await();
Thread.sleep(20);
this.readingMode = false;
nfcLatch = new CountDownLatch(1);
return this.uidString;
}
/**
* checks uid of scanned card and do nothing if Server is not in reading mode.
*
* @param uidString uid to check.
*/
public void nfcGelesen(String uidString) {
if (this.uidString.equals(uidString)) {
return;
}
if (!this.readingMode) {
return;
}
this.uidString = uidString;
nfcLatch.countDown();
}
}

View File

@@ -16,10 +16,8 @@ public class UsbCardReader extends CardReader {
/**
* Creates an Instance of the KartenLeser.
*
* @param server Backend Server to call methods on.
*/
public UsbCardReader(BackendServer server) {
super(server);
public UsbCardReader() {
new Thread(this::run).start();
}
@@ -80,7 +78,7 @@ public class UsbCardReader extends CardReader {
String data = new String(buffer, 0, bytesRead, "UTF-8").trim();
// Process the received data
server.nfcGelesen(data);
this.nfcGelesen(data);
}
// Optional: Add a delay to avoid consuming too much CPU

View File

@@ -1,6 +1,7 @@
package org.schafkopf.player;
import org.schafkopf.BackendServer;
import org.schafkopf.cardreader.CardReader;
import org.schafkopf.karte.Karte;
import org.schafkopf.karte.KartenListe;
import org.schafkopf.karte.KartenUtil;
@@ -11,10 +12,10 @@ import org.schafkopf.spielcontroller.SpielController;
*/
public class LocalPlayer extends Player {
private final BackendServer server;
private final CardReader cardReader;
public LocalPlayer(BackendServer server) {
this.server = server;
public LocalPlayer(CardReader cardReader) {
this.cardReader = cardReader;
}
@Override
@@ -27,7 +28,7 @@ public class LocalPlayer extends Player {
String uid = null;
System.out.println("Starte Warten auf Karte");
try {
uid = server.waitForCardScan();
uid = cardReader.waitForCardScan();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 815 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 859 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 971 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 954 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 797 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 603 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 646 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 730 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 768 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 905 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1008 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 870 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 412 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 470 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 498 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 626 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 957 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1010 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 959 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 686 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 600 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 741 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 881 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1009 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1015 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 958 KiB

View File

@@ -1,13 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Vite + Vue + TS</title>
<script type="module" crossorigin src="/assets/index-DZ_vcdmw.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-8PJRhba5.css">
</head>
<body class="bg-zinc-800">
<div id="app"></div>
</body>