193 lines
5.6 KiB
Vue
193 lines
5.6 KiB
Vue
|
<template>
|
||
|
<PageHead with-actions>
|
||
|
<template #actions>
|
||
|
<RouterLink
|
||
|
class="px-2 py-2"
|
||
|
:to="{
|
||
|
name: 'planner',
|
||
|
query: { buildingId: building?.id, floorId: floor?.id },
|
||
|
}"
|
||
|
>Edit floor plan</RouterLink
|
||
|
>
|
||
|
</template>
|
||
|
|
||
|
<template #default>
|
||
|
<div class="flex flex-col">
|
||
|
<h1 class="text-2xl font-bold">{{ floor?.displayName }}</h1>
|
||
|
<span class="text-sm font-light text-gray-800 line-clamp-1"
|
||
|
>Floor {{ floor?.number }} in {{ building?.displayName }}</span
|
||
|
>
|
||
|
</div>
|
||
|
</template>
|
||
|
</PageHead>
|
||
|
|
||
|
<HousePlanner
|
||
|
v-if="floorDocument"
|
||
|
:key="`fdoc${floorDocument.id}`"
|
||
|
ref="canvas"
|
||
|
class="h-[70vh]"
|
||
|
:editable="false"
|
||
|
:grid="false"
|
||
|
transparent
|
||
|
headless
|
||
|
:floor-document="floorDocument"
|
||
|
@click="clickOnRoom()"
|
||
|
>
|
||
|
<template v-for="room of rooms">
|
||
|
<div
|
||
|
v-if="!highlighted || highlighted.id === room.id"
|
||
|
:class="[
|
||
|
highlighted
|
||
|
? ''
|
||
|
: 'flex cursor-pointer items-center justify-center transition-transform hover:scale-105',
|
||
|
'pointer-events-auto absolute',
|
||
|
]"
|
||
|
@click.stop="clickOnRoom(room)"
|
||
|
:style="getRoomPositionCSS(room)"
|
||
|
>
|
||
|
<div class="absolute" :style="getRoomPolygonCSS(room)"></div>
|
||
|
<span class="pointer-events-none" v-if="!highlighted">{{
|
||
|
room.displayName
|
||
|
}}</span>
|
||
|
<template v-if="highlighted?.id === room.id">
|
||
|
<template v-for="storage of storages">
|
||
|
<div
|
||
|
class="custom-storage absolute"
|
||
|
:style="getStoragePosition(storage)"
|
||
|
>
|
||
|
<div
|
||
|
class="absolute bottom-2 left-1/2 h-20 w-20 -translate-x-1/2 rounded-lg bg-blue-400 px-2 py-2"
|
||
|
>
|
||
|
<span>{{ storage.displayName }}</span>
|
||
|
<div
|
||
|
class="absolute -bottom-2 left-1/2 h-0 w-0 -translate-x-1/2 border-x-8 border-t-[16px] border-x-transparent border-t-blue-400"
|
||
|
></div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</template>
|
||
|
</template></div
|
||
|
></template>
|
||
|
</HousePlanner>
|
||
|
</template>
|
||
|
<script setup lang="ts">
|
||
|
import { storeToRefs } from 'pinia';
|
||
|
import PageHead from '../../../components/PageHead.vue';
|
||
|
import { useRoute } from 'vue-router';
|
||
|
import { useBuildingStore } from '../../../store/building.store';
|
||
|
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';
|
||
|
|
||
|
const route = useRoute();
|
||
|
const buildingStore = useBuildingStore();
|
||
|
const { building } = storeToRefs(buildingStore);
|
||
|
const { authHeader } = useAccessToken();
|
||
|
const canvas = ref<InstanceType<typeof HousePlanner>>();
|
||
|
const highlighted = ref<RoomLayoutObject>();
|
||
|
const storages = ref<StorageListItem[]>([]);
|
||
|
|
||
|
const floor = computed(() =>
|
||
|
building.value?.floors.find(
|
||
|
(floor) => floor.number === Number(route.params.number)
|
||
|
)
|
||
|
);
|
||
|
|
||
|
const floorDocument = computed(
|
||
|
() => floor.value?.plan && JSON.parse(floor.value.plan)
|
||
|
);
|
||
|
|
||
|
const clickOnRoom = (room?: RoomLayoutObject) => {
|
||
|
canvas.value?.setViewRectangle(room?.boundingBox);
|
||
|
if (room && !highlighted.value) {
|
||
|
getRoomStorages(room);
|
||
|
} else if (!room && highlighted.value) {
|
||
|
storages.value = [];
|
||
|
}
|
||
|
highlighted.value = room;
|
||
|
};
|
||
|
|
||
|
const rooms = computed<RoomLayoutObject[]>(
|
||
|
() =>
|
||
|
(floor.value &&
|
||
|
floor.value.rooms
|
||
|
.filter((room) => room.plan?.includes('polygon'))
|
||
|
.map((room) => {
|
||
|
const { polygon } = JSON.parse(room.plan);
|
||
|
const aabb = boundingBox(polygon, [
|
||
|
[Infinity, Infinity],
|
||
|
[0, 0],
|
||
|
]);
|
||
|
return {
|
||
|
...room,
|
||
|
plan: polygon as Vec2[],
|
||
|
boundingBox: aabb,
|
||
|
storages: [],
|
||
|
} as RoomLayoutObject;
|
||
|
})) ||
|
||
|
[]
|
||
|
);
|
||
|
|
||
|
const getRoomPolygonCSS = (room: RoomLayoutObject) => {
|
||
|
const [min, max] = room.boundingBox;
|
||
|
return {
|
||
|
clipPath: `polygon(${room.plan
|
||
|
.map((point) => `${point[0] - min[0]}px ${point[1] - min[1]}px`)
|
||
|
.join(', ')})`,
|
||
|
width: `${max[0] - min[0]}px`,
|
||
|
height: `${max[1] - min[1]}px`,
|
||
|
backgroundColor: 'rgb(40 180 255 / 40%)',
|
||
|
};
|
||
|
};
|
||
|
|
||
|
const getRoomPositionCSS = (room: RoomLayoutObject) => {
|
||
|
const [min, max] = room.boundingBox;
|
||
|
return {
|
||
|
top: `${min[1]}px`,
|
||
|
left: `${min[0]}px`,
|
||
|
width: `${max[0] - min[0]}px`,
|
||
|
height: `${max[1] - min[1]}px`,
|
||
|
};
|
||
|
};
|
||
|
const getStoragePosition = (storage: any) => {
|
||
|
const locationSplit = storage.location
|
||
|
.split(',')
|
||
|
.map((int: string) => `${int}px`);
|
||
|
return {
|
||
|
left: locationSplit[0],
|
||
|
top: locationSplit[1],
|
||
|
};
|
||
|
};
|
||
|
|
||
|
const getRoomStorages = async (room: RoomLayoutObject) => {
|
||
|
try {
|
||
|
const { data: storageList } = await jfetch(
|
||
|
`${BACKEND_URL}/storage/room/${room.id}?includeWithSets=false`,
|
||
|
{
|
||
|
headers: authHeader.value,
|
||
|
}
|
||
|
);
|
||
|
const { data: setList } = await jfetch(
|
||
|
`${BACKEND_URL}/storage/set/room/${room.id}`,
|
||
|
{
|
||
|
headers: authHeader.value,
|
||
|
}
|
||
|
);
|
||
|
storages.value = [...storageList, ...setList];
|
||
|
} catch (e) {
|
||
|
console.error(e);
|
||
|
}
|
||
|
};
|
||
|
</script>
|
||
|
|
||
|
<style scoped lang="scss">
|
||
|
.custom-storage {
|
||
|
}
|
||
|
</style>
|