@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.
Files changed (38) hide show
  1. package/CHANGELOG.md +36 -1
  2. package/dist/cli.js +104 -104
  3. package/dist/cli.js.map +4 -4
  4. package/dist/index.js.map +2 -2
  5. package/package.json +2 -2
  6. package/services/frontend/eslint.config.js +31 -9
  7. package/services/frontend/src/core/setup/socketBootstrap.ts +4 -3
  8. package/services/frontend/src/core/store/state.ts +2 -7
  9. package/services/frontend/src/core/store/types/index.ts +2 -0
  10. package/services/frontend/src/orchestrator/ProductOrchestrator.vue +33 -22
  11. package/services/frontend/src/orchestrator/components/LoadingWidgetState.vue +7 -3
  12. package/services/frontend/src/orchestrator/components/composition/WidgetsLayoutLoader.vue +179 -195
  13. package/services/frontend/src/orchestrator/components/dataExport/DataExporter.vue +79 -24
  14. package/services/frontend/src/orchestrator/components/dataTable/BaseDataTable.vue +27 -14
  15. package/services/frontend/src/orchestrator/components/dataTable/BaseDataTableGroupSelectCheckbox.vue +42 -0
  16. package/services/frontend/src/orchestrator/components/index.ts +2 -0
  17. package/services/frontend/src/orchestrator/components/menu/ServerStatus.vue +2 -4
  18. package/services/frontend/src/orchestrator/components/menu/UserMenu.vue +1 -1
  19. package/services/frontend/src/orchestrator/components/welcomeScreen/AdminWelcomeScreen.vue +1 -1
  20. package/services/frontend/src/orchestrator/composables/index.ts +1 -0
  21. package/services/frontend/src/orchestrator/composables/useAppLoadingCoordinator.ts +77 -0
  22. package/services/frontend/src/orchestrator/composables/usePageNavigation.ts +1 -1
  23. package/services/frontend/src/orchestrator/composables/useUiState.ts +2 -0
  24. package/services/frontend/src/orchestrator/composables/useWidgetLifecycle.ts +65 -76
  25. package/services/frontend/src/orchestrator/composables/useWidgets/helperFunctions.ts +7 -4
  26. package/services/frontend/src/orchestrator/constants/index.ts +2 -0
  27. package/services/frontend/src/orchestrator/constants/widgetSlot.ts +7 -0
  28. package/services/frontend/src/orchestrator/exports.ts +1 -0
  29. package/services/frontend/src/orchestrator/helpers/staticWidgetRegistry.ts +9 -44
  30. package/services/frontend/src/orchestrator/helpers/widgetLayoutHelper.ts +37 -0
  31. package/services/frontend/src/orchestrator/helpers/widgetLoader.ts +0 -1
  32. package/services/frontend/src/orchestrator/helpers/widgetSlotHelper.ts +20 -0
  33. package/services/frontend/src/orchestrator/helpers/widgetVisuals.ts +11 -12
  34. package/services/frontend/src/orchestrator/helpers/wrapWidgetWithLifecycle.ts +32 -0
  35. package/services/frontend/src/orchestrator/router/routerHooks/afterEach.ts +4 -29
  36. package/services/frontend/src/orchestrator/router/routerHooks/beforeEach.ts +4 -1
  37. package/services/frontend/src/orchestrator/types/Widgets.ts +12 -6
  38. 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
- // Better-Tailwindcss: Tailwind v4 + `tw:` prefix is auto-detected from the
22
- // CSS entryPoint. Scope to files that may contain class strings.
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": "warn",
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, emits ui/setupProject
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: { loading: false },
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 { loadingHold, applicationState, configPatterns, storePatterns }
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:cursor-pointer tw:h-[40px] tw:ml-2"
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:w-full tw-h-1 tw:top-[49px]">
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" :is="welcomeScreenComponent" />
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="{ '!tw:invisible': loading }"
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
- }}</v-btn>
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
- v-if="configService.getPleaseLetUsAdvertiseOurselves()"
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 hover:tw:no-underline hover:tw:text-neutral-700 tw:transition-colors"
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
- class="tw:flex tw:items-center tw:justify-center tw:p-6 tw:min-h-[120px]"
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:w-2 tw:h-2 tw:bg-gray-400 tw:rounded-full tw:animate-pulse"
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%,