Updated mobile layout: collapsing WorkbenchTabs, floating aSide. UI fixes. (#7066)

Signed-off-by: Alexander Platov <alexander.platov@hardcoreeng.com>
This commit is contained in:
Alexander Platov 2024-10-30 19:19:04 +03:00 committed by GitHub
parent e4bf53ad1b
commit 11a6236712
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 349 additions and 181 deletions

View File

@ -501,6 +501,7 @@ input.search {
.ml-8 { margin-left: 2rem; }
.ml-10 { margin-left: 2.5rem; }
.ml-12 { margin-left: 3rem; }
.ml-14 { margin-left: 3.5rem; }
.ml-22 { margin-left: 5.5rem; }
.ml-auto { margin-left: auto; }
.mr-0-5 { margin-right: .125rem; }
@ -744,6 +745,7 @@ input.search {
.min-h-16 { min-height: 4rem; }
.min-h-30 { min-height: 7.5rem; }
.min-h-60 { min-height: 15rem; }
.max-w-0 { max-width: 0; }
.max-w-2 { max-width: .5rem; }
.max-w-4 { max-width: 1rem; }
.max-w-9 { max-width: 2.25rem; }

View File

@ -141,23 +141,40 @@
}
.antiPanel-navigator {
position: relative;
min-width: 12.5rem;
max-width: 22.5rem;
width: 17.5rem;
background-color: var(--theme-navpanel-color);
&:not(.right) {
min-width: 12.5rem;
max-width: 22.5rem;
width: 17.5rem;
}
&.right {
overflow: hidden;
border-radius: var(--medium-BorderRadius) 0 0 var(--medium-BorderRadius);
}
}
@media (max-width: 1024px) {
.antiPanel-navigator {
position: fixed;
top: calc(var(--status-bar-height) + 1px);
height: calc(100% - var(--status-bar-height) - 1px);
background-color: var(--theme-navpanel-color);
filter: drop-shadow(2px 0 1px rgba(0, 0, 0, .2));
z-index: 450;
&:not(.right) {
top: calc(var(--status-bar-height) + 1px);
height: calc(100% - var(--status-bar-height) - 1px);
filter: drop-shadow(2px 0 5px rgba(0, 0, 0, .2));
&.portrait { left: 0; }
&.landscape { left: var(--app-panel-width); }
&.portrait { left: 0; }
&.landscape { left: var(--app-panel-width); }
}
&.right {
top: var(--status-bar-height);
right: 0;
height: calc(100% - var(--status-bar-height) - 1px);
background-color: var(--theme-statusbar-color);
filter: drop-shadow(-2px 0 5px rgba(0, 0, 0, .2));
}
}
}
.antiPanel-component {

View File

@ -78,6 +78,7 @@
scrollbar-width: none;
--body-font-size: .875rem;
--status-bar-height: 36px;
--status-bar-normal-height: 36px;
--panel-aside-width: 25rem; // 20rem;
--font-family: 'IBM Plex Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto;
--mono-font: 'IBM Plex Mono', monospace;
@ -89,6 +90,10 @@
&::after,
&::before { box-sizing: border-box; }
@media (max-width: 480px) {
--status-bar-height: 70px;
}
}
:root {
--app-height: 100%;

View File

@ -36,7 +36,7 @@
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="container main flex-row-center flex-gap-2 {orientation} {kind}"
class="container main flex-between flex-gap-2 {orientation} {kind}"
style:max-width={orientation === 'horizontal' ? maxSize : 'auto'}
style:max-height={orientation === 'vertical' ? maxSize : 'auto'}
class:active={highlighted}
@ -52,7 +52,7 @@
</div>
{/if}
<span class="overflow-label">
<span class="overflow-label flex-grow">
{#if label}
{label}
{:else if labelIntl}
@ -100,11 +100,14 @@
&.horizontal {
padding: 0.125rem 0.125rem 0.125rem 0.5rem;
height: 1.625rem;
min-height: 1.625rem;
min-width: 6rem;
}
&.vertical {
padding: 0.5rem 0.125rem 0.125rem 0.125rem;
width: 1.625rem;
min-height: 6rem;
writing-mode: vertical-rl;
text-orientation: sideways;
}

View File

@ -168,7 +168,7 @@
<Header
type={'type-panel'}
noPrint={!printHeader}
{adaptive}
adaptive={$deviceInfo.isMobile ? 'disabled' : adaptive}
{hideBefore}
{hideSearch}
{hideActions}
@ -261,21 +261,21 @@
</Header>
<div class="popupPanel-body {$deviceInfo.isMobile ? 'mobile' : 'main'}" class:asideShown>
{#if $deviceInfo.isMobile}
<Scroller horizontal padding={'.5rem .75rem'}>
<div
class="popupPanel-body__mobile"
use:resizeObserver={(element) => {
innerWidth = element.clientWidth
}}
>
<div
class="popupPanel-body__mobile"
use:resizeObserver={(element) => {
innerWidth = element.clientWidth
}}
>
<Scroller horizontal padding={'.5rem .75rem'}>
{#if $$slots.header && isHeader}
<div class="popupPanel-body__header mobile bottom-divider" class:max={useMaxWidth}>
<slot name="header" />
</div>
{/if}
<slot />
</div>
</Scroller>
</Scroller>
</div>
{:else}
<div
class="popupPanel-body__main"

View File

@ -14,10 +14,12 @@
// limitations under the License.
-->
<script lang="ts">
import { onDestroy, onMount } from 'svelte'
import { onMount } from 'svelte'
import { resizeObserver } from '..'
export let scroller: HTMLElement
export let gap: 'none' | 'small' | 'big' = 'small'
export let padding: string | undefined = undefined
let divBar: HTMLElement
let isScrolling: boolean = false
@ -31,7 +33,7 @@
$: stepStyle = gap === 'small' ? 'gap-1' : gap === 'big' ? 'gap-2' : ''
const checkBar = (): void => {
if (divBar && scroller) {
if (divBar !== undefined && scroller !== undefined) {
const trackW = scroller.clientWidth
const scrollW = scroller.scrollWidth
const proc = scrollW / trackW
@ -40,13 +42,13 @@
if (mask === 'none') divBar.style.visibility = 'hidden'
else {
divBar.style.visibility = 'visible'
if (divBar) {
if (timer) {
if (divBar !== undefined) {
if (timer != null) {
clearTimeout(timer)
divBar.style.opacity = '1'
}
timer = setTimeout(() => {
if (divBar) divBar.style.opacity = '0'
if (divBar != null) divBar.style.opacity = '0'
}, 2000)
}
}
@ -54,8 +56,8 @@
}
}
const onScroll = (event: MouseEvent): void => {
if (isScrolling && divBar && scroller) {
const onScroll = (event: PointerEvent): void => {
if (isScrolling && divBar !== undefined && scroller !== undefined) {
const rectScroll = scroller.getBoundingClientRect()
let X = event.clientX - dX
if (X < rectScroll.left) X = rectScroll.left
@ -67,22 +69,22 @@
scroller.scrollLeft = (scroller.scrollWidth - scroller.clientWidth) * procBar
}
}
const onScrollEnd = (event: MouseEvent): void => {
const onScrollEnd = (event: PointerEvent): void => {
const el: HTMLElement = event.currentTarget as HTMLElement
if (el && isScrolling) {
document.removeEventListener('mousemove', onScroll)
if (el !== undefined && isScrolling) {
document.removeEventListener('pointermove', onScroll)
document.body.style.userSelect = 'auto'
document.body.style.webkitUserSelect = 'auto'
}
document.removeEventListener('mouseup', onScrollEnd)
document.removeEventListener('pointerup', onScrollEnd)
isScrolling = false
}
const onScrollStart = (event: MouseEvent): void => {
const onScrollStart = (event: PointerEvent): void => {
const el: HTMLElement = event.currentTarget as HTMLElement
if (el && scroller) {
if (el !== undefined && scroller !== undefined) {
dX = event.clientX - el.getBoundingClientRect().x
document.addEventListener('mouseup', onScrollEnd)
document.addEventListener('mousemove', onScroll)
document.addEventListener('pointerup', onScrollEnd)
document.addEventListener('pointermove', onScroll)
document.body.style.userSelect = 'none'
document.body.style.webkitUserSelect = 'none'
isScrolling = true
@ -90,8 +92,8 @@
}
const checkMask = (): void => {
maskLeft = !!(scroller && scroller.scrollLeft > 1)
maskRight = !!(scroller && scroller.scrollWidth - scroller.scrollLeft - scroller.clientWidth > 1)
maskLeft = scroller !== undefined && scroller.scrollLeft > 1
maskRight = scroller !== undefined && scroller.scrollWidth - scroller.scrollLeft - scroller.clientWidth > 1
if (maskLeft || maskRight) {
if (maskLeft && maskRight) mask = 'both'
else if (maskLeft) mask = 'left'
@ -102,30 +104,12 @@
}
onMount(() => {
if (scroller) {
const observer = new IntersectionObserver(
() => {
checkMask()
},
{ root: null, threshold: 0.1 }
)
const tempEl = scroller.querySelector('*') as HTMLElement
if (tempEl) observer.observe(tempEl)
checkMask()
scroller.addEventListener('scroll', checkMask)
}
if (scroller !== undefined) checkMask()
})
onDestroy(() => {
if (scroller) scroller.removeEventListener('scroll', checkMask)
})
const _resize = (): void => {
checkMask()
}
</script>
<svelte:window on:resize={_resize} />
<div class="scrollerbar-container">
<div bind:this={scroller} class="antiStatesBar mask-{mask} {stepStyle}">
<div class="scrollerbar-container" use:resizeObserver={checkMask}>
<div bind:this={scroller} class="antiStatesBar mask-{mask} {stepStyle}" style:padding on:scroll={checkMask}>
<slot />
</div>
<!-- svelte-ignore a11y-no-static-element-interactions -->
@ -133,8 +117,8 @@
class="bar"
class:hovered={isScrolling}
bind:this={divBar}
on:mousedown={onScrollStart}
on:mouseleave={checkMask}
on:pointerdown={onScrollStart}
on:pointerleave={checkMask}
/>
<div class="track" class:hovered={isScrolling} />
</div>

View File

@ -16,7 +16,7 @@
<script lang="ts">
export let size: 'x-small' | 'small' | 'medium' | 'large'
export let filled: boolean = false
const fill: string = 'currentColor'
export let fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">

View File

@ -15,7 +15,7 @@
-->
<script lang="ts">
export let size: 'x-small' | 'small' | 'medium' | 'large'
const fill: string = 'currentColor'
export let fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">

View File

@ -1,6 +1,6 @@
<script lang="ts">
export let size: 'small' | 'medium' | 'large'
const fill: string = 'currentColor'
export let fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">

View File

@ -5,7 +5,16 @@
import { deviceSizes, deviceWidths } from '../../types'
// import { applicationShortcutKey } from '../../utils'
import { Theme } from '@hcengineering/theme'
import { IconArrowLeft, IconArrowRight, checkMobile, deviceOptionsStore as deviceInfo } from '../../'
import {
IconArrowLeft,
IconArrowRight,
checkMobile,
deviceOptionsStore as deviceInfo,
checkAdaptiveMatching,
ButtonIcon,
IconDetailsFilled,
IconDetails
} from '../../'
import { desktopPlatform, getCurrentLocation, location, locationStorageKeyId, navigate } from '../../location'
import uiPlugin from '../../plugin'
import Component from '../Component.svelte'
@ -129,6 +138,14 @@
}
}
updateDeviceSize()
$: secondRow = checkAdaptiveMatching($deviceInfo.size, 'xs')
$: asideFloat = $deviceInfo.navigator.float
$: asideOpen = $deviceInfo.aside.visible
$: appsMini =
$deviceInfo.isMobile &&
(($deviceInfo.isPortrait && $deviceInfo.docWidth <= 480) ||
(!$deviceInfo.isPortrait && $deviceInfo.docHeight <= 480))
</script>
<svelte:window bind:innerWidth={docWidth} bind:innerHeight={docHeight} />
@ -161,9 +178,15 @@
</button>
</div>
{/if}
<div class="flex-row-center left-items flex-gap-0-5" style:-webkit-app-region={'no-drag'}>
<RootBarExtension position="left" />
</div>
{#if !secondRow}
<div
class="flex-row-center left-items flex-gap-0-5"
class:ml-14={appsMini}
style:-webkit-app-region={'no-drag'}
>
<RootBarExtension position="left" />
</div>
{/if}
<div
class="flex-row-center justify-center status-info"
style:margin-left={(isPortrait && docWidth <= 480) || (!isPortrait && docHeight <= 480) ? '1.5rem' : '0'}
@ -177,17 +200,54 @@
{/if}
</div>
<div class="flex-row-reverse" style:-webkit-app-region={'no-drag'}>
{#if asideFloat && !secondRow}
<div class="antiHSpacer x2" />
<ButtonIcon
icon={asideOpen ? IconDetailsFilled : IconDetails}
iconProps={{ fill: 'var(--theme-dark-color)' }}
kind={'tertiary'}
size={'extra-small'}
hasMenu
pressed={$deviceInfo.aside.visible}
on:click={() => {
$deviceInfo.aside.visible = !$deviceInfo.aside.visible
}}
/>
{/if}
<div class="clock">
<Clock />
</div>
<div class="flex-row-center gap-statusbar">
<RootBarExtension position="right" />
{#if !secondRow}
<RootBarExtension position="right" />
{/if}
<FontSizeSelector />
<ThemeSelector />
<LangSelector />
</div>
</div>
</div>
{#if secondRow}
<div class="flex-between h-full content-color gap-3 px-2 second-row" style:-webkit-app-region={'no-drag'}>
<div class="flex-row-center flex-gap-0-5">
<RootBarExtension position="left" />
</div>
<div class="flex-row-center flex-gap-0-5">
<RootBarExtension position="right" />
<ButtonIcon
icon={asideOpen ? IconDetailsFilled : IconDetails}
iconProps={{ fill: 'var(--theme-dark-color)' }}
kind={'tertiary'}
size={'extra-small'}
hasMenu
pressed={$deviceInfo.aside.visible}
on:click={() => {
$deviceInfo.aside.visible = !$deviceInfo.aside.visible
}}
/>
</div>
</div>
{/if}
</div>
<div class="app">
{#if application}
@ -213,6 +273,7 @@
.antiStatusBar {
-webkit-app-region: drag;
min-width: 0;
min-height: var(--status-bar-height);
height: var(--status-bar-height);
// min-width: 600px;
@ -248,6 +309,21 @@
margin: 0 12px 0 8px;
}
.second-row {
display: none;
}
@media (max-width: 480px) {
display: flex;
flex-direction: column;
gap: 2px;
padding: 2px 0;
width: 100%;
.second-row {
display: flex;
}
}
@media print {
display: none;
}

View File

@ -17,7 +17,7 @@
{#each sorted as ext (ext[1].id)}
{#if ext[0] === position}
<div id={ext[1].id} style:margin-right={'1px'}>
<div id={ext[1].id} class="clear-mins" style:margin-right={'1px'}>
<Component
is={ext[1].component}
props={ext[1].props}

View File

@ -311,6 +311,7 @@ export const deviceOptionsStore = writable<DeviceOptions>({
isPortrait: false,
isMobile: false,
navigator: { visible: true, float: false, direction: 'vertical' },
aside: { visible: true },
fontSize: 0,
size: null,
sizes: { xs: false, sm: false, md: false, lg: false, xl: false, xxl: false },

View File

@ -166,7 +166,7 @@ export const settingsSeparators: DefSeparators = [
export const mainSeparators: DefSeparators = [
{ minSize: 30, size: 'auto', maxSize: 'auto' },
{ minSize: 20, size: 30, maxSize: 80, float: 'sidebar' }
{ minSize: 25, size: 30, maxSize: 80, float: 'sidebar' }
]
export const secondNavSeparators: DefSeparators = [{ minSize: 7, size: 7.5, maxSize: 15, float: 'navigator' }, null]

View File

@ -378,6 +378,7 @@ export interface DeviceOptions {
isPortrait: boolean
isMobile: boolean
navigator: { visible: boolean, float: boolean, direction: 'vertical' | 'horizontal' }
aside: { visible: boolean }
fontSize: number
size: WidthType | null
sizes: Record<WidthType, boolean>

View File

@ -5,7 +5,8 @@ import {
getLocation,
type Location,
navigate,
languageStore
languageStore,
deviceOptionsStore as deviceInfo
} from '@hcengineering/ui'
import { type Ref, type Doc, type Class, generateId } from '@hcengineering/core'
import activity, { type ActivityMessage } from '@hcengineering/activity'
@ -179,6 +180,10 @@ export async function replyToThread (message: ActivityMessage, e: Event): Promis
const fromSidebar = isElementFromSidebar(e.target as HTMLElement)
const loc = getCurrentLocation()
const dev = get(deviceInfo)
dev.aside.visible = true
deviceInfo.set(dev)
threadMessagesStore.set(message)
if (fromSidebar) {

View File

@ -23,7 +23,7 @@
export let targetEmp: Person
export let key: string
export let onChange: (key: string, value: boolean) => void
export let selected = false
export let selected: boolean = false
const client = getClient()
const hierarchy = client.getHierarchy()

View File

@ -16,13 +16,14 @@
import { Person } from '@hcengineering/contact'
import { Doc, Mixin, Ref } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { CheckBox, Label } from '@hcengineering/ui'
import { Label, RadioButton } from '@hcengineering/ui'
import { FixedColumn } from '@hcengineering/view-resources'
export let value: Person
export let targetEmp: Person
export let cast: Ref<Mixin<Doc>> | undefined = undefined
export let key: string
export let selected = false
export let selected: boolean = false
export let onChange: (key: string, value: boolean) => void
const client = getClient()
@ -38,51 +39,51 @@
return (value as any)[key] === (targetEmp as any)[key]
}
$: attribute = hierarchy.findAttribute(cast ?? value._class, key)
const change = (sel: boolean): void => {
selected = sel
onChange(key, sel)
}
</script>
{#if !isEqual(value, targetEmp, key)}
<div class="box flex-row-center flex-between">
<div class="ml-4">
<div class="box flex-row-center flex-gap-4 flex-grow">
<FixedColumn key={'mergeLabel'} addClass={'ml-4'}>
{#if attribute?.label}
<Label label={attribute.label} />
{:else}
{key}
{/if}
</div>
</FixedColumn>
<div class="flex-center">
<div class="mr-2">
<CheckBox
circle
checked={selected}
on:value={(e) => {
selected = false
onChange(key, false)
}}
/>
</div>
<slot name="item" item={value} />
</div>
<div class="flex-row-center" />
<div class="flex-center">
<div class="mr-2">
<CheckBox
circle
checked={!selected}
on:value={(e) => {
selected = true
onChange(key, true)
}}
/>
</div>
<slot name="item" item={targetEmp} />
</div>
<FixedColumn key={'mergeFirst'} addClass="flex-row-center flex-gap-2 cursor-pointer">
<RadioButton
bind:group={selected}
value={false}
action={() => {
if (selected) change(false)
}}
>
<slot name="item" item={value} />
</RadioButton>
</FixedColumn>
<FixedColumn key={'mergeSecond'} addClass="flex-row-center flex-gap-2 cursor-pointer">
<RadioButton
bind:group={selected}
value={true}
action={() => {
if (!selected) change(true)
}}
>
<slot name="item" item={targetEmp} />
</RadioButton>
</FixedColumn>
</div>
{/if}
<style lang="scss">
.box {
margin: 0.5rem;
margin: 0.5rem 0;
padding: 0.5rem;
flex-shrink: 0;
border: 1px dashed var(--accent-color);

View File

@ -225,7 +225,7 @@
function selectMixin (mixin: Ref<Mixin<Doc>>, field: string, targetValue: boolean) {
const upd = mixinUpdate[mixin] ?? {}
if (!targetValue) {
;(upd as any)[field] = (value as any)[field]
;(upd as any)[field] = (sourcePerson as any)[mixin][field]
} else {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete (upd as any)[field]
@ -354,7 +354,7 @@
shape={'circle'}
/>
</div>
>>
<span class="mx-4">&gt;&gt;</span>
<div class="flex-row-center">
<UserBox
_class={contact.class.Person}
@ -387,13 +387,7 @@
<Avatar person={item} size={'x-large'} icon={contact.icon.Person} name={item.name} />
</svelte:fragment>
</MergeComparer>
<MergeComparer
key="name"
value={sourcePerson}
targetEmp={targetPerson}
onChange={select}
selected={update.name !== undefined}
>
<MergeComparer key="name" value={sourcePerson} targetEmp={targetPerson} onChange={select} selected>
<svelte:fragment slot="item" let:item>
{getName(client.getHierarchy(), item)}
</svelte:fragment>
@ -405,7 +399,7 @@
targetEmp={targetPerson}
onChange={select}
_class={contact.mixin.Employee}
selected={toAny(update)[attribute[0]] !== undefined}
selected
/>
{/each}
{#each mixins as mixin}
@ -419,7 +413,7 @@
selectMixin(mixin, key, value)
}}
_class={mixin}
selected={toAny(mixinUpdate)?.[mixin]?.[attribute] !== undefined}
selected
/>
{/each}
{/each}

View File

@ -48,4 +48,4 @@
})
</script>
<div bind:this={parentElement}></div>
<div bind:this={parentElement} class="hidden"></div>

View File

@ -17,8 +17,10 @@
import { uploads } from '../store'
</script>
<div class="flex-row-center flex-gap-2">
{#each $uploads as upload}
<FileUploadStatusBar {upload} />
{/each}
</div>
{#if $uploads.length > 0}
<div class="flex-row-center flex-gap-2">
{#each $uploads as upload}
<FileUploadStatusBar {upload} />
{/each}
</div>
{/if}

View File

@ -7,8 +7,8 @@
let parentElement: HTMLDivElement
onMount(() => {
pushRootBarComponent('right', uploader.component.FileUploadExt)
pushRootBarComponent('right', uploader.component.FileUploadExt, 10)
})
</script>
<div bind:this={parentElement}></div>
<div bind:this={parentElement} class="hidden"></div>

View File

@ -1,3 +1,4 @@
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
@ -43,11 +44,13 @@
}
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="flex-no-shrink{addClass ? ` ${addClass}` : ''}"
style:text-align={justify !== '' ? justify : ''}
style:min-width={`${$fixedWidthStore[key] ?? 0}px`}
use:resizeObserver={resize}
on:click
>
<slot />
</div>

View File

@ -69,8 +69,8 @@
}
&.small,
&.small .icon-container {
width: calc(var(--status-bar-height) - 8px);
height: calc(var(--status-bar-height) - 8px);
width: calc(var(--status-bar-normal-height) - 8px);
height: calc(var(--status-bar-normal-height) - 8px);
border-radius: 0.25rem;
}
&.small.selected {

View File

@ -54,8 +54,8 @@
height: 2rem;
}
&.mini {
width: 1.5rem;
height: 1.5rem;
width: 1.75rem;
height: 1.75rem;
}
&.red {
background-color: rgb(246, 105, 77);

View File

@ -158,6 +158,7 @@
const linkProviders = client.getModel().findAllSync(view.mixin.LinkIdProvider, {})
$deviceInfo.navigator.visible = getMetadata(workbench.metadata.NavigationExpandedDefault) ?? true
$deviceInfo.aside.visible = getMetadata(workbench.metadata.NavigationExpandedDefault) ?? true
async function toggleNav (): Promise<void> {
$deviceInfo.navigator.visible = !$deviceInfo.navigator.visible
@ -635,10 +636,12 @@
$: if ($deviceInfo.docWidth <= 1024 && !$deviceInfo.navigator.float) {
$deviceInfo.navigator.visible = false
$deviceInfo.navigator.float = true
$deviceInfo.aside.visible = false
} else if ($deviceInfo.docWidth > 1024 && $deviceInfo.navigator.float) {
if (getMetadata(workbench.metadata.NavigationExpandedDefault) === undefined) {
$deviceInfo.navigator.float = false
$deviceInfo.navigator.visible = true
$deviceInfo.aside.visible = true
}
}
const checkOnHide = (): void => {
@ -975,13 +978,28 @@
</div>
{/if}
</div>
{#if $sidebarStore.variant === SidebarVariant.EXPANDED}
<Separator name={'main'} index={0} color={'transparent'} separatorSize={0} short />
{#if !$deviceInfo.navigator.float}
{#if $sidebarStore.variant === SidebarVariant.EXPANDED}
<Separator name={'main'} index={0} color={'transparent'} separatorSize={0} short />
{/if}
<WidgetsBar />
{/if}
<WidgetsBar />
</div>
<Dock />
<div bind:this={cover} class="cover" />
{#if $deviceInfo.navigator.float}
<div
class="antiPanel-navigator right no-print {$deviceInfo.navigator.direction === 'horizontal'
? 'portrait'
: 'landscape'}"
style:display={$deviceInfo.aside.visible ? 'flex' : 'none'}
>
<Separator name={'main'} index={0} color={'transparent'} separatorSize={0} short float={'sidebar'} />
<div class="antiPanel-wrap__content hulyNavPanel-container">
<WidgetsBar />
</div>
</div>
{/if}
<TooltipInstance />
<PanelInstance bind:this={panelInstance} contentPanel={elementPanel}>
<svelte:fragment slot="panel-header">
@ -993,7 +1011,9 @@
<ActionContext context={{ mode: 'popup' }} />
</svelte:fragment>
</Popup>
<ComponentExtensions extension={workbench.extensions.WorkbenchExtensions} />
<div class="hidden max-w-0 max-h-0">
<ComponentExtensions extension={workbench.extensions.WorkbenchExtensions} />
</div>
<BrowserNotificatator />
{/if}
@ -1071,11 +1091,11 @@
}
.logo-container.mini {
left: 4px;
width: 1.5rem;
height: 1.5rem;
width: 1.75rem;
height: 1.75rem;
}
.topmenu-container.mini {
left: calc(1.5rem + 8px);
left: calc(1.75rem + 8px);
}
}

View File

@ -13,26 +13,75 @@
// limitations under the License.
-->
<script lang="ts">
import { createTab, tabsStore } from '../workbench'
import { createTab, tabsStore, tabIdStore } from '../workbench'
import { WorkbenchTabs } from '../index'
import WorkbenchTabPresenter from './WorkbenchTabPresenter.svelte'
import { IconAdd, ButtonIcon } from '@hcengineering/ui'
import {
IconAdd,
IconMoreH,
ButtonIcon,
ScrollerBar,
deviceOptionsStore as deviceInfo,
checkAdaptiveMatching,
showPopup
} from '@hcengineering/ui'
export let popup: boolean = false
let scroller: HTMLElement
let element: HTMLButtonElement
let pressed: boolean = false
$: devSize = $deviceInfo.size
$: mini = checkAdaptiveMatching(devSize, 'md')
$: selectedTab = $tabsStore.find((ts) => ts._id === $tabIdStore)
const showTabs = (): void => {
pressed = true
showPopup(WorkbenchTabs, { popup: true }, element, () => {
pressed = false
})
}
</script>
<div class="root flex-gap-1">
{#each $tabsStore as tab (tab._id)}
<WorkbenchTabPresenter {tab} />
{/each}
<div class="ml-1-5 plus-button mr-1">
<ButtonIcon icon={IconAdd} size="min" kind="tertiary" on:click={createTab} />
</div>
<div
class={popup ? 'selectPopup' : 'flex-row-center flex-gap-2'}
style:padding={popup ? '.5rem' : mini ? '.25rem .25rem .25rem 0' : '0 .25rem 0 0'}
>
{#if popup}
<div class="scroll">
<div class="box flex-gap-1">
{#each $tabsStore.filter((ts) => ts._id !== $tabIdStore) as tab (tab._id)}
<WorkbenchTabPresenter {tab} />
{/each}
</div>
</div>
{:else if !mini}
<ScrollerBar bind:scroller padding={'.25rem 0'}>
{#each $tabsStore as tab (tab._id)}
<WorkbenchTabPresenter {tab} />
{/each}
</ScrollerBar>
{:else if selectedTab !== undefined}
<WorkbenchTabPresenter tab={selectedTab} />
<ButtonIcon
bind:element
icon={IconMoreH}
iconProps={{ fill: 'var(--theme-dark-color)' }}
size={'extra-small'}
kind={'tertiary'}
hasMenu
{pressed}
on:click={showTabs}
/>
{/if}
{#if !popup}
<ButtonIcon
icon={IconAdd}
iconProps={{ fill: 'var(--theme-dark-color)' }}
size={'extra-small'}
kind={'tertiary'}
on:click={createTab}
/>
{/if}
</div>
<style>
.root {
display: flex;
align-items: center;
}
.plus-button {
height: 0.875rem;
}
</style>

View File

@ -17,7 +17,7 @@
import { WidgetPreference, SidebarEvent, TxSidebarEvent, OpenSidebarWidgetParams } from '@hcengineering/workbench'
import { Tx } from '@hcengineering/core'
import { onMount } from 'svelte'
import { panelstore } from '@hcengineering/ui'
import { panelstore, deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
import workbench from '../../plugin'
import { createWidgetTab, openWidget, sidebarStore, SidebarVariant } from '../../sidebar'
@ -61,7 +61,12 @@
})
</script>
<div class="antiPanel-application vertical root" class:mini id="sidebar">
<div
id="sidebar"
class="antiPanel-application vertical sidebar-container"
class:mini
class:float={$deviceInfo.navigator.float}
>
{#if mini}
<SidebarMini {widgets} {preferences} />
{:else if $sidebarStore.variant === SidebarVariant.EXPANDED}
@ -70,15 +75,25 @@
</div>
<style lang="scss">
.root {
.sidebar-container {
flex-direction: row;
min-width: 25rem;
border-radius: 0 var(--medium-BorderRadius) var(--medium-BorderRadius) 0;
&.mini {
&.mini:not(.float) {
width: 3.5rem !important;
min-width: 3.5rem !important;
max-width: 3.5rem !important;
}
&.mini.float {
justify-content: flex-end;
}
}
@media (max-width: 1024px) {
.sidebar-container {
width: 100%;
border: 1px solid var(--theme-navpanel-divider);
border-radius: var(--medium-BorderRadius);
}
}
</style>

View File

@ -14,7 +14,7 @@
-->
<script lang="ts">
import { Widget, WidgetPreference, WidgetType } from '@hcengineering/workbench'
import { IconSettings, ModernButton, showPopup } from '@hcengineering/ui'
import { IconSettings, ModernButton, showPopup, deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
import { Ref } from '@hcengineering/core'
import WidgetPresenter from './/WidgetPresenter.svelte'
@ -31,7 +31,8 @@
function handleSelectWidget (widget: Widget): void {
if (selected === widget._id) {
minimizeSidebar(true)
if ($deviceInfo.navigator.float) $deviceInfo.aside.visible = false
else minimizeSidebar(true)
} else {
openWidget(widget, $sidebarStore.widgetsState.get(widget._id)?.data, { active: true, openedByUser: true })
}
@ -100,6 +101,7 @@
width: 3.5rem;
min-width: 3.5rem;
max-width: 3.5rem;
background-color: var(--theme-navpanel-color);
border-radius: 0 var(--medium-BorderRadius) var(--medium-BorderRadius) 0;
overflow-y: auto;
}

View File

@ -37,6 +37,7 @@ export { default as NavHeader } from './components/NavHeader.svelte'
export { default as SpecialElement } from './components/navigator/SpecialElement.svelte'
export { default as SpaceView } from './components/SpaceView.svelte'
export { default as TreeSeparator } from './components/navigator/TreeSeparator.svelte'
export { default as WorkbenchTabs } from './components/WorkbenchTabs.svelte'
export { SpecialView }
export * from './utils'

View File

@ -23,7 +23,9 @@ export class TalentDetailsPage extends CommonRecruitingPage {
readonly buttonFinalContact = (): Locator => this.formMergeContacts().locator('button', { hasText: 'Final contact' })
readonly buttonMergeRow = (): Locator => this.formMergeContacts().locator('div.box')
readonly buttonMergeRow = (hasText: string): Locator =>
this.formMergeContacts().locator('div.box.flex-row-center div.antiRadio', { hasText }).locator('label')
readonly buttonPopupMergeContacts = (): Locator =>
this.formMergeContacts().locator('button:has-text("Merge contacts")')
@ -66,32 +68,17 @@ export class TalentDetailsPage extends CommonRecruitingPage {
await this.buttonFinalContact().click()
await this.selectMenuItem(this.page, talentName.finalContactName)
await expect(
this.buttonMergeRow().locator('div.flex-center', { hasText: talentName.name }).locator('label.checkbox-container')
).toBeVisible()
await this.buttonMergeRow()
.locator('div.flex-center', { hasText: talentName.name })
.locator('label.checkbox-container')
.click()
await expect(this.buttonMergeRow(talentName.name)).toBeVisible()
await this.buttonMergeRow(talentName.name).click()
if (talentName.mergeLocation) {
await this.buttonMergeRow()
.locator('div.flex-center', { hasText: talentName.location })
.locator('label.checkbox-container')
.click()
await this.buttonMergeRow(talentName.location).click()
}
if (talentName.mergeTitle) {
await this.buttonMergeRow()
.locator('div.flex-center', { hasText: talentName.title })
.locator('label.checkbox-container')
.click()
await this.buttonMergeRow(talentName.title).click()
}
if (talentName.mergeSource) {
await this.buttonMergeRow()
.locator('div.flex-center', { hasText: talentName.source })
.locator('label.checkbox-container')
.click()
await this.buttonMergeRow(talentName.source).click()
}
await this.buttonPopupMergeContacts().click()