@blokkli/editor 1.1.3 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +1693 -41
  3. package/dist/runtime/adapter/drupal/graphql/base.graphql +40 -3
  4. package/dist/runtime/adapter/drupal/graphql/comments.graphql +6 -1
  5. package/dist/runtime/adapter/drupal/graphql/transform.graphql +10 -2
  6. package/dist/runtime/adapter/drupal/graphqlMiddleware.js +44 -18
  7. package/dist/runtime/adapter/index.d.ts +15 -2
  8. package/dist/runtime/blokkliPlugins/Sidebar/index.vue +4 -1
  9. package/dist/runtime/components/BlokkliField.vue +8 -2
  10. package/dist/runtime/components/Edit/Actions/index.vue +27 -6
  11. package/dist/runtime/components/Edit/AnimationCanvas/index.vue +1 -0
  12. package/dist/runtime/components/Edit/DragInteractions/index.vue +7 -0
  13. package/dist/runtime/components/Edit/EditProvider.vue +23 -5
  14. package/dist/runtime/components/Edit/Features/Artboard/index.vue +4 -0
  15. package/dist/runtime/components/Edit/Features/BlockAddList/index.vue +1 -0
  16. package/dist/runtime/components/Edit/Features/Clipboard/index.vue +3 -1
  17. package/dist/runtime/components/Edit/Features/CommandPalette/Palette/index.vue +14 -2
  18. package/dist/runtime/components/Edit/Features/Debug/index.vue +26 -4
  19. package/dist/runtime/components/Edit/Features/Diff/DiffView/index.vue +236 -0
  20. package/dist/runtime/components/Edit/Features/Diff/index.vue +37 -0
  21. package/dist/runtime/components/Edit/Features/DraggingOverlay/DragItems/index.vue +14 -4
  22. package/dist/runtime/components/Edit/Features/DraggingOverlay/DropTargets/index.vue +3 -0
  23. package/dist/runtime/components/Edit/Features/DraggingOverlay/index.vue +20 -6
  24. package/dist/runtime/components/Edit/Features/EditForm/Frame/index.vue +8 -1
  25. package/dist/runtime/components/Edit/Features/EditableField/index.vue +11 -5
  26. package/dist/runtime/components/Edit/Features/EntityTitle/index.vue +1 -0
  27. package/dist/runtime/components/Edit/Features/History/index.vue +3 -1
  28. package/dist/runtime/components/Edit/Features/ImportExisting/index.vue +5 -2
  29. package/dist/runtime/components/Edit/Features/Library/ReusableDialog/index.vue +10 -3
  30. package/dist/runtime/components/Edit/Features/MediaLibrary/Library/Item.vue +46 -0
  31. package/dist/runtime/components/Edit/Features/MediaLibrary/Library/index.vue +66 -34
  32. package/dist/runtime/components/Edit/Features/MultiSelect/index.vue +4 -1
  33. package/dist/runtime/components/Edit/Features/Options/Form/index.vue +14 -1
  34. package/dist/runtime/components/Edit/Features/Preview/index.vue +2 -1
  35. package/dist/runtime/components/Edit/Features/PreviewGrant/index.vue +2 -1
  36. package/dist/runtime/components/Edit/Features/Publish/index.vue +2 -0
  37. package/dist/runtime/components/Edit/Features/ResponsivePreview/index.vue +2 -1
  38. package/dist/runtime/components/Edit/Features/Selection/Overlay/fragment.glsl +78 -44
  39. package/dist/runtime/components/Edit/Features/Selection/Overlay/index.vue +8 -8
  40. package/dist/runtime/components/Edit/Features/Selection/Overlay/vertex.glsl +6 -1
  41. package/dist/runtime/components/Edit/Features/Settings/Dialog/FeatureSetting/index.vue +23 -2
  42. package/dist/runtime/components/Edit/Features/Settings/Dialog/index.vue +71 -38
  43. package/dist/runtime/components/Edit/Features/Settings/index.vue +4 -0
  44. package/dist/runtime/components/Edit/Features/Transform/index.vue +5 -1
  45. package/dist/runtime/components/Edit/Features/Translations/index.vue +24 -2
  46. package/dist/runtime/components/Edit/Features/index.vue +4 -0
  47. package/dist/runtime/components/Edit/InfoBox/index.vue +14 -0
  48. package/dist/runtime/components/Edit/Messages/index.vue +10 -12
  49. package/dist/runtime/components/Edit/PreviewProvider.vue +9 -2
  50. package/dist/runtime/components/Edit/Sortli/index.vue +6 -1
  51. package/dist/runtime/components/Edit/index.d.ts +2 -1
  52. package/dist/runtime/components/Edit/index.js +3 -1
  53. package/dist/runtime/constants/index.d.ts +1 -1
  54. package/dist/runtime/constants/index.js +1 -0
  55. package/dist/runtime/css/output.css +1 -1
  56. package/dist/runtime/helpers/animationProvider.js +2 -1
  57. package/dist/runtime/helpers/featuresProvider.d.ts +7 -14
  58. package/dist/runtime/helpers/featuresProvider.js +29 -1
  59. package/dist/runtime/helpers/stateProvider.d.ts +1 -0
  60. package/dist/runtime/helpers/stateProvider.js +23 -4
  61. package/dist/runtime/helpers/uiProvider.d.ts +5 -1
  62. package/dist/runtime/helpers/uiProvider.js +10 -2
  63. package/dist/runtime/icons/diff.svg +1 -0
  64. package/dist/runtime/icons/info.svg +1 -0
  65. package/dist/runtime/types/generatedModuleTypes.d.ts +4 -4
  66. package/dist/runtime/types/index.d.ts +59 -1
  67. package/package.json +2 -1
@@ -50,30 +50,24 @@
50
50
  class="bk-media-library-items bk-scrollbar-light"
51
51
  :class="[{ 'bk-is-sortli': isSortli }, 'bk-is-' + listView]"
52
52
  >
53
- <Component :is="isSortli ? Sortli : 'div'" no-transition>
54
- <div
53
+ <Sortli v-if="isSortli" no-transition :get-drag-items="getDragItems">
54
+ <Item
55
55
  v-for="item in items"
56
56
  :key="item.mediaId"
57
- class="bk-media-library-items-item"
58
- :class="{ 'bk-is-selected': modelValue === item.mediaId }"
59
- :data-sortli-id="'media_library_' + item.mediaId"
60
- data-element-type="media_library"
61
- :data-item-bundle="item.targetBundles[0]"
62
- :data-media-id="item.mediaId"
63
- :data-media-bundle="item.mediaBundle"
64
- @click="onClick(item.mediaId)"
65
- >
66
- <div>
67
- <div class="bk-media-library-items-item-image">
68
- <img :src="item.thumbnail" />
69
- </div>
70
- </div>
71
- <div class="bk-media-library-items-item-text">
72
- <h3>{{ item.label }}</h3>
73
- <p>{{ item.context }}</p>
74
- </div>
75
- </div>
76
- </Component>
57
+ v-bind="item"
58
+ v-model="selected"
59
+ />
60
+ </Sortli>
61
+
62
+ <div v-else>
63
+ <Item v-for="item in items" :key="item.mediaId" v-bind="item" />
64
+ </div>
65
+ </div>
66
+
67
+ <div v-if="selected.length" class="bk-media-library-cancel">
68
+ <button class="bk-button bk-is-primary" @click.prevent="selected = []">
69
+ {{ $t('cancelSelection', 'Cancel selection') }}
70
+ </button>
77
71
  </div>
78
72
 
79
73
  <div class="bk-pagination">
@@ -100,20 +94,60 @@ import {
100
94
  import { Sortli, Icon } from '#blokkli/components'
101
95
  import type { MediaLibraryFilter, MediaLibraryGetResults } from './../types'
102
96
  import type { BlokkliIcon } from '#blokkli/icons'
97
+ import Item from './Item.vue'
98
+ import type { DraggableItem, DraggableMediaLibraryItem } from '#blokkli/types'
99
+ import { buildDraggableItem, falsy } from '#blokkli/helpers'
100
+ import onBlokkliEvent from '#blokkli/helpers/composables/onBlokkliEvent'
103
101
 
104
- const props = defineProps<{
102
+ defineProps<{
105
103
  isSortli?: boolean
106
104
  modelValue?: string
107
105
  }>()
108
106
 
109
- const emit = defineEmits(['update:modelValue'])
107
+ const { adapter, storage, $t } = useBlokkli()
108
+
109
+ const selected = ref<string[]>([])
110
+ const listEl = ref<HTMLDivElement | null>(null)
111
+ const page = ref(0)
112
+ const key = computed(() => Object.values(filterValues.value).join(','))
113
+
114
+ function getDragItems(activeItem?: DraggableItem): DraggableItem[] | null {
115
+ if (!selected.value.length || !listEl.value) {
116
+ return null
117
+ }
118
+
119
+ const activeId =
120
+ activeItem?.itemType === 'media_library' ? activeItem.mediaId : null
121
+
122
+ const items: DraggableMediaLibraryItem[] = selected.value
123
+ .map((id) => {
124
+ const el = listEl.value?.querySelector(
125
+ `[data-sortli-id="media_library_${id}"]`,
126
+ )
127
+ if (!(el instanceof HTMLElement)) {
128
+ return null
129
+ }
130
+
131
+ const item = buildDraggableItem(el)
132
+ if (item?.itemType === 'media_library') {
133
+ return item
134
+ }
135
+
136
+ return null
137
+ })
138
+ .filter(falsy)
139
+
140
+ if (!activeId) {
141
+ return items
142
+ }
143
+
144
+ const activeIsInSelection = items.find((v) => v.mediaId === activeId)
110
145
 
111
- const onClick = (id: string) => {
112
- if (props.isSortli) {
113
- return
146
+ if (activeIsInSelection) {
147
+ return items
114
148
  }
115
149
 
116
- emit('update:modelValue', id)
150
+ return null
117
151
  }
118
152
 
119
153
  type RenderedFilter = {
@@ -121,8 +155,6 @@ type RenderedFilter = {
121
155
  filter: MediaLibraryFilter
122
156
  }
123
157
 
124
- const { adapter, storage } = useBlokkli()
125
-
126
158
  const listView = storage.use<'horizontal' | 'grid'>(
127
159
  'mediaLibraryListView',
128
160
  'grid',
@@ -142,10 +174,6 @@ const toggleListView = () => {
142
174
 
143
175
  const filterValues = ref<Record<string, any>>({})
144
176
 
145
- const listEl = ref<HTMLDivElement | null>(null)
146
- const page = ref(0)
147
- const key = computed(() => Object.values(filterValues.value).join(','))
148
-
149
177
  watch(key, () => {
150
178
  page.value = 0
151
179
  })
@@ -182,4 +210,8 @@ const perPage = computed(() => data.value?.perPage || 0)
182
210
  const totalPages = computed(() => {
183
211
  return Math.ceil(total.value / perPage.value)
184
212
  })
213
+
214
+ onBlokkliEvent('item:dropped', function () {
215
+ selected.value = []
216
+ })
185
217
  </script>
@@ -29,7 +29,10 @@ const gl = animation.gl()
29
29
 
30
30
  const enabled = computed(
31
31
  () =>
32
- !selection.editableActive.value && state.editMode.value === 'editing' && gl,
32
+ !selection.editableActive.value &&
33
+ (state.editMode.value === 'editing' ||
34
+ state.editMode.value === 'translating') &&
35
+ gl,
33
36
  )
34
37
 
35
38
  const shouldRender = ref(false)
@@ -3,7 +3,8 @@
3
3
  v-if="availableOptions.length"
4
4
  class="bk-blokkli-item-options"
5
5
  @pointerup="onPointerUp"
6
- @mouseleave="stopChangingOptions"
6
+ @mouseleave="onMouseLeave"
7
+ @mouseenter="onMouseEnter"
7
8
  >
8
9
  <OptionsFormItem
9
10
  v-for="plugin in singleVisibleOptions"
@@ -102,6 +103,18 @@ const props = defineProps<{
102
103
  }>()
103
104
 
104
105
  let pointerTimeout: null | number = null
106
+ let mouseLeaveTimeout: null | number = null
107
+
108
+ function onMouseLeave() {
109
+ onMouseEnter()
110
+ mouseLeaveTimeout = window.setTimeout(stopChangingOptions, 500)
111
+ }
112
+
113
+ function onMouseEnter() {
114
+ if (mouseLeaveTimeout) {
115
+ window.clearTimeout(mouseLeaveTimeout)
116
+ }
117
+ }
105
118
 
106
119
  function onPointerUp(e: PointerEvent) {
107
120
  if (pointerTimeout) {
@@ -4,6 +4,7 @@
4
4
  :title="$t('previewNewWindow', 'Preview (new window)')"
5
5
  region="after-menu"
6
6
  icon="open_in_new"
7
+ :disabled="!state.canEdit.value"
7
8
  :tour-text="
8
9
  $t(
9
10
  'previewNewWindowTourText',
@@ -25,7 +26,7 @@ defineBlokkliFeature({
25
26
  description: 'Provides a button to open a preview in a new window.',
26
27
  })
27
28
 
28
- const { $t } = useBlokkli()
29
+ const { $t, state } = useBlokkli()
29
30
 
30
31
  const route = useRoute()
31
32
 
@@ -3,6 +3,7 @@
3
3
  v-if="!ui.isMobile.value"
4
4
  id="preview_with_smartphone"
5
5
  :title="$t('previewWithSmartphone', 'Preview (with smartphone)')"
6
+ :disabled="!state.canEdit.value"
6
7
  :tour-text="
7
8
  $t(
8
9
  'previewWithSmartphoneTourText',
@@ -54,7 +55,7 @@ const { adapter } = defineBlokkliFeature({
54
55
  viewports: ['desktop'],
55
56
  })
56
57
 
57
- const { $t, ui } = useBlokkli()
58
+ const { $t, ui, state } = useBlokkli()
58
59
 
59
60
  const qrCodeVisible = ref(false)
60
61
  const previewGrantUrl = ref<string | undefined | null>('')
@@ -27,6 +27,8 @@ const { adapter, settings } = defineBlokkliFeature({
27
27
  closeAfterPublish: {
28
28
  type: 'checkbox',
29
29
  label: 'Close editor after publishing',
30
+ description:
31
+ 'Immediately closes the editor after successfully publishing or saving.',
30
32
  default: true,
31
33
  group: 'behavior',
32
34
  },
@@ -4,6 +4,7 @@
4
4
  v-slot="{ width, height, isDetached }"
5
5
  :title="$t('responsivePreviewTitle', 'Responsive Preview')"
6
6
  :tour-text="tourText"
7
+ :disabled="!state.canEdit.value"
7
8
  :min-width="375"
8
9
  :min-height="375"
9
10
  :size="size"
@@ -83,7 +84,7 @@ defineBlokkliFeature({
83
84
  'Provides a responsive preview of the current edit state in an iframe.',
84
85
  })
85
86
 
86
- const { $t, storage } = useBlokkli()
87
+ const { $t, storage, state } = useBlokkli()
87
88
 
88
89
  const selectedViewportId = storage.use('mobile-preview:viewport', 'iphone-se')
89
90
  const isRotated = storage.use('mobile-preview:rotated', false)
@@ -1,17 +1,22 @@
1
1
  precision mediump float;
2
-
3
2
  varying vec4 v_quad;
4
3
  varying vec3 v_color;
5
4
  varying vec4 v_rect_radius;
6
5
  varying float v_thickness;
6
+ varying float v_rect_id;
7
7
  varying vec2 v_rect_size;
8
8
  varying vec2 v_rect_center;
9
- varying float v_transition;
9
+ varying float v_rect_width;
10
10
 
11
+ varying float v_transition;
11
12
  uniform float u_scale;
12
13
  uniform float u_dpi;
14
+ uniform float u_time;
15
+ uniform float u_is_transforming;
13
16
  uniform vec2 u_resolution;
14
17
 
18
+ #define PI (3.141592653589793)
19
+
15
20
  int pseudoQuadrant(vec2 p) {
16
21
  return int(floor(step(0.0, p.x) + 2.0 * step(0.0, -p.y)));
17
22
  }
@@ -19,79 +24,108 @@ int pseudoQuadrant(vec2 p) {
19
24
  float sdRoundBox(vec2 p, vec2 b, vec4 radii) {
20
25
  int idx = pseudoQuadrant(p);
21
26
  float cr;
22
-
23
- // Use correct radius. Bottom left and bottom right are flipped.
24
27
  if (idx == 0) cr = radii[0];
25
28
  else if (idx == 1) cr = radii[1];
26
29
  else if (idx == 2) cr = radii[3];
27
30
  else cr = radii[2];
28
-
29
31
  vec2 q = abs(p) - b + cr;
30
32
  return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - cr;
31
33
  }
32
34
 
33
- vec4 drawBox(float thickness, vec4 bg, vec4 fill, vec4 border) {
34
- float progress = gl_FragCoord.y / u_resolution.y;
35
- float u_borderThickness = max(thickness, 2.0); // The border size (in pixels)
36
- // vec2 size = v_rect_size + u_borderThickness * 2.0;
37
- vec2 size = v_rect_size + u_borderThickness * 2.0 * v_transition;
35
+ float bounceOut(float t) {
36
+ const float a = 4.0 / 11.0;
37
+ const float b = 8.0 / 11.0;
38
+ const float c = 9.0 / 10.0;
38
39
 
39
- float u_edgeSoftness = 1.0 + v_transition;
40
- vec4 radius = v_rect_radius * u_scale + vec4(u_borderThickness);
41
- vec4 u_cornerRadii = min(radius, min(size.x, size.y) / 2.0) * v_transition;
40
+ const float ca = 4356.0 / 361.0;
41
+ const float cb = 35442.0 / 1805.0;
42
+ const float cc = 16061.0 / 1805.0;
42
43
 
43
- // Border
44
- float u_borderSoftness = 1.0 + v_transition; // How soft the border should be (in pixels)
44
+ float t2 = t * t;
45
45
 
46
- // =========================================================================
46
+ return t < a
47
+ ? 7.5625 * t2
48
+ : t < b
49
+ ? 9.075 * t2 - 9.9 * t + 3.4
50
+ : t < c
51
+ ? ca * t2 - cb * t + cc
52
+ : 10.8 * t * t - 20.52 * t + 10.72;
53
+ }
47
54
 
48
- vec4 r = u_cornerRadii; // Animated corner radii
55
+ float exponentialIn(float t) {
56
+ return t == 0.0
57
+ ? t
58
+ : pow(2.0, 10.0 * (t - 1.0));
59
+ }
49
60
 
50
- // -------------------------------------------------------------------------
61
+ float getStripePattern(vec2 quadRelativePos, float time) {
62
+ float d = 300.0 * u_scale;
51
63
 
52
- // Fill SDF
53
- float distance =
54
- sdRoundBox(gl_FragCoord.xy - v_rect_center, size / 2.0, r) +
55
- u_borderThickness;
64
+ float t = mod(u_time + v_rect_id * 1000.0, 1200.0) / 1200.0;
56
65
 
57
- // AA
58
- float smoothedAlpha = 1.0 - smoothstep(0.0, u_edgeSoftness, distance);
66
+ float movement = t * 2.0 * PI;
59
67
 
60
- // -------------------------------------------------------------------------
61
- // Border: expanded from fill SDF, with AA
62
- float borderAlpha =
63
- 1.0 -
64
- smoothstep(
65
- u_borderThickness - u_borderSoftness,
66
- u_borderThickness,
67
- abs(distance - u_borderThickness)
68
- );
68
+ float normalizedSin =
69
+ (sin((quadRelativePos.y + quadRelativePos.x) / d + movement + v_rect_id) +
70
+ 1.0) /
71
+ 2.0;
69
72
 
70
- // -------------------------------------------------------------------------
71
- // Apply colors layer-by-layer: background <- rect <- border.
73
+ return normalizedSin * 0.2 + 0.5;
74
+ }
75
+
76
+ vec4 drawBox(float thickness, vec4 bg, vec4 fill, vec4 border, float offset) {
77
+ float borderThickness = max(thickness, 2.0);
78
+
79
+ float t = exponentialIn(
80
+ (sin(u_time / 270.0 - v_rect_id + offset) + 1.0) / 2.0
81
+ );
82
+
83
+ if (u_is_transforming >= 0.5) {
84
+ borderThickness = (t * 0.7 + 0.5) * borderThickness;
85
+ }
86
+ vec2 size = v_rect_size + borderThickness * 2.0 * v_transition;
87
+ float u_edgeSoftness = 1.0 + v_transition;
88
+ vec4 radius = v_rect_radius * u_scale + vec4(borderThickness);
89
+ vec4 u_cornerRadii = min(radius, min(size.x, size.y) / 2.0) * v_transition;
90
+ float u_borderSoftness = 1.0 + v_transition;
91
+
92
+ vec4 r = u_cornerRadii;
93
+
94
+ vec2 posRelativeToQuad = gl_FragCoord.xy - v_rect_center;
95
+
96
+ float mainDist = sdRoundBox(posRelativeToQuad, size / 2.0, r);
97
+ float innerDist = mainDist;
98
+
99
+ float fillAlpha = 1.0 - smoothstep(-u_edgeSoftness, 0.0, innerDist);
100
+ float borderAlpha =
101
+ 1.0 - smoothstep(-u_borderSoftness, 0.0, abs(mainDist) - borderThickness);
72
102
 
73
- // Blend background with fill
74
- vec4 res_shadow_with_rect_color = mix(bg, fill, min(fill.a, smoothedAlpha));
103
+ vec4 stripedFill = vec4(1.0, 1.0, 1.0, 0.0);
104
+ if (u_is_transforming >= 0.5) {
105
+ stripedFill = fill;
106
+ stripedFill.a = getStripePattern(posRelativeToQuad, u_time);
107
+ borderAlpha *= t + 0.6;
108
+ }
75
109
 
76
- // Blend (background+fill) with border
77
- return mix(res_shadow_with_rect_color, border, min(border.a, borderAlpha));
110
+ vec4 res_with_fill = mix(bg, stripedFill, fillAlpha * stripedFill.a);
111
+ return mix(res_with_fill, border, borderAlpha * border.a);
78
112
  }
79
113
 
80
114
  void main() {
81
- // Red border that should be below the blue border.
82
115
  vec4 borderBottom = drawBox(
83
116
  v_transition * (v_thickness * 2.0),
84
117
  vec4(v_color, 0.0),
85
- vec4(v_color, 0.5 - 0.5 * v_transition),
86
- vec4(v_color, 0.4)
118
+ vec4(v_color, 0.5),
119
+ vec4(v_color, 0.4),
120
+ 0.0
87
121
  );
88
122
 
89
- // Blue border that should be on top of the red border.
90
123
  vec4 borderTop = drawBox(
91
124
  v_thickness,
92
125
  vec4(v_color, 0.0),
93
126
  vec4(v_color, 0.0),
94
- vec4(v_color, 1.0)
127
+ vec4(v_color, 1.0),
128
+ -0.2
95
129
  );
96
130
 
97
131
  vec4 finalColor = mix(borderBottom, borderTop, borderTop.a);
@@ -22,7 +22,7 @@ const props = defineProps<{
22
22
  gl: WebGLRenderingContext
23
23
  }>()
24
24
 
25
- const { animation, theme, dom } = useBlokkli()
25
+ const { animation, theme, dom, ui } = useBlokkli()
26
26
 
27
27
  const programInfo = animation.registerProgram('selection', props.gl, [vs, fs])
28
28
 
@@ -99,15 +99,15 @@ class SelectionRectangleBufferCollector extends RectangleBufferCollector<Selecti
99
99
 
100
100
  const collector = new SelectionRectangleBufferCollector(props.gl)
101
101
 
102
- const uniforms = {
103
- u_color_default: toShaderColor(theme.accent.value[600]),
104
- u_color_inverted: [255, 255, 255],
105
- }
106
-
107
- onBlokkliEvent('canvas:draw', () => {
102
+ onBlokkliEvent('canvas:draw', (e) => {
108
103
  props.gl.useProgram(programInfo.program)
109
104
 
110
- setUniforms(programInfo, uniforms)
105
+ setUniforms(programInfo, {
106
+ u_color_default: toShaderColor(theme.accent.value[600]),
107
+ u_color_inverted: [255, 255, 255],
108
+ u_is_transforming: ui.isTransforming.value ? 1 : 0,
109
+ u_time: e.time,
110
+ })
111
111
  animation.setSharedUniforms(props.gl, programInfo)
112
112
  const { info, hasChanged } = collector.getBufferInfo()
113
113
 
@@ -27,11 +27,13 @@ varying float v_thickness;
27
27
  varying vec2 v_rect_size;
28
28
  varying vec2 v_rect_center;
29
29
  varying float v_transition;
30
+ varying float v_rect_id;
31
+ varying float v_rect_width;
30
32
 
31
33
  void main() {
32
34
  // Define the increase size in viewport terms (not affected by u_scale)
33
35
  float thickness = (0.5 + smoothstep(0.3, 1.0, u_scale) * 2.5) * u_dpi;
34
- float increaseSize = max(thickness, 15.0);
36
+ float increaseSize = max(thickness, 25.0);
35
37
 
36
38
  // Calculate the new dimensions of the quad
37
39
  vec4 adjusted_quad = a_quad;
@@ -75,10 +77,13 @@ void main() {
75
77
  );
76
78
  v_quad = transformed_quad;
77
79
 
80
+ v_rect_width = adjusted_quad.x;
81
+
78
82
  // Set color and other varying variables
79
83
  v_color = a_rect_type > 0.5 ? u_color_inverted : u_color_default;
80
84
  v_rect_radius = a_rect_radius * u_dpi;
81
85
  v_thickness = thickness;
86
+ v_rect_id = a_rect_id;
82
87
  v_rect_size = vec2(v_quad.z, v_quad.w);
83
88
  v_rect_center = vec2(v_quad.x + v_quad.z / 2.0, v_quad.y + v_quad.w / 2.0); // The pixel-space rectangle center location
84
89
  v_transition = smoothstep(0.5, 0.8, u_scale);
@@ -7,8 +7,13 @@
7
7
  class="peer"
8
8
  @change="toggleCheckbox"
9
9
  />
10
- <div />
11
- <span>{{ settingLabel }}</span>
10
+ <div class="bk-checkbox-toggle-toggle" />
11
+ <div class="bk-checkbox-toggle-label">
12
+ <div>{{ settingLabel }}</div>
13
+ <div v-if="settingDescription">
14
+ {{ settingDescription }}
15
+ </div>
16
+ </div>
12
17
  </label>
13
18
  <div v-else-if="setting.type === 'radios'">
14
19
  <h3 class="bk-form-label">
@@ -80,6 +85,22 @@ const settingLabel = computed(() => {
80
85
  )
81
86
  })
82
87
 
88
+ const settingDescription = computed(() => {
89
+ const translated = textTranslation(
90
+ 'feature_' +
91
+ props.featureId +
92
+ '_setting_' +
93
+ props.settingsKey +
94
+ '_description',
95
+ )
96
+
97
+ if (!translated && 'description' in props.setting) {
98
+ return props.setting.description
99
+ }
100
+
101
+ return translated
102
+ })
103
+
83
104
  const getOptionLabel = (key: string, defaultLabel: string) => {
84
105
  return (
85
106
  textTranslation(
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <DialogModal
3
3
  :title="$t('settingsDialogTitle', 'Change settings')"
4
- :width="700"
4
+ :width="900"
5
5
  hide-buttons
6
6
  icon="cog"
7
7
  @cancel="$emit('cancel')"
@@ -10,14 +10,17 @@
10
10
  <div v-for="group in groups" :key="group.key" class="bk-form-section">
11
11
  <h3 class="bk-settings-group-title">
12
12
  <span>{{ group.label }}</span>
13
+ <span v-if="group.id === 'beta'" class="bk-beta-indicator">BETA</span>
13
14
  </h3>
14
- <FeatureSetting
15
- v-for="setting in group.settings"
16
- :key="group.key + setting.settingsKey"
17
- :feature-id="setting.featureId"
18
- :settings-key="setting.settingsKey"
19
- :setting="setting.setting"
20
- />
15
+ <div>
16
+ <FeatureSetting
17
+ v-for="setting in group.settings"
18
+ :key="group.key + setting.settingsKey"
19
+ :feature-id="setting.featureId"
20
+ :settings-key="setting.settingsKey"
21
+ :setting="setting.setting"
22
+ />
23
+ </div>
21
24
  </div>
22
25
  </div>
23
26
  </DialogModal>
@@ -35,6 +38,8 @@ import { settingsOverride } from '#blokkli/config'
35
38
 
36
39
  const { $t, features, ui } = useBlokkli()
37
40
 
41
+ const getTranslation = $t
42
+
38
43
  type FeatureSetting = {
39
44
  featureId: ValidFeatureKey
40
45
  settingsKey: string
@@ -58,6 +63,8 @@ const getGroupLabel = (key: SettingsGroup): string => {
58
63
  return $t('settingsAdvanced', 'Advanced')
59
64
  } else if (key === 'artboard') {
60
65
  return $t('settingsArtboard', 'Artboard')
66
+ } else if (key === 'beta') {
67
+ return $t('settingsBeta', 'New Features')
61
68
  }
62
69
  return key
63
70
  }
@@ -90,37 +97,63 @@ const shouldRenderSetting = (
90
97
  const settingTypeOrder = ['checkbox', 'slider', 'method']
91
98
 
92
99
  const groups = computed<GroupedSettings[]>(() => {
93
- return Object.values(
94
- features.features.value.reduce<Record<string, GroupedSettings>>(
95
- (acc, feature) => {
96
- Object.entries(feature.settings || {}).forEach(
97
- ([settingsKey, setting]) => {
98
- const key: any = `feature:${feature.id}:${settingsKey}`
99
- if (shouldRenderSetting(key, setting)) {
100
- const group = setting.group || 'advanced'
101
- if (!acc[group]) {
102
- acc[group] = {
103
- id: group as any,
104
- key: group,
105
- label: getGroupLabel(group),
106
- icon: getGroupIcon(group),
107
- settings: [],
108
- }
109
- }
100
+ const settingGroups = features.features.value.reduce<
101
+ Partial<Record<SettingsGroup, GroupedSettings>>
102
+ >((acc, feature) => {
103
+ Object.entries(feature.settings || {}).forEach(([settingsKey, setting]) => {
104
+ const key: any = `feature:${feature.id}:${settingsKey}`
105
+ if (shouldRenderSetting(key, setting)) {
106
+ const group = setting.group || 'advanced'
107
+ if (!acc[group]) {
108
+ acc[group] = {
109
+ id: group as any,
110
+ key: group,
111
+ label: getGroupLabel(group),
112
+ icon: getGroupIcon(group),
113
+ settings: [],
114
+ }
115
+ }
110
116
 
111
- acc[group].settings.push({
112
- featureId: feature.id,
113
- settingsKey,
114
- setting,
115
- })
116
- }
117
- },
118
- )
119
- return acc
120
- },
121
- {},
122
- ),
123
- )
117
+ acc[group].settings.push({
118
+ featureId: feature.id,
119
+ settingsKey,
120
+ setting,
121
+ })
122
+ }
123
+ })
124
+ return acc
125
+ }, {})
126
+
127
+ if (features.betaFeatures.value.length) {
128
+ if (!settingGroups.beta) {
129
+ settingGroups.beta = {
130
+ id: 'beta',
131
+ key: 'betaFeatures',
132
+ label: getGroupLabel('beta'),
133
+ icon: 'bug',
134
+ settings: [],
135
+ }
136
+ }
137
+
138
+ features.betaFeatures.value.forEach((v) => {
139
+ const label = getTranslation(`feature_${v.id}_label`) || v.label
140
+ const description =
141
+ getTranslation(`feature_${v.id}_description`) || v.description
142
+ settingGroups.beta!.settings.push({
143
+ featureId: 'settings',
144
+ settingsKey: 'beta:' + v.id,
145
+ setting: {
146
+ type: 'checkbox',
147
+ default: false,
148
+ label,
149
+ description,
150
+ group: 'beta',
151
+ },
152
+ })
153
+ })
154
+ }
155
+
156
+ return Object.values(settingGroups)
124
157
  .map((group) => {
125
158
  group.settings
126
159
  .sort((a, b) => b.settingsKey.localeCompare(a.settingsKey))