@raclettejs/workbench 0.1.19 → 0.1.21

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 (28) hide show
  1. package/.raclette/backend/raclette.config.js +519 -0
  2. package/{yarn.lock → .raclette/backend/yarn.lock} +1014 -796
  3. package/.raclette/frontend/raclette.config.js +47 -0
  4. package/.raclette/frontend/yarn.lock +3428 -0
  5. package/.raclette/raclette.config.js +643 -0
  6. package/.raclette/virtual/backend/README.md +172 -0
  7. package/.raclette/virtual/backend/raclette.config.js +519 -0
  8. package/.raclette/virtual/backend/yarn.lock +3773 -0
  9. package/.raclette/virtual/frontend/README.md +511 -0
  10. package/.raclette/virtual/frontend/raclette.config.js +47 -0
  11. package/.raclette/virtual/frontend/src/core/lib/eggs/readme.md +75 -0
  12. package/.raclette/virtual/frontend/yarn.lock +3428 -0
  13. package/CHANGELOG.md +7 -0
  14. package/i18n/de-DE.json +17 -1
  15. package/i18n/en-EU.json +17 -1
  16. package/i18n/sk-SK.json +17 -1
  17. package/package.json +4 -2
  18. package/packages.json +3 -1
  19. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/CompositionWidgetsLayout.vue +8 -3
  20. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/widgetsLayout/WidgetContextMenu.vue +60 -0
  21. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/widgetsLayout/WidgetIcon.vue +4 -7
  22. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/widgetsLayout/WidgetList.vue +34 -117
  23. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/widgetsLayout/WidgetListFlat.vue +20 -0
  24. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/widgetsLayout/WidgetListGrouped.vue +50 -0
  25. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/widgetsLayout/WidgetListHeader.vue +213 -0
  26. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/widgetsLayout/WidgetListItem.vue +56 -0
  27. package/plugins/pacifico__compositions/frontend/composables/useWidgetList.ts +288 -0
  28. package/raclette.config.js +4 -1
@@ -0,0 +1,213 @@
1
+ <template>
2
+ <div
3
+ class="tw:sticky tw:top-0 tw:z-10 tw:bg-[rgb(var(--v-theme-surface))] tw:pt-2 tw:pb-2 tw:backdrop-blur-sm"
4
+ >
5
+ <div
6
+ class="tw:flex tw:items-center tw:gap-2 tw:mb-2 tw:px-1 tw:rounded-xl tw:border tw:border-slate-500/60 tw:bg-slate-50/80 tw:dark:bg-slate-900/60"
7
+ >
8
+ <SimpleSearchInput
9
+ :model-value="searchQuery"
10
+ variant="solo"
11
+ class="tw:flex-1"
12
+ :clearable="false"
13
+ :label="
14
+ $t(
15
+ 'workbench.compositionConfiguration.widgetLayout.widgetList.searchLabel',
16
+ )
17
+ "
18
+ @update:model-value="onUpdateSearchQuery"
19
+ />
20
+
21
+ <v-menu :max-width="380" :close-on-content-click="false">
22
+ <template #activator="{ props }">
23
+ <v-btn
24
+ v-bind="props"
25
+ icon
26
+ variant="tonal"
27
+ density="comfortable"
28
+ class="tw:ml-auto tw:border tw:border-slate-200/80 tw:bg-slate-100/80 hover:tw:bg-slate-200/80 tw:dark:bg-slate-800/80 dark:hover:tw:bg-slate-700/80"
29
+ :aria-label="
30
+ $t(
31
+ 'workbench.compositionConfiguration.widgetLayout.widgetList.menuAriaLabel',
32
+ )
33
+ "
34
+ >
35
+ <v-icon icon="mdi-tune-variant" />
36
+ </v-btn>
37
+ </template>
38
+
39
+ <v-card
40
+ class="tw:min-w-[260px] tw:rounded-xl tw:shadow-lg tw:border tw:border-slate-200/80"
41
+ >
42
+ <v-card-title class="tw:py-2.5 tw:px-3">
43
+ <div
44
+ class="tw:flex tw:items-center tw:justify-between tw:gap-2 tw:text-sm tw:font-medium"
45
+ >
46
+ <span>
47
+ {{
48
+ $t(
49
+ "workbench.compositionConfiguration.widgetLayout.widgetList.menuTitle",
50
+ )
51
+ }}
52
+ </span>
53
+ <span
54
+ class="tw:inline-flex tw:items-center tw:rounded-full tw:bg-slate-100/90 tw:px-2 tw:py-[2px] tw:text-[11px] tw:font-medium tw:uppercase tw:tracking-wide tw:text-slate-500 tw:dark:bg-slate-800/80"
55
+ >
56
+ {{ visibleWidgetsCount }}/{{ widgetsCount }}
57
+ </span>
58
+ </div>
59
+ </v-card-title>
60
+ <v-divider />
61
+
62
+ <v-card-text class="tw:!py-2.5 tw:!px-3 tw:space-y-4">
63
+ <div
64
+ class="tw:rounded-lg tw:bg-slate-50/80 tw:dark:bg-slate-900/40 tw:p-2.5"
65
+ >
66
+ <div
67
+ class="tw:flex tw:items-center tw:justify-between tw:gap-2 tw:mb-2"
68
+ >
69
+ <div
70
+ class="tw:text-[11px] tw:font-semibold tw:uppercase tw:tracking-wide tw:text-slate-500"
71
+ >
72
+ {{
73
+ $t(
74
+ "workbench.compositionConfiguration.widgetLayout.widgetList.sortingTitle",
75
+ )
76
+ }}
77
+ </div>
78
+ </div>
79
+ <v-chip-group
80
+ :model-value="sortMode"
81
+ column
82
+ class="tw:flex tw:flex-wrap tw:gap-1 tw:max-w-[320px]"
83
+ @update:model-value="onUpdateSortMode"
84
+ mandatory
85
+ >
86
+ <v-chip
87
+ v-for="option in sortOptions"
88
+ :key="option.value"
89
+ :value="option.value"
90
+ size="small"
91
+ variant="outlined"
92
+ class="tw:text-xs tw:font-medium tw:rounded-full tw:border-slate-200/80 tw:bg-white/90 tw:dark:bg-slate-900/60"
93
+ color="primary"
94
+ >
95
+ {{ $t(option.labelKey) }}
96
+ </v-chip>
97
+ </v-chip-group>
98
+ </div>
99
+
100
+ <div
101
+ class="tw:rounded-lg tw:bg-slate-50/80 tw:dark:bg-slate-900/40 tw:px-2.5 tw:py-2.5"
102
+ >
103
+ <div
104
+ class="tw:flex tw:items-center tw:justify-between tw:gap-2 tw:mb-2"
105
+ >
106
+ <div
107
+ class="tw:text-[11px] tw:font-semibold tw:uppercase tw:tracking-wide tw:text-slate-500"
108
+ >
109
+ {{
110
+ $t(
111
+ "workbench.compositionConfiguration.widgetLayout.widgetList.pluginFilterTitle",
112
+ )
113
+ }}
114
+ </div>
115
+ <div class="tw:flex tw:items-center tw:gap-2">
116
+ <span
117
+ class="tw:inline-flex tw:items-center tw:rounded-full tw:bg-slate-100/90 tw:px-2 tw:py-[2px] tw:text-[11px] tw:font-medium tw:uppercase tw:tracking-wide tw:text-slate-500 tw:dark:bg-slate-800/80"
118
+ >
119
+ {{ visiblePluginKeysModel.length }}/{{
120
+ pluginFilterOptions.length
121
+ }}
122
+ </span>
123
+ </div>
124
+ </div>
125
+
126
+ <div class="tw:max-h-64 tw:overflow-y-auto tw:pr-1">
127
+ <v-chip-group
128
+ v-model="visiblePluginKeysModel"
129
+ multiple
130
+ column
131
+ class="tw:flex tw:flex-wrap tw:gap-1"
132
+ >
133
+ <v-chip
134
+ v-for="plugin in pluginFilterOptions"
135
+ :key="plugin.key"
136
+ :value="plugin.key"
137
+ size="small"
138
+ variant="outlined"
139
+ filter
140
+ color="primary"
141
+ class="tw:text-[11px] tw:font-medium tw:rounded-full tw:border-slate-200/80 tw:bg-white/90 tw:dark:bg-slate-900/60"
142
+ >
143
+ {{ plugin.label }}
144
+ </v-chip>
145
+ </v-chip-group>
146
+ </div>
147
+ </div>
148
+ </v-card-text>
149
+ </v-card>
150
+ </v-menu>
151
+ </div>
152
+
153
+ <div
154
+ v-if="widgetsCount > 0"
155
+ class="tw:flex tw:items-center tw:justify-between tw:px-1.5 tw:pt-0.5 tw:text-[11px] tw:text-slate-500"
156
+ >
157
+ <span>
158
+ <template v-if="visibleWidgetsCount === widgetsCount">
159
+ {{
160
+ $t(
161
+ "workbench.compositionConfiguration.widgetLayout.widgetList.countAll",
162
+ { count: widgetsCount },
163
+ )
164
+ }}
165
+ </template>
166
+ <template v-else>
167
+ {{
168
+ $t(
169
+ "workbench.compositionConfiguration.widgetLayout.widgetList.countFiltered",
170
+ { visible: visibleWidgetsCount, total: widgetsCount },
171
+ )
172
+ }}
173
+ </template>
174
+ </span>
175
+ </div>
176
+ </div>
177
+ </template>
178
+
179
+ <script setup lang="ts">
180
+ import { computed } from "vue"
181
+ import SimpleSearchInput from "@raclettejs/core/orchestrator/components/input/SimpleSearchInput.vue"
182
+ import type { SortMode, SortOption } from "../../../composables/useWidgetList"
183
+
184
+ const props = defineProps<{
185
+ searchQuery: string
186
+ sortMode: SortMode
187
+ sortOptions: SortOption[]
188
+ pluginFilterOptions: { key: string; label: string }[]
189
+ visiblePluginKeys: string[]
190
+ widgetsCount: number
191
+ visibleWidgetsCount: number
192
+ }>()
193
+
194
+ const emit = defineEmits<{
195
+ (e: "update:searchQuery", value: string): void
196
+ (e: "update:sortMode", value: SortMode): void
197
+ (e: "update:visiblePluginKeys", value: string[]): void
198
+ }>()
199
+
200
+ const visiblePluginKeysModel = computed({
201
+ get: () => props.visiblePluginKeys,
202
+ set: (value: string[]) => emit("update:visiblePluginKeys", value),
203
+ })
204
+
205
+ const onUpdateSearchQuery = (value: string) => {
206
+ emit("update:searchQuery", value)
207
+ }
208
+
209
+ const onUpdateSortMode = (value: SortMode | null) => {
210
+ if (value === null) return
211
+ emit("update:sortMode", value)
212
+ }
213
+ </script>
@@ -0,0 +1,56 @@
1
+ <template>
2
+ <DraggableElement
3
+ :class="outerClass"
4
+ :draggable-config="{
5
+ type: 'widget',
6
+ widgetName: widget.name,
7
+ pluginKey: widget.pluginKey,
8
+ }"
9
+ >
10
+ <div
11
+ class="tw:flex tw:items-center tw:gap-3 tw:px-3 tw:py-2 tw:rounded-lg"
12
+ tabindex="0"
13
+ >
14
+ <div class="tw:flex tw:items-center tw:gap-3 tw:min-w-0">
15
+ <div
16
+ class="tw:flex-shrink-0 tw:w-8 tw:h-8 tw:flex tw:items-center tw:justify-center"
17
+ >
18
+ <WidgetIcon :module="widget" />
19
+ </div>
20
+ <div class="tw:min-w-0">
21
+ <div
22
+ class="tw:text-sm tw:font-medium tw:whitespace-nowrap tw:truncate"
23
+ >
24
+ {{ widget.title }}
25
+ </div>
26
+ <div
27
+ class="tw:text-xs tw:text-slate-500 tw:whitespace-nowrap tw:truncate"
28
+ >
29
+ {{ pluginLabel }} ·
30
+ {{ widget.author }}
31
+ </div>
32
+ </div>
33
+ </div>
34
+ </div>
35
+ </DraggableElement>
36
+ </template>
37
+
38
+ <script setup lang="ts">
39
+ import { computed } from "vue"
40
+ import { RegisteredWidgetDeclaration } from "@raclettejs/core"
41
+ import DraggableElement from "@raclettejs/core/orchestrator/components/dragAndDrop/DraggableElement.vue"
42
+ import WidgetIcon from "./WidgetIcon.vue"
43
+ import { deslugify } from "@shared/helper/pluginHelper"
44
+
45
+ const props = defineProps<{
46
+ widget: RegisteredWidgetDeclaration
47
+ outerClass?: string
48
+ }>()
49
+
50
+ const pluginLabel = computed(() => {
51
+ const parts = props.widget.pluginKey.split("__")
52
+ const slug = parts[1] ?? parts[0]
53
+ return deslugify(slug)
54
+ })
55
+ </script>
56
+
@@ -0,0 +1,288 @@
1
+ import { computed, ref, watch } from "vue"
2
+ import { useFuse } from "@vueuse/integrations/useFuse"
3
+ import useWidgets from "@raclettejs/core/orchestrator/composables/useWidgets"
4
+ import type { RegisteredWidgetDeclaration } from "@raclettejs/core"
5
+ import { deslugify } from "@shared/helper/pluginHelper"
6
+
7
+ export type SortMode = "name" | "author" | "plugin" | "group"
8
+
9
+ export type SortOption = { labelKey: string; value: SortMode }
10
+
11
+ export type GroupedWidgets = Record<
12
+ string,
13
+ Record<string, RegisteredWidgetDeclaration[]>
14
+ >
15
+
16
+ const sortOptions: SortOption[] = [
17
+ {
18
+ labelKey:
19
+ "workbench.compositionConfiguration.widgetLayout.widgetList.sortOptions.group",
20
+ value: "group",
21
+ },
22
+ {
23
+ labelKey:
24
+ "workbench.compositionConfiguration.widgetLayout.widgetList.sortOptions.name",
25
+ value: "name",
26
+ },
27
+ {
28
+ labelKey:
29
+ "workbench.compositionConfiguration.widgetLayout.widgetList.sortOptions.author",
30
+ value: "author",
31
+ },
32
+ ]
33
+
34
+ export function useWidgetList() {
35
+ const { widgets } = useWidgets()
36
+
37
+ const searchQuery = ref("")
38
+ const sortMode = ref<SortMode>("group")
39
+ const visiblePluginKeys = ref<string[]>([])
40
+
41
+ const pluginFilterOptions = computed(() => {
42
+ const map = new Map<string, { key: string; label: string }>()
43
+
44
+ widgets.value.forEach((widget) => {
45
+ if (!map.has(widget.pluginKey)) {
46
+ const parts = widget.pluginKey.split("__")
47
+ const slug = parts[1] ?? parts[0]
48
+ map.set(widget.pluginKey, {
49
+ key: widget.pluginKey,
50
+ label: deslugify(slug),
51
+ })
52
+ }
53
+ })
54
+
55
+ return Array.from(map.values()).sort((a, b) =>
56
+ a.label.localeCompare(b.label),
57
+ )
58
+ })
59
+
60
+ const widgetsCount = computed(() => widgets.value.length)
61
+
62
+ const { results: fuseResults } = useFuse<RegisteredWidgetDeclaration>(
63
+ searchQuery,
64
+ widgets,
65
+ {
66
+ fuseOptions: {
67
+ keys: ["title", "description", "author", "pluginKey", "name"],
68
+ includeScore: true,
69
+ threshold: 0.4,
70
+ },
71
+ matchAllWhenSearchEmpty: true,
72
+ },
73
+ )
74
+
75
+ const visibleWidgetsCount = computed(() => {
76
+ const query = (searchQuery.value ?? "").trim()
77
+
78
+ const baseList = query
79
+ ? fuseResults.value.map(
80
+ (result: { item: RegisteredWidgetDeclaration }) => result.item,
81
+ )
82
+ : widgets.value
83
+
84
+ const allowedPlugins = new Set(visiblePluginKeys.value)
85
+
86
+ const filteredByPlugin =
87
+ allowedPlugins.size === 0
88
+ ? baseList
89
+ : baseList.filter((widget) => allowedPlugins.has(widget.pluginKey))
90
+
91
+ return filteredByPlugin.length
92
+ })
93
+
94
+ watch(
95
+ () => widgets.value,
96
+ (current) => {
97
+ if (!current.length) return
98
+ if (visiblePluginKeys.value.length) return
99
+
100
+ visiblePluginKeys.value = Array.from(
101
+ new Set(current.map((w) => w.pluginKey)),
102
+ )
103
+ },
104
+ { immediate: true },
105
+ )
106
+
107
+ const groupedWidgets = computed<GroupedWidgets>(() => {
108
+ const unsortedResult: GroupedWidgets = {}
109
+
110
+ const allowedPlugins = new Set(visiblePluginKeys.value)
111
+
112
+ widgets.value.forEach((widget) => {
113
+ if (allowedPlugins.size && !allowedPlugins.has(widget.pluginKey)) {
114
+ return
115
+ }
116
+
117
+ if (!unsortedResult[widget.author]) {
118
+ unsortedResult[widget.author] = {}
119
+ }
120
+
121
+ if (!unsortedResult[widget.author][widget.pluginKey]) {
122
+ unsortedResult[widget.author][widget.pluginKey] = []
123
+ }
124
+
125
+ unsortedResult[widget.author][widget.pluginKey].push(widget)
126
+ })
127
+
128
+ const sortedAuthors = Object.keys(unsortedResult).sort((a, b) => {
129
+ if (a === "pacifico") {
130
+ return 1
131
+ }
132
+ if (b === "pacifico") {
133
+ return -1
134
+ }
135
+
136
+ return a.localeCompare(b)
137
+ })
138
+
139
+ const sortedResult: GroupedWidgets = {}
140
+
141
+ sortedAuthors.forEach((author) => {
142
+ const plugins = Object.keys(unsortedResult[author]).sort((a, b) => {
143
+ if (a === "core") {
144
+ return 1
145
+ }
146
+ if (b === "core") {
147
+ return -1
148
+ }
149
+
150
+ return a.localeCompare(b)
151
+ })
152
+
153
+ sortedResult[author] = {}
154
+ plugins.forEach((pluginKey) => {
155
+ sortedResult[author][pluginKey] = unsortedResult[author][pluginKey]
156
+ })
157
+ })
158
+
159
+ return sortedResult
160
+ })
161
+
162
+ const sortWidgets = (
163
+ list: RegisteredWidgetDeclaration[],
164
+ mode: SortMode,
165
+ ): RegisteredWidgetDeclaration[] => {
166
+ const copy = [...list]
167
+
168
+ switch (mode) {
169
+ case "name":
170
+ copy.sort((a, b) => a.title.localeCompare(b.title))
171
+ break
172
+ case "author":
173
+ copy.sort((a, b) => a.author.localeCompare(b.author))
174
+ break
175
+ default:
176
+ break
177
+ }
178
+
179
+ return copy
180
+ }
181
+
182
+ const filteredWidgetsFlat = computed<RegisteredWidgetDeclaration[]>(() => {
183
+ const query = (searchQuery.value ?? "").trim()
184
+
185
+ const allowedPlugins = new Set(visiblePluginKeys.value)
186
+
187
+ if (query) {
188
+ const fuseOrdered = fuseResults.value
189
+ .map((result: { item: RegisteredWidgetDeclaration }) => result.item)
190
+ .filter(
191
+ (widget: RegisteredWidgetDeclaration) =>
192
+ allowedPlugins.size === 0 || allowedPlugins.has(widget.pluginKey),
193
+ )
194
+
195
+ return fuseOrdered
196
+ }
197
+
198
+ const baseList = widgets.value
199
+
200
+ const filteredByPlugin =
201
+ allowedPlugins.size === 0
202
+ ? baseList
203
+ : baseList.filter((widget) => allowedPlugins.has(widget.pluginKey))
204
+
205
+ if (sortMode.value === "group") {
206
+ return filteredByPlugin
207
+ }
208
+
209
+ return sortWidgets(filteredByPlugin, sortMode.value)
210
+ })
211
+
212
+ const filteredGroupedWidgets = computed<GroupedWidgets>(() => {
213
+ const query = (searchQuery.value ?? "").trim()
214
+
215
+ if (!query) {
216
+ return groupedWidgets.value
217
+ }
218
+
219
+ const allowedPlugins = new Set(visiblePluginKeys.value)
220
+
221
+ const matchedWidgets = fuseResults.value
222
+ .map((result: { item: RegisteredWidgetDeclaration }) => result.item)
223
+ .filter(
224
+ (widget: RegisteredWidgetDeclaration) =>
225
+ allowedPlugins.size === 0 || allowedPlugins.has(widget.pluginKey),
226
+ )
227
+
228
+ const unsortedResult: GroupedWidgets = {}
229
+
230
+ matchedWidgets.forEach((widget: RegisteredWidgetDeclaration) => {
231
+ if (!unsortedResult[widget.author]) {
232
+ unsortedResult[widget.author] = {}
233
+ }
234
+
235
+ if (!unsortedResult[widget.author][widget.pluginKey]) {
236
+ unsortedResult[widget.author][widget.pluginKey] = []
237
+ }
238
+
239
+ unsortedResult[widget.author][widget.pluginKey].push(widget)
240
+ })
241
+
242
+ const sortedAuthors = Object.keys(unsortedResult).sort((a, b) => {
243
+ if (a === "pacifico") {
244
+ return 1
245
+ }
246
+ if (b === "pacifico") {
247
+ return -1
248
+ }
249
+
250
+ return a.localeCompare(b)
251
+ })
252
+
253
+ const sortedResult: GroupedWidgets = {}
254
+
255
+ sortedAuthors.forEach((author) => {
256
+ const plugins = Object.keys(unsortedResult[author]).sort((a, b) => {
257
+ if (a === "core") {
258
+ return 1
259
+ }
260
+ if (b === "core") {
261
+ return -1
262
+ }
263
+
264
+ return a.localeCompare(b)
265
+ })
266
+
267
+ sortedResult[author] = {}
268
+ plugins.forEach((pluginKey) => {
269
+ sortedResult[author][pluginKey] = unsortedResult[author][pluginKey]
270
+ })
271
+ })
272
+
273
+ return sortedResult
274
+ })
275
+
276
+ return {
277
+ widgets,
278
+ searchQuery,
279
+ sortMode,
280
+ visiblePluginKeys,
281
+ sortOptions,
282
+ pluginFilterOptions,
283
+ widgetsCount,
284
+ visibleWidgetsCount,
285
+ filteredWidgetsFlat,
286
+ filteredGroupedWidgets,
287
+ }
288
+ }
@@ -4,7 +4,7 @@ import interactionLinks from "./config/interactionLinks.js"
4
4
 
5
5
  export default defineRacletteConfig({
6
6
  name: "raclette-workbench",
7
-
7
+ root: ".raclette",
8
8
  // Configure services
9
9
  services: {
10
10
  frontend: {
@@ -144,4 +144,7 @@ export default defineRacletteConfig({
144
144
  eslint: {
145
145
  useRecommended: true,
146
146
  },
147
+ global: {
148
+ requiresAuthentication: true
149
+ }
147
150
  })