Language changes
This commit is contained in:
parent
b7ff7d1ada
commit
74fe885fd0
14 changed files with 288 additions and 182 deletions
|
@ -1 +1,5 @@
|
|||
{ "useTabs": true, "semi": true }
|
||||
{
|
||||
"useTabs": true,
|
||||
"semi": true,
|
||||
"svelteSortOrder": "scripts-markup-styles"
|
||||
}
|
||||
|
|
|
@ -7,14 +7,35 @@ import content_script from "../frontend/content_script/content_script.ts?file";
|
|||
let com = new Communicator();
|
||||
|
||||
const scraper = new GTranslateScraper();
|
||||
com.translateCallback = (toTranslate, sender) =>
|
||||
scraper.translate(toTranslate, sender.tab?.id);
|
||||
com.translateCallback = async (toTranslate, sender) =>
|
||||
scraper.translate(toTranslate, await getCurrentLanguages(), sender.tab?.id);
|
||||
|
||||
com.getLanguagesCallback = () => scraper.languages;
|
||||
|
||||
const db = new Flashcards();
|
||||
com.addFlashcardCallback = (c) => db.addFlashcard(c);
|
||||
com.removeFlashcardCallback = (c) => db.removeFlashcard(c);
|
||||
|
||||
const getCurrentLanguages = async () => {
|
||||
const langs = await scraper.languages;
|
||||
let srcLangCode =
|
||||
(await browser.storage.local.get("srcLang"))["srcLang"] ?? "de";
|
||||
let dstLangCode =
|
||||
(await browser.storage.local.get("dstLang"))["dstLang"] ?? "en";
|
||||
|
||||
const srcLang = langs.filter((l) => l.code == srcLangCode).pop()!;
|
||||
const dstLang = langs.filter((l) => l.code == dstLangCode).pop()!;
|
||||
return { srcLang: srcLang, dstLang: dstLang };
|
||||
};
|
||||
|
||||
const setCurrentLanguages = async (l: Partial<LanguagePair>) => {
|
||||
if (l.srcLang) await browser.storage.local.set({ srcLang: l.srcLang.code });
|
||||
if (l.dstLang) await browser.storage.local.set({ dstLang: l.dstLang.code });
|
||||
};
|
||||
|
||||
com.getCurrentLanguagesCallback = getCurrentLanguages;
|
||||
com.setCurrentLanguagesCallback = setCurrentLanguages;
|
||||
|
||||
const getTabID = async (s: Runtime.MessageSender): Promise<number> => {
|
||||
if (s.tab?.id) return s.tab.id;
|
||||
let tabs = await browser.tabs.query({
|
||||
|
|
|
@ -1,29 +1,33 @@
|
|||
import { newPromise } from "../utils";
|
||||
|
||||
export class Flashcards {
|
||||
db!: IDBDatabase;
|
||||
db: Promise<IDBDatabase>;
|
||||
|
||||
constructor() {
|
||||
let [promise, resolve, reject] = newPromise<IDBDatabase>();
|
||||
this.db = promise;
|
||||
|
||||
const req = indexedDB.open("FlashCardDB", 1);
|
||||
req.onupgradeneeded = () => {
|
||||
let objectStore = req.result.createObjectStore("flashcards", {
|
||||
keyPath: "id",
|
||||
autoIncrement: true,
|
||||
});
|
||||
objectStore.createIndex("src", "src", { unique: true });
|
||||
objectStore.createIndex("src", "src");
|
||||
objectStore.createIndex("result", "result");
|
||||
objectStore.createIndex("dateAdded", "dateAdded");
|
||||
objectStore.createIndex("exported", "exported");
|
||||
objectStore.createIndex("srcLang", "languages.srcLang.code");
|
||||
objectStore.createIndex("dstLang", "languages.dstLang.code");
|
||||
};
|
||||
req.onsuccess = () => {
|
||||
this.db = req.result;
|
||||
};
|
||||
req.onerror = () => {
|
||||
throw new Error(
|
||||
req.onsuccess = () => resolve(req.result);
|
||||
req.onerror = () =>
|
||||
reject(
|
||||
`Error initializing the database backend, ${req.error}. This shouldn't ever happen`
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
addFlashcard(t: Translation | Flashcard): Promise<Flashcard> {
|
||||
async addFlashcard(t: Translation | Flashcard): Promise<Flashcard> {
|
||||
let card: Flashcard;
|
||||
if ("dateAdded" in t && "exported" in t) card = t;
|
||||
else {
|
||||
|
@ -34,41 +38,41 @@ export class Flashcards {
|
|||
};
|
||||
}
|
||||
|
||||
let req = this.db
|
||||
let req = (await this.db)
|
||||
.transaction(["flashcards"], "readwrite")
|
||||
.objectStore("flashcards")
|
||||
.add(card);
|
||||
return new Promise((resolve, reject) => {
|
||||
let [promise, resolve, reject] = newPromise<Flashcard>();
|
||||
req.onsuccess = () => {
|
||||
card.id = req.result;
|
||||
resolve(card);
|
||||
};
|
||||
req.onerror = () => reject(req.error);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
removeFlashcard(card: Flashcard): Promise<void> {
|
||||
if (card.id) {
|
||||
let req = this.db
|
||||
async removeFlashcard(card: Flashcard): Promise<void> {
|
||||
if (!card.id) return Promise.reject("Undefined ID");
|
||||
let req = (await this.db)
|
||||
.transaction(["flashcards"], "readwrite")
|
||||
.objectStore("flashcards")
|
||||
.delete(card.id);
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
let [promise, resolve, reject] = newPromise<void>();
|
||||
req.onsuccess = () => resolve();
|
||||
req.onerror = () => reject(req.error);
|
||||
});
|
||||
} else return Promise.reject("Undefined ID");
|
||||
return promise;
|
||||
}
|
||||
|
||||
async getAllCards(): Promise<Array<Flashcard>> {
|
||||
let req = this.db
|
||||
let req = (await this.db)
|
||||
.transaction(["flashcards"], "readonly")
|
||||
.objectStore("flashcards")
|
||||
.getAll();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let [promise, resolve, reject] = newPromise<Flashcard[]>();
|
||||
req.onsuccess = () => resolve(req.result);
|
||||
req.onerror = () => reject(req.error);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,11 +4,11 @@ export enum MessageKinds {
|
|||
translationFinished = "translation",
|
||||
languageList = "languages",
|
||||
}
|
||||
interface TranslationMessage {
|
||||
export interface TranslationMessage {
|
||||
messageKind: MessageKinds.translationFinished;
|
||||
translation: Translation;
|
||||
}
|
||||
interface LanguageListMessage {
|
||||
export interface LanguageListMessage {
|
||||
messageKind: MessageKinds.languageList;
|
||||
languages: Array<Language>;
|
||||
}
|
||||
|
@ -25,11 +25,17 @@ export type Message = TranslationMessage | LanguageListMessage;
|
|||
const sendMessage = (m: Message) => conn.postMessage(m);
|
||||
|
||||
const src = <HTMLTextAreaElement>document.querySelector("textarea#source");
|
||||
|
||||
const result = <HTMLDivElement>(
|
||||
document.querySelector("div.results-container")
|
||||
);
|
||||
|
||||
const [srcLang, dstLang] = Array.from(
|
||||
document.querySelectorAll(".jfk-button-checked")
|
||||
).map((d) => ({
|
||||
code: d.getAttribute("value")!,
|
||||
name: d.textContent!,
|
||||
}));
|
||||
|
||||
const isTranslating = () => result.classList.contains("translating");
|
||||
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
|
@ -50,7 +56,14 @@ export type Message = TranslationMessage | LanguageListMessage;
|
|||
const sendTranslation = () => {
|
||||
sendMessage({
|
||||
messageKind: MessageKinds.translationFinished,
|
||||
translation: { src: src.value, result: result.innerText.trim() },
|
||||
translation: {
|
||||
src: src.value,
|
||||
result: result.innerText.trim(),
|
||||
languages: {
|
||||
srcLang: srcLang,
|
||||
dstLang: dstLang,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -1,23 +1,26 @@
|
|||
import { browser, WebRequest, Runtime } from "webextension-polyfill-ts";
|
||||
import content_script from "./gtranslate_content_script.ts?file";
|
||||
import { Message, MessageKinds } from "./gtranslate_content_script";
|
||||
import { newPromise } from "../utils";
|
||||
|
||||
interface TranslationRequest {
|
||||
tabID?: number;
|
||||
resolveFn: (value: Translation) => void;
|
||||
rejectFn: (reason: String) => void;
|
||||
toTranslate: string;
|
||||
lang: LanguagePair;
|
||||
}
|
||||
|
||||
export class GTranslateScraper {
|
||||
private conn?: Runtime.Port;
|
||||
private current?: TranslationRequest;
|
||||
private queue: Array<TranslationRequest> = [];
|
||||
private iframe = <HTMLIFrameElement>document.createElement("iframe");
|
||||
languages: Promise<Array<Language>>;
|
||||
lang: LanguagePair | undefined;
|
||||
|
||||
constructor() {
|
||||
let languagesResolve: (value: Array<Language>) => void;
|
||||
this.languages = new Promise((resolv) => (languagesResolve = resolv));
|
||||
[this.languages, languagesResolve] = newPromise<Array<Language>>();
|
||||
|
||||
browser.runtime.onConnect.addListener((p) => {
|
||||
if (p.name != "gtranslate_scraper_conn") return;
|
||||
|
@ -33,6 +36,30 @@ export class GTranslateScraper {
|
|||
}
|
||||
});
|
||||
});
|
||||
this.loadIframe("de", "en"); //Sample languages so we can get the language list
|
||||
}
|
||||
|
||||
private loadIframe(srcLangCode: string, dstLangCode: string) {
|
||||
if (!document.body.contains(this.iframe))
|
||||
document.body.appendChild(this.iframe);
|
||||
this.conn = undefined;
|
||||
|
||||
//Registers a temp content script, because we cannot inject scripts into iframes created on the background html page, because they have no tabId
|
||||
browser.contentScripts
|
||||
.register({
|
||||
matches: ["<all_urls>"],
|
||||
allFrames: true,
|
||||
js: [
|
||||
{
|
||||
file: content_script,
|
||||
},
|
||||
],
|
||||
runAt: "document_end",
|
||||
})
|
||||
.then((r) => {
|
||||
this.iframe.addEventListener("load", r.unregister, { once: true });
|
||||
this.iframe.src = `https://translate.google.com/?op=translate&sl=${srcLangCode}&tl=${dstLangCode}`;
|
||||
});
|
||||
|
||||
//This allows us to create the google translate iframe by removing the x-frame-options header
|
||||
browser.webRequest.onHeadersReceived.addListener(
|
||||
|
@ -43,57 +70,47 @@ export class GTranslateScraper {
|
|||
},
|
||||
["blocking", "responseHeaders"]
|
||||
);
|
||||
let iframe = <HTMLIFrameElement>document.createElement("iframe");
|
||||
|
||||
//TODO make the language customizable
|
||||
iframe.src = "https://translate.google.com/?op=translate&sl=de&tl=en";
|
||||
|
||||
//Registers a temp content script, because we cannot inject scripts into iframes created on the background html page, because they have no tabId
|
||||
const js = {
|
||||
file: content_script,
|
||||
};
|
||||
browser.contentScripts
|
||||
.register({
|
||||
matches: ["<all_urls>"],
|
||||
allFrames: true,
|
||||
js: [js],
|
||||
runAt: "document_end",
|
||||
})
|
||||
.then((r) => {
|
||||
iframe.addEventListener("load", r.unregister);
|
||||
document.body.appendChild(iframe);
|
||||
});
|
||||
}
|
||||
private onTranslationRecieved(message: Translation) {
|
||||
if (this.current) this.current.resolveFn(message);
|
||||
this.current = undefined;
|
||||
|
||||
private onTranslationRecieved(trans: Translation) {
|
||||
const current = this.queue.pop();
|
||||
if (current) current.resolveFn(trans);
|
||||
this.processFromQueue(this.conn!);
|
||||
}
|
||||
|
||||
private processTranslationRequest(
|
||||
request: TranslationRequest,
|
||||
conn: Runtime.Port
|
||||
) {
|
||||
this.current = request;
|
||||
conn.postMessage(request.toTranslate);
|
||||
}
|
||||
|
||||
private processFromQueue(conn: Runtime.Port) {
|
||||
const next = this.queue.pop();
|
||||
if (next) this.processTranslationRequest(next, conn);
|
||||
const request = this.queue.slice(-1).pop();
|
||||
if (!request) return;
|
||||
|
||||
if (this.lang != request.lang) {
|
||||
this.lang = request.lang;
|
||||
|
||||
this.loadIframe(this.lang.srcLang.code, this.lang.dstLang.code);
|
||||
} else conn.postMessage(request.toTranslate);
|
||||
}
|
||||
|
||||
translate(to_translate: string, tabID?: number): Promise<Translation> {
|
||||
if (to_translate == "") return Promise.resolve({ src: "", result: "" });
|
||||
translate(
|
||||
toTranslate: string,
|
||||
langs: LanguagePair,
|
||||
tabID?: number
|
||||
): Promise<Translation> {
|
||||
toTranslate = toTranslate.trim();
|
||||
if (toTranslate == "")
|
||||
return Promise.resolve({
|
||||
src: "",
|
||||
result: "",
|
||||
languages: langs,
|
||||
});
|
||||
|
||||
let [promise, resolve, reject] = newPromise<Translation>();
|
||||
|
||||
const p = new Promise((resolve: (value: Translation) => void, reject) => {
|
||||
const req = {
|
||||
toTranslate: to_translate,
|
||||
toTranslate: toTranslate,
|
||||
resolveFn: resolve,
|
||||
rejectFn: reject,
|
||||
tabID: tabID,
|
||||
lang: langs,
|
||||
};
|
||||
if (this.current || !this.conn) {
|
||||
//Remove the requests from the same tab
|
||||
if (tabID)
|
||||
this.queue = this.queue.filter((r) => {
|
||||
|
@ -103,11 +120,9 @@ export class GTranslateScraper {
|
|||
}
|
||||
});
|
||||
this.queue.push(req);
|
||||
} else {
|
||||
this.processTranslationRequest(req, this.conn);
|
||||
}
|
||||
});
|
||||
return p;
|
||||
if (this.queue.length == 1 && this.conn) this.processFromQueue(this.conn);
|
||||
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,11 +19,17 @@ export class Communicator {
|
|||
getLanguagesCallback?: (
|
||||
sender: Runtime.MessageSender
|
||||
) => Promise<Array<Language>>;
|
||||
getCurrentLanguagesCallback?: (
|
||||
sender: Runtime.MessageSender
|
||||
) => Promise<LanguagePair>;
|
||||
setCurrentLanguagesCallback?: (
|
||||
value: Partial<LanguagePair>,
|
||||
sender: Runtime.MessageSender
|
||||
) => void;
|
||||
|
||||
constructor() {
|
||||
browser.runtime.onMessage.addListener(
|
||||
(c: command, s: Runtime.MessageSender) => {
|
||||
try {
|
||||
switch (c.commandKind) {
|
||||
case commandKinds.setEnabled:
|
||||
return this.setEnabledCallback!(c.value, s);
|
||||
|
@ -37,14 +43,13 @@ export class Communicator {
|
|||
return this.removeFlashcardCallback!(c.card, s);
|
||||
case commandKinds.getLanguages:
|
||||
return this.getLanguagesCallback!(s);
|
||||
case commandKinds.getCurrentLanguages:
|
||||
return this.getCurrentLanguagesCallback!(s);
|
||||
case commandKinds.setCurrentLanguages:
|
||||
return this.setCurrentLanguagesCallback!(c.value, s);
|
||||
default:
|
||||
console.warn(`Unimplemented command ${c}`);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof ReferenceError)
|
||||
console.warn(`Unimplemented command ${c}`);
|
||||
else throw e;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -72,6 +77,12 @@ export class Communicator {
|
|||
sendMessage({
|
||||
commandKind: commandKinds.getLanguages,
|
||||
});
|
||||
static getCurrentLanguages = (): Promise<LanguagePair> =>
|
||||
sendMessage({
|
||||
commandKind: commandKinds.getCurrentLanguages,
|
||||
});
|
||||
static setCurrentLanguages = (v: Partial<LanguagePair>) =>
|
||||
sendMessage({ commandKind: commandKinds.setCurrentLanguages, value: v });
|
||||
}
|
||||
|
||||
const sendMessage = (m: command) => browser.runtime.sendMessage(m);
|
||||
|
@ -83,17 +94,22 @@ export enum commandKinds {
|
|||
addFlashcard = "addFlashcard",
|
||||
removeFlashcard = "removeFlashcard",
|
||||
getLanguages = "getLanguages",
|
||||
getCurrentLanguages = "getCurrentLanguage",
|
||||
setCurrentLanguages = "setCurrentLanguages",
|
||||
}
|
||||
interface setEnabled {
|
||||
commandKind: commandKinds.setEnabled;
|
||||
value: boolean;
|
||||
}
|
||||
|
||||
interface getEnabled {
|
||||
commandKind: commandKinds.getEnabled;
|
||||
interface getCommand {
|
||||
commandKind:
|
||||
| commandKinds.getEnabled
|
||||
| commandKinds.getLanguages
|
||||
| commandKinds.getCurrentLanguages;
|
||||
}
|
||||
|
||||
interface translate {
|
||||
interface translateCommand {
|
||||
commandKind: commandKinds.translate;
|
||||
toTranslate: string;
|
||||
}
|
||||
|
@ -108,14 +124,15 @@ interface removeFlashcard {
|
|||
card: Flashcard;
|
||||
}
|
||||
|
||||
interface getLanguages {
|
||||
commandKind: commandKinds.getLanguages;
|
||||
interface setCurrentLanguages {
|
||||
commandKind: commandKinds.setCurrentLanguages;
|
||||
value: Partial<LanguagePair>;
|
||||
}
|
||||
|
||||
export type command =
|
||||
| setEnabled
|
||||
| getEnabled
|
||||
| translate
|
||||
| getCommand
|
||||
| translateCommand
|
||||
| addFlashcard
|
||||
| removeFlashcard
|
||||
| getLanguages;
|
||||
| setCurrentLanguages;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<div id="spinner" />
|
||||
|
||||
<style lang="scss">
|
||||
#spinner {
|
||||
border: 0.3em solid #f3f3f3;
|
||||
|
@ -18,5 +20,3 @@
|
|||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="spinner" />
|
||||
|
|
|
@ -14,6 +14,25 @@
|
|||
};
|
||||
</script>
|
||||
|
||||
<div class="translatedContainer">
|
||||
<span>{trans.result}</span>
|
||||
|
||||
{#if !card}
|
||||
<button on:click={addFlashcard}>
|
||||
Add
|
||||
<br />
|
||||
card
|
||||
</button>
|
||||
{:else}
|
||||
<button on:click={removeFlashcard} class="enabled">
|
||||
Remove
|
||||
<br />
|
||||
card
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@use "../color_scheme";
|
||||
|
||||
|
@ -47,22 +66,3 @@
|
|||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="translatedContainer">
|
||||
<span>{trans.result}</span>
|
||||
|
||||
{#if !card}
|
||||
<button on:click={addFlashcard}>
|
||||
Add
|
||||
<br />
|
||||
card
|
||||
</button>
|
||||
{:else}
|
||||
<button on:click={removeFlashcard} class="enabled">
|
||||
Remove
|
||||
<br />
|
||||
card
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
|
|
|
@ -8,6 +8,17 @@
|
|||
label.toLowerCase().startsWith(filterText.toLowerCase());
|
||||
</script>
|
||||
|
||||
<div class="select-styling">
|
||||
<Select
|
||||
items={langs}
|
||||
getOptionLabel={getLabel}
|
||||
getSelectionLabel={getLabel}
|
||||
optionIdentifier="code"
|
||||
{itemFilter}
|
||||
isClearable={false}
|
||||
bind:selectedValue />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@use "../color_scheme.scss";
|
||||
.select-styling {
|
||||
|
@ -20,14 +31,3 @@
|
|||
--border: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="select-styling">
|
||||
<Select
|
||||
items={langs}
|
||||
getOptionLabel={getLabel}
|
||||
getSelectionLabel={getLabel}
|
||||
optionIdentifier="code"
|
||||
{itemFilter}
|
||||
isClearable={false}
|
||||
bind:selectedValue />
|
||||
</div>
|
||||
|
|
|
@ -4,18 +4,33 @@
|
|||
|
||||
Communicator.getEnabled().then((v) => (enabled = v));
|
||||
|
||||
const languages = Communicator.getLanguages();
|
||||
|
||||
let enabled: boolean;
|
||||
$: if (enabled !== undefined) Communicator.setEnabled(enabled);
|
||||
|
||||
const langPromise = Communicator.getLanguages();
|
||||
let srcLang: Language;
|
||||
let dstLang: Language;
|
||||
langPromise.then((langs) => {
|
||||
srcLang = langs[0];
|
||||
dstLang = langs[0];
|
||||
});
|
||||
let selected: LanguagePair;
|
||||
Communicator.getCurrentLanguages().then((l) => (selected = l));
|
||||
$: if (selected) Communicator.setCurrentLanguages(selected);
|
||||
</script>
|
||||
|
||||
<div id="container">
|
||||
|
||||
<label class:enabled>
|
||||
<input type="checkbox" bind:checked={enabled} />
|
||||
{enabled ? 'Disable Extension' : 'Enable Extension'}
|
||||
</label>
|
||||
|
||||
{#if selected}
|
||||
{#await languages then langs}
|
||||
Translate from
|
||||
<LanguageSelection {langs} bind:selectedValue={selected.srcLang} />
|
||||
To
|
||||
<LanguageSelection {langs} bind:selectedValue={selected.dstLang} />
|
||||
{/await}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@use "../color_scheme.scss";
|
||||
|
||||
|
@ -59,18 +74,3 @@
|
|||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="container">
|
||||
|
||||
<label class:enabled>
|
||||
<input type="checkbox" bind:checked={enabled} />
|
||||
{enabled ? 'ON' : 'OFF'}
|
||||
</label>
|
||||
|
||||
{#await langPromise then langs}
|
||||
Translate from
|
||||
<LanguageSelection {langs} bind:selectedValue={srcLang} />
|
||||
To
|
||||
<LanguageSelection {langs} bind:selectedValue={dstLang} />
|
||||
{/await}
|
||||
</div>
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
import App from "./Popup.svelte";
|
||||
new App({ target: document.body });
|
||||
document.addEventListener;
|
||||
|
|
|
@ -13,5 +13,11 @@
|
|||
"default_popup": "popup.html"
|
||||
},
|
||||
"background": { "scripts": ["background.bundle.js"] },
|
||||
"permissions": ["<all_urls>", "sessions", "webRequest", "webRequestBlocking"]
|
||||
"permissions": [
|
||||
"<all_urls>",
|
||||
"sessions",
|
||||
"webRequest",
|
||||
"webRequestBlocking",
|
||||
"storage"
|
||||
]
|
||||
}
|
||||
|
|
7
src/types.d.ts
vendored
7
src/types.d.ts
vendored
|
@ -6,6 +6,7 @@ declare module "*?file" {
|
|||
declare interface Translation {
|
||||
src: string;
|
||||
result: string;
|
||||
languages: LanguagePair;
|
||||
}
|
||||
|
||||
declare interface Flashcard {
|
||||
|
@ -14,6 +15,12 @@ declare interface Flashcard {
|
|||
result: string;
|
||||
dateAdded: Date;
|
||||
exported: boolean;
|
||||
languages: LanguagePair;
|
||||
}
|
||||
|
||||
declare interface LanguagePair {
|
||||
srcLang: Language;
|
||||
dstLang: Language;
|
||||
}
|
||||
|
||||
declare interface Language {
|
||||
|
|
18
src/utils.ts
Normal file
18
src/utils.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
type resolveCallback<T> = (value: T) => void;
|
||||
type rejectCallback = (reason: any) => void;
|
||||
|
||||
export const newPromise = <T>(): [
|
||||
Promise<T>,
|
||||
resolveCallback<T>,
|
||||
rejectCallback
|
||||
] => {
|
||||
let resolve: resolveCallback<T>;
|
||||
let reject: rejectCallback;
|
||||
const promise = new Promise(
|
||||
(res: resolveCallback<T>, rej: rejectCallback) => {
|
||||
resolve = res;
|
||||
reject = rej;
|
||||
}
|
||||
);
|
||||
return [promise, resolve!, reject!];
|
||||
};
|
Loading…
Reference in a new issue