forked from geolba/tethys.backend
feat: Enhance dataset management and improve frontend components
- Added preloads 'allowed_extensions_mimetypes' and 'dependent_array_min_length' in adonisrc.ts - Updated @symfony/webpack-encore from ^4.6.1 to ^5.0.1 - AdminuserController: Implemented pagination for 10 records in index method - Enabled reviewers to reject datasets to editors with email notifications (DatasetController.ts) - Submitter DatasetController: Files now loaded in ascending order (sort_order) in edit mode - file.ts: Removed serialization of fileData due to browser issues - Modified FileUpload.vue to mark already uploaded files as deleted - Improved keyword search in SearchCategoryAutocomplete.vue - Started development on Category.vue for submitters to categorize DDC - Added new route /dataset/categorize in routes.ts - Introduced 2 new rules in start/rules: allowed_extensions_mimetypes.ts and dependent_array_min_length.ts - Performed npm updates
This commit is contained in:
parent
49bd96ee77
commit
f67b736a88
|
@ -31,7 +31,9 @@ export default defineConfig({
|
||||||
() => import('#start/rules/translated_language'),
|
() => import('#start/rules/translated_language'),
|
||||||
() => import('#start/rules/unique_person'),
|
() => import('#start/rules/unique_person'),
|
||||||
() => import('#start/rules/file_length'),
|
() => import('#start/rules/file_length'),
|
||||||
() => import('#start/rules/file_scan')
|
() => import('#start/rules/file_scan'),
|
||||||
|
() => import('#start/rules/allowed_extensions_mimetypes'),
|
||||||
|
() => import('#start/rules/dependent_array_min_length')
|
||||||
],
|
],
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
@ -45,7 +45,7 @@ export default class AdminuserController {
|
||||||
// .filter(qs)
|
// .filter(qs)
|
||||||
// .preload('focusInterests')
|
// .preload('focusInterests')
|
||||||
// .preload('role')
|
// .preload('role')
|
||||||
.paginate(page, 5);
|
.paginate(page, 10);
|
||||||
|
|
||||||
// var test = request.all();
|
// var test = request.all();
|
||||||
|
|
||||||
|
|
|
@ -279,7 +279,7 @@ export default class DatasetsController {
|
||||||
let emailStatusMessage = '';
|
let emailStatusMessage = '';
|
||||||
|
|
||||||
if (sendMail == true) {
|
if (sendMail == true) {
|
||||||
if (dataset.user.email && validRecipientEmail) {
|
if (dataset.editor.email && validRecipientEmail) {
|
||||||
try {
|
try {
|
||||||
await mail.send((message) => {
|
await mail.send((message) => {
|
||||||
message.to(dataset.editor.email).subject('Dataset Rejection Notification').html(`
|
message.to(dataset.editor.email).subject('Dataset Rejection Notification').html(`
|
||||||
|
|
|
@ -216,7 +216,13 @@ export default class DatasetController {
|
||||||
authors: vine
|
authors: vine
|
||||||
.array(
|
.array(
|
||||||
vine.object({
|
vine.object({
|
||||||
email: vine.string().trim().maxLength(255).email().normalizeEmail().isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
|
email: vine
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.maxLength(255)
|
||||||
|
.email()
|
||||||
|
.normalizeEmail()
|
||||||
|
.isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
|
||||||
first_name: vine.string().trim().minLength(3).maxLength(255),
|
first_name: vine.string().trim().minLength(3).maxLength(255),
|
||||||
last_name: vine.string().trim().minLength(3).maxLength(255),
|
last_name: vine.string().trim().minLength(3).maxLength(255),
|
||||||
}),
|
}),
|
||||||
|
@ -226,7 +232,13 @@ export default class DatasetController {
|
||||||
contributors: vine
|
contributors: vine
|
||||||
.array(
|
.array(
|
||||||
vine.object({
|
vine.object({
|
||||||
email: vine.string().trim().maxLength(255).email().normalizeEmail().isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
|
email: vine
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.maxLength(255)
|
||||||
|
.email()
|
||||||
|
.normalizeEmail()
|
||||||
|
.isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
|
||||||
first_name: vine.string().trim().minLength(3).maxLength(255),
|
first_name: vine.string().trim().minLength(3).maxLength(255),
|
||||||
last_name: vine.string().trim().minLength(3).maxLength(255),
|
last_name: vine.string().trim().minLength(3).maxLength(255),
|
||||||
pivot_contributor_type: vine.enum(Object.keys(ContributorTypes)),
|
pivot_contributor_type: vine.enum(Object.keys(ContributorTypes)),
|
||||||
|
@ -293,7 +305,13 @@ export default class DatasetController {
|
||||||
authors: vine
|
authors: vine
|
||||||
.array(
|
.array(
|
||||||
vine.object({
|
vine.object({
|
||||||
email: vine.string().trim().maxLength(255).email().normalizeEmail().isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
|
email: vine
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.maxLength(255)
|
||||||
|
.email()
|
||||||
|
.normalizeEmail()
|
||||||
|
.isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
|
||||||
first_name: vine.string().trim().minLength(3).maxLength(255),
|
first_name: vine.string().trim().minLength(3).maxLength(255),
|
||||||
last_name: vine.string().trim().minLength(3).maxLength(255),
|
last_name: vine.string().trim().minLength(3).maxLength(255),
|
||||||
}),
|
}),
|
||||||
|
@ -303,7 +321,13 @@ export default class DatasetController {
|
||||||
contributors: vine
|
contributors: vine
|
||||||
.array(
|
.array(
|
||||||
vine.object({
|
vine.object({
|
||||||
email: vine.string().trim().maxLength(255).email().normalizeEmail().isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
|
email: vine
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.maxLength(255)
|
||||||
|
.email()
|
||||||
|
.normalizeEmail()
|
||||||
|
.isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
|
||||||
first_name: vine.string().trim().minLength(3).maxLength(255),
|
first_name: vine.string().trim().minLength(3).maxLength(255),
|
||||||
last_name: vine.string().trim().minLength(3).maxLength(255),
|
last_name: vine.string().trim().minLength(3).maxLength(255),
|
||||||
pivot_contributor_type: vine.enum(Object.keys(ContributorTypes)),
|
pivot_contributor_type: vine.enum(Object.keys(ContributorTypes)),
|
||||||
|
@ -791,7 +815,9 @@ export default class DatasetController {
|
||||||
builder.orderBy('id', 'asc').withCount('datasets');
|
builder.orderBy('id', 'asc').withCount('datasets');
|
||||||
})
|
})
|
||||||
.preload('references')
|
.preload('references')
|
||||||
.preload('files');
|
.preload('files', (query) => {
|
||||||
|
query.orderBy('sort_order', 'asc'); // Sort by sort_order column
|
||||||
|
});
|
||||||
|
|
||||||
const dataset = await datasetQuery.firstOrFail();
|
const dataset = await datasetQuery.firstOrFail();
|
||||||
const validStates = ['inprogress', 'rejected_editor'];
|
const validStates = ['inprogress', 'rejected_editor'];
|
||||||
|
@ -961,6 +987,18 @@ export default class DatasetController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// save coverage
|
||||||
|
const coverageData = request.input('coverage');
|
||||||
|
if (coverageData) {
|
||||||
|
if (coverageData.id) {
|
||||||
|
const coverage = await Coverage.findOrFail(coverageData.id);
|
||||||
|
coverage.merge(coverageData);
|
||||||
|
if (coverage.$isDirty) {
|
||||||
|
await coverage.useTransaction(trx).save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Save already existing files
|
// Save already existing files
|
||||||
const files = request.input('fileInputs', []);
|
const files = request.input('fileInputs', []);
|
||||||
for (const fileData of files) {
|
for (const fileData of files) {
|
||||||
|
@ -1016,6 +1054,14 @@ export default class DatasetController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const filesToDelete = request.input('filesToDelete', []);
|
||||||
|
for (const fileData of filesToDelete) {
|
||||||
|
if (fileData.id) {
|
||||||
|
const file = await File.findOrFail(fileData.id);
|
||||||
|
await file.useTransaction(trx).delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// save collection
|
// save collection
|
||||||
// const collection: Collection | null = await Collection.query().where('id', 21).first();
|
// const collection: Collection | null = await Collection.query().where('id', 21).first();
|
||||||
// collection && (await dataset.useTransaction(trx).related('collections').attach([collection.id]));
|
// collection && (await dataset.useTransaction(trx).related('collections').attach([collection.id]));
|
||||||
|
@ -1036,6 +1082,10 @@ export default class DatasetController {
|
||||||
|
|
||||||
await trx.commit();
|
await trx.commit();
|
||||||
console.log('Dataset and related models created successfully');
|
console.log('Dataset and related models created successfully');
|
||||||
|
|
||||||
|
session.flash('message', 'Dataset has been updated successfully');
|
||||||
|
// return response.redirect().toRoute('user.index');
|
||||||
|
return response.redirect().toRoute('dataset.edit', [dataset.id]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (trx !== null) {
|
if (trx !== null) {
|
||||||
await trx.rollback();
|
await trx.rollback();
|
||||||
|
@ -1044,10 +1094,6 @@ export default class DatasetController {
|
||||||
// throw new ValidationException(true, { 'upload error': `failed to create dataset and related models. ${error}` });
|
// throw new ValidationException(true, { 'upload error': `failed to create dataset and related models. ${error}` });
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
session.flash('message', 'Dataset has been updated successfully');
|
|
||||||
// return response.redirect().toRoute('user.index');
|
|
||||||
return response.redirect().back();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private extractVariableNameAndSortOrder(inputString: string): { clientFileName: string; sortOrder?: number } {
|
private extractVariableNameAndSortOrder(inputString: string): { clientFileName: string; sortOrder?: number } {
|
||||||
|
|
|
@ -46,7 +46,12 @@ export default class Dataset extends DatasetExtension {
|
||||||
@column({ columnName: 'creating_corporation' })
|
@column({ columnName: 'creating_corporation' })
|
||||||
public creating_corporation: string;
|
public creating_corporation: string;
|
||||||
|
|
||||||
@column.dateTime({ columnName: 'embargo_date' })
|
@column.dateTime({
|
||||||
|
columnName: 'embargo_date',
|
||||||
|
serialize: (value: Date | null) => {
|
||||||
|
return value ? dayjs(value).format('YYYY-MM-DD') : value;
|
||||||
|
},
|
||||||
|
})
|
||||||
public embargo_date: DateTime;
|
public embargo_date: DateTime;
|
||||||
|
|
||||||
@column({})
|
@column({})
|
||||||
|
|
|
@ -123,25 +123,25 @@ export default class File extends BaseModel {
|
||||||
|
|
||||||
readonly webkitRelativePath: string = '';
|
readonly webkitRelativePath: string = '';
|
||||||
|
|
||||||
@computed({
|
// @computed({
|
||||||
serializeAs: 'fileData',
|
// serializeAs: 'fileData',
|
||||||
})
|
// })
|
||||||
public get fileData(): string {
|
// public get fileData(): string {
|
||||||
try {
|
// try {
|
||||||
const fileContent: Buffer = fs.readFileSync(this.filePath);
|
// const fileContent: Buffer = fs.readFileSync(this.filePath);
|
||||||
// Create a Blob from the file content
|
// // Create a Blob from the file content
|
||||||
// const blob = new Blob([fileContent], { type: this.type }); // Adjust
|
// // const blob = new Blob([fileContent], { type: this.type }); // Adjust
|
||||||
// let fileSrc = URL.createObjectURL(blob);
|
// // let fileSrc = URL.createObjectURL(blob);
|
||||||
// return fileSrc;
|
// // return fileSrc;
|
||||||
|
|
||||||
// create a JSON string that contains the data in the property "blob"
|
// // create a JSON string that contains the data in the property "blob"
|
||||||
const json = JSON.stringify({ blob: fileContent.toString('base64') });
|
// const json = JSON.stringify({ blob: fileContent.toString('base64') });
|
||||||
return json;
|
// return json;
|
||||||
} catch (err) {
|
// } catch (err) {
|
||||||
// console.error(`Error reading file: ${err}`);
|
// // console.error(`Error reading file: ${err}`);
|
||||||
return '';
|
// return '';
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
public async createHashValues(trx?: TransactionClientContract) {
|
public async createHashValues(trx?: TransactionClientContract) {
|
||||||
const hashtypes: string[] = ['md5', 'sha512'];
|
const hashtypes: string[] = ['md5', 'sha512'];
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import vine, { SimpleMessagesProvider } from '@vinejs/vine';
|
import vine, { SimpleMessagesProvider } from '@vinejs/vine';
|
||||||
import { TitleTypes, DescriptionTypes, ContributorTypes, ReferenceIdentifierTypes, RelationTypes } from '#contracts/enums';
|
import { TitleTypes, DescriptionTypes, ContributorTypes, ReferenceIdentifierTypes, RelationTypes } from '#contracts/enums';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import MimeType from '#models/mime_type';
|
// import MimeType from '#models/mime_type';
|
||||||
|
|
||||||
const enabledExtensions = await MimeType.query().select('file_extension').where('enabled', true).exec();
|
// const enabledExtensions = await MimeType.query().select('file_extension').where('enabled', true).exec();
|
||||||
const extensions = enabledExtensions
|
// const extensions = enabledExtensions
|
||||||
.map((extension) => {
|
// .map((extension) => {
|
||||||
return extension.file_extension.split('|');
|
// return extension.file_extension.split('|');
|
||||||
})
|
// })
|
||||||
.flat();
|
// .flat();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the dataset's creation action
|
* Validates the dataset's creation action
|
||||||
|
@ -137,8 +137,9 @@ export const createDatasetValidator = vine.compile(
|
||||||
vine
|
vine
|
||||||
.myfile({
|
.myfile({
|
||||||
size: '512mb',
|
size: '512mb',
|
||||||
extnames: extensions,
|
//extnames: extensions,
|
||||||
})
|
})
|
||||||
|
.allowedMimetypeExtensions()
|
||||||
.filenameLength({ clientNameSizeLimit: 100 })
|
.filenameLength({ clientNameSizeLimit: 100 })
|
||||||
.fileScan({ removeInfected: true }),
|
.fileScan({ removeInfected: true }),
|
||||||
)
|
)
|
||||||
|
@ -267,13 +268,25 @@ export const updateDatasetValidator = vine.compile(
|
||||||
.minLength(3)
|
.minLength(3)
|
||||||
.distinct('value'),
|
.distinct('value'),
|
||||||
// last step
|
// last step
|
||||||
files: vine.array(
|
files: vine
|
||||||
vine.myfile({
|
.array(
|
||||||
|
vine
|
||||||
|
.myfile({
|
||||||
size: '512mb',
|
size: '512mb',
|
||||||
extnames: extensions,
|
//extnames: extensions,
|
||||||
}),
|
})
|
||||||
|
.allowedMimetypeExtensions()
|
||||||
|
.filenameLength({ clientNameSizeLimit: 100 })
|
||||||
|
.fileScan({ removeInfected: true }),
|
||||||
|
).dependentArrayMinLength({ dependentArray: 'fileInputs', min: 1}),
|
||||||
|
fileInputs: vine
|
||||||
|
.array(
|
||||||
|
vine
|
||||||
|
.object({
|
||||||
|
label: vine.string().trim().maxLength(100),
|
||||||
|
//extnames: extensions,
|
||||||
|
})
|
||||||
),
|
),
|
||||||
// .minLength(1),
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
4275
package-lock.json
generated
4275
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -37,7 +37,7 @@
|
||||||
"@mdi/js": "^7.1.96",
|
"@mdi/js": "^7.1.96",
|
||||||
"@poppinss/utils": "^6.7.2",
|
"@poppinss/utils": "^6.7.2",
|
||||||
"@swc/core": "^1.4.2",
|
"@swc/core": "^1.4.2",
|
||||||
"@symfony/webpack-encore": "^4.6.1",
|
"@symfony/webpack-encore": "^5.0.1",
|
||||||
"@tailwindcss/forms": "^0.5.2",
|
"@tailwindcss/forms": "^0.5.2",
|
||||||
"@types/bcryptjs": "^2.4.6",
|
"@types/bcryptjs": "^2.4.6",
|
||||||
"@types/clamscan": "^2.0.4",
|
"@types/clamscan": "^2.0.4",
|
||||||
|
@ -71,6 +71,7 @@
|
||||||
"vue": "^3.4.26",
|
"vue": "^3.4.26",
|
||||||
"vue-facing-decorator": "^3.0.0",
|
"vue-facing-decorator": "^3.0.0",
|
||||||
"vue-loader": "^17.0.1",
|
"vue-loader": "^17.0.1",
|
||||||
|
"webpack-dev-server": "^5.1.0",
|
||||||
"xslt3": "^2.5.0"
|
"xslt3": "^2.5.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -39,7 +39,7 @@ declare module '@adonisjs/core/http' {
|
||||||
export function isBodyParserFile(file: MultipartFile | unknown): boolean {
|
export function isBodyParserFile(file: MultipartFile | unknown): boolean {
|
||||||
return !!(file && typeof file === 'object' && 'isMultipartFile' in file);
|
return !!(file && typeof file === 'object' && 'isMultipartFile' in file);
|
||||||
}
|
}
|
||||||
async function getEnabledExtensions() {
|
export async function getEnabledExtensions() {
|
||||||
const enabledExtensions = await MimeType.query().select('file_extension').where('enabled', true).exec();
|
const enabledExtensions = await MimeType.query().select('file_extension').where('enabled', true).exec();
|
||||||
const extensions = enabledExtensions
|
const extensions = enabledExtensions
|
||||||
.map((extension) => {
|
.map((extension) => {
|
||||||
|
|
|
@ -43,11 +43,13 @@
|
||||||
"assets/resources_js_Pages_Reviewer_Dataset_Index_vue.js": "http://localhost:8080/assets/resources_js_Pages_Reviewer_Dataset_Index_vue.js",
|
"assets/resources_js_Pages_Reviewer_Dataset_Index_vue.js": "http://localhost:8080/assets/resources_js_Pages_Reviewer_Dataset_Index_vue.js",
|
||||||
"assets/resources_js_Pages_Reviewer_Dataset_Reject_vue.js": "http://localhost:8080/assets/resources_js_Pages_Reviewer_Dataset_Reject_vue.js",
|
"assets/resources_js_Pages_Reviewer_Dataset_Reject_vue.js": "http://localhost:8080/assets/resources_js_Pages_Reviewer_Dataset_Reject_vue.js",
|
||||||
"assets/resources_js_Pages_Reviewer_Dataset_Review_vue.js": "http://localhost:8080/assets/resources_js_Pages_Reviewer_Dataset_Review_vue.js",
|
"assets/resources_js_Pages_Reviewer_Dataset_Review_vue.js": "http://localhost:8080/assets/resources_js_Pages_Reviewer_Dataset_Review_vue.js",
|
||||||
"assets/resources_js_Pages_Submitter_Dataset_Create_vue-resources_js_utils_toast_css-resources_js_Com-0c4b04.css": "http://localhost:8080/assets/resources_js_Pages_Submitter_Dataset_Create_vue-resources_js_utils_toast_css-resources_js_Com-0c4b04.css",
|
"assets/resources_js_Pages_Submitter_Dataset_Category_vue.css": "http://localhost:8080/assets/resources_js_Pages_Submitter_Dataset_Category_vue.css",
|
||||||
"assets/resources_js_Pages_Submitter_Dataset_Create_vue-resources_js_utils_toast_css-resources_js_Com-0c4b04.js": "http://localhost:8080/assets/resources_js_Pages_Submitter_Dataset_Create_vue-resources_js_utils_toast_css-resources_js_Com-0c4b04.js",
|
"assets/resources_js_Pages_Submitter_Dataset_Category_vue.js": "http://localhost:8080/assets/resources_js_Pages_Submitter_Dataset_Category_vue.js",
|
||||||
|
"assets/resources_js_Pages_Submitter_Dataset_Create_vue-resources_js_utils_toast_css-resources_js_Com-03a898.css": "http://localhost:8080/assets/resources_js_Pages_Submitter_Dataset_Create_vue-resources_js_utils_toast_css-resources_js_Com-03a898.css",
|
||||||
|
"assets/resources_js_Pages_Submitter_Dataset_Create_vue-resources_js_utils_toast_css-resources_js_Com-03a898.js": "http://localhost:8080/assets/resources_js_Pages_Submitter_Dataset_Create_vue-resources_js_utils_toast_css-resources_js_Com-03a898.js",
|
||||||
"assets/resources_js_Pages_Submitter_Dataset_Delete_vue.js": "http://localhost:8080/assets/resources_js_Pages_Submitter_Dataset_Delete_vue.js",
|
"assets/resources_js_Pages_Submitter_Dataset_Delete_vue.js": "http://localhost:8080/assets/resources_js_Pages_Submitter_Dataset_Delete_vue.js",
|
||||||
"assets/resources_js_Pages_Submitter_Dataset_Edit_vue-resources_js_utils_toast_css-resources_js_Compo-b6a1eb.css": "http://localhost:8080/assets/resources_js_Pages_Submitter_Dataset_Edit_vue-resources_js_utils_toast_css-resources_js_Compo-b6a1eb.css",
|
"assets/resources_js_Pages_Submitter_Dataset_Edit_vue-resources_js_utils_toast_css-resources_js_Compo-a37b65.css": "http://localhost:8080/assets/resources_js_Pages_Submitter_Dataset_Edit_vue-resources_js_utils_toast_css-resources_js_Compo-a37b65.css",
|
||||||
"assets/resources_js_Pages_Submitter_Dataset_Edit_vue-resources_js_utils_toast_css-resources_js_Compo-b6a1eb.js": "http://localhost:8080/assets/resources_js_Pages_Submitter_Dataset_Edit_vue-resources_js_utils_toast_css-resources_js_Compo-b6a1eb.js",
|
"assets/resources_js_Pages_Submitter_Dataset_Edit_vue-resources_js_utils_toast_css-resources_js_Compo-a37b65.js": "http://localhost:8080/assets/resources_js_Pages_Submitter_Dataset_Edit_vue-resources_js_utils_toast_css-resources_js_Compo-a37b65.js",
|
||||||
"assets/resources_js_Pages_Submitter_Dataset_Index_vue.css": "http://localhost:8080/assets/resources_js_Pages_Submitter_Dataset_Index_vue.css",
|
"assets/resources_js_Pages_Submitter_Dataset_Index_vue.css": "http://localhost:8080/assets/resources_js_Pages_Submitter_Dataset_Index_vue.css",
|
||||||
"assets/resources_js_Pages_Submitter_Dataset_Index_vue.js": "http://localhost:8080/assets/resources_js_Pages_Submitter_Dataset_Index_vue.js",
|
"assets/resources_js_Pages_Submitter_Dataset_Index_vue.js": "http://localhost:8080/assets/resources_js_Pages_Submitter_Dataset_Index_vue.js",
|
||||||
"assets/resources_js_Pages_Submitter_Dataset_Release_vue.js": "http://localhost:8080/assets/resources_js_Pages_Submitter_Dataset_Release_vue.js",
|
"assets/resources_js_Pages_Submitter_Dataset_Release_vue.js": "http://localhost:8080/assets/resources_js_Pages_Submitter_Dataset_Release_vue.js",
|
||||||
|
|
|
@ -3,10 +3,6 @@
|
||||||
class="relative h-full flex flex-col bg-white dark:bg-slate-900/70 shadow-xl rounded-md"
|
class="relative h-full flex flex-col bg-white dark:bg-slate-900/70 shadow-xl rounded-md"
|
||||||
v-on:dragenter="dragEnterHandler" v-on:dragleave="dragLeaveHandler" v-on:dragover="dragOverHandler"
|
v-on:dragenter="dragEnterHandler" v-on:dragleave="dragLeaveHandler" v-on:dragover="dragOverHandler"
|
||||||
v-on:drop="dropHandler">
|
v-on:drop="dropHandler">
|
||||||
<!-- ondrop="dropHandler(event);"
|
|
||||||
ondragover="dragOverHandler(event);"
|
|
||||||
ondragleave="dragLeaveHandler(event);"
|
|
||||||
ondragenter="dragEnterHandler(event);" -->
|
|
||||||
|
|
||||||
<!-- overlay -->
|
<!-- overlay -->
|
||||||
<div id="overlay" ref="overlay"
|
<div id="overlay" ref="overlay"
|
||||||
|
@ -23,15 +19,6 @@
|
||||||
|
|
||||||
<!-- scroll area -->
|
<!-- scroll area -->
|
||||||
<div class="h-full p-8 w-full h-full flex flex-col">
|
<div class="h-full p-8 w-full h-full flex flex-col">
|
||||||
<!-- <header id="dropzone" class="border-dashed border-2 border-gray-400 py-12 flex flex-col justify-center items-center">
|
|
||||||
<p class="mb-3 font-semibold text-gray-900 flex flex-wrap justify-center">
|
|
||||||
<span>Drag and drop your</span> <span>files anywhere or</span>
|
|
||||||
</p>
|
|
||||||
<input id="hidden-input" type="file" multiple class="hidden" />
|
|
||||||
<button id="button" class="mt-2 rounded-sm px-3 py-1 bg-gray-200 hover:bg-gray-300 focus:shadow-outline focus:outline-none">
|
|
||||||
Upload a file
|
|
||||||
</button>
|
|
||||||
</header> -->
|
|
||||||
<header class="flex items-center justify-center w-full">
|
<header class="flex items-center justify-center w-full">
|
||||||
<label for="dropzone-file"
|
<label for="dropzone-file"
|
||||||
class="flex flex-col items-center justify-center w-full h-64 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 dark:bg-gray-700 hover:bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:bg-gray-600">
|
class="flex flex-col items-center justify-center w-full h-64 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 dark:bg-gray-700 hover:bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:bg-gray-600">
|
||||||
|
@ -47,14 +34,12 @@
|
||||||
</p>
|
</p>
|
||||||
<!-- <p class="text-xs text-gray-500 dark:text-gray-400">SVG, PNG, JPG or GIF (MAX. 800x400px)</p> -->
|
<!-- <p class="text-xs text-gray-500 dark:text-gray-400">SVG, PNG, JPG or GIF (MAX. 800x400px)</p> -->
|
||||||
</div>
|
</div>
|
||||||
<input id="dropzone-file" type="file" class="hidden" @change="onChangeFile" multiple="true"/>
|
<input id="dropzone-file" type="file" class="hidden" @change="onChangeFile" multiple="true" />
|
||||||
</label>
|
</label>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<h1 class="pt-8 pb-3 font-semibold sm:text-lg text-gray-900">To Upload</h1>
|
<h1 class="pt-8 pb-3 font-semibold sm:text-lg text-gray-900">To Upload</h1>
|
||||||
|
|
||||||
<!-- <ul id="gallery" class="flex flex-1 flex-wrap -m-1"> -->
|
|
||||||
|
|
||||||
<draggable id="galleryxy" tag="ul" class="flex flex-1 flex-wrap -m-1" v-model="items" item-key="sort_order">
|
<draggable id="galleryxy" tag="ul" class="flex flex-1 flex-wrap -m-1" v-model="items" item-key="sort_order">
|
||||||
<!-- <li
|
<!-- <li
|
||||||
v-if="files.length == 0"
|
v-if="files.length == 0"
|
||||||
|
@ -86,52 +71,44 @@
|
||||||
</li> -->
|
</li> -->
|
||||||
<template #item="{ index, element }">
|
<template #item="{ index, element }">
|
||||||
<li class="block p-1 w-1/2 sm:w-1/3 md:w-1/4 lg:w-1/6 xl:w-1/8 h-24" :key="index">
|
<li class="block p-1 w-1/2 sm:w-1/3 md:w-1/4 lg:w-1/6 xl:w-1/8 h-24" :key="index">
|
||||||
<article v-if="element.type.match('image.*')" tabindex="0"
|
|
||||||
class="bg-gray-50 group hasImage w-full h-full rounded-md cursor-pointer relative text-transparent hover:text-white shadow-sm">
|
|
||||||
<!-- :src="element.fileSrc" :src="generateURL(element)" -->
|
<!-- :src="element.fileSrc" :src="generateURL(element)" -->
|
||||||
<img :alt="element.name" :src="element.fileSrc"
|
<!-- <article
|
||||||
class="img-preview w-full h-full sticky object-cover rounded-md bg-fixed opacity-75" />
|
v-if="element.type.match('image.*')"
|
||||||
<!-- <section
|
tabindex="0"
|
||||||
class="hasError text-red-500 shadow-sm font-semibold flex flex-row rounded-md text-xs break-words w-full h-full z-21 absolute top-0 py-2 px-3"
|
class="bg-gray-50 group hasImage w-full h-full rounded-md cursor-pointer relative text-transparent hover:text-white shadow-sm"
|
||||||
>
|
>
|
||||||
<p class="p-1 text-xs" v-if="errors[`files.${index}`]">
|
|
||||||
{{ errors[`files.${index}`].join(', ') }}
|
<img
|
||||||
</p>
|
:alt="element.name"
|
||||||
<button
|
:src="element.fileSrc"
|
||||||
class="delete ml-auto focus:outline-none hover:bg-gray-300 p-1 rounded-md"
|
class="img-preview w-full h-full sticky object-cover rounded-md bg-fixed opacity-75"
|
||||||
@click="removeFile(index)"
|
/>
|
||||||
>
|
<section class="flex flex-col rounded-md text-xs break-words w-full h-full z-20 absolute top-0 py-2 px-3">
|
||||||
<DeleteIcon></DeleteIcon>
|
|
||||||
</button>
|
|
||||||
</section> -->
|
|
||||||
<section
|
|
||||||
class="flex flex-col rounded-md text-xs break-words w-full h-full z-20 absolute top-0 py-2 px-3">
|
|
||||||
<h1 class="flex-1">{{ element.name }}</h1>
|
<h1 class="flex-1">{{ element.name }}</h1>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<p class="p-1 size text-xs">{{ getFileSize(element) }}</p>
|
<p class="p-1 size text-xs">{{ getFileSize(element) }}</p>
|
||||||
<p class="p-1 size text-xs text-gray-700">{{ element.sort_order }}</p>
|
<p class="p-1 size text-xs text-gray-700">{{ element.sort_order }}</p>
|
||||||
<button class="delete ml-auto focus:outline-none hover:bg-gray-300 p-1 rounded-md"
|
<button
|
||||||
@click="removeFile(index)">
|
class="delete ml-auto focus:outline-none hover:bg-gray-300 p-1 rounded-md"
|
||||||
|
@click.prevent="removeFile(index)"
|
||||||
|
>
|
||||||
<DeleteIcon></DeleteIcon>
|
<DeleteIcon></DeleteIcon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<!-- <div class="text-red-400 text-sm w-full h-full rounded-md cursor-pointer relative shadow-sm" v-if="errors[`files.${index}`]">
|
</article> -->
|
||||||
{{ errors[`files.${index}`].join(', ') }}
|
|
||||||
</div> -->
|
|
||||||
</article>
|
|
||||||
<!-- :class="errors && errors[`files.${index}`] ? 'bg-red-400' : 'bg-gray-100'" -->
|
<!-- :class="errors && errors[`files.${index}`] ? 'bg-red-400' : 'bg-gray-100'" -->
|
||||||
<article v-else tabindex="0"
|
<article tabindex="0"
|
||||||
class="bg-gray-100 group w-full h-full rounded-md cursor-pointer relative shadow-sm">
|
class="bg-gray-100 group w-full h-full rounded-md cursor-pointer relative shadow-sm">
|
||||||
<section
|
<section
|
||||||
class="flex flex-col rounded-md text-xs break-words w-full h-full z-20 absolute top-0 py-2 px-3">
|
class="flex flex-col rounded-md text-xs break-words w-full h-full z-20 absolute top-0 py-2 px-3">
|
||||||
<h1 class="flex-1 text-gray-700 group-hover:text-blue-800">{{ element.name }}</h1>
|
<h1 class="flex-1 text-gray-700 group-hover:text-blue-800">{{ element.name }}</h1>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<p class="p-1 size text-xs text-gray-700">{{ getFileSize(element) }}</p>
|
<p class="p-1 size text-xs text-gray-700">{{ getFileSize(element) }}</p>
|
||||||
<p class="p-1 size text-xs text-gray-700">{{ element.sort_order }}</p>
|
<p class="p-1 size text-xs text-gray-700">sort: {{ element.sort_order }}</p>
|
||||||
<button
|
<button
|
||||||
class="delete ml-auto focus:outline-none hover:bg-gray-300 p-1 rounded-md text-gray-800"
|
class="delete ml-auto focus:outline-none hover:bg-gray-300 p-1 rounded-md text-gray-800"
|
||||||
@click="removeFile(index)">
|
@click.prevent="removeFile(index)">
|
||||||
<DeleteIcon></DeleteIcon>
|
<DeleteIcon></DeleteIcon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -142,6 +119,58 @@
|
||||||
</draggable>
|
</draggable>
|
||||||
<!-- </ul> -->
|
<!-- </ul> -->
|
||||||
|
|
||||||
|
<!--<ul id="deletetFiles"></ul> -->
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h1 v-if="deletetFiles.length > 0" class="pt-8 pb-3 font-semibold sm:text-lg text-gray-900">Files To
|
||||||
|
Delete</h1>
|
||||||
|
<ul id="deletetFiles" tag="ul" class="flex flex-1 flex-wrap -m-1">
|
||||||
|
<li v-for="(element, index) in deletetFiles" :key="index"
|
||||||
|
class="block p-1 w-1/2 sm:w-1/3 md:w-1/4 lg:w-1/6 xl:w-1/8 h-24">
|
||||||
|
<!-- <article
|
||||||
|
v-if="element.type.match('image.*')"
|
||||||
|
tabindex="0"
|
||||||
|
class="bg-red-50 group hasImage w-full h-full rounded-md cursor-pointer relative text-transparent hover:text-white shadow-sm"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:alt="element.name"
|
||||||
|
:src="element.fileSrc"
|
||||||
|
class="img-preview w-full h-full sticky object-cover rounded-md bg-fixed opacity-75"
|
||||||
|
/>
|
||||||
|
<section class="flex flex-col rounded-md text-xs break-words w-full h-full z-20 absolute top-0 py-2 px-3">
|
||||||
|
<h1 class="flex-1">{{ element.name }}</h1>
|
||||||
|
<div class="flex">
|
||||||
|
<p class="p-1 size text-xs">{{ getFileSize(element) }}</p>
|
||||||
|
<p class="p-1 size text-xs text-gray-700">{{ element.sort_order }}</p>
|
||||||
|
<button
|
||||||
|
class="delete ml-auto focus:outline-none hover:bg-gray-300 p-1 rounded-md"
|
||||||
|
@click.prevent="reactivateFile(index)"
|
||||||
|
>
|
||||||
|
<RefreshIcon></RefreshIcon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</article> -->
|
||||||
|
<article tabindex="0"
|
||||||
|
class="bg-red-100 group w-full h-full rounded-md cursor-pointer relative shadow-sm">
|
||||||
|
<section
|
||||||
|
class="flex flex-col rounded-md text-xs break-words w-full h-full z-20 absolute top-0 py-2 px-3">
|
||||||
|
<h1 class="flex-1 text-gray-700 group-hover:text-blue-800">{{ element.name }}</h1>
|
||||||
|
<div class="flex">
|
||||||
|
<!-- <p class="p-1 size text-xs text-gray-700">{{ getFileSize(element) }}</p> -->
|
||||||
|
<p class="p-1 size text-xs text-gray-700">{{ element.sort_order }}</p>
|
||||||
|
<button
|
||||||
|
class="delete ml-auto focus:outline-none hover:bg-gray-300 p-1 rounded-md text-gray-800"
|
||||||
|
@click.prevent="reactivateFile(index)">
|
||||||
|
<RefreshIcon></RefreshIcon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="fileErrors" class="flex flex-col mt-6 animate-fade-in" v-for="fileError in fileErrors">
|
<div v-if="fileErrors" class="flex flex-col mt-6 animate-fade-in" v-for="fileError in fileErrors">
|
||||||
<div class="bg-yellow-500 border-l-4 border-orange-400 text-white p-4" role="alert">
|
<div class="bg-yellow-500 border-l-4 border-orange-400 text-white p-4" role="alert">
|
||||||
<p class="font-bold">Be Warned</p>
|
<p class="font-bold">Be Warned</p>
|
||||||
|
@ -149,21 +178,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <div class="text-red-400 text-sm" v-if="errors && Array.isArray(errors['files.0'])">
|
|
||||||
{{ errors['files.0'].join(', ') }}
|
|
||||||
</div> -->
|
|
||||||
<!-- <div v-if="hasErrors">
|
|
||||||
<div class="font-medium text-red-600">Whoops! Something went wrong.</div>
|
|
||||||
|
|
||||||
<ul class="mt-3 list-disc list-inside text-sm text-red-600">
|
|
||||||
<li v-for="(error, key) in errors" :key="key">{{ error }}</li>
|
|
||||||
</ul>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- sticky footer -->
|
<!-- sticky footer -->
|
||||||
<footer class="flex justify-end px-8 pb-8 pt-4">
|
<footer class="flex justify-end px-8 pb-8 pt-4">
|
||||||
<button id="cancel" class="ml-3 rounded-sm px-3 py-1 hover:bg-gray-300 focus:shadow-outline focus:outline-none"
|
<button id="cancel"
|
||||||
|
class="ml-3 rounded-sm px-3 py-1 hover:bg-gray-300 focus:shadow-outline focus:outline-none"
|
||||||
@click="clearAllFiles">
|
@click="clearAllFiles">
|
||||||
Clear
|
Clear
|
||||||
</button>
|
</button>
|
||||||
|
@ -175,6 +195,7 @@
|
||||||
import { Component, Vue, Prop, Ref, Watch } from 'vue-facing-decorator';
|
import { Component, Vue, Prop, Ref, Watch } from 'vue-facing-decorator';
|
||||||
import { usePage } from '@inertiajs/vue3';
|
import { usePage } from '@inertiajs/vue3';
|
||||||
import DeleteIcon from '@/Components/Icons/Delete.vue';
|
import DeleteIcon from '@/Components/Icons/Delete.vue';
|
||||||
|
import RefreshIcon from '@/Components/Icons/Refresh.vue';
|
||||||
// import { Page, PageProps, Errors, ErrorBag } from '@inertiajs/inertia';
|
// import { Page, PageProps, Errors, ErrorBag } from '@inertiajs/inertia';
|
||||||
import Draggable from 'vuedraggable';
|
import Draggable from 'vuedraggable';
|
||||||
import { Buffer } from 'buffer';
|
import { Buffer } from 'buffer';
|
||||||
|
@ -212,18 +233,11 @@ interface InteriaPage {
|
||||||
name: 'file-upload',
|
name: 'file-upload',
|
||||||
components: {
|
components: {
|
||||||
DeleteIcon,
|
DeleteIcon,
|
||||||
|
RefreshIcon,
|
||||||
Draggable,
|
Draggable,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
class FileUploadComponent extends Vue {
|
class FileUploadComponent extends Vue {
|
||||||
/**
|
|
||||||
* Connect map id.
|
|
||||||
*/
|
|
||||||
// @Prop({
|
|
||||||
// type: Object,
|
|
||||||
// default: () => ({}),
|
|
||||||
// })
|
|
||||||
// errors: IErrorMessage;
|
|
||||||
|
|
||||||
@Ref('overlay') overlay: HTMLDivElement;
|
@Ref('overlay') overlay: HTMLDivElement;
|
||||||
|
|
||||||
|
@ -236,6 +250,23 @@ class FileUploadComponent extends Vue {
|
||||||
})
|
})
|
||||||
files: Array<TethysFile | File>;
|
files: Array<TethysFile | File>;
|
||||||
|
|
||||||
|
|
||||||
|
@Prop({
|
||||||
|
type: Array<File>,
|
||||||
|
default: [],
|
||||||
|
})
|
||||||
|
filesToDelete: Array<TethysFile>;
|
||||||
|
|
||||||
|
// // deletetFiles: Array<TethysFile> = [];
|
||||||
|
get deletetFiles(): Array<TethysFile> {
|
||||||
|
return this.filesToDelete;
|
||||||
|
}
|
||||||
|
set deletetFiles(values: Array<TethysFile>) {
|
||||||
|
// this.modelValue = value;
|
||||||
|
this.filesToDelete.length = 0;
|
||||||
|
this.filesToDelete.push(...values);
|
||||||
|
}
|
||||||
|
|
||||||
get items(): Array<TethysFile | File> {
|
get items(): Array<TethysFile | File> {
|
||||||
return this.files;
|
return this.files;
|
||||||
}
|
}
|
||||||
|
@ -249,10 +280,20 @@ class FileUploadComponent extends Vue {
|
||||||
// });
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
@Watch("files", {
|
@Watch('files', {
|
||||||
deep: true //also in case of pushing
|
deep: true, //also in case of pushing
|
||||||
})
|
})
|
||||||
public propertyWatcher(newItems: Array<TethysFile>) {
|
public propertyWatcherFiles(newItems: Array<TethysFile>) {
|
||||||
|
// Update sort_order based on the new index when the list is changed
|
||||||
|
newItems.forEach((item, index) => {
|
||||||
|
item.sort_order = index + 1; // Assuming sort_order starts from 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Watch('filesToDelete', {
|
||||||
|
deep: true, //also in case of pushing
|
||||||
|
})
|
||||||
|
public propertyWatcherDeletedFiles(newItems: Array<TethysFile>) {
|
||||||
// Update sort_order based on the new index when the list is changed
|
// Update sort_order based on the new index when the list is changed
|
||||||
newItems.forEach((item, index) => {
|
newItems.forEach((item, index) => {
|
||||||
item.sort_order = index + 1; // Assuming sort_order starts from 1
|
item.sort_order = index + 1; // Assuming sort_order starts from 1
|
||||||
|
@ -281,23 +322,32 @@ class FileUploadComponent extends Vue {
|
||||||
1 > --this.counter && this.overlay.classList.remove('draggedover');
|
1 > --this.counter && this.overlay.classList.remove('draggedover');
|
||||||
}
|
}
|
||||||
|
|
||||||
public dragOverHandler(e) {
|
public dragOverHandler(event: DragEvent): void {
|
||||||
if (this._hasFiles(e.dataTransfer)) {
|
if (this._hasFiles(event.dataTransfer)) {
|
||||||
e.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public startDrag(evt, item) {
|
public startDrag(event: DragEvent, item: { id: string }) {
|
||||||
evt.dataTransfer.dropEffect = 'move';
|
const dataTransfer = event.dataTransfer;
|
||||||
evt.dataTransfer.effectAllowed = 'move';
|
|
||||||
evt.dataTransfer.setData('itemID', item.id);
|
// Check if dataTransfer is not null
|
||||||
|
if (dataTransfer) {
|
||||||
|
dataTransfer.dropEffect = 'move';
|
||||||
|
dataTransfer.effectAllowed = 'move';
|
||||||
|
dataTransfer.setData('itemID', item.id);
|
||||||
|
} else {
|
||||||
|
console.warn('dataTransfer is null, drag event may not be supported.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset counter and append file to gallery when file is dropped
|
// reset counter and append file to gallery when file is dropped
|
||||||
|
|
||||||
public dropHandler(event) {
|
public dropHandler(event: DragEvent): void {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
for (const file of event.dataTransfer.files) {
|
const dataTransfer = event.dataTransfer;
|
||||||
|
if (dataTransfer) {
|
||||||
|
for (const file of event.dataTransfer?.files) {
|
||||||
// let fileName = String(file.name.replace(/\.[^/.]+$/, ''));
|
// let fileName = String(file.name.replace(/\.[^/.]+$/, ''));
|
||||||
// file.label = fileName;
|
// file.label = fileName;
|
||||||
// if (file.type.match('image.*')) {
|
// if (file.type.match('image.*')) {
|
||||||
|
@ -308,8 +358,9 @@ class FileUploadComponent extends Vue {
|
||||||
this.overlay.classList.remove('draggedover');
|
this.overlay.classList.remove('draggedover');
|
||||||
this.counter = 0;
|
this.counter = 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public onChangeFile(event) {
|
public onChangeFile(event: Event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
// let uploadedFile = event.target.files[0];
|
// let uploadedFile = event.target.files[0];
|
||||||
// let fileName = String(event.target.files[0].name.replace(/\.[^/.]+$/, ''));
|
// let fileName = String(event.target.files[0].name.replace(/\.[^/.]+$/, ''));
|
||||||
|
@ -343,13 +394,46 @@ class FileUploadComponent extends Vue {
|
||||||
return Object.fromEntries(Object.entries(this.errors).filter(([key]) => key.startsWith('file')));
|
return Object.fromEntries(Object.entries(this.errors).filter(([key]) => key.startsWith('file')));
|
||||||
}
|
}
|
||||||
|
|
||||||
public clearAllFiles(event) {
|
public clearAllFiles(event: Event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.items.splice(0);
|
this.items.splice(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeFile(key) {
|
public removeFile(key: number) {
|
||||||
|
// Check if the key is within the bounds of the items array
|
||||||
|
if (key < 0 || key >= this.items.length) {
|
||||||
|
console.error('Invalid key provided for removal.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let fileToDelete = this.items[key];
|
||||||
|
// Remove the file from items
|
||||||
this.items.splice(key, 1);
|
this.items.splice(key, 1);
|
||||||
|
|
||||||
|
// Check if the file is of type TethysFile based on its properties
|
||||||
|
if (this.isTethysFile(fileToDelete)) {
|
||||||
|
this.deletetFiles.push(fileToDelete);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Helper method to check if a file is of type TethysFile
|
||||||
|
private isTethysFile(file: any): file is TethysFile {
|
||||||
|
// Replace the following conditions with the actual properties of TethysFile
|
||||||
|
return file && typeof file.id === 'number'; // Example property check
|
||||||
|
}
|
||||||
|
|
||||||
|
public reactivateFile(key: number) {
|
||||||
|
// Check if the key is within the bounds of the items array
|
||||||
|
if (key < 0 || key >= this.deletetFiles.length) {
|
||||||
|
console.error('Invalid key provided for reactivate.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let fileToReactivate = this.deletetFiles[key];
|
||||||
|
// Remove the file from items
|
||||||
|
this.deletetFiles.splice(key, 1);
|
||||||
|
|
||||||
|
// Check if the file is of type TethysFile based on its properties
|
||||||
|
if (this.isTethysFile(fileToReactivate)) {
|
||||||
|
this.items.push(fileToReactivate);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public generateURL(file: TethysFile | File): string {
|
public generateURL(file: TethysFile | File): string {
|
||||||
|
@ -358,16 +442,16 @@ class FileUploadComponent extends Vue {
|
||||||
// const blob = new Blob([file.fileData], { type: 'image/png'});
|
// const blob = new Blob([file.fileData], { type: 'image/png'});
|
||||||
// let fileSrc = file.fileData;
|
// let fileSrc = file.fileData;
|
||||||
|
|
||||||
let localUrl: string = "";
|
let localUrl: string = '';
|
||||||
if (file instanceof File) {
|
if (file instanceof File) {
|
||||||
localUrl = URL.createObjectURL(file as Blob);
|
localUrl = URL.createObjectURL(file as Blob);
|
||||||
} else if (file.fileData) {
|
} else if (file.fileData) {
|
||||||
// const blob = new Blob([file.fileData]);
|
// const blob = new Blob([file.fileData]);
|
||||||
// localUrl = URL.createObjectURL(blob);
|
// localUrl = URL.createObjectURL(blob);
|
||||||
const parsed = JSON.parse(file.fileData);
|
const parsed = JSON.parse(file.fileData);
|
||||||
file.fileData = "";
|
file.fileData = '';
|
||||||
// retrieve the original buffer of data
|
// retrieve the original buffer of data
|
||||||
const buff = Buffer.from(parsed.blob, "base64");
|
const buff = Buffer.from(parsed.blob, 'base64');
|
||||||
const blob = new Blob([buff], { type: 'application/octet-stream' });
|
const blob = new Blob([buff], { type: 'application/octet-stream' });
|
||||||
// file.blob = blob;
|
// file.blob = blob;
|
||||||
localUrl = URL.createObjectURL(blob);
|
localUrl = URL.createObjectURL(blob);
|
||||||
|
@ -380,8 +464,6 @@ class FileUploadComponent extends Vue {
|
||||||
return localUrl;
|
return localUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// private async downloadFile(id: number): Promise<string> {
|
// private async downloadFile(id: number): Promise<string> {
|
||||||
// const response = await axios.get<Blob>(`/api/download/${id}`, {
|
// const response = await axios.get<Blob>(`/api/download/${id}`, {
|
||||||
// responseType: 'blob',
|
// responseType: 'blob',
|
||||||
|
@ -393,9 +475,7 @@ class FileUploadComponent extends Vue {
|
||||||
// return url;
|
// return url;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
public getFileSize(file: File) {
|
||||||
|
|
||||||
public getFileSize(file) {
|
|
||||||
if (file.size > 1024) {
|
if (file.size > 1024) {
|
||||||
if (file.size > 1048576) {
|
if (file.size > 1048576) {
|
||||||
return Math.round(file.size / 1048576) + 'mb';
|
return Math.round(file.size / 1048576) + 'mb';
|
||||||
|
@ -442,30 +522,23 @@ class FileUploadComponent extends Vue {
|
||||||
mime_type: file.type,
|
mime_type: file.type,
|
||||||
visible_in_frontdoor: false,
|
visible_in_frontdoor: false,
|
||||||
visible_in_oai: false,
|
visible_in_oai: false,
|
||||||
fileSrc: file.type.match('image.*')? this.generateURL(file) : "",
|
fileSrc: file.type.match('image.*') ? this.generateURL(file) : '',
|
||||||
blob: file as Blob,
|
blob: file as Blob,
|
||||||
sort_order: (this.items.length + 1),
|
sort_order: this.items.length + 1,
|
||||||
};
|
};
|
||||||
// this.items.push(test);
|
// this.items.push(test);
|
||||||
this.items[this.items.length] = test;
|
this.items[this.items.length] = test;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
this.items.push(file);
|
this.items.push(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// private async readBase64(blob: Blob): Promise<string> {
|
|
||||||
// return new Promise<string>((resolve, reject) => {
|
|
||||||
// const reader = new FileReader();
|
|
||||||
// reader.onload = (event) => resolve((event.target as FileReader).result as string);
|
|
||||||
// reader.onerror = reject;
|
|
||||||
// reader.readAsDataURL(blob);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// use to check if a file is being dragged
|
// use to check if a file is being dragged
|
||||||
private _hasFiles({ types = [] as Array<string> }) {
|
// private _hasFiles({ types = [] as Array<string> }) {
|
||||||
return types.indexOf('Files') > -1;
|
// return types.indexOf('Files') > -1;
|
||||||
|
// }
|
||||||
|
private _hasFiles(dataTransfer: DataTransfer | null): boolean {
|
||||||
|
return dataTransfer ? dataTransfer.items.length > 0 : false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export default FileUploadComponent;
|
export default FileUploadComponent;
|
||||||
|
|
27
resources/js/Components/Icons/Refresh.vue
Normal file
27
resources/js/Components/Icons/Refresh.vue
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
class="pointer-events-none fill-current w-4 h-4 ml-auto"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 118.04 122.88">
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
class="pointer-events-none"
|
||||||
|
d="M16.08,59.26A8,8,0,0,1,0,59.26a59,59,0,0,1,97.13-45V8a8,8,0,1,1,16.08,0V33.35a8,8,0,0,1-8,8L80.82,43.62a8,8,0,1,1-1.44-15.95l8-.73A43,43,0,0,0,16.08,59.26Zm22.77,19.6a8,8,0,0,1,1.44,16l-10.08.91A42.95,42.95,0,0,0,102,63.86a8,8,0,0,1,16.08,0A59,59,0,0,1,22.3,110v4.18a8,8,0,0,1-16.08,0V89.14h0a8,8,0,0,1,7.29-8l25.31-2.3Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.my-svg-component {
|
||||||
|
/* Scoped CSS here */
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
fill: none;
|
||||||
|
stroke: currentColor;
|
||||||
|
stroke-width: 1;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -295,35 +295,10 @@ async function handleInput(e: Event) {
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// Function to execute the SPARQL query against the endpoint
|
||||||
async function request(url: string, param: string) {
|
async function request(url: string, param: string) {
|
||||||
try {
|
try {
|
||||||
let query = encodeURIComponent(`
|
// Create SPARQL query to search for concepts by keyword
|
||||||
PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
|
|
||||||
select distinct (?label as ?title) ?s (strlen(str(?label)) as ?strlen)
|
|
||||||
where
|
|
||||||
{
|
|
||||||
VALUES ?n {"${sparqlEncode(param.toLowerCase())}"}
|
|
||||||
?s skos:prefLabel ?label .
|
|
||||||
filter(lang(?label)='${language.value}')
|
|
||||||
filter(regex(?label, ?n, "i")) # Case-insensitive regex match
|
|
||||||
}
|
|
||||||
order by ?label ?strlen
|
|
||||||
|
|
||||||
`);
|
|
||||||
let response = await searchTerm(url + '?query=' + query + '&format=application/json');
|
|
||||||
error.value = '';
|
|
||||||
data.results = getResults(response);
|
|
||||||
// // this.results = res.data;
|
|
||||||
// // this.loading = false;
|
|
||||||
} catch (error) {
|
|
||||||
error.value = error.message;
|
|
||||||
// this.loading = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async function requestOriginal(url: string, param: string) {
|
|
||||||
try {
|
|
||||||
let query = encodeURIComponent(`
|
let query = encodeURIComponent(`
|
||||||
PREFIX dcterms:<http://purl.org/dc/terms/>
|
PREFIX dcterms:<http://purl.org/dc/terms/>
|
||||||
PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
|
PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
|
||||||
|
@ -331,37 +306,69 @@ async function requestOriginal(url: string, param: string) {
|
||||||
WHERE {
|
WHERE {
|
||||||
VALUES ?n {"${sparqlEncode(param.toLowerCase())}"}
|
VALUES ?n {"${sparqlEncode(param.toLowerCase())}"}
|
||||||
VALUES ?p { skos:prefLabel skos:altLabel }
|
VALUES ?p { skos:prefLabel skos:altLabel }
|
||||||
?s a skos:Concept; ?p ?lEN . FILTER((lang(?lEN)="en"))
|
|
||||||
|
|
||||||
# FILTER(regex(str(?s), 'ncl/geoera/keyword'))
|
?s a skos:Concept; ?p ?lEN .
|
||||||
|
FILTER((lang(?lEN)="en"))
|
||||||
|
|
||||||
|
# New filter added to restrict the URIs to 'ncl/geoera/keyword'
|
||||||
|
FILTER(regex(str(?s), 'ncl/geoera/keyword'))
|
||||||
|
|
||||||
OPTIONAL{?s ?p ?l . FILTER(lang(?l)="${language.value}")}
|
OPTIONAL{?s ?p ?l . FILTER(lang(?l)="${language.value}")}
|
||||||
BIND(COALESCE(?l, ?lEN) AS ?L) . FILTER(regex(?L,?n,"i"))
|
BIND(COALESCE(?l, ?lEN) AS ?L) .
|
||||||
|
FILTER(regex(?L, ?n, "i"))
|
||||||
|
|
||||||
|
# Exclude "(category)" in titles
|
||||||
FILTER(!regex(?L, "\(category\)", "i"))
|
FILTER(!regex(?L, "\(category\)", "i"))
|
||||||
FILTER(!regex(?L, "\(kategorie\)", "i"))
|
FILTER(!regex(?L, "\(kategorie\)", "i"))
|
||||||
|
|
||||||
?s skos:prefLabel ?plEN . FILTER((lang(?plEN)="en"))
|
?s skos:prefLabel ?plEN . FILTER((lang(?plEN)="en"))
|
||||||
|
|
||||||
OPTIONAL{?s skos:prefLabel ?pl . FILTER(lang(?pl)="${language.value}")}
|
OPTIONAL{?s skos:prefLabel ?pl . FILTER(lang(?pl)="${language.value}")}
|
||||||
BIND(COALESCE(?pl, ?plEN) AS ?title)
|
BIND(COALESCE(?pl, ?plEN) AS ?title)
|
||||||
BIND(CONCAT(STR(?p),"|",STR(?L)) AS ?text)
|
BIND(CONCAT(STR(?p), "|", STR(?L)) AS ?text)
|
||||||
BIND(IF(?p=skos:prefLabel,1,2) AS ?sort)
|
BIND(IF(?p = skos:prefLabel, 1, 2) AS ?sort)
|
||||||
}
|
}
|
||||||
ORDER BY ?sort
|
ORDER BY ?sort
|
||||||
LIMIT 100`);
|
LIMIT 100`);
|
||||||
|
|
||||||
|
let response = await searchTerm(url + '?query=' + query + '&format=application/json'); // Execute query
|
||||||
let response = await searchTerm(url + '?query=' + query + '&format=application/json');
|
|
||||||
error.value = '';
|
error.value = '';
|
||||||
data.results = getResults(response);
|
data.results = getResults(response); // Process results and store them
|
||||||
// // this.results = res.data;
|
|
||||||
// // this.loading = false;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
error.value = error.message;
|
error.value = error.message; // Handle any errors in fetching the data
|
||||||
// this.loading = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// async function request(url: string, param: string) {
|
||||||
|
// try {
|
||||||
|
// let query = encodeURIComponent(`
|
||||||
|
// PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
|
||||||
|
// select distinct (?label as ?title) ?s (strlen(str(?label)) as ?strlen)
|
||||||
|
// where
|
||||||
|
// {
|
||||||
|
// VALUES ?n {"${sparqlEncode(param.toLowerCase())}"}
|
||||||
|
// ?s skos:prefLabel ?label .
|
||||||
|
// filter(lang(?label)='${language.value}')
|
||||||
|
// filter(regex(?label, ?n, "i")) # Case-insensitive regex match
|
||||||
|
// }
|
||||||
|
// order by ?label ?strlen
|
||||||
|
|
||||||
|
// `);
|
||||||
|
// let response = await searchTerm(url + '?query=' + query + '&format=application/json');
|
||||||
|
// error.value = '';
|
||||||
|
// data.results = getResults(response);
|
||||||
|
// // // this.results = res.data;
|
||||||
|
// // // this.loading = false;
|
||||||
|
// } catch (error) {
|
||||||
|
// error.value = error.message;
|
||||||
|
// // this.loading = false;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async function searchTerm(term: string): Promise<any> {
|
async function searchTerm(term: string): Promise<any> {
|
||||||
let res = await axios.get(term);
|
let res = await axios.get(term);
|
||||||
// console.log(res.data);
|
// console.log(res.data);
|
||||||
|
|
|
@ -57,7 +57,7 @@ const pagesList = computed(() => {
|
||||||
return pagesList;
|
return pagesList;
|
||||||
});
|
});
|
||||||
|
|
||||||
const removeItem = (key) => {
|
const removeItem = (key: number) => {
|
||||||
items.value.splice(key, 1);
|
items.value.splice(key, 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,7 @@ export interface Dataset {
|
||||||
subjects: Array<Subject>;
|
subjects: Array<Subject>;
|
||||||
references: Array<DatasetReference>;
|
references: Array<DatasetReference>;
|
||||||
files: Array<TethysFile>;
|
files: Array<TethysFile>;
|
||||||
|
filesToDelete?: Array<TethysFile>;
|
||||||
// upload: TethysFile
|
// upload: TethysFile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -86,6 +86,9 @@ const formatServerState = (state: string) => {
|
||||||
<NotificationBar v-if="flash.warning" color="warning" :icon="mdiAlertBoxOutline">
|
<NotificationBar v-if="flash.warning" color="warning" :icon="mdiAlertBoxOutline">
|
||||||
{{ flash.warning }}
|
{{ flash.warning }}
|
||||||
</NotificationBar>
|
</NotificationBar>
|
||||||
|
<NotificationBar v-if="flash.error" color="danger" :icon="mdiAlertBoxOutline">
|
||||||
|
{{ flash.error }}
|
||||||
|
</NotificationBar>
|
||||||
|
|
||||||
<!-- table -->
|
<!-- table -->
|
||||||
<CardBox class="mb-6" has-table>
|
<CardBox class="mb-6" has-table>
|
||||||
|
|
91
resources/js/Pages/Submitter/Dataset/Category.vue
Normal file
91
resources/js/Pages/Submitter/Dataset/Category.vue
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-col h-screen p-4 bg-gray-100">
|
||||||
|
<header class="flex justify-between items-center mb-4">
|
||||||
|
<h1 class="text-xl font-bold">SKOS Browser</h1>
|
||||||
|
<div class="flex space-x-2">
|
||||||
|
<button @click="updateApp" title="Update the application">
|
||||||
|
<img src="/Resources/Images/refresh.png" alt="Update" class="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
<button @click="showInfo" title="Info">
|
||||||
|
<img src="/Resources/Images/info.png" alt="Info" class="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="bg-white shadow-md rounded-lg p-4 mb-6">
|
||||||
|
<h2 class="text-lg font-semibold">GBA-Thesaurus</h2>
|
||||||
|
<label class="block text-sm font-medium">Aktueller Endpoint:</label>
|
||||||
|
<!-- <TreeView :items="endpoints" @select="onEndpointSelected" /> -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white shadow-md rounded-lg p-4">
|
||||||
|
<h2 class="text-lg font-semibold">Konzept-Suche</h2>
|
||||||
|
<!-- <Autocomplete v-model="selectedConcept" :items="concepts" placeholder="Search for a concept" @change="onConceptSelected" /> -->
|
||||||
|
<div class="mt-4">
|
||||||
|
<h3 class="text-md font-medium">Ausgewähltes Konzept</h3>
|
||||||
|
<p>{{ selectedConcept.title }}</p>
|
||||||
|
<a :href="selectedConcept.uri" target="_blank" class="text-blue-500">URI</a>
|
||||||
|
<textarea
|
||||||
|
v-model="selectedConcept.description"
|
||||||
|
class="mt-2 w-full h-24 border rounded"
|
||||||
|
placeholder="Description"
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<h3 class="text-md font-medium">Untergeordnete Konzepte</h3>
|
||||||
|
<!-- <LinkLabelList :items="narrowerConcepts" /> -->
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<h3 class="text-md font-medium">Übergeordnete Konzepte</h3>
|
||||||
|
<!-- <LinkLabelList :items="broaderConcepts" /> -->
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<h3 class="text-md font-medium">Verwandte Konzepte</h3>
|
||||||
|
<!-- <LinkLabelList :items="relatedConcepts" /> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// import TreeView from './TreeView.vue'; // Assuming you have a TreeView component
|
||||||
|
// import Autocomplete from './Autocomplete.vue'; // Assuming you have an Autocomplete component
|
||||||
|
// import LinkLabelList from './LinkLabelList.vue'; // Assuming you have a LinkLabelList component
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
// TreeView,
|
||||||
|
// Autocomplete,
|
||||||
|
// LinkLabelList,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
endpoints: [], // This should be populated with your data
|
||||||
|
concepts: [], // This should be populated with your data
|
||||||
|
selectedConcept: {},
|
||||||
|
narrowerConcepts: [], // Populate with data
|
||||||
|
broaderConcepts: [], // Populate with data
|
||||||
|
relatedConcepts: [], // Populate with data
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateApp() {
|
||||||
|
// Handle app update logic
|
||||||
|
},
|
||||||
|
showInfo() {
|
||||||
|
// Handle showing information
|
||||||
|
},
|
||||||
|
onEndpointSelected(endpoint) {
|
||||||
|
// Handle endpoint selection
|
||||||
|
},
|
||||||
|
onConceptSelected(concept) {
|
||||||
|
this.selectedConcept = concept;
|
||||||
|
// Handle concept selection logic, e.g., fetching related concepts
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* Add your styles here */
|
||||||
|
</style>
|
|
@ -965,6 +965,11 @@ Removes a selected keyword
|
||||||
|
|
||||||
<CardBox class="mb-6 shadow" has-table title="Dataset References" :header-icon="mdiPlusCircle"
|
<CardBox class="mb-6 shadow" has-table title="Dataset References" :header-icon="mdiPlusCircle"
|
||||||
v-on:header-icon-click="addReference">
|
v-on:header-icon-click="addReference">
|
||||||
|
<!-- Message when no references exist -->
|
||||||
|
<div v-if="form.references.length === 0" class="text-center py-4">
|
||||||
|
<p class="text-gray-600">No references added yet.</p>
|
||||||
|
<p class="text-gray-400">Click the plus icon above to add a new reference.</p>
|
||||||
|
</div>
|
||||||
<table class="table-fixed border-green-900" v-if="form.references.length">
|
<table class="table-fixed border-green-900" v-if="form.references.length">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
|
@ -440,7 +440,7 @@
|
||||||
</select> -->
|
</select> -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FileUploadComponent :files="form.files"></FileUploadComponent>
|
<FileUploadComponent v-model:files="form.files" v-model:filesToDelete="form.filesToDelete"></FileUploadComponent>
|
||||||
|
|
||||||
<div class="text-red-400 text-sm" v-if="form.errors['file'] && Array.isArray(form.errors['files'])">
|
<div class="text-red-400 text-sm" v-if="form.errors['file'] && Array.isArray(form.errors['files'])">
|
||||||
{{ form.errors['files'].join(', ') }}
|
{{ form.errors['files'].join(', ') }}
|
||||||
|
@ -482,9 +482,6 @@
|
||||||
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
|
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
|
||||||
import { useForm, Head, usePage } from '@inertiajs/vue3';
|
import { useForm, Head, usePage } from '@inertiajs/vue3';
|
||||||
import { computed, ComputedRef } from 'vue';
|
import { computed, ComputedRef } from 'vue';
|
||||||
// import { ref } from 'vue';
|
|
||||||
// import { MainService } from '@/Stores/main';
|
|
||||||
// import FormInput from '@/Components/FormInput.vue'; // @/Components/FormInput.vue'
|
|
||||||
import { Dataset, Title, Subject, TethysFile, Person, License } from '@/Dataset';
|
import { Dataset, Title, Subject, TethysFile, Person, License } from '@/Dataset';
|
||||||
import { stardust } from '@eidellev/adonis-stardust/client';
|
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||||
|
|
||||||
|
@ -620,6 +617,7 @@ const mapId = 'test';
|
||||||
// // });
|
// // });
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
props.dataset.filesToDelete = [];
|
||||||
let form = useForm<Dataset>(props.dataset as Dataset);
|
let form = useForm<Dataset>(props.dataset as Dataset);
|
||||||
|
|
||||||
// const mainService = MainService();
|
// const mainService = MainService();
|
||||||
|
@ -681,10 +679,17 @@ const submit = async (): Promise<void> => {
|
||||||
// return MultipartFile for file upload
|
// return MultipartFile for file upload
|
||||||
const options: FilePropertyBag = {
|
const options: FilePropertyBag = {
|
||||||
type: obj.type,
|
type: obj.type,
|
||||||
lastModified: obj.lastModified
|
lastModified: obj.lastModified,
|
||||||
|
sortOrder: obj.sort_order,
|
||||||
};
|
};
|
||||||
// let file = new File([obj.blob], `${obj.label}?sortOrder=${obj.sort_order}`, options);
|
// const file = new File([obj.blob], `${obj.label}?sortOrder=${obj.sort_order}`, options);
|
||||||
let file = new File([obj.blob], `${obj.label}`, options);
|
// const metadata = JSON.stringify({ sort_order: obj.sort_order });
|
||||||
|
// const metadataBlob = new Blob([metadata + '\n'], { type: 'application/json' });
|
||||||
|
const file = new File([obj.blob], `${obj.label}`, options,);
|
||||||
|
|
||||||
|
// const file = new File([obj.blob], `${obj.label}`, options);
|
||||||
|
|
||||||
|
|
||||||
// fileUploads[obj.sort_order] = file;
|
// fileUploads[obj.sort_order] = file;
|
||||||
fileUploads.push(file);
|
fileUploads.push(file);
|
||||||
} else {
|
} else {
|
||||||
|
@ -716,7 +721,17 @@ const submit = async (): Promise<void> => {
|
||||||
|
|
||||||
rights: 'true',
|
rights: 'true',
|
||||||
}))
|
}))
|
||||||
.put(route);
|
// .put(route);
|
||||||
|
.put(route, {
|
||||||
|
onSuccess: () => {
|
||||||
|
// console.log(form.data());
|
||||||
|
// mainService.setDataset(form.data());
|
||||||
|
// formStep.value++;
|
||||||
|
// form.filesToDelete = [];
|
||||||
|
// Clear the array using splice
|
||||||
|
form.filesToDelete?.splice(0, form.filesToDelete.length);
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasIdAttribute = (obj: License | number): obj is License => {
|
const hasIdAttribute = (obj: License | number): obj is License => {
|
||||||
|
|
|
@ -255,6 +255,10 @@ router.group(() => {
|
||||||
|
|
||||||
router.get('/person', [PersonController, 'index']).as('person.index').use([middleware.auth()]);
|
router.get('/person', [PersonController, 'index']).as('person.index').use([middleware.auth()]);
|
||||||
|
|
||||||
|
router.get('/dataset/categorize', ({ inertia }: HttpContext) => {
|
||||||
|
return inertia.render('Submitter/Dataset/Category');
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
.prefix('submitter');
|
.prefix('submitter');
|
||||||
// .middleware(['auth', 'can:dataset-list,dataset-publish']);
|
// .middleware(['auth', 'can:dataset-list,dataset-publish']);
|
||||||
|
|
82
start/rules/allowed_extensions_mimetypes.ts
Normal file
82
start/rules/allowed_extensions_mimetypes.ts
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Preloaded File - node ace make:preload rules/allowedExtensionsMimetypes
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|*/
|
||||||
|
|
||||||
|
import { FieldContext } from '@vinejs/vine/types';
|
||||||
|
import vine from '@vinejs/vine';
|
||||||
|
// import { VineString } from '@vinejs/vine';
|
||||||
|
import { VineMultipartFile, isBodyParserFile } from '#providers/vinejs_provider';
|
||||||
|
import type { MultipartFile } from '@adonisjs/core/bodyparser';
|
||||||
|
// import db from '@adonisjs/lucid/services/db';
|
||||||
|
import MimeType from '#models/mime_type';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options accepted by the unique rule
|
||||||
|
*/
|
||||||
|
// type Options = {
|
||||||
|
// mainLanguageField: string;
|
||||||
|
// typeField: string;
|
||||||
|
// };
|
||||||
|
type Options = {
|
||||||
|
// size: string | number;
|
||||||
|
// extnames: string[];
|
||||||
|
clientNameSizeLimit: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function allowedMimetypeExtensions(file: VineMultipartFile | unknown, options: Options | unknown, field: FieldContext) {
|
||||||
|
// if (typeof value !== 'string' && typeof value != 'number') {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (!isBodyParserFile(file)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const validatedFile = file as MultipartFile;
|
||||||
|
const mimeType = validatedFile?.headers['content-type']; // Get MIME type from the file
|
||||||
|
const fileExtension = validatedFile?.extname?.toLocaleLowerCase() as string; // Get file extension from the file
|
||||||
|
|
||||||
|
// validate if file extension is allowed in combination with mimetype
|
||||||
|
const mimeRecord = await MimeType.query().select('file_extension').where('name', mimeType).andWhere('enabled', true).first();
|
||||||
|
|
||||||
|
if (!mimeRecord) {
|
||||||
|
const allowedMimetypes = await MimeType.query().select('name').where('enabled', true);
|
||||||
|
// Transform allowed MIME types to a concatenated string
|
||||||
|
const allowedMimetypesString = allowedMimetypes.map((mime) => mime.name).join(', ');
|
||||||
|
// throw new Error('Invalid MIME type');
|
||||||
|
// Report error with the concatenated allowed MIME types
|
||||||
|
field.report(
|
||||||
|
`Invalid MIME type ${mimeType}. Allowed MIME types are: ${allowedMimetypesString}`,
|
||||||
|
'allowedMimetypeExtensions',
|
||||||
|
field,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const allowedExtensions = mimeRecord.file_extension.split('|');
|
||||||
|
// Validate if the file's extension is in the allowed extensions
|
||||||
|
if (!allowedExtensions.includes(fileExtension)) {
|
||||||
|
//throw new Error(`File extension ${fileExtension} is not allowed for MIME type ${mimeType}`);
|
||||||
|
field.report(
|
||||||
|
`File extension ${fileExtension} is not allowed for MIME type ${mimeType}. Allowed extensions are: ${mimeRecord.file_extension}`,
|
||||||
|
'allowedMimetypeExtensions',
|
||||||
|
field
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// if (validatedFile.clientName.length > options.clientNameSizeLimit) {
|
||||||
|
|
||||||
|
// field.report(`Filename length should be less or equal than ${options.clientNameSizeLimit} characters`, 'filenameLength', field);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const allowedMimetypeExtensionsRule = vine.createRule(allowedMimetypeExtensions);
|
||||||
|
|
||||||
|
declare module '#providers/vinejs_provider' {
|
||||||
|
interface VineMultipartFile {
|
||||||
|
allowedMimetypeExtensions(options?: Options): this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VineMultipartFile.macro('allowedMimetypeExtensions', function (this: VineMultipartFile, options: Options) {
|
||||||
|
return this.use(allowedMimetypeExtensionsRule(options));
|
||||||
|
});
|
61
start/rules/dependent_array_min_length.ts
Normal file
61
start/rules/dependent_array_min_length.ts
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Preloaded File - node ace make:preload rules/dependentArrayMinLength
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|*/
|
||||||
|
|
||||||
|
import { FieldContext } from '@vinejs/vine/types';
|
||||||
|
import vine, { VineArray } from '@vinejs/vine';
|
||||||
|
import { SchemaTypes } from '@vinejs/vine/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options accepted by the dependentArrayMinLength rule
|
||||||
|
*/
|
||||||
|
type Options = {
|
||||||
|
min: number;
|
||||||
|
dependentArray: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function dependentArrayMinLength(value: unknown, options: Options, field: FieldContext) {
|
||||||
|
const fileInputs = field.data[options.dependentArray]; // Access the dependent array
|
||||||
|
const isArrayValue = Array.isArray(value);
|
||||||
|
const isArrayFileInputs = Array.isArray(fileInputs);
|
||||||
|
|
||||||
|
if (isArrayValue && isArrayFileInputs) {
|
||||||
|
if (value.length >= options.min) {
|
||||||
|
return true; // Valid if the main array length meets the minimum
|
||||||
|
} else if (value.length === 0 && fileInputs.length >= options.min) {
|
||||||
|
return true; // Valid if the main array is empty and the dependent array meets the minimum
|
||||||
|
} else {
|
||||||
|
field.report(
|
||||||
|
`At least {{ min }} item for {{field}} field must be defined`,
|
||||||
|
'array.dependentArrayMinLength',
|
||||||
|
field,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Report if either value or dependentArray is not an array
|
||||||
|
field.report(
|
||||||
|
`Both the {{field}} field and dependent array {{dependentArray}} must be arrays.`,
|
||||||
|
'array.dependentArrayMinLength',
|
||||||
|
field,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false; // Invalid if none of the conditions are met
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dependentArrayMinLengthRule = vine.createRule(dependentArrayMinLength);
|
||||||
|
|
||||||
|
// Extend the VineArray interface with the same type parameters
|
||||||
|
declare module '@vinejs/vine' {
|
||||||
|
interface VineArray<Schema extends SchemaTypes> {
|
||||||
|
dependentArrayMinLength(options: Options): this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VineArray.macro('dependentArrayMinLength', function <Schema extends SchemaTypes>(this: VineArray<Schema>, options: Options) {
|
||||||
|
return this.use(dependentArrayMinLengthRule(options));
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user