Board: 1265: Make Card Actions extensible (#1319)

Signed-off-by: Anna No <anna.no@xored.com>
This commit is contained in:
Anna No 2022-04-08 10:08:30 +07:00 committed by GitHub
parent f44c9a59ca
commit a1e557a9b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 305 additions and 126 deletions

View File

@ -15,17 +15,18 @@
//
// To help typescript locate view plugin properly
import type { Board, Card } from '@anticrm/board'
import type { Board, Card, CardAction } from '@anticrm/board'
import type { Employee } from '@anticrm/contact'
import { Doc, FindOptions, IndexKind, Ref } from '@anticrm/core'
import { Client, Doc, DOMAIN_MODEL, FindOptions, IndexKind, Ref } from '@anticrm/core'
import { Builder, Collection, Index, Model, Prop, TypeMarkup, TypeRef, TypeString, UX } from '@anticrm/model'
import attachment from '@anticrm/model-attachment'
import chunter from '@anticrm/model-chunter'
import contact from '@anticrm/model-contact'
import core from '@anticrm/model-core'
import core, { TDoc } from '@anticrm/model-core'
import task, { TSpaceWithStates, TTask } from '@anticrm/model-task'
import view from '@anticrm/model-view'
import workbench from '@anticrm/model-workbench'
import { Asset, IntlString, Resource } from '@anticrm/platform'
import type {} from '@anticrm/view'
import board from './plugin'
@ -64,8 +65,21 @@ export class TCard extends TTask implements Card {
members!: Ref<Employee>[]
}
@Model(board.class.CardAction, core.class.Doc, DOMAIN_MODEL)
export class TCardAction extends TDoc implements CardAction {
hint?: IntlString
icon!: Asset
isInline?: boolean
isTransparent?: boolean
label!: IntlString
position!: number
type!: string
handler?: Resource<(card: Card, client: Client) => void>
supported?: Resource<(card: Card, client: Client) => boolean>
}
export function createModel (builder: Builder): void {
builder.createModel(TBoard, TCard)
builder.createModel(TBoard, TCard, TCardAction)
builder.mixin(board.class.Board, core.class.Class, workbench.mixin.SpaceView, {
view: {
@ -187,6 +201,165 @@ export function createModel (builder: Builder): void {
},
board.viewlet.Kanban
)
// card actions
builder.createDoc(
board.class.CardAction,
core.space.Model,
{
icon: board.icon.Card,
isInline: true,
label: board.string.Members,
position: 10,
type: board.cardActionType.AddToCard,
handler: board.cardActionHandler.Members
},
board.cardAction.Members
)
builder.createDoc(
board.class.CardAction,
core.space.Model,
{
icon: board.icon.Card,
isInline: true,
label: board.string.Labels,
position: 20,
type: board.cardActionType.AddToCard,
handler: board.cardActionHandler.Labels
},
board.cardAction.Labels
)
builder.createDoc(
board.class.CardAction,
core.space.Model,
{
icon: board.icon.Card,
isInline: false,
label: board.string.Checklist,
position: 30,
type: board.cardActionType.AddToCard,
handler: board.cardActionHandler.Checklist
},
board.cardAction.Checklist
)
builder.createDoc(
board.class.CardAction,
core.space.Model,
{
icon: board.icon.Card,
isInline: true,
label: board.string.Dates,
position: 40,
type: board.cardActionType.AddToCard,
handler: board.cardActionHandler.Dates
},
board.cardAction.Dates
)
builder.createDoc(
board.class.CardAction,
core.space.Model,
{
icon: board.icon.Card,
isInline: false,
label: board.string.Attachments,
position: 50,
type: board.cardActionType.AddToCard,
handler: board.cardActionHandler.Attachments
},
board.cardAction.Attachments
)
builder.createDoc(
board.class.CardAction,
core.space.Model,
{
icon: board.icon.Card,
isInline: false,
label: board.string.CustomFields,
position: 60,
type: board.cardActionType.AddToCard,
handler: board.cardActionHandler.CustomFields
},
board.cardAction.CustomFields
)
builder.createDoc(
board.class.CardAction,
core.space.Model,
{
icon: board.icon.Card,
isInline: false,
isTransparent: true,
label: board.string.AddButton,
position: 70,
type: board.cardActionType.Automation,
handler: board.cardActionHandler.AddButton
},
board.cardAction.AddButton
)
builder.createDoc(
board.class.CardAction,
core.space.Model,
{
icon: board.icon.Card,
isInline: true,
label: board.string.Move,
position: 80,
type: board.cardActionType.Action,
handler: board.cardActionHandler.Move
},
board.cardAction.Move
)
builder.createDoc(
board.class.CardAction,
core.space.Model,
{
icon: board.icon.Card,
isInline: true,
label: board.string.Copy,
position: 90,
type: board.cardActionType.Action,
handler: board.cardActionHandler.Copy
},
board.cardAction.Copy
)
builder.createDoc(
board.class.CardAction,
core.space.Model,
{
icon: board.icon.Card,
isInline: false,
label: board.string.MakeTemplate,
position: 100,
type: board.cardActionType.Action,
handler: board.cardActionHandler.MakeTemplate
},
board.cardAction.MakeTemplate
)
builder.createDoc(
board.class.CardAction,
core.space.Model,
{
icon: board.icon.Card,
isInline: false,
label: board.string.Watch,
position: 110,
type: board.cardActionType.Action,
handler: board.cardActionHandler.Watch
},
board.cardAction.Watch
)
builder.createDoc(
board.class.CardAction,
core.space.Model,
{
icon: board.icon.Card,
isInline: true,
label: board.string.Archive,
position: 120,
type: board.cardActionType.Action,
handler: board.cardActionHandler.Archive
},
board.cardAction.Archive
)
}
export { createDeps } from './creation'

View File

@ -41,8 +41,7 @@ async function createSpace (tx: TxOperations): Promise<void> {
}
export const boardOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {
},
async migrate (client: MigrationClient): Promise<void> {},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
const ops = new TxOperations(client, core.account.System)
await createSpace(ops)

View File

@ -14,10 +14,10 @@
// limitations under the License.
//
import { boardId } from '@anticrm/board'
import { boardId, Card, CardAction } from '@anticrm/board'
import board from '@anticrm/board-resources/src/plugin'
import type { Ref, Space } from '@anticrm/core'
import { mergeIds } from '@anticrm/platform'
import type { Client, Ref, Space } from '@anticrm/core'
import { mergeIds, Resource } from '@anticrm/platform'
import { KanbanTemplate, Sequence } from '@anticrm/task'
import type { AnyComponent } from '@anticrm/ui'
import { ViewletDescriptor } from '@anticrm/view'
@ -32,6 +32,34 @@ export default mergeIds(boardId, board, {
Cards: '' as AnyComponent,
KanbanView: '' as AnyComponent
},
cardAction: {
Members: '' as Ref<CardAction>,
Labels: '' as Ref<CardAction>,
Checklist: '' as Ref<CardAction>,
Dates: '' as Ref<CardAction>,
Attachments: '' as Ref<CardAction>,
CustomFields: '' as Ref<CardAction>,
AddButton: '' as Ref<CardAction>,
Move: '' as Ref<CardAction>,
Copy: '' as Ref<CardAction>,
MakeTemplate: '' as Ref<CardAction>,
Watch: '' as Ref<CardAction>,
Archive: '' as Ref<CardAction>
},
cardActionHandler: {
Members: '' as Resource<(card: Card, client: Client) => void>,
Labels: '' as Resource<(card: Card, client: Client) => void>,
Checklist: '' as Resource<(card: Card, client: Client) => void>,
Dates: '' as Resource<(card: Card, client: Client) => void>,
Attachments: '' as Resource<(card: Card, client: Client) => void>,
CustomFields: '' as Resource<(card: Card, client: Client) => void>,
AddButton: '' as Resource<(card: Card, client: Client) => void>,
Move: '' as Resource<(card: Card, client: Client) => void>,
Copy: '' as Resource<(card: Card, client: Client) => void>,
MakeTemplate: '' as Resource<(card: Card, client: Client) => void>,
Watch: '' as Resource<(card: Card, client: Client) => void>,
Archive: '' as Resource<(card: Card, client: Client) => void>
},
space: {
DefaultBoard: '' as Ref<Space>
},

View File

@ -14,30 +14,81 @@
// limitations under the License.
-->
<script lang="ts">
import type { Card } from '@anticrm/board'
import board from '@anticrm/board'
import type { Card, CardAction } from '@anticrm/board'
import { IntlString, getResource } from '@anticrm/platform'
import { getClient } from '@anticrm/presentation'
import { Button, Label } from '@anticrm/ui'
import { getEditorCardActionGroups } from '../../utils/CardActionUtils'
import plugin from '../../plugin'
import { cardActionSorter, getCardActions } from '../../utils/CardActionUtils'
export let value: Card
const client = getClient()
const actionGroups = getEditorCardActionGroups(value)
const addToCardActions: CardAction[] = []
const automationActions: CardAction[] = []
const actions: CardAction[] = []
let actionGroups: { label: IntlString; actions: CardAction[] }[] = []
getCardActions(client).then(async (result) => {
for (const action of result) {
let supported = true
if (action.supported) {
const supportedHandler = await getResource(action.supported)
supported = supportedHandler(value, client)
}
if (supported) {
if (action.type === board.cardActionType.AddToCard) {
addToCardActions.push(action)
} else if (action.type === board.cardActionType.Automation) {
automationActions.push(action)
} else if (action.type === board.cardActionType.Action) {
actions.push(action)
}
}
}
actionGroups = [
{
label: plugin.string.AddToCard,
actions: addToCardActions.sort(cardActionSorter)
},
{
label: plugin.string.Automation,
actions: automationActions.sort(cardActionSorter)
},
{
label: plugin.string.Actions,
actions: actions.sort(cardActionSorter)
}
]
})
</script>
{#if value}
<div class="flex-col flex-gap-3">
{#each actionGroups as group}
<div class="flex-col flex-gap-1">
<Label label={group.label} />
{#each group.actions as action}
<Button
icon={action.icon}
label={action.label}
kind={action.isTransparent ? 'transparent' : 'no-border'}
justify="left"
on:click={() => action.handler?.(value)}
/>
{/each}
</div>
{#if group.actions.length > 0}
<div class="flex-col flex-gap-1">
<Label label={group.label} />
{#each group.actions as action}
<Button
icon={action.icon}
label={action.label}
kind={action.isTransparent ? 'transparent' : 'no-border'}
justify="left"
on:click={async () => {
if (action.handler) {
const handler = await getResource(action.handler)
handler(value, client)
}
}}
/>
{/each}
</div>
{/if}
{/each}
</div>
{/if}

View File

@ -1,17 +0,0 @@
import { Card } from '@anticrm/board'
import { Asset, IntlString } from '@anticrm/platform'
import { AnySvelteComponent } from '@anticrm/ui'
export interface CardActionGroup {
actions: CardAction[]
hint?: IntlString
label: IntlString
}
export interface CardAction {
hint?: IntlString
icon: Asset | AnySvelteComponent
isTransparent?: boolean
label: IntlString
handler?: (card: Card) => void
}

View File

@ -1,86 +1,9 @@
import { Card } from '@anticrm/board'
import { IconAdd, IconAttachment } from '@anticrm/ui'
import { CardAction, CardActionGroup } from '../models/CardAction'
import { CardAction } from '@anticrm/board'
import { Client } from '@anticrm/core'
import board from '../plugin'
export const MembersAction: CardAction = {
icon: board.icon.Card,
label: board.string.Members
}
export const cardActionSorter = (a1: CardAction, a2: CardAction) => a1.position - a2.position
export const LabelsAction: CardAction = {
icon: board.icon.Card,
label: board.string.Labels
}
export const ChecklistAction: CardAction = {
icon: board.icon.Card,
label: board.string.Checklist
}
export const DatesAction: CardAction = {
icon: board.icon.Card,
label: board.string.Dates
}
export const AttachmentsAction: CardAction = {
icon: IconAttachment,
label: board.string.Attachments
}
export const CustomFieldsAction: CardAction = {
icon: board.icon.Card,
label: board.string.CustomFields
}
export const AddButtonAction: CardAction = {
icon: IconAdd,
isTransparent: true,
label: board.string.AddButton
}
export const MoveAction: CardAction = {
icon: board.icon.Card,
label: board.string.Move
}
export const CopyAction: CardAction = {
icon: board.icon.Card,
label: board.string.Copy
}
export const MakeTemplateAction: CardAction = {
icon: board.icon.Card,
label: board.string.MakeTemplate
}
export const WatchAction: CardAction = {
icon: board.icon.Card,
label: board.string.Watch
}
export const ArchiveAction: CardAction = {
icon: board.icon.Card,
label: board.string.Archive
}
export const getEditorCardActionGroups = (card: Card): CardActionGroup[] => {
if (card === undefined) {
return []
}
return [
{
label: board.string.AddToCard,
actions: [MembersAction, LabelsAction, ChecklistAction, DatesAction, AttachmentsAction, CustomFieldsAction]
},
{
label: board.string.Automation,
actions: [AddButtonAction]
},
{
label: board.string.Actions,
actions: [MoveAction, CopyAction, MakeTemplateAction, WatchAction, ArchiveAction]
}
]
export const getCardActions = (client: Client) => {
return client.findAll(board.class.CardAction, {})
}

View File

@ -15,8 +15,8 @@
//
import { Employee } from '@anticrm/contact'
import type { AttachedDoc, Class, Doc, Markup, Ref } from '@anticrm/core'
import type { Asset, Plugin } from '@anticrm/platform'
import type { AttachedDoc, Class, Client, Doc, Markup, Ref } from '@anticrm/core'
import type { Asset, IntlString, Plugin, Resource } from '@anticrm/platform'
import { plugin } from '@anticrm/platform'
import type { KanbanTemplateSpace, SpaceWithStates, Task } from '@anticrm/task'
@ -65,6 +65,20 @@ export interface Card extends Task {
comments?: number
attachments?: number
}
/**
* @public
*/
export interface CardAction extends Doc {
hint?: IntlString
icon: Asset
isInline?: boolean
isTransparent?: boolean
label: IntlString
position: number
type: string
handler?: Resource<(card: Card, client: Client) => void>
supported?: Resource<(card: Card, client: Client) => boolean>
}
/**
* @public
@ -78,9 +92,17 @@ const boards = plugin(boardId, {
app: {
Board: '' as Ref<Doc>
},
cardActionType: {
Editor: 'Editor',
Cover: 'Cover',
AddToCard: 'AddToCard',
Automation: 'Automation',
Action: 'Action'
},
class: {
Board: '' as Ref<Class<Board>>,
Card: '' as Ref<Class<Card>>
Card: '' as Ref<Class<Card>>,
CardAction: '' as Ref<Class<CardAction>>
},
icon: {
Board: '' as Asset,