@raclettejs/workbench 0.0.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 (89) hide show
  1. package/README.md +1 -0
  2. package/config/compositions.js +282 -0
  3. package/config/interactionLinks.js +164 -0
  4. package/i18n/de-DE.json +245 -0
  5. package/i18n/en-EU.json +245 -0
  6. package/i18n/sk-SK.json +245 -0
  7. package/package.json +63 -0
  8. package/packages.json +33 -0
  9. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/CompositionConfiguration.vue +140 -0
  10. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/CompositionIntegration.vue +3 -0
  11. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/CompositionWidgetsLayout.vue +129 -0
  12. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/widgetsLayout/WidgetContextMenu.vue +53 -0
  13. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/widgetsLayout/WidgetIcon.vue +99 -0
  14. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/widgetsLayout/WidgetList.vue +122 -0
  15. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/widgetsLayout/WidgetRenderer.vue +65 -0
  16. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/widgetsLayout/WidgetSettings.vue +96 -0
  17. package/plugins/pacifico__compositions/frontend/widgets/CompositionCreateWidget.vue +51 -0
  18. package/plugins/pacifico__compositions/frontend/widgets/CompositionEditWidget.vue +66 -0
  19. package/plugins/pacifico__compositions/frontend/widgets/CompositionListWidget.vue +132 -0
  20. package/plugins/pacifico__compositions/raclette.plugin.ts +8 -0
  21. package/plugins/pacifico__core/backend/index.ts +25 -0
  22. package/plugins/pacifico__core/backend/routes.ts +220 -0
  23. package/plugins/pacifico__core/raclette.plugin.ts +9 -0
  24. package/plugins/pacifico__interactionLinks/frontend/components/InteractionLinkConfiguration.vue +145 -0
  25. package/plugins/pacifico__interactionLinks/frontend/widgets/InteractionLinkCreateWidget.vue +48 -0
  26. package/plugins/pacifico__interactionLinks/frontend/widgets/InteractionLinkEditWidget.vue +66 -0
  27. package/plugins/pacifico__interactionLinks/frontend/widgets/InteractionLinkListWidget.vue +126 -0
  28. package/plugins/pacifico__interactionLinks/raclette.plugin.ts +8 -0
  29. package/plugins/pacifico__plugins/frontend/index.ts +42 -0
  30. package/plugins/pacifico__plugins/frontend/widgets/PluginDetailWidget.vue +26 -0
  31. package/plugins/pacifico__plugins/frontend/widgets/PluginListWidget.vue +59 -0
  32. package/plugins/pacifico__plugins/raclette.plugin.ts +8 -0
  33. package/plugins/pacifico__tags/frontend/components/TagConfiguration.vue +99 -0
  34. package/plugins/pacifico__tags/frontend/widgets/TagCreateWidget.vue +43 -0
  35. package/plugins/pacifico__tags/frontend/widgets/TagEditWidget.vue +64 -0
  36. package/plugins/pacifico__tags/frontend/widgets/TagListWidget.vue +71 -0
  37. package/plugins/pacifico__tags/raclette.plugin.ts +8 -0
  38. package/plugins/pacifico__users/frontend/components/UserConfiguration.vue +110 -0
  39. package/plugins/pacifico__users/frontend/widgets/UserCreateWidget.vue +48 -0
  40. package/plugins/pacifico__users/frontend/widgets/UserEditWidget.vue +64 -0
  41. package/plugins/pacifico__users/frontend/widgets/UserListWidget.vue +86 -0
  42. package/plugins/pacifico__users/raclette.plugin.ts +8 -0
  43. package/raclette.config.js +150 -0
  44. package/services/frontend/.DS_Store +0 -0
  45. package/services/frontend/public/favicon.png +0 -0
  46. package/services/frontend/public/home-icon.png +0 -0
  47. package/services/frontend/public/logo.png +0 -0
  48. package/services/frontend/src/app/.DS_Store +0 -0
  49. package/services/frontend/src/app/components/BaseDataTable.vue +284 -0
  50. package/services/frontend/src/app/components/dynamicForm/DynamicForm.vue +207 -0
  51. package/services/frontend/src/app/components/dynamicForm/DynamicFormTypes.ts +63 -0
  52. package/services/frontend/src/app/components/dynamicForm/DynamicInput.vue +344 -0
  53. package/services/frontend/src/app/components/dynamicForm/TextDivider.vue +13 -0
  54. package/services/frontend/src/app/components/dynamicForm/testing/DynamicFormInputTesting.vue +477 -0
  55. package/services/frontend/src/app/components/dynamicForm/typedInputs/CheckboxInput.vue +16 -0
  56. package/services/frontend/src/app/components/dynamicForm/typedInputs/ColorInput.vue +52 -0
  57. package/services/frontend/src/app/components/dynamicForm/typedInputs/CompositionInput.vue +32 -0
  58. package/services/frontend/src/app/components/dynamicForm/typedInputs/IconInput.vue +40 -0
  59. package/services/frontend/src/app/components/dynamicForm/typedInputs/ListInput.vue +51 -0
  60. package/services/frontend/src/app/components/dynamicForm/typedInputs/LocalizedInput.vue +51 -0
  61. package/services/frontend/src/app/components/dynamicForm/typedInputs/PasswordInput.vue +23 -0
  62. package/services/frontend/src/app/components/dynamicForm/typedInputs/RecordInput.vue +91 -0
  63. package/services/frontend/src/app/components/dynamicForm/typedInputs/SwitchInput.vue +17 -0
  64. package/services/frontend/src/app/components/dynamicForm/typedInputs/TagsInput.vue +58 -0
  65. package/services/frontend/src/app/components/dynamicForm/typedInputs/TriggerInput.vue +159 -0
  66. package/services/frontend/src/app/components/dynamicForm/typedInputs/UserInput.vue +63 -0
  67. package/services/frontend/src/app/components/dynamicForm/typedInputs/mdi-icons.ts +7002 -0
  68. package/services/frontend/src/app/components/dynamicForm/validators.ts +19 -0
  69. package/services/frontend/src/app/components/stepNavigator/StepNavigator.vue +331 -0
  70. package/services/frontend/src/app/components/stepNavigator/StepNavigatorTypes.ts +12 -0
  71. package/services/frontend/src/app/composables/useUserHelper.ts +254 -0
  72. package/services/frontend/src/app/composables/useVantaFog.ts +37 -0
  73. package/services/frontend/src/app/composables/useWorkbenchPlugins.ts +44 -0
  74. package/services/frontend/src/orchestrator/.DS_Store +0 -0
  75. package/services/frontend/src/orchestrator/LoginApp.vue +62 -0
  76. package/services/frontend/src/orchestrator/WelcomeScreen.vue +39 -0
  77. package/services/frontend/src/orchestrator/assets/styles/themes/dark.ts +39 -0
  78. package/services/frontend/src/orchestrator/assets/styles/themes/highContrast.ts +39 -0
  79. package/services/frontend/src/orchestrator/assets/styles/themes/light.ts +39 -0
  80. package/services/frontend/src/orchestrator/components/InitProjectScreen.vue +298 -0
  81. package/services/frontend/src/orchestrator/components/LoginScreen.vue +73 -0
  82. package/services/frontend/src/orchestrator/components/RacletteAdCorner.vue +313 -0
  83. package/services/frontend/src/orchestrator/components/layout/AppButton.vue +109 -0
  84. package/services/frontend/src/orchestrator/components/layout/AppHeadline.vue +46 -0
  85. package/services/frontend/src/orchestrator/components/layout/AppLabelledInput.vue +94 -0
  86. package/services/frontend/src/orchestrator/components/layout/AppModal.vue +28 -0
  87. package/services/frontend/src/orchestrator/components/layout/SvgButtonArrow.vue +14 -0
  88. package/services/frontend/src/orchestrator/composables/useWelcomeScreen.ts +19 -0
  89. package/yarn.lock +3435 -0
@@ -0,0 +1,140 @@
1
+ <template>
2
+ <StepNavigator
3
+ :steps
4
+ :is-saving="disabled"
5
+ :show-save-button="isEditing"
6
+ @save="$emit('save')"
7
+ @save-and-finish="$emit('save-and-finish')"
8
+ >
9
+ <template #default="{ currentStep }">
10
+ <template v-if="currentStep.id === 'config'">
11
+ <div>
12
+ <!-- @vue-generic {CompositionCreate | CompositionUpdate} -->
13
+ <DynamicForm
14
+ ref="dynamicFormRef"
15
+ v-model="composition"
16
+ :data="inputFields"
17
+ :disabled
18
+ :validate-on-change="false"
19
+ :title="
20
+ isEditing
21
+ ? $t('workbench.compositionEdit.title')
22
+ : $t('workbench.compositionCreate.title')
23
+ "
24
+ route-back-interaction-link-id="compositionListInteractionLink"
25
+ />
26
+ </div>
27
+ </template>
28
+
29
+ <template v-else-if="currentStep.id === 'layout'">
30
+ <v-navigation-drawer permanent class="tw:p-2">
31
+ <WidgetList />
32
+ </v-navigation-drawer>
33
+
34
+ <CompositionWidgetsLayout v-model="composition" />
35
+ </template>
36
+
37
+ <template v-else-if="currentStep.id === 'integration'"> </template>
38
+ </template>
39
+ </StepNavigator>
40
+ </template>
41
+
42
+ <script setup lang="ts">
43
+ import type { CompositionCreate, CompositionUpdate } from "@raclettejs/core"
44
+ import DynamicForm from "@app/components/dynamicForm/DynamicForm.vue"
45
+ import type { DynamicFormItem } from "@app/components/dynamicForm/DynamicFormTypes"
46
+ import { useI18n } from "vue-i18n"
47
+ import { computed, useTemplateRef } from "vue"
48
+ import CompositionWidgetsLayout from "./CompositionWidgetsLayout.vue"
49
+ import WidgetList from "./widgetsLayout/WidgetList.vue"
50
+ import StepNavigator from "@app/components/stepNavigator/StepNavigator.vue"
51
+ import { Step } from "@app/components/stepNavigator/StepNavigatorTypes"
52
+
53
+ defineProps<{ disabled?: boolean; isEditing?: boolean }>()
54
+
55
+ defineEmits<{
56
+ (e: "save"): void
57
+ (e: "save-and-finish"): void
58
+ }>()
59
+
60
+ const composition = defineModel<CompositionCreate | CompositionUpdate>({
61
+ required: true,
62
+ })
63
+
64
+ const dynamicFormRef = useTemplateRef("dynamicFormRef")
65
+
66
+ const { t } = useI18n()
67
+
68
+ const inputFields = computed<
69
+ DynamicFormItem<CompositionCreate | CompositionUpdate>[]
70
+ >(() => [
71
+ { divider: t("workbench.compositionConfiguration.divider.general") },
72
+ {
73
+ title: t("workbench.compositionConfiguration.title.title"),
74
+ description: t("workbench.compositionConfiguration.title.description"),
75
+ inputType: "text",
76
+ field: "title",
77
+ prependIcon: "text-short",
78
+ validation: {
79
+ required: true,
80
+ maxLength: 200,
81
+ },
82
+ },
83
+ {
84
+ title: t("workbench.compositionConfiguration.description.title"),
85
+ description: t(
86
+ "workbench.compositionConfiguration.description.description",
87
+ ),
88
+ inputType: "textarea",
89
+ field: "description",
90
+ prependIcon: "text",
91
+ },
92
+ {
93
+ title: t("workbench.compositionConfiguration.tags.title"),
94
+ description: t("workbench.compositionConfiguration.tags.description"),
95
+ inputType: "tags",
96
+ field: "tags",
97
+ prependIcon: "tag-multiple",
98
+ },
99
+ { divider: t("workbench.compositionConfiguration.divider.navigation") },
100
+ {
101
+ title: t("workbench.compositionConfiguration.pathname.title"),
102
+ description: t("workbench.compositionConfiguration.pathname.description"),
103
+ inputType: "localized",
104
+ field: "pathname",
105
+ prependIcon: "link-variant",
106
+ validation: {
107
+ required: true,
108
+ },
109
+ },
110
+ ])
111
+
112
+ const validateConfigStep = (): boolean => {
113
+ const validateAllFields = dynamicFormRef.value?.validateAllFields
114
+
115
+ if (validateAllFields) {
116
+ const isValid = !!validateAllFields()
117
+
118
+ return isValid
119
+ }
120
+ return true
121
+ }
122
+
123
+ const steps = computed<Step[]>(() => [
124
+ {
125
+ id: "config",
126
+ title: t("workbench.compositionConfiguration.steps.configuration.title"),
127
+ subtitle: t(
128
+ "workbench.compositionConfiguration.steps.configuration.subtitle",
129
+ ),
130
+ validator: validateConfigStep,
131
+ },
132
+ {
133
+ id: "layout",
134
+ title: t("workbench.compositionConfiguration.steps.widgetLayout.title"),
135
+ subtitle: t(
136
+ "workbench.compositionConfiguration.steps.widgetLayout.subtitle",
137
+ ),
138
+ },
139
+ ])
140
+ </script>
@@ -0,0 +1,3 @@
1
+ <template>
2
+ <div>Ich bin die Einbindung für eine Komposition :)</div>
3
+ </template>
@@ -0,0 +1,129 @@
1
+ <template>
2
+ <div class="tw:mt-14">
3
+ <!-- editor -->
4
+ <div
5
+ v-if="composition.widgetsLayout"
6
+ class="tw:grid"
7
+ :class="`tw:grid-cols-12`"
8
+ >
9
+ <!-- spawn a dropzone for each array element in widgetsLayout -->
10
+ <DropzoneElement
11
+ v-for="(dropzoneSlot, dropzoneIndex) in composition.widgetsLayout"
12
+ :key="dropzoneIndex"
13
+ :droppable-config="{
14
+ groups: ['widget'],
15
+ sortable: 'vertical',
16
+ onDrop: onDropWrapper(dropzoneIndex),
17
+ }"
18
+ class="tw:col-span-1 tw:grid tw:w-full tw:grid-cols-12 tw:pb-[200px] tw:empty:min-h-[200px]"
19
+ :class="`tw:col-span-${columnSizesMap[dropzoneIndex]}`"
20
+ >
21
+ <!-- each slot in slot[][] represents a widget -->
22
+ <WidgetRenderer
23
+ v-for="(widgetSlot, widgetIndex) in dropzoneSlot"
24
+ :key="widgetIndex"
25
+ :widget-config="widgetSlot"
26
+ @resize="onResize($event, dropzoneIndex, widgetIndex)"
27
+ @update-config="onConfigChanges($event, dropzoneIndex, widgetIndex)"
28
+ @remove-widget="removeWidget(widgetSlot.widget.uuid)"
29
+ @open-settings="showSettingsForWidget = widgetSlot.widget"
30
+ />
31
+ </DropzoneElement>
32
+ </div>
33
+
34
+ <v-navigation-drawer
35
+ :model-value="!!showSettingsForWidget"
36
+ location="right"
37
+ class="tw:p-2"
38
+ >
39
+ <WidgetSettings
40
+ v-if="!!showSettingsForWidget"
41
+ v-model="showSettingsForWidget"
42
+ />
43
+ </v-navigation-drawer>
44
+ </div>
45
+ </template>
46
+
47
+ <script setup lang="ts">
48
+ import { computed, ref } from "vue"
49
+ import { v4 as uuidv4 } from "uuid"
50
+ import { CompositionCreate, CompositionUpdate } from "@raclettejs/core"
51
+ import {
52
+ WidgetSlot,
53
+ type WidgetBase,
54
+ } from "@raclettejs/core/frontend/store/types"
55
+ import { OnDropFunction } from "@raclettejs/core/orchestrator/composables/useDragAndDrop"
56
+ import WidgetRenderer from "./widgetsLayout/WidgetRenderer.vue"
57
+ import WidgetSettings from "./widgetsLayout/WidgetSettings.vue"
58
+ import DropzoneElement from "@raclettejs/core/orchestrator/components/dragAndDrop/DropzoneElement.vue"
59
+
60
+ const composition = defineModel<CompositionCreate | CompositionUpdate>({
61
+ required: true,
62
+ })
63
+
64
+ const showSettingsForWidget = ref<WidgetBase | null>()
65
+
66
+ const createNewWidget = (widgetName: string, pluginKey: string) => ({
67
+ column: 12,
68
+ widget: {
69
+ uuid: uuidv4(),
70
+ name: widgetName,
71
+ pluginKey,
72
+ },
73
+ })
74
+
75
+ const onDropWrapper =
76
+ (dropzoneIndex: number): OnDropFunction =>
77
+ ({ draggableConfig, index }) => {
78
+ let widgetToPut = draggableConfig.widget
79
+
80
+ if (widgetToPut) {
81
+ removeWidget(widgetToPut.widget.uuid)
82
+ } else if (draggableConfig.widgetName && draggableConfig.pluginKey) {
83
+ widgetToPut = createNewWidget(
84
+ draggableConfig.widgetName,
85
+ draggableConfig.pluginKey,
86
+ )
87
+ }
88
+ if (widgetToPut) {
89
+ if (!composition.value.widgetsLayout) {
90
+ composition.value.widgetsLayout = []
91
+ }
92
+
93
+ composition.value.widgetsLayout[dropzoneIndex].splice(
94
+ index,
95
+ 0,
96
+ widgetToPut,
97
+ )
98
+ }
99
+ }
100
+
101
+ // split an input like "8-4" to an array with [8, 4] and consider missing inputs
102
+ const columnSizesMap = computed(() => [12])
103
+
104
+ const removeWidget = (uuid: string) => {
105
+ const layout = composition.value.widgetsLayout
106
+ if (showSettingsForWidget?.value?.uuid === uuid) {
107
+ showSettingsForWidget.value = undefined
108
+ }
109
+ for (let column of layout || []) {
110
+ for (let i = column.length - 1; i >= 0; i--) {
111
+ if (column[i].widget.uuid === uuid) {
112
+ column.splice(i, 1)
113
+ }
114
+ }
115
+ }
116
+ }
117
+
118
+ const onResize = (cols: number, dropzoneIndex: number, widgetIndex: number) => {
119
+ composition.value.widgetsLayout![dropzoneIndex][widgetIndex].column = cols
120
+ }
121
+
122
+ const onConfigChanges = (
123
+ config: WidgetSlot,
124
+ dropzoneIndex: number,
125
+ widgetIndex: number,
126
+ ) => {
127
+ composition.value.widgetsLayout[dropzoneIndex][widgetIndex] = config
128
+ }
129
+ </script>
@@ -0,0 +1,53 @@
1
+ <template>
2
+ <div class="tw:z-50">
3
+ <div class="tw:flex tw:gap-1">
4
+ <v-btn
5
+ v-if="Object.keys(configurableParameteters || {}).length"
6
+ class="tw:rounded"
7
+ icon="mdi-cog"
8
+ color="grey-darken-3"
9
+ @click="openWidgetSettings()"
10
+ />
11
+ <v-btn
12
+ class="tw:rounded"
13
+ icon="mdi-delete-outline"
14
+ color="error"
15
+ @click="removeWidget()"
16
+ />
17
+ </div>
18
+ </div>
19
+ </template>
20
+
21
+ <script setup lang="ts">
22
+ import { ref, computed, type PropType, watchEffect } from "vue"
23
+ import { clone } from "ramda"
24
+ import type { WidgetSlot } from "@raclettejs/core/frontend/store/types"
25
+ import useWidgets from "@raclettejs/core/orchestrator/composables/useWidgets"
26
+
27
+ const { widgetConfig } = defineProps({
28
+ widgetConfig: {
29
+ type: Object as PropType<WidgetSlot>,
30
+ required: true,
31
+ },
32
+ })
33
+
34
+ const emit = defineEmits<{
35
+ updateConfig: [config: WidgetSlot]
36
+ removeWidget: [void]
37
+ openSettings: [void]
38
+ }>()
39
+
40
+ const { findWidgetByName } = useWidgets()
41
+
42
+ const editableConfig = ref<WidgetSlot>(clone(widgetConfig))
43
+ const widget = computed(() => findWidgetByName(widgetConfig.widget.name))
44
+
45
+ const configurableParameteters = computed(
46
+ () => widget.value?.configurableParameters,
47
+ )
48
+ const openWidgetSettings = () => emit("openSettings")
49
+
50
+ const removeWidget = () => emit("removeWidget")
51
+
52
+ watchEffect(() => (editableConfig.value = clone(widgetConfig)))
53
+ </script>
@@ -0,0 +1,99 @@
1
+ <template>
2
+ <div>
3
+ <figure class="tw:flex tw:flex-col tw:items-center tw:gap-4">
4
+ <span
5
+ class="tw:flex tw:h-24 tw:w-24 tw:items-center tw:justify-center tw:rounded tw:shadow-lg"
6
+ :style="{
7
+ backgroundColor: module.color,
8
+ }"
9
+ >
10
+ <span
11
+ v-if="svgContent"
12
+ class="tw:flex tw:h-20 tw:w-20 tw:items-center tw:justify-center"
13
+ :style="{
14
+ '--highlight-color': module.color,
15
+ '--base-color': baseColor,
16
+ }"
17
+ v-html="svgContent"
18
+ />
19
+
20
+ <img
21
+ v-else
22
+ :src="module.icon"
23
+ :alt="`${module.title} icon`"
24
+ class="tw:size-20"
25
+ />
26
+ </span>
27
+ <figcaption class="tw:text-center tw:no-underline">
28
+ {{ module.title }}
29
+ </figcaption>
30
+ </figure>
31
+ </div>
32
+ </template>
33
+
34
+ <script setup lang="ts">
35
+ import { WidgetDeclaration } from "@raclettejs/core"
36
+ import { ref, onMounted, watch, computed } from "vue"
37
+ import type { PropType } from "vue"
38
+ import { useTheme } from "vuetify"
39
+
40
+ const props = defineProps({
41
+ module: {
42
+ type: Object as PropType<WidgetDeclaration>,
43
+ required: true,
44
+ },
45
+ })
46
+
47
+ const vuetifyTheme = useTheme()
48
+
49
+ const svgContent = ref("")
50
+
51
+ const baseColor = computed(() =>
52
+ vuetifyTheme.global.name.value === "dark" ? "#000" : "#fff",
53
+ )
54
+
55
+ const loadSvgContent = async (iconPath: string) => {
56
+ if (iconPath.startsWith("data:image/svg+xml;base64,")) {
57
+ const base64Data = iconPath.split(",")[1]
58
+
59
+ svgContent.value = atob(base64Data) // Decodes the base64 string correctly
60
+ } else if (iconPath.startsWith("data:image/svg+xml,")) {
61
+ svgContent.value = decodeURIComponent(iconPath.split(",")[1])
62
+ } else {
63
+ try {
64
+ const response = await fetch(iconPath)
65
+
66
+ if (!response.ok) {
67
+ throw new Error("Failed to fetch SVG")
68
+ }
69
+ svgContent.value = await response.text()
70
+ } catch (error) {
71
+ console.error("Error loading SVG:", error)
72
+ svgContent.value = ""
73
+ }
74
+ }
75
+ }
76
+
77
+ onMounted(() => {
78
+ if (
79
+ props.module.icon.endsWith(".svg") ||
80
+ props.module.icon.startsWith("data:image/svg+xml")
81
+ ) {
82
+ loadSvgContent(props.module.icon)
83
+ }
84
+ })
85
+
86
+ watch(
87
+ () => props.module,
88
+ (newModule) => {
89
+ if (
90
+ newModule.icon.endsWith(".svg") ||
91
+ newModule.icon.startsWith("data:image/svg+xml")
92
+ ) {
93
+ loadSvgContent(newModule.icon)
94
+ } else {
95
+ svgContent.value = ""
96
+ }
97
+ },
98
+ )
99
+ </script>
@@ -0,0 +1,122 @@
1
+ <template>
2
+ <v-list v-model:opened="openListItems">
3
+ <v-list-subheader>Modules</v-list-subheader>
4
+
5
+ <v-list-group
6
+ v-for="(authorPlugins, authorName) in groupedWidgets"
7
+ :key="authorName"
8
+ :value="authorName"
9
+ >
10
+ <template #activator="{ props }">
11
+ <v-list-item v-bind="props" :title="authorName" />
12
+ </template>
13
+ <v-list-group
14
+ v-for="(pluginWidgets, pluginKey) in authorPlugins"
15
+ :key="pluginKey"
16
+ :value="pluginKey"
17
+ >
18
+ <template #activator="{ props }">
19
+ <v-list-item
20
+ v-bind="props"
21
+ :title="deslugify(pluginKey.split('__')[1])"
22
+ ></v-list-item>
23
+ </template>
24
+
25
+ <DraggableElement
26
+ v-for="widget in pluginWidgets"
27
+ :key="widget.title"
28
+ class="tw:py-4"
29
+ :draggable-config="{
30
+ type: 'widget',
31
+ widgetName: widget.name,
32
+ pluginKey: widget.pluginKey,
33
+ }"
34
+ >
35
+ <WidgetIcon :module="widget" />
36
+ </DraggableElement>
37
+ <v-divider></v-divider>
38
+ </v-list-group>
39
+ </v-list-group>
40
+ </v-list>
41
+ </template>
42
+
43
+ <script setup lang="ts">
44
+ import { deslugify } from "@shared/helper/pluginHelper"
45
+ import { computed, ref } from "vue"
46
+ import useWidgets from "@raclettejs/core/orchestrator/composables/useWidgets"
47
+ import { RegisteredWidgetDeclaration } from "@raclettejs/core"
48
+ import WidgetIcon from "./WidgetIcon.vue"
49
+ import DraggableElement from "@raclettejs/core/orchestrator/components/dragAndDrop/DraggableElement.vue"
50
+
51
+ const { widgets } = useWidgets()
52
+
53
+ const openListItems = ref(["Users"])
54
+
55
+ const groupedWidgets = computed(() => {
56
+ const unsortedResult: Record<
57
+ string,
58
+ Record<string, RegisteredWidgetDeclaration[]>
59
+ > = {}
60
+
61
+ widgets.value.forEach((widget) => {
62
+ if (!unsortedResult[widget.author]) {
63
+ unsortedResult[widget.author] = {}
64
+ }
65
+
66
+ if (!unsortedResult[widget.author][widget.pluginKey]) {
67
+ unsortedResult[widget.author][widget.pluginKey] = []
68
+ }
69
+
70
+ unsortedResult[widget.author][widget.pluginKey].push(widget)
71
+ })
72
+
73
+ const sortedAuthors = Object.keys(unsortedResult).sort((a, b) => {
74
+ if (a === "pacifico") {
75
+ return 1
76
+ }
77
+ if (b === "pacifico") {
78
+ return -1
79
+ }
80
+
81
+ return a.localeCompare(b)
82
+ })
83
+
84
+ const sortedResult: Record<
85
+ string,
86
+ Record<string, RegisteredWidgetDeclaration[]>
87
+ > = {}
88
+
89
+ sortedAuthors.forEach((author) => {
90
+ const plugins = Object.keys(unsortedResult[author]).sort((a, b) => {
91
+ if (a === "core") {
92
+ return 1
93
+ }
94
+ if (b === "core") {
95
+ return -1
96
+ }
97
+
98
+ return a.localeCompare(b)
99
+ })
100
+
101
+ sortedResult[author] = {}
102
+ plugins.forEach((pluginKey) => {
103
+ sortedResult[author][pluginKey] = unsortedResult[author][pluginKey]
104
+ })
105
+ })
106
+
107
+ return sortedResult
108
+ })
109
+
110
+ const initOpenListItems = () => {
111
+ const keys: string[] = []
112
+
113
+ widgets.value.forEach((widget) => {
114
+ keys.push(widget.author)
115
+ keys.push(widget.pluginKey)
116
+ })
117
+
118
+ openListItems.value = [...new Set(keys)]
119
+ }
120
+
121
+ initOpenListItems()
122
+ </script>
@@ -0,0 +1,65 @@
1
+ <template>
2
+ <DraggableElement
3
+ :draggable-config="{
4
+ type: 'widget',
5
+ resize: true,
6
+ onResize,
7
+ anchorToCorner: true,
8
+ widget: widgetConfig,
9
+ }"
10
+ class="tw:group tw:relative tw:m-1 tw:border tw:border-solid tw:border-transparent tw:shadow-md tw:hover:border-blue-500"
11
+ :class="`tw:col-span-${widgetConfig.column}`"
12
+ :data-cols="widgetConfig.column"
13
+ >
14
+ <WidgetContextMenu
15
+ :widget-config="widgetConfig"
16
+ class="tw:absolute tw:right-0 tw:hidden tw:group-hover:block"
17
+ @update-config="$emit('updateConfig', $event)"
18
+ @remove-widget="$emit('removeWidget')"
19
+ @open-settings="$emit('openSettings')"
20
+ />
21
+
22
+ <div class="tw:pointer-events-none">
23
+ <div v-if="activeWidget">
24
+ <v-img :src="activeWidget?.images[0] || activeWidget.icon" />
25
+ </div>
26
+ <div v-else class="tw:bg-red-700 tw:text-2xl tw:text-white">
27
+ Module not found: "{{ widgetConfig.widget.name }}"
28
+ </div>
29
+ </div>
30
+ </DraggableElement>
31
+ </template>
32
+
33
+ <script setup lang="ts">
34
+ import { computed, type PropType } from "vue"
35
+ import DraggableElement from "@raclettejs/core/orchestrator/components/dragAndDrop/DraggableElement.vue"
36
+ import WidgetContextMenu from "./WidgetContextMenu.vue"
37
+ import useWidgets from "@raclettejs/core/orchestrator/composables/useWidgets"
38
+ import { OnResizeFunction } from "@raclettejs/core/orchestrator/composables/useDragAndDrop"
39
+ import { WidgetSlot } from "@raclettejs/core/frontend/store/types"
40
+
41
+ const props = defineProps({
42
+ widgetConfig: {
43
+ type: Object as PropType<WidgetSlot>,
44
+ required: true,
45
+ },
46
+ })
47
+
48
+ const emit = defineEmits<{
49
+ resize: [cols: number]
50
+ updateConfig: [config: WidgetSlot]
51
+ removeWidget: []
52
+ openSettings: [void]
53
+ }>()
54
+
55
+ const { widgets } = useWidgets()
56
+
57
+ const activeWidget = computed(() =>
58
+ findWidgetByName(props.widgetConfig.widget.name),
59
+ )
60
+
61
+ const onResize: OnResizeFunction = ({ cols }) => emit("resize", cols)
62
+
63
+ const findWidgetByName = (widgetName?: string) =>
64
+ widgets.value.find((widget) => widget.name === widgetName)
65
+ </script>