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-02 19:36:18 +01:00
|
|
|
|
2022-02-09 20:16:35 +01:00
|
|
|
import { DateTime } from "luxon";
|
|
|
|
|
2022-02-07 20:56:08 +01:00
|
|
|
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-03-14 21:03:39 +01:00
|
|
|
enum ViewMode {
|
|
|
|
Editor,
|
|
|
|
MatchVisualizer,
|
|
|
|
}
|
|
|
|
|
2022-02-05 16:48:08 +01:00
|
|
|
let matches = [];
|
|
|
|
|
2022-03-14 21:03:39 +01:00
|
|
|
let viewMode = ViewMode.Editor;
|
2022-02-05 16:48:08 +01:00
|
|
|
let selectedMatchId: string | undefined = undefined;
|
|
|
|
let selectedMatchLog: string | undefined = undefined;
|
2022-02-02 23:15:55 +01:00
|
|
|
|
2022-02-07 20:56:08 +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-04 22:41:05 +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());
|
2022-02-07 20:56:08 +01:00
|
|
|
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-06 16:37:11 +01:00
|
|
|
async function onMatchCreated(e: CustomEvent) {
|
|
|
|
const matchData = e.detail["match"];
|
2022-02-22 19:44:29 +01:00
|
|
|
matches.unshift(matchData);
|
2022-02-05 16:48:08 +01:00
|
|
|
matches = matches;
|
2022-02-23 23:29:14 +01:00
|
|
|
await selectMatch(matchData["id"]);
|
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);
|
2022-03-14 21:03:39 +01:00
|
|
|
|
|
|
|
viewMode = ViewMode.MatchVisualizer;
|
2022-02-22 19:44:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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-03-14 21:03:39 +01:00
|
|
|
viewMode = ViewMode.Editor;
|
2022-01-30 23:34:21 +01:00
|
|
|
}
|
2022-02-09 20:16:35 +01:00
|
|
|
|
|
|
|
function formatMatchTimestamp(timestampString: string): string {
|
2022-03-14 21:03:39 +01:00
|
|
|
let timestamp = DateTime.fromISO(timestampString, { zone: "utc" }).toLocal();
|
2022-02-09 20:16:35 +01:00
|
|
|
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-03-14 21:03:39 +01:00
|
|
|
class:selected={viewMode === ViewMode.Editor}
|
2022-02-05 16:48:08 +01:00
|
|
|
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>
|
2022-03-11 00:47:14 +01:00
|
|
|
<!-- hex is hardcoded for now, don't show map name -->
|
|
|
|
<!-- <span class="match-mapname">hex</span> -->
|
|
|
|
<!-- ugly temporary hardcode -->
|
|
|
|
<span class="match-opponent">{match["players"][1]["bot_name"]}</span>
|
2022-02-05 16:48:08 +01:00
|
|
|
</li>
|
|
|
|
{/each}
|
|
|
|
</ul>
|
|
|
|
</div>
|
|
|
|
<div class="editor-container">
|
2022-03-14 21:03:39 +01:00
|
|
|
{#if viewMode === ViewMode.MatchVisualizer}
|
2022-02-05 16:48:08 +01:00
|
|
|
<Visualizer matchLog={selectedMatchLog} />
|
2022-03-14 21:03:39 +01:00
|
|
|
{:else if viewMode === ViewMode.Editor}
|
2022-02-07 21:36:54 +01:00
|
|
|
<EditorView {editSession} />
|
2022-02-05 16:48:08 +01:00
|
|
|
{/if}
|
|
|
|
</div>
|
|
|
|
<div class="sidebar-right">
|
2022-03-14 21:03:39 +01:00
|
|
|
{#if viewMode === ViewMode.MatchVisualizer}
|
2022-02-23 22:30:36 +01:00
|
|
|
<OutputPane matchLog={selectedMatchLog} />
|
2022-03-14 21:03:39 +01:00
|
|
|
{:else if viewMode === ViewMode.Editor}
|
2022-03-06 16:37:11 +01:00
|
|
|
<SubmitPane {editSession} on:matchCreated={onMatchCreated} />
|
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;
|
2022-02-07 22:50:02 +01:00
|
|
|
flex-shrink: 0;
|
2022-02-05 16:48:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
.container {
|
|
|
|
display: flex;
|
|
|
|
flex-grow: 1;
|
2022-02-07 22:50:02 +01:00
|
|
|
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;
|
2022-02-07 22:50:02 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-03-11 00:47:14 +01:00
|
|
|
.match-opponent {
|
|
|
|
padding: 0 0.5em;
|
|
|
|
}
|
|
|
|
|
2022-02-09 20:16:35 +01:00
|
|
|
.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>
|