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
2 changes: 1 addition & 1 deletion .kiro/specs/wikibase-schema-editor/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
- Implement drag-and-drop composable functionality
- _Requirements: 2.3_

- [ ] 7. Build ColumnPalette component with TDD
- [x] 7. Build ColumnPalette component with TDD
- Write tests for displaying available data columns
- Write tests for draggable column elements
- Write tests for empty dataset state handling
Expand Down
5 changes: 2 additions & 3 deletions frontend/.eslintrc-auto-import.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
"ComponentPublicInstance": true,
"ComputedRef": true,
"CreateSchemaRequest": true,
"DATA_TYPE_COMPATIBILITY_MAP": true,
"DirectiveBinding": true,
"DragDropConfig": true,
"DragDropContext": true,
Expand Down Expand Up @@ -100,14 +99,12 @@
"effectScope": true,
"extendRef": true,
"getActivePinia": true,
"getCompatibleWikibaseTypes": true,
"getCurrentInstance": true,
"getCurrentScope": true,
"h": true,
"ignorableWatch": true,
"inject": true,
"injectLocal": true,
"isDataTypeCompatible": true,
"isDefined": true,
"isProxy": true,
"isReactive": true,
Expand Down Expand Up @@ -216,6 +213,7 @@
"useClipboardItems": true,
"useCloned": true,
"useColorMode": true,
"useColumnConversion": true,
"useColumnGeneration": true,
"useConfirm": true,
"useConfirmDialog": true,
Expand All @@ -228,6 +226,7 @@
"useCurrentElement": true,
"useCycleList": true,
"useDark": true,
"useDataTypeCompatibility": true,
"useDateFormat": true,
"useDebounce": true,
"useDebounceFn": true,
Expand Down
8 changes: 5 additions & 3 deletions frontend/auto-imports.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ declare global {
const computedWithControl: typeof import('@vueuse/core')['computedWithControl']
const controlledComputed: typeof import('@vueuse/core')['controlledComputed']
const controlledRef: typeof import('@vueuse/core')['controlledRef']
const convertProjectColumnsToColumnInfo: typeof import('./src/utils/column-conversion')['convertProjectColumnsToColumnInfo']
const createApp: typeof import('vue')['createApp']
const createDropZoneConfig: typeof import('./src/composables/useDragDropContext')['createDropZoneConfig']
const createEventHook: typeof import('@vueuse/core')['createEventHook']
Expand Down Expand Up @@ -162,6 +163,7 @@ declare global {
const useClipboardItems: typeof import('@vueuse/core')['useClipboardItems']
const useCloned: typeof import('@vueuse/core')['useCloned']
const useColorMode: typeof import('@vueuse/core')['useColorMode']
const useColumnConversion: typeof import('./src/composables/useColumnConversion')['useColumnConversion']
const useColumnGeneration: typeof import('./src/composables/useColumnGeneration')['useColumnGeneration']
const useConfirm: typeof import('primevue/useconfirm')['useConfirm']
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
Expand All @@ -174,6 +176,7 @@ declare global {
const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement']
const useCycleList: typeof import('@vueuse/core')['useCycleList']
const useDark: typeof import('@vueuse/core')['useDark']
const useDataTypeCompatibility: typeof import('./src/composables/useDataTypeCompatibility')['useDataTypeCompatibility']
const useDateFormat: typeof import('@vueuse/core')['useDateFormat']
const useDebounce: typeof import('@vueuse/core')['useDebounce']
const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn']
Expand Down Expand Up @@ -371,7 +374,6 @@ declare module 'vue' {
interface ComponentCustomProperties {
readonly ApiKey: UnwrapRef<typeof import('./src/plugins/api')['ApiKey']>
readonly ApiPlugin: UnwrapRef<typeof import('./src/plugins/api')['ApiPlugin']>
readonly DATA_TYPE_COMPATIBILITY_MAP: UnwrapRef<typeof import('./src/utils/data-type-compatibility')['DATA_TYPE_COMPATIBILITY_MAP']>
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
readonly ValidationMessages: UnwrapRef<typeof import('./src/types/wikibase-schema')['ValidationMessages']>
readonly acceptHMRUpdate: UnwrapRef<typeof import('pinia')['acceptHMRUpdate']>
Expand Down Expand Up @@ -407,14 +409,12 @@ declare module 'vue' {
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
readonly extendRef: UnwrapRef<typeof import('@vueuse/core')['extendRef']>
readonly getActivePinia: UnwrapRef<typeof import('pinia')['getActivePinia']>
readonly getCompatibleWikibaseTypes: UnwrapRef<typeof import('./src/utils/data-type-compatibility')['getCompatibleWikibaseTypes']>
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
readonly h: UnwrapRef<typeof import('vue')['h']>
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
readonly inject: UnwrapRef<typeof import('vue')['inject']>
readonly injectLocal: UnwrapRef<typeof import('@vueuse/core')['injectLocal']>
readonly isDataTypeCompatible: UnwrapRef<typeof import('./src/utils/data-type-compatibility')['isDataTypeCompatible']>
readonly isDefined: UnwrapRef<typeof import('@vueuse/core')['isDefined']>
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
Expand Down Expand Up @@ -523,6 +523,7 @@ declare module 'vue' {
readonly useClipboardItems: UnwrapRef<typeof import('@vueuse/core')['useClipboardItems']>
readonly useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
readonly useColumnConversion: UnwrapRef<typeof import('./src/composables/useColumnConversion')['useColumnConversion']>
readonly useColumnGeneration: UnwrapRef<typeof import('./src/composables/useColumnGeneration')['useColumnGeneration']>
readonly useConfirm: UnwrapRef<typeof import('primevue/useconfirm')['useConfirm']>
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
Expand All @@ -535,6 +536,7 @@ declare module 'vue' {
readonly useCurrentElement: UnwrapRef<typeof import('@vueuse/core')['useCurrentElement']>
readonly useCycleList: UnwrapRef<typeof import('@vueuse/core')['useCycleList']>
readonly useDark: UnwrapRef<typeof import('@vueuse/core')['useDark']>
readonly useDataTypeCompatibility: UnwrapRef<typeof import('./src/composables/useDataTypeCompatibility')['useDataTypeCompatibility']>
readonly useDateFormat: UnwrapRef<typeof import('@vueuse/core')['useDateFormat']>
readonly useDebounce: UnwrapRef<typeof import('@vueuse/core')['useDebounce']>
readonly useDebounceFn: UnwrapRef<typeof import('@vueuse/core')['useDebounceFn']>
Expand Down
3 changes: 3 additions & 0 deletions frontend/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
Button: typeof import('primevue/button')['default']
Card: typeof import('primevue/card')['default']
Column: typeof import('primevue/column')['default']
ColumnPalette: typeof import('./src/components/ColumnPalette.vue')['default']
ConfirmDialog: typeof import('primevue/confirmdialog')['default']
CreateProject: typeof import('./src/pages/CreateProject.vue')['default']
CustomPaginator: typeof import('./src/components/CustomPaginator.vue')['default']
DataTable: typeof import('primevue/datatable')['default']
DataTabPanel: typeof import('./src/components/DataTabPanel.vue')['default']
DefaultLayout: typeof import('./src/layouts/DefaultLayout.vue')['default']
FileUpload: typeof import('primevue/fileupload')['default']
Header: typeof import('./src/components/Header.vue')['default']
MainContent: typeof import('./src/components/MainContent.vue')['default']
OpenProject: typeof import('./src/pages/OpenProject.vue')['default']
Expand Down
116 changes: 116 additions & 0 deletions frontend/src/components/ColumnPalette.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<script setup lang="ts">
const projectStore = useProjectStore()
const dragDropStore = useDragDropStore()

const { convertProjectColumnsToColumnInfo } = useColumnConversion()

const columns = computed(() => {
return convertProjectColumnsToColumnInfo(projectStore.columns, projectStore.data)
})

const handleDragStart = (event: DragEvent, column: ColumnInfo) => {
if (!event.dataTransfer) return

// Set column data in DataTransfer for drop zones to access
event.dataTransfer.setData('application/x-column-data', JSON.stringify(column))
event.dataTransfer.setData('text/plain', column.name) // Fallback

// Set drag effect
event.dataTransfer.effectAllowed = 'copy'

// Set dragged column data in store
dragDropStore.startDrag(column)
}

const handleDragEnd = () => {
// Clean up drag state
dragDropStore.endDrag()
}

const formatSampleValues = (sampleValues: string[]) => {
if (!sampleValues || sampleValues.length === 0) return ''

const displayValues = sampleValues.slice(0, 2).join(', ')
const hasMore = sampleValues.length > 2

return `Sample: ${displayValues}${hasMore ? '...' : ''}`
}
</script>

<template>
<div class="column-palette p-4">
<div class="mb-4">
<h3 class="text-lg font-semibold text-surface-900 mb-2">Data Columns</h3>
<p class="text-sm text-surface-600">Drag columns to map them to Wikibase schema elements</p>
</div>

<!-- Empty state -->
<div
v-if="!columns || columns.length === 0"
data-testid="empty-state"
class="text-center py-8 px-4 border-2 border-dashed border-surface-300 rounded-lg"
>
<div class="text-surface-500 mb-2">
<i class="pi pi-database text-3xl"></i>
</div>
<h4 class="text-surface-700 font-medium mb-2">No data columns available</h4>
<p class="text-sm text-surface-600">Load a dataset to see available columns for mapping</p>
</div>

<!-- Column chips -->
<div
v-else
class="flex flex-wrap gap-3"
>
<div
v-for="column in columns"
:key="column.name"
:data-testid="'column-chip'"
:class="{
'opacity-50':
dragDropStore.isDragging && dragDropStore.draggedColumn?.name === column.name,
'cursor-grab': !dragDropStore.isDragging,
'cursor-grabbing':
dragDropStore.isDragging && dragDropStore.draggedColumn?.name === column.name,
}"
class="column-chip transition-opacity duration-200 hover:-translate-y-0.5 active:translate-y-0"
draggable="true"
@dragstart="handleDragStart($event, column)"
@dragend="handleDragEnd"
>
<div
class="bg-white border border-surface-300 rounded-lg p-3 shadow-sm hover:shadow-md transition-shadow"
>
<div class="flex items-center gap-2 mb-2">
<span class="font-medium text-surface-900">{{ column.name }}</span>
<Chip
:label="column.dataType"
size="small"
severity="secondary"
class="text-xs"
/>
<i
v-if="column.nullable"
class="pi pi-question-circle text-surface-400 text-xs"
title="Nullable column"
></i>
</div>

<div
v-if="column.sampleValues && column.sampleValues.length > 0"
class="text-xs text-surface-600"
>
{{ formatSampleValues(column.sampleValues) }}
</div>

<div
v-if="column.uniqueCount !== undefined"
class="text-xs text-surface-500 mt-1"
>
{{ column.uniqueCount }} unique values
</div>
</div>
</div>
</div>
</div>
</template>
Loading