New communicator
This commit is contained in:
parent
74fe885fd0
commit
72568aa9b5
8 changed files with 181 additions and 179 deletions
|
@ -1,20 +1,21 @@
|
|||
import { browser, Runtime } from "webextension-polyfill-ts";
|
||||
import { GTranslateScraper } from "./gtranslate_scraper";
|
||||
import { Flashcards } from "./database";
|
||||
import { Communicator, commandKinds } from "../communication";
|
||||
import { BackgroundMessenger } from "../background_frontend_commands";
|
||||
import content_script from "../frontend/content_script/content_script.ts?file";
|
||||
|
||||
let com = new Communicator();
|
||||
let con = new BackgroundMessenger();
|
||||
|
||||
const scraper = new GTranslateScraper();
|
||||
com.translateCallback = async (toTranslate, sender) =>
|
||||
scraper.translate(toTranslate, await getCurrentLanguages(), sender.tab?.id);
|
||||
con.addMessageListener("translate", async (toTranslate, sender) =>
|
||||
scraper.translate(toTranslate, await getCurrentLanguages(), sender.tab?.id)
|
||||
);
|
||||
|
||||
com.getLanguagesCallback = () => scraper.languages;
|
||||
con.addMessageListener("getLanguages", () => scraper.languages);
|
||||
|
||||
const db = new Flashcards();
|
||||
com.addFlashcardCallback = (c) => db.addFlashcard(c);
|
||||
com.removeFlashcardCallback = (c) => db.removeFlashcard(c);
|
||||
con.addMessageListener("addFlashcard", (c) => db.addFlashcard(c));
|
||||
con.addMessageListener("removeFlashcard", (c) => db.removeFlashcard(c));
|
||||
|
||||
const getCurrentLanguages = async () => {
|
||||
const langs = await scraper.languages;
|
||||
|
@ -33,8 +34,8 @@ const setCurrentLanguages = async (l: Partial<LanguagePair>) => {
|
|||
if (l.dstLang) await browser.storage.local.set({ dstLang: l.dstLang.code });
|
||||
};
|
||||
|
||||
com.getCurrentLanguagesCallback = getCurrentLanguages;
|
||||
com.setCurrentLanguagesCallback = setCurrentLanguages;
|
||||
con.addMessageListener("getCurrentLanguages", getCurrentLanguages);
|
||||
con.addMessageListener("setCurrentLanguages", setCurrentLanguages);
|
||||
|
||||
const getTabID = async (s: Runtime.MessageSender): Promise<number> => {
|
||||
if (s.tab?.id) return s.tab.id;
|
||||
|
@ -56,24 +57,24 @@ const injectScript = async (tabID: number, enabled?: boolean) => {
|
|||
file: content_script,
|
||||
});
|
||||
if (enabled === undefined) enabled = await isEnabledSession(tabID);
|
||||
browser.tabs.sendMessage(tabID, {
|
||||
commandKind: commandKinds.setEnabled,
|
||||
value: enabled,
|
||||
});
|
||||
con.runCommand("setEnabled", enabled, tabID);
|
||||
};
|
||||
|
||||
com.setEnabledCallback = async (v: boolean, s: Runtime.MessageSender) => {
|
||||
const tab = await getTabID(s);
|
||||
if ((await isEnabledSession(tab)) == v) return;
|
||||
con.addMessageListener(
|
||||
"setEnabled",
|
||||
async (v: boolean, s: Runtime.MessageSender) => {
|
||||
const tab = await getTabID(s);
|
||||
if ((await isEnabledSession(tab)) == v) return;
|
||||
|
||||
injectScript(tab);
|
||||
await setEnabledSession(tab, v);
|
||||
};
|
||||
injectScript(tab);
|
||||
await setEnabledSession(tab, v);
|
||||
}
|
||||
);
|
||||
|
||||
com.getEnabledCallback = async (s: Runtime.MessageSender): Promise<boolean> => {
|
||||
con.addMessageListener("getEnabled", async (s: Runtime.MessageSender) => {
|
||||
const tab = await getTabID(s);
|
||||
return isEnabledSession(tab);
|
||||
};
|
||||
});
|
||||
|
||||
browser.tabs.onUpdated.addListener(async (tabID, changeInfo) => {
|
||||
if (changeInfo.status == "complete") {
|
||||
|
|
77
src/background_frontend_commands.ts
Normal file
77
src/background_frontend_commands.ts
Normal file
|
@ -0,0 +1,77 @@
|
|||
import { Communicator, commandFunction, commandList } from "./communicator";
|
||||
import { browser } from "webextension-polyfill-ts";
|
||||
|
||||
export interface commands extends commandList {
|
||||
setEnabled: setEnabled;
|
||||
getEnabled: getEnabled;
|
||||
translate: translate;
|
||||
addFlashcard: addFlashcard;
|
||||
removeFlashcard: removeFlashcard;
|
||||
getLanguages: getLanguages;
|
||||
getCurrentLanguages: getCurrentLanguages;
|
||||
setCurrentLanguages: setCurrentLanguages;
|
||||
}
|
||||
|
||||
interface setEnabled extends commandFunction {
|
||||
args: boolean;
|
||||
}
|
||||
|
||||
interface getEnabled extends commandFunction {
|
||||
return: boolean;
|
||||
}
|
||||
|
||||
interface getLanguages extends commandFunction {
|
||||
return: Array<Language>;
|
||||
}
|
||||
|
||||
interface translate extends commandFunction {
|
||||
args: string;
|
||||
return: Translation;
|
||||
}
|
||||
|
||||
interface getCurrentLanguages extends commandFunction {
|
||||
return: LanguagePair;
|
||||
}
|
||||
|
||||
interface addFlashcard extends commandFunction {
|
||||
args: Translation | Flashcard;
|
||||
return: Flashcard;
|
||||
}
|
||||
|
||||
interface removeFlashcard extends commandFunction {
|
||||
args: Flashcard;
|
||||
}
|
||||
|
||||
interface setCurrentLanguages extends commandFunction {
|
||||
args: Partial<LanguagePair>;
|
||||
}
|
||||
|
||||
export class BackgroundMessenger extends Communicator<commands> {
|
||||
constructor() {
|
||||
super();
|
||||
browser.runtime.onMessage.addListener((m, s) => this.onMessage(m, s));
|
||||
}
|
||||
runCommand<K extends keyof commands>(
|
||||
command: K,
|
||||
args: commands[K]["args"],
|
||||
tabID: number
|
||||
) {
|
||||
const msg = this.getCommandMessage(command, args);
|
||||
return browser.tabs.sendMessage(tabID, msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class FrontendMessenger extends Communicator<commands> {
|
||||
constructor() {
|
||||
super();
|
||||
browser.runtime.onMessage.addListener((m, s) => this.onMessage(m, s));
|
||||
}
|
||||
|
||||
runCommand<K extends keyof commands>(
|
||||
command: K,
|
||||
args: commands[K]["args"]
|
||||
): commands[K]["return"] {
|
||||
const message = this.getCommandMessage(command, args);
|
||||
return browser.runtime.sendMessage(message);
|
||||
}
|
||||
}
|
|
@ -1,138 +0,0 @@
|
|||
//There has to be a better way to do this while remaining type safe...
|
||||
import { browser, Runtime } from "webextension-polyfill-ts";
|
||||
|
||||
export class Communicator {
|
||||
setEnabledCallback?: (value: boolean, sender: Runtime.MessageSender) => void;
|
||||
getEnabledCallback?: (sender: Runtime.MessageSender) => Promise<boolean>;
|
||||
translateCallback?: (
|
||||
toTranslate: string,
|
||||
sender: Runtime.MessageSender
|
||||
) => Promise<Translation>;
|
||||
addFlashcardCallback?: (
|
||||
value: Translation | Flashcard,
|
||||
sender: Runtime.MessageSender
|
||||
) => Promise<Flashcard>;
|
||||
removeFlashcardCallback?: (
|
||||
value: Flashcard,
|
||||
sender: Runtime.MessageSender
|
||||
) => Promise<void>;
|
||||
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) => {
|
||||
switch (c.commandKind) {
|
||||
case commandKinds.setEnabled:
|
||||
return this.setEnabledCallback!(c.value, s);
|
||||
case commandKinds.getEnabled:
|
||||
return this.getEnabledCallback!(s);
|
||||
case commandKinds.translate:
|
||||
return this.translateCallback!(c.toTranslate, s);
|
||||
case commandKinds.addFlashcard:
|
||||
return this.addFlashcardCallback!(c.card, s);
|
||||
case commandKinds.removeFlashcard:
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
static setEnabled = (v: boolean) =>
|
||||
sendMessage({ commandKind: commandKinds.setEnabled, value: v });
|
||||
static getEnabled = (): Promise<boolean> =>
|
||||
sendMessage({ commandKind: commandKinds.getEnabled });
|
||||
static translate = (toTranslate: string): Promise<Translation> =>
|
||||
sendMessage({
|
||||
commandKind: commandKinds.translate,
|
||||
toTranslate: toTranslate,
|
||||
});
|
||||
static addFlashcard = (card: Flashcard | Translation): Promise<Flashcard> =>
|
||||
sendMessage({
|
||||
commandKind: commandKinds.addFlashcard,
|
||||
card: card,
|
||||
});
|
||||
static removeFlashcard = (card: Flashcard) =>
|
||||
sendMessage({
|
||||
commandKind: commandKinds.removeFlashcard,
|
||||
card: card,
|
||||
});
|
||||
static getLanguages = (): Promise<Array<Language>> =>
|
||||
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);
|
||||
|
||||
export enum commandKinds {
|
||||
setEnabled = "setEnabled",
|
||||
getEnabled = "getEnabled",
|
||||
translate = "translate",
|
||||
addFlashcard = "addFlashcard",
|
||||
removeFlashcard = "removeFlashcard",
|
||||
getLanguages = "getLanguages",
|
||||
getCurrentLanguages = "getCurrentLanguage",
|
||||
setCurrentLanguages = "setCurrentLanguages",
|
||||
}
|
||||
interface setEnabled {
|
||||
commandKind: commandKinds.setEnabled;
|
||||
value: boolean;
|
||||
}
|
||||
|
||||
interface getCommand {
|
||||
commandKind:
|
||||
| commandKinds.getEnabled
|
||||
| commandKinds.getLanguages
|
||||
| commandKinds.getCurrentLanguages;
|
||||
}
|
||||
|
||||
interface translateCommand {
|
||||
commandKind: commandKinds.translate;
|
||||
toTranslate: string;
|
||||
}
|
||||
|
||||
interface addFlashcard {
|
||||
commandKind: commandKinds.addFlashcard;
|
||||
card: Translation | Flashcard;
|
||||
}
|
||||
|
||||
interface removeFlashcard {
|
||||
commandKind: commandKinds.removeFlashcard;
|
||||
card: Flashcard;
|
||||
}
|
||||
|
||||
interface setCurrentLanguages {
|
||||
commandKind: commandKinds.setCurrentLanguages;
|
||||
value: Partial<LanguagePair>;
|
||||
}
|
||||
|
||||
export type command =
|
||||
| setEnabled
|
||||
| getCommand
|
||||
| translateCommand
|
||||
| addFlashcard
|
||||
| removeFlashcard
|
||||
| setCurrentLanguages;
|
52
src/communicator.ts
Normal file
52
src/communicator.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
import type { Runtime } from "webextension-polyfill-ts";
|
||||
|
||||
export interface commandFunction {
|
||||
args: any;
|
||||
return: any;
|
||||
}
|
||||
|
||||
export interface commandList {
|
||||
[functionName: string]: commandFunction;
|
||||
}
|
||||
|
||||
interface commandMessage<T extends commandList, K extends keyof T> {
|
||||
name: K;
|
||||
args: T[K]["args"];
|
||||
}
|
||||
|
||||
declare type listener<T extends commandList, K extends keyof T> = (
|
||||
args: T[K]["args"],
|
||||
s: Runtime.MessageSender
|
||||
) => Promise<T[K]["return"]>;
|
||||
|
||||
export abstract class Communicator<T extends commandList> {
|
||||
private listeners = new Map<keyof T, listener<T, keyof T>>();
|
||||
|
||||
abstract runCommand<K extends keyof T>(
|
||||
command: K,
|
||||
args: T[K]["args"],
|
||||
...rest: any[]
|
||||
): Promise<T[K]["return"]>;
|
||||
|
||||
addMessageListener<K extends keyof T>(command: K, callback: listener<T, K>) {
|
||||
this.listeners.set(command, <any>callback);
|
||||
}
|
||||
|
||||
getCommandMessage<K extends keyof T>(
|
||||
command: K,
|
||||
...args: T[K]["args"]
|
||||
): commandMessage<T, K> {
|
||||
return { name: command, args: args };
|
||||
}
|
||||
|
||||
onMessage<K extends keyof T>(
|
||||
m: commandMessage<T, K>,
|
||||
s: Runtime.MessageSender
|
||||
) {
|
||||
let listener = this.listeners.get(m.name);
|
||||
let args: [T[keyof T]["args"], Runtime.MessageSender] = m.args;
|
||||
if (args[0] === undefined) args.pop();
|
||||
args.push(s);
|
||||
if (listener) return listener.apply(null, args);
|
||||
}
|
||||
}
|
|
@ -1,19 +1,23 @@
|
|||
<script lang="ts">
|
||||
import Spinner from "./Spinner.svelte";
|
||||
import Translated from "./Translated.svelte";
|
||||
import { Communicator } from "../../communication";
|
||||
import { createEventDispatcher, tick } from "svelte";
|
||||
import { createEventDispatcher, tick, setContext } from "svelte";
|
||||
import type { FrontendMessenger } from "../../background_frontend_commands";
|
||||
|
||||
export let toTranslate: string;
|
||||
export let connection: FrontendMessenger;
|
||||
setContext("connection", connection);
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let trans_promise: Promise<Translation>;
|
||||
$: {
|
||||
trans_promise = Communicator.translate(toTranslate).then((r) => {
|
||||
tick().then(() => dispatch("translationFinished"));
|
||||
return r;
|
||||
});
|
||||
trans_promise = connection
|
||||
.runCommand("translate", toTranslate)
|
||||
.then((r) => {
|
||||
tick().then(() => dispatch("translationFinished"));
|
||||
return r;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -21,4 +25,6 @@
|
|||
<Spinner />
|
||||
{:then trans}
|
||||
<Translated {trans} />
|
||||
{:catch}
|
||||
Error
|
||||
{/await}
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
<script lang="ts">
|
||||
import { Communicator } from "../../communication";
|
||||
import { getContext } from "svelte";
|
||||
import type { FrontendMessenger } from "../../background_frontend_commands";
|
||||
|
||||
let connection = getContext("connection") as FrontendMessenger;
|
||||
export let trans: Translation;
|
||||
let card: Flashcard | undefined;
|
||||
|
||||
const addFlashcard = async () => {
|
||||
card = await Communicator.addFlashcard(trans);
|
||||
card = await connection.runCommand("addFlashcard", trans);
|
||||
};
|
||||
|
||||
const removeFlashcard = async () => {
|
||||
if (card) await Communicator.removeFlashcard(card);
|
||||
if (card) await connection.runCommand("removeFlashcard", card);
|
||||
card = undefined;
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import { Communicator } from "../../communication";
|
||||
import tippy, { roundArrow } from "tippy.js";
|
||||
import App from "./Translate.svelte";
|
||||
import "./tippy.scss";
|
||||
import { FrontendMessenger } from "../../background_frontend_commands";
|
||||
|
||||
(() => {
|
||||
//Make sure our script only runs once
|
||||
if (window.hasRun) return;
|
||||
window.hasRun = true;
|
||||
|
||||
let con = new FrontendMessenger();
|
||||
|
||||
const tippyInstance = setupTippy();
|
||||
|
||||
let timeoutID: number;
|
||||
|
@ -18,20 +20,19 @@ import "./tippy.scss";
|
|||
timeoutID = window.setTimeout(tippyInstance.show, 500);
|
||||
};
|
||||
|
||||
let con = new Communicator();
|
||||
con.setEnabledCallback = (v: boolean) => {
|
||||
con.addMessageListener("setEnabled", async (v: boolean) => {
|
||||
document.removeEventListener("selectionchange", onSelectionChange); //Always remove it avoid duplicate event listeners
|
||||
if (v) {
|
||||
onSelectionChange(); // Call it since selection may have changed since it was enabled
|
||||
document.addEventListener("selectionchange", onSelectionChange);
|
||||
} else tippyInstance.hide();
|
||||
};
|
||||
});
|
||||
|
||||
function setupTippy() {
|
||||
const svelteElement = document.createElement("div");
|
||||
const sveleteApp = new App({
|
||||
target: svelteElement,
|
||||
props: { toTranslate: "" },
|
||||
props: { toTranslate: "", connection: con },
|
||||
});
|
||||
return tippy(document.body, {
|
||||
content: svelteElement,
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
<script lang="ts">
|
||||
import { Communicator } from "../../communication";
|
||||
import LanguageSelection from "./LanguageSelection.svelte";
|
||||
import { FrontendMessenger } from "../../background_frontend_commands";
|
||||
|
||||
Communicator.getEnabled().then((v) => (enabled = v));
|
||||
const conn = new FrontendMessenger();
|
||||
conn.runCommand("getEnabled", undefined).then((v) => (enabled = v));
|
||||
|
||||
const languages = Communicator.getLanguages();
|
||||
const languages = conn.runCommand("getLanguages", undefined);
|
||||
|
||||
let enabled: boolean;
|
||||
$: if (enabled !== undefined) Communicator.setEnabled(enabled);
|
||||
$: if (enabled !== undefined) conn.runCommand("setEnabled", enabled);
|
||||
|
||||
let selected: LanguagePair;
|
||||
Communicator.getCurrentLanguages().then((l) => (selected = l));
|
||||
$: if (selected) Communicator.setCurrentLanguages(selected);
|
||||
conn.runCommand("getCurrentLanguages", undefined).then((l) => (selected = l));
|
||||
$: if (selected) conn.runCommand("setCurrentLanguages", selected);
|
||||
</script>
|
||||
|
||||
<div id="container">
|
||||
|
|
Loading…
Reference in a new issue