- added DataMetricsBadge.vue component for showing metrics downloads, views and citations

- npm updates
- new major version typescript 5
This commit is contained in:
Arno Kaimbacher 2023-06-15 13:51:53 +02:00
parent f1fbc2d917
commit cfc81f2d90
17 changed files with 1191 additions and 612 deletions

View file

@ -0,0 +1,136 @@
import axios from "axios";
import { ComponentBase, Vue, Prop } from "vue-facing-decorator";
import testLogo from "@/assets/datacite/testLogo.vue";
const APIURL = "https://api.datacite.org";
@ComponentBase({
name: "BaseWidget",
components: {
testLogo,
},
})
export default class BaseWidget extends Vue {
@Prop({
type: Object,
required: false,
validator(value) {
const keys = Object.keys(value);
return ["citations", "views", "downloads"].some((r) => keys.includes(r));
},
})
dataInput = {};
@Prop({
type: String,
required: true,
validator(value) {
return value.match(/^10\.\d{4,5}\/[-._;()/:a-zA-Z0-9*~$=]+/);
},
})
doi!: string;
@Prop({
type: String,
required: true,
validator(value) {
return ["small", "medium", "datacite", "regular"].indexOf(value) > -1;
},
})
display!: string;
public views = "";
public citations = "";
public downloads = "";
private datacite: string | number = "";
private loading = false;
private errored = false;
get url() {
return `${APIURL}/graphql`;
}
get link() {
return `https://commons.datacite.org/doi.org/${this.doi}`;
}
// get dataInputApi() {
// return this.viewsDistribution;
// }
get alt() {
return `${Number(this.views)} Views ${Number(this.downloads)} Downloads ${Number(this.citations)} Citations from DataCite`;
}
get tooltip() {
let message = "";
message += `${this.doi} `;
message += this.datacite ? `${this.datacite} from DataCite ` : "";
return message;
}
getMetrics() {
if (this.isLocal() === false) {
this.requestMetrics();
} else {
this.grabMetrics(this.dataInput);
}
return true;
}
public pluralize(value: number | string = 0, label: string) {
if (value === 1) {
return `${value.toLocaleString("en-us")} ${label}`;
}
return `${value.toLocaleString("en-us")} ${label}s`;
}
public formatNumbers(num: any) {
if (num < 1e3) return num;
if (num >= 1e3 && num < 1e6) return `${+(num / 1e3).toFixed(1)}K`;
if (num >= 1e6 && num < 1e9) return `${+(num / 1e6).toFixed(1)}M`;
if (num >= 1e9 && num < 1e12) return `${+(num / 1e9).toFixed(1)}B`;
if (num >= 1e12) return `${+(num / 1e12).toFixed(1)}T`;
return num;
}
private isLocal(): boolean {
if (this.dataInput == null && typeof this.doi !== "undefined") {
return false;
}
return true;
}
private grabMetrics(data: any) {
this.views = (this.formatNumbers(data.views) as string) || "";
this.downloads = (this.formatNumbers(data.downloads) as string) || "";
this.citations = (this.formatNumbers(data.citations) as string) || "";
this.datacite = this.formatNumbers(data.datacite) || "";
}
private requestMetrics() {
axios({
url: this.url,
method: "post",
data: {
query: `
{
counts: work(id: "${this.doi}") {
id
views: viewCount
downloads: downloadCount
citations: citationCount
}
}
`,
},
})
.then((response) => {
this.grabMetrics(response.data.data.counts);
})
.catch((error) => {
console.log(error);
this.errored = true;
})
.finally(() => {
this.loading = false;
});
}
}

View file

@ -0,0 +1,43 @@
<script lang="ts">
import BaseWidget from "./BaseWidget";
export default BaseWidget;
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
/* https://stackoverflow.com/questions/12675622/script1028-expected-identifier-string-or-number */
.icon-metrics {
width: 17px;
height: 17px;
display: inline-block;
}
a {
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
font-family: "Cairo", "Helvetica", Arial, sans-serif;
vertical-align: top;
}
a {
color: #222222;
-webkit-transition: all 150ms linear;
-moz-transition: all 150ms linear;
-o-transition: all 150ms linear;
-ms-transition: all 150ms linear;
transition: all 150ms linear;
text-decoration: none;
}
a:hover,
a:focus {
color: #222222;
text-decoration: none;
}
a:focus,
a:active {
outline: 0;
}
a,
a:visited {
text-decoration: none;
}
</style>

View file

@ -0,0 +1,63 @@
<template>
<div v-bind:title="'Metrics for DOI: ' + doi">
<div v-if="doi">
<!-- <div v-if="display == 'small'"> -->
<SmallWidget v-bind:doi="doi" v-bind:display="display" :data-input="dataObject" />
<!-- </div> -->
</div>
<a v-else>There is no DOI</a>
</div>
</template>
<script lang="ts">
import SmallWidget from "./SmallWidget.vue";
// import MediumWidget from "./MediumWidget.vue";
// import DataCiteWidget from "./DataCiteWidget.vue";
// import RegularWidget from "./RegularWidget.vue";
import { Vue, Component, Prop } from "vue-facing-decorator";
@Component({
name: "DataMetricsBadge",
components: {
// MediumWidget,
SmallWidget,
// DataCiteWidget,
// RegularWidget,
},
})
export default class DataMetricsBadge extends Vue {
name = "DataMetricsBadge";
funtional = true;
@Prop({
type: Object,
})
dataInput!: object;
@Prop({
type: String,
default: "",
})
doi!: string;
@Prop({
type: String,
required: false,
validator(value) {
return ["small", "medium", "datacite", "regular"].indexOf(value) > -1;
},
default: "small",
})
display!: string;
get dataObject() {
// if (typeof this.dataInput !== "undefined") {
return this.dataInput;
// }
// return null;
}
}
</script>
<style scoped></style>

View file

@ -0,0 +1,143 @@
<template>
<div class="small-container">
<a v-bind:href="link">
<div class="d-flex">
<!-- <test-logo v-bind:class="'svglogo'" /> -->
<div v-bind:class="['', '0', 0].includes(citations) == false ? activeClass : inactiveClass">Citations</div>
<div class="p-2 counts" v-bind:title="pluralize(citations, 'Citation')">
{{ formatNumbers(["", "0", 0].includes(citations) == false ? citations : "&nbsp;&nbsp;") }}
</div>
<div class="p-2 span" />
<div v-if="parseInt(views) + parseInt(downloads) > 0" class="d-flex">
<div v-bind:class="['', '0', 0].includes(views) == false ? activeClass : inactiveClass">Views</div>
<div class="p-2 counts" v-bind:title="pluralize(views, 'View')">
{{ formatNumbers(["", "0", 0].includes(views) == false ? views : "&nbsp;&nbsp;") }}
</div>
<div class="p-2 span" />
<div v-bind:class="['', '0', 0].includes(downloads) == false ? activeClass : inactiveClass">Downloads</div>
<div class="p-2 counts" v-bind:title="pluralize(downloads, 'Download')">
{{ formatNumbers(["", "0", 0].includes(downloads) == false ? downloads : "&nbsp;&nbsp;") }}
</div>
<div class="p-2 span" />
</div>
</div>
</a>
</div>
</template>
<script lang="ts">
import { Component } from "vue-facing-decorator";
import BaseWidget from "./BaseWidget.vue";
// import testLogo from "@/assets/datacite/testLogo.vue";
@Component({
name: "SmallWidget",
})
export default class SmallWidget extends BaseWidget {
public activeClass = "p-2 label";
public inactiveClass = "p-2 label-empty";
public mounted(): void {
this.getMetrics();
}
}
</script>
<style scoped>
div.label {
background-color: rgba(0, 89, 173);
display: table;
width: 8%;
color: white;
font-size: 12px;
border-style: solid;
/* font-weight: bold; */
border-color: rgba(0, 89, 173);
border-width: thin;
/* text-align: center; */
height: 15px;
}
div.logo {
min-width: 100px;
padding: 0px 0px 0px 0px;
height: 15px;
}
.svglogo {
color: #455a64;
fill: #455a64;
display: table;
width: 20%;
min-width: 100px;
}
div.counts {
background-color: white;
display: table;
/* font-size: 2.5vh; */
font-size: 12px;
width: 6%;
border-style: solid;
font-weight: bold;
border-color: #78909c;
border-width: thin;
text-align: center;
height: 15px;
}
div.span {
background-color: white;
display: table;
font-size: 2.5vh;
width: 2%;
}
.small-container {
width: 100%;
min-width: 400px;
max-width: 400px;
/*
max-height: 15px;
padding-right: 15px;
padding-left: 15px; */
margin-right: auto;
margin-left: auto;
/* font-family: Arial, Helvetica, sans-serif; */
}
.d-flex {
display: -ms-flexbox !important;
display: flex !important;
}
.p-2 {
padding: 0.1rem !important;
}
.label-empty {
background-color: #78909c;
display: table;
width: 8%;
color: white;
/* font-size: 2.5vh; */
font-size: 12px;
border-style: solid;
font-weight: bold;
border-color: #78909c;
border-width: thin;
text-align: center;
height: 15px;
}
.row {
display: -ms-flexbox;
display: flex;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
margin-right: -15px;
margin-left: -15px;
}
</style>