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,15 +25,21 @@
let divBack: HTMLElement let divBack: HTMLElement
let divBar: HTMLElement let divBar: HTMLElement
let divTrack: HTMLElement let divTrack: HTMLElement
let divEl: HTMLElement let divTHead: HTMLElement
let isBack: boolean = false let elTHead: Element
let isBack: boolean = false // ?
let isTHead: boolean = false
let hasTHeads: boolean = false // ?
let isScrolling: boolean = false let isScrolling: boolean = false
let enabledChecking: boolean = false
let dY: number let dY: number
let visibleEl: number | undefined = undefined
const checkBack = (): void => { const checkBack = (): void => {
const rectScroll = divScroll.getBoundingClientRect() if (divBox) {
const el = divBox.querySelector('.scroller-back') const el = divBox.querySelector('.scroller-back')
if (el && divScroll) { if (el && divScroll) {
const rectScroll = divScroll.getBoundingClientRect()
const rectEl = el.getBoundingClientRect() const rectEl = el.getBoundingClientRect()
const bottom = document.body.clientHeight - rectScroll.bottom const bottom = document.body.clientHeight - rectScroll.bottom
let top = rectEl.top let top = rectEl.top
@ -52,24 +58,83 @@
isBack = false isBack = false
} }
} }
}
let observer = new IntersectionObserver(changes => { const checkTHeadSizes = (): void => {
for (const change of changes) { if (elTHead && divTHead && divScroll) {
if (divBack) { const elements = divTHead.querySelectorAll('div')
let rect = change.intersectionRect elements.forEach((el, i) => {
if (rect) { const th = elTHead.children.item(i)
divBack.style.left = rect.left + 'px' if (th) el.style.width = th.clientWidth + '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 const clearTHead = (): void => {
divBack.style.backgroundColor = temp.style.backgroundColor visibleEl = undefined
} divTHead.innerHTML = ''
} else divBack.style.visibility = 'hidden' 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
} }
} }
}, { root: null, threshold: .1 })
const checkBar = (): void => { const checkBar = (): void => {
if (divBar && divScroll) { if (divBar && divScroll) {
@ -119,30 +184,39 @@
} }
const checkFade = (): void => { const checkFade = (): void => {
if (divScroll) {
const t = divScroll.scrollTop const t = divScroll.scrollTop
const b = divScroll.scrollHeight - divScroll.clientHeight - t const b = divScroll.scrollHeight - divScroll.clientHeight - t
if (t > 0 && b > 0) mask = 'both' if (t > 0 && b > 0) mask = 'both'
else if (t > 0) mask = 'bottom' else if (t > 0) mask = 'bottom'
else if (b > 0) mask = 'top' else if (b > 0) mask = 'top'
else mask = 'none' else mask = 'none'
}
checkBack() checkBack()
findTHeaders()
if (isTHead) checkTHeadSizes()
if (!isScrolling) checkBar() if (!isScrolling) checkBar()
} }
let observer = new IntersectionObserver(() => checkFade(), { root: null, threshold: .1 })
onMount(() => { onMount(() => {
if (divScroll && divBox) { if (divScroll && divBox) {
divScroll.addEventListener('scroll', checkFade) divScroll.addEventListener('scroll', checkFade)
const tempEl = divBox.querySelector('*') as HTMLElement const tempEl = divBox.querySelector('*') as HTMLElement
observer.observe(tempEl) if (tempEl) observer.observe(tempEl)
if (divBox.querySelector('.scroller-back')) { checkFade()
divEl = divBox.querySelector('.scroller-back') as HTMLElement
observer.observe(divEl)
}
} }
if (divBack) checkBack() if (divBack) checkBack()
}) })
onDestroy(() => { if (divScroll) divScroll.removeEventListener('scroll', checkFade) }) 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> </script>
<svelte:window on:resize={checkFade} /> <svelte:window on:resize={checkFade} />
@ -171,6 +245,7 @@
class:hovered={isScrolling} class:hovered={isScrolling}
bind:this={divTrack} bind:this={divTrack}
/> />
<div bind:this={divTHead} class="fly-head thead-style" />
</div> </div>
<style lang="scss"> <style lang="scss">
@ -184,7 +259,6 @@
.scroll { .scroll {
flex-grow: 1; flex-grow: 1;
min-height: 0; min-height: 0;
// max-height: 10rem;
height: max-content; height: max-content;
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
@ -196,6 +270,7 @@
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
} }
.track { .track {
visibility: hidden; visibility: hidden;
position: absolute; position: absolute;
@ -224,7 +299,7 @@
opacity: .5; opacity: .5;
cursor: pointer; cursor: pointer;
z-index: 1; z-index: 1;
transition: all .1s ease-in-out; transition: all .1s;
&:hover, &.hovered { &:hover, &.hovered {
background-color: var(--theme-button-bg-hovered); background-color: var(--theme-button-bg-hovered);
@ -241,14 +316,33 @@
} }
&.hovered { transition: none; } &.hovered { transition: none; }
} }
.back { .back {
visibility: hidden; visibility: hidden;
position: fixed; position: fixed;
// left: 0;
// right: 0;
width: 0; width: 0;
height: 0; height: 0;
// background-color: red;
background-color: var(--theme-bg-accent-color); background-color: var(--theme-bg-accent-color);
z-index: -1; 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> </style>

View File

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

View File

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

View File

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

View File

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

View File

@ -19,7 +19,7 @@
import { Doc, DocumentQuery, Ref } from '@anticrm/core' import { Doc, DocumentQuery, Ref } from '@anticrm/core'
import { createQuery, getClient } from '@anticrm/presentation' import { createQuery, getClient } from '@anticrm/presentation'
import tags, { selectedTagElements, TagCategory, TagElement } from '@anticrm/tags' 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 view, { Viewlet } from '@anticrm/view'
import { Table } from '@anticrm/view-resources' import { Table } from '@anticrm/view-resources'
import recruit from '../plugin' 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) }/> <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} {#await tableDescriptor then descr}
{#if descr} {#if descr}
<Table <Table
@ -87,5 +87,4 @@
/> />
{/if} {/if}
{/await} {/await}
</ScrollBox> </Scroller>
<div class="ac-body__space-3" />

View File

@ -1,8 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;"> <svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="tags" viewBox="0 0 16 16" fill="none"> <symbol id="tags" viewBox="0 0 16 16">
<ellipse cx="8.00065" cy="5.33332" rx="2.66667" ry="2.66667" stroke="white" stroke-linecap="round"/> <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 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="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="M12 9.33334L12 14.6667" stroke="white" stroke-linecap="round"/> <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"/>
<path d="M14.666 12L9.33268 12" stroke="white" stroke-linecap="round"/>
</symbol> </symbol>
</svg> </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 { Class, Doc, DocumentQuery, FindOptions, Ref } from '@anticrm/core'
import { IntlString, translate } from '@anticrm/platform' import { IntlString, translate } from '@anticrm/platform'
import { TagCategory, TagElement } from '@anticrm/tags' 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 { Table } from '@anticrm/view-resources'
import tags from '../plugin' import tags from '../plugin'
import CategoryBar from './CategoryBar.svelte' import CategoryBar from './CategoryBar.svelte'
@ -75,7 +75,7 @@
updateResultQuery(search, category) updateResultQuery(search, category)
}} }}
/> />
<ScrollBox vertical stretch noShift> <Scroller>
<Table <Table
_class={tags.class.TagElement} _class={tags.class.TagElement}
config={[ config={[
@ -103,5 +103,4 @@
query={resultQuery} query={resultQuery}
enableChecking enableChecking
/> />
</ScrollBox> </Scroller>
<div class="ac-body__space-3" />

View File

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

View File

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