Skip to content

Задачи

Задача описывает временной интервал на timeline. Runtime использует ее id для updates и selection, groupId для размещения в строке группы, а startTime и endTime для расчета положения на временной шкале.

Пользовательский тип задачи всегда должен расширять TimelineTaskInput. Стандартные поля нужны для layout, selection, focus, drag/edit и API-операций. Дополнительные поля остаются вашими доменными данными: их можно использовать в task profiles, tooltip, filters, events и частичных updates.

ts
import type { TimelineTaskInput } from '@engine2d/timeline-chart'

interface Task extends TimelineTaskInput {
  title: string
  status: 'active' | 'done' | 'risk'
}

const start = new Date('2026-05-20T09:00:00+03:00').getTime()
const end = start + 2 * 60 * 60 * 1000

const task: Task = {
  id: 'task-1',
  groupId: 'group-1',
  title: 'Задача 1',
  status: 'active',
  startTime: start,
  endTime: end,
}

Стандартные поля

ПолеТипОбязательноеОписание
idstringдаСтабильный идентификатор задачи. Используется в update, remove, selectTasks, focusOnTask.
groupIdstring | nullдаId группы. null означает, что задача находится в ungrouped panel.
startTimenumberдаНачало интервала в timestamp milliseconds.
endTimenumberдаКонец интервала в timestamp milliseconds.
type'range' | 'background' | 'task-group'нетТип runtime-задачи. Для обычных задач можно не указывать: используется range.
visiblebooleanнетФлаг видимости, который удобно использовать в options.tasks.filter.
editableboolean или objectнетРазрешает или ограничивает drag/edit для конкретной задачи.
taskGroupKeystring | nullнетКлюч агрегации для taskGrouping.
customunknownнетПоле для произвольных данных, если их удобнее хранить отдельно от верхнего уровня задачи.

Пользовательские поля

Timeline сохраняет дополнительные поля задачи и передает их в templates, tooltip, events и API callbacks. Например, status можно использовать для выбора task profile, а progress вывести внутри кастомной карточки.

Исполняемый примерTimeline: базовый примерГруппы, задачи и временная шкала.
import { Nova, NovaNode, type NovaSchema } from '@endge/nova'

// Документационный сценарий: timeline-chart-basic
class ExampleNode extends NovaNode<Record<string, Event>> {
  render(): void {
    const schema: NovaSchema = [
      {
        type: 'rect',
        x: 24,
        y: 24,
        width: this.width - 48,
        height: this.height - 48,
        styles: { background: '#0d2745' },
      },
      {
        type: 'text',
        x: 44,
        y: 44,
        width: this.width - 88,
        height: 32,
        text: 'Timeline: базовый пример',
        styles: { color: '#e8f5ff' },
      },
    ]

    this.renderer.schema(schema)
    this.setRenderBoundsFromSchema(schema)
  }
}

export function mountExample(canvas: HTMLCanvasElement) {
  const app = Nova.createApp({
    target: canvas,
    size: { width: canvas.clientWidth, height: canvas.clientHeight, maxDpr: 2 },
    scheduler: { loop: false },
  })
  const surface = app.surface({ id: 'dirty-culling' })
  surface.createNode(ExampleNode)

  return () => app.destroy()
}

Основная область задач

Основная область timeline содержит range-задачи, background-задачи, selection overlay и drag shadows. Координаты задачи рассчитываются runtime по groupId, startTime, endTime, текущему timeRange и vertical scroll.

Размер task body задается в options.tasks. Эти настройки влияют на layout строк, но не заменяют кастомную отрисовку: task profile получает уже готовые x, y, width, height.

ts
const options = {
  tasks: {
    height: 34,
    rowGap: 6,
    filter: (task: Task) => task.visible !== false,
  },
}

Если задачи пересекаются по времени внутри одной группы, runtime учитывает group.allowOverlap. Для явного запрета пересечений задавайте allowOverlap: false на группе, а не на задаче.

Добавление, перенос и удаление

ts
import type { TimelineChartRef, TimelineGroupInput, TimelineTaskInput } from '@engine2d/timeline-chart'

interface Group extends TimelineGroupInput {
  title: string
}

interface Task extends TimelineTaskInput {
  title: string
  status: 'active' | 'done' | 'risk'
}

function addTask(timeline: TimelineChartRef<Group, Task>, start: number): void {
  timeline.add({
    tasks: [{
      id: 'task-2',
      groupId: 'group-1',
      title: 'Новая задача',
      status: 'active',
      startTime: start,
      endTime: start + 60 * 60 * 1000,
    }],
  })
}

function moveTask(timeline: TimelineChartRef<Group, Task>, start: number): void {
  timeline.update({
    tasks: [{
      id: 'task-1',
      groupId: 'group-2',
      startTime: start + 2 * 60 * 60 * 1000,
      endTime: start + 3 * 60 * 60 * 1000,
    }],
  })
}

function completeTask(timeline: TimelineChartRef<Group, Task>): void {
  timeline.update({
    tasks: [{ id: 'task-1', status: 'done', title: 'Готово' }],
  })
}

function removeTask(timeline: TimelineChartRef<Group, Task>): void {
  timeline.remove({
    tasks: ['task-2'],
  })
}

Editable

Флаг editable можно задать boolean или объектом по осям редактирования: startTime, endTime, group, move.

ts
const editableTask: Task = {
  id: 'task-editable',
  groupId: 'group-1',
  title: 'Редактируемая задача',
  status: 'active',
  startTime: start,
  endTime: start + 2 * 60 * 60 * 1000,
  editable: {
    startTime: true,
    endTime: true,
    group: true,
    move: true,
  },
}

:::callout title="Стабильные id" task.id должен оставаться тем же для одной и той же бизнес-задачи. По нему работают update, remove, selectTasks, focusOnTask и optimistic updates. Не используйте индекс массива и не заменяйте id при каждом refetch. :::