forked from oliverschwendener/ueli
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add color converter extension (oliverschwendener#1146)
* feat: added color converter extension * feat(ColorConverter): added CMYK option * Removed cmyk again * Simplified code * Added tests * Use correct terms * Fix stuff
- Loading branch information
1 parent
0af5972
commit d319c16
Showing
16 changed files
with
412 additions
and
2 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,6 +29,7 @@ | |
}, | ||
"devDependencies": { | ||
"@types/better-sqlite3": "^7.6.11", | ||
"@types/color": "^3.0.6", | ||
"@types/node": "^20.8.6", | ||
"@types/react": "^18.2.48", | ||
"@types/react-dom": "^18.2.18", | ||
|
@@ -55,6 +56,7 @@ | |
"@fluentui/react-components": "^9.54.4", | ||
"@fluentui/react-icons": "^2.0.249", | ||
"better-sqlite3": "^11.1.2", | ||
"color": "^4.2.3", | ||
"fuse.js": "^7.0.0", | ||
"fuzzysort": "^3.0.1", | ||
"i18next": "^23.8.1", | ||
|
@@ -66,5 +68,6 @@ | |
"react-router": "^6.21.3", | ||
"react-router-dom": "^6.21.3", | ||
"sharp": "^0.33.3" | ||
} | ||
}, | ||
"packageManager": "[email protected]+sha256.dae0f7e822c56b20979bb5965e3b73b8bdabb6b8b8ef121da6d857508599ca35" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export type ColorConversionResult = { | ||
format: string; | ||
value: string; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import type { ColorConversionResult } from "./ColorConversionResult"; | ||
|
||
export interface ColorConverter { | ||
convertFromString(value: string): ColorConversionResult[]; | ||
} |
105 changes: 105 additions & 0 deletions
105
src/main/Extensions/ColorConverter/ColorConverterExtension.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import type { AssetPathResolver } from "@Core/AssetPathResolver"; | ||
import { SettingsManager } from "@Core/SettingsManager"; | ||
import type { Translator } from "@Core/Translator"; | ||
import { describe, expect, it, vi } from "vitest"; | ||
import { ColorConversionResult } from "./ColorConversionResult"; | ||
import { ColorConverter } from "./ColorConverter"; | ||
import { ColorConverterExtension } from "./ColorConverterExtension"; | ||
|
||
describe(ColorConverterExtension, () => { | ||
describe(ColorConverterExtension.prototype.getSearchResultItems, () => { | ||
it("should return an empty array", async () => { | ||
const actual = await new ColorConverterExtension(null, null, null, null).getSearchResultItems(); | ||
expect(actual).toEqual([]); | ||
}); | ||
}); | ||
|
||
describe(ColorConverterExtension.prototype.getImage, () => { | ||
it("should return the correct image", async () => { | ||
const assetPathResolver = <AssetPathResolver>{ | ||
getExtensionAssetPath: vi.fn().mockReturnValue("color-converter.png"), | ||
getModuleAssetPath: () => null, | ||
}; | ||
|
||
const colorConverterExtension = new ColorConverterExtension(assetPathResolver, null, null, null); | ||
|
||
expect(colorConverterExtension.getImage()).toEqual({ url: "file://color-converter.png" }); | ||
|
||
expect(assetPathResolver.getExtensionAssetPath).toHaveBeenCalledWith( | ||
"ColorConverter", | ||
"color-converter.png", | ||
); | ||
}); | ||
}); | ||
|
||
describe(ColorConverterExtension.prototype.getSettingDefaultValue, () => { | ||
const colorConverterExtension = new ColorConverterExtension(null, null, null, null); | ||
|
||
it("should return undefined when passing a key that does not exist", () => | ||
expect(colorConverterExtension.getSettingDefaultValue("key")).toEqual(undefined)); | ||
|
||
it("should return the default formats when passing 'formats' as key", () => | ||
expect(colorConverterExtension.getSettingDefaultValue("formats")).toEqual(["HEX", "HLS", "RGB"])); | ||
}); | ||
|
||
describe(ColorConverterExtension.prototype.isSupported, () => | ||
it("should return true", () => | ||
expect(new ColorConverterExtension(null, null, null, null).isSupported()).toBe(true)), | ||
); | ||
|
||
describe(ColorConverterExtension.prototype.getInstantSearchResultItems, () => { | ||
it("should return search result items for the enabled formats", () => { | ||
const t = vi.fn().mockReturnValue("translated string"); | ||
|
||
const assetPathResolver = <AssetPathResolver>{ | ||
getExtensionAssetPath: vi.fn().mockReturnValue("color-converter.png"), | ||
getModuleAssetPath: () => null, | ||
}; | ||
|
||
const translator = <Translator>{ | ||
createT: vi.fn().mockReturnValue({ t }), | ||
}; | ||
|
||
const settingsManager = <SettingsManager>{ | ||
getValue: vi.fn().mockReturnValue(["HEX", "RGB"]), | ||
updateValue: null, | ||
}; | ||
|
||
const colorConverter = <ColorConverter>{ | ||
convertFromString: vi.fn().mockReturnValue(<ColorConversionResult[]>[ | ||
{ format: "HEX", value: "#FFFFFF" }, | ||
{ format: "HLS", value: "hsl(0, 0%, 100%)" }, | ||
{ format: "RGB", value: "rgb(255, 255, 255)" }, | ||
]), | ||
}; | ||
|
||
const colorConverterExtension = new ColorConverterExtension( | ||
assetPathResolver, | ||
settingsManager, | ||
translator, | ||
colorConverter, | ||
); | ||
|
||
const actual = colorConverterExtension.getInstantSearchResultItems("#fff"); | ||
|
||
expect(colorConverter.convertFromString).toHaveBeenCalledOnce(); | ||
expect(colorConverter.convertFromString).toHaveBeenCalledWith("#fff"); | ||
|
||
expect(actual.length).toBe(2); | ||
expect(actual.map((a) => a.name)).toEqual(["#FFFFFF", "rgb(255, 255, 255)"]); | ||
|
||
expect(actual.map(({ image }) => image)).toEqual([ | ||
{ url: "file://color-converter.png" }, | ||
{ url: "file://color-converter.png" }, | ||
]); | ||
}); | ||
}); | ||
|
||
describe(ColorConverterExtension.prototype.getI18nResources, () => | ||
it("should support en-US and de-CH", () => | ||
expect(Object.keys(new ColorConverterExtension(null, null, null, null).getI18nResources())).toEqual([ | ||
"en-US", | ||
"de-CH", | ||
])), | ||
); | ||
}); |
98 changes: 98 additions & 0 deletions
98
src/main/Extensions/ColorConverter/ColorConverterExtension.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import { SearchResultItemActionUtility, type SearchResultItem } from "@common/Core"; | ||
import type { Image } from "@common/Core/Image"; | ||
import type { Resources, Translations } from "@common/Core/Translator"; | ||
import type { AssetPathResolver } from "@Core/AssetPathResolver"; | ||
import type { Extension } from "@Core/Extension"; | ||
import type { SettingsManager } from "@Core/SettingsManager"; | ||
import type { Translator } from "@Core/Translator"; | ||
import type { ColorConverter } from "./ColorConverter"; | ||
|
||
export class ColorConverterExtension implements Extension { | ||
public readonly id = "ColorConverter"; | ||
|
||
public readonly name = "Color Converter"; | ||
|
||
public readonly nameTranslation = { | ||
key: "extensionName", | ||
namespace: "extension[ColorConverter]", | ||
}; | ||
|
||
public readonly author = { | ||
name: "Oliver Schwendener", | ||
githubUserName: "oliverschwendener", | ||
}; | ||
|
||
private readonly defaultSettings = { | ||
formats: ["HEX", "HLS", "RGB"], | ||
}; | ||
|
||
public constructor( | ||
private readonly assetPathResolver: AssetPathResolver, | ||
private readonly settingsManager: SettingsManager, | ||
private readonly translator: Translator, | ||
private readonly colorConverter: ColorConverter, | ||
) {} | ||
|
||
async getSearchResultItems(): Promise<SearchResultItem[]> { | ||
return []; | ||
} | ||
|
||
public isSupported(): boolean { | ||
return true; | ||
} | ||
|
||
public getSettingDefaultValue<T>(key: string): T { | ||
return this.defaultSettings[key] as T; | ||
} | ||
|
||
public getImage(): Image { | ||
return { | ||
url: `file://${this.assetPathResolver.getExtensionAssetPath(this.id, "color-converter.png")}`, | ||
}; | ||
} | ||
|
||
public getI18nResources(): Resources<Translations> { | ||
return { | ||
"en-US": { | ||
formats: "Color Formats", | ||
selectAColorFormat: "Select a color format", | ||
color: "{{ format }} Color", | ||
copyColorToClipboard: "Copy color to clipboard", | ||
extensionName: "Color Converter", | ||
}, | ||
"de-CH": { | ||
formats: "Farbformate", | ||
selectAColorFormat: "Wähle ein Farbformat", | ||
color: "{{ format }} Farbe", | ||
copyColorToClipboard: "Farbe in die Zwischenablage kopieren", | ||
extensionName: "Farbkonverter", | ||
}, | ||
}; | ||
} | ||
|
||
public getInstantSearchResultItems(searchTerm: string): SearchResultItem[] { | ||
const { t } = this.translator.createT(this.getI18nResources()); | ||
|
||
return this.colorConverter | ||
.convertFromString(searchTerm) | ||
.filter(({ format }) => this.getEnabledColorFormats().includes(format)) | ||
.map(({ format, value }) => ({ | ||
defaultAction: SearchResultItemActionUtility.createCopyToClipboardAction({ | ||
textToCopy: value, | ||
description: "Copy color to clipboard", | ||
descriptionTranslation: { | ||
key: "copyColorToClipboard", | ||
namespace: "extension[ColorConverter]", | ||
}, | ||
}), | ||
description: t("color", { format }), | ||
id: `color-${value}-${format}`, | ||
image: this.getImage(), | ||
name: value, | ||
})); | ||
} | ||
|
||
private getEnabledColorFormats(): string[] { | ||
return this.settingsManager.getValue(`extension[${this.id}].formats`, this.defaultSettings.formats); | ||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
src/main/Extensions/ColorConverter/ColorConverterExtensionModule.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import type { Dependencies } from "@Core/Dependencies"; | ||
import type { DependencyRegistry } from "@Core/DependencyRegistry"; | ||
import { describe, expect, it, vi } from "vitest"; | ||
import { ColorConverterExtension } from "./ColorConverterExtension"; | ||
import { ColorConverterExtensionModule } from "./ColorConverterExtensionModule"; | ||
import { QixColorConverter } from "./QixColorConverter"; | ||
|
||
describe(ColorConverterExtensionModule, () => { | ||
describe(ColorConverterExtensionModule.prototype.bootstrap, () => { | ||
it("should bootstrap the extension", () => { | ||
const dependencyRegistry = <DependencyRegistry<Dependencies>>{ | ||
get: vi.fn().mockReturnValue(null), | ||
register: null, | ||
}; | ||
|
||
expect(new ColorConverterExtensionModule().bootstrap(dependencyRegistry)).toEqual({ | ||
extension: new ColorConverterExtension(null, null, null, new QixColorConverter()), | ||
}); | ||
|
||
expect(dependencyRegistry.get).toHaveBeenCalledWith("AssetPathResolver"); | ||
expect(dependencyRegistry.get).toHaveBeenCalledWith("SettingsManager"); | ||
expect(dependencyRegistry.get).toHaveBeenCalledWith("Translator"); | ||
}); | ||
}); | ||
}); |
19 changes: 19 additions & 0 deletions
19
src/main/Extensions/ColorConverter/ColorConverterExtensionModule.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import type { Dependencies } from "@Core/Dependencies"; | ||
import type { DependencyRegistry } from "@Core/DependencyRegistry"; | ||
import type { ExtensionBootstrapResult } from "../ExtensionBootstrapResult"; | ||
import type { ExtensionModule } from "../ExtensionModule"; | ||
import { ColorConverterExtension } from "./ColorConverterExtension"; | ||
import { QixColorConverter } from "./QixColorConverter"; | ||
|
||
export class ColorConverterExtensionModule implements ExtensionModule { | ||
public bootstrap(dependencyRegistry: DependencyRegistry<Dependencies>): ExtensionBootstrapResult { | ||
return { | ||
extension: new ColorConverterExtension( | ||
dependencyRegistry.get("AssetPathResolver"), | ||
dependencyRegistry.get("SettingsManager"), | ||
dependencyRegistry.get("Translator"), | ||
new QixColorConverter(), | ||
), | ||
}; | ||
} | ||
} |
50 changes: 50 additions & 0 deletions
50
src/main/Extensions/ColorConverter/QixColorConverter.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { describe, expect, it } from "vitest"; | ||
import { ColorConversionResult } from "./ColorConversionResult"; | ||
import { QixColorConverter } from "./QixColorConverter"; | ||
|
||
describe(QixColorConverter, () => { | ||
describe(QixColorConverter.prototype.convertFromString, () => { | ||
it("should return an empty array when the input can't be parsed as a color", () => { | ||
expect(new QixColorConverter().convertFromString("")).toEqual([]); | ||
expect(new QixColorConverter().convertFromString(" ")).toEqual([]); | ||
expect(new QixColorConverter().convertFromString("1234")).toEqual([]); | ||
expect(new QixColorConverter().convertFromString("invalid input")).toEqual([]); | ||
expect(new QixColorConverter().convertFromString("#")).toEqual([]); | ||
expect(new QixColorConverter().convertFromString("#ff")).toEqual([]); | ||
expect(new QixColorConverter().convertFromString("#ffg")).toEqual([]); | ||
expect(new QixColorConverter().convertFromString("rgb")).toEqual([]); | ||
expect(new QixColorConverter().convertFromString("rgb()")).toEqual([]); | ||
expect(new QixColorConverter().convertFromString("rgb(1)")).toEqual([]); | ||
expect(new QixColorConverter().convertFromString("rgb(1,2)")).toEqual([]); | ||
expect(new QixColorConverter().convertFromString("rgb(1,2,4,5,6)")).toEqual([]); | ||
}); | ||
|
||
it("should be able to parse hex colors", () => { | ||
expect(new QixColorConverter().convertFromString("#fff")).toEqual(<ColorConversionResult[]>[ | ||
{ format: "HEX", value: "#FFFFFF" }, | ||
{ format: "HLS", value: "hsl(0, 0%, 100%)" }, | ||
{ format: "RGB", value: "rgb(255, 255, 255)" }, | ||
]); | ||
|
||
expect(new QixColorConverter().convertFromString("#ffffff")).toEqual(<ColorConversionResult[]>[ | ||
{ format: "HEX", value: "#FFFFFF" }, | ||
{ format: "HLS", value: "hsl(0, 0%, 100%)" }, | ||
{ format: "RGB", value: "rgb(255, 255, 255)" }, | ||
]); | ||
}); | ||
|
||
it("should be able to parse rgb colors", () => { | ||
expect(new QixColorConverter().convertFromString("rgb(255,255,255)")).toEqual(<ColorConversionResult[]>[ | ||
{ format: "HEX", value: "#FFFFFF" }, | ||
{ format: "HLS", value: "hsl(0, 0%, 100%)" }, | ||
{ format: "RGB", value: "rgb(255, 255, 255)" }, | ||
]); | ||
|
||
expect(new QixColorConverter().convertFromString("rgba(255,255,255,1)")).toEqual(<ColorConversionResult[]>[ | ||
{ format: "HEX", value: "#FFFFFF" }, | ||
{ format: "HLS", value: "hsl(0, 0%, 100%)" }, | ||
{ format: "RGB", value: "rgb(255, 255, 255)" }, | ||
]); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.