From 0b16d8a70602b525051de642a29f2da24a7cbc8d Mon Sep 17 00:00:00 2001 From: Kyle Andrews Date: Fri, 20 Dec 2024 12:16:48 -0500 Subject: [PATCH 1/4] Reworked monster spawner --- .../spotlight-search/spotlight-search.scss | 57 ------- .../spotlight-search/spotlight-search.ts | 55 ------- .../monster-menu/monster-menu.scss | 143 ++++++++++++++++++ .../tabletop-component/tabletop-component.ts | 6 + .../src/pages/tabletop-page/tabletop-page.ts | 15 +- client/src/room.ts | 52 ++++++- server/room.go | 4 +- server/views/layouts/main.html | 4 + server/views/layouts/vtt.html | 6 + server/views/pages/room/index.html | 73 +++++++-- .../stubs/tabletop/spotlight-search.html | 37 +++-- server/views/stubs/tabletop/spotlight.html | 13 -- wss/src/room.ts | 4 +- 13 files changed, 304 insertions(+), 165 deletions(-) delete mode 100644 client/src/components/spotlight-search/spotlight-search.scss delete mode 100644 client/src/components/spotlight-search/spotlight-search.ts create mode 100644 client/src/pages/tabletop-page/monster-menu/monster-menu.scss delete mode 100644 server/views/stubs/tabletop/spotlight.html diff --git a/client/src/components/spotlight-search/spotlight-search.scss b/client/src/components/spotlight-search/spotlight-search.scss deleted file mode 100644 index 58cc6ed..0000000 --- a/client/src/components/spotlight-search/spotlight-search.scss +++ /dev/null @@ -1,57 +0,0 @@ -spotlight-search{ - display: flex; - justify-content: center; - align-items: center; - position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100vh; - - .modal{ - width: 600px; - border-radius: 0.5rem; - background-color: var(--white); - box-shadow: var(--shadow-black-lg); - border: 1px solid var(--grey-300); - overflow: hidden; - backdrop-filter: blur(8px); - - @media (prefers-color-scheme: dark){ - background-color: hsl(var(--grey-900-hsl)/0.87); - border: 1px solid var(--grey-800); - } - - input{ - display: block; - width: 100%; - height: 42px; - padding: 0 1rem; - font-size: var(--font-md); - color: var(--grey-700); - background-color: transparent; - - @media (prefers-color-scheme: dark){ - color: var(--white); - } - } - - .results{ - border-top: 1px solid var(--grey-300); - display: block; - width: 100%; - overflow-y: auto; - overscroll-behavior: contain; - max-height: calc(36px * 6); - padding: 0.5rem; - - @media (prefers-color-scheme: dark){ - border-top-color: var(--grey-700); - } - - button{ - justify-content: flex-start; - } - } - } -} diff --git a/client/src/components/spotlight-search/spotlight-search.ts b/client/src/components/spotlight-search/spotlight-search.ts deleted file mode 100644 index 529c2cc..0000000 --- a/client/src/components/spotlight-search/spotlight-search.ts +++ /dev/null @@ -1,55 +0,0 @@ -import SuperComponent from "@codewithkyle/supercomponent"; -import {html, render} from "lit-html"; -import env from "~brixi/controllers/env"; -import { Spell as ISpell, Monster } from "types/app"; - -interface ISpotlightSearch{ - results: Array, - query: string, -} -export default class SpotlightSearch extends SuperComponent{ - private callback: Function|null; - - constructor(callback = null){ - super(); - this.lastQueueId = null; - this.callback = callback; - this.model = { - results: [], - query: "", - }; - } - - async connected(){ - await env.css(["spotlight-search"]); - this.render(); - } - - private async search(value:string){ - } - private handleInputDebounce = this.debounce(this.search.bind(this), 300); - private handleInput = (e) => { - const input = e.currentTarget as HTMLInputElement; - this.handleInputDebounce(input.value.trim()); - } - - private clickBackdrop = () => { - this.remove(); - } - - override render(){ - const view = html` -
- - `; - render(view, this); - setTimeout(()=>{ - const input = this.querySelector("input") as HTMLInputElement; - input.focus(); - }, 100); - } -} -env.bind("spotlight-search", SpotlightSearch); diff --git a/client/src/pages/tabletop-page/monster-menu/monster-menu.scss b/client/src/pages/tabletop-page/monster-menu/monster-menu.scss new file mode 100644 index 0000000..bd7e81d --- /dev/null +++ b/client/src/pages/tabletop-page/monster-menu/monster-menu.scss @@ -0,0 +1,143 @@ +monster-menu { + display: inline-block; + width: 80vw; + position: fixed; + bottom: 0; + left: 50%; + transform: translate(-50%, 100%); + border-radius: 1.5rem 1.5rem 0 0; + background-color: var(--white); + z-index: 100; + box-shadow: var(--shadow-black-lg); + border: 1px solid var(--grey-300); + visibility: hidden; + backdrop-filter: blur(8px); + padding: 1rem; + + @media (prefers-color-scheme: dark) { + background-color: hsl(var(--grey-900-hsl)/0.87); + border: 1px solid var(--grey-800); + } + + &.is-open { + transform: translate(-50%, 0); + visibility: visible; + transition: transform 150ms var(--ease-in); + } + + monster-search { + display: inline-flex; + height: 32px; + width: 376px; + flex-flow: row nowrap; + align-items: center; + background-color: --grey-100; + border-radius: 16px; + + @media (prefers-color-scheme: dark) { + background-color: hsl(var(--grey-400-hsl) / 0.15); + } + + svg { + display: inline-block; + margin: 0 0.5rem; + color: var(--grey-400); + } + + input { + display: inline-block; + height: 100%; + flex: 1; + width: 100%; + color: var(--grey-700); + background-color: transparent; + + @media (prefers-color-scheme: dark) { + color: var(--grey-300); + } + } + } + + monster-list { + display: block; + width: 100%; + height: 96px; + margin-top: 1rem; + overflow-x: auto; + + label { + display: inline-flex; + width: 96px; + height: 96px; + border-radius: 0.5rem; + flex-flow: column wrap; + justify-content: center; + align-items: center; + padding: 0.25rem; + transition: background-color 80ms var(--ease-in-out); + position: relative; + user-select: none; + cursor: pointer; + + &:hover, + &:focus-visible { + background-color: var(--grey-50); + + @media (prefers-color-scheme: dark) { + background-color: hsl(var(--white-hsl) / 0.05); + } + } + + &:not(:last-of-type){ + margin-right: 1rem; + } + + &.is-selected { + background-color: var(--grey-100); + + @media (prefers-color-scheme: dark) { + background-color: hsl(var(--white-hsl) / 0.05); + } + } + + input { + position: absolute; + top: 0; + left: 0; + visibility: hidden; + opacity:0; + } + + img { + display: inline-block; + width: 48px; + height: 48px; + border-radius: 50%; + border: 2px solid var(--black); + overflow: hidden; + object-fit: cover; + } + + span { + display: block; + text-align: center; + font-size: var(--font-xs); + color: var(--grey-800); + margin-top: 0.5rem; + + @media (prefers-color-scheme: dark) { + color: var(--white); + } + } + + .placeholder-pawn { + display: inline-block; + width: 48px; + height: 48px; + border-radius: 50%; + border: 2px solid var(--black); + background-color: var(--danger-500); + } + } + } +} diff --git a/client/src/pages/tabletop-page/tabletop-component/tabletop-component.ts b/client/src/pages/tabletop-page/tabletop-component/tabletop-component.ts index f7592d0..f59f770 100644 --- a/client/src/pages/tabletop-page/tabletop-component/tabletop-component.ts +++ b/client/src/pages/tabletop-page/tabletop-component/tabletop-component.ts @@ -135,6 +135,12 @@ export default class TabeltopComponent extends SuperComponent { + publish("tabletop", "cursor:measure"); + }); + window.addEventListener("cursor:move", (e:CustomEvent) => { + publish("tabletop", "cursor:move"); + }); this.render(); } diff --git a/client/src/pages/tabletop-page/tabletop-page.ts b/client/src/pages/tabletop-page/tabletop-page.ts index 833df83..866fc77 100644 --- a/client/src/pages/tabletop-page/tabletop-page.ts +++ b/client/src/pages/tabletop-page/tabletop-page.ts @@ -98,13 +98,14 @@ export default class TabletopPage extends SuperComponent{ items.push({ label: "Spawn Monster", callback: () => { - const tabletop = document.body.querySelector("tabletop-component") as TabeltopComponent; - let diffX = (x - tabletop.x) / tabletop.zoom; - let diffY = (y - tabletop.y) / tabletop.zoom; - sessionStorage.setItem("tabletop:spawn-monster:y", `${Math.round(diffY) - 16}`); - sessionStorage.setItem("tabletop:spawn-monster:x", `${Math.round(diffX) - 16}`); - htmx.ajax("GET", "/stub/tabletop/spotlight", { target: "spotlight-search .modal" }); - window.dispatchEvent(new CustomEvent("show-spotlight-search")); + //const tabletop = document.body.querySelector("tabletop-component") as TabeltopComponent; + //let diffX = (x - tabletop.x) / tabletop.zoom; + //let diffY = (y - tabletop.y) / tabletop.zoom; + //sessionStorage.setItem("tabletop:spawn-monster:y", `${Math.round(diffY) - 16}`); + //sessionStorage.setItem("tabletop:spawn-monster:x", `${Math.round(diffX) - 16}`); + //htmx.ajax("GET", "/stub/tabletop/spotlight", { target: "spotlight-search .modal" }); + //window.dispatchEvent(new CustomEvent("show-spotlight-search")); + window.dispatchEvent(new CustomEvent("show-monster-menu")); } }); diff --git a/client/src/room.ts b/client/src/room.ts index 861039b..1fda0af 100644 --- a/client/src/room.ts +++ b/client/src/room.ts @@ -2,12 +2,13 @@ import { subscribe } from "@codewithkyle/pubsub"; import PlayerMenu from "~components/window/windows/player-menu/player-menu"; import Window from "~components/window/window"; import { connect, send } from "~controllers/ws"; -import { Player } from "~types/app"; +import { Player, Size } from "~types/app"; import DiceBox from "~components/window/windows/dice-box/dice-box"; import MonsterManual from "~components/window/windows/monster-manual/monster-manual"; import MonsterStatBlock from "~components/window/windows/monster-stat-block/monster-stat-block"; import FogBrush from "~components/window/windows/fog-brush/fog-brush"; import DoodleBrush from "~components/window/windows/doodle-brush/doodle-brush"; +import TabeltopComponent from "pages/tabletop-page/tabletop-component/tabletop-component"; declare const htmx: any; @@ -24,6 +25,16 @@ class Room { public dmgOverlay: boolean; public activeInitiative: string | null; private hasLogOn: boolean; + private selectedMonster?: { + x: number, + y: number, + name: string, + hp: number, + ac: number, + size: string, + image: string, + monsterId: string, + }; constructor() { this.uid = ""; @@ -38,6 +49,7 @@ class Room { this.dmgOverlay = false; this.activeInitiative = null; this.hasLogOn = false; + this.selectedMonster = null; subscribe("socket", this.inbox.bind(this)); this.init(); } @@ -159,18 +171,42 @@ class Room { send("room:tabletop:spawn:players"); }); window.addEventListener("tabletop:spawn-monster", (e: CustomEvent) => { - const { uid, name, hp, ac, size, image } = e.detail; - const x = parseInt(sessionStorage.getItem("tabletop:spawn-monster:x")); - const y = parseInt(sessionStorage.getItem("tabletop:spawn-monster:y")); + const { uid, name, hp, ac, size, image, x, y, hidden } = e.detail; + const tabletop = document.body.querySelector("tabletop-component") as TabeltopComponent; + let diffX = (x - tabletop.x) / tabletop.zoom; + let diffY = (y - tabletop.y) / tabletop.zoom; + let multi = 1; + switch (size as Size){ + case "tiny": + multi = 0.25; + break; + case "small": + multi = 0.5; + break; + case "large": + multi = 2; + break; + case "huge": + multi = 3; + break; + case "gargantuan": + multi = 4; + break; + default: + multi = 1; + break; + } + const halfSize = this.gridSize * multi / 2; send("room:tabletop:spawn:monster", { - x: x, - y: y, + x: Math.round(diffX) - halfSize, + y: Math.round(diffY) - halfSize, name: name, - hp: parseInt(hp), - ac: parseInt(ac), + hp: hp, + ac: ac, size: size, image: image, monsterId: uid, + hidden: hidden, }); }); window.addEventListener("tabletop:quick-spawn", () => { diff --git a/server/room.go b/server/room.go index 3bd7dd7..8db0735 100644 --- a/server/room.go +++ b/server/room.go @@ -203,8 +203,8 @@ func RoomRoutes(app *fiber.App, rdb *redis.Client) { isGM := c.Cookies("gm", "") return c.Render("stubs/tabletop/quick-spawn", fiber.Map{ - "User": user, - "GM": isGM != "", + "User": user, + "GM": isGM != "", }) }) diff --git a/server/views/layouts/main.html b/server/views/layouts/main.html index d0d52e3..54f5127 100644 --- a/server/views/layouts/main.html +++ b/server/views/layouts/main.html @@ -9,6 +9,10 @@ + + diff --git a/server/views/layouts/vtt.html b/server/views/layouts/vtt.html index bfe64e1..7d7d2b1 100644 --- a/server/views/layouts/vtt.html +++ b/server/views/layouts/vtt.html @@ -9,6 +9,10 @@ + + @@ -36,6 +40,8 @@ + + diff --git a/server/views/pages/room/index.html b/server/views/pages/room/index.html index 6935c0e..bfe68c9 100644 --- a/server/views/pages/room/index.html +++ b/server/views/pages/room/index.html @@ -110,22 +110,12 @@ x-data="{ open: false }" x-on:show-monster-maker.window="open = true" x-show="open" + x-cloak > - - - +
+ + + + + + +
+ + + +{% endif %} diff --git a/server/views/stubs/tabletop/spotlight-search.html b/server/views/stubs/tabletop/spotlight-search.html index 592dc8f..f5ebb75 100644 --- a/server/views/stubs/tabletop/spotlight-search.html +++ b/server/views/stubs/tabletop/spotlight-search.html @@ -1,10 +1,27 @@ -
- {% for monster in Monsters %} - - {% endfor %} -
+{% for monster in Monsters %} + +{% endfor %} diff --git a/server/views/stubs/tabletop/spotlight.html b/server/views/stubs/tabletop/spotlight.html deleted file mode 100644 index a0be178..0000000 --- a/server/views/stubs/tabletop/spotlight.html +++ /dev/null @@ -1,13 +0,0 @@ - -
diff --git a/wss/src/room.ts b/wss/src/room.ts index e1ba7f5..120b3c9 100644 --- a/wss/src/room.ts +++ b/wss/src/room.ts @@ -287,7 +287,7 @@ class Room { this.broadcast("room:tabletop:pawn:delete", pawnId); } - public spawnMonster({ monsterId, x, y, name, hp, ac, size, image }){ + public spawnMonster({ monsterId, x, y, name, hp, ac, size, image, hidden }){ const id = randomUUID(); const pawn:Pawn = { x: x, @@ -295,7 +295,7 @@ class Room { hp: hp, ac: ac, room: this.code, - hidden: true, + hidden: hidden, uid: id, name: name, fullHP: hp, From 779aad9f62326660557984a5bc9e06c64d6a2fdb Mon Sep 17 00:00:00 2001 From: Kyle Andrews Date: Fri, 20 Dec 2024 12:21:04 -0500 Subject: [PATCH 2/4] Fix out of bounds windows on create --- client/src/components/window/window.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/src/components/window/window.ts b/client/src/components/window/window.ts index ef7a282..fa74f98 100644 --- a/client/src/components/window/window.ts +++ b/client/src/components/window/window.ts @@ -65,6 +65,11 @@ export default class Window extends SuperComponent{ localStorage.setItem(`${this.handle}-h`, this.h.toFixed(0).toString()); } + if (this.y < 28) this.y = 28; + if (this.y > window.innerHeight - 28) this.y = window.innerHeight - 28; + if (this.x < 0) this.x = 0; + if (this.x > window.innerWidth - this.w) this.x = window.innerWidth - this.w; + this.enableControls = settings?.enableControls ?? true; this.resize(); From fbb09aaca56b57932ab9cda72c25542fc7c26be3 Mon Sep 17 00:00:00 2001 From: Kyle Andrews Date: Fri, 20 Dec 2024 12:26:05 -0500 Subject: [PATCH 3/4] Bumped version number --- server/views/layouts/main.html | 2 +- server/views/layouts/vtt.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/views/layouts/main.html b/server/views/layouts/main.html index 54f5127..7831d33 100644 --- a/server/views/layouts/main.html +++ b/server/views/layouts/main.html @@ -4,7 +4,7 @@ - Tabletopper v0.5.0 + Tabletopper v0.6.0 diff --git a/server/views/layouts/vtt.html b/server/views/layouts/vtt.html index 7d7d2b1..27c8d2d 100644 --- a/server/views/layouts/vtt.html +++ b/server/views/layouts/vtt.html @@ -4,7 +4,7 @@ - Tabletopper v0.5.0 + Tabletopper v0.6.0 From aa630db3859b35ff586d06c52ebb97d121e0aaa9 Mon Sep 17 00:00:00 2001 From: Kyle Andrews Date: Fri, 20 Dec 2024 12:30:02 -0500 Subject: [PATCH 4/4] Cleanup & fixes --- .../tabletop-page/monster-menu/monster-menu.scss | 2 +- client/src/pages/tabletop-page/tabletop-page.ts | 7 ------- client/src/room.ts | 11 ----------- 3 files changed, 1 insertion(+), 19 deletions(-) diff --git a/client/src/pages/tabletop-page/monster-menu/monster-menu.scss b/client/src/pages/tabletop-page/monster-menu/monster-menu.scss index bd7e81d..7c1db0b 100644 --- a/client/src/pages/tabletop-page/monster-menu/monster-menu.scss +++ b/client/src/pages/tabletop-page/monster-menu/monster-menu.scss @@ -31,7 +31,7 @@ monster-menu { width: 376px; flex-flow: row nowrap; align-items: center; - background-color: --grey-100; + background-color: var(--grey-100); border-radius: 16px; @media (prefers-color-scheme: dark) { diff --git a/client/src/pages/tabletop-page/tabletop-page.ts b/client/src/pages/tabletop-page/tabletop-page.ts index 866fc77..37dbe50 100644 --- a/client/src/pages/tabletop-page/tabletop-page.ts +++ b/client/src/pages/tabletop-page/tabletop-page.ts @@ -98,13 +98,6 @@ export default class TabletopPage extends SuperComponent{ items.push({ label: "Spawn Monster", callback: () => { - //const tabletop = document.body.querySelector("tabletop-component") as TabeltopComponent; - //let diffX = (x - tabletop.x) / tabletop.zoom; - //let diffY = (y - tabletop.y) / tabletop.zoom; - //sessionStorage.setItem("tabletop:spawn-monster:y", `${Math.round(diffY) - 16}`); - //sessionStorage.setItem("tabletop:spawn-monster:x", `${Math.round(diffX) - 16}`); - //htmx.ajax("GET", "/stub/tabletop/spotlight", { target: "spotlight-search .modal" }); - //window.dispatchEvent(new CustomEvent("show-spotlight-search")); window.dispatchEvent(new CustomEvent("show-monster-menu")); } }); diff --git a/client/src/room.ts b/client/src/room.ts index 1fda0af..c9b95a1 100644 --- a/client/src/room.ts +++ b/client/src/room.ts @@ -25,16 +25,6 @@ class Room { public dmgOverlay: boolean; public activeInitiative: string | null; private hasLogOn: boolean; - private selectedMonster?: { - x: number, - y: number, - name: string, - hp: number, - ac: number, - size: string, - image: string, - monsterId: string, - }; constructor() { this.uid = ""; @@ -49,7 +39,6 @@ class Room { this.dmgOverlay = false; this.activeInitiative = null; this.hasLogOn = false; - this.selectedMonster = null; subscribe("socket", this.inbox.bind(this)); this.init(); }