From a7fcf166fad26bf954c9a4fccaf4f910576fc9e4 Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Tue, 11 Jan 2022 16:09:52 +0700 Subject: [PATCH] Fix Activity icons (#788) Signed-off-by: Andrey Sobolev --- models/task/src/index.ts | 4 +- plugins/activity-assets/assets/icons.svg | 2 +- .../src/components/TxView.svelte | 124 ++++------------ .../src/components/utils.ts | 135 ++++++++++++++++++ plugins/task-assets/assets/icons.svg | 4 + plugins/task-assets/src/index.ts | 3 +- plugins/task/src/index.ts | 3 +- plugins/view-resources/src/utils.ts | 4 +- plugins/view/src/index.ts | 2 + 9 files changed, 174 insertions(+), 107 deletions(-) create mode 100644 plugins/activity-resources/src/components/utils.ts diff --git a/models/task/src/index.ts b/models/task/src/index.ts index a2a8515b3f..fa25f1a24f 100644 --- a/models/task/src/index.ts +++ b/models/task/src/index.ts @@ -54,7 +54,7 @@ export const DOMAIN_TASK = 'task' as Domain export const DOMAIN_STATE = 'state' as Domain export const DOMAIN_KANBAN = 'kanban' as Domain @Model(task.class.State, core.class.Doc, DOMAIN_STATE, [task.interface.DocWithRank]) -@UX('State' as IntlString, undefined, undefined, 'rank') +@UX('State' as IntlString, task.icon.TaskState, undefined, 'rank') export class TState extends TDoc implements State { @Prop(TypeString(), 'Title' as IntlString) title!: string @@ -65,7 +65,7 @@ export class TState extends TDoc implements State { } @Model(task.class.DoneState, core.class.Doc, DOMAIN_STATE, [task.interface.DocWithRank]) -@UX('Done' as IntlString, undefined, undefined, 'title') +@UX('Done' as IntlString, task.icon.TaskState, undefined, 'title') export class TDoneState extends TDoc implements DoneState { @Prop(TypeString(), 'Title' as IntlString) title!: string diff --git a/plugins/activity-assets/assets/icons.svg b/plugins/activity-assets/assets/icons.svg index 22075d5191..647f5be9a2 100644 --- a/plugins/activity-assets/assets/icons.svg +++ b/plugins/activity-assets/assets/icons.svg @@ -1,5 +1,5 @@ - + diff --git a/plugins/activity-resources/src/components/TxView.svelte b/plugins/activity-resources/src/components/TxView.svelte index f1b1220590..cca472e3a9 100644 --- a/plugins/activity-resources/src/components/TxView.svelte +++ b/plugins/activity-resources/src/components/TxView.svelte @@ -18,34 +18,26 @@ import type { TxViewlet } from '@anticrm/activity' import activity from '@anticrm/activity' import contact, { EmployeeAccount, formatName } from '@anticrm/contact' - import core, { Class, Doc, Ref, TxCUD, TxUpdateDoc } from '@anticrm/core' - import { getResource, IntlString } from '@anticrm/platform' + import { Doc, Ref } from '@anticrm/core' + import { Asset, getResource } from '@anticrm/platform' import { getClient } from '@anticrm/presentation' import { - AnyComponent, - AnySvelteComponent, Component, Icon, IconEdit, IconMoreH, Label, - Menu, - showPopup, - TimeSince, - ShowMore + Menu, ShowMore, showPopup, + TimeSince } from '@anticrm/ui' import type { AttributeModel } from '@anticrm/view' - import { buildModel, getActions, getObjectPresenter } from '@anticrm/view-resources' - import { activityKey, ActivityKey, DisplayTx } from '../activity' + import { getActions } from '@anticrm/view-resources' + import { ActivityKey, DisplayTx } from '../activity' + import { getValue, TxDisplayViewlet, updateViewlet } from './utils' export let tx: DisplayTx export let viewlets: Map - type TxDisplayViewlet = - | (Pick & { - component?: AnyComponent | AnySvelteComponent - }) - | undefined let ptx: DisplayTx | undefined @@ -53,6 +45,7 @@ let props: any let employee: EmployeeAccount | undefined let model: AttributeModel[] = [] + let modelIcon: Asset | undefined = undefined let edit = false @@ -66,43 +59,12 @@ const client = getClient() - async function createPseudoViewlet (dtx: DisplayTx, label: string): Promise { - const doc = dtx.doc - if (doc === undefined) { - return - } - const docClass: Class = client.getModel().getObject(doc._class) - - const presenter = await getObjectPresenter(client, doc._class, { key: 'doc-presenter' }) - if (presenter !== undefined) { - return { - display: 'inline', - icon: docClass.icon ?? activity.icon.Activity, - label: (`${label} ` + docClass.label) as IntlString, - component: presenter.presenter - } - } - } - - async function updateViewlet (dtx: DisplayTx): Promise<{ viewlet: TxDisplayViewlet; id: Ref> }> { - const key = activityKey(dtx.tx.objectClass, dtx.tx._class) - let viewlet: TxDisplayViewlet = viewlets.get(key) - - props = { tx: dtx.tx, value: dtx.doc, edit } - - if (viewlet === undefined && dtx.tx._class === core.class.TxCreateDoc) { - // Check if we have a class presenter we could have a pseudo viewlet based on class presenter. - viewlet = await createPseudoViewlet(dtx, 'created') - } - if (viewlet === undefined && dtx.tx._class === core.class.TxRemoveDoc) { - viewlet = await createPseudoViewlet(dtx, 'deleted') - } - return { viewlet, id: dtx.tx._id } - } - - $: updateViewlet(tx).then((result) => { + $: updateViewlet(client, viewlets, tx).then((result) => { if (result.id === tx.tx._id) { viewlet = result.viewlet + model = result.model + modelIcon = result.modelIcon + props = { ...result.props, edit } } }) @@ -112,47 +74,7 @@ employee = account }) - $: if (tx.updateTx !== undefined) { - const _class = tx.updateTx.objectClass - const ops = { - client, - _class, - keys: Object.keys(tx.updateTx.operations).filter((id) => !id.startsWith('$')), - ignoreMissing: true - } - const hiddenAttrs = new Set([...client.getHierarchy().getAllAttributes(_class).entries()] - .filter(([, attr]) => attr.hidden === true) - .map(([k]) => k)) - buildModel(ops).then((m) => { - model = m.filter((x) => !hiddenAttrs.has(x.key)) - }) - } else if (tx.mixinTx !== undefined) { - const _class = tx.mixinTx.mixin - const ops = { - client, - _class, - keys: Object.keys(tx.mixinTx.attributes).filter((id) => !id.startsWith('$')), - ignoreMissing: true - } - const hiddenAttrs = new Set([...client.getHierarchy().getAllAttributes(_class).entries()] - .filter(([, attr]) => attr.hidden === true) - .map(([k]) => k)) - - buildModel(ops).then((m) => { - model = m.filter((x) => !hiddenAttrs.has(x.key)) - }) - } - - async function getValue (m: AttributeModel, utx: any): Promise { - const val = (utx as any)[m.key] - - if (client.getHierarchy().isDerived(m._class, core.class.Doc) && typeof val === 'string') { - // We have an reference, we need to find a real object to pass for presenter - return await client.findOne(m._class, { _id: val as Ref }) - } - return val - } const showMenu = async (ev: MouseEvent): Promise => { const actions = await getActions(client, tx.doc as Doc) showPopup( @@ -192,7 +114,11 @@ {#if viewlet} {:else} - + {#if viewlet === undefined && model.length > 0} + + {:else} + + {/if} {/if} @@ -226,7 +152,7 @@ {/if} {#if viewlet === undefined && model.length > 0 && tx.updateTx} {#each model as m} - {#await getValue(m, tx.updateTx.operations) then value} + {#await getValue(client, m, tx.updateTx.operations) then value} {#if value === null} unset {:else} @@ -237,7 +163,7 @@ {/each} {:else if viewlet === undefined && model.length > 0 && tx.mixinTx} {#each model as m} - {#await getValue(m, tx.mixinTx.attributes) then value} + {#await getValue(client, m, tx.mixinTx.attributes) then value} {#if value === null} unset {:else} @@ -247,13 +173,11 @@ {/await} {/each} {:else if viewlet && viewlet.display === 'inline' && viewlet.component} -
- {#if typeof viewlet.component === 'string'} - - {:else} - - {/if} -
+ {#if typeof viewlet.component === 'string'} + + {:else} + + {/if} {/if}
diff --git a/plugins/activity-resources/src/components/utils.ts b/plugins/activity-resources/src/components/utils.ts new file mode 100644 index 0000000000..250d2154f3 --- /dev/null +++ b/plugins/activity-resources/src/components/utils.ts @@ -0,0 +1,135 @@ +import type { TxViewlet } from '@anticrm/activity' +import activity from '@anticrm/activity' +import core, { Class, Client, Doc, Ref, TxCUD, TxOperations } from '@anticrm/core' +import { Asset, IntlString } from '@anticrm/platform' +import { AnyComponent, AnySvelteComponent } from '@anticrm/ui' +import { AttributeModel } from '@anticrm/view' +import { buildModel, getObjectPresenter } from '@anticrm/view-resources' +import { ActivityKey, activityKey, DisplayTx } from '../activity' + +export type TxDisplayViewlet = + | (Pick & { + component?: AnyComponent | AnySvelteComponent + }) + | undefined + +async function createPseudoViewlet ( + client: Client & TxOperations, + dtx: DisplayTx, + label: string +): Promise { + const doc = dtx.doc + if (doc === undefined) { + return + } + const docClass: Class = client.getModel().getObject(doc._class) + + const presenter = await getObjectPresenter(client, doc._class, { key: 'doc-presenter' }) + if (presenter !== undefined) { + return { + display: 'inline', + icon: docClass.icon ?? activity.icon.Activity, + label: (`${label} ` + docClass.label) as IntlString, + component: presenter.presenter + } + } +} + +export async function updateViewlet ( + client: Client & TxOperations, + viewlets: Map, + dtx: DisplayTx +): Promise<{ + viewlet: TxDisplayViewlet + id: Ref> + model: AttributeModel[] + props: any + modelIcon: Asset | undefined + }> { + const key = activityKey(dtx.tx.objectClass, dtx.tx._class) + let viewlet: TxDisplayViewlet = viewlets.get(key) + + const props = { tx: dtx.tx, value: dtx.doc, dtx } + let model: AttributeModel[] = [] + let modelIcon: Asset | undefined + + if (viewlet === undefined) { + ;({ viewlet, model } = await checkInlineViewlets(dtx, viewlet, client, model)) + if (model !== undefined) { + // Check for State attribute + for (const a of model) { + if (a.icon !== undefined) { + modelIcon = a.icon + break + } + } + } + } + return { viewlet, id: dtx.tx._id, model, props, modelIcon } +} + +async function checkInlineViewlets ( + dtx: DisplayTx, + viewlet: TxDisplayViewlet, + client: Client & TxOperations, + model: AttributeModel[] +): Promise<{ viewlet: TxDisplayViewlet, model: AttributeModel[] }> { + if (dtx.tx._class === core.class.TxCreateDoc) { + // Check if we have a class presenter we could have a pseudo viewlet based on class presenter. + viewlet = await createPseudoViewlet(client, dtx, 'created') + } + if (dtx.tx._class === core.class.TxRemoveDoc) { + viewlet = await createPseudoViewlet(client, dtx, 'deleted') + } + if (dtx.tx._class === core.class.TxUpdateDoc) { + model = await createUpdateModel(dtx, client, model) + } + return { viewlet, model } +} + +async function createUpdateModel ( + dtx: DisplayTx, + client: Client & TxOperations, + model: AttributeModel[] +): Promise { + if (dtx.updateTx !== undefined) { + const _class = dtx.updateTx.objectClass + const ops = { + client, + _class, + keys: Object.keys(dtx.updateTx.operations).filter((id) => !id.startsWith('$')), + ignoreMissing: true + } + const hiddenAttrs = getHiddenAttrs(client, _class) + model = (await buildModel(ops)).filter((x) => !hiddenAttrs.has(x.key)) + } else if (dtx.mixinTx !== undefined) { + const _class = dtx.mixinTx.mixin + const ops = { + client, + _class, + keys: Object.keys(dtx.mixinTx.attributes).filter((id) => !id.startsWith('$')), + ignoreMissing: true + } + const hiddenAttrs = getHiddenAttrs(client, _class) + model = (await buildModel(ops)).filter((x) => !hiddenAttrs.has(x.key)) + } + return model +} + +function getHiddenAttrs (client: Client & TxOperations, _class: Ref>): Set { + return new Set( + [...client.getHierarchy().getAllAttributes(_class).entries()] + .filter(([, attr]) => attr.hidden === true) + .map(([k]) => k) + ) +} + +export async function getValue (client: Client & TxOperations, m: AttributeModel, utx: any): Promise { + const val = utx[m.key] + + if (client.getHierarchy().isDerived(m._class, core.class.Doc) && typeof val === 'string') { + // We have an reference, we need to find a real object to pass for presenter + return await client.findOne(m._class, { _id: val as Ref }) + } + return val +} diff --git a/plugins/task-assets/assets/icons.svg b/plugins/task-assets/assets/icons.svg index 48d2c7308f..bb89076f86 100644 --- a/plugins/task-assets/assets/icons.svg +++ b/plugins/task-assets/assets/icons.svg @@ -19,4 +19,8 @@ + + + + diff --git a/plugins/task-assets/src/index.ts b/plugins/task-assets/src/index.ts index 36e9231e03..b18b8aea69 100644 --- a/plugins/task-assets/src/index.ts +++ b/plugins/task-assets/src/index.ts @@ -22,7 +22,8 @@ loadMetadata(task.icon, { Kanban: `${icons}#kanban`, TodoCheck: `${icons}#todo-check`, TodoUnCheck: `${icons}#todo-uncheck`, - ManageStatuses: `${icons}#manage-statuses` + ManageStatuses: `${icons}#manage-statuses`, + TaskState: `${icons}#task-state` }) addStringsLoader(taskId, async (lang: string) => await import(`../lang/${lang}.json`)) diff --git a/plugins/task/src/index.ts b/plugins/task/src/index.ts index b82e99e808..4fc9991b3b 100644 --- a/plugins/task/src/index.ts +++ b/plugins/task/src/index.ts @@ -212,7 +212,8 @@ const task = plugin(taskId, { Kanban: '' as Asset, TodoCheck: '' as Asset, TodoUnCheck: '' as Asset, - ManageStatuses: '' as Asset + ManageStatuses: '' as Asset, + TaskState: '' as Asset }, global: { // Global task root, if not attached to some other object. diff --git a/plugins/view-resources/src/utils.ts b/plugins/view-resources/src/utils.ts index 1b4689694e..918a921506 100644 --- a/plugins/view-resources/src/utils.ts +++ b/plugins/view-resources/src/utils.ts @@ -79,7 +79,8 @@ async function getAttributePresenter (client: Client, _class: Ref>, k sortingKey, _class: attrClass, label: preserveKey.label ?? attribute.label, - presenter + presenter, + icon: presenterMixin.icon } } @@ -141,7 +142,6 @@ export async function buildModel (options: BuildModelOptions): Promise a !== undefined) as AttributeModel[] } diff --git a/plugins/view/src/index.ts b/plugins/view/src/index.ts index 79fe0458e3..2f7e5e1f06 100644 --- a/plugins/view/src/index.ts +++ b/plugins/view/src/index.ts @@ -108,6 +108,8 @@ export interface AttributeModel { // Extra properties for component props?: Record sortingKey: string + // Extra icon if applicable + icon?: Asset } /**