EZQMS-602: Moved Rank to its own package (#4845)

Signed-off-by: Petr Vyazovetskiy <develop.pit@gmail.com>
This commit is contained in:
Pete Anøther 2024-03-05 02:38:10 -03:00 committed by GitHub
parent 423d6f044a
commit bbe4a3a6d6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 477 additions and 398 deletions

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,7 @@ import core, {
import tracker, { Issue, IssuePriority, IssueStatus, Project } from '@hcengineering/tracker' import tracker, { Issue, IssuePriority, IssueStatus, Project } from '@hcengineering/tracker'
import { connect } from './connect' import { connect } from './connect'
import { calcRank } from '@hcengineering/task' import { makeRank } from '@hcengineering/task'
let objectId: Ref<Issue> = generateId() let objectId: Ref<Issue> = generateId()
const space = tracker.project.DefaultProject const space = tracker.project.DefaultProject
@ -94,7 +94,7 @@ async function genIssue (client: TxOperations, statuses: Ref<IssueStatus>[]): Pr
number, number,
status: faker.random.arrayElement(statuses), status: faker.random.arrayElement(statuses),
priority: faker.random.arrayElement(Object.values(IssuePriority)) as IssuePriority, priority: faker.random.arrayElement(Object.values(IssuePriority)) as IssuePriority,
rank: calcRank(lastOne, undefined), rank: makeRank(lastOne?.rank, undefined),
comments: 0, comments: 0,
subIssues: 0, subIssues: 0,
dueDate: object.dueDate, dueDate: object.dueDate,

View File

@ -15,7 +15,7 @@ import core, {
import { MinioService } from '@hcengineering/minio' import { MinioService } from '@hcengineering/minio'
import recruit from '@hcengineering/model-recruit' import recruit from '@hcengineering/model-recruit'
import { Applicant, Candidate, Vacancy } from '@hcengineering/recruit' import { Applicant, Candidate, Vacancy } from '@hcengineering/recruit'
import task, { ProjectType, TaskType, genRanks } from '@hcengineering/task' import task, { ProjectType, TaskType, genRanks, type Rank } from '@hcengineering/task'
import faker from 'faker' import faker from 'faker'
import jpeg, { BufferRet } from 'jpeg-js' import jpeg, { BufferRet } from 'jpeg-js'
import { AttachmentOptions, addAttachments } from './attachments' import { AttachmentOptions, addAttachments } from './attachments'
@ -174,7 +174,7 @@ async function genApplicant (
options: RecruitOptions, options: RecruitOptions,
minio: MinioService, minio: MinioService,
workspaceId: WorkspaceId, workspaceId: WorkspaceId,
rank: string rank: Rank
): Promise<void> { ): Promise<void> {
const applicantId = `vacancy-${vacancyId}-${candidateId}` as Ref<Applicant> const applicantId = `vacancy-${vacancyId}-${candidateId}` as Ref<Applicant>

View File

@ -72,6 +72,7 @@ import {
type ProjectType, type ProjectType,
type ProjectTypeClass, type ProjectTypeClass,
type ProjectTypeDescriptor, type ProjectTypeDescriptor,
type Rank,
type Sequence, type Sequence,
type Task, type Task,
type TaskStatusFactory, type TaskStatusFactory,
@ -123,7 +124,7 @@ export class TTask extends TAttachedDoc implements Task {
@Prop(TypeString(), task.string.Rank) @Prop(TypeString(), task.string.Rank)
@Index(IndexKind.IndexedDsc) @Index(IndexKind.IndexedDsc)
@Hidden() @Hidden()
rank!: string rank!: Rank
@Prop(Collection(tags.class.TagReference, task.string.TaskLabels), task.string.TaskLabels) @Prop(Collection(tags.class.TagReference, task.string.TaskLabels), task.string.TaskLabels)
labels?: number labels?: number

View File

@ -44,6 +44,7 @@
"@hcengineering/platform": "^0.6.9", "@hcengineering/platform": "^0.6.9",
"@hcengineering/core": "^0.6.28", "@hcengineering/core": "^0.6.28",
"@hcengineering/presentation": "^0.6.2", "@hcengineering/presentation": "^0.6.2",
"@hcengineering/notification": "^0.6.16" "@hcengineering/notification": "^0.6.16",
"@hcengineering/rank": "^0.6.0"
} }
} }

View File

@ -25,10 +25,10 @@
Space Space
} from '@hcengineering/core' } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation' import { getClient } from '@hcengineering/presentation'
import { makeRank } from '@hcengineering/rank'
import { ScrollBox, Scroller } from '@hcengineering/ui' import { ScrollBox, Scroller } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import { CardDragEvent, DocWithRank, Item } from '../types' import { CardDragEvent, DocWithRank, Item } from '../types'
import { calcRank } from '../utils'
import KanbanRow from './KanbanRow.svelte' import KanbanRow from './KanbanRow.svelte'
export let categories: CategoryType[] = [] export let categories: CategoryType[] = []
@ -204,8 +204,7 @@
const arr = getGroupByValues(groupByDocs, state) ?? [] const arr = getGroupByValues(groupByDocs, state) ?? []
const s = arr.findIndex((p) => p._id === dragCard?._id) const s = arr.findIndex((p) => p._id === dragCard?._id)
if (s !== -1) { if (s !== -1) {
const newRank = calcRank(arr[s - 1], arr[s + 1]) dragCard.rank = makeRank(arr[s - 1]?.rank, arr[s + 1]?.rank)
dragCard.rank = newRank
} }
} }
isDragging = false isDragging = false

View File

@ -17,4 +17,3 @@ import '@hcengineering/platform-rig/profiles/ui/svelte'
export * from './types' export * from './types'
export { default as Kanban } from './components/Kanban.svelte' export { default as Kanban } from './components/Kanban.svelte'
export { default as KanbanCardEmpty } from './components/KanbanCardEmpty.svelte'

View File

@ -1,11 +1,12 @@
import { type Doc } from '@hcengineering/core' import { type Doc } from '@hcengineering/core'
import { type Asset } from '@hcengineering/platform' import { type Asset } from '@hcengineering/platform'
import type { Rank } from '@hcengineering/rank'
/** /**
* @public * @public
*/ */
export interface DocWithRank extends Doc { export interface DocWithRank extends Doc {
rank: string rank: Rank
} }
export type StateType = any export type StateType = any

View File

@ -1,47 +0,0 @@
//
// 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 { LexoRank, LexoDecimal, LexoNumeralSystem36 } from 'lexorank'
import LexoRankBucket from 'lexorank/lib/lexoRank/lexoRankBucket'
/**
* @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()
if (a.equals(b)) {
return a.genNext().toString()
}
return a.between(b).toString()
}

View File

@ -119,10 +119,6 @@ function getAttrs (target: any, prop: string): Record<string, any> {
/** /**
* @public * @public
* @param type -
* @param label -
* @param icon -
* @returns
*/ */
export function Prop (type: Type<PropertyType>, label: IntlString, extra: Partial<Attribute<PropertyType>> = {}) { export function Prop (type: Type<PropertyType>, label: IntlString, extra: Partial<Attribute<PropertyType>> = {}) {
return function (target: any, propertyKey: string): void { return function (target: any, propertyKey: string): void {
@ -225,9 +221,6 @@ export function Mixin<T extends Obj> (_class: Ref<Class<T>>, _extends: Ref<Class
/** /**
* @public * @public
* @param label -
* @param icon -
* @returns
*/ */
export function UX<T extends Obj> ( export function UX<T extends Obj> (
label: IntlString, label: IntlString,

View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig"
}

View File

@ -0,0 +1,7 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
roots: ["./src"],
coverageReporters: ["text-summary", "html"]
}

View File

@ -0,0 +1,38 @@
{
"name": "@hcengineering/rank",
"version": "0.6.0",
"main": "lib/index.js",
"svelte": "src/index.ts",
"types": "types/index.d.ts",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"_phase:build": "compile transpile src",
"_phase:validate": "compile validate",
"_phase:test": "jest --passWithNoTests --silent",
"_phase:format": "format src",
"build": "compile",
"test": "jest --passWithNoTests --silent",
"build:watch": "compile",
"format": "format src"
},
"devDependencies": {
"@hcengineering/platform-rig": "^0.6.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint-config-standard-with-typescript": "^40.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-n": "^15.4.0",
"eslint-plugin-promise": "^6.1.1",
"eslint": "^8.54.0",
"prettier": "^3.1.0",
"prettier-plugin-svelte": "^3.1.0",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"@types/jest":"^29.5.5",
"typescript":"^5.3.3"
},
"dependencies": {
"lexorank": "~1.0.4"
}
}

View File

@ -1,5 +1,5 @@
<!-- //
// Copyright © 2022 Hardcore Engineering Inc. // Copyright © 2024 Hardcore Engineering Inc.
// //
// Licensed under the Eclipse Public License, Version 2.0 (the "License"); // 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 // you may not use this file except in compliance with the License. You may
@ -11,27 +11,7 @@
// //
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
--> //
<div class="card-container"> export * from './types'
<slot /> export * from './utils'
</div>
<style lang="scss">
.card-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 0.75rem 1.5rem;
color: var(--dark-color);
background-color: var(--theme-kanban-card-bg-color);
border: 1px dotted var(--theme-kanban-card-border);
border-radius: 0.75rem;
cursor: pointer;
&:hover {
color: var(--theme-caption-color);
}
}
</style>

View File

@ -0,0 +1,21 @@
//
// Copyright © 2024 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.
//
/**
* @public
*
* String representation of {@link https://www.npmjs.com/package/lexorank LexoRank} type
*/
export type Rank = string

View File

@ -0,0 +1,42 @@
//
// Copyright © 2024 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 LexoRankBucket from 'lexorank/lib/lexoRank/lexoRankBucket'
import type { Rank } from './types'
import { LexoDecimal, LexoNumeralSystem36, LexoRank } from 'lexorank'
/** @public */
export function genRanks (count: number): Rank[] {
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)
const res: string[] = []
for (let i = 0; i < count; i++) {
cur = cur.add(gap)
res.push(new LexoRank(LexoRankBucket.BUCKET_0, cur).toString())
}
return res
}
/** @public */
export function makeRank (prev: Rank | undefined, next: Rank | undefined): Rank {
const prevLexoRank = prev === undefined ? LexoRank.min() : LexoRank.parse(prev)
const nextLexoRank = next === undefined ? LexoRank.max() : LexoRank.parse(next)
return prevLexoRank.equals(nextLexoRank)
? prevLexoRank.genNext().toString()
: prevLexoRank.between(nextLexoRank).toString()
}

View File

@ -0,0 +1,10 @@
{
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"declarationDir": "./types",
"tsBuildInfoFile": ".build/build.tsbuildinfo"
}
}

View File

@ -1,7 +1,7 @@
import { Organization } from '@hcengineering/contact' import { Organization } from '@hcengineering/contact'
import core, { Account, Client, Data, Doc, Ref, SortingOrder, Status, TxOperations } from '@hcengineering/core' import core, { Account, Client, Data, Doc, Ref, SortingOrder, Status, TxOperations } from '@hcengineering/core'
import recruit, { Applicant, Vacancy } from '@hcengineering/recruit' import recruit, { Applicant, Vacancy } from '@hcengineering/recruit'
import task, { ProjectType, calcRank } from '@hcengineering/task' import task, { ProjectType, makeRank } from '@hcengineering/task'
export async function createVacancy ( export async function createVacancy (
rawClient: Client, rawClient: Client,
@ -60,6 +60,6 @@ export async function createApplication (
...data, ...data,
status: selectedState._id, status: selectedState._id,
number: (incResult as any).object.sequence, number: (incResult as any).object.sequence,
rank: calcRank(lastOne, undefined) rank: makeRank(lastOne?.rank, undefined)
}) })
} }

View File

@ -18,7 +18,7 @@
import core, { AttachedData, Ref, SortingOrder, Space, generateId } from '@hcengineering/core' import core, { AttachedData, Ref, SortingOrder, Space, generateId } from '@hcengineering/core'
import { OK, Status } from '@hcengineering/platform' import { OK, Status } from '@hcengineering/platform'
import { Card, SpaceSelector, createQuery, getClient } from '@hcengineering/presentation' import { Card, SpaceSelector, createQuery, getClient } from '@hcengineering/presentation'
import task, { TaskType, calcRank } from '@hcengineering/task' import task, { TaskType, makeRank } from '@hcengineering/task'
import { EditBox, Grid, Status as StatusControl } from '@hcengineering/ui' import { EditBox, Grid, Status as StatusControl } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import board from '../plugin' import board from '../plugin'
@ -73,7 +73,7 @@
title, title,
kind, kind,
identifier: `CARD-${number}`, identifier: `CARD-${number}`,
rank: calcRank(lastOne, undefined), rank: makeRank(lastOne?.rank, undefined),
assignee: null, assignee: null,
description: '', description: '',
members: [], members: [],

View File

@ -15,7 +15,7 @@
<script lang="ts"> <script lang="ts">
import type { Card as BoardCard } from '@hcengineering/board' import type { Card as BoardCard } from '@hcengineering/board'
import board from '../../plugin' import board from '../../plugin'
import task, { calcRank } from '@hcengineering/task' import task, { makeRank } from '@hcengineering/task'
import { AttachedData, generateId, Ref, SortingOrder, Space } from '@hcengineering/core' import { AttachedData, generateId, Ref, SortingOrder, Space } from '@hcengineering/core'
import { IconAdd, Button, showPopup } from '@hcengineering/ui' import { IconAdd, Button, showPopup } from '@hcengineering/ui'
import { getClient } from '@hcengineering/presentation' import { getClient } from '@hcengineering/presentation'
@ -43,7 +43,7 @@
status: state._id, status: state._id,
number: (incResult as any).object.sequence, number: (incResult as any).object.sequence,
title, title,
rank: calcRank(lastOne, undefined), rank: makeRank(lastOne?.rank, undefined),
assignee: null, assignee: null,
description: '', description: '',
members: [], members: [],

View File

@ -3,7 +3,7 @@
import { Ref, SortingOrder } from '@hcengineering/core' import { Ref, SortingOrder } from '@hcengineering/core'
import { IntlString, translate } from '@hcengineering/platform' import { IntlString, translate } from '@hcengineering/platform'
import { createQuery } from '@hcengineering/presentation' import { createQuery } from '@hcengineering/presentation'
import { calcRank, State } from '@hcengineering/task' import { makeRank, State } from '@hcengineering/task'
import { DropdownLabels, DropdownTextItem, themeStore } from '@hcengineering/ui' import { DropdownLabels, DropdownTextItem, themeStore } from '@hcengineering/ui'
import board from '../../plugin' import board from '../../plugin'
@ -22,7 +22,7 @@
const filteredResult = isCopying ? result : result.filter((t) => t._id !== object._id) const filteredResult = isCopying ? result : result.filter((t) => t._id !== object._id)
;[ranks] = [...filteredResult, undefined].reduce<[DropdownTextItem[], Card | undefined]>( ;[ranks] = [...filteredResult, undefined].reduce<[DropdownTextItem[], Card | undefined]>(
([arr, prev], next) => [[...arr, { id: calcRank(prev, next), label: `${arr.length + 1}` }], next], ([arr, prev], next) => [[...arr, { id: makeRank(prev?.rank, next?.rank), label: `${arr.length + 1}` }], next],
[[], undefined] [[], undefined]
) )

View File

@ -11,7 +11,7 @@ import {
type Status type Status
} from '@hcengineering/core' } from '@hcengineering/core'
import { showPanel } from '@hcengineering/ui' import { showPanel } from '@hcengineering/ui'
import task, { calcRank } from '@hcengineering/task' import task, { makeRank } from '@hcengineering/task'
import board from '../plugin' import board from '../plugin'
export async function createCard ( export async function createCard (
@ -35,7 +35,7 @@ export async function createCard (
startDate: null, startDate: null,
dueDate: null, dueDate: null,
number, number,
rank: calcRank(lastOne, undefined), rank: makeRank(lastOne?.rank, undefined),
assignee: null, assignee: null,
identifier: `CARD-${number}`, identifier: `CARD-${number}`,
description: '', description: '',

View File

@ -20,7 +20,7 @@
import type { Customer, Funnel, Lead } from '@hcengineering/lead' import type { Customer, Funnel, Lead } from '@hcengineering/lead'
import { OK, Status } from '@hcengineering/platform' import { OK, Status } from '@hcengineering/platform'
import { Card, createQuery, getClient, InlineAttributeBar, SpaceSelector } from '@hcengineering/presentation' import { Card, createQuery, getClient, InlineAttributeBar, SpaceSelector } from '@hcengineering/presentation'
import task, { calcRank, getStates, TaskType } from '@hcengineering/task' import task, { getStates, makeRank, TaskType } from '@hcengineering/task'
import { TaskKindSelector, typeStore } from '@hcengineering/task-resources' import { TaskKindSelector, typeStore } from '@hcengineering/task-resources'
import { Button, createFocusManager, EditBox, FocusHandler, Label, Status as StatusControl } from '@hcengineering/ui' import { Button, createFocusManager, EditBox, FocusHandler, Label, Status as StatusControl } from '@hcengineering/ui'
import { statusStore } from '@hcengineering/view-resources' import { statusStore } from '@hcengineering/view-resources'
@ -92,7 +92,7 @@
identifier: `LEAD-${number}`, identifier: `LEAD-${number}`,
title, title,
kind, kind,
rank: calcRank(lastOne, undefined), rank: makeRank(lastOne?.rank, undefined),
assignee: null, assignee: null,
startDate: null, startDate: null,
dueDate: null, dueDate: null,

View File

@ -41,7 +41,7 @@
getClient getClient
} from '@hcengineering/presentation' } from '@hcengineering/presentation'
import type { Applicant, Candidate, Vacancy } from '@hcengineering/recruit' import type { Applicant, Candidate, Vacancy } from '@hcengineering/recruit'
import task, { TaskType, calcRank, getStates } from '@hcengineering/task' import task, { TaskType, getStates, makeRank } from '@hcengineering/task'
import ui, { import ui, {
Button, Button,
ColorPopup, ColorPopup,
@ -152,7 +152,7 @@
number, number,
identifier: `APP-${number}`, identifier: `APP-${number}`,
assignee: doc.assignee, assignee: doc.assignee,
rank: calcRank(lastOne, undefined), rank: makeRank(lastOne?.rank, undefined),
startDate: null, startDate: null,
dueDate: null, dueDate: null,
kind kind

View File

@ -20,7 +20,7 @@
import { Card, createQuery, getClient, InlineAttributeBar, MessageBox } from '@hcengineering/presentation' import { Card, createQuery, getClient, InlineAttributeBar, MessageBox } from '@hcengineering/presentation'
import { Vacancy as VacancyClass } from '@hcengineering/recruit' import { Vacancy as VacancyClass } from '@hcengineering/recruit'
import tags from '@hcengineering/tags' import tags from '@hcengineering/tags'
import task, { calcRank, ProjectType } from '@hcengineering/task' import task, { makeRank, ProjectType } from '@hcengineering/task'
import tracker, { Issue, IssueStatus, IssueTemplate, IssueTemplateData, Project } from '@hcengineering/tracker' import tracker, { Issue, IssueStatus, IssueTemplate, IssueTemplateData, Project } from '@hcengineering/tracker'
import { import {
Button, Button,
@ -113,7 +113,7 @@
true true
) )
const project = await client.findOne(tracker.class.Project, { _id: space }) const project = await client.findOne(tracker.class.Project, { _id: space })
const rank = calcRank(lastOne, undefined) const rank = makeRank(lastOne?.rank, undefined)
const taskType = await client.findOne(task.class.TaskType, { ofClass: tracker.class.Issue }) const taskType = await client.findOne(task.class.TaskType, { ofClass: tracker.class.Issue })
if (taskType === undefined) { if (taskType === undefined) {
return return

View File

@ -28,10 +28,11 @@ import {
import { type IntlString, type Resources } from '@hcengineering/platform' import { type IntlString, type Resources } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation' import { createQuery, getClient } from '@hcengineering/presentation'
import task, { import task, {
calcRank,
getStatusIndex, getStatusIndex,
makeRank,
type Project, type Project,
type ProjectType, type ProjectType,
type Rank,
type Task, type Task,
type TaskType type TaskType
} from '@hcengineering/task' } from '@hcengineering/task'
@ -238,8 +239,8 @@ async function statusSort (
} }
}) })
} else { } else {
const res = new Map<Ref<Status>, string>() const res = new Map<Ref<Status>, Rank>()
let prevRank: string | undefined let prevRank: Rank | undefined
const types = await client.findAll(task.class.ProjectType, {}) const types = await client.findAll(task.class.ProjectType, {})
for (const state of value) { for (const state of value) {
if (res.has(state)) continue if (res.has(state)) continue
@ -254,17 +255,14 @@ async function statusSort (
const st = statuses[index] const st = statuses[index]
const prev = index > 0 ? res.get(statuses[index - 1]) : prevRank const prev = index > 0 ? res.get(statuses[index - 1]) : prevRank
const next = index < statuses.length - 1 ? res.get(statuses[index + 1]) : undefined const next = index < statuses.length - 1 ? res.get(statuses[index + 1]) : undefined
const rank = calcRank( const rank = makeRank(prev, next)
prev !== undefined ? { rank: prev } : undefined,
next !== undefined ? { rank: next } : undefined
)
res.set(st, rank) res.set(st, rank)
prevRank = rank prevRank = rank
} }
} }
const result: Array<{ const result: Array<{
_id: Ref<Status> _id: Ref<Status>
rank: string rank: Rank
}> = [] }> = []
for (const [key, value] of res.entries()) { for (const [key, value] of res.entries()) {
result.push({ _id: key, rank: value }) result.push({ _id: key, rank: value })

View File

@ -37,9 +37,9 @@
"@hcengineering/core": "^0.6.28", "@hcengineering/core": "^0.6.28",
"@hcengineering/notification": "^0.6.16", "@hcengineering/notification": "^0.6.16",
"@hcengineering/platform": "^0.6.9", "@hcengineering/platform": "^0.6.9",
"@hcengineering/rank": "^0.6.0",
"@hcengineering/ui": "^0.6.11", "@hcengineering/ui": "^0.6.11",
"@hcengineering/view": "^0.6.9", "@hcengineering/view": "^0.6.9"
"lexorank": "~1.0.4"
}, },
"repository": "https://github.com/hcengineering/anticrm", "repository": "https://github.com/hcengineering/anticrm",
"publishConfig": { "publishConfig": {

View File

@ -29,14 +29,16 @@ import {
import { NotificationType } from '@hcengineering/notification' import { NotificationType } from '@hcengineering/notification'
import type { Asset, IntlString, Plugin, Resource } from '@hcengineering/platform' import type { Asset, IntlString, Plugin, Resource } from '@hcengineering/platform'
import { plugin } from '@hcengineering/platform' import { plugin } from '@hcengineering/platform'
import type { Rank } from '@hcengineering/rank'
import type { AnyComponent, ComponentExtensionId } from '@hcengineering/ui' import type { AnyComponent, ComponentExtensionId } from '@hcengineering/ui'
import { Action, IconProps, ViewletDescriptor } from '@hcengineering/view' import { Action, IconProps, ViewletDescriptor } from '@hcengineering/view'
/** export * from './utils'
* @public export type { Rank } from '@hcengineering/rank'
*/
/** @public */
export interface DocWithRank extends Doc { export interface DocWithRank extends Doc {
rank: string rank: Rank
} }
export interface Project extends Space { export interface Project extends Space {
@ -303,4 +305,3 @@ const task = plugin(taskId, {
}) })
export default task export default task
export * from './utils'

View File

@ -29,37 +29,18 @@ import core, {
type RefTo type RefTo
} from '@hcengineering/core' } from '@hcengineering/core'
import { PlatformError, getEmbeddedLabel, unknownStatus } from '@hcengineering/platform' import { PlatformError, getEmbeddedLabel, unknownStatus } from '@hcengineering/platform'
import { LexoDecimal, LexoNumeralSystem36, LexoRank } from 'lexorank'
import LexoRankBucket from 'lexorank/lib/lexoRank/lexoRankBucket'
import task, { Project, ProjectStatus, ProjectType, Task, TaskType } from '.' import task, { Project, ProjectStatus, ProjectType, Task, TaskType } from '.'
import { makeRank, type Rank } from '@hcengineering/rank'
export { genRanks, makeRank } from '@hcengineering/rank'
/** /**
* @public * @public
*
* TODO: Drop after everything migrates to {@link makeRank}
*/ */
export function genRanks (count: number): string[] { export const calcRank = (prev?: { rank: Rank }, next?: { rank: Rank }): string => {
const sys = new LexoNumeralSystem36() return makeRank(prev?.rank, next?.rank)
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)
const res: string[] = []
for (let i = 0; i < count; i++) {
cur = cur.add(gap)
res.push(new LexoRank(LexoRankBucket.BUCKET_0, cur).toString())
}
return res
}
/**
* @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()
if (a.equals(b)) {
return a.genNext().toString()
}
return a.between(b).toString()
} }
/** /**

View File

@ -44,7 +44,7 @@
getClient getClient
} from '@hcengineering/presentation' } from '@hcengineering/presentation'
import tags, { TagElement, TagReference } from '@hcengineering/tags' import tags, { TagElement, TagReference } from '@hcengineering/tags'
import { TaskType, calcRank } from '@hcengineering/task' import { TaskType, makeRank } from '@hcengineering/task'
import { TaskKindSelector } from '@hcengineering/task-resources' import { TaskKindSelector } from '@hcengineering/task-resources'
import { import {
Component as ComponentType, Component as ComponentType,
@ -434,7 +434,7 @@
number, number,
status: object.status, status: object.status,
priority: object.priority, priority: object.priority,
rank: calcRank(lastOne, undefined), rank: makeRank(lastOne?.rank, undefined),
comments: 0, comments: 0,
subIssues: 0, subIssues: 0,
dueDate: object.dueDate, dueDate: object.dueDate,

View File

@ -15,7 +15,7 @@
<script lang="ts"> <script lang="ts">
import core, { AttachedData, FindOptions, Ref, SortingOrder } from '@hcengineering/core' import core, { AttachedData, FindOptions, Ref, SortingOrder } from '@hcengineering/core'
import { ObjectPopup, getClient } from '@hcengineering/presentation' import { ObjectPopup, getClient } from '@hcengineering/presentation'
import { calcRank } from '@hcengineering/task' import { makeRank } from '@hcengineering/task'
import { Issue, IssueDraft } from '@hcengineering/tracker' import { Issue, IssueDraft } from '@hcengineering/tracker'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import tracker from '../plugin' import tracker from '../plugin'
@ -51,7 +51,7 @@
{ sort: { rank: SortingOrder.Descending } } { sort: { rank: SortingOrder.Descending } }
) )
rank = calcRank(lastAttachedIssue, undefined) rank = makeRank(lastAttachedIssue?.rank, undefined)
} }
await client.update(docValue, { await client.update(docValue, {

View File

@ -18,7 +18,7 @@
import core, { AttachedData, Doc, Ref, SortingOrder } from '@hcengineering/core' import core, { AttachedData, Doc, Ref, SortingOrder } from '@hcengineering/core'
import { DraftController, draftsStore, getClient } from '@hcengineering/presentation' import { DraftController, draftsStore, getClient } from '@hcengineering/presentation'
import tags from '@hcengineering/tags' import tags from '@hcengineering/tags'
import { calcRank } from '@hcengineering/task' import { makeRank } from '@hcengineering/task'
import { Component, Issue, IssueDraft, IssueParentInfo, Milestone, Project } from '@hcengineering/tracker' import { Component, Issue, IssueDraft, IssueParentInfo, Milestone, Project } from '@hcengineering/tracker'
import { Button, ExpandCollapse, Scroller } from '@hcengineering/ui' import { Button, ExpandCollapse, Scroller } from '@hcengineering/ui'
import { onDestroy } from 'svelte' import { onDestroy } from 'svelte'
@ -79,7 +79,7 @@
number, number,
status: subIssue.status ?? project.defaultIssueStatus, status: subIssue.status ?? project.defaultIssueStatus,
priority: subIssue.priority, priority: subIssue.priority,
rank: calcRank(lastOne, undefined), rank: makeRank(lastOne?.rank, undefined),
comments: 0, comments: 0,
subIssues: 0, subIssues: 0,
dueDate: null, dueDate: null,

View File

@ -37,7 +37,7 @@ import core, {
} from '@hcengineering/core' } from '@hcengineering/core'
import { type Asset, type IntlString } from '@hcengineering/platform' import { type Asset, type IntlString } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation' import { createQuery, getClient } from '@hcengineering/presentation'
import task, { calcRank, getStatusIndex, type ProjectType } from '@hcengineering/task' import task, { getStatusIndex, makeRank, type ProjectType } from '@hcengineering/task'
import { activeProjects as taskActiveProjects, taskTypeStore } from '@hcengineering/task-resources' import { activeProjects as taskActiveProjects, taskTypeStore } from '@hcengineering/task-resources'
import { import {
IssuePriority, IssuePriority,
@ -576,7 +576,7 @@ async function updateIssuesOnMove (
space, space,
{ {
...updates.get(attached._id as Ref<Issue>), ...updates.get(attached._id as Ref<Issue>),
rank: calcRank(lastOne, undefined), rank: makeRank(lastOne?.rank, undefined),
number: (incResult as any).object.sequence number: (incResult as any).object.sequence
}, },
updates updates
@ -622,7 +622,7 @@ export async function moveIssueToSpace (
space, space,
{ {
...updates.get(doc._id), ...updates.get(doc._id),
rank: calcRank(lastOne, undefined), rank: makeRank(lastOne?.rank, undefined),
number, number,
identifier: `${space.identifier}-${number}` identifier: `${space.identifier}-${number}`
}, },

View File

@ -19,7 +19,7 @@
import core, { Class, Client, Doc, Ref, SortingOrder, Space } from '@hcengineering/core' import core, { Class, Client, Doc, Ref, SortingOrder, Space } from '@hcengineering/core'
import { OK, Resource, Status, getResource, translate } from '@hcengineering/platform' import { OK, Resource, Status, getResource, translate } from '@hcengineering/platform'
import task, { Project, Task, calcRank } from '@hcengineering/task' import task, { Project, Task, makeRank } from '@hcengineering/task'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import view from '../plugin' import view from '../plugin'
import { moveToSpace } from '../utils' import { moveToSpace } from '../utils'
@ -51,7 +51,7 @@
if (needRank) { if (needRank) {
const lastOne = await client.findOne((doc as Task)._class, { space }, { sort: { rank: SortingOrder.Descending } }) const lastOne = await client.findOne((doc as Task)._class, { space }, { sort: { rank: SortingOrder.Descending } })
await moveToSpace(client, doc, space, { await moveToSpace(client, doc, space, {
rank: calcRank(lastOne, undefined) rank: makeRank(lastOne?.rank, undefined)
}) })
} else { } else {
await moveToSpace(client, doc, space) await moveToSpace(client, doc, space)

View File

@ -29,7 +29,7 @@
} from '@hcengineering/core' } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform' import { IntlString } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation' import { createQuery, getClient } from '@hcengineering/presentation'
import { DocWithRank, calcRank } from '@hcengineering/task' import { DocWithRank, makeRank } from '@hcengineering/task'
import { AnyComponent, AnySvelteComponent, ExpandCollapse, mouseAttractor } from '@hcengineering/ui' import { AnyComponent, AnySvelteComponent, ExpandCollapse, mouseAttractor } from '@hcengineering/ui'
import { AttributeModel, BuildModelKey, ViewOptionModel, ViewOptions, Viewlet } from '@hcengineering/view' import { AttributeModel, BuildModelKey, ViewOptionModel, ViewOptions, Viewlet } from '@hcengineering/view'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
@ -286,7 +286,7 @@
const prev = limited[dragItemIndex - 1] as DocWithRank const prev = limited[dragItemIndex - 1] as DocWithRank
const next = limited[dragItemIndex + 1] as DocWithRank const next = limited[dragItemIndex + 1] as DocWithRank
try { try {
const newRank = calcRank(prev, next) const newRank = makeRank(prev?.rank, next?.rank)
if ((dragItem.doc as DocWithRank)?.rank !== newRank) { if ((dragItem.doc as DocWithRank)?.rank !== newRank) {
;(update as any).rank = newRank ;(update as any).rank = newRank
} }

View File

@ -16,7 +16,7 @@
import { Class, Doc, DocumentQuery, FindOptions, FindResult, Ref, SortingOrder } from '@hcengineering/core' import { Class, Doc, DocumentQuery, FindOptions, FindResult, Ref, SortingOrder } from '@hcengineering/core'
import { Asset, getResource, IntlString } from '@hcengineering/platform' import { Asset, getResource, IntlString } from '@hcengineering/platform'
import presentation, { createQuery, getClient } from '@hcengineering/presentation' import presentation, { createQuery, getClient } from '@hcengineering/presentation'
import { calcRank, DocWithRank } from '@hcengineering/task' import { DocWithRank, makeRank } from '@hcengineering/task'
import { Button, Component, Icon, IconAdd, IconSize, Label, Loading } from '@hcengineering/ui' import { Button, Component, Icon, IconAdd, IconSize, Label, Loading } from '@hcengineering/ui'
import view, { ObjectFactory } from '@hcengineering/view' import view, { ObjectFactory } from '@hcengineering/view'
import { flip } from 'svelte/animate' import { flip } from 'svelte/animate'
@ -123,7 +123,8 @@
] ]
const sortingOrder = queryOptions?.sort?.rank ?? SORTING_ORDER const sortingOrder = queryOptions?.sort?.rank ?? SORTING_ORDER
const rank = sortingOrder === SortingOrder.Ascending ? calcRank(prev, next) : calcRank(next, prev) const rank =
sortingOrder === SortingOrder.Ascending ? makeRank(prev?.rank, next?.rank) : makeRank(next?.rank, prev?.rank)
try { try {
areItemsSorting = true areItemsSorting = true

View File

@ -586,6 +586,11 @@
"projectFolder": "packages/query", "projectFolder": "packages/query",
"shouldPublish": true "shouldPublish": true
}, },
{
"packageName": "@hcengineering/rank",
"projectFolder": "packages/rank",
"shouldPublish": true
},
{ {
"packageName": "@hcengineering/view", "packageName": "@hcengineering/view",
"projectFolder": "plugins/view", "projectFolder": "plugins/view",