@oxyshop/admin 1.3.49 → 1.3.51

Sign up to get free protection for your applications and to get access to all the features.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyshop/admin",
3
- "version": "1.3.49",
3
+ "version": "1.3.51",
4
4
  "author": "oXy Online s.r.o. <info@oxyshop.cz>",
5
5
  "main": "lib/index.js",
6
6
  "sass": "scss/main.scss",
@@ -19,15 +19,15 @@
19
19
  },
20
20
  "dependencies": {
21
21
  "@oxyshop/core": "^1.3.0",
22
- "chart.js": "~2.9.0",
22
+ "chart.js": "~3.7.0",
23
23
  "jquery.dirtyforms": "~2.0.0",
24
24
  "lightbox2": "~2.11.0",
25
- "semantic-ui-css": "~2.4.0",
25
+ "semantic-ui-css": "~2.5.0",
26
26
  "slick-carousel": "~1.8.0"
27
27
  },
28
28
  "devDependencies": {
29
- "@rollup/plugin-alias": "~3.1.0",
30
- "rollup": "~2.42.0",
31
- "rollup-plugin-scss": "~2.6.0"
29
+ "@rollup/plugin-alias": "~4.0.0",
30
+ "rollup": "~2.79.0",
31
+ "rollup-plugin-scss": "~4.0.0"
32
32
  }
33
33
  }
@@ -0,0 +1,65 @@
1
+ ;(function ($) {
2
+ 'use strict'
3
+
4
+ $.fn.extend({
5
+ articleSlugGenerator: function () {
6
+ let timeout
7
+
8
+ $('[name*="nextgen_cms_bundle_article[translations]"][name*="[name]"]').on('input', function () {
9
+ clearTimeout(timeout)
10
+ let element = $(this)
11
+
12
+ timeout = setTimeout(function () {
13
+ updateSlug(element)
14
+ }, 1000)
15
+ })
16
+
17
+ $('.toggle-article-slug-modification').on('click', function (e) {
18
+ e.preventDefault()
19
+ toggleSlugModification($(this), $(this).siblings('input'))
20
+ })
21
+
22
+ function updateSlug(element) {
23
+ let slugInput = element.parents('.content').find('[name*="[slug]"]')
24
+ let loadableParent = slugInput.parents('.field.loadable')
25
+
26
+ if ('readonly' === slugInput.attr('readonly')) {
27
+ return
28
+ }
29
+
30
+ loadableParent.addClass('loading')
31
+
32
+ $.ajax({
33
+ type: 'GET',
34
+ url: slugInput.attr('data-url'),
35
+ data: { name: element.val() },
36
+ dataType: 'json',
37
+ accept: 'application/json',
38
+ success: function (data) {
39
+ slugInput.val(data.slug)
40
+ if (slugInput.parents('.field').hasClass('error')) {
41
+ slugInput.parents('.field').removeClass('error')
42
+ slugInput.parents('.field').find('.sylius-validation-error').remove()
43
+ }
44
+ loadableParent.removeClass('loading')
45
+ },
46
+ })
47
+ }
48
+
49
+ function toggleSlugModification(button, slugInput) {
50
+ if (slugInput.attr('readonly')) {
51
+ slugInput.removeAttr('readonly')
52
+ button.html('<i class="unlock icon"></i>')
53
+ } else {
54
+ slugInput.attr('readonly', 'readonly')
55
+ button.html('<i class="lock icon"></i>')
56
+ }
57
+ }
58
+ },
59
+ })
60
+ })(jQuery)
61
+ ;(function ($) {
62
+ $(document).ready(function () {
63
+ $(this).articleSlugGenerator()
64
+ })
65
+ })(jQuery)
@@ -0,0 +1,71 @@
1
+ ;(function ($) {
2
+ 'use strict'
3
+
4
+ $.fn.extend({
5
+ blogCategorySlugGenerator: function () {
6
+ let timeout
7
+
8
+ $('[name*="nextgen_cms_bundle_blog_category[translations]"][name*="[name]"]').on('input', function () {
9
+ clearTimeout(timeout)
10
+ let element = $(this)
11
+
12
+ timeout = setTimeout(function () {
13
+ updateSlug(element)
14
+ }, 1000)
15
+ })
16
+
17
+ $('.toggle-blog-category-slug-modification').on('click', function (e) {
18
+ e.preventDefault()
19
+ toggleSlugModification($(this), $(this).siblings('input'))
20
+ })
21
+
22
+ function updateSlug(element) {
23
+ let slugInput = element.parents('.content').find('[name*="[slug]"]')
24
+ let loadableParent = slugInput.parents('.field.loadable')
25
+
26
+ if ('readonly' === slugInput.attr('readonly')) {
27
+ return
28
+ }
29
+
30
+ loadableParent.addClass('loading')
31
+
32
+ let data
33
+ data = {
34
+ name: element.val(),
35
+ locale: element.closest('[data-locale]').data('locale'),
36
+ }
37
+
38
+ $.ajax({
39
+ type: 'GET',
40
+ url: slugInput.attr('data-url'),
41
+ data: data,
42
+ dataType: 'json',
43
+ accept: 'application/json',
44
+ success: function (data) {
45
+ slugInput.val(data.slug)
46
+ if (slugInput.parents('.field').hasClass('error')) {
47
+ slugInput.parents('.field').removeClass('error')
48
+ slugInput.parents('.field').find('.sylius-validation-error').remove()
49
+ }
50
+ loadableParent.removeClass('loading')
51
+ },
52
+ })
53
+ }
54
+
55
+ function toggleSlugModification(button, slugInput) {
56
+ if (slugInput.attr('readonly')) {
57
+ slugInput.removeAttr('readonly')
58
+ button.html('<i class="unlock icon"></i>')
59
+ } else {
60
+ slugInput.attr('readonly', 'readonly')
61
+ button.html('<i class="lock icon"></i>')
62
+ }
63
+ }
64
+ },
65
+ })
66
+ })(jQuery)
67
+ ;(function ($) {
68
+ $(document).ready(function () {
69
+ $(this).blogCategorySlugGenerator()
70
+ })
71
+ })(jQuery)
@@ -0,0 +1,65 @@
1
+ ;(function ($) {
2
+ 'use strict'
3
+
4
+ $.fn.extend({
5
+ blogSlugGenerator: function () {
6
+ let timeout
7
+
8
+ $('[name*="nextgen_cms_bundle_blog[translations]"][name*="[name]"]').on('input', function () {
9
+ clearTimeout(timeout)
10
+ let element = $(this)
11
+
12
+ timeout = setTimeout(function () {
13
+ updateSlug(element)
14
+ }, 1000)
15
+ })
16
+
17
+ $('.toggle-blog-slug-modification').on('click', function (e) {
18
+ e.preventDefault()
19
+ toggleSlugModification($(this), $(this).siblings('input'))
20
+ })
21
+
22
+ function updateSlug(element) {
23
+ let slugInput = element.parents('.content').find('[name*="[slug]"]')
24
+ let loadableParent = slugInput.parents('.field.loadable')
25
+
26
+ if ('readonly' === slugInput.attr('readonly')) {
27
+ return
28
+ }
29
+
30
+ loadableParent.addClass('loading')
31
+
32
+ $.ajax({
33
+ type: 'GET',
34
+ url: slugInput.attr('data-url'),
35
+ data: { name: element.val() },
36
+ dataType: 'json',
37
+ accept: 'application/json',
38
+ success: function (data) {
39
+ slugInput.val(data.slug)
40
+ if (slugInput.parents('.field').hasClass('error')) {
41
+ slugInput.parents('.field').removeClass('error')
42
+ slugInput.parents('.field').find('.sylius-validation-error').remove()
43
+ }
44
+ loadableParent.removeClass('loading')
45
+ },
46
+ })
47
+ }
48
+
49
+ function toggleSlugModification(button, slugInput) {
50
+ if (slugInput.attr('readonly')) {
51
+ slugInput.removeAttr('readonly')
52
+ button.html('<i class="unlock icon"></i>')
53
+ } else {
54
+ slugInput.attr('readonly', 'readonly')
55
+ button.html('<i class="lock icon"></i>')
56
+ }
57
+ }
58
+ },
59
+ })
60
+ })(jQuery)
61
+ ;(function ($) {
62
+ $(document).ready(function () {
63
+ $(this).blogSlugGenerator()
64
+ })
65
+ })(jQuery)
@@ -0,0 +1,33 @@
1
+ export default class DocumentUploadInput {
2
+ /** @param {string} documentUploadInputSelector */
3
+ constructor(documentUploadInputSelector) {
4
+ this.selector = documentUploadInputSelector
5
+ }
6
+
7
+ init() {
8
+ const fileInputs = document.querySelectorAll(this.selector)
9
+
10
+ Array.prototype.forEach.call(fileInputs, (inputElement) => {
11
+ const fileUrl = inputElement.getAttribute('data-file-url')
12
+ const fileTitle = inputElement.getAttribute('data-file-name')
13
+ const downloadText = inputElement.getAttribute('data-download-translation')
14
+
15
+ const iconElement = document.createElement('i')
16
+ iconElement.classList.add('download', 'icon')
17
+
18
+ const buttonElement = document.createElement('button')
19
+ buttonElement.classList.add('ui', 'compact', 'labeled', 'icon', 'button')
20
+ buttonElement.setAttribute('type', 'button')
21
+ buttonElement.append(iconElement)
22
+ buttonElement.innerHTML += `${downloadText}: ${fileTitle}`
23
+
24
+ const downloadLink = document.createElement('a')
25
+ downloadLink.setAttribute('style', 'display: inline-block; margin: -5px 0 25px 0;')
26
+ downloadLink.setAttribute('target', '_blank')
27
+ downloadLink.setAttribute('href', fileUrl)
28
+ downloadLink.append(buttonElement)
29
+
30
+ inputElement.parentNode.parentNode.append(downloadLink)
31
+ })
32
+ }
33
+ }
@@ -13,7 +13,13 @@ export default class FeedCategorySelect {
13
13
  cache: false,
14
14
  },
15
15
  minCharacters: 2,
16
+ clearable: true,
16
17
  })
18
+
19
+ // Workaround for dropdown clear button not showing after loading initial value from the API
20
+ setTimeout(() => {
21
+ $(selectElement).dropdown('restore defaults')
22
+ }, 0)
17
23
  })
18
24
  }
19
25
  }
@@ -0,0 +1,41 @@
1
+ export default class ImageInput {
2
+ constructor(selector) {
3
+ this.selector = selector
4
+ }
5
+
6
+ init() {
7
+ document.querySelectorAll(this.selector).forEach((element) => {
8
+ element.querySelectorAll('input[type="file"]').forEach((input) => {
9
+ input.addEventListener('change', () => {
10
+ this.onImageInputChange(input)
11
+ })
12
+ })
13
+ })
14
+ }
15
+
16
+ onImageInputChange(input) {
17
+ if (!input.files || !input.files[0]) {
18
+ return
19
+ }
20
+
21
+ const reader = new FileReader()
22
+
23
+ reader.onload = (event) => {
24
+ const wrapperDiv = input.parentElement.parentElement
25
+ const image = wrapperDiv.querySelector('.image')
26
+
27
+ if (null === image) {
28
+ const img = document.createElement('img')
29
+ img.setAttribute('class', 'ui small bordered image')
30
+ img.setAttribute('src', event.target.result)
31
+
32
+ const inputLabel = wrapperDiv.querySelector(':scope > label')
33
+ wrapperDiv.insertBefore(img, inputLabel.nextSibling)
34
+ } else {
35
+ image.setAttribute('src', event.target.result)
36
+ }
37
+ }
38
+
39
+ reader.readAsDataURL(input.files[0])
40
+ }
41
+ }
@@ -0,0 +1,86 @@
1
+ import { Chart } from 'chart.js'
2
+
3
+ export default class ProductVariantPricingGraph {
4
+ constructor(containerSelector) {
5
+ this.containerSelector = containerSelector
6
+ }
7
+
8
+ init() {
9
+ document.querySelectorAll(this.containerSelector).forEach((componentContainer) => {
10
+ const graphsData = JSON.parse(componentContainer.getAttribute('data-graph'))
11
+
12
+ for (const currencyGraphIndex in graphsData) {
13
+ const currencyGraph = graphsData[currencyGraphIndex]
14
+
15
+ const currencyCodeHeader = document.createElement('h4')
16
+ currencyCodeHeader.classList.add('ui', 'header')
17
+ currencyCodeHeader.textContent = currencyGraph.currency
18
+
19
+ const graphCanvas = document.createElement('canvas')
20
+ graphCanvas.width = 400
21
+ graphCanvas.height = 200
22
+
23
+ const graphContainer = document.createElement('div')
24
+ graphContainer.append(currencyCodeHeader)
25
+ graphContainer.append(graphCanvas)
26
+
27
+ componentContainer.append(graphContainer)
28
+
29
+ const graphConfig = this.getGraphConfig(currencyGraph.graph)
30
+ new Chart(graphCanvas.getContext('2d'), graphConfig)
31
+ }
32
+ })
33
+ }
34
+
35
+ /** @private */
36
+ getGraphConfig(rawGraphData) {
37
+ return {
38
+ type: 'line',
39
+ data: {
40
+ datasets: rawGraphData.datasets.map((rawDataset) => {
41
+ return {
42
+ label: rawDataset.label,
43
+ data: rawDataset.data,
44
+ borderColor: this.getRandomRgbColor(),
45
+ fill: false,
46
+ steppedLine: true,
47
+ pointRadius: 8,
48
+ pointHoverRadius: 10,
49
+ }
50
+ }),
51
+ },
52
+ options: {
53
+ maintainAspectRatio: true,
54
+ responsive: true,
55
+ scales: {
56
+ xAxes: [
57
+ {
58
+ type: 'linear',
59
+ position: 'bottom',
60
+ scaleLabel: {
61
+ display: true,
62
+ labelString: rawGraphData.xAxisLabel,
63
+ },
64
+ },
65
+ ],
66
+ yAxes: [
67
+ {
68
+ scaleLabel: {
69
+ display: true,
70
+ labelString: rawGraphData.yAxisLabel,
71
+ },
72
+ },
73
+ ],
74
+ },
75
+ },
76
+ }
77
+ }
78
+
79
+ /** @private */
80
+ getRandomRgbColor() {
81
+ const r = Math.floor(Math.random() * 255)
82
+ const g = Math.floor(Math.random() * 255)
83
+ const b = Math.floor(Math.random() * 255)
84
+ return `rgb(${r},${g},${b})`
85
+ }
86
+ }
@@ -0,0 +1,276 @@
1
+ import axios from 'axios'
2
+ import { Chart } from 'chart.js'
3
+
4
+ export default class ProductVariantPricingSimulation {
5
+ constructor(selector) {
6
+ this.selector = selector
7
+ }
8
+
9
+ init() {
10
+ document.querySelectorAll(this.selector).forEach((element) => {
11
+ const productVariantId = element.getAttribute('data-product-variant-id')
12
+ const channels = JSON.parse(element.getAttribute('data-channels'))
13
+ const searchApiEndpoint = element.getAttribute('data-customer-search-url')
14
+ const pricingApiEndpoint = element.getAttribute('data-pricing-url')
15
+
16
+ this.initElement(element, productVariantId, channels, searchApiEndpoint, pricingApiEndpoint)
17
+ })
18
+ }
19
+
20
+ initElement(parentElement, productVariantId, channels, searchApiEndpoint, pricingApiEndpoint) {
21
+ parentElement.innerHTML = ''
22
+
23
+ const simulationData = {
24
+ productVariantId: productVariantId,
25
+ channelId: null,
26
+ currencyId: null,
27
+ customerId: null,
28
+ }
29
+
30
+ const submitButton = document.createElement('button')
31
+ const currencySelect = document.createElement('select')
32
+
33
+ const onChannelSelect = (event) => {
34
+ const selectedChannelId = parseInt(event.target.value)
35
+ simulationData.channelId = selectedChannelId
36
+ simulationData.currencyId = null
37
+ submitButton.disabled = this.isSubmitButtonDisabled(simulationData)
38
+
39
+ const selectedChannel = channels.find((channel) => {
40
+ return channel.id === selectedChannelId
41
+ })
42
+
43
+ currencySelect.innerHTML = ''
44
+ this.addSelectOptions(currencySelect, selectedChannel.currencies, 'Select currency')
45
+ }
46
+
47
+ const onCurrencySelect = (event) => {
48
+ simulationData.currencyId = event.target.value
49
+ submitButton.disabled = this.isSubmitButtonDisabled(simulationData)
50
+ }
51
+
52
+ const onCustomerSelect = (event) => {
53
+ simulationData.customerId = event.target.value
54
+ submitButton.disabled = this.isSubmitButtonDisabled(simulationData)
55
+ }
56
+
57
+ const graphWrapper = this.createGraphWrapper()
58
+
59
+ const onSubmit = () => {
60
+ const pricingUrl = `${pricingApiEndpoint}?${this.serializeObjectToQuery(simulationData)}`
61
+
62
+ axios
63
+ .get(pricingUrl)
64
+ .then((response) => {
65
+ const chartData = response.data
66
+ graphWrapper.setAttribute('data-debug-chart', JSON.stringify(chartData)) // For e2e tests
67
+ this.renderGraph(graphWrapper, chartData)
68
+ })
69
+ .catch((error) => {
70
+ console.error(error)
71
+ graphWrapper.innerHTML = 'Pricing simulation error'
72
+ })
73
+ }
74
+
75
+ const wrapper = document.createElement('div')
76
+ wrapper.setAttribute('style', 'border: solid 1px #ddd; background-color: #fafafa;')
77
+
78
+ const filters = this.createFilters(
79
+ channels,
80
+ currencySelect,
81
+ onChannelSelect,
82
+ onCurrencySelect,
83
+ onCustomerSelect,
84
+ onSubmit,
85
+ submitButton,
86
+ searchApiEndpoint
87
+ )
88
+ filters.setAttribute(
89
+ 'style',
90
+ 'display: grid; grid-template-columns: 1fr 1fr 1fr 60px; grid-gap: 10px; ' +
91
+ 'padding: 15px; border-bottom: solid 1px #ddd;'
92
+ )
93
+
94
+ const graphFiller = this.createGraphFiller()
95
+ graphWrapper.append(graphFiller)
96
+
97
+ wrapper.append(filters, graphWrapper)
98
+ parentElement.append(wrapper)
99
+ }
100
+
101
+ createFilters(
102
+ channels,
103
+ currencySelect,
104
+ onChannelSelect,
105
+ onCurrencySelect,
106
+ onCustomerSelect,
107
+ onSubmit,
108
+ submitButton,
109
+ searchApiEndpoint
110
+ ) {
111
+ const filtersWrapper = document.createElement('div')
112
+
113
+ const channelSelect = document.createElement('select')
114
+ channelSelect.classList.add('dirtyignore')
115
+ this.addSelectOptions(channelSelect, channels, 'Select channel')
116
+ channelSelect.addEventListener('change', onChannelSelect)
117
+
118
+ currencySelect.classList.add('dirtyignore')
119
+ currencySelect.addEventListener('change', onCurrencySelect)
120
+
121
+ const customerSelect = document.createElement('select')
122
+ customerSelect.classList.add('dirtyignore')
123
+ customerSelect.addEventListener('change', onCustomerSelect)
124
+
125
+ // this is delayed to avoid racing conditions
126
+ setTimeout(() => this.hookClientSearchOnSelect(customerSelect, searchApiEndpoint), 600)
127
+
128
+ submitButton.disabled = true
129
+ submitButton.setAttribute('class', 'ui icon primary button')
130
+ submitButton.type = 'button'
131
+ submitButton.addEventListener('click', onSubmit)
132
+
133
+ const playIcon = document.createElement('i')
134
+ playIcon.setAttribute('class', 'icon play')
135
+ submitButton.append(playIcon)
136
+
137
+ filtersWrapper.append(channelSelect, currencySelect, customerSelect, submitButton)
138
+
139
+ return filtersWrapper
140
+ }
141
+
142
+ createGraphWrapper() {
143
+ const wrapper = document.createElement('div')
144
+ wrapper.setAttribute('style', 'padding: 15px;')
145
+
146
+ return wrapper
147
+ }
148
+
149
+ createGraphFiller() {
150
+ const filler = document.createElement('div')
151
+ filler.setAttribute(
152
+ 'style',
153
+ 'border-radius: 7px; background-color: #eee; height: 350px; display: flex; ' +
154
+ 'justify-content: space-around; align-items: center; font-size: 4em;'
155
+ )
156
+
157
+ const chartIcon = document.createElement('i')
158
+ chartIcon.setAttribute('class', 'icon chart line')
159
+ filler.append(chartIcon)
160
+
161
+ return filler
162
+ }
163
+
164
+ addSelectOptions(select, choices, placeholder = null) {
165
+ if (placeholder !== null) {
166
+ const placeholderOption = document.createElement('option')
167
+ placeholderOption.innerHTML = placeholder
168
+ placeholderOption.disabled = true
169
+ placeholderOption.selected = true
170
+ select.append(placeholderOption)
171
+ }
172
+
173
+ for (const property in choices) {
174
+ const choice = choices[property]
175
+
176
+ const channelOption = document.createElement('option')
177
+ channelOption.innerHTML = choice.name
178
+ channelOption.setAttribute('value', choice.id)
179
+ select.append(channelOption)
180
+ }
181
+
182
+ return select
183
+ }
184
+
185
+ hookClientSearchOnSelect(selectElement, searchApiEndpoint) {
186
+ selectElement.setAttribute('class', `${selectElement.getAttribute('class')} search dropdown selection`)
187
+
188
+ $(selectElement).dropdown({
189
+ apiSettings: {
190
+ url: `${searchApiEndpoint}/{query}`,
191
+ cache: false,
192
+ },
193
+
194
+ minCharacters: 2,
195
+ })
196
+ }
197
+
198
+ isSubmitButtonDisabled(simulationData) {
199
+ return (
200
+ null === simulationData.productVariantId ||
201
+ null === simulationData.channelId ||
202
+ null === simulationData.currencyId ||
203
+ null === simulationData.customerId
204
+ )
205
+ }
206
+
207
+ serializeObjectToQuery(object) {
208
+ const query = []
209
+
210
+ for (const part in object) {
211
+ if (Object.prototype.hasOwnProperty.call(object, part)) {
212
+ query.push(`${encodeURIComponent(part)}=${encodeURIComponent(object[part])}`)
213
+ }
214
+ }
215
+
216
+ return query.join('&')
217
+ }
218
+
219
+ renderGraph(graphWrapper, graphData) {
220
+ graphWrapper.innerHTML = ''
221
+
222
+ const graphCanvas = document.createElement('canvas')
223
+ graphCanvas.width = 600
224
+ graphCanvas.height = 200
225
+
226
+ graphWrapper.append(graphCanvas)
227
+
228
+ const graphConfig = this.getGraphConfig(graphData)
229
+ console.log('graphConfig', graphConfig)
230
+ new Chart(graphCanvas.getContext('2d'), graphConfig)
231
+ }
232
+
233
+ /** @private */
234
+ getGraphConfig(rawGraphData) {
235
+ return {
236
+ type: 'line',
237
+ data: {
238
+ datasets: rawGraphData.datasets.map((rawDataset) => {
239
+ return {
240
+ label: rawDataset.label,
241
+ data: rawDataset.data,
242
+ borderColor: '#0000ff',
243
+ fill: false,
244
+ steppedLine: true,
245
+ pointRadius: 8,
246
+ pointHoverRadius: 10,
247
+ }
248
+ }),
249
+ },
250
+ options: {
251
+ maintainAspectRatio: true,
252
+ responsive: true,
253
+ scales: {
254
+ xAxes: [
255
+ {
256
+ type: 'linear',
257
+ position: 'bottom',
258
+ scaleLabel: {
259
+ display: true,
260
+ labelString: rawGraphData.xAxisLabel,
261
+ },
262
+ },
263
+ ],
264
+ yAxes: [
265
+ {
266
+ scaleLabel: {
267
+ display: true,
268
+ labelString: rawGraphData.yAxisLabel,
269
+ },
270
+ },
271
+ ],
272
+ },
273
+ },
274
+ }
275
+ }
276
+ }