mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-13 03:40:48 +00:00
374 lines
14 KiB
Svelte
374 lines
14 KiB
Svelte
<!--
|
|
// Copyright © 2024 Hardcore Engineering Inc.
|
|
//
|
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License. You may
|
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
//
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
-->
|
|
<script lang="ts">
|
|
import { DocumentUpdate, Ref } from '@hcengineering/core'
|
|
import { getClient } from '@hcengineering/presentation'
|
|
import {
|
|
Breadcrumb,
|
|
ButtonIcon,
|
|
ModernButton,
|
|
Header,
|
|
IconAdd,
|
|
Scroller,
|
|
eventToHTMLElement,
|
|
showPopup
|
|
} from '@hcengineering/ui'
|
|
import { Floor, GRID_WIDTH, Room, getFreeSpace } from '@hcengineering/love'
|
|
import { createEventDispatcher } from 'svelte'
|
|
import { floors, lockedRoom } from '../stores'
|
|
import { FloorSize, RGBAColor, ResizeInitParams, RoomSide, shadowError, shadowNormal } from '../types'
|
|
import { calculateFloorSize } from '../utils'
|
|
import AddRoomPopup from './AddRoomPopup.svelte'
|
|
import FloorGrid from './FloorGrid.svelte'
|
|
import RoomConfigure from './RoomConfigure.svelte'
|
|
import lovePlg from '../plugin'
|
|
import { Contact } from '@hcengineering/contact'
|
|
|
|
export let rooms: Room[] = []
|
|
export let floor: Ref<Floor>
|
|
export let excludedPersons: Ref<Contact>[] = []
|
|
|
|
const client = getClient()
|
|
const dispatch = createEventDispatcher()
|
|
|
|
let divScroll: HTMLElement
|
|
let resizeInitParams: ResizeInitParams | undefined = undefined
|
|
let floorContainer: HTMLDivElement
|
|
let floorRect: DOMRect
|
|
let floorOffsetInline: number
|
|
const floorSize: FloorSize = {
|
|
cols: GRID_WIDTH + 2,
|
|
rows: 5,
|
|
width: 0,
|
|
height: 0,
|
|
cellSize: 4,
|
|
cellTop: 2.5,
|
|
cellRound: 0.75
|
|
}
|
|
const roomsConf: RoomConfigure[] = []
|
|
let block: boolean = false
|
|
let lockedID: number = -1
|
|
let locked: { room: Room, size: DOMRect, map: boolean[][], side?: RoomSide } | undefined = undefined
|
|
let dragged: { x: number, y: number, offsetX: number, offsetY: number } | undefined = undefined
|
|
let dragRoom: RoomConfigure
|
|
let dragShadow: boolean = false
|
|
let oldX: number = -1
|
|
let oldY: number = -1
|
|
|
|
let cursor: string = ''
|
|
$: document.body.style.cursor = cursor
|
|
|
|
async function updateRoom (id: Ref<Room>): Promise<void> {
|
|
if (locked !== undefined && resizeInitParams !== undefined) {
|
|
const room = rooms.find((r) => r._id === id)
|
|
if (room === undefined) {
|
|
return
|
|
}
|
|
const update: DocumentUpdate<Room> = {}
|
|
if (room.x !== resizeInitParams.x) {
|
|
update.x = room.x
|
|
}
|
|
if (room.y !== resizeInitParams.y) {
|
|
update.y = room.y
|
|
}
|
|
if (room.width !== resizeInitParams.width) {
|
|
update.width = room.width
|
|
}
|
|
if (room.height !== resizeInitParams.height) {
|
|
update.height = room.height
|
|
}
|
|
if (Object.keys(update).length > 0) {
|
|
await client.update(room, update)
|
|
}
|
|
}
|
|
}
|
|
|
|
function addRoom (e: MouseEvent): void {
|
|
showPopup(AddRoomPopup, { floor }, eventToHTMLElement(e))
|
|
}
|
|
|
|
$: selectedFloor = $floors.filter((fl) => fl._id === floor)[0]
|
|
|
|
const setShadowColor = (color: RGBAColor): void => {
|
|
const { r, g, b, a } = color
|
|
if (dragged && dragRoom) dragRoom.setShadowColor(r, g, b, a)
|
|
else if (roomsConf[lockedID]) roomsConf[lockedID].setShadowColor(r, g, b, a)
|
|
}
|
|
|
|
function startDragRoom (room: Room, size: DOMRect, n: number): void {
|
|
if (room === undefined || size === undefined) return
|
|
const map: boolean[][] = getFreeSpace(rooms, room, true)
|
|
locked = { room, size, map }
|
|
dragShadow = false
|
|
lockedID = n
|
|
divScroll.addEventListener('mousemove', dragMouseMove)
|
|
window.addEventListener('mouseup', docMouseUp)
|
|
}
|
|
|
|
function dragMouseMove (e: MouseEvent): void {
|
|
if ($lockedRoom === '' || block || dragged === undefined || locked === undefined) return
|
|
block = true
|
|
if (!dragShadow && dragged && dragRoom) {
|
|
setShadowColor(shadowNormal)
|
|
dragRoom.setShadow(0, 0, 2, 2)
|
|
dragShadow = true
|
|
}
|
|
dragged.x = e.clientX - floorRect.x - dragged.offsetX + floorSize.cellRound
|
|
dragged.y = e.clientY - floorRect.y - dragged.offsetY + floorSize.cellRound
|
|
let newX: number = Math.round((dragged.x - floorSize.cellRound) / floorSize.cellSize)
|
|
let newY: number = Math.round((dragged.y - floorSize.cellRound) / floorSize.cellSize)
|
|
newX = newX < 1 ? 0 : newX + locked.room.width > GRID_WIDTH ? GRID_WIDTH - locked.room.width : newX - 1
|
|
newY = newY < 1 ? 0 : newY - 1
|
|
if (oldX !== newX || oldY !== newY) {
|
|
let checkFree: boolean = true
|
|
for (let y = newY; y < newY + locked.room.height; y++) {
|
|
if (locked.map[y] === undefined) locked.map[y] = new Array(GRID_WIDTH).fill(true)
|
|
for (let x = newX; x < newX + locked.room.width; x++) {
|
|
if (locked.map[y][x] !== undefined && !locked.map[y][x]) checkFree = false
|
|
}
|
|
}
|
|
if (checkFree) {
|
|
locked.room.x = newX
|
|
locked.room.y = newY
|
|
setShadowColor(shadowNormal)
|
|
} else {
|
|
setShadowColor(shadowError)
|
|
}
|
|
oldX = newX
|
|
oldY = newY
|
|
}
|
|
block = false
|
|
}
|
|
|
|
function startResizeRoom (room: Room, size: DOMRect, side: RoomSide, n: number): void {
|
|
if (room === undefined || size === undefined || side === undefined) return
|
|
const map: boolean[][] = getFreeSpace(rooms, room)
|
|
locked = { room, size, map, side }
|
|
lockedID = n
|
|
window.addEventListener('mousemove', resizeMouseMove)
|
|
window.addEventListener('mouseup', docMouseUp)
|
|
}
|
|
|
|
function resizeMouseMove (e: MouseEvent): void {
|
|
if ($lockedRoom === '' || locked?.room === undefined || locked.side === undefined || block) return
|
|
block = true
|
|
const error: RoomSide = { top: false, bottom: false, left: false, right: false }
|
|
const room: Room = locked.room
|
|
locked.size = roomsConf[lockedID].getRect()
|
|
|
|
if (locked.side.bottom || locked.side.top) {
|
|
let newHeight: number = locked.side.bottom
|
|
? Math.round((e.clientY - locked.size.y - floorSize.cellRound) / floorSize.cellSize)
|
|
: Math.round((locked.size.y - e.clientY + locked.size.height - floorSize.cellTop) / floorSize.cellSize)
|
|
if (newHeight < 1) newHeight = 1
|
|
let newY: number = locked.side.bottom ? room.y : room.y + room.height - newHeight
|
|
if (newY < 0) {
|
|
newHeight += newY
|
|
newY = 0
|
|
}
|
|
let freeSpace: boolean = !(room.y === 0 && locked.side.top)
|
|
if (freeSpace) {
|
|
const startY: number = locked.side.bottom ? room.y + room.height : newY
|
|
const endY: number = locked.side.bottom ? room.y + newHeight : room.y
|
|
for (let y = startY; y < endY; y++) {
|
|
if (y > 0 && locked.map[y] === undefined) locked.map[y] = new Array(GRID_WIDTH).fill(true)
|
|
for (let x = room.x; x < room.x + room.width; x++) {
|
|
if (locked.map[y][x] !== undefined && !locked.map[y][x]) freeSpace = false
|
|
}
|
|
}
|
|
}
|
|
if (locked.side.bottom) {
|
|
if (e.clientY < locked.size.y + locked.size.height - 6) {
|
|
error.bottom = locked.room.height === 1
|
|
} else if (
|
|
e.clientY >= locked.size.y + locked.size.height - 6 &&
|
|
e.clientY < locked.size.y + locked.size.height + 6
|
|
) {
|
|
error.bottom = false
|
|
} else if (e.clientY >= locked.size.y + locked.size.height + 6) error.bottom = !freeSpace
|
|
} else {
|
|
if (e.clientY > locked.size.y - floorSize.cellTop + 6) {
|
|
error.top = locked.room.height === 1
|
|
} else if (
|
|
e.clientY <= locked.size.y - floorSize.cellTop + 6 &&
|
|
e.clientY > locked.size.y - floorSize.cellTop - 6
|
|
) {
|
|
error.bottom = false
|
|
} else if (e.clientY <= locked.size.y - floorSize.cellTop - 6) error.bottom = !freeSpace
|
|
}
|
|
if ((freeSpace && room.height < newHeight) || room.height > newHeight) {
|
|
rooms[lockedID].height = newHeight
|
|
if (locked.side.top) rooms[lockedID].y = newY
|
|
locked.room = rooms[lockedID]
|
|
locked.map = getFreeSpace(rooms, locked.room)
|
|
}
|
|
}
|
|
|
|
if (locked.side.left || locked.side.right) {
|
|
let newWidth: number = locked.side.right
|
|
? Math.round((e.clientX - locked.size.x - floorSize.cellRound) / floorSize.cellSize)
|
|
: Math.round((locked.size.x - e.clientX + locked.size.width - floorSize.cellRound) / floorSize.cellSize)
|
|
if (newWidth < 1) newWidth = 1
|
|
let newX: number = locked.side.right ? room.x : room.x + room.width - newWidth
|
|
if (newX < 0) {
|
|
newWidth += newX
|
|
newX = 0
|
|
}
|
|
if (newX + newWidth > GRID_WIDTH) newWidth = GRID_WIDTH - newX
|
|
let freeSpace: boolean = !(
|
|
(room.x === 0 && locked.side.left) ||
|
|
(room.x + room.width === GRID_WIDTH && locked.side.right)
|
|
)
|
|
if (freeSpace) {
|
|
const startX: number = locked.side.right ? room.x + room.width : newX
|
|
const endX: number = locked.side.right ? room.x + newWidth : room.x
|
|
for (let y = room.y; y < room.y + room.height; y++) {
|
|
for (let x = startX; x < endX; x++) {
|
|
if (locked.map[y][x] !== undefined && !locked.map[y][x]) freeSpace = false
|
|
}
|
|
}
|
|
}
|
|
if (locked.side.right) {
|
|
if (e.clientX < locked.size.x + locked.size.width + floorSize.cellRound - 6) {
|
|
error.right = locked.room.width === 1
|
|
} else if (
|
|
e.clientX >= locked.size.x + locked.size.width + floorSize.cellRound - 6 &&
|
|
e.clientX < locked.size.x + locked.size.width + floorSize.cellRound + 6
|
|
) {
|
|
error.right = false
|
|
} else if (e.clientX >= locked.size.x + locked.size.width + floorSize.cellRound + 6) error.right = !freeSpace
|
|
} else {
|
|
if (e.clientX > locked.size.x - floorSize.cellRound + 6) {
|
|
error.left = locked.room.width === 1
|
|
} else if (
|
|
e.clientX <= locked.size.x - floorSize.cellRound + 6 &&
|
|
e.clientX > locked.size.x - floorSize.cellRound - 6
|
|
) {
|
|
error.bottom = false
|
|
} else if (e.clientX <= locked.size.x - floorSize.cellRound - 6) error.bottom = !freeSpace
|
|
}
|
|
if ((freeSpace && room.width < newWidth) || room.width > newWidth) {
|
|
rooms[lockedID].width = newWidth
|
|
if (locked.side.left) rooms[lockedID].x = newX
|
|
locked.room = rooms[lockedID]
|
|
locked.map = getFreeSpace(rooms, locked.room)
|
|
}
|
|
}
|
|
|
|
setShadowColor(error.top || error.bottom || error.left || error.right ? shadowError : shadowNormal)
|
|
block = false
|
|
}
|
|
|
|
function docMouseUp (e: MouseEvent): void {
|
|
if (locked) updateRoom(locked.room._id)
|
|
if (dragged !== undefined) {
|
|
divScroll.removeEventListener('mousemove', dragMouseMove)
|
|
dragged = undefined
|
|
dragRoom.clearShadow()
|
|
} else {
|
|
window.removeEventListener('mousemove', resizeMouseMove)
|
|
roomsConf[lockedID].clearShadow()
|
|
}
|
|
window.removeEventListener('mouseup', docMouseUp)
|
|
cursor = ''
|
|
lockedRoom.set('')
|
|
lockedID = -1
|
|
}
|
|
$: rows = calculateFloorSize(rooms) + 2
|
|
</script>
|
|
|
|
<div class="hulyComponent">
|
|
<Header allowFullsize adaptive={'disabled'}>
|
|
<Breadcrumb title={selectedFloor?.name ?? ''} size={'large'} isCurrent />
|
|
<svelte:fragment slot="actions">
|
|
<ButtonIcon icon={IconAdd} size={'small'} on:click={addRoom} />
|
|
<div class="hulyHeader-divider short" />
|
|
<ModernButton
|
|
label={lovePlg.string.FinalizeEditing}
|
|
kind={'primary'}
|
|
size={'small'}
|
|
on:click={() => dispatch('configure')}
|
|
/>
|
|
</svelte:fragment>
|
|
</Header>
|
|
<div class="hulyComponent-content__column content">
|
|
<Scroller bind:divScroll padding={'1rem'} bottomPadding={'1rem'} horizontal>
|
|
<FloorGrid
|
|
bind:floorContainer
|
|
{rows}
|
|
useResize
|
|
on:resize={(event) => {
|
|
if (event.detail === undefined) return
|
|
const { width, height } = event.detail
|
|
floorSize.width = width
|
|
floorSize.height = height
|
|
floorSize.cellSize = width / (GRID_WIDTH + 2)
|
|
floorSize.cellTop = (floorSize.cellSize / 3) * 1.6
|
|
floorSize.cellRound = floorSize.cellSize / 5
|
|
floorSize.rows = calculateFloorSize(rooms) + 2
|
|
}}
|
|
>
|
|
{#each rooms as room, i}
|
|
<RoomConfigure
|
|
bind:this={roomsConf[i]}
|
|
room={lockedID === i && dragged !== undefined && (locked?.room.x !== room.x || locked?.room.y !== room.y)
|
|
? { ...room, x: locked?.room.x ?? room.x, y: locked?.room.y ?? room.y }
|
|
: room}
|
|
placed={lockedID === i && dragged !== undefined && locked?.room.x === room.x && locked?.room.y === room.y}
|
|
cellSize={floorSize.cellSize}
|
|
{excludedPersons}
|
|
on:cursor={(event) => {
|
|
if (event.detail) cursor = event.detail
|
|
}}
|
|
on:resize={(event) => {
|
|
if (event.detail === undefined) return
|
|
const { room, size, side } = event.detail
|
|
resizeInitParams = { x: room.x, y: room.y, width: room.width, height: room.height }
|
|
floorRect = floorContainer.getBoundingClientRect()
|
|
startResizeRoom(room, size, side, i)
|
|
}}
|
|
on:move={(event) => {
|
|
if (event.detail === undefined) return
|
|
const { room, size, offset } = event.detail
|
|
floorRect = floorContainer.getBoundingClientRect()
|
|
floorOffsetInline = floorRect.x - divScroll.getBoundingClientRect().x
|
|
dragged = {
|
|
x: size.x - floorRect.x + floorSize.cellRound,
|
|
y: size.y - floorRect.y + floorSize.cellRound,
|
|
offsetX: offset.x,
|
|
offsetY: offset.y
|
|
}
|
|
resizeInitParams = { x: room.x, y: room.y, width: room.width, height: room.height }
|
|
startDragRoom(room, size, i)
|
|
}}
|
|
on:updated={(event) => {
|
|
if (event.detail !== undefined && locked) locked.size = event.detail
|
|
}}
|
|
/>
|
|
{/each}
|
|
</FloorGrid>
|
|
{#if lockedID !== -1 && dragged !== undefined && locked !== undefined}
|
|
<RoomConfigure
|
|
bind:this={dragRoom}
|
|
room={locked.room}
|
|
cellSize={floorSize.cellSize}
|
|
top={dragged.y}
|
|
left={dragged.x + floorOffsetInline - floorSize.cellRound}
|
|
/>
|
|
{/if}
|
|
</Scroller>
|
|
</div>
|
|
</div>
|