M starkingdoms-client/src/components/Chatbox.svelte => starkingdoms-client/src/components/Chatbox.svelte +63 -15
@@ 1,29 1,77 @@
<script lang="ts">
import Popup from "./ui/Popup.svelte";
+ import { type Packet, PacketType } from "../protocol.ts";
+ import { sendPacket } from "../hub.ts";
+ import { global } from "../globals.js";
+ import { afterUpdate } from "svelte";
+ import TextInput from "./ui/TextInput.svelte";
+
+ interface Message {
+ classname: string;
+ message: string;
+ }
+
+ let messages: Message[] = [];
+ let chatbox: HTMLDivElement | null;
+ let value = "";
export function addMessage(classname: string, message: string) {
- let chatbox = document.getElementById("chatbox")!;
- let should_scroll =
- chatbox!.scrollTop == chatbox!.scrollHeight - chatbox!.offsetHeight;
- let p = document.createElement("p");
- p.innerText = message;
- p.classList.add("message");
- p.classList.add(classname);
- if (chatbox.children.length > 128) {
- chatbox.children[0]!.remove();
+ messages.push({ classname: classname, message: message });
+ messages = messages; // Trigger Svelte reactivity
+ }
+
+ afterUpdate(() => {
+ if (chatbox !== null) {
+ chatbox.scrollTo(0, chatbox.scrollHeight);
}
- chatbox!.appendChild(p);
- if (should_scroll) {
- chatbox!.scrollTop = chatbox!.scrollHeight;
+ });
+
+ function onkeydown(e: KeyboardEvent) {
+ if (e.key === "Enter") {
+ if (value.startsWith(".msg")) {
+ let args = value.split(" ");
+ if (args.length < 3) {
+ addMessage("server-error", "Command error");
+
+ value = "";
+ return;
+ }
+ let target = args[1];
+ let message = args.slice(2).join(" ");
+ let chat_packet: Packet = {
+ t: PacketType.SendMessage,
+ c: {
+ target: target,
+ content: message,
+ },
+ };
+ sendPacket(global.client!, chat_packet);
+
+ addMessage("direct-message", `you -> ${target}: ${message}`);
+ } else {
+ let chat_packet: Packet = {
+ t: PacketType.SendMessage,
+ c: {
+ target: null,
+ content: value,
+ },
+ };
+ sendPacket(global.client!, chat_packet);
+ }
+ value = "";
}
}
</script>
<Popup draggable minimizable title="Chat" class="chat-container" id="chat">
- <div id="chatbox" class="chat-table mono">
- <!-- Filled by script -->
+ <div bind:this={chatbox} id="chatbox" class="chat-table mono">
+ {#each messages as message}
+ <p class="message {message.classname}">{message.message}</p>
+ {/each}
</div>
- <input
+ <TextInput
+ bind:value
+ on:keydown={onkeydown}
placeholder="Enter message or command here..."
class="chat-box"
id="chatentry"
A starkingdoms-client/src/components/ui/Button.svelte => starkingdoms-client/src/components/ui/Button.svelte +28 -0
@@ 0,0 1,28 @@
+<script lang="ts">
+ let clazz: string = "";
+ export { clazz as class };
+ export let id: string = "";
+ export let disabled = false;
+ export let style = "";
+</script>
+
+<button {id} class="btn {clazz}" {disabled} on:click on:focus {style}>
+ <slot />
+</button>
+
+<style lang="scss">
+ .btn {
+ appearance: none;
+ background: transparent;
+ color: var(--text);
+ padding: 0.675em 1em;
+ border-radius: 0.25rem;
+ cursor: text;
+ border: 2px solid var(--links);
+ transition: 0.1s ease-in-out;
+ }
+ .btn:hover {
+ cursor: pointer;
+ background-color: var(--links-transparent);
+ }
+</style>
M => +7 -20
@@ 3,6 3,7 @@
import MovableIcon from "../../icons/MovableIcon.svelte";
import ChevronDown from "../../icons/ChevronDown.svelte";
import ChevronUp from "../../icons/ChevronUp.svelte";
import Button from "./Button.svelte";
let clazz = "";
export { clazz as class };
@@ 26,16 27,15 @@
let minimized = false;
onMount(() => {
console.log("popup mounted");
if (draggable) {
console.log("importing old window positions");
let top = window.localStorage.getItem(`pop-${id}top`);
let left = window.localStorage.getItem(`pop-${id}left`);
popup.style.top = top;
popup.style.left = left;
console.log("imported, top = " + top + " left = " + left);
}
if (minimizable) {
minimized = window.localStorage.getItem(`pop-${id}minim`) === "yes";
}
});
@@ 60,15 60,6 @@
window.localStorage.setItem(`pop-${id}top`, popup.style.top);
window.localStorage.setItem(`pop-${id}left`, popup.style.left);
console.log(
"dragged, top = " +
(popup.offsetTop - pos2) +
"px" +
" left = " +
(popup.offsetLeft - pos1) +
"px",
);
}
function closeDragElement() {
@@ 82,6 73,7 @@
function toggleMinimized() {
if (minimizable) {
minimized = !minimized;
window.localStorage.setItem(`pop-${id}minim`, minimized ? "yes" : "no");
}
}
</script>
@@ 96,13 88,13 @@
{title}
</h1>
{#if minimizable}
<button class="hdrbtn" on:click|preventDefault={toggleMinimized}>
<Button class="hdrbtn" on:click={toggleMinimized}>
{#if minimized}
<ChevronDown class="hdrbtn-icon" />
{:else}
<ChevronUp class="hdrbtn-icon" />
{/if}
</button>
</Button>
{/if}
</div>
{/if}
@@ 133,16 125,11 @@
}
:global(.hdrbtn) {
cursor: pointer;
position: absolute;
width: 2rem;
height: 2rem;
right: 1em;
top: 1em;
border: 1px solid var(--links);
border-radius: 0.25rem;
appearance: none;
background: transparent;
}
:global(.hdrbtn-icon) {
A starkingdoms-client/src/components/ui/TextInput.svelte => starkingdoms-client/src/components/ui/TextInput.svelte +40 -0
@@ 0,0 1,40 @@
+<script lang="ts">
+ let clazz: string = "";
+ export { clazz as class };
+ export let id: string = "";
+ export let disabled = false;
+ export let style = "";
+ export let required = false;
+ export let autocomplete = "";
+ export let value = "";
+ export let placeholder = "";
+</script>
+
+<input
+ bind:value
+ type="text"
+ {id}
+ class="txt {clazz}"
+ {disabled}
+ on:click
+ on:focus
+ on:input
+ on:change
+ on:keydown
+ on:keyup
+ {style}
+ {required}
+ {autocomplete}
+ {placeholder} />
+
+<style lang="scss">
+ .txt {
+ appearance: none;
+ background: transparent;
+ color: var(--text);
+ padding: 0.675em 1em;
+ border: 1px solid var(--links);
+ border-radius: 0.25rem;
+ cursor: text;
+ }
+</style>
M starkingdoms-client/src/css/form.scss => starkingdoms-client/src/css/form.scss +0 -10
@@ 22,16 22,6 @@
max-width: 100%;
}
-.btn {
- @extend %form-element-shared;
- border: 2px solid var(--links);
- transition: 0.1s ease-in-out;
-}
-.btn:hover {
- cursor: pointer;
- background-color: var(--links-transparent);
-}
-
.textentry {
@extend %form-element-shared;
}
M starkingdoms-client/src/hub.ts => starkingdoms-client/src/hub.ts +1 -40
@@ 50,6 50,7 @@ export async function hub_connect(
let client: ClientHub = {
socket: ws,
};
+ global.client = client;
let packet: Packet = {
t: PacketType.ClientLogin,
@@ 163,46 164,6 @@ export async function hub_connect(
}
};
- document.getElementById("chatentry")!.onkeydown = (e) => {
- if (e.key === "Enter") {
- let value = (<HTMLInputElement>document.getElementById("chatentry")!)
- .value;
-
- if (value.startsWith(".msg")) {
- let args = value.split(" ");
- if (args.length < 3) {
- chatbox.addMessage("server-error", "Command error");
-
- (<HTMLInputElement>document.getElementById("chatentry")!).value =
- "";
- return;
- }
- let target = args[1];
- let message = args.slice(2).join(" ");
- let chat_packet: Packet = {
- t: PacketType.SendMessage,
- c: {
- target: target,
- content: message,
- },
- };
- sendPacket(client, chat_packet);
-
- chatbox.addMessage("direct-message", `you -> ${target}: ${message}`);
- } else {
- let chat_packet: Packet = {
- t: PacketType.SendMessage,
- c: {
- target: null,
- content: value,
- },
- };
- sendPacket(client, chat_packet);
- }
- (<HTMLInputElement>document.getElementById("chatentry")!).value = "";
- }
- };
-
ws.onmessage = (e) => {
let packet: Packet = JSON.parse(e.data);
M starkingdoms-client/src/packet_ui.ts => starkingdoms-client/src/packet_ui.ts +0 -1
@@ 136,7 136,6 @@ const down_arrow = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox=
</svg>`;
export function selectPacket(id: number) {
- console.log("selecting packet " + id);
if (selected_packet !== null) {
document
.getElementById("packet-" + selected_packet)!
M starkingdoms-client/src/pages/Home.svelte => starkingdoms-client/src/pages/Home.svelte +8 -6
@@ 7,6 7,8 @@
import HeartIcon from "../icons/HeartIcon.svelte";
import WarningIcon from "../icons/WarningIcon.svelte";
import Popup from "../components/ui/Popup.svelte";
+ import Button from "../components/ui/Button.svelte";
+ import TextInput from "../components/ui/TextInput.svelte";
let config = DEFAULT_CONFIG;
// Top-level await. Sets the default config, and overwrites it when the new config is avail. Thanks reactivity!
@@ 32,10 34,10 @@
const is_development = window.localStorage.getItem("stk-mode") === "debug";
- let nonprod_warning;
+ let nonprod_warning: HTMLSpanElement;
- let server;
- let username;
+ let server = "";
+ let username = "";
function playGame() {
window.location.href = `/play/?srv=${server}&username=${username}`;
@@ 58,9 60,9 @@
<form id="join-fm" class="form" on:submit|preventDefault={playGame}>
<label for="username" class="label">Username</label>
- <input
+ <TextInput
+ style="width: 100%; max-width: 100%;"
bind:value={username}
- class="textentry"
id="username"
required
autocomplete="off" />
@@ 81,7 83,7 @@
{/each}
</select>
- <button id="launch-btn" class="btn">Launch!</button>
+ <Button style="width: 100%; max-width: 100%;">Launch!</Button>
<span bind:this={nonprod_warning} class="server-danger hidden">
<WarningIcon class="server-danger-icon" />
M starkingdoms-client/src/rendering.ts => starkingdoms-client/src/rendering.ts +0 -2
@@ 41,7 41,6 @@ export function startRender() {
} else {
part_sprite = PIXI.Sprite.from(part_texture_url(part.part_type));
global.rendering!.part_sprite_map.set(id, part_sprite);
- console.log("adding part sprite");
global.rendering!.app.stage.addChild(part_sprite);
}
@@ 62,7 61,6 @@ export function startRender() {
planet_texture_url(planet.planet_type),
);
global.rendering!.planet_sprite_map.set(id, planet_sprite);
- console.log("adding planet sprite");
global.rendering!.app.stage.addChild(planet_sprite);
}