mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-29 11:31:32 +00:00
Updated mobile layout: collapsing WorkbenchTabs, floating aSide. UI fixes. (#7066)
Signed-off-by: Alexander Platov <alexander.platov@hardcoreeng.com>
This commit is contained in:
parent
e4bf53ad1b
commit
11a6236712
@ -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; }
|
||||
|
@ -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 {
|
||||
|
@ -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%;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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>
|
||||
|
@ -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">
|
||||
|
@ -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">
|
||||
|
@ -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">
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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}
|
||||
|
@ -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 },
|
||||
|
@ -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]
|
||||
|
@ -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>
|
||||
|
@ -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) {
|
||||
|
@ -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()
|
||||
|
@ -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);
|
||||
|
@ -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">>></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}
|
||||
|
@ -48,4 +48,4 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<div bind:this={parentElement}></div>
|
||||
<div bind:this={parentElement} class="hidden"></div>
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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'
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user