Redesign deck options screen, swap tooltips for help modals (#2139)

* Redesign deck config, swap tooltips for help modals, link to manual

* Replace canvas-inset with canvas-code for custom scheduling

* Make section header link to manual too

* Include elevation Sass library

* Remove two unused exports

* Fix tabbed spinboxes

* Update ftl/core/deck-config.ftl

* Update ftl/core/deck-config.ftl

* Fix format

* Make border-radius and box-shadow more subtle

* Fix margin for vertical aspect ratio

* Make direct hover on info badge apply effect instantly

* Add redirect line to manual underneath chapter
This commit is contained in:
Matthias Metelka 2022-10-25 08:18:50 +02:00 committed by GitHub
parent 2971eb9660
commit 264561cd0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 996 additions and 185 deletions

View File

@ -101,9 +101,13 @@ deck-config-bury-title = Burying
deck-config-bury-new-siblings = Bury new siblings
deck-config-bury-review-siblings = Bury review siblings
deck-config-bury-interday-learning-siblings = Bury interday learning siblings
deck-config-bury-tooltip =
Whether other cards of the same note (eg reverse cards, adjacent
cloze deletions) will be delayed until the next day.
deck-config-bury-new-tooltip =
Whether other `new` cards of the same note (eg reverse cards, adjacent cloze deletions)
will be delayed until the next day.
deck-config-bury-review-tooltip = Whether other `review` cards of the same note will be delayed until the next day.
deck-config-bury-interday-learning-tooltip =
Whether other `learning` cards of the same note with intervals > 1 day
will be delayed until the next day.
## Ordering section
@ -202,6 +206,9 @@ deck-config-show-answer-timer-tooltip =
deck-config-audio-title = Audio
deck-config-disable-autoplay = Don't play audio automatically
deck-config-disable-autoplay-tooltip =
When enabled, Anki will not play audio automatically.
It can be played manually by clicking/tapping on an audio icon, or by using the replay audio action.
deck-config-skip-question-when-replaying = Skip question when replaying answer
deck-config-always-include-question-audio-tooltip =
Whether the question audio should be included when the Replay action is

26
ftl/core/help.ftl Normal file
View File

@ -0,0 +1,26 @@
### Text shown in Help pages
## Basic terminology
help-chapter = chapter
## Header
# Tooltip for links to the manual
help-open-manual-chapter = Open chapter { $name } in Anki manual
# Displayed underneath chapter title
help-view-chapter-in-manual = View the { $chapter } on this topic in the official manual.
## Body
# Newly introduced settings may not have an explanation yet
help-no-explanation =
Whoops! There doesn't seem to be an explanation for this setting yet.
You can help us complete this help page on { $link }.
## Footer
# Link to more detailed information in the manual
help-for-more-info = For more information, see { $link } in the manual.

View File

@ -5,13 +5,16 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<script lang="ts">
import type { Breakpoint } from "./types";
let className: string = "";
export { className as class };
/* flex-basis: 100% if viewport < breakpoint otherwise
* as specified by --cols and --col-size */
export let breakpoint: Breakpoint = "xs";
</script>
<div
class="col"
class="col {className}"
class:col-xs={breakpoint === "xs"}
class:col-sm={breakpoint === "sm"}
class:col-md={breakpoint === "md"}

View File

@ -3,14 +3,20 @@ Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import type Carousel from "bootstrap/js/dist/carousel";
import type Modal from "bootstrap/js/dist/modal";
import DynamicallySlottable from "../components/DynamicallySlottable.svelte";
import Item from "../components/Item.svelte";
import * as tr from "../lib/ftl";
import CardStateCustomizer from "./CardStateCustomizer.svelte";
import HelpModal from "./HelpModal.svelte";
import type { DeckOptionsState } from "./lib";
import SettingTitle from "./SettingTitle.svelte";
import SpinBoxFloatRow from "./SpinBoxFloatRow.svelte";
import SpinBoxRow from "./SpinBoxRow.svelte";
import TitledContainer from "./TitledContainer.svelte";
import type { DeckOption } from "./types";
export let state: DeckOptionsState;
export let api: Record<string, never>;
@ -18,9 +24,66 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
const config = state.currentConfig;
const defaults = state.defaults;
const cardStateCustomizer = state.cardStateCustomizer;
const settings = {
maximumInterval: {
title: tr.schedulingMaximumInterval(),
help: tr.deckConfigMaximumIntervalTooltip(),
url: "https://docs.ankiweb.net/deck-options.html#maximum-interval",
},
startingEase: {
title: tr.schedulingStartingEase(),
help: tr.deckConfigStartingEaseTooltip(),
url: "https://docs.ankiweb.net/deck-options.html#starting-ease",
},
easyBonus: {
title: tr.schedulingEasyBonus(),
help: tr.deckConfigEasyBonusTooltip(),
url: "https://docs.ankiweb.net/deck-options.html#easy-bonus",
},
intervalModifier: {
title: tr.schedulingIntervalModifier(),
help: tr.deckConfigIntervalModifierTooltip(),
url: "https://docs.ankiweb.net/deck-options.html#interval-modifier",
},
hardInterval: {
title: tr.schedulingHardInterval(),
help: tr.deckConfigHardIntervalTooltip(),
url: "https://docs.ankiweb.net/deck-options.html#hard-interval",
},
newInterval: {
title: tr.schedulingNewInterval(),
help: tr.deckConfigNewIntervalTooltip(),
url: "https://docs.ankiweb.net/deck-options.html#new-interval",
},
customScheduling: {
title: tr.deckConfigCustomScheduling(),
help: tr.deckConfigCustomSchedulingTooltip(),
url: "https://faqs.ankiweb.net/the-2021-scheduler.html#add-ons-and-custom-scheduling",
},
};
const helpSections = Object.values(settings) as DeckOption[];
let modal: Modal;
let carousel: Carousel;
function openHelpModal(index: number): void {
modal.show();
carousel.to(index);
}
</script>
<TitledContainer title={tr.deckConfigAdvancedTitle()}>
<HelpModal
title={tr.deckConfigAdvancedTitle()}
url="https://docs.ankiweb.net/deck-options.html#advanced"
slot="tooltip"
{helpSections}
on:mount={(e) => {
modal = e.detail.modal;
carousel = e.detail.carousel;
}}
/>
<DynamicallySlottable slotHost={Item} {api}>
<Item>
<SpinBoxRow
@ -28,9 +91,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
defaultValue={defaults.maximumReviewInterval}
min={1}
max={365 * 100}
markdownTooltip={tr.deckConfigMaximumIntervalTooltip()}
>
{tr.schedulingMaximumInterval()}
<SettingTitle
on:click={() =>
openHelpModal(Object.keys(settings).indexOf("maximumInterval"))}
>{settings.maximumInterval.title}</SettingTitle
>
</SpinBoxRow>
</Item>
@ -40,9 +106,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
defaultValue={defaults.initialEase}
min={1.31}
max={5}
markdownTooltip={tr.deckConfigStartingEaseTooltip()}
>
{tr.schedulingStartingEase()}
<SettingTitle
on:click={() =>
openHelpModal(Object.keys(settings).indexOf("startingEase"))}
>{settings.startingEase.title}</SettingTitle
>
</SpinBoxFloatRow>
</Item>
@ -52,9 +121,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
defaultValue={defaults.easyMultiplier}
min={1}
max={5}
markdownTooltip={tr.deckConfigEasyBonusTooltip()}
>
{tr.schedulingEasyBonus()}
<SettingTitle
on:click={() =>
openHelpModal(Object.keys(settings).indexOf("easyBonus"))}
>{settings.easyBonus.title}</SettingTitle
>
</SpinBoxFloatRow>
</Item>
@ -64,9 +136,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
defaultValue={defaults.intervalMultiplier}
min={0.5}
max={2}
markdownTooltip={tr.deckConfigIntervalModifierTooltip()}
>
{tr.schedulingIntervalModifier()}
<SettingTitle
on:click={() =>
openHelpModal(
Object.keys(settings).indexOf("intervalModifier"),
)}>{settings.intervalModifier.title}</SettingTitle
>
</SpinBoxFloatRow>
</Item>
@ -76,9 +152,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
defaultValue={defaults.hardMultiplier}
min={0.5}
max={1.3}
markdownTooltip={tr.deckConfigHardIntervalTooltip()}
>
{tr.schedulingHardInterval()}
<SettingTitle
on:click={() =>
openHelpModal(Object.keys(settings).indexOf("hardInterval"))}
>{settings.hardInterval.title}</SettingTitle
>
</SpinBoxFloatRow>
</Item>
@ -87,15 +166,25 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
bind:value={$config.lapseMultiplier}
defaultValue={defaults.lapseMultiplier}
max={1}
markdownTooltip={tr.deckConfigNewIntervalTooltip()}
>
{tr.schedulingNewInterval()}
<SettingTitle
on:click={() =>
openHelpModal(Object.keys(settings).indexOf("newInterval"))}
>{settings.newInterval.title}</SettingTitle
>
</SpinBoxFloatRow>
</Item>
{#if state.v3Scheduler}
<Item>
<CardStateCustomizer bind:value={$cardStateCustomizer} />
<CardStateCustomizer
title={settings.customScheduling.title}
on:click={() =>
openHelpModal(
Object.keys(settings).indexOf("customScheduling"),
)}
bind:value={$cardStateCustomizer}
/>
</Item>
{/if}
</DynamicallySlottable>

View File

@ -3,28 +3,68 @@ Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import type Carousel from "bootstrap/js/dist/carousel";
import type Modal from "bootstrap/js/dist/modal";
import DynamicallySlottable from "../components/DynamicallySlottable.svelte";
import Item from "../components/Item.svelte";
import * as tr from "../lib/ftl";
import HelpModal from "./HelpModal.svelte";
import type { DeckOptionsState } from "./lib";
import SettingTitle from "./SettingTitle.svelte";
import SwitchRow from "./SwitchRow.svelte";
import TitledContainer from "./TitledContainer.svelte";
import type { DeckOption } from "./types";
export let state: DeckOptionsState;
export let api: Record<string, never>;
const config = state.currentConfig;
const defaults = state.defaults;
const settings = {
disableAutoplay: {
title: tr.deckConfigDisableAutoplay(),
help: tr.deckConfigDisableAutoplayTooltip(),
},
skipQuestionWhenReplaying: {
title: tr.deckConfigSkipQuestionWhenReplaying(),
help: tr.deckConfigAlwaysIncludeQuestionAudioTooltip(),
},
};
const helpSections = Object.values(settings) as DeckOption[];
let modal: Modal;
let carousel: Carousel;
function openHelpModal(index: number): void {
modal.show();
carousel.to(index);
}
</script>
<TitledContainer title={tr.deckConfigAudioTitle()}>
<HelpModal
title={tr.deckConfigAudioTitle()}
url="https://docs.ankiweb.net/deck-options.html#audio"
slot="tooltip"
{helpSections}
on:mount={(e) => {
modal = e.detail.modal;
carousel = e.detail.carousel;
}}
/>
<DynamicallySlottable slotHost={Item} {api}>
<Item>
<SwitchRow
bind:value={$config.disableAutoplay}
defaultValue={defaults.disableAutoplay}
>
{tr.deckConfigDisableAutoplay()}
<SettingTitle
on:click={() =>
openHelpModal(Object.keys(settings).indexOf("disableAutoplay"))}
>{settings.disableAutoplay.title}</SettingTitle
>
</SwitchRow>
</Item>
@ -32,9 +72,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<SwitchRow
bind:value={$config.skipQuestionWhenReplayingAnswer}
defaultValue={defaults.skipQuestionWhenReplayingAnswer}
markdownTooltip={tr.deckConfigAlwaysIncludeQuestionAudioTooltip()}
>
{tr.deckConfigSkipQuestionWhenReplaying()}
<SettingTitle
on:click={() =>
openHelpModal(
Object.keys(settings).indexOf("skipQuestionWhenReplaying"),
)}>{settings.skipQuestionWhenReplaying.title}</SettingTitle
>
</SwitchRow>
</Item>
</DynamicallySlottable>

View File

@ -18,6 +18,7 @@ compile_sass(
"//sass:breakpoints_lib",
"//sass:scrollbar_lib",
"//sass:night_mode_lib",
"//sass:elevation_lib",
"//sass/bootstrap",
],
)
@ -43,6 +44,7 @@ compile_svelte(
"//sass:breakpoints_lib",
"//sass:scrollbar_lib",
"//sass:night_mode_lib",
"//sass:elevation_lib",
"//sass/bootstrap",
],
)
@ -88,6 +90,7 @@ svelte_check(
"//sass:scrollbar_lib",
"//sass:breakpoints_lib",
"//sass:night_mode_lib",
"//sass:elevation_lib",
"//sass/bootstrap",
"//ts/components",
"//ts/sveltelib:sveltelib_pkg",

View File

@ -3,29 +3,69 @@ Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import type Carousel from "bootstrap/js/dist/carousel";
import type Modal from "bootstrap/js/dist/modal";
import DynamicallySlottable from "../components/DynamicallySlottable.svelte";
import Item from "../components/Item.svelte";
import * as tr from "../lib/ftl";
import HelpModal from "./HelpModal.svelte";
import type { DeckOptionsState } from "./lib";
import SettingTitle from "./SettingTitle.svelte";
import SwitchRow from "./SwitchRow.svelte";
import TitledContainer from "./TitledContainer.svelte";
import type { DeckOption } from "./types";
export let state: DeckOptionsState;
export let api: Record<string, never>;
const config = state.currentConfig;
const defaults = state.defaults;
const settings = {
buryNewSiblings: {
title: tr.deckConfigBuryNewSiblings(),
help: tr.deckConfigBuryNewTooltip(),
},
buryReviewSiblings: {
title: tr.deckConfigBuryReviewSiblings(),
help: tr.deckConfigBuryReviewTooltip(),
},
buryInterdayLearningSiblings: {
title: tr.deckConfigBuryInterdayLearningSiblings(),
help: tr.deckConfigBuryInterdayLearningTooltip(),
},
};
const helpSections = Object.values(settings) as DeckOption[];
let modal: Modal;
let carousel: Carousel;
function openHelpModal(index: number): void {
modal.show();
carousel.to(index);
}
</script>
<TitledContainer title={tr.deckConfigBuryTitle()}>
<HelpModal
title={tr.deckConfigBuryTitle()}
url="https://docs.ankiweb.net/studying.html#siblings-and-burying"
slot="tooltip"
{helpSections}
on:mount={(e) => {
modal = e.detail.modal;
carousel = e.detail.carousel;
}}
/>
<DynamicallySlottable slotHost={Item} {api}>
<Item>
<SwitchRow
bind:value={$config.buryNew}
defaultValue={defaults.buryNew}
markdownTooltip={tr.deckConfigBuryTooltip()}
<SwitchRow bind:value={$config.buryNew} defaultValue={defaults.buryNew}>
<SettingTitle
on:click={() =>
openHelpModal(Object.keys(settings).indexOf("buryNewSiblings"))}
>{settings.buryNewSiblings.title}</SettingTitle
>
{tr.deckConfigBuryNewSiblings()}
</SwitchRow>
</Item>
@ -33,9 +73,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<SwitchRow
bind:value={$config.buryReviews}
defaultValue={defaults.buryReviews}
markdownTooltip={tr.deckConfigBuryTooltip()}
>
{tr.deckConfigBuryReviewSiblings()}
<SettingTitle
on:click={() =>
openHelpModal(
Object.keys(settings).indexOf("buryReviewSiblings"),
)}>{settings.buryReviewSiblings.title}</SettingTitle
>
</SwitchRow>
</Item>
@ -44,9 +88,16 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<SwitchRow
bind:value={$config.buryInterdayLearning}
defaultValue={defaults.buryInterdayLearning}
markdownTooltip={tr.deckConfigBuryTooltip()}
>
{tr.deckConfigBuryInterdayLearningSiblings()}
<SettingTitle
on:click={() =>
openHelpModal(
Object.keys(settings).indexOf(
"buryInterdayLearningSiblings",
),
)}
>{settings.buryInterdayLearningSiblings.title}</SettingTitle
>
</SwitchRow>
</Item>
{/if}

View File

@ -5,20 +5,21 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<script lang="ts">
import Col from "../components/Col.svelte";
import Row from "../components/Row.svelte";
import * as tr from "../lib/ftl";
import ConfigInput from "./ConfigInput.svelte";
import RevertButton from "./RevertButton.svelte";
import TooltipLabel from "./TooltipLabel.svelte";
import SettingTitle from "./SettingTitle.svelte";
export let value: string;
export let title: string;
</script>
<Row>
<Col>
<div class="text">
<TooltipLabel markdownTooltip={tr.deckConfigCustomSchedulingTooltip()}>
{tr.deckConfigCustomScheduling()}:</TooltipLabel
>
<RevertButton bind:value defaultValue="" />
<ConfigInput>
<SettingTitle on:click>{title}</SettingTitle>
<RevertButton slot="revert" bind:value defaultValue="" />
</ConfigInput>
</div>
</Col>
</Row>
@ -32,13 +33,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<style lang="scss">
.text {
width: 100%;
min-height: 2em;
}
.card-state-customizer {
color: var(--fg);
background-color: var(--canvas-elevated);
background-color: var(--canvas-code);
border: 1px solid var(--border-subtle);
width: 100%;
height: 10em;
font-family: monospace;

View File

@ -0,0 +1,38 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
export let grow = true;
let width = 0;
</script>
<div
class="config-input position-relative justify-content-end"
class:flex-grow-1={grow}
style:--offset="-{width}px"
>
<div class="revert" bind:clientWidth={width}>
<slot name="revert" />
</div>
<slot />
</div>
<style lang="scss">
.revert {
position: absolute;
right: var(--offset);
color: var(--fg-faint);
}
.config-input {
&:hover,
&:focus-within {
.revert {
color: var(--fg-subtle);
}
}
.revert:hover {
color: var(--fg);
}
}
</style>

View File

@ -3,14 +3,20 @@
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import type Carousel from "bootstrap/js/dist/carousel";
import type Modal from "bootstrap/js/dist/modal";
import DynamicallySlottable from "../components/DynamicallySlottable.svelte";
import Item from "../components/Item.svelte";
import * as tr from "../lib/ftl";
import HelpModal from "./HelpModal.svelte";
import type { DeckOptionsState } from "./lib";
import { ValueTab } from "./lib";
import SettingTitle from "./SettingTitle.svelte";
import SpinBoxRow from "./SpinBoxRow.svelte";
import TabbedValue from "./TabbedValue.svelte";
import TitledContainer from "./TitledContainer.svelte";
import type { DeckOption } from "./types";
import Warning from "./Warning.svelte";
export let state: DeckOptionsState;
@ -120,35 +126,66 @@
: [],
);
let reviewsValue: number;
let newValue: number;
let reviewsValue: number;
const settings = {
newLimit: {
title: tr.schedulingNewCardsday(),
help: tr.deckConfigNewLimitTooltip() + v3Extra,
url: "https://docs.ankiweb.net/deck-options.html#new-cardsday",
},
reviewLimit: {
title: tr.schedulingMaximumReviewsday(),
help: tr.deckConfigReviewLimitTooltip() + v3Extra,
url: "https://docs.ankiweb.net/deck-options.html#maximum-reviewsday",
},
};
const helpSections = Object.values(settings) as DeckOption[];
let modal: Modal;
let carousel: Carousel;
function openHelpModal(index: number): void {
modal.show();
carousel.to(index);
}
</script>
<TitledContainer title={tr.deckConfigDailyLimits()}>
<HelpModal
title={tr.deckConfigDailyLimits()}
url="https://docs.ankiweb.net/deck-options.html#daily-limits"
slot="tooltip"
{helpSections}
on:mount={(e) => {
modal = e.detail.modal;
carousel = e.detail.carousel;
}}
/>
<DynamicallySlottable slotHost={Item} {api}>
<TabbedValue tabs={newTabs} bind:value={newValue} />
<Item>
<SpinBoxRow
bind:value={newValue}
defaultValue={defaults.newPerDay}
markdownTooltip={tr.deckConfigNewLimitTooltip() + v3Extra}
<SpinBoxRow bind:value={newValue} defaultValue={defaults.newPerDay}>
<TabbedValue slot="tabs" tabs={newTabs} bind:value={newValue} />
<SettingTitle
on:click={() =>
openHelpModal(Object.keys(settings).indexOf("newLimit"))}
>{settings.newLimit.title}</SettingTitle
>
{tr.schedulingNewCardsday()}
</SpinBoxRow>
</Item>
<Item>
<Warning warning={newCardsGreaterThanParent} />
</Item>
<TabbedValue tabs={reviewTabs} bind:value={reviewsValue} />
<Item>
<SpinBoxRow
bind:value={reviewsValue}
defaultValue={defaults.reviewsPerDay}
markdownTooltip={tr.deckConfigReviewLimitTooltip() + v3Extra}
<SpinBoxRow bind:value={reviewsValue} defaultValue={defaults.reviewsPerDay}>
<TabbedValue slot="tabs" tabs={reviewTabs} bind:value={reviewsValue} />
<SettingTitle
on:click={() =>
openHelpModal(Object.keys(settings).indexOf("reviewLimit"))}
>{settings.reviewLimit.title}</SettingTitle
>
{tr.schedulingMaximumReviewsday()}
</SpinBoxRow>
</Item>

View File

@ -64,7 +64,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<Container
breakpoint="sm"
--gutter-inline="0.25rem"
--gutter-block="0.5rem"
--gutter-block="0.75rem"
class="container-columns"
>
<DynamicallySlottable slotHost={Item} api={options}>

View File

@ -3,14 +3,20 @@ Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import type Carousel from "bootstrap/js/dist/carousel";
import type Modal from "bootstrap/js/dist/modal";
import DynamicallySlottable from "../components/DynamicallySlottable.svelte";
import Item from "../components/Item.svelte";
import * as tr from "../lib/ftl";
import { DeckConfig } from "../lib/proto";
import EnumSelectorRow from "./EnumSelectorRow.svelte";
import HelpModal from "./HelpModal.svelte";
import type { DeckOptionsState } from "./lib";
import SettingTitle from "./SettingTitle.svelte";
import { reviewMixChoices } from "./strings";
import TitledContainer from "./TitledContainer.svelte";
import type { DeckOption } from "./types";
export let state: DeckOptionsState;
export let api: Record<string, never>;
@ -84,19 +90,64 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
$config.newCardSortOrder = 0;
}
}
const settings = {
newGatherPriority: {
title: tr.deckConfigNewGatherPriority(),
help: tr.deckConfigNewGatherPriorityTooltip_2() + currentDeck,
},
newCardSortOrder: {
title: tr.deckConfigNewCardSortOrder(),
help: tr.deckConfigNewCardSortOrderTooltip_2() + currentDeck,
},
newReviewPriority: {
title: tr.deckConfigNewReviewPriority(),
help: tr.deckConfigNewReviewPriorityTooltip() + currentDeck,
},
interdayStepPriority: {
title: tr.deckConfigInterdayStepPriority(),
help: tr.deckConfigInterdayStepPriorityTooltip() + currentDeck,
},
reviewSortOrder: {
title: tr.deckConfigReviewSortOrder(),
help: tr.deckConfigReviewSortOrderTooltip() + currentDeck,
},
};
const helpSections = Object.values(settings) as DeckOption[];
let modal: Modal;
let carousel: Carousel;
function openHelpModal(index: number): void {
modal.show();
carousel.to(index);
}
</script>
<TitledContainer title={tr.deckConfigOrderingTitle()}>
<HelpModal
title={tr.deckConfigOrderingTitle()}
url="https://docs.ankiweb.net/deck-options.html#display-order"
slot="tooltip"
{helpSections}
on:mount={(e) => {
modal = e.detail.modal;
carousel = e.detail.carousel;
}}
/>
<DynamicallySlottable slotHost={Item} {api}>
<Item>
<EnumSelectorRow
bind:value={$config.newCardGatherPriority}
defaultValue={defaults.newCardGatherPriority}
choices={newGatherPriorityChoices}
markdownTooltip={tr.deckConfigNewGatherPriorityTooltip_2() +
currentDeck}
>
{tr.deckConfigNewGatherPriority()}
<SettingTitle
on:click={() =>
openHelpModal(
Object.keys(settings).indexOf("newGatherPriority"),
)}>{settings.newGatherPriority.title}</SettingTitle
>
</EnumSelectorRow>
</Item>
@ -106,9 +157,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
defaultValue={defaults.newCardSortOrder}
choices={newSortOrderChoices}
disabled={disabledNewSortOrders}
markdownTooltip={tr.deckConfigNewCardSortOrderTooltip_2() + currentDeck}
>
{tr.deckConfigNewCardSortOrder()}
<SettingTitle
on:click={() =>
openHelpModal(
Object.keys(settings).indexOf("newCardSortOrder"),
)}>{settings.newCardSortOrder.title}</SettingTitle
>
</EnumSelectorRow>
</Item>
@ -117,9 +172,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
bind:value={$config.newMix}
defaultValue={defaults.newMix}
choices={reviewMixChoices()}
markdownTooltip={tr.deckConfigNewReviewPriorityTooltip() + currentDeck}
>
{tr.deckConfigNewReviewPriority()}
<SettingTitle
on:click={() =>
openHelpModal(
Object.keys(settings).indexOf("newReviewPriority"),
)}>{settings.newReviewPriority.title}</SettingTitle
>
</EnumSelectorRow>
</Item>
@ -128,10 +187,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
bind:value={$config.interdayLearningMix}
defaultValue={defaults.interdayLearningMix}
choices={reviewMixChoices()}
markdownTooltip={tr.deckConfigInterdayStepPriorityTooltip() +
currentDeck}
>
{tr.deckConfigInterdayStepPriority()}
<SettingTitle
on:click={() =>
openHelpModal(
Object.keys(settings).indexOf("interdayStepPriority"),
)}>{settings.interdayStepPriority.title}</SettingTitle
>
</EnumSelectorRow>
</Item>
@ -140,9 +202,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
bind:value={$config.reviewOrder}
defaultValue={defaults.reviewOrder}
choices={reviewOrderChoices}
markdownTooltip={tr.deckConfigReviewSortOrderTooltip() + currentDeck}
>
{tr.deckConfigReviewSortOrder()}
<SettingTitle
on:click={() =>
openHelpModal(Object.keys(settings).indexOf("reviewSortOrder"))}
>{settings.reviewSortOrder.title}</SettingTitle
>
</EnumSelectorRow>
</Item>
</DynamicallySlottable>

View File

@ -6,24 +6,25 @@
import Col from "../components/Col.svelte";
import Row from "../components/Row.svelte";
import type { Breakpoint } from "../components/types";
import ConfigInput from "./ConfigInput.svelte";
import EnumSelector from "./EnumSelector.svelte";
import RevertButton from "./RevertButton.svelte";
import TooltipLabel from "./TooltipLabel.svelte";
export let value: number;
export let defaultValue: number;
export let breakpoint: Breakpoint = "md";
export let choices: string[];
export let disabled: number[] = [];
export let markdownTooltip: string;
</script>
<Row --cols={12}>
<Row --cols={13}>
<Col --col-size={7} {breakpoint}>
<TooltipLabel {markdownTooltip}><slot /></TooltipLabel>
<slot />
</Col>
<Col --col-size={5} {breakpoint}>
<Col --col-size={6} {breakpoint}>
<ConfigInput>
<EnumSelector bind:value options={choices} {disabled} />
<RevertButton bind:value {defaultValue} />
<RevertButton slot="revert" bind:value {defaultValue} />
</ConfigInput>
</Col>
</Row>

View File

@ -0,0 +1,183 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import Carousel from "bootstrap/js/dist/carousel";
import Modal from "bootstrap/js/dist/modal";
import { createEventDispatcher, getContext, onMount } from "svelte";
import Badge from "../components/Badge.svelte";
import Col from "../components/Col.svelte";
import { modalsKey } from "../components/context-keys";
import Row from "../components/Row.svelte";
import * as tr from "../lib/ftl";
import { pageTheme } from "../sveltelib/theme";
import HelpSection from "./HelpSection.svelte";
import { infoCircle } from "./icons";
import type { DeckOption } from "./types";
export let title: string;
export let url: string;
export let startIndex = 0;
export let helpSections: DeckOption[];
export const modalKey: string = Math.random().toString(36).substring(2);
const modals = getContext<Map<string, Modal>>(modalsKey);
let modal: Modal;
let carousel: Carousel;
let modalRef: HTMLDivElement;
let carouselRef: HTMLDivElement;
function onOkClicked(): void {
modal.hide();
}
const dispatch = createEventDispatcher();
onMount(() => {
modal = new Modal(modalRef);
carousel = new Carousel(carouselRef, { interval: false, ride: false });
/* Bootstrap's Carousel.Event interface doesn't seem to work as a type here */
carouselRef.addEventListener("slide.bs.carousel", (e: any) => {
activeIndex = e.to;
});
dispatch("mount", { modal: modal, carousel: carousel });
modals.set(modalKey, modal);
});
let activeIndex = startIndex;
</script>
<Badge on:click={() => modal.show()}>
{@html infoCircle}
</Badge>
<div
bind:this={modalRef}
class="modal fade"
tabindex="-1"
aria-labelledby="modalLabel"
aria-hidden="true"
>
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title" id="modalLabel">
{title}
</h1>
<button
type="button"
class="btn-close"
class:invert={$pageTheme.isDark}
data-bs-dismiss="modal"
aria-label="Close"
/>
{#if url}
<div class="chapter-redirect">
{@html tr.helpViewChapterInManual({
chapter: `<a href="${url}" title="${tr.helpOpenManualChapter(
{ name: title },
)}">${tr.helpChapter()}</a>`,
})}
</div>
{/if}
</div>
<div class="modal-body">
<Row --cols={4}>
<Col --col-size={1}>
<nav>
<div id="nav">
<ul>
{#each helpSections as section, i}
<li
on:click={() => {
activeIndex = i;
carousel.to(activeIndex);
}}
>
<span class:active={i == activeIndex}>
{section.title}
</span>
</li>
{/each}
</ul>
</div>
</nav>
</Col>
<Col --col-size={3}>
<div
id="helpSectionIndicators"
class="carousel slide"
bind:this={carouselRef}
>
<div class="carousel-inner">
{#each helpSections as section, i}
<div
class="carousel-item"
class:active={i == startIndex}
>
<HelpSection {section} />
</div>
{/each}
</div>
</div>
</Col>
</Row>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" on:click={onOkClicked}
>OK</button
>
</div>
</div>
</div>
</div>
<style lang="scss">
.modal-header {
align-items: unset;
flex-wrap: wrap;
}
.chapter-redirect {
width: 100%;
color: var(--fg-subtle);
font-size: small;
}
#nav {
margin-bottom: 1.5rem;
}
.modal-content {
padding-top: 0.5rem;
background-color: var(--canvas);
color: var(--fg);
border-radius: var(--border-radius-large, 10px);
}
.invert {
filter: invert(1) grayscale(100%) brightness(200%);
}
ul {
list-style-type: none;
margin: 0;
padding: 0;
}
li span {
display: block;
padding: 0.5rem 0.75rem;
text-decoration: none;
cursor: pointer;
&:hover {
background-color: var(--canvas-inset);
}
&.active {
border-left: 4px solid var(--border-focus);
}
}
</style>

View File

@ -0,0 +1,70 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import { marked } from "marked";
import Row from "../components/Row.svelte";
import * as tr from "../lib/ftl";
import type { DeckOption } from "./types";
export let section: DeckOption;
</script>
<Row>
<h2>
{#if section.url}
<a
href={section.url}
title={tr.helpOpenManualChapter({ name: section.title })}
>
{@html section.title}
</a>
{:else}
{@html section.title}
{/if}
</h2>
{#if section.help}
{@html marked(section.help)}
{:else}
{@html marked(
tr.helpNoExplanation({
link: "[GitHub](https://github.com/ankitects/anki)",
}),
)}
{/if}
</Row>
{#if section.url}
<hr />
<div class="chapter-redirect">
{@html marked(
tr.helpForMoreInfo({
link: `<a href="${section.url}" title="${tr.helpOpenManualChapter({
name: section.title,
})}">${section.title}</a>`,
}),
)}
</div>
{/if}
<style lang="scss">
h2 {
margin-bottom: 1em;
a {
cursor: pointer;
border-bottom: 1px solid var(--border);
text-decoration: none;
color: var(--fg);
&:hover {
border-color: var(--fg);
}
}
}
.chapter-redirect {
width: 100%;
color: var(--fg-subtle);
font-size: small;
}
</style>

View File

@ -3,14 +3,20 @@ Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import type Carousel from "bootstrap/js/dist/carousel";
import type Modal from "bootstrap/js/dist/modal";
import DynamicallySlottable from "../components/DynamicallySlottable.svelte";
import Item from "../components/Item.svelte";
import * as tr from "../lib/ftl";
import EnumSelectorRow from "./EnumSelectorRow.svelte";
import HelpModal from "./HelpModal.svelte";
import type { DeckOptionsState } from "./lib";
import SettingTitle from "./SettingTitle.svelte";
import SpinBoxRow from "./SpinBoxRow.svelte";
import StepsInputRow from "./StepsInputRow.svelte";
import TitledContainer from "./TitledContainer.svelte";
import type { DeckOption } from "./types";
import Warning from "./Warning.svelte";
export let state: DeckOptionsState;
@ -31,17 +37,62 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
}
const leechChoices = [tr.actionsSuspendCard(), tr.schedulingTagOnly()];
const settings = {
relearningSteps: {
title: tr.deckConfigRelearningSteps(),
help: tr.deckConfigRelearningStepsTooltip(),
url: "https://docs.ankiweb.net/deck-options.html#relearning-steps",
},
minimumInterval: {
title: tr.schedulingMinimumInterval(),
help: tr.deckConfigMinimumIntervalTooltip(),
url: "https://docs.ankiweb.net/deck-options.html#minimum-interval",
},
leechThreshold: {
title: tr.schedulingLeechThreshold(),
help: tr.deckConfigLeechThresholdTooltip(),
url: "https://docs.ankiweb.net/leeches.html#leeches",
},
leechAction: {
title: tr.schedulingLeechAction(),
help: tr.deckConfigLeechActionTooltip(),
url: "https://docs.ankiweb.net/leeches.html#waiting",
},
};
const helpSections = Object.values(settings) as DeckOption[];
let modal: Modal;
let carousel: Carousel;
function openHelpModal(index: number): void {
modal.show();
carousel.to(index);
}
</script>
<TitledContainer title={tr.schedulingLapses()}>
<HelpModal
title={tr.schedulingLapses()}
url="https://docs.ankiweb.net/deck-options.html#lapses"
slot="tooltip"
{helpSections}
on:mount={(e) => {
modal = e.detail.modal;
carousel = e.detail.carousel;
}}
/>
<DynamicallySlottable slotHost={Item} {api}>
<Item>
<StepsInputRow
bind:value={$config.relearnSteps}
defaultValue={defaults.relearnSteps}
markdownTooltip={tr.deckConfigRelearningStepsTooltip()}
>
{tr.deckConfigRelearningSteps()}
<SettingTitle
on:click={() =>
openHelpModal(Object.keys(settings).indexOf("relearningSteps"))}
>{settings.relearningSteps.title}</SettingTitle
>
</StepsInputRow>
</Item>
@ -50,9 +101,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
bind:value={$config.minimumLapseInterval}
defaultValue={defaults.minimumLapseInterval}
min={1}
markdownTooltip={tr.deckConfigMinimumIntervalTooltip()}
>
{tr.schedulingMinimumInterval()}
<SettingTitle
on:click={() =>
openHelpModal(Object.keys(settings).indexOf("minimumInterval"))}
>{settings.minimumInterval.title}</SettingTitle
>
</SpinBoxRow>
</Item>
@ -65,9 +119,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
bind:value={$config.leechThreshold}
defaultValue={defaults.leechThreshold}
min={1}
markdownTooltip={tr.deckConfigLeechThresholdTooltip()}
>
{tr.schedulingLeechThreshold()}
<SettingTitle
on:click={() =>
openHelpModal(Object.keys(settings).indexOf("leechThreshold"))}
>{settings.leechThreshold.title}</SettingTitle
>
</SpinBoxRow>
</Item>
@ -76,10 +133,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
bind:value={$config.leechAction}
defaultValue={defaults.leechAction}
choices={leechChoices}
breakpoint="sm"
markdownTooltip={tr.deckConfigLeechActionTooltip()}
breakpoint="md"
>
<SettingTitle
on:click={() =>
openHelpModal(Object.keys(settings).indexOf("leechAction"))}
>{settings.leechAction.title}</SettingTitle
>
{tr.schedulingLeechAction()}
</EnumSelectorRow>
</Item>
</DynamicallySlottable>

View File

@ -3,15 +3,21 @@ Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import type Carousel from "bootstrap/js/dist/carousel";
import type Modal from "bootstrap/js/dist/modal";
import DynamicallySlottable from "../components/DynamicallySlottable.svelte";
import Item from "../components/Item.svelte";
import * as tr from "../lib/ftl";
import { DeckConfig } from "../lib/proto";
import EnumSelectorRow from "./EnumSelectorRow.svelte";
import HelpModal from "./HelpModal.svelte";
import type { DeckOptionsState } from "./lib";
import SettingTitle from "./SettingTitle.svelte";
import SpinBoxRow from "./SpinBoxRow.svelte";
import StepsInputRow from "./StepsInputRow.svelte";
import TitledContainer from "./TitledContainer.svelte";
import type { DeckOption } from "./types";
import Warning from "./Warning.svelte";
export let state: DeckOptionsState;
@ -47,17 +53,62 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
DeckConfig.DeckConfig.Config.NewCardInsertOrder.NEW_CARD_INSERT_ORDER_RANDOM
? tr.deckConfigNewInsertionOrderRandomWithV3()
: "";
const settings = {
learningSteps: {
title: tr.deckConfigLearningSteps(),
help: tr.deckConfigLearningStepsTooltip(),
url: "https://docs.ankiweb.net/deck-options.html#learning-steps",
},
graduatingInterval: {
title: tr.schedulingGraduatingInterval(),
help: tr.deckConfigGraduatingIntervalTooltip(),
url: "https://docs.ankiweb.net/deck-options.html#graduating-interval",
},
easyInterval: {
title: tr.schedulingEasyInterval(),
help: tr.deckConfigEasyIntervalTooltip(),
url: "https://docs.ankiweb.net/deck-options.html#easy-interval",
},
insertionOrder: {
title: tr.deckConfigNewInsertionOrder(),
help: tr.deckConfigNewInsertionOrderTooltip(),
url: "https://docs.ankiweb.net/deck-options.html#insertion-order",
},
};
const helpSections = Object.values(settings) as DeckOption[];
let modal: Modal;
let carousel: Carousel;
function openHelpModal(index: number): void {
modal.show();
carousel.to(index);
}
</script>
<TitledContainer title={tr.schedulingNewCards()}>
<HelpModal
title={tr.schedulingNewCards()}
url="https://docs.ankiweb.net/deck-options.html#new-cards"
slot="tooltip"
{helpSections}
on:mount={(e) => {
modal = e.detail.modal;
carousel = e.detail.carousel;
}}
/>
<DynamicallySlottable slotHost={Item} {api}>
<Item>
<StepsInputRow
bind:value={$config.learnSteps}
defaultValue={defaults.learnSteps}
markdownTooltip={tr.deckConfigLearningStepsTooltip()}
>
{tr.deckConfigLearningSteps()}
<SettingTitle
on:click={() =>
openHelpModal(Object.keys(settings).indexOf("learningSteps"))}
>{settings.learningSteps.title}</SettingTitle
>
</StepsInputRow>
</Item>
@ -65,9 +116,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<SpinBoxRow
bind:value={$config.graduatingIntervalGood}
defaultValue={defaults.graduatingIntervalGood}
markdownTooltip={tr.deckConfigGraduatingIntervalTooltip()}
>
{tr.schedulingGraduatingInterval()}
<SettingTitle
on:click={() =>
openHelpModal(
Object.keys(settings).indexOf("graduatingInterval"),
)}>{settings.graduatingInterval.title}</SettingTitle
>
</SpinBoxRow>
</Item>
@ -79,9 +134,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<SpinBoxRow
bind:value={$config.graduatingIntervalEasy}
defaultValue={defaults.graduatingIntervalEasy}
markdownTooltip={tr.deckConfigEasyIntervalTooltip()}
>
{tr.schedulingEasyInterval()}
<SettingTitle
on:click={() =>
openHelpModal(Object.keys(settings).indexOf("easyInterval"))}
>{settings.easyInterval.title}</SettingTitle
>
</SpinBoxRow>
</Item>
@ -95,9 +153,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
defaultValue={defaults.newCardInsertOrder}
choices={newInsertOrderChoices}
breakpoint={"md"}
markdownTooltip={tr.deckConfigNewInsertionOrderTooltip()}
>
{tr.deckConfigNewInsertionOrder()}
<SettingTitle
on:click={() =>
openHelpModal(Object.keys(settings).indexOf("insertionOrder"))}
>{settings.insertionOrder.title}</SettingTitle
>
</EnumSelectorRow>
</Item>

View File

@ -53,6 +53,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
>
<div class:hide={!modified} use:asReference>
<Badge
iconSize={85}
class="p-1"
on:click={() => {
if (modified) {

View File

@ -0,0 +1,17 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<div class="setting-title" on:click>
<slot />
</div>
<style lang="scss">
.setting-title {
cursor: help;
&:hover {
text-decoration: underline;
text-decoration-style: dashed;
}
}
</style>

View File

@ -6,22 +6,23 @@
import Col from "../components/Col.svelte";
import Row from "../components/Row.svelte";
import SpinBox from "../components/SpinBox.svelte";
import ConfigInput from "./ConfigInput.svelte";
import RevertButton from "./RevertButton.svelte";
import TooltipLabel from "./TooltipLabel.svelte";
export let value: any;
export let defaultValue: any;
export let min = 0;
export let max: number | undefined = undefined;
export let markdownTooltip: string;
</script>
<Row --cols={12}>
<Col --col-size={7} breakpoint="sm">
<TooltipLabel {markdownTooltip}><slot /></TooltipLabel>
<Row --cols={13}>
<Col --col-size={7} breakpoint="xs">
<slot />
</Col>
<Col --col-size={5} breakpoint="sm">
<Col --col-size={6} breakpoint="xs">
<ConfigInput>
<SpinBox bind:value {min} {max} step={0.01} />
<RevertButton bind:value {defaultValue} />
<RevertButton slot="revert" bind:value {defaultValue} />
</ConfigInput>
</Col>
</Row>

View File

@ -6,22 +6,26 @@
import Col from "../components/Col.svelte";
import Row from "../components/Row.svelte";
import SpinBox from "../components/SpinBox.svelte";
import ConfigInput from "./ConfigInput.svelte";
import RevertButton from "./RevertButton.svelte";
import TooltipLabel from "./TooltipLabel.svelte";
export let value: any;
export let defaultValue: any;
export let min = 0;
export let max: number | undefined = undefined;
export let markdownTooltip: string;
</script>
<Row --cols={12}>
<Col --col-size={7} breakpoint="sm">
<TooltipLabel {markdownTooltip}><slot /></TooltipLabel>
<Row --cols={13}>
<Col --col-size={7} breakpoint="xs">
<slot />
</Col>
<Col --col-size={5} breakpoint="sm">
<Col --col-size={6} breakpoint="xs">
<Row class="flex-grow-1">
<slot name="tabs" />
<ConfigInput>
<SpinBox bind:value {min} {max} />
<RevertButton bind:value {defaultValue} />
<RevertButton slot="revert" bind:value {defaultValue} />
</ConfigInput>
</Row>
</Col>
</Row>

View File

@ -5,21 +5,22 @@
<script lang="ts">
import Col from "../components/Col.svelte";
import Row from "../components/Row.svelte";
import ConfigInput from "./ConfigInput.svelte";
import RevertButton from "./RevertButton.svelte";
import StepsInput from "./StepsInput.svelte";
import TooltipLabel from "./TooltipLabel.svelte";
export let value: any;
export let defaultValue: any;
export let markdownTooltip: string;
</script>
<Row --cols={12}>
<Col --col-size={7} breakpoint="sm">
<TooltipLabel {markdownTooltip}><slot /></TooltipLabel>
<Row --cols={13}>
<Col --col-size={7} breakpoint="xs">
<slot />
</Col>
<Col --col-size={5} breakpoint="sm">
<Col --col-size={6} breakpoint="xs">
<ConfigInput>
<StepsInput bind:value />
<RevertButton bind:value {defaultValue} />
<RevertButton slot="revert" bind:value {defaultValue} />
</ConfigInput>
</Col>
</Row>

View File

@ -6,25 +6,22 @@
import Col from "../components/Col.svelte";
import Row from "../components/Row.svelte";
import Switch from "../components/Switch.svelte";
import ConfigInput from "./ConfigInput.svelte";
import Label from "./Label.svelte";
import RevertButton from "./RevertButton.svelte";
import TooltipLabel from "./TooltipLabel.svelte";
export let value: boolean;
export let defaultValue: boolean;
export let markdownTooltip: string | undefined = undefined;
const id = Math.random().toString(36).substring(2);
</script>
<Row --cols={6}>
<Col --col-size={4}
>{#if markdownTooltip}<TooltipLabel for={id} {markdownTooltip}
><slot /></TooltipLabel
>{:else}<Label for={id}><slot /></Label>{/if}</Col
>
<Col --col-size={4}><Label for={id}><slot /></Label></Col>
<Col --col-justify="flex-end">
<ConfigInput grow={false}>
<Switch {id} bind:value />
<RevertButton bind:value {defaultValue} />
<RevertButton slot="revert" bind:value {defaultValue} />
</ConfigInput>
</Col>
</Row>

View File

@ -44,7 +44,7 @@
<ul>
{#each tabs as tab, idx}
<li class={activeTab === idx ? "active" : ""}>
<li class:active={activeTab === idx}>
<span on:click={handleClick(idx)}>{tab.title}</span>
</li>
{/each}
@ -52,31 +52,27 @@
<style lang="scss">
ul {
width: 100%;
display: flex;
flex-wrap: wrap;
flex-wrap: nowrap;
justify-content: space-between;
padding-left: 0;
margin-top: 1rem;
margin-bottom: 0.5rem;
margin-bottom: 0.25rem;
list-style: none;
border-bottom: 1px solid var(--border);
}
span {
border: 1px solid transparent;
border-top-left-radius: 0.25rem;
border-top-right-radius: 0.25rem;
display: block;
padding: 0.25rem 1rem;
white-space: nowrap;
cursor: pointer;
margin: 0 8px -1px 0;
color: var(--fg-subtle);
}
li.active > span {
border-color: var(--border) var(--border) var(--canvas);
color: var(--fg);
border-bottom: 4px solid var(--border-focus);
margin-bottom: -2px;
}
span:hover {
color: var(--fg);
}

View File

@ -3,13 +3,19 @@ Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import type Carousel from "bootstrap/js/dist/carousel";
import type Modal from "bootstrap/js/dist/modal";
import DynamicallySlottable from "../components/DynamicallySlottable.svelte";
import Item from "../components/Item.svelte";
import * as tr from "../lib/ftl";
import HelpModal from "./HelpModal.svelte";
import type { DeckOptionsState } from "./lib";
import SettingTitle from "./SettingTitle.svelte";
import SpinBoxRow from "./SpinBoxRow.svelte";
import SwitchRow from "./SwitchRow.svelte";
import TitledContainer from "./TitledContainer.svelte";
import type { DeckOption } from "./types";
import Warning from "./Warning.svelte";
export let state: DeckOptionsState;
@ -22,9 +28,39 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
$config.capAnswerTimeToSecs > 600
? tr.deckConfigMaximumAnswerSecsAboveRecommended()
: "";
const settings = {
maximumAnswerSecs: {
title: tr.deckConfigMaximumAnswerSecs(),
help: tr.deckConfigMaximumAnswerSecsTooltip(),
},
showAnswerTimer: {
title: tr.schedulingShowAnswerTimer(),
help: tr.deckConfigShowAnswerTimerTooltip(),
},
};
const helpSections = Object.values(settings) as DeckOption[];
let modal: Modal;
let carousel: Carousel;
function openHelpModal(index: number): void {
modal.show();
carousel.to(index);
}
</script>
<TitledContainer title={tr.deckConfigTimerTitle()}>
<HelpModal
title={tr.deckConfigTimerTitle()}
url="https://docs.ankiweb.net/deck-options.html#timer"
slot="tooltip"
{helpSections}
on:mount={(e) => {
modal = e.detail.modal;
carousel = e.detail.carousel;
}}
/>
<DynamicallySlottable slotHost={Item} {api}>
<Item>
<SpinBoxRow
@ -32,9 +68,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
defaultValue={defaults.capAnswerTimeToSecs}
min={1}
max={7200}
markdownTooltip={tr.deckConfigMaximumAnswerSecsTooltip()}
>
{tr.deckConfigMaximumAnswerSecs()}
<SettingTitle
on:click={() =>
openHelpModal(
Object.keys(settings).indexOf("maximumAnswerSecs"),
)}>{settings.maximumAnswerSecs.title}</SettingTitle
>
</SpinBoxRow>
</Item>
@ -48,9 +88,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<SwitchRow
bind:value={$config.showTimer}
defaultValue={defaults.showTimer}
markdownTooltip={tr.deckConfigShowAnswerTimerTooltip()}
>
{tr.schedulingShowAnswerTimer()}
<SettingTitle
on:click={() =>
openHelpModal(
Object.keys(settings).indexOf("showAnswerTimer"),
)}>{settings.showAnswerTimer.title}</SettingTitle
>
</SwitchRow>
</div>
</Item>

View File

@ -3,19 +3,56 @@ Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import Container from "../components/Container.svelte";
import { pageTheme } from "../sveltelib/theme";
export let title: string;
</script>
<Container --gutter-block="2px" --container-margin="0">
<div
class="container"
class:light={!$pageTheme.isDark}
class:dark={$pageTheme.isDark}
style:--gutter-block="2px"
style:--container-margin="0"
>
<div class="position-relative">
<h1>{title}</h1>
<div class="help-badge position-absolute"><slot name="tooltip" /></div>
</div>
<slot />
</Container>
</div>
<style lang="scss">
@use "sass/elevation" as *;
.container {
width: 100%;
border-radius: var(--border-radius-large, 10px);
padding: 1rem 1.75rem 0.75rem 1.25rem;
border: var(--border-subtle);
&:hover,
&:focus-within {
.help-badge {
color: var(--fg-subtle);
}
}
&.light {
@include elevation(2);
}
&.dark {
@include elevation(3);
}
}
h1 {
border-bottom: 1px solid var(--border);
}
.help-badge {
right: 0;
bottom: 4px;
color: var(--fg-faint);
transition: color 0.2s linear;
&:hover {
transition: none;
color: var(--fg);
}
}
</style>

View File

@ -1,39 +0,0 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import { marked } from "marked";
import Badge from "../components/Badge.svelte";
import WithTooltip from "../components/WithTooltip.svelte";
import { infoCircle } from "./icons";
import Label from "./Label.svelte";
export let markdownTooltip: string;
let forId: string | undefined = undefined;
export { forId as for };
</script>
<span>
{#if forId}
<Label for={forId}><slot /></Label>
{:else}
<slot />
{/if}
<WithTooltip
tooltip={marked(markdownTooltip)}
showDelay={250}
offset={[0, 20]}
placement="bottom"
let:createTooltip
>
<Badge
class="opacity-50"
iconSize={85}
on:mount={(event) => createTooltip(event.detail.span)}
>
{@html infoCircle}
</Badge>
</WithTooltip>
</span>

View File

@ -1,10 +1,14 @@
@import "sass/base";
// override Bootstrap transition duration
$carousel-transition: 0.2s;
@import "sass/bootstrap/scss/dropdown";
@import "sass/bootstrap/scss/buttons";
@import "sass/bootstrap/scss/button-group";
@import "sass/bootstrap/scss/transitions";
@import "sass/bootstrap/scss/modal";
@import "sass/bootstrap/scss/carousel";
@import "sass/bootstrap/scss/close";
@import "sass/bootstrap/scss/alert";
@import "sass/bootstrap/scss/badge";

View File

@ -17,6 +17,7 @@ import { DeckOptionsState } from "./lib";
const i18n = setupI18n({
modules: [
ModuleName.HELP,
ModuleName.SCHEDULING,
ModuleName.ACTIONS,
ModuleName.DECK_CONFIG,

8
ts/deck-options/types.ts Normal file
View File

@ -0,0 +1,8 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
export type DeckOption = {
title: string;
help?: string;
url?: string;
};