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
@@ -211,16 +211,18 @@ async function verifySheet(
spreadsheetId: sheetId,
includeGridData: false,
});
const worksheets = spreadsheets.data.sheets?.forEach((sheet) => {
const worksheets: string[] = [];
spreadsheets.data.sheets?.forEach((sheet) => {
if (sheet.properties?.title) {
return sheet.properties.title;
worksheets.push(sheet.properties.title);
}
});
if (!worksheets) {
if (worksheets.length === 0) {
throw new Error('No worksheets found');
}
return worksheets;
return { worksheetOptions: worksheets };
} catch (error) {
// attempt to catch errors caused by importing xlsx
catchCommonImportXlsxError(error);
@@ -293,7 +295,17 @@ async function verifyWorksheet(sheetId: string, worksheet: string): Promise<{ wo
if (!selectedWorksheet) {
throw new Error('Could not find worksheet');
}
if (!selectedWorksheet.properties || !selectedWorksheet.properties.sheetId) {
/*
The first spreadsheet provided by google sheet has an id = 0,
so !0 returns true, the only other number that returns true in this setup is NaN,
so if x !== 0 && x !== NaN, then !x returns false, we indeed want !NaN to return true,
but we would like !0 to return false, reason why is also checked that the id is not 0,
because if it is 0, then I should not enter the condition.
*/
if (
!selectedWorksheet.properties ||
(!selectedWorksheet.properties.sheetId && selectedWorksheet.properties.sheetId !== 0)
) {
throw new Error('Got invalid data from worksheet');
}
@@ -274,6 +274,6 @@ describe('cellRequestFromEvent()', () => {
const result2 = cellRequestFromEvent(event, 10, 1234, metadata);
expect(result2.updateCells?.start?.rowIndex).toStrictEqual(21);
expect(result2.updateCells?.start?.columnIndex).toStrictEqual(5);
expect(result2.updateCells?.fields).toStrictEqual('userEnteredValue');
expect(result2.updateCells?.fields).toStrictEqual('userEnteredValue,userEnteredFormat');
});
});
@@ -1,5 +1,5 @@
import { isOntimeBlock, isOntimeEvent, OntimeEvent, OntimeEntry } from 'ontime-types';
import { millisToString } from 'ontime-utils';
import { isOntimeBlock, isOntimeEvent, OntimeEvent, OntimeEntry, RGBColour } from 'ontime-types';
import { cssOrHexToColour, isLightColour, millisToString, mixColours } from 'ontime-utils';
import type { sheets_v4 } from '@googleapis/sheets';
import { is } from '../../utils/is.js';
@@ -100,8 +100,26 @@ export function cellRequestFromEvent(
}
}
const colors = isOntimeEvent(event) || isOntimeBlock(event) ? getAccessibleColour(event.colour) : undefined;
const cellColor: sheets_v4.Schema$CellData = !colors
? {}
: {
userEnteredFormat: {
backgroundColor: toSheetColourLevel(colors.background),
textFormat: {
foregroundColor: toSheetColourLevel(colors.text),
},
borders: {
bottom: {
style: 'SOLID',
color: toSheetColourLevel(colors.border),
},
},
},
};
const returnRows: sheets_v4.Schema$CellData[] = rowData.map(([key, _]) => {
return getCellData(key, event);
return { ...getCellData(key, event), ...cellColor };
});
return {
@@ -111,7 +129,7 @@ export function cellRequestFromEvent(
rowIndex: index + rowData[0][1]['row'] + 1,
columnIndex: titleCol,
},
fields: 'userEnteredValue',
fields: 'userEnteredValue,userEnteredFormat',
rows: [
{
values: returnRows,
@@ -156,3 +174,29 @@ function getCellData(key: keyof OntimeEvent | 'blank', event: OntimeEntry) {
return {};
}
type googleSheetCellColour = {
background: RGBColour;
text: RGBColour;
border: RGBColour;
};
function getAccessibleColour(bgColour?: string): googleSheetCellColour | undefined {
if (!bgColour) return undefined;
const background = cssOrHexToColour(bgColour);
if (!background) return undefined;
const text = isLightColour(background) ? BLACK : WHITE;
const border = mixColours(background, text, 0.2);
return { background, text, border };
}
const BLACK: RGBColour = { red: 0, green: 0, blue: 0, alpha: 1 };
const WHITE: RGBColour = { red: 255, green: 255, blue: 255, alpha: 0.98 };
// sheets use color values from 0 to 1
function toSheetColourLevel(colour: RGBColour): RGBColour {
return { red: colour.red / 255, green: colour.green / 255, blue: colour.blue / 255, alpha: colour.alpha };
}
+2 -154
View File
@@ -1,4 +1,4 @@
import { isColourHex } from 'ontime-utils';
import { CssColours, isColourHex } from 'ontime-utils';
/**
* @description Converts a value to an item in the provided enume.
@@ -95,160 +95,8 @@ export function coerceColour(value: unknown): string {
if (lowerCaseValue === '') {
return lowerCaseValue; // None colour the same as the UI 'Ø' button
}
if (!(lowerCaseValue in cssColours)) {
if (!(lowerCaseValue in CssColours)) {
throw new Error('Invalid colour name received');
}
return lowerCaseValue;
}
//https://developer.mozilla.org/en-US/docs/Web/CSS/named-color
const cssColours = {
aliceblue: '#f0f8ff',
antiquewhite: '#faebd7',
aqua: '#00ffff',
aquamarine: '#7fffd4',
azure: '#f0ffff',
beige: '#f5f5dc',
bisque: '#ffe4c4',
black: '#000000',
blanchedalmond: '#ffebcd',
blue: '#0000ff',
blueviolet: '#8a2be2',
brown: '#a52a2a',
burlywood: '#deb887',
cadetblue: '#5f9ea0',
chartreuse: '#7fff00',
chocolate: '#d2691e',
coral: '#ff7f50',
cornflowerblue: '#6495ed',
cornsilk: '#fff8dc',
crimson: '#dc143c',
cyan: '#00ffff',
darkblue: '#00008b',
darkcyan: '#008b8b',
darkgoldenrod: '#b8860b',
darkgray: '#a9a9a9',
darkgreen: '#006400',
darkgrey: '#a9a9a9',
darkkhaki: '#bdb76b',
darkmagenta: '#8b008b',
darkolivegreen: '#556b2f',
darkorange: '#ff8c00',
darkorchid: '#9932cc',
darkred: '#8b0000',
darksalmon: '#e9967a',
darkseagreen: '#8fbc8f',
darkslateblue: '#483d8b',
darkslategray: '#2f4f4f',
darkslategrey: '#2f4f4f',
darkturquoise: '#00ced1',
darkviolet: '#9400d3',
deeppink: '#ff1493',
deepskyblue: '#00bfff',
dimgray: '#696969',
dimgrey: '#696969',
dodgerblue: '#1e90ff',
firebrick: '#b22222',
floralwhite: '#fffaf0',
forestgreen: '#228b22',
fuchsia: '#ff00ff',
gainsboro: '#dcdcdc',
ghostwhite: '#f8f8ff',
goldenrod: '#daa520',
gold: '#ffd700',
gray: '#808080',
green: '#008000',
greenyellow: '#adff2f',
grey: '#808080',
honeydew: '#f0fff0',
hotpink: '#ff69b4',
indianred: '#cd5c5c',
indigo: '#4b0082',
ivory: '#fffff0',
khaki: '#f0e68c',
lavenderblush: '#fff0f5',
lavender: '#e6e6fa',
lawngreen: '#7cfc00',
lemonchiffon: '#fffacd',
lightblue: '#add8e6',
lightcoral: '#f08080',
lightcyan: '#e0ffff',
lightgoldenrodyellow: '#fafad2',
lightgray: '#d3d3d3',
lightgreen: '#90ee90',
lightgrey: '#d3d3d3',
lightpink: '#ffb6c1',
lightsalmon: '#ffa07a',
lightseagreen: '#20b2aa',
lightskyblue: '#87cefa',
lightslategray: '#778899',
lightslategrey: '#778899',
lightsteelblue: '#b0c4de',
lightyellow: '#ffffe0',
lime: '#00ff00',
limegreen: '#32cd32',
linen: '#faf0e6',
magenta: '#ff00ff',
maroon: '#800000',
mediumaquamarine: '#66cdaa',
mediumblue: '#0000cd',
mediumorchid: '#ba55d3',
mediumpurple: '#9370db',
mediumseagreen: '#3cb371',
mediumslateblue: '#7b68ee',
mediumspringgreen: '#00fa9a',
mediumturquoise: '#48d1cc',
mediumvioletred: '#c71585',
midnightblue: '#191970',
mintcream: '#f5fffa',
mistyrose: '#ffe4e1',
moccasin: '#ffe4b5',
navajowhite: '#ffdead',
navy: '#000080',
oldlace: '#fdf5e6',
olive: '#808000',
olivedrab: '#6b8e23',
orange: '#ffa500',
orangered: '#ff4500',
orchid: '#da70d6',
palegoldenrod: '#eee8aa',
palegreen: '#98fb98',
paleturquoise: '#afeeee',
palevioletred: '#db7093',
papayawhip: '#ffefd5',
peachpuff: '#ffdab9',
peru: '#cd853f',
pink: '#ffc0cb',
plum: '#dda0dd',
powderblue: '#b0e0e6',
purple: '#800080',
rebeccapurple: '#663399',
red: '#ff0000',
rosybrown: '#bc8f8f',
royalblue: '#4169e1',
saddlebrown: '#8b4513',
salmon: '#fa8072',
sandybrown: '#f4a460',
seagreen: '#2e8b57',
seashell: '#fff5ee',
sienna: '#a0522d',
silver: '#c0c0c0',
skyblue: '#87ceeb',
slateblue: '#6a5acd',
slategray: '#708090',
slategrey: '#708090',
snow: '#fffafa',
springgreen: '#00ff7f',
steelblue: '#4682b4',
tan: '#d2b48c',
teal: '#008080',
thistle: '#d8bfd8',
tomato: '#ff6347',
turquoise: '#40e0d0',
violet: '#ee82ee',
wheat: '#f5deb3',
white: '#ffffff',
whitesmoke: '#f5f5f5',
yellow: '#ffff00',
yellowgreen: '#9acd3',
} as const;
@@ -0,0 +1 @@
export type RGBColour = { [key in 'red' | 'green' | 'blue' | 'alpha']: number };
+3
View File
@@ -122,3 +122,6 @@ export {
isTimerLifeCycle,
} from './utils/guards.js';
export type { MaybeNumber, MaybeString } from './utils/utils.type.js';
// Colour
export type { RGBColour } from './definitions/Colour.type.js';
+10
View File
@@ -90,3 +90,13 @@ export {
} from './src/feature/spreadsheet-import/spreadsheetImport.js';
export { isPlaybackActive } from './src/playback-utils/playbackstate.js';
//Colour
export {
colourToHex,
cssOrHexToColour,
hexToColour,
isLightColour,
mixColours,
CssColours,
} from './src/colour/colour.utils.js';
+237
View File
@@ -0,0 +1,237 @@
import type { RGBColour } from 'ontime-types';
import { isColourHex } from '../regex-utils/isColourHex.js';
// naive colour mix
export function mixColours(colour1: RGBColour, colour2: RGBColour, p: number = 0.5) {
const w1 = p;
const w2 = 1 - w1;
return {
red: Math.round(colour1.red * w1 + colour2.red * w2),
green: Math.round(colour1.green * w1 + colour2.green * w2),
blue: Math.round(colour1.blue * w1 + colour2.blue * w2),
alpha: 1,
};
}
export function colourToHex(colour: RGBColour): string {
const alpha = Math.round(colour.alpha * 255)
.toString(16)
.padStart(2, '0');
const red = colour.red.toString(16).padStart(2, '0');
const green = colour.green.toString(16).padStart(2, '0');
const blue = colour.blue.toString(16).padStart(2, '0');
return '#' + red + green + blue + alpha;
}
export function cssOrHexToColour(colour: string): RGBColour | null {
if (colour.startsWith('#')) return hexToColour(colour);
const maybeCssColour = colour.toLocaleLowerCase();
if (maybeCssColour in CssColours) {
return hexToColour(CssColours[maybeCssColour]);
}
return null;
}
export function hexToColour(hexColour: string): RGBColour | null {
if (!isColourHex(hexColour)) return null;
let hex = hexColour.toLocaleLowerCase();
hex = hex.replace(/^#/, '');
let alpha = 1;
// full length hex #FFFFFFFF
if (hex.length === 8) {
const alphaPart = hex.slice(6, 8);
alpha = parseInt(alphaPart, 16) / 255;
hex = hex.slice(0, 6);
}
// compressed hex with alpha #FFFF
if (hex.length === 4) {
const alphaPart = hex.slice(3, 4).repeat(2);
alpha = parseInt(alphaPart, 16) / 255;
hex = hex.slice(0, 3);
}
// compressed hex without alpha or after the alpha channel has been removed
// here all the values duplicated to create a 6 length hex value
if (hex.length === 3) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}
const number = parseInt(hex, 16);
const red = number >> 16;
const green = (number >> 8) & 255;
const blue = number & 255;
if (isNaN(red) || isNaN(green) || isNaN(blue) || isNaN(alpha)) return null;
return { red, green, blue, alpha };
}
/**
* Calculates if black or white is the best contras
*
* @returns true if the colour is light -> the best contrast is black
*
* is done with YIQ calculation
* @link https://24ways.org/2010/calculating-color-contrast
*/
export function isLightColour(colour: RGBColour): boolean {
const yiq = (colour.red * 299 + colour.green * 587 + colour.blue * 114) / 1000;
return yiq >= 128;
}
//https://developer.mozilla.org/en-US/docs/Web/CSS/named-color
export const CssColours: Record<string, string> = {
aliceblue: '#f0f8ff',
antiquewhite: '#faebd7',
aqua: '#00ffff',
aquamarine: '#7fffd4',
azure: '#f0ffff',
beige: '#f5f5dc',
bisque: '#ffe4c4',
black: '#000000',
blanchedalmond: '#ffebcd',
blue: '#0000ff',
blueviolet: '#8a2be2',
brown: '#a52a2a',
burlywood: '#deb887',
cadetblue: '#5f9ea0',
chartreuse: '#7fff00',
chocolate: '#d2691e',
coral: '#ff7f50',
cornflowerblue: '#6495ed',
cornsilk: '#fff8dc',
crimson: '#dc143c',
cyan: '#00ffff',
darkblue: '#00008b',
darkcyan: '#008b8b',
darkgoldenrod: '#b8860b',
darkgray: '#a9a9a9',
darkgreen: '#006400',
darkgrey: '#a9a9a9',
darkkhaki: '#bdb76b',
darkmagenta: '#8b008b',
darkolivegreen: '#556b2f',
darkorange: '#ff8c00',
darkorchid: '#9932cc',
darkred: '#8b0000',
darksalmon: '#e9967a',
darkseagreen: '#8fbc8f',
darkslateblue: '#483d8b',
darkslategray: '#2f4f4f',
darkslategrey: '#2f4f4f',
darkturquoise: '#00ced1',
darkviolet: '#9400d3',
deeppink: '#ff1493',
deepskyblue: '#00bfff',
dimgray: '#696969',
dimgrey: '#696969',
dodgerblue: '#1e90ff',
firebrick: '#b22222',
floralwhite: '#fffaf0',
forestgreen: '#228b22',
fuchsia: '#ff00ff',
gainsboro: '#dcdcdc',
ghostwhite: '#f8f8ff',
goldenrod: '#daa520',
gold: '#ffd700',
gray: '#808080',
green: '#008000',
greenyellow: '#adff2f',
grey: '#808080',
honeydew: '#f0fff0',
hotpink: '#ff69b4',
indianred: '#cd5c5c',
indigo: '#4b0082',
ivory: '#fffff0',
khaki: '#f0e68c',
lavenderblush: '#fff0f5',
lavender: '#e6e6fa',
lawngreen: '#7cfc00',
lemonchiffon: '#fffacd',
lightblue: '#add8e6',
lightcoral: '#f08080',
lightcyan: '#e0ffff',
lightgoldenrodyellow: '#fafad2',
lightgray: '#d3d3d3',
lightgreen: '#90ee90',
lightgrey: '#d3d3d3',
lightpink: '#ffb6c1',
lightsalmon: '#ffa07a',
lightseagreen: '#20b2aa',
lightskyblue: '#87cefa',
lightslategray: '#778899',
lightslategrey: '#778899',
lightsteelblue: '#b0c4de',
lightyellow: '#ffffe0',
lime: '#00ff00',
limegreen: '#32cd32',
linen: '#faf0e6',
magenta: '#ff00ff',
maroon: '#800000',
mediumaquamarine: '#66cdaa',
mediumblue: '#0000cd',
mediumorchid: '#ba55d3',
mediumpurple: '#9370db',
mediumseagreen: '#3cb371',
mediumslateblue: '#7b68ee',
mediumspringgreen: '#00fa9a',
mediumturquoise: '#48d1cc',
mediumvioletred: '#c71585',
midnightblue: '#191970',
mintcream: '#f5fffa',
mistyrose: '#ffe4e1',
moccasin: '#ffe4b5',
navajowhite: '#ffdead',
navy: '#000080',
oldlace: '#fdf5e6',
olive: '#808000',
olivedrab: '#6b8e23',
orange: '#ffa500',
orangered: '#ff4500',
orchid: '#da70d6',
palegoldenrod: '#eee8aa',
palegreen: '#98fb98',
paleturquoise: '#afeeee',
palevioletred: '#db7093',
papayawhip: '#ffefd5',
peachpuff: '#ffdab9',
peru: '#cd853f',
pink: '#ffc0cb',
plum: '#dda0dd',
powderblue: '#b0e0e6',
purple: '#800080',
rebeccapurple: '#663399',
red: '#ff0000',
rosybrown: '#bc8f8f',
royalblue: '#4169e1',
saddlebrown: '#8b4513',
salmon: '#fa8072',
sandybrown: '#f4a460',
seagreen: '#2e8b57',
seashell: '#fff5ee',
sienna: '#a0522d',
silver: '#c0c0c0',
skyblue: '#87ceeb',
slateblue: '#6a5acd',
slategray: '#708090',
slategrey: '#708090',
snow: '#fffafa',
springgreen: '#00ff7f',
steelblue: '#4682b4',
tan: '#d2b48c',
teal: '#008080',
thistle: '#d8bfd8',
tomato: '#ff6347',
turquoise: '#40e0d0',
violet: '#ee82ee',
wheat: '#f5deb3',
white: '#ffffff',
whitesmoke: '#f5f5f5',
yellow: '#ffff00',
yellowgreen: '#9acd3',
} as const;
-54
View File
@@ -137,9 +137,6 @@ importers:
axios:
specifier: ^1.9.0
version: 1.9.0
color:
specifier: ^4.2.3
version: 4.2.3
csv-stringify:
specifier: ^6.4.5
version: 6.4.5
@@ -189,9 +186,6 @@ importers:
'@tanstack/eslint-plugin-query':
specifier: ^5.8.4
version: 5.8.4(eslint@8.56.0)(typescript@5.5.3)
'@types/color':
specifier: ^3.0.3
version: 3.0.3
'@types/prismjs':
specifier: ^1.26.5
version: 1.26.5
@@ -2234,15 +2228,6 @@ packages:
'@types/chai@5.2.2':
resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==}
'@types/color-convert@2.0.0':
resolution: {integrity: sha512-m7GG7IKKGuJUXvkZ1qqG3ChccdIM/qBBo913z+Xft0nKCX4hAU/IxKwZBU4cpRZ7GS5kV4vOblUkILtSShCPXQ==}
'@types/color-name@1.1.1':
resolution: {integrity: sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==}
'@types/color@3.0.3':
resolution: {integrity: sha512-X//qzJ3d3Zj82J9sC/C18ZY5f43utPbAJ6PhYt/M7uG6etcF6MRpKdN880KBy43B0BMzSfeT96MzrsNjFI3GbA==}
'@types/connect@3.4.35':
resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==}
@@ -2887,16 +2872,9 @@ packages:
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
color-string@1.9.1:
resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
color2k@2.0.2:
resolution: {integrity: sha512-kJhwH5nAwb34tmyuqq/lgjEKzlFXn1U99NlnB6Ws4qVaERcRUYeYP1cBw6BJ4vxaWStAUEef4WMr7WjOCnBt8w==}
color@4.2.3:
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
engines: {node: '>=12.5.0'}
combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
@@ -3898,9 +3876,6 @@ packages:
is-arrayish@0.2.1:
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
is-arrayish@0.3.2:
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
is-bigint@1.0.4:
resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
@@ -5003,9 +4978,6 @@ packages:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'}
simple-swizzle@0.2.2:
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
simple-update-notifier@2.0.0:
resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==}
engines: {node: '>=10'}
@@ -7646,16 +7618,6 @@ snapshots:
dependencies:
'@types/deep-eql': 4.0.2
'@types/color-convert@2.0.0':
dependencies:
'@types/color-name': 1.1.1
'@types/color-name@1.1.1': {}
'@types/color@3.0.3':
dependencies:
'@types/color-convert': 2.0.0
'@types/connect@3.4.35':
dependencies:
'@types/node': 22.15.26
@@ -8526,18 +8488,8 @@ snapshots:
color-name@1.1.4: {}
color-string@1.9.1:
dependencies:
color-name: 1.1.4
simple-swizzle: 0.2.2
color2k@2.0.2: {}
color@4.2.3:
dependencies:
color-convert: 2.0.1
color-string: 1.9.1
combined-stream@1.0.8:
dependencies:
delayed-stream: 1.0.0
@@ -9819,8 +9771,6 @@ snapshots:
is-arrayish@0.2.1: {}
is-arrayish@0.3.2: {}
is-bigint@1.0.4:
dependencies:
has-bigints: 1.0.2
@@ -10935,10 +10885,6 @@ snapshots:
signal-exit@4.1.0: {}
simple-swizzle@0.2.2:
dependencies:
is-arrayish: 0.3.2
simple-update-notifier@2.0.0:
dependencies:
semver: 7.6.2