add child element ordering
This commit is contained in:
parent
7323925319
commit
62dc119b11
12 changed files with 554 additions and 11 deletions
|
|
@ -38,6 +38,10 @@ interface UpdateElementResponse {
|
|||
element: Element
|
||||
}
|
||||
|
||||
interface ReorderChildElementsResponse {
|
||||
childElements: ChildElement[]
|
||||
}
|
||||
|
||||
interface ElementPatchInput {
|
||||
title?: string
|
||||
description?: string
|
||||
|
|
@ -145,6 +149,34 @@ export const useElementsStore = defineStore('elements', () => {
|
|||
}
|
||||
}
|
||||
|
||||
async function reorderChildElements(
|
||||
parentElementId: string,
|
||||
childElementIds: number[],
|
||||
): Promise<boolean> {
|
||||
childActionError.value = null
|
||||
isManagingChildren.value = true
|
||||
|
||||
try {
|
||||
const encodedParentElementId = encodeURIComponent(parentElementId)
|
||||
const response = await fetch(
|
||||
`${ELEMENTS_URL}/${encodedParentElementId}/children/order`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ childElementIds }),
|
||||
},
|
||||
)
|
||||
|
||||
return await handleReorderChildElementsResponse(response)
|
||||
} catch {
|
||||
childActionError.value = 'Network error - could not save child order'
|
||||
return false
|
||||
} finally {
|
||||
isManagingChildren.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteChildElement(
|
||||
parentElementId: string,
|
||||
childElementId: number,
|
||||
|
|
@ -350,6 +382,32 @@ export const useElementsStore = defineStore('elements', () => {
|
|||
return data.element
|
||||
}
|
||||
|
||||
async function handleReorderChildElementsResponse(
|
||||
response: Response,
|
||||
): Promise<boolean> {
|
||||
if (response.status === 401) {
|
||||
childActionError.value = 'Please log in again'
|
||||
return false
|
||||
}
|
||||
|
||||
if (response.status === 400 || response.status === 404) {
|
||||
childActionError.value = await errorMessage(
|
||||
response,
|
||||
'Could not save child order',
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
childActionError.value = 'Could not save child order'
|
||||
return false
|
||||
}
|
||||
|
||||
const data: ReorderChildElementsResponse = await response.json()
|
||||
childElements.value = data.childElements
|
||||
return true
|
||||
}
|
||||
|
||||
async function handleDeleteChildElementResponse(
|
||||
response: Response,
|
||||
childElementId: number,
|
||||
|
|
@ -406,6 +464,7 @@ export const useElementsStore = defineStore('elements', () => {
|
|||
fetchElement,
|
||||
updateElement,
|
||||
createChildElement,
|
||||
reorderChildElements,
|
||||
deleteChildElement,
|
||||
uploadElementIconImage,
|
||||
uploadElementShortPdf,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,18 @@
|
|||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
import {
|
||||
ChevronDown as ChevronDownIcon,
|
||||
ChevronUp as ChevronUpIcon,
|
||||
} from 'lucide-vue-next'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import ElementSiblingNavigation from '@/components/ElementSiblingNavigation.vue'
|
||||
import RichTextEditor from '@/components/RichTextEditor.vue'
|
||||
import SiteHeader from '@/components/SiteHeader.vue'
|
||||
import { useElementsStore } from '@/stores/elements'
|
||||
|
||||
type ChildMoveDirection = 'up' | 'down'
|
||||
|
||||
interface ElementForm {
|
||||
title: string
|
||||
description: string
|
||||
|
|
@ -168,6 +174,50 @@ async function handleRemoveChild(childElementId: number): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
async function handleMoveChild(
|
||||
childElementId: number,
|
||||
direction: ChildMoveDirection,
|
||||
): Promise<void> {
|
||||
if (elementId.value === '') {
|
||||
return
|
||||
}
|
||||
|
||||
const currentIndex = childElements.value.findIndex((childElement) => {
|
||||
return childElement.id === childElementId
|
||||
})
|
||||
if (currentIndex === -1) {
|
||||
return
|
||||
}
|
||||
|
||||
const targetIndex = direction === 'up' ? currentIndex - 1 : currentIndex + 1
|
||||
if (targetIndex < 0 || targetIndex >= childElements.value.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const reorderedChildElements = [...childElements.value]
|
||||
const movingChildElement = reorderedChildElements[currentIndex]
|
||||
const targetChildElement = reorderedChildElements[targetIndex]
|
||||
if (movingChildElement === undefined || targetChildElement === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
reorderedChildElements[currentIndex] = targetChildElement
|
||||
reorderedChildElements[targetIndex] = movingChildElement
|
||||
const childElementIds = reorderedChildElements.map((childElement) => {
|
||||
return childElement.id
|
||||
})
|
||||
|
||||
childStatus.value = null
|
||||
const saved = await elementsStore.reorderChildElements(
|
||||
elementId.value,
|
||||
childElementIds,
|
||||
)
|
||||
|
||||
if (saved) {
|
||||
childStatus.value = 'Child order saved'
|
||||
}
|
||||
}
|
||||
|
||||
function chooseIconImage(): void {
|
||||
iconImageInput.value?.click()
|
||||
}
|
||||
|
|
@ -522,7 +572,7 @@ function resetInput(changeEvent: Event): void {
|
|||
data-cy="admin-child-list"
|
||||
>
|
||||
<li
|
||||
v-for="childElement in childElements"
|
||||
v-for="(childElement, childIndex) in childElements"
|
||||
:key="childElement.id"
|
||||
class="admin-element-page__child-item"
|
||||
data-cy="admin-child-item"
|
||||
|
|
@ -545,15 +595,44 @@ function resetInput(changeEvent: Event): void {
|
|||
{{ childElement.description }}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
class="admin-element-page__secondary-button"
|
||||
data-cy="admin-remove-child"
|
||||
type="button"
|
||||
:disabled="isManagingChildren"
|
||||
@click="handleRemoveChild(childElement.id)"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
<div class="admin-element-page__child-actions">
|
||||
<div class="admin-element-page__child-order-actions">
|
||||
<button
|
||||
class="admin-element-page__icon-button"
|
||||
data-cy="admin-move-child-up"
|
||||
type="button"
|
||||
:disabled="isManagingChildren || childIndex === 0"
|
||||
aria-label="Move child up"
|
||||
title="Move child up"
|
||||
@click="handleMoveChild(childElement.id, 'up')"
|
||||
>
|
||||
<ChevronUpIcon :size="16" aria-hidden="true" />
|
||||
</button>
|
||||
<button
|
||||
class="admin-element-page__icon-button"
|
||||
data-cy="admin-move-child-down"
|
||||
type="button"
|
||||
:disabled="
|
||||
isManagingChildren ||
|
||||
childIndex === childElements.length - 1
|
||||
"
|
||||
aria-label="Move child down"
|
||||
title="Move child down"
|
||||
@click="handleMoveChild(childElement.id, 'down')"
|
||||
>
|
||||
<ChevronDownIcon :size="16" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
class="admin-element-page__secondary-button"
|
||||
data-cy="admin-remove-child"
|
||||
type="button"
|
||||
:disabled="isManagingChildren"
|
||||
@click="handleRemoveChild(childElement.id)"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<p
|
||||
|
|
@ -778,6 +857,44 @@ function resetInput(changeEvent: Event): void {
|
|||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.admin-element-page__child-actions {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
}
|
||||
|
||||
.admin-element-page__child-order-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.admin-element-page__icon-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
padding: 0;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 4px;
|
||||
color: var(--color-text);
|
||||
background: var(--color-white);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.admin-element-page__icon-button:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.admin-element-page__icon-button:hover:not(:disabled),
|
||||
.admin-element-page__icon-button:focus-visible {
|
||||
border-color: var(--color-slate);
|
||||
color: var(--color-slate);
|
||||
}
|
||||
|
||||
.admin-element-page__child-add {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
|
|
@ -890,5 +1007,9 @@ function resetInput(changeEvent: Event): void {
|
|||
.admin-element-page__child-item {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.admin-element-page__child-actions {
|
||||
align-self: flex-end;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue