417 lines
10 KiB
TypeScript
417 lines
10 KiB
TypeScript
import { ref, type Ref } from 'vue'
|
|
import { defineStore } from 'pinia'
|
|
|
|
export interface ChildElement {
|
|
id: number
|
|
title: string
|
|
description: string
|
|
}
|
|
|
|
export interface Element extends ChildElement {
|
|
iconImageUrl: string | null
|
|
richText: string
|
|
shortPdfPath: string | null
|
|
longPdfPath: string | null
|
|
youtubeUrl: string | null
|
|
}
|
|
|
|
type ElementFileType = 'iconImage' | 'shortPdf' | 'longPdf'
|
|
|
|
interface ElementResponse {
|
|
element: Element
|
|
childElements: ChildElement[]
|
|
siblingElements: ChildElement[]
|
|
}
|
|
|
|
export interface UpdateElementInput {
|
|
title: string
|
|
description: string
|
|
richText: string
|
|
youtubeUrl: string
|
|
}
|
|
|
|
export interface CreateChildElementInput {
|
|
title: string
|
|
}
|
|
|
|
interface UpdateElementResponse {
|
|
element: Element
|
|
}
|
|
|
|
interface ElementPatchInput {
|
|
title?: string
|
|
description?: string
|
|
iconImageUrl?: string
|
|
richText?: string
|
|
shortPdfPath?: string
|
|
longPdfPath?: string
|
|
youtubeUrl?: string
|
|
}
|
|
|
|
interface ErrorResponse {
|
|
error?: string
|
|
}
|
|
|
|
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL as string
|
|
const ELEMENTS_URL = `${API_BASE_URL}/api/elements`
|
|
const ELEMENT_UPDATE_URL = `${API_BASE_URL}/api/element/update`
|
|
|
|
export const useElementsStore = defineStore('elements', () => {
|
|
const element = ref<Element | null>(null)
|
|
const childElements = ref<ChildElement[]>([])
|
|
const siblingElements = ref<ChildElement[]>([])
|
|
const isLoading = ref(false)
|
|
const error = ref<string | null>(null)
|
|
const isSaving = ref(false)
|
|
const isUploadingIconImage = ref(false)
|
|
const isUploadingShortPdf = ref(false)
|
|
const isUploadingLongPdf = ref(false)
|
|
const isManagingChildren = ref(false)
|
|
const saveError = ref<string | null>(null)
|
|
const childActionError = ref<string | null>(null)
|
|
|
|
async function fetchElement(elementId: string): Promise<void> {
|
|
element.value = null
|
|
childElements.value = []
|
|
siblingElements.value = []
|
|
error.value = null
|
|
saveError.value = null
|
|
childActionError.value = null
|
|
isLoading.value = true
|
|
|
|
try {
|
|
const encodedElementId = encodeURIComponent(elementId)
|
|
const elementUrl = `${API_BASE_URL}/api/elements/${encodedElementId}`
|
|
const response = await fetch(elementUrl)
|
|
|
|
if (response.status === 404) {
|
|
error.value = 'Element not found'
|
|
return
|
|
}
|
|
|
|
if (!response.ok) {
|
|
error.value = 'Could not load element'
|
|
return
|
|
}
|
|
|
|
const data: ElementResponse = await response.json()
|
|
element.value = data.element
|
|
childElements.value = data.childElements
|
|
siblingElements.value = data.siblingElements
|
|
} catch {
|
|
childElements.value = []
|
|
siblingElements.value = []
|
|
error.value = 'Network error - could not load element'
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
async function updateElement(
|
|
elementId: string,
|
|
input: UpdateElementInput,
|
|
): Promise<boolean> {
|
|
return await saveElementPatch(elementId, input, 'Could not save element')
|
|
}
|
|
|
|
async function createChildElement(
|
|
parentElementId: string,
|
|
input: CreateChildElementInput,
|
|
): Promise<Element | null> {
|
|
childActionError.value = null
|
|
isManagingChildren.value = true
|
|
|
|
try {
|
|
const encodedParentElementId = encodeURIComponent(parentElementId)
|
|
const response = await fetch(
|
|
`${ELEMENTS_URL}/${encodedParentElementId}/children`,
|
|
{
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'include',
|
|
body: JSON.stringify(input),
|
|
},
|
|
)
|
|
|
|
return await handleChildElementResponse(
|
|
response,
|
|
'Could not add child element',
|
|
)
|
|
} catch {
|
|
childActionError.value = 'Network error - could not add child element'
|
|
return null
|
|
} finally {
|
|
isManagingChildren.value = false
|
|
}
|
|
}
|
|
|
|
async function deleteChildElement(
|
|
parentElementId: string,
|
|
childElementId: number,
|
|
): Promise<boolean> {
|
|
childActionError.value = null
|
|
isManagingChildren.value = true
|
|
|
|
try {
|
|
const encodedParentElementId = encodeURIComponent(parentElementId)
|
|
const encodedChildElementId = encodeURIComponent(
|
|
childElementId.toString(),
|
|
)
|
|
const response = await fetch(
|
|
`${ELEMENTS_URL}/${encodedParentElementId}` +
|
|
`/children/${encodedChildElementId}`,
|
|
{
|
|
method: 'DELETE',
|
|
credentials: 'include',
|
|
},
|
|
)
|
|
|
|
return await handleDeleteChildElementResponse(response, childElementId)
|
|
} catch {
|
|
childActionError.value = 'Network error - could not remove child element'
|
|
return false
|
|
} finally {
|
|
isManagingChildren.value = false
|
|
}
|
|
}
|
|
|
|
async function clearElementIconImage(elementId: string): Promise<boolean> {
|
|
return await saveElementPatch(
|
|
elementId,
|
|
{ iconImageUrl: '' },
|
|
'Could not remove icon image',
|
|
)
|
|
}
|
|
|
|
async function clearElementShortPdf(elementId: string): Promise<boolean> {
|
|
return await saveElementPatch(
|
|
elementId,
|
|
{ shortPdfPath: '' },
|
|
'Could not remove short PDF',
|
|
)
|
|
}
|
|
|
|
async function clearElementLongPdf(elementId: string): Promise<boolean> {
|
|
return await saveElementPatch(
|
|
elementId,
|
|
{ longPdfPath: '' },
|
|
'Could not remove long PDF',
|
|
)
|
|
}
|
|
|
|
async function saveElementPatch(
|
|
elementId: string,
|
|
input: ElementPatchInput,
|
|
failureMessage: string,
|
|
): Promise<boolean> {
|
|
saveError.value = null
|
|
isSaving.value = true
|
|
|
|
try {
|
|
const response = await fetch(ELEMENT_UPDATE_URL, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'include',
|
|
body: JSON.stringify({ elementId, ...input }),
|
|
})
|
|
|
|
return await handleElementResponse(response, failureMessage)
|
|
} catch {
|
|
saveError.value = `Network error - ${failureMessage.toLowerCase()}`
|
|
return false
|
|
} finally {
|
|
isSaving.value = false
|
|
}
|
|
}
|
|
|
|
async function uploadElementIconImage(
|
|
elementId: string,
|
|
file: File,
|
|
): Promise<boolean> {
|
|
return await uploadElementFile(
|
|
elementId,
|
|
file,
|
|
'iconImage',
|
|
isUploadingIconImage,
|
|
'Could not upload icon image',
|
|
'Network error - could not upload icon image',
|
|
)
|
|
}
|
|
|
|
async function uploadElementShortPdf(
|
|
elementId: string,
|
|
file: File,
|
|
): Promise<boolean> {
|
|
return await uploadElementFile(
|
|
elementId,
|
|
file,
|
|
'shortPdf',
|
|
isUploadingShortPdf,
|
|
'Could not upload short PDF',
|
|
'Network error - could not upload short PDF',
|
|
)
|
|
}
|
|
|
|
async function uploadElementLongPdf(
|
|
elementId: string,
|
|
file: File,
|
|
): Promise<boolean> {
|
|
return await uploadElementFile(
|
|
elementId,
|
|
file,
|
|
'longPdf',
|
|
isUploadingLongPdf,
|
|
'Could not upload long PDF',
|
|
'Network error - could not upload long PDF',
|
|
)
|
|
}
|
|
|
|
async function uploadElementFile(
|
|
elementId: string,
|
|
file: File,
|
|
fileType: ElementFileType,
|
|
isUploadingFile: Ref<boolean>,
|
|
failureMessage: string,
|
|
networkErrorMessage: string,
|
|
): Promise<boolean> {
|
|
saveError.value = null
|
|
isUploadingFile.value = true
|
|
|
|
try {
|
|
const formData = new FormData()
|
|
formData.append('elementId', elementId)
|
|
formData.append('fileType', fileType)
|
|
formData.append('file', file)
|
|
const response = await fetch(ELEMENT_UPDATE_URL, {
|
|
method: 'POST',
|
|
headers: { Accept: 'application/json' },
|
|
credentials: 'include',
|
|
body: formData,
|
|
})
|
|
|
|
return await handleElementResponse(response, failureMessage)
|
|
} catch {
|
|
saveError.value = networkErrorMessage
|
|
return false
|
|
} finally {
|
|
isUploadingFile.value = false
|
|
}
|
|
}
|
|
|
|
async function handleElementResponse(
|
|
response: Response,
|
|
failureMessage: string,
|
|
): Promise<boolean> {
|
|
if (response.status === 401) {
|
|
saveError.value = 'Please log in again'
|
|
return false
|
|
}
|
|
|
|
if (response.status === 404) {
|
|
saveError.value = 'Element not found'
|
|
return false
|
|
}
|
|
|
|
if (response.status === 400) {
|
|
saveError.value = await errorMessage(response, failureMessage)
|
|
return false
|
|
}
|
|
|
|
if (!response.ok) {
|
|
saveError.value = failureMessage
|
|
return false
|
|
}
|
|
|
|
const data: UpdateElementResponse = await response.json()
|
|
element.value = data.element
|
|
return true
|
|
}
|
|
|
|
async function handleChildElementResponse(
|
|
response: Response,
|
|
failureMessage: string,
|
|
): Promise<Element | null> {
|
|
if (response.status === 401) {
|
|
childActionError.value = 'Please log in again'
|
|
return null
|
|
}
|
|
|
|
if (response.status === 400 || response.status === 404) {
|
|
childActionError.value = await errorMessage(response, failureMessage)
|
|
return null
|
|
}
|
|
|
|
if (!response.ok) {
|
|
childActionError.value = failureMessage
|
|
return null
|
|
}
|
|
|
|
const data: UpdateElementResponse = await response.json()
|
|
return data.element
|
|
}
|
|
|
|
async function handleDeleteChildElementResponse(
|
|
response: Response,
|
|
childElementId: number,
|
|
): 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 remove child element',
|
|
)
|
|
return false
|
|
}
|
|
|
|
if (!response.ok) {
|
|
childActionError.value = 'Could not remove child element'
|
|
return false
|
|
}
|
|
|
|
childElements.value = childElements.value.filter((childElement) => {
|
|
return childElement.id !== childElementId
|
|
})
|
|
return true
|
|
}
|
|
|
|
async function errorMessage(
|
|
response: Response,
|
|
fallbackMessage: string,
|
|
): Promise<string> {
|
|
try {
|
|
const data: ErrorResponse = await response.json()
|
|
return data.error ?? fallbackMessage
|
|
} catch {
|
|
return fallbackMessage
|
|
}
|
|
}
|
|
|
|
return {
|
|
element,
|
|
childElements,
|
|
siblingElements,
|
|
isLoading,
|
|
error,
|
|
isSaving,
|
|
isUploadingIconImage,
|
|
isUploadingShortPdf,
|
|
isUploadingLongPdf,
|
|
isManagingChildren,
|
|
saveError,
|
|
childActionError,
|
|
fetchElement,
|
|
updateElement,
|
|
createChildElement,
|
|
deleteChildElement,
|
|
uploadElementIconImage,
|
|
uploadElementShortPdf,
|
|
uploadElementLongPdf,
|
|
clearElementIconImage,
|
|
clearElementShortPdf,
|
|
clearElementLongPdf,
|
|
}
|
|
})
|