feat: Refine trail category model (#1059)
* feat: advanced trail categories * rename remote_category * fix merge issues * subcategories for plugins mapping * fix refresh * remove vertical trail filter category scrolling * cleanup * add confirm modal for disabling a category * Fix federation issues * fix review findings * redesign category settings page * remove prio badge * fix subcategory badge layout * optimize subcategory settings layout * further settings page layout optimization * fix subcategory icon position * fix * update docs --------- Co-authored-by: Christian Beutel <>
This commit is contained in:
167
web/src/lib/components/trail/trail_bulk_edit_modal.svelte
Normal file
167
web/src/lib/components/trail/trail_bulk_edit_modal.svelte
Normal file
@@ -0,0 +1,167 @@
|
||||
<script module lang="ts">
|
||||
export type TrailBulkEditChanges = {
|
||||
category?: {
|
||||
category: string;
|
||||
subcategory: string;
|
||||
};
|
||||
difficulty?: "easy" | "moderate" | "difficult";
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import Modal from "$lib/components/base/modal.svelte";
|
||||
import Select from "$lib/components/base/select.svelte";
|
||||
import CategoryPicker, {
|
||||
type CategoryPickerSelection,
|
||||
} from "./category_picker.svelte";
|
||||
import { _ } from "svelte-i18n";
|
||||
|
||||
interface Props {
|
||||
selectedCount?: number;
|
||||
initialCategorySelection?: CategoryPickerSelection;
|
||||
initialDifficulty?: TrailBulkEditChanges["difficulty"];
|
||||
onapply?: (changes: TrailBulkEditChanges) => Promise<void> | void;
|
||||
}
|
||||
|
||||
let {
|
||||
selectedCount = 0,
|
||||
initialCategorySelection,
|
||||
initialDifficulty,
|
||||
onapply,
|
||||
}: Props = $props();
|
||||
|
||||
let modal: Modal;
|
||||
let applyCategory = $state(false);
|
||||
let applyDifficulty = $state(false);
|
||||
let categorySelection: CategoryPickerSelection | undefined = $state();
|
||||
let categoryValue = $state("");
|
||||
let difficultyValue: "easy" | "moderate" | "difficult" = $state("easy");
|
||||
let loading = $state(false);
|
||||
|
||||
let canApply = $derived(
|
||||
(applyCategory && categorySelection !== undefined) || applyDifficulty,
|
||||
);
|
||||
|
||||
export function openModal() {
|
||||
applyCategory = false;
|
||||
applyDifficulty = false;
|
||||
categorySelection = initialCategorySelection;
|
||||
categoryValue = categoryValueFromSelection(initialCategorySelection);
|
||||
difficultyValue = initialDifficulty ?? "easy";
|
||||
modal.openModal();
|
||||
}
|
||||
|
||||
function categoryValueFromSelection(selection?: CategoryPickerSelection) {
|
||||
if (!selection) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return selection.subcategory
|
||||
? `subcategory:${selection.subcategory}`
|
||||
: `category:${selection.category}`;
|
||||
}
|
||||
|
||||
function handleCategoryChange(selection: CategoryPickerSelection) {
|
||||
categorySelection = selection;
|
||||
categoryValue = selection.subcategory
|
||||
? `subcategory:${selection.subcategory}`
|
||||
: `category:${selection.category}`;
|
||||
}
|
||||
|
||||
async function apply(closeModal: () => void) {
|
||||
if (!canApply) {
|
||||
return;
|
||||
}
|
||||
|
||||
const changes: TrailBulkEditChanges = {};
|
||||
if (applyCategory) {
|
||||
if (!categorySelection) {
|
||||
return;
|
||||
}
|
||||
changes.category = categorySelection;
|
||||
}
|
||||
if (applyDifficulty) {
|
||||
changes.difficulty = difficultyValue;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
try {
|
||||
await onapply?.(changes);
|
||||
closeModal();
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
id="trail-bulk-edit-modal"
|
||||
title={$_("adjust")}
|
||||
size="md:min-w-sm"
|
||||
bind:this={modal}
|
||||
>
|
||||
{#snippet content()}
|
||||
<p class="text-sm text-gray-500">
|
||||
{$_("bulk-edit-selected-trails", {
|
||||
values: { n: selectedCount },
|
||||
})}
|
||||
</p>
|
||||
|
||||
<div class="space-y-4">
|
||||
<label class="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="h-4 w-4 accent-primary"
|
||||
bind:checked={applyCategory}
|
||||
/>
|
||||
<span class="font-medium">{$_("category")}</span>
|
||||
</label>
|
||||
{#if applyCategory}
|
||||
<CategoryPicker
|
||||
value={categoryValue}
|
||||
label=""
|
||||
loadData
|
||||
fixedDropdown
|
||||
onchange={handleCategoryChange}
|
||||
></CategoryPicker>
|
||||
{/if}
|
||||
|
||||
<label class="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="h-4 w-4 accent-primary"
|
||||
bind:checked={applyDifficulty}
|
||||
/>
|
||||
<span class="font-medium">{$_("difficulty")}</span>
|
||||
</label>
|
||||
{#if applyDifficulty}
|
||||
<Select
|
||||
value={difficultyValue}
|
||||
onchange={(value) => (difficultyValue = value)}
|
||||
items={[
|
||||
{ text: $_("easy"), value: "easy" },
|
||||
{ text: $_("moderate"), value: "moderate" },
|
||||
{ text: $_("difficult"), value: "difficult" },
|
||||
]}
|
||||
></Select>
|
||||
{/if}
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
{#snippet footer({ closeModal })}
|
||||
<div class="flex items-center gap-4">
|
||||
<button class="btn-secondary" type="button" onclick={closeModal}>
|
||||
{$_("cancel")}
|
||||
</button>
|
||||
<button
|
||||
class="btn-primary"
|
||||
class:btn-disabled={!canApply || loading}
|
||||
disabled={!canApply || loading}
|
||||
type="button"
|
||||
onclick={() => apply(closeModal)}
|
||||
>
|
||||
{$_("apply")}
|
||||
</button>
|
||||
</div>
|
||||
{/snippet}
|
||||
</Modal>
|
||||
Reference in New Issue
Block a user