Merge pull request #864 from hgiesel/graphs

Decouple GraphsPage from individual graphs and RangeBox
This commit is contained in:
Damien Elmes 2020-12-29 10:14:04 +10:00 committed by GitHub
commit be7556629b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 209 additions and 133 deletions

View File

@ -30,6 +30,7 @@ ts_library(
"GraphsPage",
"lib",
"//ts/lib",
"@npm//svelte",
"@npm//svelte2tsx",
],
)

View File

@ -3,156 +3,61 @@
</script>
<script lang="typescript">
import type { SvelteComponent } from "svelte/internal";
import type { I18n } from "anki/i18n";
import type pb from "anki/backend_proto";
import { getGraphData, RevlogRange } from "./graph-helpers";
import IntervalsGraph from "./IntervalsGraph.svelte";
import EaseGraph from "./EaseGraph.svelte";
import AddedGraph from "./AddedGraph.svelte";
import TodayStats from "./TodayStats.svelte";
import ButtonsGraph from "./ButtonsGraph.svelte";
import CardCounts from "./CardCounts.svelte";
import HourGraph from "./HourGraph.svelte";
import FutureDue from "./FutureDue.svelte";
import ReviewsGraph from "./ReviewsGraph.svelte";
import CalendarGraph from "./CalendarGraph.svelte";
export let i18n: I18n;
export let nightMode: boolean;
export let graphs: SvelteComponent[];
export let search: string;
export let days: number;
export let controller: SvelteComponent;
let active = false;
let sourceData: pb.BackendProto.GraphsOut | null = null;
let revlogRange: RevlogRange;
enum SearchRange {
Deck = 1,
Collection = 2,
Custom = 3,
}
let searchRange: SearchRange = SearchRange.Deck;
let revlogRange: RevlogRange = RevlogRange.Year;
let days: number = 31;
let refreshing = false;
let search = "deck:current";
let displayedSearch = search;
const refresh = async () => {
refreshing = true;
const refreshWith = async (search: string, days: number) => {
active = true;
try {
sourceData = await getGraphData(search, days);
revlogRange = days > 365 || days === 0 ? RevlogRange.All : RevlogRange.Year;
} catch (e) {
sourceData = null;
alert(i18n.tr(i18n.TR.STATISTICS_ERROR_FETCHING));
}
refreshing = false;
active = false;
};
$: {
// refresh if either of these change
search;
days;
refresh();
}
$: {
switch (searchRange as SearchRange) {
case SearchRange.Deck:
search = displayedSearch = "deck:current";
break;
case SearchRange.Collection:
search = displayedSearch = "";
break;
case SearchRange.Custom:
break;
}
}
$: {
switch (revlogRange as RevlogRange) {
case RevlogRange.Year:
days = 365;
break;
case RevlogRange.All:
days = 0;
break;
}
}
const searchKeyUp = (e: KeyboardEvent) => {
// fetch data on enter
if (e.key == "Enter") {
const wasSame = search == displayedSearch;
search = displayedSearch;
if (wasSame) {
// force a refresh (user may have changed current deck, etc)
refresh();
}
}
const refresh = (event: CustomEvent) => {
refreshWith(event.detail.search, event.detail.days);
};
const year = i18n.tr(i18n.TR.STATISTICS_RANGE_1_YEAR_HISTORY);
const deck = i18n.tr(i18n.TR.STATISTICS_RANGE_DECK);
const collection = i18n.tr(i18n.TR.STATISTICS_RANGE_COLLECTION);
const searchLabel = i18n.tr(i18n.TR.STATISTICS_RANGE_SEARCH);
const all = i18n.tr(i18n.TR.STATISTICS_RANGE_ALL_HISTORY);
refreshWith(search, days);
</script>
<style>
.no-focus-outline:focus {
outline: 0;
}
</style>
{#if controller}
<svelte:component
this={controller}
{i18n}
{search}
{days}
{active}
on:update={refresh} />
{/if}
<div class="range-box">
<div class="spin {refreshing ? 'active' : ''}"></div>
<div class="range-box-inner">
<label>
<input type="radio" bind:group={searchRange} value={SearchRange.Deck} />
{deck}
</label>
<label>
<input
type="radio"
bind:group={searchRange}
value={SearchRange.Collection} />
{collection}
</label>
<input
type="text"
bind:value={displayedSearch}
on:keyup={searchKeyUp}
on:focus={() => {
searchRange = SearchRange.Custom;
}}
placeholder={searchLabel} />
{#if sourceData}
<div tabindex="-1" class="no-focus-outline">
{#each graphs as graph}
<svelte:component
this={graph}
{sourceData}
{revlogRange}
{i18n}
{nightMode} />
{/each}
</div>
<div class="range-box-inner">
<label>
<input type="radio" bind:group={revlogRange} value={RevlogRange.Year} />
{year}
</label>
<label>
<input type="radio" bind:group={revlogRange} value={RevlogRange.All} />
{all}
</label>
</div>
</div>
<div class="range-box-pad" />
<div tabindex="-1" class="no-focus-outline">
{#if sourceData}
<TodayStats {sourceData} {i18n} />
<FutureDue {sourceData} {i18n} />
<CalendarGraph {sourceData} {revlogRange} {i18n} {nightMode} />
<ReviewsGraph {sourceData} {revlogRange} {i18n} />
<CardCounts {sourceData} {i18n} />
<IntervalsGraph {sourceData} {i18n} />
<EaseGraph {sourceData} {i18n} />
<HourGraph {sourceData} {revlogRange} {i18n} />
<ButtonsGraph {sourceData} {revlogRange} {i18n} />
<AddedGraph {sourceData} {i18n} />
{/if}
</div>
{/if}

120
ts/graphs/RangeBox.svelte Normal file
View File

@ -0,0 +1,120 @@
<script lang="typescript">
import { createEventDispatcher } from "svelte";
import type { I18n } from "anki/i18n";
import { RevlogRange } from "./graph-helpers";
enum SearchRange {
Deck = 1,
Collection = 2,
Custom = 3,
}
export let i18n: I18n;
export let active: boolean;
export let days: number;
export let search: string;
const dispatch = createEventDispatcher();
let revlogRange: RevlogRange =
days > 365 || days === 0 ? RevlogRange.Year : RevlogRange.All;
let searchRange: SearchRange =
search === "deck:current"
? SearchRange.Deck
: search === ""
? SearchRange.Collection
: SearchRange.Custom;
let displayedSearch = search;
const update = () => {
dispatch("update", {
days: days,
search: search,
searchRange: searchRange,
});
};
$: {
switch (searchRange as SearchRange) {
case SearchRange.Deck:
search = displayedSearch = "deck:current";
update();
break;
case SearchRange.Collection:
search = displayedSearch = "";
update();
break;
}
}
$: {
switch (revlogRange as RevlogRange) {
case RevlogRange.Year:
days = 365;
update();
break;
case RevlogRange.All:
days = 0;
update();
break;
}
}
const searchKeyUp = (e: KeyboardEvent) => {
// fetch data on enter
if (e.key == "Enter") {
search = displayedSearch;
update();
}
};
const year = i18n.tr(i18n.TR.STATISTICS_RANGE_1_YEAR_HISTORY);
const deck = i18n.tr(i18n.TR.STATISTICS_RANGE_DECK);
const collection = i18n.tr(i18n.TR.STATISTICS_RANGE_COLLECTION);
const searchLabel = i18n.tr(i18n.TR.STATISTICS_RANGE_SEARCH);
const all = i18n.tr(i18n.TR.STATISTICS_RANGE_ALL_HISTORY);
</script>
<div class="range-box">
<div class="spin" class:active></div>
<div class="range-box-inner">
<label>
<input type="radio" bind:group={searchRange} value={SearchRange.Deck} />
{deck}
</label>
<label>
<input
type="radio"
bind:group={searchRange}
value={SearchRange.Collection} />
{collection}
</label>
<input
type="text"
bind:value={displayedSearch}
on:keyup={searchKeyUp}
on:focus={() => {
searchRange = SearchRange.Custom;
}}
placeholder={searchLabel} />
</div>
<div class="range-box-inner">
<label>
<input type="radio" bind:group={revlogRange} value={RevlogRange.Year} />
{year}
</label>
<label>
<input type="radio" bind:group={revlogRange} value={RevlogRange.All} />
{all}
</label>
</div>
</div>
<div class="range-box-pad" />

View File

@ -1,15 +1,44 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import type { SvelteComponent } from "svelte/internal";
import { setupI18n } from "anki/i18n";
import GraphsPage from "./GraphsPage.svelte";
import { checkNightMode } from "anki/nightmode";
export function graphs(target: HTMLDivElement): void {
export { default as RangeBox } from "./RangeBox.svelte";
export { default as IntervalsGraph } from "./IntervalsGraph.svelte";
export { default as EaseGraph } from "./EaseGraph.svelte";
export { default as AddedGraph } from "./AddedGraph.svelte";
export { default as TodayStats } from "./TodayStats.svelte";
export { default as ButtonsGraph } from "./ButtonsGraph.svelte";
export { default as CardCounts } from "./CardCounts.svelte";
export { default as HourGraph } from "./HourGraph.svelte";
export { default as FutureDue } from "./FutureDue.svelte";
export { default as ReviewsGraph } from "./ReviewsGraph.svelte";
export { default as CalendarGraph } from "./CalendarGraph.svelte";
export { RevlogRange } from "./graph-helpers";
export function graphs(
target: HTMLDivElement,
graphs: SvelteComponent[],
{ search = "deck:current", days = 365, controller = null } = {}
): void {
const nightMode = checkNightMode();
setupI18n().then((i18n) => {
new GraphsPage({
target,
props: { i18n, nightMode: checkNightMode() },
props: {
i18n,
graphs,
nightMode,
search,
days,
controller,
},
});
});
}

View File

@ -11,6 +11,23 @@
<script src="../js/vendor/protobuf.min.js"></script>
<script src="graphs.js"></script>
<script>
anki.graphs(document.getElementById("main"));
anki.graphs(
document.getElementById("main"),
[
anki.TodayStats,
anki.FutureDue,
anki.CalendarGraph,
anki.ReviewsGraph,
anki.CardCounts,
anki.IntervalsGraph,
anki.EaseGraph,
anki.HourGraph,
anki.ButtonsGraph,
anki.AddedGraph,
],
{
controller: anki.RangeBox,
}
);
</script>
</html>

View File

@ -193,3 +193,7 @@
.align-start {
text-align: start;
}
.no-focus-outline:focus {
outline: 0;
}