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/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(); 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..7c1db0b --- /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: var(--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..37dbe50 100644 --- a/client/src/pages/tabletop-page/tabletop-page.ts +++ b/client/src/pages/tabletop-page/tabletop-page.ts @@ -98,13 +98,7 @@ 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 861039b..c9b95a1 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; @@ -159,18 +160,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..7831d33 100644 --- a/server/views/layouts/main.html +++ b/server/views/layouts/main.html @@ -4,11 +4,15 @@ - Tabletopper v0.5.0 + Tabletopper v0.6.0 + + diff --git a/server/views/layouts/vtt.html b/server/views/layouts/vtt.html index bfe64e1..27c8d2d 100644 --- a/server/views/layouts/vtt.html +++ b/server/views/layouts/vtt.html @@ -4,11 +4,15 @@ - Tabletopper v0.5.0 + Tabletopper v0.6.0 + + @@ -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,