Merge pull request #1207 from hgiesel/deckoptionssections2

Deck Options refactoring
This commit is contained in:
Damien Elmes 2021-06-22 09:36:15 +10:00 committed by GitHub
commit 76b005991e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
61 changed files with 1265 additions and 673 deletions

View File

@ -7,7 +7,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import StickyBar from "components/StickyBar.svelte";
import ButtonToolbar from "components/ButtonToolbar.svelte";
import ButtonToolbarItem from "components/ButtonToolbarItem.svelte";
import Item from "components/Item.svelte";
import ButtonGroup from "components/ButtonGroup.svelte";
import ButtonGroupItem from "components/ButtonGroupItem.svelte";
@ -27,7 +27,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<StickyBar>
<ButtonToolbar class="justify-content-between" size={2.3} wrap={false}>
<ButtonToolbarItem>
<Item>
<ButtonGroup class="flex-grow-1">
<ButtonGroupItem>
<SelectButton class="flex-grow-1" on:change={blur}>
@ -42,10 +42,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
</SelectButton>
</ButtonGroupItem>
</ButtonGroup>
</ButtonToolbarItem>
</Item>
<ButtonToolbarItem>
<Item>
<SaveButton {state} />
</ButtonToolbarItem>
</Item>
</ButtonToolbar>
</StickyBar>

View File

@ -8,7 +8,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { writable } from "svelte/store";
import { buttonGroupKey } from "./contextKeys";
import type { Identifier } from "./identifier";
import { insert, add } from "./identifier";
import { insertElement, appendElement } from "./identifier";
import type { ButtonRegistration } from "./buttons";
import { ButtonPosition } from "./buttons";
import type { SvelteComponent } from "./registration";
@ -62,9 +62,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
getDynamicInterface(buttonGroupRef);
const insertButton = (button: SvelteComponent, position: Identifier = 0) =>
addComponent(button, (added, parent) => insert(added, parent, position));
addComponent(button, (added, parent) =>
insertElement(added, parent, position)
);
const appendButton = (button: SvelteComponent, position: Identifier = -1) =>
addComponent(button, (added, parent) => add(added, parent, position));
addComponent(button, (added, parent) =>
appendElement(added, parent, position)
);
const showButton = (id: Identifier) =>
updateRegistration(({ detach }) => detach.set(false), id);

View File

@ -5,12 +5,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<script lang="typescript">
import { setContext } from "svelte";
import { writable } from "svelte/store";
import ButtonToolbarItem from "./ButtonToolbarItem.svelte";
import type { ButtonGroupRegistration } from "./buttons";
import { buttonToolbarKey } from "./contextKeys";
import Item from "./Item.svelte";
import { sectionKey } from "./contextKeys";
import type { Identifier } from "./identifier";
import { insert, add } from "./identifier";
import type { SvelteComponent } from "./registration";
import { insertElement, appendElement } from "./identifier";
import type { SvelteComponent, Registration } from "./registration";
import { makeInterface } from "./registration";
export let id: string | undefined = undefined;
@ -30,7 +29,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
$: style = buttonSize + buttonWrap;
function makeRegistration(): ButtonGroupRegistration {
function makeRegistration(): Registration {
const detach = writable(false);
return { detach };
}
@ -38,7 +37,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
const { registerComponent, dynamicItems, getDynamicInterface } =
makeInterface(makeRegistration);
setContext(buttonToolbarKey, registerComponent);
setContext(sectionKey, registerComponent);
export let api: Record<string, unknown> | undefined = undefined;
let buttonToolbarRef: HTMLDivElement;
@ -48,9 +47,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
getDynamicInterface(buttonToolbarRef);
const insertGroup = (group: SvelteComponent, position: Identifier = 0) =>
addComponent(group, (added, parent) => insert(added, parent, position));
addComponent(group, (added, parent) =>
insertElement(added, parent, position)
);
const appendGroup = (group: SvelteComponent, position: Identifier = -1) =>
addComponent(group, (added, parent) => add(added, parent, position));
addComponent(group, (added, parent) =>
appendElement(added, parent, position)
);
const showGroup = (id: Identifier) =>
updateRegistration(({ detach }) => detach.set(false), id);
@ -75,15 +78,15 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<div
bind:this={buttonToolbarRef}
{id}
class={`btn-toolbar wrap-variable ${className}`}
class={`btn-toolbar container wrap-variable ${className}`}
{style}
role="toolbar"
>
<slot />
{#each $dynamicItems as item}
<ButtonToolbarItem id={item[0].id} registration={item[1]}>
<Item id={item[0].id} registration={item[1]}>
<svelte:component this={item[0].component} {...item[0].props} />
</ButtonToolbarItem>
</Item>
{/each}
</div>

View File

@ -0,0 +1,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 Section from "./Section.svelte";
export let id: string | undefined = undefined;
let className: string = "";
export { className as class };
export let api: Record<string, never> | undefined = undefined;
</script>
<div {id} class={`container mb-1 ${className}`}>
<Section {api}>
<slot />
</Section>
</div>

View File

@ -5,23 +5,21 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<script lang="typescript">
import Detachable from "components/Detachable.svelte";
import type { ButtonGroupRegistration } from "./buttons";
import type { Register } from "./registration";
import type { Register, Registration } from "./registration";
import { getContext, hasContext } from "svelte";
import { buttonToolbarKey } from "./contextKeys";
import { sectionKey } from "./contextKeys";
export let id: string | undefined = undefined;
export let registration: ButtonGroupRegistration | undefined = undefined;
export let registration: Registration | undefined = undefined;
let detached: boolean;
if (registration) {
const { detach } = registration;
detach.subscribe((value: boolean) => (detached = value));
} else if (hasContext(buttonToolbarKey)) {
const registerComponent =
getContext<Register<ButtonGroupRegistration>>(buttonToolbarKey);
} else if (hasContext(sectionKey)) {
const registerComponent = getContext<Register<Registration>>(sectionKey);
const { detach } = registerComponent();
detach.subscribe((value: boolean) => (detached = value));
} else {

View File

@ -0,0 +1,69 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="typescript">
import { setContext } from "svelte";
import { writable } from "svelte/store";
import Item from "./Item.svelte";
import { sectionKey } from "./contextKeys";
import type { Identifier } from "./identifier";
import { insertElement, appendElement } from "./identifier";
import type { SvelteComponent, Registration } from "./registration";
import { makeInterface } from "./registration";
export let id: string | undefined = undefined;
function makeRegistration(): Registration {
const detach = writable(false);
return { detach };
}
const { registerComponent, dynamicItems, getDynamicInterface } =
makeInterface(makeRegistration);
setContext(sectionKey, registerComponent);
export let api: Record<string, never> | undefined = undefined;
let sectionRef: HTMLDivElement;
$: if (sectionRef && api) {
const { addComponent, updateRegistration } = getDynamicInterface(sectionRef);
const insert = (group: SvelteComponent, position: Identifier = 0) =>
addComponent(group, (added, parent) =>
insertElement(added, parent, position)
);
const append = (group: SvelteComponent, position: Identifier = -1) =>
addComponent(group, (added, parent) =>
appendElement(added, parent, position)
);
const show = (id: Identifier) =>
updateRegistration(({ detach }) => detach.set(false), id);
const hide = (id: Identifier) =>
updateRegistration(({ detach }) => detach.set(true), id);
const toggle = (id: Identifier) =>
updateRegistration(
({ detach }) => detach.update((old: boolean): boolean => !old),
id
);
Object.assign(api, { insert, append, show, hide, toggle });
}
</script>
<div bind:this={sectionRef} {id}>
<slot />
{#each $dynamicItems as item}
<Item id={item[0].id} registration={item[1]}>
<svelte:component this={item[0].component} {...item[0].props} />
</Item>
{/each}
</div>
<style lang="scss">
div {
display: contents;
}
</style>

View File

@ -35,6 +35,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
class="{className} form-select"
class:btn-day={!nightMode}
class:btn-night={nightMode}
class:visible-down-arrow={nightMode}
title={tooltip}
on:change
>
@ -50,6 +51,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
overflow-x: hidden;
}
.visible-down-arrow {
/* override the default down arrow */
background-image: button.down-arrow(white);
}
@include button.btn-day($with-hover: false);
@include button.btn-night($with-hover: false);
</style>

View File

@ -4,11 +4,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="typescript">
export let id: string | undefined = undefined;
let className: string | undefined;
let className: string = "";
export { className as class };
</script>
<nav {id} class={`pb-1 pt-1 ${className}`}>
<nav {id} class={`container-fluid pb-1 pt-1 ${className}`}>
<slot />
</nav>

View File

@ -8,6 +8,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { setContext } from "svelte";
import { dropdownKey } from "./contextKeys";
export let disabled = false;
setContext(dropdownKey, {
dropdown: true,
"data-bs-toggle": "dropdown",
@ -17,26 +19,26 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
const menuId = Math.random().toString(36).substring(2);
let dropdown: Dropdown;
function activateDropdown(_event: MouseEvent): void {
dropdown.toggle();
function activateDropdown(): void {
if (!disabled) {
dropdown.toggle();
}
}
/* Normally dropdown and trigger are associated with a
/* common ancestor with .dropdown class */
function createDropdown(event: CustomEvent): void {
const button: HTMLButtonElement = event.detail.button;
function createDropdown(element: HTMLElement): void {
/* Prevent focus on menu activation */
const noop = () => {};
Object.defineProperty(button, "focus", { value: noop });
Object.defineProperty(element, "focus", { value: noop });
const menu = (button.getRootNode() as Document) /* or shadow root */
const menu = (element.getRootNode() as Document) /* or shadow root */
.getElementById(menuId);
if (!menu) {
console.log(`Could not find menu "${menuId}" for dropdown menu.`);
} else {
dropdown = new Dropdown(button);
dropdown = new Dropdown(element);
/* Set custom menu without using common element with .dropdown */
(dropdown as any)._menu = menu;

View File

@ -1,6 +1,7 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import type { Writable } from "svelte/store";
import type { Registration } from "./registration";
export enum ButtonPosition {
Standalone,
@ -9,11 +10,6 @@ export enum ButtonPosition {
Rightmost,
}
export interface ButtonRegistration {
detach: Writable<boolean>;
export interface ButtonRegistration extends Registration {
position: Writable<ButtonPosition>;
}
export interface ButtonGroupRegistration {
detach: Writable<boolean>;
}

View File

@ -1,9 +1,10 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
export const nightModeKey = Symbol("nightMode");
export const touchDeviceKey = Symbol("touchDevice");
export const disabledKey = Symbol("disabled");
export const buttonToolbarKey = Symbol("buttonToolbar");
export const sectionKey = Symbol("section");
export const buttonGroupKey = Symbol("buttonGroup");
export const dropdownKey = Symbol("dropdown");
export const modalsKey = Symbol("modals");

View File

@ -2,7 +2,7 @@
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
export type Identifier = string | number;
export function find(
export function findElement(
collection: HTMLCollection,
idOrIndex: Identifier
): [number, Element] | null {
@ -34,12 +34,12 @@ export function find(
return result;
}
export function insert(
export function insertElement(
element: Element,
collection: Element,
idOrIndex: Identifier
): number {
const match = find(collection.children, idOrIndex);
const match = findElement(collection.children, idOrIndex);
if (match) {
const [index, reference] = match;
@ -51,12 +51,12 @@ export function insert(
return -1;
}
export function add(
export function appendElement(
element: Element,
collection: Element,
idOrIndex: Identifier
): number {
const match = find(collection.children, idOrIndex);
const match = findElement(collection.children, idOrIndex);
if (match) {
const [index, before] = match;
@ -69,12 +69,12 @@ export function add(
return -1;
}
export function update(
export function updateElement(
f: (element: Element) => void,
collection: Element,
idOrIndex: Identifier
): number {
const match = find(collection.children, idOrIndex);
const match = findElement(collection.children, idOrIndex);
if (match) {
const [index, element] = match;

View File

@ -1,10 +1,10 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import type { SvelteComponentTyped } from "svelte/internal";
import type { Readable } from "svelte/store";
import type { Writable, Readable } from "svelte/store";
import { writable } from "svelte/store";
import type { Identifier } from "./identifier";
import { find } from "./identifier";
import { findElement } from "./identifier";
export interface SvelteComponent {
component: SvelteComponentTyped;
@ -12,9 +12,13 @@ export interface SvelteComponent {
props: Record<string, unknown> | undefined;
}
export type Register<T> = (index?: number, registration?: T) => T;
export interface Registration {
detach: Writable<boolean>;
}
export interface RegistrationAPI<T> {
export type Register<T extends Registration> = (index?: number, registration?: T) => T;
export interface RegistrationAPI<T extends Registration> {
registerComponent: Register<T>;
items: Readable<T[]>;
dynamicItems: Readable<[SvelteComponent, T][]>;
@ -36,7 +40,9 @@ export function nodeIsElement(node: Node): node is Element {
return node.nodeType === Node.ELEMENT_NODE;
}
export function makeInterface<T>(makeRegistration: () => T): RegistrationAPI<T> {
export function makeInterface<T extends Registration>(
makeRegistration: () => T
): RegistrationAPI<T> {
const registrations: T[] = [];
const items = writable(registrations);
@ -92,7 +98,7 @@ export function makeInterface<T>(makeRegistration: () => T): RegistrationAPI<T>
update: (registration: T) => void,
position: Identifier
): void {
const match = find(elementRef.children, position);
const match = findElement(elementRef.children, position);
if (match) {
const [index] = match;

View File

@ -3,18 +3,18 @@ Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
// import * as tr from "lib/i18n";
import TitledContainer from "./TitledContainer.svelte";
import type { DeckOptionsState } from "./lib";
export let state: DeckOptionsState;
export let api: Record<string, never>;
let components = state.addonComponents;
const auxData = state.currentAuxData;
</script>
{#if $components.length || state.haveAddons}
<div>
<h2>Add-ons</h2>
<TitledContainer title="Add-ons" {api}>
<p>
If you're using an add-on that hasn't been updated to use this new screen
yet, you can access the old deck options screen by holding down the shift
@ -24,5 +24,5 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
{#each $components as addon}
<svelte:component this={addon.component} bind:data={$auxData} {...addon} />
{/each}
</div>
</TitledContainer>
{/if}

View File

@ -4,72 +4,88 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import * as tr from "lib/i18n";
import SpinBox from "./SpinBox.svelte";
import SpinBoxFloat from "./SpinBoxFloat.svelte";
import TitledContainer from "./TitledContainer.svelte";
import Item from "components/Item.svelte";
import SpinBoxRow from "./SpinBoxRow.svelte";
import SpinBoxFloatRow from "./SpinBoxFloatRow.svelte";
import type { DeckOptionsState } from "./lib";
export let state: DeckOptionsState;
export let api: Record<string, never>;
let config = state.currentConfig;
let defaults = state.defaults;
</script>
<h2>{tr.deckConfigAdvancedTitle()}</h2>
<TitledContainer title={tr.deckConfigAdvancedTitle()} {api}>
<Item>
<SpinBoxRow
bind:value={$config.maximumReviewInterval}
defaultValue={defaults.maximumReviewInterval}
min={1}
max={365 * 100}
markdownTooltip={tr.deckConfigMaximumIntervalTooltip()}
>
{tr.schedulingMaximumInterval()}
</SpinBoxRow>
</Item>
<SpinBox
label={tr.schedulingMaximumInterval()}
tooltip={tr.deckConfigMaximumIntervalTooltip()}
min={1}
max={365 * 100}
defaultValue={defaults.maximumReviewInterval}
bind:value={$config.maximumReviewInterval}
/>
<Item>
<SpinBoxFloatRow
bind:value={$config.initialEase}
defaultValue={defaults.initialEase}
min={1.31}
max={5}
markdownTooltip={tr.deckConfigStartingEaseTooltip()}
>
{tr.schedulingStartingEase()}
</SpinBoxFloatRow>
</Item>
<SpinBoxFloat
label={tr.schedulingStartingEase()}
tooltip={tr.deckConfigStartingEaseTooltip()}
min={1.31}
max={5}
defaultValue={defaults.initialEase}
value={$config.initialEase}
on:changed={(evt) => ($config.initialEase = evt.detail.value)}
/>
<Item>
<SpinBoxFloatRow
bind:value={$config.easyMultiplier}
defaultValue={defaults.easyMultiplier}
min={1}
max={3}
markdownTooltip={tr.deckConfigEasyBonusTooltip()}
>
{tr.schedulingEasyBonus()}
</SpinBoxFloatRow>
</Item>
<SpinBoxFloat
label={tr.schedulingEasyBonus()}
tooltip={tr.deckConfigEasyBonusTooltip()}
min={1}
max={3}
defaultValue={defaults.easyMultiplier}
value={$config.easyMultiplier}
on:changed={(evt) => ($config.easyMultiplier = evt.detail.value)}
/>
<Item>
<SpinBoxFloatRow
bind:value={$config.intervalMultiplier}
defaultValue={defaults.intervalMultiplier}
min={0.5}
max={2}
markdownTooltip={tr.deckConfigIntervalModifierTooltip()}
>
{tr.schedulingIntervalModifier()}
</SpinBoxFloatRow>
</Item>
<SpinBoxFloat
label={tr.schedulingIntervalModifier()}
tooltip={tr.deckConfigIntervalModifierTooltip()}
min={0.5}
max={2}
defaultValue={defaults.intervalMultiplier}
value={$config.intervalMultiplier}
on:changed={(evt) => ($config.intervalMultiplier = evt.detail.value)}
/>
<Item>
<SpinBoxFloatRow
bind:value={$config.hardMultiplier}
defaultValue={defaults.hardMultiplier}
min={0.5}
max={1.3}
markdownTooltip={tr.deckConfigHardIntervalTooltip()}
>
{tr.schedulingHardInterval()}
</SpinBoxFloatRow>
</Item>
<SpinBoxFloat
label={tr.schedulingHardInterval()}
tooltip={tr.deckConfigHardIntervalTooltip()}
min={0.5}
max={1.3}
defaultValue={defaults.hardMultiplier}
value={$config.hardMultiplier}
on:changed={(evt) => ($config.hardMultiplier = evt.detail.value)}
/>
<SpinBoxFloat
label={tr.schedulingNewInterval()}
tooltip={tr.deckConfigNewIntervalTooltip()}
min={0}
max={1}
defaultValue={defaults.lapseMultiplier}
value={$config.lapseMultiplier}
on:changed={(evt) => ($config.lapseMultiplier = evt.detail.value)}
/>
<Item>
<SpinBoxFloatRow
bind:value={$config.lapseMultiplier}
defaultValue={defaults.lapseMultiplier}
max={1}
markdownTooltip={tr.deckConfigNewIntervalTooltip()}
>
{tr.schedulingNewInterval()}
</SpinBoxFloatRow>
</Item>
</TitledContainer>

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">
import * as tr from "lib/i18n";
import TitledContainer from "./TitledContainer.svelte";
import Item from "components/Item.svelte";
import SwitchRow from "./SwitchRow.svelte";
import type { DeckOptionsState } from "./lib";
export let state: DeckOptionsState;
export let api: Record<string, never>;
let config = state.currentConfig;
let defaults = state.defaults;
</script>
<TitledContainer title={tr.deckConfigAudioTitle()} {api}>
<Item>
<SwitchRow
bind:value={$config.disableAutoplay}
defaultValue={defaults.disableAutoplay}
>
{tr.deckConfigDisableAutoplay()}
</SwitchRow>
</Item>
<Item>
<SwitchRow
bind:value={$config.skipQuestionWhenReplayingAnswer}
defaultValue={defaults.skipQuestionWhenReplayingAnswer}
markdownTooltip={tr.deckConfigAlwaysIncludeQuestionAudioTooltip()}
>
{tr.schedulingAlwaysIncludeQuestionSideWhenReplaying()}
</SwitchRow>
</Item>
</TitledContainer>

View File

@ -38,6 +38,7 @@ copy_bootstrap_icons(
icons = [
"arrow-counterclockwise.svg",
"info-circle.svg",
"gear.svg",
],
)
@ -133,6 +134,7 @@ svelte_check(
"*.svelte",
]) + [
"//ts/sass:button_mixins_lib",
"//ts/sass:night_mode_lib",
"//ts/sass/bootstrap",
"@npm//@types/bootstrap",
"@npm//@types/lodash-es",

View File

@ -0,0 +1,46 @@
<!--
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 { DropdownProps } from "components/dropdown";
import { dropdownKey } from "components/contextKeys";
import { onMount, createEventDispatcher, getContext } from "svelte";
let className = "";
export { className as class };
const dispatch = createEventDispatcher();
let spanRef: HTMLSpanElement;
const dropdownProps = getContext<DropdownProps>(dropdownKey) ?? { dropdown: false };
onMount(() => {
dispatch("mount", { span: spanRef });
});
</script>
<span
bind:this={spanRef}
class={`badge ${className}`}
class:dropdown-toggle={dropdownProps.dropdown}
{...dropdownProps}
on:click
>
<slot />
</span>
<style>
.badge {
color: inherit;
}
.dropdown-toggle::after {
display: none;
}
span :global(svg) {
vertical-align: -0.125rem;
}
</style>

View File

@ -4,26 +4,36 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import * as tr from "lib/i18n";
import CheckBox from "./CheckBox.svelte";
import TitledContainer from "./TitledContainer.svelte";
import Item from "components/Item.svelte";
import SwitchRow from "./SwitchRow.svelte";
import type { DeckOptionsState } from "./lib";
export let state: DeckOptionsState;
export let api: Record<string, never>;
let config = state.currentConfig;
let defaults = state.defaults;
</script>
<h2>{tr.deckConfigBuryTitle()}</h2>
<TitledContainer title={tr.deckConfigBuryTitle()} {api}>
<Item>
<SwitchRow
bind:value={$config.buryNew}
defaultValue={defaults.buryNew}
markdownTooltip={tr.deckConfigBuryTooltip()}
>
{tr.deckConfigBuryNewSiblings()}
</SwitchRow>
</Item>
<CheckBox
label={tr.deckConfigBuryNewSiblings()}
tooltip={tr.deckConfigBuryTooltip()}
defaultValue={defaults.buryNew}
bind:value={$config.buryNew}
/>
<CheckBox
label={tr.deckConfigBuryReviewSiblings()}
tooltip={tr.deckConfigBuryTooltip()}
defaultValue={defaults.buryReviews}
bind:value={$config.buryReviews}
/>
<Item>
<SwitchRow
bind:value={$config.buryReviews}
defaultValue={defaults.buryReviews}
markdownTooltip={tr.deckConfigBuryTooltip()}
>
{tr.deckConfigBuryReviewSiblings()}
</SwitchRow>
</Item>
</TitledContainer>

View File

@ -3,26 +3,7 @@ Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import ConfigEntry from "./ConfigEntry.svelte";
import HelpPopup from "./HelpPopup.svelte";
export let label: string;
export let value: boolean;
export let defaultValue: boolean;
export let tooltip = "";
export let id: string | undefined = undefined;
</script>
<ConfigEntry {id} label="" wholeLine={true} bind:value {defaultValue}>
<div class="checkbox-outer">
<label> <input type="checkbox" bind:checked={value} /> {label} </label>
{#if tooltip}
<HelpPopup html={tooltip} />
{/if}
</div>
</ConfigEntry>
<style lang="scss">
.checkbox-outer {
margin-top: 0.5em;
}
</style>
<label> <input type="checkbox" bind:checked={value} /> <slot /> </label>

View File

@ -0,0 +1,26 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import Row from "./Row.svelte";
import Col from "./Col.svelte";
import Label from "./Label.svelte";
import TooltipLabel from "./TooltipLabel.svelte";
import CheckBox from "./CheckBox.svelte";
import RevertButton from "./RevertButton.svelte";
export let value: boolean;
export let defaultValue: boolean;
export let markdownTooltip: string | undefined = undefined;
</script>
<Row>
<Col>
<RevertButton bind:value {defaultValue} />
<CheckBox bind:value
>{#if markdownTooltip}<TooltipLabel {markdownTooltip}><slot /></TooltipLabel
>{:else}<Label><slot /></Label>{/if}</CheckBox
>
</Col>
</Row>

22
ts/deckoptions/Col.svelte Normal file
View File

@ -0,0 +1,22 @@
<!--
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 { Breakpoint, Size } from "./col";
export let breakpoint: Breakpoint | undefined = undefined;
export let size: Size | undefined = undefined;
export let grow = true;
let colClass: string;
$: {
const breakpointComponent = breakpoint ? `-${breakpoint}` : "";
const sizeComponent = size ? `-${size}` : "";
colClass = "col" + breakpointComponent + sizeComponent;
}
</script>
<div class={`${colClass} d-flex align-items-center`} class:flex-grow-0={!grow}>
<slot />
</div>

View File

@ -1,48 +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 DailyLimits from "./DailyLimits.svelte";
import DisplayOrder from "./DisplayOrder.svelte";
import NewOptions from "./NewOptions.svelte";
import AdvancedOptions from "./AdvancedOptions.svelte";
import BuryOptions from "./BuryOptions.svelte";
import LapseOptions from "./LapseOptions.svelte";
import GeneralOptions from "./GeneralOptions.svelte";
import Addons from "./Addons.svelte";
import type { DeckOptionsState } from "./lib";
export let state: DeckOptionsState;
</script>
<div class="outer">
<DailyLimits {state} />
<NewOptions {state} />
<LapseOptions {state} />
<BuryOptions {state} />
{#if state.v3Scheduler}
<DisplayOrder {state} />
{/if}
<GeneralOptions {state} />
<Addons {state} />
<AdvancedOptions {state} />
</div>
<style lang="scss">
:global(h2) {
margin-top: 1em;
font-weight: bold;
// adding a border decreases the default font size,
// so increase it again
font-size: 2em;
border-bottom: 1px solid var(--medium-border);
margin-right: 16px;
margin-bottom: 0.5em;
}
.outer {
// the right margin has an indent to allow for the undo
// buttons; add the same indent on the left for balance
padding-left: 16px;
}
</style>

View File

@ -1,80 +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 { slide } from "svelte/transition";
import RevertButton from "./RevertButton.svelte";
import HelpPopup from "./HelpPopup.svelte";
export let label: string;
export let tooltip = "";
export let value: any;
export let defaultValue: any;
/// empty strings will be ignored
export let warnings: string[] = [];
export let wholeLine = false;
export let id: string | undefined = undefined;
let renderedTooltip: string;
$: renderedTooltip = marked(tooltip);
</script>
<div {id} class="outer">
{#if label}
<div class="table">
<span class="vcenter">
{label}
{#if renderedTooltip}
<HelpPopup html={renderedTooltip} />
{/if}
</span>
</div>
{/if}
<div class="input-grid" class:full-grid-width={wholeLine}>
<slot />
<RevertButton bind:value {defaultValue} on:revert />
</div>
<div class="full-grid-width">
{#each warnings as warning}
{#if warning}
<div class="alert alert-warning" in:slide out:slide>{warning}</div>
{/if}
{/each}
</div>
</div>
<style lang="scss">
.outer {
display: grid;
grid-template-columns: 7fr 3fr;
grid-row-gap: 0.5em;
}
.full-grid-width {
grid-column: 1 / 6;
}
.table {
display: table;
height: 100%;
}
.vcenter {
display: table-cell;
vertical-align: middle;
}
.alert {
margin-top: 0.5em;
}
.input-grid {
display: grid;
grid-column-gap: 0.5em;
grid-template-columns: 10fr 16px;
}
</style>

View File

@ -12,7 +12,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import TextInputModal from "./TextInputModal.svelte";
import StickyBar from "components/StickyBar.svelte";
import ButtonToolbar from "components/ButtonToolbar.svelte";
import ButtonToolbarItem from "components/ButtonToolbarItem.svelte";
import Item from "components/Item.svelte";
import ButtonGroup from "components/ButtonGroup.svelte";
import ButtonGroupItem from "components/ButtonGroupItem.svelte";
@ -87,9 +87,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
bind:modalKey
/>
<StickyBar>
<StickyBar class="g-1">
<ButtonToolbar class="justify-content-between" size={2.3} wrap={false}>
<ButtonToolbarItem>
<Item>
<ButtonGroup class="flex-grow-1">
<ButtonGroupItem>
<SelectButton class="flex-grow-1" on:change={blur}>
@ -104,15 +104,15 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
</SelectButton>
</ButtonGroupItem>
</ButtonGroup>
</ButtonToolbarItem>
</Item>
<ButtonToolbarItem>
<Item>
<SaveButton
{state}
on:add={promptToAdd}
on:clone={promptToClone}
on:rename={promptToRename}
/>
</ButtonToolbarItem>
</Item>
</ButtonToolbar>
</StickyBar>

View File

@ -1,13 +1,18 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import * as tr from "lib/i18n";
import SpinBox from "./SpinBox.svelte";
import TitledContainer from "./TitledContainer.svelte";
import Item from "components/Item.svelte";
import SpinBoxRow from "./SpinBoxRow.svelte";
import Warning from "./Warning.svelte";
import type { DeckOptionsState } from "./lib";
export let state: DeckOptionsState;
export let api: Record<string, never>;
let config = state.currentConfig;
let defaults = state.defaults;
let parentLimits = state.parentLimits;
@ -33,22 +38,28 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
: "";
</script>
<h2>{tr.deckConfigDailyLimits()}</h2>
<TitledContainer title={tr.deckConfigDailyLimits()} {api}>
<Item>
<SpinBoxRow
bind:value={$config.newPerDay}
defaultValue={defaults.newPerDay}
markdownTooltip={tr.deckConfigNewLimitTooltip() + v3Extra}
>
{tr.schedulingNewCardsday()}
</SpinBoxRow>
<SpinBox
label={tr.schedulingNewCardsday()}
tooltip={tr.deckConfigNewLimitTooltip() + v3Extra}
min={0}
warnings={[newCardsGreaterThanParent]}
defaultValue={defaults.newPerDay}
bind:value={$config.newPerDay}
/>
<Warning warning={newCardsGreaterThanParent} />
</Item>
<SpinBox
label={tr.schedulingMaximumReviewsday()}
tooltip={tr.deckConfigReviewLimitTooltip() + v3Extra}
min={0}
warnings={[reviewsTooLow]}
defaultValue={defaults.reviewsPerDay}
bind:value={$config.reviewsPerDay}
/>
<Item>
<SpinBoxRow
bind:value={$config.reviewsPerDay}
defaultValue={defaults.reviewsPerDay}
markdownTooltip={tr.deckConfigReviewLimitTooltip() + v3Extra}
>
{tr.schedulingMaximumReviewsday()}
</SpinBoxRow>
<Warning warning={reviewsTooLow} />
</Item>
</TitledContainer>

View File

@ -4,7 +4,18 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import ConfigSelector from "./ConfigSelector.svelte";
import ConfigEditor from "./ConfigEditor.svelte";
import Container from "components/Container.svelte";
import Item from "components/Item.svelte";
import DailyLimits from "./DailyLimits.svelte";
import DisplayOrder from "./DisplayOrder.svelte";
import NewOptions from "./NewOptions.svelte";
import AdvancedOptions from "./AdvancedOptions.svelte";
import BuryOptions from "./BuryOptions.svelte";
import LapseOptions from "./LapseOptions.svelte";
import TimerOptions from "./TimerOptions.svelte";
import AudioOptions from "./AudioOptions.svelte";
import Addons from "./Addons.svelte";
import type { DeckOptionsState } from "./lib";
import type { Writable } from "svelte/store";
import HtmlAddon from "./HtmlAddon.svelte";
@ -31,7 +42,52 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
},
];
}
export const options = {};
export const dailyLimits = {};
export const newOptions = {};
export const lapseOptions = {};
export const buryOptions = {};
export const displayOrder = {};
export const timerOptions = {};
export const audioOptions = {};
export const addonOptions = {};
export const advancedOptions = {};
</script>
<ConfigSelector {state} />
<ConfigEditor {state} />
<Container api={options} class="g-1">
<Item>
<DailyLimits {state} api={dailyLimits} />
</Item>
<Item>
<NewOptions {state} api={newOptions} />
</Item>
<Item>
<LapseOptions {state} api={lapseOptions} />
</Item>
<Item>
<BuryOptions {state} api={buryOptions} />
</Item>
{#if state.v3Scheduler}
<Item>
<DisplayOrder {state} api={displayOrder} />
</Item>
{/if}
<Item>
<TimerOptions {state} api={timerOptions} />
</Item>
<Item>
<AudioOptions {state} api={audioOptions} />
</Item>
<Item>
<Addons {state} api={addonOptions} />
</Item>
<Item>
<AdvancedOptions {state} api={advancedOptions} />
</Item>
</Container>

View File

@ -4,12 +4,16 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import * as tr from "lib/i18n";
import EnumSelector from "./EnumSelector.svelte";
import TitledContainer from "./TitledContainer.svelte";
import Item from "components/Item.svelte";
import EnumSelectorRow from "./EnumSelectorRow.svelte";
import type { DeckOptionsState } from "./lib";
import { reviewMixChoices } from "./strings";
export let state: DeckOptionsState;
export let api: Record<string, never>;
let config = state.currentConfig;
let defaults = state.defaults;
@ -35,44 +39,59 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
];
</script>
<h2>{tr.deckConfigOrderingTitle()}</h2>
<TitledContainer title={tr.deckConfigOrderingTitle()} {api}>
<Item>
<EnumSelectorRow
bind:value={$config.newCardGatherPriority}
defaultValue={defaults.newCardGatherPriority}
choices={newGatherPriorityChoices}
markdownTooltip={tr.deckConfigNewGatherPriorityTooltip()}
>
{tr.deckConfigNewGatherPriority()}
</EnumSelectorRow>
</Item>
<EnumSelector
label={tr.deckConfigNewGatherPriority()}
tooltip={tr.deckConfigNewGatherPriorityTooltip()}
choices={newGatherPriorityChoices}
defaultValue={defaults.newCardGatherPriority}
bind:value={$config.newCardGatherPriority}
/>
<Item>
<EnumSelectorRow
bind:value={$config.newCardSortOrder}
defaultValue={defaults.newCardSortOrder}
choices={newSortOrderChoices}
markdownTooltip={tr.deckConfigNewCardSortOrderTooltip()}
>
{tr.deckConfigNewCardSortOrder()}
</EnumSelectorRow>
</Item>
<EnumSelector
label={tr.deckConfigNewCardSortOrder()}
tooltip={tr.deckConfigNewCardSortOrderTooltip()}
choices={newSortOrderChoices}
defaultValue={defaults.newCardSortOrder}
bind:value={$config.newCardSortOrder}
/>
<Item>
<EnumSelectorRow
bind:value={$config.newMix}
defaultValue={defaults.newMix}
choices={reviewMixChoices()}
markdownTooltip={tr.deckConfigNewReviewPriorityTooltip()}
>
{tr.deckConfigNewReviewPriority()}
</EnumSelectorRow>
</Item>
<EnumSelector
label={tr.deckConfigNewReviewPriority()}
tooltip={tr.deckConfigNewReviewPriorityTooltip()}
choices={reviewMixChoices()}
defaultValue={defaults.newMix}
bind:value={$config.newMix}
/>
<Item>
<EnumSelectorRow
bind:value={$config.interdayLearningMix}
defaultValue={defaults.interdayLearningMix}
choices={reviewMixChoices()}
markdownTooltip={tr.deckConfigInterdayStepPriorityTooltip()}
>
{tr.deckConfigInterdayStepPriority()}
</EnumSelectorRow>
</Item>
<EnumSelector
label={tr.deckConfigInterdayStepPriority()}
tooltip={tr.deckConfigInterdayStepPriorityTooltip()}
choices={reviewMixChoices()}
defaultValue={defaults.interdayLearningMix}
bind:value={$config.interdayLearningMix}
/>
<EnumSelector
label={tr.deckConfigReviewSortOrder()}
tooltip={tr.deckConfigReviewSortOrderTooltip()}
choices={reviewOrderChoices}
defaultValue={defaults.reviewOrder}
bind:value={$config.reviewOrder}
/>
<Item>
<EnumSelectorRow
bind:value={$config.reviewOrder}
defaultValue={defaults.reviewOrder}
choices={reviewOrderChoices}
markdownTooltip={tr.deckConfigReviewSortOrderTooltip()}
>
{tr.deckConfigReviewSortOrder()}
</EnumSelectorRow>
</Item>
</TitledContainer>

View File

@ -3,19 +3,36 @@ Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import ConfigEntry from "./ConfigEntry.svelte";
import { getContext } from "svelte";
import { nightModeKey } from "components/contextKeys";
export let label: string;
export let choices: string[];
export let value: number = 0;
export let defaultValue: number;
export let tooltip = "";
const nightMode = getContext<boolean>(nightModeKey);
</script>
<ConfigEntry {label} {tooltip} wholeLine={true} bind:value {defaultValue}>
<select bind:value class="form-select">
{#each choices as choice, idx}
<option value={idx}>{choice}</option>
{/each}
</select>
</ConfigEntry>
<select
bind:value
class:nightMode
class:visible-down-arrow={nightMode}
class="form-select"
>
{#each choices as choice, idx}
<option value={idx}>{choice}</option>
{/each}
</select>
<style lang="scss">
@use "ts/sass/night_mode" as nightmode;
@use "ts/sass/button_mixins" as button;
.nightMode {
@include nightmode.input;
}
.visible-down-arrow {
/* override the default down arrow */
background-image: button.down-arrow(white);
}
</style>

View File

@ -0,0 +1,29 @@
<!--
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 { Breakpoint } from "./col";
import Row from "./Row.svelte";
import Col from "./Col.svelte";
import TooltipLabel from "./TooltipLabel.svelte";
import EnumSelector from "./EnumSelector.svelte";
import RevertButton from "./RevertButton.svelte";
export let value: number;
export let defaultValue: number;
export let breakpoint: Breakpoint = "md";
export let choices: string[];
export let markdownTooltip: string;
</script>
<Row>
<Col size={7}>
<RevertButton bind:value {defaultValue} />
<TooltipLabel {markdownTooltip}><slot /></TooltipLabel>
</Col>
<Col {breakpoint} size={5}>
<EnumSelector bind:value {choices} />
</Col>
</Row>

View File

@ -1,48 +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 * as tr from "lib/i18n";
import SpinBox from "./SpinBox.svelte";
import CheckBox from "./CheckBox.svelte";
import type { DeckOptionsState } from "./lib";
export let state: DeckOptionsState;
let config = state.currentConfig;
let defaults = state.defaults;
</script>
<h2>{tr.deckConfigTimerTitle()}</h2>
<SpinBox
label={tr.deckConfigMaximumAnswerSecs()}
tooltip={tr.deckConfigMaximumAnswerSecsTooltip()}
min={30}
max={600}
defaultValue={defaults.capAnswerTimeToSecs}
bind:value={$config.capAnswerTimeToSecs}
/>
<CheckBox
id="showAnswerTimer"
label={tr.schedulingShowAnswerTimer()}
tooltip={tr.deckConfigShowAnswerTimerTooltip()}
defaultValue={defaults.showTimer}
bind:value={$config.showTimer}
/>
<h2>{tr.deckConfigAudioTitle()}</h2>
<CheckBox
label={tr.deckConfigDisableAutoplay()}
defaultValue={defaults.disableAutoplay}
bind:value={$config.disableAutoplay}
/>
<CheckBox
label={tr.schedulingAlwaysIncludeQuestionSideWhenReplaying()}
tooltip={tr.deckConfigAlwaysIncludeQuestionAudioTooltip()}
defaultValue={defaults.skipQuestionWhenReplayingAnswer}
bind:value={$config.skipQuestionWhenReplayingAnswer}
/>

View File

@ -1,32 +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 { infoCircle } from "./icons";
import { onMount } from "svelte";
import Tooltip from "bootstrap/js/dist/tooltip";
export let html: string;
let ref: HTMLAnchorElement;
onMount(() => {
new Tooltip(ref, {
placement: "bottom",
html: true,
offset: [0, 20],
});
});
</script>
<span bind:this={ref} title={html}>
{@html infoCircle}
</span>
<style>
span :global(svg) {
vertical-align: text-bottom;
opacity: 0.3;
}
</style>

View File

@ -0,0 +1,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 { onMount, createEventDispatcher } from "svelte";
let forId: string;
export { forId as for };
const dispatch = createEventDispatcher();
let spanRef: HTMLSpanElement;
onMount(() => {
dispatch("mount", { span: spanRef });
});
</script>
<label bind:this={spanRef} for={forId}><slot /></label>

View File

@ -4,12 +4,17 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import * as tr from "lib/i18n";
import SpinBox from "./SpinBox.svelte";
import EnumSelector from "./EnumSelector.svelte";
import StepsInput from "./StepsInput.svelte";
import TitledContainer from "./TitledContainer.svelte";
import Item from "components/Item.svelte";
import StepsInputRow from "./StepsInputRow.svelte";
import SpinBoxRow from "./SpinBoxRow.svelte";
import EnumSelectorRow from "./EnumSelectorRow.svelte";
import Warning from "./Warning.svelte";
import type { DeckOptionsState } from "./lib";
export let state: DeckOptionsState;
export let api = {};
let config = state.currentConfig;
let defaults = state.defaults;
@ -27,39 +32,50 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
const leechChoices = [tr.actionsSuspendCard(), tr.schedulingTagOnly()];
</script>
<div>
<h2>{tr.schedulingLapses()}</h2>
<TitledContainer title={tr.schedulingLapses()} {api}>
<Item>
<StepsInputRow
bind:value={$config.relearnSteps}
defaultValue={defaults.relearnSteps}
markdownTooltip={tr.deckConfigRelearningStepsTooltip()}
>
{tr.deckConfigRelearningSteps()}
</StepsInputRow>
</Item>
<StepsInput
label={tr.deckConfigRelearningSteps()}
tooltip={tr.deckConfigRelearningStepsTooltip()}
defaultValue={defaults.relearnSteps}
value={$config.relearnSteps}
on:changed={(evt) => ($config.relearnSteps = evt.detail.value)}
/>
<Item>
<SpinBoxRow
bind:value={$config.minimumLapseInterval}
defaultValue={defaults.minimumLapseInterval}
min={1}
markdownTooltip={tr.deckConfigMinimumIntervalTooltip()}
>
{tr.schedulingMinimumInterval()}
</SpinBoxRow>
<SpinBox
label={tr.schedulingMinimumInterval()}
tooltip={tr.deckConfigMinimumIntervalTooltip()}
warnings={[stepsExceedMinimumInterval]}
min={1}
defaultValue={defaults.minimumLapseInterval}
bind:value={$config.minimumLapseInterval}
/>
<Warning warning={stepsExceedMinimumInterval} />
</Item>
<SpinBox
label={tr.schedulingLeechThreshold()}
tooltip={tr.deckConfigLeechThresholdTooltip()}
min={1}
defaultValue={defaults.leechThreshold}
bind:value={$config.leechThreshold}
/>
<Item>
<SpinBoxRow
bind:value={$config.leechThreshold}
defaultValue={defaults.leechThreshold}
min={1}
markdownTooltip={tr.deckConfigLeechThresholdTooltip()}
>
{tr.schedulingLeechThreshold()}
</SpinBoxRow>
</Item>
<EnumSelector
label={tr.schedulingLeechAction()}
tooltip={tr.deckConfigLeechActionTooltip()}
choices={leechChoices}
defaultValue={defaults.leechAction}
bind:value={$config.leechAction}
/>
</div>
<Item>
<EnumSelectorRow
bind:value={$config.leechAction}
defaultValue={defaults.leechAction}
choices={leechChoices}
breakpoint="sm"
markdownTooltip={tr.deckConfigLeechActionTooltip()}
>
{tr.schedulingLeechAction()}
</EnumSelectorRow>
</Item>
</TitledContainer>

View File

@ -4,12 +4,17 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import * as tr from "lib/i18n";
import SpinBox from "./SpinBox.svelte";
import StepsInput from "./StepsInput.svelte";
import EnumSelector from "./EnumSelector.svelte";
import TitledContainer from "./TitledContainer.svelte";
import Item from "components/Item.svelte";
import StepsInputRow from "./StepsInputRow.svelte";
import SpinBoxRow from "./SpinBoxRow.svelte";
import EnumSelectorRow from "./EnumSelectorRow.svelte";
import Warning from "./Warning.svelte";
import type { DeckOptionsState } from "./lib";
export let state: DeckOptionsState;
export let api = {};
let config = state.currentConfig;
let defaults = state.defaults;
@ -35,36 +40,50 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
: "";
</script>
<h2>{tr.schedulingNewCards()}</h2>
<TitledContainer title={tr.schedulingNewCards()} {api}>
<Item>
<StepsInputRow
bind:value={$config.learnSteps}
defaultValue={defaults.learnSteps}
markdownTooltip={tr.deckConfigLearningStepsTooltip()}
>
{tr.deckConfigLearningSteps()}
</StepsInputRow>
</Item>
<StepsInput
label={tr.deckConfigLearningSteps()}
tooltip={tr.deckConfigLearningStepsTooltip()}
defaultValue={defaults.learnSteps}
value={$config.learnSteps}
on:changed={(evt) => ($config.learnSteps = evt.detail.value)}
/>
<Item>
<SpinBoxRow
bind:value={$config.graduatingIntervalGood}
defaultValue={defaults.graduatingIntervalGood}
markdownTooltip={tr.deckConfigGraduatingIntervalTooltip()}
>
{tr.schedulingGraduatingInterval()}
</SpinBoxRow>
<SpinBox
label={tr.schedulingGraduatingInterval()}
tooltip={tr.deckConfigGraduatingIntervalTooltip()}
warnings={[stepsExceedGraduatingInterval]}
defaultValue={defaults.graduatingIntervalGood}
bind:value={$config.graduatingIntervalGood}
/>
<Warning warning={stepsExceedGraduatingInterval} />
</Item>
<SpinBox
label={tr.schedulingEasyInterval()}
tooltip={tr.deckConfigEasyIntervalTooltip()}
warnings={[goodExceedsEasy]}
defaultValue={defaults.graduatingIntervalEasy}
bind:value={$config.graduatingIntervalEasy}
/>
<Item>
<SpinBoxRow
bind:value={$config.graduatingIntervalEasy}
defaultValue={defaults.graduatingIntervalEasy}
markdownTooltip={tr.deckConfigEasyIntervalTooltip()}
>
{tr.schedulingEasyInterval()}
</SpinBoxRow>
<EnumSelector
label={tr.deckConfigNewInsertionOrder()}
tooltip={tr.deckConfigNewInsertionOrderTooltip()}
choices={newInsertOrderChoices}
defaultValue={defaults.newCardInsertOrder}
bind:value={$config.newCardInsertOrder}
/>
<Warning warning={goodExceedsEasy} />
</Item>
<Item>
<EnumSelectorRow
bind:value={$config.newCardInsertOrder}
defaultValue={defaults.newCardInsertOrder}
choices={newInsertOrderChoices}
breakpoint={"md"}
markdownTooltip={tr.deckConfigNewInsertionOrderTooltip()}
>
{tr.deckConfigNewInsertionOrder()}
</EnumSelectorRow>
</Item>
</TitledContainer>

View File

@ -4,68 +4,84 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import * as tr from "lib/i18n";
import { revertIcon } from "./icons";
import { createEventDispatcher } from "svelte";
import WithDropdownMenu from "components/WithDropdownMenu.svelte";
import DropdownMenu from "components/DropdownMenu.svelte";
import DropdownItem from "components/DropdownItem.svelte";
import Badge from "./Badge.svelte";
import { gearIcon, revertIcon } from "./icons";
import { isEqual as isEqualLodash, cloneDeep } from "lodash-es";
// import { onMount } from "svelte";
// import Tooltip from "bootstrap/js/dist/tooltip";
import { touchDeviceKey } from "components/contextKeys";
import { getContext } from "svelte";
let ref: HTMLDivElement;
type T = unknown;
// fixme: figure out why this breaks halfway down the page
// onMount(() => {
// new Tooltip(ref, {
// placement: "bottom",
// html: true,
// offset: [0, 20],
// });
// });
export let value: T;
export let defaultValue: T;
export let value: any;
export let defaultValue: any;
const dispatch = createEventDispatcher();
function isEqual(a: unknown, b: unknown): boolean {
function isEqual(a: T, b: T): boolean {
if (typeof a === "number" && typeof b === "number") {
// round to .01 precision before comparing,
// so the values coming out of the UI match
// the originals
return isEqualLodash(Math.round(a * 100) / 100, Math.round(b * 100) / 100);
} else {
return isEqualLodash(a, b);
a = Math.round(a * 100) / 100;
b = Math.round(b * 100) / 100;
}
return isEqualLodash(a, b);
}
let modified: boolean;
$: modified = !isEqual(value, defaultValue);
$: className = !modified ? "opacity-25" : "";
const isTouchDevice = getContext<boolean>(touchDeviceKey);
/// This component can be used either with bind:value, or by listening
/// to the revert event.
function revert(): void {
value = cloneDeep(defaultValue);
dispatch("revert", { value });
}
</script>
{#if modified}
<div
class="img-div"
on:click={revert}
bind:this={ref}
title={tr.deckConfigRevertButtonTooltip()}
<WithDropdownMenu
disabled={!modified}
let:createDropdown
let:activateDropdown
let:menuId
>
<Badge
class={`p-1 ${className}`}
on:mount={(event) => createDropdown(event.detail.span)}
on:click={activateDropdown}
>
{@html revertIcon}
</div>
{/if}
{@html gearIcon}
</Badge>
<DropdownMenu id={menuId}>
<DropdownItem
class={`spinner ${isTouchDevice ? "spin-always" : ""}`}
on:click={() => {
revert();
// Otherwise the menu won't close when the item is clicked
// TODO: investigate why this is necessary
activateDropdown();
}}
>
{tr.deckConfigRevertButtonTooltip()}<Badge>{@html revertIcon}</Badge>
</DropdownItem>
</DropdownMenu>
</WithDropdownMenu>
<style lang="scss">
.img-div {
display: flex;
:global(.spinner:hover .badge, .spinner.spin-always .badge) {
animation: spin-animation 1s infinite;
animation-timing-function: linear;
}
:global(svg) {
align-self: center;
opacity: 0.3;
@keyframes -global-spin-animation {
0% {
transform: rotate(360deg);
}
100% {
transform: rotate(0deg);
}
}
</style>

11
ts/deckoptions/Row.svelte Normal file
View File

@ -0,0 +1,11 @@
<!--
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 id: string | undefined = undefined;
</script>
<div {id} class="row gx-0 gy-2 mt-0">
<slot />
</div>

View File

@ -69,7 +69,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<ButtonGroupItem>
<WithDropdownMenu let:createDropdown let:activateDropdown let:menuId>
<LabelButton on:mount={createDropdown} on:click={activateDropdown} />
<LabelButton
on:mount={(event) => createDropdown(event.detail.button)}
on:click={activateDropdown}
/>
<DropdownMenu id={menuId}>
<DropdownItem on:click={() => dispatch("add")}
>{tr.deckConfigAddGroup()}</DropdownItem

View File

@ -3,15 +3,14 @@ Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import ConfigEntry from "./ConfigEntry.svelte";
import { getContext } from "svelte";
import { nightModeKey } from "components/contextKeys";
export let label: string;
export let tooltip: string;
export let value: number;
export let min = 1;
export let max = 9999;
export let warnings: string[] = [];
export let defaultValue: number = 0;
const nightMode = getContext<boolean>(nightModeKey);
function checkMinMax() {
if (value > max) {
@ -22,13 +21,22 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
}
</script>
<ConfigEntry {label} {tooltip} {warnings} bind:value {defaultValue}>
<input
type="number"
{min}
{max}
bind:value
class="form-control"
on:blur={checkMinMax}
/>
</ConfigEntry>
<input
type="number"
pattern="[0-9]*"
inputmode="numeric"
{min}
{max}
bind:value
class="form-control"
class:nightMode
on:blur={checkMinMax}
/>
<style lang="scss">
@use "ts/sass/night_mode" as nightmode;
.nightMode {
@include nightmode.input;
}
</style>

View File

@ -3,41 +3,40 @@ Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import { createEventDispatcher } from "svelte";
import ConfigEntry from "./ConfigEntry.svelte";
import type { NumberValueEvent } from "./events";
import { getContext } from "svelte";
import { nightModeKey } from "components/contextKeys";
export let label: string;
export let tooltip: string;
export let value: number;
export let defaultValue: number;
export let min = 1;
export let max = 9999;
const dispatch = createEventDispatcher();
let stringValue: string;
$: stringValue = value.toFixed(2);
function update(this: HTMLInputElement): void {
dispatch("changed", {
value: Math.min(max, Math.max(min, parseFloat(this.value))),
});
}
const nightMode = getContext<boolean>(nightModeKey);
function revert(evt: NumberValueEvent): void {
dispatch("changed", { value: evt.detail.value });
function update(this: HTMLInputElement): void {
value = Math.min(max, Math.max(min, parseFloat(this.value)));
}
</script>
<ConfigEntry {label} {tooltip} {value} {defaultValue} on:revert={revert}>
<input
type="number"
{min}
{max}
step="0.01"
value={stringValue}
on:blur={update}
class="form-control"
/>
</ConfigEntry>
<input
type="number"
pattern="[0-9]*"
inputmode="decimal"
class="form-control"
class:nightMode
{min}
{max}
step="0.01"
value={stringValue}
on:blur={update}
/>
<style lang="scss">
@use "ts/sass/night_mode" as nightmode;
.nightMode {
@include nightmode.input;
}
</style>

View File

@ -0,0 +1,27 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import Row from "./Row.svelte";
import Col from "./Col.svelte";
import TooltipLabel from "./TooltipLabel.svelte";
import SpinBoxFloat from "./SpinBoxFloat.svelte";
import RevertButton from "./RevertButton.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>
<Col size={7}>
<RevertButton bind:value {defaultValue} />
<TooltipLabel {markdownTooltip}><slot /></TooltipLabel>
</Col>
<Col size={5}>
<SpinBoxFloat bind:value {min} {max} />
</Col>
</Row>

View File

@ -0,0 +1,27 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import Row from "./Row.svelte";
import Col from "./Col.svelte";
import TooltipLabel from "./TooltipLabel.svelte";
import SpinBox from "./SpinBox.svelte";
import RevertButton from "./RevertButton.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>
<Col size={7}>
<RevertButton bind:value {defaultValue} />
<TooltipLabel {markdownTooltip}><slot /></TooltipLabel>
</Col>
<Col size={5}>
<SpinBox bind:value {min} {max} />
</Col>
</Row>

View File

@ -3,40 +3,34 @@ Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import { createEventDispatcher } from "svelte";
import { getContext } from "svelte";
import { nightModeKey } from "components/contextKeys";
import { stepsToString, stringToSteps } from "./steps";
import ConfigEntry from "./ConfigEntry.svelte";
import type { NumberValueEvent } from "./events";
export let label: string;
export let tooltip: string;
export let value: number[];
export let defaultValue: number[];
export let warnings: string[] = [];
const dispatch = createEventDispatcher();
let stringValue: string;
$: stringValue = stepsToString(value);
function update(this: HTMLInputElement): void {
const value = stringToSteps(this.value);
dispatch("changed", { value });
}
const nightMode = getContext<boolean>(nightModeKey);
function revert(evt: NumberValueEvent): void {
dispatch("changed", { value: evt.detail.value });
function update(this: HTMLInputElement): void {
value = stringToSteps(this.value);
}
</script>
<ConfigEntry
{label}
{tooltip}
{value}
{defaultValue}
{warnings}
wholeLine={value.length > 2}
on:revert={revert}
>
<input type="text" value={stringValue} on:blur={update} class="form-control" />
</ConfigEntry>
<input
type="text"
value={stringValue}
class="form-control"
class:nightMode
on:blur={update}
/>
<style lang="scss">
@use "ts/sass/night_mode" as nightmode;
.nightMode {
@include nightmode.input;
}
</style>

View File

@ -0,0 +1,25 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import Row from "./Row.svelte";
import Col from "./Col.svelte";
import TooltipLabel from "./TooltipLabel.svelte";
import StepsInput from "./StepsInput.svelte";
import RevertButton from "./RevertButton.svelte";
export let value: any;
export let defaultValue: any;
export let markdownTooltip: string;
</script>
<Row>
<Col size={7}>
<RevertButton bind:value {defaultValue} />
<TooltipLabel {markdownTooltip}><slot /></TooltipLabel>
</Col>
<Col size={5}>
<StepsInput bind:value />
</Col>
</Row>

View File

@ -0,0 +1,50 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import { getContext } from "svelte";
import { nightModeKey } from "components/contextKeys";
export let id: string | undefined;
export let value: boolean;
export let disabled = false;
const nightMode = getContext<boolean>(nightModeKey);
</script>
<div class="form-check form-switch">
<input
{id}
type="checkbox"
class="form-check-input"
class:nightMode
bind:checked={value}
{disabled}
/>
</div>
<style lang="scss">
.form-switch {
/* bootstrap adds a default 2.5em left pad, which causes */
/* text to wrap prematurely */
padding-left: 0.5em;
}
.form-check-input {
-webkit-appearance: none;
height: 1.6em;
/* otherwise the switch circle shows slightly off-centered */
margin-top: 0;
.form-switch & {
width: 3em;
margin-left: 1.5em;
}
}
.nightMode:not(:checked) {
background-color: var(--frame-bg);
border-color: var(--border);
}
</style>

View File

@ -0,0 +1,30 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import Row from "./Row.svelte";
import Col from "./Col.svelte";
import Label from "./Label.svelte";
import TooltipLabel from "./TooltipLabel.svelte";
import Switch from "./Switch.svelte";
import RevertButton from "./RevertButton.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>
<Col>
<RevertButton bind:value {defaultValue} />
{#if markdownTooltip}<TooltipLabel for={id} {markdownTooltip}
><slot /></TooltipLabel
>{:else}<Label for={id}><slot /></Label>{/if}
</Col>
<Col grow={false}>
<Switch {id} bind:value />
</Col>
</Row>

View File

@ -0,0 +1,42 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import * as tr from "lib/i18n";
import TitledContainer from "./TitledContainer.svelte";
import Item from "components/Item.svelte";
import SpinBoxRow from "./SpinBoxRow.svelte";
import SwitchRow from "./SwitchRow.svelte";
import type { DeckOptionsState } from "./lib";
export let state: DeckOptionsState;
export let api: Record<string, never>;
let config = state.currentConfig;
let defaults = state.defaults;
</script>
<TitledContainer title={tr.deckConfigTimerTitle()} {api}>
<Item>
<SpinBoxRow
bind:value={$config.capAnswerTimeToSecs}
defaultValue={defaults.capAnswerTimeToSecs}
min={30}
max={600}
markdownTooltip={tr.deckConfigMaximumAnswerSecsTooltip()}
>
{tr.deckConfigMaximumAnswerSecs()}
</SpinBoxRow>
</Item>
<Item>
<SwitchRow
bind:value={$config.showTimer}
defaultValue={defaults.showTimer}
markdownTooltip={tr.deckConfigShowAnswerTimerTooltip()}
>
{tr.schedulingShowAnswerTimer()}
</SwitchRow>
</Item>
</TitledContainer>

View File

@ -0,0 +1,24 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import Section from "components/Section.svelte";
export let title: string;
export let api: Record<string, never> | undefined = undefined;
</script>
<div class="container-fluid my-4">
<h1>{title}</h1>
<Section {api}>
<slot />
</Section>
</div>
<style lang="scss">
h1 {
border-bottom: 1px solid var(--medium-border);
}
</style>

View File

@ -0,0 +1,27 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="typescript">
import marked from "marked";
import { infoCircle } from "./icons";
import WithTooltip from "./WithTooltip.svelte";
import Label from "./Label.svelte";
import Badge from "./Badge.svelte";
export let markdownTooltip: string;
let forId: string;
export { forId as for };
</script>
<span>
<Label for={forId}><slot /></Label>
<WithTooltip tooltip={marked(markdownTooltip)} let:createTooltip>
<Badge
class="opacity-50"
on:mount={(event) => createTooltip(event.detail.span)}
>
{@html infoCircle}
</Badge>
</WithTooltip>
</span>

View File

@ -0,0 +1,18 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import { slide } from "svelte/transition";
import Row from "./Row.svelte";
export let warning: string;
</script>
{#if warning}
<Row>
<div class="col-12 alert alert-warning mb-0" in:slide out:slide>
{warning}
</div>
</Row>
{/if}

View File

@ -0,0 +1,42 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import { onDestroy } from "svelte";
import Tooltip from "bootstrap/js/dist/tooltip";
type TriggerType =
| "hover focus"
| "click"
| "hover"
| "focus"
| "manual"
| "click hover"
| "click focus"
| "click hover focus";
export let tooltip: string;
export let trigger: TriggerType = "hover focus";
let tooltipObject: Tooltip;
function createTooltip(element: HTMLElement): void {
element.title = tooltip;
tooltipObject = new Tooltip(element, {
placement: "bottom",
html: true,
offset: [0, 20],
delay: { show: 250, hide: 0 },
trigger,
});
}
onDestroy(() => {
if (tooltipObject) {
tooltipObject.dispose();
}
});
</script>
<slot {createTooltip} {tooltipObject} />

View File

@ -1,4 +1,4 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
export type NumberValueEvent = { detail: { value: number } };
export type Size = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
export type Breakpoint = "xs" | "sm" | "md" | "lg" | "xl";

View File

@ -1,59 +1,46 @@
$tooltip-padding-y: 0.45rem;
$tooltip-padding-x: 0.65rem;
$tooltip-max-width: 300px;
@use "ts/sass/vars";
@use "ts/sass/scrollbar";
@use "ts/sass/bootstrap-dark";
@import "ts/sass/base";
@import "ts/sass/bootstrap/containers";
@import "ts/sass/bootstrap/grid";
@import "ts/sass/bootstrap/dropdown";
@import "ts/sass/bootstrap/forms";
@import "ts/sass/bootstrap/buttons";
@import "ts/sass/bootstrap/button-group";
@import "ts/sass/bootstrap/transitions";
@import "ts/sass/bootstrap/modal";
@import "ts/sass/bootstrap/close";
@import "ts/sass/bootstrap/alert";
@import "ts/sass/bootstrap/tooltip";
@import "ts/sass/bootstrap/badge";
.night-mode {
@include scrollbar.night-mode;
@include bootstrap-dark.night-mode;
}
// the unprefixed version wasn't added until Chrome 81
.form-control,
.form-select {
// the unprefixed version wasn't added until Chrome 81
-webkit-appearance: none;
}
body {
width: min(100vw, 35em);
margin: 0 auto;
// leave some space for rounded screens
margin-bottom: 2em;
}
html {
overflow-x: hidden;
}
#main {
padding: 0.5em;
padding-top: 0;
}
.tooltip-inner {
max-width: 300px;
text-align: left;
p {
margin: 0.8em 0.4em 0.8em 0.8em;
// marked transpiles tooltips into multiple paragraphs
// where trailing <p>s cause a bottom margin
> p:last-child {
display: inline;
}
// the default code color in tooltips is difficult to read; we'll probably
// want to add more of our own styling in the future
code {
color: #ffaaaa;
}
}
// the default code color in tooltips is difficult to read; we'll probably
// want to add more of our own styling in the future
code {
color: #ffaaaa;
}
// override the default down arrow colour in <select> elements
.night-mode select {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23FFFFFF' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");
}

View File

@ -3,7 +3,6 @@
// Import icons from bootstrap
import revertIcon from "./arrow-counterclockwise.svg";
import infoCircle from "./info-circle.svg";
export { revertIcon, infoCircle };
export { default as revertIcon } from "./arrow-counterclockwise.svg";
export { default as infoCircle } from "./info-circle.svg";
export { default as gearIcon } from "./gear.svg";

View File

@ -14,7 +14,7 @@ import SpinBoxFloat from "./SpinBoxFloat.svelte";
import EnumSelector from "./EnumSelector.svelte";
import CheckBox from "./CheckBox.svelte";
import { nightModeKey, modalsKey } from "components/contextKeys";
import { nightModeKey, touchDeviceKey, modalsKey } from "components/contextKeys";
export async function deckOptions(
target: HTMLDivElement,
@ -31,13 +31,16 @@ export async function deckOptions(
}),
]);
const nightMode = checkNightMode();
const context = new Map();
const nightMode = checkNightMode();
context.set(nightModeKey, nightMode);
const modals = new Map();
context.set(modalsKey, modals);
const touchDevice = "ontouchstart" in document.documentElement;
context.set(touchDeviceKey, touchDevice);
const state = new DeckOptionsState(deckId, info);
return new DeckOptionsPage({
target,

View File

@ -33,7 +33,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { isApplePlatform } from "lib/platform";
import StickyBar from "components/StickyBar.svelte";
import ButtonToolbar from "components/ButtonToolbar.svelte";
import ButtonToolbarItem from "components/ButtonToolbarItem.svelte";
import Item from "components/Item.svelte";
import NoteTypeButtons from "./NoteTypeButtons.svelte";
import FormatInlineButtons from "./FormatInlineButtons.svelte";
@ -41,37 +41,37 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import ColorButtons from "./ColorButtons.svelte";
import TemplateButtons from "./TemplateButtons.svelte";
export let size = isApplePlatform() ? 1.6 : 2.0;
export let wrap = true;
export const toolbar = {};
export const notetypeButtons = {};
export const formatInlineButtons = {};
export const formatBlockButtons = {};
export const colorButtons = {};
export const templateButtons = {};
export let size = isApplePlatform() ? 1.6 : 2.0;
export let wrap = true;
</script>
<StickyBar>
<ButtonToolbar {size} {wrap} api={toolbar}>
<ButtonToolbarItem id="notetype">
<Item id="notetype">
<NoteTypeButtons api={notetypeButtons} />
</ButtonToolbarItem>
</Item>
<ButtonToolbarItem id="inlineFormatting">
<Item id="inlineFormatting">
<FormatInlineButtons api={formatInlineButtons} />
</ButtonToolbarItem>
</Item>
<ButtonToolbarItem id="blockFormatting">
<Item id="blockFormatting">
<FormatBlockButtons api={formatBlockButtons} />
</ButtonToolbarItem>
</Item>
<ButtonToolbarItem id="color">
<Item id="color">
<ColorButtons api={colorButtons} />
</ButtonToolbarItem>
</Item>
<ButtonToolbarItem id="template">
<Item id="template">
<TemplateButtons api={templateButtons} />
</ButtonToolbarItem>
</Item>
</ButtonToolbar>
</StickyBar>

View File

@ -10,7 +10,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import ButtonGroupItem from "components/ButtonGroupItem.svelte";
import IconButton from "components/IconButton.svelte";
import ButtonDropdown from "components/ButtonDropdown.svelte";
import ButtonToolbarItem from "components/ButtonToolbarItem.svelte";
import Item from "components/Item.svelte";
import WithDropdownMenu from "components/WithDropdownMenu.svelte";
import OnlyEditable from "./OnlyEditable.svelte";
import CommandIconButton from "./CommandIconButton.svelte";
@ -71,7 +71,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
</OnlyEditable>
<ButtonDropdown id={menuId}>
<ButtonToolbarItem id="justify">
<Item id="justify">
<ButtonGroup>
<ButtonGroupItem>
<CommandIconButton
@ -109,9 +109,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
>
</ButtonGroupItem>
</ButtonGroup>
</ButtonToolbarItem>
</Item>
<ButtonToolbarItem id="indentation">
<Item id="indentation">
<ButtonGroup>
<ButtonGroupItem>
<OnlyEditable let:disabled>
@ -137,7 +137,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
</OnlyEditable>
</ButtonGroupItem>
</ButtonGroup>
</ButtonToolbarItem>
</Item>
</ButtonDropdown>
</WithDropdownMenu>
</ButtonGroupItem>

View File

@ -53,6 +53,14 @@ sass_library(
visibility = ["//visibility:public"],
)
sass_library(
name = "night_mode_lib",
srcs = [
"night_mode.scss",
],
visibility = ["//visibility:public"],
)
exports_files(
["_vars.scss"],
visibility = ["//visibility:public"],

View File

@ -6,6 +6,19 @@ $body-bg: var(--window-bg);
$link-hover-color: var(--link);
$link-hover-decoration: none;
$utilities: (
"opacity": (
property: opacity,
values: (
0: 0,
25: 0.25,
50: 0.5,
75: 0.75,
100: 1,
),
),
);
@import "ts/sass/bootstrap/bootstrap-reboot";
@import "ts/sass/bootstrap/bootstrap-utilities";

View File

@ -116,3 +116,7 @@ $focus-color: $blue;
box-shadow: inset 0 calc(var(--buttons-size) / 15) calc(var(--buttons-size) / 5)
rgba(black, $intensity);
}
@function down-arrow($color) {
@return url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='transparent' stroke='#{$color}' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");
}

11
ts/sass/night_mode.scss Normal file
View File

@ -0,0 +1,11 @@
/* Copyright: Ankitects Pty Ltd and contributors
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
@mixin input {
background-color: var(--frame-bg);
border-color: var(--border);
&:focus {
background-color: var(--window-bg);
}
}