From 5fc255cb36916d2e45eb387e7d53c65149d957b1 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 12 Aug 2025 16:33:50 +0200 Subject: [PATCH] feat(gantt): rebuild the gantt chart (#1001) --- .../e2e/project/project-view-gantt.spec.ts | 53 +- frontend/package.json | 1 - frontend/pnpm-lock.yaml | 50 -- frontend/src/components/gantt/GanttChart.vue | 507 ++++++++++++++++++ .../src/components/gantt/GanttChartBody.vue | 41 ++ frontend/src/components/gantt/GanttRow.vue | 40 ++ .../src/components/gantt/GanttRowBars.vue | 308 +++++++++++ .../components/gantt/GanttTimelineHeader.vue | 157 ++++++ .../gantt/GanttVerticalGridLines.vue | 45 ++ .../gantt/primitives/GanttBarPrimitive.vue | 81 +++ .../gantt/primitives/GanttChartPrimitive.vue | 81 +++ .../gantt/primitives/GanttRowPrimitive.vue | 43 ++ .../misc/keyboard-shortcuts/shortcuts.ts | 30 ++ .../components/project/views/ProjectGantt.vue | 3 +- frontend/src/components/tasks/GanttChart.vue | 288 ---------- frontend/src/composables/useGanttBar.ts | 108 ++++ frontend/src/helpers/color/getTextColor.ts | 11 + frontend/src/i18n/lang/en.json | 25 +- frontend/src/models/label.ts | 7 +- 19 files changed, 1520 insertions(+), 359 deletions(-) create mode 100644 frontend/src/components/gantt/GanttChart.vue create mode 100644 frontend/src/components/gantt/GanttChartBody.vue create mode 100644 frontend/src/components/gantt/GanttRow.vue create mode 100644 frontend/src/components/gantt/GanttRowBars.vue create mode 100644 frontend/src/components/gantt/GanttTimelineHeader.vue create mode 100644 frontend/src/components/gantt/GanttVerticalGridLines.vue create mode 100644 frontend/src/components/gantt/primitives/GanttBarPrimitive.vue create mode 100644 frontend/src/components/gantt/primitives/GanttChartPrimitive.vue create mode 100644 frontend/src/components/gantt/primitives/GanttRowPrimitive.vue delete mode 100644 frontend/src/components/tasks/GanttChart.vue create mode 100644 frontend/src/composables/useGanttBar.ts create mode 100644 frontend/src/helpers/color/getTextColor.ts diff --git a/frontend/cypress/e2e/project/project-view-gantt.spec.ts b/frontend/cypress/e2e/project/project-view-gantt.spec.ts index f9b815dcb..af0aad667 100644 --- a/frontend/cypress/e2e/project/project-view-gantt.spec.ts +++ b/frontend/cypress/e2e/project/project-view-gantt.spec.ts @@ -13,7 +13,7 @@ describe('Project View Gantt', () => { const tasks = TaskFactory.create(1) cy.visit('/projects/1/2') - cy.get('.g-gantt-rows-container') + cy.get('.gantt-rows') .should('not.contain', tasks[0].title) }) @@ -27,9 +27,9 @@ describe('Project View Gantt', () => { cy.visit('/projects/1/2') - cy.get('.g-timeunits-container') - .should('contain', dayjs(now).format('MMMM')) - .should('contain', dayjs(nextMonth).format('MMMM')) + cy.get('.gantt-timeline-months') + .should('contain', dayjs(now).format('MMMM YYYY')) + .should('contain', dayjs(nextMonth).format('MMMM YYYY')) }) it('Shows tasks with dates', () => { @@ -40,7 +40,7 @@ describe('Project View Gantt', () => { }) cy.visit('/projects/1/2') - cy.get('.g-gantt-rows-container') + cy.get('.gantt-rows') .should('not.be.empty') .should('contain', tasks[0].title) }) @@ -56,7 +56,7 @@ describe('Project View Gantt', () => { .contains('Show tasks without dates') .click() - cy.get('.g-gantt-rows-container') + cy.get('.gantt-rows') .should('not.be.empty') .should('contain', tasks[0].title) }) @@ -71,11 +71,40 @@ describe('Project View Gantt', () => { }) cy.visit('/projects/1/2') - cy.get('.g-gantt-rows-container .g-gantt-row .g-gantt-row-bars-container div .g-gantt-bar') + cy.get('.gantt-rows .gantt-row-bars .gantt-bar') .first() - .trigger('mousedown', {which: 1}) - .trigger('mousemove', {clientX: 500, clientY: 0}) - .trigger('mouseup', {force: true}) + .then($bar => { + // Get the current position of the bar + const rect = $bar[0].getBoundingClientRect() + const startX = rect.left + rect.width / 2 + const startY = rect.top + rect.height / 2 + + // Trigger pointer events with proper coordinates and delays + cy.wrap($bar) + .trigger('pointerdown', { + clientX: startX, + clientY: startY, + pointerId: 1, + which: 1 + }) + .wait(100) // Wait to ensure double-click detection doesn't interfere + .trigger('pointermove', { + clientX: startX + 10, // Small initial movement to trigger drag + clientY: startY, + pointerId: 1 + }) + .trigger('pointermove', { + clientX: startX + 150, // Move 150px to the right (about 5 days) + clientY: startY, + pointerId: 1 + }) + .trigger('pointerup', { + clientX: startX + 150, + clientY: startY, + pointerId: 1, + force: true + }) + }) cy.wait('@taskUpdate') }) @@ -101,7 +130,7 @@ describe('Project View Gantt', () => { it('Should change the date range based on date query parameters', () => { cy.visit('/projects/1/2?dateFrom=2022-09-25&dateTo=2022-11-05') - cy.get('.g-timeunits-container') + cy.get('.gantt-timeline-months') .should('contain', 'September 2022') .should('contain', 'October 2022') .should('contain', 'November 2022') @@ -117,7 +146,7 @@ describe('Project View Gantt', () => { }) cy.visit('/projects/1/2') - cy.get('.gantt-container .g-gantt-chart .g-gantt-row-bars-container .g-gantt-bar') + cy.get('.gantt-container .gantt-row-bars .gantt-bar') .dblclick() cy.url() diff --git a/frontend/package.json b/frontend/package.json index debb68cbc..e00c18981 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -56,7 +56,6 @@ "@fortawesome/free-solid-svg-icons": "7.0.0", "@fortawesome/vue-fontawesome": "3.1.1", "@github/hotkey": "3.1.1", - "@infectoone/vue-ganttastic": "2.3.2", "@intlify/unplugin-vue-i18n": "6.0.8", "@kyvg/vue3-notification": "3.4.1", "@sentry/tracing": "7.120.4", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 7cf54a1c1..eac0aa1d9 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -34,9 +34,6 @@ importers: '@github/hotkey': specifier: 3.1.1 version: 3.1.1(patch_hash=145ab3233cbcd3bc934b4961cd8710e2b15e4ae5dd20862a8d1d6621d7f9d4a8) - '@infectoone/vue-ganttastic': - specifier: 2.3.2 - version: 2.3.2(dayjs@1.11.13)(vue@3.5.18(typescript@5.9.2)) '@intlify/unplugin-vue-i18n': specifier: 6.0.8 version: 6.0.8(@vue/compiler-dom@3.5.18)(eslint@9.33.0(jiti@2.4.2))(rollup@4.46.2)(typescript@5.9.2)(vue-i18n@11.1.11(vue@3.5.18(typescript@5.9.2)))(vue@3.5.18(typescript@5.9.2)) @@ -1512,12 +1509,6 @@ packages: resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==} engines: {node: '>=18.18'} - '@infectoone/vue-ganttastic@2.3.2': - resolution: {integrity: sha512-krxHdlZvo4cdS4axQ99qb756RzwieI7LcyY2vAIehJ5Sxd/jz5Pu/vTplTC0Rxqj8T4v1knYPK9uvTMkQYWYng==} - peerDependencies: - dayjs: ^1.11.5 - vue: ^3.2.40 - '@intlify/bundle-utils@10.0.1': resolution: {integrity: sha512-WkaXfSevtpgtUR4t8K2M6lbR7g03mtOxFeh+vXp5KExvPqS12ppaRj1QxzwRuRI5VUto54A22BjKoBMLyHILWQ==} engines: {node: '>= 18'} @@ -2335,9 +2326,6 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - '@types/web-bluetooth@0.0.16': - resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==} - '@types/web-bluetooth@0.0.21': resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} @@ -2660,15 +2648,9 @@ packages: peerDependencies: vue: ^3.5.0 - '@vueuse/core@9.13.0': - resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==} - '@vueuse/metadata@13.6.0': resolution: {integrity: sha512-rnIH7JvU7NjrpexTsl2Iwv0V0yAx9cw7+clymjKuLSXG0QMcLD0LDgdNmXic+qL0SGvgSVPEpM9IDO/wqo1vkQ==} - '@vueuse/metadata@9.13.0': - resolution: {integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==} - '@vueuse/router@13.6.0': resolution: {integrity: sha512-iXRwR4K7nz4PReW0QudhnM9NtYGvN4KrskFgF9G7NouM43big3bpSNRRocJKFWK7iu97ww5y82B3QA2zz3S/vw==} peerDependencies: @@ -2680,9 +2662,6 @@ packages: peerDependencies: vue: ^3.5.0 - '@vueuse/shared@9.13.0': - resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==} - abbrev@2.0.0: resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -8368,14 +8347,6 @@ snapshots: '@humanwhocodes/retry@0.4.2': {} - '@infectoone/vue-ganttastic@2.3.2(dayjs@1.11.13)(vue@3.5.18(typescript@5.9.2))': - dependencies: - '@vueuse/core': 9.13.0(vue@3.5.18(typescript@5.9.2)) - dayjs: 1.11.13 - vue: 3.5.18(typescript@5.9.2) - transitivePeerDependencies: - - '@vue/composition-api' - '@intlify/bundle-utils@10.0.1(vue-i18n@11.1.11(vue@3.5.18(typescript@5.9.2)))': dependencies: '@intlify/message-compiler': 11.1.5 @@ -9173,8 +9144,6 @@ snapshots: '@types/unist@3.0.3': {} - '@types/web-bluetooth@0.0.16': {} - '@types/web-bluetooth@0.0.21': {} '@types/whatwg-mimetype@3.0.2': {} @@ -9656,20 +9625,8 @@ snapshots: '@vueuse/shared': 13.6.0(vue@3.5.18(typescript@5.9.2)) vue: 3.5.18(typescript@5.9.2) - '@vueuse/core@9.13.0(vue@3.5.18(typescript@5.9.2))': - dependencies: - '@types/web-bluetooth': 0.0.16 - '@vueuse/metadata': 9.13.0 - '@vueuse/shared': 9.13.0(vue@3.5.18(typescript@5.9.2)) - vue-demi: 0.14.10(vue@3.5.18(typescript@5.9.2)) - transitivePeerDependencies: - - '@vue/composition-api' - - vue - '@vueuse/metadata@13.6.0': {} - '@vueuse/metadata@9.13.0': {} - '@vueuse/router@13.6.0(vue-router@4.5.1(vue@3.5.18(typescript@5.9.2)))(vue@3.5.18(typescript@5.9.2))': dependencies: '@vueuse/shared': 13.6.0(vue@3.5.18(typescript@5.9.2)) @@ -9680,13 +9637,6 @@ snapshots: dependencies: vue: 3.5.18(typescript@5.9.2) - '@vueuse/shared@9.13.0(vue@3.5.18(typescript@5.9.2))': - dependencies: - vue-demi: 0.14.10(vue@3.5.18(typescript@5.9.2)) - transitivePeerDependencies: - - '@vue/composition-api' - - vue - abbrev@2.0.0: {} acorn-jsx@5.3.2(acorn@8.15.0): diff --git a/frontend/src/components/gantt/GanttChart.vue b/frontend/src/components/gantt/GanttChart.vue new file mode 100644 index 000000000..23fab0cc3 --- /dev/null +++ b/frontend/src/components/gantt/GanttChart.vue @@ -0,0 +1,507 @@ + + + + + diff --git a/frontend/src/components/gantt/GanttChartBody.vue b/frontend/src/components/gantt/GanttChartBody.vue new file mode 100644 index 000000000..bda0f4814 --- /dev/null +++ b/frontend/src/components/gantt/GanttChartBody.vue @@ -0,0 +1,41 @@ + + + diff --git a/frontend/src/components/gantt/GanttRow.vue b/frontend/src/components/gantt/GanttRow.vue new file mode 100644 index 000000000..f355ff121 --- /dev/null +++ b/frontend/src/components/gantt/GanttRow.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/frontend/src/components/gantt/GanttRowBars.vue b/frontend/src/components/gantt/GanttRowBars.vue new file mode 100644 index 000000000..7ae7f429e --- /dev/null +++ b/frontend/src/components/gantt/GanttRowBars.vue @@ -0,0 +1,308 @@ + + + + + diff --git a/frontend/src/components/gantt/GanttTimelineHeader.vue b/frontend/src/components/gantt/GanttTimelineHeader.vue new file mode 100644 index 000000000..4f60ed28e --- /dev/null +++ b/frontend/src/components/gantt/GanttTimelineHeader.vue @@ -0,0 +1,157 @@ + + + + + diff --git a/frontend/src/components/gantt/GanttVerticalGridLines.vue b/frontend/src/components/gantt/GanttVerticalGridLines.vue new file mode 100644 index 000000000..74988fda9 --- /dev/null +++ b/frontend/src/components/gantt/GanttVerticalGridLines.vue @@ -0,0 +1,45 @@ + + + + + diff --git a/frontend/src/components/gantt/primitives/GanttBarPrimitive.vue b/frontend/src/components/gantt/primitives/GanttBarPrimitive.vue new file mode 100644 index 000000000..b95d5e72a --- /dev/null +++ b/frontend/src/components/gantt/primitives/GanttBarPrimitive.vue @@ -0,0 +1,81 @@ + + + diff --git a/frontend/src/components/gantt/primitives/GanttChartPrimitive.vue b/frontend/src/components/gantt/primitives/GanttChartPrimitive.vue new file mode 100644 index 000000000..6b751a618 --- /dev/null +++ b/frontend/src/components/gantt/primitives/GanttChartPrimitive.vue @@ -0,0 +1,81 @@ + + + diff --git a/frontend/src/components/gantt/primitives/GanttRowPrimitive.vue b/frontend/src/components/gantt/primitives/GanttRowPrimitive.vue new file mode 100644 index 000000000..933146d0f --- /dev/null +++ b/frontend/src/components/gantt/primitives/GanttRowPrimitive.vue @@ -0,0 +1,43 @@ + + + diff --git a/frontend/src/components/misc/keyboard-shortcuts/shortcuts.ts b/frontend/src/components/misc/keyboard-shortcuts/shortcuts.ts index f5826d986..5ef093e5c 100644 --- a/frontend/src/components/misc/keyboard-shortcuts/shortcuts.ts +++ b/frontend/src/components/misc/keyboard-shortcuts/shortcuts.ts @@ -114,6 +114,36 @@ export const KEYBOARD_SHORTCUTS: ShortcutGroup[] = [ }, ], }, + { + title: 'keyboardShortcuts.gantt.title', + available: (route) => route.name === 'project.view', + shortcuts: [ + { + title: 'keyboardShortcuts.gantt.moveTaskLeft', + keys: ['←'], + }, + { + title: 'keyboardShortcuts.gantt.moveTaskRight', + keys: ['→'], + }, + { + title: 'keyboardShortcuts.gantt.expandTaskLeft', + keys: ['shift', '←'], + }, + { + title: 'keyboardShortcuts.gantt.expandTaskRight', + keys: ['shift', '→'], + }, + { + title: 'keyboardShortcuts.gantt.shrinkTaskLeft', + keys: [ctrl, '←'], + }, + { + title: 'keyboardShortcuts.gantt.shrinkTaskRight', + keys: [ctrl, '→'], + }, + ], + }, { title: 'keyboardShortcuts.task.title', available: (route) => route.name === 'task.detail', diff --git a/frontend/src/components/project/views/ProjectGantt.vue b/frontend/src/components/project/views/ProjectGantt.vue index a287e103c..5d997e58e 100644 --- a/frontend/src/components/project/views/ProjectGantt.vue +++ b/frontend/src/components/project/views/ProjectGantt.vue @@ -85,7 +85,7 @@ import ProjectWrapper from '@/components/project/ProjectWrapper.vue' import FancyCheckbox from '@/components/input/FancyCheckbox.vue' import TaskForm from '@/components/tasks/TaskForm.vue' -import {createAsyncComponent} from '@/helpers/createAsyncComponent' +import GanttChart from '@/components/gantt/GanttChart.vue' import {useGanttFilters} from '../../../views/project/helpers/useGanttFilters' import {RIGHTS} from '@/constants/rights' @@ -101,7 +101,6 @@ const props = defineProps<{ viewId: IProjectView['id'] }>() -const GanttChart = createAsyncComponent(() => import('@/components/tasks/GanttChart.vue')) const baseStore = useBaseStore() const canWrite = computed(() => baseStore.currentProject?.maxRight > RIGHTS.READ) diff --git a/frontend/src/components/tasks/GanttChart.vue b/frontend/src/components/tasks/GanttChart.vue deleted file mode 100644 index 535bf6c23..000000000 --- a/frontend/src/components/tasks/GanttChart.vue +++ /dev/null @@ -1,288 +0,0 @@ - - - - - - - - diff --git a/frontend/src/composables/useGanttBar.ts b/frontend/src/composables/useGanttBar.ts new file mode 100644 index 000000000..a72ab13e8 --- /dev/null +++ b/frontend/src/composables/useGanttBar.ts @@ -0,0 +1,108 @@ +import { ref } from 'vue' + +export interface GanttBarModel { + id: string + start: Date + end: Date + meta?: { + label?: string + color?: string + hasActualDates?: boolean + isDone?: boolean + task?: unknown + } +} +export interface UseGanttBarOptions { + model: GanttBarModel + timelineStart: Date + timelineEnd: Date + onUpdate?: (id: string, newStart: Date, newEnd: Date) => void +} + +export function useGanttBar(options: UseGanttBarOptions) { + const dragging = ref(false) + const selected = ref(false) + const focused = ref(false) + + function onFocus() { + focused.value = true + } + + function onBlur() { + focused.value = false + } + + function changeSize(direction: 'left' | 'right', modifier: -1 | 1) { + const newStart = new Date(options.model.start) + const newEnd = new Date(options.model.end) + + if (direction === 'left') { + // Shift+Left: Expand task to the left (move start date earlier) + newStart.setDate(newStart.getDate() - 1 * modifier) + } else { + // Shift+Right: Expand task to the right (move end date later) + newEnd.setDate(newEnd.getDate() + 1 * modifier) + } + + // Validate that start is before end (maintain minimum 1 day duration) + if (newStart < newEnd) { + options.model.start = newStart + options.model.end = newEnd + + if (options.onUpdate) { + options.onUpdate(options.model.id, newStart, newEnd) + } + } + } + + function onKeyDown(e: KeyboardEvent) { + // task expanding + if (e.shiftKey) { + if (e.key === 'ArrowLeft') { + e.preventDefault() + changeSize('left', 1) + } + if (e.key === 'ArrowRight') { + e.preventDefault() + changeSize('right', 1) + } + } + // task shrinking + else if (e.ctrlKey) { + if (e.key === 'ArrowLeft') { + e.preventDefault() + changeSize('left', -1) + } + if (e.key === 'ArrowRight') { + e.preventDefault() + changeSize('right', -1) + } + } + // task movement + else if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') { + e.preventDefault() + + const dir = e.key === 'ArrowRight' ? 1 : -1 + const newStart = new Date(options.model.start) + newStart.setDate(newStart.getDate() + dir) + const newEnd = new Date(options.model.end) + newEnd.setDate(newEnd.getDate() + dir) + + options.model.start = newStart + options.model.end = newEnd + + if (options.onUpdate) { + options.onUpdate(options.model.id, newStart, newEnd) + } + } + } + + return { + dragging, + selected, + focused, + onFocus, + onBlur, + onKeyDown, + } +} diff --git a/frontend/src/helpers/color/getTextColor.ts b/frontend/src/helpers/color/getTextColor.ts new file mode 100644 index 000000000..35ba8e2f5 --- /dev/null +++ b/frontend/src/helpers/color/getTextColor.ts @@ -0,0 +1,11 @@ +import { colorIsDark } from './colorIsDark' + +export const LIGHT = 'hsl(220, 13%, 91%)' // grey-200 +export const DARK = 'hsl(215, 27.9%, 16.9%)' // grey-800 + +export function getTextColor(backgroundColor: string) { + return colorIsDark(backgroundColor) + // Fixed colors to avoid flipping in dark mode + ? DARK + : LIGHT +} diff --git a/frontend/src/i18n/lang/en.json b/frontend/src/i18n/lang/en.json index a11c44484..2e0de1432 100644 --- a/frontend/src/i18n/lang/en.json +++ b/frontend/src/i18n/lang/en.json @@ -366,7 +366,21 @@ "day": "Day", "hour": "Hour", "range": "Date Range", - "noDates": "This task has no dates set." + "chartLabel": "Project Gantt Chart", + "taskBarsForRow": "Task bars for row {rowId}", + "taskBarLabel": "Task: {task}. From {startDate} to {endDate}. {dateType}. Click to edit, drag to move.", + "scheduledDates": "Scheduled dates", + "estimatedDates": "Estimated dates", + "resizeStartDate": "Resize start date for task {task}", + "resizeEndDate": "Resize end date for task {task}", + "timelineHeader": "Timeline header with months and days", + "monthsRow": "Months row", + "daysRow": "Days row", + "monthLabel": "Month: {month}", + "dayLabel": "Day: {date}, {weekday}", + "dayLabelToday": "Today: {date}, {weekday}", + "taskAriaLabel": "Task: {task}", + "taskAriaLabelById": "Task {id}" }, "table": { "title": "Table", @@ -1101,6 +1115,15 @@ "navigateDown": "Highlight next task", "navigateUp": "Highlight previous task", "open": "Open highlighted task" + }, + "gantt": { + "title": "Gantt Chart", + "moveTaskLeft": "Move task to earlier date", + "moveTaskRight": "Move task to later date", + "expandTaskLeft": "Expand task start date earlier", + "expandTaskRight": "Expand task end date later", + "shrinkTaskLeft": "Shrink task from start date", + "shrinkTaskRight": "Shrink task from end date" } }, "update": { diff --git a/frontend/src/models/label.ts b/frontend/src/models/label.ts index 116b2f989..cc4a154e3 100644 --- a/frontend/src/models/label.ts +++ b/frontend/src/models/label.ts @@ -4,7 +4,7 @@ import UserModel from './user' import type {ILabel} from '@/modelTypes/ILabel' import type {IUser} from '@/modelTypes/IUser' -import {colorIsDark} from '@/helpers/color/colorIsDark' +import {getTextColor} from '@/helpers/color/getTextColor' export default class LabelModel extends AbstractModel implements ILabel { id = 0 @@ -29,10 +29,7 @@ export default class LabelModel extends AbstractModel implements ILabel } if (this.hexColor !== '') { - this.textColor = colorIsDark(this.hexColor) - // Fixed colors to avoid flipping in dark mode - ? 'hsl(215, 27.9%, 16.9%)' // grey-800 - : 'hsl(220, 13%, 91%)' // grey-200 + this.textColor = getTextColor(this.hexColor) } this.createdBy = new UserModel(this.createdBy)