Skip to content

Commit

Permalink
Added color converter feature (Fixes oliverschwendener#133)
Browse files Browse the repository at this point in the history
  • Loading branch information
oliverschwendener committed Aug 15, 2019
1 parent db64de2 commit c817415
Show file tree
Hide file tree
Showing 28 changed files with 438 additions and 19 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,14 @@
"package:publish": "./node_modules/.bin/electron-builder --config electron-builder-config.yml --publish onTag"
},
"devDependencies": {
"@types/color": "^3.0.0",
"@types/jest": "^24.0.17",
"@types/lodash": "^4.14.136",
"@types/mathjs": "^5.0.1",
"@types/node-powershell": "^3.1.0",
"@types/vue-color": "^2.4.2",
"axios": "^0.19.0",
"color": "^3.1.2",
"electron": "^5.0.9",
"electron-builder": "^21.2.0",
"electron-store": "^3.2.0",
Expand Down
8 changes: 8 additions & 0 deletions src/common/config/color-converter-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export interface ColorConverterOptions {
isEnabled: boolean;
hexEnabled: boolean;
rgbEnabled: boolean;
rgbaEnabled: boolean;
hslEnabled: boolean;
showColorPreview: boolean;
}
10 changes: 10 additions & 0 deletions src/common/config/default-color-converter-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ColorConverterOptions } from "./color-converter-options";

export const defaultColorConverterOptions: ColorConverterOptions = {
hexEnabled: true,
hslEnabled: true,
isEnabled: true,
rgbEnabled: true,
rgbaEnabled: true,
showColorPreview: false,
};
2 changes: 2 additions & 0 deletions src/common/config/default-user-config-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ import { defaultCommandlineOptions } from "./default-commandline-options";
import { defaultOperatingSystemSettingsOptions } from "./default-operating-system-settings-options";
import { defaultSimpleFolderSearchOptions } from "./default-simple-folder-search-options";
import { defaultUwpSearchOptions } from "./default-uwp-search-options";
import { defaultColorConverterOptions } from "./default-color-converter-options";

export const defaultUserConfigOptions: UserConfigOptions = {
appearanceOptions: defaultAppearanceOptions,
applicationSearchOptions: defaultApplicationSearchOptions,
calculatorOptions: defaultCalculatorOptions,
colorConverterOptions: defaultColorConverterOptions,
colorThemeOptions: defaultColorThemeOptions,
commandlineOptions: defaultCommandlineOptions,
currencyConverterOptions: defaultCurrencyConverterOptions,
Expand Down
2 changes: 2 additions & 0 deletions src/common/config/user-config-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { CommandlineOptions } from "./commandline-options";
import { OperatingSystemSettingsOptions } from "./operating-system-settings-options";
import { SimpleFolderSearchOptions } from "./simple-folder-search-options";
import { UwpSearchOptions } from "./uwp-search-options";
import { ColorConverterOptions } from "./color-converter-options";

export interface UserConfigOptions {
appearanceOptions: AppearanceOptions;
Expand All @@ -41,5 +42,6 @@ export interface UserConfigOptions {
workflowOptions: WorkflowOptions;
commandlineOptions: CommandlineOptions;
simpleFolderSearchOptions: SimpleFolderSearchOptions;
colorConverterOptions: ColorConverterOptions;
uwpSearchOptions: UwpSearchOptions;
}
5 changes: 5 additions & 0 deletions src/common/icon/default-icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import { Icon } from "./icon";

import { IconType } from "./icon-type";

export const defaultColorConverterIcon: Icon = {
parameter: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path fill="#d7dfe2" d="M6,43V33h37c0.552,0,1,0.448,1,1v8c0,0.552-0.448,1-1,1H6z"></path><path fill="#b0bec5" d="M33.162 33L29.228 33 12.289 43 16.223 43z"></path><path fill="#8c9eff" d="M36,43V33h7c0.552,0,1,0.448,1,1v8c0,0.552-0.448,1-1,1H36z"></path><path fill="#5c6bc0" d="M25 34H35V42H25z" transform="rotate(90 30 38)"></path><path fill="#3f51b5" d="M26 37.228L33.162 33 29.228 33 26 34.906z"></path><path fill="#303f9f" d="M15 34H25V42H15z" transform="rotate(90 20 38)"></path><path fill="#283593" d="M24 36.086L16 40.809 16 43 16.223 43 24 38.409z"></path><path fill="#d7dfe2" d="M41.861,24.381l-4.067-6.889c-0.281-0.476-0.894-0.633-1.37-0.353L6,35.1v3.284L8.725,43h3.564 l29.219-17.25C41.984,25.47,42.142,24.856,41.861,24.381z"></path><path fill="#b0bec5" d="M28.875 21.596L25.325 23.691 14.892 41.464 18.441 39.368z"></path><path fill="#81c784" d="M35.48,29.309l-5.084-8.611l6.028-3.559c0.476-0.281,1.089-0.123,1.37,0.353l4.067,6.889 c0.281,0.476,0.123,1.089-0.353,1.37L35.48,29.309z"></path><path fill="#43a047" d="M22.772 24.054H32.772V32.054H22.772z" transform="rotate(59.443 27.773 28.055)"></path><path fill="#2e7d32" d="M14.16 29.137H24.16V37.137H14.16z" transform="rotate(59.443 19.161 33.139)"></path><path fill="#2e7d32" d="M14.16 29.137H24.16V37.137H14.16z" transform="rotate(59.443 19.161 33.139)"></path><path fill="#d7dfe2" d="M31.506,11.188l-6.899-4.05c-0.476-0.28-1.089-0.12-1.369,0.356L6,36.858v3.421L10.636,43h3.354 l17.872-30.443C32.142,12.08,31.982,11.468,31.506,11.188z"></path><path fill="#1b5e20" d="M21.787 29.719L17.155 37.609 18.258 39.477 18.441 39.368 22.95 31.688z"></path><path fill="#388e3c" d="M28.74 21.826L28.674 21.714 25.325 23.691 22.945 27.746 24.108 29.716z"></path><path fill="#ffd740" d="M28.318,18.593l-8.624-5.063l3.544-6.037c0.28-0.476,0.892-0.636,1.369-0.356l6.899,4.05 c0.476,0.28,0.636,0.892,0.356,1.369L28.318,18.593z"></path><path fill="#ffc400" d="M15.969 17.236H25.969V25.236H15.969z" transform="rotate(30.416 20.968 21.235)"></path><path fill="#ff9800" d="M10.906 25.86H20.906V33.86H10.906z" transform="rotate(30.416 15.905 29.858)"></path><path fill="#d7dfe2" d="M16,43H6V6c0-0.552,0.448-1,1-1h8c0.552,0,1,0.448,1,1V43z"></path><path fill="#ff8a80" d="M16,13H6V6c0-0.552,0.448-1,1-1h8c0.552,0,1,0.448,1,1V13z"></path><path fill="#ff5252" d="M6 15H16V23H6z"></path><path fill="#d50000" d="M6 25H16V33H6z"></path><path fill="#455a64" d="M11 37A1 1 0 1 0 11 39A1 1 0 1 0 11 37Z"></path><path fill="#90a4ae" d="M16 35.652L16 40.809 18.258 39.477z"></path><path fill="#b0bec5" d="M16 34.553L16 39.576 18.193 35.841zM16 22.957L16 25.277 18 26.451 18 24.132z"></path><path fill="#ffab00" d="M16 22.957L18 24.132 18 16.417 16 19.824z"></path><path fill="#f57c00" d="M16 34.553L18 35.727 18 26.451 16 25.277z"></path></svg>`,
type: IconType.SVG,
};

export const defaultErrorIcon: Icon = {
parameter: `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 48 48" version="1.1"><g id="surface1"><path style=" fill:#F44336;" d="M 42 37 C 42 39.761719 39.761719 42 37 42 L 11 42 C 8.238281 42 6 39.761719 6 37 L 6 11 C 6 8.238281 8.238281 6 11 6 L 37 6 C 39.761719 6 42 8.238281 42 11 Z "></path><path style=" fill:#FFEBEE;" d="M 16.828125 34 L 14 31.171875 L 31 14.171875 L 33.828125 17 Z "></path><path style=" fill:#FFEBEE;" d="M 34.023438 30.945313 L 31.195313 33.773438 L 14.105469 16.679688 L 16.933594 13.851563 Z "></path></g></svg>`,
type: IconType.SVG,
Expand Down
23 changes: 20 additions & 3 deletions src/common/icon/icon-helpers.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { IconType } from "./icon-type";
import { Icon } from "./icon";
import { isValidColorCode } from "../../main/plugins/color-converter-plugin/color-converter-helpers";
import { TranslationSet } from "../translation/translation-set";

export function isValidIconType(iconType: IconType): boolean {
return iconType !== undefined
&& Object.values(IconType).find((i) => i === iconType) !== undefined;
&& Object.values(IconType).some((i) => i === iconType);
}

export function isValidIcon(icon: Icon): boolean {
Expand All @@ -16,13 +18,28 @@ export function isValidIcon(icon: Icon): boolean {

export function isValidIconParameter(icon: Icon): boolean {
if (icon.type === IconType.SVG) {
return icon.parameter.startsWith("<svg")
&& icon.parameter.endsWith("</svg>");
return icon.parameter.trim().startsWith("<svg")
&& icon.parameter.trim().endsWith("</svg>");
}

if (icon.type === IconType.URL) {
return true;
}

if (icon.type === IconType.Color) {
return isValidColorCode(icon.parameter);
}

return false;
}

export function getIconTypeLabel(iconType: IconType, translations: TranslationSet): string {
switch (iconType) {
case IconType.Color:
return translations.iconTypeColor;
case IconType.SVG:
return "SVG";
case IconType.URL:
return "URL";
}
}
1 change: 1 addition & 0 deletions src/common/icon/icon-type.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export enum IconType {
URL = "URL",
SVG = "SVG",
Color = "color",
}
6 changes: 6 additions & 0 deletions src/common/translation/english-translation-set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ export const englishTranslationSet: TranslationSet = {
shortcutSettingsAddShortcut: "Add shortcut",
shortcutSettingsEditModalImageUrl: "Image URL",
shortcutSettingsEditModalSvgString: "SVG string",
shortcutSettingsEditModalColor: "Color",
shortcutSettingsEditModalGoogleWebsite: "Google website",
shortcutSettingsEditModalDownloadsFolder: "Downloads folder",
shortcutSettingsEditModalCommand: "Command",
Expand Down Expand Up @@ -297,13 +298,18 @@ export const englishTranslationSet: TranslationSet = {

uwpSettingsDescription: "This plugin enables you to find preinstalled UWP apps.",

colorConverter: "Color converter",
colorConverterDescription: "This plugin enables you to quickly convert colors into different formats.",
colorConverterShowColorPreview: "Show color preview",

cancel: "Cancel",
save: "Save",
add: "Add",
remove: "Remove",
edit: "Edit",
forExample: "For example",
iconType: "Icon type",
iconTypeColor: "Color",
showFullFilePath: "Show full file path",
yes: "Yes",
no: "No",
Expand Down
6 changes: 6 additions & 0 deletions src/common/translation/german-translation-set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ export const germanTranslationSet: TranslationSet = {
shortcutSettingsAddShortcut: "Shortcut hinzufügen",
shortcutSettingsEditModalImageUrl: "Bild URL",
shortcutSettingsEditModalSvgString: "SVG Zeichenkette",
shortcutSettingsEditModalColor: "Farbe",
shortcutSettingsEditModalGoogleWebsite: "Google Webseite",
shortcutSettingsEditModalDownloadsFolder: "Downloads Ordner",
shortcutSettingsEditModalCommand: "Befehl",
Expand Down Expand Up @@ -297,13 +298,18 @@ export const germanTranslationSet: TranslationSet = {

uwpSettingsDescription: "Dieses Plugin erlaubt es dir vorinstallierte UWP-Apps zu finden.",

colorConverter: "Farbkonverter",
colorConverterDescription: "Dieses Plugin erlaubt es dir Farben in verschiedene Formate zu konvertieren.",
colorConverterShowColorPreview: "Farbvorschau anzeigen",

cancel: "Abbrechen",
save: "Speichern",
add: "Hinzufügen",
remove: "Entfernen",
edit: "Bearbeiten",
forExample: "Zum Beispiel",
iconType: "Icontyp",
iconTypeColor: "Farbe",
showFullFilePath: "Vollständiger Dateipfad anzeigen",
yes: "Ja",
no: "Nein",
Expand Down
6 changes: 6 additions & 0 deletions src/common/translation/translation-set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ export interface TranslationSet {
shortcutSettingsAddShortcut: string;
shortcutSettingsEditModalImageUrl: string;
shortcutSettingsEditModalSvgString: string;
shortcutSettingsEditModalColor: string;
shortcutSettingsEditModalGoogleWebsite: string;
shortcutSettingsEditModalDownloadsFolder: string;
shortcutSettingsEditModalCommand: string;
Expand Down Expand Up @@ -294,13 +295,18 @@ export interface TranslationSet {

uwpSettingsDescription: string;

colorConverter: string;
colorConverterDescription: string;
colorConverterShowColorPreview: string;

cancel: string;
save: string;
add: string;
remove: string;
edit: string;
forExample: string;
iconType: string;
iconTypeColor: string;
showFullFilePath: string;
yes: string;
no: string;
Expand Down
1 change: 1 addition & 0 deletions src/main/plugin-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ export enum PluginType {
Commandline = "commandline-plugin",
SimpleFolderSearch = "simple-folder-search-plugin",
Uwp = "uwp",
ColorConverter = "color-converter",
Test = "test-plugin",
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { isValidColorCode } from "./color-converter-helpers";

describe(isValidColorCode.name, () => {
it("should return true if value is a valid hex code", () => {
const validColorCodes = [
"#fff",
"#ffffff",
"#FFF",
"#FFFFFF",
"rgb(255,255,255)",
"rgba(255,255,255,0)",
"rgb(1,2,3)",
"rgba(0,0,0,1)",
" #fff",
"#fff ",
];

validColorCodes.forEach((validColorCode) => {
const actual = isValidColorCode(validColorCode);
expect(actual).toBe(true);
});
});

it("should return false if value is an invalid hex code", () => {
const invalidColorCodes = [
"ffffff",
"fff",
"some string",
"",
"undefined",
"null",
"#",
"rgb()",
"rgba()",
"blue",
"black",
"yellow",
];

invalidColorCodes.forEach((invalidColorCode) => {
const actual = isValidColorCode(invalidColorCode);
expect(actual).toBe(false);
});
});
});
14 changes: 14 additions & 0 deletions src/main/plugins/color-converter-plugin/color-converter-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as color from "color";
import { StringHelpers } from "../../../common/helpers/string-helpers";

export function isValidColorCode(value: string): boolean {
value = StringHelpers.replaceWhitespace(value.trim(), "");
try {
color(value);
return value.startsWith("#")
|| value.startsWith("rgb(")
|| value.startsWith("rgba(");
} catch {
return false;
}
}
87 changes: 87 additions & 0 deletions src/main/plugins/color-converter-plugin/color-converter-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { ExecutionPlugin } from "../../execution-plugin";
import { PluginType } from "../../plugin-type";
import { ColorConverterOptions } from "../../../common/config/color-converter-options";
import { isValidColorCode } from "./color-converter-helpers";
import { SearchResultItem } from "../../../common/search-result-item";
import { TranslationSet } from "../../../common/translation/translation-set";
import { UserConfigOptions } from "../../../common/config/user-config-options";
import * as color from "color";
import { defaultColorConverterIcon } from "../../../common/icon/default-icons";
import { IconType } from "../../../common/icon/icon-type";
import { StringHelpers } from "../../../common/helpers/string-helpers";

export class ColorConverterPlugin implements ExecutionPlugin {
public pluginType: PluginType.ColorConverter;
private config: ColorConverterOptions;
private readonly clipboardCopier: (value: string) => Promise<void>;

constructor(config: ColorConverterOptions, clipboardCopier: (value: string) => Promise<void>) {
this.config = config;
this.clipboardCopier = clipboardCopier;
}

public isValidUserInput(userInput: string, fallback?: boolean | undefined): boolean {
return isValidColorCode(userInput);
}
public getSearchResults(userInput: string, fallback?: boolean | undefined): Promise<SearchResultItem[]> {
return new Promise((resolve, reject) => {
resolve(this.buildSearchResult(userInput));
});
}

public isEnabled(): boolean {
return this.config.isEnabled;
}

public execute(searchResultItem: SearchResultItem, privileged: boolean): Promise<void> {
return this.clipboardCopier(searchResultItem.executionArgument);
}

public updateConfig(updatedConfig: UserConfigOptions, translationSet: TranslationSet): Promise<void> {
return new Promise((resolve, reject) => {
this.config = updatedConfig.colorConverterOptions;
resolve();
});
}

private buildSearchResult(value: string): SearchResultItem[] {
const converted = color(StringHelpers.replaceWhitespace(value.trim(), ""));
const result: SearchResultItem[] = [];

if (this.config.hexEnabled) {
result.push(this.buildColorSearchResult("HEX", converted.hex().toString()));
}

if (this.config.rgbEnabled) {
result.push(this.buildColorSearchResult("RGB", converted.rgb().toString()));
}

if (this.config.rgbaEnabled) {
const rgbaString = `rgba(${converted.red()}, ${converted.green()}, ${converted.blue()}, ${converted.alpha()})`;
result.push(this.buildColorSearchResult("RGBA", rgbaString));
}

if (this.config.hslEnabled) {
result.push(this.buildColorSearchResult("HSL", converted.hsl().toString()));
}

return result;
}

private buildColorSearchResult(colorName: string, colorValue: string): SearchResultItem {
return {
description: colorName,
executionArgument: colorValue,
hideMainWindowAfterExecution: true,
icon: this.config.showColorPreview
? {
parameter: colorValue,
type: IconType.Color,
}
: defaultColorConverterIcon,
name: colorValue,
originPluginType: this.pluginType,
searchable: [],
};
}
}
10 changes: 4 additions & 6 deletions src/main/plugins/translation-plugin/translation-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { ExecutionPlugin } from "../../execution-plugin";
import { SearchResultItem } from "../../../common/search-result-item";
import { UserConfigOptions } from "../../../common/config/user-config-options";
import { PluginType } from "../../plugin-type";
import { clipboard } from "electron";
import { StringHelpers } from "../../../common/helpers/string-helpers";
import { defaultErrorIcon, defaultTranslatorIcon } from "../../../common/icon/default-icons";
import { LingueeTranslator } from "./linguee-translator";
Expand All @@ -12,16 +11,15 @@ export class TranslationPlugin implements ExecutionPlugin {
public readonly pluginType = PluginType.TranslationPlugin;
private config: TranslationOptions;
private delay: NodeJS.Timeout | number;
private readonly clipboardCopier: (value: string) => Promise<void>;

constructor(config: TranslationOptions) {
constructor(config: TranslationOptions, clipboardCopier: (value: string) => Promise<void>) {
this.config = config;
this.clipboardCopier = clipboardCopier;
}

public execute(searchResultItem: SearchResultItem): Promise<void> {
return new Promise((resolve) => {
clipboard.writeText(searchResultItem.executionArgument);
resolve();
});
return this.clipboardCopier(searchResultItem.executionArgument);
}

public getSearchResults(userInput: string): Promise<SearchResultItem[]> {
Expand Down
Loading

0 comments on commit c817415

Please sign in to comment.