diff --git a/lib/about.ts b/lib/about.ts index 8b5d9c3a..8875af13 100644 --- a/lib/about.ts +++ b/lib/about.ts @@ -6,13 +6,13 @@ export const About = function (picturesSource: string, picturesLicense: string): const iconConfig = window.config.icon; // apply custom styling from config.json to reflect changes in config.json - const applyColor = (selector: string, key: string) => { + const applyColor = (selector: string, key: keyof typeof iconConfig) => { // this applies the color config like Leaflet circleMarker config does const el = d.querySelector(selector) as HTMLElement; const cfgIcon = iconConfig[key]; - el.style.backgroundColor = cfgIcon.fillColor; - el.style.borderColor = cfgIcon.color; + el.style.backgroundColor = cfgIcon.fillColor ?? ""; + el.style.borderColor = cfgIcon.color ?? ""; }; applyColor(".legend-new .symbol", "new"); applyColor(".legend-online .symbol", "online"); diff --git a/lib/config_default.ts b/lib/config_default.ts index 840ed7b0..66dac260 100644 --- a/lib/config_default.ts +++ b/lib/config_default.ts @@ -182,7 +182,7 @@ export interface Config { devicePicturesSource: string; devicePicturesLicense: string; geo?: Geo[]; - fixedCenter: LatLngBoundsExpression; + fixedCenter?: LatLngBoundsExpression; } export const config: Config = { diff --git a/lib/container.ts b/lib/container.ts index 7ba277dd..86a54d78 100644 --- a/lib/container.ts +++ b/lib/container.ts @@ -11,19 +11,15 @@ export const Container = function (tag?: string): CanRender & CanAdd { tag = "div"; } - const self = { - add: undefined, - render: undefined, - }; - - let container = document.createElement(tag); - - self.add = function add(d: CanRender) { - d.render(container); - }; + const container = document.createElement(tag); - self.render = function render(el: HTMLElement) { - el.appendChild(container); + const self: CanRender & CanAdd = { + add(d: CanRender) { + d.render(container); + }, + render(el: HTMLElement) { + el.appendChild(container); + }, }; return self; diff --git a/lib/datadistributor.ts b/lib/datadistributor.ts index c6043748..015dbcd2 100644 --- a/lib/datadistributor.ts +++ b/lib/datadistributor.ts @@ -42,7 +42,7 @@ export interface GenericFilter extends Filter { export type FilterMethod = (node: Node) => boolean; export const DataDistributor = function () { - let targets = []; + let targets: CanSetData[] = []; let filterObservers: CanFiltersChanged[] = []; let filters: Filter[] = []; let filteredData: ObjectsLinksAndNodes; @@ -103,7 +103,7 @@ export const DataDistributor = function () { let newItem = true; filters.forEach(function (oldFilter: Filter) { - if (oldFilter.getKey && oldFilter.getKey() === filter.getKey()) { + if (oldFilter.getKey && filter.getKey && oldFilter.getKey() === filter.getKey()) { removeFilter(oldFilter); newItem = false; } diff --git a/lib/filters/filtergui.ts b/lib/filters/filtergui.ts index 3d389e24..50049604 100644 --- a/lib/filters/filtergui.ts +++ b/lib/filters/filtergui.ts @@ -11,12 +11,12 @@ export const FilterGui = function (distributor: ReturnType any)[] = []; + let refreshFunctions: ((preserveFocus?: boolean) => void)[] = []; let timer: ReturnType; let input = document.createElement("input"); diff --git a/lib/filters/nodefilter.ts b/lib/filters/nodefilter.ts index 2dcc5740..01dc0aeb 100644 --- a/lib/filters/nodefilter.ts +++ b/lib/filters/nodefilter.ts @@ -5,10 +5,15 @@ export const NodeFilter = function (filter: FilterMethod) { let node: ObjectsLinksAndNodes = Object.create(data); node.nodes = { all: [], lost: [], new: [], offline: [], online: [] }; - for (let key in data.nodes) { - if (data.nodes.hasOwnProperty(key)) { - node.nodes[key] = data.nodes[key].filter(filter); - } + const nodeKeys: (keyof import("../datadistributor.js").NodesByState)[] = [ + "all", + "lost", + "new", + "offline", + "online", + ]; + for (const key of nodeKeys) { + node.nodes[key] = data.nodes[key].filter(filter); } node.links = data.links.filter(function (d) { diff --git a/lib/forcegraph.ts b/lib/forcegraph.ts index f3b98842..04a28742 100644 --- a/lib/forcegraph.ts +++ b/lib/forcegraph.ts @@ -7,33 +7,41 @@ import * as d3Timer from "d3-timer"; import * as d3Zoom from "d3-zoom"; import math from "./utils/math.js"; -import draw, { MapLink } from "./forcegraph/draw.js"; +import draw, { MapLink, MapNode } from "./forcegraph/draw.js"; import { Sidebar } from "./sidebar.js"; import { ClientPointEvent } from "d3-selection"; import { ObjectsLinksAndNodes } from "./datadistributor.js"; -import { Link, Node } from "./utils/node.js"; +import { Link, Node, NodeId } from "./utils/node.js"; export const ForceGraph = function (linkScale: (t: any) => any, sidebar: ReturnType) { - const self = { - setData: undefined, - resetView: undefined, - gotoNode: undefined, - gotoLink: undefined, - gotoLocation: undefined, - destroy: undefined, - render: undefined, + const self: { + setData: (data: ObjectsLinksAndNodes) => void; + resetView: () => void; + gotoNode: (nodeData: Node, nodeDict: { [k: NodeId]: Node }) => void; + gotoLink: (linkData: Link[]) => void; + gotoLocation: () => void; + destroy: () => void; + render: (d: HTMLElement) => void; + } = { + setData: () => {}, + resetView: () => {}, + gotoNode: (_a, _b) => {}, + gotoLink: () => {}, + gotoLocation: () => {}, + destroy: () => {}, + render: () => {}, }; - let el: HTMLElement; // Element to display graph in + let el: HTMLElement; let canvas: HTMLCanvasElement; let ctx: CanvasRenderingContext2D; let force: d3Force.Simulation | null; let forceLink: d3Force.Force & { links?: (links: any) => any }; let transform = d3Zoom.zoomIdentity; - let intNodes = []; - let dictNodes = {}; - let intLinks = []; - let movetoTimer: NodeJS.Timeout; + let intNodes: MapNode[] = []; + let dictNodes: Record = {}; + let intLinks: MapLink[] = []; + let movetoTimer: ReturnType; let initial = 1.8; let NODE_RADIUS_DRAG = 10; @@ -55,11 +63,11 @@ export const ForceGraph = function (linkScale: (t: any) => any, sidebar: ReturnT } function transformPosition(p: { k: number; x: number; y: number }) { - // @ts-ignore To fix those some further refactoring is needed or else the nodes jump around when clicking near them + // @ts-expect-error d3 zoom transform mutation transform.x = p.x; - // @ts-ignore + // @ts-expect-error d3 zoom transform mutation transform.y = p.y; - // @ts-ignore + // @ts-expect-error d3 zoom transform mutation transform.k = p.k; } @@ -71,27 +79,27 @@ export const ForceGraph = function (linkScale: (t: any) => any, sidebar: ReturnT }, 300); return; } - let result = callback(); - let x = result[0]; - let y = result[1]; - let k = result[2]; - let end = { + const result = callback(); + const x = result[0]; + const y = result[1]; + const k = result[2]; + const end = { k: k, x: (canvas.width + sidebar.getWidth()) / 2 - x * k, y: canvas.height / 2 - y * k, }; - let start = { x: transform.x, y: transform.y, k: transform.k }; + const start = { x: transform.x, y: transform.y, k: transform.k }; - let interpolate = d3Interpolate.interpolateObject(start, end); + const interpolate = d3Interpolate.interpolateObject(start, end); - let timer = d3Timer.timer(function (t) { + const timer = d3Timer.timer(function (t) { if (t >= ZOOM_ANIMATE_DURATION) { timer.stop(); return; } - let v = interpolate(d3Ease.easeQuadInOut(t / ZOOM_ANIMATE_DURATION)); + const v = interpolate(d3Ease.easeQuadInOut(t / ZOOM_ANIMATE_DURATION)); transformPosition(v); window.requestAnimationFrame(redraw); }); @@ -102,21 +110,21 @@ export const ForceGraph = function (linkScale: (t: any) => any, sidebar: ReturnT return; } - let click = transform.invert([event.clientX, event.clientY]); - let point = { x: click[0], y: click[1] }; - let node = force.find(point.x, point.y, NODE_RADIUS_SELECT); - let router = window.router; + const click = transform.invert([event.clientX, event.clientY]); + const point = { x: click[0], y: click[1] }; + const node = force?.find(point.x, point.y, NODE_RADIUS_SELECT); + const router = window.router; if (node !== undefined) { - // @ts-ignore + // @ts-expect-error d3 simulation node has o router.fullUrl({ node: node.o.node_id }); return; } - let closedLink: MapLink; + let closedLink: MapLink | undefined; let radius = LINK_RADIUS_SELECT; intLinks.forEach(function (link: MapLink) { - let distance = math.distanceLink(point, link.source, link.target); + const distance = math.distanceLink(point, link.source, link.target); if (distance < radius) { closedLink = link; radius = distance; @@ -134,8 +142,8 @@ export const ForceGraph = function (linkScale: (t: any) => any, sidebar: ReturnT ctx.translate(transform.x, transform.y); ctx.scale(transform.k, transform.k); - intLinks.forEach(draw.drawLink); - intNodes.forEach(draw.drawNode); + intLinks.forEach((l) => draw.drawLink(l)); + intNodes.forEach((n) => draw.drawNode(n)); ctx.restore(); } @@ -146,22 +154,22 @@ export const ForceGraph = function (linkScale: (t: any) => any, sidebar: ReturnT forceLink = d3Force .forceLink() .distance(function (node) { - // @ts-ignore + // @ts-expect-error d3 node if (node.o.type.indexOf("vpn") === 0) { return 0; } return 75; }) .strength(function (node) { - // @ts-ignore + // @ts-expect-error d3 node if (node.o.type.indexOf("vpn") === 0) { return 0.02; } - // @ts-ignore + // @ts-expect-error d3 node return Math.max(0.5, node.o.source_tq); }); - let zoom = d3Zoom + const zoom = d3Zoom .zoom() .scaleExtent([ZOOM_MIN, ZOOM_MAX]) .on("zoom", function (event) { @@ -180,11 +188,11 @@ export const ForceGraph = function (linkScale: (t: any) => any, sidebar: ReturnT .on("tick", redraw) .alphaDecay(0.025); - let drag = d3Drag + const drag = d3Drag .drag() .subject(function (event) { - let e = transform.invert([event.x, event.y]); - let node = force.find(e[0], e[1], NODE_RADIUS_DRAG); + const e = transform.invert([event.x, event.y]); + const node = force?.find(e[0], e[1], NODE_RADIUS_DRAG); if (node !== undefined) { node.x = event.x; @@ -194,7 +202,7 @@ export const ForceGraph = function (linkScale: (t: any) => any, sidebar: ReturnT return undefined; }) .on("start", function (event) { - if (!event.active) { + if (!event.active && force) { force.alphaTarget(FORCE_ALPHA).restart(); } event.subject.fx = transform.invertX(event.subject.x); @@ -205,16 +213,19 @@ export const ForceGraph = function (linkScale: (t: any) => any, sidebar: ReturnT event.subject.fy = transform.invertY(event.y); }) .on("end", function (event) { - if (!event.active) { + if (!event.active && force) { force.alphaTarget(0); } event.subject.fx = null; event.subject.fy = null; }); - canvas = d3Selection.select(el).append("canvas").on("click", onClick).call(drag).call(zoom).node(); + const canvasEl = d3Selection.select(el).append("canvas").on("click", onClick); + canvasEl.call(drag as unknown as (selection: typeof canvasEl) => void); + canvasEl.call(zoom as unknown as (selection: typeof canvasEl) => void); + canvas = canvasEl.node()!; - ctx = canvas.getContext("2d"); + ctx = canvas.getContext("2d")!; draw.setCTX(ctx); window.addEventListener("resize", function () { @@ -223,10 +234,12 @@ export const ForceGraph = function (linkScale: (t: any) => any, sidebar: ReturnT }); self.setData = function setData(data: ObjectsLinksAndNodes) { + const nd = data.nodeDict ?? {}; + intNodes = data.nodes.all.map(function (nodeData) { let node = dictNodes[nodeData.node_id]; if (!node) { - node = {}; + node = {} as MapNode; dictNodes[nodeData.node_id] = node; } @@ -237,7 +250,7 @@ export const ForceGraph = function (linkScale: (t: any) => any, sidebar: ReturnT intLinks = data.links .filter(function (link) { - return data.nodeDict[link.source.node_id].is_online && data.nodeDict[link.target.node_id].is_online; + return nd[link.source.node_id].is_online && nd[link.target.node_id].is_online; }) .map(function (link) { return { @@ -246,13 +259,15 @@ export const ForceGraph = function (linkScale: (t: any) => any, sidebar: ReturnT target: dictNodes[link.target.node_id], color: linkScale(link.source_tq), color_to: linkScale(link.target_tq), + x: 0, + y: 0, }; }); - force.nodes(intNodes); - forceLink.links(intLinks); + force!.nodes(intNodes); + forceLink.links!(intLinks); - force.alpha(initial).velocityDecay(0.15).restart(); + force!.alpha(initial).velocityDecay(0.15).restart(); if (initial === 1.8) { initial = 0.5; } @@ -262,33 +277,37 @@ export const ForceGraph = function (linkScale: (t: any) => any, sidebar: ReturnT self.resetView = function resetView() { moveTo(function calcToReset() { - let config = window.config; + const config = window.config; draw.setHighlight(null); return [0, 0, (ZOOM_MIN + config.forceGraph.zoomModifier) / 2]; }, true); }; - self.gotoNode = function gotoNode(nodeData: Node) { + self.gotoNode = function gotoNode(nodeData: Node, _nodeDict: { [k: NodeId]: Node }) { moveTo(function calcToNode() { draw.setHighlight({ type: "node", id: nodeData.node_id }); - let node = dictNodes[nodeData.node_id]; + const node = dictNodes[nodeData.node_id]; if (node) { - return [node.x, node.y, (ZOOM_MAX + 1) / 2]; + return [node.x!, node.y!, (ZOOM_MAX + 1) / 2]; } - return self.resetView(); + const config = window.config; + draw.setHighlight(null); + return [0, 0, (ZOOM_MIN + config.forceGraph.zoomModifier) / 2]; }); }; self.gotoLink = function gotoLink(linkData: Link[]) { moveTo(function calcToLink() { draw.setHighlight({ type: "link", id: linkData[0].id }); - let link = intLinks.find(function (link) { + const link = intLinks.find(function (link) { return link.o.id === linkData[0].id; }); if (link) { - return [(link.source.x + link.target.x) / 2, (link.source.y + link.target.y) / 2, ZOOM_MAX / 2 + ZOOM_MIN]; + return [(link.source.x! + link.target.x!) / 2, (link.source.y! + link.target.y!) / 2, ZOOM_MAX / 2 + ZOOM_MIN]; } - return self.resetView(); + const config = window.config; + draw.setHighlight(null); + return [0, 0, (ZOOM_MIN + config.forceGraph.zoomModifier) / 2]; }); }; @@ -297,8 +316,8 @@ export const ForceGraph = function (linkScale: (t: any) => any, sidebar: ReturnT }; self.destroy = function destroy() { - force.stop(); - canvas.parentNode.removeChild(canvas); + force?.stop(); + canvas.parentNode?.removeChild(canvas); force = null; if (el.parentNode) { diff --git a/lib/forcegraph/draw.ts b/lib/forcegraph/draw.ts index 4ff66c2d..0821dd8f 100644 --- a/lib/forcegraph/draw.ts +++ b/lib/forcegraph/draw.ts @@ -16,16 +16,7 @@ export interface MapLink extends Point { color_to: string; } -const self = { - drawNode: undefined, - drawLink: undefined, - setCTX: undefined, - setHighlight: undefined, - setTransform: undefined, - setMaxArea: undefined, -}; - -let ctx: CanvasRenderingContext2D; // Canvas context +let ctx: CanvasRenderingContext2D; let width: number; let height: number; let transform: ZoomTransform; @@ -36,7 +27,7 @@ let LINE_RADIUS = 12; function drawDetailNode(node: MapNode) { if (transform.k > 1 && node.o.is_online) { - let config = window.config; + const config = window.config; helper.positionClients(ctx, node, Math.PI, node.o, 15); ctx.beginPath(); let name = node.o.node_id; @@ -51,7 +42,7 @@ function drawDetailNode(node: MapNode) { function drawHighlightNode(node: MapNode) { if (highlight && highlight.type === "node" && node.o.node_id === highlight.id) { - let config = window.config; + const config = window.config; ctx.arc(node.x, node.y, NODE_RADIUS * 1.5, 0, 2 * Math.PI); ctx.fillStyle = config.forceGraph.highlightColor; ctx.fill(); @@ -61,7 +52,7 @@ function drawHighlightNode(node: MapNode) { function drawHighlightLink(link: MapLink, to: number[]) { if (highlight && highlight.type === "link" && link.o.id === highlight.id) { - let config = window.config; + const config = window.config; ctx.lineTo(to[0], to[1]); ctx.strokeStyle = config.forceGraph.highlightColor; ctx.lineWidth = LINE_RADIUS * 2; @@ -72,98 +63,100 @@ function drawHighlightLink(link: MapLink, to: number[]) { return to; } -self.drawNode = function drawNode(node: MapNode) { - if ( - node.x < transform.invertX(0) || - node.y < transform.invertY(0) || - transform.invertX(width) < node.x || - transform.invertY(height) < node.y - ) { - return; - } - ctx.beginPath(); - - drawHighlightNode(node); - - let config = window.config; - if (node.o.is_online) { - ctx.arc(node.x, node.y, 8, 0, 2 * Math.PI); - if (node.o.is_gateway) { - ctx.rect(node.x - 9, node.y - 9, 18, 18); +const self = { + drawNode(node: MapNode) { + if ( + node.x < transform.invertX(0) || + node.y < transform.invertY(0) || + transform.invertX(width) < node.x || + transform.invertY(height) < node.y + ) { + return; } - if (helper.hasUplink(node.o)) { - ctx.fillStyle = config.forceGraph.nodeUplinkColor; + ctx.beginPath(); + + drawHighlightNode(node); + + const config = window.config; + if (node.o.is_online) { + ctx.arc(node.x, node.y, 8, 0, 2 * Math.PI); + if (node.o.is_gateway) { + ctx.rect(node.x - 9, node.y - 9, 18, 18); + } + if (helper.hasUplink(node.o)) { + ctx.fillStyle = config.forceGraph.nodeUplinkColor ?? "#000"; + } else { + ctx.fillStyle = config.forceGraph.nodeColor ?? "#000"; + } } else { - ctx.fillStyle = config.forceGraph.nodeColor; + ctx.arc(node.x, node.y, 6, 0, 2 * Math.PI); + ctx.fillStyle = config.forceGraph.nodeOfflineColor; } - } else { - ctx.arc(node.x, node.y, 6, 0, 2 * Math.PI); - ctx.fillStyle = config.forceGraph.nodeOfflineColor; - } - ctx.fill(); - - drawDetailNode(node); -}; + ctx.fill(); -self.drawLink = function drawLink(link: MapLink) { - let config = window.config; - let zero = transform.invert([0, 0]); - let area = transform.invert([width, height]); - if ( - (link.source.x < zero[0] && link.target.x < zero[0]) || - (link.source.y < zero[1] && link.target.y < zero[1]) || - (link.source.x > area[0] && link.target.x > area[0]) || - (link.source.y > area[1] && link.target.y > area[1]) - ) { - return; - } - ctx.beginPath(); - ctx.moveTo(link.source.x, link.source.y); - let to = [link.target.x, link.target.y]; + drawDetailNode(node); + }, + + drawLink(link: MapLink) { + const config = window.config; + const zero = transform.invert([0, 0]); + const area = transform.invert([width, height]); + if ( + (link.source.x < zero[0] && link.target.x < zero[0]) || + (link.source.y < zero[1] && link.target.y < zero[1]) || + (link.source.x > area[0] && link.target.x > area[0]) || + (link.source.y > area[1] && link.target.y > area[1]) + ) { + return; + } + ctx.beginPath(); + ctx.moveTo(link.source.x, link.source.y); + let to = [link.target.x, link.target.y]; - to = drawHighlightLink(link, to); + to = drawHighlightLink(link, to); - let grd = ctx.createLinearGradient(link.source.x, link.source.y, link.target.x, link.target.y); + const grd = ctx.createLinearGradient(link.source.x, link.source.y, link.target.x, link.target.y); - ctx.lineTo(to[0], to[1]); - if (link.o.type.indexOf("vpn") === 0) { - ctx.globalAlpha = 0.2; - ctx.lineWidth = 1.5; - } else if (link.o.type.indexOf("other") === 0) { - ctx.globalAlpha = 1; - ctx.lineWidth = 3.5; - if (link.o.source_tq >= 0.99 && link.o.target_tq >= 0.99) { - link.color = config.forceGraph.otherLinkColor; - link.color_to = config.forceGraph.otherLinkColor; + ctx.lineTo(to[0], to[1]); + if (link.o.type.indexOf("vpn") === 0) { + ctx.globalAlpha = 0.2; + ctx.lineWidth = 1.5; + } else if (link.o.type.indexOf("other") === 0) { + ctx.globalAlpha = 1; + ctx.lineWidth = 3.5; + if (link.o.source_tq >= 0.99 && link.o.target_tq >= 0.99) { + link.color = config.forceGraph.otherLinkColor; + link.color_to = config.forceGraph.otherLinkColor; + } + } else { + ctx.globalAlpha = 0.8; + ctx.lineWidth = 2.5; } - } else { - ctx.globalAlpha = 0.8; - ctx.lineWidth = 2.5; - } - grd.addColorStop(0.45, link.color); - grd.addColorStop(0.55, link.color_to); - ctx.strokeStyle = grd; - ctx.stroke(); - ctx.globalAlpha = 1; -}; + grd.addColorStop(0.45, link.color); + grd.addColorStop(0.55, link.color_to); + ctx.strokeStyle = grd; + ctx.stroke(); + ctx.globalAlpha = 1; + }, -self.setCTX = function setCTX(newValue: CanvasRenderingContext2D) { - ctx = newValue; -}; + setCTX(newValue: CanvasRenderingContext2D) { + ctx = newValue; + }, -self.setHighlight = function setHighlight(newValue: Highlight) { - highlight = newValue; -}; + setHighlight(newValue: Highlight) { + highlight = newValue; + }, -self.setTransform = function setTransform(newValue: ZoomTransform) { - transform = newValue; -}; + setTransform(newValue: ZoomTransform) { + transform = newValue; + }, -self.setMaxArea = function setMaxArea(newWidth: number, newHeight: number) { - width = newWidth; - height = newHeight; + setMaxArea(newWidth: number, newHeight: number) { + width = newWidth; + height = newHeight; + }, }; export default self; diff --git a/lib/global.d.ts b/lib/global.d.ts index b0be883e..7395331d 100644 --- a/lib/global.d.ts +++ b/lib/global.d.ts @@ -3,6 +3,9 @@ import { Router } from "./utils/router.js"; export {}; +declare module "*.scss"; +declare module "../scss/main.scss"; + declare global { const __APP_VERSION__: string; @@ -10,4 +13,16 @@ declare global { config: Config; router: Router; } + + interface Document { + webkitFullscreenElement?: Element | null; + mozFullScreenElement?: Element | null; + webkitExitFullscreen?: () => void; + mozCancelFullScreen?: () => void; + } + + interface HTMLElement { + webkitRequestFullScreen?: () => void; + mozRequestFullScreen?: () => void; + } } diff --git a/lib/gui.ts b/lib/gui.ts index b2609b51..8dd7537d 100644 --- a/lib/gui.ts +++ b/lib/gui.ts @@ -18,12 +18,13 @@ import { FilterGui } from "./filters/filtergui.js"; import { HostnameFilter } from "./filters/hostname.js"; import * as helper from "./utils/helper.js"; import { Language } from "./utils/language.js"; +import { ObjectsLinksAndNodes } from "./datadistributor.js"; export const Gui = function (language: ReturnType) { - const self = { - setData: undefined, + const self: { setData: (data: ObjectsLinksAndNodes) => void } = { + setData: () => {}, }; - let content: ReturnType; + let content: ReturnType | ReturnType | null = null; let contentDiv: HTMLDivElement; let router = window.router; let config = window.config; diff --git a/lib/infobox/link.ts b/lib/infobox/link.ts index 8e07da48..f98673d7 100644 --- a/lib/infobox/link.ts +++ b/lib/infobox/link.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import { classModule, eventListenersModule, h, init, propsModule, styleModule, VNode } from "snabbdom"; import { _ } from "../utils/language.js"; import * as helper from "../utils/helper.js"; diff --git a/lib/infobox/location.ts b/lib/infobox/location.ts index 89ee2634..90bbb73b 100644 --- a/lib/infobox/location.ts +++ b/lib/infobox/location.ts @@ -74,7 +74,7 @@ export const location = function (el: HTMLElement, position: TargetLocation) { } function copy2clip(id: string) { - let copyField: HTMLTextAreaElement = document.querySelector("#" + id); + const copyField = document.querySelector("#" + id) as HTMLTextAreaElement; copyField.select(); try { document.execCommand("copy"); diff --git a/lib/infobox/main.ts b/lib/infobox/main.ts index 0d5c40dc..c30b44b7 100644 --- a/lib/infobox/main.ts +++ b/lib/infobox/main.ts @@ -1,28 +1,32 @@ import { _ } from "../utils/language.js"; -import { Link } from "./link.js"; -import { Node } from "./node.js"; +import { Link as LinkView } from "./link.js"; +import { Node as NodeView } from "./node.js"; import { location } from "./location.js"; import { Link as LinkData, Node as NodeData, NodeId } from "../utils/node.js"; import { Sidebar } from "../sidebar.js"; import { TargetLocation } from "../utils/router.js"; import { ObjectsLinksAndNodes } from "../datadistributor.js"; +import { Target } from "../utils/router.js"; -export const Main = function (sidebar: ReturnType, linkScale: (t: any) => any) { - const self = { - resetView: undefined, - gotoNode: undefined, - gotoLink: undefined, - gotoLocation: undefined, - setData: undefined, - }; - let el: HTMLDivElement; - let node: ReturnType; - let link: ReturnType; +type InfoboxPanel = { + render: () => void; + setData: (data: ObjectsLinksAndNodes) => void; +}; + +export const Main = function ( + sidebar: ReturnType, + linkScale: (t: any) => any, +): Target & { setData: (nodeOrLinkData: ObjectsLinksAndNodes) => void } { + let el: HTMLDivElement | undefined; + let node: InfoboxPanel | undefined; + let link: InfoboxPanel | undefined; function destroy() { if (el && el.parentNode) { el.parentNode.removeChild(el); - node = link = el = undefined; + node = undefined; + link = undefined; + el = undefined; sidebar.reveal(); } } @@ -37,11 +41,11 @@ export const Main = function (sidebar: ReturnType, linkScale: (t el.scrollIntoView(false); el.classList.add("infobox"); - // @ts-ignore + // @ts-expect-error destroy hook for legacy code el.destroy = destroy; - let router = window.router; - let closeButton = document.createElement("button"); + const router = window.router; + const closeButton = document.createElement("button"); closeButton.classList.add("close"); closeButton.classList.add("ion-close"); closeButton.setAttribute("aria-label", _.t("close")); @@ -51,33 +55,33 @@ export const Main = function (sidebar: ReturnType, linkScale: (t el.appendChild(closeButton); } - self.resetView = destroy; + return { + resetView: destroy, - self.gotoNode = function gotoNode(nodeData: NodeData, nodeDict: { [k: NodeId]: NodeData }) { - create(); - node = Node(el, nodeData, linkScale, nodeDict); - node.render(); - }; + gotoNode(nodeData: NodeData, nodeDict: { [k: NodeId]: NodeData }) { + create(); + node = NodeView(el!, nodeData, linkScale, nodeDict) as unknown as InfoboxPanel; + node.render(); + }, - self.gotoLink = function gotoLink(linkData: LinkData[]) { - create(); - link = Link(el, linkData, linkScale); - link.render(); - }; + gotoLink(linkData: LinkData[]) { + create(); + link = LinkView(el!, linkData, linkScale) as unknown as InfoboxPanel; + link.render(); + }, - self.gotoLocation = function gotoLocation(locationData: TargetLocation) { - create(); - location(el, locationData); - }; + gotoLocation(locationData: TargetLocation) { + create(); + location(el!, locationData); + }, - self.setData = function setData(nodeOrLinkData: ObjectsLinksAndNodes) { - if (typeof node === "object") { - node.setData(nodeOrLinkData); - } - if (typeof link === "object") { - link.setData(nodeOrLinkData); - } + setData(nodeOrLinkData: ObjectsLinksAndNodes) { + if (node) { + node.setData(nodeOrLinkData); + } + if (link) { + link.setData(nodeOrLinkData); + } + }, }; - - return self; }; diff --git a/lib/infobox/node.ts b/lib/infobox/node.ts index 3e090f14..4146d5b1 100644 --- a/lib/infobox/node.ts +++ b/lib/infobox/node.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import { h, classModule, eventListenersModule, init, propsModule, styleModule, VNode } from "snabbdom"; import { _ } from "../utils/language.js"; diff --git a/lib/legend.ts b/lib/legend.ts index 17e7b0e5..358b8179 100644 --- a/lib/legend.ts +++ b/lib/legend.ts @@ -2,68 +2,68 @@ import { _ } from "./utils/language.js"; import * as helper from "./utils/helper.js"; import { Language } from "./utils/language.js"; import { ObjectsLinksAndNodes } from "./datadistributor.js"; +import { CanSetData } from "./datadistributor.js"; +import { CanRender } from "./container.js"; -export const Legend = function (language: ReturnType) { - const self = { - setData: undefined, - render: undefined, - }; - let stats = document.createTextNode(""); - let timestamp = document.createTextNode(""); - - self.setData = function setData(data: ObjectsLinksAndNodes) { - let totalNodes = Object.keys(data.nodeDict).length; - let totalOnlineNodes = data.nodes.online.length; - let totalClients = helper.sum( - data.nodes.online.map(function (node) { - return node.clients; - }), - ); - let totalGateways = helper.sum( - data.nodes.online - .filter(function (node) { - return node.is_gateway; - }) - .map(helper.one), - ); +export const Legend = function (language: ReturnType): CanSetData & CanRender { + const stats = document.createTextNode(""); + const timestamp = document.createTextNode(""); - stats.textContent = - _.t("sidebar.nodes", { total: totalNodes, online: totalOnlineNodes }) + - " " + - _.t("sidebar.clients", { smart_count: totalClients }) + - " " + - _.t("sidebar.gateway", { smart_count: totalGateways }); + return { + setData(data: ObjectsLinksAndNodes) { + const nodeDict = data.nodeDict ?? {}; + const totalNodes = Object.keys(nodeDict).length; + const totalOnlineNodes = data.nodes.online.length; + const totalClients = helper.sum( + data.nodes.online.map(function (node) { + return node.clients; + }), + ); + const totalGateways = helper.sum( + data.nodes.online + .filter(function (node) { + return node.is_gateway; + }) + .map(helper.one), + ); - timestamp.textContent = _.t("sidebar.lastUpdate") + " " + data.timestamp.fromNow(); - }; + stats.textContent = + _.t("sidebar.nodes", { total: totalNodes, online: totalOnlineNodes }) + + " " + + _.t("sidebar.clients", { smart_count: totalClients }) + + " " + + _.t("sidebar.gateway", { smart_count: totalGateways }); - self.render = function render(el: HTMLElement) { - let config = window.config; - let h1 = document.createElement("h1"); - h1.textContent = config.siteName; - el.appendChild(h1); + const ts = data.timestamp; + timestamp.textContent = _.t("sidebar.lastUpdate") + " " + (ts ? ts.fromNow() : ""); + }, - language.languageSelect(el); + render(el: HTMLElement) { + const config = window.config; + const h1 = document.createElement("h1"); + h1.textContent = config.siteName; + el.appendChild(h1); - let p = document.createElement("p"); - p.classList.add("legend"); + language.languageSelect(el); - p.appendChild(stats); - p.appendChild(document.createElement("br")); - p.appendChild(timestamp); + const p = document.createElement("p"); + p.classList.add("legend"); - if (config.linkList) { + p.appendChild(stats); p.appendChild(document.createElement("br")); - config.linkList.forEach(function (link) { - let a = document.createElement("a"); - a.innerText = link.title; - a.href = link.href; - p.appendChild(a); - }); - } + p.appendChild(timestamp); - el.appendChild(p); - }; + if (config.linkList) { + p.appendChild(document.createElement("br")); + config.linkList.forEach(function (link) { + const a = document.createElement("a"); + a.innerText = link.title; + a.href = link.href; + p.appendChild(a); + }); + } - return self; + el.appendChild(p); + }, + }; }; diff --git a/lib/linklist.ts b/lib/linklist.ts index fab0c2fc..07cb9ff4 100644 --- a/lib/linklist.ts +++ b/lib/linklist.ts @@ -10,7 +10,7 @@ function linkName(link: Link) { return (link.source ? link.source.hostname : link.id) + " – " + link.target.hostname; } -let headings: Heading[] = [ +const headings: Heading[] = [ { name: "", sort: function (a, b) { @@ -43,15 +43,11 @@ let headings: Heading[] = [ ]; export const Linklist = function (linkScale: (t: any) => any): CanRender & CanSetData { - let router = window.router; - let table = SortTable(headings, 3, renderRow); - const self = { - render: undefined, - setData: undefined, - }; + const router = window.router; + const table = SortTable(headings, 3, renderRow); function renderRow(link: Link) { - let td1Content = [ + const td1Content = [ h( "a", { @@ -88,20 +84,17 @@ export const Linklist = function (linkScale: (t: any) => any): CanRender & CanSe ]); } - self.render = function render(d: HTMLElement) { - let h2 = document.createElement("h2"); - h2.textContent = _.t("node.links"); - d.appendChild(h2); - table.el.classList.add("link-list"); - d.appendChild(table.el); - }; - - self.setData = function setData(d: ObjectsLinksAndNodes) { - table.setData(d.links); - }; - return { - setData: self.setData, - render: self.render, + render(d: HTMLElement) { + const h2 = document.createElement("h2"); + h2.textContent = _.t("node.links"); + d.appendChild(h2); + table.el.classList.add("link-list"); + d.appendChild(table.el); + }, + + setData(d: ObjectsLinksAndNodes) { + table.setData(d.links); + }, }; }; diff --git a/lib/load.ts b/lib/load.ts index 36110f77..8d6111d3 100644 --- a/lib/load.ts +++ b/lib/load.ts @@ -4,7 +4,7 @@ import { main } from "./main.js"; export const load = async () => { const configResponse = await fetch("config.json"); if (!configResponse.ok) { - document.querySelector(".loader").innerHTML = + document.querySelector(".loader")!.innerHTML = "config.json can not be loaded:" + "
" + configResponse.statusText + @@ -16,6 +16,6 @@ export const load = async () => { return; } const config = await configResponse.json(); - globalThis.config = Object.assign(defaultConfig, config); + window.config = Object.assign(defaultConfig, config); main(); }; diff --git a/lib/main.ts b/lib/main.ts index 1ff025c1..c186ba2a 100644 --- a/lib/main.ts +++ b/lib/main.ts @@ -6,15 +6,16 @@ import { Router } from "./utils/router.js"; import { Gui } from "./gui.js"; import { Language } from "./utils/language.js"; import * as helper from "./utils/helper.js"; -import { Link } from "./utils/node.js"; +import { Link, Node } from "./utils/node.js"; +import { ObjectsLinksAndNodes } from "./datadistributor.js"; export const main = () => { function handleData(data: { links: Link[]; nodes: Node[]; timestamp: string }[]) { - let config = window.config; - let timestamp: string; - let nodes = []; - let links = []; - let nodeDict = {}; + const config = window.config; + let timestamp = ""; + let nodes: Node[] = []; + let links: Link[] = []; + const nodeDict: Record = {}; for (let i = 0; i < data.length; ++i) { nodes = nodes.concat(data[i].nodes); @@ -27,17 +28,17 @@ export const main = () => { node.lastseen = moment.utc(node.lastseen).local(); }); - let age = moment().subtract(config.maxAge, "days"); + const age = moment().subtract(config.maxAge, "days"); - let online = nodes.filter(function (node) { + const online = nodes.filter(function (node) { return node.is_online; }); - let offline = nodes.filter(function (node) { + const offline = nodes.filter(function (node) { return !node.is_online; }); - let newnodes = helper.limit("firstseen", age, helper.sortByKey("firstseen", online)); - let lostnodes = helper.limit("lastseen", age, helper.sortByKey("lastseen", offline)); + const newnodes = helper.limit("firstseen", age, helper.sortByKey("firstseen", online as any)); + const lostnodes = helper.limit("lastseen", age, helper.sortByKey("lastseen", offline as any)); nodes.forEach(function (node) { node.neighbours = []; @@ -45,19 +46,21 @@ export const main = () => { }); links.forEach(function (link) { - link.source = nodeDict[link.source]; - link.target = nodeDict[link.target]; + const raw = link as Link & { source: string; target: string }; + link.source = nodeDict[raw.source]; + link.target = nodeDict[raw.target]; link.id = [link.source.node_id, link.target.node_id].join("-"); link.source.neighbours.push({ node: link.target, link: link }); link.target.neighbours.push({ node: link.source, link: link }); try { - link.latlngs = []; - link.latlngs.push(L.latLng(link.source.location.latitude, link.source.location.longitude)); - link.latlngs.push(L.latLng(link.target.location.latitude, link.target.location.longitude)); + const latlngs: L.LatLng[] = []; + latlngs.push(L.latLng(link.source.location.latitude, link.source.location.longitude)); + latlngs.push(L.latLng(link.target.location.latitude, link.target.location.longitude)); + (link as Link & { latlngs: L.LatLng[] }).latlngs = latlngs; - link.distance = link.latlngs[0].distanceTo(link.latlngs[1]); + link.distance = latlngs[0].distanceTo(latlngs[1]); } catch (e) { // ignore exception } @@ -75,12 +78,12 @@ export const main = () => { }, links: links, nodeDict: nodeDict, - }; + } as unknown as ObjectsLinksAndNodes; } - let config = window.config; - let language = Language(); - let router = (window.router = new Router(language)); + const config = window.config; + const language = Language(); + const router = (window.router = new Router(language)); config.dataPath.forEach(function (_element, i) { config.dataPath[i] += "meshviewer.json"; @@ -96,35 +99,41 @@ export const main = () => { .then(function (nodesData) { return new Promise(function (resolve, reject) { let count = 0; - (function waitForLanguage() { - if (Object.keys(_.phrases).length > 0) { + function waitForLanguage() { + if (Object.keys(_.phrases ?? {}).length > 0) { resolve(nodesData); } else if (count > 500) { reject(new Error("translation not loaded after 10 seconds")); } else { - setTimeout(waitForLanguage.bind(this), 20); + setTimeout(waitForLanguage, 20); } count++; - })(); + } + waitForLanguage(); }); }) - .then(function (nodesData: any) { - let gui = Gui(language); - gui.setData(nodesData); - router.setData(nodesData); + .then(function (nodesData) { + const data = nodesData as ObjectsLinksAndNodes; + const gui = Gui(language); + gui.setData(data); + router.setData(data); router.resolve(); window.setInterval(function () { - update().then(function (nodesData: any) { - gui.setData(nodesData); - router.setData(nodesData); + update().then(function (fresh) { + const nd = fresh as ObjectsLinksAndNodes; + gui.setData(nd); + router.setData(nd); }); }, 60000); }) - .catch(function (e) { - document.querySelector(".loader").innerHTML += - e.message + - '


or report to your community'; + .catch(function (e: Error) { + const loader = document.querySelector(".loader"); + if (loader) { + loader.innerHTML += + e.message + + '


or report to your community'; + } console.warn(e); }); }; diff --git a/lib/map.ts b/lib/map.ts index 71a5d5b1..1b2b5540 100644 --- a/lib/map.ts +++ b/lib/map.ts @@ -18,21 +18,29 @@ let options = { }; export const Map = function (linkScale: (t: any) => any, sidebar: ReturnType, buttons: HTMLElement) { - const self = { - setData: undefined, - resetView: undefined, - gotoNode: undefined, - gotoLink: undefined, - gotoLocation: undefined, - destroy: undefined, - render: undefined, + const self: { + setData: (data: ObjectsLinksAndNodes) => void; + resetView: () => void; + gotoNode: (node: Node, nodeDict: { [k: NodeId]: Node }) => void; + gotoLink: (link: Link[]) => void; + gotoLocation: (destination: L.LatLngLiteral & { zoom: number }) => void; + destroy: () => void; + render: (d: HTMLElement) => void; + } = { + setData: () => {}, + resetView: () => {}, + gotoNode: (_n, _d) => {}, + gotoLink: () => {}, + gotoLocation: () => {}, + destroy: () => {}, + render: () => {}, }; let savedView: { center: LatLng; zoom: number } | undefined; let config = window.config; let map: L.Map & { setActiveArea?: any }; let layerControl: L.Control.Layers; - let baseLayers = {}; + let baseLayers: Record = {}; function saveView() { savedView = { @@ -42,7 +50,7 @@ export const Map = function (linkScale: (t: any) => any, sidebar: ReturnType any, sidebar: ReturnType now.getHours()) ) { - item.config.order = item.config.start * -1; + item.config.order = (typeof item.config.start === "number" ? item.config.start : (item.config.end ?? i)) * -1; } else { item.config.order = i; } @@ -149,7 +157,7 @@ export const Map = function (linkScale: (t: any) => any, sidebar: ReturnType any, sidebar: ReturnType any, sidebar: ReturnType void; + getLatLng: () => L.LatLngExpression; + getBounds?: () => L.LatLngBoundsExpression; + resetStyle: () => void; + } + > = {}; + let linkDict: Record< + string, + { + setStyle: (s: any) => void; + getLatLng: () => L.LatLngExpression; + getBounds?: () => L.LatLngBoundsExpression; + resetStyle: () => void; + } + > = {}; let highlight: { type: "node"; o: Node } | { type: "link"; o: Link } | undefined; function resetMarkerStyles( @@ -208,8 +232,8 @@ export const Map = function (linkScale: (t: any) => any, sidebar: ReturnType L.LatLngExpression; getBounds?: () => L.LatLngBoundsExpression }) { let bounds: L.LatLngBoundsExpression; - if ("getBounds" in element) { - bounds = element.getBounds(); + if ("getBounds" in element && typeof element.getBounds === "function") { + bounds = element.getBounds()!; } else { bounds = L.latLngBounds([element.getLatLng()]); } @@ -221,7 +245,9 @@ export const Map = function (linkScale: (t: any) => any, sidebar: ReturnType L.LatLngExpression; getBounds?: () => L.LatLngBoundsExpression }; + let target: + | { setStyle: any; getLatLng: () => L.LatLngExpression; getBounds?: () => L.LatLngBoundsExpression } + | undefined; if (highlight !== undefined) { if (highlight.type === "node" && nodeDict[highlight.o.node_id]) { @@ -238,7 +264,7 @@ export const Map = function (linkScale: (t: any) => any, sidebar: ReturnType any, sidebar: ReturnType void }, + f: (v: boolean) => void, + options?: L.ControlOptions, + ) { L.Util.setOptions(this, options); this.f = f; }, - update: function () { + update: function (this: L.Control & { active: boolean; button: HTMLElement }) { this.button.classList.toggle("active", this.active); }, - set: function (activeValue) { + set: function (this: L.Control & { active: boolean; button: HTMLElement }, activeValue: boolean) { this.active = activeValue; this.update(); }, }); -let LocateButton = ButtonBase.extend({ - onAdd: function () { - let button = L.DomUtil.create("button", "ion-locate"); +const LocateButton = ButtonBase.extend({ + onAdd: function (this: L.Control & { button: HTMLElement }) { + const button = L.DomUtil.create("button", "ion-locate"); button.setAttribute("aria-label", _.t("button.tracking")); L.DomEvent.disableClickPropagation(button); L.DomEvent.addListener(button, "click", this.onClick, this); @@ -38,18 +43,16 @@ let LocateButton = ButtonBase.extend({ return button; }, - onClick: function () { + onClick: function (this: L.Control & { f: (v: boolean) => void; active: boolean }) { this.f(!this.active); }, }); -let CoordsPickerButton = ButtonBase.extend({ - onAdd: function () { - let button = L.DomUtil.create("button", "ion-pin"); +const CoordsPickerButton = ButtonBase.extend({ + onAdd: function (this: L.Control & { button: HTMLElement }) { + const button = L.DomUtil.create("button", "ion-pin"); button.setAttribute("aria-label", _.t("button.location")); - // Click propagation isn't disabled as this causes problems with the - // location picking mode; instead propagation is stopped in onClick(). L.DomEvent.addListener(button, "click", this.onClick, this); this.button = button; @@ -57,15 +60,15 @@ let CoordsPickerButton = ButtonBase.extend({ return button; }, - onClick: function (e) { + onClick: function (this: L.Control & { f: (v: boolean) => void; active: boolean }, e: L.LeafletMouseEvent) { L.DomEvent.stopPropagation(e); this.f(!this.active); }, }); -let RulerButton = ButtonBase.extend({ - onAdd: function () { - let button = L.DomUtil.create("button", "ion-ruler"); +const RulerButton = ButtonBase.extend({ + onAdd: function (this: L.Control & { button: HTMLElement }) { + const button = L.DomUtil.create("button", "ion-ruler"); button.setAttribute("aria-label", _.t("button.ruler")); L.DomEvent.disableClickPropagation(button); L.DomEvent.addListener(button, "click", this.onClick, this); @@ -75,23 +78,34 @@ let RulerButton = ButtonBase.extend({ return button; }, - onClick: function (e) { + onClick: function (this: L.Control & { f: (v: boolean) => void; active: boolean }, e: L.LeafletMouseEvent) { L.DomEvent.stopPropagation(e); this.f(!this.active); }, }); -export const Button = function (map, buttons) { - let userLocation; - const self = { - clearButtons: undefined, - disableTracking: undefined, - locationFound: undefined, - locationError: undefined, - init: undefined, +export type MapButtonApi = { + clearButtons: () => void; + disableTracking: () => void; + locationFound: (location: L.LocationEvent) => void; + locationError: () => void; + init: () => void; +}; + +export const Button = function (map: L.Map, buttons: HTMLElement): MapButtonApi { + let userLocation: InstanceType | null = null; + const self: MapButtonApi = { + clearButtons: () => {}, + disableTracking: () => {}, + locationFound: () => {}, + locationError: () => {}, + init: () => {}, }; - let locateUserButton = new LocateButton(function (activate) { + const locateUserButton = new (LocateButton as unknown as new (f: (v: boolean) => void) => L.Control & { + onAdd: () => HTMLElement; + set: (v: boolean) => void; + })(function (activate: boolean) { if (activate) { enableTracking(); } else { @@ -99,10 +113,10 @@ export const Button = function (map, buttons) { } }); - let mybuttons = []; + const mybuttons: HTMLElement[] = []; - function addButton(button) { - let el = button.onAdd(); + function addButton(button: L.Control & { onAdd: () => HTMLElement }) { + const el = button.onAdd(); mybuttons.push(el); buttons.appendChild(el); } @@ -113,7 +127,10 @@ export const Button = function (map, buttons) { }); }; - let showCoordsPickerButton = new CoordsPickerButton(function (activate) { + const showCoordsPickerButton = new (CoordsPickerButton as unknown as new (f: (v: boolean) => void) => L.Control & { + onAdd: () => HTMLElement; + set: (v: boolean) => void; + })(function (activate: boolean) { if (activate) { enableCoords(); } else { @@ -121,8 +138,10 @@ export const Button = function (map, buttons) { } }); - // Ruler / measure distances button - let rulerButton = new RulerButton(function (activate) { + const rulerButton = new (RulerButton as unknown as new (f: (v: boolean) => void) => L.Control & { + onAdd: () => HTMLElement; + set: (v: boolean) => void; + })(function (activate: boolean) { if (activate) { enableRuler(); } else { @@ -130,13 +149,13 @@ export const Button = function (map, buttons) { } }); - let rulerLayerGroup = null; - let rulerMarkers = []; - let rulerLines = null; - let rulerLinesBg = null; - let rulerDistancePopups = []; + let rulerLayerGroup: L.LayerGroup | null = null; + const rulerMarkers: L.CircleMarker[] = []; + let rulerLines: L.Polyline | null = null; + let rulerLinesBg: L.Polyline | null = null; + const rulerDistancePopups: L.Popup[] = []; - function formatDistance(m) { + function formatDistance(m: number) { if (m >= 1000) { return (m / 1000).toFixed(2) + " km"; } @@ -144,15 +163,12 @@ export const Button = function (map, buttons) { } function enableRuler() { - // create layer group for ruler markers/lines rulerLayerGroup = L.layerGroup().addTo(map); rulerLinesBg = L.polyline([], { color: "#fff", weight: 3, }).addTo(rulerLayerGroup); rulerLines = L.polyline([], { color: "#000", weight: 2, dashArray: "4" }).addTo(rulerLayerGroup); - rulerMarkers = []; - rulerDistancePopups = []; map.getContainer().classList.add("measure-active"); map.on("click", onRulerClick); @@ -167,44 +183,44 @@ export const Button = function (map, buttons) { map.removeLayer(rulerLayerGroup); rulerLayerGroup = null; } - rulerMarkers = []; + rulerMarkers.length = 0; rulerLines = null; rulerLinesBg = null; - rulerDistancePopups = []; + rulerDistancePopups.length = 0; rulerButton.set(false); } - function onRulerClick(e) { - let latlng = e.latlng; - let config = window.config; - let marker = L.circleMarker(latlng, { + function onRulerClick(e: L.LeafletMouseEvent) { + const latlng = e.latlng; + const config = window.config; + if (!rulerLayerGroup || !rulerLines || !rulerLinesBg) { + return; + } + const marker = L.circleMarker(latlng, { radius: 5, color: config.map && config.map.labelNewColor ? config.map.labelNewColor : "#333", }).addTo(rulerLayerGroup); rulerMarkers.push(marker); - // add to polyline - let latlngs = rulerLines.getLatLngs(); + const latlngs = rulerLines.getLatLngs() as L.LatLng[]; latlngs.push(latlng); rulerLines.setLatLngs(latlngs); rulerLinesBg.setLatLngs(latlngs); - // compute segment distance and total distance if (latlngs.length >= 2) { - let last = latlngs[latlngs.length - 2]; - let segDist = latlng.distanceTo(latlngs[latlngs.length - 2]); - // show popup at midpoint for segment - let midLat = (latlng.lat + last.lat) / 2; - let midLng = (latlng.lng + last.lng) / 2; + const last = latlngs[latlngs.length - 2]; + const segDist = latlng.distanceTo(latlngs[latlngs.length - 2]); + + const midLat = (latlng.lat + last.lat) / 2; + const midLng = (latlng.lng + last.lng) / 2; - // compute total let total = 0; for (let i = 1; i < latlngs.length; i++) { total += latlngs[i].distanceTo(latlngs[i - 1]); } - let popup = L.popup({ closeButton: false, autoClose: false, className: "ruler-popup" }) + const popup = L.popup({ closeButton: false, autoClose: false, className: "ruler-popup" }) .setLatLng([midLat, midLng]) .setContent("" + formatDistance(segDist) + "
" + formatDistance(total)) .addTo(rulerLayerGroup); @@ -240,12 +256,13 @@ export const Button = function (map, buttons) { showCoordsPickerButton.set(false); } - function showCoordinates(clicked) { + function showCoordinates(clicked: L.LeafletMouseEvent) { + const router = window.router; router.fullUrl({ zoom: map.getZoom(), lat: clicked.latlng.lat, lng: clicked.latlng.lng }); disableCoords(); } - self.locationFound = function locationFound(location) { + self.locationFound = function locationFound(location: L.LocationEvent) { if (!userLocation) { userLocation = new LocationMarker(location.latlng).addTo(map); } @@ -262,9 +279,9 @@ export const Button = function (map, buttons) { }; self.init = function init() { - addButton(locateUserButton); - addButton(showCoordsPickerButton); - addButton(rulerButton); + addButton(locateUserButton as L.Control & { onAdd: () => HTMLElement }); + addButton(showCoordsPickerButton as L.Control & { onAdd: () => HTMLElement }); + addButton(rulerButton as L.Control & { onAdd: () => HTMLElement }); }; return self; diff --git a/lib/map/clientlayer.ts b/lib/map/clientlayer.ts index 7cee9216..365c9759 100644 --- a/lib/map/clientlayer.ts +++ b/lib/map/clientlayer.ts @@ -27,21 +27,17 @@ export const ClientLayer = L.GridLayer.extend({ this.redraw(); }, createTile: function (tilePoint: Coords) { - let tile: HTMLElement & { - width?: number; - height?: number; - getContext?: (type: string) => CanvasRenderingContext2D; - } = L.DomUtil.create("canvas", "leaflet-tile"); + const tile = L.DomUtil.create("canvas", "leaflet-tile") as HTMLCanvasElement; - let tileSize = this.options.tileSize; - tile.width = tileSize; - tile.height = tileSize; + const tileSize = this.options.tileSize; + tile.width = tileSize as number; + tile.height = tileSize as number; if (!this.data) { return tile; } - let ctx = tile.getContext("2d"); + const ctx = tile.getContext("2d")!; let size = tilePoint.multiplyBy(tileSize); let map = this._map; diff --git a/lib/nodelist.ts b/lib/nodelist.ts index 0990dc8f..47c6bd56 100644 --- a/lib/nodelist.ts +++ b/lib/nodelist.ts @@ -1,3 +1,4 @@ +import moment from "moment"; import { h, VNode } from "snabbdom"; import { _ } from "./utils/language.js"; import { Heading, SortTable } from "./sorttable.js"; @@ -7,7 +8,6 @@ import { CanSetData, ObjectsLinksAndNodes } from "./datadistributor.js"; import { CanRender } from "./container.js"; function showUptime(uptime: number) { - // 1000ms are 1 second and 60 second are 1min: 60 * 1000 = 60000 let seconds = uptime / 60000; if (Math.abs(seconds) < 60) { return Math.round(seconds) + " m"; @@ -20,7 +20,7 @@ function showUptime(uptime: number) { return Math.round(seconds) + " d"; } -let headings: Heading[] = [ +const headings: Heading[] = [ { name: "", }, @@ -58,11 +58,8 @@ let headings: Heading[] = [ ]; export const Nodelist = function (): CanSetData & CanRender { - let router = window.router; - const self = { - render: undefined, - setData: undefined, - }; + const router = window.router; + const table = SortTable(headings, 1, renderRow); function renderRow(node: Node) { let td0Content: string | VNode = ""; @@ -70,7 +67,7 @@ export const Nodelist = function (): CanSetData & CanRender { td0Content = h("span", { props: { className: "icon ion-location", title: _.t("location.location") } }); } - let td1Content = h( + const td1Content = h( "a", { props: { @@ -95,32 +92,28 @@ export const Nodelist = function (): CanSetData & CanRender { ]); } - let table = SortTable(headings, 1, renderRow); - - self.render = function render(d: HTMLElement) { - let h2 = document.createElement("h2"); - h2.textContent = _.t("node.all"); - d.appendChild(h2); - table.el.classList.add("node-list"); - d.appendChild(table.el); - }; - - self.setData = function setData(nodesData: ObjectsLinksAndNodes) { - let nodesList = nodesData.nodes.all.map(function (node) { - let nodeData = Object.create(node); - if (node.is_online) { - nodeData.uptime = nodesData.now.valueOf() - new Date(node.uptime).getTime(); - } else { - nodeData.uptime = node.lastseen.valueOf() - nodesData.now.valueOf(); - } - return nodeData; - }); + return { + render(d: HTMLElement) { + const h2 = document.createElement("h2"); + h2.textContent = _.t("node.all"); + d.appendChild(h2); + table.el.classList.add("node-list"); + d.appendChild(table.el); + }, - table.setData(nodesList); - }; + setData(nodesData: ObjectsLinksAndNodes) { + const now = nodesData.now ?? moment(); + const nodesList = nodesData.nodes.all.map(function (node) { + const nodeData = Object.create(node); + if (node.is_online) { + nodeData.uptime = now.valueOf() - new Date(node.uptime).getTime(); + } else { + nodeData.uptime = node.lastseen.valueOf() - now.valueOf(); + } + return nodeData; + }); - return { - setData: self.setData, - render: self.render, + table.setData(nodesList); + }, }; }; diff --git a/lib/proportions.ts b/lib/proportions.ts index ec9eaba3..0e1c9745 100644 --- a/lib/proportions.ts +++ b/lib/proportions.ts @@ -1,5 +1,5 @@ import * as d3Interpolate from "d3-interpolate"; -import { Moment } from "moment"; +import moment, { Moment } from "moment"; import { classModule, eventListenersModule, h, init, propsModule, styleModule, VNode } from "snabbdom"; import { DataDistributor, Filter, GenericFilter, ObjectsLinksAndNodes } from "./datadistributor.js"; import { GenericNodeFilter } from "./filters/genericnode.js"; @@ -84,14 +84,18 @@ const statusFieldMapping: Record = { const patch = init([classModule, propsModule, styleModule, eventListenersModule]); export const Proportions = function (filterManager: ReturnType) { - const self = { - setData: undefined, - render: undefined, - renderSingle: undefined, + const self: { + setData: (data: ObjectsLinksAndNodes) => void; + render: (el: HTMLElement) => void; + renderSingle: (el: HTMLElement, mappingName: string) => void; + } = { + setData: () => {}, + render: () => {}, + renderSingle: () => {}, }; let config = window.config; let scale = d3Interpolate.interpolate(config.forceGraph.tqFrom, config.forceGraph.tqTo); - let time: Moment; + let time: Moment = moment(); let tables: Record = {}; // flag set while we apply filters programmatically from the URL hash @@ -136,10 +140,11 @@ export const Proportions = function (filterManager: ReturnType number) { const m = statusFieldMapping[name]; @@ -284,7 +292,12 @@ export const Proportions = function (filterManager: ReturnType String(v)), + ); if (negate) { filter.setNegate(true); } @@ -308,14 +321,14 @@ export const Proportions = function (filterManager: ReturnType window.innerWidth || sidebar.classList.contains("hidden")) { - return 0; - } else if (gridBreakpoints.xl[0] > window.innerWidth) { - return gridBreakpoints.lg[1]; - } - return gridBreakpoints.xl[1]; - }; - - self.add = function add(d: CanRender) { - d.render(container); - }; - - self.ensureVisible = function ensureVisible() { - sidebar.classList.remove("hidden"); + return { + getWidth() { + if (gridBreakpoints.lg[0] > window.innerWidth || sidebar.classList.contains("hidden")) { + return 0; + } else if (gridBreakpoints.xl[0] > window.innerWidth) { + return gridBreakpoints.lg[1]; + } + return gridBreakpoints.xl[1]; + }, + + add(d: CanRender) { + d.render(container); + }, + + ensureVisible() { + sidebar.classList.remove("hidden"); + }, + + hide() { + container.children[1].classList.add("hide"); + container.children[2].classList.add("hide"); + }, + + reveal() { + container.children[1].classList.remove("hide"); + container.children[2].classList.remove("hide"); + }, + + container: sidebar, + button, }; - - self.hide = function hide() { - container.children[1].classList.add("hide"); - container.children[2].classList.add("hide"); - }; - - self.reveal = function reveal() { - container.children[1].classList.remove("hide"); - container.children[2].classList.remove("hide"); - }; - - self.container = sidebar; - self.button = button; - - return self; }; diff --git a/lib/simplenodelist.ts b/lib/simplenodelist.ts index 08b5fd0b..be0febf5 100644 --- a/lib/simplenodelist.ts +++ b/lib/simplenodelist.ts @@ -3,70 +3,68 @@ import { classModule, eventListenersModule, h, init, propsModule, styleModule, V import { _ } from "./utils/language.js"; import * as helper from "./utils/helper.js"; -import { ObjectsLinksAndNodes } from "./datadistributor.js"; +import { NodesByState, ObjectsLinksAndNodes } from "./datadistributor.js"; import { Node } from "./utils/node.js"; const patch = init([classModule, propsModule, styleModule, eventListenersModule]); export const SimpleNodelist = function (nodesState: string, field: string, title: string) { - const self = { - render: undefined, - setData: undefined, - }; let listContainer: VNode = h("div"); - self.render = function render(d: HTMLElement) { - let containerEl = document.createElement("div"); - d.appendChild(containerEl); - listContainer = patch(containerEl, listContainer); - }; + return { + render(d: HTMLElement) { + const containerEl = document.createElement("div"); + d.appendChild(containerEl); + listContainer = patch(containerEl, listContainer); + }, - self.setData = function setData(data: ObjectsLinksAndNodes) { - let nodeList = data.nodes[nodesState]; + setData(data: ObjectsLinksAndNodes) { + const key = nodesState as keyof NodesByState; + const nodeList = data.nodes[key]; - let newContainer = h("div"); + const newContainer = h("div"); - if (nodeList.length > 0) { - let items = nodeList.map(function (node: Node) { - let router = window.router; - let td0Content: null | VNode = null; - if (helper.hasLocation(node)) { - td0Content = h("span", { props: { className: "icon ion-location", title: _.t("location.location") } }); - } + if (nodeList.length > 0) { + const items = nodeList.map(function (node: Node) { + const router = window.router; + let td0Content: null | VNode = null; + if (helper.hasLocation(node)) { + td0Content = h("span", { props: { className: "icon ion-location", title: _.t("location.location") } }); + } - let td1Content = h( - "a", - { - props: { - className: ["hostname", node.is_online ? "online" : "offline"].join(" "), - href: router.generateLink({ node: node.node_id }), - }, - on: { - click: function (e: Event) { - router.fullUrl({ node: node.node_id }, e); + const td1Content = h( + "a", + { + props: { + className: ["hostname", node.is_online ? "online" : "offline"].join(" "), + href: router.generateLink({ node: node.node_id }), + }, + on: { + click: function (e: Event) { + router.fullUrl({ node: node.node_id }, e); + }, }, }, - }, - node.hostname, - ); + node.hostname, + ); - return h("tr", [h("td", td0Content), h("td", td1Content), h("td", moment(node[field]).from(data.now))]); - }); + const raw = node as unknown as Record; + return h("tr", [h("td", td0Content), h("td", td1Content), h("td", moment(raw[field]).from(data.now))]); + }); - newContainer.children = [ - h("h2", title), - h( - "table", - { - props: { className: "node-list" }, - }, - h("tbody", items), - ), - ]; - } + newContainer.children = [ + h("h2", title), + h( + "table", + { + props: { className: "node-list" }, + }, + h("tbody", items), + ), + ]; + } - listContainer = patch(listContainer, newContainer); + listContainer = patch(listContainer, newContainer); + }, }; - - return self; }; diff --git a/lib/sorttable.ts b/lib/sorttable.ts index e125cf71..a8ffb92e 100644 --- a/lib/sorttable.ts +++ b/lib/sorttable.ts @@ -13,21 +13,26 @@ const patch = init([classModule, propsModule, styleModule, eventListenersModule] export const SortTable = function ( headings: Heading[], sortIndex: number, - renderRow: (element: any, i: number, all: []) => any, + renderRow: (element: any, i: number, all: any[]) => any, className: string[] = [], ) { + let data: any[] = []; + let sortReverse = false; + let currentSortIndex = sortIndex; + const self: { el: HTMLElement; - vnode: VNode; + vnode: VNode | null; setData: (data: any[]) => void; - } = { el: undefined, setData: undefined, vnode: null }; - let data: any[]; - let sortReverse = false; - self.el = document.createElement("table"); + } = { + el: document.createElement("table"), + vnode: null, + setData: () => {}, + }; function sortTable(i: number) { - sortReverse = i === sortIndex ? !sortReverse : false; - sortIndex = i; + sortReverse = i === currentSortIndex ? !sortReverse : false; + currentSortIndex = i; updateView(); } @@ -39,15 +44,14 @@ export const SortTable = function ( } function updateView() { - let children = []; + const children: VNode[] = []; if (data.length !== 0) { - let th = headings.map(function (row, i) { + const th = headings.map(function (row, i) { let name = _.t(row.name); - let properties = { + const properties: { onclick: () => void; className: string; title?: string } = { onclick: sortTableHandler(i), className: "sort-header", - title: undefined, }; if (row.class) { @@ -56,24 +60,30 @@ export const SortTable = function ( name = ""; } - if (sortIndex === i) { + if (currentSortIndex === i) { properties.className += sortReverse ? " sort-up" : " sort-down"; } return h("th", { props: properties }, name); }); - let links = data.slice(0).sort(headings[sortIndex].sort); + const sortFn = headings[currentSortIndex].sort ?? (() => 0); + let links = data.slice(0).sort(sortFn); - if (headings[sortIndex].reverse ? !sortReverse : sortReverse) { + if (headings[currentSortIndex].reverse ? !sortReverse : sortReverse) { links = links.reverse(); } children.push(h("thead", h("tr", th))); - children.push(h("tbody", links.map(renderRow))); + children.push( + h( + "tbody", + links.map((row, idx, arr) => renderRow(row, idx, arr)), + ), + ); } - let elNew = h("table", { props: { className } }, children); + const elNew = h("table", { props: { className } }, children); self.vnode = patch(self.vnode ?? self.el, elNew); } diff --git a/lib/tabs.ts b/lib/tabs.ts index 557c7e6c..03909a68 100644 --- a/lib/tabs.ts +++ b/lib/tabs.ts @@ -1,18 +1,15 @@ import { _ } from "./utils/language.js"; import { CanRender } from "./container.js"; -export const Tabs = function () { - const self = { - add: undefined, - render: undefined, - }; +type TabLi = HTMLLIElement & { child: CanRender }; - let tabs = document.createElement("ul"); +export const Tabs = function () { + const tabs = document.createElement("ul"); tabs.classList.add("tabs"); - let container = document.createElement("div"); + const container = document.createElement("div"); - function gotoTab(li: HTMLLIElement) { + function gotoTab(li: TabLi) { for (let i = 0; i < tabs.children.length; i++) { tabs.children[i].classList.remove("visible"); } @@ -23,45 +20,39 @@ export const Tabs = function () { li.classList.add("visible"); - let tab = document.createElement("div"); + const tab = document.createElement("div"); tab.classList.add("tab"); container.appendChild(tab); - // @ts-ignore li.child.render(tab); } - function switchTab() { - gotoTab(this); - - return false; - } - - self.add = function add(title: string, child: CanRender) { - let li = document.createElement("li"); - li.textContent = _.t(title); - li.onclick = switchTab; - // @ts-ignore - li.child = child; - tabs.appendChild(li); - - let anyVisible = false; - - for (let i = 0; i < tabs.children.length; i++) { - if (tabs.children[i].classList.contains("visible")) { - anyVisible = true; - break; + return { + add(title: string, child: CanRender) { + const li = document.createElement("li") as TabLi; + li.textContent = _.t(title); + li.addEventListener("click", function (this: HTMLElement) { + gotoTab(this as TabLi); + }); + li.child = child; + tabs.appendChild(li); + + let anyVisible = false; + + for (let i = 0; i < tabs.children.length; i++) { + if (tabs.children[i].classList.contains("visible")) { + anyVisible = true; + break; + } } - } - if (!anyVisible) { - gotoTab(li); - } - }; + if (!anyVisible) { + gotoTab(li); + } + }, - self.render = function render(el: HTMLElement) { - el.appendChild(tabs); - el.appendChild(container); + render(el: HTMLElement) { + el.appendChild(tabs); + el.appendChild(container); + }, }; - - return self; }; diff --git a/lib/title.ts b/lib/title.ts index 828aefe7..f6b4e697 100644 --- a/lib/title.ts +++ b/lib/title.ts @@ -1,17 +1,10 @@ -import { Link, Node } from "./utils/node.js"; - -export const Title = function () { - const self = { - resetView: undefined, - gotoNode: undefined, - gotoLink: undefined, - gotoLocation: undefined, - destroy: undefined, - }; +import { Link, Node, NodeId } from "./utils/node.js"; +import { Target } from "./utils/router.js"; +export const Title = function (): Target & { destroy: () => void } { function setTitle(addedTitle?: string) { - let config = window.config; - let title = [config.siteName]; + const config = window.config; + const title = [config.siteName]; if (addedTitle !== undefined) { title.unshift(addedTitle); @@ -20,23 +13,23 @@ export const Title = function () { document.title = title.join(" - "); } - self.resetView = function resetView() { - setTitle(); - }; + return { + resetView() { + setTitle(); + }, - self.gotoNode = function gotoNode(node: Node) { - setTitle(node.hostname); - }; + gotoNode(node: Node, _nodeDict: { [k: NodeId]: Node }) { + setTitle(node.hostname); + }, - self.gotoLink = function gotoLink(link: Link[]) { - setTitle(link[0].source.hostname + " \u21D4 " + link[0].target.hostname); - }; + gotoLink(link: Link[]) { + setTitle(link[0].source.hostname + " \u21D4 " + link[0].target.hostname); + }, - self.gotoLocation = function gotoLocation() { - // ignore - }; - - self.destroy = function destroy() {}; + gotoLocation() { + // ignore + }, - return self; + destroy() {}, + }; }; diff --git a/lib/utils/helper.ts b/lib/utils/helper.ts index 02025be5..2872d93b 100644 --- a/lib/utils/helper.ts +++ b/lib/utils/helper.ts @@ -27,7 +27,7 @@ export const get = function get(url: string) { }; export const getJSON = function getJSON(url: string) { - return get(url).then(JSON.parse); + return get(url).then((text: unknown) => JSON.parse(String(text))); }; export const sortByKey = function sortByKey(key: string, data: { [k: string]: Moment }[]) { @@ -57,9 +57,8 @@ export const one = function one() { }; export const dictGet = function dictGet(dict: { [x: string]: any }, keys: string[]) { - let key = keys.shift(); - - if (!(key in dict)) { + const key = keys.shift(); + if (key === undefined || !(key in dict)) { return null; } @@ -98,7 +97,7 @@ export const hasUplink = function hasUplink(data: Node | {}) { }; export const subtract = function subtract(a: Node[], b: Node[]) { - let ids = {}; + const ids: Record = {}; b.forEach(function (d) { ids[d.node_id] = true; @@ -239,19 +238,21 @@ export const positionClients = function positionClients( }; export const fullscreen = function fullscreen(btn: HTMLButtonElement) { - if (!document.fullscreenElement && !document["webkitFullscreenElement"] && !document["mozFullScreenElement"]) { - let fel = document.firstElementChild; - let func = fel.requestFullscreen || fel["webkitRequestFullScreen"] || fel["mozRequestFullScreen"]; - func.call(fel); + const fel = document.documentElement; + if (!document.fullscreenElement && !document.webkitFullscreenElement && !document.mozFullScreenElement) { + const enter = + fel.requestFullscreen?.bind(fel) ?? fel.webkitRequestFullScreen?.bind(fel) ?? fel.mozRequestFullScreen?.bind(fel); + enter?.(); btn.classList.remove("ion-full-enter"); btn.classList.add("ion-full-exit"); } else { - let func = document.exitFullscreen || document["webkitExitFullscreen"] || document["mozCancelFullScreen"]; - if (func) { - func.call(document); - btn.classList.remove("ion-full-exit"); - btn.classList.add("ion-full-enter"); - } + const exit = + document.exitFullscreen?.bind(document) ?? + document.webkitExitFullscreen?.bind(document) ?? + document.mozCancelFullScreen?.bind(document); + exit?.(); + btn.classList.remove("ion-full-exit"); + btn.classList.add("ion-full-enter"); } }; diff --git a/lib/utils/language.ts b/lib/utils/language.ts index 1559e1a4..8373b9e5 100644 --- a/lib/utils/language.ts +++ b/lib/utils/language.ts @@ -9,7 +9,7 @@ export let _: Polyglot & { phrases?: { [k: string]: any } }; export const Language = function () { let router: Router; - let config = globalThis.config; + const config = window.config; function languageSelect(el: HTMLElement) { let select = document.createElement("select"); @@ -65,9 +65,9 @@ export const Language = function () { function init(routing: Router) { router = routing; /** global: _ */ - _ = new Polyglot({ locale: getLocale(routing.getLang()), allowMissing: true }); + _ = new Polyglot({ locale: getLocale(routing.getLang() ?? undefined), allowMissing: true }); helper.getJSON("locale/" + _.locale() + ".json?" + config.cacheBreaker).then(setTranslation); - document.querySelector("html").setAttribute("lang", _.locale()); + document.querySelector("html")!.setAttribute("lang", _.locale()); } return { diff --git a/lib/utils/math.ts b/lib/utils/math.ts index 149de95d..7ff29a5d 100644 --- a/lib/utils/math.ts +++ b/lib/utils/math.ts @@ -1,33 +1,29 @@ const self = { - distance: undefined, - distancePoint: undefined, - distanceLink: undefined, -}; - -self.distance = function distance(a: Point, b: Point) { - return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y); -}; + distance(a: Point, b: Point) { + return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y); + }, -self.distancePoint = function distancePoint(a: Point, b: Point) { - return Math.sqrt(self.distance(a, b)); -}; + distancePoint(a: Point, b: Point) { + return Math.sqrt(self.distance(a, b)); + }, -self.distanceLink = function distanceLink(p: Point, a: Point, b: Point) { - /* http://stackoverflow.com/questions/849211 */ - let l2 = self.distance(a, b); - if (l2 === 0) { - return self.distance(p, a); - } - let t = ((p.x - a.x) * (b.x - a.x) + (p.y - a.y) * (b.y - a.y)) / l2; - if (t < 0) { - return self.distance(p, a); - } else if (t > 1) { - return self.distance(p, b); - } - return self.distancePoint(p, { - x: a.x + t * (b.x - a.x), - y: a.y + t * (b.y - a.y), - }); + distanceLink(p: Point, a: Point, b: Point) { + /* http://stackoverflow.com/questions/849211 */ + const l2 = self.distance(a, b); + if (l2 === 0) { + return self.distance(p, a); + } + const t = ((p.x - a.x) * (b.x - a.x) + (p.y - a.y) * (b.y - a.y)) / l2; + if (t < 0) { + return self.distance(p, a); + } else if (t > 1) { + return self.distance(p, b); + } + return self.distancePoint(p, { + x: a.x + t * (b.x - a.x), + y: a.y + t * (b.y - a.y), + }); + }, }; export default self; diff --git a/lib/utils/node.ts b/lib/utils/node.ts index 9f354eee..7f5d9440 100644 --- a/lib/utils/node.ts +++ b/lib/utils/node.ts @@ -1,4 +1,4 @@ -import { h } from "snabbdom"; +import { h, VNode } from "snabbdom"; import moment, { Moment } from "moment"; import { _ } from "./language.js"; import * as helper from "./helper.js"; @@ -65,22 +65,6 @@ export interface Node { autoupdater: Autoupdater; } -const self = { - showStatus: undefined, - showGeoURI: undefined, - showGateway: undefined, - showFirmware: undefined, - showUptime: undefined, - showFirstSeen: undefined, - showLoad: undefined, - showRAM: undefined, - showDomain: undefined, - countLocalClients: undefined, - showClients: undefined, - showIPs: undefined, - showAutoupdate: undefined, -}; - function showBar(value: string, width: number, warning: boolean) { return h("span", { props: { className: "bar" + (warning ? " warning" : "") } }, [ h("span", { @@ -90,145 +74,148 @@ function showBar(value: string, width: number, warning: boolean) { ]); } -self.showStatus = function showStatus(node: Node) { - return h( - "td", - { props: { className: node.is_online ? "online" : "offline" } }, - _.t(node.is_online ? "node.lastOnline" : "node.lastOffline", { - time: node.lastseen.fromNow(), - date: node.lastseen.format("DD.MM.YYYY, H:mm:ss"), - }), - ); -}; - -self.showGeoURI = function showGeoURI(data: Node) { - if (!helper.hasLocation(data)) { - return undefined; - } - - return h( - "td", - h( - "a", - { props: { href: "geo:" + data.location.latitude + "," + data.location.longitude } }, - Number(data.location.latitude.toFixed(6)) + ", " + Number(data.location.longitude.toFixed(6)), - ), - ); -}; - -self.showGateway = function showGateway(node: Node) { - return node.is_gateway ? _.t("yes") : undefined; -}; - -self.showFirmware = function showFirmware(node: Node) { - return ( - [helper.dictGet(node, ["firmware", "release"]), helper.dictGet(node, ["firmware", "base"])] - .filter(function (value) { - return value !== null; - }) - .join(" / ") || undefined - ); -}; - -self.showUptime = function showUptime(node: Node) { - return moment.utc(node.uptime).local().fromNow(true); -}; - -self.showFirstSeen = function showFirstSeen(node: Node) { - return moment.utc(node.firstseen).local().fromNow(); -}; - -self.showLoad = function showLoad(node: Node) { - return showBar(node.loadavg.toFixed(2), node.loadavg / (node.nproc || 1), node.loadavg >= node.nproc); -}; - -self.showRAM = function showRAM(node: Node) { - return showBar(Math.round(node.memory_usage * 100) + " %", node.memory_usage, node.memory_usage >= 0.8); -}; - -self.showDomain = function showDomain(node: Node) { - let domainTitle = node.domain; - let config = window.config; - if (config.domainNames) { - config.domainNames.some(function (domain) { - if (domainTitle === domain.domain) { - domainTitle = domain.name; - return true; - } - }); - } - return domainTitle; -}; - -self.countLocalClients = function countLocalClients(node: Node, visited = {}) { - if (node.node_id in visited) return 0; - visited[node.node_id] = 1; - let count = node.clients || 0; - node.neighbours.forEach(function (neighbour) { - if (neighbour.link.type === "vpn") return; - count += self.countLocalClients(neighbour.node, visited); - }); - return count; -}; - -self.showClients = function showClients(node: Node) { - if (!node.is_online) { - return undefined; - } - let localClients = self.countLocalClients(node); - - let clients = [ - h("span", [ - node.clients > 0 ? node.clients : _.t("none"), - h("br"), - h("i", { props: { className: "ion-people", title: _.t("node.clients") } }), - ]), - h("span", { props: { className: "legend-24ghz" } }, [ - node.clients_wifi24, - h("br"), - h("span", { props: { className: "symbol", title: "2,4 GHz" } }), - ]), - h("span", { props: { className: "legend-5ghz" } }, [ - node.clients_wifi5, - h("br"), - h("span", { props: { className: "symbol", title: "5 GHz" } }), - ]), - h("span", { props: { className: "legend-others" } }, [ - node.clients_other, - h("br"), - h("span", { props: { className: "symbol", title: _.t("others") } }), - ]), - h("span", [ - localClients > 0 ? localClients : _.t("none"), - h("br"), - h("i", { props: { className: "ion-share-alt", title: _.t("node.localClients") } }), - ]), - ]; - return h("td", { props: { className: "clients" } }, clients); -}; +const self = { + showStatus(node: Node) { + return h( + "td", + { props: { className: node.is_online ? "online" : "offline" } }, + _.t(node.is_online ? "node.lastOnline" : "node.lastOffline", { + time: node.lastseen.fromNow(), + date: node.lastseen.format("DD.MM.YYYY, H:mm:ss"), + }), + ); + }, + + showGeoURI(data: Node) { + if (!helper.hasLocation(data)) { + return undefined; + } -self.showIPs = function showIPs(node: Node) { - let string = []; - let ips = node.addresses; - ips.sort(); - ips.forEach(function (ip, i) { - if (i > 0) { - string.push(h("br")); + return h( + "td", + h( + "a", + { props: { href: "geo:" + data.location.latitude + "," + data.location.longitude } }, + Number(data.location.latitude.toFixed(6)) + ", " + Number(data.location.longitude.toFixed(6)), + ), + ); + }, + + showGateway(node: Node) { + return node.is_gateway ? _.t("yes") : undefined; + }, + + showFirmware(node: Node) { + return ( + [helper.dictGet(node, ["firmware", "release"]), helper.dictGet(node, ["firmware", "base"])] + .filter(function (value) { + return value !== null; + }) + .join(" / ") || undefined + ); + }, + + showUptime(node: Node) { + return moment.utc(node.uptime).local().fromNow(true); + }, + + showFirstSeen(node: Node) { + return moment.utc(node.firstseen).local().fromNow(); + }, + + showLoad(node: Node) { + return showBar(node.loadavg.toFixed(2), node.loadavg / (node.nproc || 1), node.loadavg >= node.nproc); + }, + + showRAM(node: Node) { + return showBar(Math.round(node.memory_usage * 100) + " %", node.memory_usage, node.memory_usage >= 0.8); + }, + + showDomain(node: Node) { + let domainTitle = node.domain; + const config = window.config; + if (config.domainNames) { + config.domainNames.some(function (domain) { + if (domainTitle === domain.domain) { + domainTitle = domain.name; + return true; + } + return false; + }); } + return domainTitle; + }, + + countLocalClients(node: Node, visited: Record = {}) { + if (node.node_id in visited) return 0; + visited[node.node_id] = 1; + let count = node.clients || 0; + node.neighbours.forEach(function (neighbour) { + if (neighbour.link.type === "vpn") return; + count += self.countLocalClients(neighbour.node, visited); + }); + return count; + }, - if (ip.indexOf("fe80:") !== 0) { - string.push(h("a", { props: { href: "http://[" + ip + "]/", target: "_blank" } }, ip)); - } else { - string.push(ip); + showClients(node: Node) { + if (!node.is_online) { + return undefined; } - }); - return h("td", string); -}; + const localClients = self.countLocalClients(node); + + const clients = [ + h("span", [ + node.clients > 0 ? node.clients : _.t("none"), + h("br"), + h("i", { props: { className: "ion-people", title: _.t("node.clients") } }), + ]), + h("span", { props: { className: "legend-24ghz" } }, [ + node.clients_wifi24, + h("br"), + h("span", { props: { className: "symbol", title: "2,4 GHz" } }), + ]), + h("span", { props: { className: "legend-5ghz" } }, [ + node.clients_wifi5, + h("br"), + h("span", { props: { className: "symbol", title: "5 GHz" } }), + ]), + h("span", { props: { className: "legend-others" } }, [ + node.clients_other, + h("br"), + h("span", { props: { className: "symbol", title: _.t("others") } }), + ]), + h("span", [ + localClients > 0 ? localClients : _.t("none"), + h("br"), + h("i", { props: { className: "ion-share-alt", title: _.t("node.localClients") } }), + ]), + ]; + return h("td", { props: { className: "clients" } }, clients); + }, + + showIPs(node: Node) { + const parts: (VNode | string)[] = []; + const ips = node.addresses; + ips.sort(); + ips.forEach(function (ip, i) { + if (i > 0) { + parts.push(h("br")); + } -self.showAutoupdate = function showAutoupdate(node: Node) { - return node.autoupdater.enabled - ? _.t("node.activated", { branch: node.autoupdater.branch }) - : _.t("node.deactivated"); + if (ip.indexOf("fe80:") !== 0) { + parts.push(h("a", { props: { href: "http://[" + ip + "]/", target: "_blank" } }, ip)); + } else { + parts.push(ip); + } + }); + return h("td", parts); + }, + + showAutoupdate(node: Node) { + return node.autoupdater.enabled + ? _.t("node.activated", { branch: node.autoupdater.branch }) + : _.t("node.deactivated"); + }, }; export default self; diff --git a/lib/utils/router.ts b/lib/utils/router.ts index 03a07298..73d7868c 100644 --- a/lib/utils/router.ts +++ b/lib/utils/router.ts @@ -1,15 +1,7 @@ import Navigo, { Match } from "navigo"; import { Language } from "./language.js"; -import { Link, NodeId } from "./node.js"; -import { Moment } from "moment"; - -export interface Objects { - nodeDict: NodeId[]; - links: Link[]; - nodes?: Node[]; - now?: Moment; - timestamp?: Moment; -} +import { Link, Node, NodeId } from "./node.js"; +import { ObjectsLinksAndNodes } from "../datadistributor.js"; export interface TargetLocation { lng: number; @@ -19,7 +11,7 @@ export interface TargetLocation { export interface Target { resetView(): void; - gotoNode(nodeId: NodeId, nodeIdList: NodeId[]): any; + gotoNode(node: Node, nodeDict: { [k: NodeId]: Node }): any; gotoLink(link: Link[]): any; gotoLocation(locationData: TargetLocation): any; } @@ -28,22 +20,38 @@ interface Views { [k: string]: () => any; } +type RouteData = Record | string[] | null; + +function routeGroup(data: RouteData, index: number): string | undefined { + if (data == null) { + return undefined; + } + if (Array.isArray(data)) { + return data[index]; + } + return data[String(index)]; +} + export class Router extends Navigo { init = false; - objects: Objects = { nodeDict: [], links: [] }; + objects: ObjectsLinksAndNodes = { + nodes: { all: [], lost: [], new: [], offline: [], online: [] }, + links: [], + nodeDict: {}, + }; targets: Target[] = []; views: Views = {}; - currentState = { - lang: undefined, // like de or en - view: undefined, // map or graph - node: undefined, // Node ID - link: undefined, // Two node IDs concatenated by - - zoom: undefined, - lat: undefined, - lng: undefined, - }; - state = { lang: null, view: "map" }; - language = undefined; + currentState: { + lang?: string; + view?: string; + node?: string; + link?: string; + zoom?: string; + lat?: string; + lng?: string; + } = {}; + state: { lang: string | null; view: string } = { lang: null, view: "map" }; + language!: ReturnType; constructor(language: ReturnType) { super("/", { hash: true }); @@ -59,18 +67,20 @@ export class Router extends Navigo { } gotoNode(node: { nodeId: NodeId }) { - if (this.objects.nodeDict[node.nodeId]) { + const dict = this.objects.nodeDict ?? {}; + const n = dict[node.nodeId]; + if (n) { this.targets.forEach((target) => { - target.gotoNode(this.objects.nodeDict[node.nodeId], this.objects.nodeDict); + target.gotoNode(n, dict); }); } } gotoLink(linkData: { linkId: string }) { - let link = this.objects.links.filter(function (value) { + const link = this.objects.links.filter(function (value) { return value.id === linkData.linkId; }); - if (link) { + if (link.length) { this.targets.forEach(function (target) { target.gotoLink(link); }); @@ -86,28 +96,29 @@ export class Router extends Navigo { } customRoute(match?: Match) { - let lang: string | undefined = match.data[0]; - let viewValue: "map" | "graph" | string | undefined = match.data[1]; - let node: string | undefined = match.data[2]; - let link: string | undefined = match.data[3]; - let zoom: number | string | undefined = match.data[4]; - let lat: number | string | undefined = match.data[5]; - let lng: number | string | undefined = match.data[6]; + const d = match?.data as RouteData; + const lang = routeGroup(d, 0); + let viewValue: "map" | "graph" | string | undefined = routeGroup(d, 1); + const node = routeGroup(d, 2); + const link = routeGroup(d, 3); + const zoom = routeGroup(d, 4); + const lat = routeGroup(d, 5); + const lng = routeGroup(d, 6); this.currentState = { - lang: lang, + lang, view: viewValue, - node: node, - link: link, - zoom: zoom, - lat: lat, - lng: lng, + node, + link, + zoom, + lat, + lng, }; if (lang && lang !== this.state.lang && lang === this.language.getLocale(lang)) { console.debug("Language change reload"); - const prefix = match.hashString.startsWith("/") ? "" : "/"; - location.hash = prefix + match.hashString; + const prefix = match!.hashString.startsWith("/") ? "" : "/"; + location.hash = prefix + match!.hashString; location.reload(); } @@ -126,9 +137,9 @@ export class Router extends Navigo { } else if (lat) { this.targets.forEach((target) => { target.gotoLocation({ - zoom: parseInt(this.currentState.zoom, 10), - lat: parseFloat(this.currentState.lat), - lng: parseFloat(this.currentState.lng), + zoom: parseInt(this.currentState.zoom ?? "0", 10), + lat: parseFloat(this.currentState.lat ?? "0"), + lng: parseFloat(this.currentState.lng ?? "0"), }); }); } else { @@ -142,7 +153,11 @@ export class Router extends Navigo { /^\/?!(.*)?$/, (match?: Match) => { console.debug("fixing legacy url"); - this.navigate(match.data[0]); + const d = match?.data as RouteData; + const first = Array.isArray(d) ? d[0] : d && typeof d === "object" ? Object.values(d)[0] : undefined; + if (first !== undefined) { + this.navigate(String(first)); + } }, ) .on( @@ -176,38 +191,42 @@ export class Router extends Navigo { return "?" + qs.toString(); } - generateLink(data?: {}, full?: boolean) { + generateLink(data?: Record, full?: boolean) { let result = ""; + let merged: Record; if (full) { - data = Object.assign({}, this.state, data); + merged = Object.assign({}, this.state, data); } else { result = "#"; - data = Object.assign({}, this.currentState, data); + merged = Object.assign({}, this.currentState, data); } - for (let key in data) { - if (!data.hasOwnProperty(key) || data[key] === undefined || data[key] === "") { + for (const key in merged) { + if (!Object.prototype.hasOwnProperty.call(merged, key)) { + continue; + } + const v = merged[key]; + if (v === undefined || v === "") { continue; } - result += "/" + data[key]; + result += "/" + String(v); } - // add data query params const params = this.getParams(); result += this.paramsToUrl(params); return result; } - fullUrl(data?: {}, e?: Event | false) { + fullUrl(data?: Record, e?: Event | false) { if (e) { e.preventDefault(); } this.navigate(this.generateLink(data, true)); } - deepUrl(data?: {}, e?: Event | false) { + deepUrl(data?: Record, e?: Event | false) { if (e) { e.preventDefault(); } @@ -215,7 +234,7 @@ export class Router extends Navigo { } getLang() { - let lang = location.hash.match(/^\/?#?\/(\w{2})\//); + const lang = location.hash.match(/^\/?#?\/(\w{2})\//); if (lang) { this.state.lang = this.language.getLocale(lang[1]); return lang[1]; @@ -236,7 +255,6 @@ export class Router extends Navigo { const params = new URLSearchParams(queryString); params.forEach(function (value, key) { - // value can be something like "v1,!v2" const parts = value.split(",").filter(Boolean); if (!out[key]) { @@ -274,7 +292,7 @@ export class Router extends Navigo { return this.currentState.view; } - setData(data: Objects) { + setData(data: ObjectsLinksAndNodes) { this.objects = data; } } diff --git a/lib/utils/version.ts b/lib/utils/version.ts index 9a63cf34..dfd3f0c3 100644 --- a/lib/utils/version.ts +++ b/lib/utils/version.ts @@ -1,98 +1,100 @@ -type Version = { epoch: number; upstream: string; debian: string }; +type VersionLike = { epoch: number; upstream: string; debian: string }; -const Version = function (v: string) { - // remove leading "v" or "V" if present - if (v.startsWith("v") || v.startsWith("V")) { - v = v.slice(1); - } - let versionResult = /^[a-zA-Z]?([0-9]*(?=:))?:(.*)/.exec(v); - let version = versionResult && versionResult[2] ? versionResult[2] : v; - let versionParts = version.split("-"); +export class Version implements VersionLike { + epoch: number; + debian: string; + upstream: string; - this.epoch = versionResult ? Number(versionResult[1]) : 0; - this.debian = versionParts.length > 1 ? versionParts.pop() : ""; - this.upstream = versionParts.join("-"); -}; + constructor(v: string) { + if (v.startsWith("v") || v.startsWith("V")) { + v = v.slice(1); + } + const versionResult = /^[a-zA-Z]?([0-9]*(?=:))?:(.*)/.exec(v); + const version = versionResult && versionResult[2] ? versionResult[2] : v; + const versionParts = version.split("-"); -Version.prototype.compare = function (b: Version) { - if ((this.epoch > 0 || b.epoch > 0) && Math.sign(this.epoch - b.epoch) !== 0) { - return Math.sign(this.epoch - b.epoch); + this.epoch = versionResult ? Number(versionResult[1]) : 0; + this.debian = versionParts.length > 1 ? versionParts.pop()! : ""; + this.upstream = versionParts.join("-"); } - if (this.compareStrings(this.upstream, b.upstream) !== 0) { - return this.compareStrings(this.upstream, b.upstream); - } - return this.compareStrings(this.debian, b.debian); -}; -Version.prototype.charCode = function (c: string) { - if (/[a-zA-Z]/.test(c)) { - return c.charCodeAt(0) - "A".charCodeAt(0) + 1; - } else if (/[.:+-:]/.test(c)) { - return c.charCodeAt(0) + "z".charCodeAt(0) + 1; - } // char codes are 46..58 - return 0; -}; + compare(b: VersionLike) { + if ((this.epoch > 0 || b.epoch > 0) && Math.sign(this.epoch - b.epoch) !== 0) { + return Math.sign(this.epoch - b.epoch); + } + if (this.compareStrings(this.upstream, b.upstream) !== 0) { + return this.compareStrings(this.upstream, b.upstream); + } + return this.compareStrings(this.debian, b.debian); + } -// find index in "array" by "fn" callback. -Version.prototype.findIndex = function (array: string[], fn: (c: string, i: number) => boolean) { - for (let i = 0; i < array.length; i++) { - if (fn(array[i], i)) { - return i; + charCode(c: string) { + if (/[a-zA-Z]/.test(c)) { + return c.charCodeAt(0) - "A".charCodeAt(0) + 1; + } else if (/[.:+-:]/.test(c)) { + return c.charCodeAt(0) + "z".charCodeAt(0) + 1; } + return 0; } - return -1; -}; -Version.prototype.compareChunk = function (a: string, b: string) { - let ca = a.split(""); - let cb = b.split(""); - let diff = this.findIndex(ca, function (c: string, index: number) { - return !(cb[index] && c === cb[index]); - }); - if (diff === -1) { - if (cb.length > ca.length) { - if (cb[ca.length] === "~") { - return 1; + findIndex(array: string[], fn: (c: string, i: number) => boolean) { + for (let i = 0; i < array.length; i++) { + if (fn(array[i], i)) { + return i; } - return -1; } - return 0; // no diff found and same length - } else if (!cb[diff]) { - return ca[diff] === "~" ? -1 : 1; + return -1; } - return this.charCode(ca[diff]) > this.charCode(cb[diff]) ? 1 : -1; -}; -Version.prototype.compareStrings = function (a: string, b: string) { - if (a === b) { - return 0; - } - let parseA = /([^0-9]+|[0-9]+)/g; - let parseB = /([^0-9]+|[0-9]+)/g; - let ra = parseA.exec(a); - let rb = parseB.exec(b); - while (ra !== null && rb !== null) { - if ((isNaN(Number(ra[1])) || isNaN(Number(rb[1]))) && ra[1] !== rb[1]) { - // a or b is not a number and they're not equal. Note : "" IS a number so both null is impossible - return this.compareChunk(ra[1], rb[1]); - } // both are numbers - if (ra[1] !== rb[1]) { - return parseInt(ra[1], 10) > parseInt(rb[1], 10) ? 1 : -1; + compareChunk(a: string, b: string) { + const ca = a.split(""); + const cb = b.split(""); + const diff = this.findIndex(ca, function (c: string, index: number) { + return !(cb[index] && c === cb[index]); + }); + if (diff === -1) { + if (cb.length > ca.length) { + if (cb[ca.length] === "~") { + return 1; + } + return -1; + } + return 0; + } else if (!cb[diff]) { + return ca[diff] === "~" ? -1 : 1; } - ra = parseA.exec(a); - rb = parseB.exec(b); + return this.charCode(ca[diff]) > this.charCode(cb[diff]) ? 1 : -1; } - if (!ra && rb) { - // rb doesn't get exec-ed when ra == null - return rb.length > 0 && rb[1].split("")[0] === "~" ? 1 : -1; - } else if (ra && !rb) { - return ra[1].split("")[0] === "~" ? -1 : 1; + + compareStrings(a: string, b: string) { + if (a === b) { + return 0; + } + const parseA = /([^0-9]+|[0-9]+)/g; + const parseB = /([^0-9]+|[0-9]+)/g; + let ra = parseA.exec(a); + let rb = parseB.exec(b); + while (ra !== null && rb !== null) { + if ((isNaN(Number(ra[1])) || isNaN(Number(rb[1]))) && ra[1] !== rb[1]) { + return this.compareChunk(ra[1], rb[1]); + } + if (ra[1] !== rb[1]) { + return parseInt(ra[1], 10) > parseInt(rb[1], 10) ? 1 : -1; + } + ra = parseA.exec(a); + rb = parseB.exec(b); + } + if (!ra && rb) { + return rb.length > 0 && rb[1].split("")[0] === "~" ? 1 : -1; + } else if (ra && !rb) { + return ra[1].split("")[0] === "~" ? -1 : 1; + } + return 0; } - return 0; -}; +} export const compare = (a: string, b: string) => { - let va = new Version(a); - let vb = new Version(b); + const va = new Version(a); + const vb = new Version(b); return vb.compare(va); }; diff --git a/package-lock.json b/package-lock.json index ca76ba23..e78c820b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,7 +47,7 @@ "globals": "^17.4.0", "prettier": "^3.8.1", "sass": "^1.98.0", - "typescript": "^5.9.3", + "typescript": "^6.0.2", "vite": "^7.3.1", "vite-plugin-checker": "^0.12.0", "vite-plugin-pwa": "^1.2.0" @@ -5975,9 +5975,9 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "dev": true, "license": "MIT" }, @@ -7626,9 +7626,9 @@ } }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", "dev": true, "license": "Apache-2.0", "bin": { diff --git a/package.json b/package.json index 4b626f25..52addf55 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "globals": "^17.4.0", "prettier": "^3.8.1", "sass": "^1.98.0", - "typescript": "^5.9.3", + "typescript": "^6.0.2", "vite": "^7.3.1", "vite-plugin-checker": "^0.12.0", "vite-plugin-pwa": "^1.2.0" diff --git a/tsconfig.json b/tsconfig.json index 9a473bba..ecebf272 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,10 +1,15 @@ { "compilerOptions": { "target": "es2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], "allowJs": true, "module": "ESNext", - "moduleResolution": "Node", - "esModuleInterop": true + "moduleResolution": "bundler", + "esModuleInterop": true, + "skipLibCheck": true, + "strict": true, + "noImplicitThis": true, + "useUnknownInCatchVariables": true }, - "include": ["lib"] + "include": ["lib", "vite-env.d.ts"] } diff --git a/vite-env.d.ts b/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/vite-env.d.ts @@ -0,0 +1 @@ +///