Use TextInputModal directly from svelte component

* the only important thing is that it is not positioned within elements
  with display: none
* I think we can treat the existence of the modal to be a kind of
  "precondition" that has to be met for the component to be used
This commit is contained in:
Henrik Giesel 2021-05-25 18:54:58 +02:00 committed by Damien Elmes
parent 412091ae60
commit 5cc6fc7d9b
8 changed files with 76 additions and 102 deletions

View file

@ -6,3 +6,4 @@ export const disabledKey = Symbol("disabled");
export const buttonToolbarKey = Symbol("buttonToolbar"); export const buttonToolbarKey = Symbol("buttonToolbar");
export const buttonGroupKey = Symbol("buttonGroup"); export const buttonGroupKey = Symbol("buttonGroup");
export const dropdownKey = Symbol("dropdown"); export const dropdownKey = Symbol("dropdown");
export const modalsKey = Symbol("modals");

View file

@ -61,11 +61,9 @@ ts_library(
"lib.ts", "lib.ts",
"steps.ts", "steps.ts",
"strings.ts", "strings.ts",
"textInputModal.ts",
], ],
module_name = "deckoptions", module_name = "deckoptions",
deps = [ deps = [
"TextInputModal",
"//ts:image_module_support", "//ts:image_module_support",
"//ts/lib", "//ts/lib",
"//ts/lib:backend_proto", "//ts/lib:backend_proto",

View file

@ -6,8 +6,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
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 WithTheming from "components/WithTheming.svelte"; import TextInputModal from "./TextInputModal.svelte";
import StickyBar from "components/StickyBar.svelte"; import StickyBar from "components/StickyBar.svelte";
import WithTheming from "components/WithTheming.svelte";
import ButtonToolbar from "components/ButtonToolbar.svelte"; import ButtonToolbar from "components/ButtonToolbar.svelte";
import ButtonToolbarItem from "components/ButtonToolbarItem.svelte"; import ButtonToolbarItem from "components/ButtonToolbarItem.svelte";
import ButtonGroup from "components/ButtonGroup.svelte"; import ButtonGroup from "components/ButtonGroup.svelte";
@ -20,6 +21,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
export let state: DeckOptionsState; export let state: DeckOptionsState;
let configList = state.configList; let configList = state.configList;
let addModalKey: string;
let renameModalKey: string;
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})`;
@ -28,8 +32,31 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
function blur(event: Event): void { function blur(event: Event): void {
state.setCurrentIndex(parseInt((event.target! as HTMLSelectElement).value)); state.setCurrentIndex(parseInt((event.target! as HTMLSelectElement).value));
} }
function onAddConfig(text: string): void {
const trimmed = text.trim();
if (trimmed.length) {
state.addConfig(trimmed);
}
}
function onRenameConfig(text: string): void {
state.setCurrentName(text);
}
</script> </script>
<TextInputModal
title="Add Config"
prompt="Name"
onOk={onAddConfig}
bind:modalKey={addModalKey} />
<TextInputModal
title="Rename Config"
prompt="Name"
onOk={onRenameConfig}
value={state.getCurrentName()}
bind:modalKey={renameModalKey} />
<StickyBar> <StickyBar>
<WithTheming style="--toolbar-size: 2.3rem; --toolbar-wrap: nowrap"> <WithTheming style="--toolbar-size: 2.3rem; --toolbar-wrap: nowrap">
<ButtonToolbar class="justify-content-between"> <ButtonToolbar class="justify-content-between">
@ -50,7 +77,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
</ButtonToolbarItem> </ButtonToolbarItem>
<ButtonToolbarItem> <ButtonToolbarItem>
<SaveButton {state} /> <SaveButton {state} {addModalKey} {renameModalKey} />
</ButtonToolbarItem> </ButtonToolbarItem>
</ButtonToolbar> </ButtonToolbar>
</WithTheming> </WithTheming>

View file

@ -41,21 +41,5 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
onDestroy(() => registerCleanup?.()); onDestroy(() => registerCleanup?.());
</script> </script>
<style lang="scss">
.editor {
// without this, the initial viewport can be wrong
overflow-x: hidden;
}
</style>
<ConfigSelector {state} /> <ConfigSelector {state} />
<div>
<div id="modal">
<!-- filled in later-->
</div>
<div class="editor">
<ConfigEditor {state} /> <ConfigEditor {state} />
</div>
</div>

View file

@ -4,8 +4,10 @@ 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 { textInputModal } from "./textInputModal"; import { modalsKey } from "components/contextKeys";
import { getContext } from "svelte";
import type { DeckOptionsState } from "./lib"; import type { DeckOptionsState } from "./lib";
import type Modal from "bootstrap/js/dist/modal";
import ButtonGroup from "components/ButtonGroup.svelte"; import ButtonGroup from "components/ButtonGroup.svelte";
import ButtonGroupItem from "components/ButtonGroupItem.svelte"; import ButtonGroupItem from "components/ButtonGroupItem.svelte";
@ -18,29 +20,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
export let state: DeckOptionsState; export let state: DeckOptionsState;
function addConfig(): void { const modals = getContext<Map<string, Modal>>(modalsKey);
textInputModal({
title: "Add Config",
prompt: "Name:",
onOk: (text: string) => {
const trimmed = text.trim();
if (trimmed.length) {
state.addConfig(trimmed);
}
},
});
}
function renameConfig(): void {
textInputModal({
title: "Rename Config",
prompt: "Name:",
startingValue: state.getCurrentName(),
onOk: (text: string) => {
state.setCurrentName(text);
},
});
}
function removeConfig(): void { function removeConfig(): void {
// show pop-up after dropdown has gone away // show pop-up after dropdown has gone away
@ -67,6 +47,17 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
function save(applyToChildDecks: boolean): void { function save(applyToChildDecks: boolean): void {
state.save(applyToChildDecks); state.save(applyToChildDecks);
} }
export let addModalKey: string;
export let renameModalKey: string;
function showAddModal() {
modals.get(addModalKey)!.show();
}
function showRenameModal() {
modals.get(renameModalKey)!.show();
}
</script> </script>
<ButtonGroup> <ButtonGroup>
@ -78,8 +69,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<WithDropdownMenu let:createDropdown let:activateDropdown let:menuId> <WithDropdownMenu let:createDropdown let:activateDropdown let:menuId>
<LabelButton on:mount={createDropdown} on:click={activateDropdown} /> <LabelButton on:mount={createDropdown} on:click={activateDropdown} />
<DropdownMenu id={menuId}> <DropdownMenu id={menuId}>
<DropdownItem on:click={addConfig}>Add Config</DropdownItem> <DropdownItem on:click={showAddModal}>Add Config</DropdownItem>
<DropdownItem on:click={renameConfig}>Rename Config</DropdownItem> <DropdownItem on:click={showRenameModal}>Rename Config</DropdownItem>
<DropdownItem on:click={removeConfig}>Remove Config</DropdownItem> <DropdownItem on:click={removeConfig}>Remove Config</DropdownItem>
<DropdownDivider /> <DropdownDivider />
<DropdownItem on:click={() => save(true)}> <DropdownItem on:click={() => save(true)}>

View file

@ -7,43 +7,40 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
@typescript-eslint/no-non-null-assertion: "off", @typescript-eslint/no-non-null-assertion: "off",
*/ */
import { onMount, onDestroy, getContext } from "svelte"; import { onMount, onDestroy, getContext } from "svelte";
import { nightModeKey } from "components/contextKeys"; import { nightModeKey, modalsKey } from "components/contextKeys";
import Modal from "bootstrap/js/dist/modal"; import Modal from "bootstrap/js/dist/modal";
export let title: string; export let title: string;
export let prompt: string; export let prompt: string;
export let startingValue = ""; export let value = "";
export let onOk: (text: string) => void; export let onOk: (text: string) => void;
let inputRef: HTMLInputElement; export const modalKey: string = Math.random().toString(36).substring(2);
const modals = getContext<Map<string, Modal>>(modalsKey);
let modalRef: HTMLDivElement;
let modal: Modal; let modal: Modal;
function onShown(): void { let inputRef: HTMLInputElement;
inputRef.focus();
}
function onHidden(): void {
const container = document.getElementById("modal")!;
container.removeChild(container.firstElementChild!);
}
function onOkClicked(): void { function onOkClicked(): void {
onOk(inputRef.value); onOk(inputRef.value);
modal.hide(); modal.hide();
} }
function onShown(): void {
inputRef.focus();
}
onMount(() => { onMount(() => {
const container = document.getElementById("modal")!; modalRef.addEventListener("shown.bs.modal", onShown);
container.addEventListener("shown.bs.modal", onShown); modal = new Modal(modalRef);
container.addEventListener("hidden.bs.modal", onHidden); modals.set(modalKey, modal);
modal = new Modal(container.firstElementChild!, {});
modal.show();
}); });
onDestroy(() => { onDestroy(() => {
const container = document.getElementById("modal")!; modalRef.removeEventListener("shown.bs.modal", onShown);
container.removeEventListener("shown.bs.modal", onShown);
container.removeEventListener("hidden.bs.modal", onHidden);
}); });
const nightMode = getContext<boolean>(nightModeKey); const nightMode = getContext<boolean>(nightModeKey);
@ -60,7 +57,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
} }
</style> </style>
<div class="modal fade" tabindex="-1" aria-labelledby="modalLabel" aria-hidden="true"> <div
bind:this={modalRef}
class="modal fade"
tabindex="-1"
aria-labelledby="modalLabel"
aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content" class:default-colors={nightMode}> <div class="modal-content" class:default-colors={nightMode}>
<div class="modal-header"> <div class="modal-header">
@ -77,13 +79,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<div class="mb-3"> <div class="mb-3">
<label <label
for="prompt-input" for="prompt-input"
class="col-form-label">{prompt}</label> class="col-form-label">{prompt}:</label>
<input <input
id="prompt-input" id="prompt-input"
bind:this={inputRef} bind:this={inputRef}
type="text" type="text"
class="form-control" class="form-control"
value={startingValue} /> bind:value />
</div> </div>
</form> </form>
</div> </div>

View file

@ -14,7 +14,7 @@ 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"; import { nightModeKey, modalsKey } from "components/contextKeys";
export async function deckOptions( export async function deckOptions(
target: HTMLDivElement, target: HTMLDivElement,
@ -35,6 +35,9 @@ export async function deckOptions(
const context = new Map(); const context = new Map();
context.set(nightModeKey, nightMode); context.set(nightModeKey, nightMode);
const modals = new Map();
context.set(modalsKey, modals);
const state = new DeckOptionsState(deckId, info); const state = new DeckOptionsState(deckId, info);
return new DeckOptionsPage({ return new DeckOptionsPage({
target, target,

View file

@ -1,32 +0,0 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
/* eslint
@typescript-eslint/no-non-null-assertion: "off",
*/
import TextInputModal from "./TextInputModal.svelte";
import { checkNightMode } from "lib/nightmode";
import { nightModeKey } from "components/contextKeys";
export interface TextInputModalProps {
title: string;
prompt: string;
startingValue?: string;
onOk: (text: string) => void;
}
export function textInputModal(props: TextInputModalProps): TextInputModal {
const target = document.getElementById("modal")!;
const nightMode = checkNightMode();
const context = new Map();
context.set(nightModeKey, nightMode);
return new TextInputModal({
target,
props,
context,
} as any);
}