@raclettejs/core 0.1.17 → 0.1.19
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.
- package/CHANGELOG.md +30 -1
- package/package.json +2 -2
- package/services/backend/package.json +2 -2
- package/services/backend/src/core/config/configService.ts +4 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/composition.service.ts +86 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/index.ts +4 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.post.bulk.ts +58 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.restore.ts +55 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/interactionLink.service.ts +96 -9
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/index.ts +4 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.post.bulk.ts +60 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.restore.ts +55 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/project.model.ts +17 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/project.schema.ts +18 -6
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/index.ts +5 -5
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/index.ts +4 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.post.bulk.ts +58 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.restore.ts +55 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/tag.service.ts +74 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/index.ts +2 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.restore.ts +55 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/user.service.ts +1 -0
- package/services/backend/src/domains/system/routes/route.user.login.ts +4 -2
- package/services/backend/src/shared/types/core/ProjectUpdate.types.ts +1 -0
- package/services/backend/src/shared/types/dataTypes/tags.ts +1 -0
- package/services/frontend/package.json +8 -8
- package/services/frontend/src/core/lib/configService.ts +1 -1
- package/services/frontend/src/core/lib/data/dataApi.ts +10 -1
- package/services/frontend/src/core/lib/data/fetchDataHandler.ts +3 -23
- package/services/frontend/src/core/lib/data/writeDataHandler.ts +8 -13
- package/services/frontend/src/core/lib/dataHelper.ts +15 -0
- package/services/frontend/src/core/lib/eggs/snow.ts +32 -43
- package/services/frontend/src/core/lib/eventWhitelist.ts +4 -13
- package/services/frontend/src/core/main.ts +7 -1
- package/services/frontend/src/orchestrator/LoginApp.vue +0 -1
- package/services/frontend/src/orchestrator/ProductOrchestrator.vue +40 -5
- package/services/frontend/src/orchestrator/components/composition/CompositionOverlay.vue +7 -3
- package/services/frontend/src/orchestrator/components/composition/WidgetsLayoutLoader.vue +12 -24
- package/services/frontend/src/orchestrator/components/input/SimpleSearchInput.vue +16 -4
- package/services/frontend/src/orchestrator/components/menu/AppBarMenu.vue +184 -0
- package/services/frontend/src/orchestrator/components/menu/UserMenu.vue +62 -46
- package/services/frontend/src/orchestrator/components/navigation/PageNavigationSidebar.vue +11 -1
- package/services/frontend/src/orchestrator/composables/usePageNavigation.ts +10 -1
- package/services/frontend/src/orchestrator/i18n/de-DE.json +15 -0
- package/services/frontend/src/orchestrator/i18n/en-EU.json +15 -0
- package/services/frontend/src/orchestrator/i18n/sk.json +15 -0
- package/services/frontend/src/orchestrator/router/index.ts +16 -0
- package/yarn.lock +4 -4
- package/services/frontend/src/orchestrator/setup/loginApp.ts +0 -27
|
@@ -7,11 +7,13 @@ export default () => {
|
|
|
7
7
|
|
|
8
8
|
// This function allows you to turn on and off the snow
|
|
9
9
|
const toggle_snow = () => {
|
|
10
|
-
const check_box = document.getElementById(
|
|
10
|
+
const check_box = document.getElementById(
|
|
11
|
+
"toggle_snow",
|
|
12
|
+
) as HTMLInputElement | null
|
|
11
13
|
const snowEl = document.getElementById("snow")
|
|
12
14
|
|
|
13
15
|
if (snowEl) {
|
|
14
|
-
snowEl.style.display = check_box
|
|
16
|
+
snowEl.style.display = check_box?.checked ? "block" : "none"
|
|
15
17
|
}
|
|
16
18
|
}
|
|
17
19
|
|
|
@@ -29,87 +31,74 @@ export default () => {
|
|
|
29
31
|
const randomDuration = 8 + Math.random() * 15 // Duration between 8-23s
|
|
30
32
|
const randomDelay = Math.random() * 10 // Delay between 0-10s
|
|
31
33
|
const randomOpacity = 0.3 + Math.random() * 0.7 // Opacity between 0.3-1
|
|
32
|
-
const randomAnimation = Math.floor(Math.random() *
|
|
34
|
+
const randomAnimation = Math.floor(Math.random() * 3) + 1 // Choose from 3 animations
|
|
33
35
|
|
|
34
|
-
snowflake.style.setProperty("--start-x", `${randomX}
|
|
35
|
-
snowflake.style.setProperty("--scale", randomScale)
|
|
36
|
+
snowflake.style.setProperty("--start-x", `${randomX}%`)
|
|
37
|
+
snowflake.style.setProperty("--scale", randomScale.toString())
|
|
36
38
|
snowflake.style.setProperty("--duration", `${randomDuration}s`)
|
|
37
39
|
snowflake.style.setProperty("--delay", `${randomDelay}s`)
|
|
38
|
-
snowflake.style.setProperty("--opacity", randomOpacity)
|
|
40
|
+
snowflake.style.setProperty("--opacity", randomOpacity.toString())
|
|
39
41
|
snowflake.style.setProperty("--animation-name", `fall-${randomAnimation}`)
|
|
40
42
|
|
|
41
43
|
fragment.appendChild(snowflake)
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
document.getElementById("snow")
|
|
46
|
+
document.getElementById("snow")?.appendChild(fragment)
|
|
45
47
|
}
|
|
46
48
|
|
|
47
|
-
// Optimized CSS with
|
|
49
|
+
// Optimized CSS with GPU acceleration and simplified animations
|
|
48
50
|
const createOptimizedCSS = () => {
|
|
49
51
|
const css = `
|
|
50
52
|
#snow {
|
|
51
53
|
position: fixed;
|
|
52
54
|
top: 0;
|
|
53
55
|
left: 0;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
right: 0;
|
|
57
|
+
bottom: 0;
|
|
56
58
|
pointer-events: none;
|
|
57
59
|
z-index: 1000;
|
|
60
|
+
overflow: hidden;
|
|
58
61
|
}
|
|
59
62
|
|
|
60
63
|
.snowflake {
|
|
61
64
|
position: absolute;
|
|
65
|
+
top: -10px;
|
|
62
66
|
width: 8px;
|
|
63
67
|
height: 8px;
|
|
64
68
|
background: white;
|
|
65
69
|
border-radius: 50%;
|
|
66
|
-
|
|
70
|
+
|
|
71
|
+
/* GPU acceleration - critical for performance */
|
|
67
72
|
will-change: transform;
|
|
73
|
+
transform: translateZ(0);
|
|
74
|
+
backface-visibility: hidden;
|
|
75
|
+
|
|
76
|
+
/* Simplified glow effect - much lighter than drop-shadow filter */
|
|
77
|
+
box-shadow: 0 0 6px rgba(255, 255, 255, 0.6);
|
|
68
78
|
|
|
69
79
|
/* Use CSS custom properties for individual variations */
|
|
70
80
|
left: var(--start-x);
|
|
71
81
|
opacity: var(--opacity);
|
|
72
|
-
transform: scale(var(--scale));
|
|
73
82
|
animation: var(--animation-name) var(--duration) var(--delay) linear infinite;
|
|
74
83
|
}
|
|
75
84
|
|
|
76
|
-
/*
|
|
85
|
+
/* Simplified to 3 falling patterns - uses only transform for best performance */
|
|
77
86
|
@keyframes fall-1 {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
75% { transform: translateY(75vh) translateX(8px) scale(var(--scale)); }
|
|
82
|
-
100% { transform: translateY(100vh) translateX(0) scale(var(--scale)); }
|
|
87
|
+
to {
|
|
88
|
+
transform: translateY(100vh) translateX(20px) scale(var(--scale)) translateZ(0);
|
|
89
|
+
}
|
|
83
90
|
}
|
|
84
91
|
|
|
85
92
|
@keyframes fall-2 {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
100% { transform: translateY(100vh) translateX(-5px) scale(var(--scale)); }
|
|
93
|
+
to {
|
|
94
|
+
transform: translateY(100vh) translateX(-20px) scale(var(--scale)) translateZ(0);
|
|
95
|
+
}
|
|
90
96
|
}
|
|
91
97
|
|
|
92
98
|
@keyframes fall-3 {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
70% { transform: translateY(70vh) translateX(15px) scale(var(--scale)); }
|
|
97
|
-
100% { transform: translateY(100vh) translateX(0) scale(var(--scale)); }
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
@keyframes fall-4 {
|
|
101
|
-
0% { transform: translateY(-10px) translateX(0) scale(var(--scale)); }
|
|
102
|
-
35% { transform: translateY(35vh) translateX(20px) scale(var(--scale)); }
|
|
103
|
-
65% { transform: translateY(65vh) translateX(-8px) scale(var(--scale)); }
|
|
104
|
-
100% { transform: translateY(100vh) translateX(12px) scale(var(--scale)); }
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
@keyframes fall-5 {
|
|
108
|
-
0% { transform: translateY(-10px) translateX(0) scale(var(--scale)); }
|
|
109
|
-
15% { transform: translateY(15vh) translateX(-5px) scale(var(--scale)); }
|
|
110
|
-
45% { transform: translateY(45vh) translateX(12px) scale(var(--scale)); }
|
|
111
|
-
80% { transform: translateY(80vh) translateX(-7px) scale(var(--scale)); }
|
|
112
|
-
100% { transform: translateY(100vh) translateX(3px) scale(var(--scale)); }
|
|
99
|
+
to {
|
|
100
|
+
transform: translateY(100vh) translateX(0px) scale(var(--scale)) translateZ(0);
|
|
101
|
+
}
|
|
113
102
|
}
|
|
114
103
|
`
|
|
115
104
|
|
|
@@ -142,7 +131,7 @@ export default () => {
|
|
|
142
131
|
} else {
|
|
143
132
|
// Remove existing snow
|
|
144
133
|
snowEl.remove()
|
|
145
|
-
//
|
|
134
|
+
// Remove styles as well
|
|
146
135
|
const styles = document.getElementById("snow-styles")
|
|
147
136
|
if (styles) {
|
|
148
137
|
styles.remove()
|
|
@@ -6,19 +6,9 @@
|
|
|
6
6
|
* change of state, others represent a request addressed to a specific actor,
|
|
7
7
|
* e.g. 'asks the http client to get x'
|
|
8
8
|
*
|
|
9
|
-
* @TODO
|
|
10
|
-
* - find clear naming policy
|
|
11
|
-
* - use speaking event names, even though they are longer
|
|
12
|
-
* - distinguish between a information and request (e.g. store_dispatch is a request to the store)
|
|
13
9
|
*/
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"socket_leaveRoom",
|
|
17
|
-
"socket_dataUpdated",
|
|
18
|
-
"ui_setupProject",
|
|
19
|
-
"ui_addToSnackBar",
|
|
20
|
-
]
|
|
21
|
-
const eventWhitelist = legacyEventWhitelist.concat([
|
|
10
|
+
|
|
11
|
+
const eventWhitelist = [
|
|
22
12
|
"forms/init",
|
|
23
13
|
"forms/validate",
|
|
24
14
|
"widgets/reset/workingState",
|
|
@@ -27,7 +17,8 @@ const eventWhitelist = legacyEventWhitelist.concat([
|
|
|
27
17
|
"socket/data/update",
|
|
28
18
|
"socket/join/room",
|
|
29
19
|
"socket/leave/room",
|
|
30
|
-
|
|
20
|
+
"backend/error",
|
|
21
|
+
]
|
|
31
22
|
const eventWildcards = [
|
|
32
23
|
"query_deleted",
|
|
33
24
|
"store_actionResolved",
|
|
@@ -159,12 +159,18 @@ const generateLoginCallback =
|
|
|
159
159
|
success: true,
|
|
160
160
|
token: token,
|
|
161
161
|
redirect: () => {
|
|
162
|
+
const params = new URLSearchParams(window.location.search)
|
|
163
|
+
const redirect = params.get("redirect") || ""
|
|
162
164
|
// If we're in the same origin, we keep the history
|
|
163
165
|
if (
|
|
164
166
|
_callbackUrl.startsWith(window.location.origin) ||
|
|
165
167
|
_callbackUrl.startsWith("/")
|
|
166
168
|
) {
|
|
167
|
-
|
|
169
|
+
if (redirect.length && redirect.startsWith("/:")) {
|
|
170
|
+
window.location.href = _callbackUrl + redirect
|
|
171
|
+
} else {
|
|
172
|
+
window.location.reload()
|
|
173
|
+
}
|
|
168
174
|
} else {
|
|
169
175
|
// Otherwise do a full page reload to the callback URL
|
|
170
176
|
window.location.replace(_callbackUrl)
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
:order="-10"
|
|
12
12
|
v-if="availableInterfaces.indexOf('topBar') > -1"
|
|
13
13
|
id="appAppbar"
|
|
14
|
+
ref="appAppbar"
|
|
14
15
|
color="topbar-bg"
|
|
15
16
|
density="compact"
|
|
16
17
|
flat
|
|
@@ -38,7 +39,8 @@
|
|
|
38
39
|
|
|
39
40
|
<img v-if="logoExists" class="tw:h-[40px]" :src="logoSrc" alt="Logo" />
|
|
40
41
|
</div>
|
|
41
|
-
|
|
42
|
+
<v-spacer />
|
|
43
|
+
<AppBarMenu />
|
|
42
44
|
<v-spacer />
|
|
43
45
|
|
|
44
46
|
<DevIndicator v-if="isDevelopment" />
|
|
@@ -60,7 +62,7 @@
|
|
|
60
62
|
/>
|
|
61
63
|
|
|
62
64
|
<!-- Main content -->
|
|
63
|
-
<v-main class="tw:flex tw:flex-col">
|
|
65
|
+
<v-main class="tw:flex tw:flex-col" id="appMainContent">
|
|
64
66
|
<div class="tw:w-full tw-h-1 tw:top-[49px]">
|
|
65
67
|
<v-progress-linear
|
|
66
68
|
:height="2"
|
|
@@ -80,6 +82,7 @@
|
|
|
80
82
|
slot-type="page"
|
|
81
83
|
:slot-layout="slotLayout"
|
|
82
84
|
@scroll.passive="onScroll($event)"
|
|
85
|
+
:max-height="availableComponentHeight"
|
|
83
86
|
/>
|
|
84
87
|
|
|
85
88
|
<v-empty-state
|
|
@@ -98,6 +101,7 @@
|
|
|
98
101
|
<v-footer
|
|
99
102
|
:order="-1"
|
|
100
103
|
color="footer-bg"
|
|
104
|
+
ref="appFooter"
|
|
101
105
|
border-thin
|
|
102
106
|
v-if="configService.getPleaseLetUsAdvertiseOurselves()"
|
|
103
107
|
class="tw:items-center tw:text-center tw:flex tw:flex-col tw:border-t-1 tw:grow-0! tw:mt-auto"
|
|
@@ -123,7 +127,15 @@
|
|
|
123
127
|
</template>
|
|
124
128
|
|
|
125
129
|
<script setup lang="ts">
|
|
126
|
-
import {
|
|
130
|
+
import {
|
|
131
|
+
onMounted,
|
|
132
|
+
onUnmounted,
|
|
133
|
+
useTemplateRef,
|
|
134
|
+
ref,
|
|
135
|
+
watch,
|
|
136
|
+
computed,
|
|
137
|
+
defineAsyncComponent,
|
|
138
|
+
} from "vue"
|
|
127
139
|
import UserNotifications from "./components/menu/UserNotifications.vue"
|
|
128
140
|
import PageNavigation from "./components/navigation/PageNavigationSidebar.vue"
|
|
129
141
|
import snackbarStack from "./components/snackbar/SnackStack.vue"
|
|
@@ -142,6 +154,9 @@ import { isDevelopmentMode } from "@racletteCore/helpers/devMode"
|
|
|
142
154
|
import useDragAndDrop from "./composables/useDragAndDrop"
|
|
143
155
|
import { useCurrentComposition } from "./composables"
|
|
144
156
|
import WidgetsLayoutLoader from "./components/composition/WidgetsLayoutLoader.vue"
|
|
157
|
+
import AppBarMenu from "./components/menu/AppBarMenu.vue"
|
|
158
|
+
import { useWindowSize } from "@vueuse/core"
|
|
159
|
+
const { height: windowHeight } = useWindowSize()
|
|
145
160
|
|
|
146
161
|
useTitle(`raclette - ${configService.getApplicationTitle()}`)
|
|
147
162
|
const route = useRoute()
|
|
@@ -154,6 +169,22 @@ useRouteState()
|
|
|
154
169
|
const { isDraggingOrResizingAnything } = useDragAndDrop()
|
|
155
170
|
const { widgetsLayout, slotLayout } = useCurrentComposition()
|
|
156
171
|
|
|
172
|
+
const appAppbarRef = useTemplateRef("appAppbar")
|
|
173
|
+
const appFooterRef = useTemplateRef("appFooter")
|
|
174
|
+
const availableComponentHeight = ref(0)
|
|
175
|
+
|
|
176
|
+
const updateAvailableHeight = () => {
|
|
177
|
+
const appbarHeight = appAppbarRef.value?.offsetHeight || 0
|
|
178
|
+
const appFooterHeight = appFooterRef.value?.offsetHeight || 0
|
|
179
|
+
const progressBarHeight = 2 // Progress bar height
|
|
180
|
+
const borderAndPadding = 8 // Border and padding buffer
|
|
181
|
+
availableComponentHeight.value =
|
|
182
|
+
windowHeight.value -
|
|
183
|
+
appbarHeight -
|
|
184
|
+
appFooterHeight -
|
|
185
|
+
progressBarHeight -
|
|
186
|
+
borderAndPadding
|
|
187
|
+
}
|
|
157
188
|
const showWelcomeScreen = computed(() => {
|
|
158
189
|
const isDynamicPage = route.name === "app.space.page"
|
|
159
190
|
let hasPageIdParameter = Boolean(route.params.pageId?.length)
|
|
@@ -170,7 +201,7 @@ const WelcomeScreenComponent = defineAsyncComponent({
|
|
|
170
201
|
template: `
|
|
171
202
|
<div class="tw:flex tw:items-center tw:justify-center tw:h-full tw:text-center tw:p-8">
|
|
172
203
|
<div>
|
|
173
|
-
<h1 class="tw:text-4xl tw:font-bold tw:mb-4 tw:text-gray-800">Welcome to
|
|
204
|
+
<h1 class="tw:text-4xl tw:font-bold tw:mb-4 tw:text-gray-800">Welcome to racletteJS</h1>
|
|
174
205
|
<p class="tw:text-gray-600 tw:text-lg">Get started by selecting a page from the navigation.</p>
|
|
175
206
|
</div>
|
|
176
207
|
</div>
|
|
@@ -232,8 +263,12 @@ onMounted(async () => {
|
|
|
232
263
|
logoExists.value = await checkImageExists(logoSrc.value)
|
|
233
264
|
// check if we have a message from checkToken to display as a snackbar
|
|
234
265
|
loginUserNotifications()
|
|
266
|
+
updateAvailableHeight()
|
|
267
|
+
window.addEventListener("resize", updateAvailableHeight)
|
|
268
|
+
})
|
|
269
|
+
onUnmounted(() => {
|
|
270
|
+
window.removeEventListener("resize", updateAvailableHeight)
|
|
235
271
|
})
|
|
236
|
-
|
|
237
272
|
watch(theme, () => {
|
|
238
273
|
setTheme(theme.value)
|
|
239
274
|
})
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<DefaultModal v-model="showModal">
|
|
3
|
-
<div class="tw:p-5 tw:h-full">
|
|
3
|
+
<div class="tw:p-5 tw:h-full" ref="appCompositionOverlay">
|
|
4
4
|
<WidgetsLayoutLoader
|
|
5
5
|
v-if="widgetsLayout"
|
|
6
6
|
:widgets-layout="widgetsLayout"
|
|
7
7
|
:slot-layout="slotLayout"
|
|
8
8
|
slot-type="modal"
|
|
9
|
+
:max-height="availableComponentHeight"
|
|
9
10
|
/>
|
|
10
11
|
</div>
|
|
11
12
|
</DefaultModal>
|
|
@@ -14,10 +15,13 @@
|
|
|
14
15
|
<script setup lang="ts">
|
|
15
16
|
import { DefaultModal, WidgetsLayoutLoader } from "@racletteOrchestrator"
|
|
16
17
|
import useCurrentComposition from "@racletteOrchestrator/composables/useCurrentComposition"
|
|
17
|
-
import { computed } from "vue"
|
|
18
|
+
import { computed, useTemplateRef } from "vue"
|
|
18
19
|
import useRouteState from "@racletteOrchestrator/composables/useRouteState"
|
|
19
20
|
import { DEFAULT_MODAL_SLOT_NAME } from "@racletteOrchestrator/router/routeParserHelper"
|
|
20
|
-
|
|
21
|
+
const appCompositionOverlayRef = useTemplateRef("appCompositionOverlay")
|
|
22
|
+
const availableComponentHeight = computed(
|
|
23
|
+
() => appCompositionOverlayRef?.value?.offsetHeight || 400,
|
|
24
|
+
)
|
|
21
25
|
const { widgetsLayout, slotLayout } = useCurrentComposition(
|
|
22
26
|
DEFAULT_MODAL_SLOT_NAME,
|
|
23
27
|
)
|
|
@@ -59,7 +59,6 @@
|
|
|
59
59
|
v-if="slot.widget"
|
|
60
60
|
:is="getSlotWidget(slot.widget)"
|
|
61
61
|
:uuid="slot.widget.uuid"
|
|
62
|
-
class="tw:h-full"
|
|
63
62
|
:class="[
|
|
64
63
|
hasSpacing &&
|
|
65
64
|
widgetStates[slot.widget.uuid].config
|
|
@@ -91,16 +90,20 @@ import type { WidgetBase } from "@racletteCore/store/types"
|
|
|
91
90
|
import { watch, ref, h, computed, type Component } from "vue"
|
|
92
91
|
import { widgetLoader } from "@racletteOrchestrator/helpers/widgetLoader"
|
|
93
92
|
import { useDisplay } from "vuetify"
|
|
94
|
-
import { useWindowSize } from "@vueuse/core"
|
|
95
93
|
import useStateSubscriber from "@racletteOrchestrator/composables/useStateSubscriber"
|
|
96
94
|
import useRouteState from "@racletteOrchestrator/composables/useRouteState"
|
|
97
95
|
|
|
98
|
-
const props =
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
96
|
+
const props = withDefaults(
|
|
97
|
+
defineProps<{
|
|
98
|
+
widgetsLayout: Composition["widgetsLayout"]
|
|
99
|
+
slotType: string
|
|
100
|
+
slotLayout: string
|
|
101
|
+
maxHeight?: number
|
|
102
|
+
}>(),
|
|
103
|
+
{
|
|
104
|
+
maxHeight: 0,
|
|
105
|
+
},
|
|
106
|
+
)
|
|
104
107
|
const { compositionSlots } = useRouteState()
|
|
105
108
|
|
|
106
109
|
const currentRouteConfig = computed(() =>
|
|
@@ -131,7 +134,6 @@ const rowClasses = computed(() => {
|
|
|
131
134
|
})
|
|
132
135
|
|
|
133
136
|
const { mdAndDown } = useDisplay()
|
|
134
|
-
const { height: windowHeight } = useWindowSize()
|
|
135
137
|
|
|
136
138
|
const { state: widgetStates } = useStateSubscriber("widgets")
|
|
137
139
|
const routeParams = computed(() => {
|
|
@@ -152,26 +154,12 @@ const routeParams = computed(() => {
|
|
|
152
154
|
// Create a reactive key that changes when widgetsLayout changes
|
|
153
155
|
const gridKey = ref(0)
|
|
154
156
|
|
|
155
|
-
// Calculate available height for widgets
|
|
156
|
-
const availableHeight = computed(() => {
|
|
157
|
-
// this is really just a simplified approach for now
|
|
158
|
-
|
|
159
|
-
// TODO: this needs to be more dynamic
|
|
160
|
-
const appBarHeight = 48 // Vuetify compact app bar height
|
|
161
|
-
const progressBarHeight = 2 // Progress bar height
|
|
162
|
-
const borderAndPadding = 8 // Border and padding buffer
|
|
163
|
-
|
|
164
|
-
const calculated =
|
|
165
|
-
windowHeight.value - appBarHeight - progressBarHeight - borderAndPadding
|
|
166
|
-
return calculated
|
|
167
|
-
})
|
|
168
|
-
|
|
169
157
|
// Calculate max height per widget based on grid layout
|
|
170
158
|
const maxWidgetHeight = computed(() => {
|
|
171
159
|
// Always set max height to the calculated available screen height (with buffer)
|
|
172
160
|
// Later we should determine a max value based on other widgets in each row, so this value will be different across rows and columns
|
|
173
161
|
|
|
174
|
-
return Math.min(
|
|
162
|
+
return Math.min(props.maxHeight, 2400)
|
|
175
163
|
})
|
|
176
164
|
|
|
177
165
|
const widgetComponentCache = new Map<string, Component>()
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<v-text-field
|
|
3
3
|
ref="search-input"
|
|
4
|
-
v-model="
|
|
4
|
+
v-model="workingString"
|
|
5
5
|
clearable
|
|
6
6
|
:autofocus="autoFocus"
|
|
7
7
|
density="compact"
|
|
8
|
-
:label="$t('core.search_input')"
|
|
8
|
+
:label="label || $t('core.search_input')"
|
|
9
9
|
prepend-inner-icon="mdi-magnify"
|
|
10
10
|
flat
|
|
11
11
|
hide-details
|
|
@@ -14,12 +14,15 @@
|
|
|
14
14
|
</template>
|
|
15
15
|
|
|
16
16
|
<script setup lang="ts">
|
|
17
|
-
import { useTemplateRef, onMounted } from "vue"
|
|
17
|
+
import { useTemplateRef, onMounted, computed } from "vue"
|
|
18
18
|
import { awaitTime } from "@racletteCore/lib/dataHelper"
|
|
19
|
+
import { debounceHelper } from "@raclettejs/core/frontend"
|
|
20
|
+
const debounce = debounceHelper()
|
|
19
21
|
|
|
20
22
|
const props = withDefaults(
|
|
21
23
|
defineProps<{
|
|
22
24
|
autoFocus?: boolean
|
|
25
|
+
label?: string
|
|
23
26
|
}>(),
|
|
24
27
|
{
|
|
25
28
|
autoFocus: false,
|
|
@@ -30,7 +33,16 @@ const model = defineModel({
|
|
|
30
33
|
type: String,
|
|
31
34
|
required: true,
|
|
32
35
|
})
|
|
33
|
-
|
|
36
|
+
const workingString = computed({
|
|
37
|
+
get() {
|
|
38
|
+
return model.value
|
|
39
|
+
},
|
|
40
|
+
set(newVal) {
|
|
41
|
+
debounce(() => {
|
|
42
|
+
model.value = newVal
|
|
43
|
+
})
|
|
44
|
+
},
|
|
45
|
+
})
|
|
34
46
|
const input = useTemplateRef("search-input")
|
|
35
47
|
|
|
36
48
|
onMounted(async () => {
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="tw:h-full tw:flex tw-items-stretchc" ref="appAppbarMenu">
|
|
3
|
+
<template v-for="item in pageNavigation.appBarItems" :key="item.route">
|
|
4
|
+
<v-tab
|
|
5
|
+
:key="locale"
|
|
6
|
+
v-if="item.slotType !== 'inline'"
|
|
7
|
+
:disabled="activeRouteName === item.route || !item.route"
|
|
8
|
+
:active="activeRouteName === item.route"
|
|
9
|
+
:value="item.route || ''"
|
|
10
|
+
color="primary"
|
|
11
|
+
class="nav-link tw:h-full tw-flex tw-items-center tw-justify-center"
|
|
12
|
+
link
|
|
13
|
+
@click="navigate(item)"
|
|
14
|
+
:prepend-icon="item.icon"
|
|
15
|
+
:text="item.title"
|
|
16
|
+
/>
|
|
17
|
+
<template v-else>
|
|
18
|
+
<template v-if="item.icon || item.title">
|
|
19
|
+
<v-tab
|
|
20
|
+
class="tw:h-full tw-flex tw-items-stretch"
|
|
21
|
+
:color="floatingWindow.visible ? 'primary' : 'none'"
|
|
22
|
+
@click="openFloatingWindow(item)"
|
|
23
|
+
><v-icon>{{ item.icon || "mdi-window-restore" }}</v-icon
|
|
24
|
+
>{{ item.title }}</v-tab
|
|
25
|
+
>
|
|
26
|
+
</template>
|
|
27
|
+
<div v-else class="tw:h-full tw-flex tw-items-stretch">
|
|
28
|
+
<WidgetsLayoutLoader
|
|
29
|
+
v-if="compositions[item.compositionId]"
|
|
30
|
+
:widgets-layout="compositions[item.compositionId].widgetsLayout"
|
|
31
|
+
slot-type="inline"
|
|
32
|
+
:slot-layout="compositions[item.compositionId].slotLayout"
|
|
33
|
+
:max-height="availableComponentHeight"
|
|
34
|
+
/>
|
|
35
|
+
</div>
|
|
36
|
+
</template>
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<!-- Floating Window -->
|
|
40
|
+
<teleport to="body">
|
|
41
|
+
<div
|
|
42
|
+
v-if="floatingWindow.visible"
|
|
43
|
+
:style="{
|
|
44
|
+
position: 'fixed',
|
|
45
|
+
left: floatingWindow.x + 'px',
|
|
46
|
+
top: floatingWindow.y + 'px',
|
|
47
|
+
zIndex: 9999,
|
|
48
|
+
cursor: isDragging ? 'grabbing' : 'grab',
|
|
49
|
+
}"
|
|
50
|
+
@mousedown="startDrag"
|
|
51
|
+
class="tw:select-none"
|
|
52
|
+
>
|
|
53
|
+
<v-card min-width="400" max-width="800" elevation="8">
|
|
54
|
+
<v-card-title
|
|
55
|
+
class="d-flex justify-space-between align-center bg-primary"
|
|
56
|
+
>
|
|
57
|
+
<span>{{ floatingWindow.item?.title || "Widget" }}</span>
|
|
58
|
+
<v-btn
|
|
59
|
+
icon="mdi-close"
|
|
60
|
+
size="small"
|
|
61
|
+
variant="text"
|
|
62
|
+
@click.stop="closeFloatingWindow"
|
|
63
|
+
></v-btn>
|
|
64
|
+
</v-card-title>
|
|
65
|
+
<v-card-text class="pa-4">
|
|
66
|
+
<WidgetsLayoutLoader
|
|
67
|
+
v-if="
|
|
68
|
+
floatingWindow.item &&
|
|
69
|
+
compositions[floatingWindow.item.compositionId]
|
|
70
|
+
"
|
|
71
|
+
:max-height="800"
|
|
72
|
+
:widgets-layout="
|
|
73
|
+
compositions[floatingWindow.item.compositionId].widgetsLayout
|
|
74
|
+
"
|
|
75
|
+
slot-type="inline"
|
|
76
|
+
:slot-layout="
|
|
77
|
+
compositions[floatingWindow.item.compositionId].slotLayout
|
|
78
|
+
"
|
|
79
|
+
/>
|
|
80
|
+
</v-card-text>
|
|
81
|
+
</v-card>
|
|
82
|
+
</div>
|
|
83
|
+
</teleport>
|
|
84
|
+
</div>
|
|
85
|
+
</template>
|
|
86
|
+
|
|
87
|
+
<script setup lang="ts">
|
|
88
|
+
import { computed, useTemplateRef, reactive, ref } from "vue"
|
|
89
|
+
import { useRouter } from "vue-router"
|
|
90
|
+
import useRouteState from "@racletteOrchestrator/composables/useRouteState"
|
|
91
|
+
import usePageNavigation, {
|
|
92
|
+
type NavigationItem,
|
|
93
|
+
} from "@racletteOrchestrator/composables/usePageNavigation"
|
|
94
|
+
import WidgetsLayoutLoader from "../composition/WidgetsLayoutLoader.vue"
|
|
95
|
+
import { DEFAULT_SLOT_NAME } from "@racletteOrchestrator/router/routeParserHelper"
|
|
96
|
+
import useStateSubscriber from "@racletteOrchestrator/composables/useStateSubscriber"
|
|
97
|
+
|
|
98
|
+
const appAppbarMenuRef = useTemplateRef("appAppbarMenu")
|
|
99
|
+
const availableComponentHeight = computed(
|
|
100
|
+
() => appAppbarMenuRef?.value?.offsetHeight || 48,
|
|
101
|
+
)
|
|
102
|
+
const { state: pageNavigation } = usePageNavigation()
|
|
103
|
+
const { state: compositions } = useStateSubscriber("compositions")
|
|
104
|
+
const router = useRouter()
|
|
105
|
+
const { getCompositionLinkForSlotName } = useRouteState()
|
|
106
|
+
const currentPageRoute = getCompositionLinkForSlotName(DEFAULT_SLOT_NAME)
|
|
107
|
+
|
|
108
|
+
const activeRouteName = computed(() => {
|
|
109
|
+
const currentRoute = router.currentRoute.value
|
|
110
|
+
const pageCompositionLinkValue = currentPageRoute?.value?.slotValue
|
|
111
|
+
|
|
112
|
+
if (currentRoute.name === "app.space.page") {
|
|
113
|
+
return pageCompositionLinkValue || currentRoute.params.pageId
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return pageCompositionLinkValue || currentRoute.name
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
// Floating window state
|
|
120
|
+
const floatingWindow = reactive({
|
|
121
|
+
visible: false,
|
|
122
|
+
x: 100,
|
|
123
|
+
y: 100,
|
|
124
|
+
item: null as NavigationItem | null,
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
const isDragging = ref(false)
|
|
128
|
+
const dragOffset = reactive({ x: 0, y: 0 })
|
|
129
|
+
|
|
130
|
+
const openFloatingWindow = (item: NavigationItem) => {
|
|
131
|
+
if (floatingWindow.item) return closeFloatingWindow()
|
|
132
|
+
floatingWindow.item = item
|
|
133
|
+
floatingWindow.visible = true
|
|
134
|
+
// Center the window on screen
|
|
135
|
+
floatingWindow.x = (window.innerWidth - 400) / 2
|
|
136
|
+
floatingWindow.y = (window.innerHeight - 300) / 2
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const closeFloatingWindow = () => {
|
|
140
|
+
floatingWindow.visible = false
|
|
141
|
+
floatingWindow.item = null
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const startDrag = (e: MouseEvent) => {
|
|
145
|
+
// Don't drag if clicking on close button or other interactive elements
|
|
146
|
+
if ((e.target as HTMLElement).closest(".v-btn")) {
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
isDragging.value = true
|
|
151
|
+
dragOffset.x = e.clientX - floatingWindow.x
|
|
152
|
+
dragOffset.y = e.clientY - floatingWindow.y
|
|
153
|
+
|
|
154
|
+
let rafPending = false
|
|
155
|
+
let lastEvent: MouseEvent
|
|
156
|
+
|
|
157
|
+
const onMouseMove = (e: MouseEvent) => {
|
|
158
|
+
lastEvent = e
|
|
159
|
+
if (!rafPending) {
|
|
160
|
+
rafPending = true
|
|
161
|
+
requestAnimationFrame(() => {
|
|
162
|
+
rafPending = false
|
|
163
|
+
if (isDragging.value) {
|
|
164
|
+
floatingWindow.x = lastEvent.clientX - dragOffset.x
|
|
165
|
+
floatingWindow.y = lastEvent.clientY - dragOffset.y
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const onMouseUp = () => {
|
|
172
|
+
isDragging.value = false
|
|
173
|
+
document.removeEventListener("mousemove", onMouseMove)
|
|
174
|
+
document.removeEventListener("mouseup", onMouseUp)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
document.addEventListener("mousemove", onMouseMove)
|
|
178
|
+
document.addEventListener("mouseup", onMouseUp)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const navigate = (item: NavigationItem) => {
|
|
182
|
+
item.navigateTo()
|
|
183
|
+
}
|
|
184
|
+
</script>
|