@sitecore-feaas/clientside 0.1.19 → 0.1.20

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 (79) hide show
  1. package/build/basic.js +5 -1
  2. package/build/basic.js.map +1 -1
  3. package/build/cloud.js +40 -7
  4. package/build/cloud.js.map +1 -1
  5. package/build/components/FEAASBase.js +60 -21
  6. package/build/components/FEAASBase.js.map +1 -1
  7. package/build/components/FEAASComponent.js +35 -1
  8. package/build/components/FEAASComponent.js.map +1 -1
  9. package/build/components/FEAASElement.js +14 -0
  10. package/build/components/FEAASElement.js.map +1 -0
  11. package/build/components/FEAASLoader.js +13 -1
  12. package/build/components/FEAASLoader.js.map +1 -1
  13. package/build/components/FEAASStylesheet.js.map +1 -1
  14. package/build/dom.js +6 -0
  15. package/build/dom.js.map +1 -1
  16. package/build/fetch.js +48 -45
  17. package/build/fetch.js.map +1 -1
  18. package/build/ui/FEAASContext.js +12 -13
  19. package/build/ui/FEAASContext.js.map +1 -1
  20. package/build/ui/FEAASEditor.js +448 -76
  21. package/build/ui/FEAASEditor.js.map +1 -1
  22. package/build/ui/FEAASPicker.js +9 -59
  23. package/build/ui/FEAASPicker.js.map +1 -1
  24. package/dist/browser/basic.esm.js +5 -5
  25. package/dist/browser/basic.esm.js.map +1 -1
  26. package/dist/browser/basic.js +5 -5
  27. package/dist/browser/basic.js.map +1 -1
  28. package/dist/browser/index.esm.js +5 -10
  29. package/dist/browser/index.esm.js.map +1 -1
  30. package/dist/browser/index.js +5 -10
  31. package/dist/browser/index.js.map +1 -1
  32. package/dist/node/basic.esm.js +3 -3
  33. package/dist/node/basic.esm.js.map +1 -1
  34. package/dist/node/basic.js +3 -3
  35. package/dist/node/basic.js.map +1 -1
  36. package/dist/node/index.esm.js +3 -8
  37. package/dist/node/index.esm.js.map +1 -1
  38. package/dist/node/index.js +3 -8
  39. package/dist/node/index.js.map +1 -1
  40. package/examples/index.html +8 -49
  41. package/package.json +1 -6
  42. package/src/basic.ts +8 -1
  43. package/src/cloud.ts +43 -13
  44. package/src/components/FEAASBase.tsx +73 -20
  45. package/src/components/{FEAASComponent.ts → FEAASComponent.tsx} +37 -1
  46. package/src/components/FEAASElement.tsx +21 -0
  47. package/src/components/{FEAASLoader.ts → FEAASLoader.tsx} +14 -1
  48. package/src/components/{FEAASStylesheet.ts → FEAASStylesheet.tsx} +0 -0
  49. package/src/dom.ts +5 -0
  50. package/src/fetch.ts +82 -51
  51. package/src/ui/FEAASContext.tsx +13 -14
  52. package/src/ui/FEAASEditor.tsx +598 -144
  53. package/src/ui/FEAASPicker.tsx +18 -65
  54. package/test/components.ts +19 -0
  55. package/types/basic.d.ts +3 -0
  56. package/types/basic.d.ts.map +1 -1
  57. package/types/cloud.d.ts +8 -1
  58. package/types/cloud.d.ts.map +1 -1
  59. package/types/components/FEAASBase.d.ts +18 -4
  60. package/types/components/FEAASBase.d.ts.map +1 -1
  61. package/types/components/FEAASComponent.d.ts +7 -0
  62. package/types/components/FEAASComponent.d.ts.map +1 -1
  63. package/types/components/FEAASElement.d.ts +12 -0
  64. package/types/components/FEAASElement.d.ts.map +1 -0
  65. package/types/components/FEAASLoader.d.ts +6 -0
  66. package/types/components/FEAASLoader.d.ts.map +1 -1
  67. package/types/components/FEAASStylesheet.d.ts +2 -2
  68. package/types/components/FEAASStylesheet.d.ts.map +1 -1
  69. package/types/dom.d.ts.map +1 -1
  70. package/types/fetch.d.ts +42 -1
  71. package/types/fetch.d.ts.map +1 -1
  72. package/types/tsbuildinfo +1 -1
  73. package/types/tsbuildinfo_ui +1 -1
  74. package/types/ui/FEAASContext.d.ts +4 -4
  75. package/types/ui/FEAASContext.d.ts.map +1 -1
  76. package/types/ui/FEAASEditor.d.ts +71 -5
  77. package/types/ui/FEAASEditor.d.ts.map +1 -1
  78. package/types/ui/FEAASPicker.d.ts +15 -34
  79. package/types/ui/FEAASPicker.d.ts.map +1 -1
@@ -1,5 +1,6 @@
1
1
  import type * as FEAASEditorImports from '@sitecore-feaas/frontend/types/components/editor/Editor.js'
2
2
  import { FEAASBase, OptionalExcept } from '../components/FEAASBase.js'
3
+ import { FEAASLoader } from '../components/FEAASLoader.js'
3
4
 
4
5
  export function getFramePosition(element: HTMLElement) {
5
6
  let positions = []
@@ -41,13 +42,17 @@ export function FEAASEditorProps(element: FEAASBase = this) {
41
42
  return {
42
43
  context: HTMLElement,
43
44
  frontend: element?.getContextAttribute('frontend'),
44
- src: element?.getAttribute('src') ?? '/assets/editor.js',
45
+ src: element?.getAttribute('src') ?? '/editor.js',
45
46
  library: element?.getAttribute('library'),
46
47
  component: element?.getAttribute('component'),
47
48
  version: element?.getAttribute('version'),
49
+ instance: element?.getAttribute('instance'),
50
+ forked: element?.getAttribute('forked'),
48
51
  revision: element?.getAttribute('revision') || 'saved',
49
52
  hidden: element?.getAttribute('hidden') != null,
50
- for: element?.getAttribute('for')
53
+ for: element?.getAttribute('for'),
54
+ state: element?.getAttribute('state') || ('idle' as 'idle' | 'loading' | 'ready'),
55
+ changed: element?.getAttribute('changed') != null
51
56
  }
52
57
  }
53
58
  type FEAASEditorProps = ReturnType<typeof FEAASEditorProps>
@@ -59,29 +64,68 @@ type Rect = {
59
64
  }
60
65
 
61
66
  export class FEAASEditor extends FEAASBase<FEAASEditorProps, typeof FEAASEditorImports> {
62
- chrome: HTMLElement
63
- wrapper: HTMLElement
64
-
65
67
  framePoint: Pick<Rect, 'top' | 'left'> = { top: 0, left: 0 }
66
68
  wrapperRect: Rect = { top: 0, left: 0, width: 0, height: 0 }
67
69
  bodyRect: Rect = { top: 0, left: 0, width: 0, height: 0 }
68
70
 
71
+ static observedAttributes = ['library', 'component', 'version', 'revision', 'hidden', 'state', 'changed', 'forked']
72
+
69
73
  getProps() {
70
74
  return FEAASEditorProps(this)
71
75
  }
76
+ reactRootElement: HTMLDivElement
77
+ getReactRootElement() {
78
+ if (!this.reactRootElement) {
79
+ this.reactRootElement = document.createElement('div')
80
+ this.getRoot().appendChild(this.reactRootElement)
81
+ }
82
+ return this.reactRootElement
83
+ }
72
84
 
73
- getTarget = () => {
74
- return document.querySelector('iframe').contentWindow.document.getElementById(this.props.for)
85
+ target: HTMLElement
86
+ setTarget = (
87
+ element: HTMLElement,
88
+ props = {
89
+ component: element.getAttribute('component'),
90
+ version: element.getAttribute('version'),
91
+ revision: element.getAttribute('revision'),
92
+ instance: element.getAttribute('instance')
93
+ }
94
+ ) => {
95
+ if (!this.closing && (!this.target || element == null)) {
96
+ this.set(props)
97
+ this.target = element
98
+ return true
99
+ }
100
+ }
101
+
102
+ showLoader() {
103
+ if (!this.loader) {
104
+ this.loader = document.createElement('feaas-loader') as FEAASLoader
105
+ this.loader.setAttribute('opacity', '0.6')
106
+ this.loader.style.zIndex = '1000'
107
+ this.target.appendChild(this.loader)
108
+ if (this.target.tagName == 'FEAAS-COMPONENT') {
109
+ this.target.style.position = 'relative'
110
+ }
111
+ }
112
+ }
113
+
114
+ hideLoader() {
115
+ if (this.loader) {
116
+ this.loader.setAttribute('hidden', 'hidden')
117
+ if (this.target.tagName == 'FEAAS-COMPONENT') {
118
+ this.target.style.position = 'relative'
119
+ this.target.style.position = ''
120
+ }
121
+ }
75
122
  }
76
123
 
77
124
  load() {
78
125
  return import(/* @vite-ignore */ /* webpackIgnore: true */ this.formatURL(this.props.src, this.props.frontend))
79
126
  }
80
127
 
81
- onChromeMeasureElement = (element: HTMLElement, rect?: Rect) => {
82
- if (rect == null) {
83
- rect = element.getBoundingClientRect()
84
- }
128
+ onChromeMeasureElement = (element: HTMLElement, rect: Rect = element.getBoundingClientRect()) => {
85
129
  if (element.ownerDocument == document) {
86
130
  return {
87
131
  left: rect.left - this.bodyRect.left,
@@ -100,184 +144,594 @@ export class FEAASEditor extends FEAASBase<FEAASEditorProps, typeof FEAASEditorI
100
144
 
101
145
  onBeforeMeasure: FEAASEditorImports.EditorContext['onChromeBeforeMeasure'] = (refs) => {
102
146
  if (refs.memoized.focusable) this.framePoint = getFramePosition(refs.memoized.focusable)
103
- this.wrapperRect = this.wrapper.getBoundingClientRect()
104
- this.bodyRect = this.wrapper.ownerDocument.body.parentElement.getBoundingClientRect()
147
+ if (this.chrome) {
148
+ this.wrapperRect = this.chrome.positionable.getBoundingClientRect()
149
+ this.bodyRect = this.chrome.positionable.ownerDocument.body.parentElement.getBoundingClientRect()
150
+ }
105
151
  }
106
152
 
107
153
  onMeasure: FEAASEditorImports.EditorContext['onChromeMeasure'] = () => {}
108
154
  onPosition: FEAASEditorImports.EditorContext['onChromePosition'] = ({ focusable, root }) => {
109
- const chrome = this.wrapper.querySelector('.editor-chrome') as HTMLElement
110
- const absolute = this.wrapper.querySelector('.ui.overlays') as HTMLElement
155
+ if (!this.chrome || this.props.hidden) return
111
156
  if (focusable) {
112
157
  const topbarHeight = 64
113
158
  const padding = 16
114
- chrome.style.width = focusable.width + padding * 2 + 'px'
115
- chrome.style.height = focusable.height + padding * 1 + topbarHeight + 'px'
116
- chrome.style.left = focusable.left - padding + 'px'
117
- chrome.style.top = focusable.top - topbarHeight + 'px'
118
- if (absolute) {
119
- absolute.style.left = root.left + 'px'
120
- absolute.style.top = root.top + 'px'
159
+ this.chrome.wrapper.style.width = focusable.width + padding * 2 + 'px'
160
+ this.chrome.wrapper.style.height = focusable.height + padding * 1 + topbarHeight + 'px'
161
+ this.chrome.wrapper.style.left = focusable.left - padding + 'px'
162
+ this.chrome.wrapper.style.top = focusable.top - topbarHeight + 'px'
163
+ this.chrome.wrapper.style.opacity = '1'
164
+ this.chrome.absolute = this.querySelector('.ui.overlays')
165
+ if (this.chrome.absolute) {
166
+ this.chrome.absolute.style.left = root.left + 'px'
167
+ this.chrome.absolute.style.top = root.top + 'px'
121
168
  }
122
169
  } else {
123
- chrome.style.left = '-1000px'
124
- chrome.style.top = '-1000px'
170
+ this.chrome.wrapper.style.opacity = '0'
171
+ this.chrome.wrapper.style.left = '-1000px'
172
+ this.chrome.wrapper.style.top = '-1000px'
125
173
  // wrapper.style.left = '-1000px'
126
174
  // wrapper.style.top = '-1000px'
127
175
  }
176
+
177
+ // this.setPlaceholder()
178
+ }
179
+
180
+ onWindowMessage = (event: MessageEvent) => {
181
+ switch (event.data?.type) {
182
+ case 'feaasEdit':
183
+ const placeholder = this.getPlaceholder()
184
+ if (this.setTarget(placeholder.editable, event.data)) {
185
+ this.loader = placeholder.loader
186
+ document.body.appendChild(placeholder.clipper)
187
+ this.editedUID = event.data.id
188
+ this.setPlaceholder()
189
+ this.open()
190
+ }
191
+ break
192
+ case 'feaasMeasurements':
193
+ this.measurements = event.data.measurements
194
+ if (!this.props.hidden) this.setPlaceholder()
195
+ break
196
+ }
197
+ }
198
+
199
+ connectedCallback(): void {
200
+ super.connectedCallback()
201
+
202
+ window.addEventListener('message', this.onWindowMessage)
203
+ }
204
+
205
+ disconnectedCallback(): void {
206
+ super.disconnectedCallback()
207
+
208
+ window.removeEventListener('message', this.onWindowMessage)
209
+ }
210
+
211
+ onContentChange = () => {
212
+ this.setAttribute('changed', 'changed')
213
+ requestAnimationFrame(() => {
214
+ this.setIframeComponentContent()
215
+ })
216
+ }
217
+
218
+ iframe: HTMLIFrameElement
219
+ setIframe(iframe: HTMLIFrameElement) {
220
+ this.iframe = iframe
221
+ }
222
+
223
+ setIframeComponentContent() {
224
+ if (this.placeholder && this.iframe && this.placeholder.editable.innerHTML)
225
+ this.iframe.contentWindow.postMessage({
226
+ type: 'feaasUpdate',
227
+ id: this.editedUID,
228
+ content: this.placeholder.editable.innerHTML
229
+ })
230
+ }
231
+
232
+ setPlaceholder() {
233
+ const x = this.iframe.clientWidth
234
+ const y = this.iframe.clientHeight
235
+ const rect = this.iframe.getBoundingClientRect()
236
+ const placeholder = this.getPlaceholder()
237
+ const body = document.body.getBoundingClientRect()
238
+ const measurement = this.measurements[this.editedUID]
239
+ if (!measurement) return
240
+ Object.assign(placeholder.clipper.style, {
241
+ position: 'absolute',
242
+ top: rect.top + this.iframe.clientTop - body.top + 'px',
243
+ left: rect.left + this.iframe.clientLeft - body.left + 'px',
244
+ width: x + 'px',
245
+ height: y + 'px',
246
+ pointerEvents: 'none',
247
+ zIndex: 1,
248
+ overflow: 'hidden'
249
+ })
250
+ Object.assign(placeholder.positionable.style, {
251
+ position: 'absolute',
252
+ top: measurement.top + 'px',
253
+ left: measurement.left + 'px',
254
+ width: measurement.width + 'px',
255
+ zIndex: 2,
256
+ transition: 'opacity 0.4s',
257
+ //height: measurement.height + 'px',
258
+ pointerEvents: this.props.hidden ? 'none' : 'all'
259
+ //outline: '3px solid orange'
260
+ })
261
+ Object.assign(placeholder.loader.style, {
262
+ position: 'absolute',
263
+ top: measurement.top + 'px',
264
+ left: measurement.left + 'px',
265
+ width: measurement.width + 'px',
266
+ height: measurement.height + 'px',
267
+ filter: 'opacity(0.6)',
268
+ zIndex: 3,
269
+ pointerEvents: 'none'
270
+ //outline: '3px solid orange'
271
+ })
272
+ document.dispatchEvent(new Event('feaasRefresh', { bubbles: true }))
273
+ }
274
+
275
+ chrome: {
276
+ positionable: HTMLDivElement
277
+ absolute: HTMLDivElement
278
+ wrapper: HTMLDivElement
279
+ }
280
+
281
+ measurements: Record<string, DOMRect>
282
+ editedUID: string
283
+ placeholder: {
284
+ clipper: HTMLDivElement
285
+ positionable: HTMLDivElement
286
+ editable: HTMLDivElement
287
+ loader: FEAASLoader
288
+ }
289
+ loader: FEAASLoader
290
+ getPlaceholder() {
291
+ if (this.placeholder) return this.placeholder
292
+ this.placeholder = {
293
+ clipper: document.createElement('div'),
294
+ positionable: document.createElement('div'),
295
+ editable: Object.assign(document.createElement('div'), { className: '-feaas' }),
296
+ loader: document.createElement('feaas-loader') as FEAASLoader
297
+ }
298
+ this.placeholder.clipper.appendChild(this.placeholder.positionable)
299
+ this.placeholder.positionable.appendChild(this.placeholder.editable)
300
+ this.placeholder.clipper.appendChild(this.placeholder.loader)
301
+ return this.placeholder
302
+ }
303
+
304
+ attributeChangedCallback(name: string, oldValue: string, newValue: string): void {
305
+ if (oldValue === newValue) return
306
+ if (name == 'hidden') {
307
+ this.style.setProperty('display', 'block', 'important')
308
+ this.style.setProperty('transition', 'opacity 0.3s', 'important')
309
+ if (newValue) {
310
+ this.style.setProperty('opacity', '0', 'important')
311
+ this.style.pointerEvents = 'none'
312
+ } else {
313
+ this.style.setProperty('opacity', '1', 'important')
314
+ this.style.pointerEvents = 'all'
315
+ }
316
+ }
317
+ super.attributeChangedCallback(name, oldValue, newValue)
318
+ }
319
+
320
+ open() {
321
+ clearTimeout(this.closing)
322
+ this.removeAttribute('hidden')
323
+ this.removeAttribute('changed')
324
+ this.setLoadingState()
325
+ }
326
+
327
+ shouldFork(version: FEAASEditorImports.SDK.VersionModel) {
328
+ return version.forkOriginId == null && this.props.instance != null
329
+ }
330
+
331
+ cleanup() {
332
+ if (this.placeholder) {
333
+ this.placeholder.clipper?.remove()
334
+ this.placeholder = null
335
+ }
336
+ }
337
+ onClose = () => {
338
+ this.setAttribute('hidden', 'hidden')
339
+ if (this.placeholder) {
340
+ this.placeholder.positionable.style.opacity = '0'
341
+ this.placeholder.positionable.style.pointerEvents = 'none'
342
+ }
343
+ if (this.chrome) {
344
+ this.chrome.wrapper.style.opacity = '0'
345
+ }
346
+ // FIXME: Workaround for CKEditor not removing event listeners properly
347
+ // Reported: https://github.com/ckeditor/ckeditor5/issues/13141
348
+ // Have to recreate the whole component
349
+ if (this.target?.parentNode) {
350
+ this.target.outerHTML = this.target.outerHTML
351
+ }
352
+ this.setTarget(null, {
353
+ component: null,
354
+ version: null,
355
+ instance: null,
356
+ revision: null
357
+ })
358
+ this.setEditorContext({
359
+ isFocused: false
360
+ })
361
+ }
362
+ close() {
363
+ console.log('Editor is not ready')
364
+ }
365
+ unfork() {
366
+ console.log('Editor is not ready')
128
367
  }
129
- /*
130
- getRoot(): HTMLElement | FEAASBase<any, any> | ShadowRoot {
131
- return this.shadowRoot || this.attachShadow({ mode: 'open' })
368
+ discard() {
369
+ console.log('Editor is not ready')
132
370
  }
133
- reactRoot: HTMLDivElement
134
- getReactRoot() {
135
- if (!this.reactRoot) {
136
- this.reactRoot = document.createElement('div')
137
- this.reactRoot.className = 'react-root'
138
- this.getRoot().appendChild(this.reactRoot)
371
+ save() {
372
+ console.log('Editor is not ready')
373
+ }
374
+ versionContext: FEAASEditorImports.VersionContext
375
+ setVersionContext(props: Partial<FEAASEditorImports.VersionContext>) {
376
+ console.log('Editor is not ready')
377
+ }
378
+ onVersionStateChange = (
379
+ versionContext: FEAASEditorImports.VersionContext,
380
+ setVersionContext: (context: Partial<FEAASEditorImports.VersionContext>) => void
381
+ ) => {
382
+ this.versionContext = versionContext
383
+ this.setVersionContext = setVersionContext
384
+ }
385
+
386
+ editorContext: FEAASEditorImports.EditorContext
387
+ setEditorContext(props: Partial<FEAASEditorImports.EditorContext>) {
388
+ console.log('Editor is not ready')
389
+ }
390
+ onEditorStateChange = (
391
+ editorContext: FEAASEditorImports.EditorContext,
392
+ setEditorContext: (context: Partial<FEAASEditorImports.EditorContext>) => void
393
+ ) => {
394
+ this.editorContext = editorContext
395
+ this.setEditorContext = setEditorContext
396
+ // On editor loaded
397
+ if (editorContext.editor && this.props.state == 'loading' && !this.props.hidden) {
398
+ this.setAttribute('state', 'ready')
399
+ this.ownerDocument.fonts.ready.then(() =>
400
+ setTimeout(() => {
401
+ this.setReadyState()
402
+ this.setIframeComponentContent()
403
+ }, 100)
404
+ )
139
405
  }
140
- return this.reactRoot
141
406
  }
142
- */
407
+
408
+ setLoadingState() {
409
+ this.showLoader()
410
+ this.setAttribute('state', 'loading')
411
+ }
412
+
413
+ setReadyState() {
414
+ this.hideLoader()
415
+ this.setEditorContext({
416
+ activeVersionId: this.component.versions[0].id
417
+ })
418
+ this.setAttribute('state', 'ready')
419
+ }
420
+
421
+ component: FEAASEditorImports.SDK.ComponentModel
422
+ version: FEAASEditorImports.SDK.VersionModel
423
+ closing: ReturnType<typeof setTimeout>
424
+
143
425
  renderReact(
144
- { children, component: componentId, version: versionId }: React.PropsWithChildren<FEAASEditorProps>,
426
+ {
427
+ children,
428
+ component: componentId,
429
+ version: versionId,
430
+ instance: instanceId
431
+ }: React.PropsWithChildren<FEAASEditorProps>,
145
432
  {
146
433
  Editor,
147
434
  VersionStatusList,
435
+ VersionStatusButton,
148
436
  Version,
437
+ VersionMenu,
149
438
  EditorUIUndo,
150
439
  EditorUIMode,
151
- React: { useEffect, useMemo, useState },
152
- Chakra: { HStack, Box },
440
+ React: { useEffect, useMemo, useState, useContext },
441
+ Chakra: { HStack, Box, Button, MenuItem },
153
442
  Chrome,
154
443
  ReactDOM,
155
444
  useSDK,
156
445
  useSlots,
157
- useModelObserver
446
+ useModelObserver,
447
+ ConfirmationContext,
448
+ VersionContext
158
449
  }: typeof FEAASEditorImports
159
450
  ) {
160
451
  if (!this.payload) return
452
+ const { React } = this.payload
161
453
  const { sdk } = useSDK()
162
454
  const [component, setComponent] = useState<FEAASEditorImports.SDK.ComponentModel>()
455
+ const { setConfirm } = useContext(ConfirmationContext)
456
+
457
+ const usedId = instanceId || versionId
163
458
  const library = sdk.library
459
+
460
+ if (component) this.component = component
461
+
164
462
  useEffect(() => {
165
- return setComponent(library.components.getItem(componentId))
166
- }, [componentId, library])
463
+ if (this.props.hidden) return
464
+ const comp = library.components.getItem(componentId).clone()
465
+ comp.versions
466
+ .search({ ids: [versionId, instanceId].filter(Boolean), componentIds: [componentId] })
467
+ .then((versions) => {
468
+ comp.versions.setItems(versions)
469
+ const fork = comp.findVersion(instanceId, ['staged', 'published'])
470
+ const original = comp.findVersion(versionId, ['staged', 'published'])
471
+ if (fork) {
472
+ if (fork.deletedAt) {
473
+ // Get unforked version up to date
474
+ fork.getDraft().changeAndNotify(original.toProperties()).restore() //.publish()
475
+ console.error('FEAAS Editor: Restoring fork')
476
+ } else {
477
+ console.error('FEAAS Editor: Pre-forked')
478
+ this.setAttribute('forked', fork.id)
479
+ }
480
+ } else {
481
+ this.removeAttribute('forked')
482
+ if (this.shouldFork(original)) {
483
+ console.error('FEAAS Editor: Forking')
484
+ const newForkId = original.fork(instanceId).id
485
+ this.setAttribute('instance', newForkId)
486
+ } else {
487
+ console.error('FEAAS Editor: Regular')
488
+ }
489
+ }
490
+ setComponent(comp)
491
+ })
492
+ return () => {
493
+ this.closing = setTimeout(() => {
494
+ this.closing = null
495
+ this.cleanup()
496
+ setComponent(null)
497
+ }, 400)
498
+ }
499
+ }, [versionId, this.props.hidden])
500
+
501
+ const versions = useModelObserver(component?.versions, []).filter((v) => v.id == usedId)
502
+ const version = versions[0]
503
+ if (version && !this.props.hidden) this.version = version
504
+
167
505
  useEffect(() => {
168
- component?.versions.fetch()
506
+ this.unfork = () => {
507
+ setConfirm({
508
+ title: 'Reverting component customizations',
509
+ button: 'Discard',
510
+ body: 'Are you sure to reset component to its original state? All changes will be lost.',
511
+ action: () => {
512
+ const original = component.findVersion(versionId, ['staged', 'published'])
513
+ component.findVersion(instanceId).unstage().unpublish().changeAndNotify(original.toProperties())
514
+ component.saveVersions(false)
515
+ this.setVersionContext({
516
+ needsWrite: true
517
+ })
518
+ this.removeAttribute('forked')
519
+ requestAnimationFrame(this.onClose)
520
+ }
521
+ })
522
+ }
523
+ this.discard = () => {
524
+ if (this.props.changed) {
525
+ component.findVersion(usedId).revert()
526
+ component.saveVersions(false)
527
+ this.setVersionContext({
528
+ needsWrite: true
529
+ })
530
+ }
531
+ }
532
+ this.close = () => {
533
+ setConfirm({
534
+ title: 'Component has unsaved changes',
535
+ button: 'Discard',
536
+ body: 'Are you sure to close editor and discard changes?',
537
+ bypass: !this.props.changed,
538
+ action: () => {
539
+ this.discard()
540
+ requestAnimationFrame(this.onClose)
541
+ }
542
+ })
543
+ }
544
+ this.save = () => {
545
+ setConfirm({
546
+ bypass: !!this.props.forked,
547
+ title: 'Customizing component',
548
+ button: 'Save',
549
+ body: 'Changes to the component will only be visible on that page. Changes to component done in Component Builder will not be reflected here. It will be possible to revert the customization.',
550
+ action: () => {
551
+ component
552
+ .findVersion(usedId)
553
+ .commitData({
554
+ view: this.editorContext.editor.getData({ rootName: usedId }),
555
+ model: ''
556
+ })
557
+ .stage()
558
+ .publish()
559
+ this.removeAttribute('changed')
560
+ this.setAttribute('forked', usedId)
561
+ component.saveVersions(false)
562
+ }
563
+ })
564
+ }
565
+ return () => {
566
+ delete this.unfork
567
+ delete this.discard
568
+ delete this.save
569
+ delete this.editorContext
570
+ delete this.versionContext
571
+ delete this.setEditorContext
572
+ delete this.setVersionContext
573
+ }
169
574
  }, [component])
170
575
 
171
- const target = useMemo(this.getTarget, [this.props.for])
172
-
173
- const observedVersions = useModelObserver(component?.versions)
174
- const versions = useMemo(() => {
175
- return observedVersions?.filter((v) => v.id == versionId || !versionId) || []
176
- }, [observedVersions])
177
-
178
- if (!component) return
179
- return useSlots<any, 'picker' | 'children'>(children, ({ picker, children }) => (
180
- <Box ref={(el) => (this.wrapper = el)}>
181
- <Box className='editor-wrapper' position='absolute' left={0} top={0}>
182
- <Editor
183
- datasources={sdk.datasources}
184
- sdk={sdk}
185
- library={sdk.library}
186
- component={component}
187
- versions={versions}
188
- context={{
189
- onChromeMeasure: this.onMeasure,
190
- onChromeBeforeMeasure: this.onBeforeMeasure,
191
- onChromeMeasureElement: this.onChromeMeasureElement,
192
- onChromePosition: this.onPosition
576
+ const target = useMemo(() => {
577
+ return this.target
578
+ }, [component])
579
+ return useMemo(
580
+ () =>
581
+ component &&
582
+ useSlots<any, 'picker' | 'children'>(children, ({ picker, children }) => (
583
+ <Box
584
+ ref={(el) => {
585
+ this.chrome = el
586
+ ? {
587
+ positionable: el,
588
+ wrapper: el.querySelector('.editor-chrome'),
589
+ absolute: this.querySelector('.ui.overlays')
590
+ }
591
+ : null
193
592
  }}
194
593
  >
195
- {({ editor, status, isArchivedDisplayed }) => ({
196
- after: children,
197
- picker: picker,
198
- chrome: editor && <Chrome />,
199
- children: component
200
- .reduceVersionRevisions(versions)
201
- .filter((snapshot) => {
202
- return isArchivedDisplayed || !(snapshot.saved || snapshot.draft)?.deletedAt
203
- })
204
- .map((snapshot, index) => (
205
- <Box
206
- key={snapshot.head.id}
207
- position='absolute'
208
- left={0}
209
- top={0}
210
- className='editor-chrome'
211
- p={4}
212
- boxShadow={'2xl'}
213
- pointerEvents='none'
214
- >
594
+ <Box className='editor-wrapper' position='absolute' left={0} top={0}>
595
+ <Editor
596
+ datasources={sdk.datasources}
597
+ sdk={sdk}
598
+ library={sdk.library}
599
+ component={component}
600
+ versions={versions}
601
+ context={{
602
+ isAutosaveEnabled: false,
603
+ onChromeMeasure: this.onMeasure,
604
+ onChromeBeforeMeasure: this.onBeforeMeasure,
605
+ onChromeMeasureElement: this.onChromeMeasureElement,
606
+ onChromePosition: this.onPosition,
607
+ onContentChange: this.onContentChange
608
+ }}
609
+ >
610
+ {{
611
+ exports: this.onEditorStateChange,
612
+ after: children,
613
+ chrome: ({ editor }) => editor && <Chrome />,
614
+ children: ({ isArchivedDisplayed, editor, status }) => (
215
615
  <Box
216
- className='borders'
217
- pointerEvents={'none'}
218
- left={0}
219
- width='4'
220
- top={0}
221
- bottom={0}
222
- backdropFilter='blur(2px)'
223
- background='rgb(244 244 244 / 80%)'
224
- position='absolute'
225
- ></Box>
226
- <Box
227
- className='borders'
228
- pointerEvents={'none'}
229
- right={0}
230
- width='4'
231
- top={0}
232
- bottom={0}
233
- backdropFilter='blur(2px)'
234
- background='rgb(244 244 244 / 80%)'
235
- position='absolute'
236
- ></Box>
237
- <Box
238
- className='borders'
239
- pointerEvents={'none'}
240
- top={0}
241
- height='16'
242
- left={0}
243
- right={0}
244
- backdropFilter='blur(2px)'
245
- background='rgb(244 244 244 / 80%)'
246
616
  position='absolute'
247
- ></Box>
248
- <Box
249
- className='borders'
250
- pointerEvents={'none'}
251
- bottom={0}
252
- height='4'
253
617
  left={0}
254
- right={0}
255
- backdropFilter='blur(2px)'
256
- background='rgb(244 244 244 / 80%)'
257
- position='absolute'
258
- ></Box>
259
- <Version version={snapshot.head} node={target}>
260
- {{
261
- left: (
262
- <HStack justifyContent='space-between'>
263
- <EditorUIUndo size='sm' editor={status == 'ready' ? editor : null} />
264
- <EditorUIMode size='sm' />
265
- </HStack>
266
- ),
267
- middle: <VersionStatusList />
268
- }}
269
- </Version>
270
- </Box>
271
- ))
272
- })}
273
- </Editor>
274
- </Box>
275
- </Box>
276
- ))
618
+ top={0}
619
+ className='editor-chrome'
620
+ p={4}
621
+ boxShadow={'2xl'}
622
+ pointerEvents='none'
623
+ opacity={0}
624
+ transition='opacity 0.3s'
625
+ >
626
+ <Box
627
+ left={0}
628
+ width='4'
629
+ top={0}
630
+ bottom={0}
631
+ backdropFilter='blur(2px)'
632
+ background='rgb(244 244 244 / 80%)'
633
+ position='absolute'
634
+ ></Box>
635
+ <Box
636
+ right={0}
637
+ width='4'
638
+ top={0}
639
+ bottom={0}
640
+ backdropFilter='blur(2px)'
641
+ background='rgb(244 244 244 / 80%)'
642
+ position='absolute'
643
+ ></Box>
644
+ <Box
645
+ top={0}
646
+ height='16'
647
+ left={0}
648
+ right={0}
649
+ backdropFilter='blur(2px)'
650
+ background='rgb(244 244 244 / 80%)'
651
+ position='absolute'
652
+ ></Box>
653
+ <Box
654
+ bottom={0}
655
+ height='4'
656
+ left={0}
657
+ right={0}
658
+ backdropFilter='blur(2px)'
659
+ background='rgb(244 244 244 / 80%)'
660
+ position='absolute'
661
+ ></Box>
662
+ {version && (
663
+ <Version
664
+ version={version}
665
+ node={target}
666
+ ignoreBreakpoints={true}
667
+ deps={[this.props.forked, this.props.changed]}
668
+ >
669
+ {{
670
+ exports: this.onVersionStateChange,
671
+ left: (
672
+ <HStack justifyContent='space-between'>
673
+ <EditorUIUndo size='sm' editor={status == 'ready' ? editor : null} />
674
+ <EditorUIMode size='sm' />
675
+ </HStack>
676
+ ),
677
+ middle: (versionContext, setVersionContext) => [
678
+ false && (
679
+ <VersionContext.Provider
680
+ value={[
681
+ {
682
+ ...versionContext,
683
+ version: component.findVersion(versionId)
684
+ },
685
+ setVersionContext
686
+ ]}
687
+ >
688
+ <VersionStatusList></VersionStatusList>
689
+ </VersionContext.Provider>
690
+ )
691
+ ],
692
+ right: (
693
+ <>
694
+ <Button variant='secondary' size='sm' onClick={this.close}>
695
+ Close
696
+ </Button>
697
+ <Button
698
+ variant='primary'
699
+ size='sm'
700
+ onClick={this.save}
701
+ isDisabled={!this.props.changed}
702
+ >
703
+ {!this.props.changed
704
+ ? 'Up to date'
705
+ : this.props.forked
706
+ ? 'Save'
707
+ : 'Save customization'}
708
+ </Button>
709
+ <VersionMenu isDisabled={!this.props.forked && !this.props.changed}>
710
+ <MenuItem onClick={this.unfork} isDisabled={!this.props.forked}>
711
+ Unfork
712
+ </MenuItem>
713
+ <MenuItem onClick={this.discard} isDisabled={!this.props.changed}>
714
+ Discard
715
+ </MenuItem>
716
+ </VersionMenu>
717
+ </>
718
+ )
719
+ }}
720
+ </Version>
721
+ )}
722
+ </Box>
723
+ )
724
+ }}
725
+ </Editor>
726
+ </Box>
727
+ </Box>
728
+ )),
729
+ [version, this.props.forked, this.props.changed]
730
+ )
277
731
  }
278
732
 
279
733
  isReadyToLoad(): boolean {
280
- return this.props.component != null && this.props.version != null
734
+ return this.props.component != null && this.props.version != null && !this.props.hidden
281
735
  }
282
736
  }
283
737