Update Panel, Telegram, Scroller layouts. (#1621)

Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
Alexander Platov 2022-05-03 19:04:21 +03:00 committed by GitHub
parent 797e72332e
commit 864095e324
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 257 additions and 407 deletions

View File

@ -19,18 +19,19 @@
import type { Doc } from '@anticrm/core'
import notification from '@anticrm/notification'
import type { Asset } from '@anticrm/platform'
import { AnyComponent, AnySvelteComponent, Component, Panel, Icon } from '@anticrm/ui'
import { AnySvelteComponent, Component, Panel, Icon, Scroller } from '@anticrm/ui'
export let title: string | undefined = undefined
export let subtitle: string | undefined = undefined
export let icon: Asset | AnySvelteComponent | undefined = undefined
export let rightSection: AnyComponent | undefined = undefined
export let withoutActivity: boolean = false
export let object: Doc
export let panelWidth: number = 0
export let innerWidth: number = 0
export let isHeader: boolean = true
export let isSub: boolean = true
export let isAside: boolean = true
export let isCustomAttr: boolean = true
export let minimize: boolean = false
let docWidth: number = 0
@ -39,14 +40,7 @@
</script>
<svelte:window bind:innerWidth={docWidth} />
<Panel
rightSection={rightSection !== undefined}
bind:isAside
isHeader={needHeader}
bind:panelWidth
bind:innerWidth
on:close
>
<Panel bind:isAside isHeader={needHeader} bind:panelWidth bind:innerWidth on:close>
<svelte:fragment slot="title">
<div class="popupPanel-title__content-container antiTitle">
{#if $$slots.navigator}
@ -90,7 +84,7 @@
</div>
</div>
{/if}
{#if $$slots['custom-attributes']}
{#if $$slots['custom-attributes'] && isCustomAttr}
{#if isSub}<div class="header-row"><slot name="custom-attributes" direction="row" /></div>{/if}
{:else if $$slots.attributes && minimize}<div class="header-row">
<slot name="attributes" direction="row" />
@ -107,18 +101,22 @@
</div>
</div>
{/if}
{#if $$slots['custom-attributes']}
{#if $$slots['custom-attributes'] && isCustomAttr}
<slot name="custom-attributes" direction="column" />
{:else if $$slots.attributes}<slot name="attributes" direction="column" />{/if}
{#if $$slots.aside}<slot name="aside" />{/if}
</div>
</svelte:fragment>
{#if rightSection !== undefined}
{#if withoutActivity}
<slot />
{:else}
<Component is={activity.component.Activity} props={{ object, integrate: true }}>
<slot />
</Component>
<Scroller>
<div class="popupPanel-body__main-content py-10 clear-mins">
<Component is={activity.component.Activity} props={{ object, integrate: true }}>
<slot />
</Component>
</div>
</Scroller>
{/if}
</Panel>

View File

@ -370,9 +370,11 @@ p:last-child { margin-block-end: 0; }
.pb-2 { padding-bottom: .5rem; }
.pb-3 { padding-bottom: .75rem; }
.pb-4 { padding-bottom: 1rem; }
.pb-6 { padding-bottom: 1.5rem; }
.px-2 { padding: 0 .5rem; }
.px-3 { padding: 0 .75rem; }
.px-4 { padding: 0 1rem; }
.py-10 { padding: 2.5rem 0; }
.p-1 { padding: .25rem; }
.p-2 { padding: .5rem; }

View File

@ -202,6 +202,10 @@
.antiNav-bottomFade { mask-image: linear-gradient(0deg, rgba(0, 0, 0, 0) 0, rgba(0, 0, 0, 1) 1px, rgba(0, 0, 0, 1) calc(100% - 2rem), rgba(0, 0, 0, 0) 100%); }
.antiNav-bothFade { mask-image: linear-gradient(0deg, rgba(0, 0, 0, 0) 0, rgba(0, 0, 0, 1) 2rem, rgba(0, 0, 0, 1) calc(100% - 2rem), rgba(0, 0, 0, 0) 100%); }
.antiNav-noneFade { mask-image: linear-gradient(0deg, rgba(0, 0, 0, 0) 0, rgba(0, 0, 0, 1) 1px, rgba(0, 0, 0, 1) calc(100% - 1px), rgba(0, 0, 0, 0) 100%); }
.tableFade.antiNav-topFade { mask-image: linear-gradient(0deg, rgba(0, 0, 0, 0) 0, rgba(0, 0, 0, 1) 2rem, rgba(0, 0, 0, 1) calc(100% - 1px), rgba(0, 0, 0, 0) 100%); }
.tableFade.antiNav-bottomFade { mask-image: linear-gradient(0deg, rgba(0, 0, 0, 0) 0, rgba(0, 0, 0, 1) 1px, rgba(0, 0, 0, 1) calc(100% - 4.5rem - 1px), rgba(0, 0, 0, 0) calc(100% - 2.5rem), rgba(0, 0, 0, 1) calc(100% - 2.5rem + .5px)); }
.tableFade.antiNav-bothFade { mask-image: linear-gradient(0deg, rgba(0, 0, 0, 0) 0, rgba(0, 0, 0, 1) 2rem, rgba(0, 0, 0, 1) calc(100% - 4.5rem - 1px), rgba(0, 0, 0, 0) calc(100% - 2.5rem), rgba(0, 0, 0, 1) calc(100% - 2.5rem + .5px)); }
.tableFade.antiNav-noneFade { mask-image: linear-gradient(0deg, rgba(0, 0, 0, 0) 0, rgba(0, 0, 0, 1) 1px, rgba(0, 0, 0, 1) calc(100% - 1px), rgba(0, 0, 0, 0) 100%); }
/* Basic */
.antiTitle {

View File

@ -324,6 +324,7 @@
/* Table */
.antiTable {
position: relative;
width: 100%;
th, td {
@ -415,6 +416,13 @@
&.checking { background-color: var(--highlight-select); } // --theme-table-bg-hover
&.selected { background-color: var(--highlight-hover); } // --menu-bg-select
}
.scroller-thead {
position: sticky;
top: 0;
z-index: 999;
background-color: var(--body-color);
}
}
// Hide row menu in Tooltip

View File

@ -127,8 +127,9 @@
width: calc(100% - 5rem);
min-width: 0;
max-width: 900px;
border-bottom: 1px solid var(--divider-color);
&.bottom-divider { border-bottom: 1px solid var(--divider-color); }
&.top-divider { border-top: 1px solid var(--divider-color); }
.header-row {
display: flex;
align-items: center;

View File

@ -14,9 +14,8 @@
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { Button, IconClose, IconDetails, Scroller } from '..'
import { Button, IconClose, IconDetails } from '..'
export let rightSection: boolean = false
export let innerWidth: number = 0
export let panelWidth: number = 0
export let isHeader: boolean = true
@ -65,15 +64,11 @@
<div class="popupPanel-body" class:asideShown>
<div class="popupPanel-body__main" bind:clientWidth={innerWidth}>
{#if $$slots.header && isHeader}
<div class="popupPanel-body__main-header">
<div class="popupPanel-body__main-header bottom-divider">
<slot name="header" />
</div>
{/if}
<Scroller>
<div class="popupPanel-body__main-content">
<slot />
</div>
</Scroller>
<slot />
</div>
{#if $$slots.aside && isAside}
<div class="popupPanel-body__aside" class:float={asideFloat} class:shown={asideShown}>

View File

@ -17,8 +17,9 @@
export let padding: boolean = false
export let autoscroll: boolean = false
export let correctPadding: number = 0
// export let correctPadding: number = 0
export let bottomStart: boolean = false
export let tableFade: boolean = false
let mask: 'top' | 'bottom' | 'both' | 'none' = 'bottom'
@ -27,17 +28,11 @@
let divBack: HTMLElement
let divBar: HTMLElement
let divTrack: HTMLElement
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
let belowContent: number | undefined = undefined
let scrolling: boolean = false
let scrolling: boolean = autoscroll
let firstScroll: boolean = autoscroll
const checkBack = (): void => {
@ -65,86 +60,6 @@
}
}
const checkTHeadSizes = (): void => {
if (elTHead && divTHead && divScroll) {
const elems = divTHead.querySelectorAll('div')
elems.forEach((el, i) => {
const th = elTHead.children.item(i)
if (th) el.style.width = th.clientWidth + 'px'
})
}
}
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 && 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 - correctPadding + '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) {
const proc = (divScroll.clientHeight / divScroll.scrollHeight) * 100
@ -195,7 +110,6 @@
const checkFade = (): void => {
if (divScroll) {
scrolling = false
const t = divScroll.scrollTop
const b = divScroll.scrollHeight - divScroll.clientHeight - t
if (t > 0 && b > 0) mask = 'both'
@ -204,19 +118,19 @@
else mask = 'none'
}
checkBack()
findTHeaders()
if (isTHead) checkTHeadSizes()
if (!isScrolling) checkBar()
if (scrolling && belowContent && belowContent > 1) {
divScroll.scrollTop = divScroll.scrollHeight - divScroll.clientHeight
}
}
const observer = new IntersectionObserver(() => checkFade(), { root: null, threshold: 0.1 })
$: if (firstScroll && divScroll && divScroll.clientHeight !== divScroll.scrollHeight) {
$: if (autoscroll && !scrolling && belowContent && belowContent < 1 && divScroll) {
divScroll.scrollTop = divScroll.scrollHeight - divScroll.clientHeight
firstScroll = false
scrolling = true
}
$: if (autoscroll && belowContent && belowContent <= 50) scrolling = true
$: if (scrolling && belowContent && belowContent > 50) {
$: if (scrolling && divScroll && divScroll.scrollHeight - divScroll.scrollTop - divScroll.clientHeight < 5) {
divScroll.scrollTop = divScroll.scrollHeight - divScroll.clientHeight
}
@ -237,19 +151,26 @@
const tempEl = divBox.querySelector('*') as HTMLElement
if (tempEl) observer.observe(tempEl)
if (scrolling) divScroll.scrollTop = divScroll.scrollHeight - divScroll.clientHeight
checkFade()
clearTHead()
findTHeaders()
belowContent = divScroll.scrollHeight - divScroll.clientHeight - divScroll.scrollTop
checkFade()
}
})
let divWidth: number = 0
const _resize = (): void => {
clearTHead()
checkFade()
}
$: if (divWidth) _resize()
const _scroll = (ev: Event): void => {
if (ev.type === 'scroll') {
firstScroll ? (firstScroll = false) : (scrolling = false)
if (ev.target) {
const el: HTMLElement = ev.target as HTMLElement
if (el.scrollHeight - el.scrollTop - el.clientHeight < 5) scrolling = true
}
}
}
</script>
<svelte:window on:resize={_resize} />
@ -257,7 +178,9 @@
<div
bind:this={divScroll}
bind:clientWidth={divWidth}
on:scroll={_scroll}
class="scroll relative"
class:tableFade
class:antiNav-topFade={mask === 'top'}
class:antiNav-bottomFade={mask === 'bottom'}
class:antiNav-bothFade={mask === 'both'}
@ -270,7 +193,6 @@
<div bind:this={divBack} class="back" />
<div class="bar" class:hovered={isScrolling} bind:this={divBar} on:mousedown={onScrollStart} />
<div class="track" class:hovered={isScrolling} bind:this={divTrack} />
<div bind:this={divTHead} class="fly-head thead-style" />
</div>
<style lang="scss">
@ -366,23 +288,4 @@
background-color: var(--body-color);
z-index: -1;
}
.fly-head {
overflow: hidden;
position: fixed;
pointer-events: none;
visibility: hidden;
transition: top 0.2s ease-out, opacity 0.2s;
}
.thead-style {
display: flex;
align-items: center;
min-width: 0;
height: 2.5rem;
font-weight: 500;
font-size: 0.75rem;
color: var(--dark-color);
background-color: var(--board-bg-color);
box-shadow: inset 0 -1px 0 0 var(--theme-bg-focused-color);
user-select: none;
}
</style>

View File

@ -20,6 +20,6 @@
<svg class="svg-{size}" {fill} viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path
d="M12.7,7.5H3.9L7,4.4L6.3,3.6l-4,4L2,8l0.4,0.4l4,4L7,11.6L3.9,8.5h8.8c0.3,0,0.5-0.2,0.5-0.5S12.9,7.5,12.7,7.5z"
d="M13.1,7.5H4.3l3.1-3.1L6.7,3.6l-4,4L2.4,8l0.4,0.4l4,4l0.6-0.8L4.3,8.5h8.8c0.3,0,0.5-0.2,0.5-0.5S13.3,7.5,13.1,7.5z"
/>
</svg>

View File

@ -0,0 +1,25 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
export let size: 'small' | 'medium' | 'large'
const fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path
d="M2.4,8c0,0.3,0.2,0.5,0.5,0.5h8.8l-3.1,3.1l0.6,0.8l4-4L13.6,8l-0.3-0.4l-4-4L8.6,4.4l3.1,3.1l-8.8,0C2.7,7.5,2.4,7.7,2.4,8z"
/>
</svg>

View File

@ -114,6 +114,7 @@ export { default as IconInfo } from './components/icons/Info.svelte'
export { default as IconBlueCheck } from './components/icons/BlueCheck.svelte'
export { default as IconCheck } from './components/icons/Check.svelte'
export { default as IconArrowLeft } from './components/icons/ArrowLeft.svelte'
export { default as IconArrowRight } from './components/icons/ArrowRight.svelte'
export { default as IconNavPrev } from './components/icons/NavPrev.svelte'
export { default as IconNavNext } from './components/icons/NavNext.svelte'
export { default as IconDPCalendar } from './components/calendar/icons/DPCalendar.svelte'

View File

@ -8,7 +8,6 @@ export interface PanelProps {
_class: string
element?: PopupAlignment
rightSection?: AnyComponent
fullSize?: boolean
}
export const panelstore = writable<{ panel?: PanelProps | undefined }>({ panel: undefined })
@ -17,7 +16,13 @@ let currentLocation: string | undefined
location.subscribe((loc) => {
if (loc.fragment !== currentLocation && loc.fragment !== undefined && loc.fragment.trim().length > 0) {
const props = decodeURIComponent(loc.fragment).split('|')
showPanel(props[0] as AnyComponent, props[1], props[2], (props[3] ?? undefined) as PopupAlignment)
showPanel(
props[0] as AnyComponent,
props[1],
props[2],
(props[3] ?? undefined) as PopupAlignment,
(props[4] ?? undefined) as AnyComponent
)
} else if (
(loc.fragment === undefined || (loc.fragment !== undefined && loc.fragment.trim().length === 0)) &&
currentLocation !== undefined
@ -47,7 +52,7 @@ export function showPanel (
element?: PopupAlignment,
rightSection?: AnyComponent
): void {
const newLoc = getPanelURI(component, _id, _class, element)
const newLoc = getPanelURI(component, _id, _class, element, rightSection)
if (currentLocation === newLoc) {
return
}

View File

@ -198,8 +198,8 @@ export function fitPopupElement (modalHTML: HTMLElement, element?: PopupAlignmen
} else if (element === 'float') {
newProps.top = 'calc(var(--status-bar-height) + .25rem)'
newProps.bottom = '.25rem'
newProps.width = '40rem'
newProps.maxWidth = '40%'
newProps.width = '40%'
newProps.maxWidth = '60%'
newProps.right = '.25rem'
show = true
} else if (element === 'account') {

View File

@ -87,7 +87,7 @@
{/if}
</div>
{:else}
<div class="mt-4 pb-4 bottom-highlight-select">
<div class="pb-6 bottom-highlight-select">
<slot />
</div>
<div class="flex-row-center h-14 px-3 mt-4 antiTitle">

View File

@ -16,7 +16,7 @@
import { createEventDispatcher, onMount } from 'svelte'
import type { IntlString } from '@anticrm/platform'
import { translate } from '@anticrm/platform'
import { Button, Icon, IconClose, IconBlueCheck } from '@anticrm/ui'
import { Button, Icon, IconClose, IconBlueCheck, IconArrowRight } from '@anticrm/ui'
export let value: string = ''
export let placeholder: IntlString
@ -60,6 +60,7 @@
>
<div class="icon"><Icon icon={IconClose} size={'inline'} /></div>
</div>
<Button kind={'transparent'} size={'small'} icon={IconArrowRight} on:click={() => dispatch('update', 'open')} />
<Button kind={'transparent'} size={'small'} icon={IconBlueCheck} on:click={() => dispatch('close', value)} />
</div>
</div>

View File

@ -25,23 +25,19 @@
import { getChannelProviders } from '../utils'
import ChannelEditor from './ChannelEditor.svelte'
import { NotificationClientImpl } from '@anticrm/notification-resources'
import { onDestroy } from 'svelte'
export let value: AttachedData<Channel>[] | Channel | null
export let editable = false
export let editable: boolean = false
export let kind: ButtonKind = 'no-border'
export let size: ButtonSize = 'small'
export let length: 'short' | 'full' = 'full'
export let shape: 'circle' | undefined = undefined
// export let reverse: boolean = false
export let integrations: Set<Ref<Doc>> = new Set<Ref<Doc>>()
const notificationClient = NotificationClientImpl.getClient()
const lastViews = notificationClient.getLastViews()
const dispatch = createEventDispatcher()
let editMode = false
interface Item {
label: IntlString
icon: Asset
@ -140,8 +136,8 @@
}
$: if (providers) updateMenu()
const dropItem = (n: number): void => {
displayItems = displayItems.filter((it, i) => i !== n)
const dropItem = (n: number): Item[] => {
return displayItems.filter((it, i) => i !== n)
}
const saveItems = (): void => {
value = filterUndefined(displayItems)
@ -156,15 +152,10 @@
ev.target as HTMLElement,
(result) => {
if (result !== undefined) {
if (result == null || result === '') dropItem(n)
if (result === null || result === '') displayItems = dropItem(n)
else displayItems[n].value = result
} else if (displayItems[n].value === '') dropItem(n)
saveItems()
if (actions.length > 0 && addBtn) {
if (result !== undefined) addBtn.click()
else disableEdit()
} else {
disableEdit()
saveItems()
if (displayItems.length < providers.size && addBtn) addBtn.click()
}
},
(result) => {
@ -172,22 +163,26 @@
if (result === 'left') {
closePopup()
if (displayItems[n].value === '') {
dropItem(n)
displayItems = dropItem(n)
saveItems()
}
if (n === 0) addBtn.click()
else btns[n - 1].click()
if (n === 0) {
if (addBtn) addBtn.click()
else btns[displayItems.length - 1].click()
} else btns[n - 1].click()
} else if (result === 'right') {
closePopup()
if (displayItems[n].value === '') {
dropItem(n)
displayItems = dropItem(n)
saveItems()
if (n === displayItems.length) addBtn.click()
else btns[n + 1].click()
} else {
if (n === displayItems.length - 1) addBtn.click()
else btns[n + 1].click()
}
if (n === displayItems.length - 1) {
if (addBtn) addBtn.click()
else btns[0].click()
} else btns[n + 1].click()
} else if (result === 'open') {
closePopup()
dispatch('open', { presenter: channel.presenter })
}
}
}
@ -198,11 +193,7 @@
Menu,
{ actions },
ev.target as HTMLElement,
(result) => {
if (result === undefined) {
disableEdit()
}
},
() => {},
(result) => {
if (result !== undefined && displayItems.length > 0) {
if (result === 'left') {
@ -217,31 +208,9 @@
)
}
let copied: boolean = false
let div: HTMLDivElement
function listener (e: MouseEvent): void {
if (e.target !== null && !div.contains(e.target as Node)) {
disableEdit()
}
}
function enableEdit () {
window.addEventListener('click', listener)
editMode = true
}
function disableEdit () {
window.removeEventListener('click', listener)
editMode = false
}
onDestroy(() => {
window.removeEventListener('click', listener)
})
</script>
<div
bind:this={div}
class="{displayItems.length === 0 ? 'clear-mins' : 'buttons-group'} {kind === 'no-border'
? 'xsmall-gap'
: 'xxsmall-gap'}"
@ -256,7 +225,7 @@
{shape}
click={item.value === ''}
on:click={(ev) => {
if (editMode) editChannel(item, i, ev)
if (editable) editChannel(item, i, ev)
}}
/>
{:else}
@ -270,12 +239,12 @@
{kind}
{size}
{shape}
highlight={item.integration || item.notification || editMode}
highlight={item.integration || item.notification}
on:click={(ev) => {
if (editMode) {
if (editable) {
editChannel(item, i, ev)
} else {
dispatch('click', item)
dispatch('open', item)
if (!copied) {
navigator.clipboard.writeText(item.value)
copied = true
@ -293,12 +262,11 @@
<Button
bind:input={addBtn}
icon={contact.icon.SocialEdit}
highlight={editMode}
label={displayItems.length === 0 ? presentation.string.AddSocialLinks : undefined}
{kind}
{size}
{shape}
on:click={editMode ? showMenu : enableEdit}
on:click={showMenu}
/>
{/if}
</div>
@ -334,6 +302,7 @@
transition-duration: 0.15s;
transition-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
pointer-events: none;
z-index: 1000;
}
&:hover .tooltip {
transform: translate(-50%, -0.5rem) scale(1);

View File

@ -20,7 +20,6 @@
import { ChannelProvider, Channel } from '@anticrm/contact'
import { showPanel } from '@anticrm/ui'
import view from '@anticrm/view'
import contact from '../plugin'
import ChannelsDropdown from './ChannelsDropdown.svelte'
@ -105,11 +104,11 @@
Promise.all(promises)
}
function click (ev: any) {
function _open (ev: any) {
if (ev.detail.presenter !== undefined && Array.isArray(channels)) {
const channel = channels[0]
if (channel !== undefined) {
showPanel(view.component.EditDoc, channel.attachedTo, channel.attachedToClass, 'content', ev.detail.presenter)
showPanel(ev.detail.presenter, channel.attachedTo, channel.attachedToClass, 'float')
}
}
}
@ -126,5 +125,5 @@
on:change={(e) => {
if (editable) save(e.detail)
}}
on:click={click}
on:open={_open}
/>

View File

@ -27,14 +27,14 @@
export let length: 'short' | 'full' = 'short'
export let shape: 'circle' | undefined = 'circle'
function click (ev: any) {
function _open (ev: any) {
if (ev.detail.presenter !== undefined && Array.isArray(value)) {
const channel = value[0]
if (channel !== undefined) {
showPanel(ev.detail.presenter, channel.attachedTo, channel.attachedToClass, 'float', ev.detail.presenter)
showPanel(ev.detail.presenter, channel.attachedTo, channel.attachedToClass, 'float')
}
}
}
</script>
<ChannelsDropdown bind:value {length} {kind} {size} {shape} {editable} on:click={click} />
<ChannelsDropdown bind:value {length} {kind} {size} {shape} {editable} on:open={_open} />

View File

@ -66,7 +66,7 @@
/>
</div>
<Scroller>
<Scroller tableFade>
{#await tableDescriptor then descr}
{#if descr}
<TableBrowser

View File

@ -143,9 +143,8 @@
attachedTo={object._id}
attachedClass={object._class}
{editable}
{integrations}
bind:integrations
shape={'circle'}
on:click
/>
</div>

View File

@ -14,7 +14,6 @@
// limitations under the License.
-->
<script lang="ts">
import type { AnyComponent } from '@anticrm/ui'
import { Ref, Doc, Class } from '@anticrm/core'
import contact, { Channel } from '@anticrm/contact'
import { SharedMessage } from '@anticrm/gmail'
@ -28,10 +27,8 @@
export let _id: Ref<Doc>
export let _class: Ref<Class<Doc>>
export let rightSection: AnyComponent | undefined = undefined
// export let object: Contact
// $: console.log('!!!!!!!!!!!! id: ', _id, ' - class: ', _class)
let object: any
let newMessage: boolean = false
let currentMessage: SharedMessage | undefined = undefined
@ -76,7 +73,7 @@
<Panel
icon={contact.icon.Email}
title={'Email'}
{rightSection}
withoutActivity
{object}
isHeader={false}
isAside={false}

View File

@ -67,7 +67,7 @@
/>
</div>
<Scroller>
<Scroller tableFade>
{#await tableDescriptor then descr}
{#if descr}
<Table

View File

@ -49,7 +49,7 @@
/>
</div>
<Scroller>
<Scroller tableFade>
{#await tableDescriptor then descr}
{#if descr}
<Table

View File

@ -82,6 +82,6 @@
<Button icon={IconAdd} label={recruit.string.ApplicationCreateLabel} kind={'primary'} on:click={showCreateDialog} />
</div>
<Scroller>
<Scroller tableFade>
<TableBrowser _class={recruit.class.Applicant} {config} {options} query={resultQuery} showNotification />
</Scroller>

View File

@ -93,7 +93,7 @@
mode: 'browser'
}}
/>
<Scroller>
<Scroller tableFade>
{#await tableDescriptor then descr}
{#if descr}
<TableBrowser

View File

@ -89,7 +89,7 @@
/>
<Button icon={IconAdd} label={recruit.string.VacancyCreateLabel} kind={'primary'} on:click={showCreateDialog} />
</div>
<Scroller>
<Scroller tableFade>
<Table
_class={recruit.class.Vacancy}
config={[

View File

@ -183,13 +183,13 @@
</div>
<style lang="scss">
.counter {
padding-right: 0.125rem;
min-width: 1.5rem;
text-align: right;
font-size: 0.8125rem;
color: var(--caption-color);
}
// .counter {
// padding-right: 0.125rem;
// min-width: 1.5rem;
// text-align: right;
// font-size: 0.8125rem;
// color: var(--caption-color);
// }
.empty {
display: flex;
justify-content: center;

View File

@ -108,7 +108,7 @@
updateResultQuery(search, category)
}}
/>
<Scroller>
<Scroller tableFade>
<TableBrowser
_class={tags.class.TagElement}
config={[

View File

@ -99,7 +99,7 @@
on:change={(evt) => updateCategory(evt.detail)}
/>
<Scroller>
<Scroller tableFade>
<Table
{_class}
config={[

View File

@ -24,7 +24,6 @@
import { createQuery, getClient } from '@anticrm/presentation'
import setting, { Integration } from '@anticrm/setting'
import type { NewTelegramMessage, SharedTelegramMessage, TelegramMessage } from '@anticrm/telegram'
import type { AnyComponent } from '@anticrm/ui'
import { Button, eventToHTMLElement, IconShare, Tooltip, Scroller, showPopup } from '@anticrm/ui'
import telegram from '../plugin'
import Connect from './Connect.svelte'
@ -34,7 +33,6 @@
export let _id: Ref<Contact>
export let _class: Ref<Class<Contact>>
export let rightSection: AnyComponent | undefined = undefined
let object: Contact
let channel: Channel | undefined = undefined
@ -77,7 +75,7 @@
telegram.class.Message,
{ attachedTo: channelId },
(res) => {
messages = res.reverse()
messages = res
if (channel !== undefined) {
notificationClient.updateLastView(channel._id, channel._class, undefined, true)
}
@ -191,7 +189,7 @@
<Panel
icon={TelegramIcon}
title={'Telegram'}
{rightSection}
withoutActivity
{object}
isHeader={false}
isAside={false}
@ -203,27 +201,43 @@
You and {formatName(object.name)}
</svelte:fragment>
<svelte:fragment slot="tools">
<Tooltip label={telegram.string.Share}>
{#if integration === undefined}
<Button
icon={IconShare}
kind={'transparent'}
size={'medium'}
on:click={async () => {
selectable = !selectable
label={telegram.string.Connect}
kind={'primary'}
on:click={(e) => {
showPopup(Connect, {}, eventToHTMLElement(e), onConnectClose)
}}
/>
</Tooltip>
{:else if integration.disabled}
<Button
label={setting.string.Reconnect}
kind={'primary'}
on:click={(e) => {
showPopup(Reconnect, {}, eventToHTMLElement(e), onReconnect)
}}
/>
{:else}
<Tooltip label={telegram.string.Share}>
<Button
icon={IconShare}
kind={'transparent'}
size={'medium'}
on:click={async () => {
selectable = !selectable
}}
/>
</Tooltip>
{/if}
</svelte:fragment>
<div class="telegram-content" class:selectable>
<Scroller bottomStart autoscroll>
{#if messages && accounts}
<Messages messages={convertMessages(messages, accounts)} {selectable} bind:selected />
{/if}
</Scroller>
</div>
<Scroller bottomStart autoscroll>
{#if messages && accounts}
<Messages messages={convertMessages(messages, accounts)} {selectable} bind:selected />
{/if}
</Scroller>
<div class="ref-input" class:selectable>
<div class="popupPanel-body__main-header ref-input" class:selectable>
{#if selectable}
<div class="flex-between">
<span>{selected.size} messages selected</span>
@ -242,26 +256,8 @@
</div>
</div>
</div>
{:else if integration === undefined}
<div class="flex-center">
<Button
label={telegram.string.Connect}
kind={'primary'}
on:click={(e) => {
showPopup(Connect, {}, eventToHTMLElement(e), onConnectClose)
}}
/>
</div>
{:else if integration.disabled}
<div class="flex-center">
<Button
label={setting.string.Reconnect}
kind={'primary'}
on:click={(e) => {
showPopup(Reconnect, {}, eventToHTMLElement(e), onReconnect)
}}
/>
</div>
{:else if integration === undefined || integration.disabled}
<div class="flex-center h-18">No integration</div>
{:else}
<AttachmentRefInput
space={telegram.space.Telegram}
@ -276,7 +272,7 @@
<style lang="scss">
.ref-input {
padding: 0 0 1.5rem;
padding: 0.5rem 0 1.5rem;
&.selectable {
padding: 1rem 0;
@ -284,14 +280,4 @@
border-top: 1px solid var(--divider-color);
}
}
.telegram-content {
padding-bottom: 1.5rem;
min-height: 0;
height: 100%;
&.selectable {
padding-bottom: 0;
}
}
</style>

View File

@ -32,29 +32,31 @@
const dispatch = createEventDispatcher()
</script>
<div
class="message-row"
class:selectable
class:selected-row={selected}
on:click={() => {
dispatch('select', message)
}}
>
<div class="check-box">
{#if selectable}<CheckBox circle primary bind:checked={selected} />{/if}
</div>
<div class="message-container" class:out={!message.incoming}>
<div class="message" class:outcoming={!message.incoming} class:selected>
{#if showName}
<div class="name" style="color: {getPlatformColorForText(message.sender)}">{formatName(message.sender)}</div>
{/if}
{#if attachments}
<AttachmentList {attachments} />
{/if}
<div class="flex">
<div class="caption-color mr-4"><MessageViewer message={message.content} /></div>
<div class="time">
{new Date(message.sendOn).toLocaleString('default', { hour: 'numeric', minute: 'numeric' })}
<div class="message-row-bg" class:selectable class:selected-row={selected}>
<div
class="message-row"
class:selectable
class:selected-row={selected}
on:click={() => {
dispatch('select', message)
}}
>
<div class="check-box">
{#if selectable}<CheckBox circle primary bind:checked={selected} />{/if}
</div>
<div class="message-container" class:out={!message.incoming}>
<div class="message" class:outcoming={!message.incoming} class:selected>
{#if showName}
<div class="name" style="color: {getPlatformColorForText(message.sender)}">{formatName(message.sender)}</div>
{/if}
{#if attachments}
<AttachmentList {attachments} />
{/if}
<div class="flex">
<div class="caption-color mr-4"><MessageViewer message={message.content} /></div>
<div class="time">
{new Date(message.sendOn).toLocaleString('default', { hour: 'numeric', minute: 'numeric' })}
</div>
</div>
</div>
</div>
@ -62,36 +64,51 @@
</div>
<style lang="scss">
.message-row-bg {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
max-width: 100%;
min-width: 0;
min-height: 0;
}
.message-row {
display: flex;
justify-content: stretch;
align-items: center;
margin: 0 auto;
width: 100%;
min-width: 0;
max-width: 900px;
}
&.selectable {
cursor: pointer;
.message-row.selectable,
.message-row-bg.selectable {
cursor: pointer;
&:hover {
background-color: var(--button-bg-hover);
}
&.selected-row {
background-color: var(--button-bg-color);
&:hover {
background-color: var(--highlight-hover);
background-color: var(--button-bg-hover);
}
&.selected-row {
background-color: var(--highlight-select);
}
.message {
cursor: pointer;
}
.selected {
background-color: var(--primary-bg-color);
&:hover {
background-color: var(--highlight-hover);
}
}
.message {
cursor: pointer;
}
.selected {
background-color: var(--primary-bg-color);
&:hover {
background-color: var(--primary-bg-hover);
}
&:hover {
background-color: var(--primary-bg-hover);
}
}
}
.check-box {
display: flex;
justify-content: center;

View File

@ -69,6 +69,7 @@
span {
margin-left: 0.25rem;
width: 1.6rem;
white-space: nowrap;
text-transform: capitalize;
font-weight: 500;
color: var(--theme-caption-color);

View File

@ -59,6 +59,7 @@
span {
margin-left: 0.25rem;
width: 1.6rem;
white-space: nowrap;
text-transform: capitalize;
font-weight: 500;
color: var(--theme-caption-color);

View File

@ -14,11 +14,11 @@
// limitations under the License.
-->
<script lang="ts">
import contact, { ChannelProvider, formatName } from '@anticrm/contact'
import core, { Class, ClassifierKind, Doc, getCurrentAccount, Mixin, Obj, Ref, Space } from '@anticrm/core'
import contact, { formatName } from '@anticrm/contact'
import core, { Class, ClassifierKind, Doc, Mixin, Obj, Ref } from '@anticrm/core'
import notification from '@anticrm/notification'
import { Panel } from '@anticrm/panel'
import { Asset, getResource, IntlString, translate } from '@anticrm/platform'
import { Asset, getResource, translate } from '@anticrm/platform'
import {
AttributesBar,
createQuery,
@ -26,7 +26,6 @@
getClient,
KeyedAttribute
} from '@anticrm/presentation'
import setting, { IntegrationType } from '@anticrm/setting'
import { AnyComponent, Component } from '@anticrm/ui'
import view from '@anticrm/view'
import { createEventDispatcher, onDestroy } from 'svelte'
@ -36,7 +35,6 @@
export let _id: Ref<Doc>
export let _class: Ref<Class<Doc>>
export let rightSection: AnyComponent | undefined = undefined
let lastId: Ref<Doc> = _id
let lastClass: Ref<Class<Doc>> = _class
@ -237,51 +235,6 @@
}
let panelWidth: number = 0
let innerWidth: number = 0
const accountId = getCurrentAccount()._id
let channelProviders: ChannelProvider[] | undefined
let currentProviders: ChannelProvider[] | undefined
let integrations: Set<Ref<IntegrationType>> = new Set<Ref<IntegrationType>>()
let displayedIntegrations:
| {
icon: Asset | undefined
label: IntlString
presenter: AnyComponent | undefined
value: string
}[]
| undefined = []
client.findAll(contact.class.ChannelProvider, {}).then((res) => {
channelProviders = res
})
const settingsQuery = createQuery()
$: settingsQuery.query(
setting.class.Integration,
{ space: accountId as string as Ref<Space>, disabled: false },
(res) => {
integrations = new Set(res.map((p) => p.type))
}
)
const channelsQuery = createQuery()
$: _id &&
integrations &&
channelProviders &&
channelsQuery.query(contact.class.Channel, { attachedTo: _id }, (res) => {
const channels = res
currentProviders = channelProviders?.filter((provider) =>
provider.integrationType ? integrations.has(provider.integrationType as Ref<IntegrationType>) : false
)
displayedIntegrations = []
currentProviders?.forEach((provider) => {
displayedIntegrations?.push({
icon: provider.icon,
label: provider.label,
presenter: provider.presenter,
value: channels.filter((ch) => ch.provider === provider._id)[0].value
})
})
})
let minimize: boolean = false
</script>
@ -295,7 +248,6 @@
<Panel
{icon}
{title}
{rightSection}
{object}
bind:minimize
isHeader={false}
@ -321,23 +273,18 @@
{/if}
</svelte:fragment>
<div class="main-editor">
{#if mainEditor}
<Component
is={mainEditor}
props={{ object }}
on:open={(ev) => {
ignoreKeys = ev.detail.ignoreKeys
ignoreMixins = new Set(ev.detail.ignoreMixins)
updateKeys()
getMixins()
}}
on:click={(ev) => {
rightSection = ev.detail.presenter
}}
/>
{/if}
</div>
{#if mainEditor}
<Component
is={mainEditor}
props={{ object }}
on:open={(ev) => {
ignoreKeys = ev.detail.ignoreKeys
ignoreMixins = new Set(ev.detail.ignoreMixins)
updateKeys()
getMixins()
}}
/>
{/if}
{#each collectionEditors as collection}
{#if collection.editor}
<div class="mt-6">
@ -357,11 +304,3 @@
{/each}
</Panel>
{/if}
<style lang="scss">
.main-editor {
display: flex;
justify-content: center;
flex-direction: column;
}
</style>

View File

@ -35,6 +35,6 @@
mode: 'browser'
}}
/>
<Scroller>
<Scroller tableFade>
<TableBrowser {_class} {config} {options} query={resultQuery} {baseMenuClass} showNotification />
</Scroller>

View File

@ -27,14 +27,13 @@ test.describe('contact tests', () => {
await page.fill('[placeholder="Location"]', 'LoPlaza')
// Click .flex-center.icon-button
await page.click('text=Edit profile John Appleseed LoPlaza >> button')
await page.click('text=Edit profile John Appleseed LoPlaza >> button')
// await page.click('button:has-text("Add social links")')
// Click [placeholder="john\.appleseed\@apple\.com"]
await page.click('button:has-text("Email")')
// Fill [placeholder="john\.appleseed\@apple\.com"]
await page.fill('[placeholder="john\\.appleseed\\@apple\\.com"]', 'wer@qwe.com')
// Click text=Apply
await page.click('.button.transparent')
await page.click('button:nth-child(3)')
})
test('create-template', async ({ page }) => {
// Go to http://localhost:8083/workbench%3Acomponent%3AWorkbenchApp