Skip to content
Commits on Source (3)
......@@ -3,6 +3,8 @@ module.exports = {
env: {
node: true,
},
ignorePatterns: ["node_modules", "build", "coverage"],
plugins: ["eslint-comments", "functional"],
extends: [
"plugin:vue/essential",
"eslint:recommended",
......@@ -14,14 +16,12 @@ module.exports = {
ecmaVersion: 2020,
},
rules: {
"@typescript-eslint/explicit-module-boundary-types": "off",
"eslint-comments/disable-enable-pair": [
"error",
{ "allowWholeFile": true }
],
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
"@typescript-eslint/no-this-alias": [
"error",
{
allowDestructuring: false,
allowedNames: ["app", "me"],
},
],
},
};
{
"name": "@coscine/resourcecontentview",
"version": "1.13.0",
"version": "1.14.0",
"private": true,
"directories": {
"doc": "docs"
......@@ -11,12 +11,14 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
"@coscine/api-client": "^1.1.1",
"@coscine/api-connection": "^1.31.0",
"@coscine/app-util": "^1.9.0",
"@coscine/component-library": "^1.10.0",
"@coscine/form-generator": "^1.15.1",
"@coscine/vuex-store": "^1.2.0",
"bootstrap-vue": "^2.21.2",
"file-saver": "^2.0.5",
"jquery": "^3.5.1",
"vue": "^2.6.12",
"vue-i18n": "^8.22.0",
......@@ -32,6 +34,7 @@
"@semantic-release/gitlab": "^6.0.5",
"@semantic-release/npm": "^7.0.6",
"@semantic-release/release-notes-generator": "^9.0.1",
"@types/file-saver": "^2.0.4",
"@types/jquery": "^3.5.2",
"@types/node": "^14.14.20",
"@types/rdf-validate-shacl": "^0.2.4",
......@@ -45,6 +48,10 @@
"conventional-changelog-eslint": "3.0.9",
"core-js": "^3.8.2",
"eslint": "^6.7.2",
"eslint-import-resolver-node": "^0.3.6",
"eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-functional": "^4.0.2",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-vue": "^6.2.2",
"lint-staged": "^9.5.0",
......@@ -68,5 +75,5 @@
"url": "https://git.rwth-aachen.de/coscine/frontend/apps/resourcecontentview.git"
},
"license": "MIT",
"packageManager": "yarn@3.1.0"
"packageManager": "yarn@3.1.1"
}
......@@ -125,8 +125,6 @@ import Vue from "vue";
import FilesView from "./components/FilesView.vue";
import MetadataManager from "./components/MetadataManager.vue";
import MetadataManagerUtil from "./utils/MetadataManagerUtil";
import { LanguageUtil, GuidUtil, MergeUtil } from "@coscine/app-util";
import {
ResourceApi,
......@@ -302,10 +300,9 @@ export default Vue.extend({
(this.canEditResource = response.data.isResourceCreator)
);
ProjectRoleApi.getUserRoles(this.projectId, (response: any) => {
const app = this;
response.data.forEach((userRole: any) => {
if (userRole.role.displayName.toLowerCase() === "owner") {
app.canEditResource = true;
this.canEditResource = true;
}
});
});
......
......@@ -175,7 +175,7 @@
<script lang="ts">
import Vue, { PropType } from "vue";
import FilesViewHeader from "./FilesViewHeader.vue";
import { BlobApi, TreeApi } from "@coscine/api-connection";
import { BlobApi, TreeApi } from "@coscine/api-client";
import { LanguageUtil, GuidUtil, FileUtil } from "@coscine/app-util";
import { EntryDefinition } from "@/utils/EntryDefinition";
......@@ -186,6 +186,7 @@ import MetadataManagerUtil from "../utils/MetadataManagerUtil";
import { mapState } from "vuex";
import { BTable } from "bootstrap-vue";
import fileSaver from "file-saver";
export default Vue.extend({
name: "DataSource",
......@@ -337,12 +338,14 @@ export default Vue.extend({
}
}
},
deleteFile(file: EntryDefinition) {
async deleteFile(file: EntryDefinition) {
this.$emit("waitingForResponse", true);
BlobApi.deleteObject(this.resourceId, file.absolutepath, () => {
this.$emit("waitingForResponse", false);
location.reload();
});
await BlobApi.blobDeleteFileWithParameter(
this.resourceId,
file.absolutepath
);
this.$emit("waitingForResponse", false);
location.reload();
},
getTableHeaders() {
const headers = [
......@@ -435,7 +438,7 @@ export default Vue.extend({
}
this.$set(this, "columns", tmpColumns);
},
openDataSourceObject(rowData: EntryDefinition) {
async openDataSourceObject(rowData: EntryDefinition) {
if (rowData.isFolder === true && rowData.absolutepath.endsWith("/")) {
this.isBusy = true;
......@@ -446,67 +449,69 @@ export default Vue.extend({
// Show location change
window.location.hash = rowData.absolutepath;
TreeApi.getMetadata(
const response = await TreeApi.treeGetMetadataWithParameter(
this.resourceId,
rowData.absolutepath,
(response: any) => {
const tmpFolder = [] as EntryDefinition[];
if (rowData.name === "..") {
this.$emit("currentFolder", this.folderPath.pop() as string);
} else if (this.currentFolder !== "") {
this.folderPath.push(this.currentFolder);
this.$emit("currentFolder", rowData.absolutepath);
} else {
this.$emit("currentFolder", rowData.absolutepath);
}
rowData.absolutepath
);
if (this.folderPath.length > 0) {
const path = this.folderPath[this.folderPath.length - 1];
tmpFolder.push({
isFolder: true,
name: "..",
absolutepath: path,
path: path,
showOnly: true,
});
}
const tmpFolder = [] as EntryDefinition[];
if (rowData.name === "..") {
this.$emit("currentFolder", this.folderPath.pop() as string);
} else if (this.currentFolder !== "") {
this.folderPath.push(this.currentFolder);
this.$emit("currentFolder", rowData.absolutepath);
} else {
this.$emit("currentFolder", rowData.absolutepath);
}
for (const obj of response.data.data.fileStorage) {
const newEntry: EntryDefinition = {
isFolder: obj.IsFolder,
name: obj.Name,
path: obj.Path,
absolutepath: obj.Path,
lastModified: obj.Modified,
created: obj.Created,
size: obj.Size,
};
if (this.folderPath.length > 0) {
const path = this.folderPath[this.folderPath.length - 1];
tmpFolder.push({
isFolder: true,
name: "..",
absolutepath: path,
path: path,
showOnly: true,
});
}
const resultArray = MetadataManagerUtil.filterMetadataStorage(
response.data.data.metadataStorage,
newEntry.absolutepath
);
for (const obj of response.data.data.fileStorage) {
const newEntry: EntryDefinition = {
isFolder: obj.IsFolder,
name: obj.Name,
path: obj.Path,
absolutepath: obj.Path,
lastModified: obj.Modified,
created: obj.Created,
size: obj.Size,
};
if (resultArray.length > 0) {
const result = resultArray[0][Object.keys(resultArray[0])[0]];
const resultArray = MetadataManagerUtil.filterMetadataStorage(
response.data.data.metadataStorage,
newEntry.absolutepath
);
for (const key of Object.keys(result)) {
(newEntry as any)[key] = result[key][0].value;
}
}
if (resultArray.length > 0) {
const result = resultArray[0][Object.keys(resultArray[0])[0]];
tmpFolder.push(newEntry);
for (const key of Object.keys(result)) {
(newEntry as any)[key] = result[key][0].value;
}
this.isBusy = false;
this.$emit("folder", tmpFolder);
}
);
tmpFolder.push(newEntry);
}
this.isBusy = false;
this.$emit("folder", tmpFolder);
} else {
BlobApi.downloadObject(
const response = await BlobApi.blobGetFileWithParameter(
this.resourceId,
rowData.absolutepath,
rowData.name
{
responseType: "blob",
}
);
fileSaver.saveAs(response.data, rowData.name);
}
},
onRowSelected(items: EntryDefinition[]) {
......
......@@ -234,34 +234,46 @@
id="metadataManagerButtonRowBottomContainer"
:class="saveButtonDisabled ? 'buttondisabled' : ''"
>
<b-button
id="metadataManagerButtonRowBottomSave"
class="metadataManagerButtonRowBottomSave"
variant="primary"
@click="
isUploading
? undefined
: !showDetail
? uploadPreparation()
: update()
"
:disabled="saveButtonDisabled"
><b-spinner label="Spinning" v-show="isUploading"></b-spinner
>{{
isUploading
? $t("metadataManagerBtnSaving")
: !showDetail
? $t("metadataManagerBtnUpload")
: $t("metadataManagerBtnUpdate")
}}</b-button
>
<b-col>
<b-progress
v-if="isUploading"
class="metadataManagerButtonRowProgress"
:value="progressStatus"
max="100"
show-progress
animated
></b-progress>
</b-col>
<b-col id="metadataManagerButtonCol">
<b-button
id="metadataManagerButtonRowBottomSave"
class="metadataManagerButtonRowBottomSave"
variant="primary"
@click="
isUploading
? undefined
: !showDetail
? uploadPreparation()
: update()
"
:disabled="saveButtonDisabled"
><b-spinner label="Spinning" v-show="isUploading"></b-spinner
>{{
isUploading
? $t("metadataManagerBtnSaving")
: !showDetail
? $t("metadataManagerBtnUpload")
: $t("metadataManagerBtnUpdate")
}}</b-button
>
</b-col>
</div>
</b-row>
<b-popover
:disabled="valid || validationResults.length === 0"
over
custom-class="b-popover"
target="metadataManagerButtonRowBottomContainer"
target="metadataManagerButtonCol"
triggers="hover focus"
placement="top"
>
......@@ -382,8 +394,9 @@
<script lang="ts">
import Vue, { PropType, VNode } from "vue";
import { BlobApi, TreeApi } from "@coscine/api-connection";
import { BlobApi, TreeApi } from "@coscine/api-client";
import { BIconLink45deg } from "bootstrap-vue";
import fileSaver from "file-saver";
import { CoscineFormGroup } from "@coscine/component-library";
import "@coscine/component-library/dist/index.css";
......@@ -420,6 +433,7 @@ export default Vue.extend({
modalIsVisible: false,
fileListError: [] as EntryDefinition[],
currentlyUploadedFiles: [] as EntryDefinition[],
progressStatus: 0,
valid: false,
validationResults: [] as any[],
};
......@@ -622,6 +636,7 @@ export default Vue.extend({
},
adjustRemainingFiles(file: EntryDefinition, error = false, reload = false) {
this.numberOfCurrentlyProcessedFiles -= 1;
this.progressStatus = 0;
file.uploading = false;
if (error) {
this.fileListError.push(file);
......@@ -670,7 +685,7 @@ export default Vue.extend({
}
}
},
initRemainingFiles(numberOfFiles: any) {
initRemainingFiles(numberOfFiles: number) {
this.fileListError = [];
this.numberOfCurrentlyProcessedFiles = numberOfFiles;
this.totalNumberOfCurrentlyProcessedFiles = numberOfFiles;
......@@ -709,7 +724,7 @@ export default Vue.extend({
this.inputOk = true;
}
},
uploadPreparation() {
async uploadPreparation() {
this.uploadFileListNewFiles = [];
this.uploadFileListReplaceFiles = [];
for (const fileToUpload of this.fileListUpload) {
......@@ -723,10 +738,10 @@ export default Vue.extend({
if (this.uploadFileListReplaceFiles.length > 0) {
this.showModalSaveDuplicateFiles();
} else {
this.upload();
await this.upload();
}
},
upload() {
async upload() {
if (this.currentFileId === -1) {
this.applyMetadataTemplate(this.allFilesUpload);
}
......@@ -734,42 +749,37 @@ export default Vue.extend({
this.initRemainingFiles(this.fileListUpload.length);
for (const fileToUpload of this.fileListUpload) {
const file = fileToUpload;
this.uploadFile(file);
await this.uploadFile(file);
}
} else {
this.initRemainingFiles(this.uploadFileListNewFiles.length);
for (const fileToUpload of this.uploadFileListNewFiles) {
const file = fileToUpload;
this.uploadFile(file);
await this.uploadFile(file);
}
}
},
uploadFile(file: EntryDefinition) {
TreeApi.storeMetadataForFile(
this.resourceId,
file.path + file.name,
file.metadata,
() => {
const form = new FormData();
if (
!this.resourceContentSettings.metadataView.editableDataUrl &&
file.info
) {
form.append("files", file.info);
this.handleUploadContent(form, file);
this.adjustRemainingFiles(file);
} else if (file.dataUrl !== undefined) {
this.fillData(file);
const blob = new Blob([file.dataUrl], { type: "plain/text" });
form.append("files", blob, file.name);
this.handleUploadContent(form, file);
this.adjustRemainingFiles(file);
}
},
() => {
this.adjustRemainingFiles(file, true);
async uploadFile(file: EntryDefinition) {
this.removeEmptyValues(file);
try {
await TreeApi.treeStoreMetadataForFileWithParameter(
this.resourceId,
file.absolutepath,
{ data: file.metadata }
);
if (
!this.resourceContentSettings.metadataView.editableDataUrl &&
file.info
) {
await this.handleUploadContent(file.info, file);
} else if (file.dataUrl !== undefined) {
this.fillData(file);
const blob = new Blob([file.dataUrl], { type: "plain/text" });
await this.handleUploadContent(blob, file);
}
);
} catch {
this.adjustRemainingFiles(file, true);
}
},
fillData(file: EntryDefinition) {
if (file.dataUrl === null || file.dataUrl === undefined) {
......@@ -779,40 +789,47 @@ export default Vue.extend({
type: "text/plain",
});
},
handleUploadContent(contents: FormData, file: EntryDefinition) {
// TODO: Might replaceable by "file.absolutepath"
const absolutepath = (file.path !== "/" ? file.path : "") + file.name;
BlobApi.uploadObject(
this.resourceId,
absolutepath,
contents,
() => {
const entry = this.folder.find((x) => x.name === file.name);
if (entry === undefined) {
const newRow: EntryDefinition = {
isFolder: false,
name: file.name,
absolutepath: "/" + absolutepath,
lastModified: new Date().toString(),
created: new Date().toString(),
size: file.size,
path: file.path,
info: file.info,
};
MetadataManagerUtil.copyMetadata(file.metadata, newRow, false);
this.folder.push(newRow);
} else {
entry.lastModified = new Date().toString();
entry.created = new Date().toString();
entry.size = file.size;
MetadataManagerUtil.copyMetadata(file.metadata, entry, false);
async handleUploadContent(contents: Blob, file: EntryDefinition) {
this.progressStatus = 0;
try {
await BlobApi.blobUploadFileWithParameter(
this.resourceId,
file.absolutepath,
[contents],
{
onUploadProgress: (progressEvent: ProgressEvent) => {
this.progressStatus = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
},
}
this.adjustRemainingFiles(file);
},
() => {
this.adjustRemainingFiles(file, true);
}
);
);
} catch {
this.adjustRemainingFiles(file, true);
return;
}
const entry = this.folder.find((x) => x.name === file.name);
if (entry === undefined) {
const newRow: EntryDefinition = {
isFolder: false,
name: file.name,
absolutepath: file.absolutepath,
lastModified: new Date().toString(),
created: new Date().toString(),
size: file.size,
path: file.path,
info: file.info,
};
MetadataManagerUtil.copyMetadata(file.metadata, newRow, false);
this.folder.push(newRow);
} else {
entry.lastModified = new Date().toString();
entry.created = new Date().toString();
entry.size = file.size;
MetadataManagerUtil.copyMetadata(file.metadata, entry, false);
}
this.adjustRemainingFiles(file);
},
openURL() {
if (this.showDetail) {
......@@ -831,13 +848,20 @@ export default Vue.extend({
}
}
},
download() {
async download() {
if (this.showDetail) {
for (const editableFile of this.shownFiles) {
BlobApi.downloadObject(
const response = await BlobApi.blobGetFileWithParameter(
this.resourceId,
editableFile.absolutepath,
editableFile.name
{
responseType: "blob",
}
);
fileSaver.saveAs(response.data, editableFile.name);
window.location.hash = window.location.hash.substring(
0,
window.location.hash.lastIndexOf("/")
);
}
}
......@@ -845,7 +869,7 @@ export default Vue.extend({
selectFiles() {
this.$emit("clickFileSelect");
},
update() {
async update() {
if (this.currentFileId === -1) {
this.applyMetadataTemplate(this.allFilesEdit);
}
......@@ -853,53 +877,67 @@ export default Vue.extend({
for (const editableFile of this.fileListEdit) {
const file = editableFile as EntryDefinition;
TreeApi.storeMetadataForFile(
this.resourceId,
file.absolutepath,
file.metadata,
() => {
const tmp = this.folder.find((x) => x.name === file.name);
if (
this.resourceContentSettings.metadataView.editableDataUrl &&
file.dataUrl !== undefined
) {
this.fillData(file);
const form = new FormData();
const blob = new Blob([file.dataUrl], { type: "plain/text" });
form.append("files", blob, file.name);
this.handleUploadContent(form, file);
} else {
MetadataManagerUtil.copyMetadata(file.metadata, tmp, false);
this.adjustRemainingFiles(file);
}
},
() => {
this.adjustRemainingFiles(file, true);
this.removeEmptyValues(file);
try {
await TreeApi.treeStoreMetadataForFileWithParameter(
this.resourceId,
file.absolutepath,
{ data: file.metadata }
);
const tmp = this.folder.find((x) => x.name === file.name);
if (
this.resourceContentSettings.metadataView.editableDataUrl &&
file.dataUrl !== undefined
) {
this.fillData(file);
const blob = new Blob([file.dataUrl], { type: "plain/text" });
await this.handleUploadContent(blob, file);
} else {
MetadataManagerUtil.copyMetadata(file.metadata, tmp, false);
this.adjustRemainingFiles(file);
}
);
} catch {
this.adjustRemainingFiles(file, true);
}
}
},
deleteFiles() {
removeEmptyValues(file: EntryDefinition) {
// remove empty nodes
for (const node in file.metadata) {
const property = file.metadata[node];
if (
property.length === 0 ||
property[0].value === undefined ||
property[0].value === null ||
property[0].value.trim() === ""
) {
delete file.metadata[node];
}
}
},
async deleteFiles() {
this.initRemainingFiles(this.fileListEdit.length);
for (const fileToDelete of this.fileListEdit) {
this.deleteFile(fileToDelete);
await this.deleteFile(fileToDelete);
}
this.cleanup();
},
deleteFile(file: EntryDefinition) {
BlobApi.deleteObject(
this.resourceId,
file.absolutepath,
() => {
this.adjustRemainingFiles(file, false, true);
},
() => {
this.adjustRemainingFiles(file, true, true);
}
);
async deleteFile(file: EntryDefinition) {
let failure = false;
try {
await BlobApi.blobDeleteFileWithParameter(
this.resourceId,
file.absolutepath
);
} catch {
failure = true;
}
this.adjustRemainingFiles(file, failure, true);
},
getOptions() {
async getOptions() {
this.shownFiles = [] as EntryDefinition[];
if (this.showDetail) {
this.shownFiles = this.fileListEdit;
......@@ -916,16 +954,14 @@ export default Vue.extend({
return;
}
element.requesting = true;
BlobApi.getDownloadObject(
const response = await BlobApi.blobGetFileWithParameter(
this.resourceId,
element.absolutepath,
(response: any) => {
(response.data as Blob).text().then((text: string) => {
this.$set(element, "dataUrl", text);
element.requesting = false;
});
}
element.absolutepath
);
(response.data as Blob).text().then((text: string) => {
this.$set(element, "dataUrl", text);
element.requesting = false;
});
}
}
},
......@@ -988,9 +1024,9 @@ export default Vue.extend({
hideModalDeleteFiles() {
(this.$refs.modalDeleteFiles as any).hide();
},
deleteModalDeleteFiles() {
async deleteModalDeleteFiles() {
(this.$refs.modalDeleteFiles as any).hide();
this.deleteFiles();
await this.deleteFiles();
},
showModalSaveDuplicateFiles() {
(this.$refs.modalSaveDuplicateFiles as any).show();
......@@ -1183,12 +1219,11 @@ button span.spinner-border {
width: 100%;
}
#metadataManagerButtonRowBottom #metadataManagerButtonRowBottomContainer {
display: inline-block;
display: inline-flex;
position: absolute;
right: 0px;
width: auto;
height: 33px;
margin-right: 20px;
}
#metadataManagerButtonRowBottom
#metadataManagerButtonRowBottomContainer.buttondisabled {
......@@ -1197,6 +1232,10 @@ button span.spinner-border {
#metadataManagerButtonRowBottom .metadataManagerButtonRowBottomSave.disabled {
pointer-events: none;
}
#metadataManagerButtonRowBottom .metadataManagerButtonRowProgress {
width: 350px;
margin-top: 13px;
}
#metadataManagerButtonRowBottom .processStatement {
position: absolute;
top: 13px;
......
import { TreeApi } from "@coscine/api-connection";
import { TreeApi } from "@coscine/api-client";
import { EntryDefinition } from "./EntryDefinition";
export default {
......@@ -25,47 +25,46 @@ export default {
return false;
});
},
loadMetadata(
async loadMetadata(
callback: (response: any) => void,
fileInfo: EntryDefinition,
resourceId: string
) {
if (fileInfo !== undefined && fileInfo.absolutepath !== undefined) {
TreeApi.getMetadata(
const response = await TreeApi.treeGetMetadataWithParameter(
resourceId,
fileInfo.absolutepath,
(response: any) => {
const metadataStorage = response.data.data.metadataStorage;
fileInfo.absolutepath
);
if (metadataStorage.length === 0) {
return callback({});
}
const metadataStorage = response.data.data.metadataStorage;
const resultArray = this.filterMetadataStorage(
metadataStorage,
fileInfo.absolutepath
);
if (metadataStorage.length === 0) {
return callback({});
}
if (resultArray.length === 0) {
return callback({});
}
const resultArray = this.filterMetadataStorage(
metadataStorage,
fileInfo.absolutepath
);
const result = resultArray[0];
if (resultArray.length === 0) {
return callback({});
}
const objectKeys = Object.keys(result);
if (
response.data.data.metadataStorage !== undefined &&
objectKeys.length === 1
) {
return callback(result[objectKeys[0]]);
} else if (
response.data.data.metadataStorage !== undefined &&
objectKeys.length > 1
) {
return callback(result);
}
}
);
const result = resultArray[0];
const objectKeys = Object.keys(result);
if (
response.data.data.metadataStorage !== undefined &&
objectKeys.length === 1
) {
return callback(result[objectKeys[0]]);
} else if (
response.data.data.metadataStorage !== undefined &&
objectKeys.length > 1
) {
return callback(result);
}
} else {
return callback({});
}
......
This diff is collapsed.