Skip to content

Commit

Permalink
feat: Enhance species configuration UI with advanced editing and acti…
Browse files Browse the repository at this point in the history
…on management

- Refactor species configuration section with improved editing capabilities
- Add support for editing species configuration name and threshold
- Implement custom action menu with edit, add action, and remove options
- Enhance input validation and user experience for species configuration
- Update Alpine.js components to support more flexible species management
- Improve responsiveness and layout of species configuration inputs
  • Loading branch information
tphakala committed Feb 26, 2025
1 parent f61acb5 commit ffdf395
Show file tree
Hide file tree
Showing 6 changed files with 508 additions and 233 deletions.
18 changes: 18 additions & 0 deletions assets/tailwind.css
Original file line number Diff line number Diff line change
Expand Up @@ -2368,6 +2368,15 @@ input.tab:checked + .tab-content,
--alert-bg-mix: var(--fallback-b1,oklch(var(--b1)/1));
}

.badge-neutral {
--tw-border-opacity: 1;
border-color: var(--fallback-n,oklch(var(--n)/var(--tw-border-opacity)));
--tw-bg-opacity: 1;
background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));
--tw-text-opacity: 1;
color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));
}

.badge-primary {
--tw-border-opacity: 1;
border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)));
Expand Down Expand Up @@ -2402,6 +2411,11 @@ input.tab:checked + .tab-content,
color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
}

.badge-outline.badge-neutral {
--tw-text-opacity: 1;
color: var(--fallback-n,oklch(var(--n)/var(--tw-text-opacity)));
}

.badge-outline.badge-primary {
--tw-text-opacity: 1;
color: var(--fallback-p,oklch(var(--p)/var(--tw-text-opacity)));
Expand Down Expand Up @@ -5174,6 +5188,10 @@ html:has(.drawer-toggle:checked) {
width: 6rem;
}

.w-28 {
width: 7rem;
}

.w-3 {
width: 0.75rem;
}
Expand Down
4 changes: 2 additions & 2 deletions views/components/speciesInput.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
class="input input-sm input-bordered w-full">

<datalist id="{{if .inputId}}{{.inputId}}-suggestions{{else}}species-suggestions{{end}}">
<template x-for="suggestion in {{.predictions}}" :key="suggestion">
<option :value="suggestion"></option>
<template x-for="species in {{.predictions}}" :key="species">
<option :value="species"></option>
</template>
</datalist>
</div>
Expand Down
26 changes: 21 additions & 5 deletions views/components/speciesList.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,28 @@
*/}}
<div class="space-y-2">
<template x-for="(item, index) in {{.species}}" :key="index">
<div class="settings-list-item flex items-center justify-between p-2 rounded-md bg-base-200 {{.itemClass}}">
<div class="settings-list-item flex items-center justify-between p-2 rounded-md bg-base-200 {{.itemClass}}"
x-data="{
isEditing: false,
checkEditState() {
{{if .editIndex}}
this.isEditing = {{.editIndex}} === index;
{{else}}
this.isEditing = false;
{{end}}
}
}"
x-init="checkEditState()"
{{if .editIndex}}@edit-update.window="checkEditState()"{{end}}>
<div class="flex-grow">
<!-- Display mode -->
<span x-show="{{if .editIndex}}{{.editIndex}} !== index{{else}}true{{end}}"
<span x-show="!isEditing"
x-text="{{if .customDisplay}}{{.customDisplay}}(item){{else}}item{{end}}"
class="text-sm"></span>

<!-- Edit mode -->
{{if and .editMode .editValue .onSave}}
<input x-show="{{.editIndex}} === index"
<input x-show="isEditing"
x-model="{{.editValue}}"
@keyup.enter="{{.onSave}}"
@keyup.escape="{{.onCancel}}"
Expand All @@ -35,7 +47,11 @@
<div class="flex-shrink-0">
<!-- Actions -->
{{if .actionTemplate}}
<div x-data="{ index: index, item: item, listType: {{if .listType}}{{.listType}}{{else}}null{{end}} }"
<div x-data="{
index: index,
item: item,
listType: {{if .listType}}{{.listType}}{{else}}null{{end}}
}"
@edit-species.window="if($event.detail.index === index && ($event.detail.listType === listType || !$event.detail.listType)) { {{if .onEdit}}{{.onEdit}}{{end}} }"
@remove-species.window="if($event.detail.index === index && ($event.detail.listType === listType || !$event.detail.listType)) { {{.onRemove}} }"
@save-edit-species.window="if($event.detail.index === index && ($event.detail.listType === listType || !$event.detail.listType)) { {{if .onSave}}{{.onSave}}($event){{end}} }"
Expand All @@ -46,7 +62,7 @@
{{else}}
<button type="button"
@click.prevent="{{.onRemove}}"
class="btn btn-xs"
class="btn btn-sm"
aria-label="Remove item">Remove</button>
{{end}}
</div>
Expand Down
207 changes: 167 additions & 40 deletions views/components/speciesListActionMenu.html
Original file line number Diff line number Diff line change
@@ -1,53 +1,180 @@
{{define "speciesListActionMenu"}}
{{/*
Action menu component for species list items
- Provides a dropdown menu with edit and delete options
- Dispatches events for parent components to handle:
- edit-species event with the index of the species to edit
- remove-species event with the index of the species to remove
- Provides a dropdown menu with configurable actions
- Dispatches events for parent components to handle
- Supports custom actions through the menuItems parameter
- Default actions are edit and delete if no custom actions provided
- Includes sophisticated dropdown positioning logic from the elements version
- Supports both numeric indices and string-based keys (species names)
*/}}
<div class="relative" x-data="{ open: false }">
<!-- Menu trigger button with three dots -->
<button type="button"
@click.prevent.stop="open = !open"
class="btn btn-xs btn-ghost btn-circle"
aria-label="Open actions menu">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z" />
</svg>
<div class="dropdown"
x-data="{
open: false,
// Ensure index and editIndex are properly defined with fallbacks
safeIndex: typeof index !== 'undefined' ? index : null,
// Use local context for edit state and check on render
inEditMode() {
return typeof editIndex !== 'undefined' && editIndex === this.safeIndex;
},
notInEditMode() {
return typeof editIndex === 'undefined' || editIndex !== this.safeIndex;
},
updatePosition() {
this.$nextTick(() => {
const menu = this.$refs.menu;
const button = this.$refs.button;
if (!menu || !button) return;
const buttonRect = button.getBoundingClientRect();
const spaceBelow = window.innerHeight - buttonRect.bottom;
const spaceAbove = buttonRect.top;
const menuHeight = menu.offsetHeight;
// Position menu relative to viewport
menu.style.position = 'fixed';
menu.style.zIndex = '50';
// Determine vertical position
if (spaceBelow < menuHeight && spaceAbove > spaceBelow) {
menu.style.bottom = (window.innerHeight - buttonRect.top + 8) + 'px';
menu.style.top = 'auto';
} else {
menu.style.top = (buttonRect.bottom + 8) + 'px';
menu.style.bottom = 'auto';
}
// Always align menu's right edge with button's right edge
menu.style.left = 'auto';
menu.style.right = (window.innerWidth - buttonRect.right) + 'px';
});
}
}"
class="relative">

<!-- Action button - visible in normal mode -->
<button x-show="notInEditMode()"
x-ref="button"
@click="open = !open; if (open) updatePosition()"
type="button"
class="btn btn-ghost btn-sm">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z" />
</svg>
</button>

<!-- Edit mode buttons - Save and Cancel -->
<div x-show="inEditMode()" class="flex space-x-2" x-cloak>
<!-- Save button -->
<button @click.prevent.stop="$dispatch('save-edit-species', { index: safeIndex, listType: listType })"
type="button"
class="btn btn-primary btn-sm">
Save
</button>

<!-- Dropdown menu -->
<div x-show="open"
@click.away="open = false"
@keydown.escape.window="open = false"
class="absolute right-0 mt-2 z-10 w-40 bg-base-100 shadow-lg rounded-md"
x-cloak>
<div class="py-1 rounded-md">
<!-- Edit option -->
<!-- Cancel button -->
<button @click.prevent.stop="$dispatch('cancel-edit-species', { index: safeIndex, listType: listType })"
type="button"
class="btn btn-outline btn-warning btn-sm">
Cancel
</button>
</div>

<!-- Dropdown menu - only visible in normal mode -->
<div x-show="open && notInEditMode()"
x-ref="menu"
@click.away="open = false"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95"
class="fixed menu p-2 shadow-lg bg-base-100 rounded-box w-40 border border-base-300"
x-cloak>

<!-- Custom menu items if provided -->
<template x-if="typeof customMenuItems !== 'undefined' && customMenuItems">
<div class="py-1 rounded-md">
<!-- Edit option for custom species configuration -->
<template x-if="customMenuItems.includes('editConfig')">
<div>
<button type="button"
@click.prevent.stop="$dispatch('edit-species', { index: index }); open = false"
@click.prevent.stop="$dispatch('edit-species-config', { species: item, index: safeIndex }); open = false"
class="w-full text-left px-4 py-2 text-sm hover:bg-base-200">
<span class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
Edit
</span>
<span class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4 mr-2">
<path d="M13.488 2.513a1.75 1.75 0 0 0-2.475 0L6.75 6.774a2.75 2.75 0 0 0-.596.892l-.848 2.047a.75.75 0 0 0 .98.98l2.047-.848a2.75 2.75 0 0 0 .892-.596l4.261-4.262a1.75 1.75 0 0 0 0-2.474Z" />
<path d="M4.75 3.5c-.69 0-1.25.56-1.25 1.25v6.5c0 .69.56 1.25 1.25 1.25h6.5c.69 0 1.25-.56 1.25-1.25V9A.75.75 0 0 1 14 9v2.25A2.75 2.75 0 0 1 11.25 14h-6.5A2.75 2.75 0 0 1 2 11.25v-6.5A2.75 2.75 0 0 1 4.75 2H7a.75.75 0 0 1 0 1.5H4.75Z" />
</svg>
<span>Edit</span>
</span>
</button>

<!-- Delete option -->
<button type="button"
@click.prevent.stop="$dispatch('remove-species', { index: index }); open = false"
class="w-full text-left px-4 py-2 text-sm text-error hover:bg-base-200">
<span class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
Delete
</span>
</div>
</template>

<!-- Add Action button (for species config) -->
<template x-if="customMenuItems.includes('addAction')">
<div>
<button type="button"
@click.prevent.stop="$dispatch('species-add-action', { species: item, index: safeIndex }); open = false"
class="w-full text-left px-4 py-2 text-sm hover:bg-base-200">
<span class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
<span x-text="typeof $root.speciesSettings !== 'undefined' && $root.speciesSettings.Config && $root.speciesSettings.Config[item]?.Actions?.length ? 'Edit Action' : 'Add Action'"></span>
</span>
</button>
</div>
</template>

<!-- Delete/Remove button for custom actions -->
<div>
<button type="button"
@click.prevent.stop="$dispatch('remove-species', { index: safeIndex, listType: listType, species: item }); open = false"
class="w-full text-left px-4 py-2 text-sm hover:bg-base-200">
<span class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4 mr-2">
<path fill-rule="evenodd" d="M5 3.25V4H2.75a.75.75 0 0 0 0 1.5h.3l.815 8.15A1.5 1.5 0 0 0 5.357 15h5.285a1.5 1.5 0 0 0 1.493-1.35l.815-8.15h.3a.75.75 0 0 0 0-1.5H11v-.75A2.25 2.25 0 0 0 8.75 1h-1.5A2.25 2.25 0 0 0 5 3.25Zm2.25-.75a.75.75 0 0 0-.75.75V4h3v-.75a.75.75 0 0 0-.75-.75h-1.5ZM6.05 6a.75.75 0 0 1 .787.713l.275 5.5a.75.75 0 0 1-1.498.075l-.275-5.5A.75.75 0 0 1 6.05 6Zm3.9 0a.75.75 0 0 1 .712.787l-.275 5.5a.75.75 0 0 1-1.498-.075l.275-5.5a.75.75 0 0 1 .786-.711Z" clip-rule="evenodd" />
</svg>
<span>Remove</span>
</span>
</button>
</div>
</div>
</div>
</template>

<!-- Default menu items if no custom items provided -->
<template x-if="typeof customMenuItems === 'undefined' || !customMenuItems">
<div>
<!-- Edit option -->
<a href="#"
class="block px-4 py-2 text-sm hover:bg-base-200"
@click.prevent.stop="open = false; $dispatch('edit-species', { index: safeIndex, listType: listType })">
<div class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4">
<path d="M13.488 2.513a1.75 1.75 0 0 0-2.475 0L6.75 6.774a2.75 2.75 0 0 0-.596.892l-.848 2.047a.75.75 0 0 0 .98.98l2.047-.848a2.75 2.75 0 0 0 .892-.596l4.261-4.262a1.75 1.75 0 0 0 0-2.474Z" />
<path d="M4.75 3.5c-.69 0-1.25.56-1.25 1.25v6.5c0 .69.56 1.25 1.25 1.25h6.5c.69 0 1.25-.56 1.25-1.25V9A.75.75 0 0 1 14 9v2.25A2.75 2.75 0 0 1 11.25 14h-6.5A2.75 2.75 0 0 1 2 11.25v-6.5A2.75 2.75 0 0 1 4.75 2H7a.75.75 0 0 1 0 1.5H4.75Z" />
</svg>
<span>Edit</span>
</div>
</a>

<!-- Remove option -->
<a href="#"
class="block px-4 py-2 text-sm hover:bg-base-200"
@click.prevent.stop="open = false; $dispatch('remove-species', { index: safeIndex, listType: listType })">
<div class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4">
<path fill-rule="evenodd" d="M5 3.25V4H2.75a.75.75 0 0 0 0 1.5h.3l.815 8.15A1.5 1.5 0 0 0 5.357 15h5.285a1.5 1.5 0 0 0 1.493-1.35l.815-8.15h.3a.75.75 0 0 0 0-1.5H11v-.75A2.25 2.25 0 0 0 8.75 1h-1.5A2.25 2.25 0 0 0 5 3.25Zm2.25-.75a.75.75 0 0 0-.75.75V4h3v-.75a.75.75 0 0 0-.75-.75h-1.5ZM6.05 6a.75.75 0 0 1 .787.713l.275 5.5a.75.75 0 0 1-1.498.075l-.275-5.5A.75.75 0 0 1 6.05 6Zm3.9 0a.75.75 0 0 1 .712.787l-.275 5.5a.75.75 0 0 1-1.498-.075l.275-5.5a.75.75 0 0 1 .786-.711Z" clip-rule="evenodd" />
</svg>
<span>Remove</span>
</div>
</a>
</div>
</template>

</div>
</div>
{{end}}
Loading

0 comments on commit ffdf395

Please sign in to comment.