Skip to content

Commit

Permalink
feat(CurrencyConversion): added extension
Browse files Browse the repository at this point in the history
  • Loading branch information
oliverschwendener committed Feb 20, 2024
1 parent efb6879 commit f561471
Show file tree
Hide file tree
Showing 8 changed files with 896 additions and 0 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
148 changes: 148 additions & 0 deletions src/main/Extensions/CurrencyConversion/CurrencyConversion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import type { AssetPathResolver } from "@Core/AssetPathResolver";
import type { Extension } from "@Core/Extension";
import type { SettingsManager } from "@Core/SettingsManager";
import { SearchResultItemActionUtility, type SearchResultItem } from "@common/Core";
import { getExtensionSettingKey, type Translations } from "@common/Core/Extension";
import type { Image } from "@common/Core/Image";
import type { Net } from "electron";

export class CurrencyConversion implements Extension {
private static readonly translationNamespace = "extension[CurrencyConversion]";

public readonly id = "CurrencyConversion";
public readonly name = "Currency Conversion";

public readonly nameTranslation = {
key: "extensionName",
namespace: CurrencyConversion.translationNamespace,
};

public readonly author = {
name: "Oliver Schwendener",
githubUserName: "oliverschwendener",
};

private readonly defaultSettings = {
currencies: ["usd", "chf", "eur"],
};

private readonly rates: Record<string, Record<string, number>>;

public constructor(
private readonly settingsManager: SettingsManager,
private readonly net: Net,
private readonly assetPathResolver: AssetPathResolver,
) {
this.rates = {};
}

public getInstantSearchResultItems(searchTerm: string): SearchResultItem[] {
const parts = searchTerm.trim().split(" ");

const validators = [
() => parts.length === 4,
() => !isNaN(Number(parts[0])),
() => Object.keys(this.rates).includes(parts[1]),
() => ["in", "to"].includes(parts[2].toLowerCase()),
() => Object.keys(this.rates[parts[1]]).includes(parts[3]),
];

for (const validator of validators) {
if (!validator()) {
return [];
}
}

const conversionResult = this.convert({
value: Number(parts[0]),
base: parts[1],
target: parts[3],
});

return [
{
defaultAction: SearchResultItemActionUtility.createCopyToClipboardAction({
textToCopy: conversionResult.toFixed(2),
description: "Currency Conversion",
descriptionTranslation: {
key: "copyToClipboard",
namespace: CurrencyConversion.translationNamespace,
},
}),
description: "Currency Conversion",
descriptionTranslation: {
key: "currencyConversion",
namespace: CurrencyConversion.translationNamespace,
},
id: `currency-conversion:instant-result`,
image: this.getImage(),
name: `${conversionResult.toFixed(2)} ${parts[3].toUpperCase()}`,
},
];
}

public async getSearchResultItems(): Promise<SearchResultItem[]> {
await this.setRates();
return [];
}

public isSupported(): boolean {
return true;
}

public getSettingDefaultValue<T>(key: string): T {
return this.defaultSettings[key];
}

public getImage(): Image {
return {
url: `file://${this.assetPathResolver.getExtensionAssetPath(this.id, "currency-conversion.png")}`,
};
}

public getTranslations(): Translations {
return {
"en-US": {
extensionName: "Currency Conversion",
currencies: "Currencies",
selectCurrencies: "Select currencies",
copyToClipboard: "Copy to clipboard",
currencyConversion: "Currency Conversion",
},
"de-CH": {
extensionName: "Währungsumrechnung",
currencies: "Währungen",
selectCurrencies: "Währungen wählen",
copyToClipboard: "In Zwischenablage kopieren",
currencyConversion: "Währungsumrechnung",
},
};
}

public getSettingKeysTriggeringRescan(): string[] {
return [getExtensionSettingKey(this.id, "currencies")];
}

private convert({ value, base, target }: { value: number; base: string; target: string }): number {
return value * this.rates[base][target];
}

private async setRates(): Promise<void> {
const currencies = this.settingsManager.getValue(
getExtensionSettingKey(this.id, "currencies"),
this.defaultSettings.currencies,
);

await Promise.allSettled(currencies.map((currency) => this.setRate(currency)));
}

private async setRate(currency: string): Promise<void> {
const response = await this.net.fetch(
`https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api@1/latest/currencies/${currency}.json`,
);

const responseJson = await response.json();

this.rates[currency] = responseJson[currency];
}
}
17 changes: 17 additions & 0 deletions src/main/Extensions/CurrencyConversion/CurrencyConversionModule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { Dependencies } from "@Core/Dependencies";
import type { DependencyRegistry } from "@Core/DependencyRegistry";
import type { ExtensionBootstrapResult } from "../ExtensionBootstrapResult";
import type { ExtensionModule } from "../ExtensionModule";
import { CurrencyConversion } from "./CurrencyConversion";

export class CurrencyConversionModule implements ExtensionModule {
public bootstrap(dependencyRegistry: DependencyRegistry<Dependencies>): ExtensionBootstrapResult {
return {
extension: new CurrencyConversion(
dependencyRegistry.get("SettingsManager"),
dependencyRegistry.get("Net"),
dependencyRegistry.get("AssetPathResolver"),
),
};
}
}
2 changes: 2 additions & 0 deletions src/main/Extensions/ExtensionLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { AppearanceSwitcherModule } from "./AppearanceSwitcher";
import { ApplicationSearchModule } from "./ApplicationSearch";
import { BrowserBookmarksModule } from "./BrowserBookmarks";
import { CalculatorModule } from "./Calculator";
import { CurrencyConversionModule } from "./CurrencyConversion/CurrencyConversionModule";
import { DeeplTranslatorModule } from "./DeeplTranslator";
import { ExtensionModule } from "./ExtensionModule";
import { FileSearchModule } from "./FileSearch/FileSearchModule";
Expand All @@ -19,6 +20,7 @@ export class ExtensionLoader {
new ApplicationSearchModule(),
new BrowserBookmarksModule(),
new CalculatorModule(),
new CurrencyConversionModule(),
new DeeplTranslatorModule(),
new FileSearchModule(),
new ShortcutsModule(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useExtensionSetting } from "@Core/Hooks";
import { Section } from "@Core/Settings/Section";
import { SectionList } from "@Core/Settings/SectionList";
import { Dropdown, Field, Option } from "@fluentui/react-components";
import { useTranslation } from "react-i18next";
import { availableCurrencies } from "./availableCurrencies";

export const CurrencyConversionSettings = () => {
const { t } = useTranslation();
const ns = "extension[CurrencyConversion]";

const { value: currencies, updateValue: setCurrencies } = useExtensionSetting<string[]>({
extensionId: "CurrencyConversion",
key: "currencies",
});

return (
<SectionList>
<Section>
<Field label={t("currencies", { ns })}>
<Dropdown
selectedOptions={currencies}
value={currencies.map((c) => c.toUpperCase()).join(", ")}
placeholder={t("selectCurrencies", { ns })}
multiselect
onOptionSelect={(_, { selectedOptions }) => setCurrencies(selectedOptions)}
>
{Object.keys(availableCurrencies).map((availableCurrency) => (
<Option key={availableCurrency} value={availableCurrency}>
{`${availableCurrency.toUpperCase()} (${availableCurrencies[availableCurrency]})`}
</Option>
))}
</Dropdown>
</Field>
</Section>
</SectionList>
);
};
Loading

0 comments on commit f561471

Please sign in to comment.