@blokkli/editor 1.2.0 → 1.3.1

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 (63) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +1678 -41
  3. package/dist/runtime/adapter/drupal/graphql/base.graphql +14 -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 +26 -18
  7. package/dist/runtime/adapter/index.d.ts +6 -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/EditForm/Frame/index.vue +8 -1
  24. package/dist/runtime/components/Edit/Features/EditableField/index.vue +11 -5
  25. package/dist/runtime/components/Edit/Features/EntityTitle/index.vue +1 -0
  26. package/dist/runtime/components/Edit/Features/History/index.vue +3 -1
  27. package/dist/runtime/components/Edit/Features/ImportExisting/index.vue +5 -2
  28. package/dist/runtime/components/Edit/Features/Library/ReusableDialog/index.vue +10 -3
  29. package/dist/runtime/components/Edit/Features/MediaLibrary/Library/Item.vue +2 -0
  30. package/dist/runtime/components/Edit/Features/MultiSelect/index.vue +4 -1
  31. package/dist/runtime/components/Edit/Features/Options/Form/index.vue +14 -1
  32. package/dist/runtime/components/Edit/Features/Preview/index.vue +2 -1
  33. package/dist/runtime/components/Edit/Features/PreviewGrant/index.vue +2 -1
  34. package/dist/runtime/components/Edit/Features/Publish/index.vue +2 -0
  35. package/dist/runtime/components/Edit/Features/ResponsivePreview/index.vue +2 -1
  36. package/dist/runtime/components/Edit/Features/Selection/Overlay/fragment.glsl +78 -44
  37. package/dist/runtime/components/Edit/Features/Selection/Overlay/index.vue +8 -8
  38. package/dist/runtime/components/Edit/Features/Selection/Overlay/vertex.glsl +6 -1
  39. package/dist/runtime/components/Edit/Features/Settings/Dialog/FeatureSetting/index.vue +23 -2
  40. package/dist/runtime/components/Edit/Features/Settings/Dialog/index.vue +71 -38
  41. package/dist/runtime/components/Edit/Features/Settings/index.vue +4 -0
  42. package/dist/runtime/components/Edit/Features/Transform/index.vue +5 -1
  43. package/dist/runtime/components/Edit/Features/Translations/index.vue +24 -2
  44. package/dist/runtime/components/Edit/Features/index.vue +4 -0
  45. package/dist/runtime/components/Edit/InfoBox/index.vue +14 -0
  46. package/dist/runtime/components/Edit/Messages/index.vue +10 -12
  47. package/dist/runtime/components/Edit/PreviewProvider.vue +9 -2
  48. package/dist/runtime/components/Edit/index.d.ts +2 -1
  49. package/dist/runtime/components/Edit/index.js +3 -1
  50. package/dist/runtime/constants/index.d.ts +1 -1
  51. package/dist/runtime/constants/index.js +1 -0
  52. package/dist/runtime/css/output.css +1 -1
  53. package/dist/runtime/helpers/animationProvider.js +2 -1
  54. package/dist/runtime/helpers/featuresProvider.d.ts +7 -14
  55. package/dist/runtime/helpers/featuresProvider.js +29 -1
  56. package/dist/runtime/helpers/stateProvider.d.ts +1 -0
  57. package/dist/runtime/helpers/stateProvider.js +23 -4
  58. package/dist/runtime/helpers/uiProvider.d.ts +5 -1
  59. package/dist/runtime/helpers/uiProvider.js +10 -2
  60. package/dist/runtime/icons/diff.svg +1 -0
  61. package/dist/runtime/icons/info.svg +1 -0
  62. package/dist/runtime/types/index.d.ts +59 -1
  63. package/package.json +2 -1
@@ -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))
@@ -43,12 +43,16 @@ const { settings } = defineBlokkliFeature({
43
43
  type: 'checkbox',
44
44
  default: true,
45
45
  label: 'Use animations',
46
+ description:
47
+ 'Animates UI elements like dialogs or drawers or interactions like drag and drop or scroll changes.',
46
48
  group: 'advanced',
47
49
  },
48
50
  lowPerformanceMode: {
49
51
  type: 'checkbox',
50
52
  default: false,
51
53
  label: 'Enable low performance mode',
54
+ description:
55
+ 'Reduces the animations and interactivity to a minimum for devices with low performance.',
52
56
  group: 'advanced',
53
57
  },
54
58
  resetAllSettings: {
@@ -44,7 +44,7 @@ const { adapter } = defineBlokkliFeature({
44
44
  screenshot: 'feature-transform.jpg',
45
45
  })
46
46
 
47
- const { types, selection, state, $t, dom } = useBlokkli()
47
+ const { types, selection, state, $t, dom, ui } = useBlokkli()
48
48
 
49
49
  const {
50
50
  data: plugins,
@@ -64,6 +64,8 @@ watch(selection.uuids, async () => {
64
64
  })
65
65
 
66
66
  async function onTransform(plugin: TransformPlugin, uuids: string[]) {
67
+ ui.setTransform(plugin.label)
68
+
67
69
  await state.mutateWithLoadingState(
68
70
  () =>
69
71
  adapter.applyTransformPlugin({
@@ -75,6 +77,8 @@ async function onTransform(plugin: TransformPlugin, uuids: string[]) {
75
77
  'The action "@name" could not be executed.',
76
78
  ).replace('@name', plugin.label),
77
79
  )
80
+
81
+ ui.setTransform()
78
82
  }
79
83
 
80
84
  const itemBundleIds = computed(() =>
@@ -50,7 +50,7 @@
50
50
  </PluginTourItem>
51
51
  </Teleport>
52
52
 
53
- <Teleport to="body">
53
+ <Teleport to="#bk-banner-container">
54
54
  <Banner
55
55
  v-if="state.editMode.value === 'translating'"
56
56
  :active-language="activeLanguage"
@@ -116,7 +116,20 @@ const { translation, editMode } = state
116
116
 
117
117
  const isOpen = ref(false)
118
118
 
119
- const isDropdown = computed(() => ui.isMobile.value || items.value.length > 5)
119
+ const isDropdown = computed(() => {
120
+ // Always a dropdown on mobile.
121
+ if (ui.isMobile.value) {
122
+ return true
123
+ }
124
+
125
+ // It is a dropdown if all langcodes combined is greater than 15.
126
+ // That way up to 7 languages with 2-char langcodes are displayed as radio buttons.
127
+ // This handles cases where langcodes are e.g. 'en-US', 'en-GB', 'de-CH', etc.
128
+ // In this case it switches to a dropdown. This is better than relying on the number
129
+ // languages.
130
+ const allCodes = items.value.map((v) => v.code).join('')
131
+ return allCodes.length > 15
132
+ })
120
133
  const activeLangcode = computed(() => context.value.language)
121
134
  const activeLanguage = computed<Language>(() => {
122
135
  return (
@@ -212,6 +225,15 @@ onBlokkliEvent('item:doubleClick', function (block) {
212
225
  }
213
226
  })
214
227
 
228
+ onBlokkliEvent('entity:translated', (langcode) => {
229
+ const targetTranslation = translation.value.translations?.find(
230
+ (v) => v.id === langcode,
231
+ )
232
+ if (targetTranslation) {
233
+ adapter.changeLanguage(targetTranslation)
234
+ }
235
+ })
236
+
215
237
  onMounted(() => {
216
238
  // Make sure the user is not trying to edit a translation that does not exist.
217
239
  const translationExists = !!translation.value.translations?.find(
@@ -48,6 +48,10 @@ const availableFeatures = computed(() => {
48
48
  return false
49
49
  }
50
50
 
51
+ if (v.beta && !features.enabledBetaFeatures.value.includes(v.id)) {
52
+ return false
53
+ }
54
+
51
55
  return !v.viewports.length || v.viewports.includes(ui.appViewport.value)
52
56
  })
53
57
  })
@@ -0,0 +1,14 @@
1
+ <template>
2
+ <div class="bk-info-box">
3
+ <Icon name="info" />
4
+ <p v-html="text" />
5
+ </div>
6
+ </template>
7
+
8
+ <script setup lang="ts">
9
+ import { Icon } from '#blokkli/components'
10
+
11
+ defineProps<{
12
+ text: string
13
+ }>()
14
+ </script>
@@ -1,16 +1,14 @@
1
1
  <template>
2
- <Teleport to="body">
3
- <div class="bk bk-messages">
4
- <TransitionGroup name="bk-message">
5
- <Item
6
- v-for="(message, index) in messages"
7
- v-bind="message"
8
- :key="index"
9
- @close="removeMessage(index)"
10
- />
11
- </TransitionGroup>
12
- </div>
13
- </Teleport>
2
+ <div class="bk bk-messages">
3
+ <TransitionGroup name="bk-message">
4
+ <Item
5
+ v-for="(message, index) in messages"
6
+ v-bind="message"
7
+ :key="index"
8
+ @close="removeMessage(index)"
9
+ />
10
+ </TransitionGroup>
11
+ </div>
14
12
  </template>
15
13
 
16
14
  <script lang="ts" setup>
@@ -62,7 +62,7 @@ const mutatedEntity = computed(
62
62
  () => mutatedEntityFromState.value || props.entity,
63
63
  )
64
64
 
65
- const { data, refresh } = await useAsyncData(() =>
65
+ const { data, refresh, error } = await useAsyncData(() =>
66
66
  adapter.loadState().then((v) => adapter.mapState(v)),
67
67
  )
68
68
 
@@ -94,8 +94,10 @@ const updateState = () => {
94
94
 
95
95
  updateState()
96
96
 
97
+ const isPreview = computed(() => !error.value)
98
+
97
99
  provide(INJECT_MUTATED_FIELDS_MAP, mutatedFieldsMap)
98
- provide(INJECT_IS_PREVIEW, true)
100
+ provide(INJECT_IS_PREVIEW, isPreview)
99
101
  provide<ItemEditContext>(INJECT_EDIT_CONTEXT, {
100
102
  mutatedOptions,
101
103
  eventBus,
@@ -127,6 +129,11 @@ const onMouseDown = () => {
127
129
  * update.
128
130
  */
129
131
  function checkChangedDate() {
132
+ // State loading has failed, so we can return here; no need to poll.
133
+ if (!isPreview.value) {
134
+ return
135
+ }
136
+
130
137
  clearTimeout(timeout)
131
138
 
132
139
  const delay = adapter.getLastChanged ? 1000 : 5000
@@ -12,4 +12,5 @@ import Loading from './Loading/index.vue.js';
12
12
  import Highlight from './Highlight/index.vue.js';
13
13
  import ViewportBlockingRect from './ViewportBlockingRect/index.vue.js';
14
14
  import ScrollBoundary from './ScrollBoundary/index.vue.js';
15
- export { ItemIcon, ShortcutIndicator, RelativeTime, Icon, Resizable, DialogModal, ScaleToFit, Sortli, FormOverlay, AddListItem, Loading, Highlight, ViewportBlockingRect, ScrollBoundary, };
15
+ import InfoBox from './InfoBox/index.vue.js';
16
+ export { ItemIcon, ShortcutIndicator, RelativeTime, Icon, Resizable, DialogModal, ScaleToFit, Sortli, FormOverlay, AddListItem, Loading, Highlight, ViewportBlockingRect, ScrollBoundary, InfoBox, };
@@ -12,6 +12,7 @@ import Loading from "./Loading/index.vue";
12
12
  import Highlight from "./Highlight/index.vue";
13
13
  import ViewportBlockingRect from "./ViewportBlockingRect/index.vue";
14
14
  import ScrollBoundary from "./ScrollBoundary/index.vue";
15
+ import InfoBox from "./InfoBox/index.vue";
15
16
  export {
16
17
  ItemIcon,
17
18
  ShortcutIndicator,
@@ -26,5 +27,6 @@ export {
26
27
  Loading,
27
28
  Highlight,
28
29
  ViewportBlockingRect,
29
- ScrollBoundary
30
+ ScrollBoundary,
31
+ InfoBox
30
32
  };
@@ -1,4 +1,4 @@
1
- export declare const SETTINGS_GROUP: readonly ["appearance", "artboard", "behavior", "advanced"];
1
+ export declare const SETTINGS_GROUP: readonly ["appearance", "artboard", "behavior", "beta", "advanced"];
2
2
  export declare const VIEWPORT: readonly ["mobile", "desktop"];
3
3
  export type SettingsGroup = (typeof SETTINGS_GROUP)[number];
4
4
  export type Viewport = (typeof VIEWPORT)[number];
@@ -2,6 +2,7 @@ export const SETTINGS_GROUP = [
2
2
  "appearance",
3
3
  "artboard",
4
4
  "behavior",
5
+ "beta",
5
6
  "advanced"
6
7
  ];
7
8
  export const VIEWPORT = ["mobile", "desktop"];