commit 03
This commit is contained in:
parent
6b9b020c5d
commit
9a71f52528
7 changed files with 158 additions and 13 deletions
33
src/App.vue
33
src/App.vue
|
@ -2,12 +2,31 @@
|
|||
<div class="bg-gray-900 h-100vh w-100vw overflow-hidden text-white">
|
||||
<div :class="$style.noise"/>
|
||||
<div :class="$style.vignette"/>
|
||||
<div class="relative h-full">
|
||||
<div v-if="isLoading" class="flex flex-col justify-center items-center text-lg inset-0 absolute">
|
||||
<span class="text-center">Verbindung wird hergestellt…</span>
|
||||
</div>
|
||||
<main>
|
||||
<InteractionsScreen/>
|
||||
<div v-if="isLoading" class="flex flex-col justify-center items-center text-lg inset-0 absolute">
|
||||
<span class="text-center">Verbindung wird hergestellt…</span>
|
||||
</div>
|
||||
<div class="h-full relative flex flex-col">
|
||||
<nav class="mx-auto py-4">
|
||||
<div class="flex items-center rounded-lg bg-dark-400 overflow-hidden">
|
||||
<button
|
||||
class="px-4 py-2 bg-gray-700"
|
||||
:class="activeTab !== 'interactions' && 'bg-opacity-0'"
|
||||
@click="activeTab = 'interactions'"
|
||||
>
|
||||
Interagieren
|
||||
</button>
|
||||
<button
|
||||
class="px-4 py-2 bg-gray-700"
|
||||
:class="activeTab !== 'queue' && 'bg-opacity-0'"
|
||||
@click="activeTab = 'queue'"
|
||||
>
|
||||
Abstimmen
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
<main class="flex-grow">
|
||||
<InteractionsScreen v-if="activeTab === 'interactions'"/>
|
||||
<QueueScreen v-else-if="activeTab === 'queue'"/>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -74,8 +93,10 @@
|
|||
import { trpcClient } from "./trpc"
|
||||
import { useGame } from "./game"
|
||||
import InteractionsScreen from "./screens/InteractionsScreen.vue"
|
||||
import QueueScreen from "./screens/QueueScreen.vue"
|
||||
|
||||
const isLoading = ref(true)
|
||||
const activeTab = ref<"interactions" | "queue">("interactions")
|
||||
const game = useGame()
|
||||
|
||||
trpcClient.join.subscribe(undefined, {
|
||||
|
|
65
src/components/InteractionQueueItemCard.vue
Normal file
65
src/components/InteractionQueueItemCard.vue
Normal file
|
@ -0,0 +1,65 @@
|
|||
<template>
|
||||
<div class="bg-dark-600 rounded-lg flex overflow-hidden">
|
||||
<div class="flex-grow flex items-center justify-center gap-2 px-3 py-4">
|
||||
<template v-if="item.interaction.type === 'use'">
|
||||
<HandPointingIcon class="text-4xl mb-6"/>
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<ObjectPicture :object-id="item.interaction.objectId"/>
|
||||
<div class="text-sm text-gray-300 text-center">
|
||||
{{ game.allObjectsById.get(item.interaction.objectId)!.label }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else v-for="(objectId, index) in item.interaction.objectIds" :key="objectId">
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<ObjectPicture :object-id="objectId"/>
|
||||
<div class="text-sm text-gray-300 text-center">
|
||||
{{ game.allObjectsById.get(objectId)!.label }}
|
||||
</div>
|
||||
</div>
|
||||
<PlusIcon v-if="index < item.interaction.objectIds.size - 1" class="text-3xl mb-6"/>
|
||||
</template>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 justify-between items-center bg-gray-800 w-17">
|
||||
<div class="flex flex-col justify-center items-center py-3">
|
||||
<div class="text-2xl">{{ item.votes }}</div>
|
||||
<div class="text-sm text-center">
|
||||
Vote{{item.votes === 1 ? "" : "s" }}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="align-end flex-grow py-2 w-full"
|
||||
:class="game.currentInteractionId === item.id ? 'bg-blue-500' : 'bg-gray-700'"
|
||||
@click="toggleVote()"
|
||||
>
|
||||
+1
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style module lang="scss">
|
||||
|
||||
</style>
|
||||
|
||||
<script setup lang="ts">
|
||||
import PlusIcon from "virtual:icons/ph/plus-bold"
|
||||
import HandPointingIcon from "virtual:icons/ph/hand-pointing-duotone"
|
||||
import type { InteractionQueueItem } from "../shared/script/types"
|
||||
import ObjectPicture from "./ObjectPicture.vue"
|
||||
import { useGame } from "../game"
|
||||
|
||||
const props = defineProps<{
|
||||
item: InteractionQueueItem
|
||||
}>()
|
||||
|
||||
const game = useGame()
|
||||
|
||||
function toggleVote() {
|
||||
if (game.currentInteractionId === props.item.id) {
|
||||
game.revokeCurrentInteractionVote()
|
||||
} else {
|
||||
game.voteForInteraction(props.item.interaction)
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -8,7 +8,7 @@
|
|||
}"
|
||||
:data-object-id="object.id"
|
||||
>
|
||||
<img :src="`/objects/${props.object.id}.png`" :alt="object.label" class="invert filter object-contain max-w-15"/>
|
||||
<ObjectPicture :object-id="object.id"/>
|
||||
<div class="text-sm text-gray-300">
|
||||
{{ object.label }}
|
||||
</div>
|
||||
|
@ -41,6 +41,7 @@
|
|||
import interact from "@interactjs/interact"
|
||||
import { useCurrentElement } from "@vueuse/core"
|
||||
import { computed, onMounted, onUnmounted, ref, useCssModule } from "vue"
|
||||
import ObjectPicture from "./ObjectPicture.vue"
|
||||
|
||||
const props = defineProps<{
|
||||
object: GameObject
|
||||
|
|
13
src/components/ObjectPicture.vue
Normal file
13
src/components/ObjectPicture.vue
Normal file
|
@ -0,0 +1,13 @@
|
|||
<template>
|
||||
<img :src="`/objects/${objectId}.png`" alt="" class="invert filter object-contain max-w-15"/>
|
||||
</template>
|
||||
|
||||
<style module lang="scss">
|
||||
|
||||
</style>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
objectId: string
|
||||
}>()
|
||||
</script>
|
14
src/game.ts
14
src/game.ts
|
@ -1,7 +1,7 @@
|
|||
import { defineStore } from "pinia"
|
||||
import { computed, reactive, ref } from "vue"
|
||||
import { script } from "./shared/script"
|
||||
import type { Interaction, InteractionQueueItem } from "./shared/script/types"
|
||||
import type { GameObject, Interaction, InteractionQueueItem } from "./shared/script/types"
|
||||
import { trpcClient } from "./trpc"
|
||||
import type { GameEvent } from "./shared/gameEvents"
|
||||
import { getInteractionQueueItemId } from "./shared/util"
|
||||
|
@ -13,10 +13,20 @@ export const useGame = defineStore("gameState", () => {
|
|||
const currentInteractionId = computed(() =>
|
||||
currentInteraction.value === null ? null : getInteractionQueueItemId(currentInteraction.value))
|
||||
|
||||
const sortedInteractionQueue = computed(() => [...interactionQueue.values()].sort((a, b) => b.votes - a.votes))
|
||||
|
||||
const currentRoom = computed(() => script.roomsById.get(currentRoomId.value)!)
|
||||
|
||||
return {
|
||||
currentRoomId,
|
||||
interactionQueue,
|
||||
currentRoom: computed(() => script.roomsById.get(currentRoomId.value)!),
|
||||
sortedInteractionQueue,
|
||||
currentRoom,
|
||||
allObjectsById: computed(() => {
|
||||
const map = new Map<string, GameObject>()
|
||||
currentRoom.value.initialObjects.union(currentRoom.value.hiddenObjects).forEach(o => map.set(o.id, o))
|
||||
return map
|
||||
}),
|
||||
currentInteraction,
|
||||
currentInteractionId,
|
||||
voteForInteraction(interaction: Interaction) {
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
<template>
|
||||
<div class="h-100vh flex flex-col relative">
|
||||
<div class="h-full flex flex-col relative">
|
||||
<div class="flex-shrink-0 flex order-1">
|
||||
<ObjectCardDropZone
|
||||
label="Use"
|
||||
label="Benutzen"
|
||||
class="bg-green-800"
|
||||
:has-floating="useFloatingObjectIds.size > 0"
|
||||
@object-change="(v, i) => createSetInclusionSetter(useFloatingObjectIds)(v, i)"
|
||||
@object-drop="onObjectUseDrop"
|
||||
/>
|
||||
<ObjectCardDropZone
|
||||
label="Combine"
|
||||
label="Kombinieren"
|
||||
class="bg-blue-900"
|
||||
:has-floating="combineFloatingObjectIds.size > 0"
|
||||
@object-change="(v, i) => createSetInclusionSetter(combineFloatingObjectIds)(v, i)"
|
||||
@object-drop="onObjectInteractionDrop"
|
||||
/>
|
||||
</div>
|
||||
<div ref="objectsContainerElement" class="grid gap-3 grid-cols-2 flex-grow auto-rows-min p-4">
|
||||
<div ref="objectsContainerElement" class="grid gap-3 grid-cols-2 flex-grow auto-rows-min p-4 pt-0">
|
||||
<ObjectCard
|
||||
v-for="object in game.currentRoom.initialObjects"
|
||||
:key="object.id"
|
||||
|
|
35
src/screens/QueueScreen.vue
Normal file
35
src/screens/QueueScreen.vue
Normal file
|
@ -0,0 +1,35 @@
|
|||
<template>
|
||||
<transition-group tag="div" name="list" class="h-full flex flex-col gap-4 p-4 pt-0 relative">
|
||||
<InteractionQueueItemCard
|
||||
v-for="item in game.sortedInteractionQueue"
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
/>
|
||||
</transition-group>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.list-move,
|
||||
.list-enter-active,
|
||||
.list-leave-active {
|
||||
transition: 400ms ease;
|
||||
transition-property: opacity, transform;
|
||||
}
|
||||
|
||||
.list-enter-from,
|
||||
.list-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(-30px);
|
||||
}
|
||||
|
||||
.list-leave-active {
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script setup lang="ts">
|
||||
import InteractionQueueItemCard from "../components/InteractionQueueItemCard.vue"
|
||||
import { useGame } from "../game"
|
||||
|
||||
const game = useGame()
|
||||
</script>
|
Loading…
Add table
Reference in a new issue