From caff5834835444916d164c02fdbf17d6e6cc84c5 Mon Sep 17 00:00:00 2001 From: Ilya Sumbatyants Date: Tue, 14 Dec 2021 16:24:14 +0700 Subject: [PATCH] Kanban templates & won/lost states (#612) Signed-off-by: Ilya Sumbatyants --- dev/generator/src/kanban.ts | 31 ++- dev/generator/src/recruit.ts | 3 +- models/all/src/migration.ts | 8 +- models/chunter/src/plugin.ts | 2 +- models/core/src/index.ts | 7 +- models/core/src/security.ts | 17 +- models/lead/src/index.ts | 11 + models/lead/src/plugin.ts | 3 +- models/recruit/src/index.ts | 15 +- models/recruit/src/plugin.ts | 3 +- models/setting/src/index.ts | 6 + models/task/package.json | 3 +- models/task/src/index.ts | 12 + models/task/src/plugin.ts | 3 +- models/view/src/index.ts | 135 +++++++++-- models/view/src/migration.ts | 54 +++++ models/workbench/package.json | 3 +- models/workbench/src/index.ts | 7 +- models/workbench/src/plugin.ts | 5 +- packages/core/src/classes.ts | 18 ++ packages/core/src/component.ts | 4 + packages/theme/styles/_layouts.scss | 6 + .../ui/src/components/DumbDropdown.svelte | 200 ++++++++++++++++ packages/ui/src/components/Icon.svelte | 7 +- packages/ui/src/index.ts | 1 + packages/ui/src/types.ts | 5 + .../src/components/CreateFunnel.svelte | 21 +- .../src/components/CreateLead.svelte | 1 + .../src/components/TemplatesIcon.svelte | 21 ++ plugins/lead-resources/src/index.ts | 4 +- plugins/lead/src/index.ts | 25 +- .../src/components/CreateApplication.svelte | 1 + .../src/components/CreateVacancy.svelte | 55 ++--- .../src/components/TemplatesIcon.svelte | 20 ++ .../src/components/icons/Vacancy.svelte | 2 +- plugins/recruit-resources/src/index.ts | 4 +- plugins/recruit/package.json | 3 +- plugins/recruit/src/index.ts | 4 + plugins/setting-assets/assets/icons.svg | 5 + plugins/setting-assets/lang/en.json | 7 +- plugins/setting-assets/src/index.ts | 1 + plugins/setting-resources/package.json | 5 +- .../src/components/statuses/Folders.svelte | 120 ++++++++++ .../components/statuses/ManageStatuses.svelte | 125 ++++++++++ .../src/components/statuses/Templates.svelte | 159 +++++++++++++ plugins/setting-resources/src/index.ts | 4 +- plugins/setting/src/index.ts | 8 +- .../src/components/CreateProject.svelte | 22 +- .../src/components/CreateTask.svelte | 10 +- .../src/components/TemplatesIcon.svelte | 21 ++ plugins/task-resources/src/index.ts | 4 +- plugins/task/src/index.ts | 28 ++- .../src/components/Connect.svelte | 6 +- plugins/view-assets/lang/en.json | 5 +- .../src/components/ColorsPopup.svelte | 0 .../src/components/KanbanEditor.svelte | 79 +++++++ .../components/KanbanTemplateEditor.svelte | 103 +++++++++ .../components/KanbanTemplateSelector.svelte | 39 ++++ .../src/components/KanbanView.svelte | 218 +++++++++++++----- .../src/components/PopupDialog.svelte | 0 .../src/components/StatesEditor.svelte | 197 ++++++++++++++++ .../src/components/StatusesPopup.svelte | 59 +++++ .../src/components/icons/Circles.svelte | 0 plugins/view-resources/src/index.ts | 3 + plugins/view/src/index.ts | 127 +++++++++- plugins/workbench-resources/package.json | 3 +- .../components/ApplicationPresenter.svelte | 34 +++ .../src/components/EditStatuses.svelte | 188 ++++----------- .../src/components/StatusesPopup.svelte | 94 -------- .../src/components/Workbench.svelte | 1 + plugins/workbench-resources/src/index.ts | 4 +- 71 files changed, 1978 insertions(+), 431 deletions(-) create mode 100644 models/view/src/migration.ts create mode 100644 packages/ui/src/components/DumbDropdown.svelte create mode 100644 plugins/lead-resources/src/components/TemplatesIcon.svelte create mode 100644 plugins/recruit-resources/src/components/TemplatesIcon.svelte create mode 100644 plugins/setting-resources/src/components/statuses/Folders.svelte create mode 100644 plugins/setting-resources/src/components/statuses/ManageStatuses.svelte create mode 100644 plugins/setting-resources/src/components/statuses/Templates.svelte create mode 100644 plugins/task-resources/src/components/TemplatesIcon.svelte rename plugins/{workbench-resources => view-resources}/src/components/ColorsPopup.svelte (100%) create mode 100644 plugins/view-resources/src/components/KanbanEditor.svelte create mode 100644 plugins/view-resources/src/components/KanbanTemplateEditor.svelte create mode 100644 plugins/view-resources/src/components/KanbanTemplateSelector.svelte rename plugins/{workbench-resources => view-resources}/src/components/PopupDialog.svelte (100%) create mode 100644 plugins/view-resources/src/components/StatesEditor.svelte create mode 100644 plugins/view-resources/src/components/StatusesPopup.svelte rename plugins/{workbench-resources => view-resources}/src/components/icons/Circles.svelte (100%) create mode 100644 plugins/workbench-resources/src/components/ApplicationPresenter.svelte delete mode 100644 plugins/workbench-resources/src/components/StatusesPopup.svelte diff --git a/dev/generator/src/kanban.ts b/dev/generator/src/kanban.ts index 40712b8daa..e44a86359f 100644 --- a/dev/generator/src/kanban.ts +++ b/dev/generator/src/kanban.ts @@ -1,17 +1,17 @@ -import core, { Ref, SpaceWithStates, State, TxOperations } from '@anticrm/core' +import core, { DoneState, Ref, SpaceWithStates, State, TxOperations } from '@anticrm/core' import { findOrUpdate } from './utils' import view, { Kanban } from '@anticrm/view' export async function createUpdateSpaceKanban (spaceId: Ref, client: TxOperations): Promise[]> { - const states = [ + const rawStates = [ { color: '#7C6FCD', name: 'Initial' }, { color: '#6F7BC5', name: 'Intermidiate' }, { color: '#77C07B', name: 'OverIntermidiate' }, { color: '#A5D179', name: 'Done' }, { color: '#F28469', name: 'Invalid' } ] - const ids: Array> = [] - for (const st of states) { + const states: Array> = [] + for (const st of rawStates) { const sid = ('generated-' + spaceId + '.state.' + st.name.toLowerCase().replace(' ', '_')) as Ref await findOrUpdate(client, spaceId, core.class.State, sid, @@ -20,7 +20,23 @@ export async function createUpdateSpaceKanban (spaceId: Ref, cl color: st.color } ) - ids.push(sid) + states.push(sid) + } + + const rawDoneStates = [ + { class: core.class.WonState, title: 'Won' }, + { class: core.class.LostState, title: 'Lost' } + ] + const doneStates: Array> = [] + for (const st of rawDoneStates) { + const sid = ('generated-' + spaceId + '.done-state.' + st.title.toLowerCase().replace(' ', '_')) as Ref + await findOrUpdate(client, spaceId, st.class, + sid, + { + title: st.title + } + ) + doneStates.push(sid) } await findOrUpdate(client, spaceId, @@ -28,9 +44,10 @@ export async function createUpdateSpaceKanban (spaceId: Ref, cl ('generated-' + spaceId + '.kanban') as Ref, { attachedTo: spaceId, - states: ids, + states, + doneStates, order: [] } ) - return ids + return states } diff --git a/dev/generator/src/recruit.ts b/dev/generator/src/recruit.ts index 518ac15959..8471baffdf 100644 --- a/dev/generator/src/recruit.ts +++ b/dev/generator/src/recruit.ts @@ -99,7 +99,8 @@ export async function generateContacts (transactorUrl: string, dbName: string, o const applicant: AttachedData = { number: faker.datatype.number(), employee: faker.random.arrayElement(emoloyeeIds), - state: faker.random.arrayElement(states) + state: faker.random.arrayElement(states), + doneState: null } // Update or create candidate diff --git a/models/all/src/migration.ts b/models/all/src/migration.ts index df2acd3521..69e5c3a7b6 100644 --- a/models/all/src/migration.ts +++ b/models/all/src/migration.ts @@ -19,5 +19,11 @@ import { MigrateOperation } from '@anticrm/model' import { taskOperation } from '@anticrm/model-task' import { attachmentOperation } from '@anticrm/model-attachment' import { leadOperation } from '@anticrm/model-lead' +import { viewOperation } from '@anticrm/model-view' -export const migrateOperations: MigrateOperation[] = [taskOperation, attachmentOperation, leadOperation] +export const migrateOperations: MigrateOperation[] = [ + taskOperation, + attachmentOperation, + leadOperation, + viewOperation +] diff --git a/models/chunter/src/plugin.ts b/models/chunter/src/plugin.ts index dc8e9a7b1b..7e6ebc4025 100644 --- a/models/chunter/src/plugin.ts +++ b/models/chunter/src/plugin.ts @@ -18,7 +18,7 @@ import chunter from '@anticrm/chunter-resources/src/plugin' import type { IntlString } from '@anticrm/platform' import { mergeIds } from '@anticrm/platform' import type { Ref } from '@anticrm/core' -import { ViewletDescriptor } from '@anticrm/view' +import type { ViewletDescriptor } from '@anticrm/view' import type { AnyComponent } from '@anticrm/ui' import type { TxViewlet } from '@anticrm/activity' import { Application } from '@anticrm/workbench' diff --git a/models/core/src/index.ts b/models/core/src/index.ts index 9bc93b3516..db496cd0cd 100644 --- a/models/core/src/index.ts +++ b/models/core/src/index.ts @@ -16,7 +16,7 @@ import { Builder } from '@anticrm/model' import core from './component' import { TAttribute, TClass, TDoc, TMixin, TObj, TType, TTypeString, TTypeBoolean, TTypeTimestamp, TTypeDate, TAttachedDoc, TCollection, TRefTo } from './core' -import { TSpace, TAccount, TState, TSpaceWithStates, TDocWithState } from './security' +import { TSpace, TAccount, TState, TDoneState, TWonState, TLostState, TSpaceWithStates, TDocWithState } from './security' import { TTx, TTxCreateDoc, TTxMixin, TTxUpdateDoc, TTxCUD, TTxPutBag, TTxRemoveDoc, TTxBulkWrite, TTxCollectionCUD } from './tx' export * from './core' @@ -52,6 +52,9 @@ export function createModel (builder: Builder): void { TRefTo, TCollection, TTypeDate, - TState + TState, + TDoneState, + TWonState, + TLostState ) } diff --git a/models/core/src/security.ts b/models/core/src/security.ts index 2ff043681a..5fc6fc4ab9 100644 --- a/models/core/src/security.ts +++ b/models/core/src/security.ts @@ -13,7 +13,7 @@ // limitations under the License. // -import type { Account, Arr, Domain, Ref, Space, State } from '@anticrm/core' +import type { Account, Arr, Domain, Ref, Space, State, DoneState, WonState, LostState } from '@anticrm/core' import { DOMAIN_MODEL } from '@anticrm/core' import { Implements, Model, Prop, TypeBoolean, TypeRef, TypeString } from '@anticrm/model' import type { IntlString } from '@anticrm/platform' @@ -51,11 +51,26 @@ export class TState extends TDoc implements State { color!: string } +@Model(core.class.DoneState, core.class.Doc, DOMAIN_STATE) +export class TDoneState extends TDoc implements DoneState { + @Prop(TypeString(), 'Title' as IntlString) + title!: string +} + +@Model(core.class.WonState, core.class.DoneState, DOMAIN_STATE) +export class TWonState extends TDoneState implements WonState {} + +@Model(core.class.LostState, core.class.DoneState, DOMAIN_STATE) +export class TLostState extends TDoneState implements LostState {} + @Implements(core.interface.DocWithState) export class TDocWithState extends TDoc { @Prop(TypeRef(core.class.State), 'State' as IntlString) state!: Ref + @Prop(TypeRef(core.class.DoneState), 'Done Status' as IntlString) + doneState!: Ref | null + @Prop(TypeString(), 'No.' as IntlString) number!: number } diff --git a/models/lead/src/index.ts b/models/lead/src/index.ts index acbf62ce1b..cb7c37e720 100644 --- a/models/lead/src/index.ts +++ b/models/lead/src/index.ts @@ -14,6 +14,9 @@ // limitations under the License. // +// To help typescript locate view plugin properly +import type {} from '@anticrm/view' + import type { Contact } from '@anticrm/contact' import type { Doc, Domain, FindOptions, Ref } from '@anticrm/core' import { Builder, Model, Prop, TypeRef, TypeString, UX } from '@anticrm/model' @@ -134,6 +137,14 @@ export function createModel (builder: Builder): void { sequence: 0 }) + builder.createDoc(view.class.KanbanTemplateSpace, core.space.Model, { + name: 'Funnels', + description: 'Manage funnel statuses', + members: [], + private: false, + icon: lead.component.TemplatesIcon + }, lead.space.FunnelTemplates) + createKanban(lead.space.DefaultFunnel, async (_class, space, data, id) => { builder.createDoc(_class, space, data, id) return await Promise.resolve() diff --git a/models/lead/src/plugin.ts b/models/lead/src/plugin.ts index 9a9023626f..020d25305f 100644 --- a/models/lead/src/plugin.ts +++ b/models/lead/src/plugin.ts @@ -32,7 +32,8 @@ export default mergeIds(leadId, lead, { CreateLead: '' as AnyComponent, EditLead: '' as AnyComponent, KanbanCard: '' as AnyComponent, - LeadPresenter: '' as AnyComponent + LeadPresenter: '' as AnyComponent, + TemplatesIcon: '' as AnyComponent }, space: { DefaultFunnel: '' as Ref diff --git a/models/recruit/src/index.ts b/models/recruit/src/index.ts index d1ba02cf68..68c6384035 100644 --- a/models/recruit/src/index.ts +++ b/models/recruit/src/index.ts @@ -14,7 +14,7 @@ // import type { Employee } from '@anticrm/contact' -import type { Doc, Domain, FindOptions, Ref, Timestamp } from '@anticrm/core' +import { Doc, Domain, FindOptions, Ref, Timestamp } from '@anticrm/core' import { Builder, Model, Prop, TypeBoolean, TypeDate, TypeRef, TypeString, UX, Collection } from '@anticrm/model' import chunter from '@anticrm/model-chunter' import contact, { TPerson } from '@anticrm/model-contact' @@ -22,7 +22,7 @@ import core, { TAttachedDoc, TDocWithState, TSpace, TSpaceWithStates } from '@an import view from '@anticrm/model-view' import workbench from '@anticrm/model-workbench' import type { IntlString } from '@anticrm/platform' -import type { Applicant, Candidate, Candidates, Vacancy } from '@anticrm/recruit' +import { Applicant, Candidate, Candidates, Vacancy } from '@anticrm/recruit' import recruit from './plugin' import attachment from '@anticrm/model-attachment' @@ -92,8 +92,9 @@ export class TApplicant extends TAttachedDoc implements Applicant { @Prop(TypeRef(contact.class.Employee), 'Assigned recruiter' as IntlString) employee!: Ref | null - // We need this two to make typescript happy. + // We need these to make typescript happy. declare state: TDocWithState['state'] + declare doneState: TDocWithState['doneState'] declare number: TDocWithState['number'] } @@ -239,6 +240,14 @@ export function createModel (builder: Builder): void { attachedTo: recruit.class.Applicant, sequence: 0 }) + + builder.createDoc(view.class.KanbanTemplateSpace, core.space.Model, { + name: 'Vacancies', + description: 'Manage vacancy statuses', + members: [], + private: false, + icon: recruit.component.TemplatesIcon + }, recruit.space.VacancyTemplates) } export { default } from './plugin' diff --git a/models/recruit/src/plugin.ts b/models/recruit/src/plugin.ts index 197dd5eec5..0e1d12cd3c 100644 --- a/models/recruit/src/plugin.ts +++ b/models/recruit/src/plugin.ts @@ -44,7 +44,8 @@ export default mergeIds(recruitId, recruit, { KanbanCard: '' as AnyComponent, ApplicationPresenter: '' as AnyComponent, ApplicationsPresenter: '' as AnyComponent, - EditVacancy: '' as AnyComponent + EditVacancy: '' as AnyComponent, + TemplatesIcon: '' as AnyComponent }, space: { CandidatesPublic: '' as Ref diff --git a/models/setting/src/index.ts b/models/setting/src/index.ts index 41c3d3ea16..48dc387645 100644 --- a/models/setting/src/index.ts +++ b/models/setting/src/index.ts @@ -61,6 +61,12 @@ export function createModel (builder: Builder): void { icon: setting.icon.Integrations, component: setting.component.Integrations }, + { + id: 'statuses', + label: setting.string.ManageStatuses, + icon: setting.icon.Statuses, + component: setting.component.ManageStatuses + }, { id: 'support', label: setting.string.Support, diff --git a/models/task/package.json b/models/task/package.json index d579a0401c..e5bc544482 100644 --- a/models/task/package.json +++ b/models/task/package.json @@ -37,6 +37,7 @@ "@anticrm/model-attachment": "~0.6.0", "@anticrm/task": "~0.6.0", "@anticrm/model-chunter": "~0.6.0", - "@anticrm/workbench": "~0.6.1" + "@anticrm/workbench": "~0.6.1", + "@anticrm/view": "~0.6.0" } } diff --git a/models/task/src/index.ts b/models/task/src/index.ts index 831055d3b0..e87c9ec885 100644 --- a/models/task/src/index.ts +++ b/models/task/src/index.ts @@ -13,6 +13,9 @@ // limitations under the License. // +// To help typescript locate view plugin properly +import type {} from '@anticrm/view' + import attachment from '@anticrm/model-attachment' import type { Employee } from '@anticrm/contact' import contact from '@anticrm/contact' @@ -36,6 +39,7 @@ export class TProject extends TSpaceWithStates implements Project {} export class TTask extends TDoc implements Task { declare number: DocWithState['number'] declare state: DocWithState['state'] + declare doneState: DocWithState['doneState'] @Prop(TypeString(), 'Name' as IntlString) name!: string @@ -139,6 +143,14 @@ export function createModel (builder: Builder): void { members: [] }, task.space.TasksPublic) + builder.createDoc(view.class.KanbanTemplateSpace, core.space.Model, { + name: 'Projects', + description: 'Manage project statuses', + members: [], + private: false, + icon: task.component.TemplatesIcon + }, task.space.ProjectTemplates) + createProjectKanban(task.space.TasksPublic, async (_class, space, data, id) => { builder.createDoc(_class, space, data, id) return await Promise.resolve() diff --git a/models/task/src/plugin.ts b/models/task/src/plugin.ts index c028ae54da..4dcf761814 100644 --- a/models/task/src/plugin.ts +++ b/models/task/src/plugin.ts @@ -31,7 +31,8 @@ export default mergeIds(taskId, task, { CreateTask: '' as AnyComponent, EditTask: '' as AnyComponent, TaskPresenter: '' as AnyComponent, - KanbanCard: '' as AnyComponent + KanbanCard: '' as AnyComponent, + TemplatesIcon: '' as AnyComponent }, string: { Task: '' as IntlString, diff --git a/models/view/src/index.ts b/models/view/src/index.ts index fad6c260d7..e7138e85f4 100644 --- a/models/view/src/index.ts +++ b/models/view/src/index.ts @@ -14,13 +14,30 @@ // import type { IntlString, Asset, Resource } from '@anticrm/platform' -import type { Ref, Class, Space, Doc, Arr, Domain, State } from '@anticrm/core' +import type { Ref, Class, Space, Doc, Arr, Domain, State, DoneState } from '@anticrm/core' import { DOMAIN_MODEL } from '@anticrm/core' -import { Model, Mixin, Builder } from '@anticrm/model' +import { Model, Mixin, Builder, Prop, Collection, TypeString } from '@anticrm/model' import type { AnyComponent } from '@anticrm/ui' -import type { ViewletDescriptor, Viewlet, AttributeEditor, AttributePresenter, KanbanCard, ObjectEditor, Action, ActionTarget, Kanban, Sequence } from '@anticrm/view' +import type { + ViewletDescriptor, + Viewlet, + AttributeEditor, + AttributePresenter, + KanbanCard, + ObjectEditor, + Action, + ActionTarget, + Kanban, + Sequence, + KanbanTemplateSpace, + KanbanTemplate, + StateTemplate, + DoneStateTemplate, + WonStateTemplate, + LostStateTemplate +} from '@anticrm/view' -import core, { TDoc, TClass } from '@anticrm/model-core' +import core, { TDoc, TClass, TSpace, TAttachedDoc } from '@anticrm/model-core' import view from './plugin' @@ -75,11 +92,53 @@ export class TActionTarget extends TDoc implements ActionTarget { @Model(view.class.Kanban, core.class.Doc, DOMAIN_KANBAN) export class TKanban extends TDoc implements Kanban { - attachedTo!: Ref states!: Arr> + doneStates!: Arr> + attachedTo!: Ref order!: Arr> } +@Model(view.class.KanbanTemplateSpace, core.class.Space, DOMAIN_MODEL) +export class TKanbanTemplateSpace extends TSpace implements KanbanTemplateSpace { + icon!: AnyComponent +} + +@Model(view.class.StateTemplate, core.class.AttachedDoc, DOMAIN_KANBAN) +export class TStateTemplate extends TAttachedDoc implements StateTemplate { + @Prop(TypeString(), 'Title' as IntlString) + title!: string + + @Prop(TypeString(), 'Color' as IntlString) + color!: string +} + +@Model(view.class.DoneStateTemplate, core.class.AttachedDoc, DOMAIN_KANBAN) +export class TDoneStateTemplate extends TAttachedDoc implements DoneStateTemplate { + @Prop(TypeString(), 'Title' as IntlString) + title!: string +} + +@Model(view.class.WonStateTemplate, view.class.DoneStateTemplate, DOMAIN_KANBAN) +export class TWonStateTemplate extends TDoneStateTemplate implements WonStateTemplate {} + +@Model(view.class.LostStateTemplate, view.class.DoneStateTemplate, DOMAIN_KANBAN) +export class TLostStateTemplate extends TDoneStateTemplate implements LostStateTemplate {} + +@Model(view.class.KanbanTemplate, core.class.Doc, DOMAIN_KANBAN) +export class TKanbanTemplate extends TDoc implements KanbanTemplate { + @Prop(TypeString(), 'Title' as IntlString) + title!: string + + states!: Arr> + doneStates!: Arr> + + @Prop(Collection(view.class.StateTemplate), 'States' as IntlString) + statesC!: number + + @Prop(Collection(view.class.DoneStateTemplate), 'Done States' as IntlString) + doneStatesC!: number +} + @Model(view.class.Sequence, core.class.Doc, DOMAIN_KANBAN) export class TSequence extends TDoc implements Sequence { attachedTo!: Ref> @@ -87,7 +146,24 @@ export class TSequence extends TDoc implements Sequence { } export function createModel (builder: Builder): void { - builder.createModel(TAttributeEditor, TAttributePresenter, TKanbanCard, TObjectEditor, TViewletDescriptor, TViewlet, TAction, TActionTarget, TKanban, TSequence) + builder.createModel( + TAttributeEditor, + TAttributePresenter, + TKanbanCard, + TObjectEditor, + TViewletDescriptor, + TViewlet, + TAction, + TActionTarget, + TKanban, + TSequence, + TKanbanTemplateSpace, + TStateTemplate, + TDoneStateTemplate, + TWonStateTemplate, + TLostStateTemplate, + TKanbanTemplate + ) builder.mixin(core.class.TypeString, core.class.Class, view.mixin.AttributeEditor, { editor: view.component.StringEditor @@ -125,23 +201,38 @@ export function createModel (builder: Builder): void { presenter: view.component.StatePresenter }) - builder.createDoc(view.class.ViewletDescriptor, core.space.Model, { - label: 'Table' as IntlString, - icon: view.icon.Table, - component: view.component.TableView - }, view.viewlet.Table) + builder.createDoc( + view.class.ViewletDescriptor, + core.space.Model, + { + label: 'Table' as IntlString, + icon: view.icon.Table, + component: view.component.TableView + }, + view.viewlet.Table + ) - builder.createDoc(view.class.ViewletDescriptor, core.space.Model, { - label: 'Kanban' as IntlString, - icon: view.icon.Kanban, - component: view.component.KanbanView - }, view.viewlet.Kanban) + builder.createDoc( + view.class.ViewletDescriptor, + core.space.Model, + { + label: 'Kanban' as IntlString, + icon: view.icon.Kanban, + component: view.component.KanbanView + }, + view.viewlet.Kanban + ) - builder.createDoc(view.class.Action, core.space.Model, { - label: 'Delete' as IntlString, - icon: view.icon.Delete, - action: view.actionImpl.Delete - }, view.action.Delete) + builder.createDoc( + view.class.Action, + core.space.Model, + { + label: 'Delete' as IntlString, + icon: view.icon.Delete, + action: view.actionImpl.Delete + }, + view.action.Delete + ) builder.createDoc(view.class.ActionTarget, core.space.Model, { target: core.class.Doc, @@ -168,3 +259,5 @@ export function createModel (builder: Builder): void { } export default view + +export { viewOperation } from './migration' diff --git a/models/view/src/migration.ts b/models/view/src/migration.ts new file mode 100644 index 0000000000..a08a4d7a43 --- /dev/null +++ b/models/view/src/migration.ts @@ -0,0 +1,54 @@ +// +// 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. +// + +import { TxOperations } from '@anticrm/core' +import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@anticrm/model' +import core from '@anticrm/model-core' +import view from './plugin' + +export const viewOperation: MigrateOperation = { + async migrate (client: MigrationClient): Promise { + + }, + async upgrade (client: MigrationUpgradeClient): Promise { + console.log('View: Performing model upgrades') + + const ops = new TxOperations(client, core.account.System) + const kanbans = (await client.findAll(view.class.Kanban, {})) + .filter((kanban) => kanban.doneStates == null) + + await Promise.all( + kanbans + .map(async (kanban) => { + console.log(`Updating kanban: ${kanban._id}`) + try { + const doneStates = await Promise.all([ + ops.createDoc(core.class.WonState, kanban.space, { + title: 'Won' + }), + ops.createDoc(core.class.LostState, kanban.space, { + title: 'Lost' + }) + ]) + + await ops.updateDoc(kanban._class, kanban.space, kanban._id, { + doneStates + }) + } catch (e) { + console.error(e) + } + })) + } +} diff --git a/models/workbench/package.json b/models/workbench/package.json index 1447fcd4c9..1f991f71fe 100644 --- a/models/workbench/package.json +++ b/models/workbench/package.json @@ -30,6 +30,7 @@ "@anticrm/platform": "~0.6.5", "@anticrm/model-core": "~0.6.0", "@anticrm/workbench": "~0.6.1", - "@anticrm/ui": "~0.6.0" + "@anticrm/ui": "~0.6.0", + "@anticrm/view": "~0.6.0" } } diff --git a/models/workbench/src/index.ts b/models/workbench/src/index.ts index 6472784632..fabb7b9add 100644 --- a/models/workbench/src/index.ts +++ b/models/workbench/src/index.ts @@ -15,13 +15,15 @@ import type { IntlString, Asset } from '@anticrm/platform' import { DOMAIN_MODEL } from '@anticrm/core' -import { Model, Mixin, Builder } from '@anticrm/model' +import { Model, Mixin, Builder, UX } from '@anticrm/model' import type { Application, SpaceView, ViewConfiguration } from '@anticrm/workbench' +import view from '@anticrm/view' import core, { TDoc, TClass } from '@anticrm/model-core' import workbench from './plugin' @Model(workbench.class.Application, core.class.Doc, DOMAIN_MODEL) +@UX('Application' as IntlString) export class TApplication extends TDoc implements Application { label!: IntlString icon!: Asset @@ -35,6 +37,9 @@ export class TSpaceView extends TClass implements SpaceView { export function createModel (builder: Builder): void { builder.createModel(TApplication, TSpaceView) + builder.mixin(workbench.class.Application, core.class.Class, view.mixin.AttributePresenter, { + presenter: workbench.component.ApplicationPresenter + }) } export default workbench diff --git a/models/workbench/src/plugin.ts b/models/workbench/src/plugin.ts index 4acbd2eb7d..39c4430999 100644 --- a/models/workbench/src/plugin.ts +++ b/models/workbench/src/plugin.ts @@ -14,8 +14,11 @@ // import { mergeIds } from '@anticrm/platform' -import {} from '@anticrm/ui' +import { AnyComponent } from '@anticrm/ui' import workbench, { workbenchId } from '@anticrm/workbench' export default mergeIds(workbenchId, workbench, { + component: { + ApplicationPresenter: '' as AnyComponent + } }) diff --git a/packages/core/src/classes.ts b/packages/core/src/classes.ts index bbca5c3907..af9762e656 100644 --- a/packages/core/src/classes.ts +++ b/packages/core/src/classes.ts @@ -229,11 +229,29 @@ export interface State extends Doc { color: string } +/** + * @public + */ +export interface DoneState extends Doc { + title: string +} + +/** + * @public + */ +export interface WonState extends DoneState {} + +/** + * @public + */ +export interface LostState extends DoneState {} + /** * @public */ export interface DocWithState extends Doc { state: Ref + doneState: Ref | null number: number } diff --git a/packages/core/src/component.ts b/packages/core/src/component.ts index b75cd9f2be..faebf1c738 100644 --- a/packages/core/src/component.ts +++ b/packages/core/src/component.ts @@ -14,6 +14,7 @@ // import type { Plugin, StatusCode } from '@anticrm/platform' import { plugin } from '@anticrm/platform' +import { DoneState, LostState, WonState } from '.' import type { Account, AnyAttribute, AttachedDoc, Class, Doc, DocWithState, Interface, Obj, PropertyType, Ref, Space, SpaceWithStates, State, Timestamp, Type, Collection, RefTo } from './classes' import type { Tx, TxBulkWrite, TxCollectionCUD, TxCreateDoc, TxCUD, TxMixin, TxPutBag, TxRemoveDoc, TxUpdateDoc } from './tx' @@ -43,6 +44,9 @@ export default plugin(coreId, { SpaceWithStates: '' as Ref>, Account: '' as Ref>, State: '' as Ref>, + DoneState: '' as Ref>, + WonState: '' as Ref>, + LostState: '' as Ref>, TypeString: '' as Ref>>, TypeBoolean: '' as Ref>>, TypeTimestamp: '' as Ref>>, diff --git a/packages/theme/styles/_layouts.scss b/packages/theme/styles/_layouts.scss index 596edb39cf..583736302b 100644 --- a/packages/theme/styles/_layouts.scss +++ b/packages/theme/styles/_layouts.scss @@ -239,9 +239,15 @@ p:last-child { margin-block-end: 0; } } .h-full { height: 100%; } +.w-full { width: 100%; } .square-36 { width: 2.25rem; height: 2.25rem; } /* --------- */ +.svg-xsmall { + width: 0.857em; + height: 0.857em; +} + .svg-small { width: 1.143em; height: 1.143em; diff --git a/packages/ui/src/components/DumbDropdown.svelte b/packages/ui/src/components/DumbDropdown.svelte new file mode 100644 index 0000000000..bbc55a58db --- /dev/null +++ b/packages/ui/src/components/DumbDropdown.svelte @@ -0,0 +1,200 @@ + + + +
+
+
+ {#if title !== undefined} +
+
+ {/if} +
+ {#if selectedItem?.label !== undefined} + {selectedItem.label} + {:else} +
+
+
+ {#if isOpened} + + {:else} + + {/if} +
+
+ {#if isOpened} +
+
+ {#each items as item (item.id)} +
onItemClick(item.id)}> + {item.label} +
+ {/each} +
+
+ {/if} +
+ + diff --git a/packages/ui/src/components/Icon.svelte b/packages/ui/src/components/Icon.svelte index 4cc5135cef..0e5da4bbbd 100644 --- a/packages/ui/src/components/Icon.svelte +++ b/packages/ui/src/components/Icon.svelte @@ -17,7 +17,7 @@ import { AnySvelteComponent } from '../types'; export let icon: Asset | AnySvelteComponent - export let size: 'small' | 'medium' | 'large' + export let size: 'xsmall' | 'small' | 'medium' | 'large' export let fill = 'currentColor' export let filled: boolean = false @@ -42,6 +42,11 @@ {/if} \ No newline at end of file diff --git a/plugins/lead-resources/src/index.ts b/plugins/lead-resources/src/index.ts index 0824a9e57d..11fe1aad34 100644 --- a/plugins/lead-resources/src/index.ts +++ b/plugins/lead-resources/src/index.ts @@ -20,6 +20,7 @@ import CreateLead from './components/CreateLead.svelte' import EditLead from './components/EditLead.svelte' import KanbanCard from './components/KanbanCard.svelte' import LeadPresenter from './components/LeadPresenter.svelte' +import TemplatesIcon from './components/TemplatesIcon.svelte' export default async (): Promise => ({ component: { @@ -27,6 +28,7 @@ export default async (): Promise => ({ CreateLead, EditLead, KanbanCard, - LeadPresenter + LeadPresenter, + TemplatesIcon } }) diff --git a/plugins/lead/src/index.ts b/plugins/lead/src/index.ts index d3a46b25f7..f46aa669eb 100644 --- a/plugins/lead/src/index.ts +++ b/plugins/lead/src/index.ts @@ -16,8 +16,8 @@ import { plugin } from '@anticrm/platform' import type { Asset, Plugin } from '@anticrm/platform' -import core from '@anticrm/core' -import view, { Kanban } from '@anticrm/view' +import core, { DoneState } from '@anticrm/core' +import view, { Kanban, KanbanTemplateSpace } from '@anticrm/view' import type { Class, Data, Doc, DocWithState, Ref, Space, SpaceWithStates, State } from '@anticrm/core' import type { Contact } from '@anticrm/contact' @@ -51,6 +51,9 @@ export default plugin(leadId, { Funnel: '' as Asset, Lead: '' as Asset, LeadApplication: '' as Asset + }, + space: { + FunnelTemplates: '' as Ref } }) @@ -83,6 +86,23 @@ export async function createKanban ( ) ids.push(sid) } + const rawDoneStates = [ + { class: core.class.WonState, title: 'Won' }, + { class: core.class.LostState, title: 'Lost' } + ] + const doneStates: Array> = [] + for (const st of rawDoneStates) { + const sid = (funnelId + '.done-state.' + st.title.toLowerCase().replace(' ', '_')) as Ref + await factory( + st.class, + funnelId, + { + title: st.title + }, + sid + ) + doneStates.push(sid) + } await factory( view.class.Kanban, @@ -90,6 +110,7 @@ export async function createKanban ( { attachedTo: funnelId, states: ids, + doneStates, order: [] }, (funnelId + '.kanban.') as Ref diff --git a/plugins/recruit-resources/src/components/CreateApplication.svelte b/plugins/recruit-resources/src/components/CreateApplication.svelte index a28762496a..4fa48af811 100644 --- a/plugins/recruit-resources/src/components/CreateApplication.svelte +++ b/plugins/recruit-resources/src/components/CreateApplication.svelte @@ -72,6 +72,7 @@ 'applications', { state: state._id, + doneState: null, number: incResult.object.sequence, employee: employee } diff --git a/plugins/recruit-resources/src/components/CreateVacancy.svelte b/plugins/recruit-resources/src/components/CreateVacancy.svelte index 976f93714a..66362eb2dd 100644 --- a/plugins/recruit-resources/src/components/CreateVacancy.svelte +++ b/plugins/recruit-resources/src/components/CreateVacancy.svelte @@ -15,20 +15,23 @@ @@ -94,5 +64,6 @@ + diff --git a/plugins/recruit-resources/src/components/TemplatesIcon.svelte b/plugins/recruit-resources/src/components/TemplatesIcon.svelte new file mode 100644 index 0000000000..ae229409f7 --- /dev/null +++ b/plugins/recruit-resources/src/components/TemplatesIcon.svelte @@ -0,0 +1,20 @@ + + +
+ +
+ + \ No newline at end of file diff --git a/plugins/recruit-resources/src/components/icons/Vacancy.svelte b/plugins/recruit-resources/src/components/icons/Vacancy.svelte index d355d674b4..109c4844ce 100644 --- a/plugins/recruit-resources/src/components/icons/Vacancy.svelte +++ b/plugins/recruit-resources/src/components/icons/Vacancy.svelte @@ -15,7 +15,7 @@ --> diff --git a/plugins/recruit-resources/src/index.ts b/plugins/recruit-resources/src/index.ts index a595d49c36..86bea5add3 100644 --- a/plugins/recruit-resources/src/index.ts +++ b/plugins/recruit-resources/src/index.ts @@ -24,6 +24,7 @@ import KanbanCard from './components/KanbanCard.svelte' import EditVacancy from './components/EditVacancy.svelte' import ApplicationPresenter from './components/ApplicationPresenter.svelte' import ApplicationsPresenter from './components/ApplicationsPresenter.svelte' +import TemplatesIcon from './components/TemplatesIcon.svelte' import { showPopup } from '@anticrm/ui' import { Resources } from '@anticrm/platform' @@ -45,6 +46,7 @@ export default async (): Promise => ({ KanbanCard, ApplicationPresenter, ApplicationsPresenter, - EditVacancy + EditVacancy, + TemplatesIcon } }) diff --git a/plugins/recruit/package.json b/plugins/recruit/package.json index 65da50561c..85ceffbf46 100644 --- a/plugins/recruit/package.json +++ b/plugins/recruit/package.json @@ -29,6 +29,7 @@ "@anticrm/platform": "~0.6.5", "@anticrm/core": "~0.6.11", "@anticrm/contact": "~0.6.2", - "@anticrm/chunter": "~0.6.0" + "@anticrm/chunter": "~0.6.0", + "@anticrm/view": "~0.6.0" } } diff --git a/plugins/recruit/src/index.ts b/plugins/recruit/src/index.ts index 61ac861529..c7857fbbff 100644 --- a/plugins/recruit/src/index.ts +++ b/plugins/recruit/src/index.ts @@ -17,6 +17,7 @@ import { plugin } from '@anticrm/platform' import type { Plugin, Asset } from '@anticrm/platform' import type { Space, SpaceWithStates, DocWithState, Ref, Class, AttachedDoc, Timestamp } from '@anticrm/core' import type { Employee, Person } from '@anticrm/contact' +import type { KanbanTemplateSpace } from '@anticrm/view' /** * @public @@ -72,5 +73,8 @@ export default plugin(recruitId, { Location: '' as Asset, Calendar: '' as Asset, Create: '' as Asset + }, + space: { + VacancyTemplates: '' as Ref } }) diff --git a/plugins/setting-assets/assets/icons.svg b/plugins/setting-assets/assets/icons.svg index 694a48ad58..9ea8b5f88a 100644 --- a/plugins/setting-assets/assets/icons.svg +++ b/plugins/setting-assets/assets/icons.svg @@ -19,4 +19,9 @@ + + + + + diff --git a/plugins/setting-assets/lang/en.json b/plugins/setting-assets/lang/en.json index d8e09ead80..d2eff32e8b 100644 --- a/plugins/setting-assets/lang/en.json +++ b/plugins/setting-assets/lang/en.json @@ -1,9 +1,14 @@ { "string": { "Setting": "Setting", + "ManageStatuses": "Manage Statuses", "Integrations": "Integrations", "Support": "Support", "Privacy": "Privacy", - "Terms": "Terms" + "Terms": "Terms", + + "Folders": "Folders", + "Templates": "Templates", + "Delete": "Delete" } } \ No newline at end of file diff --git a/plugins/setting-assets/src/index.ts b/plugins/setting-assets/src/index.ts index 274494bc3b..4b9c850d39 100644 --- a/plugins/setting-assets/src/index.ts +++ b/plugins/setting-assets/src/index.ts @@ -20,6 +20,7 @@ const icons = require('../assets/icons.svg') loadMetadata(setting.icon, { Setting: `${icons}#settings`, Integrations: `${icons}#integration`, + Statuses: `${icons}#statuses`, Support: `${icons}#support`, Privacy: `${icons}#privacy`, Terms: `${icons}#terms` diff --git a/plugins/setting-resources/package.json b/plugins/setting-resources/package.json index b0d2d49d80..36539cfc35 100644 --- a/plugins/setting-resources/package.json +++ b/plugins/setting-resources/package.json @@ -35,6 +35,9 @@ "svelte": "^3.37.0", "@anticrm/setting": "~0.6.0", "@anticrm/ui": "~0.6.0", - "@anticrm/presentation": "~0.6.2" + "@anticrm/presentation": "~0.6.2", + "@anticrm/view": "~0.6.0", + "@anticrm/view-resources": "~0.6.0", + "@anticrm/workbench": "~0.6.1" } } diff --git a/plugins/setting-resources/src/components/statuses/Folders.svelte b/plugins/setting-resources/src/components/statuses/Folders.svelte new file mode 100644 index 0000000000..8f74110c2b --- /dev/null +++ b/plugins/setting-resources/src/components/statuses/Folders.svelte @@ -0,0 +1,120 @@ + + + + +
+
+
+
+ {#each folders as f (f._id)} +
select(f)}> +
+ +
+
+
+ {f.name} +
+
+ {f.description} +
+
+
+ {/each} +
+
+ + diff --git a/plugins/setting-resources/src/components/statuses/ManageStatuses.svelte b/plugins/setting-resources/src/components/statuses/ManageStatuses.svelte new file mode 100644 index 0000000000..72fa963723 --- /dev/null +++ b/plugins/setting-resources/src/components/statuses/ManageStatuses.svelte @@ -0,0 +1,125 @@ + + + + +
+
+ +
+
+
+ +
+
+ {#if folder !== undefined} + + {/if} +
+
+ {#if template !== undefined} + deleteState(e.detail)}/> + {/if} +
+
+
+ + diff --git a/plugins/setting-resources/src/components/statuses/Templates.svelte b/plugins/setting-resources/src/components/statuses/Templates.svelte new file mode 100644 index 0000000000..91da0e7123 --- /dev/null +++ b/plugins/setting-resources/src/components/statuses/Templates.svelte @@ -0,0 +1,159 @@ + + + + +
+
+
+
+ {#each templates as t (t._id)} +
select(t)}> + +
{ + showPopup(ContextMenu, { object: t }, ev.target, () => {}) + }} + > + +
+
+ {/each} +
+
+ + diff --git a/plugins/setting-resources/src/index.ts b/plugins/setting-resources/src/index.ts index 4d7084653d..75f9bc8d9e 100644 --- a/plugins/setting-resources/src/index.ts +++ b/plugins/setting-resources/src/index.ts @@ -15,13 +15,15 @@ import Integrations from './components/Integrations.svelte' import ConnectEmail from './components/integrations/ConnectEmail.svelte' +import ManageStatuses from './components/statuses/ManageStatuses.svelte' import IconGmail from './components/icons/Gmail.svelte' export default async () => ({ component: { Integrations, ConnectEmail, - IconGmail + IconGmail, + ManageStatuses }, handler: { EmailDisconnectHandler: async () => {} diff --git a/plugins/setting/src/index.ts b/plugins/setting/src/index.ts index 8982989a1e..c03684c5e8 100644 --- a/plugins/setting/src/index.ts +++ b/plugins/setting/src/index.ts @@ -58,6 +58,7 @@ export default plugin(settingId, { component: { Setting: '' as AnyComponent, Integrations: '' as AnyComponent, + ManageStatuses: '' as AnyComponent, Support: '' as AnyComponent, Privacy: '' as AnyComponent, Terms: '' as AnyComponent, @@ -70,13 +71,18 @@ export default plugin(settingId, { string: { Setting: '' as IntlString, Integrations: '' as IntlString, + ManageStatuses: '' as IntlString, Support: '' as IntlString, Privacy: '' as IntlString, - Terms: '' as IntlString + Terms: '' as IntlString, + Folders: '' as IntlString, + Templates: '' as IntlString, + Delete: '' as IntlString }, icon: { Setting: '' as Asset, Integrations: '' as Asset, + Statuses: '' as Asset, Support: '' as Asset, Privacy: '' as Asset, Terms: '' as Asset diff --git a/plugins/task-resources/src/components/CreateProject.svelte b/plugins/task-resources/src/components/CreateProject.svelte index 49532e7179..4e99560993 100644 --- a/plugins/task-resources/src/components/CreateProject.svelte +++ b/plugins/task-resources/src/components/CreateProject.svelte @@ -13,18 +13,19 @@ // limitations under the License. --> @@ -63,5 +64,6 @@ + diff --git a/plugins/task-resources/src/components/CreateTask.svelte b/plugins/task-resources/src/components/CreateTask.svelte index cc04edda63..08f6551ac7 100644 --- a/plugins/task-resources/src/components/CreateTask.svelte +++ b/plugins/task-resources/src/components/CreateTask.svelte @@ -15,6 +15,7 @@ + +
+ +
+ + \ No newline at end of file diff --git a/plugins/task-resources/src/index.ts b/plugins/task-resources/src/index.ts index c0fcd7d76d..30a67ce4d0 100644 --- a/plugins/task-resources/src/index.ts +++ b/plugins/task-resources/src/index.ts @@ -20,12 +20,14 @@ import CreateTask from './components/CreateTask.svelte' import CreateProject from './components/CreateProject.svelte' import TaskPresenter from './components/TaskPresenter.svelte' import KanbanCard from './components/KanbanCard.svelte' +import TemplatesIcon from './components/TemplatesIcon.svelte' export default async (): Promise => ({ component: { CreateTask, CreateProject, TaskPresenter, - KanbanCard + KanbanCard, + TemplatesIcon } }) diff --git a/plugins/task/src/index.ts b/plugins/task/src/index.ts index d95957ae34..87370a630d 100644 --- a/plugins/task/src/index.ts +++ b/plugins/task/src/index.ts @@ -14,11 +14,11 @@ // import type { Employee } from '@anticrm/contact' -import type { Class, Data, Doc, Ref, Space, State } from '@anticrm/core' +import type { Class, Data, Doc, DocWithState, DoneState, Ref, Space, State } from '@anticrm/core' import type { Asset, Plugin } from '@anticrm/platform' import { plugin } from '@anticrm/platform' import core from '@anticrm/core' -import view, { Kanban } from '@anticrm/view' +import view, { Kanban, KanbanTemplateSpace } from '@anticrm/view' /** * @public @@ -28,7 +28,7 @@ export interface Project extends Space {} /** * @public */ -export interface Task extends Doc { +export interface Task extends DocWithState { number: number // Sequence number name: string @@ -52,6 +52,9 @@ export default plugin(taskId, { }, icon: { Task: '' as Asset + }, + space: { + ProjectTemplates: '' as Ref } }) @@ -84,12 +87,31 @@ export async function createProjectKanban ( ids.push(sid) } + const rawDoneStates = [ + { class: core.class.WonState, title: 'Won' }, + { class: core.class.LostState, title: 'Lost' } + ] + const doneStates: Array> = [] + for (const st of rawDoneStates) { + const sid = (projectId + '.done-state.' + st.title.toLowerCase().replace(' ', '_')) as Ref + await factory( + st.class, + projectId, + { + title: st.title + }, + sid + ) + doneStates.push(sid) + } + await factory( view.class.Kanban, projectId, { attachedTo: projectId, states: ids, + doneStates, order: [] }, (projectId + '.kanban.') as Ref diff --git a/plugins/telegram-resources/src/components/Connect.svelte b/plugins/telegram-resources/src/components/Connect.svelte index 549cd29255..9227065f4a 100644 --- a/plugins/telegram-resources/src/components/Connect.svelte +++ b/plugins/telegram-resources/src/components/Connect.svelte @@ -35,12 +35,16 @@ if (res.next === 'code') { requested = true } + + if (res.next === 'end') { + dispatch('close', { value: phone }) + } } async function sendPassword (): Promise { const res = await sendRequest('/signin/pass', { phone, pass: password }) if (res.next === 'end') { - dispatch('close') + dispatch('close', { value: phone }) } } diff --git a/plugins/view-assets/lang/en.json b/plugins/view-assets/lang/en.json index e7e785221a..d946a2715d 100644 --- a/plugins/view-assets/lang/en.json +++ b/plugins/view-assets/lang/en.json @@ -1,6 +1,7 @@ { "string": { "MoveClass": "Move {class}", - "SelectToMove": "Select the {classLabel} you want to move {class} to." + "SelectToMove": "Select the {classLabel} you want to move {class} to.", + "Delete": "Delete" } -} \ No newline at end of file +} diff --git a/plugins/workbench-resources/src/components/ColorsPopup.svelte b/plugins/view-resources/src/components/ColorsPopup.svelte similarity index 100% rename from plugins/workbench-resources/src/components/ColorsPopup.svelte rename to plugins/view-resources/src/components/ColorsPopup.svelte diff --git a/plugins/view-resources/src/components/KanbanEditor.svelte b/plugins/view-resources/src/components/KanbanEditor.svelte new file mode 100644 index 0000000000..cda2f1f749 --- /dev/null +++ b/plugins/view-resources/src/components/KanbanEditor.svelte @@ -0,0 +1,79 @@ + + + + diff --git a/plugins/view-resources/src/components/KanbanTemplateEditor.svelte b/plugins/view-resources/src/components/KanbanTemplateEditor.svelte new file mode 100644 index 0000000000..c7ddf3df00 --- /dev/null +++ b/plugins/view-resources/src/components/KanbanTemplateEditor.svelte @@ -0,0 +1,103 @@ + + + + diff --git a/plugins/view-resources/src/components/KanbanTemplateSelector.svelte b/plugins/view-resources/src/components/KanbanTemplateSelector.svelte new file mode 100644 index 0000000000..889944ad65 --- /dev/null +++ b/plugins/view-resources/src/components/KanbanTemplateSelector.svelte @@ -0,0 +1,39 @@ + + + + diff --git a/plugins/view-resources/src/components/KanbanView.svelte b/plugins/view-resources/src/components/KanbanView.svelte index afd0dbf8ac..fc8c899517 100644 --- a/plugins/view-resources/src/components/KanbanView.svelte +++ b/plugins/view-resources/src/components/KanbanView.svelte @@ -15,7 +15,7 @@ --> {#await cardPresenter(_class)} {:then presenter} -
- -
- {#each states as state, i (state)} - { - event.preventDefault() - if (dragCard.state !== state._id) { - dragCard.state = state._id - } - }} - on:drop={() => { - move(state._id) - }}> - - {#each objects as object, j (object)} - {#if object.state === state._id} -
{ - dragover(ev, object) - dragCardEndPosition = j - }} - on:drop|preventDefault={() => { - dragCardEndPosition = j - }} - > - { - dragCardInitialState = state._id - dragCardInitialPosition = j - dragCardEndPosition = j - dragCard = objects[j] - }}/> -
- {/if} - {/each} -
- {/each} - +
+
+ +
+ {#each states as state (state)} + { + event.preventDefault() + if (dragCard.state !== state._id) { + dragCard.state = state._id + } + }} + on:drop={() => { + move(state._id) + isDragging = false + }}> + + {#each objects as object, j (object)} + {#if object !== undefined && object.state === state._id} +
{ + dragover(ev, object) + dragCardEndPosition = j + }} + on:drop|preventDefault={() => { + dragCardEndPosition = j + isDragging = false + }} + > + { + dragCardInitialState = state._id + dragCardInitialPosition = j + dragCardEndPosition = j + dragCard = object + isDragging = true + }} + on:dragend={() => { + isDragging = false + }}/> +
+ {/if} + {/each} +
+ {/each} + +
+
+
+ {#if isDragging && wonState !== undefined && lostState !== undefined} +
+
{ + hoveredDoneState = wonState?._id + }} + on:dragleave={() => { + hoveredDoneState = undefined + }} + on:dragover|preventDefault={() => {}} + on:drop={onDone(wonState)}> +
+ {wonState.title} +
+
{ + console.log('enter') + hoveredDoneState = lostState?._id + }} + on:dragleave={() => { + console.log('leave') + hoveredDoneState = undefined + }} + on:dragover|preventDefault={() => {}} + on:drop={onDone(lostState)}> +
+ {lostState.title} +
- + {/if}
{/await} diff --git a/plugins/workbench-resources/src/components/PopupDialog.svelte b/plugins/view-resources/src/components/PopupDialog.svelte similarity index 100% rename from plugins/workbench-resources/src/components/PopupDialog.svelte rename to plugins/view-resources/src/components/PopupDialog.svelte diff --git a/plugins/view-resources/src/components/StatesEditor.svelte b/plugins/view-resources/src/components/StatesEditor.svelte new file mode 100644 index 0000000000..0a1f3546c6 --- /dev/null +++ b/plugins/view-resources/src/components/StatesEditor.svelte @@ -0,0 +1,197 @@ + + + +
+
+
+
+
+ {#each states as state, i} + {#if state} +
{ + dragover(ev, i) + }} + on:drop|preventDefault={() => { + onMove(i) + }} + on:dragstart={() => { + selected = i + dragState = states[i]._id + }} + on:dragend={() => { + selected = undefined + }} + > +
+
{ + showPopup(ColorsPopup, {}, elements[i], onColorChange(state)) + }} + /> +
+
{ + showPopup(StatusesPopup, { onDelete: () => dispatch('delete', { state }) }, ev.target, () => {}) + }} + > + +
+
+ {/if} + {/each} +
+
+
+
+
+
+ {#each wonStates as state, i} + {#if state} +
+
+
+
+
+ {/if} + {/each} +
+
+
+
+
+
+ {#each lostStates as state, i} + {#if state} +
+
+
+
+
+ {/if} + {/each} +
+
+
+ + diff --git a/plugins/view-resources/src/components/StatusesPopup.svelte b/plugins/view-resources/src/components/StatusesPopup.svelte new file mode 100644 index 0000000000..e41959b0cb --- /dev/null +++ b/plugins/view-resources/src/components/StatusesPopup.svelte @@ -0,0 +1,59 @@ + + + + + + + diff --git a/plugins/workbench-resources/src/components/icons/Circles.svelte b/plugins/view-resources/src/components/icons/Circles.svelte similarity index 100% rename from plugins/workbench-resources/src/components/icons/Circles.svelte rename to plugins/view-resources/src/components/icons/Circles.svelte diff --git a/plugins/view-resources/src/index.ts b/plugins/view-resources/src/index.ts index 3416e8fe1b..86ce31f71f 100644 --- a/plugins/view-resources/src/index.ts +++ b/plugins/view-resources/src/index.ts @@ -33,6 +33,9 @@ import { deleteObject } from './utils' import MoveView from './components/Move.svelte' export { default as ContextMenu } from './components/Menu.svelte' +export { default as KanbanEditor } from './components/KanbanEditor.svelte' +export { default as KanbanTemplateEditor } from './components/KanbanTemplateEditor.svelte' +export { default as KanbanTemplateSelector } from './components/KanbanTemplateSelector.svelte' export { buildModel, getActions, getObjectPresenter } from './utils' export { Table } diff --git a/plugins/view/src/index.ts b/plugins/view/src/index.ts index 81398de648..b58a2447ed 100644 --- a/plugins/view/src/index.ts +++ b/plugins/view/src/index.ts @@ -14,9 +14,10 @@ // limitations under the License. // +import core from '@anticrm/core' import type { Plugin, Asset, Resource, IntlString } from '@anticrm/platform' import { plugin } from '@anticrm/platform' -import type { Ref, Mixin, UXObject, Space, FindOptions, Class, Doc, Arr, State, Client, Obj } from '@anticrm/core' +import type { Ref, Mixin, UXObject, Space, FindOptions, Class, Doc, Arr, State, Client, Obj, DoneState, AttachedDoc, WonState, LostState, TxOperations } from '@anticrm/core' import type { AnyComponent, AnySvelteComponent } from '@anticrm/ui' @@ -81,21 +82,60 @@ export interface ActionTarget extends Doc { action: Ref } +/** + * @public + */ +export interface Sequence extends Doc { + attachedTo: Ref> + sequence: number +} + /** * @public */ export interface Kanban extends Doc { attachedTo: Ref states: Arr> + doneStates: Arr> order: Arr> } /** * @public */ -export interface Sequence extends Doc { - attachedTo: Ref> - sequence: number +export interface StateTemplate extends AttachedDoc, State {} + +/** + * @public + */ +export interface DoneStateTemplate extends AttachedDoc, DoneState {} + +/** + * @public + */ +export interface WonStateTemplate extends DoneStateTemplate, WonState {} + +/** + * @public + */ +export interface LostStateTemplate extends DoneStateTemplate, LostState {} + +/** + * @public + */ +export interface KanbanTemplate extends Doc { + title: string + states: Arr> + doneStates: Arr> + statesC: number + doneStatesC: number +} + +/** + * @public + */ +export interface KanbanTemplateSpace extends Space { + icon: AnyComponent } /** @@ -134,7 +174,10 @@ export interface BuildModelOptions { ignoreMissing?: boolean } -export default plugin(viewId, { +/** + * @public + */ +const view = plugin(viewId, { mixin: { AttributeEditor: '' as Ref>, AttributePresenter: '' as Ref>, @@ -147,7 +190,13 @@ export default plugin(viewId, { Action: '' as Ref>, ActionTarget: '' as Ref>, Kanban: '' as Ref>, - Sequence: '' as Ref> + Sequence: '' as Ref>, + StateTemplate: '' as Ref>, + DoneStateTemplate: '' as Ref>, + WonStateTemplate: '' as Ref>, + LostStateTemplate: '' as Ref>, + KanbanTemplate: '' as Ref>, + KanbanTemplateSpace: '' as Ref> }, viewlet: { Table: '' as Ref, @@ -161,5 +210,71 @@ export default plugin(viewId, { Kanban: '' as Asset, Delete: '' as Asset, Move: '' as Asset + }, + string: { + Delete: '' as IntlString } }) +export default view + +/** + * @public + */ +export async function createKanban (client: Client & TxOperations, attachedTo: Ref, templateId?: Ref): Promise> { + if (templateId === undefined) { + return await client.createDoc(view.class.Kanban, attachedTo, { + attachedTo, + states: [], + doneStates: await Promise.all([ + client.createDoc(core.class.WonState, attachedTo, { + title: 'Won' + }), + client.createDoc(core.class.LostState, attachedTo, { + title: 'Lost' + }) + ]), + order: [] + }) + } + + const template = await client.findOne(view.class.KanbanTemplate, { _id: templateId }) + + if (template === undefined) { + throw Error(`Failed to find target kanban template: ${templateId}`) + } + + const tmplStates = await client.findAll(view.class.StateTemplate, { attachedTo: template._id }) + const states = await Promise.all( + template.states + .map((id) => tmplStates.find((x) => x._id === id)) + .filter((tstate): tstate is StateTemplate => tstate !== undefined) + .map(async (state) => await client.createDoc(core.class.State, attachedTo, { color: state.color, title: state.title })) + ) + + const doneClassMap = new Map>, Ref>>([ + [view.class.WonStateTemplate, core.class.WonState], + [view.class.LostStateTemplate, core.class.LostState] + ]) + const tmplDoneStates = await client.findAll(view.class.DoneStateTemplate, { attachedTo: template._id }) + const doneStates = (await Promise.all( + template.doneStates + .map((id) => tmplDoneStates.find((x) => x._id === id)) + .filter((tstate): tstate is DoneStateTemplate => tstate !== undefined) + .map(async (state) => { + const cl = doneClassMap.get(state._class) + + if (cl === undefined) { + return + } + + return await client.createDoc(cl, attachedTo, { title: state.title }) + }) + )).filter((x): x is Ref => x !== undefined) + + return await client.createDoc(view.class.Kanban, attachedTo, { + attachedTo, + states, + doneStates, + order: [] + }) +} diff --git a/plugins/workbench-resources/package.json b/plugins/workbench-resources/package.json index 46b4666172..cee8c4060a 100644 --- a/plugins/workbench-resources/package.json +++ b/plugins/workbench-resources/package.json @@ -40,6 +40,7 @@ "@anticrm/presentation": "~0.6.2", "@anticrm/login": "~0.6.1", "@anticrm/setting": "~0.6.0", - "@anticrm/contact": "~0.6.2" + "@anticrm/contact": "~0.6.2", + "@anticrm/view-resources": "~0.6.0" } } diff --git a/plugins/workbench-resources/src/components/ApplicationPresenter.svelte b/plugins/workbench-resources/src/components/ApplicationPresenter.svelte new file mode 100644 index 0000000000..f1c303a548 --- /dev/null +++ b/plugins/workbench-resources/src/components/ApplicationPresenter.svelte @@ -0,0 +1,34 @@ + + + + +
+ +
+ + + \ No newline at end of file diff --git a/plugins/workbench-resources/src/components/EditStatuses.svelte b/plugins/workbench-resources/src/components/EditStatuses.svelte index 0603c8cfb2..147e700912 100644 --- a/plugins/workbench-resources/src/components/EditStatuses.svelte +++ b/plugins/workbench-resources/src/components/EditStatuses.svelte @@ -15,16 +15,16 @@ --> @@ -133,46 +90,9 @@
dispatch('close')}>
-
-
-
- {#each states as state, i} - {#if state} -
{ - dragover(ev, i) - }} - on:drop|preventDefault={() => { - move(i) - }} - on:dragstart={() => { - dragStateInitialPosition = selected = i - dragState = states[i]._id - }} - on:dragend={() => { - selected = undefined - }} - > -
-
{ - showPopup(ColorsPopup, {}, elements[i], onColorChange(state)) - }} - /> -
-
{ - showPopup(StatusesPopup, { kanban, state, spaceClass }, ev.target, (result) => { if (result) console.log('StatusesPopup:', result) }) - }} - > - -
-
- {/if} - {/each} -
+ {#if kanban !== undefined} + deleteState(e.detail)} /> + {/if}
@@ -208,39 +128,9 @@ cursor: pointer; } } - .content { margin: 1rem 2.5rem 1rem 2.5rem; } + .content { + overflow: auto; + margin: 1rem 2.5rem 1rem 2.5rem; + } } - - .states { - padding: .625rem 1rem; - color: #fff; - background-color: rgba(67, 67, 72, .3); - border: 1px solid var(--theme-bg-accent-color); - border-radius: .75rem; - user-select: none; - - &-header { - margin-bottom: 1rem; - font-weight: 600; - font-size: .75rem; - color: var(--theme-content-trans-color); - } - - .bar { - margin-right: .375rem; - width: .375rem; - height: 1rem; - opacity: .4; - cursor: grabbing; - } - .color { - margin-right: .75rem; - width: 1rem; - height: 1rem; - border-radius: .25rem; - cursor: pointer; - } - .tool { margin-left: 1rem; } - } - .states + .states { margin-top: .5rem; } diff --git a/plugins/workbench-resources/src/components/StatusesPopup.svelte b/plugins/workbench-resources/src/components/StatusesPopup.svelte deleted file mode 100644 index 1602711c92..0000000000 --- a/plugins/workbench-resources/src/components/StatusesPopup.svelte +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - diff --git a/plugins/workbench-resources/src/components/Workbench.svelte b/plugins/workbench-resources/src/components/Workbench.svelte index 05683aa7f6..35978b3de4 100644 --- a/plugins/workbench-resources/src/components/Workbench.svelte +++ b/plugins/workbench-resources/src/components/Workbench.svelte @@ -170,6 +170,7 @@ height: 100%; border-radius: 1.25rem; background-color: var(--theme-bg-color); + overflow: hidden; } } diff --git a/plugins/workbench-resources/src/index.ts b/plugins/workbench-resources/src/index.ts index af238bd441..42089bc005 100644 --- a/plugins/workbench-resources/src/index.ts +++ b/plugins/workbench-resources/src/index.ts @@ -14,6 +14,7 @@ // import WorkbenchApp from './components/WorkbenchApp.svelte' +import ApplicationPresenter from './components/ApplicationPresenter.svelte' /*! * Anticrm Platform™ Workbench Plugin @@ -22,6 +23,7 @@ import WorkbenchApp from './components/WorkbenchApp.svelte' */ export default async () => ({ component: { - WorkbenchApp + WorkbenchApp, + ApplicationPresenter } })