@blokkli/editor 2.0.0-alpha.16 → 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 (106) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +265 -83
  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/EditIndicator.vue +118 -44
  18. package/dist/runtime/components/Edit/EditIndicator.vue.d.ts +3 -0
  19. package/dist/runtime/components/Edit/EditProvider.vue +79 -22
  20. package/dist/runtime/components/Edit/EditProvider.vue.d.ts +2 -0
  21. package/dist/runtime/components/Edit/Features/Analyze/Overlay/index.vue +19 -20
  22. package/dist/runtime/components/Edit/Features/BlockAddList/index.vue +1 -1
  23. package/dist/runtime/components/Edit/Features/CommandPalette/index.vue +2 -0
  24. package/dist/runtime/components/Edit/Features/Comments/AddForm/index.vue +35 -20
  25. package/dist/runtime/components/Edit/Features/Comments/AddForm/index.vue.d.ts +5 -3
  26. package/dist/runtime/components/Edit/Features/Comments/CommentInput/index.vue +29 -0
  27. package/dist/runtime/components/Edit/Features/Comments/CommentInput/index.vue.d.ts +13 -0
  28. package/dist/runtime/components/Edit/Features/Comments/Overlay/Item/index.vue +22 -16
  29. package/dist/runtime/components/Edit/Features/Comments/Overlay/Item/index.vue.d.ts +1 -0
  30. package/dist/runtime/components/Edit/Features/Comments/Overlay/index.vue +15 -6
  31. package/dist/runtime/components/Edit/Features/Comments/index.vue +20 -8
  32. package/dist/runtime/components/Edit/Features/Debug/Rects/index.vue +26 -35
  33. package/dist/runtime/components/Edit/Features/Debug/Renderer.vue +240 -0
  34. package/dist/runtime/components/Edit/Features/Debug/Renderer.vue.d.ts +6 -0
  35. package/dist/runtime/components/Edit/Features/Debug/index.vue +4 -165
  36. package/dist/runtime/components/Edit/Features/DraggingOverlay/DragItems/index.vue +1 -1
  37. package/dist/runtime/components/Edit/Features/DraggingOverlay/DropTargets/index.vue +41 -37
  38. package/dist/runtime/components/Edit/Features/Edit/index.vue +1 -1
  39. package/dist/runtime/components/Edit/Features/EditableField/Overlay/Frame/index.vue +63 -3
  40. package/dist/runtime/components/Edit/Features/EditableField/Overlay/Plaintext/index.vue +13 -9
  41. package/dist/runtime/components/Edit/Features/EditableField/Overlay/index.vue +17 -76
  42. package/dist/runtime/components/Edit/Features/EditableField/index.vue +1 -1
  43. package/dist/runtime/components/Edit/Features/History/index.vue +5 -2
  44. package/dist/runtime/components/Edit/Features/Hover/Overlay/fragment.glsl +139 -0
  45. package/dist/runtime/components/Edit/Features/Hover/Overlay/index.vue +270 -0
  46. package/dist/runtime/components/Edit/Features/Hover/Overlay/index.vue.d.ts +6 -0
  47. package/dist/runtime/components/Edit/Features/Hover/Overlay/vertex.glsl +117 -0
  48. package/dist/runtime/components/Edit/Features/Hover/index.vue +25 -0
  49. package/dist/runtime/components/Edit/Features/Library/LibraryDialog/index.vue +19 -27
  50. package/dist/runtime/components/Edit/Features/Library/ReusableDialog/index.vue +27 -23
  51. package/dist/runtime/components/Edit/Features/Library/index.vue +2 -1
  52. package/dist/runtime/components/Edit/Features/MultiSelect/Overlay/index.vue +34 -27
  53. package/dist/runtime/components/Edit/Features/MultiSelect/index.vue +2 -4
  54. package/dist/runtime/components/Edit/Features/Options/Form/Item.vue +6 -1
  55. package/dist/runtime/components/Edit/Features/Options/Form/index.vue +1 -0
  56. package/dist/runtime/components/Edit/Features/Ownership/Renderer.vue +35 -0
  57. package/dist/runtime/components/Edit/Features/Ownership/Renderer.vue.d.ts +6 -0
  58. package/dist/runtime/components/Edit/Features/Ownership/index.vue +7 -25
  59. package/dist/runtime/components/Edit/Features/ProxyView/index.vue +5 -1
  60. package/dist/runtime/components/Edit/Features/Selection/AddButtons/Overlay/index.vue +39 -74
  61. package/dist/runtime/components/Edit/Features/Selection/AddButtons/Overlay/index.vue.d.ts +4 -2
  62. package/dist/runtime/components/Edit/Features/Selection/AddButtons/Renderer/fragment.glsl +106 -0
  63. package/dist/runtime/components/Edit/Features/Selection/AddButtons/Renderer/index.vue +417 -0
  64. package/dist/runtime/components/Edit/Features/Selection/AddButtons/Renderer/index.vue.d.ts +32 -0
  65. package/dist/runtime/components/Edit/Features/Selection/AddButtons/Renderer/vertex.glsl +102 -0
  66. package/dist/runtime/components/Edit/Features/Selection/AddButtons/index.vue +33 -106
  67. package/dist/runtime/components/Edit/Features/Selection/Overlay/index.vue +88 -29
  68. package/dist/runtime/components/Edit/Features/Selection/Overlay/index.vue.d.ts +2 -0
  69. package/dist/runtime/components/Edit/Features/Selection/Overlay/vertex.glsl +11 -2
  70. package/dist/runtime/components/Edit/Features/Selection/index.vue +5 -12
  71. package/dist/runtime/components/Edit/Features/Translations/Banner/index.vue +17 -11
  72. package/dist/runtime/components/Edit/Features/Translations/index.vue +13 -16
  73. package/dist/runtime/components/Edit/Form/Text/index.vue +2 -1
  74. package/dist/runtime/components/Edit/Form/Text/index.vue.d.ts +1 -0
  75. package/dist/runtime/components/Edit/Indicators/index.vue +1 -1
  76. package/dist/runtime/components/Edit/Konami/Game/index.vue +5 -5
  77. package/dist/runtime/components/Edit/index.d.ts +5 -3
  78. package/dist/runtime/components/Edit/index.js +8 -4
  79. package/dist/runtime/composables/defineBlokkli.js +4 -2
  80. package/dist/runtime/css/output.css +1 -1
  81. package/dist/runtime/helpers/animationProvider.d.ts +34 -1
  82. package/dist/runtime/helpers/animationProvider.js +175 -48
  83. package/dist/runtime/helpers/composables/defineRenderer.d.ts +8 -0
  84. package/dist/runtime/helpers/composables/defineRenderer.js +8 -0
  85. package/dist/runtime/helpers/composables/useStickyToolbar.d.ts +4 -1
  86. package/dist/runtime/helpers/composables/useStickyToolbar.js +53 -35
  87. package/dist/runtime/helpers/dom/index.d.ts +1 -0
  88. package/dist/runtime/helpers/domProvider.d.ts +46 -0
  89. package/dist/runtime/helpers/domProvider.js +95 -6
  90. package/dist/runtime/helpers/editableProvider.d.ts +14 -0
  91. package/dist/runtime/helpers/editableProvider.js +144 -0
  92. package/dist/runtime/helpers/stateProvider.d.ts +6 -2
  93. package/dist/runtime/helpers/stateProvider.js +66 -3
  94. package/dist/runtime/helpers/storageProvider.d.ts +3 -2
  95. package/dist/runtime/helpers/storageProvider.js +6 -2
  96. package/dist/runtime/helpers/symbols.d.ts +1 -0
  97. package/dist/runtime/helpers/symbols.js +1 -0
  98. package/dist/runtime/helpers/uiProvider.d.ts +8 -1
  99. package/dist/runtime/helpers/uiProvider.js +34 -2
  100. package/dist/runtime/plugins/blokkliEditable.js +74 -3
  101. package/dist/runtime/types/index.d.ts +13 -1
  102. package/package.json +1 -1
  103. package/dist/runtime/components/Edit/DragInteractions/index.vue +0 -401
  104. package/dist/runtime/components/Edit/Features/Selection/AddButtons/AddButtonsField.vue +0 -54
  105. package/dist/runtime/components/Edit/Features/Selection/AddButtons/AddButtonsField.vue.d.ts +0 -14
  106. /package/dist/runtime/components/Edit/{DragInteractions → Features/Hover}/index.vue.d.ts +0 -0
@@ -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
  }
@@ -1,17 +1,88 @@
1
+ import {
2
+ INJECT_APP,
3
+ INJECT_ENTITY_CONTEXT,
4
+ INJECT_IS_IN_REUSABLE
5
+ } from "#blokkli/helpers/symbols";
1
6
  import { defineNuxtPlugin } from "#imports";
7
+ function getInjection(vnode, symbol) {
8
+ return vnode.ctx?.provides?.[symbol];
9
+ }
10
+ function getBlokkliApp(vnode) {
11
+ return getInjection(vnode, INJECT_APP);
12
+ }
13
+ function getEntityContext(vnode) {
14
+ return getInjection(vnode, INJECT_ENTITY_CONTEXT);
15
+ }
16
+ function isInReusable(vnode) {
17
+ return !!getInjection(vnode, INJECT_IS_IN_REUSABLE);
18
+ }
19
+ function getFieldName(binding) {
20
+ const fieldName = binding.value?.name || binding.arg;
21
+ if (fieldName && typeof fieldName === "string") {
22
+ return fieldName;
23
+ }
24
+ }
25
+ function isEditing() {
26
+ if (import.meta.client) {
27
+ return window.location.search.includes("blokkliEditing");
28
+ }
29
+ return false;
30
+ }
2
31
  export default defineNuxtPlugin((nuxtApp) => {
3
32
  nuxtApp.vueApp.directive("blokkli-editable", {
4
- created(el, binding) {
33
+ created(el, binding, vnode) {
5
34
  if (import.meta.client) {
6
- if (!window.location.search.includes("blokkliEditing")) {
35
+ if (!isEditing()) {
7
36
  return;
8
37
  }
9
- const fieldName = binding.value?.name || binding.arg;
38
+ if (isInReusable(vnode)) {
39
+ return;
40
+ }
41
+ const fieldName = getFieldName(binding);
10
42
  if (!fieldName) {
11
43
  return;
12
44
  }
13
45
  el.dataset.blokkliEditableField = fieldName;
14
46
  }
47
+ },
48
+ mounted(el, binding, vnode) {
49
+ if (!isEditing()) {
50
+ return;
51
+ }
52
+ if (isInReusable(vnode)) {
53
+ return;
54
+ }
55
+ const app = getBlokkliApp(vnode);
56
+ if (!app) {
57
+ return;
58
+ }
59
+ const entity = getEntityContext(vnode);
60
+ if (!entity) {
61
+ return;
62
+ }
63
+ const fieldName = getFieldName(binding);
64
+ if (!fieldName) {
65
+ return;
66
+ }
67
+ app.editable.registerEditableField(el, fieldName, entity);
68
+ },
69
+ beforeUnmount(el, binding, vnode) {
70
+ if (!isEditing()) {
71
+ return;
72
+ }
73
+ const app = getBlokkliApp(vnode);
74
+ if (!app) {
75
+ return;
76
+ }
77
+ const entity = getEntityContext(vnode);
78
+ if (!entity) {
79
+ return;
80
+ }
81
+ const fieldName = getFieldName(binding);
82
+ if (!fieldName) {
83
+ return;
84
+ }
85
+ app.editable.unregisterEditableField(el, fieldName, entity);
15
86
  }
16
87
  });
17
88
  nuxtApp.vueApp.directive("blokkli-droppable", {
@@ -8,6 +8,7 @@ import type { KeyboardProvider } from '../helpers/keyboardProvider.js';
8
8
  import type { UiProvider } from '../helpers/uiProvider.js';
9
9
  import type { AnimationProvider } from '../helpers/animationProvider.js';
10
10
  import type { StateProvider } from '../helpers/stateProvider.js';
11
+ import type { EditableProvider } from '../helpers/editableProvider.js';
11
12
  import type { TextProvider } from '../helpers/textProvider.js';
12
13
  import type { PluginProvider } from '../helpers/pluginProvider.js';
13
14
  import type { eventBus } from './../helpers/eventBus.js';
@@ -381,6 +382,7 @@ export type BlokkliProviderEntityContext = {
381
382
  bundle: string;
382
383
  language?: string;
383
384
  };
385
+ export type EditPermission = 'view' | 'edit' | 'review';
384
386
  export type EditEntity = {
385
387
  label?: string;
386
388
  status?: boolean;
@@ -569,7 +571,7 @@ export interface BlockBundleDefinition {
569
571
  allowReusable?: boolean;
570
572
  isTranslatable?: boolean;
571
573
  }
572
- export type EditMode = 'readonly' | 'editing' | 'translating';
574
+ export type EditMode = 'readonly' | 'editing' | 'translating' | 'review';
573
575
  export type MutatedOptions = {
574
576
  [uuid: string]: {
575
577
  [key: string]: string;
@@ -848,11 +850,15 @@ export type Coord = {
848
850
  };
849
851
  export type Rectangle = Size & Coord;
850
852
  export type CanvasDrawEvent = {
853
+ gl: WebGLRenderingContext;
851
854
  mouseX: number;
852
855
  mouseY: number;
856
+ mouseArtboard: Coord;
853
857
  artboardOffset: Coord;
854
858
  artboardScale: number;
859
+ artboardSize: Size;
855
860
  time: number;
861
+ selectedUuids: string[];
856
862
  };
857
863
  export type MakeReusableEvent = {
858
864
  label: string;
@@ -1043,10 +1049,15 @@ export type EventbusEvents = {
1043
1049
  'item:doubleClick': DraggableExistingBlock;
1044
1050
  scrollIntoView: ScrollIntoViewEvent;
1045
1051
  'animationFrame:before': AnimationFrameBeforeEvent;
1052
+ 'animationFrame:after': undefined;
1046
1053
  'canvas:draw': CanvasDrawEvent;
1047
1054
  'state:reloaded': undefined;
1048
1055
  addContentSearchItem: AddContentSearchItemEvent;
1049
1056
  'option:update': UpdateBlockOptionEvent;
1057
+ /**
1058
+ * Emitted after finishing changing options.
1059
+ */
1060
+ 'option:finish-change': undefined;
1050
1061
  'plugin:mount': PluginMountEvent;
1051
1062
  'plugin:unmount': PluginUnmountEvent;
1052
1063
  'editable:focus': EditableFieldFocusEvent;
@@ -1129,6 +1140,7 @@ export interface BlokkliApp {
1129
1140
  debug: DebugProvider;
1130
1141
  indicators: IndicatorsProvider;
1131
1142
  plugins: PluginProvider;
1143
+ editable: EditableProvider;
1132
1144
  }
1133
1145
  export type PasteExistingBlocksEvent = {
1134
1146
  uuids: string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blokkli/editor",
3
- "version": "2.0.0-alpha.16",
3
+ "version": "2.0.0-alpha.17",
4
4
  "description": "Interactive page building experience for Nuxt",
5
5
  "repository": "blokkli/editor",
6
6
  "type": "module",