Merge pull request #1207 from hgiesel/deckoptionssections2
Deck Options refactoring
This commit is contained in:
commit
76b005991e
@ -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>
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
||||
|
19
ts/components/Container.svelte
Normal file
19
ts/components/Container.svelte
Normal 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>
|
@ -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 {
|
69
ts/components/Section.svelte
Normal file
69
ts/components/Section.svelte
Normal 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>
|
@ -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>
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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>;
|
||||
}
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
|
38
ts/deckoptions/AudioOptions.svelte
Normal file
38
ts/deckoptions/AudioOptions.svelte
Normal 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>
|
@ -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",
|
||||
|
46
ts/deckoptions/Badge.svelte
Normal file
46
ts/deckoptions/Badge.svelte
Normal 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>
|
@ -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>
|
||||
|
@ -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>
|
||||
|
26
ts/deckoptions/CheckBoxRow.svelte
Normal file
26
ts/deckoptions/CheckBoxRow.svelte
Normal 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
22
ts/deckoptions/Col.svelte
Normal 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>
|
@ -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>
|
@ -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>
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
29
ts/deckoptions/EnumSelectorRow.svelte
Normal file
29
ts/deckoptions/EnumSelectorRow.svelte
Normal 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>
|
@ -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}
|
||||
/>
|
@ -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>
|
20
ts/deckoptions/Label.svelte
Normal file
20
ts/deckoptions/Label.svelte
Normal 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>
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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
11
ts/deckoptions/Row.svelte
Normal 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>
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
27
ts/deckoptions/SpinBoxFloatRow.svelte
Normal file
27
ts/deckoptions/SpinBoxFloatRow.svelte
Normal 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>
|
27
ts/deckoptions/SpinBoxRow.svelte
Normal file
27
ts/deckoptions/SpinBoxRow.svelte
Normal 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>
|
@ -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>
|
||||
|
25
ts/deckoptions/StepsInputRow.svelte
Normal file
25
ts/deckoptions/StepsInputRow.svelte
Normal 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>
|
50
ts/deckoptions/Switch.svelte
Normal file
50
ts/deckoptions/Switch.svelte
Normal 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>
|
30
ts/deckoptions/SwitchRow.svelte
Normal file
30
ts/deckoptions/SwitchRow.svelte
Normal 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>
|
42
ts/deckoptions/TimerOptions.svelte
Normal file
42
ts/deckoptions/TimerOptions.svelte
Normal 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>
|
24
ts/deckoptions/TitledContainer.svelte
Normal file
24
ts/deckoptions/TitledContainer.svelte
Normal 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>
|
27
ts/deckoptions/TooltipLabel.svelte
Normal file
27
ts/deckoptions/TooltipLabel.svelte
Normal 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>
|
18
ts/deckoptions/Warning.svelte
Normal file
18
ts/deckoptions/Warning.svelte
Normal 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}
|
42
ts/deckoptions/WithTooltip.svelte
Normal file
42
ts/deckoptions/WithTooltip.svelte
Normal 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} />
|
@ -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";
|
@ -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");
|
||||
}
|
||||
|
@ -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";
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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"],
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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
11
ts/sass/night_mode.scss
Normal 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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user