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: 2 additions & 0 deletions frontend/.eslintrc-auto-import.json
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@
"useDragDropStore": true,
"useDraggable": true,
"useDropZone": true,
"useDropZoneStyling": true,
"useElementBounding": true,
"useElementByPoint": true,
"useElementHover": true,
Expand Down Expand Up @@ -378,6 +379,7 @@
"useUserMedia": true,
"useVModel": true,
"useVModels": true,
"useValidationCore": true,
"useValidationErrors": true,
"useValidationStore": true,
"useValueMapping": true,
Expand Down
4 changes: 4 additions & 0 deletions frontend/auto-imports.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ declare global {
const useDragDropStore: typeof import('./src/stores/drag-drop.store')['useDragDropStore']
const useDraggable: typeof import('@vueuse/core')['useDraggable']
const useDropZone: typeof import('@vueuse/core')['useDropZone']
const useDropZoneStyling: typeof import('./src/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 @@ -319,6 +320,7 @@ declare global {
const useUserMedia: typeof import('@vueuse/core')['useUserMedia']
const useVModel: typeof import('@vueuse/core')['useVModel']
const useVModels: typeof import('@vueuse/core')['useVModels']
const useValidationCore: typeof import('./src/composables/useValidationCore')['useValidationCore']
const useValidationErrors: typeof import('./src/composables/useValidationErrors')['useValidationErrors']
const useValidationStore: typeof import('./src/stores/validation.store')['useValidationStore']
const useValueMapping: typeof import('./src/composables/useValueMapping')['useValueMapping']
Expand Down Expand Up @@ -569,6 +571,7 @@ declare module 'vue' {
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']>
readonly useDropZoneStyling: UnwrapRef<typeof import('./src/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 @@ -700,6 +703,7 @@ declare module 'vue' {
readonly useUserMedia: UnwrapRef<typeof import('@vueuse/core')['useUserMedia']>
readonly useVModel: UnwrapRef<typeof import('@vueuse/core')['useVModel']>
readonly useVModels: UnwrapRef<typeof import('@vueuse/core')['useVModels']>
readonly useValidationCore: UnwrapRef<typeof import('./src/composables/useValidationCore')['useValidationCore']>
readonly useValidationErrors: UnwrapRef<typeof import('./src/composables/useValidationErrors')['useValidationErrors']>
readonly useValidationStore: UnwrapRef<typeof import('./src/stores/validation.store')['useValidationStore']>
readonly useValueMapping: UnwrapRef<typeof import('./src/composables/useValueMapping')['useValueMapping']>
Expand Down
4 changes: 4 additions & 0 deletions frontend/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,14 @@ declare module 'vue' {
TabPanels: typeof import('primevue/tabpanels')['default']
Tabs: typeof import('primevue/tabs')['default']
Tag: typeof import('primevue/tag')['default']
TermSection: typeof import('./src/components/TermSection.vue')['default']
TermsEditor: typeof import('./src/components/TermsEditor.vue')['default']
Toast: typeof import('primevue/toast')['default']
ToggleSwitch: typeof import('primevue/toggleswitch')['default']
ValidationDisplay: typeof import('./src/components/ValidationDisplay.vue')['default']
ValidationErrorDisplay: typeof import('./src/components/ValidationErrorDisplay.vue')['default']
ValidationStatusBar: typeof import('./src/components/ValidationStatusBar.vue')['default']
ValidationSuggestions: typeof import('./src/components/ValidationSuggestions.vue')['default']
WikibaseSchemaEditor: typeof import('./src/components/WikibaseSchemaEditor.vue')['default']
}
export interface GlobalDirectives {
Expand Down
34 changes: 32 additions & 2 deletions frontend/src/components/SchemaDropZone.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,23 @@ const {
setLanguageCode,
} = useSchemaDropZone()

// Schema validation UI for enhanced drop zone styling
const { getDropZoneClasses, currentDragFeedback } = useSchemaValidationUI()

// Computed drop zone classes that combine both systems
const enhancedDropZoneClasses = computed(() => {
const targetPath = `item.terms.${props.termType}s.${props.languageCode}`
const validationClasses = getDropZoneClasses(targetPath)

// dropZoneClasses returns an object for Vue class binding
// validationClasses returns an array of class names
// We need to combine them properly
return [
dropZoneClasses.value, // Object for Vue class binding
...validationClasses, // Array of class strings
]
})

// Set configuration from props
setTermType(props.termType as 'label' | 'description' | 'alias')
setLanguageCode(props.languageCode)
Expand All @@ -58,8 +75,8 @@ watch(
<div
:data-testid="testId"
:class="[
'grow flex flex-row items-center justify-center border-2 border-dashed border-surface-300 rounded-lg text-center transition-colors',
dropZoneClasses,
'grow flex flex-row items-center justify-center border-2 border-dashed rounded-lg text-center transition-colors',
enhancedDropZoneClasses,
]"
@dragover="handleDragOver"
@dragenter="handleDragEnter"
Expand All @@ -68,5 +85,18 @@ watch(
>
<i :class="[icon, 'text-2xl text-surface-400']" />
<p class="text-surface-600">{{ placeholder }}</p>

<!-- Real-time validation feedback -->
<div
v-if="currentDragFeedback"
class="mt-2 text-xs px-2 py-1 rounded"
:class="[
currentDragFeedback.type === 'success'
? 'bg-green-100 text-green-700'
: 'bg-red-100 text-red-700',
]"
>
{{ currentDragFeedback.message }}
</div>
</div>
</template>
20 changes: 18 additions & 2 deletions frontend/src/components/StatementEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ const {
getValidationSeverity,
} = useStatementValidationDisplay()

// Schema validation UI for enhanced validation feedback
const { getDropZoneClasses, currentDragFeedback } = useSchemaValidationUI()

// Set up the column drop callback
setOnColumnDrop((_column) => {
handleColumnDrop(_column)
Expand Down Expand Up @@ -352,7 +355,7 @@ watch(
<div v-if="isColumnType">
<!-- Drop Zone for Columns -->
<div
:class="dropZoneClasses"
:class="[dropZoneClasses, getDropZoneClasses('statement.value')]"
class="border-2 border-dashed rounded-lg p-4 text-center transition-all duration-200 ease-in-out"
@dragover="handleDragOver"
@dragenter="handleDragEnter"
Expand Down Expand Up @@ -392,10 +395,23 @@ watch(
<!-- Show drop zone message if no column selected -->
<div
v-else
class="flex items-center justify-center gap-2 text-surface-600"
class="flex flex-col items-center justify-center gap-2 text-surface-600"
>
<i class="pi pi-upload" />
<span class="text-sm font-medium">Drop column here</span>

<!-- Real-time validation feedback -->
<div
v-if="currentDragFeedback"
class="mt-2 text-xs px-2 py-1 rounded"
:class="[
currentDragFeedback.type === 'success'
? 'bg-green-100 text-green-700'
: 'bg-red-100 text-red-700',
]"
>
{{ currentDragFeedback.message }}
</div>
</div>
</div>
</div>
Expand Down
55 changes: 55 additions & 0 deletions frontend/src/components/TermSection.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<script setup lang="ts">
interface Props {
title: string
termType: 'label' | 'description' | 'alias'
icon: string
placeholder: string
testId: string
validationPath: string
}

const props = defineProps<Props>()

// Schema validation UI for section-level validation feedback
const { getPathValidationStatus, getValidationIcon } = useSchemaValidationUI()

// Get validation status and icon for this term type
const validation = computed(() => getPathValidationStatus(props.validationPath))
const validationIcon = computed(() => getValidationIcon(props.validationPath))

// Compute border and background classes based on validation status
const containerClasses = computed(() => [
'border rounded-lg p-4',
validation.value.hasErrors
? 'border-red-300 bg-red-50'
: validation.value.hasWarnings
? 'border-yellow-300 bg-yellow-50'
: 'border-surface-200',
])

// Compute total issues count
const totalIssues = computed(
() => validation.value.errors.length + validation.value.warnings.length,
)
</script>

<template>
<div :class="containerClasses">
<div class="flex items-center justify-between mb-3">
<h3 class="text-lg font-semibold">{{ title }}</h3>
<div
v-if="validationIcon"
class="flex items-center gap-1"
>
<i :class="[validationIcon.icon, validationIcon.class]" />
<span class="text-xs text-surface-600">{{ totalIssues }} issues</span>
</div>
</div>
<LanguageDropZone
:term-type="termType"
:icon="icon"
:placeholder="placeholder"
:test-id="testId"
/>
</div>
</template>
51 changes: 24 additions & 27 deletions frontend/src/components/TermsEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,33 @@
<template>
<div class="terms-editor space-y-6">
<!-- Labels Section -->
<div class="border border-surface-200 rounded-lg p-4">
<h3 class="text-lg font-semibold mb-3">Labels</h3>
<LanguageDropZone
term-type="label"
icon="pi pi-tag"
placeholder="Drop column for labels"
test-id="label-drop-zone"
/>
</div>
<TermSection
title="Labels"
term-type="label"
icon="pi pi-tag"
placeholder="Drop column for labels"
test-id="label-drop-zone"
validation-path="item.terms.labels"
/>

<!-- Descriptions Section -->
<div class="border border-surface-200 rounded-lg p-4">
<h3 class="text-lg font-semibold mb-3">Descriptions</h3>
<LanguageDropZone
term-type="description"
icon="pi pi-file-o"
placeholder="Drop column for descriptions"
test-id="description-drop-zone"
/>
</div>
<TermSection
title="Descriptions"
term-type="description"
icon="pi pi-file-o"
placeholder="Drop column for descriptions"
test-id="description-drop-zone"
validation-path="item.terms.descriptions"
/>

<!-- Aliases Section -->
<div class="border border-surface-200 rounded-lg p-4">
<h3 class="text-lg font-semibold mb-3">Aliases</h3>
<LanguageDropZone
term-type="alias"
icon="pi pi-tags"
placeholder="Drop column for aliases"
test-id="alias-drop-zone"
/>
</div>
<TermSection
title="Aliases"
term-type="alias"
icon="pi pi-tags"
placeholder="Drop column for aliases"
test-id="alias-drop-zone"
validation-path="item.terms.aliases"
/>
</div>
</template>
Loading