Inbox as popup (#2957)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2023-04-12 14:20:01 +06:00 committed by GitHub
parent 3c0fdc9698
commit 981cbada6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 76 additions and 34 deletions

View File

@ -15,7 +15,7 @@
--> -->
<script lang="ts"> <script lang="ts">
import { ObjectSearchPopup, ObjectSearchResult } from '@hcengineering/presentation' import { ObjectSearchPopup, ObjectSearchResult } from '@hcengineering/presentation'
import { showPopup, resizeObserver, deviceOptionsStore as deviceInfo } from '@hcengineering/ui' import { showPopup, resizeObserver, deviceOptionsStore as deviceInfo, PopupResult } from '@hcengineering/ui'
import { onDestroy, onMount } from 'svelte' import { onDestroy, onMount } from 'svelte'
import DummyPopup from './DummyPopup.svelte' import DummyPopup from './DummyPopup.svelte'
@ -25,10 +25,10 @@
export let close: () => void export let close: () => void
let popup: HTMLDivElement let popup: HTMLDivElement
let popupClose: () => void let dummyPopup: PopupResult
onMount(() => { onMount(() => {
popupClose = showPopup( dummyPopup = showPopup(
DummyPopup, DummyPopup,
{}, {},
undefined, undefined,
@ -39,7 +39,7 @@
}) })
onDestroy(() => { onDestroy(() => {
popupClose() dummyPopup.close()
}) })
function dispatchItem (item: ObjectSearchResult): void { function dispatchItem (item: ObjectSearchResult): void {

View File

@ -15,6 +15,8 @@
<script lang="ts"> <script lang="ts">
import { popupstore as modal } from '../popups' import { popupstore as modal } from '../popups'
import PopupInstance from './PopupInstance.svelte' import PopupInstance from './PopupInstance.svelte'
export let contentPanel: HTMLElement
</script> </script>
{#if $modal.length > 0} {#if $modal.length > 0}
@ -31,6 +33,7 @@
zIndex={(i + 1) * 500} zIndex={(i + 1) * 500}
top={$modal.length - 1 === i} top={$modal.length - 1 === i}
close={popup.close} close={popup.close}
{contentPanel}
overlay={popup.options.overlay} overlay={popup.options.overlay}
/> />
{/key} {/key}

View File

@ -27,6 +27,7 @@
export let zIndex: number export let zIndex: number
export let top: boolean export let top: boolean
export let close: () => void export let close: () => void
export let contentPanel: HTMLElement
let modalHTML: HTMLElement let modalHTML: HTMLElement
let componentInstance: any let componentInstance: any
@ -67,13 +68,13 @@
_close(undefined) _close(undefined)
} }
const fitPopup = (modalHTML: HTMLElement, element: PopupAlignment | undefined): void => { const fitPopup = (modalHTML: HTMLElement, element: PopupAlignment | undefined, contentPanel: HTMLElement): void => {
if ((fullSize || docSize) && (element === 'float' || element === 'centered')) { if ((fullSize || docSize) && (element === 'float' || element === 'centered')) {
options = fitPopupElement(modalHTML, 'full') options = fitPopupElement(modalHTML, 'full', contentPanel)
options.props.maxHeight = '100vh' options.props.maxHeight = '100vh'
if (!modalHTML.classList.contains('fullsize')) modalHTML.classList.add('fullsize') if (!modalHTML.classList.contains('fullsize')) modalHTML.classList.add('fullsize')
} else { } else {
options = fitPopupElement(modalHTML, element) options = fitPopupElement(modalHTML, element, contentPanel)
if (modalHTML.classList.contains('fullsize')) modalHTML.classList.remove('fullsize') if (modalHTML.classList.contains('fullsize')) modalHTML.classList.remove('fullsize')
} }
options.fullSize = fullSize options.fullSize = fullSize
@ -103,7 +104,7 @@
$: if (modalHTML !== undefined && oldModalHTML !== modalHTML) { $: if (modalHTML !== undefined && oldModalHTML !== modalHTML) {
oldModalHTML = modalHTML oldModalHTML = modalHTML
fitPopup(modalHTML, element) fitPopup(modalHTML, element, contentPanel)
showing = true showing = true
modalHTML.addEventListener( modalHTML.addEventListener(
'transitionend', 'transitionend',
@ -121,7 +122,7 @@
<svelte:window <svelte:window
on:resize={() => { on:resize={() => {
if (modalHTML) { if (modalHTML) {
fitPopup(modalHTML, element) fitPopup(modalHTML, element, contentPanel)
} }
}} }}
on:keydown={handleKeydown} on:keydown={handleKeydown}
@ -155,10 +156,10 @@
on:close={(ev) => _close(ev?.detail)} on:close={(ev) => _close(ev?.detail)}
on:fullsize={() => { on:fullsize={() => {
fullSize = !fullSize fullSize = !fullSize
fitPopup(modalHTML, element) fitPopup(modalHTML, element, contentPanel)
}} }}
on:changeContent={() => { on:changeContent={() => {
fitPopup(modalHTML, element) fitPopup(modalHTML, element, contentPanel)
}} }}
/> />
</div> </div>

View File

@ -15,6 +15,7 @@
import { writable, derived } from 'svelte/store' import { writable, derived } from 'svelte/store'
import { Location as PlatformLocation } from './types' import { Location as PlatformLocation } from './types'
import { closePopup } from './popups'
export function locationToUrl (location: PlatformLocation): string { export function locationToUrl (location: PlatformLocation): string {
let result = '/' let result = '/'
@ -110,6 +111,7 @@ window.addEventListener('popstate', () => {
export const location = derived(locationWritable, (loc) => loc) export const location = derived(locationWritable, (loc) => loc)
export function navigate (location: PlatformLocation, store = true): void { export function navigate (location: PlatformLocation, store = true): void {
closePopup()
const url = locationToUrl(location) const url = locationToUrl(location)
if (locationToUrl(getCurrentLocation()) !== url) { if (locationToUrl(getCurrentLocation()) !== url) {
if (store) { if (store) {

View File

@ -12,7 +12,7 @@ import type {
} from './types' } from './types'
import { ComponentType } from 'svelte' import { ComponentType } from 'svelte'
interface CompAndProps { export interface CompAndProps {
id: string id: string
is: AnySvelteComponent | ComponentType is: AnySvelteComponent | ComponentType
props: any props: any
@ -26,6 +26,11 @@ interface CompAndProps {
} }
} }
export interface PopupResult {
id: string
close: () => void
}
export const popupstore = writable<CompAndProps[]>([]) export const popupstore = writable<CompAndProps[]>([])
function addPopup (props: CompAndProps): void { function addPopup (props: CompAndProps): void {
@ -45,7 +50,7 @@ export function showPopup (
category: string category: string
overlay: boolean overlay: boolean
} = { category: 'popup', overlay: true } } = { category: 'popup', overlay: true }
): () => void { ): PopupResult {
const id = `${popupId++}` const id = `${popupId++}`
const closePopupOp = (): void => { const closePopupOp = (): void => {
popupstore.update((popups) => { popupstore.update((popups) => {
@ -66,7 +71,10 @@ export function showPopup (
} else { } else {
addPopup({ id, is: component, props, element: _element, onClose, onUpdate, close: closePopupOp, options }) addPopup({ id, is: component, props, element: _element, onClose, onUpdate, close: closePopupOp, options })
} }
return closePopupOp return {
id,
close: closePopupOp
}
} }
export function closePopup (category?: string): void { export function closePopup (category?: string): void {

View File

@ -71,21 +71,21 @@
return new Promise<'single' | 'multiple' | 'close'>((resolve) => { return new Promise<'single' | 'multiple' | 'close'>((resolve) => {
const popupOpts = { const popupOpts = {
onAddSingle: () => { onAddSingle: () => {
closePopup() popup.close()
resolve('single') resolve('single')
}, },
onAddMultiple: () => { onAddMultiple: () => {
closePopup() popup.close()
resolve('multiple') resolve('multiple')
}, },
onClose: () => { onClose: () => {
closePopup() popup.close()
resolve('close') resolve('close')
}, },
cardsNumber: splittedTitle.length cardsNumber: splittedTitle.length
} }
const closePopup = showPopup(AddMultipleCardsPopup, popupOpts, anchorRef, () => resolve('close')) const popup = showPopup(AddMultipleCardsPopup, popupOpts, anchorRef, () => resolve('close'))
}).then((value) => { }).then((value) => {
if (value === 'single' || value === 'close') { if (value === 'single' || value === 'close') {
return addCard(title.replace('\n', ' ')).then((res) => { return addCard(title.replace('\n', ' ')).then((res) => {

View File

@ -96,10 +96,12 @@
</div> </div>
</div> </div>
{/if} {/if}
{#if loading} {#if component && _id && _class}
<Loading />
{:else if component && _id && _class}
<Component is={component} props={{ embedded: true, _id, _class }} /> <Component is={component} props={{ embedded: true, _id, _class }} />
{:else}
<div class="antiPanel-component filled w-full">
<Loading />
</div>
{/if} {/if}
</div> </div>

View File

@ -83,6 +83,10 @@
_class, _class,
{ _id }, { _id },
async (result) => { async (result) => {
if (saveTrigger !== undefined) {
clearTimeout(saveTrigger)
await save()
}
;[issue] = result ;[issue] = result
title = issue.title title = issue.title
description = issue.description description = issue.description
@ -177,7 +181,7 @@
{/if} {/if}
<EditBox bind:value={title} placeholder={tracker.string.IssueTitlePlaceholder} kind="large-style" on:blur={save} /> <EditBox bind:value={title} placeholder={tracker.string.IssueTitlePlaceholder} kind="large-style" on:blur={save} />
<div class="w-full mt-6"> <div class="w-full mt-6">
{#key _id} {#key issue._id}
<AttachmentStyledBox <AttachmentStyledBox
bind:this={descriptionBox} bind:this={descriptionBox}
useAttachmentPreview={true} useAttachmentPreview={true}

View File

@ -47,7 +47,6 @@
} }
let selectedId: Ref<FilteredView> | undefined = undefined let selectedId: Ref<FilteredView> | undefined = undefined
let selectedFW: FilteredView[] | undefined = undefined
async function load (fv: FilteredView): Promise<void> { async function load (fv: FilteredView): Promise<void> {
if (fv.viewletId !== undefined && fv.viewletId !== null) { if (fv.viewletId !== undefined && fv.viewletId !== null) {
const viewlet = await client.findOne(view.class.Viewlet, { _id: fv.viewletId }) const viewlet = await client.findOne(view.class.Viewlet, { _id: fv.viewletId })
@ -66,7 +65,7 @@
} }
const clearSelection = () => { const clearSelection = () => {
selectedId = selectedFW = undefined selectedId = undefined
dispatch('select', false) dispatch('select', false)
} }

View File

@ -24,6 +24,7 @@
import request, { RequestStatus } from '@hcengineering/request' import request, { RequestStatus } from '@hcengineering/request'
import { import {
AnyComponent, AnyComponent,
CompAndProps,
Component, Component,
DatePickerPopup, DatePickerPopup,
Label, Label,
@ -33,6 +34,7 @@
Popup, Popup,
PopupAlignment, PopupAlignment,
PopupPosAlignment, PopupPosAlignment,
PopupResult,
ResolvedLocation, ResolvedLocation,
TooltipInstance, TooltipInstance,
areLocationsEqual, areLocationsEqual,
@ -43,6 +45,7 @@
location, location,
navigate, navigate,
openPanel, openPanel,
popupstore,
resizeObserver, resizeObserver,
showPopup showPopup
} from '@hcengineering/ui' } from '@hcengineering/ui'
@ -500,7 +503,18 @@
} }
} }
let prevLoc: Location | undefined = undefined function checkInbox (popups: CompAndProps[]) {
if (inboxPopup !== undefined) {
const exists = popups.find((p) => p.id === inboxPopup?.id)
if (!exists) {
inboxPopup = undefined
}
}
}
$: checkInbox($popupstore)
let inboxPopup: PopupResult | undefined = undefined
</script> </script>
{#if employee?.active === true} {#if employee?.active === true}
@ -568,17 +582,26 @@
<AppItem <AppItem
icon={notification.icon.Notifications} icon={notification.icon.Notifications}
label={notification.string.Inbox} label={notification.string.Inbox}
selected={currentAppAlias === notificationId} selected={currentAppAlias === notificationId || inboxPopup !== undefined}
on:click={(e) => { on:click={(e) => {
if (currentAppAlias === notificationId) { if (e.metaKey || e.ctrlKey) return
e.preventDefault() if (inboxPopup) {
e.stopPropagation() inboxPopup.close()
if (prevLoc !== undefined) {
navigate(prevLoc)
}
} else { } else {
prevLoc = $location inboxPopup = showPopup(
notification.component.Inbox,
{ visibileNav: true },
'content',
undefined,
undefined,
{
category: 'popup',
overlay: false
}
)
} }
e.preventDefault()
e.stopPropagation()
}} }}
notify={hasNotification} notify={hasNotification}
/> />
@ -672,7 +695,7 @@
<ActionContext context={{ mode: 'panel' }} /> <ActionContext context={{ mode: 'panel' }} />
</svelte:fragment> </svelte:fragment>
</PanelInstance> </PanelInstance>
<Popup> <Popup {contentPanel}>
<svelte:fragment slot="popup-header"> <svelte:fragment slot="popup-header">
<ActionContext context={{ mode: 'popup' }} /> <ActionContext context={{ mode: 'popup' }} />
</svelte:fragment> </svelte:fragment>