@@ 3,14 3,13 @@
import createDebug from "debug";
import "../css/themes/catppuccin-mocha.scss";
import "../css/style.scss";
- import { parseJwt } from "../jwt.ts";
import HeartIcon from "../icons/HeartIcon.svelte";
import Popup from "../components/ui/Popup.svelte";
+ import { unpack_save } from "../save.ts";
+ import { PartType } from "../protocol.ts";
+ import { onMount } from "svelte";
+ import { part_texture_url } from "../textures.ts";
import Button from "../components/ui/Button.svelte";
- import TextInput from "../components/ui/TextInput.svelte";
- import PasswordInput from "../components/ui/PasswordInput.svelte";
- import tex_hearty from "../assets/hearty.svg";
- import tex_hub from "../assets/hub_off.svg";
let config = DEFAULT_CONFIG;
// Top-level await. Sets the default config, and overwrites it when the new config is avail. Thanks reactivity!
@@ 23,29 22,290 @@
`Hello, world! StarKingdoms ${APP_VERSION} (${COMMIT_HASH}) at your service!`,
);
logger("Current view: ShipEditor.svelte");
+
+ let canvas: HTMLCanvasElement;
+
+ let x = window.innerWidth / 2 - 24;
+ let y = window.innerHeight / 2 - 24;
+
+ let grid_x = 0;
+ let grid_y = 0;
+
+ let scale = 1.0;
+
+ $: grid_size = 48 * scale;
+
+ function handleWheel(e: WheelEvent) {
+ let delta = e.shiftKey ? 0.01 : 0.1;
+ if (e.deltaY < 0) {
+ scale += delta;
+ } else {
+ scale -= delta;
+ }
+ if (scale > 5) {
+ scale = 5;
+ } else if (scale < 0.1) {
+ scale = 0.1;
+ }
+ }
+
+ let isdragging = false;
+
+ let last_cx = -1;
+ let last_cy = -1;
+
+ function mousedown(e: MouseEvent) {
+ isdragging = true;
+ last_cx = e.clientX;
+ last_cy = e.clientY;
+ }
+ function mouseup(e) {
+ isdragging = false;
+ }
+
+ function mousemove(e: MouseEvent) {
+ if (isdragging) {
+ if (last_cx != -1) {
+ x += e.clientX - last_cx;
+ }
+ last_cx = e.clientX;
+ if (last_cy != -1) {
+ y += e.clientY - last_cy;
+ }
+ last_cy = e.clientY;
+ } else {
+ grid_x = Math.round((e.clientX - x) / grid_size - 0.5);
+ grid_y = Math.round((e.clientY - y) / grid_size - 0.5);
+ }
+ }
+
+ function goto(nx: number, ny: number) {
+ x = nx + (window.innerWidth / 2 - 24);
+ y = ny + (window.innerHeight / 2 - 24);
+ }
+
+ if (window.localStorage.getItem("save") === null) {
+ window.location.href = "/";
+ }
+
+ let save = unpack_save(window.localStorage.getItem("save")!);
+ console.log(save);
+
+ let grid: Map<number, Map<number, PartType>> = new Map();
+
+ function placePart(x: number, y: number, part: PartType) {
+ if (!grid.has(x)) {
+ grid.set(x, new Map());
+ }
+
+ grid.get(x)?.set(y, part);
+ }
+
+ let textures: Map<PartType, HTMLImageElement> = new Map();
+ let context: CanvasRenderingContext2D | null = null;
+
+ function render(
+ ctx: CanvasRenderingContext2D | null,
+ x: number,
+ y: number,
+ scale: number,
+ grid: Map<number, Map<number, PartType>>,
+ grid_x: number,
+ grid_y: number,
+ ) {
+ if (ctx === null) {
+ return;
+ }
+
+ // clear out the canvas
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ grid.forEach((row, x_coord) => {
+ row.forEach(async (part_type, y_coord) => {
+ // draw the image
+ if (!textures.has(part_type)) {
+ let promise = new Promise((resolve, reject) => {
+ let img = new Image(grid_size, grid_size);
+ img.onload = () => resolve(img);
+ img.onerror = reject;
+ img.src = part_texture_url(part_type, true);
+ });
+
+ let img = await promise;
+
+ textures.set(part_type, <HTMLImageElement>img);
+ }
+
+ let img = textures.get(part_type)!;
+
+ let canvas_x = x_coord * grid_size + x;
+ let canvas_y = y_coord * grid_size + y;
+
+ ctx.drawImage(img, canvas_x, canvas_y, grid_size, grid_size);
+ });
+ });
+
+ ctx.beginPath();
+ ctx.strokeStyle = "rgb(166, 227, 161)";
+ ctx.lineWidth = 2;
+ ctx.strokeRect(
+ grid_x * grid_size + x,
+ grid_y * grid_size + y,
+ grid_size,
+ grid_size,
+ );
+ ctx.stroke();
+ }
+
+ $: {
+ render(context, x, y, scale, grid, grid_x, grid_y);
+ }
+
+ onMount(() => {
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+ context = canvas.getContext("2d");
+ render(context, x, y, scale, grid, grid_x, grid_y);
+ });
+
+ window.onresize = () => {
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+ };
+
+ let confirm_reload_save = false;
+ let confirm_save = false;
+
+ let part_counts: Map<PartType, { used: number; available: number }> =
+ new Map();
+
+ function place(x: number, y: number, data: any) {
+ if (data === null) {
+ return;
+ }
+
+ let [type, children] = data;
+
+ placePart(x, y, type);
+
+ let existing_part_count =
+ part_counts.get(<PartType>type) === undefined
+ ? { used: 0, available: 0 }
+ : part_counts.get(<PartType>type)!;
+
+ part_counts.set(<PartType>type, {
+ used: existing_part_count.used + 1,
+ available: existing_part_count.available + 1,
+ });
+
+ if (children[0] !== null) {
+ place(x, y + 1, children[0]);
+ }
+ if (children[1] !== null) {
+ place(x + 1, y, children[1]);
+ }
+ if (children[2] !== null) {
+ place(x, y - 1, children[2]);
+ }
+ if (children[3] !== null) {
+ place(x - 1, y, children[3]);
+ }
+ }
+
+ function load_save_data(save: any) {
+ grid = new Map();
+ place(0, 0, [PartType.Hearty, save[0]]);
+ }
+
+ $: {
+ load_save_data(save);
+ }
+
+ function reload_btn() {
+ if (confirm_reload_save) {
+ save = unpack_save(window.localStorage.getItem("save")!);
+ }
+ confirm_reload_save = !confirm_reload_save;
+ setTimeout(() => {
+ confirm_reload_save = false;
+ }, 5000);
+ }
+
+ function save_btn() {
+ if (confirm_save) {
+ // todo: @ghostly you need to turn this back into a savefile and then call pack_partial on it
+ window.location.href = "/";
+ }
+ confirm_save = !confirm_save;
+ setTimeout(() => {
+ confirm_save = false;
+ }, 5000);
+ }
</script>
-<span
- style="position: absolute; left: 0px; top: 0px; min-width: 20px; height: 100%; width: 250px; background-color: var(--bg-secondary-2)">
- <ul
- style="list-style-type: none; width: 100%; padding: 0px; display: inline-block;
- text-align: center; font-size: 48px; margin-block-start: 0px; margin-block-end: 0px;">
- <li style="padding-top: 20px;">
- <img src={tex_hearty} style="width: 50%" />
- </li>
- <li style="padding-top: 20px; vertical-align: middle; text-align: center">
- <span
- style="position: relative; display: inline-block; height: 2em; line-height: 2em">
- 2
- </span>
- <img src={tex_hub} style="width: 2em" />
- </li>
- </ul>
-</span>
-<span
- style="position: absolute; bottom: 0px; right: 0px; width: calc(100% - 250px); height: 20%; background-color: var(--bg-secondary-1)">
+<Popup title="Parts" id="parts" draggable minimizable style="width: 15em;">
+ {#each part_counts.entries() as [type, counts]}
+ {#if type !== PartType.Hearty}
+ <div>
+ <img
+ style="vertical-align: middle;"
+ src={part_texture_url(type, true)}
+ width="24"
+ height="24"
+ alt={type} />
+ <span>{type} - {counts.used} used of {counts.available}</span>
+ </div>
+ {/if}
+ {/each}
+</Popup>
+
+<Popup
+ title="Tools"
+ id="tools"
+ draggable
+ minimizable
+ style="width: min-content;">
+ <Button
+ style="width: 16em; margin-top: 5px;"
+ on:click={() => {
+ goto(0, 0);
+ scale = 1.0;
+ }}>
+ Reset Viewport
+ </Button>
+ <Button
+ on:click={reload_btn}
+ variant="danger"
+ style="min-width: 16em; margin-top: 5px;">
+ {#if confirm_reload_save}
+ Are you sure? (again to confirm, resets in 5s)
+ {:else}
+ Reload Save
+ {/if}
+ </Button>
+ <Button
+ on:click={save_btn}
+ variant="danger"
+ style="min-width: 16em; margin-top: 5px;">
+ {#if confirm_save}
+ Are you sure? Overwrites old save! (again to confirm, resets in 5s)
+ {:else}
+ Save Changes
+ {/if}
+ </Button>
+</Popup>
+
+<canvas
+ on:mousedown|preventDefault={mousedown}
+ on:mouseup|preventDefault={mouseup}
+ on:mousemove|preventDefault={mousemove}
+ on:wheel|preventDefault={handleWheel}
+ style="background-size: {grid_size}px {grid_size}px; background-position: {x}px {y}px;"
+ bind:this={canvas} />
+
+<span style="position: absolute; top: 10px; right: 10px;">
+ ({grid_x}, {grid_y}) {scale.toFixed(2)}x
</span>
-<canvas id="shipeditor" />
<span class="footer-left">
StarKingdoms Client {APP_VERSION} ({COMMIT_HASH})
@@ 53,3 313,30 @@
<span class="footer-right">
Made with <HeartIcon class="footer-icon" /> by the StarKingdoms team
</span>
+
+<style>
+ :global(#tools) {
+ position: absolute;
+ bottom: 0.5em;
+ left: 0.5em;
+ }
+
+ :global(#parts) {
+ position: absolute;
+ top: 0.5em;
+ left: 0.5em;
+ }
+
+ canvas {
+ width: 100vw;
+ height: 100vh;
+ position: absolute;
+ top: 0;
+ left: 0;
+ padding: 0;
+ margin: 0;
+
+ background-image: linear-gradient(to right, #adadad20 1px, transparent 1px),
+ linear-gradient(to bottom, #adadad20 1px, transparent 1px);
+ }
+</style>