cartodb-4.42/lib/assets/javascripts/new-dashboard/pages/Data/CatalogDatasetData.vue

410 lines
11 KiB
Vue
Raw Normal View History

2024-04-06 13:25:13 +08:00
<template>
<div v-if="!isFetching" class="grid grid-cell u-flex__justify--center">
<div class="grid-cell grid-cell--col12 u-mt--28">
<div class="u-flex u-flex__justify--between title-container">
<h2 class="title is-caption is-txtMainTextColor">
Data sample
<transition name="fade">
<a
v-if="variables && variables.length > 0"
@click="scrollToVariables()"
class="is-small"
>View {{ numberColumns }} variables list</a
>
</transition>
</h2>
<div class="is-small text txtMainTextColor">
<span class="source u-flex u-flex__align--center" style="white-space:pre-wrap;" v-if="source"
>(*) Sample not available: this preview is for&nbsp;
<i class="is-semibold is-italic">{{ source }}</i></span
>
<span class="grey" v-else-if="numberRows > 0"
>First {{ numberRows }} rows</span
>
<span class="grey" v-else>Not available</span>
</div>
</div>
<div class="table-wrapper" ref="tableWrapper" v-if="columns.length">
<div
class="tooltip is-small"
:class="{ first: tooltip.isFirst, last: tooltip.isLast }"
v-if="tooltip.visible"
:style="{ left: tooltip.left + 'px' }"
>
<p class="text is-small">
<span class="is-semibold">Description:</span>
{{ tooltip.description }}
</p>
<p class="text is-small">
<span class="is-semibold">Type:</span> {{ tooltip.type }}
</p>
</div>
<div v-if="numberRows > 0" class="scrollable-table u-mt--24">
<table class="text is-small u-width--100">
<tr>
<th></th>
<th
@mousemove="showTooltip(value, $event)"
@mouseleave="hideTooltip"
v-for="value in columns"
:key="value"
>
{{ value }}
</th>
</tr>
<tr v-for="n in numberRows" :key="n">
<td class="is-semibold">{{ n - 1 }}</td>
<td v-for="sample of tableSample" :key="sample.column_name">
<template v-if="sample.column_name !== 'geom'">
<span
v-if="
sample.values[n - 1] !== null &&
sample.values[n - 1] !== undefined
"
>{{ sample.values[n - 1] }}</span
>
<span v-else class="is-txtLightGrey is-italic">null</span>
</template>
<template v-else>
<span>GeoJSON</span>
</template>
</td>
</tr>
</table>
</div>
</div>
<NotAvailable
v-else
:title="'Sample is not available'"
:description="
'This data sample cant be shown because the real dataset only contains a few rows.'
"
:contactUrl="getFormURL()"
:mode="'contact'"
></NotAvailable>
</div>
<transition name="fade">
<div
v-if="variables && variables.length > 0"
class="grid-cell--col12 u-mt--60"
ref="variablesSection"
>
<div class="u-flex u-flex__align--center u-flex__justify--between">
<h2 class="grid-cell title is-caption is-txtMainTextColor">
Variables
</h2>
<span class="is-txtMidGrey text is-small u-pr--10">{{numberColumns}} variables</span>
</div>
<ul class="u-mt--24 text f12 is-small is-txtMainTextColor">
<li class="grid title is-txtMidGrey header-row">
<div class="grid-cell grid-cell--col4">Column Name</div>
<div
class="grid-cell grid-cell--col7 grid-cell--col6--tablet grid-cell--col5--mobile"
>
Description
</div>
<div
class="grid-cell grid-cell--col1 grid-cell--col2--tablet grid-cell--col3--mobile"
>
Type
</div>
</li>
<li
class="grid info-row"
v-for="variable in variables"
:key="variable.slug"
>
<div class="grid-cell grid-cell--col4 is-semibold name-cell">
{{ variable.column_name }}
</div>
<div
class="grid-cell grid-cell--col7 grid-cell--col6--tablet grid-cell--col5--mobile"
>
{{ variable.description }}
</div>
<div
class="grid-cell grid-cell--col1 grid-cell--col2--tablet grid-cell--col3--mobile"
>
{{ variable.db_type }}
</div>
</li>
</ul>
</div>
</transition>
</div>
</template>
<script>
import { mapState } from 'vuex';
import NotAvailable from 'new-dashboard/components/Catalog/NotAvailable.vue';
import { formURL } from 'new-dashboard/utils/catalog/form-url';
import { sendCustomDimensions } from 'new-dashboard/utils/catalog/custom-dimensions-ga';
export default {
name: 'CatalogDatasetData',
components: {
NotAvailable
},
data () {
return {
tooltip: {
visible: false,
isFirst: false,
isLast: false,
left: 0,
description: null,
type: null
}
};
},
watch: {
dataset: {
handler (value) {
if (value && value.category_name) {
sendCustomDimensions(
value.category_name,
value.country_name,
value.is_public_data,
value.provider_name
);
}
},
immediate: true
}
},
computed: {
...mapState({
dataset: state => state.catalog.dataset,
variables: state => state.catalog.variables,
isFetching: state => state.catalog.isFetching
}),
isPublicWebsite () {
return !(this.$store.state.user && this.$store.state.user.id);
},
tableKey () {
if (this.dataset && this.dataset.summary_json) {
if (this.dataset.summary_json.ordered_glimpses) {
return 'ordered_glimpses';
} else if (this.dataset.summary_json.default_ordered_glimpses) {
return 'default_ordered_glimpses';
}
}
return null;
},
source () {
if (this.tableKey === 'default_ordered_glimpses') {
return this.dataset.summary_json[this.tableKey].source;
}
return null;
},
tableSample () {
if (this.tableKey && this.dataset.summary_json) {
const geom_column = { column_name: 'geom', values: Array(10) };
return [...this.dataset.summary_json[this.tableKey].tail, geom_column];
}
return [];
},
columns () {
return this.tableSample ? this.tableSample.map(t => t.column_name) : [];
},
numberRows () {
// return this.columns.length ? this.tableSample[this.columns[0]].length : 0;
return this.tableSample && this.tableSample.length > 0 ? this.tableSample[0].values.length : 0;
},
numberColumns () {
return this.variables ? this.variables.length : this.columns.length;
},
isGeography () {
return this.$route.params.type === 'geography';
}
},
methods: {
fetchVariables () {
this.$store.dispatch('catalog/fetchVariables', {
id: this.$route.params.datasetId,
type: this.$route.params.type
});
},
findVariableInfo (variableName) {
return this.variables.find(e => e.column_name === variableName);
},
showTooltip (variableName, event) {
let tooltipInfo = this.findVariableInfo(variableName);
if (tooltipInfo) {
let tableBoundingSize = this.$refs.tableWrapper.getBoundingClientRect();
this.tooltip.left =
event.target.getBoundingClientRect().left -
this.$refs.tableWrapper.getBoundingClientRect().left +
event.offsetX;
if (this.tooltip.left < 140) {
this.tooltip.isFirst = true;
this.tooltip.left -= 26;
} else if (tableBoundingSize.width - this.tooltip.left < 120) {
this.tooltip.isLast = true;
this.tooltip.left += 26;
} else {
this.tooltip.isFirst = false;
this.tooltip.isLast = false;
}
this.tooltip.description = tooltipInfo.description;
this.tooltip.type = tooltipInfo.db_type;
this.tooltip.visible = true;
} else {
this.hideTooltip();
}
},
hideTooltip () {
this.tooltip.visible = false;
this.tooltip.isFirst = false;
this.tooltip.isLast = false;
},
getFormURL () {
return formURL(this.dataset);
},
scrollToVariables () {
window.scrollTo({
top: this.$refs.variablesSection.offsetTop,
left: 0,
behavior: 'smooth'
});
}
},
mounted () {
this.fetchVariables();
}
};
</script>
<style scoped lang="scss">
@import 'new-dashboard/styles/variables';
.title a {
margin-left: 26px;
}
a {
cursor: pointer;
}
.scrollable-table {
width: 100%;
overflow: auto;
td,
th {
padding: 12px 24px 12px 8px;
white-space: nowrap;
}
tr:nth-child(even) {
background-color: $color-primary--soft;
}
th {
position: relative;
color: $link__color;
text-align: left;
}
}
.info-row {
padding: 14px 0;
border-bottom: 1px solid $blue--100;
}
.name-cell {
overflow: hidden;
text-overflow: ellipsis;
}
.header-row {
padding-bottom: 12px;
border-bottom: 2px solid $blue--100;
}
.tooltip-container {
position: absolute;
z-index: 2;
bottom: 100%;
margin-left: 32px;
padding-bottom: 8px;
}
.table-wrapper {
position: relative;
}
.tooltip {
position: absolute;
bottom: calc(100% + 8px);
width: 300px;
padding: 12px 16px 8px;
transform: translateX(-50%);
border: 1px solid $border-color;
border-radius: 4px;
background-color: #fff;
word-break: break-word;
&::before {
content: '';
position: absolute;
bottom: -8px;
left: calc(50% - 12px); // To compensate right extra padding
width: 14px;
height: 14px;
transform: rotate(45deg);
border: 1px solid $neutral--200;
border-top: none;
border-left: none;
border-radius: 2px;
background-color: #fff;
}
&.first {
transform: translateX(0);
&::before {
left: 12px;
}
}
&.last {
transform: translateX(-100%);
&::before {
right: 24px;
left: auto;
}
}
}
.grey {
opacity: 0.48;
}
.source {
&:after {
content: url('../../assets/icons/catalog/interface-alert-triangle.svg');
margin-left: 12px;
}
}
@media (max-width: $layout-mobile) {
.title-container {
flex-wrap: wrap;
>h2 {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
>div {
width: 100%;
text-align: right;
}
}
}
</style>