70 lines
3.5 KiB
TypeScript
70 lines
3.5 KiB
TypeScript
// Общая механика «шагов, которые могут идти параллельно» — 1:1 повторяет
|
||
// request-portal/src/webparts/requestPortal/shared/approval/useMatrixEditor.ts
|
||
// (blockRanges/swapBlocks/withOrders/markParallel), только обобщена под
|
||
// generic-тип с полями order/parallelWithPrev, чтобы работать и с
|
||
// ManagedApprovalRow здесь, и не тащить зависимость на request-portal.
|
||
//
|
||
// Параллельность кодируется ЧЕРЕЗ РАВНЫЙ order у соседних строк — отдельного
|
||
// флага в SharePoint нет (см. FIELDS в requestManageService.ts: там только
|
||
// StepOrder). parallelWithPrev — это производное поле, вычисляемое на клиенте
|
||
// из совпадения order с предыдущей строкой (см. markParallel), а не то, что
|
||
// хранится в списке.
|
||
export interface IOrderedItem {
|
||
order: number;
|
||
parallelWithPrev?: boolean;
|
||
}
|
||
|
||
export interface IBlockRange {
|
||
start: number;
|
||
end: number;
|
||
}
|
||
|
||
/** Разбивает строки (уже отсортированные по order) на шаги — блоки с общим order. */
|
||
export function blockRanges<T extends IOrderedItem>(rows: T[]): IBlockRange[] {
|
||
const ranges: IBlockRange[] = [];
|
||
rows.forEach((r, i) => {
|
||
if (i === 0 || !r.parallelWithPrev) ranges.push({ start: i, end: i });
|
||
else ranges[ranges.length - 1].end = i;
|
||
});
|
||
return ranges;
|
||
}
|
||
|
||
export function blockIndexOf(ranges: IBlockRange[], row: number): number {
|
||
for (let i = 0; i < ranges.length; i += 1) {
|
||
if (row >= ranges[i].start && row <= ranges[i].end) return i;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
/** Меняет местами два блока (диапазона индексов), не трогая порядок строк внутри них. */
|
||
export function swapBlocks<T>(rows: T[], a: IBlockRange, b: IBlockRange): T[] {
|
||
return [
|
||
...rows.slice(0, a.start),
|
||
...rows.slice(b.start, b.end + 1),
|
||
...rows.slice(a.start, a.end + 1),
|
||
...rows.slice(b.end + 1)
|
||
];
|
||
}
|
||
|
||
/**
|
||
* Пересчитывает order начиная с floor+1: у параллельных строк (parallelWithPrev)
|
||
* order общий с предыдущей, у остальных — на единицу больше. Вызывается после
|
||
* КАЖДОГО перемещения/переключения параллельности, иначе order разъедется с тем,
|
||
* что показано на экране. floor — order последней ЗАБЛОКИРОВАННОЙ (уже решённой)
|
||
* строки фазы; строки после неё не могут получить order <= floor — это и есть
|
||
* гарантия «нельзя поставить согласующего выше того, кто уже одобрил».
|
||
*/
|
||
export function withOrdersFrom<T extends IOrderedItem>(rows: T[], floor: number): T[] {
|
||
let order = floor;
|
||
return rows.map((r, i) => {
|
||
const parallel = i > 0 && !!r.parallelWithPrev;
|
||
if (!parallel) order += 1;
|
||
return { ...r, order, parallelWithPrev: parallel };
|
||
});
|
||
}
|
||
|
||
/** Заполняет parallelWithPrev по факту совпадения order с предыдущей строкой (строки уже отсортированы по order). */
|
||
export function markParallel<T extends IOrderedItem>(rows: T[]): T[] {
|
||
return rows.map((r, i) => ({ ...r, parallelWithPrev: i > 0 && r.order === rows[i - 1].order }));
|
||
}
|