Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions frontend/.eslintrc-auto-import.json
Original file line number Diff line number Diff line change
Expand Up @@ -248,12 +248,9 @@
"useDevicesList": true,
"useDisplayMedia": true,
"useDocumentVisibility": true,
"useDragDropContext": true,
"useDragDropHandlers": true,
"useDragDropStore": true,
"useDraggable": true,
"useDropZone": true,
"useDropZoneStyling": true,
"useElementBounding": true,
"useElementByPoint": true,
"useElementHover": true,
Expand Down Expand Up @@ -353,7 +350,6 @@
"useSpeechSynthesis": true,
"useStatementConfig": true,
"useStatementDataTypeValidation": true,
"useStatementDropZone": true,
"useStatementEditor": true,
"useStatementValidationDisplay": true,
"useStepper": true,
Expand Down
8 changes: 0 additions & 8 deletions frontend/auto-imports.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,12 +183,9 @@ declare global {
const useDevicesList: typeof import('@vueuse/core')['useDevicesList']
const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia']
const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility']
const useDragDropContext: typeof import('./src/shared/composables/useDragDropContext')['useDragDropContext']
const useDragDropHandlers: typeof import('./src/shared/composables/useDragDropHandlers')['useDragDropHandlers']
const useDragDropStore: typeof import('./src/features/data-processing/stores/drag-drop.store')['useDragDropStore']
const useDraggable: typeof import('@vueuse/core')['useDraggable']
const useDropZone: typeof import('@vueuse/core')['useDropZone']
const useDropZoneStyling: typeof import('./src/shared/composables/useDropZoneStyling')['useDropZoneStyling']
const useElementBounding: typeof import('@vueuse/core')['useElementBounding']
const useElementByPoint: typeof import('@vueuse/core')['useElementByPoint']
const useElementHover: typeof import('@vueuse/core')['useElementHover']
Expand Down Expand Up @@ -288,7 +285,6 @@ declare global {
const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis']
const useStatementConfig: typeof import('./src/features/wikibase-schema/composables/useStatementConfig')['useStatementConfig']
const useStatementDataTypeValidation: typeof import('./src/features/wikibase-schema/composables/useStatementDataTypeValidation')['useStatementDataTypeValidation']
const useStatementDropZone: typeof import('./src/features/wikibase-schema/composables/useStatementDropZone')['useStatementDropZone']
const useStatementEditor: typeof import('./src/features/wikibase-schema/composables/useStatementEditor')['useStatementEditor']
const useStatementValidationDisplay: typeof import('./src/features/wikibase-schema/composables/useStatementValidationDisplay')['useStatementValidationDisplay']
const useStepper: typeof import('@vueuse/core')['useStepper']
Expand Down Expand Up @@ -581,12 +577,9 @@ declare module 'vue' {
readonly useDevicesList: UnwrapRef<typeof import('@vueuse/core')['useDevicesList']>
readonly useDisplayMedia: UnwrapRef<typeof import('@vueuse/core')['useDisplayMedia']>
readonly useDocumentVisibility: UnwrapRef<typeof import('@vueuse/core')['useDocumentVisibility']>
readonly useDragDropContext: UnwrapRef<typeof import('./src/shared/composables/useDragDropContext')['useDragDropContext']>
readonly useDragDropHandlers: UnwrapRef<typeof import('./src/shared/composables/useDragDropHandlers')['useDragDropHandlers']>
readonly useDragDropStore: UnwrapRef<typeof import('./src/features/data-processing/stores/drag-drop.store')['useDragDropStore']>
readonly useDraggable: UnwrapRef<typeof import('@vueuse/core')['useDraggable']>
readonly useDropZone: UnwrapRef<typeof import('@vueuse/core')['useDropZone']>
readonly useDropZoneStyling: UnwrapRef<typeof import('./src/shared/composables/useDropZoneStyling')['useDropZoneStyling']>
readonly useElementBounding: UnwrapRef<typeof import('@vueuse/core')['useElementBounding']>
readonly useElementByPoint: UnwrapRef<typeof import('@vueuse/core')['useElementByPoint']>
readonly useElementHover: UnwrapRef<typeof import('@vueuse/core')['useElementHover']>
Expand Down Expand Up @@ -686,7 +679,6 @@ declare module 'vue' {
readonly useSpeechSynthesis: UnwrapRef<typeof import('@vueuse/core')['useSpeechSynthesis']>
readonly useStatementConfig: UnwrapRef<typeof import('./src/features/wikibase-schema/composables/useStatementConfig')['useStatementConfig']>
readonly useStatementDataTypeValidation: UnwrapRef<typeof import('./src/features/wikibase-schema/composables/useStatementDataTypeValidation')['useStatementDataTypeValidation']>
readonly useStatementDropZone: UnwrapRef<typeof import('./src/features/wikibase-schema/composables/useStatementDropZone')['useStatementDropZone']>
readonly useStatementEditor: UnwrapRef<typeof import('./src/features/wikibase-schema/composables/useStatementEditor')['useStatementEditor']>
readonly useStatementValidationDisplay: UnwrapRef<typeof import('./src/features/wikibase-schema/composables/useStatementValidationDisplay')['useStatementValidationDisplay']>
readonly useStepper: UnwrapRef<typeof import('@vueuse/core')['useStepper']>
Expand Down
2 changes: 1 addition & 1 deletion frontend/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ declare module 'vue' {
DataTable: typeof import('primevue/datatable')['default']
DataTabPanel: typeof import('./src/features/data-processing/components/DataTabPanel.vue')['default']
DefaultLayout: typeof import('./src/core/layouts/DefaultLayout.vue')['default']
DropZone: typeof import('./src/features/wikibase-schema/components/DropZone.vue')['default']
FileUpload: typeof import('primevue/fileupload')['default']
Header: typeof import('./src/shared/components/Header.vue')['default']
InputText: typeof import('primevue/inputtext')['default']
Expand All @@ -39,7 +40,6 @@ declare module 'vue' {
ReferencesEditor: typeof import('./src/features/wikibase-schema/components/ReferencesEditor.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SchemaDropZone: typeof import('./src/features/wikibase-schema/components/SchemaDropZone.vue')['default']
SchemaSelector: typeof import('./src/features/wikibase-schema/components/SchemaSelector.vue')['default']
SchemaTabPanel: typeof import('./src/features/wikibase-schema/components/SchemaTabPanel.vue')['default']
Select: typeof import('primevue/select')['default']
Expand Down
162 changes: 162 additions & 0 deletions frontend/src/features/wikibase-schema/components/DropZone.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
<script setup lang="ts">
// Props
const props = defineProps<{
icon: string
placeholder: string
testId: string
acceptedTypes: WikibaseDataType[]
disabled?: boolean
validator?: (columnInfo: ColumnInfo) => boolean
selectedColumn?: {
name: string
dataType: string
}
}>()

// Emits
const emit = defineEmits<{
'column-dropped': [columnInfo: ColumnInfo]
'clear-selection': []
}>()

const dragDropStore = useDragDropStore()
const isOverDropZone = ref(false)

const { isDataTypeCompatible } = useDataTypeCompatibility()

/**
* Core validation function for column-target compatibility
*/
const validateColumnForTarget = (columnInfo: ColumnInfo | null) => {
if (!columnInfo) {
return false
}

// Use validator if provided
if (props.validator) {
return props.validator(columnInfo)
}

// Default validation logic - data type compatibility
if (!isDataTypeCompatible(columnInfo.dataType, props.acceptedTypes)) {
return false
}

return true
}

// CSS classes using shared styling logic
const dropZoneClasses = computed(() => {
if (!dragDropStore.draggedColumn) {
return {
'border-primary-400 bg-primary-50': isOverDropZone.value,
}
}

const isValidTarget = validateColumnForTarget(dragDropStore.draggedColumn)

return {
'border-primary-400 bg-primary-50': isOverDropZone.value,
'border-green-400 bg-green-50': dragDropStore.isDragging && isValidTarget,
'border-red-400 bg-red-50': dragDropStore.isDragging && !isValidTarget,
}
})

// Event handlers
const handleDragOver = (event: DragEvent): void => {
if (props.disabled) return

event.preventDefault()
if (event.dataTransfer) {
event.dataTransfer.dropEffect =
validateColumnForTarget(dragDropStore.draggedColumn) ? 'copy' : 'none'
}
}

const handleDragEnter = (event?: DragEvent) => {
if (props.disabled) return

event?.preventDefault()
isOverDropZone.value = true
}

const handleDragLeave = (event?: DragEvent) => {
if (props.disabled) return

event?.preventDefault()
isOverDropZone.value = false
}

const handleDrop = (event: DragEvent): void => {
if (props.disabled) return

event.preventDefault()

const columnData = event.dataTransfer?.getData('application/x-column-data')
if (!columnData) {
isOverDropZone.value = false
return
}

try {
const columnInfo = JSON.parse(columnData) as ColumnInfo

// Only proceed if drop validation passes
if (validateColumnForTarget(columnInfo)) {
emit('column-dropped', columnInfo)
}
} catch (error) {
console.error('Failed to parse column data:', error)
} finally {
isOverDropZone.value = false
}
}
</script>

<template>
<div
:data-testid="testId"
:class="[
'grow flex flex-row items-center justify-center border-2 border-dashed border-gray-400 rounded-lg text-center transition-colors',
dropZoneClasses,
{ 'opacity-50 cursor-not-allowed': disabled },
]"
@dragover="handleDragOver"
@dragenter="handleDragEnter"
@dragleave="handleDragLeave"
@drop="handleDrop"
>
<!-- Show selected column info if column is selected -->
<div
v-if="selectedColumn"
class="flex items-center justify-center gap-3"
>
<div class="flex items-center gap-2 bg-white border border-surface-200 rounded px-3 py-2">
<i class="pi pi-database text-primary-600" />
<span class="font-medium text-surface-900">
{{ selectedColumn.name }}
</span>
<Tag
:value="selectedColumn.dataType"
size="small"
severity="secondary"
/>
</div>
<Button
v-tooltip="'Clear column selection'"
icon="pi pi-times"
size="small"
severity="secondary"
text
:disabled="disabled"
@click="emit('clear-selection')"
/>
</div>

<!-- Default drop zone content when no column is selected -->
<template v-else>
<i :class="[icon, 'text-2xl text-surface-400']" />
<p class="text-surface-600">{{ placeholder }}</p>
</template>
</div>
</template>
108 changes: 0 additions & 108 deletions frontend/src/features/wikibase-schema/components/SchemaDropZone.vue

This file was deleted.

Loading