diff --git a/frontend/express/public/javascripts/countly/vue/components/content.js b/frontend/express/public/javascripts/countly/vue/components/content.js
index ae8c99b7c6d..9d5aad9bc6d 100644
--- a/frontend/express/public/javascripts/countly/vue/components/content.js
+++ b/frontend/express/public/javascripts/countly/vue/components/content.js
@@ -1,4 +1,4 @@
-/* global Vue, CV, countlyCommon */
+/* global Vue, CV, countlyCommon, ElementTiptap */
(function(countlyVue) {
Vue.component("cly-content-layout", countlyVue.components.create({
template: CV.T('/javascripts/countly/vue/templates/content/content.html'),
@@ -369,65 +369,154 @@
`,
}));
- Vue.component("cly-content-steps", countlyVue.components.create({
+ const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_COLOR_PICKER = 'color-picker';
+ const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_DROPDOWN = 'dropdown';
+ const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_IMAGE_RADIO = 'image-radio';
+ const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_INPUT = 'input';
+ const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_LIST_BLOCK = 'list-block';
+ const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_NUMBER = 'number';
+ const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_SLIDER = 'slider';
+ const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_SWAPPER = 'swapper';
+ const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_SWITCH = 'switch';
+ const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_TAB = 'tab';
+ const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_TEXTAREA = 'textarea';
+ const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_UPLOAD = 'upload';
+
+ Vue.component('cly-content-builder-sidebar-step', countlyVue.components.create({
props: {
header: {
- type: String,
- required: false,
- default: null
+ default: null,
+ type: String
},
- collapse: {
- type: Boolean,
- required: false,
- default: true
+
+ collapsible: {
+ default: false,
+ type: Boolean
+ },
+
+ inputs: {
+ default: () => [],
+ type: Array
}
},
+
+ emits: [
+ 'add-asset',
+ 'delete-asset',
+ 'input-value-change',
+ 'upload-asset'
+ ],
+
data() {
return {
- activeSection: ["section"]
+ section: ['body']
};
},
+
+ computed: {
+ bodyComponent() {
+ return this.collapsible ? 'el-collapse-item' : 'div';
+ },
+
+ bodyComponentProps() {
+ if (!this.collapsible) {
+ return null;
+ }
+
+ return {
+ name: 'body',
+ testId: this.dataTestId,
+ title: this.header
+ };
+ },
+
+ dataTestId() {
+ return `content-drawer-sidebar-step-${this.header.toLowerCase().replaceAll(' ', '-')}`;
+ },
+
+ formattedInputs() {
+ if (this.inputs.length) {
+ return this.inputs.map(input => ({
+ ...input,
+ ...!!input.subHeader && {
+ hasSubHeader: true,
+ dataTestId: `content-drawer-sidebar-step-${input.subHeader.toLowerCase().replaceAll(' ', '-')}-label`
+ }
+ }));
+ }
+
+ return [];
+ },
+
+ wrapperComponent() {
+ return this.collapsible ? 'el-collapse' : 'div';
+ }
+ },
+
methods: {
+ onAddAsset(payload) {
+ const { input, payload: eventPayload } = payload || {};
+ const { id, key, type } = input || {};
+
+ if (type === COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_LIST_BLOCK) {
+ this.$emit('add-asset', eventPayload);
+ }
+ else {
+ this.$emit('add-asset', { id, key });
+ }
+ },
+
+ onDeleteAsset(payload) {
+ const { input, payload: eventPayload } = payload || {};
+ const { id, key, type } = input || {};
+
+ if (type === COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_LIST_BLOCK) {
+ this.$emit('delete-asset', eventPayload);
+ }
+ else {
+ this.$emit('delete-asset', { id, key });
+ }
+ },
+
+ onUploadAsset(payload) {
+ this.$emit('upload-asset', payload);
+ },
+
+ onInputChange(payload) {
+ const { input, payload: inputPayload } = payload || {};
+ const { id, key, type } = input || {};
+
+ if (type === COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_LIST_BLOCK) {
+ this.$emit('input-value-change', inputPayload);
+ }
+ else {
+ this.$emit('input-value-change', {
+ id,
+ key,
+ value: inputPayload
+ });
+ }
+ }
},
- template: `
-
- `,
+
+ template: CV.T('/javascripts/countly/vue/templates/content/UI/content-builder-sidebar-step.html'),
}));
// CONSTANTS
- const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_COLOR_PICKER = 'color-picker';
- const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_DROPDOWN = 'dropdown';
- const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_INPUT = 'input';
- const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_NUMBER = 'number';
- const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_SLIDER = 'slider';
- const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_SWAPPER = 'swapper';
- const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_SWITCH = 'switch';
- const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_TAB = 'tab';
- const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_UPLOAD = 'upload';
-
const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE = {
[COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_COLOR_PICKER]: 'cly-colorpicker',
[COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_DROPDOWN]: 'el-select',
+ [COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_IMAGE_RADIO]: 'div',
[COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_INPUT]: 'el-input',
+ [COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_LIST_BLOCK]: 'cly-content-block-list-input',
[COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_NUMBER]: 'el-input-number',
[COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_SLIDER]: 'el-slider',
[COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_SWAPPER]: 'cly-option-swapper',
[COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_SWITCH]: 'el-switch',
[COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_TAB]: 'div',
+ [COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_TEXTAREA]: 'el-tiptap',
[COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_UPLOAD]: 'el-upload'
};
@@ -452,7 +541,7 @@
},
labelIcon: {
- default: 'cly-io cly-io-question-mark-circle',
+ default: 'ion ion-help-circled',
type: String
},
@@ -481,6 +570,11 @@
type: String
},
+ size: {
+ default: null,
+ type: String
+ },
+
subHeader: {
default: null,
type: String
@@ -498,12 +592,7 @@
value: {
default: null,
- type: [String, Number, Boolean, Object]
- },
-
- size: {
- default: null,
- type: String
+ type: [String, Number, Boolean, Object, Array]
},
withComponentTooltip: {
@@ -520,9 +609,42 @@
emits: [
'add-asset',
'delete-asset',
- 'input'
+ 'input',
+ 'upload-asset'
],
+ data() {
+ return {
+ textareaExtensions: [
+ new ElementTiptap.Doc(),
+ new ElementTiptap.Text(),
+ new ElementTiptap.Paragraph(),
+ new ElementTiptap.TextColor({colors: countlyCommon.GRAPH_COLORS}),
+ new ElementTiptap.FontType({
+ fontTypes: {
+ Inter: 'Inter',
+ Lato: 'Lato',
+ Oswald: 'Oswald',
+ 'Roboto-Mono': 'Roboto-Mono',
+ Ubuntu: 'Ubuntu'
+ }
+ }),
+ new ElementTiptap.FontSize({
+ fontSizes: ['8', '10', '12', '14', '16', '18', '20', '24', '30', '36', '48', '60', '72', '96']
+ }),
+ new ElementTiptap.LineHeight(),
+ new ElementTiptap.Bold(),
+ new ElementTiptap.Italic(),
+ new ElementTiptap.Underline(),
+ new ElementTiptap.ListItem(),
+ new ElementTiptap.BulletList(),
+ new ElementTiptap.OrderedList(),
+ new ElementTiptap.FormatClear(),
+ new ElementTiptap.History()
+ ],
+ };
+ },
+
computed: {
componentValue: {
get() {
@@ -538,25 +660,30 @@
return +this.value || 0;
}
+ if (this.isListBlockInput) {
+ return null;
+ }
+
return this.value || null;
},
+
set(newValue) {
this.$emit('input', newValue);
}
},
computedAttrs() {
- if (this.isUploadInput) {
- return {
+ return {
+ ...this.$attrs,
+ ...this.isColorPickerInput && { newUI: true },
+ ...this.isTextareaInput && { extensions: this.textareaExtensions },
+ ...this.isUploadInput && {
action: '',
drag: true,
multiple: false,
- showFileList: false,
- ...this.$attrs
- };
- }
-
- return this.$attrs;
+ showFileList: false
+ }
+ };
},
controlsProp() {
@@ -567,14 +694,26 @@
return this.type === COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_DROPDOWN;
},
+ isColorPickerInput() {
+ return this.type === COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_COLOR_PICKER;
+ },
+
isComponentWithOptions() {
return this.isDropdownInput && Array.isArray(this.options) && this.options.length;
},
+ isImageRadioInput() {
+ return this.type === COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_IMAGE_RADIO;
+ },
+
isLabelTooltipVisible() {
return this.withLabelTooltip && this.labelTooltip;
},
+ isListBlockInput() {
+ return this.type === COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_LIST_BLOCK;
+ },
+
isSliderInput() {
return this.type === COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_SLIDER;
},
@@ -590,6 +729,10 @@
return this.type === COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_SWAPPER;
},
+ isTextareaInput() {
+ return this.type === COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_TEXTAREA;
+ },
+
isUploadInput() {
return this.type === COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_UPLOAD;
},
@@ -611,6 +754,14 @@
},
methods: {
+ onAddAsset(payload) {
+ this.$emit('add-asset', payload);
+ },
+
+ onDeleteAsset(payload) {
+ this.$emit('delete-asset', payload);
+ },
+
onUploadAddButtonClick() {
this.$emit('add-asset');
},
@@ -623,6 +774,37 @@
template: CV.T('/javascripts/countly/vue/templates/content/UI/content-sidebar-input.html')
}));
+ Vue.component('cly-content-block-list-input', countlyVue.components.create({
+ props: {
+ blockInputs: {
+ default: () => [],
+ type: Array
+ }
+ },
+
+ emits: [
+ 'add-asset',
+ 'delete-asset',
+ 'input'
+ ],
+
+ methods: {
+ onAddAsset(payload) {
+ this.$emit('add-asset', payload);
+ },
+
+ onDeleteAsset(payload) {
+ this.$emit('delete-asset', payload);
+ },
+
+ onInput(payload) {
+ this.$emit('input', payload);
+ }
+ },
+
+ template: CV.T('/javascripts/countly/vue/templates/content/UI/content-block-list-input.html'),
+ }));
+
Vue.component("cly-option-swapper", countlyVue.components.create({
template: CV.T('/javascripts/countly/vue/templates/UI/option-swapper.html'),
diff --git a/frontend/express/public/javascripts/countly/vue/components/input.js b/frontend/express/public/javascripts/countly/vue/components/input.js
index 70d66c02eb0..384006a65fe 100644
--- a/frontend/express/public/javascripts/countly/vue/components/input.js
+++ b/frontend/express/public/javascripts/countly/vue/components/input.js
@@ -4,6 +4,9 @@
var _mixins = countlyVue.mixins;
var HEX_COLOR_REGEX = new RegExp('^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$', 'i');
+ const COLOR_ALPHA = 'alpha';
+ const COLOR_FORMAT_HEX = 'hex';
+ const COLOR_FORMAT_RGB = 'rgb';
Vue.component("cly-colorpicker", countlyVue.components.create({
template: CV.T('/javascripts/countly/vue/templates/UI/color-picker.html'),
@@ -12,7 +15,16 @@
picker: window.VueColor.Sketch
},
+ mixins: [
+ _mixins.i18n
+ ],
+
props: {
+ newUI: {
+ type: Boolean,
+ default: false
+ },
+
placement: {
default: 'left',
type: String
@@ -39,12 +51,10 @@
'input'
],
- mixins: [
- _mixins.i18n
- ],
-
- data: function() {
+ data() {
return {
+ colorByFormats: {},
+
isOpened: false,
previousColor: null
@@ -53,65 +63,210 @@
computed: {
bodyClasses() {
- return ['cly-vue-color-picker__body--' + this.placement];
+ return {
+ [`cly-vue-color-picker__body--${this.placement}`]: true,
+ 'cly-vue-color-picker__body--new-ui': this.isNewUIApplied
+ };
+ },
+
+ colorAlpha: {
+ get() {
+ const alpha = typeof this.colorByFormats[COLOR_ALPHA] === 'number' ?
+ this.colorByFormats[COLOR_ALPHA] :
+ 0;
+
+ return Math.round(100 * alpha);
+ },
+ set(value) {
+ const valueAsNumber = +value;
+
+ if (valueAsNumber >= 0 && valueAsNumber <= 100) {
+ this.colorByFormats[COLOR_ALPHA] = valueAsNumber ? valueAsNumber / 100 : 0;
+ }
+ }
},
dropStyles() {
- return { color: this.localValue };
+ return { backgroundColor: `rgba(${Object.values(this.pickerColor).join(', ')})` };
},
- localValue: {
+ hexColor: {
get() {
- return (this.value || this.resetValue);
+ const hexColor = this.colorByFormats[COLOR_FORMAT_HEX];
+
+ return hexColor.startsWith('#') ? hexColor.slice(1) : hexColor;
},
set(value) {
- let finalValue = value;
+ this.colorByFormats[COLOR_FORMAT_HEX] = value;
+ }
+ },
- if (!finalValue.startsWith('#')) {
- finalValue = `#${finalValue}`;
+ inputValue: {
+ get() {
+ return this.rgbToHex(this.pickerColor);
+ },
+ set(value) {
+ if (value.match(HEX_COLOR_REGEX)) {
+ this.pickerColor = this.hexToRgb(value, true);
}
+ }
+ },
- if (finalValue.match(HEX_COLOR_REGEX)) {
- this.$emit('input', finalValue);
- }
+ isNewUIApplied() {
+ return this.newUI;
+ },
+
+ pickerColor: {
+ get() {
+ return this.hexToRgb(this.value || this.resetValue, true);
+ },
+ set(rgbColor) {
+ this.$emit('input', this.rgbToHex(rgbColor, true));
+ }
+ },
+
+ rgbColor: {
+ get() {
+ return this.colorByFormats[COLOR_FORMAT_RGB];
+ },
+ set(value) {
+ this.colorByFormats[COLOR_FORMAT_RGB] = JSON.parse(JSON.stringify(value));
}
}
},
watch: {
+ [`colorByFormats.${COLOR_ALPHA}`]: {
+ handler(value) {
+ if (typeof value === 'number') {
+ this.pickerColor = this.rgbColor;
+ }
+ }
+ },
+
+ [`colorByFormats.${COLOR_FORMAT_HEX}`]: {
+ handler(value) {
+ if (value) {
+ const hexValue = `#${value}`;
+
+ if (hexValue.match(HEX_COLOR_REGEX)) {
+ this.pickerColor = this.hexToRgb(hexValue);
+ }
+ }
+ }
+ },
+
+ [`colorByFormats.${COLOR_FORMAT_RGB}`]: {
+ handler(value) {
+ if (value) {
+ this.pickerColor = value;
+ }
+ }
+ },
+
isOpened(value) {
if (value) {
this.previousColor = JSON.parse(JSON.stringify(this.value));
}
+ },
+
+ value: {
+ handler(value) {
+ if (value) {
+ this.setPickerInputValues();
+ }
+ },
+ immediate: true
}
},
methods: {
- onInputContainerClick() {
- this.isOpened = true;
+ closePicker() {
+ this.isOpened = false;
},
- close() {
- this.isOpened = false;
+ hexToRgb(hex, includeAlpha = false) {
+ let hexString = hex.replace('#', '');
+
+ if (hexString.length === 3) {
+ hexString = hexString.split('').map(c => c + c).join('');
+ }
+
+ return {
+ r: parseInt(hexString.slice(0, 2), 16) || 0,
+ g: parseInt(hexString.slice(2, 4), 16) || 0,
+ b: parseInt(hexString.slice(4, 6), 16) || 0,
+ a: includeAlpha && hexString.length === 8 ? parseInt(hexString.slice(6, 8), 16) / 255 : 1
+ };
},
onCancelClick() {
- this.localValue = this.previousColor;
- this.close();
+ this.inputValue = this.previousColor;
+ this.closePicker();
+ },
+
+ onClickOutside() {
+ this.closePicker();
},
onConfirmClick() {
- this.$emit('change', this.localValue);
- this.close();
+ this.$emit('change', this.inputValue);
+ this.closePicker();
+ },
+
+ onInputContainerClick() {
+ this.isOpened = true;
},
onPickerInput(color) {
- this.localValue = color.hex8 || color.hex;
+ const { a = 0, ...rgb } = color.rgba;
+ const alpha = Math.round(a * 100);
+
+ if (alpha !== this.colorAlpha) {
+ this.colorAlpha = alpha;
+ }
+
+ if (!_.isEqual(this.rgbColor, rgb)) {
+ this.pickerColor = rgb;
+ }
},
onResetClick() {
- this.localValue = this.resetValue;
- this.close();
+ this.inputValue = this.resetValue;
+ this.closePicker();
+ },
+
+ onRGBInput(key, value) {
+ this.rgbColor = JSON.parse(JSON.stringify({
+ ...this.rgbColor,
+ [key]: +value
+ }));
+ },
+
+ rgbToHex(rgbColor, includeAlpha = false) {
+ const { r, g, b } = rgbColor || {};
+ const hexString = `#${this.valueToHex(r)}${this.valueToHex(g)}${this.valueToHex(b)}`;
+ const opacity = this.colorAlpha ? Math.round((this.colorAlpha / 100) * 255) : 0;
+
+ return `${hexString}${includeAlpha ? this.valueToHex(opacity) : ''}`;
+ },
+
+ setPickerInputValues() {
+ const { a, ...rgb } = this.pickerColor;
+
+ if (!_.isEqual(this.rgbColor, rgb)) {
+ this.colorByFormats = JSON.parse(JSON.stringify({
+ [COLOR_ALPHA]: a,
+ [COLOR_FORMAT_RGB]: rgb,
+ [COLOR_FORMAT_HEX]: this.rgbToHex(rgb)
+ }));
+ }
+ },
+
+ valueToHex(value) {
+ const hex = value.toString(16);
+
+ return hex.length === 1 ? '0' + hex : hex;
}
}
}));
diff --git a/frontend/express/public/javascripts/countly/vue/templates/UI/color-picker.html b/frontend/express/public/javascripts/countly/vue/templates/UI/color-picker.html
index 06a5bece2ff..b39323f3940 100644
--- a/frontend/express/public/javascripts/countly/vue/templates/UI/color-picker.html
+++ b/frontend/express/public/javascripts/countly/vue/templates/UI/color-picker.html
@@ -3,7 +3,7 @@
class="cly-vue-color-picker__input-container"
:data-test-id="testId"
@click.stop="onInputContainerClick"
- >
+ >
@@ -29,16 +31,59 @@
+
diff --git a/frontend/express/public/javascripts/countly/vue/templates/content/UI/content-builder-sidebar-step.html b/frontend/express/public/javascripts/countly/vue/templates/content/UI/content-builder-sidebar-step.html
new file mode 100644
index 00000000000..848933c31a7
--- /dev/null
+++ b/frontend/express/public/javascripts/countly/vue/templates/content/UI/content-builder-sidebar-step.html
@@ -0,0 +1,39 @@
+
diff --git a/frontend/express/public/javascripts/countly/vue/templates/content/UI/content-sidebar-input.html b/frontend/express/public/javascripts/countly/vue/templates/content/UI/content-sidebar-input.html
index 5739273bfcb..12896b1fd8a 100644
--- a/frontend/express/public/javascripts/countly/vue/templates/content/UI/content-sidebar-input.html
+++ b/frontend/express/public/javascripts/countly/vue/templates/content/UI/content-sidebar-input.html
@@ -1,96 +1,117 @@
-