@blokkli/editor 2.0.0-alpha.15 → 2.0.0-alpha.17

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 (116) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +330 -93
  3. package/dist/modules/drupal/graphql/base/fragment.blokkliProps.graphql +1 -1
  4. package/dist/modules/drupal/graphql/features/comments.graphql +11 -8
  5. package/dist/modules/drupal/runtime/adapter/index.js +2 -2
  6. package/dist/runtime/blokkliPlugins/ItemAction/index.vue +1 -3
  7. package/dist/runtime/components/Blocks/FromLibrary/index.vue +4 -2
  8. package/dist/runtime/components/BlokkliEditable.vue +22 -4
  9. package/dist/runtime/components/BlokkliProvider.vue +29 -20
  10. package/dist/runtime/components/BlokkliProvider.vue.d.ts +2 -1
  11. package/dist/runtime/components/Edit/Actions/index.vue +9 -4
  12. package/dist/runtime/components/Edit/AnimationCanvas/index.vue +420 -25
  13. package/dist/runtime/components/Edit/ArtboardTooltip/index.vue +80 -0
  14. package/dist/runtime/components/Edit/ArtboardTooltip/index.vue.d.ts +32 -0
  15. package/dist/runtime/components/Edit/Banner/index.vue +51 -0
  16. package/dist/runtime/components/Edit/Banner/index.vue.d.ts +18 -0
  17. package/dist/runtime/components/Edit/Dialog/index.vue +3 -0
  18. package/dist/runtime/components/Edit/Dialog/index.vue.d.ts +2 -0
  19. package/dist/runtime/components/Edit/EditIndicator.vue +118 -44
  20. package/dist/runtime/components/Edit/EditIndicator.vue.d.ts +3 -0
  21. package/dist/runtime/components/Edit/EditProvider.vue +79 -22
  22. package/dist/runtime/components/Edit/EditProvider.vue.d.ts +2 -0
  23. package/dist/runtime/components/Edit/Features/Analyze/Overlay/index.vue +19 -20
  24. package/dist/runtime/components/Edit/Features/BlockAddList/index.vue +1 -1
  25. package/dist/runtime/components/Edit/Features/CommandPalette/index.vue +2 -0
  26. package/dist/runtime/components/Edit/Features/Comments/AddForm/index.vue +35 -20
  27. package/dist/runtime/components/Edit/Features/Comments/AddForm/index.vue.d.ts +5 -3
  28. package/dist/runtime/components/Edit/Features/Comments/CommentInput/index.vue +29 -0
  29. package/dist/runtime/components/Edit/Features/Comments/CommentInput/index.vue.d.ts +13 -0
  30. package/dist/runtime/components/Edit/Features/Comments/Overlay/Item/index.vue +22 -16
  31. package/dist/runtime/components/Edit/Features/Comments/Overlay/Item/index.vue.d.ts +1 -0
  32. package/dist/runtime/components/Edit/Features/Comments/Overlay/index.vue +15 -6
  33. package/dist/runtime/components/Edit/Features/Comments/index.vue +20 -8
  34. package/dist/runtime/components/Edit/Features/Debug/Rects/index.vue +26 -35
  35. package/dist/runtime/components/Edit/Features/Debug/Renderer.vue +240 -0
  36. package/dist/runtime/components/Edit/Features/Debug/Renderer.vue.d.ts +6 -0
  37. package/dist/runtime/components/Edit/Features/Debug/index.vue +4 -165
  38. package/dist/runtime/components/Edit/Features/DraggingOverlay/DragItems/index.vue +1 -1
  39. package/dist/runtime/components/Edit/Features/DraggingOverlay/DropTargets/fragment.glsl +7 -1
  40. package/dist/runtime/components/Edit/Features/DraggingOverlay/DropTargets/index.vue +62 -39
  41. package/dist/runtime/components/Edit/Features/DraggingOverlay/DropTargets/vertex.glsl +1 -0
  42. package/dist/runtime/components/Edit/Features/Edit/index.vue +1 -1
  43. package/dist/runtime/components/Edit/Features/EditableField/Overlay/Frame/index.vue +63 -3
  44. package/dist/runtime/components/Edit/Features/EditableField/Overlay/Plaintext/index.vue +13 -9
  45. package/dist/runtime/components/Edit/Features/EditableField/Overlay/index.vue +17 -76
  46. package/dist/runtime/components/Edit/Features/EditableField/index.vue +1 -1
  47. package/dist/runtime/components/Edit/Features/History/index.vue +5 -2
  48. package/dist/runtime/components/Edit/Features/Hover/Overlay/fragment.glsl +139 -0
  49. package/dist/runtime/components/Edit/Features/Hover/Overlay/index.vue +270 -0
  50. package/dist/runtime/components/Edit/Features/Hover/Overlay/index.vue.d.ts +6 -0
  51. package/dist/runtime/components/Edit/Features/Hover/Overlay/vertex.glsl +117 -0
  52. package/dist/runtime/components/Edit/Features/Hover/index.vue +25 -0
  53. package/dist/runtime/components/Edit/Features/Library/LibraryDialog/index.vue +19 -27
  54. package/dist/runtime/components/Edit/Features/Library/ReusableDialog/index.vue +27 -23
  55. package/dist/runtime/components/Edit/Features/Library/index.vue +2 -1
  56. package/dist/runtime/components/Edit/Features/MultiSelect/Overlay/index.vue +34 -27
  57. package/dist/runtime/components/Edit/Features/MultiSelect/index.vue +2 -4
  58. package/dist/runtime/components/Edit/Features/Options/Form/Item.vue +6 -1
  59. package/dist/runtime/components/Edit/Features/Options/Form/index.vue +1 -0
  60. package/dist/runtime/components/Edit/Features/Ownership/Renderer.vue +35 -0
  61. package/dist/runtime/components/Edit/Features/Ownership/Renderer.vue.d.ts +6 -0
  62. package/dist/runtime/components/Edit/Features/Ownership/index.vue +7 -25
  63. package/dist/runtime/components/Edit/Features/ProxyView/index.vue +5 -1
  64. package/dist/runtime/components/Edit/Features/Publish/Dialog/Summary.vue +2 -2
  65. package/dist/runtime/components/Edit/Features/Publish/Dialog/index.vue +17 -7
  66. package/dist/runtime/components/Edit/Features/Selection/AddButtons/Overlay/index.vue +39 -74
  67. package/dist/runtime/components/Edit/Features/Selection/AddButtons/Overlay/index.vue.d.ts +4 -2
  68. package/dist/runtime/components/Edit/Features/Selection/AddButtons/Renderer/fragment.glsl +106 -0
  69. package/dist/runtime/components/Edit/Features/Selection/AddButtons/Renderer/index.vue +417 -0
  70. package/dist/runtime/components/Edit/Features/Selection/AddButtons/Renderer/index.vue.d.ts +32 -0
  71. package/dist/runtime/components/Edit/Features/Selection/AddButtons/Renderer/vertex.glsl +102 -0
  72. package/dist/runtime/components/Edit/Features/Selection/AddButtons/index.vue +33 -106
  73. package/dist/runtime/components/Edit/Features/Selection/Overlay/index.vue +88 -29
  74. package/dist/runtime/components/Edit/Features/Selection/Overlay/index.vue.d.ts +2 -0
  75. package/dist/runtime/components/Edit/Features/Selection/Overlay/vertex.glsl +11 -2
  76. package/dist/runtime/components/Edit/Features/Selection/index.vue +26 -19
  77. package/dist/runtime/components/Edit/Features/Translations/Banner/index.vue +17 -11
  78. package/dist/runtime/components/Edit/Features/Translations/index.vue +13 -16
  79. package/dist/runtime/components/Edit/Form/Text/index.vue +2 -1
  80. package/dist/runtime/components/Edit/Form/Text/index.vue.d.ts +1 -0
  81. package/dist/runtime/components/Edit/Indicators/index.vue +1 -1
  82. package/dist/runtime/components/Edit/Konami/Game/index.vue +5 -5
  83. package/dist/runtime/components/Edit/index.d.ts +5 -3
  84. package/dist/runtime/components/Edit/index.js +8 -4
  85. package/dist/runtime/composables/defineBlokkli.js +5 -3
  86. package/dist/runtime/css/output.css +1 -1
  87. package/dist/runtime/helpers/animationProvider.d.ts +34 -1
  88. package/dist/runtime/helpers/animationProvider.js +175 -48
  89. package/dist/runtime/helpers/composables/defineRenderer.d.ts +8 -0
  90. package/dist/runtime/helpers/composables/defineRenderer.js +8 -0
  91. package/dist/runtime/helpers/composables/useDelayedIntersectionObserver.d.ts +1 -1
  92. package/dist/runtime/helpers/composables/useDelayedIntersectionObserver.js +3 -2
  93. package/dist/runtime/helpers/composables/useStickyToolbar.d.ts +4 -1
  94. package/dist/runtime/helpers/composables/useStickyToolbar.js +53 -35
  95. package/dist/runtime/helpers/dom/index.d.ts +1 -0
  96. package/dist/runtime/helpers/domProvider.d.ts +46 -0
  97. package/dist/runtime/helpers/domProvider.js +101 -7
  98. package/dist/runtime/helpers/editableProvider.d.ts +14 -0
  99. package/dist/runtime/helpers/editableProvider.js +144 -0
  100. package/dist/runtime/helpers/stateProvider.d.ts +6 -2
  101. package/dist/runtime/helpers/stateProvider.js +66 -3
  102. package/dist/runtime/helpers/storageProvider.d.ts +3 -2
  103. package/dist/runtime/helpers/storageProvider.js +6 -2
  104. package/dist/runtime/helpers/symbols.d.ts +1 -0
  105. package/dist/runtime/helpers/symbols.js +1 -0
  106. package/dist/runtime/helpers/uiProvider.d.ts +8 -1
  107. package/dist/runtime/helpers/uiProvider.js +34 -2
  108. package/dist/runtime/helpers/webgl/index.d.ts +11 -2
  109. package/dist/runtime/helpers/webgl/index.js +162 -7
  110. package/dist/runtime/plugins/blokkliEditable.js +74 -3
  111. package/dist/runtime/types/index.d.ts +13 -1
  112. package/package.json +1 -1
  113. package/dist/runtime/components/Edit/DragInteractions/index.vue +0 -401
  114. package/dist/runtime/components/Edit/Features/Selection/AddButtons/AddButtonsField.vue +0 -54
  115. package/dist/runtime/components/Edit/Features/Selection/AddButtons/AddButtonsField.vue.d.ts +0 -14
  116. /package/dist/runtime/components/Edit/{DragInteractions → Features/Hover}/index.vue.d.ts +0 -0
@@ -59,6 +59,7 @@ export default function(ui, debug, definitions) {
59
59
  const blockRects = {};
60
60
  const fieldRects = {};
61
61
  const blockUuidCurrentKey = {};
62
+ const observedElements = {};
62
63
  let draggableBlockCache = {};
63
64
  let initTimeout = null;
64
65
  const isInitalizing = ref(true);
@@ -80,6 +81,9 @@ export default function(ui, debug, definitions) {
80
81
  if (!uuid) {
81
82
  return;
82
83
  }
84
+ if (!registeredBlocks[uuid]) {
85
+ continue;
86
+ }
83
87
  const currentRect = blockRects[uuid];
84
88
  const now = performance.now();
85
89
  if (!currentRect) {
@@ -118,6 +122,9 @@ export default function(ui, debug, definitions) {
118
122
  }
119
123
  fieldRects[fieldKey] = ui.getAbsoluteElementRect(rect, scale, offset);
120
124
  } else if (uuid) {
125
+ if (!registeredBlocks[uuid]) {
126
+ continue;
127
+ }
121
128
  const newRect = ui.getAbsoluteElementRect(rect, scale, offset);
122
129
  const currentRect = blockRects[uuid];
123
130
  if (currentRect) {
@@ -139,7 +146,12 @@ export default function(ui, debug, definitions) {
139
146
  }
140
147
  }
141
148
  }
142
- const intersectionObserver = useDelayedIntersectionObserver(intersectionCallback);
149
+ const intersectionObserver = useDelayedIntersectionObserver(
150
+ intersectionCallback,
151
+ {
152
+ rootMargin: "400px 0px 400px 0px"
153
+ }
154
+ );
143
155
  const registeredFieldTypes = computed(() => {
144
156
  const fields = Object.values(registeredFields);
145
157
  const found = /* @__PURE__ */ new Set();
@@ -370,11 +382,13 @@ export default function(ui, debug, definitions) {
370
382
  }
371
383
  stateReloadTimeout = window.setTimeout(updateVisibleRects, 300);
372
384
  });
373
- onBlokkliEvent("ui:resized", function() {
385
+ function forceRefresh() {
374
386
  updateVisibleRects();
375
387
  getVisibleFields().forEach(refreshFieldRect);
376
388
  logger.log("Refreshed all visible rects");
377
- });
389
+ }
390
+ onBlokkliEvent("option:finish-change", forceRefresh);
391
+ onBlokkliEvent("ui:resized", forceRefresh);
378
392
  function init() {
379
393
  intersectionObserver.init();
380
394
  intersectionReady.value = true;
@@ -443,9 +457,10 @@ export default function(ui, debug, definitions) {
443
457
  item.hostFieldListType,
444
458
  item.hostBundle
445
459
  );
460
+ registeredBlocks[item.uuid] = el;
461
+ observedElements[item.uuid] = observableElement;
446
462
  intersectionObserver.observe(observableElement);
447
463
  resizeObserver.observe(observableElement);
448
- registeredBlocks[item.uuid] = el;
449
464
  }
450
465
  }
451
466
  function unregisterBlock(key, uuid) {
@@ -455,16 +470,94 @@ export default function(ui, debug, definitions) {
455
470
  }
456
471
  logger.log("unregisterBlock: " + uuid);
457
472
  const el = registeredBlocks[uuid];
473
+ const observedElement = observedElements[uuid];
474
+ if (observedElement) {
475
+ intersectionObserver.unobserve(observedElement);
476
+ resizeObserver.unobserve(observedElement);
477
+ delete observedElements[uuid];
478
+ }
458
479
  if (el) {
459
- intersectionObserver.unobserve(el);
460
- resizeObserver.unobserve(el);
461
480
  dragElementUuidMap.delete(el);
462
481
  }
463
482
  dragElementCache.delete(uuid);
464
483
  registeredBlocks[uuid] = void 0;
465
484
  delete blockRects[uuid];
485
+ delete blockUuidCurrentKey[uuid];
466
486
  visibleBlocks.delete(uuid);
467
487
  }
488
+ function getDebugData() {
489
+ const allUuids = /* @__PURE__ */ new Set([
490
+ ...Object.keys(registeredBlocks),
491
+ ...Object.keys(blockRects),
492
+ ...Object.keys(observedElements),
493
+ ...Object.keys(blockUuidCurrentKey),
494
+ ...Object.keys(draggableBlockCache)
495
+ ]);
496
+ const blocksInfo = Array.from(allUuids).map((uuid) => {
497
+ const el = registeredBlocks[uuid];
498
+ return {
499
+ uuid,
500
+ hasElement: !!el,
501
+ hasObservedElement: !!observedElements[uuid],
502
+ hasRect: !!blockRects[uuid],
503
+ hasCurrentKey: !!blockUuidCurrentKey[uuid],
504
+ isVisible: visibleBlocks.has(uuid),
505
+ inCache: !!draggableBlockCache[uuid],
506
+ elementInfo: el ? {
507
+ tagName: el.tagName,
508
+ bundle: el.dataset.itemBundle,
509
+ hostBundle: el.dataset.hostBundle,
510
+ fieldListType: el.dataset.hostFieldListType
511
+ } : void 0
512
+ };
513
+ });
514
+ const fieldsInfo = Object.entries(registeredFields).filter(([, field]) => !!field).map(([key, field]) => ({
515
+ key,
516
+ isVisible: visibleFields.has(key),
517
+ hasRect: !!fieldRects[key],
518
+ entityType: field.entity.type,
519
+ entityBundle: field.entity.bundle,
520
+ fieldName: field.fieldName
521
+ }));
522
+ const registeredUuids = new Set(
523
+ Object.entries(registeredBlocks).filter(([, el]) => !!el).map(([uuid]) => uuid)
524
+ );
525
+ const rectsWithoutRegistration = Object.keys(blockRects).filter(
526
+ (uuid) => !registeredUuids.has(uuid)
527
+ );
528
+ const observedElementsWithoutRegistration = Object.keys(
529
+ observedElements
530
+ ).filter((uuid) => !registeredUuids.has(uuid));
531
+ const keysWithoutRegistration = Object.keys(blockUuidCurrentKey).filter(
532
+ (uuid) => !registeredUuids.has(uuid)
533
+ );
534
+ return {
535
+ registeredBlocks: blocksInfo,
536
+ fields: fieldsInfo,
537
+ summary: {
538
+ totalRegisteredBlocks: Object.keys(registeredBlocks).length,
539
+ totalBlocksWithElements: Object.values(registeredBlocks).filter(
540
+ (el) => !!el
541
+ ).length,
542
+ totalObservedElements: Object.keys(observedElements).length,
543
+ totalBlockRects: Object.keys(blockRects).length,
544
+ totalVisibleBlocks: visibleBlocks.size,
545
+ totalRegisteredFields: Object.values(registeredFields).filter(
546
+ (f) => !!f
547
+ ).length,
548
+ totalVisibleFields: visibleFields.size,
549
+ totalFieldRects: Object.keys(fieldRects).length,
550
+ cacheSize: Object.keys(draggableBlockCache).length,
551
+ isInitializing: isInitalizing.value,
552
+ isReady: mutationsReady.value && intersectionReady.value && !isInitalizing.value
553
+ },
554
+ orphanedData: {
555
+ rectsWithoutRegistration,
556
+ observedElementsWithoutRegistration,
557
+ keysWithoutRegistration
558
+ }
559
+ };
560
+ }
468
561
  return {
469
562
  findBlock,
470
563
  getAllBlocks,
@@ -493,6 +586,7 @@ export default function(ui, debug, definitions) {
493
586
  registeredFieldTypes,
494
587
  registerBlock,
495
588
  unregisterBlock,
496
- registeredBlockUuids
589
+ registeredBlockUuids,
590
+ getDebugData
497
591
  };
498
592
  }
@@ -0,0 +1,14 @@
1
+ import type { EntityContext, Rectangle } from '#blokkli/types';
2
+ import type { UiProvider } from './uiProvider.js';
3
+ type EditableFieldData = EntityContext & {
4
+ fieldName: string;
5
+ };
6
+ export type EditableProvider = {
7
+ init: () => void;
8
+ registerEditableField: (el: HTMLElement, fieldName: string, entity: EntityContext) => void;
9
+ unregisterEditableField: (el: HTMLElement, fieldName: string, entity: EntityContext) => void;
10
+ getVisible: () => Rectangle[];
11
+ getEditableAtPoint: (x: number, y: number) => EditableFieldData | undefined;
12
+ };
13
+ export default function (ui: UiProvider): EditableProvider;
14
+ export {};
@@ -0,0 +1,144 @@
1
+ import { falsy } from "#blokkli/helpers";
2
+ import useDelayedIntersectionObserver from "./composables/useDelayedIntersectionObserver.js";
3
+ import { onBeforeUnmount } from "#imports";
4
+ import onBlokkliEvent from "./composables/onBlokkliEvent.js";
5
+ export default function(ui) {
6
+ let stateReloadTimeout = null;
7
+ const editableFieldElementMap = /* @__PURE__ */ new WeakMap();
8
+ const editableFieldElements = /* @__PURE__ */ new Map();
9
+ const editableFieldData = /* @__PURE__ */ new Map();
10
+ const rects = {};
11
+ const visible = /* @__PURE__ */ new Set();
12
+ function getVisible() {
13
+ return [...visible.keys()].map((key) => {
14
+ return rects[key];
15
+ }).filter(falsy);
16
+ }
17
+ function getEditableKey(fieldName, entity) {
18
+ return `${entity.type}:${entity.uuid}:${fieldName}`;
19
+ }
20
+ function intersectionCallback(entries) {
21
+ const scale = ui.artboardScale.value;
22
+ const offset = ui.artboardOffset.value;
23
+ for (const entry of entries) {
24
+ if (entry.target instanceof HTMLElement) {
25
+ const data = editableFieldElementMap.get(entry.target);
26
+ if (!data) {
27
+ continue;
28
+ }
29
+ const key = getEditableKey(data.fieldName, data);
30
+ const domRect = entry.target.getBoundingClientRect();
31
+ rects[key] ||= {
32
+ width: 0,
33
+ height: 0,
34
+ x: 0,
35
+ y: 0,
36
+ key
37
+ };
38
+ const newRect = ui.getAbsoluteElementRect(domRect, scale, offset);
39
+ rects[key].width = newRect.width;
40
+ rects[key].height = newRect.height;
41
+ rects[key].x = newRect.x;
42
+ rects[key].y = newRect.y;
43
+ if (entry.isIntersecting) {
44
+ visible.add(key);
45
+ } else {
46
+ visible.delete(key);
47
+ }
48
+ }
49
+ }
50
+ }
51
+ const intersectionObserver = useDelayedIntersectionObserver(
52
+ intersectionCallback,
53
+ {
54
+ rootMargin: "400px 0px 400px 0px"
55
+ }
56
+ );
57
+ function registerEditableField(el, fieldName, entity) {
58
+ const key = getEditableKey(fieldName, entity);
59
+ const data = {
60
+ ...entity,
61
+ fieldName
62
+ };
63
+ editableFieldElementMap.set(el, data);
64
+ editableFieldData.set(key, data);
65
+ intersectionObserver.observe(el);
66
+ editableFieldElements.set(key, el);
67
+ }
68
+ function unregisterEditableField(el, fieldName, entity) {
69
+ const key = getEditableKey(fieldName, entity);
70
+ intersectionObserver.unobserve(el);
71
+ editableFieldElementMap.delete(el);
72
+ editableFieldData.delete(key);
73
+ delete rects[key];
74
+ visible.delete(key);
75
+ editableFieldElements.delete(key);
76
+ }
77
+ function init() {
78
+ intersectionObserver.init();
79
+ }
80
+ function getEditableAtPoint(x, y) {
81
+ const scale = ui.artboardScale.value;
82
+ const offset = ui.artboardOffset.value;
83
+ const artboardX = x / scale - offset.x / scale;
84
+ const artboardY = y / scale - offset.y / scale;
85
+ for (const key of visible) {
86
+ const rect = rects[key];
87
+ if (!rect) continue;
88
+ if (artboardX >= rect.x && artboardX <= rect.x + rect.width && artboardY >= rect.y && artboardY <= rect.y + rect.height) {
89
+ return editableFieldData.get(key);
90
+ }
91
+ }
92
+ return void 0;
93
+ }
94
+ function updateRects() {
95
+ const scale = ui.artboardScale.value;
96
+ const offset = ui.artboardOffset.value;
97
+ const keysToUpdate = editableFieldElements.size < 150 ? Array.from(editableFieldElements.keys()) : Array.from(visible);
98
+ for (let i = 0; i < keysToUpdate.length; i++) {
99
+ const key = keysToUpdate[i];
100
+ const el = editableFieldElements.get(key);
101
+ if (!el) continue;
102
+ const domRect = el.getBoundingClientRect();
103
+ const newRect = ui.getAbsoluteElementRect(domRect, scale, offset);
104
+ if (rects[key]) {
105
+ rects[key].width = newRect.width;
106
+ rects[key].height = newRect.height;
107
+ rects[key].x = newRect.x;
108
+ rects[key].y = newRect.y;
109
+ } else {
110
+ rects[key] = {
111
+ width: newRect.width,
112
+ height: newRect.height,
113
+ x: newRect.x,
114
+ y: newRect.y,
115
+ key
116
+ };
117
+ }
118
+ }
119
+ }
120
+ function handleRefresh() {
121
+ if (stateReloadTimeout) {
122
+ window.clearTimeout(stateReloadTimeout);
123
+ }
124
+ if (visible.size < 150) {
125
+ updateRects();
126
+ }
127
+ stateReloadTimeout = window.setTimeout(updateRects, 300);
128
+ }
129
+ onBlokkliEvent("state:reloaded", handleRefresh);
130
+ onBlokkliEvent("ui:resized", handleRefresh);
131
+ onBlokkliEvent("option:finish-change", handleRefresh);
132
+ onBeforeUnmount(() => {
133
+ if (stateReloadTimeout) {
134
+ window.clearTimeout(stateReloadTimeout);
135
+ }
136
+ });
137
+ return {
138
+ registerEditableField,
139
+ unregisterEditableField,
140
+ init,
141
+ getVisible,
142
+ getEditableAtPoint
143
+ };
144
+ }
@@ -1,6 +1,6 @@
1
1
  import { type Ref, type ComputedRef } from 'vue';
2
2
  import type { BlokkliAdapter, AdapterContext } from '../adapter/index.js';
3
- import type { MutatedField, EditEntity, MutatedOptions, TranslationState, MappedState, MutationItem, Validation, MutateWithLoadingStateFunction, EditMode, FieldListItem, PublishOptions } from '#blokkli/types';
3
+ import type { MutatedField, EditEntity, MutatedOptions, TranslationState, MappedState, MutationItem, Validation, MutateWithLoadingStateFunction, EditMode, FieldListItem, PublishOptions, EditPermission } from '#blokkli/types';
4
4
  import type { TextProvider } from './textProvider.js';
5
5
  export type BlokkliOwner = {
6
6
  name: string | undefined;
@@ -27,16 +27,20 @@ export type StateProvider = {
27
27
  editMode: Readonly<Ref<EditMode>>;
28
28
  mutatedEntity: Readonly<Ref<any>>;
29
29
  canEdit: ComputedRef<boolean>;
30
+ permissions: ComputedRef<EditPermission[]>;
30
31
  stateAvailable: ComputedRef<boolean>;
31
32
  isLoading: Readonly<Ref<boolean>>;
33
+ fromLibraryUuids: Readonly<Ref<Readonly<string[]>>>;
32
34
  getFieldBlockCount: (key: string) => number;
33
35
  getBlockBundleCount: (bundle: string) => number;
34
36
  getFieldListItem: (uuid: string) => FieldListItem | undefined;
35
37
  getFieldListForBlock: (uuid: string) => MutatedField | undefined;
36
38
  getMutatedField: (uuid: string, fieldName: string) => MutatedField | undefined;
37
39
  getAllUuids: (bundle?: string) => string[];
40
+ getNestingLevel: (uuid: string) => number;
41
+ isChildOf: (childUuid: string, parentUuid: string) => boolean;
38
42
  getMappedState: () => MappedState;
39
43
  setOverrideState: (state: MappedState) => void;
40
44
  clearOverrideState: () => void;
41
45
  };
42
- export default function (adapter: BlokkliAdapter<any>, context: ComputedRef<AdapterContext>, $t: TextProvider, providerKey: string): Promise<StateProvider>;
46
+ export default function (adapter: BlokkliAdapter<any>, context: ComputedRef<AdapterContext>, $t: TextProvider, providerKey: string, permissions: EditPermission[]): Promise<StateProvider>;
@@ -12,6 +12,7 @@ import { falsy, getFieldKey } from "#blokkli/helpers";
12
12
  import { eventBus, emitMessage } from "#blokkli/helpers/eventBus";
13
13
  import { nextTick } from "#imports";
14
14
  import { addElementClasses } from "./addElementClasses.js";
15
+ import { BUNDLE_FROM_LIBRARY } from "#blokkli/constants";
15
16
  const HOST_OPTION_KEY = "HOST";
16
17
  function mapPublishOptions(context) {
17
18
  return {
@@ -24,7 +25,7 @@ function mapPublishOptions(context) {
24
25
  revisionLogMessage: context?.publishOptions?.revisionLogMessage ?? null
25
26
  };
26
27
  }
27
- export default async function(adapter, context, $t, providerKey) {
28
+ export default async function(adapter, context, $t, providerKey, permissions) {
28
29
  let _mappedState = null;
29
30
  const overrideHostOptions = useState("options:" + providerKey);
30
31
  const stateLoaded = ref(false);
@@ -58,6 +59,8 @@ export default async function(adapter, context, $t, providerKey) {
58
59
  const blockBundleCount = ref({});
59
60
  const fieldListItemMap = /* @__PURE__ */ new Map();
60
61
  let bundleToUuids = {};
62
+ const fromLibraryUuids = ref([]);
63
+ const nestingLevelMap = /* @__PURE__ */ new Map();
61
64
  function getFieldListItem(uuid) {
62
65
  const fieldKey = fieldListItemMap.get(uuid);
63
66
  if (!fieldKey) {
@@ -141,8 +144,10 @@ export default async function(adapter, context, $t, providerKey) {
141
144
  const visitedFieldKeys = [];
142
145
  const newBlockBundleCount = {};
143
146
  fieldListItemMap.clear();
147
+ nestingLevelMap.clear();
144
148
  fieldBlockCount = {};
145
149
  bundleToUuids = {};
150
+ const fromLibrary = [];
146
151
  for (let i = 0; i < newMutatedFields.length; i++) {
147
152
  const field = newMutatedFields[i];
148
153
  const key = getFieldKey(field.entityUuid, field.name);
@@ -162,6 +167,16 @@ export default async function(adapter, context, $t, providerKey) {
162
167
  bundleToUuids[item.bundle] = [];
163
168
  }
164
169
  bundleToUuids[item.bundle].push(item.uuid);
170
+ if (item.bundle === BUNDLE_FROM_LIBRARY) {
171
+ fromLibrary.push(item.uuid);
172
+ }
173
+ }
174
+ }
175
+ for (let i = 0; i < newMutatedFields.length; i++) {
176
+ const field = newMutatedFields[i];
177
+ for (let j = 0; j < field.list.length; j++) {
178
+ const item = field.list[j];
179
+ calculateNestingLevel(item.uuid);
165
180
  }
166
181
  }
167
182
  blockBundleCount.value = newBlockBundleCount;
@@ -172,6 +187,7 @@ export default async function(adapter, context, $t, providerKey) {
172
187
  mutatedFieldsMap[key] = void 0;
173
188
  }
174
189
  }
190
+ fromLibraryUuids.value = fromLibrary;
175
191
  eventBus.emit("updateMutatedFields", { fields: newMutatedFields });
176
192
  nextTick(() => {
177
193
  refreshKey.value = Date.now().toString();
@@ -205,6 +221,49 @@ export default async function(adapter, context, $t, providerKey) {
205
221
  }
206
222
  return bundleToUuids[bundle] ?? [];
207
223
  }
224
+ function calculateNestingLevel(uuid) {
225
+ const cached = nestingLevelMap.get(uuid);
226
+ if (cached !== void 0) {
227
+ return cached;
228
+ }
229
+ const fieldKey = fieldListItemMap.get(uuid);
230
+ if (!fieldKey) {
231
+ nestingLevelMap.set(uuid, 0);
232
+ return 0;
233
+ }
234
+ const field = mutatedFieldsMap[fieldKey];
235
+ if (!field) {
236
+ nestingLevelMap.set(uuid, 0);
237
+ return 0;
238
+ }
239
+ const parentEntityUuid = field.entityUuid;
240
+ const parentFieldKey = fieldListItemMap.get(parentEntityUuid);
241
+ if (!parentFieldKey) {
242
+ nestingLevelMap.set(uuid, 0);
243
+ return 0;
244
+ }
245
+ const parentLevel = calculateNestingLevel(parentEntityUuid);
246
+ const level = parentLevel + 1;
247
+ nestingLevelMap.set(uuid, level);
248
+ return level;
249
+ }
250
+ function getNestingLevel(uuid) {
251
+ return nestingLevelMap.get(uuid) ?? 0;
252
+ }
253
+ function isChildOf(childUuid, parentUuid) {
254
+ const fieldKey = fieldListItemMap.get(childUuid);
255
+ if (!fieldKey) {
256
+ return false;
257
+ }
258
+ const field = mutatedFieldsMap[fieldKey];
259
+ if (!field) {
260
+ return false;
261
+ }
262
+ if (field.entityUuid === parentUuid) {
263
+ return true;
264
+ }
265
+ return isChildOf(field.entityUuid, parentUuid);
266
+ }
208
267
  addElementClasses(document.body, "bk-body-loading", isLoading);
209
268
  const mutateWithLoadingState = async (callback, errorMessage, successMessage) => {
210
269
  if (!callback) {
@@ -254,7 +313,7 @@ export default async function(adapter, context, $t, providerKey) {
254
313
  }
255
314
  }
256
315
  const canEdit = computed(
257
- () => stateLoaded.value && !!owner.value?.currentUserIsOwner && !stateLoadError.value
316
+ () => stateLoaded.value && !!owner.value?.currentUserIsOwner && !stateLoadError.value && permissions.includes("edit")
258
317
  );
259
318
  const isTranslation = computed(
260
319
  () => context.value.language !== translation.value.sourceLanguage && translation.value.isTranslatable
@@ -322,7 +381,11 @@ export default async function(adapter, context, $t, providerKey) {
322
381
  getMutatedField,
323
382
  getFieldListForBlock,
324
383
  getAllUuids,
384
+ getNestingLevel,
385
+ isChildOf,
325
386
  setOverrideState,
326
- clearOverrideState
387
+ clearOverrideState,
388
+ fromLibraryUuids: readonly(fromLibraryUuids),
389
+ permissions: computed(() => permissions)
327
390
  };
328
391
  }
@@ -1,7 +1,8 @@
1
1
  import { type ComputedRef, type WritableComputedRef } from '#imports';
2
- import type { BlokkliAdapter } from '#blokkli/adapter';
2
+ import type { AdapterContext, BlokkliAdapter } from '#blokkli/adapter';
3
3
  export type StorageProvider = {
4
4
  use: <T>(key: string | ComputedRef<string>, defaultValue: T, persist?: boolean) => WritableComputedRef<T>;
5
+ useWithContextPrefix: <T>(key: string, defaultValue: T, persist?: boolean) => WritableComputedRef<T>;
5
6
  clearAll: () => void;
6
7
  clear: (key: string) => void;
7
8
  };
@@ -13,4 +14,4 @@ export type StorageProvider = {
13
14
  * This composable can be used to keep state across page navigations and
14
15
  * even after a refresh.
15
16
  */
16
- export default function (adapter: BlokkliAdapter<any>): Promise<StorageProvider>;
17
+ export default function (adapter: BlokkliAdapter<any>, context: ComputedRef<AdapterContext>): Promise<StorageProvider>;
@@ -15,7 +15,7 @@ const getExisting = (key) => {
15
15
  } catch {
16
16
  }
17
17
  };
18
- export default async function(adapter) {
18
+ export default async function(adapter, context) {
19
19
  const values = ref({});
20
20
  const defaults = ref({});
21
21
  let timeout = null;
@@ -97,6 +97,10 @@ export default async function(adapter) {
97
97
  }
98
98
  });
99
99
  };
100
+ const useWithContextPrefix = (key, providedDefaultValue, persist) => {
101
+ const fullKey = key + ":" + context.value.entityType + ":" + context.value.entityUuid;
102
+ return use(fullKey, providedDefaultValue, persist);
103
+ };
100
104
  const clearAll = () => {
101
105
  values.value = {};
102
106
  Object.keys(window.localStorage).forEach((key) => {
@@ -110,5 +114,5 @@ export default async function(adapter) {
110
114
  values.value[storageKey] = void 0;
111
115
  window.localStorage.removeItem(storageKey);
112
116
  };
113
- return { use, clearAll, clear };
117
+ return { use, useWithContextPrefix, clearAll, clear };
114
118
  }
@@ -6,6 +6,7 @@ export declare const INJECT_NESTING_LEVEL: unique symbol;
6
6
  export declare const INJECT_IS_PREVIEW: unique symbol;
7
7
  export declare const INJECT_IS_IN_REUSABLE: unique symbol;
8
8
  export declare const INJECT_REUSABLE_OPTIONS: unique symbol;
9
+ export declare const INJECT_REUSABLE_UUID: unique symbol;
9
10
  export declare const INJECT_FIELD_LIST_TYPE: unique symbol;
10
11
  export declare const INJECT_FIELD_LIST_BLOCKS: unique symbol;
11
12
  export declare const INJECT_FIELD_PROXY_MODE: unique symbol;
@@ -6,6 +6,7 @@ export const INJECT_NESTING_LEVEL = Symbol("blokkli_nesting_level");
6
6
  export const INJECT_IS_PREVIEW = Symbol("blokkli_is_preview");
7
7
  export const INJECT_IS_IN_REUSABLE = Symbol("blokkli_is_in_reusable");
8
8
  export const INJECT_REUSABLE_OPTIONS = Symbol("blokkli_from_library_options");
9
+ export const INJECT_REUSABLE_UUID = Symbol("blokkli_from_library_uuid");
9
10
  export const INJECT_FIELD_LIST_TYPE = Symbol("blokkli_field_list_type");
10
11
  export const INJECT_FIELD_LIST_BLOCKS = Symbol("blokkli_field_list_blocks");
11
12
  export const INJECT_FIELD_PROXY_MODE = Symbol("blokkli_field_proxy_mode");
@@ -4,6 +4,7 @@ import type { AddListOrientation, Coord, Rectangle, Size } from '#blokkli/types'
4
4
  import type { Viewport } from '#blokkli/constants';
5
5
  import type { StateProvider } from './stateProvider.js';
6
6
  import type { AdapterContext } from '#blokkli/adapter';
7
+ import type { ThemeColorName } from '#blokkli/types/theme';
7
8
  export type UiProvider = {
8
9
  rootElement: () => HTMLElement;
9
10
  artboardElement: () => HTMLElement;
@@ -19,7 +20,11 @@ export type UiProvider = {
19
20
  isAnalyzing: Ref<boolean>;
20
21
  isProxyMode: Ref<boolean>;
21
22
  hasDialogOpen: Ref<boolean>;
22
- hasAddTooltipOpen: Ref<boolean>;
23
+ hasTooltipOpen: ComputedRef<boolean>;
24
+ openTooltip: Ref<string>;
25
+ selectionColor: ComputedRef<ThemeColorName | null>;
26
+ setSelectionColor: (id: string, color: ThemeColorName) => void;
27
+ removeSelectionColor: (id: string) => void;
23
28
  hasTransformOverlayOpen: Ref<boolean>;
24
29
  isTransforming: ComputedRef<boolean>;
25
30
  setTransform: (label?: string | null | undefined) => void;
@@ -44,5 +49,7 @@ export type UiProvider = {
44
49
  formatDate: (date: string | Date, options?: Intl.DateTimeFormatOptions) => string;
45
50
  getAbsoluteElementRect: (v: HTMLElement | Rectangle, scale?: number, offset?: Coord) => Rectangle;
46
51
  getViewportRelativeRect: (rect: Rectangle, scale?: number, offset?: Coord) => Rectangle;
52
+ setBannerHeight: (id: string, height: number) => void;
53
+ removeBanner: (id: string) => void;
47
54
  };
48
55
  export default function (storage: StorageProvider, state: StateProvider, context: ComputedRef<AdapterContext>): UiProvider;
@@ -29,12 +29,19 @@ export default function(storage, state, context) {
29
29
  const isProxyMode = ref(false);
30
30
  const menuIsOpen = ref(false);
31
31
  const hasDialogOpen = ref(false);
32
- const hasAddTooltipOpen = ref(false);
32
+ const openTooltip = ref("");
33
33
  const hasTransformOverlayOpen = ref(false);
34
34
  const isAnimating = ref(false);
35
35
  const isAnalyzing = ref(false);
36
36
  const transformLabel = ref("");
37
37
  const openContextMenu = ref("");
38
+ const banners = ref({});
39
+ function setBannerHeight(id, height) {
40
+ banners.value[id] = height;
41
+ }
42
+ function removeBanner(id) {
43
+ banners.value[id] = 0;
44
+ }
38
45
  const selectionTopLeft = ref({ x: 0, y: 0 });
39
46
  const baseSettings = storage.use("feature:settings:settings", {});
40
47
  const lowPerformanceMode = computed(
@@ -175,6 +182,11 @@ export default function(storage, state, context) {
175
182
  height -= 70;
176
183
  }
177
184
  }
185
+ const bannerHeights = Object.values(banners.value).filter(Boolean);
186
+ bannerHeights.forEach((bannerHeight) => {
187
+ height -= bannerHeight;
188
+ });
189
+ height -= bannerHeights.length * 10;
178
190
  return height;
179
191
  });
180
192
  const blockingPaddingX = computed(() => 15);
@@ -279,6 +291,20 @@ export default function(storage, state, context) {
279
291
  resizeObserver.unobserve(artboard);
280
292
  resizeObserver.disconnect();
281
293
  });
294
+ const hasTooltipOpen = computed(() => !!openTooltip.value);
295
+ const selectionColors = ref([]);
296
+ function setSelectionColor(id, color) {
297
+ selectionColors.value = [
298
+ ...selectionColors.value.filter((v) => v.id !== id),
299
+ { id, color }
300
+ ];
301
+ }
302
+ function removeSelectionColor(id) {
303
+ selectionColors.value = selectionColors.value.filter((v) => v.id !== id);
304
+ }
305
+ const selectionColor = computed(() => {
306
+ return selectionColors.value[selectionColors.value.length - 1]?.color ?? null;
307
+ });
282
308
  return {
283
309
  menu: {
284
310
  isOpen: menuIsOpen,
@@ -318,6 +344,12 @@ export default function(storage, state, context) {
318
344
  formatDate,
319
345
  hasDialogOpen,
320
346
  hasTransformOverlayOpen,
321
- hasAddTooltipOpen
347
+ hasTooltipOpen,
348
+ openTooltip,
349
+ selectionColor,
350
+ setSelectionColor,
351
+ removeSelectionColor,
352
+ setBannerHeight,
353
+ removeBanner
322
354
  };
323
355
  }
@@ -8,10 +8,16 @@ type RectangleBufferRect = Rectangle & {
8
8
  };
9
9
  type RectangleBufferCollectorOptions = {
10
10
  padding?: number;
11
+ deferredMode?: boolean;
11
12
  };
12
13
  type PlacedRectangle = Rectangle & {
13
14
  originalY: number;
14
15
  };
16
+ type PendingRect<T> = {
17
+ rect: Omit<T, 'index'>;
18
+ type: number;
19
+ checkOverlap: boolean;
20
+ };
15
21
  export declare class RectangleBufferCollector<T extends RectangleBufferRect> {
16
22
  gl?: WebGLRenderingContext;
17
23
  added: Set<string>;
@@ -26,10 +32,13 @@ export declare class RectangleBufferCollector<T extends RectangleBufferRect> {
26
32
  index: number;
27
33
  bufferInfo: BufferInfo | null;
28
34
  placedRects: PlacedRectangle[];
29
- constructor(gl?: WebGLRenderingContext, _options?: RectangleBufferCollectorOptions);
35
+ deferredMode: boolean;
36
+ pendingRects: PendingRect<T>[];
37
+ constructor(gl?: WebGLRenderingContext, options?: RectangleBufferCollectorOptions);
30
38
  reset(): void;
31
- getIdealPosition(x: number, y: number, width: number, height: number): Rectangle;
39
+ getIdealPosition(x: number, y: number, width: number, height: number, isEmptyField?: boolean): Rectangle;
32
40
  addRectangle(rect: Omit<T, 'index'>, type: number, checkOverlap?: boolean): void;
41
+ processPendingRects(): void;
33
42
  getIndex(id: string): number | undefined;
34
43
  updateRectangle(): void;
35
44
  createBufferInfo(): BufferInfo | null;