@raclettejs/core 0.1.40 → 0.1.42
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 +36 -1
- package/dist/cli.js +104 -104
- package/dist/cli.js.map +4 -4
- package/dist/index.js.map +2 -2
- package/package.json +2 -2
- package/services/frontend/eslint.config.js +31 -9
- package/services/frontend/src/core/setup/socketBootstrap.ts +4 -3
- package/services/frontend/src/core/store/state.ts +2 -7
- package/services/frontend/src/core/store/types/index.ts +2 -0
- package/services/frontend/src/orchestrator/ProductOrchestrator.vue +33 -22
- package/services/frontend/src/orchestrator/components/LoadingWidgetState.vue +7 -3
- package/services/frontend/src/orchestrator/components/composition/WidgetsLayoutLoader.vue +179 -195
- package/services/frontend/src/orchestrator/components/dataExport/DataExporter.vue +79 -24
- package/services/frontend/src/orchestrator/components/dataTable/BaseDataTable.vue +27 -14
- package/services/frontend/src/orchestrator/components/dataTable/BaseDataTableGroupSelectCheckbox.vue +42 -0
- package/services/frontend/src/orchestrator/components/index.ts +2 -0
- package/services/frontend/src/orchestrator/components/menu/ServerStatus.vue +2 -4
- package/services/frontend/src/orchestrator/components/menu/UserMenu.vue +1 -1
- package/services/frontend/src/orchestrator/components/welcomeScreen/AdminWelcomeScreen.vue +1 -1
- package/services/frontend/src/orchestrator/composables/index.ts +1 -0
- package/services/frontend/src/orchestrator/composables/useAppLoadingCoordinator.ts +77 -0
- package/services/frontend/src/orchestrator/composables/usePageNavigation.ts +1 -1
- package/services/frontend/src/orchestrator/composables/useUiState.ts +2 -0
- package/services/frontend/src/orchestrator/composables/useWidgetLifecycle.ts +65 -76
- package/services/frontend/src/orchestrator/composables/useWidgets/helperFunctions.ts +7 -4
- package/services/frontend/src/orchestrator/constants/index.ts +2 -0
- package/services/frontend/src/orchestrator/constants/widgetSlot.ts +7 -0
- package/services/frontend/src/orchestrator/exports.ts +1 -0
- package/services/frontend/src/orchestrator/helpers/staticWidgetRegistry.ts +9 -44
- package/services/frontend/src/orchestrator/helpers/widgetLayoutHelper.ts +37 -0
- package/services/frontend/src/orchestrator/helpers/widgetLoader.ts +0 -1
- package/services/frontend/src/orchestrator/helpers/widgetSlotHelper.ts +20 -0
- package/services/frontend/src/orchestrator/helpers/widgetVisuals.ts +11 -12
- package/services/frontend/src/orchestrator/helpers/wrapWidgetWithLifecycle.ts +32 -0
- package/services/frontend/src/orchestrator/router/routerHooks/afterEach.ts +4 -29
- package/services/frontend/src/orchestrator/router/routerHooks/beforeEach.ts +4 -1
- package/services/frontend/src/orchestrator/types/Widgets.ts +12 -6
- package/src/types.ts +7 -1
|
@@ -2,6 +2,8 @@ import parentConfig from "../../eslint.config.mjs"
|
|
|
2
2
|
import pluginVue from "eslint-plugin-vue"
|
|
3
3
|
import betterTailwind from "eslint-plugin-better-tailwindcss"
|
|
4
4
|
import globals from "globals"
|
|
5
|
+
import vueParser from "vue-eslint-parser"
|
|
6
|
+
import tseslint from "typescript-eslint"
|
|
5
7
|
|
|
6
8
|
export default [
|
|
7
9
|
...parentConfig,
|
|
@@ -18,8 +20,28 @@ export default [
|
|
|
18
20
|
// Vue config
|
|
19
21
|
...pluginVue.configs["flat/recommended"],
|
|
20
22
|
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
+
// TypeScript in <script lang="ts"> — vue-eslint-parser delegates to @typescript-eslint/parser
|
|
24
|
+
{
|
|
25
|
+
files: ["**/*.vue"],
|
|
26
|
+
plugins: {
|
|
27
|
+
"@typescript-eslint": tseslint.plugin,
|
|
28
|
+
},
|
|
29
|
+
languageOptions: {
|
|
30
|
+
parser: vueParser,
|
|
31
|
+
parserOptions: {
|
|
32
|
+
parser: tseslint.parser,
|
|
33
|
+
project: "./tsconfig.app.json",
|
|
34
|
+
extraFileExtensions: [".vue"],
|
|
35
|
+
},
|
|
36
|
+
sourceType: "module",
|
|
37
|
+
globals: {
|
|
38
|
+
...globals.browser,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
// Better-Tailwindcss: Tailwind v4 + `prefix(tw)` from tailwindStyles.css.
|
|
44
|
+
// `tw:!utility`, `tw:utility!`, `tw:hover:utility` — not `!tw:utility` or `hover:tw:utility`.
|
|
23
45
|
{
|
|
24
46
|
...betterTailwind.configs.recommended,
|
|
25
47
|
files: ["**/*.{vue,ts,tsx,js,jsx,cjs,mjs}"],
|
|
@@ -34,17 +56,17 @@ export default [
|
|
|
34
56
|
rules: {
|
|
35
57
|
// Tailwind v4 + `@source inline(...)` generates utilities dynamically,
|
|
36
58
|
// so unknown-class reports are noisy - downgrade to a warning.
|
|
37
|
-
"better-tailwindcss/no-unknown-classes":
|
|
59
|
+
"better-tailwindcss/no-unknown-classes": [
|
|
60
|
+
"warn",
|
|
61
|
+
{
|
|
62
|
+
// Non-Tailwind classes from scoped/global CSS (e.g. ProductOrchestrator.vue)
|
|
63
|
+
ignore: ["^raclette$", "^footerLink$"],
|
|
64
|
+
},
|
|
65
|
+
],
|
|
38
66
|
},
|
|
39
67
|
},
|
|
40
68
|
{
|
|
41
69
|
files: ["**/*.vue"],
|
|
42
|
-
languageOptions: {
|
|
43
|
-
sourceType: "module",
|
|
44
|
-
globals: {
|
|
45
|
-
...globals.browser,
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
70
|
rules: {
|
|
49
71
|
// Essential Vue rules (from ESLint plugin Vue)
|
|
50
72
|
"vue/multi-word-component-names": "off",
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* Flow:
|
|
5
5
|
* 1. Server emits joinConfirmation → client dispatches projectInit (resets project-scoped store slices)
|
|
6
6
|
* 2. projectInit completes → store emits store_actionResolved_{resolveId}
|
|
7
|
-
* 3. onProjectInitResolved runs setupSpace, syncs route compositions,
|
|
7
|
+
* 3. onProjectInitResolved runs setupSpace, syncs route compositions,
|
|
8
|
+
* sets ui.bootstrapComplete, then emits ui/setupProject
|
|
8
9
|
*/
|
|
9
10
|
import type { Composition } from "@shared/types/core/Composition.types"
|
|
10
11
|
import type { InteractionLink } from "@shared/types/core/InteractionLink.types"
|
|
@@ -55,7 +56,7 @@ export const startProjectBootstrap = (
|
|
|
55
56
|
|
|
56
57
|
store.dispatch({
|
|
57
58
|
type: "ui/update",
|
|
58
|
-
payload: { loading: true },
|
|
59
|
+
payload: { loading: true, bootstrapComplete: false },
|
|
59
60
|
})
|
|
60
61
|
|
|
61
62
|
// Same setupSpace as logged-in users (landing page detection, etc.)
|
|
@@ -70,7 +71,7 @@ export const startProjectBootstrap = (
|
|
|
70
71
|
|
|
71
72
|
store.dispatch({
|
|
72
73
|
type: "ui/update",
|
|
73
|
-
payload: {
|
|
74
|
+
payload: { bootstrapComplete: true },
|
|
74
75
|
})
|
|
75
76
|
|
|
76
77
|
eventbus.emit("ui/setupProject", roomId)
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
*
|
|
4
4
|
* exports
|
|
5
5
|
*
|
|
6
|
-
* - loadingHold
|
|
7
6
|
* - applicationState (initial state of the store)
|
|
8
7
|
* - configPatterns
|
|
9
8
|
* - storePatterns
|
|
@@ -11,11 +10,6 @@
|
|
|
11
10
|
|
|
12
11
|
import type { ApplicationState } from "./types"
|
|
13
12
|
|
|
14
|
-
/**
|
|
15
|
-
* Used as a timer value
|
|
16
|
-
*/
|
|
17
|
-
const loadingHold = 300
|
|
18
|
-
|
|
19
13
|
/**
|
|
20
14
|
* applicationState
|
|
21
15
|
* Our actual application data
|
|
@@ -50,6 +44,7 @@ const applicationState: ApplicationState = {
|
|
|
50
44
|
mobile: false,
|
|
51
45
|
// indicates if the page is in loading state
|
|
52
46
|
loading: true,
|
|
47
|
+
bootstrapComplete: false,
|
|
53
48
|
theme: "light",
|
|
54
49
|
locale: "en-EU",
|
|
55
50
|
shortcuts: {},
|
|
@@ -143,4 +138,4 @@ const storePatterns = {
|
|
|
143
138
|
},
|
|
144
139
|
}
|
|
145
140
|
|
|
146
|
-
export {
|
|
141
|
+
export { applicationState, configPatterns, storePatterns }
|
|
@@ -54,6 +54,8 @@ export type UIState = {
|
|
|
54
54
|
navigationOpen: boolean
|
|
55
55
|
availableInterfaces: ("pageNavigation" | "modalTools" | "topBar")[]
|
|
56
56
|
loading: boolean
|
|
57
|
+
/** True after socket bootstrap (`setupSpace`) has finished for the current project. */
|
|
58
|
+
bootstrapComplete: boolean
|
|
57
59
|
mobile: boolean
|
|
58
60
|
theme: "light" | "dark"
|
|
59
61
|
locale: "de-DE" | "en-EU"
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
>
|
|
9
9
|
<!-- Global Toolbar -->
|
|
10
10
|
<v-app-bar
|
|
11
|
-
:order="-10"
|
|
12
11
|
v-if="availableInterfaces.indexOf('topBar') > -1"
|
|
13
12
|
id="appAppbar"
|
|
14
13
|
ref="appAppbar"
|
|
14
|
+
:order="-10"
|
|
15
15
|
color="topbar-bg"
|
|
16
16
|
density="compact"
|
|
17
17
|
flat
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
}"
|
|
32
32
|
>
|
|
33
33
|
<img
|
|
34
|
-
class="tw:
|
|
34
|
+
class="tw:ml-2 tw:h-[40px] tw:cursor-pointer"
|
|
35
35
|
src="/home-icon.png"
|
|
36
36
|
/>
|
|
37
37
|
</RouterLink>
|
|
@@ -57,17 +57,17 @@
|
|
|
57
57
|
<snackbarStack />
|
|
58
58
|
<!-- Navigation Drawer -->
|
|
59
59
|
<PageNavigation
|
|
60
|
-
:key="locale"
|
|
61
60
|
v-if="availableInterfaces.indexOf('pageNavigation') > -1"
|
|
61
|
+
:key="locale"
|
|
62
62
|
/>
|
|
63
63
|
|
|
64
64
|
<!-- Main content -->
|
|
65
65
|
<v-main
|
|
66
|
-
class="tw:flex tw:flex-col tw:min-h-0 tw:overflow-y-auto tw:h-screen"
|
|
67
66
|
id="appMainContent"
|
|
67
|
+
class="tw:flex tw:h-screen tw:min-h-0 tw:flex-col tw:overflow-y-auto"
|
|
68
68
|
:aria-busy="loading"
|
|
69
69
|
>
|
|
70
|
-
<div class="tw:
|
|
70
|
+
<div class="tw:h-1 tw:top-[49px] tw:w-full">
|
|
71
71
|
<v-progress-linear
|
|
72
72
|
:height="2"
|
|
73
73
|
:indeterminate="loading"
|
|
@@ -78,26 +78,24 @@
|
|
|
78
78
|
/>
|
|
79
79
|
</div>
|
|
80
80
|
|
|
81
|
-
<span
|
|
82
|
-
v-if="loading"
|
|
83
|
-
class="tw:sr-only"
|
|
84
|
-
role="status"
|
|
85
|
-
aria-live="polite"
|
|
86
|
-
>
|
|
81
|
+
<span v-if="loading" class="tw:sr-only" role="status" aria-live="polite">
|
|
87
82
|
{{ $t("core.app_loading") }}
|
|
88
83
|
</span>
|
|
89
84
|
|
|
90
|
-
<component v-if="showWelcomeScreen"
|
|
85
|
+
<component :is="welcomeScreenComponent" v-if="showWelcomeScreen" />
|
|
91
86
|
|
|
92
87
|
<template v-else>
|
|
93
88
|
<WidgetsLayoutLoader
|
|
94
89
|
v-if="widgetsLayout"
|
|
90
|
+
:key="pageLoaderKey"
|
|
91
|
+
track-readiness
|
|
95
92
|
class="tw:min-h-0 tw:pb-0"
|
|
96
|
-
:class="{ '
|
|
93
|
+
:class="{ 'tw:!invisible': loading }"
|
|
97
94
|
:aria-hidden="loading"
|
|
98
95
|
:widgets-layout="widgetsLayout"
|
|
99
96
|
slot-type="page"
|
|
100
97
|
:slot-layout="slotLayout"
|
|
98
|
+
@all-widgets-ready="onPageWidgetsReady"
|
|
101
99
|
/>
|
|
102
100
|
|
|
103
101
|
<v-empty-state
|
|
@@ -107,33 +105,33 @@
|
|
|
107
105
|
:text="$t('core.error.page_not_foundSubtitle')"
|
|
108
106
|
image="/Logo_Main_V1.png"
|
|
109
107
|
>
|
|
110
|
-
<v-btn :to="{ path: '/' }">
|
|
111
|
-
$t("core.error.return_to_home")
|
|
112
|
-
|
|
108
|
+
<v-btn :to="{ path: '/' }">
|
|
109
|
+
{{ $t("core.error.return_to_home") }}
|
|
110
|
+
</v-btn>
|
|
113
111
|
</v-empty-state>
|
|
114
112
|
</template>
|
|
115
113
|
|
|
116
114
|
<v-footer
|
|
115
|
+
v-if="configService.getPleaseLetUsAdvertiseOurselves()"
|
|
116
|
+
ref="appFooter"
|
|
117
117
|
:order="-1"
|
|
118
118
|
color="footer-bg"
|
|
119
|
-
ref="appFooter"
|
|
120
119
|
border-thin
|
|
121
|
-
|
|
122
|
-
class="tw:items-center tw:text-center tw:flex tw:flex-col tw:border-t tw:grow-0! tw:mt-auto"
|
|
120
|
+
class="tw:mt-auto tw:flex tw:grow-0! tw:flex-col tw:items-center tw:border-t tw:text-center"
|
|
123
121
|
>
|
|
124
122
|
<a
|
|
125
123
|
href="https://www.raclettejs.com"
|
|
126
124
|
rel="noopener noreferrer"
|
|
127
125
|
target="_blank"
|
|
128
126
|
title="raclette"
|
|
129
|
-
class="tw:text-[0.9rem] tw:text-neutral-500 tw:no-underline
|
|
127
|
+
class="tw:hover:no-underline tw:hover:text-neutral-700 tw:text-[0.9rem] tw:text-neutral-500 tw:no-underline tw:transition-colors"
|
|
130
128
|
>
|
|
131
129
|
Cooked with <span class="raclette">raclette 🧀</span></a
|
|
132
130
|
>
|
|
133
131
|
<span
|
|
134
132
|
v-if="configService.getAdvertiseYourself().length"
|
|
133
|
+
class="footerLink tw:hover:no-underline tw:hover:text-neutral-700 tw:text-[0.9rem] tw:text-neutral-500 tw:no-underline tw:transition-colors"
|
|
135
134
|
v-html="configService.getAdvertiseYourself()"
|
|
136
|
-
class="footerLink tw:text-[0.9rem] tw:text-neutral-500 tw:no-underline hover:tw:no-underline hover:tw:text-neutral-700 tw:transition-colors"
|
|
137
135
|
></span>
|
|
138
136
|
</v-footer>
|
|
139
137
|
</v-main>
|
|
@@ -172,6 +170,7 @@ import { useCurrentComposition } from "./composables"
|
|
|
172
170
|
import WidgetsLayoutLoader from "./components/composition/WidgetsLayoutLoader.vue"
|
|
173
171
|
import AppBarMenu from "./components/menu/AppBarMenu.vue"
|
|
174
172
|
import { useWindowSize } from "@vueuse/core"
|
|
173
|
+
import { useAppLoadingCoordinator } from "./composables/useAppLoadingCoordinator"
|
|
175
174
|
const { height: windowHeight } = useWindowSize()
|
|
176
175
|
|
|
177
176
|
useTitle(`raclette - ${configService.getApplicationTitle()}`)
|
|
@@ -183,7 +182,9 @@ const { loading, theme, locale, landingPage, availableInterfaces } =
|
|
|
183
182
|
useUiState()
|
|
184
183
|
useRouteState()
|
|
185
184
|
const { isDraggingOrResizingAnything } = useDragAndDrop()
|
|
186
|
-
const { widgetsLayout, slotLayout } = useCurrentComposition()
|
|
185
|
+
const { widgetsLayout, slotLayout, id: compositionId } = useCurrentComposition()
|
|
186
|
+
const { pageLoaderKey, onPageWidgetsReady, markPageReadyWithoutWidgets } =
|
|
187
|
+
useAppLoadingCoordinator(compositionId)
|
|
187
188
|
|
|
188
189
|
const appAppbarRef = useTemplateRef("appAppbar")
|
|
189
190
|
const appFooterRef = useTemplateRef("appFooter")
|
|
@@ -238,6 +239,16 @@ const welcomeScreenComponent = computed(() => {
|
|
|
238
239
|
return showWelcomeScreen.value ? WelcomeScreenComponent : null
|
|
239
240
|
})
|
|
240
241
|
|
|
242
|
+
watch(
|
|
243
|
+
[showWelcomeScreen, widgetsLayout],
|
|
244
|
+
([welcome, layout]) => {
|
|
245
|
+
if (welcome || !layout) {
|
|
246
|
+
markPageReadyWithoutWidgets()
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
{ immediate: true },
|
|
250
|
+
)
|
|
251
|
+
|
|
241
252
|
const logoExists = ref(false)
|
|
242
253
|
const logoSrc = ref("/logo.png")
|
|
243
254
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
|
-
|
|
3
|
+
:[WIDGET_CHUNK_LOADING_ATTR]="true"
|
|
4
|
+
class="tw:flex tw:min-h-[120px] tw:items-center tw:justify-center tw:p-6"
|
|
4
5
|
>
|
|
5
6
|
<div class="tw:flex tw:flex-col tw:items-center tw:gap-3">
|
|
6
|
-
<!-- Subtle pulsing dot animation -->
|
|
7
7
|
<div class="tw:flex tw:gap-1">
|
|
8
8
|
<div
|
|
9
9
|
v-for="i in 3"
|
|
10
10
|
:key="i"
|
|
11
|
-
class="tw:
|
|
11
|
+
class="tw:size-2 tw:animate-pulse tw:rounded-full tw:bg-gray-400"
|
|
12
12
|
:style="{ 'animation-delay': `${(i - 1) * 0.2}s` }"
|
|
13
13
|
/>
|
|
14
14
|
</div>
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
</div>
|
|
17
17
|
</template>
|
|
18
18
|
|
|
19
|
+
<script setup lang="ts">
|
|
20
|
+
import { WIDGET_CHUNK_LOADING_ATTR } from "@racletteOrchestrator/constants/widgetSlot"
|
|
21
|
+
</script>
|
|
22
|
+
|
|
19
23
|
<style scoped>
|
|
20
24
|
@keyframes pulse {
|
|
21
25
|
0%,
|