Colorised spreadsheet (#1643)

* feat: colorise spreadSheet with rundown color

* feat: add colorisation in spreadsheet for block

* feat: border color

* fix: cleanup accessible color

* fix: review before merge (https://github.com/cpvalente/ontime/pull/1608)

* fix: usgae of map instead foreach (https://github.com/cpvalente/ontime/pull/1608)

* Update apps/server/src/services/sheet-service/SheetService.ts

Co-authored-by: Carlos Valente <34649812+cpvalente@users.noreply.github.com>

* fix: comment condition

* fix: Remove Color plugin (but don't can get color like 'red' or 'black')

* fix: isLight for more clean code

* fix: remove map on isLight function

* fix: remove debug logger

* refactor colour

* lint

---------

Co-authored-by: Gwenitora <gwenitora.gardu74@gmail.com>
Co-authored-by: Gwenitora <61121870+Gwenitora@users.noreply.github.com>
Co-authored-by: Carlos Valente <34649812+cpvalente@users.noreply.github.com>
This commit is contained in:
Alex Christoffer Rasmussen
2025-06-24 22:38:03 +02:00
committed by GitHub
parent 984dcb0181
commit 4872cd0a48
14 changed files with 350 additions and 252 deletions
-2
View File
@@ -21,7 +21,6 @@
"@tanstack/react-table": "^8.21.3",
"autosize": "^6.0.1",
"axios": "^1.9.0",
"color": "^4.2.3",
"csv-stringify": "^6.4.5",
"framer-motion": "^10.10.0",
"prismjs": "^1.29.0",
@@ -67,7 +66,6 @@
"devDependencies": {
"@sentry/vite-plugin": "^2.16.1",
"@tanstack/eslint-plugin-query": "^5.8.4",
"@types/color": "^3.0.3",
"@types/prismjs": "^1.26.5",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.10",
@@ -18,19 +18,19 @@ describe('getAccessibleColour()', () => {
it('handles named colours', () => {
const colour = 'red';
const { backgroundColor, color } = getAccessibleColour(colour);
expect(backgroundColor).toBe('#FF0000FF');
expect(backgroundColor).toBe('#ff0000ff');
expect(color).toBe('#fffffa');
});
it('handles hex colours', () => {
const colour = '#0F0';
const { backgroundColor, color } = getAccessibleColour(colour);
expect(backgroundColor).toBe('#00FF00FF');
expect(backgroundColor).toBe('#00ff00ff');
expect(color).toBe('black');
});
it('handles transparens', () => {
const colour = '#0F08';
const { backgroundColor, color } = getAccessibleColour(colour);
expect(backgroundColor).toBe('#0C940CFF');
expect(backgroundColor).toBe('#0c940cff');
expect(color).toBe('#fffffa');
});
});
+16 -19
View File
@@ -1,27 +1,27 @@
import Color from 'color';
import { RGBColour } from 'ontime-types';
import { colourToHex, cssOrHexToColour, isLightColour, mixColours } from 'ontime-utils';
type ColourCombination = {
backgroundColor: string;
color: string;
};
const defaultUiBackground: RGBColour = { red: 26, green: 26, blue: 26, alpha: 1 };
/**
* @description Selects text colour to maintain accessible contrast
* @param bgColour
* @return {{backgroundColor, color: string}}
*/
export const getAccessibleColour = (bgColour?: string): ColourCombination => {
if (bgColour) {
try {
const originalColour = Color(bgColour);
const backgroundColorMix = originalColour.alpha(1).mix(Color('#1a1a1a'), 1 - originalColour.alpha());
const textColor = backgroundColorMix.isLight() ? 'black' : '#fffffa';
return { backgroundColor: backgroundColorMix.hexa(), color: textColor };
} catch (_error) {
/* we do not handle errors here */
}
}
return { backgroundColor: '#1a1a1a', color: '#fffffa' };
if (!bgColour) return { backgroundColor: '#1a1a1a', color: '#fffffa' };
const originalColour = cssOrHexToColour(bgColour);
if (!originalColour) return { backgroundColor: '#1a1a1a', color: '#fffffa' };
const backgroundColorMix = mixColours(defaultUiBackground, originalColour, 1 - originalColour.alpha);
const textColor = isLightColour(backgroundColorMix) ? 'black' : '#fffffa';
return { backgroundColor: colourToHex(backgroundColorMix), color: textColor };
};
/**
@@ -39,11 +39,8 @@ export const timerPlaceholderMin = '––:––';
* Adds opacity to a given colour if possible
*/
export function alpha(colour: string, amount: number): string {
try {
const withAlpha = Color(colour).alpha(amount).hexa();
return withAlpha;
} catch (_error) {
/* we do not handle errors here */
}
return colour;
const originalColour = cssOrHexToColour(colour);
if (!originalColour) return colour;
originalColour.alpha = amount;
return colourToHex(originalColour);
}
@@ -1,7 +1,7 @@
import { MutableRefObject } from 'react';
import { RowModel, Table } from '@tanstack/react-table';
import Color from 'color';
import { isOntimeBlock, isOntimeDelay, isOntimeEvent, OntimeEntry } from 'ontime-types';
import { colourToHex, cssOrHexToColour } from 'ontime-utils';
import { useSelectedEventId } from '../../../../common/hooks/useSocket';
import { lazyEvaluate } from '../../../../common/utils/lazyEvaluate';
@@ -76,12 +76,13 @@ export default function CuesheetBody(props: CuesheetBodyProps) {
if (isSelected) {
rowBgColour = '#D20300'; // $red-700
} else if (entry.colour) {
try {
// the colour is user defined and might be invalid
const accessibleBackgroundColor = Color(getAccessibleColour(entry.colour).backgroundColor);
rowBgColour = accessibleBackgroundColor.fade(0.75).hexa();
} catch (_error) {
/* we do not handle errors here */
// the colour is user defined and might be invalid
const accessibleBackgroundColor = cssOrHexToColour(getAccessibleColour(entry.colour).backgroundColor);
if (accessibleBackgroundColor !== null) {
rowBgColour = colourToHex({
...accessibleBackgroundColor,
alpha: accessibleBackgroundColor.alpha * 0.25,
});
}
}
@@ -1,8 +1,8 @@
import { memo, MutableRefObject, useLayoutEffect, useRef, useState } from 'react';
import { IoEllipsisHorizontal } from 'react-icons/io5';
import { flexRender, Table } from '@tanstack/react-table';
import Color from 'color';
import { OntimeEntry, OntimeEvent } from 'ontime-types';
import { OntimeEntry, OntimeEvent, RGBColour } from 'ontime-types';
import { colourToHex, cssOrHexToColour } from 'ontime-utils';
import IconButton from '../../../../common/components/buttons/IconButton';
import { cx, getAccessibleColour } from '../../../../common/utils/styleUtils';
@@ -75,7 +75,8 @@ function EventRow(props: EventRowProps) {
}, [ownRef, selectedRef]);
const { color, backgroundColor } = getAccessibleColour(event.colour);
const mutedText = Color(color).fade(0.4).hexa();
const tmpColour = cssOrHexToColour(color) as RGBColour; // we know this to be a correct colour
const mutedText = colourToHex({ ...tmpColour, alpha: tmpColour.alpha * 0.6 });
return (
<tr