Merge pull request #501 from hcengineering/task-states

Add Task Kanban and States
This commit is contained in:
Andrey Sobolev 2021-12-03 02:38:08 +07:00 committed by GitHub
commit 15ec138a80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 397 additions and 146 deletions

View File

@ -15,25 +15,26 @@
import type { Employee } from '@anticrm/contact'
import contact from '@anticrm/contact'
import type { Doc, Domain, FindOptions, Ref } from '@anticrm/core'
import type { Doc, DocWithState, Domain, FindOptions, Ref } from '@anticrm/core'
import { Builder, Model, Prop, TypeString, UX } from '@anticrm/model'
import chunter from '@anticrm/model-chunter'
import core, { TDoc, TSpace } from '@anticrm/model-core'
import core, { TDoc, TSpaceWithStates } from '@anticrm/model-core'
import view from '@anticrm/model-view'
import workbench from '@anticrm/model-workbench'
import type { IntlString } from '@anticrm/platform'
import type { Project, Task } from '@anticrm/task'
import { createProjectKanban } from '@anticrm/task-resources'
import task from './plugin'
@Model(task.class.Project, core.class.Space)
@Model(task.class.Project, core.class.SpaceWithStates)
@UX('Project' as IntlString, task.icon.Task)
export class TProject extends TSpace implements Project {}
export class TProject extends TSpaceWithStates implements Project {}
@Model(task.class.Task, core.class.Doc, 'task' as Domain)
@Model(task.class.Task, core.class.Doc, 'task' as Domain, [core.interface.DocWithState])
@UX('Task' as IntlString, task.icon.Task, 'TASK' as IntlString)
export class TTask extends TDoc implements Task {
@Prop(TypeString(), 'No.' as IntlString)
number!: number
declare number: DocWithState['number']
declare state: DocWithState['state']
@Prop(TypeString(), 'Name' as IntlString)
name!: string
@ -47,6 +48,9 @@ export class TTask extends TDoc implements Task {
@Prop(TypeString(), 'Comments' as IntlString)
comments!: number
@Prop(TypeString(), 'Attachments' as IntlString)
attachments!: number
@Prop(TypeString(), 'Labels' as IntlString)
labels!: string
}
@ -104,6 +108,29 @@ export function createModel (builder: Builder): void {
editor: task.component.EditTask
})
builder.createDoc(view.class.Sequence, view.space.Sequence, {
attachedTo: task.class.Task,
sequence: 0
})
builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: task.class.Task,
descriptor: view.viewlet.Kanban,
open: task.component.EditTask,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
options: {
lookup: {
assignee: contact.class.EmployeeAccount,
state: core.class.State
}
} as FindOptions<Doc>, // TODO: fix
config: ['$lookup.attachedTo', '$lookup.state']
})
builder.mixin(task.class.Task, core.class.Class, view.mixin.KanbanCard, {
card: task.component.KanbanCard
})
builder.createDoc(task.class.Project, core.space.Model, {
name: 'public',
description: 'Public tasks',
@ -111,8 +138,8 @@ export function createModel (builder: Builder): void {
members: []
}, task.space.TasksPublic)
builder.createDoc(view.class.Sequence, view.space.Sequence, {
attachedTo: task.class.Task,
sequence: 0
})
createProjectKanban(task.space.TasksPublic, async (_class, space, data, id) => {
builder.createDoc(_class, space, data, id)
return await Promise.resolve()
}).catch((err) => console.error(err))
}

View File

@ -30,7 +30,8 @@ export default mergeIds(taskId, task, {
CreateProject: '' as AnyComponent,
CreateTask: '' as AnyComponent,
EditTask: '' as AnyComponent,
TaskPresenter: '' as AnyComponent
TaskPresenter: '' as AnyComponent,
KanbanCard: '' as AnyComponent
},
string: {
Task: '' as IntlString,

View File

@ -15,6 +15,7 @@
"TaskDescription": "Description",
"UploadDropFilesHere": "Upload or drop files here",
"NoAttachmentsForTask": "There are no attachments for this task.",
"AssigneeRequired": "Assignee is required"
"AssigneeRequired": "Assignee is required",
"More": "Options"
}
}

View File

@ -41,6 +41,7 @@
"@anticrm/panel": "~0.6.0",
"@anticrm/view": "~0.6.0",
"@anticrm/view-resources": "~0.6.0",
"@anticrm/login": "~0.6.1"
"@anticrm/login": "~0.6.1",
"@anticrm/chunter-resources": "~0.6.0"
}
}

View File

@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { Attachment } from '@anticrm/chunter'
import chunter from '@anticrm/chunter'
@ -32,7 +31,9 @@
let attachments: Attachment[] = []
const query = createQuery()
$: query.query(chunter.class.Attachment, { attachedTo: objectId }, result => { attachments = result })
$: query.query(chunter.class.Attachment, { attachedTo: objectId }, (result) => {
attachments = result
})
let inputFile: HTMLInputElement
let loading = 0
@ -84,17 +85,37 @@
<div class="flex-row-center">
<span class="title">Attachments</span>
{#if loading}
<Spinner/>
<Spinner />
{:else}
<CircleButton icon={IconAdd} size={'small'} on:click={ () => { inputFile.click() } } />
<CircleButton
icon={IconAdd}
size={'small'}
on:click={() => {
inputFile.click()
}}
/>
{/if}
<input bind:this={inputFile} multiple type="file" name="file" id="file" style="display: none" on:change={fileSelected}/>
<input
bind:this={inputFile}
multiple
type="file"
name="file"
id="file"
style="display: none"
on:change={fileSelected}
/>
</div>
{#if attachments.length === 0 && !loading}
<div class="flex-col-center mt-5 zone-container" class:solid={dragover}
on:dragover|preventDefault={ () => { dragover = true } }
on:dragleave={ () => { dragover = false } }
<div
class="flex-col-center mt-5 zone-container"
class:solid={dragover}
on:dragover|preventDefault={() => {
dragover = true
}}
on:dragleave={() => {
dragover = false
}}
on:drop|preventDefault|stopPropagation={fileDrop}
>
<UploadDuo size={'large'} />
@ -106,11 +127,11 @@
</div>
</div>
{:else}
<Table
<Table
_class={chunter.class.Attachment}
config={['', 'lastModified']}
options={ {} }
query={ { attachedTo: objectId } }
options={{}}
query={{ attachedTo: objectId }}
/>
{/if}
</div>
@ -121,7 +142,7 @@
flex-direction: column;
.title {
margin-right: .75rem;
margin-right: 0.75rem;
font-weight: 500;
font-size: 1.25rem;
color: var(--theme-caption-color);
@ -131,8 +152,8 @@
.zone-container {
padding: 1rem;
color: var(--theme-caption-color);
background: rgba(255, 255, 255, .03);
border: 1px dashed rgba(255, 255, 255, .16);
border-radius: .75rem;
background: rgba(255, 255, 255, 0.03);
border: 1px dashed rgba(255, 255, 255, 0.16);
border-radius: 0.75rem;
}
</style>
</style>

View File

@ -12,15 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { IconFolder, EditBox, ToggleWithLabel, Grid } from '@anticrm/ui'
import core, { generateId, Ref } from '@anticrm/core'
import { getClient, SpaceCreateCard } from '@anticrm/presentation'
import { Project } from '@anticrm/task'
import { EditBox, Grid, IconFolder, ToggleWithLabel } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import task from '../plugin'
import core from '@anticrm/core'
import { createProjectKanban } from '../utils'
const dispatch = createEventDispatcher()
@ -33,24 +32,36 @@
const client = getClient()
function createProject () {
client.createDoc(task.class.Project, core.space.Model, {
name,
description,
private: false,
members: []
async function createProject (): Promise<void> {
const id: Ref<Project> = generateId()
await client.createDoc(
task.class.Project,
core.space.Model,
{
name,
description,
private: false,
members: []
},
id
)
await createProjectKanban(id, async (_class, space, data, id) => {
await client.createDoc(_class, space, data, id)
})
}
</script>
<SpaceCreateCard
label={task.string.CreateProject}
label={task.string.CreateProject}
okAction={createProject}
canSave={name.length > 0}
on:close={() => { dispatch('close') }}
on:close={() => {
dispatch('close')
}}
>
<Grid column={1} rowGap={1.5}>
<EditBox label={task.string.ProjectName} icon={IconFolder} bind:value={name} placeholder={'Project name'} focus/>
<ToggleWithLabel label={task.string.MakePrivate} description={task.string.MakePrivateDescription}/>
<EditBox label={task.string.ProjectName} icon={IconFolder} bind:value={name} placeholder={'Project name'} focus />
<ToggleWithLabel label={task.string.MakePrivate} description={task.string.MakePrivateDescription} />
</Grid>
</SpaceCreateCard>

View File

@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import contact, { Employee, EmployeeAccount } from '@anticrm/contact'
import type { Data, Ref, Space } from '@anticrm/core'
@ -31,14 +30,14 @@
const status: Status = OK
let assignee: Ref<EmployeeAccount> // | null = null
const object: Data<Task> = {
name: '',
description: '',
assignee: undefined as unknown as Ref<Employee>,
number: 0
}
const dispatch = createEventDispatcher()
const client = getClient()
const taskId = generateId()
@ -52,11 +51,17 @@
if (sequence === undefined) {
throw new Error('sequence object not found')
}
const incResult = await client.updateDoc(view.class.Sequence, view.space.Sequence, sequence._id, {
$inc: { sequence: 1 }
}, true)
const incResult = await client.updateDoc(
view.class.Sequence,
view.space.Sequence,
sequence._id,
{
$inc: { sequence: 1 }
},
true
)
const value: Data<Task> = {
name: object.name,
description: object.description,
@ -71,32 +76,50 @@
<!-- <DialogHeader {space} {object} {newValue} {resume} create={true} on:save={createCandidate}/> -->
<Card label={task.string.CreateTask}
okAction={createTask}
canSave={object.name.length > 0 && assignee !== undefined}
spaceClass={task.class.Project}
spaceLabel={task.string.ProjectName}
spacePlaceholder={task.string.SelectProject}
bind:space={_space}
on:close={() => { dispatch('close') }}>
<StatusControl slot="error" {status} />
<Grid column={1} rowGap={1.5}>
<EditBox label={task.string.TaskName} bind:value={object.name} icon={task.icon.Task} placeholder="The boring task" maxWidth="39rem" focus/>
<UserBox _class={contact.class.EmployeeAccount} title='Assignee *' caption='Assign this task' bind:value={assignee} />
</Grid>
<Card
label={task.string.CreateTask}
okAction={createTask}
canSave={object.name.length > 0 && assignee !== undefined}
spaceClass={task.class.Project}
spaceLabel={task.string.ProjectName}
spacePlaceholder={task.string.SelectProject}
bind:space={_space}
on:close={() => {
dispatch('close')
}}
>
<StatusControl slot="error" {status} />
<Grid column={1} rowGap={1.5}>
<EditBox
label={task.string.TaskName}
bind:value={object.name}
icon={task.icon.Task}
placeholder="The boring task"
maxWidth="39rem"
focus
/>
<UserBox
_class={contact.class.EmployeeAccount}
title="Assignee *"
caption="Assign this task"
bind:value={assignee}
/>
</Grid>
</Card>
<style lang="scss">
.channels {
margin-top: 1.25rem;
span { margin-left: .5rem; }
span {
margin-left: 0.5rem;
}
}
.locations {
span {
margin-bottom: .125rem;
margin-bottom: 0.125rem;
font-weight: 500;
font-size: .75rem;
font-size: 0.75rem;
color: var(--theme-content-accent-color);
}
@ -104,7 +127,7 @@
display: flex;
justify-content: space-between;
align-items: center;
margin-top: .75rem;
margin-top: 0.75rem;
color: var(--theme-caption-color);
}
}
@ -117,12 +140,14 @@
.resume {
margin-top: 1rem;
padding: .75rem;
background: rgba(255, 255, 255, .05);
border: 1px dashed rgba(255, 255, 255, .2);
border-radius: .5rem;
padding: 0.75rem;
background: rgba(255, 255, 255, 0.05);
border: 1px dashed rgba(255, 255, 255, 0.2);
border-radius: 0.5rem;
backdrop-filter: blur(10px);
&.solid { border-style: solid; }
&.solid {
border-style: solid;
}
}
// .resume a {
// font-size: .75rem;

View File

@ -12,24 +12,25 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { Ref } from '@anticrm/core'
import { Panel } from '@anticrm/panel'
import { createQuery, getClient } from '@anticrm/presentation'
import type { Task } from '@anticrm/task'
import { EditBox, Grid } from '@anticrm/ui'
import { EditBox, Grid } from '@anticrm/ui'
import view from '@anticrm/view'
import { createEventDispatcher } from 'svelte'
import task from '../plugin'
import Attachments from './Attachments.svelte'
import TaskHeader from './TaskHeader.svelte'
export let _id: Ref<Task>
let object: Task
const query = createQuery()
$: query.query(task.class.Task, { _id }, result => { object = result[0] })
$: query.query(task.class.Task, { _id }, (result) => {
object = result[0]
})
const dispatch = createEventDispatcher()
const client = getClient()
@ -40,19 +41,40 @@ import { EditBox, Grid } from '@anticrm/ui'
</script>
{#if object !== undefined}
<Panel icon={view.icon.Table} title={object.name} {object} on:close={() => { dispatch('close') }}>
<TaskHeader {object} slot="subtitle" />
<Panel
icon={view.icon.Table}
title={object.name}
{object}
on:close={() => {
dispatch('close')
}}
>
<TaskHeader {object} slot="subtitle" />
<Grid column={1} rowGap={1.5}>
<EditBox label={task.string.TaskName} bind:value={object.name} icon={task.icon.Task} placeholder="The boring task" maxWidth="39rem" focus on:change={(evt) => change('name', object.name)}/>
<EditBox label={task.string.TaskDescription} bind:value={object.description} icon={task.icon.Task} placeholder="Description" maxWidth="39rem" on:change={(evt) => change('description', object.description)}/>
</Grid>
<div class="mt-14">
<Attachments objectId={object._id} _class={object._class} space={object.space} />
</div>
</Panel>
<Grid column={1} rowGap={1.5}>
<EditBox
label={task.string.TaskName}
bind:value={object.name}
icon={task.icon.Task}
placeholder="The boring task"
maxWidth="39rem"
focus
on:change={(evt) => change('name', object.name)}
/>
<EditBox
label={task.string.TaskDescription}
bind:value={object.description}
icon={task.icon.Task}
placeholder="Description"
maxWidth="39rem"
on:change={(evt) => change('description', object.description)}
/>
</Grid>
<div class="mt-14">
<Attachments objectId={object._id} _class={object._class} space={object.space} />
</div>
</Panel>
{/if}
<style lang="scss">

View File

@ -0,0 +1,91 @@
<!--
// 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 { AttachmentsPresenter, CommentsPresenter } from '@anticrm/chunter-resources'
import { formatName } from '@anticrm/contact'
import type { WithLookup } from '@anticrm/core'
import { Avatar } from '@anticrm/presentation'
import type { Task } from '@anticrm/task'
import { ActionIcon, IconMoreH, Label, showPopup } from '@anticrm/ui'
import { ContextMenu } from '@anticrm/view-resources'
import task from '../plugin'
import TaskPresenter from './TaskPresenter.svelte'
export let object: WithLookup<Task>
export let draggable: boolean
const showMenu = (ev?: Event): void => {
showPopup(ContextMenu, { object }, (ev as MouseEvent).target as HTMLElement)
}
</script>
<div class="card-container" {draggable} class:draggable on:dragstart on:dragend>
<div class="content">
<div class="flex-row-center">
<div class="flex-col ml-2">
<div class="sm-tool-icon step-lr75">
<TaskPresenter value={object} />
</div>
<div class="fs-title">{object.name}</div>
<div class="small-text">{object.description}</div>
</div>
</div>
</div>
<div class="flex-between">
{#if object.$lookup?.assignee}
<div class="flex-center safari-gap-1">
<Avatar avatar={object.$lookup?.assignee?.avatar} size={'x-small'} />
<Label label={formatName(object.$lookup?.assignee?.name)} />
</div>
{/if}
<div class="flex-row-reverse">
<ActionIcon
label={task.string.More}
action={(evt) => {
showMenu(evt)
}}
icon={IconMoreH}
size={'small'}
/>
{#if (object.comments ?? 0) > 0}
<div class="step-lr75"><CommentsPresenter value={object} /></div>
{/if}
{#if (object.attachments ?? 0) > 0}
<div class="step-lr75"><AttachmentsPresenter value={object} /></div>
{/if}
</div>
</div>
</div>
<style lang="scss">
.card-container {
display: flex;
flex-direction: column;
padding: 1rem 1.25rem;
background-color: rgba(222, 222, 240, 0.06);
border-radius: 0.75rem;
user-select: none;
backdrop-filter: blur(10px);
.content {
display: flex;
justify-content: space-between;
margin-bottom: 1rem;
}
&.draggable {
cursor: grab;
}
}
</style>

View File

@ -13,10 +13,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import contact from '@anticrm/contact'
import { getClient, UserBox } from '@anticrm/presentation'
import { AttributeBarEditor, getClient, UserBox } from '@anticrm/presentation'
import { Task } from '@anticrm/task'
import task from '../plugin'
@ -29,13 +28,19 @@
</script>
<div class="flex-between header">
<UserBox _class={contact.class.Employee} title={task.string.TaskAssignee} caption='Assignee' bind:value={object.assignee} on:change={change} />
<!-- <AttributeBarEditor key={'state'} {object} showHeader={false} /> -->
<UserBox
_class={contact.class.Employee}
title={task.string.TaskAssignee}
caption="Assignee"
bind:value={object.assignee}
on:change={change}
/>
<AttributeBarEditor key={'state'} {object} showHeader={false} />
</div>
<style lang="scss">
.header {
width: 100%;
padding: 0 .5rem;
padding: 0 0.5rem;
}
</style>
</style>

View File

@ -13,27 +13,24 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { Task } from '@anticrm/task'
import { closeTooltip, Icon, showPopup } from '@anticrm/ui'
import EditTask from './EditTask.svelte'
import { getClient } from '@anticrm/presentation'
import task from '../plugin'
import type { Task } from '@anticrm/task'
import { closeTooltip, Icon, showPopup } from '@anticrm/ui'
import EditTask from './EditTask.svelte'
import { getClient } from '@anticrm/presentation'
import task from '../plugin'
export let value: Task
export let value: Task
const client = getClient()
const shortLabel = client.getHierarchy().getClass(value._class).shortLabel
function show () {
closeTooltip()
showPopup(EditTask, { _id: value._id }, 'full')
}
const client = getClient()
const shortLabel = client.getHierarchy().getClass(value._class).shortLabel
function show () {
closeTooltip()
showPopup(EditTask, { _id: value._id }, 'full')
}
</script>
<div class="sm-tool-icon" on:click={show}>
<span class="icon"><Icon icon={task.icon.Task} size={'small'}/></span>{shortLabel}-{value.number}
<span class="icon"><Icon icon={task.icon.Task} size={'small'} /></span>{shortLabel}-{value.number}
</div>

View File

@ -13,16 +13,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
export let size: 'small' | 'medium' | 'large'
const fill: string = 'currentColor'
</script>
<svg class="svg-{size}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="var(--duotone-color)" d="M18,6.4c-0.1,0-0.2,0-0.3,0c-0.1,0-0.1,0-0.1,0c-0.1,0-0.1,0-0.1,0c0,0,0,0-0.1-0.1c0,0,0,0-0.1-0.1 c0-0.1-0.1-0.2-0.2-0.4c-0.9-1.9-2.8-3.3-5.1-3.3c-2.3,0-4.2,1.4-5.1,3.3C6.8,5.9,6.7,6,6.7,6.1c0,0.1-0.1,0.1-0.1,0.1 C6.6,6.3,6.6,6.3,6.5,6.3c0,0,0,0-0.1,0c0,0,0,0-0.1,0c-0.1,0-0.2,0-0.3,0C4,6.4,2.4,8,2.4,10S4,13.6,6,13.6h6h6 c2,0,3.6-1.6,3.6-3.6S20,6.4,18,6.4z"/>
<path
fill="var(--duotone-color)"
d="M18,6.4c-0.1,0-0.2,0-0.3,0c-0.1,0-0.1,0-0.1,0c-0.1,0-0.1,0-0.1,0c0,0,0,0-0.1-0.1c0,0,0,0-0.1-0.1 c0-0.1-0.1-0.2-0.2-0.4c-0.9-1.9-2.8-3.3-5.1-3.3c-2.3,0-4.2,1.4-5.1,3.3C6.8,5.9,6.7,6,6.7,6.1c0,0.1-0.1,0.1-0.1,0.1 C6.6,6.3,6.6,6.3,6.5,6.3c0,0,0,0-0.1,0c0,0,0,0-0.1,0c-0.1,0-0.2,0-0.3,0C4,6.4,2.4,8,2.4,10S4,13.6,6,13.6h6h6 c2,0,3.6-1.6,3.6-3.6S20,6.4,18,6.4z"
/>
<g {fill}>
<path d="M18,6.4c-0.1,0-0.2,0-0.3,0c-0.1,0-0.1,0-0.1,0c-0.1,0-0.1,0-0.1,0c0,0,0,0-0.1-0.1c0,0,0,0,0,0c0,0,0,0,0,0 c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0-0.1-0.1c0-0.1-0.1-0.2-0.2-0.4c-0.9-1.9-2.8-3.3-5.1-3.3c-2.3,0-4.2,1.4-5.1,3.3 C6.8,5.9,6.7,6,6.7,6.1c0,0.1-0.1,0.1-0.1,0.1c0,0,0,0,0,0C6.6,6.3,6.6,6.3,6.5,6.3c0,0,0,0-0.1,0c0,0,0,0-0.1,0 c-0.1,0-0.2,0-0.3,0C4,6.4,2.4,8,2.4,10S4,13.6,6,13.6h0.6l1.2-1.2H6c-1.3,0-2.4-1.1-2.4-2.4c0-1.3,1.1-2.4,2.4-2.4h0.1 c0.2,0,0.4,0,0.6,0c0.2,0,0.4-0.1,0.6-0.2c0.2-0.1,0.3-0.3,0.4-0.4c0.1-0.1,0.1-0.2,0.2-0.3C7.8,6.5,7.9,6.4,8,6.2l0,0 c0.7-1.5,2.2-2.6,4-2.6s3.3,1.1,4,2.6l0,0c0.1,0.2,0.1,0.3,0.2,0.4c0,0.1,0.1,0.2,0.2,0.3c0.1,0.2,0.2,0.3,0.4,0.4 c0.2,0.1,0.4,0.2,0.6,0.2c0.2,0,0.4,0,0.6,0H18c1.3,0,2.4,1.1,2.4,2.4c0,1.3-1.1,2.4-2.4,2.4h-1.8l1.2,1.2H18c2,0,3.6-1.6,3.6-3.6 S20,6.4,18,6.4z"/>
<path d="M12,11.2l-4.4,4.4l0.8,0.8l3-3V21c0,0.3,0.3,0.6,0.6,0.6s0.6-0.3,0.6-0.6v-7.6l3,3l0.8-0.8L12,11.2z"/>
<path
d="M18,6.4c-0.1,0-0.2,0-0.3,0c-0.1,0-0.1,0-0.1,0c-0.1,0-0.1,0-0.1,0c0,0,0,0-0.1-0.1c0,0,0,0,0,0c0,0,0,0,0,0 c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0-0.1-0.1c0-0.1-0.1-0.2-0.2-0.4c-0.9-1.9-2.8-3.3-5.1-3.3c-2.3,0-4.2,1.4-5.1,3.3 C6.8,5.9,6.7,6,6.7,6.1c0,0.1-0.1,0.1-0.1,0.1c0,0,0,0,0,0C6.6,6.3,6.6,6.3,6.5,6.3c0,0,0,0-0.1,0c0,0,0,0-0.1,0 c-0.1,0-0.2,0-0.3,0C4,6.4,2.4,8,2.4,10S4,13.6,6,13.6h0.6l1.2-1.2H6c-1.3,0-2.4-1.1-2.4-2.4c0-1.3,1.1-2.4,2.4-2.4h0.1 c0.2,0,0.4,0,0.6,0c0.2,0,0.4-0.1,0.6-0.2c0.2-0.1,0.3-0.3,0.4-0.4c0.1-0.1,0.1-0.2,0.2-0.3C7.8,6.5,7.9,6.4,8,6.2l0,0 c0.7-1.5,2.2-2.6,4-2.6s3.3,1.1,4,2.6l0,0c0.1,0.2,0.1,0.3,0.2,0.4c0,0.1,0.1,0.2,0.2,0.3c0.1,0.2,0.2,0.3,0.4,0.4 c0.2,0.1,0.4,0.2,0.6,0.2c0.2,0,0.4,0,0.6,0H18c1.3,0,2.4,1.1,2.4,2.4c0,1.3-1.1,2.4-2.4,2.4h-1.8l1.2,1.2H18c2,0,3.6-1.6,3.6-3.6 S20,6.4,18,6.4z"
/>
<path d="M12,11.2l-4.4,4.4l0.8,0.8l3-3V21c0,0.3,0.3,0.6,0.6,0.6s0.6-0.3,0.6-0.6v-7.6l3,3l0.8-0.8L12,11.2z" />
</g>
</svg>

View File

@ -19,11 +19,15 @@ import { Resources } from '@anticrm/platform'
import CreateTask from './components/CreateTask.svelte'
import CreateProject from './components/CreateProject.svelte'
import TaskPresenter from './components/TaskPresenter.svelte'
import KanbanCard from './components/KanbanCard.svelte'
export { createProjectKanban } from './utils'
export default async (): Promise<Resources> => ({
component: {
CreateTask,
CreateProject,
TaskPresenter
TaskPresenter,
KanbanCard
}
})

View File

@ -32,7 +32,8 @@ export default mergeIds(taskId, task, {
TaskAssignee: '' as IntlString,
TaskDescription: '' as IntlString,
NoAttachmentsForTask: '' as IntlString,
UploadDropFilesHere: '' as IntlString
UploadDropFilesHere: '' as IntlString,
More: '' as IntlString
},
status: {
AssigneeRequired: '' as IntlString

View File

@ -14,9 +14,12 @@
// limitations under the License.
//
import type { Doc, Ref, Space } from '@anticrm/core'
import type { Class, Data, Doc, Ref, Space, State } from '@anticrm/core'
import login from '@anticrm/login'
import { getMetadata } from '@anticrm/platform'
import { Project } from '@anticrm/task'
import core from '@anticrm/core'
import view, { Kanban } from '@anticrm/view'
export async function uploadFile (space: Ref<Space>, file: File, attachedTo: Ref<Doc>): Promise<string> {
console.log(file)
@ -40,3 +43,41 @@ export async function uploadFile (space: Ref<Space>, file: File, attachedTo: Ref
console.log(uuid)
return uuid
}
export async function createProjectKanban (
projectId: Ref<Project>,
factory: <T extends Doc>(_class: Ref<Class<T>>, space: Ref<Space>, data: Data<T>, id: Ref<T>) => Promise<void>
): Promise<void> {
const states = [
{ color: '#7C6FCD', name: 'Open' },
{ color: '#6F7BC5', name: 'In Progress' },
{ color: '#77C07B', name: 'Under review' },
{ color: '#A5D179', name: 'Done' },
{ color: '#F28469', name: 'Invalid' }
]
const ids: Array<Ref<State>> = []
for (const st of states) {
const sid = (projectId + '.state.' + st.name.toLowerCase().replace(' ', '_')) as Ref<State>
await factory(
core.class.State,
projectId,
{
title: st.name,
color: st.color
},
sid
)
ids.push(sid)
}
await factory(
view.class.Kanban,
projectId,
{
attachedTo: projectId,
states: ids,
order: []
},
(projectId + '.kanban.') as Ref<Kanban>
)
}

View File

@ -1,59 +1,58 @@
//
// Copyright © 2020 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.
//
import type { AttachedDoc, Doc } from '@anticrm/core'
import core from '@anticrm/core'
import StringEditor from './components/StringEditor.svelte'
import StringPresenter from './components/StringPresenter.svelte'
import BooleanEditor from './components/BooleanEditor.svelte'
import BooleanPresenter from './components/BooleanPresenter.svelte'
import StatePresenter from './components/StatePresenter.svelte'
import StateEditor from './components/StateEditor.svelte'
import TimestampPresenter from './components/TimestampPresenter.svelte'
import DateEditor from './components/DateEditor.svelte'
import DatePresenter from './components/DatePresenter.svelte'
import TableView from './components/TableView.svelte'
import Table from './components/Table.svelte'
import KanbanView from './components/KanbanView.svelte'
import { Resources } from '@anticrm/platform'
import { getClient, MessageBox } from '@anticrm/presentation'
import { showPopup } from '@anticrm/ui'
import {buildModel} from './utils'
import BooleanEditor from './components/BooleanEditor.svelte'
import BooleanPresenter from './components/BooleanPresenter.svelte'
import DateEditor from './components/DateEditor.svelte'
import DatePresenter from './components/DatePresenter.svelte'
import KanbanView from './components/KanbanView.svelte'
import StateEditor from './components/StateEditor.svelte'
import StatePresenter from './components/StatePresenter.svelte'
import StringEditor from './components/StringEditor.svelte'
import StringPresenter from './components/StringPresenter.svelte'
import Table from './components/Table.svelte'
import TableView from './components/TableView.svelte'
import TimestampPresenter from './components/TimestampPresenter.svelte'
export { default as ContextMenu } from './components/Menu.svelte'
export { buildModel, getActions, getObjectPresenter } from './utils'
export { Table }
export { buildModel, getObjectPresenter, getActions } from './utils'
function Delete(object: Doc): void {
function Delete (object: Doc): void {
showPopup(MessageBox, {
label: 'Delete object',
message: 'Do you want to delete this object?'
}, undefined, (result) => {
if (result) {
if (result !== undefined) {
const client = getClient()
if(client.getHierarchy().isDerived(object._class, core.class.AttachedDoc)) {
if (client.getHierarchy().isDerived(object._class, core.class.AttachedDoc)) {
const adoc = object as AttachedDoc
client.removeCollection(object._class, object.space, adoc._id, adoc.attachedTo, adoc.attachedToClass, adoc.collection)
client.removeCollection(object._class, object.space, adoc._id, adoc.attachedTo, adoc.attachedToClass, adoc.collection).catch(err => console.error(err))
} else {
client.removeDoc(object._class, object.space, object._id)
client.removeDoc(object._class, object.space, object._id).catch(err => console.error(err))
}
}
})
}
export default async () => ({
export default async (): Promise<Resources> => ({
actionImpl: {
Delete
},