Fix Popup for @ document referencing and Emoji popup (#2398)

Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
Alexander Platov
2022-11-29 13:44:40 +07:00
committed by GitHub
parent 90bc1da39f
commit 63b0e63866
9 changed files with 177 additions and 116 deletions
@@ -17,7 +17,16 @@
import { Ref, RelatedDocument } from '@hcengineering/core'
import { getResource, IntlString } from '@hcengineering/platform'
import ui, { Button, createFocusManager, EditBox, FocusHandler, IconSearch, Label, ListView } from '@hcengineering/ui'
import ui, {
Button,
createFocusManager,
EditBox,
FocusHandler,
IconSearch,
Label,
ListView,
resizeObserver
} from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import presentation from '../plugin'
import { ObjectSearchCategory, ObjectSearchResult } from '../types'
@@ -135,48 +144,42 @@
<FocusHandler {manager} />
<form class="antiCard dialog completion" on:keydown={onKeyDown}>
<div class="p-4 flex-col flex-grow">
<form class="antiCard dialog completion" on:keydown={onKeyDown} use:resizeObserver={() => dispatch('changeSize')}>
<div class="header-dialog">
{#if label}
<div class="fs-title flex-grow mb-4">
<Label {label} />
</div>
{/if}
<div class="flex-col flex-grow">
<div class="flex">
{#each categories as c, i}
{@const status = categoryStatus[c._id] ?? 0}
<div class="flex">
<div class="ap-categoryItem">
<Button
focusIndex={i + 1}
kind={'transparent'}
showTooltip={{ label: c.label }}
selected={category?._id === c._id}
icon={c.icon}
size={'x-large'}
disabled={status === 0}
on:click={() => {
category = c
}}
/>
</div>
</div>
{/each}
</div>
<div class="mt-2 mb-1 flex-row-center">
<EditBox
focus
icon={IconSearch}
kind={'search-style'}
focusIndex={0}
bind:value={query}
on:input={() => updateItems(category, query, relatedDocuments)}
placeholder={category?.label}
/>
</div>
<Label label={ui.string.Suggested} />
<div class="flex-row-center gap-1">
{#each categories as c, i}
{@const status = categoryStatus[c._id] ?? 0}
<div class="ap-categoryItem">
<Button
focusIndex={i + 1}
kind={'transparent'}
showTooltip={{ label: c.label }}
selected={category?._id === c._id}
icon={c.icon}
size={'x-large'}
disabled={status === 0}
on:click={() => {
category = c
}}
/>
</div>
{/each}
</div>
<EditBox
focus
icon={IconSearch}
kind={'search-style'}
focusIndex={0}
bind:value={query}
on:input={() => updateItems(category, query, relatedDocuments)}
placeholder={category?.label}
/>
<Label label={ui.string.Suggested} />
</div>
<div class="antiCard-content min-h-60 max-h-60">
<ListView bind:this={list} bind:selection count={items.length}>
@@ -197,6 +200,14 @@
</form>
<style lang="scss">
.header-dialog {
display: flex;
flex-direction: column;
flex-shrink: 0;
padding: 1.125rem 1.125rem 0;
min-width: 0;
min-height: 0;
}
.completion {
z-index: 2000;
}
@@ -1,8 +1,10 @@
<script lang="ts">
import emojiRegex from 'emoji-regex'
import { getContext } from 'svelte'
import { createEventDispatcher } from 'svelte'
import { IntlString } from '@hcengineering/platform'
import { AnySvelteComponent, Label, tooltip } from '@hcengineering/ui'
import { Label, tooltip, Scroller, emojiSP } from '@hcengineering/ui'
import type { AnySvelteComponent } from '@hcengineering/ui'
import Emoji from './icons/Emoji.svelte'
import Food from './icons/Food.svelte'
import Nature from './icons/Nature.svelte'
@@ -15,6 +17,8 @@
let div: HTMLDivElement
const regex = emojiRegex()
const { currentFontSize } = getContext('fontsize') as { currentFontSize: string }
function getEmojis (startCode: number, endCode: number, postfix?: number[]): (string | undefined)[] {
return [...Array(endCode - startCode + 1).keys()].map((v) => {
const str = postfix ? String.fromCodePoint(v + startCode, ...postfix) : String.fromCodePoint(v + startCode)
@@ -101,44 +105,30 @@
]
const dispatch = createEventDispatcher()
const headerHeight = 55
function handleScrollToCategory (categoryId: string) {
const offset = document.getElementById(categoryId)?.offsetTop
if (offset) div.scrollTo(0, offset - headerHeight)
const el = document.getElementById(categoryId)
if (el) {
const next = el.nextElementSibling as HTMLElement
div.scroll(0, next.offsetTop - (currentFontSize === 'small-font' ? 14 : 16) * 1.75)
}
}
let currentCategory = categories[0]
const padding = 16
function handleScroll () {
const divTop = div?.getBoundingClientRect().top
const categoryDivs = div.getElementsByClassName('categoryName')
const i = Array.from(categoryDivs).findIndex((element) => {
if (element?.nodeType === Node.ELEMENT_NODE) {
const elementTop = element?.getBoundingClientRect().top
if (elementTop >= divTop + padding) {
return true
}
}
return false
})
let firstVisibleCategory: Element | null
if (i > 0) {
firstVisibleCategory = categoryDivs.item(i - 1)
} else {
firstVisibleCategory = categoryDivs.item(categoryDivs.length - 1)
}
if (firstVisibleCategory !== null) {
currentCategory = categories.find((c) => c.id === firstVisibleCategory!.id) ?? categories[0]
function handleScrolled (catId?: CustomEvent) {
if (catId) {
const curCat = categories.find((it) => it.id === catId.detail[catId.detail.length - 1])
if (curCat) currentCategory = curCat
}
}
</script>
<div class="antiPopup antiPopup-withHeader pb-3 popup">
<div class="flex-between ml-4 pt-2 pb-2 mr-4 header">
<div class="antiPopup antiPopup-withHeader popup">
<div class="flex-row-center popup-header">
{#each categories as category}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
use:tooltip={{ label: category.label }}
class="flex-grow pt-2 pb-2 pl-2 pr-2 element"
class="element m-0-5"
class:selected={currentCategory === category}
on:click={() => handleScrollToCategory(category.id)}
>
@@ -146,35 +136,69 @@
</div>
{/each}
</div>
<div class="flex-col vScroll" bind:this={div} on:scroll={handleScroll}>
<div class="w-85 flex-col">
<div class="scrolling">
<Scroller bind:divScroll={div} on:scrolledCategories={handleScrolled} fade={emojiSP} noStretch>
{#each categories as category}
<div class="ap-header">
<div id={category.id} class="ap-caption categoryName"><Label label={category.label} /></div>
<div id={category.id} class="scroll-header categoryHeader">
<Label label={category.label} />
</div>
<div class="palette ml-4">
<div class="palette ml-3">
{#each category.emojis as emoji}
{#if emoji !== undefined}
<div class="p-1 element" on:click={() => dispatch('close', emoji)}>{emoji}</div>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="element m-0-5" on:click={() => dispatch('close', emoji)}>{emoji}</div>
{/if}
{/each}
</div>
{/each}
</div>
</Scroller>
</div>
<div class="ap-space x2" />
</div>
<style lang="scss">
.popup {
height: 25rem;
height: 21.25rem;
}
.scrolling {
min-height: 0;
height: 16.5rem;
max-height: 16.5rem;
}
.popup-header {
margin: 0.75rem 0.75rem 0.5rem;
}
.scroll-header {
position: sticky;
top: 0;
margin: 0.75rem 0.75rem 0.25rem;
padding: 0.25rem 0.75rem;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
color: var(--dark-color);
background-color: var(--board-bg-color);
border-radius: 0.25rem;
&:first-child {
margin-top: 0;
}
}
.palette {
display: grid;
grid-template-columns: repeat(8, 1fr);
font-size: x-large;
display: flex;
flex-wrap: wrap;
width: 19.25rem;
font-size: 1.75rem;
}
.element {
display: flex;
justify-content: center;
align-items: center;
width: 2.5rem;
height: 2.5rem;
border-radius: 0.25rem;
color: var(--caption-color);
cursor: pointer;
&:hover {
background-color: var(--popup-bg-hover);
}
@@ -183,9 +207,4 @@
background-color: var(--popup-bg-hover);
}
}
.header {
justify-content: start;
border-bottom: 1px solid var(--divider-color);
}
</style>
@@ -15,7 +15,7 @@
-->
<script lang="ts">
import { ObjectSearchPopup, ObjectSearchResult } from '@hcengineering/presentation'
import { showPopup } from '@hcengineering/ui'
import { showPopup, resizeObserver, deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
import { onDestroy, onMount } from 'svelte'
import DummyPopup from './DummyPopup.svelte'
@@ -28,9 +28,14 @@
let popupClose: () => void
onMount(() => {
popupClose = showPopup(DummyPopup, {}, undefined, () => {
close()
})
popupClose = showPopup(
DummyPopup,
{},
undefined,
() => close(),
() => {},
{ overlay: false }
)
})
onDestroy(() => {
@@ -55,38 +60,42 @@
function updateStyle (): void {
const rect = clientRect()
const docW = document.body.clientWidth
const wDoc = $deviceInfo.docWidth
const hDoc = $deviceInfo.docHeight
let tempStyle = ''
if (rect.top < 292) {
if (rect.top < hDoc - rect.bottom) {
// 20rem - 1.75rem
tempStyle = `top: calc(${rect.bottom}px + .75rem); max-heigth: calc(115vh - ${rect.bottom}px - 1.75rem); `
tempStyle = `top: calc(${rect.bottom}px + .75rem); max-height: calc(${hDoc - rect.bottom}px - 1.75rem); `
} else {
tempStyle = `bottom: calc(115vh - ${rect.top}px + .75rem); max-heigth: calc(${rect.top}px - 1.75rem); `
tempStyle = `bottom: calc(${hDoc - rect.top}px + .75rem); max-height: calc(${rect.top}px - 1.75rem); `
}
if (docW - rect.left > 452) {
if (rect.left + wPopup > wDoc - 16) {
// 30rem - 1.75rem
tempStyle += `left: ${rect.left}px;`
tempStyle += 'right: 1rem;'
} else {
tempStyle += `right: calc(115vh - ${rect.right}px);`
tempStyle += `left: ${rect.left}px;`
}
style = tempStyle
}
let style = 'visibility: hidden'
$: if (popup) updateStyle()
let wPopup: number = 0
</script>
<svelte:window on:resize={() => updateStyle()} />
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="overlay" on:click={() => close()} />
<div
class="overlay"
on:click={() => {
close()
bind:this={popup}
class="completion"
{style}
use:resizeObserver={(element) => {
wPopup = element.clientWidth
updateStyle()
}}
/>
<div bind:this={popup} class="antiPopup antiPopup-withHeader antiPopup-withCategory completion" {style}>
<div class="completion">
<ObjectSearchPopup bind:this={searchPopup} {query} on:close={(evt) => dispatchItem(evt.detail)} />
</div>
>
<ObjectSearchPopup bind:this={searchPopup} {query} on:close={(evt) => dispatchItem(evt.detail)} />
</div>
<style lang="scss">
@@ -101,10 +110,10 @@
.completion {
position: absolute;
min-width: 20rem;
max-width: 30rem;
min-height: 0;
height: 20rem;
// min-width: 20rem;
// max-width: 30rem;
// min-height: 0;
// max-height: 30rem;
z-index: 2000;
}
</style>
+3
View File
@@ -417,6 +417,8 @@ input.search {
.my-2 { margin: .5rem 0; }
.my-4 { margin: 1rem 0; }
.m-0-5 { margin: .125rem; }
.pl-1 { padding-left: .25rem; }
.pl-2 { padding-left: .5rem; }
.pl-3 { padding-left: .75rem; }
@@ -494,6 +496,7 @@ input.search {
.h-full { height: 100%; }
.h-75p { height: 75%; }
.h-2 { height: .5rem; }
.h-3 { height: .75rem; }
.h-4 { height: 1rem; }
.h-6 { height: 1.5rem; }
.h-7 { height: 1.75rem; }
+2 -2
View File
@@ -134,9 +134,9 @@
&.dialog {
width: 45rem;
height: max-content;
height: auto;
max-width: 60rem;
max-height: calc(100vh - 2rem);
max-height: inherit;
}
&.mobile {
width: 90vw;
+4 -2
View File
@@ -208,6 +208,8 @@
.ap-space {
flex-shrink: 0;
height: .25rem;
&.x2 { height: .5rem; }
&.x3 { height: .75rem; }
}
.ap-scroll {
flex-grow: 1;
@@ -312,8 +314,8 @@
font-size: .75rem;
font-weight: 600;
text-transform: uppercase;
color: var(--theme-content-dark-color);
}
color: var(--dark-color);
}
}
}
@@ -67,6 +67,7 @@
{#each Array(count) as _, row}
<slot name="category" item={row} />
<div
class="list-item"
class:selection={row === selection}
on:mouseover={() => onRow(row)}
on:focus={() => {}}
@@ -84,6 +85,9 @@
// border-radius: 0.25rem;
user-select: none;
.list-item {
border-radius: 0.25rem;
}
.selection {
background-color: var(--menu-bg-select);
}
+19 -7
View File
@@ -13,7 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import { beforeUpdate, afterUpdate, onDestroy, onMount } from 'svelte'
import { beforeUpdate, afterUpdate, onDestroy, onMount, createEventDispatcher } from 'svelte'
import { resizeObserver } from '../resize'
import { themeStore as themeOptions } from '@hcengineering/theme'
import type { FadeOptions } from '../types'
@@ -27,13 +27,16 @@
export let invertScroll: boolean = false
export let horizontal: boolean = false
export let contentDirection: 'vertical' | 'vertical-reverse' | 'horizontal' = 'vertical'
export let noStretch: boolean = false
export let divScroll: HTMLElement | undefined = undefined
const dispatch = createEventDispatcher()
let mask: 'top' | 'bottom' | 'both' | 'none' = 'none'
let topCrop: 'top' | 'bottom' | 'full' | 'none' = 'none'
let topCropValue: number = 0
let maskH: 'left' | 'right' | 'both' | 'none' = 'none'
let divScroll: HTMLElement
let divHScroll: HTMLElement
let divBox: HTMLElement
let divBar: HTMLElement
@@ -222,21 +225,29 @@
}
const scrollDown = (): void => {
divScroll.scrollTop = divScroll.scrollHeight - divHeight
if (divScroll) divScroll.scrollTop = divScroll.scrollHeight - divHeight
}
$: if (scrolling && belowContent && belowContent > 10) scrollDown()
const checkIntersection = (entries: IntersectionObserverEntry[], observer: IntersectionObserver) => {
const interArr: Element[] = []
entries.forEach((el) => {
if (el.isIntersecting) inter.add(el.target)
else inter.delete(el.target)
if (el.isIntersecting) {
inter.add(el.target)
interArr.push(el.target)
} else inter.delete(el.target)
})
if (interArr.length > 0) {
dispatch('lastScrolledCategory', interArr[interArr.length - 1]?.getAttribute('id'))
const interCats: string[] = interArr.map((it) => it.getAttribute('id') as string)
dispatch('scrolledCategories', interCats)
}
}
const checkIntersectionFade = () => {
topCrop = 'none'
topCropValue = 0
if (!fade.offset?.top) return
if (!fade.offset?.top || !divScroll) return
const offset = divScroll.getBoundingClientRect().top
inter.forEach((el) => {
const rect = el.getBoundingClientRect()
@@ -322,13 +333,14 @@
: contentDirection === 'vertical-reverse'
? 'column-reverse'
: 'row'}
style:height={contentDirection === 'vertical-reverse' ? 'max-content' : '100%'}
style:height={contentDirection === 'vertical-reverse' ? 'max-content' : noStretch ? 'auto' : '100%'}
use:resizeObserver={(element) => {
boxHeight = element.clientHeight
boxWidth = element.clientWidth
}}
on:dragover
on:drop
on:scroll
>
{#if bottomStart}<div class="flex-grow flex-shrink" />{/if}
<slot />
+1
View File
@@ -227,6 +227,7 @@ export const defaultSP: FadeOptions = { multipler: { top: 0, bottom: 0 } }
export const tableSP: FadeOptions = { offset: { top: true }, multipler: { top: 2.5, bottom: 0 } }
export const tableHRscheduleY: FadeOptions = { offset: { top: true }, multipler: { top: 5, bottom: 0 } }
export const issueSP: FadeOptions = { offset: { top: true }, multipler: { top: 3, bottom: 0 } }
export const emojiSP: FadeOptions = { offset: { top: true }, multipler: { top: 1.5, bottom: 0 } }
export interface DeviceOptions {
docWidth: number