Tasks as Attached Documents (#539)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2021-12-15 16:04:43 +07:00 committed by GitHub
parent 78f4916b6d
commit e958388e4c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
113 changed files with 1238 additions and 898 deletions

View File

@ -51,7 +51,7 @@ specifiers:
'@rush-temp/model-server-chunter': file:./projects/model-server-chunter.tgz
'@rush-temp/model-server-core': file:./projects/model-server-core.tgz
'@rush-temp/model-server-recruit': file:./projects/model-server-recruit.tgz
'@rush-temp/model-server-view': file:./projects/model-server-view.tgz
'@rush-temp/model-server-task': file:./projects/model-server-task.tgz
'@rush-temp/model-setting': file:./projects/model-setting.tgz
'@rush-temp/model-task': file:./projects/model-task.tgz
'@rush-temp/model-telegram': file:./projects/model-telegram.tgz
@ -74,8 +74,8 @@ specifiers:
'@rush-temp/server-core': file:./projects/server-core.tgz
'@rush-temp/server-recruit': file:./projects/server-recruit.tgz
'@rush-temp/server-recruit-resources': file:./projects/server-recruit-resources.tgz
'@rush-temp/server-view': file:./projects/server-view.tgz
'@rush-temp/server-view-resources': file:./projects/server-view-resources.tgz
'@rush-temp/server-task': file:./projects/server-task.tgz
'@rush-temp/server-task-resources': file:./projects/server-task-resources.tgz
'@rush-temp/server-ws': file:./projects/server-ws.tgz
'@rush-temp/setting': file:./projects/setting.tgz
'@rush-temp/setting-assets': file:./projects/setting-assets.tgz
@ -220,7 +220,7 @@ dependencies:
'@rush-temp/model-server-chunter': file:projects/model-server-chunter.tgz_typescript@4.4.3
'@rush-temp/model-server-core': file:projects/model-server-core.tgz_typescript@4.4.3
'@rush-temp/model-server-recruit': file:projects/model-server-recruit.tgz_typescript@4.4.3
'@rush-temp/model-server-view': file:projects/model-server-view.tgz_typescript@4.4.3
'@rush-temp/model-server-task': file:projects/model-server-task.tgz_typescript@4.4.3
'@rush-temp/model-setting': file:projects/model-setting.tgz_typescript@4.4.3
'@rush-temp/model-task': file:projects/model-task.tgz_typescript@4.4.3
'@rush-temp/model-telegram': file:projects/model-telegram.tgz_typescript@4.4.3
@ -243,8 +243,8 @@ dependencies:
'@rush-temp/server-core': file:projects/server-core.tgz
'@rush-temp/server-recruit': file:projects/server-recruit.tgz
'@rush-temp/server-recruit-resources': file:projects/server-recruit-resources.tgz
'@rush-temp/server-view': file:projects/server-view.tgz
'@rush-temp/server-view-resources': file:projects/server-view-resources.tgz
'@rush-temp/server-task': file:projects/server-task.tgz
'@rush-temp/server-task-resources': file:projects/server-task-resources.tgz
'@rush-temp/server-ws': file:projects/server-ws.tgz
'@rush-temp/setting': file:projects/setting.tgz
'@rush-temp/setting-assets': file:projects/setting-assets.tgz
@ -10588,7 +10588,7 @@ packages:
dev: false
file:projects/generator.tgz:
resolution: {integrity: sha512-vufLNo3Nd5EZMfZnpDyCgzmtiOxuuOCfdcez1VUXR8EJV+07YGGLKpYcUsJ+hp7vrOMe5/iW0gGun4p42SEL9Q==, tarball: file:projects/generator.tgz}
resolution: {integrity: sha512-Difehi/KDbulPB9s3UOP1fSeLfvWQwpSUr77OnMF31EoCLAXT+9ugFduvlveFdnYCttYKpgGO2bvaxBaH7krdA==, tarball: file:projects/generator.tgz}
name: '@rush-temp/generator'
version: 0.0.0
dependencies:
@ -10633,7 +10633,7 @@ packages:
dev: false
file:projects/lead-resources.tgz_476f694f64637160ae71e12ff57815b9:
resolution: {integrity: sha512-o80Jqqi39oV9wTk/nhjlU53bHy92gsqqCQo1nWTsPLlMB+R1nYY5/Yw/GFxsuVHSzbzjmahUnkHIOxltJMSTig==, tarball: file:projects/lead-resources.tgz}
resolution: {integrity: sha512-Bko+x/XDsWFrhZ4EfXCZ8GwS56DMgWA5t2tYGyPF7nlzIPY97oJWPZGc2z1T7olyx0bye+Od9Vk7qmZI3KuBkQ==, tarball: file:projects/lead-resources.tgz}
id: file:projects/lead-resources.tgz
name: '@rush-temp/lead-resources'
version: 0.0.0
@ -10668,7 +10668,7 @@ packages:
dev: false
file:projects/lead.tgz:
resolution: {integrity: sha512-fdeirdweHpZ+WcGz5xlKcvue+gtDXhK5WNFINY6XnhVgfUvNsEtEgiWX1RaQ8uSReXbaEok6GGmhsV29SOr1lQ==, tarball: file:projects/lead.tgz}
resolution: {integrity: sha512-M28QADdDu2RRVXMqicIrIf8ithwSkKwdxaOe2HZJrvO+K8wXarOtFKkFf8Hg0Kqjtf6oYkS7RZMQhnwf3Fe94Q==, tarball: file:projects/lead.tgz}
name: '@rush-temp/lead'
version: 0.0.0
dependencies:
@ -10770,7 +10770,7 @@ packages:
dev: false
file:projects/model-all.tgz_typescript@4.4.3:
resolution: {integrity: sha512-nbSO6SqE93R4cFPMUkIU19Nn5ZAMItWdQ5+7++7lBjqpJUtUxxgWsIPHjk1wmDmdu4LjPq5F5UruZwQQgu7wJw==, tarball: file:projects/model-all.tgz}
resolution: {integrity: sha512-XyhGIWfnSxUKXSXQ/yfvCx+S5xqBMJ4eko1pDFv0v048sQJy6fefxg3LOLyCDodNzsa5KxGdqvk7Y8H7Izop0g==, tarball: file:projects/model-all.tgz}
id: file:projects/model-all.tgz
name: '@rush-temp/model-all'
version: 0.0.0
@ -10900,7 +10900,7 @@ packages:
dev: false
file:projects/model-lead.tgz_typescript@4.4.3:
resolution: {integrity: sha512-qEMZ6aUEiEZgFvIMcqR8lTVkKKiUNM4eAa5OeLjnDmY1TkFF60EJpnE0e2D1SzO8zPdS+cDH+jT9FqRvXtkO9A==, tarball: file:projects/model-lead.tgz}
resolution: {integrity: sha512-3+Wf+/TdMpbuSoiTaI/ga+1KHSsPd1q9MDtpG6i4oAhxt/48aTZnfOV4nt9hrpWy2MmQWVhK9YJFTaKGIgKwOA==, tarball: file:projects/model-lead.tgz}
id: file:projects/model-lead.tgz
name: '@rush-temp/model-lead'
version: 0.0.0
@ -10921,7 +10921,7 @@ packages:
dev: false
file:projects/model-recruit.tgz_typescript@4.4.3:
resolution: {integrity: sha512-LgqXtVecQ6ZdlAb4ufvWI+kDIeeMFtm/5mXvUXBbGsmg07rCa4tsqfm9uwecj6TvpaPYC3mlyYv/N316VeKeGA==, tarball: file:projects/model-recruit.tgz}
resolution: {integrity: sha512-wIeXWWUD1Lwg8R43hEvmJzZ/ooN4DWdqv1+2vPFDZU/1xKwlW7Gqu1hZM5qUUVHbLLgMOmHsHDscPfn7tB11UQ==, tarball: file:projects/model-recruit.tgz}
id: file:projects/model-recruit.tgz
name: '@rush-temp/model-recruit'
version: 0.0.0
@ -11023,10 +11023,10 @@ packages:
- typescript
dev: false
file:projects/model-server-view.tgz_typescript@4.4.3:
resolution: {integrity: sha512-1LEHcc6xr1A+plRNPtWBHEp8RooarT/XXKfovuqvHugwdjz9ddTHYThxrQcZFuUMya+F1mBgOHdohjYikWZnXQ==, tarball: file:projects/model-server-view.tgz}
id: file:projects/model-server-view.tgz
name: '@rush-temp/model-server-view'
file:projects/model-server-task.tgz_typescript@4.4.3:
resolution: {integrity: sha512-FZRz0sa1wBabVBBZietKGBDDF6vrJzSH6bcWlm8K3OkLl9jHoZlXRjBHIx4f0Vo1DSIPSH++/gULbgNAsd2p1A==, tarball: file:projects/model-server-task.tgz}
id: file:projects/model-server-task.tgz
name: '@rush-temp/model-server-task'
version: 0.0.0
dependencies:
'@rushstack/heft': 0.41.1
@ -11339,7 +11339,7 @@ packages:
dev: false
file:projects/prod.tgz_sass@1.42.1+typescript@4.4.3:
resolution: {integrity: sha512-LFYJVCBzP4R4RpgdxrsiolRiVKAZn2F5mKKb0prXnYIKrRVKLlMkPexB7bnNbg5KG0NBey6WZoxek6/cQ1xk6w==, tarball: file:projects/prod.tgz}
resolution: {integrity: sha512-jgSulZmT4Kq28boQo8BJQrdkcN660EtY7XUH0QWcgGYHDBXFiGfP4eoIxAJL4wtb/JKwKLEkwG+SwTUIuIb7Xw==, tarball: file:projects/prod.tgz}
id: file:projects/prod.tgz
name: '@rush-temp/prod'
version: 0.0.0
@ -11413,7 +11413,7 @@ packages:
dev: false
file:projects/recruit-resources.tgz_476f694f64637160ae71e12ff57815b9:
resolution: {integrity: sha512-+AEmp4tNJEHvQatbY5fN1niGYpaIyWMPXbo0QRhUDELdEkm0nhevUfw2Cf44Yj0DroXL+S0YgnXcEI7AtV2QOQ==, tarball: file:projects/recruit-resources.tgz}
resolution: {integrity: sha512-DtOMgqpR+/A09g0gIsXhEy9/GmgTcIEypvvZ53m//Q4YVYXG8/u82bpE6Na74+g4FyQ+S9FaNwMXH2Da1WDnwQ==, tarball: file:projects/recruit-resources.tgz}
id: file:projects/recruit-resources.tgz
name: '@rush-temp/recruit-resources'
version: 0.0.0
@ -11450,7 +11450,7 @@ packages:
dev: false
file:projects/recruit.tgz:
resolution: {integrity: sha512-nqrkga8ccMV8sqQ6O4ta9qEdvHkgEChdViELXUXViGldrhz6qi9UAE1aDuVYHgEFRX+eC+OACSME0YpHbibylw==, tarball: file:projects/recruit.tgz}
resolution: {integrity: sha512-vvQ892DoJCDRaRuVWx+DBpuKPtCic0O0PdgPDtbKxl+pWk31MVdEhO6m+ySpqkPsu297ot/D7lpYXcK/775fow==, tarball: file:projects/recruit.tgz}
name: '@rush-temp/recruit'
version: 0.0.0
dependencies:
@ -11573,9 +11573,9 @@ packages:
- supports-color
dev: false
file:projects/server-view-resources.tgz:
resolution: {integrity: sha512-8Gg8dO9C0bLtdRQ08F7Yegi8o4Y/VwljPEHRevt7Z6+SpLrpBcbouBgdIaiELK+oXQiPfZWdl3Fwyhd54YJGQQ==, tarball: file:projects/server-view-resources.tgz}
name: '@rush-temp/server-view-resources'
file:projects/server-task-resources.tgz:
resolution: {integrity: sha512-Y2B9G8w4IpTnfIoDV+LtiARLb568CfTKG28VyMc2faS4QUz62f3PLU+ODB8NxwG5f1LsJf8DLrZM8fPRzYdc6w==, tarball: file:projects/server-task-resources.tgz}
name: '@rush-temp/server-task-resources'
version: 0.0.0
dependencies:
'@rushstack/heft': 0.41.1
@ -11593,9 +11593,9 @@ packages:
- supports-color
dev: false
file:projects/server-view.tgz:
resolution: {integrity: sha512-7VfwkR2Yqg0aeuFS+1MWOhCqtuiQBXFqoyxmflQ5+cigwTpJko06NkKnFlGFUW3V+XCxZH4i36PQJod2MXXrng==, tarball: file:projects/server-view.tgz}
name: '@rush-temp/server-view'
file:projects/server-task.tgz:
resolution: {integrity: sha512-qaB/cZSAPKuvTUfBv5wsqD0r/YoQO/hy7rD7WBC0lx2TOWIp0/aacYKVkEltinbYjJzP4gg9cQQNful4Mvpp5Q==, tarball: file:projects/server-task.tgz}
name: '@rush-temp/server-task'
version: 0.0.0
dependencies:
'@rushstack/heft': 0.41.1
@ -11641,7 +11641,7 @@ packages:
dev: false
file:projects/server.tgz:
resolution: {integrity: sha512-V8XWLBzilsZ/5Nuh52ZM8D02XsIGYuOHKhu6q+q7ERPCRm/q3GTMzcZ3WpE3A3ngtFDxHIZJMS314gK50XKM6g==, tarball: file:projects/server.tgz}
resolution: {integrity: sha512-fFYknnDfAOz+iq8INHKSgCjYUkbxqLiHIBM55W8QtmZq0R8hMLEwCcLiyNqL1iCgXKs8sGHf+aMbZcciBNi1Ow==, tarball: file:projects/server.tgz}
name: '@rush-temp/server'
version: 0.0.0
dependencies:
@ -11673,7 +11673,7 @@ packages:
dev: false
file:projects/setting-resources.tgz_476f694f64637160ae71e12ff57815b9:
resolution: {integrity: sha512-Ybi5GrbbI/5SMBIe5W9Ub8cwA3wIKPCj98lSJ+Z9YCokpjYn2dGhRJ5LeBKBPu8LDWPfxlrU2Yi17YcHu0xufw==, tarball: file:projects/setting-resources.tgz}
resolution: {integrity: sha512-r/wZZM67R0JwXnUUHxlo5gS1wshjqgf6qJx/fBKfe5d59ZqIXvLD41NhkNBwA7iQsRd+QNmCS4u1fNtCjl20dQ==, tarball: file:projects/setting-resources.tgz}
id: file:projects/setting-resources.tgz
name: '@rush-temp/setting-resources'
version: 0.0.0
@ -11734,7 +11734,7 @@ packages:
dev: false
file:projects/task-resources.tgz_e1367da94684b005adf08f025c517b1a:
resolution: {integrity: sha512-8q7drsS3cuBhtj4PIl+/QIH8QDt0fZWk89fH3FxY/3Oog8Xd5dGkBH4YdPHUSyk9k5u9iS/P8OwvR9CKMBmxRg==, tarball: file:projects/task-resources.tgz}
resolution: {integrity: sha512-s/MtNSr/ERDwItbtGtcaE+nnTepUKUsuD2SrtGcpgYUXT3XarZyVaGHC4Q9ol6Ok7RajfDSdAj1ZD8V2LLn3Pw==, tarball: file:projects/task-resources.tgz}
id: file:projects/task-resources.tgz
name: '@rush-temp/task-resources'
version: 0.0.0
@ -11769,7 +11769,7 @@ packages:
dev: false
file:projects/task.tgz:
resolution: {integrity: sha512-Yi88o15ExlXV9xqFXzpmi4I5OjnnY7l6SiQasIC7HqKc5/lG4KFvw/dlZHsFnEbRw3Zxa+laq9MejSMqLEzEVw==, tarball: file:projects/task.tgz}
resolution: {integrity: sha512-H61a9iYDCDExaqx08I8pBpSZj2gDyDvF+K+R/QjML1YouYeUa1M1UPpjfz4DHwT7a67TJCasvqJ9nh0/ZNvcDw==, tarball: file:projects/task.tgz}
name: '@rush-temp/task'
version: 0.0.0
dependencies:

View File

@ -18,7 +18,7 @@ services:
volumes:
- files:/data
elastic:
image: 'elasticsearch:7.14.0'
image: 'elasticsearch:7.14.2'
command: |
/bin/sh -c "./bin/elasticsearch-plugin list | grep -q ingest-attachment || yes | ./bin/elasticsearch-plugin install --silent ingest-attachment;
/usr/local/bin/docker-entrypoint.sh eswrapper"

View File

@ -58,7 +58,7 @@
"@anticrm/attachment": "~0.6.1",
"minio": "^7.0.19",
"@types/pdfkit": "~0.12.3",
"@anticrm/view": "~0.6.0",
"@anticrm/task": "~0.6.0",
"jpeg-js": "~0.4.3"
}
}

View File

@ -1,6 +1,7 @@
import core, { DoneState, Ref, SpaceWithStates, State, TxOperations } from '@anticrm/core'
import { Ref, TxOperations } from '@anticrm/core'
import task, { DoneState, Kanban, SpaceWithStates, State } from '@anticrm/task'
import { findOrUpdate } from './utils'
import view, { Kanban } from '@anticrm/view'
export async function createUpdateSpaceKanban (spaceId: Ref<SpaceWithStates>, client: TxOperations): Promise<Ref<State>[]> {
const rawStates = [
@ -13,7 +14,7 @@ export async function createUpdateSpaceKanban (spaceId: Ref<SpaceWithStates>, cl
const states: Array<Ref<State>> = []
for (const st of rawStates) {
const sid = ('generated-' + spaceId + '.state.' + st.name.toLowerCase().replace(' ', '_')) as Ref<State>
await findOrUpdate(client, spaceId, core.class.State,
await findOrUpdate(client, spaceId, task.class.State,
sid,
{
title: st.name,
@ -24,8 +25,8 @@ export async function createUpdateSpaceKanban (spaceId: Ref<SpaceWithStates>, cl
}
const rawDoneStates = [
{ class: core.class.WonState, title: 'Won' },
{ class: core.class.LostState, title: 'Lost' }
{ class: task.class.WonState, title: 'Won' },
{ class: task.class.LostState, title: 'Lost' }
]
const doneStates: Array<Ref<DoneState>> = []
for (const st of rawDoneStates) {
@ -40,7 +41,7 @@ export async function createUpdateSpaceKanban (spaceId: Ref<SpaceWithStates>, cl
}
await findOrUpdate(client, spaceId,
view.class.Kanban,
task.class.Kanban,
('generated-' + spaceId + '.kanban') as Ref<Kanban>,
{
attachedTo: spaceId,

View File

@ -98,7 +98,7 @@ export async function generateContacts (transactorUrl: string, dbName: string, o
const applicant: AttachedData<Applicant> = {
number: faker.datatype.number(),
employee: faker.random.arrayElement(emoloyeeIds),
assignee: faker.random.arrayElement(emoloyeeIds),
state: faker.random.arrayElement(states),
doneState: null
}

View File

@ -71,8 +71,8 @@
"@anticrm/contact-assets": "~0.6.0",
"@anticrm/server-recruit": "~0.6.1",
"@anticrm/server-recruit-resources": "~0.6.0",
"@anticrm/server-view": "~0.6.0",
"@anticrm/server-view-resources": "~0.6.0",
"@anticrm/server-task": "~0.6.0",
"@anticrm/server-task-resources": "~0.6.0",
"@anticrm/activity": "~0.6.0",
"@anticrm/activity-assets": "~0.6.0",
"@anticrm/activity-resources": "~0.6.0",

View File

@ -19,7 +19,7 @@ import login from '@anticrm/login'
import { clientId } from '@anticrm/client'
import { serverChunterId } from '@anticrm/server-chunter'
import { serverRecruitId } from '@anticrm/server-recruit'
import { serverViewId } from '@anticrm/server-view'
import { serverViewId } from '@anticrm/server-task'
import { setMetadata } from '@anticrm/platform'
@ -33,7 +33,7 @@ export function configurePlatformDev() {
addLocation(clientId, () => import(/* webpackChunkName: "client-dev" */ '@anticrm/dev-client-resources'))
addLocation(serverChunterId, () => import(/* webpackChunkName: "server-chunter" */ '@anticrm/dev-server-chunter-resources'))
addLocation(serverRecruitId, () => import(/* webpackChunkName: "server-recruit" */ '@anticrm/server-recruit-resources'))
addLocation(serverViewId, () => import(/* webpackChunkName: "server-view" */ '@anticrm/server-view-resources'))
addLocation(serverViewId, () => import(/* webpackChunkName: "server-task" */ '@anticrm/server-task-resources'))
// Set devmodel to hook client to be able to present all activity
enableDevModel()

View File

@ -69,4 +69,22 @@ export class MigrateClientImpl implements MigrationClient {
return { matched: result.matchedCount, updated: result.modifiedCount }
}
}
async move <T extends Doc>(sourceDomain: Domain, query: DocumentQuery<T>, targetDomain: Domain): Promise<MigrationResult> {
const q = this.translateQuery(query)
const cursor = this.db.collection(sourceDomain).find<T>(q)
const target = this.db.collection(targetDomain)
const result: MigrationResult = {
matched: 0,
updated: 0
}
let doc: Document | null
while ((doc = await cursor.next()) != null) {
await target.insertOne(doc)
result.matched++
result.updated++
}
await this.db.collection(sourceDomain).deleteMany(q)
return result
}
}

View File

@ -43,7 +43,7 @@
"@anticrm/model-server-core": "~0.6.0",
"@anticrm/model-server-chunter": "~0.6.0",
"@anticrm/model-server-recruit": "~0.6.0",
"@anticrm/model-server-view": "~0.6.0",
"@anticrm/model-server-task": "~0.6.0",
"@anticrm/model-activity": "~0.6.0",
"@anticrm/model-attachment": "~0.6.0",
"@anticrm/core": "~0.6.13"

View File

@ -30,7 +30,7 @@ import { createModel as leadModel } from '@anticrm/model-lead'
import { createModel as serverCoreModel } from '@anticrm/model-server-core'
import { createModel as serverChunterModel } from '@anticrm/model-server-chunter'
import { createModel as serverRecruitModel } from '@anticrm/model-server-recruit'
import { createModel as serverViewModel } from '@anticrm/model-server-view'
import { createModel as serverViewModel } from '@anticrm/model-server-task'
import { createModel as activityModel } from '@anticrm/model-activity'
import { createDemo } from '@anticrm/model-demo'

View File

@ -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, TDoneState, TWonState, TLostState, TSpaceWithStates, TDocWithState } from './security'
import { TSpace, TAccount } from './security'
import { TTx, TTxCreateDoc, TTxMixin, TTxUpdateDoc, TTxCUD, TTxPutBag, TTxRemoveDoc, TTxBulkWrite, TTxCollectionCUD } from './tx'
export * from './core'
@ -41,8 +41,6 @@ export function createModel (builder: Builder): void {
TTxRemoveDoc,
TTxBulkWrite,
TSpace,
TDocWithState,
TSpaceWithStates,
TAccount,
TAttribute,
TType,
@ -51,10 +49,6 @@ export function createModel (builder: Builder): void {
TTypeTimestamp,
TRefTo,
TCollection,
TTypeDate,
TState,
TDoneState,
TWonState,
TLostState
TTypeDate
)
}

View File

@ -13,15 +13,13 @@
// limitations under the License.
//
import type { Account, Arr, Domain, Ref, Space, State, DoneState, WonState, LostState } from '@anticrm/core'
import type { Account, Arr, Ref, Space } from '@anticrm/core'
import { DOMAIN_MODEL } from '@anticrm/core'
import { Implements, Model, Prop, TypeBoolean, TypeRef, TypeString } from '@anticrm/model'
import { Model, Prop, TypeBoolean, TypeString } from '@anticrm/model'
import type { IntlString } from '@anticrm/platform'
import core from './component'
import { TDoc } from './core'
export const DOMAIN_STATE = 'state' as Domain
// S P A C E
@Model(core.class.Space, core.class.Doc, DOMAIN_MODEL)
@ -42,39 +40,3 @@ export class TSpace extends TDoc implements Space {
export class TAccount extends TDoc implements Account {
email!: string
}
@Model(core.class.State, core.class.Doc, DOMAIN_STATE)
export class TState extends TDoc implements State {
@Prop(TypeString(), 'Title' as IntlString)
title!: string
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<State>
@Prop(TypeRef(core.class.DoneState), 'Done Status' as IntlString)
doneState!: Ref<DoneState> | null
@Prop(TypeString(), 'No.' as IntlString)
number!: number
}
@Model(core.class.SpaceWithStates, core.class.Space)
export class TSpaceWithStates extends TSpace {
}

View File

@ -38,6 +38,9 @@
"@anticrm/model-attachment": "~0.6.0",
"@anticrm/lead": "~0.6.0",
"@anticrm/lead-resources": "~0.6.0",
"@anticrm/view": "~0.6.0"
"@anticrm/view": "~0.6.0",
"@anticrm/task": "~0.6.0",
"@anticrm/model-task": "~0.6.0",
"@anticrm/workbench": "~0.6.1"
}
}

View File

@ -15,31 +15,29 @@
//
// 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 type { Doc, FindOptions, Ref } from '@anticrm/core'
import type { Funnel, Lead } from '@anticrm/lead'
import { createKanban } from '@anticrm/lead'
import { Builder, Model, Prop, TypeRef, TypeString, UX } from '@anticrm/model'
import attachment from '@anticrm/model-attachment'
import chunter from '@anticrm/model-chunter'
import contact from '@anticrm/model-contact'
import core, { TDocWithState, TSpaceWithStates } from '@anticrm/model-core'
import core from '@anticrm/model-core'
import task, { TSpaceWithStates, TTask } from '@anticrm/model-task'
import view from '@anticrm/model-view'
import workbench from '@anticrm/model-workbench'
import type { IntlString } from '@anticrm/platform'
import type { Funnel, Lead } from '@anticrm/lead'
import { createKanban } from '@anticrm/lead'
import type { } from '@anticrm/view'
import lead from './plugin'
import attachment from '@anticrm/model-attachment'
export const DOMAIN_LEAD = 'lead' as Domain
@Model(lead.class.Funnel, core.class.SpaceWithStates)
@Model(lead.class.Funnel, task.class.SpaceWithStates)
@UX(lead.string.Funnel, lead.icon.Funnel)
export class TFunnel extends TSpaceWithStates implements Funnel {}
@Model(lead.class.Lead, core.class.Doc, DOMAIN_LEAD, [core.interface.DocWithState])
@Model(lead.class.Lead, task.class.Task)
@UX('Lead' as IntlString)
export class TLead extends TDocWithState implements Lead {
export class TLead extends TTask implements Lead {
@Prop(TypeString(), 'Title' as IntlString)
title!: string
@ -77,7 +75,7 @@ export function createModel (builder: Builder): void {
}
]
}
})
}, lead.app.Lead)
builder.createDoc(lead.class.Funnel, core.space.Model, {
name: 'Funnel',
description: 'Default funnel',
@ -93,7 +91,7 @@ export function createModel (builder: Builder): void {
options: {
lookup: {
customer: contact.class.Contact,
state: core.class.State
state: task.class.State
}
} as FindOptions<Doc>, // TODO: fix
config: [
@ -108,19 +106,19 @@ export function createModel (builder: Builder): void {
builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: lead.class.Lead,
descriptor: view.viewlet.Kanban,
descriptor: task.viewlet.Kanban,
open: lead.component.EditLead,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
options: {
lookup: {
customer: contact.class.Contact,
state: core.class.State
state: task.class.State
}
} as FindOptions<Doc>, // TODO: fix
config: ['$lookup.customer', '$lookup.state']
})
builder.mixin(lead.class.Lead, core.class.Class, view.mixin.KanbanCard, {
builder.mixin(lead.class.Lead, core.class.Class, task.mixin.KanbanCard, {
card: lead.component.KanbanCard
})
@ -132,12 +130,12 @@ export function createModel (builder: Builder): void {
presenter: lead.component.LeadPresenter
})
builder.createDoc(view.class.Sequence, view.space.Sequence, {
builder.createDoc(task.class.Sequence, task.space.Sequence, {
attachedTo: lead.class.Lead,
sequence: 0
})
builder.createDoc(view.class.KanbanTemplateSpace, core.space.Model, {
builder.createDoc(task.class.KanbanTemplateSpace, core.space.Model, {
name: 'Funnels',
description: 'Manage funnel statuses',
members: [],
@ -151,5 +149,5 @@ export function createModel (builder: Builder): void {
}).catch((err) => console.error(err))
}
export { default } from './plugin'
export { leadOperation } from './migration'
export { default } from './plugin'

View File

@ -17,7 +17,7 @@
import { Doc, TxOperations } from '@anticrm/core'
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@anticrm/model'
import core from '@anticrm/model-core'
import view from '@anticrm/model-view'
import task from '@anticrm/model-task'
import { createKanban } from '@anticrm/lead'
import lead from './plugin'
@ -29,7 +29,7 @@ export const leadOperation: MigrateOperation = {
console.log('Lead: Performing model upgrades')
const ops = new TxOperations(client, core.account.System)
if (await client.findOne(view.class.Kanban, { attachedTo: lead.space.DefaultFunnel }) === undefined) {
if (await client.findOne(task.class.Kanban, { attachedTo: lead.space.DefaultFunnel }) === undefined) {
console.info('Create kanban for default funnel.')
await createKanban(lead.space.DefaultFunnel, async (_class, space, data, id) => {
const doc = await ops.findOne<Doc>(_class, { _id: id })
@ -43,6 +43,17 @@ export const leadOperation: MigrateOperation = {
console.log('Lead: => default funnel Kanban is ok')
}
if (await client.findOne(task.class.Sequence, { attachedTo: lead.class.Lead }) === undefined) {
console.info('Create sequence for default task project.')
// We need to create sequence
await ops.createDoc(task.class.Sequence, task.space.Sequence, {
attachedTo: lead.class.Lead,
sequence: 0
})
} else {
console.log('Task: => sequence is ok')
}
const outdatedLeads = (await client.findAll(lead.class.Lead, {}))
.filter((x) => x.doneState === undefined)

View File

@ -15,13 +15,18 @@
//
import type { Ref, Space } from '@anticrm/core'
import type { IntlString } from '@anticrm/platform'
import { mergeIds } from '@anticrm/platform'
import { leadId } from '@anticrm/lead'
import lead from '@anticrm/lead-resources/src/plugin'
import type { IntlString } from '@anticrm/platform'
import { mergeIds } from '@anticrm/platform'
import '@anticrm/task'
import type { AnyComponent } from '@anticrm/ui'
import { Application } from '@anticrm/workbench'
export default mergeIds(leadId, lead, {
app: {
Lead: '' as Ref<Application>
},
string: {
Funnel: '' as IntlString,
Funnels: '' as IntlString,

View File

@ -39,6 +39,9 @@
"@anticrm/chunter": "~0.6.0",
"@anticrm/model-attachment": "~0.6.0",
"@anticrm/model-chunter": "~0.6.0",
"@anticrm/view": "~0.6.0"
"@anticrm/view": "~0.6.0",
"@anticrm/task": "~0.6.0",
"@anticrm/model-task": "~0.6.0",
"@anticrm/workbench": "~0.6.1"
}
}

View File

@ -14,21 +14,20 @@
//
import type { Employee } from '@anticrm/contact'
import { Doc, Domain, FindOptions, Ref, Timestamp } from '@anticrm/core'
import { Builder, Model, Prop, TypeBoolean, TypeDate, TypeRef, TypeString, UX, Collection } from '@anticrm/model'
import { Doc, FindOptions, Ref, Timestamp } from '@anticrm/core'
import { Builder, Collection, Model, Prop, TypeBoolean, TypeDate, TypeRef, TypeString, UX } from '@anticrm/model'
import attachment from '@anticrm/model-attachment'
import chunter from '@anticrm/model-chunter'
import contact, { TPerson } from '@anticrm/model-contact'
import core, { TAttachedDoc, TDocWithState, TSpace, TSpaceWithStates } from '@anticrm/model-core'
import core, { TSpace } from '@anticrm/model-core'
import task, { TSpaceWithStates, TTask } from '@anticrm/model-task'
import view from '@anticrm/model-view'
import workbench from '@anticrm/model-workbench'
import type { IntlString } from '@anticrm/platform'
import { Applicant, Candidate, Candidates, Vacancy } from '@anticrm/recruit'
import recruit from './plugin'
import attachment from '@anticrm/model-attachment'
export const DOMAIN_RECRUIT = 'recruit' as Domain
@Model(recruit.class.Vacancy, core.class.SpaceWithStates)
@Model(recruit.class.Vacancy, task.class.SpaceWithStates)
@UX(recruit.string.Vacancy, recruit.icon.Vacancy)
export class TVacancy extends TSpaceWithStates implements Vacancy {
@Prop(TypeString(), 'Full description' as IntlString)
@ -76,9 +75,9 @@ export class TCandidate extends TPerson implements Candidate {
source?: string
}
@Model(recruit.class.Applicant, core.class.AttachedDoc, DOMAIN_RECRUIT, [core.interface.DocWithState])
@Model(recruit.class.Applicant, task.class.Task)
@UX('Application' as IntlString, recruit.icon.RecruitApplication, 'APP' as IntlString)
export class TApplicant extends TAttachedDoc implements Applicant {
export class TApplicant extends TTask implements Applicant {
// We need to declare, to provide property with label
@Prop(TypeRef(recruit.class.Candidate), 'Candidate' as IntlString)
declare attachedTo: Ref<Candidate>
@ -90,12 +89,7 @@ export class TApplicant extends TAttachedDoc implements Applicant {
comments?: number
@Prop(TypeRef(contact.class.Employee), 'Assigned recruiter' as IntlString)
employee!: Ref<Employee> | null
// We need these to make typescript happy.
declare state: TDocWithState['state']
declare doneState: TDocWithState['doneState']
declare number: TDocWithState['number']
declare assignee: Ref<Employee> | null
}
export function createModel (builder: Builder): void {
@ -136,7 +130,7 @@ export function createModel (builder: Builder): void {
}
]
}
})
}, recruit.app.Recruit)
builder.createDoc(
recruit.class.Candidates,
core.space.Model,
@ -179,7 +173,7 @@ export function createModel (builder: Builder): void {
options: {
lookup: {
attachedTo: recruit.class.Candidate,
state: core.class.State
state: task.class.State
}
} as FindOptions<Doc>, // TODO: fix
config: [
@ -196,19 +190,19 @@ export function createModel (builder: Builder): void {
builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: recruit.class.Applicant,
descriptor: view.viewlet.Kanban,
descriptor: task.viewlet.Kanban,
open: recruit.component.EditCandidate,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
options: {
lookup: {
attachedTo: recruit.class.Candidate,
state: core.class.State
state: task.class.State
}
} as FindOptions<Doc>, // TODO: fix
config: ['$lookup.attachedTo', '$lookup.state', '$lookup.attachedTo.city', '$lookup.attachedTo.channels']
})
builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.KanbanCard, {
builder.mixin(recruit.class.Applicant, core.class.Class, task.mixin.KanbanCard, {
card: recruit.component.KanbanCard
})
@ -236,12 +230,17 @@ export function createModel (builder: Builder): void {
action: recruit.action.CreateApplication
})
builder.createDoc(view.class.Sequence, view.space.Sequence, {
builder.createDoc(view.class.ActionTarget, core.space.Model, {
target: recruit.class.Candidate,
action: task.action.CreateTask
})
builder.createDoc(task.class.Sequence, task.space.Sequence, {
attachedTo: recruit.class.Applicant,
sequence: 0
})
builder.createDoc(view.class.KanbanTemplateSpace, core.space.Model, {
builder.createDoc(task.class.KanbanTemplateSpace, core.space.Model, {
name: 'Vacancies',
description: 'Manage vacancy statuses',
members: [],
@ -250,6 +249,5 @@ export function createModel (builder: Builder): void {
}, recruit.space.VacancyTemplates)
}
export { default } from './plugin'
export { recruitOperation } from './migration'
export { default } from './plugin'

View File

@ -20,8 +20,13 @@ import { recruitId } from '@anticrm/recruit'
import recruit from '@anticrm/recruit-resources/src/plugin'
import type { AnyComponent } from '@anticrm/ui'
import type { Action } from '@anticrm/view'
import { Application } from '@anticrm/workbench'
import '@anticrm/task'
export default mergeIds(recruitId, recruit, {
app: {
Recruit: '' as Ref<Application>
},
action: {
CreateApplication: '' as Ref<Action>
},

View File

@ -1,5 +1,5 @@
{
"name": "@anticrm/model-server-view",
"name": "@anticrm/model-server-task",
"version": "0.6.0",
"main": "lib/index.js",
"author": "Anticrm Platform Contributors",
@ -28,7 +28,7 @@
"@anticrm/core": "~0.6.11",
"@anticrm/model": "~0.6.0",
"@anticrm/platform": "~0.6.5",
"@anticrm/server-view": "~0.6.0",
"@anticrm/server-task": "~0.6.0",
"@anticrm/server-core": "~0.6.0"
}
}

View File

@ -18,10 +18,10 @@ import { Builder } from '@anticrm/model'
import serverCore from '@anticrm/server-core'
import core from '@anticrm/core'
import serverView from '@anticrm/server-view'
import serverTask from '@anticrm/server-task'
export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverView.trigger.OnDocWithState
trigger: serverTask.trigger.OnTask
})
}

View File

@ -19,27 +19,77 @@ import type {} from '@anticrm/view'
import attachment from '@anticrm/model-attachment'
import type { Employee } from '@anticrm/contact'
import contact from '@anticrm/contact'
import type { Doc, DocWithState, Domain, FindOptions, Ref } from '@anticrm/core'
import { Builder, Model, Prop, TypeRef, TypeString, UX } from '@anticrm/model'
import { Arr, Class, Doc, Domain, DOMAIN_MODEL, FindOptions, Ref, Space } from '@anticrm/core'
import { Builder, Collection, Mixin, Model, Prop, TypeRef, TypeString, UX } from '@anticrm/model'
import chunter from '@anticrm/model-chunter'
import core, { TDoc, TSpaceWithStates } from '@anticrm/model-core'
import core, { TAttachedDoc, TClass, TDoc, TSpace } 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 type { Kanban, KanbanCard, Project, State, Issue, Sequence, DoneState, WonState, LostState, KanbanTemplateSpace, StateTemplate, DoneStateTemplate, WonStateTemplate, LostStateTemplate, KanbanTemplate, Task } from '@anticrm/task'
import { createProjectKanban } from '@anticrm/task'
import task from './plugin'
import { AnyComponent } from '@anticrm/ui'
@Model(task.class.Project, core.class.SpaceWithStates)
export { default } from './plugin'
export const DOMAIN_TASK = 'task' as Domain
export const DOMAIN_STATE = 'state' as Domain
export const DOMAIN_KANBAN = 'kanban' as Domain
@Model(task.class.State, core.class.Doc, DOMAIN_STATE)
export class TState extends TDoc implements State {
@Prop(TypeString(), 'Title' as IntlString)
title!: string
color!: string
}
@Model(task.class.DoneState, core.class.Doc, DOMAIN_STATE)
export class TDoneState extends TDoc implements DoneState {
@Prop(TypeString(), 'Title' as IntlString)
title!: string
}
@Model(task.class.WonState, task.class.DoneState, DOMAIN_STATE)
export class TWonState extends TDoneState implements WonState {}
@Model(task.class.LostState, task.class.DoneState, DOMAIN_STATE)
export class TLostState extends TDoneState implements LostState {}
/**
* @public
*
* No domain is specified, since pure Tasks could not exists
*/
@Model(task.class.Task, core.class.AttachedDoc, DOMAIN_TASK)
export class TTask extends TAttachedDoc implements Task {
@Prop(TypeRef(task.class.State), 'State' as IntlString)
state!: Ref<State>
@Prop(TypeRef(task.class.DoneState), 'Done Status' as IntlString)
doneState!: Ref<DoneState> | null
@Prop(TypeString(), 'No.' as IntlString)
number!: number
// @Prop(TypeRef(contact.class.Employee), 'Assignee' as IntlString)
assignee!: Ref<Employee> | null
}
@Model(task.class.SpaceWithStates, core.class.Space)
export class TSpaceWithStates extends TSpace {
}
@Model(task.class.Project, task.class.SpaceWithStates)
@UX('Project' as IntlString, task.icon.Task)
export class TProject extends TSpaceWithStates implements Project {}
@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 {
declare number: DocWithState['number']
declare state: DocWithState['state']
declare doneState: DocWithState['doneState']
@Model(task.class.Issue, task.class.Task, DOMAIN_TASK)
@UX('Task' as IntlString, task.icon.Task, 'Task' as IntlString)
export class TIssue extends TTask implements Issue {
// We need to declare, to provide property with label
@Prop(TypeRef(core.class.Doc), 'Parent' as IntlString)
declare attachedTo: Ref<Doc>
@Prop(TypeString(), 'Name' as IntlString)
name!: string
@ -47,9 +97,6 @@ export class TTask extends TDoc implements Task {
@Prop(TypeString(), 'Description' as IntlString)
description!: string
@Prop(TypeRef(contact.class.Employee), 'Assignee' as IntlString)
assignee!: Ref<Employee> | null
@Prop(TypeString(), 'Comments' as IntlString)
comments!: number
@ -58,13 +105,93 @@ export class TTask extends TDoc implements Task {
@Prop(TypeString(), 'Labels' as IntlString)
labels!: string
@Prop(TypeRef(contact.class.Employee), 'Assignee' as IntlString)
declare assignee: Ref<Employee> | null
}
@Mixin(task.mixin.KanbanCard, core.class.Class)
export class TKanbanCard extends TClass implements KanbanCard {
card!: AnyComponent
}
@Model(task.class.Kanban, core.class.Doc, DOMAIN_KANBAN)
export class TKanban extends TDoc implements Kanban {
states!: Arr<Ref<State>>
doneStates!: Arr<Ref<DoneState>>
attachedTo!: Ref<Space>
order!: Arr<Ref<Doc>>
}
@Model(task.class.KanbanTemplateSpace, core.class.Space, DOMAIN_MODEL)
export class TKanbanTemplateSpace extends TSpace implements KanbanTemplateSpace {
icon!: AnyComponent
}
@Model(task.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(task.class.DoneStateTemplate, core.class.AttachedDoc, DOMAIN_KANBAN)
export class TDoneStateTemplate extends TAttachedDoc implements DoneStateTemplate {
@Prop(TypeString(), 'Title' as IntlString)
title!: string
}
@Model(task.class.WonStateTemplate, task.class.DoneStateTemplate, DOMAIN_KANBAN)
export class TWonStateTemplate extends TDoneStateTemplate implements WonStateTemplate {}
@Model(task.class.LostStateTemplate, task.class.DoneStateTemplate, DOMAIN_KANBAN)
export class TLostStateTemplate extends TDoneStateTemplate implements LostStateTemplate {}
@Model(task.class.KanbanTemplate, core.class.Doc, DOMAIN_KANBAN)
export class TKanbanTemplate extends TDoc implements KanbanTemplate {
@Prop(TypeString(), 'Title' as IntlString)
title!: string
states!: Arr<Ref<StateTemplate>>
doneStates!: Arr<Ref<DoneStateTemplate>>
@Prop(Collection(task.class.StateTemplate), 'States' as IntlString)
statesC!: number
@Prop(Collection(task.class.DoneStateTemplate), 'Done States' as IntlString)
doneStatesC!: number
}
@Model(task.class.Sequence, core.class.Doc, DOMAIN_KANBAN)
export class TSequence extends TDoc implements Sequence {
attachedTo!: Ref<Class<Doc>>
sequence!: number
}
export function createModel (builder: Builder): void {
builder.createModel(TProject, TTask)
builder.createModel(
TState,
TDoneState,
TWonState,
TLostState,
TKanbanCard,
TKanban,
TKanbanTemplateSpace,
TStateTemplate,
TDoneStateTemplate,
TWonStateTemplate,
TLostStateTemplate,
TKanbanTemplate,
TSequence,
TTask,
TSpaceWithStates,
TProject,
TIssue)
builder.mixin(task.class.Project, core.class.Class, workbench.mixin.SpaceView, {
view: {
class: task.class.Task,
class: task.class.Issue,
createItemDialog: task.component.CreateTask
}
})
@ -86,7 +213,7 @@ export function createModel (builder: Builder): void {
}, task.app.Tasks)
builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: task.class.Task,
attachTo: task.class.Issue,
descriptor: view.viewlet.Table,
open: task.component.EditTask,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
@ -105,34 +232,38 @@ export function createModel (builder: Builder): void {
]
})
builder.mixin(task.class.Task, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(task.class.Issue, core.class.Class, view.mixin.AttributePresenter, {
presenter: task.component.TaskPresenter
})
builder.mixin(task.class.Task, core.class.Class, view.mixin.ObjectEditor, {
builder.mixin(task.class.Issue, core.class.Class, view.mixin.ObjectEditor, {
editor: task.component.EditTask
})
builder.createDoc(view.class.Sequence, view.space.Sequence, {
attachedTo: task.class.Task,
builder.createDoc(task.class.Sequence, task.space.Sequence, {
attachedTo: task.class.Issue,
sequence: 0
})
builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: task.class.Task,
descriptor: view.viewlet.Kanban,
attachTo: task.class.Issue,
descriptor: task.viewlet.Kanban,
open: task.component.EditTask,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
options: {
lookup: {
assignee: contact.class.Employee,
state: core.class.State
state: task.class.State
// attachedTo: core.class.Doc
}
} as FindOptions<Doc>, // TODO: fix
config: ['$lookup.attachedTo', '$lookup.state']
config: [
// '$lookup.attachedTo',
'$lookup.state',
'$lookup.assignee']
})
builder.mixin(task.class.Task, core.class.Class, view.mixin.KanbanCard, {
builder.mixin(task.class.Issue, core.class.Class, task.mixin.KanbanCard, {
card: task.component.KanbanCard
})
@ -143,7 +274,7 @@ export function createModel (builder: Builder): void {
members: []
}, task.space.TasksPublic)
builder.createDoc(view.class.KanbanTemplateSpace, core.space.Model, {
builder.createDoc(task.class.KanbanTemplateSpace, core.space.Model, {
name: 'Projects',
description: 'Manage project statuses',
members: [],
@ -155,6 +286,44 @@ export function createModel (builder: Builder): void {
builder.createDoc(_class, space, data, id)
return await Promise.resolve()
}).catch((err) => console.error(err))
builder.createDoc(view.class.Action, core.space.Model, {
label: 'Create task' as IntlString,
icon: task.icon.Task,
action: task.actionImpl.CreateTask
}, task.action.CreateTask)
builder.createDoc(view.class.Action, core.space.Model, {
label: 'Edit Statuses' as IntlString,
icon: view.icon.MoreH,
action: task.actionImpl.EditStatuses
}, task.action.EditStatuses)
builder.createDoc(view.class.ActionTarget, core.space.Model, {
target: task.class.SpaceWithStates,
action: task.action.EditStatuses
})
builder.mixin(task.class.State, core.class.Class, view.mixin.AttributeEditor, {
editor: task.component.StateEditor
})
builder.mixin(task.class.State, core.class.Class, view.mixin.AttributePresenter, {
presenter: task.component.StatePresenter
})
builder.createDoc(view.class.ViewletDescriptor, core.space.Model, {
label: 'Kanban' as IntlString,
icon: task.icon.Kanban,
component: task.component.KanbanView
}, task.viewlet.Kanban)
builder.createDoc(core.class.Space, core.space.Model, {
name: 'Sequences',
description: 'Internal space to store sequence numbers',
members: [],
private: false
}, task.space.Sequence)
}
export { taskOperation } from './migration'

View File

@ -13,32 +13,64 @@
// limitations under the License.
//
import { Doc, TxOperations } from '@anticrm/core'
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@anticrm/model'
import { Class, Doc, Domain, DOMAIN_TX, Ref, TxCUD, TxOperations } from '@anticrm/core'
import { MigrateOperation, MigrateUpdate, MigrationClient, MigrationResult, MigrationUpgradeClient } from '@anticrm/model'
import core from '@anticrm/model-core'
import view from '@anticrm/model-view'
import { createProjectKanban } from '@anticrm/task'
import { DOMAIN_TASK, DOMAIN_STATE, DOMAIN_KANBAN } from '.'
import task from './plugin'
function logInfo (msg: string, result: MigrationResult): void {
if (result.updated > 0) {
console.log(`Tasks: Migrate ${msg} ${result.updated}`)
}
}
async function migrateClass<T extends Doc> (client: MigrationClient, domain: Domain, from: Ref<Class<Doc>>, to: Ref<Class<T>>, extraOps: MigrateUpdate<T> = {}, txExtraOps: MigrateUpdate<TxCUD<Doc>> = {}): Promise<void> {
logInfo(`Migrate ${from} => ${to}: `,
await client.update<Doc>(domain, { _class: from }, { ...extraOps, _class: to }))
logInfo(`Migrate ${from} => ${to} Transactions`,
await client.update<TxCUD<Doc>>(DOMAIN_TX, { objectClass: from }, { ...txExtraOps, objectClass: to }))
}
export const taskOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {
// Since we should not have Task class instances, we convert them all to Issue.
await migrateClass(client, DOMAIN_TASK, task.class.Task, task.class.Issue)
await migrateClass(client, DOMAIN_STATE, 'core:class:State' as Ref<Class<Doc>>, task.class.State)
await migrateClass(client, DOMAIN_KANBAN, 'view:class:Kanban' as Ref<Class<Doc>>, task.class.Kanban)
await migrateClass(client, DOMAIN_KANBAN, 'view:class:Sequence' as Ref<Class<Doc>>, task.class.Sequence, { space: task.space.Sequence }, { objectSpace: task.space.Sequence })
// Update attached to for task
await client.update(DOMAIN_KANBAN, { _class: task.class.Sequence, attachedTo: task.class.Task }, { attachedTo: task.class.Issue })
await migrateClass(client, DOMAIN_KANBAN, 'view:class:KanbanTemplate' as Ref<Class<Doc>>, task.class.KanbanTemplate)
await migrateClass(client, DOMAIN_KANBAN, 'view:class:StateTemplate' as Ref<Class<Doc>>, task.class.StateTemplate)
await migrateClass(client, DOMAIN_KANBAN, 'view:class:DoneStateTemplate' as Ref<Class<Doc>>, task.class.DoneStateTemplate)
await migrateClass(client, DOMAIN_KANBAN, 'view:class:LostStateTemplate' as Ref<Class<Doc>>, task.class.LostStateTemplate)
await client.move('recruit' as Domain, {
_class: 'recruit:class:Applicant' as Ref<Class<Doc>>
}, DOMAIN_TASK)
await client.move('lead' as Domain, {
_class: 'lead:class:Lead' as Ref<Class<Doc>>
}, DOMAIN_TASK)
},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
console.log('Task: Performing model upgrades')
const ops = new TxOperations(client, core.account.System)
if (await client.findOne(view.class.Sequence, { attachedTo: task.class.Task }) === undefined) {
if (await client.findOne(task.class.Sequence, { attachedTo: task.class.Issue }) === undefined) {
console.info('Create sequence for default task project.')
// We need to create sequence
await ops.createDoc(view.class.Sequence, view.space.Sequence, {
attachedTo: task.class.Task,
await ops.createDoc(task.class.Sequence, task.space.Sequence, {
attachedTo: task.class.Issue,
sequence: 0
})
} else {
console.log('Task: => sequence is ok')
}
if (await client.findOne(view.class.Kanban, { attachedTo: task.space.TasksPublic }) === undefined) {
if (await client.findOne(task.class.Kanban, { attachedTo: task.space.TasksPublic }) === undefined) {
console.info('Create kanban for default task project.')
await createProjectKanban(task.space.TasksPublic, async (_class, space, data, id) => {
const doc = await ops.findOne<Doc>(_class, { _id: id })
@ -52,6 +84,33 @@ export const taskOperation: MigrateOperation = {
console.log('Task: => public project Kanban is ok')
}
console.log('View: Performing model upgrades')
const kanbans = (await client.findAll(task.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(task.class.WonState, kanban.space, {
title: 'Won'
}),
ops.createDoc(task.class.LostState, kanban.space, {
title: 'Lost'
})
])
await ops.updateDoc(kanban._class, kanban.space, kanban._id, {
doneStates
})
} catch (e) {
console.error(e)
}
}))
const outdatedTasks = (await client.findAll(task.class.Task, {}))
.filter((x) => x.doneState === undefined)

View File

@ -14,17 +14,26 @@
// limitations under the License.
//
import type { Ref, Space } from '@anticrm/core'
import type { IntlString } from '@anticrm/platform'
import type { Doc, Ref, Space } from '@anticrm/core'
import type { IntlString, Resource } from '@anticrm/platform'
import { mergeIds } from '@anticrm/platform'
import task, { taskId } from '@anticrm/task'
import type { AnyComponent } from '@anticrm/ui'
import { Application } from '@anticrm/workbench'
import type { Action } from '@anticrm/view'
export default mergeIds(taskId, task, {
app: {
Tasks: '' as Ref<Application>
},
action: {
CreateTask: '' as Ref<Action>,
EditStatuses: '' as Ref<Action>
},
actionImpl: {
CreateTask: '' as Resource<(object: Doc) => Promise<void>>,
EditStatuses: '' as Resource<(object: Doc) => Promise<void>>
},
component: {
ProjectView: '' as AnyComponent,
CreateProject: '' as AnyComponent,
@ -32,7 +41,10 @@ export default mergeIds(taskId, task, {
EditTask: '' as AnyComponent,
TaskPresenter: '' as AnyComponent,
KanbanCard: '' as AnyComponent,
TemplatesIcon: '' as AnyComponent
TemplatesIcon: '' as AnyComponent,
StatePresenter: '' as AnyComponent,
StateEditor: '' as AnyComponent,
KanbanView: '' as AnyComponent
},
string: {
Task: '' as IntlString,

View File

@ -13,36 +13,15 @@
// limitations under the License.
//
import type { IntlString, Asset, Resource } from '@anticrm/platform'
import type { Ref, Class, Space, Doc, Arr, Domain, State, DoneState } from '@anticrm/core'
import type { Class, Doc, Ref, Space } from '@anticrm/core'
import { DOMAIN_MODEL } from '@anticrm/core'
import { Model, Mixin, Builder, Prop, Collection, TypeString } from '@anticrm/model'
import { Builder, Mixin, Model } from '@anticrm/model'
import core, { TClass, TDoc } from '@anticrm/model-core'
import type { Asset, IntlString, Resource } from '@anticrm/platform'
import type { AnyComponent } from '@anticrm/ui'
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, TSpace, TAttachedDoc } from '@anticrm/model-core'
import type { Action, ActionTarget, AttributeEditor, AttributePresenter, ObjectEditor, Viewlet, ViewletDescriptor } from '@anticrm/view'
import view from './plugin'
const DOMAIN_KANBAN = 'kanban' as Domain
@Mixin(view.mixin.AttributeEditor, core.class.Class)
export class TAttributeEditor extends TClass implements AttributeEditor {
editor!: AnyComponent
@ -53,11 +32,6 @@ export class TAttributePresenter extends TClass implements AttributePresenter {
presenter!: AnyComponent
}
@Mixin(view.mixin.KanbanCard, core.class.Class)
export class TKanbanCard extends TClass implements KanbanCard {
card!: AnyComponent
}
@Mixin(view.mixin.ObjectEditor, core.class.Class)
export class TObjectEditor extends TClass implements ObjectEditor {
editor!: AnyComponent
@ -90,80 +64,8 @@ export class TActionTarget extends TDoc implements ActionTarget {
action!: Ref<Action>
}
@Model(view.class.Kanban, core.class.Doc, DOMAIN_KANBAN)
export class TKanban extends TDoc implements Kanban {
states!: Arr<Ref<State>>
doneStates!: Arr<Ref<DoneState>>
attachedTo!: Ref<Space>
order!: Arr<Ref<Doc>>
}
@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<Ref<StateTemplate>>
doneStates!: Arr<Ref<DoneStateTemplate>>
@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<Class<Doc>>
sequence!: number
}
export function createModel (builder: Builder): void {
builder.createModel(
TAttributeEditor,
TAttributePresenter,
TKanbanCard,
TObjectEditor,
TViewletDescriptor,
TViewlet,
TAction,
TActionTarget,
TKanban,
TSequence,
TKanbanTemplateSpace,
TStateTemplate,
TDoneStateTemplate,
TWonStateTemplate,
TLostStateTemplate,
TKanbanTemplate
)
builder.createModel(TAttributeEditor, TAttributePresenter, TObjectEditor, TViewletDescriptor, TViewlet, TAction, TActionTarget)
builder.mixin(core.class.TypeString, core.class.Class, view.mixin.AttributeEditor, {
editor: view.component.StringEditor
@ -193,46 +95,17 @@ export function createModel (builder: Builder): void {
editor: view.component.DateEditor
})
builder.mixin(core.class.State, core.class.Class, view.mixin.AttributeEditor, {
editor: view.component.StateEditor
})
builder.createDoc(view.class.ViewletDescriptor, core.space.Model, {
label: 'Table' as IntlString,
icon: view.icon.Table,
component: view.component.TableView
}, view.viewlet.Table)
builder.mixin(core.class.State, core.class.Class, view.mixin.AttributePresenter, {
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: '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,
@ -249,13 +122,6 @@ export function createModel (builder: Builder): void {
target: core.class.Doc,
action: view.action.Move
})
builder.createDoc(core.class.Space, core.space.Model, {
name: 'Sequences',
description: 'Internal space to store sequence numbers',
members: [],
private: false
}, view.space.Sequence)
}
export default view

View File

@ -13,42 +13,12 @@
// 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<void> {
},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
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)
}
}))
}
}

View File

@ -33,12 +33,9 @@ export default mergeIds(viewId, view, {
StringPresenter: '' as AnyComponent,
BooleanPresenter: '' as AnyComponent,
BooleanEditor: '' as AnyComponent,
StatePresenter: '' as AnyComponent,
StateEditor: '' as AnyComponent,
TimestampPresenter: '' as AnyComponent,
DateEditor: '' as AnyComponent,
DatePresenter: '' as AnyComponent,
TableView: '' as AnyComponent,
KanbanView: '' as AnyComponent
TableView: '' as AnyComponent
}
})

View File

@ -44,13 +44,13 @@ describe('hierarchy', () => {
it('isImplements', async () => {
const hierarchy = prepare()
let isImplements = hierarchy.isImplements(test.class.Task, core.interface.DocWithState)
let isImplements = hierarchy.isImplements(test.class.Task, test.interface.WithState)
expect(isImplements).toBeTruthy()
isImplements = hierarchy.isImplements(test.class.TaskCheckItem, core.interface.DocWithState)
isImplements = hierarchy.isImplements(test.class.TaskCheckItem, test.interface.WithState)
expect(isImplements).toBeTruthy()
const notImplements = hierarchy.isImplements(core.class.Space, core.interface.DocWithState)
const notImplements = hierarchy.isImplements(core.class.Space, test.interface.WithState)
expect(notImplements).not.toBeTruthy()
})

View File

@ -15,7 +15,6 @@
import type { IntlString, Plugin } from '@anticrm/platform'
import { plugin } from '@anticrm/platform'
import { DocWithState } from '..'
import type { Arr, Class, Data, Doc, Interface, Mixin, Obj, Ref } from '../classes'
import { AttachedDoc, ClassifierKind, DOMAIN_MODEL } from '../classes'
import core from '../component'
@ -44,11 +43,15 @@ export interface AttachedComment extends AttachedDoc {
message: string
}
export interface Task extends Doc, DocWithState {
export interface WithState extends Doc {
state: number
number: number
}
export interface Task extends Doc, WithState {
name: string
}
export interface TaskCheckItem extends AttachedDoc, DocWithState {
export interface TaskCheckItem extends AttachedDoc, WithState {
name: string
complete: boolean
}
@ -63,7 +66,8 @@ export const test = plugin('test' as Plugin, {
TestComment: '' as Ref<Class<AttachedComment>>
},
interface: {
DummyDocWithState: '' as Ref<Interface<DocWithState>>
WithState: '' as Ref<Interface<WithState>>,
DummyWithState: '' as Ref<Interface<WithState>>
}
})
@ -82,7 +86,7 @@ export function genMinModel (): TxCUD<Doc>[] {
txes.push(createClass(core.class.Space, { label: 'Space' as IntlString, extends: core.class.Doc, kind: ClassifierKind.CLASS, domain: DOMAIN_MODEL }))
txes.push(createClass(core.class.Account, { label: 'Account' as IntlString, extends: core.class.Doc, kind: ClassifierKind.CLASS, domain: DOMAIN_MODEL }))
txes.push(createInterface(core.interface.DocWithState, { label: 'DocWithState' as IntlString, extends: [], kind: ClassifierKind.INTERFACE }))
txes.push(createInterface(test.interface.WithState, { label: 'WithState' as IntlString, extends: [], kind: ClassifierKind.INTERFACE }))
txes.push(createClass(core.class.Tx, { label: 'Tx' as IntlString, extends: core.class.Doc, kind: ClassifierKind.CLASS, domain: DOMAIN_TX }))
txes.push(createClass(core.class.TxCUD, { label: 'TxCUD' as IntlString, extends: core.class.Tx, kind: ClassifierKind.CLASS, domain: DOMAIN_TX }))
@ -93,10 +97,10 @@ export function genMinModel (): TxCUD<Doc>[] {
txes.push(createClass(test.mixin.TestMixin, { label: 'TestMixin' as IntlString, extends: core.class.Doc, kind: ClassifierKind.MIXIN }))
txes.push(createInterface(test.interface.DummyDocWithState, { label: 'DummyDocWithState' as IntlString, extends: [core.interface.DocWithState], kind: ClassifierKind.INTERFACE }))
txes.push(createInterface(test.interface.DummyWithState, { label: 'DummyWithState' as IntlString, extends: [test.interface.WithState], kind: ClassifierKind.INTERFACE }))
txes.push(createClass(test.class.TestComment, { label: 'TestComment' as IntlString, extends: core.class.AttachedDoc, kind: ClassifierKind.CLASS }))
txes.push(createClass(test.class.Task, { label: 'Task' as IntlString, extends: core.class.Doc, implements: [test.interface.DummyDocWithState], kind: ClassifierKind.CLASS }))
txes.push(createClass(test.class.TaskCheckItem, { label: 'Task' as IntlString, extends: core.class.AttachedDoc, implements: [core.interface.DocWithState], kind: ClassifierKind.CLASS }))
txes.push(createClass(test.class.Task, { label: 'Task' as IntlString, extends: core.class.Doc, implements: [test.interface.DummyWithState], kind: ClassifierKind.CLASS }))
txes.push(createClass(test.class.TaskCheckItem, { label: 'Task' as IntlString, extends: core.class.AttachedDoc, implements: [test.interface.WithState], kind: ClassifierKind.CLASS }))
txes.push(
createDoc(core.class.Space, {

View File

@ -218,45 +218,3 @@ export interface Space extends Doc {
export interface Account extends Doc {
email: string
}
// S T A T E
/**
* @public
*/
export interface State extends Doc {
title: string
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<State>
doneState: Ref<DoneState> | null
number: number
}
/**
* @public
*/
export interface SpaceWithStates extends Space {
}

View File

@ -14,8 +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 { Account, AnyAttribute, AttachedDoc, Class, Doc, Interface, Obj, PropertyType, Ref, Space, Timestamp, Type, Collection, RefTo } from './classes'
import type { Tx, TxBulkWrite, TxCollectionCUD, TxCreateDoc, TxCUD, TxMixin, TxPutBag, TxRemoveDoc, TxUpdateDoc } from './tx'
/**
@ -41,12 +40,7 @@ export default plugin(coreId, {
TxRemoveDoc: '' as Ref<Class<TxRemoveDoc<Doc>>>,
TxPutBag: '' as Ref<Class<TxPutBag<PropertyType>>>,
Space: '' as Ref<Class<Space>>,
SpaceWithStates: '' as Ref<Class<SpaceWithStates>>,
Account: '' as Ref<Class<Account>>,
State: '' as Ref<Class<State>>,
DoneState: '' as Ref<Class<DoneState>>,
WonState: '' as Ref<Class<WonState>>,
LostState: '' as Ref<Class<LostState>>,
TypeString: '' as Ref<Class<Type<string>>>,
TypeBoolean: '' as Ref<Class<Type<boolean>>>,
TypeTimestamp: '' as Ref<Class<Type<Timestamp>>>,
@ -55,9 +49,6 @@ export default plugin(coreId, {
Collection: '' as Ref<Class<Collection<AttachedDoc>>>,
Bag: '' as Ref<Class<Type<Record<string, PropertyType>>>>
},
interface: {
DocWithState: '' as Ref<Interface<DocWithState>>
},
space: {
Tx: '' as Ref<Space>,
Model: '' as Ref<Space>

View File

@ -28,6 +28,9 @@ export interface MigrationClient {
// Allow to raw update documents inside domain.
update: <T extends Doc>(domain: Domain, query: DocumentQuery<T>, operations: MigrateUpdate<T>) => Promise<MigrationResult>
// Move documents per domain
move: <T extends Doc>(sourceDomain: Domain, query: DocumentQuery<T>, targetDomain: Domain) => Promise<MigrationResult>
}
/**

View File

@ -56,7 +56,7 @@ export class PlatformError<P extends Record<string, any>> extends Error {
readonly status: Status<P>
constructor (status: Status<P>) {
super(`${status.severity}: ${status.code}`)
super(`${status.severity}: ${status.code} ${JSON.stringify(status.params)}`)
this.status = status
}
}

View File

@ -49,7 +49,7 @@
</Tooltip>
</td>
<td>
<ActionIcon direction={undefined} size='small' icon={view.icon.Kanban} label={toIntl('Perform Class Query')} action={() => {
<ActionIcon direction={undefined} size='small' icon={view.icon.MoreH} label={toIntl('Perform Class Query')} action={() => {
}}/>
</td>
</tr>

View File

@ -39,6 +39,8 @@
"@anticrm/panel": "~0.6.0",
"@anticrm/contact": "~0.6.2",
"@anticrm/view": "~0.6.0",
"@anticrm/task": "~0.6.0",
"@anticrm/task-resources": "~0.6.0",
"@anticrm/view-resources": "~0.6.0",
"@anticrm/attachment-resources": "~0.6.0",
"@anticrm/contact-resources": "~0.6.0",

View File

@ -19,8 +19,8 @@
import { EditBox, Grid, IconFolder, ToggleWithLabel } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import lead from '../plugin'
import view, { createKanban, KanbanTemplate } from '@anticrm/view'
import { KanbanTemplateSelector } from '@anticrm/view-resources'
import task, { createKanban, KanbanTemplate } from '@anticrm/task'
import { KanbanTemplateSelector } from '@anticrm/task-resources'
const dispatch = createEventDispatcher()
@ -35,7 +35,7 @@
const client = getClient()
async function createFunnel (): Promise<void> {
if (templateId !== undefined && await client.findOne(view.class.KanbanTemplate, { _id: templateId }) === undefined) {
if (templateId !== undefined && await client.findOne(task.class.KanbanTemplate, { _id: templateId }) === undefined) {
throw Error(`Failed to find target kanban template: ${templateId}`)
}

View File

@ -15,15 +15,15 @@
-->
<script lang="ts">
import contact, { Contact } from '@anticrm/contact'
import core, { Data, Ref, Space } from '@anticrm/core'
import { Data, Ref, Space } from '@anticrm/core'
import { generateId } from '@anticrm/core'
import { OK, Status } from '@anticrm/platform'
import { Card, getClient, UserBox } from '@anticrm/presentation'
import type { Lead } from '@anticrm/lead'
import { EditBox, Grid, Status as StatusControl } from '@anticrm/ui'
import view from '@anticrm/view'
import { createEventDispatcher } from 'svelte'
import lead from '../plugin'
import task from '@anticrm/task'
export let space: Ref<Space>
@ -43,18 +43,18 @@
}
async function createLead () {
const state = await client.findOne(core.class.State, { space: _space })
const state = await client.findOne(task.class.State, { space: _space })
if (state === undefined) {
throw new Error('create application: state not found')
}
const sequence = await client.findOne(view.class.Sequence, { attachedTo: lead.class.Lead })
const sequence = await client.findOne(task.class.Sequence, { attachedTo: lead.class.Lead })
if (sequence === undefined) {
throw new Error('sequence object not found')
}
const incResult = await client.updateDoc(
view.class.Sequence,
view.space.Sequence,
task.class.Sequence,
task.space.Sequence,
sequence._id,
{
$inc: { sequence: 1 }
@ -67,7 +67,10 @@
doneState: null,
number: (incResult as any).object.sequence,
title: title,
customer: customer!
customer: customer!,
attachedTo: customer!,
attachedToClass: contact.class.Contact,
collection: 'leads'
}
await client.createDoc(lead.class.Lead, _space, value, leadId)

View File

@ -29,6 +29,7 @@
"@anticrm/contact": "~0.6.2",
"@anticrm/core": "~0.6.11",
"@anticrm/platform": "~0.6.5",
"@anticrm/view": "~0.6.0"
"@anticrm/view": "~0.6.0",
"@anticrm/task": "~0.6.0"
}
}

View File

@ -14,12 +14,11 @@
// limitations under the License.
//
import { plugin } from '@anticrm/platform'
import type { Asset, Plugin } from '@anticrm/platform'
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'
import type { Class, Data, Doc, Ref, Space } from '@anticrm/core'
import type { Asset, Plugin } from '@anticrm/platform'
import { plugin } from '@anticrm/platform'
import task, { DoneState, Kanban, KanbanTemplateSpace, SpaceWithStates, State, Task } from '@anticrm/task'
/**
* @public
@ -29,7 +28,7 @@ export interface Funnel extends SpaceWithStates {}
/**
* @public
*/
export interface Lead extends DocWithState {
export interface Lead extends Task {
title: string
customer: Ref<Contact>
@ -76,7 +75,7 @@ export async function createKanban (
for (const st of states) {
const sid = (funnelId + '.state.' + st.name.toLowerCase().replace(' ', '_')) as Ref<State>
await factory(
core.class.State,
task.class.State,
funnelId,
{
title: st.name,
@ -87,8 +86,8 @@ export async function createKanban (
ids.push(sid)
}
const rawDoneStates = [
{ class: core.class.WonState, title: 'Won' },
{ class: core.class.LostState, title: 'Lost' }
{ class: task.class.WonState, title: 'Won' },
{ class: task.class.LostState, title: 'Lost' }
]
const doneStates: Array<Ref<DoneState>> = []
for (const st of rawDoneStates) {
@ -105,7 +104,7 @@ export async function createKanban (
}
await factory(
view.class.Kanban,
task.class.Kanban,
funnelId,
{
attachedTo: funnelId,
@ -113,6 +112,6 @@ export async function createKanban (
doneStates,
order: []
},
(funnelId + '.kanban.') as Ref<Kanban>
(funnelId + '.kanban') as Ref<Kanban>
)
}

View File

@ -49,6 +49,8 @@
"@anticrm/chunter-resources": "~0.6.0",
"@anticrm/setting": "~0.6.0",
"@anticrm/view": "~0.6.0",
"@anticrm/view-resources": "~0.6.0"
"@anticrm/view-resources": "~0.6.0",
"@anticrm/task": "~0.6.0",
"@anticrm/task-resources": "~0.6.0"
}
}

View File

@ -29,7 +29,7 @@
object.attachedTo,
object.attachedToClass,
object.collection,
{ employee: object.employee }
{ assignee: object.assignee }
)
}
</script>
@ -39,7 +39,7 @@
_class={contact.class.Employee}
title="Assigned recruiter"
caption="Recruiters"
bind:value={object.employee}
bind:value={object.assignee}
on:change={change}
allowDeselect
titleDeselect={'Unassign recruiter'}

View File

@ -22,7 +22,7 @@
import FileDuo from "./icons/FileDuo.svelte"
import { Table } from '@anticrm/view-resources'
import core from '@anticrm/core'
import task from '@anticrm/task'
import recruit from '../plugin'
export let objectId: Ref<Doc>
@ -50,8 +50,8 @@
options={
{
lookup: {
state: core.class.State,
space: core.class.Space
state: task.class.State,
space: task.class.Space
}
}
}

View File

@ -16,15 +16,11 @@
<script lang="ts">
import type { Applicant, Candidate } from '@anticrm/recruit'
import { CircleButton, showPopup, closeTooltip } from '@anticrm/ui'
import Vacancy from './icons/Vacancy.svelte'
import { getClient, createQuery } from '@anticrm/presentation'
import EditApplication from './EditApplication.svelte'
import { Table } from '@anticrm/view-resources'
import recruit from '@anticrm/recruit'
import core from '@anticrm/core'
import type { Candidate } from '@anticrm/recruit'
import recruit from '@anticrm/recruit'
import task from '@anticrm/task'
import { Table } from '@anticrm/view-resources'
export let value: Candidate
@ -36,7 +32,7 @@
options={
{
lookup: {
state: core.class.State,
state: task.class.State,
space: core.class.Space
}
}

View File

@ -13,26 +13,21 @@
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import type { Ref, Space, SpaceWithStates } from '@anticrm/core'
import { Status, OK, Severity } from '@anticrm/platform'
import { DatePicker, EditBox, Tabs, Section, Grid, Status as StatusControl } from '@anticrm/ui'
import { UserBox, Card, UserInfo, Avatar } from '@anticrm/presentation'
import type { Employee, Person } from '@anticrm/contact'
import type { Candidate } from '@anticrm/recruit'
import Address from './icons/Address.svelte'
import Attachment from './icons/Attachment.svelte'
import { getClient } from '@anticrm/presentation'
import core from '@anticrm/core'
import recruit from '../plugin'
import type { Employee } from '@anticrm/contact'
import contact from '@anticrm/contact'
import view from '@anticrm/view'
import type { Ref, Space } from '@anticrm/core'
import { OK, Severity, Status } from '@anticrm/platform'
import { Card, getClient, UserBox } from '@anticrm/presentation'
import type { Candidate } from '@anticrm/recruit'
import type { SpaceWithStates } from '@anticrm/task'
import task from '@anticrm/task'
import { Grid, Status as StatusControl } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import recruit from '../plugin'
export let space: Ref<SpaceWithStates>
export let candidate: Ref<Candidate>
export let employee: Ref<Employee>
export let assignee: Ref<Employee>
export let preserveCandidate = false
@ -43,21 +38,21 @@
const client = getClient()
export function canClose (): boolean {
return candidate === undefined && employee === undefined
return candidate === undefined && assignee === undefined
}
async function createApplication () {
const state = await client.findOne(core.class.State, { space: _space })
const state = await client.findOne(task.class.State, { space: _space })
if (state === undefined) {
throw new Error('create application: state not found')
}
const sequence = await client.findOne(view.class.Sequence, { attachedTo: recruit.class.Applicant })
const sequence = await client.findOne(task.class.Sequence, { attachedTo: recruit.class.Applicant })
if (sequence === undefined) {
throw new Error('sequence object not found')
}
const incResult = await client.updateDoc(
view.class.Sequence,
view.space.Sequence,
task.class.Sequence,
task.space.Sequence,
sequence._id,
{
$inc: { sequence: 1 }
@ -74,13 +69,13 @@
state: state._id,
doneState: null,
number: incResult.object.sequence,
employee: employee
assignee: assignee
}
)
}
async function validate (candidate: Ref<Candidate>, space: Ref<Space>) {
if (candidate == undefined) {
if (candidate === undefined) {
status = new Status(Severity.INFO, recruit.status.CandidateRequired, {})
} else {
if (space === undefined) {
@ -120,7 +115,7 @@
_class={contact.class.Employee}
title="Assigned recruiter"
caption="Recruiters"
bind:value={employee}
bind:value={assignee}
allowDeselect
titleDeselect={'Unassign recruiter'}
/>

View File

@ -19,8 +19,8 @@
import core, { Ref } from '@anticrm/core'
import { EditBox, Grid, Dropdown } from '@anticrm/ui'
import { getClient, SpaceCreateCard } from '@anticrm/presentation'
import view, { KanbanTemplate, createKanban } from '@anticrm/view'
import { KanbanTemplateSelector } from '@anticrm/view-resources'
import task, { KanbanTemplate, createKanban } from '@anticrm/task'
import { KanbanTemplateSelector } from '@anticrm/task-resources'
import Company from './icons/Company.svelte'
import Vacancy from './icons/Vacancy.svelte'
@ -40,7 +40,7 @@
const client = getClient()
async function createVacancy() {
if (templateId !== undefined && await client.findOne(view.class.KanbanTemplate, { _id: templateId }) === undefined) {
if (templateId !== undefined && await client.findOne(task.class.KanbanTemplate, { _id: templateId }) === undefined) {
throw Error(`Failed to find target kanban template: ${templateId}`)
}

View File

@ -57,7 +57,6 @@
<div class="attachments">
<Attachments objectId={object._id} _class={object._class} space={object.space} />
</div>
</Panel>
{/if}
@ -65,7 +64,7 @@
.attachments {
margin-top: 3.5rem;
}
.grid-cards {
display: grid;
grid-template-columns: 1fr 1fr;

View File

@ -166,7 +166,7 @@
<div class="mt-14">
<Applications objectId={object._id} _class={object._class} space={object.space} />
</div>
<div class="mt-14">
<Attachments
objectId={object._id}

View File

@ -1,30 +1,29 @@
//
// 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 { Ref } from '@anticrm/core'
import type { IntlString, StatusCode } from '@anticrm/platform'
import { mergeIds } from '@anticrm/platform'
import type { IntlString, StatusCode } from '@anticrm/platform'
import type { Ref, Class } from '@anticrm/core'
import type { AnyComponent } from '@anticrm/ui'
import type { Applicant, Candidate, Candidates, Vacancy } from '@anticrm/recruit'
import recruit, { recruitId } from '@anticrm/recruit'
import { KanbanTemplateSpace } from '@anticrm/task'
export default mergeIds(recruitId, recruit, {
status: {
ApplicationExists: '' as StatusCode,
CandidateRequired: '' as StatusCode,
VacancyRequired: '' as StatusCode,
VacancyRequired: '' as StatusCode
},
string: {
CreateVacancy: '' as IntlString,
@ -41,6 +40,9 @@ export default mergeIds(recruitId, recruit, {
NoAttachmentsForCandidate: '' as IntlString,
FirstName: '' as IntlString,
LastName: '' as IntlString,
LastName: '' as IntlString
},
space: {
VacancyTemplates: '' as Ref<KanbanTemplateSpace>
}
})

View File

@ -30,6 +30,6 @@
"@anticrm/core": "~0.6.11",
"@anticrm/contact": "~0.6.2",
"@anticrm/chunter": "~0.6.0",
"@anticrm/view": "~0.6.0"
"@anticrm/task": "~0.6.0"
}
}

View File

@ -13,11 +13,11 @@
// limitations under the License.
//
import type { Person } from '@anticrm/contact'
import type { Class, Ref, Space, Timestamp } from '@anticrm/core'
import type { Asset, Plugin } from '@anticrm/platform'
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'
import type { SpaceWithStates, Task } from '@anticrm/task'
/**
* @public
@ -49,10 +49,9 @@ export interface Candidate extends Person {
/**
* @public
*/
export interface Applicant extends DocWithState, AttachedDoc {
export interface Applicant extends Task {
attachments?: number
comments?: number
employee: Ref<Employee> | null
}
/**
@ -73,8 +72,5 @@ export default plugin(recruitId, {
Location: '' as Asset,
Calendar: '' as Asset,
Create: '' as Asset
},
space: {
VacancyTemplates: '' as Ref<KanbanTemplateSpace>
}
})

View File

@ -38,6 +38,8 @@
"@anticrm/presentation": "~0.6.2",
"@anticrm/view": "~0.6.0",
"@anticrm/view-resources": "~0.6.0",
"@anticrm/task": "~0.6.0",
"@anticrm/task-resources": "~0.6.0",
"@anticrm/workbench": "~0.6.1"
}
}

View File

@ -17,14 +17,14 @@
<script lang="ts">
import { createQuery } from '@anticrm/presentation'
import { Label, Component } from '@anticrm/ui'
import view, { KanbanTemplateSpace } from '@anticrm/view'
import task, { KanbanTemplateSpace } from '@anticrm/task'
import setting from '@anticrm/setting'
export let folder: KanbanTemplateSpace | undefined
let folders: KanbanTemplateSpace[] = []
const query = createQuery()
$: query.query(view.class.KanbanTemplateSpace, {}, (result) => { folders = result })
$: query.query(task.class.KanbanTemplateSpace, {}, (result) => { folders = result })
$: if (folder === undefined && folders.length > 0) {
folder = folders[0]

View File

@ -18,8 +18,8 @@
import type { Ref, Space, Doc, Class } from '@anticrm/core'
import { getClient, MessageBox } from '@anticrm/presentation'
import { Label, Icon, showPopup } from '@anticrm/ui'
import type { KanbanTemplate, KanbanTemplateSpace, StateTemplate } from '@anticrm/view'
import { KanbanTemplateEditor } from '@anticrm/view-resources'
import type { KanbanTemplate, KanbanTemplateSpace, StateTemplate } from '@anticrm/task'
import { KanbanTemplateEditor } from '@anticrm/task-resources'
import setting from '@anticrm/setting'
import Folders from './Folders.svelte'
@ -40,14 +40,14 @@
}
showPopup(MessageBox, {
label: 'Delete status',
message: 'Do you want to delete this status?'
}, undefined, async (result) => {
if (result && template !== undefined) {
await client.updateDoc(template._class, template.space, template._id, { $pull: { states: state._id }})
await client.removeCollection(state._class, template.space, state._id, template._id, template._class, 'statesC')
}
})
label: 'Delete status',
message: 'Do you want to delete this status?'
}, undefined, async (result) => {
if (result && template !== undefined) {
await client.updateDoc(template._class, template.space, template._id, { $pull: { states: state._id } })
await client.removeCollection(state._class, template.space, state._id, template._id, template._class, 'statesC')
}
})
}
</script>

View File

@ -19,7 +19,7 @@
import type { Ref } from '@anticrm/core'
import { AttributeEditor, createQuery, getClient } from '@anticrm/presentation'
import { CircleButton, IconAdd, IconMoreH, Label, showPopup } from '@anticrm/ui'
import view, { KanbanTemplate, KanbanTemplateSpace, LostStateTemplate, WonStateTemplate } from '@anticrm/view'
import task, { KanbanTemplate, KanbanTemplateSpace, LostStateTemplate, WonStateTemplate } from '@anticrm/task'
import { ContextMenu } from '@anticrm/view-resources'
import setting from '@anticrm/setting'
@ -30,7 +30,7 @@
let templateMap = new Map<Ref<KanbanTemplate>, KanbanTemplate>()
const templatesQ = createQuery()
$: if (folder !== undefined) {
templatesQ.query(view.class.KanbanTemplate, { space: folder._id }, (result) => {
templatesQ.query(task.class.KanbanTemplate, { space: folder._id }, (result) => {
templates = result
})
}
@ -51,7 +51,7 @@
const space = folder._id
const template = await client.createDoc(view.class.KanbanTemplate, space, {
const template = await client.createDoc(task.class.KanbanTemplate, space, {
states: [],
doneStates: [],
doneStatesC: 0,
@ -62,12 +62,12 @@
const doneStates = [
{
id: generateId<WonStateTemplate>(),
class: view.class.WonStateTemplate,
class: task.class.WonStateTemplate,
title: 'Won'
},
{
id: generateId<LostStateTemplate>(),
class: view.class.LostStateTemplate,
class: task.class.LostStateTemplate,
title: 'Lost'
}
]
@ -77,7 +77,7 @@
ds.class,
space,
template,
view.class.KanbanTemplate,
task.class.KanbanTemplate,
'doneStatesC',
{
title: ds.title
@ -87,7 +87,7 @@
}))
for (const ds of doneStates) {
await client.updateDoc(view.class.KanbanTemplate, space, template, {
await client.updateDoc(task.class.KanbanTemplate, space, template, {
$push: {
doneStates: ds.id
}
@ -108,7 +108,7 @@
<div class="content">
{#each templates as t (t._id)}
<div class="item flex-between" class:selected={t._id === template?._id} on:click={() => select(t)}>
<AttributeEditor maxWidth="20rem" _class={view.class.KanbanTemplate} object={t} key="title"/>
<AttributeEditor maxWidth="20rem" _class={task.class.KanbanTemplate} object={t} key="title"/>
<div class="tool hover-trans"
on:click|stopPropagation={(ev) => {
showPopup(ContextMenu, { object: t }, ev.target, () => {})

View File

@ -5,4 +5,11 @@
<path d="M19.2,11.4c-0.3,0-0.6,0.3-0.6,0.6v6.6c0,0.7-0.5,1.3-1.2,1.3H4.8c-0.7,0-1.2-0.6-1.2-1.3V5.4 c0-0.7,0.5-1.3,1.2-1.3h9.9c0.3,0,0.6-0.3,0.6-0.6S15,2.9,14.7,2.9H4.8C3.5,2.9,2.4,4,2.4,5.4v13.2c0,1.4,1.1,2.5,2.4,2.5h12.6 c1.3,0,2.4-1.1,2.4-2.5V12C19.8,11.7,19.5,11.4,19.2,11.4z"/>
</g>
</symbol>
<symbol id="kanban" viewBox="0 0 16 16">
<path d="M14,0h-2.8H9.6H6.4H4.8H2C0.9,0,0,0.9,0,2v12c0,1.1,0.9,2,2,2h2.8h1.6h3.2h1.6H14c1.1,0,2-0.9,2-2V2C16,0.9,15.1,0,14,0z M2,14.8c-0.4,0-0.8-0.4-0.8-0.8V2c0-0.4,0.4-0.8,0.8-0.8h2.8v13.6H2z M6.4,14.8V1.2h3.2v13.6H6.4z M14.8,14c0,0.4-0.4,0.8-0.8,0.8 h-2.8V1.2H14c0.4,0,0.8,0.4,0.8,0.8V14z"/>
</symbol>
<symbol id='status' viewBox="0 0 16 16">
<path d="M14.4,8c0,3.5-2.9,6.4-6.4,6.4c-3.5,0-6.4-2.9-6.4-6.4c0-3.5,2.9-6.4,6.4-6.4c0.1-0.4,0.3-0.8,0.5-1.2c-0.2,0-0.3,0-0.5,0 C3.8,0.4,0.4,3.8,0.4,8c0,4.2,3.4,7.6,7.6,7.6s7.6-3.4,7.6-7.6c0-0.2,0-0.3,0-0.5C15.2,7.7,14.8,7.9,14.4,8z"/>
<circle cx="13" cy="3" r="3"/>
</symbol>
</svg>

Before

Width:  |  Height:  |  Size: 622 B

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -16,6 +16,8 @@
"NoAttachmentsForTask": "There are no attachments for this task.",
"AssigneeRequired": "Assignee is required",
"More": "Options",
"TaskUnAssign": "Unassign"
"TaskUnAssign": "Unassign",
"NoTaskForObject": "No tasks defined",
"Delete": "Delete"
}
}

View File

@ -19,6 +19,8 @@ import task, {taskId} from '@anticrm/task'
const icons = require('../assets/icons.svg')
loadMetadata(task.icon, {
Task: `${icons}#task`,
Kanban: `${icons}#kanban`,
Status: `${icons}#status`
})
addStringsLoader(taskId, async (lang: string) => await import(`../lang/${lang}.json`))

View File

@ -44,6 +44,7 @@
"@anticrm/view-resources": "~0.6.0",
"@anticrm/login": "~0.6.1",
"@anticrm/attachment-resources": "~0.6.0",
"@anticrm/chunter-resources": "~0.6.0"
"@anticrm/chunter-resources": "~0.6.0",
"@anticrm/workbench": "~0.6.1"
}
}

View File

@ -18,8 +18,8 @@
import { EditBox, Grid, IconFolder, ToggleWithLabel } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import task from '../plugin'
import view, { createKanban, KanbanTemplate } from '@anticrm/view'
import { KanbanTemplateSelector } from '@anticrm/view-resources'
import { createKanban, KanbanTemplate } from '@anticrm/task'
import KanbanTemplateSelector from './kanban/KanbanTemplateSelector.svelte'
const dispatch = createEventDispatcher()
@ -34,7 +34,7 @@
const client = getClient()
async function createProject (): Promise<void> {
if (templateId !== undefined && await client.findOne(view.class.KanbanTemplate, { _id: templateId }) === undefined) {
if (templateId !== undefined && await client.findOne(task.class.KanbanTemplate, { _id: templateId }) === undefined) {
throw Error(`Failed to find target kanban template: ${templateId}`)
}

View File

@ -14,53 +14,58 @@
-->
<script lang="ts">
import contact, { Employee } from '@anticrm/contact'
import type { Data, Ref, Space } from '@anticrm/core'
import core from '@anticrm/core'
import type { AttachedData, Data, Doc, Ref, Space } from '@anticrm/core'
import { generateId } from '@anticrm/core'
import { OK, Status } from '@anticrm/platform'
import { Card, getClient, UserBox } from '@anticrm/presentation'
import { Task } from '@anticrm/task'
import { Issue, State } from '@anticrm/task'
import { EditBox, Grid, Status as StatusControl } from '@anticrm/ui'
import view from '@anticrm/view'
import { createEventDispatcher } from 'svelte'
import task from '../plugin'
export let space: Ref<Space>
export let parent: Pick<Doc, '_id' | '_class'> | undefined
let _space = space
$: _space = space
const status: Status = OK
let assignee: Ref<Employee> | null = null
const object: Data<Task> = {
const object: Data<Issue> = {
name: '',
description: '',
assignee: null,
number: 0
number: 0,
attachedTo: task.global.Task,
attachedToClass: task.class.Issue,
collection: 'tasks',
state: '' as Ref<State>
}
const dispatch = createEventDispatcher()
const client = getClient()
const taskId = generateId()
const taskId: Ref<Issue> = generateId()
export function canClose (): boolean {
return object.name !== ''
}
async function createTask () {
const state = await client.findOne(core.class.State, { space: _space })
const state = await client.findOne(task.class.State, { space: _space })
if (state === undefined) {
throw new Error('create application: state not found')
}
const sequence = await client.findOne(view.class.Sequence, { attachedTo: task.class.Task })
const sequence = await client.findOne(task.class.Sequence, { attachedTo: task.class.Issue })
if (sequence === undefined) {
throw new Error('sequence object not found')
}
const incResult = await client.updateDoc(
view.class.Sequence,
view.space.Sequence,
task.class.Sequence,
task.space.Sequence,
sequence._id,
{
$inc: { sequence: 1 }
@ -68,7 +73,7 @@
true
)
const value: Data<Task> = {
const value: AttachedData<Issue> = {
name: object.name,
description: object.description,
assignee,
@ -77,8 +82,7 @@
state: state._id
}
await client.createDoc(task.class.Task, _space, value, taskId)
dispatch('close')
await client.addCollection(task.class.Issue, _space, parent?._id ?? task.global.Task, parent?._class ?? task.class.Issue, 'tasks', value, taskId)
}
</script>
@ -87,7 +91,7 @@
<Card
label={task.string.CreateTask}
okAction={createTask}
canSave={object.name.length > 0}
canSave={object.name.length > 0 && _space != null}
spaceClass={task.class.Project}
spaceLabel={task.string.ProjectName}
spacePlaceholder={task.string.SelectProject}

View File

@ -16,7 +16,7 @@
import type { Ref } from '@anticrm/core'
import { Panel } from '@anticrm/panel'
import { createQuery, getClient } from '@anticrm/presentation'
import type { Task } from '@anticrm/task'
import type { Issue } from '@anticrm/task'
import { EditBox, Grid } from '@anticrm/ui'
import view from '@anticrm/view'
import { createEventDispatcher } from 'svelte'
@ -24,11 +24,11 @@
import { Attachments } from '@anticrm/attachment-resources'
import TaskHeader from './TaskHeader.svelte'
export let _id: Ref<Task>
let object: Task
export let _id: Ref<Issue>
let object: Issue
const query = createQuery()
$: query.query(task.class.Task, { _id }, (result) => {
$: query.query(task.class.Issue, { _id }, (result) => {
object = result[0]
})
@ -36,7 +36,7 @@
const client = getClient()
function change (field: string, value: any) {
client.updateDoc(task.class.Task, object.space, object._id, { [field]: value })
client.updateCollection(object._class, object.space, object._id, object.attachedTo, object.attachedToClass, object.collection, { [field]: value })
}
</script>

View File

@ -18,13 +18,13 @@
import { formatName } from '@anticrm/contact'
import type { WithLookup } from '@anticrm/core'
import { Avatar } from '@anticrm/presentation'
import type { Task } from '@anticrm/task'
import type { Issue } 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 object: WithLookup<Issue>
export let draggable: boolean
const showMenu = (ev?: Event): void => {

View File

@ -16,14 +16,14 @@
<script lang="ts">
import contact from '@anticrm/contact'
import { AttributeBarEditor, getClient, UserBox } from '@anticrm/presentation'
import { Task } from '@anticrm/task'
import { Issue } from '@anticrm/task'
import task from '../plugin'
export let object: Task
export let object: Issue
const client = getClient()
function change () {
client.updateDoc(object._class, object.space, object._id, { assignee: object.assignee })
client.updateCollection(object._class, object.space, object._id, object.attachedTo, object.attachedToClass, object.collection, { assignee: object.assignee })
}
</script>

View File

@ -14,13 +14,13 @@
// limitations under the License.
-->
<script lang="ts">
import type { Task } from '@anticrm/task'
import type { Issue } 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: Issue
const client = getClient()
const shortLabel = client.getHierarchy().getClass(value._class).shortLabel

View File

@ -0,0 +1,93 @@
<!--
// 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.
-->
<script lang="ts">
import type { Ref, Space, Doc, Class } from '@anticrm/core'
import type { Issue } from '@anticrm/task'
import { createQuery } from '@anticrm/presentation'
import { CircleButton, IconAdd, showPopup, Label } from '@anticrm/ui'
import CreateTask from './CreateTask.svelte'
// import FileDuo from "./icons/FileDuo.svelte"
import { Table } from '@anticrm/view-resources'
import core from '@anticrm/core'
import task from '../plugin'
export let objectId: Ref<Doc>
export let space: Ref<Space>
export let _class: Ref<Class<Doc>>
let tasks: Issue[] = []
const query = createQuery()
$: query.query(task.class.Issue, { attachedTo: objectId }, result => { tasks = result })
const createApp = (ev: MouseEvent): void =>
showPopup(CreateTask, { parent: { _id: objectId, _class, space } }, ev.target as HTMLElement, () => {})
</script>
<div class="applications-container">
<div class="flex-row-center">
<div class="title">Tasks</div>
<CircleButton icon={IconAdd} size={'small'} on:click={createApp} />
</div>
{#if tasks.length > 0}
<Table
_class={task.class.Issue}
config={['', '$lookup.space.name', '$lookup.state']}
options={
{
lookup: {
state: task.class.State,
space: core.class.Space
}
}
}
query={ { attachedTo: objectId } }
/>
{:else}
<div class="flex-col-center mt-5 createapp-container">
<!-- <FileDuo size={'large'} /> -->
<div class="small-text content-dark-color mt-2">
<Label label={task.string.NoTaskForObject} />
</div>
<div class="small-text">
<a href={'#'} on:click={createApp}><Label label={task.string.CreateTask} /></a>
</div>
</div>
{/if}
</div>
<style lang="scss">
.applications-container {
display: flex;
flex-direction: column;
.title {
margin-right: .75rem;
font-weight: 500;
font-size: 1.25rem;
color: var(--theme-caption-color);
}
}
.createapp-container {
padding: 1rem;
color: var(--theme-caption-color);
background: var(--theme-bg-accent-color);
border: 1px solid var(--theme-bg-accent-color);
border-radius: .75rem;
}
</style>

View File

@ -14,12 +14,12 @@
// limitations under the License.
-->
<script lang="ts">
import type { Ref, State, Doc, DoneState } from '@anticrm/core'
import type { Ref, Doc } from '@anticrm/core'
import { createQuery, getClient } from '@anticrm/presentation'
import type { Kanban } from '@anticrm/view'
import type { Kanban, State, DoneState } from '@anticrm/task'
import task from '@anticrm/task'
import core from '@anticrm/core'
import StatesEditor from './StatesEditor.svelte'
import StatesEditor from '../state/StatesEditor.svelte'
export let kanban: Kanban
@ -28,12 +28,12 @@
let doneStates: DoneState[] = []
let wonStates: DoneState[] = []
let lostStates: DoneState[] = []
$: wonStates = doneStates.filter((x) => x._class === core.class.WonState)
$: lostStates = doneStates.filter((x) => x._class === core.class.LostState)
$: wonStates = doneStates.filter((x) => x._class === task.class.WonState)
$: lostStates = doneStates.filter((x) => x._class === task.class.LostState)
const client = getClient()
function sort <T extends Doc>(order: Ref<T>[], items: T[]): T[] {
function sort <T extends Doc> (order: Ref<T>[], items: T[]): T[] {
if (items.length === 0) {
return []
}
@ -47,10 +47,10 @@
}
const statesQ = createQuery()
$: statesQ.query(core.class.State, { _id: { $in: kanban.states ?? [] } }, result => { states = sort(kanban.states, result) })
$: statesQ.query(task.class.State, { _id: { $in: kanban.states ?? [] } }, result => { states = sort(kanban.states, result) })
const doneStatesQ = createQuery()
$: doneStatesQ.query(core.class.DoneState, { _id: { $in: kanban.doneStates }}, (result) => { doneStates = sort(kanban.doneStates, result) })
$: doneStatesQ.query(task.class.DoneState, { _id: { $in: kanban.doneStates } }, (result) => { doneStates = sort(kanban.doneStates, result) })
async function onMove ({ detail: { stateID, position } }: { detail: { stateID: Ref<State>, position: number } }) {
client.updateDoc(kanban._class, kanban.space, kanban._id, {
@ -64,7 +64,7 @@
}
async function onAdd () {
const state = await client.createDoc(core.class.State, kanban.space, {
const state = await client.createDoc(task.class.State, kanban.space, {
title: 'New State',
color: '#7C6FCD'
})

View File

@ -15,13 +15,13 @@
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { Ref, State, Space, Doc, generateId } from '@anticrm/core'
import { Ref, Space, Doc, generateId } from '@anticrm/core'
import core from '@anticrm/core'
import { createQuery, getClient } from '@anticrm/presentation'
import type { DoneStateTemplate, KanbanTemplate, StateTemplate } from '@anticrm/view'
import view from '@anticrm/view'
import type { State, DoneStateTemplate, KanbanTemplate, StateTemplate } from '@anticrm/task'
import task from '@anticrm/task'
import StatesEditor from './StatesEditor.svelte'
import StatesEditor from '../state/StatesEditor.svelte'
export let kanban: KanbanTemplate
@ -29,8 +29,8 @@
let doneStates: DoneStateTemplate[] = []
let wonStates: DoneStateTemplate[] = []
let lostStates: DoneStateTemplate[] = []
$: wonStates = doneStates.filter((x) => x._class === view.class.WonStateTemplate)
$: lostStates = doneStates.filter((x) => x._class === view.class.LostStateTemplate)
$: wonStates = doneStates.filter((x) => x._class === task.class.WonStateTemplate)
$: lostStates = doneStates.filter((x) => x._class === task.class.LostStateTemplate)
const dispatch = createEventDispatcher()
const client = getClient()
@ -49,10 +49,10 @@
}
const statesQ = createQuery()
$: statesQ.query(view.class.StateTemplate, { attachedTo: kanban._id }, result => { states = sort(kanban.states, result) })
$: statesQ.query(task.class.StateTemplate, { attachedTo: kanban._id }, result => { states = sort(kanban.states, result) })
const doneStatesQ = createQuery()
$: doneStatesQ.query(view.class.DoneStateTemplate, { attachedTo: kanban._id }, (result) => { doneStates = sort(kanban.doneStates, result) })
$: doneStatesQ.query(task.class.DoneStateTemplate, { attachedTo: kanban._id }, (result) => { doneStates = sort(kanban.doneStates, result) })
let space: Space | undefined
const spaceQ = createQuery()
@ -72,7 +72,7 @@
async function onAdd () {
const stateID = generateId<StateTemplate>()
await client.addCollection(
view.class.StateTemplate,
task.class.StateTemplate,
kanban.space,
kanban._id,
kanban._class,

View File

@ -14,18 +14,18 @@
-->
<script lang="ts">
import type { Ref } from '@anticrm/core'
import { createQuery } from '@anticrm/presentation';
import { createQuery } from '@anticrm/presentation'
import { DumbDropdown } from '@anticrm/ui'
import type { DumbDropdownItem } from '@anticrm/ui/src/types';
import type { KanbanTemplate, KanbanTemplateSpace } from '@anticrm/view'
import view from '@anticrm/view'
import type { DumbDropdownItem } from '@anticrm/ui/src/types'
import type { KanbanTemplate, KanbanTemplateSpace } from '@anticrm/task'
import task from '@anticrm/task'
export let folders: Ref<KanbanTemplateSpace>[]
export let template: Ref<KanbanTemplate> | undefined = undefined
let templates: KanbanTemplate[] = []
const templatesQ = createQuery()
$: templatesQ.query(view.class.KanbanTemplate, { space: { $in: folders }}, (result) => { templates = result })
$: templatesQ.query(task.class.KanbanTemplate, { space: { $in: folders } }, (result) => { templates = result })
let items: DumbDropdownItem[] = []
$: items = [

View File

@ -15,14 +15,14 @@
-->
<script lang="ts">
import type { AttachedDoc, Class, Doc, DoneState, FindOptions, LostState, Ref, SpaceWithStates, State, TxCUD, WonState } from '@anticrm/core'
import type { AttachedDoc, Class, Doc, FindOptions, Ref } from '@anticrm/core'
import core from '@anticrm/core'
import { getResource } from '@anticrm/platform'
import { createQuery, getClient } from '@anticrm/presentation'
import type { Kanban, SpaceWithStates, State, Task } from '@anticrm/task'
import task, { DoneState, LostState, WonState } from '@anticrm/task'
import type { AnySvelteComponent } from '@anticrm/ui'
import { AnyComponent, Loading, ScrollBox } from '@anticrm/ui'
import type { Kanban } from '@anticrm/view'
import view from '@anticrm/view'
import KanbanPanel from './KanbanPanel.svelte'
import KanbanPanelEmpty from './KanbanPanelEmpty.svelte'
@ -46,7 +46,7 @@
let lostState: LostState | undefined
const kanbanQuery = createQuery()
$: kanbanQuery.query(view.class.Kanban, { attachedTo: space }, result => { kanban = result[0] })
$: kanbanQuery.query(task.class.Kanban, { attachedTo: space }, result => { kanban = result[0] })
$: kanbanStates = kanban?.states ?? []
$: kanbanDoneStates = kanban?.doneStates ?? []
@ -65,14 +65,14 @@
}
const statesQuery = createQuery()
$: if (kanbanStates.length > 0) statesQuery.query(core.class.State, { _id: { $in: kanbanStates } }, result => { rawStates = result })
$: if (kanbanStates.length > 0) statesQuery.query(task.class.State, { _id: { $in: kanbanStates } }, result => { rawStates = result })
$: states = sort(kanban, rawStates)
const doneStatesQ = createQuery()
$: if (kanbanDoneStates.length > 0) {
doneStatesQ.query(core.class.DoneState, { _id: { $in: kanbanDoneStates }}, (result) => {
wonState = result.find((x) => x._class === core.class.WonState)
lostState = result.find((x) => x._class === core.class.LostState)
doneStatesQ.query(task.class.DoneState, { _id: { $in: kanbanDoneStates } }, (result) => {
wonState = result.find((x) => x._class === task.class.WonState)
lostState = result.find((x) => x._class === task.class.LostState)
})
}
@ -91,7 +91,6 @@
async function move (state: Ref<State>) {
const id = dragCard._id
const txes: TxCUD<Doc>[] = []
if (dragCardInitialState !== state) {
if (client.getHierarchy().isDerived(_class, core.class.AttachedDoc)) {
@ -99,12 +98,12 @@
// We need to update using updateCollection
client.updateCollection(_class, space, id as Ref<Doc> as Ref<AttachedDoc>, adoc.attachedTo, adoc.attachedToClass, adoc.collection, { state })
} else {
client.updateDoc(_class, space, id, { state })
client.updateDoc<Task>(_class, space, id as Ref<Task>, { state })
}
}
if (dragCardInitialPosition !== dragCardEndPosition) {
client.updateDoc(view.class.Kanban, space, kanban._id, {
client.updateDoc(task.class.Kanban, space, kanban._id, {
$move: {
order: {
$value: id,
@ -124,17 +123,17 @@
async function cardPresenter (_class: Ref<Class<Doc>>): Promise<AnySvelteComponent> {
const clazz = client.getHierarchy().getClass(_class)
const presenterMixin = client.getHierarchy().as(clazz, view.mixin.KanbanCard)
const presenterMixin = client.getHierarchy().as(clazz, task.mixin.KanbanCard)
return await getResource(presenterMixin.card)
}
const onDone = (state: DoneState) => async () => {
if (client.getHierarchy().isDerived(_class, core.class.AttachedDoc)) {
const adoc: AttachedDoc = dragCard as Doc as AttachedDoc
await client.updateCollection(
await client.updateCollection<Doc, Task>(
_class,
space,
adoc._id as Ref<Doc> as Ref<AttachedDoc>,
adoc._id as Ref<Task>,
adoc.attachedTo,
adoc.attachedToClass,
adoc.collection,

View File

@ -0,0 +1,16 @@
<script lang="ts">
const fill: string = 'var(--theme-caption-color)'
</script>
<svg {fill} viewBox="0 0 6 16" xmlns="http://www.w3.org/2000/svg">
<circle cx="1" cy="1" r="1"/>
<circle cx="4.5" cy="1" r="1"/>
<circle cx="1" cy="4.5" r="1"/>
<circle cx="4.5" cy="4.5" r="1"/>
<circle cx="1" cy="8" r="1"/>
<circle cx="4.5" cy="8" r="1"/>
<circle cx="1" cy="11.5" r="1"/>
<circle cx="4.5" cy="11.5" r="1"/>
<circle cx="1" cy="15" r="1"/>
<circle cx="4.5" cy="15" r="1"/>
</svg>

View File

@ -0,0 +1,50 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// 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 { createEventDispatcher } from 'svelte'
import PopupDialog from './PopupDialog.svelte'
export let colors: string[] = ['#A5D179', '#77C07B', '#60B96E', '#45AEA3', '#46CBDE', '#47BDF6',
'#5AADF6', '#73A6CD', '#B977CB', '#7C6FCD', '#6F7BC5', '#F28469']
export let columns: number = 5
const dispatch = createEventDispatcher()
</script>
<PopupDialog label={'CHOOSE A COLOR'}>
<div class="color-grid" style="grid-template-columns: repeat({columns}, 1.5rem)">
{#each colors as color}
<div class="color" style="background-color: {color}" on:click={() => { dispatch('close', color) }} />
{/each}
</div>
</PopupDialog>
<style lang="scss">
.color-grid {
display: grid;
grid-auto-rows: 1.5rem;
gap: 1rem;
.color {
border: 1px solid transparent;
border-radius: 50%;
cursor: pointer;
&:hover { border-color: var(--theme-button-border-focused); }
}
}
</style>

View File

@ -15,19 +15,15 @@
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import type { Ref, SpaceWithStates, State, Class, Obj, Space } from '@anticrm/core'
import { Label, showPopup } from '@anticrm/ui'
import { createQuery, getClient, MessageBox } from '@anticrm/presentation'
import type { Kanban } from '@anticrm/view'
import { KanbanEditor } from '@anticrm/view-resources'
import Close from './icons/Close.svelte'
import Status from './icons/Status.svelte'
import workbench from '../plugin'
import type { Class, Obj, Ref } from '@anticrm/core'
import core from '@anticrm/core'
import view from '@anticrm/view'
import { createQuery, getClient, MessageBox } from '@anticrm/presentation'
import type { Kanban, SpaceWithStates, State } from '@anticrm/task'
import task from '@anticrm/task'
import KanbanEditor from '../kanban/KanbanEditor.svelte'
import { Icon, IconClose, Label, showPopup } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import workbench from '@anticrm/workbench'
export let _id: Ref<SpaceWithStates>
export let spaceClass: Ref<Class<Obj>>
@ -40,7 +36,7 @@
const dispatch = createEventDispatcher()
const kanbanQ = createQuery()
$: kanbanQ.query(view.class.Kanban, { attachedTo: _id }, result => { kanban = result[0] })
$: kanbanQ.query(task.class.Kanban, { attachedTo: _id }, result => { kanban = result[0] })
const spaceQ = createQuery()
$: spaceQ.query<Class<SpaceWithStates>>(core.class.Class, { _id: spaceClass }, result => { spaceClassInstance = result.shift() })
@ -48,7 +44,7 @@
const spaceI = createQuery()
$: spaceI.query<SpaceWithStates>(spaceClass, { _id: _id }, result => { spaceInstance = result.shift() })
async function deleteState({ state }: { state: State }) {
async function deleteState ({ state }: { state: State }) {
if (spaceInstance === undefined) {
return
}
@ -82,12 +78,12 @@
<div class="flex-between header">
<div class="flex-grow flex-col">
<div class="flex-row-center">
<div class="icon"><Status size={'small'} /></div>
<div class="icon"><Icon icon={task.icon.Status} size={'small'} /></div>
<span class="overflow-label title">Manage application statuses within <Label label={spaceClassInstance?.label}/></span>
</div>
<div class="overflow-label subtitle">{spaceInstance?.name}</div>
</div>
<div class="tool" on:click={() => dispatch('close')}><Close size={'small'} /></div>
<div class="tool" on:click={() => dispatch('close')}><IconClose size={'small'} /></div>
</div>
<div class="flex-grow flex-col content">
{#if kanban !== undefined}

View File

@ -0,0 +1,66 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// 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 type { IntlString } from '@anticrm/platform'
import { Label } from '@anticrm/ui'
export let label: IntlString
</script>
<div class="card-container">
<div class="card-bg" />
<div class="overflow-label label"><Label {label} /></div>
<div class="content"><slot /></div>
</div>
<style lang="scss">
.card-container {
position: relative;
display: flex;
flex-direction: column;
padding: 32px 1.5rem 1.25rem;
border-radius: .75rem;
.label {
flex-shrink: 0;
margin-bottom: 1.25rem;
text-transform: uppercase;
font-weight: 600;
font-size: .75rem;
color: var(--theme-content-trans-color);
}
.content {
flex-shrink: 0;
flex-grow: 1;
height: fit-content;
}
.card-bg {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: var(--theme-card-bg);
border-radius: .75rem;
backdrop-filter: blur(24px);
box-shadow: var(--theme-card-shadow);
z-index: -1;
}
}
</style>

View File

@ -15,7 +15,8 @@
-->
<script lang="ts">
import core, { Ref, State } from '@anticrm/core'
import { Ref } from '@anticrm/core'
import task, { State } from '@anticrm/task'
import { createQuery } from '@anticrm/presentation'
import { showPopup } from '@anticrm/ui'
import StatePresenter from './StatePresenter.svelte'
@ -28,7 +29,7 @@
let opened: boolean = false
const query = createQuery()
$: query.query(core.class.State, { _id: value }, (res) => {
$: query.query(task.class.State, { _id: value }, (res) => {
state = res[0]
}, { limit: 1 })
</script>
@ -39,7 +40,7 @@
if (!opened) {
opened = true
showPopup(StatesPopup, { space: state.space }, container, (result) => {
if (result) {
if (result) {
value = result._id
onChange(value)
}

View File

@ -14,16 +14,15 @@
// limitations under the License.
-->
<script lang="ts">
import { Ref } from '@anticrm/core'
import { AttributeEditor, getClient } from '@anticrm/presentation'
import type { DoneState, State } from '@anticrm/task'
import { CircleButton, IconAdd, IconMoreH, Label, showPopup } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import type { Ref, State, DoneState } from '@anticrm/core'
import { CircleButton, IconAdd, Label, IconMoreH, showPopup } from '@anticrm/ui'
import { getClient, AttributeEditor } from '@anticrm/presentation'
import Circles from './icons/Circles.svelte'
import ColorsPopup from './ColorsPopup.svelte'
import Circles from './Circles.svelte'
import StatusesPopup from './StatusesPopup.svelte'
import core from '@anticrm/core'
export let states: State[] = []
export let wonStates: DoneState[] = []
export let lostStates: DoneState[] = []

View File

@ -15,15 +15,16 @@
-->
<script lang="ts">
import core, { Ref, SpaceWithStates, State } from "@anticrm/core"
import { createQuery } from "@anticrm/presentation"
import { createEventDispatcher } from "svelte"
import { Ref } from '@anticrm/core'
import { createQuery } from '@anticrm/presentation'
import task, { SpaceWithStates, State } from '@anticrm/task'
import { createEventDispatcher } from 'svelte'
export let space: Ref<SpaceWithStates>
let states: State[] = []
const dispatch = createEventDispatcher()
const statesQuery = createQuery()
statesQuery.query(core.class.State, { space }, (res) => states = res)
statesQuery.query(task.class.State, { space }, (res) => { states = res })
</script>

View File

@ -16,7 +16,7 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { Label, IconDelete as Delete } from '@anticrm/ui'
import view from '@anticrm/view'
import task from '../../plugin'
export let onDelete: () => void
@ -28,7 +28,7 @@
<div class="icon">
<Delete size={'medium'} />
</div>
<div class="flex-grow"><Label label={view.string.Delete} /></div>
<div class="flex-grow"><Label label={task.string.Delete} /></div>
</div>
</div>

View File

@ -21,6 +21,27 @@ import CreateProject from './components/CreateProject.svelte'
import TaskPresenter from './components/TaskPresenter.svelte'
import KanbanCard from './components/KanbanCard.svelte'
import TemplatesIcon from './components/TemplatesIcon.svelte'
import { Doc } from '@anticrm/core'
import { showPopup } from '@anticrm/ui'
import KanbanView from './components/kanban/KanbanView.svelte'
import StateEditor from './components/state/StateEditor.svelte'
import StatePresenter from './components/state/StatePresenter.svelte'
import EditStatuses from './components/state/EditStatuses.svelte'
import { SpaceWithStates } from '@anticrm/task'
export { default as KanbanTemplateEditor } from './components/kanban/KanbanTemplateEditor.svelte'
export { default as KanbanTemplateSelector } from './components/kanban/KanbanTemplateSelector.svelte'
export { default as Tasks } from './components/Tasks.svelte'
async function createTask (object: Doc): Promise<void> {
showPopup(CreateTask, { parent: object._id, space: object.space })
}
async function editStatuses (object: SpaceWithStates): Promise<void> {
showPopup(EditStatuses, { _id: object._id, spaceClass: object._class }, 'right')
}
export default async (): Promise<Resources> => ({
component: {
@ -28,6 +49,13 @@ export default async (): Promise<Resources> => ({
CreateProject,
TaskPresenter,
KanbanCard,
TemplatesIcon
TemplatesIcon,
KanbanView,
StatePresenter,
StateEditor
},
actionImpl: {
CreateTask: createTask,
EditStatuses: editStatuses
}
})

View File

@ -33,7 +33,10 @@ export default mergeIds(taskId, task, {
TaskUnAssign: '' as IntlString,
TaskDescription: '' as IntlString,
NoAttachmentsForTask: '' as IntlString,
More: '' as IntlString
More: '' as IntlString,
UploadDropFilesHere: '' as IntlString,
NoTaskForObject: '' as IntlString,
Delete: '' as IntlString
},
status: {
AssigneeRequired: '' as IntlString

View File

@ -29,6 +29,7 @@
"@anticrm/platform": "~0.6.5",
"@anticrm/core": "~0.6.11",
"@anticrm/contact": "~0.6.2",
"@anticrm/view": "~0.6.0"
"@anticrm/view": "~0.6.0",
"@anticrm/ui": "~0.6.0"
}
}

View File

@ -14,49 +14,186 @@
//
import type { Employee } from '@anticrm/contact'
import type { Class, Data, Doc, DocWithState, DoneState, Ref, Space, State } from '@anticrm/core'
import type { AttachedDoc, Class, Client, Data, Doc, Mixin, Ref, Space, TxOperations } from '@anticrm/core'
import { Arr } from '@anticrm/core'
import type { Asset, Plugin } from '@anticrm/platform'
import { plugin } from '@anticrm/platform'
import core from '@anticrm/core'
import view, { Kanban, KanbanTemplateSpace } from '@anticrm/view'
import type { AnyComponent } from '@anticrm/ui'
import { ViewletDescriptor } from '@anticrm/view'
// S T A T E
/**
* @public
*/
export interface Project extends Space {}
export interface State extends Doc {
title: string
color: string
}
/**
* @public
*/
export interface Task extends DocWithState {
export interface DoneState extends Doc {
title: string
}
/**
* @public
*/
export interface WonState extends DoneState {}
/**
* @public
*/
export interface LostState extends DoneState {}
/**
* @public
*/
export interface Task extends AttachedDoc {
state: Ref<State>
doneState: Ref<DoneState> | null
number: number
assignee: Ref<Employee> | null
}
/**
* @public
*/
export interface SpaceWithStates extends Space {
}
/**
* @public
*/
export interface Project extends SpaceWithStates {}
/**
* @public
*/
export interface Issue extends Task {
number: number // Sequence number
name: string
description: string
assignee: Ref<Employee> | null
comments?: number
attachments?: number
labels?: string
}
/**
* @public
*/
export interface KanbanCard extends Class<Doc> {
card: AnyComponent
}
/**
* @public
*/
export interface Kanban extends Doc {
attachedTo: Ref<Space>
states: Arr<Ref<State>>
doneStates: Arr<Ref<DoneState>>
order: Arr<Ref<Doc>>
}
/**
* @public
*/
export interface Sequence extends Doc {
attachedTo: Ref<Class<Doc>>
sequence: number
}
/**
* @public
*/
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<Ref<StateTemplate>>
doneStates: Arr<Ref<DoneStateTemplate>>
statesC: number
doneStatesC: number
}
/**
* @public
*/
export interface KanbanTemplateSpace extends Space {
icon: AnyComponent
}
/**
* @public
*/
export const taskId = 'task' as Plugin
export default plugin(taskId, {
/**
* @public
*/
const task = plugin(taskId, {
mixin: {
KanbanCard: '' as Ref<Mixin<KanbanCard>>
},
class: {
Issue: '' as Ref<Class<Issue>>,
Project: '' as Ref<Class<Project>>,
State: '' as Ref<Class<State>>,
DoneState: '' as Ref<Class<DoneState>>,
WonState: '' as Ref<Class<WonState>>,
LostState: '' as Ref<Class<LostState>>,
SpaceWithStates: '' as Ref<Class<SpaceWithStates>>,
Task: '' as Ref<Class<Task>>,
Project: '' as Ref<Class<Project>>
Kanban: '' as Ref<Class<Kanban>>,
Sequence: '' as Ref<Class<Sequence>>,
StateTemplate: '' as Ref<Class<StateTemplate>>,
DoneStateTemplate: '' as Ref<Class<DoneStateTemplate>>,
WonStateTemplate: '' as Ref<Class<WonStateTemplate>>,
LostStateTemplate: '' as Ref<Class<LostStateTemplate>>,
KanbanTemplate: '' as Ref<Class<KanbanTemplate>>,
KanbanTemplateSpace: '' as Ref<Class<KanbanTemplateSpace>>
},
viewlet: {
Kanban: '' as Ref<ViewletDescriptor>
},
icon: {
Task: '' as Asset
Task: '' as Asset,
Kanban: '' as Asset,
Status: '' as Asset
},
global: {
// Global task root, if not attached to some other object.
Task: '' as Ref<Issue>
},
space: {
ProjectTemplates: '' as Ref<KanbanTemplateSpace>
ProjectTemplates: '' as Ref<KanbanTemplateSpace>,
Sequence: '' as Ref<Space>
}
})
export default task
/**
* @public
@ -76,7 +213,7 @@ export async function createProjectKanban (
for (const st of states) {
const sid = (projectId + '.state.' + st.name.toLowerCase().replace(' ', '_')) as Ref<State>
await factory(
core.class.State,
task.class.State,
projectId,
{
title: st.name,
@ -88,8 +225,8 @@ export async function createProjectKanban (
}
const rawDoneStates = [
{ class: core.class.WonState, title: 'Won' },
{ class: core.class.LostState, title: 'Lost' }
{ class: task.class.WonState, title: 'Won' },
{ class: task.class.LostState, title: 'Lost' }
]
const doneStates: Array<Ref<DoneState>> = []
for (const st of rawDoneStates) {
@ -106,7 +243,7 @@ export async function createProjectKanban (
}
await factory(
view.class.Kanban,
task.class.Kanban,
projectId,
{
attachedTo: projectId,
@ -114,6 +251,68 @@ export async function createProjectKanban (
doneStates,
order: []
},
(projectId + '.kanban.') as Ref<Kanban>
(projectId + '.kanban') as Ref<Kanban>
)
}
/**
* @public
*/
export async function createKanban (client: Client & TxOperations, attachedTo: Ref<Space>, templateId?: Ref<KanbanTemplate>): Promise<Ref<Kanban>> {
if (templateId === undefined) {
return await client.createDoc(task.class.Kanban, attachedTo, {
attachedTo,
states: [],
doneStates: await Promise.all([
client.createDoc(task.class.WonState, attachedTo, {
title: 'Won'
}),
client.createDoc(task.class.LostState, attachedTo, {
title: 'Lost'
})
]),
order: []
})
}
const template = await client.findOne(task.class.KanbanTemplate, { _id: templateId })
if (template === undefined) {
throw Error(`Failed to find target kanban template: ${templateId}`)
}
const tmplStates = await client.findAll(task.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(task.class.State, attachedTo, { color: state.color, title: state.title }))
)
const doneClassMap = new Map<Ref<Class<DoneStateTemplate>>, Ref<Class<DoneState>>>([
[task.class.WonStateTemplate, task.class.WonState],
[task.class.LostStateTemplate, task.class.LostState]
])
const tmplDoneStates = await client.findAll(task.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<DoneState> => x !== undefined)
return await client.createDoc(task.class.Kanban, attachedTo, {
attachedTo,
states,
doneStates,
order: []
})
}

View File

@ -6,9 +6,6 @@
<rect y="14.3" width="16" height="1.7" />
</g>
</symbol>
<symbol id="kanban" viewBox="0 0 16 16">
<path d="M14,0h-2.8H9.6H6.4H4.8H2C0.9,0,0,0.9,0,2v12c0,1.1,0.9,2,2,2h2.8h1.6h3.2h1.6H14c1.1,0,2-0.9,2-2V2C16,0.9,15.1,0,14,0z M2,14.8c-0.4,0-0.8-0.4-0.8-0.8V2c0-0.4,0.4-0.8,0.8-0.8h2.8v13.6H2z M6.4,14.8V1.2h3.2v13.6H6.4z M14.8,14c0,0.4-0.4,0.8-0.8,0.8 h-2.8V1.2H14c0.4,0,0.8,0.4,0.8,0.8V14z"/>
</symbol>
<symbol id="card" viewBox="0 0 16 16">
<g>
<rect width="2.9" height="2.9" />
@ -28,4 +25,11 @@
<symbol id="move" viewBox="0 0 16 16">
<path d="M9.5,1.3C9.3,1,8.9,1,8.6,1.3c-0.2,0.2-0.2,0.6,0,0.9l5.2,5.2H0.6C0.3,7.4,0,7.7,0,8c0,0.3,0.3,0.6,0.6,0.6 h13.2l-5.2,5.2c-0.2,0.2-0.2,0.6,0,0.9c0.2,0.2,0.6,0.2,0.9,0l6.3-6.3c0.2-0.2,0.2-0.6,0-0.9L9.5,1.3z"/>
</symbol>
<symbol id='more-h' viewBox="0 0 16 16">
<path d="M8,6.6C7.2,6.6,6.6,7.2,6.6,8c0,0.8,0.6,1.4,1.4,1.4S9.4,8.8,9.4,8C9.4,7.2,8.8,6.6,8,6.6z" />
<path d="M3.2,6.6C2.4,6.6,1.8,7.2,1.8,8c0,0.8,0.6,1.4,1.4,1.4S4.6,8.8,4.6,8C4.6,7.2,4,6.6,3.2,6.6z" />
<path d="M12.8,6.6c-0.8,0-1.4,0.6-1.4,1.4c0,0.8,0.6,1.4,1.4,1.4s1.4-0.6,1.4-1.4C14.2,7.2,13.6,6.6,12.8,6.6z" />
</symbol>
</svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -19,9 +19,9 @@ import view, { viewId } from '@anticrm/view'
const icons = require('../assets/icons.svg')
loadMetadata(view.icon, {
Table: `${icons}#table`,
Kanban: `${icons}#kanban`,
Delete: `${icons}#delete`,
Move: `${icons}#move`
Move: `${icons}#move`,
MoreH: `${icons}#more-h`
})
addStringsLoader(viewId, async (lang: string) => await import(`../lang/${lang}.json`))

View File

@ -19,7 +19,7 @@
import PopupDialog from './PopupDialog.svelte'
export let colors: string[] = ['#A5D179', '#77C07B', '#60B96E', '#45AEA3', '#46CBDE', '#47BDF6',
'#5AADF6', '#73A6CD', '#B977CB', '#7C6FCD', '#6F7BC5', '#F28469']
'#5AADF6', '#73A6CD', '#B977CB', '#7C6FCD', '#6F7BC5', '#F28469']
export let columns: number = 5
const dispatch = createEventDispatcher()

View File

@ -21,9 +21,6 @@ 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'
@ -33,9 +30,6 @@ 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 }
@ -69,10 +63,7 @@ export default async (): Promise<Resources> => ({
StringPresenter,
BooleanPresenter,
BooleanEditor,
StatePresenter,
StateEditor,
TableView,
KanbanView,
TimestampPresenter,
DateEditor,
DatePresenter

View File

@ -128,19 +128,28 @@ export async function buildModel (options: BuildModelOptions): Promise<Attribute
return (await Promise.all(model)).filter(a => a !== undefined) as AttributeModel[]
}
function filterActions (client: Client, _class: Ref<Class<Obj>>, targets: ActionTarget[]): Array<Ref<Action>> {
function filterActions (client: Client, _class: Ref<Class<Obj>>, targets: ActionTarget[], derived: Ref<Class<Doc>> = core.class.Doc): Array<Ref<Action>> {
const result: Array<Ref<Action>> = []
const hierarchy = client.getHierarchy()
for (const target of targets) {
if (client.getHierarchy().isDerived(_class, target.target)) {
if (hierarchy.isDerived(_class, target.target) && client.getHierarchy().isDerived(target.target, derived)) {
result.push(target.action)
}
}
return result
}
export async function getActions (client: Client, _class: Ref<Class<Obj>>): Promise<FindResult<Action>> {
/**
* @public
*
* Find all action contributions applicable for specified _class.
* If derivedFrom is specifie, only actions applicable to derivedFrom class will be used.
* So if we have contribution for Doc, Space and we ask for SpaceWithStates and derivedFrom=Space,
* we won't recieve Doc contribution but recieve Space ones.
*/
export async function getActions (client: Client, _class: Ref<Class<Obj>>, derived: Ref<Class<Doc>> = core.class.Doc): Promise<FindResult<Action>> {
const targets = await client.findAll(view.class.ActionTarget, {})
return await client.findAll(view.class.Action, { _id: { $in: filterActions(client, _class, targets) } })
return await client.findAll(view.class.Action, { _id: { $in: filterActions(client, _class, targets, derived) } })
}
export async function deleteObject (client: Client & TxOperations, object: Doc): Promise<void> {

View File

@ -14,11 +14,9 @@
// limitations under the License.
//
import core from '@anticrm/core'
import type { Plugin, Asset, Resource, IntlString } from '@anticrm/platform'
import type { Class, Client, Doc, FindOptions, Mixin, Obj, Ref, Space, UXObject } from '@anticrm/core'
import type { Asset, IntlString, Plugin, Resource } from '@anticrm/platform'
import { plugin } from '@anticrm/platform'
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'
/**
@ -35,13 +33,6 @@ export interface AttributePresenter extends Class<Doc> {
presenter: AnyComponent
}
/**
* @public
*/
export interface KanbanCard extends Class<Doc> {
card: AnyComponent
}
/**
* @public
*/
@ -82,62 +73,6 @@ export interface ActionTarget extends Doc {
action: Ref<Action>
}
/**
* @public
*/
export interface Sequence extends Doc {
attachedTo: Ref<Class<Doc>>
sequence: number
}
/**
* @public
*/
export interface Kanban extends Doc {
attachedTo: Ref<Space>
states: Arr<Ref<State>>
doneStates: Arr<Ref<DoneState>>
order: Arr<Ref<Doc>>
}
/**
* @public
*/
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<Ref<StateTemplate>>
doneStates: Arr<Ref<DoneStateTemplate>>
statesC: number
doneStatesC: number
}
/**
* @public
*/
export interface KanbanTemplateSpace extends Space {
icon: AnyComponent
}
/**
* @public
*/
@ -181,100 +116,22 @@ const view = plugin(viewId, {
mixin: {
AttributeEditor: '' as Ref<Mixin<AttributeEditor>>,
AttributePresenter: '' as Ref<Mixin<AttributePresenter>>,
KanbanCard: '' as Ref<Mixin<KanbanCard>>,
ObjectEditor: '' as Ref<Mixin<ObjectEditor>>
},
class: {
ViewletDescriptor: '' as Ref<Class<ViewletDescriptor>>,
Viewlet: '' as Ref<Class<Viewlet>>,
Action: '' as Ref<Class<Action>>,
ActionTarget: '' as Ref<Class<ActionTarget>>,
Kanban: '' as Ref<Class<Kanban>>,
Sequence: '' as Ref<Class<Sequence>>,
StateTemplate: '' as Ref<Class<StateTemplate>>,
DoneStateTemplate: '' as Ref<Class<DoneStateTemplate>>,
WonStateTemplate: '' as Ref<Class<WonStateTemplate>>,
LostStateTemplate: '' as Ref<Class<LostStateTemplate>>,
KanbanTemplate: '' as Ref<Class<KanbanTemplate>>,
KanbanTemplateSpace: '' as Ref<Class<KanbanTemplateSpace>>
ActionTarget: '' as Ref<Class<ActionTarget>>
},
viewlet: {
Table: '' as Ref<ViewletDescriptor>,
Kanban: '' as Ref<ViewletDescriptor>
},
space: {
Sequence: '' as Ref<Space>
Table: '' as Ref<ViewletDescriptor>
},
icon: {
Table: '' as Asset,
Kanban: '' as Asset,
Delete: '' as Asset,
MoreH: '' as Asset,
Move: '' as Asset
},
string: {
Delete: '' as IntlString
}
})
export default view
/**
* @public
*/
export async function createKanban (client: Client & TxOperations, attachedTo: Ref<Space>, templateId?: Ref<KanbanTemplate>): Promise<Ref<Kanban>> {
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<Class<DoneStateTemplate>>, Ref<Class<DoneState>>>([
[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<DoneState> => x !== undefined)
return await client.createDoc(view.class.Kanban, attachedTo, {
attachedTo,
states,
doneStates,
order: []
})
}

View File

@ -1,9 +0,0 @@
<script lang="ts">
export let size: 'small' | 'medium' | 'large'
const fill: string = 'var(--theme-caption-color)'
</script>
<svg class="svg-{size}" {fill} viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path d="M14.4,8c0,3.5-2.9,6.4-6.4,6.4c-3.5,0-6.4-2.9-6.4-6.4c0-3.5,2.9-6.4,6.4-6.4c0.1-0.4,0.3-0.8,0.5-1.2c-0.2,0-0.3,0-0.5,0 C3.8,0.4,0.4,3.8,0.4,8c0,4.2,3.4,7.6,7.6,7.6s7.6-3.4,7.6-7.6c0-0.2,0-0.3,0-0.5C15.2,7.7,14.8,7.9,14.4,8z"/>
<circle cx="13" cy="3" r="3"/>
</svg>

View File

@ -14,24 +14,18 @@
-->
<script lang="ts">
import { onDestroy } from 'svelte'
import type { Asset, IntlString } from '@anticrm/platform'
import type { Ref, Space, Doc } from '@anticrm/core'
import type { Doc, Ref, Space } from '@anticrm/core'
import core from '@anticrm/core'
import { getResource, IntlString } from '@anticrm/platform'
import { createQuery, getClient } from '@anticrm/presentation'
import { Action, getCurrentLocation, IconAdd, IconEdit, location, navigate, showPopup } from '@anticrm/ui'
import { getActions as getContributedActions } from '@anticrm/view-resources'
import type { SpacesNavModel } from '@anticrm/workbench'
import { Action, navigate, getCurrentLocation, location, IconAdd, IconMoreH, IconEdit } from '@anticrm/ui'
import { getClient, createQuery } from '@anticrm/presentation'
import { showPopup } from '@anticrm/ui'
import { onDestroy } from 'svelte'
import { classIcon } from '../../utils'
import TreeNode from './TreeNode.svelte'
import TreeItem from './TreeItem.svelte'
import EditStatuses from '../EditStatuses.svelte'
import SpacePanel from './SpacePanel.svelte'
import TreeItem from './TreeItem.svelte'
import TreeNode from './TreeNode.svelte'
export let model: SpacesNavModel
@ -50,14 +44,6 @@
}
}
const editStatuses: Action = {
label: 'Edit Statuses' as IntlString,
icon: IconMoreH,
action: async (_id: Ref<Doc>): Promise<void> => {
showPopup(EditStatuses, { _id, spaceClass: model.spaceClass }, 'right')
}
}
const editSpace: Action = {
label: 'Open' as IntlString,
icon: IconEdit,
@ -78,10 +64,19 @@
selected = loc.path[2] as Ref<Space>
}))
function getActions (space: Space): Action[] {
async function getActions (space: Space): Promise<Action[]> {
const result = [editSpace]
if (client.getHierarchy().isDerived(space._class, core.class.SpaceWithStates)) {
result.push(editStatuses)
const extraActions = await getContributedActions(client, space._class, core.class.Space)
for (const act of extraActions) {
result.push({
icon: act.icon ?? IconEdit,
label: act.label,
action: async (props, ev) => {
const impl = await getResource(act.action)
await impl(space)
}
})
}
return result
}
@ -90,7 +85,9 @@
<div>
<TreeNode label={model.label} actions={[addSpace]}>
{#each spaces as space}
<TreeItem _id={space._id} title={space.name} icon={classIcon(client, space._class)} selected={selected === space._id} actions={getActions(space)} on:click={() => { selectSpace(space._id) }}/>
{#await getActions(space) then actions}
<TreeItem _id={space._id} title={space.name} icon={classIcon(client, space._class)} selected={selected === space._id} {actions} on:click={() => { selectSpace(space._id) }}/>
{/await}
{/each}
</TreeNode>
</div>

View File

@ -737,18 +737,18 @@
"shouldPublish": true
},
{
"packageName": "@anticrm/server-view-resources",
"projectFolder": "server/view-resources",
"packageName": "@anticrm/server-task-resources",
"projectFolder": "server/task-resources",
"shouldPublish": true
},
{
"packageName": "@anticrm/server-view",
"projectFolder": "server/view",
"packageName": "@anticrm/server-task",
"projectFolder": "server/task",
"shouldPublish": true
},
{
"packageName": "@anticrm/model-server-view",
"projectFolder": "models/server-view",
"packageName": "@anticrm/model-server-task",
"projectFolder": "models/server-task",
"shouldPublish": true
},
{

View File

@ -118,14 +118,13 @@ class TServerStorage implements ServerStorage {
return []
}
if (colTx.tx._class === core.class.TxCreateDoc) {
attachedTo = (await this.findAll(_class, { _id }))[0]
const txFactory = new TxFactory(tx.modifiedBy)
return [txFactory.createTxUpdateDoc(_class, attachedTo.space, _id, { $inc: { [colTx.collection]: 1 } })]
} else if (colTx.tx._class === core.class.TxRemoveDoc) {
attachedTo = (await this.findAll(_class, { _id }))[0]
const txFactory = new TxFactory(tx.modifiedBy)
return [txFactory.createTxUpdateDoc(_class, attachedTo.space, _id, { $inc: { [colTx.collection]: -1 } })]
const isCreateTx = colTx.tx._class === core.class.TxCreateDoc
if (isCreateTx || colTx.tx._class === core.class.TxRemoveDoc) {
attachedTo = (await this.findAll(_class, { _id }, { limit: 1 }))[0]
if (attachedTo !== undefined) {
const txFactory = new TxFactory(tx.modifiedBy)
return [txFactory.createTxUpdateDoc(_class, attachedTo.space, _id, { $inc: { [colTx.collection]: isCreateTx ? 1 : -1 } })]
}
}
}
return []

View File

@ -44,7 +44,7 @@
"@anticrm/server-recruit-resources": "~0.6.0",
"@anticrm/mongo": "~0.6.1",
"@anticrm/elastic": "~0.6.0",
"@anticrm/server-view": "~0.6.0",
"@anticrm/server-view-resources": "~0.6.0"
"@anticrm/server-task": "~0.6.0",
"@anticrm/server-task-resources": "~0.6.0"
}
}

Some files were not shown because too many files have changed in this diff Show More