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
74 changes: 68 additions & 6 deletions .kiro/specs/wikibase-schema-editor/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ The schema editor follows a hierarchical component structure:
graph TD
A[WikibaseSchemaEditor] --> B[SchemaToolbar]
A --> C[ColumnPalette]
C --> C1[ColumnItem]
A --> D[SchemaCanvas]
D --> E[ItemEditor]
E --> F[TermsEditor]
Expand All @@ -50,12 +51,14 @@ graph TD
- Provides validation feedback

#### 2. ColumnPalette
- **Purpose**: Displays available data columns as draggable elements
- **Purpose**: Displays available data columns as draggable elements with optional sample data
- **Responsibilities**:
- Fetches column information from project data
- Renders columns as draggable chips/badges
- Provides visual feedback during drag operations
- Shows column data types and sample values
- Shows column data types and conditionally shows sample values
- Manages sample data visibility toggle state
- Provides toggle button interface for showing/hiding sample data

#### 3. SchemaCanvas
- **Purpose**: Main editing area where schema structure is built
Expand Down Expand Up @@ -103,6 +106,14 @@ graph TD
- Property selection for qualifier/reference properties
- Value mapping for qualifier/reference values

#### 9. ColumnItem
- **Purpose**: Individual draggable column element within the ColumnPalette
- **Responsibilities**:
- Renders individual column as draggable chip
- Shows column name and data type
- Conditionally displays sample data based on parent toggle state
- Handles drag start/end events for the column

### Data Models

#### Schema Data Structure
Expand Down Expand Up @@ -415,16 +426,64 @@ AutoImport({

## VueUse Implementation Details

### ColumnPalette Container with Sample Data Toggle

```vue
<!-- ColumnPalette.vue - Container component with sample data toggle -->
<script setup lang="ts">
// Note: Vue, VueUse, and PrimeVue components are auto-imported
// No need to import: ref, computed, Button, etc.

const props = defineProps<{
columns: ColumnInfo[]
}>()

// Sample data visibility state (hidden by default)
const showSampleData = ref(false)

const toggleSampleData = () => {
showSampleData.value = !showSampleData.value
}
</script>

<template>
<div class="column-palette">
<!-- Toggle button at the top -->
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold">Columns</h3>
<Button
:icon="showSampleData ? 'pi pi-eye-slash' : 'pi pi-eye'"
:label="showSampleData ? 'Hide Samples' : 'Show Samples'"
size="small"
severity="secondary"
@click="toggleSampleData"
/>
</div>

<!-- Column items -->
<div class="flex flex-wrap gap-2">
<ColumnItem
v-for="column in columns"
:key="column.name"
:column-info="column"
:show-sample-data="showSampleData"
/>
</div>
</div>
</template>
```

### Column Dragging with UseDraggable Component and HTML5 DataTransfer

```vue
<!-- ColumnPalette.vue - Making columns draggable with data transfer -->
<!-- ColumnItem.vue - Individual draggable column component -->
<script setup lang="ts">
// Note: Vue, VueUse, and PrimeVue components are auto-imported
// No need to import: ref, computed, Chip, etc.

const props = defineProps<{
columnInfo: ColumnInfo
showSampleData: boolean
}>()

const dragDropStore = useDragDropStore() // Auto-imported from stores
Expand Down Expand Up @@ -482,9 +541,12 @@ const handleDragEnd = (event: DragEvent) => {
</template>
</Chip>

<div v-if="columnInfo.sampleValues?.length" class="text-xs text-surface-600 mt-1">
Sample: {{ columnInfo.sampleValues.slice(0, 2).join(', ') }}
<span v-if="columnInfo.sampleValues.length > 2">...</span>
<div
v-if="showSampleData && columnInfo.sampleValues?.length"
class="text-xs text-surface-600 mt-1"
>
Sample: {{ columnInfo.sampleValues.slice(0, 3).join(', ') }}
<span v-if="columnInfo.sampleValues.length > 3">...</span>
</div>
</div>
</UseDraggable>
Expand Down
3 changes: 3 additions & 0 deletions .kiro/specs/wikibase-schema-editor/requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ The Wikibase Schema Editor is a visual interface that allows users to create and
2. WHEN the dataset has columns THEN the system SHALL show each column name as a draggable element
3. WHEN the user drags a column name THEN the system SHALL provide visual feedback indicating valid drop targets
4. IF the dataset has no columns THEN the system SHALL display an appropriate message indicating no data is available
5. WHEN the column palette loads THEN the system SHALL hide sample data by default
6. WHEN the user clicks the sample data toggle button THEN the system SHALL show or hide sample data for all columns
7. WHEN sample data is visible THEN the system SHALL display up to 3 sample values per column below the column name

### Requirement 3

Expand Down
8 changes: 5 additions & 3 deletions .kiro/specs/wikibase-schema-editor/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,16 @@
- Write tests for displaying available data columns
- Write tests for draggable column elements
- Write tests for empty dataset state handling
- Write tests for sample data toggle functionality
- Write tests for sample data visibility state management
- Implement ColumnPalette component
- _Requirements: 2.1, 2.2, 2.4_
- _Requirements: 2.1, 2.2, 2.4, 2.5, 2.6_

- [x] 8. Create column data type indicators
- Write tests for column data type display and tooltips
- Write tests for sample value display functionality
- Implement column data type indicators and tooltips
- _Requirements: 8.2_
- _Requirements: 8.2, 2.7_

- [x] 9. Build basic WikibaseSchemaEditor container
- Write tests for main container component initialization
Expand All @@ -61,7 +63,7 @@
- Implement basic ItemEditor component structure
- _Requirements: 1.1, 1.2_

- [ ] 11. Build TermsEditor component with TDD
- [x] 11. Build TermsEditor component with TDD
- Write tests for Labels, Descriptions, and Aliases drop zones
- Write tests for multilingual configuration interface
- Implement TermsEditor component with drop zone functionality
Expand Down
4 changes: 4 additions & 0 deletions frontend/.eslintrc-auto-import.json
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@
"useDisplayMedia": true,
"useDocumentVisibility": true,
"useDragDropContext": true,
"useDragDropHandlers": true,
"useDragDropStore": true,
"useDraggable": true,
"useDropZone": true,
Expand Down Expand Up @@ -274,6 +275,7 @@
"useInterval": true,
"useIntervalFn": true,
"useKeyModifier": true,
"useLanguageDropZone": true,
"useLastChanged": true,
"useLink": true,
"useLocalStorage": true,
Expand Down Expand Up @@ -323,6 +325,7 @@
"useSSRWidth": true,
"useSchemaApi": true,
"useSchemaBuilder": true,
"useSchemaDropZone": true,
"useSchemaStore": true,
"useScreenOrientation": true,
"useScreenSafeArea": true,
Expand All @@ -343,6 +346,7 @@
"useSwipe": true,
"useTemplateRef": true,
"useTemplateRefsList": true,
"useTermsEditor": true,
"useTextDirection": true,
"useTextSelection": true,
"useTextareaAutosize": true,
Expand Down
8 changes: 8 additions & 0 deletions frontend/auto-imports.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ declare global {
const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia']
const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility']
const useDragDropContext: typeof import('./src/composables/useDragDropContext')['useDragDropContext']
const useDragDropHandlers: typeof import('./src/composables/useDragDropHandlers')['useDragDropHandlers']
const useDragDropStore: typeof import('./src/stores/drag-drop.store')['useDragDropStore']
const useDraggable: typeof import('@vueuse/core')['useDraggable']
const useDropZone: typeof import('@vueuse/core')['useDropZone']
Expand Down Expand Up @@ -215,6 +216,7 @@ declare global {
const useInterval: typeof import('@vueuse/core')['useInterval']
const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn']
const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier']
const useLanguageDropZone: typeof import('./src/composables/useLanguageDropZone')['useLanguageDropZone']
const useLastChanged: typeof import('@vueuse/core')['useLastChanged']
const useLink: typeof import('vue-router')['useLink']
const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage']
Expand Down Expand Up @@ -264,6 +266,7 @@ declare global {
const useSSRWidth: typeof import('@vueuse/core')['useSSRWidth']
const useSchemaApi: typeof import('./src/composables/useSchemaApi')['useSchemaApi']
const useSchemaBuilder: typeof import('./src/composables/useSchemaBuilder')['useSchemaBuilder']
const useSchemaDropZone: typeof import('./src/composables/useSchemaDropZone')['useSchemaDropZone']
const useSchemaStore: typeof import('./src/stores/schema.store')['useSchemaStore']
const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation']
const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea']
Expand All @@ -284,6 +287,7 @@ declare global {
const useSwipe: typeof import('@vueuse/core')['useSwipe']
const useTemplateRef: typeof import('vue')['useTemplateRef']
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
const useTermsEditor: typeof import('./src/composables/useTermsEditor')['useTermsEditor']
const useTextDirection: typeof import('@vueuse/core')['useTextDirection']
const useTextSelection: typeof import('@vueuse/core')['useTextSelection']
const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize']
Expand Down Expand Up @@ -547,6 +551,7 @@ declare module 'vue' {
readonly useDisplayMedia: UnwrapRef<typeof import('@vueuse/core')['useDisplayMedia']>
readonly useDocumentVisibility: UnwrapRef<typeof import('@vueuse/core')['useDocumentVisibility']>
readonly useDragDropContext: UnwrapRef<typeof import('./src/composables/useDragDropContext')['useDragDropContext']>
readonly useDragDropHandlers: UnwrapRef<typeof import('./src/composables/useDragDropHandlers')['useDragDropHandlers']>
readonly useDragDropStore: UnwrapRef<typeof import('./src/stores/drag-drop.store')['useDragDropStore']>
readonly useDraggable: UnwrapRef<typeof import('@vueuse/core')['useDraggable']>
readonly useDropZone: UnwrapRef<typeof import('@vueuse/core')['useDropZone']>
Expand Down Expand Up @@ -579,6 +584,7 @@ declare module 'vue' {
readonly useInterval: UnwrapRef<typeof import('@vueuse/core')['useInterval']>
readonly useIntervalFn: UnwrapRef<typeof import('@vueuse/core')['useIntervalFn']>
readonly useKeyModifier: UnwrapRef<typeof import('@vueuse/core')['useKeyModifier']>
readonly useLanguageDropZone: UnwrapRef<typeof import('./src/composables/useLanguageDropZone')['useLanguageDropZone']>
readonly useLastChanged: UnwrapRef<typeof import('@vueuse/core')['useLastChanged']>
readonly useLink: UnwrapRef<typeof import('vue-router')['useLink']>
readonly useLocalStorage: UnwrapRef<typeof import('@vueuse/core')['useLocalStorage']>
Expand Down Expand Up @@ -628,6 +634,7 @@ declare module 'vue' {
readonly useSSRWidth: UnwrapRef<typeof import('@vueuse/core')['useSSRWidth']>
readonly useSchemaApi: UnwrapRef<typeof import('./src/composables/useSchemaApi')['useSchemaApi']>
readonly useSchemaBuilder: UnwrapRef<typeof import('./src/composables/useSchemaBuilder')['useSchemaBuilder']>
readonly useSchemaDropZone: UnwrapRef<typeof import('./src/composables/useSchemaDropZone')['useSchemaDropZone']>
readonly useSchemaStore: UnwrapRef<typeof import('./src/stores/schema.store')['useSchemaStore']>
readonly useScreenOrientation: UnwrapRef<typeof import('@vueuse/core')['useScreenOrientation']>
readonly useScreenSafeArea: UnwrapRef<typeof import('@vueuse/core')['useScreenSafeArea']>
Expand All @@ -648,6 +655,7 @@ declare module 'vue' {
readonly useSwipe: UnwrapRef<typeof import('@vueuse/core')['useSwipe']>
readonly useTemplateRef: UnwrapRef<typeof import('vue')['useTemplateRef']>
readonly useTemplateRefsList: UnwrapRef<typeof import('@vueuse/core')['useTemplateRefsList']>
readonly useTermsEditor: UnwrapRef<typeof import('./src/composables/useTermsEditor')['useTermsEditor']>
readonly useTextDirection: UnwrapRef<typeof import('@vueuse/core')['useTextDirection']>
readonly useTextSelection: UnwrapRef<typeof import('@vueuse/core')['useTextSelection']>
readonly useTextareaAutosize: UnwrapRef<typeof import('@vueuse/core')['useTextareaAutosize']>
Expand Down
7 changes: 5 additions & 2 deletions frontend/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
Button: typeof import('primevue/button')['default']
Card: typeof import('primevue/card')['default']
Chip: typeof import('primevue/chip')['default']
Column: typeof import('primevue/column')['default']
ColumnPalette: typeof import('./src/components/ColumnPalette.vue')['default']
Expand All @@ -19,24 +18,28 @@ declare module 'vue' {
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']
ItemEditor: typeof import('./src/components/ItemEditor.vue')['default']
LanguageDropZone: typeof import('./src/components/LanguageDropZone.vue')['default']
MainContent: typeof import('./src/components/MainContent.vue')['default']
OpenProject: typeof import('./src/pages/OpenProject.vue')['default']
Paginator: typeof import('primevue/paginator')['default']
ProgressSpinner: typeof import('primevue/progressspinner')['default']
ProjectView: typeof import('./src/views/ProjectView.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SchemaDropZone: typeof import('./src/components/SchemaDropZone.vue')['default']
SchemaTabPanel: typeof import('./src/components/SchemaTabPanel.vue')['default']
Select: typeof import('primevue/select')['default']
Sidebar: typeof import('./src/components/Sidebar.vue')['default']
Tab: typeof import('primevue/tab')['default']
TabList: typeof import('primevue/tablist')['default']
TabPanel: typeof import('primevue/tabpanel')['default']
TabPanels: typeof import('primevue/tabpanels')['default']
Tabs: typeof import('primevue/tabs')['default']
TermsEditor: typeof import('./src/components/TermsEditor.vue')['default']
Toast: typeof import('primevue/toast')['default']
ToggleSwitch: typeof import('primevue/toggleswitch')['default']
ValidationErrorDisplay: typeof import('./src/components/ValidationErrorDisplay.vue')['default']
WikibaseSchemaEditor: typeof import('./src/components/WikibaseSchemaEditor.vue')['default']
}
Expand Down
45 changes: 32 additions & 13 deletions frontend/src/components/ColumnPalette.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,25 @@ const { convertProjectColumnsToColumnInfo } = useColumnConversion()
const { formatDataTypeDisplayName, generateColumnTooltip, getDataTypeIcon, getDataTypeSeverity } =
useColumnDataTypeIndicators()

const columns = computed(() => {
// Sample data visibility state (hidden by default)
const showSampleData = ref(false)

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

const handleDragStart = (event: DragEvent, _column: ColumnInfo) => {
const handleDragStart = (event: DragEvent, dataCol: 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
event.dataTransfer.setData('application/x-column-data', JSON.stringify(dataCol))
event.dataTransfer.setData('text/plain', dataCol.name) // Fallback

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

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

const handleDragEnd = () => {
Expand All @@ -32,23 +35,38 @@ const handleDragEnd = () => {
const formatSampleValues = (sampleValues: string[]) => {
if (!sampleValues || sampleValues.length === 0) return ''

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

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>
<!-- Header with toggle switch -->
<div class="flex justify-between items-center mb-4">
<div>
<h3 class="text-lg font-semibold text-surface-900 mb-1">Data Columns</h3>
</div>
<div class="flex items-center gap-2">
<label
for="sample-toggle"
class="text-sm font-medium text-surface-700"
>
Show Samples
</label>
<ToggleSwitch
id="sample-toggle"
v-model="showSampleData"
data-testid="sample-toggle-switch"
/>
</div>
</div>

<!-- Empty state -->
<div
v-if="!columns || columns.length === 0"
v-if="!dataColumns || dataColumns.length === 0"
data-testid="empty-state"
class="text-center py-8 px-4 border-2 border-dashed border-surface-300 rounded-lg"
>
Expand All @@ -65,7 +83,7 @@ const formatSampleValues = (sampleValues: string[]) => {
class="flex flex-wrap gap-3"
>
<div
v-for="col in columns"
v-for="col in dataColumns"
:key="col.name"
:data-testid="'column-chip'"
:class="{
Expand Down Expand Up @@ -105,8 +123,9 @@ const formatSampleValues = (sampleValues: string[]) => {
</div>

<div
v-if="col.sampleValues && col.sampleValues.length > 0"
v-if="showSampleData && col.sampleValues && col.sampleValues.length > 0"
class="text-xs text-surface-600"
data-testid="sample-values"
:title="`Sample values: ${col.sampleValues.slice(0, 5).join(', ')}${col.sampleValues.length > 5 ? '...' : ''}`"
>
{{ formatSampleValues(col.sampleValues) }}
Expand Down
Loading