Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • 2.11.0-privacyPolicy
  • APIv2
  • Docs/Setup
  • Experiment/fix-debugging
  • Experimental/Heinrichs-cypress
  • Feature/xxxx-turnOffDataPub
  • Fix/xxxx-ToS400Error
  • Fix/xxxx-migrateLogin
  • Fix/xxxx-tokenUploadButton
  • Hotfix/0038-correctDownload
  • Hotfix/1917-PublicFilesVisibility
  • Hotfix/1963-fixOrganizationField
  • Hotfix/2015-PublicFilesVisibility
  • Hotfix/2130-uiv2ContactChange
  • Hotfix/2144-invitationCall
  • Hotfix/2150-fixUpload
  • Hotfix/2160-userOrgsInst
  • Hotfix/2190-requiredFieldsForUserProfile
  • Hotfix/2196-RCVTableTranslation
  • Hotfix/2212-fixFiles
  • Hotfix/2226-userProfileSaveButton
  • Hotfix/2232-dependencyFix
  • Hotfix/2233-fixMe
  • Hotfix/2258-saveButtonWorksAsExpected
  • Hotfix/2296-selectedValuesNotReturned
  • Hotfix/2308-defaultLicense
  • Hotfix/2335-fixingSearchRCV
  • Hotfix/2353-dropShape
  • Hotfix/2370-fixDeleteButton
  • Hotfix/2378-linkedFix
  • Hotfix/2379-filesDragAndDrop
  • Hotfix/2382-guestStillBuggy
  • Hotfix/2384-guestsAndLinked
  • Hotfix/2427-adminTrouble
  • Hotfix/2459-EncodingPath
  • Hotfix/2465-orcidLink
  • Hotfix/2465-orcidLink-v1.25.1
  • Hotfix/2504-formGen
  • Hotfix/2541-resCreate
  • Hotfix/2601-correctMetadataIdentity
  • Hotfix/2611-feedback
  • Hotfix/2618-turtle
  • Hotfix/2681-validationErrors
  • Hotfix/2684-correctEncoding
  • Hotfix/2684-fixSubMetadata
  • Hotfix/2713-validateEntryName
  • Hotfix/2734-allowEmptyLicense
  • Hotfix/2765-encodingAgain
  • Hotfix/2852-adaptTextForToSUi
  • Hotfix/2853-optimizationV4
  • Hotfix/2943-reloadingResources
  • Hotfix/2943-searchHighlighting
  • Hotfix/2957-styleAndUpgrade
  • Hotfix/2971-fixTextInDataPub
  • Hotfix/2989-cookieLength
  • Hotfix/662-keepSidebarExpanded
  • Hotfix/xxxx-correctLinking
  • Hotfix/xxxx-folderRecursive
  • Hotfix/xxxx-fullscreenCss
  • Hotfix/xxxx-homepageDisplay
  • Hotfix/xxxx-liveReleaseFixes
  • Hotfix/xxxx-partnerProjects
  • Hotfix/xxxx-workingFileIndex
  • Issue/1782-structualDataIntegration
  • Issue/1792-newMetadataStructure
  • Issue/1822-coscineUIv2App
  • Issue/1824-componentsUIv2
  • Issue/1824-routerAdditions
  • Issue/1825-codeQualityPipelines
  • Issue/1833-newLogin
  • Issue/1843-multipleFilesValidation
  • Issue/1860-searchScoping
  • Issue/1861-searchMetadata
  • Issue/1862-searchFacets
  • Issue/1863-paginationForSearch
  • Issue/1926-userProfile
  • Issue/1927-projectAppMigration
  • Issue/1928-sidebarmenuAddition
  • Issue/1929-vuexToPinia
  • Issue/1938-internalHandling
  • Issue/1951-quotaImplementation
  • Issue/1953-owlImports
  • Issue/1957-resourceAppMigration
  • Issue/1957-resourceAppMigrationNew
  • Issue/1962-SearchAppUI2
  • Issue/1964-tokenExpiryUIv2
  • Issue/1965-userListMigration
  • Issue/1970-breadcrumbs
  • Issue/1971-projectEditCreateMigration
  • Issue/1972-homeDepot
  • Issue/1974-shibbolethLogout
  • Issue/1976-resouceCreationVaildEmail
  • Issue/1979-supportAdminUIv2Migration
  • Issue/1980-userManagement
  • Issue/1985-adaptSidebar
  • Issue/2002-migrateResourceCreate
  • Issue/2003-resourceSettings
  • Issue/2008-quotaManagement
  • Issue/2011-pathConfig
  • Issue/2016-BannerMigration
  • 1.28.0-pilot
  • v1.0.0
  • v1.1.0
  • v1.10.0
  • v1.10.1
  • v1.10.2
  • v1.10.3
  • v1.11.0
  • v1.11.1
  • v1.11.2
  • v1.11.3
  • v1.11.4
  • v1.11.5
  • v1.11.6
  • v1.11.7
  • v1.12.0
  • v1.13.0
  • v1.14.0
  • v1.14.1
  • v1.14.2
  • v1.14.3
  • v1.15.0
  • v1.15.1
  • v1.16.0
  • v1.16.1
  • v1.16.2
  • v1.16.3
  • v1.17.0
  • v1.17.1
  • v1.17.2
  • v1.18.0
  • v1.18.1
  • v1.19.0
  • v1.2.0
  • v1.20.0
  • v1.20.1
  • v1.20.2
  • v1.20.3
  • v1.20.4
  • v1.20.5
  • v1.21.0
  • v1.22.0
  • v1.22.1
  • v1.22.2
  • v1.23.0
  • v1.23.1
  • v1.23.2
  • v1.23.3
  • v1.23.4
  • v1.23.5
  • v1.23.6
  • v1.23.6-patch-2417-2427
  • v1.24.0
  • v1.24.1
  • v1.25.0
  • v1.25.1
  • v1.26.0
  • v1.26.1
  • v1.27.0
  • v1.27.1
  • v1.27.1-pilot
  • v1.28.0
  • v1.29.0
  • v1.29.1
  • v1.29.2
  • v1.3.0
  • v1.30.0
  • v1.30.1
  • v1.30.2
  • v1.31.0
  • v1.32.0
  • v1.4.0
  • v1.4.1
  • v1.5.0
  • v1.6.0
  • v1.6.1
  • v1.6.2
  • v1.7.0
  • v1.8.0
  • v1.8.1
  • v1.8.2
  • v1.9.0
  • v2.0.0
  • v2.1.0
  • v2.10.0
  • v2.10.1
  • v2.11.0
  • v2.12.0
  • v2.12.1
  • v2.12.2
  • v2.12.3
  • v2.12.4
  • v2.12.5
  • v2.13.0
  • v2.13.1
  • v2.13.2
  • v2.13.3
  • v2.13.4
  • v2.14.0
  • v2.15.0
200 results

Target

Select target project
  • coscine/frontend/apps/ui
1 result
Select Git revision
  • 2.11.0-privacyPolicy
  • APIv2
  • Docs/Setup
  • Experiment/fix-debugging
  • Experimental/Heinrichs-cypress
  • Feature/xxxx-turnOffDataPub
  • Fix/xxxx-ToS400Error
  • Fix/xxxx-migrateLogin
  • Fix/xxxx-tokenUploadButton
  • Hotfix/0038-correctDownload
  • Hotfix/1917-PublicFilesVisibility
  • Hotfix/1963-fixOrganizationField
  • Hotfix/2015-PublicFilesVisibility
  • Hotfix/2130-uiv2ContactChange
  • Hotfix/2144-invitationCall
  • Hotfix/2150-fixUpload
  • Hotfix/2160-userOrgsInst
  • Hotfix/2190-requiredFieldsForUserProfile
  • Hotfix/2196-RCVTableTranslation
  • Hotfix/2212-fixFiles
  • Hotfix/2226-userProfileSaveButton
  • Hotfix/2232-dependencyFix
  • Hotfix/2233-fixMe
  • Hotfix/2258-saveButtonWorksAsExpected
  • Hotfix/2296-selectedValuesNotReturned
  • Hotfix/2308-defaultLicense
  • Hotfix/2335-fixingSearchRCV
  • Hotfix/2353-dropShape
  • Hotfix/2370-fixDeleteButton
  • Hotfix/2378-linkedFix
  • Hotfix/2379-filesDragAndDrop
  • Hotfix/2382-guestStillBuggy
  • Hotfix/2384-guestsAndLinked
  • Hotfix/2427-adminTrouble
  • Hotfix/2459-EncodingPath
  • Hotfix/2465-orcidLink
  • Hotfix/2465-orcidLink-v1.25.1
  • Hotfix/2504-formGen
  • Hotfix/2541-resCreate
  • Hotfix/2601-correctMetadataIdentity
  • Hotfix/2611-feedback
  • Hotfix/2618-turtle
  • Hotfix/2681-validationErrors
  • Hotfix/2684-correctEncoding
  • Hotfix/2684-fixSubMetadata
  • Hotfix/2713-validateEntryName
  • Hotfix/2734-allowEmptyLicense
  • Hotfix/2765-encodingAgain
  • Hotfix/2852-adaptTextForToSUi
  • Hotfix/2853-optimizationV4
  • Hotfix/2943-reloadingResources
  • Hotfix/2943-searchHighlighting
  • Hotfix/2957-styleAndUpgrade
  • Hotfix/2971-fixTextInDataPub
  • Hotfix/2989-cookieLength
  • Hotfix/662-keepSidebarExpanded
  • Hotfix/xxxx-correctLinking
  • Hotfix/xxxx-folderRecursive
  • Hotfix/xxxx-fullscreenCss
  • Hotfix/xxxx-homepageDisplay
  • Hotfix/xxxx-liveReleaseFixes
  • Hotfix/xxxx-partnerProjects
  • Hotfix/xxxx-workingFileIndex
  • Issue/1782-structualDataIntegration
  • Issue/1792-newMetadataStructure
  • Issue/1822-coscineUIv2App
  • Issue/1824-componentsUIv2
  • Issue/1824-routerAdditions
  • Issue/1825-codeQualityPipelines
  • Issue/1833-newLogin
  • Issue/1843-multipleFilesValidation
  • Issue/1860-searchScoping
  • Issue/1861-searchMetadata
  • Issue/1862-searchFacets
  • Issue/1863-paginationForSearch
  • Issue/1926-userProfile
  • Issue/1927-projectAppMigration
  • Issue/1928-sidebarmenuAddition
  • Issue/1929-vuexToPinia
  • Issue/1938-internalHandling
  • Issue/1951-quotaImplementation
  • Issue/1953-owlImports
  • Issue/1957-resourceAppMigration
  • Issue/1957-resourceAppMigrationNew
  • Issue/1962-SearchAppUI2
  • Issue/1964-tokenExpiryUIv2
  • Issue/1965-userListMigration
  • Issue/1970-breadcrumbs
  • Issue/1971-projectEditCreateMigration
  • Issue/1972-homeDepot
  • Issue/1974-shibbolethLogout
  • Issue/1976-resouceCreationVaildEmail
  • Issue/1979-supportAdminUIv2Migration
  • Issue/1980-userManagement
  • Issue/1985-adaptSidebar
  • Issue/2002-migrateResourceCreate
  • Issue/2003-resourceSettings
  • Issue/2008-quotaManagement
  • Issue/2011-pathConfig
  • Issue/2016-BannerMigration
  • 1.28.0-pilot
  • v1.0.0
  • v1.1.0
  • v1.10.0
  • v1.10.1
  • v1.10.2
  • v1.10.3
  • v1.11.0
  • v1.11.1
  • v1.11.2
  • v1.11.3
  • v1.11.4
  • v1.11.5
  • v1.11.6
  • v1.11.7
  • v1.12.0
  • v1.13.0
  • v1.14.0
  • v1.14.1
  • v1.14.2
  • v1.14.3
  • v1.15.0
  • v1.15.1
  • v1.16.0
  • v1.16.1
  • v1.16.2
  • v1.16.3
  • v1.17.0
  • v1.17.1
  • v1.17.2
  • v1.18.0
  • v1.18.1
  • v1.19.0
  • v1.2.0
  • v1.20.0
  • v1.20.1
  • v1.20.2
  • v1.20.3
  • v1.20.4
  • v1.20.5
  • v1.21.0
  • v1.22.0
  • v1.22.1
  • v1.22.2
  • v1.23.0
  • v1.23.1
  • v1.23.2
  • v1.23.3
  • v1.23.4
  • v1.23.5
  • v1.23.6
  • v1.23.6-patch-2417-2427
  • v1.24.0
  • v1.24.1
  • v1.25.0
  • v1.25.1
  • v1.26.0
  • v1.26.1
  • v1.27.0
  • v1.27.1
  • v1.27.1-pilot
  • v1.28.0
  • v1.29.0
  • v1.29.1
  • v1.29.2
  • v1.3.0
  • v1.30.0
  • v1.30.1
  • v1.30.2
  • v1.31.0
  • v1.32.0
  • v1.4.0
  • v1.4.1
  • v1.5.0
  • v1.6.0
  • v1.6.1
  • v1.6.2
  • v1.7.0
  • v1.8.0
  • v1.8.1
  • v1.8.2
  • v1.9.0
  • v2.0.0
  • v2.1.0
  • v2.10.0
  • v2.10.1
  • v2.11.0
  • v2.12.0
  • v2.12.1
  • v2.12.2
  • v2.12.3
  • v2.12.4
  • v2.12.5
  • v2.13.0
  • v2.13.1
  • v2.13.2
  • v2.13.3
  • v2.13.4
  • v2.14.0
  • v2.15.0
200 results
Show changes
Showing
with 453 additions and 412 deletions
......@@ -6,7 +6,21 @@
label-for="ApplicationProfiles"
:mandatory="true"
:label="$t('page.createResource.metadata.applicationProfileLabel')"
:info="true"
>
<template #popover>
{{ $t("page.createResource.metadata.applicationProfilePopover") }}
<b-link
:href="
$t(
'page.createResource.metadata.applicationProfilePopoverUrl'
).toString()
"
target="_blank"
>{{ $t("default.help") }}
</b-link>
</template>
<div class="d-flex align-items-center">
<b-form-select
id="ApplicationProfiles"
......@@ -73,11 +87,13 @@
:key="resource.applicationProfile"
:fixed-value-mode="fixedValueMode"
:fixed-values="resource.fixedValues"
:application-profile-id="resource.applicationProfile"
:s-h-a-c-l-definition="applicationProfileString"
:locale="$root.$i18n.locale"
:selected-shape="resource.applicationProfile"
:shapes="applicationProfile"
:class-receiver="receiveClass"
:user-receiver="async () => user"
mime-type="application/ld+json"
shape-mime-type="application/ld+json"
@inputFixedValues="inputFixedValues"
/>
</div>
</template>
......@@ -89,6 +105,7 @@ import useResourceStore from "../../store";
import useUserStore from "@/modules/user/store";
import "@/plugins/form-generator";
import CreateAPModal from "./modals/CreateAPModal.vue";
import type { Dataset } from "rdf-js";
import type { ResourceObject } from "@coscine/api-client/dist/types/Coscine.Api.Resources";
import type { GroupedApplicationProfiles } from "../../types";
import type { BilingualLabels } from "@coscine/api-client/dist/types/Coscine.Api.Metadata";
......@@ -107,8 +124,8 @@ export default defineComponent({
type: Array as PropType<GroupedApplicationProfiles[]>,
required: true,
},
applicationProfileString: {
type: [String, null] as PropType<string | null>,
applicationProfile: {
type: [Object, null] as PropType<Dataset | null>,
default: null,
},
isLoadingFormGenerator: {
......@@ -179,13 +196,14 @@ export default defineComponent({
},
methods: {
inputFixedValues(fixedValues: Record<string, unknown>) {
this.resource.fixedValues = fixedValues;
this.$emit("input", this.resource);
},
async receiveClass(className: string): Promise<BilingualLabels> {
return await this.resourceStore.getClass(className);
},
notifyFormGenerator() {
if (!this.resource.fixedValues) {
this.$set(this.resource, "fixedValues", {});
}
this.$set(
this.resource,
"applicationProfile",
......
......@@ -45,15 +45,15 @@
<FormGenerator
v-else
:key="resource.applicationProfile"
:project-id="project.id"
:fixed-value-mode="fixedValueMode"
:fixed-values="resource.fixedValues"
:application-profile-id="resource.applicationProfile"
:locale="$root.$i18n.locale"
:disabled-mode="true"
:s-h-a-c-l-definition="applicationProfileString"
:selected-shape="resource.applicationProfile"
:shapes="applicationProfile"
:class-receiver="receiveClass"
:user-receiver="async () => user"
mime-type="application/ld+json"
shape-mime-type="application/ld+json"
/>
</b-card>
......@@ -85,6 +85,7 @@ import useUserStore from "@/modules/user/store";
import "@/plugins/form-generator";
import General from "./General.vue";
import { navigateToProject } from "@/router";
import type { Dataset } from "rdf-js";
import type { ResourceObject } from "@coscine/api-client/dist/types/Coscine.Api.Resources";
import type { ResourceCreationTab, ResourceTypeOption } from "../../types";
import type { ProjectObject } from "@coscine/api-client/dist/types/Coscine.Api.Project";
......@@ -103,8 +104,8 @@ export default defineComponent({
type: Array as PropType<ResourceCreationTab[]>,
required: true,
},
applicationProfileString: {
type: [String, null] as PropType<string | null>,
applicationProfile: {
type: [Object, null] as PropType<Dataset | null>,
default: null,
},
isLoadingFormGenerator: {
......
......@@ -152,15 +152,14 @@ import MetadataManagerUtil from "../../utils/MetadataManagerUtil";
import fileSaver from "file-saver";
import type { ResourceTypeInformation } from "@coscine/api-client/dist/types/Coscine.Api.Resources";
import type {
ApplicationProfile,
Metadata,
VisitedResourceObject,
} from "../../types";
import type { VisitedResourceObject } from "../../types";
import type { BFormRow, BTable, BvTableField } from "bootstrap-vue";
import { FileUtil } from "../../utils/FileUtil";
import { v4 as uuidv4 } from "uuid";
import { parseRDFDefinition } from "../../utils/linkedData";
import factory from "rdf-ext";
import type { Dataset, Literal, Quad } from "rdf-js";
interface CustomTableField extends BvTableField {
key: string;
......@@ -229,7 +228,7 @@ export default defineComponent({
},
computed: {
applicationProfile(): ApplicationProfile | null {
applicationProfile(): Dataset | null {
return this.resourceStore.currentFullApplicationProfile;
},
resource(): null | VisitedResourceObject {
......@@ -318,76 +317,55 @@ export default defineComponent({
getColumns() {
this.columns.length = 0;
// keys a, b, name, active
if (
this.applicationProfile &&
this.applicationProfile.length > 0 &&
this.applicationProfile[0]["@graph"]
) {
const entries = this.applicationProfile[0]["@graph"] as Metadata[];
if (this.applicationProfile) {
const oldColumns = this.loadFromLocalStorage();
for (const entry of entries) {
if (
typeof entry !== "string" &&
Object.prototype.hasOwnProperty.call(
entry,
"http://www.w3.org/ns/shacl#name"
)
) {
const paths = this.applicationProfile.match(
undefined,
factory.namedNode("http://www.w3.org/ns/shacl#path")
);
for (const path of paths) {
// read the active value from the localStorage
const identifier = path.object.value;
const names = this.applicationProfile.match(
path.subject,
factory.namedNode("http://www.w3.org/ns/shacl#name")
);
let name: string | null = null;
for (let nameEntry of names) {
name = nameEntry.object.value;
if (
Object.prototype.hasOwnProperty.call(
entry,
"https://purl.org/coscine/invisible"
) &&
typeof entry["https://purl.org/coscine/invisible"][0] !==
"string" &&
!Array.isArray(entry["https://purl.org/coscine/invisible"][0]) &&
entry["https://purl.org/coscine/invisible"][0]["@value"] === "1"
(nameEntry.object as Literal).language === this.$root.$i18n.locale
) {
continue;
break;
}
for (const name of entry[
"http://www.w3.org/ns/shacl#name"
] as Metadata[]) {
if (
typeof name !== "string" &&
name["@language"] === this.$i18n.locale &&
typeof entry["http://www.w3.org/ns/shacl#path"][0] !==
"string" &&
!Array.isArray(entry["http://www.w3.org/ns/shacl#path"][0])
) {
const identifier =
entry["http://www.w3.org/ns/shacl#path"][0]["@id"];
// read the active value from the localStorage
let activeColumn = false;
for (const oldColumn of oldColumns) {
if (oldColumn.key === identifier) {
activeColumn = oldColumn.active;
}
}
this.columns.push(
reactive({
label: name["@value"] as string,
key: identifier as string,
sortable: true,
active: activeColumn,
formatter: (value, key, item: FolderContent) => {
const metadata = item.metadata;
if (metadata && metadata[key]) {
const formatEntry = metadata[key][0];
if (
typeof formatEntry !== "string" &&
!Array.isArray(formatEntry)
) {
return formatEntry["value"];
}
}
return "";
},
})
);
}
}
let activeColumn = false;
for (const oldColumn of oldColumns) {
if (oldColumn.key === identifier) {
activeColumn = oldColumn.active;
}
}
if (name) {
this.columns.push(
reactive({
label: name,
key: identifier,
sortable: true,
active: activeColumn,
formatter: (value, key, item: FolderContent) => {
const metadata = item.metadata;
const entries = metadata.match(undefined, path.object);
if (entries.size) {
return Array.from(entries)
.map((entry: Quad) => entry.object.value)
.join("<br>");
}
return "";
},
})
);
}
}
}
},
......@@ -404,7 +382,7 @@ export default defineComponent({
absolutePath: currentFolder,
version: `${+new Date()}`,
size: 0,
metadata: {},
metadata: factory.dataset() as unknown as Dataset,
});
currentFolder = currentFolder.substring(
0,
......@@ -439,7 +417,7 @@ export default defineComponent({
name: currentFolder.substring(currentFolder.lastIndexOf("/") + 1),
path: currentFolder,
absolutePath: currentFolder,
metadata: {},
metadata: factory.dataset() as unknown as Dataset,
});
},
saveInLocalStorage() {
......@@ -508,7 +486,7 @@ export default defineComponent({
absolutePath: path,
path: path,
readOnly: true,
metadata: {},
metadata: factory.dataset() as unknown as Dataset,
};
tmpFolder.push(navigateUpFolder);
}
......@@ -524,7 +502,7 @@ export default defineComponent({
created: obj.Created,
size: obj.Size,
version: `${+new Date()}`,
metadata: {},
metadata: factory.dataset() as unknown as Dataset,
};
const resultArray = MetadataManagerUtil.filterMetadataStorage(
......@@ -534,7 +512,9 @@ export default defineComponent({
if (resultArray.length > 0) {
const result = resultArray[0][Object.keys(resultArray[0])[0]];
newEntry.metadata = result as Metadata;
if (typeof result === "string") {
newEntry.metadata = await parseRDFDefinition(result);
}
}
tmpFolder.push(newEntry);
......
......@@ -49,20 +49,23 @@
:resource="resource"
/>
<span
v-if="resource && resource.applicationProfile !== ''"
v-if="
resource && resource.applicationProfile !== '' && applicationProfile
"
class="generatedFormSpan"
>
<FormGenerator
:key="formGeneratorKey"
class="generatedForm"
:application-profile-id="resource.applicationProfile"
:fixed-values="resource.fixedValues"
:form-data="currentMetadata"
:s-h-a-c-l-definition="applicationProfileString"
:locale="$root.$i18n.locale"
:selected-shape="resource.applicationProfile"
:shapes="applicationProfile"
:disabled-mode="resource.archived"
:class-receiver="receiveClass"
:user-receiver="async () => user"
mime-type="application/ld+json"
@input="inputMetadata"
@isValid="isValid"
/>
</span>
......@@ -127,7 +130,6 @@ import type {
} from "../../utils/EntryDefinition";
import "@/plugins/form-generator";
import { cloneDeep } from "lodash";
import type {
ResourceObject,
......@@ -135,12 +137,13 @@ import type {
} from "@coscine/api-client/dist/types/Coscine.Api.Resources";
import type { UserObject } from "@coscine/api-client/dist/types/Coscine.Api.User";
import type { BilingualLabels } from "@coscine/api-client/dist/types/Coscine.Api.Metadata";
import type { Dataset } from "rdf-js";
import factory from "rdf-ext";
import type ValidationReport from "rdf-validate-shacl/src/validation-report";
import { v4 as uuidv4 } from "uuid";
import type { ValidationResult } from "rdf-validate-shacl/src/validation-report";
import type { ApplicationProfile, Metadata } from "../../types";
import useNotificationStore from "@/store/notification";
export default defineComponent({
......@@ -214,15 +217,9 @@ export default defineComponent({
};
},
computed: {
applicationProfile(): ApplicationProfile | null {
applicationProfile(): Dataset | null {
return this.resourceStore.currentFullApplicationProfile;
},
applicationProfileString(): string | null {
if (this.applicationProfile) {
return JSON.stringify(this.applicationProfile);
}
return null;
},
resource(): null | ResourceObject {
return this.resourceStore.currentResource;
},
......@@ -257,7 +254,7 @@ export default defineComponent({
currentFolderContent(): FolderContent | undefined {
return this.shownFiles[this.currentFileId];
},
currentMetadata(): Metadata {
currentMetadata(): Dataset {
if (
this.currentFileId >= 0 &&
this.currentFileId < this.shownFiles.length
......@@ -268,7 +265,7 @@ export default defineComponent({
) {
return this.currentFolderContent.metadata;
} else {
return {};
return factory.dataset() as unknown as Dataset;
}
} else {
return this.metadataTemplate;
......@@ -285,15 +282,21 @@ export default defineComponent({
: "")
);
},
metadataTemplate(): Metadata {
metadataTemplate(): Dataset {
if (this.resource && this.resource.applicationProfile) {
return {
"http://www.w3.org/1999/02/22-rdf-syntax-ns#type": [
{ type: "uri", value: this.resource.applicationProfile },
],
};
const dataset = factory.dataset();
dataset.add(
factory.quad(
factory.blankNode(),
factory.namedNode(
"http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
),
factory.namedNode(this.resource.applicationProfile)
)
);
return dataset as unknown as Dataset;
} else {
return {};
return factory.dataset() as unknown as Dataset;
}
},
saveButtonDisabled(): boolean {
......@@ -332,29 +335,11 @@ export default defineComponent({
return await this.resourceStore.getClass(className);
},
applyMetadataTemplate() {
const keyList = Object.keys(this.metadataTemplate);
if (keyList.length > 0) {
for (const key of keyList) {
const entry = this.metadataTemplate[key][0];
if (
typeof entry !== "string" &&
!Array.isArray(entry) &&
entry["value"] !== ""
) {
for (const currentFile of this.shownFiles) {
const innerKeyList = Object.keys(entry);
const file = currentFile;
if (file.metadata === undefined) {
file.metadata = {};
}
const entryList: Metadata[] = [];
entryList.push({});
for (const innerKey of innerKeyList) {
entryList[0][innerKey] = entry[innerKey];
}
file.metadata[key] = entryList;
}
}
if (this.metadataTemplate.size > 0) {
for (const currentFile of this.shownFiles) {
currentFile.metadata = factory.dataset(
Array.from(this.metadataTemplate)
) as unknown as Dataset;
}
}
},
......@@ -455,6 +440,11 @@ export default defineComponent({
this.$emit("isUploading", true);
}
},
inputMetadata(metadata: Dataset) {
if (this.currentFolderContent) {
this.currentFolderContent.metadata = metadata;
}
},
async uploadPreparation() {
this.uploadFileListNewFiles = [];
this.uploadFileListReplaceFiles = [];
......@@ -494,7 +484,7 @@ export default defineComponent({
const result = await this.resourceStore.storeMetadata(
this.resource,
file.absolutePath,
this.removeEmptyValues(file.metadata)
file.metadata
);
if (result) {
if (!this.editableDataUrl && file.info) {
......@@ -546,10 +536,10 @@ export default defineComponent({
size: file.size,
path: file.path,
info: file.info,
metadata: {},
metadata: factory.dataset() as unknown as Dataset,
version: `${+new Date()}`,
};
MetadataManagerUtil.copyMetadata(file.metadata, newRow, false);
MetadataManagerUtil.copyMetadata(file.metadata, newRow);
this.$emit("folderContents", [...this.folderContents, newRow]);
} else {
entry.lastModified = new Date().toString();
......@@ -557,7 +547,7 @@ export default defineComponent({
if (!entry.isFolder) {
entry.size = file.size;
}
MetadataManagerUtil.copyMetadata(file.metadata, entry, false);
MetadataManagerUtil.copyMetadata(file.metadata, entry);
}
this.adjustRemainingFiles(file);
},
......@@ -588,7 +578,7 @@ export default defineComponent({
const result = await this.resourceStore.storeMetadata(
this.resource,
editableFile.absolutePath,
this.removeEmptyValues(editableFile.metadata)
editableFile.metadata
);
if (result) {
const tmp = this.folderContents.find(
......@@ -605,7 +595,7 @@ export default defineComponent({
});
await this.handleUploadContent(blob, editableFile);
} else {
MetadataManagerUtil.copyMetadata(editableFile.metadata, tmp, false);
MetadataManagerUtil.copyMetadata(editableFile.metadata, tmp);
this.adjustRemainingFiles(editableFile);
}
} else {
......@@ -613,25 +603,6 @@ export default defineComponent({
}
}
},
removeEmptyValues(metadata: Metadata) {
const refinedMetadata = cloneDeep(metadata);
// remove empty nodes
for (const node in refinedMetadata) {
const property = refinedMetadata[node];
if (
property.length === 0 ||
typeof property[0] === "string" ||
Array.isArray(property[0]) ||
property[0]["value"] === undefined ||
property[0]["value"] === null ||
(typeof property[0]["value"] === "string" &&
property[0]["value"].trim() === "")
) {
delete refinedMetadata[node];
}
}
return refinedMetadata;
},
async deleteFolderContents() {
this.initRemainingFiles(this.fileListEdit.length);
......
......@@ -5,16 +5,18 @@
<b-spinner variant="primary" />
</b-row>
<FormGenerator
v-else
v-else-if="resource"
:key="resourceForm.applicationProfile"
:disabled-mode="!isOwner || resource.archived"
:fixed-value-mode="true"
:fixed-values="resourceForm.fixedValues"
:application-profile-id="resourceForm.applicationProfile"
:s-h-a-c-l-definition="applicationProfileString"
:locale="$root.$i18n.locale"
:selected-shape="resource.applicationProfile"
:shapes="applicationProfile"
:class-receiver="receiveClass"
:user-receiver="async () => user"
mime-type="application/ld+json"
shape-mime-type="application/ld+json"
@inputFixedValues="inputFixedValues"
/>
</div>
</template>
......@@ -26,6 +28,7 @@ import useResourceStore from "../../store";
import useProjectStore from "@/modules/project/store";
import useUserStore from "@/modules/user/store";
import "@/plugins/form-generator";
import type { Dataset } from "rdf-js";
import type { ResourceObject } from "@coscine/api-client/dist/types/Coscine.Api.Resources";
import type { BilingualLabels } from "@coscine/api-client/dist/types/Coscine.Api.Metadata";
import type { UserObject } from "@coscine/api-client/dist/types/Coscine.Api.User";
......@@ -36,8 +39,8 @@ export default defineComponent({
type: Object as PropType<ResourceObject>,
required: true,
},
applicationProfileString: {
type: [String, null] as PropType<string | null>,
applicationProfile: {
type: [Object, null] as PropType<Dataset | null>,
default: null,
},
isLoadingFormGenerator: {
......@@ -86,6 +89,10 @@ export default defineComponent({
},
methods: {
inputFixedValues(fixedValues: Record<string, unknown>) {
this.resourceForm.fixedValues = fixedValues;
this.$emit("input", this.resourceForm);
},
async receiveClass(className: string): Promise<BilingualLabels> {
return await this.resourceStore.getClass(className);
},
......
......@@ -16,6 +16,10 @@ export default {
labels: {
resourceType: "Ressourcentyp:",
size: "Ressourcengröße:",
resourceTypePopover:
"Für weitere Informationen zu Ressourcentypen siehe",
resourceTypePopoverUrl:
"https://help.itc.rwth-aachen.de/service/b2b7729fd93f4c7080b475776f6b5d87/article/7812e3fcd3a241808f5b91e11b6ca3a9/",
},
popover: {
title: "Quota nicht ausreichend",
......@@ -41,6 +45,10 @@ export default {
},
applicationProfileLabel: "Applikationsprofil:",
selectApplicationProfile: "Bitte wählen sie ein Applikationsprofil aus",
applicationProfilePopover:
"Für weitere Informationen zu Applikationsprofilen siehe",
applicationProfilePopoverUrl:
"https://help.itc.rwth-aachen.de/service/b2b7729fd93f4c7080b475776f6b5d87/article/7812e3fcd3a241808f5b91e11b6ca3a9/",
},
overview: {
title: "Schritt 4: @:(form.steps.fourth)",
......@@ -370,6 +378,10 @@ export default {
resourceMetadataVisibility: "Sichtbarkeit der Metadaten",
resourceMetadataVisibilityLabel:
"@:(form.resource.resourceMetadataVisibility)@:(form.labelSymbol)",
resourceMetadataPopover:
"Für weitere Informationen zur Sichtbarkeit siehe",
resourceMetadataPopoverUrl:
"https://help.itc.rwth-aachen.de/service/b2b7729fd93f4c7080b475776f6b5d87/article/7812e3fcd3a241808f5b91e11b6ca3a9/",
resourceLicense: "Lizenz",
resourceLicenseLabel:
......
......@@ -13,9 +13,13 @@ export default {
title: "Step 1: @:(form.steps.first)",
needMore: "Need more?",
bucketSize: "{size} GB, this equals approximately {files} files.",
labels: {
resourceType: "Resource Type:",
size: "Resource Size:",
resourceTypePopover: "For more information on resource types see",
resourceTypePopoverUrl:
"https://help.itc.rwth-aachen.de/en/service/b2b7729fd93f4c7080b475776f6b5d87/article/7812e3fcd3a241808f5b91e11b6ca3a9/",
},
popover: {
title: "Quota not sufficient",
......@@ -41,6 +45,10 @@ export default {
},
applicationProfileLabel: "Application Profile:",
selectApplicationProfile: "Please select an application profile",
applicationProfilePopover:
"For more information on application profiles see",
applicationProfilePopoverUrl:
"https://help.itc.rwth-aachen.de/en/service/b2b7729fd93f4c7080b475776f6b5d87/article/7812e3fcd3a241808f5b91e11b6ca3a9/",
},
overview: {
title: "Step 4: @:(form.steps.fourth)",
......@@ -361,6 +369,9 @@ export default {
resourceMetadataVisibility: "Metadata Visibility",
resourceMetadataVisibilityLabel:
"@:(form.resource.resourceMetadataVisibility)@:(form.labelSymbol)",
resourceMetadataPopover: "For more information on visibility see",
resourceMetadataPopoverUrl:
"https://help.itc.rwth-aachen.de/en/service/b2b7729fd93f4c7080b475776f6b5d87/article/7812e3fcd3a241808f5b91e11b6ca3a9/",
resourceLicense: "License",
resourceLicenseLabel:
......
......@@ -35,7 +35,7 @@
v-else-if="currentTab === 2"
v-model="resource"
:application-profile-list="groupedAPList"
:application-profile-string="applicationProfileString"
:application-profile="applicationProfile"
:is-loading-form-generator="isLoadingFormGenerator"
@valid="setNextTab"
/>
......@@ -43,7 +43,7 @@
v-else-if="currentTab === 3"
v-model="resource"
:tabs="tabs"
:application-profile-string="applicationProfileString"
:application-profile="applicationProfile"
:is-loading-form-generator="isLoadingFormGenerator"
@back="back"
@toTab="toTab"
......@@ -89,6 +89,7 @@ import type {
VisibilityObject,
} from "@coscine/api-client/dist/types/Coscine.Api.Resources";
import type { ProjectObject } from "@coscine/api-client/dist/types/Coscine.Api.Project";
import type { Dataset } from "rdf-js";
import type {
GroupedApplicationProfiles,
ResourceCreationTab,
......@@ -111,6 +112,7 @@ export default defineComponent({
resource: {
description: "",
displayName: "",
fixedValues: {},
resourceName: "",
keywords: "",
license: "",
......@@ -120,7 +122,7 @@ export default defineComponent({
resourceTypeOption: {} as ResourceTypeOption,
} as ResourceObject,
groupedAPList: [] as GroupedApplicationProfiles[],
applicationProfileString: null as string | null,
applicationProfile: null as Dataset | null,
isLoadingFormGenerator: false,
isWaitingForResponse: false,
isLoading: false,
......@@ -253,10 +255,10 @@ export default defineComponent({
await this.resourceStore.getApplicationProfile(
this.resource.applicationProfile
);
this.applicationProfileString = JSON.stringify(applicationProfile);
this.applicationProfile = applicationProfile;
this.isLoadingFormGenerator = false;
} else {
this.applicationProfileString = null;
this.applicationProfile = null;
}
},
back() {
......
......@@ -27,7 +27,7 @@
v-show="tabs[currentTab].step === 'general'"
v-model="resourceForm"
:is-loading="isLoading"
:readonly="false"
:readonly="resource && resource.archived"
@validation="validation = $event"
/>
......@@ -35,7 +35,7 @@
<Metadata
v-show="tabs[currentTab].step === 'metadata'"
v-model="resourceForm"
:application-profile-string="applicationProfileString"
:application-profile="applicationProfile"
:is-loading-form-generator="isLoading"
@clickSave="clickSave"
/>
......@@ -83,10 +83,11 @@ import type {
ResourceObject,
VisibilityObject,
} from "@coscine/api-client/dist/types/Coscine.Api.Resources";
import type { Dataset } from "rdf-js";
import type { ResourceCreationTab, ResourceTypeOption } from "../types";
import type { ProjectObject } from "@coscine/api-client/dist/types/Coscine.Api.Project";
import type { Validation } from "vuelidate";
import useNotificationStore from "@/store/notification";
import type { Validation } from "@vuelidate/core";
export default defineComponent({
components: {
......@@ -118,7 +119,7 @@ export default defineComponent({
visibility: {} as VisibilityObject,
resourceTypeOption: {} as ResourceTypeOption,
} as ResourceObject,
applicationProfileString: null as string | null,
applicationProfile: null as Dataset | null,
validation: {} as Validation,
isWaitingForResponse: false,
isLoading: false,
......@@ -233,10 +234,10 @@ export default defineComponent({
await this.resourceStore.getApplicationProfile(
this.resource.applicationProfile
);
this.applicationProfileString = JSON.stringify(applicationProfile);
this.applicationProfile = applicationProfile;
this.isLoading = false;
} else {
this.applicationProfileString = null;
this.applicationProfile = null;
}
},
......
import { defineStore } from "pinia";
import type {
ApplicationProfile,
ResourceState,
VisitedResourceObject,
} from "./types";
import type { ResourceState, VisitedResourceObject } from "./types";
import type { Dataset } from "rdf-js";
import { reactive } from "vue-demi";
import {
......@@ -20,6 +17,8 @@ import type { BilingualLabels } from "@coscine/api-client/dist/types/Coscine.Api
import type { ProjectObject } from "@coscine/api-client/dist/types/Coscine.Api.Project";
import useNotificationStore from "@/store/notification";
import type { AxiosError } from "axios";
import { parseRDFDefinition, serializeRDFDefinition } from "./utils/linkedData";
import factory from "rdf-ext";
/*
Store variable name is "this.<id>Store"
id: "resource" --> this.resourceStore
......@@ -49,7 +48,7 @@ export const useResourceStore = defineStore({
:label = "this.resourceStore.<getter_name>;
*/
getters: {
currentFullApplicationProfile(): ApplicationProfile | null {
currentFullApplicationProfile(): Dataset | null {
if (this.currentId) {
return this.visitedResources[this.currentId].fullApplicationProfile;
} else {
......@@ -102,7 +101,11 @@ export const useResourceStore = defineStore({
const apiResponse = await MetadataApi.metadataGetProfile(
resource.applicationProfile
);
resource.fullApplicationProfile = apiResponse.data;
resource.fullApplicationProfile = await parseRDFDefinition(
JSON.stringify(apiResponse.data),
"application/ld+json",
resource.applicationProfile
);
} else {
console.error("Resource's application profile may be undefined.");
}
......@@ -112,19 +115,21 @@ export const useResourceStore = defineStore({
}
},
async getApplicationProfile(
applicationProfile: string
): Promise<ApplicationProfile> {
async getApplicationProfile(applicationProfile: string): Promise<Dataset> {
const notificationStore = useNotificationStore();
try {
const apiResponse = await MetadataApi.metadataGetProfile(
applicationProfile
);
return apiResponse.data;
return await parseRDFDefinition(
JSON.stringify(apiResponse.data),
"application/ld+json",
applicationProfile
);
} catch (error) {
// Handle other Status Codes
notificationStore.postApiErrorNotification(error as AxiosError);
return [];
return factory.dataset();
}
},
......@@ -361,7 +366,8 @@ export const useResourceStore = defineStore({
if (resource && resource.id) {
const response = await TreeApi.treeGetMetadataWithParameter(
resource.id,
absoluteFilePath
absoluteFilePath,
"text/turtle"
);
return response.data;
} else {
......@@ -405,7 +411,7 @@ export const useResourceStore = defineStore({
async storeMetadata(
resource: ResourceObject | null,
absoluteFilePath: string,
body: unknown
body: Dataset
) {
const notificationStore = useNotificationStore();
try {
......@@ -413,7 +419,8 @@ export const useResourceStore = defineStore({
await TreeApi.treeStoreMetadataForFileWithParameter(
resource.id,
absoluteFilePath,
{ data: body }
"text/turtle",
{ data: { metadata: await serializeRDFDefinition(body) } }
);
return true;
} else {
......
......@@ -3,19 +3,18 @@ import type {
ResourceObject,
ResourceTypeInformation,
} from "@coscine/api-client/dist/types/Coscine.Api.Resources";
export interface Metadata {
[key: string | number]: Array<Metadata> | Metadata | string;
}
export type ApplicationProfile = Array<Metadata>;
import type { Dataset } from "rdf-js";
export interface VisitedResourceObject extends ResourceObject {
fullApplicationProfile: ApplicationProfile | null;
fullApplicationProfile: Dataset | null;
usedQuota: number | null;
storedColumns: string | null;
}
export interface Metadata {
[key: string | number]: Array<Metadata> | Metadata | string;
}
export interface LabeledResourceObject extends ResourceTypeInformation {
iDisplayName: string;
iFullName: string;
......
import type { Metadata } from "../types";
import type { Dataset } from "rdf-js";
export interface GeneralInformation {
id: string;
......@@ -9,7 +9,7 @@ export interface GeneralInformation {
lastModified?: string;
readOnly?: boolean;
created?: string;
metadata: Metadata;
metadata: Dataset;
}
export interface FileInformation extends GeneralInformation {
......
import useResourceStore from "../store";
import type { ResourceObject } from "@coscine/api-client/dist/types/Coscine.Api.Resources";
import type { FolderContent } from "./EntryDefinition";
import { Metadata } from "../types";
import type { Dataset } from "rdf-js";
import factory from "rdf-ext";
import { parseRDFDefinition } from "./linkedData";
import type { Metadata } from "../types";
export default {
filterMetadataStorage(
......@@ -13,13 +16,16 @@ export default {
const graphName = Object.keys(x)[0];
const pathQueryString = "path=";
if (graphName.indexOf(pathQueryString) !== -1) {
let path = graphName.substr(
let path = graphName.substring(
graphName.indexOf(pathQueryString) + pathQueryString.length
);
if (path.indexOf("&") !== -1) {
path = path.substr(0, path.indexOf("&"));
path = path.substring(0, path.indexOf("&"));
}
if (decodeURIComponent(path) === filterPath) {
if (
decodeURIComponent(path) === filterPath ||
decodeURIComponent(path) === "/" + filterPath
) {
return true;
}
}
......@@ -28,7 +34,7 @@ export default {
});
},
async loadMetadata(
callback: (response: Metadata) => void,
callback: (response: Dataset) => void,
fileInfo: FolderContent,
resource: ResourceObject | null
) {
......@@ -41,7 +47,7 @@ export default {
const metadataStorage = response.data.metadataStorage;
if (metadataStorage.length === 0) {
return callback({});
return callback(factory.dataset() as unknown as Dataset);
}
const resultArray = this.filterMetadataStorage(
......@@ -50,7 +56,7 @@ export default {
);
if (resultArray.length === 0) {
return callback({});
return callback(factory.dataset() as unknown as Dataset);
}
const result = resultArray[0];
......@@ -60,55 +66,27 @@ export default {
response.data.metadataStorage !== undefined &&
objectKeys.length === 1
) {
return callback(result[objectKeys[0]] as Metadata);
const entry = result[objectKeys[0]];
if (typeof entry === "string") {
return callback(await parseRDFDefinition(entry));
}
} else if (
response.data.metadataStorage !== undefined &&
objectKeys.length > 1
) {
return callback(result);
const entry = result;
if (typeof entry === "string") {
return callback(await parseRDFDefinition(entry));
}
}
} else {
return callback({});
}
return callback(factory.dataset() as unknown as Dataset);
},
copyMetadata(
source: Metadata | undefined,
target: FolderContent | undefined,
copyStructure = true,
createNode = false
) {
copyMetadata(source: Dataset | undefined, target: FolderContent | undefined) {
if (source && target) {
if (!target.metadata) {
target.metadata = {};
}
let copyTarget: Metadata = target.metadata;
if (createNode) {
target.metadata = {};
copyTarget = target.metadata;
}
const objectKeys = Object.keys(source);
for (const objectKey of objectKeys) {
if (copyStructure) {
copyTarget[objectKey] = [];
}
for (let i = 0; i < source[objectKey].length; i++) {
if (copyStructure) {
(copyTarget[objectKey] as Metadata[]).push({});
}
const innerObjectKeys = Object.keys(source[objectKey][i]);
for (const innerObjectKey of innerObjectKeys) {
if (copyStructure) {
(copyTarget[objectKey][i] as Metadata)[innerObjectKey] = (
source[objectKey][i] as Metadata
)[innerObjectKey];
} else if (innerObjectKey === "value") {
copyTarget[objectKey] = (source[objectKey][i] as Metadata)[
innerObjectKey
];
}
}
}
}
target.metadata = factory.dataset(
Array.from(source)
) as unknown as Dataset;
}
},
};
import factory from "rdf-ext";
import { Readable } from "stream";
import rdfParser from "rdf-parse";
import type { Dataset } from "rdf-js";
import { serializers } from "@rdfjs-elements/formats-pretty";
import stringifyStream from "stream-to-string";
export async function parseRDFDefinition(
definition: string,
contentType = "text/turtle",
baseIRI = "http://coscine.rwth-aachen.de"
): Promise<Dataset> {
const input = new Readable({
read: () => {
input.push(definition);
input.push(null);
},
});
const dataset = factory.dataset();
await new Promise((resolve) => {
rdfParser
.parse(input, { contentType: contentType, baseIRI: baseIRI })
.on("data", (quad) => dataset.add(quad))
.on("error", (error) => console.error(error))
.on("end", () => resolve(dataset));
});
return dataset as unknown as Dataset;
}
export async function serializeRDFDefinition(
dataset: Dataset,
contentType = "text/turtle"
): Promise<string> {
const canonical = dataset.toCanonical();
if (!canonical) {
return "";
}
const output = serializers.import(contentType, dataset.toStream());
if (output === null) {
return "";
}
return await stringifyStream(output as NodeJS.ReadableStream);
}
......@@ -62,7 +62,9 @@
<b-form-input
id="givenname"
v-model="$v.form.givenname.$model"
:state="$v.form.givenname.$dirty ? !$v.form.givenname.$error : null"
:state="
$v.form.givenname.$dirty ? !$v.form.givenname.$invalid : null
"
:placeholder="
$t('page.userprofile.form.personalInformation.givenName')
"
......@@ -81,7 +83,7 @@
<b-form-input
id="surname"
v-model="$v.form.surname.$model"
:state="$v.form.surname.$dirty ? !$v.form.surname.$error : null"
:state="$v.form.surname.$dirty ? !$v.form.surname.$invalid : null"
:placeholder="
$t('page.userprofile.form.personalInformation.surname')
"
......@@ -101,7 +103,9 @@
id="Email"
v-model="$v.form.emailAddress.$model"
:state="
$v.form.emailAddress.$dirty ? !$v.form.emailAddress.$error : null
$v.form.emailAddress.$dirty || !profileForm.form.emailAddress
? !$v.form.emailAddress.$invalid
: null
"
:placeholder="$t('page.userprofile.form.personalInformation.email')"
/>
......@@ -126,8 +130,6 @@
:options="ror"
:multiple="false"
:loading="loadingOrganizations"
label="displayName"
track-by="displayName"
:show-labels="false"
:placeholder="
$t(
......@@ -136,12 +138,6 @@
"
@search-change="triggerFetchOptions"
>
<template slot="singleLabel" slot-scope="props">
{{ props.option.displayName }}
</template>
<template slot="option" slot-scope="props">
{{ props.option.displayName }}
</template>
<template slot="noOptions">
{{
$t(
......@@ -251,6 +247,7 @@
<CoscineHeadline
:headline="$t('page.userprofile.form.userPreferences.header')"
/>
<!-- Language -->
<coscine-form-group
:mandatory="true"
label-for="language"
......@@ -263,7 +260,16 @@
<b-form-radio-group
id="language"
class="bv-no-focus-ring"
:checked="form.language !== undefined ? form.language.id : false"
:checked="
profileForm.form.language !== undefined
? profileForm.form.language.id
: false
"
:state="
profileForm.form.language && profileForm.form.language.id
? null
: false
"
:options="languages"
name="radios-stacked"
text-field="displayName"
......@@ -330,38 +336,8 @@
:disabled="$v.form.$invalid || savingProfile || !$v.form.$anyDirty"
@click.prevent="clickSave"
>
<b-iconstack
v-if="savingProfile"
animation="spin"
aria-hidden="true"
>
<b-icon
stacked
icon="dot"
aria-hidden="true"
shift-v="4"
></b-icon>
<b-icon
stacked
icon="dot"
aria-hidden="true"
shift-v="-4"
></b-icon>
<b-icon
stacked
icon="dot"
aria-hidden="true"
shift-h="4"
></b-icon>
<b-icon
stacked
icon="dot"
aria-hidden="true"
shift-h="-4"
></b-icon>
</b-iconstack>
{{ $t("buttons.save") }}</b-button
>
{{ $t("buttons.save") }}
</b-button>
</coscine-form-group>
</b-form>
</div>
......@@ -370,14 +346,10 @@
</template>
<script lang="ts">
import { defineComponent } from "vue-demi";
import { validationMixin } from "vuelidate";
import { required, email } from "vuelidate/lib/validators";
import { clone } from "lodash";
import CoscineFormGroup from "@/components/coscine/CoscineFormGroup.vue";
import { defineComponent, reactive } from "vue-demi";
import { required, email } from "@vuelidate/validators";
import { useVuelidate, Validation } from "@vuelidate/core";
import AccessToken from "./components/AccessToken.vue";
import CoscineHeadline from "@/components/coscine/CoscineHeadline.vue";
import "@/plugins/deprecated/vue-multiselect";
......@@ -396,39 +368,43 @@ import type { OrganizationObject } from "@coscine/api-client/dist/types/Coscine.
import useNotificationStore from "@/store/notification";
export default defineComponent({
components: {
AccessToken,
CoscineFormGroup,
CoscineHeadline,
},
mixins: [validationMixin],
components: { AccessToken },
setup() {
const mainStore = useMainStore();
const userStore = useUserStore();
const notificationStore = useNotificationStore();
return { mainStore, userStore, notificationStore };
},
validations: {
form: {
title: {},
givenname: {
required,
},
surname: {
required,
/*
Definition of the validation rules and initial state
will enable proper typings in the code
*/
const profileForm = reactive({
form: {
title: "",
givenname: "",
surname: "",
emailAddress: "",
organization: "",
institute: "",
disciplines: [] as DisciplineObject[],
language: {} as LanguageObject,
} as UserObject,
});
const profileRules = {
form: {
title: {},
givenname: { required },
surname: { required },
emailAddress: { email, required },
organization: {},
institute: {},
disciplines: {},
language: { id: {} },
},
emailAddress: {
email,
required,
},
organization: {},
institute: {},
disciplines: {},
language: {
id: {},
},
},
};
const $v = useVuelidate(profileRules, profileForm) as Validation<
typeof profileRules
>;
return { mainStore, userStore, notificationStore, $v, profileForm };
},
data() {
return {
......@@ -436,7 +412,6 @@ export default defineComponent({
savingProfile: false,
currentUserComponent: "UserProfileComponent",
disciplineLabel: "displayNameEn",
form: {} as UserObject,
selectedExternalOrganization: null as Record<string, unknown> | null,
queryTimer: 0,
loadingOrganizations: false,
......@@ -450,7 +425,7 @@ export default defineComponent({
return this.userStore.userProfile.disciplines;
},
emailHint(): string {
if (!this.form.emailAddress) {
if (!this.profileForm.form.emailAddress) {
return this.$t(
"page.userprofile.form.personalInformation.emailChange.noAddress"
).toString();
......@@ -469,11 +444,12 @@ export default defineComponent({
this.institutes.length > 0
);
},
institutes(): OrganizationObject[] {
institutes(): string[] {
if (this.memberOrganizations) {
return this.memberOrganizations.filter(
(organization) => organization.url?.indexOf("#") !== -1
);
return this.memberOrganizations
.filter((organization) => organization.url?.indexOf("#") !== -1) // If does contain "#" it's a sub level organization, otherwise top level
.map((org) => (org.displayName ? org.displayName : "")) // Extract organization display name, could contain empty strings
.filter((n) => n); // Filter out empty strings, if any;;
}
return [];
},
......@@ -493,16 +469,22 @@ export default defineComponent({
}
return false;
},
organizations(): OrganizationObject[] {
organizations(): string[] {
if (this.memberOrganizations) {
this.memberOrganizations.filter(
(organization) => !(organization.url?.indexOf("#") !== -1)
);
return this.memberOrganizations
.filter((organization) => !(organization.url?.indexOf("#") !== -1)) // If does contain "#" it's a sub level organization, otherwise top level
.map((org) => (org.displayName ? org.displayName : "")) // Extract organization display name, could contain empty strings
.filter((n) => n); // Filter out empty strings, if any;
}
return [];
},
ror(): null | OrganizationObject[] {
return this.userStore.userProfile.organizations;
ror(): null | string[] {
const organizations = this.userStore.userProfile.organizations;
if (organizations) {
return organizations
.map((org) => (org.displayName ? org.displayName : "")) // Extract organization display name, could contain empty strings
.filter((n) => n); // Filter out empty strings, if any
} else return null;
},
shibbolethConnected(): boolean {
if (this.user.externalAuthenticators) {
......@@ -537,7 +519,7 @@ export default defineComponent({
methods: {
cloneUser() {
if (this.user !== null) {
this.form = clone(this.user);
Object.assign(this.profileForm.form, this.user);
}
},
async clickConnect(provider: string) {
......@@ -545,18 +527,17 @@ export default defineComponent({
location.href = "/coscine/api/Coscine.Api.STS/account/logout";
},
async clickSave() {
this.$v.$touch();
if (
!this.$v.form.$anyError &&
!this.$v.form.$invalid &&
this.$v.form.$anyDirty
) {
this.$v.form.$touch();
if (!this.$v.form.$invalid && this.$v.form.$anyDirty) {
this.savingProfile = true;
// SECTION FOR SAVING ANY EMAIL CHANGES
// SIDE EFFECT: NO OTHER FIELD CHANGE CAN BE SAVED TOGETHER WITH A CHANGE OF THE EMAIL ADDRESS
// TODO
if (
this.form.emailAddress &&
this.form.emailAddress !== this.user.emailAddress
this.profileForm.form.emailAddress &&
this.profileForm.form.emailAddress !== this.user.emailAddress
) {
await this.userStore.updateEmail(this.form.emailAddress);
await this.userStore.updateEmail(this.profileForm.form.emailAddress);
await Promise.all([
this.userStore.retrieveContactChange(),
this.userStore.retrieveUser(),
......@@ -570,22 +551,30 @@ export default defineComponent({
).toString(),
});
}
try {
await this.userStore.updateUser(this.form);
// SECTION FOR SAVING THE REST OF THE FIELDS
const updatedUser = await this.userStore.updateUser(
this.profileForm.form
);
if (updatedUser) {
// On Success
this.$v.form.$reset();
this.notificationStore.postNotification({
title: this.$t("toast.onSave.success.title").toString(),
body: this.$t("toast.onSave.success.message").toString(),
});
} catch {
} else {
this.savingProfile = false;
// On Failure
this.notificationStore.postNotification({
title: this.$t("toast.onSave.failure.title").toString(),
body: this.$t("toast.onSave.failure.message").toString(),
variant: "danger",
});
} finally {
this.savingProfile = false;
}
await Promise.all([
this.userStore.retrieveContactChange(),
this.userStore.retrieveUser(),
]);
}
},
triggerFetchOptions(search: string) {
......
......@@ -7,15 +7,24 @@
:body="$t('page.userprofile.form.accessToken.modal.createToken.body')"
>
<div class="create-modal-content">
<b-button-group id="tokenButtonGroup" style="width: 100%">
<b-form-input
<b-input-group id="tokenButtonGroup" style="width: 100%">
<b-form-textarea
id="accessToken"
v-model="$v.token.AccessToken.$model"
:readonly="true"
rows="7"
no-resize
/>
<b-button id="copyButton" style="margin: inherit" @click="copyToken">
<b-icon-clipboard />
</b-button>
<b-input-group-append>
<b-button
id="copyButton"
style="margin: inherit"
variant="primary"
@click="copyToken"
>
<b-icon icon="clipboard" />
</b-button>
</b-input-group-append>
<b-tooltip ref="tooltip" target="copyButton" triggers="focus">
{{
$t(
......@@ -23,7 +32,7 @@
)
}}
</b-tooltip>
</b-button-group>
</b-input-group>
</div>
<template #buttons>
<div align="right">
......@@ -168,19 +177,14 @@
</template>
<script lang="ts">
import { defineComponent } from "vue-demi";
import { validationMixin } from "vuelidate";
import { required } from "vuelidate/lib/validators";
import { BIconClipboard } from "bootstrap-vue";
import { defineComponent, reactive } from "vue-demi";
import { required } from "@vuelidate/validators";
import { useVuelidate, Validation } from "@vuelidate/core";
// import the main store
import useMainStore from "@/store/index";
// import the store for current module
import useUserStore from "../../store";
import moment from "moment";
import type {
AddApiTokenParameter,
ApiTokenObject,
......@@ -193,24 +197,33 @@ interface TokenValidityBoundDates {
}
export default defineComponent({
components: {
BIconClipboard,
},
mixins: [validationMixin],
setup() {
const mainStore = useMainStore();
const userStore = useUserStore();
return { mainStore, userStore };
},
validations: {
token: {
TokenExpirationDate: {},
TokenName: {
required,
/*
Definition of the validation rules and initial state
will enable proper typings in the code
*/
const tokenForm = reactive({
token: {
TokenExpirationDate: new Date(),
TokenName: "",
AccessToken: "",
},
AccessToken: {},
},
});
const tokenRules = {
token: {
TokenExpirationDate: {},
TokenName: { required },
AccessToken: {},
},
};
const $v = useVuelidate(tokenRules, tokenForm) as Validation<
typeof tokenRules
>;
return { mainStore, userStore, $v, tokenForm };
},
data() {
return {
......@@ -248,11 +261,6 @@ export default defineComponent({
selectedTokenId: "",
selectedTokenName: "",
externalUser: true,
token: {
TokenExpirationDate: new Date(),
TokenName: "",
AccessToken: "" as string,
},
};
},
......@@ -279,7 +287,7 @@ export default defineComponent({
watch: {
tokenValidityBounds() {
this.token.TokenExpirationDate = moment(
this.tokenForm.token.TokenExpirationDate = moment(
this.tokenValidityBounds.minDate
).toDate();
},
......@@ -293,23 +301,23 @@ export default defineComponent({
methods: {
async createToken() {
const data: AddApiTokenParameter = {
name: this.token.TokenName,
name: this.tokenForm.token.TokenName,
expiration: 0,
};
const expDate = moment(this.token.TokenExpirationDate);
const expDate = moment(this.tokenForm.token.TokenExpirationDate);
data.expiration = Math.ceil(
moment.duration(expDate.diff(today)).asDays()
);
const token = await this.userStore.storeToken(data);
await this.userStore.retrieveTokens();
if (token) {
this.token.AccessToken = token.token ?? "";
this.token.TokenName = "";
this.tokenForm.token.AccessToken = token.token ?? "";
this.tokenForm.token.TokenName = "";
this.isCreateModalVisible = true;
}
},
copyToken() {
navigator.clipboard.writeText(this.token.AccessToken);
navigator.clipboard.writeText(this.tokenForm.token.AccessToken);
},
// -- delete selected token
revokeToken(selectedToken: ApiTokenObject) {
......
......@@ -254,13 +254,15 @@ export const useUserStore = defineStore({
}
},
async updateUser(user: UserObject) {
async updateUser(user: UserObject): Promise<boolean> {
const notificationStore = useNotificationStore();
try {
await UserApi.userUpdateUser(user);
return true;
} catch (error) {
// Handle other Status Codes
notificationStore.postApiErrorNotification(error as AxiosError);
return false;
}
},
......
import Vue from "vue";
import { BootstrapVue, BootstrapVueIcons } from "bootstrap-vue";
import { IconsPlugin } from "bootstrap-vue";
import "bootstrap/dist/css/bootstrap.css";
import "bootstrap-vue/dist/bootstrap-vue.css";
import "bootstrap/dist/css/bootstrap.min.css";
import "bootstrap-vue/dist/bootstrap-vue.min.css";
Vue.use(BootstrapVue);
Vue.use(BootstrapVueIcons);
Vue.use(IconsPlugin);
......@@ -3,8 +3,8 @@
/* Value taken from bootstrap */
font-size: 1rem;
/* All bellow is to center the gray placeholder vertically */
padding: 0px !important;
margin: 0 !important;
padding: 0px;
margin: 0;
position: absolute;
top: 50%;
-ms-transform: translateY(-50%);
......@@ -24,31 +24,32 @@
.multiselect__input,
.multiselect__single {
padding: 0px !important;
margin: 0px !important;
padding: 0px;
margin: 0px;
/* All bellow is to center the placeholder vertically when active */
-ms-transform: translateY(15%) !important;
transform: translateY(15%) !important;
-ms-transform: translateY(15%);
transform: translateY(15%);
}
.multiselect__tags {
/* Values taken from bootstrap */
padding-left: 0.75rem !important;
padding-top: 0.375rem !important;
padding-bottom: 0.375rem !important;
min-height: calc(1.5rem + 0.75rem + 2px) !important;
padding-left: 0.75rem;
padding-top: 0.375rem;
padding-bottom: 0.375rem;
min-height: calc(1.5rem + 0.75rem + 2px);
background-color: transparent;
}
.multiselect__tags-wrap {
margin: 0 !important;
padding: 0 !important;
margin: 0;
padding: 0;
}
.multiselect__tag {
background: var(--primary) !important;
color: var(--white) !important;
margin-bottom: 0px !important;
margin-right: 5px !important;
margin-bottom: 0px;
margin-right: 5px;
}
.multiselect__tag-icon:after {
......@@ -68,4 +69,8 @@
.multiselect__tag-icon:focus:after,
.multiselect__tag-icon:hover:after {
color: var(--white) !important;
}
.multiselect--disabled {
border-radius: 0.25rem;
}
\ No newline at end of file
import { Buffer } from "buffer/";
globalThis.global = globalThis;
window.global = globalThis;
/* eslint-disable @typescript-eslint/no-explicit-any */
(global.Buffer as any) = Buffer;
(window.Buffer as any) = Buffer;
/* eslint-enable @typescript-eslint/no-explicit-any */