aboutsummaryrefslogtreecommitdiff
path: root/web/pw-server/src/routes
diff options
context:
space:
mode:
Diffstat (limited to 'web/pw-server/src/routes')
-rw-r--r--web/pw-server/src/routes/__layout.svelte39
-rw-r--r--web/pw-server/src/routes/editor.svelte277
-rw-r--r--web/pw-server/src/routes/index.svelte277
3 files changed, 310 insertions, 283 deletions
diff --git a/web/pw-server/src/routes/__layout.svelte b/web/pw-server/src/routes/__layout.svelte
index 53eb265..2267491 100644
--- a/web/pw-server/src/routes/__layout.svelte
+++ b/web/pw-server/src/routes/__layout.svelte
@@ -6,10 +6,17 @@
<div class="outer-container">
<div class="navbar">
- <div class="navbar-main">
- <a href="/">PlanetWars</a>
+ <div class="navbar-left">
+ <div class="navbar-header">
+ <a href="/">PlanetWars</a>
+ </div>
+ <div class="navbar-item">
+ <a href="/editor">Editor</a>
+ </div>
+ </div>
+ <div class="navbar-right">
+ <UserControls />
</div>
- <UserControls />
</div>
<slot />
</div>
@@ -34,13 +41,33 @@
padding: 0 15px;
}
- .navbar-main {
+ .navbar-left {
+ display: flex;
+ }
+
+ .navbar-right {
+ display: flex;
+ }
+
+ .navbar-header {
margin: auto 0;
+ padding-right: 24px;
}
- .navbar-main a {
+ .navbar-header a {
font-size: 20px;
- color: #eee;
+ color: #fff;
+ text-decoration: none;
+ }
+ .navbar-item {
+ margin: auto 0;
+ padding: 0 8px;
+ }
+
+ .navbar-item a {
+ font-size: 14px;
+ color: #fff;
text-decoration: none;
+ font-weight: 600;
}
</style>
diff --git a/web/pw-server/src/routes/editor.svelte b/web/pw-server/src/routes/editor.svelte
new file mode 100644
index 0000000..85c2454
--- /dev/null
+++ b/web/pw-server/src/routes/editor.svelte
@@ -0,0 +1,277 @@
+<script lang="ts">
+ import Visualizer from "$lib/components/Visualizer.svelte";
+ import EditorView from "$lib/components/EditorView.svelte";
+ import { onMount } from "svelte";
+
+ import { DateTime } from "luxon";
+
+ import type { Ace } from "ace-builds";
+ import ace from "ace-builds/src-noconflict/ace?client";
+ import * as AcePythonMode from "ace-builds/src-noconflict/mode-python?client";
+ import { getBotCode, saveBotCode, hasBotCode } from "$lib/bot_code";
+ import { debounce } from "$lib/utils";
+ import SubmitPane from "$lib/components/SubmitPane.svelte";
+ import OutputPane from "$lib/components/OutputPane.svelte";
+ import RulesView from "$lib/components/RulesView.svelte";
+ import Leaderboard from "$lib/components/Leaderboard.svelte";
+
+ enum ViewMode {
+ Editor,
+ MatchVisualizer,
+ Rules,
+ Leaderboard,
+ }
+
+ let matches = [];
+
+ let viewMode = ViewMode.Editor;
+ let selectedMatchId: string | undefined = undefined;
+ let selectedMatchLog: string | undefined = undefined;
+
+ let editSession: Ace.EditSession;
+
+ onMount(() => {
+ if (!hasBotCode()) {
+ viewMode = ViewMode.Rules;
+ }
+ init_editor();
+ });
+
+ function init_editor() {
+ editSession = new ace.EditSession(getBotCode());
+ editSession.setMode(new AcePythonMode.Mode());
+
+ 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));
+ }
+
+ async function onMatchCreated(e: CustomEvent) {
+ const matchData = e.detail["match"];
+ matches.unshift(matchData);
+ matches = matches;
+ await selectMatch(matchData["id"]);
+ }
+
+ async function selectMatch(matchId: string) {
+ selectedMatchId = matchId;
+ selectedMatchLog = null;
+ fetchSelectedMatchLog(matchId);
+
+ viewMode = ViewMode.MatchVisualizer;
+ }
+
+ 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);
+ }
+ }
+
+ 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`, {
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+
+ let log = await res.text();
+ return log;
+ }
+
+ function setViewMode(viewMode_: ViewMode) {
+ selectedMatchId = undefined;
+ selectedMatchLog = undefined;
+ viewMode = viewMode_;
+ }
+
+ function selectRules() {
+ selectedMatchId = undefined;
+ selectedMatchLog = undefined;
+ viewMode = ViewMode.Rules;
+ }
+
+ function formatMatchTimestamp(timestampString: string): string {
+ let timestamp = DateTime.fromISO(timestampString, { zone: "utc" }).toLocal();
+ if (timestamp.startOf("day").equals(DateTime.now().startOf("day"))) {
+ return timestamp.toFormat("HH:mm");
+ } else {
+ return timestamp.toFormat("dd/MM");
+ }
+ }
+
+ $: selectedMatch = matches.find((m) => m["id"] === selectedMatchId);
+</script>
+
+<div class="container">
+ <div class="sidebar-left">
+ <div
+ class="editor-button sidebar-item"
+ class:selected={viewMode === ViewMode.Editor}
+ on:click={() => setViewMode(ViewMode.Editor)}
+ >
+ Editor
+ </div>
+ <div
+ class="rules-button sidebar-item"
+ class:selected={viewMode === ViewMode.Rules}
+ on:click={() => setViewMode(ViewMode.Rules)}
+ >
+ Rules
+ </div>
+ <div
+ class="sidebar-item"
+ class:selected={viewMode === ViewMode.Leaderboard}
+ on:click={() => setViewMode(ViewMode.Leaderboard)}
+ >
+ Leaderboard
+ </div>
+ <div class="sidebar-header">match history</div>
+ <ul class="match-list">
+ {#each matches as match}
+ <li
+ class="match-card sidebar-item"
+ on:click={() => selectMatch(match.id)}
+ class:selected={match.id === selectedMatchId}
+ >
+ <span class="match-timestamp">{formatMatchTimestamp(match.timestamp)}</span>
+ <!-- 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>
+ </li>
+ {/each}
+ </ul>
+ </div>
+ <div class="editor-container">
+ {#if viewMode === ViewMode.MatchVisualizer}
+ <Visualizer matchData={selectedMatch} matchLog={selectedMatchLog} />
+ {:else if viewMode === ViewMode.Editor}
+ <EditorView {editSession} />
+ {:else if viewMode === ViewMode.Rules}
+ <RulesView />
+ {:else if viewMode === ViewMode.Leaderboard}
+ <Leaderboard />
+ {/if}
+ </div>
+ <div class="sidebar-right">
+ {#if viewMode === ViewMode.MatchVisualizer}
+ <OutputPane matchLog={selectedMatchLog} />
+ {:else if viewMode === ViewMode.Editor}
+ <SubmitPane {editSession} on:matchCreated={onMatchCreated} />
+ {/if}
+ </div>
+</div>
+
+<style lang="scss">
+ @import "src/styles/variables.scss";
+
+ .container {
+ display: flex;
+ flex-grow: 1;
+ min-height: 0;
+ }
+
+ .sidebar-left {
+ width: 240px;
+ background-color: $bg-color;
+ display: flex;
+ flex-direction: column;
+ }
+ .sidebar-right {
+ width: 400px;
+ background-color: white;
+ border-left: 1px solid;
+ padding: 0;
+ display: flex;
+ overflow: hidden;
+ }
+ .editor-container {
+ flex-grow: 1;
+ flex-shrink: 1;
+ overflow: hidden;
+ background-color: white;
+ }
+
+ .editor-container {
+ height: 100%;
+ }
+
+ .sidebar-item {
+ color: #eee;
+ padding: 15px;
+ }
+
+ .sidebar-item:hover {
+ background-color: #333;
+ }
+
+ .sidebar-item.selected {
+ background-color: #333;
+ }
+
+ .match-list {
+ list-style: none;
+ color: #eee;
+ padding-top: 15px;
+ overflow-y: scroll;
+ padding-left: 0px;
+ }
+
+ .match-card {
+ padding: 10px 15px;
+ font-size: 11pt;
+ }
+
+ .match-timestamp {
+ color: #ccc;
+ }
+
+ .match-opponent {
+ 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;
+ }
+</style>
diff --git a/web/pw-server/src/routes/index.svelte b/web/pw-server/src/routes/index.svelte
index 85c2454..e69de29 100644
--- a/web/pw-server/src/routes/index.svelte
+++ b/web/pw-server/src/routes/index.svelte
@@ -1,277 +0,0 @@
-<script lang="ts">
- import Visualizer from "$lib/components/Visualizer.svelte";
- import EditorView from "$lib/components/EditorView.svelte";
- import { onMount } from "svelte";
-
- import { DateTime } from "luxon";
-
- import type { Ace } from "ace-builds";
- import ace from "ace-builds/src-noconflict/ace?client";
- import * as AcePythonMode from "ace-builds/src-noconflict/mode-python?client";
- import { getBotCode, saveBotCode, hasBotCode } from "$lib/bot_code";
- import { debounce } from "$lib/utils";
- import SubmitPane from "$lib/components/SubmitPane.svelte";
- import OutputPane from "$lib/components/OutputPane.svelte";
- import RulesView from "$lib/components/RulesView.svelte";
- import Leaderboard from "$lib/components/Leaderboard.svelte";
-
- enum ViewMode {
- Editor,
- MatchVisualizer,
- Rules,
- Leaderboard,
- }
-
- let matches = [];
-
- let viewMode = ViewMode.Editor;
- let selectedMatchId: string | undefined = undefined;
- let selectedMatchLog: string | undefined = undefined;
-
- let editSession: Ace.EditSession;
-
- onMount(() => {
- if (!hasBotCode()) {
- viewMode = ViewMode.Rules;
- }
- init_editor();
- });
-
- function init_editor() {
- editSession = new ace.EditSession(getBotCode());
- editSession.setMode(new AcePythonMode.Mode());
-
- 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));
- }
-
- async function onMatchCreated(e: CustomEvent) {
- const matchData = e.detail["match"];
- matches.unshift(matchData);
- matches = matches;
- await selectMatch(matchData["id"]);
- }
-
- async function selectMatch(matchId: string) {
- selectedMatchId = matchId;
- selectedMatchLog = null;
- fetchSelectedMatchLog(matchId);
-
- viewMode = ViewMode.MatchVisualizer;
- }
-
- 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);
- }
- }
-
- 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`, {
- headers: {
- "Content-Type": "application/json",
- },
- });
-
- let log = await res.text();
- return log;
- }
-
- function setViewMode(viewMode_: ViewMode) {
- selectedMatchId = undefined;
- selectedMatchLog = undefined;
- viewMode = viewMode_;
- }
-
- function selectRules() {
- selectedMatchId = undefined;
- selectedMatchLog = undefined;
- viewMode = ViewMode.Rules;
- }
-
- function formatMatchTimestamp(timestampString: string): string {
- let timestamp = DateTime.fromISO(timestampString, { zone: "utc" }).toLocal();
- if (timestamp.startOf("day").equals(DateTime.now().startOf("day"))) {
- return timestamp.toFormat("HH:mm");
- } else {
- return timestamp.toFormat("dd/MM");
- }
- }
-
- $: selectedMatch = matches.find((m) => m["id"] === selectedMatchId);
-</script>
-
-<div class="container">
- <div class="sidebar-left">
- <div
- class="editor-button sidebar-item"
- class:selected={viewMode === ViewMode.Editor}
- on:click={() => setViewMode(ViewMode.Editor)}
- >
- Editor
- </div>
- <div
- class="rules-button sidebar-item"
- class:selected={viewMode === ViewMode.Rules}
- on:click={() => setViewMode(ViewMode.Rules)}
- >
- Rules
- </div>
- <div
- class="sidebar-item"
- class:selected={viewMode === ViewMode.Leaderboard}
- on:click={() => setViewMode(ViewMode.Leaderboard)}
- >
- Leaderboard
- </div>
- <div class="sidebar-header">match history</div>
- <ul class="match-list">
- {#each matches as match}
- <li
- class="match-card sidebar-item"
- on:click={() => selectMatch(match.id)}
- class:selected={match.id === selectedMatchId}
- >
- <span class="match-timestamp">{formatMatchTimestamp(match.timestamp)}</span>
- <!-- 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>
- </li>
- {/each}
- </ul>
- </div>
- <div class="editor-container">
- {#if viewMode === ViewMode.MatchVisualizer}
- <Visualizer matchData={selectedMatch} matchLog={selectedMatchLog} />
- {:else if viewMode === ViewMode.Editor}
- <EditorView {editSession} />
- {:else if viewMode === ViewMode.Rules}
- <RulesView />
- {:else if viewMode === ViewMode.Leaderboard}
- <Leaderboard />
- {/if}
- </div>
- <div class="sidebar-right">
- {#if viewMode === ViewMode.MatchVisualizer}
- <OutputPane matchLog={selectedMatchLog} />
- {:else if viewMode === ViewMode.Editor}
- <SubmitPane {editSession} on:matchCreated={onMatchCreated} />
- {/if}
- </div>
-</div>
-
-<style lang="scss">
- @import "src/styles/variables.scss";
-
- .container {
- display: flex;
- flex-grow: 1;
- min-height: 0;
- }
-
- .sidebar-left {
- width: 240px;
- background-color: $bg-color;
- display: flex;
- flex-direction: column;
- }
- .sidebar-right {
- width: 400px;
- background-color: white;
- border-left: 1px solid;
- padding: 0;
- display: flex;
- overflow: hidden;
- }
- .editor-container {
- flex-grow: 1;
- flex-shrink: 1;
- overflow: hidden;
- background-color: white;
- }
-
- .editor-container {
- height: 100%;
- }
-
- .sidebar-item {
- color: #eee;
- padding: 15px;
- }
-
- .sidebar-item:hover {
- background-color: #333;
- }
-
- .sidebar-item.selected {
- background-color: #333;
- }
-
- .match-list {
- list-style: none;
- color: #eee;
- padding-top: 15px;
- overflow-y: scroll;
- padding-left: 0px;
- }
-
- .match-card {
- padding: 10px 15px;
- font-size: 11pt;
- }
-
- .match-timestamp {
- color: #ccc;
- }
-
- .match-opponent {
- 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;
- }
-</style>