Anki/ts/graphs/RangeBox.svelte
Damien Elmes 7d8f19e6e4 merge in Henrik's TS/Svelte refactor with some changes
- The previous commits moved the majority of the remaining global css
into components; move the remaining @emotion/css references into
ticks.scss and the styling of the Graph.svelte. This is not as elegant
as the emotion solution, but builds a whole lot faster, and most of
our styling can be scoped to a component anyway.
- Leave the .html files in ts/ for now. AnkiMobile uses them, and
AnkiDroid likely will in the future too. In the long run we'll likely
move to loading the JS into an existing page instead of loading a
separate page, but at that point we can just exclude the .html file from
copy_files_into_group() without affecting other clients.

Closes #1074
2021-03-21 23:01:18 +10:00

169 lines
4 KiB
Svelte

<script lang="typescript">
import { createEventDispatcher } from "svelte";
import InputBox from "./InputBox.svelte";
import type { I18n } from "anki/i18n";
import { RevlogRange, daysToRevlogRange } from "./graph-helpers";
enum SearchRange {
Deck = 1,
Collection = 2,
Custom = 3,
}
type UpdateEventMap = {
update: { days: number; search: string; searchRange: SearchRange };
};
export let i18n: I18n;
export let active: boolean;
export let days: number;
export let search: string;
const dispatch = createEventDispatcher<UpdateEventMap>();
let revlogRange = daysToRevlogRange(days);
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>
<style lang="scss">
.range-box {
position: fixed;
z-index: 1;
top: 0;
width: 100%;
color: var(--text-fg);
background: var(--window-bg);
padding: 0.5em;
@media print {
position: absolute;
}
}
@keyframes spin {
0% {
-webkit-transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
}
}
.spin {
display: inline-block;
position: absolute;
font-size: 2em;
animation: spin;
animation-duration: 1s;
animation-iteration-count: infinite;
opacity: 0;
&.active {
opacity: 0.5;
transition: opacity 1s;
}
}
.range-box-pad {
height: 2em;
}
</style>
<div class="range-box">
<div class="spin" class:active></div>
<InputBox>
<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} />
</InputBox>
<InputBox>
<label>
<input type="radio" bind:group={revlogRange} value={RevlogRange.Year} />
{year}
</label>
<label>
<input type="radio" bind:group={revlogRange} value={RevlogRange.All} />
{all}
</label>
</InputBox>
</div>
<div class="range-box-pad" />