downthemall/lib/windowutils.ts

240 lines
5.8 KiB
TypeScript

"use strict";
// License: MIT
import { windows, tabs, runtime, CHROME } from "../lib/browser";
import { getManager } from "./manager/man";
import DEFAULT_ICONS from "../data/icons.json";
import { Prefs } from "./prefs";
import { _ } from "./i18n";
import { WindowStateTracker } from "./windowstatetracker";
// eslint-disable-next-line no-unused-vars
import { Port, Bus } from "./bus";
import { timeout } from "./util";
const DONATE_URL = "https://www.downthemall.org/howto/donate/";
const DONATE_LANG_URLS = Object.freeze(new Map([
["de", "https://www.downthemall.org/howto/donate/spenden/"],
]));
const MANAGER_URL = "/windows/manager.html";
export async function mostRecentBrowser(incognito: boolean): Promise<any> {
let window;
try {
window = await windows.getCurrent();
if (window.type !== "normal") {
throw new Error("not a normal window");
}
if (incognito && !window.incognito) {
throw new Error("Not incognito");
}
}
catch {
try {
window = await windows.getlastFocused();
if (window.type !== "normal") {
throw new Error("not a normal window");
}
if (incognito && !window.incognito) {
throw new Error("Not incognito");
}
}
catch {
window = Array.from(await windows.getAll({windowTypes: ["normal"]})).
filter(
(w: any) => w.type === "normal" && !!w.incognito === !!incognito).
pop();
}
}
if (!window) {
window = await windows.create({
incognito: !!incognito,
type: "normal",
});
}
return window;
}
export async function openInTab(url: string, incognito: boolean) {
const window = await mostRecentBrowser(incognito);
await tabs.create({
active: true,
url,
windowId: window.id,
});
await windows.update(window.id, {focused: true});
}
export async function openInTabOrFocus(url: string, incognito: boolean) {
const etabs = await tabs.query({
url
});
if (etabs.length) {
const tab = etabs.pop();
await tabs.update(tab.id, {active: true});
await windows.update(tab.windowId, {focused: true});
return;
}
await openInTab(url, incognito);
}
export async function maybeOpenInTab(url: string, incognito: boolean) {
const etabs = await tabs.query({
url
});
if (etabs.length) {
return;
}
await openInTab(url, incognito);
}
export async function donate() {
const url = DONATE_LANG_URLS.get(_("language_code")) || DONATE_URL;
await openInTab(url, false);
}
export async function openPrefs() {
await runtime.openOptionsPage();
}
export async function openManager(focus = true) {
try {
await getManager();
}
catch (ex) {
console.error(ex.toString(), ex);
}
const url = runtime.getURL(MANAGER_URL);
const openInPopup = await Prefs.get("manager-in-popup");
if (openInPopup) {
const etabs = await tabs.query({
url
});
if (etabs.length) {
if (!focus) {
return;
}
const tab = etabs.pop();
await tabs.update(tab.id, {active: true});
await windows.update(tab.windowId, {focused: true});
return;
}
const tracker = new WindowStateTracker("manager", {
minWidth: 700,
minHeight: 500,
});
await tracker.init();
const windowOptions = tracker.getOptions({
url,
type: "popup",
});
const window = await windows.create(windowOptions);
tracker.track(window.id);
try {
if (!CHROME) {
windows.update(window.id, tracker.getOptions({}));
}
const port = await Promise.race<Port>([
new Promise<Port>(resolve => Bus.oncePort("manager", port => {
resolve(port);
return true;
})),
timeout<Port>(5 * 1000)]);
if (!port.isSelf) {
throw Error("Invalid sender connected");
}
tracker.track(window.id, port);
}
catch (ex) {
console.error("couldn't track manager", ex);
}
return;
}
if (focus) {
await openInTabOrFocus(runtime.getURL(MANAGER_URL), false);
}
else {
await maybeOpenInTab(runtime.getURL(MANAGER_URL), false);
}
}
export async function openUrls(urls: string, incognito: boolean) {
const window = await mostRecentBrowser(incognito);
for (const url of urls) {
try {
await tabs.create({
active: url === urls[0],
url,
windowId: window.id,
});
}
catch (ex) {
console.error(ex);
}
}
await windows.update(window.id, {focused: true});
}
const ICONS = Object.freeze((() => {
const rv: any[] = [];
for (const [k, v] of Object.entries<string[]>(DEFAULT_ICONS)) {
for (const ext of v) {
rv.push([`file.${ext}`, `icon-file-${k}`]);
}
}
return new Map<string, string>(rv);
})());
export const DEFAULT_ICON_SIZE = 16;
// eslint-disable-next-line no-unused-vars
export function iconForPath(path: string, size = DEFAULT_ICON_SIZE) {
const web = /^https?:\/\//.test(path);
let file = path.split(/[\\/]/).pop();
if (file) {
const idx = file.lastIndexOf(".");
if (idx > 0) {
file = `file${file.substr(idx)}`;
file.replace(/\?.*?/g, "");
}
else {
file = undefined;
}
}
if (!file) {
if (web) {
file = "file.html";
}
else {
file = "file";
}
}
return ICONS.get(file) || "icon-file-generic";
}
/**
* Resolves when an element becomes first viisble
* @param {Element} el Element to observe
* @returns {Promise<Element>}
*/
export function visible(el: Element | string) {
const elem = typeof el === "string" ?
document.querySelector<HTMLElement>(el) :
el as HTMLElement;
if (!elem) {
return Promise.resolve();
}
return new Promise(resolve => {
const obs = new IntersectionObserver(entries => {
if (!entries.some(e => e.isIntersecting)) {
return;
}
obs.disconnect();
resolve();
});
obs.observe(elem);
});
}