forked from geolba/tethys.backend
- added npm package dotenv-webpack for using env variables on clientside
- 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:
parent
a7142f694f
commit
d8bdce1369
54
app/Controllers/Http/Api/FileController.ts
Normal file
54
app/Controllers/Http/Api/FileController.ts
Normal 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}.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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')));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
179
app/Validators/UpdateDatasetValidator.ts
Normal file
179
app/Validators/UpdateDatasetValidator.ts
Normal 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
1279
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
getFileSize(file) {
|
||||
// setTimeout(() => {
|
||||
// URL.revokeObjectURL(localUrl);
|
||||
// }, 1000);
|
||||
return localUrl;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -59,8 +59,9 @@ Map.include({
|
|||
});
|
||||
const DEFAULT_BASE_LAYER_NAME = 'BaseLayer';
|
||||
const DEFAULT_BASE_LAYER_ATTRIBUTION = '© <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,
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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,6 +290,9 @@ 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
|
||||
|
@ -730,7 +731,8 @@ Removes a selected keyword
|
|||
|
||||
<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)">
|
||||
<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
|
||||
|
|
|
@ -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>
|
||||
|
||||
<!-- (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>
|
||||
|
||||
<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="{ '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)">
|
||||
<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>
|
||||
|
||||
<!-- titles -->
|
||||
<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>
|
||||
|
|
240
resources/js/Pages/Submitter/EditComponent.ts
Normal file
240
resources/js/Pages/Submitter/EditComponent.ts
Normal 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);
|
|
@ -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);
|
||||
// });
|
||||
// },
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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");
|
||||
})
|
||||
|
|
|
@ -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$/,
|
||||
|
|
Loading…
Reference in New Issue
Block a user