Merge pull request #1172 from hgiesel/deckoptionsdropdown

Deck Options Top Bar
This commit is contained in:
Damien Elmes 2021-05-19 10:41:20 +10:00 committed by GitHub
commit cc13dde909
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 192 additions and 155 deletions

View file

@ -9,7 +9,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import WithTheming from "./WithTheming.svelte"; import WithTheming from "./WithTheming.svelte";
import ButtonToolbar from "./ButtonToolbar.svelte"; import ButtonToolbar from "./ButtonToolbar.svelte";
export let id: string | undefined; export let id: string;
let className = ""; let className = "";
export { className as class }; export { className as class };

View file

@ -0,0 +1,5 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<hr class="dropdown-divider" />

View file

@ -6,7 +6,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { setContext } from "svelte"; import { setContext } from "svelte";
import { dropdownKey } from "./contextKeys"; import { dropdownKey } from "./contextKeys";
export let id: string | undefined; export let id: string;
setContext(dropdownKey, null); setContext(dropdownKey, null);
</script> </script>

View file

@ -11,6 +11,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
export let id: string | undefined = undefined; export let id: string | undefined = undefined;
let className: string = ""; let className: string = "";
export { className as class }; export { className as class };
export let theme = "anki";
function extendClassName(className: string, theme: string): string {
return `btn ${theme !== "anki" ? `btn-${theme}` : ""}${className}`;
}
export let tooltip: string | undefined = undefined; export let tooltip: string | undefined = undefined;
export let active = false; export let active = false;
@ -48,11 +53,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<button <button
bind:this={buttonRef} bind:this={buttonRef}
{id} {id}
class={`btn ${className}`} class={extendClassName(className, theme)}
class:active class:active
class:dropdown-toggle={dropdownProps.dropdown} class:dropdown-toggle={dropdownProps.dropdown}
class:btn-day={!nightMode} class:btn-day={theme === 'anki' && !nightMode}
class:btn-night={nightMode} class:btn-night={theme === 'anki' && nightMode}
title={tooltip} title={tooltip}
{...dropdownProps} {...dropdownProps}
disabled={_disabled} disabled={_disabled}

View file

@ -5,36 +5,29 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<script lang="typescript"> <script lang="typescript">
import type { Readable } from "svelte/store"; import type { Readable } from "svelte/store";
import { onMount, createEventDispatcher, getContext } from "svelte"; import { onMount, createEventDispatcher, getContext } from "svelte";
import { disabledKey } from "./contextKeys"; import { disabledKey, nightModeKey } from "./contextKeys";
import SelectOption from "./SelectOption.svelte";
interface Option { export let id: string | undefined = undefined;
label: string; let className = "";
value: string; export { className as class };
selected?: false;
}
export let id: string; export let tooltip: string | undefined = undefined;
export let className = "";
export let tooltip: string;
function extendClassName(classes: string) {
return `form-select ${classes}`;
}
export let disables = true; export let disables = true;
export let options: Option[];
const nightMode = getContext<boolean>(nightModeKey);
const disabled = getContext<Readable<boolean>>(disabledKey);
$: _disabled = disables && $disabled;
let buttonRef: HTMLSelectElement; let buttonRef: HTMLSelectElement;
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
onMount(() => dispatch("mount", { button: buttonRef })); onMount(() => dispatch("mount", { button: buttonRef }));
const disabled = getContext<Readable<boolean>>(disabledKey);
$: _disabled = disables && $disabled;
</script> </script>
<style lang="scss"> <style lang="scss">
@use "ts/sass/button_mixins" as button;
select { select {
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
@ -47,24 +40,26 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
box-shadow: none; box-shadow: none;
border-radius: 0; border-radius: 0;
&:hover {
background-color: #eee;
}
&:focus { &:focus {
outline: none; outline: none;
} }
} }
@include button.btn-day($with-hover: false);
@include button.btn-night($with-hover: false);
</style> </style>
<!-- svelte-ignore a11y-no-onchange -->
<select <select
tabindex="-1" tabindex="-1"
bind:this={buttonRef} bind:this={buttonRef}
disabled={_disabled} disabled={_disabled}
{id} {id}
class={extendClassName(className)} class={className}
title={tooltip}> class:btn-day={!nightMode}
{#each options as option} class:btn-night={nightMode}
<SelectOption {...option} /> title={tooltip}
{/each} on:change>
<slot />
</select> </select>

View file

@ -3,9 +3,10 @@ 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="typescript"> <script lang="typescript">
export let label: string; export let value: string | undefined = undefined;
export let value: string;
export let selected = false; export let selected = false;
</script> </script>
<option {selected} {value}>{label}</option> <option {value} {selected}>
<slot />
</option>

View file

@ -15,6 +15,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
}); });
const menuId = Math.random().toString(36).substring(2); const menuId = Math.random().toString(36).substring(2);
let dropdown: Dropdown;
function activateDropdown(_event: MouseEvent): void {
dropdown.toggle();
}
/* Normally dropdown and trigger are associated with a /* Normally dropdown and trigger are associated with a
/* common ancestor with .dropdown class */ /* common ancestor with .dropdown class */
@ -31,7 +36,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
if (!menu) { if (!menu) {
console.log(`Could not find menu "${menuId}" for dropdown menu.`); console.log(`Could not find menu "${menuId}" for dropdown menu.`);
} else { } else {
const dropdown = new Dropdown(button); dropdown = new Dropdown(button);
/* Set custom menu without using common element with .dropdown */ /* Set custom menu without using common element with .dropdown */
(dropdown as any)._menu = menu; (dropdown as any)._menu = menu;
@ -39,4 +44,4 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
} }
</script> </script>
<slot {createDropdown} {menuId} /> <slot {createDropdown} {activateDropdown} {menuId} />

View file

@ -4,6 +4,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
--> -->
<script lang="typescript"> <script lang="typescript">
export let id: string | undefined = undefined; export let id: string | undefined = undefined;
let className = "";
export { className as class };
export let style: string; export let style: string;
</script> </script>
@ -13,6 +15,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
} }
</style> </style>
<div {id} {style}> <div {id} class={className} {style}>
<slot /> <slot />
</div> </div>

View file

@ -27,6 +27,7 @@ compile_svelte(
srcs = svelte_files, srcs = svelte_files,
deps = [ deps = [
"//ts/sveltelib", "//ts/sveltelib",
"//ts/components",
"@npm//@types/bootstrap", "@npm//@types/bootstrap",
"@npm//@types/marked", "@npm//@types/marked",
], ],
@ -47,6 +48,7 @@ ts_library(
"DeckOptionsPage", "DeckOptionsPage",
"lib", "lib",
"//ts/lib", "//ts/lib",
"//ts/components",
"@npm//@popperjs", "@npm//@popperjs",
"@npm//svelte2tsx", "@npm//svelte2tsx",
], ],
@ -100,6 +102,8 @@ esbuild(
":base_css", ":base_css",
"//ts/sveltelib", "//ts/sveltelib",
"@npm//marked", "@npm//marked",
"//ts/components",
"//ts/components:svelte_components",
] + svelte_names, ] + svelte_names,
) )
@ -132,6 +136,7 @@ svelte_check(
"@npm//@types/bootstrap", "@npm//@types/bootstrap",
"@npm//@types/lodash-es", "@npm//@types/lodash-es",
"@npm//@types/marked", "@npm//@types/marked",
"//ts/components:svelte_components",
], ],
) )

View file

@ -5,7 +5,17 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<script lang="ts"> <script lang="ts">
import * as tr from "lib/i18n"; import * as tr from "lib/i18n";
import type { DeckOptionsState, ConfigListEntry } from "./lib"; import type { DeckOptionsState, ConfigListEntry } from "./lib";
import OptionsDropdown from "./OptionsDropdown.svelte";
import WithTheming from "components/WithTheming.svelte";
import StickyBar from "components/StickyBar.svelte";
import ButtonToolbar from "components/ButtonToolbar.svelte";
import ButtonToolbarItem from "components/ButtonToolbarItem.svelte";
import ButtonGroup from "components/ButtonGroup.svelte";
import ButtonGroupItem from "components/ButtonGroupItem.svelte";
import SelectButton from "components/SelectButton.svelte";
import SelectOption from "components/SelectOption.svelte";
import SaveButton from "./SaveButton.svelte";
export let state: DeckOptionsState; export let state: DeckOptionsState;
let configList = state.configList; let configList = state.configList;
@ -15,43 +25,35 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
return `${entry.name} (${count})`; return `${entry.name} (${count})`;
} }
function blur(this: HTMLSelectElement) { function blur(event: Event): void {
state.setCurrentIndex(parseInt(this.value)); state.setCurrentIndex(parseInt((event.target! as HTMLSelectElement).value));
} }
</script> </script>
<style lang="scss"> <StickyBar>
.sticky-bar {
position: sticky;
z-index: 1;
top: 0;
color: var(--text-fg);
background: var(--window-bg);
padding-bottom: 0.5em;
padding-top: 0.5em;
}
.selector-grid {
display: grid;
grid-template-columns: 6fr 1fr;
grid-column-gap: 0.5em;
padding-right: 0.5em;
}
</style>
<div class="sticky-bar">
<div>{tr.actionsOptionsFor({ val: state.currentDeck.name })}</div> <div>{tr.actionsOptionsFor({ val: state.currentDeck.name })}</div>
<div class="selector-grid"> <WithTheming style="--toolbar-size: 30px; --toolbar-wrap: nowrap">
<!-- svelte-ignore a11y-no-onchange --> <ButtonToolbar class="justify-content-between">
<select class="form-select" on:change={blur}> <ButtonToolbarItem>
<ButtonGroup class="flex-grow-1">
<ButtonGroupItem>
<SelectButton class="flex-grow-1" on:change={blur}>
{#each $configList as entry} {#each $configList as entry}
<option value={entry.idx} selected={entry.current}> <SelectOption
value={String(entry.idx)}
selected={entry.current}>
{configLabel(entry)} {configLabel(entry)}
</option> </SelectOption>
{/each} {/each}
</select> </SelectButton>
</ButtonGroupItem>
</ButtonGroup>
</ButtonToolbarItem>
<OptionsDropdown {state} /> <ButtonToolbarItem>
</div> <SaveButton {state} />
</div> </ButtonToolbarItem>
</ButtonToolbar>
</WithTheming>
</StickyBar>

View file

@ -48,13 +48,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
} }
</style> </style>
<ConfigSelector {state} />
<div> <div>
<div id="modal"> <div id="modal">
<!-- filled in later--> <!-- filled in later-->
</div> </div>
<ConfigSelector {state} />
<div class="editor"> <div class="editor">
<ConfigEditor {state} /> <ConfigEditor {state} />
</div> </div>

View file

@ -7,6 +7,15 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { textInputModal } from "./textInputModal"; import { textInputModal } from "./textInputModal";
import type { DeckOptionsState } from "./lib"; import type { DeckOptionsState } from "./lib";
import ButtonGroup from "components/ButtonGroup.svelte";
import ButtonGroupItem from "components/ButtonGroupItem.svelte";
import LabelButton from "components/LabelButton.svelte";
import DropdownMenu from "components/DropdownMenu.svelte";
import DropdownItem from "components/DropdownItem.svelte";
import DropdownDivider from "components/DropdownDivider.svelte";
import WithDropdownMenu from "components/WithDropdownMenu.svelte";
export let state: DeckOptionsState; export let state: DeckOptionsState;
function addConfig(): void { function addConfig(): void {
@ -60,38 +69,23 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
} }
</script> </script>
<style> <ButtonGroup>
:global(svg) { <ButtonGroupItem>
vertical-align: text-bottom; <LabelButton theme="primary" on:click={() => save(false)}>Save</LabelButton>
} </ButtonGroupItem>
</style>
<div class="btn-group" dir="ltr"> <ButtonGroupItem>
<button <WithDropdownMenu let:createDropdown let:activateDropdown let:menuId>
type="button" <LabelButton on:mount={createDropdown} on:click={activateDropdown} />
class="btn btn-primary" <DropdownMenu id={menuId}>
on:click={() => save(false)}>Save</button> <DropdownItem on:click={addConfig}>Add Config</DropdownItem>
<button <DropdownItem on:click={renameConfig}>Rename Config</DropdownItem>
type="button" <DropdownItem on:click={removeConfig}>Remove Config</DropdownItem>
class="btn btn-secondary dropdown-toggle dropdown-toggle-split" <DropdownDivider />
data-bs-toggle="dropdown" <DropdownItem on:click={() => save(true)}>
aria-expanded="false"> Save to All Children
<span class="visually-hidden">Toggle Dropdown</span> </DropdownItem>
</button> </DropdownMenu>
<ul class="dropdown-menu"> </WithDropdownMenu>
<li><a class="dropdown-item" href={'#'} on:click={addConfig}>Add Config</a></li> </ButtonGroupItem>
<li> </ButtonGroup>
<a class="dropdown-item" href={'#'} on:click={renameConfig}>Rename Config</a>
</li>
<li>
<a class="dropdown-item" href={'#'} on:click={removeConfig}>Remove Config</a>
</li>
<li>
<hr class="dropdown-divider" />
</li>
<li>
<a class="dropdown-item" href={'#'} on:click={() => save(true)}>Save to All
Children</a>
</li>
</ul>
</div>

View file

@ -1,3 +1,4 @@
@use "ts/sass/vars";
@use "ts/sass/scrollbar"; @use "ts/sass/scrollbar";
@use "ts/sass/bootstrap-dark"; @use "ts/sass/bootstrap-dark";

View file

@ -1,6 +1,10 @@
// Copyright: Ankitects Pty Ltd and contributors // Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
/* eslint
@typescript-eslint/no-explicit-any: "off",
*/
import { getDeckOptionsInfo, DeckOptionsState } from "./lib"; import { getDeckOptionsInfo, DeckOptionsState } from "./lib";
import { setupI18n, ModuleName } from "lib/i18n"; import { setupI18n, ModuleName } from "lib/i18n";
import { checkNightMode } from "lib/nightmode"; import { checkNightMode } from "lib/nightmode";
@ -10,20 +14,33 @@ import SpinBoxFloat from "./SpinBoxFloat.svelte";
import EnumSelector from "./EnumSelector.svelte"; import EnumSelector from "./EnumSelector.svelte";
import CheckBox from "./CheckBox.svelte"; import CheckBox from "./CheckBox.svelte";
import { nightModeKey } from "components/contextKeys";
export async function deckOptions( export async function deckOptions(
target: HTMLDivElement, target: HTMLDivElement,
deckId: number deckId: number
): Promise<DeckOptionsPage> { ): Promise<DeckOptionsPage> {
checkNightMode(); const [info] = await Promise.all([
await setupI18n({ getDeckOptionsInfo(deckId),
modules: [ModuleName.SCHEDULING, ModuleName.ACTIONS, ModuleName.DECK_CONFIG], setupI18n({
}); modules: [
const info = await getDeckOptionsInfo(deckId); ModuleName.SCHEDULING,
ModuleName.ACTIONS,
ModuleName.DECK_CONFIG,
],
}),
]);
const nightMode = checkNightMode();
const context = new Map();
context.set(nightModeKey, nightMode);
const state = new DeckOptionsState(deckId, info); const state = new DeckOptionsState(deckId, info);
return new DeckOptionsPage({ return new DeckOptionsPage({
target, target,
props: { state }, props: { state },
}); context,
} as any);
} }
export const deckConfigComponents = { export const deckConfigComponents = {

View file

@ -153,6 +153,7 @@ prettier_test(
name = "format_check", name = "format_check",
srcs = glob([ srcs = glob([
"*.ts", "*.ts",
"*.svelte",
]), ]),
) )

View file

@ -62,7 +62,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
</ButtonGroupItem> </ButtonGroupItem>
<ButtonGroupItem> <ButtonGroupItem>
<WithDropdownMenu let:menuId let:createDropdown> <WithDropdownMenu let:createDropdown let:menuId>
<IconButton on:mount={createDropdown}> <IconButton on:mount={createDropdown}>
{@html functionIcon} {@html functionIcon}
</IconButton> </IconButton>

View file

@ -20,7 +20,7 @@ export function initToolbar(i18n: Promise<void>): Promise<EditorToolbar> {
toolbarResolve = resolve; toolbarResolve = resolve;
}); });
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () =>
i18n.then(() => { i18n.then(() => {
const target = document.body; const target = document.body;
const anchor = document.getElementById("fields")!; const anchor = document.getElementById("fields")!;
@ -33,8 +33,8 @@ export function initToolbar(i18n: Promise<void>): Promise<EditorToolbar> {
); );
toolbarResolve(new EditorToolbar({ target, anchor, context } as any)); toolbarResolve(new EditorToolbar({ target, anchor, context } as any));
}); })
}); );
return toolbarPromise; return toolbarPromise;
} }

View file

@ -5,8 +5,7 @@
@use 'fusion_vars'; @use 'fusion_vars';
@mixin night-mode { @mixin night-mode {
input, input {
select {
background-color: var(--frame-bg); background-color: var(--frame-bg);
border-color: var(--border); border-color: var(--border);
@ -15,19 +14,6 @@
} }
} }
.dropdown-menu {
background-color: var(--frame-bg);
.dropdown-item {
color: var(--text-fg);
&:hover,
&:active {
background-color: var(--window-bg);
}
}
}
.modal-content { .modal-content {
background-color: var(--window-bg); background-color: var(--window-bg);
color: var(--text-fg); color: var(--text-fg);

View file

@ -17,15 +17,23 @@ $btn-base-color-day: white;
border-color: var(--medium-border) !important; border-color: var(--medium-border) !important;
} }
@mixin btn-day($with-disabled: true, $with-margin: true) { @mixin btn-day(
$with-hover: true,
$with-active: true,
$with-disabled: true,
$with-margin: true
) {
.btn-day { .btn-day {
@include btn-day-base; @include btn-day-base;
@content ($btn-base-color-day); @content ($btn-base-color-day);
@if ($with-hover) {
&:hover { &:hover {
background-color: darken($btn-base-color-day, 8%); background-color: darken($btn-base-color-day, 8%);
} }
}
@if ($with-active) {
&:active, &:active,
&.active { &.active {
@include impressed-shadow(0.25); @include impressed-shadow(0.25);
@ -34,6 +42,7 @@ $btn-base-color-day: white;
&:active.active { &:active.active {
box-shadow: none; box-shadow: none;
} }
}
@if ($with-disabled) { @if ($with-disabled) {
&[disabled] { &[disabled] {
@ -56,16 +65,24 @@ $btn-base-color-night: #666;
border-color: $btn-base-color-night; border-color: $btn-base-color-night;
} }
@mixin btn-night($with-disabled: true, $with-margin: true) { @mixin btn-night(
$with-hover: true,
$with-active: true,
$with-disabled: true,
$with-margin: true
) {
.btn-night { .btn-night {
@include btn-night-base; @include btn-night-base;
@content ($btn-base-color-night); @content ($btn-base-color-night);
@if ($with-hover) {
&:hover { &:hover {
background-color: lighten($btn-base-color-night, 8%); background-color: lighten($btn-base-color-night, 8%);
border-color: lighten($btn-base-color-night, 8%); border-color: lighten($btn-base-color-night, 8%);
} }
}
@if ($with-disabled) {
&:active, &:active,
&.active { &.active {
@include impressed-shadow(0.35); @include impressed-shadow(0.35);
@ -76,6 +93,7 @@ $btn-base-color-night: #666;
box-shadow: none; box-shadow: none;
border-color: $btn-base-color-night; border-color: $btn-base-color-night;
} }
}
@if ($with-disabled) { @if ($with-disabled) {
&[disabled] { &[disabled] {