@quazardous/qdadm 1.19.3
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/LICENSE +21 -0
- package/README.md +111 -0
- package/package.json +101 -0
- package/src/assets/logo.svg +6 -0
- package/src/auth/SessionAuthAdapter.ts +459 -0
- package/src/auth/index.ts +15 -0
- package/src/chain/ActiveStack.ts +154 -0
- package/src/chain/StackHydrator.ts +292 -0
- package/src/chain/index.ts +25 -0
- package/src/chain/useActiveStack.ts +120 -0
- package/src/chain/useStackHydrator.ts +155 -0
- package/src/components/BoolCell.vue +34 -0
- package/src/components/InfoBanner.vue +73 -0
- package/src/components/QdadmRoot.vue +52 -0
- package/src/components/SeverityTag.vue +86 -0
- package/src/components/dialogs/BulkStatusDialog.vue +60 -0
- package/src/components/dialogs/MultiStepDialog.vue +331 -0
- package/src/components/dialogs/SimpleDialog.vue +122 -0
- package/src/components/dialogs/UnsavedChangesDialog.vue +80 -0
- package/src/components/display/BannerZone.vue +65 -0
- package/src/components/display/CardsGrid.vue +72 -0
- package/src/components/display/CopyableId.vue +84 -0
- package/src/components/display/EmptyState.vue +62 -0
- package/src/components/display/IntensityBar.vue +190 -0
- package/src/components/display/RichCardsGrid.vue +101 -0
- package/src/components/edit/FormActions.vue +78 -0
- package/src/components/edit/FormField.vue +82 -0
- package/src/components/edit/FormInput.vue +158 -0
- package/src/components/edit/FormPage.vue +260 -0
- package/src/components/edit/FormTab.vue +217 -0
- package/src/components/edit/FormTabs.vue +120 -0
- package/src/components/edit/LookupField.vue +406 -0
- package/src/components/edit/LookupPickerDialog.vue +230 -0
- package/src/components/editors/JsonEditorFoldable.vue +470 -0
- package/src/components/editors/JsonStructuredField.vue +264 -0
- package/src/components/editors/JsonViewer.vue +51 -0
- package/src/components/editors/KeyValueEditor.vue +314 -0
- package/src/components/editors/LanguageEditor.vue +259 -0
- package/src/components/editors/PermissionEditor.vue +558 -0
- package/src/components/editors/ScopeEditor.vue +372 -0
- package/src/components/editors/VanillaJsonEditor.vue +183 -0
- package/src/components/index.ts +83 -0
- package/src/components/item/FieldGroups.vue +392 -0
- package/src/components/layout/AppLayout.vue +1032 -0
- package/src/components/layout/BaseLayout.vue +190 -0
- package/src/components/layout/Breadcrumb.vue +55 -0
- package/src/components/layout/DashboardLayout.vue +104 -0
- package/src/components/layout/FormLayout.vue +256 -0
- package/src/components/layout/ListLayout.vue +278 -0
- package/src/components/layout/PageHeader.vue +156 -0
- package/src/components/layout/PageLayout.vue +126 -0
- package/src/components/layout/PageNav.vue +192 -0
- package/src/components/layout/SidebarBox.vue +87 -0
- package/src/components/layout/Zone.vue +182 -0
- package/src/components/layout/defaults/DefaultBreadcrumb.vue +149 -0
- package/src/components/layout/defaults/DefaultFooter.vue +89 -0
- package/src/components/layout/defaults/DefaultFormActions.vue +67 -0
- package/src/components/layout/defaults/DefaultHeader.vue +78 -0
- package/src/components/layout/defaults/DefaultMenu.vue +188 -0
- package/src/components/layout/defaults/DefaultPagination.vue +75 -0
- package/src/components/layout/defaults/DefaultTable.vue +137 -0
- package/src/components/layout/defaults/DefaultUserInfo.vue +111 -0
- package/src/components/layout/defaults/index.ts +16 -0
- package/src/components/lists/ActionButtons.vue +49 -0
- package/src/components/lists/ActionColumn.vue +40 -0
- package/src/components/lists/FilterBar.vue +54 -0
- package/src/components/lists/ListPage.vue +467 -0
- package/src/components/pages/LoginPage.vue +334 -0
- package/src/components/pages/NotFoundPage.vue +98 -0
- package/src/components/show/ShowDisplay.vue +343 -0
- package/src/components/show/ShowField.vue +67 -0
- package/src/components/show/ShowPage.vue +431 -0
- package/src/composables/index.ts +155 -0
- package/src/composables/useApp.ts +67 -0
- package/src/composables/useAuth.ts +98 -0
- package/src/composables/useBareForm.ts +319 -0
- package/src/composables/useBreadcrumb.ts +252 -0
- package/src/composables/useChildPage.ts +113 -0
- package/src/composables/useCurrentEntity.ts +60 -0
- package/src/composables/useDeferred.ts +94 -0
- package/src/composables/useDirtyState.ts +138 -0
- package/src/composables/useEntityItemFormPage.ts +942 -0
- package/src/composables/useEntityItemFormPage.types.ts +451 -0
- package/src/composables/useEntityItemPage.ts +438 -0
- package/src/composables/useEntityItemShowPage.ts +722 -0
- package/src/composables/useFieldManager.ts +692 -0
- package/src/composables/useForm.ts +544 -0
- package/src/composables/useGuardStore.ts +53 -0
- package/src/composables/useHooks.ts +56 -0
- package/src/composables/useInfoBanner.ts +217 -0
- package/src/composables/useJsonSyntax.ts +122 -0
- package/src/composables/useLayoutResolver.ts +329 -0
- package/src/composables/useListPage.ts +1535 -0
- package/src/composables/useListPage.types.ts +437 -0
- package/src/composables/useListPage.utils.ts +101 -0
- package/src/composables/useNavContext.ts +439 -0
- package/src/composables/useNavigation.ts +216 -0
- package/src/composables/useOptionsLookup.ts +323 -0
- package/src/composables/usePageTitle.ts +84 -0
- package/src/composables/useSSEBridge.ts +142 -0
- package/src/composables/useSemanticBreadcrumb.ts +222 -0
- package/src/composables/useSidebarState.ts +24 -0
- package/src/composables/useSignals.ts +52 -0
- package/src/composables/useUnsavedChangesGuard.ts +155 -0
- package/src/composables/useUserImpersonator.ts +317 -0
- package/src/composables/useZoneRegistry.ts +252 -0
- package/src/core/bundles.ts +558 -0
- package/src/core/decorator.ts +420 -0
- package/src/core/extension.ts +470 -0
- package/src/core/index.ts +59 -0
- package/src/deferred/DeferredRegistry.ts +353 -0
- package/src/deferred/index.ts +15 -0
- package/src/editors/index.ts +12 -0
- package/src/entity/EntityManager.cache.ts +530 -0
- package/src/entity/EntityManager.crud.ts +615 -0
- package/src/entity/EntityManager.interface.ts +78 -0
- package/src/entity/EntityManager.query.ts +221 -0
- package/src/entity/EntityManager.relations.ts +151 -0
- package/src/entity/EntityManager.ts +944 -0
- package/src/entity/EntityManager.types.ts +288 -0
- package/src/entity/auth/CompositeAuthAdapter.ts +194 -0
- package/src/entity/auth/EntityAuthAdapter.ts +119 -0
- package/src/entity/auth/PermissiveAdapter.ts +43 -0
- package/src/entity/auth/RoleHierarchy.ts +128 -0
- package/src/entity/auth/SecurityChecker.ts +202 -0
- package/src/entity/auth/factory.ts +203 -0
- package/src/entity/auth/index.ts +47 -0
- package/src/entity/factory.test.js +189 -0
- package/src/entity/factory.ts +146 -0
- package/src/entity/index.ts +49 -0
- package/src/entity/storage/ApiStorage.ts +228 -0
- package/src/entity/storage/IStorage.ts +100 -0
- package/src/entity/storage/LocalStorage.ts +200 -0
- package/src/entity/storage/MemoryStorage.ts +184 -0
- package/src/entity/storage/MockApiStorage.ts +318 -0
- package/src/entity/storage/SdkStorage.ts +377 -0
- package/src/entity/storage/factory.test.js +159 -0
- package/src/entity/storage/factory.ts +209 -0
- package/src/entity/storage/index.ts +75 -0
- package/src/gen/FieldMapper.ts +122 -0
- package/src/gen/StorageProfileFactory.ts +93 -0
- package/src/gen/connectors/BaseConnector.ts +150 -0
- package/src/gen/connectors/ManualConnector.test.js +499 -0
- package/src/gen/connectors/ManualConnector.ts +414 -0
- package/src/gen/connectors/OpenAPIConnector.test.js +836 -0
- package/src/gen/connectors/OpenAPIConnector.ts +693 -0
- package/src/gen/connectors/__fixtures__/sample-openapi.json +311 -0
- package/src/gen/connectors/index.ts +27 -0
- package/src/gen/createManagers.ts +245 -0
- package/src/gen/decorators.ts +134 -0
- package/src/gen/generateManagers.test.js +540 -0
- package/src/gen/generateManagers.ts +398 -0
- package/src/gen/index.ts +66 -0
- package/src/gen/schema.ts +248 -0
- package/src/gen/vite-plugin.ts +114 -0
- package/src/generated/managers/testManager.js +45 -0
- package/src/generated/managers/testManager.ts +46 -0
- package/src/hooks/HookRegistry.ts +16 -0
- package/src/hooks/index.ts +15 -0
- package/src/i18n/I18n.ts +344 -0
- package/src/i18n/IncrementalDomainProvider.ts +153 -0
- package/src/i18n/InlineTranslationProvider.ts +4 -0
- package/src/i18n/LazyTranslationProvider.ts +102 -0
- package/src/i18n/MessagesRegistry.ts +4 -0
- package/src/i18n/Resolver.ts +4 -0
- package/src/i18n/__tests__/I18n.test.ts +169 -0
- package/src/i18n/__tests__/IncrementalDomainProvider.test.ts +146 -0
- package/src/i18n/__tests__/LazyTranslationProvider.test.ts +100 -0
- package/src/i18n/__tests__/Resolver.test.ts +271 -0
- package/src/i18n/defaults/DefaultCoreProvider.ts +28 -0
- package/src/i18n/defaults/core.en.yml +55 -0
- package/src/i18n/defaults/core.fr.yml +55 -0
- package/src/i18n/index.ts +55 -0
- package/src/i18n/loaders/raw-modules.d.ts +15 -0
- package/src/i18n/loaders/yaml.ts +35 -0
- package/src/i18n/strategies.ts +4 -0
- package/src/i18n/types.ts +34 -0
- package/src/i18n/useI18n.ts +34 -0
- package/src/index.ts +453 -0
- package/src/kernel/EventRouter.ts +23 -0
- package/src/kernel/Kernel.api.ts +79 -0
- package/src/kernel/Kernel.auth.ts +120 -0
- package/src/kernel/Kernel.i18n.ts +29 -0
- package/src/kernel/Kernel.modules.ts +170 -0
- package/src/kernel/Kernel.registries.ts +237 -0
- package/src/kernel/Kernel.routing.ts +334 -0
- package/src/kernel/Kernel.ts +460 -0
- package/src/kernel/Kernel.types.ts +263 -0
- package/src/kernel/Kernel.vue.ts +304 -0
- package/src/kernel/KernelContext.entities.ts +80 -0
- package/src/kernel/KernelContext.events.ts +57 -0
- package/src/kernel/KernelContext.i18n.ts +37 -0
- package/src/kernel/KernelContext.permissions.ts +38 -0
- package/src/kernel/KernelContext.routing.ts +280 -0
- package/src/kernel/KernelContext.ts +252 -0
- package/src/kernel/KernelContext.types.ts +173 -0
- package/src/kernel/KernelContext.zones.ts +54 -0
- package/src/kernel/Module.ts +169 -0
- package/src/kernel/ModuleLoader.ts +645 -0
- package/src/kernel/SSEBridge.ts +11 -0
- package/src/kernel/SignalBus.ts +48 -0
- package/src/kernel/index.ts +56 -0
- package/src/module/index.ts +33 -0
- package/src/module/moduleRegistry.ts +492 -0
- package/src/modules/debug/AuthCollector.ts +501 -0
- package/src/modules/debug/Collector.ts +20 -0
- package/src/modules/debug/DebugBridge.ts +14 -0
- package/src/modules/debug/DebugModule.ts +220 -0
- package/src/modules/debug/EntitiesCollector.ts +839 -0
- package/src/modules/debug/ErrorCollector.ts +4 -0
- package/src/modules/debug/I18nCollector.ts +9 -0
- package/src/modules/debug/LocalStorageAdapter.ts +7 -0
- package/src/modules/debug/RouterCollector.ts +449 -0
- package/src/modules/debug/SignalCollector.ts +4 -0
- package/src/modules/debug/ToastCollector.ts +4 -0
- package/src/modules/debug/ZonesCollector.ts +447 -0
- package/src/modules/debug/components/DebugBar.vue +31 -0
- package/src/modules/debug/components/adminPanelsConfig.ts +42 -0
- package/src/modules/debug/components/index.ts +9 -0
- package/src/modules/debug/components/panels/AuthPanel.vue +267 -0
- package/src/modules/debug/components/panels/EntitiesPanel.vue +572 -0
- package/src/modules/debug/components/panels/I18nPanel.vue +738 -0
- package/src/modules/debug/components/panels/RouterPanel.vue +440 -0
- package/src/modules/debug/components/panels/ZonesPanel.vue +133 -0
- package/src/modules/debug/components/panels/index.ts +16 -0
- package/src/modules/debug/index.ts +58 -0
- package/src/modules/debug/styles.scss +2671 -0
- package/src/notifications/NotificationBadge.vue +35 -0
- package/src/notifications/NotificationListener.vue +71 -0
- package/src/notifications/NotificationModule.ts +76 -0
- package/src/notifications/NotificationPanel.vue +478 -0
- package/src/notifications/NotificationStore.ts +288 -0
- package/src/notifications/index.ts +42 -0
- package/src/notifications/styles.scss +32 -0
- package/src/orchestrator/Orchestrator.ts +427 -0
- package/src/orchestrator/index.ts +8 -0
- package/src/orchestrator/useOrchestrator.ts +80 -0
- package/src/plugin.ts +251 -0
- package/src/query/FilterQuery.ts +306 -0
- package/src/query/QueryExecutor.ts +341 -0
- package/src/query/index.ts +16 -0
- package/src/security/EntityRolesProvider.ts +385 -0
- package/src/security/PermissionMatcher.ts +152 -0
- package/src/security/PermissionRegistry.ts +273 -0
- package/src/security/PersistableRolesProvider.ts +584 -0
- package/src/security/RolesManager.ts +136 -0
- package/src/security/RolesProvider.ts +167 -0
- package/src/security/RolesProviderStorage.ts +211 -0
- package/src/security/SecurityModule.ts +94 -0
- package/src/security/StaticRolesProvider.ts +117 -0
- package/src/security/UsersManager.ts +163 -0
- package/src/security/index.ts +77 -0
- package/src/security/pages/RoleForm.vue +276 -0
- package/src/security/pages/RoleList.vue +123 -0
- package/src/styles/_alerts.scss +95 -0
- package/src/styles/_buttons.scss +391 -0
- package/src/styles/_cards.scss +241 -0
- package/src/styles/_code.scss +78 -0
- package/src/styles/_dialogs.scss +55 -0
- package/src/styles/_filter-bar.scss +70 -0
- package/src/styles/_forms.scss +434 -0
- package/src/styles/_helpers.scss +153 -0
- package/src/styles/_lists.scss +225 -0
- package/src/styles/_main.scss +356 -0
- package/src/styles/_markdown.scss +88 -0
- package/src/styles/_responsive.scss +255 -0
- package/src/styles/_show-pages.scss +186 -0
- package/src/styles/_sidebar.scss +380 -0
- package/src/styles/_states.scss +115 -0
- package/src/styles/_stats.scss +47 -0
- package/src/styles/_variables.scss +224 -0
- package/src/styles/index.scss +38 -0
- package/src/styles/theme/_components.scss +331 -0
- package/src/styles/theme/_index.scss +10 -0
- package/src/styles/theme/_tokens.scss +147 -0
- package/src/styles/theme/_utilities.scss +175 -0
- package/src/toast/ToastBridgeModule.ts +68 -0
- package/src/toast/ToastListener.vue +55 -0
- package/src/toast/index.ts +26 -0
- package/src/toast/useSignalToast.ts +154 -0
- package/src/types/index.ts +285 -0
- package/src/types/pluralize.d.ts +19 -0
- package/src/types/quarkernel.d.ts +50 -0
- package/src/utils/formatters.ts +174 -0
- package/src/utils/index.ts +35 -0
- package/src/utils/transformers.ts +131 -0
- package/src/vite/qdadmDebugPlugin.ts +401 -0
- package/src/vite-env.d.ts +50 -0
- package/src/zones/ZoneRegistry.ts +934 -0
- package/src/zones/index.ts +29 -0
- package/src/zones/zones.ts +191 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useInfoBanner - Programmatic banner management
|
|
3
|
+
*
|
|
4
|
+
* Manages persistent info banners that appear at the top of page content.
|
|
5
|
+
* Unlike toasts, banners stay visible until explicitly dismissed.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* // In a component
|
|
9
|
+
* const { showBanner, hideBanner, clearBanners } = useInfoBanner()
|
|
10
|
+
*
|
|
11
|
+
* // Show a warning banner
|
|
12
|
+
* showBanner({
|
|
13
|
+
* id: 'unsaved',
|
|
14
|
+
* severity: 'warn',
|
|
15
|
+
* message: 'You have unsaved changes',
|
|
16
|
+
* closable: true
|
|
17
|
+
* })
|
|
18
|
+
*
|
|
19
|
+
* // Hide a specific banner
|
|
20
|
+
* hideBanner('unsaved')
|
|
21
|
+
*
|
|
22
|
+
* // Clear all banners
|
|
23
|
+
* clearBanners()
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* // With custom icon
|
|
27
|
+
* showBanner({
|
|
28
|
+
* id: 'offline',
|
|
29
|
+
* severity: 'error',
|
|
30
|
+
* icon: 'pi-wifi',
|
|
31
|
+
* message: 'You are offline. Changes will be saved when connection is restored.'
|
|
32
|
+
* })
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* // In layout - get banners to render
|
|
36
|
+
* const { banners } = useInfoBanner()
|
|
37
|
+
*
|
|
38
|
+
* <InfoBanner
|
|
39
|
+
* v-for="banner in banners"
|
|
40
|
+
* :key="banner.id"
|
|
41
|
+
* :severity="banner.severity"
|
|
42
|
+
* :icon="banner.icon"
|
|
43
|
+
* :closable="banner.closable"
|
|
44
|
+
* @close="hideBanner(banner.id)"
|
|
45
|
+
* >
|
|
46
|
+
* {{ banner.message }}
|
|
47
|
+
* </InfoBanner>
|
|
48
|
+
*/
|
|
49
|
+
import { inject, provide, reactive, computed, type ComputedRef, type InjectionKey } from 'vue'
|
|
50
|
+
|
|
51
|
+
const BANNER_KEY: InjectionKey<BannerStore> = Symbol('qdadm-banners')
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Banner severity types
|
|
55
|
+
*/
|
|
56
|
+
export type BannerSeverity = 'info' | 'success' | 'warn' | 'error'
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Banner configuration options
|
|
60
|
+
*/
|
|
61
|
+
export interface BannerOptions {
|
|
62
|
+
/** Unique identifier (replaces existing with same id) */
|
|
63
|
+
id?: string
|
|
64
|
+
/** Banner severity: info, success, warn, error */
|
|
65
|
+
severity?: BannerSeverity
|
|
66
|
+
/** Banner message (can include HTML) */
|
|
67
|
+
message?: string
|
|
68
|
+
/** Custom icon or false to hide */
|
|
69
|
+
icon?: string | boolean
|
|
70
|
+
/** Allow user to dismiss (default: true) */
|
|
71
|
+
closable?: boolean
|
|
72
|
+
/** Higher priority banners appear first */
|
|
73
|
+
priority?: number
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Internal banner state
|
|
78
|
+
*/
|
|
79
|
+
export interface Banner {
|
|
80
|
+
id: string
|
|
81
|
+
severity: BannerSeverity
|
|
82
|
+
message: string
|
|
83
|
+
icon?: string | boolean
|
|
84
|
+
closable: boolean
|
|
85
|
+
priority: number
|
|
86
|
+
createdAt: number
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Banner store state
|
|
91
|
+
*/
|
|
92
|
+
interface BannerState {
|
|
93
|
+
banners: Banner[]
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Banner store interface
|
|
98
|
+
*/
|
|
99
|
+
export interface BannerStore {
|
|
100
|
+
banners: ComputedRef<Banner[]>
|
|
101
|
+
showBanner: (options: BannerOptions) => string
|
|
102
|
+
hideBanner: (id: string) => void
|
|
103
|
+
clearBanners: () => void
|
|
104
|
+
hasBanner: (id: string) => boolean
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Create the banner store (call once in app root)
|
|
109
|
+
*/
|
|
110
|
+
export function createBannerStore(): BannerStore {
|
|
111
|
+
const state = reactive<BannerState>({
|
|
112
|
+
banners: [],
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Show a banner
|
|
117
|
+
* @param options - Banner options
|
|
118
|
+
*/
|
|
119
|
+
function showBanner(options: BannerOptions): string {
|
|
120
|
+
const banner: Banner = {
|
|
121
|
+
id: options.id || `banner-${Date.now()}`,
|
|
122
|
+
severity: options.severity || 'info',
|
|
123
|
+
message: options.message || '',
|
|
124
|
+
icon: options.icon,
|
|
125
|
+
closable: options.closable !== false,
|
|
126
|
+
priority: options.priority || 0,
|
|
127
|
+
createdAt: Date.now(),
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Remove existing banner with same id
|
|
131
|
+
const existingIndex = state.banners.findIndex((b) => b.id === banner.id)
|
|
132
|
+
if (existingIndex !== -1) {
|
|
133
|
+
state.banners.splice(existingIndex, 1)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Insert by priority (higher first)
|
|
137
|
+
const insertIndex = state.banners.findIndex((b) => b.priority < banner.priority)
|
|
138
|
+
if (insertIndex === -1) {
|
|
139
|
+
state.banners.push(banner)
|
|
140
|
+
} else {
|
|
141
|
+
state.banners.splice(insertIndex, 0, banner)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return banner.id
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Hide a specific banner
|
|
149
|
+
* @param id - Banner id to remove
|
|
150
|
+
*/
|
|
151
|
+
function hideBanner(id: string): void {
|
|
152
|
+
const index = state.banners.findIndex((b) => b.id === id)
|
|
153
|
+
if (index !== -1) {
|
|
154
|
+
state.banners.splice(index, 1)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Clear all banners
|
|
160
|
+
*/
|
|
161
|
+
function clearBanners(): void {
|
|
162
|
+
state.banners.length = 0
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Check if a banner exists
|
|
167
|
+
* @param id - Banner id
|
|
168
|
+
* @returns True if banner exists
|
|
169
|
+
*/
|
|
170
|
+
function hasBanner(id: string): boolean {
|
|
171
|
+
return state.banners.some((b) => b.id === id)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const banners = computed(() => state.banners)
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
banners,
|
|
178
|
+
showBanner,
|
|
179
|
+
hideBanner,
|
|
180
|
+
clearBanners,
|
|
181
|
+
hasBanner,
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Provide the banner store to descendant components
|
|
187
|
+
* Call this in your app root or layout component
|
|
188
|
+
*/
|
|
189
|
+
export function provideBannerStore(): BannerStore {
|
|
190
|
+
const store = createBannerStore()
|
|
191
|
+
provide(BANNER_KEY, store)
|
|
192
|
+
return store
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Use the banner system in a component
|
|
197
|
+
* @returns Banner API
|
|
198
|
+
*/
|
|
199
|
+
export function useInfoBanner(): BannerStore {
|
|
200
|
+
const store = inject(BANNER_KEY)
|
|
201
|
+
|
|
202
|
+
if (!store) {
|
|
203
|
+
console.warn(
|
|
204
|
+
'[qdadm] useInfoBanner: banner store not provided. Call provideBannerStore() in app root.'
|
|
205
|
+
)
|
|
206
|
+
// Return a no-op store to prevent crashes
|
|
207
|
+
return {
|
|
208
|
+
banners: computed(() => []),
|
|
209
|
+
showBanner: () => '',
|
|
210
|
+
hideBanner: () => {},
|
|
211
|
+
clearBanners: () => {},
|
|
212
|
+
hasBanner: () => false,
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return store
|
|
217
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useJsonSyntax - Shared JSON syntax highlighting utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides consistent JSON formatting and syntax highlighting
|
|
5
|
+
* across JsonEditor, JsonViewer, and JsonEditorFoldable components.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* JSON value type enumeration
|
|
10
|
+
*/
|
|
11
|
+
export type JsonValueType = 'string' | 'number' | 'boolean' | 'null' | 'array' | 'object'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Result of safe JSON parsing
|
|
15
|
+
*/
|
|
16
|
+
export interface SafeJsonParseResult<T = unknown> {
|
|
17
|
+
value: T | null
|
|
18
|
+
error: string | null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Apply syntax highlighting to JSON string
|
|
23
|
+
* @param jsonText - Raw JSON text
|
|
24
|
+
* @returns HTML with syntax highlighting spans
|
|
25
|
+
*/
|
|
26
|
+
export function highlightJson(jsonText: string): string {
|
|
27
|
+
if (!jsonText) {
|
|
28
|
+
return ''
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
jsonText
|
|
33
|
+
.replace(/&/g, '&')
|
|
34
|
+
.replace(/</g, '<')
|
|
35
|
+
.replace(/>/g, '>')
|
|
36
|
+
// Strings (values only - after colon or in arrays)
|
|
37
|
+
.replace(/("(?:[^"\\]|\\.)*")(?=\s*[,\]}]|\s*$)/gm, '<span class="json-string">$1</span>')
|
|
38
|
+
// Keys (before colon)
|
|
39
|
+
.replace(/("(?:[^"\\]|\\.)*")(\s*:)/g, '<span class="json-key">$1</span>$2')
|
|
40
|
+
// Numbers
|
|
41
|
+
.replace(/\b(-?\d+\.?\d*)\b/g, '<span class="json-number">$1</span>')
|
|
42
|
+
// Booleans
|
|
43
|
+
.replace(/\b(true|false)\b/g, '<span class="json-boolean">$1</span>')
|
|
44
|
+
// Null
|
|
45
|
+
.replace(/\bnull\b/g, '<span class="json-null">null</span>')
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get the type of a JSON value for display
|
|
51
|
+
* @param value - The value to check
|
|
52
|
+
* @returns Type name: 'string', 'number', 'boolean', 'null', 'array', 'object'
|
|
53
|
+
*/
|
|
54
|
+
export function getJsonValueType(value: unknown): JsonValueType {
|
|
55
|
+
if (value === null) return 'null'
|
|
56
|
+
if (Array.isArray(value)) return 'array'
|
|
57
|
+
return typeof value as JsonValueType
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get a short preview of a JSON value
|
|
62
|
+
* @param value - The value to preview
|
|
63
|
+
* @param maxLength - Maximum length for string previews
|
|
64
|
+
* @returns Short preview text
|
|
65
|
+
*/
|
|
66
|
+
export function getJsonPreview(value: unknown, maxLength = 50): string {
|
|
67
|
+
const type = getJsonValueType(value)
|
|
68
|
+
|
|
69
|
+
switch (type) {
|
|
70
|
+
case 'string': {
|
|
71
|
+
const strValue = value as string
|
|
72
|
+
const preview = strValue.substring(0, maxLength)
|
|
73
|
+
return preview + (strValue.length > maxLength ? '...' : '')
|
|
74
|
+
}
|
|
75
|
+
case 'array': {
|
|
76
|
+
const arrValue = value as unknown[]
|
|
77
|
+
return `[${arrValue.length} item${arrValue.length !== 1 ? 's' : ''}]`
|
|
78
|
+
}
|
|
79
|
+
case 'object': {
|
|
80
|
+
const objValue = value as Record<string, unknown>
|
|
81
|
+
const keyCount = Object.keys(objValue).length
|
|
82
|
+
return `{${keyCount} key${keyCount !== 1 ? 's' : ''}}`
|
|
83
|
+
}
|
|
84
|
+
case 'null':
|
|
85
|
+
return 'null'
|
|
86
|
+
case 'boolean':
|
|
87
|
+
return String(value)
|
|
88
|
+
case 'number':
|
|
89
|
+
return String(value)
|
|
90
|
+
default:
|
|
91
|
+
return String(value)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Safely parse JSON with error handling
|
|
97
|
+
* @param text - JSON text to parse
|
|
98
|
+
* @returns Parse result with value and error
|
|
99
|
+
*/
|
|
100
|
+
export function safeJsonParse<T = unknown>(text: string): SafeJsonParseResult<T> {
|
|
101
|
+
if (!text || !text.trim()) {
|
|
102
|
+
return { value: null, error: null }
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
return { value: JSON.parse(text) as T, error: null }
|
|
106
|
+
} catch (e) {
|
|
107
|
+
return { value: null, error: (e as Error).message }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* CSS class names for JSON syntax highlighting
|
|
113
|
+
* Import these in your component's style section
|
|
114
|
+
*/
|
|
115
|
+
export const JSON_SYNTAX_CLASSES = `
|
|
116
|
+
.json-key { color: #881391; }
|
|
117
|
+
.json-string { color: #1a1aa6; }
|
|
118
|
+
.json-number { color: #1c00cf; }
|
|
119
|
+
.json-boolean { color: #0d22aa; font-weight: 500; }
|
|
120
|
+
.json-null { color: #808080; font-style: italic; }
|
|
121
|
+
.json-placeholder { color: var(--p-surface-400); }
|
|
122
|
+
`
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useLayoutResolver - Automatic layout resolution for entity pages
|
|
3
|
+
*
|
|
4
|
+
* Resolves the appropriate layout component based on:
|
|
5
|
+
* 1. Route meta: `{ meta: { layout: 'list' } }` (highest priority)
|
|
6
|
+
* 2. Page name convention: `*ListPage` -> ListLayout, `*EditPage` -> FormLayout
|
|
7
|
+
* 3. Explicit prop: `layout="list"` passed to component
|
|
8
|
+
*
|
|
9
|
+
* Layout types:
|
|
10
|
+
* - 'list' -> ListLayout (entity list pages)
|
|
11
|
+
* - 'form' -> FormLayout (create/edit pages)
|
|
12
|
+
* - 'dashboard' -> DashboardLayout (dashboard pages)
|
|
13
|
+
* - 'base' -> BaseLayout (generic pages)
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* ```ts
|
|
17
|
+
* const { layoutComponent, layoutType, resolveLayout } = useLayoutResolver()
|
|
18
|
+
*
|
|
19
|
+
* // In template:
|
|
20
|
+
* <component :is="layoutComponent">
|
|
21
|
+
* <template #main>...</template>
|
|
22
|
+
* </component>
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* Or with explicit layout prop:
|
|
26
|
+
* ```ts
|
|
27
|
+
* const { layoutComponent } = useLayoutResolver({ layout: 'list' })
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
import { computed, inject, getCurrentInstance, type ComputedRef, type Component } from 'vue'
|
|
31
|
+
import { useRoute } from 'vue-router'
|
|
32
|
+
|
|
33
|
+
// Layout type constants
|
|
34
|
+
export const LAYOUT_TYPES = {
|
|
35
|
+
LIST: 'list',
|
|
36
|
+
FORM: 'form',
|
|
37
|
+
DASHBOARD: 'dashboard',
|
|
38
|
+
BASE: 'base',
|
|
39
|
+
} as const
|
|
40
|
+
|
|
41
|
+
export type LayoutType = (typeof LAYOUT_TYPES)[keyof typeof LAYOUT_TYPES]
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Layout components map
|
|
45
|
+
*/
|
|
46
|
+
export interface LayoutComponentsMap {
|
|
47
|
+
list?: Component | null
|
|
48
|
+
form?: Component | null
|
|
49
|
+
dashboard?: Component | null
|
|
50
|
+
base?: Component | null
|
|
51
|
+
[key: string]: Component | null | undefined
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Page name pattern for layout detection
|
|
56
|
+
*/
|
|
57
|
+
interface PageNamePattern {
|
|
58
|
+
pattern: RegExp
|
|
59
|
+
layout: LayoutType
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Page name patterns for auto-detection
|
|
63
|
+
const PAGE_NAME_PATTERNS: PageNamePattern[] = [
|
|
64
|
+
{ pattern: /List(Page)?$/i, layout: LAYOUT_TYPES.LIST },
|
|
65
|
+
{ pattern: /(Edit|Create|Form)(Page)?$/i, layout: LAYOUT_TYPES.FORM },
|
|
66
|
+
{ pattern: /Dashboard(Page)?$/i, layout: LAYOUT_TYPES.DASHBOARD },
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
// Route name patterns for auto-detection
|
|
70
|
+
const ROUTE_NAME_PATTERNS: PageNamePattern[] = [
|
|
71
|
+
{ pattern: /-list$/i, layout: LAYOUT_TYPES.LIST },
|
|
72
|
+
{ pattern: /-(edit|create)$/i, layout: LAYOUT_TYPES.FORM },
|
|
73
|
+
{ pattern: /-dashboard$/i, layout: LAYOUT_TYPES.DASHBOARD },
|
|
74
|
+
// Common patterns without suffix
|
|
75
|
+
{ pattern: /^dashboard$/i, layout: LAYOUT_TYPES.DASHBOARD },
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Resolve layout type from component name
|
|
80
|
+
* @param componentName - Vue component name
|
|
81
|
+
* @returns Layout type or null
|
|
82
|
+
*/
|
|
83
|
+
function resolveFromComponentName(componentName: string | null | undefined): LayoutType | null {
|
|
84
|
+
if (!componentName) return null
|
|
85
|
+
|
|
86
|
+
for (const { pattern, layout } of PAGE_NAME_PATTERNS) {
|
|
87
|
+
if (pattern.test(componentName)) {
|
|
88
|
+
return layout
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return null
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Resolve layout type from route name
|
|
96
|
+
* @param routeName - Route name
|
|
97
|
+
* @returns Layout type or null
|
|
98
|
+
*/
|
|
99
|
+
function resolveFromRouteName(routeName: string | null | undefined): LayoutType | null {
|
|
100
|
+
if (!routeName) return null
|
|
101
|
+
|
|
102
|
+
for (const { pattern, layout } of ROUTE_NAME_PATTERNS) {
|
|
103
|
+
if (pattern.test(routeName)) {
|
|
104
|
+
return layout
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return null
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Options for useLayoutResolver
|
|
112
|
+
*/
|
|
113
|
+
export interface UseLayoutResolverOptions {
|
|
114
|
+
/** Explicit layout type override */
|
|
115
|
+
layout?: LayoutType
|
|
116
|
+
/** Default layout if none detected (default: 'base') */
|
|
117
|
+
default?: LayoutType
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Resolution context for resolveLayout
|
|
122
|
+
*/
|
|
123
|
+
export interface LayoutResolutionContext {
|
|
124
|
+
routeMeta?: { layout?: LayoutType }
|
|
125
|
+
componentName?: string
|
|
126
|
+
routeName?: string
|
|
127
|
+
explicit?: LayoutType
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Return type for useLayoutResolver
|
|
132
|
+
*/
|
|
133
|
+
export interface UseLayoutResolverReturn {
|
|
134
|
+
/** Current resolved layout type */
|
|
135
|
+
layoutType: ComputedRef<LayoutType>
|
|
136
|
+
/** Layout component for current type (if injected) */
|
|
137
|
+
layoutComponent: ComputedRef<Component | null>
|
|
138
|
+
/** Check if specific layout type is active */
|
|
139
|
+
isLayout: (type: LayoutType) => boolean
|
|
140
|
+
/** Manually resolve layout for given context */
|
|
141
|
+
resolveLayout: (context?: LayoutResolutionContext) => LayoutType
|
|
142
|
+
/** Layout type constants */
|
|
143
|
+
LAYOUT_TYPES: typeof LAYOUT_TYPES
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* useLayoutResolver composable
|
|
148
|
+
*
|
|
149
|
+
* @param options - Configuration options
|
|
150
|
+
* @returns Layout resolution utilities
|
|
151
|
+
*/
|
|
152
|
+
export function useLayoutResolver(options: UseLayoutResolverOptions = {}): UseLayoutResolverReturn {
|
|
153
|
+
const route = useRoute()
|
|
154
|
+
const instance = getCurrentInstance()
|
|
155
|
+
|
|
156
|
+
// Get layout components from injection (provided by Kernel)
|
|
157
|
+
const layoutComponents = inject<LayoutComponentsMap | null>('qdadmLayoutComponents', null)
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Resolve the layout type based on priority:
|
|
161
|
+
* 1. Explicit option.layout
|
|
162
|
+
* 2. Route meta.layout
|
|
163
|
+
* 3. Component name pattern
|
|
164
|
+
* 4. Route name pattern
|
|
165
|
+
* 5. Default
|
|
166
|
+
*/
|
|
167
|
+
const layoutType = computed((): LayoutType => {
|
|
168
|
+
// Priority 1: Explicit layout prop/option
|
|
169
|
+
if (options.layout) {
|
|
170
|
+
return options.layout
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Priority 2: Route meta.layout
|
|
174
|
+
if (route.meta?.layout) {
|
|
175
|
+
return route.meta.layout as LayoutType
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Priority 3: Component name pattern
|
|
179
|
+
const componentName =
|
|
180
|
+
(instance?.type as { name?: string; __name?: string })?.name ||
|
|
181
|
+
(instance?.type as { name?: string; __name?: string })?.__name
|
|
182
|
+
const fromComponentName = resolveFromComponentName(componentName)
|
|
183
|
+
if (fromComponentName) {
|
|
184
|
+
return fromComponentName
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Priority 4: Route name pattern
|
|
188
|
+
const fromRouteName = resolveFromRouteName(route.name as string | undefined)
|
|
189
|
+
if (fromRouteName) {
|
|
190
|
+
return fromRouteName
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Priority 5: Default
|
|
194
|
+
return options.default || LAYOUT_TYPES.BASE
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Get the layout component for the resolved type
|
|
199
|
+
*/
|
|
200
|
+
const layoutComponent = computed((): Component | null => {
|
|
201
|
+
if (!layoutComponents) {
|
|
202
|
+
// No components injected, return null (caller handles fallback)
|
|
203
|
+
return null
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const type = layoutType.value
|
|
207
|
+
return layoutComponents[type] || layoutComponents.base || null
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Check if a specific layout type is active
|
|
212
|
+
* @param type - Layout type to check
|
|
213
|
+
* @returns True if layout type matches
|
|
214
|
+
*/
|
|
215
|
+
function isLayout(type: LayoutType): boolean {
|
|
216
|
+
return layoutType.value === type
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Manually resolve layout for a given context
|
|
221
|
+
* Useful for programmatic layout determination
|
|
222
|
+
*
|
|
223
|
+
* @param context - Resolution context
|
|
224
|
+
* @returns Resolved layout type
|
|
225
|
+
*/
|
|
226
|
+
function resolveLayout(context: LayoutResolutionContext = {}): LayoutType {
|
|
227
|
+
// Priority 1: Explicit
|
|
228
|
+
if (context.explicit) {
|
|
229
|
+
return context.explicit
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Priority 2: Route meta
|
|
233
|
+
if (context.routeMeta?.layout) {
|
|
234
|
+
return context.routeMeta.layout
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Priority 3: Component name
|
|
238
|
+
const fromComponent = resolveFromComponentName(context.componentName)
|
|
239
|
+
if (fromComponent) {
|
|
240
|
+
return fromComponent
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Priority 4: Route name
|
|
244
|
+
const fromRoute = resolveFromRouteName(context.routeName)
|
|
245
|
+
if (fromRoute) {
|
|
246
|
+
return fromRoute
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Default
|
|
250
|
+
return options.default || LAYOUT_TYPES.BASE
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
layoutType,
|
|
255
|
+
layoutComponent,
|
|
256
|
+
isLayout,
|
|
257
|
+
resolveLayout,
|
|
258
|
+
LAYOUT_TYPES,
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Layout components input for createLayoutComponents
|
|
264
|
+
*/
|
|
265
|
+
export interface LayoutComponentsInput {
|
|
266
|
+
list?: Component
|
|
267
|
+
form?: Component
|
|
268
|
+
dashboard?: Component
|
|
269
|
+
base?: Component
|
|
270
|
+
ListLayout?: Component
|
|
271
|
+
FormLayout?: Component
|
|
272
|
+
DashboardLayout?: Component
|
|
273
|
+
BaseLayout?: Component
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Create layout components map for Kernel injection
|
|
278
|
+
*
|
|
279
|
+
* @param components - Layout component imports
|
|
280
|
+
* @returns Layout components map
|
|
281
|
+
*
|
|
282
|
+
* @example
|
|
283
|
+
* ```ts
|
|
284
|
+
* import ListLayout from './components/layout/ListLayout.vue'
|
|
285
|
+
* import FormLayout from './components/layout/FormLayout.vue'
|
|
286
|
+
*
|
|
287
|
+
* const layouts = createLayoutComponents({
|
|
288
|
+
* list: ListLayout,
|
|
289
|
+
* form: FormLayout
|
|
290
|
+
* })
|
|
291
|
+
* ```
|
|
292
|
+
*/
|
|
293
|
+
export function createLayoutComponents(components: LayoutComponentsInput = {}): LayoutComponentsMap {
|
|
294
|
+
return {
|
|
295
|
+
[LAYOUT_TYPES.LIST]: components.list || components.ListLayout || null,
|
|
296
|
+
[LAYOUT_TYPES.FORM]: components.form || components.FormLayout || null,
|
|
297
|
+
[LAYOUT_TYPES.DASHBOARD]: components.dashboard || components.DashboardLayout || null,
|
|
298
|
+
[LAYOUT_TYPES.BASE]: components.base || components.BaseLayout || null,
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Route meta object with layout
|
|
304
|
+
*/
|
|
305
|
+
export interface LayoutMeta {
|
|
306
|
+
layout: LayoutType
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Get route meta configuration for layout
|
|
311
|
+
*
|
|
312
|
+
* Helper to add layout meta to route definitions
|
|
313
|
+
*
|
|
314
|
+
* @param layoutType - Layout type
|
|
315
|
+
* @returns Route meta object
|
|
316
|
+
*
|
|
317
|
+
* @example
|
|
318
|
+
* ```ts
|
|
319
|
+
* {
|
|
320
|
+
* path: 'books',
|
|
321
|
+
* name: 'book-list',
|
|
322
|
+
* component: BookList,
|
|
323
|
+
* meta: layoutMeta('list')
|
|
324
|
+
* }
|
|
325
|
+
* ```
|
|
326
|
+
*/
|
|
327
|
+
export function layoutMeta(layoutType: LayoutType): LayoutMeta {
|
|
328
|
+
return { layout: layoutType }
|
|
329
|
+
}
|