@oxyshop/admin 1.3.49 → 1.3.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ }