mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-12 19:30:52 +00:00
Fix Activity icons (#788)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
decf27ef0d
commit
a7fcf166fa
@ -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
|
||||
|
@ -1,5 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
|
||||
<symbol id="activity" viewBox="0 0 20 20" width='20' height='20' fill="none">
|
||||
<symbol id="activity" viewBox="0 0 20 20" width='16' height='16' fill="none">
|
||||
<path d="M6.03772 12.3181L8.532 9.07628L11.3772 11.3112L13.818 8.16095" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" fill='none'/>
|
||||
<ellipse cx="16.6632" cy="3.50027" rx="1.60183" ry="1.60183" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12.4372 2.6001H6.38078C3.87125 2.6001 2.31519 4.37737 2.31519 6.8869V13.6222C2.31519 16.1318 3.84074 17.9014 6.38078 17.9014H13.5509C16.0604 17.9014 17.6165 16.1318 17.6165 13.6222V7.75647" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
|
Before Width: | Height: | Size: 770 B After Width: | Height: | Size: 770 B |
@ -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<ActivityKey, TxViewlet>
|
||||
|
||||
type TxDisplayViewlet =
|
||||
| (Pick<TxViewlet, 'icon' | 'label' | 'display' | 'editable' | 'hideOnRemove'> & {
|
||||
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<TxDisplayViewlet> {
|
||||
const doc = dtx.doc
|
||||
if (doc === undefined) {
|
||||
return
|
||||
}
|
||||
const docClass: Class<Doc> = 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<TxCUD<Doc>> }> {
|
||||
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<any> {
|
||||
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<Doc> })
|
||||
}
|
||||
return val
|
||||
}
|
||||
const showMenu = async (ev: MouseEvent): Promise<void> => {
|
||||
const actions = await getActions(client, tx.doc as Doc)
|
||||
showPopup(
|
||||
@ -192,7 +114,11 @@
|
||||
{#if viewlet}
|
||||
<Icon icon={viewlet.icon} size="small" />
|
||||
{:else}
|
||||
<Icon icon={activity.icon.Activity} size="small" />
|
||||
{#if viewlet === undefined && model.length > 0}
|
||||
<Icon icon={modelIcon !== undefined ? modelIcon : IconEdit} size="small" />
|
||||
{:else}
|
||||
<Icon icon={activity.icon.Activity} size="small" />
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@ -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}
|
||||
<span>unset <Label label={m.label} /></span>
|
||||
{: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}
|
||||
<span>unset <Label label={m.label} /></span>
|
||||
{:else}
|
||||
@ -247,13 +173,11 @@
|
||||
{/await}
|
||||
{/each}
|
||||
{:else if viewlet && viewlet.display === 'inline' && viewlet.component}
|
||||
<div>
|
||||
{#if typeof viewlet.component === 'string'}
|
||||
<Component is={viewlet.component} {props} on:close={onCancelEdit} />
|
||||
{:else}
|
||||
<svelte:component this={viewlet.component} {...props} on:close={onCancelEdit} />
|
||||
{/if}
|
||||
</div>
|
||||
{#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>
|
||||
<div class="time"><TimeSince value={tx.tx.modifiedOn} /></div>
|
||||
|
135
plugins/activity-resources/src/components/utils.ts
Normal file
135
plugins/activity-resources/src/components/utils.ts
Normal file
@ -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<TxViewlet, 'icon' | 'label' | 'display' | 'editable' | 'hideOnRemove'> & {
|
||||
component?: AnyComponent | AnySvelteComponent
|
||||
})
|
||||
| undefined
|
||||
|
||||
async function createPseudoViewlet (
|
||||
client: Client & TxOperations,
|
||||
dtx: DisplayTx,
|
||||
label: string
|
||||
): Promise<TxDisplayViewlet> {
|
||||
const doc = dtx.doc
|
||||
if (doc === undefined) {
|
||||
return
|
||||
}
|
||||
const docClass: Class<Doc> = 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<ActivityKey, TxViewlet>,
|
||||
dtx: DisplayTx
|
||||
): Promise<{
|
||||
viewlet: TxDisplayViewlet
|
||||
id: Ref<TxCUD<Doc>>
|
||||
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<AttributeModel[]> {
|
||||
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<Class<Doc>>): Set<string> {
|
||||
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<any> {
|
||||
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<Doc> })
|
||||
}
|
||||
return val
|
||||
}
|
@ -19,4 +19,8 @@
|
||||
<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="task-state" viewBox="0 0 16 16">
|
||||
<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>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2.2 KiB |
@ -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`))
|
||||
|
@ -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.
|
||||
|
@ -79,7 +79,8 @@ async function getAttributePresenter (client: Client, _class: Ref<Class<Obj>>, 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<Attribute
|
||||
return errorPresenter
|
||||
}
|
||||
})
|
||||
console.log(model)
|
||||
return (await Promise.all(model)).filter(a => a !== undefined) as AttributeModel[]
|
||||
}
|
||||
|
||||
|
@ -108,6 +108,8 @@ export interface AttributeModel {
|
||||
// Extra properties for component
|
||||
props?: Record<string, any>
|
||||
sortingKey: string
|
||||
// Extra icon if applicable
|
||||
icon?: Asset
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user