Tracker Issue tracking system (#1243)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev
2022-03-31 15:32:42 +07:00
committed by GitHub
parent 944cfafcba
commit 8cd1797696
87 changed files with 2382 additions and 175 deletions
+112 -5
View File
@@ -80,6 +80,7 @@ specifiers:
'@rush-temp/model-server-task': file:./projects/model-server-task.tgz
'@rush-temp/model-server-telegram': file:./projects/model-server-telegram.tgz
'@rush-temp/model-setting': file:./projects/model-setting.tgz
'@rush-temp/model-tracker': file:./projects/model-tracker.tgz
'@rush-temp/model-tags': file:./projects/model-tags.tgz
'@rush-temp/model-task': file:./projects/model-task.tgz
'@rush-temp/model-telegram': file:./projects/model-telegram.tgz
@@ -136,6 +137,9 @@ specifiers:
'@rush-temp/setting': file:./projects/setting.tgz
'@rush-temp/setting-assets': file:./projects/setting-assets.tgz
'@rush-temp/setting-resources': file:./projects/setting-resources.tgz
'@rush-temp/tracker': file:./projects/tracker.tgz
'@rush-temp/tracker-assets': file:./projects/tracker-assets.tgz
'@rush-temp/tracker-resources': file:./projects/tracker-resources.tgz
'@rush-temp/tags': file:./projects/tags.tgz
'@rush-temp/tags-assets': file:./projects/tags-assets.tgz
'@rush-temp/tags-resources': file:./projects/tags-resources.tgz
@@ -339,6 +343,7 @@ dependencies:
'@rush-temp/model-server-task': file:projects/model-server-task.tgz_typescript@4.5.4
'@rush-temp/model-server-telegram': file:projects/model-server-telegram.tgz_typescript@4.5.4
'@rush-temp/model-setting': file:projects/model-setting.tgz_typescript@4.5.4
'@rush-temp/model-tracker': file:projects/model-tracker.tgz_typescript@4.5.4
'@rush-temp/model-tags': file:projects/model-tags.tgz_typescript@4.5.4
'@rush-temp/model-task': file:projects/model-task.tgz_typescript@4.5.4
'@rush-temp/model-telegram': file:projects/model-telegram.tgz_typescript@4.5.4
@@ -395,6 +400,9 @@ dependencies:
'@rush-temp/setting': file:projects/setting.tgz
'@rush-temp/setting-assets': file:projects/setting-assets.tgz_typescript@4.5.4
'@rush-temp/setting-resources': file:projects/setting-resources.tgz_096c09b0b673a57c275d9767a12070b1
'@rush-temp/tracker': file:projects/tracker.tgz
'@rush-temp/tracker-assets': file:projects/tracker-assets.tgz_typescript@4.5.4
'@rush-temp/tracker-resources': file:projects/tracker-resources.tgz_096c09b0b673a57c275d9767a12070b1
'@rush-temp/tags': file:projects/tags.tgz
'@rush-temp/tags-assets': file:projects/tags-assets.tgz
'@rush-temp/tags-resources': file:projects/tags-resources.tgz_096c09b0b673a57c275d9767a12070b1
@@ -12440,7 +12448,7 @@ packages:
dev: false
file:projects/model-all.tgz_typescript@4.5.4:
resolution: {integrity: sha512-CTYZ52iBQ2S06DVd6jUK+PHCpgLDvO7lrujkMWxCO3RUn+77uyDZBGK43n+7UF1HZNSHvYAhyA8fBTvp1kOylw==, tarball: file:projects/model-all.tgz}
resolution: {integrity: sha512-LFMD9UgTqaXrqIVbVyZudBvzL8xL6KphHnShut9no15Y5ZozOYv9hksYqBU959YFHClTkUI22XzwaFUPTSideA==, tarball: file:projects/model-all.tgz}
id: file:projects/model-all.tgz
name: '@rush-temp/model-all'
version: 0.0.0
@@ -13051,6 +13059,27 @@ packages:
- typescript
dev: false
file:projects/model-tracker.tgz_typescript@4.5.4:
resolution: {integrity: sha512-PWxsEQ61Fe/moBL+dROHGw2bQRaaJBL/N7HozRZPxXoRfXkH/DwNMvH/UofBL4mIFVs8FfQU03FaFnNdahgNZw==, tarball: file:projects/model-tracker.tgz}
id: file:projects/model-tracker.tgz
name: '@rush-temp/model-tracker'
version: 0.0.0
dependencies:
'@rushstack/heft': 0.41.8
'@types/heft-jest': 1.0.2
'@typescript-eslint/eslint-plugin': 5.7.0_c25e8c1f4f4f7aaed27aa6f9ce042237
'@typescript-eslint/parser': 5.7.0_eslint@7.32.0+typescript@4.5.4
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_ce2fa0c4dfa1c256100cababd749a13a
eslint-plugin-import: 2.25.3_eslint@7.32.0
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.2.0_eslint@7.32.0
prettier: 2.5.1
transitivePeerDependencies:
- supports-color
- typescript
dev: false
file:projects/model-tags.tgz_typescript@4.5.4:
resolution: {integrity: sha512-tRhFRRosgUsAll6FSrGLSKycNeDNbH8M2JTvhOrMOQjsVafRvCz+oM+/sHg+tln4CSFTxU/+9cjPfHfQNx5fVA==, tarball: file:projects/model-tags.tgz}
id: file:projects/model-tags.tgz
@@ -13439,7 +13468,7 @@ packages:
dev: false
file:projects/presentation.tgz_096c09b0b673a57c275d9767a12070b1:
resolution: {integrity: sha512-MP45QOJeNLCukhDFHH8hPeV0lX/HivC+PW3nIIALWyHstmCshVJiu8KjkfrwDl6gj6kRENV9pUOlQcWdp+9ezA==, tarball: file:projects/presentation.tgz}
resolution: {integrity: sha512-DSAHNO42i3or1GCHYIM6/V6Yx0aLQlGy1QG6hVTipV/KeBdxZF/hetQNcEufUbwWKAZAwyEIEMIZDFl8tlpKIw==, tarball: file:projects/presentation.tgz}
id: file:projects/presentation.tgz
name: '@rush-temp/presentation'
version: 0.0.0
@@ -13475,7 +13504,7 @@ packages:
dev: false
file:projects/prod.tgz_a07ec81d4d975778878ca12202ea119e:
resolution: {integrity: sha512-HHCNzvjIf/tNB1Wr5Fk4HGuoP66fHONafJd6DPh4kpTC7T8Vc5Y/UlneWS3cUjhCsB9jgYejEbu1Osp0DPXCZA==, tarball: file:projects/prod.tgz}
resolution: {integrity: sha512-lAhrU2LvSbV+Hg0ug0LHYTe3RMC8MgyyuFOFXQNMwXftXqQfXkgciBMjWh+g9PB/aXcJwSLm3scgACCtGG0wWw==, tarball: file:projects/prod.tgz}
id: file:projects/prod.tgz
name: '@rush-temp/prod'
version: 0.0.0
@@ -14289,7 +14318,7 @@ packages:
dev: false
file:projects/server.tgz:
resolution: {integrity: sha512-Gx/PoIoObAiRX21Fo+Z/y7tqqgHW8URVudDz8azH/0hW25pRfR03xPw7oR0GsuF2IBZ4S8nyH1U2HSPpE76W7g==, tarball: file:projects/server.tgz}
resolution: {integrity: sha512-TEYNvDfpWbWWHcltHwEyC1B81v5c9UUyGmNx9C9sdItiWxLb8P/lQ+a+xca+5eJ+FQmRxm3VJywrvscmttTfPg==, tarball: file:projects/server.tgz}
name: '@rush-temp/server'
version: 0.0.0
dependencies:
@@ -14395,6 +14424,84 @@ packages:
- supports-color
dev: false
file:projects/tracker-assets.tgz_typescript@4.5.4:
resolution: {integrity: sha512-sTaCJb1JkS7lN4xuyIFPuQwlafQHtMB7yZ3lrWTX/MgMwxr4UWHnZODK631lGxhio4MlMgXX0KYqiDwfxnQJqg==, tarball: file:projects/tracker-assets.tgz}
id: file:projects/tracker-assets.tgz
name: '@rush-temp/tracker-assets'
version: 0.0.0
dependencies:
'@rushstack/heft': 0.41.8
'@types/heft-jest': 1.0.2
'@types/node': 16.11.14
'@typescript-eslint/eslint-plugin': 5.7.0_c25e8c1f4f4f7aaed27aa6f9ce042237
'@typescript-eslint/parser': 5.7.0_eslint@7.32.0+typescript@4.5.4
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_ce2fa0c4dfa1c256100cababd749a13a
eslint-plugin-import: 2.25.3_eslint@7.32.0
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.2.0_eslint@7.32.0
prettier: 2.5.1
transitivePeerDependencies:
- supports-color
- typescript
dev: false
file:projects/tracker-resources.tgz_096c09b0b673a57c275d9767a12070b1:
resolution: {integrity: sha512-IwrLGQsCGO7NtMvEY2i7mA81An6fhYDq8Y4aKYZKSX/ptG85/xCJ+YayjxbX70iUoogHJfgYGPTklPJjBAXIKg==, tarball: file:projects/tracker-resources.tgz}
id: file:projects/tracker-resources.tgz
name: '@rush-temp/tracker-resources'
version: 0.0.0
dependencies:
'@typescript-eslint/eslint-plugin': 5.7.0_c25e8c1f4f4f7aaed27aa6f9ce042237
'@typescript-eslint/parser': 5.7.0_eslint@7.32.0+typescript@4.5.4
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_ce2fa0c4dfa1c256100cababd749a13a
eslint-plugin-import: 2.25.3_eslint@7.32.0
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.2.0_eslint@7.32.0
eslint-plugin-svelte3: 3.2.1_eslint@7.32.0+svelte@3.44.3
lexorank: 1.0.4
prettier: 2.5.1
prettier-plugin-svelte: 2.5.1_prettier@2.5.1+svelte@3.44.3
sass: 1.45.0
svelte: 3.44.3
svelte-check: 2.3.0_4374c622c67ed7479ff0e44c29d09bce
svelte-loader: 3.1.2_svelte@3.44.3
svelte-preprocess: 4.10.3_14d64cad431e31f100de7363af24a44f
typescript: 4.5.4
transitivePeerDependencies:
- '@babel/core'
- coffeescript
- less
- node-sass
- postcss
- postcss-load-config
- pug
- stylus
- sugarss
- supports-color
dev: false
file:projects/tracker.tgz:
resolution: {integrity: sha512-4BE4OCd7TawHUfz5pI8hYADlDGMLsNrczRPbsuEy1hrA+4BSTkjn5rDDSZ3iu9TLtB3TV3anvbjiZhZOrbiDyw==, tarball: file:projects/tracker.tgz}
name: '@rush-temp/tracker'
version: 0.0.0
dependencies:
'@rushstack/heft': 0.41.8
'@types/heft-jest': 1.0.2
'@typescript-eslint/eslint-plugin': 5.7.0_c25e8c1f4f4f7aaed27aa6f9ce042237
'@typescript-eslint/parser': 5.7.0_eslint@7.32.0+typescript@4.5.4
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_ce2fa0c4dfa1c256100cababd749a13a
eslint-plugin-import: 2.25.3_eslint@7.32.0
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.2.0_eslint@7.32.0
prettier: 2.5.1
typescript: 4.5.4
transitivePeerDependencies:
- supports-color
dev: false
file:projects/tags-assets.tgz:
resolution: {integrity: sha512-c3kDt2CRIqT3lDJNJPawZ+M3j979cOkF1mYeRKUOsNQ1Zs9wnqXjxDxe1ZECpOAUbWXNhKj/Mru0XfEyaCxHQA==, tarball: file:projects/tags-assets.tgz}
name: '@rush-temp/tags-assets'
@@ -14808,7 +14915,7 @@ packages:
dev: false
file:projects/tool.tgz:
resolution: {integrity: sha512-F3Px6/29RVwqI/G3FsImf5dKvi53Tz7R/LSRyeG81NIN0WMGEsuOB6iWE9GD8PS/W7qSnKsFC8+VWVvJN+gVeQ==, tarball: file:projects/tool.tgz}
resolution: {integrity: sha512-JwGKWGU/S/EONMAYohP8k5D9kO8xvd8VG2f7Mw375r83TpoSKBPpLVqrmNZUb70EZTTCihEO9oiM6NVqbjWq9w==, tarball: file:projects/tool.tgz}
name: '@rush-temp/tool'
version: 0.0.0
dependencies:
+5 -1
View File
@@ -130,6 +130,10 @@
"@anticrm/server-gmail": "~0.6.0",
"@anticrm/server-gmail-resources": "~0.6.0",
"@anticrm/server-telegram": "~0.6.0",
"@anticrm/server-telegram-resources": "~0.6.0"
"@anticrm/server-telegram-resources": "~0.6.0",
"@anticrm/presentation": "~0.6.2",
"@anticrm/tracker": "~0.6.0",
"@anticrm/tracker-assets": "~0.6.0",
"@anticrm/tracker-resources": "~0.6.0"
}
}
+6 -1
View File
@@ -36,6 +36,7 @@ import { templatesId } from '@anticrm/templates'
import { notificationId } from '@anticrm/notification'
import { tagsId } from '@anticrm/tags'
import { calendarId } from '@anticrm/calendar'
import { trackerId } from '@anticrm/tracker'
import rekoni from '@anticrm/rekoni'
import '@anticrm/login-assets'
@@ -56,6 +57,8 @@ import '@anticrm/templates-assets'
import '@anticrm/notification-assets'
import '@anticrm/tags-assets'
import '@anticrm/calendar-assets'
import '@anticrm/tracker-assets'
import presentation from '@anticrm/presentation'
import { setMetadata } from '@anticrm/platform'
export async function configurePlatform() {
@@ -67,7 +70,7 @@ export async function configurePlatform() {
if( config.MODEL_VERSION != null) {
console.log('Minimal Model version requirement', config.MODEL_VERSION)
setMetadata(workbench.metadata.RequiredVersion, config.MODEL_VERSION)
setMetadata(presentation.metadata.RequiredVersion, config.MODEL_VERSION)
}
setMetadata(login.metadata.TelegramUrl, process.env.TELEGRAM_URL ?? 'http://localhost:8086')
setMetadata(login.metadata.GmailUrl, process.env.GMAIL_URL ?? 'http://localhost:8087')
@@ -98,4 +101,6 @@ export async function configurePlatform() {
addLocation(notificationId, () => import(/* webpackChunkName: "notification" */ '@anticrm/notification-resources'))
addLocation(tagsId, () => import(/* webpackChunkName: "tags" */ '@anticrm/tags-resources'))
addLocation(calendarId, () => import(/* webpackChunkName: "calendar" */ '@anticrm/calendar-resources'))
addLocation(trackerId, () => import(/* webpackChunkName: "tracker" */ '@anticrm/tracker-resources'))
}
+2 -1
View File
@@ -66,6 +66,7 @@
"@anticrm/model-calendar": "~0.6.0",
"@anticrm/model-server-calendar": "~0.6.0",
"@anticrm/model-server-gmail": "~0.6.0",
"@anticrm/model-server-telegram": "~0.6.0"
"@anticrm/model-server-telegram": "~0.6.0",
"@anticrm/model-tracker": "~0.6.0"
}
}
+2
View File
@@ -52,6 +52,7 @@ import { createModel as calendarModel } from '@anticrm/model-calendar'
import { createModel as serverCalendarModel } from '@anticrm/model-server-calendar'
import { createModel as serverGmailModel } from '@anticrm/model-server-gmail'
import { createModel as serverTelegramModel } from '@anticrm/model-server-telegram'
import { createModel as trackerModel } from '@anticrm/model-tracker'
export const version: Data<Version> = jsonVersion as Data<Version>
@@ -92,6 +93,7 @@ const builders = [
serverCalendarModel,
serverGmailModel,
serverTelegramModel,
trackerModel,
createDemo
]
+3 -1
View File
@@ -29,6 +29,7 @@ import { taskOperation } from '@anticrm/model-task'
import { telegramOperation } from '@anticrm/model-telegram'
import { templatesOperation } from '@anticrm/model-templates'
import { viewOperation } from '@anticrm/model-view'
import { trackerOperation } from '@anticrm/model-tracker'
export const migrateOperations: MigrateOperation[] = [
coreOperation,
@@ -44,5 +45,6 @@ export const migrateOperations: MigrateOperation[] = [
contactOperation,
tagsOperation,
notificationOperation,
settingOperation
settingOperation,
trackerOperation
]
+29 -29
View File
@@ -265,35 +265,35 @@ export function createModel (builder: Builder): void {
task.viewlet.StatusTable
)
builder.createDoc(
workbench.class.Application,
core.space.Model,
{
label: task.string.ApplicationLabelTask,
icon: task.icon.Task,
hidden: false,
navigatorModel: {
spaces: [
{
label: task.string.Projects,
spaceClass: task.class.Project,
addSpaceLabel: task.string.CreateProject,
createComponent: task.component.CreateProject
}
],
specials: [
{
id: 'assigned',
label: task.string.Assigned,
icon: task.icon.Task,
component: task.component.AssignedTasks,
position: 'top'
}
]
}
},
task.app.Tasks
)
// builder.createDoc(
// workbench.class.Application,
// core.space.Model,
// {
// label: task.string.ApplicationLabelTask,
// icon: task.icon.Task,
// hidden: false,
// navigatorModel: {
// spaces: [
// {
// label: task.string.Projects,
// spaceClass: task.class.Project,
// addSpaceLabel: task.string.CreateProject,
// createComponent: task.component.CreateProject
// }
// ],
// specials: [
// {
// id: 'assigned',
// label: task.string.Assigned,
// icon: task.icon.Task,
// component: task.component.AssignedTasks,
// position: 'top'
// }
// ]
// }
// },
// task.app.Tasks
// )
builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: task.class.Issue,
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/model-rig/profiles/default/config/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}
+4
View File
@@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/
+18
View File
@@ -0,0 +1,18 @@
// The "rig.json" file directs tools to look for their config files in an external package.
// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
/**
* (Required) The name of the rig package to inherit from.
* It should be an NPM package name with the "-rig" suffix.
*/
"rigPackageName": "@anticrm/model-rig"
/**
* (Optional) Selects a config profile from the rig package. The name must consist of
* lowercase alphanumeric words separated by hyphens, for example "sample-profile".
* If omitted, then the "default" profile will be used."
*/
// "rigProfile": "your-profile-name"
}
+45
View File
@@ -0,0 +1,45 @@
{
"name": "@anticrm/model-tracker",
"version": "0.6.0",
"main": "lib/index.js",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "heft build",
"build:watch": "tsc",
"lint:fix": "eslint --fix src",
"lint": "eslint src",
"format": "prettier --write src && eslint --fix src"
},
"devDependencies": {
"@anticrm/model-rig": "~0.6.0",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-node": "^11.1.0",
"eslint": "^7.32.0",
"@types/heft-jest": "^1.0.2",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"prettier": "^2.4.1",
"@rushstack/heft": "^0.41.1"
},
"dependencies": {
"@anticrm/core": "~0.6.16",
"@anticrm/model": "~0.6.0",
"@anticrm/ui": "~0.6.0",
"@anticrm/contact": "~0.6.5",
"@anticrm/platform": "~0.6.5",
"@anticrm/model-core": "~0.6.0",
"@anticrm/model-view": "~0.6.0",
"@anticrm/model-workbench": "~0.6.1",
"@anticrm/model-contact": "~0.6.1",
"@anticrm/model-attachment": "~0.6.0",
"@anticrm/tracker": "~0.6.0",
"@anticrm/tracker-resources": "~0.6.0",
"@anticrm/model-chunter": "~0.6.0",
"@anticrm/workbench": "~0.6.1",
"@anticrm/view": "~0.6.0",
"@anticrm/model-presentation": "~0.6.0"
}
}
+43
View File
@@ -0,0 +1,43 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import core, { Client, TxOperations } from '@anticrm/core'
import { Team } from '@anticrm/tracker'
import tracker from './plugin'
export async function createDeps (client: Client): Promise<void> {
const tx = new TxOperations(client, core.account.System)
const current = await tx.findOne(tracker.class.Team, {
_id: tracker.team.DefaultTeam
})
if (current === undefined) {
await tx.createDoc<Team>(
tracker.class.Team,
core.space.Space,
{
name: 'Default',
description: 'Default team',
private: false,
members: [],
archived: false,
identifier: 'TSK',
sequence: 0
},
tracker.team.DefaultTeam
)
}
}
+222
View File
@@ -0,0 +1,222 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import type { Employee } from '@anticrm/contact'
import contact from '@anticrm/contact'
import { Domain, IndexKind, Markup, Ref, Timestamp } from '@anticrm/core'
import {
Builder,
Collection, Hidden, Index, Model,
Prop, TypeDate,
TypeMarkup,
TypeNumber,
TypeRef,
TypeString,
UX
} from '@anticrm/model'
import attachment from '@anticrm/model-attachment'
import chunter from '@anticrm/model-chunter'
import core, { DOMAIN_SPACE, TDoc, TSpace } from '@anticrm/model-core'
import { IntlString } from '@anticrm/platform'
import { Document, Issue, IssuePriority, IssueStatus, Team } from '@anticrm/tracker'
import tracker from './plugin'
import workbench from '@anticrm/model-workbench'
export { createDeps } from './creation'
export { trackerOperation } from './migration'
export { default } from './plugin'
export const DOMAIN_TRACKER = 'tracker' as Domain
/**
* @public
*/
@Model(tracker.class.Team, core.class.Space, DOMAIN_SPACE)
@UX(tracker.string.Team, tracker.icon.Team, tracker.string.Team)
export class TTeam extends TSpace implements Team {
@Prop(TypeString(), tracker.string.Title)
@Index(IndexKind.FullText)
reamLogo!: IntlString
@Prop(TypeString(), tracker.string.Identifier)
@Index(IndexKind.FullText)
identifier!: IntlString
@Prop(TypeNumber(), tracker.string.Number)
@Hidden()
sequence!: number
}
/**
* @public
*/
@Model(tracker.class.Issue, core.class.Doc, DOMAIN_TRACKER)
@UX(tracker.string.Issue, tracker.icon.Issue, tracker.string.Issue)
export class TIssue extends TDoc implements Issue {
@Prop(TypeString(), tracker.string.Title)
@Index(IndexKind.FullText)
title!: string
@Prop(TypeMarkup(), tracker.string.Description)
@Index(IndexKind.FullText)
description!: Markup
@Prop(TypeNumber(), tracker.string.Status)
status!: IssueStatus
@Prop(TypeNumber(), tracker.string.Priority)
priority!: IssuePriority
@Prop(TypeNumber(), tracker.string.Number)
number!: number
@Prop(TypeRef(contact.class.Employee), tracker.string.Assignee)
assignee!: Ref<Employee> | null
@Prop(TypeRef(tracker.class.Issue), tracker.string.Parent)
parentIssue!: Ref<Issue>
@Prop(Collection(tracker.class.Issue), tracker.string.BlockedBy)
blockedBy!: Ref<Issue>[]
@Prop(Collection(tracker.class.Issue), tracker.string.RelatedTo)
relatedIssue!: Ref<Issue>[]
@Prop(Collection(chunter.class.Comment), tracker.string.Comments)
comments!: number
@Prop(Collection(attachment.class.Attachment), tracker.string.Attachments)
attachments!: number
// @Prop(Collection(core.class.TypeString), tracker.string.Labels)
labels?: string[]
declare space: Ref<Team>
@Prop(TypeDate(true), tracker.string.Number)
dueDate!: Timestamp | null
@Prop(TypeString(), tracker.string.Rank)
@Hidden()
rank!: string
}
/**
* @public
*/
@Model(tracker.class.Document, core.class.Doc, DOMAIN_TRACKER)
@UX(tracker.string.Document, tracker.icon.Document, tracker.string.Document)
export class TDocument extends TDoc implements Document {
@Prop(TypeString(), tracker.string.Title)
@Index(IndexKind.FullText)
title!: string
@Prop(TypeString(), tracker.string.DocumentIcon)
icon!: string | null
@Prop(TypeString(), tracker.string.DocumentColor)
color!: number
@Prop(TypeMarkup(), tracker.string.Description)
@Index(IndexKind.FullText)
content!: Markup
declare space: Ref<Team>
}
export function createModel (builder: Builder): void {
builder.createModel(
TTeam,
TIssue
)
builder.createDoc(
workbench.class.Application,
core.space.Model,
{
label: tracker.string.TrackerApplication,
icon: tracker.icon.Issue,
hidden: false,
navigatorModel: {
specials: [
{
id: 'inbox',
position: 'top',
label: tracker.string.Inbox,
icon: tracker.icon.TrackerApplication,
component: tracker.component.Inbox
},
{
id: 'my-issues',
position: 'top',
label: tracker.string.MyIssues,
icon: tracker.icon.TrackerApplication,
component: tracker.component.MyIssues
},
{
id: 'views',
position: 'top',
label: tracker.string.Views,
icon: tracker.icon.TrackerApplication,
component: tracker.component.Views
}
],
spaces: [
{
label: tracker.string.Teams,
spaceClass: tracker.class.Team,
// icon: tracker.icon.TrackerApplication,
addSpaceLabel: tracker.string.CreateTeam,
createComponent: tracker.component.CreateTeam,
specials: [
{
id: 'issues',
label: tracker.string.Issues,
icon: tracker.icon.TrackerApplication,
component: tracker.component.Issues
},
{
id: 'active',
label: tracker.string.Active,
// icon: tracker.icon.TrackerApplication,
component: tracker.component.Active
},
{
id: 'backlog',
label: tracker.string.Backlog,
// icon: tracker.icon.TrackerApplication,
component: tracker.component.Backlog
},
{
id: 'board',
label: tracker.string.Board,
// icon: tracker.icon.TrackerApplication,
component: tracker.component.Board
},
{
id: 'projects',
label: tracker.string.Projects,
icon: tracker.icon.TrackerApplication,
component: tracker.component.Projects
}
]
}]
},
navHeaderComponent: tracker.component.NewIssueHeader
},
tracker.app.Tracker
)
}
+27
View File
@@ -0,0 +1,27 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import {
MigrateOperation, MigrationClient, MigrationUpgradeClient
} from '@anticrm/model'
import { createDeps } from './creation'
export const trackerOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {
},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
await createDeps(client)
}
}
+39
View File
@@ -0,0 +1,39 @@
//
// 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.
//
import { Ref } from '@anticrm/core'
import { IntlString, mergeIds } from '@anticrm/platform'
import { Team, trackerId } from '@anticrm/tracker'
import tracker from '@anticrm/tracker-resources/src/plugin'
import type { AnyComponent } from '@anticrm/ui'
import { Application } from '@anticrm/workbench'
export default mergeIds(trackerId, tracker, {
string: {
TrackerApplication: '' as IntlString,
Teams: '' as IntlString
},
team: {
DefaultTeam: '' as Ref<Team>
},
component: {
// Required to pass build without errorsF
Nope: '' as AnyComponent
},
app: {
Tracker: '' as Ref<Application>
}
})
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "./node_modules/@anticrm/model-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
}
}
+2 -1
View File
@@ -39,6 +39,7 @@
"svelte": "^3.37.0",
"@anticrm/contact": "~0.6.5",
"@anticrm/login": "~0.6.1",
"@anticrm/image-cropper": "~0.6.0"
"@anticrm/image-cropper": "~0.6.0",
"@anticrm/client": "~0.6.2"
}
}
+68
View File
@@ -0,0 +1,68 @@
import client from '@anticrm/client'
import contact from '@anticrm/contact'
import core, { Client, setCurrentAccount, Version } from '@anticrm/core'
import login from '@anticrm/login'
import { getMetadata, getResource } from '@anticrm/platform'
import { fetchMetadataLocalStorage, getCurrentLocation, navigate, setMetadataLocalStorage } from '@anticrm/ui'
import presentation from './plugin'
export let versionError: string | undefined = ''
export async function connect (title: string): Promise<Client | undefined> {
const token = fetchMetadataLocalStorage(login.metadata.LoginToken)
const endpoint = fetchMetadataLocalStorage(login.metadata.LoginEndpoint)
const email = fetchMetadataLocalStorage(login.metadata.LoginEmail)
if (token === null || endpoint === null || email === null) {
navigate({ path: [login.component.LoginApp], query: { navigateUrl: encodeURIComponent(JSON.stringify(getCurrentLocation())) } })
return
}
const getClient = await getResource(client.function.GetClient)
const instance = await getClient(token, endpoint)
console.log('logging in as', email)
const me = await instance.findOne(contact.class.EmployeeAccount, { email })
if (me !== undefined) {
console.log('login: employee account', me)
setCurrentAccount(me)
} else {
console.error('WARNING: no employee account found.')
setMetadataLocalStorage(login.metadata.LoginToken, null)
setMetadataLocalStorage(login.metadata.LoginEndpoint, null)
setMetadataLocalStorage(login.metadata.LoginEmail, null)
setMetadataLocalStorage(login.metadata.CurrentWorkspace, null)
navigate({ path: [login.component.LoginApp], query: { navigateUrl: encodeURIComponent(JSON.stringify(getCurrentLocation())) } })
return
}
try {
console.log('checking model version')
const version = await instance.findOne<Version>(core.class.Version, {})
console.log('Model version', version)
const requirdVersion = getMetadata(presentation.metadata.RequiredVersion)
if (requirdVersion !== undefined) {
console.log('checking min model version', requirdVersion)
const versionStr = `${version?.major as number}.${version?.minor as number}.${version?.patch as number}`
if (version === undefined || requirdVersion !== versionStr) {
versionError = `${versionStr} => ${requirdVersion}`
return undefined
}
}
} catch (err: any) {
console.log(err)
const requirdVersion = getMetadata(presentation.metadata.RequiredVersion)
console.log('checking min model version', requirdVersion)
if (requirdVersion !== undefined) {
versionError = `'unknown' => ${requirdVersion}`
return undefined
}
}
// Update window title
document.title = [(fetchMetadataLocalStorage(login.metadata.CurrentWorkspace)), title].filter(it => it).join(' - ')
return instance
}
+1
View File
@@ -41,3 +41,4 @@ addStringsLoader(presentationId, async (lang: string) => {
})
export { default } from './plugin'
export { connect, versionError } from './connect'
+4 -1
View File
@@ -15,7 +15,7 @@
//
import { Class, Ref } from '@anticrm/core'
import type { IntlString, Plugin } from '@anticrm/platform'
import type { IntlString, Metadata, Plugin } from '@anticrm/platform'
import { plugin } from '@anticrm/platform'
import { ObjectSearchCategory } from '.'
@@ -40,5 +40,8 @@ export default plugin(presentationId, {
AddSocialLinks: '' as IntlString,
Change: '' as IntlString,
Remove: '' as IntlString
},
metadata: {
RequiredVersion: '' as Metadata<string>
}
})
+1
View File
@@ -272,6 +272,7 @@ p:last-child { margin-block-end: 0; }
.ml-3 { margin-left: .75rem; }
.ml-4 { margin-left: 1rem; }
.ml-6 { margin-left: 1.5rem; }
.ml-8 { margin-left: 2rem; }
.ml-10 { margin-left: 2.5rem; }
.mr-1 { margin-right: .25rem; }
.mr-2 { margin-right: .5rem; }
+2 -3
View File
@@ -56,7 +56,7 @@
flex-shrink: 0;
display: flex;
align-items: center;
margin: 0 1rem;
padding: 0.25rem;
height: 2.25rem;
border-radius: .5rem;
user-select: none;
@@ -65,8 +65,7 @@
.an-element__icon {
min-width: 1rem;
color: var(--theme-content-trans-color);
margin: 0 1.125rem 0 .625rem;
&.sub { margin: 0 .5rem 0 2.75rem }
margin: 0 1.125rem 0 0;
}
.an-element__label {
flex-grow: 1;
+3 -1
View File
@@ -34,7 +34,9 @@
{:then Ctor}
<ErrorBoundary>
<Ctor {...props} on:change on:close on:open on:click on:delete>
<slot />
{#if $$slots.default}
<slot />
{/if}
</Ctor>
</ErrorBoundary>
{:catch err}
-1
View File
@@ -29,7 +29,6 @@ export default mergeIds(calendarId, calendar, {
CreateReminder: '' as IntlString,
EditReminder: '' as IntlString,
ReminderTime: '' as IntlString,
Reminders: '' as IntlString,
ModeDay: '' as IntlString,
ModeWeek: '' as IntlString,
ModeMonth: '' as IntlString,
+2 -1
View File
@@ -99,7 +99,8 @@ const calendarPlugin = plugin(calendarId, {
Participants: '' as IntlString,
NoParticipants: '' as IntlString,
PersonsLabel: '' as IntlString,
EventNumber: '' as IntlString
EventNumber: '' as IntlString,
Reminders: '' as IntlString
},
ids: {
ReminderNotification: '' as Ref<NotificationType>
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/platform-rig/profiles/default/config/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}
+23
View File
@@ -0,0 +1,23 @@
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="tracker" viewBox="0 0 24 24">
<g>
<path d="M21.4,3.1c-0.2-0.2-0.6-0.2-0.8,0L11.1,13l-2.3-2.4c-0.2-0.2-0.6-0.2-0.8,0s-0.2,0.6,0,0.8l2.7,2.8 c0.1,0.1,0.3,0.2,0.4,0.2s0.3-0.1,0.4-0.2l9.9-10.4C21.7,3.7,21.7,3.3,21.4,3.1z"/>
<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="project" viewBox="0 0 16 16">
<path d="M11.7,3.6h-1.1C10.4,2.7,9.7,2,8.8,2H7.2C6.3,2,5.6,2.7,5.4,3.6H4.3C3,3.6,2,4.6,2,5.9V8c0,0.2,0.1,0.3,0.2,0.4 C3.8,9.3,5.9,9.9,8,9.9c2.1,0,4.2-0.5,5.8-1.5C13.9,8.3,14,8.2,14,8V5.9C14,4.6,13,3.6,11.7,3.6z M7.2,3h1.5c0.4,0,0.6,0.2,0.8,0.6 H6.5C6.6,3.2,6.9,3,7.2,3z M13,7.7c-1.4,0.8-3.2,1.2-5,1.2c-1.8,0-3.6-0.4-5-1.2V5.9c0-0.7,0.6-1.3,1.3-1.3h7.4 c0.7,0,1.3,0.6,1.3,1.3V7.7z"/>
<path d="M13.5,9.7c-0.3,0-0.5,0.2-0.5,0.5l-0.1,1.5c-0.1,0.8-0.7,1.3-1.4,1.3H4.5c-0.7,0-1.4-0.6-1.4-1.3L3,10.1 c0-0.3-0.3-0.5-0.5-0.5C2.2,9.7,2,9.9,2,10.2l0.1,1.5C2.2,13,3.3,14,4.5,14h6.9c1.3,0,2.3-1,2.4-2.3l0.1-1.5 C14,9.9,13.8,9.7,13.5,9.7z"/>
</symbol>
<symbol id="issue" viewBox="0 0 16 16">
<path d="M13.3,8.3c-0.1,2.8-2.5,5.1-5.4,5.1C5,13.4,2.6,11,2.6,8c0-2.9,2.3-5.2,5.1-5.4c0.1-0.4,0.2-0.7,0.4-1c0,0-0.1,0-0.1,0 C4.4,1.7,1.6,4.5,1.6,8c0,3.5,2.9,6.4,6.4,6.4s6.4-2.9,6.4-6.4c0,0,0-0.1,0-0.1C14,8.1,13.7,8.2,13.3,8.3z"/>
<ellipse cx="12.1" cy="3.9" rx="2.5" ry="2.5"/>
</symbol>
<symbol id="team" viewBox="0 0 24 24">
<path d="M13,1h-2.2H9.4H6.6H5.2H3C1.9,1,1,1.9,1,3v10c0,1.1,0.9,2,2,2h2.2h1.4h2.8h1.4H13c1.1,0,2-0.9,2-2V3C15,1.9,14.1,1,13,1z M3,13.8c-0.4,0-0.8-0.4-0.8-0.8V3c0-0.4,0.4-0.8,0.8-0.8h2.2v11.6H3z M6.6,13.8V2.2h2.8v11.6H6.6z M13.8,13c0,0.4-0.4,0.8-0.8,0.8 h-2.2V2.2H13c0.4,0,0.8,0.4,0.8,0.8V13z"/>
</symbol>
<symbol id="document" viewBox="0 0 16 16">
<path d="M8,14.5c-3.6,0-6.5-2.9-6.5-6.5S4.4,1.5,8,1.5s6.5,2.9,6.5,6.5S11.6,14.5,8,14.5z M8,2.5C5,2.5,2.5,5,2.5,8 c0,3,2.5,5.5,5.5,5.5c3,0,5.5-2.5,5.5-5.5C13.5,5,11,2.5,8,2.5z"/>
<polygon points="7.4,10.7 5,8.4 5.7,7.6 7.3,9.3 10.3,5.7 11,6.3 "/>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

+18
View File
@@ -0,0 +1,18 @@
// The "rig.json" file directs tools to look for their config files in an external package.
// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
/**
* (Required) The name of the rig package to inherit from.
* It should be an NPM package name with the "-rig" suffix.
*/
"rigPackageName": "@anticrm/platform-rig"
/**
* (Optional) Selects a config profile from the rig package. The name must consist of
* lowercase alphanumeric words separated by hyphens, for example "sample-profile".
* If omitted, then the "default" profile will be used."
*/
// "rigProfile": "your-profile-name"
}
+46
View File
@@ -0,0 +1,46 @@
{
"string": {
"TrackerApplication": "Tracker",
"Teams": "Your teams",
"More": "More",
"Delete": "Delete",
"Open": "Open",
"Members": "Members",
"Inbox": "Inbox",
"MyIssues": "My issues",
"Issues": "Issues",
"Views": "Views",
"Active": "Active",
"Backlog": "Backlog",
"Board": "Board",
"Projects": "Projects",
"CreateTeam": "Create team",
"NewIssue": "New issue",
"Title": "Title",
"Description": "",
"Status": "",
"Priority": "",
"Number": "",
"Assignee": "",
"Parent": "",
"BlockedBy": "",
"RelatedTo": "",
"Comments": "",
"Attachments": "",
"Labels": "",
"Space": "",
"DueDate": "",
"Team": "",
"Issue": "",
"Document": "",
"DocumentIcon": "",
"DocumentColor": "",
"Rank": "",
"IssueTitlePlaceholder": "Issue title",
"IssueDescriptionPlaceholder": "Add description"
},
"status": {
}
}
+22
View File
@@ -0,0 +1,22 @@
{
"string": {
"TrackerApplication": "Tracker",
"Teams": "Your teams",
"More": "More",
"Delete": "Delete",
"Open": "Open",
"Members": "Members",
"Inbox": "Inbox",
"MyIssues": "My issues",
"Issues": "Issues",
"Views": "Views",
"Active": "Active",
"Backlog": "Backlog",
"Board": "Board",
"Projects": "Projects",
"CreateTeam": "Create team"
},
"status": {
}
}
+33
View File
@@ -0,0 +1,33 @@
{
"name": "@anticrm/tracker-assets",
"version": "0.6.0",
"main": "lib/index.js",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "heft build",
"build:docs": "",
"lint": "eslint src",
"lint:fix": "eslint --fix src",
"format": "prettier --write src && eslint --fix src",
"build:watch": "tsc"
},
"devDependencies": {
"@anticrm/platform-rig": "~0.6.0",
"@types/heft-jest": "^1.0.2",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.1",
"eslint": "^7.32.0",
"prettier": "^2.4.1",
"@rushstack/heft": "^0.41.1",
"@types/node": "~16.11.12"
},
"dependencies": {
"@anticrm/platform": "~0.6.5",
"@anticrm/tracker": "~0.6.0"
}
}
+28
View File
@@ -0,0 +1,28 @@
//
// Copyright © 2022 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.
//
import { loadMetadata, addStringsLoader } from '@anticrm/platform'
import tracker, { trackerId } from '@anticrm/tracker'
const icons = require('../assets/icons.svg') as string // eslint-disable-line
loadMetadata(tracker.icon, {
TrackerApplication: `${icons}#tracker`,
Project: `${icons}#project`,
Issue: `${icons}#issue`,
Team: `${icons}#team`,
Document: `${icons}#document`
})
addStringsLoader(trackerId, async (lang: string) => await import(`../lang/${lang}.json`))
+15
View File
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"moduleResolution": "node",
"target": "esnext",
"module": "esnext",
"declaration": true,
"outDir": "./lib",
"strict": true,
"esModuleInterop": true,
"lib": [
"esnext",
"dom"
]
}
}
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/platform-rig/profiles/ui/config/eslint.config.json'],
parserOptions: { tsconfigRootDir: __dirname },
settings: {
'svelte3/ignore-styles': () => true
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 957 B

+53
View File
@@ -0,0 +1,53 @@
{
"name": "@anticrm/tracker-resources",
"version": "0.6.0",
"main": "src/index.ts",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "echo 'no build for ui'",
"build:docs": "api-extractor run --local",
"lint": "eslint",
"lint:fix": "eslint --fix src",
"format": "prettier --write --plugin-search-dir=. src && eslint --fix src"
},
"devDependencies": {
"svelte-loader": "^3.1.2",
"sass": "^1.37.5",
"svelte-preprocess": "^4.10.3",
"@anticrm/platform-rig": "~0.6.0",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-svelte3": "~3.2.1",
"prettier-plugin-svelte": "^2.2.0",
"eslint": "^7.32.0",
"prettier": "^2.4.1",
"svelte-check": "^2.2.10",
"typescript": "^4.3.5"
},
"dependencies": {
"svelte": "^3.37.0",
"@anticrm/platform": "~0.6.5",
"@anticrm/core": "~0.6.16",
"@anticrm/client": "~0.6.1",
"@anticrm/tracker": "~0.6.0",
"@anticrm/ui": "~0.6.0",
"@anticrm/view": "~0.6.0",
"@anticrm/presentation": "~0.6.2",
"@anticrm/login": "~0.6.1",
"@anticrm/setting": "~0.6.0",
"@anticrm/calendar": "~0.6.0",
"@anticrm/task": "~0.6.0",
"@anticrm/chunter": "~0.6.0",
"@anticrm/notification": "~0.6.0",
"@anticrm/notification-resources": "~0.6.0",
"@anticrm/contact": "~0.6.5",
"@anticrm/view-resources": "~0.6.0",
"lexorank": "~1.0.4",
"@anticrm/text-editor": "~0.6.0"
}
}
@@ -0,0 +1,5 @@
module.exports = {
plugins: [
require('autoprefixer')
]
}
@@ -0,0 +1,112 @@
<!--
// 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 contact, { Employee, EmployeeAccount, formatName } from '@anticrm/contact'
import { getCurrentAccount } from '@anticrm/core'
import login from '@anticrm/login'
import { Avatar, createQuery } from '@anticrm/presentation'
import setting, { SettingsCategory } from '@anticrm/setting'
import { closePopup, getCurrentLocation, Icon, Label, navigate, setMetadataLocalStorage } from '@anticrm/ui'
// const client = getClient()
async function getItems (): Promise<SettingsCategory[]> {
return []
}
const account = getCurrentAccount() as EmployeeAccount
let employee: Employee | undefined
const employeeQ = createQuery()
employeeQ.query(contact.class.Employee, {
_id: account.employee
}, (res) => {
employee = res[0]
}, { limit: 1 })
function selectCategory (sp: SettingsCategory): void {
closePopup()
const loc = getCurrentLocation()
loc.path[1] = setting.ids.SettingApp
loc.path[2] = sp.name
loc.path.length = 3
navigate(loc)
}
function signOut (): void {
setMetadataLocalStorage(login.metadata.LoginToken, null)
setMetadataLocalStorage(login.metadata.LoginEndpoint, null)
setMetadataLocalStorage(login.metadata.LoginEmail, null)
setMetadataLocalStorage(login.metadata.CurrentWorkspace, null)
navigate({ path: [login.component.LoginApp] })
}
function selectWorkspace (): void {
navigate({ path: [login.component.LoginApp, 'selectWorkspace'] })
}
function filterItems (items: SettingsCategory[]): SettingsCategory[] {
return items?.filter((p) => p.name !== 'profile' && p.name !== 'password')
}
function editProfile (items: SettingsCategory[] | undefined): void {
const profile = items?.find((p) => p.name === 'profile')
if (profile === undefined) return
selectCategory(profile)
}
</script>
<div class="antiPopup">
<div class="ap-space" />
<div class="ap-scroll"><div class="ap-box">
{#await getItems() then items}
<div class="ap-menuItem flex-row-center" on:click={() => { editProfile(items) }}>
{#if employee}
<Avatar avatar={employee.avatar} size={'medium'} />
{/if}
<div class="ml-2 flex-col">
{#if account}
<div class="overflow-label fs-bold caption-color">{formatName(account.name)}</div>
<div class="overflow-label text-sm content-dark-color">{account.email}</div>
{/if}
</div>
</div>
{#if items}
{#each filterItems(items) as item }
<button class="ap-menuItem" on:click={() => selectCategory(item)}>
<div class='mr-2'>
<Icon icon={item.icon} size={'small'}/>
</div>
<Label label={item.label} />
</button>
{/each}
{/if}
<button class="ap-menuItem" on:click={selectWorkspace}>
<div class='mr-2'>
<Icon icon={setting.icon.SelectWorkspace} size={'small'}/>
</div>
<Label label={setting.string.SelectWorkspace} />
</button>
<button class="ap-menuItem" on:click={signOut}>
<div class='mr-2'>
<Icon icon={setting.icon.Signout} size={'small'}/>
</div>
<Label label={setting.string.Signout} />
</button>
{/await}
</div></div>
<div class="ap-space" />
</div>
@@ -0,0 +1,80 @@
<!--
// 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 { IntlString, Asset } from '@anticrm/platform'
import type { AnySvelteComponent } from '@anticrm/ui'
import { Icon, Tooltip } from '@anticrm/ui'
export let label: IntlString
export let icon: Asset | AnySvelteComponent
export let action: () => Promise<void>
export let selected: boolean
export let notify: boolean
</script>
<Tooltip {label}>
<button class="app" id={'app-' + label} class:selected={selected} on:click|stopPropagation={action}>
<div class="flex-center icon-container" class:noty={notify}>
<Icon icon={icon} size={'large'}/>
</div>
{#if notify}<div class="marker"/>{/if}
</button>
</Tooltip>
<style lang="scss">
.app {
position: relative;
padding: 0;
width: 3.25rem;
height: 3.25rem;
background-color: transparent;
border: 1px solid transparent;
border-radius: .5rem;
cursor: pointer;
outline: none;
.icon-container {
width: 3.25rem;
height: 3.25rem;
color: var(--theme-content-trans-color);
.normal-font &.noty { clip-path: url(#notify-normal); }
.small-font &.noty { clip-path: url(#notify-small); }
}
&:hover .icon-container { color: var(--theme-caption-color); }
&:focus {
border: 1px solid var(--primary-button-focused-border);
box-shadow: 0 0 0 3px var(--primary-button-outline);
.icon-container { color: var(--theme-caption-color); }
}
&.selected {
background-color: var(--theme-menu-selection);
.icon-container { color: var(--theme-caption-color); }
}
}
.marker {
position: absolute;
top: .75rem;
right: .75rem;
width: .5rem;
height: .5rem;
border-radius: 50%;
background-color: var(--highlight-red);
}
</style>
@@ -0,0 +1,63 @@
<!--
// 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 { createEventDispatcher } from 'svelte'
import type { Ref, Class, Space, DocumentQuery } from '@anticrm/core'
import { Button, Label } from '@anticrm/ui'
import SpaceSelect from './SpaceSelect.svelte'
import presentation from '@anticrm/presentation'
export let spaceClass: Ref<Class<Space>> | undefined = undefined
export let space: Ref<Space> | undefined = undefined
export let spaceQuery: DocumentQuery<Space> | undefined = { archived: false }
export let spaceLabel: IntlString | undefined = undefined
export let spacePlaceholder: IntlString | undefined = undefined
export let label: IntlString
export let labelProps: any | undefined = undefined
export let okAction: () => void
export let canSave: boolean = false
export let okLabel: IntlString = presentation.string.Create
export let cancelLabel: IntlString = presentation.string.Cancel
const dispatch = createEventDispatcher()
</script>
<form class="antiCard w-165" on:submit|preventDefault={ () => {} }>
<div class="antiCard-header">
<div class="antiCard-header__title"><Label {label} params={labelProps ?? {}} /></div>
{#if $$slots.error}
<div class="antiCard-header__error">
<slot name="error" />
</div>
{/if}
</div>
<div class="antiCard-content"><slot /></div>
{#if spaceClass && spaceLabel && spacePlaceholder}
<div class="antiCard-pool">
<div class="antiCard-pool__separator" />
<SpaceSelect _class={spaceClass} spaceQuery={spaceQuery} label={spaceLabel} placeholder={spacePlaceholder} bind:value={space} />
</div>
{/if}
<div class="antiCard-footer">
<Button disabled={!canSave} label={okLabel} size={'small'} transparent primary on:click={() => { okAction(); dispatch('close') }} />
<Button label={cancelLabel} size={'small'} transparent on:click={() => { dispatch('close') }} />
</div>
</form>
@@ -0,0 +1,121 @@
<!--
// Copyright © 2022 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 contact, { Employee } from '@anticrm/contact'
import core, { Data, generateId, Ref, SortingOrder } from '@anticrm/core'
import { OK, Status } from '@anticrm/platform'
import { getClient, UserBox } from '@anticrm/presentation'
import { Issue, IssuePriority, IssueStatus, Team } from '@anticrm/tracker'
import { StyledTextBox } from '@anticrm/text-editor'
import { EditBox, Grid, Status as StatusControl } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import tracker from '../plugin'
import { calcRank } from '../utils'
import Card from './Card.svelte';
export let space: Ref<Team>
export let parent: Ref<Issue> | undefined
$: _space = space
$: _parent = parent
const status: Status = OK
let assignee: Ref<Employee> | null = null
const object: Data<Issue> = {
title: '',
description: '',
assignee: null,
number: 0,
rank: '',
status: IssueStatus.Backlog,
priority: IssuePriority.NoPriority,
dueDate: null,
comments: 0
}
const dispatch = createEventDispatcher()
const client = getClient()
const taskId: Ref<Issue> = generateId()
export function canClose (): boolean {
return object.title !== ''
}
async function createIssue () {
const lastOne = await client.findOne<Issue>(
tracker.class.Issue,
{ status: object.status },
{ sort: { rank: SortingOrder.Descending } }
)
const incResult = await client.updateDoc(
tracker.class.Team,
core.space.Space,
_space,
{
$inc: { sequence: 1 }
},
true
)
const value: Data<Issue> = {
title: object.title,
description: object.description,
assignee,
number: (incResult as any).object.sequence,
status: object.status,
priority: object.priority,
rank: calcRank(lastOne, undefined),
parentIssue: _parent,
comments: 0,
dueDate: object.dueDate
}
await client.createDoc(tracker.class.Issue, _space, value, taskId)
}
</script>
<Card
label={tracker.string.NewIssue}
okAction={createIssue}
canSave={object.title.length > 0 && _space != null}
spaceClass={tracker.class.Team}
spaceLabel={tracker.string.Team}
spacePlaceholder={tracker.string.SelectTeam}
bind:space={_space}
on:close={() => {
dispatch('close')
}}
>
<StatusControl slot="error" {status} />
<Grid column={1} rowGap={1.5}>
<EditBox
label={tracker.string.Title}
bind:value={object.title}
placeholder={tracker.string.IssueTitlePlaceholder}
maxWidth={'16rem'}
focus
/>
<StyledTextBox alwaysEdit bind:content={object.description} placeholder={tracker.string.IssueDescriptionPlaceholder}/>
<UserBox
_class={contact.class.Employee}
title={tracker.string.Assignee}
caption={tracker.string.Assignee}
bind:value={assignee}
allowDeselect
titleDeselect={tracker.string.TaskUnAssign}
/>
</Grid>
</Card>
@@ -0,0 +1,26 @@
<script lang="ts">
import { Ref, Space } from '@anticrm/core'
import ui, { ActionIcon, Button, IconEdit, IconSearch, showPopup } from '@anticrm/ui'
import tracker from '../plugin'
import CreateIssue from './CreateIssue.svelte'
export let currentSpace: Ref<Space>
async function newIssue(target: EventTarget | null): Promise<void> {
showPopup(CreateIssue, { space: currentSpace }, target as HTMLElement)
}
</script>
<div class="flex-between mt-4 mb-4">
<div class="flex-grow">
<Button
icon={IconEdit}
label={tracker.string.NewIssue}
width={'100%'}
size={'small'}
on:click={(evt) => newIssue(evt.currentTarget)}
/>
</div>
<div class="ml-2">
<ActionIcon icon={IconSearch} label={ui.string.Search} action={async () => {}} size={'large'} />
</div>
</div>
@@ -0,0 +1,43 @@
<!--
// 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 { IconFolder } from '@anticrm/ui'
import type { Space } from '@anticrm/core'
export let value: Space
export let subtitle: string | undefined = undefined
export let size: 'medium' | 'large'
</script>
<div class="flex-row-center">
<div class="flex-center {size} caption-color flex-no-shrink"><IconFolder size={'small'} /></div>
<div class="flex-col ml-2 min-w-0">
{#if subtitle}<div class="content-dark-color text-sm">{subtitle}</div>{/if}
<div class="content-accent-color overflow-label">{value.name}</div>
</div>
</div>
<style lang="scss">
.medium {
width: 1.75rem;
height: 1.75rem;
}
.large {
width: 2.25rem;
height: 2.25rem;
}
</style>
@@ -0,0 +1,79 @@
<!--
// 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 { Class, DocumentQuery, Ref, Space } from '@anticrm/core'
import type { IntlString } from '@anticrm/platform'
import { getClient } from '@anticrm/presentation'
import { IconFolder, Label, showPopup } from '@anticrm/ui'
import { onMount } from 'svelte'
import SpacesPopup from './SpacesPopup.svelte'
export let _class: Ref<Class<Space>>
export let spaceQuery: DocumentQuery<Space> | undefined = { archived: false }
export let label: IntlString
export let placeholder: IntlString
export let value: Ref<Space> | undefined
export let show: boolean = false
let selected: Space | undefined
let btn: HTMLElement
const client = getClient()
async function updateSelected (value: Ref<Space> | undefined) {
selected = value !== undefined ? await client.findOne(_class, { ...(spaceQuery ?? {}), _id: value }) : undefined
}
$: updateSelected(value)
onMount(() => {
if (btn && show) {
btn.click()
show = false
}
})
</script>
<div class="flex-col cursor-pointer"
bind:this={btn}
on:click|preventDefault={() => {
showPopup(SpacesPopup, { _class, spaceQuery }, btn, (result) => {
if (result) {
value = result._id
}
})
}}
>
<div class="overflow-label label"><Label {label} /></div>
<div class="flex-row-center space">
<span class="mr-1"><IconFolder size={'small'} /></span>
<span class="overflow-label" class:caption-color={selected} class:content-dark-color={!selected}>
{#if selected}
{selected.name}
{:else}
<Label label={placeholder} />
{/if}
</span>
</div>
</div>
<style lang="scss">
.label {
margin-bottom: .125rem;
font-weight: 500;
font-size: .75rem;
color: var(--theme-content-accent-color);
}
</style>
@@ -0,0 +1,53 @@
<!--
// 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 { afterUpdate, createEventDispatcher } from 'svelte'
import ui, { Label, EditWithIcon, IconSearch } from '@anticrm/ui'
import SpaceInfo from './SpaceInfo.svelte'
import type { Ref, Class, Space, DocumentQuery } from '@anticrm/core'
import { createQuery } from '@anticrm/presentation'
export let _class: Ref<Class<Space>>
export let spaceQuery: DocumentQuery<Space> | undefined
let search: string = ''
let objects: Space[] = []
const dispatch = createEventDispatcher()
const query = createQuery()
$: query.query(_class, { ...(spaceQuery ?? {}), name: { $like: '%' + search + '%' } }, result => { objects = result })
afterUpdate(() => { dispatch('update', Date.now()) })
</script>
<div class="antiPopup antiPopup-withHeader">
<div class="ap-space" />
<div class="ap-header">
<EditWithIcon icon={IconSearch} bind:value={search} placeholder={ui.string.SearchDots} focus />
<div class="ap-caption"><Label label={ui.string.Suggested} /></div>
</div>
<div class="ap-space" />
<div class="ap-scroll">
<div class="ap-box">
{#each objects as space}
<button class="ap-menuItem" on:click={() => { dispatch('close', space) }}>
<SpaceInfo size={'large'} value={space} />
</button>
{/each}
</div>
</div>
<div class="ap-space" />
</div>
@@ -0,0 +1,8 @@
<script lang="ts">
export let size: 'small' | 'medium' | 'large'
const fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<polygon points="6.4,14.4 5.6,13.6 11.3,8 5.6,2.4 6.4,1.6 12.7,8 "/>
</svg>
@@ -0,0 +1,8 @@
<script lang="ts">
export let size: 'small' | 'medium' | 'large'
const fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<polygon points="8,11.7 1.6,5.4 2.4,4.6 8,10.3 13.6,4.6 14.4,5.4 "/>
</svg>
@@ -0,0 +1,8 @@
<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.5,8c0-1.4-0.4-2.7-1.2-3.8c-0.8-1.1-1.9-1.9-3.2-2.4c-1.3-0.4-2.7-0.5-4-0.1C4.8,2.2,3.7,3,2.8,4C2,5.1,1.5,6.4,1.5,7.8 c0,1.4,0.3,2.7,1.1,3.8c0.8,1.1,1.8,2,3.1,2.5c0.7,0.3,1.5,0.4,2.3,0.4c0.6,0,1.1-0.1,1.7-0.2c0.3-0.1,0.4-0.3,0.4-0.6 c-0.1-0.3-0.3-0.4-0.6-0.4c-1.1,0.3-2.3,0.2-3.4-0.2c-1.1-0.4-2-1.1-2.6-2.1c-0.6-1-1-2.1-0.9-3.2c0-1.2,0.4-2.3,1.1-3.2 c0.7-0.9,1.7-1.6,2.8-1.9c1.1-0.3,2.3-0.3,3.4,0.1c1.1,0.4,2,1.1,2.7,2c0.7,0.9,1,2,1,3.2c0,0,0,0,0,0v1c0,0.6-0.5,1.2-1.2,1.2 c-0.6,0-1.2-0.5-1.2-1.2V8c0,0,0,0,0,0s0,0,0,0V6c0-0.3-0.2-0.5-0.5-0.5c-0.2,0-0.3,0.1-0.4,0.3C9.7,5.2,8.9,4.8,8,4.8 C6.3,4.8,4.8,6.3,4.8,8s1.4,3.2,3.2,3.2c1,0,1.8-0.5,2.4-1.2c0.4,0.7,1.1,1.2,1.9,1.2c1.2,0,2.2-1,2.2-2.2L14.5,8 C14.5,8,14.5,8,14.5,8z M8,10.2c-1.2,0-2.2-1-2.2-2.2c0-1.2,1-2.2,2.2-2.2c1.2,0,2.2,1,2.2,2.2v0C10.2,9.2,9.2,10.2,8,10.2z"/>
</svg>
@@ -0,0 +1,9 @@
<script lang="ts">
export let size: 'small' | 'medium' | 'large'
const fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M3,9.6h18c0.3,0,0.6-0.3,0.6-0.6S21.3,8.4,21,8.4H3C2.7,8.4,2.4,8.7,2.4,9S2.7,9.6,3,9.6z"/>
<path d="M21,14.4H3c-0.3,0-0.6,0.3-0.6,0.6s0.3,0.6,0.6,0.6h18c0.3,0,0.6-0.3,0.6-0.6S21.3,14.4,21,14.4z"/>
</svg>
@@ -0,0 +1 @@
Some inbox stuff here.
@@ -0,0 +1,12 @@
<script lang='ts'>
import { Ref } from '@anticrm/core'
import { IssueStatus, Team } from '@anticrm/tracker'
import Issues from './Issues.svelte'
export let currentSpace: Ref<Team>
let todalIssues = 0
</script>
<div class='fs-title'>
Active issues {todalIssues}
</div>
<Issues currentSpace={currentSpace} categories={[IssueStatus.InProgress, IssueStatus.Todo]} on:content={(evt) => { todalIssues = evt.detail.length } }></Issues>
@@ -0,0 +1,7 @@
<script lang='ts'>
import { Ref } from '@anticrm/core'
import { IssueStatus, Team } from '@anticrm/tracker'
import Issues from './Issues.svelte'
export let currentSpace: Ref<Team>
</script>
<Issues {currentSpace} categories={[IssueStatus.Backlog]}></Issues>
@@ -0,0 +1,52 @@
<script lang="ts">
import { DocumentQuery, FindOptions } from '@anticrm/core'
import { Issue, IssueStatus, Team } from '@anticrm/tracker'
import { Scroller } from '@anticrm/ui'
import { Table } from '@anticrm/view-resources'
import tracker from '../../plugin'
import contact from '@anticrm/contact'
export let query: DocumentQuery<Issue>
export let category: IssueStatus
export let currentTeam: Team
const options: FindOptions<Issue> = {
lookup: {
assignee: contact.class.Employee
}
}
let visible = false
</script>
<div class='category' class:visible={visible}>
<div class='fs-title'>
{IssueStatus[category]}
</div>
<Scroller>
<Table
_class={tracker.class.Issue}
config={[
{ key: '', presenter: tracker.component.IssuePresenter, props: { currentTeam }, label: tracker.string.Issue },
'title',
// 'status',
'$lookup.assignee',
'modifiedOn'
]}
options={options}
query={{ ...query, status: category }}
showNotification
highlightRows
on:content={(evt) => { visible = evt.detail.length > 0 }}
/>
</Scroller>
</div>
<style lang="scss">
.category {
display: none;
&.visible {
display: block;
}
}
</style>
@@ -0,0 +1,81 @@
<!--
// 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 contact from '@anticrm/contact'
import { getClient, UserBox } from '@anticrm/presentation'
import type { Issue } from '@anticrm/tracker'
import { StyledTextBox } from '@anticrm/text-editor'
import { EditBox, Grid } from '@anticrm/ui'
import { createEventDispatcher, onMount } from 'svelte'
import tracker from '../../plugin'
import Card from '../Card.svelte'
export let object: Issue
const dispatch = createEventDispatcher()
const client = getClient()
function change (field: string, value: any) {
client.update(object, { [field]: value })
}
onMount(() => {
dispatch('open', { ignoreKeys: ['comments', 'name', 'description', 'number'] })
})
</script>
<Card
label={tracker.string.NewIssue}
okAction={() => {}}
canSave={true}
on:close={() => {
dispatch('close')
}}
>
{#if object !== undefined}
<Grid column={1} rowGap={1.5}>
<EditBox
label={tracker.string.Title}
bind:value={object.title}
placeholder={tracker.string.IssueTitlePlaceholder}
maxWidth={'16rem'}
focus
on:change={() => change('title', object.title) }
/>
<StyledTextBox alwaysEdit bind:content={object.description} placeholder={tracker.string.IssueDescriptionPlaceholder}
on:change={() => change('description', object.description) }/>
<UserBox
_class={contact.class.Employee}
title={tracker.string.Assignee}
caption={tracker.string.Assignee}
bind:value={object.assignee}
allowDeselect
titleDeselect={tracker.string.TaskUnAssign}
on:change={() => change('assignee', object.assignee) }
/>
</Grid>
{/if}
</Card>
<style lang="scss">
.description {
display: flex;
padding: 1rem;
height: 12rem;
border-radius: .25rem;
background-color: var(--theme-bg-accent-color);
border: 1px solid var(--theme-bg-accent-color);
}
</style>
@@ -0,0 +1,45 @@
<!--
// 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 { getClient } from '@anticrm/presentation'
import type { Issue, Team } from '@anticrm/tracker'
import { Icon, showPopup } from '@anticrm/ui'
import tracker from '../../plugin'
export let value: Issue
export let currentTeam: Team
export let inline: boolean = false
const client = getClient()
const shortLabel = client.getHierarchy().getClass(value._class).shortLabel
function show (evt: Event) {
showPopup(tracker.component.EditIssue, { object: value }, evt.currentTarget)
}
</script>
{#if value && shortLabel}
<div
class="flex-presenter"
class:inline-presenter={inline}
on:click={show}
>
<div class="icon">
<Icon icon={tracker.icon.Issue} size={'small'} />
</div>
<span class="label nowrap">{currentTeam.identifier}-{value.number}</span>
</div>
{/if}
@@ -0,0 +1,55 @@
<!--
// Copyright © 2022 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 { DocumentQuery, Ref } from '@anticrm/core'
import { createQuery } from '@anticrm/presentation'
import { Issue, IssueStatus, Team } from '@anticrm/tracker'
import { ScrollBox } from '@anticrm/ui'
import CategoryPresenter from './CategoryPresenter.svelte'
import tracker from '../../plugin'
export let currentSpace: Ref<Team>
export let categories = [
IssueStatus.InProgress,
IssueStatus.Todo,
IssueStatus.Backlog,
IssueStatus.Done,
IssueStatus.Canceled
]
export let query: DocumentQuery<Issue> = {}
export let search: string = ''
$: resultQuery =
search === '' ? { space: currentSpace, ...query } : { $search: search, space: currentSpace, ...query }
const spaceQuery = createQuery()
let currentTeam: Team | undefined
$: spaceQuery.query(tracker.class.Team, { _id: currentSpace }, (res) => {
currentTeam = res.shift()
})
</script>
{#if currentTeam}
<ScrollBox vertical stretch>
<div class="ml-4 mt-4">
{#each categories as category}
<CategoryPresenter {category} query={resultQuery} {currentTeam} />
{/each}
</div>
</ScrollBox>
{/if}
+47
View File
@@ -0,0 +1,47 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { Resources } from '@anticrm/platform'
import NopeComponent from './components/NopeComponent.svelte'
import Active from './components/issues/Active.svelte'
import Backlog from './components/issues/Backlog.svelte'
import Board from './components/issues/Board.svelte'
import Inbox from './components/inbox/Inbox.svelte'
import Issues from './components/issues/Issues.svelte'
import MyIssues from './components/myissues/MyIssues.svelte'
import Projects from './components/projects/Projects.svelte'
import Views from './components/views/Views.svelte'
import IssuePresenter from './components/issues/IssuePresenter.svelte'
import EditIssue from './components/issues/EditIssue.svelte'
import NewIssueHeader from './components/NewIssueHeader.svelte'
export default async (): Promise<Resources> => ({
component: {
NopeComponent,
Active,
Backlog,
Board,
Inbox,
Issues,
MyIssues,
Projects,
Views,
IssuePresenter,
EditIssue,
NewIssueHeader
}
})
+80
View File
@@ -0,0 +1,80 @@
//
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import type { IntlString } from '@anticrm/platform'
import { mergeIds } from '@anticrm/platform'
import tracker, { trackerId } from '../../tracker/lib'
import { AnyComponent } from '@anticrm/ui'
export default mergeIds(trackerId, tracker, {
string: {
More: '' as IntlString,
Delete: '' as IntlString,
Open: '' as IntlString,
Members: '' as IntlString,
Inbox: '' as IntlString,
MyIssues: '' as IntlString,
Issues: '' as IntlString,
Views: '' as IntlString,
Active: '' as IntlString,
Backlog: '' as IntlString,
Board: '' as IntlString,
Projects: '' as IntlString,
CreateTeam: '' as IntlString,
NewIssue: '' as IntlString,
Team: '' as IntlString,
SelectTeam: '' as IntlString,
Title: '' as IntlString,
Identifier: '' as IntlString,
Description: '' as IntlString,
Status: '' as IntlString,
Priority: '' as IntlString,
Number: '' as IntlString,
Assignee: '' as IntlString,
Parent: '' as IntlString,
BlockedBy: '' as IntlString,
RelatedTo: '' as IntlString,
Comments: '' as IntlString,
Attachments: '' as IntlString,
Labels: '' as IntlString,
Space: '' as IntlString,
DueDate: '' as IntlString,
Issue: '' as IntlString,
Document: '' as IntlString,
DocumentIcon: '' as IntlString,
DocumentColor: '' as IntlString,
Rank: '' as IntlString,
IssueTitlePlaceholder: '' as IntlString,
IssueDescriptionPlaceholder: '' as IntlString,
TaskUnAssign: '' as IntlString
},
component: {
NopeComponent: '' as AnyComponent,
Inbox: '' as AnyComponent,
MyIssues: '' as AnyComponent,
Issues: '' as AnyComponent,
Views: '' as AnyComponent,
Active: '' as AnyComponent,
Backlog: '' as AnyComponent,
Board: '' as AnyComponent,
Projects: '' as AnyComponent,
IssuePresenter: '' as AnyComponent,
EditIssue: '' as AnyComponent,
CreateTeam: '' as AnyComponent,
NewIssueHeader: '' as AnyComponent
}
})
+63
View File
@@ -0,0 +1,63 @@
//
// 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.
//
import { Ref } from '@anticrm/core'
import type { Asset, IntlString } from '@anticrm/platform'
import { Team } from '@anticrm/tracker'
import { AnyComponent } from '@anticrm/ui'
import { LexoDecimal, LexoNumeralSystem36, LexoRank } from 'lexorank'
import LexoRankBucket from 'lexorank/lib/lexoRank/lexoRankBucket'
export interface NavigationItem {
id: string
label: IntlString
icon: Asset
component: AnyComponent
componentProps?: Record<string, string>
top: boolean
}
export interface Selection {
currentTeam?: Ref<Team>
currentSpecial?: string
}
/**
* @public
*/
export const genRanks = (count: number): Generator<string, void, unknown> =>
(function * () {
const sys = new LexoNumeralSystem36()
const base = 36
const max = base ** 6
const gap = LexoDecimal.parse(Math.trunc(max / (count + 2)).toString(base), sys)
let cur = LexoDecimal.parse('0', sys)
for (let i = 0; i < count; i++) {
cur = cur.add(gap)
yield new LexoRank(LexoRankBucket.BUCKET_0, cur).toString()
}
})()
/**
* @public
*/
export const calcRank = (prev?: { rank: string }, next?: { rank: string }): string => {
const a = prev?.rank !== undefined ? LexoRank.parse(prev.rank) : LexoRank.min()
const b = next?.rank !== undefined ? LexoRank.parse(next.rank) : LexoRank.max()
return a.between(b).toString()
}
@@ -0,0 +1,5 @@
const sveltePreprocess = require('svelte-preprocess')
module.exports = {
preprocess: sveltePreprocess()
};
+15
View File
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"moduleResolution": "node",
"target": "esnext",
"module": "esnext",
"declaration": true,
"outDir": "./lib",
"strict": true,
"esModuleInterop": true,
"lib": [
"esnext",
"dom"
]
}
}
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/platform-rig/profiles/default/config/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}
+4
View File
@@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/
+18
View File
@@ -0,0 +1,18 @@
// The "rig.json" file directs tools to look for their config files in an external package.
// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
/**
* (Required) The name of the rig package to inherit from.
* It should be an NPM package name with the "-rig" suffix.
*/
"rigPackageName": "@anticrm/platform-rig"
/**
* (Optional) Selects a config profile from the rig package. The name must consist of
* lowercase alphanumeric words separated by hyphens, for example "sample-profile".
* If omitted, then the "default" profile will be used."
*/
// "rigProfile": "your-profile-name"
}
+37
View File
@@ -0,0 +1,37 @@
{
"name": "@anticrm/tracker",
"version": "0.6.0",
"main": "lib/index.js",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "heft build",
"build:watch": "tsc",
"lint:fix": "eslint --fix src",
"lint": "eslint src",
"format": "prettier --write src && eslint --fix src"
},
"devDependencies": {
"@anticrm/platform-rig": "~0.6.0",
"@types/heft-jest": "^1.0.2",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-node": "^11.1.0",
"eslint": "^7.32.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"prettier": "^2.4.1",
"@rushstack/heft": "^0.41.1",
"typescript": "^4.3.5"
},
"dependencies": {
"@anticrm/core": "~0.6.16",
"@anticrm/platform": "~0.6.5",
"@anticrm/ui": "~0.6.0",
"@anticrm/contact": "~0.6.5",
"@anticrm/chunter": "~0.6.1",
"@anticrm/attachment": "~0.6.1",
"@anticrm/task": "~0.6.0"
}
}
+145
View File
@@ -0,0 +1,145 @@
//
// Copyright © 2022 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.
//
import { Employee } from '@anticrm/contact'
import type { Class, Doc, Markup, Ref, Space, Timestamp } from '@anticrm/core'
import type { Asset, IntlString, Plugin } from '@anticrm/platform'
import { plugin } from '@anticrm/platform'
import { AnyComponent } from '@anticrm/ui'
/**
* @public
*/
export interface Team extends Space {
teamLogo?: string | null
identifier: string // Team identifier
sequence: number
}
/**
* @public
*/
export enum IssueStatus {
Backlog,
Todo,
InProgress,
Done,
Canceled
}
/**
* @public
*/
export enum IssuePriority {
NoPriority,
Urgent,
High,
Medium,
Low
}
/**
* @public
*/
export interface Issue extends Doc {
title: string
description: Markup
status: IssueStatus
priority: IssuePriority
number: number
assignee: Ref<Employee> | null
// For subtasks
parentIssue?: Ref<Issue>
blockedBy?: Ref<Issue>[]
relatedIssue?: Ref<Issue>[]
comments: number
attachments?: number
labels?: string[]
space: Ref<Team>
dueDate: Timestamp | null
rank: string
}
/**
* @public
*/
export interface Document extends Doc {
title: string
icon: string | null
color: number
content?: Markup
space: Ref<Team>
}
/**
* @public
*/
export enum ProjectStatus {
Planned,
InProgress,
Paused,
Completed,
Canceled
}
/**
* @public
*/
export interface Project extends Doc {
label: IntlString
description?: Markup
space: Ref<Team>
comments: number
attachments?: number
startDate: Timestamp | null
targetDate: Timestamp | null
// Ref<Document>[]
documents: number
}
/**
* @public
*/
export const trackerId = 'tracker' as Plugin
export default plugin(trackerId, {
class: {
Team: '' as Ref<Class<Team>>,
Issue: '' as Ref<Class<Issue>>,
Document: '' as Ref<Class<Document>>,
Project: '' as Ref<Class<Project>>
},
component: {
Tracker: '' as AnyComponent,
TrackerApp: '' as AnyComponent
},
icon: {
TrackerApplication: '' as Asset,
Project: '' as Asset,
Issue: '' as Asset,
Team: '' as Asset,
Document: '' as Asset
}
})
+9
View File
@@ -0,0 +1,9 @@
{
"extends": "./node_modules/@anticrm/platform-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"lib": ["esnext", "dom"]
}
}
@@ -23,6 +23,7 @@
import MoreV from './icons/MoreV.svelte'
import Menu from './Menu.svelte'
import notification from '@anticrm/notification'
import { createEventDispatcher } from 'svelte'
export let _class: Ref<Class<Doc>>
export let query: DocumentQuery<Doc>
@@ -45,6 +46,8 @@
const q = createQuery()
const dispatch = createEventDispatcher()
$: sortingFunction = (config.find(it => (typeof it !== 'string') && it.sortingKey === sortKey) as BuildModelKey)?.sortingFunction
let qindex = 0
@@ -69,6 +72,7 @@
const sf = sortingFunction
objects.sort((a, b) => -1 * sortOrder * sf(a, b))
}
dispatch('content', objects)
loading = false
},
{ sort: { [sortKey]: sortOrder }, ...options, limit: 200 }
@@ -15,7 +15,7 @@
<script lang="ts">
import contact, { Employee, EmployeeAccount, formatName } from '@anticrm/contact'
import { getCurrentAccount, Ref } from '@anticrm/core'
import { getCurrentAccount } from '@anticrm/core'
import login from '@anticrm/login'
import { Avatar, createQuery, getClient } from '@anticrm/presentation'
import setting, { SettingsCategory } from '@anticrm/setting'
@@ -15,13 +15,20 @@
<script lang="ts">
import type { IntlString } from '@anticrm/platform'
import { Label } from '@anticrm/ui'
import { fetchMetadataLocalStorage, Label } from '@anticrm/ui'
import login from '@anticrm/login'
export let label: IntlString
// export let action: () => Promise<void> | void
</script>
<div class="antiNav-header">
<span class="fs-title overflow-label"><Label {label}></Label></span>
<div class='flex-row'>
<span class="fs-title overflow-label"><Label {label}></Label></span>
<div>
{fetchMetadataLocalStorage(login.metadata.CurrentWorkspace)}
</div>
</div>
<!-- <ActionIcon label={workbench.string.More} icon={IconMoreH} size={'small'} {action}/> -->
</div>
@@ -95,18 +95,18 @@
<Scroller>
{#if model.specials}
{#each topSpecials as special}
<SpecialElement label={special.label} icon={special.icon} on:click={() => dispatch('special', special.id)} selected={special.id === currentSpecial} />
<SpecialElement label={special.label} icon={special.icon} on:click={() => dispatch('special', special.id)} selected={special.id === currentSpecial} indent={'ml-2'} />
{/each}
{/if}
{#if showDivider}<TreeSeparator />{/if}
{#each model.spaces as m (m.label)}
<SpacesNav spaces={shownSpaces.filter(it => hierarchy.isDerived(it._class, m.spaceClass))} {currentSpace} model={m} on:space/>
<SpacesNav spaces={shownSpaces.filter(it => hierarchy.isDerived(it._class, m.spaceClass))} {currentSpace} model={m} on:space {currentSpecial}/>
{/each}
{#if model.specials}
{#each bottomSpecials as special}
<SpecialElement label={special.label} icon={special.icon} on:click={() => dispatch('special', special.id)} selected={special.id === currentSpecial} />
<SpecialElement label={special.label} icon={special.icon} on:click={() => dispatch('special', special.id)} selected={special.id === currentSpecial} indent={'ml-2'} />
{/each}
{/if}
<div class="antiNav-space" />
@@ -13,17 +13,17 @@
// limitations under the License.
-->
<script lang="ts">
import calendar from '@anticrm/calendar'
import contact, { Employee, EmployeeAccount } from '@anticrm/contact'
import core, { Client, getCurrentAccount, Ref, Space } from '@anticrm/core'
import notification, { NotificationStatus } from '@anticrm/notification'
import { NotificationClientImpl } from '@anticrm/notification-resources'
import { IntlString } from '@anticrm/platform'
import { Avatar, createQuery, getClient, setClient } from '@anticrm/presentation'
import { Avatar, createQuery, setClient } from '@anticrm/presentation'
import {
AnyComponent, closePopup,
closeTooltip,
Component,
getCurrentLocation,
Component, getCurrentLocation,
location,
navigate,
PanelInstance,
@@ -33,7 +33,6 @@
} from '@anticrm/ui'
import type { Application, NavigatorModel, SpecialNavModel, ViewConfiguration } from '@anticrm/workbench'
import { onDestroy } from 'svelte'
import calendar from '@anticrm/calendar'
import workbench from '../plugin'
import AccountPopup from './AccountPopup.svelte'
import ActivityStatus from './ActivityStatus.svelte'
@@ -43,6 +42,7 @@
import NavHeader from './NavHeader.svelte'
import Navigator from './Navigator.svelte'
import SpaceView from './SpaceView.svelte'
export let client: Client
@@ -72,20 +72,42 @@
const currentFolder = loc.path[2] as Ref<Space>
if (currentSpecial !== currentFolder) {
specialComponent = getSpecialComponent(currentFolder)
if (specialComponent !== undefined) {
const newSpecial = getSpecialComponent(currentFolder)
if (newSpecial !== undefined) {
specialComponent = newSpecial
currentSpecial = currentFolder
currentSpace = undefined
return
}
}
updateSpace(currentFolder)
updateSpace(currentFolder, loc.path[3])
})
)
async function updateSpace (spaceId?: Ref<Space>): Promise<void> {
async function updateSpace (spaceId?: Ref<Space>, spaceSpecial?: string): Promise<void> {
if (spaceId === currentSpace) {
// Check if we need update location.
const loc = getCurrentLocation()
if (loc.path[3] !== spaceSpecial) {
if (spaceSpecial !== undefined) {
loc.path[3] = spaceSpecial
loc.path.length = 4
} else {
loc.path.length = 3
}
if (spaceSpecial !== undefined) {
loc.path[3] = spaceSpecial
loc.path.length = 4
specialComponent = getSpecialComponent(spaceSpecial)
currentSpecial = spaceSpecial
} else {
loc.path.length = 3
spaceSpecial = undefined
currentSpecial = undefined
}
navigate(loc)
}
return
}
if (spaceId === undefined) {
@@ -95,16 +117,23 @@
if (space) {
currentSpace = spaceId
currentSpecial = undefined
specialComponent = undefined
const spaceClass = client.getHierarchy().getClass(space._class) // (await client.findAll(core.class.Class, { _id: space._class }))[0]
const view = client.getHierarchy().as(spaceClass, workbench.mixin.SpaceView)
currentView = view.view
createItemDialog = currentView.createItemDialog ?? undefined
createItemLabel = currentView.createItemLabel ?? undefined
createItemDialog = currentView?.createItemDialog ?? undefined
createItemLabel = currentView?.createItemLabel ?? undefined
const loc = getCurrentLocation()
loc.path[2] = spaceId
loc.path.length = 3
if (spaceSpecial !== undefined) {
loc.path[3] = spaceSpecial
currentSpecial = spaceSpecial
loc.path.length = 4
specialComponent = getSpecialComponent(spaceSpecial)
}
navigate(loc)
} else {
currentView = undefined
@@ -112,7 +141,6 @@
createItemLabel = undefined
}
}
$: updateSpace(currentSpace)
function selectSpecial (id: string): void {
specialComponent = getSpecialComponent(id)
@@ -136,7 +164,16 @@
}
function getSpecialComponent (id: string): SpecialNavModel | undefined {
return navigatorModel?.specials?.find((x) => x.id === id)
const sp = navigatorModel?.specials?.find((x) => x.id === id)
if (sp !== undefined) {
return sp
}
for (const s of navigatorModel?.spaces ?? []) {
const sp = s.specials?.find((x) => x.id === id)
if (sp !== undefined) {
return sp
}
}
}
let apps: Application[] = []
@@ -227,8 +264,8 @@
</svg>
<div class="workbench-container">
<div class="antiPanel-application" on:click={toggleNav}>
<div class="flex-col">
<ActivityStatus status="active" />
<div class="flex-col">
<ActivityStatus status="active" />
<AppItem
icon={TopMenu}
label={visibileNav ? workbench.string.HideMenu : workbench.string.ShowMenu}
@@ -279,24 +316,30 @@
</div>
{#if currentApplication && navigatorModel && navigator && visibileNav}
<div class="antiPanel-navigator filled indent">
{#if currentApplication}
{#if currentApplication}
<NavHeader label={currentApplication.label} />
{#if currentApplication.navHeaderComponent}
<Component is={currentApplication.navHeaderComponent} props={{ currentSpace }}/>
{/if}
{/if}
<Navigator
{currentSpace}
{currentSpecial}
model={navigatorModel}
on:special={(evt) => selectSpecial(evt.detail)}
on:space={(evt) => updateSpace(evt.detail)}
on:space={(evt) => updateSpace(evt.detail.space, evt.detail.spaceSpecial)}
on:archive={(evt) => selectArchive()}
/>
{#if currentApplication.navFooterComponent}
<Component is={currentApplication.navFooterComponent} props={{ currentSpace }}/>
{/if}
</div>
{/if}
<div class="antiPanel-component indent antiComponent" class:filled={isNavigate}>
{#if currentApplication && currentApplication.component}
<Component is={currentApplication.component} />
<Component is={currentApplication.component} props={{ currentSpace }}/>
{:else if specialComponent}
<Component is={specialComponent.component} props={{ model: navigatorModel, ...specialComponent.componentProps }} />
<Component is={specialComponent.component} props={{ model: navigatorModel, ...specialComponent.componentProps, currentSpace }} />
{:else}
<SpaceView {currentSpace} {currentView} {createItemDialog} {createItemLabel} />
{/if}
@@ -14,84 +14,13 @@
-->
<script lang="ts">
import { getMetadata, getResource } from '@anticrm/platform'
import type { Client } from '@anticrm/core'
import core from '@anticrm/core'
import { setCurrentAccount } from '@anticrm/core'
import { navigate, Loading, fetchMetadataLocalStorage, setMetadataLocalStorage, getCurrentLocation, locationToUrl } from '@anticrm/ui'
import client from '@anticrm/client'
import login from '@anticrm/login'
import contact from '@anticrm/contact'
import { connect, versionError } from '@anticrm/presentation'
import { Loading } from '@anticrm/ui'
import Workbench from './Workbench.svelte'
import workbench from '../plugin'
let versionError: string | undefined = ''
async function connect (): Promise<Client | undefined> {
const token = fetchMetadataLocalStorage(login.metadata.LoginToken)
const endpoint = fetchMetadataLocalStorage(login.metadata.LoginEndpoint)
const email = fetchMetadataLocalStorage(login.metadata.LoginEmail)
if (token === null || endpoint === null || email === null) {
navigate({ path: [login.component.LoginApp], query: { navigateUrl: encodeURIComponent(JSON.stringify(getCurrentLocation())) } })
return
}
const getClient = await getResource(client.function.GetClient)
const instance = await getClient(token, endpoint)
console.log('logging in as', email)
const me = await instance.findOne(contact.class.EmployeeAccount, { email })
if (me !== undefined) {
console.log('login: employee account', me)
setCurrentAccount(me)
} else {
console.error('WARNING: no employee account found.')
setMetadataLocalStorage(login.metadata.LoginToken, null)
setMetadataLocalStorage(login.metadata.LoginEndpoint, null)
setMetadataLocalStorage(login.metadata.LoginEmail, null)
setMetadataLocalStorage(login.metadata.CurrentWorkspace, null)
navigate({ path: [login.component.LoginApp], query: { navigateUrl: encodeURIComponent(JSON.stringify(getCurrentLocation())) } })
return
}
try {
console.log('checking model version')
const version = await instance.findOne(core.class.Version, {})
console.log('Model version', version)
const requirdVersion = getMetadata(workbench.metadata.RequiredVersion)
if (requirdVersion !== undefined) {
console.log('checking min model version', requirdVersion)
const versionStr = `${version?.major}.${version?.minor}.${version?.patch}`
if (version === undefined || requirdVersion !== versionStr) {
versionError = `${versionStr} => ${requirdVersion}`
return undefined
}
}
} catch (err: any) {
console.log(err)
const requirdVersion = getMetadata(workbench.metadata.RequiredVersion)
console.log('checking min model version', requirdVersion)
if (requirdVersion !== undefined) {
versionError = `'unknown' => ${requirdVersion}`
return undefined
}
}
// Update window title
document.title = [(fetchMetadataLocalStorage(login.metadata.CurrentWorkspace)), 'Platform'].filter(it => it).join(' - ')
return instance
}
</script>
{#await connect()}
{#await connect('Platform')}
<Loading/>
{:then client}
{#if !client && versionError}
@@ -99,7 +28,7 @@ async function connect (): Promise<Client | undefined> {
<h1>Server is under maintenance.</h1>
{versionError}
</div>
{:else}
{:else if client}
<Workbench {client}/>
{/if}
{:catch error}
@@ -13,23 +13,25 @@
// limitations under the License.
-->
<script lang="ts">
import type { Class,Doc,Ref,Space } from '@anticrm/core'
import type { Class, Doc, Ref, Space } from '@anticrm/core'
import core from '@anticrm/core'
import { getResource } from '@anticrm/platform'
import { getClient } from '@anticrm/presentation'
import { Action,AnyComponent,IconAdd,IconEdit,showPanel,showPopup } from '@anticrm/ui'
import { Action, AnyComponent, IconAdd, IconEdit, showPanel, showPopup } from '@anticrm/ui'
import view from '@anticrm/view'
import { getActions as getContributedActions } from '@anticrm/view-resources'
import { SpacesNavModel } from '@anticrm/workbench'
import { createEventDispatcher } from 'svelte'
import plugin from '../../plugin'
import { classIcon } from '../../utils'
import SpecialElement from './SpecialElement.svelte'
import TreeItem from './TreeItem.svelte'
import TreeNode from './TreeNode.svelte'
export let model: SpacesNavModel
export let currentSpace: Ref<Space> | undefined
export let spaces: Space[]
export let currentSpecial: string | undefined
const client = getClient()
const dispatch = createEventDispatcher()
@@ -50,7 +52,7 @@
}
}
async function getEditor (_class: Ref<Class<Doc>>): Promise<AnyComponent | undefined> {
async function getEditor(_class: Ref<Class<Doc>>): Promise<AnyComponent | undefined> {
const hierarchy = client.getHierarchy()
const clazz = hierarchy.getClass(_class)
const editorMixin = hierarchy.as(clazz, view.mixin.ObjectEditor)
@@ -58,11 +60,11 @@
return editorMixin.editor
}
function selectSpace (id: Ref<Space>) {
dispatch('space', id)
function selectSpace(id: Ref<Space>, spaceSpecial?: string) {
dispatch('space', { space: id, spaceSpecial })
}
async function getActions (space: Space): Promise<Action[]> {
async function getActions(space: Space): Promise<Action[]> {
const result = [editSpace]
const extraActions = await getContributedActions(client, space, core.class.Space)
@@ -80,10 +82,26 @@
}
</script>
<div>
<TreeNode label={model.label} actions={async () => [addSpace]}>
{#each spaces as space (space._id)}
<TreeNode label={model.label} actions={async () => [addSpace]} indent={'ml-2'}>
{#each spaces as space (space._id)}
{#if model.specials}
<TreeNode title={space.name} indent={'ml-2'}>
{#each model.specials as special}
<SpecialElement
indent={'ml-4'}
label={special.label}
icon={special.icon}
on:click={() => dispatch('special', special.id)}
selected={currentSpace === space._id && special.id === currentSpecial}
on:click={() => {
selectSpace(space._id, special.id)
}}
/>
{/each}
</TreeNode>
{:else}
<TreeItem
indent={'ml-4'}
_id={space._id}
title={space.name}
icon={classIcon(client, space._class)}
@@ -93,6 +111,6 @@
selectSpace(space._id)
}}
/>
{/each}
</TreeNode>
</div>
{/if}
{/each}
</TreeNode>
@@ -23,13 +23,14 @@
export let notifications = 0
export let actions: Action[] = []
export let selected: boolean = false
export let indent: 'default' | 'ml-2' | 'ml-4' | 'ml-8' = 'default'
const dispatch = createEventDispatcher()
</script>
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
<div
class="antiNav-element" class:selected
class="antiNav-element" class:selected class:ml-2={indent === 'ml-2'} class:ml-4={indent === 'ml-4'} class:ml-8={indent === 'ml-8'}
on:click|stopPropagation={() => {
dispatch('click')
}}
@@ -31,11 +31,12 @@
export let collapsed = false
export let selected = false
export let actions: () => Promise<Action[]> = async () => []
export let indent: 'default' | 'ml-2' | 'ml-4' | 'ml-8' = 'default'
const dispatch = createEventDispatcher()
let hovered = false
async function onMenuClick (ev: MouseEvent) {
async function onMenuClick(ev: MouseEvent) {
showPopup(Menu, { actions: await actions(), ctx: _id }, ev.target as HTMLElement, () => {
hovered = false
})
@@ -43,19 +44,37 @@
}
</script>
<div class="antiNav-element" class:selected class:hovered
<div
class="antiNav-element"
class:selected
class:hovered
class:ml-2={indent === 'ml-2'}
class:ml-4={indent === 'ml-4'}
class:ml-8={indent === 'ml-8'}
on:click|stopPropagation={() => {
if (node && !icon) collapsed = !collapsed
dispatch('click')
}}
>
<div class="an-element__icon" class:sub={!node}>
{#if icon}
<Icon {icon} size={'small'} />
{:else if collapsed}<Collapsed size={'small'} />{:else}<Expanded size={'small'} />{/if}
</div>
<span class="an-element__label" class:title={node}>
{#if label}<Label {label} />{:else}{title}{/if}
<div class="flex-row-center">
{#if icon}
<div class="an-element__icon" class:sub={!node}>
<Icon {icon} size={'small'} />
</div>
{/if}
{#if label}<Label {label} />{:else}{title}{/if}
{#if !icon}
<div class="ml-2" class:sub={!node}>
{#if collapsed}
<Collapsed size={'small'} />
{:else}
<Expanded size={'small'} />
{/if}
</div>
{/if}
</div>
</span>
{#if node === false}
<div class="an-element__tool" on:click|stopPropagation={onMenuClick}>
@@ -26,9 +26,10 @@
export let notifications = 0
export let actions: () => Promise<Action[]> = async () => []
export let selected: boolean = false
export let indent: 'default' | 'ml-2' | 'ml-4' | 'ml-8' = 'default'
const dispatch = createEventDispatcher()
</script>
<TreeElement {_id} {icon} {title} {notifications} {selected} {actions} collapsed on:click={() => { dispatch('click') }}/>
<TreeElement {_id} {icon} {title} {notifications} {selected} {actions} collapsed {indent} on:click={() => { dispatch('click') }}/>
@@ -18,13 +18,15 @@
import type { Action } from '@anticrm/ui'
import TreeElement from './TreeElement.svelte'
export let label: IntlString
export let title: string | undefined = undefined
export let label: IntlString | undefined = undefined
export let actions: () => Promise<Action[]> = async () => []
export let notifications = 0
export let collapsed = false
export let indent: 'default' | 'ml-2' | 'ml-4' | 'ml-8' = 'default'
</script>
<TreeElement {label} {notifications} {collapsed} {actions} node>
<TreeElement {title} {label} {notifications} {collapsed} {actions} node {indent}>
<slot/>
</TreeElement>
+8 -5
View File
@@ -14,7 +14,7 @@
//
import type { Class, Doc, Mixin, Obj, Ref, Space } from '@anticrm/core'
import type { Asset, IntlString, Metadata, Plugin, Resource } from '@anticrm/platform'
import type { Asset, IntlString, Plugin, Resource } from '@anticrm/platform'
import { plugin } from '@anticrm/platform'
import { AnyComponent } from '@anticrm/ui'
@@ -29,6 +29,9 @@ export interface Application extends Doc {
// Component will be displayed in case navigator model is not defined, or nothing is selected in navigator model
component?: AnyComponent
navHeaderComponent?: AnyComponent
navFooterComponent?: AnyComponent
}
/**
@@ -39,6 +42,9 @@ export interface SpacesNavModel {
spaceClass: Ref<Class<Space>>
addSpaceLabel: IntlString
createComponent: AnyComponent
// Child special items.
specials?: SpecialNavModel[]
}
/**
@@ -55,7 +61,7 @@ export interface NavigatorModel {
export interface SpecialNavModel {
id: string // Uniq id
label: IntlString
icon: Asset
icon?: Asset
component: AnyComponent
componentProps?: Record<string, string>
position?: 'top'|'bottom' // undefined == 'top
@@ -94,8 +100,5 @@ export default plugin(workbenchId, {
},
component: {
WorkbenchApp: '' as AnyComponent
},
metadata: {
RequiredVersion: '' as Metadata<string>
}
})
+21
View File
@@ -1211,5 +1211,26 @@
"projectFolder": "server-plugins/telegram-resources",
"shouldPublish": true
},
{
"packageName": "@anticrm/tracker",
"projectFolder": "plugins/tracker",
"shouldPublish": true
},
{
"packageName": "@anticrm/tracker-assets",
"projectFolder": "plugins/tracker-assets",
"shouldPublish": true
},
{
"packageName": "@anticrm/tracker-resources",
"projectFolder": "plugins/tracker-resources",
"shouldPublish": true
},
{
"packageName": "@anticrm/model-tracker",
"projectFolder": "models/tracker",
"shouldPublish": true
},
]
}
+1 -1
View File
@@ -1,5 +1,5 @@
res=''
echo "Warting Elastic to up and running with attachment processor..."
echo "Warning Elastic to up and running with attachment processor..."
while true
do
res=$(curl -s -XPUT "localhost:9201/_ingest/pipeline/attachment?pretty" -H 'Content-Type: application/json' -d'