diff --git a/src/components/Background.svelte b/src/components/Background.svelte index 90e23d99..8913c976 100644 --- a/src/components/Background.svelte +++ b/src/components/Background.svelte @@ -6,6 +6,7 @@ export let onChange: (color: Color) => void; export let bg: Color; export let colorSpace: any; + export let onSpaceChange: (space: string) => void; $: bgHex = bg.toHex(); @@ -19,7 +20,12 @@ onChange(Color.colorFromString(e.target.value, colorSpace)); }} /> - + void; + export let onSpaceChange: (space: string) => void; export let colorMode: any = "lab"; $: measuredColorMode = color.spaceName; type Channel = { @@ -148,10 +149,7 @@ { - // @ts-ignore - configStore.setChannelPickerSpace(e.currentTarget.value); - }} + on:change={(e) => onSpaceChange(e.currentTarget.value)} > {#each [...Object.keys(colorConfigs)] as colorMode} {colorMode} @@ -170,7 +168,7 @@ {channel.name} ({channel.min}-{channel.max}) { + // @ts-ignore + configStore.setCompareBackgroundSpace(space); + }} bg={Color.colorFromHex(bg, colorSpace)} - {colorSpace} + colorSpace={$configStore.compareBackgroundSpace} /> (showDiff = !showDiff)}> diff --git a/src/content-modules/Controls.svelte b/src/content-modules/Controls.svelte index c1fdeb8f..7614ef71 100644 --- a/src/content-modules/Controls.svelte +++ b/src/content-modules/Controls.svelte @@ -41,6 +41,10 @@ { + // @ts-ignore + configStore.setChannelPickerSpace(space); + }} onColorChange={(color) => { const updatedColors = [...colors]; updatedColors[focusedColors[0]] = color.toColorSpace(colorSpace); diff --git a/src/content-modules/MainColumn.svelte b/src/content-modules/MainColumn.svelte index 3ed30c83..a9792a16 100644 --- a/src/content-modules/MainColumn.svelte +++ b/src/content-modules/MainColumn.svelte @@ -42,9 +42,14 @@ onChange={(space) => colorStore.setColorSpace(space)} /> colorStore.setBackground(bg)} + onSpaceChange={(space) => { + // @ts-ignore + configStore.setChannelPickerSpaceBackground(space); + }} + onChange={(bg) => + colorStore.setBackground(bg.toColorSpace(currentPal.colorSpace))} bg={currentPal.background} - colorSpace={currentPal.colorSpace} + colorSpace={$configStore.channelPickerSpaceBackground} /> { const fix2 = await suggestLintFix(examplePal, exampleLint2).then((x) => x[0]); expect(fix2.colors.map((x) => x.toHex())).toMatchSnapshot(); }); + +test("ColorLint - MuthGuidelines (1) Background desaturation sufficient", () => { + const newLint = CreateCustomLint(MuthGuidelines[0]); + + const examplePal = makePalFromString([ + "#0084a9", + "#009de5", + "#5fb1ff", + "#bbc3ff", + ]); + examplePal.background = Color.colorFromHex("#f4e3e3", "lab"); + const exampleLint = new newLint(examplePal).run(); + expect(exampleLint.passes).toBe(true); + expect(exampleLint.message).toMatchSnapshot(); + + const examplePal2 = makePalFromString([ + "#0084a9", + "#009de5", + "#5fb1ff", + "#ecddff", + ]); + examplePal2.background = Color.colorFromHex("#000", "lab"); + const exampleLint2 = new newLint(examplePal2).run(); + expect(exampleLint2.passes).toBe(false); + expect(exampleLint2.message).toMatchSnapshot(); +}); + +test("ColorLint - MuthGuidelines (2) Avoid Tetradic Palettes", () => { + const newLint = CreateCustomLint(MuthGuidelines[1]); + + const examplePal = makePalFromString([ + "#0084a9", + "#009de5", + "#5fb1ff", + "#bbc3ff", + ]); + const exampleLint = new newLint(examplePal).run(); + expect(exampleLint.passes).toBe(true); + expect(exampleLint.message).toMatchSnapshot(); + + const examplePal2 = makePalFromString([ + "#d23bae", + "#3b6dd2", + "#d89a35", + "#36d745", + ]); + const exampleLint2 = new newLint(examplePal2).run(); + expect(exampleLint2.passes).toBe(false); + expect(exampleLint2.message).toMatchSnapshot(); +}); + +test("ColorLint - MuthGuidelines (3) Prefer yellowish or blueish greens", () => { + const newLint = CreateCustomLint(MuthGuidelines[2]); + + const examplePal = makePalFromString(["#bee38d", "#bbc3ff"]); + const exampleLint = new newLint(examplePal).run(); + expect(exampleLint.passes).toBe(true); + expect(exampleLint.message).toMatchSnapshot(); + + const examplePal2 = makePalFromString(["#0084a9", "#93e789"]); + const exampleLint2 = new newLint(examplePal2).run(); + expect(exampleLint2.passes).toBe(false); + expect(exampleLint2.message).toMatchSnapshot(); +}); + +test("ColorLint - MuthGuidelines (4) Avoid too much contrast with the background", () => { + const newLint = CreateCustomLint(MuthGuidelines[3]); + + const examplePal = makePalFromString(["#f4b05d", "#3c828d", "#1c4b76"]); + examplePal.background = Color.colorFromHex("#f9f9f9", "lab"); + const exampleLint = new newLint(examplePal).run(); + expect(exampleLint.passes).toBe(true); + expect(exampleLint.message).toMatchSnapshot(); + + const examplePal2 = makePalFromString(["#0e2d48", "#3c828d", "#b87930"]); + examplePal2.background = Color.colorFromHex("#f9f9f9", "lab"); + const exampleLint2 = new newLint(examplePal2).run(); + expect(exampleLint2.passes).toBe(false); + expect(exampleLint2.message).toMatchSnapshot(); +}); diff --git a/src/lib/ColorLint.ts b/src/lib/ColorLint.ts index 6cbfbd3c..e7f3d20d 100644 --- a/src/lib/ColorLint.ts +++ b/src/lib/ColorLint.ts @@ -13,6 +13,7 @@ export interface LintResult { affectTypes: Affect[]; contextTypes: Context[]; subscribedFix: string; + naturalLanguageProgram: string; } export class ColorLint { @@ -28,6 +29,7 @@ export class ColorLint { group: string = ""; description: string = ""; blameMode: "pair" | "single" | "none" = "none"; + naturalLanguageProgram: string = ""; level: LintLevel = "error"; subscribedFix: string = "none"; diff --git a/src/lib/CustomLint.ts b/src/lib/CustomLint.ts index 789f8052..62490b3c 100644 --- a/src/lib/CustomLint.ts +++ b/src/lib/CustomLint.ts @@ -34,6 +34,7 @@ export function CreateCustomLint(props: CustomLint) { isCustom = props.id; blameMode = props.blameMode; subscribedFix = props.subscribedFix || "none"; + naturalLanguageProgram = prettyPrintLL(Json.parse(props.program)); _runCheck(options: any) { const prog = Json.parse(props.program); diff --git a/src/lib/__snapshots__/ColorLint.test.ts.snap b/src/lib/__snapshots__/ColorLint.test.ts.snap index c57d910f..971a606d 100644 --- a/src/lib/__snapshots__/ColorLint.test.ts.snap +++ b/src/lib/__snapshots__/ColorLint.test.ts.snap @@ -48,6 +48,22 @@ exports[`ColorLint - MaxColors 1`] = `"This palette has too many colors and may exports[`ColorLint - MaxColors 2`] = `"This palette has too many colors and may be hard to discriminate in some contexts. Maximum: 10."`; +exports[`ColorLint - MuthGuidelines (1) Background desaturation sufficient 1`] = `"Colorful backgrounds can seem like a good idea, but they can easily distract from your data and they limit your potential color palette. Desaturated colors are your best bet. Roughly, you want either HSL.L > 90 and HSV.S < 8 for light backgrounds, or you want 10 < HSL.L < 25 and HSV.S < 20 for dark backgrounds. See "https://blog.datawrapper.de/beautifulcolors/#12" for more"`; + +exports[`ColorLint - MuthGuidelines (1) Background desaturation sufficient 2`] = `"Colorful backgrounds can seem like a good idea, but they can easily distract from your data and they limit your potential color palette. Desaturated colors are your best bet. Roughly, you want either HSL.L > 90 and HSV.S < 8 for light backgrounds, or you want 10 < HSL.L < 25 and HSV.S < 20 for dark backgrounds. See "https://blog.datawrapper.de/beautifulcolors/#12" for more"`; + +exports[`ColorLint - MuthGuidelines (2) Avoid Tetradic Palettes 1`] = `"This palette is tetradic, which is not recommended."`; + +exports[`ColorLint - MuthGuidelines (2) Avoid Tetradic Palettes 2`] = `"This palette is tetradic, which is not recommended."`; + +exports[`ColorLint - MuthGuidelines (3) Prefer yellowish or blueish greens 1`] = `"When using green, it is better to try to it a yellow or blue one (hue angle less than 60 or greater than 160). T See "https://blog.datawrapper.de/beautifulcolors/#5" for more."`; + +exports[`ColorLint - MuthGuidelines (3) Prefer yellowish or blueish greens 2`] = `"When using green, it is better to try to it a yellow or blue one (hue angle less than 60 or greater than 160). T See "https://blog.datawrapper.de/beautifulcolors/#5" for more."`; + +exports[`ColorLint - MuthGuidelines (4) Avoid too much contrast with the background 1`] = `"Don't make your colors too dark and saturated when you're using a bright background. If in doubt, try it out. Make your colors lighter, pull some saturation out of them and see how it feels. See "https://blog.datawrapper.de/beautifulcolors/#11" for more."`; + +exports[`ColorLint - MuthGuidelines (4) Avoid too much contrast with the background 2`] = `"Don't make your colors too dark and saturated when you're using a bright background. If in doubt, try it out. Make your colors lighter, pull some saturation out of them and see how it feels. See "https://blog.datawrapper.de/beautifulcolors/#11" for more."`; + exports[`ColorLint - MutuallyDistinct 1`] = `"Some colors in this palette () are not differentiable from each other."`; exports[`ColorLint - MutuallyDistinct 2`] = `"Some colors in this palette (#f5f5dc and #d7fcef) are not differentiable from each other."`; diff --git a/src/lib/lint-language/LintLanguage.test.ts b/src/lib/lint-language/LintLanguage.test.ts index a9c8bead..9b77840a 100644 --- a/src/lib/lint-language/LintLanguage.test.ts +++ b/src/lib/lint-language/LintLanguage.test.ts @@ -67,7 +67,9 @@ test("LintLanguage conjunctions", () => { ], }; expect(LLEval(prog1, exampleColors).result).toBe(true); - expect(prettyPrintLL(prog1)).toBe("count(colors) < 10 and count(colors) > 2"); + expect(prettyPrintLL(prog1)).toBe( + "(count(colors) < 10 and count(colors) > 2)" + ); const prog2 = { or: [ @@ -76,7 +78,9 @@ test("LintLanguage conjunctions", () => { ], }; expect(LLEval(prog2, exampleColors).result).toBe(false); - expect(prettyPrintLL(prog2)).toBe("count(colors) < 2 or count(colors) > 10"); + expect(prettyPrintLL(prog2)).toBe( + "(count(colors) < 2 or count(colors) > 10)" + ); }); test("LintLanguage Quantifiers All - Simple", () => { @@ -165,7 +169,7 @@ test("LintLanguage permutativeBlame - Tableau 10", () => { }); const expectedOutBlind = (type: string) => - `ALL a in colors, ALL b in colors WHERE index(a) != index(b), NOT similar(cvdSim(a, ${type}), cvdSim(b, ${type})) > 9`; + `ALL a in colors, ALL b in colors WHERE index(a) != index(b), NOT similar(cvdSim(a, ${type}), cvdSim(b, ${type})) < 9`; const allBlindProg = (type: string) => ({ all: { in: "colors", @@ -397,11 +401,12 @@ test("LintLanguage to color rotate", () => { in: "colors", varb: "b", predicate: { - "==": { + similar: { left: { "hsl.h": "a" }, right: { "+": { left: { "hsl.h": "b" }, right: 180 }, }, + threshold: 5, }, }, }, @@ -409,7 +414,7 @@ test("LintLanguage to color rotate", () => { }, }; expect(prettyPrintLL(realisticProgram)).toBe( - "EXIST a in colors, EXIST b in colors, hsl.h(a) == hsl.h(b) + 180" + "EXIST a in colors, EXIST b in colors, similar(hsl.h(a), hsl.h(b) + 180) < 5" ); expect(LLEval(realisticProgram, exampleColors).result).toBe(false); }); @@ -633,7 +638,7 @@ test("LintLanguage Sequential Colors", () => { const inOrder = toPal(["#d4a8ff", "#7bb9ff", "#008694"]); expect(LLEval(program, inOrder).result).toBe(true); expect(prettyPrintLL(program)).toBe( - "ALL (a, b) in colors WHERE index(a) - 1 == index(b), lab.l(a) > lab.l(b) or ALL (a, b) in colors WHERE index(a) - 1 == index(b), lab.l(a) < lab.l(b)" + "(ALL (a, b) in colors WHERE index(a) - 1 == index(b), lab.l(a) > lab.l(b) or ALL (a, b) in colors WHERE index(a) - 1 == index(b), lab.l(a) < lab.l(b))" ); }); @@ -721,7 +726,7 @@ test("LintLanguage Background differentiability", () => { }, }; const astString = prettyPrintLL(program); - expect(astString).toBe("ALL a in colors, NOT similar(a, background) > 15"); + expect(astString).toBe("ALL a in colors, NOT similar(a, background) < 15"); const result = LLEval(program, toPal(["#fff", "#eee", "#000", "#ddd"])); expect(result.result).toBe(false); expect(result.blame).toStrictEqual([0, 1, 3]); @@ -740,7 +745,7 @@ test("LintLanguage Sequential Similarity", () => { }; const astString = prettyPrintLL(program); expect(astString).toBe( - "ALL (a, b) in colors WHERE index(a) != index(b), NOT similar(a, b) > 10" + "ALL (a, b) in colors WHERE index(a) != index(b), NOT similar(a, b) < 10" ); const result = LLEval(program, toPal(["#fff", "#eee", "#000", "#ddd"])); expect(result.result).toBe(false); diff --git a/src/lib/lint-language/lint-language.ts b/src/lib/lint-language/lint-language.ts index 780a5605..fa491399 100644 --- a/src/lib/lint-language/lint-language.ts +++ b/src/lib/lint-language/lint-language.ts @@ -233,7 +233,7 @@ export class LLConjunction extends LLNode { if (this.type === "id") return this.children[0].toString(); if (this.type === "none") return ""; if (this.type === "not") return `NOT ${this.children[0].toString()}`; - return this.children.map((x) => x.toString()).join(` ${this.type} `); + return `(${this.children.map((x) => x.toString()).join(` ${this.type} `)})`; } } @@ -407,7 +407,12 @@ function compareValues( } return diff < thresh; } - throw new Error("Type error. Similar must be used with colors."); + if (typeof left === "number" && typeof right === "number") { + return Math.abs(left - right) < thresh; + } + throw new Error( + "Type error. Similar must be used with colors or numbers." + ); case "==": return left === right; case "!=": @@ -477,7 +482,7 @@ export class LLPredicate extends LLNode { const left = this.left.toString(); const right = this.right.toString(); if (this.type === "similar") { - return `similar(${left}, ${right}) > ${this.threshold}`; + return `similar(${left}, ${right}) < ${this.threshold}`; } return `${left} ${type} ${right}`; } diff --git a/src/lib/linter-tools/lint-fixer.ts b/src/lib/linter-tools/lint-fixer.ts index 296573cc..6517ba91 100644 --- a/src/lib/linter-tools/lint-fixer.ts +++ b/src/lib/linter-tools/lint-fixer.ts @@ -9,8 +9,8 @@ export async function suggestLintAIFix( engine: string ) { const colorSpace = palette.colorSpace; - console.log(lint); - return suggestFix(palette, lint.message, engine as any).then((x) => { + const msg = `${lint.message}\n\nFailed: ${lint.naturalLanguageProgram}`; + return suggestFix(palette, msg, engine as any).then((x) => { if (x.length === 0) { throw new Error("No suggestions"); } diff --git a/src/lib/linter-tools/lint-worker.worker.ts b/src/lib/linter-tools/lint-worker.worker.ts index d065f0e1..c86e82c8 100644 --- a/src/lib/linter-tools/lint-worker.worker.ts +++ b/src/lib/linter-tools/lint-worker.worker.ts @@ -1,5 +1,6 @@ import * as idb from "idb-keyval"; import { runLintChecks } from "../linter"; +import { prettyPrintLL } from "../lint-language/lint-language"; import type { CustomLint } from "../CustomLint"; import type { Palette } from "../../types"; import { Color } from "../Color"; @@ -84,6 +85,7 @@ async function dispatch(cmd: Command) { subscribedFix: x.subscribedFix, affectTypes: x.affectTypes, contextTypes: x.contextTypes, + naturalLanguageProgram: x.naturalLanguageProgram, }; }); simpleLintCache.set(cmd.content, result); diff --git a/src/lib/linter.ts b/src/lib/linter.ts index e7c2eb00..12d79f9c 100644 --- a/src/lib/linter.ts +++ b/src/lib/linter.ts @@ -17,6 +17,7 @@ import Fair from "./lints/fair"; import Gamut from "./lints/in-gamut"; import MaxColors from "./lints/max-colors"; import MutuallyDistinct from "./lints/mutually-distinct"; +import MuthGuidelines from "./lints/muth-guidelines"; import NameDiscriminability from "./lints/name-discrim"; import SequentialOrder from "./lints/sequential-order"; import SizeDiscrim from "./lints/size-discrim"; @@ -26,6 +27,7 @@ export const BUILT_INS: CustomLint[] = [ ...Affects, ...ColorBlindness, ...Fair, + ...MuthGuidelines, ...SizeDiscrim, AvoidExtremes, BackgroundDifferentiability, diff --git a/src/lib/lints/affects.ts b/src/lib/lints/affects.ts index 07f34a15..02a4d9bf 100644 --- a/src/lib/lints/affects.ts +++ b/src/lib/lints/affects.ts @@ -1,6 +1,5 @@ import { JSONToPrettyString } from "../utils"; import type { CustomLint } from "../CustomLint"; -import type { LintFixer } from "../linter-tools/lint-fixer"; // "Highly saturated light colors will not be appropriate for SERIOUS/TRUST/CALM": ALL (FILTER colors c, lab(c) > threshold) b, NOT hsl(b) > threshold const lints: CustomLint[] = []; diff --git a/src/lib/lints/muth-guidelines.ts b/src/lib/lints/muth-guidelines.ts new file mode 100644 index 00000000..f6d7d864 --- /dev/null +++ b/src/lib/lints/muth-guidelines.ts @@ -0,0 +1,162 @@ +import { JSONToPrettyString } from "../utils"; +import type { CustomLint } from "../CustomLint"; + +const lints: CustomLint[] = []; + +// Choose a background that's desaturated enough +// https://blog.datawrapper.de/beautifulcolors/#12 +const bgDeSaturated: CustomLint = { + name: `Background desaturation sufficient`, + program: JSONToPrettyString({ + // @ts-ignore + $schema: `${location.href}lint-schema.json`, + or: [ + // light background + { + or: [ + { + and: [ + // loosed the lighteness to 90 from 94, to make it fit her example + { ">": { left: { "hsl.l": "background" }, right: 90 } }, + { "<": { left: { "hsv.s": "background" }, right: 8 } }, + ], + }, + { ">": { left: { "hsl.l": "background" }, right: 99 } }, + ], + }, + // dark background + { + and: [ + { ">": { left: { "hsl.l": "background" }, right: 10 } }, + { "<": { left: { "hsl.l": "background" }, right: 26 } }, + { "<": { left: { "hsv.s": "background" }, right: 21 } }, + ], + }, + ], + }), + taskTypes: ["sequential", "diverging", "categorical"] as const, + affectTypes: [], + level: "warning", + group: "design", + description: "Background should be sufficiently desaturated. ", + failMessage: `Colorful backgrounds can seem like a good idea, but they can easily distract from your data and they limit your potential color palette. Desaturated colors are your best bet. Roughly, you want either HSL.L > 90 and HSV.S < 8 for light backgrounds, or you want 10 < HSL.L < 25 and HSV.S < 20 for dark backgrounds. See "https://blog.datawrapper.de/beautifulcolors/#12" for more`, + id: `background-de-saturation-built-in`, + blameMode: "none", +}; +lints.push(bgDeSaturated); + +const avoidTetradic: CustomLint = { + name: `Avoid Tetradic Palettes`, + program: JSONToPrettyString({ + // @ts-ignore + $schema: `${location.href}lint-schema.json`, + not: { + exist: { + varb: "a", + in: "colors", + predicate: { + and: [ + ...[90, 180, 270].map((angle) => ({ + exist: { + varb: "b", + in: "colors", + predicate: { + similar: { + left: { "hsl.h": "a" }, + right: { "+": { left: { "hsl.h": "b" }, right: 90 } }, + threshold: 5, + }, + }, + }, + })), + ], + }, + }, + }, + }), + taskTypes: ["sequential", "diverging", "categorical"] as const, + affectTypes: [], + level: "warning", + group: "design", + description: `Tetradic palettes are hard to work with and are not recommended.`, + failMessage: `This palette is tetradic, which is not recommended.`, + id: `avoid-tetradic-built-in`, + blameMode: "pair", +}; +lints.push(avoidTetradic); + +// When using green, make it a yellow or blue one +// https://blog.datawrapper.de/beautifulcolors/#5 +const avoidGreen: CustomLint = { + name: `Prefer yellowish or blueish greens`, + program: JSONToPrettyString({ + // @ts-ignore + $schema: `${location.href}lint-schema.json`, + all: { + varb: "a", + in: "colors", + predicate: { + or: [ + // using the looser bands intentionally, tigher bands would be !(60 <= x <= 160) + { "<": { left: { "hsl.h": "a" }, right: 90 } }, + { ">": { left: { "hsl.h": "a" }, right: 150 } }, + ], + }, + }, + }), + taskTypes: ["sequential", "diverging", "categorical"] as const, + affectTypes: [], + level: "warning", + group: "design", + description: `When using green, make it a yellow or blue one. This makes it easier to play nicely with other colors.`, + failMessage: `When using green, it is better to try to it a yellow or blue one (hue angle less than 60 or greater than 160). T See "https://blog.datawrapper.de/beautifulcolors/#5" for more.`, + id: `avoid-green-built-in`, + blameMode: "single", +}; +lints.push(avoidGreen); + +// The opposite is true, too: Don’t make your colors too dark and saturated when you’re using a bright background. If in doubt, try it out. Make your colors lighter, pull some saturation out of them and see how it feels. +// https://blog.datawrapper.de/beautifulcolors/#11 +const AvoidTooMuchContrastWithTheBackground: CustomLint = { + name: `Avoid too much contrast with the background`, + program: JSONToPrettyString({ + // @ts-ignore + $schema: `${location.href}lint-schema.json`, + or: [ + // bright background + { + and: [ + { ">": { left: { "hsl.l": "background" }, right: 50 } }, + { + all: { + varb: "a", + in: "colors", + predicate: { + "<": { + left: { + contrast: { left: "a", right: "background" }, + algorithm: "WCAG21", + }, + right: 10, + }, + }, + }, + }, + ], + }, + // not bright background + { "<": { left: { "hsl.l": "background" }, right: 50 } }, + ], + }), + taskTypes: ["sequential", "diverging", "categorical"] as const, + affectTypes: [], + level: "warning", + group: "design", + description: `Don't make your colors too dark and saturated when you're using a bright background. If in doubt, try it out. Make your colors lighter, pull some saturation out of them and see how it feels.`, + failMessage: `Don't make your colors too dark and saturated when you're using a bright background. If in doubt, try it out. Make your colors lighter, pull some saturation out of them and see how it feels. See "https://blog.datawrapper.de/beautifulcolors/#11" for more.`, + id: `avoid-too-much-contrast-with-the-background-built-in`, + blameMode: "single", +}; +lints.push(AvoidTooMuchContrastWithTheBackground); + +export default lints; diff --git a/src/linting/Eval.svelte b/src/linting/Eval.svelte index a07c8e09..025dadeb 100644 --- a/src/linting/Eval.svelte +++ b/src/linting/Eval.svelte @@ -134,8 +134,15 @@ { setTimeout(() => { + const outPal = { + ...currentPal, + evalConfig: { + ...currentPal.evalConfig, + globallyIgnoredLints: $lintStore.globallyIgnoredLints, + }, + }; loadLints() - .then(() => lint(currentPal, false)) + .then(() => lint(outPal, false)) .then((res) => { checks = res; }); diff --git a/src/linting/ExplanationViewer.svelte b/src/linting/ExplanationViewer.svelte index d4667ad7..1358404f 100644 --- a/src/linting/ExplanationViewer.svelte +++ b/src/linting/ExplanationViewer.svelte @@ -25,7 +25,11 @@ currentTextBlock = ""; } let color = message.slice(idx, idx + hexLength + 1); - output.push({ content: color, type: "color" }); + if (hexLength === 3 || hexLength === 6) { + output.push({ content: color, type: "color" }); + } else { + output.push({ content: color, type: "text" }); + } idx += hexLength; } else { currentTextBlock += message[idx]; diff --git a/src/linting/GlobalLintConfig.svelte b/src/linting/GlobalLintConfig.svelte index 14f18eab..33c32c57 100644 --- a/src/linting/GlobalLintConfig.svelte +++ b/src/linting/GlobalLintConfig.svelte @@ -6,6 +6,15 @@ $: lints = [...$lintStore.lints].sort((a, b) => a.name.localeCompare(b.name)); $: ignoreList = $lintStore.globallyIgnoredLints; $: ignoredSet = new Set(ignoreList); + + $: lintsByGroup = lints.reduce( + (acc, lint) => { + acc[lint.group] = acc[lint.group] || []; + acc[lint.group].push(lint); + return acc; + }, + {} as Record + ); @@ -28,29 +37,32 @@ - {#each lints as lint} - - { - const newLints = [...ignoreList]; - if (ignoredSet.has(lint.id)) { - newLints.filter((l) => l !== lint.id); - } else { - newLints.push(lint.id); - } - lintStore.setGloballyIgnoredLints(newLints); - }} - /> - - {lint.name} - {#if lint.taskTypes.length && lint.taskTypes.length !== 3} - ({lint.taskTypes.join(", ")}) - {/if} - - + {#each Object.entries(lintsByGroup) as [group, lints]} + {group} + {#each lints as lint} + + { + let newLints = [...ignoreList]; + if (ignoredSet.has(lint.id)) { + newLints = newLints.filter((l) => l !== lint.id); + } else { + newLints.push(lint.id); + } + lintStore.setGloballyIgnoredLints(newLints); + }} + /> + + {lint.name} + {#if lint.taskTypes.length && lint.taskTypes.length !== 3} + ({lint.taskTypes.join(", ")}) + {/if} + + + {/each} {/each} diff --git a/src/stores/config-store.ts b/src/stores/config-store.ts index 25dde705..be42be50 100644 --- a/src/stores/config-store.ts +++ b/src/stores/config-store.ts @@ -5,7 +5,9 @@ interface StoreData { comparePal: number | undefined; compareSelectedExample: number; channelPickerSpace: "lab" | "lch" | "hsl" | "hsv" | "rgb"; + channelPickerSpaceBackground: "lab" | "lch" | "hsl" | "hsv" | "rgb"; compareBackground: string | undefined; + compareBackgroundSpace: "lab" | "lch" | "hsl" | "hsv" | "rgb"; engine: "openai" | "google"; evalDisplayMode: "regular" | "compact"; evalDeltaDisplay: "none" | "76" | "CMC" | "2000" | "ITP" | "Jz" | "OK"; @@ -30,6 +32,7 @@ const InitialStore: StoreData = { comparePal: undefined, compareSelectedExample: -1, compareBackground: undefined, + compareBackgroundSpace: "lab", engine: "openai", evalDisplayMode: "regular", evalDeltaDisplay: "none", @@ -45,6 +48,7 @@ const InitialStore: StoreData = { mainColumnSelectedExample: -1, useSimulatorOnExamples: false, channelPickerSpace: "lab", + channelPickerSpaceBackground: "lab", xZoom: [0, 1], yZoom: [0, 1], zZoom: [0, 1], @@ -121,8 +125,13 @@ function createStore() { persist((old) => ({ ...old, exampleRoute: n })), setChannelPickerSpace: (n: StoreData["channelPickerSpace"]) => persist((old) => ({ ...old, channelPickerSpace: n })), + setChannelPickerSpaceBackground: ( + n: StoreData["channelPickerSpaceBackground"] + ) => persist((old) => ({ ...old, channelPickerSpaceBackground: n })), setCompareBackground: (n: StoreData["compareBackground"]) => persist((old) => ({ ...old, compareBackground: n })), + setCompareBackgroundSpace: (n: StoreData["compareBackgroundSpace"]) => + persist((old) => ({ ...old, compareBackgroundSpace: n })), }; }