Update Scroller: detect Table. Replace ScrollBox -> Scroller. Replace icon. (#1047)

Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
Alexander Platov 2022-02-23 16:10:21 +07:00 committed by GitHub
parent 5a74bda519
commit 7a8f581741
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 176 additions and 100 deletions

View File

@ -25,51 +25,116 @@
let divBack: HTMLElement
let divBar: HTMLElement
let divTrack: HTMLElement
let divEl: HTMLElement
let isBack: boolean = false
let divTHead: HTMLElement
let elTHead: Element
let isBack: boolean = false // ?
let isTHead: boolean = false
let hasTHeads: boolean = false // ?
let isScrolling: boolean = false
let enabledChecking: boolean = false
let dY: number
let visibleEl: number | undefined = undefined
const checkBack = (): void => {
const rectScroll = divScroll.getBoundingClientRect()
const el = divBox.querySelector('.scroller-back')
if (el && divScroll) {
const rectEl = el.getBoundingClientRect()
const bottom = document.body.clientHeight - rectScroll.bottom
let top = rectEl.top
if (top < rectScroll.top) top = rectScroll.top
if (top > rectScroll.bottom) top = rectScroll.top + rectScroll.height
divBack.style.left = rectScroll.left + 'px'
divBack.style.right = document.body.clientWidth - rectScroll.right + 'px'
divBack.style.top = top + 'px'
divBack.style.bottom = bottom + 'px'
divBack.style.height = 'auto'
divBack.style.width = 'auto'
divBack.style.visibility = 'visible'
isBack = true
} else {
divBack.style.visibility = 'hidden'
isBack = false
if (divBox) {
const el = divBox.querySelector('.scroller-back')
if (el && divScroll) {
const rectScroll = divScroll.getBoundingClientRect()
const rectEl = el.getBoundingClientRect()
const bottom = document.body.clientHeight - rectScroll.bottom
let top = rectEl.top
if (top < rectScroll.top) top = rectScroll.top
if (top > rectScroll.bottom) top = rectScroll.top + rectScroll.height
divBack.style.left = rectScroll.left + 'px'
divBack.style.right = document.body.clientWidth - rectScroll.right + 'px'
divBack.style.top = top + 'px'
divBack.style.bottom = bottom + 'px'
divBack.style.height = 'auto'
divBack.style.width = 'auto'
divBack.style.visibility = 'visible'
isBack = true
} else {
divBack.style.visibility = 'hidden'
isBack = false
}
}
}
let observer = new IntersectionObserver(changes => {
for (const change of changes) {
if (divBack) {
let rect = change.intersectionRect
if (rect) {
divBack.style.left = rect.left + 'px'
divBack.style.right = document.body.clientWidth - rect.right + 'px'
divBack.style.top = rect.top + 'px'
divBack.style.bottom = document.body.clientHeight - rect.bottom + 'px'
if (change.target) {
const temp: HTMLElement = change.target as HTMLElement
divBack.style.backgroundColor = temp.style.backgroundColor
}
} else divBack.style.visibility = 'hidden'
}
const checkTHeadSizes = (): void => {
if (elTHead && divTHead && divScroll) {
const elements = divTHead.querySelectorAll('div')
elements.forEach((el, i) => {
const th = elTHead.children.item(i)
if (th) el.style.width = th.clientWidth + 'px'
})
}
}, { root: null, threshold: .1 })
}
const clearTHead = (): void => {
visibleEl = undefined
divTHead.innerHTML = ''
divTHead.style.visibility = 'hidden'
divTHead.style.opacity = '0'
isTHead = false
}
const fillTHead = (el: Element): boolean => {
const tr: Element | null = el.children.item(0)
if (tr) {
for (let i = 0; i < tr.children.length; i++) {
const th = tr.children.item(i)
if (th) {
let newStyle = `flex-shrink: 0; width: ${th.clientWidth}px; `
if ((i === 0 && !enabledChecking) || (i === 1 && enabledChecking)) newStyle += `padding-right: 1.5rem;`
else if (i === tr.children.length - 1) newStyle += `padding-left: 1.5rem;`
else if (i === 0 && enabledChecking) newStyle += `padding: 0 .75rem;`
else newStyle += `padding: 0 1.5rem;`
if (th.classList.contains('sorted')) newStyle += ` margin-right: .25rem;`
divTHead.insertAdjacentHTML('beforeend', `<div style="${newStyle}">${tr.children.item(i)?.textContent}</div>`)
}
}
isTHead = true
elTHead = tr
}
return isTHead
}
const findTHeaders = (): void => {
if (divBox) {
const elements = divBox.querySelectorAll('.scroller-thead')
if (elements.length > 0 && divScroll) {
const rectScroll = divScroll.getBoundingClientRect()
hasTHeads = true
elements.forEach((el, i) => {
const rect = el.getBoundingClientRect()
enabledChecking = el.parentElement?.classList.contains('enableChecking') ?? false
const rectTable = el.parentElement?.getBoundingClientRect()
if (rectTable) {
if (rectTable.top < rectScroll.top && rectTable.bottom > rectScroll.top + rect.height) {
if (!isTHead && divTHead)
if (fillTHead(el)) visibleEl = i
if (isTHead) {
if (rect.width > rectScroll.width) divTHead.style.width = rectScroll.width + 'px'
else divTHead.style.width = rect.width + 'px'
divTHead.style.height = rect.height + 'px'
divTHead.style.left = rect.left + 'px'
if (rect.bottom - 16 < rectScroll.top && rectTable.bottom > rectScroll.top + rect.height) {
divTHead.style.top = rectScroll.top + 'px'
divTHead.style.visibility = 'visible'
divTHead.style.opacity = '.9'
} else {
divTHead.style.top = rect.top + 'px'
divTHead.style.visibility = 'hidden'
divTHead.style.opacity = '0'
}
}
} else if ((rectTable.top > rectScroll.top + rect.height || rectTable.bottom < rectScroll.top + rect.height) && isTHead && visibleEl === i)
clearTHead()
}
})
} else hasTHeads = false
}
}
const checkBar = (): void => {
if (divBar && divScroll) {
@ -119,33 +184,42 @@
}
const checkFade = (): void => {
const t = divScroll.scrollTop
const b = divScroll.scrollHeight - divScroll.clientHeight - t
if (t > 0 && b > 0) mask = 'both'
else if (t > 0) mask = 'bottom'
else if (b > 0) mask = 'top'
else mask = 'none'
if (divScroll) {
const t = divScroll.scrollTop
const b = divScroll.scrollHeight - divScroll.clientHeight - t
if (t > 0 && b > 0) mask = 'both'
else if (t > 0) mask = 'bottom'
else if (b > 0) mask = 'top'
else mask = 'none'
}
checkBack()
findTHeaders()
if (isTHead) checkTHeadSizes()
if (!isScrolling) checkBar()
}
let observer = new IntersectionObserver(() => checkFade(), { root: null, threshold: .1 })
onMount(() => {
if (divScroll && divBox) {
divScroll.addEventListener('scroll', checkFade)
const tempEl = divBox.querySelector('*') as HTMLElement
observer.observe(tempEl)
if (divBox.querySelector('.scroller-back')) {
divEl = divBox.querySelector('.scroller-back') as HTMLElement
observer.observe(divEl)
}
if (tempEl) observer.observe(tempEl)
checkFade()
}
if (divBack) checkBack()
})
onDestroy(() => { if (divScroll) divScroll.removeEventListener('scroll', checkFade) })
afterUpdate(() => { if (divScroll) checkFade() })
afterUpdate(() => {
if (divScroll && divBox) {
const tempEl = divBox.querySelector('*') as HTMLElement
if (tempEl) observer.observe(tempEl)
checkFade()
}
})
</script>
<svelte:window on:resize={checkFade} />
<svelte:window on:resize={checkFade} />
<div class="scroller-container">
<div
bind:this={divScroll}
@ -171,6 +245,7 @@
class:hovered={isScrolling}
bind:this={divTrack}
/>
<div bind:this={divTHead} class="fly-head thead-style" />
</div>
<style lang="scss">
@ -184,7 +259,6 @@
.scroll {
flex-grow: 1;
min-height: 0;
// max-height: 10rem;
height: max-content;
overflow-x: hidden;
overflow-y: auto;
@ -196,6 +270,7 @@
flex-direction: column;
height: 100%;
}
.track {
visibility: hidden;
position: absolute;
@ -224,7 +299,7 @@
opacity: .5;
cursor: pointer;
z-index: 1;
transition: all .1s ease-in-out;
transition: all .1s;
&:hover, &.hovered {
background-color: var(--theme-button-bg-hovered);
@ -241,14 +316,33 @@
}
&.hovered { transition: none; }
}
.back {
visibility: hidden;
position: fixed;
// left: 0;
// right: 0;
width: 0;
height: 0;
// background-color: red;
background-color: var(--theme-bg-accent-color);
z-index: -1;
}
.fly-head {
overflow: hidden;
position: fixed;
pointer-events: none;
visibility: hidden;
transition: top .2s ease-out, opacity .2s;
}
.thead-style {
display: flex;
align-items: center;
min-width: 0;
height: 2.5rem;
font-weight: 500;
font-size: 0.75rem;
color: var(--theme-content-dark-color);
background-color: var(--theme-bg-color);
box-shadow: inset 0 -1px 0 0 var(--theme-bg-focused-color);
user-select: none;
}
</style>

View File

@ -17,7 +17,7 @@
<script lang="ts">
import { Doc, DocumentQuery } from '@anticrm/core'
import { getClient } from '@anticrm/presentation'
import { Button, Icon, IconAdd, Label, ScrollBox, SearchEdit, showPopup } from '@anticrm/ui'
import { Button, Icon, IconAdd, Label, Scroller, SearchEdit, showPopup } from '@anticrm/ui'
import view, { Viewlet } from '@anticrm/view'
import { Table } from '@anticrm/view-resources'
import contact from '../plugin'
@ -52,7 +52,7 @@
<Button icon={IconAdd} label={contact.string.Create} primary={true} size={'small'} on:click={(ev) => showCreateDialog(ev)}/>
</div>
<ScrollBox vertical stretch noShift>
<Scroller>
{#await tableDescriptor then descr}
{#if descr}
<Table
@ -64,6 +64,5 @@
/>
{/if}
{/await}
</ScrollBox>
<div class="ac-body__space-3" />
</Scroller>
</div>

View File

@ -15,7 +15,7 @@
-->
<script lang="ts">
import { DocumentQuery } from '@anticrm/core'
import { Button, Icon, Label, ScrollBox, SearchEdit, showPopup, IconAdd } from '@anticrm/ui'
import { Button, Icon, Label, Scroller, SearchEdit, showPopup, IconAdd } from '@anticrm/ui'
import type { Category } from '@anticrm/inventory'
import inventory from '../plugin'
import CreateCategory from './CreateCategory.svelte'
@ -51,7 +51,6 @@
/>
</div>
<ScrollBox vertical stretch noShift>
<Scroller>
<HierarchyView _class={inventory.class.Category} config={['', 'modifiedOn']} options={{}} query={resultQuery} />
</ScrollBox>
<div class="ac-body__space-3" />
</Scroller>

View File

@ -14,7 +14,7 @@
// limitations under the License.
-->
<script lang="ts">
import ui, { Button, EditWithIcon, Icon, IconSearch, Label, ScrollBox, showPopup, IconAdd } from '@anticrm/ui'
import ui, { Button, EditWithIcon, Icon, IconSearch, Label, Scroller, showPopup, IconAdd } from '@anticrm/ui'
import CreateProduct from './CreateProduct.svelte'
import inventory from '../plugin'
import { Table } from '@anticrm/view-resources'
@ -58,7 +58,7 @@
/>
</div>
<ScrollBox vertical stretch noShift>
<Scroller>
{#await tableDescriptor then descr}
{#if descr}
<Table
@ -70,5 +70,4 @@
/>
{/if}
{/await}
</ScrollBox>
<div class="ac-body__space-3" />
</Scroller>

View File

@ -17,7 +17,7 @@
<script lang="ts">
import { Doc, DocumentQuery } from '@anticrm/core'
import { getClient } from '@anticrm/presentation'
import { Icon, Label, ScrollBox, SearchEdit } from '@anticrm/ui'
import { Icon, Label, Scroller, SearchEdit } from '@anticrm/ui'
import { Table } from '@anticrm/view-resources'
import view, { Viewlet } from '@anticrm/view'
import lead from '../plugin'
@ -44,7 +44,7 @@
}}/>
</div>
<ScrollBox vertical stretch noShift>
<Scroller>
{#await tableDescriptor then descr}
{#if descr}
<Table
@ -56,5 +56,4 @@
/>
{/if}
{/await}
</ScrollBox>
<div class="ac-body__space-3" />
</Scroller>

View File

@ -19,7 +19,7 @@
import { Doc, DocumentQuery, Ref } from '@anticrm/core'
import { createQuery, getClient } from '@anticrm/presentation'
import tags, { selectedTagElements, TagCategory, TagElement } from '@anticrm/tags'
import { Button, Component, Icon, Label, ScrollBox, SearchEdit, showPopup } from '@anticrm/ui'
import { Button, Component, Icon, Label, Scroller, SearchEdit, showPopup } from '@anticrm/ui'
import view, { Viewlet } from '@anticrm/view'
import { Table } from '@anticrm/view-resources'
import recruit from '../plugin'
@ -75,7 +75,7 @@
<Component is={tags.component.TagsCategoryBar} props={{ targetClass: recruit.mixin.Candidate, category }} on:change={(evt) => updateCategory(evt.detail) }/>
<ScrollBox vertical stretch noShift>
<Scroller>
{#await tableDescriptor then descr}
{#if descr}
<Table
@ -87,5 +87,4 @@
/>
{/if}
{/await}
</ScrollBox>
<div class="ac-body__space-3" />
</Scroller>

View File

@ -1,8 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="tags" viewBox="0 0 16 16" fill="none">
<ellipse cx="8.00065" cy="5.33332" rx="2.66667" ry="2.66667" stroke="white" stroke-linecap="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.49602 10.1829C8.08386 10.1559 7.66752 10.1625 7.25413 10.2033C6.13653 10.3136 5.07583 10.6708 4.20812 11.2416C3.34031 11.8123 2.69243 12.5801 2.37694 13.4632C2.28403 13.7232 2.41953 14.0093 2.67957 14.1022C2.93962 14.1951 3.22574 14.0596 3.31865 13.7996C3.54917 13.1543 4.03933 12.5495 4.75765 12.077C5.47607 11.6045 6.37836 11.2946 7.35239 11.1985C7.40347 11.1934 7.45458 11.189 7.50571 11.1851C7.70324 10.7429 8.05662 10.3856 8.49602 10.1829Z" fill="white"/>
<path d="M12 9.33334L12 14.6667" stroke="white" stroke-linecap="round"/>
<path d="M14.666 12L9.33268 12" stroke="white" stroke-linecap="round"/>
<symbol id="tags" viewBox="0 0 16 16">
<path d="M8,8.5c1.7,0,3.2-1.4,3.2-3.2S9.7,2.2,8,2.2c-1.7,0-3.2,1.4-3.2,3.2S6.3,8.5,8,8.5z M8,3.2c1.2,0,2.2,1,2.2,2.2 S9.2,7.5,8,7.5s-2.2-1-2.2-2.2S6.8,3.2,8,3.2z"/>
<path d="M4.2,11.2c-0.9,0.6-1.5,1.3-1.8,2.2c-0.1,0.3,0,0.5,0.3,0.6c0.3,0.1,0.5,0,0.6-0.3c0.2-0.6,0.7-1.3,1.4-1.7 c0.7-0.5,1.6-0.8,2.6-0.9c0.1,0,0.1,0,0.2,0c0.2-0.4,0.6-0.8,1-1c-0.4,0-0.8,0-1.2,0C6.1,10.3,5.1,10.7,4.2,11.2z"/>
<path d="M14.7,11.5h-2.2V9.3c0-0.3-0.2-0.5-0.5-0.5s-0.5,0.2-0.5,0.5v2.2H9.3c-0.3,0-0.5,0.2-0.5,0.5s0.2,0.5,0.5,0.5h2.2v2.2 c0,0.3,0.2,0.5,0.5,0.5s0.5-0.2,0.5-0.5v-2.2h2.2c0.3,0,0.5-0.2,0.5-0.5S14.9,11.5,14.7,11.5z"/>
</symbol>
</svg>

Before

Width:  |  Height:  |  Size: 913 B

After

Width:  |  Height:  |  Size: 746 B

View File

@ -16,7 +16,7 @@
import { Class, Doc, DocumentQuery, FindOptions, Ref } from '@anticrm/core'
import { IntlString, translate } from '@anticrm/platform'
import { TagCategory, TagElement } from '@anticrm/tags'
import { Button, Icon, Label, ScrollBox, SearchEdit, showPopup } from '@anticrm/ui'
import { Button, Icon, Label, Scroller, SearchEdit, showPopup } from '@anticrm/ui'
import { Table } from '@anticrm/view-resources'
import tags from '../plugin'
import CategoryBar from './CategoryBar.svelte'
@ -75,7 +75,7 @@
updateResultQuery(search, category)
}}
/>
<ScrollBox vertical stretch noShift>
<Scroller>
<Table
_class={tags.class.TagElement}
config={[
@ -103,5 +103,4 @@
query={resultQuery}
enableChecking
/>
</ScrollBox>
<div class="ac-body__space-3" />
</Scroller>

View File

@ -124,8 +124,8 @@
{/if}
{:then model}
<table class="table-body" class:enableChecking>
<thead>
<tr class="tr-head">
<thead class="scroller-thead">
<tr class="tr-head scroller-thead__tr">
{#each model as attribute, cellHead}
{#if enableChecking && !cellHead}
<th>

View File

@ -15,7 +15,7 @@
-->
<script lang="ts">
import type { Class, Doc, DocumentQuery, FindOptions, Ref, Space } from '@anticrm/core'
import { ScrollBox } from '@anticrm/ui'
import { Scroller } from '@anticrm/ui'
import Table from './Table.svelte'
export let _class: Ref<Class<Doc>>
@ -29,17 +29,6 @@
$: resultQuery = search === '' ? { space, ...query } : { $search: search, space, ...query }
</script>
<div class="tableview-container">
<ScrollBox vertical stretch noShift>
<Table {_class} {config} {options} query={resultQuery} {baseMenuClass} enableChecking />
</ScrollBox>
</div>
<style lang="scss">
.tableview-container {
flex-grow: 1;
margin-bottom: .75rem;
min-height: 0;
height: 100%;
}
</style>
<Scroller>
<Table {_class} {config} {options} query={resultQuery} {baseMenuClass} enableChecking />
</Scroller>