From 56380c4958c503dc0e2286244ba754bebf579cc5 Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Mon, 20 Dec 2021 17:18:29 +0700 Subject: [PATCH] Todos Support (#681) Signed-off-by: Andrey Sobolev --- models/task/src/index.ts | 73 ++++++++++++++- models/task/src/plugin.ts | 12 ++- packages/core/src/memdb.ts | 8 +- packages/core/src/query.ts | 16 ++++ .../presentation/src/components/Card.svelte | 23 +++-- .../src/components/TxView.svelte | 10 +- plugins/task-assets/assets/icons.svg | 7 ++ plugins/task-assets/lang/en.json | 12 ++- plugins/task-assets/src/index.ts | 4 +- .../src/components/todos/CreateTodo.svelte | 76 ++++++++++++++++ .../src/components/todos/EditTodo.svelte | 91 +++++++++++++++++++ .../components/todos/TodoItemPresenter.svelte | 33 +++++++ .../todos/TodoStatePresenter.svelte | 47 ++++++++++ .../src/components/todos/Todos.svelte | 86 ++++++++++++++++++ plugins/task-resources/src/index.ts | 28 +++++- plugins/task-resources/src/plugin.ts | 17 +++- plugins/task/src/index.ts | 20 +++- .../view-resources/src/components/Menu.svelte | 11 +-- .../src/components/Table.svelte | 3 +- plugins/view-resources/src/utils.ts | 42 +++++---- plugins/view/src/index.ts | 17 ++-- .../src/components/navigator/SpacesNav.svelte | 2 +- 22 files changed, 564 insertions(+), 74 deletions(-) create mode 100644 plugins/task-resources/src/components/todos/CreateTodo.svelte create mode 100644 plugins/task-resources/src/components/todos/EditTodo.svelte create mode 100644 plugins/task-resources/src/components/todos/TodoItemPresenter.svelte create mode 100644 plugins/task-resources/src/components/todos/TodoStatePresenter.svelte create mode 100644 plugins/task-resources/src/components/todos/Todos.svelte diff --git a/models/task/src/index.ts b/models/task/src/index.ts index e20825c414..789437859f 100644 --- a/models/task/src/index.ts +++ b/models/task/src/index.ts @@ -14,13 +14,13 @@ // // To help typescript locate view plugin properly -import type {} from '@anticrm/view' +import type { ActionTarget } from '@anticrm/view' import attachment from '@anticrm/model-attachment' import type { Employee } from '@anticrm/contact' import contact from '@anticrm/contact' -import { Arr, Class, Doc, Domain, DOMAIN_MODEL, FindOptions, Ref, Space } from '@anticrm/core' -import { Builder, Collection, Mixin, Model, Prop, TypeRef, TypeString, UX } from '@anticrm/model' +import { Arr, Class, Doc, Domain, DOMAIN_MODEL, FindOptions, Ref, Space, Timestamp } from '@anticrm/core' +import { Builder, Collection, Mixin, Model, Prop, TypeBoolean, TypeDate, TypeRef, TypeString, UX } from '@anticrm/model' import chunter from '@anticrm/model-chunter' import core, { TAttachedDoc, TClass, TDoc, TSpace } from '@anticrm/model-core' import view from '@anticrm/model-view' @@ -42,7 +42,8 @@ import type { WonStateTemplate, LostStateTemplate, KanbanTemplate, - Task + Task, + TodoItem } from '@anticrm/task' import { createProjectKanban } from '@anticrm/task' import task from './plugin' @@ -99,6 +100,22 @@ export class TTask extends TAttachedDoc implements Task { assignee!: Ref | null declare rank: string + + @Prop(Collection(task.class.TodoItem), "Todo's" as IntlString) + todoItems!: number +} + +@Model(task.class.TodoItem, core.class.AttachedDoc, DOMAIN_TASK) +@UX('Todo' as IntlString) +export class TTodoItem extends TAttachedDoc implements TodoItem { + @Prop(TypeString(), 'Name' as IntlString, task.icon.Task) + name!: string + + @Prop(TypeBoolean(), 'Complete' as IntlString) + done!: boolean + + @Prop(TypeDate(), 'Due date' as IntlString) + dueTo?: Timestamp } @Model(task.class.SpaceWithStates, core.class.Space) @@ -212,7 +229,8 @@ export function createModel (builder: Builder): void { TTask, TSpaceWithStates, TProject, - TIssue + TIssue, + TTodoItem ) builder.mixin(task.class.Project, core.class.Class, workbench.mixin.SpaceView, { view: { @@ -389,6 +407,51 @@ export function createModel (builder: Builder): void { builder.mixin(task.class.DoneState, core.class.Class, view.mixin.AttributePresenter, { presenter: task.component.DoneStatePresenter }) + + builder.mixin(task.class.TodoItem, core.class.Class, view.mixin.AttributeEditor, { + editor: task.component.Todos + }) + + builder.mixin(task.class.TodoItem, core.class.Class, view.mixin.AttributePresenter, { + presenter: task.component.TodoItemPresenter + }) + + builder.createDoc( + view.class.Action, + core.space.Model, + { + label: 'Mark as done' as IntlString, + icon: task.icon.TodoCheck, + action: task.actionImpl.TodoItemMarkDone + }, + task.action.TodoItemMarkDone + ) + + builder.createDoc( + view.class.Action, + core.space.Model, + { + label: 'Mark as undone' as IntlString, + icon: task.icon.TodoUnCheck, + action: task.actionImpl.TodoItemMarkUnDone + }, + task.action.TodoItemMarkUnDone + ) + + builder.createDoc>(view.class.ActionTarget, core.space.Model, { + target: task.class.TodoItem, + action: task.action.TodoItemMarkDone, + query: { + done: false + } + }) + builder.createDoc(view.class.ActionTarget, core.space.Model, { + target: task.class.TodoItem, + action: task.action.TodoItemMarkUnDone, + query: { + done: true + } + }) } export { taskOperation } from './migration' diff --git a/models/task/src/plugin.ts b/models/task/src/plugin.ts index 985c73a832..024b4dce73 100644 --- a/models/task/src/plugin.ts +++ b/models/task/src/plugin.ts @@ -28,11 +28,15 @@ export default mergeIds(taskId, task, { }, action: { CreateTask: '' as Ref, - EditStatuses: '' as Ref + EditStatuses: '' as Ref, + TodoItemMarkDone: '' as Ref, + TodoItemMarkUnDone: '' as Ref }, actionImpl: { CreateTask: '' as Resource<(object: Doc) => Promise>, - EditStatuses: '' as Resource<(object: Doc) => Promise> + EditStatuses: '' as Resource<(object: Doc) => Promise>, + TodoItemMarkDone: '' as Resource<(object: Doc) => Promise>, + TodoItemMarkUnDone: '' as Resource<(object: Doc) => Promise> }, component: { ProjectView: '' as AnyComponent, @@ -46,7 +50,9 @@ export default mergeIds(taskId, task, { StatePresenter: '' as AnyComponent, DoneStatePresenter: '' as AnyComponent, StateEditor: '' as AnyComponent, - KanbanView: '' as AnyComponent + KanbanView: '' as AnyComponent, + Todos: '' as AnyComponent, + TodoItemPresenter: '' as AnyComponent }, string: { Task: '' as IntlString, diff --git a/packages/core/src/memdb.ts b/packages/core/src/memdb.ts index 56cca715ef..d652df41c0 100644 --- a/packages/core/src/memdb.ts +++ b/packages/core/src/memdb.ts @@ -18,7 +18,7 @@ import clone from 'just-clone' import type { Class, Doc, Ref } from './classes' import core from './component' import { Hierarchy } from './hierarchy' -import { findProperty, resultSort } from './query' +import { matchQuery, resultSort } from './query' import type { DocumentQuery, FindOptions, FindResult, LookupData, Refs, Storage, TxResult, WithLookup } from './storage' import type { Tx, TxCreateDoc, TxMixin, TxPutBag, TxRemoveDoc, TxUpdateDoc } from './tx' import { TxProcessor } from './tx' @@ -108,11 +108,7 @@ export abstract class MemDb extends TxProcessor { result = this.getObjectsByClass(_class) } - for (const key in query) { - if (key === '_id' && ((query._id as any)?.$like === undefined || query._id === undefined)) continue - const value = (query as any)[key] - result = findProperty(result, key, value) - } + result = matchQuery(result, query) if (options?.lookup !== undefined) result = this.lookup(result as T[], options.lookup) diff --git a/packages/core/src/query.ts b/packages/core/src/query.ts index 4af88a3a98..7aad33fc3f 100644 --- a/packages/core/src/query.ts +++ b/packages/core/src/query.ts @@ -1,3 +1,4 @@ +import { DocumentQuery } from '.' import { Doc } from './classes' import { createPredicates, isPredicate } from './predicate' import { SortingQuery } from './storage' @@ -102,3 +103,18 @@ function getValue (key: string, obj: any): any { } return value } +/** + * @public + */ +export function matchQuery (docs: Doc[], query: DocumentQuery): Doc[] { + let result = [...docs] + for (const key in query) { + if (key === '_id' && ((query._id as any)?.$like === undefined || query._id === undefined)) continue + const value = (query as any)[key] + result = findProperty(result, key, value) + if (result.length === 0) { + break + } + } + return result +} diff --git a/packages/presentation/src/components/Card.svelte b/packages/presentation/src/components/Card.svelte index 11f02f54ef..88155d74e4 100644 --- a/packages/presentation/src/components/Card.svelte +++ b/packages/presentation/src/components/Card.svelte @@ -26,14 +26,17 @@ import SpaceSelect from './SpaceSelect.svelte' import presentation from '..' - export let spaceClass: Ref> + export let spaceClass: Ref> | undefined = undefined export let space: Ref - export let spaceLabel: IntlString - export let spacePlaceholder: IntlString + export let spaceLabel: IntlString | undefined = undefined + export let spacePlaceholder: IntlString | undefined = undefined export let label: IntlString export let okAction: () => void export let canSave: boolean = false + export let okLabel: IntlString = presentation.string.Create + export let cancelLabel: IntlString = presentation.string.Cancel + const dispatch = createEventDispatcher() @@ -48,13 +51,15 @@ {/if}
-
-
- -
+ {#if spaceClass && spaceLabel && spacePlaceholder} +
+
+ +
+ {/if} diff --git a/plugins/activity-resources/src/components/TxView.svelte b/plugins/activity-resources/src/components/TxView.svelte index dfbbe4bcef..a8d876829c 100644 --- a/plugins/activity-resources/src/components/TxView.svelte +++ b/plugins/activity-resources/src/components/TxView.svelte @@ -33,7 +33,7 @@ showPopup, TimeSince } from '@anticrm/ui' - import type { Action, AttributeModel } from '@anticrm/view' + import type { AttributeModel } from '@anticrm/view' import { buildModel, getActions, getObjectPresenter } from '@anticrm/view-resources' import { activityKey, ActivityKey, DisplayTx } from '../activity' import ShowMore from './ShowMore.svelte' @@ -53,7 +53,6 @@ let props: any let employee: EmployeeAccount | undefined let model: AttributeModel[] = [] - let actions: Action[] = [] let edit = false @@ -74,7 +73,7 @@ } const docClass: Class = client.getModel().getObject(doc._class) - const presenter = await getObjectPresenter(client, doc._class, 'doc-presenter') + const presenter = await getObjectPresenter(client, doc._class, { key: 'doc-presenter' }) if (presenter !== undefined) { return { display: 'inline', @@ -125,10 +124,6 @@ }) } - $: getActions(client, tx.tx.objectClass).then((result) => { - actions = result - }) - async function getValue (m: AttributeModel, utx: TxUpdateDoc): Promise { const val = (utx.operations as any)[m.key] console.log(m._class, m.key, val, typeof val) @@ -140,6 +135,7 @@ return val } const showMenu = async (ev: MouseEvent): Promise => { + const actions = await getActions(client, tx.doc as Doc) showPopup( Menu, { diff --git a/plugins/task-assets/assets/icons.svg b/plugins/task-assets/assets/icons.svg index 3f4e1a9256..da5680c168 100644 --- a/plugins/task-assets/assets/icons.svg +++ b/plugins/task-assets/assets/icons.svg @@ -12,4 +12,11 @@ + + + + + + + diff --git a/plugins/task-assets/lang/en.json b/plugins/task-assets/lang/en.json index 3ac54db9ad..2ca5313731 100644 --- a/plugins/task-assets/lang/en.json +++ b/plugins/task-assets/lang/en.json @@ -17,6 +17,16 @@ "More": "Options", "TaskUnAssign": "Unassign", "NoTaskForObject": "No tasks defined", - "Delete": "Delete" + "Delete": "Delete", + "NoTodoItems": "No to do's defined", + "TodoName": "Name", + "TodoState": "State", + "DoneState": "done", + "UndoneState": "todo", + "TodoDueDate": "Due to", + "TodoDescription": "To do description *", + "TodoEdit": "Edit To Do", + "TodoSave": "Save", + "TodoCreate": "Create To Do" } } \ No newline at end of file diff --git a/plugins/task-assets/src/index.ts b/plugins/task-assets/src/index.ts index a579fc4198..89ad1e7a6f 100644 --- a/plugins/task-assets/src/index.ts +++ b/plugins/task-assets/src/index.ts @@ -20,7 +20,9 @@ const icons = require('../assets/icons.svg') loadMetadata(task.icon, { Task: `${icons}#task`, Kanban: `${icons}#kanban`, - Status: `${icons}#status` + Status: `${icons}#status`, + TodoCheck: `${icons}#todo-check`, + TodoUnCheck: `${icons}#todo-uncheck` }) addStringsLoader(taskId, async (lang: string) => await import(`../lang/${lang}.json`)) diff --git a/plugins/task-resources/src/components/todos/CreateTodo.svelte b/plugins/task-resources/src/components/todos/CreateTodo.svelte new file mode 100644 index 0000000000..17b41e3bfe --- /dev/null +++ b/plugins/task-resources/src/components/todos/CreateTodo.svelte @@ -0,0 +1,76 @@ + + + + 0} + bind:space={_space} + on:close={() => { + dispatch('close') + }} + okLabel={task.string.TodoSave}> + + + + + diff --git a/plugins/task-resources/src/components/todos/EditTodo.svelte b/plugins/task-resources/src/components/todos/EditTodo.svelte new file mode 100644 index 0000000000..aa9d16d28b --- /dev/null +++ b/plugins/task-resources/src/components/todos/EditTodo.svelte @@ -0,0 +1,91 @@ + + + + 0} + space={item.space} + on:close={() => { + dispatch('close') + }} + okLabel={task.string.TodoSave}> + + + + + diff --git a/plugins/task-resources/src/components/todos/TodoItemPresenter.svelte b/plugins/task-resources/src/components/todos/TodoItemPresenter.svelte new file mode 100644 index 0000000000..e231a1333a --- /dev/null +++ b/plugins/task-resources/src/components/todos/TodoItemPresenter.svelte @@ -0,0 +1,33 @@ + + + +
show(evt.target)}> + {value.name} +
diff --git a/plugins/task-resources/src/components/todos/TodoStatePresenter.svelte b/plugins/task-resources/src/components/todos/TodoStatePresenter.svelte new file mode 100644 index 0000000000..5d755f9392 --- /dev/null +++ b/plugins/task-resources/src/components/todos/TodoStatePresenter.svelte @@ -0,0 +1,47 @@ + + + + +{#if value } +
+
+{/if} + + diff --git a/plugins/task-resources/src/components/todos/Todos.svelte b/plugins/task-resources/src/components/todos/Todos.svelte new file mode 100644 index 0000000000..d069f050e2 --- /dev/null +++ b/plugins/task-resources/src/components/todos/Todos.svelte @@ -0,0 +1,86 @@ + + + + +
+
+
To Do's
+ +
+ {#if todos.length > 0} + + {:else} +
+
+ +
+
+ {/if} + + + diff --git a/plugins/task-resources/src/index.ts b/plugins/task-resources/src/index.ts index 87ae8aa2c8..0c84578e3a 100644 --- a/plugins/task-resources/src/index.ts +++ b/plugins/task-resources/src/index.ts @@ -24,13 +24,17 @@ import TemplatesIcon from './components/TemplatesIcon.svelte' import EditIssue from './components/EditIssue.svelte' import { Doc } from '@anticrm/core' import { showPopup } from '@anticrm/ui' +import { getClient } from '@anticrm/presentation' import KanbanView from './components/kanban/KanbanView.svelte' import StateEditor from './components/state/StateEditor.svelte' import StatePresenter from './components/state/StatePresenter.svelte' import DoneStatePresenter from './components/state/DoneStatePresenter.svelte' import EditStatuses from './components/state/EditStatuses.svelte' -import { SpaceWithStates } from '@anticrm/task' +import { SpaceWithStates, TodoItem } from '@anticrm/task' +import Todos from './components/todos/Todos.svelte' +import TodoItemPresenter from './components/todos/TodoItemPresenter.svelte' +import TodoStatePresenter from './components/todos/TodoStatePresenter.svelte' export { default as KanbanTemplateEditor } from './components/kanban/KanbanTemplateEditor.svelte' export { default as KanbanTemplateSelector } from './components/kanban/KanbanTemplateSelector.svelte' @@ -46,6 +50,19 @@ async function editStatuses (object: SpaceWithStates): Promise { showPopup(EditStatuses, { _id: object._id, spaceClass: object._class }, 'right') } +async function toggleDone (value: boolean, object: TodoItem): Promise { + await getClient().updateCollection( + object._class, + object.space, + object._id, + object.attachedTo, + object.attachedToClass, + object.collection, { + done: value + } + ) +} + export default async (): Promise => ({ component: { CreateTask, @@ -57,10 +74,15 @@ export default async (): Promise => ({ KanbanView, StatePresenter, StateEditor, - DoneStatePresenter + DoneStatePresenter, + Todos, + TodoItemPresenter, + TodoStatePresenter }, actionImpl: { CreateTask: createTask, - EditStatuses: editStatuses + EditStatuses: editStatuses, + TodoItemMarkDone: async (obj: TodoItem) => await toggleDone(true, obj), + TodoItemMarkUnDone: async (obj: TodoItem) => await toggleDone(false, obj) } }) diff --git a/plugins/task-resources/src/plugin.ts b/plugins/task-resources/src/plugin.ts index 66339d3257..6c8122c11a 100644 --- a/plugins/task-resources/src/plugin.ts +++ b/plugins/task-resources/src/plugin.ts @@ -14,8 +14,8 @@ // import { IntlString, mergeIds } from '@anticrm/platform' - import task, { taskId } from '@anticrm/task' +import { AnyComponent } from '@anticrm/ui' export default mergeIds(taskId, task, { string: { @@ -35,9 +35,22 @@ export default mergeIds(taskId, task, { More: '' as IntlString, UploadDropFilesHere: '' as IntlString, NoTaskForObject: '' as IntlString, - Delete: '' as IntlString + Delete: '' as IntlString, + NoTodoItems: '' as IntlString, + TodoName: '' as IntlString, + TodoState: '' as IntlString, + DoneState: '' as IntlString, + UndoneState: '' as IntlString, + TodoDueDate: '' as IntlString, + TodoDescription: '' as IntlString, + TodoEdit: '' as IntlString, + TodoSave: '' as IntlString, + TodoCreate: '' as IntlString }, status: { AssigneeRequired: '' as IntlString + }, + component: { + TodoStatePresenter: '' as AnyComponent } }) diff --git a/plugins/task/src/index.ts b/plugins/task/src/index.ts index c35886e7bd..1a092d8e21 100644 --- a/plugins/task/src/index.ts +++ b/plugins/task/src/index.ts @@ -14,7 +14,7 @@ // import type { Employee } from '@anticrm/contact' -import { AttachedDoc, Class, Client, Data, Doc, DocWithRank, genRanks, Mixin, Ref, Space, TxOperations } from '@anticrm/core' +import { AttachedDoc, Class, Client, Data, Doc, DocWithRank, genRanks, Mixin, Ref, Space, Timestamp, TxOperations } from '@anticrm/core' import type { Asset, Plugin } from '@anticrm/platform' import { plugin } from '@anticrm/platform' import type { AnyComponent } from '@anticrm/ui' @@ -55,6 +55,17 @@ export interface Task extends AttachedDoc, DocWithRank { doneState: Ref | null number: number assignee: Ref | null + + todoItems?: number +} + +/** + * @public + */ +export interface TodoItem extends AttachedDoc { + name: string + done: boolean + dueTo?: Timestamp } /** @@ -168,7 +179,8 @@ const task = plugin(taskId, { WonStateTemplate: '' as Ref>, LostStateTemplate: '' as Ref>, KanbanTemplate: '' as Ref>, - KanbanTemplateSpace: '' as Ref> + KanbanTemplateSpace: '' as Ref>, + TodoItem: '' as Ref> }, viewlet: { Kanban: '' as Ref @@ -176,7 +188,9 @@ const task = plugin(taskId, { icon: { Task: '' as Asset, Kanban: '' as Asset, - Status: '' as Asset + Status: '' as Asset, + TodoCheck: '' as Asset, + TodoUnCheck: '' as Asset }, global: { // Global task root, if not attached to some other object. diff --git a/plugins/view-resources/src/components/Menu.svelte b/plugins/view-resources/src/components/Menu.svelte index a080eef98f..e01aed5ce8 100644 --- a/plugins/view-resources/src/components/Menu.svelte +++ b/plugins/view-resources/src/components/Menu.svelte @@ -19,7 +19,6 @@ import { getResource } from '@anticrm/platform' import { getClient } from '@anticrm/presentation' import { Menu } from '@anticrm/ui' - import { createEventDispatcher } from 'svelte' import { getActions } from '../utils' export let object: Doc @@ -31,20 +30,18 @@ }[] = [] const client = getClient() - const dispatch = createEventDispatcher() - - async function invokeAction(action: Resource<(object: Doc) => Promise>) { - dispatch('close') + + async function invokeAction (action: Resource<(object: Doc) => Promise>) { const impl = await getResource(action) await impl(object) } - getActions(client, object._class).then(result => { + getActions(client, object).then(result => { actions = result.map(a => ({ label: a.label, icon: a.icon, action: () => { invokeAction(a.action) } - }) ) + })) }) diff --git a/plugins/view-resources/src/components/Table.svelte b/plugins/view-resources/src/components/Table.svelte index 12eba76c84..d2fbf4fd84 100644 --- a/plugins/view-resources/src/components/Table.svelte +++ b/plugins/view-resources/src/components/Table.svelte @@ -19,6 +19,7 @@ import { SortingOrder } from '@anticrm/core' import { createQuery, getClient } from '@anticrm/presentation' import { IconDown, IconUp, Label, Loading, showPopup } from '@anticrm/ui' + import { BuildModelKey } from '@anticrm/view' import { buildModel } from '../utils' import MoreV from './icons/MoreV.svelte' import Menu from './Menu.svelte' @@ -26,7 +27,7 @@ export let _class: Ref> export let query: DocumentQuery export let options: FindOptions | undefined - export let config: string[] + export let config: (BuildModelKey|string)[] let sortKey = 'modifiedOn' let sortOrder = SortingOrder.Descending diff --git a/plugins/view-resources/src/utils.ts b/plugins/view-resources/src/utils.ts index d8001b74e4..22eb2359fe 100644 --- a/plugins/view-resources/src/utils.ts +++ b/plugins/view-resources/src/utils.ts @@ -14,7 +14,7 @@ // limitations under the License. // -import core, { AttachedDoc, Class, Client, Collection, Doc, FindOptions, FindResult, Obj, Ref, TxOperations } from '@anticrm/core' +import core, { AttachedDoc, Class, Client, Collection, Doc, FindOptions, FindResult, Obj, Ref, TxOperations, matchQuery } from '@anticrm/core' import type { IntlString } from '@anticrm/platform' import { getResource } from '@anticrm/platform' import { getAttributePresenterClass } from '@anticrm/presentation' @@ -41,9 +41,9 @@ export async function getObjectPresenter (client: Client, _class: Ref (key.length > 0 ? key + '.' + clazz.sortingKey : clazz.sortingKey) : key return { - key, + key: preserveKey.key, _class, - label: clazz.label, + label: preserveKey.label ?? clazz.label, presenter, sortingKey } @@ -68,16 +68,16 @@ async function getAttributePresenter (client: Client, _class: Ref>, k const sortingKey = attribute.type._class === core.class.ArrOf ? resultKey + '.length' : resultKey const presenter = await getResource(presenterMixin.presenter) return { - key: resultKey, + key: preserveKey.key, sortingKey, _class: attrClass, - label: attribute.label, + label: preserveKey.label ?? attribute.label, presenter } } async function getPresenter (client: Client, _class: Ref>, key: BuildModelKey, preserveKey: BuildModelKey, options?: FindOptions): Promise { - if (typeof key === 'object') { + if (key.presenter !== undefined) { const { presenter, label, sortingKey } = key return { key: '', @@ -87,41 +87,41 @@ async function getPresenter (client: Client, _class: Ref>, key: Build presenter: await getResource(presenter) } } - if (key.length === 0) { + if (key.key.length === 0) { return await getObjectPresenter(client, _class, preserveKey) } else { - const split = key.split('.') + const split = key.key.split('.') if (split[0] === '$lookup') { const lookupClass = (options?.lookup as any)[split[1]] as Ref> if (lookupClass === undefined) { throw new Error('lookup class does not provided for ' + split[1]) } - const lookupKey = split[2] ?? '' + const lookupKey = { ...key, key: split[2] ?? '' } const model = await getPresenter(client, lookupClass, lookupKey, preserveKey) - if (lookupKey === '') { + if (lookupKey.key === '') { const attribute = client.getHierarchy().getAttribute(_class, split[1]) model.label = attribute.label } else { - const attribute = client.getHierarchy().getAttribute(lookupClass, lookupKey) + const attribute = client.getHierarchy().getAttribute(lookupClass, lookupKey.key) model.label = attribute.label } return model } - return await getAttributePresenter(client, _class, key, preserveKey) + return await getAttributePresenter(client, _class, key.key, preserveKey) } } export async function buildModel (options: BuildModelOptions): Promise { console.log('building table model for', options) // eslint-disable-next-line array-callback-return - const model = options.keys.map(async key => { + const model = options.keys.map(key => typeof key === 'string' ? { key: key } : key).map(async key => { try { return await getPresenter(options.client, options._class, key, key, options.options) } catch (err: any) { if ((options.ignoreMissing ?? false)) { return undefined } - const stringKey = (typeof key === 'string') ? key : key.label + const stringKey = key.label ?? key.key console.error('Failed to find presenter for', key, err) const errorPresenter: AttributeModel = { key: '', @@ -138,11 +138,17 @@ export async function buildModel (options: BuildModelOptions): Promise a !== undefined) as AttributeModel[] } -function filterActions (client: Client, _class: Ref>, targets: ActionTarget[], derived: Ref> = core.class.Doc): Array> { +function filterActions (client: Client, doc: Doc, targets: ActionTarget[], derived: Ref> = core.class.Doc): Array> { const result: Array> = [] const hierarchy = client.getHierarchy() for (const target of targets) { - if (hierarchy.isDerived(_class, target.target) && client.getHierarchy().isDerived(target.target, derived)) { + if (target.query !== undefined) { + const r = matchQuery([doc], target.query) + if (r.length === 0) { + continue + } + } + if (hierarchy.isDerived(doc._class, target.target) && client.getHierarchy().isDerived(target.target, derived)) { result.push(target.action) } } @@ -157,9 +163,9 @@ function filterActions (client: Client, _class: Ref>, targets: Action * So if we have contribution for Doc, Space and we ask for SpaceWithStates and derivedFrom=Space, * we won't recieve Doc contribution but recieve Space ones. */ -export async function getActions (client: Client, _class: Ref>, derived: Ref> = core.class.Doc): Promise> { +export async function getActions (client: Client, doc: Doc, derived: Ref> = core.class.Doc): Promise> { const targets = await client.findAll(view.class.ActionTarget, {}) - return await client.findAll(view.class.Action, { _id: { $in: filterActions(client, _class, targets, derived) } }) + return await client.findAll(view.class.Action, { _id: { $in: filterActions(client, doc, targets, derived) } }) } export async function deleteObject (client: Client & TxOperations, object: Doc): Promise { diff --git a/plugins/view/src/index.ts b/plugins/view/src/index.ts index 6157892804..321309a8ff 100644 --- a/plugins/view/src/index.ts +++ b/plugins/view/src/index.ts @@ -14,7 +14,7 @@ // limitations under the License. // -import type { Class, Client, Doc, FindOptions, Mixin, Obj, Ref, Space, UXObject } from '@anticrm/core' +import type { Class, Client, Doc, DocumentQuery, FindOptions, Mixin, Obj, Ref, Space, UXObject } from '@anticrm/core' import type { Asset, IntlString, Plugin, Resource, Status } from '@anticrm/platform' import { plugin } from '@anticrm/platform' import type { AnyComponent, AnySvelteComponent } from '@anticrm/ui' @@ -75,9 +75,11 @@ export interface Action extends Doc, UXObject { /** * @public */ -export interface ActionTarget extends Doc { - target: Ref> +export interface ActionTarget extends Doc { + target: Ref> action: Ref + + query?: DocumentQuery } /** @@ -88,9 +90,10 @@ export const viewId = 'view' as Plugin /** * @public */ -export type BuildModelKey = string | { - presenter: AnyComponent - label: string +export interface BuildModelKey { + key: string + presenter?: AnyComponent + label?: IntlString sortingKey?: string } @@ -113,7 +116,7 @@ export interface AttributeModel { export interface BuildModelOptions { client: Client _class: Ref> - keys: BuildModelKey[] + keys: (BuildModelKey | string)[] options?: FindOptions ignoreMissing?: boolean } diff --git a/plugins/workbench-resources/src/components/navigator/SpacesNav.svelte b/plugins/workbench-resources/src/components/navigator/SpacesNav.svelte index 6929e3e3dd..27e246ce75 100644 --- a/plugins/workbench-resources/src/components/navigator/SpacesNav.svelte +++ b/plugins/workbench-resources/src/components/navigator/SpacesNav.svelte @@ -67,7 +67,7 @@ async function getActions (space: Space): Promise { const result = [editSpace] - const extraActions = await getContributedActions(client, space._class, core.class.Space) + const extraActions = await getContributedActions(client, space, core.class.Space) for (const act of extraActions) { result.push({ icon: act.icon ?? IconEdit,