@haus-tech/product-import-export-plugin 3.0.21 → 3.0.24-0

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.
Files changed (77) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +90 -0
  3. package/package.json +8 -14
  4. package/{dist → src}/api/plugin.controller.js +3 -2
  5. package/src/api/plugin.controller.js.map +1 -0
  6. package/{dist → src}/api/product-export.controller.d.ts +1 -1
  7. package/{dist → src}/api/product-export.controller.js +28 -13
  8. package/src/api/product-export.controller.js.map +1 -0
  9. package/{dist → src}/api/product-import.controller.d.ts +0 -1
  10. package/{dist → src}/api/product-import.controller.js +3 -2
  11. package/src/api/product-import.controller.js.map +1 -0
  12. package/{dist → src}/constants.js +1 -0
  13. package/src/constants.js.map +1 -0
  14. package/{dist → src}/helpers/generate-asset-hash.js +2 -2
  15. package/src/helpers/generate-asset-hash.js.map +1 -0
  16. package/{dist → src}/helpers/normalize-string.js +2 -2
  17. package/src/helpers/normalize-string.js.map +1 -0
  18. package/{dist → src}/helpers/remove-update-at-fields.js +2 -2
  19. package/src/helpers/remove-update-at-fields.js.map +1 -0
  20. package/{dist → src}/index.js +1 -0
  21. package/src/index.js.map +1 -0
  22. package/{dist → src}/product-import-export.plugin.js +23 -11
  23. package/src/product-import-export.plugin.js.map +1 -0
  24. package/{dist → src}/providers/import-providers/asset-importer.d.ts +0 -1
  25. package/{dist → src}/providers/import-providers/asset-importer.js +3 -2
  26. package/src/providers/import-providers/asset-importer.js.map +1 -0
  27. package/{dist → src}/providers/import-providers/import-parser.d.ts +0 -1
  28. package/{dist → src}/providers/import-providers/import-parser.js +4 -3
  29. package/src/providers/import-providers/import-parser.js.map +1 -0
  30. package/{dist → src}/providers/import-providers/product-importer.d.ts +0 -1
  31. package/{dist → src}/providers/import-providers/product-importer.js +5 -4
  32. package/src/providers/import-providers/product-importer.js.map +1 -0
  33. package/{dist → src}/services/extended-fast-importer.service.js +3 -2
  34. package/src/services/extended-fast-importer.service.js.map +1 -0
  35. package/{dist → src}/services/product-export.service.js +28 -18
  36. package/src/services/product-export.service.js.map +1 -0
  37. package/{dist → src}/services/product-import.service.d.ts +0 -1
  38. package/{dist → src}/services/product-import.service.js +3 -2
  39. package/src/services/product-import.service.js.map +1 -0
  40. package/{dist → src}/types.js +1 -0
  41. package/src/types.js.map +1 -0
  42. package/{dist → src}/ui/components/export-dialog.component.js +3 -2
  43. package/src/ui/components/export-dialog.component.js.map +1 -0
  44. package/{dist → src}/ui/components/product-import.component.js +3 -2
  45. package/src/ui/components/product-import.component.js.map +1 -0
  46. package/{dist → src}/ui/product-export.service.js +3 -2
  47. package/src/ui/product-export.service.js.map +1 -0
  48. package/src/ui/providers.d.ts +3 -0
  49. package/{dist → src}/ui/providers.js +1 -0
  50. package/src/ui/providers.js.map +1 -0
  51. package/src/ui/routes.d.ts +31 -0
  52. package/{dist → src}/ui/routes.js +1 -0
  53. package/src/ui/routes.js.map +1 -0
  54. package/dist/ui/components/export-dialog.component.html +0 -83
  55. package/dist/ui/components/export-dialog.component.ts +0 -165
  56. package/dist/ui/components/product-import.component.html +0 -123
  57. package/dist/ui/components/product-import.component.ts +0 -550
  58. package/dist/ui/product-export.service.ts +0 -107
  59. package/dist/ui/providers.d.ts +0 -2
  60. package/dist/ui/providers.ts +0 -57
  61. package/dist/ui/routes.d.ts +0 -31
  62. package/dist/ui/routes.ts +0 -11
  63. package/dist/ui/translations/en.json +0 -53
  64. package/dist/ui/translations/sv.json +0 -53
  65. /package/{dist → src}/api/plugin.controller.d.ts +0 -0
  66. /package/{dist → src}/constants.d.ts +0 -0
  67. /package/{dist → src}/helpers/generate-asset-hash.d.ts +0 -0
  68. /package/{dist → src}/helpers/normalize-string.d.ts +0 -0
  69. /package/{dist → src}/helpers/remove-update-at-fields.d.ts +0 -0
  70. /package/{dist → src}/index.d.ts +0 -0
  71. /package/{dist → src}/product-import-export.plugin.d.ts +0 -0
  72. /package/{dist → src}/services/extended-fast-importer.service.d.ts +0 -0
  73. /package/{dist → src}/services/product-export.service.d.ts +0 -0
  74. /package/{dist → src}/types.d.ts +0 -0
  75. /package/{dist → src}/ui/components/export-dialog.component.d.ts +0 -0
  76. /package/{dist → src}/ui/components/product-import.component.d.ts +0 -0
  77. /package/{dist → src}/ui/product-export.service.d.ts +0 -0
@@ -1,550 +0,0 @@
1
- import { SharedModule, getServerLocation } from '@vendure/admin-ui/core'
2
- import { Component, ViewChild, ElementRef, ChangeDetectorRef, OnInit } from '@angular/core'
3
- import { NotificationService, LocalStorageService } from '@vendure/admin-ui/core'
4
- import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'
5
- import { TranslateService } from '@ngx-translate/core'
6
- import { endsWith, startsWith, size, uniq } from 'lodash'
7
- import { AdminUiConfig } from '@vendure/common/lib/shared-types'
8
- import { Channel, LanguageCode } from '@vendure/core'
9
-
10
- type UpdatingStrategy = 'replace' | 'merge'
11
-
12
- type ImportOptions = {
13
- updateProductSlug?: string
14
- }
15
-
16
- type ValidateReturnType = {
17
- isValid: boolean
18
- langCodes?: (string | undefined)[]
19
- clearFile?: boolean
20
- }
21
- export interface PluginInitOptions {
22
- visibleOptions?: Array<keyof ImportOptions>
23
- defaultOptions?: ImportOptions
24
- }
25
-
26
- @Component({
27
- selector: 'product-import',
28
- templateUrl: './product-import.component.html',
29
- standalone: true,
30
- imports: [SharedModule],
31
- })
32
- export class ProductImportComponent implements OnInit {
33
- serverPath: string
34
- validateFile: File | null = null
35
- selectedFile: File | null = null
36
- updateProductSlug = true
37
- selectedMainLanguage: LanguageCode | undefined
38
- availableLanguages: LanguageCode[] = []
39
- updatingStrategy: UpdatingStrategy = 'merge'
40
-
41
- @ViewChild('fileUpload', { static: false }) fileUploadElement: ElementRef<HTMLInputElement>
42
-
43
- config: PluginInitOptions
44
- appConfig: AdminUiConfig
45
- currentChannel: Channel
46
-
47
- constructor(
48
- private notificationService: NotificationService,
49
- private cd: ChangeDetectorRef,
50
- private translate: TranslateService,
51
- private localStorageService: LocalStorageService,
52
- ) {
53
- this.serverPath = getServerLocation()
54
- }
55
-
56
- async ngOnInit() {
57
- this.appConfig = await fetch('./vendure-ui-config.json').then((res) => res.json())
58
-
59
- this.config = await fetch(`${this.serverPath}/product-import-export/config`, {
60
- method: 'GET',
61
- headers: {
62
- 'Content-Type': 'application/json',
63
- },
64
- })
65
- .then((res) => res.json())
66
- .then((data) => data.importOptions)
67
- }
68
-
69
- doValidation(file: File): void {
70
- this.validateCsvStructure(file).then(({ isValid, langCodes, clearFile = true }) => {
71
- if (isValid) {
72
- this.selectedFile = file
73
- } else if (clearFile) {
74
- this.clearFile()
75
- }
76
-
77
- if (langCodes && !this.selectedMainLanguage) {
78
- this.availableLanguages = langCodes.filter((code) => code) as LanguageCode[]
79
- this.selectedMainLanguage =
80
- size(this.availableLanguages) === 1
81
- ? (this.availableLanguages?.[0] as LanguageCode)
82
- : undefined
83
- }
84
-
85
- this.cd.detectChanges()
86
- })
87
- }
88
-
89
- onMainLanguageChange() {
90
- this.cd.detectChanges()
91
-
92
- if (this.validateFile) {
93
- this.doValidation(this.validateFile)
94
- }
95
- }
96
-
97
- onFileSelected(event: Event): void {
98
- const input = event.target as HTMLInputElement
99
- if (input.files && input.files.length > 0) {
100
- const file = input.files[0]
101
- if (file.type !== 'text/csv') {
102
- this.notificationService.error(
103
- this.translate.instant('product-import.notifications.invalid-file-type'),
104
- )
105
- this.clearFile()
106
- return
107
- }
108
-
109
- this.validateFile = file
110
-
111
- this.doValidation(file)
112
- }
113
- }
114
-
115
- clearFile(): void {
116
- this.fileUploadElement.nativeElement.value = ''
117
- this.selectedFile = null
118
- this.validateFile = null
119
- this.selectedMainLanguage = undefined
120
- this.updatingStrategy = 'merge'
121
- }
122
-
123
- async validateCsvStructure(file: File): Promise<ValidateReturnType> {
124
- const baseColumns = ['sku']
125
-
126
- const optionalColumns = [
127
- 'assets',
128
- 'price',
129
- 'taxCategory',
130
- 'stockOnHand',
131
- 'trackInventory',
132
- 'variantAssets',
133
- ]
134
-
135
- const translatableBaseColumns = ['name']
136
-
137
- const translatableColumns = [
138
- 'slug',
139
- 'description',
140
- 'facets',
141
- 'optionGroups',
142
- 'optionValues',
143
- 'variantFacets',
144
- ]
145
-
146
- return new Promise<ValidateReturnType>((resolve) => {
147
- const reader = new FileReader()
148
- reader.onload = async (e) => {
149
- const text = e.target?.result as string
150
- const lines = text.split('\n')
151
-
152
- // Remove empty lines
153
- lines.forEach((line, index) => {
154
- if (line.trim() === '') {
155
- lines.splice(index, 1)
156
- }
157
- })
158
-
159
- // Check if number of columns for each line is consistent and the same as headers
160
- const headerLength = this.splitLines(lines[0]).length
161
- const inconsistentLines = lines.filter(
162
- (line) => this.splitLines(line).length !== headerLength,
163
- )
164
-
165
- if (inconsistentLines.length > 0) {
166
- console.log('Inconsistent number of columns in the CSV file.')
167
- this.notificationService.error(
168
- this.translate.instant('product-import.notifications.inconsistent-columns'),
169
- )
170
- resolve({ isValid: false })
171
- return
172
- }
173
-
174
- if (lines.length > 0) {
175
- const header = this.splitLines(lines[0]).map((col) => col.trim())
176
- console.log('CSV Header:', header)
177
-
178
- this.currentChannel = await fetch(`${this.serverPath}/product-import-export/channel`, {
179
- method: 'GET',
180
- headers: {
181
- 'Content-Type': 'application/json',
182
- ...this.getRequestHeaders(),
183
- },
184
- })
185
- .then((res) => res.json())
186
- .then((data) => data)
187
- const availableLanguages = this.currentChannel.availableLanguageCodes
188
-
189
- // Extract all language codes from headers
190
- const languageCodes = header
191
- .map((col) => col.match(/:(\w{2})$/)?.[1])
192
- .filter((code, index, self) => code && self.indexOf(code) === index)
193
-
194
- console.log('Language Codes:', languageCodes)
195
-
196
- let validationLanguage = size(languageCodes) === 0 ? undefined : this.selectedMainLanguage
197
-
198
- if (!this.selectedMainLanguage && size(languageCodes) < 2) {
199
- validationLanguage = (languageCodes as LanguageCode[])?.[0]
200
- }
201
-
202
- if (!this.selectedMainLanguage && size(languageCodes) > 1) {
203
- console.log(
204
- 'Multiple languages found in the CSV file. Please select the main language.',
205
- )
206
- resolve({ isValid: true, langCodes: languageCodes, clearFile: false })
207
- return
208
- }
209
-
210
- if (!this.selectedMainLanguage && size(languageCodes) === 0) {
211
- resolve({ isValid: true, langCodes: availableLanguages, clearFile: false })
212
- return
213
- }
214
-
215
- console.log('Validation language:', validationLanguage)
216
-
217
- // Check if csv-file includes languages that are not available in the system
218
-
219
- const missingLanguages = (languageCodes as LanguageCode[]).filter(
220
- (code) => !availableLanguages.includes(code),
221
- )
222
- if (missingLanguages.length > 0) {
223
- const missingLangs = missingLanguages.join(', ')
224
- console.log('Missing languages:', missingLangs)
225
- this.notificationService.error(
226
- `${this.translate.instant(
227
- 'product-import.notifications.missing-languages',
228
- )} ${missingLangs}`,
229
- )
230
- resolve({ isValid: false })
231
- return
232
- }
233
-
234
- // Validate base columns
235
- const missingBaseColumns = baseColumns.filter((col) => !header.includes(col))
236
- const missingTranslatableBaseColumns = translatableBaseColumns.filter(
237
- (col) => !header.includes(validationLanguage ? `${col}:${validationLanguage}` : col),
238
- )
239
-
240
- const combinedMissingBaseColumns = [
241
- ...missingBaseColumns,
242
- ...missingTranslatableBaseColumns,
243
- ]
244
- if (combinedMissingBaseColumns.length > 0) {
245
- const missingColumns = combinedMissingBaseColumns.join(', ')
246
- console.log('Missing base columns:', missingColumns)
247
- this.notificationService.error(
248
- `${this.translate.instant(
249
- 'product-import.notifications.missing-base-columns',
250
- )} ${missingColumns}`,
251
- )
252
- resolve({ isValid: false })
253
- return
254
- }
255
-
256
- const nameColumn = `name:${validationLanguage}` || 'name'
257
-
258
- // Get optionGroups and optionValues columns for the main language
259
- const optionGroupsColumn = `optionGroups:${validationLanguage}` || 'optionGroups'
260
- const optionValuesColumn = `optionValues:${validationLanguage}` || 'optionValues'
261
-
262
- const optionGroupsIndex = header.indexOf(optionGroupsColumn)
263
- const optionValuesIndex = header.indexOf(optionValuesColumn)
264
-
265
- // if (optionGroupsIndex === -1 || optionValuesIndex === -1) {
266
- // console.log(
267
- // `Missing optionGroups or optionValues columns for language: ${validationLanguage}`,
268
- // )
269
- // this.notificationService.error(
270
- // `${this.translate.instant(
271
- // 'product-import.notifications.missing-option-columns',
272
- // )} ${validationLanguage}`,
273
- // )
274
- // resolve({ isValid: false })
275
- // return
276
- // }
277
-
278
- // Loop through each row to validate the option values
279
- let currentOptionGroupCount = 0
280
-
281
- for (let i = 1; i < lines.length; i++) {
282
- const row = this.splitLines(lines[i]).map((col) => col.trim())
283
-
284
- // Check if it's a product row (it has a 'name' field)
285
- const isProductRow = row[header.indexOf(nameColumn)]
286
-
287
- if (isProductRow) {
288
- // It's a new product row, get the option groups
289
- const optionGroups = (row[optionGroupsIndex]?.split('|') ?? []).filter(
290
- (group) => group !== '',
291
- )
292
- const optionValues = (row[optionValuesIndex]?.split('|') ?? []).filter(
293
- (value) => value !== '',
294
- )
295
- currentOptionGroupCount = optionGroups.length
296
-
297
- // Validate that the number of option values matches the number of option groups
298
- if (optionValues.length !== currentOptionGroupCount) {
299
- this.notificationService.error(
300
- `${this.translate.instant(
301
- 'product-import.notifications.mismatched-option-values',
302
- )} Row: ${i + 1}. Expected ${currentOptionGroupCount} option values but got ${optionValues.length}.`,
303
- )
304
- resolve({ isValid: false })
305
- return
306
- }
307
- } else {
308
- // It's a variant row, validate the option values
309
- const optionValues = (row[optionValuesIndex]?.split('|') ?? []).filter(
310
- (value) => value !== '',
311
- )
312
-
313
- // Validate that the number of option values matches the product's option groups
314
- if (optionValues.length !== currentOptionGroupCount) {
315
- this.notificationService.error(
316
- `${this.translate.instant(
317
- 'product-import.notifications.mismatched-option-values',
318
- )} Row: ${i + 1}. Expected ${currentOptionGroupCount} option values but got ${optionValues.length}. ${JSON.stringify(
319
- optionValues,
320
- )}`,
321
- )
322
- resolve({ isValid: false })
323
- return
324
- }
325
- }
326
- }
327
-
328
- console.log('All option values for the main language are valid.')
329
-
330
- // Check if a row contains JSON, if so check if it is valid. This should be done for all columns, not just translatable ones.
331
- for (let i = 1; i < lines.length; i++) {
332
- const row = this.splitLines(lines[i]).map((col) => col.trim())
333
- for (const col of header) {
334
- const colIndex = header.indexOf(col)
335
- if (colIndex !== -1 && row[colIndex]) {
336
- try {
337
- const { isJson } = this.checkifJson(row[colIndex])
338
- if (isJson) {
339
- JSON.parse(
340
- row[colIndex]
341
- .trim()
342
- .replace(/^"/, '')
343
- .replace(/"$/, '')
344
- .replace(/'/g, '"')
345
- .replace(/\s+/g, ' ')
346
- .replace(/,\s*'/g, ", '"),
347
- )
348
- }
349
- } catch (error) {
350
- console.log(`Invalid JSON in column: ${col}, row: ${i + 1}`)
351
- this.notificationService.error(
352
- this.translate.instant('product-import.notifications.invalid-json', {
353
- col,
354
- row: i + 1,
355
- }),
356
- )
357
- resolve({ isValid: false })
358
- return
359
- }
360
- }
361
- }
362
- }
363
- console.log('All JSON values are valid.')
364
-
365
- const { errorRows: missingBaseTranslatableValuesRows } = this.checkTranslatabeColumns(
366
- lines,
367
- translatableBaseColumns,
368
- header,
369
- languageCodes,
370
- )
371
-
372
- if (missingBaseTranslatableValuesRows.length > 0) {
373
- console.log('Missing translation values in rows:', missingBaseTranslatableValuesRows)
374
- this.notificationService.error(
375
- `${this.translate.instant(
376
- 'product-import.notifications.missing-main-language-name',
377
- )} Rows: ${uniq(missingBaseTranslatableValuesRows).join(', ')}`,
378
- )
379
-
380
- resolve({ isValid: false })
381
- return
382
- }
383
-
384
- // Check if all translatable columns have values for the main language
385
- const { errorRows, errorColumns } = this.checkTranslatabeColumns(
386
- lines,
387
- translatableColumns,
388
- header,
389
- languageCodes,
390
- )
391
-
392
- if (errorRows.length > 0) {
393
- console.log('Missing translation values in rows:', errorRows)
394
- this.notificationService.warning(
395
- `${this.translate.instant(
396
- 'product-import.notifications.missing-translation-values',
397
- )} Rows: ${uniq(errorRows).join(', ')} Columns: ${uniq(errorColumns).join(', ')}`,
398
- )
399
- }
400
-
401
- resolve({ isValid: true, langCodes: languageCodes })
402
- } else {
403
- console.log('No lines in CSV file.')
404
- this.notificationService.error(
405
- this.translate.instant('product-import.notifications.no-lines-in-file'),
406
- )
407
- resolve({ isValid: false })
408
- }
409
- }
410
- reader.onerror = () => {
411
- console.log('Error reading file.')
412
- this.notificationService.error(
413
- this.translate.instant('product-import.notifications.file-read-error'),
414
- )
415
- resolve({ isValid: false })
416
- }
417
- reader.readAsText(file)
418
- })
419
- }
420
-
421
- async uploadFile(): Promise<void> {
422
- if (this.selectedFile && this.selectedMainLanguage && this.updatingStrategy) {
423
- const formData = new FormData()
424
- formData.append('file', this.selectedFile)
425
- formData.append('updateProductSlug', this.updateProductSlug.toString())
426
- formData.append('mainLanguage', this.selectedMainLanguage)
427
- formData.append('updatingStrategy', this.updatingStrategy)
428
-
429
- try {
430
- const res = await fetch(`${this.serverPath}/product-import/upload`, {
431
- method: 'POST',
432
- body: formData,
433
- headers: this.getRequestHeaders(),
434
- })
435
-
436
- if (res.ok) {
437
- this.clearFile()
438
- this.notificationService.success(
439
- this.translate.instant('product-import.notifications.upload-success'),
440
- )
441
- this.cd.detectChanges()
442
- } else {
443
- this.notificationService.error(
444
- this.translate.instant('product-import.notifications.upload-error'),
445
- )
446
- }
447
- } catch (error) {
448
- this.notificationService.error(
449
- this.translate.instant('product-import.notifications.upload-error'),
450
- )
451
- }
452
- } else {
453
- this.notificationService.error(
454
- this.translate.instant('product-import.notifications.no-file-selected'),
455
- )
456
- }
457
- }
458
-
459
- splitLines(text: string): string[] {
460
- // Split text by commas, but not if comma is inside double quotes
461
- const splittedText = text.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/)
462
-
463
- return splittedText
464
- }
465
-
466
- checkifJson(text: string): {
467
- startsWithJson: boolean
468
- endsWithJson: boolean
469
- startChar: string
470
- endChar: string
471
- isJson?: boolean
472
- } {
473
- // Replace starting and ending double quotes if exists
474
- text = text.trim().replace(/^"/, '').replace(/"$/, '')
475
-
476
- return {
477
- startsWithJson: startsWith(text.trim(), '{') || startsWith(text.trim(), '['),
478
- endsWithJson: endsWith(text.trim(), '}') || endsWith(text.trim(), ']'),
479
- startChar: text.trim().charAt(0),
480
- endChar: text.trim().charAt(text.trim().length - 1),
481
- isJson:
482
- startsWith(text.trim(), '{') ||
483
- startsWith(text.trim(), '[') ||
484
- endsWith(text.trim(), '}') ||
485
- endsWith(text.trim(), ']'),
486
- }
487
- }
488
-
489
- getRequestHeaders(): Record<string, string> {
490
- const headers: Record<string, string> = {}
491
- const channelToken = this.localStorageService.get('activeChannelToken')
492
- const { tokenMethod, channelTokenKey } = this.appConfig
493
-
494
- if (channelToken) {
495
- headers[channelTokenKey ?? 'vendure-token'] = channelToken
496
- }
497
- if (tokenMethod === 'bearer') {
498
- const authToken = this.localStorageService.get('authToken')
499
- if (authToken) {
500
- headers.authorization = `Bearer ${authToken}`
501
- }
502
- }
503
- return headers
504
- }
505
-
506
- checkTranslatabeColumns(
507
- lines: string[],
508
- columns: string[],
509
- header: string[],
510
- languageCodes: (string | undefined)[],
511
- ): { errorRows: number[]; errorColumns: string[] } {
512
- let errorRows: number[] = []
513
- let errorColumns: string[] = []
514
-
515
- if (size(languageCodes) < 2) {
516
- console.log('Only one language found in the file.')
517
- return { errorRows, errorColumns }
518
- }
519
-
520
- for (let i = 1; i < lines.length; i++) {
521
- const row = this.splitLines(lines[i]).map((col) => col.trim())
522
- for (const baseColumn of columns) {
523
- const mainLanguageColumn = `${baseColumn}:${this.selectedMainLanguage}`
524
- const colIndex = header.indexOf(mainLanguageColumn)
525
- const mainLanguageValue = row[colIndex]
526
- const mainLanguageHasValue = mainLanguageValue !== undefined && mainLanguageValue !== ''
527
-
528
- if (mainLanguageHasValue) {
529
- continue
530
- }
531
- for (const lang of languageCodes) {
532
- if (lang) {
533
- // Check if lang is defined
534
- const langSpecificColumn = `${baseColumn}:${lang}`
535
- const colIndex = header.indexOf(langSpecificColumn)
536
- const colValue = row[colIndex]
537
- const colHasValue = colValue !== undefined && colValue !== ''
538
-
539
- if (!mainLanguageHasValue && colHasValue) {
540
- errorRows.push(i + 1)
541
- errorColumns.push(langSpecificColumn)
542
- }
543
- }
544
- }
545
- }
546
- }
547
-
548
- return { errorRows, errorColumns }
549
- }
550
- }
@@ -1,107 +0,0 @@
1
- import { Injectable } from '@angular/core'
2
- import {
3
- DataService,
4
- LocalStorageService,
5
- NotificationService,
6
- getServerLocation,
7
- } from '@vendure/admin-ui/core'
8
- import { Product } from '@vendure/core'
9
-
10
- @Injectable()
11
- export class ProductExportService {
12
- serverPath: string
13
- constructor(
14
- protected dataService: DataService,
15
- private notificationService: NotificationService,
16
- private localStorageService: LocalStorageService,
17
- ) {
18
- this.serverPath = getServerLocation()
19
- }
20
-
21
- async getCustomFields(productIds: string[]) {
22
- return fetch(`${this.serverPath}/product-export/custom-fields`, {
23
- method: 'POST',
24
- headers: {
25
- 'Content-Type': 'application/json',
26
- ...this.getHeaders(),
27
- },
28
- body: JSON.stringify(productIds),
29
- })
30
- .then((res) => res.json())
31
- .then((data: { name: string; type: string }[]) => {
32
- return data.map((field) => field.name)
33
- })
34
- }
35
-
36
- async getConfig() {
37
- return fetch(`${this.serverPath}/product-import-export/config`, {
38
- method: 'GET',
39
- headers: {
40
- 'Content-Type': 'application/json',
41
- ...this.getHeaders(),
42
- },
43
- })
44
- .then((res) => res.json())
45
- .then((data) => data)
46
- }
47
-
48
- async exportProducts(
49
- selection: Product[],
50
- fileName?: string,
51
- customFields?: string[],
52
- exportAssetsAs?: 'url' | 'json',
53
- selectedExportFields?: string[],
54
- ) {
55
- this.notificationService.info(`Exporting ${selection.length} products to ${fileName}.csv`)
56
- const productIds = selection.map((product) => product.id)
57
- try {
58
- const res = await fetch(
59
- `${this.serverPath}/product-export/export?fileName=${fileName}&customFields=${customFields}&exportAssetsAs=${exportAssetsAs}&selectedExportFields=${selectedExportFields}`,
60
- {
61
- method: 'POST',
62
- headers: {
63
- 'Content-Type': 'application/json',
64
- ...this.getHeaders(),
65
- },
66
- body: JSON.stringify(productIds),
67
- },
68
- )
69
- if (!res.ok) {
70
- const json = await res.json()
71
- throw Error(json?.message)
72
- }
73
- const header = res.headers.get('Content-Disposition')
74
- const parts = header!.split(';')
75
- const filename = parts[1].split('=')[1]
76
- const blob = await res.blob()
77
- await this.downloadBlob(blob, filename)
78
- } catch (err: any) {
79
- console.error(err)
80
- this.notificationService.error(err.message)
81
- }
82
- }
83
-
84
- private getHeaders(): Record<string, string> {
85
- const headers: Record<string, string> = {}
86
- const channelToken = this.localStorageService.get('activeChannelToken')
87
- if (channelToken) {
88
- headers['vendure-token'] = channelToken
89
- }
90
- const authToken = this.localStorageService.get('authToken')
91
- if (authToken) {
92
- headers.authorization = `Bearer ${authToken}`
93
- }
94
- return headers
95
- }
96
-
97
- private async downloadBlob(blob: Blob, fileName: string): Promise<void> {
98
- const blobUrl = window.URL.createObjectURL(blob)
99
- const a = document.createElement('a')
100
- document.body.appendChild(a)
101
- a.setAttribute('hidden', 'true')
102
- a.href = blobUrl
103
- a.download = fileName
104
- a.setAttribute('target', '_blank')
105
- a.click()
106
- }
107
- }
@@ -1,2 +0,0 @@
1
- declare const _default: import("@angular/core").Provider[];
2
- export default _default;