cartodb-4.42/lib/assets/javascripts/new-dashboard/components/MapsList.vue

368 lines
12 KiB
Vue
Raw Normal View History

2024-04-06 13:25:13 +08:00
<template>
<div class="container grid">
<div class="full-width">
<SectionTitle class="grid-cell" :showActionButton="!selectedMaps.length" ref="headerContainer">
<template slot="icon">
<img src="../assets/icons/section-title/map.svg">
</template>
<template slot="title">
<VisualizationsTitle
:defaultTitle="$t(`MapsPage.header.title['${appliedFilter}']`)"
:selectedItems="selectedMaps.length"/>
</template>
<template slot="warning">
<NotificationBadge type="warning" v-if="shouldShowLimitsWarning">
<div class="warning">
<span v-if="isOutOfPublicMapsQuota && !isOutOfPrivateMapsQuota" class="is-bold" v-html="$t('MapsPage.header.warning.counter', { counter: `${publicMapsCount}/${publicMapsQuota}`, type: `public` })"></span>
<span v-if="isOutOfPrivateMapsQuota && !isOutOfPublicMapsQuota" class="is-bold" v-html="$t('MapsPage.header.warning.counter', { counter: `${privateMapsCount}/${privateMapsQuota}`, type: `private` })"></span>
<span v-if="isOutOfPublicMapsQuota && isOutOfPrivateMapsQuota" class="is-bold" v-html="$t('MapsPage.header.warning.doubleCounter', { publicCounter: `${publicMapsCount}/${publicMapsQuota}`, privateCounter: `${privateMapsCount}/${privateMapsQuota}`})"></span>
<span v-html="$t('MapsPage.header.warning.upgrade', { path: upgradeUrl })"></span>
</div>
</NotificationBadge>
</template>
<template slot="dropdownButton">
<MapBulkActions
:selectedMaps="selectedMaps"
:areAllMapsSelected="areAllMapsSelected"
v-if="hasBulkActions && selectedMaps.length"
@selectAll="selectAll"
@deselectAll="deselectAll"></MapBulkActions>
<SettingsDropdown
section="maps"
v-if="!selectedMaps.length"
:filter="appliedFilter"
:order="appliedOrder"
:orderDirection="appliedOrderDirection"
:metadata="mapsMetadata"
@filterChanged="applyFilter">
<img svg-inline src="../assets/icons/common/filter.svg">
</SettingsDropdown>
<div class="mapcard-view-mode" @click="toggleViewMode" v-if="shouldShowViewSwitcher">
<img svg-inline src="../assets/icons/common/compactMap.svg" v-if="!isCondensed">
<img svg-inline src="../assets/icons/common/standardMap.svg" v-if="isCondensed">
</div>
</template>
<template slot="actionButton" v-if="!isFirstTimeViewingDashboard && !selectedMaps.length">
<CreateButton visualizationType="maps" :disabled="!canCreateMaps">{{ $t(`MapsPage.createMap`) }}</CreateButton>
</template>
</SectionTitle>
<div class="grid-cell" v-if="initialState">
<CreateMapCard></CreateMapCard>
</div>
<slot name="navigation"></slot>
<div
v-if="shouldShowListHeader"
class="grid-cell grid-cell--noMargin grid-cell--col12 grid__head--sticky"
:class="{ 'has-user-notification': isNotificationVisible }">
<CondensedMapHeader
:order="appliedOrder"
:orderDirection="appliedOrderDirection"
@orderChanged="applyOrder"
v-if="shouldShowListHeader">
</CondensedMapHeader>
</div>
<ul :class="[isCondensed ? 'grid grid-cell' : 'grid']" v-if="isFetchingMaps">
<li :class="[isCondensed ? condensedCSSClasses : cardCSSClasses]" v-for="n in maxVisibleMaps" :key="n">
<MapCardFake :condensed="isCondensed"></MapCardFake>
</li>
</ul>
<ul :class="[isCondensed ? 'grid grid-column grid-cell' : 'grid']" v-if="!isFetchingMaps && currentEntriesCount > 0">
<li v-for="map in maps" :class="[isCondensed ? condensedCSSClasses : cardCSSClasses]" :key="map.id">
<MapCard
:condensed="isCondensed"
:visualization="map"
:isSelected="isMapSelected(map)"
:selectMode="isSomeMapSelected"
:canHover="canHoverCard"
@toggleSelection="toggleSelected"
@contentChanged="onContentChanged">
</MapCard>
</li>
</ul>
<EmptyState
:text="emptyStateText"
v-if="emptyState">
<img svg-inline src="../assets/icons/common/compass.svg">
</EmptyState>
</div>
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex';
import CreateButton from 'new-dashboard/components/CreateButton.vue';
import CreateMapCard from 'new-dashboard/components/CreateMapCard';
import EmptyState from 'new-dashboard/components/States/EmptyState';
import InitialState from 'new-dashboard/components/States/InitialState';
import MapBulkActions from 'new-dashboard/components/BulkActions/MapBulkActions.vue';
import MapCard from 'new-dashboard/components/MapCard/MapCard.vue';
import CondensedMapHeader from 'new-dashboard/components/MapCard/CondensedMapHeader.vue';
import MapCardFake from 'new-dashboard/components/MapCard/fakes/MapCardFake';
import SectionTitle from 'new-dashboard/components/SectionTitle';
import VisualizationsTitle from 'new-dashboard/components/VisualizationsTitle';
import NotificationBadge from 'new-dashboard/components/NotificationBadge';
import SettingsDropdown from 'new-dashboard/components/Settings/Settings';
import { shiftClick } from 'new-dashboard/utils/shift-click.service.js';
export default {
name: 'MapsList',
props: {
maxVisibleMaps: {
type: Number,
default: 12
},
hasBulkActions: {
type: Boolean,
default: false
},
canHoverCard: {
type: Boolean,
default: false
},
canChangeViewMode: {
type: Boolean,
default: false
},
isCondensedDefault: {
type: Boolean,
default: true
}
},
components: {
CreateButton,
CreateMapCard,
EmptyState,
SettingsDropdown,
MapBulkActions,
MapCard,
CondensedMapHeader,
MapCardFake,
SectionTitle,
VisualizationsTitle,
NotificationBadge,
InitialState
},
data () {
return {
selectedMaps: [],
cardCSSClasses: 'grid-cell grid-cell--col4 grid-cell--col6--tablet grid-cell--col12--mobile map-element',
condensedCSSClasses: 'card-condensed',
isCondensed: this.isCondensedDefault,
lastCheckedItem: null
};
},
computed: {
...mapState({
appliedFilter: state => state.maps.filterType,
appliedOrder: state => state.maps.order,
appliedOrderDirection: state => state.maps.orderDirection,
maps: state => state.maps.list,
mapsMetadata: state => state.maps.metadata,
isFetchingMaps: state => state.maps.isFetching,
currentEntriesCount: state => state.maps.metadata.total_entries,
filterType: state => state.maps.filterType,
totalUserEntries: state => state.maps.metadata.total_user_entries,
totalShared: state => state.maps.metadata.total_shared,
isFirstTimeViewingDashboard: state => state.config.isFirstTimeViewingDashboard,
upgradeUrl: state => state.config.upgrade_url
}),
...mapGetters({
publicMapsQuota: 'user/publicMapsQuota',
publicMapsCount: 'user/publicMapsCount',
isOutOfPublicMapsQuota: 'user/isOutOfPublicMapsQuota',
privateMapsQuota: 'user/privateMapsQuota',
privateMapsCount: 'user/privateMapsCount',
isOutOfPrivateMapsQuota: 'user/isOutOfPrivateMapsQuota',
canCreateMaps: 'user/canCreateMaps'
}),
areAllMapsSelected () {
return Object.keys(this.maps).length === this.selectedMaps.length;
},
initialState () {
return this.isFirstTimeViewingDashboard &&
!this.hasSharedMaps &&
!this.isFetchingMaps &&
this.hasFilterApplied('mine') &&
this.totalUserEntries <= 0;
},
emptyState () {
return ((!this.isFirstTimeViewingDashboard || this.hasSharedMaps) || this.isFirstTimeViewerAfterAction) &&
!this.isFetchingMaps &&
!this.currentEntriesCount;
},
emptyStateText () {
const route = this.$router.resolve({name: 'maps', params: { filter: 'shared' }});
return this.hasSharedMaps
? this.$t('MapsPage.emptyCase.onlyShared', { path: route.href })
: this.$t('MapsPage.emptyCase.default', { path: route.href });
},
isFirstTimeViewerAfterAction () {
// First time viewing dashboard but user has performed any action such as drag and dropping a dataset (no page refreshing)
return this.isFirstTimeViewingDashboard && this.currentEntriesCount <= 0 && !this.hasFilterApplied('mine');
},
hasSharedMaps () {
return this.totalShared > 0;
},
isSomeMapSelected () {
return this.selectedMaps.length > 0;
},
shouldShowLimitsWarning () {
return this.isOutOfPublicMapsQuota || this.isOutOfPrivateMapsQuota;
},
shouldShowViewSwitcher () {
return this.canChangeViewMode && !this.initialState && !this.emptyState && !this.selectedMaps.length;
},
shouldShowListHeader () {
return this.isCondensed && !this.emptyState && !this.initialState;
},
isNotificationVisible () {
return this.$store.getters['user/isNotificationVisible'];
}
},
methods: {
fetchMaps () {
this.$store.dispatch('maps/fetch');
},
applyFilter (filter) {
this.$emit('applyFilter', filter);
},
applyOrder (orderParams) {
this.$emit('applyOrder', orderParams);
},
toggleSelected ({ map, isSelected, event }) {
if (this.selectedMaps.length && event.shiftKey) {
this.doShiftClick(map);
return;
}
if (isSelected) {
this.lastCheckedItem = map;
this.selectedMaps.push(map);
return;
}
this.selectedMaps = this.selectedMaps.filter(selectedMap => selectedMap.id !== map.id);
},
doShiftClick (map) {
const mapsArray = [...Object.values(this.maps)];
this.selectedMaps = shiftClick(mapsArray, this.selectedMaps, map, this.lastCheckedItem || map);
},
selectAll () {
this.selectedMaps = [...Object.values(this.$store.state.maps.list)];
},
deselectAll () {
this.selectedMaps = [];
},
isMapSelected (map) {
return this.selectedMaps.some(selectedMap => selectedMap.id === map.id);
},
hasFilterApplied (filter) {
return this.filterType === filter;
},
toggleViewMode () {
this.isCondensed = !this.isCondensed;
},
getHeaderContainer () {
return this.$refs.headerContainer;
},
onContentChanged (type) {
this.$emit('contentChanged', type);
}
},
watch: {
isCondensed (isCompactMapView) {
if (isCompactMapView) {
localStorage.mapViewMode = 'compact';
} else {
localStorage.mapViewMode = 'standard';
}
},
selectedMaps () {
this.$emit('selectionChange', this.selectedMaps);
}
}
};
</script>
<style scoped lang="scss">
@import 'new-dashboard/styles/variables';
.map-element {
margin-bottom: 36px;
}
.full-width {
width: 100%;
}
.grid__head--sticky {
top: $header__height;
}
.grid__head--sticky.has-user-notification {
top: $header__height + $notification-warning__height;
}
.pagination-element {
margin-top: 28px;
}
.empty-state {
margin: 20vh 0 8vh;
}
.grid-column {
flex-direction: column;
}
.card-condensed {
width: 100%;
border-bottom: 1px solid #EBEEF5;
&:last-child {
border-bottom: 0;
}
}
.mapcard-view-mode {
display: flex;
align-items: center;
justify-content: center;
width: 38px;
height: 36px;
margin-left: 24px;
padding: 9px;
cursor: pointer;
&:hover,
&:focus {
background-color: $softblue;
}
&:active {
background-color: $primary-color;
.svgicon {
fill: $white;
}
}
}
.warning {
white-space: nowrap;
}
</style>