|
|
|
@ -53,67 +53,67 @@
|
|
|
|
|
transparent
|
|
|
|
|
headless
|
|
|
|
|
:floor-document="floorDocument"
|
|
|
|
|
@click="clickOnRoom(undefined, $event)"
|
|
|
|
|
@click="clickOnRoom()"
|
|
|
|
|
@zoom="(newZoom) => (canvasZoom = newZoom)"
|
|
|
|
|
>
|
|
|
|
|
<RoomPolygon
|
|
|
|
|
v-for="room of rooms"
|
|
|
|
|
:room="room"
|
|
|
|
|
:zoom="canvasZoom"
|
|
|
|
|
:highlighted="selectedRoom?.id"
|
|
|
|
|
@clicked-in="(ev) => clickOnRoom(room, ev)"
|
|
|
|
|
@clicked-in="(x, y) => clickOnRoom(room, x, y)"
|
|
|
|
|
@mouse-moved-in="(x, y) => mouseMovedInRoom(room, x, y)"
|
|
|
|
|
>
|
|
|
|
|
<template v-if="selectedRoom?.id === room.id">
|
|
|
|
|
<StorageBubble
|
|
|
|
|
:storage="storage"
|
|
|
|
|
v-for="storage of storages"
|
|
|
|
|
:class="{ 'z-20': storage.id === hoveredBubble }"
|
|
|
|
|
@mouseenter="() => (hoveredBubble = storage.id)"
|
|
|
|
|
@mouseleave="() => (hoveredBubble = undefined)"
|
|
|
|
|
>
|
|
|
|
|
<span class="text-md font-bold">{{ storage.displayName }}</span>
|
|
|
|
|
<span class="text-sm">Stored items: {{ storage.itemCount }}</span>
|
|
|
|
|
</StorageBubble>
|
|
|
|
|
<template v-for="storage of storages">
|
|
|
|
|
<StorageBubble
|
|
|
|
|
v-if="!movingBubble || storage.id === movingBubble?.id"
|
|
|
|
|
:storage="storage"
|
|
|
|
|
:class="{
|
|
|
|
|
'z-20': storage.id === hoveredBubble?.id,
|
|
|
|
|
'pointer-events-none': !!movingBubble,
|
|
|
|
|
}"
|
|
|
|
|
@mouseenter="() => (hoveredBubble = storage)"
|
|
|
|
|
@start-moving="moveBubble(storage)"
|
|
|
|
|
>
|
|
|
|
|
<div class="flex items-center space-x-1">
|
|
|
|
|
<span class="text-md font-bold">{{
|
|
|
|
|
storage.displayName
|
|
|
|
|
}}</span>
|
|
|
|
|
<span
|
|
|
|
|
v-if="isSet(storage)"
|
|
|
|
|
class="text-[0.75rem] font-bold uppercase text-gray-400"
|
|
|
|
|
>(Storage Set)</span
|
|
|
|
|
>
|
|
|
|
|
</div>
|
|
|
|
|
<span class="text-sm" v-if="!isSet(storage)"
|
|
|
|
|
>Stored items:
|
|
|
|
|
{{ (storage as StorageListItem).itemCount }}</span
|
|
|
|
|
>
|
|
|
|
|
<span class="text-sm" v-else
|
|
|
|
|
>Storages:
|
|
|
|
|
{{ (storage as StorageSetListItem).storages.length }}</span
|
|
|
|
|
>
|
|
|
|
|
</StorageBubble>
|
|
|
|
|
</template>
|
|
|
|
|
</template>
|
|
|
|
|
</RoomPolygon>
|
|
|
|
|
</HousePlanner>
|
|
|
|
|
</div>
|
|
|
|
|
</Transition>
|
|
|
|
|
|
|
|
|
|
<div class="grid grid-cols-3">
|
|
|
|
|
<div class="flex flex-col">
|
|
|
|
|
<button
|
|
|
|
|
v-for="room of rooms"
|
|
|
|
|
@click="selectRoomFromList(room)"
|
|
|
|
|
:class="[
|
|
|
|
|
selectedRoom?.id === room.id
|
|
|
|
|
? 'bg-blue-100 hover:bg-blue-200'
|
|
|
|
|
: 'hover:bg-blue-100',
|
|
|
|
|
'flex items-center justify-between border-b-2 border-gray-100 py-2 px-2',
|
|
|
|
|
]"
|
|
|
|
|
>
|
|
|
|
|
{{ room.displayName }} <ChevronRightIcon class="h-4 w-4" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="flex flex-col" v-if="selectedRoom?.id">
|
|
|
|
|
<button
|
|
|
|
|
v-for="storage of storages"
|
|
|
|
|
:class="[
|
|
|
|
|
'hover:bg-blue-100',
|
|
|
|
|
'flex items-center justify-between border-b-2 border-gray-100 py-2 px-2',
|
|
|
|
|
]"
|
|
|
|
|
>
|
|
|
|
|
{{ storage.displayName }} ({{ storage.itemCount }})
|
|
|
|
|
<ChevronRightIcon class="h-4 w-4" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<ItemSelector
|
|
|
|
|
:rooms="rooms"
|
|
|
|
|
:storages="storages"
|
|
|
|
|
:selected-room="selectedRoom"
|
|
|
|
|
:selected-storage="selectedStorage"
|
|
|
|
|
@select-room="(room) => selectRoomFromList(room)"
|
|
|
|
|
@select-storage="(storage) => selectStorage(storage)"
|
|
|
|
|
/>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import {
|
|
|
|
|
ChevronDownIcon,
|
|
|
|
|
ChevronRightIcon,
|
|
|
|
|
ChevronUpIcon,
|
|
|
|
|
} from '@heroicons/vue/24/outline';
|
|
|
|
|
import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/vue/24/outline';
|
|
|
|
|
import { storeToRefs } from 'pinia';
|
|
|
|
|
import PageHead from '../../../components/PageHead.vue';
|
|
|
|
|
import { useRoute } from 'vue-router';
|
|
|
|
@ -122,26 +122,34 @@ import { computed, ref } from '@vue/reactivity';
|
|
|
|
|
import HousePlanner from '../../../components/house-planner/HousePlanner.vue';
|
|
|
|
|
import { boundingBox } from '../../../modules/house-planner/utils';
|
|
|
|
|
import { Vec2 } from '../../../modules/house-planner/interfaces';
|
|
|
|
|
import { watch } from 'vue';
|
|
|
|
|
import { RoomLayoutObject } from '../../../interfaces/room.interfaces';
|
|
|
|
|
import jfetch from '../../../utils/jfetch';
|
|
|
|
|
import { BACKEND_URL } from '../../../constants';
|
|
|
|
|
import { useAccessToken } from '../../../composables/useAccessToken';
|
|
|
|
|
import { StorageListItem } from '../../../interfaces/storage.interfaces';
|
|
|
|
|
import {
|
|
|
|
|
StorageListItem,
|
|
|
|
|
StorageSetListItem,
|
|
|
|
|
StorageSharedType,
|
|
|
|
|
} from '../../../interfaces/storage.interfaces';
|
|
|
|
|
import StorageBubble from './StorageBubble.vue';
|
|
|
|
|
import RoomPolygon from './RoomPolygon.vue';
|
|
|
|
|
import { useLocalStorage } from '@vueuse/core';
|
|
|
|
|
import isSet from '../../../utils/is-storage-set';
|
|
|
|
|
import ItemSelector from './ItemSelector.vue';
|
|
|
|
|
|
|
|
|
|
const route = useRoute();
|
|
|
|
|
const buildingStore = useBuildingStore();
|
|
|
|
|
const { building } = storeToRefs(buildingStore);
|
|
|
|
|
const { authHeader } = useAccessToken();
|
|
|
|
|
const canvas = ref<InstanceType<typeof HousePlanner>>();
|
|
|
|
|
const storages = ref<StorageListItem[]>([]);
|
|
|
|
|
const selectedRoom = ref<RoomLayoutObject>();
|
|
|
|
|
const showMap = useLocalStorage('showRoomMap', true, { writeDefaults: false });
|
|
|
|
|
const hoveredBubble = ref<number>();
|
|
|
|
|
const selectedStorage = ref<number>();
|
|
|
|
|
const canvasZoom = ref(1);
|
|
|
|
|
const storages = ref<StorageSharedType[]>([]);
|
|
|
|
|
const selectedRoom = ref<RoomLayoutObject>();
|
|
|
|
|
const hoveredBubble = ref<StorageSharedType>();
|
|
|
|
|
const movingBubble = ref<StorageSharedType>();
|
|
|
|
|
const selectedSet = ref<StorageSetListItem>();
|
|
|
|
|
const selectedStorage = ref<StorageListItem>();
|
|
|
|
|
|
|
|
|
|
const floor = computed(() =>
|
|
|
|
|
building.value?.floors.find(
|
|
|
|
@ -153,32 +161,75 @@ const floorDocument = computed(
|
|
|
|
|
() => floor.value?.plan && JSON.parse(floor.value.plan)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const roomCoordinate = (ev: MouseEvent) => {
|
|
|
|
|
const rect = (ev.target as HTMLElement).getBoundingClientRect();
|
|
|
|
|
const [x, y] = [ev.clientX - rect.left, ev.clientY - rect.top];
|
|
|
|
|
console.log(x, y);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const clickOnRoom = (room: RoomLayoutObject | undefined, ev: MouseEvent) => {
|
|
|
|
|
const clickOnRoom = (room?: RoomLayoutObject, x?: number, y?: number) => {
|
|
|
|
|
canvas.value?.setViewRectangle(room?.boundingBox);
|
|
|
|
|
selectedStorage.value = undefined;
|
|
|
|
|
selectedSet.value = undefined;
|
|
|
|
|
if (room && (!selectedRoom.value || room.id !== selectedRoom.value?.id)) {
|
|
|
|
|
getRoomStorages(room);
|
|
|
|
|
} else if (room?.id == selectedRoom.value?.id) {
|
|
|
|
|
roomCoordinate(ev);
|
|
|
|
|
if (movingBubble.value && x != null && y != null) {
|
|
|
|
|
setBubbleLocation(x, y);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
storages.value = [];
|
|
|
|
|
}
|
|
|
|
|
selectedRoom.value = room;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const mouseMovedInRoom = (room: RoomLayoutObject, x: number, y: number) => {
|
|
|
|
|
if (movingBubble.value) {
|
|
|
|
|
const storage = storages.value.find(
|
|
|
|
|
(item) => item.id === movingBubble.value?.id
|
|
|
|
|
);
|
|
|
|
|
if (!storage) return;
|
|
|
|
|
storage.location = `${x},${y}`;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const moveBubble = (bubble: StorageSharedType) => {
|
|
|
|
|
movingBubble.value = bubble;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const setBubbleLocation = async (x: number, y: number) => {
|
|
|
|
|
if (!movingBubble.value) return;
|
|
|
|
|
await jfetch(
|
|
|
|
|
`${BACKEND_URL}/storage/${
|
|
|
|
|
(movingBubble.value as StorageSetListItem).storages ? 'set' : 'storages'
|
|
|
|
|
}/${movingBubble.value.id}`,
|
|
|
|
|
{
|
|
|
|
|
method: 'PATCH',
|
|
|
|
|
body: {
|
|
|
|
|
location: `${x},${y}`,
|
|
|
|
|
},
|
|
|
|
|
headers: authHeader.value,
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
movingBubble.value = undefined;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const selectRoomFromList = (room: RoomLayoutObject) => {
|
|
|
|
|
canvas.value?.setViewRectangle(room?.boundingBox);
|
|
|
|
|
selectedStorage.value = undefined;
|
|
|
|
|
selectedSet.value = undefined;
|
|
|
|
|
getRoomStorages(room);
|
|
|
|
|
selectedRoom.value = room;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const selectStorage = (storage: StorageSharedType) => {
|
|
|
|
|
if (isSet(storage)) {
|
|
|
|
|
selectedSet.value = storage as StorageSetListItem;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
selectedStorage.value = storage as StorageListItem;
|
|
|
|
|
jfetch(`${BACKEND_URL}/storage/storages/${storage.id}`, {
|
|
|
|
|
headers: authHeader.value,
|
|
|
|
|
}).then(({ data: storage }) => {
|
|
|
|
|
selectedStorage.value = storage;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const rooms = computed<RoomLayoutObject[]>(
|
|
|
|
|
() =>
|
|
|
|
|
(floor.value &&
|
|
|
|
|