From 981cbada6ac242587cda771b8dc0f18fc353688d Mon Sep 17 00:00:00 2001
From: Denis Bykhov <bykhov.denis@gmail.com>
Date: Wed, 12 Apr 2023 14:20:01 +0600
Subject: [PATCH] Inbox as popup (#2957)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
---
 .../src/components/MentionList.svelte         |  8 ++--
 packages/ui/src/components/Popup.svelte       |  3 ++
 .../ui/src/components/PopupInstance.svelte    | 15 ++++---
 packages/ui/src/location.ts                   |  2 +
 packages/ui/src/popups.ts                     | 14 ++++--
 .../src/components/add-card/AddCard.svelte    |  8 ++--
 .../src/components/Inbox.svelte               |  8 ++--
 .../components/issues/edit/EditIssue.svelte   |  6 ++-
 .../src/components/SavedView.svelte           |  3 +-
 .../src/components/Workbench.svelte           | 43 ++++++++++++++-----
 10 files changed, 76 insertions(+), 34 deletions(-)

diff --git a/packages/text-editor/src/components/MentionList.svelte b/packages/text-editor/src/components/MentionList.svelte
index e2dfd06bd0..b5908641c9 100644
--- a/packages/text-editor/src/components/MentionList.svelte
+++ b/packages/text-editor/src/components/MentionList.svelte
@@ -15,7 +15,7 @@
 -->
 <script lang="ts">
   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 DummyPopup from './DummyPopup.svelte'
 
@@ -25,10 +25,10 @@
   export let close: () => void
 
   let popup: HTMLDivElement
-  let popupClose: () => void
+  let dummyPopup: PopupResult
 
   onMount(() => {
-    popupClose = showPopup(
+    dummyPopup = showPopup(
       DummyPopup,
       {},
       undefined,
@@ -39,7 +39,7 @@
   })
 
   onDestroy(() => {
-    popupClose()
+    dummyPopup.close()
   })
 
   function dispatchItem (item: ObjectSearchResult): void {
diff --git a/packages/ui/src/components/Popup.svelte b/packages/ui/src/components/Popup.svelte
index 166a06665a..7e7aa42952 100644
--- a/packages/ui/src/components/Popup.svelte
+++ b/packages/ui/src/components/Popup.svelte
@@ -15,6 +15,8 @@
 <script lang="ts">
   import { popupstore as modal } from '../popups'
   import PopupInstance from './PopupInstance.svelte'
+
+  export let contentPanel: HTMLElement
 </script>
 
 {#if $modal.length > 0}
@@ -31,6 +33,7 @@
       zIndex={(i + 1) * 500}
       top={$modal.length - 1 === i}
       close={popup.close}
+      {contentPanel}
       overlay={popup.options.overlay}
     />
   {/key}
diff --git a/packages/ui/src/components/PopupInstance.svelte b/packages/ui/src/components/PopupInstance.svelte
index fea646f88c..b40b5e4aa9 100644
--- a/packages/ui/src/components/PopupInstance.svelte
+++ b/packages/ui/src/components/PopupInstance.svelte
@@ -27,6 +27,7 @@
   export let zIndex: number
   export let top: boolean
   export let close: () => void
+  export let contentPanel: HTMLElement
 
   let modalHTML: HTMLElement
   let componentInstance: any
@@ -67,13 +68,13 @@
     _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')) {
-      options = fitPopupElement(modalHTML, 'full')
+      options = fitPopupElement(modalHTML, 'full', contentPanel)
       options.props.maxHeight = '100vh'
       if (!modalHTML.classList.contains('fullsize')) modalHTML.classList.add('fullsize')
     } else {
-      options = fitPopupElement(modalHTML, element)
+      options = fitPopupElement(modalHTML, element, contentPanel)
       if (modalHTML.classList.contains('fullsize')) modalHTML.classList.remove('fullsize')
     }
     options.fullSize = fullSize
@@ -103,7 +104,7 @@
 
   $: if (modalHTML !== undefined && oldModalHTML !== modalHTML) {
     oldModalHTML = modalHTML
-    fitPopup(modalHTML, element)
+    fitPopup(modalHTML, element, contentPanel)
     showing = true
     modalHTML.addEventListener(
       'transitionend',
@@ -121,7 +122,7 @@
 <svelte:window
   on:resize={() => {
     if (modalHTML) {
-      fitPopup(modalHTML, element)
+      fitPopup(modalHTML, element, contentPanel)
     }
   }}
   on:keydown={handleKeydown}
@@ -155,10 +156,10 @@
     on:close={(ev) => _close(ev?.detail)}
     on:fullsize={() => {
       fullSize = !fullSize
-      fitPopup(modalHTML, element)
+      fitPopup(modalHTML, element, contentPanel)
     }}
     on:changeContent={() => {
-      fitPopup(modalHTML, element)
+      fitPopup(modalHTML, element, contentPanel)
     }}
   />
 </div>
diff --git a/packages/ui/src/location.ts b/packages/ui/src/location.ts
index c893be3407..172e167df9 100644
--- a/packages/ui/src/location.ts
+++ b/packages/ui/src/location.ts
@@ -15,6 +15,7 @@
 
 import { writable, derived } from 'svelte/store'
 import { Location as PlatformLocation } from './types'
+import { closePopup } from './popups'
 
 export function locationToUrl (location: PlatformLocation): string {
   let result = '/'
@@ -110,6 +111,7 @@ window.addEventListener('popstate', () => {
 export const location = derived(locationWritable, (loc) => loc)
 
 export function navigate (location: PlatformLocation, store = true): void {
+  closePopup()
   const url = locationToUrl(location)
   if (locationToUrl(getCurrentLocation()) !== url) {
     if (store) {
diff --git a/packages/ui/src/popups.ts b/packages/ui/src/popups.ts
index 2853ebdb7e..7433b58c96 100644
--- a/packages/ui/src/popups.ts
+++ b/packages/ui/src/popups.ts
@@ -12,7 +12,7 @@ import type {
 } from './types'
 import { ComponentType } from 'svelte'
 
-interface CompAndProps {
+export interface CompAndProps {
   id: string
   is: AnySvelteComponent | ComponentType
   props: any
@@ -26,6 +26,11 @@ interface CompAndProps {
   }
 }
 
+export interface PopupResult {
+  id: string
+  close: () => void
+}
+
 export const popupstore = writable<CompAndProps[]>([])
 
 function addPopup (props: CompAndProps): void {
@@ -45,7 +50,7 @@ export function showPopup (
     category: string
     overlay: boolean
   } = { category: 'popup', overlay: true }
-): () => void {
+): PopupResult {
   const id = `${popupId++}`
   const closePopupOp = (): void => {
     popupstore.update((popups) => {
@@ -66,7 +71,10 @@ export function showPopup (
   } else {
     addPopup({ id, is: component, props, element: _element, onClose, onUpdate, close: closePopupOp, options })
   }
-  return closePopupOp
+  return {
+    id,
+    close: closePopupOp
+  }
 }
 
 export function closePopup (category?: string): void {
diff --git a/plugins/board-resources/src/components/add-card/AddCard.svelte b/plugins/board-resources/src/components/add-card/AddCard.svelte
index 148578542a..67cb4d9554 100644
--- a/plugins/board-resources/src/components/add-card/AddCard.svelte
+++ b/plugins/board-resources/src/components/add-card/AddCard.svelte
@@ -71,21 +71,21 @@
     return new Promise<'single' | 'multiple' | 'close'>((resolve) => {
       const popupOpts = {
         onAddSingle: () => {
-          closePopup()
+          popup.close()
           resolve('single')
         },
         onAddMultiple: () => {
-          closePopup()
+          popup.close()
           resolve('multiple')
         },
         onClose: () => {
-          closePopup()
+          popup.close()
           resolve('close')
         },
         cardsNumber: splittedTitle.length
       }
 
-      const closePopup = showPopup(AddMultipleCardsPopup, popupOpts, anchorRef, () => resolve('close'))
+      const popup = showPopup(AddMultipleCardsPopup, popupOpts, anchorRef, () => resolve('close'))
     }).then((value) => {
       if (value === 'single' || value === 'close') {
         return addCard(title.replace('\n', ' ')).then((res) => {
diff --git a/plugins/notification-resources/src/components/Inbox.svelte b/plugins/notification-resources/src/components/Inbox.svelte
index 0a13538bcc..e513ef8245 100644
--- a/plugins/notification-resources/src/components/Inbox.svelte
+++ b/plugins/notification-resources/src/components/Inbox.svelte
@@ -96,10 +96,12 @@
       </div>
     </div>
   {/if}
-  {#if loading}
-    <Loading />
-  {:else if component && _id && _class}
+  {#if component && _id && _class}
     <Component is={component} props={{ embedded: true, _id, _class }} />
+  {:else}
+    <div class="antiPanel-component filled w-full">
+      <Loading />
+    </div>
   {/if}
 </div>
 
diff --git a/plugins/tracker-resources/src/components/issues/edit/EditIssue.svelte b/plugins/tracker-resources/src/components/issues/edit/EditIssue.svelte
index bbef924c82..786c365cd6 100644
--- a/plugins/tracker-resources/src/components/issues/edit/EditIssue.svelte
+++ b/plugins/tracker-resources/src/components/issues/edit/EditIssue.svelte
@@ -83,6 +83,10 @@
       _class,
       { _id },
       async (result) => {
+        if (saveTrigger !== undefined) {
+          clearTimeout(saveTrigger)
+          await save()
+        }
         ;[issue] = result
         title = issue.title
         description = issue.description
@@ -177,7 +181,7 @@
     {/if}
     <EditBox bind:value={title} placeholder={tracker.string.IssueTitlePlaceholder} kind="large-style" on:blur={save} />
     <div class="w-full mt-6">
-      {#key _id}
+      {#key issue._id}
         <AttachmentStyledBox
           bind:this={descriptionBox}
           useAttachmentPreview={true}
diff --git a/plugins/workbench-resources/src/components/SavedView.svelte b/plugins/workbench-resources/src/components/SavedView.svelte
index 997c2c09f4..89d3a176fa 100644
--- a/plugins/workbench-resources/src/components/SavedView.svelte
+++ b/plugins/workbench-resources/src/components/SavedView.svelte
@@ -47,7 +47,6 @@
   }
 
   let selectedId: Ref<FilteredView> | undefined = undefined
-  let selectedFW: FilteredView[] | undefined = undefined
   async function load (fv: FilteredView): Promise<void> {
     if (fv.viewletId !== undefined && fv.viewletId !== null) {
       const viewlet = await client.findOne(view.class.Viewlet, { _id: fv.viewletId })
@@ -66,7 +65,7 @@
   }
 
   const clearSelection = () => {
-    selectedId = selectedFW = undefined
+    selectedId = undefined
     dispatch('select', false)
   }
 
diff --git a/plugins/workbench-resources/src/components/Workbench.svelte b/plugins/workbench-resources/src/components/Workbench.svelte
index 6a5a0455ff..f42cae13ae 100644
--- a/plugins/workbench-resources/src/components/Workbench.svelte
+++ b/plugins/workbench-resources/src/components/Workbench.svelte
@@ -24,6 +24,7 @@
   import request, { RequestStatus } from '@hcengineering/request'
   import {
     AnyComponent,
+    CompAndProps,
     Component,
     DatePickerPopup,
     Label,
@@ -33,6 +34,7 @@
     Popup,
     PopupAlignment,
     PopupPosAlignment,
+    PopupResult,
     ResolvedLocation,
     TooltipInstance,
     areLocationsEqual,
@@ -43,6 +45,7 @@
     location,
     navigate,
     openPanel,
+    popupstore,
     resizeObserver,
     showPopup
   } 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>
 
 {#if employee?.active === true}
@@ -568,17 +582,26 @@
           <AppItem
             icon={notification.icon.Notifications}
             label={notification.string.Inbox}
-            selected={currentAppAlias === notificationId}
+            selected={currentAppAlias === notificationId || inboxPopup !== undefined}
             on:click={(e) => {
-              if (currentAppAlias === notificationId) {
-                e.preventDefault()
-                e.stopPropagation()
-                if (prevLoc !== undefined) {
-                  navigate(prevLoc)
-                }
+              if (e.metaKey || e.ctrlKey) return
+              if (inboxPopup) {
+                inboxPopup.close()
               } else {
-                prevLoc = $location
+                inboxPopup = showPopup(
+                  notification.component.Inbox,
+                  { visibileNav: true },
+                  'content',
+                  undefined,
+                  undefined,
+                  {
+                    category: 'popup',
+                    overlay: false
+                  }
+                )
               }
+              e.preventDefault()
+              e.stopPropagation()
             }}
             notify={hasNotification}
           />
@@ -672,7 +695,7 @@
       <ActionContext context={{ mode: 'panel' }} />
     </svelte:fragment>
   </PanelInstance>
-  <Popup>
+  <Popup {contentPanel}>
     <svelte:fragment slot="popup-header">
       <ActionContext context={{ mode: 'popup' }} />
     </svelte:fragment>