Selection/Navigation fixes (#1500)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2022-04-24 12:18:03 +07:00 committed by GitHub
parent a45fecaf69
commit 0188f2dcc3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 232 additions and 258 deletions

View File

@ -26,7 +26,7 @@ import attachment from '@anticrm/model-attachment'
import chunter from '@anticrm/model-chunter'
import core, { TAccount, TAttachedDoc, TDoc, TSpace } from '@anticrm/model-core'
import presentation from '@anticrm/model-presentation'
import view from '@anticrm/model-view'
import view, { actionTarget } from '@anticrm/model-view'
import workbench from '@anticrm/model-workbench'
import type { Asset, IntlString } from '@anticrm/platform'
import contact from './plugin'
@ -265,6 +265,8 @@ export function createModel (builder: Builder): void {
label: contact.string.SearchOrganization,
query: contact.completion.OrganizationQuery
}, contact.completion.OrganizationCategory)
actionTarget(builder, view.action.Open, contact.class.Contact, { mode: ['browser', 'context'] })
}
export { contactOperation } from './migration'

View File

@ -457,6 +457,9 @@ export function createModel (builder: Builder): void {
actions: [view.action.Delete]
})
createReviewModel(builder)
actionTarget(builder, view.action.Open, recruit.class.Vacancy, { mode: ['browser', 'context'] })
actionTarget(builder, view.action.Open, recruit.class.Applicant, { mode: ['browser', 'context'] })
}
export { recruitOperation } from './migration'

View File

@ -17,6 +17,7 @@ import { IntlString, mergeIds } from '@anticrm/platform'
import { TagCategory, tagsId } from '@anticrm/tags'
import tags from '@anticrm/tags-resources/src/plugin'
import type { AnyComponent } from '@anticrm/ui'
import { ViewAction } from '@anticrm/model-view'
export default mergeIds(tagsId, tags, {
// Without it, CLI version is failed with some svelte dependency exception.
@ -44,5 +45,8 @@ export default mergeIds(tagsId, tags, {
},
category: {
Category: '' as Ref<TagCategory>
},
actionImpl: {
Open: '' as ViewAction
}
})

View File

@ -33,12 +33,11 @@ import {
import attachment from '@anticrm/model-attachment'
import chunter from '@anticrm/model-chunter'
import core, { DOMAIN_SPACE, TAttachedDoc, TDoc, TSpace } from '@anticrm/model-core'
import workbench from '@anticrm/model-workbench'
import { Asset, IntlString } from '@anticrm/platform'
import { Document, Issue, IssuePriority, IssueStatus, IssueStatusCategory, Team } from '@anticrm/tracker'
import tracker from './plugin'
import workbench from '@anticrm/model-workbench'
export { trackerOperation } from './migration'
export { default } from './plugin'

View File

@ -37,12 +37,14 @@ import type {
TextPresenter,
ViewAction,
ViewContext,
ViewContextType,
Viewlet,
ViewletDescriptor
} from '@anticrm/view'
import view from './plugin'
export { viewOperation } from './migration'
export { ViewAction }
export function createAction (
builder: Builder,
@ -73,12 +75,22 @@ export function actionTarget (
builder: Builder,
action: Ref<Action>,
target: Ref<Class<Doc>>,
context: ViewContext
options: {
mode: ViewContextType | ViewContextType[]
application?: Ref<Doc>
group?: string
override?: ViewAction
}
): void {
builder.createDoc(view.class.ActionTarget, core.space.Model, {
target,
action,
context
context: {
mode: options.mode,
application: options.application,
group: options.group
},
override: options.override
})
}
@ -290,8 +302,14 @@ export function createModel (builder: Builder): void {
})
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'] })
createAction(builder, view.action.Open, view.string.Open, view.actionImpl.Open, {
icon: view.icon.Open,
keyBinding: ['Enter'],
singleInput: true
})
// Should be contributed via individual plugins.
// actionTarget(builder, view.action.Open, core.class.Doc, { mode: ['browser', 'context'] })
}
export default view

View File

@ -37,7 +37,7 @@ export default mergeIds(viewId, view, {
ShowActions: '' as Ref<Action>,
// Edit document
Edit: '' as Ref<Action>
Open: '' as Ref<Action>
},
actionImpl: {
Delete: '' as ViewAction,
@ -56,7 +56,7 @@ export default mergeIds(viewId, view, {
ShowPreview: '' as ViewAction,
ShowActions: '' as ViewAction,
Edit: '' as ViewAction
Open: '' as ViewAction
},
component: {
StringEditor: '' as AnyComponent,
@ -90,6 +90,6 @@ export default mergeIds(viewId, view, {
SelectDown: '' as IntlString,
ShowPreview: '' as IntlString,
ShowActions: '' as IntlString,
Edit: '' as IntlString
Open: '' as IntlString
}
})

View File

@ -186,7 +186,26 @@
stateRefs[statePos].scrollIntoView({ behavior: 'auto', block: 'nearest' })
}
export function selectStatePosition (pos: number, direction: 'up' | 'down' | 'left' | 'right'): void {
export function select (offset: 1 | -1 | 0, of?: Doc, dir?: 'vertical' | 'horizontal'): void {
let pos = (((of !== undefined) ? objects.findIndex(it => it._id === of._id) : selection) ?? -1)
if (pos === -1) {
for (const st of states) {
const stateObjs = getStateObjects(objects, st)
if (stateObjs.length > 0) {
pos = objects.findIndex(it => it._id === stateObjs[0].it._id)
console.log('SELECT', '#1', pos)
break
}
}
}
if (pos < 0) {
pos = 0
}
if (pos >= objects.length) {
pos = objects.length - 1
}
const obj = objects[pos]
if (obj === undefined) {
return
@ -201,16 +220,13 @@
if (statePos === undefined) {
return
}
switch (direction) {
case 'up':
if (offset === -1) {
if (dir === undefined || dir === 'vertical') {
scrollInto(objState)
dispatch('obj-focus', (stateObjs[statePos - 1] ?? stateObjs[0]).it)
break
case 'down':
scrollInto(objState)
dispatch('obj-focus', (stateObjs[statePos + 1] ?? stateObjs[stateObjs.length - 1]).it)
break
case 'left':
return
} else {
while (objState > 0) {
objState--
const nstateObjs = getStateObjects(objects, states[objState])
@ -220,8 +236,14 @@
break
}
}
break
case 'right':
}
}
if (offset === 1) {
if (dir === undefined || dir === 'vertical') {
scrollInto(objState)
dispatch('obj-focus', (stateObjs[statePos + 1] ?? stateObjs[stateObjs.length - 1]).it)
return
} else {
while (objState < states.length - 1) {
objState++
const nstateObjs = getStateObjects(objects, states[objState])
@ -231,7 +253,11 @@
break
}
}
break
}
}
if (offset === 0) {
scrollInto(objState)
dispatch('obj-focus', obj)
}
}

View File

@ -76,7 +76,6 @@
placeholder={attribute?.label}
{maxWidth}
value={getAttribute(client, object, { key: attributeKey, attr: attribute })}
attributeType={attribute.type}
space={object.space}
{onChange}
{focus}
@ -95,7 +94,6 @@
placeholder={attribute?.label}
{maxWidth}
value={getAttribute(client, object, { key: attributeKey, attr: attribute })}
attributeType={attribute.type}
space={object.space}
{onChange}
{focus}
@ -107,7 +105,6 @@
this={instance}
{maxWidth}
value={getAttribute(client, object, { key: attributeKey, attr: attribute })}
attributeType={attribute.type}
space={object.space}
{onChange}
{focus}

View File

@ -33,10 +33,8 @@
export let labelProps: any | undefined = undefined
export let okAction: () => void
export let canSave: boolean = false
export let size: 'small'| 'medium' = 'small'
export let createMore: boolean | undefined = undefined
export let okLabel: IntlString = presentation.string.Create
export let cancelLabel: IntlString = presentation.string.Cancel
const dispatch = createEventDispatcher()
</script>

View File

@ -31,7 +31,6 @@
export let label: IntlString
export let placeholder: IntlString = presentation.string.Search
export let value: Ref<Contact> | null | undefined
export let show: boolean = false
export let allowDeselect = false
export let titleDeselect: IntlString | undefined = undefined
export let readonly = false

View File

@ -16,7 +16,7 @@
import type { IntlString } from '@anticrm/platform'
import { translate } from '@anticrm/platform'
import { createEventDispatcher } from 'svelte'
import { Icon, Label, getPlatformColor } from '..'
import { getPlatformColor } from '..'
export let placeholder: IntlString | undefined = undefined
export let placeholderParam: any | undefined = undefined

View File

@ -23,7 +23,7 @@
export let ctx: any = undefined
const dispatch = createEventDispatcher()
let btns: HTMLButtonElement[] = []
const btns: HTMLButtonElement[] = []
const keyDown = (ev: KeyboardEvent, n: number): void => {
if (ev.key === 'ArrowDown') {

View File

@ -16,7 +16,6 @@
import { createEventDispatcher } from 'svelte'
import TimeShiftPresenter from './TimeShiftPresenter.svelte'
export let value: number
export let direction: 'before' | 'after'
export let minutes: number[] = [5, 15, 30]
export let hours: number[] = [1, 2, 4]

View File

@ -52,7 +52,7 @@
{#if $$slots.cell}
<slot name="header" date={day(weekMonday, 0)} days={displayedDaysCount} />
{/if}
{#each [...Array(displayedHours).keys()] as hourOfDay, row}
{#each [...Array(displayedHours).keys()] as hourOfDay}
<tr class="antiTable-body__row">
<td style="width: 50px;" class="calendar-td">
<div class="antiTable-cells__firstCell">
@ -63,7 +63,7 @@
<td
class="antiTable-body__border calendar-td cell"
style={`height: ${cellHeight};`}
on:click={(evt) => {
on:click={() => {
onSelect(day(weekMonday, dayIndex))
}}
>
@ -82,15 +82,6 @@
padding: 0;
margin: 0;
}
.selected {
// border-radius: 3px;
background-color: var(--primary-button-enabled);
// border-color: var(--primary-button-focused-border);
color: var(--primary-button-color);
border-radius: 0.5rem;
height: calc(100% - 5px);
width: calc(100% - 5px);
}
.cell {
width: 8rem;
overflow: hidden;

View File

@ -119,7 +119,7 @@ export function fitPopupPositionedElement (modalHTML: HTMLElement, alignment: Po
modalHTML.style.left = modalHTML.style.right = modalHTML.style.top = modalHTML.style.bottom = ''
modalHTML.style.maxHeight = modalHTML.style.height = ''
modalHTML.style.maxWidth = modalHTML.style.width = ''
if (alignment.position) {
if (alignment.position !== undefined) {
if (alignment.position.v === 'top') {
modalHTML.style.top = `${rect.top}px`
} else if (alignment.position.v === 'bottom') {
@ -171,6 +171,7 @@ export function fitPopupElement (modalHTML: HTMLElement, element?: PopupAlignmen
modalHTML.style.top = `calc(${rect.top}px + 0.5rem)`
modalHTML.style.bottom = '0.75rem'
modalHTML.style.right = '0.75rem'
modalHTML.style.maxWidth = '50%'
show = true
} else if (element === 'top') {
modalHTML.style.top = '15vh'

View File

@ -42,7 +42,7 @@ export interface AnySvelteComponentWithProps {
export interface Action {
label: IntlString
icon: Asset | AnySvelteComponent
action: (props: any, ev?: Event) => Promise<void>
action: (props: any, ev: Event) => Promise<void>
}
export interface IPopupItem {

View File

@ -127,10 +127,6 @@
.ref-input {
flex-shrink: 0;
padding: 1.5rem 2.5rem;
&.fill {
background-color: var(--body-color);
}
}
.p-activity {
padding: 1.5rem 2.5rem 2.5rem;

View File

@ -19,16 +19,7 @@
import core, { AnyAttribute, Doc, getCurrentAccount, Ref } from '@anticrm/core'
import { Asset, getResource } from '@anticrm/platform'
import { getClient } from '@anticrm/presentation'
import {
Component,
Icon, IconEdit,
IconMoreH,
Label,
Menu,
ShowMore,
showPopup,
TimeSince
} from '@anticrm/ui'
import { Component, Icon, IconEdit, IconMoreH, Label, Menu, ShowMore, showPopup, TimeSince } from '@anticrm/ui'
import type { AttributeModel } from '@anticrm/view'
import { getActions } from '@anticrm/view-resources'
import { ActivityKey, DisplayTx } from '../activity'
@ -60,7 +51,7 @@
const client = getClient()
function getProps (props: any, edit: boolean): any {
function getProps(props: any, edit: boolean): any {
return { ...props, edit }
}
@ -96,7 +87,7 @@
...actions.map((a) => ({
label: a.label,
icon: a.icon,
action: async (evt: Event) => {
action: async (ctx:any, evt: Event) => {
const impl = await getResource(a.action)
await impl(tx.doc as Doc, evt)
}
@ -110,7 +101,7 @@
edit = false
props = getProps(props, edit)
}
function isMessageType (attr?: AnyAttribute): boolean {
function isMessageType(attr?: AnyAttribute): boolean {
return attr?.type._class === core.class.TypeMarkup
}
@ -181,11 +172,11 @@
{/if}
{#if isMessageType(m.attribute)}
<div class="strong message emphasized">
<svelte:component this={m.presenter} {value} attributeType={m.attribute?.type} />
<svelte:component this={m.presenter} {value} />
</div>
{:else}
<div class="strong">
<svelte:component this={m.presenter} {value} attributeType={m.attribute?.type} />
<svelte:component this={m.presenter} {value} />
</div>
{/if}
{/if}
@ -206,11 +197,11 @@
</span>
{#if isMessageType(m.attribute)}
<div class="strong message emphasized">
<svelte:component this={m.presenter} {value} attributeType={m.attribute?.type} />
<svelte:component this={m.presenter} {value} />
</div>
{:else}
<div class="strong">
<svelte:component this={m.presenter} {value} attributeType={m.attribute?.type} />
<svelte:component this={m.presenter} {value} />
</div>
{/if}
{/if}
@ -223,12 +214,10 @@
<TxViewTx {tx} {onCancelEdit} {edit} {viewlet}/>
</div>
</ShowMore>
{:else}
{#if typeof viewlet.component === 'string'}
<Component is={viewlet.component} {props} on:close={onCancelEdit} />
{:else}
<svelte:component this={viewlet.component} {...props} on:close={onCancelEdit} />
{/if}
{:else if typeof viewlet.component === 'string'}
<Component is={viewlet.component} {props} on:close={onCancelEdit} />
{:else}
<svelte:component this={viewlet.component} {...props} on:close={onCancelEdit} />
{/if}
{/if}
</div>
@ -242,14 +231,12 @@
<ShowMore ignore={edit}>
{#if tx.collectionAttribute !== undefined && (tx.txDocIds?.size ?? 0) > 1}
<div class="flex-row-center flex-grow flex-wrap">
<TxViewTx {tx} {onCancelEdit} {edit} {viewlet}/>
<TxViewTx {tx} {onCancelEdit} {edit} {viewlet} />
</div>
{:else if typeof viewlet.component === 'string'}
<Component is={viewlet.component} {props} on:close={onCancelEdit} />
{:else}
{#if typeof viewlet.component === 'string'}
<Component is={viewlet.component} {props} on:close={onCancelEdit} />
{:else}
<svelte:component this={viewlet.component} {...props} on:close={onCancelEdit} />
{/if}
<svelte:component this={viewlet.component} {...props} on:close={onCancelEdit} />
{/if}
</ShowMore>
</div>

View File

@ -12,8 +12,12 @@
function filterTx (dtx: DisplayTx[], _class: Ref<Class<Doc>>): DisplayTx[] {
return dtx.filter((it) => it.tx._class === _class)
}
function getProps (props: any, edit: boolean): any {
return { ...props, edit }
function getProps (ctx: DisplayTx, edit: boolean): any {
if (viewlet?.pseudo) {
return { value: ctx.doc }
}
const dprops = getDTxProps(ctx)
return { ...dprops, edit }
}
</script>
@ -24,9 +28,9 @@
{/if}
<div class="mr-2">
{#if typeof viewlet?.component === 'string'}
<Component is={viewlet?.component} props={getProps(getDTxProps(ctx), edit)} on:close={onCancelEdit} />
<Component is={viewlet?.component} props={getProps(ctx, edit)} on:close={onCancelEdit} />
{:else}
<svelte:component this={viewlet?.component} {...getProps(getDTxProps(ctx), edit)} on:close={onCancelEdit} />
<svelte:component this={viewlet?.component} {...getProps(ctx, edit)} on:close={onCancelEdit} />
{/if}
</div>
{/each}
@ -36,9 +40,9 @@
{/if}
<div class="mr-2">
{#if typeof viewlet?.component === 'string'}
<Component is={viewlet?.component} props={getProps(getDTxProps(ctx), edit)} on:close={onCancelEdit} />
<Component is={viewlet?.component} props={getProps(ctx, edit)} on:close={onCancelEdit} />
{:else}
<svelte:component this={viewlet?.component} {...getProps(getDTxProps(ctx), edit)} on:close={onCancelEdit} />
<svelte:component this={viewlet?.component} {...getProps(ctx, edit)} on:close={onCancelEdit} />
{/if}
</div>
{/each}

View File

@ -10,6 +10,7 @@ import activity from '../plugin'
export type TxDisplayViewlet =
| (Pick<TxViewlet, 'icon' | 'label' | 'display' | 'editable' | 'hideOnRemove' | 'labelComponent' | 'labelParams'> & {
component?: AnyComponent | AnySvelteComponent
pseudo: boolean
})
| undefined
@ -41,13 +42,14 @@ async function createPseudoViewlet (
icon: docClass.icon ?? activity.icon.Activity,
label: label,
labelParams: { _class: trLabel, collection: dtx.collectionAttribute?.label !== undefined ? await translate(dtx.collectionAttribute?.label, {}) : '' },
component: presenter.presenter
component: presenter.presenter,
pseudo: true
}
}
}
export function getDTxProps (dtx: DisplayTx): any {
return { tx: dtx.tx, value: dtx.doc, dtx }
return { tx: dtx.tx, value: dtx.doc }
}
function getViewlet (viewlets: Map<ActivityKey, TxViewlet>, dtx: DisplayTx): TxDisplayViewlet | undefined {
@ -57,7 +59,10 @@ function getViewlet (viewlets: Map<ActivityKey, TxViewlet>, dtx: DisplayTx): TxD
} else {
key = activityKey(dtx.tx.objectClass, dtx.tx._class)
}
return viewlets.get(key)
const vl = viewlets.get(key)
if (vl !== undefined) {
return { ...vl, pseudo: false }
}
}
export async function updateViewlet (
@ -73,12 +78,14 @@ export async function updateViewlet (
}> {
let viewlet = getViewlet(viewlets, dtx)
const props = getDTxProps(dtx)
let props = getDTxProps(dtx)
let model: AttributeModel[] = []
let modelIcon: Asset | undefined
if (viewlet === undefined) {
;({ viewlet, model } = await checkInlineViewlets(dtx, viewlet, client, model))
// Only value is necessary for inline viewlets
props = { value: dtx.doc }
if (model !== undefined) {
// Check for State attribute
for (const a of model) {

View File

@ -30,7 +30,6 @@
import DatePresenter from './presenters/DatePresenter.svelte'
export let object: WithLookup<Card>
export let dragged: boolean
let loadingAttachment = 0
let dragoverAttachment = false

View File

@ -21,7 +21,7 @@
import type { Kanban, SpaceWithStates, State } from '@anticrm/task'
import task, { calcRank } from '@anticrm/task'
import { showPopup } from '@anticrm/ui'
import { ActionContext, focusStore, ListSelectionProvider, Menu, selectionStore } from '@anticrm/view-resources'
import { ActionContext, focusStore, ListSelectionProvider, Menu, SelectDirection, selectionStore } from '@anticrm/view-resources'
import { onMount } from 'svelte'
import AddCard from './add-card/AddCard.svelte'
import KanbanCard from './KanbanCard.svelte'
@ -74,21 +74,8 @@
let kanbanUI: KanbanUI
const listProvider = new ListSelectionProvider(
(pos, dir) => {
if (dir === 'vertical') {
// Select next
kanbanUI.selectStatePosition(pos, 'down')
} else {
kanbanUI.selectStatePosition(pos, 'right')
}
},
(pos, dir) => {
// Select prev
if (dir === 'vertical') {
kanbanUI.selectStatePosition(pos, 'up')
} else {
kanbanUI.selectStatePosition(pos, 'left')
}
(offset: 1 | -1 | 0, of?: Doc, dir?: SelectDirection) => {
kanbanUI.select(offset, of, dir)
}
)
onMount(() => {
@ -133,8 +120,8 @@
on:contextmenu={(evt) => showMenu(evt.detail.evt, evt.detail.objects)}
selection={listProvider.current($focusStore)}
>
<svelte:fragment slot="card" let:object let:dragged>
<KanbanCard object={castObject(object)} {dragged} />
<svelte:fragment slot="card" let:object>
<KanbanCard object={castObject(object)} />
</svelte:fragment>
<svelte:fragment slot="additionalPanel">

View File

@ -1,7 +1,5 @@
<script lang="ts">
import { Card, CardLabel } from '@anticrm/board'
import { createEventDispatcher } from 'svelte'
import CardLabelsEditor from './CardLabelsEditor.svelte'
import CardLabelsPicker from './CardLabelsPicker.svelte'
@ -12,15 +10,11 @@
object?: CardLabel
} = {}
let search: string | undefined = undefined
const dispatch = createEventDispatcher()
function setEditMode (isEdit: boolean, object?: CardLabel) {
editMode = { isEdit, object }
}
function close () {
dispatch('close')
}
</script>
{#if editMode.isEdit}

View File

@ -25,7 +25,7 @@
const maxLenght: number = 30
const trimFilename = (fname: string): string =>
fname.length > maxLenght ? fname.substr(0, (maxLenght - 1) / 2) + '...' + fname.substr(-(maxLenght - 1) / 2) : fname
fname.length > maxLenght ? fname.substring(0, (maxLenght - 1) / 2) + '...' + fname.substring(-(maxLenght - 1) / 2) : fname
function iconLabel (name: string): string {
const parts = name.split('.')

View File

@ -108,7 +108,7 @@
...actions.map((a) => ({
label: a.label,
icon: a.icon,
action: async (evt: MouseEvent) => {
action: async (ctx:any, evt: MouseEvent) => {
const impl = await getResource(a.action)
await impl(message, evt)
}

View File

@ -26,7 +26,6 @@
import notification from '@anticrm/notification'
export let object: WithLookup<Lead>
export let dragged: boolean
function showMenu (ev?: Event): void {
showPopup(ContextMenu, { object }, (ev as MouseEvent).target as HTMLElement)

View File

@ -26,7 +26,6 @@
export let items: TagReference[] = []
export let targetClass: Ref<Class<Doc>>
export let key: KeyedAttribute
export let elements: Map<Ref<TagElement>, TagElement>
export let newElements: TagElement[] = []
export let countLabel: IntlString

View File

@ -11,20 +11,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Doc, TxOperations } from '@anticrm/core'
import { Resources } from '@anticrm/platform'
import { TagElement } from '@anticrm/tags'
import { eventToHTMLElement, showPopup } from '@anticrm/ui'
import TagsCategoryBar from './components/CategoryBar.svelte'
import CategoryPresenter from './components/CategoryPresenter.svelte'
import EditTagElement from './components/EditTagElement.svelte'
import TagElementPresenter from './components/TagElementPresenter.svelte'
import TagReferencePresenter from './components/TagReferencePresenter.svelte'
import Tags from './components/Tags.svelte'
import TagsPresenter from './components/TagsPresenter.svelte'
import TagsItemPresenter from './components/TagsItemPresenter.svelte'
import TagsView from './components/TagsView.svelte'
import TagsEditor from './components/TagsEditor.svelte'
import TagsDropdownEditor from './components/TagsDropdownEditor.svelte'
import CategoryPresenter from './components/CategoryPresenter.svelte'
import tags from './plugin'
import TagsCategoryBar from './components/CategoryBar.svelte'
import TagsEditor from './components/TagsEditor.svelte'
import TagsItemPresenter from './components/TagsItemPresenter.svelte'
import TagsPresenter from './components/TagsPresenter.svelte'
import TagsView from './components/TagsView.svelte'
export default async (): Promise<Resources> => ({
component: {
@ -38,5 +38,10 @@ export default async (): Promise<Resources> => ({
TagsItemPresenter,
CategoryPresenter,
TagsCategoryBar
},
actionImpl: {
Open: (value: TagElement, evt: MouseEvent) => {
showPopup(EditTagElement, { value, keyTitle: '' }, eventToHTMLElement(evt))
}
}
})

View File

@ -21,7 +21,7 @@
import type { Kanban, SpaceWithStates, State, Task } from '@anticrm/task'
import task from '@anticrm/task'
import { showPopup } from '@anticrm/ui'
import { ActionContext, focusStore, ListSelectionProvider, selectionStore } from '@anticrm/view-resources'
import { ActionContext, focusStore, ListSelectionProvider, SelectDirection, selectionStore } from '@anticrm/view-resources'
import Menu from '@anticrm/view-resources/src/components/Menu.svelte'
import { onMount } from 'svelte'
import KanbanDragDone from './KanbanDragDone.svelte'
@ -66,22 +66,10 @@
/* eslint-disable no-undef */
let kanbanUI: KanbanUI
let objects: Doc[] = []
const listProvider = new ListSelectionProvider(
(pos, dir) => {
if (dir === 'vertical') {
// Select next
kanbanUI.selectStatePosition(pos, 'down')
} else {
kanbanUI.selectStatePosition(pos, 'right')
}
},
(pos, dir) => {
// Select prev
if (dir === 'vertical') {
kanbanUI.selectStatePosition(pos, 'up')
} else {
kanbanUI.selectStatePosition(pos, 'left')
}
(offset: 1 | -1 | 0, of?: Doc, dir?: SelectDirection) => {
kanbanUI.select(offset, of, dir)
}
)
onMount(() => {
@ -96,7 +84,6 @@
// selection = undefined
})
}
</script>
{#await cardPresenter then presenter}
@ -118,6 +105,7 @@
rankFieldName={'rank'}
on:content={(evt) => {
listProvider.update(evt.detail)
objects = evt.detail
}}
on:obj-focus={(evt) => {
listProvider.updateFocus(evt.detail)

View File

@ -19,7 +19,7 @@
import { createQuery } from '@anticrm/presentation'
import { Issue, Team } from '@anticrm/tracker'
import { Button, eventToHTMLElement, Icon, IconAdd, showPopup, Tooltip } from '@anticrm/ui'
import { focusStore, ListSelectionProvider, selectionStore } from '@anticrm/view-resources'
import { focusStore, ListSelectionProvider, SelectDirection, selectionStore } from '@anticrm/view-resources'
import ActionContext from '@anticrm/view-resources/src/components/ActionContext.svelte'
import Menu from '@anticrm/view-resources/src/components/Menu.svelte'
import { onMount } from 'svelte'
@ -70,21 +70,8 @@ import PriorityPresenter from './PriorityPresenter.svelte'
let kanbanUI: Kanban
const listProvider = new ListSelectionProvider(
(pos, dir) => {
if (dir === 'vertical') {
// Select next
kanbanUI.selectStatePosition(pos, 'down')
} else {
kanbanUI.selectStatePosition(pos, 'right')
}
},
(pos, dir) => {
// Select prev
if (dir === 'vertical') {
kanbanUI.selectStatePosition(pos, 'up')
} else {
kanbanUI.selectStatePosition(pos, 'left')
}
(offset: 1 | -1 | 0, of?: Doc, dir?: SelectDirection) => {
kanbanUI.select(offset, of, dir)
}
)
onMount(() => {

View File

@ -41,4 +41,9 @@
<path d="M13.3,8.3c-0.1,2.8-2.5,5.1-5.4,5.1C5,13.4,2.6,11,2.6,8c0-2.9,2.3-5.2,5.1-5.4c0.1-0.4,0.2-0.7,0.4-1c0,0-0.1,0-0.1,0 C4.4,1.7,1.6,4.5,1.6,8c0,3.5,2.9,6.4,6.4,6.4s6.4-2.9,6.4-6.4c0,0,0-0.1,0-0.1C14,8.1,13.7,8.2,13.3,8.3z"/>
<ellipse cx="12.1" cy="3.9" rx="2.5" ry="2.5"/>
</symbol>
<symbol id='open' viewBox="0 0 16 16">
<path d="M14,13.1H9.2c-0.3,0-0.5,0.2-0.5,0.5s0.2,0.5,0.5,0.5H14c0.3,0,0.5-0.2,0.5-0.5S14.3,13.1,14,13.1z"/>
<path d="M11.4,7.1C11.4,7.1,11.4,7.1,11.4,7.1c1.2-1.6,1.3-1.6,1.3-1.6C12.9,5,13,4.5,12.9,4c-0.1-0.5-0.4-0.9-0.8-1.2 c0,0-1.1-0.9-1.1-0.9c-0.8-0.7-2.1-0.6-2.8,0.3c0,0,0,0,0,0l-6.3,7.9c-0.3,0.4-0.4,0.9-0.3,1.4l0.5,2.3c0.1,0.2,0.3,0.4,0.5,0.4 c0,0,0,0,0,0l2.4,0c0.5,0,1-0.2,1.3-0.6C8.9,10.2,10.5,8.2,11.4,7.1C11.4,7.1,11.4,7.1,11.4,7.1z M8.9,2.8c0.3-0.4,1-0.5,1.4-0.1 c0,0,1.2,0.9,1.2,0.9c0.2,0.1,0.4,0.3,0.4,0.6c0.1,0.2,0,0.5-0.1,0.7c0,0-0.4,0.5-0.9,1.2L8.1,3.9L8.9,2.8z M5.5,12.9 C5.4,13,5.2,13.1,5,13.1l-2,0l-0.5-1.9c0-0.2,0-0.4,0.1-0.5l4.8-6l2.8,2.2C8.9,8.6,6.8,11.2,5.5,12.9z"/>
</symbol>
</svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -12,6 +12,7 @@
"Table": "Table",
"Role": "Role",
"DeleteObject": "Delete object",
"DeleteObjectConfirm": "Do you want to delete this {count, plural, =1 {object} other {# objects}}?"
"DeleteObjectConfirm": "Do you want to delete this {count, plural, =1 {object} other {# objects}}?",
"Open": "Open"
}
}

View File

@ -12,6 +12,7 @@
"Table": "Таблица",
"Role": "Роль",
"DeleteObject": "Удалить объект",
"DeleteObjectConfirm": "Вы действительно хотите удалить {count, plural, =1 {этот обьект} other {эти # обьекта}}?"
"DeleteObjectConfirm": "Вы действительно хотите удалить {count, plural, =1 {этот обьект} other {эти # обьекта}}?",
"Open": "Открыть"
}
}

View File

@ -23,7 +23,8 @@ loadMetadata(view.icon, {
Move: `${icons}#move`,
MoreH: `${icons}#more-h`,
Archive: `${icons}#archive`,
Statuses: `${icons}#statuses`
Statuses: `${icons}#statuses`,
Open: `${icons}#open`
})
addStringsLoader(viewId, async (lang: string) => await import(`../lang/${lang}.json`))

View File

@ -36,29 +36,10 @@ focusStore.subscribe((it) => {
$focusStore = 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 => {
if (old !== undefined) {
return $focusStore.focus
}
})
}
}
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)
export function select (evt: Event|undefined, offset: 1 | -1 | 0, of?: Doc, direction?: SelectDirection): void {
if ($focusStore.provider?.select !== undefined) {
$focusStore.provider?.select(offset, of, direction)
evt?.preventDefault()
previewDocument.update(old => {
if (old !== undefined) {
return $focusStore.focus
@ -93,10 +74,10 @@ function SelectItemAll (doc: Doc | undefined, evt: Event): void {
evt.preventDefault()
}
const MoveUp = (doc: Doc | undefined, evt: Event): void => selPrev(doc, evt, 'vertical')
const MoveDown = (doc: Doc | undefined, evt: Event): void => selNext(doc, evt, 'vertical')
const MoveLeft = (doc: Doc | undefined, evt: Event): void => selPrev(doc, evt, 'horizontal')
const MoveRight = (doc: Doc | undefined, evt: Event): void => selNext(doc, evt, 'horizontal')
const MoveUp = (doc: Doc | undefined, evt: Event): void => select(evt, -1, doc, 'vertical')
const MoveDown = (doc: Doc | undefined, evt: Event): void => select(evt, 1, doc, 'vertical')
const MoveLeft = (doc: Doc | undefined, evt: Event): void => select(evt, -1, doc, 'horizontal')
const MoveRight = (doc: Doc | undefined, evt: Event): void => select(evt, 1, doc, 'horizontal')
function ShowActions (doc: Doc | Doc[] | undefined, evt: Event): void {
evt.preventDefault()
@ -112,7 +93,7 @@ function ShowPreview (doc: Doc | undefined, evt: Event): void {
evt.preventDefault()
}
function Edit (doc: Doc, evt: Event): void {
function Open (doc: Doc, evt: Event): void {
evt.preventDefault()
showPanel(view.component.EditDoc, doc._id, Hierarchy.mixinOrClass(doc), 'content')
}
@ -132,5 +113,5 @@ export const actionImpl = {
SelectItemAll,
ShowActions,
ShowPreview,
Edit
Open
}

View File

@ -22,7 +22,6 @@
export let value: number | null | undefined
// export let label: IntlString
export let onChange: (value: any) => void
// export let attributeType: TypeDate | undefined
</script>
<DateRangePresenter {value} withTime editable on:change={(res) => { if (res.detail !== undefined) onChange(res.detail) }} />

View File

@ -19,7 +19,6 @@
import { DateRangePresenter } from '@anticrm/ui'
export let value: number | null | undefined
// export let attributeType: TypeDate | undefined
</script>
<DateRangePresenter {value} />

View File

@ -26,14 +26,12 @@
getClient,
KeyedAttribute
} from '@anticrm/presentation'
import { AnyComponent, Button, Component, IconDownOutline, IconUpOutline, Label, PopupAlignment, showPanel } from '@anticrm/ui'
import { AnyComponent, Component, Label, PopupAlignment } 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'
import ActionContext from './ActionContext.svelte'
import UpDownNavigator from './UpDownNavigator.svelte'
export let _id: Ref<Doc>
export let _class: Ref<Class<Doc>>
@ -241,17 +239,6 @@
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'
@ -270,8 +257,7 @@
}}
>
<svelte:fragment slot="navigate-actions">
<Button icon={IconDownOutline} kind={'secondary'} size={'medium'} on:click={() => next(true)}/>
<Button icon={IconUpOutline} kind={'secondary'} size={'medium'} on:click={() => next(false)}/>
<UpDownNavigator element={object}/>
</svelte:fragment>
<div class="w-full" slot="subtitle">
{#if !headerLoading}

View File

@ -38,7 +38,7 @@
actions = result.map(a => ({
label: a.label,
icon: a.icon as Asset,
action: async (evt: Event) => { invokeAction(evt, a.action) }
action: async (props:any, evt: Event) => { invokeAction(evt, a.action) }
}))
loaded = 1
})

View File

@ -14,7 +14,7 @@
-->
<script lang="ts">
import type { Class, Doc, Ref, Type } from '@anticrm/core'
import type { Class, Doc, Ref } from '@anticrm/core'
import { createQuery, getClient } from '@anticrm/presentation'
import { AttributeModel } from '@anticrm/view'
import { getObjectPresenter } from '../utils'
@ -22,7 +22,6 @@
export let objectId: Ref<Doc>
export let _class: Ref<Class<Doc>>
export let value: Doc | undefined
export let attributeType: Type<any>
export let props: Record<string, any> = {}
const client = getClient()
@ -45,5 +44,5 @@
</script>
{#if presenter}
<svelte:component this={presenter.presenter} value={doc} {attributeType} {...props} inline />
<svelte:component this={presenter.presenter} value={doc} {...props} inline />
{/if}

View File

@ -16,9 +16,6 @@
<script lang="ts">
export let value: string
// eslint-disable-next-line
// export let attributeType: Type<any>
</script>
<span class="lines-limit-2">{value}</span>

View File

@ -21,7 +21,7 @@
import { CheckBox, Component, IconDown, IconUp, Label, Loading, showPopup, Spinner } from '@anticrm/ui'
import { BuildModelKey } from '@anticrm/view'
import { createEventDispatcher } from 'svelte'
import { selectionStore } from '../selection'
import { SelectDirection } from '../selection'
import { buildModel, LoadingProps } from '../utils'
import Menu from './Menu.svelte'
@ -132,14 +132,20 @@
dispatch('row-focus', object)
}
export function scrollSelection (pos: number): void {
if (pos !== -1) {
const r = refs[pos]
if (r !== undefined) {
selection = pos
onRow(objects[pos])
r?.scrollIntoView({ behavior: 'auto', block: 'nearest' })
}
export function select (offset: 1 | -1 | 0, of?: Doc, dir?: SelectDirection): void {
let pos = (((of !== undefined) ? objects.findIndex(it => it._id === of._id) : selection) ?? -1)
pos += offset
if (pos < 0) {
pos = 0
}
if (pos >= objects.length) {
pos = objects.length - 1
}
const r = refs[pos]
selection = pos
onRow(objects[pos])
if (r !== undefined) {
r?.scrollIntoView({ behavior: 'auto', block: 'nearest' })
}
}
</script>

View File

@ -15,9 +15,9 @@
<script lang="ts">
import type { Class, Doc, DocumentQuery, FindOptions, Ref } from '@anticrm/core'
import { BuildModelKey } from '@anticrm/view'
import { onMount } from 'svelte'
import { onMount } from 'svelte'
import { ActionContext } from '..'
import { focusStore, ListSelectionProvider, selectionStore } from '../selection'
import { focusStore, ListSelectionProvider, SelectDirection, selectionStore } from '../selection'
import { LoadingProps } from '../utils'
import Table from './Table.svelte'
@ -33,16 +33,10 @@ import { onMount } from 'svelte'
let table: Table
const listProvider = new ListSelectionProvider(
(pos, dir) => {
(offset: 1 | -1 | 0, of?: Doc, dir?: SelectDirection) => {
if (dir === 'vertical') {
// Select next
table.scrollSelection(pos + 1)
}
},
(pos, dir) => {
// Select prev
if (dir === 'vertical') {
table.scrollSelection(pos - 1)
table.select(offset, of, dir)
}
}
)

View File

@ -0,0 +1,23 @@
<script lang="ts">
import { Doc } from '@anticrm/core'
import { Button, IconDownOutline, IconUpOutline, panelstore, showPanel } from '@anticrm/ui'
import { tick } from 'svelte'
import { select } from '../actionImpl'
import { focusStore } from '../selection'
export let element: Doc
async function next (evt: Event, pn: boolean): Promise<void> {
select(evt, pn ? 1 : -1, element, 'vertical')
await tick()
if ($focusStore.focus !== undefined && $panelstore.panel !== undefined) {
showPanel($panelstore.panel.component, $focusStore.focus._id, $focusStore.focus._class, $panelstore.panel?.element ?? 'content', $panelstore.panel.rightSection)
}
}
$: select(undefined, 0, element, 'vertical')
</script>
<Button icon={IconDownOutline} kind={'secondary'} size={'medium'} on:click={(evt) => next(evt, true)}/>
<Button icon={IconUpOutline} kind={'secondary'} size={'medium'} on:click={(evt) => next(evt, false)}/>

View File

@ -9,12 +9,13 @@ import { writable } from 'svelte/store'
export type SelectDirection = 'vertical' | 'horizontal'
export interface SelectionFocusProvider {
// -1 - previous
// 0 - selec of as current
// 1 - next
// * If vertical, next will return item under.
// * If horizontal, next will return item on right.
next?: (direction?: SelectDirection) => void
// * If vertical, next will return item amove.
// * If horizontal, next will return item on left.
prev?: (vertical?: SelectDirection) => void
// of - document offset from we requesting.
select?: (offset: 1 | -1 | 0, of?: Doc, direction?: SelectDirection) => void
// Update documents content
update: (docs: Doc[]) => void
@ -89,8 +90,7 @@ export class ListSelectionProvider implements SelectionFocusProvider {
_docs: Doc[] = []
_current?: FocusSelection
constructor (
private readonly selectNext: (cur: number, direction?: SelectDirection) => void,
private readonly selectPrev: (cur: number, direction?: SelectDirection) => void
private readonly delegate: (offset: 1 | -1 | 0, of?: Doc, direction?: SelectDirection) => void
) {
const unsubscribe = focusStore.subscribe((doc) => {
this._current = doc
@ -101,29 +101,21 @@ export class ListSelectionProvider implements SelectionFocusProvider {
})
}
next (direction?: SelectDirection): void {
this.selectNext(this.current(this._current) ?? 0, direction)
}
prev (direction?: SelectDirection): void {
this.selectPrev(this.current(this._current) ?? this._docs.length - 1, direction)
select (offset: 1 | -1 | 0, of?: Doc, direction?: SelectDirection): void {
this.delegate(offset, of, direction)
}
update (docs: Doc[]): void {
this._docs = docs
if (this._docs.length > 0) {
if (this._current === undefined) {
updateFocus({
focus: this._docs[0],
provider: this
})
if (this._current?.focus === undefined) {
this.delegate(0, undefined, 'vertical')
} else {
// Check if we don't have object, we need to select first one.
if (this._docs.findIndex((it) => it._id === this._current?.focus?._id) === -1) {
updateFocus({ focus: this._docs[0], provider: this })
}
this.delegate(0, this._current?.focus, 'vertical')
}
updateFocus({ focus: this._current?.focus, provider: this })
}
}

View File

@ -105,7 +105,7 @@ async function getAttributePresenter (
_class: attrClass,
label: preserveKey.label ?? attribute.label,
presenter,
props: { attributeType: attribute.type },
props: { },
icon: presenterMixin.icon,
attribute
}

View File

@ -280,7 +280,8 @@ const view = plugin(viewId, {
MoreH: '' as Asset,
Move: '' as Asset,
Archive: '' as Asset,
Statuses: '' as Asset
Statuses: '' as Asset,
Open: '' as Asset
}
})
export default view

View File

@ -49,7 +49,7 @@
</span>
{#each actions as action}
<div class="an-element__tool">
<ActionIcon label={action.label} icon={action.icon} size={'small'} action={action.action} />
<ActionIcon label={action.label} icon={action.icon} size={'small'} action={(evt) => action.action({}, evt)} />
</div>
{/each}
{#if notifications > 0}