1
|
|
|
/** |
2
|
|
|
* @package admin |
3
|
|
|
*/ |
4
|
|
|
|
5
|
|
|
import type Vue from 'vue'; |
6
|
|
|
import { updateSubscriber, register, handleGet } from '@shopware-ag/admin-extension-sdk/es/data'; |
7
|
|
|
import { get, debounce } from 'lodash'; |
8
|
|
|
import { selectData } from '@shopware-ag/admin-extension-sdk/es/data/_internals/selectData'; |
9
|
|
|
import MissingPrivilegesError from '@shopware-ag/admin-extension-sdk/es/privileges/missing-privileges-error'; |
10
|
|
|
|
11
|
|
|
type publishOptions = { |
12
|
|
|
id: string, |
13
|
|
|
path: string, |
14
|
|
|
scope: Vue, |
15
|
|
|
} |
16
|
|
|
|
17
|
|
|
type dataset = { |
18
|
|
|
id: string, |
19
|
|
|
scope: number, |
20
|
|
|
data: unknown |
21
|
|
|
} |
22
|
|
|
|
23
|
|
|
type transferObject = { |
24
|
|
|
[key: string|symbol]: unknown |
25
|
|
|
} |
26
|
|
|
|
27
|
|
|
type ParsedPath = { |
28
|
|
|
pathToLastSegment: string, |
29
|
|
|
lastSegment: string, |
30
|
|
|
}; |
31
|
|
|
|
32
|
|
|
type vueWithUid = Partial<Vue> & { _uid: number }; |
33
|
|
|
|
34
|
|
|
// This is used by the Vue devtool extension plugin |
35
|
|
|
let publishedDataSets: dataset[] = []; |
36
|
|
|
|
37
|
|
|
handleGet((data, additionalOptions) => { |
38
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access |
39
|
|
|
const origin = additionalOptions?._event_?.origin; |
40
|
|
|
const registeredDataSet = publishedDataSets.find(s => s.id === data.id); |
41
|
|
|
|
42
|
|
|
if (!registeredDataSet) { |
43
|
|
|
return null; |
44
|
|
|
} |
45
|
|
|
|
46
|
|
|
const selectors = data.selectors; |
47
|
|
|
|
48
|
|
|
if (!selectors) { |
49
|
|
|
return registeredDataSet.data; |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
const selectedData = selectData(registeredDataSet.data, selectors, 'datasetGet', origin); |
53
|
|
|
|
54
|
|
|
if (selectedData instanceof MissingPrivilegesError) { |
55
|
|
|
console.error(selectedData); |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
return selectedData; |
59
|
|
|
}); |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* Splits an object path like "foo.bar.buz" to "{ pathToLastSegment: 'foo.bar', lastSegment: 'buz' }". |
63
|
|
|
*/ |
64
|
|
|
function parsePath(path :string): ParsedPath | null { |
65
|
|
|
if (!path.includes('.')) { |
66
|
|
|
return null; |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
const properties = path.split('.'); |
70
|
|
|
const lastSegment = properties.pop(); |
71
|
|
|
const pathToLastSegment = properties.join('.'); |
72
|
|
|
|
73
|
|
|
if (lastSegment && lastSegment.length && pathToLastSegment && pathToLastSegment.length) { |
74
|
|
|
return { |
75
|
|
|
pathToLastSegment, |
76
|
|
|
lastSegment, |
77
|
|
|
}; |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
return null; |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
// eslint-disable-next-line sw-deprecation-rules/private-feature-declarations |
84
|
|
|
export function publishData({ id, path, scope }: publishOptions): void { |
85
|
|
|
const registeredDataSet = publishedDataSets.find(s => s.id === id); |
86
|
|
|
|
87
|
|
|
// Dataset registered from different scope? Prevent update. |
88
|
|
|
if (registeredDataSet && registeredDataSet.scope !== (scope as vueWithUid)._uid) { |
89
|
|
|
console.error(`The dataset id "${id}" you tried to publish is already registered.`); |
90
|
|
|
|
91
|
|
|
return; |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
// Dataset registered from same scope? Update. |
95
|
|
|
if (registeredDataSet && registeredDataSet.scope === (scope as vueWithUid)._uid) { |
96
|
|
|
// eslint-disable-next-line @typescript-eslint/no-empty-function |
97
|
|
|
register({ id: id, data: get(scope, path) }).catch(() => {}); |
98
|
|
|
|
99
|
|
|
return; |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
// Create updateSubscriber which maps back changes from the app to Vue |
103
|
|
|
updateSubscriber(id, (value) => { |
104
|
|
|
// Null updates are not allowed |
105
|
|
|
if (!value) { |
106
|
|
|
return; |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
function setObject(transferObject: transferObject, prePath: string|null = null): void { |
110
|
|
|
if (typeof transferObject?.getIsDirty === 'function' && !transferObject.getIsDirty()) { |
111
|
|
|
return; |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
Object.keys(transferObject).forEach((property) => { |
115
|
|
|
let realPath : string; |
116
|
|
|
if (prePath) { |
117
|
|
|
realPath = `${prePath}.${property}`; |
118
|
|
|
} else { |
119
|
|
|
realPath = `${path}.${property}`; |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
const parsedPath = parsePath(realPath); |
123
|
|
|
if (parsedPath === null) { |
124
|
|
|
return; |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
// @ts-expect-error |
128
|
|
|
// eslint-disable-next-line max-len |
129
|
|
|
if (Shopware.Utils.hasOwnProperty(transferObject[property], 'getDraft', this) && typeof transferObject[property].getDraft === 'function') { |
130
|
|
|
setObject({ [property]: Shopware.Utils.object.cloneDeep(transferObject[property]) }, realPath); |
131
|
|
|
|
132
|
|
|
return; |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
if (Array.isArray(transferObject[property])) { |
136
|
|
|
(transferObject[property] as Array<unknown>).forEach((c, index) => { |
137
|
|
|
setObject({ [index]: c }, realPath); |
138
|
|
|
}); |
139
|
|
|
|
140
|
|
|
return; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
scope.$set( |
144
|
|
|
Shopware.Utils.object.get(scope, parsedPath.pathToLastSegment) as Vue, |
145
|
|
|
parsedPath.lastSegment, |
146
|
|
|
transferObject[property], |
147
|
|
|
); |
148
|
|
|
}); |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
// @ts-expect-error |
152
|
|
|
if (typeof value.data?.getDraft === 'function') { |
153
|
|
|
setObject(value.data as transferObject); |
154
|
|
|
|
155
|
|
|
return; |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
if (Array.isArray(value.data)) { |
159
|
|
|
value.data.forEach((entry, index) => { |
160
|
|
|
if (entry === null || typeof entry !== 'object') { |
161
|
|
|
return; |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
setObject({ [index]: entry as unknown }); |
165
|
|
|
}); |
166
|
|
|
} else if (typeof value.data === 'object') { |
167
|
|
|
setObject(value.data as transferObject); |
168
|
|
|
|
169
|
|
|
return; |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
// Vue.set does not resolve path's therefore we need to resolve to the last child property |
173
|
|
|
if (path.includes('.')) { |
174
|
|
|
const properties = path.split('.'); |
175
|
|
|
const lastPath = properties.pop(); |
176
|
|
|
const newPath = properties.join('.'); |
177
|
|
|
if (!lastPath) { |
178
|
|
|
return; |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
scope.$set(Shopware.Utils.object.get(scope, newPath) as Vue, lastPath, value.data); |
182
|
|
|
|
183
|
|
|
return; |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
scope.$set(scope, path, value.data); |
187
|
|
|
}); |
188
|
|
|
|
189
|
|
|
// Watch for Changes on the Reactive Vue property and automatically publish them |
190
|
|
|
const unwatch = scope.$watch(path, debounce((value: Vue) => { |
191
|
|
|
// const preparedValue = prepareValue(value); |
192
|
|
|
|
193
|
|
|
// eslint-disable-next-line @typescript-eslint/no-empty-function |
194
|
|
|
register({ id: id, data: value }).catch(() => {}); |
195
|
|
|
|
196
|
|
|
const dataSet = publishedDataSets.find(set => set.id === id); |
197
|
|
|
if (dataSet) { |
198
|
|
|
dataSet.data = value; |
199
|
|
|
|
200
|
|
|
return; |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
publishedDataSets.push({ |
204
|
|
|
id, |
205
|
|
|
data: value, |
206
|
|
|
scope: (scope as vueWithUid)._uid, |
207
|
|
|
}); |
208
|
|
|
}, 750), { |
209
|
|
|
deep: true, |
210
|
|
|
immediate: true, |
211
|
|
|
}); |
212
|
|
|
|
213
|
|
|
// Before the registering component gets destroyed, destroy the watcher and deregister the dataset |
214
|
|
|
scope.$once('hook:beforeDestroy', () => { |
215
|
|
|
publishedDataSets = publishedDataSets.filter(value => value.id !== id); |
216
|
|
|
|
217
|
|
|
unwatch(); |
218
|
|
|
}); |
219
|
|
|
|
220
|
|
|
// eslint-disable-next-line @typescript-eslint/no-empty-function |
221
|
|
|
register({ id: id, data: get(scope, path) }).catch(() => {}); |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
// eslint-disable-next-line sw-deprecation-rules/private-feature-declarations |
225
|
|
|
export function getPublishedDataSets(): dataset[] { |
226
|
|
|
return publishedDataSets; |
227
|
|
|
} |
228
|
|
|
|