Merge remote-tracking branch 'origin/develop' into staging

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2024-12-07 00:39:24 +07:00
commit b0fb5469af
No known key found for this signature in database
GPG Key ID: BD80F68D68D8F7F2
7 changed files with 110 additions and 192 deletions

View File

@ -352,7 +352,9 @@ async function processMigrateJsonForDoc (
if (value.startsWith('{')) {
// For some reason we have documents that are already markups
const jsonId = await saveCollabJson(ctx, storageAdapter, workspaceId, collabId, value)
const jsonId = await retry(5, async () => {
return await saveCollabJson(ctx, storageAdapter, workspaceId, collabId, value)
})
update[attributeName] = jsonId
continue
}
@ -371,23 +373,28 @@ async function processMigrateJsonForDoc (
// If document id has changed, save it with new name to ensure we will be able to load it later
const ydocId = makeCollabYdocId(collabId)
if (ydocId !== currentYdocId) {
ctx.info('saving collaborative doc with new name', { collabId, ydocId, currentYdocId })
const buffer = await storageAdapter.read(ctx, workspaceId, currentYdocId)
await storageAdapter.put(
ctx,
workspaceId,
ydocId,
Buffer.concat(buffer as any),
'application/ydoc',
buffer.length
)
await retry(5, async () => {
const stat = await storageAdapter.stat(ctx, workspaceId, currentYdocId)
if (stat !== undefined) {
const buffer = await storageAdapter.read(ctx, workspaceId, currentYdocId)
await storageAdapter.put(
ctx,
workspaceId,
ydocId,
Buffer.concat(buffer as any),
'application/ydoc',
buffer.length
)
}
})
}
const unset = update.$unset ?? {}
update.$unset = { ...unset, [attribute.name]: 1 }
} catch (err: any) {
ctx.warn('failed to process collaborative doc', { workspaceId, collabId, currentYdocId, err: err.message })
} catch (err) {
const error = err instanceof Error ? err.message : String(err)
ctx.warn('failed to process collaborative doc', { workspaceId, collabId, currentYdocId, error })
}
const unset = update.$unset ?? {}
update.$unset = { ...unset, [attribute.name]: 1 }
}
return update
@ -510,3 +517,19 @@ export const coreOperation: MigrateOperation = {
])
}
}
async function retry<T> (retries: number, op: () => Promise<T>): Promise<T> {
let error: any
while (retries > 0) {
retries--
try {
return await op()
} catch (err: any) {
error = err
if (retries !== 0) {
await new Promise((resolve) => setTimeout(resolve, 50))
}
}
}
throw error
}

View File

@ -275,8 +275,7 @@ export function createModel (builder: Builder): void {
label: love.string.Office,
type: WidgetType.Fixed,
icon: love.icon.Love,
component: love.component.LoveWidget,
headerLabel: love.string.Office
component: love.component.LoveWidget
},
love.ids.LoveWidget
)

View File

@ -86,14 +86,7 @@
let pressed: boolean = false
const clickMore = (e: MouseEvent): void => {
pressed = true
const value: SelectPopupValueType[] = [
{
id: 'configure',
icon: IconSettings,
label: configure ? plugin.string.FinalizeEditing : plugin.string.EditOffice
},
{ id: 'rename', icon: IconEdit, label: plugin.string.RenameAFloor }
]
const value: SelectPopupValueType[] = [{ id: 'rename', icon: IconEdit, label: plugin.string.RenameAFloor }]
showPopup(SelectPopup, { value }, eventToHTMLElement(e), (result) => {
if (result === 'configure') {
dispatch('configure', floor)

View File

@ -1,105 +0,0 @@
<!--
// 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 { AccountRole, Ref, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
import {
ButtonIcon,
IconAdd,
Label,
Scroller,
Separator,
defineSeparators,
eventToHTMLElement,
showPopup,
deviceOptionsStore as deviceInfo
} from '@hcengineering/ui'
import { Floor as FloorType, Room } from '@hcengineering/love'
import love from '../plugin'
import { floors, rooms } from '../stores'
import { loveSeparators } from '../types'
import EditFloorPopup from './EditFloorPopup.svelte'
import FloorPreview from './FloorPreview.svelte'
export let floor: Ref<FloorType>
export let configure: boolean
const me = getCurrentAccount()
function getRooms (rooms: Room[], floor: Ref<FloorType>): Room[] {
return rooms.filter((p) => p.floor === floor)
}
function addFloor (e: MouseEvent): void {
showPopup(EditFloorPopup, {}, eventToHTMLElement(e))
}
let editable: boolean = false
$: editable = hasAccountRole(me, AccountRole.Maintainer)
defineSeparators('love', loveSeparators)
</script>
{#if $deviceInfo.navigator.visible}
<div
class="antiPanel-navigator {$deviceInfo.navigator.direction === 'horizontal'
? 'portrait'
: 'landscape'} border-left will-change-opacity"
class:fly={$deviceInfo.navigator.float}
>
<div class="antiPanel-wrap__content hulyNavPanel-container">
<div class="hulyNavPanel-header" class:withButton={editable}>
<span class="overflow-label">
<Label label={love.string.Floors} />
</span>
{#if editable}
<ButtonIcon icon={IconAdd} kind={'primary'} size={'small'} on:click={addFloor} />
{/if}
</div>
<Scroller fade={{ multipler: { top: 4, bottom: 0 } }} shrink>
{#each $floors as _floor}
<FloorPreview
showRoomName
floor={_floor}
configurable
configure={configure && floor === _floor._id}
rooms={getRooms($rooms, _floor._id)}
selected={floor === _floor._id}
background={'var(--theme-navpanel-color)'}
on:configure={() => {
if (floor === _floor._id) {
configure = !configure
} else {
floor = _floor._id
configure = true
}
}}
on:select={() => {
floor = _floor._id
}}
/>
{/each}
</Scroller>
</div>
<Separator name={'love'} float={$deviceInfo.navigator.float ? 'navigator' : true} index={0} color={'transparent'} />
</div>
<Separator
name={'love'}
float={$deviceInfo.navigator.float}
index={0}
color={'transparent'}
separatorSize={0}
short
/>
{/if}

View File

@ -20,17 +20,26 @@
import { getClient } from '@hcengineering/presentation'
import { deviceOptionsStore as deviceInfo, getCurrentLocation, navigate } from '@hcengineering/ui'
import { onDestroy, onMount } from 'svelte'
import { activeFloor, floors, infos, invites, myInfo, myRequests, rooms, storePromise } from '../stores'
import {
activeFloor,
floors,
infos,
invites,
myInfo,
myRequests,
rooms,
selectedFloor,
storePromise
} from '../stores'
import { connectToMeeting, tryConnect } from '../utils'
import Floor from './Floor.svelte'
import FloorConfigure from './FloorConfigure.svelte'
import Floors from './Floors.svelte'
function getRooms (rooms: Room[], floor: Ref<FloorType>): Room[] {
return rooms.filter((p) => p.floor === floor)
}
let selectedFloor = $activeFloor === '' ? $floors[0]?._id : $activeFloor
$: floor = $selectedFloor ?? ($activeFloor === '' ? $floors[0]?._id : $activeFloor)
let configure: boolean = false
let replacedPanel: HTMLElement
@ -73,21 +82,15 @@
})
</script>
<Floors bind:floor={selectedFloor} bind:configure />
<div class="antiPanel-component filledNav" bind:this={replacedPanel}>
{#if configure}
<FloorConfigure
rooms={getRooms($rooms, selectedFloor)}
floor={selectedFloor}
rooms={getRooms($rooms, floor)}
{floor}
{excludedPersons}
on:configure={() => (configure = false)}
/>
{:else}
<Floor
rooms={getRooms($rooms, selectedFloor)}
floor={selectedFloor}
on:configure={() => (configure = true)}
on:open
/>
<Floor rooms={getRooms($rooms, floor)} {floor} on:configure={() => (configure = true)} on:open />
{/if}
</div>

View File

@ -13,74 +13,78 @@
// limitations under the License.
-->
<script lang="ts">
import { Floor, Room } from '@hcengineering/love'
import { Ref } from '@hcengineering/core'
import ui, { IconChevronLeft, ModernButton, Scroller } from '@hcengineering/ui'
import { AccountRole, getCurrentAccount, hasAccountRole, Ref } from '@hcengineering/core'
import love, { Floor, Room } from '@hcengineering/love'
import { Breadcrumbs, ButtonIcon, eventToHTMLElement, Header, IconAdd, Scroller, showPopup } from '@hcengineering/ui'
import { floors, rooms, selectedFloor } from '../stores'
import FloorPreview from './FloorPreview.svelte'
import { floors, rooms } from '../stores'
import IconLayers from './icons/Layers.svelte'
import love from '../plugin'
import EditFloorPopup from './EditFloorPopup.svelte'
let selectedFloor: Floor | undefined
let floorsSelector: boolean = false
let configure: boolean
$: if (selectedFloor === undefined && $floors.length > 0) {
selectedFloor = $floors[0]
const me = getCurrentAccount()
let floor: Floor | undefined
$: if (floor === undefined && $floors.length > 0) {
floor = $floors[0]
}
function getRooms (rooms: Room[], floor: Ref<Floor>): Room[] {
return rooms.filter((p) => p.floor === floor)
}
function changeMode (): void {
floorsSelector = !floorsSelector
function addFloor (e: MouseEvent): void {
showPopup(EditFloorPopup, {}, eventToHTMLElement(e))
}
function selectFloor (_id: Ref<Floor>): void {
selectedFloor = $floors.find((p) => p._id === _id)
floorsSelector = false
}
let editable: boolean = false
$: editable = hasAccountRole(me, AccountRole.Maintainer)
</script>
<Header
allowFullsize={false}
type="type-aside"
hideBefore={true}
hideActions={false}
hideDescription={true}
adaptive="disabled"
closeOnEscape={false}
on:close
>
<Breadcrumbs items={[{ label: love.string.Office }]} currentOnly />
<svelte:fragment slot="extra">
{#if editable}
<ButtonIcon icon={IconAdd} kind={'primary'} size={'small'} on:click={addFloor} />
{/if}
</svelte:fragment>
</Header>
<div class="hulyModal-container noTopIndent type-aside">
<div class="hulyModal-content">
<Scroller>
{#if floorsSelector}
{#each $floors as _floor}
<FloorPreview
showRoomName
floor={_floor}
rooms={getRooms($rooms, _floor._id)}
selected={selectedFloor?._id === _floor._id}
kind={'no-border'}
background={'var(--theme-panel-color)'}
on:select={() => {
selectFloor(_floor._id)
}}
/>
{/each}
{:else if selectedFloor}
{#each $floors as _floor}
<FloorPreview
floor={selectedFloor}
showRoomName
rooms={getRooms($rooms, selectedFloor._id)}
selected
isOpen
disabled
cropped
kind={'no-border'}
background={'var(--theme-panel-color)'}
floor={_floor}
configurable
configure={configure && floor?._id === _floor._id}
rooms={getRooms($rooms, _floor._id)}
selected={floor?._id === _floor._id}
background={'var(--theme-navpanel-color)'}
on:configure={() => {
if (floor?._id === _floor._id) {
configure = !configure
} else {
selectedFloor.set(_floor?._id)
floor = _floor
configure = true
}
}}
on:select={() => {
selectedFloor.set(_floor?._id)
floor = _floor
}}
/>
{/if}
{/each}
</Scroller>
</div>
{#if floorsSelector || $floors.length > 1}
<div class="hulyModal-footer">
<ModernButton
on:click={changeMode}
icon={floorsSelector ? IconChevronLeft : IconLayers}
label={floorsSelector ? ui.string.Back : love.string.ChangeFloor}
/>
</div>
{/if}
</div>

View File

@ -34,6 +34,7 @@ export const currentRoom = derived([rooms, myInfo], ([rooms, myInfo]) => {
return myInfo !== undefined ? rooms.find((p) => p._id === myInfo.room) : undefined
})
export const floors = writable<Floor[]>([])
export const selectedFloor = writable<Ref<Floor> | undefined>(undefined)
export const activeFloor = derived([rooms, myInfo, myOffice], ([rooms, myInfo, myOffice]) => {
let res: Ref<Floor> | undefined
if (myInfo !== undefined) {