mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 19:11:33 +03:00
TSK-1451: Fix focus issues + jump workaround (#3167)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
a57f3b8c2f
commit
53c3f58e9d
@ -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"
|
||||
}
|
||||
|
@ -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 (
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
})
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
</script>
|
||||
|
||||
<svelte:window on:resize={_resize} />
|
||||
@ -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
|
||||
}}
|
||||
>
|
||||
<div
|
||||
|
@ -60,6 +60,9 @@ class FocusManagerImpl implements FocusManager {
|
||||
}
|
||||
|
||||
setFocusPos (order: number): void {
|
||||
if (order === -1) {
|
||||
return
|
||||
}
|
||||
const idx = this.elements.findIndex((it) => it.order === order)
|
||||
if (idx !== undefined) {
|
||||
this.current = idx
|
||||
|
@ -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<T> (id: Metadata<T>, value: T | null): void {
|
||||
if (value != null) {
|
||||
localStorage.setItem(id, typeof value === 'string' ? value : JSON.stringify(value))
|
||||
@ -30,6 +33,9 @@ export function setMetadataLocalStorage<T> (id: Metadata<T>, value: T | null): v
|
||||
setMetadata(id, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function fetchMetadataLocalStorage<T> (id: Metadata<T>): T | null {
|
||||
const data = localStorage.getItem(id)
|
||||
if (data === null) {
|
||||
@ -45,14 +51,27 @@ export function fetchMetadataLocalStorage<T> (id: Metadata<T>): 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,
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
</script>
|
||||
|
||||
<ActionContext
|
||||
@ -124,16 +126,18 @@
|
||||
{#if loading}
|
||||
<Loading />
|
||||
{:else}
|
||||
{#each docs as doc, i}
|
||||
<NotificationView
|
||||
value={doc}
|
||||
selected={selected === i}
|
||||
{viewlets}
|
||||
on:click={() => {
|
||||
selected = i
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
<ListView bind:this={listView} count={docs.length} selection={selected}>
|
||||
<svelte:fragment slot="item" let:item>
|
||||
<NotificationView
|
||||
value={docs[item]}
|
||||
selected={selected === item}
|
||||
{viewlets}
|
||||
on:click={() => {
|
||||
selected = item
|
||||
}}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</ListView>
|
||||
{/if}
|
||||
</Scroller>
|
||||
</div>
|
||||
|
@ -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'
|
||||
</script>
|
||||
|
||||
<FocusHandler {manager} />
|
||||
<ActionContext
|
||||
context={{
|
||||
mode: 'editor'
|
||||
}}
|
||||
/>
|
||||
{#if !embedded}
|
||||
<FocusHandler {manager} isEnabled={isContextEnabled} />
|
||||
<ActionContext
|
||||
context={{
|
||||
mode: 'editor'
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if issue !== undefined}
|
||||
<Panel
|
||||
@ -204,6 +210,7 @@
|
||||
placeholder={tracker.string.IssueTitlePlaceholder}
|
||||
kind="large-style"
|
||||
on:blur={save}
|
||||
focus={!embedded}
|
||||
/>
|
||||
<div class="w-full mt-6">
|
||||
{#key issue._id}
|
||||
|
@ -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 {
|
||||
|
@ -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])
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
@ -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)
|
||||
|
@ -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<ViewContext[]>([])
|
||||
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<ContextStore>(new ContextStore([]))
|
||||
|
Loading…
Reference in New Issue
Block a user