- added npm package dotenv-webpack for using env variables on clientside
All checks were successful
CI Pipeline / japa-tests (push) Successful in 53s

- added API File Controller for downloading files e.g. /api/download/1022
- also create has codes by submitting new dataset
- added edit dataset functionalities for role submitter
- added the following route for role submitter: /dataset/:id/update', 'DatasetController.update'
- created extra UpdateDatasetValidator.ts for validating updated dataset
- npm updates
This commit is contained in:
Kaimbacher 2023-11-22 17:06:55 +01:00
parent a7142f694f
commit d8bdce1369
23 changed files with 2181 additions and 853 deletions

View File

@ -0,0 +1,54 @@
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
import File from 'App/Models/File';
import { StatusCodes } from 'http-status-codes';
import * as fs from 'fs';
import * as path from 'path';
// node ace make:controller Author
export default class FileController {
// @Get("download/:id")
public async findOne({ response, params }: HttpContextContract) {
const id = params.id;
const file = await File.findOrFail(id);
// const file = await File.findOne({
// where: { id: id },
// });
if (file) {
const filePath = '/storage/app/public/' + file.pathName;
const ext = path.extname(filePath);
const fileName = file.label + ext;
try {
fs.accessSync(filePath, fs.constants.R_OK); //| fs.constants.W_OK);
// console.log("can read/write:", path);
response
.header('Cache-Control', 'no-cache private')
.header('Content-Description', 'File Transfer')
.header('Content-Type', file.mimeType)
.header('Content-Disposition', 'inline; filename=' + fileName)
.header('Content-Transfer-Encoding', 'binary')
.header('Access-Control-Allow-Origin', '*')
.header('Access-Control-Allow-Methods', 'GET,POST');
response.status(StatusCodes.OK).download(filePath);
} catch (err) {
// console.log("no access:", path);
response.status(StatusCodes.NOT_FOUND).send({
message: `File with id ${id} doesn't exist on file server`,
});
}
// res.status(StatusCodes.OK).sendFile(filePath, (err) => {
// // res.setHeader("Content-Type", "application/json");
// // res.removeHeader("Content-Disposition");
// res.status(StatusCodes.NOT_FOUND).send({
// message: `File with id ${id} doesn't exist on file server`,
// });
// });
} else {
response.status(StatusCodes.NOT_FOUND).send({
message: `Cannot find File with id=${id}.`,
});
}
}
}

View File

@ -15,6 +15,7 @@ import Database from '@ioc:Adonis/Lucid/Database';
import { TransactionClientContract } from '@ioc:Adonis/Lucid/Database';
import Subject from 'App/Models/Subject';
import CreateDatasetValidator from 'App/Validators/CreateDatasetValidator';
import UpdateDatasetValidator from 'App/Validators/UpdateDatasetValidator';
import {
TitleTypes,
DescriptionTypes,
@ -33,8 +34,6 @@ import ClamScan from 'clamscan';
import { ValidationException } from '@ioc:Adonis/Core/Validator';
import Drive from '@ioc:Adonis/Core/Drive';
import { Exception } from '@adonisjs/core/build/standalone';
// import XmlModel from 'App/Library/XmlModel';
// import { XMLBuilder } from 'xmlbuilder2/lib/interfaces';
export default class DatasetController {
public async index({ auth, request, inertia }: HttpContextContract) {
@ -355,7 +354,7 @@ export default class DatasetController {
//store licenses:
const licenses: number[] = request.input('licenses', []);
dataset.useTransaction(trx).related('licenses').sync(licenses);
await dataset.useTransaction(trx).related('licenses').sync(licenses);
// save authors and contributors
await this.savePersons(dataset, request.input('authors', []), 'author', trx);
@ -456,7 +455,7 @@ export default class DatasetController {
newFile.visibleInOai = true;
// let path = coverImage.filePath;
await dataset.useTransaction(trx).related('files').save(newFile);
// await newFile.createHashValues();
await newFile.createHashValues(trx);
}
}
@ -682,29 +681,39 @@ export default class DatasetController {
// throw new GeneralException(trans('exceptions.publish.release.update_error'));
}
public async edit({ params, inertia }) {
const datasetQuery = Dataset.query().where('id', params.id);
datasetQuery.preload('titles').preload('descriptions').preload('coverage');
const dataset = await datasetQuery.firstOrFail();
public async edit({ request, inertia, response }) {
const id = request.param('id');
const datasetQuery = Dataset.query().where('id', id);
datasetQuery
.preload('titles', (query) => query.orderBy('id', 'asc'))
.preload('descriptions', (query) => query.orderBy('id', 'asc'))
.preload('coverage')
.preload('licenses')
.preload('authors')
.preload('contributors')
.preload('subjects')
.preload('references')
.preload('files');
// await dataset.loadMany([
// 'licenses',
// 'authors',
// 'contributors',
// 'titles',
// 'abstracts',
// 'files',
// 'coverage',
// 'subjects',
// 'references',
// ]);
const dataset = await datasetQuery.firstOrFail();
const validStates = ['inprogress', 'rejected_editor'];
if (!validStates.includes(dataset.server_state)) {
// session.flash('errors', 'Invalid server state!');
return response
.flash(
'warning',
`Invalid server state. Dataset with id ${id} cannot be edited. Datset has server state ${dataset.server_state}.`,
)
.redirect()
.toRoute('dataset.list');
}
const titleTypes = Object.entries(TitleTypes)
// .filter(([value]) => value !== 'Main')
.filter(([value]) => value !== 'Main')
.map(([key, value]) => ({ value: key, label: value }));
const descriptionTypes = Object.entries(DescriptionTypes)
// .filter(([value]) => value !== 'Abstract')
.filter(([value]) => value !== 'Abstract')
.map(([key, value]) => ({ value: key, label: value }));
const languages = await Language.query().where('active', true).pluck('part1', 'part1');
@ -724,33 +733,11 @@ export default class DatasetController {
const currentYear = currentDate.getFullYear();
const years = Array.from({ length: currentYear - 1990 + 1 }, (_, index) => 1990 + index);
// const licenses = await License.query()
// .select('id', 'name_long', 'link_licence')
// .orderBy('sort_order')
// .fetch();
const licenses = await License.query().select('id', 'name_long', 'link_licence').orderBy('sort_order');
const licenses = await License.query().select('id', 'name_long').where('active', 'true').pluck('name_long', 'id');
// const userHasRoles = user.roles;
// const datasetHasLicenses = await dataset.related('licenses').query().pluck('id');
// const checkeds = dataset.licenses.first().id;
const keywordTypes = {
uncontrolled: 'uncontrolled',
swd: 'swd',
};
const referenceTypes = ['DOI', 'Handle', 'ISBN', 'ISSN', 'URL', 'URN'];
const relationTypes = [
'IsSupplementTo',
'IsSupplementedBy',
'IsContinuedBy',
'Continues',
'IsNewVersionOf',
'IsPartOf',
'HasPart',
'Compiles',
'IsVariantFormOf',
];
const doctypes = {
analysisdata: { label: 'Analysis', value: 'analysisdata' },
measurementdata: { label: 'Measurements', value: 'measurementdata' },
@ -771,16 +758,164 @@ export default class DatasetController {
// messages,
projects,
licenses,
// datasetHasLicenses: Object.keys(datasetHasLicenses).map((key) => datasetHasLicenses[key]), //convert object to array with license ids
// checkeds,
years,
// languages,
keywordTypes,
referenceTypes,
relationTypes,
subjectTypes: SubjectTypes,
referenceIdentifierTypes: Object.entries(ReferenceIdentifierTypes).map(([key, value]) => ({ value: key, label: value })),
relationTypes: Object.entries(RelationTypes).map(([key, value]) => ({ value: key, label: value })),
doctypes,
});
}
public async update({ request, response, session }: HttpContextContract) {
try {
// await request.validate({ schema: newDatasetSchema, messages: this.messages });
await request.validate(UpdateDatasetValidator);
} catch (error) {
// - Handle errors
// return response.badRequest(error.messages);
throw error;
// return response.badRequest(error.messages);
}
const id = request.param('id');
let trx: TransactionClientContract | null = null;
try {
trx = await Database.transaction();
// const user = (await User.find(auth.user?.id)) as User;
// await this.createDatasetAndAssociations(user, request, trx);
const dataset = await Dataset.findOrFail(id);
// save the licenses
const licenses: number[] = request.input('licenses', []);
// await dataset.useTransaction(trx).related('licenses').sync(licenses);
await dataset.useTransaction(trx).related('licenses').sync(licenses);
// save authors and contributors
await dataset.useTransaction(trx).related('authors').sync([]);
await dataset.useTransaction(trx).related('contributors').sync([]);
await this.savePersons(dataset, request.input('authors', []), 'author', trx);
await this.savePersons(dataset, request.input('contributors', []), 'contributor', trx);
//save the titles:
const titles = request.input('titles', []);
// const savedTitles:Array<Title> = [];
for (const titleData of titles) {
if (titleData.id) {
const title = await Title.findOrFail(titleData.id);
title.value = titleData.value;
title.language = titleData.language;
title.type = titleData.type;
if (title.$isDirty) {
await title.useTransaction(trx).save();
// await dataset.useTransaction(trx).related('titles').save(title);
// savedTitles.push(title);
}
} else {
const title = new Title();
title.fill(titleData);
// savedTitles.push(title);
await dataset.useTransaction(trx).related('titles').save(title);
}
}
// save the abstracts
const descriptions = request.input('descriptions', []);
// const savedTitles:Array<Title> = [];
for (const descriptionData of descriptions) {
if (descriptionData.id) {
const description = await Description.findOrFail(descriptionData.id);
description.value = descriptionData.value;
description.language = descriptionData.language;
description.type = descriptionData.type;
if (description.$isDirty) {
await description.useTransaction(trx).save();
// await dataset.useTransaction(trx).related('titles').save(title);
// savedTitles.push(title);
}
} else {
const description = new Description();
description.fill(descriptionData);
// savedTitles.push(title);
await dataset.useTransaction(trx).related('descriptions').save(description);
}
}
// Save already existing files
const files = request.input('fileInputs', []);
for (const fileData of files) {
if (fileData.id) {
const file = await File.findOrFail(fileData.id);
file.label = fileData.label;
file.sortOrder = fileData.sort_order;
if (file.$isDirty) {
await file.useTransaction(trx).save();
}
}
}
// handle new uploaded files:
const uploadedFiles = request.files('files');
if (Array.isArray(uploadedFiles) && uploadedFiles.length > 0) {
// let index = 1;
// for (const key in files) {
// const formFile = files[key]
// for (const fileData of files) {
for (const [index, fileData] of uploadedFiles.entries()) {
// const uploads = request.file('uploads');
// const fileIndex = formFile.file;
// const file = uploads[fileIndex];
const fileName = `file-${cuid()}.${fileData.extname}`;
const mimeType = fileData.headers['content-type'] || 'application/octet-stream'; // Fallback to a default MIME type
const datasetFolder = `files/${dataset.id}`;
await fileData.moveToDisk(
datasetFolder,
{
name: fileName,
overwrite: true, // overwrite in case of conflict
},
'local',
);
// save file metadata into db
const newFile = new File();
newFile.pathName = `${datasetFolder}/${fileName}`;
newFile.fileSize = fileData.size;
newFile.mimeType = mimeType;
newFile.label = fileData.clientName;
newFile.sortOrder = index;
newFile.visibleInFrontdoor = true;
newFile.visibleInOai = true;
// let path = coverImage.filePath;
await dataset.useTransaction(trx).related('files').save(newFile);
await newFile.createHashValues();
}
}
const input = request.only(['project_id', 'embargo_date', 'language', 'type', 'creating_corporation']);
// dataset.type = request.input('type');
dataset.merge(input);
// let test: boolean = dataset.$isDirty;
await dataset.useTransaction(trx).save();
await trx.commit();
console.log('Dataset and related models created successfully');
} catch (error) {
if (trx !== null) {
await trx.rollback();
}
console.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;
}
session.flash('message', 'Dataset has been created successfully');
// return response.redirect().toRoute('user.index');
return response.redirect().back();
}
public async delete({ request, inertia, response, session }) {
const id = request.param('id');
try {

View File

@ -9,6 +9,11 @@ export default class Description extends BaseModel {
public static timestamps = false;
public static fillable: string[] = ['value', 'type', 'language'];
@column({
isPrimary: true,
})
public id: number;
@column({})
public document_id: number;

View File

@ -1,19 +1,18 @@
import { DateTime } from 'luxon';
import {
column,
hasMany,
HasMany,
belongsTo,
BelongsTo,
// manyToMany,
// ManyToMany,
SnakeCaseNamingStrategy,
} from '@ioc:Adonis/Lucid/Orm';
import { column, hasMany, HasMany, belongsTo, BelongsTo, SnakeCaseNamingStrategy, computed } from '@ioc:Adonis/Lucid/Orm';
import HashValue from './HashValue';
import Dataset from './Dataset';
import BaseModel from './BaseModel';
// import { Buffer } from 'buffer';
import * as fs from 'fs';
import crypto from 'crypto';
import { TransactionClientContract } from '@ioc:Adonis/Lucid/Database';
export default class File extends BaseModel {
// private readonly _data: Uint8Array;
// private readonly _type: string;
// private readonly _size: number;
public static namingStrategy = new SnakeCaseNamingStrategy();
public static primaryKey = 'id';
public static table = 'document_files';
@ -73,4 +72,93 @@ export default class File extends BaseModel {
foreignKey: 'file_id',
})
public hashvalues: HasMany<typeof HashValue>;
@computed({
serializeAs: 'filePath',
})
public get filePath() {
return `/storage/app/public/${this.pathName}`;
// const mainTitle = this.titles?.find((title) => title.type === 'Main');
// return mainTitle ? mainTitle.value : null;
}
@computed({
serializeAs: 'size',
})
public get size() {
return this.fileSize;
}
@computed({
serializeAs: 'type',
})
public get type() {
return this.mimeType;
}
@computed({
serializeAs: 'name',
})
get name(): string {
return this.label;
}
@computed({
serializeAs: 'lastModified',
})
get lastModified(): number {
return this.updatedAt.toUnixInteger(); //.toFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
}
readonly webkitRelativePath: string = '';
@computed({
serializeAs: 'fileData',
})
public get fileData(): string {
// return this.fileData;
// const fileData = fs.readFileSync(path.resolve(__dirname, this.filePath));
// const fileData = fs.readFileSync(this.filePath);
const fileContent: Buffer = fs.readFileSync(this.filePath);
// Create a Blob from the file content
// const blob = new Blob([fileContent], { type: this.type }); // Adjust
// let fileSrc = URL.createObjectURL(blob);
// return fileSrc;
// return Buffer.from(fileContent);
// get the buffer from somewhere
// const buff = fs.readFileSync('./test.bin');
// create a JSON string that contains the data in the property "blob"
const json = JSON.stringify({ blob: fileContent.toString('base64') });
return json;
}
public async createHashValues(trx?: TransactionClientContract) {
const hashtypes: string[] = ['md5', 'sha512'];
for (const type of hashtypes) {
const hash = new HashValue();
hash.type = type;
const hashString = await this.checksumFile(this.filePath, type); // Assuming getRealHash is a method in the same model
hash.value = hashString;
// https://github.com/adonisjs/core/discussions/1872#discussioncomment-132289
const file: File = this;
if (trx) {
await file.useTransaction(trx).related('hashvalues').save(hash); // Save the hash value to the database
} else {
await file.related('hashvalues').save(hash); // Save the hash value to the database
}
}
}
private async checksumFile(path, hashName = 'md5'): Promise<string> {
return new Promise((resolve, reject) => {
const hash = crypto.createHash(hashName);
const stream = fs.createReadStream(path);
stream.on('error', (err) => reject(err));
stream.on('data', (chunk) => hash.update(chunk));
stream.on('end', () => resolve(hash.digest('hex')));
});
}
}

View File

@ -3,7 +3,7 @@ import File from './File';
export default class HashValue extends BaseModel {
public static namingStrategy = new SnakeCaseNamingStrategy();
public static primaryKey = 'file_id, type';
// public static primaryKey = 'file_id,type';
public static table = 'file_hashvalues';
// static get primaryKey () {
@ -20,10 +20,10 @@ export default class HashValue extends BaseModel {
// public id: number;
// Foreign key is still on the same model
@column({})
@column({ isPrimary: true })
public file_id: number;
@column({})
@column({ isPrimary: true })
public type: string;
@column()

View File

@ -10,6 +10,11 @@ export default class Title extends BaseModel {
public static timestamps = false;
public static fillable: string[] = ['value', 'type', 'language'];
@column({
isPrimary: true,
})
public id: number;
@column({})
public document_id: number;

View File

@ -136,7 +136,7 @@ export default class CreateDatasetValidator {
'unique': '{{ field }} must be unique, and this value is already taken',
// 'confirmed': '{{ field }} is not correct',
'licenses.minLength': 'at least {{ options.minLength }} permission must be defined',
'licenses.*.number': 'Define roles as valid numbers',
'licenses.*.number': 'Define licences as valid numbers',
'rights.equalTo': 'you must agree to continue',
'titles.0.value.minLength': 'Main Title must be at least {{ options.minLength }} characters long',

View File

@ -0,0 +1,179 @@
import { schema, CustomMessages, rules } from '@ioc:Adonis/Core/Validator';
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
import dayjs from 'dayjs';
import { TitleTypes, DescriptionTypes, RelationTypes, ReferenceIdentifierTypes, ContributorTypes } from 'Contracts/enums';
export default class UpdateDatasetValidator {
constructor(protected ctx: HttpContextContract) {}
/*
* Define schema to validate the "shape", "type", "formatting" and "integrity" of data.
*
* For example:
* 1. The username must be of data type string. But then also, it should
* not contain special characters or numbers.
* ```
* schema.string({}, [ rules.alpha() ])
* ```
*
* 2. The email must be of data type string, formatted as a valid
* email. But also, not used by any other user.
* ```
* schema.string({}, [
* rules.email(),
* rules.unique({ table: 'users', column: 'email' }),
* ])
* ```
*/
public schema = schema.create({
// first step
language: schema.string({ trim: true }, [
rules.regex(/^[a-zA-Z0-9-_]+$/), //Must be alphanumeric with hyphens or underscores
]),
licenses: schema.array([rules.minLength(1)]).members(schema.number()), // define at least one license for the new dataset
rights: schema.string([rules.equalTo('true')]),
// second step
type: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
creating_corporation: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
titles: schema.array([rules.minLength(1)]).members(
schema.object().members({
value: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
type: schema.enum(Object.values(TitleTypes)),
language: schema.string({ trim: true }, [
rules.minLength(2),
rules.maxLength(255),
rules.translatedLanguage('/language', 'type'),
]),
}),
),
descriptions: schema.array([rules.minLength(1)]).members(
schema.object().members({
value: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
type: schema.enum(Object.values(DescriptionTypes)),
language: schema.string({ trim: true }, [
rules.minLength(2),
rules.maxLength(255),
rules.translatedLanguage('/language', 'type'),
]),
}),
),
authors: schema.array([rules.minLength(1)]).members(schema.object().members({ email: schema.string({ trim: true }) })),
contributors: schema.array.optional().members(
schema.object().members({
email: schema.string({ trim: true }),
pivot_contributor_type: schema.enum(Object.keys(ContributorTypes)),
}),
),
// third step
project_id: schema.number.optional(),
embargo_date: schema.date.optional({ format: 'yyyy-MM-dd' }, [rules.after(10, 'days')]),
coverage: schema.object().members({
x_min: schema.number(),
x_max: schema.number(),
y_min: schema.number(),
y_max: schema.number(),
elevation_absolut: schema.number.optional(),
elevation_min: schema.number.optional([rules.requiredIfExists('elevation_max')]),
elevation_max: schema.number.optional([rules.requiredIfExists('elevation_min')]),
depth_absolut: schema.number.optional(),
depth_min: schema.number.optional([rules.requiredIfExists('depth_max')]),
depth_max: schema.number.optional([rules.requiredIfExists('depth_min')]),
}),
references: schema.array.optional([rules.uniqueArray('value')]).members(
schema.object().members({
value: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
type: schema.enum(Object.values(ReferenceIdentifierTypes)),
relation: schema.enum(Object.values(RelationTypes)),
label: schema.string({ trim: true }, [rules.minLength(2), rules.maxLength(255)]),
}),
),
subjects: schema.array([rules.minLength(3), rules.uniqueArray('value')]).members(
schema.object().members({
value: schema.string({ trim: true }, [
rules.minLength(3),
rules.maxLength(255),
// rules.unique({ table: 'dataset_subjects', column: 'value' }),
]),
// type: schema.enum(Object.values(TitleTypes)),
language: schema.string({ trim: true }, [rules.minLength(2), rules.maxLength(255)]),
}),
),
// file: schema.file({
// size: '100mb',
// extnames: ['jpg', 'gif', 'png'],
// }),
files: schema.array.optional().members(
schema.file({
size: '100mb',
extnames: ['jpg', 'gif', 'png', 'tif', 'pdf'],
}),
)
// upload: schema.object().members({
// label: schema.string({ trim: true }, [rules.maxLength(255)]),
// // label: schema.string({ trim: true }, [
// // // rules.minLength(3),
// // // rules.maxLength(255),
// // ]),
// }),
});
/**
* Custom messages for validation failures. You can make use of dot notation `(.)`
* for targeting nested fields and array expressions `(*)` for targeting all
* children of an array. For example:
*
* {
* 'profile.username.required': 'Username is required',
* 'scores.*.number': 'Define scores as valid numbers'
* }
*
*/
public messages: CustomMessages = {
'minLength': '{{ field }} must be at least {{ options.minLength }} characters long',
'maxLength': '{{ field }} must be less then {{ options.maxLength }} characters long',
'required': '{{ field }} is required',
'unique': '{{ field }} must be unique, and this value is already taken',
// 'confirmed': '{{ field }} is not correct',
'licenses.minLength': 'at least {{ options.minLength }} permission must be defined',
'licenses.*.number': 'Define licences as valid numbers',
'rights.equalTo': 'you must agree to continue',
'titles.0.value.minLength': 'Main Title must be at least {{ options.minLength }} characters long',
'titles.0.value.required': 'Main Title is required',
'titles.*.value.required': 'Additional title is required, if defined',
'titles.*.type.required': 'Additional title type is required',
'titles.*.language.required': 'Additional title language is required',
'titles.*.language.translatedLanguage': 'The language of the translated title must be different from the language of the dataset',
'descriptions.0.value.minLength': 'Main Abstract must be at least {{ options.minLength }} characters long',
'descriptions.0.value.required': 'Main Abstract is required',
'descriptions.*.value.required': 'Additional description is required, if defined',
'descriptions.*.type.required': 'Additional description type is required',
'descriptions.*.language.required': 'Additional description language is required',
'descriptions.*.language.translatedLanguage':
'The language of the translated description must be different from the language of the dataset',
'authors.minLength': 'at least {{ options.minLength }} author must be defined',
'contributors.*.pivot_contributor_type.required': 'contributor type is required, if defined',
'after': `{{ field }} must be older than ${dayjs().add(10, 'day')}`,
'subjects.minLength': 'at least {{ options.minLength }} keywords must be defined',
'subjects.uniqueArray': 'The {{ options.array }} array must have unique values based on the {{ options.field }} attribute.',
'subjects.*.value.required': 'keyword value is required',
'subjects.*.value.minLength': 'keyword value must be at least {{ options.minLength }} characters long',
'subjects.*.type.required': 'keyword type is required',
'subjects.*.language.required': 'language of keyword is required',
'references.*.value.required': 'Additional reference value is required, if defined',
'references.*.type.required': 'Additional reference identifier type is required',
'references.*.relation.required': 'Additional reference relation type is required',
'references.*.label.required': 'Additional reference label is required',
'files.minLength': 'At least {{ options.minLength }} file upload is required.',
'files.*.size': 'file size is to big',
'files.extnames': 'file extension is not supported',
};
}

1279
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -43,6 +43,7 @@
"autoprefixer": "^10.4.13",
"babel-preset-typescript-vue3": "^2.0.17",
"chart.js": "^4.2.0",
"dotenv-webpack": "^8.0.1",
"eslint": "^8.32.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-adonis": "^2.1.1",

View File

@ -1,34 +1,21 @@
<template>
<section
aria-label="File Upload Modal"
<section aria-label="File Upload Modal"
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:drop="dropHandler"
>
v-on:dragenter="dragEnterHandler" v-on:dragleave="dragLeaveHandler" v-on:dragover="dragOverHandler"
v-on:drop="dropHandler">
<!-- ondrop="dropHandler(event);"
ondragover="dragOverHandler(event);"
ondragleave="dragLeaveHandler(event);"
ondragenter="dragEnterHandler(event);" -->
<!-- overlay -->
<div
id="overlay"
ref="overlay"
class="w-full h-full absolute top-0 left-0 pointer-events-none z-50 flex flex-col items-center justify-center rounded-md"
>
<div id="overlay" ref="overlay"
class="w-full h-full absolute top-0 left-0 pointer-events-none z-50 flex flex-col items-center justify-center rounded-md">
<i>
<svg
class="fill-current w-12 h-12 mb-3 text-blue-700"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<svg class="fill-current w-12 h-12 mb-3 text-blue-700" xmlns="http://www.w3.org/2000/svg" width="24"
height="24" viewBox="0 0 24 24">
<path
d="M19.479 10.092c-.212-3.951-3.473-7.092-7.479-7.092-4.005 0-7.267 3.141-7.479 7.092-2.57.463-4.521 2.706-4.521 5.408 0 3.037 2.463 5.5 5.5 5.5h13c3.037 0 5.5-2.463 5.5-5.5 0-2.702-1.951-4.945-4.521-5.408zm-7.479-1.092l4 4h-3v4h-2v-4h-3l4-4z"
/>
d="M19.479 10.092c-.212-3.951-3.473-7.092-7.479-7.092-4.005 0-7.267 3.141-7.479 7.092-2.57.463-4.521 2.706-4.521 5.408 0 3.037 2.463 5.5 5.5 5.5h13c3.037 0 5.5-2.463 5.5-5.5 0-2.702-1.951-4.945-4.521-5.408zm-7.479-1.092l4 4h-3v4h-2v-4h-3l4-4z" />
</svg>
</i>
<p class="text-lg text-blue-700">Drop files to upload</p>
@ -46,25 +33,14 @@
</button>
</header> -->
<header class="flex items-center justify-center w-full">
<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"
>
<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">
<div class="flex flex-col items-center justify-center pt-5 pb-6">
<svg
aria-hidden="true"
class="w-10 h-10 mb-3 text-gray-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
></path>
<svg aria-hidden="true" class="w-10 h-10 mb-3 text-gray-400" fill="none" stroke="currentColor"
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12">
</path>
</svg>
<p class="mb-2 text-sm text-gray-500 dark:text-gray-400">
<span class="font-semibold">Click to upload</span> or drag and drop
@ -78,7 +54,8 @@
<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="gallery" tag="ul" class="flex flex-1 flex-wrap -m-1" v-model="files" item-key="sorting">
<draggable id="galleryxy" tag="ul" class="flex flex-1 flex-wrap -m-1" v-model="items" item-key="sort_order">
<!-- <li
v-if="files.length == 0"
id="empty"
@ -108,17 +85,12 @@
<span class="text-small text-gray-500">No files selected</span>
</li> -->
<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">
<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"
>
<img
:alt="element.name"
:src="generateURL(element)"
class="img-preview w-full h-full sticky object-cover rounded-md bg-fixed opacity-75"
/>
<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)" -->
<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="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"
>
@ -132,15 +104,14 @@
<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">
<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">{{ index }}</p>
<button
class="delete ml-auto focus:outline-none hover:bg-gray-300 p-1 rounded-md"
@click="removeFile(index)"
>
<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="removeFile(index)">
<DeleteIcon></DeleteIcon>
</button>
</div>
@ -150,16 +121,17 @@
</div> -->
</article>
<!-- :class="errors && errors[`files.${index}`] ? 'bg-red-400' : 'bg-gray-100'" -->
<article v-else tabindex="0" class="bg-gray-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">
<article v-else tabindex="0"
class="bg-gray-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">{{ index }}</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="removeFile(index)"
>
@click="removeFile(index)">
<DeleteIcon></DeleteIcon>
</button>
</div>
@ -191,11 +163,8 @@
<!-- sticky footer -->
<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"
@click="clearAllFiles"
>
<button id="cancel" class="ml-3 rounded-sm px-3 py-1 hover:bg-gray-300 focus:shadow-outline focus:outline-none"
@click="clearAllFiles">
Clear
</button>
</footer>
@ -203,13 +172,20 @@
</template>
<script lang="ts">
import { Component, Vue, Prop, Ref } from 'vue-facing-decorator';
// import BaseButton from './BaseButton.vue';
import { Component, Vue, Prop, Ref, Watch } from 'vue-facing-decorator';
import { usePage } from '@inertiajs/vue3';
import DeleteIcon from '@/Components/Icons/Delete.vue';
// import { Page, PageProps, Errors, ErrorBag } from '@inertiajs/inertia';
import Draggable from 'vuedraggable';
import { TestFile } from '@/Dataset';
import { Buffer } from 'buffer';
import { TethysFile } from '@/Dataset';
// lastModified: 1691759507591
// lastModifiedDate: Fri Aug 11 2023 15:11:47 GMT+0200 (Mitteleuropäische Sommerzeit)
// name: 'freieIP.png'
// size: 112237
// type: 'image/png'
// webkitRelativePath: ''
interface IDictionary {
[index: string]: Array<string>;
@ -239,7 +215,7 @@ interface InteriaPage {
Draggable,
},
})
export default class FileUploadComponent extends Vue {
class FileUploadComponent extends Vue {
/**
* Connect map id.
*/
@ -255,22 +231,45 @@ export default class FileUploadComponent extends Vue {
// @Prop() files: Array<TestFile>;
@Prop({
type: Array<TestFile>,
type: Array<File>,
default: [],
})
modelValue: Array<TestFile>;
// mdiTrashCan = mdiTrashCan;
files: Array<TethysFile | File>;
get files() {
return this.modelValue;
get items(): Array<TethysFile | File> {
return this.files;
}
set files(value: Array<TestFile>) {
set items(values: Array<TethysFile | File>) {
// this.modelValue = value;
this.modelValue.length = 0;
this.modelValue.push(...value);
this.files.length = 0;
this.files.push(...values);
// values.forEach((item, index) => {
// item.sort_order = index + 1; // Assuming sort_order starts from 1
// this.files.push(item);
// });
}
dragEnterHandler(e) {
@Watch("files", {
deep: true
})
public propertyWatcher(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
});
}
public created() {
for (const file of this.files) {
if (!(file instanceof File)) {
// console.log(`${file.name} path is ${file.filePath} here.`);
this.generateURL(file);
// console.log(`${file.fileSrc} path.`);
}
}
}
public dragEnterHandler(e) {
e.preventDefault();
if (!this._hasFiles(e.dataTransfer)) {
return;
@ -278,17 +277,17 @@ export default class FileUploadComponent extends Vue {
++this.counter && this.overlay.classList.add('draggedover');
}
dragLeaveHandler() {
public dragLeaveHandler() {
1 > --this.counter && this.overlay.classList.remove('draggedover');
}
dragOverHandler(e) {
public dragOverHandler(e) {
if (this._hasFiles(e.dataTransfer)) {
e.preventDefault();
}
}
startDrag(evt, item) {
public startDrag(evt, item) {
evt.dataTransfer.dropEffect = 'move';
evt.dataTransfer.effectAllowed = 'move';
evt.dataTransfer.setData('itemID', item.id);
@ -296,28 +295,31 @@ export default class FileUploadComponent extends Vue {
// reset counter and append file to gallery when file is dropped
dropHandler(event) {
public dropHandler(event) {
event.preventDefault();
for (const file of event.dataTransfer.files) {
// let fileName = String(file.name.replace(/\.[^/.]+$/, ''));
// file.label = fileName;
// if (file.type.match('image.*')) {
// this.generateURL(file);
// }
this._addFile(file);
}
this.overlay.classList.remove('draggedover');
this.counter = 0;
}
onChangeFile(event) {
public onChangeFile(event) {
event.preventDefault();
// let uploadedFile = event.target.files[0];
// let fileName = String(event.target.files[0].name.replace(/\.[^/.]+$/, ''));
// form.file = event.target.files[0];
// form.upload.label = fileName;
// console.log(file.file);
for (const file of event.target.files) {
// let fileName = String(event.target.files[0].name.replace(/\.[^/.]+$/, ''));
// file.label = fileName;
// if (file.type.match('image.*')) {
// this.generateURL(file);
// }
this._addFile(file);
}
// this.overlay.classList.remove('draggedover');
@ -341,24 +343,58 @@ export default class FileUploadComponent extends Vue {
return Object.fromEntries(Object.entries(this.errors).filter(([key]) => key.startsWith('file')));
}
clearAllFiles(event) {
public clearAllFiles(event) {
event.preventDefault();
this.files.splice(0);
this.items.splice(0);
}
removeFile(key) {
this.files.splice(key, 1);
public removeFile(key) {
this.items.splice(key, 1);
}
generateURL(file) {
let fileSrc = URL.createObjectURL(file);
setTimeout(() => {
URL.revokeObjectURL(fileSrc);
}, 1000);
return fileSrc;
public generateURL(file: TethysFile | File): string {
// const arrayBuffer = Buffer.from(file.fileData.data);
// const blob = new Blob([file.fileData.data], { type: 'application/octet-stream' });
// const blob = new Blob([file.fileData], { type: 'image/png'});
// let fileSrc = file.fileData;
let localUrl: string = "";
if (file instanceof File) {
localUrl = URL.createObjectURL(file as Blob);
} else if (file.filePath) {
// const blob = new Blob([file.fileData]);
// localUrl = URL.createObjectURL(blob);
const parsed = JSON.parse(file.fileData);
// retrieve the original buffer of data
const buff = Buffer.from(parsed.blob, "base64");
const blob = new Blob([buff], { type: 'application/octet-stream' });
// file.blob = blob;
localUrl = URL.createObjectURL(blob);
file.fileSrc = localUrl;
}
// setTimeout(() => {
// URL.revokeObjectURL(localUrl);
// }, 1000);
return localUrl;
}
getFileSize(file) {
// private async downloadFile(id: number): Promise<string> {
// const response = await axios.get<Blob>(`/api/download/${id}`, {
// responseType: 'blob',
// });
// const url = URL.createObjectURL(response.data);
// setTimeout(() => {
// URL.revokeObjectURL(url);
// }, 1000);
// return url;
// }
public getFileSize(file) {
if (file.size > 1024) {
if (file.size > 1048576) {
return Math.round(file.size / 1048576) + 'mb';
@ -370,29 +406,75 @@ export default class FileUploadComponent extends Vue {
}
}
// check if file is of type image and prepend the initialied
// template to the target element
private _addFile(file: TestFile) {
// const isImage = file.type.match('image.*');
// const objectURL = URL.createObjectURL(file);
// private _addFile(file) {
// // const isImage = file.type.match('image.*');
// // const objectURL = URL.createObjectURL(file);
// this.files[objectURL] = file;
// let test: TethysFile = { upload: file, label: "dfdsfs", sorting: 0 };
// file.sorting = this.files.length;
this.files.push(file);
// // this.files[objectURL] = file;
// // let test: TethysFile = { upload: file, label: "dfdsfs", sorting: 0 };
// // file.sorting = this.files.length;
// file.sort_order = (this.items.length + 1),
// this.files.push(file);
// }
private _addFile(file: File) {
// const reader = new FileReader();
// reader.onload = (event) => {
// const base64Data = (event.target as FileReader).result as string;
// this.items.push(test);
// };
// reader.readAsDataURL(file);
if (file instanceof File) {
// const base64Data = await this.readBase64(file);
let test: TethysFile = {
label: file.name,
name: file.name,
size: file.size,
file_size: file.size,
// fileData: JSON.stringify({ blob: base64Data }),
// filePath: file.mozFullPath,
// path_name: file.mozFullPath,
webkitRelativePath: '',
lastModified: file.lastModified,
type: file.type,
mime_type: file.type,
visible_in_frontdoor: false,
visible_in_oai: false,
fileSrc: file.type.match('image.*')? this.generateURL(file) : "",
blob: file as Blob,
sort_order: (this.items.length + 1),
};
// this.items.push(test);
this.items[this.items.length] = test;
}
else {
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
private _hasFiles({ types = [] as Array<string> }) {
return types.indexOf('Files') > -1;
}
}
export default FileUploadComponent;
</script>
<style lang="css">
.hasImage:hover section {
background-color: rgba(5, 5, 5, 0.4);
}
.hasImage:hover button:hover {
background: rgba(5, 5, 5, 0.45);
}
@ -409,6 +491,7 @@ i {
#overlay.draggedover {
background-color: rgba(255, 255, 255, 0.7);
}
#overlay.draggedover p,
#overlay.draggedover i {
opacity: 1;

View File

@ -1,4 +1,4 @@
<script setup>
<script setup lang="ts">
import { computed } from 'vue';
import FormCheckRadio from '@/Components/FormCheckRadio.vue';
const props = defineProps({
@ -13,7 +13,7 @@ const props = defineProps({
type: {
type: String,
default: 'checkbox',
validator: (value) => ['checkbox', 'radio', 'switch'].includes(value),
validator: (value: string) => ['checkbox', 'radio', 'switch'].includes(value),
},
componentClass: {
type: String,
@ -27,11 +27,34 @@ const props = defineProps({
});
const emit = defineEmits(['update:modelValue']);
const computedValue = computed({
get: () => props.modelValue,
// get: () => props.modelValue,
get: () => {
// const ids = props.modelValue.map((obj) => obj.id);
// return ids;
if (Array.isArray(props.modelValue)) {
if (props.modelValue.every((item) => typeof item === 'number')) {
return props.modelValue;
} else if (props.modelValue.every((item) => hasIdAttribute(item))) {
const ids = props.modelValue.map((obj) => obj.id.toString());
return ids;
}
return props.modelValue;
}
// return props.modelValue;
},
set: (value) => {
emit('update:modelValue', value);
},
});
// Define a type guard to check if an object has an 'id' attribute
// function hasIdAttribute(obj: any): obj is { id: any } {
// return typeof obj === 'object' && 'id' in obj;
// }
const hasIdAttribute = (obj: any): obj is { id: any } => {
return typeof obj === 'object' && 'id' in obj;
};
</script>
<template>

View File

@ -59,8 +59,9 @@ Map.include({
});
const DEFAULT_BASE_LAYER_NAME = 'BaseLayer';
const DEFAULT_BASE_LAYER_ATTRIBUTION = '&copy; <a target="_blank" href="http://osm.org/copyright">OpenStreetMap</a> contributors';
// const OPEN_SEARCH_HOST = 'http://localhost:9200';
const OPEN_SEARCH_HOST = 'http://192.168.21.18';
// const OPENSEARCH_HOST = 'http://localhost:9200';
const OPENSEARCH_HOST = 'http://192.168.21.18';
// const OPENSEARCH_HOST = `http://${process.env.OPENSEARCH_PUBLIC_HOST}`;
let map: Map;
const props = defineProps({
@ -226,7 +227,7 @@ const handleDrawEventCreated = async (event) => {
try {
let response = await axios({
method: 'POST',
url: OPEN_SEARCH_HOST + '/tethys-records/_search',
url: OPENSEARCH_HOST + '/tethys-records/_search',
headers: { 'Content-Type': 'application/json' },
data: {
size: 1000,

View File

@ -6,7 +6,7 @@ import { gradientBgPurplePink, gradientBgDark, gradientBgPinkRed, gradientBgGree
const props = defineProps({
bg: {
type: String,
required: true,
required: false,
validator: (value) => ['purplePink', 'pinkRed', 'greenBlue'].includes(value),
},
});
@ -23,9 +23,11 @@ const colorClass = computed(() => {
return gradientBgPinkRed;
case 'greenBlue':
return gradientBgGreenBlue;
default:
return 'bg-white text-black dark:bg-slate-900/70 dark:text-white';
}
return 'bg-white';
// return 'bg-white';
});
</script>

View File

@ -12,9 +12,10 @@ export interface Dataset {
| (IErrorMessage | undefined)
| Coverage
| Array<DatasetReference>
| Array<File>;
| Array<File>
| (Array<number> | Array<Object>);
language: Ref<string>;
// licenses: Array<number>;
licenses: Array<number> | Array<Object>;
rights: boolean;
type: string;
creating_corporation: string;
@ -29,24 +30,49 @@ export interface Dataset {
// async (user): Promise<void>;
subjects: Array<Subject>;
references: Array<DatasetReference>;
files: Array<TestFile> | undefined;
files: Array<TethysFile>;
// upload: TethysFile
}
/** Provides information about files and allows JavaScript in a web page to access their content. */
export interface TestFile extends Blob {
// export interface TethysFile {
// readonly lastModified: number;
// readonly name: string;
// readonly webkitRelativePath: string;
// id: number;
// label: string;
// sorting: number;
// filePath: string;
// fileSrc: string;
// }
export interface TethysFile {
readonly lastModified: number;
readonly name: string;
readonly webkitRelativePath: string;
id?: number;
label: string;
sorting: number;
}
// sorting: number;
// path_name?: string; //only db path_name
filePath?: string;
fileSrc?: string;
blob: Blob;
fileData?: any;
// export interface TethysFile {
// label: string,
// sorting: number,
// upload: File,
// }
//additional:
comment?: string;
document_id?: number;
file_size: number;
language?: string;
mime_type: string;
type?: string;
size: number;
sort_order: number;
visible_in_frontdoor: boolean;
visible_in_oai: boolean;
}
export interface Subject {
// id: number;
@ -64,12 +90,14 @@ export interface DatasetReference {
}
export interface Title {
id?: number;
value: string;
type: string;
language: string | Ref<string>;
}
export interface Description {
id?: number;
value: string;
type: string;
language: string | Ref<string>;

View File

@ -34,6 +34,7 @@ import FormControl from '@/Components/FormControl.vue';
<LayoutGuest>
<Head title="Login" />
<!-- <SectionFullScreen v-slot="{ cardClass }" :bg="'greenBlue'"> -->
<SectionFullScreen v-slot="{ cardClass }">
<a class="text-2xl font-semibold flex justify-center items-center mb-8 lg:mb-10">
<img src="/logo.svg" class="h-10 mr-4" alt="Windster Logo" />

View File

@ -22,9 +22,7 @@ import CardBox from '@/Components/CardBox.vue';
import FormField from '@/Components/FormField.vue';
import FormControl from '@/Components/FormControl.vue';
import FormCheckRadioGroup from '@/Components/FormCheckRadioGroup.vue';
// import BaseDivider from '@/Components/BaseDivider.vue';
import BaseButton from '@/Components/BaseButton.vue';
// import BaseButtons from '@/Components/BaseButtons.vue';
import { stardust } from '@eidellev/adonis-stardust/client';
// import { Inertia } from '@inertiajs/inertia';
import CardBoxModal from '@/Components/CardBoxModal.vue';
@ -196,7 +194,7 @@ if (Object.keys(mainService.dataset).length == 0) {
// titles: [{ value: '', type: 'Main', language: language }],
// descriptions: [{ value: '', type: 'Abstract', language: language }],
// });
let form = useForm<Dataset>(dataset);
let form = useForm<Dataset>(dataset as Dataset);
// form.defaults();
// const emit = defineEmits(['update:modelValue', 'setRef']);
@ -292,11 +290,14 @@ const submit = async () => {
// this.currentStatus = STATUS_SAVING;
// serrors = [];
// const files = form.files.map((obj) => {
// return new File([obj.blob], obj.label, { type: obj.type, lastModified: obj.lastModified });
// });
// formStep.value++;
await form
.transform((data) => ({
...data,
...data,
rights: form.rights && form.rights == true ? 'true' : 'false',
}))
.post(route, {
@ -729,8 +730,9 @@ Removes a selected keyword
</SearchAutocomplete>
<TablePersons :persons="form.contributors" v-if="form.contributors.length > 0"
:contributortypes="contributorTypes" :errors="form.errors"/>
<div class="text-red-400 text-sm" v-if="form.errors.contributors && Array.isArray(form.errors.contributors)">
:contributortypes="contributorTypes" :errors="form.errors" />
<div class="text-red-400 text-sm"
v-if="form.errors.contributors && Array.isArray(form.errors.contributors)">
{{ form.errors.contributors.join(', ') }}
</div>
</CardBox>
@ -1013,7 +1015,7 @@ Removes a selected keyword
</p>
</div> -->
<FileUploadComponent v-model="form.files"></FileUploadComponent>
<FileUploadComponent :files="form.files"></FileUploadComponent>
<div class="text-red-400 text-sm" v-if="form.errors['file'] && Array.isArray(form.errors['file'])">
{{ form.errors['file'].join(', ') }}
@ -1039,7 +1041,8 @@ Removes a selected keyword
Next
</button>
<button v-if="formStep == 4" :disabled="form.processing" :class="{ 'opacity-25': form.processing }"
<button v-if="formStep == 4" :disabled="form.processing"
:class="{ 'opacity-25': form.processing }"
class="text-base hover:scale-110 focus:outline-none flex justify-center px-4 py-2 rounded font-bold cursor-pointer hover:bg-teal-200 bg-teal-100 text-teal-700 border duration-200 ease-in-out border-teal-600 transition"
@click.stop="submit">
Save

View File

@ -12,6 +12,7 @@
<!-- <div class="max-w-2xl mx-auto"> -->
<CardBox :form="true">
<FormValidationErrors v-bind:errors="errors" />
<div class="mb-4">
<!-- <label for="title" class="block text-gray-700 font-bold mb-2">Title:</label>
<input
@ -21,6 +22,7 @@
v-model="form.language"
/> -->
<div class="flex flex-col md:flex-row">
<!-- (1) language field -->
<FormField label="Language *" help="required: select dataset main language"
:class="{ 'text-red-400': errors.language }" class="w-full flex-1">
<FormControl required v-model="form.language" :type="'select'" placeholder="[Enter Language]"
@ -31,17 +33,41 @@
</FormControl>
</FormField>
</div>
<FormField label="Dataset Type *" help="required: dataset type"
:class="{ 'text-red-400': form.errors.type }">
<FormControl required v-model="form.type" :type="'select'" placeholder="-- select type --"
:errors="errors.type" :options="doctypes">
<div class="text-red-400 text-sm" v-if="form.errors.type && Array.isArray(form.errors.type)">
{{ form.errors.type.join(', ') }}
</div>
</FormControl>
<!-- (2) licenses -->
<FormField label="Licenses" wrap-body :class="{ 'text-red-400': form.errors.licenses }"
class="mt-8 w-full mx-2 flex-1">
<FormCheckRadioGroup v-model="form.licenses" name="licenses" is-column :options="licenses" />
</FormField>
<!-- titles -->
<div class="flex flex-col md:flex-row">
<!-- (3) dataset_type -->
<FormField label="Dataset Type *" help="required: dataset type"
:class="{ 'text-red-400': form.errors.type }" class="w-full mx-2 flex-1">
<FormControl required v-model="form.type" :type="'select'" placeholder="-- select type --"
:errors="errors.type" :options="doctypes">
<div class="text-red-400 text-sm"
v-if="form.errors.type && Array.isArray(form.errors.type)">
{{ form.errors.type.join(', ') }}
</div>
</FormControl>
</FormField>
<!-- (4) creating_corporation -->
<FormField label="Creating Corporation *"
:class="{ 'text-red-400': form.errors.creating_corporation }" class="w-full mx-2 flex-1">
<FormControl required v-model="form.creating_corporation" type="text"
placeholder="[enter creating corporation]" :is-read-only="true">
<div class="text-red-400 text-sm"
v-if="form.errors.creating_corporation && Array.isArray(form.errors.creating_corporation)">
{{ form.errors.creating_corporation.join(', ') }}
</div>
</FormControl>
</FormField>
</div>
<BaseDivider />
<!-- (5) titles -->
<CardBox class="mb-6 shadow" :has-form-data="false" title="Titles" :icon="mdiFinance"
:header-icon="mdiPlusCircle" v-on:header-icon-click="addTitle()">
<div class="flex flex-col md:flex-row">
@ -79,8 +105,8 @@
</tr>
</thead>
<tbody>
<template v-for="(item, index) in form.titles" :key="index">
<tr v-if="item.type != 'Main'">
<template v-for="(title, index) in form.titles" :key="index">
<tr v-if="title.type != 'Main'">
<!-- <td scope="row">{{ index + 1 }}</td> -->
<td data-label="Title Value">
<FormControl required v-model="form.titles[index].value" type="text"
@ -113,7 +139,7 @@
<BaseButtons type="justify-start lg:justify-end" no-wrap>
<!-- <BaseButton color="info" :icon="mdiEye" small @click="isModalActive = true" /> -->
<BaseButton color="danger" :icon="mdiTrashCan" small
@click.prevent="removeTitle(index)" />
v-if="title.id == undefined" @click.prevent="removeTitle(index)" />
</BaseButtons>
</td>
</tr>
@ -122,6 +148,7 @@
</table>
</CardBox>
<!-- (6) descriptions -->
<CardBox class="mb-6 shadow" :has-form-data="false" title="Descriptions" :icon="mdiFinance"
:header-icon="mdiPlusCircle" v-on:header-icon-click="addDescription()">
<div class="flex flex-col md:flex-row">
@ -172,7 +199,7 @@
</td>
<td data-label="Description Type">
<FormControl required v-model="form.descriptions[index].type" type="select"
:options="props.descriptiontypes" placeholder="[select title type]">
:options="descriptiontypes" placeholder="[select title type]">
<div class="text-red-400 text-sm"
v-if="Array.isArray(form.errors[`descriptions.${index}.type`])">
{{ form.errors[`descriptions.${index}.type`].join(', ') }}
@ -191,7 +218,7 @@
<td class="before:hidden lg:w-1 whitespace-nowrap">
<BaseButtons type="justify-start lg:justify-end" no-wrap>
<!-- <BaseButton color="info" :icon="mdiEye" small @click="isModalActive = true" /> -->
<BaseButton color="danger" :icon="mdiTrashCan" small
<BaseButton color="danger" :icon="mdiTrashCan" small v-if="item.id == undefined"
@click.prevent="removeDescription(index)" />
</BaseButtons>
</td>
@ -201,14 +228,182 @@
</table>
</CardBox>
<MapComponent v-if="form.coverage"
:mapOptions="mapOptions"
:baseMaps="baseMaps"
:fitBounds="fitBounds"
:coverage="form.coverage"
:mapId="mapId"
v-bind-event:onMapInitializedEvent="onMapInitialized"
></MapComponent>
<!-- (7) authors -->
<CardBox class="mb-6 shadow" has-table title="Authors" :icon="mdiBookOpenPageVariant">
<SearchAutocomplete source="/api/persons" :response-property="'first_name'"
placeholder="search in person table...." v-on:person="onAddAuthor"></SearchAutocomplete>
<TablePersons :persons="form.authors" v-if="form.authors.length > 0" />
<div class="text-red-400 text-sm" v-if="errors.authors && Array.isArray(errors.authors)">
{{ errors.authors.join(', ') }}
</div>
</CardBox>
<!-- (8) contributors -->
<CardBox class="mb-6 shadow" has-table title="Contributors" :icon="mdiBookOpenPageVariant">
<SearchAutocomplete source="/api/persons" :response-property="'first_name'"
placeholder="search in person table...." v-on:person="onAddContributor">
</SearchAutocomplete>
<TablePersons :persons="form.contributors" v-if="form.contributors.length > 0"
:contributortypes="contributorTypes" :errors="form.errors" />
<div class="text-red-400 text-sm"
v-if="form.errors.contributors && Array.isArray(form.errors.contributors)">
{{ form.errors.contributors.join(', ') }}
</div>
</CardBox>
<div class="flex flex-col md:flex-row">
<!-- (9) project_id -->
<FormField label="Project.." help="project is optional"
:class="{ 'text-red-400': errors.project_id }" class="w-full mx-2 flex-1">
<FormControl required v-model="form.project_id" :type="'select'" placeholder="[Select Project]"
:errors="form.errors.project_id" :options="projects">
<div class="text-red-400 text-sm" v-if="form.errors.project_id">
{{ form.errors.project_id.join(', ') }}
</div>
</FormControl>
</FormField>
<!-- (10) embargo_date -->
<FormField label="Embargo Date.." help="embargo date is optional"
:class="{ 'text-red-400': errors.embargo_date }" class="w-full mx-2 flex-1">
<FormControl v-model="form.embargo_date" :type="'date'" placeholder="date('y-m-d')"
:errors="form.errors.embargo_date">
<div class="text-red-400 text-sm" v-if="form.errors.embargo_date">
{{ form.errors.embargo_date.join(', ') }}
</div>
</FormControl>
</FormField>
</div>
<BaseDivider />
<MapComponent v-if="form.coverage" :mapOptions="mapOptions" :baseMaps="baseMaps" :fitBounds="fitBounds"
:coverage="form.coverage" :mapId="mapId" v-bind-event:onMapInitializedEvent="onMapInitialized">
</MapComponent>
<div class="flex flex-col md:flex-row">
<!-- x min and max -->
<FormField label="Coverage X Min" :class="{ 'text-red-400': form.errors['coverage.x_min'] }"
class="w-full mx-2 flex-1">
<FormControl required v-model="form.coverage.x_min" type="text" placeholder="[enter x_min]">
<div class="text-red-400 text-sm"
v-if="form.errors['coverage.x_min'] && Array.isArray(form.errors['coverage.x_min'])">
{{ form.errors['coverage.x_min'].join(', ') }}
</div>
</FormControl>
</FormField>
<FormField label="Coverage X Max" :class="{ 'text-red-400': form.errors['coverage.x_max'] }"
class="w-full mx-2 flex-1">
<FormControl required v-model="form.coverage.x_max" type="text" placeholder="[enter x_max]">
<div class="text-red-400 text-sm"
v-if="form.errors['coverage.x_max'] && Array.isArray(form.errors['coverage.x_max'])">
{{ form.errors['coverage.x_max'].join(', ') }}
</div>
</FormControl>
</FormField>
<!-- y min and max -->
<FormField label="Coverage Y Min" :class="{ 'text-red-400': form.errors['coverage.y_min'] }"
class="w-full mx-2 flex-1">
<FormControl required v-model="form.coverage.y_min" type="text" placeholder="[enter y_min]">
<div class="text-red-400 text-sm"
v-if="form.errors['coverage.y_min'] && Array.isArray(form.errors['coverage.y_min'])">
{{ form.errors['coverage.y_min'].join(', ') }}
</div>
</FormControl>
</FormField>
<FormField label="Coverage Y Max" :class="{ 'text-red-400': form.errors['coverage.y_max'] }"
class="w-full mx-2 flex-1">
<FormControl required v-model="form.coverage.y_max" type="text" placeholder="[enter y_max]">
<div class="text-red-400 text-sm"
v-if="form.errors['coverage.y_max'] && Array.isArray(form.errors['coverage.y_max'])">
{{ form.errors['coverage.y_max'].join(', ') }}
</div>
</FormControl>
</FormField>
</div>
<CardBox class="mb-6 shadow" has-table title="Dataset References" :header-icon="mdiPlusCircle"
v-on:header-icon-click="addReference">
<table class="table-fixed border-green-900" v-if="form.references.length">
<thead>
<tr>
<th class="w-4/12">Value</th>
<th class="w-2/12">Type</th>
<th class="w-3/12">Relation</th>
<th class="w-2/12">Label</th>
<th class="w-1/12"></th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in form.references">
<td data-label="Reference Value">
<!-- <input name="Reference Value" class="form-control"
placeholder="[VALUE]" v-model="item.value" /> -->
<FormControl required v-model="item.value" :type="'text'" placeholder="[VALUE]"
:errors="form.errors.embargo_date">
<div class="text-red-400 text-sm"
v-if="form.errors[`references.${index}.value`] && Array.isArray(form.errors[`references.${index}.value`])">
{{ form.errors[`references.${index}.value`].join(', ') }}
</div>
</FormControl>
</td>
<td>
<FormControl required v-model="form.references[index].type" type="select"
:options="referenceIdentifierTypes" placeholder="[type]">
<div class="text-red-400 text-sm"
v-if="Array.isArray(form.errors[`references.${index}.type`])">
{{ form.errors[`references.${index}.type`].join(', ') }}
</div>
</FormControl>
</td>
<td>
<!-- {!! Form::select('Reference[Relation]', $relationTypes, null,
['placeholder' => '[relationType]', 'v-model' => 'item.relation',
'data-vv-scope' => 'step-2'])
!!} -->
<FormControl required v-model="form.references[index].relation" type="select"
:options="relationTypes" placeholder="[relation type]">
<div class="text-red-400 text-sm"
v-if="Array.isArray(form.errors[`references.${index}.relation`])">
{{ form.errors[`references.${index}.relation`].join(', ') }}
</div>
</FormControl>
</td>
<td data-label="Reference Label">
<!-- <input name="Reference Label" class="form-control" v-model="item.label" /> -->
<FormControl required v-model="form.references[index].label" type="text"
placeholder="[reference label]">
<div class="text-red-400 text-sm"
v-if="form.errors[`references.${index}.label`] && Array.isArray(form.errors[`references.${index}.label`])">
{{ form.errors[`references.${index}.label`].join(', ') }}
</div>
</FormControl>
</td>
<td class="before:hidden lg:w-1 whitespace-nowrap">
<!-- <BaseButton color="info" :icon="mdiEye" small @click="isModalActive = true" /> -->
<BaseButton color="danger" :icon="mdiTrashCan" small
@click.prevent="removeReference(index)" />
</td>
</tr>
</tbody>
</table>
</CardBox>
<BaseDivider />
<CardBox class="mb-6 shadow" has-table title="Dataset Keywords" :icon="mdiEarthPlus"
:header-icon="mdiPlusCircle" v-on:header-icon-click="addKeyword">
<!-- <ul>
<li v-for="(subject, index) in form.subjects" :key="index">
{{ subject.value }} <BaseButton color="danger" :icon="mdiTrashCan" small @click.prevent="removeKeyword(index)" />
</li>
</ul> -->
<TableKeywords :keywords="form.subjects" :errors="form.errors" :subjectTypes="subjectTypes"
v-if="form.subjects.length > 0" />
</CardBox>
</div>
@ -233,18 +428,14 @@
</option>
</select> -->
</div>
<div class="mb-4">
<label for="license" class="block text-gray-700 font-bold mb-2">License:</label>
<!-- <select
id="license"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
v-model="dataset.license_id"
>
<option v-for="license in licenses" :key="license.id" :value="license.id" class="block px-4 py-2 text-gray-700">
{{ license.name_long }}
</option>
</select> -->
<FileUploadComponent :files="form.files"></FileUploadComponent>
<div class="text-red-400 text-sm" v-if="form.errors['file'] && Array.isArray(form.errors['files'])">
{{ form.errors['files'].join(', ') }}
</div>
<!-- Add more input fields for the other properties of the dataset -->
<!-- <button
type="submit"
@ -252,11 +443,17 @@
>
Save
</button> -->
<template #footer>
<BaseButtons>
<BaseButton type="submit" label="Submit" color="info" :class="{ 'opacity-25': form.processing }"
:disabled="form.processing">
<BaseButton @click.stop="submit" :disabled="form.processing" label="Save" color="info"
:class="{ 'opacity-25': form.processing }" small>
</BaseButton>
<!-- <button :disabled="form.processing" :class="{ 'opacity-25': form.processing }"
class="text-base hover:scale-110 focus:outline-none flex justify-center px-4 py-2 rounded font-bold cursor-pointer hover:bg-teal-200 bg-teal-100 text-teal-700 border duration-200 ease-in-out border-teal-600 transition"
@click.stop="submit">
Save
</button> -->
</BaseButtons>
</template>
</CardBox>
@ -266,31 +463,61 @@
</template>
<script setup lang="ts">
import { Head, useForm } from '@inertiajs/vue3';
// import EditComponent from "./../EditComponent";
// export default EditComponent;
// import { Component, Vue, Prop, Setup, toNative } from 'vue-facing-decorator';
// import AuthLayout from '@/Layouts/Auth.vue';
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
import { Dataset, Title } from '@/Dataset';
import { useForm, Head } from '@inertiajs/vue3';
// import { ref } from 'vue';
// import { MainService } from '@/Stores/main';
// import FormInput from '@/Components/FormInput.vue'; // @/Components/FormInput.vue'
import { Dataset, Title, Subject, TethysFile } from '@/Dataset';
import { stardust } from '@eidellev/adonis-stardust/client';
import FormField from '@/Components/FormField.vue';
import FormControl from '@/Components/FormControl.vue';
import SectionMain from '@/Components/SectionMain.vue';
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
import { mdiImageText, mdiArrowLeftBoldOutline, mdiPlusCircle, mdiFinance, mdiTrashCan } from '@mdi/js';
// import BaseDivider from '@/Components/BaseDivider.vue';
import FormCheckRadioGroup from '@/Components/FormCheckRadioGroup.vue';
import BaseButton from '@/Components/BaseButton.vue';
import BaseButtons from '@/Components/BaseButtons.vue';
import BaseDivider from '@/Components/BaseDivider.vue';
import CardBox from '@/Components/CardBox.vue';
import { stardust } from '@eidellev/adonis-stardust/client';
import MapComponent from '@/Components/Map/map.component.vue';
import SearchAutocomplete from '@/Components/SearchAutocomplete.vue';
import TablePersons from '@/Components/TablePersons.vue';
import TableKeywords from '@/Components/TableKeywords.vue';
import FormValidationErrors from '@/Components/FormValidationErrors.vue';
import FileUploadComponent from '@/Components/FileUpload.vue';
import { MapOptions } from '@/Components/Map/MapOptions';
import { LatLngBoundsExpression } from 'leaflet/src/geo/LatLngBounds';
import { LayerOptions } from '@/Components/Map/LayerOptions';
import {
mdiImageText,
mdiArrowLeftBoldOutline,
mdiPlusCircle,
mdiFinance,
mdiTrashCan,
mdiBookOpenPageVariant,
mdiEarthPlus,
} from '@mdi/js';
import { notify } from '@/notiwind';
const props = defineProps({
dataset: {
errors: {
type: Object,
default: () => ({}),
},
licenses: {
type: Object,
default: () => ({}),
},
languages: {
type: Object,
default: () => ({}),
},
languages: {},
doctypes: {
type: Object,
default: () => ({}),
@ -299,14 +526,37 @@ const props = defineProps({
type: Object,
default: () => ({}),
},
projects: {
type: Object,
default: () => ({}),
},
descriptiontypes: {
type: Object,
default: () => ({}),
},
errors: {
contributorTypes: {
type: Object,
default: () => ({}),
},
subjectTypes: {
type: Object,
default: () => ({}),
},
referenceIdentifierTypes: {
type: Object,
default: () => ({}),
},
relationTypes: {
type: Object,
default: () => ({}),
},
dataset: {
type: Object,
default: () => ({}),
},
});
// const projects = reactive([]);
@ -325,7 +575,42 @@ const fitBounds: LatLngBoundsExpression = [
];
const mapId = 'test';
// const downloadFile = async (id: string): Promise<string> => {
// const response = await axios.get<Blob>(`/api/download/${id}`, {
// responseType: 'blob',
// });
// const url = URL.createObjectURL(response.data);
// setTimeout(() => {
// URL.revokeObjectURL(url);
// }, 1000);
// return url;
// };
// for (const file of props.dataset.files) {
// // console.log(`${file.name} path is ${file.filePath} here.`);
// file.fileSrc = ref("");
// // downloadFile(file.id).then((value: string) => {
// // file.fileSrc = ref(value);
// // form = useForm<Dataset>(props.dataset as Dataset);
// // });
// }
let form = useForm<Dataset>(props.dataset as Dataset);
// const mainService = MainService();
// mainService.fetchfiles(props.dataset);
// const files = computed(() => props.dataset.file);
// let form = useForm<Dataset>(props.dataset as Dataset);
// const form = useForm({
// _method: 'put',
// login: props.user.login,
@ -342,9 +627,63 @@ let form = useForm<Dataset>(props.dataset as Dataset);
// this.projects = data.projects;
// this.licenses = data.licenses;
// }
const submit = async (): Promise<void> => {
let route = stardust.route('dataset.update', [props.dataset.id]);
// await Inertia.post('/app/register', this.form);
// await router.post('/app/register', this.form);
if (form.licenses.every((item) => hasIdAttribute(item))) {
form.licenses = form.licenses.map((obj) => obj.id.toString());
}
const [fileUploads, fileInputs] = form.files?.reduce(
([fileUploads, fileInputs], obj) => {
if (!obj.id) {
// return MultipartFile for file upload
fileUploads[obj.sort_order] = new File([obj.blob], obj.label, { type: obj.type, lastModified: obj.lastModified });
} else {
// return normal request input
fileInputs.push(obj);
}
return [fileUploads, fileInputs];
},
[[], []] as [Array<File>, Array<TethysFile>]
) as [Array<File>, Array<TethysFile>];
await form
.transform((data) => ({
...data,
licenses: form.licenses.every((item) => hasIdAttribute(item))
? form.licenses.map((obj) => obj.id.toString())
: form.licenses,
files: fileUploads,
fileInputs: fileInputs,
// files: form.files.map((obj) => {
// let file;
// if (!obj.id) {
// // return MultipartFile for file upload
// file = new File([obj.blob], obj.label, { type: obj.type, lastModified: obj.lastModified });
// } else {
// // return normal request input
// file = obj;
// }
// return file;
// }),
rights: 'true',
}))
.put(route);
};
const hasIdAttribute = (obj: any): obj is { id: any } => {
return typeof obj === 'object' && 'id' in obj;
};
const addTitle = () => {
let newTitle: Title = { value: '', language: '', type: '' };
//this.dataset.files.push(uploadedFiles[i]);
form.titles.push(newTitle);
};
const removeTitle = (key) => {
@ -353,20 +692,58 @@ const removeTitle = (key) => {
const addDescription = () => {
let newDescription = { value: '', language: '', type: '' };
//this.dataset.files.push(uploadedFiles[i]);
form.descriptions.push(newDescription);
};
const removeDescription = (key) => {
form.descriptions.splice(key, 1);
};
const onAddAuthor = (person) => {
if (form.authors.filter((e) => e.id === person.id).length > 0) {
notify({ type: 'warning', title: 'Warning', text: 'person is already defined as author' }, 4000);
} else if (form.contributors.filter((e) => e.id === person.id).length > 0) {
notify({ type: 'warning', title: 'Warning', text: 'person is already defined as contributor' });
} else {
form.authors.push(person);
notify({ type: 'info', text: 'person has been successfully added as author' });
}
};
const onAddContributor = (person) => {
if (form.contributors.filter((e) => e.id === person.id).length > 0) {
notify({ type: 'warning', title: 'Warning', text: 'person is already defined as contributor' }, 4000);
} else if (form.authors.filter((e) => e.id === person.id).length > 0) {
notify({ type: 'warning', title: 'Warning', text: 'person is already defined as author' }, 4000);
} else {
// person.pivot = { contributor_type: '' };
// // person.pivot = { name_type: '', contributor_type: '' };
form.contributors.push(person);
notify({ type: 'info', text: 'person has been successfully added as contributor' }, 4000);
}
};
const addKeyword = () => {
let newSubject: Subject = { value: 'test', language: '', type: 'uncontrolled' };
//this.dataset.files.push(uploadedFiles[i]);
form.subjects.push(newSubject);
};
const addReference = () => {
let newReference = { value: '', label: '', relation: '', type: '' };
//this.dataset.files.push(uploadedFiles[i]);
form.references.push(newReference);
};
const removeReference = (key) => {
form.references.splice(key, 1);
};
const onMapInitialized = (newItem) => {
// notify({ type: 'info', text: message });
console.log(newItem);
};
</script>
<!-- <style>
<style>
.max-w-2xl {
max-width: 2xl;
}
@ -398,4 +775,4 @@ const onMapInitialized = (newItem) => {
0 2px 4px 0 rgba(66, 72, 78, 0.12),
0 4px 8px 0 rgba(66, 72, 78, 0.16);
}
</style> -->
</style>

View File

@ -0,0 +1,240 @@
import { Component, Vue, Prop, toNative } from 'vue-facing-decorator';
// import AuthLayout from '@/Layouts/Auth.vue';
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
import { useForm, InertiaForm, Head } from '@inertiajs/vue3';
import FormInput from '@/Components/FormInput.vue'; // @/Components/FormInput.vue'
import { Dataset, Title, Subject } from '@/Dataset';
import { stardust } from '@eidellev/adonis-stardust/client';
import FormField from '@/Components/FormField.vue';
import FormControl from '@/Components/FormControl.vue';
import SectionMain from '@/Components/SectionMain.vue';
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
import FormCheckRadioGroup from '@/Components/FormCheckRadioGroup.vue';
import BaseButton from '@/Components/BaseButton.vue';
import BaseButtons from '@/Components/BaseButtons.vue';
import BaseDivider from '@/Components/BaseDivider.vue';
import CardBox from '@/Components/CardBox.vue';
import MapComponent from '@/Components/Map/map.component.vue';
import SearchAutocomplete from '@/Components/SearchAutocomplete.vue';
import TablePersons from '@/Components/TablePersons.vue';
import TableKeywords from '@/Components/TableKeywords.vue';
import FormValidationErrors from '@/Components/FormValidationErrors.vue';
import FileUploadComponent from '@/Components/FileUpload.vue';
import { MapOptions } from '@/Components/Map/MapOptions';
import { LatLngBoundsExpression } from 'leaflet/src/geo/LatLngBounds';
import { LayerOptions } from '@/Components/Map/LayerOptions';
import {
mdiImageText,
mdiArrowLeftBoldOutline,
mdiPlusCircle,
mdiFinance,
mdiTrashCan,
mdiBookOpenPageVariant,
mdiEarthPlus,
} from '@mdi/js';
import { notify } from '@/notiwind';
export interface IErrorMessage {
[key: string]: Array<string>;
}
@Component({
name: 'EditComponent',
components: {
LayoutAuthenticated,
FormInput,
Head,
FormField,
FormControl,
SectionMain,
SectionTitleLineWithButton,
FormCheckRadioGroup,
BaseButton,
BaseButtons,
BaseDivider,
CardBox,
MapComponent,
SearchAutocomplete,
TablePersons,
TableKeywords,
FormValidationErrors,
FileUploadComponent,
},
})
class EditComponent extends Vue {
// Component Property
@Prop({ type: Object, default: () => ({}) })
public errors: IErrorMessage;
@Prop({ type: Object, default: () => ({}) })
public licenses;
@Prop({ type: Object, default: () => ({}) })
public languages;
@Prop({ type: Object, default: () => ({}) })
public doctypes;
@Prop({ type: Object, default: () => ({}) })
public titletypes;
@Prop({ type: Object, default: () => ({}) })
public projects;
@Prop({ type: Object, default: () => ({}) })
public descriptiontypes;
@Prop({ type: Object, default: () => {} })
public contributorTypes;
@Prop({ type: Object, default: () => ({}) })
public subjectTypes;
@Prop({ type: Object, default: () => ({}) })
public referenceIdentifierTypes;
@Prop({ type: Object, default: () => ({}) })
public relationTypes;
@Prop({ type: Object, default: () => ({}) })
public dataset: Dataset;
// @Prop({
// type: Object,
// default: () => ({}),
// })
// public datasetHasLicenses;
// Data Property
// public form: InertiaForm<Dataset>; // = useForm<Dataset>(this.dataset as Dataset);
// public form : InertiaForm<Dataset>= useForm<Dataset>([]);
// @Setup(() => useForm<Dataset>(this.dataset as Dataset))
public form: InertiaForm<Dataset>;
// @Hook
created() {
this.form = useForm<Dataset>(this.dataset as Dataset);
// this.form.licenses = this.datasetHasLicenses;
this.form.uploads = [];
}
public mapOptions: MapOptions = {
center: [48.208174, 16.373819],
zoom: 3,
zoomControl: false,
attributionControl: false,
};
public baseMaps: Map<string, LayerOptions> = new Map<string, LayerOptions>();
public fitBounds: LatLngBoundsExpression = [
[46.4318173285, 9.47996951665],
[49.0390742051, 16.9796667823],
];
public mapId = 'test';
mdiImageText = mdiImageText;
mdiArrowLeftBoldOutline = mdiArrowLeftBoldOutline;
mdiPlusCircle = mdiPlusCircle;
mdiFinance = mdiFinance;
mdiTrashCan = mdiTrashCan;
mdiBookOpenPageVariant = mdiBookOpenPageVariant;
mdiEarthPlus = mdiEarthPlus;
stardust = stardust;
// mounted() {
// this.form = useForm<Dataset>(this.dataset as Dataset);// Initialize myData with the value of propValue
// }
// public results: Array<any> = [];
// Component method
public async submit(): Promise<void> {
let route = this.stardust.route('dataset.update', [this.dataset.id]);
// await Inertia.post('/app/register', this.form);
// await router.post('/app/register', this.form);
if (this.form.licenses.every((item) => this.hasIdAttribute(item))) {
this.form.licenses = this.form.licenses.map((obj) => obj.id.toString());
}
await this.form
.transform((data) => ({
...data,
licenses: this.form.licenses.every((item) => this.hasIdAttribute(item))
? this.form.licenses.map((obj) => obj.id.toString())
: this.form.licenses,
rights: 'true',
}))
.put(route);
}
private hasIdAttribute(obj: any): obj is { id: any } {
return typeof obj === 'object' && 'id' in obj;
}
public addTitle(): void {
const newTitle: Title = { value: '', language: '', type: '' };
this.form.titles.push(newTitle);
}
public removeTitle(key: number): void {
this.form.titles.splice(key, 1);
}
public addDescription(): void {
const newDescription = { value: '', language: '', type: '' };
this.form.descriptions.push(newDescription);
}
public removeDescription(key: number): void {
this.form.descriptions.splice(key, 1);
}
public onAddAuthor(person) {
if (this.form.authors.filter((e) => e.id === person.id).length > 0) {
notify({ type: 'warning', title: 'Warning', text: 'person is already defined as author' }, 4000);
} else if (this.form.contributors.filter((e) => e.id === person.id).length > 0) {
notify({ type: 'warning', title: 'Warning', text: 'person is already defined as contributor' });
} else {
this.form.authors.push(person);
notify({ type: 'info', text: 'person has been successfully added as author' });
}
}
public onAddContributor(person) {
if (this.form.contributors.filter((e) => e.id === person.id).length > 0) {
notify({ type: 'warning', title: 'Warning', text: 'person is already defined as contributor' }, 4000);
} else if (this.form.authors.filter((e) => e.id === person.id).length > 0) {
notify({ type: 'warning', title: 'Warning', text: 'person is already defined as author' }, 4000);
} else {
// person.pivot = { contributor_type: '' };
// // person.pivot = { name_type: '', contributor_type: '' };
this.form.contributors.push(person);
notify({ type: 'info', text: 'person has been successfully added as contributor' }, 4000);
}
}
public addKeyword() {
let newSubject: Subject = { value: 'test', language: '', type: 'uncontrolled' };
//this.dataset.files.push(uploadedFiles[i]);
this.form.subjects.push(newSubject);
}
public addReference() {
let newReference = { value: '', label: '', relation: '', type: '' };
//this.dataset.files.push(uploadedFiles[i]);
this.form.references.push(newReference);
}
public removeReference(key) {
this.form.references.splice(key, 1);
}
public onMapInitialized(newItem: any): void {
console.log(newItem);
}
}
export default toNative(EditComponent);
// export default toNative(EditComponent);

View File

@ -39,6 +39,7 @@ export const MainService = defineStore('main', {
authors: [] as Array<Person>,
// persons: [] as Array<Person>,
datasets: [],
files:[],
dataset: {} as Dataset,
}),
@ -108,5 +109,19 @@ export const MainService = defineStore('main', {
alert(error.message);
});
},
// fetchfiles(id) {
// // sampleDataKey= authors or datasets
// axios
// .get(`api/files/${id}`)
// .then((r) => {
// if (r.data) {
// this[sampleDataKey] = r.data;
// }
// })
// .catch((error) => {
// alert(error.message);
// });
// },
},
});

View File

@ -172,6 +172,12 @@ Route.group(() => {
.as('dataset.edit')
.where('id', Route.matchers.number())
.middleware(['auth', 'can:dataset-submit']);
Route.put('/dataset/:id/update', 'DatasetController.update')
.as('dataset.update')
.where('id', Route.matchers.number())
.middleware(['auth', 'can:dataset-submit']);
Route.get('/dataset/:id/delete', 'DatasetController.delete').as('dataset.delete').middleware(['auth', 'can:dataset-delete']);
Route.put('/dataset/:id/deleteupdate', 'DatasetController.deleteUpdate')
.as('dataset.deleteUpdate')

View File

@ -16,6 +16,8 @@ Route.group(() => {
Route.get('/dataset/:publish_id', 'DatasetController.findOne').as('dataset.findOne');
Route.get('/sitelinks/:year', 'HomeController.findDocumentsPerYear');
Route.get('/years', 'HomeController.findYears');
Route.get('/download/:id', 'FileController.findOne').as('file.findOne');
});
// .middleware("auth:api");
})

View File

@ -1,6 +1,17 @@
const { join, resolve, dirname } = require('path');
const Encore = require('@symfony/webpack-encore');
const { VueLoaderPlugin } = require('vue-loader');
const dotenv = require('dotenv-webpack');
// Load the environment variables from the.env file
Encore.addPlugin(
new dotenv({
path: ".env",
defaults: ".env",
systemvars: true,
allowEmptyValues: true,
})
)
const babelLoader = {
// test: /\.js$/,