planetwars.dev/web/pw-server/src/routes/index.svelte

282 lines
6.4 KiB
Svelte
Raw Normal View History

2022-01-30 23:34:21 +01:00
<script lang="ts">
2022-02-05 16:48:08 +01:00
import Visualizer from "$lib/components/Visualizer.svelte";
2022-02-07 21:36:54 +01:00
import EditorView from "$lib/components/EditorView.svelte";
2022-02-02 23:15:55 +01:00
import { onMount } from "svelte";
2022-02-05 16:48:08 +01:00
import "./style.css";
2022-02-09 20:16:35 +01:00
import { DateTime } from "luxon";
import type { Ace } from "ace-builds";
2022-02-07 21:36:54 +01:00
import ace from "ace-builds/src-noconflict/ace?client";
import * as AcePythonMode from "ace-builds/src-noconflict/mode-python?client";
2022-02-22 18:48:59 +01:00
import { getBotCode, saveBotCode } from "$lib/bot_code";
import { debounce } from "$lib/utils";
2022-02-23 22:30:36 +01:00
import SubmitPane from "$lib/components/SubmitPane.svelte";
import OutputPane from "$lib/components/OutputPane.svelte";
2022-02-21 21:00:05 +01:00
2022-02-05 16:48:08 +01:00
let matches = [];
let selectedMatchId: string | undefined = undefined;
let selectedMatchLog: string | undefined = undefined;
2022-02-02 23:15:55 +01:00
let editSession: Ace.EditSession;
2022-02-07 21:36:54 +01:00
onMount(() => {
init_editor();
2022-02-05 16:48:08 +01:00
});
2022-02-07 21:36:54 +01:00
function init_editor() {
2022-02-22 18:48:59 +01:00
editSession = new ace.EditSession(getBotCode());
editSession.setMode(new AcePythonMode.Mode());
2022-02-22 18:48:59 +01:00
const saveCode = () => {
const code = editSession.getDocument().getValue();
saveBotCode(code);
};
// cast to any because the type annotations are wrong here
(editSession as any).on("change", debounce(saveCode, 2000));
2022-02-05 16:48:08 +01:00
}
2022-01-30 23:34:21 +01:00
2022-03-05 12:10:41 +01:00
async function submitBot(e: CustomEvent) {
console.log(e.detail);
2022-01-30 23:34:21 +01:00
let response = await fetch("/api/submit_bot", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
code: editSession.getDocument().getValue(),
2022-03-05 12:10:41 +01:00
opponent_name: e.detail["opponentName"],
2022-01-30 23:34:21 +01:00
}),
});
if (!response.ok) {
throw Error(response.statusText);
}
let responseData = await response.json();
2022-02-05 16:48:08 +01:00
2022-02-09 20:16:35 +01:00
let matchData = responseData["match"];
2022-02-22 19:44:29 +01:00
matches.unshift(matchData);
2022-02-05 16:48:08 +01:00
matches = matches;
await selectMatch(matchData["id"]);
2022-02-05 16:48:08 +01:00
}
2022-03-04 20:23:58 +01:00
async function saveBot(e: CustomEvent) {
let response = await fetch("/api/save_bot", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
bot_name: e.detail["botName"],
code: editSession.getDocument().getValue(),
}),
});
}
2022-02-05 16:48:08 +01:00
async function selectMatch(matchId: string) {
selectedMatchId = matchId;
2022-02-22 19:44:29 +01:00
selectedMatchLog = null;
fetchSelectedMatchLog(matchId);
}
async function fetchSelectedMatchLog(matchId: string) {
if (matchId !== selectedMatchId) {
return;
}
let matchLog = await getMatchLog(matchId);
if (matchLog) {
selectedMatchLog = matchLog;
} else {
// try again in 1 second
setTimeout(fetchSelectedMatchLog, 1000, matchId);
}
2022-02-05 16:48:08 +01:00
}
2022-02-17 17:11:16 +01:00
async function getMatchData(matchId: string) {
let response = await fetch(`/api/matches/${matchId}`, {
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw Error(response.statusText);
}
let matchData = await response.json();
return matchData;
}
async function getMatchLog(matchId: string) {
const matchData = await getMatchData(matchId);
console.log(matchData);
if (matchData["state"] !== "Finished") {
// log is not available yet
return null;
}
const res = await fetch(`/api/matches/${matchId}/log`, {
2022-02-05 16:48:08 +01:00
headers: {
"Content-Type": "application/json",
},
});
let log = await res.text();
return log;
}
function selectEditor() {
selectedMatchId = undefined;
selectedMatchLog = undefined;
2022-01-30 23:34:21 +01:00
}
2022-02-09 20:16:35 +01:00
function formatMatchTimestamp(timestampString: string): string {
let timestamp = DateTime.fromISO(timestampString);
if (timestamp.startOf("day").equals(DateTime.now().startOf("day"))) {
return timestamp.toFormat("HH:mm");
} else {
return timestamp.toFormat("dd/MM");
}
}
2022-01-30 23:34:21 +01:00
</script>
2022-02-05 16:48:08 +01:00
<div class="outer-container">
<div class="top-bar" />
<div class="container">
<div class="sidebar-left">
<div
2022-02-09 20:16:35 +01:00
class="editor-button sidebar-item"
2022-02-05 16:48:08 +01:00
class:selected={selectedMatchId === undefined}
on:click={selectEditor}
>
Editor
</div>
2022-02-09 20:16:35 +01:00
<div class="sidebar-header">match history</div>
2022-02-05 16:48:08 +01:00
<ul class="match-list">
{#each matches as match}
2022-02-07 21:36:54 +01:00
<li
class="match-card sidebar-item"
2022-02-09 20:16:35 +01:00
on:click={() => selectMatch(match.id)}
class:selected={match.id === selectedMatchId}
2022-02-07 21:36:54 +01:00
>
2022-02-09 20:16:35 +01:00
<span class="match-timestamp">{formatMatchTimestamp(match.timestamp)}</span>
<!-- hardcode hex for now, maps are not yet implemented -->
<span class="match-mapname">hex</span>
2022-02-05 16:48:08 +01:00
</li>
{/each}
</ul>
</div>
<div class="editor-container">
{#if selectedMatchId}
2022-02-05 16:48:08 +01:00
<Visualizer matchLog={selectedMatchLog} />
{:else}
2022-02-07 21:36:54 +01:00
<EditorView {editSession} />
2022-02-05 16:48:08 +01:00
{/if}
</div>
<div class="sidebar-right">
{#if selectedMatchId}
2022-02-23 22:30:36 +01:00
<OutputPane matchLog={selectedMatchLog} />
{:else}
2022-03-04 20:23:58 +01:00
<SubmitPane on:submitBot={submitBot} on:saveBot={saveBot} />
2022-02-23 22:30:36 +01:00
{/if}
2022-02-05 16:48:08 +01:00
</div>
</div>
</div>
<style lang="scss">
$bg-color: rgb(41, 41, 41);
.outer-container {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
}
.top-bar {
height: 40px;
background-color: $bg-color;
border-bottom: 1px solid;
flex-shrink: 0;
2022-02-05 16:48:08 +01:00
}
.container {
display: flex;
flex-grow: 1;
min-height: 0;
2022-02-05 16:48:08 +01:00
}
.sidebar-left {
width: 240px;
background-color: $bg-color;
}
.sidebar-right {
width: 400px;
background-color: white;
border-left: 1px solid;
2022-02-23 22:30:36 +01:00
padding: 0;
display: flex;
2022-03-03 21:38:49 +01:00
overflow: hidden;
2022-02-05 16:48:08 +01:00
}
.editor-container {
flex-grow: 1;
flex-shrink: 1;
2022-02-05 16:48:08 +01:00
overflow: hidden;
}
2022-02-02 23:15:55 +01:00
2022-02-05 16:48:08 +01:00
.editor-container {
height: 100%;
}
2022-02-09 20:16:35 +01:00
.editor-button {
2022-02-05 16:48:08 +01:00
padding: 15px;
2022-02-09 20:16:35 +01:00
}
.sidebar-item {
2022-02-05 16:48:08 +01:00
color: #eee;
}
.sidebar-item:hover {
background-color: #333;
}
.sidebar-item.selected {
background-color: #333;
}
.match-list {
list-style: none;
color: #eee;
2022-02-09 20:16:35 +01:00
padding-top: 15px;
}
.match-card {
padding: 10px 15px;
font-size: 11pt;
}
.match-timestamp {
color: #ccc;
}
.match-mapname {
padding: 0 0.5em;
}
.sidebar-header {
margin-top: 2em;
text-transform: uppercase;
font-weight: 600;
color: rgba(255, 255, 255, 0.7);
font-size: 14px;
font-family: "Open Sans", sans-serif;
padding-left: 14px;
2022-02-02 23:15:55 +01:00
}
</style>