From 53c3f58e9df157c51b2570b5c7675da229b4aed1 Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Fri, 12 May 2023 13:41:27 +0700 Subject: [PATCH] TSK-1451: Fix focus issues + jump workaround (#3167) Signed-off-by: Andrey Sobolev --- models/notification/package.json | 1 + models/notification/src/index.ts | 19 +++++++++++++- .../src/components/ReferenceInput.svelte | 10 ++++--- .../src/components/StyledTextBox.svelte | 9 ++++--- .../ui/src/components/FocusHandler.svelte | 3 ++- packages/ui/src/components/Scroller.svelte | 17 ++++++++++-- packages/ui/src/focus.ts | 3 +++ packages/ui/src/utils.ts | 21 ++++++++++++++- .../src/components/Avatar.svelte | 10 ++++--- .../src/components/Inbox.svelte | 26 +++++++++++-------- .../components/issues/edit/EditIssue.svelte | 21 ++++++++++----- plugins/view-resources/src/actionImpl.ts | 17 ++++++------ .../src/components/ActionContext.svelte | 16 ++++++------ .../src/components/ActionHandler.svelte | 8 +++--- plugins/view-resources/src/context.ts | 20 ++++++++++++-- 15 files changed, 144 insertions(+), 57 deletions(-) diff --git a/models/notification/package.json b/models/notification/package.json index 94a5d85f6c..12610b0698 100644 --- a/models/notification/package.json +++ b/models/notification/package.json @@ -34,6 +34,7 @@ "@hcengineering/model-view": "^0.6.0", "@hcengineering/view": "^0.6.6", "@hcengineering/workbench": "^0.6.6", + "@hcengineering/model-workbench": "^0.6.1", "@hcengineering/notification": "^0.6.12", "@hcengineering/setting": "^0.6.7" } diff --git a/models/notification/src/index.ts b/models/notification/src/index.ts index 40784297b6..2e9008a95b 100644 --- a/models/notification/src/index.ts +++ b/models/notification/src/index.ts @@ -34,6 +34,7 @@ import { ArrOf, Builder, Index, Mixin, Model, Prop, TypeRef, TypeString, UX } fr import core, { TAttachedDoc, TClass, TDoc } from '@hcengineering/model-core' import preference, { TPreference } from '@hcengineering/model-preference' import view, { createAction } from '@hcengineering/model-view' +import workbench from '@hcengineering/model-workbench' import { DocUpdates, EmailNotification, @@ -50,7 +51,6 @@ import { import type { Asset, IntlString } from '@hcengineering/platform' import setting from '@hcengineering/setting' import { AnyComponent } from '@hcengineering/ui' -import workbench from '@hcengineering/workbench' import notification from './plugin' export { notificationId } from '@hcengineering/notification' @@ -284,6 +284,23 @@ export function createModel (builder: Builder): void { builder.mixin(notification.class.DocUpdates, core.class.Class, view.mixin.IgnoreActions, { actions: [view.action.Delete, view.action.Open] }) + + createAction(builder, { + action: workbench.actionImpl.Navigate, + actionProps: { + mode: 'app', + application: notificationId, + special: notificationId + }, + label: notification.string.Inbox, + icon: view.icon.ArrowRight, + input: 'none', + category: view.category.Navigation, + target: core.class.Doc, + context: { + mode: ['workbench', 'browser', 'editor', 'panel', 'popup'] + } + }) } export function generateClassNotificationTypes ( diff --git a/packages/text-editor/src/components/ReferenceInput.svelte b/packages/text-editor/src/components/ReferenceInput.svelte index b0f1c604ad..4a43026d25 100644 --- a/packages/text-editor/src/components/ReferenceInput.svelte +++ b/packages/text-editor/src/components/ReferenceInput.svelte @@ -213,15 +213,17 @@ export let focusIndex = -1 const { idx, focusManager } = registerFocus(focusIndex, { focus: () => { - focused = true - textEditor.focus() - return textEditor.isEditable() + const editable = textEditor.isEditable() + if (editable) { + focused = true + textEditor.focus() + } + return editable }, isFocus: () => focused }) const updateFocus = () => { if (focusIndex !== -1) { - console.trace('focuse') focusManager?.setFocus(idx) } } diff --git a/packages/text-editor/src/components/StyledTextBox.svelte b/packages/text-editor/src/components/StyledTextBox.svelte index 5bd6cfba44..f171fd24bb 100644 --- a/packages/text-editor/src/components/StyledTextBox.svelte +++ b/packages/text-editor/src/components/StyledTextBox.svelte @@ -104,9 +104,12 @@ export let focusIndex = -1 const { idx, focusManager } = registerFocus(focusIndex, { focus: () => { - focused = true - focus() - return textEditor.isEditable() + const editable = textEditor.isEditable() + if (editable) { + focused = true + focus() + } + return editable }, isFocus: () => focused }) diff --git a/packages/ui/src/components/FocusHandler.svelte b/packages/ui/src/components/FocusHandler.svelte index 1272aaaf46..e31bb1f286 100644 --- a/packages/ui/src/components/FocusHandler.svelte +++ b/packages/ui/src/components/FocusHandler.svelte @@ -2,9 +2,10 @@ import { FocusManager } from '../focus' export let manager: FocusManager + export let isEnabled: boolean = true function handleKey (evt: KeyboardEvent): void { - if (evt.code === 'Tab') { + if (evt.code === 'Tab' && isEnabled) { evt.preventDefault() evt.stopPropagation() manager.next(evt.shiftKey ? -1 : 1) diff --git a/packages/ui/src/components/Scroller.svelte b/packages/ui/src/components/Scroller.svelte index af6b89b17d..0f8a095bff 100644 --- a/packages/ui/src/components/Scroller.svelte +++ b/packages/ui/src/components/Scroller.svelte @@ -22,6 +22,7 @@ import IconUpOutline from './icons/UpOutline.svelte' import IconDownOutline from './icons/DownOutline.svelte' import HalfUpDown from './icons/HalfUpDown.svelte' + import { isSafari } from '../utils' export let padding: string | undefined = undefined export let autoscroll: boolean = false @@ -291,7 +292,9 @@ } const scrollDown = (): void => { - if (divScroll) divScroll.scrollTop = divScroll.scrollHeight - divHeight + 2 + if (divScroll) { + divScroll.scrollTop = divScroll.scrollHeight - divHeight + 2 + } } $: if (scrolling && belowContent && belowContent > 0) scrollDown() @@ -444,6 +447,7 @@ (orientir === 'horizontal' && (maskH === 'left' || maskH === 'both')) ? 'visible' : 'hidden' + let scrollY: number = 0 @@ -467,8 +471,17 @@ }} class="scroll relative flex-shrink" class:overflow-x={horizontal ? 'auto' : 'hidden'} - on:scroll={() => { + on:scroll={(evt) => { if ($tooltipstore.label !== undefined) closeTooltip() + const newPos = divScroll?.scrollTop ?? 0 + + // TODO: Workaround: https://front.hc.engineering/workbench/platform/tracker/TSK-760 + // In Safari scroll could jump on click, with no particular reason. + + if (scrollY !== 0 && Math.abs(newPos - scrollY) > 100 && divScroll !== undefined && isSafari()) { + divScroll.scrollTop = scrollY + } + scrollY = divScroll?.scrollTop ?? 0 }} >
it.order === order) if (idx !== undefined) { this.current = idx diff --git a/packages/ui/src/utils.ts b/packages/ui/src/utils.ts index ac99ce0875..ae5b1b65c6 100644 --- a/packages/ui/src/utils.ts +++ b/packages/ui/src/utils.ts @@ -16,11 +16,14 @@ import { generateId } from '@hcengineering/core' import type { Metadata } from '@hcengineering/platform' import { setMetadata } from '@hcengineering/platform' -import { writable } from 'svelte/store' import autolinker from 'autolinker' +import { writable } from 'svelte/store' import { Notification, NotificationPosition, NotificationSeverity, notificationsStore } from '.' import { AnyComponent, AnySvelteComponent } from './types' +/** + * @public + */ export function setMetadataLocalStorage (id: Metadata, value: T | null): void { if (value != null) { localStorage.setItem(id, typeof value === 'string' ? value : JSON.stringify(value)) @@ -30,6 +33,9 @@ export function setMetadataLocalStorage (id: Metadata, value: T | null): v setMetadata(id, value) } +/** + * @public + */ export function fetchMetadataLocalStorage (id: Metadata): T | null { const data = localStorage.getItem(id) if (data === null) { @@ -45,14 +51,27 @@ export function fetchMetadataLocalStorage (id: Metadata): T | null { } } +/** + * @public + */ export function checkMobile (): boolean { return /Android|webOS|iPhone|iPad|iPod|BlackBerry|Mobile|Opera Mini/i.test(navigator.userAgent) } +/** + * @public + */ +export function isSafari (): boolean { + return navigator.userAgent.toLowerCase().includes('safari/') +} + export function floorFractionDigits (n: number | string, amount: number): number { return Number(Number(n).toFixed(amount)) } +/** + * @public + */ export function addNotification ( title: string, subTitle: string, diff --git a/plugins/contact-resources/src/components/Avatar.svelte b/plugins/contact-resources/src/components/Avatar.svelte index 3dc83d4ede..c64b07f15c 100644 --- a/plugins/contact-resources/src/components/Avatar.svelte +++ b/plugins/contact-resources/src/components/Avatar.svelte @@ -79,10 +79,12 @@ } else { const uri = avatar.split('://')[1] - const color = (await getResource(avatarProvider.getUrl))(uri, size) - style = `background-color: ${color}` - accentColor = hexToRgb(color) - dispatch('accent-color', accentColor) + const color: string | undefined = (await getResource(avatarProvider.getUrl))(uri, size) + if (color != null) { + style = `background-color: ${color}` + accentColor = hexToRgb(color) + dispatch('accent-color', accentColor) + } } } $: updateStyle(avatar, avatarProvider) diff --git a/plugins/notification-resources/src/components/Inbox.svelte b/plugins/notification-resources/src/components/Inbox.svelte index 9ba121d77e..064ba2ac08 100644 --- a/plugins/notification-resources/src/components/Inbox.svelte +++ b/plugins/notification-resources/src/components/Inbox.svelte @@ -18,7 +18,7 @@ import { Class, Doc, getCurrentAccount, Ref } from '@hcengineering/core' import notification, { DocUpdates } from '@hcengineering/notification' import { createQuery, getClient } from '@hcengineering/presentation' - import { AnyComponent, Component, Label, Loading, Scroller } from '@hcengineering/ui' + import { AnyComponent, Component, Label, ListView, Loading, Scroller } from '@hcengineering/ui' import view from '@hcengineering/view' import { ActionContext, ListSelectionProvider, SelectDirection } from '@hcengineering/view-resources' import NotificationView from './NotificationView.svelte' @@ -94,6 +94,7 @@ const value = selected + offset if (docs[value] !== undefined) { selected = value + listView?.select(selected) } } }) @@ -104,6 +105,7 @@ }) let selected = 0 + let listView: ListView {:else} - {#each docs as doc, i} - { - selected = i - }} - /> - {/each} + + + { + selected = item + }} + /> + + {/if}
diff --git a/plugins/tracker-resources/src/components/issues/edit/EditIssue.svelte b/plugins/tracker-resources/src/components/issues/edit/EditIssue.svelte index 0ab573e816..7af383e574 100644 --- a/plugins/tracker-resources/src/components/issues/edit/EditIssue.svelte +++ b/plugins/tracker-resources/src/components/issues/edit/EditIssue.svelte @@ -34,7 +34,7 @@ navigate, showPopup } from '@hcengineering/ui' - import { ActionContext, ContextMenu, DocNavLink, UpDownNavigator } from '@hcengineering/view-resources' + import { ActionContext, ContextMenu, DocNavLink, UpDownNavigator, contextStore } from '@hcengineering/view-resources' import { createEventDispatcher, onDestroy, onMount } from 'svelte' import { generateIssueShortLink, getIssueId } from '../../../issues' import tracker from '../../../plugin' @@ -149,14 +149,20 @@ } return true } + + // If it is embedded + $: lastCtx = $contextStore.getLastContext() + $: isContextEnabled = lastCtx?.mode === 'editor' || lastCtx?.mode === 'browser' - - +{#if !embedded} + + +{/if} {#if issue !== undefined}
{#key issue._id} diff --git a/plugins/view-resources/src/actionImpl.ts b/plugins/view-resources/src/actionImpl.ts index e8deebbe4f..78c53b9c63 100644 --- a/plugins/view-resources/src/actionImpl.ts +++ b/plugins/view-resources/src/actionImpl.ts @@ -1,22 +1,21 @@ import { Class, Doc, DocumentQuery, Hierarchy, Ref, Space, TxResult } from '@hcengineering/core' -import { Asset, getResource, IntlString, Resource } from '@hcengineering/platform' -import { getClient, MessageBox, updateAttribute } from '@hcengineering/presentation' +import { Asset, IntlString, Resource, getResource } from '@hcengineering/platform' +import { MessageBox, getClient, updateAttribute } from '@hcengineering/presentation' import { AnyComponent, AnySvelteComponent, + PopupAlignment, + PopupPosAlignment, closeTooltip, isPopupPosAlignment, navigate, - PopupAlignment, - PopupPosAlignment, showPanel, showPopup } from '@hcengineering/ui' -import { ViewContext } from '@hcengineering/view' import MoveView from './components/Move.svelte' -import { contextStore } from './context' +import { ContextStore, contextStore } from './context' import view from './plugin' -import { FocusSelection, focusStore, previewDocument, SelectDirection, selectionStore } from './selection' +import { FocusSelection, SelectDirection, focusStore, previewDocument, selectionStore } from './selection' import { deleteObjects, getObjectLinkFragment } from './utils' /** @@ -100,7 +99,7 @@ focusStore.subscribe((it) => { $focusStore = it }) -let $contextStore: ViewContext[] +let $contextStore: ContextStore contextStore.subscribe((it) => { $contextStore = it }) @@ -153,7 +152,7 @@ const MoveRight = (doc: Doc | undefined, evt: Event): void => select(evt, 1, $fo function ShowActions (doc: Doc | Doc[] | undefined, evt: Event): void { evt.preventDefault() - showPopup(view.component.ActionsPopup, { viewContext: $contextStore[$contextStore.length - 1] }, 'top') + showPopup(view.component.ActionsPopup, { viewContext: $contextStore.getLastContext() }, 'top') } function ShowPreview (doc: Doc | Doc[] | undefined, evt: Event): void { diff --git a/plugins/view-resources/src/components/ActionContext.svelte b/plugins/view-resources/src/components/ActionContext.svelte index 49dafd60a6..d1246dd9bf 100644 --- a/plugins/view-resources/src/components/ActionContext.svelte +++ b/plugins/view-resources/src/components/ActionContext.svelte @@ -16,34 +16,34 @@ import { generateId } from '@hcengineering/core' import { ViewContext } from '@hcengineering/view' import { onDestroy } from 'svelte' - import { contextStore } from '../context' + import { ContextStore, contextStore } from '../context' export let context: ViewContext const id = generateId() - $: len = $contextStore.findIndex((it) => (it as any).id === id) + $: len = $contextStore.contexts.findIndex((it) => (it as any).id === id) onDestroy(() => { contextStore.update((t) => { - return t.slice(0, len ?? 0) + return new ContextStore(t.contexts.slice(0, len ?? 0)) }) }) $: { contextStore.update((cur) => { - const pos = cur.findIndex((it) => (it as any).id === id) + const pos = cur.contexts.findIndex((it) => (it as any).id === id) const newCur = { id, mode: context.mode, - application: context.application ?? cur[(pos !== -1 ? pos : cur.length) - 1]?.application + application: context.application ?? cur.contexts[(pos !== -1 ? pos : cur.contexts.length) - 1]?.application } if (pos === -1) { - len = cur.length - return [...cur, newCur] + len = cur.contexts.length + return new ContextStore([...cur.contexts, newCur]) } len = pos - return [...cur.slice(0, pos), newCur] + return new ContextStore([...cur.contexts.slice(0, pos), newCur]) }) } diff --git a/plugins/view-resources/src/components/ActionHandler.svelte b/plugins/view-resources/src/components/ActionHandler.svelte index 71e2412404..039486e25a 100644 --- a/plugins/view-resources/src/components/ActionHandler.svelte +++ b/plugins/view-resources/src/components/ActionHandler.svelte @@ -60,9 +60,9 @@ return await getContextActions(client, docs, context) } - $: ctx = $contextStore[$contextStore.length - 1] - $: mode = $contextStore[$contextStore.length - 1]?.mode - $: application = $contextStore[$contextStore.length - 1]?.application + $: ctx = $contextStore.getLastContext() + $: mode = $contextStore.getLastContext()?.mode + $: application = $contextStore.getLastContext()?.application function keyPrefix (key: KeyboardEvent): string { return ( @@ -158,7 +158,7 @@ } // For none we ignore all actions. - if (ctx.mode === 'none') { + if (ctx?.mode === 'none') { return } clearTimeout(timer) diff --git a/plugins/view-resources/src/context.ts b/plugins/view-resources/src/context.ts index 132e969a13..ffa7ddbb54 100644 --- a/plugins/view-resources/src/context.ts +++ b/plugins/view-resources/src/context.ts @@ -1,7 +1,23 @@ -import { ViewContext } from '@hcengineering/view' +import { ViewContext, ViewContextType } from '@hcengineering/view' import { writable } from 'svelte/store' /** * @public */ -export const contextStore = writable([]) +export class ContextStore { + constructor (readonly contexts: ViewContext[]) {} + + getLastContext (): ViewContext | undefined { + return this.contexts[this.contexts.length - 1] + } + + isIncludes (type: ViewContextType): boolean { + return ( + this.contexts.find((it) => it.mode === type || (Array.isArray(it.mode) && it.mode.includes(type))) !== undefined + ) + } +} +/** + * @public + */ +export const contextStore = writable(new ContextStore([]))