Add ability to configure columns in metric and chart blocks
This commit is contained in:
parent
03ec8d3930
commit
144eb912fb
@ -1,12 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<b-button
|
<b-button
|
||||||
size="lg"
|
:size="size"
|
||||||
variant="light"
|
:variant="variant"
|
||||||
class="flex-fill"
|
class="flex-fill"
|
||||||
|
:disabled="disabled"
|
||||||
@click="showModal = true"
|
@click="showModal = true"
|
||||||
>
|
>
|
||||||
{{ $t('allRecords.columns.title') }}
|
<slot>
|
||||||
|
{{ $t('allRecords.columns.title') }}
|
||||||
|
</slot>
|
||||||
</b-button>
|
</b-button>
|
||||||
|
|
||||||
<b-modal
|
<b-modal
|
||||||
@ -60,6 +63,21 @@ export default {
|
|||||||
required: true,
|
required: true,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default: 'lg',
|
||||||
|
},
|
||||||
|
|
||||||
|
variant: {
|
||||||
|
type: String,
|
||||||
|
default: 'light',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data () {
|
data () {
|
||||||
@ -70,10 +88,10 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
created () {
|
watch: {
|
||||||
this.filteredFields = this.fields.map(f => {
|
fields (fields) {
|
||||||
return { ...f.moduleField }
|
this.filteredFields = this.module.filterFields(fields)
|
||||||
})
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeDestroy () {
|
beforeDestroy () {
|
||||||
|
|||||||
@ -175,6 +175,7 @@ export default {
|
|||||||
this.$root.$emit(`drill-down-recordList:${recordListUniqueID}`, prefilter)
|
this.$root.$emit(`drill-down-recordList:${recordListUniqueID}`, prefilter)
|
||||||
} else {
|
} else {
|
||||||
const { title } = this.block
|
const { title } = this.block
|
||||||
|
const { fields = [] } = this.options.drillDown.recordListOptions || {}
|
||||||
|
|
||||||
// Open in modal
|
// Open in modal
|
||||||
const block = new compose.PageBlockRecordList({
|
const block = new compose.PageBlockRecordList({
|
||||||
@ -182,6 +183,7 @@ export default {
|
|||||||
blockID: `drillDown-${chartID}`,
|
blockID: `drillDown-${chartID}`,
|
||||||
options: {
|
options: {
|
||||||
moduleID,
|
moduleID,
|
||||||
|
fields,
|
||||||
prefilter,
|
prefilter,
|
||||||
presort: 'createdAt DESC',
|
presort: 'createdAt DESC',
|
||||||
hideRecordReminderButton: true,
|
hideRecordReminderButton: true,
|
||||||
|
|||||||
@ -18,11 +18,11 @@
|
|||||||
|
|
||||||
<b-input-group-append>
|
<b-input-group-append>
|
||||||
<b-button
|
<b-button
|
||||||
v-b-tooltip.noninteractive.hover="{ title: $t('chart.openInBuilder'), container: '#body' }"
|
v-b-tooltip.hover="{ title: $t(chartSelectorTooltip), container: '#body' }"
|
||||||
:disabled="!selectedChart || (!selectedChart.canUpdateChart && !selectedChart.canDeleteChart)"
|
:disabled="selectedChart && (!selectedChart.canUpdateChart && !selectedChart.canDeleteChart)"
|
||||||
variant="extra-light"
|
variant="extra-light"
|
||||||
class="d-flex align-items-center"
|
class="d-flex align-items-center"
|
||||||
:to="{ name: 'admin.charts.edit', params: { chartID: (selectedChart || {}).chartID }, query: null }"
|
:to="{ name: chartExternalLink, params: { chartID: (selectedChart || {}).chartID }, query: null }"
|
||||||
>
|
>
|
||||||
<font-awesome-icon :icon="['fas', 'external-link-alt']" />
|
<font-awesome-icon :icon="['fas', 'external-link-alt']" />
|
||||||
</b-button>
|
</b-button>
|
||||||
@ -32,6 +32,7 @@
|
|||||||
|
|
||||||
<template v-if="isDrillDownAvailable">
|
<template v-if="isDrillDownAvailable">
|
||||||
<b-form-group
|
<b-form-group
|
||||||
|
:label="$t('chart.drillDown.label')"
|
||||||
:description="$t('chart.drillDown.description')"
|
:description="$t('chart.drillDown.description')"
|
||||||
label-class="d-flex align-items-center text-primary"
|
label-class="d-flex align-items-center text-primary"
|
||||||
class="mb-1"
|
class="mb-1"
|
||||||
@ -46,16 +47,32 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<c-input-select
|
<b-input-group>
|
||||||
v-model="options.drillDown.blockID"
|
<c-input-select
|
||||||
:options="drillDownOptions"
|
v-model="options.drillDown.blockID"
|
||||||
:get-option-key="getOptionKey"
|
:options="drillDownOptions"
|
||||||
:disabled="!options.drillDown.enabled"
|
:get-option-key="getOptionKey"
|
||||||
:get-option-label="o => o.title || o.kind"
|
:disabled="!options.drillDown.enabled"
|
||||||
:reduce="option => option.blockID"
|
:get-option-label="o => o.title || o.kind"
|
||||||
:clearable="true"
|
:reduce="option => option.blockID"
|
||||||
:placeholder="$t('chart.drillDown.openInModal')"
|
:clearable="true"
|
||||||
/>
|
:placeholder="$t('chart.drillDown.openInModal')"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<b-input-group-append>
|
||||||
|
<column-picker
|
||||||
|
ref="columnPicker"
|
||||||
|
:module="selectedChartModule"
|
||||||
|
:fields="selectedDrilldownFields"
|
||||||
|
:disabled="!!options.drillDown.blockID || !options.drillDown.enabled"
|
||||||
|
variant="extra-light"
|
||||||
|
size="md"
|
||||||
|
@updateFields="onUpdateFields"
|
||||||
|
>
|
||||||
|
<font-awesome-icon :icon="['fas', 'wrench']" />
|
||||||
|
</column-picker>
|
||||||
|
</b-input-group-append>
|
||||||
|
</b-input-group>
|
||||||
</b-form-group>
|
</b-form-group>
|
||||||
</template>
|
</template>
|
||||||
</b-tab>
|
</b-tab>
|
||||||
@ -64,6 +81,7 @@
|
|||||||
import base from './base'
|
import base from './base'
|
||||||
import { mapGetters } from 'vuex'
|
import { mapGetters } from 'vuex'
|
||||||
import { NoID } from '@cortezaproject/corteza-js'
|
import { NoID } from '@cortezaproject/corteza-js'
|
||||||
|
import ColumnPicker from 'corteza-webapp-compose/src/components/Admin/Module/Records/ColumnPicker'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
i18nOptions: {
|
i18nOptions: {
|
||||||
@ -72,11 +90,16 @@ export default {
|
|||||||
|
|
||||||
name: 'Chart',
|
name: 'Chart',
|
||||||
|
|
||||||
|
components: {
|
||||||
|
ColumnPicker,
|
||||||
|
},
|
||||||
|
|
||||||
extends: base,
|
extends: base,
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
charts: 'chart/set',
|
charts: 'chart/set',
|
||||||
|
getModuleByID: 'module/getByID',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
selectedChart () {
|
selectedChart () {
|
||||||
@ -87,6 +110,14 @@ export default {
|
|||||||
return this.charts.find(({ chartID }) => chartID === this.options.chartID)
|
return this.charts.find(({ chartID }) => chartID === this.options.chartID)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
chartExternalLink () {
|
||||||
|
return !this.selectedChart ? 'admin.charts' : 'admin.charts.edit'
|
||||||
|
},
|
||||||
|
|
||||||
|
chartSelectorTooltip () {
|
||||||
|
return !this.selectedChart ? 'chart.openChartList' : 'chart.openInBuilder'
|
||||||
|
},
|
||||||
|
|
||||||
selectedChartModuleID () {
|
selectedChartModuleID () {
|
||||||
if (!this.selectedChart) return
|
if (!this.selectedChart) return
|
||||||
|
|
||||||
@ -95,6 +126,18 @@ export default {
|
|||||||
return moduleID
|
return moduleID
|
||||||
},
|
},
|
||||||
|
|
||||||
|
selectedChartModule () {
|
||||||
|
if (!this.selectedChartModuleID) return
|
||||||
|
|
||||||
|
return this.getModuleByID(this.selectedChartModuleID)
|
||||||
|
},
|
||||||
|
|
||||||
|
selectedDrilldownFields () {
|
||||||
|
if (!this.selectedChart) return []
|
||||||
|
|
||||||
|
return this.options.drillDown.recordListOptions.fields
|
||||||
|
},
|
||||||
|
|
||||||
isDrillDownAvailable () {
|
isDrillDownAvailable () {
|
||||||
if (!this.selectedChart) return
|
if (!this.selectedChart) return
|
||||||
|
|
||||||
@ -110,15 +153,16 @@ export default {
|
|||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
chartSelected () {
|
chartSelected () {
|
||||||
this.options.drillDown = {
|
this.block.resetDrillDown()
|
||||||
enabled: false,
|
|
||||||
blockID: '',
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getOptionKey ({ chartID }) {
|
getOptionKey ({ chartID }) {
|
||||||
return chartID
|
return chartID
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onUpdateFields (fields) {
|
||||||
|
this.options.drillDown.recordListOptions.fields = fields.map(({ fieldID }) => fieldID)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -223,13 +223,16 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
// Open in modal
|
// Open in modal
|
||||||
const metricID = `${this.block.blockID}-${name.replace(/\s+/g, '-').toLowerCase()}-${moduleID}-${metricIndex}`
|
const metricID = `${this.block.blockID}-${name.replace(/\s+/g, '-').toLowerCase()}-${moduleID}-${metricIndex}`
|
||||||
|
|
||||||
const { title } = this.block
|
const { title } = this.block
|
||||||
|
const { fields = [] } = this.options.metrics[metricIndex].drillDown.recordListOptions || {}
|
||||||
|
|
||||||
const block = new compose.PageBlockRecordList({
|
const block = new compose.PageBlockRecordList({
|
||||||
title: name || title || this.$t('metric.metricDrillDown'),
|
title: name || title || this.$t('metric.metricDrillDown'),
|
||||||
blockID: `drillDown-${metricID}`,
|
blockID: `drillDown-${metricID}`,
|
||||||
options: {
|
options: {
|
||||||
moduleID,
|
moduleID,
|
||||||
|
fields,
|
||||||
prefilter: filter,
|
prefilter: filter,
|
||||||
presort: 'createdAt DESC',
|
presort: 'createdAt DESC',
|
||||||
hideRecordReminderButton: true,
|
hideRecordReminderButton: true,
|
||||||
|
|||||||
@ -166,7 +166,7 @@
|
|||||||
</b-form-group>
|
</b-form-group>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset v-if="selectedMetricModule">
|
||||||
<h5>
|
<h5>
|
||||||
{{ $t('metric.edit.metricLabel') }}
|
{{ $t('metric.edit.metricLabel') }}
|
||||||
</h5>
|
</h5>
|
||||||
@ -263,23 +263,39 @@
|
|||||||
>
|
>
|
||||||
<template #label>
|
<template #label>
|
||||||
{{ $t('metric.drillDown.label') }}
|
{{ $t('metric.drillDown.label') }}
|
||||||
|
|
||||||
<b-form-checkbox
|
<b-form-checkbox
|
||||||
v-model="edit.drillDown.enabled"
|
v-model="edit.drillDown.enabled"
|
||||||
switch
|
switch
|
||||||
class="ml-1"
|
class="ml-1 mb-1"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<c-input-select
|
<b-input-group>
|
||||||
v-model="edit.drillDown.blockID"
|
<c-input-select
|
||||||
:options="drillDownOptions"
|
v-model="edit.drillDown.blockID"
|
||||||
:disabled="!edit.drillDown.enabled"
|
:options="drillDownOptions"
|
||||||
:get-option-label="o => o.title || o.kind"
|
:disabled="!edit.drillDown.enabled"
|
||||||
:reduce="option => option.blockID"
|
:get-option-label="o => o.title || o.kind"
|
||||||
:clearable="true"
|
:reduce="option => option.blockID"
|
||||||
:placeholder="$t('metric.drillDown.openInModal')"
|
:clearable="true"
|
||||||
append-to-body
|
:placeholder="$t('metric.drillDown.openInModal')"
|
||||||
/>
|
append-to-body
|
||||||
|
/>
|
||||||
|
|
||||||
|
<b-input-group-append>
|
||||||
|
<column-picker
|
||||||
|
:module="selectedMetricModule"
|
||||||
|
:disabled="!!edit.drillDown.blockID || !edit.drillDown.enabled"
|
||||||
|
:fields="selectedDrilldownFields"
|
||||||
|
variant="extra-light"
|
||||||
|
size="md"
|
||||||
|
@updateFields="onUpdateFields"
|
||||||
|
>
|
||||||
|
<font-awesome-icon :icon="['fas', 'wrench']" />
|
||||||
|
</column-picker>
|
||||||
|
</b-input-group-append>
|
||||||
|
</b-input-group>
|
||||||
</b-form-group>
|
</b-form-group>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</b-card>
|
</b-card>
|
||||||
@ -333,6 +349,7 @@ import base from '../base'
|
|||||||
import MStyle from './MStyle'
|
import MStyle from './MStyle'
|
||||||
import { mapGetters } from 'vuex'
|
import { mapGetters } from 'vuex'
|
||||||
import MetricBase from '../MetricBase'
|
import MetricBase from '../MetricBase'
|
||||||
|
import ColumnPicker from 'corteza-webapp-compose/src/components/Admin/Module/Records/ColumnPicker'
|
||||||
import { compose, NoID } from '@cortezaproject/corteza-js'
|
import { compose, NoID } from '@cortezaproject/corteza-js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -344,6 +361,7 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
MStyle,
|
MStyle,
|
||||||
MetricBase,
|
MetricBase,
|
||||||
|
ColumnPicker,
|
||||||
},
|
},
|
||||||
extends: base,
|
extends: base,
|
||||||
|
|
||||||
@ -375,7 +393,7 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
modules: 'module/set',
|
modules: 'module/set',
|
||||||
moduleByID: 'module/getByID',
|
getModuleByID: 'module/getByID',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
fields () {
|
fields () {
|
||||||
@ -383,7 +401,13 @@ export default {
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.moduleByID(this.edit.moduleID).fields
|
return this.getModuleByID(this.edit.moduleID).fields
|
||||||
|
},
|
||||||
|
|
||||||
|
selectedDrilldownFields () {
|
||||||
|
if (!this.edit || !this.edit.drillDown.recordListOptions.fields) return []
|
||||||
|
|
||||||
|
return this.edit.drillDown.recordListOptions.fields
|
||||||
},
|
},
|
||||||
|
|
||||||
metricFields () {
|
metricFields () {
|
||||||
@ -406,6 +430,12 @@ export default {
|
|||||||
drillDownOptions () {
|
drillDownOptions () {
|
||||||
return this.page.blocks.filter(({ blockID, kind, options = {} }) => kind === 'RecordList' && blockID !== NoID && options.moduleID === this.edit.moduleID)
|
return this.page.blocks.filter(({ blockID, kind, options = {} }) => kind === 'RecordList' && blockID !== NoID && options.moduleID === this.edit.moduleID)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
selectedMetricModule () {
|
||||||
|
if (!this.edit.moduleID) return undefined
|
||||||
|
|
||||||
|
return this.getModuleByID(this.edit.moduleID)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
@ -471,6 +501,10 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onUpdateFields (fields) {
|
||||||
|
this.edit.drillDown.recordListOptions.fields = fields
|
||||||
|
},
|
||||||
|
|
||||||
setDefaultValues () {
|
setDefaultValues () {
|
||||||
this.edit = undefined
|
this.edit = undefined
|
||||||
this.dimensionModifiers = []
|
this.dimensionModifiers = []
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
import { PageBlock, PageBlockInput, Registry } from './base'
|
import { PageBlock, PageBlockInput, Registry } from './base'
|
||||||
import { Apply, CortezaID, NoID } from '../../../cast'
|
import { Apply,NoID } from '../../../cast'
|
||||||
|
import { Options as PageBlockRecordListOptions } from './record-list'
|
||||||
|
import { cloneDeep, merge } from 'lodash'
|
||||||
|
|
||||||
const kind = 'Chart'
|
const kind = 'Chart'
|
||||||
|
|
||||||
interface DrillDown {
|
interface DrillDown {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
blockID?: string;
|
blockID: string;
|
||||||
|
recordListOptions: Partial<PageBlockRecordListOptions>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Options {
|
interface Options {
|
||||||
@ -24,7 +27,10 @@ const defaults: Readonly<Options> = Object.freeze({
|
|||||||
drillDown: {
|
drillDown: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
blockID: '',
|
blockID: '',
|
||||||
},
|
recordListOptions: {
|
||||||
|
fields: [],
|
||||||
|
},
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export class PageBlockChart extends PageBlock {
|
export class PageBlockChart extends PageBlock {
|
||||||
@ -47,9 +53,13 @@ export class PageBlockChart extends PageBlock {
|
|||||||
Apply(this.options, o, Boolean, 'showRefresh')
|
Apply(this.options, o, Boolean, 'showRefresh')
|
||||||
|
|
||||||
if (o.drillDown) {
|
if (o.drillDown) {
|
||||||
this.options.drillDown = o.drillDown
|
this.options.drillDown = merge({}, defaults.drillDown, o.drillDown)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resetDrillDown(): void {
|
||||||
|
this.options.drillDown = cloneDeep(defaults.drillDown)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Registry.set(kind, PageBlockChart)
|
Registry.set(kind, PageBlockChart)
|
||||||
|
|||||||
@ -1,14 +1,15 @@
|
|||||||
import { PageBlock, PageBlockInput, Registry } from './base'
|
import { PageBlock, PageBlockInput, Registry } from './base'
|
||||||
import { merge } from 'lodash'
|
import { merge } from 'lodash'
|
||||||
import { Apply } from '../../../cast'
|
import { Apply } from '../../../cast'
|
||||||
|
import { Options as PageBlockRecordListOptions } from './record-list'
|
||||||
const kind = 'Metric'
|
const kind = 'Metric'
|
||||||
|
|
||||||
type Reporter = (p: ReporterParams) => Promise<any>
|
type Reporter = (p: ReporterParams) => Promise<any>
|
||||||
|
|
||||||
interface DrillDown {
|
interface DrillDown {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
blockID?: string;
|
blockID: string;
|
||||||
|
recordListOptions: Partial<PageBlockRecordListOptions>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ReporterParams {
|
interface ReporterParams {
|
||||||
@ -62,9 +63,13 @@ const defaultMetric: Readonly<Metric> = Object.freeze({
|
|||||||
color: '#000000',
|
color: '#000000',
|
||||||
fontSize: undefined,
|
fontSize: undefined,
|
||||||
},
|
},
|
||||||
|
|
||||||
drillDown: {
|
drillDown: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
blockID: '',
|
blockID: '',
|
||||||
|
recordListOptions: {
|
||||||
|
fields: [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,7 @@ interface FilterPreset {
|
|||||||
roles: string[];
|
roles: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Options {
|
export interface Options {
|
||||||
moduleID: string;
|
moduleID: string;
|
||||||
prefilter: string;
|
prefilter: string;
|
||||||
presort: string;
|
presort: string;
|
||||||
|
|||||||
@ -98,8 +98,9 @@ chart:
|
|||||||
display: Chart to display inside this block
|
display: Chart to display inside this block
|
||||||
drillDown:
|
drillDown:
|
||||||
label: Drill down
|
label: Drill down
|
||||||
description: If a record list is selected, it will be used to display the drill down data. If no record list is selected the drill down data will be shown in a modal.
|
description: If a record list is selected, it will be used to display the drill down data. If no record list is selected, the drill down data will be shown in a modal.
|
||||||
openInModal: Open in modal
|
openInModal: Open in modal
|
||||||
|
configureColumns: Choose the default fields
|
||||||
edit:
|
edit:
|
||||||
label: Edit chart
|
label: Edit chart
|
||||||
dimension:
|
dimension:
|
||||||
@ -119,6 +120,7 @@ chart:
|
|||||||
chartId: Chart preview (ID {{0}})
|
chartId: Chart preview (ID {{0}})
|
||||||
searchPlaceholder: Type here to search all charts in this namespace
|
searchPlaceholder: Type here to search all charts in this namespace
|
||||||
openInBuilder: Open in chart builder
|
openInBuilder: Open in chart builder
|
||||||
|
openChartList: Open chart list
|
||||||
content:
|
content:
|
||||||
ok: Ok
|
ok: Ok
|
||||||
label: Content
|
label: Content
|
||||||
@ -201,7 +203,7 @@ metric:
|
|||||||
defaultMetricLabel: Unnamed metric
|
defaultMetricLabel: Unnamed metric
|
||||||
drillDown:
|
drillDown:
|
||||||
label: Drill down
|
label: Drill down
|
||||||
description: If a record list is selected, it will be used to display the drill down data. If no record list is selected the drill down data will be shown in a modal.
|
description: If a record list is selected, it will be used to display the drill down data. If no record list is selected, the drill down data will be shown in a modal.
|
||||||
openInModal: Open in modal
|
openInModal: Open in modal
|
||||||
edit:
|
edit:
|
||||||
bucketLabel: Bucket size
|
bucketLabel: Bucket size
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user