mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
Refactor Select component | Fix CSV import issue (#2227)
* Refactor Select component and implement/update it in various screens * Remove redundant select CSS * Tweak DeckOptionsPage * Fix CSV import layout * Fix save button margin in change notetype screen * Fix sticky header positioning * Remove unused imports * Make StickyHeader sticky instead of fixed
This commit is contained in:
parent
9ecc0ffdc6
commit
9c45a2f7d0
23 changed files with 248 additions and 197 deletions
|
@ -105,8 +105,6 @@ $focus-color: var(--shadow-focus);
|
||||||
|
|
||||||
@mixin select($with-disabled: true) {
|
@mixin select($with-disabled: true) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
|
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
|
@ -57,44 +57,11 @@ samp {
|
||||||
unicode-bidi: normal !important;
|
unicode-bidi: normal !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
[dir="rtl"] {
|
|
||||||
.form-select {
|
|
||||||
/* flip <select>'s arrow */
|
|
||||||
background-position: left 0.75rem center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.form-select:focus {
|
|
||||||
outline: none;
|
|
||||||
border: 1px solid color(border-focus);
|
|
||||||
box-shadow: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.night-mode .form-select:disabled {
|
|
||||||
background-color: color(fg-disabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
.reduced-motion * {
|
.reduced-motion * {
|
||||||
transition: none !important;
|
transition: none !important;
|
||||||
animation: none !important;
|
animation: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
|
||||||
@include button.background;
|
|
||||||
@include button.border-radius;
|
|
||||||
|
|
||||||
cursor: pointer;
|
|
||||||
outline: none;
|
|
||||||
|
|
||||||
&:focus,
|
|
||||||
&.focus {
|
|
||||||
border: 1px solid color(border-focus);
|
|
||||||
}
|
|
||||||
option {
|
|
||||||
background: color(canvas-elevated);
|
|
||||||
color: color(fg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
label,
|
label,
|
||||||
input[type="radio"],
|
input[type="radio"],
|
||||||
input[type="checkbox"] {
|
input[type="checkbox"] {
|
||||||
|
|
|
@ -19,14 +19,14 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
const options = $info.getOldNamesIncludingNothing(ctx);
|
const options = $info.getOldNamesIncludingNothing(ctx);
|
||||||
$: state.setOldIndex(ctx, newIndex, oldIndex);
|
$: state.setOldIndex(ctx, newIndex, oldIndex);
|
||||||
|
$: label = options[oldIndex];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Row --cols={2}>
|
<Row --cols={2}>
|
||||||
<Col --col-size={1}>
|
<Col --col-size={1}>
|
||||||
<!-- svelte-ignore a11y-no-onchange -->
|
<Select bind:value={oldIndex} {label}>
|
||||||
<Select current={options[oldIndex]}>
|
|
||||||
{#each options as name, idx}
|
{#each options as name, idx}
|
||||||
<SelectOption on:select={() => (oldIndex = idx)}>{name}</SelectOption>
|
<SelectOption value={idx}>{name}</SelectOption>
|
||||||
{/each}
|
{/each}
|
||||||
</Select>
|
</Select>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
|
@ -4,7 +4,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Badge from "../components/Badge.svelte";
|
import Badge from "../components/Badge.svelte";
|
||||||
import ButtonGroup from "../components/ButtonGroup.svelte";
|
|
||||||
import ButtonToolbar from "../components/ButtonToolbar.svelte";
|
import ButtonToolbar from "../components/ButtonToolbar.svelte";
|
||||||
import LabelButton from "../components/LabelButton.svelte";
|
import LabelButton from "../components/LabelButton.svelte";
|
||||||
import Select from "../components/Select.svelte";
|
import Select from "../components/Select.svelte";
|
||||||
|
@ -16,13 +15,17 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
export let state: ChangeNotetypeState;
|
export let state: ChangeNotetypeState;
|
||||||
const notetypes = state.notetypes;
|
const notetypes = state.notetypes;
|
||||||
const info = state.info;
|
const info = state.info;
|
||||||
let value: number = 0;
|
|
||||||
|
|
||||||
$: state.setTargetNotetypeIndex(value);
|
let value: number = 0;
|
||||||
$: options = Array.from($notetypes, (notetype) => notetype.name);
|
$: options = Array.from($notetypes, (notetype) => notetype.name);
|
||||||
|
$: label = options[value];
|
||||||
|
|
||||||
|
function blur(e: CustomEvent): void {
|
||||||
|
state.setTargetNotetypeIndex(e.detail.newIdx);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ButtonToolbar class="justify-content-between" size={2.3} wrap={false}>
|
<ButtonToolbar class="justify-content-between" wrap={false}>
|
||||||
<LabelButton disabled={true}>
|
<LabelButton disabled={true}>
|
||||||
{$info.oldNotetypeName}
|
{$info.oldNotetypeName}
|
||||||
</LabelButton>
|
</LabelButton>
|
||||||
|
@ -33,13 +36,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
{@html arrowRightIcon}
|
{@html arrowRightIcon}
|
||||||
{/if}
|
{/if}
|
||||||
</Badge>
|
</Badge>
|
||||||
<ButtonGroup class="flex-grow-1">
|
<Select class="flex-grow-1" bind:value {label} on:change={blur}>
|
||||||
<Select class="flex-grow-1" current={options[value]}>
|
{#each options as option, idx}
|
||||||
{#each options as option, idx}
|
<SelectOption value={idx}>{option}</SelectOption>
|
||||||
<SelectOption on:select={() => (value = idx)}>{option}</SelectOption>
|
{/each}
|
||||||
{/each}
|
</Select>
|
||||||
</Select>
|
|
||||||
</ButtonGroup>
|
|
||||||
|
|
||||||
<SaveButton {state} />
|
<SaveButton {state} />
|
||||||
</ButtonToolbar>
|
</ButtonToolbar>
|
||||||
|
|
|
@ -29,7 +29,15 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
tooltip={getPlatformString(keyCombination)}
|
tooltip={getPlatformString(keyCombination)}
|
||||||
on:click={save}
|
on:click={save}
|
||||||
--border-left-radius="5px"
|
--border-left-radius="5px"
|
||||||
--border-right-radius="5px">{tr.actionsSave()}</LabelButton
|
--border-right-radius="5px"
|
||||||
>
|
>
|
||||||
|
<div class="save">{tr.actionsSave()}</div>
|
||||||
|
</LabelButton>
|
||||||
<Shortcut {keyCombination} on:action={save} />
|
<Shortcut {keyCombination} on:action={save} />
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.save {
|
||||||
|
margin: 0.15rem 0.75rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -3,25 +3,48 @@ Copyright: Ankitects Pty Ltd and contributors
|
||||||
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { createEventDispatcher, setContext } from "svelte";
|
||||||
|
|
||||||
|
import { selectKey } from "./context-keys";
|
||||||
import IconConstrain from "./IconConstrain.svelte";
|
import IconConstrain from "./IconConstrain.svelte";
|
||||||
import { chevronDown } from "./icons";
|
import { chevronDown } from "./icons";
|
||||||
import Popover from "./Popover.svelte";
|
import Popover from "./Popover.svelte";
|
||||||
import WithFloating from "./WithFloating.svelte";
|
import WithFloating from "./WithFloating.svelte";
|
||||||
|
|
||||||
export let id: string | undefined = undefined;
|
export let id: string | undefined = undefined;
|
||||||
|
|
||||||
let className = "";
|
let className = "";
|
||||||
export { className as class };
|
export { className as class };
|
||||||
|
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
export let current: string = "";
|
export let label = "<br>";
|
||||||
|
export let value = 0;
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
function setValue(v: number) {
|
||||||
|
dispatch("change", { oldIdx: value, newIdx: v });
|
||||||
|
value = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
export let element: HTMLElement | undefined = undefined;
|
||||||
|
|
||||||
export let tooltip: string | undefined = undefined;
|
export let tooltip: string | undefined = undefined;
|
||||||
|
|
||||||
const rtl: boolean = window.getComputedStyle(document.body).direction == "rtl";
|
const rtl: boolean = window.getComputedStyle(document.body).direction == "rtl";
|
||||||
export let element: HTMLElement | undefined = undefined;
|
|
||||||
let hover = false;
|
let hover = false;
|
||||||
|
|
||||||
let showFloating = false;
|
let showFloating = false;
|
||||||
let clientWidth: number;
|
let clientWidth: number;
|
||||||
|
|
||||||
|
async function handleKey(e: KeyboardEvent) {
|
||||||
|
if (e.code === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
showFloating = !showFloating;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setContext(selectKey, setValue);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<WithFloating
|
<WithFloating
|
||||||
|
@ -31,6 +54,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
hideArrow
|
hideArrow
|
||||||
inline
|
inline
|
||||||
closeOnInsideClick
|
closeOnInsideClick
|
||||||
|
keepOnKeyup
|
||||||
on:close={() => (showFloating = false)}
|
on:close={() => (showFloating = false)}
|
||||||
let:asReference
|
let:asReference
|
||||||
>
|
>
|
||||||
|
@ -41,7 +65,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
class:hover
|
class:hover
|
||||||
{disabled}
|
{disabled}
|
||||||
title={tooltip}
|
title={tooltip}
|
||||||
tabindex="-1"
|
tabindex="0"
|
||||||
|
on:keypress={handleKey}
|
||||||
on:mouseenter={() => (hover = true)}
|
on:mouseenter={() => (hover = true)}
|
||||||
on:mouseleave={() => (hover = false)}
|
on:mouseleave={() => (hover = false)}
|
||||||
on:click={() => (showFloating = !showFloating)}
|
on:click={() => (showFloating = !showFloating)}
|
||||||
|
@ -49,7 +74,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
use:asReference
|
use:asReference
|
||||||
bind:clientWidth
|
bind:clientWidth
|
||||||
>
|
>
|
||||||
{current}
|
<div class="inner">
|
||||||
|
<div class="label">{@html label}</div>
|
||||||
|
</div>
|
||||||
<div class="chevron">
|
<div class="chevron">
|
||||||
<IconConstrain iconSize={80}>
|
<IconConstrain iconSize={80}>
|
||||||
{@html chevronDown}
|
{@html chevronDown}
|
||||||
|
@ -63,26 +90,42 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@use "sass/button-mixins" as button;
|
@use "sass/button-mixins" as button;
|
||||||
|
|
||||||
|
$padding-inline: 0.5rem;
|
||||||
|
|
||||||
.select-container {
|
.select-container {
|
||||||
@include button.select($with-disabled: false);
|
@include button.select($with-disabled: false);
|
||||||
padding: 0.2rem 2rem 0.2rem 0.75rem;
|
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
height: var(--buttons-size, 100%);
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.inner {
|
||||||
|
flex-grow: 1;
|
||||||
|
position: relative;
|
||||||
|
.label {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: $padding-inline;
|
||||||
|
bottom: 0;
|
||||||
|
left: $padding-inline;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.chevron {
|
.chevron {
|
||||||
position: absolute;
|
height: 100%;
|
||||||
top: 0;
|
align-self: flex-end;
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: auto;
|
|
||||||
border-left: 1px solid var(--border-subtle);
|
border-left: 1px solid var(--border-subtle);
|
||||||
}
|
}
|
||||||
|
|
||||||
:global([dir="rtl"]) {
|
:global([dir="rtl"]) {
|
||||||
.chevron {
|
.chevron {
|
||||||
left: 0;
|
|
||||||
right: auto;
|
|
||||||
border-left: none;
|
border-left: none;
|
||||||
border-right: 1px solid var(--border-subtle);
|
border-right: 1px solid var(--border-subtle);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,22 +3,51 @@ Copyright: Ankitects Pty Ltd and contributors
|
||||||
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from "svelte";
|
import { getContext } from "svelte";
|
||||||
|
|
||||||
|
import { selectKey } from "./context-keys";
|
||||||
import DropdownItem from "./DropdownItem.svelte";
|
import DropdownItem from "./DropdownItem.svelte";
|
||||||
|
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
|
export let value: number;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
let element: HTMLButtonElement;
|
let element: HTMLButtonElement;
|
||||||
|
|
||||||
function onSelect(): void {
|
function handleKey(e: KeyboardEvent) {
|
||||||
if (!disabled) {
|
/* Arrow key navigation */
|
||||||
dispatch("select");
|
switch (e.code) {
|
||||||
|
case "ArrowUp": {
|
||||||
|
const prevSibling = element?.previousElementSibling as HTMLElement;
|
||||||
|
if (prevSibling) {
|
||||||
|
prevSibling.focus();
|
||||||
|
} else {
|
||||||
|
// close popover
|
||||||
|
document.body.click();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "ArrowDown": {
|
||||||
|
const nextSibling = element?.nextElementSibling as HTMLElement;
|
||||||
|
if (nextSibling) {
|
||||||
|
nextSibling.focus();
|
||||||
|
} else {
|
||||||
|
// close popover
|
||||||
|
document.body.click();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const setValue: Function = getContext(selectKey);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DropdownItem {disabled} on:click={onSelect} bind:buttonRef={element}>
|
<DropdownItem
|
||||||
|
{disabled}
|
||||||
|
on:click={() => setValue(value)}
|
||||||
|
on:keydown={handleKey}
|
||||||
|
bind:buttonRef={element}
|
||||||
|
tabbable
|
||||||
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
|
|
|
@ -8,3 +8,4 @@ export const dropdownKey = Symbol("dropdown");
|
||||||
export const modalsKey = Symbol("modals");
|
export const modalsKey = Symbol("modals");
|
||||||
export const floatingKey = Symbol("floating");
|
export const floatingKey = Symbol("floating");
|
||||||
export const overlayKey = Symbol("overlay");
|
export const overlayKey = Symbol("overlay");
|
||||||
|
export const selectKey = Symbol("select");
|
||||||
|
|
|
@ -8,7 +8,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import type Modal from "bootstrap/js/dist/modal";
|
import type Modal from "bootstrap/js/dist/modal";
|
||||||
import { createEventDispatcher, getContext } from "svelte";
|
import { createEventDispatcher, getContext } from "svelte";
|
||||||
|
|
||||||
import ButtonGroup from "../components/ButtonGroup.svelte";
|
|
||||||
import ButtonToolbar from "../components/ButtonToolbar.svelte";
|
import ButtonToolbar from "../components/ButtonToolbar.svelte";
|
||||||
import { modalsKey } from "../components/context-keys";
|
import { modalsKey } from "../components/context-keys";
|
||||||
import Select from "../components/Select.svelte";
|
import Select from "../components/Select.svelte";
|
||||||
|
@ -23,19 +22,18 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
const dispatchPresetChange = () => dispatch("presetchange");
|
const dispatchPresetChange = () => dispatch("presetchange");
|
||||||
|
|
||||||
$: {
|
$: label = configLabel($configList.find((entry) => entry.current)!);
|
||||||
state.setCurrentIndex(value);
|
|
||||||
dispatchPresetChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
$: options = Array.from($configList, (entry) => configLabel(entry));
|
|
||||||
$: value = $configList.find((entry) => entry.current)?.idx || 0;
|
|
||||||
|
|
||||||
function configLabel(entry: ConfigListEntry): string {
|
function configLabel(entry: ConfigListEntry): string {
|
||||||
const count = tr.deckConfigUsedByDecks({ decks: entry.useCount });
|
const count = tr.deckConfigUsedByDecks({ decks: entry.useCount });
|
||||||
return `${entry.name} (${count})`;
|
return `${entry.name} (${count})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function blur(e: CustomEvent): void {
|
||||||
|
state.setCurrentIndex(e.detail.newIdx);
|
||||||
|
dispatchPresetChange();
|
||||||
|
}
|
||||||
|
|
||||||
function onAddConfig(text: string): void {
|
function onAddConfig(text: string): void {
|
||||||
const trimmed = text.trim();
|
const trimmed = text.trim();
|
||||||
if (trimmed.length) {
|
if (trimmed.length) {
|
||||||
|
@ -94,16 +92,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<StickyContainer --gutter-block="0.5rem" --sticky-borders="0 0 1px" breakpoint="sm">
|
<StickyContainer --gutter-block="0.5rem" --sticky-borders="0 0 1px" breakpoint="sm">
|
||||||
<ButtonToolbar class="justify-content-between" size={2.3} wrap={false}>
|
<ButtonToolbar class="justify-content-between flex-grow-1" wrap={false}>
|
||||||
<ButtonGroup class="flex-grow-1">
|
<Select class="flex-grow-1" {label} on:change={blur}>
|
||||||
<Select class="flex-grow-1" current={options[value]}>
|
{#each $configList as entry}
|
||||||
{#each options as option, idx}
|
<SelectOption value={entry.idx}>{configLabel(entry)}</SelectOption>
|
||||||
<SelectOption on:select={() => (value = idx)}
|
{/each}
|
||||||
>{option}
|
</Select>
|
||||||
</SelectOption>
|
|
||||||
{/each}
|
|
||||||
</Select>
|
|
||||||
</ButtonGroup>
|
|
||||||
|
|
||||||
<SaveButton
|
<SaveButton
|
||||||
{state}
|
{state}
|
||||||
|
|
|
@ -9,11 +9,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
export let options: string[] = [];
|
export let options: string[] = [];
|
||||||
export let disabled: number[] = [];
|
export let disabled: number[] = [];
|
||||||
export let value = 0;
|
export let value = 0;
|
||||||
|
|
||||||
|
$: label = options[value];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Select current={options[value]}>
|
<Select bind:value {label}>
|
||||||
{#each options as option, idx}
|
{#each options as option, idx}
|
||||||
<SelectOption disabled={disabled.includes(idx)} on:select={() => (value = idx)}
|
<SelectOption value={idx} disabled={disabled.includes(idx)}
|
||||||
>{option}</SelectOption
|
>{option}</SelectOption
|
||||||
>
|
>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
@ -113,6 +113,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.save {
|
.save {
|
||||||
margin: 0.2rem 0.75rem;
|
margin: 0 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Todo: find more elegant fix for misalignment */
|
||||||
|
:global(.chevron) {
|
||||||
|
height: 100% !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -10,8 +10,7 @@
|
||||||
.setting-title {
|
.setting-title {
|
||||||
cursor: help;
|
cursor: help;
|
||||||
&:hover {
|
&:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline dotted var(--fg-subtle);
|
||||||
text-decoration-style: dashed;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -3,7 +3,6 @@ Copyright: Ankitects Pty Ltd and contributors
|
||||||
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { pageTheme } from "../sveltelib/theme";
|
|
||||||
import { stepsToString, stringToSteps } from "./steps";
|
import { stepsToString, stringToSteps } from "./steps";
|
||||||
|
|
||||||
export let value: number[];
|
export let value: number[];
|
||||||
|
@ -16,18 +15,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<input
|
<input type="text" value={stringValue} on:blur={update} />
|
||||||
type="text"
|
|
||||||
value={stringValue}
|
|
||||||
class="form-control"
|
|
||||||
class:nightMode={$pageTheme.isDark}
|
|
||||||
on:blur={update}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@use "sass/night-mode" as nightmode;
|
input {
|
||||||
|
width: 100%;
|
||||||
.nightMode {
|
|
||||||
@include nightmode.input;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -14,3 +14,8 @@ $carousel-transition: 0.2s;
|
||||||
@import "bootstrap/scss/badge";
|
@import "bootstrap/scss/badge";
|
||||||
@import "sass/bootstrap-forms";
|
@import "sass/bootstrap-forms";
|
||||||
@import "sass/bootstrap-tooltip";
|
@import "sass/bootstrap-tooltip";
|
||||||
|
|
||||||
|
input[type="text"] {
|
||||||
|
padding-inline: 0.5rem;
|
||||||
|
background: var(--canvas-inset);
|
||||||
|
}
|
||||||
|
|
|
@ -8,9 +8,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
import Col from "../components/Col.svelte";
|
import Col from "../components/Col.svelte";
|
||||||
import Row from "../components/Row.svelte";
|
import Row from "../components/Row.svelte";
|
||||||
|
import Select from "../components/Select.svelte";
|
||||||
|
import SelectOption from "../components/SelectOption.svelte";
|
||||||
|
|
||||||
export let deckNameIds: Decks.DeckNameId[];
|
export let deckNameIds: Decks.DeckNameId[];
|
||||||
export let deckId: number;
|
export let deckId: number;
|
||||||
|
|
||||||
|
$: label = deckNameIds.find((d) => d.id === deckId)?.name.replace(/^.+::/, "...");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Row --cols={2}>
|
<Row --cols={2}>
|
||||||
|
@ -18,11 +22,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
{tr.decksDeck()}
|
{tr.decksDeck()}
|
||||||
</Col>
|
</Col>
|
||||||
<Col --col-size={1}>
|
<Col --col-size={1}>
|
||||||
<!-- svelte-ignore a11y-no-onchange -->
|
<Select bind:value={deckId} {label}>
|
||||||
<select class="form-select" bind:value={deckId}>
|
|
||||||
{#each deckNameIds as { id, name }}
|
{#each deckNameIds as { id, name }}
|
||||||
<option value={id}>{name}</option>
|
<SelectOption value={id}>{name}</SelectOption>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</Select>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
@ -8,6 +8,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
import Col from "../components/Col.svelte";
|
import Col from "../components/Col.svelte";
|
||||||
import Row from "../components/Row.svelte";
|
import Row from "../components/Row.svelte";
|
||||||
|
import Select from "../components/Select.svelte";
|
||||||
|
import SelectOption from "../components/SelectOption.svelte";
|
||||||
|
|
||||||
export let delimiter: ImportExport.CsvMetadata.Delimiter;
|
export let delimiter: ImportExport.CsvMetadata.Delimiter;
|
||||||
export let disabled: boolean;
|
export let disabled: boolean;
|
||||||
|
@ -21,6 +23,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
{ value: Delimiter.COMMA, label: tr.importingComma() },
|
{ value: Delimiter.COMMA, label: tr.importingComma() },
|
||||||
{ value: Delimiter.SPACE, label: tr.studyingSpace() },
|
{ value: Delimiter.SPACE, label: tr.studyingSpace() },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$: label = delimiters.find((d) => d.value === delimiter)?.label;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Row --cols={2}>
|
<Row --cols={2}>
|
||||||
|
@ -28,11 +32,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
{tr.importingFieldSeparator()}
|
{tr.importingFieldSeparator()}
|
||||||
</Col>
|
</Col>
|
||||||
<Col --col-size={1}>
|
<Col --col-size={1}>
|
||||||
<!-- svelte-ignore a11y-no-onchange -->
|
<Select bind:value={delimiter} {disabled} {label}>
|
||||||
<select class="form-select" bind:value={delimiter} {disabled}>
|
|
||||||
{#each delimiters as { value, label }}
|
{#each delimiters as { value, label }}
|
||||||
<option {value}>{label}</option>
|
<SelectOption {value}>{label}</SelectOption>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</Select>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
@ -8,6 +8,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
import Col from "../components/Col.svelte";
|
import Col from "../components/Col.svelte";
|
||||||
import Row from "../components/Row.svelte";
|
import Row from "../components/Row.svelte";
|
||||||
|
import Select from "../components/Select.svelte";
|
||||||
|
import SelectOption from "../components/SelectOption.svelte";
|
||||||
|
|
||||||
export let dupeResolution: ImportExport.CsvMetadata.DupeResolution;
|
export let dupeResolution: ImportExport.CsvMetadata.DupeResolution;
|
||||||
|
|
||||||
|
@ -25,6 +27,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
label: tr.importingPreserve(),
|
label: tr.importingPreserve(),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$: label = dupeResolutions.find((r) => r.value === dupeResolution)?.label;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Row --cols={2}>
|
<Row --cols={2}>
|
||||||
|
@ -32,11 +36,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
{tr.importingExistingNotes()}
|
{tr.importingExistingNotes()}
|
||||||
</Col>
|
</Col>
|
||||||
<Col --col-size={1}>
|
<Col --col-size={1}>
|
||||||
<!-- svelte-ignore a11y-no-onchange -->
|
<Select bind:value={dupeResolution} {label}>
|
||||||
<select class="form-select" bind:value={dupeResolution}>
|
|
||||||
{#each dupeResolutions as { label, value }}
|
{#each dupeResolutions as { label, value }}
|
||||||
<option {value}>{label}</option>
|
<SelectOption {value}>{label}</SelectOption>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</Select>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
@ -20,7 +20,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import { getColumnOptions, getCsvMetadata } from "./lib";
|
import { getColumnOptions, getCsvMetadata } from "./lib";
|
||||||
import NotetypeSelector from "./NotetypeSelector.svelte";
|
import NotetypeSelector from "./NotetypeSelector.svelte";
|
||||||
import Preview from "./Preview.svelte";
|
import Preview from "./Preview.svelte";
|
||||||
import StickyFooter from "./StickyFooter.svelte";
|
import StickyHeader from "./StickyHeader.svelte";
|
||||||
import Tags from "./Tags.svelte";
|
import Tags from "./Tags.svelte";
|
||||||
|
|
||||||
export let path: string;
|
export let path: string;
|
||||||
|
@ -92,6 +92,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<StickyHeader {path} {onImport} />
|
||||||
|
|
||||||
<Container class="csv-page">
|
<Container class="csv-page">
|
||||||
<Row --cols={2}>
|
<Row --cols={2}>
|
||||||
<Col --col-size={1} breakpoint="md">
|
<Col --col-size={1} breakpoint="md">
|
||||||
|
@ -128,7 +130,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
</Container>
|
</Container>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<StickyFooter {path} {onImport} />
|
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@ -138,9 +139,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
:global(.row) {
|
:global(.row) {
|
||||||
// rows have negative margins by default
|
// rows have negative margins by default
|
||||||
--bs-gutter-x: 0;
|
--bs-gutter-x: 0;
|
||||||
// ensure equal spacing between tall rows like
|
margin-bottom: 0.5rem;
|
||||||
// dropdowns, and short rows like checkboxes
|
|
||||||
min-height: 3em;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -5,23 +5,28 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Col from "../components/Col.svelte";
|
import Col from "../components/Col.svelte";
|
||||||
import Row from "../components/Row.svelte";
|
import Row from "../components/Row.svelte";
|
||||||
|
import Select from "../components/Select.svelte";
|
||||||
|
import SelectOption from "../components/SelectOption.svelte";
|
||||||
import type { ColumnOption } from "./lib";
|
import type { ColumnOption } from "./lib";
|
||||||
|
|
||||||
export let label: string;
|
let rowLabel: string;
|
||||||
|
export { rowLabel as label };
|
||||||
|
|
||||||
export let columnOptions: ColumnOption[];
|
export let columnOptions: ColumnOption[];
|
||||||
export let value: number;
|
export let value: number;
|
||||||
|
|
||||||
|
$: label = columnOptions.find((o) => o.value === value)?.label;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Row --cols={2}>
|
<Row --cols={2}>
|
||||||
<Col --col-size={1}>
|
<Col --col-size={1}>
|
||||||
{label}
|
{rowLabel}
|
||||||
</Col>
|
</Col>
|
||||||
<Col --col-size={1}>
|
<Col --col-size={1}>
|
||||||
<!-- svelte-ignore a11y-no-onchange -->
|
<Select bind:value {label}>
|
||||||
<select class="form-select" bind:value>
|
|
||||||
{#each columnOptions as { label, value, disabled }}
|
{#each columnOptions as { label, value, disabled }}
|
||||||
<option {value} {disabled}>{label}</option>
|
<SelectOption {value} {disabled}>{label}</SelectOption>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</Select>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
@ -8,9 +8,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
import Col from "../components/Col.svelte";
|
import Col from "../components/Col.svelte";
|
||||||
import Row from "../components/Row.svelte";
|
import Row from "../components/Row.svelte";
|
||||||
|
import Select from "../components/Select.svelte";
|
||||||
|
import SelectOption from "../components/SelectOption.svelte";
|
||||||
|
|
||||||
export let notetypeNameIds: Notetypes.NotetypeNameId[];
|
export let notetypeNameIds: Notetypes.NotetypeNameId[];
|
||||||
export let notetypeId: number;
|
export let notetypeId: number;
|
||||||
|
|
||||||
|
$: label = notetypeNameIds.find((n) => n.id === notetypeId)?.name;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Row --cols={2}>
|
<Row --cols={2}>
|
||||||
|
@ -18,11 +22,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
{tr.notetypesNotetype()}
|
{tr.notetypesNotetype()}
|
||||||
</Col>
|
</Col>
|
||||||
<Col --col-size={1}>
|
<Col --col-size={1}>
|
||||||
<!-- svelte-ignore a11y-no-onchange -->
|
<Select bind:value={notetypeId} {label}>
|
||||||
<select class="form-select" bind:value={notetypeId}>
|
|
||||||
{#each notetypeNameIds as { id, name }}
|
{#each notetypeNameIds as { id, name }}
|
||||||
<option value={id}>{name}</option>
|
<SelectOption value={id}>{name}</SelectOption>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</Select>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
@ -1,59 +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 "@tslib/ftl";
|
|
||||||
import { getPlatformString } from "@tslib/shortcuts";
|
|
||||||
|
|
||||||
import ButtonGroup from "../components/ButtonGroup.svelte";
|
|
||||||
import Col from "../components/Col.svelte";
|
|
||||||
import LabelButton from "../components/LabelButton.svelte";
|
|
||||||
import Row from "../components/Row.svelte";
|
|
||||||
import Shortcut from "../components/Shortcut.svelte";
|
|
||||||
|
|
||||||
export let path: string;
|
|
||||||
export let onImport: () => void;
|
|
||||||
|
|
||||||
const keyCombination = "Control+Enter";
|
|
||||||
|
|
||||||
function basename(path: String): String {
|
|
||||||
return path.split(/[\\/]/).pop()!;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div style:flex-grow="1" />
|
|
||||||
<div class="sticky-footer">
|
|
||||||
<Row --cols={5}
|
|
||||||
><Col --col-size={4}>{basename(path)}</Col><Col --col-justify="end">
|
|
||||||
<ButtonGroup size={2}>
|
|
||||||
<LabelButton
|
|
||||||
primary
|
|
||||||
tooltip={getPlatformString(keyCombination)}
|
|
||||||
on:click={onImport}
|
|
||||||
--border-left-radius="5px"
|
|
||||||
--border-right-radius="5px">{tr.actionsImport()}</LabelButton
|
|
||||||
>
|
|
||||||
<Shortcut {keyCombination} on:action={onImport} />
|
|
||||||
</ButtonGroup></Col
|
|
||||||
></Row
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.sticky-footer {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
z-index: 10;
|
|
||||||
|
|
||||||
margin: 0;
|
|
||||||
padding: 0.25rem;
|
|
||||||
|
|
||||||
background: var(--canvas);
|
|
||||||
border-style: solid none none;
|
|
||||||
border-color: var(--border);
|
|
||||||
border-width: thin;
|
|
||||||
}
|
|
||||||
</style>
|
|
56
ts/import-csv/StickyHeader.svelte
Normal file
56
ts/import-csv/StickyHeader.svelte
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
<!--
|
||||||
|
Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
import * as tr from "@tslib/ftl";
|
||||||
|
import { getPlatformString } from "@tslib/shortcuts";
|
||||||
|
|
||||||
|
import LabelButton from "../components/LabelButton.svelte";
|
||||||
|
import Shortcut from "../components/Shortcut.svelte";
|
||||||
|
|
||||||
|
export let path: string;
|
||||||
|
export let onImport: () => void;
|
||||||
|
|
||||||
|
const keyCombination = "Control+Enter";
|
||||||
|
|
||||||
|
function basename(path: String): String {
|
||||||
|
return path.split(/[\\/]/).pop()!;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="sticky-header d-flex flex-row justify-content-between">
|
||||||
|
<div class="filename">{basename(path)}</div>
|
||||||
|
<div class="accept">
|
||||||
|
<LabelButton
|
||||||
|
primary
|
||||||
|
tooltip={getPlatformString(keyCombination)}
|
||||||
|
on:click={onImport}
|
||||||
|
--border-left-radius="5px"
|
||||||
|
--border-right-radius="5px"
|
||||||
|
>
|
||||||
|
<div class="import">{tr.actionsImport()}</div>
|
||||||
|
</LabelButton>
|
||||||
|
<Shortcut {keyCombination} on:action={onImport} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.sticky-header {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
|
||||||
|
background: var(--canvas);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
|
||||||
|
.import {
|
||||||
|
margin-inline: 0.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -14,13 +14,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
height: 100vh;
|
||||||
width: min(100vw, 70em);
|
width: min(100vw, 70em);
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 1em;
|
padding: 0 1em 1em 1em;
|
||||||
// pad out the underside of the footer
|
|
||||||
padding-bottom: 5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue