diff --git a/models/task/src/index.ts b/models/task/src/index.ts index e0661790ad..c84c29d03a 100644 --- a/models/task/src/index.ts +++ b/models/task/src/index.ts @@ -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)) } diff --git a/models/task/src/plugin.ts b/models/task/src/plugin.ts index 76c915e7a4..c028ae54da 100644 --- a/models/task/src/plugin.ts +++ b/models/task/src/plugin.ts @@ -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, diff --git a/plugins/task-assets/lang/en.json b/plugins/task-assets/lang/en.json index 8d41621ab9..ae5098566e 100644 --- a/plugins/task-assets/lang/en.json +++ b/plugins/task-assets/lang/en.json @@ -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" } } \ No newline at end of file diff --git a/plugins/task-resources/package.json b/plugins/task-resources/package.json index bf4d9db4f4..2e11a54f27 100644 --- a/plugins/task-resources/package.json +++ b/plugins/task-resources/package.json @@ -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" } } diff --git a/plugins/task-resources/src/components/Attachments.svelte b/plugins/task-resources/src/components/Attachments.svelte index abff2dbade..985629243f 100644 --- a/plugins/task-resources/src/components/Attachments.svelte +++ b/plugins/task-resources/src/components/Attachments.svelte @@ -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> \ No newline at end of file +</style> diff --git a/plugins/task-resources/src/components/CreateProject.svelte b/plugins/task-resources/src/components/CreateProject.svelte index 686c25803d..3314de04de 100644 --- a/plugins/task-resources/src/components/CreateProject.svelte +++ b/plugins/task-resources/src/components/CreateProject.svelte @@ -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> diff --git a/plugins/task-resources/src/components/CreateTask.svelte b/plugins/task-resources/src/components/CreateTask.svelte index 67bab2f5c3..9cddc13f25 100644 --- a/plugins/task-resources/src/components/CreateTask.svelte +++ b/plugins/task-resources/src/components/CreateTask.svelte @@ -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; diff --git a/plugins/task-resources/src/components/EditTask.svelte b/plugins/task-resources/src/components/EditTask.svelte index c8f23af891..6a2cdda37b 100644 --- a/plugins/task-resources/src/components/EditTask.svelte +++ b/plugins/task-resources/src/components/EditTask.svelte @@ -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"> diff --git a/plugins/task-resources/src/components/KanbanCard.svelte b/plugins/task-resources/src/components/KanbanCard.svelte new file mode 100644 index 0000000000..e5b954faa6 --- /dev/null +++ b/plugins/task-resources/src/components/KanbanCard.svelte @@ -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> diff --git a/plugins/task-resources/src/components/TaskHeader.svelte b/plugins/task-resources/src/components/TaskHeader.svelte index 365e35da26..a8fa60b8a5 100644 --- a/plugins/task-resources/src/components/TaskHeader.svelte +++ b/plugins/task-resources/src/components/TaskHeader.svelte @@ -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> \ No newline at end of file +</style> diff --git a/plugins/task-resources/src/components/TaskPresenter.svelte b/plugins/task-resources/src/components/TaskPresenter.svelte index c1b4b169b8..704288bc17 100644 --- a/plugins/task-resources/src/components/TaskPresenter.svelte +++ b/plugins/task-resources/src/components/TaskPresenter.svelte @@ -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> diff --git a/plugins/task-resources/src/components/icons/UploadDuo.svelte b/plugins/task-resources/src/components/icons/UploadDuo.svelte index ee79d553c9..dc405e584d 100644 --- a/plugins/task-resources/src/components/icons/UploadDuo.svelte +++ b/plugins/task-resources/src/components/icons/UploadDuo.svelte @@ -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> diff --git a/plugins/task-resources/src/index.ts b/plugins/task-resources/src/index.ts index ad64ff7533..e5c441acaf 100644 --- a/plugins/task-resources/src/index.ts +++ b/plugins/task-resources/src/index.ts @@ -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 } }) diff --git a/plugins/task-resources/src/plugin.ts b/plugins/task-resources/src/plugin.ts index e58bd05df2..c65fe72254 100644 --- a/plugins/task-resources/src/plugin.ts +++ b/plugins/task-resources/src/plugin.ts @@ -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 diff --git a/plugins/task-resources/src/utils.ts b/plugins/task-resources/src/utils.ts index f61a3d32d9..659d7adfce 100644 --- a/plugins/task-resources/src/utils.ts +++ b/plugins/task-resources/src/utils.ts @@ -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> + ) +} diff --git a/plugins/view-resources/src/index.ts b/plugins/view-resources/src/index.ts index 16e97c97f7..0c848ab6d9 100644 --- a/plugins/view-resources/src/index.ts +++ b/plugins/view-resources/src/index.ts @@ -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 },