LOVE: scaling the floor (#8145)

Signed-off-by: Alexander Platov <alexander.platov@hardcoreeng.com>
This commit is contained in:
Alexander Platov 2025-03-06 11:37:17 +03:00 committed by GitHub
parent d33f7ba28f
commit 618034e6fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 155 additions and 34 deletions

View File

@ -39,6 +39,7 @@
& > span { & > span {
color: var(--theme-caption-color); color: var(--theme-caption-color);
transition: font-size .15s ease-in-out;
cursor: default; cursor: default;
} }
} }
@ -46,12 +47,18 @@
overflow: hidden; overflow: hidden;
width: 100%; width: 100%;
height: 100%; height: 100%;
max-width: 90%; min-width: .5rem;
max-height: 90%; min-height: .5rem;
transition-property: max-width, max-height; max-width: 100%;
transition-duration: .15s; max-height: 100%;
transition-timing-function: ease-in-out; aspect-ratio: 1;
transition: transform .15s ease-in-out;
transform: scale(.9);
.preview & {
min-width: .5rem;
min-height: .5rem;
}
&.hovered:hover { &.hovered:hover {
background-color: transparent; background-color: transparent;
@ -64,8 +71,8 @@
&.hovered { background-color: var(--theme-popup-hover); } &.hovered { background-color: var(--theme-popup-hover); }
} }
&:not(.preview)::before { top: calc(100% / var(--huly-floor-roomHeight) / 3 * -1.6); } :not(.preview) &::before { top: calc(100% / var(--huly-floor-roomHeight) / 3 * -1.6); }
&.preview::before { .preview &::before {
top: calc(100% / var(--huly-floor-roomHeight) / 3 * -1); top: calc(100% / var(--huly-floor-roomHeight) / 3 * -1);
bottom: calc(100% / var(--huly-floor-roomHeight) / 3 * -1); bottom: calc(100% / var(--huly-floor-roomHeight) / 3 * -1);
left: calc(100% / var(--huly-floor-roomWidth) / 3 * -1); left: calc(100% / var(--huly-floor-roomWidth) / 3 * -1);
@ -80,8 +87,7 @@
cursor: pointer; cursor: pointer;
&:hover { &:hover {
max-width: 100%; transform: scale(1);
max-height: 100%;
} }
} }
} }
@ -95,8 +101,12 @@
.floorGrid-configureRoom { .floorGrid-configureRoom {
&__header { &__header {
top: calc(100% / var(--huly-floor-roomHeight) / 3 * -1.6 + .75rem - 1px); top: calc(100% / var(--huly-floor-roomHeight) / 3 * -1.6 + .75rem - 1px);
// transition: top .15s ease-in-out;
& > .antiEditBox input { font-size: .8125rem; }
& > .antiEditBox input {
font-size: .8125rem;
transition: font-size .15s ease-in-out;
}
} }
&__field { &__field {
width: 90%; width: 90%;
@ -139,6 +149,33 @@
} }
} }
// Room conners and header
@container floorGridContainer (max-width: 800px) {
.floorGrid-room::before,
.floorGrid-configureRoom::before { border-radius: 0.75rem; }
.floorGrid-room .floorGrid-room__header {
top: calc(100% / var(--huly-floor-roomHeight) / 3 * -1.6 + .125rem + 1px);
span { font-size: .75rem; }
svg { width: 0.75rem; }
}
.floorGrid-configureRoom .floorGrid-configureRoom__header {
top: calc(100% / var(--huly-floor-roomHeight) / 3 * -1.6 + .25rem);
& > .antiEditBox input { font-size: .75rem; }
}
}
@container floorGridContainer (max-width: 600px) {
.floorGrid-room::before,
.floorGrid-configureRoom::before { border-radius: 0.5rem; }
.floorGrid-room .floorGrid-room__header {
top: calc(100% / var(--huly-floor-roomHeight) / 3 * -1.6 + .125rem);
span { font-size: .7rem; }
}
.floorGrid-configureRoom .floorGrid-configureRoom__header {
top: calc(100% / var(--huly-floor-roomHeight) / 3 * -1.6 + .125rem);
& > .antiEditBox input { font-size: .7rem; }
}
}
// Room // Room
@media only screen and (max-width: 1024px) { @media only screen and (max-width: 1024px) {
.room-container + .bar > .bar__left-panel button span { display: none; } .room-container + .bar > .bar__left-panel button span { display: none; }

View File

@ -41,6 +41,7 @@
export let categoryHeader: boolean = false export let categoryHeader: boolean = false
export let hiddenHeader: boolean = false export let hiddenHeader: boolean = false
export let background: string | undefined = undefined export let background: string | undefined = undefined
export let contentAlign: 'start' | 'center' | 'end' | 'stretch' = 'start'
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@ -125,7 +126,7 @@
<slot name="actions" /> <slot name="actions" />
</div> </div>
</button> </button>
<div class="hulyAccordionItem-content"> <div class="hulyAccordionItem-content" style:align-items={contentAlign}>
<slot /> <slot />
</div> </div>
</div> </div>

View File

@ -14,15 +14,25 @@
--> -->
<script lang="ts"> <script lang="ts">
import { AccountRole, Ref, getCurrentAccount, hasAccountRole, WithLookup } from '@hcengineering/core' import { AccountRole, Ref, getCurrentAccount, hasAccountRole, WithLookup } from '@hcengineering/core'
import { Breadcrumb, Header, IconEdit, ModernButton, Component } from '@hcengineering/ui' import ui, {
Breadcrumb,
Header,
IconEdit,
ModernButton,
Component,
IconMaxWidth,
IconMinWidth,
Button
} from '@hcengineering/ui'
import { Floor, Room } from '@hcengineering/love' import { Floor, Room } from '@hcengineering/love'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import { ViewletSelector } from '@hcengineering/view-resources' import { ViewletSelector } from '@hcengineering/view-resources'
import { Viewlet, ViewletPreference } from '@hcengineering/view' import { Viewlet, ViewletPreference } from '@hcengineering/view'
import lovePlg from '../plugin' import lovePlg from '../plugin'
import { currentRoom, floors } from '../stores' import { currentRoom, floors, loveUseMaxWidth } from '../stores'
import ControlBar from './ControlBar.svelte' import ControlBar from './ControlBar.svelte'
import { toggleLoveUseMaxWidth } from '../utils'
export let rooms: Room[] = [] export let rooms: Room[] = []
export let floor: Ref<Floor> export let floor: Ref<Floor>
@ -48,6 +58,14 @@
<ViewletSelector bind:viewlet bind:preference bind:loading viewletQuery={{ attachTo: lovePlg.class.Floor }} /> <ViewletSelector bind:viewlet bind:preference bind:loading viewletQuery={{ attachTo: lovePlg.class.Floor }} />
</svelte:fragment> </svelte:fragment>
<svelte:fragment slot="actions"> <svelte:fragment slot="actions">
<Button
icon={$loveUseMaxWidth ? IconMaxWidth : IconMinWidth}
kind={'regular'}
pressed={$loveUseMaxWidth}
size={'medium'}
showTooltip={{ label: ui.string.UseMaxWidth }}
on:click={toggleLoveUseMaxWidth}
/>
{#if editable} {#if editable}
<ModernButton <ModernButton
icon={IconEdit} icon={IconEdit}

View File

@ -15,7 +15,10 @@
<script lang="ts"> <script lang="ts">
import { DocumentUpdate, Ref } from '@hcengineering/core' import { DocumentUpdate, Ref } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation' import { getClient } from '@hcengineering/presentation'
import { import ui, {
Button,
IconMaxWidth,
IconMinWidth,
Breadcrumb, Breadcrumb,
ButtonIcon, ButtonIcon,
ModernButton, ModernButton,
@ -27,9 +30,9 @@
} from '@hcengineering/ui' } from '@hcengineering/ui'
import { Floor, GRID_WIDTH, Room, getFreeSpace } from '@hcengineering/love' import { Floor, GRID_WIDTH, Room, getFreeSpace } from '@hcengineering/love'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import { floors, lockedRoom } from '../stores' import { floors, lockedRoom, loveUseMaxWidth } from '../stores'
import { FloorSize, RGBAColor, ResizeInitParams, RoomSide, shadowError, shadowNormal } from '../types' import { FloorSize, RGBAColor, ResizeInitParams, RoomSide, shadowError, shadowNormal } from '../types'
import { calculateFloorSize } from '../utils' import { calculateFloorSize, toggleLoveUseMaxWidth } from '../utils'
import AddRoomPopup from './AddRoomPopup.svelte' import AddRoomPopup from './AddRoomPopup.svelte'
import FloorGrid from './FloorGrid.svelte' import FloorGrid from './FloorGrid.svelte'
import RoomConfigure from './RoomConfigure.svelte' import RoomConfigure from './RoomConfigure.svelte'
@ -295,6 +298,14 @@
<svelte:fragment slot="actions"> <svelte:fragment slot="actions">
<ButtonIcon icon={IconAdd} size={'small'} on:click={addRoom} /> <ButtonIcon icon={IconAdd} size={'small'} on:click={addRoom} />
<div class="hulyHeader-divider short" /> <div class="hulyHeader-divider short" />
<Button
icon={$loveUseMaxWidth ? IconMaxWidth : IconMinWidth}
kind={'regular'}
pressed={$loveUseMaxWidth}
size={'medium'}
showTooltip={{ label: ui.string.UseMaxWidth }}
on:click={toggleLoveUseMaxWidth}
/>
<ModernButton <ModernButton
label={lovePlg.string.FinalizeEditing} label={lovePlg.string.FinalizeEditing}
kind={'primary'} kind={'primary'}
@ -308,6 +319,7 @@
<FloorGrid <FloorGrid
bind:floorContainer bind:floorContainer
{rows} {rows}
useResize
on:resize={(event) => { on:resize={(event) => {
if (event.detail === undefined) return if (event.detail === undefined) return
const { width, height } = event.detail const { width, height } = event.detail

View File

@ -16,24 +16,50 @@
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import { resizeObserver } from '@hcengineering/ui' import { resizeObserver } from '@hcengineering/ui'
import { GRID_WIDTH } from '@hcengineering/love' import { GRID_WIDTH } from '@hcengineering/love'
import { loveUseMaxWidth } from '../stores'
export let floorContainer: HTMLDivElement export let floorContainer: HTMLDivElement
export let marginInline: string = 'auto' export let marginInline: string = 'auto'
export let preview: boolean = false export let preview: boolean = false
export let useResize: boolean = false
export let rows: number = 5 export let rows: number = 5
const MAX_SIZE_REM = 2 // Maximum cell size in rems
const MIN_SIZE_REM = 1.5
const MAX_SIZE_UMW_REM = 4 // Maximum cell size in rems (Use max width)
const MIN_SIZE_UMW_REM = 2
const MAX_SIZE_PRE_REM = 2 // Maximum cell size in rems (preview mode)
const MIN_SIZE_PRE_REM = 0.5
const FULL_GW = GRID_WIDTH + 2
$: minWidth = preview
? `${FULL_GW * MIN_SIZE_PRE_REM}rem`
: `${FULL_GW * ($loveUseMaxWidth ? MIN_SIZE_UMW_REM : MIN_SIZE_REM)}rem`
$: minHeight = preview
? `${rows * MIN_SIZE_PRE_REM}rem`
: `${rows * ($loveUseMaxWidth ? MIN_SIZE_UMW_REM : MIN_SIZE_REM)}rem`
$: maxWidth = preview
? `${FULL_GW * MAX_SIZE_PRE_REM}rem`
: `${FULL_GW * ($loveUseMaxWidth ? MAX_SIZE_UMW_REM : MAX_SIZE_REM)}rem`
$: maxHeight = preview
? `${rows * MAX_SIZE_PRE_REM}rem`
: `${rows * ($loveUseMaxWidth ? MAX_SIZE_UMW_REM : MAX_SIZE_REM)}rem`
$: style = `min-width: ${minWidth}; min-height: ${minHeight}; max-width: ${maxWidth}; max-height: ${maxHeight};`
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
</script> </script>
{#if preview} {#if !useResize}
<!-- svelte-ignore a11y-mouse-events-have-key-events --> <!-- svelte-ignore a11y-mouse-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions --> <!-- svelte-ignore a11y-no-static-element-interactions -->
<div <div
bind:this={floorContainer} bind:this={floorContainer}
class="floorGrid" class="floorGrid"
style:grid-template-columns={`repeat(${GRID_WIDTH + 2}, 1fr)`} {style}
style:grid-template-rows={rows ? `repeat(${rows}, 1fr)` : undefined} style:grid-template-columns={`repeat(${FULL_GW}, ${preview ? `minmax(${MIN_SIZE_PRE_REM}rem, ${MAX_SIZE_PRE_REM}rem)` : '1fr'})`}
style:max-width={`${(GRID_WIDTH + 2) * 4}rem`} style:grid-template-rows={rows
? `repeat(${rows}, ${preview ? `minmax(${MIN_SIZE_PRE_REM}rem, ${MAX_SIZE_PRE_REM}rem)` : '1fr'})`
: undefined}
style:aspect-ratio={`${FULL_GW} / ${rows}`}
style:margin-inline={marginInline} style:margin-inline={marginInline}
on:mouseover on:mouseover
> >
@ -46,9 +72,10 @@
dispatch('resize', { width: element.clientWidth, height: element.clientHeight }) dispatch('resize', { width: element.clientWidth, height: element.clientHeight })
}} }}
class="floorGrid" class="floorGrid"
style:grid-template-columns={`repeat(${GRID_WIDTH + 2}, 1fr)`} {style}
style:grid-template-columns={`repeat(${FULL_GW}, 1fr)`}
style:grid-template-rows={rows ? `repeat(${rows}, 1fr)` : undefined} style:grid-template-rows={rows ? `repeat(${rows}, 1fr)` : undefined}
style:max-width={`${(GRID_WIDTH + 2) * 4}rem`} style:aspect-ratio={`${FULL_GW} / ${rows}`}
style:margin-inline={marginInline} style:margin-inline={marginInline}
> >
<slot /> <slot />
@ -59,12 +86,13 @@
.floorGrid { .floorGrid {
position: relative; position: relative;
display: grid; display: grid;
grid-template-columns: repeat(17, 1fr);
grid-auto-flow: row; grid-auto-flow: row;
grid-auto-rows: 1fr; grid-auto-rows: 1fr;
place-items: stretch; place-items: stretch;
place-content: center; place-content: start center;
flex-shrink: 0;
gap: 0; gap: 0;
width: 100%; width: 100%;
container: floorGridContainer / inline-size;
} }
</style> </style>

View File

@ -107,6 +107,7 @@
{disabled} {disabled}
{kind} {kind}
categoryHeader categoryHeader
contentAlign={'center'}
on:select on:select
> >
<svelte:fragment slot="actions"> <svelte:fragment slot="actions">

View File

@ -32,8 +32,8 @@
$: rows = calculateFloorSize(rooms) - 1 $: rows = calculateFloorSize(rooms) - 1
</script> </script>
<Scroller padding="1rem" bottomPadding="4rem" horizontal> <Scroller padding="1rem" bottomPadding="4rem" align={'center'} horizontal>
<FloorGrid bind:floorContainer {rows} preview> <FloorGrid bind:floorContainer {rows}>
{#each rooms as room} {#each rooms as room}
<RoomPreview {room} info={getInfo(room._id, $infos)} on:open /> <RoomPreview {room} info={getInfo(room._id, $infos)} on:open />
{/each} {/each}

View File

@ -139,14 +139,14 @@
.label { .label {
position: absolute; position: absolute;
bottom: 0; top: 0;
left: 0; left: 0;
max-width: 100%; max-width: 100%;
padding: 0.25rem 0.5rem 0.5rem 1rem; padding: 0.5rem 0.5rem 0.25rem 1rem;
color: white; color: white;
font-weight: 500; font-weight: 500;
background-color: rgba(0, 0, 0, 0.3); background-color: rgba(0, 0, 0, 0.3);
border-radius: 0 0.25rem 0 0.75rem; border-radius: 0.75rem 0 0.25rem 0;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.25rem; gap: 0.25rem;
@ -158,8 +158,8 @@
text-shadow: 0 0 0.25rem black; text-shadow: 0 0 0.25rem black;
} }
&.filled { &.filled {
padding: 0.25rem 1rem 0.5rem 1rem; padding: 0.5rem 1rem 0.25rem 1rem;
border-radius: 0 0 0.75rem 0.75rem; border-radius: 0.75rem 0.75rem 0 0;
} }
} }
&.small .label { &.small .label {

View File

@ -144,7 +144,6 @@
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y-click-events-have-key-events -->
<div <div
class="floorGrid-room" class="floorGrid-room"
class:preview
class:hovered class:hovered
class:disabled class:disabled
class:myOffice={$myInfo?.room === room._id} class:myOffice={$myInfo?.room === room._id}

View File

@ -17,6 +17,7 @@ import { aiBotEmailSocialId } from '@hcengineering/ai-bot'
import love from './plugin' import love from './plugin'
import { personRefByPersonIdStore } from '@hcengineering/contact-resources' import { personRefByPersonIdStore } from '@hcengineering/contact-resources'
import { getLoveUseMaxWidth } from './utils'
export const rooms = writable<Room[]>([]) export const rooms = writable<Room[]>([])
export const myOffice = derived(rooms, (val) => { export const myOffice = derived(rooms, (val) => {
@ -141,3 +142,5 @@ onClient(() => {
) )
}) })
export const lockedRoom = writable<string>('') export const lockedRoom = writable<string>('')
export const loveUseMaxWidth = writable<boolean>(getLoveUseMaxWidth())

View File

@ -86,7 +86,14 @@ import { get, writable } from 'svelte/store'
import { sendMessage } from './broadcast' import { sendMessage } from './broadcast'
import RoomSettingsPopup from './components/RoomSettingsPopup.svelte' import RoomSettingsPopup from './components/RoomSettingsPopup.svelte'
import love from './plugin' import love from './plugin'
import { $myPreferences, currentMeetingMinutes, currentRoom, myOffice, selectedRoomPlace } from './stores' import {
$myPreferences,
currentMeetingMinutes,
currentRoom,
myOffice,
selectedRoomPlace,
loveUseMaxWidth
} from './stores'
export const selectedCamId = 'selectedDevice_cam' export const selectedCamId = 'selectedDevice_cam'
export const selectedMicId = 'selectedDevice_mic' export const selectedMicId = 'selectedDevice_mic'
@ -1158,3 +1165,18 @@ export async function getMeetingMinutesTitle (
return meeting?.title ?? '' return meeting?.title ?? ''
} }
// Floor Scaling (Max Width)
const useMaxWidthKey = 'loveUseMaxWidth'
export const saveLoveUseMaxWidth = (useMaxWidth: boolean): void => {
if (useMaxWidth) localStorage.setItem(useMaxWidthKey, 'true')
else localStorage.removeItem(useMaxWidthKey)
}
export const getLoveUseMaxWidth = (): boolean => {
return localStorage.getItem(useMaxWidthKey) === 'true'
}
export const toggleLoveUseMaxWidth = (): void => {
const value = !get(loveUseMaxWidth)
loveUseMaxWidth.set(value)
saveLoveUseMaxWidth(value)
}