mirror of
https://github.com/ankitects/anki.git
synced 2025-12-10 21:36:55 -05:00
more experimental updates to deck config screen
- try out bootstrap modals - they're not perfect, but let's see how they go for now. Won't be hard to switch to bridge commands if required. - handle adding/renaming/removing - add a class to manage the state
This commit is contained in:
parent
a6ed8e90ce
commit
c3fc07ac20
15 changed files with 442 additions and 109 deletions
|
|
@ -5,10 +5,11 @@ load("//ts/svelte:svelte.bzl", "compile_svelte", "svelte", "svelte_check")
|
||||||
load("//ts:esbuild.bzl", "esbuild")
|
load("//ts:esbuild.bzl", "esbuild")
|
||||||
load("//ts:vendor.bzl", "copy_bootstrap_icons")
|
load("//ts:vendor.bzl", "copy_bootstrap_icons")
|
||||||
load("//ts:compile_sass.bzl", "compile_sass")
|
load("//ts:compile_sass.bzl", "compile_sass")
|
||||||
|
load("@npm//jest-cli:index.bzl", "jest_test")
|
||||||
|
|
||||||
compile_sass(
|
compile_sass(
|
||||||
group = "base_css",
|
|
||||||
srcs = ["deckconfig-base.scss"],
|
srcs = ["deckconfig-base.scss"],
|
||||||
|
group = "base_css",
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = [
|
deps = [
|
||||||
"//ts/sass:base_lib",
|
"//ts/sass:base_lib",
|
||||||
|
|
@ -24,6 +25,9 @@ svelte_names = [f.replace(".svelte", "") for f in svelte_files]
|
||||||
compile_svelte(
|
compile_svelte(
|
||||||
name = "svelte",
|
name = "svelte",
|
||||||
srcs = svelte_files,
|
srcs = svelte_files,
|
||||||
|
deps = [
|
||||||
|
"@npm//@types/bootstrap",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
copy_bootstrap_icons(
|
copy_bootstrap_icons(
|
||||||
|
|
@ -50,12 +54,16 @@ ts_library(
|
||||||
"icons.ts",
|
"icons.ts",
|
||||||
"lib.ts",
|
"lib.ts",
|
||||||
"steps.ts",
|
"steps.ts",
|
||||||
|
"textInputModal.ts",
|
||||||
],
|
],
|
||||||
module_name = "deckconfig",
|
module_name = "deckconfig",
|
||||||
deps = [
|
deps = [
|
||||||
|
"TextInputModal",
|
||||||
"//ts:image_module_support",
|
"//ts:image_module_support",
|
||||||
"//ts/lib",
|
"//ts/lib",
|
||||||
"//ts/lib:backend_proto",
|
"//ts/lib:backend_proto",
|
||||||
|
"@npm//lodash-es",
|
||||||
|
"@npm//svelte",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -112,5 +120,40 @@ svelte_check(
|
||||||
srcs = glob([
|
srcs = glob([
|
||||||
"*.ts",
|
"*.ts",
|
||||||
"*.svelte",
|
"*.svelte",
|
||||||
]),
|
]) + [
|
||||||
|
"@npm//@types/bootstrap",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
ts_library(
|
||||||
|
name = "test_lib",
|
||||||
|
srcs = glob(["*.test.ts"]),
|
||||||
|
tsconfig = "//ts:tsconfig.json",
|
||||||
|
deps = [
|
||||||
|
":lib",
|
||||||
|
"//ts/lib:backend_proto",
|
||||||
|
"@npm//@types/jest",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
jest_test(
|
||||||
|
name = "test",
|
||||||
|
args = [
|
||||||
|
"--no-cache",
|
||||||
|
"--no-watchman",
|
||||||
|
"--ci",
|
||||||
|
"--colors",
|
||||||
|
"--config",
|
||||||
|
"$(location //ts:jest.config.js)",
|
||||||
|
],
|
||||||
|
data = [
|
||||||
|
":test_lib",
|
||||||
|
"//ts:jest.config.js",
|
||||||
|
"@npm//protobufjs",
|
||||||
|
],
|
||||||
|
target_compatible_with = select({
|
||||||
|
"@platforms//os:osx": [],
|
||||||
|
"@platforms//os:linux": [],
|
||||||
|
"//conditions:default": ["@platforms//os:linux"],
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,13 @@ Copyright: Ankitects Pty Ltd and contributors
|
||||||
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type pb from "anki/backend_proto";
|
|
||||||
import NewOptions from "./NewOptions.svelte";
|
import NewOptions from "./NewOptions.svelte";
|
||||||
import ReviewOptions from "./ReviewOptions.svelte";
|
import ReviewOptions from "./ReviewOptions.svelte";
|
||||||
import LapseOptions from "./LapseOptions.svelte";
|
import LapseOptions from "./LapseOptions.svelte";
|
||||||
import GeneralOptions from "./GeneralOptions.svelte";
|
import GeneralOptions from "./GeneralOptions.svelte";
|
||||||
|
import type { DeckConfigState } from "./lib";
|
||||||
|
|
||||||
export let config: pb.BackendProto.DeckConfig.Config;
|
export let state: DeckConfigState;
|
||||||
export let defaults: pb.BackendProto.DeckConfig.Config;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
@ -24,8 +23,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<NewOptions bind:config {defaults} />
|
<NewOptions {state} />
|
||||||
<ReviewOptions bind:config {defaults} />
|
<ReviewOptions {state} />
|
||||||
<LapseOptions bind:config {defaults} />
|
<LapseOptions {state} />
|
||||||
<GeneralOptions bind:config {defaults} />
|
<GeneralOptions {state} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,19 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import * as tr from "anki/i18n";
|
import * as tr from "anki/i18n";
|
||||||
import type { DeckConfigId, ConfigWithCount } from "./lib";
|
import type { DeckConfigState, ConfigListEntry } from "./lib";
|
||||||
import OptionsDropdown from "./OptionsDropdown.svelte";
|
import OptionsDropdown from "./OptionsDropdown.svelte";
|
||||||
|
|
||||||
export let allConfig: ConfigWithCount[];
|
export let state: DeckConfigState;
|
||||||
export let selectedConfigId: DeckConfigId;
|
let configList = state.configList;
|
||||||
|
|
||||||
function configLabel(config: ConfigWithCount): string {
|
function configLabel(entry: ConfigListEntry): string {
|
||||||
const name = config.config.name;
|
const count = tr.deckConfigUsedByDecks({ decks: entry.useCount });
|
||||||
const count = tr.deckConfigUsedByDecks({ decks: config.useCount });
|
return `${entry.name} (${count})`;
|
||||||
return `${name} (${count})`;
|
}
|
||||||
|
|
||||||
|
function myblur(this: HTMLSelectElement) {
|
||||||
|
state.setCurrentIndex(parseInt(this.value));
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -47,12 +50,15 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
<div class="outer">
|
<div class="outer">
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<select bind:value={selectedConfigId} class="form-select">
|
<!-- svelte-ignore a11y-no-onchange -->
|
||||||
{#each allConfig as config}
|
<select class="form-select" on:change={myblur}>
|
||||||
<option value={config.config.id}>{configLabel(config)}</option>
|
{#each $configList as entry}
|
||||||
|
<option value={entry.idx} selected={entry.current}>
|
||||||
|
{configLabel(entry)}
|
||||||
|
</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<OptionsDropdown />
|
<OptionsDropdown {state} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -3,24 +3,12 @@ Copyright: Ankitects Pty Ltd and contributors
|
||||||
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type pb from "anki/backend_proto";
|
|
||||||
import ConfigSelector from "./ConfigSelector.svelte";
|
import ConfigSelector from "./ConfigSelector.svelte";
|
||||||
import ConfigEditor from "./ConfigEditor.svelte";
|
import ConfigEditor from "./ConfigEditor.svelte";
|
||||||
import * as tr from "anki/i18n";
|
import * as tr from "anki/i18n";
|
||||||
import type { DeckConfigState } from "./lib";
|
import type { DeckConfigState } from "./lib";
|
||||||
|
|
||||||
export let state: DeckConfigState;
|
export let state: DeckConfigState;
|
||||||
|
|
||||||
let selectedConfigId = state.selectedConfigId;
|
|
||||||
|
|
||||||
let selectedConfig: pb.BackendProto.DeckConfig.Config;
|
|
||||||
$: {
|
|
||||||
selectedConfig = (
|
|
||||||
state.allConfigs.find((e) => e.config.id == selectedConfigId)?.config ??
|
|
||||||
state.allConfigs[0].config
|
|
||||||
).config as pb.BackendProto.DeckConfig.Config;
|
|
||||||
}
|
|
||||||
let defaults = state.defaults;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
@ -38,12 +26,16 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<div id="modal">
|
||||||
|
<!-- filled in later-->
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="outer">
|
<div class="outer">
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<div><b>{tr.actionsOptionsFor({ val: state.deckName })}</b></div>
|
<div><b>{tr.actionsOptionsFor({ val: state.currentDeck.name })}</b></div>
|
||||||
|
|
||||||
<ConfigSelector allConfig={state.allConfigs} bind:selectedConfigId />
|
<ConfigSelector {state} />
|
||||||
|
|
||||||
<ConfigEditor config={selectedConfig} {defaults} />
|
<ConfigEditor {state} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,14 @@ Copyright: Ankitects Pty Ltd and contributors
|
||||||
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type pb from "anki/backend_proto";
|
|
||||||
import * as tr from "anki/i18n";
|
import * as tr from "anki/i18n";
|
||||||
import SpinBox from "./SpinBox.svelte";
|
import SpinBox from "./SpinBox.svelte";
|
||||||
import CheckBox from "./CheckBox.svelte";
|
import CheckBox from "./CheckBox.svelte";
|
||||||
|
import type { DeckConfigState } from "./lib";
|
||||||
|
|
||||||
export let config: pb.BackendProto.DeckConfig.Config;
|
export let state: DeckConfigState;
|
||||||
export let defaults: pb.BackendProto.DeckConfig.Config;
|
let config = state.currentConfig;
|
||||||
|
let defaults = state.defaults;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -21,23 +22,23 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
min={30}
|
min={30}
|
||||||
max={600}
|
max={600}
|
||||||
defaultValue={defaults.capAnswerTimeToSecs}
|
defaultValue={defaults.capAnswerTimeToSecs}
|
||||||
bind:value={config.capAnswerTimeToSecs} />
|
bind:value={$config.capAnswerTimeToSecs} />
|
||||||
|
|
||||||
<CheckBox
|
<CheckBox
|
||||||
label="Answer timer"
|
label="Answer timer"
|
||||||
subLabel={tr.schedulingShowAnswerTimer()}
|
subLabel={tr.schedulingShowAnswerTimer()}
|
||||||
defaultValue={defaults.showTimer}
|
defaultValue={defaults.showTimer}
|
||||||
bind:value={config.showTimer} />
|
bind:value={$config.showTimer} />
|
||||||
|
|
||||||
<CheckBox
|
<CheckBox
|
||||||
label="Autoplay"
|
label="Autoplay"
|
||||||
subLabel="Don't play audio automatically"
|
subLabel="Don't play audio automatically"
|
||||||
defaultValue={defaults.disableAutoplay}
|
defaultValue={defaults.disableAutoplay}
|
||||||
bind:value={config.disableAutoplay} />
|
bind:value={$config.disableAutoplay} />
|
||||||
|
|
||||||
<CheckBox
|
<CheckBox
|
||||||
label="Question Audio"
|
label="Question Audio"
|
||||||
subLabel={tr.schedulingAlwaysIncludeQuestionSideWhenReplaying()}
|
subLabel={tr.schedulingAlwaysIncludeQuestionSideWhenReplaying()}
|
||||||
defaultValue={defaults.skipQuestionWhenReplayingAnswer}
|
defaultValue={defaults.skipQuestionWhenReplayingAnswer}
|
||||||
bind:value={config.skipQuestionWhenReplayingAnswer} />
|
bind:value={$config.skipQuestionWhenReplayingAnswer} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,16 @@ Copyright: Ankitects Pty Ltd and contributors
|
||||||
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type pb from "anki/backend_proto";
|
|
||||||
import * as tr from "anki/i18n";
|
import * as tr from "anki/i18n";
|
||||||
import SpinBox from "./SpinBox.svelte";
|
import SpinBox from "./SpinBox.svelte";
|
||||||
import SpinBoxFloat from "./SpinBoxFloat.svelte";
|
import SpinBoxFloat from "./SpinBoxFloat.svelte";
|
||||||
import StepsInput from "./StepsInput.svelte";
|
import StepsInput from "./StepsInput.svelte";
|
||||||
import EnumSelector from "./EnumSelector.svelte";
|
import EnumSelector from "./EnumSelector.svelte";
|
||||||
|
import type { DeckConfigState } from "./lib";
|
||||||
|
|
||||||
export let config: pb.BackendProto.DeckConfig.Config;
|
export let state: DeckConfigState;
|
||||||
export let defaults: pb.BackendProto.DeckConfig.Config;
|
let config = state.currentConfig;
|
||||||
|
let defaults = state.defaults;
|
||||||
|
|
||||||
const leechChoices = [tr.actionsSuspendCard(), tr.schedulingTagOnly()];
|
const leechChoices = [tr.actionsSuspendCard(), tr.schedulingTagOnly()];
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -23,8 +24,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
label="Relearning steps"
|
label="Relearning steps"
|
||||||
subLabel="Relearning steps, separated by spaces."
|
subLabel="Relearning steps, separated by spaces."
|
||||||
defaultValue={defaults.relearnSteps}
|
defaultValue={defaults.relearnSteps}
|
||||||
value={config.relearnSteps}
|
value={$config.relearnSteps}
|
||||||
on:changed={(evt) => (config.relearnSteps = evt.detail.value)} />
|
on:changed={(evt) => ($config.relearnSteps = evt.detail.value)} />
|
||||||
|
|
||||||
<SpinBoxFloat
|
<SpinBoxFloat
|
||||||
label={tr.schedulingNewInterval()}
|
label={tr.schedulingNewInterval()}
|
||||||
|
|
@ -32,27 +33,27 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
min={0}
|
min={0}
|
||||||
max={1}
|
max={1}
|
||||||
defaultValue={defaults.lapseMultiplier}
|
defaultValue={defaults.lapseMultiplier}
|
||||||
value={config.lapseMultiplier}
|
value={$config.lapseMultiplier}
|
||||||
on:changed={(evt) => (config.lapseMultiplier = evt.detail.value)} />
|
on:changed={(evt) => ($config.lapseMultiplier = evt.detail.value)} />
|
||||||
|
|
||||||
<SpinBox
|
<SpinBox
|
||||||
label={tr.schedulingMinimumInterval()}
|
label={tr.schedulingMinimumInterval()}
|
||||||
subLabel="The minimum new interval a lapsed card will be given after relearning."
|
subLabel="The minimum new interval a lapsed card will be given after relearning."
|
||||||
min={1}
|
min={1}
|
||||||
defaultValue={defaults.minimumLapseInterval}
|
defaultValue={defaults.minimumLapseInterval}
|
||||||
bind:value={config.minimumLapseInterval} />
|
bind:value={$config.minimumLapseInterval} />
|
||||||
|
|
||||||
<SpinBox
|
<SpinBox
|
||||||
label={tr.schedulingLeechThreshold()}
|
label={tr.schedulingLeechThreshold()}
|
||||||
subLabel="Number of times Again needs to be pressed on a review card to make it a leech."
|
subLabel="Number of times Again needs to be pressed on a review card to make it a leech."
|
||||||
min={1}
|
min={1}
|
||||||
defaultValue={defaults.leechThreshold}
|
defaultValue={defaults.leechThreshold}
|
||||||
bind:value={config.leechThreshold} />
|
bind:value={$config.leechThreshold} />
|
||||||
|
|
||||||
<EnumSelector
|
<EnumSelector
|
||||||
label={tr.schedulingLeechAction()}
|
label={tr.schedulingLeechAction()}
|
||||||
subLabel=""
|
subLabel=""
|
||||||
choices={leechChoices}
|
choices={leechChoices}
|
||||||
defaultValue={defaults.leechAction}
|
defaultValue={defaults.leechAction}
|
||||||
bind:value={config.leechAction} />
|
bind:value={$config.leechAction} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,17 @@ Copyright: Ankitects Pty Ltd and contributors
|
||||||
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type pb from "anki/backend_proto";
|
|
||||||
import * as tr from "anki/i18n";
|
import * as tr from "anki/i18n";
|
||||||
import SpinBox from "./SpinBox.svelte";
|
import SpinBox from "./SpinBox.svelte";
|
||||||
import SpinBoxFloat from "./SpinBoxFloat.svelte";
|
import SpinBoxFloat from "./SpinBoxFloat.svelte";
|
||||||
import CheckBox from "./CheckBox.svelte";
|
import CheckBox from "./CheckBox.svelte";
|
||||||
import StepsInput from "./StepsInput.svelte";
|
import StepsInput from "./StepsInput.svelte";
|
||||||
import EnumSelector from "./EnumSelector.svelte";
|
import EnumSelector from "./EnumSelector.svelte";
|
||||||
|
import type { DeckConfigState } from "./lib";
|
||||||
|
|
||||||
export let config: pb.BackendProto.DeckConfig.Config;
|
export let state: DeckConfigState;
|
||||||
export let defaults: pb.BackendProto.DeckConfig.Config;
|
let config = state.currentConfig;
|
||||||
|
let defaults = state.defaults;
|
||||||
|
|
||||||
const newOrderChoices = [
|
const newOrderChoices = [
|
||||||
tr.schedulingShowNewCardsInOrderAdded(),
|
tr.schedulingShowNewCardsInOrderAdded(),
|
||||||
|
|
@ -21,15 +22,18 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
let stepsExceedGraduatingInterval: boolean;
|
let stepsExceedGraduatingInterval: boolean;
|
||||||
$: {
|
$: {
|
||||||
const lastLearnStepInDays = config.learnSteps.length
|
const lastLearnStepInDays = $config.learnSteps.length
|
||||||
? config.learnSteps[config.learnSteps.length - 1] / 60 / 24
|
? $config.learnSteps[$config.learnSteps.length - 1] / 60 / 24
|
||||||
: 0;
|
: 0;
|
||||||
stepsExceedGraduatingInterval =
|
stepsExceedGraduatingInterval =
|
||||||
lastLearnStepInDays > config.graduatingIntervalGood;
|
lastLearnStepInDays > $config.graduatingIntervalGood;
|
||||||
}
|
}
|
||||||
|
|
||||||
let goodExceedsEasy: boolean;
|
$: goodExceedsEasy =
|
||||||
$: goodExceedsEasy = config.graduatingIntervalGood > config.graduatingIntervalEasy;
|
$config.graduatingIntervalGood > $config.graduatingIntervalEasy;
|
||||||
|
|
||||||
|
// fixme: change impl; support warning messages
|
||||||
|
$: newCardsGreaterThanParent = $config.newPerDay > state.currentDeck.parentNewLimit;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -40,36 +44,37 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
subLabel="Learning steps, separated by spaces."
|
subLabel="Learning steps, separated by spaces."
|
||||||
warn={stepsExceedGraduatingInterval}
|
warn={stepsExceedGraduatingInterval}
|
||||||
defaultValue={defaults.learnSteps}
|
defaultValue={defaults.learnSteps}
|
||||||
value={config.learnSteps}
|
value={$config.learnSteps}
|
||||||
on:changed={(evt) => (config.learnSteps = evt.detail.value)} />
|
on:changed={(evt) => ($config.learnSteps = evt.detail.value)} />
|
||||||
|
|
||||||
<EnumSelector
|
<EnumSelector
|
||||||
label={tr.schedulingOrder()}
|
label={tr.schedulingOrder()}
|
||||||
subLabel=""
|
subLabel=""
|
||||||
choices={newOrderChoices}
|
choices={newOrderChoices}
|
||||||
defaultValue={defaults.newCardOrder}
|
defaultValue={defaults.newCardOrder}
|
||||||
bind:value={config.newCardOrder} />
|
bind:value={$config.newCardOrder} />
|
||||||
|
|
||||||
<SpinBox
|
<SpinBox
|
||||||
label={tr.schedulingNewCardsday()}
|
label={tr.schedulingNewCardsday()}
|
||||||
subLabel="The maximum number of new cards to introduce in a day."
|
subLabel="The maximum number of new cards to introduce in a day."
|
||||||
min={0}
|
min={0}
|
||||||
|
warn={newCardsGreaterThanParent}
|
||||||
defaultValue={defaults.newPerDay}
|
defaultValue={defaults.newPerDay}
|
||||||
bind:value={config.newPerDay} />
|
bind:value={$config.newPerDay} />
|
||||||
|
|
||||||
<SpinBox
|
<SpinBox
|
||||||
label={tr.schedulingGraduatingInterval()}
|
label={tr.schedulingGraduatingInterval()}
|
||||||
subLabel="Days to wait after answering Good on the last learning step."
|
subLabel="Days to wait after answering Good on the last learning step."
|
||||||
warn={stepsExceedGraduatingInterval || goodExceedsEasy}
|
warn={stepsExceedGraduatingInterval || goodExceedsEasy}
|
||||||
defaultValue={defaults.graduatingIntervalGood}
|
defaultValue={defaults.graduatingIntervalGood}
|
||||||
bind:value={config.graduatingIntervalGood} />
|
bind:value={$config.graduatingIntervalGood} />
|
||||||
|
|
||||||
<SpinBox
|
<SpinBox
|
||||||
label={tr.schedulingEasyInterval()}
|
label={tr.schedulingEasyInterval()}
|
||||||
subLabel="Days to wait after answering Easy on the first learning step."
|
subLabel="Days to wait after answering Easy on the first learning step."
|
||||||
warn={goodExceedsEasy}
|
warn={goodExceedsEasy}
|
||||||
defaultValue={defaults.graduatingIntervalEasy}
|
defaultValue={defaults.graduatingIntervalEasy}
|
||||||
bind:value={config.graduatingIntervalEasy} />
|
bind:value={$config.graduatingIntervalEasy} />
|
||||||
|
|
||||||
<SpinBoxFloat
|
<SpinBoxFloat
|
||||||
label={tr.schedulingStartingEase()}
|
label={tr.schedulingStartingEase()}
|
||||||
|
|
@ -77,12 +82,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
min={1.31}
|
min={1.31}
|
||||||
max={5}
|
max={5}
|
||||||
defaultValue={defaults.initialEase}
|
defaultValue={defaults.initialEase}
|
||||||
value={config.initialEase}
|
value={$config.initialEase}
|
||||||
on:changed={(evt) => (config.initialEase = evt.detail.value)} />
|
on:changed={(evt) => ($config.initialEase = evt.detail.value)} />
|
||||||
|
|
||||||
<CheckBox
|
<CheckBox
|
||||||
label="Bury New"
|
label="Bury New"
|
||||||
subLabel={tr.schedulingBuryRelatedNewCardsUntilThe()}
|
subLabel={tr.schedulingBuryRelatedNewCardsUntilThe()}
|
||||||
defaultValue={defaults.buryNew}
|
defaultValue={defaults.buryNew}
|
||||||
bind:value={config.buryNew} />
|
bind:value={$config.buryNew} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,50 @@
|
||||||
Copyright: Ankitects Pty Ltd and contributors
|
Copyright: Ankitects Pty Ltd and contributors
|
||||||
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
-->
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
// import * as tr from "anki/i18n";
|
||||||
|
import { textInputModal } from "./textInputModal";
|
||||||
|
import type { DeckConfigState } from "./lib";
|
||||||
|
|
||||||
|
export let state: DeckConfigState;
|
||||||
|
|
||||||
|
function addConfig(): void {
|
||||||
|
textInputModal({
|
||||||
|
title: "Add Config",
|
||||||
|
prompt: "Name:",
|
||||||
|
onOk: (text: string) => {
|
||||||
|
const trimmed = text.trim();
|
||||||
|
if (trimmed.length) {
|
||||||
|
state.addConfig(trimmed);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renameConfig(): void {
|
||||||
|
textInputModal({
|
||||||
|
title: "Rename Config",
|
||||||
|
prompt: "Name:",
|
||||||
|
startingValue: state.getCurrentName(),
|
||||||
|
onOk: (text: string) => {
|
||||||
|
state.setCurrentName(text);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeConfig(): void {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (confirm("Are you sure?")) {
|
||||||
|
try {
|
||||||
|
state.removeCurrentConfig();
|
||||||
|
} catch (err) {
|
||||||
|
alert(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
:global(svg) {
|
:global(svg) {
|
||||||
vertical-align: text-bottom;
|
vertical-align: text-bottom;
|
||||||
|
|
@ -18,13 +62,16 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
<span class="visually-hidden">Toggle Dropdown</span>
|
<span class="visually-hidden">Toggle Dropdown</span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li><a class="dropdown-item" href={'#'}>Add</a></li>
|
<li><a class="dropdown-item" href={'#'} on:click={addConfig}>Add Config</a></li>
|
||||||
<li><a class="dropdown-item" href={'#'}>Rename</a></li>
|
<li>
|
||||||
<li><a class="dropdown-item" href={'#'}>Remove</a></li>
|
<a class="dropdown-item" href={'#'} on:click={renameConfig}>Rename Config</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item" href={'#'} on:click={removeConfig}>Remove Config</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<hr class="dropdown-divider" />
|
<hr class="dropdown-divider" />
|
||||||
</li>
|
</li>
|
||||||
<input type="checkbox" class="form-check-input" id="dropdownCheck" />
|
<li><a class="dropdown-item" href={'#'}>Apply to Child Decks</a></li>
|
||||||
<label class="form-check-label" for="dropdownCheck"> Apply to Children </label>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,15 @@ Copyright: Ankitects Pty Ltd and contributors
|
||||||
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type pb from "anki/backend_proto";
|
|
||||||
import * as tr from "anki/i18n";
|
import * as tr from "anki/i18n";
|
||||||
import SpinBox from "./SpinBox.svelte";
|
import SpinBox from "./SpinBox.svelte";
|
||||||
import SpinBoxFloat from "./SpinBoxFloat.svelte";
|
import SpinBoxFloat from "./SpinBoxFloat.svelte";
|
||||||
import CheckBox from "./CheckBox.svelte";
|
import CheckBox from "./CheckBox.svelte";
|
||||||
|
import type { DeckConfigState } from "./lib";
|
||||||
|
|
||||||
export let config: pb.BackendProto.DeckConfig.Config;
|
export let state: DeckConfigState;
|
||||||
export let defaults: pb.BackendProto.DeckConfig.Config;
|
let config = state.currentConfig;
|
||||||
|
let defaults = state.defaults;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -21,7 +22,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
subLabel="The maximum number of reviews cards to show in a day."
|
subLabel="The maximum number of reviews cards to show in a day."
|
||||||
min={0}
|
min={0}
|
||||||
defaultValue={defaults.reviewsPerDay}
|
defaultValue={defaults.reviewsPerDay}
|
||||||
bind:value={config.reviewsPerDay} />
|
bind:value={$config.reviewsPerDay} />
|
||||||
|
|
||||||
<SpinBoxFloat
|
<SpinBoxFloat
|
||||||
label={tr.schedulingEasyBonus()}
|
label={tr.schedulingEasyBonus()}
|
||||||
|
|
@ -29,8 +30,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
min={1}
|
min={1}
|
||||||
max={3}
|
max={3}
|
||||||
defaultValue={defaults.easyMultiplier}
|
defaultValue={defaults.easyMultiplier}
|
||||||
value={config.easyMultiplier}
|
value={$config.easyMultiplier}
|
||||||
on:changed={(evt) => (config.easyMultiplier = evt.detail.value)} />
|
on:changed={(evt) => ($config.easyMultiplier = evt.detail.value)} />
|
||||||
|
|
||||||
<SpinBoxFloat
|
<SpinBoxFloat
|
||||||
label={tr.schedulingIntervalModifier()}
|
label={tr.schedulingIntervalModifier()}
|
||||||
|
|
@ -38,8 +39,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
min={0.5}
|
min={0.5}
|
||||||
max={2}
|
max={2}
|
||||||
defaultValue={defaults.intervalMultiplier}
|
defaultValue={defaults.intervalMultiplier}
|
||||||
value={config.intervalMultiplier}
|
value={$config.intervalMultiplier}
|
||||||
on:changed={(evt) => (config.intervalMultiplier = evt.detail.value)} />
|
on:changed={(evt) => ($config.intervalMultiplier = evt.detail.value)} />
|
||||||
|
|
||||||
<SpinBox
|
<SpinBox
|
||||||
label={tr.schedulingMaximumInterval()}
|
label={tr.schedulingMaximumInterval()}
|
||||||
|
|
@ -47,7 +48,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
min={1}
|
min={1}
|
||||||
max={365 * 100}
|
max={365 * 100}
|
||||||
defaultValue={defaults.maximumReviewInterval}
|
defaultValue={defaults.maximumReviewInterval}
|
||||||
bind:value={config.maximumReviewInterval} />
|
bind:value={$config.maximumReviewInterval} />
|
||||||
|
|
||||||
<SpinBoxFloat
|
<SpinBoxFloat
|
||||||
label={tr.schedulingHardInterval()}
|
label={tr.schedulingHardInterval()}
|
||||||
|
|
@ -55,12 +56,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
min={0.5}
|
min={0.5}
|
||||||
max={1.3}
|
max={1.3}
|
||||||
defaultValue={defaults.hardMultiplier}
|
defaultValue={defaults.hardMultiplier}
|
||||||
value={config.hardMultiplier}
|
value={$config.hardMultiplier}
|
||||||
on:changed={(evt) => (config.hardMultiplier = evt.detail.value)} />
|
on:changed={(evt) => ($config.hardMultiplier = evt.detail.value)} />
|
||||||
|
|
||||||
<CheckBox
|
<CheckBox
|
||||||
label="Bury Reviews"
|
label="Bury Reviews"
|
||||||
subLabel={tr.schedulingBuryRelatedReviewsUntilTheNext()}
|
subLabel={tr.schedulingBuryRelatedReviewsUntilTheNext()}
|
||||||
defaultValue={defaults.buryReviews}
|
defaultValue={defaults.buryReviews}
|
||||||
bind:value={config.buryReviews} />
|
bind:value={$config.buryReviews} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
93
ts/deckconfig/TextInputModal.svelte
Normal file
93
ts/deckconfig/TextInputModal.svelte
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
<!--
|
||||||
|
Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
/* eslint
|
||||||
|
@typescript-eslint/no-non-null-assertion: "off",
|
||||||
|
*/
|
||||||
|
import { onMount, onDestroy } from "svelte";
|
||||||
|
import Modal from "bootstrap/js/dist/modal";
|
||||||
|
|
||||||
|
export let title: string;
|
||||||
|
export let prompt: string;
|
||||||
|
export let startingValue = "";
|
||||||
|
export let onOk: (text: string) => void;
|
||||||
|
|
||||||
|
let inputRef: HTMLInputElement;
|
||||||
|
let modal: Modal;
|
||||||
|
|
||||||
|
function onShown(): void {
|
||||||
|
inputRef.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onHidden(): void {
|
||||||
|
const container = document.getElementById("modal")!;
|
||||||
|
container.removeChild(container.firstElementChild!);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onOkClicked(): void {
|
||||||
|
onOk(inputRef.value);
|
||||||
|
modal.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKeyUp(evt: KeyboardEvent): void {
|
||||||
|
if (evt.code === "Enter") {
|
||||||
|
onOkClicked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
const container = document.getElementById("modal")!;
|
||||||
|
container.addEventListener("shown.bs.modal", onShown);
|
||||||
|
container.addEventListener("hidden.bs.modal", onHidden);
|
||||||
|
modal = new Modal(container.firstElementChild!, {});
|
||||||
|
modal.show();
|
||||||
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
const container = document.getElementById("modal")!;
|
||||||
|
container.removeEventListener("shown.bs.modal", onShown);
|
||||||
|
container.removeEventListener("hidden.bs.modal", onHidden);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="modal fade" tabindex="-1" aria-labelledby="modalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog" on:keyup={onKeyUp}>
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="modalLabel">{title}</h5>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn-close"
|
||||||
|
data-bs-dismiss="modal"
|
||||||
|
aria-label="Close" />
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label
|
||||||
|
for="prompt-input"
|
||||||
|
class="col-form-label">{prompt}</label>
|
||||||
|
<input
|
||||||
|
id="prompt-input"
|
||||||
|
bind:this={inputRef}
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
value={startingValue} />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-secondary"
|
||||||
|
data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
on:click={onOkClicked}>OK</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -5,6 +5,8 @@
|
||||||
@import "ts/sass/bootstrap/forms";
|
@import "ts/sass/bootstrap/forms";
|
||||||
@import "ts/sass/bootstrap/buttons";
|
@import "ts/sass/bootstrap/buttons";
|
||||||
@import "ts/sass/bootstrap/button-group";
|
@import "ts/sass/bootstrap/button-group";
|
||||||
|
@import "ts/sass/bootstrap/modal";
|
||||||
|
@import "ts/sass/bootstrap/close";
|
||||||
|
|
||||||
.night-mode {
|
.night-mode {
|
||||||
@include scrollbar.night-mode;
|
@include scrollbar.night-mode;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright: Ankitects Pty Ltd and contributors
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
import { getDeckConfigInfo, stateFromUpdateData } from "./lib";
|
import { getDeckConfigInfo, DeckConfigState } from "./lib";
|
||||||
import { setupI18n, ModuleName } from "anki/i18n";
|
import { setupI18n, ModuleName } from "anki/i18n";
|
||||||
import { checkNightMode } from "anki/nightmode";
|
import { checkNightMode } from "anki/nightmode";
|
||||||
import DeckConfigPage from "./DeckConfigPage.svelte";
|
import DeckConfigPage from "./DeckConfigPage.svelte";
|
||||||
|
|
@ -15,7 +15,7 @@ export async function deckConfig(
|
||||||
modules: [ModuleName.SCHEDULING, ModuleName.ACTIONS, ModuleName.DECK_CONFIG],
|
modules: [ModuleName.SCHEDULING, ModuleName.ACTIONS, ModuleName.DECK_CONFIG],
|
||||||
});
|
});
|
||||||
const info = await getDeckConfigInfo(deckId);
|
const info = await getDeckConfigInfo(deckId);
|
||||||
const state = stateFromUpdateData(info);
|
const state = new DeckConfigState(info);
|
||||||
new DeckConfigPage({
|
new DeckConfigPage({
|
||||||
target,
|
target,
|
||||||
props: { state },
|
props: { state },
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@
|
||||||
|
|
||||||
import pb from "anki/backend_proto";
|
import pb from "anki/backend_proto";
|
||||||
import { postRequest } from "anki/postrequest";
|
import { postRequest } from "anki/postrequest";
|
||||||
|
import { Writable, writable, get, Readable, readable } from "svelte/store";
|
||||||
|
import { isEqual, cloneDeep } from "lodash-es";
|
||||||
|
import * as tr from "anki/i18n";
|
||||||
|
|
||||||
export async function getDeckConfigInfo(
|
export async function getDeckConfigInfo(
|
||||||
deckId: number
|
deckId: number
|
||||||
|
|
@ -23,30 +26,148 @@ export interface ConfigWithCount {
|
||||||
useCount: number;
|
useCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DeckConfigState {
|
/// Info for showing the top selector
|
||||||
deckName: string;
|
export interface ConfigListEntry {
|
||||||
selectedConfigId: DeckConfigId;
|
idx: number;
|
||||||
removedConfigs: DeckConfigId[];
|
name: string;
|
||||||
renamedConfigs: Map<DeckConfigId, string>;
|
useCount: number;
|
||||||
allConfigs: ConfigWithCount[];
|
current: boolean;
|
||||||
defaults: pb.BackendProto.DeckConfig.Config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stateFromUpdateData(
|
type ConfigInner = pb.BackendProto.DeckConfig.Config;
|
||||||
data: pb.BackendProto.DeckConfigForUpdate
|
export class DeckConfigState {
|
||||||
): DeckConfigState {
|
readonly currentConfig: Writable<ConfigInner>;
|
||||||
const current = data.currentDeck as pb.BackendProto.DeckConfigForUpdate.CurrentDeck;
|
readonly configList: Readable<ConfigListEntry[]>;
|
||||||
return {
|
readonly currentDeck: pb.BackendProto.DeckConfigForUpdate.CurrentDeck;
|
||||||
deckName: current.name,
|
readonly defaults: ConfigInner;
|
||||||
selectedConfigId: current.configId,
|
|
||||||
removedConfigs: [],
|
private configs: ConfigWithCount[];
|
||||||
renamedConfigs: new Map(),
|
private selectedIdx: number;
|
||||||
allConfigs: data.allConfig.map((config) => {
|
private configListSetter?: (val: ConfigListEntry[]) => void;
|
||||||
|
private removedConfigs: DeckConfigId[] = [];
|
||||||
|
|
||||||
|
constructor(data: pb.BackendProto.DeckConfigForUpdate) {
|
||||||
|
this.currentDeck = data.currentDeck as pb.BackendProto.DeckConfigForUpdate.CurrentDeck;
|
||||||
|
this.defaults = data.defaults!.config! as ConfigInner;
|
||||||
|
this.configs = data.allConfig.map((config) => {
|
||||||
return {
|
return {
|
||||||
config: config.config as pb.BackendProto.DeckConfig,
|
config: config.config as pb.BackendProto.DeckConfig,
|
||||||
useCount: config.useCount!,
|
useCount: config.useCount!,
|
||||||
};
|
};
|
||||||
}),
|
});
|
||||||
defaults: data.defaults!.config! as pb.BackendProto.DeckConfig.Config,
|
this.selectedIdx =
|
||||||
};
|
this.configs.findIndex((c) => c.config.id === this.currentDeck.configId) ??
|
||||||
|
0;
|
||||||
|
// decrement the use count of the starting item, as we'll apply +1 to currently
|
||||||
|
// selected one at display time
|
||||||
|
this.configs[this.selectedIdx].useCount -= 1;
|
||||||
|
this.currentConfig = writable(this.getCurrentConfig());
|
||||||
|
this.configList = readable(this.getConfigList(), (set) => {
|
||||||
|
this.configListSetter = set;
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentIndex(index: number): void {
|
||||||
|
this.saveCurrentConfig();
|
||||||
|
this.selectedIdx = index;
|
||||||
|
this.updateCurrentConfig();
|
||||||
|
// use counts have changed
|
||||||
|
this.updateConfigList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Persist any changes made to the current config into the list of configs.
|
||||||
|
saveCurrentConfig(): void {
|
||||||
|
const config = get(this.currentConfig);
|
||||||
|
if (!isEqual(config, this.configs[this.selectedIdx].config.config)) {
|
||||||
|
console.log("save");
|
||||||
|
this.configs[this.selectedIdx].config.config = config;
|
||||||
|
this.configs[this.selectedIdx].config.mtimeSecs = 0;
|
||||||
|
} else {
|
||||||
|
console.log("no changes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentName(): string {
|
||||||
|
return this.configs[this.selectedIdx].config.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentName(name: string): void {
|
||||||
|
if (this.configs[this.selectedIdx].config.name === name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const uniqueName = this.ensureNewNameUnique(name);
|
||||||
|
this.configs[this.selectedIdx].config.name = uniqueName;
|
||||||
|
this.configs[this.selectedIdx].config.mtimeSecs = 0;
|
||||||
|
this.updateConfigList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a new config, making it current.
|
||||||
|
/// not already a new config.
|
||||||
|
addConfig(name: string): void {
|
||||||
|
const uniqueName = this.ensureNewNameUnique(name);
|
||||||
|
const config = pb.BackendProto.DeckConfig.create({
|
||||||
|
id: 0,
|
||||||
|
name: uniqueName,
|
||||||
|
config: cloneDeep(this.defaults),
|
||||||
|
});
|
||||||
|
const configWithCount = { config, useCount: 0 };
|
||||||
|
this.configs.push(configWithCount);
|
||||||
|
this.selectedIdx = this.configs.length - 1;
|
||||||
|
this.updateCurrentConfig();
|
||||||
|
this.updateConfigList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Will throw if the default deck is selected.
|
||||||
|
removeCurrentConfig(): void {
|
||||||
|
const currentId = this.configs[this.selectedIdx].config.id;
|
||||||
|
if (currentId === 1) {
|
||||||
|
throw "can't remove default config";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentId !== 0) {
|
||||||
|
this.removedConfigs.push(currentId);
|
||||||
|
}
|
||||||
|
this.configs.splice(this.selectedIdx, 1);
|
||||||
|
this.selectedIdx = Math.max(0, this.selectedIdx - 1);
|
||||||
|
this.updateCurrentConfig();
|
||||||
|
this.updateConfigList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ensureNewNameUnique(name: string): string {
|
||||||
|
if (this.configs.find((e) => e.config.name === name) !== undefined) {
|
||||||
|
return name + (new Date().getTime() / 1000).toFixed(0);
|
||||||
|
} else {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateCurrentConfig(): void {
|
||||||
|
this.currentConfig.set(this.getCurrentConfig());
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateConfigList(): void {
|
||||||
|
this.configListSetter?.(this.getConfigList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a copy of the currently selected config.
|
||||||
|
private getCurrentConfig(): ConfigInner {
|
||||||
|
return cloneDeep(this.configs[this.selectedIdx].config.config as ConfigInner);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getConfigList(): ConfigListEntry[] {
|
||||||
|
const list: ConfigListEntry[] = this.configs.map((c, idx) => {
|
||||||
|
const useCount = c.useCount + (idx === this.selectedIdx ? 1 : 0);
|
||||||
|
return {
|
||||||
|
name: c.config.name,
|
||||||
|
current: idx === this.selectedIdx,
|
||||||
|
idx,
|
||||||
|
useCount,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
list.sort((a, b) =>
|
||||||
|
a.name.localeCompare(b.name, tr.i18n.langs, { sensitivity: "base" })
|
||||||
|
);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
23
ts/deckconfig/textInputModal.ts
Normal file
23
ts/deckconfig/textInputModal.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
/* eslint
|
||||||
|
@typescript-eslint/no-non-null-assertion: "off",
|
||||||
|
*/
|
||||||
|
|
||||||
|
import TextInputModal from "./TextInputModal.svelte";
|
||||||
|
|
||||||
|
export interface TextInputModalProps {
|
||||||
|
title: string;
|
||||||
|
prompt: string;
|
||||||
|
startingValue?: string;
|
||||||
|
onOk: (string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function textInputModal(props: TextInputModalProps): TextInputModal {
|
||||||
|
const target = document.getElementById("modal")!;
|
||||||
|
return new TextInputModal({
|
||||||
|
target,
|
||||||
|
props,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -100,7 +100,6 @@ jest_test(
|
||||||
data = [
|
data = [
|
||||||
":test_lib",
|
":test_lib",
|
||||||
"//ts:jest.config.js",
|
"//ts:jest.config.js",
|
||||||
"//ts:package.json",
|
|
||||||
"@npm//protobufjs",
|
"@npm//protobufjs",
|
||||||
],
|
],
|
||||||
target_compatible_with = select({
|
target_compatible_with = select({
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue