diff --git a/lib/api.ts b/lib/api.ts index 187282a..a87de33 100644 --- a/lib/api.ts +++ b/lib/api.ts @@ -5,7 +5,8 @@ import { TYPE_LINK, TYPE_MEDIA } from "./constants"; import { filters } from "./filters"; import { Prefs } from "./prefs"; import { lazy } from "./util"; -import { Item, makeUniqueItems } from "./item"; +// eslint-disable-next-line no-unused-vars +import { Item, makeUniqueItems, BaseItem } from "./item"; import { getManager } from "./manager/man"; import { select } from "./select"; import { single } from "./single"; @@ -16,12 +17,17 @@ import { _ } from "./i18n"; const MAX_BATCH = 10000; +export interface QueueOptions { + mask?: string; + paused?: boolean; +} + export const API = new class APIImpl { - async filter(arr: any, type: number) { + async filter(arr: BaseItem[], type: number) { return await (await filters()).filterItemsByType(arr, type); } - async queue(items: any, options: any) { + async queue(items: BaseItem[], options: QueueOptions) { await MASK.init(); const {mask = MASK.current} = options; @@ -36,12 +42,9 @@ export const API = new class APIImpl { fileName: null, title: "", description: "", - fromMetalink: false, startDate: new Date(), - hashes: [], private: false, postData: null, - cleanRequest: false, mask, date: Date.now(), paused @@ -54,7 +57,7 @@ export const API = new class APIImpl { } return currentBatch; }); - items = items.map((i: any) => { + items = items.map(i => { delete i.idx; return new Item(i, defaults); }); @@ -79,7 +82,7 @@ export const API = new class APIImpl { } } - sanity(links: any[], media: any[]) { + sanity(links: BaseItem[], media: BaseItem[]) { if (!links.length && !media.length) { new Notification(null, _("no-links")); return false; @@ -87,7 +90,7 @@ export const API = new class APIImpl { return true; } - async turbo(links: any[], media: any[]) { + async turbo(links: BaseItem[], media: BaseItem[]) { if (!this.sanity(links, media)) { return false; } @@ -105,37 +108,36 @@ export const API = new class APIImpl { return await this.queue(selected, {paused: await Prefs.get("add-paused")}); } - async regularInternal(selected: any) { - if (selected.mask && !selected.maskOnce) { + async regularInternal(selected: BaseItem[], options: any) { + if (options.mask && !options.maskOnce) { await MASK.init(); - await MASK.push(selected.mask); + await MASK.push(options.mask); } - if (typeof selected.fast === "string" && !selected.fastOnce) { + if (typeof options.fast === "string" && !options.fastOnce) { await FASTFILTER.init(); - await FASTFILTER.push(selected.fast); + await FASTFILTER.push(options.fast); } - if (typeof selected.type === "string") { - await Prefs.set("last-type", selected.type); + if (typeof options.type === "string") { + await Prefs.set("last-type", options.type); } - const {items} = selected; - delete selected.items; - return await this.queue(items, selected); + return await this.queue(selected, options); } - async regular(links: any[], media: any[]) { + async regular(links: BaseItem[], media: BaseItem[]) { if (!this.sanity(links, media)) { return false; } - const selected = await select(links, media); - return this.regularInternal(selected); + const {items, options} = await select(links, media); + console.log(items, options); + return this.regularInternal(items, options); } - async singleTurbo(item: any) { + async singleTurbo(item: BaseItem) { return await this.queue([item], {paused: await Prefs.get("add-paused")}); } - async singleRegular(item: any) { - const selected = await single(item); - return this.regularInternal(selected); + async singleRegular(item: BaseItem | null) { + const {items, options} = await single(item); + return this.regularInternal(items, options); } }(); diff --git a/lib/background.ts b/lib/background.ts index 8ed6d48..dcf6b42 100644 --- a/lib/background.ts +++ b/lib/background.ts @@ -13,9 +13,14 @@ import { browserAction as action, menus as _menus, contextMenus as _cmenus, tabs, - webNavigation as nav + webNavigation as nav, + // eslint-disable-next-line no-unused-vars + Tab, + // eslint-disable-next-line no-unused-vars + MenuClickInfo } from "./browser"; import { Bus } from "./bus"; +import { filterInSitu } from "./util"; const menus = typeof (_menus) !== "undefined" && _menus || _cmenus; @@ -23,7 +28,7 @@ const menus = typeof (_menus) !== "undefined" && _menus || _cmenus; const GATHER = "/bundles/content-gather.js"; -async function runContentJob(tab: any, file: string, msg: any) { +async function runContentJob(tab: Tab, file: string, msg: any) { try { const res = await tabs.executeScript(tab.id, { file, @@ -56,7 +61,7 @@ type SelectionOptions = { selectionOnly: boolean; allTabs: boolean; turbo: boolean; - tab: any; + tab: Tab; }; @@ -71,9 +76,8 @@ class Handler { return makeUniqueItems( results.filter(e => e[what]).map(e => { const finisher = new Finisher(e); - return e[what]. - map((item: any) => finisher.finish(item)). - filter((i: any) => i); + return filterInSitu(e[what]. + map((item: any) => finisher.finish(item)), e => !!e); })); } @@ -348,7 +352,7 @@ locale.then(() => { } } - async findSingleItem(tab: any, url: string, turbo = false) { + async findSingleItem(tab: Tab, url: string, turbo = false) { if (!url) { return; } @@ -368,7 +372,7 @@ locale.then(() => { API[turbo ? "singleTurbo" : "singleRegular"](item); } - onClicked(info: any, tab: any) { + onClicked(info: MenuClickInfo, tab: Tab) { if (!tab.id) { return; } @@ -378,7 +382,7 @@ locale.then(() => { console.error("Invalid Handler for", menuItemId); return; } - const rv = handler.call(this, info, tab); + const rv: Promise | void = handler.call(this, info, tab); if (rv && rv.catch) { rv.catch(console.error); } @@ -394,7 +398,7 @@ locale.then(() => { }, tab[0]); } - async onClickedDTARegular(info: any, tab: any) { + async onClickedDTARegular(info: MenuClickInfo, tab: Tab) { return await this.performSelection({ selectionOnly: false, allTabs: false, @@ -403,7 +407,7 @@ locale.then(() => { }); } - async onClickedDTARegularAll(info: any, tab: any) { + async onClickedDTARegularAll(info: MenuClickInfo, tab: Tab) { return await this.performSelection({ selectionOnly: false, allTabs: true, @@ -412,7 +416,7 @@ locale.then(() => { }); } - async onClickedDTARegularSelection(info: any, tab: any) { + async onClickedDTARegularSelection(info: MenuClickInfo, tab: Tab) { return await this.performSelection({ selectionOnly: true, allTabs: false, @@ -421,7 +425,7 @@ locale.then(() => { }); } - async onClickedDTATurbo(info: any, tab: any) { + async onClickedDTATurbo(info: MenuClickInfo, tab: Tab) { return await this.performSelection({ selectionOnly: false, allTabs: false, @@ -430,7 +434,7 @@ locale.then(() => { }); } - async onClickedDTATurboAll(info: any, tab: any) { + async onClickedDTATurboAll(info: MenuClickInfo, tab: Tab) { return await this.performSelection({ selectionOnly: false, allTabs: true, @@ -439,7 +443,7 @@ locale.then(() => { }); } - async onClickedDTATurboSelection(info: any, tab: any) { + async onClickedDTATurboSelection(info: MenuClickInfo, tab: Tab) { return await this.performSelection({ selectionOnly: true, allTabs: false, @@ -448,28 +452,46 @@ locale.then(() => { }); } - async onClickedDTARegularLink(info: any, tab: any) { - return await this.findSingleItem(tab, info.linkUrl, false); + async onClickedDTARegularLink(info: MenuClickInfo, tab: Tab) { + if (!info.linkUrl) { + return; + } + await this.findSingleItem(tab, info.linkUrl, false); } - async onClickedDTATurboLink(info: any, tab: any) { - return await this.findSingleItem(tab, info.linkUrl, true); + async onClickedDTATurboLink(info: MenuClickInfo, tab: Tab) { + if (!info.linkUrl) { + return; + } + await this.findSingleItem(tab, info.linkUrl, true); } - async onClickedDTARegularImage(info: any, tab: any) { - return await this.findSingleItem(tab, info.srcUrl, false); + async onClickedDTARegularImage(info: MenuClickInfo, tab: Tab) { + if (!info.srcUrl) { + return; + } + await this.findSingleItem(tab, info.srcUrl, false); } - async onClickedDTATurboImage(info: any, tab: any) { - return await this.findSingleItem(tab, info.srcUrl, true); + async onClickedDTATurboImage(info: MenuClickInfo, tab: Tab) { + if (!info.srcUrl) { + return; + } + await this.findSingleItem(tab, info.srcUrl, true); } - async onClickedDTARegularMedia(info: any, tab: any) { - return await this.findSingleItem(tab, info.srcUrl, false); + async onClickedDTARegularMedia(info: MenuClickInfo, tab: Tab) { + if (!info.srcUrl) { + return; + } + await this.findSingleItem(tab, info.srcUrl, false); } - async onClickedDTATurboMedia(info: any, tab: any) { - return await this.findSingleItem(tab, info.srcUrl, true); + async onClickedDTATurboMedia(info: MenuClickInfo, tab: Tab) { + if (!info.srcUrl) { + return; + } + await this.findSingleItem(tab, info.srcUrl, true); } onClickedDTAAdd() { diff --git a/lib/batches.ts b/lib/batches.ts index d61dd25..e0a0d79 100644 --- a/lib/batches.ts +++ b/lib/batches.ts @@ -98,9 +98,9 @@ export class BatchGenerator implements Generator { public readonly hasInvalid: boolean; - public readonly length: any; + public readonly length: number; - public readonly preview: any; + public readonly preview: string; constructor(str: string) { this.gens = []; diff --git a/lib/browser.ts b/lib/browser.ts index 77b155d..e6ffbc3 100644 --- a/lib/browser.ts +++ b/lib/browser.ts @@ -3,6 +3,42 @@ // eslint-disable-next-line @typescript-eslint/no-var-requires const polyfill = require("webextension-polyfill"); +interface ExtensionListener { + addListener: (listener: Function) => void; + removeListener: (listener: Function) => void; +} + +export interface MessageSender { + tab?: Tab; + frameId?: number; + id?: number; + url?: string; + tlsChannelId?: string; +} + + +export interface Tab { + id?: number; +} + +export interface MenuClickInfo { + menuItemId: string | number; + button?: number; + linkUrl?: string; + srcUrl?: string; +} + + +export interface RawPort { + error: any; + name: string; + onDisconnect: ExtensionListener; + onMessage: ExtensionListener; + sender?: MessageSender; + disconnect: () => void; + postMessage: (message: any) => void; +} + export const {extension} = polyfill; export const {notifications} = polyfill; export const {browserAction} = polyfill; diff --git a/lib/bus.ts b/lib/bus.ts index efc4408..0c580db 100644 --- a/lib/bus.ts +++ b/lib/bus.ts @@ -2,22 +2,18 @@ // License: MIT import { EventEmitter } from "./events"; -import {runtime, tabs} from "./browser"; +// eslint-disable-next-line no-unused-vars +import {runtime, tabs, RawPort, MessageSender} from "./browser"; export class Port extends EventEmitter { - private port: any; + private port: RawPort | null; - constructor(port: any) { + constructor(port: RawPort) { super(); this.port = port; let disconnected = false; - let tabListener: any; const disconnect = () => { - if (tabListener) { - tabs.onRemoved.removeListener(tabListener); - tabListener = null; - } if (disconnected) { return; } @@ -41,12 +37,17 @@ export class Port extends EventEmitter { } get name() { + if (!this.port) { + return null; + } return this.port.name; } get id() { - return this.port.sender && ( - this.port.sender.id || this.port.sender.extensionId); + if (!this.port || !this.port.sender) { + return null; + } + return this.port.sender.id; } get isSelf() { @@ -54,6 +55,9 @@ export class Port extends EventEmitter { } post(msg: string, ...data: any[]) { + if (!this.port) { + return; + } if (!data) { this.port.postMessage({msg}); return; @@ -65,14 +69,17 @@ export class Port extends EventEmitter { } onMessage(message: any) { - if (Object.keys(message).includes("msg")) { - this.emit(message.msg, message); + if (!this.port) { return; } if (Array.isArray(message)) { message.forEach(this.onMessage, this); return; } + if (Object.keys(message).includes("msg")) { + this.emit(message.msg, message); + return; + } if (typeof message === "string") { this.emit(message); return; @@ -100,7 +107,7 @@ export const Bus = new class extends EventEmitter { runtime.onConnect.addListener(this.onConnect.bind(this)); } - onMessage(msg: any, sender: any, callback: any) { + onMessage(msg: any, sender: MessageSender, callback: any) { let {type = null} = msg; if (!type) { type = msg; @@ -108,7 +115,7 @@ export const Bus = new class extends EventEmitter { this.emit(type, msg, callback); } - onConnect(port: any) { + onConnect(port: RawPort) { if (!port.name) { port.disconnect(); return; diff --git a/lib/db.ts b/lib/db.ts index d2d2b10..2fe8f59 100644 --- a/lib/db.ts +++ b/lib/db.ts @@ -1,5 +1,8 @@ "use strict"; +// eslint-disable-next-line no-unused-vars +import { BaseItem } from "./item"; + // License: MIT const VERSION = 1; @@ -40,12 +43,12 @@ export const DB = new class DB { }); } - getAllInternal(resolve: (items: any[]) => void, reject: Function) { + getAllInternal(resolve: (items: BaseItem[]) => void, reject: Function) { if (!this.db) { reject(new Error("db closed")); return; } - const items: any[] = []; + const items: BaseItem[] = []; const transaction = this.db.transaction(STORE, "readonly"); transaction.onerror = ex => reject(ex); const store = transaction.objectStore(STORE); diff --git a/lib/filters.ts b/lib/filters.ts index 136538f..b3d64f7 100644 --- a/lib/filters.ts +++ b/lib/filters.ts @@ -12,6 +12,8 @@ import { Overlayable } from "./objectoverlay"; import * as DEFAULT_FILTERS from "../data/filters.json"; import { FASTFILTER } from "./recentlist"; import { _, locale } from "./i18n"; +// eslint-disable-next-line no-unused-vars +import { BaseItem } from "./item"; const REG_ESCAPE = /[{}()[\]\\^$.]/g; const REG_FNMATCH = /[*?]/; @@ -174,25 +176,37 @@ export class Matcher { } /* eslint-enable no-unused-vars */ - matchItem(item: any) { + matchItem(item: BaseItem) { const {usable = "", title = "", description = "", fileName = ""} = item; return this.match(usable) || this.match(title) || this.match(description) || this.match(fileName); } } +interface RawFilter extends Object { + active: boolean; + type: number; + label: string; + expr: string; + icon?: string; + custom?: boolean; + isOverridden?: (prop: string) => boolean; + reset?: () => void; + toJSON?: () => any; +} + export class Filter { private readonly owner: Filters; - public readonly id: any; + public readonly id: string | symbol; - private readonly raw: any; + private readonly raw: RawFilter; private _label: string; private _reg: Matcher; - constructor(owner: Filters, id: any, raw: any) { + constructor(owner: Filters, id: string | symbol, raw: RawFilter) { if (!owner || !id || !raw) { throw new Error("null argument"); } @@ -204,9 +218,11 @@ export class Filter { init() { this._label = this.raw.label; - if (this.id !== FAST && this.id.startsWith("deffilter-") && - !this.raw.isOverridden("label")) { - this._label = _(this.id) || this._label; + if (typeof this.raw.isOverridden !== "undefined" && + typeof this.id === "string") { + if (this.id.startsWith("deffilter-") && !this.raw.isOverridden("label")) { + this._label = _(this.id) || this._label; + } } this._reg = Matcher.fromExpression(this.expr); Object.seal(this); @@ -282,7 +298,7 @@ export class Filter { } async reset() { - if (this.raw.custom) { + if (!this.raw.reset) { throw Error("Cannot reset non-default filter"); } this.raw.reset(); @@ -292,7 +308,10 @@ export class Filter { async "delete"() { if (!this.raw.custom) { - throw Error("Cannot delete default filter"); + throw new Error("Cannot delete default filter"); + } + if (typeof this.id !== "string") { + throw new Error("Cannot delete symbolized"); } await this.owner.delete(this.id); } @@ -301,7 +320,7 @@ export class Filter { return this._reg.match(str); } - matchItem(item: any) { + matchItem(item: BaseItem) { return this._reg.matchItem(item); } @@ -316,8 +335,7 @@ class FastFilter extends Filter { throw new Error("Invalid fast filter value"); } super(owner, FAST, { - id: FAST, - label: FAST, + label: "fast", type: TYPE_ALL, active: true, expr: value, @@ -409,11 +427,11 @@ class Filters extends EventEmitter { await this.save(); } - "get"(id: any) { + "get"(id: string | symbol) { return this.filters.find(e => e.id === id); } - async "delete"(id: any) { + async "delete"(id: string) { const idx = this.filters.findIndex(e => e.id === id); if (idx < 0) { return; @@ -517,7 +535,7 @@ class Filters extends EventEmitter { this.regenerate(); } - async filterItemsByType(items: any[], type: number) { + async filterItemsByType(items: BaseItem[], type: number) { const matcher = this.typeMatchers.get(type); const fast = await this.getFastFilter(); return items.filter(function(item) { @@ -544,12 +562,14 @@ class Filters extends EventEmitter { } } -let _filters: any; +let _filters: Filters; +let _loader: Promise; export async function filters(): Promise { - if (!_filters) { + if (!_loader) { _filters = new Filters(); - await _filters.load(); + _loader = _filters.load(); } + await _loader; return _filters; } diff --git a/lib/item.ts b/lib/item.ts index a8b6abc..f182528 100644 --- a/lib/item.ts +++ b/lib/item.ts @@ -4,18 +4,32 @@ import { ALLOWED_SCHEMES } from "./constants"; import { TRANSFERABLE_PROPERTIES } from "./constants"; +export interface BaseItem { + url: string; + usable: string; + referrer?: string; + usableReferrer?: string; + description?: string; + title?: string; + fileName?: string; + batch?: number; + idx: number; + mask?: string; + startDate?: number; + private?: boolean; + postData?: string; + paused?: boolean; +} + const OPTIONPROPS = Object.freeze([ "referrer", "usableReferrer", "description", "title", "fileName", "batch", "idx", "mask", - "fromMetalink", "startDate", - "hashes", "private", "postData", - "cleanRequest", "paused" ]); @@ -34,7 +48,7 @@ function maybeAssign(options: any, what: any) { this[what] = val; } -export class Item { +export class Item implements BaseItem { public url: string; public usable: string; @@ -43,6 +57,8 @@ export class Item { public usableReferrer: string; + public idx: number; + constructor(raw: any, options?: any) { Object.assign(this, raw); OPTIONPROPS.forEach(maybeAssign.bind(this, options || {})); diff --git a/lib/manager/basedownload.ts b/lib/manager/basedownload.ts index 27f94cf..cda21e2 100644 --- a/lib/manager/basedownload.ts +++ b/lib/manager/basedownload.ts @@ -29,8 +29,6 @@ const SAVEDPROPS = [ "serverName", // other options "private", - "fromMetalink", - "cleanRequest", // db "manId", "dbId", diff --git a/lib/select.ts b/lib/select.ts index af76b9b..ce231ed 100644 --- a/lib/select.ts +++ b/lib/select.ts @@ -10,10 +10,24 @@ import { donate, openPrefs, openUrls } from "./windowutils"; import { filters, FAST, Filter } from "./filters"; import { WindowStateTracker } from "./windowstatetracker"; import { windows } from "./browser"; +// eslint-disable-next-line no-unused-vars +import { BaseItem } from "./item"; +interface BaseMatchedItem extends BaseItem { + matched?: string | null; + prevMatched?: string | null; +} -function computeSelection(filters: any[], items: any[], onlyFast: boolean) { - let ws = items.map((item: any, idx: number) => { +export interface ItemDelta { + idx: number; + matched?: string | null; +} + +function computeSelection( + filters: Filter[], + items: BaseMatchedItem[], + onlyFast: boolean): ItemDelta[] { + let ws = items.map((item, idx: number) => { item.idx = idx; const {matched = null} = item; item.prevMatched = matched; @@ -23,9 +37,15 @@ function computeSelection(filters: any[], items: any[], onlyFast: boolean) { for (const filter of filters) { ws = ws.filter(item => { if (filter.matchItem(item)) { - item.matched = filter.id === FAST ? - "fast" : - (onlyFast ? null : filter.id); + if (filter.id === FAST) { + item.matched = "fast"; + } + else if (!onlyFast && typeof filter.id === "string") { + item.matched = filter.id; + } + else { + item.matched = null; + } } return !item.matched; }); @@ -41,6 +61,9 @@ function computeSelection(filters: any[], items: any[], onlyFast: boolean) { function *computeActiveFiltersGen( filters: Filter[], activeOverrides: Map) { for (const filter of filters) { + if (typeof filter.id !== "string") { + continue; + } const override = activeOverrides.get(filter.id); if (typeof override === "boolean") { if (override) { @@ -59,11 +82,11 @@ function computeActiveFilters( return Array.from(computeActiveFiltersGen(filters, activeOverrides)); } -function filtersToDescs(filters: any[]) { +function filtersToDescs(filters: Filter[]) { return filters.map(f => f.descriptor); } -export async function select(links: any[], media: any[]) { +export async function select(links: BaseItem[], media: BaseItem[]) { const fm = await filters(); const tracker = new WindowStateTracker("select", { minWidth: 700, @@ -85,7 +108,7 @@ export async function select(links: any[], media: any[]) { tracker.track(window.id, port); const overrides = new Map(); - let fast: any = null; + let fast: Filter | null = null; let onlyFast: false; try { fast = await fm.getFastFilter(); @@ -95,16 +118,16 @@ export async function select(links: any[], media: any[]) { } const sendFilters = function(delta = false) { - let {linkFilters, mediaFilters} = fm; + const {linkFilters, mediaFilters} = fm; const alink = computeActiveFilters(linkFilters, overrides); const amedia = computeActiveFilters(mediaFilters, overrides); const sactiveFilters = new Set(); [alink, amedia].forEach( a => a.forEach(filter => sactiveFilters.add(filter.id))); const activeFilters = Array.from(sactiveFilters); - linkFilters = filtersToDescs(linkFilters); - mediaFilters = filtersToDescs(mediaFilters); - port.post("filters", {linkFilters, mediaFilters, activeFilters}); + const linkFilterDescs = filtersToDescs(linkFilters); + const mediaFilterDescs = filtersToDescs(mediaFilters); + port.post("filters", {linkFilterDescs, mediaFilterDescs, activeFilters}); if (fast) { alink.unshift(fast); @@ -128,9 +151,6 @@ export async function select(links: any[], media: any[]) { }); port.on("queue", (msg: any) => { - const selected = new Set(msg.items); - const items = (msg.type === "links" ? links : media); - msg.items = items.filter((item: any, idx: number) => selected.has(idx)); done.resolve(msg); }); @@ -175,7 +195,11 @@ export async function select(links: any[], media: any[]) { sendFilters(false); const type = await Prefs.get("last-type", "links"); port.post("items", {type, links, media}); - const result = await done; + const {items, options} = await done; + const selectedIndexes = new Set(items); + const selectedList = (options.type === "links" ? links : media); + const selectedItems = selectedList.filter( + (item: BaseItem, idx: number) => selectedIndexes.has(idx)); for (const [filter, override] of overrides) { const f = fm.get(filter); if (f) { @@ -183,7 +207,7 @@ export async function select(links: any[], media: any[]) { } } await fm.save(); - return result; + return {items: selectedItems, options}; } finally { fm.off("changed", sendFilters); diff --git a/lib/single.ts b/lib/single.ts index 4182e90..ce2e2c6 100644 --- a/lib/single.ts +++ b/lib/single.ts @@ -7,8 +7,10 @@ import { WindowStateTracker } from "./windowstatetracker"; import { Promised, timeout } from "./util"; import { donate } from "./windowutils"; import { windows } from "./browser"; +// eslint-disable-next-line no-unused-vars +import { BaseItem } from "./item"; -export async function single(item: any) { +export async function single(item: BaseItem | null) { const tracker = new WindowStateTracker("single", { minWidth: 700, minHeight: 460 @@ -46,7 +48,9 @@ export async function single(item: any) { donate(); }); - port.post("item", item); + if (item) { + port.post("item", {item}); + } return await done; } finally { diff --git a/uikit/lib/basetable.ts b/uikit/lib/basetable.ts index 92749c8..1ee25ae 100644 --- a/uikit/lib/basetable.ts +++ b/uikit/lib/basetable.ts @@ -14,6 +14,8 @@ import { } from "./tablesymbols"; import { InvalidatedSet, UpdateRecord } from "./tableutil"; import { addClass, clampUInt, IS_MAC } from "./util"; +// eslint-disable-next-line no-unused-vars +import { TableConfig } from "./config"; const ROWS_SMALL_UPDATE = 5; const PIXEL_PREC = 5; @@ -79,7 +81,7 @@ export class BaseTable extends AbstractTable { [COLS]: Columns; - constructor(elem: any, config: any, version?: number) { + constructor(elem: any, config: TableConfig | null, version?: number) { config = (config && config.version === version && config) || {}; super(); @@ -121,9 +123,9 @@ export class BaseTable extends AbstractTable { this.makeDOM(config); } - makeDOM(config: any) { + makeDOM(config: TableConfig) { const configColumns = "columns" in config ? config.columns : null; - const cols = this[COLS] = new Columns(this, configColumns); + const cols = this[COLS] = new Columns(this, configColumns || null); const container = document.createElement("div"); const thead = document.createElement("div"); @@ -241,7 +243,7 @@ export class BaseTable extends AbstractTable { return new SelectionRange(firstIdx, lastIdx); } - get config() { + get config(): TableConfig { return { version: this.version, columns: this.columnConfig diff --git a/uikit/lib/column.ts b/uikit/lib/column.ts index d7ac818..d529999 100644 --- a/uikit/lib/column.ts +++ b/uikit/lib/column.ts @@ -1,14 +1,12 @@ "use strict"; +// License: MIT /* eslint-disable no-unused-vars */ import { TableEvents } from "./tableevents"; import {addClass, debounce, sum} from "./util"; import {EventEmitter} from "./events"; import {APOOL} from "./animationpool"; - -/* eslint-enable no-unused-vars */ - -// License: MIT +import { ColumnConfig, ColumnConfigs } from "./config"; const PIXLIT_WIDTH = 2; const MIN_COL_WIDTH = 16; @@ -55,8 +53,7 @@ export class Column extends EventEmitter { columns: Columns, col: HTMLTableHeaderCellElement, id: number, - config: any) { - config = config || {}; + config: ColumnConfig | null) { super(); this.columns = columns; this.elem = col; @@ -89,7 +86,7 @@ export class Column extends EventEmitter { this.elem.appendChild(containerElem); - if ("visible" in config) { + if (config) { this.visible = config.visible; } this.initWidths(config); @@ -148,18 +145,18 @@ export class Column extends EventEmitter { return Math.max(0, this.currentWidth - this.minWidth); } - get config() { + get config(): ColumnConfig { return { visible: this.visible, width: this.currentWidth, }; } - initWidths(config: any) { + initWidths(config: ColumnConfig | null) { const style = getComputedStyle(this.elem, null); this.minWidth = toPixel(style.getPropertyValue("min-width"), MIN_COL_WIDTH); this.maxWidth = toPixel(style.getPropertyValue("max-width"), 0); - const width = config.width || this.baseWidth; + const width = (config && config.width) || this.baseWidth; this.setWidth(width); } @@ -236,7 +233,7 @@ export class Columns extends EventEmitter { public visible: Column[]; - constructor(table: any, config: any) { + constructor(table: any, config: ColumnConfigs | null) { config = config || {}; super(); this.table = table; @@ -247,7 +244,9 @@ export class Columns extends EventEmitter { this.named = new Map(); this.cols = Array.from(table.elem.querySelectorAll("th")). map((colEl: HTMLTableHeaderCellElement, colid: number) => { - const columnConfig = colEl.id in config ? config[colEl.id] : null; + const columnConfig = config && colEl.id in config ? + config[colEl.id] : + null; const col = new Column(this, colEl, colid, columnConfig); col.on("gripmoved", this.gripmoved); this.named.set(colEl.id, col); @@ -261,7 +260,7 @@ export class Columns extends EventEmitter { Object.seal(this); } - get config() { + get config(): ColumnConfigs { const rv: any = {}; for (const c of this.cols) { rv[c.elem.id] = c.config; diff --git a/uikit/lib/config.ts b/uikit/lib/config.ts new file mode 100644 index 0000000..7262c20 --- /dev/null +++ b/uikit/lib/config.ts @@ -0,0 +1,13 @@ +"use strict"; +// License: MIT + +export interface ColumnConfig { + visible: boolean; + width: number; +} +export type ColumnConfigs ={ [name: string]: ColumnConfig }; + +export interface TableConfig { + version?: number; + columns?: ColumnConfigs; +} diff --git a/uikit/lib/contextmenu.ts b/uikit/lib/contextmenu.ts index aafe3da..8ef2fa7 100644 --- a/uikit/lib/contextmenu.ts +++ b/uikit/lib/contextmenu.ts @@ -33,6 +33,14 @@ export interface MenuPosition { clientY: number; } +interface MenuOptions { + disabled?: string; + allowClick?: string; + icon?: string; + key?: string; + autoHide?: string; +} + export class MenuItemBase { public readonly owner: ContextMenu; @@ -44,7 +52,7 @@ export class MenuItemBase { public readonly key: string; - public readonly autohide: boolean; + public readonly autoHide: boolean; public readonly elem: HTMLLIElement; @@ -54,18 +62,16 @@ export class MenuItemBase { public readonly keyElem: HTMLSpanElement; - constructor(owner: ContextMenu, id = "", text = "", { - key = "", icon = "", autohide = Object() - }) { + constructor(owner: ContextMenu, id = "", text = "", options: MenuOptions) { this.owner = owner; if (!id) { id = `contextmenu-${++ids}`; } this.id = id; this.text = text || ""; - this.icon = icon || ""; - this.key = key || ""; - this.autohide = autohide !== "false" && autohide !== false; + this.icon = options.icon || ""; + this.key = options.key || ""; + this.autoHide = options.autoHide !== "false"; this.elem = document.createElement("li"); this.elem.id = this.id; @@ -96,13 +102,14 @@ export class MenuItemBase { } export class MenuItem extends MenuItemBase { - constructor(owner: ContextMenu, id = "", text = "", options: any = {}) { + constructor( + owner: ContextMenu, id = "", text = "", options: MenuOptions = {}) { options = options || {}; super(owner, id, text, options); - this.disabled = !!options.disabled; + this.disabled = options.disabled === "true"; this.elem.setAttribute("aria-role", "menuitem"); this.elem.addEventListener( - "click", () => this.owner.emit("clicked", this.id, this.autohide)); + "click", () => this.owner.emit("clicked", this.id, this.autoHide)); } get disabled() { @@ -132,7 +139,8 @@ export class SubMenuItem extends MenuItemBase { public readonly expandElem: HTMLSpanElement; - constructor(owner: ContextMenu, id = "", text = "", options: any = {}) { + constructor( + owner: ContextMenu, id = "", text = "", options: MenuOptions = {}) { super(owner, id, text, options); this.elem.setAttribute("aria-role", "menuitem"); this.elem.setAttribute("aria-haspopup", "true"); @@ -145,8 +153,8 @@ export class SubMenuItem extends MenuItemBase { this.expandElem.textContent = "►"; this.elem.appendChild(this.expandElem); this.elem.addEventListener("click", event => { - if (options.allowClick) { - this.owner.emit("clicked", this.id, this.autohide); + if (options.allowClick === "true") { + this.owner.emit("clicked", this.id, this.autoHide); } event.stopPropagation(); event.preventDefault(); @@ -160,7 +168,7 @@ export class SubMenuItem extends MenuItemBase { this.owner.on("showing", () => { this.menu.dismiss(); }); - this.menu.on("clicked", (...args: any) => { + this.menu.on("clicked", (...args: any[]) => { this.owner.emit("clicked", ...args); }); } @@ -215,7 +223,7 @@ export class SubMenuItem extends MenuItemBase { export class ContextMenu extends EventEmitter { id: string; - items: any[]; + items: MenuItemBase[]; itemMap: Map; @@ -223,7 +231,7 @@ export class ContextMenu extends EventEmitter { showing: boolean; - _maybeDismiss: any; + _maybeDismiss: (this: Window, ev: MouseEvent) => any; constructor(el?: any) { super(); @@ -348,10 +356,12 @@ export class ContextMenu extends EventEmitter { return this.itemMap.get(id); } - add(item: MenuItemBase, before: any = "") { + add(item: MenuItemBase, before: MenuItemBase | string = "") { let idx = this.items.length; if (before) { - before = before.id || before; + if (typeof before !== "string") { + before = before.id; + } const ni = this.items.findIndex(i => i.id === before); if (ni >= 0) { idx = ni; @@ -366,8 +376,8 @@ export class ContextMenu extends EventEmitter { this.itemMap.set(item.id, item); } - remove(item: any) { - const id = item.id || item; + remove(item: MenuItemBase | string) { + const id = typeof item === "string" ? item : item.id; const idx = this.items.findIndex(i => i.id === id); if (idx >= 0) { this.items.splice(idx, 1); diff --git a/uikit/lib/modal.ts b/uikit/lib/modal.ts index bee054a..ecefa1a 100644 --- a/uikit/lib/modal.ts +++ b/uikit/lib/modal.ts @@ -1,15 +1,20 @@ "use strict"; // License: MIT -interface ModalButton { +export interface ModalButton { title: string; value: string; default?: boolean; dismiss?: boolean; } +interface Promised { + resolve: Function; + reject: Function; +} + export default abstract class ModalDialog { - private _showing: any; + private _showing: Promised | null; private _dismiss: HTMLButtonElement | null; @@ -107,7 +112,10 @@ export default abstract class ModalDialog { ]; } - done(button: any) { + done(button: ModalButton) { + if (!this._showing) { + return; + } const value = this.convertValue(button.value); if (button.dismiss) { this._showing.reject(new Error(value)); @@ -130,7 +138,7 @@ export default abstract class ModalDialog { } - async show() { + async show(): Promise { if (this._showing) { throw new Error("Double show"); } diff --git a/uikit/lib/row.ts b/uikit/lib/row.ts index 27b0ae7..10e1659 100644 --- a/uikit/lib/row.ts +++ b/uikit/lib/row.ts @@ -38,7 +38,7 @@ class Hover { private hovering: boolean; - private timer: any; + private timer: number | null; constructor(row: Row) { this.row = row; @@ -62,7 +62,7 @@ class Hover { this.elem.addEventListener("mousemove", this.onmove, {passive: true}); this.x = evt.clientX; this.y = evt.clientY; - this.timer = setTimeout(this.onhover, HOVER_TIME); + this.timer = window.setTimeout(this.onhover, HOVER_TIME); } onleave() { @@ -93,7 +93,7 @@ class Hover { if (this.timer) { clearTimeout(this.timer); } - this.timer = setTimeout(this.onhover, HOVER_TIME); + this.timer = window.setTimeout(this.onhover, HOVER_TIME); } } diff --git a/uikit/lib/tableevents.ts b/uikit/lib/tableevents.ts index 70a6b0c..64f8151 100644 --- a/uikit/lib/tableevents.ts +++ b/uikit/lib/tableevents.ts @@ -12,6 +12,8 @@ import {Row} from "./row"; import {APOOL} from "./animationpool"; import {COLS, ROWCACHE, VISIBLE} from "./tablesymbols"; import {ContextMenu, MenuItem} from "./contextmenu"; +// eslint-disable-next-line no-unused-vars +import { TableConfig } from "./config"; const RESIZE_DEBOUNCE = 500; const SCROLL_DEBOUNCE = 250; @@ -19,7 +21,7 @@ const SCROLL_DEBOUNCE = 250; export class TableEvents extends BaseTable { private oldVisibleTop: number; - constructor(elem: any, config?: any, version?: number) { + constructor(elem: any, config: TableConfig | null, version?: number) { super(elem, config, version); const {selection} = this; selection.on("selection-added", this.selectionAdded.bind(this)); @@ -172,7 +174,7 @@ export class TableEvents extends BaseTable { ctx, id, col.spanElem.textContent || "", - {autohide: "false"}); + {autoHide: "false"}); ctx.add(item); item.iconElem.textContent = col.visible ? "✓" : " "; ctx.on(id, async () => { diff --git a/uikit/lib/tableutil.ts b/uikit/lib/tableutil.ts index 8c50d42..ae32bd1 100644 --- a/uikit/lib/tableutil.ts +++ b/uikit/lib/tableutil.ts @@ -8,6 +8,8 @@ import { Row } from "./row"; import {APOOL} from "./animationpool"; import {ROW_CACHE_SIZE, ROW_REUSE_SIZE} from "./constants"; import {clampUInt} from "./util"; +// eslint-disable-next-line no-unused-vars +import { BaseTable } from "./basetable"; export class InvalidatedSet extends Set { @@ -70,7 +72,7 @@ export class UpdateRecord { bottom: number; - constructor(table: any, cols: Column[]) { + constructor(table: BaseTable, cols: Column[]) { this.rowCount = table.rowCount; this.scrollTop = table.visibleTop; this.rowHeight = table.rowHeight; diff --git a/uikit/lib/util.ts b/uikit/lib/util.ts index 65182e6..6c4138b 100644 --- a/uikit/lib/util.ts +++ b/uikit/lib/util.ts @@ -13,14 +13,21 @@ export function addClass(elem: HTMLElement, ...cls: string[]) { } } +interface Timer { + args: any[]; +} + export function debounce(fn: Function, to: number) { - let timer: any; + let timer: Timer | null; return function(...args: any[]) { if (timer) { timer.args = args; return; } setTimeout(function() { + if (!timer) { + return; + } const {args} = timer; timer = null; try { @@ -38,7 +45,7 @@ function sumreduce(p: number, c: number) { return p + c; } -export function sum(arr: any[]) { +export function sum(arr: number[]) { return arr.reduce(sumreduce, 0); } diff --git a/windows/dropdown.ts b/windows/dropdown.ts index 304e7bb..3f60fa3 100644 --- a/windows/dropdown.ts +++ b/windows/dropdown.ts @@ -13,7 +13,7 @@ export class Dropdown extends EventEmitter { select: HTMLSelectElement; - constructor(el: string, options: any[] = []) { + constructor(el: string, options: string[] = []) { super(); let input = document.querySelector(el); if (!input || !input.parentElement) { diff --git a/windows/manager.html b/windows/manager.html index 6f781a4..fd942a6 100644 --- a/windows/manager.html +++ b/windows/manager.html @@ -128,8 +128,8 @@