kanban take cards implementation from extensions

Signed-off-by: Andrey Platov <andrey@hardcoreeng.com>
This commit is contained in:
Andrey Platov 2021-09-07 10:36:50 +02:00
parent 951fd2ae10
commit a26c10ad8c
No known key found for this signature in database
GPG Key ID: C8787EFEB4B64AF0
10 changed files with 621 additions and 627 deletions

File diff suppressed because it is too large Load Diff

View File

@ -138,6 +138,10 @@ export function createModel (builder: Builder): void {
} as FindOptions<Doc>, // TODO: fix
config: ['$lookup.candidate', '$lookup.state', '$lookup.candidate.city', '$lookup.candidate.channels']
})
builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.KanbanCard, {
card: recruit.component.KanbanCard
})
}
export { default } from './plugin'

View File

@ -32,7 +32,8 @@ export default mergeIds(recruitId, recruit, {
CreateCandidates: '' as AnyComponent,
CreateCandidate: '' as AnyComponent,
CreateApplication: '' as AnyComponent,
EditCandidate: '' as AnyComponent
EditCandidate: '' as AnyComponent,
KanbanCard: '' as AnyComponent
},
space: {
CandidatesPublic: '' as Ref<Space>

View File

@ -18,7 +18,7 @@ import type { Ref, Class, Space } from '@anticrm/core'
import { DOMAIN_MODEL } from '@anticrm/core'
import { Model, Mixin, Builder } from '@anticrm/model'
import type { AnyComponent } from '@anticrm/ui'
import type { ViewletDescriptor, Viewlet, AttributeEditor, AttributePresenter } from '@anticrm/view'
import type { ViewletDescriptor, Viewlet, AttributeEditor, AttributePresenter, KanbanCard } from '@anticrm/view'
import core, { TDoc, TClass } from '@anticrm/model-core'
@ -34,6 +34,11 @@ export class TAttributePresenter extends TClass implements AttributePresenter {
presenter!: AnyComponent
}
@Mixin(view.mixin.KanbanCard, core.class.Class)
export class TKanbanCard extends TClass implements KanbanCard {
card!: AnyComponent
}
@Model(view.class.ViewletDescriptor, core.class.Doc, DOMAIN_MODEL)
export class TViewletDescriptor extends TDoc implements ViewletDescriptor {
component!: AnyComponent
@ -48,7 +53,7 @@ export class TViewlet extends TDoc implements Viewlet {
}
export function createModel (builder: Builder): void {
builder.createModel(TAttributeEditor, TAttributePresenter, TViewletDescriptor, TViewlet)
builder.createModel(TAttributeEditor, TAttributePresenter, TKanbanCard, TViewletDescriptor, TViewlet)
builder.mixin(core.class.TypeString, core.class.Class, view.mixin.AttributeEditor, {
editor: view.component.StringEditor

View File

@ -14,10 +14,10 @@
-->
<script lang="ts">
import { UserInfo } from '@anticrm/presentation'
import { ActionIcon, IconMoreH, IconFile } from '@anticrm/ui'
import IconWithLabel from './IconWithLabel.svelte'
import Tag from './Tag.svelte'
import { UserInfo, Avatar } from '@anticrm/presentation'
import { Icon, Label, IconThread, IconAttachment } from '@anticrm/ui'
import type { WithLookup } from '@anticrm/core'
import type { Applicant } from '@anticrm/recruit'
interface ICard {
_id: number
@ -27,63 +27,139 @@
state: number
}
export let card: ICard
export let object: WithLookup<Applicant>
export let draggable: boolean
</script>
<div class="card-container" {draggable} class:draggable on:dragstart on:dragend>
<div class="header">
<UserInfo value={{firstName: card.firstName, lastName: card.lastName }} subtitle={'Candidate'} size={'small'} />
<ActionIcon icon={IconMoreH} label={'More..'} direction={'left'} />
</div>
<div class="card-bg" />
<div class="content">
<IconWithLabel icon={IconFile} label={'Team Interview'} />
<div class="description">{card.description}</div>
<div class="flex-center">
<div class="avatar">
<Avatar size={'large'} />
</div>
</div>
<div class="flex-col">
<div class="name">{object.$lookup?.candidate?.firstName} {object.$lookup?.candidate?.lastName}</div>
<div class="city">{object.$lookup?.candidate?.city}</div>
<div class="tags">
<Tag icon={IconFile} label={'Application'} />
<div class="tag"><Label label={'Application'} /></div>
<div class="tag"><Label label={'Resume'} /></div>
</div>
</div>
</div>
<div class="footer">
<Avatar size={'small'} />
<div class="flex-row-center">
<div class="flex-row-center caption-color tool">
<span class="icon"><IconAttachment size={'small'} /></span>
4
</div>
<div class="flex-row-center caption-color tool">
<span class="icon"><IconThread size={'small'} /></span>
5
</div>
</div>
</div>
</div>
<style lang="scss">
@import '../../../../packages/theme/styles/mixins.scss';
.card-container {
position: relative;
display: flex;
flex-direction: column;
align-items: stretch;
background-color: var(--theme-button-bg-hovered);
border: 1px solid var(--theme-bg-accent-color);
border-radius: .75rem;
overflow: hidden;
user-select: none;
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14px 16px;
width: 100%;
min-height: 60px;
background-color: var(--theme-button-bg-focused);
border-radius: 11px 11px 0 0;
}
.content {
display: flex;
flex-direction: column;
align-items: stretch;
padding: 16px;
align-items: center;
padding: 1.25rem;
.description {
margin-top: 8px;
.avatar {
position: relative;
margin-right: 1rem;
width: 5rem;
height: 5rem;
border-radius: 50%;
filter: drop-shadow(0px 24px 94px rgba(50, 53, 47, 1));
&::after {
content: '';
@include bg-layer(transparent, .1);
border: 2px solid #fff;
border-radius: 50%;
}
}
.name {
margin: .25rem 0;
font-weight: 500;
font-size: 1rem;
color: var(--theme-caption-color);
}
.city {
font-weight: 500;
font-size: .75rem;
}
.tags {
display: flex;
gap: 8px;
margin-top: 16px;
margin-top: .5rem;
.tag {
position: relative;
display: flex;
align-items: center;
padding: .375rem .5rem;
font-weight: 500;
font-size: .625rem;
text-align: center;
color: var(--theme-caption-color);
&::after {
content: '';
@include bg-layer(#fff, .04);
border-radius: .5rem;
}
}
.tag + .tag { margin-left: .5rem; }
}
}
.footer {
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
padding: .75rem 1.25rem;
width: 100%;
min-height: 3rem;
&::after {
content: '';
@include bg-layer(#fff, .04);
}
.tool .icon {
margin-right: .25rem;
opacity: .4;
}
.tool + .tool {
margin-left: .75rem;
}
}
&.draggable {
cursor: grab;
}
.card-bg {
@include bg-layer(var(--theme-card-bg), .06);
}
}
:global(.card-container + .card-container) { margin-top: .75rem; }
</style>

View File

@ -20,6 +20,7 @@ import CreateApplication from './components/CreateApplication.svelte'
import EditCandidate from './components/EditCandidate.svelte'
import CandidateGeneral from './components/CandidateGeneral.svelte'
import Attachments from './components/Attachments.svelte'
import KanbanCard from './components/KanbanCard.svelte'
export default async () => ({
component: {
@ -29,6 +30,7 @@ export default async () => ({
CreateApplication,
EditCandidate,
CandidateGeneral,
Attachments
Attachments,
KanbanCard
},
})

View File

@ -1,163 +0,0 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { UserInfo, Avatar } from '@anticrm/presentation'
import { Icon, Label, IconThread, IconAttachment } from '@anticrm/ui'
interface ICard {
_id: number
firstName: string
lastName: string
description: string
state: number
}
export let card: ICard
export let draggable: boolean
</script>
<div class="card-container" {draggable} class:draggable on:dragstart on:dragend>
<div class="card-bg" />
<div class="content">
<div class="flex-center">
<div class="avatar">
<Avatar size={'large'} />
</div>
</div>
<div class="flex-col">
<div class="name">{card.firstName} {card.lastName}</div>
<div class="city">{card.description}</div>
<div class="tags">
<div class="tag"><Label label={'Application'} /></div>
<div class="tag"><Label label={'Resume'} /></div>
</div>
</div>
</div>
<div class="footer">
<Avatar size={'small'} />
<div class="flex-row-center">
<div class="flex-row-center caption-color tool">
<span class="icon"><IconAttachment size={'small'} /></span>
4
</div>
<div class="flex-row-center caption-color tool">
<span class="icon"><IconThread size={'small'} /></span>
5
</div>
</div>
</div>
</div>
<style lang="scss">
@import '../../../../packages/theme/styles/mixins.scss';
.card-container {
position: relative;
display: flex;
flex-direction: column;
align-items: stretch;
border-radius: .75rem;
overflow: hidden;
user-select: none;
.content {
display: flex;
align-items: center;
padding: 1.25rem;
.avatar {
position: relative;
margin-right: 1rem;
width: 5rem;
height: 5rem;
border-radius: 50%;
filter: drop-shadow(0px 24px 94px rgba(50, 53, 47, 1));
&::after {
content: '';
@include bg-layer(transparent, .1);
border: 2px solid #fff;
border-radius: 50%;
}
}
.name {
margin: .25rem 0;
font-weight: 500;
font-size: 1rem;
color: var(--theme-caption-color);
}
.city {
font-weight: 500;
font-size: .75rem;
}
.tags {
display: flex;
margin-top: .5rem;
.tag {
position: relative;
display: flex;
align-items: center;
padding: .375rem .5rem;
font-weight: 500;
font-size: .625rem;
text-align: center;
color: var(--theme-caption-color);
&::after {
content: '';
@include bg-layer(#fff, .04);
border-radius: .5rem;
}
}
.tag + .tag { margin-left: .5rem; }
}
}
.footer {
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
padding: .75rem 1.25rem;
width: 100%;
min-height: 3rem;
&::after {
content: '';
@include bg-layer(#fff, .04);
}
.tool .icon {
margin-right: .25rem;
opacity: .4;
}
.tool + .tool {
margin-left: .75rem;
}
}
&.draggable {
cursor: grab;
}
.card-bg {
@include bg-layer(var(--theme-card-bg), .06);
}
}
:global(.card-container + .card-container) { margin-top: .75rem; }
</style>

View File

@ -17,20 +17,20 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import type { Ref, Class, Doc, Space, FindOptions, State } from '@anticrm/core'
import { getResource } from '@anticrm/platform'
import { buildModel } from '../utils'
import { getClient } from '@anticrm/presentation'
import { Label, showPopup, Loading, ScrollBox } from '@anticrm/ui'
import type { AnyComponent } from '@anticrm/ui'
import { Label, showPopup, Loading, ScrollBox, AnyComponent } from '@anticrm/ui'
import type { AnySvelteComponent } from '@anticrm/ui'
import { createQuery } from '@anticrm/presentation'
import KanbanPanel from './KanbanPanel.svelte'
import KanbanPanelEmpty from './KanbanPanelEmpty.svelte'
import KanbanCard from './KanbanCard.svelte'
import KanbanCardEmpty from './KanbanCardEmpty.svelte'
import core from '@anticrm/core'
import { _ID_SEPARATOR } from '@anticrm/platform';
import view from '@anticrm/view'
export let _class: Ref<Class<(Doc & { state: Ref<State> })>>
export let space: Ref<Space>
@ -75,11 +75,17 @@ import { _ID_SEPARATOR } from '@anticrm/platform';
let dragCard: (Doc & { state: Ref<State>}) | undefined
async function cardPresenter(_class: Ref<Class<Doc>>): Promise<AnySvelteComponent> {
const clazz = client.getHierarchy().getClass(_class)
const presenterMixin = client.getHierarchy().as(clazz, view.mixin.KanbanCard)
return await getResource(presenterMixin.card)
}
</script>
{#await buildModel(client, _class, config, options)}
{#await cardPresenter(_class)}
<Loading/>
{:then model}
{:then presenter}
<div class="kanban-container">
<ScrollBox>
<div class="kanban-content">
@ -97,15 +103,14 @@ import { _ID_SEPARATOR } from '@anticrm/platform';
}}
>
<KanbanCardEmpty label={'Create new application'} />
{#each objects.filter((c) => c.state === state._id) as card}
<KanbanCard {card} draggable={true}
{#each objects.filter((c) => c.state === state._id) as object}
<svelte:component this={presenter} {object} draggable={true}
on:dragstart={() => {
dragCard = card
dragCard = object
}}
on:dragend={() => {
dragCard = undefined
}}
/>
}}/>
{/each}
</KanbanPanel>
{/each}

View File

@ -34,6 +34,13 @@ export interface AttributePresenter extends Class<Doc> {
presenter: AnyComponent
}
/**
* @public
*/
export interface KanbanCard extends Class<Doc> {
card: AnyComponent
}
/**
* @public
*/
@ -60,7 +67,8 @@ export const viewId = 'view' as Plugin
export default plugin(viewId, {
mixin: {
AttributeEditor: '' as Ref<Mixin<AttributeEditor>>,
AttributePresenter: '' as Ref<Mixin<AttributePresenter>>
AttributePresenter: '' as Ref<Mixin<AttributePresenter>>,
KanbanCard: '' as Ref<Mixin<KanbanCard>>
},
class: {
ViewletDescriptor: '' as Ref<Class<ViewletDescriptor>>,

File diff suppressed because it is too large Load Diff