Keyboard edit action (#1487)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2022-04-22 13:30:34 +07:00 committed by GitHub
parent 04ed6e0e25
commit 299d9a9541
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 146 additions and 66 deletions

View File

@ -289,6 +289,9 @@ export function createModel (builder: Builder): void {
singleInput: true
})
actionTarget(builder, view.action.ShowPreview, core.class.Doc, { mode: 'browser' })
createAction(builder, view.action.Edit, view.string.Edit, view.actionImpl.Edit, { keyBinding: ['Enter'], singleInput: true })
actionTarget(builder, view.action.Edit, core.class.Doc, { mode: ['browser', 'context'] })
}
export default view

View File

@ -34,7 +34,10 @@ export default mergeIds(viewId, view, {
SelectDown: '' as Ref<Action>,
ShowPreview: '' as Ref<Action>,
ShowActions: '' as Ref<Action>
ShowActions: '' as Ref<Action>,
// Edit document
Edit: '' as Ref<Action>
},
actionImpl: {
Delete: '' as ViewAction,
@ -51,7 +54,9 @@ export default mergeIds(viewId, view, {
SelectDown: '' as ViewAction,
ShowPreview: '' as ViewAction,
ShowActions: '' as ViewAction
ShowActions: '' as ViewAction,
Edit: '' as ViewAction
},
component: {
StringEditor: '' as AnyComponent,
@ -84,6 +89,7 @@ export default mergeIds(viewId, view, {
SelectUp: '' as IntlString,
SelectDown: '' as IntlString,
ShowPreview: '' as IntlString,
ShowActions: '' as IntlString
ShowActions: '' as IntlString,
Edit: '' as IntlString
}
})

View File

@ -32,18 +32,26 @@
let innerWidth = 0
$: allowFullSize = innerWidth > 900 && position === 'full'
$: allowFullSize = innerWidth > 900 && (position === 'full' || position === 'content')
$: isFullSize = allowFullSize && fullSize
</script>
<svelte:window bind:innerWidth />
<Panel {title} {subtitle} {icon} on:close rightSection={isFullSize}>
<Panel {title} {subtitle} {icon} on:close rightSection={isFullSize} bind:innerWidth>
<svelte:fragment slot="subtitle">
<slot name="subtitle" />
</svelte:fragment>
<svelte:fragment slot='navigate-actions'>
<slot name='navigate-actions'/>
</svelte:fragment>
<svelte:fragment slot="commands">
<Component is={calendar.component.DocReminder} props={{ value: object, title }} />
<div class="ml-2">
<Component is={notification.component.LastViewEditor} props={{ value: object }} />
<div class='flex-row-center'>
<slot name="actions" />
</div>
<div class='flex-row-center flex-grow gap-2'>
<Component is={calendar.component.DocReminder} props={{ value: object, title }} />
<div class="ml-2">
<Component is={notification.component.LastViewEditor} props={{ value: object }} />
</div>
</div>
</svelte:fragment>
@ -65,7 +73,7 @@
}}
/>
</div>
{/if}
{/if}
</svelte:fragment>
{#if isFullSize}
<Scroller>
@ -77,4 +85,3 @@
</Component>
{/if}
</Panel>

View File

@ -39,7 +39,7 @@
}
&.mirror {
justify-content: space-between;
padding: 0 2.5rem;
padding: 0 0.5rem;
&-tool {
justify-content: space-between;

View File

@ -42,7 +42,6 @@
top: 1.375rem;
right: 2rem;
&.grow-reverse {
left: 0px;
display: flex;
flex-direction: row-reverse;
justify-content: flex-end;

View File

@ -26,16 +26,28 @@
export let rightSection: boolean = false
export let reverseCommands = false
export let showHeader = true
export let innerWidth: number = 0
const dispatch = createEventDispatcher()
</script>
<div class="antiPanel antiComponent">
<div class:panel-content={!rightSection} class:ad-section-50={rightSection} class:divide={rightSection}>
<div class="antiPanel antiComponent" bind:clientWidth={innerWidth}>
<div class:panel-content={!rightSection} class:ad-section-50={rightSection} class:divide={rightSection}>
{#if showHeader}
<div class="ac-header short mirror divide">
<div class="ac-header__wrap-title">
<div class="tool flex-row-center">
<ActionIcon
icon={IconClose}
size={'medium'}
action={() => {
dispatch('close')
}} />
</div>
{#if $$slots['navigate-actions']}
<slot name="navigate-actions" />
{/if}
<div class="ac-header__wrap-title flex-grow">
{#if icon}
<div class="ac-header__icon">
<Icon {icon} size={'large'} />
@ -52,6 +64,20 @@
</div>
{/if}
</div>
{:else}
<div class="ac-header short mirror divide">
<div class="tool flex-row-center">
<ActionIcon
icon={IconClose}
size={'medium'}
action={() => {
dispatch('close')
}} />
</div>
{#if $$slots['navigate-actions']}
<slot name="navigate-actions" />
{/if}
</div>
{/if}
{#if $$slots.subtitle}
<div class="ac-subtitle">
@ -69,21 +95,13 @@
<slot name="rightSection" />
{/if}
<div class="ad-tools" class:grow-reverse={reverseCommands}>
<div class="ad-tools flex-row-center flex-grow h-4" class:grow-reverse={reverseCommands}>
{#if !rightSection && $$slots.commands}
<div class="flex">
<slot name="commands" />
</div>
{/if}
<slot name="actions" />
<div class="tool">
<ActionIcon
icon={IconClose}
size={'medium'}
action={() => {
dispatch('close')
}} />
</div>
</div>
</div>

View File

@ -70,6 +70,9 @@
afterUpdate(() => {
if (props) fitPopup(props, contentPanel)
})
export function fitPopupInstance (): void {
if (props) fitPopup(props, contentPanel)
}
</script>
<svelte:window

View File

@ -189,9 +189,9 @@ export function fitPopupElement (modalHTML: HTMLElement, element?: PopupAlignmen
} else if (element === 'content' && contentPanel !== undefined) {
const rect = contentPanel.getBoundingClientRect()
modalHTML.style.top = `calc(${rect.top}px)`
modalHTML.style.height = `${rect.height}px`
modalHTML.style.height = `${Math.min(rect.height, window.innerHeight - rect.top)}px`
modalHTML.style.left = `calc(${rect.left}px)`
modalHTML.style.width = `${rect.width}px`
modalHTML.style.width = `${Math.min(rect.width, window.innerWidth - rect.left)}px`
} else if (element === 'middle') {
if (contentPanel !== undefined) {
const rect = contentPanel.getBoundingClientRect()

View File

@ -27,7 +27,7 @@
<a
class="flex-presenter"
class:inline-presenter={inline}
href="#{getPanelURI(view.component.EditDoc, value._id, value._class, 'full')}"
href="#{getPanelURI(view.component.EditDoc, value._id, value._class, 'content')}"
>
<div class="icon">
<Icon icon={board.icon.Board} size={'small'} />

View File

@ -27,7 +27,7 @@
<a
class="flex-presenter"
class:inline-presenter={inline}
href="#{getPanelURI(view.component.EditDoc, value._id, value._class, 'full')}"
href="#{getPanelURI(view.component.EditDoc, value._id, value._class, 'content')}"
>
<div class="icon">
<Icon icon={board.icon.Card} size={'small'} />

View File

@ -28,7 +28,7 @@
$: persons = Array.isArray(value) ? value : [value]
async function onClick (p: Person) {
showPanel(view.component.EditDoc, p._id, Hierarchy.mixinOrClass(p), 'full')
showPanel(view.component.EditDoc, p._id, Hierarchy.mixinOrClass(p), 'content')
}
</script>

View File

@ -22,7 +22,7 @@
export let value: Reminder
function click (): void {
showPanel(view.component.EditDoc, value._id, value._class, 'full')
showPanel(view.component.EditDoc, value._id, value._class, 'content')
}
const objectPresenter = getResource(view.component.ObjectPresenter)

View File

@ -29,7 +29,7 @@
}
function click (event: Event): void {
showPanel(view.component.EditDoc, event._id, event._class, 'full')
showPanel(view.component.EditDoc, event._id, event._class, 'content')
}
</script>

View File

@ -27,7 +27,7 @@
async function onClick () {
if (employee !== undefined) {
showPopup(EditDoc, { _id: employee._id, _class: employee._class }, 'full')
showPopup(EditDoc, { _id: employee._id, _class: employee._class }, 'content')
}
}
const client = getClient()

View File

@ -28,7 +28,7 @@
<a
class="flex-presenter"
class:inline-presenter={inline}
href="#{getPanelURI(view.component.EditDoc, value._id, value._class, 'full')}"
href="#{getPanelURI(view.component.EditDoc, value._id, value._class, 'content')}"
>
<div class="icon circle"><Company size={'small'} /></div>
<span class="label">{value.name}</span>

View File

@ -32,7 +32,7 @@
<a
class="flex-presenter"
class:inline-presenter={inline}
href="#{getPanelURI(view.component.EditDoc, value._id, Hierarchy.mixinOrClass(value), 'full')}"
href="#{getPanelURI(view.component.EditDoc, value._id, Hierarchy.mixinOrClass(value), 'content')}"
>
<div class="icon">
<Avatar size={avatarSize} avatar={value?.avatar} />

View File

@ -27,7 +27,7 @@
<a
class="flex-presenter"
class:inline-presenter={inline}
href="#{getPanelURI(view.component.EditDoc, value._id, value._class, 'full')}"
href="#{getPanelURI(view.component.EditDoc, value._id, value._class, 'content')}"
>
<div class="icon"><Icon icon={inventory.icon.Products} size={'small'} /></div>
<span class="label">{value.name}</span>

View File

@ -33,7 +33,7 @@
}
function showLead () {
showPanel(view.component.EditDoc, object._id, object._class, 'full')
showPanel(view.component.EditDoc, object._id, object._class, 'content')
}
</script>

View File

@ -27,7 +27,7 @@
<a
class="flex-presenter"
class:inline-presenter={inline}
href="#{getPanelURI(view.component.EditDoc, value._id, value._class, 'full')}"
href="#{getPanelURI(view.component.EditDoc, value._id, value._class, 'content')}"
>
<div class="icon">
<Icon icon={lead.icon.Lead} size={'small'} />

View File

@ -32,7 +32,7 @@
<a
class="flex-presenter"
class:inline-presenter={inline}
href="#{getPanelURI(view.component.EditDoc, value._id, value._class, 'full')}"
href="#{getPanelURI(view.component.EditDoc, value._id, value._class, 'content')}"
>
<div class="icon">
<Icon icon={recruit.icon.Application} size={'small'} />

View File

@ -45,7 +45,7 @@
<Avatar avatar={candidate.avatar} size={'large'} />
{#if candidate}
<div class="name lines-limit-2" class:over-underline={!disabled} on:click={() => {
if (!disabled) showPanel(view.component.EditDoc, candidate._id, candidate._class, 'full')
if (!disabled) showPanel(view.component.EditDoc, candidate._id, candidate._class, 'content')
}}>{formatName(candidate.name)}</div>
<div class="description lines-limit-2">{candidate.title ?? ''}</div>
<div class="description overflow-label">{candidate.city ?? ''}</div>

View File

@ -21,7 +21,7 @@
import { Avatar } from '@anticrm/presentation'
import type { Applicant } from '@anticrm/recruit'
import task, { TodoItem } from '@anticrm/task'
import { ActionIcon, Component, IconMoreH, showPanel, Tooltip } from '@anticrm/ui'
import { Component, showPanel, Tooltip } from '@anticrm/ui'
import view from '@anticrm/view'
import ApplicationPresenter from './ApplicationPresenter.svelte'
@ -29,7 +29,7 @@
export let dragged: boolean
function showCandidate () {
showPanel(view.component.EditDoc, object.attachedTo, object.attachedToClass, 'full')
showPanel(view.component.EditDoc, object.attachedTo, object.attachedToClass, 'content')
}
$: todoItems = (object.$lookup?.todoItems as TodoItem[]) ?? []

View File

@ -56,7 +56,7 @@
/>
<div class="clear-mins" on:click={() => {
if (candidate !== undefined) {
showPanel(view.component.EditDoc, candidate._id, candidate._class, 'full')
showPanel(view.component.EditDoc, candidate._id, candidate._class, 'content')
}
}}>
<UserBox

View File

@ -28,7 +28,7 @@
$: persons = Array.isArray(value) ? value : [value]
async function onClick (p: Person) {
showPanel(view.component.EditDoc, p._id, Hierarchy.mixinOrClass(p), 'full')
showPanel(view.component.EditDoc, p._id, Hierarchy.mixinOrClass(p), 'content')
}
</script>

View File

@ -40,7 +40,7 @@
<a
class="flex-presenter"
class:inline-presenter={inline}
href="#{getPanelURI(view.component.EditDoc, value._id, value._class, 'right')}"
href="#{getPanelURI(view.component.EditDoc, value._id, value._class, 'content')}"
>
<div class="icon">
<Icon icon={recruit.icon.Application} size={'small'} />

View File

@ -31,7 +31,7 @@
<a
class="flex-presenter"
class:inline-presenter={inline}
href="#{getPanelURI(view.component.EditDoc, value._id, value._class, 'full')}"
href="#{getPanelURI(view.component.EditDoc, value._id, value._class, 'content')}"
>
<div class="icon">
<Icon icon={task.icon.Task} size={'small'} />

View File

@ -73,7 +73,7 @@
<IssuePresenter value={object} {currentTeam} />
{/if}
</svelte:fragment>
<svelte:fragment slot="actions">
<svelte:fragment slot="navigate-actions">
<div class="tool flex gap-1">
<Button icon={IconDown}/>
<Button icon={IconUp}/>

View File

@ -1,6 +1,6 @@
import { Doc } from '@anticrm/core'
import { Doc, Hierarchy } from '@anticrm/core'
import { getClient, MessageBox } from '@anticrm/presentation'
import { showPopup } from '@anticrm/ui'
import { showPanel, showPopup } from '@anticrm/ui'
import MoveView from './components/Move.svelte'
import view from './plugin'
import { FocusSelection, focusStore, SelectDirection, selectionStore, previewDocument } from './selection'
@ -37,6 +37,10 @@ focusStore.subscribe((it) => {
})
function selPrev (doc: Doc | undefined, evt: Event, dir: SelectDirection): void {
selectPrevItem(dir)
evt.preventDefault()
}
export function selectPrevItem (dir: SelectDirection): void {
if ($focusStore.provider?.prev !== undefined) {
$focusStore.provider?.prev(dir)
previewDocument.update(old => {
@ -44,10 +48,15 @@ function selPrev (doc: Doc | undefined, evt: Event, dir: SelectDirection): void
return $focusStore.focus
}
})
evt.preventDefault()
}
}
function selNext (doc: Doc|undefined, evt: Event, dir: SelectDirection): void {
selectNextItem(dir)
evt.preventDefault()
}
export function selectNextItem (dir: SelectDirection): void {
if ($focusStore.provider?.next !== undefined) {
$focusStore.provider?.next(dir)
previewDocument.update(old => {
@ -55,7 +64,6 @@ function selNext (doc: Doc|undefined, evt: Event, dir: SelectDirection): void {
return $focusStore.focus
}
})
evt.preventDefault()
}
}
@ -103,6 +111,12 @@ function ShowPreview (doc: Doc | undefined, evt: Event): void {
})
evt.preventDefault()
}
function Edit (doc: Doc, evt: Event): void {
evt.preventDefault()
showPanel(view.component.EditDoc, doc._id, Hierarchy.mixinOrClass(doc), 'content')
}
/**
* @public
*/
@ -117,5 +131,6 @@ export const actionImpl = {
SelectItemNone,
SelectItemAll,
ShowActions,
ShowPreview
ShowPreview,
Edit
}

View File

@ -3,7 +3,7 @@
import { ViewContext } from '@anticrm/view'
import { onDestroy } from 'svelte'
import { contextStore } from '../context'
import { previewDocument } from '../selection';
import { previewDocument } from '../selection'
export let context: ViewContext

View File

@ -49,6 +49,7 @@
async function handleKeys (evt: KeyboardEvent): Promise<void> {
const targetTagName = (evt.target as any)?.tagName?.toLowerCase()
if (targetTagName === 'input' || targetTagName === 'button' || targetTagName === 'textarea') {
console.log('no keyboard because', targetTagName, evt.target)
return
}
lastKey = evt

View File

@ -26,11 +26,14 @@
getClient,
KeyedAttribute
} from '@anticrm/presentation'
import { AnyComponent, Component, Label, PopupAlignment } from '@anticrm/ui'
import { AnyComponent, Button, Component, IconDown, IconUp, Label, PopupAlignment, showPanel } from '@anticrm/ui'
import view from '@anticrm/view'
import { createEventDispatcher, onDestroy } from 'svelte'
import ActionContext from './ActionContext.svelte'
import { getCollectionCounter, getMixinStyle } from '../utils'
import { selectNextItem, selectPrevItem } from '../actionImpl'
import { tick } from 'svelte'
import { focusStore } from '../selection'
export let _id: Ref<Doc>
export let _class: Ref<Class<Doc>>
@ -238,6 +241,17 @@
headerLoading = false
})
}
async function next (pn: boolean): Promise<void> {
if (pn) {
selectNextItem('vertical')
} else {
selectPrevItem('vertical')
}
await tick()
if ($focusStore.focus !== undefined) {
showPanel(view.component.EditDoc, $focusStore.focus._id, $focusStore.focus._class, 'content')
}
}
</script>
<ActionContext context={{
mode: 'editor'
@ -255,6 +269,12 @@
dispatch('close')
}}
>
<svelte:fragment slot="navigate-actions">
<div class="tool flex-row-center gap-1 mr-4">
<Button icon={IconDown} kind={'no-border'} on:click={() => next(true)}/>
<Button icon={IconUp} kind={'no-border'} on:click={() => next(false)}/>
</div>
</svelte:fragment>
<div class="w-full" slot="subtitle">
{#if !headerLoading}
{#if headerEditor !== undefined}

View File

@ -62,7 +62,6 @@ export function updateFocus (selection?: FocusSelection): void {
cur.focus = selection?.focus
cur.provider = selection?.provider
;(cur as any).now = now
console.log('update focus', selection?.focus)
previewDocument.update(old => {
if (old !== undefined) {
return selection?.focus

View File

@ -130,6 +130,9 @@ export interface ActionTarget<T extends Doc = Doc> extends Doc {
action: Ref<Action>
query?: DocumentQuery<T>
context: ViewContext
// If specified, will be used instead of action from Action.
override?: ViewAction
}
/**

View File

@ -38,7 +38,7 @@
} from '@anticrm/ui'
import { ActionContext, ActionHandler } from '@anticrm/view-resources'
import type { Application, NavigatorModel, SpecialNavModel, ViewConfiguration } from '@anticrm/workbench'
import { onDestroy } from 'svelte'
import { onDestroy, tick } from 'svelte'
import workbench from '../plugin'
import AccountPopup from './AccountPopup.svelte'
import AppItem from './AppItem.svelte'
@ -75,10 +75,16 @@
apps = result
})
let panelInstance: PanelInstance
let visibileNav: boolean = true
async function toggleNav (): Promise<void> {
visibileNav = !visibileNav
closeTooltip()
if (currentApplication && navigatorModel && navigator) {
await tick()
panelInstance.fitPopupInstance()
}
}
const account = getCurrentAccount() as EmployeeAccount
@ -262,7 +268,7 @@
const resizing = (event: MouseEvent): void => {
if (isResizing && aside) {
let X = event.clientX - dX
const X = event.clientX - dX
const newWidth = asideWidth + oldX - X
if (newWidth > 320 && componentWidth - (oldX - X) > 320) {
aside.style.width = aside.style.maxWidth = aside.style.minWidth = newWidth + 'px'
@ -313,16 +319,16 @@
</clipPath>
</svg>
<div class="workbench-container">
<div class="antiPanel-application" on:click={toggleNav}>
<div class="antiPanel-application">
<div class="flex-col mt-1">
<!-- <ActivityStatus status="active" /> -->
<AppItem
icon={TopMenu}
label={visibileNav ? workbench.string.HideMenu : workbench.string.ShowMenu}
selected={!visibileNav}
action={toggleNav}
notify={false}
/>
<AppItem
icon={TopMenu}
label={visibileNav ? workbench.string.HideMenu : workbench.string.ShowMenu}
selected={!visibileNav}
action={toggleNav}
notify={false}
/>
</div>
<Applications
{apps}
@ -413,7 +419,7 @@
{/if}
</div>
<div bind:this={cover} class="cover" />
<PanelInstance {contentPanel} >
<PanelInstance bind:this={panelInstance} {contentPanel} >
<svelte:fragment slot='panel-header'>
<ActionContext
context={{ mode: 'panel' }}