@kit-data-manager/pid-component
Version:
The PID-Component is a web component that can be used to evaluate and display FAIR Digital Objects, PIDs, ORCiDs, and possibly other identifiers in a user-friendly way. It is easily extensible to support other identifier types.
1,155 lines (1,137 loc) • 125 kB
JavaScript
/*!
*
* Copyright 2024 Karlsruhe Institute of Technology.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { h, r as registerInstance, H as Host, a as getElement } from './index-DzUQJMIf.js';
import './json-viewer.entry.js';
class GenericIdentifierType {
constructor(value, settings) {
this._isDarkMode = false;
this._settings = [];
this._items = [];
this._actions = [];
this._value = value;
this._settings = settings;
this.updateDarkMode();
}
get settings() {
return this._settings;
}
set settings(value) {
this._settings = value;
this.updateDarkMode();
}
get items() {
return this._items;
}
get actions() {
return this._actions;
}
get value() {
return this._value;
}
get data() {
return undefined;
}
renderBody() {
return undefined;
}
get isDarkMode() {
return this._isDarkMode;
}
updateDarkMode() {
var _a;
const darkModeSetting = (_a = this._settings) === null || _a === void 0 ? void 0 : _a.find(setting => setting.name === 'darkMode');
if (darkModeSetting) {
const darkMode = darkModeSetting.value;
if (darkMode === 'dark') {
this._isDarkMode = true;
}
else if (darkMode === 'light') {
this._isDarkMode = false;
}
else if (darkMode === 'system' && typeof window !== 'undefined' && window.matchMedia) {
this._isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
}
}
else {
this._isDarkMode = false;
}
}
}
class DateType extends GenericIdentifierType {
getSettingsKey() {
return 'DateType';
}
async hasCorrectFormat() {
const regex = new RegExp('^([0-9]{4})-([0]?[1-9]|1[0-2])-([0-2][0-9]|3[0-1])(T([0-1][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9](.[0-9]*)?(Z|([+|-]([0-1][0-9]|2[0-3]):[0-5][0-9])){1}))$');
return regex.test(this.value);
}
init() {
this._date = new Date(this.value);
return;
}
renderPreview() {
return h("span", null, this._date.toLocaleString());
}
}
let cacheInstance;
async function open() {
if ('caches' in self) {
cacheInstance = await caches.open('pid-component');
}
}
async function cachedFetch(url, init) {
await open();
if (cacheInstance) {
const response = await cacheInstance.match(url);
if (response) {
return response.json();
}
else {
let response;
const parts = url.split('://');
if (parts[0] !== 'https') {
response = await fetch(`https://${parts[1]}`, init);
if (!response) {
console.log(`404 for https://${parts[1]} - trying http://${parts[1]}`);
response = await fetch(`http://${parts[1]}`, init);
}
}
else {
response = await fetch(url, init);
}
await cacheInstance.put(url, response.clone());
return response.json();
}
}
else {
const response = await fetch(url, init);
return response.json();
}
}
async function clearCache() {
if (cacheInstance) {
await cacheInstance.delete('pid-component');
}
}
class ORCIDInfo {
constructor(orcid, ORCiDJSON, familyName, givenNames, employments, preferredLocale, biography, emails, keywords, researcherUrls, country) {
this._orcid = orcid;
this._familyName = familyName;
this._givenNames = givenNames;
this._employments = employments;
this._preferredLocale = preferredLocale;
this._biography = biography;
this._emails = emails;
this._keywords = keywords;
this._researcherUrls = researcherUrls;
this._country = country;
this._ORCiDJSON = ORCiDJSON;
}
get orcid() {
return this._orcid;
}
get familyName() {
return this._familyName;
}
get givenNames() {
return this._givenNames;
}
get ORCiDJSON() {
return this._ORCiDJSON;
}
get employments() {
return this._employments;
}
get preferredLocale() {
return this._preferredLocale;
}
get biography() {
return this._biography;
}
get emails() {
return this._emails;
}
get keywords() {
return this._keywords;
}
get researcherUrls() {
return this._researcherUrls;
}
get country() {
return this._country;
}
static isORCiD(text) {
const regex = new RegExp('^(https://orcid.org/)?[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{3}[0-9X]$');
return regex.test(text);
}
static async getORCiDInfo(orcid) {
if (!ORCIDInfo.isORCiD(orcid))
throw new Error('Invalid input');
if (orcid.match('^(https://orcid.org/)?[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{3}[0-9X]$') !== null)
orcid = orcid.replace('https://orcid.org/', '');
const rawOrcidJSON = await cachedFetch(`https://pub.orcid.org/v3.0/${orcid}`, {
headers: {
Accept: 'application/json',
},
});
let familyName = '';
let givenNames = [];
try {
familyName = rawOrcidJSON['person']['name']['family-name']['value'];
}
catch (e) {
console.debug(e);
}
try {
givenNames = rawOrcidJSON['person']['name']['given-names']['value'];
}
catch (e) {
console.debug(e);
}
const affiliations = rawOrcidJSON['activities-summary']['employments']['affiliation-group'];
const employments = [];
try {
for (let i = 0; i < affiliations.length; i++) {
const employmentSummary = affiliations[i]['summaries'][0]['employment-summary'];
const employment = new Employment(new Date(), null, '', '');
if (employmentSummary['start-date'] !== null)
employment.startDate = new Date(employmentSummary['start-date']['year']['value'], employmentSummary['start-date']['month']['value'], employmentSummary['start-date']['day']['value']);
if (employmentSummary['end-date'] !== null)
employment.endDate = new Date(employmentSummary['end-date']['year']['value'], employmentSummary['end-date']['month']['value'], employmentSummary['end-date']['day']['value']);
employment.organization = employmentSummary['organization']['name'];
employment.department = employmentSummary['department-name'];
employments.push(employment);
}
}
catch (e) {
console.debug(e);
}
const preferredLocale = rawOrcidJSON['preferences']['locale'] !== null ? rawOrcidJSON['preferences']['locale'] : undefined;
const biography = rawOrcidJSON['person']['biography'] !== null ? rawOrcidJSON['person']['biography']['content'] : undefined;
const emails = [];
if (rawOrcidJSON['person']['emails']['email'] !== null) {
for (const email of rawOrcidJSON['person']['emails']['email']) {
emails.push({
email: email['email'],
primary: email['primary'],
verified: email['verified'],
});
}
}
const keywords = [];
if (rawOrcidJSON['person']['keywords']['keyword'] !== null) {
for (const keyword of rawOrcidJSON['person']['keywords']['keyword']) {
keywords.push({
content: keyword['content'],
index: keyword['display-index'],
});
}
keywords.sort((a, b) => a.index - b.index);
}
const researcherUrls = [];
if (rawOrcidJSON['person']['researcher-urls']['researcher-url'] !== null) {
for (const researcherUrl of rawOrcidJSON['person']['researcher-urls']['researcher-url']) {
researcherUrls.push({
url: researcherUrl['url']['value'],
name: researcherUrl['url-name'],
index: researcherUrl['display-index'],
});
}
researcherUrls.sort((a, b) => a.index - b.index);
}
const country = rawOrcidJSON['person']['addresses']['address'].length > 0 ? rawOrcidJSON['person']['addresses']['address'][0]['country']['value'] : undefined;
return new ORCIDInfo(orcid, rawOrcidJSON, familyName, givenNames, employments, preferredLocale, biography, emails, keywords, researcherUrls, country);
}
static fromJSON(serialized) {
const data = JSON.parse(serialized);
const employments = data.employments.map(employment => Employment.fromJSON(employment));
return new ORCIDInfo(data.orcid, data.ORCiDJSON, data.familyName, data.givenNames, employments, data.preferredLocale, data.biography, data.emails, data.keywords, data.researcherUrls, data.country);
}
getAffiliationsAt(date) {
const affiliations = [];
for (const employment of this._employments) {
if (employment.startDate <= date && employment.endDate === null)
affiliations.push(employment);
if (employment.startDate <= date && employment.endDate !== null && employment.endDate >= date)
affiliations.push(employment);
}
return affiliations;
}
getAffiliationAsString(affiliation, showDepartment = true) {
if (affiliation === undefined || affiliation.organization === null)
return undefined;
else {
if (showDepartment && affiliation.department !== null)
return `${affiliation.organization} [${affiliation.department}]`;
else
return affiliation.organization;
}
}
toObject() {
return {
orcid: this._orcid,
ORCiDJSON: this._ORCiDJSON,
familyName: this._familyName,
givenNames: this._givenNames,
employments: this._employments.map(employment => JSON.stringify(employment.toObject())),
preferredLocale: this._preferredLocale,
biography: this._biography,
emails: this._emails,
keywords: this._keywords,
researcherUrls: this._researcherUrls,
country: this._country,
};
}
}
class Employment {
constructor(startDate, endDate, organization, department) {
this.startDate = startDate;
this.endDate = endDate;
this.organization = organization;
this.department = department;
}
static fromJSON(serialized) {
const data = JSON.parse(serialized);
const startDate = new Date(data.startDate);
const endDate = data.endDate === null ? null : new Date(data.endDate);
return new Employment(startDate, endDate, data.organization, data.department);
}
toObject() {
return {
startDate: this.startDate,
endDate: this.endDate,
organization: this.organization,
department: this.department,
};
}
}
class FoldableItem {
constructor(priority, keyTitle, value, keyTooltip, keyLink, valueRegex, renderDynamically) {
this._estimatedTypePriority = 0;
this._priority = priority;
this._keyTitle = keyTitle;
this._value = value;
this._keyTooltip = keyTooltip;
this._keyLink = keyLink;
this._valueRegex = valueRegex;
this._renderDynamically = renderDynamically !== undefined ? renderDynamically : true;
this._estimatedTypePriority = renderDynamically ? 0 : 0;
}
get priority() {
return this._priority;
}
get keyTitle() {
return this._keyTitle;
}
get value() {
return this._value;
}
get keyTooltip() {
return this._keyTooltip;
}
get keyLink() {
return this._keyLink;
}
get valueRegex() {
return this._valueRegex;
}
get renderDynamically() {
return this._renderDynamically;
}
get estimatedTypePriority() {
return this._estimatedTypePriority;
}
isValidValue() {
return this._valueRegex ? this._valueRegex.test(this._value) : true;
}
equals(other) {
return (this._keyTitle === other._keyTitle &&
this._value === other._value &&
this._keyTooltip === other._keyTooltip &&
this._keyLink === other._keyLink &&
this._renderDynamically === other._renderDynamically);
}
}
class FoldableAction {
constructor(priority, title, link, style) {
this._priority = priority;
this._title = title;
this._link = link;
this._style = style;
}
get priority() {
return this._priority;
}
get title() {
return this._title;
}
get link() {
return this._link;
}
get style() {
return this._style;
}
equals(other) {
return this._priority === other._priority && this._title === other._title && this._link === other._link && this._style === other._style;
}
}
class ORCIDType extends GenericIdentifierType {
constructor() {
super(...arguments);
this.affiliationAt = new Date(Date.now());
this.showAffiliation = true;
}
get data() {
return JSON.stringify(this._orcidInfo.toObject());
}
async hasCorrectFormat() {
return ORCIDInfo.isORCiD(this.value);
}
async init(data) {
if (data !== undefined) {
this._orcidInfo = ORCIDInfo.fromJSON(data);
console.debug('reload ORCIDInfo from data', this._orcidInfo);
}
else {
this._orcidInfo = await ORCIDInfo.getORCiDInfo(this.value);
console.debug('load ORCIDInfo from API', this._orcidInfo);
}
if (this.settings) {
for (const i of this.settings) {
switch (i['name']) {
case 'affiliationAt':
this.affiliationAt = new Date(i['value']);
break;
case 'showAffiliation':
this.showAffiliation = i['value'] === true || i['value'] === 'true' || i['value'] === '1';
break;
}
}
}
this.items.push(new FoldableItem(0, 'ORCiD', this._orcidInfo.orcid, 'ORCiD is a free service for researchers to distinguish themselves by creating a unique personal identifier.', 'https://orcid.org', undefined, false));
try {
const givenNames = this._orcidInfo.givenNames;
if (givenNames) {
new FoldableItem(2, 'Given Names', this._orcidInfo.givenNames.toString(), 'The given names of the person.');
}
}
catch (e) {
console.log('Failed to obtain given names from ORCiD record.', e);
}
this.actions.push(new FoldableAction(0, 'Open ORCiD profile', `https://orcid.org/${this._orcidInfo.orcid}`, 'primary'));
try {
const affiliations = this._orcidInfo.getAffiliationsAt(new Date(Date.now()));
for (const data of affiliations) {
const affiliation = this._orcidInfo.getAffiliationAsString(data);
if (affiliation !== undefined && affiliation.length > 2)
this.items.push(new FoldableItem(50, 'Current Affiliation', affiliation, 'The current affiliation of the person.', undefined, undefined, false));
}
}
catch (e) {
console.log('Failed to obtain affiliations from ORCiD record.', e);
}
if (this._orcidInfo.getAffiliationsAt(this.affiliationAt) !== this._orcidInfo.getAffiliationsAt(new Date()) &&
this.affiliationAt.toLocaleDateString('en-US') !== new Date().toLocaleDateString('en-US')) {
const affiliationsThen = this._orcidInfo.getAffiliationsAt(this.affiliationAt);
for (const data of affiliationsThen) {
const affiliation = this._orcidInfo.getAffiliationAsString(data);
this.items.push(new FoldableItem(49, 'Affiliation at ' +
this.affiliationAt.toLocaleDateString('en-US', {
year: 'numeric',
month: 'numeric',
day: 'numeric',
}), affiliation, 'The affiliation of the person at the given date.', undefined, undefined, false));
}
}
if (this._orcidInfo.emails) {
const primary = this._orcidInfo.emails.filter(email => email.primary)[0];
const other = this._orcidInfo.emails.filter(email => !email.primary);
if (primary) {
this.items.push(new FoldableItem(20, 'Primary E-Mail address', primary.email, 'The primary e-mail address of the person.'));
this.actions.push(new FoldableAction(0, 'Send E-Mail', `mailto:${primary.email}`, 'secondary'));
}
if (other.length > 0)
this.items.push(new FoldableItem(70, 'Other E-Mail addresses', other.map(email => email.email).join(', '), 'All other e-mail addresses of the person.'));
if (this._orcidInfo.preferredLocale)
this.items.push(new FoldableItem(25, 'Preferred Language', this._orcidInfo.preferredLocale, 'The preferred locale/language of the person.'));
for (const url of this._orcidInfo.researcherUrls) {
this.items.push(new FoldableItem(100, url.name, url.url, 'A link to a website specified by the person.'));
}
if (this._orcidInfo.keywords.length > 50)
this.items.push(new FoldableItem(60, 'Keywords', this._orcidInfo.keywords.map(keyword => keyword.content).join(', '), 'Keywords specified by the person.', undefined, undefined, false));
if (this._orcidInfo.biography)
this.items.push(new FoldableItem(200, 'Biography', this._orcidInfo.biography, 'The biography of the person.', undefined, undefined, false));
if (this._orcidInfo.country)
this.items.push(new FoldableItem(30, 'Country', this._orcidInfo.country, 'The country of the person.'));
}
}
isResolvable() {
return this._orcidInfo.ORCiDJSON !== undefined;
}
renderPreview() {
return (h("span", { class: `inline-flex flex-nowrap items-center align-top font-mono ${this.isDarkMode ? 'text-gray-200' : ''}` },
h("svg", { version: "1.1", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 256 256", class: 'mr-1 h-5 flex-none items-center p-0.5' },
h("style", { type: "text/css" },
`.st0{fill:#A6CE39;}`,
`.st1{fill:#FFFFFF;}`),
h("path", { class: "st0", d: "M256,128c0,70.7-57.3,128-128,128C57.3,256,0,198.7,0,128C0,57.3,57.3,0,128,0C198.7,0,256,57.3,256,128z" }),
h("g", null,
h("path", { class: "st1", d: "M86.3,186.2H70.9V79.1h15.4v48.4V186.2z" }),
h("path", { class: "st1", d: "M108.9,79.1h41.6c39.6,0,57,28.3,57,53.6c0,27.5-21.5,53.6-56.8,53.6h-41.8V79.1z M124.3,172.4h24.5\n c34.9,0,42.9-26.5,42.9-39.7c0-21.5-13.7-39.7-43.7-39.7h-23.7V172.4z" }),
h("path", { class: "st1", d: "M88.7,56.8c0,5.5-4.5,10.1-10.1,10.1c-5.6,0-10.1-4.6-10.1-10.1c0-5.6,4.5-10.1,10.1-10.1\n C84.2,46.7,88.7,51.3,88.7,56.8z" }))),
h("span", { class: `flex-none items-center px-1 ${this.isDarkMode ? 'text-gray-200' : ''}` },
this._orcidInfo.familyName,
", ",
this._orcidInfo.givenNames,
' ',
this.showAffiliation && this._orcidInfo.getAffiliationsAt(new Date()).length > 0
? `(${this._orcidInfo.getAffiliationAsString(this._orcidInfo.getAffiliationsAt(new Date())[0], false)}${this._orcidInfo.getAffiliationsAt(this.affiliationAt).length > 0 &&
this.affiliationAt.toLocaleDateString() !== new Date().toLocaleDateString() &&
this._orcidInfo.getAffiliationsAt(this.affiliationAt)[0].organization !== this._orcidInfo.getAffiliationsAt(new Date())[0].organization
? `, then: ${this._orcidInfo.getAffiliationsAt(this.affiliationAt)[0].organization}`
: ''})`
: '')));
}
getSettingsKey() {
return 'ORCIDType';
}
}
class PID {
constructor(prefix, suffix) {
this._prefix = prefix;
this._suffix = suffix;
}
get prefix() {
return this._prefix;
}
get suffix() {
return this._suffix;
}
static isPID(text) {
return new RegExp('^([0-9A-Za-z])+.([0-9A-Za-z])+/([!-~])+$').test(text);
}
static getPIDFromString(pid) {
if (!PID.isPID(pid))
throw new Error('Invalid input');
const pidSplit = pid.split('/');
return new PID(pidSplit[0], pidSplit[1]);
}
static fromJSON(serialized) {
const data = JSON.parse(serialized);
return new PID(data.prefix, data.suffix);
}
toString() {
return `${this.prefix}/${this.suffix}`;
}
isResolvable() {
return !unresolvables.has(this) && !this.prefix.toUpperCase().match('^(0$|0\\.|HS_|10320$)');
}
async resolve() {
if (unresolvables.has(this))
return undefined;
else if (handleMap.has(this))
return handleMap.get(this);
else {
const rawJson = (await cachedFetch(`https://hdl.handle.net/api/handles/${this.prefix}/${this.suffix}#resolve`));
console.log(rawJson);
const valuePromises = rawJson.values.map(async (value) => {
const type = (async () => {
if (PID.isPID(value.type)) {
const pid = PID.getPIDFromString(value.type);
const dataType = await PIDDataType.resolveDataType(pid);
return dataType instanceof PIDDataType ? dataType : pid;
}
return value.type;
})();
return {
index: value.index,
type: await type,
data: value.data,
ttl: value.ttl,
timestamp: Date.parse(value.timestamp),
};
});
const values = await Promise.all(valuePromises);
const record = new PIDRecord(this, values);
handleMap.set(this, record);
return record;
}
}
toObject() {
return {
prefix: this.prefix,
suffix: this.suffix,
};
}
}
const locationType = new PID('10320', 'loc');
class PIDDataType {
constructor(pid, name, description, redirectURL, regex) {
this._pid = pid;
this._name = name;
this._description = description;
this._regex = regex;
this._redirectURL = redirectURL;
}
get pid() {
return this._pid;
}
get name() {
return this._name;
}
get description() {
return this._description;
}
get redirectURL() {
return this._redirectURL;
}
get regex() {
return this._regex;
}
static async resolveDataType(pid) {
if (typeMap.has(pid))
return typeMap.get(pid);
if (!pid.isResolvable()) {
console.debug(`PID ${pid.toString()} has been marked as unresolvable`);
return undefined;
}
const pidRecord = await pid.resolve();
if (pidRecord === undefined) {
console.debug(`PID ${pid.toString()} could not be resolved via the API`);
unresolvables.add(pid);
return undefined;
}
const tempDataType = { name: '', description: '', redirectURL: '', ePICJSON: {} };
for (let i = 0; i < pidRecord.values.length; i++) {
const currentValue = pidRecord.values[i];
if (currentValue.type === locationType || currentValue.type.toString() === locationType.toString()) {
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(currentValue.data.value, 'text/xml');
const xmlLocations = xmlDoc.getElementsByTagName('location');
for (let j = 0; j < xmlLocations.length; j++) {
const newLocation = {
href: xmlLocations[j].getAttribute('href'),
weight: undefined,
view: undefined,
resolvedData: undefined,
};
try {
newLocation.weight = parseInt(xmlLocations[j].getAttribute('weight'));
}
catch (_ignored) {
}
try {
newLocation.view = xmlLocations[j].getAttribute('view');
}
catch (ignored) {
}
try {
if (newLocation.view === 'json') {
newLocation.resolvedData = await cachedFetch(newLocation.href);
tempDataType.ePICJSON = newLocation.resolvedData;
tempDataType.name = newLocation.resolvedData['name'];
tempDataType.description = newLocation.resolvedData['description'];
}
else {
tempDataType.redirectURL = newLocation.href;
}
}
catch (ignored) {
}
}
}
}
try {
const type = new PIDDataType(pid, tempDataType.name, tempDataType.description, tempDataType.redirectURL, tempDataType.regex);
typeMap.set(pid, type);
return type;
}
catch (e) {
console.error(e);
return undefined;
}
}
static fromJSON(serialized) {
const data = JSON.parse(serialized);
return new PIDDataType(PID.fromJSON(data.pid), data.name, data.description, data.redirectURL, data.regex);
}
toObject() {
return {
pid: JSON.stringify(this._pid.toObject()),
name: this._name,
description: this._description,
redirectURL: this._redirectURL,
regex: this._regex,
};
}
}
class PIDRecord {
constructor(pid, values) {
this._values = [];
this._pid = pid;
this._values = values;
}
get pid() {
return this._pid;
}
get values() {
return this._values;
}
static fromJSON(serialized) {
const data = JSON.parse(serialized);
const values = data.values.map(value => {
const parsed = JSON.parse(value);
const parsedType = JSON.parse(parsed.type);
let type;
if (parsedType.pidDataType !== undefined) {
type = PIDDataType.fromJSON(parsedType.pidDataType);
}
else if (parsedType.pid !== undefined) {
type = PID.fromJSON(parsedType.pid);
}
else {
type = parsedType.string;
}
const data = JSON.parse(parsed.data);
return {
index: parsed.index,
type: type,
data: data,
ttl: parsed.ttl,
timestamp: parsed.timestamp,
};
});
return new PIDRecord(PID.fromJSON(data.pid), values);
}
toObject() {
return {
pid: JSON.stringify(this._pid.toObject()),
values: this._values.map(value => JSON.stringify({
index: value.index,
type: JSON.stringify({
pid: value.type instanceof PID ? JSON.stringify(value.type.toObject()) : undefined,
pidDataType: value.type instanceof PIDDataType ? JSON.stringify(value.type.toObject()) : undefined,
string: typeof value.type == 'string' ? value.type : undefined,
}),
data: JSON.stringify(value.data),
ttl: value.ttl,
timestamp: value.timestamp,
})),
};
}
}
class HandleType extends GenericIdentifierType {
constructor() {
super(...arguments);
this._parts = [];
}
get data() {
return JSON.stringify(this._pidRecord.toObject());
}
async hasCorrectFormat() {
return PID.isPID(this.value);
}
async init(data) {
if (data !== undefined) {
this._pidRecord = PIDRecord.fromJSON(data);
this._parts = await Promise.all([
{
text: this._pidRecord.pid.prefix,
nextExists: true,
},
{
text: this._pidRecord.pid.suffix,
nextExists: false,
},
]);
console.debug('reload PIDRecord from data', this._pidRecord);
}
else {
const pid = PID.getPIDFromString(this.value);
this._parts = [
{
text: pid.prefix,
nextExists: true,
},
{
text: pid.suffix,
nextExists: false,
},
];
this._pidRecord = await pid.resolve();
console.debug('load PIDRecord from API', this._pidRecord);
}
for (const value of this._pidRecord.values) {
if (value.type instanceof PIDDataType) {
this.items.push(new FoldableItem(0, value.type.name, value.data.value, value.type.description, value.type.redirectURL, value.type.regex));
}
}
this.actions.push(new FoldableAction(0, 'Open in FAIR-DOscope', `https://kit-data-manager.github.io/fairdoscope/?pid=${this._pidRecord.pid.toString()}`, 'primary'));
this.actions.push(new FoldableAction(0, 'View in Handle.net registry', `https://hdl.handle.net/${this._pidRecord.pid.toString()}`, 'secondary'));
return;
}
isResolvable() {
return this._pidRecord.values.length > 0;
}
renderPreview() {
return (h("span", { class: 'rounded-md bg-inherit font-mono font-bold' }, this._parts.map(element => {
return (h("span", { class: 'font-mono font-bold' },
h("color-highlight", { text: element.text }),
h("span", { class: `mx-0.5 font-mono font-bold` }, element.nextExists ? '/' : '')));
})));
}
getSettingsKey() {
return 'HandleType';
}
}
class EmailType extends GenericIdentifierType {
getSettingsKey() {
return 'EmailType';
}
async hasCorrectFormat() {
const regex = /^(([\w\-.]+@([\w-]+\.)+[\w-]{2,})(\s*,\s*)?)*$/gm;
return regex.test(this.value);
}
init() {
return;
}
renderPreview() {
return (h("span", { class: `inline-flex items-center gap-2 font-mono text-sm text-blue-500` }, this.value
.split(new RegExp(/\s*,\s*/))
.filter(email => email.length > 0)
.map(email => {
return (h("a", { href: 'mailto:' + email, rel: 'noopener noreferrer', target: "_blank", class: `inline-flex items-center rounded-md border border-slate-500 px-1 py-0.5 font-mono text-sm text-blue-500` },
h("svg", { xmlns: "http://www.w3.org/2000/svg", fill: "none", "aria-hidden": "true", viewBox: "0 0 24 24", stroke: "currentColor", "stroke-width": "1", height: "20px", class: 'mr-2' },
h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", d: "M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75" })),
h("span", { class: `ml-2 text-blue-400 hover:text-blue-500` }, email)));
})));
}
}
class URLType extends GenericIdentifierType {
getSettingsKey() {
return 'URLType';
}
async hasCorrectFormat() {
const regex = new RegExp('^http(s)?:(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?$');
return regex.test(this.value);
}
init() {
return;
}
renderPreview() {
return (h("a", { href: this.value, target: "_blank", rel: 'noopener noreferrer', class: `font-mono text-sm text-blue-400` }, this.value));
}
}
class FallbackType extends GenericIdentifierType {
async hasCorrectFormat() {
return true;
}
init() {
return;
}
renderPreview() {
return h("span", null, this.value);
}
getSettingsKey() {
return 'FallbackType';
}
}
class LocaleType extends GenericIdentifierType {
getSettingsKey() {
return 'LocaleType';
}
async hasCorrectFormat() {
const regex = /^([a-zA-Z]{2})(-[A-Z]{2})?$/;
return regex.test(this.value);
}
init() {
return;
}
renderPreview() {
return h("locale-visualization", { locale: this.value, showFlag: true });
}
}
class JSONType extends GenericIdentifierType {
constructor() {
super(...arguments);
this._parsedJsonResult = undefined;
}
getParsedJson() {
if (this._parsedJsonResult === undefined) {
try {
if (typeof this.value === 'object' && this.value !== null) {
this._parsedJsonResult = { data: this.value };
return this._parsedJsonResult;
}
if (typeof this.value !== 'string') {
throw new Error('Input value is not a string or object');
}
const trimmedValue = this.value.trim();
if (trimmedValue === '') {
this._parsedJsonResult = { error: new Error('Empty JSON string') };
return this._parsedJsonResult;
}
const parsed = JSON.parse(trimmedValue);
this._parsedJsonResult = { data: parsed };
}
catch (e) {
this._parsedJsonResult = { error: e instanceof Error ? e : new Error(String(e)) };
}
}
return this._parsedJsonResult;
}
getSettingsKey() {
return 'JSONType';
}
async hasCorrectFormat() {
const { data, error } = this.getParsedJson();
if (error) {
console.warn('JSONType has incorrect format:', error.message, 'for value:', this.value);
return false;
}
return typeof data === 'object' && data !== null;
}
init() {
this._parsedJsonResult = undefined;
return Promise.resolve();
}
isResolvable() {
return false;
}
renderPreview() {
const { data: jsonObj, error } = this.getParsedJson();
if (error) {
return h("span", { class: 'text-red-500' }, "Invalid JSON");
}
const isComplexObjectOrArray = typeof jsonObj === 'object' && jsonObj !== null;
const isArray = Array.isArray(jsonObj);
if (isComplexObjectOrArray) {
const entryCount = Object.keys(jsonObj).length;
return (h("div", { class: "w-full" },
h("div", { class: `flex items-center rounded-md font-mono text-xs` },
h("span", { class: `mr-1 font-medium` }, isArray ? 'Array' : 'Object'),
h("span", { class: 'text-gray-500' }, isArray ? '[' : '{'),
h("span", { class: `text-xs text-gray-500` },
entryCount,
" ",
entryCount === 1 ? 'item' : 'items'),
h("span", { class: 'text-gray-500' }, isArray ? ']' : '}'))));
}
return (h("div", { class: "w-full" },
h("pre", { class: `max-w-full overflow-x-auto rounded-md font-mono text-xs whitespace-pre-wrap` }, JSON.stringify(jsonObj, null, 2))));
}
renderBody() {
var _a, _b;
const { data: parsedData, error } = this.getParsedJson();
const darkModeValue = ((_b = (_a = this.settings) === null || _a === void 0 ? void 0 : _a.find(setting => setting.name === 'darkMode')) === null || _b === void 0 ? void 0 : _b.value) || 'system';
if (error) {
return (h("div", { class: "w-full overflow-y-auto" },
h("span", { class: 'text-red-500' },
"Invalid JSON data: ",
error.message)));
}
return (h("div", { class: "w-full overflow-y-auto" },
h("json-viewer", { data: parsedData, "expand-all": false, "show-line-numbers": true, theme: darkModeValue },
h("span", { class: 'text-red-500' }, "Could not display JSON data."))));
}
}
class RORType extends GenericIdentifierType {
constructor() {
super(...arguments);
this.relationshipTypes = {
parent: {
title: 'Parent Organization',
tooltip: 'Organization that this organization is part of',
},
child: {
title: 'Child Organization',
tooltip: 'Organization that is part of this organization',
},
related: {
title: 'Related Organization',
tooltip: 'Organization that is related to this organization',
},
predecessor: {
title: 'Predecessor Organization',
tooltip: 'Organization that preceded this organization',
},
successor: {
title: 'Successor Organization',
tooltip: 'Organization that succeeded this organization',
},
};
this.contentMappings = {
active: '🟢 Active',
inactive: '⚪️ Inactive',
withdrawn: '⚠️ Withdrawn',
education: '🏫 Education',
funder: '💰 Funder',
healthcare: '🏥 Healthcare',
company: '🏢 Company',
archive: '📚 Archive',
nonprofit: '🎗️ Nonprofit',
government: '🏛️ Government',
facility: '🔬 Facility',
other: 'Other',
unknown: '❓ Unknown',
};
}
getSettingsKey() {
return 'RORType';
}
async hasCorrectFormat() {
const regex = new RegExp('^https?://ror.org/[0-9a-z]{9}$', 'i');
return regex.test(this.value);
}
getRorId() {
return this.value.split('/').pop();
}
getOptimizedContent(content) {
if (this.contentMappings[content.toLowerCase()]) {
return this.contentMappings[content.toLowerCase()];
}
return content;
}
async init() {
try {
const rorId = this.getRorId();
const response = await fetch(`https://api.ror.org/v2/organizations/${rorId}`);
if (!response.ok) {
throw new Error(`Failed to fetch ROR data: ${response.status}`);
}
this.rorData = await response.json();
if (!this.rorData)
return;
if (!this.rorData.names || this.rorData.names.length === 0) {
this.label = 'Unknown';
this.items.push(new FoldableItem(0, 'Name', 'Unknown', 'No names available for this organization'));
return;
}
else {
for (const name of this.rorData.names) {
const types = name.types || [];
if (types.includes('acronym')) {
this.acronym = name.value;
this.items.push(new FoldableItem(20, 'Acronym', name.value, 'Short form of the organization name'));
}
else if (types.includes('ror_display')) {
this.label = name.value;
this.items.push(new FoldableItem(1, 'Display Name', name.value, 'Name used for display purposes'));
}
else if (types.includes('alias')) {
this.items.push(new FoldableItem(5, 'Alias', name.value, 'Alternative name for the organization'));
}
else if (types.includes('label')) {
this.items.push(new FoldableItem(15, 'Label', name.value, 'Name in another language or script'));
}
}
}
this.items.push(new FoldableItem(20, 'ROR ID', this.rorData.id, 'Unique identifier for the organization in the ROR registry', null, null, false));
this.actions.push(new FoldableAction(10, 'View on ROR', this.rorData.id, 'primary'));
this.items.push(new FoldableItem(30, 'Status', this.getOptimizedContent(this.rorData.status || 'unknown'), 'Current status of the organization in the ROR registry'));
if (!this.rorData.types || this.rorData.types.length === 0) {
this.items.push(new FoldableItem(25, 'Type', this.getOptimizedContent('unknown'), 'Type of organization'));
}
else {
for (const type of this.rorData.types) {
this.items.push(new FoldableItem(25, 'Type', this.getOptimizedContent(type), 'Type of organization'));
}
}
if (this.rorData.links && this.rorData.links.length > 0) {
for (const link of this.rorData.links) {
if (link.type) {
this.items.push(new FoldableItem(35, `Link to ${link.type}`, link.value, 'External link related to the organization'));
}
else {
this.items.push(new FoldableItem(35, `Link`, link.value, 'External link related to the organization'));
}
}
}
if (this.rorData.external_ids && this.rorData.external_ids.length > 0) {
for (const external of this.rorData.external_ids) {
const type = external.type;
const value = external.preferred || external.all[0];
this.items.push(new FoldableItem(40, `External ID: ${type}`, value, `Identifier from another system: ${type}`));
}
}
if (this.rorData.relationships && this.rorData.relationships.length > 0) {
for (const rel of this.rorData.relationships) {
const relationType = this.relationshipTypes[rel.type] || { title: rel.type, tooltip: `${rel.type} organization` };
this.items.push(new FoldableItem(90, relationType.title, rel.id, relationType.tooltip));
}
}
if (this.rorData.locations && this.rorData.locations.length > 0) {
for (const location of this.rorData.locations) {
const details = location.geonames_details;
if (details.country_code) {
this.items.push(new FoldableItem(50, 'Country', details.country_code, 'Country where the organization is located'));
}
if (details.lat && details.lng) {
this.items.push(new FoldableItem(55, 'Coordinates', `${details.lat}, ${details.lng}`, 'Geographic coordinates of the organization'));
const osmUrl = `https://www.openstreetmap.org/?mlat=${details.lat}&mlon=${details.lng}&zoom=15`;
this.actions.push(new FoldableAction(20, 'View on OpenStreetMap', osmUrl, 'secondary'));
}
}
}
}
catch (error) {
console.error('Error fetching ROR data:', error);
this.items.push(new FoldableItem(0, 'Error', `Failed to fetch data from ROR API: ${error.message}`));
}
}
renderPreview() {
if (!this.rorData) {
return h("span", { class: `font-mono text-sm` },
"Loading ROR: ",
this.value,
"...");
}
return (h("span", { class: `inline-flex flex-nowrap items-center align-top font-mono` },
h("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 164 118", version: "1.1", class: 'mr-1 h-5 flex-none items-center p-0.5', style: { fillRule: 'evenodd', clipRule: 'evenodd', strokeLinejoin: 'round', strokeMiterlimit: '2' } },
h("g", { transform: "matrix(0.994301,0,0,0.989352,0,0)" },
h("rect", { x: "0", y: "0", width: "164.94", height: "119.27", style: { fill: 'white' } })),
h("g", { transform: "matrix(1,0,0,1,-0.945,-0.815)" },
h("path", { d: "M68.65,4.16L56.52,22.74L44.38,4.16L68.65,4.16Z", style: { fill: 'rgb(83,186,161)', fillRule: 'nonzero' } }),
h("path", { d: "M119.41,4.16L107.28,22.74L95.14,4.16L119.41,4.16Z", style: { fill: 'rgb(83,186,161)', fillRule: 'nonzero' } }),
h("path", { d: "M44.38,115.47L56.52,96.88L68.65,115.47L44.38,115.47Z", style: { fill: 'rgb(83,186,161)', fillRule: 'nonzero' } }),
h("path", { d: "M95.14,115.47L107.28,96.88L119.41,115.47L95.14,115.47Z", style: { fill: 'rgb(83,186,161)', fillRule: 'nonzero' } }),
h("path", { d: "M145.53,63.71C149.83,62.91 153.1,61 155.33,57.99C157.57,54.98 158.68,51.32 158.68,47.03C158.68,43.47 158.06,40.51 156.83,38.13C155.6,35.75 153.93,33.86 151.84,32.45C149.75,31.05 147.31,30.04 144.53,29.44C141.75,28.84 138.81,28.54 135.72,28.54L112.16,28.54L112.16,47.37C111.97,46.82 111.77,46.28 111.55,45.74C109.92,41.79 107.64,38.42 104.71,35.64C101.78,32.86 98.32,30.72 94.3,29.23C90.29,27.74 85.9,26.99 81.14,26.99C76.38,26.99 72,27.74 67.98,29.23C63.97,30.72 60.5,32.86 57.57,35.64C54.95,38.13 52.85,41.1 51.27,44.54C51.04,42.07 50.46,39.93 49.53,38.13C48.3,35.75 46.63,33.86 44.54,32.45C42.45,31.05 40.01,30.04 37.23,29.44C34.45,28.84 31.51,28.54 28.42,28.54L4.87,28.54L4.87,89.42L18.28,89.42L18.28,65.08L24.9,65.08L37.63,89.42L53.71,89.42L38.24,63.71C42.54,62.91 45.81,61 48.04,57.99C48.14,57.85 48.23,57.7 48.33,57.56C48.31,58.03 48.3,58.5 48.3,58.98C48.3,63.85 49.12,68.27 50.75,72.22C52.38,76.17 54.66,79.54 57.59,82.32C60.51,85.1 63.98,87.24 68,88.73C72.01,90.22 76.4,90.97 81.16,90.97C85.92,90.97 90.3,90.22 94.32,88.73C98.33,87.24 101.8,85.1 104.73,82.32C107.65,79.54 109.93,76.17 111.57,72.22C111.79,71.69 111.99,71.14 112.18,70.59L112.18,89.42L125.59,89.42L125.59,65.08L132.21,65.08L144.94,89.42L161.02,89.42L145.53,63.71ZM36.39,50.81C35.67,51.73 34.77,52.4 33.68,52.83C32.59,53.26 31.37,53.52 30.03,53.6C28.68,53.69 27.41,53.73 26.2,53.73L18.29,53.73L18.29,39.89L27.06,39.89C28.26,39.89 29.5,39.98 30.76,40.15C32.02,40.32 33.14,40.65 34.11,41.14C35.08,41.63 35.89,42.33 36.52,43.25C37.15,44.17 37.47,45.4 37.47,46.95C37.47,48.6 37.11,49.89 36.39,50.81ZM98.74,66.85C97.85,69.23 96.58,71.29 94.91,73.04C93.25,74.79 91.26,76.15 88.93,77.13C86.61,78.11 84.01,78.59 81.15,78.59C78.28,78.59 75.69,78.1 73.37,77.13C71.05,76.16 69.06,74.79 67.39,73.04C65.73,71.29 64.45,69.23 63.56,66.85C62.67,64.47 62.23,61.85 62.23,58.98C62.23,56.17 62.67,53.56 63.56,51.15C64.45,48.74 65.72,46.67 67.39,44.92C69.05,43.17 71.04,41.81 73.37,40.83C75.69,39.86 78.28,39.37 81.15,39.37C84.02,39.37 86.61,39.86 88.93,40.83C91.25,41.8 93.24,43.17 94.91,44.92C96.57,46.67 97.85,48.75 98.74,51.15C99.63,53.56 100.07,56.17 100.07,58.98C100.07,61.85 99.63,64.47 98.74,66.85ZM143.68,50.81C142.96,51.73 142.06,52.4 140.97,52.83C139.88,53.26 138.66,53.52 137.32,53.6C135.97,53.69 134.7,53.73 133.49,53.73L125.58,53.73L125.58,39.89L134.35,39.89C135.55,39.89 136.79,39.98 138.05,40.15C139.31,40.32 140.43,40.65 141.4,41.14C142.37,41.63 143.18,42.33 143.81,43.25C144.44,44.17 144.76,45.4 144.76,46.95C144.76,48.6 144.4,49.89 143.68,50.81Z", style: { fill: 'rgb(32,40,38)', fillRule: 'nonzero' } }))),
h("span", { class: `flex-none items-center px-1` },
this.label,
this.acronym ? ' (' + this.acronym + ')' : '')));
}
get data() {
return this.rorData;
}
}
const urlRegex = new RegExp('^https?://spdx.org/licenses/[\\w.\\-+]+/?$', 'i');
class SPDXType extends GenericIdentifierType {
constructor() {
super(...arguments);
this.licenseData = null;
this.licenseId = '';
this.corsFallback = true;
this.corsProxy = 'https://corsproxy.io/?';
this.spdxBaseUrl = 'https://spdx.org/licenses';
this.fileFormat = 'json';
this.requestTimeout = 10000;
}
getSettingsKey() {
return 'SPDXType';
}
async