From dcff5e28fae9076d1f5bd5714554e842429082cb Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Mon, 22 Jun 2020 19:11:50 +1000 Subject: [PATCH] add top level component and pass search/day limit back from frontend --- qt/aqt/mediasrv.py | 18 ++++++--- ts/package.json | 2 + ts/src/html/graphs.html | 6 ++- ts/src/stats/GraphsPage.svelte | 65 ++++++++++++++++++++++++++++++ ts/src/stats/IntervalsGraph.svelte | 14 ++++--- ts/src/stats/graphs.ts | 39 ++++++++---------- ts/src/stats/intervals.ts | 13 ++++-- ts/webpack.config.js | 2 +- 8 files changed, 119 insertions(+), 40 deletions(-) create mode 100644 ts/src/stats/GraphsPage.svelte diff --git a/qt/aqt/mediasrv.py b/qt/aqt/mediasrv.py index a3a38d1e2..66fb2ecb9 100644 --- a/qt/aqt/mediasrv.py +++ b/qt/aqt/mediasrv.py @@ -14,6 +14,7 @@ from typing import Optional import aqt from anki.collection import Collection +from anki.rsbackend import from_json_bytes from anki.utils import devMode from aqt.qt import * from aqt.utils import aqt_data_folder @@ -78,7 +79,7 @@ class MediaServer(threading.Thread): class RequestHandler(http.server.SimpleHTTPRequestHandler): - timeout = 1 + timeout = 10 mw: Optional[aqt.main.AnkiQt] = None def do_GET(self): @@ -181,7 +182,9 @@ class RequestHandler(http.server.SimpleHTTPRequestHandler): cmd = self.path[len("/_anki/") :] if cmd == "graphData": - data = graph_data(self.mw.col) + content_length = int(self.headers['Content-Length']) + body = self.rfile.read(content_length) + data = graph_data(self.mw.col, **from_json_bytes(body)) else: self.send_error(HTTPStatus.NOT_FOUND, "Method not found") return @@ -195,10 +198,13 @@ class RequestHandler(http.server.SimpleHTTPRequestHandler): self.wfile.write(data) -def graph_data(col: Collection) -> bytes: - graphs = col.backend.graphs(search="", days=0) - return graphs - +def graph_data(col: Collection, search: str, days: str) -> bytes: + try: + return col.backend.graphs(search=search, days=days) + except Exception as e: + # likely searching error + print(e) + return b'' # work around Windows machines with incorrect mime type RequestHandler.extensions_map[".css"] = "text/css" diff --git a/ts/package.json b/ts/package.json index 5c1fb531f..9f2cbd610 100644 --- a/ts/package.json +++ b/ts/package.json @@ -10,6 +10,7 @@ "@types/d3-selection": "^1.4.1", "@types/d3-shape": "^1.3.2", "@types/d3-transition": "^1.1.6", + "@types/lodash.debounce": "^4.0.6", "@types/lodash.throttle": "^4.1.6", "@types/long": "^4.0.1", "@typescript-eslint/eslint-plugin": "^2.11.0", @@ -47,6 +48,7 @@ "d3-selection": "^1.4.1", "d3-shape": "^1.3.7", "d3-transition": "^1.3.2", + "lodash.debounce": "^4.0.8", "lodash.throttle": "^4.1.1", "protobufjs": "^6.9.0" }, diff --git a/ts/src/html/graphs.html b/ts/src/html/graphs.html index 33a634c71..cb91994c1 100644 --- a/ts/src/html/graphs.html +++ b/ts/src/html/graphs.html @@ -4,9 +4,11 @@ -
+
diff --git a/ts/src/stats/GraphsPage.svelte b/ts/src/stats/GraphsPage.svelte new file mode 100644 index 000000000..3536d83e6 --- /dev/null +++ b/ts/src/stats/GraphsPage.svelte @@ -0,0 +1,65 @@ + + + + +
+ + + +
+ + + + diff --git a/ts/src/stats/IntervalsGraph.svelte b/ts/src/stats/IntervalsGraph.svelte index ef656d8bd..5fedf49a6 100644 --- a/ts/src/stats/IntervalsGraph.svelte +++ b/ts/src/stats/IntervalsGraph.svelte @@ -9,7 +9,7 @@ import { onMount } from "svelte"; import pb from "../backend/proto"; - export let cards: pb.BackendProto.Card[] | null = null; + export let data: pb.BackendProto.GraphsOut | null = null; let svg = null as HTMLElement | SVGElement | null; let updater = null as IntervalUpdateFn | null; @@ -20,10 +20,14 @@ let range = IntervalRange.Percentile95; - let graphData: IntervalGraphData; - $: graphData = gatherIntervalData(cards!); - $: if (updater) { - updater(graphData, range); + let intervalData: IntervalGraphData | null = null; + $: if (data) { + console.log("gathering data"); + intervalData = gatherIntervalData(data); + } + + $: if (intervalData && updater) { + updater(intervalData, range); } diff --git a/ts/src/stats/graphs.ts b/ts/src/stats/graphs.ts index 4b193642b..1166a8ece 100644 --- a/ts/src/stats/graphs.ts +++ b/ts/src/stats/graphs.ts @@ -7,40 +7,35 @@ @typescript-eslint/ban-ts-ignore: "off" */ import pb from "../backend/proto"; -import style from "./graphs.css"; -async function fetchData(): Promise { - let t = performance.now(); +async function fetchData(search: string, days: number): Promise { const resp = await fetch("/_anki/graphData", { method: "POST", + body: JSON.stringify({ + search, + days, + }), }); if (!resp.ok) { throw Error(`unexpected reply: ${resp.statusText}`); } - console.log(`fetch in ${performance.now() - t}ms`); - t = performance.now(); // get returned bytes const respBlob = await resp.blob(); const respBuf = await new Response(respBlob).arrayBuffer(); const bytes = new Uint8Array(respBuf); - console.log(`bytes in ${performance.now() - t}ms`); - t = performance.now(); return bytes; } -import IntervalGraph from "./IntervalsGraph.svelte"; - -export async function renderGraphs(): Promise { - const bytes = await fetchData(); - let t = performance.now(); - const data = pb.BackendProto.GraphsOut.decode(bytes); - console.log(`decode in ${performance.now() - t}ms`); - t = performance.now(); - - document.head.append(style); - - new IntervalGraph({ - target: document.getElementById("svelte")!, - props: { cards: data.cards2 }, - }); +export async function getGraphData( + search: string, + days: number +): Promise { + const bytes = await fetchData(search, days); + return pb.BackendProto.GraphsOut.decode(bytes); +} + +export enum GraphRange { + Month = 1, + Year = 2, + All = 3, } diff --git a/ts/src/stats/intervals.ts b/ts/src/stats/intervals.ts index 9e8a87c14..f7a6f15a9 100644 --- a/ts/src/stats/intervals.ts +++ b/ts/src/stats/intervals.ts @@ -31,8 +31,8 @@ export enum IntervalRange { All = 4, } -export function gatherIntervalData(cards: pb.BackendProto.Card[]): IntervalGraphData { - const intervals = cards +export function gatherIntervalData(data: pb.BackendProto.GraphsOut): IntervalGraphData { + const intervals = (data.cards2 as pb.BackendProto.Card[]) .filter((c) => c.queue == CardQueue.Review) .map((c) => c.ivl); return { intervals }; @@ -152,11 +152,16 @@ export function intervalGraph(svgElem: SVGElement): IntervalUpdateFn { yAxisGroup.transition(t as any).call(updateYAxis as any, y); + function barWidth(d: any): number { + const width = Math.max(0, x(d.x1) - x(d.x0) - 1); + return width ? width : 0; + } + const updateBar = (sel: any) => { return sel.call((sel) => sel .transition(t as any) - .attr("width", (d: any) => Math.max(0, x(d.x1) - x(d.x0) - 1)) + .attr("width", barWidth) .attr("x", (d: any) => x(d.x0)) .attr("y", (d: any) => y(d.length)!) .attr("height", (d: any) => y(0) - y(d.length)) @@ -210,7 +215,7 @@ export function intervalGraph(svgElem: SVGElement): IntervalUpdateFn { .join("rect") .attr("x", (d: any) => x(d.x0)) .attr("y", () => y(yMax!)) - .attr("width", (d: any) => Math.max(0, x(d.x1) - x(d.x0) - 1)) + .attr("width", barWidth) .attr("height", () => y(0) - y(yMax!)) .attr("fill", "none") .attr("pointer-events", "all") diff --git a/ts/webpack.config.js b/ts/webpack.config.js index 807669a9e..b48784b6b 100644 --- a/ts/webpack.config.js +++ b/ts/webpack.config.js @@ -5,7 +5,7 @@ var path = require("path"); module.exports = { entry: { - graphs: ["./src/stats/graphs.ts"], + graphs: ["./src/stats/GraphsPage.svelte"], }, output: { library: "anki",