Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ const { readFileSync, existsSync, mkdirSync, createWriteStream } = require('fs')
const { spawn } = require('child_process')

function parseSvelteCheckLog(logContent) {
const lines = logContent.split('\n')
// Human output includes ANSI colors, even when redirected to a log file.
// Strip them before looking for "Error:" so failed checks report the errors.
const lines = logContent.replace(/\x1b\[[0-9;]*m/g, '').split('\n')
const errors = []
let currentError = null

Expand Down
26 changes: 26 additions & 0 deletions packages/gantt/src/__tests__/time-scale.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,29 @@ describe('time-scale — secondary label (year/month supra row)', () => {
void DAY
})
})

describe('time-scale locale', () => {
const origin = Date.UTC(2026, 0, 1)

it.each(['en', 'ru', 'de', 'ja'])('formats day and month labels using %s', (locale) => {
const expected = new Intl.DateTimeFormat(locale, { month: 'short', timeZone: 'UTC' }).format(origin)
expect(createTimeScale('day', origin, undefined, { locale }).ticks([origin, origin])[0].secondaryLabel).toBe(
expected
)
expect(createTimeScale('month', origin, undefined, { locale }).ticks([origin, origin])[0].label).toBe(expected)
})

it('accepts translated week and quarter labels without changing geometry', () => {
const labels = {
locale: 'ru',
weekLabel: (week: number) => `Нед. ${week}`,
quarterLabel: (quarter: number) => `Кв. ${quarter}`
}
const week = createTimeScale('week', origin, 14, labels)
const first = week.ticks([origin, origin + 7 * DAY_MS])[0]
expect(first.label).toBe('Нед. 2')
expect(first.date).toBe(Date.UTC(2026, 0, 5))
expect(week.toX(first.date)).toBe(56)
expect(createTimeScale('quarter', origin, undefined, labels).ticks([origin, origin])[0].label).toBe('Кв. 1')
})
})
31 changes: 23 additions & 8 deletions packages/gantt/src/__tests__/toolbar-overflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const ALL: ToolbarTier[] = [...TOOLBAR_TIERS]
// One easy-to-reason-about width per tier.
const W: Record<ToolbarTier, number> = {
group: 100,
color: 80,
nav: 200,
date: 150,
zoom: 120,
Expand All @@ -39,24 +40,31 @@ describe('computeToolbarOverflow', () => {
})

it('keeps everything inline when it fits exactly', () => {
// 100+200+150+120+60+90 = 720, plus five gaps = 740
const r = computeToolbarOverflow(ALL, W, 740, GAP, MORE)
// 100+80+200+150+120+60+90 = 800, plus six gaps = 824
const r = computeToolbarOverflow(ALL, W, 824, GAP, MORE)
expect(r.hidden).toEqual([])
expect(r.visible).toEqual(ALL)
})

it('collapses the least important tier first', () => {
const r = computeToolbarOverflow(ALL, W, 739, GAP, MORE)
const r = computeToolbarOverflow(ALL, W, 823, GAP, MORE)
expect(r.hidden).toEqual(['savedview'])
expect(r.visible).toEqual(['group', 'nav', 'date', 'zoom', 'undo'])
expect(r.visible).toEqual(['group', 'color', 'nav', 'date', 'zoom', 'undo'])
})

it('collapses further tiers in TOOLBAR_COLLAPSE_ORDER as space shrinks', () => {
expect(computeToolbarOverflow(ALL, W, 600, GAP, MORE).hidden).toEqual(['date', 'savedview'])
expect(computeToolbarOverflow(ALL, W, 450, GAP, MORE).hidden).toEqual(['group', 'date', 'savedview'])
expect(computeToolbarOverflow(ALL, W, 400, GAP, MORE).hidden).toEqual(['group', 'date', 'undo', 'savedview'])
expect(computeToolbarOverflow(ALL, W, 600, GAP, MORE).hidden).toEqual(['color', 'date', 'savedview'])
expect(computeToolbarOverflow(ALL, W, 450, GAP, MORE).hidden).toEqual(['group', 'color', 'date', 'savedview'])
expect(computeToolbarOverflow(ALL, W, 400, GAP, MORE).hidden).toEqual([
'group',
'color',
'date',
'undo',
'savedview'
])
expect(computeToolbarOverflow(ALL, W, 300, GAP, MORE).hidden).toEqual([
'group',
'color',
'date',
'zoom',
'undo',
Expand All @@ -71,7 +79,7 @@ describe('computeToolbarOverflow', () => {
})

it('never drops a tier — visible + hidden is always the input set', () => {
for (const available of [-1, 0, 30, 120, 300, 500, 740, 2000]) {
for (const available of [-1, 0, 30, 120, 300, 500, 824, 2000]) {
const r = computeToolbarOverflow(ALL, W, available, GAP, MORE)
expect([...r.visible, ...r.hidden].sort()).toEqual([...ALL].sort())
}
Expand All @@ -83,6 +91,13 @@ describe('computeToolbarOverflow', () => {
expect(r.hidden).toEqual(ALL.filter((t) => r.hidden.includes(t)))
})

it('keeps grouping inline when only one of the two view controls fits', () => {
const present: ToolbarTier[] = ['group', 'color', 'nav', 'zoom', 'undo']
const r = computeToolbarOverflow(present, W, 510, GAP, 0)
expect(r.visible).toEqual(['group', 'nav', 'zoom', 'undo'])
expect(r.hidden).toEqual(['color'])
})

it('ignores tiers that are not present', () => {
const present: ToolbarTier[] = ['nav', 'zoom']
// nav(200) + zoom(120) + gap = 324 > 240; nav alone plus the "…" trigger
Expand Down
25 changes: 20 additions & 5 deletions packages/gantt/src/time-scale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,22 @@ export interface TimeScale {
ticks: (range: [number, number]) => Tick[]
}

export function createTimeScale (zoom: ZoomLevel, origin: number, pxPerDayOverride?: number): TimeScale {
/** Presentation settings supplied by the host application's active language. */
export interface TimeScaleLocale {
locale?: string
weekLabel?: (week: number) => string
quarterLabel?: (quarter: number) => string
}

export function createTimeScale (
zoom: ZoomLevel,
origin: number,
pxPerDayOverride?: number,
labels: TimeScaleLocale = {}
): TimeScale {
const locale = labels.locale ?? 'en'
const weekLabel = labels.weekLabel ?? ((week: number) => `W${week}`)
const quarterLabel = labels.quarterLabel ?? ((quarter: number) => `Q${quarter}`)
const pxPerDay = pxPerDayOverride ?? PX_PER_DAY[zoom]
const originSnapped = snapToUtcMidnight(origin)

Expand All @@ -71,7 +86,7 @@ export function createTimeScale (zoom: ZoomLevel, origin: number, pxPerDayOverri
const m = d.getUTCMonth()
let secondary: string | undefined
if (first || m !== lastMonth) {
secondary = d.toLocaleString(undefined, { month: 'short', timeZone: 'UTC' })
secondary = d.toLocaleString(locale, { month: 'short', timeZone: 'UTC' })
lastMonth = m
first = false
}
Expand Down Expand Up @@ -106,7 +121,7 @@ export function createTimeScale (zoom: ZoomLevel, origin: number, pxPerDayOverri
}
result.push({
date: cursor,
label: `W${isoWeekNumber(c)}`,
label: weekLabel(isoWeekNumber(c)),
level: isFirstWeekOfMonth ? 'major' : 'minor',
secondaryLabel: secondary
})
Expand Down Expand Up @@ -135,7 +150,7 @@ export function createTimeScale (zoom: ZoomLevel, origin: number, pxPerDayOverri
}
result.push({
date: cursor,
label: c.toLocaleString(undefined, { month: 'short', timeZone: 'UTC' }),
label: c.toLocaleString(locale, { month: 'short', timeZone: 'UTC' }),
level: c.getUTCMonth() === 0 ? 'major' : 'minor',
secondaryLabel: secondary
})
Expand Down Expand Up @@ -170,7 +185,7 @@ export function createTimeScale (zoom: ZoomLevel, origin: number, pxPerDayOverri
}
result.push({
date: cursor,
label: `Q${qNum}`,
label: quarterLabel(qNum),
level: qNum === 1 ? 'major' : 'minor',
secondaryLabel: secondary
})
Expand Down
15 changes: 12 additions & 3 deletions packages/gantt/src/toolbar-overflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
* that collapses as a whole; splitting e.g. the date-navigation into single
* buttons would leave a half-usable control group behind.
*/
export const TOOLBAR_TIERS = ['group', 'nav', 'date', 'zoom', 'undo', 'savedview'] as const
export const TOOLBAR_TIERS = ['group', 'color', 'nav', 'date', 'zoom', 'undo', 'savedview'] as const
export type ToolbarTier = (typeof TOOLBAR_TIERS)[number]

/**
Expand All @@ -37,12 +37,21 @@ export type ToolbarTier = (typeof TOOLBAR_TIERS)[number]
* - `savedview` is a passive "modified" indicator plus its update button; the
* same action is reachable from the More-actions menu.
* - `date` (jump-to-date) duplicates what panning and the nav buttons do.
* - `group` (group-by / colour-by) is view configuration, not navigation.
* - `color` is cosmetic; keep `group` visible longer because it changes the
* layout of the issue list and chart.
* - `undo` matters while editing, but the keyboard shortcuts stay live.
* - `zoom` and `nav` are the two controls a Gantt is unusable without, so
* they are the last to go.
*/
export const TOOLBAR_COLLAPSE_ORDER: readonly ToolbarTier[] = ['savedview', 'date', 'group', 'undo', 'zoom', 'nav']
export const TOOLBAR_COLLAPSE_ORDER: readonly ToolbarTier[] = [
'savedview',
'date',
'color',
'group',
'undo',
'zoom',
'nav'
]

/**
* Width assumed for a tier that has never been measured. Deliberately
Expand Down
6 changes: 2 additions & 4 deletions plugins/tracker-assets/assets/icons.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 13 additions & 1 deletion plugins/tracker-assets/lang/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,19 @@
"SearchEmptyTitle": "Pro „{query}\" nebyly nalezeny žádné úkoly",
"SearchEmptyActiveFilters": "Aktivní filtry: {filters}",
"SearchEmptyClearFilters": "Hledat bez filtrů",
"SearchEmptyAllProjects": "Hledat ve všech projektech"
"SearchEmptyAllProjects": "Hledat ve všech projektech",
"GanttWeekLabel": "T{week}",
"GanttQuarterLabel": "Q{quarter}",
"GanttWithoutDates": "{count} bez dat",
"GanttDates": "Data",
"GanttZoomLabel": "Přiblížení",
"GanttSetDates": "Nastavit data",
"GanttNoScheduledTasks": "Žádné naplánované úkoly",
"GanttTodayAlreadyVisible": "Dnešek je již zobrazen",
"GanttVisibleRange": "Rozsah",
"GanttNotScheduled": "Nenaplánováno",
"GanttMissingStartDates": "{count} úkolů nemá datum zahájení.",
"GanttSetDatesHint": "Nastavte u úkolu datum zahájení a termín dokončení, aby se zobrazil v časové ose."
},
"status": {}
}
13 changes: 12 additions & 1 deletion plugins/tracker-assets/lang/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,8 @@
"GanttJumpToDate": "Zu Datum springen",
"GanttZoomDay": "Tag",
"GanttZoomWeek": "Woche",
"GanttWeekLabel": "KW{week}",
"GanttQuarterLabel": "Q{quarter}",
"GanttZoomMonth": "Monat",
"GanttZoomQuarter": "Quartal",
"GanttZoomCustom": "Benutzerdefiniert",
Expand Down Expand Up @@ -563,7 +565,16 @@
"SearchEmptyTitle": "Keine Treffer für \"{query}\"",
"SearchEmptyActiveFilters": "Aktive Filter: {filters}",
"SearchEmptyClearFilters": "Suche ohne Filter",
"SearchEmptyAllProjects": "In allen Projekten suchen"
"SearchEmptyAllProjects": "In allen Projekten suchen",
"GanttWithoutDates": "{count} ohne Daten",
"GanttDates": "Termine",
"GanttSetDates": "Daten festlegen",
"GanttNoScheduledTasks": "Keine geplanten Aufgaben",
"GanttTodayAlreadyVisible": "Heute ist bereits sichtbar",
"GanttVisibleRange": "Zeitraum",
"GanttNotScheduled": "Nicht geplant",
"GanttMissingStartDates": "Für {count} Aufgaben ist kein Startdatum festgelegt.",
"GanttSetDatesHint": "Legen Sie für ein Issue Start- und Fälligkeitsdatum fest, damit es auf der Zeitachse erscheint."
},
"status": {}
}
13 changes: 12 additions & 1 deletion plugins/tracker-assets/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,8 @@
"GanttJumpToDate": "Jump to date",
"GanttZoomDay": "Day",
"GanttZoomWeek": "Week",
"GanttWeekLabel": "W{week}",
"GanttQuarterLabel": "Q{quarter}",
"GanttZoomMonth": "Month",
"GanttZoomQuarter": "Quarter",
"GanttZoomCustom": "Custom",
Expand Down Expand Up @@ -563,7 +565,16 @@
"SearchEmptyTitle": "No issues found for \"{query}\"",
"SearchEmptyActiveFilters": "Active filters: {filters}",
"SearchEmptyClearFilters": "Search without filters",
"SearchEmptyAllProjects": "Search in all projects"
"SearchEmptyAllProjects": "Search in all projects",
"GanttWithoutDates": "{count} without dates",
"GanttDates": "Dates",
"GanttSetDates": "Set dates",
"GanttNoScheduledTasks": "No scheduled tasks",
"GanttTodayAlreadyVisible": "Today is already in view",
"GanttVisibleRange": "Range",
"GanttNotScheduled": "Not scheduled",
"GanttMissingStartDates": "{count} issues have no start date.",
"GanttSetDatesHint": "Set start and due dates in an issue to show its timeline."
},
"status": {}
}
14 changes: 13 additions & 1 deletion plugins/tracker-assets/lang/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,19 @@
"SearchEmptyTitle": "No se encontraron tareas para \"{query}\"",
"SearchEmptyActiveFilters": "Filtros activos: {filters}",
"SearchEmptyClearFilters": "Buscar sin filtros",
"SearchEmptyAllProjects": "Buscar en todos los proyectos"
"SearchEmptyAllProjects": "Buscar en todos los proyectos",
"GanttWeekLabel": "S{week}",
"GanttQuarterLabel": "T{quarter}",
"GanttWithoutDates": "{count} sin fechas",
"GanttDates": "Fechas",
"GanttZoomLabel": "Zoom",
"GanttSetDates": "Establecer fechas",
"GanttNoScheduledTasks": "No hay tareas programadas",
"GanttTodayAlreadyVisible": "Hoy ya está visible",
"GanttVisibleRange": "Intervalo",
"GanttNotScheduled": "Sin programar",
"GanttMissingStartDates": "{count} tareas no tienen fecha de inicio.",
"GanttSetDatesHint": "Establece las fechas de inicio y vencimiento de una tarea para mostrarla en la línea de tiempo."
},
"status": {}
}
14 changes: 13 additions & 1 deletion plugins/tracker-assets/lang/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,19 @@
"SearchEmptyTitle": "Aucun problème trouvé pour \"{query}\"",
"SearchEmptyActiveFilters": "Filtres actifs : {filters}",
"SearchEmptyClearFilters": "Rechercher sans filtres",
"SearchEmptyAllProjects": "Rechercher dans tous les projets"
"SearchEmptyAllProjects": "Rechercher dans tous les projets",
"GanttWeekLabel": "S{week}",
"GanttQuarterLabel": "T{quarter}",
"GanttWithoutDates": "{count} sans dates",
"GanttDates": "Dates",
"GanttZoomLabel": "Zoom",
"GanttSetDates": "Définir les dates",
"GanttNoScheduledTasks": "Aucune tâche planifiée",
"GanttTodayAlreadyVisible": "Aujourd'hui est déjà visible",
"GanttVisibleRange": "Période",
"GanttNotScheduled": "Non planifié",
"GanttMissingStartDates": "{count} tâches n'ont pas de date de début.",
"GanttSetDatesHint": "Définissez les dates de début et d'échéance d'une tâche pour l'afficher sur la chronologie."
},
"status": {}
}
14 changes: 13 additions & 1 deletion plugins/tracker-assets/lang/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,19 @@
"SearchEmptyTitle": "Nessuna issue trovata per \"{query}\"",
"SearchEmptyActiveFilters": "Filtri attivi: {filters}",
"SearchEmptyClearFilters": "Cerca senza filtri",
"SearchEmptyAllProjects": "Cerca in tutti i progetti"
"SearchEmptyAllProjects": "Cerca in tutti i progetti",
"GanttWeekLabel": "S{week}",
"GanttQuarterLabel": "T{quarter}",
"GanttWithoutDates": "{count} senza date",
"GanttDates": "Date",
"GanttZoomLabel": "Zoom",
"GanttSetDates": "Imposta le date",
"GanttNoScheduledTasks": "Nessuna attività pianificata",
"GanttTodayAlreadyVisible": "Oggi è già visibile",
"GanttVisibleRange": "Intervallo",
"GanttNotScheduled": "Non pianificato",
"GanttMissingStartDates": "{count} attività non hanno una data di inizio.",
"GanttSetDatesHint": "Imposta le date di inizio e scadenza di un'attività per mostrarla nella cronologia."
},
"status": {}
}
14 changes: 13 additions & 1 deletion plugins/tracker-assets/lang/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,19 @@
"SearchEmptyTitle": "\"{query}\" に一致するイシューが見つかりません",
"SearchEmptyActiveFilters": "アクティブなフィルター:{filters}",
"SearchEmptyClearFilters": "フィルターなしで検索",
"SearchEmptyAllProjects": "すべてのプロジェクトを検索"
"SearchEmptyAllProjects": "すべてのプロジェクトを検索",
"GanttWeekLabel": "第{week}週",
"GanttQuarterLabel": "第{quarter}四半期",
"GanttWithoutDates": "日付なし: {count}",
"GanttDates": "日付",
"GanttZoomLabel": "ズーム",
"GanttSetDates": "日付を設定",
"GanttNoScheduledTasks": "予定されたタスクはありません",
"GanttTodayAlreadyVisible": "今日の日付はすでに表示されています",
"GanttVisibleRange": "表示範囲",
"GanttNotScheduled": "未予定",
"GanttMissingStartDates": "{count} 件の課題に開始日が設定されていません。",
"GanttSetDatesHint": "課題に開始日と期限を設定すると、タイムラインに表示されます。"
},
"status": {}
}
14 changes: 13 additions & 1 deletion plugins/tracker-assets/lang/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,19 @@
"SearchEmptyTitle": "\"{query}\"에 대한 이슈를 찾을 수 없습니다",
"SearchEmptyActiveFilters": "활성 필터: {filters}",
"SearchEmptyClearFilters": "필터 없이 검색",
"SearchEmptyAllProjects": "모든 프로젝트에서 검색"
"SearchEmptyAllProjects": "모든 프로젝트에서 검색",
"GanttWeekLabel": "{week}주",
"GanttQuarterLabel": "{quarter}분기",
"GanttWithoutDates": "날짜 없음: {count}",
"GanttDates": "날짜",
"GanttZoomLabel": "확대/축소",
"GanttSetDates": "날짜 설정",
"GanttNoScheduledTasks": "예정된 작업이 없습니다",
"GanttTodayAlreadyVisible": "오늘이 이미 표시되어 있습니다",
"GanttVisibleRange": "표시 범위",
"GanttNotScheduled": "일정 없음",
"GanttMissingStartDates": "{count}개 이슈에 시작일이 없습니다.",
"GanttSetDatesHint": "이슈의 시작일과 마감일을 설정하면 타임라인에 표시됩니다."
},
"status": {}
}
Loading
Loading