Adds initial support for uploading table data #87

Open
jsheunis wants to merge 2 commits from tables into main
9 changed files with 594 additions and 186 deletions

7
package-lock.json generated
View file

@ -16,6 +16,7 @@
"@rdfjs/parser-n3": "^2.0.2",
"rdf-ext": "^2.5.2",
"roboto-fontface": "^0.10.0",
"tabulator-tables": "^6.3.1",
"util": "^0.12.5",
"uuid": "^10.0.0"
},
@ -5211,6 +5212,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/tabulator-tables": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/tabulator-tables/-/tabulator-tables-6.3.1.tgz",
"integrity": "sha512-qFW7kfadtcaISQIibKAIy0f3eeIXUVi8242Vly1iJfMD79kfEGzfczNuPBN/80hDxHzQJXYbmJ8VipI40hQtfA==",
"license": "MIT"
},
"node_modules/tinybench": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",

View file

@ -58,6 +58,7 @@
"@rdfjs/parser-n3": "^2.0.2",
"rdf-ext": "^2.5.2",
"roboto-fontface": "^0.10.0",
"tabulator-tables": "^6.3.1",
"util": "^0.12.5",
"uuid": "^10.0.0"
},

208
src/classes/DataTable.js Normal file
View file

@ -0,0 +1,208 @@
import { SHACL } from '../modules/namespaces'
import { findObjectByKey } from '../modules/utils'
import { toCURIE, toIRI } from 'shacl-tulip';
import { toRaw } from 'vue';
const reactiveCloneFunc = ((data) => {
return structuredClone(toRaw(data))
});
export class DataTable {
constructor(table, class_iri, record_iri, shapesDS, formData, allPrefixes, ID_IRI) {
this.DELIMITER = ',';
this.table = table
this.columns = table.getColumnDefinitions().map(col => col.title)
this.class_iri = class_iri
this.record_iri = record_iri
this.shapesDS = shapesDS
this.nodeShape = this.shapesDS.data.nodeShapes[class_iri]
this.ID_IRI = ID_IRI
this.allPrefixes = allPrefixes
this.formData = formData
this.mappedProps = this.mapProps()
}
mapProps() {
var mappedProps = {}
mappedProps.props = {}
mappedProps.requiredProps = []
mappedProps.multivaluedProps = []
mappedProps.blankNodeProps = []
mappedProps.namedNodeProps = []
var propertyShapes = this.nodeShape.properties // this is an array ob objects
for (var p=0; p<propertyShapes.length; p++) {
// Get the propert name
var pShape = propertyShapes[p];
var propertyName = toCURIE(pShape[SHACL.path.value], this.allPrefixes, "parts").property
if (propertyName) {
mappedProps.props[propertyName] = pShape
} else {
console.log(`\nProp: ${propertyName}`)
console.log('- propertyname not found, not mapping it; this is the shape:')
console.log(pShape)
continue;
}
// Is property required?
if (this.propertyIsRequired(pShape)) {
// console.log(`- this is a required property`)
mappedProps.requiredProps.push(propertyName)
}
// Is property multivalued?
if (this.propertyIsList(pShape)) {
// console.log(`- this is a multivalued property`)
mappedProps.multivaluedProps.push(propertyName)
}
// What is the nodeKind and type (from SHACL shape):
// - just a string/number/...
// - list of strings/numbers/...
// - list of pids (named nodes)
// - list of objects (blank nodes) => cannot be handled
const [propertyIsBN, propertyIsIRI] = this.getNodeDeets(pShape)
if (propertyIsBN) {mappedProps.blankNodeProps.push(propertyName)}
if (propertyIsIRI) {mappedProps.namedNodeProps.push(propertyName)}
}
mappedProps.allProps = Object.keys(mappedProps.props);
return mappedProps
}
propertyIsRequired(pShape) {
// sh:minCount must be greater than zero
return pShape[SHACL.minCount?.value] > 0 ? true : false
}
propertyIsList(pShape) {
if (pShape.hasOwnProperty(SHACL.maxCount)) {
if (pShape[SHACL.maxCount] == 1) {
return false
} else if (pShape[SHACL.maxCount] > 1) {
return true
} else {
return false
}
} else {
return true
}
}
getNodeDeets(pShape) {
var isBlankNode = false
var isIRI = false
if ( pShape.hasOwnProperty(SHACL.nodeKind.value) ) {
if (pShape[SHACL.nodeKind.value] == SHACL.IRI.value) {
isIRI = true
} else if (pShape[SHACL.nodeKind.value] == SHACL.BlankNode.value) {
isBlankNode = true
} else if (pShape[SHACL.nodeKind.value] == SHACL.BlankNodeOrIRI.value
&& pShape.hasOwnProperty(SHACL.class.value)) {
var rangeNodeShape = this.shapesDS.data.nodeShapes[pShape[SHACL.class.value]]
var foundIDpropshape = findObjectByKey(rangeNodeShape.properties, SHACL.path.value, this.ID_IRI)
if (foundIDpropshape) {
isIRI = true
} else {
isBlankNode = true
}
}
}
return [isBlankNode, isIRI]
}
saveTable(rdfDS, parentShapeIRI, parentPropertyIRI) {
// Props have already been mapped
// We first check if the ID_IRI, if expected to be provided, is in the list of columns
var idPropertyName = toCURIE(this.ID_IRI, this.allPrefixes, "parts").property
if (this.mappedProps.allProps.includes(idPropertyName) && !this.columns.includes(idPropertyName) ) {
var msg = `ID_IRI property name '${idPropertyName}' is not included in the table columns, while it is expected. Cannot save data.`
throw Error(msg)
}
// TODO: handle required properties in the same way? Do requiredProperties necessarily include ID_IRI too?
// Now we check for column headings that can't be contained/processed in CSV
// (isBlankNode, or is not in list of properties)
// Warn that this column will be ignored
for (var c of this.columns) {
if (this.mappedProps.blankNodeProps.includes(c) ) {
console.log(`Column '${c}' cannot be processed because it expects a blank node property, ignoring...`)
}
if (!this.mappedProps.allProps.includes(c) ) {
console.log(`Column '${c}' cannot be processed because it is not in the list of possible properties, ignoring...`)
}
}
// Now cycle through the rows and save data
var tableData = this.table.getData()
for (var row of tableData) {
var nodeID
if (this.columns.includes(idPropertyName)) {
nodeID = row[idPropertyName]
} else {
nodeID = '_:' + crypto.randomUUID()// blanknode
}
// First process nodeID, i.e. subject (for both named node and blank node)
this.formData.addSubject(this.class_iri, nodeID)
// For each row cell, we need to add the relevant record(s) to formData
for (var p of Object.keys(row)) {
// ID_IRI column has already been processed, but the predicate should still be added... TODO inspect
// if (p == idPropertyName) {continue;}
// Ignore blanknode columns
if (this.mappedProps.blankNodeProps.includes(p)) {continue;}
// Ignore columns that aren't in the list of possible properties
if (!this.mappedProps.allProps.includes(p)) {continue;}
// If the value is empty, skip
if (!row[p]) {continue;}
// If the value exists
// First get the predicate uri from the column name
var predicate_uri = this.mappedProps.props[p][SHACL.path.value]
// First check if this is a multivalued slot and then split it
if (this.mappedProps.multivaluedProps.includes(p) && row[p].split(this.DELIMITER).length > 1) {
var p_parts = row[p].split(this.DELIMITER)
// Add a addPredicate for first element, addObject for rest
for (var i=0;i<p_parts.length;i++) {
if (i==0) {
this.formData.addPredicate(this.class_iri, nodeID, predicate_uri, [p_parts[i]])
} else {
// This pushes null into the content array, after which we still need to set the value
this.formData.addObject(this.class_iri, nodeID, predicate_uri);
this.formData.content[this.class_iri][nodeID][predicate_uri][i] = p_parts[i];
}
}
} else {
this.formData.addPredicate(this.class_iri, nodeID, predicate_uri, [row[p]])
}
}
var saved_node = this.formData.saveNode(
this.class_iri,
nodeID,
this.shapesDS,
rdfDS,
false,
reactiveCloneFunc
)
// Lastly, if uploading a table from nodeShapeEditor, the saved nodes
// should be linked to the record via their ID (which could be named or blank nodes)
if (this.record_iri) {
// Get the iri of the property for which the InstancesSelectEditor was instantiated
console.log("saved from instancesselecteditor, have to link new record to parent record")
var pred_iri = parentPropertyIRI
// If a predicate does not exist yet, use addPredicate else addObject
if (Object.keys(this.formData.content[parentShapeIRI][this.record_iri]).indexOf(pred_iri) < 0) {
this.formData.addPredicate(parentShapeIRI, this.record_iri, pred_iri, [nodeID])
} else {
console.log('content value before sdding object:')
console.log(toRaw(this.formData.content[parentShapeIRI][this.record_iri][pred_iri]))
// If the predicate exists, but it only has a single null value in the array, don't addObject and just set value
var predVal = toRaw(this.formData.content[parentShapeIRI][this.record_iri][pred_iri])
if (predVal.length == 1 && predVal[0] == null) {
console.log("not running addObject")
this.formData.content[parentShapeIRI][this.record_iri][pred_iri][0] = nodeID;
} else {
this.formData.addObject(parentShapeIRI, this.record_iri, pred_iri);
var l = this.formData.content[parentShapeIRI][this.record_iri][pred_iri].length
this.formData.content[parentShapeIRI][this.record_iri][pred_iri][l-1] = nodeID;
}
}
}
}
}
}

View file

@ -1,181 +0,0 @@
<template>
<v-container>
<!-- Drag and Drop area -->
<v-card
class="drag-drop-area"
:class="{'dragover': isDragging}"
@dragover.prevent="onDragOver"
@dragleave.prevent="onDragLeave"
@drop.prevent="onFileDrop"
>
<v-row>
<!-- File input on the left -->
<v-col cols="6">
<v-file-input
label="Upload RDF file"
v-model="selectedFile"
:rules="[rules.required, rules.validRdfFile]"
prepend-icon="mdi-upload"
accept=".jsonld,.rdf,.ttl,.nt,.trig,.xml"
@change="onFileSelect"
></v-file-input>
</v-col>
<!-- URL input on the right -->
<v-col cols="6">
<v-text-field
label="Or paste RDF file URL"
v-model="fileUrl"
prepend-icon="mdi-link"
@change="onUrlInput"
></v-text-field>
</v-col>
</v-row>
</v-card>
<!-- Uploaded file details or preview -->
<v-card v-if="fileData" class="mt-5">
<v-card-title>File details:</v-card-title>
<v-card-text>
<p><strong>Name:</strong> {{ fileData.name }}</p>
<p><strong>Size:</strong> {{ formatBytes(fileData.size) }}</p>
<p><strong>Type:</strong> {{ fileData.type }}</p>
<v-img v-if="isImage" :src="fileData.url" max-width="300" />
</v-card-text>
</v-card>
</v-container>
</template>
<script setup>
import { ref, computed } from 'vue'
import rdf from 'rdf-ext'
// Allowed RDF file MIME types
const allowedMimeTypes = [
'application/ld+json', // JSON-LD
'application/rdf+xml', // RDF/XML
'text/turtle', // Turtle
'application/n-triples', // N-Triples
'application/trig', // TriG
]
const selectedFile = ref(null)
const fileUrl = ref('')
const isDragging = ref(false)
const fileData = ref(null)
// Check if the file is an image (for preview purposes)
const isImage = computed(() => {
return fileData.value && fileData.value.type.startsWith('image/')
})
const rules = {
required: value => !!value || 'File is required',
validRdfFile: value => {
if (value && value.type && allowedMimeTypes.includes(value.type)) {
return true
}
return 'Invalid file type. Please upload a valid RDF file.'
}
}
// Format file sizes
const formatBytes = (bytes, decimals = 2) => {
if (bytes === 0) return '0 Bytes'
const k = 1024
const dm = decimals < 0 ? 0 : decimals
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
}
// Handle file selection from the file input
const onFileSelect = (event) => {
const file = event.target.files[0]
if (file) {
validateAndReadFile(file)
}
}
// Handle dragging over the drop area
const onDragOver = () => {
isDragging.value = true
}
// Handle drag leave (when the file is dragged out of the area)
const onDragLeave = () => {
isDragging.value = false
}
// Handle file drop event
const onFileDrop = (event) => {
isDragging.value = false
const file = event.dataTransfer.files[0]
if (file) {
validateAndReadFile(file)
}
}
// Handle URL input for an RDF file
const onUrlInput = () => {
const url = fileUrl.value
fetch(url)
.then(response => response.blob())
.then(blob => {
const file = new File([blob], url.substring(url.lastIndexOf('/') + 1), {
type: blob.type,
})
validateAndReadFile(file)
})
.catch(() => {
alert('Failed to fetch file from URL')
})
}
// Validate file type and read it
const validateAndReadFile = (file) => {
if (!allowedMimeTypes.includes(file.type)) {
alert('Invalid file type. Please upload a valid RDF file.')
return
}
// Further validate if it's an RDF file by trying to parse it
const reader = new FileReader()
reader.onload = (e) => {
const content = e.target.result
try {
rdf.dataset() // Dummy usage of rdf-ext; actual validation logic can be added here if necessary.
// If the file is valid, process and store it
fileData.value = {
name: file.name,
size: file.size,
type: file.type,
url: URL.createObjectURL(file),
}
} catch (error) {
alert('The file content is not a valid RDF serialization.')
}
}
reader.readAsText(file)
}
</script>
<style scoped>
.drag-drop-area {
padding: 20px;
border: 2px solid transparent;
transition: all 0.3s ease;
}
.drag-drop-area.dragover {
border: 2px dashed #3f51b5;
background-color: #f0f0f0;
filter: grayscale(50%);
}
.drag-drop-area .v-col {
display: flex;
align-items: center;
}
</style>

View file

@ -68,6 +68,19 @@
</div>
</template>
</v-autocomplete>
<v-dialog
v-model="uploadMultiRecord"
transition="dialog-top-transition"
>
<TableLoader
@close-multirecord="uploadMultiRecord = false"
:shape_iri="propClass"
:node_idx="props.node_idx"
:propClassList="propClassList"
:parent_shape_iri="props.node_uid"
:parent_property_iri="props.triple_uid"
></TableLoader>
</v-dialog>
</v-input>
</template>
@ -145,6 +158,7 @@
newNodeIdx.value = null
};
provide('saveFormHandler', saveDialogForm);
const uploadMultiRecord = inject('uploadMultiRecord')
// ------------------- //
// Computed properties //
@ -190,6 +204,10 @@
}
}, {immediate: true });
watch(uploadMultiRecord, (newval) => {
if (newval) {console.log("Upload button pressed")}
}, { immediate: true });
watch(rdfDS.data.graph, () => {
console.log("CHECK: graphdata instanceselecteditor")

View file

@ -9,6 +9,7 @@
size="x-small"
class="rounded-lg"
@click="editInstanceItem(record)"
:disabled="props.formOpen"
></v-btn>
</v-card-title>
<v-card-subtitle>{{ toCURIE(record.subtitle, allPrefixes) }}</v-card-subtitle>

View file

@ -36,7 +36,7 @@
density="comfortable"
></v-btn>
&nbsp;
<!-- Add button -->
<!-- Add single button -->
<v-btn v-if="allowAddTriple(triple_idx)"
rounded="0"
elevation="1"
@ -44,6 +44,15 @@
@click="formData.addObject(localNodeUid, localNodeIdx, my_uid)"
density="comfortable"
></v-btn>
<!-- Add multiple button -->
&nbsp;
<v-btn v-if="allowAddMany(triple_idx)"
rounded="0"
elevation="1"
icon="mdi-upload"
@click="selectUploadMultiRecord()"
density="comfortable"
></v-btn>
</v-col>
</v-row>
</span>
@ -52,7 +61,7 @@
</template>
<script setup>
import { ref, onMounted, onBeforeMount, computed, inject, onBeforeUpdate, onBeforeUnmount, watch, toRaw} from 'vue'
import { ref, onMounted, onBeforeMount, computed, inject, onBeforeUpdate, onBeforeUnmount, provide, watch, toRaw} from 'vue'
import { SHACL } from '../modules/namespaces'
import { useRules } from '../composables/rules'
import { nameOrCURIE, addCodeTagsToText} from '../modules/utils';
@ -83,6 +92,8 @@
const shapesDS = inject('shapesDS');
const show_all_fields = inject('show_all_fields');
const { isRequired } = useRules(localPropertyShape.value)
const uploadMultiRecord = ref(false)
provide('uploadMultiRecord', uploadMultiRecord)
// ----------------- //
// Lifecycle methods //
@ -172,6 +183,17 @@
return false
}
function allowAddMany(idx) {
if (allowAddTriple(idx) && [SHACL.IRI.value, SHACL.BlankNodeOrIRI.value, SHACL.BlankNode.value].includes(localPropertyShape.value[SHACL.nodeKind.value])) {
return true
}
return false
}
function selectUploadMultiRecord() {
uploadMultiRecord.value = true;
}
</script>

View file

@ -34,7 +34,8 @@
<span v-if="selectedIRI">
<h2 class="mx-4 mb-4 truncate-heading">
{{ toCURIE(selectedIRI, allPrefixes) }}
&nbsp;&nbsp; <v-btn icon="mdi-plus" size="x-small" variant="tonal" @click="addInstanceItem()"></v-btn>
&nbsp;&nbsp; <v-btn icon="mdi-plus" size="x-small" variant="tonal" @click="addInstanceItem()" :disabled="formOpen"></v-btn>
&nbsp;<v-btn icon="mdi-upload" size="x-small" variant="tonal" @click="selectUploadMultiRecord()" :disabled="formOpen"></v-btn>
</h2>
<p class="mx-4 mb-4" v-html="formattedDescription"></p>
@ -52,6 +53,15 @@
<em>No items</em>
</div>
</span>
<v-dialog
v-model="uploadMultiRecord"
transition="dialog-top-transition"
>
<TableLoader
@close-multirecord="uploadMultiRecord = false"
:shape_iri="selectedIRI"
></TableLoader>
</v-dialog>
</span>
<span v-else style="margin-top: 1em; margin-left: 1em;">
<em>Select a data type</em>
@ -173,6 +183,8 @@
// - FETCH FROM SERVICE IF REQUIRED
// - SET VIEW FROM QUERY
// ---------------------------------------------- //
const uploadMultiRecord = ref(false)
const componentReady = ref(false)
const allPrefixes = reactive({});
const page_ready = ref(false);
provide('allPrefixes', allPrefixes)
@ -226,7 +238,7 @@
provide('submitFn', submitFn)
provide('canSubmit', canSubmit)
const noSubmitDialog = ref(false)
const submitDialog = ref(false)
const submitDialog = ref(false)
provide('submitDialog', submitDialog)
// When user clicks the submit button
watch(submitButtonPressed, (newValue) => {
@ -243,7 +255,9 @@
}, { immediate: true });
function selectUploadMultiRecord() {
uploadMultiRecord.value = true;
}
const activatedInstancesSelectEditor = ref(null)
provide('activatedInstancesSelectEditor', activatedInstancesSelectEditor)

View file

@ -0,0 +1,318 @@
<template>
<v-container fluid>
<v-card class="d-flex flex-column justify-center">
<!-- Drag and Drop area -->
<v-card-title class="justify-center">Upload, drag and drop, or link a CSV file</v-card-title>
<v-card-text>
<v-select :items="allClassIRIs" v-model="selectedShapeIRI" variant="solo" density="compact">
</v-select>
</v-card-text>
<v-card
class="drag-drop-area"
:class="{'dragover': isDragging}"
max-width="80%"
variant="tonal"
@dragover.prevent="onDragOver"
@dragleave.prevent="onDragLeave"
@drop.prevent="onFileDrop"
>
<v-row >
<!-- File input on the left -->
<v-col cols="6" class="justify-center align-center">
<v-input>
<v-icon style="margin-top: 0.25em;">mdi-upload</v-icon>
<v-btn
variant="outlined"
@click="onFileSelect"
style="margin-left: 1em;"
> Upload a CSV/TSV file
</v-btn>
</v-input>
</v-col>
<!-- URL input on the right -->
<v-col cols="6">
<v-text-field
label="Or paste a CSV/TSV file URL"
density="compact"
variant="outlined"
v-model="fileUrl"
prepend-icon="mdi-link"
@change="onUrlInput"
></v-text-field>
</v-col>
</v-row>
</v-card>
<!-- Uploaded file details or preview -->
<v-card v-if="fileData" class="mt-5">
<v-card-title>File details:</v-card-title>
<v-card-text>
<p><strong>Name:</strong> {{ fileData.name }}</p>
<p><strong>Size:</strong> {{ formatBytes(fileData.size) }}</p>
<p><strong>Type:</strong> {{ fileData.type }}</p>
<v-img v-if="isImage" :src="fileData.url" max-width="300" />
</v-card-text>
</v-card>
<v-card v-show="showTable">
<div id="mytable"></div>
<div style="display: flex; margin: 1em; margin-left: auto;">
<v-btn
text="Cancel"
@click="closeDialog()"
style="margin-left: auto; margin-right: 1em;"
prepend-icon="mdi-close-box"
></v-btn>
<v-btn
text="Save"
type="submit"
@click="saveData()"
prepend-icon="mdi-content-save"
></v-btn>
</div>
</v-card>
</v-card>
</v-container>
</template>
<script setup>
import { ref, computed, onMounted, inject, toRaw} from 'vue'
import {TabulatorFull as Tabulator} from 'tabulator-tables';
import { DataTable } from '../classes/DataTable'
import { toCURIE } from 'shacl-tulip';
// ----- //
// Props //
// ----- //
const props = defineProps({
shape_iri: String,
node_idx: String,
propClassList: Array,
parent_shape_iri: String,
parent_property_iri: String,
})
const localShapeIri = ref(props.shape_iri);
console.log(localShapeIri.value)
const localNodeIdx = ref(props.node_idx);
console.log(localNodeIdx.value)
console.log(props.propClassList)
const shapesDS = inject('shapesDS')
const rdfDS = inject('rdfDS')
const formData = inject('formData')
const shape_obj = shapesDS.data.nodeShapes[localShapeIri.value]
const allPrefixes = inject('allPrefixes');
const ID_IRI = inject('ID_IRI')
const allClassIRIs = ref([])
const selectedShapeIRI = ref(localShapeIri.value)
onMounted( () => {
// if the node_idx is passed, it means the TableLoader component
// was activated from within the NodeShapeEditor (via InstancesSelectEditor).
if (props.node_idx) {
allClassIRIs.value = props.propClassList
} else {
allClassIRIs.value.push(
{
title: toCURIE(localShapeIri.value, allPrefixes),
value: localShapeIri.value
}
)
}
})
const emit = defineEmits(['close-multirecord']);
function closeDialog() {
emit('close-multirecord');
};
// Allowed RDF file MIME types
const allowedMimeTypes = [
'text/csv',
'text/tsv'
]
var table
const showTable = ref(false)
const selectedFile = ref(null)
const fileUrl = ref('')
const isDragging = ref(false)
const fileData = ref(null)
// Check if the file is an image (for preview purposes)
const isImage = computed(() => {
return fileData.value && fileData.value.type.startsWith('image/')
})
const rules = {
// required: value => !!value || 'File is required',
validRdfFile: value => {
if (value && value.type && allowedMimeTypes.includes(value.type)) {
return true
}
// console.log(value.type)
return 'Invalid file type. Please upload a valid CSV/TSV file.'
}
}
onMounted(() => {
table = new Tabulator("#mytable", {
// height:'50vh',
layout:"fitColumns",
// importFormat:"csv",
autoColumns: true
});
})
// Format file sizes
const formatBytes = (bytes, decimals = 2) => {
if (bytes === 0) return '0 Bytes'
const k = 1024
const dm = decimals < 0 ? 0 : decimals
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
}
// Handle file upload button
function onFileSelect() {
table.import("csv", ".csv,.tsv")
.then(() => {
showTable.value = true;
})
.catch(() => {
console.error("Table importing failed!")
})
}
// Handle dragging over the drop area
const onDragOver = () => {
isDragging.value = true
}
// Handle drag leave (when the file is dragged out of the area)
const onDragLeave = () => {
isDragging.value = false
}
// Handle file drop event
const onFileDrop = (event) => {
isDragging.value = false
const file = event.dataTransfer.files[0]
if (file) {
validateAndReadFile(file)
}
}
// Handle URL input
const onUrlInput = () => {
const url = fileUrl.value
fetch(url)
.then(response => response.blob())
.then(blob => {
const file = new File([blob], url.substring(url.lastIndexOf('/') + 1), {
type: blob.type,
})
validateAndReadFile(file)
})
.catch(() => {
alert('Failed to fetch file from URL')
})
}
// Validate file type and read it
const validateAndReadFile = (file) => {
if (!allowedMimeTypes.includes(file.type)) {
alert('Invalid file type. Please upload a valid CSV/TSV file.')
return
}
// Further validate if it's an RDF file by trying to parse it
const reader = new FileReader()
reader.onload = (e) => {
const content = e.target.result
table = new Tabulator("#mytable", {
// height:'50vh',
layout:"fitColumns",
importFormat:"csv",
autoColumns: true
});
table.on("tableBuilt", function(){
table.setData(content);
showTable.value = true;
});
}
reader.readAsText(file)
}
function saveData() {
console.log(table.getColumnDefinitions())
console.log(table.getData())
console.log(toRaw(shape_obj))
console.log(toRaw(shape_obj.properties))
console.log(table.getColumnDefinitions().map(col => col.title))
const dT = new DataTable(
table,
selectedShapeIRI.value,
localNodeIdx.value,
shapesDS,
formData,
toRaw(allPrefixes),
ID_IRI.value
)
dT.saveTable(rdfDS, props.parent_shape_iri, props.parent_property_iri)
closeDialog()
// console.log(toRaw(formData.content))
// shape_obj = shapesDS.data.nodeShapes[localShapeIri.value]
};
</script>
<style scoped>
.drag-drop-area {
padding: 20px;
padding-bottom: 0;
/* border: 1px solid rgb(201, 201, 201); */
border: 1px solid transparent;
transition: all 0.3s ease;
margin: auto;
margin-bottom:1em;
min-width: 80vw;
}
.drag-drop-area.dragover {
border: 2px dashed #3f51b5;
background-color: #f0f0f0;
filter: grayscale(50%);
}
.drag-drop-area .v-col {
display: flex;
align-items: center;
}
#mytable {
margin: 1em 1em;
}
</style>
<style lang="scss">
@import "/node_modules/tabulator-tables/dist/css/tabulator_site.min.css";
</style>