Initial commit

This commit is contained in:
zhongjin
2020-06-15 10:58:47 +08:00
commit 4f1dfe7564
8590 changed files with 1516878 additions and 0 deletions
@@ -0,0 +1,257 @@
<template>
<section class="catalog">
<div class="container grid">
<div class="full-width">
<SectionTitle class="grid-cell">
<template slot="icon">
<img src="../../assets/icons/section-title/catalog.svg" width="18" height="20" />
</template>
<template slot="title">
<span>{{ $t('CatalogPage.header') }}</span>
</template>
</SectionTitle>
</div>
</div>
<div class="catalog__filter container grid" action="">
<div class="grid-cell grid-cell--col6 catalog__filter--dropdown">
<CatalogDropdown ref="dropdownCategories"
:title="$t('CatalogDropdown.category.title')"
:placeholder="$t('CatalogDropdown.category.placeholder')"
:options="categories"
:disabled="true"
@selected="getCountries"
@reset="resetCategory">
<template slot="extra">
<span v-html="$t('CatalogDropdown.category.extra')" class="text is-small"></span>
</template>
</CatalogDropdown>
</div>
<div class="grid-cell grid-cell--col6 catalog__filter--dropdown">
<CatalogDropdown ref="dropdownCountries"
:title="$t('CatalogDropdown.country.title')"
:placeholder="$t('CatalogDropdown.country.placeholder')"
:options="countries"
:disabled="true"
:limitHeight="true"
@open="false"
@selected="getDatasets"
@reset="resetCountry">
<template slot="extra">
<span v-html="$t('CatalogDropdown.country.extra')" class="text is-small"></span>
</template>
</CatalogDropdown>
</div>
</div>
<!-- Datasets list -->
<div class="container grid" v-if="datasets.length > 0">
<div class="grid-cell grid-cell--col12">
<CatalogListHeader :order="appliedOrder" :orderDirection="appliedOrderDirection" @changeOrder="applyOrder"></CatalogListHeader>
<ul v-if="!isFetchingDatasets" >
<li v-for="(dataset) in currentPageDatasets" :key="dataset.id">
<CatalogCard :dataset="dataset"></CatalogCard>
</li>
</ul>
<ul v-else>
<li v-for="(n) in resultsPerPage" :key="n">
<CatalogFakeCard />
</li>
</ul>
</div>
</div>
<Pagination v-if="shouldShowPagination" :page=currentPage :numPages=numPages @pageChange="goToPage"></Pagination>
</section>
</template>
<script>
import { mapState } from 'vuex';
import sendCustomEvent from 'new-dashboard/utils/send-custom-event';
import SectionTitle from 'new-dashboard/components/SectionTitle';
import CatalogListHeader from 'new-dashboard/components/Catalog/CatalogListHeader';
import CatalogCard from 'new-dashboard/components/Catalog/CatalogCard';
import CatalogFakeCard from 'new-dashboard/components/Catalog/CatalogFakeCard';
import CatalogDropdown from 'new-dashboard/components/Catalog/CatalogDropdown';
import Pagination from 'new-dashboard/components/Pagination';
export default {
name: 'CatalogPage',
components: {
SectionTitle,
CatalogListHeader,
CatalogCard,
CatalogFakeCard,
CatalogDropdown,
Pagination
},
beforeRouteUpdate (to, from, next) {
sendCustomEvent('catalogSelectCategory', {
catalogSelectedCategory: to.query.category
});
sendCustomEvent('catalogSelectCountry', {
catalogSelectedCountry: to.query.country
});
next();
},
mounted () {
const category = this.$route.query.category;
const country = this.$route.query.country;
if (category && country) {
this.setUrlOptions();
} else {
this.getCategories();
}
},
computed: {
...mapState({
numPages: state => state.catalog.numPages,
currentPage: state => state.catalog.page,
datasets: state => state.catalog.list,
categories: state => state.catalog.categories,
countries: state => state.catalog.countries,
isFetchingDatasets: state => state.catalog.isFetching,
numResults: state => state.catalog.umResults,
resultsPerPage: state => state.catalog.resultsPerPage,
appliedOrder: state => state.catalog.order,
appliedOrderDirection: state => state.catalog.orderDirection
}),
shouldShowPagination () {
return !this.isFetchingDatasets && this.numPages > 1;
},
currentPageDatasets () {
return this.datasets.slice(((this.currentPage - 1) * this.resultsPerPage), (this.currentPage * this.resultsPerPage));
}
},
methods: {
goToPage (page) {
window.scroll({ top: 0, left: 0 });
this.$router.push({
name: 'catalog',
params: this.$route.params,
query: { ...this.$route.query, page }
});
},
applyOrder (orderParams) {
this.$router.push({
name: 'catalog',
params: this.$route.params,
query: {
...this.$route.query,
page: 1,
order: orderParams.order,
order_direction: orderParams.direction
}
});
this.$store.dispatch('catalog/order', orderParams);
},
getCategories () {
this.clearList();
this.$store.dispatch('catalog/fetchCategories')
.then(
() => {
this.$refs.dropdownCategories.enableDropdown();
this.$refs.dropdownCategories.openDropdown();
},
(err) => {
this.$refs.dropdownCategories.enableDropdown();
this.$refs.dropdownCategories.setError(err);
}
);
},
getCountries (category) {
this.clearList();
this.$store.dispatch('catalog/fetchCountries', category)
.then(
() => {
this.$refs.dropdownCountries.clearInput();
this.$refs.dropdownCountries.enableDropdown();
window.scroll({ top: 0, left: 0 });
},
(err) => {
this.$refs.dropdownCountries.enableDropdown();
this.$refs.dropdownCountries.setError(err);
}
);
},
getDatasets (country) {
this.$store.dispatch('catalog/fetchDatasets', {
category: this.$refs.dropdownCategories.selected,
country
}).then(
() => {
this.$router.push({
name: 'catalog',
query: {
category: this.$refs.dropdownCategories.selected,
country
}
});
window.scroll({ top: 0, left: 0 });
},
(err) => {
this.$refs.dropdownCategories.setError(err);
this.$refs.dropdownCountries.setError(err);
}
);
},
resetCategory () {
this.$refs.dropdownCountries.clearInput();
this.$refs.dropdownCountries.disableDropdown();
this.clearList();
},
resetCountry () {
this.clearList();
},
clearList () {
this.$store.dispatch('catalog/clearList');
},
setUrlOptions () {
const category = this.$route.query.category;
const country = this.$route.query.country;
const promiseDatasets = this.$store.dispatch('catalog/fetchDatasets', {
category: category,
country: country
});
const promiseCategories = this.$store.dispatch('catalog/fetchCategories');
const promiseCountires = this.$store.dispatch('catalog/fetchCountries', category);
Promise.all([promiseDatasets, promiseCategories, promiseCountires])
.then(
() => {
if (this.datasets.length > 0) {
this.$refs.dropdownCategories.setInput(category);
this.$refs.dropdownCountries.setInput(country);
} else {
this.getCategories();
this.$router.push({ name: 'catalog' });
}
},
() => {
this.$router.push({ name: 'catalog' });
}
);
}
}
};
</script>
<style scoped lang="scss">
@import 'new-dashboard/styles/variables';
.catalog {
min-height: 640px;
margin-top: 64px;
&__filter {
justify-content: space-between;
height: 168px;
&--dropdown {
position: relative;
}
}
}
.full-width {
width: 100%;
}
</style>
@@ -0,0 +1,176 @@
<template>
<Page class="page--data">
<SecondaryNavigation>
<a class="catalogDetail__back title is-small" href="javascript:history.back()">
<img class="catalogDetail__back--icon" svg-inline src="../../assets/icons/common/back.svg"/>
<span>{{ $t('CatalogDetailPage.back') }}</span>
</a>
</SecondaryNavigation>
<section v-if="!isFetchingDatasets" class="catalogDetail">
<div class="container grid">
<div class="full-width">
<SectionTitle class="grid-cell">
<template slot="icon">
<img src="../../assets/icons/section-title/data.svg" width="18" height="20" />
</template>
<template slot="title">
<span>{{ dataset.name }}</span>
</template>
<template slot="actionButton">
<CatalogRequestSuccess v-if="hasBeenSuccesfullyRequested"></CatalogRequestSuccess>
<button v-else class="button is-primary" @click="requestDataset">{{ $t('CatalogDetailPage.requestDataset') }}</button>
</template>
</SectionTitle>
<div class="catalogDetail__description grid-cell">
<span class="text is-caption">
{{ dataset.description }}
</span>
</div>
</div>
<div class="grid-cell grid-cell--col6">
<h4 class="catalogDetail__label title is-caption">{{ $t('CatalogDetailPage.variableGroups') }}</h4>
<ul v-for="variable in dataset.variable_name" :key="variable" class="catalogDetail__list">
<li class="text is-caption catalogDetail__list--item">{{variable}}</li>
</ul>
</div>
<div class="grid-cell grid-cell--col3">
<h4 class="catalogDetail__label title is-caption">{{ $t('CatalogDetailPage.category') }}</h4>
<div class="u-flex">
<div :class="`catalog__icon catalog__icon--${getCSSModifier(dataset.category)}`"></div>
<p class="text is-caption">{{ dataset.category }}</p>
</div>
</div>
<div class="grid-cell grid-cell--col3">
<h4 class="catalogDetail__label title is-caption">{{ $t('CatalogDetailPage.country') }}</h4>
<div class="u-flex">
<div :class="`catalog__icon catalog__icon--${getCSSModifier(dataset.country)}`"></div>
<p class="text is-caption">{{ dataset.country }}</p>
</div>
</div>
</div>
</section>
</Page>
</template>
<script>
import { mapState } from 'vuex';
import toObject from 'new-dashboard/utils/to-object';
import getCSSModifier from 'new-dashboard/utils/get-css-modifier';
import sendCustomEvent from 'new-dashboard/utils/send-custom-event';
import Page from 'new-dashboard/components/Page';
import SecondaryNavigation from 'new-dashboard/components/SecondaryNavigation';
import SectionTitle from 'new-dashboard/components/SectionTitle';
import CatalogRequestSuccess from 'new-dashboard/components/Catalog/CatalogRequestSuccess';
export default {
name: 'CatalogDetail',
components: {
Page,
SecondaryNavigation,
SectionTitle,
CatalogRequestSuccess
},
data: function () {
return {
hasBeenSuccesfullyRequested: false
};
},
computed: {
...mapState({
user: state => state.user,
datasets: state => state.catalog.list,
isFetchingDatasets: state => state.catalog.isFetching,
dataset (state) {
if (this.isFetchingDatasets) {
return {};
}
const datasetList = toObject(this.datasets, 'id');
const selectedDataset = datasetList[this.$route.params.id];
sendCustomEvent('catalogSelectDataset', {
catalogSelectedDataset: selectedDataset.name
});
return selectedDataset;
}
})
},
methods: {
getCSSModifier,
requestDataset () {
this.$store.dispatch('catalog/requestDataset', { user: this.user, dataset: this.dataset })
.then(
() => {
this.hasBeenSuccesfullyRequested = true;
this.sendCustomCatalogEvents(this.dataset);
}
);
},
sendCustomCatalogEvents (dataset) {
sendCustomEvent('catalogRequestDataset', {
catalogRequestedDataset: dataset.name
});
sendCustomEvent('catalogRequestCountry', {
catalogRequestedCountry: dataset.country
});
sendCustomEvent('catalogRequestCategory', {
catalogRequestedCategory: dataset.category
});
}
}
};
</script>
<style scoped lang="scss">
@import "new-dashboard/styles/variables";
.page--data {
padding-top: 64px;
}
.full-width {
width: 100%;
}
.catalogDetail {
margin-top: 64px;
&__back {
display: flex;
align-items: center;
padding: 24px 0;
&--icon {
width: 7px;
height: 12px;
margin-right: 8px;
}
}
&__description {
margin-bottom: 64px;
}
&__label {
margin-bottom: 24px;
}
&__list {
list-style-position: inside;
list-style-type: disc;
&--item {
margin-bottom: 16px;
}
}
}
.catalog__icon {
width: 24px;
height: 24px;
margin-right: 12px;
background-size: contain;
}
</style>
@@ -0,0 +1,57 @@
<template>
<Page class="page--data">
<SecondaryNavigation>
<div class="tabs">
<router-link :to="{ name: 'datasets' }" class="tabs__item title is-small" exact active-class="is-active" :class="{'is-active': isDatasetPage }">
<span>{{ $t('DataPage.tabs.yourDatasets') }}</span>
</router-link>
<router-link :to="{ name: 'catalog' }" class="tabs__item title is-small" active-class="is-active">
<span>{{ $t('DataPage.tabs.catalog') }}</span>
</router-link>
</div>
</SecondaryNavigation>
<router-view></router-view>
</Page>
</template>
<script>
import Page from 'new-dashboard/components/Page';
import SecondaryNavigation from 'new-dashboard/components/SecondaryNavigation';
import { isAllowed } from 'new-dashboard/core/configuration/filters';
export default {
name: 'DataPage',
components: {
Page,
SecondaryNavigation
},
computed: {
isDatasetPage () {
return isAllowed(this.$route.params.filter);
}
}
};
</script>
<style scoped lang="scss">
@import "new-dashboard/styles/variables";
.tabs {
display: flex;
&__item {
margin-right: 48px;
padding: 24px 0 20px;
border-bottom: 4px solid transparent;
background: none;
color: $color-primary;
text-transform: none;
&.is-active {
border-color: currentColor;
color: $black;
}
}
}
</style>
@@ -0,0 +1,142 @@
<template>
<section class="datasets-section">
<StickySubheader :is-visible="Boolean(selectedDatasets.length && isScrollPastHeader)">
<h2 class="title is-caption">
{{ $t('BulkActions.selected', {count: selectedDatasets.length}) }}
</h2>
<DatasetBulkActions
:selectedDatasets="selectedDatasets"
:areAllDatasetsSelected="areAllDatasetsSelected"
@selectAll="selectAll"
@deselectAll="deselectAll"></DatasetBulkActions>
</StickySubheader>
<DatasetsList
ref="datasetsList"
class="grid__content"
:hasBulkActions="true"
:canHoverCard="true"
:maxVisibleDatasets="maxVisibleDatasets"
@applyFilter="applyFilter"
@applyOrder="applyOrder"
@selectionChange="updateSelected" />
<Pagination v-if="shouldShowPagination" :page=currentPage :numPages=numPages @pageChange="goToPage"></Pagination>
</section>
</template>
<script>
import { mapState } from 'vuex';
import { checkFilters } from 'new-dashboard/router/hooks/check-navigation';
import Pagination from 'new-dashboard/components/Pagination';
import DatasetBulkActions from 'new-dashboard/components/BulkActions/DatasetBulkActions.vue';
import StickySubheader from '../../components/StickySubheader';
import DatasetsList from 'new-dashboard/components/DatasetsList.vue';
export default {
name: 'DatasetsPage',
components: {
DatasetBulkActions,
StickySubheader,
Pagination,
DatasetsList
},
data () {
return {
isScrollPastHeader: false,
maxVisibleDatasets: 12,
selectedDatasets: []
};
},
beforeMount () {
if (this.$store.getters['user/isViewer']) {
// Redirect to shared datasets page if user is viewer
return this.$router.replace({ name: 'datasets', params: { filter: 'shared' } });
}
},
mounted () {
this.stickyScrollPosition = this.getHeaderBottomPageOffset();
this.$onScrollChange = this.onScrollChange.bind(this);
document.addEventListener('scroll', this.$onScrollChange, { passive: true });
},
beforeDestroy () {
document.removeEventListener('scroll', this.$onScrollChange, { passive: true });
},
beforeRouteUpdate (to, from, next) {
checkFilters('datasets', 'datasets', to, from, next);
},
computed: {
...mapState({
numPages: state => state.datasets.numPages,
currentPage: state => state.datasets.page,
datasets: state => state.datasets.list,
isFetchingDatasets: state => state.datasets.isFetching,
numResults: state => state.datasets.metadata.total_entries
}),
isNotificationVisible () {
return this.$store.getters['user/isNotificationVisible'];
},
areAllDatasetsSelected () {
return Object.keys(this.datasets).length === this.selectedDatasets.length;
},
shouldShowPagination () {
return !this.isFetchingDatasets && this.numResults > 0 && this.numPages > 1;
}
},
methods: {
updateSelected (selected) {
this.selectedDatasets = selected;
},
goToPage (page) {
this.deselectAll();
window.scroll({ top: 0, left: 0 });
this.$router.push({
name: 'datasets',
params: this.$route.params,
query: {...this.$route.query, page}
});
},
applyFilter (filter) {
this.$router.push({ name: 'datasets', params: { filter } });
},
applyOrder (orderParams) {
this.deselectAll();
this.$router.push({
name: 'datasets',
params: this.$route.params,
query: {
...this.$route.query,
page: 1,
order: orderParams.order,
order_direction: orderParams.direction
}
});
},
selectAll () {
this.$refs.datasetsList.selectAll();
},
deselectAll () {
this.$refs.datasetsList.deselectAll();
},
onScrollChange () {
this.isScrollPastHeader = window.pageYOffset > this.stickyScrollPosition;
},
getHeaderBottomPageOffset () {
const headerContainer = this.$refs.datasetsList.getHeaderContainer();
const headerBoundingClientRect = headerContainer.$el.getBoundingClientRect();
const notificationHeight = this.isNotificationVisible ? 60 : 0;
return headerBoundingClientRect.top - notificationHeight;
}
}
};
</script>
<style scoped lang="scss">
@import 'new-dashboard/styles/variables';
.datasets-section {
margin-top: 64px;
}
</style>