mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-10 15:47:32 +00:00
Dashboard data sources frontend
This commit is contained in:
parent
5d683b1168
commit
45e02f5875
52 changed files with 2107 additions and 259 deletions
web-frontend
modules
core
assets/scss
components
all.scss
variables.scssdashboard
all.scsscreate_widget_button.scsscreate_widget_card.scssdashboard_app.scssdashboard_app_header.scssdashboard_sidebar.scssdashboard_summary_widget.scssdashboard_widget.scssempty_dashboard.scssempty_dashboard_sidebar.scsswidget_header.scsswidget_settings_base_form.scss
dropdown_section.scssselect.scsscomponents
mixins
plugins
dashboard
assets/images/widgets
components
CreateWidgetButton.vueCreateWidgetModal.vueDashboardContent.vueDashboardContentHeader.vueDashboardHeader.vueDashboardSidebar.vueEmptyDashboard.vueEmptyDashboardSidebar.vueWidgetBoard.vue
data_source
widget
locales
middleware.jsmiddleware
pages
plugin.jsservices
store
widgetTypes.jstest/unit
core/components/__snapshots__
database
__snapshots__
components
|
@ -24,6 +24,7 @@
|
|||
@import 'select';
|
||||
@import 'choose_select';
|
||||
@import 'dropdown';
|
||||
@import 'dropdown_section';
|
||||
@import 'field_context';
|
||||
@import 'tooltip';
|
||||
@import 'rating';
|
||||
|
|
|
@ -1,2 +1,11 @@
|
|||
@import 'components/dashboard_app';
|
||||
@import 'components/dashboard_app_header';
|
||||
@import 'dashboard_app';
|
||||
@import 'dashboard_app_header';
|
||||
@import 'empty_dashboard';
|
||||
@import 'dashboard_sidebar';
|
||||
@import 'empty_dashboard_sidebar';
|
||||
@import 'create_widget_card';
|
||||
@import 'dashboard_widget';
|
||||
@import 'dashboard_summary_widget';
|
||||
@import 'create_widget_button';
|
||||
@import 'widget_header';
|
||||
@import 'widget_settings_base_form';
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
.create-widget-button {
|
||||
border: 1px dashed #cdcecf;
|
||||
box-shadow: 0 1px 2px 0 rgba(7, 8, 16, 0.1);
|
||||
margin: 24px 0;
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
@include rounded($rounded-md);
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
.create-widget-card {
|
||||
max-width: 116px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.create-widget-card__name {
|
||||
color: $palette-neutral-1200;
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.create-widget-card__img-container {
|
||||
padding: 16px 24px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ededed;
|
||||
background: #fafafa;
|
||||
box-shadow: 0 1px 2px 0 rgba(7, 8, 16, 0.1);
|
||||
}
|
||||
|
||||
.create-widget-card:hover .create-widget-card__img-container {
|
||||
border: 1px solid #d7d8d9;
|
||||
background: #f7f7f7;
|
||||
}
|
|
@ -1,14 +1,32 @@
|
|||
.dashboard-app__layout {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.dashboard-app__layout-scrollable {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.dashboard-app__content {
|
||||
min-height: calc(100vh - 100px);
|
||||
min-width: 400px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 24px 20px 24px 24px;
|
||||
padding: 56px 80px;
|
||||
background-color: $white;
|
||||
border: 1px solid $palette-neutral-200;
|
||||
box-shadow: 0 1px 2px 0 rgba(7, 8, 16, 0.1);
|
||||
|
||||
@include rounded($rounded);
|
||||
|
||||
@media screen and (max-width: $dashboard-breakpoint) {
|
||||
padding: 20px 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-app__content-header {
|
||||
margin: 56px 80px 0;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.dashboard-app__title {
|
|
@ -0,0 +1,5 @@
|
|||
.dashboard-sidebar {
|
||||
background: #fff;
|
||||
padding: 16px 16px 14px;
|
||||
border-left: 1px solid $palette-neutral-200;
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
.dashboard-summary-widget {
|
||||
padding: 0 24px 24px;
|
||||
}
|
||||
|
||||
.dashboard-summary-widget__summary {
|
||||
color: $palette-neutral-1200;
|
||||
font-size: 40px;
|
||||
font-weight: 600;
|
||||
line-height: 40px;
|
||||
margin-top: 16px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.dashboard-summary-widget__summary--misconfigured {
|
||||
color: #cdcecf;
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
.dashboard-widget {
|
||||
border: 1px solid $palette-neutral-200;
|
||||
box-shadow: 0 1px 2px 0 rgba(7, 8, 16, 0.1);
|
||||
position: relative;
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
@include rounded($rounded-md);
|
||||
}
|
||||
|
||||
.dashboard-widget--selected {
|
||||
outline: 1.5px solid #4e5cfe;
|
||||
outline-offset: 8px;
|
||||
}
|
||||
|
||||
.dashboard-widget--selectable {
|
||||
border: 1px dashed #cdcecf;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-widget__name {
|
||||
@include elevation($elevation-low);
|
||||
@include absolute(-37px, auto, auto, 0);
|
||||
@include rounded(80px);
|
||||
|
||||
font-size: 10px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 12px;
|
||||
letter-spacing: 0.2px;
|
||||
background-color: #4e5cfe;
|
||||
color: $white;
|
||||
cursor: default;
|
||||
padding: 4px 8px;
|
||||
z-index: 1;
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
.empty-dashboard {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
flex-grow: 2;
|
||||
margin: 32px 0;
|
||||
border-radius: 6px;
|
||||
border: 1px dashed #d7d8d9;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.empty-dashboard__content {
|
||||
width: 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.empty-dashboard__content-title {
|
||||
color: $palette-neutral-1200;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.empty-dashboard__content-subtitle {
|
||||
text-align: center;
|
||||
color: #6a6b70;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
margin-bottom: 24px;
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
.empty-dashboard-sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0 20%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-dashboard-sidebar__icon {
|
||||
font-size: 20px;
|
||||
color: $color-neutral-400;
|
||||
margin-bottom: 24px;
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid $palette-neutral-200;
|
||||
box-shadow: 0 1px 2px 0 rgba(7, 8, 16, 0.1);
|
||||
}
|
||||
|
||||
.empty-dashboard-sidebar__title {
|
||||
color: $palette-neutral-1200;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.empty-dashboard-sidebar__message {
|
||||
color: $palette-neutral-900;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
.widget-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: start;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.widget-header__main {
|
||||
padding-top: 24px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.widget-header__context-menu {
|
||||
flex-grow: 1;
|
||||
text-align: right;
|
||||
margin: 6px -14px 0 7px;
|
||||
width: 46px;
|
||||
}
|
||||
|
||||
.widget-header__title-wrapper {
|
||||
display: flex;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.widget-header__title {
|
||||
color: $palette-neutral-1200;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.widget-header__description {
|
||||
color: #6a6b70;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
margin-top: 8px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.widget-header__fix-configuration {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: #b23f30;
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
line-height: 12px;
|
||||
letter-spacing: 0.2px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 80px;
|
||||
background: #fff2f0;
|
||||
white-space: nowrap;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
.widget-settings-base-form {
|
||||
border-bottom: 1px solid $color-neutral-200;
|
||||
padding-bottom: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
.dropdown-section {
|
||||
margin-bottom: 4px;
|
||||
display: none;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&:has(.select__item.visible) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-section__title {
|
||||
padding: 12px 0 8px 12px;
|
||||
color: $palette-neutral-900;
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 11px;
|
||||
letter-spacing: 0.22px;
|
||||
}
|
|
@ -188,6 +188,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.select__item-link--indented {
|
||||
padding-left: 14px;
|
||||
}
|
||||
|
||||
.select__item-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
|
@ -61,7 +61,7 @@ $file-field-modal-foot-height: 108px !default;
|
|||
|
||||
$onboarding-breakpoint: 920px !default;
|
||||
|
||||
$dashboard-breakpoint: 1100px;
|
||||
$dashboard-breakpoint: 900px;
|
||||
|
||||
$builder-page-max-width: 1280px;
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
class="select__item select__item--no-options"
|
||||
:class="{
|
||||
hidden: !isVisible(query),
|
||||
visible: isVisible(query),
|
||||
active: isActive(value),
|
||||
disabled: disabled,
|
||||
hover: isHovering(value),
|
||||
|
@ -12,6 +13,7 @@
|
|||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
:class="{ 'select__item-link--indented': indented }"
|
||||
@click="select(value, disabled)"
|
||||
@mousemove="hover(value, disabled)"
|
||||
>
|
||||
|
|
18
web-frontend/modules/core/components/DropdownSection.vue
Normal file
18
web-frontend/modules/core/components/DropdownSection.vue
Normal file
|
@ -0,0 +1,18 @@
|
|||
<template>
|
||||
<div class="dropdown-section">
|
||||
<div class="dropdown-section__title">{{ title }}</div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'DropdownSection',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -45,6 +45,11 @@ export default {
|
|||
required: false,
|
||||
default: true,
|
||||
},
|
||||
indented: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
@ -23,6 +23,11 @@ export default {
|
|||
return {
|
||||
// A list of values that the form allows. If null all values are allowed.
|
||||
allowedValues: null,
|
||||
// By setting emitValuesOnReset to false in the form's component
|
||||
// the values changed event won't be sent right after resetting the
|
||||
// form
|
||||
emitValuesOnReset: true,
|
||||
isAfterReset: true,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
@ -201,6 +206,8 @@ export default {
|
|||
* first level of children.
|
||||
*/
|
||||
async reset(deep = false) {
|
||||
this.isAfterReset = true
|
||||
|
||||
Object.assign(
|
||||
this.values,
|
||||
this.$options.data.call(this).values,
|
||||
|
@ -237,7 +244,13 @@ export default {
|
|||
},
|
||||
|
||||
emitChange(newValues) {
|
||||
this.$emit('values-changed', newValues)
|
||||
if (this.emitValuesOnReset === true || this.isAfterReset === false) {
|
||||
this.$emit('values-changed', newValues)
|
||||
}
|
||||
|
||||
if (this.isAfterReset) {
|
||||
this.isAfterReset = false
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import Context from '@baserow/modules/core/components/Context'
|
|||
import Modal from '@baserow/modules/core/components/Modal'
|
||||
import Editable from '@baserow/modules/core/components/Editable'
|
||||
import Dropdown from '@baserow/modules/core/components/Dropdown'
|
||||
import DropdownSection from '@baserow/modules/core/components/DropdownSection'
|
||||
import DropdownItem from '@baserow/modules/core/components/DropdownItem'
|
||||
import Picker from '@baserow/modules/core/components/Picker'
|
||||
import ProgressBar from '@baserow/modules/core/components/ProgressBar'
|
||||
|
@ -65,6 +66,7 @@ function setupVue(Vue) {
|
|||
Vue.component('Modal', Modal)
|
||||
Vue.component('Editable', Editable)
|
||||
Vue.component('Dropdown', Dropdown)
|
||||
Vue.component('DropdownSection', DropdownSection)
|
||||
Vue.component('DropdownItem', DropdownItem)
|
||||
Vue.component('Checkbox', Checkbox)
|
||||
Vue.component('Radio', Radio)
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<svg width="72" height="40" viewBox="0 0 72 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="1.25" y="1.25" width="69.5" height="37.5" rx="4.75" fill="white"/>
|
||||
<rect x="1.25" y="1.25" width="69.5" height="37.5" rx="4.75" stroke="#E6E6E7" stroke-width="1.5"/>
|
||||
<rect opacity="0.48" x="12" y="28" width="14" height="4" rx="2" fill="#E6E6E7"/>
|
||||
<rect opacity="0.48" x="47" y="28" width="13" height="4" rx="2" fill="#E6E6E7"/>
|
||||
<path d="M14.2539 18.6816V16.4551H12.0332V14.9434H14.2539V12.7168H15.7656V14.9434H17.998V16.4551H15.7656V18.6816H14.2539ZM19.1816 18.4824V17.0527L22.7734 11.375H25.0586V17.0234H26.1309V18.4824H25.0586V20H23.3184V18.4824H19.1816ZM23.3477 17.0234V13.3672H23.2832L21.0156 16.9531V17.0234H23.3477Z" fill="#0D9439"/>
|
||||
<path d="M51.9414 14.9727V16.3906H48.0098V14.9727H51.9414ZM52.832 20V18.6816L55.8965 15.8398C56.6699 15.0898 57.0918 14.6152 57.1035 13.9297C57.0918 13.1738 56.5293 12.6992 55.7793 12.7051C54.9824 12.6992 54.4668 13.2031 54.4727 14.0352H52.7441C52.7324 12.3359 53.9746 11.2578 55.791 11.2578C57.6309 11.2578 58.8555 12.3125 58.8672 13.8125C58.8555 14.791 58.375 15.5996 56.6113 17.2168L55.3516 18.4473V18.5059H58.9844V20H52.832Z" fill="#B23F30"/>
|
||||
<rect x="36" y="12" width="1" height="16" fill="#EDEDED"/>
|
||||
</svg>
|
After (image error) Size: 1.2 KiB |
|
@ -0,0 +1,34 @@
|
|||
<template>
|
||||
<div class="create-widget-button">
|
||||
<CreateWidgetModal
|
||||
ref="createWidgetModal"
|
||||
:dashboard="dashboard"
|
||||
@widget-type-selected="$emit('widget-type-selected', $event)"
|
||||
/>
|
||||
<ButtonFloating
|
||||
icon="iconoir-plus"
|
||||
type="secondary"
|
||||
@click="openCreateWidgetModal"
|
||||
></ButtonFloating>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CreateWidgetModal from '@baserow/modules/dashboard/components/CreateWidgetModal'
|
||||
|
||||
export default {
|
||||
name: 'CreateWidgetButton',
|
||||
components: { CreateWidgetModal },
|
||||
props: {
|
||||
dashboard: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openCreateWidgetModal() {
|
||||
this.$refs.createWidgetModal.show()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,48 @@
|
|||
<template>
|
||||
<Modal>
|
||||
<h2 class="box__title">
|
||||
{{ $t('createWidgetModal.title') }}
|
||||
</h2>
|
||||
<div>
|
||||
<a
|
||||
v-for="widgetType in widgetTypes"
|
||||
:key="widgetType.type"
|
||||
class="create-widget-card"
|
||||
@click="widgetTypeSelected(widgetType.type)"
|
||||
>
|
||||
<div class="create-widget-card__img-container">
|
||||
<img :src="widgetType.createWidgetImage" />
|
||||
</div>
|
||||
<div class="create-widget-card__name">
|
||||
{{ widgetType.name }}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import modal from '@baserow/modules/core/mixins/modal'
|
||||
|
||||
export default {
|
||||
name: 'CreateWidgetModal',
|
||||
mixins: [modal],
|
||||
props: {
|
||||
dashboard: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
widgetTypes() {
|
||||
return this.$registry.getAll('dashboardWidget')
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
widgetTypeSelected(widgetType) {
|
||||
this.$emit('widget-type-selected', widgetType)
|
||||
this.hide()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
104
web-frontend/modules/dashboard/components/DashboardContent.vue
Normal file
104
web-frontend/modules/dashboard/components/DashboardContent.vue
Normal file
|
@ -0,0 +1,104 @@
|
|||
<template>
|
||||
<div>
|
||||
<div v-if="!isLoading">
|
||||
<div class="layout__col-2-2 dashboard-app__layout">
|
||||
<div
|
||||
class="dashboard-app__layout-scrollable"
|
||||
:style="{ width: `calc(100% - ${sidebarWidth}px)` }"
|
||||
>
|
||||
<div class="dashboard-app__content">
|
||||
<DashboardContentHeader :dashboard="dashboard" />
|
||||
<EmptyDashboard
|
||||
v-if="isEmpty"
|
||||
:dashboard="dashboard"
|
||||
@widget-type-selected="createWidget($event)"
|
||||
/>
|
||||
<template v-else>
|
||||
<WidgetBoard :dashboard="dashboard" />
|
||||
<CreateWidgetButton
|
||||
v-if="isEditMode && canCreateWidget"
|
||||
:dashboard="dashboard"
|
||||
@widget-type-selected="createWidget($event)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<DashboardSidebar
|
||||
v-if="isEditMode"
|
||||
:dashboard="dashboard"
|
||||
:style="{ width: `${sidebarWidth}px` }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import EmptyDashboard from '@baserow/modules/dashboard/components/EmptyDashboard'
|
||||
import CreateWidgetButton from '@baserow/modules/dashboard/components/CreateWidgetButton'
|
||||
import DashboardSidebar from '@baserow/modules/dashboard/components/DashboardSidebar'
|
||||
import DashboardContentHeader from '@baserow/modules/dashboard/components/DashboardContentHeader'
|
||||
import WidgetBoard from '@baserow/modules/dashboard/components/WidgetBoard'
|
||||
import { mapGetters, mapActions } from 'vuex'
|
||||
import { notifyIf } from '@baserow/modules/core/utils/error'
|
||||
|
||||
export default {
|
||||
name: 'DashboardContent',
|
||||
components: {
|
||||
EmptyDashboard,
|
||||
CreateWidgetButton,
|
||||
WidgetBoard,
|
||||
DashboardContentHeader,
|
||||
DashboardSidebar,
|
||||
},
|
||||
props: {
|
||||
dashboard: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
contentHeight: 0,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
isEditMode: 'dashboardApplication/isEditMode',
|
||||
isEmpty: 'dashboardApplication/isEmpty',
|
||||
isLoading: 'dashboardApplication/isLoading',
|
||||
}),
|
||||
sidebarWidth() {
|
||||
if (this.isEditMode) {
|
||||
return 352
|
||||
}
|
||||
return 0
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
toggleEditMode: 'dashboardApplication/toggleEditMode',
|
||||
enterEditMode: 'dashboardApplication/enterEditMode',
|
||||
}),
|
||||
canCreateWidget() {
|
||||
return this.$hasPermission(
|
||||
'dashboard.create_widget',
|
||||
this.dashboard,
|
||||
this.dashboard.workspace.id
|
||||
)
|
||||
},
|
||||
async createWidget(widgetType) {
|
||||
const typeFromRegistry = this.$registry.get('dashboardWidget', widgetType)
|
||||
try {
|
||||
await this.$store.dispatch('dashboardApplication/createWidget', {
|
||||
dashboard: this.dashboard,
|
||||
widget: { title: typeFromRegistry.name, type: widgetType },
|
||||
})
|
||||
this.enterEditMode()
|
||||
} catch (error) {
|
||||
notifyIf(error, 'dashboard')
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,112 @@
|
|||
<template>
|
||||
<div class="dashboard-app__content-header">
|
||||
<div class="dashboard-app__title">
|
||||
<Editable
|
||||
ref="dashboardNameEditable"
|
||||
:value="dashboard.name"
|
||||
@change="renameApplication(dashboard, $event)"
|
||||
@editing="editingDashboardName = $event"
|
||||
></Editable>
|
||||
<a
|
||||
v-if="isEditMode"
|
||||
class="dashboard-app__edit"
|
||||
:class="{ 'visibility-hidden': editingDashboardName }"
|
||||
@click="editName"
|
||||
>
|
||||
<i class="dashboard-app__edit-icon iconoir-edit-pencil"></i
|
||||
></a>
|
||||
</div>
|
||||
<div
|
||||
v-if="isEditMode || dashboard.description"
|
||||
class="dashboard-app__description"
|
||||
>
|
||||
<Editable
|
||||
ref="dashboardDescriptionEditable"
|
||||
:value="dashboard.description"
|
||||
:placeholder="$t('dashboard.descriptionPlaceholder')"
|
||||
@change="updateDescription(dashboard, $event)"
|
||||
@editing="editingDashboardDescription = $event"
|
||||
></Editable>
|
||||
<a
|
||||
v-if="isEditMode"
|
||||
class="dashboard-app__edit"
|
||||
:class="{ 'visibility-hidden': editingDashboardDescription }"
|
||||
@click="editDescription"
|
||||
>
|
||||
<i class="dashboard-app__edit-icon iconoir-edit-pencil"></i
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { notifyIf } from '@baserow/modules/core/utils/error'
|
||||
import { mapGetters, mapActions } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'DashboardContentHeader',
|
||||
props: {
|
||||
dashboard: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editingDashboardName: false,
|
||||
editingDashboardDescription: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
isEditMode: 'dashboardApplication/isEditMode',
|
||||
isEmpty: 'dashboardApplication/isEmpty',
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
selectWidget: 'dashboardApplication/selectWidget',
|
||||
}),
|
||||
editName() {
|
||||
this.$refs.dashboardNameEditable.edit()
|
||||
this.editingDashboardName = true
|
||||
this.selectWidget(null)
|
||||
},
|
||||
editDescription() {
|
||||
this.$refs.dashboardDescriptionEditable.edit()
|
||||
this.editingDashboardDescription = true
|
||||
this.selectWidget(null)
|
||||
},
|
||||
async renameApplication(application, event) {
|
||||
try {
|
||||
await this.$store.dispatch('application/update', {
|
||||
application,
|
||||
values: {
|
||||
name: event.value,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
this.$refs.dashboardNameEditable.set(event.oldValue)
|
||||
notifyIf(error, 'application')
|
||||
} finally {
|
||||
this.editingDashboardName = false
|
||||
}
|
||||
},
|
||||
async updateDescription(application, event) {
|
||||
try {
|
||||
await this.$store.dispatch('application/update', {
|
||||
application,
|
||||
values: {
|
||||
description: event.value,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
this.$refs.dashboardDescriptionEditable.set(event.oldValue)
|
||||
notifyIf(error, 'application')
|
||||
} finally {
|
||||
this.editingDashboardDescription = false
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -1,11 +1,14 @@
|
|||
<template>
|
||||
<header class="layout__col-2-1 header header--space-between">
|
||||
<DashboardHeaderMenuItems v-if="!isEditMode" :dashboard="dashboard" />
|
||||
<div v-else class="dashboard-app-header__done-editing">
|
||||
<Button type="primary" @click="doneEditing">{{
|
||||
$t('dashboardHeader.doneEditing')
|
||||
}}</Button>
|
||||
</div>
|
||||
<div v-show="isLoading" class="header__loading"></div>
|
||||
<template v-if="!isLoading">
|
||||
<DashboardHeaderMenuItems v-if="!isEditMode" :dashboard="dashboard" />
|
||||
<div v-else class="dashboard-app-header__done-editing">
|
||||
<Button type="primary" @click="doneEditing">{{
|
||||
$t('dashboardHeader.doneEditing')
|
||||
}}</Button>
|
||||
</div>
|
||||
</template>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
|
@ -27,6 +30,7 @@ export default {
|
|||
computed: {
|
||||
...mapGetters({
|
||||
isEditMode: 'dashboardApplication/isEditMode',
|
||||
isLoading: 'dashboardApplication/isLoading',
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<template>
|
||||
<div class="dashboard-sidebar">
|
||||
<WidgetSettings
|
||||
v-if="selectedWidget"
|
||||
:dashboard="dashboard"
|
||||
:widget="selectedWidget"
|
||||
/>
|
||||
<EmptyDashboardSidebar v-else />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import EmptyDashboardSidebar from '@baserow/modules/dashboard/components/EmptyDashboardSidebar'
|
||||
import WidgetSettings from '@baserow/modules/dashboard/components/widget/WidgetSettings'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'DashboardSidebar',
|
||||
components: { EmptyDashboardSidebar, WidgetSettings },
|
||||
props: {
|
||||
dashboard: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
selectedWidget: 'dashboardApplication/getSelectedWidget',
|
||||
}),
|
||||
},
|
||||
}
|
||||
</script>
|
51
web-frontend/modules/dashboard/components/EmptyDashboard.vue
Normal file
51
web-frontend/modules/dashboard/components/EmptyDashboard.vue
Normal file
|
@ -0,0 +1,51 @@
|
|||
<template>
|
||||
<div class="empty-dashboard">
|
||||
<div class="empty-dashboard__content">
|
||||
<div class="empty-dashboard__content-title">
|
||||
{{ $t('emptyDashboard.title') }}
|
||||
</div>
|
||||
<div v-if="canCreateWidget" class="empty-dashboard__content-subtitle">
|
||||
{{ $t('emptyDashboard.subtitle') }}
|
||||
</div>
|
||||
<Button
|
||||
v-if="canCreateWidget"
|
||||
type="primary"
|
||||
icon="iconoir-plus"
|
||||
@click="openCreateWidgetModal"
|
||||
>{{ $t('emptyDashboard.addWidget') }}</Button
|
||||
>
|
||||
</div>
|
||||
<CreateWidgetModal
|
||||
ref="createWidgetModal"
|
||||
:dashboard="dashboard"
|
||||
@widget-type-selected="$emit('widget-type-selected', $event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CreateWidgetModal from '@baserow/modules/dashboard/components/CreateWidgetModal'
|
||||
|
||||
export default {
|
||||
name: 'EmptyDashboard',
|
||||
components: { CreateWidgetModal },
|
||||
props: {
|
||||
dashboard: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openCreateWidgetModal() {
|
||||
this.$refs.createWidgetModal.show()
|
||||
},
|
||||
canCreateWidget() {
|
||||
return this.$hasPermission(
|
||||
'dashboard.create_widget',
|
||||
this.dashboard,
|
||||
this.dashboard.workspace.id
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,17 @@
|
|||
<template>
|
||||
<div class="empty-dashboard-sidebar">
|
||||
<i class="iconoir-cursor-pointer empty-dashboard-sidebar__icon"></i>
|
||||
<div class="empty-dashboard-sidebar__title">
|
||||
{{ $t('emptyDashboardSidebar.title') }}
|
||||
</div>
|
||||
<div class="empty-dashboard-sidebar__message">
|
||||
{{ $t('emptyDashboardSidebar.message') }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'EmptyDashboardSidebar',
|
||||
}
|
||||
</script>
|
32
web-frontend/modules/dashboard/components/WidgetBoard.vue
Normal file
32
web-frontend/modules/dashboard/components/WidgetBoard.vue
Normal file
|
@ -0,0 +1,32 @@
|
|||
<template>
|
||||
<div>
|
||||
<DashboardWidget
|
||||
v-for="widget in widgets"
|
||||
:key="widget.id"
|
||||
:widget="widget"
|
||||
:dashboard="dashboard"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import DashboardWidget from '@baserow/modules/dashboard/components/widget/DashboardWidget'
|
||||
|
||||
export default {
|
||||
name: 'WidgetBoard',
|
||||
components: { DashboardWidget },
|
||||
props: {
|
||||
dashboard: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
isEditMode: 'dashboardApplication/isEditMode',
|
||||
widgets: 'dashboardApplication/getWidgets',
|
||||
}),
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,303 @@
|
|||
<template>
|
||||
<form @submit.prevent>
|
||||
<FormSection
|
||||
:title="$t('aggregateRowsDataSourceForm.data')"
|
||||
class="margin-bottom-2"
|
||||
>
|
||||
<FormGroup
|
||||
:label="$t('aggregateRowsDataSourceForm.sourceFieldLabel')"
|
||||
class="margin-bottom-2"
|
||||
small-label
|
||||
required
|
||||
horizontal
|
||||
horizontal-narrow
|
||||
>
|
||||
<Dropdown
|
||||
v-model="values.table_id"
|
||||
:show-search="true"
|
||||
fixed-items
|
||||
:error="fieldHasErrors('table_id')"
|
||||
@change="$v.values.table_id.$touch()"
|
||||
>
|
||||
<DropdownSection
|
||||
v-for="database in databases"
|
||||
:key="database.id"
|
||||
:title="`${database.name} (${database.id})`"
|
||||
>
|
||||
<DropdownItem
|
||||
v-for="table in database.tables"
|
||||
:key="table.id"
|
||||
:name="table.name"
|
||||
:value="table.id"
|
||||
:indented="true"
|
||||
>
|
||||
{{ table.name }}
|
||||
</DropdownItem>
|
||||
</DropdownSection>
|
||||
</Dropdown>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
v-if="values.table_id && !fieldHasErrors('table_id')"
|
||||
:label="$t('aggregateRowsDataSourceForm.viewFieldLabel')"
|
||||
class="margin-bottom-2"
|
||||
small-label
|
||||
required
|
||||
horizontal
|
||||
horizontal-narrow
|
||||
>
|
||||
<Dropdown
|
||||
v-model="values.view_id"
|
||||
:show-search="false"
|
||||
fixed-items
|
||||
:error="fieldHasErrors('view_id')"
|
||||
@change="$v.values.view_id.$touch()"
|
||||
>
|
||||
<DropdownItem
|
||||
:name="$t('aggregateRowsDataSourceForm.notSelected')"
|
||||
:value="null"
|
||||
>{{ $t('aggregateRowsDataSourceForm.notSelected') }}</DropdownItem
|
||||
>
|
||||
<DropdownItem
|
||||
v-for="view in tableViews"
|
||||
:key="view.id"
|
||||
:name="view.name"
|
||||
:value="view.id"
|
||||
>
|
||||
{{ view.name }}
|
||||
</DropdownItem>
|
||||
</Dropdown>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
v-if="values.table_id && !fieldHasErrors('table_id')"
|
||||
class="margin-bottom-2"
|
||||
small-label
|
||||
:label="$t('aggregateRowsDataSourceForm.aggregationFieldLabel')"
|
||||
required
|
||||
horizontal
|
||||
horizontal-narrow
|
||||
>
|
||||
<Dropdown
|
||||
v-model="values.field_id"
|
||||
:disabled="tableFields.length === 0"
|
||||
:error="fieldHasErrors('field_id')"
|
||||
@change="$v.values.field_id.$touch()"
|
||||
>
|
||||
<DropdownItem
|
||||
v-for="field in tableFields"
|
||||
:key="field.id"
|
||||
:name="field.name"
|
||||
:value="field.id"
|
||||
:icon="fieldIconClass(field)"
|
||||
>
|
||||
</DropdownItem>
|
||||
</Dropdown>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
v-if="!fieldHasErrors('field_id')"
|
||||
small-label
|
||||
:label="$t('aggregateRowsDataSourceForm.aggregationTypeLabel')"
|
||||
required
|
||||
horizontal
|
||||
horizontal-narrow
|
||||
>
|
||||
<Dropdown
|
||||
v-model="values.aggregation_type"
|
||||
:error="fieldHasErrors('aggregation_type')"
|
||||
@change="$v.values.aggregation_type.$touch()"
|
||||
>
|
||||
<DropdownItem
|
||||
v-for="viewAggregation in viewAggregationTypes"
|
||||
:key="viewAggregation.getType()"
|
||||
:name="viewAggregation.getName()"
|
||||
:value="viewAggregation.getType()"
|
||||
>
|
||||
</DropdownItem>
|
||||
</Dropdown>
|
||||
</FormGroup>
|
||||
</FormSection>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import form from '@baserow/modules/core/mixins/form'
|
||||
import { required } from 'vuelidate/lib/validators'
|
||||
|
||||
const includes = (array) => (value) => {
|
||||
return array.includes(value)
|
||||
}
|
||||
|
||||
const includesIfSet = (array) => (value) => {
|
||||
if (value === null || value === undefined) {
|
||||
return true
|
||||
}
|
||||
return array.includes(value)
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'AggregateRowsDataSourceForm',
|
||||
mixins: [form],
|
||||
props: {
|
||||
dashboard: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
widget: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
dataSource: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
allowedValues: ['table_id', 'view_id', 'field_id', 'aggregation_type'],
|
||||
values: {
|
||||
table_id: null,
|
||||
view_id: null,
|
||||
field_id: null,
|
||||
aggregation_type: 'sum',
|
||||
},
|
||||
tableLoading: false,
|
||||
databaseSelectedId: null,
|
||||
emitValuesOnReset: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
integration() {
|
||||
return this.$store.getters['dashboardApplication/getIntegrationById'](
|
||||
this.dataSource.integration_id
|
||||
)
|
||||
},
|
||||
databases() {
|
||||
return this.integration.context_data.databases
|
||||
},
|
||||
databaseSelected() {
|
||||
return this.databases.find(
|
||||
(database) => database.id === this.databaseSelectedId
|
||||
)
|
||||
},
|
||||
tables() {
|
||||
return this.databases.map((database) => database.tables).flat()
|
||||
},
|
||||
tableIds() {
|
||||
return this.tables.map((table) => table.id)
|
||||
},
|
||||
tableSelected() {
|
||||
return this.tables.find(({ id }) => id === this.values.table_id)
|
||||
},
|
||||
tableFields() {
|
||||
return this.tableSelected?.fields || []
|
||||
},
|
||||
tableFieldIds() {
|
||||
return this.tableFields.map((field) => field.id)
|
||||
},
|
||||
tableViews() {
|
||||
return (
|
||||
this.databaseSelected?.views.filter(
|
||||
(view) => view.table_id === this.values.table_id
|
||||
) || []
|
||||
)
|
||||
},
|
||||
tableViewIds() {
|
||||
return this.tableViews.map((view) => view.id)
|
||||
},
|
||||
viewAggregationTypes() {
|
||||
const selectedField = this.tableFields.find(
|
||||
(field) => field.id === this.values.field_id
|
||||
)
|
||||
if (!selectedField) return []
|
||||
return this.$registry
|
||||
.getOrderedList('viewAggregation')
|
||||
.filter((agg) => agg.fieldIsCompatible(selectedField))
|
||||
},
|
||||
aggregationTypeNames() {
|
||||
return this.viewAggregationTypes.map((aggType) => aggType.getType())
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
dataSource: {
|
||||
handler() {
|
||||
// Reset the form to set default values
|
||||
// again after a different widget is selected
|
||||
this.reset(true)
|
||||
// Run form validation so that
|
||||
// problems are highlighted immediately
|
||||
this.$v.$touch()
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
'values.table_id': {
|
||||
handler(tableId) {
|
||||
if (tableId !== null) {
|
||||
const databaseOfTableId = this.databases.find((database) =>
|
||||
database.tables.some((table) => table.id === tableId)
|
||||
)
|
||||
if (databaseOfTableId) {
|
||||
this.databaseSelectedId = databaseOfTableId.id
|
||||
}
|
||||
|
||||
// If the values are not changed by the user
|
||||
// we don't want to continue with preselecting
|
||||
// default values
|
||||
if (tableId === this.defaultValues.table_id) {
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
!this.tableViews.some((view) => view.id === this.values.view_id)
|
||||
) {
|
||||
this.values.view_id = null
|
||||
}
|
||||
|
||||
if (
|
||||
!this.tableFields.some((field) => field.id === this.values.field_id)
|
||||
) {
|
||||
if (this.tableFields.length > 0) {
|
||||
this.values.field_id = this.tableFields[0].id
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
'values.field_id': {
|
||||
handler(fieldId) {
|
||||
if (fieldId !== null) {
|
||||
if (
|
||||
!this.viewAggregationTypes.some(
|
||||
(agg) => agg.getType() === this.values.aggregation_type
|
||||
)
|
||||
) {
|
||||
if (this.viewAggregationTypes.length > 0) {
|
||||
this.values.aggregation_type =
|
||||
this.viewAggregationTypes[0].getType()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
immediate: false,
|
||||
},
|
||||
},
|
||||
validations() {
|
||||
return {
|
||||
values: {
|
||||
table_id: { required, isValidTableId: includesIfSet(this.tableIds) },
|
||||
view_id: { isValidViewId: includesIfSet(this.tableViewIds) },
|
||||
field_id: { required, isValidFieldId: includes(this.tableFieldIds) },
|
||||
aggregation_type: {
|
||||
required,
|
||||
isValidAggregationType: includes(this.aggregationTypeNames),
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fieldIconClass(field) {
|
||||
const fieldType = this.$registry.get('field', field.type)
|
||||
return fieldType.iconClass
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,73 @@
|
|||
<template>
|
||||
<div
|
||||
class="dashboard-widget"
|
||||
:class="{
|
||||
'dashboard-widget--selected': isSelected,
|
||||
'dashboard-widget--selectable': isSelectable,
|
||||
}"
|
||||
@click="selectWidgetIfAllowed(widget.id)"
|
||||
>
|
||||
<div v-if="isSelected && isEditMode" class="dashboard-widget__name">
|
||||
{{ widgetType.name }}
|
||||
</div>
|
||||
<component
|
||||
:is="widgetComponent(widget.type)"
|
||||
:dashboard="dashboard"
|
||||
:widget="widget"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'DashboardWidget',
|
||||
props: {
|
||||
dashboard: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
widget: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
selectedWidgetId: 'dashboardApplication/getSelectedWidgetId',
|
||||
isEditMode: 'dashboardApplication/isEditMode',
|
||||
}),
|
||||
isSelected() {
|
||||
return this.selectedWidgetId === this.widget.id && this.isEditMode
|
||||
},
|
||||
isSelectable() {
|
||||
return this.selectedWidgetId !== this.widget.id && this.isEditMode
|
||||
},
|
||||
widgetType() {
|
||||
return this.$registry.get('dashboardWidget', this.widget.type)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
selectWidget: 'dashboardApplication/selectWidget',
|
||||
}),
|
||||
widgetComponent(type) {
|
||||
const widgetType = this.$registry.get('dashboardWidget', type)
|
||||
return widgetType.component
|
||||
},
|
||||
selectWidgetIfAllowed(widgetId) {
|
||||
if (this.canSelectWidget()) {
|
||||
this.selectWidget(widgetId)
|
||||
}
|
||||
},
|
||||
canSelectWidget() {
|
||||
return this.$hasPermission(
|
||||
'dashboard.widget.update',
|
||||
this.widget,
|
||||
this.dashboard.workspace.id
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,88 @@
|
|||
<template>
|
||||
<div class="dashboard-summary-widget">
|
||||
<div class="widget-header">
|
||||
<div class="widget-header__main">
|
||||
<div class="widget-header__title-wrapper">
|
||||
<div class="widget-header__title">{{ widget.title }}</div>
|
||||
<div
|
||||
v-if="dataSourceMisconfigured"
|
||||
class="widget-header__fix-configuration"
|
||||
>
|
||||
<svg
|
||||
width="5"
|
||||
height="6"
|
||||
viewBox="0 0 5 6"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="2.5" cy="3" r="2.5" fill="#FF5A44" />
|
||||
</svg>
|
||||
{{ $t('widget.fixConfiguration') }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="widget.description" class="widget-header__description">
|
||||
{{ widget.description }}
|
||||
</div>
|
||||
</div>
|
||||
<WidgetContextMenu
|
||||
v-if="isEditMode"
|
||||
:widget="widget"
|
||||
:dashboard="dashboard"
|
||||
@delete-widget="$emit('delete-widget', $event)"
|
||||
></WidgetContextMenu>
|
||||
</div>
|
||||
<div
|
||||
class="dashboard-summary-widget__summary"
|
||||
:class="{
|
||||
'dashboard-summary-widget__summary--misconfigured':
|
||||
dataSourceMisconfigured,
|
||||
}"
|
||||
>
|
||||
{{ result }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import WidgetContextMenu from '@baserow/modules/dashboard/components/widget/WidgetContextMenu'
|
||||
|
||||
export default {
|
||||
name: 'SummaryWidget',
|
||||
components: { WidgetContextMenu },
|
||||
props: {
|
||||
dashboard: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
widget: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
getDataSourceById: 'dashboardApplication/getDataSourceById',
|
||||
getDataForDataSource: 'dashboardApplication/getDataForDataSource',
|
||||
isEditMode: 'dashboardApplication/isEditMode',
|
||||
}),
|
||||
dataSource() {
|
||||
return this.getDataSourceById(this.widget.data_source_id)
|
||||
},
|
||||
result() {
|
||||
const data = this.getDataForDataSource(this.dataSource?.id)
|
||||
if (data && data.result !== undefined) {
|
||||
return data.result
|
||||
}
|
||||
return 0
|
||||
},
|
||||
dataSourceMisconfigured() {
|
||||
const data = this.getDataForDataSource(this.dataSource?.id)
|
||||
if (data) {
|
||||
return !!data._error
|
||||
}
|
||||
return false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,70 @@
|
|||
<template>
|
||||
<AggregateRowsDataSourceForm
|
||||
v-if="dataSource"
|
||||
ref="dataSourceForm"
|
||||
:dashboard="dashboard"
|
||||
:widget="widget"
|
||||
:data-source="dataSource"
|
||||
:default-values="dataSource"
|
||||
@values-changed="onDataSourceValuesChanged"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AggregateRowsDataSourceForm from '@baserow/modules/dashboard/components/data_source/AggregateRowsDataSourceForm'
|
||||
import error from '@baserow/modules/core/mixins/error'
|
||||
import { mapActions } from 'vuex'
|
||||
import { notifyIf } from '@baserow/modules/core/utils/error'
|
||||
|
||||
export default {
|
||||
name: 'SummaryWidgetSettings',
|
||||
components: { AggregateRowsDataSourceForm },
|
||||
mixins: [error],
|
||||
props: {
|
||||
dashboard: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
widget: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dataSource() {
|
||||
return this.$store.getters['dashboardApplication/getDataSourceById'](
|
||||
this.widget.data_source_id
|
||||
)
|
||||
},
|
||||
integration() {
|
||||
return this.$store.getters['dashboardApplication/getIntegrationById'](
|
||||
this.dataSource.integration_id
|
||||
)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
updateDataSource: 'dashboardApplication/updateDataSource',
|
||||
}),
|
||||
async onDataSourceValuesChanged(changedDataSourceValues) {
|
||||
if (this.$refs.dataSourceForm.isFormValid()) {
|
||||
try {
|
||||
await this.updateDataSource({
|
||||
dataSourceId: this.dataSource.id,
|
||||
values: changedDataSourceValues,
|
||||
})
|
||||
} catch (error) {
|
||||
this.$refs.dataSourceForm.reset()
|
||||
this.$refs.dataSourceForm.touch()
|
||||
notifyIf(error, 'dashboard')
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,65 @@
|
|||
<template>
|
||||
<Context ref="context" overflow-scroll max-height-if-outside-viewport>
|
||||
<ul class="context__menu">
|
||||
<li v-if="canBeDeleted" class="context__menu-item">
|
||||
<a
|
||||
class="context__menu-item-link context__menu-item-link--delete"
|
||||
:class="{ 'context__menu-item-link--loading': isDeleteInProgress }"
|
||||
@click="deleteWidget()"
|
||||
>
|
||||
<i class="context__menu-item-icon iconoir-bin"></i>
|
||||
{{ $t('widgetContext.delete') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</Context>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import context from '@baserow/modules/core/mixins/context'
|
||||
import { notifyIf } from '@baserow/modules/core/utils/error'
|
||||
|
||||
export default {
|
||||
name: 'WidgetContext',
|
||||
mixins: [context],
|
||||
props: {
|
||||
dashboard: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
widget: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isDeleteInProgress: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
canBeDeleted() {
|
||||
return this.$hasPermission(
|
||||
'dashboard.widget.delete',
|
||||
this.widget,
|
||||
this.dashboard.workspace.id
|
||||
)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async deleteWidget() {
|
||||
this.isDeleteInProgress = true
|
||||
try {
|
||||
await this.$store.dispatch(
|
||||
'dashboardApplication/deleteWidget',
|
||||
this.widget.id
|
||||
)
|
||||
} catch (error) {
|
||||
notifyIf(error, 'dashboard')
|
||||
}
|
||||
this.isDeleteInProgress = false
|
||||
this.hide()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,37 @@
|
|||
<template>
|
||||
<div ref="contextButton" class="widget-header__context-menu">
|
||||
<ButtonIcon
|
||||
icon="iconoir-more-vert"
|
||||
type="secondary"
|
||||
size="regular"
|
||||
@click.stop="
|
||||
$refs.context.toggle($refs.contextButton, 'bottom', 'right', 8, 0)
|
||||
"
|
||||
></ButtonIcon>
|
||||
<WidgetContext
|
||||
ref="context"
|
||||
:widget="widget"
|
||||
:dashboard="dashboard"
|
||||
@delete-widget="$emit('delete-widget', $event)"
|
||||
></WidgetContext>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WidgetContext from '@baserow/modules/dashboard/components/widget/WidgetContext'
|
||||
|
||||
export default {
|
||||
name: 'WidgetContextMenu',
|
||||
components: { WidgetContext },
|
||||
props: {
|
||||
dashboard: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
widget: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,74 @@
|
|||
<template>
|
||||
<div>
|
||||
<WidgetSettingsBaseForm
|
||||
ref="form"
|
||||
:widget="widget"
|
||||
:default-values="widget"
|
||||
@values-changed="baseFormValuesChanged($event)"
|
||||
/>
|
||||
<component
|
||||
:is="widgetSettingsComponent"
|
||||
:dashboard="dashboard"
|
||||
:widget="widget"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import WidgetSettingsBaseForm from '@baserow/modules/dashboard/components/widget/WidgetSettingsBaseForm'
|
||||
import { mapActions } from 'vuex'
|
||||
import { notifyIf } from '@baserow/modules/core/utils/error'
|
||||
|
||||
export default {
|
||||
name: 'WidgetSettings',
|
||||
components: { WidgetSettingsBaseForm },
|
||||
props: {
|
||||
dashboard: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
widget: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
widgetType() {
|
||||
return this.$registry.get('dashboardWidget', this.widget.type)
|
||||
},
|
||||
widgetSettingsComponent() {
|
||||
return this.widgetType.settingsComponent
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
updateWidget: 'dashboardApplication/updateWidget',
|
||||
}),
|
||||
async baseFormValuesChanged(values) {
|
||||
if (this.$refs.form.isFormValid()) {
|
||||
const defaultValues = this.$refs.form.defaultValues
|
||||
const originalValues = JSON.parse(JSON.stringify(defaultValues))
|
||||
const defaultWithUpdatedValues = { ...defaultValues, ...values }
|
||||
|
||||
// Compute only the values in the form that has been actually
|
||||
// changed
|
||||
const updatedValues = Object.fromEntries(
|
||||
Object.entries(defaultWithUpdatedValues).filter(
|
||||
([key, value]) => !_.isEqual(value, defaultValues[key])
|
||||
)
|
||||
)
|
||||
try {
|
||||
await this.updateWidget({
|
||||
widgetId: this.widget.id,
|
||||
values: updatedValues,
|
||||
originalValues,
|
||||
})
|
||||
} catch (error) {
|
||||
notifyIf(error, 'dashboard')
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,101 @@
|
|||
<template>
|
||||
<form
|
||||
class="widget-settings-base-form"
|
||||
@submit.prevent
|
||||
@keydown.enter.prevent
|
||||
>
|
||||
<FormSection>
|
||||
<FormGroup
|
||||
small-label
|
||||
:label="$t('widgetSettings.title')"
|
||||
:error="fieldHasErrors('title')"
|
||||
class="margin-bottom-2"
|
||||
required
|
||||
>
|
||||
<FormInput
|
||||
ref="title"
|
||||
v-model="values.title"
|
||||
:placeholder="$t('widgetSettings.title')"
|
||||
:error="fieldHasErrors('title')"
|
||||
@blur="$v.values.title.$touch()"
|
||||
></FormInput>
|
||||
<template #error>
|
||||
<span v-if="$v.values.title.$dirty && !$v.values.title.required">
|
||||
{{ $t('error.requiredField') }}
|
||||
</span>
|
||||
<span v-if="$v.values.title.$dirty && !$v.values.title.maxLength">
|
||||
{{ $t('error.maxLength', { max: 255 }) }}
|
||||
</span>
|
||||
</template>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
small-label
|
||||
:label="$t('widgetSettings.description')"
|
||||
:error="fieldHasErrors('description')"
|
||||
class="margin-bottom-2"
|
||||
>
|
||||
<FormTextarea
|
||||
ref="description"
|
||||
v-model="values.description"
|
||||
:max-rows="2"
|
||||
:auto-expandable="true"
|
||||
size="small"
|
||||
:placeholder="$t('widgetSettings.description') + '...'"
|
||||
:error="fieldHasErrors('description')"
|
||||
@blur="$v.values.description.$touch()"
|
||||
></FormTextarea>
|
||||
<template #error>
|
||||
<span
|
||||
v-if="
|
||||
$v.values.description.$dirty && !$v.values.description.maxLength
|
||||
"
|
||||
>
|
||||
{{ $t('error.maxLength', { max: 255 }) }}
|
||||
</span>
|
||||
</template>
|
||||
</FormGroup>
|
||||
</FormSection>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { required, maxLength } from 'vuelidate/lib/validators'
|
||||
import form from '@baserow/modules/core/mixins/form'
|
||||
|
||||
export default {
|
||||
name: 'WidgetSettingsBaseForm',
|
||||
mixins: [form],
|
||||
props: {
|
||||
widget: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
allowedValues: ['title', 'description'],
|
||||
values: {
|
||||
title: '',
|
||||
description: '',
|
||||
},
|
||||
emitValuesOnReset: false,
|
||||
}
|
||||
},
|
||||
validations() {
|
||||
return {
|
||||
values: {
|
||||
title: { required, maxLength: maxLength(255) },
|
||||
description: { maxLength: maxLength(255) },
|
||||
},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
widget: {
|
||||
handler(value) {
|
||||
this.reset(true)
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -5,7 +5,40 @@
|
|||
"dashboardHeader": {
|
||||
"doneEditing": "Done editing"
|
||||
},
|
||||
"emptyDashboard": {
|
||||
"title": "This dashboard doesn't have any widgets",
|
||||
"subtitle": "Get started by adding one.",
|
||||
"addWidget": "Add widget"
|
||||
},
|
||||
"dashboardHeaderMenuItems": {
|
||||
"editMode": "Edit mode"
|
||||
},
|
||||
"createWidgetModal": {
|
||||
"title": "Add new widget"
|
||||
},
|
||||
"widget": {
|
||||
"fixConfiguration": "Fix configuration"
|
||||
},
|
||||
"summaryWidget": {
|
||||
"name": "Summary"
|
||||
},
|
||||
"emptyDashboardSidebar": {
|
||||
"title": "No element selected",
|
||||
"message": "Click on one of the elements to see details."
|
||||
},
|
||||
"widgetSettings": {
|
||||
"title": "Title",
|
||||
"description": "Description"
|
||||
},
|
||||
"aggregateRowsDataSourceForm": {
|
||||
"data": "Data",
|
||||
"sourceFieldLabel": "Source",
|
||||
"viewFieldLabel": "View",
|
||||
"notSelected": "Not selected",
|
||||
"aggregationFieldLabel": "Field",
|
||||
"aggregationTypeLabel": "Summary"
|
||||
},
|
||||
"widgetContext": {
|
||||
"delete": "Delete"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
/**
|
||||
* Nothing here yet
|
||||
*/
|
||||
import dashboardLoading from '@baserow/modules/dashboard/middleware/dashboardLoading'
|
||||
|
||||
/* eslint-disable-next-line */
|
||||
import Middleware from './middleware'
|
||||
|
||||
Middleware.dashboardLoading = dashboardLoading
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* Middleware that changes the dashboard loading state to true before the route
|
||||
* changes.
|
||||
*/
|
||||
export default async function ({ route, from, store, app }) {
|
||||
function parseIntOrNull(x) {
|
||||
return x != null ? parseInt(x) : null
|
||||
}
|
||||
|
||||
const toDashboardId = parseIntOrNull(route?.params?.dashboardId)
|
||||
const fromDashboardId = parseIntOrNull(from?.params?.dashboardId)
|
||||
const differentDashboardId = fromDashboardId !== toDashboardId
|
||||
|
||||
if (!from || differentDashboardId) {
|
||||
await store.dispatch('dashboardApplication/setLoading', true)
|
||||
}
|
||||
}
|
|
@ -1,70 +1,24 @@
|
|||
<template>
|
||||
<div class="dashboard-app">
|
||||
<DashboardHeader :dashboard="dashboard" />
|
||||
<div class="layout__col-2-2 dashboard-app__content">
|
||||
<div class="dashboard-app__content-header">
|
||||
<div class="dashboard-app__title">
|
||||
<Editable
|
||||
ref="dashboardNameEditable"
|
||||
:value="dashboard.name"
|
||||
@change="renameApplication(dashboard, $event)"
|
||||
@editing="editingDashboardName = $event"
|
||||
></Editable>
|
||||
<a
|
||||
v-if="isEditMode"
|
||||
class="dashboard-app__edit"
|
||||
:class="{ 'visibility-hidden': editingDashboardName }"
|
||||
@click="editName"
|
||||
>
|
||||
<i class="dashboard-app__edit-icon iconoir-edit-pencil"></i
|
||||
></a>
|
||||
</div>
|
||||
<div
|
||||
v-if="isEditMode || dashboard.description"
|
||||
class="dashboard-app__description"
|
||||
>
|
||||
<Editable
|
||||
ref="dashboardDescriptionEditable"
|
||||
:value="dashboard.description"
|
||||
:placeholder="$t('dashboard.descriptionPlaceholder')"
|
||||
@change="updateDescription(dashboard, $event)"
|
||||
@editing="editingDashboardDescription = $event"
|
||||
></Editable>
|
||||
<a
|
||||
v-if="isEditMode"
|
||||
class="dashboard-app__edit"
|
||||
:class="{ 'visibility-hidden': editingDashboardDescription }"
|
||||
@click="editDescription"
|
||||
>
|
||||
<i class="dashboard-app__edit-icon iconoir-edit-pencil"></i
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DashboardContent :dashboard="dashboard" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DashboardHeader from '@baserow/modules/dashboard/components/DashboardHeader'
|
||||
import { notifyIf } from '@baserow/modules/core/utils/error'
|
||||
import { mapGetters } from 'vuex'
|
||||
import DashboardContent from '@baserow/modules/dashboard/components/DashboardContent'
|
||||
|
||||
export default {
|
||||
name: 'Dashboard',
|
||||
components: { DashboardHeader },
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
if (from.params.dashboardId !== to.params?.dashboardId) {
|
||||
this.$store.dispatch('dashboardApplication/reset')
|
||||
}
|
||||
next()
|
||||
},
|
||||
components: { DashboardHeader, DashboardContent },
|
||||
beforeRouteLeave(to, from, next) {
|
||||
this.$store.dispatch('dashboardApplication/reset')
|
||||
this.$store.dispatch('application/unselect')
|
||||
next()
|
||||
},
|
||||
layout: 'app',
|
||||
async asyncData({ store, params, error, $registry }) {
|
||||
middleware: 'dashboardLoading',
|
||||
async asyncData({ app, store, params, error, $registry }) {
|
||||
const dashboardId = parseInt(params.dashboardId)
|
||||
const data = {}
|
||||
try {
|
||||
|
@ -76,63 +30,22 @@ export default {
|
|||
'workspace/selectById',
|
||||
dashboard.workspace.id
|
||||
)
|
||||
const forEditing = app.$hasPermission(
|
||||
'application.update',
|
||||
dashboard,
|
||||
dashboard.workspace.id
|
||||
)
|
||||
await store.dispatch('dashboardApplication/fetchInitial', {
|
||||
dashboardId: dashboard.id,
|
||||
forEditing,
|
||||
})
|
||||
data.workspace = workspace
|
||||
data.dashboard = dashboard
|
||||
await store.dispatch('dashboardApplication/setLoading', false)
|
||||
} catch (e) {
|
||||
return error({ statusCode: 404, message: 'Dashboard not found.' })
|
||||
}
|
||||
return data
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editingDashboardName: false,
|
||||
editingDashboardDescription: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
isEditMode: 'dashboardApplication/isEditMode',
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
editName() {
|
||||
this.$refs.dashboardNameEditable.edit()
|
||||
this.editingDashboardName = true
|
||||
},
|
||||
editDescription() {
|
||||
this.$refs.dashboardDescriptionEditable.edit()
|
||||
this.editingDashboardDescription = true
|
||||
},
|
||||
async renameApplication(application, event) {
|
||||
try {
|
||||
await this.$store.dispatch('application/update', {
|
||||
application,
|
||||
values: {
|
||||
name: event.value,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
this.$refs.dashboardNameEditable.set(event.oldValue)
|
||||
notifyIf(error, 'application')
|
||||
} finally {
|
||||
this.editingDashboardName = false
|
||||
}
|
||||
},
|
||||
async updateDescription(application, event) {
|
||||
try {
|
||||
await this.$store.dispatch('application/update', {
|
||||
application,
|
||||
values: {
|
||||
description: event.value,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
this.$refs.dashboardDescriptionEditable.set(event.oldValue)
|
||||
notifyIf(error, 'application')
|
||||
} finally {
|
||||
this.editingDashboardDescription = false
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -8,6 +8,7 @@ import pl from '@baserow/modules/dashboard/locales/pl.json'
|
|||
import ko from '@baserow/modules/dashboard/locales/ko.json'
|
||||
|
||||
import { DashboardApplicationType } from '@baserow/modules/dashboard/applicationTypes'
|
||||
import { SummaryWidgetType } from '@baserow/modules/dashboard/widgetTypes'
|
||||
import dashboardApplicationStore from '@baserow/modules/dashboard/store/dashboardApplication'
|
||||
import { FF_DASHBOARDS } from '@baserow/modules/core/plugins/featureFlags'
|
||||
|
||||
|
@ -31,5 +32,6 @@ export default (context) => {
|
|||
|
||||
if (app.$featureFlagIsEnabled(FF_DASHBOARDS)) {
|
||||
app.$registry.register('application', new DashboardApplicationType(context))
|
||||
app.$registry.register('dashboardWidget', new SummaryWidgetType(context))
|
||||
}
|
||||
}
|
||||
|
|
13
web-frontend/modules/dashboard/services/dataSource.js
Normal file
13
web-frontend/modules/dashboard/services/dataSource.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
export default (client) => {
|
||||
return {
|
||||
getAllDataSources(dashboardId) {
|
||||
return client.get(`/dashboard/${dashboardId}/data-sources/`)
|
||||
},
|
||||
update(dataSourceId, values = {}) {
|
||||
return client.patch(`/dashboard/data-sources/${dataSourceId}/`, values)
|
||||
},
|
||||
dispatch(dataSourceId) {
|
||||
return client.post(`/dashboard/data-sources/${dataSourceId}/dispatch/`)
|
||||
},
|
||||
}
|
||||
}
|
18
web-frontend/modules/dashboard/services/widget.js
Normal file
18
web-frontend/modules/dashboard/services/widget.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
export default (client) => {
|
||||
return {
|
||||
getAllWidgets(dashboardId) {
|
||||
return client.get(`/dashboard/${dashboardId}/widgets/`)
|
||||
},
|
||||
create(dashboardId, widget = {}) {
|
||||
return client.post(`/dashboard/${dashboardId}/widgets/`, {
|
||||
...widget,
|
||||
})
|
||||
},
|
||||
update(widgetId, values = {}) {
|
||||
return client.patch(`/dashboard/widgets/${widgetId}/`, values)
|
||||
},
|
||||
delete(widgetId) {
|
||||
return client.delete(`/dashboard/widgets/${widgetId}/`)
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,29 +1,228 @@
|
|||
import WidgetService from '@baserow/modules/dashboard/services/widget'
|
||||
import DataSourceService from '@baserow/modules/dashboard/services/dataSource'
|
||||
import IntegrationService from '@baserow/modules/core/services/integration'
|
||||
import debounce from 'lodash/debounce'
|
||||
|
||||
export const state = () => ({
|
||||
loading: false,
|
||||
editMode: false,
|
||||
selectedWidgetId: null,
|
||||
widgets: [],
|
||||
dataSources: [],
|
||||
integrations: [],
|
||||
// A cache for data that has been
|
||||
// returned as a result of dispatching
|
||||
// a data source. The keys are data source ids.
|
||||
data: {},
|
||||
})
|
||||
|
||||
let debouncedWidgetUpdate = null
|
||||
|
||||
export const mutations = {
|
||||
RESET(state) {
|
||||
state.editMode = false
|
||||
state.selectedWidgetId = null
|
||||
state.widgets = []
|
||||
state.dataSources = []
|
||||
state.integrations = []
|
||||
state.data = {}
|
||||
},
|
||||
TOGGLE_EDIT_MODE(state) {
|
||||
state.editMode = !state.editMode
|
||||
},
|
||||
ADD_WIDGET(state, widget) {
|
||||
state.widgets.push(widget)
|
||||
},
|
||||
ADD_DATA_SOURCE(state, dataSource) {
|
||||
state.dataSources.push(dataSource)
|
||||
},
|
||||
UPDATE_DATA_SOURCE(state, { dataSourceId, values }) {
|
||||
const dataSource = state.dataSources.find(
|
||||
(dataSource) => dataSource.id === dataSourceId
|
||||
)
|
||||
Object.assign(dataSource, values)
|
||||
},
|
||||
UPDATE_DATA(state, { dataSourceId, values }) {
|
||||
if (state.data[dataSourceId] === undefined) {
|
||||
state.data[dataSourceId] = {}
|
||||
}
|
||||
state.data = {
|
||||
...state.data,
|
||||
[dataSourceId]: { ...values },
|
||||
}
|
||||
},
|
||||
ADD_INTEGRATION(state, integration) {
|
||||
state.integrations.push(integration)
|
||||
},
|
||||
SELECT_WIDGET(state, widgetId) {
|
||||
state.selectedWidgetId = widgetId
|
||||
},
|
||||
UPDATE_WIDGET(state, { widgetId, values }) {
|
||||
const widget = state.widgets.find((widget) => widget.id === widgetId)
|
||||
Object.assign(widget, values)
|
||||
},
|
||||
DELETE_WIDGET(state, widgetId) {
|
||||
const index = state.widgets.findIndex((widget) => widget.id === widgetId)
|
||||
state.widgets.splice(index, 1)
|
||||
},
|
||||
SET_LOADING(state, value) {
|
||||
state.loading = value
|
||||
},
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
setLoading({ commit }, value) {
|
||||
commit('SET_LOADING', value)
|
||||
},
|
||||
reset({ commit }) {
|
||||
commit('RESET')
|
||||
},
|
||||
toggleEditMode({ commit }) {
|
||||
commit('TOGGLE_EDIT_MODE')
|
||||
},
|
||||
enterEditMode({ getters, commit }) {
|
||||
if (!getters.isEditMode) {
|
||||
commit('TOGGLE_EDIT_MODE')
|
||||
}
|
||||
},
|
||||
selectWidget({ commit }, widgetId) {
|
||||
commit('SELECT_WIDGET', widgetId)
|
||||
},
|
||||
updateWidget({ commit }, { widgetId, values, originalValues }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
commit('UPDATE_WIDGET', { widgetId, values })
|
||||
|
||||
let previousOriginalValues = originalValues
|
||||
if (debouncedWidgetUpdate) {
|
||||
debouncedWidgetUpdate.cancel()
|
||||
previousOriginalValues = debouncedWidgetUpdate.originalValues
|
||||
}
|
||||
|
||||
debouncedWidgetUpdate = debounce(async () => {
|
||||
try {
|
||||
await WidgetService(this.$client).update(widgetId, values)
|
||||
debouncedWidgetUpdate = null
|
||||
resolve()
|
||||
} catch (error) {
|
||||
commit('UPDATE_WIDGET', { widgetId, values: previousOriginalValues })
|
||||
reject(error)
|
||||
}
|
||||
}, 1000)
|
||||
debouncedWidgetUpdate.originalValues = previousOriginalValues
|
||||
debouncedWidgetUpdate()
|
||||
})
|
||||
},
|
||||
async updateDataSource({ commit, dispatch }, { dataSourceId, values }) {
|
||||
const { data } = await DataSourceService(this.$client).update(
|
||||
dataSourceId,
|
||||
values
|
||||
)
|
||||
commit('UPDATE_DATA_SOURCE', { dataSourceId, values: data })
|
||||
|
||||
try {
|
||||
await dispatch('dispatchDataSource', dataSourceId)
|
||||
} catch (error) {
|
||||
commit('UPDATE_DATA', { dataSourceId, values: { _error: true } })
|
||||
}
|
||||
},
|
||||
async fetchInitial({ commit, dispatch }, { dashboardId, forEditing }) {
|
||||
commit('RESET')
|
||||
const { data } = await WidgetService(this.$client).getAllWidgets(
|
||||
dashboardId
|
||||
)
|
||||
data.forEach((widget) => {
|
||||
commit('ADD_WIDGET', widget)
|
||||
})
|
||||
await dispatch('fetchNewDataSources', dashboardId)
|
||||
|
||||
if (forEditing) {
|
||||
const { data: integrationsData } = await IntegrationService(
|
||||
this.$client
|
||||
).fetchAll(dashboardId)
|
||||
integrationsData.forEach((integration) => {
|
||||
commit('ADD_INTEGRATION', integration)
|
||||
})
|
||||
}
|
||||
},
|
||||
async fetchNewDataSources({ commit, dispatch, getters }, dashboardId) {
|
||||
const { data: dataSourcesData } = await DataSourceService(
|
||||
this.$client
|
||||
).getAllDataSources(dashboardId)
|
||||
dataSourcesData.forEach(async (dataSource) => {
|
||||
if (!getters.getDataSourceById(dataSource.id)) {
|
||||
commit('ADD_DATA_SOURCE', dataSource)
|
||||
await dispatch('dispatchDataSource', dataSource.id)
|
||||
}
|
||||
})
|
||||
},
|
||||
async createWidget({ dispatch }, { dashboard, widget }) {
|
||||
const { data } = await WidgetService(this.$client).create(
|
||||
dashboard.id,
|
||||
widget
|
||||
)
|
||||
return await dispatch('handleNewWidgetCreated', {
|
||||
...data,
|
||||
})
|
||||
},
|
||||
async handleNewWidgetCreated({ commit, dispatch }, createdWidget) {
|
||||
commit('ADD_WIDGET', createdWidget)
|
||||
await dispatch('fetchNewDataSources', createdWidget.dashboard_id)
|
||||
dispatch('selectWidget', createdWidget.id)
|
||||
},
|
||||
async dispatchDataSource({ commit }, dataSourceId) {
|
||||
try {
|
||||
const { data } = await DataSourceService(this.$client).dispatch(
|
||||
dataSourceId
|
||||
)
|
||||
commit('UPDATE_DATA', { dataSourceId, values: data })
|
||||
} catch (error) {
|
||||
commit('UPDATE_DATA', { dataSourceId, values: { _error: true } })
|
||||
}
|
||||
},
|
||||
async deleteWidget({ commit }, widgetId) {
|
||||
await WidgetService(this.$client).delete(widgetId)
|
||||
commit('DELETE_WIDGET', widgetId)
|
||||
},
|
||||
}
|
||||
|
||||
export const getters = {
|
||||
isEditMode(state) {
|
||||
return state.editMode
|
||||
},
|
||||
isLoading(state) {
|
||||
return state.loading
|
||||
},
|
||||
isEmpty(state) {
|
||||
return state.widgets.length === 0
|
||||
},
|
||||
getWidgetById: (state, getters) => (widgetId) => {
|
||||
return state.widgets.find((widget) => widget.id === widgetId)
|
||||
},
|
||||
getWidgets(state) {
|
||||
return state.widgets
|
||||
},
|
||||
getSelectedWidgetId(state) {
|
||||
return state.selectedWidgetId
|
||||
},
|
||||
getSelectedWidget(state) {
|
||||
return state.widgets.find((widget) => widget.id === state.selectedWidgetId)
|
||||
},
|
||||
getDataSourceById: (state, getters) => (dataSourceId) => {
|
||||
return state.dataSources.find(
|
||||
(dataSource) => dataSource.id === dataSourceId
|
||||
)
|
||||
},
|
||||
getDataForDataSource: (state, getters) => (dataSourceId) => {
|
||||
return state.data[dataSourceId]
|
||||
},
|
||||
getIntegrations(state) {
|
||||
return state.integrations
|
||||
},
|
||||
getIntegrationById: (state) => (integrationId) => {
|
||||
return state.integrations.find(
|
||||
(integration) => integration.id === integrationId
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
export default {
|
||||
|
|
57
web-frontend/modules/dashboard/widgetTypes.js
Normal file
57
web-frontend/modules/dashboard/widgetTypes.js
Normal file
|
@ -0,0 +1,57 @@
|
|||
import { Registerable } from '@baserow/modules/core/registry'
|
||||
import SummaryWidgetSvg from '@baserow/modules/dashboard/assets/images/widgets/summary_widget.svg'
|
||||
import SummaryWidget from '@baserow/modules/dashboard/components/widget/SummaryWidget'
|
||||
import SummaryWidgetSettings from '@baserow/modules/dashboard/components/widget/SummaryWidgetSettings'
|
||||
|
||||
export class WidgetType extends Registerable {
|
||||
constructor(...args) {
|
||||
super(...args)
|
||||
this.type = this.getType()
|
||||
|
||||
if (this.type === null) {
|
||||
throw new Error('The type name of a widget type must be set.')
|
||||
}
|
||||
|
||||
if (this.name === null) {
|
||||
throw new Error('The name of a widget type must be set.')
|
||||
}
|
||||
}
|
||||
|
||||
get name() {
|
||||
return null
|
||||
}
|
||||
|
||||
get createWidgetImage() {
|
||||
return null
|
||||
}
|
||||
|
||||
get component() {
|
||||
return null
|
||||
}
|
||||
|
||||
get settingsComponent() {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export class SummaryWidgetType extends WidgetType {
|
||||
static getType() {
|
||||
return 'summary'
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.app.i18n.t('summaryWidget.name')
|
||||
}
|
||||
|
||||
get createWidgetImage() {
|
||||
return SummaryWidgetSvg
|
||||
}
|
||||
|
||||
get component() {
|
||||
return SummaryWidget
|
||||
}
|
||||
|
||||
get settingsComponent() {
|
||||
return SummaryWidgetSettings
|
||||
}
|
||||
}
|
|
@ -48,7 +48,7 @@ exports[`Dropdown component Test slots 1`] = `
|
|||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
class="select__item select__item--no-options active"
|
||||
class="select__item select__item--no-options visible active"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -119,7 +119,7 @@ exports[`Dropdown component With items 1`] = `
|
|||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -205,7 +205,7 @@ exports[`Dropdown component With items 2`] = `
|
|||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
class="select__item select__item--no-options active"
|
||||
class="select__item select__item--no-options visible active"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -236,7 +236,7 @@ exports[`Dropdown component With items 2`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -267,7 +267,7 @@ exports[`Dropdown component With items 2`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options disabled"
|
||||
class="select__item select__item--no-options visible disabled"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -298,7 +298,7 @@ exports[`Dropdown component With items 2`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -331,7 +331,7 @@ exports[`Dropdown component With items 2`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -405,7 +405,7 @@ exports[`Dropdown component With items 3`] = `
|
|||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
class="select__item select__item--no-options active"
|
||||
class="select__item select__item--no-options visible active"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -436,7 +436,7 @@ exports[`Dropdown component With items 3`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -677,7 +677,7 @@ exports[`Dropdown component change value prop 1`] = `
|
|||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -708,7 +708,7 @@ exports[`Dropdown component change value prop 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options active"
|
||||
class="select__item select__item--no-options visible active"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -794,7 +794,7 @@ exports[`Dropdown component test focus 1`] = `
|
|||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
class="select__item select__item--no-options active hover"
|
||||
class="select__item select__item--no-options visible active hover"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -880,7 +880,7 @@ exports[`Dropdown component test focus 2`] = `
|
|||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
class="select__item select__item--no-options active hover"
|
||||
class="select__item select__item--no-options visible active hover"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -966,7 +966,7 @@ exports[`Dropdown component test interactions 1`] = `
|
|||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
class="select__item select__item--no-options active hover"
|
||||
class="select__item select__item--no-options visible active hover"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -997,7 +997,7 @@ exports[`Dropdown component test interactions 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1083,7 +1083,7 @@ exports[`Dropdown component test interactions 2`] = `
|
|||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
class="select__item select__item--no-options active hover"
|
||||
class="select__item select__item--no-options visible active hover"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1114,7 +1114,7 @@ exports[`Dropdown component test interactions 2`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
|
|
@ -230,7 +230,7 @@ exports[`Public View Page Tests Can see a publicly shared grid view 1`] = `
|
|||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -261,7 +261,7 @@ exports[`Public View Page Tests Can see a publicly shared grid view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -292,7 +292,7 @@ exports[`Public View Page Tests Can see a publicly shared grid view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
|
|
@ -103,7 +103,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
class="select__item select__item--no-options active hover"
|
||||
class="select__item select__item--no-options visible active hover"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -134,7 +134,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -331,7 +331,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
class="select__item select__item--no-options active"
|
||||
class="select__item select__item--no-options visible active"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -362,7 +362,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -393,7 +393,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -424,7 +424,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -455,7 +455,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -486,7 +486,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -611,7 +611,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
class="select__item select__item--no-options active"
|
||||
class="select__item select__item--no-options visible active"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -642,7 +642,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -673,7 +673,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -704,7 +704,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -735,7 +735,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -766,7 +766,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -797,7 +797,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -828,7 +828,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -859,7 +859,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -890,7 +890,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -921,7 +921,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -952,7 +952,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -983,7 +983,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1014,7 +1014,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1045,7 +1045,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1076,7 +1076,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1107,7 +1107,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1138,7 +1138,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1169,7 +1169,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1200,7 +1200,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1231,7 +1231,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1262,7 +1262,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1293,7 +1293,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1324,7 +1324,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1355,7 +1355,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1386,7 +1386,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1417,7 +1417,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1448,7 +1448,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1479,7 +1479,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1510,7 +1510,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1541,7 +1541,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1572,7 +1572,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1603,7 +1603,7 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1907,7 +1907,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
class="select__item select__item--no-options hover"
|
||||
class="select__item select__item--no-options visible hover"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1938,7 +1938,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options active"
|
||||
class="select__item select__item--no-options visible active"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -2135,7 +2135,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
class="select__item select__item--no-options active"
|
||||
class="select__item select__item--no-options visible active"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -2166,7 +2166,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -2197,7 +2197,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -2228,7 +2228,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -2259,7 +2259,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -2290,7 +2290,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -2415,7 +2415,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
class="select__item select__item--no-options active"
|
||||
class="select__item select__item--no-options visible active"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -2446,7 +2446,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -2477,7 +2477,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -2508,7 +2508,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -2539,7 +2539,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -2570,7 +2570,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -2601,7 +2601,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -2632,7 +2632,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -2663,7 +2663,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -2694,7 +2694,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -2725,7 +2725,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -2756,7 +2756,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -2787,7 +2787,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -2818,7 +2818,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -2849,7 +2849,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -2880,7 +2880,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -2911,7 +2911,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -2942,7 +2942,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -2973,7 +2973,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -3004,7 +3004,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -3035,7 +3035,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -3066,7 +3066,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -3097,7 +3097,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -3128,7 +3128,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -3159,7 +3159,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -3190,7 +3190,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -3221,7 +3221,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -3252,7 +3252,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -3283,7 +3283,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -3314,7 +3314,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -3345,7 +3345,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -3376,7 +3376,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -3407,7 +3407,7 @@ exports[`Preview exportTableModal Modal with view 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
|
|
@ -150,7 +150,7 @@ exports[`ViewFilterForm match snapshots Full view filter component 1`] = `
|
|||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
class="select__item select__item--no-options active"
|
||||
class="select__item select__item--no-options visible active"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -180,7 +180,7 @@ exports[`ViewFilterForm match snapshots Full view filter component 1`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -210,7 +210,7 @@ exports[`ViewFilterForm match snapshots Full view filter component 1`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -298,7 +298,7 @@ exports[`ViewFilterForm match snapshots Full view filter component 1`] = `
|
|||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
class="select__item select__item--no-options active"
|
||||
class="select__item select__item--no-options visible active"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -328,7 +328,7 @@ exports[`ViewFilterForm match snapshots Full view filter component 1`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -358,7 +358,7 @@ exports[`ViewFilterForm match snapshots Full view filter component 1`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -388,7 +388,7 @@ exports[`ViewFilterForm match snapshots Full view filter component 1`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -418,7 +418,7 @@ exports[`ViewFilterForm match snapshots Full view filter component 1`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -448,7 +448,7 @@ exports[`ViewFilterForm match snapshots Full view filter component 1`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -478,7 +478,7 @@ exports[`ViewFilterForm match snapshots Full view filter component 1`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -508,7 +508,7 @@ exports[`ViewFilterForm match snapshots Full view filter component 1`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -538,7 +538,7 @@ exports[`ViewFilterForm match snapshots Full view filter component 1`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -659,7 +659,7 @@ exports[`ViewFilterForm match snapshots Full view filter component 1`] = `
|
|||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
class="select__item select__item--no-options active"
|
||||
class="select__item select__item--no-options visible active"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -690,7 +690,7 @@ exports[`ViewFilterForm match snapshots Full view filter component 1`] = `
|
|||
</li>
|
||||
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -783,7 +783,7 @@ exports[`ViewFilterForm match snapshots Full view filter component 1`] = `
|
|||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -813,7 +813,7 @@ exports[`ViewFilterForm match snapshots Full view filter component 1`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options active"
|
||||
class="select__item select__item--no-options visible active"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -843,7 +843,7 @@ exports[`ViewFilterForm match snapshots Full view filter component 1`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -931,7 +931,7 @@ exports[`ViewFilterForm match snapshots Full view filter component 1`] = `
|
|||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
class="select__item select__item--no-options active"
|
||||
class="select__item select__item--no-options visible active"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -961,7 +961,7 @@ exports[`ViewFilterForm match snapshots Full view filter component 1`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -991,7 +991,7 @@ exports[`ViewFilterForm match snapshots Full view filter component 1`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1021,7 +1021,7 @@ exports[`ViewFilterForm match snapshots Full view filter component 1`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1051,7 +1051,7 @@ exports[`ViewFilterForm match snapshots Full view filter component 1`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1081,7 +1081,7 @@ exports[`ViewFilterForm match snapshots Full view filter component 1`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1300,7 +1300,7 @@ exports[`ViewFilterForm match snapshots Test rating filter 1`] = `
|
|||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1330,7 +1330,7 @@ exports[`ViewFilterForm match snapshots Test rating filter 1`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options active"
|
||||
class="select__item select__item--no-options visible active"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1360,7 +1360,7 @@ exports[`ViewFilterForm match snapshots Test rating filter 1`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1449,7 +1449,7 @@ exports[`ViewFilterForm match snapshots Test rating filter 1`] = `
|
|||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
class="select__item select__item--no-options active hover"
|
||||
class="select__item select__item--no-options visible active hover"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1479,7 +1479,7 @@ exports[`ViewFilterForm match snapshots Test rating filter 1`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1509,7 +1509,7 @@ exports[`ViewFilterForm match snapshots Test rating filter 1`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1539,7 +1539,7 @@ exports[`ViewFilterForm match snapshots Test rating filter 1`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1569,7 +1569,7 @@ exports[`ViewFilterForm match snapshots Test rating filter 1`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1599,7 +1599,7 @@ exports[`ViewFilterForm match snapshots Test rating filter 1`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1818,7 +1818,7 @@ exports[`ViewFilterForm match snapshots Test rating filter 2`] = `
|
|||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1848,7 +1848,7 @@ exports[`ViewFilterForm match snapshots Test rating filter 2`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options active"
|
||||
class="select__item select__item--no-options visible active"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1878,7 +1878,7 @@ exports[`ViewFilterForm match snapshots Test rating filter 2`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1967,7 +1967,7 @@ exports[`ViewFilterForm match snapshots Test rating filter 2`] = `
|
|||
tabindex="-1"
|
||||
>
|
||||
<li
|
||||
class="select__item select__item--no-options active hover"
|
||||
class="select__item select__item--no-options visible active hover"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -1997,7 +1997,7 @@ exports[`ViewFilterForm match snapshots Test rating filter 2`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -2027,7 +2027,7 @@ exports[`ViewFilterForm match snapshots Test rating filter 2`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -2057,7 +2057,7 @@ exports[`ViewFilterForm match snapshots Test rating filter 2`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -2087,7 +2087,7 @@ exports[`ViewFilterForm match snapshots Test rating filter 2`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
@ -2117,7 +2117,7 @@ exports[`ViewFilterForm match snapshots Test rating filter 2`] = `
|
|||
/>
|
||||
</li>
|
||||
<li
|
||||
class="select__item select__item--no-options"
|
||||
class="select__item select__item--no-options visible"
|
||||
>
|
||||
<a
|
||||
class="select__item-link"
|
||||
|
|
Loading…
Add table
Reference in a new issue